Skip to content

Port migration plan, new and status onto the CLI engine; redirect the four retired status flags - #29982

Merged
wmadden merged 73 commits into
mainfrom
s5-orm-migration-write
Aug 12, 2026
Merged

Port migration plan, new and status onto the CLI engine; redirect the four retired status flags#29982
wmadden merged 73 commits into
mainfrom
s5-orm-migration-write

Conversation

@wmadden-electric

@wmadden-electric wmadden-electric commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

At a glance

After this PR, three more migration commands run on the new engine-based CLI, and the old migration status flags tell you where their functionality went instead of failing as unknown flags:

$ prisma-next migration status
migrations:  migrations
from:        0b4bec62…
app:
  ○   b18b261  @contract
  │↑  20260810T1108_…  ⧗ pending
  ○   0b4bec6
⚠ 1 pending — run `prisma-next migrate --to b18b261eb36b`

$ prisma-next migration status --graph
✗ CLI.COMMAND_MOVED  `migration status --graph` has moved
  → Use the replacement: prisma-next migration graph

The decision

We port migration plan, migration new and migration status from the old commander.js CLI onto @prisma/cli-engine, and we declare the four retired migration status flags (--graph, --all, --limit, --ref) as flag redirects, so an old invocation gets a typed CLI.COMMAND_MOVED error naming the replacement command instead of an "unknown flag" failure.

Background for readers new to this series: we are moving the CLI's commands, one batch per PR, from the commander.js implementation onto the engine, which owns argument parsing, output formatting (--json/--quiet), and a typed result protocol — every run ends in a result envelope carrying an exit code, optional diagnostics, and typed next actions. migration list, show, log and graph were ported in earlier PRs; this PR is the remaining three read/write commands, their tests, and the redirect declarations.

How the change builds up

1. The three commands. Each is defined through the shared defineOrmCommand helper and registered in the ORM command family (src/orm/family.ts). migration new scaffolds a migration file, migration plan previews the steps and SQL to reach a target contract, migration status reports pending migrations against the database marker.

2. migration status is the first ported command with typed diagnostics. It can hit three conditions while still answering the question it was asked: CONTRACT.UNREADABLE, MIGRATION.MARKER_NOT_IN_HISTORY, and MIGRATION.MISSING_INVARIANTS. Each is reported as a severity: 'warn' diagnostic on a successful (exit 0) result — finding these conditions is the command doing its job, not the command failing. Each finding appears both in the --json document and as an engine diagnostic with next actions (src/orm/migration/status-findings.ts). Tests assert code, severity and exit code together, since that trio is the behavioral contract.

3. The flag redirects become possible. The engine requires a flag redirect to name a mounted command, so the four retired migration status flags could not be declared until migration status itself existed on the engine. They were recorded as a debt in the port's findings file and are closed out here: --graph points at migration graph, --all and --limit at migration log --db <url>, and --ref at migration status --to <contract>.

Intentional divergences from the commander CLI

  • --legend no longer errors under --json/--quiet. Engine 0.0.8 gives a handler no view of the active output format, so the commander's MIGRATION.LEGEND_HUMAN_ONLY error cannot be implemented; the flag is silently ignored, matching the already-merged migration list and graph.
  • Glyph auto-detection is gone across all four tree commands: the commander picked ASCII when off-TTY or in a non-UTF-8 locale; the port draws Unicode unless you pass --ascii.
  • Header cards lose the config: row and the command title. migration new gains a header where the commander printed an empty one.
  • The commander's Next: prose becomes typed next actions; the missing-invariants text block becomes an engine-rendered diagnostic.
  • Human output moves to stderr; stdout is empty for all three commands (there is no machine payload outside --json).

Reviewer notes

  • {bin} substitution applies to help examples and redirect replacements, but not to NextAction.command. A redirect renders as <your binary name> migration graph, while a next action names prisma-next literally. Worth knowing before the binary is ever renamed.
  • migration plan resolves its origin from the db ref, not the graph tip — test fixtures need a db ref plus a contract snapshot, or every plan comes out greenfield. This cost real debugging time and is recorded for the next porter.

Verification

pnpm build, pnpm --filter @internal/cli test (134 files, 1628 tests), pnpm typecheck, pnpm lint, pnpm lint:deps, check:error-reference, and the CLI journey suite (49 files, 134 tests). Each command was also run through the built binary against the demo project and diffed against the commander output, through a pty, with colour and with NO_COLOR=1.

Two environmental problems surfaced, neither caused by this change and both recorded: init-journey fails when two worktrees pack into the same shared tarball cache and the installer reads a half-written archive, and test:integration is unreliable under parallel load on this machine — 46, 30 and 5 failures across three runs, all passing when run in isolation.

Alternatives considered

  • Fail the run when migration status finds a problem (error-severity diagnostics, nonzero exit). Rejected: the command still delivered its full answer, and the engine refuses an error-severity diagnostic on a run that exits 0 — so the choice is genuinely binary, and warn-at-exit-0 matches what the command means.
  • Port the commander's MIGRATION.LEGEND_HUMAN_ONLY error for --legend under --json. Not implementable on engine 0.0.8 (handlers cannot see the active format); silently ignoring the flag keeps the three tree commands consistent rather than special-casing one.
  • Declare the retired flag redirects in an earlier PR. Impossible: the engine validates that a redirect names a mounted command, and migration status did not exist on the engine until now. The interim was an explicit recorded debt, not an oversight.
  • Port glyph auto-detection (ASCII off-TTY / non-UTF-8 locale). Dropped in favour of the explicit --ascii flag, consistent with the already-ported tree commands.

wmadden-electric and others added 30 commits August 9, 2026 13:30
…ion marker

Config loading no longer fails wholesale on the first structural problem.

- loadConfig now returns Result<{ config, diagnostics }, CliStructuredError>.
  Structural problems in an evaluated config become CONFIG.VALIDATION_FAILED
  diagnostics tagged with the config section they concern (meta.section,
  meta.field). Commands fail (exit 2, rendering the diagnostic) only when a
  diagnostic concerns a section they read, via requireConfigSections /
  loadConfigForSections; commands not touching that section proceed.
- A config module that cannot be evaluated at all fails the load with the
  new CONFIG.EVALUATION_FAILED (previously surfaced as CLI.UNEXPECTED).
- defineConfig stops validating and throwing: a throw there happens at
  module-evaluation time and would turn every structural problem into an
  all-commands-fail evaluation error. Validation moved to the loader via
  collectConfigIssues, which reports every problem instead of the first.
- defineConfig now normalizes and stamps a non-enumerable config-format
  version marker; the loader rejects configs that were not created by the
  current defineConfig with the new CONFIG.VERSION_MARKER_MISSING (fail
  early, no best-effort reading of unmarked configs). The marker is read
  from c12's raw layer because the c12 merge drops non-enumerable
  properties.
- All production call sites (CLI commands and operations, language server,
  Vite plugin, cli-telemetry) consume the new API. Ten commands that
  previously leaked config errors as unhandled throws (stack trace,
  exit 1) — including db init and db update — now render the structured
  envelope and exit 2.
- Error registry: CONFIG.EVALUATION_FAILED and CONFIG.VERSION_MARKER_MISSING
  added; CONFIG.VALIDATION_FAILED updated with the new meta shape and
  producing sites.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…sting

Adds createFixtureControlClient, a ControlClient double for host and
product tests: every operation resolves realistic postgres-flavored
fixture payloads without touching a database or driver, each fixture is
overridable per test, and every call is recorded for assertions.

The implementing class `implements ControlClient` and a type test asserts
the double stays in sync with the real client, so an interface change
fails compilation here. Published via the @internal/cli
./control-api/testing subpath, which the shell build maps to
@prisma/orm-toolchain/cli/control-api/testing automatically.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
The framework domain is family-blind, and `lint:framework-vocabulary`
counted seven new SQL/target-vocabulary lines: six in the fixture
ControlClient defaults and one in the `CONFIG.VERSION_MARKER_MISSING`
fix text.

The fixture defaults now report neutral `FIXTURE_TARGET_ID` /
`FIXTURE_FAMILY_ID` ids (both exported so tests can assert them), an
operation label that names the model rather than the DDL, and an empty
introspection payload. The marker error points at the target package
config entrypoint without naming a target; the error reference keeps the
concrete example.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
The example-migration regen writes a temporary config that spreads the
example's real config. The version marker is non-enumerable, so the
spread dropped it and every emit failed with
CONFIG.VERSION_MARKER_MISSING. Passing the spread result through
defineConfig re-stamps it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…tion

src/load.ts sat below the package coverage thresholds: loadConfigForSections
had no test at all, and none of the toConfigLoadFailure arms were exercised
beyond the plain-Error case.

Adds tests for loadConfigForSections (clean, blocking diagnostic, and load
failure), for a config module that throws a CliStructuredError, a plain
structured error, a non-Error value, or fails to resolve an import, and for
a contract that declares no inputs.

finalizeConfig grew a finalizeContractConfig half that takes a contract
section directly, so the loader no longer re-checks a contract it has
already narrowed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
`lint:legacy-name` rejects `prisma-next` outside the allowed uses, and the
unresolvable-import test used it in a made-up package name. Any
unresolvable specifier works.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
The `orm` command family owns one config section, and the whole Prisma
Next configuration nests under it: `contract`, `db`, `migrations` and the
rest become subsections of `orm`. The engine models one section per
family, so per-subsection blocking does not survive the nesting — any
structural problem anywhere in the section blocks every `orm` command.

The validator is synchronous and total: it takes the raw section value,
returns engine diagnostics, and never throws. Structural checking reuses
`collectConfigIssues`; the emitted-artifact-as-contract-input check moves
here too, since it needs neither the filesystem nor the config directory.
Everything the loader does asynchronously — evaluating the file and
finalizing paths against its directory — stays in the bin adapter.

`@prisma/cli-engine` is pinned at an exact version so the shell and the
family share one module instance. It is a first-party Prisma package, so
it joins the release-age cooldown exemption list for the same reason
`@prisma/dev` is on it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
The engine and prisma/prisma each define a class called
`CliStructuredError`, and the engine's duck-typed guard accepts both. The
two are not the same shape: the engine settles on `error.nextActions`,
which prisma/prisma's class never sets, and prisma/prisma emits `fix`
prose, which the protocol has no field for. Left unconverted, an error
settles as an envelope with `nextActions: undefined` and a stray `fix`.

`normalizeError` is that conversion, and it is the only one: handlers
pass what they return through `notOk` and what a top-of-handler catch
sees through the same helper. Everything below keeps raising exactly what
it raises today. `nextActions` is always present, derived from the `fix`
prose while the transition lasts — one action per line of it, since a
multi-line fix is several pieces of advice.

`toEngineDiagnostic` is the same projection without the throw, for
callers that need a diagnostic rather than an error.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
The engine takes its config from the bin, and its own convenience loader
reads prisma.config.ts with the $prismaConfig marker — not the file this
bin reads. So the bin owns the load: the ORM's c12 loader evaluates the
module and finalizes contract paths against the config file's directory,
and the adapter nests the whole result as the single `orm` section.

Only failures that prevent evaluation entirely — no file, a module that
does not evaluate, a missing version marker — become diagnostics here,
tagged `section: null` so they fail exactly the commands that read
config. A config that evaluates but is structurally wrong passes through
untouched; that verdict is the section validator's.

Config discovery now takes the directory as a parameter instead of
reading process state, so the adapter honours the cwd the engine was
given. That is what lets a test harness run several projects in one
process, and it defaults to process.cwd() for every existing caller.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
The `orm` command family, the second prisma-next binary, and the three
telemetry commands, all landing beside the commander program rather than
replacing it — the old bin still owns `bin.prisma-next` until the
retirement round.

The family carries the config section, an empty command map for now, and
the docs base the engine appends each diagnostic code to. `telemetry
status|enable|disable` are deliberately NOT family members: they are
commands of this binary only, the unified shell has its own, and they
retire with the binary at cutover.

Telemetry now reports from `onSettled`, so the event carries the exit
code. Two consequences, both intended: a run killed before settlement
emits nothing, and so does a run that never reaches a mounted command.
The old `telemetry`-group exemption is gone with the pre-run fire — under
`onSettled`, `telemetry disable` has already disabled by the time the
hook would send. The wire shape is unchanged: the engine's value-free
snapshot is projected into the shape the existing sanitiser reads, and
the detached sender still derives the two config-derived fields itself.

The pinned engine has no shell-level `--config`, so the bin reads the
flag off argv and strips it before the engine parses. That interim is
deleted when a version carrying the engine's own `--config` publishes.

`--db` gets one shared spec so its brief and placeholder cannot drift
across the commands that will declare it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
The first ported command, and the template the rest copy: one file per
command under src/orm/<group>/, definition and handler together, the
handler calling the existing control-API operation with config and cwd
from the context rather than loading either itself.

The tree renderer keeps its line-producing code and its lines ship as the
`stdout` presentation — the engine writes them to stdout in human mode
and drops them in json mode, which keeps the json document the only
machine surface. They are deliberately not `list` blocks: block rendering
prefixes every item with "- ", which would glue a bullet onto box
drawing. Colour is off until the engine exposes its resolved colour mode.
The header details become a fields block on stderr.

The old commander `migration list` is untouched and still owns the
shipped binary; this one runs on the second bin.

Two behaviour changes fall out of the engine surface, both divergences.
`--legend` is now human-only decoration rather than an error when
combined with `--json` or `--quiet`: a handler cannot see the resolved
format, and in json mode the human presentation is never materialized, so
the legend simply does not appear. And migrations resolve against the
invocation directory rather than the config file's directory — the same
place for every invocation that does not pass the interim `--config`.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
Journeys have always run in-process against commander factories, calling
`process.chdir` and passing `--config` on every step — which is why their
vitest config needs `pool: forks`. `runOnEngine` is the replacement: it
builds a fresh `TestCli` per step and passes the step directory as `cwd`,
so nothing about a run is process-global.

The harness takes config as an already-evaluated record and `run()` has
no config option, so the journey's real `prisma-next.config.ts` is
evaluated here, through the same adapter the binary uses. Evaluating it
per step is what lets a step that writes or rewrites the config be picked
up by the next one, which is what `init` journeys will need.

Only `migration list` moves onto it this round — it is the only ported
command. Every other wrapper still runs the commander factory, and both
paths coexist until the shell is retired. The new journey is the proof
the harness works against a real project on disk: two planned migrations,
the tree on stdout in human mode, a clean frame stream in json mode, and
an unknown space settling as an errored envelope with typed next actions
and no `fix`.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
Six operation modules loaded config themselves and fell back to the
process cwd for path resolution. Under a harness that supplies a
directory per run, that is silently wrong — the operation would read a
different project than the one the command was invoked against. They now
take the already-loaded config and the invocation directory as explicit
parameters, and no file under src/control-api/ calls the config loader or
reads process.cwd. `resolveMigrationPaths` takes the directory too.

`contract emit` loses its double load in the process: the command already
loaded config to compute header display paths, and that result is now
what the operation receives.

Two ordering changes fall out of hoisting the load, both only observable
when the config is broken. `contract emit`'s header load now asks for all
five sections rather than just `contract`, so a config broken elsewhere
errors before the header prints. And `ref set` loads config before
checking the ref name, so an invalid name plus an unloadable config
reports the config error.

`migration check` was not on the list but is under the same directory and
had two process-cwd reads, so it takes a cwd too. Its
`enumerateCheckSpaces` is re-exported, making that a published signature
change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…rting

`telemetry status|enable|disable` already exist on the prisma-cli side
and will be served by the unified binary, so the prisma-next bin does not
carry its own copies — they would only die again with the commander shell
at cutover. The commander CLI still ships its versions until then; this
removes the engine ports written earlier in the round.

Reporting is unaffected: the bin still wires the engine's onSettled hook
to this repo's telemetry sender.

`@prisma/orm-toolchain` declares the same exact engine pin as
`@internal/cli`, which the shell build check requires — the two must
resolve one module instance rather than two copies with distinct brands
and classes. The init journey installs into a scratch project outside the
workspace, so it needs its own release-age exemption for that pin.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
The typed remediation is produced in the command layer and nowhere else: a
run-command action names an executable CLI invocation, which is knowledge
only the CLI has. `src/utils/cli-errors.ts` is CLI-package code, so its
fourteen factories attach the actions directly; `@internal/errors` and
every other library keeps raising `code`/`why`/`fix` prose, and no
foundation package learns the NextAction type.

The factories now build an `ActionableCliError`, a CLI-package subclass
that carries both fields. `fix` stays because the commander shell still
renders it and every pre-cutover change is additive; the handler boundary
is what drops it. `normalizeError` therefore prefers a raised error's typed
actions over deriving them from prose, and only short-circuits for an
error the engine itself built — identity, not a duck test, since the
engine is an exact-pinned unbundled dependency with one module instance.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
The `--json` document `migration check` publishes is the CLI's own output, so
its failure rows carry the typed remediation rather than `fix` prose:
`checkFailureSchema` swaps the `fix: string` field for `nextActions`, and
the two producers — the integrity-violation catalogue and the explicit
per-space graph checks — build actions instead of sentences.

This is a breaking change to the published `--json` shape, and the one
user-visible change in this PR that reaches the commander shell: its human
output now prints a `next:` line per action where it printed one `fix:`
line. Recorded for the divergence file.

The two action constructors move to their own module so the check
producers and the error factories share them without the check path
importing the error module.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
The second ported command, and the first with a positional. The detail
block — metadata, the operation tree, the DDL preview — is a rich
renderer, so its lines keep their line-producing code and ship as the
`stdout` presentation exactly as `migration list`'s table does; the header
details become a fields block on stderr, minus the `config` row, since
config discovery is the shell's and no ported command declares `--config`.

A path-looking target used to resolve against the process working
directory. It now resolves against `ctx.cwd`, which is what makes the
command correct under a harness that supplies a cwd per run;
`resolveAppTargetPath` and `resolveTargetPathAcrossSpaces` take it
explicitly and `migration check` threads its own through unchanged.

The path-resolution helpers move to a sibling module the migration
commands share, and the two errors `migration show` raises on its own
become named factories carrying typed next actions, used by both the
commander command and the ported handler.

The old commander `migration show` is untouched and still owns the
shipped binary.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
The ledger table keeps its line-producing code and ships as the `stdout`
presentation; the masked connection URL becomes the one header field on
stderr, the `config` row going away with per-command `--config`.

Carries the dotted-code fix the contract calls for: an unsupported target
raised `CLI.UNEXPECTED` here where every sibling raises
`MIGRATION.TARGET_UNSUPPORTED`. The ported command raises the migration
code, which is already documented in the error reference.

`--db` comes from the shared flag constant, so its brief and placeholder
cannot drift from the other database-touching commands.

The old commander `migration log` is untouched and still owns the shipped
binary.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
The engine reserves --format, so DOT cannot become a format value. Instead
the precedence quirk goes away: with --dot the DOT text is the command's
stdout payload in human mode, and in json mode the result carries it as a
`dot` field alongside the graph document, so a caller asking for json
never gets DOT where json was promised. --dot with --legend stays an
error and --dot still ignores --space.

Tree-section building, the human rendering and the DOT rendering move to
a formatter module the commander command and the ported handler share, so
the two cannot drift while they coexist.

Header paths render relative to the invocation directory, as the commander
shell rendered them. `migration list` moves to the same helper — it was
printing the absolute migrations directory, which was a needless
divergence from the shell it replaces.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
`migration show`, `migration log` and `migration graph` join `migration list`
on `runOnEngine`, so their journeys drive the ported commands rather than
the commander factories. Assertions move from captured stdout to the
envelope, the presented document and the exit code.

The DOT journey changes shape because its premise did: it used to pin that
an explicit `--dot` beat the auto-JSON default when stdout was piped.
Under the engine that precedence problem cannot arise — piping selects
json and the DOT rides the result as a `dot` field alongside the graph
document, so a caller who asked for json is never handed DOT. The journey
now pins both halves plus the `--legend` refusal.

`migration log` gets the end-to-end coverage it has never had: two
migrations applied, both edges reported from the live ledger, chained
so each edge starts where the last ended.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
The seeded operation used an operationClass the op schema rejects, so
the loader dropped the package and the assertions ran against an empty
project — including one that expected the empty-project line right
after seeding. Seed a valid operation, assert the rendered table, and
cover the empty project as its own case.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
The four `as Record<string, unknown>` casts in `config-validation.ts` broke
the repo rule against bare `as` in production code, and they also hid a
reporting wart: a section holding a non-object (`family: 'sql'`) walked the
descriptor fields anyway and produced one issue per missing field instead
of one clear "must be an object" issue.

`isObject` already narrows to `Record<string, unknown>` without a cast, so
`validateFamily`, `validateTarget`, and the `adapter`/`driver` walks now
use it and report a single object-type issue per malformed section.

Also rewrites the rulecard's "Default Path Resolution" example, which told
authors to use `pathe` but then showed only a bare string fallback. It now
shows what the loader actually does: resolve the config-relative default
against the config file's directory.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
Two defects in `loadConfig`, both reported against the wrong file.

First, load failures were classified by searching the error message for
'not found', 'Cannot find' or 'ENOENT'. A config that imports a missing
package throws `Cannot find module 'x'`, so the user was told their config
file did not exist when it did. Verified against c12: a missing config
file never throws — c12 resolves nothing and returns an empty config,
which `loadConfig` already maps to CONFIG.FILE_NOT_FOUND on the
non-throwing path. So everything reaching this catch came from evaluating
a file that exists, and all of it is now CONFIG.EVALUATION_FAILED.

Second, the version-marker check accepted the marker from any c12 layer,
including `extends` bases and rc files. A config that does not itself go
through `defineConfig` passed as long as some base did. c12 puts the
requested config file first in `layers` (we pass no `overrides`), so the
check now reads only that layer.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
A command that reads a section it did not declare bypasses section-scoped
diagnostics: the malformed value flows into execution and fails later as
something else. `migration status` turned a malformed `contract` section
into a CONTRACT.UNREADABLE warning; `migrate` failed while resolving the
contract path.

Swept all 21 `loadConfigForSections` call sites against the sections each
one reads, directly or through a helper (`resolveContractPath`,
`readContractEnvelope`, `loadContractRawSafely`, `buildReadAggregate`,
`loadAggregateIntegrityViolations` read `contract`; `resolveMigrationPaths`
reads `migrations`). Eleven were short:

- `contract` added to migrate, migration status, migration list,
  migration graph, migration check, migration ref set, migrate --show
- `migrations` added to migration log, db sign, db verify, and the shared
  migration-command scaffold (db init, db update)

`driver` is deliberately not added to the read-only commands that build a
control stack. `createControlStack` stores the descriptor but only
database operations use it, and every command that connects already
declares `driver`; requiring it everywhere would defeat section scoping.

The regression test lives in the integration suite rather than the CLI
package: the CLI suite runs with `isolate: false`, so a new file importing
command modules collides with the per-file `@internal/config-loader` mocks
its neighbours already register. The integration test drives the real
commands against a real malformed config and covers the command handlers
that are module-private.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…ycle

The double served every operation from fixtures whether or not `connect()`
had been awaited, so a caller that forgot to connect passed its tests and
then failed against the real client.

It now tracks initialization the way `ControlClientImpl` does: `init()` is
idempotent, `connect()` calls it, and the eleven operations the real
client routes through `ensureConnected` (verify, schemaVerify, sign,
dbInit, dbUpdate, dbVerify, readMarker, readAllMarkers, readLedger,
migrate, introspect) reject with DRIVER.NOT_CONNECTED while disconnected.
The five that only need `init()` in the real client (toSchemaView,
inferPslContract, getPslBlockDescriptors, toOperationPreview, emit) keep
working without a connection.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…s from

`resolveConfigInputs` required family, target, adapter, driver and
extensions before it knew whether the project was PSL or TypeScript. A
TypeScript contract project derives its inputs from `contract.source`
alone and never builds a control stack, so a diagnostic on an unrelated
control section stopped formatting and analysis for it.

It now loads once and narrows twice: `contract` and `formatter` for every
project, and the control sections only on the PSL branch that actually
assembles the stack.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
The real client connects from options.connection before it checks for
a driver, so an operation that supplies one needs no prior connect().
The double rejected first, failing calls production accepts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
Reworking the load-failure classification and the marker check changed
load.ts branch count and dropped it under the 95% threshold. The three
uncovered arms are defensive fallbacks for conditions their callees
exclude: a non-Error throw, and two c12 fields that are always set on
the paths that reach them. Each is annotated with its reason.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…to s5-orm-cli-port

Brings in the seven review-fix commits that landed on the base branch after
this port forked from it, and reconciles them with the port's refactor.

The two conflicts are the same shape: the review fix added `contract` to a
`loadConfigForSections` call inside a control-API operation, while the port
had removed that call entirely — operations now take `config` and `cwd` as
parameters. Kept the port's parameterized form and moved the added section
to the caller that now loads config:

- `migrate --show` (`commands/migrate.ts`) gains `contract`
- `ref set` (`commands/ref.ts`) gains `contract`

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
wmadden-electric and others added 13 commits August 12, 2026 09:23
pnpm links dependencies strictly per package, and the CLI package does not
declare @prisma/orm-postgres, @prisma/orm-mongo or dotenv. A test project
created in the OS temp directory therefore cannot resolve them, so a
scaffolded prisma-next.config.ts written there fails to import its own
config entrypoint. That is invisible when the test happens to resolve from
the process working directory instead, and it fails in CI.

The fixture package declares those dependencies and nothing else; test
projects are created underneath it so Node walks up into its node_modules.
Its ephemeral test-* subdirectories are gitignored, matching how the
integration suite already treats its own fixture apps.

Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
The offline write commands ran against a project in the OS temp directory,
outside the repository, where no workspace dependency is linked. It joins
the rest of the ORM command tests in the fixture package that declares
them.

Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
init chooses the package manager by walking up from the project it is
scaffolding. A project inside the repository walks out of the fixture package
and finds the repository's pnpm lockfile, so init treats it as part of a pnpm
workspace and reports the repository's catalog as the project's own. Naming a
manager on the fixture package ends the walk there and gives a test project the
same neutral answer it had in the OS temp directory. A test that wants a
different manager writes a lockfile into its own project, which detection finds
first.

Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
The fixture package declared a package manager to stop detection walking
up into the repository. That was fighting the point of putting test
projects in the repository: a project here really is inside a pnpm
workspace, and pnpm is the correct answer. Which manager runs an install
belongs to the CLI engine, so no ORM test should pin it.

Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>

# Conflicts:
#	.agents/rules/no-family-vocabulary-in-framework.mdc
#	scripts/lint-framework-vocabulary.config.json
#	scripts/lint-framework-vocabulary.mjs
#	scripts/lint-framework-vocabulary.test.mjs
Two ways ANSI reached output that should carry none. The on-path label
applied the palette's emphasis whatever colorize said, so a run with
colour turned off on a capable terminal still got bold on the directory
name — every sibling painter already honoured the flag.

And the span reader matched an SGR sequence carrying one parameter, so
a sequence carrying several survived into a span and put raw escape
bytes in front of the engine. It now reads the whole parameter list and
applies each code, which strips the shapes a 256-colour or combined
style emits.

Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
Base automatically changed from s5-orm-adopt-engine-8 to main August 12, 2026 09:24
The vocabulary checker is now a Biome GritQL plugin, so the old
`framework-vocabulary-ignore` comment no longer suppresses anything and was
left behind as an inert comment by the merge. Restate it as the suppression
the plugin actually reads, keeping the original reasoning.

Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/1-framework/3-tooling/cli/src/utils/formatters/migration-graph-labels.ts (1)

310-328: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Align the on-path branch with the off-path fallback.

The off-path branch at line 318 falls back to style.dirName when opts.colorize is false. The on-path branch at line 315 does not; rolePainter returns identity in that case. An on-path migration name therefore skips style.dirName while every other name goes through it. If a caller supplies a styler that adds non-colour decoration, on-path names lose it.

♻️ Proposed fix
   if (highlight === 'on-path') {
     // On-path: tint the name with the on-path green (matching the route's green
     // glyphs in the gutter), not bolded.
-    dirNameStyler = rolePainter(palette, opts.colorize, 'on-path');
+    dirNameStyler = opts.colorize ? rolePainter(palette, true, 'on-path') : style.dirName;
     hashOverride = undefined;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/1-framework/3-tooling/cli/src/utils/formatters/migration-graph-labels.ts`
around lines 310 - 328, Update the on-path branch in the highlight selection
logic to use style.dirName when opts.colorize is false, matching the off-path
fallback; retain the rolePainter-based on-path styler when colorization is
enabled and leave hashOverride unchanged.
🧹 Nitpick comments (3)
packages/1-framework/3-tooling/cli/src/orm/cli.ts (1)

60-69: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove the Biome suppression.

Line 67 disables lint/plugin/no-family-vocabulary. The TypeScript rules prohibit Biome suppressions. Replace the local suppression with an approved adapter or request an explicit exception.

As per coding guidelines, “Never suppress Biome lints.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/1-framework/3-tooling/cli/src/orm/cli.ts` around lines 60 - 69,
Remove the biome-ignore suppression in runtimeFromProcess and preserve the
stderr.columns behavior without inline Biome suppression. Use the project’s
approved adapter for the terminal-width property, or obtain an explicit lint
exception through the established configuration process if no adapter exists.

Source: Coding guidelines

packages/1-framework/3-tooling/cli/src/orm/migration/show.ts (1)

41-100: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Operation and preview block builders are duplicated across two commands. operationNodes, operationBlocks, and previewBlocks exist in both files with the same structure and the same destructive warning text. The differences are limited to the tree root label, the empty-operations result, and the optional-preview guard.

  • packages/1-framework/3-tooling/cli/src/orm/migration/show.ts#L41-L100: move these three helpers into a shared formatter module and accept the tree root label and the empty-operations block as parameters.
  • packages/1-framework/3-tooling/cli/src/orm/migration/plan.ts#L50-L99: delete the local copies and call the shared helpers, passing result.dir ?? 'operations' as the label and an empty block list for the empty case.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/1-framework/3-tooling/cli/src/orm/migration/show.ts` around lines 41
- 100, Extract operationNodes, operationBlocks, and previewBlocks into a shared
migration formatter module, parameterizing operationBlocks with the tree root
label and empty-operations block while preserving the shared destructive warning
and preview formatting. In
packages/1-framework/3-tooling/cli/src/orm/migration/show.ts#L41-L100, replace
the local helpers with calls to the shared implementations using show’s existing
values. In packages/1-framework/3-tooling/cli/src/orm/migration/plan.ts#L50-L99,
remove the duplicate helpers and call the shared helpers with result.dir ??
'operations' and an empty block list for the empty case, retaining the
optional-preview guard.
packages/1-framework/3-tooling/cli/src/utils/formatters/migration-graph-palette.ts (1)

21-35: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Derive the lane count from the lane arrays.

LANE_COUNT is a separate constant. ANSI_LANES and TONE_LANES each hold six entries today. If a colour is added to one array only, lane % LANE_COUNT silently ignores it and the ?? fallbacks hide the mismatch.

♻️ Proposed fix
-const LANE_COUNT = 6;
-
 const forced = createColors({ useColor: true });

 const ANSI_LANES = [
   forced.white,
   forced.cyan,
   forced.yellow,
   forced.blueBright,
   forced.magenta,
   forced.green,
 ] as const;
-  lane: (lane, text) => (ANSI_LANES[lane % LANE_COUNT] ?? ((value: string) => value))(text),
+  lane: (lane, text) => (ANSI_LANES[lane % ANSI_LANES.length] ?? ((value: string) => value))(text),
-  lane: (lane, text) => tonePainter(TONE_LANES[lane % LANE_COUNT] ?? 'color-1')(text),
+  lane: (lane, text) => tonePainter(TONE_LANES[lane % TONE_LANES.length] ?? 'color-1')(text),

Also applies to: 45-45

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/1-framework/3-tooling/cli/src/utils/formatters/migration-graph-palette.ts`
around lines 21 - 35, Remove the standalone LANE_COUNT constant and derive the
lane count from the lane array length, using the same derived value wherever
lane modulo or bounds logic is applied. Ensure ANSI_LANES and TONE_LANES remain
aligned, and avoid fallback behavior that hides mismatched array lengths.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/1-framework/3-tooling/cli/src/orm/migration/paths.ts`:
- Around line 25-34: Replace the cwd-derived path from projectConfigPathFor with
the selected configuration’s LoadedConfig.path when invoking migration new and
migration plan handlers. Propagate that effective path through the relevant
handler context so createProjectSpecifierResolver anchors manifest discovery to
the actual config location, and add a regression test covering --config pointing
outside cwd.

In `@packages/1-framework/3-tooling/cli/src/orm/migration/plan.ts`:
- Around line 42-45: Update the migration plan path construction for
emittedExtensionDirs and written paths to use the configured migrations
directory from config.migrations.dir instead of the hardcoded "migrations"
segment. Preserve the existing spaceId and dirName path structure in both
rendering locations.

In `@packages/1-framework/3-tooling/cli/src/orm/migration/status-findings.ts`:
- Around line 72-78: Add severity: 'warn' to the MIGRATION.MISSING_INVARIANTS
JSON schema and both document construction paths, including the object returned
by the missing-invariants finding builder. Update affected fixtures to include
the new required severity field.

In `@packages/1-framework/3-tooling/cli/src/orm/migration/status.ts`:
- Around line 399-402: Update
packages/1-framework/3-tooling/cli/src/orm/migration/status.ts at lines 399-402
so markerNotInHistoryFinding receives entry.space and includes that space in its
message and meta, preserving distinct findings for diverged spaces. At lines
480-495, record the space whose hasMigrationPath check fails and pass that
space’s marker and target hash to buildNoPathSummary instead of the app-space
values.

---

Outside diff comments:
In
`@packages/1-framework/3-tooling/cli/src/utils/formatters/migration-graph-labels.ts`:
- Around line 310-328: Update the on-path branch in the highlight selection
logic to use style.dirName when opts.colorize is false, matching the off-path
fallback; retain the rolePainter-based on-path styler when colorization is
enabled and leave hashOverride unchanged.

---

Nitpick comments:
In `@packages/1-framework/3-tooling/cli/src/orm/cli.ts`:
- Around line 60-69: Remove the biome-ignore suppression in runtimeFromProcess
and preserve the stderr.columns behavior without inline Biome suppression. Use
the project’s approved adapter for the terminal-width property, or obtain an
explicit lint exception through the established configuration process if no
adapter exists.

In `@packages/1-framework/3-tooling/cli/src/orm/migration/show.ts`:
- Around line 41-100: Extract operationNodes, operationBlocks, and previewBlocks
into a shared migration formatter module, parameterizing operationBlocks with
the tree root label and empty-operations block while preserving the shared
destructive warning and preview formatting. In
packages/1-framework/3-tooling/cli/src/orm/migration/show.ts#L41-L100, replace
the local helpers with calls to the shared implementations using show’s existing
values. In packages/1-framework/3-tooling/cli/src/orm/migration/plan.ts#L50-L99,
remove the duplicate helpers and call the shared helpers with result.dir ??
'operations' and an empty block list for the empty case, retaining the
optional-preview guard.

In
`@packages/1-framework/3-tooling/cli/src/utils/formatters/migration-graph-palette.ts`:
- Around line 21-35: Remove the standalone LANE_COUNT constant and derive the
lane count from the lane array length, using the same derived value wherever
lane modulo or bounds logic is applied. Ensure ANSI_LANES and TONE_LANES remain
aligned, and avoid fallback behavior that hides mismatched array lengths.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro Plus

Run ID: 8576ea22-7660-4c63-8d58-5baa134ab267

📥 Commits

Reviewing files that changed from the base of the PR and between 941195d and 269df06.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (58)
  • packages/1-framework/3-tooling/cli/.gitignore
  • packages/1-framework/3-tooling/cli/package.json
  • packages/1-framework/3-tooling/cli/src/orm/cli.ts
  • packages/1-framework/3-tooling/cli/src/orm/family.ts
  • packages/1-framework/3-tooling/cli/src/orm/load-config.ts
  • packages/1-framework/3-tooling/cli/src/orm/migration/graph.ts
  • packages/1-framework/3-tooling/cli/src/orm/migration/list.ts
  • packages/1-framework/3-tooling/cli/src/orm/migration/log.ts
  • packages/1-framework/3-tooling/cli/src/orm/migration/new.ts
  • packages/1-framework/3-tooling/cli/src/orm/migration/paths.ts
  • packages/1-framework/3-tooling/cli/src/orm/migration/plan.ts
  • packages/1-framework/3-tooling/cli/src/orm/migration/show.ts
  • packages/1-framework/3-tooling/cli/src/orm/migration/status-findings.ts
  • packages/1-framework/3-tooling/cli/src/orm/migration/status.ts
  • packages/1-framework/3-tooling/cli/src/utils/formatters/migration-graph-command-render.ts
  • packages/1-framework/3-tooling/cli/src/utils/formatters/migration-graph-labels.ts
  • packages/1-framework/3-tooling/cli/src/utils/formatters/migration-graph-occlusion-render.ts
  • packages/1-framework/3-tooling/cli/src/utils/formatters/migration-graph-palette.ts
  • packages/1-framework/3-tooling/cli/src/utils/formatters/migration-graph-sections.ts
  • packages/1-framework/3-tooling/cli/src/utils/formatters/migration-graph-space-render.ts
  • packages/1-framework/3-tooling/cli/src/utils/formatters/migration-list-render.ts
  • packages/1-framework/3-tooling/cli/src/utils/formatters/migration-list-styler.ts
  • packages/1-framework/3-tooling/cli/src/utils/formatters/tone-markup.ts
  • packages/1-framework/3-tooling/cli/test/control-api/contract-snapshot-resolution.test.ts
  • packages/1-framework/3-tooling/cli/test/control-api/migrate-show-plan.test.ts
  • packages/1-framework/3-tooling/cli/test/control-api/migration-graph-entries.test.ts
  • packages/1-framework/3-tooling/cli/test/control-api/ref-advancement.test.ts
  • packages/1-framework/3-tooling/cli/test/control-api/refs.test.ts
  • packages/1-framework/3-tooling/cli/test/fixture-app/README.md
  • packages/1-framework/3-tooling/cli/test/fixture-app/package.json
  • packages/1-framework/3-tooling/cli/test/orm/cli.test.ts
  • packages/1-framework/3-tooling/cli/test/orm/fixtures/offline-project.ts
  • packages/1-framework/3-tooling/cli/test/orm/load-config.test.ts
  • packages/1-framework/3-tooling/cli/test/orm/migration-graph.test.ts
  • packages/1-framework/3-tooling/cli/test/orm/migration-list.test.ts
  • packages/1-framework/3-tooling/cli/test/orm/migration-log.test.ts
  • packages/1-framework/3-tooling/cli/test/orm/migration-new.test.ts
  • packages/1-framework/3-tooling/cli/test/orm/migration-plan.test.ts
  • packages/1-framework/3-tooling/cli/test/orm/migration-show.test.ts
  • packages/1-framework/3-tooling/cli/test/orm/migration-status.test.ts
  • packages/1-framework/3-tooling/cli/test/utils/formatters/tone-markup.test.ts
  • packages/1-framework/3-tooling/cli/test/utils/test-project-dir.ts
  • packages/9-public/@prisma/orm-toolchain/package.json
  • test/integration/package.json
  • test/integration/test/cli-journeys/data-transform-not-null-backfill.e2e.test.ts
  • test/integration/test/cli-journeys/data-transform-nullable-tightening.e2e.test.ts
  • test/integration/test/cli-journeys/data-transform-type-change.e2e.test.ts
  • test/integration/test/cli-journeys/drift-deleted-root.e2e.test.ts
  • test/integration/test/cli-journeys/invariant-routing.e2e.test.ts
  • test/integration/test/cli-journeys/marker-read-errors-status-empty-migrations.e2e.test.ts
  • test/integration/test/cli-journeys/migration-list.e2e.test.ts
  • test/integration/test/cli-journeys/migration-log.e2e.test.ts
  • test/integration/test/cli-journeys/migration-status-diagnostics.e2e.test.ts
  • test/integration/test/cli-journeys/multi-step-migration.e2e.test.ts
  • test/integration/test/cli-journeys/ref-routing.e2e.test.ts
  • test/integration/test/cli-journeys/schema-evolution-migrations.e2e.test.ts
  • test/integration/test/cli.migration-plan-ref-aware.e2e.test.ts
  • test/integration/test/utils/journey-test-helpers.ts

Comment thread packages/1-framework/3-tooling/cli/src/orm/migration/paths.ts
Comment thread packages/1-framework/3-tooling/cli/src/orm/migration/plan.ts
Comment thread packages/1-framework/3-tooling/cli/src/orm/migration/status.ts
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>

# Conflicts:
#	packages/1-framework/3-tooling/cli/src/orm/family.ts
#	packages/1-framework/3-tooling/cli/test/orm/cli.test.ts
… directory

`migration plan` hardcoded the `migrations` path segment when rendering
`emittedExtensionDirs` rows and the review next-action, so a project with
`migrations.dir` configured pointed users at directories that do not exist.
The presentation now receives the configured directory (relative to the
invocation dir) and joins extension-space paths under it.

Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…s document, no module mock

Three changes to `migration status` and its published JSON document:

- The MIGRATION.MISSING_INVARIANTS document now carries `severity: "warn"`
  like its sibling diagnostics, in the arktype schema and both construction
  paths (the ORM finding builder and the pre-engine command).
- Per-space conditions are no longer reported with app-space identity: the
  marker-not-in-history finding names the space it diverged in (message and
  diagnostic meta), and the no-path / diverged headline uses the failing
  space's marker and target hashes instead of the app space's.
- The migration-status test drives the real control client over fake
  family/driver descriptors seeded through createTestCli config, replacing
  the vi.mock of src/control-api/client and the module-registry reset dance.

Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
Mount the full command tree: main's createBinCommands factory (db init,
db schema, format, migrate, ref set/list/delete) plus this branch's
migration plan/new/status. The four migration-status flag redirects stay
because migration status is now mounted. Deduplicate appRefsDirFor in
migration/paths.ts (both sides added it; main's copy kept) and drop the
commander-command imports in journey-test-helpers that both sides
retired in favour of engine runs.

Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
@wmadden-electric wmadden-electric changed the title Port migration plan, new and status; declare the retired status flag redirects Port migration plan, new and status onto the CLI engine; redirect the four retired status flags Aug 12, 2026
@wmadden
wmadden added this pull request to the merge queue Aug 12, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch Aug 12, 2026
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>

# Conflicts:
#	test/integration/test/utils/journey-test-helpers.ts
@wmadden
wmadden added this pull request to the merge queue Aug 12, 2026
Merged via the queue into main with commit 91e1d79 Aug 12, 2026
16 of 18 checks passed
@wmadden
wmadden deleted the s5-orm-migration-write branch August 12, 2026 11:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants