Found by the domain:spec execution seat while implementing #16431 option (c) — the never-published ratchet. Out of scope there by that card's own fence (triage ruled (c) reports what is already true and changes no schema), so it is filed rather than fixed. Unassigned, bare, for triage.
Measured on origin/main @ 142c01c88ee1be4c24e20f7b803a511a00c992fb
hook is a declared metadata type that authors write as documents — packages/spec/src/kernel/metadata-plugin.zod.ts:800:
{ type: 'hook', label: 'Hook', filePatterns: ['**/*.hook.ts', '**/*.hook.yml'],
supportsOverlay: false, allowOrgOverride: false, allowRuntimeCreate: true, … }
HookSchema emits no JSON Schema. From pnpm --filter @objectstack/spec check:authorable-surface (exit 0 — the skip is a warn):
⊘ Data.HookSchema: Custom types cannot be represented in JSON Schema (skipped)
The unrepresentable member is the deprecated inline-function half of handler, packages/spec/src/data/hook.zod.ts:252:
handler: z.union([z.string(), z.custom<(...args: any[]) => any>(…)]).optional()
What that costs, beyond the missing reference page
Counted in packages/spec/, each with a firing control on the same corpus:
grep -ho '"data/Hook:[^"]*"' authorable-surface/data.json | wc -l -> 0
grep -ho '"data/HookContext:[^"]*"' authorable-surface/data.json | wc -l -> 14 (control)
grep -ho '"data/Object:[^"]*"' authorable-surface/data.json | wc -l -> 43 (control)
grep -c '"data/Hook"' json-schema.manifest/data.json -> 0
grep -c '"data/HookContext"' json-schema.manifest/data.json -> 1 (control)
So the authorable keys of a live, author-written metadata type are recorded by no key-level ratchet:
build-schemas.ts records the mechanism in its own words, in the #4725 block: reachability "is keyed by zodByDefKey, populated only for defs this build EMITS". The measurements above are direct; the mechanism pointer is offered as the likely cause, not as a proof.
Sibling instance, same shape
Data.MemoryConfigSchema — a memory datasource's authorable config — is unprojectable for the same structural reason: exactly one branch of its persistence union ({ type: 'custom', adapter }) carries live functions, while everything an author can write in YAML is representable. Both are now recorded in packages/spec/unemitted-schemas.baseline.json with that reasoning.
Why this is worth a card rather than a passing note
#16431 measured the population (23 exports) and ruled its own remedy scope to the four filter.zod.ts exports whose cause is z.date(). This one has a different cause (z.custom), a different remedy, and a consequence that card does not state: it is not only a documentation gap but a ratchet coverage gap on a metadata type the platform invites authors to write.
A plausible remedy is narrow: the inline-function handler form is already marked DEPRECATED in favour of body, so retiring that union member (ADR-0049 enforce-or-remove, with the ADR-0087 conversion the retirement kit requires) would make HookSchema projectable and pull its keys back under both ratchets. That is a decision, not an edit — hence this card.
Refs: #16431 · #3855 · #4650 · #4666 · #2978 · #4725
Generated by Claude Code
Found by the
domain:specexecution seat while implementing #16431 option (c) — the never-published ratchet. Out of scope there by that card's own fence (triage ruled (c) reports what is already true and changes no schema), so it is filed rather than fixed. Unassigned, bare, for triage.Measured on
origin/main@142c01c88ee1be4c24e20f7b803a511a00c992fbhookis a declared metadata type that authors write as documents —packages/spec/src/kernel/metadata-plugin.zod.ts:800:HookSchemaemits no JSON Schema. Frompnpm --filter @objectstack/spec check:authorable-surface(exit 0 — the skip is a warn):The unrepresentable member is the deprecated inline-function half of
handler,packages/spec/src/data/hook.zod.ts:252:What that costs, beyond the missing reference page
Counted in
packages/spec/, each with a firing control on the same corpus:So the authorable keys of a live, author-written metadata type are recorded by no key-level ratchet:
data/Hook:keys to compare against, so a renamed or deleted key onHookSchemamoves nothing it can see;HookSchemais likewise outside it;data/Hookwas never injson-schema.manifest/, which is the state [finding] Five filter operators ($gt/$gte/$lt/$lte/$between) reach NO published reference page —build-schemas.tsskips their whole schema over an unrepresentablez.date(), and the skip is silent #16431 is about.build-schemas.tsrecords the mechanism in its own words, in the #4725 block: reachability "is keyed byzodByDefKey, populated only for defs this build EMITS". The measurements above are direct; the mechanism pointer is offered as the likely cause, not as a proof.Sibling instance, same shape
Data.MemoryConfigSchema— a memory datasource's authorableconfig— is unprojectable for the same structural reason: exactly one branch of itspersistenceunion ({ type: 'custom', adapter }) carries live functions, while everything an author can write in YAML is representable. Both are now recorded inpackages/spec/unemitted-schemas.baseline.jsonwith that reasoning.Why this is worth a card rather than a passing note
#16431 measured the population (23 exports) and ruled its own remedy scope to the four
filter.zod.tsexports whose cause isz.date(). This one has a different cause (z.custom), a different remedy, and a consequence that card does not state: it is not only a documentation gap but a ratchet coverage gap on a metadata type the platform invites authors to write.A plausible remedy is narrow: the inline-function
handlerform is already marked DEPRECATED in favour ofbody, so retiring that union member (ADR-0049 enforce-or-remove, with the ADR-0087 conversion the retirement kit requires) would makeHookSchemaprojectable and pull its keys back under both ratchets. That is a decision, not an edit — hence this card.Refs: #16431 · #3855 · #4650 · #4666 · #2978 · #4725
Generated by Claude Code