Releases and testing
How a change is proved correct and how it reaches production. The step-by-step runbooks are in Operations; this page is the shape of the thing.
Rules: 15 · Platform reliability (PLT).
1. Four test stacks, because there are four places a rule can live
| Stack | Proves | Command |
|---|---|---|
| Vitest (+ Testing Library) | browser code — handlers, hooks, screens, shared libraries | CI=true npx vitest run |
SQL suites in supabase/tests/*.sql |
what the database refuses | scripts/db-test.sh |
deno check |
edge functions compile | scripts/deno-check-functions.sh [name] |
| Playwright | the app loads and the main journeys work end to end | npm run e2e |
Plus two static guards: node scripts/check-migrations.mjs (duplicate versions, bad
filenames, non-idempotent CREATEs) and node scripts/check-docs.mjs (docs links, nav
coverage, retired route and permission names).
npm test opens watch mode
Use CI=true npx vitest run or npm run test:run in a script or an agent session.
npm test alone waits for keystrokes forever.
A database rule gets a database test
A rule enforced in Postgres — an RLS policy, a trigger, a constraint, an RPC — has a test in
supabase/tests/*.sql named with the rule ID it proves, wrapped in BEGIN … ROLLBACK,
ending with an ALL … PASSED line (PLT-022). A
psql exit code of 0 without that line is a failure.
Eighteen suites exist today: access_model, audit_trail, booking_lifecycle,
bookings_paging, cancellation_chain, cancellation_chain_followups, data_isolation,
finance_controls, finance_paging, incidents, inventory_holds_deadlines,
inventory_integrity, journey, list_paging, money_integrity, role_dashboards,
security_lockdown, tickets_visa_comms.
Testing a browser check is not the same as testing the rule. "Test the rule, not the
implementation" — a test names the rule ID:
it('PAX-004: infant cannot occupy a seat', …).
Type checking only counts if it runs
npx tsc --noEmit checks nothing — the root tsconfig.json is a solution file with
"files": []. It hid 41 real errors for months, including a filter control that never
rendered and realised-FX journal lines that read a field that did not exist, so they always
posted zero.
The real check is npm run typecheck (both project configs). CI runs
npm run typecheck:ratchet, which fails only on errors not in
scripts/typecheck-baseline.txt. The baseline only shrinks
(PLT-021): fixing errors regenerates it with
npm run typecheck:ratchet -- --update, and nobody adds lines to it to get a PR green.
Drift tests
Two tests exist to stop documentation and database from parting company:
src/test/permissions.matrix.test.ts— every permission referenced in code is declared inPERMISSIONS.mdand inserted by a migration; every documented permission is referenced and seeded; every documented role exists in theRoleTypeenum and gets aRolerow from a migration; andSUPER_ADMINis the only role seeded every permission.src/test/api.permission-coverage.test.ts— everyPOST/PUT/PATCH/DELETEbranch insrc/lib/api.tshas arequirePermission()within the next few lines, or an explicit// @no-permission: <reason>comment. Two incidents led to it.
The role and permission seed test came from a real failure: only two roles were ever created by a migration, so a database restored from backup had 2 roles and 204 grants instead of 20 and about 949 — everyone but the super admin saw an empty app. A restore is not a backup until it has been tried (PLT-042).
2. The merge gates
A PR merges to main only when the migration guard, lint, the type-check ratchet, the unit
tests, the build and the bundle budgets pass; plus the database suites when migrations or
tests changed, deno check when functions changed, and Playwright on every non-Dependabot
run (PLT-020). Details:
CI/CD.
3. The release path
branch → CI gates → squash merge to main
├── semantic-release tags and writes the changelog
├── Cloudflare Pages builds the app and the docs site
└── migrations: a manually approved pipeline run
(fresh backup first, then supabase db push)
Four rules govern the last step:
- Migrations are applied by the pipeline, never by hand. No
supabase db push,psqlDDL or dashboard SQL against production except during a declared incident, written up afterwards (PLT-010). - Migrations are forward-only and idempotent. A migration merged to
mainis never edited, renamed or deleted; fixes are new migrations. Every statement is safe to run twice (PLT-011). - Database first, frontend second. Schema changes ship backward-compatible with the frontend that is currently live; the frontend that needs them ships after. Destructive changes ship only once no live frontend uses the old shape (PLT-014).
- Every release notes how to undo it (PLT-043), and someone runs the smoke checklist afterwards (PLT-032).
Edge functions have their own checklist: deno check passes, every secret it reads is set in
the target project, verify_jwt in supabase/config.toml matches the function's own auth,
its migrations are already applied, it is smoke-tested, and the deploy is recorded
(PLT-012).
4. What the platform does not have yet
Stated plainly because each is a real risk, not a nicety:
| Missing | Rule | Consequence |
|---|---|---|
| A staging environment | PLT-001 | Migrations and functions go from a laptop to production |
| Point-in-time recovery (production is on the Supabase free plan) | PLT-002 | The recovery point is up to 24 hours, not minutes |
| A tested restore | PLT-042 | A backup that has never been restored is not a backup |
| Uptime checks and alerting | PLT-031 | Errors are noticed when staff complain |
Any scheduler beyond the communications dispatcher — pg_cron runs only alhuda-communications-dispatcher (every five minutes, 20260929110100), alhuda-whatsapp-templates-sync (WhatsApp templates from Meta, 03:00 IST, 20261003110000, AUD-024) and alhuda-leave-nightly (leave accruals and comp-off expiry, 00:30 IST, 20261003094200, LV-062); no other job, no scheduled edge function |
AIR §22, AUD-010 | Deadline alerts, hold expiry sweeps and drift checks derive on read instead of running nightly |
The first three are owner decisions (a paid plan, a staging project, monitoring), recorded in PROGRAM.md → Wave 1P.
5. Where to look
| Concern | Path |
|---|---|
| Running the tests | Testing |
| The pipeline | CI/CD, .github/workflows/ci.yml |
| Shipping | Deploy runbook, Deployment |
| Migrations | Database migrations |
| Backups | Backup & restore |
| A local copy of the whole product | Local preview |