Found while implementing #11431. Filed unassigned. Cannot be fixed from the driver lane — packages/spec is a different seat.
What
packages/spec/src/data/field.zod.ts declares:
maxLength: z.number().optional().describe("Max character length"),on the base field schema. Two consequences:
- No shape validation.
.number() alone accepts 0, -5, 12.5 and Infinity. None of those is a character length, and each parses without complaint. - Authorable on every type. Being on the base schema,
maxLength is legal on boolean, date, lookup, autonumber, formula — types where it describes nothing that is stored.
Evidence that this is not theoretical
schema-drift.ts read a malformed bound as authoritative and planned DDL no server accepts — maxLength: 0 took the narrowing arm (0 > 255 is false) and asked for varchar(0), at severity error / category destructive, i.e. as work os migrate apply --allow-destructive should go do. Fixed on the driver side in #11431 by refusing anything that is not a positive integer, but that is a consumer defending itself against a producer that should not have emitted the value (PD #12).
The authoring forms already disagree about where the key applies, which suggests the base-schema placement was never deliberate:
field.form.ts shows maxLength for text / textarea / email onlyobject.form.ts shows it for text,textarea,email,url,phone,password,markdown,html,richtext
Neither list matches the other, and neither matches the base schema's "every type".
The write-time validator (packages/objectql/src/validation/record-validator.ts) enforces maxLength for text | textarea | email | url | phone | password | markdown | html | richtext | code — a third list, and the closest thing to a real answer for which types the key means something.
Direction (for triage — not prescribing)
Two separable decisions:
Generated by Claude Code
Found while implementing #11431. Filed unassigned. Cannot be fixed from the driver lane —
packages/specis a different seat.What
packages/spec/src/data/field.zod.tsdeclares:on the base field schema. Two consequences:
.number()alone accepts0,-5,12.5andInfinity. None of those is a character length, and each parses without complaint.maxLengthis legal onboolean,date,lookup,autonumber,formula— types where it describes nothing that is stored.Evidence that this is not theoretical
schema-drift.tsread a malformed bound as authoritative and planned DDL no server accepts —maxLength: 0took the narrowing arm (0 > 255is false) and asked forvarchar(0), at severityerror/ categorydestructive, i.e. as workos migrate apply --allow-destructiveshould go do. Fixed on the driver side in #11431 by refusing anything that is not a positive integer, but that is a consumer defending itself against a producer that should not have emitted the value (PD #12).The authoring forms already disagree about where the key applies, which suggests the base-schema placement was never deliberate:
field.form.tsshowsmaxLengthfortext/textarea/emailonlyobject.form.tsshows it fortext,textarea,email,url,phone,password,markdown,html,richtextNeither list matches the other, and neither matches the base schema's "every type".
The write-time validator (
packages/objectql/src/validation/record-validator.ts) enforcesmaxLengthfortext | textarea | email | url | phone | password | markdown | html | richtext | code— a third list, and the closest thing to a real answer for which types the key means something.Direction (for triage — not prescribing)
Two separable decisions:
z.number().int().min(1)is the house pattern, and the file already applies exactly it toprecisionone field below (z.number().int().min(0)) for the same stated reason — spec:Field.scaleaccepts meaningless declarations (2.5,-1) — now that scale is enforced, malformed declarations should be refused at authoring time #8321's "a non-integer or negative declaration has no defined meaning". Cheap, and strictly a tightening.maxLengthshould be refused on types that do not store a bounded string is the larger question, and touches ADR-0078 declared=enforced. The validator's ten-type list is the natural candidate for the allowed set.Generated by Claude Code