Retire the paper metadata-customization protocol with its full coupling set - #13186
Conversation
… contracts, metadata limb) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LpRNHxWZgSUgVnFT9mQQo4
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LpRNHxWZgSUgVnFT9mQQo4
…rictness ledger Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LpRNHxWZgSUgVnFT9mQQo4
`scripts/file-description.test.ts` pins how many rendered descriptions the level-1 heading demotion touches (#12249). `kernel/metadata-customization.zod.ts` was one of those level-1 openers, so removing the module moves the count 38 -> 37 in both assertions. `src/type-alias-convention.pin.test.ts` pins the isomorphic-alias count in prose AND recomputes it from the source (ADR-0122), so the four vacated Iso408-411 rows move it 837 -> 833 in the section header and the test title the runtime companion checks the prose against. Both are re-baselines of a machine-checked count, not relaxations: the recomputation still runs and still fails if either number goes stale. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LpRNHxWZgSUgVnFT9mQQo4
…dened-customization-retirement
…, re-declare the kernel page count `check:doc-authoring` flagged the three `retiredKey()` prescriptions: a customer reading a parse refusal has no tracker, so an issue id there is a citation-shaped token resolving to nothing. Per that gate's rule the issue id NEXT TO an ADR id is the strippable half -- each prescription keeps `ADR-0049 enforce-or-remove` plus its ADR-0126/ADR-0005 FROM -> TO mapping, so none is left bare. Reference pages regenerated (the tombstone text projects into `content/docs/references/**`). `check:quick-reference-counts` flagged the [total] side: deleting the `kernel/metadata-customization` reference page moves what `content/docs/references/kernel/` publishes from 31 to 30. The curated table's own row count (17) is untouched -- no row ever pointed at that page. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LpRNHxWZgSUgVnFT9mQQo4
…dened-customization-retirement # Conflicts: # content/docs/kernel/contracts/metadata-service.mdx
📓 Docs Drift CheckThis PR changes 2 package(s): 11 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 2 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 128 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin e6270d951a9986e74ab951f80386232f5f4fc093 && git checkout e6270d951a9986e74ab951f80386232f5f4fc093
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 47d9b77b1c957aeb47fc67b1fa15c8eef2110af9 86d1000469ac5b87472f238dac2b16ecc043cc4c && git checkout -B drift-repro 47d9b77b1c957aeb47fc67b1fa15c8eef2110af9 && git merge --no-ff 86d1000469ac5b87472f238dac2b16ecc043cc4c
node scripts/docs-audit/affected-docs.mjs --json 47d9b77b1c957aeb47fc67b1fa15c8eef2110af9
|
`check:llms-txt` reds on two counts, and the file is hand-kept with no
generator on purpose: deleting `kernel/metadata-customization.zod.ts` takes
`src/kernel/` from 32 to 31 and the domain-summed total from 208 to 207.
Both rows re-read rather than digit-patched, per the gate's own instruction:
- The heading's method prose ("counted as `*.zod.ts` under `src/<domain>/`")
stays exactly true, and is in fact what explains 207 against the 208 files
on disk -- `src/stack.zod.ts` sits outside any domain directory. Every other
domain row was re-counted independently and already matched; only kernel
was stale.
- The kernel row's Key Schemas column (Plugin, Manifest, Events, Feature,
Context, Package Registry) never named the removed module, so nothing
beside that number became untrue.
Third fix, which the gate does NOT catch: the section-6 contract table lists
`IMetadataService` METHODS, and `overlay` was one of them. That entry was true
at the base commit (4 members) and is false now (0) -- this retirement removed
them. Left alone it would ship inside the npm tarball telling AI consumers to
generate `metadataService.saveOverlay(...)`, which is exactly the failure the
file's header warns about. Dropped from the row.
⛔ Deliberately NOT touched: the same row claims `delete`, which resolves to 0
members at the base commit too -- the interface spells it `unregister`. That
is pre-existing and unrelated to this retirement, so it is reported rather
than folded in.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LpRNHxWZgSUgVnFT9mQQo4
…dened-customization-retirement
Fixes #13135
Retires the paper metadata-customization protocol with the full coupling set the fork report measured. The single-module deletion the original card described does not compile — any compiling PR performs the wider removal — so this PR performs it declared rather than incidentally.
Charter
What is removed
packages/spec/src/kernel/metadata-customization.zod.ts— whole module, plus itskernel/index.tsbarrel line (replaced by a comment naming the two mechanisms that actually ship).kernel/metadata-plugin.zod.ts— the authorablecustomizationPoliciesandmergeStrategykeys, retired through thespec-property-retirementroutes in full:retiredKey()tombstones,RETIRED_KEYS_BY_MAJOR[18]entries, ledgers. Not a bare delete.api/metadata.zod.ts§5 — the Overlay/Customization contracts for REST endpoints no adapter ever served. Section numbering is preserved: ids are claims, not positions.contracts/metadata-service.ts— the optionalgetOverlay/saveOverlay/removeOverlay/getEffectivemembers typed againstMetadataOverlay.kernel/metadata-loader.zod.ts—persistence.overlayWritable, tombstoned (the nested object is not strict, so a plain deletion would strip the key in silence).packages/metadata— the in-memory overlay limb onMetadataManager, its test cases, the'overlay'feature-log entry, and the three-way-mergeROADMAP.mdrow (which ADR-0126 rules out rather than defers).type-alias-convention.pin.test.ts— the four vacated Iso408-411 rows, with the machine-checked count re-baselined 837 to 833 in both the prose and the title the runtime companion checks against.kernel/metadata-customizationpage disappears with the module), the strictness-ledger counts, and the skill reference index.Registered: 10
RETIRED_DEFS_BY_MAJOR[18]entries, 3RETIRED_KEYS_BY_MAJOR[18]tombstones, the D3 semantic entrymetadata-customization-protocol-retired, and a retirement pin test.Judgment calls, stated because a reviewer should be able to disagree with them
minor, notmajor, and prescriptions registered under 18. v17.0.0 was cut before this landed, so the removal ships on the 17.x line under the launch-window convention while the prescription lives at the major boundary whereos migrate metausers look. This follows the18.api__PackageRollbackResponseprecedent, whose entry carries the same reconciliation paragraph. It is also the only lawful choice mechanically:check-changeset-no-major.mjsrefuses amajorbump, and its verdict here isThis diff introduces no major bump.Note this reads differently from the Epic: packaged-metadata customization (ADR-0126) — flows first, v17 line #12150 epic's line that the removal "rides the next major (v18)" — that line predates the v17 cut, and the registry major (18) is what it names in substance.applyConversionsToStoredItemmaps a metadata type onto one of its collections. Plugin and manager configs are neither —PLURAL_TO_SINGULARhas nopluginsentry — so aMetadataConversionhere would be a transform with no seam that ever runs. The prescription reaches authors through the tombstone and the D3 semantic entry instead. Same reasoning as the siblingkernel/MetadataPluginConfig:additionalTypesentry.check:doc-authoringflags an internal issue id in customer-facing spec text: a customer reading a parse refusal has no tracker. Each prescription keepsADR-0049 enforce-or-removeplus its ADR-0126 / ADR-0005 FROM-to-TO mapping, so none is left bare — which that rule requires.content/docs/references/kernel/page count re-declared 31 to 30 inquick-reference.mdx. The curated table's own row count (17) is untouched: no row ever pointed at the deleted page.llms.txtre-derived, not digit-patched (CI caught this one).check:llms-txtreddened on the first push: deleting the module takessrc/kernel/from 32 to 31 and the domain-summed total from 208 to 207, in a hand-kept file that ships to AI consumers with deliberately no generator. Both rows were re-read per the gate's own instruction rather than having their numbers swapped: the heading's method prose stays true and is what explains 207 against the 208.zod.tsfiles on disk (src/stack.zod.tssits outside any domain directory), every other domain row was re-counted independently and already matched, and the kernel row's Key Schemas column never named the removed module. A third correction the gate does not catch: the section-6 contract table listsIMetadataServicemethods andoverlaywas one of them — true at the base commit (4 members), false now (0). Left alone it would have shipped inside the npm tarball telling an AI consumer to generatemetadataService.saveOverlay(...). Dropped. ⛔ Deliberately not folded in: the same row also claimsdelete, which resolves to 0 members at the base commit too (the interface spells itunregister) — pre-existing and unrelated, so it is reported rather than fixed here.wip:commits pushed by the predecessor dispatch before it terminated. Rewriting them requires a force-push, which AGENTS.md multi-agent rule 3 forbids without exception ("Nevergit push --force/--force-with-lease"). They are therefore still on the branch. Please compose the squash message from this PR's title and body rather than from the concatenated commit list.Explicitly not in this PR
api/package-api.zod.tsmergeStrategy enum,kernel/package-upgrade.zod.ts) — separately declared surfaces, untouched.Reverse verification
A compile probe placed inside
packages/metadataso@objectstack/specresolves through the packageexportsmap into the built dist, notsrc/:MetadataPluginConfigSchemafrom@objectstack/spec/kernelandMetadataBulkResponseSchemafrom@objectstack/spec/apiboth compile clean, so the entries resolve and the negative legs below are readings rather than a broken module path. An earlier run of this probe from a directory with no resolution path failed its control and was discarded.TS2724: '"@objectstack/spec/kernel"' has no exported member named 'MetadataOverlaySchema', the same for theMetadataOverlaytype, andTS2724: '"@objectstack/spec/api"' has no exported member named 'MetadataOverlayResponseSchema'.undefined-only on the authoring side, and writing a value fails:TS2322: Type '{ type: string; }[]' is not assignable to type 'undefined'. The control asserting a surviving key is notundefined-only passes, and deliberately flipping that control to the wrong expectation makes it fail, so the harness is falsifiable.MetadataPluginConfigSchema.safeParserefuses atpath=customizationPolicies,expected=never, with the prescription itself as the message.api-surface/kernel.jsoncarried 17 rows matching the retired protocol at the base commit and carries 0 now;content/docs/references/kernel/metadata-customization.mdxis gone, with 0 residual references in the kernelmeta.json, the kernel index and the references index.Validation
All readings below are at
86d100046, the branch head — taken after the secondorigin/mainmerge and after the final commit, not before it.A note on that second merge. The charter predicted this collision and it happened: PR #13133 (from #12384) landed on
mainwhile this branch was in validation, and itscontent/docs/kernel/contracts/metadata-service.mdxedit restored thesaveOverlay/getEffectiveteaching block in the Org-Level Customization section. Resolved in favour of deletion, as the charter directs — the retired members' teaching goes with them. The page now carries the ADR-0005 org-overlay description in that slot. A sweep ofcontent/finds no remaining call site for any of the four retired members.Gate family derived mechanically, no hand-fed paths:
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack, three-dot semantics against the merge base. Exit codes were captured before any pipe in every run.Quoting the gates' own verdict lines:
pnpm --filter @objectstack/spec check:generated—✓ All 14 generated artifacts are up to date.(covers api-surface, export-origins, authorable-surface plus its.base.jsonanchor, json schemas,content/docs/references/**, skill docs and refs, the strictness ledger, liveness and test-typecheck)pnpm --filter @objectstack/spec typecheck— clean, andcheck:test-typecheck: OK — @objectstack/spec's test layer compiles under packages/spec/tsconfig.test.jsonpnpm --filter @objectstack/spec check:llms-txt—97 claim(s) re-derived — every advertised symbol resolves against api-surface/ (17 entry points), every subpath is a real exports key, the inventory matches src/ (14 domains, 207 schemas) and the package table matches the workspace (68 published).pnpm check:type-check-debt --re-measure—OK — 31 ledger entr(ies) re-measured in 245.6s, 1570 raw tsc error(s) total, none above its recorded number.withsurplus: nonepnpm check:doc-authoring—✓ doc authoring guard: 13326 customer-facing string(s) across 686 spec sources clean — no internal issue-id references.pnpm check:quick-reference-counts—✓ ... every "(N of M schemas)" heading matches its table AND content/docs/references/pnpm --filter @objectstack/spec check:skill-examples—✅ 260 prose examples type-check across 3 surface(s)pnpm check:i18n—OK (9 package(s) — all bundles in sync, no undeclared authoring keys).node scripts/check-adr-0087-registration.mjs,check:liveness,check:empty-state,check:strictness-ledger,check:variant-docs,check:meta-url-spelling,check:type-check-coverage,check:dual-build-cjs-loads,check:nul-bytes— all exit 0check:generateddeclares it does not run was executed this round (llms-txt,empty-state,skill-examples,template-manifests,variant-docs,exported-any,dual-source-exports,entry-nameability,browser-reachable-entries,scripts-typecheck,objectui-pin-citations) — all green. Running only a subset of that list is how thellms.txtstaleness reached CI.Two families in the derived set were NOT MEASURED here, and neither is a pass:
node scripts/check-test-completeness.mjs—PREREQUISITE NOT MET — this gate grades a saved turbo run test log, and no log was named(exit 3 since fix(scripts): refuse a logless check-test-completeness run instead of answering exit 1 #13152; CI tees the log and passes the path).node scripts/pm/check-half-states.mjs—PREREQUISITE NOT MET — the token in the environment is not a valid GitHub credential, exit 3, whose own text says "no reading at all". The lint-requiredpnpm check:pm-half-statesis green.check:react-declaration-parityis the standingEXTERNAL_INPUT_REQUIREDcase: its right-hand side is objectui'ssdui.manifest.json, which this repo cannot produce.Suites, both at
86d100046:packages/spec—Test Files 443 passed (443),Tests 11746 passed (11746);packages/metadata—Test Files 38 passed (38),Tests 669 passed (669).packages/metadatahas notypecheckscript — it is covered by the DEBT ledger, so its type story is the ratchet verdict above, not a per-package script that would have matched nothing and exited 0.skills/**readingsThe diff touches one published-skill file, and it is a generated index (
gen:skill-refs) whose line leaves with the module it names.skills/objectstack-platform/references/_index.md: 62 to 61 lines (net -1).SKILL.mdunderskills/objectstack-platform/: 1286 to 1286 lines (unchanged).SKILL.mdunderskills/: 10515 to 10515 lines (unchanged).Net is a deletion, so no expansion budget is consumed.
Landing posture — please read before touching the merge controls
needs:contract-reviewis attached and stays. Clause-② applies on both legs: PATH (packages/spec/src/**) and CONTENT (a public-export deletion narrows the published face).skills/objectstack-platform/references/_index.mdis underskills/**, which AGENTS.md Prime Directive 14 names. One path hit forks the whole PR, so ⛔ no AI seat merges, queues, or arms auto-merge on it, and landing needs either an approval pinned to the current head by aGOVERNED_APPROVERSaccount or the maintainer's hand-merge. The governed file cannot be split out:check:skill-refscompares that generated index against the spec source, so the line must leave in the same commit as the module.opusunder the recorded quota-exhaustion exemption (maintainer 2026-08-13, verbatim: 「fable 如果用完了,可以用 opus」), after the fable-tier dispatch terminated on an HTTP 429;needs:contract-reviewis the compensating control.Out-of-scope finding
Filed unassigned: #13185 — ADR-0005 still names
metadata-customization.zod.tsas a kept, future-phase model. Governed surface, so it is not touched here; it may fold into #13136's ADR pass if a reviewer prefers one governed PR.Authored by the os-dev seat of session
session_01LpRNHxWZgSUgVnFT9mQQo4(https://claude.ai/code/session_01LpRNHxWZgSUgVnFT9mQQo4), continuing the quota-terminated dispatch on the predecessor's surviving branch. This trailing line is deliberate: on this repo a body PATCH has been measured to trim the attribution footer, so the session URL is kept in prose where it survives.