Use this matrix before merging changes.
- Areas:
app/admin,app/api/admin,lib/admin,lib/control-plane - Minimum checks:
npx tsc -p tsconfig.json --noEmitnpm run check:admin-nav
- Also verify:
- CSRF still enforced on privileged mutations
- audit logging still writes correctly
- permission gates match route intent
- Areas:
lib/billing,app/api/admin/orgs/*/plan,app/api/admin/trials* - Minimum checks:
npx tsc -p tsconfig.json --noEmit
- Also verify:
- restore/blocked status logic
- plan sync and entitlement sync
- trial expiry/extension semantics
- Areas:
lib/security,app/api/admin/security*,app/api/session/* - Minimum checks:
npx tsc -p tsconfig.json --noEmitnpm run check:security-baseline
- Also verify:
- authz boundaries
- rate limiting
- session revoke / alert state transitions
- Areas:
app/(marketing) - Minimum checks:
npx tsc -p tsconfig.json --noEmitnpm run audit:marketing-copy
- Also verify:
- no unsupported claims
- trust/procurement links still resolve
- Areas:
supabase/migrations - Minimum checks:
npx tsc -p tsconfig.json --noEmitnpm run test:db:ledger-alignment(R6 — fails if FS files drift from the recorded ledger snapshot)npm run test:db:secdef-grants(Audit 2026-05-27 — fails if a new SECURITY DEFINER function leaks anon/auth EXECUTE; allowlist inscripts/.security-definer-rpc-allowlist.json)
- Also verify:
- route assumptions match column names
- lifecycle/audit/status semantics stay consistent
- new columns have safe defaults where needed
- For SECURITY DEFINER functions:
REVOKE EXECUTE ... FROM PUBLIC, anon, authenticated;explicitly — Supabase auto-grants to anon + authenticated on CREATE FUNCTION via default privileges; aREVOKE FROM PUBLICalone does NOT remove the explicit anon/auth grants. Seesupabase/migrations/20260624060_*for the canonical pattern.
- Before applying a new migration to production:
- The historical "19/217 ledger gap" is repaired as of 2026-05-27 (R6). For any
new migration, apply via
mcp__claude_ai_Supabase__execute_sqland explicitly INSERT the ledger row under the FS-prefix version. Seedocs/operations/migration-history-repair.mdfor the pattern + idempotent insert recipe. - For tenant-table DDL: verify on a dev branch (
supabase branch create) and confirmmcp__claude_ai_Supabase__get_advisorsreports no new RLS / search-path warnings before merging. - For schema additions that touch existing tables: rerun
npm run db:ledger:snapshotafter apply so the alignment check has the latest baseline.
- The historical "19/217 ledger gap" is repaired as of 2026-05-27 (R6). For any
new migration, apply via
- New code that reads or writes tenant tables MUST use
createSupabaseOrgClient(orgId)from@/lib/supabase/org-scopedrather thancreateSupabaseAdminClient()+.eq('organization_id', orgId). The wrapper makes the org filter structural so a missed.eq()cannot leak rows across tenants once FORCE RLS is enabled. - If admin-client access is intentional (cron, cross-tenant scan, security
detection that legitimately spans orgs), suppress the warning inline with:
// eslint-disable-next-line formaos/no-admin-client-with-org-filter // Reason: <one-line justification> const admin = createSupabaseAdminClient(); - When you add a new tenant table, register it in
TENANT_TABLE_SCOPESinlib/supabase/org-scoped.tsalong with itsorganization_id/org_idcolumn name. The wrapper will throw at runtime if a caller touches an unregistered table — this is intentional and forces an explicit decision rather than silent admin-fall-through.
- Areas:
app/onboarding,lib/analytics/activation-telemetry,lib/provisioning - Minimum checks:
npx tsc -p tsconfig.json --noEmit
- Also verify:
- step progression
- fallback/error behavior
- activation metrics remain consistent
- Areas:
lib/audit,app/api/audit/*,app/api/cron/audit-chain-anchor - Minimum checks:
npx jest __tests__/lib/auditnpm run test:db:secdef-grants
- Also verify:
- any new SECURITY DEFINER function REVOKEs anon + authenticated explicitly
entry_hash+entry_maccolumns NOT mutated post-write (RESTRICTIVE policies)- canonical JSON payload in
hash-utils.tsmatches Postgres_audit_log_compute_hash_v2byte-for-byte - new audit-emitting paths use
writeAuditLog()(v2 default, v3-hmac underAUDIT_CHAIN_V3_ENABLED=true), never rawINSERT INTO audit_log
- Areas:
docs/operations,docs/audit,docs/adr - Minimum checks:
npm run test:db:ledger-alignmentnpm run test:db:restore-recency(passes warn-only until first DR drill recorded)npm run test:security:leaked-secrets
- Cadence:
- Monthly PITR restore drill —
docs/operations/pitr-restore-runbook.md. Record viascripts/verify-restore.mjsagainst a restored branch. - Per secret-rotation event —
docs/operations/secret-rotation-runbook.md. Record viascripts/record-secret-rotation.mjs. - Monthly dormant-user review — read latest
dormant_user_reviewsrow.
- Monthly PITR restore drill —