Skip to content

TML-3072: fold ref-paired snapshots into the content-addressed store - #1024

Merged
wmadden-electric merged 1 commit into
mainfrom
tml-3072-deduplicate-ref-paired-snapshots-into-migrationssnapshots
Jul 22, 2026
Merged

TML-3072: fold ref-paired snapshots into the content-addressed store#1024
wmadden-electric merged 1 commit into
mainfrom
tml-3072-deduplicate-ref-paired-snapshots-into-migrationssnapshots

Conversation

@wmadden-electric

Copy link
Copy Markdown
Contributor

Linked issue

Refs TML-3072. Stacked on #1018 (TML-3059, the content-addressed snapshot store this folds into); review and merge that first.

At a glance

migrations/
  refs/
    prod.json                     # { "hash": "sha256:efd4…", "invariants": [...] } — the whole ref
  snapshots/
    efd408cf…d51e/
      contract.json               # the contract prod points at, stored once by hash
      contract.d.ts

Before this PR a ref was the pointer plus paired sibling files refs/prod.contract.json / refs/prod.contract.d.ts — a full contract copy per ref, duplicating bytes the snapshot store already holds.

Decision

A ref is now only its refs/<name>.json pointer; the contract it names resolves through migrations/snapshots/<hex>/ by the pointer's hash. The ADR-218 ref-paired snapshot files are neither written nor read anywhere, refs/snapshot.ts is deleted, and "snapshot" stops being an overloaded concept: ref = pointer, one content-addressed store. The one-shot layout migrator from #1018 also folds any existing ref pairs, so downstream trees convert in the same single run. Design record: the "Snapshot concept boundary, and the path to one store" section of ADR 240, now stated as shipped; ADR 218 carries a status note.

Reviewer notes

  • ref delete no longer deletes contract bytes. Store entries are shared by hash — another ref or a migration bookend may reference the same contract — so delete removes only the pointer. Orphan store entries are by design (content-addressed, write-if-absent); the old orphan-healing logic is deleted with nothing to heal.
  • Error semantics sharpened. A pointer whose store entry is missing is a hard MIGRATION.CONTRACT_SNAPSHOT_MISSING error, never a silent fallback to graph-node resolution (one e2e scenario asserting that fallback was deleted — its premise no longer exists). The pointer-absent error is renamed MIGRATION.SNAPSHOT_MISSINGMIGRATION.REF_NOT_RESOLVABLE with text describing what it now means; error codes are new in 0.17 (ADR 239), so the rename ships before any release carries the old name.
  • Provenance retag. ContractAtResult.provenance literal 'snapshot''ref' — the old name was inverted (the literal store read was tagged 'graph-node' while 'snapshot' meant the refs read). Swept through plan-resolution.ts, migration-plan.ts output, and tests.
  • contractAt now returns the pointer's hash for the ref branch (authoritative), not the caller's hash argument; pinned by a test.
  • Write ordering. Ref advancement validates the ref name, writes the store entry (write-if-absent), then the pointer — a crash between the writes leaves a harmless orphan store entry, never a dangling pointer.

How it fits together

  1. ReadercontractAt's ref branch (aggregate.ts) reads the pointer via readRef, then the store entry keyed by the pointer's hash. Pointer absent → existing graph-node fallback → REF_NOT_RESOLVABLE.
  2. Writers — all four commands funnel through two sites: executeRefAdvancement (db init / db update / migrate --advance-ref) writes store-then-pointer; ref set just writes the pointer (it reads the bytes from the store, so the store write is a guaranteed no-op).
  3. Deletionref delete unlinks the pointer only.
  4. Conversionscripts/migrate-migrations-layout.mjs gains a refs step: plan (pointer must exist, inner storage.storageHash must equal the pointer's hash, .d.ts must exist — any violation aborts before a single write across all roots), then apply (write-if-absent into the store, delete the pair, pointer untouched).
  5. Downstream guidance — the 0.16-to-0.17 upgrade instructions in both skills (user, extension author) document the layout change with a **/refs/*.contract.json detection glob.

Behavior changes & evidence

Summary

Removes the last full-contract copies in the RC-frozen migrations/ layout so every contract snapshot (migration bookend, ref, extension head) lives behind one hash-addressed store. Sequenced before the RC layout freeze because refs/<name>.contract.* is public on-disk surface.

Testing performed

  • pnpm build, pnpm typecheck (143/143), pnpm lint:deps, pnpm lint:throws (delta 0), pnpm lint:casts (−5), pnpm lint:framework-vocabulary, pnpm fixtures:check
  • pnpm test:packages — 13,325 passed (migration-tools 585, cli 1,350)
  • pnpm test:integration — 1,172 passed (one run hit the known sql-orm-client PGlite parallel-load flake, "portal C_N does not exist", in 3 files; full re-run 1,172/1,172 green)
  • pnpm test:e2e — 109 passed; pnpm test:scripts — all passed
  • pnpm check:upgrade-coverage --mode pr, pnpm check:release-notes --mode pr, pnpm lint:skills

Skill update

Both 0.16-to-0.17 upgrade-instruction files gained a ref-paired-snapshots-moved-to-content-addressed-store entry; skills/prisma-next-migrations/SKILL.md and skills/prisma-next-migration-review/SKILL.md reworded to the pointer+store model.

Alternatives considered

  • Keep ref-paired files as a readable fallback. Rejected: two readers for one concept is the overload this ticket removes, and 0.17 is already a clean layout break (tml-3059: deduplicate migration contracts into a content-addressed migrations/snapshots/ store #1018) — one conversion, one model.
  • Delete store entries on ref delete when unreferenced. Rejected: requires reference counting across refs and graph nodes for no benefit; orphan content-addressed entries are inert and reclaimed naturally by the migrator/regen flows.
  • Rename the CLI-level SNAPSHOT_MISSING error too. Kept: it serves a second, unrelated scenario (viaRef: false) that was never part of the paired-snapshot model; only the migration-tools error whose meaning actually changed was renamed.

Checklist

  • All commits are signed off (git commit -s) per the DCO.
  • 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.

@wmadden-electric
wmadden-electric requested a review from a team as a code owner July 21, 2026 18:14
@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@wmadden-electric, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 19 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 8f66f787-b414-43ca-b543-e53e08bdb9a1

📥 Commits

Reviewing files that changed from the base of the PR and between 0e51f1f and 13a42b1.

📒 Files selected for processing (37)
  • docs/architecture docs/adrs/ADR 218 - Refs with paired contract snapshots and universal graph-node invariant.md
  • docs/architecture docs/adrs/ADR 240 - Contract snapshots live in a content-addressed store.md
  • docs/architecture docs/subsystems/7. Migration System.md
  • docs/glossary.md
  • packages/1-framework/3-tooling/cli/src/commands/db-init.ts
  • packages/1-framework/3-tooling/cli/src/commands/db-update.ts
  • packages/1-framework/3-tooling/cli/src/commands/migrate.ts
  • packages/1-framework/3-tooling/cli/src/commands/migration-plan.ts
  • packages/1-framework/3-tooling/cli/src/commands/ref.ts
  • packages/1-framework/3-tooling/cli/src/utils/cli-errors.ts
  • packages/1-framework/3-tooling/cli/src/utils/contract-at-errors.ts
  • packages/1-framework/3-tooling/cli/src/utils/plan-resolution.ts
  • packages/1-framework/3-tooling/cli/src/utils/ref-advancement.ts
  • packages/1-framework/3-tooling/cli/test/commands/migration-plan-command.test.ts
  • packages/1-framework/3-tooling/cli/test/commands/read-commands-json-golden.test.ts
  • packages/1-framework/3-tooling/cli/test/commands/ref.test.ts
  • packages/1-framework/3-tooling/cli/test/utils/plan-resolution.test.ts
  • packages/1-framework/3-tooling/cli/test/utils/ref-advancement.test.ts
  • packages/1-framework/3-tooling/migration/src/aggregate/aggregate.ts
  • packages/1-framework/3-tooling/migration/src/aggregate/types.ts
  • packages/1-framework/3-tooling/migration/src/errors.ts
  • packages/1-framework/3-tooling/migration/src/exports/errors.ts
  • packages/1-framework/3-tooling/migration/src/exports/refs.ts
  • packages/1-framework/3-tooling/migration/src/refs/snapshot.ts
  • packages/1-framework/3-tooling/migration/test/aggregate/contract-at.test.ts
  • packages/1-framework/3-tooling/migration/test/refs/snapshot-failure.test.ts
  • packages/1-framework/3-tooling/migration/test/refs/snapshot.test.ts
  • scripts/migrate-migrations-layout.mjs
  • scripts/migrate-migrations-layout.test.mjs
  • skills/extension-author/prisma-next-extension-upgrade/upgrades/0.16-to-0.17/instructions.md
  • skills/prisma-next-migration-review/SKILL.md
  • skills/prisma-next-migrations/SKILL.md
  • skills/upgrade/prisma-next-upgrade/upgrades/0.16-to-0.17/instructions.md
  • test/integration/test/cli.db-ref-advancement.e2e.test.ts
  • test/integration/test/cli.migrate-ref-advancement.e2e.test.ts
  • test/integration/test/cli.migration-plan-ref-aware.e2e.test.ts
  • test/integration/test/cli.ref-pointer-integration.e2e.test.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch tml-3072-deduplicate-ref-paired-snapshots-into-migrationssnapshots

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.

@github-actions

Copy link
Copy Markdown

size-limit report 📦

Path Size
postgres / no-emit 161.81 KB (0%)
postgres / emit 145.14 KB (0%)
mongo / no-emit 99.67 KB (0%)
mongo / emit 89.86 KB (0%)
cf-worker / no-emit 186.83 KB (0%)
cf-worker / emit 168.17 KB (0%)

@pkg-pr-new

pkg-pr-new Bot commented Jul 21, 2026

Copy link
Copy Markdown

Open in StackBlitz

@prisma-next/extension-author-tools

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

@prisma-next/mongo-runtime

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

@prisma-next/family-mongo

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

@prisma-next/sql-runtime

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

@prisma-next/family-sql

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

@prisma-next/extension-arktype-json

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

@prisma-next/middleware-cache

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

@prisma-next/mongo

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

@prisma-next/extension-paradedb

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

@prisma-next/extension-pgvector

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

@prisma-next/extension-postgis

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

@prisma-next/postgres

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

@prisma-next/sql-orm-client

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

@prisma-next/sqlite

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

@prisma-next/extension-supabase

npm i https://pkg.pr.new/@prisma-next/extension-supabase@1024

@prisma-next/target-mongo

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

@prisma-next/adapter-mongo

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

@prisma-next/driver-mongo

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

@prisma-next/contract

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

@prisma-next/utils

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

@prisma-next/config

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

@prisma-next/errors

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

@prisma-next/framework-components

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

@prisma-next/operations

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

@prisma-next/ts-render

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

@prisma-next/contract-authoring

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

@prisma-next/ids

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

@prisma-next/psl-parser

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

@prisma-next/psl-printer

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

@prisma-next/cli

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

@prisma-next/cli-telemetry

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

@prisma-next/config-loader

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

@prisma-next/emitter

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

@prisma-next/language-server

npm i https://pkg.pr.new/@prisma-next/language-server@1024

@prisma-next/migration-tools

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

prisma-next

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

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

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

@prisma-next/mongo-codec

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

@prisma-next/mongo-contract

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

@prisma-next/mongo-value

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

@prisma-next/mongo-contract-psl

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

@prisma-next/mongo-contract-ts

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

@prisma-next/mongo-emitter

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

@prisma-next/mongo-schema-ir

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

@prisma-next/mongo-query-ast

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

@prisma-next/mongo-orm

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

@prisma-next/mongo-query-builder

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

@prisma-next/mongo-lowering

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

@prisma-next/mongo-wire

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

@prisma-next/sql-contract

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

@prisma-next/sql-errors

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

@prisma-next/sql-operations

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

@prisma-next/sql-schema-ir

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

@prisma-next/sql-contract-psl

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

@prisma-next/sql-contract-ts

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

@prisma-next/sql-contract-emitter

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

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

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

@prisma-next/sql-relational-core

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

@prisma-next/sql-builder

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

@prisma-next/target-postgres

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

@prisma-next/target-sqlite

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

@prisma-next/adapter-postgres

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

@prisma-next/adapter-sqlite

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

@prisma-next/driver-postgres

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

@prisma-next/driver-sqlite

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

commit: 13a42b1

Base automatically changed from tml-3059-dedupe-migration-snapshots to main July 22, 2026 09:56
…the store

A ref is now only its refs/<name>.json pointer ({hash, invariants}); the
contract it names resolves through migrations/snapshots/<hex>/ by the
pointer's hash. The ADR-218 paired sibling files (refs/<name>.contract.json
/ .contract.d.ts) are neither written nor read: contractAt's ref branch
reads pointer-then-store (provenance retagged 'snapshot' -> 'ref'),
ref-advancement and ref set write store-then-pointer (write-if-absent), and
ref delete removes only the pointer — store entries are shared by hash.
refs/snapshot.ts is deleted; a pointer whose store entry is missing is a
hard MIGRATION.CONTRACT_SNAPSHOT_MISSING error, and the pointer-absent
error is renamed MIGRATION.REF_NOT_RESOLVABLE to describe what it now
means. The one-shot migrator folds existing ref pairs into the store, and
the 0.16-to-0.17 upgrade instructions (both skills) document the layout
change. ADR 218 carries a status note; ADR 240's path-to-one-store section
now states the fold as shipped.

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
wmadden-electric force-pushed the tml-3072-deduplicate-ref-paired-snapshots-into-migrationssnapshots branch from 854ce6d to 13a42b1 Compare July 22, 2026 09:57
@wmadden-electric
wmadden-electric added this pull request to the merge queue Jul 22, 2026
Merged via the queue into main with commit 6285c8e Jul 22, 2026
22 checks passed
@wmadden-electric
wmadden-electric deleted the tml-3072-deduplicate-ref-paired-snapshots-into-migrationssnapshots branch July 22, 2026 11:35
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