Skip to content

[finding] FILE_REFERENCE_TYPES disagree about their column: driver-sql puts file/image/avatar/video/audio in JSON_COLUMN_TYPES, packages/cli generate.ts gives them VARCHAR(2048) — and neither side is obviously the one that should move #15041

Description

@os-trump

Found while implementing #14828, which corrects five field-type vocabulary rows in packages/cli/src/commands/generate.ts against the driver. This is a sixth disagreement in the same table that #14828 deliberately does not touch, because unlike the five it is not a wrong value — it is two sides holding two different ADR-0104 positions. Filed unassigned for triage. No severity asserted.

The shape

Measured on origin/main at 99b4deba49.

The driver puts the whole file family in a JSON column. packages/drivers/driver-sql/src/sql-driver.ts:232:

const JSON_COLUMN_TYPES = new Set<string>([
  ...STRUCTURED_JSON_TYPES, ...FILE_REFERENCE_TYPES, ...MULTI_OPTION_TYPES,
  'object', 'array',
]);

so createColumn's catch-all gives image / file / avatar / video / audio this.jsonColumn(table, name), and isJsonField deserializes them on read. Its comment says why: "image/file/avatar/video/audio hold structured upload metadata".

The CLI generator gives the same five a scalar varchar. packages/cli/src/commands/generate.ts:

file: 'VARCHAR(2048)',
image: 'VARCHAR(2048)',
...
// FILE_REFERENCE_TYPES: the ADR-0104 D3 stored form is an opaque `sys_file`
// id string, which is why `file` / `image` above are already a varchar; these
// three are the same class and take the same answer.
avatar: 'VARCHAR(2048)',
video: 'VARCHAR(2048)',
audio: 'VARCHAR(2048)',

Why this is a decision and not a defect

Both sides cite the same ADR and are describing different points on its timeline. The spec's own class doc (packages/spec/src/data/field-value.zod.ts) states both at once:

/**
 * Media/attachment types. Stored form TODAY is the legacy inline metadata
 * object (`{url, name?, size?, ...}`) or an opaque file-id/url string;
 * ADR-0104 D3 (file-as-reference) narrows this to a `sys_file` id. The stored
 * schema below deliberately admits both until D3 lands.
 */

So the driver is pre-D3 (a JSON column, because the stored value may still be an object) and #14657 chose the post-D3 answer for the generator (a varchar, because the stored value is an id string). Whichever is right, the CLI's generated migration and the runtime's own syncSchema currently create two different physical columns for one authored field, and isJsonField will try to JSON.parse what a hand-run generated migration put in a varchar.

What is NOT the question

⛔ Not "the generator is wrong, copy the driver". #14828 established that the driver is the authority for which column exists, and applied it to five rows where the driver's answer was unambiguous. Here the driver's answer is explicitly labelled as the shape D3 is retiring, so copying it would move the generator backwards. The question is which side D3 makes move, and when.

Notes for triage

Dedup

search_issues in the vocabulary lane returned 14 results (#14828, #14829, #14657, #13871, #12593, #12732, #12017, #12586, ...) and none of them is this; the control is that same query returning the four cards this file's history is made of, in the same session.

Generated by Claude Code

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions