You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
CrudEndpointsConfigSchema.patterns is an exhaustive enum-keyed z.record — the input type demands all five operations and a partial parses to explicit undefined entries (wants z.partialRecord) #14365
Found while implementing #11984 (making RestServer.normalizeConfig parse and consume crud / metadata / batch / routes). Filed, not fixed — the declaration lives in packages/spec, which is the spec seat's surface.
What was measured
On origin/main @ 08e49496f, zod 4.4.3. packages/spec/src/api/rest-server.zod.ts declares:
The runtime parse accepts the partial, then invents the missing keys. zod's $ZodRecord enum-key branch walks every enum value and writes payload.value[key] = result.value for each, so CrudEndpointsConfigSchema.parse({ patterns: { list: {...} } }).patterns has five keys, four of them explicitly undefined. Object.keys(...) answers five for a config that wrote one.
Same shape one level down, with a non-optional value: RouteGenerationConfigSchema.overrides declares operations: z.record(CrudOperation, z.boolean()), which is exhaustive AND required, so a partial overrides.account.operations: { list: false } is refused with four expected boolean, received undefined issues. Since #11984 executes that schema at RestServer construction, this is a live refusal (pinned there as the declared behaviour; the input TYPE already demanded all five at typed authoring sites).
Why it matters
A partial record is the natural authoring shape for both keys, and an AI-authored config will write the partial. Today the type says "no", the runtime says "yes" (for patterns) or "no" (for overrides.*.operations), and the parsed patterns carries phantom entries any future consumer iterating it would have to skip. Note that nothing in packages/rest reads patterns or overrides today at all — the inert-key half is a separate card (declared-but-unconsumed routes / crud keys, filed alongside this one).
Suggested fix (spec seat)
z.partialRecord(CrudOperation, CrudEndpointPatternSchema) for patterns (optional keys, no phantom entries, input type is a Partial), and a decision for overrides.*.operations — z.partialRecord(CrudOperation, z.boolean()) if "unspecified operation keeps its default" is the intended reading. Both change the JSON schema / authorable-surface artifacts (gen:schema), and packages/rest's pin in rest-sub-config-parse-not-cast.test.ts asserts only the contract-stable half (the written pattern survives, no pattern is invented), so it stays green under either declaration.
Blocked-by: #14691
Found while implementing #11984 (making
RestServer.normalizeConfigparse and consumecrud/metadata/batch/routes). Filed, not fixed — the declaration lives inpackages/spec, which is the spec seat's surface.What was measured
On
origin/main@08e49496f, zod4.4.3.packages/spec/src/api/rest-server.zod.tsdeclares:zod 4 reads an enum-keyed
z.recordas an EXHAUSTIVE record, with two consequences that disagree with each other:z.inputof that record is a Record whose keys are all five CRUD operations (valuesCrudEndpointPatternorundefined), sotscrefuses the natural partialpatterns: { list: { method: 'GET', path: '/x' } }— measured asTS2739 ... is missing the following properties ...: update, delete, create, readinRestServer.normalizeConfigstill castscrud/metadata/batch/routesinstead of parsing them —batch.maxBatchSizebounds and three declared enums never execute #11984's pin file, which had to write the fixture withas never.$ZodRecordenum-key branch walks every enum value and writespayload.value[key] = result.valuefor each, soCrudEndpointsConfigSchema.parse({ patterns: { list: {...} } }).patternshas five keys, four of them explicitlyundefined.Object.keys(...)answers five for a config that wrote one.Same shape one level down, with a non-optional value:
RouteGenerationConfigSchema.overridesdeclaresoperations: z.record(CrudOperation, z.boolean()), which is exhaustive AND required, so a partialoverrides.account.operations: { list: false }is refused with fourexpected boolean, received undefinedissues. Since #11984 executes that schema atRestServerconstruction, this is a live refusal (pinned there as the declared behaviour; the input TYPE already demanded all five at typed authoring sites).Why it matters
A partial record is the natural authoring shape for both keys, and an AI-authored config will write the partial. Today the type says "no", the runtime says "yes" (for
patterns) or "no" (foroverrides.*.operations), and the parsedpatternscarries phantom entries any future consumer iterating it would have to skip. Note that nothing inpackages/restreadspatternsoroverridestoday at all — the inert-key half is a separate card (declared-but-unconsumedroutes/crudkeys, filed alongside this one).Suggested fix (spec seat)
z.partialRecord(CrudOperation, CrudEndpointPatternSchema)forpatterns(optional keys, no phantom entries, input type is a Partial), and a decision foroverrides.*.operations—z.partialRecord(CrudOperation, z.boolean())if "unspecified operation keeps its default" is the intended reading. Both change the JSON schema / authorable-surface artifacts (gen:schema), andpackages/rest's pin inrest-sub-config-parse-not-cast.test.tsasserts only the contract-stable half (the written pattern survives, no pattern is invented), so it stays green under either declaration.Generated by Claude Code
Generated by Claude Code