Skip to content

fix(gate): one discriminator for the ADR-0087 label-vs-mention decision, both directions pinned - #18933

Merged
os-elon-musk merged 3 commits into
mainfrom
claude/issue-18494-label-mention-discriminator
Sep 18, 2026
Merged

os-elon-musk merged 3 commits into
mainfrom
claude/issue-18494-label-mention-discriminator

Conversation

@os-elon-musk

Copy link
Copy Markdown
Collaborator

Fixes #18494
Fixes #18493

Clause-②: no

Two cards, one predicate: findMigrationPrescription's label-vs-mention decision in
scripts/check-adr-0087-registration.mjs, misclassifying in opposite directions. They are
folded because the repairs are coupled, not because they are adjacent — see "The coupling,
measured" below, which is the reason this is one PR and one battery.

What changed

Commit 1 (#18494) — LABEL_COLON_RE, and one arm in labelPositioned. The in-line arm reads the
ONE character LEFT of the placeholder (GOVERNING_WORD_RE), which is structurally blind to a label
whose own words are a noun phrase: **Schema Migration FROM → TO:** delete the block has an
ordinary letter there and is a label all the same. The signal the left side cannot supply is on the
right — the convention's own colon, which a running sentence never puts on its object. The class is
derived from the corpus, not invented: over 1322 tracked md/mdx at 625db0e85 (1007
occurrences), 321 are closed by a colon and the markup that may stand between the placeholder and
that colon is exactly three runs — nothing (295), a closing paren (25) and a bold marker (1). The
full-width colon rides along as this file's ordinary Chinese parity at a measured population of
zero, on the precedent REMOVAL_INSTRUCTION states for its own Chinese arm. Asked AFTER the
heading arm, so punctuation cannot overturn a denial (#17357).

Commit 2 (#18493) — two failures of the #17864 transparency rule to compose:

  1. FRAMING_TAIL_RE carried no word anchor while its sibling MIGRATION_FRAMING_RE anchors
    its English alternatives, so a framing word that is merely the tail of an identifier was
    stripped as a word of its own: the sys_migration FROM → TO is documented elsewhere stripped to
    the sys_, exposed an underscore instead of the letter that was really there, and answered
    "nothing governs this". The anchor is a negative lookbehind rather than a consuming
    boundary, because the match is fed to String.replace and a consumed delimiter would be deleted
    with the word.
  2. The in-line arm is computed within ONE line, so a hard wrap put the governor out of its reach in
    both directions. A prefix that is entirely a framing word now falls through to the
    cross-line arm — transparency means the word is not there, so the placeholder opens its line —
    and the cross-line arm strips a framing tail off the line above before asking
    WRAPPED_GOVERNOR_RE.

The reversal clause — #18493's half admits inputs the gate refuses today

That is legitimate only as the removal of a mis-refusal, and the gate's own docblock on
withoutFramingTail said so before the anchor existed, in these words:

The identifier shape is a SEPARATE false positive, stated rather than hidden and out of this
repair's reach: the sys_migration FROM → TO is documented elsewhere is governed by the and
still reads as a label, because what the strip exposes is _ rather than the letter that was
there.

and, on the wrap, labelPositioned's own statement of the rule:

A FRAMING word between the governor and the placeholder is transparent, and transparency is a
claim in BOTH directions (#17864). It does not make an occurrence a label — the Migration FROM → TO is documented elsewhere is the same mention as the FROM → TO is documented elsewhere, one
framing word apart

Nothing beyond those documented mention shapes is admitted: the change adds no vocabulary and no
new exemption, it composes two rules the file already states. Counter-evidence for that claim is
the residue, stated in the docblock rather than reached for — a framing word after a hyphen or a
dot is still stripped, exactly as a word boundary reads it. Measured over the same tree, the
population of EVERY in-line framing tail, of any delimiter, is 0, so no reading here separates
those classes, and widening what the gate ADMITS on an unmeasured shape is the maintainer's floor.
Filed as a finding instead.

Per-card criteria, each demonstrated independently

Through the real CLI on a committed probe changeset in a temp repo, with the gate's
pre-change blob and its post-change blob staged into the same fixture, reading the process exit
code (base = 625db0e85, head = this branch):

case base head
#18494 probe: **Schema Migration FROM → TO:** delete the block, no concrete rewrite exit 0 exit 1, Evidence (from-to-label) naming that label
#18494 control: the same sentence with no colon (the #17864 mention) exit 0 exit 0
#18493 T1 mention: the sys_migration FROM → TO is documented elsewhere exit 1 exit 0
#18493 T1 label FLOOR: sys_migration FROM → TO: delete the block (F9/R2) exit 1 exit 1
#18493 T2 W1: governor ends the line, framing word opens the next exit 1 exit 0
#18493 T2 W2: governor and framing word above, placeholder below exit 1 exit 0

#18494's card lists six label shapes, all reading null on current main; the whole list was
treated as the target set and all six now read from-to-label, as do the card's own three fairness
controls (a noun-phrase label with no framing word in it at all — the same defect one word
shorter, which this discriminator cannot and should not separate).

The coupling, measured

Word-anchoring FRAMING_TAIL_RE takes the gate's own F9 floor with it —
sys_migration FROM → TO: stopped being a label by an accident of the strip — unless the colon
that actually closes that label is holding it up. That is not an argument; it is ablation A below,
where removing the #18494 arm reds F9, a pin neither card touches.

Reverse verification, each leg through scripts/ablation-replace.mjs (anchor must hit, on-disk
counts and blob hashes asserted, restore proven by blob-equals-HEAD and an empty git diff HEAD):

ablation what is removed self-test
A the colon arm 15 red: L1-L7, L-E2E-R, X2, SS1, SS3, SS7 — and F9, the pre-existing floor
B the identifier word anchor 3 red: X1, SS4, SS8
C the entirely-framing fall-through 2 red: X4, X10
D the wrap-side framing strip 2 red: X5, SS5

Anti-see-saw: SS1-SS8 pins the two directions as minimal pairs in ONE battery under one
floor — each pair differs by one character or one line break and asserts opposite verdicts, so a
tuning that buys one direction with the other reds a pair, and deleting half the pairs to silence
that drops the battery below its pinned count. L1-L13 and X1-X11 carry each card's own reds and
floors.

The gate I am editing judges this PR

Before: exit 0, self-test 405 assertions. After: exit 0, self-test 441 assertions — the
battery grew by 36 and shrank nowhere.

Gates

Derived from the change set by node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack
(1 path, three-dot semantics, merge base d5ccfb4e3), each run with its exit code redirected to a
file and read back, then reconciled with --ran: 32 derived, 32 run, 0 NOT-MEASURED, 0 UNRUN,
all exit 0
— a derived zero, not a claimed one, and no family exited 3.

Stock measurement, the one #18494's second direction owes: over 429 changesets and 1322
tracked md/mdx at 625db0e85, zero verdict deltas for the whole change, with the
before/after instrument lit 3/3 by the three probe shapes. pnpm lint narrowed and the
narrowing declared: 1 of the 6855 files eslint's own config selects, 0 errors 0 warnings, exit 0
(--format json); the config states that this repo "never enables type-aware linting
(no parserOptions.project, no typed rules) for ANY file", so a one-file diff cannot move the
verdict on a file it did not touch. Readings taken at b0678d370. origin/main merged once before
this PR opened.

Acceptance notes

Noted, not filed — observations, no repro, no contract text:

  • MIGRATION_FRAMING_RE's docblock names migrations/registry.ts among the identifiers its word
    anchor keeps out, but a word boundary treats a slash as a boundary too, so that one alternative
    of the claim does not hold as written. Successor: the next PR that touches that docblock.
  • The in-line framing-tail path has population 0 over the whole corpus, in every delimiter class,
    so both arms repaired here are pins against a future author rather than against today's stock —
    exactly as both cards state.

维护者速读(草稿)

改了什么 —— 一个门禁的判别器:changeset 正文里的 FROM → TO 到底是「这里就是改写清单」(标签)
还是「改写清单在别处」(提及)。两张卡是同一个判断的两个反方向,合成一个 PR 修:一边放过了不带改写
的破坏性变更,一边拦住了本不该拦的作者。

为什么改 —— 放过的那一边是真成本:破坏性改动进了消费者手里,既没有改写说明,也没有登记入账,而
门禁报的是一次干净的运行。拦错的那一边是摩擦成本:作者被要求补一份他不欠的改写,而门禁给出的「证据」
就是他自己那句话。两边不能分开修——收紧一边会把另一边推得更远,这份 PR 的实测把这条耦合摆出来了。

风险与代价(含回滚) —— 全量实测:429 份 changeset、1322 个 md/mdx 文件,判定零变化,仪器三/三
点亮;门禁自身 32 族全绿。回滚按倒序:先回退第二个 commit(#18493)、再回退第一个(#18494)。
⚠️ 只回退第一个而留下第二个会红掉门禁已有的 F9 底线——这正是两张卡耦合的机械证据,不是本 PR 的缺陷。
未做的事:词边界在连字符/点号后留下的残余没有跟着修,已作为 finding 上报,因为那会在零实测的形状上
放宽门禁的接纳面,那是维护者的底线。

席位意见 ——

你要做的 —— 判两件事:① #18494 那一半是 widening(门禁更严),它落在六种拼法之外还带上了卡面
自己的三个 fairness control——这个判别器分不开、也不该分开它们,请确认这个更大的目标集可以接受;
② #18493 那一半让门禁接纳了它今天拒收的输入,上面引了门禁自己的原话证明那是误拒,请确认这条引用
成立。若两条都成立,这个 PR 只欠合并。


Generated by Claude Code

…colon that closes it

`findMigrationPrescription`'s in-line arm reads the ONE character left of the
`FROM -> TO` placeholder (`GOVERNING_WORD_RE`). That is sound for the shapes it
was measured on and structurally blind to one family: a label whose own words are
a noun phrase. `**Schema Migration FROM -> TO:** delete the block` has an
ordinary letter immediately before the framed placeholder, so the occurrence read
as a MENTION, the body evidenced no prescription, and a changeset claiming
`not-required (no-migration-prescription)` while shipping no rewrite at all was
admitted -- a published break reaching consumers with neither a prescription nor
a ledger entry.

The signal the left side cannot supply is on the right: `FROM -> TO:` is the
convention's own punctuation for "what follows is the mapping", and a running
sentence does not put a colon on its object. `LABEL_COLON_RE` is a closed class
derived from the corpus (1322 tracked md/mdx at 625db0e, 1007 occurrences): 321
are closed by a colon, and the markup that may stand between the placeholder and
that colon is exactly three runs -- nothing (295), `)` (25) and `**` (1). The
full-width colon rides along as this file's ordinary Chinese parity at a measured
population of zero, so the disposition does not turn on the author's keyboard.

Direction: a colon can only ever make an occurrence a LABEL, which is what the
gate REFUSES the catch-all on, so nothing admitted today is refused because of
`carriesConcreteRewrite` and nothing refused today is admitted. It is asked
AFTER the heading arm so punctuation cannot overturn a denial (#17357). Measured
over the whole stock at that tree: 429 changesets and 1322 md/mdx files, ZERO
verdict deltas, instrument lit by the probe shape.

Clause-②: no

Claude-Session: https://claude.ai/code/session_019srGWGCBBCBHqcDoRZpQRh
Co-authored-by: Claude <noreply@anthropic.com>
…ose wraps

`findMigrationPrescription` reads a MENTION as a LABEL through two failures of
the #17864 transparency rule to compose -- the false-positive direction, where
the gate hard-blocks a PR by demanding a concrete rewrite the author does not
owe, with an evidence line naming the author's own sentence.

1. `FRAMING_TAIL_RE` carried no word anchor while its sibling
   `MIGRATION_FRAMING_RE` anchors its English alternatives, so a framing word
   that is merely the tail of an IDENTIFIER was stripped as a word of its own:
   `the sys_migration FROM -> TO is documented elsewhere` stripped to `the sys_`,
   exposed `_` instead of the `n` that was really there, and answered "nothing
   governs this". The anchor is a lookbehind rather than a consuming boundary,
   because the match is fed to `String.replace`.

2. The in-line arm is computed within ONE line, so a markdown hard wrap put the
   governor out of its reach in both directions. A prefix that is ENTIRELY a
   framing word now falls through to the cross-line arm -- transparency means the
   word is not there, so the placeholder opens its line -- and the cross-line arm
   strips a framing tail off the line above before asking the closed class.

The two cards are coupled rather than adjacent: the anchor takes the F9 floor
(`sys_migration FROM -> TO:`) with it unless the colon that actually closes that
label is holding it up, which is the first commit on this branch. Pinned as
minimal pairs in one battery (SS1-SS8) so neither direction can be traded for
the other, plus X1-X11 for both triggers and their floors. The residue `\b`
leaves -- a framing word after a hyphen or a dot -- is stated in the docblock
rather than reached for: measured over 429 changesets and 1322 tracked md/mdx at
625db0e, the population of every in-line framing tail is 0, so nothing
separates those classes, and widening what the gate ADMITS on an unmeasured
shape is not this predicate's call.

Stock: zero verdict deltas over that corpus, instrument lit 3/3.
Self-test 405 -> 441 assertions.

Clause-②: no

Claude-Session: https://claude.ai/code/session_019srGWGCBBCBHqcDoRZpQRh
Co-authored-by: Claude <noreply@anthropic.com>
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

2 participants