Database migration governance for Zidney's database-per-tenant architecture — naming, safety, tenant fan-out, and rollback rules
Zidney uses a database-per-tenant model with PostgreSQL. Every schema change must be applied to ALL tenant databases. This makes migrations the highest-risk operation in the platform.
This skill consolidates migration rules from apps/api/AGENTS.md, docker/AGENTS.md, and the root AGENTS.md into a single authoritative source.
apps/api/src/db/master/migrations/ ← Master (platform) DB migrations
apps/api/src/db/tenant/migrations/ ← Tenant DB migrations (applied per-tenant)
AI must never write migration SQL outside these directories.
NNNN_<descriptive_name>.ts (zero-padded sequential number)schema_versionFor any column or table change, follow this 3-phase pattern:
When applying tenant migrations:
Before writing any migration, AI must evaluate:
| Operation | Lock Level | Risk |
|---|---|---|
| ADD COLUMN (nullable) | ACCESS EXCLUSIVE (brief) | Low |
| ADD COLUMN (with default) | ACCESS EXCLUSIVE | Medium — PG 11+ handles this fast |
| DROP COLUMN | ACCESS EXCLUSIVE (brief) | Low |
| ADD INDEX | SHARE (blocks writes) | HIGH — use CONCURRENTLY |
| ADD INDEX CONCURRENTLY | SHARE UPDATE EXCLUSIVE | Low |
| ALTER COLUMN TYPE | ACCESS EXCLUSIVE | HIGH — rewrites table |
| ADD NOT NULL constraint | ACCESS EXCLUSIVE | HIGH — full table scan |
For HIGH-risk operations:
CREATE INDEX CONCURRENTLY instead of CREATE INDEXDROP TABLE without explicit approval — archived data must be preservedTRUNCATE in migration filesDELETE without WHERE in migration filesMigrations are generated via Drizzle Kit but may be hand-edited for safety:
bun drizzle-kit generate # Generate migration from schema changes
bun drizzle-kit push # Apply to development database (NEVER production)
AI must review generated migrations before committing — Drizzle may generate unsafe operations.
Before committing any migration:
schema_version incrementbun run arch:audit to validate architecture complianceVERDICT: PASS — migration follows all rules
VERDICT: BLOCKED — migration violates safety rules (specify which)