Uh oh!
There was an error while loading. Please reload this page.
fix(spec): minLength is a positive integer, authorable only on bounded-string field types; register the deferred #11566 maxLength ADR-0087 entry - #12191
Conversation
…d-string field types Issue #11949 context (maintainer ruling 2026-08-25, option B): the #11566 template applies in full. Shape tightens to z.number().int().min(1) — the lower bound is 1, so minLength: 0 is refused loudly ("no minimum" is expressed by omitting the key, not by declaring a vacuous truth). Applicability converges on BOUNDED_STRING_FIELD_TYPES via a superRefine twin of maxLength's; both authoring-form rows align to the same set (field.form.ts previously showed the key for 3 types, object.form.ts for 9). Card relationship is declared in the PR body, not here. Ruled pre-step: corpus + generator grep for minLength: 0 / negative / fractional output — zero hits repo-wide, so no cleanup rides the PR and no customer-facing generator fork exists. - field.zod.ts: shape + applicability superRefine + set doc comment - field.form.ts / object.form.ts: minLength rows aligned to the ten - field.test.ts: refusal envelopes (path + code + message) for 0 / -5 / 12.5 and for nine wrong-type representatives; byte-identical round-trip on all ten bounded-string types; minLength: 1 lower-bound pin; absence stays absent - lint RHS-rule census re-derived 44 -> 41 (3 fewer == literal comparisons after the in-list respell of the field-form row — the #11989 precedent) - D3 semantic entry field-min-length-malformed-or-misplaced-refused (major-18 one-file shard + gen:migration-registry) - changeset: @objectstack/spec minor (launch-window convention), adr-0087: registered Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NDGG54XF5gbTLdQzCtnaVV
…DR-0087 ledger Issue #11950 context: the #11566 enforcement (PR #11989) shipped without its ADR-0087 ledger entry — the migrations registry was serialized behind an in-flight change in that wave — and this commit lands the missing half as a major-18 D3 semantic entry, following the #8321 scale/precision template. Semantics are authoritative to PR #11989's actual diff: refused shapes (0 / negative / non-integer on any type), refused placement (any value outside the ten-member BOUNDED_STRING_FIELD_TYPES set as of that landing), forms convergence, byte-identical round-trip for well-formed declarations. The entry separates the mechanical half (misplaced keys were inert by construction — the validator's bounded-string branch never read them) from the judgment half (malformed values on bounded-string types WERE consumed by the raw comparison — maxLength: 0 accepted only empty strings, a negative value refused every write — so the author must re-declare the bound they meant). No accept/reject behaviour moves in this commit. Card relationship is declared in the PR body, not here. - D3 semantic entry field-max-length-malformed-or-misplaced-refused (major-18 one-file shard + gen:migration-registry) - changeset: @objectstack/spec minor, adr-0087: registered Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NDGG54XF5gbTLdQzCtnaVV
…1949-minlength-defect-pair Semantic merge, both intents stacked: minLength rows/describe/superRefine message and tests move with the set exactly as #12119 moved maxLength's — forms at the twelve, message enumerates the set itself (the #12017 two-copies lesson), wrongTypes gains 'color', accept pin covers signature/qrcode. Generated artifacts deferred to the regeneration commit per the os-regen procedure. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NDGG54XF5gbTLdQzCtnaVV
Discharges the os-regen deferral the merge commit recorded: registry regenerated from the (merge-updated) semantic shards; field.mdx rendered from the merged schema (minLength row at the twelve-member set). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NDGG54XF5gbTLdQzCtnaVV
…nlength-defect-pair
📓 Docs Drift CheckThis PR changes 1 package(s): 14 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 1 release-owned page(s) also name something this change touched. These are read-only:
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 8403770dbb5c2a64bcb4c3b390246d6a5b629c52 && git checkout 8403770dbb5c2a64bcb4c3b390246d6a5b629c52
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 494279cb31f1d92adab959763085e19c923a8652 6a2b4ed83e3e1f9573f7dbd5acc83bb2bf466860 && git checkout -B drift-repro 494279cb31f1d92adab959763085e19c923a8652 && git merge --no-ff 6a2b4ed83e3e1f9573f7dbd5acc83bb2bf466860
node scripts/docs-audit/affected-docs.mjs --json 494279cb31f1d92adab959763085e19c923a8652
|
Uh oh!
There was an error while loading. Please reload this page.
) gen:migration-registry (byte-identical — main's generated regions already carried the #12191 semantic entries), rebuild restoring the ui/ObjectGridProps:defaultSort [RETIRED] surface mark, gen:docs restoring the retired reference row. Sibling entries and implementation bodies asserted present (field-max/min-length semantic entries, object editMode, minLength). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NDGG54XF5gbTLdQzCtnaVV
Fixes#11949
Fixes#11950
Family fold (declared in the #11949 claim): one branch, one PR, one commit per member. Draft by dispatch contract — Clause-② yes on the chain head, so the PR stays draft for the
needs:contract-reviewchain; no AI seat flips it ready.Member 1 — #11949:
minLengthjoins the bounded-string tightening (commit 1)Maintainer ruling (2026-08-25, decision-inbox batch 8, verbatim 「同意」 on recommendation B): shape
z.number().int().min(1); the lower bound is 1 —minLength: 0is refused loudly, "no minimum" is expressed by omitting the key; applicability restricted toBOUNDED_STRING_FIELD_TYPES; both authoring forms' rows aligned.packages/spec/src/data/field.zod.ts— shape tightened; applicability superRefine (twin ofmaxLength's spec:maxLengthis authorable on every field type and validated as no more than a number —maxLength: 0andmaxLength: 12.5parse cleanly #11566 check, reading the same exported set); set doc comment extended.packages/spec/src/data/field.form.ts—minLengthrow respelled from a 3-type==chain to the in-list matching themaxLengthrow beside it.packages/spec/src/data/object.form.ts—minLengthrow aligned to the same set (was 9 types,codemissing).packages/spec/src/data/field.test.ts— refusal envelopes asserting path + code + message for0/-5/12.5(too_small / too_small / invalid_type at[minLength]) and for ten wrong-type representatives (custom issue naming the legal set and the offending type); byte-identical round-trip (minLength: 2in →2out) on every bounded-string type;minLength: 1lower-bound pin; absence stays absent (no default materializes).packages/lint/src/validate-predicate-path-refs.test.ts— RHS-rule corpus census re-derived 44 → 41 (3 fewer==literal comparisons after the in-list respell; the exact fix(spec): maxLength is a positive integer, authorable only on bounded-string field types #11989 precedent, which took it 47 → 44).field-min-length-malformed-or-misplaced-refused(major-18 one-file shard +gen:migration-registry).@objectstack/specminor (launch-window convention),adr-0087: registered.Ruled pre-step: corpus & generator sweep (gate condition for landing)
The ruling required a grep of the corpus and generators for existing
minLength: 0/ negative / fractional output before landing, with a hard stop if a shipped customer-facing generator emitsminLength: 0.minLength: 0|-N|N.Mover*.ts,tsx,js,json,yml,yaml,md,mdx, node_modules/dist excluded). No cleanup rides the PR; no generator forks back — the stop condition did not fire.minLengthoccurrence):examples/**— no field-levelminLengthat all; spec fixtures (field.test.ts) —textfields, legal;packages/rest/src/import-dryrun-parity.test.ts—text+minLength: 3, legal;packages/rest/src/export-format.test.ts— alookupfixture deliberately carrying the eight retired presentation keys (minLength: 2among them) to pin that they do NOT travel into the export meta map — it never crossesFieldSchema.parse, i.e. outside this rule's consumption radius, and fix(spec): maxLength is a positive integer, authorable only on bounded-string field types #11989 left itsmaxLength: 20twin untouched for the same reason; fast-checkfc.string({ minLength })sites — a different API entirely;skills/objectstack-data/rules/field-types.mddocumentsminLengthonly ontext/textarea/password— all inside the set, no edit needed (noskills/**diff in this PR).Member 2 — #11950: the deferred #11566
maxLengthADR-0087 ledger entry (commit 2)The #11566 enforcement (PR #11989) shipped without its ledger entry (registry serialized behind an in-flight change in that wave; deferral recorded in that PR's own changeset). This commit registers it: D3 semantic entry
field-max-length-malformed-or-misplaced-refused(major-18 shard + regeneration), following the #8321scale/precisiontemplate, semantics authoritative to PR #11989's actual diff.One deliberate correction to the card body's wording, measured against
record-validator.ts: "nothing ever enforced the malformed value" is true for misplaced keys (the validator's bounded-string branch never read them — inert by construction) but NOT for malformed values on bounded-string types — the raw comparison consumed them (maxLength: 0accepted only empty strings; a negative value refused every write;12.5behaved as "at most 12"). The entry states the measured truth and routes those to the judgment half (re-declare the intended bound), which is also why deleting them is not sold as behaviour-preserving. Same split applied to theminLengthentry (0/negative genuinely unfailable; fractional consumed as "at least the next integer up").Clause-② for this member: no — the entry registers an already-landed narrowing; no accept/reject behaviour moves in commit 2 (no D2 conversion is added; the load path is untouched).
Dispatch-premise timeline (both members)
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 landed / 12-member set" interaction note was not yet true at this branch's base (577fabf): the set had ten members,record-validator.tsstill hand-coded its list, andsignature/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 was open with PR feat(data): signature and qrcode join the bounded-string family end to end #12119 in flight. Member 1 was first implemented set-driven at ten (the superRefine reads the set; enforcement matched because the validator's hand list equalled it).origin/mainstacks both intents:minLengthmoves with the set exactly as that PR movedmaxLength— both form rows at the twelve, the describe strings name signature/qrcode, the superRefine message now enumerates the set itself (adoptingsignature/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's driver-sql's varchar-sizing type list and spec's BOUNDED_STRING_FIELD_TYPES must now agree, and nothing pins them — the same three-lists-disagree defect #11566 was filed for, one layer down #12017-two-copies fix rather than a prose copy), tests pin the twelve and addcolorto the wrong-type representatives. The changeset and both ledger entries state the twelve-member landing truth (themaxLengthentry keeps the historically-accurate ten of fix(spec): maxLength is a positive integer, authorable only on bounded-string field types #11989's own landing, with thesignature/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 growth noted).Verification
Full suite at the post-#11875 merged tree (branch commit
1f68923, identical spec surface carried to final head5f50d3e— the second merge brought no overlap with this diff):pnpm --filter @objectstack/spec test— Test Files 426 passed (426) / Tests 11364 passed (11364) (includes the new spec:minLengthstill has the pre-#11566 defect pair — validated as no more than a number, authorable on every field type #11949 block: refusal envelopes for0/-5/12.5and ten wrong-type representatives incl.color, twelve-type byte-identical round-trip,minLength: 1lower-bound pin, absence pin).pnpm --filter @objectstack/spec typecheck— green (same lock run, command-exit 0).pnpm --filter @objectstack/objectql exec vitest run src/validation/record-validator.test.ts— 92 passed (the write-seam consumer, now set-driven post-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; no validator edit needed forminLength— the branch already appliesmin_lengthto every set member).pnpm --filter @objectstack/lint exec vitest run src/validate-predicate-path-refs.test.ts— 54 passed; the RHS-census equality holds at the re-derived 41.pnpm --filter @objectstack/spec check:generated— gate's own line at final head5f50d3e: "✓ All 14 generated artifacts are up to date."5f50d3e, re-run after the lastorigin/mainmerge — every family derived byscripts/pm/dispatch-gates.mjs(no paths; stderr stamps repo + commit) plus the dispatch-named set, all green: changeset-gate-self-tests · cross-package-test-inputs · doc-anchors · doc-authoring · doc-formula-expressions · doc-security-posture · docs-audit-scope · docs-redirects · empty-state · liveness · merge-driver · objectui-changeset · published-files · published-readme-links · query-options-erasure · quick-reference-counts · react-page-adapter-contract · role-word · slot-lookup · spec-parsed-alias · strictness-ledger · test-source-alias · type-source-resolution · variant-docs · adr-0087-registration · changeset-no-major · nul-bytes · ci-filter-parity (spelling note: the runnable form isnode scripts/check-ci-filter-parity.mjs; there is nopnpm check:ci-filter-parityscript).pnpm lintand the full multi-package farm are CI's runs; local verification was scoped to the affected packages plus every path-derived gate above (declared narrowing — CI runs the farm on this PR).H17 note: zero currency surface in this diff (all
currencymentions ingit diff origin/main...HEADare unchanged context lines);currencyConfig/currency-fraction-digits.tsuntouched.Out-of-scope finding filed: #12174 (ui
FormFieldBaseSchemacarries the same pre-#11566 constraint-shape defect pair; applicability story differs — measured readers needed before template vs enforce-or-remove).Generated by Claude Code