Skip to content

fix(guardrails): correct coder mutation guidance - #2770

Open
zaxbysauce wants to merge 3 commits into
mainfrom
fix/issue-2758-coder-mutation-guidance
Open

zaxbysauce wants to merge 3 commits into
mainfrom
fix/issue-2758-coder-mutation-guidance

Conversation

@zaxbysauce

@zaxbysauce zaxbysauce commented Sep 14, 2026

Copy link
Copy Markdown
Collaborator

Closes #2758

Summary

  • Split TASK_WORKFLOW_CODER_MUTATION_REQUIRED from genuine Stage A attribution-recovery failures in both Stage A guardrail transition catches.
  • Replace incorrect /swarm recover guidance with actionable accepted-coder-mutation guidance, while preserving genuine attribution recovery semantics.
  • Add durable PASS/failure regressions, exhaustive non-overlap classification coverage, and a pending release-note fragment.

Invariant audit

  • 1 (plugin init): not touched - no initialization or startup-path changes; bun run build passed.
  • 2 (runtime portability): not touched - no runtime loader, bundle, export, or platform portability changes; build passed and dist/ remains generated/uncommitted.
  • 3 (subprocesses): not touched - no subprocess source changes; invariant checks passed.
  • 4 (.swarm containment): not touched - no runtime state or working-directory logic changes.
  • 5 (plan durability): not touched - no plan ledger, projection, checkpoint, or workflow persistence changes.
  • 6 (test_runner safety): not touched - validation used bounded focused shell test commands; no test-runner implementation changed.
  • 7 (test writing): touched - four focused bun:test files use existing fixtures and no new mock.module seams; bun run check:test-file-cap and focused suite passed.
  • 8 (session state): not touched - no session maps, eviction, or cross-session state changes.
  • 9 (guardrails/retry): touched - structured reducer error categories now route to category-correct remediation; focused C1/C5 real-hook regressions and C2 classification guard passed.
  • 10 (chat/system msg): not touched - no chat transform or message-carrier changes.
  • 11 (tool registration): not touched - no tools, agents, commands, or maps changed.
  • 12 (release/cache): touched - added docs/releases/pending/guardrails-coder-mutation-guidance-2758.md; pending-fragment validation passed and no cache code changed.

Test plan

  • bun --smol test tests/unit/hooks/guardrails-durable-stage-a-attribution.test.ts tests/unit/hooks/stage-a-error-classification.test.ts tests/unit/hooks/stage-a-attribution-classification-preserving.test.ts tests/unit/hooks/guardrails-stage-a-coder-mutation-failure.test.ts --timeout 60000 — 18 passed, 0 failed.
  • Frozen acceptance replay: C1/C2/C5 RED on origin/main and GREEN on this commit; C3 GREEN on both; checkpoint verification OK for all four paths.
  • bun run typecheck — passed.
  • bun run build — passed.
  • bun run lint:ci — passed with four pre-existing warnings in unchanged base files; the two changed test files pass the scoped Biome check.
  • Pinned Biome check on all five changed source/test files — passed.
  • bun run check:invariants — passed with pre-existing advisory findings only.
  • bun run check:test-file-cap — passed.
  • bun run check:pending-fragment — passed.
  • Deferred-work scan and git diff --check — passed.
  • Independent implementation review — APPROVE.
  • GLM 5.3 high-effort final critic — APPROVE_WITH_NOTES; wording and publication notes were corrected, with no blocking defect.

Known caveat

bun run package:smoke reached the local-tarball npm-install stage but timed out repeatedly on this Windows host; packaging, dependencies, exports, and generated artifacts are unchanged.

The PR branch is now rebased onto current origin/main (b865ba262f48e1ccfc966c054a5ce323b62a9cdb), so the earlier base-drift condition is addressed. The follow-up head is b8f2383164956ee1364f4059c40d7069c963f65e; required remote checks must be rerun against that exact head. Keep the PR draft until the CI and approval gates are satisfied.

@github-actions

Copy link
Copy Markdown
Contributor

Drift check report

Found 2 drift finding(s): 0 error, 0 warning, 2 notice.

required-check-contract (2)

  • 🔵 notice scripts/required-check-contract.json: [RULESET_DIVERGENCE] intended-required context "drift" is not yet required by the captured ruleset
  • 🔵 notice scripts/required-check-contract.json: [RULESET_DIVERGENCE] intended-required context "drift" is not present for every expected event in captured external workflow evidence

@zaxbysauce

Copy link
Copy Markdown
Collaborator Author

🤖 Multi-Stage PR Review

Pipeline: MiniMax-M2.7-highspeed (orientation) (context pack) → MiniMax-M2.7-highspeed (explorer) + MiniMax-M2.7-highspeed (explorer B) (parallel explore, distinct lenses) → GLM-5-turbo (critique) ↔ GLM-5-turbo (critique) (cross-critique) → MiniMax-M2.7-highspeed (fallback arbiter) (arbiter: blind-spot + synthesize)
Commit reviewed: 00e1a842aa1d


🔍 PR Intent

Reconstructed obligation list (from PR text, issue #2758, commits, changed tests, changed docs).

  • O-001: Correctly classify TASK_WORKFLOW_CODER_MUTATION_REQUIRED as a coder-mutation-required error (not an attribution miss) in both Stage A guardrail catch blocks.
  • O-002: Replace the incorrect /swarm recover guidance with actionable guidance: dispatch a coder for a real code change, or mark the task blocked if none exists.
  • O-003: Preserve genuine attribution-recovery semantics for TASK_WORKFLOW_STAGE_A_REQUIRED (keep the /swarm recover advice for genuine attribution failures).
  • O-004: Add regression coverage for the pass-side coder-mutation flow and the fail-side coder-mutation flow.
  • O-005: Add a pending release note fragment.

📦 Implementation Summary

The PR splits a single STAGE_A_ATTRIBUTION_MISS_CODES set into two disjoint exported sets: STAGE_A_CODER_MUTATION_REQUIRED_CODES (containing TASK_WORKFLOW_CODER_MUTATION_REQUIRED) and STAGE_A_ATTRIBUTION_MISS_CODES (containing TASK_WORKFLOW_STAGE_A_REQUIRED). Both catch blocks in createGuardrailsHooks (toolAfter pass-side at ~line 1262 and fail-side at ~line 1335) now route through a new STAGE_A_CODER_MUTATION_REQUIRED_CODES.has(code) check before the attribution check, emitting a log + advisory with actionable guidance for the former and preserving the /swarm recover message for the latter. Four test files are added/updated; a release note fragment is added.


✅ / ⚠️ / ❌ Intended vs Actual

Obligation Status Evidence (file:line)
O-001 SUPPORTED src/hooks/guardrails/index.ts:207-210 — new separate set added; both catches updated
O-002 SUPPORTED src/hooks/guardrails/index.ts:1267-1273,1338-1344 — actionable guidance replacing /swarm recover
O-003 SUPPORTED src/hooks/guardrails/index.ts:1262,1335else if still fires for STAGE_A_ATTRIBUTION_MISS_CODES
O-004 SUPPORTED tests/unit/hooks/guardrails-durable-stage-a-attribution.test.ts:381-419, guardrails-stage-a-coder-mutation-failure.test.ts:1-84, stage-a-attribution-classification-preserving.test.ts:1-30, stage-a-error-classification.test.ts:82-99
O-005 SUPPORTED docs/releases/pending/guardrails-coder-mutation-guidance-2758.md:1-19

🚨 Confirmed Findings

None — all reviewer-confirmed findings were false positives or already addressed by existing test coverage.


🔬 Unverified but Plausible Risks

None — all plausible concerns either (a) have structural proof the risk is blocked, or (b) are already covered by the exhaustive classification guardrail test.


🧪 Test / Coverage Gaps

None found. The exhaustive classification test (stage-a-error-classification.test.ts) mechanically verifies every TASK_WORKFLOW_* code in gate-evidence.ts is either classified or allowlisted, preventing silent regression.


📋 Shipped-vs-Claimed Gaps

None.


📝 Merge Recommendation

APPROVE

The fix is correct, the classification is exhaustive and mechanically enforced, the test coverage is thorough, and every reviewer-confirmed finding was a false positive. The PR fully addresses issue #2758 with a minimal, well-reasoned change.

Check Result
No CRITICAL findings
No unresolved STEALTH_CHANGE
No UNSUPPORTED obligations
Test coverage adequate
No hardcoded secrets
All async errors handled
Input validation present
No broken agent role boundaries
Prompt format contracts intact
Lockfile consistent

🔁 Validation provenance

Findings DROPPED (one-line reason):

# Finding Reason
1 Silent swallow at line 1262/1338 if code not in either set null return from stageAWriteErrorCode for non-TASK_WORKFLOW errors is intentional and allowlisted by design; the exhaustive classification test pins every reducer code.
2 optionalCoderMutationCodes() vacuous pass instanceof Set fallback returning new Set() is intentional defensive behaviour; covered by the disjointness + exhaustive tests in stage-a-error-classification.test.ts.
3 getCoderMutationCodes() vacuous pass Same as above — stage-a-error-classification.test.ts:85 explicitly asserts the exact singleton set.
4 Asymmetric fragility (direct import vs cast) Not a defect — direct import is the safer pattern, cast is defensive fallback; both are intentional.
5 Missing inverse non-overlap test Already covered by stage-a-error-classification.test.ts:88-91 which explicitly asserts no overlap.
6–7 Message injection via error code interpolation Error codes come from internal reducer constants; no user-controllable path exists.
8–9 HIGH severity on cast fragility Downgraded: existing exhaustive classification tests catch vacuous pass; stage-a-error-classification.test.ts:85 is a concrete structural proof.
10 Other advisories could mask regression Minor nit; the pass/fail advisory is present and asserts absence of /swarm recover.

Blind-spot pass: No additional real defects found. The null path through the catch chain is intentional (non-TASK_WORKFLOW errors are expected and allowlisted).


🔒 Reviewed by a 3-model cross-family adversarial debate (architect → dual-lens parallel explorers → cross-critique → arbiter) for high recall with low false-positive noise. Findings are advisory — verify before acting.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

The change modifies advisory routing in the guardrails/retry subsystem (a non-negotiable invariant surface with a history of subtle regressions), so final human sign-off is warranted despite no issues being found.

Pull request overview

This PR fixes issue #2758, where the Stage A guardrail catches in src/hooks/guardrails/index.ts misclassified the reducer's TASK_WORKFLOW_CODER_MUTATION_REQUIRED throw as an attribution miss and advised the architect to run the human-only /swarm recover command. That advice was both wrong (the result was attributed) and non-actionable, causing the architect to waste run time and model spend chasing a non-existent recovery path. The fix separates the coder-mutation-required category from genuine attribution-recovery failures and gives the architect actionable guidance (dispatch a coder for a real change, or mark the task blocked).

The change is verified against the reducer in src/gate-evidence.ts:647-668: stage_a_passed/stage_a_failed throw only TASK_WORKFLOW_CODER_MUTATION_REQUIRED (or a generation-mismatch fencing code), so the newly split coder-mutation branch is the live path while the retained STAGE_A_REQUIRED attribution branch stays defensive at these two catches but remains live in the delegation-gate Stage B surfaces.

Changes:

  • Split TASK_WORKFLOW_CODER_MUTATION_REQUIRED into a dedicated STAGE_A_CODER_MUTATION_REQUIRED_CODES set, disjoint from STAGE_A_ATTRIBUTION_MISS_CODES, and route it to distinct advisory/critical-warn guidance in both Stage A catch blocks.
  • Add regression and classification-non-overlap coverage across three new/updated test files, plus a pass-side rework_required regression.
  • Add a pending release fragment documenting the guidance correction.
File summaries
File Description
src/hooks/guardrails/index.ts Adds STAGE_A_CODER_MUTATION_REQUIRED_CODES, updates the classification doc comment, and routes coder-mutation-required throws to actionable guidance in both Stage A failure/pass catches.
tests/unit/hooks/stage-a-error-classification.test.ts Extends the mechanical classification pin to the new set and adds disjointness/attribution-preservation assertions.
tests/unit/hooks/stage-a-attribution-classification-preserving.test.ts New test confirming TASK_WORKFLOW_STAGE_A_REQUIRED stays in attribution recovery only.
tests/unit/hooks/guardrails-stage-a-coder-mutation-failure.test.ts New real-hook regression for the failure-side coder-mutation guidance.
tests/unit/hooks/guardrails-durable-stage-a-attribution.test.ts Adds a pass-side rework_required regression asserting coder-mutation guidance without recovery advice.
docs/releases/pending/guardrails-coder-mutation-guidance-2758.md Pending release note for the guardrail message correction.
Review details
  • Files reviewed: 6/6 changed files
  • Comments generated: 0
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@zaxbysauce

Copy link
Copy Markdown
Collaborator Author

Swarm PR Review — #2770 fix(guardrails): correct coder mutation guidance

Scope: true PR diff verified against merge-base ef59deb85d (NOT the current main tip, which has advanced past this branch's point — see F-001). 6 files, +249/-19: src/hooks/guardrails/index.ts, 4 test files, 1 release-note fragment. Reviewed under the swarm-pr-review protocol: 2 base explorer lanes (6 dimensions), 1 risk-family micro-lane (4 matched families of 11), 1 independent reviewer pass (10/10 candidates validated), 1 critic challenge on the highest-severity finding.

Verdict: APPROVE_WITH_NOTES

The code change itself is small, correct, symmetric across both guardrail catch sites, and behaviorally tested (verified failing→passing on the intended branches, not just "did not throw"). Zero CRITICAL or HIGH findings against the PR's own code. The one blocker to merging is an external CI gate defect, not a defect in this PR.


F-001 (MEDIUM) — release-owner-guard CI gate is blocking this PR on a false positive; not caused by this PR

Status: confirmed independently by reviewer, reproduced and adversarially challenged by critic (UPHELD, severity corrected HIGH→MEDIUM, mechanism corrected).

Required checks release-owner-guard, quality, and unit-passed are currently failing on this PR with:

unauthorized edit to release-please-owned file(s): .release-please-manifest.json, CHANGELOG.md, package.json

This is a false positive. The true PR diff (merge-base ef59deb85d...head 00e1a842a) never touches those three files — confirmed by git diff --merge-base returning empty, independently reproduced twice. quality fails only because it hard-depends on release-owner-guard; unit is then skipped, which makes unit-passed fail via its if: always() check. One root cause, three red checks.

Corrected mechanism (critic caught a factual error in the initial root-cause trace): the failing comparison is not driven by the TRUSTED_BASE_SHA env var set in .github/workflows/ci.yml:139. scripts/check-required-check-contract.ts (trustedGitHubContext, ~line 1245) re-derives base.sha directly from GITHUB_EVENT_PATH, and releaseOwnerDiffArgs (~line 811) builds a two-dot git diff base head instead of a merge-base-relative comparison. Since main picked up a release-please version bump after this branch's merge-base was cut, the two-dot diff makes the PR look like it "reverts" those files, when it never touched them.

This is a genuine bug, not intentional strictness — this repo does document deliberate two-dot usage elsewhere (ci.yml:191-195, detect-paths) with an explicit comment explaining why; no such rationale exists for the release-owner guard, and the guard's own doc (docs/ci-required-check-contract.md) describes an authorship model, not a structural-divergence model. A merge-base/three-dot fix was checked for a smuggling gap (could a real revert of these files slip through?) and none was found — a genuine revert is always authored content relative to the merge-base and would still be caught.

Recommended remediation (does not block merging this specific PR — see below):

  • Fix releaseOwnerDiffArgs/changedFilesForGuard in scripts/check-required-check-contract.ts to use git diff --merge-base (or three-dot) semantics for the pull_request event, keeping two-dot for merge_group.
  • That fix must land as its own PR branched from current main — because ci.yml runs the checker at the base revision, a fix committed on a PR whose base predates it has zero effect on that PR's own guard run; bundling the fix into a PR and then merging main in to unblock it would falsely validate on the rebase, not the fix.
  • Update the pinned arg-array assertion in tests/unit/scripts/required-check-contract-wiring-2677.test.ts:432-444 and add a merge-base-relative regression test.
  • To unblock PR fix(guardrails): correct coder mutation guidance #2770 itself: merge current main into this branch (or rebase). That makes base.sha == merge-base and the guard passes for the ordinary reason. No code change to this PR is required.

Informational findings (no action required to merge)

All below are LOW severity, confirmed by the independent reviewer, and either pre-existing or benign/defensive:

  • Dead-but-documented branch: the /swarm recover (attribution-miss) branch in both guardrail catches (src/hooks/guardrails/index.ts:1273, :1346) is currently unreachable in production — the reducer (src/gate-evidence.ts) can only throw TASK_WORKFLOW_CODER_MUTATION_REQUIRED at these two call sites, not TASK_WORKFLOW_STAGE_A_REQUIRED. The PR's own doc comment (lines 186-191) states this explicitly as defensive/forward-looking. No action needed.
  • Test coverage is table-only for the dead branch (tests/unit/hooks/stage-a-attribution-classification-preserving.test.ts, stage-a-error-classification.test.ts) — consistent with the above; an end-to-end test isn't possible without artificial mock injection, which is a reasonable coverage ceiling given the branch is unreachable today.
  • Pre-existing stale test title: tests/unit/hooks/guardrails-durable-stage-a-attribution.test.ts:354 is titled "attribution-miss write failure escalates to a visible advisory" but its body/assertions actually exercise the coder-mutation path. This test predates the PR's diff (untouched by it) and is now more confusing in context — a good opportunity for a follow-up cosmetic rename, not a blocker.
  • Test-quality nit (introduced by this PR): tests/unit/hooks/guardrails-stage-a-coder-mutation-failure.test.ts:60-83 relies on the reducer's implicit default initial state to reach the coder-mutation branch, rather than explicitly seeding a precondition the way the companion test in guardrails-durable-stage-a-attribution.test.ts:382-393 does. It's not vacuous (verified it actually exercises the branch), just under-documented — worth a follow-up to seed the precondition explicitly so the coupling to the reducer's default is visible.
  • No security/injection concerns: new advisory/log messages interpolate only taskId and a code value constrained to a hardcoded Set; no raw error content reaches the new branches.
  • No other stale-guidance call sites: repo-wide grep confirms no other location still emits /swarm recover guidance specifically for TASK_WORKFLOW_CODER_MUTATION_REQUIRED.

Disproved candidates

  • Advisory accumulation risk on repeated retries — disproved: pushAdvisory dedupes on normalized message text and the queue is capped.
  • Release-note fragment heading format non-conformance — disproved: the fragment's headings (## What/## Why/## Migration) are used by 75/236/151 existing pending fragments respectively; no linter enforces a single canonical set.

Clean lanes (no findings)

intent-architecture (both catch blocks symmetric and correct), security-trust, concurrency-state (purely additive classification, no new mutation/retry semantics), test-infrastructure (proper isolation via resetSwarmState/createSafeTestDir, meaningful assertions), privacy-observability, unclassified-risk.


Validation provenance

  • Capability profile: B (Claude Code native subagents, no swarm controller) · Depth tier: S/M (6 files, ~249 lines, one risk trigger: state-machine/guardrail-retry logic)
  • Base dimensions covered: 6/6 (2 consolidated lanes) · Risk families evaluated: 11/11 (4 matched → dedicated micro-lane; 7 not-triggered with absence evidence: auth/secrets, untrusted-input, subprocess, dependencies/build, api/schema, ui/a11y, generated-provenance)
  • Candidates generated: 10 · Reviewer-validated: 10/10 (7 CONFIRMED, 2 DISPROVED, 1 PRE_EXISTING) · Critic-challenged: 1 (the MEDIUM CI-gate finding, UPHELD with corrections)
  • Falsification probes: provided for every confirmed finding above (repo-relative grep/diff commands or test re-runs)

🤖 Generated with Claude Code — swarm-pr-review

https://claude.ai/code/session_01XqUz4WeqEXrAiNybUpFGTP

@zaxbysauce zaxbysauce left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Swarm PR review

Verdict: APPROVE_WITH_NOTES (implementation approved; keep the draft while required checks are blocked).

  • Confirmed low-severity test-quality gap: tests/unit/hooks/guardrails-stage-a-coder-mutation-failure.test.ts:73 asserts the queued advisory but not the independent criticalWarn output from either Stage A catch. Recommended follow-up: capture console.warn/logger output and assert the accepted-coder-mutation guidance plus absence of NOT attributed and /swarm recover.
  • Pre-existing delivery blocker: the unchanged release-owner-guard two-dot comparison sees base-only release files after main advanced from the branch fork, so release-owner-guard, quality, and unit-passed fail/skip. The exact three-dot PR diff has no release-owned files. Update/rebase the branch and rerun required checks before merging.
  • All other reviewed candidates were disproved or pre-existing. The four issue obligations are met at exact head 00e1a842aa1d4a53d66d44c521c1e9d1d8362555.

Review artifacts: .agents/issue-traces/2758-guardrails-coder-mutation-guidance/pr-review-2770/final-review.md and findings.jsonl.

@zaxbysauce
zaxbysauce force-pushed the fix/issue-2758-coder-mutation-guidance branch from 00e1a84 to 7afc75e Compare September 14, 2026 21:41
@zaxbysauce
zaxbysauce force-pushed the fix/issue-2758-coder-mutation-guidance branch from 7afc75e to b8f2383 Compare September 14, 2026 21:57
@zaxbysauce

Copy link
Copy Markdown
Collaborator Author

Feedback closure — PR #2770 (round 2)

The requested feedback fixes are complete and verified on b8f2383164956ee1364f4059c40d7069c963f65e, rebased onto b865ba262f48e1ccfc966c054a5ce323b62a9cdb.

  • FIXED: FB-001 independent criticalWarn assertions; FB-002 explicit idle precondition; FB-003 stale test title; FB-004 release-owner base drift operationally; FB-009 publication evidence/body refresh; FB-010 branch rebase.
  • PRE-EXISTING / NON-BLOCKING: FB-005 ruleset drift notices.
  • DISPROVED: FB-006 Copilot informational review (no actionable finding).
  • CLEAN: FB-008 no inline review threads; FB-011 no generated or unrelated output.
  • EXTERNAL GATE: FB-007 requires a real GitHub reviewer approval; no self-approval or admin bypass was used.

Verification completed:

  • Frozen four-file acceptance slice: 18 passed, 0 failed, 50 assertions (plus per-file isolation).
  • bun run typecheck, bun run build, scoped Biome, bun run check:invariants, bun run check:mock-cleanup, bun run check:test-file-cap, and bun run check:pending-fragment: passed.
  • GitHub CI run 34901587404 is green on the exact head: quality, release-owner, unit shards, coverage, integration, package-check, security, smoke, drift, and platform regressions all passed.
  • GLM 5.3 high-effort final critic: APPROVE_WITH_NOTES; no blocking defect. Independent closeout reviewer: PASS.

No review threads were resolved because none were present and no thread-resolution authorization was supplied. The remaining blocker is GitHub approval before merge.

@zaxbysauce

Copy link
Copy Markdown
Collaborator Author

🤖 Multi-Stage PR Review

Pipeline: MiniMax-M2.7-highspeed (orientation) (context pack) → MiniMax-M2.7-highspeed (explorer) + MiniMax-M2.7-highspeed (explorer B) (parallel explore, distinct lenses) → GLM-5-turbo (critique) ↔ GLM-5-turbo (critique) (cross-critique) → MiniMax-M2.7-highspeed (fallback arbiter) (arbiter: blind-spot + synthesize)
Commit reviewed: b8f238316495


Phase 0 — Reconstruct Intent

O-001: Split TASK_WORKFLOW_CODER_MUTATION_REQUIRED into its own classification set, separate from STAGE_A_ATTRIBUTION_MISS_CODES
O-002: Route coder-mutation errors to actionable messaging ("dispatch a coder, or mark blocked") in both pass-side and fail-side catch blocks
O-003: Preserve attribution-recovery routing and /swarm recover guidance for TASK_WORKFLOW_STAGE_A_REQUIRED
O-004: Add regression coverage for both pass-side and fail-side coder-mutation paths
O-005: Add classification guardrail ensuring the two sets are disjoint and exhaustive against reducer error codes


Phase 1 — Summarise Actual Behaviour

The PR adds a new STAGE_A_CODER_MUTATION_REQUIRED_CODES set containing only TASK_WORKFLOW_CODER_MUTATION_REQUIRED, and splits the catch blocks in toolAfter (pass side, index.ts:1319) and toolAfter (fail side, index.ts:1262) so coder-mutation errors push category-correct advisories and criticalWarn logs with the "dispatch a coder / mark blocked" message, while TASK_WORKFLOW_STAGE_A_REQUIRED continues to route to the /swarm recover path. Four new/updated test files pin the routing, message content, and classification disjointness.


Phase 2 — Compare Intended vs Actual

Obligation Status Evidence
O-001 SUPPORTED index.ts:207–210 — new STAGE_A_CODER_MUTATION_REQUIRED_CODES set
O-002 SUPPORTED index.ts:1265–1275 (fail-side), index.ts:1339–1349 (pass-side) — both push advisory + log with coder-mutation guidance
O-003 SUPPORTED index.ts:1277–1281 (fail-side), index.ts:1351–1355 (pass-side) — attribution-miss keeps /swarm recover
O-004 SUPPORTED guardrails-stage-a-coder-mutation-failure.test.ts (fail-side), guardrails-durable-stage-a-attribution.test.ts:385–431 (pass-side)
O-005 SUPPORTED stage-a-error-classification.test.ts:82–100 — disjointness and exhaustive reducer-code coverage

Phase 3 — Review Confirmed Findings

[MEDIUM] "Stage A fail catch block pushes advisory only for coder-mutation errors but NOT for attribution-miss errors"

Challenge: The diff at index.ts:1265–1281 shows both branches push advisories:

  • Coder-mutation: index.ts:1271–1274pushAdvisory(session, …)
  • Attribution-miss: index.ts:1277–1281pushAdvisory(session, …)
    Verdict: DROP — finding is refuted by the diff. Both routes push.

[MEDIUM] "Stage A pass catch block pushes advisory only for coder-mutation errors but NOT for attribution-miss errors"

Challenge: Same diff at index.ts:1339–1355 shows both branches push:

  • Coder-mutation: index.ts:1344–1347
  • Attribution-miss: index.ts:1351–1355
    Verdict: DROP — refuted by the diff. Both routes push.

[MEDIUM] "Stage A fail catch: session not null-checked before pushAdvisory"

Challenge: At both catch sites, session comes from const session = swarmState.agentSessions.get(sessionID) which is preceded by const session = ensureAgentSession(…) at index.ts:1239. ensureAgentSession always returns a non-null session. Additionally, the pass-side catch (index.ts:1339) calls pushAdvisory(session, …) with no guard at all — if the finding were real, the pass-side is equally broken, but no reviewer flagged it.
Verdict: DROP — pre-existing structural guarantee; no + line introduces a null risk.

[MEDIUM] "Stage A pass catch: session not null-checked before pushAdvisory"

Challenge: Same as above — ensureAgentSession always returns non-null. The entire catch block is inside the session closure scope.
Verdict: DROP — pre-existing structural guarantee.

[MEDIUM] "stageAWriteErrorCode returns null/undefined, unclear if legitimate"

Challenge: stageAWriteErrorCode returning null is documented and intentional (non-TASK_WORKFLOW errors fall through silently, which is pre-existing and out-of-scope for this PR). The && guard is correct TypeScript — null && … is falsy and skips the block.
Verdict: DROP — intentional design, not a defect introduced by this PR.

[MEDIUM] "optionalCoderMutationCodes returns empty Set causing vacuous pass"

Challenge: The primary assertion in this test (STAGE_A_ATTRIBUTION_MISS_CODES.has(…)) is a direct export check — it fails immediately if the export is absent. The secondary assertion via optionalCoderMutationCodes() is a sanity cross-check. Additionally, stage-a-error-classification.test.ts:82–86 has a hard equality assertion (toEqual(new Set(['TASK_WORKFLOW_CODER_MUTATION_REQUIRED']))) that would catch a missing/renamed export.
Verdict: DROP — primary coverage is sound; secondary cross-check is supplementary.

[MEDIUM] "test for attribution-miss fail-side write failure is missing after rename"

Challenge: The test "attribution-miss write failure…" (guardrails-durable-stage-a-attribution.test.ts:354) has always tested the coder-mutation path (its own comment says "NO durable coder_delegated evidence" → throws TASK_WORKFLOW_CODER_MUTATION_REQUIRED). The rename corrects the label, it doesn't remove coverage. stage-a-error-classification.test.ts:87–100 provides direct unit-test coverage for STAGE_A_ATTRIBUTION_MISS_CODES classification.
Verdict: DROP — rename is accurate; classification coverage exists elsewhere.

[LOW] "test name semantic drift"

Challenge: See above — the original name was inaccurate; the rename is correct.
Verdict: DROP — no drift; name was wrong before.

[LOW] "find() may return undefined before .includes()"

Challenge: Bun's toBeDefined() matcher fails before the .includes() call is reached. This is a pre-existing pattern used across all test files in this suite.
Verdict: DROP — pre-existing test idiom in unchanged test infrastructure.

[LOW] "advisory.toContain('before Stage A') mismatch"

Challenge: The implementation says "before Stage A can be recorded again". toContain('before Stage A') is a substring match — the longer string contains the shorter one, so the assertion passes correctly.
Verdict: DROP — substring match is valid; no mismatch.

[LOW] "getCoderMutationCodes vacuous pass"

Challenge: Same as optionalCoderMutationCodes — mitigated by stage-a-error-classification.test.ts:82–86 which uses toEqual (hard equality, not has). Cannot pass vacuously at that assertion.
Verdict: DROP — mitigated by hard equality guard in companion test.

[LOW] "advisory spam from dual pushAdvisory + criticalWarn"

Challenge: pushAdvisory deduplicates messages against pendingAdvisoryMessages before appending. The "spam" scenario requires the same error to be thrown on every tool call without ever being cleared — an extreme edge case not introduced by this PR's routing change.
Verdict: DROP — not structurally introduced by this PR; deduplication is upstream.


Phase 4 — Runtime-Aware False-Positive Control

All findings about session null-checks, vacuous test assertions, and advisory spam are either structurally guaranteed safe (session), mitigated by companion hard-equality tests (vacuous pass), or dependent on upstream deduplication behavior not in scope of this PR (spam).


Phase 5 — Blind-Spot Pass

Potential: silent swallow of unrecognized error codes. Both catch blocks use:

if (code && STAGE_A_CODER_MUTATION_REQUIRED_CODES.has(code)) {  }
else if (code && STAGE_A_ATTRIBUTION_MISS_CODES.has(code)) {  }
// no else — null/unknown codes silently fall through

An error whose message does not start with a TASK_WORKFLOW_* code is silently swallowed. However, the reducer in src/gate-evidence.ts throws only TASK_WORKFLOW_* codes (verified by the exhaustiveness test), and transitionTaskWorkflowEvidence and stage_a_failed wrap all reducer errors through the same extractErrorCode path. This is pre-existing behavior, entirely outside the PR's scope (fixing coder-mutation guidance), and is a latent concern in the broader guardrail error-handling architecture rather than a PR-introduced regression. I will note it as unverified but plausible rather than confirmed.


🔍 PR Intent

  • O-001 Split TASK_WORKFLOW_CODER_MUTATION_REQUIRED into its own classification set
  • O-002 Route coder-mutation errors to actionable "dispatch coder / mark blocked" guidance in both pass and fail catch blocks
  • O-003 Preserve attribution-recovery /swarm recover routing for TASK_WORKFLOW_STAGE_A_REQUIRED
  • O-004 Add regression coverage for both coder-mutation paths (pass-side and fail-side)
  • O-005 Add classification guardrail ensuring disjoint, exhaustive error-code coverage

📦 Implementation Summary

Adds STAGE_A_CODER_MUTATION_REQUIRED_CODES (index.ts:207–210) and bifurcates both the pass-side (index.ts:1319) and fail-side (index.ts:1262) catch blocks so TASK_WORKFLOW_CODER_MUTATION_REQUIRED receives category-correct guidance while TASK_WORKFLOW_STAGE_A_REQUIRED retains its attribution-recovery path. Four test files pin routing correctness, message content, and classification disjointness.


✅ / ⚠️ / ❌ Intended vs Actual

Obligation Status Evidence (file:line)
O-001 SUPPORTED src/hooks/guardrails/index.ts:207–210
O-002 SUPPORTED src/hooks/guardrails/index.ts:1265–1275, 1339–1349
O-003 SUPPORTED src/hooks/guardrails/index.ts:1277–1281, 1351–1355
O-004 SUPPORTED guardrails-stage-a-coder-mutation-failure.test.ts, guardrails-durable-stage-a-attribution.test.ts:385–431
O-005 SUPPORTED stage-a-error-classification.test.ts:82–100

🚨 Confirmed Findings

None — all 13 confirmed findings were successfully refuted by diff analysis or determined to be pre-existing, non-PR-introduced concerns.


🔬 Unverified but Plausible Risks

  • Risk: Unknown error codes (non-TASK_WORKFLOW_* messages from the reducer or wrapped I/O errors) are silently swallowed in both catch blocks with no logging or advisory.
    • Why suspicious: if (code && …) guards mean code === null falls through both branches with no else-clause. The exhaustiveness test in stage-a-error-classification.test.ts covers known reducer codes only; it does not guard against an unexpected thrown object or I/O error leaking through.
    • What would verify it: Instrument both catch blocks with an else-branch that logs at warn level (e.g., [guardrails] Stage A write failed with unrecognized code null); add a test exercising the I/O error path directly.

🧪 Test / Coverage Gaps

  • Gap: No integration test exercises the attribution-miss /swarm recover advisory end-to-end through either catch block. The classification is covered by stage-a-error-classification.test.ts (direct unit assertions) but not by an advisory-content integration test.
    • Evidence: guardrails-durable-stage-a-attribution.test.ts covers coder-mutation pass/fail paths end-to-end; stage-a-attribution-classification-preserving.test.ts covers attribution-miss classification directly but not through the hook.
    • Note: This is a pre-existing gap; the PR does not reduce coverage.

📋 Shipped-vs-Claimed Gaps

None.


📝 Merge Recommendation

[APPROVE]

The PR correctly splits the error classification, routes both catch blocks to category-appropriate messaging, adds comprehensive regression coverage for the coder-mutation path, and preserves attribution-recovery semantics. All 13 confirmed findings were refuted by the diff — they described the pre-fix code state, not PR-introduced defects.

Check Result
No CRITICAL findings
No unresolved STEALTH_CHANGE
No UNSUPPORTED obligations
Test coverage adequate
No hardcoded secrets
All async errors handled
Input validation present
No broken agent role boundaries
Prompt format contracts intact
Lockfile consistent ✅ (unchanged)

🔁 Validation provenance

Confirmed findings DROPPED (13/13):

  • F1–F2 (missing advisories on fail/pass): refuted by diff — both branches push advisory
  • F3–F4 (null session): refuted — ensureAgentSession always returns non-null; pre-existing structural guarantee
  • F5 (null code guard): refuted — && guard is intentional and correct; pre-existing
  • F6 (vacuous optionalCoderMutationCodes): dropped — primary assertion is direct export check; mitigated by hard equality in companion test
  • F7 (attribution-miss test coverage after rename): dropped — original test always exercised coder-mutation path; rename is accurate; classification covered in companion file
  • F8–F14 (test name drift, undefined find, substring mismatch, vacuous getCoderMutationCodes, advisory spam): all dropped — pre-existing idiom, correct substring semantics, upstream deduplication, or mitigated by companion test

Blind-spot findings added (1):

  • Silent swallow of unrecognized error codes — pre-existing latent concern, outside PR scope, noted as unverified but plausible

🔒 Reviewed by a 3-model cross-family adversarial debate (architect → dual-lens parallel explorers → cross-critique → arbiter) for high recall with low false-positive noise. Findings are advisory — verify before acting.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Guardrails label TASK_WORKFLOW_CODER_MUTATION_REQUIRED as an attribution miss and advise the human-only /swarm recover

3 participants