Commit 2f11e2d
fix(lint): read a framed placeholder as the mention it is, in line as in a heading (#18484)
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](https://claude.ai/code/session_01KB5PFtxuy1x3dcR5gxudx6)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 8fa6b97 commit 2f11e2d
1 file changed
Lines changed: 155 additions & 2 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
403 | 403 | | |
404 | 404 | | |
405 | 405 | | |
| 406 | + | |
406 | 407 | | |
407 | 408 | | |
408 | 409 | | |
| |||
432 | 433 | | |
433 | 434 | | |
434 | 435 | | |
| 436 | + | |
435 | 437 | | |
436 | 438 | | |
437 | 439 | | |
| |||
1217 | 1219 | | |
1218 | 1220 | | |
1219 | 1221 | | |
| 1222 | + | |
| 1223 | + | |
| 1224 | + | |
| 1225 | + | |
1220 | 1226 | | |
1221 | 1227 | | |
1222 | 1228 | | |
1223 | 1229 | | |
| 1230 | + | |
| 1231 | + | |
| 1232 | + | |
| 1233 | + | |
| 1234 | + | |
| 1235 | + | |
| 1236 | + | |
| 1237 | + | |
| 1238 | + | |
| 1239 | + | |
| 1240 | + | |
| 1241 | + | |
| 1242 | + | |
| 1243 | + | |
| 1244 | + | |
| 1245 | + | |
| 1246 | + | |
| 1247 | + | |
| 1248 | + | |
| 1249 | + | |
| 1250 | + | |
| 1251 | + | |
| 1252 | + | |
| 1253 | + | |
| 1254 | + | |
| 1255 | + | |
| 1256 | + | |
| 1257 | + | |
| 1258 | + | |
| 1259 | + | |
| 1260 | + | |
| 1261 | + | |
| 1262 | + | |
| 1263 | + | |
| 1264 | + | |
| 1265 | + | |
| 1266 | + | |
| 1267 | + | |
| 1268 | + | |
| 1269 | + | |
| 1270 | + | |
| 1271 | + | |
| 1272 | + | |
| 1273 | + | |
| 1274 | + | |
1224 | 1275 | | |
1225 | 1276 | | |
1226 | 1277 | | |
| |||
1368 | 1419 | | |
1369 | 1420 | | |
1370 | 1421 | | |
| 1422 | + | |
| 1423 | + | |
| 1424 | + | |
| 1425 | + | |
| 1426 | + | |
| 1427 | + | |
| 1428 | + | |
| 1429 | + | |
| 1430 | + | |
| 1431 | + | |
| 1432 | + | |
| 1433 | + | |
| 1434 | + | |
1371 | 1435 | | |
1372 | 1436 | | |
1373 | 1437 | | |
| |||
1418 | 1482 | | |
1419 | 1483 | | |
1420 | 1484 | | |
1421 | | - | |
| 1485 | + | |
1422 | 1486 | | |
1423 | | - | |
| 1487 | + | |
| 1488 | + | |
| 1489 | + | |
| 1490 | + | |
| 1491 | + | |
| 1492 | + | |
| 1493 | + | |
1424 | 1494 | | |
1425 | 1495 | | |
1426 | 1496 | | |
| |||
4290 | 4360 | | |
4291 | 4361 | | |
4292 | 4362 | | |
| 4363 | + | |
| 4364 | + | |
| 4365 | + | |
| 4366 | + | |
| 4367 | + | |
| 4368 | + | |
| 4369 | + | |
| 4370 | + | |
| 4371 | + | |
| 4372 | + | |
| 4373 | + | |
| 4374 | + | |
| 4375 | + | |
| 4376 | + | |
| 4377 | + | |
| 4378 | + | |
| 4379 | + | |
| 4380 | + | |
| 4381 | + | |
| 4382 | + | |
| 4383 | + | |
| 4384 | + | |
| 4385 | + | |
| 4386 | + | |
| 4387 | + | |
| 4388 | + | |
| 4389 | + | |
| 4390 | + | |
4293 | 4391 | | |
4294 | 4392 | | |
4295 | 4393 | | |
| |||
5943 | 6041 | | |
5944 | 6042 | | |
5945 | 6043 | | |
| 6044 | + | |
| 6045 | + | |
| 6046 | + | |
| 6047 | + | |
| 6048 | + | |
| 6049 | + | |
| 6050 | + | |
| 6051 | + | |
| 6052 | + | |
| 6053 | + | |
| 6054 | + | |
| 6055 | + | |
| 6056 | + | |
| 6057 | + | |
| 6058 | + | |
| 6059 | + | |
| 6060 | + | |
| 6061 | + | |
| 6062 | + | |
| 6063 | + | |
| 6064 | + | |
| 6065 | + | |
| 6066 | + | |
| 6067 | + | |
| 6068 | + | |
| 6069 | + | |
| 6070 | + | |
| 6071 | + | |
| 6072 | + | |
| 6073 | + | |
| 6074 | + | |
| 6075 | + | |
| 6076 | + | |
| 6077 | + | |
| 6078 | + | |
| 6079 | + | |
| 6080 | + | |
| 6081 | + | |
| 6082 | + | |
| 6083 | + | |
| 6084 | + | |
| 6085 | + | |
| 6086 | + | |
| 6087 | + | |
| 6088 | + | |
| 6089 | + | |
| 6090 | + | |
| 6091 | + | |
| 6092 | + | |
| 6093 | + | |
| 6094 | + | |
| 6095 | + | |
| 6096 | + | |
| 6097 | + | |
| 6098 | + | |
5946 | 6099 | | |
5947 | 6100 | | |
5948 | 6101 | | |
| |||
0 commit comments