diff --git a/.github/workflows/noema-review.yml b/.github/workflows/noema-review.yml index 9be705a50c..ff648754c8 100644 --- a/.github/workflows/noema-review.yml +++ b/.github/workflows/noema-review.yml @@ -841,8 +841,8 @@ jobs: }' | gh api -X POST "repos/${TARGET_REPOSITORY}/dispatches" --input - echo "::notice::Scheduled Noema transport continuation re-dispatch for ${TARGET_REPOSITORY}#${PR_NUMBER} at ${EXPECTED_HEAD_SHA} (attempt ${NEXT_ATTEMPT})." - - name: Upload contextual-orchestrator sidecar evidence on failure - if: failure() && env.PR_NUMBER != '' + - name: Upload contextual-orchestrator sidecar evidence + if: always() && env.PR_NUMBER != '' uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: noema-sidecar-evidence diff --git a/docs/noema-sidecar-evidence-1218.md b/docs/noema-sidecar-evidence-1218.md new file mode 100644 index 0000000000..594dee4239 --- /dev/null +++ b/docs/noema-sidecar-evidence-1218.md @@ -0,0 +1,25 @@ +# Noema startup evidence for contextual-orchestrator PR #1218 + +On 2026-09-27, run `36025318452`, attempt 3, job `108389560765` +was inspected at consumer head `2fed942d378cd959250f648a16b35276279c9603`. +The job used gateway pin `767e67fbc6b881a452761f32abb69b9971b9b03b`. +Dependencies installed successfully. At 2026-09-26T12:37:08Z the sidecar +started; no health/preflight-ready confirmation followed. The job was cancelled +at 18:35:14Z, approximately six hours after admission. This does not establish +that a Noema review request or any particular provider attempt occurred. + +Artifact `10877250808` was created on 2026-09-25T17:04:14Z and belongs to an +earlier attempt. It must not be attributed to attempt 3 merely because the +workflow run ID and consumer head are equal. + +The workflow uploaded its two existing sanitized evidence files only under +`failure()`. Using `always()` preserves those same files after successful, +failed and normally cancelled execution, without collecting raw provider logs. +The pinned uploader, file allowlist, five-day retention and absent-file behavior +remain intact. A hard runner timeout may prevent cleanup/upload entirely; this +change cannot recover the missing attempt-3 evidence or prove its internal +startup cause. Gateway inference timeouts must not be invented to hide it. + +Verification: the changed workflow contract fails on the previous source; +the Noema workflow contract suite and actionlint verify the revised step. +Hosted execution and independent review remain required before integration. diff --git a/tests/test_noema_orchestrator_workflow_contract.py b/tests/test_noema_orchestrator_workflow_contract.py index 490286aacc..fd9a7e9cc7 100644 --- a/tests/test_noema_orchestrator_workflow_contract.py +++ b/tests/test_noema_orchestrator_workflow_contract.py @@ -493,19 +493,16 @@ def test_noema_review_job_has_no_job_level_timeout() -> None: ), "the two-hour-per-model allowance this bound relies on must still be documented" -def test_noema_review_uploads_sidecar_evidence_on_failure() -> None: - """A failed verdict phase ships the sanitized sidecar stderr and preflight report. - - Before this step a failed Noema run left ``artifacts=0`` (run 33981136873: - 3122 s, then HTTP 502, no per-route trace in the job log). The stderr file - is the sidecar sanitizer's bounded allowlist output -- the same file Strix - already publishes in ``strix-reports`` -- so shipping it on failure adds - diagnosis without adding exposure (#1935 follow-up). +def test_noema_review_retains_sanitized_sidecar_evidence_after_any_outcome() -> None: + """Retain existing sanitized evidence on success, failure and cancellation. + + A forced runner shutdown can still prevent upload; this contract only + removes the failure-only gate without adding raw logs or new files. """ workflow = workflow_text("noema-review.yml") - name = "Upload contextual-orchestrator sidecar evidence on failure" + name = "Upload contextual-orchestrator sidecar evidence" step = workflow_step(workflow, name) - assert "if: failure() && env.PR_NUMBER != ''" in step + assert "if: always() && env.PR_NUMBER != ''" in step strix_pin = re.search( r"actions/upload-artifact@([0-9a-f]{40})", workflow_text("strix.yml") ).group(1)