Skip to content

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 in PERMISSIONS.md and inserted by a migration; every documented permission is referenced and seeded; every documented role exists in the RoleType enum and gets a Role row from a migration; and SUPER_ADMIN is the only role seeded every permission.
  • src/test/api.permission-coverage.test.ts — every POST / PUT / PATCH / DELETE branch in src/lib/api.ts has a requirePermission() 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, psql DDL 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 main is 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