Repository navigation
Commit b06b2db
fix(cli): one column width for the file family in both os generate migration formats (#18014)
Fixes #17883
Clause-②: no
`os generate migration` gave the file family — `file` / `image` /
`avatar` / `video` / `audio` — two different column widths depending on
`--format`. The typescript format emitted a bare `table.string(name)`,
which `generate.ts` itself states is knex's `varchar(255)`, while
`--format sql` emitted `VARCHAR(2048)` for the same field.
## The stop condition, measured first
Triage ruled the direction (the two halves must agree) with a hard
fence: **if 255 turns out to be deliberate, stop and route the card to
`needs-user-decision` instead of forcing the alignment.**
`generate.ts:1456` says `VARCHAR(50)` and `VARCHAR(2048)` "were widths
this file invented", and invented numbers have history. Five readings,
taken on this branch's base `611795e40`:
1. **ADR-0104** records the maintainer ruling on #15041 verbatim: *"The
driver is the side that moves; the generator's `VARCHAR(2048)` already
states the ruled end-state and stands."* Its column section adds the
operative criterion: the driver's width mirror "answers for the family
what the generator answers, so the pin that sweeps the other string
classes can sweep this one — **a width the two sides disagree on would
be this fork again, one level down**."
2. **`driver-sql`'s own constant header** (`MEDIA_ID_VARCHAR_CHARS =
2048`) states it as a rule, not a taste: *"A driver that created knex's
default `varchar(255)` here would open a fresh divergence between the
two producers of the same column."*
3. **`media-column-move.ts:101`** — `MEDIA_ID_MOVE_WIDTH = 2048`, used
in the `type varchar(2048) using (…)` retype. Read as a control only;
not touched.
4. **History**: `git log -S` on the arm shows the family joined the
typescript switch's REFERENCE arm in `431979e67` (#14834), a card about
giving every `FieldType` member an *entry* — a presence fix. That arm's
derivation is the target row's `id` column
(`table.string('id').primary()`), which was never the file family's
derivation. The 255 was **inherited by sharing an arm**, never chosen
for this family.
5. **No dialect depends on it**: the driver's own ceiling is
`MAX_VARCHAR_CHARS = 16383`, and both the SQL format and the driver
already ship 2048 for this family everywhere.
No reading makes 255 deliberate, so the alignment stands and the card
does not move to `needs-user-decision`. The typescript half is the side
that was out of step.
## The change
The family leaves the reference arm and takes one of its own. The width
is **read from the SQL format's own entry** rather than retyped beside
it, so the two formats cannot drift apart again — a second literal
`2048` would be the same defect one line over.
```diff
- table.string('cover_image').nullable();
+ table.string('cover_image', 2048).nullable();
```
⛔ The SQL half did not move. 2048 is the already-shipped target; the
repair is the typescript half joining it, never the two halves meeting
in the middle. The new pin asserts the agreed width **against
`driver-sql`'s own constant** precisely so that two halves that had both
drifted to knex's default could not satisfy it.
## Acceptance — both directions, five types, with the control the file
documents
One probe object, one field per type, both formats, before and after the
fix. The whole delta is five lines:
```
table.string('id').primary();
- table.string('f_file').nullable(); + table.string('f_file', 2048).nullable();
- table.string('f_image').nullable(); + table.string('f_image', 2048).nullable();
- table.string('f_avatar').nullable(); + table.string('f_avatar', 2048).nullable();
- table.string('f_video').nullable(); + table.string('f_video', 2048).nullable();
- table.string('f_audio').nullable(); + table.string('f_audio', 2048).nullable();
table.text('f_text').nullable(); [control — byte-identical]
table.string('f_lookup').nullable(); [control — byte-identical]
table.string('f_master_detail').nullable(); [control — byte-identical]
table.string('f_user').nullable(); [control — byte-identical]
table.string('f_tree').nullable(); [control — byte-identical]
table.string('f_autonumber').nullable(); [control — byte-identical]
```
- **Field by field, all five**: `--format sql` gives `VARCHAR(2048)` and
the typescript format gives `table.string(name, 2048)` for `file`,
`image`, `avatar`, `video` and `audio`. One verified is not five, so the
pin sweeps `FILE_REFERENCE_TYPES` imported from the spec rather than a
list written out.
- **The control did not move**: `f_text` — the non-file family the file
itself documents at `generate.ts:1403`-`:1405`, where both halves
already agree — is byte-identical before and after, and so is every
other non-family column and the entire `--format sql` emission (the diff
above has no hunk on the SQL side at all). ⛔ No control was invented;
the documented one was used.
**Reverse verification.** With the fix committed, the pre-fix
`generate.ts` was put back on disk and the new pin run against it: **5
of 8 tests red**, one per family member, with the three
control/anti-vacuity tests staying green — the discriminating direction.
On-disk proof of the mutation rather than an editor's exit code: the
injected resolver went 4 occurrences ⇒ 0, the blob hash moved `8d8c34a0`
⇒ `1fccbeff`. Restore verified by state, not by exit code: blob back to
`8d8c34a0`, `git diff HEAD` empty. The subject resolves through a
relative intra-package import (`./generate.js`), never through a
dependency's `exports`, so no `dist` sits between the edit and the
verdict.
## Premise falsified — acceptance item 4 pointed at the wrong table
The dispatch order asked for the self-describing table at
`generate.ts:1461` to be updated, on the reading that it "still records
the pre-#15989 three-way reading (`driver varchar(255)`) and is wrong on
its first column". Re-derived on this branch, that is not so:
```
:1460 // f_phone driver varchar(255) sql gen varchar(50) ts gen varchar(255)
:1461 // f_url driver varchar(255) sql gen varchar(2048) ts gen varchar(255)
```
Those two rows are about `phone` and `url` — the **STRING family**, not
the file family. #15989 moved the file family to 2048; it did not touch
`url`, whose driver column is still `declaredVarcharLength(field)` =
`DEFAULT_STRING_VARCHAR_CHARS` = 255 with no declared `maxLength`. The
rows are the measured *pre-*#16091 reading kept deliberately as the
rationale for the entries that replaced them ("`VARCHAR(50)` and
`VARCHAR(2048)` **were** widths this file invented"). Rewriting `driver
varchar(255)` to 2048 there would have introduced an error, so those
lines are byte-identical in this PR.
The stale text about the file family is a different paragraph —
`generate.ts:1375`-`:1381`, which held the family out of scope on the
ground that it "is in the driver's `JSON_COLUMN_TYPES` today", untrue
since #15989 landed. That paragraph is rewritten, which serves what
acceptance item 4 actually asked for: no table left out of date the
moment the fix lands. The same stale sentence in
`generate-field-type-vocabulary.pin.test.ts`'s header is corrected with
it.
## Verification
Base `611795e40`; readings taken at `3b3ba8a35`. Exit codes captured
before any pipe.
| Run | Result |
|:--|:--|
| `pnpm --filter '@objectstack/cli^...' build` | `VERDICT command-exit
0` (dependency closure) |
| `pnpm --filter @objectstack/cli exec vitest run --project unit` |
`VERDICT command-exit 0` — 206 files, 2958 tests passed |
| `pnpm --filter @objectstack/cli typecheck` | `VERDICT command-exit 0`
(`tsc --noEmit` + `check:test-typecheck`, ledger held) |
| derived gate family, `scripts/pm/dispatch-gates.mjs --commands` | 62
derived, **62 run, 0 NOT-MEASURED, 0 UNRUN**, every one exit 0 |
| `eslint . --no-inline-config --format json` | exit 0 — **6705 files, 0
errors, 0 warnings** (whole tree, not a narrowing) |
Both typecheck programs were shown to contain the changed files (`tsc -p
tsconfig.json --listFiles`: 1532 files, `commands/generate.ts` and the
new pin both present), so the green is about them and not around them.
Three runs needed a prerequisite before they measured anything, and each
was re-run to a real verdict rather than recorded as a pass:
`check:dual-build-cjs-loads` and `check:i18n-coverage` both exited **3**
(`PREREQUISITE NOT MET` — built output missing) until the build they
prescribe was run, then exit 0; `check:type-check-debt` exited 124 at a
240s budget and then **3** under `--max-old-space-size=4096` (the child
`tsc` OOM'd inside my own cap), and exit 0 at 6144 MB, which the gate
names as its own CI-shaped ceiling.
The `integration` tier is declared to CI: this diff touches no spawn
entry, no `bin/`, no `test/helpers/serve-process.ts` and no driver or
kernel boot path.
## Changeset — measured, not assumed
`patch` on `@objectstack/cli`. `packages/cli` is published (not
private), `files[]` is `dist README.md CHANGELOG.md`, and
`tsconfig.build.json` compiles `src`. After `pnpm --filter
@objectstack/cli build`, the changed subject is present in the shipped
path: `packages/cli/dist/commands/generate.js` carries the new resolver
(4 occurrences) and the sized emission (1). Positive control, a known
shipped symbol in the same file: 3 hits. Negative control, a nonsense
token: 0 hits. Something published moves, so a changeset is owed; a bug
fix in a released package takes `patch`.
## Acceptance notes
- ⛔ Fences honoured: `media-column-move.ts` is read as a control and not
edited; the `os migrate files-to-references` retype statement is
untouched; no width outside the file family moved; `packages/spec` is
not touched; `content/docs/releases/**` is not touched. No export was
added to a published entry point.
- `noted, not filed`: on MySQL an index over a `varchar(2048)` utf8mb4
column exceeds InnoDB's 3072-byte index key limit, and since #16317 both
formats emit declared `unique` indexes. This is not created here — the
SQL format and the driver already build the column at 2048 — and it is
unmeasured (no MySQL cell in this container), so it is an observation
rather than a card. Successor: the `driver-sql` seat, on the next card
that touches the media column width; on a per-dialect pin it would
surface in `Temporal Conformance (live PG + MySQL)`.
- `noted, not filed`: `generate-string-family-width.pin.test.ts`
excludes `FILE_REFERENCE_TYPES` from the character-catch-all sweep and
gives a per-deployment reason that is now only half true (the driver's
arm is per-deployment; the generator's width is not). The exclusion is
still correct and the file family now has its own pin, so nothing is
uncovered. Successor: whoever next edits that pin's derivation.
- The new pin lives at
`packages/cli/src/commands/generate-file-reference-width.pin.test.ts`,
beside its seven sibling `generate-*.pin.test.ts` files, rather than
under `packages/cli/test/**` as the claim comment's declared file face
spelled it. Same package, unit tier, and `check:tier-file-adoption` /
`check:cross-package-test-inputs` both pass.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01TSf4DV7ziu4V5j73e46b7c)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: os-sales <sales@objectstack.ai>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>1 parent a83dbb6 commit b06b2db
4 files changed
Lines changed: 321 additions & 19 deletions
File tree
- .changeset
- packages/cli/src/commands
| 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 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
93 | 93 | | |
94 | 94 | | |
95 | 95 | | |
96 | | - | |
97 | | - | |
98 | | - | |
99 | | - | |
100 | | - | |
101 | | - | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
102 | 104 | | |
103 | 105 | | |
104 | 106 | | |
| |||
375 | 377 | | |
376 | 378 | | |
377 | 379 | | |
378 | | - | |
| 380 | + | |
| 381 | + | |
| 382 | + | |
| 383 | + | |
| 384 | + | |
| 385 | + | |
| 386 | + | |
| 387 | + | |
| 388 | + | |
| 389 | + | |
379 | 390 | | |
380 | | - | |
| 391 | + | |
381 | 392 | | |
382 | 393 | | |
383 | 394 | | |
| |||
669 | 680 | | |
670 | 681 | | |
671 | 682 | | |
672 | | - | |
| 683 | + | |
| 684 | + | |
| 685 | + | |
| 686 | + | |
| 687 | + | |
| 688 | + | |
673 | 689 | | |
674 | 690 | | |
675 | 691 | | |
| 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 | + | |
0 commit comments