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
spec: Field.scale accepts meaningless declarations (2.5, -1) — now that scale is enforced, malformed declarations should be refused at authoring time #8321
z.number() admits scale: 2.5 and scale: -1, neither of which has a defined meaning as a decimal-places count. The same looseness exists on precision (Total digits).
Why it matters now
#7501 made scale enforced at write time (rejection with max_scale). The runtime branch deliberately guards on Number.isInteger(def.scale) && def.scale >= 0 and leaves a malformed declaration unenforced — inventing floor/round semantics in a consumer would be the PD #12 guessing the ruling rejected. That is the correct runtime posture, but it means a typo'd declaration (scale: 2.5) silently gets no enforcement at all: exactly the declared-but-inert shape that hides AI-authored metadata errors.
The house shape is to refuse this at the producer: z.number().int().min(0) (and the analogous tightening for precision), so a meaningless declaration fails loudly at authoring/publish time instead of degrading to silence.
Notes for whoever takes it
The runtime guard is pinned in packages/objectql/src/validation/record-validator.test.ts ("a malformed declaration (non-integer or negative scale) stays unenforced") — that pin should flip to authoring-time refusal semantics when the schema tightens, not be deleted.
Tightening an authorable key changes the authorable surface: gen:schema artifacts and a fixture sweep across the schema's consumers are part of the change, and any stored metadata carrying a malformed value needs the ADR-0087 conversion-layer treatment rather than a hard break.
Follow-up to #7501 (which remains open until its own PR lands; nothing here closes it).
What
packages/spec/src/data/field.zod.tsdeclares the field-level number constraint as:z.number()admitsscale: 2.5andscale: -1, neither of which has a defined meaning as a decimal-places count. The same looseness exists onprecision(Total digits).Why it matters now
#7501 made
scaleenforced at write time (rejection withmax_scale). The runtime branch deliberately guards onNumber.isInteger(def.scale) && def.scale >= 0and leaves a malformed declaration unenforced — inventing floor/round semantics in a consumer would be the PD #12 guessing the ruling rejected. That is the correct runtime posture, but it means a typo'd declaration (scale: 2.5) silently gets no enforcement at all: exactly the declared-but-inert shape that hides AI-authored metadata errors.The house shape is to refuse this at the producer:
z.number().int().min(0)(and the analogous tightening forprecision), so a meaningless declaration fails loudly at authoring/publish time instead of degrading to silence.Notes for whoever takes it
packages/objectql/src/validation/record-validator.test.ts("a malformed declaration (non-integer or negative scale) stays unenforced") — that pin should flip to authoring-time refusal semantics when the schema tightens, not be deleted.gen:schemaartifacts and a fixture sweep across the schema's consumers are part of the change, and any stored metadata carrying a malformed value needs the ADR-0087 conversion-layer treatment rather than a hard break.CurrencyConfigSchemahas its ownprecisionwith an alias table (field.zod.ts—scale → precisionthere) — a different surface; do not conflate the two (the A number field's declaredscaleis never enforced — values with more decimals are accepted and stored verbatim (min/max on the same field are enforced) #7501 thread documents the trap).Generated by Claude Code