Skip to content

TML-2769: restructure the migration ledger into a readable per-migration journal#665

Open
wmadden-electric wants to merge 15 commits into
mainfrom
tml-2769-make-the-migration-ledger-readable
Open

TML-2769: restructure the migration ledger into a readable per-migration journal#665
wmadden-electric wants to merge 15 commits into
mainfrom
tml-2769-make-the-migration-ledger-readable

Conversation

@wmadden-electric
Copy link
Copy Markdown
Contributor

@wmadden-electric wmadden-electric commented Jun 1, 2026

Linked issue

Refs TML-2769. Control-plane foundation for migration status (TML-2748) and migration log (TML-2770). Review follow-ups tracked in TML-2774.

At a glance

The migration ledger is now a readable per-migration journal — one row per applied migration edge — with a readLedger API that returns the same shape on every target:

readLedger(options: {
  readonly driver: ControlDriverInstance<TFamilyId, string>;
  readonly space: string;
}): Promise<readonly LedgerEntryRecord[]>; // append (apply) order

interface LedgerEntryRecord {
  readonly space: string;
  readonly migrationName: string;
  readonly migrationHash: string;
  readonly from: string | null; // null only for the ∅ origin
  readonly to: string;
  readonly appliedAt: Date;
  readonly operationCount: number;
}

Before this PR the ledger was write-only, its three target schemas had diverged, and it recorded one collapsed row per space-apply (origin→destination spanning the whole walked path) — a shape that can't answer "is this migration applied?" or "what ran, in what order?".

Decision

Restructure the on-apply ledger into a per-migration journal and add a read API:

  1. Per-edge write. A space-apply walks a path of edges; we now write one ledger row per applied edge (inside the per-space transaction, in walk order) instead of one collapsed row. Each row records the edge's space, migration_name (dirName), migration_hash (the exact-match key status will use), per-edge from/to core hashes, and the edge's authored operations (sliced from plan.operations by operationCount).
  2. Schema convergence. Postgres/SQLite gain space + migration_name + migration_hash columns; Mongo gains migrationName + migrationHash + operations on its ledger docs. contract_json_before/after is retained but materialised only at the apply's endpoints (first/last edge) — interior edges store null.
  3. readLedger read API on ControlFamilyInstance, both family instances, all three adapters, and the CLI control client (a thin pass-through mirroring readMarker). It returns a space's entries in apply order with cross-target parity.

The branch also lands the design-of-record for the wider migration read-command family (list / graph / status / log / ledger) under projects/migration-graph-rendering/ — the spec and plan this slice was cut from.

How it fits together

  1. Thread the per-edge breakdown to the runner. apply.ts already computes PerSpacePlan.migrationEdges ({migrationHash, dirName, from, to, operationCount}) during graph-walk planning; it now passes them into each per-space runner execute option. The SQL runner options type carries the field; Mongo receives it structurally.
  2. Write one row per edge. The SQL runners (postgres/sqlite) and the Mongo runner replace the single ledger write with a loop over the edges, slicing plan.operations by each edge's operationCount to attribute ops in walk order, asserting sum(operationCount) === plan.operations.length. Synth (greenfield db init) plans have no authored edges, so they keep writing a single synthesised row keyed by the plan destination (from = null, empty migration name).
  3. Read it back with parity. Each adapter's readLedger probes for the ledger store (returning [] when absent, mirroring readMarker), selects the space's rows in apply order, and projects them to LedgerEntryRecord. The ∅ origin reads as from: null on every target (normalising SQL null/sha256:empty and Mongo's ''), and operationCount is derived from the stored ops rather than returning the ops array — so the three targets yield an identical record.

Behavior changes & evidence

  • SQL ledger writes one row per applied edge with space/migration_name/migration_hash and per-edge fromto chaining; endpoint-only contract_json. — packages/3-targets/3-targets/postgres/src/core/migrations/runner.ts, packages/3-targets/3-targets/postgres/src/core/migrations/statement-builders.ts. Evidence: packages/3-targets/6-adapters/postgres/test/migrations/runner.ledger.integration.test.ts, packages/3-targets/6-adapters/sqlite/test/migrations/runner.ledger.test.ts.
  • Mongo ledger writes one _prisma_migrations doc per applied edge carrying migrationName/migrationHash/operations, with a synth fallback; the stricter Mongo no-op skip is unchanged. — packages/3-mongo-target/1-mongo-target/src/core/mongo-runner.ts, packages/3-mongo-target/2-mongo-adapter/src/core/marker-ledger.ts. Evidence: packages/3-mongo-target/1-mongo-target/test/mongo-runner.test.ts, packages/3-mongo-target/2-mongo-adapter/test/marker-ledger.test.ts.
  • readLedger(space) returns a space's journal in apply order with cross-target parity.packages/1-framework/1-core/framework-components/src/control/control-instances.ts, packages/1-framework/3-tooling/cli/src/control-api/client.ts. Evidence: the round-trip + cross-target shape parity assertions in all three ledger test files above.

Reviewer notes

  • migrationEdges reaches the Mongo runner structurally, not by a declared framework type. The framework MigrationRunnerPerSpaceOptions can't import AggregateMigrationEdgeRef (it lives in migration-tools, a higher layer than framework-components, and lint:deps forbids the upward dependency), so Mongo's descriptor passes the field through via object-spread. It works and is tested at the runner level, but a refactor that reconstructs the options object explicitly would silently drop per-edge Mongo writes. Tracked as item 2 of TML-2774.
  • The ∅-origin sentinel 'sha256:empty' + its from→null normalisation helper are duplicated across the three adapters rather than sharing the real EMPTY_CONTRACT_HASH constant. Deliberate for now (the shared home is a cross-layer move); tracked as item 1 of TML-2774.
  • Synth op-count parity is unconditional only because greenfield init has no idempotency skips. SQL synth counts skip records, Mongo synth doesn't; latent divergence tracked as item 3 of TML-2774.
  • Largest surface is the read-side commit (8d6f2bba6) — it touches the SPI, both families, three adapters, and the client, but every layer is a mechanical mirror of the existing readMarker wiring.
  • No back-compat / migration of existing ledger rows — this is a prototype; old-shaped rows are not read.

Verification

Run on the post-merge HEAD (branch merged up to origin/main):

  • @prisma-next/cli typecheck — pass
  • @prisma-next/adapter-postgres — 541 passed | 4 expected-fail (incl. ledger round-trip)
  • @prisma-next/adapter-sqlite — 154 passed (incl. ledger round-trip)
  • @prisma-next/adapter-mongo — 287 passed
  • @prisma-next/target-mongo — 400 passed
  • biome check on touched files — clean (pre-existing no-bare-cast infos only)

Follow-ups

  • TML-2774 — harden the journal: share the ∅-origin sentinel, make Mongo's migrationEdges threading type-safe, align synth op-count parity and read robustness, add throw-path + wrapper-threading tests.
  • TML-2748 (status) and TML-2770 (log) consume this read API.

Alternatives considered

  • Add two columns, keep the collapsed-per-apply row. Rejected: a per-apply row can't answer "is this edge applied?" (status) or "one row per apply event" (log) — both need per-edge granularity, so the restructure was the honest first step.
  • Materialise intermediate contract_json for every edge of a multi-edge apply. Rejected as out of scope: no consumer reads interior snapshots, and synthesising them is non-trivial. Endpoints are materialised; interiors store null.
  • Return the operations array from readLedger. Rejected in favour of operationCount — the count gives cross-target parity (SQL stores ops as JSON, Mongo as a BSON array) and is all the read consumers need; the full ops remain on disk as an audit record.

Skill update

n/a — internal control-plane API; no user-facing CLI/contract surface changes in this PR (the consuming status/log commands ship separately).

Checklist

  • All commits are signed off (git commit -s).
  • I read CONTRIBUTING.md and the change is scoped to one logical concern.
  • Tests are updated.
  • The PR title is in TML-NNNN: <sentence-case title> form.
  • The Skill update section above is filled in.

Summary by CodeRabbit

  • New Features

    • Read per-space migration ledger entries with metadata, timestamps, operation counts, and contract boundaries.
    • Emit per-edge ledger rows when providing migrationEdges; preserve single-row synth behavior when omitted.
    • Exposed ledger-read helpers for normalizing stored values and added package export.
  • Tests

    • Added/extended unit and integration tests validating per-edge ledger behavior, reads, writes, and helper parsing.
  • Chores

    • Updated DB ledger schemas to include space, migration_name, and migration_hash.

wmadden added 9 commits June 1, 2026 13:57
Folds in the cross-cutting design decisions for the migration read-command
family (list / graph / status / log) and the ledger foundation that status and
log depend on:

- decisions.md: D1-D10 (command-family model, shared renderer, space policy B,
  list/graph split, list->tree, ledger foundation, log, status reframe with
  --from/--to, and where path/invariants live).
- New slice specs: migration-graph-space-flag (TML-2767), list-renders-tree
  (TML-2768).
- README: full slice list incl. ledger (TML-2769), status (TML-2748), log
  (TML-2770) and the future siblings (TML-2771, TML-2772).

Signed-off-by: Will Madden <madden@prisma.io>
Investigation found the on-apply ledger records one collapsed row per
space-apply (not per migration) and the three target schemas have diverged
(PG/SQLite lack a space column; Mongo lacks operations). status and log both
need one row per migration edge, so the foundation slice is a restructure into a
per-edge journal (space + name + hash per row), not an additive field bump.

- New slice spec: slices/ledger-foundation/spec.md.
- decisions.md D7 rewritten to the per-migration-journal restructure.

Signed-off-by: Will Madden <madden@prisma.io>
Signed-off-by: Will Madden <madden@prisma.io>
…t future (D7)

Signed-off-by: Will Madden <madden@prisma.io>
The SQL ledger recorded one collapsed row per space-apply, spanning the whole
walked path, on a schema with no space column — the wrong shape for status
("is this migration applied?") and log ("one row per apply event"). Restructure
it into a per-migration journal.

Postgres + SQLite ledger tables gain space, migration_name, and migration_hash.
The per-edge breakdown (PerSpacePlan.migrationEdges) is threaded from the apply
layer into the SQL runner execute options; the runner writes one row per applied
edge inside the per-space transaction, attributing ops by slicing plan.operations
by each edge's operationCount in walk order. contract_json is materialised only
at the apply's endpoints (interiors null). synth plans (no authored edges) keep a
single row keyed by the plan destination, now carrying the space id.

Mongo gains an optional migrationEdges field on its runner options so the apply
layer typechecks; its write behaviour is unchanged (per-edge parity is the next
dispatch).

Signed-off-by: Will Madden <madden@prisma.io>
Bring Mongo to parity with the SQL per-migration journal. Each applied edge now
writes one _prisma_migrations ledger doc carrying migration_name, migration_hash,
and that edge's operations alongside the existing space/from/to/appliedAt. The
runner threads the per-edge breakdown (migrationEdges) through a per-edge loop,
attributing ops by slicing plan.operations by operationCount in walk order; synth
applies (no authored edges) write a single doc keyed by the plan destination with
an empty migration name. Mongo's stricter no-op skip is left intact.

Signed-off-by: Will Madden <madden@prisma.io>
Complete the ledger read side. A new LedgerEntryRecord (space, migrationName,
migrationHash, from, to, appliedAt, operationCount) sits beside ContractMarkerRecord,
and readLedger is added to the ControlFamilyInstance SPI, the SQL and Mongo family
instances, the Postgres/SQLite/Mongo adapters, and the CLI control client as a thin
pass-through mirroring readMarker. Reads return a space's entries in apply order with
cross-target parity: the ∅ origin reads as from: null on every target (normalising
SQL null/sha256:empty and Mongo's empty-string), and operationCount is derived from
the stored operations rather than returning the ops themselves. Round-trip and parity
tests cover single-edge, multi-edge, and missing-table reads on all three targets.

Signed-off-by: Will Madden <madden@prisma.io>
…one ref

Close the read-side coverage gap the review flagged: the Postgres and SQLite
synth-apply tests now read the synthesised row back through readLedger and assert
its from→null normalisation and operationCount projection, not just the raw stored
row. Also drop a stale milestone label from a runner-deps comment.

Signed-off-by: Will Madden <madden@prisma.io>
…-2769-make-the-migration-ledger-readable
@wmadden-electric wmadden-electric requested a review from a team as a code owner June 1, 2026 13:02
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 1, 2026

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: a549c963-ba84-4257-a916-a38798339828

📥 Commits

Reviewing files that changed from the base of the PR and between 860c52c and 4a1bdc5.

⛔ Files ignored due to path filters (1)
  • projects/migration-graph-rendering/slices/ledger-foundation/spec.md is excluded by !projects/**
📒 Files selected for processing (18)
  • packages/1-framework/1-core/framework-components/src/control/control-migration-types.ts
  • packages/2-mongo-family/9-family/src/core/control-instance.ts
  • packages/2-sql/9-family/package.json
  • packages/2-sql/9-family/src/core/control-instance.ts
  • packages/2-sql/9-family/src/core/ledger-read.ts
  • packages/2-sql/9-family/src/core/migrations/types.ts
  • packages/2-sql/9-family/src/exports/ledger-read.ts
  • packages/2-sql/9-family/test/ledger-read.test.ts
  • packages/2-sql/9-family/tsdown.config.ts
  • packages/3-mongo-target/1-mongo-target/src/core/mongo-runner.ts
  • packages/3-mongo-target/1-mongo-target/test/mongo-runner-integration.test.ts
  • packages/3-mongo-target/1-mongo-target/test/mongo-runner.test.ts
  • packages/3-mongo-target/2-mongo-adapter/src/core/marker-ledger.ts
  • packages/3-mongo-target/2-mongo-adapter/test/marker-ledger.test.ts
  • packages/3-targets/3-targets/sqlite/src/core/migrations/statement-builders.ts
  • packages/3-targets/6-adapters/postgres/src/core/control-adapter.ts
  • packages/3-targets/6-adapters/sqlite/src/core/control-adapter.ts
  • packages/3-targets/6-adapters/sqlite/test/migrations/runner.ledger.test.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/2-sql/9-family/tsdown.config.ts
🚧 Files skipped from review as they are similar to previous changes (9)
  • packages/2-sql/9-family/src/core/migrations/types.ts
  • packages/3-targets/3-targets/sqlite/src/core/migrations/statement-builders.ts
  • packages/3-targets/6-adapters/sqlite/src/core/control-adapter.ts
  • packages/3-mongo-target/2-mongo-adapter/test/marker-ledger.test.ts
  • packages/2-sql/9-family/src/core/control-instance.ts
  • packages/2-mongo-family/9-family/src/core/control-instance.ts
  • packages/3-mongo-target/1-mongo-target/src/core/mongo-runner.ts
  • packages/3-mongo-target/2-mongo-adapter/src/core/marker-ledger.ts
  • packages/3-targets/6-adapters/sqlite/test/migrations/runner.ledger.test.ts

📝 Walkthrough

Walkthrough

Adds a LedgerEntryRecord contract and readLedger APIs across control clients, family instances, and storage adapters; threads optional migrationEdges into runner options; runners emit per-edge or synth ledger rows; SQL/SQLite/Postgres schemas and builders updated; comprehensive tests added for per-edge behavior.

Changes

Ledger Reading and Per-Edge Ledger Tracking

Layer / File(s) Summary
Ledger contract type and exports
packages/1-framework/0-foundation/contract/src/types.ts, packages/1-framework/0-foundation/contract/src/exports/types.ts
Adds LedgerEntryRecord interface and re-exports it.
Control API / client surface updates
packages/1-framework/3-tooling/cli/src/control-api/types.ts, packages/1-framework/3-tooling/cli/src/control-api/client.ts, packages/1-framework/3-tooling/cli/src/control-api/operations/apply.ts
Adds ControlClient.readLedger(space?), implements ControlClientImpl.readLedger, and threads migrationEdges into per-space runner options.
Control-family interfaces and instances
packages/1-framework/1-core/framework-components/src/control/control-instances.ts, packages/2-mongo-family/9-family/src/core/control-adapter.ts, packages/2-sql/9-family/src/core/control-adapter.ts, packages/2-sql/9-family/src/core/control-instance.ts, packages/2-mongo-family/9-family/src/core/control-instance.ts
Extends control-family contracts and instance wiring to require/expose readLedger and updates type imports/guards.
Mongo marker-ledger and adapter
packages/3-mongo-target/2-mongo-adapter/src/core/marker-ledger.ts, packages/3-mongo-target/2-mongo-adapter/src/core/mongo-control-adapter.ts, packages/3-mongo-target/2-mongo-adapter/src/core/runner-deps.ts, packages/3-mongo-target/2-mongo-adapter/src/exports/control.ts
Adds readLedger(db, space), normalizes origin/appliedAt/operationCount, expands writeLedgerEntry payload, and exposes read/write via MongoControlAdapter.
Mongo runner per-edge emission
packages/3-mongo-target/1-mongo-target/src/core/mongo-runner.ts
Tracks executed plan ops, validates migrationEdges operation counts, slices operations per edge, and records per-edge ledger rows or a single synth row when edges absent.
Mongo runner & adapter tests
packages/3-mongo-target/1-mongo-target/test/mongo-runner.test.ts, packages/3-mongo-target/1-mongo-target/test/mongo-runner-integration.test.ts, packages/3-mongo-target/2-mongo-adapter/test/marker-ledger.test.ts
Adds unit and integration tests covering single-edge, multi-edge, synth apply, normalization, and readLedger behaviors.
SQL ledger-read helpers & export
packages/2-sql/9-family/src/core/ledger-read.ts, packages/2-sql/9-family/src/exports/ledger-read.ts, packages/2-sql/9-family/test/ledger-read.test.ts, packages/2-sql/9-family/tsdown.config.ts, packages/2-sql/9-family/package.json
Adds helpers to normalize stored ledger values, tests, re-exports, tsdown entry, and package export ./ledger-read.
Migration runner options: migrationEdges
packages/1-framework/1-core/framework-components/src/control/control-migration-types.ts, packages/2-sql/9-family/src/core/migrations/types.ts
Adds optional migrationEdges to per-space runner options for per-edge ledger emission.
Postgres schema, statement builders, and runner
packages/3-targets/3-targets/postgres/src/core/migrations/statement-builders.ts, packages/3-targets/3-targets/postgres/src/core/migrations/runner.ts
Adds space, migration_name, migration_hash columns to ledger table SQL and updates LedgerInsertInput/builders; refactors runner to emit per-edge or single ledger inserts with validation.
Postgres adapter and integration tests
packages/3-targets/6-adapters/postgres/src/core/control-adapter.ts, packages/3-targets/6-adapters/postgres/test/migrations/fixtures/runner-fixtures.ts, packages/3-targets/6-adapters/postgres/test/migrations/runner.ledger.integration.test.ts
Implements readLedger with table existence probe and normalization; adds fixtures and integration tests validating per-edge and synth ledger behavior.
SQLite schema, runner, adapter, and tests
packages/3-targets/3-targets/sqlite/src/core/migrations/statement-builders.ts, packages/3-targets/3-targets/sqlite/src/core/migrations/runner.ts, packages/3-targets/6-adapters/sqlite/src/core/control-adapter.ts, packages/3-targets/6-adapters/sqlite/test/migrations/runner.ledger.test.ts
Updates _prisma_ledger schema and insert builders; refactors runner to emit per-edge rows or synth row; implements readLedger and adds comprehensive tests.
Test fixture mocking
packages/1-framework/3-tooling/cli/test/config-types.test.ts
Mocks new readLedger method in CLI test fixture returning an empty array.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • prisma/prisma-next#655: Both PRs touch migration runner perSpaceOptions/migrationEdges control-flow changes.

Suggested reviewers

  • wmadden
  • aqrln

Poem

🐇 I hopped through ledgers, line by line,

each edge a carrot, hashed and fine.
From Mongo piles to SQL beds,
I traced the hops where migration treads.
Hooray — the ledger tells its tale!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'restructure the migration ledger into a readable per-migration journal' clearly and accurately describes the main architectural change - converting the ledger from write-only to a readable per-migration journal structure with a new readLedger API.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch tml-2769-make-the-migration-ledger-readable

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Jun 1, 2026

Open in StackBlitz

@prisma-next/extension-author-tools

npm i https://pkg.pr.new/@prisma-next/extension-author-tools@665

@prisma-next/mongo-runtime

npm i https://pkg.pr.new/@prisma-next/mongo-runtime@665

@prisma-next/family-mongo

npm i https://pkg.pr.new/@prisma-next/family-mongo@665

@prisma-next/sql-runtime

npm i https://pkg.pr.new/@prisma-next/sql-runtime@665

@prisma-next/family-sql

npm i https://pkg.pr.new/@prisma-next/family-sql@665

@prisma-next/extension-arktype-json

npm i https://pkg.pr.new/@prisma-next/extension-arktype-json@665

@prisma-next/middleware-cache

npm i https://pkg.pr.new/@prisma-next/middleware-cache@665

@prisma-next/mongo

npm i https://pkg.pr.new/@prisma-next/mongo@665

@prisma-next/extension-paradedb

npm i https://pkg.pr.new/@prisma-next/extension-paradedb@665

@prisma-next/extension-pgvector

npm i https://pkg.pr.new/@prisma-next/extension-pgvector@665

@prisma-next/extension-postgis

npm i https://pkg.pr.new/@prisma-next/extension-postgis@665

@prisma-next/postgres

npm i https://pkg.pr.new/@prisma-next/postgres@665

@prisma-next/sql-orm-client

npm i https://pkg.pr.new/@prisma-next/sql-orm-client@665

@prisma-next/sqlite

npm i https://pkg.pr.new/@prisma-next/sqlite@665

@prisma-next/target-mongo

npm i https://pkg.pr.new/@prisma-next/target-mongo@665

@prisma-next/adapter-mongo

npm i https://pkg.pr.new/@prisma-next/adapter-mongo@665

@prisma-next/driver-mongo

npm i https://pkg.pr.new/@prisma-next/driver-mongo@665

@prisma-next/contract

npm i https://pkg.pr.new/@prisma-next/contract@665

@prisma-next/utils

npm i https://pkg.pr.new/@prisma-next/utils@665

@prisma-next/config

npm i https://pkg.pr.new/@prisma-next/config@665

@prisma-next/errors

npm i https://pkg.pr.new/@prisma-next/errors@665

@prisma-next/framework-components

npm i https://pkg.pr.new/@prisma-next/framework-components@665

@prisma-next/operations

npm i https://pkg.pr.new/@prisma-next/operations@665

@prisma-next/ts-render

npm i https://pkg.pr.new/@prisma-next/ts-render@665

@prisma-next/contract-authoring

npm i https://pkg.pr.new/@prisma-next/contract-authoring@665

@prisma-next/ids

npm i https://pkg.pr.new/@prisma-next/ids@665

@prisma-next/psl-parser

npm i https://pkg.pr.new/@prisma-next/psl-parser@665

@prisma-next/psl-printer

npm i https://pkg.pr.new/@prisma-next/psl-printer@665

@prisma-next/cli

npm i https://pkg.pr.new/@prisma-next/cli@665

@prisma-next/cli-telemetry

npm i https://pkg.pr.new/@prisma-next/cli-telemetry@665

@prisma-next/emitter

npm i https://pkg.pr.new/@prisma-next/emitter@665

@prisma-next/migration-tools

npm i https://pkg.pr.new/@prisma-next/migration-tools@665

prisma-next

npm i https://pkg.pr.new/prisma-next@665

@prisma-next/vite-plugin-contract-emit

npm i https://pkg.pr.new/@prisma-next/vite-plugin-contract-emit@665

@prisma-next/mongo-codec

npm i https://pkg.pr.new/@prisma-next/mongo-codec@665

@prisma-next/mongo-contract

npm i https://pkg.pr.new/@prisma-next/mongo-contract@665

@prisma-next/mongo-value

npm i https://pkg.pr.new/@prisma-next/mongo-value@665

@prisma-next/mongo-contract-psl

npm i https://pkg.pr.new/@prisma-next/mongo-contract-psl@665

@prisma-next/mongo-contract-ts

npm i https://pkg.pr.new/@prisma-next/mongo-contract-ts@665

@prisma-next/mongo-emitter

npm i https://pkg.pr.new/@prisma-next/mongo-emitter@665

@prisma-next/mongo-schema-ir

npm i https://pkg.pr.new/@prisma-next/mongo-schema-ir@665

@prisma-next/mongo-query-ast

npm i https://pkg.pr.new/@prisma-next/mongo-query-ast@665

@prisma-next/mongo-orm

npm i https://pkg.pr.new/@prisma-next/mongo-orm@665

@prisma-next/mongo-query-builder

npm i https://pkg.pr.new/@prisma-next/mongo-query-builder@665

@prisma-next/mongo-lowering

npm i https://pkg.pr.new/@prisma-next/mongo-lowering@665

@prisma-next/mongo-wire

npm i https://pkg.pr.new/@prisma-next/mongo-wire@665

@prisma-next/sql-contract

npm i https://pkg.pr.new/@prisma-next/sql-contract@665

@prisma-next/sql-errors

npm i https://pkg.pr.new/@prisma-next/sql-errors@665

@prisma-next/sql-operations

npm i https://pkg.pr.new/@prisma-next/sql-operations@665

@prisma-next/sql-schema-ir

npm i https://pkg.pr.new/@prisma-next/sql-schema-ir@665

@prisma-next/sql-contract-psl

npm i https://pkg.pr.new/@prisma-next/sql-contract-psl@665

@prisma-next/sql-contract-ts

npm i https://pkg.pr.new/@prisma-next/sql-contract-ts@665

@prisma-next/sql-contract-emitter

npm i https://pkg.pr.new/@prisma-next/sql-contract-emitter@665

@prisma-next/sql-lane-query-builder

npm i https://pkg.pr.new/@prisma-next/sql-lane-query-builder@665

@prisma-next/sql-relational-core

npm i https://pkg.pr.new/@prisma-next/sql-relational-core@665

@prisma-next/sql-builder

npm i https://pkg.pr.new/@prisma-next/sql-builder@665

@prisma-next/target-postgres

npm i https://pkg.pr.new/@prisma-next/target-postgres@665

@prisma-next/target-sqlite

npm i https://pkg.pr.new/@prisma-next/target-sqlite@665

@prisma-next/adapter-postgres

npm i https://pkg.pr.new/@prisma-next/adapter-postgres@665

@prisma-next/adapter-sqlite

npm i https://pkg.pr.new/@prisma-next/adapter-sqlite@665

@prisma-next/driver-postgres

npm i https://pkg.pr.new/@prisma-next/driver-postgres@665

@prisma-next/driver-sqlite

npm i https://pkg.pr.new/@prisma-next/driver-sqlite@665

commit: 5c6b928

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 1, 2026

size-limit report 📦

Path Size
postgres / no-emit 135.96 KB (+0.03% 🔺)
postgres / emit 125.62 KB (+0.03% 🔺)
mongo / no-emit 75.69 KB (0%)
mongo / emit 70.68 KB (0%)

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (3)
packages/2-mongo-family/9-family/src/core/control-instance.ts (1)

174-177: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Update error message to mention readLedger.

The type guard now requires readLedger (lines 91-92), but the error message still only mentions "missing readMarker, readAllMarkers, or introspectSchema".

📝 Proposed fix
       throw new Error(
-        'Adapter does not implement MongoControlAdapter (missing readMarker, readAllMarkers, or introspectSchema)',
+        'Adapter does not implement MongoControlAdapter (missing readMarker, readAllMarkers, readLedger, or introspectSchema)',
       );
🤖 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/2-mongo-family/9-family/src/core/control-instance.ts` around lines
174 - 177, The thrown Error when the isMongoControlAdapter type guard fails
should mention the newly required method readLedger; update the error message in
the control-adapter check (the block that tests
isMongoControlAdapter(controlAdapter) and throws) so it lists "missing
readMarker, readAllMarkers, readLedger, or introspectSchema" (or equivalent) to
reflect the updated type guard requirement.
packages/2-sql/9-family/src/core/control-instance.ts (1)

376-379: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Update error message to mention readLedger.

The type guard now requires readLedger (lines 267-268), but the error message still only mentions "missing introspect, readMarker, or readAllMarkers".

📝 Proposed fix
       throw new Error(
-        'Adapter does not implement SqlControlAdapter (missing introspect, readMarker, or readAllMarkers)',
+        'Adapter does not implement SqlControlAdapter (missing introspect, readMarker, readAllMarkers, or readLedger)',
       );
🤖 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/2-sql/9-family/src/core/control-instance.ts` around lines 376 - 379,
The error thrown when !isSqlControlAdapter(controlAdapter) references missing
methods but omits the newly required readLedger; update the Error message in the
throw inside control-instance.ts to list readLedger alongside introspect,
readMarker, and readAllMarkers (i.e., mention that the adapter is missing
introspect, readMarker, readAllMarkers, or readLedger) so the message matches
the isSqlControlAdapter type guard and helps identify the missing method on
controlAdapter.
packages/3-targets/3-targets/sqlite/src/core/migrations/statement-builders.ts (1)

46-60: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard or migrate the legacy _prisma_ledger shape.

CREATE TABLE IF NOT EXISTS leaves an existing ledger table untouched, so upgraded databases keep the old column set and the new reads/inserts in this PR will fail on space, migration_name, or migration_hash. Please either converge the table here or add an explicit legacy-shape failure before the runner/adapter touches it.

🤖 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/3-targets/3-targets/sqlite/src/core/migrations/statement-builders.ts`
around lines 46 - 60, The CREATE TABLE IF NOT EXISTS in
ensureLedgerTableStatement leaves older _prisma_ledger schemas intact, causing
missing columns (e.g., space, migration_name, migration_hash,
origin_*/destination_* etc.) to break new reads/inserts; update the migration to
either (A) detect and migrate legacy shape by querying PRAGMA
table_info('_prisma_ledger') and issuing ALTER TABLE ... ADD COLUMN for any
missing columns (ensuring NOT NULL columns get safe defaults or backfilled
values), or (B) explicitly fail fast by checking the schema shape before the
runner/adapter touches the table and throwing a clear error advising migration;
implement this check/migration in the same module that defines
ensureLedgerTableStatement so startup will converge or abort before using
_prisma_ledger.
🧹 Nitpick comments (3)
packages/3-mongo-target/1-mongo-target/src/core/mongo-runner.ts (1)

279-282: ⚡ Quick win

Use structured error for validation failure.

Line 280 throws a generic Error when edge operation counts don't sum to plan length. Consider using errorRunnerFailed (imported at line 3) for consistency with other runner failures and to provide structured error context.

🔧 Proposed fix using structured error
-        throw new Error(
-          `Ledger write: plan.operations length (${plan.operations.length}) does not match sum of migrationEdges operationCount (${totalEdgeOps})`,
-        );
+        throw errorRunnerFailed(
+          `Ledger write: plan.operations length (${plan.operations.length}) does not match sum of migrationEdges operationCount (${totalEdgeOps})`,
+          {
+            why: 'The migration edges provided to the runner must account for all planned operations.',
+            meta: { planOperationsLength: plan.operations.length, totalEdgeOps },
+          },
+        );
🤖 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/3-mongo-target/1-mongo-target/src/core/mongo-runner.ts` around lines
279 - 282, Replace the generic throw new Error in mongo-runner.ts (the check
that compares totalEdgeOps and plan.operations.length) with a call to the
existing errorRunnerFailed helper so the failure is structured; call
errorRunnerFailed(...) passing a clear message and include contextual fields
(e.g., reason, planOperations: plan.operations.length, totalEdgeOps) to match
how other runner failures are reported and keep the import of errorRunnerFailed
used consistently.
packages/3-targets/3-targets/postgres/src/core/migrations/runner.ts (1)

635-640: ⚖️ Poor tradeoff

Error handling inconsistency: direct throw vs runner failure pattern.

The validation at lines 637-640 throws an Error directly, but other runner errors return runnerFailure(...) (e.g., lines 321-332, 525-534). Consider wrapping this in a runnerFailure for consistency:

       if (totalEdgeOps !== plan.operations.length) {
-        throw new Error(
-          `Ledger write: plan.operations length (${plan.operations.length}) does not match sum of migrationEdges operationCount (${totalEdgeOps})`,
-        );
+        return runnerFailure(
+          'LEDGER_EDGE_MISMATCH',
+          `Ledger write: plan.operations length (${plan.operations.length}) does not match sum of migrationEdges operationCount (${totalEdgeOps})`,
+          {
+            meta: {
+              planOperationsLength: plan.operations.length,
+              edgeOperationsSum: totalEdgeOps,
+            },
+          },
+        );
       }

Note: This would require changing recordLedgerEntries return type to Promise<Result<void, SqlMigrationRunnerFailure>> and handling the result at the call site (line 169).

🤖 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/3-targets/3-targets/postgres/src/core/migrations/runner.ts` around
lines 635 - 640, The validation in recordLedgerEntries currently throws an Error
when plan.operations.length !== sum of edge.operationCount; change this to
return runnerFailure(...) using the SqlMigrationRunnerFailure variant used
elsewhere (match the pattern from runnerFailure calls around lines with
runnerFailure usages) so recordLedgerEntries returns Promise<Result<void,
SqlMigrationRunnerFailure>> instead of throwing; update the function signature
(recordLedgerEntries) and replace the throw with a runnerFailure(...) call
carrying the same diagnostic message, then update the caller at the call site
that invokes recordLedgerEntries (the code noted around line 169) to handle the
Result (unwrap or propagate failure consistently) instead of expecting an
exception.
packages/3-targets/6-adapters/postgres/src/core/control-adapter.ts (1)

50-62: ⚡ Quick win

Hardcoded EMPTY_ORIGIN_CORE_HASH matches the canonical EMPTY_CONTRACT_HASH ('sha256:empty'), so there’s no correctness mismatch risk. Keeping the local constant is fine; optionally import and compare against EMPTY_CONTRACT_HASH for consistency.

🤖 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/3-targets/6-adapters/postgres/src/core/control-adapter.ts` around
lines 50 - 62, The code defines a local constant EMPTY_ORIGIN_CORE_HASH and
compares it in ledgerOriginFromStored; to keep consistency with the canonical
name, remove the local EMPTY_ORIGIN_CORE_HASH and import EMPTY_CONTRACT_HASH (or
rename to match) instead, then update ledgerOriginFromStored to compare
originCoreHash against EMPTY_CONTRACT_HASH; ensure the imported symbol
(EMPTY_CONTRACT_HASH) is referenced where EMPTY_ORIGIN_CORE_HASH was used and
that ledgerOriginFromStored still returns null for null, empty string, or the
empty-contract hash.
🤖 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/3-targets/6-adapters/sqlite/src/core/control-adapter.ts`:
- Around line 45-47: coerceLedgerAppliedAt currently uses new Date(value) which
treats timezone-less SQL timestamps like "YYYY-MM-DD HH:MM:SS" as local time;
update coerceLedgerAppliedAt to parse such SQLite datetime strings as UTC by
normalizing the string before constructing the Date (for example convert
"YYYY-MM-DD HH:MM:SS" to an ISO-like "YYYY-MM-DDTHH:MM:SSZ" or otherwise append
a 'Z' so Date parses it as UTC) and keep the existing behavior when value is
already a Date; locate and modify the coerceLedgerAppliedAt function to
implement this normalization.

---

Outside diff comments:
In `@packages/2-mongo-family/9-family/src/core/control-instance.ts`:
- Around line 174-177: The thrown Error when the isMongoControlAdapter type
guard fails should mention the newly required method readLedger; update the
error message in the control-adapter check (the block that tests
isMongoControlAdapter(controlAdapter) and throws) so it lists "missing
readMarker, readAllMarkers, readLedger, or introspectSchema" (or equivalent) to
reflect the updated type guard requirement.

In `@packages/2-sql/9-family/src/core/control-instance.ts`:
- Around line 376-379: The error thrown when
!isSqlControlAdapter(controlAdapter) references missing methods but omits the
newly required readLedger; update the Error message in the throw inside
control-instance.ts to list readLedger alongside introspect, readMarker, and
readAllMarkers (i.e., mention that the adapter is missing introspect,
readMarker, readAllMarkers, or readLedger) so the message matches the
isSqlControlAdapter type guard and helps identify the missing method on
controlAdapter.

In
`@packages/3-targets/3-targets/sqlite/src/core/migrations/statement-builders.ts`:
- Around line 46-60: The CREATE TABLE IF NOT EXISTS in
ensureLedgerTableStatement leaves older _prisma_ledger schemas intact, causing
missing columns (e.g., space, migration_name, migration_hash,
origin_*/destination_* etc.) to break new reads/inserts; update the migration to
either (A) detect and migrate legacy shape by querying PRAGMA
table_info('_prisma_ledger') and issuing ALTER TABLE ... ADD COLUMN for any
missing columns (ensuring NOT NULL columns get safe defaults or backfilled
values), or (B) explicitly fail fast by checking the schema shape before the
runner/adapter touches the table and throwing a clear error advising migration;
implement this check/migration in the same module that defines
ensureLedgerTableStatement so startup will converge or abort before using
_prisma_ledger.

---

Nitpick comments:
In `@packages/3-mongo-target/1-mongo-target/src/core/mongo-runner.ts`:
- Around line 279-282: Replace the generic throw new Error in mongo-runner.ts
(the check that compares totalEdgeOps and plan.operations.length) with a call to
the existing errorRunnerFailed helper so the failure is structured; call
errorRunnerFailed(...) passing a clear message and include contextual fields
(e.g., reason, planOperations: plan.operations.length, totalEdgeOps) to match
how other runner failures are reported and keep the import of errorRunnerFailed
used consistently.

In `@packages/3-targets/3-targets/postgres/src/core/migrations/runner.ts`:
- Around line 635-640: The validation in recordLedgerEntries currently throws an
Error when plan.operations.length !== sum of edge.operationCount; change this to
return runnerFailure(...) using the SqlMigrationRunnerFailure variant used
elsewhere (match the pattern from runnerFailure calls around lines with
runnerFailure usages) so recordLedgerEntries returns Promise<Result<void,
SqlMigrationRunnerFailure>> instead of throwing; update the function signature
(recordLedgerEntries) and replace the throw with a runnerFailure(...) call
carrying the same diagnostic message, then update the caller at the call site
that invokes recordLedgerEntries (the code noted around line 169) to handle the
Result (unwrap or propagate failure consistently) instead of expecting an
exception.

In `@packages/3-targets/6-adapters/postgres/src/core/control-adapter.ts`:
- Around line 50-62: The code defines a local constant EMPTY_ORIGIN_CORE_HASH
and compares it in ledgerOriginFromStored; to keep consistency with the
canonical name, remove the local EMPTY_ORIGIN_CORE_HASH and import
EMPTY_CONTRACT_HASH (or rename to match) instead, then update
ledgerOriginFromStored to compare originCoreHash against EMPTY_CONTRACT_HASH;
ensure the imported symbol (EMPTY_CONTRACT_HASH) is referenced where
EMPTY_ORIGIN_CORE_HASH was used and that ledgerOriginFromStored still returns
null for null, empty string, or the empty-contract hash.
🪄 Autofix (Beta)

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

Run ID: f8251f5a-abe4-451b-b739-98af66722508

📥 Commits

Reviewing files that changed from the base of the PR and between 82216b8 and 860c52c.

⛔ Files ignored due to path filters (7)
  • projects/migration-graph-rendering/README.md is excluded by !projects/**
  • projects/migration-graph-rendering/decisions.md is excluded by !projects/**
  • projects/migration-graph-rendering/slices/ledger-foundation/plan.md is excluded by !projects/**
  • projects/migration-graph-rendering/slices/ledger-foundation/spec.md is excluded by !projects/**
  • projects/migration-graph-rendering/slices/list-renders-tree/spec.md is excluded by !projects/**
  • projects/migration-graph-rendering/slices/migration-graph-space-flag/spec.md is excluded by !projects/**
  • projects/migration-graph-rendering/slices/remove-list-graph-renderer/spec.md is excluded by !projects/**
📒 Files selected for processing (29)
  • packages/1-framework/0-foundation/contract/src/exports/types.ts
  • packages/1-framework/0-foundation/contract/src/types.ts
  • packages/1-framework/1-core/framework-components/src/control/control-instances.ts
  • packages/1-framework/3-tooling/cli/src/control-api/client.ts
  • packages/1-framework/3-tooling/cli/src/control-api/operations/apply.ts
  • packages/1-framework/3-tooling/cli/src/control-api/types.ts
  • packages/1-framework/3-tooling/cli/test/config-types.test.ts
  • packages/2-mongo-family/9-family/src/core/control-adapter.ts
  • packages/2-mongo-family/9-family/src/core/control-instance.ts
  • packages/2-sql/9-family/src/core/control-adapter.ts
  • packages/2-sql/9-family/src/core/control-instance.ts
  • packages/2-sql/9-family/src/core/migrations/types.ts
  • packages/3-mongo-target/1-mongo-target/src/core/mongo-runner.ts
  • packages/3-mongo-target/1-mongo-target/test/mongo-runner.test.ts
  • packages/3-mongo-target/2-mongo-adapter/src/core/marker-ledger.ts
  • packages/3-mongo-target/2-mongo-adapter/src/core/mongo-control-adapter.ts
  • packages/3-mongo-target/2-mongo-adapter/src/core/runner-deps.ts
  • packages/3-mongo-target/2-mongo-adapter/src/exports/control.ts
  • packages/3-mongo-target/2-mongo-adapter/test/marker-ledger.test.ts
  • packages/3-targets/3-targets/postgres/src/core/migrations/runner.ts
  • packages/3-targets/3-targets/postgres/src/core/migrations/statement-builders.ts
  • packages/3-targets/3-targets/sqlite/src/core/migrations/runner.ts
  • packages/3-targets/3-targets/sqlite/src/core/migrations/statement-builders.ts
  • packages/3-targets/6-adapters/postgres/src/core/control-adapter.ts
  • packages/3-targets/6-adapters/postgres/test/migrations/fixtures/runner-fixtures.ts
  • packages/3-targets/6-adapters/postgres/test/migrations/runner.ledger.integration.test.ts
  • packages/3-targets/6-adapters/sqlite/src/core/control-adapter.ts
  • packages/3-targets/6-adapters/sqlite/test/migrations/fixtures/runner-fixtures.ts
  • packages/3-targets/6-adapters/sqlite/test/migrations/runner.ledger.test.ts

Comment thread packages/3-targets/6-adapters/sqlite/src/core/control-adapter.ts Outdated
Comment thread packages/2-sql/9-family/src/core/control-instance.ts
wmadden added 6 commits June 1, 2026 17:41
Consolidate the SQL ledger read-coercion helpers into a shared
`@prisma-next/family-sql/ledger-read` module so both SQL adapters stop
duplicating `ledgerOriginFromStored` / `coerceLedgerAppliedAt` /
`operationCountFromStored` and the `'sha256:empty'` sentinel (now sourced
from `EMPTY_CONTRACT_HASH`).

Fix the SQLite ledger timestamp round-trip: `created_at` now defaults to a
Z-suffixed ISO string and `coerceLedgerAppliedAt` interprets any remaining
designator-less datetime as UTC, so `appliedAt` no longer shifts by the
local offset.

Align Mongo `readLedger` with the SQL adapters' lenient read policy — it
now skips legacy/malformed ledger docs instead of throwing.

Refresh the stale `isSqlControlAdapter` / `isMongoControlAdapter`
diagnostics to list `readLedger`, and cover the previously untested
op-count-mismatch guard, the Mongo wrapper-level `migrationEdges`
threading, and the `appliedAt` parsed value.

Signed-off-by: Will Madden <madden@prisma.io>
…ions

apply already forwards migrationEdges into runner.execute() per-space
options; add the field to MigrationRunnerPerSpaceOptions (with undefined
for exactOptionalPropertyTypes) and fix the mongo wrapper integration
test family cast.

Signed-off-by: Will Madden <madden@prisma.io>
…onalPropertyTypes

Runner execute options must accept explicit undefined for migrationEdges so
framework per-space options spread cleanly into mongo and SQL runners.

Signed-off-by: Will Madden <madden@prisma.io>
Use blindCast for the readLedger probe so the cast ratchet stays flat when
extending the existing isSqlControlAdapter / isMongoControlAdapter checks.

Signed-off-by: Will Madden <madden@prisma.io>
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.

2 participants