Skip to content

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

@huangyiirene

Found while fixing #11794 (richtext emitted 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.ts already rests on it, in as many words:

A TEXT column refuses nothing a maxLength allows, so there is no divergence to plan an ALTER for; the bound is enforced at the write seam.

The measurement

validateRecord (packages/objectql/src/validation/record-validator.ts) applies its max_length / min_length branch to a hard-coded type list. Measured — a { type: T, maxLength: 64 } field, a 100-character value, mode: 'insert':

text REFUSED VALIDATION_FAILED f must be <= 64 characters (got 100)
textarea REFUSED VALIDATION_FAILED f must be <= 64 characters (got 100)
markdown REFUSED VALIDATION_FAILED f must be <= 64 characters (got 100)
html REFUSED VALIDATION_FAILED f must be <= 64 characters (got 100)
richtext REFUSED VALIDATION_FAILED f must be <= 64 characters (got 100)
code REFUSED VALIDATION_FAILED f must be <= 64 characters (got 100)
signature ACCEPTED <- no max_length branch
qrcode ACCEPTED <- no max_length branch
secret ACCEPTED <- no max_length branch
color ACCEPTED <- no max_length branch
string ACCEPTED <- no max_length branch (the column carries it — #11431)

maxLength is a plain optional key on FieldSchema (field.zod.ts), admitted on every field type. So Field.signature({ maxLength: 64 }) parses, publishes, and binds nothing.

Why that leaves these two types stuck

signature and qrcode are STRING_VALUE_TYPES members whose stored value is the author's own value, and it is routinely far past 255 characters — field-zoo writes a data-URI PNG for signature. So they have the same live defect #11794 fixed for richtext. Measured on live MySQL 8.0.46 (STRICT_TRANS_TABLES) and Postgres 16, a 1000-character value today:

 physical column 1000-char write
signature pg character varying(255) REFUSED 22001
signature my varchar(255) REFUSED ER_DATA_TOO_LONG
qrcode pg character varying(255) REFUSED 22001
qrcode my varchar(255) REFUSED ER_DATA_TOO_LONG

But moving them into the TEXT family the way richtext and code moved would replace an under-accepting column with an over-accepting one: with no write-seam bound and no column bound, a declared maxLength would 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 column varchar(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:

  1. Give the write seam a bound for them — add signature / qrcode to the record-validator's string branch, then move them into the TEXT family. Declared = enforced, and the data-URI defect goes away.
  2. Treat them as string-familydeclaredVarcharLength, i.e. varchar(maxLength) with TEXT above MAX_VARCHAR_CHARS. Honours a declared bound, but an undeclared signature still takes varchar(255) and still refuses a data URI.
  3. Rule maxLength inapplicable 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.

secret and color sit in the same measurement and may or may not travel with the answer: secret persists an opaque sys_secret ref (ADR-0100) rather than the declared value, and color is short by construction — neither has the live refusal these two have.

Reproduce

  • Write-seam table: validateRecord({ fields: { f: { type: T, maxLength: 64 } } }, { f: 'y'.repeat(100) }, 'insert') per type.
  • Physical columns and refusals: packages/drivers/driver-sql/src/sql-driver-11794-richtext-text-family.test.ts with OS_TEST_POSTGRES_URL / OS_TEST_MYSQL_URL provisioned; its body_sig / body_qr are the controls.

Generated by Claude Code

Metadata

Metadata

Assignees

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions