Summary
The audit found the same guard-drift pattern that #9399 fixed, still live in four more places. In each case a protection was added to the N instances someone grepped for, and the N+1th sibling — usually in the same file — was missed. This is a recurring class, not four coincidences, so the deliverables include a structural check as well as the four fixes.
The four live instances
1. Linked-issue satisfaction advisory has no commit-threshold cap (highest cost)
src/queue/processors.ts:8449's early-return set lacks commitThresholdReached. Both siblings have it — src/queue/slop-detection.ts:60,66 (via d66d17cea) and the ai_review path via focus-manifest.ts:205. Result: unbounded per-push LLM spend on long-lived PRs, at exactly the point where the other two advisory runners stop.
2. #9398's webhook-redelivery guard missed the panel-checkbox twins
#9398/#9399 established (same day) that the checkbox and text-command siblings must share redelivery guards. These four still lack it:
maybeProcessPrPanelGenerateTests (processors.ts:14211) — the text twin has it at :13504-13505
maybeProcessPrPanelRetrigger (:13995)
maybeProcessGateOverrideCommand (:12906)
maybeProcessResolveCommand (:13171)
A redelivered checkbox payload therefore re-pushes a second E2E test commit, re-runs a paid review, or re-writes an overridden check-run.
3. resolveCopycatDuplicateSibling open-state pre-filter present in 1 of 3 call sites
src/queue/duplicate-detection.ts:195 has it; duplicate-detection.ts:97 and src/rules/advisory.ts:1092 do not, and the callee documents the invariant as caller-owned (src/signals/copycat-duplicate.ts:32). advisory.ts has no downstream re-check, so a stale-cached-open, actually-merged sibling can mint a corroborated duplicate_pr_risk blocker.
4. ai-slop-cache-input.ts fingerprint omits its own routing salt
src/queue/slop-detection.ts:137 swaps env.AI → env.AI_ADVISORY, but the fingerprint (src/review/ai-slop-cache-input.ts:20-29) carries only BYOK identity — null on the shared path. Flipping advisory_ai_routing.slop therefore replays the other model's cached advisory at an unchanged head. The rule it violates is written down in its own sibling (src/review/ai-review-cache-input.ts:65-69).
Two more, lower severity
The structural fix (the actual point of this issue)
Fixing four siblings without changing the shape guarantees a fifth. Deliverables:
Expected outcome
The four live drifts are closed, and the two structures that generate them (a 16k-line file of near-identical handlers, and three parallel advisory runners) stop being able to.
Summary
The audit found the same guard-drift pattern that #9399 fixed, still live in four more places. In each case a protection was added to the N instances someone grepped for, and the N+1th sibling — usually in the same file — was missed. This is a recurring class, not four coincidences, so the deliverables include a structural check as well as the four fixes.
The four live instances
1. Linked-issue satisfaction advisory has no commit-threshold cap (highest cost)
src/queue/processors.ts:8449's early-return set lackscommitThresholdReached. Both siblings have it —src/queue/slop-detection.ts:60,66(viad66d17cea) and the ai_review path viafocus-manifest.ts:205. Result: unbounded per-push LLM spend on long-lived PRs, at exactly the point where the other two advisory runners stop.2. #9398's webhook-redelivery guard missed the panel-checkbox twins
#9398/#9399 established (same day) that the checkbox and text-command siblings must share redelivery guards. These four still lack it:
maybeProcessPrPanelGenerateTests(processors.ts:14211) — the text twin has it at:13504-13505maybeProcessPrPanelRetrigger(:13995)maybeProcessGateOverrideCommand(:12906)maybeProcessResolveCommand(:13171)A redelivered checkbox payload therefore re-pushes a second E2E test commit, re-runs a paid review, or re-writes an overridden check-run.
3.
resolveCopycatDuplicateSiblingopen-state pre-filter present in 1 of 3 call sitessrc/queue/duplicate-detection.ts:195has it;duplicate-detection.ts:97andsrc/rules/advisory.ts:1092do not, and the callee documents the invariant as caller-owned (src/signals/copycat-duplicate.ts:32).advisory.tshas no downstream re-check, so a stale-cached-open, actually-merged sibling can mint a corroboratedduplicate_pr_riskblocker.4.
ai-slop-cache-input.tsfingerprint omits its own routing saltsrc/queue/slop-detection.ts:137swapsenv.AI → env.AI_ADVISORY, but the fingerprint (src/review/ai-slop-cache-input.ts:20-29) carries only BYOK identity — null on the shared path. Flippingadvisory_ai_routing.sloptherefore replays the other model's cached advisory at an unchanged head. The rule it violates is written down in its own sibling (src/review/ai-review-cache-input.ts:65-69).Two more, lower severity
@loopover reviewon a closed PR posts "Re-review triggered" and then no-ops (processors.ts:13253) — its two family members got this guard in orb(review): panel re-run checkbox on a closed/merged PR does real work, then silently no-ops #9020/queue: generate-tests checkbox and text-command both missing the pr.state !== "open" guard #9020 added to their retrigger sibling #9399.src/review/surface-disposition-reconciler.ts:79-90lacks the requeue-interval guard its sweep-tick sibling has (src/review/pending-closure-watchdog.ts:87-94), so it can enqueue up to 200 redundant regate jobs per tick behind an unclaimable lock.The structural fix (the actual point of this issue)
Fixing four siblings without changing the shape guarantees a fifth. Deliverables:
processors.tsdiffering mainly in payload extraction. Put them behind one dispatch that owns state checks, redelivery dedup and rate limiting once. This structurally retires instance 2 and the closed-PR item.run*ForAdvisoryrunners should share one precondition struct (commit threshold, pause/freeze, mode, budget). Retires instance 1.scripts/check-import-specifiers.tsis a working template. A check that flags "N call sites of X where M < N apply guard Y" is hard in general, but the specific high-value case — every handler in the command dispatch table declaring its guard set explicitly — is cheap once the dispatch exists.Expected outcome
The four live drifts are closed, and the two structures that generate them (a 16k-line file of near-identical handlers, and three parallel advisory runners) stop being able to.