Tier 4 T-2 Report — Onset VM provision + Supabase stack + Gate B + BWS¶
Date: 2026-08-29
Status: T-2 COMPLETE (infra + BWS + P0 smoke). Gate B + PHI: see TIER_4_ONSET_GATE_B_REPORT.md. Await Ryan authorization for T-3.
Parent: TIER_4_ONSET_T-1_REPORT.md
Execution plan: ONSET_MIGRATION_PLAN.md
Executive summary¶
T-2 provisions VM 118/119 on pve-r940, deploys the self-hosted Supabase stack on VM 119 with 24 Edge Functions, populates ONSET_APP_PROD_* in BWS, and passes P0 Edge Function smoke after injecting function secrets into the edge-runtime container.
Hard gate not cleared: pg_dump from hosted emxrmjysvxeboavhatry could not run. Neither ONSET_HOSTED_DATABASE_URL / HOSTED_DATABASE_URL nor SUPABASE_ACCESS_TOKEN exists in BWS. PHI decrypt smoke was not executed (depends on Gate B restore).
Ryan action required: Add hosted session-pooler URL to onset BWS as ONSET_HOSTED_DATABASE_URL, or add SUPABASE_ACCESS_TOKEN to last1-platform BWS, then re-run Gate B + PHI smoke before authorizing T-3.
Phase 1 — VM provision ✅¶
| VMID | Hostname | LAN IP | Status | Notes |
|---|---|---|---|---|
| 119 | onset-db |
192.168.1.102/24 | Running, guest agent up | ZFS data disk mounted at /var/lib/supabase-data (98 GB, 1% used) |
| 118 | onset-app |
192.168.1.101/24 | Running, guest agent up | App VM idle (no Docker/clinic yet; expected T-3) |
IP deviation: T-1 planned .100 for supabase. 192.168.1.100 is occupied (responds to ping; SSH refused). Used .102 instead. Scripts default ONSET_DB_LAN_IP=192.168.1.102.
Post-provision fixes applied:
| Issue | Resolution |
|---|---|
Cloud-init did not create ryan user |
Created manually via qm guest exec |
| Cloud-init SSH keys not applied | Injected from /var/lib/vz/snippets/last1id-sshkeys |
fail2ban banned agent-01 (192.168.1.30) |
Unbanned on both VMs |
Phase 2 — Supabase stack (VM 119) ✅¶
| Check | Result |
|---|---|
| Docker + data root | /var/lib/supabase-data/docker |
| Compose path | /opt/supabase/docker |
| Realtime | Disabled (Onset pattern) |
| Kong/Envoy | http://192.168.1.102:8000 |
| All compose services | healthy (auth, db, rest, storage, functions, pooler/supavisor, studio, meta, imgproxy, api-gw) |
| Generated stack secrets | /root/onset-secrets/supabase.env |
Edge Functions synced: 24 production function directories under volumes/functions/ (plus _shared, edge-runtime main router, and template hello bootstrap dir).
Hosted parity (source of truth, pre-restore):
| Metric | Hosted emxrmjysvxeboavhatry |
|---|---|
auth.users |
8 |
practitioner_patients |
5 (all 5 patient_email rows enc:v1:) |
product_events |
244 |
cron.job |
3 |
pg_policies (public) |
76 |
storage.objects |
20 |
R940 VM 119 DB is still empty seed schema until Gate B restore.
Phase 3 — Gate B rehearsal ❌ BLOCKED¶
| Step | Result |
|---|---|
Load ONSET_HOSTED_DATABASE_URL / HOSTED_DATABASE_URL from onset BWS |
Not found |
Load SUPABASE_ACCESS_TOKEN from last1-platform BWS |
Not found |
supabase db dump --project-ref emxrmjysvxeboavhatry on agent-01 |
Not authenticated |
pg_dump / pg_restore |
Not run |
Blocker log: /var/lib/supabase-data/gate-b/gate-b-rehearsal.log on VM 119:
BLOCKER: set HOSTED_DATABASE_URL, ONSET_HOSTED_DATABASE_URL in BWS, or SUPABASE_ACCESS_TOKEN for supabase db dump
Expected pooler format (from onset-ios ENVIRONMENTS.md):
postgresql://postgres.emxrmjysvxeboavhatry:<PROD_DB_PW>@aws-1-us-east-1.pooler.supabase.com:5432/postgres?sslmode=require
Railway clinic service does not expose Postgres password (only SUPABASE_SERVICE_ROLE_KEY, anon key, public URL).
Phase 4 — PHI decrypt smoke ❌ NOT RUN (HARD GATE)¶
| Step | Result |
|---|---|
PATIENT_PII_ENCRYPTION_KEY in BWS |
✅ ONSET_APP_PROD_PATIENT_PII_ENCRYPTION_KEY synced from Railway |
Restore hosted practitioner_patients to R940 |
❌ Blocked on Gate B |
gate-b-phi-decrypt-smoke.sh |
Not executed |
Per T-1 / Ryan Q6: Do not proceed to T-3 until all 5/5 rows decrypt cleanly on R940 after restore.
Phase 5 — BWS ONSET_APP_PROD_* ✅¶
Project: onset (1c643206-048d-42ea-980c-b48001216f05)
| Action | Result |
|---|---|
| Railway clinic → BWS | 30 keys updated (sync-app-bws-from-railway.py) |
| R940 stack keys → BWS | 8 created, 3 updated (push-db-stack-bws.py) |
| Platform assertion copy | ONSET_APP_PROD_ONSET_IDENTITY_ASSERTION_SECRET from last1-platform |
Total ONSET_APP_PROD_* |
39 |
Legacy onset keys (SUPABASE_URL, onset_prod_*, etc.) |
Retained |
Not populated (Gate B dependency): ONSET_HOSTED_DATABASE_URL
Scripts:
scripts/onset/sync-app-bws-from-railway.pyscripts/onset/push-db-stack-bws.py
Phase 6 — P0 Edge Function smoke ✅¶
Executed from agent-01 against http://192.168.1.102:8000 after injecting function secrets into edge-runtime compose environment:
| Function | HTTP | Verdict | Notes |
|---|---|---|---|
last1-connect-assertion |
401 | PASS | Reachable; expects authenticated user JWT + linked patient_last1_identities row (none until Gate B) |
practitioner-notifications |
200 | PASS | {"ok":true} with eventType: weekly_summary + valid x-notify-secret |
T-2 workaround: Function secrets (ONSET_IDENTITY_ASSERTION_SECRET, NOTIFY_FUNCTION_SECRET, PATIENT_PII_ENCRYPTION_KEY, SENDGRID_API_KEY) were merged into VM .env and docker-compose.yml functions service (not yet in setup-supabase-stack.sh; follow-up for T-3).
Smoke script gaps noted:
- VM 119 has no
bwsCLI; run smoke from agent-01 or install bws on VM. - Original
smoke-edge-functions.shpayload{"action":"health"}is invalid; use a realeventType.
Constraints honored¶
| Constraint | Status |
|---|---|
| No DNS changes | OK |
| No hosted teardown | OK |
| Do not touch admin / lastvet-edge / last1id-edge / realoutcomes-edge | OK |
| Credential isolation (onset BWS + Railway read-only) | OK |
| PHI hard gate | Not cleared — Gate B blocked |
Anomalies / follow-ups¶
- Supabase LAN IP
.102not.100— confirm permanent plan with Ryan. - Gate B credential gap — add
ONSET_HOSTED_DATABASE_URLto onset BWS orSUPABASE_ACCESS_TOKENto last1-platform BWS. - Cloud-init user/SSH — provision scripts should match RealOutcomes hardening (ryan user + SSH keys).
- fail2ban — whitelist agent-01 (
192.168.1.30) on new VMs. - EF secret injection — automate in
setup-supabase-stack.shor a dedicatedinject-onset-function-secrets.sh. - Auth health curl —
/auth/v1/healthreturns 401 withoutapikey; stack is healthy via compose healthchecks. - Function dir count — 26 top-level dirs on disk = 24 prod +
_shared+main+hello(not 24 duplicates).
STOP — Next gate¶
| Phase | Status | Ryan action |
|---|---|---|
| T-0 Audit | Done | — |
| T-1 Planning | Done | — |
| T-2 VMs + Supabase + BWS + P0 smoke | Partial | Provide Gate B credentials; re-run Gate B + PHI smoke |
| T-3 Clinic auth unification + app deploy | Blocked | Authorize after PHI smoke PASS |
| T-4 DNS cutover | Blocked | — |
| T-5 Teardown | Blocked | — |
Ryan unblock checklist (before T-3)¶
- Add to onset BWS:
ONSET_HOSTED_DATABASE_URL= hosted session pooler URL foremxrmjysvxeboavhatry(PG 17, port 5432 pooler,sslmode=require).
Or addSUPABASE_ACCESS_TOKEN(sbp_…) tolast1-platformBWS. - On VM 119:
bash /tmp/gate-b-rehearsal.shthenbash /tmp/gate-b-phi-decrypt-smoke.sh. - STOP if PHI smoke fails — report decrypt counts before any T-3 work.
- Confirm
.102vs.100IP plan.
Scripts index¶
| Script | Purpose |
|---|---|
provision-vm-app.sh |
VM 118 on pve-r940 |
provision-vm-supabase.sh |
VM 119 + ZFS data disk |
setup-supabase-stack.sh |
Supabase Docker + 24 EF sync |
sync-onset-functions.sh |
Rsync EF sources from agent mirrors |
gate-b-rehearsal.sh |
Hosted dump → restore → verify |
gate-b-phi-decrypt-smoke.sh |
HARD GATE PHI decrypt on 5 rows |
sync-app-bws-from-railway.py |
Railway clinic env → BWS |
push-db-stack-bws.py |
VM stack keys → BWS |
smoke-edge-functions.sh |
P0 EF smoke |