Skip to content

spec: Field.scale accepts meaningless declarations (2.5, -1) — now that scale is enforced, malformed declarations should be refused at authoring time #8321

Description

@os-zhuang

Follow-up to #7501 (which remains open until its own PR lands; nothing here closes it).

What

packages/spec/src/data/field.zod.ts declares the field-level number constraint as:

scale: z.number().optional().describe('Decimal places'),

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.
  • CurrencyConfigSchema has its own precision with an alias table (field.zod.tsscale → precision there) — a different surface; do not conflate the two (the A number field's declared scale is 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

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions