fix(opencode): source-backed supply-chain fallback so the approve gate reaches a conclusion#356
fix(opencode): source-backed supply-chain fallback so the approve gate reaches a conclusion#356seonghobae wants to merge 3 commits into
Conversation
…rove gate reaches a conclusion When OpenCode's Review Overview control is APPROVE but a PR has failing peer supply-chain checks (osv-scan, trivy-fs, dependency-review), the approve gate collects the failed checks and must convert them into a source-backed REQUEST_CHANGES. The fallback helper (emit_opencode_failed_check_fallback_findings.sh) had emitters for pytest, cancelled checks, Strix reports, and billing-lock, but NONE for supply-chain scanners. So finding_index stayed 0, build_failed_check_fallback_body returned 1, stop_failed_check_fallback_unavailable fired, no review was posted, and the gate printed NO_CONCLUSION / FAILED_CHECK_DIAGNOSIS_UNAVAILABLE and exited 1 — the "Run merge scheduler" step was skipped and the PR was stuck forever (opencode-review = failure with no actionable review). Supply-chain findings ARE source-backed: osv/trivy/dependency-review name the exact vulnerable package, its manifest file, the CVE/GHSA id, and the fixed version. This maps those to concrete findings instead of weakening the fail-closed contract. Changes: - emit_opencode_failed_check_fallback_findings.sh: add emit_supply_chain_findings (+ extract_supply_chain_records) that parses osv-scanner / trivy-fs / dependency-review failed-check blocks for two evidence shapes — canonical "- Supply-chain vulnerability: id=... severity=... package=... installed=... fixed=... manifest=... line=..." lines and raw Trivy findings tables — and emits one source-backed finding per distinct vulnerability with: manifest path resolved under repo_root, a positive line number (locate the package in the manifest, or honor the scanner's SARIF line hint; never line 0), severity, a CVE/GHSA + package title, problem/root_cause quoting the scanner evidence and failed check label, fix_direction "bump <pkg> from <installed> to <fixed>", a GitHub-suggestion-ready ```suggestion diff for simple version pins, and a regression_test_direction. Wired into the dispatch sequence next to the other emit_* calls. URL-only supply-chain evidence still yields no finding (fail-closed preserved). - collect_failed_check_evidence.sh: extend evidence collection so the emitter has real data in production. For failed osv-scanner / trivy checks (which upload SARIF to code scanning rather than logging findings), pull the current-head code-scanning alerts and normalize each into a canonical "- Supply-chain vulnerability:" line (package, manifest path+line, advisory id, severity, fixed version). Best-effort and guarded; never fails the collector. - test_strix_quick_gate.sh: add static assertions that the helper defines emit_supply_chain_findings, scopes to osv/trivy/dependency-review, states a concrete "bump <pkg> from <installed> to <fixed>", offers a ```suggestion diff, and is wired into the dispatch; add static assertions that the collector defines emit_supply_chain_alert_evidence, reads code-scanning/alerts, and emits canonical supply-chain lines. Add two functional tests: one feeds synthetic osv (canonical) + trivy (table) evidence plus a matching manifest and asserts source-backed findings (path, positive line, CVE/GHSA, from->to bump, suggestion); the other asserts URL-only supply-chain evidence stays fail-closed. No existing test assertion was relaxed or deleted. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RTAMs4bpSZS77Xe3RQjv9P
The supply-chain fallback joined per-vulnerability records with a TAB and read them back with IFS=$'\t'. Tab is an IFS-whitespace character, so read collapses consecutive tabs: an empty interior field (missing installed OR missing fixed) shifted every later column left by one. Since the collector appends installed=/fixed= only when present, no-installed-version SARIF alerts and no-fix advisories are common real inputs, producing garbled findings (severity word in the advisory-id slot, CVE id in the version slot). Switch the internal record delimiter to the ASCII Unit Separator (\x1f) for both the perl join and the read loop. \x1f is not IFS-whitespace, so empty interior fields are preserved positionally. Guard the emitter so a missing installed or fixed never yields a broken sentence or a CVE id in a version slot: "upgrade <pkg> to <fixed>" when installed is absent, and "no fixed version is available upstream for <pkg> <installed>; remove or replace the dependency, or pin to a patched fork" when there is no fix. The canonical human-readable evidence line format is unchanged. Add assert_opencode_failed_check_fallback_preserves_empty_supply_chain_columns, a regression test feeding one record with no installed version and one with no fixed version; it asserts the advisory id lands in the title (not the severity word), no CVE/GHSA id appears in a version slot, columns are not shifted, and line numbers stay positive. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RTAMs4bpSZS77Xe3RQjv9P
Follow-up fix: empty supply-chain columns no longer collapseAdversarial review found a correctness bug in the new supply-chain path of Fix (commit 082cf89):
Regression test: Verification: |
The dead-end
When OpenCode's Review Overview control is APPROVE but the PR has failing peer supply-chain checks (
osv-scan / osv-scan,trivy-fs,dependency-review), the approve gate collects the failed peer checks and must justify a source-backedREQUEST_CHANGES. It callsrun_failed_check_diagnosis, thenbuild_failed_check_fallback_body->emit_line_specific_fallback_findings->scripts/ci/emit_opencode_failed_check_fallback_findings.sh.That helper had emitters for pytest, cancelled checks, Strix reports, and billing-lock — but none for supply-chain scanners. So for a supply-chain failure
finding_indexstayed0,build_failed_check_fallback_bodyreturned1,stop_failed_check_fallback_unavailablefired, no review was posted, and the gate printedNO_CONCLUSION/FAILED_CHECK_DIAGNOSIS_UNAVAILABLEand exited 1. The "Run merge scheduler" step was skipped and the PR was stuck:opencode-review = failurewith no actionable review, no conclusion, never merges.Repro: clearfolio#23 (run 28863381355) — model control = APPROVE, failing peers
osv-scan / osv-scanandtrivy-fs.Why a source-backed emitter (not weakening fail-closed)
The guard tests deliberately fail-closed rather than post URL-only reviews. Supply-chain findings are source-backed: osv/trivy/dependency-review name the exact vulnerable package, its manifest file, the CVE/GHSA id, and the fixed version. The correct fix is to map those to concrete findings (package +
manifest:line+ CVE + "bump A->B"), which makes the fallback succeed and posts an actionableREQUEST_CHANGES. No URL-only path was added and no existing assertion was relaxed.The fix
scripts/ci/emit_opencode_failed_check_fallback_findings.shemit_supply_chain_findings(+extract_supply_chain_records), mirroringemit_pytest_failure_findings/emit_strix_report_findings. It parsesosv-scanner/trivy-fs/dependency-reviewfailed-check blocks for two evidence shapes:- Supply-chain vulnerability: id=CVE-… severity=… package=… installed=… fixed=… manifest=… line=…requirements.txt (pip)).${GITHUB_WORKSPACE:-$PWD}, a positive line number (locate the package in the manifest, else honor the scanner's SARIF line hint; never line 0), severity, a CVE/GHSA + package title, problem/root_cause quoting the scanner evidence and the failed check label + exact phrase,fix_direction= concrete "bump<pkg>from<installed>to<fixed>", a GitHub-suggestion-ready```suggestiondiff for simple version pins, and aregression_test_direction.emit_*calls.stop_failed_check_fallback_unavailablestill fires for genuine no-evidence).scripts/ci/collect_failed_check_evidence.sh(evidence extension)emit_supply_chain_alert_evidencepulls the current-head code-scanning alerts for the failed check's tool and normalizes each into a canonical- Supply-chain vulnerability:line (package, manifest path+line, advisory id, severity, fixed version) so the fallback has real, source-backed data. Best-effort and guarded — never fails the collector.scripts/ci/test_strix_quick_gate.sh(guard tests, existingassert_file_containsstyle)emit_supply_chain_findings, scopes to osv/trivy/dependency-review, states a concrete "bump<pkg>from<installed>to<fixed>", offers a```suggestiondiff, and is wired into the dispatch; collector definesemit_supply_chain_alert_evidence, readscode-scanning/alerts, and emits canonical supply-chain lines.assert_opencode_failed_check_fallback_maps_supply_chain_vulnerabilities: feeds synthetic osv (canonical) + trivy (table) evidence plus a matching manifest, and asserts source-backed findings (exactpath:line, CVE/GHSA,from->tobump, suggestion pin).assert_opencode_failed_check_fallback_rejects_url_only_supply_chain: asserts URL-only supply-chain evidence emits no finding and stays fail-closed (exit 1).Test results
requestsfindings; trivy table row ->requirements.txt:3urllib3finding withbump from 1.25.0 to 1.26.18and aurllib3==1.26.18suggestion).shellcheck -S warningclean on the emitter and collector;bash -nclean on all three files.Pre-existing, unrelated failures (not caused by this PR)
The repo's
scripts/ci/test_strix_quick_gate.shandtests/pytest suite are not green onmainin a clean checkout, due to workflow/test drift in files this PR does not touch (.github/workflows/opencode-review.yml,strix.yml, merge-scheduler, noema-review). Verified identical on a pristineorigin/mainworktree: 14 guard-suite failures and 3 pytest failures with vs. without this change — this PR adds zero new failures. Those drift failures should be fixed in a separate PR; they are orthogonal to the supply-chain fallback dead-end fixed here.🤖 Generated with Claude Code