Repository navigation
signature / qrcode have no maxLength enforcement anywhere, so they cannot join the TEXT family — a data-URI signature is refused at 255 chars and the declared bound binds nothing #11875
Description
Activity
Triage: routed to the decision inbox (
needs-user-decision+domain:engine, type Bug — the data-URI refusal at 255 chars is live on the published face, but every repair direction shapes a public contract, which is the manual floor). 四维分析(从业务角度):① 项目长远合理性:#11794 立了不变量「进 TEXT 家族 = 写缝强制 maxLength」。选项 1(写缝加界 + 两类型入 TEXT)缩小特例、declared = enforced;选项 3(裁 maxLength 对这两类型不适用,发布期拒绝 + TEXT)同样闭合、但走 ADR-0049 退役流程;选项 2(varchar(maxLength))让「未声明界的 signature」继续在 255 被拒,且强制与否取决于是否恰好有索引 —— 加深同一声明的方言分叉,最差。
② 实际业务拉动:真实存在 —— field-zoo 自己就给 signature 写 data-URI PNG,在 PG(22001)与 MySQL(ER_DATA_TOO_LONG)双双实测被拒。签名/二维码是常见业务字段,这是用户今天就撞的已发布面缺陷。
③ 防 AI 写代码犯错:选项 1 的写缝响亮拒绝优于列上限静默截断;「maxLength 声明了却无人兑现」正是声明即须强制的反例 —— 今天Field.signature({ maxLength: 64 })解析、发布、绑定为零。
④ 创业阶段不扩散需求:选项 1 只是把两个类型加进既有 string 分支,零新面;选项 3 义务更小(少两个可声明键)但要走退役流程。secret/color不随裁 —— secret 存的是 sys_secret ref(ADR-0100),color 构造上就短。推荐 1(写缝给 signature/qrcode 加 max_length 分支,然后按 #11794 的既定路线入 TEXT 家族);若维护者认为 maxLength 对这两类型本无意义,则 3。⛔ 不推荐 2。
置信缺口:存量部署里是否有作者真的声明过 signature/qrcode 的 maxLength(census 未跑);两类型入 TEXT 后对 #11374 keyed-and-bounded 规则的交互只在卡内推理、未实测。<!-- os-decision-facets -->
Generated by Claude Code
os-support-ai commented
on Aug 25, 2026 CollaboratorMore actionsMaintainer ruling — option 1: the write seam enforces
maxLengthforsignature/qrcode, then both join the TEXT familySource: maintainer, 2026-08-25, live PM chat (decision-inbox batch 6 review, session
session_01KWRU3s15AJz7PGW7a7wdCh), verbatim: 「其他同意」 — accepting the batch's presented recommendation for this card: option 1.signatureandqrcodejoinvalidateRecord'smax_lengthbranch (declared bound refuses loudly at the write seam), and then move into the TEXT family under exactly the invariant driver-sql:richtextis emitted as varchar(255) while itsmarkdown/htmlsiblings get TEXT — a rich-text body is capped at 255 characters #11794 established — a TEXT column is permitted precisely because the seam enforces the declared bound. The live defect (a data-URI signature refused at 255 chars on both measured dialects) goes away; declared = enforced.- Option 2 (varchar(maxLength)) is rejected as presented: an undeclared bound would still refuse at 255, and enforcement would depend on whether an index happens to key the column — dialect/shape-divergent enforcement of one declaration.
- Option 3 remains the recorded alternative if implementation shows
maxLengthgenuinely meaningless for these types — but the ruling is 1; a flip to 3 would be a fork back, not a silent pivot. secretandcolorare explicitly not covered by this ruling (opaque ref per ADR-0100; short by construction).- Interaction check carried from the card: verify against the driver-sql: the platform-objects schema does not sync onto MySQL — unbounded string fields become TEXT, which MySQL refuses to index #11374 keyed-and-bounded rule on the real matrices, and extend
sql-driver-11794-richtext-text-family.test.ts's "STILL-OPEN half" cases into the closed shape.
Discipline: Clause-② yes (accept/reject behaviour changes in both directions: long values become storable, over-bound values become refused);
CONTRACT_REVIEW_TIER.State:
needs-user-decision→pm:queuein the same write;domain:enginestands.
Generated by Claude Code
Claim: PM loop (
domain:engineseat)
Session:session_01W6HFzyH98W1YaQXhJUJt6o
Branch:claude/issue-11875-signature-qrcode-bounded-text-family
Worktree:objectstack-issue-11875
Domain:domain:engine
Container & model: M,mode:subagent,CONTRACT_REVIEW_TIER
Clause-②: yes — both limbs. The maintainer ruling already declared the declaration limb (accept/reject moves in both directions). The path limb fires too, for the reason below.⚠️ The card's premise sentence is stale — read this before startingThe card states: "
maxLengthis a plain optional key onFieldSchema(field.zod.ts), admitted on every field type. SoField.signature({ maxLength: 64 })parses, publishes, and binds nothing."That was true when filed (2026-08-24). It is no longer true on
origin/main. #11566/#11989 landed after it:packages/spec/src/data/field.zod.ts:123—BOUNDED_STRING_FIELD_TYPES=text, textarea, email, url, phone, password, markdown, html, richtext, code. Neithersignaturenorqrcodeis a member.packages/spec/src/data/field.zod.ts:1649— asuperRefinethat refusesmaxLengthon any type outside that set.
So
Field.signature({ maxLength: 64 })is refused at the authoring seam today. The defect's shape has changed: it is no longer "a bound that binds nothing", it is "a bound that cannot be declared at all, on a type whose real values are routinely past 255".⇒ Option 1 therefore requires a
packages/spec/src/**edit (adding both types to the set), which is the clause ② path limb. Note the refusal message at:1655-1657hand-enumerates the ten types in prose — a second copy of the same list that must move with it or it drifts on day one. This is the #12017 family; do not leave it inconsistent.The live physical defect the card measured (data-URI signature refused at 255 on PG 22001 / MySQL
ER_DATA_TOO_LONG) is unaffected by this and still stands — re-measure it, don't recall it.Serial constraints cleared
Measured by symbol on
origin/main(packages/drivers/driver-sql/src/sql-driver.ts, 15,248 lines):- This card's band:
13275–13900—declaredVarcharLength(:13321),keyableTextLength(:13330),varcharColumnChars(:13468), and thecreateColumn/alter call sites at:13536 :13773 :13864. - ⛔ driver-sql: a declared field named
id/created_at/updated_atis silently discarded by initObjects — declared type, length and constraints all ignored with no diagnostic #12015 is dispatched in parallel on the8600–9130band (builtinColumnsat:8629 :8645 :9078 :9100 :9127). Do not touch it. If your change needs a line in that band, stop and report — that is a serial breach, not a merge to resolve. - Second surface
packages/objectql/src/validation/record-validator.ts(max_lengthat:557) — no other open PR touches it. ⚠️ Fence: PR feat(spec): declare the audit door and catch the delete request schema up — both meta-door casts retired #12003 is open onpackages/spec/api-surface/api.json,authorable-surface/api.json,export-origins/api.json,json-schema.manifest/api.json. Adding members to aReadonlySet<string>should not move any of them (membership is not in the type). If a regeneration does rewrite one of those four files, stop and report rather than landing a conflict against feat(spec): declare the audit door and catch the delete request schema up — both meta-door casts retired #12003.
Prior art to read first, not recall: #11876 landed —
richtext/codeare already in the TEXT family onmain, so the card's "PR #11876 (open)" note is resolved.
Generated by Claude Code
- added a commit that references this issue
on Aug 25, 2026 { "issue": 11875, "status": "done", "branch": "claude/issue-11875-signature-qrcode-bounded-text-family", "pr": "https://github.com/objectstack-ai/objectstack/pull/12119", "premise_still_valid": true, "summary": "Ruled option 1 implemented at head 5311cee259 (draft PR #12119, base 8450eeacb6): signature/qrcode join BOUNDED_STRING_FIELD_TYPES in field.zod.ts (the claim comment's stale-premise warning re-measured and confirmed on this tree — the #11566 superRefine did refuse maxLength on them, so the spec edit was required; the refusal message now enumerates the set itself instead of a prose copy, and both authoring forms move with it); the record-validator's max_length/min_length branch now reads the spec set instead of a hand-copied ten-type list, so declared bounds on both types refuse loudly with field-named ADR-0112 envelopes; createColumn and its varcharColumnChars mirror move both types from catch-all varchar(255) into the TEXT family under the #11794 invariant. secret/color untouched per the ruling. The 11794 test file's STILL-OPEN half is extended into the closed shape (same data-URI measurement, direction flipped) plus a live keyed-and-bounded case; #12015's 8600-9130 band untouched; PR #12003's four fenced api.json files unchanged after regeneration (only gen:docs was stale). Changeset minor x3 (spec/objectql/driver-sql), not declared-breaking, no ADR-0087 marker owed per the gate's own verdict.", "tests": "All at head 5311cee259 (single commit; union derived at it). Live re-measurement on PG 16 + MySQL 8.0.46 STRICT_TRANS_TABLES via information_schema.COLUMNS read by a separate query: post-change body_sig/body_qr = text on both dialects, 1000-char data-URI accepted and round-trips byte-identically, c_color control refused by the server (22001 / ER_DATA_TOO_LONG); keyed signature/qrcode maxLength:64 = varchar(64) in the catalog, 64-char boundary accepted, 65 refused by the server on both dialects (#11374 interaction: enforcement of the declared bound never depends on the index — the write seam holds it everywhere, keyed columns also enforce it physically). Validator boundary both directions in record-validator.test.ts (+10 tests: 100>64 refused max_length {maxLength:64,actual:100}, 64 accepted, 65 refused, update covered, min_length travels, bound-less data-URI accepted, secret/color carve-out controls). Suites: spec 'Test Files 425 passed / Tests 11322 passed'; objectql '232 / 4127 passed'; driver-sql full suite with both live servers under CI's skewed zones 'Test Files 139 passed / Tests 2833 passed | 1 skipped'. Typecheck turbo 14 successful exit 0; repo-wide pnpm lint (eslint . --no-inline-config) exit 0 full run, no narrowing. Gates: dispatch-gates.mjs --repo objectstack-ai/objectstack provenance-stamped at 5311cee259; all 40 matched + 7 convention families ran locally, exits captured pre-pipe, all 0 (adr-0087: 'this PR adds no declared-breaking changeset'; i18n: '9 package(s) — all bundles in sync'; nul-bytes: 'no raw ASCII control bytes'; type-check-debt --re-measure on the fully built closure: 'none above its recorded number'). Ablations from the committed state, predictions written first, mutations proved by anchored grep counts, restores verified by git hash-object == HEAD blob + clean porcelain; both consuming suites import their subject from source (./record-validator.js, ../src/index.js) so no dist rebuild leg applies: write-seam leg (set-read 1->0, hand-list 0->1) = predicted 6 red / 4 green; storage leg (case labels 2->0 each, 2 markers) = predicted-per-test 9 red / 2 green, its red leg reproducing the card's live defect verbatim (PG 'value too long for type character varying(255)', MySQL ER_DATA_TOO_LONG). Storage-leg take 1 was VOID (replacement swallowed the block-opening brace, transform error) — discarded and redone brace-preserving.", "open_questions": [], "out_of_scope_findings": [ "filed as #12117 (finding label): type-compat.ts CANONICAL_TO_FIELD.text.exact omits signature/qrcode, which this change makes the driver's own emitted TEXT shape for them (binary.exact still lists signature); pre-filing REST issue search unavailable in this session (403 'GitHub access is not enabled') — local grep found no covering card, filed with a dedup note", "already filed as #11949 (not re-filed): minLength twin defect (shape + every-type applicability + 3-vs-9 form lists); left an interaction note there — the validator branch now reads BOUNDED_STRING_FIELD_TYPES, so a minLength ruling needs only a spec-side superRefine, and the set is twelve members now" ] }
Generated by Claude Code
os-dev-report
(Authoritative copy — the marker on the previous comment was stripped by the body sanitizer, making it invisible to the report scan; same JSON.)
{ "issue": 11875, "status": "done", "branch": "claude/issue-11875-signature-qrcode-bounded-text-family", "pr": "https://github.com/objectstack-ai/objectstack/pull/12119", "premise_still_valid": true, "summary": "Ruled option 1 implemented at head 5311cee259 (draft PR #12119, base 8450eeacb6): signature/qrcode join BOUNDED_STRING_FIELD_TYPES in field.zod.ts (the claim comment's stale-premise warning re-measured and confirmed on this tree — the #11566 superRefine did refuse maxLength on them, so the spec edit was required; the refusal message now enumerates the set itself instead of a prose copy, and both authoring forms move with it); the record-validator's max_length/min_length branch now reads the spec set instead of a hand-copied ten-type list, so declared bounds on both types refuse loudly with field-named ADR-0112 envelopes; createColumn and its varcharColumnChars mirror move both types from catch-all varchar(255) into the TEXT family under the #11794 invariant. secret/color untouched per the ruling. The 11794 test file's STILL-OPEN half is extended into the closed shape (same data-URI measurement, direction flipped) plus a live keyed-and-bounded case; #12015's 8600-9130 band untouched; PR #12003's four fenced api.json files unchanged after regeneration (only gen:docs was stale). Changeset minor x3 (spec/objectql/driver-sql), not declared-breaking, no ADR-0087 marker owed per the gate's own verdict.", "tests": "All at head 5311cee259 (single commit; union derived at it). Live re-measurement on PG 16 + MySQL 8.0.46 STRICT_TRANS_TABLES via information_schema.COLUMNS read by a separate query: post-change body_sig/body_qr = text on both dialects, 1000-char data-URI accepted and round-trips byte-identically, c_color control refused by the server (22001 / ER_DATA_TOO_LONG); keyed signature/qrcode maxLength:64 = varchar(64) in the catalog, 64-char boundary accepted, 65 refused by the server on both dialects (#11374 interaction: enforcement of the declared bound never depends on the index — the write seam holds it everywhere, keyed columns also enforce it physically). Validator boundary both directions in record-validator.test.ts (+10 tests: 100-char vs 64 refused max_length {maxLength:64,actual:100}, 64 accepted, 65 refused, update covered, min_length travels, bound-less data-URI accepted, secret/color carve-out controls). Suites: spec 'Test Files 425 passed / Tests 11322 passed'; objectql '232 / 4127 passed'; driver-sql full suite with both live servers under CI's skewed zones 'Test Files 139 passed / Tests 2833 passed | 1 skipped'. Typecheck turbo 14 successful exit 0; repo-wide pnpm lint (eslint . --no-inline-config) exit 0 full run, no narrowing. Gates: dispatch-gates.mjs --repo objectstack-ai/objectstack provenance-stamped at 5311cee259; all 40 matched + 7 convention families ran locally, exits captured pre-pipe, all 0 (adr-0087: 'this PR adds no declared-breaking changeset'; i18n: '9 package(s) — all bundles in sync'; nul-bytes: 'no raw ASCII control bytes'; type-check-debt --re-measure on the fully built closure: 'none above its recorded number'). Ablations from the committed state, predictions written first, mutations proved by anchored grep counts, restores verified by git hash-object == HEAD blob + clean porcelain; both consuming suites import their subject from source (./record-validator.js, ../src/index.js) so no dist rebuild leg applies: write-seam leg (set-read 1 to 0, hand-list 0 to 1) = predicted 6 red / 4 green; storage leg (case labels 2 to 0 each, 2 markers) = predicted-per-test 9 red / 2 green, its red leg reproducing the card's live defect verbatim (PG 'value too long for type character varying(255)', MySQL ER_DATA_TOO_LONG). Storage-leg take 1 was VOID (replacement swallowed the block-opening brace, transform error) — discarded and redone brace-preserving.", "open_questions": [], "out_of_scope_findings": [ "filed as #12117 (finding label): type-compat.ts CANONICAL_TO_FIELD.text.exact omits signature/qrcode, which this change makes the driver's own emitted TEXT shape for them (binary.exact still lists signature); pre-filing REST issue search unavailable in this session (403 'GitHub access is not enabled') — local grep found no covering card, filed with a dedup note", "already filed as #11949 (not re-filed): minLength twin defect (shape + every-type applicability + 3-vs-9 form lists); left an interaction note there — the validator branch now reads BOUNDED_STRING_FIELD_TYPES, so a minLength ruling needs only a spec-side superRefine, and the set is twelve members now" ] }
Generated by Claude Code
ACCEPT — PR #12119. Reviewed at
CONTRACT_REVIEW_TIER, so the accept/reject transitions were held to the bar rather than taken on the report's word.The stale premise I flagged at dispatch was real, and the fix went one better than asked
I warned that the card's "
maxLengthis a plain optional key admitted on every field type" had been retired by #11566/#11989. The dev re-measured on its own tree and confirmed it: thesuperRefinedid refusemaxLengthon both types, so the spec edit was required, not optional. Verified on the branch:BOUNDED_STRING_FIELD_TYPES = text, textarea, email, url, phone, password, markdown, html, richtext, code, signature, qrcode12 members.
secretandcolorcorrectly absent — the ruling's carve-outs hold.The second hand-maintained copy I flagged is gone by construction, not by being updated. The refusal message used to spell the ten types in prose; it now derives them:
`${[...BOUNDED_STRING_FIELD_TYPES].map((t) => `'${t}'`).join(', ')} — `And the write seam did the same thing —
record-validator.tsreplaced a ten-way||chain withBOUNDED_STRING_FIELD_TYPES.has(t). So two hand-copies became one shared constant. That is the #12017 family's defect fixed a layer down as a side effect of this card, and it is the reason #12017 was serial-queued behind this one rather than run alongside it.Both declared fences held — measured, not taken on trust
fence result #12015's band 8600–9200ofsql-driver.tsuntouched — every hunk is at 13485 / 13795 / 13813 / 13823 / 13980#11722's band 10100–10560untouched PR #12003's four packages/spec/*/api.json0 files changed ✅ (The
13980hunk is a modest overrun of the band I declared, into territory no card holds. Noted, not a breach.)Clause ② — the transitions, both directions, on live servers
Read from
information_schema.COLUMNSby a separate query rather than from emitted DDL, on PG 16 and MySQL 8.0.46STRICT_TRANS_TABLES:- Widening:
body_sig/body_qrread back astexton both dialects; a 1000-char data URI is accepted and round-trips byte-identically — where the card measured22001/ER_DATA_TOO_LONGbefore. - Narrowing: a declared
maxLength: 64refuses at 65 and accepts at exactly 64, on insert and update, with a field-named ADR-0112max_lengthenvelope. - The control fires:
c_coloris still refused by the server, so the widening is specific to the two types and not an artifact of the harness.
The #11374 interaction I fenced at dispatch was measured and came out clean: a keyed, bounded
signatureis emittedvarchar(64)and the server refuses 65 — so enforcement of the declared bound never depends on whether an index happens to key the column. That dialect/shape-divergent enforcement was the specific hazard that could have made this fix worse than the defect, and it does not survive.Both authoring forms and the docs page moved with the set. Changeset
minor×3, correctly graded.Two process points handled the way I want them handled
- A void ablation leg was discarded and redone, not salvaged. The storage leg's first take swallowed a block-opening brace and produced a transform error; it was thrown out and rerun brace-preserving. The red leg then reproduced the card's live defect verbatim (
value too long for type character varying(255)/ER_DATA_TOO_LONG). - The sanitizer trap fired for real. The HTML-comment report marker was stripped from comment
5408667003; the dev noticed, re-posted with a literal marker, and verified the marker survived. This is the hazard my seat post warns about — reading back only the marker is not verification — and it was handled correctly rather than left as a silently truncated report.
One gap I found in review, filed rather than used to hold the PR
The repair reaches new tables. On a pre-existing table,
schema-drift.ts's varchar differ requiresdeclaredMaxLength !== undefined, so an undeclaredsignature/qrcodecolumn staysvarchar(255)and no drift op reports it at all — while a declared-bound one correctly getswiden_varchar. The undeclared case is the card's own motivating example (field-zoo's data-URI PNG), so an existing deployment sees no change and no diagnostic.⛔ Not a defect in this PR, which declares the "existing tables stay
varchar(255)" boundary honestly in its changeset and delivers all three ruled seams. Filed as #12121 so the remaining half is not rediscovered.Also carried out of this card: #12117 (
type-compat.ts'stext.exactomits both types, whilebinary.exactstill listssignature). I checked the blast radius independently — the only non-test consumer ofisCompatible/suggestFieldTypeForSqlTypeisservice-datasource/src/external-datasource-service.ts, the external-column import path, so there is no managed-table drift impact and filing rather than widening this PR was the right call. Its dedup note says the filing session's issue search returned 403; I re-ran it here and the channel answered with a firing control, so the "no covering card" reading is live.Flipping to ready and enqueueing once CI converges.
Generated by Claude Code
- Widening:
LANDED — PR #12119 merged as
a11c1a57d6.Verified by content on
origin/main:'signature', 'qrcode'present inBOUNDED_STRING_FIELD_TYPES→ 1 (0 before). Control: the set declaration itself → 1, so the probe fires.All three ruled seams are in:
- Authoring — both types are in the spec set;
Field.signature({ maxLength: 64 })parses where spec:maxLengthis authorable on every field type and validated as no more than a number —maxLength: 0andmaxLength: 12.5parse cleanly #11566 refused it. - Write seam —
record-validatorrefuses an over-long value with a field-named ADR-0112max_lengthenvelope, boundary measured both directions (64 accepted, 65 refused, insert and update). - Storage — both are in the TEXT family, under exactly the driver-sql:
richtextis emitted as varchar(255) while itsmarkdown/htmlsiblings get TEXT — a rich-text body is capped at 255 characters #11794 invariant. Measured live: a 1000-char data URI now lands in atextcolumn on PG 16 and MySQL 8.0.46 and round-trips byte-identically, where it was refused22001/ER_DATA_TOO_LONGbefore.c_colorcontrol still refused, so the widening is specific.
The #11374 keyed-and-bounded interaction came out clean — the hazard that could have made this fix worse than the defect. A keyed, bounded
signatureis emittedvarchar(64)and the server refuses 65, so enforcement of the declared bound never depends on whether an index happens to key the column.Two hand-maintained copies were removed rather than updated, which was not asked for and is the most valuable part of the diff:
record-validator's ten-way||chain andfield.zod.ts's prose enumeration in the refusal message both now readBOUNDED_STRING_FIELD_TYPES. That is the #12017 family closed one layer down — and #12017 has been re-scoped accordingly, since only the driver half remains.secretandcolorstayed out, per the ruling.Two follow-ups carried out of this card, neither a defect in it:
- [finding] An UNDECLARED signature/qrcode column on a pre-existing table stays varchar(255) and no drift op reports it — the #11875 repair reaches new tables only #12121 — the repair reaches new tables only; an undeclared
signature/qrcodecolumn on a pre-existing table staysvarchar(255)and no drift op reports it, because the varchar differ requiresdeclaredMaxLength !== undefined. - type-compat: the
textcanonical column's exact field-type set omitssignature/qrcode, which the driver now emits as TEXT #12117 —type-compat.ts'stext.exactomits both types, which this change makes the driver's own emitted shape. Blast radius measured: the only non-test consumer is the external-datasource import path.
Closing as completed;
pm:dispatchedstripped in the same stroke.
Generated by Claude Code
- Authoring — both types are in the spec set;
- added a commit that references this issue
on Aug 25, 2026 - added a commit that references this issue
on Aug 26, 2026 - added a commit that references this issue
on Sep 4, 2026 - added a commit that references this issue
on Sep 9, 2026
Found while fixing #11794 (
richtextemitted as varchar(255)). Filed unassigned; deliberately not fixed there — that card's fence was "if it turns out to widen accepted physical shapes beyond the declared contract, stop and report", and these two are exactly what it caught.Related: #11431 (the string family ignores
maxLength). Independent of it — that card is about a bound that exists and is not honoured; this one is about a bound that is honoured by nothing at all.The invariant #11794 established
A field type may take an unbounded TEXT column exactly when the write seam enforces its declared
maxLength. That is not a new rule —schema-drift.tsalready rests on it, in as many words:The measurement
validateRecord(packages/objectql/src/validation/record-validator.ts) applies itsmax_length/min_lengthbranch to a hard-coded type list. Measured — a{ type: T, maxLength: 64 }field, a 100-character value,mode: 'insert':maxLengthis a plain optional key onFieldSchema(field.zod.ts), admitted on every field type. SoField.signature({ maxLength: 64 })parses, publishes, and binds nothing.Why that leaves these two types stuck
signatureandqrcodeareSTRING_VALUE_TYPESmembers whose stored value is the author's own value, and it is routinely far past 255 characters — field-zoo writes a data-URI PNG forsignature. So they have the same live defect #11794 fixed forrichtext. Measured on live MySQL 8.0.46 (STRICT_TRANS_TABLES) and Postgres 16, a 1000-character value today:But moving them into the TEXT family the way
richtextandcodemoved would replace an under-accepting column with an over-accepting one: with no write-seam bound and no column bound, a declaredmaxLengthwould be enforced by nothing on any dialect. That is a physical surface wider than the declared contract, so #11794 left them where they were and asserted the refusal out loud instead (sql-driver-11794-richtext-text-family.test.ts, "records the STILL-OPEN half"). Note the enforcement is not uniformly absent even today: the #11374 keyed-and-bounded rule would give a keyed bounded columnvarchar(maxLength), so a move would make enforcement depend on whether an index happens to key the column — the dialect/shape-divergent enforcement of one declaration that this file's conformance matrices exist to close.The decision this needs
Not prescribing a remedy. At least three readings, and they shape a public contract:
signature/qrcodeto the record-validator's string branch, then move them into the TEXT family. Declared = enforced, and the data-URI defect goes away.declaredVarcharLength, i.e.varchar(maxLength)with TEXT aboveMAX_VARCHAR_CHARS. Honours a declared bound, but an undeclared signature still takes varchar(255) and still refuses a data URI.maxLengthinapplicable to them (ADR-0049 enforce-or-remove) and refuse it at publish time — then TEXT is unambiguously correct and option 1's validator change is unnecessary.secretandcolorsit in the same measurement and may or may not travel with the answer:secretpersists an opaquesys_secretref (ADR-0100) rather than the declared value, andcoloris short by construction — neither has the live refusal these two have.Reproduce
validateRecord({ fields: { f: { type: T, maxLength: 64 } } }, { f: 'y'.repeat(100) }, 'insert')per type.packages/drivers/driver-sql/src/sql-driver-11794-richtext-text-family.test.tswithOS_TEST_POSTGRES_URL/OS_TEST_MYSQL_URLprovisioned; itsbody_sig/body_qrare the controls.Generated by Claude Code