Uh oh!
There was an error while loading. Please reload this page.
fix(spec): maxLength is a positive integer, authorable only on bounded-string field types - #11989
Conversation
…d-string field types Issue #11566 context: shape tightens to the #8321 house pattern (z.number().int().min(1)); applicability converges to the write-time validator's ten-type list, exported as BOUNDED_STRING_FIELD_TYPES; both authoring-form lists align to the same ten. Card relationship is declared in the PR body, not here. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K93P8PbH7kVjsAXkqzH1zY
📓 Docs Drift CheckThis PR changes 1 package(s): 28 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 1 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 126 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 478e98476fe562d06dea9297d29b767dd0728cd5 && git checkout 478e98476fe562d06dea9297d29b767dd0728cd5
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin d63b01436035f0866df8c22156e26ab2a064580d e36b89a0f37d61cde9ff8937de55544e46250e69 && git checkout -B drift-repro d63b01436035f0866df8c22156e26ab2a064580d && git merge --no-ff e36b89a0f37d61cde9ff8937de55544e46250e69
node scripts/docs-audit/affected-docs.mjs --json d63b01436035f0866df8c22156e26ab2a064580d
|
…d-form maxLength respell The anti-vacuity count tracks the shipped predicate corpus; the field form's maxLength row went from a 3-way == chain to one in-list while converging on the ten bounded-string types, and in-list literals are deliberately outside the RHS rule's anchor. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K93P8PbH7kVjsAXkqzH1zY
Uh oh!
There was an error while loading. Please reload this page.
Fixes#11566
Ruling implemented
Maintainer ruling, 2026-08-24, recorded on the card (verbatim, untranslated): 「四维分析一致的,接手你的建议。」 — (a) shape tightens to the house pattern
z.number().int().min(1)(the #8321precision/scaleprecedent one field below); (b) applicability converges to the write-time validator's ten-type list; (c) both authoring-form lists align to the same ten in the same stroke. This narrows the accept set (clause-② card).What changed
packages/spec/src/data/field.zod.tsmaxLengthis nowz.number().int().min(1)—0, negatives and non-integers are refused at parse with the same issue codes as the spec:Field.scaleaccepts meaningless declarations (2.5,-1) — now that scale is enforced, malformed declarations should be refused at authoring time #8321 precedent (too_small/invalid_type), at path['maxLength'].BOUNDED_STRING_FIELD_TYPES(the validator's ten:text textarea email url phone password markdown html richtext code), following theREFERENCE_VALUE_TYPES/STRUCTURED_JSON_TYPEShouse pattern.superRefinecheck (thereferenceVia-on-texthouse pattern for type-scoped key refusal): an authoredmaxLengthon any type outside the ten is refused with acustomissue at['maxLength']whose message names the legal set, the offending type, and the fix.maxLengthhas no schema default, so the check can never fire on an un-authored key; absence stays absence (no.overwrite()involvement — pinned).packages/spec/src/data/field.form.ts—maxLengthrow'svisibleWhengrows from 3 types (text/textarea/email) to the ten.packages/spec/src/data/object.form.ts—maxLengthrow grows from 9 types to the ten (codewas missing).packages/spec/src/data/field.test.ts— new pin block#11566: 3 shape refusals (0 / −5 / 12.5, asserting specific zod issuecode+path+ message substance), 9 applicability refusals (one representative per wrongly-accepting type family incl. thesecretnear-miss, assertingcustomcode + path + message), acceptance round-trip on all ten legal types, and absence-stays-absence ontext/boolean/lookup..changeset/maxlength-bounded-string-only.md— minor (launch-window convention: accept-set narrowings ride minors, percheck-changeset-no-major.mjsand the migration registry step18's own text); names exactly what newly gets rejected.check:generated --fix, which proved exactly these three stale):content/docs/references/data/field.mdx,api-surface/data.json,export-origins/data.json.check:authorable-surfaceandcheck:skill-refswere green without regeneration — noskills/**files are touched by this PR.PM mechanism assumptions — measured
length/size→maxLength, field.zod.ts): measured — thealiasestable instrictObjectfeeds only the unknown-key rejection guidance (strictObjectError→strictUnknownKeyError);FieldSchemais.strict(), solength:/size:are refused outright and no value ever flows through an alias intomaxLength. No bypass exists; nothing to tighten there.packages/objectql/src/validation/record-validator.ts:554enforcesmaxLength/minLengthfor exactly the ten types, today. Deliberately not rewired to import the new constant in this PR (outside the claimed file surface; membership is identical — pinned on both sides).ac59744); the issue's line refs had moved as predicted.superRefinechecks (referenceVia,radio+multiple,master_detail+set_null); the.overwrite()machinery is only for default materialization, whichmaxLengthdoes not have.superRefineused accordingly.Sweep for now-rejected in-repo authors
Repo-wide scan (values + same-object type pairing + builder-style
Field.<type>({...}), acrosspackages examples skills apps content): zero spec-parsed surfaces author a malformedmaxLengthor place it on a non-ten type. All authored values are positive integers on bounded-string types. The only non-conforming shapes are deliberate consumer-defense fixtures that never pass throughFieldSchema.parse—driver-sql's #11431 tests (sql-driver-string-maxlength-varchar.test.ts:maxLength: 0/12.5, andmaxLengthonlookup/user/autonumber/secret/select, fed raw to the driver) and rest/objectql raw-shape fixtures (buildFieldMetaMap, ColumnInfovarcharshapes). Preserved verbatim per the dispatch constraint.Deferred / follow-ups (filed)
Field.scaleaccepts meaningless declarations (2.5,-1) — now that scale is enforced, malformed declarations should be refused at authoring time #8321 step18 template: mechanical delete of malformed/misplaced values + semantic re-declare prescription).packages/spec/src/migrations/registry.tsis held by the Retire the nine dead members of the plugin-manifestcontributesblock (events/menus/themes/translations/actions/drivers/fieldTypes/functions/commands) — tombstone route,kindssurvives #10724/contributes.kinds.globsis declared-but-unenforced: nothing reads it, and the file-type discovery it advertises globsfilePatternsoff a registrycontributes.kindsdoes not extend #11169 family dispatch this wave and was not touched, per the dispatch constraint. The changeset carries no**BREAKING**declaration (matching the FieldSchema acceptsdeleteBehavior: 'set_null'on amaster_detail, and the engine silently resolves it tocascade#9689 accept-set-narrowing precedent's changeset), so the ADR-0087 disposition gate does not fire on this PR; the ledger debt is tracked in the open issue instead of landing silently.minLengthstill has the pre-#11566 defect pair — validated as no more than a number, authorable on every field type #11949 —minLengthstill has the identical pre-spec:maxLengthis authorable on every field type and validated as no more than a number —maxLength: 0andmaxLength: 12.5parse cleanly #11566 defect pair (shape + base-schema applicability + both forms' stale lists). Out of ruled scope, and its lower bound (0vs1) is a judgment call — filed unassigned, not fixed here.signature/qrcodehave nomaxLengthenforcement anywhere, so they cannot join the TEXT family — a data-URI signature is refused at 255 chars and the declared bound binds nothing #11875 (open,needs-user-decision): whethersignature/qrcodejoin the TEXT family. If ruled in,BOUNDED_STRING_FIELD_TYPESis the one place the set grows.Verification
All local readings below were taken at commit
9ca3a81and re-affirmed at the current heade36b89a(changeset/ratchet/test-kind gate union re-run there, all exit 0; verdict lines quoted from each gate's own output, exit codes captured before any pipe).packages/specfield.test.ts:Test Files 1 passed (1)/Tests 184 passed (184)(includes all new pins).@objectstack/specsuite:Test Files 424 passed (424)/Tests 11288 passed (11288);pnpm --filter @objectstack/spec typecheckexit 0 (chain includescheck:test-typecheck: OK).git restore --source=origin/main --worktreeon field.zod.ts; mutation grep-confirmed on disk —BOUNDED_STRING_FIELD_TYPEScount 0 — no dist in the loop, spec tests import./field.zodsource): exactly the predicted 12 reds — the 3 shape pins + the 9 applicability pins — everything else green (Failed Tests 12). Restore leg:git checkout HEAD -- <path>, grep count back to 4,git statusclean vs HEAD (byte-identity with the tree the green runs measured).@objectstack/objectqlfull suiteTest Files 232 passed (232)/Tests 4113 passed (4113)— record-validator + all fixtures green against the rebuilt specdist;@objectstack/driver-sqlfull suiteTest Files 128 passed | 8 skipped (136)/Tests 1981 passed | 114 skipped (2095)(skips are the live PG/MySQL dialect matrix, no DB in the container) — schema-drift and the driver-sql: the string family ignoresmaxLengthtoo — a declared 1024 becomes varchar(255) and refuses legitimate writes #11431 string-maxlength-varchar consumer-defense fixtures all green and preserved verbatim;@objectstack/lintfull suiteTest Files 81 passed (81)/Tests 2294 passed (2294)— its RHS-rule anti-vacuity census re-derived 47 → 44 (commite36b89a): the field form'smaxLengthrow went from a 3-way==chain to oneinlist, and the pin's own anchor note placesin-list literals outside the RHS rule; the count is a corpus census, not a style rule (the Field designer form still offers 'Set null' for a master_detail's deleteBehavior — a choice #9689 makes a publish-time rejection #11410 precedent one test below).node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack, derivation stampedat commit 9ca3a81): derived families run locally, each exit 0 with its own verdict line —check-adr-0087-registration("this PR adds no declared-breaking changeset"),check-changeset-no-major("nomajorbump"),check-empty-changeset,check:spec-parsed-alias,check:cross-package-test-inputs("all declared"),check:engine-double-contract,check:where-matcher("none new"),check:query-options-erasure("baseline key set verified against ac59744").check:i18n(convention-triggered by the two form edits) initially refused loudly — workspace CLI not built, "Nothing was checked" — afterturbo run build --filter=@objectstack/cli:check-i18n-bundles: OK (9 package(s) — all bundles in sync, no undeclared authoring keys)(the visibleWhen respell moves no extracted string; helpText unchanged). Declared narrowing: repo-widepnpm lintandcheck:type-check-debt --re-measureare left to CI (CI runs the farm regardless); spec's own typecheck + test-typecheck accounting ran green above.Generated by Claude Code
Generated by Claude Code