You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
src/services/linked-issue-satisfaction.ts (the pure AI-backed analysis core) plus its orchestration in src/queue/processors.ts produce a real, live, AI-confidence-driven finding — code: "linked_issue_scope_mismatch" — that hard-blocks a PR when args.settings.linkedIssueSatisfactionGateMode === "block" and the assessed result.result.status === "unaddressed" (see the args.advisory.findings.push({ code: "linked_issue_scope_mismatch", ... }) call in processors.ts, guarded by exactly that condition; the comment immediately above it confirms advisory mode never pushes this finding at all).
Unlike every deterministic rule this epic's other sub-issues (#8083-#8088) were built to backtest, this finding has never been wired into the shared calibration module (#7982, packages/loopover-engine/src/calibration/signal-tracking.ts). Confirmed by grep: there are zero calls to createSignalStore(env).recordRuleFired anywhere in src/review/ or src/queue/ today — the ORB-side adapter (src/review/signal-tracking-wire.ts) exists but has no live caller yet. That means this specific, gate-authority-bearing AI judgment is currently invisible to both the reactive self-correction system (#7980/#7983/#7984/#7986) and this epic's own backtest primitives.
⚠️ Scope. This issue wires exactly one finding code (linked_issue_scope_mismatch) and exactly one override direction (reversal). It does NOT implement a "confirmed" override path — see Boundaries below for why.
Requirements
In src/queue/processors.ts, inside the existing if (args.settings.linkedIssueSatisfactionGateMode === "block" && result.result.status === "unaddressed") block (the one that pushes the linked_issue_scope_mismatch finding), also call createSignalStore(env).recordRuleFired(...) — import createSignalStore from ../review/signal-tracking-wire. Pass:
(nowIso from ../utils/json, matching signal-tracking-wire.ts's own usage.)
Wrap the call in .catch(() => undefined) — best-effort, matching the fail-open discipline the immediately-adjacent cache-write-error handling in this same function already uses, and SignalStore's own doc comment ("every write is best-effort... a failure to record a signal must never fail the review pass that produced it"). A recordRuleFired failure must never throw out of this code path.
Do not record anything outside this if block — no finding fires in advisory mode or for any status other than "unaddressed", so nothing should be recorded in those cases either.
In src/review/outcomes-wire.ts's recordReversalSignals, wire a reversal override: when this function is about to record a reversal for a targetId (both the immediate contributor-reopen reversal_reopened path, and the owner reopen-then-merge path from Count an owner reopen-then-merge as a real reversal signal, not administrative noise #7985), first check whether a linked_issue_scope_mismatchRuleFiredEvent exists for that same target — call createSignalStore(env).queryRuleHistory("linked_issue_scope_mismatch", sinceMs) (a lookback window of 30 days in milliseconds is sufficient; a stricter/looser window is an acceptable implementation choice as long as it is a fixed constant, not configurable in this PR) and check whether any entry's targetKey matches targetId. If it does, additionally call recordHumanOverride({ ruleId: "linked_issue_scope_mismatch", targetKey: targetId, verdict: "reversed", occurredAt: nowIso() }) on the same createSignalStore(env) instance. Same best-effort .catch(() => undefined) discipline as above — this must never affect whether the underlying reversal itself is recorded.
Deliverables
recordRuleFired call wired at the finding-push site in src/queue/processors.ts.
recordHumanOverride (verdict "reversed") wired into recordReversalSignals in src/review/outcomes-wire.ts, gated on a prior linked_issue_scope_mismatch fired event existing for the same target.
Tests extending the existing test file(s) for the touched functions, covering: a fired event is recorded exactly when the finding is pushed (mode "block" + status "unaddressed"); no fired event when mode is "advisory"/"off" or status is "addressed"/"partial"; a "reversed" override is recorded when a PR with a prior fired event is reopened-then-merged (both the immediate-contributor-reopen and owner-reopen-then-merge paths); no override recorded when a PR is reopened with no prior fired event for that target; both new write paths degrade silently (no thrown error, no effect on the surrounding function's normal return value) when the SignalStore call rejects.
Test Coverage Requirements
99%+ patch coverage (branch-counted), this repo's standard gate. Cover both new call sites and their fail-open .catch branches explicitly — a test that never exercises the SignalStore failure path does not satisfy this requirement.
Expected Outcome
linked_issue_scope_mismatch's fired + reversed history becomes real, queryable data in the shared calibration module — usable immediately by #7983/#7984 (once built) for reactive per-rule detection, and by this epic's own backtest primitives (#8083-#8086) for prospective validation of a proposed change to this specific AI judgment (e.g. a different confidence floor or prompt), with zero new scoring code required.
src/services/linked-issue-satisfaction.ts + src/queue/processors.ts (the finding this wires — read both before starting)
Boundaries
No "confirmed" override path in this issue. There is currently no existing signal anywhere in this codebase for "a maintainer explicitly confirmed an automated close/block was correct," for this or any other rule (verified: recordReversalSignals only ever detects disagreement/reversal, never confirmation — there is no positive-confirmation precedent to mirror). Inventing one (a maintainer command, an explicit approval action, or any other new UX) is a product-design decision that belongs in a separate, maintainer-scoped issue, not folded into this mechanical wiring change. No change to linkedIssueSatisfactionGateMode's own blocking behavior — this issue only records data about decisions already being made, it does not change what gets blocked.
Context
src/services/linked-issue-satisfaction.ts(the pure AI-backed analysis core) plus its orchestration insrc/queue/processors.tsproduce a real, live, AI-confidence-driven finding —code: "linked_issue_scope_mismatch"— that hard-blocks a PR whenargs.settings.linkedIssueSatisfactionGateMode === "block"and the assessedresult.result.status === "unaddressed"(see theargs.advisory.findings.push({ code: "linked_issue_scope_mismatch", ... })call inprocessors.ts, guarded by exactly that condition; the comment immediately above it confirmsadvisorymode never pushes this finding at all).Unlike every deterministic rule this epic's other sub-issues (#8083-#8088) were built to backtest, this finding has never been wired into the shared calibration module (#7982,
packages/loopover-engine/src/calibration/signal-tracking.ts). Confirmed by grep: there are zero calls tocreateSignalStore(env).recordRuleFiredanywhere insrc/review/orsrc/queue/today — the ORB-side adapter (src/review/signal-tracking-wire.ts) exists but has no live caller yet. That means this specific, gate-authority-bearing AI judgment is currently invisible to both the reactive self-correction system (#7980/#7983/#7984/#7986) and this epic's own backtest primitives.Requirements
src/queue/processors.ts, inside the existingif (args.settings.linkedIssueSatisfactionGateMode === "block" && result.result.status === "unaddressed")block (the one that pushes thelinked_issue_scope_mismatchfinding), also callcreateSignalStore(env).recordRuleFired(...)— importcreateSignalStorefrom../review/signal-tracking-wire. Pass:nowIsofrom../utils/json, matchingsignal-tracking-wire.ts's own usage.).catch(() => undefined)— best-effort, matching the fail-open discipline the immediately-adjacent cache-write-error handling in this same function already uses, andSignalStore's own doc comment ("every write is best-effort... a failure to record a signal must never fail the review pass that produced it"). ArecordRuleFiredfailure must never throw out of this code path.ifblock — no finding fires inadvisorymode or for any status other than"unaddressed", so nothing should be recorded in those cases either.src/review/outcomes-wire.ts'srecordReversalSignals, wire a reversal override: when this function is about to record a reversal for atargetId(both the immediate contributor-reopenreversal_reopenedpath, and the owner reopen-then-merge path from Count an owner reopen-then-merge as a real reversal signal, not administrative noise #7985), first check whether alinked_issue_scope_mismatchRuleFiredEventexists for that same target — callcreateSignalStore(env).queryRuleHistory("linked_issue_scope_mismatch", sinceMs)(a lookback window of 30 days in milliseconds is sufficient; a stricter/looser window is an acceptable implementation choice as long as it is a fixed constant, not configurable in this PR) and check whether any entry'stargetKeymatchestargetId. If it does, additionally callrecordHumanOverride({ ruleId: "linked_issue_scope_mismatch", targetKey: targetId, verdict: "reversed", occurredAt: nowIso() })on the samecreateSignalStore(env)instance. Same best-effort.catch(() => undefined)discipline as above — this must never affect whether the underlying reversal itself is recorded.Deliverables
recordRuleFiredcall wired at the finding-push site insrc/queue/processors.ts.recordHumanOverride(verdict"reversed") wired intorecordReversalSignalsinsrc/review/outcomes-wire.ts, gated on a priorlinked_issue_scope_mismatchfired event existing for the same target."block"+ status"unaddressed"); no fired event when mode is"advisory"/"off"or status is"addressed"/"partial"; a"reversed"override is recorded when a PR with a prior fired event is reopened-then-merged (both the immediate-contributor-reopen and owner-reopen-then-merge paths); no override recorded when a PR is reopened with no prior fired event for that target; both new write paths degrade silently (no thrown error, no effect on the surrounding function's normal return value) when theSignalStorecall rejects.Test Coverage Requirements
99%+ patch coverage (branch-counted), this repo's standard gate. Cover both new call sites and their fail-open
.catchbranches explicitly — a test that never exercises theSignalStorefailure path does not satisfy this requirement.Expected Outcome
linked_issue_scope_mismatch's fired + reversed history becomes real, queryable data in the shared calibration module — usable immediately by #7983/#7984 (once built) for reactive per-rule detection, and by this epic's own backtest primitives (#8083-#8086) for prospective validation of a proposed change to this specific AI judgment (e.g. a different confidence floor or prompt), with zero new scoring code required.Links & Resources
src/review/signal-tracking-wire.ts(createSignalStore, the adapter to call — already built, do not create a second one)src/review/outcomes-wire.ts(recordReversalSignals, and Count an owner reopen-then-merge as a real reversal signal, not administrative noise #7985's own owner-reopen-then-merge design to mirror the target-matching logic against)src/services/linked-issue-satisfaction.ts+src/queue/processors.ts(the finding this wires — read both before starting)Boundaries
No "confirmed" override path in this issue. There is currently no existing signal anywhere in this codebase for "a maintainer explicitly confirmed an automated close/block was correct," for this or any other rule (verified:
recordReversalSignalsonly ever detects disagreement/reversal, never confirmation — there is no positive-confirmation precedent to mirror). Inventing one (a maintainer command, an explicit approval action, or any other new UX) is a product-design decision that belongs in a separate, maintainer-scoped issue, not folded into this mechanical wiring change. No change tolinkedIssueSatisfactionGateMode's own blocking behavior — this issue only records data about decisions already being made, it does not change what gets blocked.