# Workflow: safe migration deploys

## Goal
Migrations never run through the pooler, and never run unrehearsed.

## Steps
1. In CI, create a schema-only branch from production.
2. Run the migration against the schema-only branch using the direct connection string.
3. Diff the resulting schema against expectations; fail the pipeline on unexpected diffs.
4. On deploy: run the migration against production using the direct string (`DATABASE_URL_UNPOOLED` / migrate-specific env var).
5. Deploy the app with the pooled string (`DATABASE_URL`).
6. Delete the schema-only branch.

## Ordering constraints
- Migrate before deploying the code that depends on the new schema (expand-then-contract for anything destructive).
- The rehearsal branch must be created from the current production branch, or the rehearsal is fiction.

## Traps
- Running migrations through `-pooler`: PgBouncer transaction mode breaks multi-statement migrations with session state.
- One env var for both: the app gets the direct string "temporarily" and it becomes permanent; keep two vars.
- Destructive migrations (drop column) need the expand/contract pattern regardless of where they run.

## Verify
CI shows the rehearsal migration and diff on every PR; production deploys log which string the migrator used.