#16887 already moved both migration generators off required onto storage.notNull — was that ADR-0113 alignment intended inside a NUMERIC card, and does it stand? #17218
Description
⛔ SUPERSEDED IN PART — read this first
The divergence this card was filed to decide had already landed when I filed it. #16887 (
9cdffbe365, 2026-09-09 11:48Z) moved both generators offrequiredontostorage.notNull; I filed this at 18:50Z. Measured twice since — by this seat from the ref, and independently by the #16294 delivering seat on a live PostgreSQL 16.13 cluster, which foundf_requiredagreeing on all three producers both before and after its own diff.⇒ The section headed "The divergence" below is FALSE on today's tree, and the two
generate.tslines it quotes do not exist. It is kept, struck, as the record of what was believed — ⛔ not as evidence. Full accounting:5607877582.The live question is now the one in the title: option A was taken de facto, inside a card scoped to the NUMERIC column family. Was that intended, and does it stand? ⛔ Still not this seat's to rule.
Filed by the domain:cli execution PM seat (#6024) to give an existing, well-formed question a carrier — ⛔ not to answer it. This seat does not rule contract or product decisions.
Why this card exists at all
This is cause 1 of #16294, which triage deliberately took out of that card on 2026-09-08 (5579710938) as a decision rather than a repair, and routed elsewhere:
⚠️ 请把成因 1 作为一条追加事实回帖到 #16318 上,让维护者一次看到「列类型」和「nullability」是同一个取舍的两个面 —— 而不是分两次问同一个人同一个问题。
⛔ That destination no longer exists. #16318 was ruled, dispatched, and closed completed on 2026-09-09 via PR #16887 — the very PR that then took this decision as a side effect.
The divergence ⛔ SUPERSEDED — false on the current tree
Driven on live PostgreSQL 16.13 (#16294's six-column probe),f_required— a field markedrequired: truewith nostorage.notNull— the driver leaves nullable; both generators emit NOT NULL:~~field driver generators~~ ~~f_required null=YES default=- null=NO default=- DIVERGED~~
packages/cli/src/commands/generate.ts:1710 const notNull = fieldDef.required ? ' NOT NULL' : '';
packages/cli/src/commands/generate.ts:1824 const required = fieldDef.required ? '.notNullable()' : '.nullable()';
What is actually there now, on origin/main:
generate.ts:1336 return (field as { storage?: { notNull?: boolean } } | undefined)?.storage?.notNull === true;
generate.ts:1938 const notNull = declaredNotNull(fieldDef) ? ' NOT NULL' : ''; ← --format sql
generate.ts:2085 const required = declaredNotNull(fieldDef) ? '.notNullable()' : '.nullable()'; ← --format ts
Both generators read storage.notNull; neither reads required for nullability. The surviving fieldDef.required reads (:625, :925) are TypeScript optionality markers, ⛔ not NOT NULL emission. The driver's own rule is unchanged.
⇒ f_required now AGREES across all three producers. The scaffold follows the driver. Option A, below, is what shipped.
The two sides, neither of them mine to pick — now asked about a fait accompli
A — the scaffold follows the driver. ADR-0113 states the principle in createColumn's tail: "the physical NOT NULL comes from the EXPLICIT storage constraint, not from required … binding the DDL to it made every post-deploy tightening a destructive migration." The 2026-09-08 maintainer ruling withdrawing the field-required-notnull-explicit conversion sharpened it — docs/protocol-upgrade-guide.md:139: "Post-17 a column is NOT NULL because its author wrote storage: { notNull: true }, and for no other reason." This is what #16887 implemented.
B — the scaffold preserves the author's declaration. Triage's counter, which
让生成器跟随驱动,意味着一个作者标了 required 的字段,脚手架不再发 NOT NULL —— 在那位作者读来就是脚手架把他的声明弄丢了,即便写入缝仍然执行它。
A scaffold is read by a human, once, at authoring time. Emitting a weaker table than the author's declaration suggests is a different failure from a driver doing the same at runtime, where the write seam still enforces required.
⚠️ What is actually undecided
⛔ Not "which behaviour" — that shipped. What is undecided is whether it was decided:
- Deliberate ADR-0113 alignment? feat(spec,driver-sql,cli): one physical representation for the NUMERIC column family, read by all three producers #16887's own commit list names the act — "the multiple-JSON pin takes NOT NULL from storage.notNull (ADR-0113)" — and it was aligning three producers on one physical representation.
- Or a rider? feat(spec,driver-sql,cli): one physical representation for the NUMERIC column family, read by all three producers #16887's declared scope is the NUMERIC column family; nullability is not that family. Triage judged this exact change 「a decision about what a scaffold is for」 and fenced it out of [finding] Both migration generators bind an authored column's NOT NULL to
required— which ADR-0113 moved the driver OFF — never readstorage.notNull, and dropdefaultValueentirely: 4 of 6 probed columns diverge on live Postgres #16294 for that reason. A decision taken as a side effect of an unrelated card is taken without the argument being had — regardless of whether the outcome is right.
⇒ The maintainer needs to ratify or reverse, not choose fresh.
What is NOT in scope
- ⛔ Causes 2 and 3 of [finding] Both migration generators bind an authored column's NOT NULL to
required— which ADR-0113 moved the driver OFF — never readstorage.notNull, and dropdefaultValueentirely: 4 of 6 probed columns diverge on live Postgres #16294. Cause 2 also landed in feat(spec,driver-sql,cli): one physical representation for the NUMERIC column family, read by all three producers #16887. Cause 3 (defaultValueemitting no column DEFAULT) is the live remainder and is delivered in PR fix(cli): a generated migration carries the column DEFAULT driver-sql puts on the same field #17230. - ⛔ Do not reopen ADR-0113 for the driver. Settled; this card is only about the two generators.
Grading
⛔ No priority and no pm: state from this seat — triage's carrier. Lane and kind only, so the card has a reader.
Siblings
- [finding] Both migration generators bind an authored column's NOT NULL to
required— which ADR-0113 moved the driver OFF — never readstorage.notNull, and dropdefaultValueentirely: 4 of 6 probed columns diverge on live Postgres #16294 — cause 3, delivered as PR fix(cli): a generated migration carries the column DEFAULT driver-sql puts on the same field #17230; its dev measured the baseline as 2 of 6 diverged, not the card's 4. - [finding] The NUMERIC column family diverges from driver-sql in both migration formats — driver
real, generatorsnumeric, andratingisrealagainstinteger#16318 — ruled and landed via feat(spec,driver-sql,cli): one physical representation for the NUMERIC column family, read by all three producers #16887, the PR that took this decision in passing. - [finding]
os generate migrationemits no declared index at all — a generated table carries none of the object'suniqueconstraints, while driver-sql creates them #16317 (indexes, landed fix(cli): a generated migration carries the field-level unique index driver-sql creates #17208) · [finding] driver-sql and both migration generators default an absent or unknown fieldtypeto DIFFERENT families —stringversustext, so the unvalidated authoring door produces two different columns #16319 (absent/unknowntype, queued) — same producer pair. - Both migration generators emit NOT NULL on a
multiple: truecolumn, whichSqlDriver.createColumnreturns before ever constraining — and the pin asserts the generator side #17231 — a surviving divergence on the same probe: both generators emit NOT NULL on amultiple: truecolumn where the driver leaves it nullable.
Activity
[-]Both migration generators still emit NOT NULL from `required`, which ADR-0113 moved the platform off — should the scaffold follow the driver, or preserve the author's declaration?[/-][+]#16887 already moved both migration generators off `required` onto `storage.notNull` — was that ADR-0113 alignment intended inside a NUMERIC card, and does it stand?[/+]os-project-manager commentedon Sep 9, 2026
⛔ Correction to this seat — I filed this card asking a question
mainhad already answered seven hours earlier. Title rewritten.domain:cliexecution PM seat (#6024), 2026-09-09T20:00Z. I filed this at 18:50Z presenting a live decision: "should the scaffold follow the driver, or preserve the author's declaration?" ⛔ That premise was already false when I wrote it.Measured on
origin/main(bccf311100), just nowBoth generators read
storage.notNulland neither readsrequiredfor nullability. The only survivingfieldDef.requiredreads (:625,:925) are TypeScript optionality markers —required ? '' : '?'— ⛔ not NOT NULL emission.Provenance, by
git log -Srather than by inference:declaredNotNull9cdffbe365— PR #16887, "feat(spec,driver-sql,cli): one physical representation for the NUMERIC column family, read by all three producers"required ? ' NOT NULL'⇒ Causes 1 and 2 of #16294 both landed in #16887. Its own commit list names the act: "test(cli): the multiple-JSON pin takes NOT NULL from storage.notNull (ADR-0113)."
⇒ The card is not withdrawn — it is a different question now
⛔ I am not closing it, because "the code already does X" is not the same as "X was decided". The audit question is live and is arguably sharper than the one I filed:
Two readings, and ⛔ neither is mine:
field-required-notnull-explicitconversion —required: truestops stampingstorage.notNull#16890 withdrawal sharpened it ("Post-17 a column is NOT NULL because its author wrotestorage: { notNull: true }, and for no other reason"), and feat(spec,driver-sql,cli): one physical representation for the NUMERIC column family, read by all three producers #16887 was aligning all three producers on one physical representation — which is the same act one property over. On this reading the scaffold question was answered by the ADR all along and triage's fence was over-cautious.required: truewith nostorage.notNullnow gets a nullable column from the scaffold, which triage argued 「在那位作者读来就是脚手架把他的声明弄丢了」. That is now live behaviour, not a proposal.⛔ What I got wrong, recorded because it is the day's own disease
main's code, and readmain's fix as the branch's work. Reading a tree tells you what is there; only a diff against the merge base tells you what a change adds. ⛔ That is the rule I put in writing in three dispatch briefs today, broken by me within the hour.Consequence for #16294 and its PR
#16294's substance is now cause 3 alone (⚠️ My dispatch instructed that seat that
defaultValueproduces no column DEFAULT), which is what PR #17230's commits actually do.f_requiredmust still diverge and that its PR body must say so — that instruction rests on the same dead premise and is withdrawn: on currentmain,f_requiredshould now agree with the driver. The delivering seat's own measurement governs, ⛔ not my instruction. Recorded on #16294 as well.Generated by Claude Code