chore(spec): backfill nine ADR-0087 ledger entries missing from the v17 stock (#6350) - #7624
Conversation
…17 stock (#6350) The #6148 completeness gate is diff-only by design (#6129), so every breaking changeset already in the v17 train was never compared against the ADR-0087 ledger. This is the one-time manual reconciliation the maintainer ruled for. The gate is untouched and still judges diffs only. Measured today: 274 declared-breaking changesets in stock, 106 in the residue, 61 flagged as never having touched a ledger file. Judging all 61 by hand found nine genuinely missing entries, registered here as D3 semantic migrations, and each of the nine stock changesets now carries its adr-0087 disposition marker. Two had shipped half a retirement: tool.requiresConfirmation carries a live retiredKey() tombstone and SortNodeSchema carries aliases:{direction:'order'}, but neither had the ledger half. A retirement needs both. spec-changes.json and docs/protocol-upgrade-guide.md are regenerated. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0198a2mSFFM18npoJfNtNv8z
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckThis PR changes 1 package(s): 106 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
⛔ 7 release-owned page(s) also reference the affected code. These are read-only:
|
Fixes #6350
One-time manual ADR-0087 ledger reconciliation over the v17 stock, per the maintainer ruling (comment 5250635216). ⛔ The gate is untouched and still judges diffs only — nothing in CI starts auditing inventory, for the ruling's stated reason (#6129).
Fresh measurement — today vs the stale card
Every number on the card was from 2026-08-09 and flagged as a floor. Re-measured on
origin/main@e124711:!)~)The set did not grow, so the full 61 were judged rather than sampled. The stock grew by 17 breaking changesets while the residue shrank — every one of those 17 landed carrying a disposition, which is the forward gate working.
?instead of!/~(the script says so itself and prints the fix). Counting wasgrep -c/wc -lthroughout, nevergrep | head. Arithmetic reconciles: 39 answered + 0 unpublished-exempt + 129 no-prescription-exempt + 106 residue = 274. No in-repo pin of these quantities exists — the classifier's arms are pinned (self-test S1–S7) but no count is; the only in-repo number ischeck-adr-0087-registration.mjs:1622's prose "227", which is explicitly a statement about filing time, not a ratchet.Positive controls — the scanner proved it can see before any zero was believed
Coverage was judged with a harness that separates ledger subjects (
id:/surface:/export const entry) from prose mentions (text inside another entry'sreason/acceptanceCriteria). That distinction is the whole point: it is exactly the confusion that gave the issue-number proxy precision 0/3.IStorageService(pinned as having an entry)semantic/17.storage-service-list-retired.ts:7 surface: 'contracts.IStorageService.list'HttpServer(pinned as lacking one)ctx.user.roles— drawn from the candidate set itselfsemantic/17.actor-user-roles-to-positions.tssurface matches the changeset's face verbatimRecordDetailsProps— the target's own defui/RecordDetailsProps:layoutfound;sections/hideFieldsabsentThe last two are controls drawn from the targets' own unique content, not generic ones: the same search, on the same def and the same corpus, finds real coverage where it exists.
Registered — 9 entries, with the evidence each was missing
runtime-httpserver-wrapper-retired(#5122)HttpServerdelegating wrapperHttpServer*hits are prose in other entries, one of which (http-server-runtime-vocabulary-retired) registers #5295'ssystem/http-server.zod.tsvocabulary — a different face. Sibling fixture R13 has the same shape and an entry.record-details-sections-object-form(#5611)RecordDetailsProps.sections+hideFieldsui/RecordDetailsProps:layout), proving the search reaches it;sections/hideFieldshave 0 subject hits. Authorable metadata on the publish/parse path.data-driver-query-omit-object(#5181)IDataDriverquery-parameter contractdata-driver-find-stream-retired— a different member. Two later, smaller driver-parameter changes (#6321, #6083) both registered and both cite #5181 as background; the larger sibling they derive from never got an entry.sort-node-direction-rejected(#4721)orderBy[].direction→orderdirectionis a common word, the textbook proxy trap. Half a retirement had shipped:SortNodeSchemacarriesaliases: { direction: 'order' }in code with no ledger half.tool-requires-confirmation-retired(#3715)tool.requiresConfirmationretiredKey()tombstone stands inai/tool.zod.ts:59with the full prescription, and 0 ledger subjects. Perregistry.ts: the tombstone is the proof the removal was declared, the ledger entry is the documentation channel — "a retirement needs both".export-axis-opt-in(#3544)allowExportunset flips to denyrest-requireauth-default-flip(12) andaction-descriptor-resume-authority-default-flip(17).apimethod-enum-shrink(#3543)enable.apiMethods8 legacy valueshistory→get,search→list), which is why it is semantic not mechanical.sharing-rule-recipient-reconcile(#1878)group/guest/ owner-typesharing-recipient-role-to-position= the role→position rename;sharing-rule-access-level-full-to-edit= access levels). Security surface: silent under-sharing.client-delete-result-success(#5638)DeleteDataResult.deleted→successif (r.deleted)has readundefinedsince it was written.Each of the nine stock changesets now carries its
<!-- adr-0087: registered … -->marker, so the judgement is recorded where the next auditor reads rather than only in a PR body.Reconciliation is measurable in the repo's own standing audit surface (
--audit-stock, before → after): answered 39 → 48, residue 106 → 97, flagged candidates 61 → 52.Judged NOT missing — 34 candidates, with what covers each
Full accounting of the 61: 9 registered + 34 not-missing + 11 owed-but-not-written + 7 borderline-not-owed = 61. All 61 were judged; 11 of the judgements were not acted on, and that is declared below rather than rounded to done.
Wrong channel — a pure def/export rename (7). Not my judgement call; the repo rules on it twice.
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." Positive control: 4 of the 7 are literally in theRENAMED_DEFStable —action-location-dual-source-c17(:109),package-dependency-dual-source-c7(:120),http-method-defkey-collision(:139),field-mapping-tri-source-c12(:83–84). Of the rest,rate-limit-config-dual-source-c9is documented by name in that table's header as ABSORBED (renamed then retired in the same unreleased major; carried by theconnector-rate-limit-config-removedconversion, which is present);http-method-dual-source-c14re-exports an existing type with zero shape change;dual-source-cross-form-convergence'sTransformTypeSchemahalf is covered by the retired-defshared/FieldMappingTransform.Covered by a named existing entry (2).
tidy-donkeys-yawn([runtime]ctx.user的roles别名(值是positions)没有关闭日期 —— #5613 给 ctx.session 装了迁移窗口,同名同值的 ctx.user 面仍是无限期别名(observation) #6011) →actor-user-roles-to-positions, surfaceaction body / AI route: ctx.user.roles (req.user.roles)— an exact face match.!means "no commit that touched this changeset also touched a ledger file", and that is true here because the backfill landed in a separate PR (feat(spec): ADR-0087 台账登记 ctx.user.roles 的立即退役 (#6011) #6138) long after.!≠ omission, and this is the proof by construction.apis-loud-reject-and-apiregistry-retirement(声明式apis:(ApiEndpoint)入站面全链路零执行:元数据装载成功、路由从未挂载、matchEndpoint全仓无实现 #4936/ApiRegistry/api-registryplugin 只在packages/core/examples/里被装配,无任何真实 composition 挂载 ——ApiEndpointRegistrationSchema因此整面零执行 #4939) →declarative-apis-endpoints-live, which cites 声明式apis:(ApiEndpoint)入站面全链路零执行:元数据装载成功、路由从未挂载、matchEndpoint全仓无实现 #4936 by number and supersedes it: v17 ships the executor, so telling an upgrader "apis:is refused" would be false for the version they are upgrading to.Declared-only family removed — no author path, no consumer (14).
dev-plugin-protocol-family-removed,kernel-metadata-loader-envelope-removed,lifecycle-event-registry-enforced,plugin-lifecycle-hooks-retired,prune-dead-audit-config-cluster,prune-dead-capabilities-descriptor,prune-orphan-featureflag-schema,objectql-dead-use-retired,session-dual-source-c4,event-schema-dual-source-c6,environment-artifact-dual-source-c10,tenant-plan-dual-source-c16,etl-author-shape-aliases,retire-the-dev-stub-table. Each states its own exemption and gives the reason the exemption is real (no load path parses it, so no authored source can carry the keys); the two most explicit cite theplugin-runtime.zod.ts DynamicLoadingConfigprecedent (#3950). The ETL family additionally is registered, as retired-defsautomation/ETL*.Runtime behaviour or wire shape, with no authored metadata and no removed authorable key (11).
empty-capability-answers-501,retire-the-dispatcher-auth-mock,dispatcher-error-code-is-semantic,settings-error-details-declared-slot,findone-requires-a-predicate,deprecate-kernel-assignment-notifications,better-auth-1-7-0-rc-2-and-prod-dep-batch(a third-party upgrade shipping its own idempotent boot-time backfill),compose-stacks-i18n-single-valued,hook-context-api-scoped-contextandsubscribe-metadata-event-subject(both type narrowings — additive for already-correct code),action-param-option-visible-when(a capability opened, not removed).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.These are authorable metadata with real rename tables and 0 subject coverage, and the class is demonstrably registrable —
ui-schemas-strict-unknown-keys(major 15) anddashboard-widget-strict-unknown-keys(major 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, and choosing 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 single largest piece of remaining work on this card.Flagged rather than rounded:
http-server-config-retired(nine keys with a full FROM → TO table, but the changeset shows there was no authoring entry at all — they could not be written down),retire-dev-service-marker,durable-suspended-screen-refetch(a sync→async contract break),sys-comment-retire-visibility-reply-count,membership-grade-not-capability-channel,converge-activation-event-schema(defineStudioPlugin'sactivationEventsstring→object is authored),views-translation-key-runtime-identity(translation-bundle keys). Any of these could reasonably be ruled owed.Gates run (with invocation scope)
pnpm --filter @objectstack/spec run check:migration-registrypnpm --filter @objectstack/spec run check:spec-changespnpm --filter @objectstack/spec run check:upgrade-guidepnpm --filter @objectstack/spec run typechecktsc --noEmit+ scripts + test-typecheck)pnpm --filter @objectstack/spec exec vitest run src/migrations/migrations.test.tsnode scripts/check-adr-0087-registration.mjs --self-testnode scripts/check-adr-0087-registration.mjs --base origin/mainnode scripts/check-empty-changeset.mjs --base origin/mainChangeset included (
@objectstack/spec: minor) — the entries changespec-changes.json, the published upgrade guide, and whatos migrate metaemits.⛔ Untouched, per scope: the gate's diff-only behaviour,
packages/spec/src/**/*.zod.ts, andcontent/docs/releases/**.Could not verify
~rows were not examined. They are outside this card's!set, but~only means some commit touched a ledger file — it does not prove the entry covers that face. That is an unexamined seam of the same shape.ApiRegistrysub-face of 声明式apis:(ApiEndpoint)入站面全链路零执行:元数据装载成功、路由从未挂载、matchEndpoint全仓无实现 #4936 has no entry of its own; I judge it not owed (a registry class, not authored metadata) but it is the weakest of the not-missing calls.Generated by Claude Code