Repository navigation
Commit 32be735
fix(driver-sql): honour storage.notNull on the multi-value column, the ADR-0113 site it returned above (#18630)
Fixes #17231
Clause-②: no
Honouring a declared constraint is narrowing back to the contract
(ADR-0113), not widening one — 「拉回已声明契约不触条款②」. No accept set grows and
no public surface enlarges: a column whose metadata declared it
constrained becomes constrained.
## The falsification triage asked for, answered FIRST
Triage flagged its own premise as falsify-first: *does ADR-0113 intend
multi-value / JSON columns to be in scope at all, or did the
short-circuit predate and survive P0 deliberately?* **It does not
falsify.** Four readings, taken on this branch's base:
1. **ADR-0113 carves out no field type.** Its D2 is unconditional —
`storage.notNull` "is what emits `col.notNullable()` and what
schema-drift compares against the physical column" — and the word
`multiple` (and any JSON/array wording) appears nowhere in the record.
Its P0 note names the site verbatim: 「the physical constraint now keys
off the explicitly-authored `storage.notNull` at that same
`#createColumn` site」, with `@objectstack/driver-sql` (`sql-driver.ts`
column DDL, `schema-drift.ts`) listed as the consumer.
2. **The declaration is authorable, measured.** `storage.notNull`'s only
declared exclusivity is `requiredWhen` (`FieldSchema.superRefine`,
ADR-0113 Q1 rider). `{ type: 'lookup', reference: 'sys_user', multiple:
true, storage: { notNull: true } }` parses green, and the `requiredWhen`
pair is still refused — both asserted in the new suite, the second as
the discriminating control.
3. **ADR-0113 is already implemented as in-scope for JSON columns at its
OTHER named consumer in this package.** `diffManagedTable` compares
`storage.notNull` against the physical column for every field
`fieldHasColumn` answers true for, and that predicate answers
multi-value FIRST. So the platform reported `nullability_mismatch` /
`tighten_not_null` / **destructive** against a table it had itself just
created, with no rows in it. A reading in which JSON columns are out of
ADR-0113's scope has to call the differ wrong too.
4. **The nearest counter-reading is about DRIFT, not about the create
path.** ADR-0113's Context row — 「imposing `NOT NULL` over possibly-null
data is the classifier's `destructive` class」 — is the
`tighten_not_null` ceremony over an EXISTING column, untouched here.
`createColumn` runs on `CREATE TABLE` and on `ALTER TABLE ADD COLUMN`,
so the column it constrains is always EMPTY, which is the same reason
the string family's #11431 note gives for sizing a `varchar` at this
very site.
⛔ NOT MEASURED, and the conclusion does not rest on it: the historical
half of the question ("did the short-circuit survive P0 deliberately?").
This checkout is shallow (500 commits) and both `git log -S` probes
collapse onto the shallow boundary commit, so no date ordering was read.
What replaces it is (3): had P0 exempted multi-value columns, the
exemption would be in the differ, and it is not — nor is any exemption
recorded in `createColumn`, a method that records every one it does have
(virtual `formula`, the FILE family's deployment fact, `unique` deferred
to the tenancy-aware path, `options[].default`).
## What changed
`SqlDriver.createColumn` decides the JSON column shape before its
per-type switch and `return`ed there — above the ADR-0113 nullability
line and above the column DEFAULT. The short-circuit decides the column
TYPE; returning made it silently decide the column's CONSTRAINTS as
well.
The constraint predicate is now stated once, as `declaresColumnNotNull`,
and asked at BOTH of `createColumn`'s exits. Truthiness, not `=== true`:
that is the test the tail has always applied, and this moves WHERE the
question is asked, never which values answer it.
⛔ The pin was not touched and the generators were not relaxed:
`packages/cli` has no file in this diff. The multi-value short-circuit
still decides `multiple` before the type switch, so
`generate-multiple-json-column.pin.test.ts`'s source-read of this method
reads the same two landmarks it always did.
The column DEFAULT is still NOT emitted on this path. `os generate
migration` skips it for the same recorded reason
(`declaredColumnDefault`: the multi-value shape has no scalar DDL form),
so the two producers already agreed there and nothing diverges.
## Evidence — driven, with the control fired
Server-free, on three of the card's own probe shapes plus two controls,
through `initObjects` into an in-memory `better-sqlite3` database read
back with knex `columnInfo()`.
Before the repair, on base `21b7c12b4f` — 6 passed, 3 failed:
```
× the multi-value column honours `storage.notNull` expected true to be false
× an INSERT omitting the field is refused promise resolved instead of rejecting
× the differ agrees with the writer [ { kind: 'nullability_mismatch',
column: 'multi_nn',
expected: 'NOT NULL', actual: 'NULL',
category: 'destructive',
op: { type: 'tighten_not_null' } } ]
✓ control — this reader can see a NOT NULL, and sees NULL where nothing declared one
✓ …and nothing else acquired the constraint: `multiple` and `required` still do not bind the column
```
The control is what makes the green afterwards a reading: `scalar_nn` (a
scalar carrying the same `storage.notNull`) comes back NOT NULL in BOTH
runs, so the harness was never blind to the constraint. After the
repair: **9 passed, 2 skipped**, the two skips named below.
Also asserted, so the repair cannot be bought with the type decision:
the constrained multi-value column's type still equals the unconstrained
multi-value column's, and still differs from the scalar's.
## Verification
Run through the shared verify lock; exit codes captured before any pipe.
| what | reading |
|---|---|
| `pnpm --filter @objectstack/driver-sql test` | **178 files passed, 11
skipped · 2627 tests passed, 168 skipped** (exit 0), at merged HEAD
`c20356f0e1` |
| `pnpm --filter @objectstack/driver-sql typecheck` | exit 0. The new
suite is really in that program: `tsc --noEmit --listFiles` names it,
one occurrence |
| gate families, derived not guessed | `scripts/pm/dispatch-gates.mjs
--repo objectstack-ai/objectstack --commands` at `c20356f0e1` ⇒ **61
families**, each run with its exit code recorded, reconciled with
`--ran`: **61 derived, 61 run, 0 NOT-MEASURED, 0 UNRUN** |
| gate results | 60 green. 1 red — `check:cross-package-test-inputs` —
is NOT from this diff; the control is below |
| spec-generated artefacts after merging `main` | `pnpm --filter
@objectstack/spec build && check:generated` ⇒ "All 15 generated
artifacts are up to date" |
**The narrowing declared, with its three pieces of evidence.** `pnpm
lint` is a repo-scale scan CI owns; what ran here is `eslint
--no-inline-config --format json` over the two changed source files ⇒
**2 files, 0 errors, 0 warnings**, at `c20356f0e1`. (i) The population
comes from eslint's own config — the base block matches
`**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}`, so the third changed path, a
`.changeset/*.md`, is outside eslint's population entirely; (ii) the
file count is the JSON reporter's own array length, 2; (iii) this repo's
single `eslint.config.mjs` never enables type-aware linting for ANY file
(no `parserOptions.project`, no typed `@typescript-eslint` rules —
stated and measured in its own header), so this diff cannot move the
verdict on a file it does not contain.
### ✅ Dialect cells — ALL THREE executed, after the patch round
> 1 parent cb04f45 commit 32be735
3 files changed
Lines changed: 345 additions & 2 deletions
File tree
- .changeset
- packages/drivers/driver-sql/src
| 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 | + | |
| 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 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1788 | 1788 | | |
1789 | 1789 | | |
1790 | 1790 | | |
| 1791 | + | |
| 1792 | + | |
| 1793 | + | |
| 1794 | + | |
| 1795 | + | |
| 1796 | + | |
| 1797 | + | |
| 1798 | + | |
| 1799 | + | |
| 1800 | + | |
| 1801 | + | |
| 1802 | + | |
| 1803 | + | |
| 1804 | + | |
| 1805 | + | |
| 1806 | + | |
| 1807 | + | |
| 1808 | + | |
| 1809 | + | |
| 1810 | + | |
| 1811 | + | |
| 1812 | + | |
1791 | 1813 | | |
1792 | 1814 | | |
1793 | 1815 | | |
| |||
17382 | 17404 | | |
17383 | 17405 | | |
17384 | 17406 | | |
| 17407 | + | |
| 17408 | + | |
| 17409 | + | |
| 17410 | + | |
| 17411 | + | |
| 17412 | + | |
| 17413 | + | |
| 17414 | + | |
| 17415 | + | |
| 17416 | + | |
| 17417 | + | |
| 17418 | + | |
| 17419 | + | |
| 17420 | + | |
| 17421 | + | |
| 17422 | + | |
| 17423 | + | |
| 17424 | + | |
| 17425 | + | |
| 17426 | + | |
| 17427 | + | |
| 17428 | + | |
| 17429 | + | |
| 17430 | + | |
| 17431 | + | |
17385 | 17432 | | |
17386 | | - | |
| 17433 | + | |
| 17434 | + | |
17387 | 17435 | | |
17388 | 17436 | | |
17389 | 17437 | | |
| |||
17790 | 17838 | | |
17791 | 17839 | | |
17792 | 17840 | | |
17793 | | - | |
| 17841 | + | |
| 17842 | + | |
| 17843 | + | |
| 17844 | + | |
| 17845 | + | |
17794 | 17846 | | |
17795 | 17847 | | |
17796 | 17848 | | |
| |||
0 commit comments