Repository navigation
[Decision] The #4001 unknown-key strictness wave is owed ADR-0087 entries — one per major, or one per batch? #7630
Description
Split out of #6350 at landing (PR #7624), because #6350 auto-closes on that merge and this is the one piece of its work that was deliberately not done. Filed so it does not evaporate with the card that produced it.
What is owed
Eleven declared-breaking changesets in the v17 stock, all part of the #4001 unknown-key strictness wave and its authoring-gate neighbours:
unknown-key-strictness-tier-a · -step2 · -automation-batch11 · -ui-batch13 · -ui-batch15 · -ui-batch16 · view-subblock-strictness-batch18 · strict-automation-control-flow-state-machine · rare-jars-shave · user-filters-allow-add-tab-promote-and-close · view-union-identity-precondition
All eleven are authorable metadata with real rename tables and 0 ledger subject coverage. The class is demonstrably registrable — the repo already carries two of exactly this shape:
ui-schemas-strict-unknown-keys(major 15)dashboard-widget-strict-unknown-keys(major 16)
The decision
Both existing precedents register a wave as one entry per major, not one entry per batch. The eleven above are batches within one major.
- A — one entry for the major (mirrors both precedents). Fewer, larger entries; an upgrader reads one prescription covering the wave.
- B — one entry per batch (eleven entries). Finer provenance, each traceable to its changeset; diverges from the only two precedents in the registry.
- C — some middle grouping (e.g. by surface: automation / ui / views).
Why the dev stopped rather than picking
Stated in its report and worth preserving verbatim in spirit: the registries are consumed as a set, and a wrong grouping produces no error anywhere. Nothing goes red. It would simply be wrong, silently, in the artifact whose entire purpose is to be the trustworthy record — which is the failure mode #6350 exists to correct. Choosing the shape unilaterally would harden an invented grouping into a set-consumed ledger.
That is the same reasoning that kept the earlier audit from registering only its three sampled omissions, and it was right both times.
Context from the landed work (PR #7624)
The reconciliation that did land judged all 61 flagged candidates: 9 registered, 34 judged not-missing with a named cover for each, 11 owed and declared unwritten (this card), 7 borderline-not-owed. Standing audit surface moved: answered 39 → 48, residue 106 → 97, flagged 61 → 52.
Two adjacent seams recorded, NOT part of this decision
- The 45
~rows were never examined.~only means some commit touched a ledger file — it does not prove the entry covers that face. Same shape as the!seam this card came from, and unmeasured. - 7 borderline candidates were judged not-owed with lower confidence and named in PR chore(spec): backfill nine ADR-0087 ledger entries missing from the v17 stock (#6350) #7624's body. Any could reasonably be ruled owed.
Neither is being asked here. They are written down so the next auditor does not have to rediscover that they are open.
Release board
target:v17 — this is v17 ledger completeness and plausibly belongs on the release board too. ⛔ Not applying target:* here: that label has exactly one producer per backlog and it is the triage seat, not this one. Flagging it for that seat to attach or decline.
Refs: #6350 (parent, closes with PR #7624), #4001 (the strictness wave), #6129 (the ruling that the gate stays diff-only), ADR-0087.
Activity
huangyiirene commentedon Aug 12, 2026
Maintainer ruling — 2026-08-12
裁定:方向 A —— 一个 major 一条 ADR-0087 entry。
要点:
ui-schemas-strict-unknown-keysmajor 15、dashboard-widget-strict-unknown-keysmajor 16)同形:wave 按 major 登记。~行未验、7 个 borderline)维持记录状态,⛔ 不属于本卡范围,不趁机扩。target:*标签仍由分诊座位定夺(本裁定不代加)。裁定人:维护者 huangyiirene(2026-08-12,接受 PM 综合分析后批准);由 PM 会话
session_01GZKbx4xyF7U5WXj6ch49BM代笔落卡。转pm:queue。注意:若 entry 落在docs/adr/**,该 PR 按 #6741 走人工合并通道。Generated by Claude Code
os-zhuang commentedon Aug 12, 2026
Claim: PM loop round 3 (takeover shift,
domain:spec-toolingseat #6018)Session:
session_01WocN37om5bw81JDoEEMA2eBranch:
claude/issue-7630-adr-0087-v17-strictness-entryWorktree:
objectstack-issue-7630(cloud session — fresh clone, own container)Domain:
domain:spec-toolingFile surface:
packages/spec/src/migrations/entries/semantic/17.<name>.ts(new),packages/spec/src/migrations/registry.ts(one registration line), whatever generated artifacts follow from those two by the generator (e.g.packages/spec/spec-changes.json,docs/protocol-upgrade-guide.md— committed as generator output only), one changeset. ⛔ Nopackages/spec/src/**/*.zod.ts, no strictness-ledger counts, no seam expansion (the 45~rows and 7 borderline candidates stay untouched), notarget:*label actions. ⛔docs/adr/**is not expected to change; if it genuinely must, that flips landing to the #6741 human-merge channel — say so loudly.Container & model: M (the entry is an upgrade prescription consumed as a set — "a wrong grouping produces no error anywhere"; judgment face, not gate face),
mode:cloud,model: opusSerial constraints cleared: no in-flight card in this lane. Cross-seat note for the
domain:specseat's veto window: this card continues the ADR-0087 registration program (#6350 / PR #7624, this lane's), the routing label was applied at triage and re-affirmed at the decision re-queue (2026-08-12T03:08Z), and the touched migrations files are neither*.zod.tsnor acceptance-surface — the legal metadata set does not change. If the spec seat disagrees, object here before the PR lands; the dev will mergeorigin/mainand re-runcheck:generatedbefore push in case a spec-lane regeneration is in flight on the same generated tree.Pre-dispatch premise re-checks at
origin/main(2026-08-12T04:0xZ): both precedent entries exist atpackages/spec/src/migrations/entries/semantic/15.ui-schemas-strict-unknown-keys.ts/16.dashboard-widget-strict-unknown-keys.tsand are wired throughregistry.ts,spec-changes.jsonanddocs/protocol-upgrade-guide.md; 4 of the 11 named changesets spot-verified present in.changeset/(dev re-enumerates all 11 at work time).Generated by Claude Code
os-zhuang commentedon Aug 12, 2026
os-dev report — #7630
Status:
done(draft PR open) · Branch:claude/issue-7630-adr-0087-v17-strictness-entry· PR: #7927 (draft,Fixes #7630)What landed
Direction A, exactly as ruled: one v17 ADR-0087 semantic migration entry,
authoring-schemas-strict-unknown-keys, folding all eleven batches, with per-batch provenance written inside the entry. Not eleven entries, not a per-surface grouping.packages/spec/src/migrations/entries/semantic/17.authoring-schemas-strict-unknown-keys.ts(new)packages/spec/src/migrations/registry.ts— generated region only, viagen:migration-registry; nothing typed between the markerspackages/spec/spec-changes.json,docs/protocol-upgrade-guide.md— regenerated projections, committed as generator output<!-- adr-0087: registered authoring-schemas-strict-unknown-keys -->.changeset/adr-0087-v17-strictness-wave-entry.md—@objectstack/spec: minorProvenance is deliberately in two places, because the two readers read different artifacts: the leading
//comment block (carried by the generator intoregistry.tsabove the entry — the archaeologist's batch-by-batch walk) and thereasonfield (the eleven changeset names in one sentence, so the trace also reachesspec-changes.jsonand the upgrade guide, which the leading comment does not).Premise re-checks (all held; one correction)
.changeset/atorigin/main. No premise fork.registry.ts,spec-changes.jsonanddocs/protocol-upgrade-guide.md. Confirmed.spec-changes.json/protocol-upgrade-guide.mdare generator output, not hand-maintained:gen:spec-changes/gen:upgrade-guide, gated bycheck:spec-changes/check:upgrade-guide, and both carrymerge=os-regenin.gitattributes. Committed as generator output only.classifyStockChangesetmoves a row out ofresidueonly whenreadDispositionfinds a marker; the ledger entry alone moves nothing. PR chore(spec): backfill nine ADR-0087 ledger entries missing from the v17 stock (#6350) #7624 did exactly this (its diff is 9 entries plus+2on each of 9 changesets). So the eleven marker edits are in scope and were made. They are invisible to the diff gate (M, breaking at head and at base → skipped as inherited stock), which the gate run confirms.check:adr-0087-registrationasserts on this tree: it judges only changesets this PR newly adds or newly turns breaking. Verdict here: "this PR adds no declared-breaking changeset (12 non-breaking changeset(s) seen)" — green, and this entry does not change its verdict, because the eleven were already breaking at base. The gate stays diff-only per Check Changeset 会因为「别人的 PR 合进了 main」而变绿 —— merge ref 里的他人 changeset 被算成本 PR 新增的 #6129.Acceptance evidence —
--audit-stock, before → after--audit-stockrefuses the ledger-touch signal on a shallow clone rather than approximating it (every row reads?).git fetch --unshallow --filter=blob:nonefirst; then the baseline reproduces this card's own numbers exactly.!)~)52 → 41 is exactly the eleven.
~holding at 45 is the positive check that the adjacent seam was not touched.Id wired in both directions:
ledgerAt('HEAD')carriesauthoring-schemas-strict-unknown-keys(145 ids) and so doesprojectedMigrationIds(spec-changes.json)(71 ids) — the markers resolve against a real entry.Gates
pnpm run check:adr-0087-registrationpnpm --filter @objectstack/spec run typecheckpnpm --filter @objectstack/spec run testpnpm --filter @objectstack/spec run check:generatedcheck:api-surfacereads the builtdist, and reported "not built" until then)origin/mainmerged (569611f94), then all three generators re-run — zero further diff, so nothing in the spec regeneration lane is racing this tree.git check-attr mergeverified on every touched path, with.gitattributesas the positive control.Scope held
⛔ no
*.zod.ts· ⛔ no strictness-ledger.counts.md· ⛔ the 45~rows and 7 borderline candidates untouched (and measured unchanged) · ⛔ notarget:*label action · ⛔content/docs/releases/**untouched · ✅docs/adr/**NOT touched — the measured shape did not require it, so landing does not flip to the #6741 human-merge channel.Out-of-scope findings (recorded, not acted on)
registry.tscannot be linted.npx eslint packages/spec/src/migrations/registry.ts→Parsing error: Maximum call stack size exceeded. Pre-existing: the identical error reproduces on theorigin/maincopy of the file with none of my changes in it. The file is ~4,600 lines of deeply-nested string concatenation and the TS-ESLint parser blows its stack on it.pnpm lintiseslint . --no-inline-configwith no stack-size flag, so this is presumably already the case onmain. Worth a card; not this one's to fix.const step17still reads "there is no semantic residue and thesemanticlist is deliberately empty" — the list now holds ~60 entries. It is hand-written prose outside the generated markers, so no gate catches it, and PR chore(spec): backfill nine ADR-0087 ledger entries missing from the v17 stock (#6350) #7624 left it alone too. Not touched here for the same reason: it is the merge-conflict-magnet text the Splitmigrations/registry.ts's two append tables into per-entry files (registry half of #6957's ruling) #7297 split exists to route around, and correcting it is a decision about that prose, not about this entry.Neither was acted on.
Generated by Claude Code
os-zhuang commentedon Aug 12, 2026
Review verdict: ACCEPT — PR #7927, direct-acceptance path.
The dev session ended its turn
review_readyat 04:40Z without posting theos-dev-reportissue comment; per the report-loss fallback the PR body is the report, and it is comprehensive. Verified against GitHub, not the report's claims:17.authoring-schemas-strict-unknown-keys.ts,registry.ts+spec-changes.json+docs/protocol-upgrade-guide.mdas generator output (gen:migration-registry; os-regen attrs confirmed with a positive control), 11 stock changesets gaining the chore(spec): backfill nine ADR-0087 ledger entries missing from the v17 stock (#6350) #7624-styleadr-0087: registereddisposition marker (+2 lines each — outside the claim's declared surface, accepted as a disclosed addition following the program's own precedent), one real@objectstack/spec: minorchangeset with precedent citations. ⛔ Nodocs/adr/**— the ⛔ Discipline: ADRs are confirmed and merged by the maintainer only — no AI seat may merge, queue, or auto-merge adocs/adr/**PR #6741 human-merge conditional does NOT trigger; ⛔ no*.zod.ts, no.counts.md, nocontent/docs/releases/**; the 45~rows measured unchanged (held at 45) — the seam-untouched claim is a measurement, not a promise.reasonfield (with the measured rationale for the duplication — comments do not reach the projections).--audit-stockflagged 52 → 41 (exactly the eleven), answered 51 → 62, both directions of the id wired (ledgerAt145 ids / projections 71 ids), baseline reproducing the card's own numbers.success(2 skipped by filter). The PR-body's two open local items (fresh-worktreecheck:api-surface, pre-existingregistry.tseslint parse error onorigin/main) are both answered by the green CI conclusions.Landing: flipping ready + auto-merge now; queue-membership verification per the amended notes-21 sequence.
Generated by Claude Code
os-zhuang commentedon Aug 12, 2026
Landed: PR #7927 merged via the queue as
3140f9c. Post-merge verification by content onorigin/main: the entry file (99 lines), the registry id, bothspec-changes.jsonprojections and the changeset disposition markers all present — the queue merge did not swallow the generated tree. The v17 ledger entry the maintainer ruled for is in; audit surface moved flagged 52 → 41.pm:dispatchedstripped on close. The two recorded seams (45~rows, 7 borderline) remain open observations exactly as the ruling left them.Generated by Claude Code
chore(spec): register the objectstack-ai#4001 unknown-key strictness …