Skip to content

migration check rejects invariantId-carrying additive self-edges (NOOP_SELF_EDGE) its own invariants doc sanctions #30197

Description

@teamgbg

versions

  • @prisma/orm-postgres 8.0.0-rc.8
  • prisma CLI 8.0.0-rc.8 (dist bundle, skills sync 2026-09-02)

what we hit

A migration package whose from == to (a true contract self-edge) is refused by migration check with MIGRATION.CHECK_NOOP_SELF_EDGE unless it carries an op with operationClass: "data".

But the shipped skill reference (skills/prisma-8/references/... invariants module doc, deriveProvidedInvariants) states the two axes are orthogonal:

Both data-class ops (data-transforms, e.g. backfills) and additive-class opaque DDL (e.g. cipherstash's vendored EQL bundle via installEqlBundleOp) may declare invariantIds: ... an extension can ship additive non-IR-derivable DDL (the only way the planner can know the bundle is already applied is via the invariantId on the marker) without needing to mis-classify it as data-class.

installEqlBundleOp is cited as exactly the sanctioned shape: an additive, non-IR-derivable DDL self-edge whose invariantId records marker bookkeeping. Under the checker's current rule (pkg.ops.some(op => op.operationClass === "data") in the bundle), that package would be flagged NOOP_SELF_EDGE.

our concrete case (not hypothetical)

Two real migrations on a live project, authored the documented way (rawSql with precheck/execute/postcheck + invariantId, self-emitted ops.json + migration.json with populated providedInvariants):

  1. ALTER TYPE public.lane_liveness_state ADD VALUE 'capture_declared_off' (+ COMMENT ON TYPE)
  2. CREATE OR REPLACE VIEW public.cli_session_liveness AS ... (view bodies are not contract-visible, so a view-body replace can never be a from != to edge)

Both are pure additive DDL. Neither has any honest data component (a view replace has nothing to backfill), so neither can carry a data-class op, so both are refused as "true no-op self-edges" — which they are not: they change database objects and have real postchecks.

A parallel package in the same repo that DOES have a backfill passes, confirming the checker's data-only gate.

ask

Either:

  1. Relax the self-edge check to accept self-edges whose ops carry invariantIds (the documented marker-bookkeeping case), or
  2. Document that additive-only self-edges are unsupported and give the sanctioned pattern for non-IR-derivable DDL that has no data component (e.g. view-body replaces, enum-value additions that must precede dependent DDL in a separate transaction).

repro sketch

prisma migration new --name x --from <current-hash>   # no contract change
# migration.ts: one rawSql({ operationClass: "additive", invariantId: "ns:thing", precheck, execute, postcheck })
prisma migration check
# -> MIGRATION.CHECK_NOOP_SELF_EDGE

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions