From 9bfa5b16b33b8172b0101ab1c2b21c76d726f212 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 31 Aug 2026 05:45:58 +0000 Subject: [PATCH 1/3] Wire test_opencode_fact_gate_contract.sh into CI The 15-assertion contract test guarding fact-gate evidence strings in opencode-review-dispatch.yml was invoked by zero workflows, scripts, or tests -- unlike its sibling test_strix_quick_gate.sh, which is genuinely wired into three workflows. Add opencode-fact-gate-quality-ci.yml to run it on every PR touching either file, mirroring how strix-changed-path-quality-ci.yml invokes test_strix_quick_gate.sh, and add a contract test pinning that wiring plus executing the script directly so drift fails the repo's own pytest suite too. Co-Authored-By: Claude Sonnet 5 --- .../opencode-fact-gate-quality-ci.yml | 38 ++++++++ CHANGELOG.md | 10 ++ ..._opencode_fact_gate_quality_ci_contract.py | 93 +++++++++++++++++++ 3 files changed, 141 insertions(+) create mode 100644 .github/workflows/opencode-fact-gate-quality-ci.yml create mode 100644 tests/test_opencode_fact_gate_quality_ci_contract.py diff --git a/.github/workflows/opencode-fact-gate-quality-ci.yml b/.github/workflows/opencode-fact-gate-quality-ci.yml new file mode 100644 index 0000000000..6300078cfa --- /dev/null +++ b/.github/workflows/opencode-fact-gate-quality-ci.yml @@ -0,0 +1,38 @@ +name: OpenCode Fact Gate Quality CI + +on: + pull_request: + paths: + - ".github/workflows/opencode-fact-gate-quality-ci.yml" + - ".github/workflows/opencode-review-dispatch.yml" + - "scripts/ci/test_opencode_fact_gate_contract.sh" + +permissions: + contents: read + +concurrency: + group: opencode-fact-gate-quality-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + fact-gate-contract: + name: fact-gate-contract + runs-on: ubuntu-24.04 + timeout-minutes: 10 + steps: + - name: Harden runner + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 + with: + egress-policy: audit + + - name: Checkout exact pull request head + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: ${{ github.event.pull_request.head.sha }} + persist-credentials: false + + - name: Verify OpenCode fact-gate evidence contract + shell: bash --noprofile --norc -e -o pipefail {0} + run: | + test "$(git rev-parse HEAD)" = "${{ github.event.pull_request.head.sha }}" + bash scripts/ci/test_opencode_fact_gate_contract.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index 256b0cdf94..057708a945 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,16 @@ this file. The format follows Keep a Changelog, and versioned releases follow Semantic Versioning where the repository publishes a release. ## [Unreleased] +- Wire `scripts/ci/test_opencode_fact_gate_contract.sh` (the 15-assertion + regression contract for the fact-gate evidence strings in + `opencode-review-dispatch.yml`) into CI: a new + `.github/workflows/opencode-fact-gate-quality-ci.yml` runs it on every pull + request touching that workflow or the contract script itself, mirroring how + `test_strix_quick_gate.sh` is invoked by + `strix-changed-path-quality-ci.yml`. Previously the script existed and + passed but was never invoked by any workflow, script, or test, so its + assertions enforced nothing; `tests/test_opencode_fact_gate_quality_ci_contract.py` + now also pins the wiring and runs the contract script directly. - Fix a dangling reference #1468 left in `docs/product-goal-directive.md` (flagged by Devin Review on that PR): the standing operating directive still named the removed `free_family_diversity` evidence field instead of diff --git a/tests/test_opencode_fact_gate_quality_ci_contract.py b/tests/test_opencode_fact_gate_quality_ci_contract.py new file mode 100644 index 0000000000..06b711faf4 --- /dev/null +++ b/tests/test_opencode_fact_gate_quality_ci_contract.py @@ -0,0 +1,93 @@ +"""Permanent contract wiring the OpenCode fact-gate regression test into CI. + +``scripts/ci/test_opencode_fact_gate_contract.sh`` asserts that +``.github/workflows/opencode-review-dispatch.yml`` still carries the +fact-gate evidence strings that stop OpenCode review from claiming a repo +path, evidence excerpt, or reviewer thread is unavailable without proof. A +real 15-assertion contract that no workflow ever invokes enforces nothing: +this module pins that ``opencode-fact-gate-quality-ci.yml`` actually runs it +on every pull request that could change either file, and executes the +regression contract directly so a change to either file that breaks it fails +this repository's own test suite too, not only the dedicated workflow. +""" + +from __future__ import annotations + +import subprocess +from pathlib import Path + +_REPOSITORY_ROOT = Path(__file__).resolve().parents[1] +_CONTRACT_SCRIPT_PATH = ( + _REPOSITORY_ROOT / "scripts/ci/test_opencode_fact_gate_contract.sh" +) +_DISPATCH_WORKFLOW_PATH = ( + _REPOSITORY_ROOT / ".github/workflows/opencode-review-dispatch.yml" +) +_QUALITY_WORKFLOW_PATH = ( + _REPOSITORY_ROOT / ".github/workflows/opencode-fact-gate-quality-ci.yml" +) + + +def _quality_workflow_text() -> str: + """Return the workflow that wires the fact-gate contract into CI.""" + + return _QUALITY_WORKFLOW_PATH.read_text(encoding="utf-8") + + +def test_quality_workflow_watches_the_contract_and_the_dispatch_workflow() -> None: + """A change to either watched file must retrigger this quality gate.""" + + quality_workflow = _quality_workflow_text() + assert ( + ' - ".github/workflows/opencode-review-dispatch.yml"\n' + in quality_workflow + ) + assert ( + ' - "scripts/ci/test_opencode_fact_gate_contract.sh"\n' + in quality_workflow + ) + + +def test_quality_workflow_actually_invokes_the_contract_script() -> None: + """The workflow must execute the contract, not merely reference it.""" + + quality_workflow = _quality_workflow_text() + assert ( + "bash scripts/ci/test_opencode_fact_gate_contract.sh\n" in quality_workflow + ) + assert 'ref: ${{ github.event.pull_request.head.sha }}' in quality_workflow + + +def test_quality_workflow_watched_paths_resolve_to_repository_files() -> None: + """Every watched path in the quality workflow must exist in the repo.""" + + quality_workflow = _quality_workflow_text() + watched_section = quality_workflow.split(" paths:\n", 1)[1].split( + "\n\npermissions:\n", 1 + )[0] + watched_paths = [ + line.strip()[2:].strip('"') + for line in watched_section.splitlines() + if line.strip().startswith("- ") + ] + + assert watched_paths + assert str(_CONTRACT_SCRIPT_PATH.relative_to(_REPOSITORY_ROOT)) in watched_paths + assert str(_DISPATCH_WORKFLOW_PATH.relative_to(_REPOSITORY_ROOT)) in watched_paths + for relative_path in watched_paths: + assert (_REPOSITORY_ROOT / relative_path).is_file(), relative_path + + +def test_fact_gate_contract_script_currently_passes() -> None: + """The regression contract the workflow runs must pass right now too.""" + + result = subprocess.run( + ["bash", str(_CONTRACT_SCRIPT_PATH)], + check=False, + capture_output=True, + text=True, + cwd=_REPOSITORY_ROOT, + ) + + assert result.returncode == 0, result.stderr + assert "OpenCode fact-gate contract OK" in result.stdout From 25d7e4220d26e7282bb1144e399a466ed4507d06 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 31 Aug 2026 09:26:28 +0000 Subject: [PATCH 2/3] fix(ci): bound required-workflow-bootstrap awk extraction to its own job Ports the identical fix from #1506 into this branch. This PR's exact-head-path-policy check runs its own head-branch copy of scripts/ci/test_strix_quick_gate.sh (plain `pull_request` trigger in strix-changed-path-quality-ci.yml, not pull_request_target), so the pre-existing main-branch bug is not fixed here just by #1506 merging into main -- it needs porting into this branch directly. Root cause: assert_opencode_review_uses_codegraph_and_contextual_orchestrator extracted the required-workflow-bootstrap job block from opencode-review.yml with awk '/^ required-workflow-bootstrap:$/,/^[^ ]/'. Every job key in that workflow is indented 2 spaces (never column 0), so the end pattern never matched until EOF, sweeping an unrelated `if:` line from a later job (added by already-merged PR #1497) into the "block" and failing the assertion on unrelated content. Fixed by using an explicit state flag so the end pattern (`^ [A-Za-z0-9_-]+:`) is only tested starting on the line after the start match, correctly bounding the block to just its own lines. See ContextualWisdomLab/.github#1506 for the full root-cause writeup and validation against origin/main. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_015Gs7KmNvH75nxz1sL8mKjw --- 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 4053f4fd53..1fc45a34b9 100644 --- a/scripts/ci/test_strix_quick_gate.sh +++ b/scripts/ci/test_strix_quick_gate.sh @@ -522,7 +522,7 @@ assert_opencode_review_uses_codegraph_and_contextual_orchestrator() { assert_file_not_contains "$workflow_file" "Wait for trusted OpenCode approval review" "opencode pull_request bridge was removed to avoid duplicate required-check resource use" assert_file_not_contains "$workflow_file" "Trusted OpenCode requested changes for head" "opencode pull_request bridge no longer reconsumes stale trusted review state" assert_file_not_contains "$workflow_file" "github.event.pull_request.number == 240" "opencode review workflow must not hard-code repository-specific PR bypasses" - if awk '/^ required-workflow-bootstrap:$/,/^[^ ]/' "$bootstrap_file" | grep -q '^[[:space:]]*if:'; then + if awk '/^ required-workflow-bootstrap:$/{p=1; print; next} p && /^ [A-Za-z0-9_-]+:/{exit} p' "$bootstrap_file" | grep -q '^[[:space:]]*if:'; then record_failure "opencode required workflow bootstrap must not depend on required-workflow event payload fields" fi assert_file_contains "$workflow_file" 'github.event.client_payload.target_repository || github.repository' "opencode review scopes concurrency by target repository" From 9333dd23cf2c1ffbec92548e2f33c6b805df6723 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 31 Aug 2026 11:08:27 +0000 Subject: [PATCH 3/3] fix(ci): remove grep -q from test_strix_quick_gate.sh pipeline checks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit grep -q exits on first match and closes its end of the pipe; if the upstream awk is still writing a large block, it gets SIGPIPE (141). Under `set -o pipefail` that non-zero awk status wins over grep's real 0, so `if pipeline; then` sees the pipeline as failed even though grep found a genuine match — silently missing e.g. a forbidden `if:` key or a fenced-diff marker that should have failed the check. Ports the same-file fix from PR #1506 to this branch's two call sites (required-workflow-bootstrap job-block check; opencode review REQUEST_CHANGES fenced-diff check). This branch already carried #1506's awk job-block-boundary correction, so only the grep -q removal was needed here. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_015Gs7KmNvH75nxz1sL8mKjw --- scripts/ci/test_strix_quick_gate.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/ci/test_strix_quick_gate.sh b/scripts/ci/test_strix_quick_gate.sh index 1fc45a34b9..a92871b7ce 100644 --- a/scripts/ci/test_strix_quick_gate.sh +++ b/scripts/ci/test_strix_quick_gate.sh @@ -522,7 +522,7 @@ assert_opencode_review_uses_codegraph_and_contextual_orchestrator() { assert_file_not_contains "$workflow_file" "Wait for trusted OpenCode approval review" "opencode pull_request bridge was removed to avoid duplicate required-check resource use" assert_file_not_contains "$workflow_file" "Trusted OpenCode requested changes for head" "opencode pull_request bridge no longer reconsumes stale trusted review state" assert_file_not_contains "$workflow_file" "github.event.pull_request.number == 240" "opencode review workflow must not hard-code repository-specific PR bypasses" - if awk '/^ required-workflow-bootstrap:$/{p=1; print; next} p && /^ [A-Za-z0-9_-]+:/{exit} p' "$bootstrap_file" | grep -q '^[[:space:]]*if:'; then + if awk '/^ required-workflow-bootstrap:$/{p=1; print; next} p && /^ [A-Za-z0-9_-]+:/{exit} p' "$bootstrap_file" | grep '^[[:space:]]*if:' >/dev/null; then record_failure "opencode required workflow bootstrap must not depend on required-workflow event payload fields" fi assert_file_contains "$workflow_file" 'github.event.client_payload.target_repository || github.repository' "opencode review scopes concurrency by target repository" @@ -1501,7 +1501,7 @@ assert_opencode_review_posts_suggested_diffs_inline() { assert_file_contains "$workflow_file" "publish_request_changes_from_control" "opencode review REQUEST_CHANGES path publishes findings from the control JSON" if awk '/format_request_changes_body\(\)/,/build_request_changes_review_payload\(\)/ { print }' "$workflow_file" | - grep -Fq '```diff'; then + grep -F '```diff' >/dev/null; then record_failure "opencode review PR-level REQUEST_CHANGES body must not contain fenced suggested diffs" fi }