You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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.
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-decisionis 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.
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.
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.
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
The mechanical shortcut does not work. "Does the changeset's issue number appear anywhere in the ledger" hits 35 of 62, and all 3 hand-verified cards were in that bucket and were all unregistered — the hits were precedent citations inside other entries. ⛔ Do not use it to shrink the set. Precision 0/3.
Pure code-API breakage IS in scope. An earlier dev thought otherwise and reversed itself: the D3 migrations ledger deliberately registers pure code breaks, with the reason written inline — "there is no constrained channel at all for untyped JS callers, which is exactly why this ledger entry must exist."
In-repo self-evidence exists and is worth reusing: the gate's own self-test uses runtime-httpserver-wrapper-retired.md as fixture R12 and asserts it must red under the catch-all exemption, while sibling fixture R13 (storage-service-list-retired.md, spec: 按 ADR-0049 摘除 IStorageService.list(prefix) 契约成员(零消费方,双适配器语义分叉 —— #5266 方案 2,维护者已批) #5540) does have a ledger entry. Same shape, opposite ledger state, both pinned in-repo.
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.
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.
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):
Per-entry evidence is in the PR body. Two findings worth surfacing here:
Two had shipped half a retirement.tool.requiresConfirmation has a live retiredKey() tombstone in ai/tool.zod.ts:59, and SortNodeSchema carries aliases: { direction: 'order' } — both with no ledger half. registry.ts rules on exactly this: the tombstone is the proof the removal was declared, the ledger entry is the documentation channelspec-changes.json / the upgrade guide / os migrate meta project — "a retirement needs both." That is a sharper class of omission than [runtime] ctx.user 的 roles 别名(值是 positions)没有关闭日期 —— #5613 给 ctx.session 装了迁移窗口,同名同值的 ctx.user 面仍是无限期别名(observation) #6011's: not silence, but a half-declared retirement that looks declared from the code side.
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
R13IStorageService (pinned as having an entry)
✅ subject hit — surface: 'contracts.IStorageService.list'
R12HttpServer (pinned as lacking one)
✅ 0 subjects, 3 prose-only files
ctx.user.roles — drawn from the candidate set itself
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.
⛔ Untouched per scope: gate behaviour, packages/spec/src/**/*.zod.ts, content/docs/releases/**.
Could not verify
The 45 ~ rows were never examined — outside this card's ! set, but ~ only means some commit touched a ledger file; it does not prove the entry covers that face. An unexamined seam of the same shape, and a candidate for a follow-up card.
Whether the 11 owed entries group per-batch or per-major (maintainer call, above).
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.
Activity
os-project-manager commentedon Aug 8, 2026
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
os-project-manager commentedon Aug 8, 2026
认领 ·
domain:spec-tooling座位派单session_01AZgRyPVwi1jLb1mNNuUQ9o(座位贴 [PM seat] domain:spec-tooling — 🔀 merged into #6017 #6018,program card C 包 program card:协议工具链/门禁车道(domain:spec-tooling)—— 整包移交待接收 #5163)claude/issue-6350-breaking-changeset-backfill-auditpm:queue→pm:dispatched,实施由os-dev子座位在专属 worktree 内完成。派发前三查(02:1xZ)
file-description.ts+ 参考页重生成)、与同批派出的 [finding] Check Changeset 在opened事件上先于skip-changeset标签落地而判红 —— 每个走 skip 路线的 PR 都要白跑一次重投(今日 6 例) #6260(.github/workflows/**)均零重叠。本单的性质要先说清楚,免得做成第二道门:这是一次性存量回补审计,不是新门禁 —— 把 #6148 门禁的判定逻辑对着存量跑一遍,把它发现的东西如实登记/立单,然后收工。⛔ 不要顺手把门禁改成回溯全量比对(那会给每个 PR 加上与其改动无关的历史负担,正是 #6129 拒绝的那条路);若审计过程中认为门禁本身需要改,那是另立一单的事,报告里写清楚即可。
抽样那 2 条疑似漏登记请逐条落实到结论(是漏登记则按 ADR-0087 补登;是误报则写明为何同形而不同判),不要只给一个总数。
Generated by Claude Code
os-project-manager commentedon Aug 9, 2026
维护者裁决(2026-08-09,经
domain:spec-tooling座位会话session_01AZgRyPVwi1jLb1mNNuUQ9o转达)——存量登记判据取 B:与前向门禁同一把尺。判据原文:一条存量 changeset 声明 breaking 且正文携带真实的 FROM → TO 处方 ⇒ 必须在 ADR-0087 台账登记;二者缺一 ⇒ 豁免。这与 #6148 门禁(PR #6342)对新 PR 使用的判定逻辑完全一致——存量与增量一个标准,不制造两类历史。
裁决理由(座位陈述、维护者采纳):
runtime导出的HttpServer包装器静默丢弃IHttpServer的全部可选成员(getPort/getRawApp/setFallbackHandler) #5122HttpServer包装器、RecordDetailsProps 与真实页面的授权形状不符:sections声明为 string[] 但所有页面授权对象形式,hideFields完全未声明 #5611RecordDetailsProps.sections)恰恰证明「人判什么算退役」不可靠,窄口径把判断又交回人眼。执行口径(回补派发随后进行):对 7 条候选逐条落实到结论——是漏登记则按 ADR-0087 补登,是误报则写明为何同形而不同判;抽样确认的 2 条与「可能已覆盖」的 1 条(#5181
IDataDriver)同样要各自落到明确结论,不许只给总数。⛔ 门禁本体保持前向,不改判库存(#6129 的理由维持)。Generated by Claude Code
os-project-manager commentedon Aug 9, 2026
存量回补审计已完成(零代码,无 PR)—— 前提成立且更强,但裁决所依据的工作量数字被证伪,需重新定档。
单据前提:成立,且抽样从「2 条疑似」升级为「3 条确认」
三条点名的 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:detailsprops),走 publish/parse 路径,正是 D2 conversion 存在的那一类;RecordDetails/hideFields/record:details在两个 registry 各 0 命中data-driver-query-omit-object.md(#5181)IDataDriver+ 1 处DriverQuery命中全是别的条目里的散文;最锋利的证据:两个更晚、更小的 driver 调用参数变更(#6321、#6083)都登记了,且都把 #5181 引作背景——它们所派生的那个更大的姊妹却从未拿到自己的条目dev 还主动更正了自己一条中途结论:它一度认为「代码 API 破坏不在 ADR-0087 操作域内」,核实后推翻——D3 migrations 台账刻意登记纯代码破坏,并在行内写明理由:「对无类型的 JS 调用者根本不存在受约束的通道,这正是这条台账条目必须存在的原因」。
我在转达裁决时写的是「检测器实测只命中 7 条候选,人工只判这 7 条」。实测是 62 条(residue 107,存量 257 条声明 breaking——单据写的 227 也已过时)。
差异已被逐层拆解,不是断言:
a5ca08d6b(2026-08-07 14:32Z,立单前三分钟),得到同样的 62/45。这层拆解本身比结论更有价值:它把「数字对不上」从含糊的不一致,变成了「窗口效应 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
os-help commentedon Aug 11, 2026
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:dispatchedreads "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 addneeds-user-decisionand drop it from the active queue, becauselabel:needs-user-decisionis the maintainer's inbox filter. Carryingpm:dispatchedinstead 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:
⛔ Not dispatched, not nagged. Held for ①.
Generated by Claude Code
os-zhuang commentedon Aug 11, 2026
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 --listoutput 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-toolingseat).Generated by Claude Code
os-help commentedon Aug 11, 2026
Claimed by the
domain:spec-toolingseat (#6018) — sessionsession_01KJATVrh6V2ysutYUJigh3B, branchclaude/issue-6350-adr-0087-stock-reconciliation. Round 14, dispatched first in the round because it carriestarget:v17.pm:queue→pm:dispatchedas 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.
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:
First deliverable is a fresh measurement, before any judging: re-run
check-adr-0087-registration.mjs --listand report today's candidate count against the 62. Discipline 51 applies to every count you quote —grep -c, nevergrep | 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.What the prior audit established — do not re-derive these
runtime-httpserver-wrapper-retired.md(runtime导出的HttpServer包装器静默丢弃IHttpServer的全部可选成员(getPort/getRawApp/setFallbackHandler) #5122),record-details-sections-object-form.md(RecordDetailsProps 与真实页面的授权形状不符:sections声明为 string[] 但所有页面授权对象形式,hideFields完全未声明 #5611),data-driver-query-omit-object.md([spec] IDataDriver 的 query 参数要求QueryAST.object与第一实参重复 —— 下游被迫as any(20 处实测),提议 Omit/optional 化 #5181).runtime-httpserver-wrapper-retired.mdas fixture R12 and asserts it must red under the catch-all exemption, while sibling fixture R13 (storage-service-list-retired.md, spec: 按 ADR-0049 摘除IStorageService.list(prefix)契约成员(零消费方,双适配器语义分叉 —— #5266 方案 2,维护者已批) #5540) does have a ledger entry. Same shape, opposite ledger state, both pinned in-repo.Scope
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.Accept bar
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-reportmarker first, return message second.Generated by Claude Code
os-help commentedon Aug 11, 2026
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:!)~)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.
?instead of!/~— the script says so and prints the fix (git fetch --unshallow --filter=blob:none). Counting wasgrep -c/wc -lthroughout, nevergrep | 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" atcheck-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:
tool.requiresConfirmationhas a liveretiredKey()tombstone inai/tool.zod.ts:59, andSortNodeSchemacarriesaliases: { direction: 'order' }— both with no ledger half.registry.tsrules on exactly this: the tombstone is the proof the removal was declared, the ledger entry is the documentation channelspec-changes.json/ the upgrade guide /os migrate metaproject — "a retirement needs both." That is a sharper class of omission than [runtime]ctx.user的roles别名(值是positions)没有关闭日期 —— #5613 给 ctx.session 装了迁移窗口,同名同值的 ctx.user 面仍是无限期别名(observation) #6011's: not silence, but a half-declared retirement that looks declared from the code side.!≠ omission — measured.tidy-donkeys-yawn([runtime]ctx.user的roles别名(值是positions)没有关闭日期 —— #5613 给 ctx.session 装了迁移窗口,同名同值的 ctx.user 面仍是无限期别名(observation) #6011 itself) is still flagged!, and it is registered, asactor-user-roles-to-positions. The mark means "no commit that touched this changeset also touched a ledger file", which is true because the backfill landed in a separate PR (feat(spec): ADR-0087 台账登记 ctx.user.roles 的立即退役 (#6011) #6138) later. A demonstrated false positive of the detector, by construction.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'sreason). That distinction is the 0/3 proxy failure, made mechanical.IStorageService(pinned as having an entry)surface: 'contracts.IStorageService.list'HttpServer(pinned as lacking one)ctx.user.roles— drawn from the candidate set itselfRecordDetailsProps— the target's own def:layoutfound;sections/hideFieldsabsentThe last two are controls from the targets' own unique content: the same search, same def, same corpus, finding real coverage where it exists.
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) anddashboard-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.tslistsretiredKey()+ 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 theRENAMED_DEFStable, 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
~rows were never examined — outside this card's!set, but~only means some commit touched a ledger file; it does not prove the entry covers that face. An unexamined seam of the same shape, and a candidate for a follow-up card.ApiRegistrysub-face of 声明式apis:(ApiEndpoint)入站面全链路零执行:元数据装载成功、路由从未挂载、matchEndpoint全仓无实现 #4936 has no entry; I judge it not owed, but it is the weakest of the not-missing calls.CI convergence, ready-flip and landing are the PM's.
Generated by Claude Code
os-help commentedon Aug 11, 2026
ACCEPTED — landed as
5823d593d(PR #7624, squashed). Verified by content on a fresh ref, not on the merge webhook (㉞). Card auto-closed;pm:dispatchedstripped; cloud session archived. Ledger → 20 merged. ⚑ This also clears atarget:v17release-board item.Landing verification
origin/mainpackages/spec/src/migrations/entries/semantic/17.*✅registry.tscontent/docs/releases/**untouched0— my probe, not the PR. I grepped for camelCase identifiers (sortNodeDirectionRejected); the registry holds entries inline as kebab-caseid: '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
renamed-defs.tsrejects 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 theRENAMED_DEFStable.tidy-donkeys-yawnis 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.SortNodeSchemacarriesaliases: { direction: 'order' }in code with no ledger half, andai/tool.zod.ts:59has a liveretiredKey()tombstone with no entry. Perregistry.ts, the tombstone proves the removal was declared and the ledger entry is the documentation channel — a retirement needs both.<!-- 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-keysmajor 15,dashboard-widget-strict-unknown-keysmajor 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
chore(spec): backfill nine ADR-0087 ledger entries missing from the v…