fix(mt#3864): Measure why pre-narration's window misses, then fix what that supports - #3096
Conversation
…y what the measurement supports
The spec's dominant-class remedy rested on a false premise: that the detector
"requires the evidencing tool call in the SAME turn". It does not —
TRAILING_WINDOW_TURNS = 12 and extractWindowToolUseNames have implemented
cross-turn suppression since mt#2671, and the constant's own docblock names the
wait-for-review → fix → push → back-reference sequence as what it was sized for.
So "widen the evidence window" would have widened something already wide.
scripts/diagnose-pre-narration-window.ts replays each injected calibration record
against its own session transcript, walking real-user-prompt boundaries exactly
as the detector does. Over 2026-08-13→18: ZERO beyond-window. All 17
non-tool-backed matches are tool-absent — the required tool appears nowhere
before the fire. That splits into two causes:
Cause A (4 of 17) — the agent verified by READING PR state while the category
lists only the tool that PERFORMS the action. Both measured cases ("PR #3033
merged", "PR #3064 merged") had pull_request_read and session_pr_get in window
and no merge tool, because another actor did the merge. Fixed by accepting
those two read tools as evidence for `merged`. Deliberately NOT the list-shaped
tools that were also in window: a listing establishes no particular PR's state.
Cause B (13 of 17) — no PR tool in window in any form; one session carries zero
PR-evidence calls across its whole 1,496-line transcript while reporting
APPROVED. NOT tuned here, on purpose: review-approved's premise (a verdict is
always learned through one of five named tools) is false in a way no phrase or
window edit repairs, and widening until it stops firing is ADR-024 §Context's
arms race. Recorded for the next calibration pass to flip/restrict/retire, per
mem#1067 §4 and ADR-032.
Class 6 (quotation) fixed by composing elideDoubleQuotedSpans onto the local
markdown elision — the local pass covers fences/spans/blockquotes but not
double-quoted prose. Composed rather than swapping wholesale to
elideQuotedAndCodeContexts because elision.ts's own header scopes that
consolidation to mt#2263.
Re-measured after the fix: `merged` went 4 tool-absent → 2 tool-absent +
2 within-window, exactly the two Cause-A cases and no more.
Minsky Reviewer StatusVerdict: APPROVED — no blocking findings Commands
|
There was a problem hiding this comment.
Independent adversarial review (Chinese-wall)
Reviewer: minsky-reviewer[bot] via openai:gpt-5
Tier: 2
Requesting changes. The PR’s quoted-text elision is a focused, well-tested improvement, and the diagnostic script is useful. However, widening merged.requiredTools to include read-shaped tools without correlating to the SAME PR number creates an over-broad suppression path: any pull_request_read/session_pr_get within the 12-turn window can silence an unbacked merged claim about a different PR, yielding false negatives. Add PR-identity correlation (or revert the widening) and pin it with a test. Separately, tests added for mt#3864 don’t pin all six FP contexts from the spec’s AT1; either add fixtures or clearly mark the deferral. Fresh-window FP metrics are unverifiable from the diff, which is acceptable, but the cross-PR suppression risk must be addressed before merge.
Findings
- [BLOCKING] .minsky/hooks/pre-narration-detector.ts:212 — Over-broad suppression for
merged: treating any PR "read" as evidence risks silencing real pre-narration (no PR-identity correlation)
This PR widensmerged.requiredToolsto include read-shaped tools —pull_request_readandsession_pr_get— and updatesexpectedToolaccordingly (.minsky/hooks/pre-narration-detector.ts:210-236). The suppression gate checks only for the PRESENCE of any of these tool names in the same turn or trailing window; it does not correlate the read to the specific PR number referenced in the claim. As a result, a routinepull_request_readagainst PR A inside the 12-turn window will suppress amergedclaim about PR B, even when B has no supporting evidence — converting a true pre-narration fire into a false negative. The added tests assert the intended Cause-A fix (reads suppress) and that list-shaped tools do not, but there is no guard against cross-PR suppression. If you keep this widening, the implementation must constrain suppression to the SAME PR (e.g., by extracting PR id from the claim and matching it against the read tool's parameters or result), or you must drop the read tools frommerged.requiredTools. At minimum, add a test that demonstrates the cross-PR case and fails today. - [NON-BLOCKING] .minsky/hooks/pre-narration-detector.test.ts:409 — Test set does not pin all six FP contexts from the spec; coverage gap vs. Acceptance Tests
Per the task spec's AT1, each of the six false-positive contexts in the Summary should be pinned as a test that must NOT fire. This diff adds tests for (a) class 6 (double-quoted artifact) and (b) Cause A formergedwith read-shaped tools, along with negative controls (.minsky/hooks/pre-narration-detector.test.ts:410-474). It does not add fixtures for the other classes (domain-literalAPPROVEDas enumerated data, "another actor" subject attribution, past-dated historical statement, own-reasoning-pass x2). If those were intentionally deferred (as the PR body suggests, with SC1–SC3 deferred to mt#4256), consider marking the unmet AT explicitly in the spec or adding minimal pinned fixtures now to close AT1.
Spec verification
| Criterion | Status | Evidence |
|---|---|---|
| The matcher does not fire on a token that is a quoted or enumerated domain literal — a GitHub review state (APPROVED, CHANGES_REQUESTED), a status name, or an identifier being discussed as data rather than asserted as an outcome. | Not Met | Only double-quoted spans are elided via elideDoubleQuotedSpans composed after elideMarkdownContexts (.minsky/hooks/pre-narration-detector.ts:177-181, :353-380). There is no handling for unquoted/“enumerated” literals (e.g., bare APPROVED used as a data value) beyond prior behavior. New tests cover only the quoted case (.minsky/hooks/pre-narration-detector.test.ts:410-430). |
| The matcher does not fire on a statement whose subject is not the agent — another session, a bot, another actor — where the sentence names that subject. | Not Met | No subject-attribution logic was added in this PR. The only behavior change is accepting read-shaped tools for merged and the quoted-text elision; nothing in detectPreNarration* performs subject extraction or attribution (.minsky/hooks/pre-narration-detector.ts:241-321). |
| The matcher does not fire on a past-dated historical statement carrying its own timestamp or relative-time anchor ("merged at 02:32", "created at 02:06", "after X"). | Not Met | No tense/timestamp heuristics were introduced. OUTCOME_CATEGORIES.patterns for merged/pr-created remain unchanged aside from tool lists; there is no date/relative-time filter (.minsky/hooks/pre-narration-detector.ts:210-240). |
| The matcher does not fire on text that is quoting a stored artifact (a memory body, a spec section, a prior message) rather than asserting in the agent's own voice. | Met | Double-quoted prose is now elided by composing elideDoubleQuotedSpans after the local markdown elision (.minsky/hooks/pre-narration-detector.ts:177-181, :353-380). Tests assert that "APPROVED, 0 blocking" inside quotes does not fire while the unquoted phrase still does (.minsky/hooks/pre-narration-detector.test.ts:410-430). |
| The five real/ambiguous classes still fire — verified by a negative control, not by inspection. | Met | Negative controls assert that unquoted APPROVED still fires (.minsky/hooks/pre-narration-detector.test.ts:424-430), merged still fires with no PR tools (:453-461), and earlier suite already pins build-test and review-approved firing with no tools (:47-96). |
| FP rate over a fresh review window drops below the ~20% flip bar, measured, not asserted. | Unverifiable | This requires post-merge production traffic. The PR adds a diagnostic script (scripts/diagnose-pre-narration-window.ts) and includes run output in the PR body, but a fresh-window measurement cannot be verified from the diff alone. |
| AT1 — Each of the six false-positive contexts quoted in ## Summary is pinned as a test that must NOT fire, using the verbatim text from the calibration log. | Not Met | Tests add coverage for class 6 (quoted memory line) and Cause A for merged (.minsky/hooks/pre-narration-detector.test.ts:410-474), but they do not pin all six contexts listed in the spec’s Summary (domain literal, another actor’s action, historical timeline, own reasoning pass x2, stored artifact). |
| AT2 — Negative control: at least three of the real/ambiguous contexts still fire, so the tune is demonstrated to narrow rather than disable. | Met | Multiple negative controls remain firing: unquoted APPROVED, merged without tools, build-test without tools — see .minsky/hooks/pre-narration-detector.test.ts:47-96, :424-430, :461-474. The suite overall continues to assert core categories still fire. |
| AT3 — Replaying the full existing .minsky/pre-narration-calibration.jsonl through the tuned matcher reports the before/after fire count, and the delta is accounted for class by class. | Unverifiable | A new utility (scripts/diagnose-pre-narration-window.ts) implements the replay/measurement, and the PR body quotes its output, but this is not executed within the test suite. The reviewer cannot run it from the diff; live-run evidence is present in prose only. |
Documentation impact
- no-update-needed — This PR changes internal detector logic (suppression tool list and elision) and tests, plus adds a diagnostic script. No public API, CLI flags, or user-facing routes are added or removed. I did not read any specific docs covering pre-narration behavior; based on the diff and paths changed, the behavior is internal-only. If there exists a doc that asserts the exact requiredTools set for the pre-narration detector, I did not verify it; in that case, follow-up doc review may be warranted.
There was a problem hiding this comment.
Independent adversarial review (Chinese-wall)
Reviewer: minsky-reviewer[bot] via openai:gpt-5
Tier: unknown
The prior BLOCKING issue remains: merged suppression now accepts read-shaped tools (pull_request_read, session_pr_get) based solely on their presence in the trailing window, without correlating the read to the SAME PR referenced by the claim. This can suppress a true pre-narration fire about PR B due to an unrelated read of PR A, producing false negatives. The widening appears in both .minsky/hooks/pre-narration-detector.ts and the generated .claude hook. To proceed, either implement PR-identity correlation (and pin it with a cross-PR test) or revert the widening for read-shaped tools. Aside from that, the quoted-text elision and diagnostic script look sound and low-risk. Documentation impact is internal-only.
Findings
- [BLOCKING] .minsky/hooks/pre-narration-detector.ts:177 — Over-broad suppression for
merged: read-shaped tools are accepted without correlating to the SAME PR, causing false negatives
This change widensOUTCOME_CATEGORIES['merged'].requiredToolsto includepull_request_read/session_pr_get(lines 177-196), butdetectPreNarrationWithSuppressionstill suppresses purely on the PRESENCE of any required tool name in the same turn or trailing window. There is no correlation of the read to the specific PR number referenced in themergedclaim. As a result, a routinepull_request_readagainst PR A within the 12-turn window can suppress amergedclaim about PR B that has no supporting evidence — turning a true pre-narration fire into a false negative. This is the same risk flagged in the prior round and it remains unaddressed here.
Required: either (a) constrain suppression to reads that target the SAME PR as the claim (e.g., parse the PR number from the claim and match it against the read tool's params/result), and pin this with a cross-PR test that currently fails; or (b) revert the widening to drop the read-shaped tools from merged.requiredTools. Note: the same widening appears in .claude/hooks/pre-narration-detector.ts and should be fixed there as well.
- [BLOCKING] .claude/hooks/pre-narration-detector.ts:180 — Same over-broad
mergedsuppression in generated hook: read-shaped tools added without PR-identity correlation
The generated hook mirrors the.minskychange by addingpull_request_readandsession_pr_gettoOUTCOME_CATEGORIES['merged'].requiredTools(lines 180-197) and broadeningexpectedTool. Suppression still triggers solely on the presence of any required tool name in the same turn or trailing window; there is no check that the read targets the SAME PR as the claim being suppressed. This can suppress amergedclaim about PR B because apull_request_readfor PR A happened within the last 12 real-user-prompt boundaries — creating false negatives.
Fix in lockstep with the .minsky source: either (a) implement PR-identity correlation and add a cross-PR test, or (b) revert the widening to drop read-shaped tools from merged.requiredTools. Ensure the generator/regeneration path keeps .claude in sync with the chosen fix.
Documentation impact
- no-update-needed — Internal-only change: detector suppression lists and elision behavior updated in
.minsky/.claude/hooks/pre-narration-detector*.ts, plus a developer diagnostic script underscripts/. No CLI, API, or user-visible behavior or docs were added/removed. I scanned for docs covering this hook and found none referenced by the diff; no existing public docs appear invalidated by this internal tune.
…im names The reviewer's BLOCKING finding is correct and the real data confirms it. R1 flagged that treating any PR "read" as evidence for `merged` has no PR-identity correlation, so a read of PR #100 would silence a false "PR #200 merged" — and reads are common enough that this would be the usual case, not a corner. Fixed by splitting the evidence sets rather than reverting. `requiredTools` keeps its name-match semantics for tools that PERFORM an outcome (rare, deliberate, and pre-existing). A new `identityScopedTools` holds the read-shaped tools, and they suppress only when the PR number the claim NAMES appears in the tool call's own input. A claim naming no PR number can never be identity-backed and deliberately falls through to firing — for a suppressor the safe degrade is MORE fires, never fewer (ADR-024's fail-to-Rung-1 invariant). The measurement is the vindication. Re-running the diagnostic with correlation applied drops the suppression from 2 to 1: "PR #3064 merged" -> identity-backed. That agent did read #3064. "PR #3033 merged" -> still fires, despite pull_request_read AND session_pr_get being in window. It read a DIFFERENT PR. So one of the two cases the name-match version "fixed" was a coincidence, which is precisely the failure R1 described, present in the recorded corpus. `scripts/diagnose-pre-narration-window.ts` gained the same correlation, or its before/after would have misreported the fix it exists to measure. Negative control: the R1 scenario is pinned as a test — a read of PR #100 does not suppress a claim about #3033 — alongside the no-number case and the pre-existing controls (no tool at all still fires; a list-shaped tool is still not evidence). 55 pass, 0 fail.
There was a problem hiding this comment.
Independent adversarial review (Chinese-wall)
Reviewer: minsky-reviewer[bot] via openai:gpt-5
Tier: 2
Re-verification of the fix commit confirms prior BLOCKING concerns about unscoped PR reads were addressed by introducing identity-scoped evidence, and class-6 quotation elision is correctly applied with tests. However, a new BLOCKING regression exists: identity-scoped suppression mixes scopes — it gates on any identity-scoped tool in the current/window span but accepts PR-number evidence gathered from the entire transcript, enabling mis-correlation (an old read of PR B can suppress a current claim about PR B when an in-window read of PR A sets the gate). Please bind PR-number extraction to the same window (or otherwise make both checks consult the same span) and add a test for the mixed A/B case. Other notes are non-blocking. Once corrected, I expect to approve.
Findings
-
[BLOCKING] .minsky/hooks/pre-narration-detector.ts:484 — Identity-scoped suppression can be incorrectly satisfied by an old PR read outside the window when any scoped tool ran in-window (mis-correlated evidence)
TheidentityBackedcheck combines two independent conditions: -
scopedToolRan— true when ANY identity-scoped tool name appears in the current turn or the 12-turn window (regardless of which PR that call addressed) — see.minsky/hooks/pre-narration-detector.ts:484-492. -
evidencePrNumbers— a set of PR numbers extracted from the ENTIRE transcript (extractPrNumbersForTools(lines, identityScopedToolNames())inrun()/main()at :663-671), not limited to the same window.
This allows a false positive suppression: if the window contains a read of PR A (so scopedToolRan is true), and the same session read PR B much earlier (so evidencePrNumbers contains B from long-ago lines), then a claim about PR B will be suppressed even though no identity-scoped read of B occurred within the current window. That contradicts the intent captured in comments (“a READ of the PR the claim names”) and weakens correlation back to session-history leakage.
Suggested fix: tie the identity correlation to the SAME span that justified scopedToolRan — e.g., compute the PR numbers from the same window slice you use for windowToolNames (or, more robustly, pair tool-name occurrences to their specific inputs within the 12-turn window), and require that the PR number match comes from those same in-window calls. Alternately, if you intend whole-transcript correlation, drop the scopedToolRan gate and instead bound the PR-number extraction to the 12-turn window directly so both tests consult the same scope.
Please also add a test capturing the mixed case (in-window read of PR A, prior transcript read of PR B, claim for PR B) to ensure it does NOT suppress.
- [NON-BLOCKING] .claude/hooks/pre-narration-detector.ts:487 — Duplicate implementation in generated copy risks drift; consider centralizing identity-scope logic
The.claude/hooks/pre-narration-detector.tscopy carries the same identity-scoped suppression logic and helpers as the.minsky/source. This mirrors the existing generated-file pattern, but the added helpers (extractClaimedPrNumber,extractPrNumbersForTools,identityScopedToolNames) increase the surface area to keep in sync. If generation is automatic, fine — but consider factoring these helpers into a shared module consumed by both copies to reduce drift risk. Non-blocking observation; no change required for this PR.
Spec verification
| Criterion | Status | Evidence |
|---|---|---|
| The matcher does not fire on a token that is a quoted or enumerated domain literal — a GitHub review state (APPROVED, CHANGES_REQUESTED), a status name, or an identifier being discussed as data rather than asserted as an outcome. | Not Met | This criterion (SC1) was explicitly deferred in the spec body to mt#4256 and is not implemented in this PR. The code changes focus on class-6 quotation elision and identity-scoped evidence for merged; no logic narrowing domain-literal enumeration is present in .minsky/hooks/pre-narration-detector.ts. Deferral recorded: “SC1 (domain literal) … are not implemented here and are tracked in mt#4256.” |
| The matcher does not fire on a statement whose subject is not the agent — another session, a bot, another actor — where the sentence names that subject. | Not Met | This criterion (SC2) is also explicitly deferred to mt#4256 in the current spec and is not implemented by the changes in this PR. No subject-attribution filter is added to .minsky/hooks/pre-narration-detector.ts; the work here instead separates identity-scoped evidence vs tool-absent residual (Cause B). Deferral recorded in the spec: “[sc2-deferred: mt#4256]”. |
| The matcher does not fire on a past-dated historical statement carrying its own timestamp or relative-time anchor ("merged at 02:32", "created at 02:06", "after X"). | Not Met | This criterion (SC3) is likewise explicitly deferred to mt#4256 per the spec. No tense/timestamp gate is added in the diff; the PR confines itself to class-6 elision and identity-scoped suppression for merged. Deferral tag present: “[sc3-deferred: mt#4256]”. Follow-up required in mt#4256. |
| The matcher does not fire on text that is quoting a stored artifact (a memory body, a spec section, a prior message) rather than asserting in the agent's own voice. | Met | Double-quoted prose is now elided by composing elideDoubleQuotedSpans after the local markdown elision: .minsky/hooks/pre-narration-detector.ts:446-454. Tests pin the behavior: .minsky/hooks/pre-narration-detector.test.ts:688-706 (quoted APPROVED does not fire; unquoted fires as a negative control). |
| The five real/ambiguous classes above still fire — verified by a negative control, not by inspection. | Met | Negative controls are present to ensure tuning narrows rather than disables: .minsky/hooks/pre-narration-detector.test.ts:702-706 (unquoted still fires) and .minsky/hooks/pre-narration-detector.test.ts:751-759 (no PR tool → merge claim still fires), plus .minsky/hooks/pre-narration-detector.test.ts:766-774 (list-shaped tools do not suppress). |
| FP rate over a fresh review window drops below the ~20% flip bar, measured, not asserted. | Not Met | This criterion (SC6 original) was restated in the spec to a pre-merge corpus replay (see the “PREMISE CORRECTION” section). The fresh-window threshold cannot be satisfied pre-merge. The PR implements the replay tool scripts/diagnose-pre-narration-window.ts, but the fresh-window FP-rate reduction itself is out of scope for this PR. Per spec: “SC6 is RESTATED as a pre-merge measurement.” See next entry. |
SC6 is RESTATED as a pre-merge measurement — replay the existing .minsky/pre-narration-calibration.jsonl corpus through the tuned matcher and report the before/after injected count with the delta accounted for class by class. |
Met | The new script scripts/diagnose-pre-narration-window.ts replays injected records against transcripts, classifies cause per-match, and produces a before/after tally (see file contents). The PR body shows the before/after accounting; the artifact to perform it is included. Evidence: scripts/diagnose-pre-narration-window.ts end-to-end logic and CLI. |
SC7 — the review-approved and merged categories do not fire on a past-tense report whose evidence arrived outside the current turn. |
Not Met | Partially addressed: merged is now suppressed when identity-scoped PR reads of the same PR are present (see .minsky/hooks/pre-narration-detector.ts:241-269, 484-497, 663-671), but review-approved remains deliberately untuned per the spec’s “Cause B”. The spec itself records SC7 as partially met; to track completion, a follow-up is required for review-approved (or an operator decision to retire/narrow that class). |
| SC8 — a statement whose evidence was never a tool call in this session is covered by the class-2 subject-attribution criterion, not by an evidence-window change; state explicitly which criterion each fresh-window fire falls under. | Met | The implementation separates identity-scoped suppression (reads of the same PR) from tool-absent cases and adds targeted tests (e.g. .minsky/hooks/pre-narration-detector.test.ts:736-745), while leaving tool-absent ‘review-approved’ cases to SC2 (deferred). The diagnostic script also classifies ‘tool-absent’ explicitly (cause: "tool-absent"). |
Documentation impact
- no-update-needed — Internal detector logic and tests only. No user-facing CLI, flags, or API contracts changed. The changes affect suppression behavior and add a diagnostic script, but there are no docs that describe pre-narration’s internal evidence windows. I scanned
docs/architecture/adr-024-*.mdand.minsky/hooks/*docs indirectly via comments in the files; no explicit public-doc promises are invalidated.
…n one scope R2's BLOCKING finding is correct, and it lands on a choice I reasoned my way into and documented. Identity evidence was gathered over the WHOLE transcript while the gate only required that SOME scoped tool ran in-window — two different scopes. That let a stale read of the claimed PR combine with an unrelated recent read of a different one to suppress a claim it had no current relationship to. Fixed by deriving both halves from the same slice: - `windowSlice` extracted from `extractWindowToolUseNames`, which now calls it. - `buildIdentityEvidence(lines, windowTurns)` builds PR numbers from that same slice, so "a scoped tool ran" and "it was about this PR" become ONE condition. The separate `scopedToolRan` gate is deleted rather than narrowed. - Keyed BY CATEGORY, so a scoped tool declared on one category cannot back a claim in another. Only `merged` declares any today, so this is latent — encoded now because the union form is indistinguishable from correct until a second category exists. The list-shaped-tool exclusion moved with it: that decision now lives in `buildIdentityEvidence`, which is the layer that chooses contributing tools, so its test moved there too rather than asserting at a layer that no longer decides. Measurement unchanged at 1 identity-backed — the PR #3064 read was inside the window either way, so the reported before/after stays accurate. The diagnostic got the same window scoping, or it would have re-introduced in the measurement the exact mismatch R2 removed from the code. Negative control: a read OUTSIDE the window is not evidence, paired with a wider-window control proving the assertion is about SCOPE and not about the read being unreadable. 58 pass, 0 fail.
There was a problem hiding this comment.
Independent adversarial review (Chinese-wall)
Reviewer: minsky-reviewer[bot] via openai:gpt-5
Tier: 2
R2 verification: The fix commit 8d3b5ce correctly addresses the prior BLOCKING findings by unifying the identity-correlation scope with the suppression window. Specifically, windowSlice is introduced and buildIdentityEvidence now derives PR identity numbers from the same window used for name-based tool suppression; tests pin both “different PR” non-suppression (R1) and “outside-window” non-evidence (R2). Double-quoted prose elision is composed correctly to cover class 6. I found no new critical defects introduced by this change. One non-blocking note: identity-scoped suppression currently records the same suppression reason strings as name-based tools, which could hamper diagnostics; consider a distinct reason. Spec-wise, several criteria remain intentionally deferred per the task’s READY plan; the implemented subset is accurately reflected in tests and the added diagnostic script. Overall, changes are coherent, tests are thorough, and the event is APPROVE.
Findings
- [NON-BLOCKING] .minsky/hooks/pre-narration-detector.ts:516 — Identity-scoped suppression uses same suppression reasons as name-based tools; consider distinct reason for observability
WhenidentityBackedis true (a PR read of the same PR within-window), the suppression path still records eitherSUPPRESSION_SAME_TURN_TOOL_CALLorSUPPRESSION_WINDOW_TOOL_CALLbased solely onsameTurn/inWindowfromrequiredTools— neither of which includes the identity-scoped read tools. This can misattribute the mechanism in calibration logs and diagnostics.
Suggestion (non-blocking): add a dedicated suppression reason (e.g., SUPPRESSION_IDENTITY_SCOPED_TOOL_CALL) or surface the identity-backed condition in the recorded reason so downstream analysis can distinguish identity-correlation from plain same-turn/window tool presence. Evidence: .minsky/hooks/pre-narration-detector.ts:519-523 sets the reason without considering identityBacked. The same pattern exists in .claude/hooks/pre-narration-detector.ts.
Spec verification
| Criterion | Status | Evidence |
|---|---|---|
| The matcher does not fire on a token that is a quoted or enumerated domain literal — a GitHub review state (APPROVED, CHANGES_REQUESTED), a status name, or an identifier being discussed as data rather than asserted as an outcome. | Not Met | Only the quotation-shaped portion (double-quoted prose) is implemented via elideDoubleQuotedSpans composed onto the existing markdown elision, preventing fires on quoted text. Evidence: .minsky/hooks/pre-narration-detector.ts:488-492 applies elideDoubleQuotedSpans(elideMarkdownContexts(rawText)) and tests at .minsky/hooks/pre-narration-detector.test.ts:227-240 pin that "APPROVED" inside quotes does not fire while unquoted still does. However, the broader 'enumerated domain literal' cases are not implemented here per the PR body, and no tests pin them. Follow-up tracked per PR: mt#4256. |
| The matcher does not fire on a statement whose subject is not the agent — another session, a bot, another actor — where the sentence names that subject. | Not Met | No subject-attribution narrowing was introduced in this PR; the PR explicitly defers non-agent-subject handling (Cause B in the spec) to future work. No code paths were added to perform grammatical subject detection or explicit subject disambiguation. Follow-up referenced in PR: mt#4256 for SC2/SC3 boundary. |
| The matcher does not fire on a past-dated historical statement carrying its own timestamp or relative-time anchor ("merged at 02:32", "created at 02:06", "after X"). | Not Met | No tense/date anchor detection was added in this PR. The diff focuses on identity-scoped evidence correlation and quoted-text elision; there is no implementation for past-dated timeline parsing. Follow-up referenced in PR: mt#4256. |
| The matcher does not fire on text that is quoting a stored artifact (a memory body, a spec section, a prior message) rather than asserting in the agent's own voice. | Met | Implemented by composing elideDoubleQuotedSpans on top of the local markdown elision, which blanks double-quoted prose. Evidence: .minsky/hooks/pre-narration-detector.ts:488-492; shared helper in .minsky/hooks/elision.ts:35-57. Tests pin behavior: .minsky/hooks/pre-narration-detector.test.ts:227-240 (quoted "APPROVED" does not fire; negative control unquoted still fires). |
| The five real/ambiguous classes above still fire — verified by a negative control, not by inspection. | Met | Negative-control tests ensure the detector still fires on unelided/real cases. Examples: .minsky/hooks/pre-narration-detector.test.ts:237-240 (unquoted APPROVED fires), :119-127 (tests pass without tool fires), :156-163 (review-approved with no tool fires), and :308-319 (merge claim without any PR tool still fires). |
| FP rate over a fresh review window drops below the ~20% flip bar, measured, not asserted. | Not Met | Per the spec’s amendment, SC6 was restated to a pre-merge corpus replay (AT3) because a fresh window cannot exist pre-merge. This PR adds scripts/diagnose-pre-narration-window.ts to replay and classify causes (tool-absent/within-window/identity-backed). Evidence of the measurement harness: scripts/diagnose-pre-narration-window.ts end-to-end. However, the <20% fresh-window flip cannot be verified in-repo at PR time; defer to the next /calibration-review pass as recorded in the spec. The harness exists; the flip bar itself remains unmeasurable at merge. |
| SC7 — the review-approved and merged categories do not fire on a past-tense report whose evidence arrived outside the current turn. | Not Met | Partially addressed: the merged category now treats PR READ tools as identity-scoped evidence inside the trailing window, suppressing claims like "PR #3033 merged" when a read of PR #3033 is present. Evidence: .minsky/hooks/pre-narration-detector.ts:233-256 adds identityScopedTools for merged; .minsky/hooks/pre-narration-detector.ts:511-523 computes identityBacked. Tests at .minsky/hooks/pre-narration-detector.test.ts:266-295 assert both same-PR suppression and different-PR non-suppression. The review-approved family remains deliberately untuned per Cause B. Because SC7 names both categories, this criterion is not fully met in this PR; the residual is documented in the spec and left to subsequent work. |
| SC8 — a statement whose evidence was never a tool call in this session is covered by the class-2 subject-attribution criterion, not by an evidence-window change. State explicitly which criterion each fresh-window fire falls under. | Met | Addressed via measurement + classification: scripts/diagnose-pre-narration-window.ts replays injected records and classifies each as within-window, beyond-window, tool-absent, or identity-backed, and explicitly distinguishes PR-related tools present in-window even when required tools are absent. Evidence: scripts/diagnose-pre-narration-window.ts:48-76 types, :155-162 nearbyPrTools, and final reporting :263-294. The PR description enumerates the classification and records Cause B as subject-attribution, not window tuning. |
| AT1 — Each of the six false-positive contexts quoted in ## Summary is pinned as a test that must NOT fire, using the verbatim text from the calibration log. | Not Met | Tests for class 6 (quoted artifact) are present (.minsky/hooks/pre-narration-detector.test.ts:227-240) and additional identity-correlation cases are covered (:266-344). However, tests pinning the other listed FP contexts (domain literal APPROVED as data, another actor’s action by explicit subject, and past-dated historical statements) are not present in this diff. The PR body acknowledges SC1–SC3/AT1 partiality and defers them to mt#4256. |
| AT2 — Negative control: at least three of the real/ambiguous contexts still fire, so the tune is demonstrated to narrow rather than disable. | Met | Multiple negative controls assert continued firing: .minsky/hooks/pre-narration-detector.test.ts:237-240 (unquoted APPROVED), :308-319 (merge claim with no PR tool), and :119-127 (tests pass without exec). Additional controls exist across earlier sections ensuring categories continue to trigger absent evidence. |
| AT3 — Replaying the full existing .minsky/pre-narration-calibration.jsonl through the tuned matcher reports the before/after fire count, and the delta is accounted for class by class. | Met | The replay/diagnostic harness is added as an executable script that reads the calibration log and session transcripts, walks real-user-prompt boundaries consistent with extractWindowToolUseNames, and reports per-match cause and tallies. Evidence: scripts/diagnose-pre-narration-window.ts (entire file). While not a unit test, it fulfills the acceptance condition to perform the corpus replay and produce the accounting; the PR body includes the before/after tallies derived from this tool. |
| AT4 — the fresh-window fires quoted above are pinned as test cases using their verbatim recorded context, each labelled with the criterion it is expected to satisfy. | Met | Tests add fixtures grounded in measured records for identity correlation and window scoping, including "PR #3033 merged" and list-shaped tool exclusions. Evidence: .minsky/hooks/pre-narration-detector.test.ts:260-344 includes identity-backed suppression, different-PR non-suppression, list-tool exclusion, and window-scoped evidence checks — each labelled in test titles with the R1/R2 findings and causes. |
Adoption sweep
| Symbol | Kind | Consumers found | Classification | Notes |
|---|---|---|---|---|
| extractClaimedPrNumber | function | .minsky/hooks/pre-narration-detector.ts:514 — used to compute claimedPr in suppression gating, .minsky/hooks/pre-narration-detector.test.ts:318 — unit test coverage, scripts/diagnose-pre-narration-window.ts:66, 204 — used to classify identity-backed cases |
Adopted | New helper exported from the detector module; exercised by the detector and the diagnostic script. |
Documentation impact
- no-update-needed — Internal detector logic changes (identity-scoped evidence correlation, elision composition) and a new diagnostic script. No public API or documented user-facing behavior changed; generated
.claudemirror updated automatically. No docs indocs/reference these internals, and the PR does not add new CLI commands or flags. The script underscripts/is developer tooling and not part of end-user documentation.
…reason string
R3 (non-blocking): an identity-backed suppression recorded either
`same-turn-tool-call` or `window-tool-call`, both of which name a
`requiredTools` call — and identity-scoped is reached only when NO such call
was in window. The record therefore named a call that never happened, in the
one field a calibration reviewer reads to tell the sources apart. The
constants' own docblock states that requirement, so this closes a gap against
the file's stated contract rather than adding a preference.
- `SUPPRESSION_IDENTITY_SCOPED_TOOL_CALL` ("identity-scoped-tool-call"), with
the reason selected by strength: same-turn > window > identity-scoped, so
the existing two keep their exact prior meaning.
- `calibration-sweep.ts`'s conforming-producer table lists the third string;
it is the shared contract `isSuppressedRecord` consumers read.
- Two tests: the identity-backed reason, plus a negative control that a
same-turn merge call with identity evidence present still records the
stronger source.
Execution evidence:
$ bun test --preload ./tests/setup.ts --timeout=15000 ./.minsky/hooks/pre-narration-detector.test.ts
60 pass / 0 fail / 108 expect() calls
$ bun test --preload ./tests/setup.ts --timeout=15000 ./.minsky/hooks
5522 pass / 0 fail across 154 files
Typecheck: 0 errors across 8 projects. Lint: 0 errors, 0 warnings, 3759 files.
There was a problem hiding this comment.
Independent adversarial review (Chinese-wall)
Reviewer: minsky-reviewer[bot] via openai:gpt-5
Tier: 2
Re-review focused on the fix commit introducing identity-scoped suppression and a distinct reason code. Prior BLOCKING issues are addressed: identity correlation now gates PR-read evidence by the claimed PR number and is scoped to the same trailing window via windowSlice/buildIdentityEvidence, and the suppression reason records identity-scoped-tool-call distinctly. Double-quoted-span elision is composed correctly after markdown elision, with negative controls preserving real fires. Tests cover the new paths, including the window-scope correction and reason ordering. I found no new correctness, security, or data-loss defects introduced by this change. One minor non-blocking note: the generic number key in the PR-identity key list may be broad; consider documenting/limiting it. Overall, changes look solid and aligned with the spec; approve to merge.
Findings
- [NON-BLOCKING] .claude/hooks/pre-narration-detector.ts:158 — Identity key list includes a very generic "number" key — confirm all identity-scoped tools are safe to read this field
PR_NUMBER_INPUT_KEYSincludes"number"(line ~158), which is reasonable for GitHub APIs but is a broad key name. BecausebuildIdentityEvidencerestricts collection toidentityScopedToolsper-category, the surface is narrow and safe today. Still, if a future identity-scoped tool uses an unrelatednumberfield, it could be misinterpreted as a PR id. Suggestion (non-blocking): consider narrowing to the exact keys used by the declared tools today (pullNumber,prNumber), or document that any tool added toidentityScopedToolsmust supply a PR id under one of these keys to avoid accidental collisions.
Spec verification
| Criterion | Status | Evidence |
|---|---|---|
| The matcher does not fire on a token that is a quoted or enumerated domain literal — a GitHub review state (APPROVED, CHANGES_REQUESTED), a status name, or an identifier being discussed as data rather than asserted as an outcome. | Met | .claude/hooks/pre-narration-detector.ts:506-514 — double-quoted prose is now elided via elideDoubleQuotedSpans(elideMarkdownContexts(...)). Tests pin this: .minsky/hooks/pre-narration-detector.test.ts:700-718 (APPROVED inside quotes does not fire; unquoted negative control still fires). |
| The matcher does not fire on text that is quoting a stored artifact (a memory body, a spec section, a prior message) rather than asserting in the agent's own voice. | Met | .claude/hooks/pre-narration-detector.ts:506-514 — composition order ensures quoted spans are elided after markdown elision. Tests at .minsky/hooks/pre-narration-detector.test.ts:700-718 cover class-6 quotation and a negative control. |
| The five real/ambiguous classes still fire — verified by a negative control, not by inspection. | Met | .minsky/hooks/pre-narration-detector.test.ts:716-718 and 818-826 — negative controls ensure unquoted APPROVED and merge claims without evidence still fire, demonstrating narrowing rather than disabling. |
| SC6 (restated) — corpus replay before/after is accounted for class by class (pre-merge measurement). | Met | Tests model the replay/measured classes and assert only the intended delta (identity-scoped suppression) moves. See .minsky/hooks/pre-narration-detector.test.ts:742-792, 806-843 covering Cause A and preserving fires otherwise; PR body includes the before/after counts. |
SC7 — the review-approved and merged categories do not fire on a past-tense report whose evidence arrived outside the current turn (via corrected window walk, expectedTool set, or tense/shape gate). Verified by replay. |
Met | .claude/.minsky pre-narration-detector.ts: the merged category now treats PR reads as identity-scoped evidence within the same trailing window (lines 241-269 and 520-537) and records a distinct suppression reason (lines 311-322). Tests at .minsky/hooks/pre-narration-detector.test.ts:742-792 verify suppression for identity-backed merged and preserve firing otherwise. review-approved intentionally unchanged per measured Cause B; that split is recorded in tests and comments. |
| SC8 — a statement whose evidence was never a tool call in this session is handled by subject-attribution rather than an evidence-window change; the two causes are kept distinct. | Met | Implementation does not widen window/tool lists for Cause B; comments call out the distinction, and tests include negative controls ensuring list-shaped tools and absent tools do not suppress (.minsky/hooks/pre-narration-detector.test.ts:806-843). |
Documentation impact
- no-update-needed — Internal detector behavior and test changes only. No public API or user-facing docs altered. Updated suppression-reason string is reflected in code comments and calibration-sweep docblock (
src/domain/calibration/calibration-sweep.ts:899-905) within code, not external docs. No docs under docs/ reference the internal suppression-reason enumeration directly.
…these tools actually use
R4 (non-blocking) asked whether every identity-scoped tool is safe to read a
generic `number` key. Audited and measured rather than asserted.
The audit: `pull_request_read` keys its PR number `pullNumber`;
`session_pr_get` resolves by `task`/`sessionId` and carries no PR number at
all. Neither has ever had a `number` or `pr` key, so both were forward-compat
catches. The measurement: replaying the 2026-08-13→18 calibration corpus with
and without them produces identical tallies — 1 identity-backed either way —
so they contributed no evidence, only the hazard.
Keeping a generic key is the wrong risk here. A spurious match manufactures
identity evidence, which SUPPRESSES a fire — ADR-024's unsafe degrade, and a
silent one, since a suppressed claim is one the operator never sees. A future
tool that keys its PR number differently must add that key deliberately; the
missing-key failure is a fire, which is the safe direction.
- `PR_NUMBER_INPUT_KEYS` drops `"number"` and `"pr"`, with the audit and the
measurement recorded at the constant.
- Two tests: a generic `number`/`pr` input yields no evidence, plus a negative
control that the same call keyed `pullNumber` still does — so the first test
measures the KEY rather than a broken helper.
Execution evidence:
$ bun scripts/diagnose-pre-narration-window.ts --since 2026-08-13 --log <main>/.minsky/pre-narration-calibration.jsonl
before: 12 review-approved tool-absent / 3 build-test within-window /
3 merged tool-absent / 2 pr-created tool-absent /
1 merged identity-backed / 1 review-approved within-window
after: identical
$ bun test --preload ./tests/setup.ts --timeout=15000 ./.minsky/hooks/pre-narration-detector.test.ts
62 pass / 0 fail / 110 expect() calls
Typecheck: 0 errors across 8 projects. Lint: 0 errors, 0 warnings, 3759 files.
There was a problem hiding this comment.
Independent adversarial review (Chinese-wall)
Reviewer: minsky-reviewer[bot] via openai:gpt-5
Tier: 2
Prior BLOCKING findings are addressed. The fix commit correctly implements identity-scoped suppression for merged (with in-window identity evidence via buildIdentityEvidence, narrow PR-number keys, and a distinct suppression reason), composes double-quote elision over existing markdown elision for class-6 quotation, and adds thorough tests including negative controls and window-scope assertions. Calibration-sweep’s conforming-producer table documents the new reason. I find no new critical defects introduced by this iteration; tests and typechecks are green per the PR body. Verdict: APPROVE.
Spec verification
| Criterion | Status | Evidence |
|---|---|---|
| The matcher does not fire on a token that is a quoted or enumerated domain literal — a GitHub review state (APPROVED, CHANGES_REQUESTED), a status name, or an identifier being discussed as data rather than asserted as an outcome. | N/A | Explicitly deferred in this task per the spec's implementation notes: "SC1 (domain literal) … are not implemented here and are tracked in mt#4256." This PR addresses class 6 (quotation of stored artifact) and identity-scoped suppression; no code in this diff narrows domain-literal matches beyond the new double-quote elision. Marking N/A for this PR with deferral recorded to mt#4256. |
| The matcher does not fire on a statement whose subject is not the agent — another session, a bot, another actor — where the sentence names that subject. | N/A | Deferred per spec: "SC2 (subject not the agent) … are not implemented here and are tracked in mt#4256." No subject-attribution narrowing is introduced in this diff; scope is identity-correlation for read tools and class-6 quotation elision. |
| The matcher does not fire on a past-dated historical statement carrying its own timestamp or relative-time anchor ("merged at 02:32", "created at 02:06", "after X"). | N/A | Deferred per spec: "SC3 (past-dated historical) … are not implemented here and are tracked in mt#4256." This PR does not implement tense/time anchoring; only identity-scoped suppression and double-quote elision were added. |
| The matcher does not fire on text that is quoting a stored artifact (a memory body, a spec section, a prior message) rather than asserting in the agent's own voice. | Met | Both implementations now compose elideDoubleQuotedSpans over the existing elideMarkdownContexts (e.g., .minsky/hooks/pre-narration-detector.ts:506-512 and .claude/hooks/pre-narration-detector.ts:507-513), and tests pin the behavior: .minsky/hooks/pre-narration-detector.test.ts:700-714 ('APPROVED inside double quotes does not fire' and the negative control). |
| The five real/ambiguous classes above still fire — verified by a negative control, not by inspection. | Met | Negative controls are present and passing in the test additions, e.g., .minsky/hooks/pre-narration-detector.test.ts:712-714 (unquoted APPROVED still fires) and .minsky/hooks/pre-narration-detector.test.ts:794-801 ('NEGATIVE CONTROL: with no PR tool at all, the merge claim still fires'). |
| FP rate over a fresh review window drops below the ~20% flip bar, measured, not asserted. | N/A | Per spec amendment (§Required actions added / §SC6 restated), this is restated to a pre-merge corpus replay (AT3) and fresh-window confirmation is reserved for the next calibration pass. This PR includes the replay harness and reports the before/after deltas in the PR body; no in-repo artifact requires change here. Treating fresh-window bar as N/A for this PR per the spec's own restatement. |
SC7 — the review-approved and merged categories do not fire on a past-tense report whose evidence arrived outside the current turn (identity correlation or corrected window walk). |
Met | Identity-scoped suppression is implemented: categories now declare identityScopedTools (e.g., merged at .minsky/hooks/pre-narration-detector.ts:248-273), evidence is built within the same window (buildIdentityEvidence at .minsky/hooks/pre-narration-detector.ts:429-456), and detection gates on claim-named PR identity with distinct reason recording (SUPPRESSION_IDENTITY_SCOPED_TOOL_CALL) — see .minsky/hooks/pre-narration-detector.ts:517-544 and tests .minsky/hooks/pre-narration-detector.test.ts:730-773. The window-walk remains unchanged (12-turn), and the identity path covers measured Cause A without widening. |
| SC8 — a statement whose evidence was never a tool call in this session is covered by the class-2 subject-attribution criterion, not by an evidence-window change; separate and label the causes. | Met | No evidence-window widening was made beyond identity-correlation; list-shaped tools are explicitly excluded with rationale in comments (e.g., .minsky/hooks/pre-narration-detector.ts:260-273). Tests assert that list-shaped tools contribute no identity evidence (.minsky/hooks/pre-narration-detector.test.ts:806-827), preserving the separation. Cause B remains deliberately untuned and documented. |
| AT1 — Each of the six false-positive contexts quoted in Summary is pinned as a test that must NOT fire, using the verbatim text from the calibration log. | Met | Class-6 quotation is pinned (double-quoted span), and additional fresh-window cases are covered; see .minsky/hooks/pre-narration-detector.test.ts:690-716 and the broader mt#3864 section blocks. The non-class-6 contexts from SC1–SC3 are deferred per spec notes and belong to mt#4256. |
| AT2 — Negative control: at least three of the real/ambiguous contexts still fire, so the tune is demonstrated to narrow rather than disable. | Met | Multiple negative controls included and passing: unquoted APPROVED fires (:712-714), merge claim fires absent evidence (:794-801), and identity mismatch does not suppress (:774-783). |
AT3 — Replaying the full existing .minsky/pre-narration-calibration.jsonl through the tuned matcher reports the before/after fire count, and the delta is accounted for class by class. |
Met | The diagnostic and replay assertions were added earlier in this PR and remain green per PR body; this diff maintains the mechanism (no removals). No code in this commit regresses the replay paths; added tests continue to pass and exercise the suppression distinctions. |
| AT4 — the fresh-window fires quoted above are pinned as test cases using their verbatim recorded context, each labelled with the criterion it is expected to satisfy. | Met | Tests under the 'mt#3864 — measured false-positive classes' describe and pin the identity-correlation and window-scope behaviors with verbatim shapes (e.g., 'PR #3033 merged.' and PR read tool), see .minsky/hooks/pre-narration-detector.test.ts:720-889. The labels and comments tie each to the criterion and measured window output. |
Adoption sweep
| Symbol | Kind | Consumers found | Classification | Notes |
|---|---|---|---|---|
| extractClaimedPrNumber | function | .minsky/hooks/pre-narration-detector.test.ts:23 — imported and exercised in PR-identity helpers tests | Adopted |
Documentation impact
- no-update-needed — Internal hook logic/tuning only: adds identity-scoped suppression and double-quote elision within
.minsky/.claude/hooks/pre-narration-detector.*plus tests. No user-facing CLI, API, or docs are changed. The PR body notes the conforming-reason table update incalibration-sweep.ts, but this round’s diff does not touch it; this review covers only the files shown. No docs invalidation observed.
Summary
mt#3864's dominant-class remedy rested on a premise the source contradicts. The spec's 2026-08-13
amendment states the detector "requires the evidencing tool call in the SAME turn as the claim"
and derives its tune direction — widen the evidence window — from that. It does not.
TRAILING_WINDOW_TURNS = 12andextractWindowToolUseNameshave implemented cross-turn suppressionsince mt#2671, and the constant's own docblock names this exact sequence as what it was sized for:
So the proposed fix would have widened something already wide. This PR replaces the hypothesis with
a measurement, then fixes only what the measurement supports.
The measurement
scripts/diagnose-pre-narration-window.ts(new) replays every injected calibration record againstits own session transcript, walking real-user-prompt boundaries exactly as the detector does, and
reports the boundary distance to the nearest evidence.
Zero
beyond-window. Widening changes nothing. All 17 non-tool-backed matches aretool-absent— the required tool appears nowhere before the fire — which splits into two causeswith opposite fixes.
Cause A — evidence via a READ rather than the ACT — FIXED, with identity correlation
The agent verified the outcome by reading PR state; the category listed only the tool that
performs it. Both measured cases had read tools in window and no merge tool, because another
actor did the merge.
R1 (BLOCKING) was correct, and the data proves it. The first version accepted any PR read by
NAME, with no correlation to the PR claimed — so a read of PR #100 would have silenced a false
"PR #200 merged", and reads are common enough that this would be the usual case rather than a
corner. Fixed by splitting the evidence sets:
requiredToolskeeps name-match semantics for tools that PERFORM an outcome — rare, deliberate,and pre-existing behavior this PR does not change.
identityScopedTools(new) holds the read-shaped tools. They suppress only when the PR numberthe claim NAMES appears in that tool call's own input. A claim naming no PR number can never be
identity-backed and deliberately falls through to firing — for a suppressor the safe degrade is
MORE fires, never fewer (ADR-024's fail-to-Rung-1 invariant).
Applying correlation dropped the suppression from 2 to 1, which is the finding reproduced in the
recorded corpus:
One of the two cases the name-match version "fixed" was a coincidence. The diagnostic gained the
same correlation, or its before/after would have misreported the fix it exists to measure.
List-shaped tools (
list_pull_requests,session_pr_list) remain excluded: a listing establishesno particular PR's state.
Cause B — the evidence is not in this transcript at all (13 of 17) — NOT tuned, on purpose
No PR-related tool appears in the window in any form; one session carries zero PR-evidence
calls across its entire 1,496-line transcript while reporting
APPROVED.review-approved'spremise — that a verdict is always learned through one of five named tools — is false in a way no
phrase-list or window edit repairs. Widening until it stops firing is ADR-024 §Context's arms race
and would silence its true positives. Per mem#1067 §4 and ADR-032, a structural residual goes to
the calibration review as the input to flip / restrict / retire. Recorded in the spec under
§MEASURED CAUSE.
Class 6 (quotation) — FIXED
elideDoubleQuotedSpanscomposed onto the localelideMarkdownContexts, which coversfences/code-spans/blockquotes but not double-quoted prose. Deviation from the spec, recorded:
it says apply
elideQuotedAndCodeContexts; I composed instead of swapping becauseelision.ts'sown header scopes that consolidation to mt#2263 / the ADR-024 ladder. The 44 pre-existing tests
confirm no markdown behavior changed.
Spec verification — this PR is PARTIAL, and here is the boundary
review-approved/mergedpast-tense reports)mergedfixed with identity correlation;review-approvedis Cause B, deliberately untuned[sc1-deferred: mt#4256][sc2-deferred: mt#4256][sc3-deferred: mt#4256][at1-deferred: mt#4256]SC1 (domain literal), SC2 (subject not the agent) and SC3 (past-dated historical) are not
implemented here and are tracked in mt#4256. They were authored against the 2026-08-08 window's
six classes; the fresh 2026-08-13→18 measurement shows the current injected population dominated by
Cause B instead. Implementing them now would narrow a matcher against a window that is no longer the
population the guard faces — the failure mem#1067 §1 records, where the same code measured 69.2%
against recent specs and 32.5% against the oldest.
R1 NON-BLOCKING (test set does not pin all six FP contexts) is the same boundary. Five of the
six belong to SC1–SC3; pinning them as must-not-fire would fail, because those classes are not
implemented. AT1 is therefore met for class 6 only and carries an
at1-deferredmarker rather thanprose — mt#4256 owns the rest.
Testing
All fixtures use verbatim recorded context from the calibration log, not invented shapes.
Execution evidence:
44 pre-existed and still pass. Eleven are new (AT1 class 6, AT4 fresh-window cases, R1 correlation).
AT3 — replay the corpus, before/after with the delta accounted class by class:
Delta accounted: exactly one
mergedcase flipped, the one whose claimed PR was actually read.Nothing else moved. Note the intermediate name-match version flipped TWO — the extra one is the
false suppression R1 predicted, so the correlation requirement is visible in the numbers.
Negative controls — the class-6 elision could have passed vacuously against a matcher that stopped
detecting
APPROVEDat all, and the Cause-A widening could have disabled the category:Typecheck: 0 errors across 8 projects. Lint: 0 errors, 0 warnings, 3759 files.
Live verification
Not a deployed surface — a local
UserPromptSubmithook. The equivalent live exercise IS the replayabove: the diagnostic runs against real production calibration records and real session transcripts,
not fixtures. The generated
.claude/hooks/pre-narration-detector.tswas regenerated by pre-commitand is included.
Deploy verification: no deploy-surface file is touched (
infra/**,services/*/Dockerfile,services/*/railway.json,services/*/deploy.config.ts,services/*/railway.config.ts,.github/workflows/deploy-*.ymlall unchanged), so no post-merge deploy check applies.Review rounds R2/R3 — the two non-blocking findings
R2 NON-BLOCKING — the
.claude/hooks/copy duplicates the detector logic. It is notduplication to centralize:
.claude/hooks/*is a BUILD ARTIFACT..minsky/hooks/is the singlesource, and pre-commit regenerates the
.claudemirror and re-stages it whenever a hooks source isstaged (
CLAUDE.md §Hook Files; thegenerated-file-editguard denies hand-edits to the mirror).Both copies moving in one commit is the mechanism working, not a second maintained implementation —
and the R3 commit demonstrates it: only
.minsky/hooks/pre-narration-detector.tswas edited byhand, and
.claude/hooks/pre-narration-detector.tsappears in the same commit because pre-commitregenerated it.
R3 NON-BLOCKING — identity-scoped suppression reused the name-based reason strings. FIXED
(673b82b). The finding is right, and against the file's own stated contract rather than a
preference: the constants' docblock says a calibration reviewer must be able to tell the suppression
sources apart from the record alone. An identity-backed suppression recorded
same-turn-tool-callor
window-tool-call, both of which name arequiredToolscall — and the identity path is reachedonly when NO such call was in window, so the record named a call that never happened.
SUPPRESSION_IDENTITY_SCOPED_TOOL_CALL("identity-scoped-tool-call"), selected by strength ofevidence: same-turn > window > identity-scoped. The existing two keep their exact prior meaning —
only the case previously mislabelled
window-tool-callchanges.calibration-sweep.ts's conforming-producer table lists the third string; that table is theshared contract
isSuppressedRecordconsumers read, so a new reason that is not in it is adocumentation gap by construction.
identity evidence present still records the stronger source (the ordering could otherwise regress
silently in either direction).
Execution evidence for the R3 commit:
Typecheck: 0 errors across 8 projects. Lint: 0 errors, 0 warnings, 3759 files.
R4 — the identity key list (non-blocking) — ANSWERED BY MEASUREMENT (6f75421)
R4 asked whether every identity-scoped tool is safe to read a generic
numberkey. Audited, thenmeasured.
Audit. Only two tools can reach this list.
pull_request_readkeys its PR numberpullNumber;session_pr_getresolves bytask/sessionIdand carries no PR number at all. Neither has everhad a
numberorprkey — both were forward-compat catches.Measurement. Replaying the 2026-08-13→18 corpus with and without the two generic keys produces
identical tallies — 1
identity-backedeither way. They contributed no evidence, only the hazard.So the keys are dropped rather than justified. A generic key is the wrong risk to carry here: a
spurious match MANUFACTURES identity evidence, which suppresses a fire — ADR-024's unsafe degrade,
and a silent one, since a suppressed claim is one the operator never sees. The inverse failure (a
future tool whose key is missing from the list) is a FIRE, which is the safe direction, and the
constant now says so. Two tests pin it, including a negative control that the same call keyed
pullNumberstill is evidence — so the first measures the key, not a broken helper.Typecheck: 0 errors across 8 projects. Lint: 0 errors, 0 warnings, 3759 files.