Skip to content

fix(spec): maxLength is a positive integer, authorable only on bounded-string field types - #11989

Merged
os-support-ai merged 2 commits into
mainfrom
claude/issue-11566-maxlength-shape-applicability
Aug 25, 2026
Merged

fix(spec): maxLength is a positive integer, authorable only on bounded-string field types#11989
os-support-ai merged 2 commits into
mainfrom
claude/issue-11566-maxlength-shape-applicability

Conversation

@claude

@claudeclaudeBot commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

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/scale precedent 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.ts
    • maxLength is now z.number().int().min(1)0, negatives and non-integers are refused at parse with the same issue codes as the spec: Field.scale accepts 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'].
    • New exported vocabulary BOUNDED_STRING_FIELD_TYPES (the validator's ten: text textarea email url phone password markdown html richtext code), following the REFERENCE_VALUE_TYPES / STRUCTURED_JSON_TYPES house pattern.
    • New superRefine check (the referenceVia-on-text house pattern for type-scoped key refusal): an authored maxLength on any type outside the ten is refused with a custom issue at ['maxLength'] whose message names the legal set, the offending type, and the fix. maxLength has 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.tsmaxLength row's visibleWhen grows from 3 types (text/textarea/email) to the ten.
  • packages/spec/src/data/object.form.tsmaxLength row grows from 9 types to the ten (code was missing).
  • packages/spec/src/data/field.test.ts — new pin block #11566: 3 shape refusals (0 / −5 / 12.5, asserting specific zod issue code + path + message substance), 9 applicability refusals (one representative per wrongly-accepting type family incl. the secret near-miss, asserting custom code + path + message), acceptance round-trip on all ten legal types, and absence-stays-absence on text/boolean/lookup.
  • .changeset/maxlength-bounded-string-only.md — minor (launch-window convention: accept-set narrowings ride minors, per check-changeset-no-major.mjs and the migration registry step18's own text); names exactly what newly gets rejected.
  • Regenerated (via 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-surface and check:skill-refs were green without regeneration — no skills/** files are touched by this PR.

PM mechanism assumptions — measured

  • Alias map (length/sizemaxLength, field.zod.ts): measured — the aliases table in strictObject feeds only the unknown-key rejection guidance (strictObjectErrorstrictUnknownKeyError); FieldSchema is .strict(), so length:/size: are refused outright and no value ever flows through an alias into maxLength. No bypass exists; nothing to tighten there.
  • Record-validator list: measured — packages/objectql/src/validation/record-validator.ts:554 enforces maxLength/minLength for 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).
  • fix(spec): materialize deleteBehavior only on reference field types #11903 same-file churn: worked from post-merge main (ac59744); the issue's line refs had moved as predicted.
  • deleteBehavior as the scoping house pattern: measured — the type-scoped refusals in this file are superRefine checks (referenceVia, radio+multiple, master_detail+set_null); the .overwrite() machinery is only for default materialization, which maxLength does not have. superRefine used accordingly.

Sweep for now-rejected in-repo authors

Repo-wide scan (values + same-object type pairing + builder-style Field.<type>({...}), across packages examples skills apps content): zero spec-parsed surfaces author a malformed maxLength or 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 through FieldSchema.parsedriver-sql's #11431 tests (sql-driver-string-maxlength-varchar.test.ts: maxLength: 0/12.5, and maxLength on lookup/user/autonumber/secret/select, fed raw to the driver) and rest/objectql raw-shape fixtures (buildFieldMetaMap, ColumnInfo varchar shapes). Preserved verbatim per the dispatch constraint.

Deferred / follow-ups (filed)

Verification

All local readings below were taken at commit 9ca3a81 and re-affirmed at the current head e36b89a (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/spec field.test.ts: Test Files 1 passed (1) / Tests 184 passed (184) (includes all new pins).
  • Full @objectstack/spec suite: Test Files 424 passed (424) / Tests 11288 passed (11288); pnpm --filter @objectstack/spec typecheck exit 0 (chain includes check:test-typecheck: OK).
  • Reverse verification (from the committed state, git restore --source=origin/main --worktree on field.zod.ts; mutation grep-confirmed on disk — BOUNDED_STRING_FIELD_TYPES count 0 — no dist in the loop, spec tests import ./field.zod source): 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 status clean vs HEAD (byte-identity with the tree the green runs measured).
  • Consumers (required readings): @objectstack/objectql full suite Test Files 232 passed (232) / Tests 4113 passed (4113) — record-validator + all fixtures green against the rebuilt spec dist; @objectstack/driver-sql full suite Test 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 ignores maxLength too — a declared 1024 becomes varchar(255) and refuses legitimate writes #11431 string-maxlength-varchar consumer-defense fixtures all green and preserved verbatim; @objectstack/lint full suite Test Files 81 passed (81) / Tests 2294 passed (2294) — its RHS-rule anti-vacuity census re-derived 47 → 44 (commit e36b89a): the field form's maxLength row went from a 3-way == chain to one in list, and the pin's own anchor note places in-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).
  • Gate family re-derived from the actual diff (node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack, derivation stamped at 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 ("no major bump"), 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" — after turbo 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-wide pnpm lint and check:type-check-debt --re-measure are 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

…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
@github-actionsgithub-actionsBot added size/m documentation Improvements or additions to documentation protocol:data tests tooling labels Aug 25, 2026
@github-actions

github-actionsBot commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec, touching 3 documentable anchor(s). ⚠️2 changed file(s) yielded no anchor (packages/spec/api-surface/data.json, packages/spec/export-origins/data.json), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

28 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json d63b01436035f0866df8c22156e26ab2a064580d.

1 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 2 changed file(s) yielded no anchor (packages/spec/api-surface/data.json, packages/spec/export-origins/data.json) — pages documenting those are invisible to this run
  • 1 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 45 of 222 client-bound route-ledger rows — the other 177 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 126 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json d63b01436035f0866df8c22156e26ab2a064580dpackageMentionDocs.

Which tree this was computed on

This run read content/docs from 478e98476fe562d06dea9297d29b767dd0728cd5 — the merge of head e36b89a0f37d61cde9ff8937de55544e46250e69 into base d63b01436035f0866df8c22156e26ab2a064580d, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# 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

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs d63b01436035f0866df8c22156e26ab2a064580d → pass the list as
args.docs, on the commit named under Which tree this was computed on.

…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
@os-support-ai
os-support-ai marked this pull request as ready for review August 25, 2026 04:32
@os-support-ai
os-support-ai added this pull request to the merge queueAug 25, 2026
Merged via the queue into main with commit 2cc7122Aug 25, 2026
36 checks passed
@os-support-ai
os-support-ai deleted the claude/issue-11566-maxlength-shape-applicability branch August 25, 2026 04:51
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationprotocol:datasize/mteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

spec: maxLength is authorable on every field type and validated as no more than a number — maxLength: 0 and maxLength: 12.5 parse cleanly

2 participants

@os-support-ai@claude