Business continuity and disaster recovery plan (draft)
Sign-off: approved by [Kyle] on [date], second reader [Dan] on [date]; recorded in approvals.md. Review yearly.
Draft for AV-3 and ORG-24, with the restore test for AV-1, AV-2 and ORG-23. 27 Oct 2026. Owners: Kyle (plan), Dan (restore test). Review yearly, after the tabletop.
What matters most
Drivers must be able to file a DVIR every morning. The offline DVIR already keeps inspections on the tablet until the server confirms them, so a short outage of Supabase or Vercel loses no inspections: they wait in the outbox and upload when the service returns. Dashboards and reports can wait longer.
Targets (to confirm by measurement, then put in the contract)
| Target | How it is met | Status | |
|---|---|---|---|
| RPO (data we could lose) | 5 minutes with point-in-time recovery; up to 24 hours today | Supabase point-in-time recovery if bought; the daily backup today | PITR off (read 28 Oct): RPO up to 24 hours today; AV-2 decides |
| RTO (time to be back) | 4 hours | Restore into a new project, repoint Vercel | Never measured (AV-1) |
| Capture during an outage | No inspection lost | The offline outbox | Built; device test pending |
Do not put these numbers in a contract until the restore test below has met them.
Scenarios
| Scenario | What we do | Who |
|---|---|---|
| Supabase down (their outage) | Watch status.supabase.com; tell customers capture continues offline; nothing to restore | Kyle tells customers, Dan watches |
| Vercel down | Watch vercel-status.com; the installed app still opens from its cache for DVIR capture; dashboards are down | Same |
| Data deleted or corrupted (a bad migration, a mistaken reset) | Stop writes; restore the last daily backup before the damage (or a point before it, if PITR is bought) into a new project; check; repoint | Dan |
| Supabase project lost or account locked | Restore the latest backup into a new project in the Lucent Axis org; rotate keys; repoint Vercel | Dan |
| GitHub unavailable | Every laptop holds a full clone; Vercel keeps serving the last deployment | Dan |
| A person unavailable | Two owners on every account (OW-6); this plan and the vault let the other act | Kyle, Dan |
A lesson from the first copy (28 Oct)
The company database began as a copy of the demo database, and the copy did not keep the
database's permissions: 83 tables and 26 column grants came back with Supabase's defaults, so
signed-in users could write tables the migrations had made read-only. Row-level security still
held, but the grants were a second wall that had gone. Every restore or copy therefore ends
with scripts/db/verify_*.sql (they fail on exactly this) and a comparison of grants against
the source; a pg_dump taken for a restore keeps privileges (no --no-privileges).
Restore test record
A full timed restore once before the Type I, then once a year; a quarterly spot check in
between (the control calendar). One copy of this table per test, kept in
docs/compliance/evidence/<year>/ and logged in the control calendar (or in the compliance
platform, if one is chosen).
| Field | Value |
|---|---|
| Date and who ran it | |
| Source: project and the point in time restored to | |
| Target: new project name (never the live one) | |
| Started / data available / app working against it | |
| Measured RTO (start to app working) | |
| Measured RPO (restored point vs the last write before it) | |
Checks: row counts of incident, inspection, action, person match; a persona signs in; a photo opens |
|
| Problems found and what changed | |
| Target project deleted afterwards (date) |
Note for whoever runs it: never point load.sh --reset, demo_reset() or any reset at the live
database during a test. Restore into a NEW project.
On-call
Two people, Kyle and Dan, each the other's backup. Severity 2 and 3: a response within business hours. Out of hours: best effort, until a client contract says otherwise. Phone numbers and other contact details are kept in the vault, not in the repository. Severity is defined in the incident response plan.
Tabletop (ORG-24)
Once a year: one hour, one scenario from the table above, walked through end to end. First suggestion: a migration drops a column on a Tuesday morning and is noticed at noon. Record who attended, what was found and what changed.