Skip to content

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.py
  • scripts/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 bws CLI; run smoke from agent-01 or install bws on VM.
  • Original smoke-edge-functions.sh payload {"action":"health"} is invalid; use a real eventType.

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

  1. Supabase LAN IP .102 not .100 — confirm permanent plan with Ryan.
  2. Gate B credential gap — add ONSET_HOSTED_DATABASE_URL to onset BWS or SUPABASE_ACCESS_TOKEN to last1-platform BWS.
  3. Cloud-init user/SSH — provision scripts should match RealOutcomes hardening (ryan user + SSH keys).
  4. fail2ban — whitelist agent-01 (192.168.1.30) on new VMs.
  5. EF secret injection — automate in setup-supabase-stack.sh or a dedicated inject-onset-function-secrets.sh.
  6. Auth health curl — /auth/v1/health returns 401 without apikey; stack is healthy via compose healthchecks.
  7. 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)

  1. Add to onset BWS: ONSET_HOSTED_DATABASE_URL = hosted session pooler URL for emxrmjysvxeboavhatry (PG 17, port 5432 pooler, sslmode=require).
    Or add SUPABASE_ACCESS_TOKEN (sbp_…) to last1-platform BWS.
  2. On VM 119: bash /tmp/gate-b-rehearsal.sh then bash /tmp/gate-b-phi-decrypt-smoke.sh.
  3. STOP if PHI smoke fails — report decrypt counts before any T-3 work.
  4. Confirm .102 vs .100 IP 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