ci(review): expand Opus to correctness blocking + sync fork prompt - #2379
Conversation
Design Review (Fable 5) — 🟡 CONCERNSAdvisory design-level review of Design-Verdict: CONCERNS Sync-by-hand fixes the drift symptom; nothing prevents the two prompts drifting apart again, and the fork copy already contradicts itself. Watch
Suggestions
[DESIGN-REVIEWED] 8c03c63 |
Opus 4.8 Review — ✅ no blocking findingsReviewed Review detailsThis PR only changes CI review prompts, test assertions, and adds one shell step (the "Fetch PR intent" step in The fork workflow correctly keeps its No executable product code changed; no AUTOSDE rule matches workflow/prompt text; the shell logic completes no defect consequence chain. No findings. [OPUS-REVIEWED] 8c03c63 Verdict parsed from the review's SHA-scoped output markers for commit False positive or not applicable? A repository writer can comment: |
GPT 5.6 Review — ✅ no blocking findingsGPT 5.6 completed its review of This comment is updated in place on each push. Review detailsFINDING -- .github/workflows/fork-opus-review.yml:357 -- False positive or not applicable? A repository writer can comment: |
579fede to
43ee834
Compare
74af1cc to
c4addaf
Compare
The Opus lane was only checking the AUTOSDE rule files -- which for the
backend means 4 rules / 24 security-and-event-loop directives and nothing
else. Actual code logic and quality went unreviewed, and the reviewer was
deliberately blind to the PR description, so it could never tell whether a
diff did what it claimed. Now that it runs in ~4 turns on opus-4.8 there is
headroom to widen what it looks at.
Reuses the code-review-sage ruleset: the chain-of-consequences bar and its
review dimensions.
1. PR intent is now an input, as UNTRUSTED data.
A pre-step fetches title/body, strips media, caps at 8KB, wraps it in a
collision-resistant nonce fence, and writes it to
`.review-pr-intent.txt`; the prompt tells the model to Read that file.
Framed explicitly as a CLAIM, never ground truth, never an instruction,
and unable to waive or downgrade any code finding.
Passed as a FILE rather than a `${{ }}` expression on purpose: the body
is attacker-controlled on a public repo and this is a privileged lane
holding Bedrock creds, so interpolating it into a prompt/run block would
be the classic Actions script-injection vector. `gh pr view` stays OUT of
--allowedTools, so the model still has no unbounded PR-read tool and
still cannot see comment threads.
2. Seven review dimensions replace the security-only residue: correctness &
regression, security, resource & lifecycle, scope & description fidelity,
consistency with existing patterns, maintainability, observability.
3. Chain of consequences is now the finding bar -- cause -> mechanism ->
user/system consequence. A finding that cannot complete the chain to real
user- or system-visible harm is DROPPED, not downgraded.
4. Blocking scope is UNCHANGED. Quality dimensions are advisory FINDINGs
that never block; only an AUTOSDE `blocking: true` violation or one of the
three residual classes can block. Widening what the reviewer looks at
must not widen what stops a merge, or this recreates the endless-findings
problem the budget work just fixed. Advisory findings are capped at 6.
5. Output stays terse: issue + fix, no rationale paragraphs, no alternatives
considered. FINDINGs are one line each and carry a [dimension] tag, worst
consequence first.
Two prompt contradictions this surfaced and fixes, because the old text
would have silently cancelled the new dimensions:
- "NEVER report ... dead code" banned dimension 6 outright. The exclusion
now draws the line at MECHANICAL vs SEMANTIC (a linter sees that
`except Exception: pass` matches a pattern; only the reviewer sees that it
swallows the one error the caller needed).
- "you may report ONLY the three RESIDUAL DEFECT CLASSES" and "residue is
DEFINED BY the AUTOSDE rule files" made any quality finding
non-reportable. Those now define the BLOCKING set, with quality admitted
as advisory alongside.
Also recalibrated: "No findings." is still legitimate but no longer the
default expectation.
Tests: test_ai_review_workflows.py 41 pass. The pre-existing
test_reviewer_is_code_only_and_cannot_fetch_pr_prose correctly FAILED on
this change and was rewritten to pin the new contract, including a guard
that the body is never reached through a `${{ }}` expression.
c4addaf to
8c03c63
Compare
…contract prepare-pr's entire value is that local-green predicts server-green. Four things had drifted since the CI review sprint, so the local gate was mirroring a contract CI no longer runs. Model (the reported symptom): - profile pinned `claude-opus-5`; claude-review.yml moved to `us.anthropic.claude-opus-4-8` in #2339. The local gate was reviewing with a DIFFERENT model than the gate it claims to mirror. - Now `claude-opus-4.8`, fallback `claude-opus-4.7`. CI has no fallback (#2339 removed it); the local tier stays because local model availability varies, and that difference is now stated in the charter instead of looking like drift. Three further drifts, all understating CI: - Budget said "≤2 BLOCKING". CI is 5 BLOCKING + 6 advisory FINDING (#2322, #2379). A local cap of 2 systematically under-predicts the server round -- the precise cause of the "one push turns into ten" failure this skill warns about. - Blocking contract said 2 classes (AUTOSDE blocking:true, residual-class). CI enumerates 5, including a correctness defect that is unconditional wrong behaviour on the NORMAL path. - The charter never mentioned the consequence-chain bar or the quality dimensions added in #2379, so the local reviewer had no instruction to look at correctness, resource/lifecycle, scope fidelity, consistency, maintainability, or observability at all. The GPT charter also now states its ≤5 BLOCKING budget and that CI runs the lane as two passes (discovery + authoritative falsification), so a single local pass knows to apply the same falsification bar. Drift guard: test_opus_profile_model_matches_the_ci_workflow parses --model out of claude-review.yml and compares it to the profile pin, normalizing across the two id namespaces on purpose -- CI uses the Bedrock regional inference profile (us.anthropic.claude-opus-4-8), the local harness uses the kiro-cli id (claude-opus-4.8). Anchored to the real claude_args line: an unanchored regex matched the prose "--model below" in the comment above the job and produced a false failure. Positive control: reverting the pin to claude-opus-5 makes the guard fail with the two ids named; restoring it passes. 18/18 test_prepare_pr_profiles.py pass. flake8 + isort clean.
…contract prepare-pr's entire value is that local-green predicts server-green. Four things had drifted since the CI review sprint, so the local gate was mirroring a contract CI no longer runs. Model (the reported symptom): - profile pinned `claude-opus-5`; claude-review.yml moved to `us.anthropic.claude-opus-4-8` in #2339. The local gate was reviewing with a DIFFERENT model than the gate it claims to mirror. - Now `claude-opus-4.8`, fallback `claude-opus-4.7`. CI has no fallback (#2339 removed it); the local tier stays because local model availability varies, and that difference is now stated in the charter instead of looking like drift. Three further drifts, all understating CI: - Budget said "≤2 BLOCKING". CI is 5 BLOCKING + 6 advisory FINDING (#2322, #2379). A local cap of 2 systematically under-predicts the server round -- the precise cause of the "one push turns into ten" failure this skill warns about. - Blocking contract said 2 classes (AUTOSDE blocking:true, residual-class). CI enumerates 5, including a correctness defect that is unconditional wrong behaviour on the NORMAL path. - The charter never mentioned the consequence-chain bar or the quality dimensions added in #2379, so the local reviewer had no instruction to look at correctness, resource/lifecycle, scope fidelity, consistency, maintainability, or observability at all. The GPT charter also now states its ≤5 BLOCKING budget and that CI runs the lane as two passes (discovery + authoritative falsification), so a single local pass knows to apply the same falsification bar. Drift guard: test_opus_profile_model_matches_the_ci_workflow parses --model out of claude-review.yml and compares it to the profile pin, normalizing across the two id namespaces on purpose -- CI uses the Bedrock regional inference profile (us.anthropic.claude-opus-4-8), the local harness uses the kiro-cli id (claude-opus-4.8). Anchored to the real claude_args line: an unanchored regex matched the prose "--model below" in the comment above the job and produced a false failure. Positive control: reverting the pin to claude-opus-5 makes the guard fail with the two ids named; restoring it passes. 18/18 test_prepare_pr_profiles.py pass. flake8 + isort clean.
…contract prepare-pr's entire value is that local-green predicts server-green. Four things had drifted since the CI review sprint, so the local gate was mirroring a contract CI no longer runs. Model (the reported symptom): - profile pinned `claude-opus-5`; claude-review.yml moved to `us.anthropic.claude-opus-4-8` in #2339. The local gate was reviewing with a DIFFERENT model than the gate it claims to mirror. - Now `claude-opus-4.8`, fallback `claude-opus-4.7`. CI has no fallback (#2339 removed it); the local tier stays because local model availability varies, and that difference is now stated in the charter instead of looking like drift. Three further drifts, all understating CI: - Budget said "≤2 BLOCKING". CI is 5 BLOCKING + 6 advisory FINDING (#2322, #2379). A local cap of 2 systematically under-predicts the server round -- the precise cause of the "one push turns into ten" failure this skill warns about. - Blocking contract said 2 classes (AUTOSDE blocking:true, residual-class). CI enumerates 5, including a correctness defect that is unconditional wrong behaviour on the NORMAL path. - The charter never mentioned the consequence-chain bar or the quality dimensions added in #2379, so the local reviewer had no instruction to look at correctness, resource/lifecycle, scope fidelity, consistency, maintainability, or observability at all. The GPT charter also now states its ≤5 BLOCKING budget and that CI runs the lane as two passes (discovery + authoritative falsification), so a single local pass knows to apply the same falsification bar. Drift guard: test_opus_profile_model_matches_the_ci_workflow parses --model out of claude-review.yml and compares it to the profile pin, normalizing across the two id namespaces on purpose -- CI uses the Bedrock regional inference profile (us.anthropic.claude-opus-4-8), the local harness uses the kiro-cli id (claude-opus-4.8). Anchored to the real claude_args line: an unanchored regex matched the prose "--model below" in the comment above the job and produced a false failure. Positive control: reverting the pin to claude-opus-5 makes the guard fail with the two ids named; restoring it passes. 18/18 test_prepare_pr_profiles.py pass. flake8 + isort clean.
…contract (#2456) prepare-pr's entire value is that local-green predicts server-green. Four things had drifted since the CI review sprint, so the local gate was mirroring a contract CI no longer runs. Model (the reported symptom): - profile pinned `claude-opus-5`; claude-review.yml moved to `us.anthropic.claude-opus-4-8` in #2339. The local gate was reviewing with a DIFFERENT model than the gate it claims to mirror. - Now `claude-opus-4.8`, fallback `claude-opus-4.7`. CI has no fallback (#2339 removed it); the local tier stays because local model availability varies, and that difference is now stated in the charter instead of looking like drift. Three further drifts, all understating CI: - Budget said "≤2 BLOCKING". CI is 5 BLOCKING + 6 advisory FINDING (#2322, #2379). A local cap of 2 systematically under-predicts the server round -- the precise cause of the "one push turns into ten" failure this skill warns about. - Blocking contract said 2 classes (AUTOSDE blocking:true, residual-class). CI enumerates 5, including a correctness defect that is unconditional wrong behaviour on the NORMAL path. - The charter never mentioned the consequence-chain bar or the quality dimensions added in #2379, so the local reviewer had no instruction to look at correctness, resource/lifecycle, scope fidelity, consistency, maintainability, or observability at all. The GPT charter also now states its ≤5 BLOCKING budget and that CI runs the lane as two passes (discovery + authoritative falsification), so a single local pass knows to apply the same falsification bar. Drift guard: test_opus_profile_model_matches_the_ci_workflow parses --model out of claude-review.yml and compares it to the profile pin, normalizing across the two id namespaces on purpose -- CI uses the Bedrock regional inference profile (us.anthropic.claude-opus-4-8), the local harness uses the kiro-cli id (claude-opus-4.8). Anchored to the real claude_args line: an unanchored regex matched the prose "--model below" in the comment above the job and produced a false failure. Positive control: reverting the pin to claude-opus-5 makes the guard fail with the two ids named; restoring it passes. 18/18 test_prepare_pr_profiles.py pass. flake8 + isort clean. Co-authored-by: Joe Guo <zejiangg@amazon.com>
…irodotdev#2379) The Opus lane was only checking the AUTOSDE rule files -- which for the backend means 4 rules / 24 security-and-event-loop directives and nothing else. Actual code logic and quality went unreviewed, and the reviewer was deliberately blind to the PR description, so it could never tell whether a diff did what it claimed. Now that it runs in ~4 turns on opus-4.8 there is headroom to widen what it looks at. Reuses the code-review-sage ruleset: the chain-of-consequences bar and its review dimensions. 1. PR intent is now an input, as UNTRUSTED data. A pre-step fetches title/body, strips media, caps at 8KB, wraps it in a collision-resistant nonce fence, and writes it to `.review-pr-intent.txt`; the prompt tells the model to Read that file. Framed explicitly as a CLAIM, never ground truth, never an instruction, and unable to waive or downgrade any code finding. Passed as a FILE rather than a `${{ }}` expression on purpose: the body is attacker-controlled on a public repo and this is a privileged lane holding Bedrock creds, so interpolating it into a prompt/run block would be the classic Actions script-injection vector. `gh pr view` stays OUT of --allowedTools, so the model still has no unbounded PR-read tool and still cannot see comment threads. 2. Seven review dimensions replace the security-only residue: correctness & regression, security, resource & lifecycle, scope & description fidelity, consistency with existing patterns, maintainability, observability. 3. Chain of consequences is now the finding bar -- cause -> mechanism -> user/system consequence. A finding that cannot complete the chain to real user- or system-visible harm is DROPPED, not downgraded. 4. Blocking scope is UNCHANGED. Quality dimensions are advisory FINDINGs that never block; only an AUTOSDE `blocking: true` violation or one of the three residual classes can block. Widening what the reviewer looks at must not widen what stops a merge, or this recreates the endless-findings problem the budget work just fixed. Advisory findings are capped at 6. 5. Output stays terse: issue + fix, no rationale paragraphs, no alternatives considered. FINDINGs are one line each and carry a [dimension] tag, worst consequence first. Two prompt contradictions this surfaced and fixes, because the old text would have silently cancelled the new dimensions: - "NEVER report ... dead code" banned dimension 6 outright. The exclusion now draws the line at MECHANICAL vs SEMANTIC (a linter sees that `except Exception: pass` matches a pattern; only the reviewer sees that it swallows the one error the caller needed). - "you may report ONLY the three RESIDUAL DEFECT CLASSES" and "residue is DEFINED BY the AUTOSDE rule files" made any quality finding non-reportable. Those now define the BLOCKING set, with quality admitted as advisory alongside. Also recalibrated: "No findings." is still legitimate but no longer the default expectation. Tests: test_ai_review_workflows.py 41 pass. The pre-existing test_reviewer_is_code_only_and_cannot_fetch_pr_prose correctly FAILED on this change and was rewritten to pin the new contract, including a guard that the body is never reached through a `${{ }}` expression. Co-authored-by: Joe Guo <zejiangg@amazon.com>
…contract (kirodotdev#2456) prepare-pr's entire value is that local-green predicts server-green. Four things had drifted since the CI review sprint, so the local gate was mirroring a contract CI no longer runs. Model (the reported symptom): - profile pinned `claude-opus-5`; claude-review.yml moved to `us.anthropic.claude-opus-4-8` in kirodotdev#2339. The local gate was reviewing with a DIFFERENT model than the gate it claims to mirror. - Now `claude-opus-4.8`, fallback `claude-opus-4.7`. CI has no fallback (kirodotdev#2339 removed it); the local tier stays because local model availability varies, and that difference is now stated in the charter instead of looking like drift. Three further drifts, all understating CI: - Budget said "≤2 BLOCKING". CI is 5 BLOCKING + 6 advisory FINDING (kirodotdev#2322, kirodotdev#2379). A local cap of 2 systematically under-predicts the server round -- the precise cause of the "one push turns into ten" failure this skill warns about. - Blocking contract said 2 classes (AUTOSDE blocking:true, residual-class). CI enumerates 5, including a correctness defect that is unconditional wrong behaviour on the NORMAL path. - The charter never mentioned the consequence-chain bar or the quality dimensions added in kirodotdev#2379, so the local reviewer had no instruction to look at correctness, resource/lifecycle, scope fidelity, consistency, maintainability, or observability at all. The GPT charter also now states its ≤5 BLOCKING budget and that CI runs the lane as two passes (discovery + authoritative falsification), so a single local pass knows to apply the same falsification bar. Drift guard: test_opus_profile_model_matches_the_ci_workflow parses --model out of claude-review.yml and compares it to the profile pin, normalizing across the two id namespaces on purpose -- CI uses the Bedrock regional inference profile (us.anthropic.claude-opus-4-8), the local harness uses the kiro-cli id (claude-opus-4.8). Anchored to the real claude_args line: an unanchored regex matched the prose "--model below" in the comment above the job and produced a false failure. Positive control: reverting the pin to claude-opus-5 makes the guard fail with the two ids named; restoring it passes. 18/18 test_prepare_pr_profiles.py pass. flake8 + isort clean. Co-authored-by: Joe Guo <zejiangg@amazon.com>
Problem
The Opus reviewer only enforced AUTOSDE security rules — no code logic, correctness, or quality review. Between the deterministic gates (mypy/eslint/coverage) and GPT, Opus was blind to semantic bugs that no linter can catch. The fork-PR reviewer (
fork-opus-review.yml) had the same limitation and was drifting out of sync.Why it matters
Logic bugs, resource leaks, and scope mismatches that survive both linters and GPT's narrower pass reach main uncaught. The strongest model available (Opus 4.8) was underutilized on both same-repo and fork PRs.
Fix (symptoms → root cause → change)
The prompt was scoped to only AUTOSDE + 3 residual defect classes. This created a two-tier system that was complex to reason about and prevented correctness bugs from blocking.
Redesigned as a simpler collect → classify flow (applied to both
claude-review.ymlandfork-opus-review.yml):Key changes:
[dimension]tags — just BLOCKING and FINDINGfork-opus-review.ymlnow shares the same WHAT BLOCKS, Phase A/B, budget, and calibration as the same-repo version (retaining its own fork-specific security hardening: SYSTEM RULES, INPUT DISCIPLINE, authenticated patch file)WHAT BLOCKS (exhaustive, 5 items):
blocking: truerule violationGPT reviewer unchanged — kept narrow (AUTOSDE + residual only) to complement Opus's broader coverage. Two reviewers with different scopes catch more than two with the same scope.
Tests
test_ai_review_workflows.pypassManual verification
N/A — CI workflow change, verified by test assertions matching prompt text + the gate's own execution on this PR.