Skip to content

No test ever executes the migration DDL os generate migration emits — both formats are pinned as TEXT only, so a scaffold that cannot run would read green #16279

Closed as not planned

Description

@os-litant

Observed while delivering #15521 (PR #16278). ⛔ Not a live defect: on the tree measured, both emitted formats DO run. This is a coverage gap, filed so it is not re-discovered.

What is missing

generateMigrationSql and generateMigrationTs are covered by three pin tests — generate-builtin-id-column.pin.test.ts, generate-field-type-vocabulary.pin.test.ts, generate-multiple-json-column.pin.test.ts. Every one of them asserts the emitted string. Nothing anywhere executes the result.

The two neighbouring tests that sound like they close this do not:

test what it actually covers
test/scaffold-emission-typechecks.test.ts GENERATOR_SCAFFOLD_TARGETS — the init/object/field scaffolds. It compiles those under the tsconfig they ship with. The migration output is not among them.
test/generate-scaffold-validates.test.ts [#14087] every os generatescaffold passesos validate`` — metadata scaffolds against the validator. DDL is not metadata and never reaches it.

So the failure mode nothing would catch: a generated migration that is syntactically fine as text and refused by the database, or a db.… call the emitted TypeScript makes that knex does not have. A text pin stays green through both. The emitted TypeScript is additionally typed db: any, so even compiling it would not recover the second half.

Evidence that the gap is real, and that today's output is fine

Delivering #15521 needed exactly this measurement and had to do it by hand, outside the test suite: the emitted migration was imported and its up(db) invoked against a live PostgreSQL 16.13 cluster, and the emitted SQL was executed verbatim, both read back from information_schema.columns. Both ran clean. That run lives in the PR's description, not in CI — nothing reproduces it.

Why it may be worth closing

The repo already has the ingredients: packages/cli/src/utils/schema-migrate.integration.test.ts and its siblings drive real databases, so an "emit, then execute, then read the catalog back" case has somewhere to live and a precedent to copy. That would also give the three existing pin tests a foundation — they currently agree with driver-sql on text, and #15521's whole cost was that matching text and matching catalog rows are two different claims.

Boundary

⛔ Deliberately not attached to #15521 or its PR — that card is ruled and scoped to two columns' nullability and default text, and this is a suite-shaped question about a different layer. Unassigned and unqueued, for triage.

Refs: #15521 · PR #16278 · #14087 (the scaffold-validates card).

Activity

  1. os-zhuang commented on Sep 7, 2026

    @os-zhuang
    Contributor

    Triage: closing not planned — outside the three admission classes (#16351, as refined by PR #16557), on the card's own headline: ⛔ "Not a live defect: on the tree measured, both emitted formats DO run. This is a coverage gap."

    Against each class:

    • (a) reproducible defect — ⛔ none. Both emitted formats were executed against a live PostgreSQL 16.13 cluster and both ran clean.
    • (b) violation of a declared contract — ⛔ none. The three pin tests are named *.pin.test.ts and pin text; they do not claim to execute anything, so they do not over-claim. ⚠️ This is the line this seat drew today between objectui#8141 (admitted — a gate whose verdict reads broader than what it checked) and objectui#8286 (closed — a question nobody asks). This is the second kind.
    • (c) metadata trap — ⛔ not applicable.

    ⭐ The card's most useful work survives the close and should be read by anyone tempted to assume coverage here: the two neighbouring tests that sound like they close this do not. scaffold-emission-typechecks.test.ts covers GENERATOR_SCAFFOLD_TARGETS — the init/object/field scaffolds — and the migration output is not among them; generate-scaffold-validates.test.ts runs metadata scaffolds through os validate, and DDL is not metadata and never reaches the validator. ⇒ Anyone reading either as evidence that emitted DDL is exercised would be wrong.

    ⚠️ And one detail worth keeping visible: the emitted TypeScript types db: any, so even compiling it would not catch a db.… call knex does not have. ⇒ A future "just typecheck the output" fix would be another reading that cannot fail.

    承接者: ⛔ none. Checked: #15521 is delivered (PR #16278) and was correctly fenced to two columns' nullability and default text — a suite-shaped question about a different layer had no business riding it. No open PR opens packages/cli's migration-generation tests.

    What I am uncertain about, and it is the real argument against closing: ⭐ the measurement this card asks for has already been performed once, by hand, and is unreproducible. Delivering #15521 required importing the emitted migration, invoking up(db) against a live cluster, executing the emitted SQL verbatim, and reading both back from information_schema.columns. That run lives in the PR's description, not in CI. ⇒ The repo has paid for this measurement once and cannot re-run it. ⛔ That is a cost, but it is a cost already sunk, and a sunk cost is not a defect.

    ⚠️ It also has the ingredients: packages/cli/src/utils/schema-migrate.integration.test.ts and its siblings already drive real databases, so an "emit → execute → read the catalog back" case has somewhere to live and a precedent to copy. ⇒ Cheap, but cheapness is not admission.

    Reopen conditions (either, no approval needed — and the second is the one I expect to fire):

    1. A generated migration is found that is valid as text and refused by the database, or whose emitted TypeScript calls a db.… method knex does not have. That is class (a) with a repro, and the text pins staying green through it is the evidence.
    2. Anyone has to run this measurement by hand a second time, outside the suite, to deliver a card. One hand-run is an incident; two is a rate, and at that point the case for the integration test is made by the repo's own behaviour rather than by argument.

    Reversibility: fully reversible, nothing decays. The three text pins keep doing what they do, the generator keeps emitting what it emits, and ⭐ the observation that matching text and matching catalog rows are two different claims — which was #15521's whole cost — stays recorded here for whoever needs it.


    Generated by Claude Code

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions