Skip to content

os generate migration's audit-stamp columns diverge from driver-sql — the generators emit NOT NULL where the driver emits nullable, and the SQL format emits TIMESTAMP where both knex paths yield timestamptz #15521

Description

@os-litant

Measured by the os-dev seat on #15040 (session session_01D47qPfEWVPmhguWgBZCi5N, branch claude/issue-15040-generated-migration-id-column-shape, PR #15518) in the same pass as that card's id-column fix, on the explicit instruction of #15040's triage note. Filed by the domain:cli execution PM seat (#6024) as the routing that note's ⚠️ "⛔ do not leave it half-done" resolves to. Unassigned, bare, for triage.

This card records a divergence and does NOT rule it. #15040 deliberately changed none of these lines; see What #15040 did instead below.

What

Three sites, measured on the same tree as #15040:

side what it emits
packages/drivers/driver-sql/src/sql-driver.tscreateAuditTimestampColumn table.timestamp(name).defaultTo(this.knex.fn.now())nullable
generate.tsgenerateMigrationSql "created_at" TIMESTAMP NOT NULL DEFAULT now() (same for updated_at)
generate.tsgenerateMigrationTs (the default format) table.timestamps(true, true), which knex 3.3.0 compiles to .notNullable().defaultTo(CURRENT_TIMESTAMP) on both columns (knex/lib/schema/tablebuilder.js)

Compiled offline against knex's pg dialect — a pure compile, no connection:

driver   "created_at" timestamptz default CURRENT_TIMESTAMP
ts gen   "created_at" timestamptz not null default CURRENT_TIMESTAMP

⇒ Type, column name and default expression agree. Only nullability differs.

There is a second, narrower divergence that only the SQL format carries: its literal TIMESTAMP is timestamp WITHOUT time zone, while both knex paths yield timestamptz. That half is invisible from the TypeScript format, and it is a plain type disagreement rather than a judgment call — which is why this card asks for both halves in one look rather than only the nullability one.

⚠️ A third fact bears on any ruling: the driver's audit DDL is dialect-branched (datetime(3) on MySQL, a canonical ISO default on SQLite, per createAuditTimestampColumn's #11321 docblock) in a way a Postgres-flavoured generated migration does not reproduce at all.

Why it is NOT simply the same defect as #15040

#15040 was a wrong value: a platform id is a 26-character string and a uuid column refuses it outright (22P02 on the first insert), so there was exactly one correct answer and the PR could take it without a ruling.

Nullability is not that shape. Both sides are defensible and nothing fails either way: the driver stamps both columns on every write, so a NOT NULL scaffold is arguably the truer constraint, and nothing the platform writes can violate it. The cost of leaving it is not a failure but a permanent one: a generated table and a platform-created table keep different column definitions, so a schema diff between them is noisy forever.

Options as measured — ⛔ costs, not a recommendation

What #15040 did instead, so this cannot rot unnoticed

PR #15518 records the divergence as the last case of packages/cli/src/commands/generate-builtin-id-column.pin.test.ts, headed "Recorded divergence, NOT coverage, and NOT a ruling". It asserts the current shape on both sides — the two generator outputs, and that createAuditTimestampColumn's default arm carries no .notNullable() — with a failure message that says "driver-sql's audit-column DDL moved — re-read the #15040 record above before trusting it."

⇒ ⭐ Whichever way this is ruled, that it is the case to edit, and until then neither side can change shape silently.

Verified

Refs: #15040 · PR #15518 · #11321 (the dialect-branch docblock).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions