Commit d285bf0
fix(spec)!: refuse
Fixes #17469
Maintainer ruling of 2026-09-13 (director seat decision batch #128 item
5, option **1′**), implemented in full: **one definition of
"multi-valued"**. `FieldSchema` refuses `multiple: true` at the
authoring entrance on every type outside `MULTI_CAPABLE_TYPES` ∪
`MULTI_OPTION_TYPES`, and `@objectstack/driver-sql` derives its storage
decisions from `isMultiValueField` rather than reading the flag raw.
`MULTI_CAPABLE_TYPES` and `isMultiValueField` are untouched, exactly as
the ruling requires.
One PR across two domains, on the cross-domain exception path — ⛔
deliberately not split into a driver PR that could land without the
refusal.
## 1 — Entrance refusal (`packages/spec`)
`packages/spec/src/data/field.zod.ts`, in the same `superRefine` and
immediately after the #11437 `radio` check it generalises. The message
names the field, its type, the declarable set and a type-aware remedy
(`lookup` for a reference type, `file`/`image` for a media type, the
option types otherwise).
The declarable set is **derived** from `MULTI_CAPABLE_TYPES` ∪
`MULTI_OPTION_TYPES` rather than re-listed (#12017 two-copies shape),
and the remedy list filters `radio` out mechanically so the author is
never sent from one refusal into another. Both are computed on first use
rather than at module top level: `field-value.zod` reaches back into
`field.zod` through `shared/strict-object` → `shared/suggestions.zod`,
and spreading the sets at module scope threw `TypeError:
MULTI_CAPABLE_TYPES is not iterable` in six spec suites — measured, then
fixed.
`radio` is *inside* the set, so the two refusals never double-fire; a
pin asserts exactly one issue on the `multiple` path for `radio`.
## 2 — Storage alignment (`packages/drivers/driver-sql`) — seventeen
expressions, not one
⭐ **The ruling named `isJsonField` as the instance it had measured, not
as an exhaustive list.** Measurement found sixteen more expressions
asking the same question, pinned equal to one another by the package's
own tests. Aligning only the named one re-opens #11535 **in reverse**:
the DDL writer builds a JSON column that the read-side deserializer no
longer recognises.
Every site below was read individually against the premise *"this
expression asks whether the value is multi-valued"*. The evidence column
is the site's own contract text or its neighbours':
| # | site | expression (before) | why it means "is this multi-valued" |
|---|---|---|---|
| 1 | `sql-driver.ts` `crossFieldComparisonClass` | `if (decl.multiple)
return null;` | its own docblock: refuses "every JSON-stored shape
(`multiple: true` and the `JSON_COLUMN_TYPES` classes — element-wise
semantics SQL comparison operators do not have)" |
| 2 | `createColumn` | `if (field.multiple) { jsonColumn; return; }` |
the multi-value short-circuit above the type switch;
`JSON_COLUMN_TYPES`' header calls itself the single source for "both the
DDL column-type switch and `isJsonField` so the two can't drift" |
| 3 | `isJsonField` limb 1 | `if (!field.multiple &&
FILE_REFERENCE_TYPES.has(type))` | selects the SINGLE-VALUE media arm;
`multiple` is the multi/single discriminator |
| 4 | `isJsonField` limb 2 | `JSON_COLUMN_TYPES.has(type) \|\|
!!field.multiple` | the ruled site |
| 5 | `varcharColumnChars` | `if (field?.multiple) return null;` | its
own comment: "`multiple` is decided before the type switch in
`createColumn` — a JSON column, whatever the element type would have
been". multiple: true outside the multi-capable types, and derive driver-sql's storage from the spec predicate (#18187)1 parent d4554d4 commit d285bf0
25 files changed
Lines changed: 1242 additions & 682 deletions
File tree
- .changeset
- content/docs/references
- data
- system
- packages
- cli/src/commands
- drivers/driver-sql/src
- spec/src
- data
- migrations
- entries/semantic
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
60 | 60 | | |
61 | 61 | | |
62 | 62 | | |
63 | | - | |
| 63 | + | |
64 | 64 | | |
65 | 65 | | |
66 | 66 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
222 | 222 | | |
223 | 223 | | |
224 | 224 | | |
225 | | - | |
| 225 | + | |
226 | 226 | | |
227 | 227 | | |
228 | 228 | | |
| |||
555 | 555 | | |
556 | 556 | | |
557 | 557 | | |
558 | | - | |
| 558 | + | |
559 | 559 | | |
560 | 560 | | |
561 | 561 | | |
| |||
0 commit comments