Chitragupt is a CLI, and its subcommands are the lifecycle of a schema — from seeing what would change, to applying it, to auditing what's been applied. Its own summary:
Chitragupt compares your desired schema (from schema.json) against
the actual PostgreSQL database state and generates incremental migrations.
Commands:
diff Show schema differences
generate Generate migration files from diff
migrate Run pending UP migrations
rollback Rollback last N migrations
status Show applied/pending migrations
create Create empty migration template
grants Sync database roles and grants
validate Verify migration checksumsThe core loop: diff → generate → migrate
diffconnects to the live database and prints the differences between it and the desired schema, applying nothing. It is the read-only preview — "what would change" — and the way you confirm a database matches the model (a cleandiffis zero differences).generateturns that diff into a migration (UP to apply the delta, DOWN to reverse it). If there is no difference it writes nothing — "Schema is up to date."migrateruns the pending UP migration against the database.
In local development these are chained for you: the docker stack's one-shot migration step runs generate then migrate, so a new slice's DDL appears the moment you bring the stack up — no separate step, nothing to remember.
Auditing and reversing
statusshows which migrations have been applied and which are pending — the ledger of what the database has seen.rollbackreverses the last N migrations by running their DOWN SQL. Becausegenerateemits both directions, a migration is reversible by construction.validateverifies the checksums of applied migrations — it detects whether a migration's recorded content matches what actually ran, catching tampering or drift in the migration history.createwrites an empty migration template for the rare case you need a hand-authored step outside the derived diff.
Deploy-time: the same commands, run for you
The declarative model shines at deploy. There is no committed migration log to ship — the deploy runs generate against the production database (computing the delta for whatever it currently is) and then migrate, exactly as kalki_sys. An empty diff is a no-op, so redeploying an unchanged schema does nothing.
⚠️ The deploy job auto-applies whatever diff it computes — including destructive operations. A change to the desired schema that drops a column runs at deploy with no human in the loop. So a schema change is reviewed the same way code is: by reviewing the model (the entities) in the pull request, because that is what the migration is derived from. Preview the effect first with
diffagainst a production-like database.
Roles and grants come with the migration
Schema is only half of a secure database; the other half is who can touch it. Chitragupt handles that as part of generate/migrate, not as a separate step: the diff includes a bootstrap CREATE ROLE for the least-privileged application role (when it doesn't yet exist), and every CREATE SCHEMA bundles the GRANTs that role needs on the tables Chitragupt creates. So the least-privilege boundary is maintained automatically as the schema grows — you don't run a command to sync grants; they ride the migration. (The grants subcommand is reserved for future standalone role syncing; today the role and GRANT emission lives in the diff.) Row-level security, the other half of the isolation story, is on the Security & roles page.
doctor — a pre-deploy health check
Because RLS silently no-ops if the request path runs as a superuser, Chitragupt ships a read-only doctor command that catches that trap before it bites. It probes both connection pools and reports findings by severity: it fails if the app pool's role is a superuser or has BYPASSRLS (every policy would be silently ignored), and warns on softer misconfigurations — the system pool not actually being elevated, a role identity that doesn't match what the datastore declares, single-role mode, or policies still targeting PUBLIC. Run it as a fast gate before trusting a deployment's isolation.
Safety: destructive operations are surfaced, not hidden
Chitragupt classifies every operation by risk — safe, warning (a column-type or nullability change), destructive (dropping a column, index, constraint, or policy), or irreversible (dropping a table, schema, or enum). At generate time destructive ops are printed with a ! marker and a warning banner, and the generated SQL carries inline -- WARNING [DESTRUCTIVE] comments. It also refuses to guess: adding a NOT NULL column with no safe default fails generation with a clear error rather than emitting SQL that would break on existing rows — you fix it in the model and re-generate. Preview anything with --dry-run, which shows what would run without executing it. (The diff + generate pair is the plan — there's no separate plan step.)
A pin bump needs the tool rebuilt
One operational note: Chitragupt is versioned with the framework. When you bump the framework pin, the chitragupt binary must be rebuilt at the new version — the deploy image builds it at $FRAMEWORK_VERSION, and running an older binary against a newer desired schema computes a stale diff. Match the tool to the pin.