# ADR-0001: Database schema changes are manual Supabase CLI migrations Date: 2026-07-08 Status: Accepted ## Context Schema v1 (`db/schema.sql`, ~600 lines) was committed but wired to nothing. The only SQL that ran automatically was `nix/supabase/init.sql` via `services.postgresql.initialScript` — which executes **only at first initdb**, so on the already-initialized db-host cluster neither it nor any schema change would ever apply again. A delivery mechanism was needed. Options considered: 1. **Automated runner** — a systemd oneshot on db-host applying migrations on every deploy (dbmate/atlas or plain psql with a tracking table). 2. **Manual Supabase CLI migrations** — plain-SQL files in `supabase/migrations/`, applied by a human with `supabase db push`. 3. **Extending initialScript** — rejected outright: never re-runs on an existing cluster. ## Decision Option 2. Schema changes are rare, deliberate, admin-level operations; a human applies them on purpose, and no automation exists to break or to apply a half-reviewed migration as a side effect of a deploy. * Migrations live in `supabase/migrations/_.sql`; schema v1 moved there as the first one. `supabase/config.toml` is the minimal CLI project marker. * Applied via SSH tunnel to db-host as the `postgres` superuser (see `supabase/README.md` for the exact commands). The CLI records applied files in `supabase_migrations.schema_migrations`, so pushes are incremental and re-running is safe. Individual migrations need **not** be idempotent. * Layering rule: `nix/supabase/init.sql` holds only cluster bootstrap (Supabase schemas, admin/API roles, extensions needing `shared_preload_libraries`); everything else — including the `gebos_ingest` role and its grants, which are coupled to application tables and must reach existing clusters — lives in migrations. Passwords live in neither (see ADR-0002). ## Consequences * Deploys (`deploy-rs`) never touch the schema; a deploy and a migration are two separate, independently reversible acts. * A human must remember to push after merging a migration. Accepted for a pilot with one operator. * The local `nix run .#dev` Postgres has no TimescaleDB yet, so the v1 migration's `create_hypertable` fails there until the dev stack gains the extension.