Database Runbook
This runbook covers the migration workflow, Neon branch strategy, and RLS testing for Factory apps.Architecture
Every Factory app gets an isolated Neon project (not just a schema). This ensures:- No cross-app data leakage at the database level
- Independent scaling and compute pause per app
- Clean isolation during app transfer to a buyer
env.DB) for connection pooling and latency reduction.
Neon branch strategy
Running migrations
Migrations live insrc/db/migrations/ as numbered SQL files (001_init.sql, etc.).
Apply manually
Apply via CI
Themigrate.yml workflow runs on push to main and applies migrations automatically using the DATABASE_URL GitHub secret.
Inspecting the schema
Factory events table
Each app maintains its ownfactory_events table inside its own isolated Neon database. The schema is identical across all apps (shared DDL from the scaffold), but there is no central database — each app writes and queries its own events independently via @latimer-woods-tech/analytics.
A future factory_core Neon project will aggregate events across all apps when factory-admin is built. Until then, cross-app analytics require querying each app’s database separately.
Row-Level Security (RLS)
Factory apps use RLS to isolate tenant data when multiple tenants share a table.Enabling RLS
Setting the tenant context per request
Testing RLS in development
Backup and restore
Neon provides continuous branching which doubles as PITR (Point-in-Time Recovery). For explicit backups:Neon connection strings
Connection strings follow this format:Getting a WORKING string (you have operator access — use it)
Do not assume you lack DB access, and do not trust the stored*_CONNECTION_STRING secrets blindly — they drift (rotated password not
propagated to GCP) and several carry a leading UTF-8 BOM. Mint a fresh one:
password authentication failed (rotated pw) or is not a valid URL
(leading BOM — strip with while (cs.charCodeAt(0) !== 0x70) cs = cs.slice(1)).
See lessons-learned.md → “You HAVE Neon access” for the full rationale.
Storing for an app
Store in GitHub Secrets as{APP}_CONNECTION_STRING and pass to Wrangler: