Skip to content

Database Migrations

OpenInsure runs six databases across three hosting providers. Each has its own migration strategy, CLI, and deploy cadence.

┌─────────────────────────────────────────────────────────────────────┐
│ CLOUDFLARE EDGE │
│ │
│ oi-sys-api ──Hyperdrive──▶ PlanetScale (ai) │
│ oi-sys-auth ──D1──▶ oi-auth │
│ oi-sys-uw ──D1──▶ oi-submissions │
│ KV (cache, sessions, config) · R2 (documents, assets) │
└──────────────────────────────┬──────────────────────────────────────┘
┌──────────────────────────────▼──────────────────────────────────────┐
│ PLANETSCALE (pushdown org) │
│ ai ········· 171 tables · Drizzle ORM · Branches: main, ci-test │
│ spicedb ···· SpiceDB datastore · Branch: main │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ FLY.IO (iad) │
│ openinsure-spicedb ······· SpiceDB serve (reads PlanetScale) │
│ openinsure-documenso-db ·· Fly Postgres (Prisma, auto-migrate) │
│ openinsure-tigerbeetle ··· TigerBeetle ledger (compile-time) │
└─────────────────────────────────────────────────────────────────────┘
DatabaseEngineCLIMigration ToolPurpose
pushdown/aiVitess 22.0pscaleDrizzle ORMPrimary transactional (171 tables)
pushdown/spicedbVitess 22.0pscaleSpiceDB binaryZanzibar authorization
oi-submissionsD1 (SQLite)wranglerD1 migrationsAspire UW form data
oi-authD1 (SQLite)wranglerD1 migrationsIdentity store
documenso-dbPostgresflyctlPrismaE-signature documents
TigerBeetleTigerBeetleflyctlCompile-timeDouble-entry ledger

All databases are fully migrated and in sync.

DatabaseStatusDetail
PlanetScale aimain (DR #7)171 tables
PlanetScale spicedbmainAt head
D1 oi-submissionsProduction15 migrations
D1 oi-authProduction2 migrations
Fly documenso-dbProductionPrisma auto
TigerBeetleRunningCompile-time
Terminal window
openinsure db status # All 6 databases in one view (~2s)
openinsure db migrate # Run all pending migrations (parallel)
openinsure db migrate --ps # PlanetScale only
openinsure db migrate --d1 # D1 only (submissions + auth)
openinsure db migrate --spicedb # SpiceDB only
openinsure db deploy # Interactive PlanetScale deploy request
make db-status # Delegates to CLI
make db-migrate # Delegates to CLI
make db-migrate-ps-deploy # Delegates to CLI
CronUTCJob
0 3 * * *03:00Earned premium batch (GL + TigerBeetle)
0 4 * * 004:00 SunD1 retention cleanup
0 5 * * *05:00Infrastructure health rollup
0 6 * * *06:00Portfolio sweep
0 7 * * *07:00Ledger reconciliation (TigerBeetle vs GL)
0 8 * * *08:00Payment reminders
0 9 * * *09:00Renewal notices
0 18 * * 1-518:00 weekdaysFL DHSMV export
*/15 * * * *every 15mMailbox ingest

TigerBeetle is the authoritative double-entry ledger for all financial transactions.

9 account types per org: CARRIER_PAYABLE, MGA_FIDUCIARY, MGA_REVENUE, PRODUCER_PAYABLE, TAX_AUTHORITY_PAYABLE, LOSS_FUND, CLAIMS_PAID, RESERVES, REINSURER_PAYABLE.

Three active data flows:

  1. Earned premium (03:00 UTC cron) — posts LOSS_FUND to CARRIER_PAYABLE transfer after GL journal entries
  2. Bordereau settlement — SettlementService moves funds from payable accounts to external payout accounts
  3. Ledger reconciliation (07:00 UTC cron) — compares TigerBeetle balances against PlanetScale GL per org

Graceful degradation: if TigerBeetle is down, GL posting succeeds and reconciliation catches drift.

  1. Edit schema in packages/db/src/schema/ 2. Push: make db-migrate-ps (opens tunnel to ci-test) 3. Verify: pscale branch diff ai ci-test 4. Deploy: pnpm cli db deploy (interactive: create, approve, deploy DR) 5. Regenerate types: make db-generate
  1. Add .sql file to apps/underwriting-workbench/migrations/ or apps/auth/migrations/ 2. Test locally: make db-migrate-d1-local 3. Apply remote: make db-migrate-d1

Auto-migrates on container restart (spicedb datastore migrate head). Manual: make db-migrate-spicedb.

YOU manage (Drizzle / Wrangler):
pushdown/ai ········ Drizzle schema + migrations
oi-submissions ····· SQL migration files
oi-auth ············ SQL migration files
SELF-MANAGED (by application binary):
pushdown/spicedb ··· spicedb datastore migrate head
documenso-db ······· Prisma migrate (on deploy)
TigerBeetle ········ Compile-time schema (no DDL)

D1 “duplicate column name” — Column exists but migration wasn’t recorded. Mark as applied:

Terminal window
npx wrangler d1 execute <DB> --remote --config <CONFIG> \
--command "INSERT INTO d1_migrations (name) VALUES ('<FILE>')"

PlanetScale “BLOB/TEXT in key” — Change text() to varchar('col', { length: 255 }) in Drizzle schema.

PlanetScale “column cannot be null” — Existing rows have NULLs. Make column nullable or add default.

TigerBeetle health failing — Check proxy: curl https://openinsure-tigerbeetle.fly.dev/health

Database Schema

Table definitions and ORM patterns.

Billing (TigerBeetle)

Fiduciary split flow and ledger accounts.

CI/CD

How migrations run in CircleCI.