Migration frameworks: the SQL your ORM wrote
Prisma, Drizzle, Supabase, Flyway, Liquibase, dbmate, golang-migrate, sqlx, Atlas, Sqitch, Hasura and Marten are detected from the repository and checked as the SQL they generated; Rails, Django, Knex, TypeORM and Marten's AutoCreate are run by bolvrk capture against a throwaway Postgres and checked as the SQL they executed.
Most migrations are not written by hand any more. A developer, or an agent, changes a schema file and the framework writes the SQL: an @@index line in a Prisma schema becomes a plain CREATE INDEX, which holds a SHARE lock on the table for the whole build. Nothing in the schema file says so, and the reviewer approves the schema file. The safe form exists in every framework and every framework defaults away from it, because CONCURRENTLY cannot run inside the transaction they wrap migrations in.
So the check runs where the truth is: on the generated SQL, in the directory the framework keeps it. With no files named and no globs in bolvrk.json, bolvrk check looks for a recognised layout and checks it; the Action does the same when its migrations input is empty. Prisma and Drizzle also say which database they target in their own config, so a SQLite project gets the SQLite corpus without a flag.
$ npx bolvrk check
Prisma: checking 3 migration files under prisma/migrations/*/migration.sql (postgres).
# pick one, or turn detection off
npx bolvrk check --framework=drizzle
npx bolvrk check --framework=none migrations/*.sqlWhat is recognised
Prisma (prisma/migrations/*/migration.sql), Drizzle (drizzle/*.sql), Supabase (supabase/migrations/*.sql), Flyway (V*__*.sql under sql/, db/migration/ or src/main/resources/db/migration/), Liquibase formatted SQL changelogs, dbmate (db/migrations/*.sql, the migrate:up section only), golang-migrate (*.up.sql), sqlx, Atlas, Sqitch (deploy/*.sql), Hasura (migrations/**/up.sql) and Marten (a .NET project referencing the package, with db-patch output under marten/ or db/patches/). A bare migrations/ folder only counts when a matching file exists, so an unrelated directory of that name claims nothing.
The framework's own format is never parsed and no DSL is interpreted: an adapter is a directory layout, the globs for the forward direction, and at most a slice of the file.
Frameworks whose migrations are code: bolvrk capture
Rails, Django, Knex, TypeORM, Sequelize and friends keep no SQL on disk; the SQL exists only when they run. Marten is both: its db-patch files are SQL on disk, and its AutoCreate mode applies schema changes at application start, which only capture can see. So bolvrk capture runs your own migrate command against a throwaway Postgres it starts with Docker, statement logging on, and checks what the server was told to do. The command sees DATABASE_URL and the PG* variables pointed at the throwaway server, empty and destroyed afterwards, and nothing else database-shaped: a tool configured for production cannot reach it from here.
Statements are grouped by the migration the tool recorded in its own bookkeeping table (schema_migrations, django_migrations, and the rest), reads and the bookkeeping rows are dropped, and the result is checked as a set with the same rules, policy, formats and --remote as bolvrk check. The live-schema rules have nothing to say against an empty server; the static corpus, which is most of it, runs in full.
npx bolvrk capture -- bin/rails db:migrate
npx bolvrk capture -- python manage.py migrate
npx bolvrk capture -- npx knex migrate:latest
npx bolvrk capture --keep -- dotnet run -- db-apply # keep the captured .sql filesThe CLI is open source and needs no account: npx bolvrk check migration.sql