Skip to content

ADR-0087 探测器 branch 1 仍有一条**过匹配**残留:硬换行把「提及」折到行首后仍被读成标签 #7094

Closed

Description

@os-project-manager

代 #6967 的 dev 提交(它在 ~10 分钟内三次尝试查重均被 GitHub 限流打回,拒绝在未查重的情况下立单——这个克制是对的;查重由座位补做,见文末)。未认领、未定级,定级归分诊。

出处:#6967 / PR #7078(labelPositioned + carriesConcreteRewrite 落地)。

残留

#7078 把「记号被使用」与「记号被提及」区分开的判据,是看占位符紧邻前方由什么支配:只有标记、句子边界或框定词时算标签。

本仓散文硬换行在 ~80 列。于是一条「提及」的 FROM → TO 若恰好折到新行开头,它就位于第 0 列、紧邻前方什么也没有——支配它的那个词在上一行。判据据此把它读成标签,过匹配依旧。

活体标本:.changeset/changelog-ships-in-tarball.md

…changesets to carry their
FROM → TO migration because…

their 在上一行,FROM 在行首。语义上是提及(说的是「changeset 各自携带处方」这件事),形状上是标签。

今天为什么还够不成伤害

该 changeset 不声明破坏性,因此从不被门禁judge——所以是观察类,不是活缺陷。危害要等到某天一条声明破坏性的 changeset 恰好在这个位置折行。

⚠️ 为什么值得立卡,而不是只写进代码注释

这是 dev 的论证,座位认同:

findMigrationPrescription 此前三次修复(#6419 / #6497 / #6559)在块注释里记录过的残留,全部是欠匹配——它们只会多给一次豁免,后果是漏登记,可由 --audit-stock 事后扫出来。

本条是过匹配,与 #6967 本身同属硬卡作者的危害类:命中之后四条处置全堵,作者唯一能做的是去改写一句本身为真的散文。欠匹配和过匹配不该按同一档记录。

已经做了什么

残留已写进门禁的块注释与 labelPositioned 的文档注释,并写明了为什么没有尝试跨行前缀判据:一个折行的标签同样常见,仅凭上一行无法把两者分开。所以这不是「忘了处理」,是一个已知且有理由的边界——立卡是为了让它可被检索、可被分诊定级,而不是只活在一个文件的注释里。

修法方向(不主张)

若要收口,需要的是比「上一行」更强的信号——例如判断上一行是否以句子终止符结尾、或该段落整体是否构成处方块。⚠️ 任何收紧都必须先在存量上测「带走多少条真处方」:#7078 实测过两条更宽的判据,其中一条会带走 5 条真处方(含一条声明破坏性的 app-dead-authoring-keys.md)。先测后写在这条分支上已经有两次教训。

查重(座位补做,dev 当时被限流)

labelPositioned / from-to-label / hard-wrapped / over-match 检索开放单:零同题命中(七条结果分别是 driver-sql $regex、_unpublished、searchableFields 等无关面)。按 #4949 纪律执行。

Activity

  1. claude commented on Aug 9, 2026

    @claude
    Contributor

    Triage: finding + domain:spec-tooling.

    • Grade: observation-class by the card's own honest measurement — the live specimen (.changeset/changelog-ships-in-tarball.md, lines 73-74) declares no breaking change, so the over-match arms nothing today; harm requires a breaking changeset to hard-wrap a mention at column 0. Held as finding until then.
    • Routing anchor (read, not guessed): the fix lands in scripts/check-adr-0087-registration.mjs — verified on origin/main @ ac244ad: labelPositioned at :823, findMigrationPrescription at :889, and the wrapped-line specimen intact in the changeset. A contract gate around the ADR-0087 registries is squarely the "machinery around the spec contract" half of the 2026-08-09 ruling (域标签词表有两个未入表条目:domain:spec-tooling(在用,文件面与 devx 完全重叠)与 domain:ui(一例,与 repo:objectui 重复) #5469) ⇒ domain:spec-tooling, not devx.
    • Dedup: the filing seat already ran it (rate-limited dev, seat completed the search — zero same-topic hits); spot-checked open issues for the over-match/hard-wrap face, nothing new since.
    • The card's own caution stands for any future fix: two wider predicates were already measured to take out 5 real prescriptions — measure before tightening.

    本评论来自分诊座位 Routine(#5474 试点),不构成认领。


    Generated by Claude Code

  2. claude commented on Aug 10, 2026

    @claude
    Contributor

    Findings round (periodic re-verification): HOLD maintained (finding + domain:spec-tooling unchanged). Re-checked @ origin/main 603cab8: scripts/check-adr-0087-registration.mjs anchors drifted by lines only (labelPositioned :823 → :853, findMigrationPrescription :889 → :919); the wrapped-line specimen in .changeset/changelog-ships-in-tarball.md is intact and still declares no breaking change, so the over-match still arms nothing today. Restart condition (a breaking changeset hard-wrapping a mention to column 0) unfired.

    本评论来自分诊座位 Routine(#5474 试点),不构成认领。


    Generated by Claude Code

  3. os-zhuang commented on Aug 11, 2026

    @os-zhuang
    Contributor

    Findings triage: HOLD.

    Re-verified @ origin/main 8dd98bf: labelPositioned now at scripts/check-adr-0087-registration.mjs:853 (line drift only, consumed at :936), and the wrapped-line specimen .changeset/changelog-ships-in-tarball.md is intact and still all-patch bumps (declares no breaking change), so the over-match still arms nothing today. Restart condition unfired: no breaking-declaring changeset has hard-wrapped a mention to column 0. Held — the hazard class (over-match hard-blocks an author with no self-service exit) keeps this worth its record until the column-0 predicate learns to look at the previous line.

    本评论来自分诊座位 Routine(#5474 试点),不构成认领。


    Generated by Claude Code

  4. os-zhuang commented on Aug 11, 2026

    @os-zhuang
    Contributor

    Findings triage round, 2026-08-11 (PM session, maintainer-directed: 「跑一轮集中定级」).

    Graded: promote. This residue is an OVER-match — when it fires it hard-blocks an author with no escape but rewording their own prose, unlike the under-match family that only over-exempts (auditable after the fact). The dev's argument for filing is accepted; the fix is a look-back-one-line refinement of the dominating-token test. S.

    finding → pm:queue (domain:spec-tooling).


    Generated by Claude Code

  5. self-assigned this
    on Aug 11, 2026
  6. os-help commented on Aug 11, 2026

    @os-help
    Collaborator

    Claim: domain:spec-tooling seat (#6018) · session session_01KJATVrh6V2ysutYUJigh3B · branch claude/issue-7094-adr-0087-branch1-overmatch · round 16 · model opus · mode:cloud

    pm:queue → pm:dispatched as one write, read back.

    ⚠️ This detector was measured against a real corpus today — use those readings

    #6350 / PR #7624 (5823d593d, today) ran this detector over the whole v17 stock and left behind numbers you should treat as the current baseline rather than re-deriving from scratch:

    • stock 274 declared-breaking changesets · residue 106 → 97 · flagged candidates 61 → 52 (after 9 entries were registered)
    • ⚠️ The clone must be unshallowed first. A shallow clone suppresses the ledger-touch signal entirely and every candidate reports ? instead of !/~. The script says so itself and prints the fix. Miss this and your whole measurement is silently meaningless.
    • ⚠️ A measured false positive of the same detector, from that run: tidy-donkeys-yawn is flagged ! because no commit that touched that changeset also touched a ledger file — true, because the backfill landed in a separate PR (feat(spec): ADR-0087 台账登记 ctx.user.roles 的立即退役 (#6011) #6138) much later. ! ≠ omission. Your over-match fix must not "fix" that class by accident; it is a different mechanism.

    The deliverable

    Branch 1's residual over-match: a hard line-wrap that folds a mention to the start of a line makes it read as a label. Fix the predicate so a wrapped mention is not read as a declaration.

    The obvious wrong fix, named so you build the test for it: tightening the pattern until the wrapped-mention specimen stops matching, while also losing genuine declarations that happen to be indented or wrapped differently. ⇒ The accept bar is two-sided.

    Accept bar — two-sided, both required

    1. The over-match specimen stops matching — the wrapped mention from this card, verbatim.
    2. Every genuine declaration still matches. ⛔ This is the half that gets skipped. Run the detector over the real corpus before and after and show the flagged-candidate count; a drop beyond the specimen(s) you intended is a regression, not a win. Name any candidate that changed state and why.
    3. The script's own self-test stays green — node scripts/check-adr-0087-registration.mjs --self-test (142 assertions over real temp git repos, per today's run). If your change needs a new fixture, add it there; that harness is the in-repo pin for this detector's arms.
    4. A scanner must prove it can see before any zero is believed — pair "no longer matches" with a positive control that still does.

    Counts: grep -c / wc -l, ⛔ never grep | head; reconcile against the self-test's own pinned expectations.

    Scope

    Gates with invocation scope — read the package.json rather than guessing; ⛔ never paste a bare script name as if runnable.

    Report at draft-PR time (#6644 L2) — ⛔ no idle-polling CI. Issue comment with the os-dev-report marker first, return message second. State explicitly: candidate counts before/after with every state change accounted for, the self-test result, your positive control, and anything you could not verify.


    Generated by Claude Code

  7. os-help commented on Aug 11, 2026

    @os-help
    Collaborator

    os-dev report · session session_01PJS4XSauE4SRNAvRUMyw2Z · branch claude/issue-7094-adr-0087-branch1-overmatch · draft PR #7805 (233052dab)

    What changed

    labelPositioned gained one arm. When the placeholder opens its line (bare or merely indented — a wrapped list item counts), there is no character to its left, so the question is asked of the line above — and it is a deliberately narrower question there:

    • within a line, adjacency is the evidence, so any attached word governs (GOVERNING_WORD_RE, untouched);
    • across a break, adjacency proves nothing — a label may open the line under a finished thought — so only a closed class of determiners, possessives and complement-taking prepositions counts (new WRAPPED_GOVERNOR_RE). carry their continues; ends the line here does not.

    A structural line above (heading, table row, fence marker) does not wrap; a blank line above opens a paragraph. Both keep exactly their prior reading.

    ⚠️ The named wrong fix happened, and its own control caught it. I wrote the wider version first — reuse GOVERNING_WORD_RE across the wrap. It passes the specimen and the whole-stock diff identically (same 2 rows leave), so the corpus alone would have ratified it. It failed only on the wrapped-label assertion, exactly the shape #7078 predicted when it declined this. Pinned as P52 rather than described.

    Candidate counts, before → after (unshallowed clone; no SHALLOW CLONE banner in either run)

    before after
    changesets scanned 1792 1792
    hits 176 174
    of them declared-breaking 132 132
    --audit-stock residue 97 97
    --audit-stock ! candidates 52 52

    Baseline reproduces the seat's readings (residue 97 / candidates 52). --audit-stock output is byte-identical before and after — diff clean, not just equal counts — so no declared-breaking changeset changed state and no worklist row moved. tidy-donkeys-yawn is still flagged !, untouched: that is a different mechanism (a backfill that landed in #6138) and this fix does not reach it.

    Every state change, accounted for — exactly 2 rows, both patch, both mentions on inspection:

    1. changelog-ships-in-tarball.md — the card's specimen, intended.
    2. notification-retirement-evidence-corrected.md — an expansion I am disclosing rather than absorbing. Not named on the card; found by the measurement, not by pattern-guessing. Docs-only correction narrating which commits touched a published FROM → TO (…the evidence and the ↩ FROM → TO), and one on the #4651 app-area entry…). Same wrapped-mention mechanism, same verdict; it declares no breaking change, so like the specimen it was never judged.

    Of the stock's 49 line-opening occurrences, 46 sit under a blank line and are untouched; all 3 with prose above are mentions, and the third keeps its hit (below).

    Positive controls — paired with every zero

    • apimethod-enum-shrink.md — declared-breaking, holds the third wrapped mention and a real label; keeps its hit on the earlier **Migration (FROM → TO).**. Pinned as P61.
    • app-dead-authoring-keys.md — the declared-breaking prescription the earlier rejected widening cost; still matches.
    • P52 — a wrapped label with no concrete rewrite in the body to fall back on, so it cannot pass via corroboration. This is the assertion that killed the wider predicate.

    Gates (invocation read from package.json)

    • pnpm check:adr-0087-registration → green. Self-test 142 → 153 assertions (11 added, P51–P61, both directions).
    • node scripts/check-empty-changeset.mjs, node scripts/check-changeset-no-major.mjs → green.
    • npx eslint scripts/check-adr-0087-registration.mjs --no-inline-config → clean.

    Scope

    In: the predicate, its fixtures, one changeset (@objectstack/spec patch). The gate's diff-only posture is untouched; no registry contents; nothing registered, and #7630's per-major-vs-per-batch question is neither answered nor presupposed; no packages/spec/** or content/docs/releases/**.

    Not verified

    • Prose wrapped inside a fenced block. The fence's opening line is structural and excluded, but a line inside a fence reads as prose and this predicate is given one line of context, not block structure. No stock occurrence has that shape. Stated in the code, not hidden.
    • The closed class is a finite word list; a wrap breaking after some other complement-taking word (e.g. an adjective) would still read as a label. That is the under-match direction — it over-exempts and is auditable after the fact — so it is the safe side to err on, but it is not zero.
    • CI on fix(spec-tooling): a hard-wrapped mention is not a migration label (#7094) #7805 has not been observed; not polled, per the brief.

    Generated by Claude Code

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

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions