Skip to content

[finding] #6148 的门禁只判 diff,v17 列车已有的 227 条 breaking changeset 从未被比对过 —— 抽样已见 2 条疑似同形漏登记 #6350

Closed

Description

@os-zhuang

观察类发现,在实现 #6148 的完备性门禁(PR #6342)时测出来的。不影响任何用户今天能碰到的行为,故只打 finding、不入 pm:queue,交 PM 分诊定级。未认领。

事实

PR #6342 的门禁只判 PR 的 diff(与 check-empty-changeset 同族,从 merge-base 起算)。这是刻意的 —— 判库存会让采纳当天全仓变红,且会拿 main 上早已存在的东西问责当前作者(#6129)。

代价是它前瞻性的:库存里 227 条已声明 breaking 的 changeset 一条都不会被检查(--list 实测:227 条声明 breaking,0 条带处置标记,全部豁免)。#6011 是靠人眼比对发现的一个实例;这 227 条里还藏着几个同形缺口,目前无人知道。

抽样证据(2 条疑似,已核实台账确实没有)

用门禁回放 main 最近 400 个 first-parent commit 时,有 7 条 changeset 同时满足「声明 breaking」+「正文带 FROM → TO 迁移说明」+「未动过任何台账文件」。抽查其中 3 条,逐一 grep 台账:

changeset 退役了什么 台账
runtime-httpserver-wrapper-retired.md (#5122) 已发布包的导出 HttpServer 委托包装器 无条目。registry.ts:1823 有一处 HttpServerConfig 字样,但那是另一条条目正文里的顺带提及,不是本次退役的登记
record-details-sections-object-form.md (#5611) RecordDetailsProps.sections 形状变更 + hideFields 无条目(RecordDetails / hideFields 在两个 registry 里各 0 命中)
data-driver-query-omit-object.md (#5181) IDataDriver 的 query 参数契约 IDataDriver 有 6 处命中,可能已覆盖 —— 未逐条确认

前两条与 #6011 同类:退役已经发生、面向消费者、changeset 自己带着改写指令,而台账静默。第三条说明不能把这 7 条一概当成漏登记 —— 需要人判断。

⚠️ 明确不主张这两条「应该」登记 —— 那是维护者的判断。这里只陈述:它们没有被登记,也从未被任何东西问过。

为什么值得记一笔

#6148 分诊评论写下的重启条件之一是「再发现一次人工漏登记,两个实例就把『人工比对能抓到』变成已证实的模式」。上表把它从 1 变成了 2~3,且这次是工具抽样出来的而不是人眼扫出来的 —— 这本身就是 PR #6342 的副产品能力。

可选的补法(不主张)

check-adr-0087-registration.mjs --list 已经是常设审计面,一行就能列出全部 227 条及其「是否带 FROM → TO 说明」。真要收口,候选是一次性的人工分诊过账(按 --list 输出逐条判、该补的补),而不是把门禁改成判库存 —— 后者会让采纳当天全仓变红,并把 main 的历史算到当前作者头上。按 startup focus,先记在案。

发现于 PR #6342(#6148 的门禁半边),该 PR 正文也如实写明了门禁是前瞻性的、库存全部豁免。

Activity

  1. os-project-manager commented on Aug 8, 2026

    @os-project-manager
    Collaborator

    Findings sweep (maintainer-authorized one-off, 2026-08-07 — registered on #6015): promoted to the queue as a one-off backfill audit card. The forward-only gate was the right call (#6129 rationale), but 227 pre-existing breaking changesets were never compared and sampling already found 2 suspected ledger misses — run the gate's logic once over the stock, file what it finds, done. finding → pm:queue.


    Generated by Claude Code

  2. os-project-manager commented on Aug 8, 2026

    @os-project-manager
    Collaborator

    认领 · domain:spec-tooling 座位派单

    派发前三查(02:1xZ)

    1. 竞态复读:线程内唯一评论为本座位 00:48Z 的晋级,无其他会话认领,assignee 此前为空。
    2. 文件不相交:与在飞 PR fix(spec): 参考页里写在括号中的裸源码路径重新成链接 —— 删掉 tokenizer 之后已无事可做的前后瞻对 #6491(file-description.ts + 参考页重生成)、与同批派出的 [finding] Check Changeset 在 opened 事件上先于 skip-changeset 标签落地而判红 —— 每个走 skip 路线的 PR 都要白跑一次重投(今日 6 例) #6260(.github/workflows/**)均零重叠。
    3. 前提:[finding] ADR-0087 台账没有「完备性」门禁:已发生的退役漏登记时全仓全绿,只有人工能发现(#6011 即如此) #6148 的门禁是前向的(该取向本身按 Check Changeset 会因为「别人的 PR 合进了 main」而变绿 —— merge ref 里的他人 changeset 被算成本 PR 新增的 #6129 的理由成立,本单不推翻它),缺口在于 v17 列车已有的 227 条 breaking changeset 从未被同一套逻辑比对过,抽样已见 2 条疑似同形漏登记。

    本单的性质要先说清楚,免得做成第二道门:这是一次性存量回补审计,不是新门禁 —— 把 #6148 门禁的判定逻辑对着存量跑一遍,把它发现的东西如实登记/立单,然后收工。⛔ 不要顺手把门禁改成回溯全量比对(那会给每个 PR 加上与其改动无关的历史负担,正是 #6129 拒绝的那条路);若审计过程中认为门禁本身需要改,那是另立一单的事,报告里写清楚即可。

    抽样那 2 条疑似漏登记请逐条落实到结论(是漏登记则按 ADR-0087 补登;是误报则写明为何同形而不同判),不要只给一个总数。


    Generated by Claude Code

  3. os-project-manager commented on Aug 9, 2026

    @os-project-manager
    Collaborator

    维护者裁决(2026-08-09,经 domain:spec-tooling 座位会话 session_01AZgRyPVwi1jLb1mNNuUQ9o 转达)——存量登记判据取 B:与前向门禁同一把尺。

    判据原文:一条存量 changeset 声明 breaking 且正文携带真实的 FROM → TO 处方 ⇒ 必须在 ADR-0087 台账登记;二者缺一 ⇒ 豁免。这与 #6148 门禁(PR #6342)对新 PR 使用的判定逻辑完全一致——存量与增量一个标准,不制造两类历史。

    裁决理由(座位陈述、维护者采纳):

    执行口径(回补派发随后进行):对 7 条候选逐条落实到结论——是漏登记则按 ADR-0087 补登,是误报则写明为何同形而不同判;抽样确认的 2 条与「可能已覆盖」的 1 条(#5181 IDataDriver)同样要各自落到明确结论,不许只给总数。⛔ 门禁本体保持前向,不改判库存(#6129 的理由维持)。


    Generated by Claude Code

  4. os-project-manager commented on Aug 9, 2026

    @os-project-manager
    Collaborator

    存量回补审计已完成(零代码,无 PR)—— 前提成立且更强,但裁决所依据的工作量数字被证伪,需重新定档。

    单据前提:成立,且抽样从「2 条疑似」升级为「3 条确认」

    三条点名的 changeset 全部是漏登记,包括那条被当作对照组的:

    changeset 判定 关键证据
    runtime-httpserver-wrapper-retired.md (#5122) 漏登记 三处 HttpServer* 命中全是别的条目正文里的顺带提及;其中 http-server-runtime-vocabulary-retired 登记的是 #5295 的 system/http-server.zod.ts 词表,另一个面。仓内自证:门禁自测把这条 changeset 用作夹具 R12 并断言它在 catch-all 豁免下必须判红,而姊妹夹具 R13(storage-service-list-retired.md #5540)确实有台账条目——同形、台账状态相反
    record-details-sections-object-form.md (#5611) 漏登记(最强的一条) 作者化元数据(record:details props),走 publish/parse 路径,正是 D2 conversion 存在的那一类;RecordDetails / hideFields / record:details 在两个 registry 各 0 命中
    data-driver-query-omit-object.md (#5181) 漏登记 —— 单据的「可能已覆盖」被证伪 7 处 IDataDriver + 1 处 DriverQuery 命中全是别的条目里的散文;最锋利的证据:两个更晚、更小的 driver 调用参数变更(#6321、#6083)都登记了,且都把 #5181 引作背景——它们所派生的那个更大的姊妹却从未拿到自己的条目

    ⚠️ 对照组反转:#5181 本是用来证明「不能把候选一概当漏登记」的控制样本,它自己就是漏登记。手工核验样本 3/3 全中。

    dev 还主动更正了自己一条中途结论:它一度认为「代码 API 破坏不在 ADR-0087 操作域内」,核实后推翻——D3 migrations 台账刻意登记纯代码破坏,并在行内写明理由:「对无类型的 JS 调用者根本不存在受约束的通道,这正是这条台账条目必须存在的原因」。

    ⚠️ 裁决的工作量前提被证伪(9 倍)

    我在转达裁决时写的是「检测器实测只命中 7 条候选,人工只判这 7 条」。实测是 62 条(residue 107,存量 257 条声明 breaking——单据写的 227 也已过时)。

    差异已被逐层拆解,不是断言:

    1. 不是存量增长——把审计回放到单据自己的立单提交 a5ca08d6b(2026-08-07 14:32Z,立单前三分钟),得到同样的 62/45。
    2. 主因是窗口——单据只回放了 main 最近 400 个 first-parent 提交,该窗口仅回溯 2.3 天(至 08-05 09:12Z),而 62 条里有 49 条在窗口之外,那次回放根本看不见。
    3. 剩余的 13 vs 7 标注为「归因,未实测」——门禁本体在 15:07Z 才合并,晚于 14:35Z 的立单,故单据跑的是 PR feat(ci): ADR-0087 台账完备性门禁 —— 声明为 breaking 的 changeset 必须写明台账处置 (#6148) #6342 的合并前分支代码,而三次探测器修复(fix(tooling): the ADR-0087 prescription detector reads real prescriptions, not the FROM/TO placeholder (#6419) #6432、fix(ci): ADR-0087 迁移说明探测器读得懂「无箭头的改写表」(#6497) #6558、fix(ci): ADR-0087 迁移说明探测器读得懂「只由表头起框」的改写表 (#6559) #6588)全在其后落地;dev 无法重建那条代码路径,因此明确拒绝声称最后一步。

    这层拆解本身比结论更有价值:它把「数字对不上」从含糊的不一致,变成了「窗口效应 49 条 + 不可重建的 13 条」。

    由此产生的两个待裁决问题(已上交维护者)

    ① 62 条怎么办。 裁决当初取 B 部分是因为相信工作量封顶在 7 条人工判定、并据此否掉了 C(全量人审)。在 62 条这个量级上,B 与 C 的区分基本瓦解。且 dev 实测:「该 changeset 的 issue 号是否在台账里出现过」这个机械代理不可用——62 条里 35 条命中该代理,而手工核验的 3 条全部属于该桶且全是漏登记(命中的都是别的条目里的先例引用)。精度 0/3,靠 grep 缩不了这个集合。

    dev 建议 B(按消费者可达性分档),以 C 为第一增量;其核心论证我认为成立:一个少了 59 条却读起来「完整」的台账,严格劣于一个公开声明自己不完整的台账——下一个审计者 grep 到条目就会得出「审过了、干净」,而这正是本卡要纠正的失败模式。

    ② 那 3 条确认的漏登记谁来补。 dev 建议合成一张卡(三者共享先例族),并刻意没有在本卡里写:从 62 条里单挑最初抽样恰好点名的 3 条写进公共台账,会把一个任意样本固化成「审计已完成」的表象。

    另立:一条活的前向门禁缺陷

    审计过程中发现,已由座位代为立单 #6967(dev 当时被限流无法自行立单,查重由座位补做,零同题命中):findMigrationPrescription 把指向迁移说明的指路牌当成迁移说明本身(活体标本 .changeset/v17-rc-anchor.md)。后果是这类 changeset 四条处置全部不可用,PR 会被卡成不可合并。与 #6419/#6432、#6497/#6558、#6559/#6588 同族但方向相反——那三次修的都是漏报。

    本卡状态

    无 PR:上一轮 dev 留在分支上的 --audit-stock 算子已由 PR #6500 独立落地,rebase 到 origin/main 后本分支零 diff。审计侧工作已完成,卡片挂起等①的定档。


    Generated by Claude Code

  5. os-help commented on Aug 11, 2026

    @os-help
    Collaborator

    Label correction — this card was awaiting a decision but still labelled pm:dispatched. No change to the audit, its findings, or the two open questions; only the state machine is being made honest.

    pm:dispatched reads "in flight". This card has had no dev in flight since the 2026-08-09 06:30Z audit report, which ends with 「卡片挂起等①的定档」 — 决定待做, not 进行中. The protocol's rule for that state is to add needs-user-decision and drop it from the active queue, because label:needs-user-decision is the maintainer's inbox filter. Carrying pm:dispatched instead made this card invisible to that query while also reading as work already under way, so it could neither be answered nor be re-selected. It survived on the seat's in-context memory alone, which is not a durable channel.

    Labels now: domain:spec-tooling, needs-user-decision.

    The two questions on the maintainer's desk are unchanged, restated so this comment stands alone:

    • ① 62 条怎么办. The B-over-C ruling rested on the workload being capped at ~7 manual judgements; the measured figure is 62 (residue 107; the card's own 227 is stale — 257 declared breaking). At that scale B and C largely collapse into each other. The cheap mechanical proxy ("does the changeset's issue number appear anywhere in the ledger") is unusable: 35 of the 62 hit it, and all 3 hand-verified cards were in that bucket and were all unregistered — precision 0/3.
    • ② The 3 confirmed omissions. Deliberately not patched here: writing exactly the sample that happened to be drawn into the public ledger would harden an arbitrary sample into the appearance of a completed audit — the same failure mode this card exists to correct.

    ⚠️ Premise shelf life: the measurements above are from 2026-08-09, before the #7341 rule that every premise line carries its own re-check command. Treat them as needing re-measurement, not as current readings — the stock only grows, so the 62 is a floor, not a figure.

    ⛔ Not dispatched, not nagged. Held for ①.


    Generated by Claude Code

  6. os-zhuang commented on Aug 11, 2026

    @os-zhuang
    ContributorAuthor

    Maintainer ruling recorded 2026-08-11 (PM session, executing the maintainer's direct instruction in chat — verbatim: 「接受你的全部建议,请更新 issue 的状态和标签」, accepting the four-lens decision-inbox review in full).

    Ruling: one-time manual ledger reconciliation, scheduled before the v17 board-clearing. Work the check-adr-0087-registration.mjs --list output over the 227 breaking-declared changesets, judge each flagged candidate, register the genuinely missing entries. ⛔ The gate itself stays diff-only — auditing inventory in CI would turn the whole repo red on adoption day and bill main's history to current authors.

    Adding target:v17: this is a pre-release action (ledger completeness for already-shipped breaking changes), so it belongs on the release board where the board-clearing pass will consume it.

    State: needs-user-decision → pm:queue + target:v17 (domain:spec-tooling seat).


    Generated by Claude Code

  7. os-help commented on Aug 11, 2026

    @os-help
    Collaborator

    Claimed by the domain:spec-tooling seat (#6018) — session session_01KJATVrh6V2ysutYUJigh3B, branch claude/issue-6350-adr-0087-stock-reconciliation. Round 14, dispatched first in the round because it carries target:v17. pm:queue → pm:dispatched as one write.

    Model opus, mode:cloud. Not mechanical and not close to it: the ruling's unit of work is "judge each flagged candidate", and this lane has already measured that the cheap mechanical proxy for that judgement has precision 0/3.

    Ruling taken as given and not re-opened: one-time manual ledger reconciliation, before the v17 board-clearing; the gate itself stays diff-only. ⛔ Do not add inventory auditing to CI — the ruling's stated reason is that it would turn the repo red on adoption day and bill main's history to current authors. That reason is now part of the card's scope, not a footnote.

    ⚠️ Every number on this card is stale — re-measure before you plan

    The audit that produced them ran 2026-08-09, two days ago, before the #7341 rule that premises carry re-check commands. Treat all of these as floors, not figures — the stock only grows:

    quantity recorded status
    breaking-declared changesets 227 (card body) / 257 (08-09 audit) ⚠️ both stale; the card body's 227 was already wrong on 08-09
    detector-flagged candidates 62 ⚠️ floor — re-run to get today's
    residue 107 ⚠️ floor
    hand-verified omissions 3 of 3 ✅ all three confirmed unregistered

    First deliverable is a fresh measurement, before any judging: re-run check-adr-0087-registration.mjs --list and report today's candidate count against the 62. Discipline 51 applies to every count you quote — grep -c, never grep | head (a pager in a counting pipeline errs low and gives no signal that it truncated), and reconcile against any in-repo pin of the same quantity before you publish it.

    ⚠️ If the candidate set has grown materially beyond 62, report before grinding through all of them. Judging 60+ candidates by hand may exceed one session; a partial pass that is declared partial is fine, a partial pass that reads as complete is the exact failure this card exists to correct.

    What the prior audit established — do not re-derive these

    Scope

    • In: registering the genuinely-missing entries in the ADR-0087 registries; the judgement record for every candidate you examined (including the ones you judged not missing — that record is the deliverable's evidence, not scaffolding).
    • ⛔ Out: changing check-adr-0087-registration.mjs's gate behaviour (it stays diff-only, per the ruling); packages/spec/src/**/*.zod.ts; ⛔ content/docs/releases/** — never touched in a code PR; the v17 release notes are compiled centrally from changesets at release time.
    • ⚠️ Do not register only the three known omissions and stop. The prior dev refused to do exactly that, with the right reason: writing the arbitrary sample that happened to be drawn would harden it into the appearance of a completed audit. If you cannot finish the set, say which candidates you judged and which you did not — ⛔ never round a partial pass to done.

    Accept bar

    • The fresh candidate count, stated, next to the stale 62.
    • For every entry you add: the evidence it was missing, not merely that the detector flagged it.
    • For every candidate you judge not missing: which ledger entry covers it, and a check that the entry is about that face rather than a passing mention in another entry's prose — that confusion is precisely what produced the 0/3 proxy precision.
    • ㉜ — a scanner must prove it can see before any zero is believed. Any "no ledger entry exists" claim needs a positive control showing the same search finding an entry that does exist.

    Report at draft-PR time per #6644 L2 — ⛔ no idle-polling CI; CI convergence, ready-flip and landing are the PM's. Issue comment carrying the os-dev-report marker first, return message second.


    Generated by Claude Code

  8. os-help commented on Aug 11, 2026

    @os-help
    Collaborator

    Draft PR #7624 — one-time ADR-0087 stock reconciliation. ⛔ Gate untouched: still diff-only, nothing in CI audits inventory.

    First deliverable — today's count vs the stale 62

    Re-measured on origin/main @ e124711:

    quantity card (2026-08-09) today (08-11)
    declared-breaking in stock 227 body / 257 audit 274
    residue 107 106
    detector-flagged candidates (!) 62 61
    ledger-touched (~) — 45

    The candidate set did not grow (62 → 61), so I judged the full 61 rather than sampling. Stock gained 17 breaking changesets while the residue shrank — those 17 all landed carrying dispositions, i.e. the forward gate working.

    ⚠️ Trap worth recording for the next run: the clone must be unshallowed first. Shallow, the ledger-touch signal is suppressed and all 106 residue rows report ? instead of !/~ — the script says so and prints the fix (git fetch --unshallow --filter=blob:none). Counting was grep -c / wc -l throughout, never grep | head. Arithmetic reconciles: 39 + 0 + 129 + 106 = 274. No in-repo pin of these quantities exists — the classifier's arms are pinned (self-test S1–S7), no count is; the only in-repo number is the prose "227" at check-adr-0087-registration.mjs:1622, explicitly a statement about filing time, not a ratchet.

    Judged: 61 of 61. Registered: 9.

    Full accounting: 9 registered + 34 not-missing + 11 owed-but-not-written + 7 borderline-not-owed = 61.

    Registered (all three previously-confirmed omissions, plus six new, deliberately spread across spec / runtime / client / authorable-metadata / code-contract faces so the set cannot read as the sample that happened to be drawn):

    runtime-httpserver-wrapper-retired (#5122) · record-details-sections-object-form (#5611) · data-driver-query-omit-object (#5181) · sort-node-direction-rejected (#4721) · tool-requires-confirmation-retired (#3715) · export-axis-opt-in (#3544) · apimethod-enum-shrink (#3543) · sharing-rule-recipient-reconcile (#1878) · client-delete-result-success (#5638)

    Per-entry evidence is in the PR body. Two findings worth surfacing here:

    Reconciliation is visible in the repo's own standing audit surface (--audit-stock, before → after): answered 39 → 48, residue 106 → 97, flagged 61 → 52. Each of the nine stock changesets now carries its <!-- adr-0087: registered … --> marker, so the judgement is recorded where the next auditor reads, not only in a PR body.

    Positive controls — nothing zero was believed on faith

    Coverage was judged with a harness separating ledger subjects (id: / surface: / export const entry) from prose mentions (text inside another entry's reason). That distinction is the 0/3 proxy failure, made mechanical.

    control returned
    R13 IStorageService (pinned as having an entry) ✅ subject hit — surface: 'contracts.IStorageService.list'
    R12 HttpServer (pinned as lacking one) ✅ 0 subjects, 3 prose-only files
    ctx.user.roles — drawn from the candidate set itself ✅ subject hit, face matches verbatim
    RecordDetailsProps — the target's own def ✅ sibling :layout found; sections / hideFields absent

    The last two are controls from the targets' own unique content: the same search, same def, same corpus, finding real coverage where it exists.

    ⚠️ Declared partial — 11 judged OWED, deliberately not written

    The #4001 unknown-key strictness wave and its authoring-gate neighbours (-tier-a, -step2, -automation-batch11, -ui-batch13/15/16, view-subblock-strictness-batch18, strict-automation-control-flow-state-machine, rare-jars-shave, user-filters-allow-add-tab…, view-union-identity-precondition).

    Authorable metadata, real rename tables, 0 subject coverage, and the class is demonstrably registrable — ui-schemas-strict-unknown-keys (15) and dashboard-widget-strict-unknown-keys (16) are exactly this shape. I judge them owed. I did not write them because the precedent registers a wave as one entry per major, not one per batch; picking the grouping myself would harden an invented shape into a set-consumed ledger where a wrong grouping produces no error anywhere. ⚑ This is a maintainer call and is the largest remaining piece of this card.

    Separately, 7 borderline judged not-owed with lower confidence, flagged rather than rounded: http-server-config-retired, retire-dev-service-marker, durable-suspended-screen-refetch, sys-comment-retire-visibility-reply-count, membership-grade-not-capability-channel, converge-activation-event-schema, views-translation-key-runtime-identity.

    One judgement that is the repo's, not mine

    Seven candidates are pure def/export renames, and registering them in ADR-0087 would be a defect — stated twice in-repo. scripts/lib/renamed-defs.ts lists retiredKey() + an ADR-0087 conversion as remedy #2 and rejects it: "semantically false… Registering it would pollute the ADR-0087 registries with a migration consumers must not run." migrations/registry.ts:4000: "A rename is not a retirement… an entry here would be a false claim that the contract shrank." Control: 4 of the 7 are literally in the RENAMED_DEFS table, and a 5th is documented by name in its header as ABSORBED.

    Gates (invocation scope)

    pnpm --filter @objectstack/spec run → check:migration-registry ✅ (self-test ok; 70 semantic / 18 retired-key / 45 retired-def) · check:spec-changes ✅ · check:upgrade-guide ✅ · typecheck ✅. pnpm --filter @objectstack/spec exec vitest run src/migrations/migrations.test.ts ✅ 92 passed (full chain replays). node scripts/check-adr-0087-registration.mjs --self-test ✅ 142 assertions · --base origin/main ✅. node scripts/check-empty-changeset.mjs --base origin/main ✅. Changeset included (@objectstack/spec: minor).

    ⛔ Untouched per scope: gate behaviour, packages/spec/src/**/*.zod.ts, content/docs/releases/**.

    Could not verify

    CI convergence, ready-flip and landing are the PM's.


    Generated by Claude Code

  9. os-help commented on Aug 11, 2026

    @os-help
    Collaborator

    ACCEPTED — landed as 5823d593d (PR #7624, squashed). Verified by content on a fresh ref, not on the merge webhook (㉞). Card auto-closed; pm:dispatched stripped; cloud session archived. Ledger → 20 merged. ⚑ This also clears a target:v17 release-board item.

    Landing verification

    # criterion reading @ origin/main
    1 all 9 entries exist as files 9/9 under packages/spec/src/migrations/entries/semantic/17.* ✅
    2 all 9 registered in registry.ts 9/9, each exactly once ✅
    3 ⛔ content/docs/releases/** untouched 0 files across the whole merge range ✅

    ⚠️ Criterion 2 first returned 0 — my probe, not the PR. I grepped for camelCase identifiers (sortNodeDirectionRejected); the registry holds entries inline as kebab-case id: 'sort-node-direction-rejected'. Re-run against the real idiom with a positive control — id: 'storage-service-list-retired', an entry that predates this PR, found by the same pattern — and all nine resolve. A scanner must prove it can see before any zero is believed, and that applies to the reviewer's scanner too. This is the second time this shift a guessed identifier nearly manufactured a defect in a correct PR.

    What made this report unusually strong

    • It measured before it planned, and the measurement changed the picture. Stock 227 → 274, but candidates 62 → 61 and residue 107 → 106. The stock grew by 17 while the residue shrank — every one of those 17 landed carrying a disposition. That is the forward gate working, and it is only visible because the re-measurement was made a precondition rather than a formality.
    • Because the set had not grown, all 61 were judged — not sampled. The accounting closes exactly: 9 registered + 34 not-missing + 11 owed-unwritten + 7 borderline = 61.
    • Every "not missing" call names its cover, and the wrong-channel group is not the dev's own judgement — it is the repo ruling on itself, twice (renamed-defs.ts rejects registering a rename as "semantically false… a migration consumers must not run"; registry.ts:4000: "A rename is not a retirement"). 4 of those 7 are literally in the RENAMED_DEFS table.
    • Positive controls drawn from the targets' own content, not generic ones — including the repo's natural R12/R13 pair (same shape, opposite ledger state, both pinned).
    • ⚠️ It found a measured false positive of the detector and proved it by construction: 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. That belongs in the next auditor's hands.
    • Two half-retirements surfaced: SortNodeSchema carries aliases: { direction: 'order' } in code with no ledger half, and ai/tool.zod.ts:59 has a live retiredKey() tombstone with no entry. Per registry.ts, the tombstone proves the removal was declared and the ledger entry is the documentation channel — a retirement needs both.
    • The judgements are recorded where the next auditor reads — each stock changeset now carries its own <!-- adr-0087: registered … --> marker, not only a PR body that scrolls away.

    Measurable on the repo's own standing audit surface: answered 39 → 48, residue 106 → 97, flagged candidates 61 → 52.

    What was deliberately NOT done, and where it now lives

    11 entries judged owed and left unwritten — the #4001 unknown-key strictness wave and its authoring-gate neighbours. The dev judged them owed, and declined to write them because the only two precedents (ui-schemas-strict-unknown-keys major 15, dashboard-widget-strict-unknown-keys major 16) register a wave as one entry per major, while these are batches inside one major. Choosing the grouping unilaterally would harden an invented shape into a set-consumed ledger where a wrong grouping produces no error anywhere — nothing goes red, it is simply wrong in the artifact whose only purpose is to be trustworthy.

    That is the same reasoning that stopped the earlier audit from registering just its three sampled omissions, and it was right both times.

    ⇒ Filed as #7630 (needs-user-decision), so it does not evaporate with this card. Landing it would take the flagged count from 52 toward 41 — the single largest remaining piece.

    Also recorded there, and not part of that decision: the 45 ~ rows were never examined (~ only means some commit touched a ledger file — it does not prove the entry covers that face), and 7 borderline candidates were judged not-owed with lower confidence.

    ⛔ The gate is untouched and still judges diffs only, per the ruling.


    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

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions