Found while implementing #14657. Filed unassigned for triage. No severity asserted. #14657 is not addressed here and remains open until its own PR lands.
The shape
multiple appears exactly four times in packages/cli/src/commands/generate.ts, and all four are on the TypeScript side:
562 function fieldTypeToTs(fieldType: string, multiple?: boolean): string {
564 return multiple ? `${base}[]` : base;
607 const tsType = fieldTypeToTs(fType, !!fieldDef.multiple); // os generate types
831 const tsType = fieldTypeToTs(fType, !!fieldDef.multiple); // os generate client
generateMigrationSql and generateMigrationTs never read it. fieldTypeToSql(fieldType) does not even take the parameter.
So for Field.lookup({ reference: 'account', multiple: true }) the CLI emits, from one config in one run:
os generate types — account?: string[]os generate migration --format sql — VARCHAR(36)os generate migration (typescript, the default) — table.uuid('account')
The runtime disagrees with the last two. driver-sql's isJsonField is JSON_COLUMN_TYPES.has(type) || !!field.multiple — the multiple flag alone puts the column in the JSON class, and sql-driver.ts handles it "at the top of createColumn", before the per-type switch. So a multiple: true field of ANY multi-capable type (MULTI_CAPABLE_TYPES = select, radio, lookup, user, file, image) is a JSON column in the platform and a scalar column in the generated migration.
Why it is a defect rather than a gap
It is silent in both directions: the scaffold looks right, the generated TypeScript is right, and only the column is wrong. The write path then serializes an array into a scalar column — the same class of failure #field-zoo recorded when array-valued fields reached the SQLite binder unserialized ("SQLite3 can only bind numbers, strings, bigints, buffers, and null"), except here it is the generated DDL rather than the driver that is out of step.
Why it was not folded into #14657
#14657 is about members with no vocabulary entry. This is orthogonal: it affects members that HAVE an entry, through a flag no migration generator reads, and the fix is a parameter through fieldTypeToSql plus a pre-switch branch in generateMigrationTs — a change to the generators' shape rather than to any vocabulary. Filing it separately keeps that decision out of a card whose whole point was that hand-carried lists drift.
Notes for triage
Dedup
search_issues for this shape returned total_count: 0; a control query in the same session returned #14657 and #13871, so the empty result is a reading rather than a broken search.
Found while implementing #14657. Filed unassigned for triage. No severity asserted. #14657 is not addressed here and remains open until its own PR lands.
The shape
multipleappears exactly four times inpackages/cli/src/commands/generate.ts, and all four are on the TypeScript side:generateMigrationSqlandgenerateMigrationTsnever read it.fieldTypeToSql(fieldType)does not even take the parameter.So for
Field.lookup({ reference: 'account', multiple: true })the CLI emits, from one config in one run:os generate types—account?: string[]os generate migration --format sql—VARCHAR(36)os generate migration(typescript, the default) —table.uuid('account')The runtime disagrees with the last two.
driver-sql'sisJsonFieldisJSON_COLUMN_TYPES.has(type) || !!field.multiple— themultipleflag alone puts the column in the JSON class, andsql-driver.tshandles it "at the top of createColumn", before the per-type switch. So amultiple: truefield of ANY multi-capable type (MULTI_CAPABLE_TYPES= select, radio, lookup, user, file, image) is a JSON column in the platform and a scalar column in the generated migration.Why it is a defect rather than a gap
It is silent in both directions: the scaffold looks right, the generated TypeScript is right, and only the column is wrong. The write path then serializes an array into a scalar column — the same class of failure
#field-zoorecorded when array-valued fields reached the SQLite binder unserialized ("SQLite3 can only bind numbers, strings, bigints, buffers, and null"), except here it is the generated DDL rather than the driver that is out of step.Why it was not folded into #14657
#14657 is about members with no vocabulary entry. This is orthogonal: it affects members that HAVE an entry, through a flag no migration generator reads, and the fix is a parameter through
fieldTypeToSqlplus a pre-switch branch ingenerateMigrationTs— a change to the generators' shape rather than to any vocabulary. Filing it separately keeps that decision out of a card whose whole point was that hand-carried lists drift.Notes for triage
packages/cli⇒ likelydomain:cli.MULTI_CAPABLE_TYPESandisMultiValueFieldin@objectstack/spec/datafield-value.zod.ts, which is what objectql's record-validator and rest's import-coerce derive from. A fix should read that rather than re-listing.generate.ts, so they may want sequencing rather than parallel claims.Dedup
search_issuesfor this shape returnedtotal_count: 0; a control query in the same session returned #14657 and #13871, so the empty result is a reading rather than a broken search.