From c3dca30e3dfa1f40c5f9393f305b6a18b10c73da Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 24 Aug 2026 03:36:01 +0900 Subject: [PATCH 1/5] fix(opencode): retain adversarial fallback scope --- .../workflows/opencode-review-dispatch.yml | 2 + CHANGELOG.md | 3 ++ .../opencode-adversarial-fallback-scope.md | 41 +++++++++++++++++++ tests/test_opencode_agent_contract.py | 8 ++++ ...t_pr_review_autofix_nvidia_nim_contract.py | 2 +- 5 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 docs/doctoring/opencode-adversarial-fallback-scope.md diff --git a/.github/workflows/opencode-review-dispatch.yml b/.github/workflows/opencode-review-dispatch.yml index ce7939845..13d47026c 100644 --- a/.github/workflows/opencode-review-dispatch.yml +++ b/.github/workflows/opencode-review-dispatch.yml @@ -2439,12 +2439,14 @@ jobs: ContextualWisdomLab/.github:scripts/ci/pr_head_replay_guard.py | \ ContextualWisdomLab/.github:scripts/ci/pr_review_merge_scheduler.py | \ ContextualWisdomLab/.github:scripts/ci/run_opencode_review_model_pool.sh | \ + ContextualWisdomLab/.github:scripts/ci/adversarial_evidence.py | \ ContextualWisdomLab/.github:scripts/ci/opencode_review_normalize_output.py | \ ContextualWisdomLab/.github:scripts/ci/strix_quick_gate.sh | \ ContextualWisdomLab/.github:scripts/ci/validate_opencode_failed_check_review.sh | \ ContextualWisdomLab/.github:tests/test_changed_file_syntax_gate.py | \ ContextualWisdomLab/.github:tests/test_javascript_coverage_gate.py | \ ContextualWisdomLab/.github:tests/test_materialize_base_javascript_packages.py | \ + ContextualWisdomLab/.github:tests/test_adversarial_evidence.py | \ ContextualWisdomLab/.github:tests/test_opencode_agent_contract.py | \ ContextualWisdomLab/.github:tests/test_opencode_model_pool_runner.py | \ ContextualWisdomLab/.github:tests/test_pr_head_replay_guard.py | \ diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b0ef8d44..71ff6e0cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -55,6 +55,9 @@ Semantic Versioning where the repository publishes a release. ### Fixed +- Kept the extracted adversarial-evidence gate and its contract test inside the + central OpenCode review-process fallback scope, so changes to that trust + boundary retain the same exact-file eligibility and adversarial review path. - Publish only the sanitized cumulative Strix report tree, avoiding a later copy of relative scanner output that could reintroduce known internal warning text into uploaded security evidence. diff --git a/docs/doctoring/opencode-adversarial-fallback-scope.md b/docs/doctoring/opencode-adversarial-fallback-scope.md new file mode 100644 index 000000000..63dec0c0f --- /dev/null +++ b/docs/doctoring/opencode-adversarial-fallback-scope.md @@ -0,0 +1,41 @@ +# OpenCode adversarial fallback scope + +## Incident boundary + +The central OpenCode fallback allowlist omitted `adversarial_evidence.py` and +its contract test after the gate was extracted from an already-allowlisted +review helper. A pull request that changed the extracted trust-boundary code +therefore lost the bounded central review-process fallback solely because of +the refactor. + +## Decision + +Treat the extracted gate and its test as the same review-process unit as the +normalizer and approval gate by adding their exact repository paths to +`fallback_changed_file_allowed`. The existing +`fallback_changed_file_counts_as_core` function already classifies every +allowlisted central path except `.jules/bolt.md` as core, so no new classifier, +provider rule, credential, or approval path is needed. + +This keeps the changed control and its regression evidence inside one +assessment scope. NIST SP 800-53 Rev. 5, control SA-11, requires ongoing unit, +integration, system, or regression evaluation and evidence at the defined +depth and coverage (National Institute of Standards and Technology, 2020). +The stable OWASP Web Security Testing Guide likewise includes positive and +negative security-control requirements in the security test suite (OWASP +Foundation, 2020). + +## Verification + +`tests/test_opencode_agent_contract.py` pins both exact paths in the workflow. +The allowlist stays closed: unrelated files remain ineligible, and every merge +still requires the existing exact-head checks and independent review policy. + +## References + +National Institute of Standards and Technology. (2020). *Security and privacy +controls for information systems and organizations* (NIST Special Publication +800-53, Revision 5). https://doi.org/10.6028/NIST.SP.800-53r5 + +OWASP Foundation. (2020). *OWASP web security testing guide* (Version 4.2). +https://owasp.org/www-project-web-security-testing-guide/v42/ diff --git a/tests/test_opencode_agent_contract.py b/tests/test_opencode_agent_contract.py index e00cc5214..72d7678af 100644 --- a/tests/test_opencode_agent_contract.py +++ b/tests/test_opencode_agent_contract.py @@ -1395,6 +1395,10 @@ def test_workflow_provisions_sandbox_tool_and_reviewer_agent(): "ContextualWisdomLab/.github:scripts/ci/opencode_review_approve_gate.sh | \\" in workflow ) + assert ( + "ContextualWisdomLab/.github:scripts/ci/adversarial_evidence.py | \\" + in workflow + ) assert "scripts/ci/run_opencode_review_model_pool.sh | \\" in workflow assert ( "ContextualWisdomLab/.github:tests/test_javascript_coverage_gate.py | \\" @@ -1404,6 +1408,10 @@ def test_workflow_provisions_sandbox_tool_and_reviewer_agent(): "ContextualWisdomLab/.github:tests/test_materialize_base_javascript_packages.py | \\" in workflow ) + assert ( + "ContextualWisdomLab/.github:tests/test_adversarial_evidence.py | \\" + in workflow + ) assert "tests/test_opencode_agent_contract.py | \\" in workflow assert ( "ContextualWisdomLab/appguardrail:scripts/ci/collect_org_security_failures.py" diff --git a/tests/test_pr_review_autofix_nvidia_nim_contract.py b/tests/test_pr_review_autofix_nvidia_nim_contract.py index d0210b1ab..fc91d7b84 100644 --- a/tests/test_pr_review_autofix_nvidia_nim_contract.py +++ b/tests/test_pr_review_autofix_nvidia_nim_contract.py @@ -19,7 +19,7 @@ DOCTORING_RECORD = Path("docs/doctoring/hourly-nvidia-nim-autofix.md") CHANGELOG = Path("CHANGELOG.md") REVIEW_DISPATCH_WORKFLOW = Path(".github/workflows/opencode-review-dispatch.yml") -REVIEW_DISPATCH_BLOB_SHA = "ce7939845286be9668a01d5c640e867a8490ee5c" +REVIEW_DISPATCH_BLOB_SHA = "13d47026c7a25b33f6e29efb967a81a1835c2b4c" def _workflow_text(path: Path) -> str: From 4967b283fdb81551ea64049d92866f051eabbc68 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 23 Aug 2026 20:16:18 -0700 Subject: [PATCH 2/5] fix: bind coverage artifacts to workflow attempts (#1278) --- .../workflows/opencode-review-dispatch.yml | 66 ++++++- CHANGELOG.md | 3 + .../opencode-coverage-artifact-reruns.md | 44 +++++ tests/test_opencode_agent_contract.py | 4 + ...encode_coverage_artifact_rerun_contract.py | 187 ++++++++++++++++++ ...t_pr_review_autofix_nvidia_nim_contract.py | 2 +- 6 files changed, 301 insertions(+), 5 deletions(-) create mode 100644 docs/doctoring/opencode-coverage-artifact-reruns.md create mode 100644 tests/test_opencode_coverage_artifact_rerun_contract.py diff --git a/.github/workflows/opencode-review-dispatch.yml b/.github/workflows/opencode-review-dispatch.yml index 13d47026c..f6d15b780 100644 --- a/.github/workflows/opencode-review-dispatch.yml +++ b/.github/workflows/opencode-review-dispatch.yml @@ -222,6 +222,9 @@ jobs: permissions: contents: read id-token: write + outputs: + coverage_source_artifact_id: ${{ steps.coverage_source_upload.outputs.artifact-id }} + coverage_source_run_attempt: ${{ steps.coverage_source_attempt.outputs.run_attempt }} env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true steps: @@ -349,10 +352,23 @@ jobs: git -C "$COVERAGE_SOURCE_WORKDIR" status --short tar -cf "$COVERAGE_SOURCE_ARCHIVE" -C "$COVERAGE_SOURCE_WORKDIR" . + - name: Record coverage source workflow attempt + id: coverage_source_attempt + env: + GITHUB_RUN_ATTEMPT: ${{ github.run_attempt }} + shell: bash --noprofile --norc -e -o pipefail {0} + run: | + if ! [[ "$GITHUB_RUN_ATTEMPT" =~ ^[1-9][0-9]*$ ]]; then + echo "::error::Coverage producer workflow attempt is not a positive integer." + exit 1 + fi + printf 'run_attempt=%s\n' "$GITHUB_RUN_ATTEMPT" >>"$GITHUB_OUTPUT" + - name: Upload materialized pull request merge tree + id: coverage_source_upload uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: opencode-coverage-source + name: opencode-coverage-source-${{ github.run_attempt }} path: ${{ runner.temp }}/opencode-coverage-source.tar if-no-files-found: error retention-days: 1 @@ -431,14 +447,55 @@ jobs: if: needs.coverage-source-tree.result != 'success' run: | echo "::error::Coverage source tree could not be materialized; see the coverage-source-tree job log for the exact target repository, base SHA, head SHA, and fetch or merge failure." - exit 1 + # Continue to the unified current-attempt recovery gate for bounded + # fail-closed guidance. - - name: Download materialized pull request merge tree + - name: Verify coverage source identity for current workflow attempt + if: always() + id: coverage_source_identity + continue-on-error: true + env: + COVERAGE_SOURCE_ARTIFACT_ID: ${{ needs.coverage-source-tree.outputs.coverage_source_artifact_id }} + COVERAGE_SOURCE_RUN_ATTEMPT: ${{ needs.coverage-source-tree.outputs.coverage_source_run_attempt }} + CURRENT_RUN_ATTEMPT: ${{ github.run_attempt }} + shell: bash --noprofile --norc -e -o pipefail {0} + run: | + if ! [[ "$CURRENT_RUN_ATTEMPT" =~ ^[1-9][0-9]*$ ]] || \ + [ "$COVERAGE_SOURCE_RUN_ATTEMPT" != "$CURRENT_RUN_ATTEMPT" ]; then + echo "::error::Coverage source was not produced in current workflow attempt ${CURRENT_RUN_ATTEMPT:-missing}; producer attempt=${COVERAGE_SOURCE_RUN_ATTEMPT:-missing}." + echo "::error::Use a full rerun or a fresh repository dispatch; failed-jobs-only reruns cannot reuse prior-attempt source evidence." + exit 1 + fi + if ! [[ "$COVERAGE_SOURCE_ARTIFACT_ID" =~ ^[1-9][0-9]*$ ]]; then + echo "::error::Coverage source artifact ID is missing or malformed for current workflow attempt." + echo "::error::Use a full rerun or a fresh repository dispatch so the producer publishes current-attempt evidence." + exit 1 + fi + artifact_id=$COVERAGE_SOURCE_ARTIFACT_ID + printf 'artifact_id=%s\n' "$artifact_id" >>"$GITHUB_OUTPUT" + + - name: Download current-attempt materialized pull request merge tree + if: >- + always() + && needs.coverage-source-tree.result == 'success' + && steps.coverage_source_identity.outcome == 'success' + id: coverage_source_download + continue-on-error: true uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: - name: opencode-coverage-source + artifact-ids: ${{ steps.coverage_source_identity.outputs.artifact_id }} path: ${{ runner.temp }}/opencode-coverage-artifact + - name: Report missing current-attempt coverage source + if: always() && (needs.coverage-source-tree.result != 'success' || steps.coverage_source_identity.outcome != 'success' || steps.coverage_source_download.outcome != 'success') + env: + GITHUB_RUN_ATTEMPT: ${{ github.run_attempt }} + shell: bash --noprofile --norc -e -o pipefail {0} + run: | + echo "::error::Coverage source evidence is unavailable for workflow run attempt ${GITHUB_RUN_ATTEMPT}; a failed-jobs-only rerun cannot safely reconstruct or reuse source evidence from another attempt." + echo "::error::Use a full rerun or a fresh repository dispatch so coverage-source-tree uploads exact current-attempt evidence." + exit 1 + - name: Prepare pull request merge tree for coverage measurement env: COVERAGE_SOURCE_ARCHIVE: ${{ runner.temp }}/opencode-coverage-artifact/opencode-coverage-source.tar @@ -2447,6 +2504,7 @@ jobs: ContextualWisdomLab/.github:tests/test_javascript_coverage_gate.py | \ ContextualWisdomLab/.github:tests/test_materialize_base_javascript_packages.py | \ ContextualWisdomLab/.github:tests/test_adversarial_evidence.py | \ + ContextualWisdomLab/.github:tests/test_opencode_coverage_artifact_rerun_contract.py | \ ContextualWisdomLab/.github:tests/test_opencode_agent_contract.py | \ ContextualWisdomLab/.github:tests/test_opencode_model_pool_runner.py | \ ContextualWisdomLab/.github:tests/test_pr_head_replay_guard.py | \ diff --git a/CHANGELOG.md b/CHANGELOG.md index 52cccde71..99dc4f7d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -60,6 +60,9 @@ Semantic Versioning where the repository publishes a release. ### Fixed +- Bound OpenCode coverage source evidence to the current workflow attempt and + immutable upload artifact ID, failing closed with full-rerun guidance when a + failed-jobs-only rerun cannot obtain exact current-attempt evidence. - Kept the extracted adversarial-evidence gate and its contract test inside the central OpenCode review-process fallback scope, so changes to that trust boundary retain the same exact-file eligibility and adversarial review path. diff --git a/docs/doctoring/opencode-coverage-artifact-reruns.md b/docs/doctoring/opencode-coverage-artifact-reruns.md new file mode 100644 index 000000000..da7814dda --- /dev/null +++ b/docs/doctoring/opencode-coverage-artifact-reruns.md @@ -0,0 +1,44 @@ +# OpenCode coverage artifact reruns + +## Customer-operability decision + +OpenCode coverage now accepts only the immutable artifact identifier emitted by +`actions/upload-artifact` in the current GitHub Actions workflow attempt. The +producer exports both that identifier and `github.run_attempt`; the +credential-free coverage consumer checks that the producer attempt equals its +own attempt before downloading by ID. + +This closes an evidence-integrity gap in failed-jobs-only reruns. GitHub can +reuse successful job outputs from the earlier attempt, while artifacts can be +expired or unavailable independently. A static artifact name therefore cannot +prove that coverage inspected the exact source produced for the current +attempt. Missing, malformed, expired, or prior-attempt evidence now fails +closed and tells an operator to use a full rerun or a fresh repository +dispatch. The existing one-day retention window remains bounded; no lookup or +fallback to an earlier attempt is permitted. + +## Verification and rollback + +Repository tests parse the complete producer and consumer job blocks. They +require the attempt-scoped artifact name, immutable upload output, current +attempt comparison, exact-ID download, credential-free consumer permissions, +and actionable recovery message. The central workflow's ordinary full quality +gate exercises the new contract at 100% statement, branch, and docstring +coverage. + +Rollback is a normal revert of the workflow, contract test, and fallback-scope +entry. Operators must not restore name-based or prior-attempt artifact lookup; +until a replacement contract is available, a missing producer must continue to +fail closed. + +## References + +GitHub. (n.d.). *Re-running workflows and jobs*. Retrieved August 24, 2026, +from https://docs.github.com/en/actions/how-tos/manage-workflow-runs/re-run-workflows-and-jobs + +GitHub. (n.d.). *REST API endpoints for GitHub Actions artifacts*. Retrieved +August 24, 2026, from +https://docs.github.com/en/rest/actions/artifacts?apiVersion=2026-03-10 + +GitHub. (n.d.). *upload-artifact* [Computer software]. Retrieved August 24, +2026, from https://github.com/actions/upload-artifact/blob/main/README.md diff --git a/tests/test_opencode_agent_contract.py b/tests/test_opencode_agent_contract.py index 72d7678af..aa76f229c 100644 --- a/tests/test_opencode_agent_contract.py +++ b/tests/test_opencode_agent_contract.py @@ -1412,6 +1412,10 @@ def test_workflow_provisions_sandbox_tool_and_reviewer_agent(): "ContextualWisdomLab/.github:tests/test_adversarial_evidence.py | \\" in workflow ) + assert ( + "ContextualWisdomLab/.github:tests/test_opencode_coverage_artifact_rerun_contract.py | \\" + in workflow + ) assert "tests/test_opencode_agent_contract.py | \\" in workflow assert ( "ContextualWisdomLab/appguardrail:scripts/ci/collect_org_security_failures.py" diff --git a/tests/test_opencode_coverage_artifact_rerun_contract.py b/tests/test_opencode_coverage_artifact_rerun_contract.py new file mode 100644 index 000000000..7a891ac36 --- /dev/null +++ b/tests/test_opencode_coverage_artifact_rerun_contract.py @@ -0,0 +1,187 @@ +"""Contracts for rerun-safe OpenCode coverage artifact handoff.""" + +from pathlib import Path + + +WORKFLOW_PATH = Path(".github/workflows/opencode-review-dispatch.yml") +TEMPORARY_REPAIR_GLOBS = ( + ".github/opencode-attempt-scoped-coverage-artifact*.trigger", + ".github/pr812*.trigger", + ".github/workflows/*opencode*artifact*materializ*.yml", + ".github/workflows/*opencode*artifact*repair*.yml", + ".github/workflows/pr812-finalize*.yml", + "scripts/ci/*opencode*artifact*patch*.py", +) + + +def _workflow_text() -> str: + """Return the protected OpenCode repository-dispatch workflow source.""" + return WORKFLOW_PATH.read_text(encoding="utf-8") + + +def _job_block(workflow: str, job_name: str, next_job_name: str) -> str: + """Return one top-level workflow job block bounded by the next job.""" + start = workflow.index(f" {job_name}:\n") + end = workflow.index(f"\n {next_job_name}:\n", start) + return workflow[start:end] + + +def _step_block(job: str, step_name: str, next_step_name: str) -> str: + """Return one workflow step bounded by the following named step.""" + start = job.index(f" - name: {step_name}\n") + end = job.index(f"\n - name: {next_step_name}\n", start) + return job[start:end] + + +def test_coverage_source_artifact_is_attempt_scoped_and_downloaded_by_id() -> None: + """Bind every producer attempt to its immutable uploaded artifact ID.""" + workflow = _workflow_text() + source_job = _job_block(workflow, "coverage-source-tree", "coverage-evidence") + evidence_job = _job_block(workflow, "coverage-evidence", "opencode-review-target") + + assert ( + "coverage_source_artifact_id: " + "${{ steps.coverage_source_upload.outputs.artifact-id }}" + in source_job + ) + assert "id: coverage_source_upload" in source_job + assert "name: opencode-coverage-source-${{ github.run_attempt }}" in source_job + assert "retention-days: 1" in source_job + + identity = _step_block( + evidence_job, + "Verify coverage source identity for current workflow attempt", + "Download current-attempt materialized pull request merge tree", + ) + download = _step_block( + evidence_job, + "Download current-attempt materialized pull request merge tree", + "Report missing current-attempt coverage source", + ) + assert "id: coverage_source_identity" in identity + assert ( + "COVERAGE_SOURCE_ARTIFACT_ID: " + "${{ needs.coverage-source-tree.outputs.coverage_source_artifact_id }}" + in identity + ) + assert '[[ "$COVERAGE_SOURCE_ARTIFACT_ID" =~ ^[1-9][0-9]*$ ]]' in identity + assert "artifact_id=$COVERAGE_SOURCE_ARTIFACT_ID" in identity + assert ( + "artifact-ids: ${{ steps.coverage_source_identity.outputs.artifact_id }}" + in download + ) + assert ( + "artifact-ids: " + "${{ needs.coverage-source-tree.outputs.coverage_source_artifact_id }}" + not in download + ) + assert "name: opencode-coverage-source\n" not in download + + +def test_coverage_source_requires_current_producer_attempt() -> None: + """Reject reused producer output when a selective rerun advances the attempt.""" + workflow = _workflow_text() + source_job = _job_block(workflow, "coverage-source-tree", "coverage-evidence") + evidence_job = _job_block(workflow, "coverage-evidence", "opencode-review-target") + identity = _step_block( + evidence_job, + "Verify coverage source identity for current workflow attempt", + "Download current-attempt materialized pull request merge tree", + ) + + assert ( + "coverage_source_run_attempt: " + "${{ steps.coverage_source_attempt.outputs.run_attempt }}" + in source_job + ) + assert "id: coverage_source_attempt" in source_job + assert "GITHUB_RUN_ATTEMPT: ${{ github.run_attempt }}" in source_job + assert "run_attempt=%s" in source_job + + assert ( + "COVERAGE_SOURCE_RUN_ATTEMPT: " + "${{ needs.coverage-source-tree.outputs.coverage_source_run_attempt }}" + in identity + ) + assert "CURRENT_RUN_ATTEMPT: ${{ github.run_attempt }}" in identity + assert '[ "$COVERAGE_SOURCE_RUN_ATTEMPT" != "$CURRENT_RUN_ATTEMPT" ]' in identity + assert "failed-jobs-only reruns cannot reuse prior-attempt source evidence" in identity + assert "full rerun or a fresh repository dispatch" in identity + + guard_index = evidence_job.index( + "- name: Verify coverage source identity for current workflow attempt" + ) + download_index = evidence_job.index( + "- name: Download current-attempt materialized pull request merge tree" + ) + assert guard_index < download_index + + +def test_missing_or_expired_artifact_fails_with_bounded_recovery_guidance() -> None: + """Keep fail-closed recovery reachable after producer or download failures.""" + workflow = _workflow_text() + evidence_job = _job_block(workflow, "coverage-evidence", "opencode-review-target") + producer_failure = _step_block( + evidence_job, + "Report coverage source materialization failure", + "Verify coverage source identity for current workflow attempt", + ) + identity = _step_block( + evidence_job, + "Verify coverage source identity for current workflow attempt", + "Download current-attempt materialized pull request merge tree", + ) + download = _step_block( + evidence_job, + "Download current-attempt materialized pull request merge tree", + "Report missing current-attempt coverage source", + ) + recovery = _step_block( + evidence_job, + "Report missing current-attempt coverage source", + "Prepare pull request merge tree for coverage measurement", + ) + + assert "if: needs.coverage-source-tree.result != 'success'" in producer_failure + assert "exit 1" not in producer_failure + assert "id: coverage_source_identity" in identity + assert "if: always()" in identity + assert "continue-on-error: true" in identity + assert "id: coverage_source_download" in download + assert "continue-on-error: true" in download + assert "needs.coverage-source-tree.result == 'success'" in download + assert "steps.coverage_source_identity.outcome == 'success'" in download + assert "if: always() && (" in recovery + assert "needs.coverage-source-tree.result != 'success'" in recovery + assert "steps.coverage_source_identity.outcome != 'success'" in recovery + assert "steps.coverage_source_download.outcome != 'success'" in recovery + assert "failed-jobs-only rerun" in recovery + assert "full rerun or a fresh repository dispatch" in recovery + assert "GITHUB_RUN_ATTEMPT" in recovery + assert "exit 1" in recovery + assert "list-artifacts" not in identity + download + recovery + + +def test_coverage_consumer_remains_credential_free() -> None: + """Keep repository and OIDC credentials outside the untrusted-test job.""" + workflow = _workflow_text() + evidence_job = _job_block(workflow, "coverage-evidence", "opencode-review-target") + permissions = evidence_job.split(" outputs:\n", 1)[0] + + assert "actions: read" in permissions + assert "contents:" not in permissions + assert "id-token:" not in permissions + assert "secrets." not in evidence_job + assert "GH_TOKEN:" not in evidence_job + + +def test_temporary_branch_writers_are_absent_from_final_tree() -> None: + """Reject versioned or renamed materializers and branch finalizers.""" + unexpected = sorted( + { + str(path) + for pattern in TEMPORARY_REPAIR_GLOBS + for path in Path(".").glob(pattern) + } + ) + assert unexpected == [] diff --git a/tests/test_pr_review_autofix_nvidia_nim_contract.py b/tests/test_pr_review_autofix_nvidia_nim_contract.py index fc91d7b84..090e12a3f 100644 --- a/tests/test_pr_review_autofix_nvidia_nim_contract.py +++ b/tests/test_pr_review_autofix_nvidia_nim_contract.py @@ -19,7 +19,7 @@ DOCTORING_RECORD = Path("docs/doctoring/hourly-nvidia-nim-autofix.md") CHANGELOG = Path("CHANGELOG.md") REVIEW_DISPATCH_WORKFLOW = Path(".github/workflows/opencode-review-dispatch.yml") -REVIEW_DISPATCH_BLOB_SHA = "13d47026c7a25b33f6e29efb967a81a1835c2b4c" +REVIEW_DISPATCH_BLOB_SHA = "f6d15b78004cd0e2fa0c8c59a05fc834fd91b79a" def _workflow_text(path: Path) -> str: From 359ac2ace22efcd6680fd1c7b93e0794d84eab3f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 24 Aug 2026 12:37:53 +0900 Subject: [PATCH 3/5] test(opencode): track attempt-scoped coverage download --- scripts/ci/test_strix_quick_gate.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/ci/test_strix_quick_gate.sh b/scripts/ci/test_strix_quick_gate.sh index bf0a8693e..a1e1333be 100755 --- a/scripts/ci/test_strix_quick_gate.sh +++ b/scripts/ci/test_strix_quick_gate.sh @@ -929,7 +929,7 @@ assert_file_contains "$REPO_ROOT/scripts/ci/run_opencode_review_model_pool.sh" ' assert_file_contains "$workflow_file" "Materialize pull request merge tree for coverage measurement" "required OpenCode reviews measure coverage instead of approving skipped coverage evidence" assert_file_contains "$workflow_file" "Exchange OpenCode app token for target repository coverage reads" "coverage source materialization can read private target repositories during central manual dispatch" assert_file_contains "$workflow_file" "Upload materialized pull request merge tree" "coverage source materialization passes only a prepared merge tree artifact to the PR-head coverage job" - assert_file_contains "$workflow_file" "Download materialized pull request merge tree" "coverage evidence consumes the prepared merge tree artifact without target-repository credentials" + assert_file_contains "$workflow_file" "Download current-attempt materialized pull request merge tree" "coverage evidence consumes the prepared current-attempt merge tree artifact without target-repository credentials" assert_file_contains "$workflow_file" "Report coverage source materialization failure" "coverage evidence logs source materialization failures as the coverage blocker" local coverage_merge_tree_step coverage_merge_tree_step="$( From 5fff8c8a84dfe0aafa86bb69eb1be2dc06453742 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 24 Aug 2026 12:42:15 +0900 Subject: [PATCH 4/5] fix(strix): align coverage artifact contract --- CHANGELOG.md | 3 +++ docs/doctoring/opencode-adversarial-fallback-scope.md | 3 +++ scripts/ci/test_strix_quick_gate.sh | 2 +- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 99dc4f7d5..00a297b68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -60,6 +60,9 @@ Semantic Versioning where the repository publishes a release. ### Fixed +- Synchronized the Strix quick-gate contract with the current-attempt OpenCode + coverage-artifact download step, keeping the self-test aligned with the + exact-head artifact provenance boundary. - Bound OpenCode coverage source evidence to the current workflow attempt and immutable upload artifact ID, failing closed with full-rerun guidance when a failed-jobs-only rerun cannot obtain exact current-attempt evidence. diff --git a/docs/doctoring/opencode-adversarial-fallback-scope.md b/docs/doctoring/opencode-adversarial-fallback-scope.md index 63dec0c0f..4689886ce 100644 --- a/docs/doctoring/opencode-adversarial-fallback-scope.md +++ b/docs/doctoring/opencode-adversarial-fallback-scope.md @@ -30,6 +30,9 @@ Foundation, 2020). `tests/test_opencode_agent_contract.py` pins both exact paths in the workflow. The allowlist stays closed: unrelated files remain ineligible, and every merge still requires the existing exact-head checks and independent review policy. +The Strix quick-gate self-test names the current-attempt coverage artifact +download step, so a coverage-artifact hardening rename cannot silently leave +the protected workflow contract stale. ## References diff --git a/scripts/ci/test_strix_quick_gate.sh b/scripts/ci/test_strix_quick_gate.sh index bf0a8693e..f02f87da3 100755 --- a/scripts/ci/test_strix_quick_gate.sh +++ b/scripts/ci/test_strix_quick_gate.sh @@ -929,7 +929,7 @@ assert_file_contains "$REPO_ROOT/scripts/ci/run_opencode_review_model_pool.sh" ' assert_file_contains "$workflow_file" "Materialize pull request merge tree for coverage measurement" "required OpenCode reviews measure coverage instead of approving skipped coverage evidence" assert_file_contains "$workflow_file" "Exchange OpenCode app token for target repository coverage reads" "coverage source materialization can read private target repositories during central manual dispatch" assert_file_contains "$workflow_file" "Upload materialized pull request merge tree" "coverage source materialization passes only a prepared merge tree artifact to the PR-head coverage job" - assert_file_contains "$workflow_file" "Download materialized pull request merge tree" "coverage evidence consumes the prepared merge tree artifact without target-repository credentials" + assert_file_contains "$workflow_file" "Download current-attempt materialized pull request merge tree" "coverage evidence consumes the current-attempt prepared merge tree artifact without target-repository credentials" assert_file_contains "$workflow_file" "Report coverage source materialization failure" "coverage evidence logs source materialization failures as the coverage blocker" local coverage_merge_tree_step coverage_merge_tree_step="$( From 9fd37e272a28c2761ba09d4697c3e4d9a1ebf5e0 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 24 Aug 2026 12:56:37 +0900 Subject: [PATCH 5/5] fix(strix): classify litellm timeout fallback signals --- scripts/ci/strix_quick_gate.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/ci/strix_quick_gate.sh b/scripts/ci/strix_quick_gate.sh index 337373001..9cb5a5d5f 100755 --- a/scripts/ci/strix_quick_gate.sh +++ b/scripts/ci/strix_quick_gate.sh @@ -3072,6 +3072,10 @@ is_timeout_error() { return 0 fi + if grep -Fq 'litellm.Timeout' "$STRIX_LOG"; then + return 0 + fi + if grep -Fq 'Strix run timed out after' "$STRIX_LOG"; then return 0 fi