Skip to content

fix(lint): read a framed placeholder as the mention it is, in line as in a heading - #18484

Merged
os-warren merged 3 commits into
mainfrom
claude/issue-17864-adr0087-framing-word-mention
Sep 16, 2026
Merged

os-warren merged 3 commits into
mainfrom
claude/issue-17864-adr0087-framing-word-mention

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

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, so FRAMING_TAIL_RE strips 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, and GOVERNING_WORD_RE is 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, registered has no entry to name, and @objectstack/spec is published so unpublished is 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 at 8fe5cb8e51 (origin/main at 2026-09-16T14:19:18Z), then again after the repair at 39b7dd7e71 (2026-09-16T15:17Z):

input before after
probe — the Migration FROM → TO is documented elsewhere from-to-label (the bug) null
control A — the same sentence, framing word removed null null
control B — framing word, no governor: Migration FROM → TO: delete the block from-to-label from-to-label

One 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 withoutFramingTail helper and differ only in the question they ask of the result — one definition rather than two that can drift, for the reason HEADING_RE is 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 by carriesConcreteRewrite the 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:

constructed input verdict
the sys_migration FROM → TO is documented elsewhere from-to-label — a separate false positive, see acceptance notes
sys_migration FROM → TO: delete the block from-to-label (unchanged)
the upgrade migration FROM → TO is documented elsewhere null
the Migration FROM → TO … (two spaces) null
**Migration** FROM → TO: delete the block from-to-label
## The Migration FROM → TO from-to-label (headings are labels by construction)
their rename FROM → TO guide + a concrete rewrite in the body from-to-label
their rename FROM → TO guide lives in the release notes null
the⟨tab⟩Migration FROM → TO … null
Migration. FROM → TO: delete the block from-to-label

The in-tree population that changes verdict: ZERO, with controls

  • The gate's own verdict over the live stock is byte-identical: findMigrationPrescription over all 343 changesets in .changeset/, before and after, diff empty — 24 non-null verdicts, same branch and same evidence line for each.
  • The shape this narrowing reaches occurs 0 times across 1160 tracked markdown files (.changeset/, packages/**/CHANGELOG.md — which is where changeset bodies ship verbatim — content/docs/, docs/, skills/).
  • Lit control, same scanner, same corpora: 892 lines carry the placeholder at all (22 · 851 · 2 · 13 · 4). Seeded positive control: the scanner matches the card's probe sentence and its CJK spelling. Dark control: a fabricated token matches 0.
  • So the answer to "are any of them currently correct?" is that there are none to be correct or incorrect; nothing in this tree moves.

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, blob 9c46cb6a91 → 87b8997af6), and restores under a trap to git checkout HEAD -- naming the file, proven by git diff HEAD empty and the on-disk blob hash equal to the HEAD blob again.

Ablated: --self-test exit 1, 4 failures — F1, F3, F-E2E-G (this repair's red set) and D7, 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 by scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack exit 0, plus repo-wide pnpm lint (eslint . --no-inline-config) exit 0 — not narrowed, the whole tree. node scripts/check-adr-0087-registration.mjs --base origin/main exit 0; --self-test exit 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 grades check-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 private with no files list, and a repo-root gate script cannot sit inside any packages/* tarball. Labelled skip-changeset.

Acceptance notes

  • To file (a reproducible defect, same family, different trigger): FRAMING_TAIL_RE is not word-anchored while its sibling MIGRATION_FRAMING_RE is, so it strips a framing word out of an identifier. Probe: findMigrationPrescription('the sys_migration FROM → TO is documented elsewhere') answers from-to-label at 39b7dd7e71; the occurrence is governed by the, 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.
  • Noted, not filed: SELF_TEST_BATTERY_FLOOR is 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

… 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>

Copy link
Copy Markdown
Collaborator Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 39b7dd7e71b2650379bbe167b1fe505eb43a3230

① Derived judgments

Diff scope: one file, scripts/check-adr-0087-registration.mjs, +155/−2, base blob 8562c08b36 → head blob 9619a76c73; origin/main (9c1897e) still carries the base blob, so no drift. Six regexes byte-identical base vs head; only the composition changed. Every change to what the gate accepts and refuses, one by one:

  1. New helper withoutFramingTail(prefix) = strip FRAMING_TAIL_RE, then right-trim. Verdict: correct; the docblock states the mechanism, the one-word-only strip, and the identifier blind spot.
  2. Heading arm now calls the helper. Verdict: no behavioral change, the arm already composed both replacements since check-adr-0087-registration reads a heading that DENIES a migration prescription as evidence of one, so not-required (no-migration-prescription) is unclaimable by the case it names #17357; ablation 2 (in-line arm alone reverted) leaves D7 green, ablation 1 (helper trim dropped) reds D7 with F1/F3/F-E2E-G, so the round's reading that both arms rest on the same right-trim is verified. The unification is the right one: both arms ask an end-anchored question of the word behind the framing word, and each arm keeps its own pin, so drift in either is still caught. It couples nothing that should stay separate.
  3. In-line arm now asks GOVERNING_WORD_RE of the trimmed strip. This is the only accept/refuse change. What it REFUSES less: an in-line occurrence whose prefix ends letter-or-的, whitespace, one framing word. Measured: card rows re-derived exactly (probe label → null, control A null/null, control B label/label); round's ten pins reproduce verbatim; end to end on committed changesets the framed mention is admitted at head (exit 0) and hard-blocked at base (exit 1, evidence names the sentence), the same body shipping a rewrite is still refused at head (exit 1), the governor-less label still refuses (exit 1). What it should still read as a label and does: markup, bullet, ordered list, finished sentence, paren, dash, digit, identifier tail, heading, any body with a concrete rewrite, Chinese without 的, all label on both copies.
  4. Dangerous direction, my own cases: compound-noun labels flip label → mention on head: **Schema Migration FROM → TO:** delete the block, - Schema Migration FROM → TO:, Key renames FROM → TO:, **Required migration FROM → TO:**, Authoring-key migration FROM → TO:, 字段的 迁移 FROM → TO:, and end to end A1 is base exit 1 / head exit 0 on a prose-only body. Verdict: not a defect this PR introduces. The fairness control shows base already reads the same shapes as mentions without the framing word (**Schema FROM → TO:**, Key FROM → TO:, **Removed keys FROM → TO:** are null on base and head), so head is the consistent application of the gate's letter-adjacency rule through a transparent framing word; the stock writes such labels with a paren, a dash or a bare framing word, all unchanged. In-tree population of the reached shape: 0 occurrences over 1233 tracked md/mdx and 0 over .changeset at current origin/main, with 12 seeded scanner controls lit, a 1000-line (round's 892 reproduced exactly by grep) lit control, and 159 non-null verdicts identical base vs head, 24 of them in .changeset. But the PR's sentence "Structurally, no" is true only in the gate's operational sense of governs; the compound-noun label is an exposed false-negative shape the docblock does not name. See ③.
  5. Roster rows for F1-F9 (9) and F-E2E (2), and the eleven new cases. Verdict: correct; self-test head exit 0 with 384 assertions (base 371); ablation red set is exactly F1, F3, F-E2E-G plus D7, and every floor F2, F4–F9, F-E2E-R stays green under both ablations.
  6. The /i void reading: the corrected case-sensitive reading is 0 by three instruments (scanner, per-line grep, multi-line grep -z with 104 lit files), each with a lit seed. No other reading in the PR carries the flag. I could not reproduce the void 1-hit at all; nothing rests on it.
  7. Hard-wrap re-derivation: grep -z over all prose for governor, whitespace across a newline, framing word, placeholder returns 0 files, lit control 104, seeded w1/w2/inline lit, dark seeds dark. The zero stands.
  8. Real gate at head against origin/main 9c1897e: exit 0. CI Lint & Repo Gates in progress at 15:44Z, NOT MEASURED from CI; pnpm check:pm-dispatch-gates NOT MEASURED, declared by the round and not run here.

② Semver level

skip-changeset with no changeset file is correct. Rule: origin/main AGENTS.md (9c1897e), Post-Task Checklist step 3, lines 1064–1067, the label is for a diff that publishes nothing from any released package. The single changed path is a repo-root script; root package.json is private with no files list; .changeset is identical between base and head; Check Changeset check run success. Clause-②: no is right on the mechanical floor: no published package export or payload key changes.

③ Boundary flags

  • Verified: FRAMING_TAIL_RE has no word anchor while MIGRATION_FRAMING_RE anchors its English alternatives with word boundaries (source read at head). Probe the sys_migration FROM → TO is documented elsewhere answers from-to-label on BOTH base and head; sys_migration FROM → TO: delete the block answers from-to-label on both. It is a surviving false positive of a different trigger, pre-existing, unchanged by this PR. The PR was right to leave it: word-anchoring would also flip sys_migration FROM → TO: (F9/R2) from label to mention, a narrowing owing its own stock measurement. ESCALATE: as of 15:32Z no issue carries it (only PR 18484 mentions FRAMING_TAIL_RE in the 100 issues since 2026-09-16); the PM must file it with the round's dedupe words.
  • New, not raised by the round: the card's shape survives across a hard wrap. the newline Migration FROM → TO … and the Migration newline FROM → TO … both read from-to-label on base and head (the ADR-0087 探测器 branch 1 仍有一条**过匹配**残留:硬换行把「提及」折到行首后仍被读成标签 #7094 control carry their newline FROM → TO migration is null on both). In-tree population 0 by the H1/H2 scanner with lit seeds and by grep -z. Out of this card's diff, owing its own measurement because it touches the wrapped arm; file as same family, trigger: line break.
  • New, not raised by the round: the exposed compound-noun label (judgment 4). Recommend a successor pin either accepting it as a floor with a docblock sentence beside the identifier one, or treating a noun plus framing word as framing; not blocking here because the population is 0 and the base already reads the un-framed shape the same way.
  • SELF_TEST_BATTERY_FLOOR 50 against 55 declared batteries: verified by count. Agree it is a shared ratchet and not this card's.
  • The round's ablation was taken at the fix commit's blob 9c46cb6a91, before the comment-only docblock commit; re-run here at the head blob with the identical red set. Reporting imprecision only.
  • needs:contract-review is not on the PR (labels size/m, skip-changeset); the seat's to hang. The stale os-dev.md note is not a contract matter.

Implemented-by: claude/issue-17864-adr0087-framing-word-mention
Reviewed-by: session_01KB5PFtxuy1x3dcR5gxudx6

VERDICT: PASS


Generated by Claude Code

Copy link
Copy Markdown
Collaborator Author

Landing provenance — PR #18484, card #17864, head 39b7dd7e71b2650379bbe167b1fe505eb43a3230

domain:spec execution seat, session_01KB5PFtxuy1x3dcR5gxudx6, 2026-09-16T15:52Z. Every reading below is command output taken in this window, ⛔ not recall and ⛔ not any round's self-report.

Tier, verified by transcript

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.

⚠️ ③ is re-read after the ready flip before the enqueue, because a ready flip starts 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 blob 9619a76c73 and got the identical red set (F1, F3, F-E2E-G, D7). Reporting imprecision only.
  • The round's void /i reading (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

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

@os-warren
os-warren marked this pull request as ready for review September 16, 2026 15:53
@os-warren
os-warren added this pull request to the merge queue Sep 16, 2026
Merged via the queue into main with commit 2f11e2d Sep 16, 2026
37 checks passed
@os-warren
os-warren deleted the claude/issue-17864-adr0087-framing-word-mention branch September 16, 2026 16:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

check-adr-0087-registration reads a governed MENTION as a label whenever a framing word sits between the governor and the placeholder

2 participants