Repository navigation
fix(lint): read a framed placeholder as the mention it is, in line as in a heading - #18484
Conversation
… in a heading `findMigrationPrescription`'s branch 1 tells a placeholder USED as the convention's label from one merely MENTIONED by asking whether an ordinary word directly governs it. A framing word (`Migration`, `迁移`, `rename`, `upgrade`) frames rather than governs, so `FRAMING_TAIL_RE` strips it to expose the word behind it -- but the in-line arm never re-trimmed the space that word sat behind, and `GOVERNING_WORD_RE` is end-anchored. The question was asked of a trailing space, answered "nothing governs this", and the occurrence read as a LABEL. Inserting one framing word between a governor and the placeholder therefore flipped a mention into a label: `the Migration FROM -> TO is documented elsewhere` evidenced a prescription that `the FROM -> TO is documented elsewhere` correctly does not. That is the false-positive direction, where the cost is a hard-blocked PR rather than a misreport. The heading arm already composed the two replacements correctly and nothing held the two equal, so both now go through one `withoutFramingTail` helper and differ only in the question they ask of the result. Measured on this tree before believing it: the card's probe flips to `null` while both controls hold (the framing word removed stays `null`; the framing word with no governor stays `from-to-label`), and the gate's verdict over all 343 changesets in the live stock is byte-identical, 24 hits before and after. The shape this narrowing reaches occurs 0 times across 1160 tracked markdown files (lit control: 892 lines carry the placeholder at all; seeded control: the scanner matches the probe sentence). Self-test: 9 unit pins and an end-to-end pair, F1/F3 and F-E2E-G red without the repair, F2 the positive control, F4 the control that makes F1 a reading, F5-F9 and F-E2E-R the floors in the dangerous direction. Claude-Session: https://claude.ai/code/session_01KB5PFtxuy1x3dcR5gxudx6 Co-authored-by: Claude <noreply@anthropic.com>
… not reach `FRAMING_TAIL_RE` is not word-anchored, so it strips a framing word out of an identifier: `the sys_migration FROM -> TO is documented elsewhere` is governed by `the`, the strip exposes `_` rather than the letter that was there, and the occurrence still reads as a label. The right-trim cannot reach it -- with no whitespace before the framing word there is none left behind to trim -- and word-anchoring the pattern changes WHICH prefixes are stripped at all, a wider claim that needs its own measurement over the changeset stock. Stated rather than hidden, in the docblock that now carries the mechanism. Claude-Session: https://claude.ai/code/session_01KB5PFtxuy1x3dcR5gxudx6 Co-authored-by: Claude <noreply@anthropic.com>
…r0087-framing-word-mention
Contract reviewServed-tier: ① Derived judgmentsDiff scope: one file,
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
Landing provenance — PR #18484, card #17864, head
|
| reading | value |
|---|---|
reviewer transcript "model":"…" |
84 / 84 claude-fable-5-1 |
control — an os-dev round in the same container |
297 / 297 claude-opus-5 |
record's Served-tier: token |
the constant's NAME, which is the accepted spelling (check-clause2-carriers.mjs:2875, CONTRACT_REVIEW_TIER_NAME) — ⛔ never a model id |
Independence
Implemented-by: claude/issue-17864-adr0087-framing-word-mention · Reviewed-by: session_01KB5PFtxuy1x3dcR5gxudx6. Not a self-review: the reviewer was fed the card, the rulings and the PR only — ⛔ never the dispatch order, ⛔ never this seat's conclusions. Its record was adopted verbatim (comment 5700402265); ⛔ a record is adopted whole or voided whole, there is no editing.
落地前检三条
| check | reading | |
|---|---|---|
| ① | at-tier PASS in case, naming this head | record 5700402265, Head-sha: 39b7dd7e71b2650379bbe167b1fe505eb43a3230 — the head this comment names |
| ② | node scripts/pm/check-clause2-carriers.mjs --pair 18484 |
exit 0 — declaration readable in the fixed spelling, both carriers agree, no widening tell |
| ③ | every check green, latest run per check NAME | 35 runs / 31 names, NOT-GREEN = NONE at 15:52:42Z |
Also read, ⛔ not assumed: scripts/pm/check-governed-merges.mjs --pr 18484 → 0 of 1 path hits the register (5 surfaces) ⇒ NOT governed, ordinary queue landing applies. Derived from GOVERNED_SURFACES on the FINAL file list, ⛔ not recalled — the register has grown several times in two days.
Governed Surface Queue Guard (measured three times today) — a pre-ready green is not the reading the queue sees.
Clause-②: no is declared on a line-initial body line (verified on the stored body), which is what a no with no carrier label requires — there is no label to declare it for you.
What the review changed about the round's own account, recorded rather than smoothed over
- The round's ablation was taken at the fix commit's blob
9c46cb6a91, before the comment-only docblock commit; the review re-ran it at the head blob9619a76c73and got the identical red set (F1,F3,F-E2E-G,D7). Reporting imprecision only. - The round's void
/ireading (a loose case-insensitive grep) is discarded, not repaired: the corrected case-sensitive reading is 0 by three independent instruments, each with a lit seed. The review could not reproduce the void 1-hit at all. Nothing in the verdict rests on it. - The PR body's "Structurally, no" about the dangerous direction is true only in the gate's operational sense of governs. It is not false, but it is narrower than it reads — carried as card check-adr-0087-registration: a compound-noun label (
**Schema Migration FROM → TO:**) reads as a MENTION and is admitted with no rewrite #18494 below.
Residue filed BEFORE this enqueue, ⛔ not after
- check-adr-0087-registration: findMigrationPrescription still reads a MENTION as a label through an identifier tail and across a hard wrap #18493 — the two surviving false positives of
findMigrationPrescription:FRAMING_TAIL_REhas no word anchor (an identifier tail likesys_migrationframing-strips), and the in-line arm is line-oriented so a hard wrap walks through it. Both read identically on base and head ⇒ ⛔ not this PR's. Population 0 over 1233 tracked md/mdx, with 12 lit seeds, agrep -zcross-check (0 files, exit 123) and a 104-file lit control. - check-adr-0087-registration: a compound-noun label (
**Schema Migration FROM → TO:**) reads as a MENTION and is admitted with no rewrite #18494 — the false negative this PR exposes: a compound-noun label (**Schema Migration FROM → TO:**) reads as a mention and is admitted with no rewrite. The fairness control is what makes it not-blocking — base already reads the same shapes as mentions with the framing word removed — so head is the consistent application of a pre-existing letter-adjacency rule, and the in-tree population is 0.
What this landing does NOT settle
The gate still has a framing-word predicate that answers the same way for "this is a mention" and for "I cannot tell". Both directions of that ambiguity are now carried on open cards (#18493, #18494) rather than in this PR. SELF_TEST_BATTERY_FLOOR 50 against 55 declared batteries is a shared ratchet, ⛔ not this card's, and is left alone.
Generated by Claude Code
Fixes #17864
Clause-②: no — a repo-root gate script exports no published symbol and adds no key to a published payload.
The defect, by symbol
findMigrationPrescription's branch 1 tells a placeholder used as the convention's label from one merely mentioned by asking whether an ordinary word directly governs it. A framing word (Migration,迁移,rename,upgrade) frames rather than governs, soFRAMING_TAIL_REstrips it to expose the word behind it.labelPositioned's in-line arm never asked the question of that word. The strip removes the framing word and leaves the space it sat behind, andGOVERNING_WORD_REis end-anchored on a letter or的— neither of which a trailing space can be. So the question was asked of a space, answered "nothing governs this", and the occurrence read as a LABEL.⇒ inserting one framing word between a governor and the placeholder flipped a mention into a label. That is the #6967 false-positive direction, where the cost is a hard-blocked PR rather than a misreport: the catch-all is refused as contradicted,
registeredhas no entry to name, and@objectstack/specis published sounpublishedis false — the author is left with no legal disposition at all.The heading arm has composed the two replacements correctly since #17357 (
.replace(FRAMING_TAIL_RE, '').replace(/\s+$/, '')) and nothing held the two arms equal.The card's table, re-derived on the current tree
⛔ Not trusted from the card, which read at
d87a6936ef. Re-run at8fe5cb8e51(origin/mainat 2026-09-16T14:19:18Z), then again after the repair at39b7dd7e71(2026-09-16T15:17Z):the Migration FROM → TO is documented elsewherefrom-to-label(the bug)nullnullnullMigration FROM → TO: delete the blockfrom-to-labelfrom-to-labelOne framing word is the only difference between the probe and control A, and the verdict flips: the probe is a reading, not an assertion. Control B shows the label arm still fires.
The repair
Both arms now go through one
withoutFramingTailhelper and differ only in the question they ask of the result — one definition rather than two that can drift, for the reasonHEADING_REis hoisted. The helper's docblock carries the mechanism, and what the strip deliberately does not reach.Judged in BOTH directions
The fix direction is narrowing, so the dangerous direction is the other one: does anything that should read as a label now read as a mention?
Structurally, no. The right-trim only reaches a prefix where whitespace precedes the framing word, so what the strip exposes is asked the ordinary in-line question. Every non-prose boundary survives, pinned as floors: a finished sentence (
it. Migration FROM → TO:, F5), markup (**Migration FROM → TO:**, F6), a bullet (F7), a heading (the heading arm, unchanged, D2/D4/D6/D7/D9), and an identifier tail with no space to trim (F9). Every occurrence this moves is one where an ordinary word governs a framed placeholder — and each is still granted back bycarriesConcreteRewritethe moment the body SHOWS a rewrite (F8, F-E2E-R): the narrowing removes no hit whose body ships the goods.Adversarial cases beyond the card's three, run at
39b7dd7e71:the sys_migration FROM → TO is documented elsewherefrom-to-label— a separate false positive, see acceptance notessys_migration FROM → TO: delete the blockfrom-to-label(unchanged)the upgrade migration FROM → TO is documented elsewherenullthe Migration FROM → TO …(two spaces)null**Migration** FROM → TO: delete the blockfrom-to-label## The Migration FROM → TOfrom-to-label(headings are labels by construction)their rename FROM → TO guide+ a concrete rewrite in the bodyfrom-to-labeltheir rename FROM → TO guide lives in the release notesnullthe⟨tab⟩Migration FROM → TO …nullMigration. FROM → TO: delete the blockfrom-to-labelThe in-tree population that changes verdict: ZERO, with controls
findMigrationPrescriptionover all 343 changesets in.changeset/, before and after, diff empty — 24 non-null verdicts, same branch and same evidence line for each..changeset/,packages/**/CHANGELOG.md— which is where changeset bodies ship verbatim —content/docs/,docs/,skills/).Reverse verification
The repair is committed first; the ablation drops the right-trim from
withoutFramingTail, proves the mutation reached disk (anchor count 1→0, injected count 0→1, blob9c46cb6a91→87b8997af6), and restores under atraptogit checkout HEAD --naming the file, proven bygit diff HEADempty and the on-disk blob hash equal to the HEAD blob again.Ablated:
--self-testexit 1, 4 failures —F1,F3,F-E2E-G(this repair's red set) andD7, the existing #17357 heading case, which turns out to depend on exactly the same right-trim. Restored: exit 0, 384 assertions. Every floor stayed green under the mutation, which is what makes them floors rather than specimens.Gates
At
39b7dd7e71: 33 of the 34 families derived byscripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackexit 0, plus repo-widepnpm lint(eslint . --no-inline-config) exit 0 — not narrowed, the whole tree.node scripts/check-adr-0087-registration.mjs --base origin/mainexit 0;--self-testexit 0, 384 assertions.⊘ NOT MEASURED —
pnpm check:pm-dispatch-gates: it does not finish inside this container's foreground limit (killed at 300 s and again at 540 s). It was progressing rather than hung — 1551 then 1737 lines of its own passing output — and it gradescheck-dispatch-gates's fixtures, which this diff does not touch. CI runs it.No changeset: this diff publishes nothing from any released package — the root manifest is
privatewith nofileslist, and a repo-root gate script cannot sit inside anypackages/*tarball. Labelledskip-changeset.Acceptance notes
FRAMING_TAIL_REis not word-anchored while its siblingMIGRATION_FRAMING_REis, so it strips a framing word out of an identifier. Probe:findMigrationPrescription('the sys_migration FROM → TO is documented elsewhere')answersfrom-to-labelat39b7dd7e71; the occurrence is governed bythe, and the strip exposes_rather than the letter that was there. Out of scope here because word-anchoring changes WHICH prefixes are stripped at all — a wider claim than transparency, owing its own measurement over the changeset stock, exactly as this card warned. Stated in the helper's docblock rather than hidden. Dedupe words:FRAMING_TAIL_RE, word-anchor, identifier,sys_migration,labelPositioned.SELF_TEST_BATTERY_FLOORis 50 while the roster now declares 55 batteries, so five entries could be deleted before the roster-size floor reds — the floor is documented as a floor, and tightening it is a shared-ratchet edit this card did not ask for. Successor: the next author adding a battery to this file.Generated by Claude Code