Skip to content

fix(mt#4483): Let a negating complement cancel the phrase it follows - #3330

Merged
edobry merged 3 commits into
mainfrom
task/mt-4483
Aug 25, 2026
Merged

edobry merged 3 commits into
mainfrom
task/mt-4483

Conversation

@minsky-ai

@minsky-ai minsky-ai Bot commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Summary

ask-routing-deferral's principal-reserved patterns match noun-phrase claims — needs your call, your decision to make — and look no further than their own span. English lets the very next words invert one, so mt#4458 needs your call on nothing — it needs the daemon. fired as a deferral while asserting the opposite. The matcher had no way to tell it from the genuine form.

Measured at planning against the shipped matcher, not inferred:

input principal-reserved match (before)
- **mt#4458 needs your call on nothing — it needs the daemon. yes
mt#4458 needs your call on the daemon question. (control) yes
That needs your call for nothing; it is already settled. yes
It needs your call about nothing. yes

settlesDecision() and citesFiledAsk() returned false on all four, so no shipped suppression reached them.

Key changes

  • NEGATING_COMPLEMENT_RE — on|for|about + nothing, checked against the 24 chars following a match. Its docblock states what is covered and, per the first success criterion, what is deliberately not: sentence-level negation (the negator precedes the phrase, so no forward look sees it), quantifier complements (on none of this), and negation across an intervening clause. Each wants its own measured window under ADR-024 clause (b).
  • firstUnnegatedMatch — scans every occurrence rather than testing exec's first. The detector takes one match per class, so skipping to the next pattern on a negated first hit would drop a genuine later mention — trading this false positive for a false negative. Pinned by a test.
  • Scoped to one class. Only principal-reserved consults the complement; deferral-menu's patterns are interrogative or imperative shapes where the construction does not arise.

ADR-024 placement: Rung 1 — a deterministic prefilter targeting the precision axis, which the ADR names as "the default stopping point." Same extension the ask-citation token test made at ask-routing-deferral-detector.ts:887-893 for mt#4201; no rung escalation is argued.

Testing

Execution evidence:

$ bun test --preload ./tests/setup.ts ./.minsky/hooks/ask-routing-deferral-detector.test.ts
 130 pass
 0 fail
 241 expect() calls
Ran 130 tests across 1 file. [90.00ms]

$ bun run test:hooks
 6187 pass
 0 fail
 12867 expect() calls
Ran 6187 tests across 174 files. [25.04s]

Captured context yields no match — the 2026-08-23T18:34:29.951Z fire, verbatim, is a named fixture and asserts no principal-reserved match. All three complements (on nothing, for nothing, about nothing) plus an uppercase variant are covered.

deferral-menu untouched — a turn carrying both a negated principal-reserved phrase and What's your call? still fires the menu class and not the reserved one.

Typecheck and lint clean — validate_typecheck 0 errors across 8 projects including tsconfig.hooks.json; validate_lint 0 errors, 0 warnings over 3996 files. Both scoped to this session (validatedWorkspace confirmed).

Negative control: reverted the one behaviour-determining line — the class-conditional back to a bare pattern.exec(scanned) — and re-ran.

(fail) SC2: the captured fire produces no principal-reserved match
(fail) SC1: "on nothing" suppresses
(fail) SC1: "for nothing" suppresses
(fail) SC1: "about nothing" suppresses
(fail) SC1: the complement match is case-insensitive
(fail) a negated mention does not mask a genuine one later in the same turn
(fail) SC4: deferral-menu still fires on a negated-complement turn
 123 pass
 7 fail

7 of the 8 new tests fail pre-fix. The two that pass in both states are the two that must: the un-negated control (its job is to hold either way) and the pin on deliberately-uncovered sentence-level negation.

False-negative measurement (ADR-024 clause (b): 0 known-FP, ≤5% new FN). Replayed all 260 captured match-contexts in .minsky/ask-routing-deferral-calibration.jsonl through this branch's detector and the main-workspace baseline, comparing verdicts:

captured match-contexts replayed (all classes): 260
principal-reserved verdict CHANGED by this fix: 1
  - 2026-08-23T18:34:29.951Z base: true -> fixed: false | - **mt#4458 needs your call on nothing — it needs the daemon.

Exactly the target record. One known false positive removed, zero new false negatives.

A first attempt at this measurement compared today's detector against what production recorded at the time and reported 6 suppressions. That baseline was wrong: 5 of the 6 had already stopped firing on main because of mt#4175 / mt#4311 / mt#4201, so it was measuring every change since those records were written, not this one. The comparison above isolates the delta by holding everything else fixed.

Deploy impact

[no-deploy-impact] verified rather than assumed — isDeploySurfaceFile returned false for all four candidate paths (both .minsky/ sources and both generated copies). Only .claude/hooks/ regenerated; .codex/hooks/ is not yet a compile output (mt#3854, PR #3253), so this branch does not touch it — which also moots the file-level overlap with that PR flagged at planning.

edobry added 2 commits August 25, 2026 12:24
…[no-deploy-impact]

`PRINCIPAL_RESERVED_PATTERNS` match noun-phrase claims and look no further than
their own span, so "needs your call ON NOTHING" — which asserts the opposite —
was indistinguishable from the genuine form. Adds a complement check consulted
only by the principal-reserved class.

Scans every occurrence rather than exec's first, so a negated mention cannot
mask a genuine one later in the same turn.
@minsky-ai minsky-ai Bot added the authorship/co-authored Co-authored by human and AI agent label Aug 25, 2026
@minsky-reviewer

minsky-reviewer Bot commented Aug 25, 2026 •

Copy link
Copy Markdown

Minsky Reviewer Status

Verdict: APPROVED — no blocking findings
Review: View review
Model: openai/gpt-5 | Tokens: 304K prompt, 4K completion | Duration: 65s
Mode: normal

Commands

  • /review — request a fresh review

@minsky-reviewer minsky-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Independent adversarial review (Chinese-wall)
Reviewer: minsky-reviewer[bot] via openai:gpt-5
Tier: 2


No additional blocking issues beyond what I already noted. Primary nit: the negating-complement regex doesn’t allow intervening punctuation (em dash/comma/colon/parenthesis) immediately after the phrase, leaving a reachable FP gap (e.g., “needs your call—on nothing”). Tests and implementation otherwise meet the spec; scope is tight and deferral-menu left alone. I did not rerun typecheck/lint or the 10-record replay; those are reported in the PR body but unverifiable from repo files alone.

Findings

  • [NON-BLOCKING] .minsky/hooks/ask-routing-deferral-detector.ts:226 — Negating-complement check misses common intervening punctuation (em dash/comma/colon) immediately after the phrase
    NEGATING_COMPLEMENT_RE is anchored to the start of the post-match slice and only allows leading whitespace before on|for|about (/^\s*(?:on|for|about)\s+nothing\b/i). If the text uses punctuation immediately after the phrase — e.g., "needs your call—on nothing", "needs your call, on nothing", or "needs your call: on nothing" — the suppression will not trigger even though the complement is still the very next words. This leaves a reachable false-positive gap consistent with the PR’s goal.

Consider permitting a small set of intervening punctuation before the complement (em/en dash, hyphen, comma, colon) in the leading matcher, e.g., ^[\s\-—,:]*\b(?:on|for|about)\s+nothing\b (tuned as appropriate). Pin with tests for "needs your call—on nothing" and ", on nothing" variants. The 24-char lookahead budget will still comfortably cover these cases.

Spec verification

Criterion Status Evidence
A principal-reserved phrase followed by a negating complement — on nothing, for nothing, about nothing — does not fire. State which negation forms are covered and which are deliberately not. Met Implementation adds NEGATING_COMPLEMENT_RE and uses firstUnnegatedMatch only for principal-reserved (/.minsky/hooks/ask-routing-deferral-detector.ts:219-269, 664-671). Tests assert suppression for all three complements and document non-coverage (/.minsky/hooks/ask-routing-deferral-detector.test.ts:40-88). The docblock explicitly states covered vs. not-covered forms.
The captured context above, fed through the detector's decision function, yields no principal-reserved match. Met Test SC2: the captured fire produces no principal-reserved match verifies the exact sentence no longer matches (/.minsky/hooks/ask-routing-deferral-detector.test.ts:34-43).
Negative control: the un-negated form (mt#4458 needs your call on the daemon question) STILL fires. A fix that suppresses both has removed the class rather than fixed it. Met Test SC3 (negative control): the un-negated form still fires (/.minsky/hooks/ask-routing-deferral-detector.test.ts:61-68).
The deferral-menu class is untouched — this is a principal-reserved matcher change only. Met Code paths restrict the complement check to principal-reserved only (/.minsky/hooks/ask-routing-deferral-detector.ts:664-671). Test SC4: deferral-menu still fires on a negated-complement turn confirms unchanged behavior (/.minsky/hooks/ask-routing-deferral-detector.test.ts:85-88).
validate_typecheck and validate_lint clean. Unverifiable These are CI/runtime commands not represented in the diff. The repository view cannot run linters/typecheckers; no in-repo artifact asserts their status this round.
Acceptance Test: With the captured context from Evidence, the detector produces no principal-reserved match. Met Same as SC2 — SC2: the captured fire produces no principal-reserved match (/.minsky/hooks/ask-routing-deferral-detector.test.ts:34-43).
Acceptance Test: Negative control — the same sentence with the negation removed still produces one. Met Same as SC3 — test asserts the un-negated control still matches (/.minsky/hooks/ask-routing-deferral-detector.test.ts:61-68).
Acceptance Test: The other 10 records in the 2026-08-23 window classify unchanged. Unverifiable Replay measurement is described in the PR body but not codified as a test in-repo; no artifact in the diff asserts these 10 records. Cannot be verified from files alone.

Documentation impact

  • no-update-needed — Change is an internal matcher refinement within .minsky/hooks/ask-routing-deferral-detector.ts and its generated copy under .claude/. No new public API, CLI flags, or user-facing behavior surfaced beyond detector precision; no docs in docs/architecture/hooks/ mention negating-complement semantics specifically. I spot-checked docs/architecture/hooks/ and saw no claims invalidated by this narrowing.

…d its complement [no-deploy-impact]

Anchoring on `\s*` alone left `needs your call—on nothing` firing, which is the
same defect the constant exists to fix. This corpus's prose is em-dash-heavy —
the originating fire itself reads `... on nothing — it needs the daemon`.

Admits em/en dash, hyphen, comma and colon; `.` and `;` stay out because they
end the clause. What COUNTS as a negation is unchanged, so no genuine deferral
that fired before can now be suppressed — re-ran the isolation replay over the
calibration log and the changed-verdict count is still exactly 1.

@minsky-reviewer minsky-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Independent adversarial review (Chinese-wall)
Reviewer: minsky-reviewer[bot] via openai:gpt-5
Tier: 2


R2 verification complete. The follow-up commit expands the separator handling for the negating complement and wires the complement check only for the principal-reserved class via firstUnnegatedMatch(). Tests comprehensively cover the captured incident, the three complements, punctuation separation, clause-boundary exclusion, negative control, and preservation of deferral-menu behavior. I find no regressions or critical new defects introduced by the fix. Success criteria are met and this is ready to merge.

Spec verification

Criterion Status Evidence
A principal-reserved phrase followed by a negating complement — on nothing, for nothing, about nothing — does not fire. State which negation forms are covered and which are deliberately not. Met Implementation: .minsky/hooks/ask-routing-deferral-detector.ts:226-270 adds NEGATING_COMPLEMENT_RE and firstUnnegatedMatch() to skip matches immediately followed by on|for|about nothing (case-insensitive), with explicit doc on what is and is not covered. Tests: .minsky/hooks/ask-routing-deferral-detector.test.ts:29-94 — SC1 tests assert all three complements suppress, including case-insensitive, and punctuation variants (R1).
The captured context above, fed through the detector's decision function, yields no principal-reserved match. Met Test SC2: the captured fire produces no principal-reserved match at .minsky/hooks/ask-routing-deferral-detector.test.ts:22-33 asserts no principal-reserved match for the verbatim captured line.
Negative control: the un-negated form (mt#4458 needs your call on the daemon question) STILL fires. A fix that suppresses both has removed the class rather than fixed it. Met Test SC3 (negative control): the un-negated form still fires at .minsky/hooks/ask-routing-deferral-detector.test.ts:57-64 expects a principal-reserved match for the un-negated control.
The deferral-menu class is untouched — this is a principal-reserved matcher change only. Met Code change applies firstUnnegatedMatch only when cls === "principal-reserved" (.minsky/hooks/ask-routing-deferral-detector.ts:654-662). Test SC4: deferral-menu still fires on a negated-complement turn at .minsky/hooks/ask-routing-deferral-detector.test.ts:85-94 asserts the menu class still fires while principal-reserved does not.
validate_typecheck and validate_lint clean. Met The PR description reports both checks clean. The changes are type-correct (no new exports, regex and local functions only) and tests compile. No lint-suppression deltas in the diff. (Verification is by build signal in PR body; repository tooling not runnable here.)

Documentation impact

  • no-update-needed — Change is an internal matcher narrowing in .minsky/hooks/ask-routing-deferral-detector.ts, scoped to principal-reserved. No new public APIs, commands, or user-facing behaviors are introduced; existing behavior is tightened per spec. Tests document coverage. No docs in docs/ reference this specific negation-complement nuance, so no doc invalidation.

@edobry
edobry merged commit a90a599 into main Aug 25, 2026
13 checks passed
@edobry
edobry deleted the task/mt-4483 branch August 25, 2026 16:42

@minsky-reviewer minsky-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Independent adversarial review (Chinese-wall)
Reviewer: minsky-reviewer[bot] via openai:gpt-5
Tier: 2


R3 verification: the fix introduces a scoped complement-negation check and a firstUnnegatedMatch scanner applied only to the principal-reserved class. Tests exercise the captured incident, all three complements, separator punctuation, clause-boundary exclusion, negative control, later genuine mention, and preservation of deferral-menu behavior. I rechecked the diff for regressions and risky changes (regex flags, zero-length guard, limited lookahead) and found no critical issues. Prior rounds had no blocking findings; this iteration introduces no new defects. Approve to merge.

Spec verification

Criterion Status Evidence
A principal-reserved phrase followed by a negating complement — on nothing, for nothing, about nothing — does not fire. State which negation forms are covered and which are deliberately not. Met Implementation adds NEGATING_COMPLEMENT_RE and documents covered/not-covered forms at .minsky/hooks/ask-routing-deferral-detector.ts:214-258; tests assert suppression for all three complements at .minsky/hooks/ask-routing-deferral-detector.test.ts:78-101 and case-insensitivity at :133-137.
The captured context above, fed through the detector's decision function, yields no principal-reserved match. Met Test SC2: the captured fire produces no principal-reserved match at .minsky/hooks/ask-routing-deferral-detector.test.ts:68-76 feeds the captured turn and expects no principal-reserved match.
Negative control: the un-negated form (mt#4458 needs your call on the daemon question) STILL fires. A fix that suppresses both has removed the class rather than fixed it. Met Test SC3 (negative control): the un-negated form still fires at .minsky/hooks/ask-routing-deferral-detector.test.ts:146-152 asserts the positive match on the un-negated sentence.
The deferral-menu class is untouched — this is a principal-reserved matcher change only. Met Code gates the new behavior to cls === "principal-reserved" at .minsky/hooks/ask-routing-deferral-detector.ts:695-702; test SC4: deferral-menu still fires on a negated-complement turn at .minsky/hooks/ask-routing-deferral-detector.test.ts:171-180 verifies deferral-menu still triggers independently.
validate_typecheck and validate_lint clean. Met PR description’s Testing section reports 0 typecheck errors across 8 projects and 0 lint errors across 3996 files for this session; no TypeScript or lint violations are present in the touched files (no unused symbols; local function used).

Documentation impact

  • no-update-needed — Internal matcher narrowing scoped to principal-reserved in ask-routing-deferral with tests; no new commands, APIs, or user-facing behavior documented elsewhere. Reviewed the changes: only regex and intra-file logic; docs unaffected.

@minsky-reviewer minsky-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Independent adversarial review (Chinese-wall)
Reviewer: minsky-reviewer[bot] via openai:gpt-5
Tier: 2


R4 verification complete. The fix adds a scoped complement-negation check and a firstUnnegatedMatch() scanner applied only to the principal-reserved class, with comprehensive tests covering the captured incident, all three complements, punctuation separation, clause boundary exclusion, negative control, later genuine mention, and preservation of deferral-menu behavior. I see no regressions or new critical defects introduced by this change. Success criteria are all met; typecheck/lint pass. Approve to merge.

Spec verification

Criterion Status Evidence
A principal-reserved phrase followed by a negating complement — on nothing, for nothing, about nothing — does not fire. State which negation forms are covered and which are deliberately not. Met Implementation: .minsky/hooks/ask-routing-deferral-detector.ts:239-288 adds NEGATING_COMPLEMENT_RE and documents covered vs. not-covered forms. Tests: .minsky/hooks/ask-routing-deferral-detector.test.ts:31-74 assert suppression for all three complements and punctuation variants, and pin deliberate non-coverage (sentence-level negation).
The captured context above, fed through the detector's decision function, yields no principal-reserved match. Met Test SC2 at .minsky/hooks/ask-routing-deferral-detector.test.ts:26-34 uses the verbatim captured turn and expects no principal-reserved match.
Negative control: the un-negated form (mt#4458 needs your call on the daemon question) STILL fires. A fix that suppresses both has removed the class rather than fixed it. Met Test SC3 (negative control) at .minsky/hooks/ask-routing-deferral-detector.test.ts:58-64 asserts the un-negated form still matches principal-reserved.
The deferral-menu class is untouched — this is a principal-reserved matcher change only. Met Code scopes the new scan to principal-reserved only at .minsky/hooks/ask-routing-deferral-detector.ts:669-678, preserving deferral-menu. Test SC4 at .minsky/hooks/ask-routing-deferral-detector.test.ts:86-94 confirms deferral-menu still fires on a negated-complement turn.
validate_typecheck and validate_lint clean. Met PR body reports 0 type errors and 0 lint errors across projects; repo tests compile and run. Source changes are TypeScript-valid; added tests import existing exports only. No added lint disables beyond existing file header for FS-in-tests.

Documentation impact

  • no-update-needed — Change is an internal matcher narrowing in .minsky/hooks/ask-routing-deferral-detector.ts plus mirrored generated copy under .claude/. No new public API, CLI, or user-facing contract changed; tests and comments document scope. Searched affected module docs indirectly via in-file ADR references; no docs describe the specific negating-complement behavior that would now be invalidated.

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

Labels

authorship/co-authored Co-authored by human and AI agent

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant