Part of the coding standards. supabase/README.md owns migration order, table grants, and the database gate. docs/operations.md owns applying migrations to staging and production.
- A
CHECKpasses when its expression isNULL. Test nullable columns explicitly withIS NOT NULLinside the branch that requires them (#19). - Grant the privileges a client read path needs, and let RLS limit the rows. Revoking
SELECTbroke the daily usage count (#70). When a migration narrows grants, check every client write path that touches the table (#44).
npm run lint runs squawk on new migrations. Check every -- squawk-ignore against the table's production size: an ignore is fine on a small table and wrong on a large one. Index builds on populated tables use CREATE INDEX CONCURRENTLY in their own file (#13), and long locks run at low traffic (#11).
Do not compare a timestamp produced by an Edge Function with one produced by Postgres. Clock skew made the erase fence either accept writes after "Delete all data" or reject fresh requests. Take both times from the database, or compare counters (#13).
A migration that anyone can apply must not delete user data as a side effect. Put the deletion in a function, leave the call out of the migration, and run it as a separate step that needs explicit authorization for that environment. Write down the preservation and restore path before that step (#72, #71). 20260926090100_legacy_debate_cleanup.sql now follows this.
Use the generated Database type with createClient<Database>(...) rather than casting the client through unknown (#13). A service-role client filters by the user ID it validated from the bearer token, never by an ID from the request body (#90 security review).