The database is the schema. Diff it like one.
dpm is declarative, ORM-agnostic Postgres schema migration in Rust: introspect two states, diff the catalogs, emit reviewable SQL that converges one onto the other — then prove it converged, and let seven independent tools countersign the proof.
Install
# curl — prebuilt binary from the latest release (cargo fallback) curl -fsSL https://raw.githubusercontent.com/declarative-migrations/declarative-postgres-migrate.rs/main/scripts/install.sh | bash # Homebrew brew install declarative-migrations/tap/dpm # From source cargo install --git https://github.com/declarative-migrations/declarative-postgres-migrate.rs
One idea, taken seriously
The Postgres system catalogs are the neutral interchange format. Whether a schema was authored
by Prisma, Drizzle, SeaORM, ent, peewee, or raw SQL — once it exists,
pg_catalog describes it canonically. dpm deparses both sides
with the server itself (pg_get_constraintdef,
pg_get_indexdef, format_type, … under
search_path = ''), so comparison is exact string equality.
No regex reconciliation of hand-written SQL against Postgres' re-serialized forms.
dpm diff --source postgres://…/desired --target postgres://…/live dpm verify --source schema.sql --target "$DATABASE_URL" --shadow "$SHADOW_DATABASE_URL" --cross-check-all dpm apply --yes # interactive confirm without --yes; destructive ops need explicit consent
Any source, any target
Live database ↔ catalog.json dump ↔ schema.sql (or a pg_dump -s dump, materialized on a shadow server). All nine pairings produce byte-identical migrations — asserted in CI.
Convergence, proven
dpm verify replays your target onto a throwaway replica, applies the migration, re-introspects, re-diffs. An empty plan is the proof. Exit codes made for CI.
Destructive = two consents
--allow-destructive-sql to generate drops (otherwise commented out), --allow-destructive-ops to execute them. Apply fails closed.
Supabase-aware
Managed schemas excluded by default, RLS + policies are first-class diffed objects, transaction-pooler connections detected and refused with a fix hint.
Seven tools countersign every migration
dpm never asks you to take its word for it. Six independent diff engines must see zero remaining difference after dpm migrates; flyway validates the script under a standard migration runner. The test matrix runs all of them against ten schema fixtures — and it has already caught real bugs (that's the point).
dpm verify … --cross-check-all # every installed tool dpm verify … --cross-check-with-migra --cross-check-with-atlas dpm verify … --cross-check-with-ai # AI discrepancy scan over all reports
The AI layer reviews migrations too — dpm review drives the
Anthropic / OpenAI / Gemini HTTP APIs (or the claude / codex / gemini CLIs) with a fail-closed
machine verdict: no parseable DPM_VERDICT, no approval.
ORM-agnostic by construction
| You use | dpm source |
|---|---|
| Drizzle | drizzle-kit export → schema.sql |
| Prisma | prisma migrate diff --from-empty --to-schema-datamodel --script |
| SeaORM / sqlx / ent / Django / Rails | migrations → scratch db → --source postgres://scratch |
| Raw SQL | the file is the source |
“Diff my Drizzle app against the SeaORM service's database” is one command. dpm never parses ORM code.
How verify works
- 01Everything is reviewable SQL — dpm never executes without explicit consent.
- 02Constraint adds on existing tables use NOT VALID + VALIDATE; enum values are added outside the transaction; FKs land last.
- 03Manual-review items (enum label removal, partition-strategy changes) are surfaced as comments, never guessed at.