Skip to content

[Decision] The #4001 unknown-key strictness wave is owed ADR-0087 entries — one per major, or one per batch? #7630

Closed

Description

@os-help

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.

⚠️ This is a grouping call, not a "should we register it" call. The dev judged these owed and said so plainly; it declined to invent the shape.

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.

⚠️ Landing this card's work would take the flagged count from 52 toward 41. It is the single largest remaining piece.

Two adjacent seams recorded, NOT part of this decision

Neither is being asked here. They are written down so the next auditor does not have to rediscover that they are open.

Release board

⚠️ Parent #6350 carried 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

  1. huangyiirene commented on Aug 12, 2026

    @huangyiirene
    Collaborator

    Maintainer ruling — 2026-08-12

    裁定:方向 A —— 一个 major 一条 ADR-0087 entry。

    要点:

    • 与登记簿仅有的两个先例(ui-schemas-strict-unknown-keys major 15、dashboard-widget-strict-unknown-keys major 16)同形:wave 按 major 登记。
    • 十一个 batch 折进一条 v17 entry;per-batch 溯源(changeset 名单)写在 entry 内部,升级者读一条处方覆盖整个 wave,考古者仍能逐 batch 回溯。
    • 两个相邻 seam(45 个 ~ 行未验、7 个 borderline)维持记录状态,⛔ 不属于本卡范围,不趁机扩。
    • target:* 标签仍由分诊座位定夺(本裁定不代加)。

    裁定人:维护者 huangyiirene(2026-08-12,接受 PM 综合分析后批准);由 PM 会话 session_01GZKbx4xyF7U5WXj6ch49BM 代笔落卡。转 pm:queue。注意:若 entry 落在 docs/adr/**,该 PR 按 #6741 走人工合并通道。


    Generated by Claude Code

  2. self-assigned this
    on Aug 12, 2026
  3. os-zhuang commented on Aug 12, 2026

    @os-zhuang
    Contributor

    Claim: PM loop round 3 (takeover shift, domain:spec-tooling seat #6018)
    Session: session_01WocN37om5bw81JDoEEMA2e
    Branch: claude/issue-7630-adr-0087-v17-strictness-entry
    Worktree: objectstack-issue-7630 (cloud session — fresh clone, own container)
    Domain: domain:spec-tooling
    File 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. ⛔ No packages/spec/src/**/*.zod.ts, no strictness-ledger counts, no seam expansion (the 45 ~ rows and 7 borderline candidates stay untouched), no target:* 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: opus
    Serial constraints cleared: no in-flight card in this lane. Cross-seat note for the domain:spec seat'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.ts nor acceptance-surface — the legal metadata set does not change. If the spec seat disagrees, object here before the PR lands; the dev will merge origin/main and re-run check:generated before 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 at packages/spec/src/migrations/entries/semantic/15.ui-schemas-strict-unknown-keys.ts / 16.dashboard-widget-strict-unknown-keys.ts and are wired through registry.ts, spec-changes.json and docs/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

  4. os-zhuang commented on Aug 12, 2026

    @os-zhuang
    Contributor

    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, via gen:migration-registry; nothing typed between the markers
    • packages/spec/spec-changes.json, docs/protocol-upgrade-guide.md — regenerated projections, committed as generator output
    • the eleven stock changesets, +2 lines each: <!-- adr-0087: registered authoring-schemas-strict-unknown-keys -->
    • .changeset/adr-0087-v17-strictness-wave-entry.md — @objectstack/spec: minor

    Provenance is deliberately in two places, because the two readers read different artifacts: the leading // comment block (carried by the generator into registry.ts above the entry — the archaeologist's batch-by-batch walk) and the reason field (the eleven changeset names in one sentence, so the trace also reaches spec-changes.json and the upgrade guide, which the leading comment does not).

    Premise re-checks (all held; one correction)

    • All eleven changesets present and unrenamed in .changeset/ at origin/main. No premise fork.
    • Both precedents exist at the named paths and are wired through registry.ts, spec-changes.json and docs/protocol-upgrade-guide.md. Confirmed.
    • spec-changes.json / protocol-upgrade-guide.md are generator output, not hand-maintained: gen:spec-changes / gen:upgrade-guide, gated by check:spec-changes / check:upgrade-guide, and both carry merge=os-regen in .gitattributes. Committed as generator output only.
    • Correction to the dispatch's file surface. The card listed the surface as entry + registry + generated + one changeset, and ⛔ did not mention the eleven stock changesets. But the acceptance evidence the card asked for (flagged 52 → ~41) is driven entirely by the disposition marker on each changeset — classifyStockChangeset moves a row out of residue only when readDisposition finds 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 +2 on 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.
    • What check:adr-0087-registration asserts 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

    ⚠️ The container clones shallow, and --audit-stock refuses the ledger-touch signal on a shallow clone rather than approximating it (every row reads ?). git fetch --unshallow --filter=blob:none first; then the baseline reproduces this card's own numbers exactly.

    before after
    already answered 51 62
    residue 97 86
    flagged (!) 52 41
    ledger-touched (~) 45 45

    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') carries authoring-schemas-strict-unknown-keys (145 ids) and so does projectedMigrationIds(spec-changes.json) (71 ids) — the markers resolve against a real entry.

    Gates

    gate result
    pnpm run check:adr-0087-registration ✅ self-test 153 assertions + gate green
    pnpm --filter @objectstack/spec run typecheck ✅
    pnpm --filter @objectstack/spec run test ✅ 380 files / 10017 tests passed, 481s, exit 0 — no failures, none skipped, nothing flaky
    pnpm --filter @objectstack/spec run check:generated ✅ all 13 up to date (the package was built first — check:api-surface reads the built dist, and reported "not built" until then)
    eslint (touched files) ✅ new entry file clean. ⚠️ pre-existing below.

    origin/main merged (569611f94), then all three generators re-run — zero further diff, so nothing in the spec regeneration lane is racing this tree. git check-attr merge verified on every touched path, with .gitattributes as the positive control.

    Scope held

    ⛔ no *.zod.ts · ⛔ no strictness-ledger .counts.md · ⛔ the 45 ~ rows and 7 borderline candidates untouched (and measured unchanged) · ⛔ no target:* 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)

    1. registry.ts cannot be linted. npx eslint packages/spec/src/migrations/registry.ts → Parsing error: Maximum call stack size exceeded. Pre-existing: the identical error reproduces on the origin/main copy 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 lint is eslint . --no-inline-config with no stack-size flag, so this is presumably already the case on main. Worth a card; not this one's to fix.
    2. The step17 doc comment is stale. The block comment above const step17 still reads "there is no semantic residue and the semantic list 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 Split migrations/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

  5. os-zhuang commented on Aug 12, 2026

    @os-zhuang
    Contributor

    Review verdict: ACCEPT — PR #7927, direct-acceptance path.

    The dev session ended its turn review_ready at 04:40Z without posting the os-dev-report issue comment; per the report-loss fallback the PR body is the report, and it is comprehensive. Verified against GitHub, not the report's claims:

    • Path face read directly (16 files): the new entry 17.authoring-schemas-strict-unknown-keys.ts, registry.ts + spec-changes.json + docs/protocol-upgrade-guide.md as 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-style adr-0087: registered disposition 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: minor changeset with precedent citations. ⛔ No docs/adr/** — the ⛔ Discipline: ADRs are confirmed and merged by the maintainer only — no AI seat may merge, queue, or auto-merge a docs/adr/** PR #6741 human-merge conditional does NOT trigger; ⛔ no *.zod.ts, no .counts.md, no content/docs/releases/**; the 45 ~ rows measured unchanged (held at 45) — the seam-untouched claim is a measurement, not a promise.
    • Ruling implemented as ruled: ONE entry for the major, named by the precedents' convention; the eleven batches re-enumerated against the live tree and traced both in the entry's ledger comment and in the reason field (with the measured rationale for the duplication — comments do not reach the projections).
    • Acceptance evidence: --audit-stock flagged 52 → 41 (exactly the eleven), answered 51 → 62, both directions of the id wired (ledgerAt 145 ids / projections 71 ids), baseline reproducing the card's own numbers.
    • CI: 27 check runs all completed — ESLint, TypeScript Type Check, Test Core (3/3), Build Core, Dogfood, Temporal Conformance all success (2 skipped by filter). The PR-body's two open local items (fresh-worktree check:api-surface, pre-existing registry.ts eslint parse error on origin/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

  6. os-zhuang commented on Aug 12, 2026

    @os-zhuang
    Contributor

    Landed: PR #7927 merged via the queue as 3140f9c. Post-merge verification by content on origin/main: the entry file (99 lines), the registry id, both spec-changes.json projections 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:dispatched stripped on close. The two recorded seams (45 ~ rows, 7 borderline) remain open observations exactly as the ruling left them.


    Generated by Claude Code

  7. added a commit that references this issue on Aug 17, 2026
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