Skip to content

Tier 4 Onset T-3 Gate C Report — Clinic Portal LAN Deploy + Smoke

Status: STOP — Ryan manual OAuth loop verification
Date: 2026-08-29
Prior: TIER_4_ONSET_T3_GATE_B_REPORT.md (PR #7 merged f53a7e1)
Target: VM 118 onset-app @ http://192.168.1.101:3000


Executive summary

Gate C deploys merged PR #7 (Last1 ID auth) to R940 LAN. Env rendered from BWS on VM 118. Container recreated (not restarted). Automated smoke PASS. Full practitioner login still blocked until Gate D identity remap; OAuth loop soak is Ryan's next step.


Pre-deploy (Ryan)

Item Status
PR #7 merged to main Done (f53a7e1)
ONSET_APP_PROD_CLINIC_SESSION_SECRET in onset BWS Present
Playbook §5b service-role tenant filters Added (R940_MIGRATION_PLAYBOOK.md)
scripts/onset/deploy-onset-app.sh Added (RealOutcomes pattern, port 3000)

Deploy steps executed

  1. Provision paths on VM 118: /opt/onset/src, /opt/onset/scripts, /opt/onset/.env
  2. Rsync clinic.theonset.app main → /opt/onset/src (excl. node_modules, .next)
  3. Install bws CLI on VM 118
  4. Render env: render-onset-env-app.sh → /opt/onset/.env (51 keys)
  5. Deploy: deploy-onset-app.sh → docker rm -f onset-clinic-app && docker run ... (container recreate)

Build fix (deploy script)

First build baked empty NEXT_PUBLIC_SUPABASE_URL because .dockerignore excludes .env from Docker context. Minifier then reduced createAdminSupabase() to always return null → /api/clinic/access returned 500.

Fix: pass NEXT_PUBLIC_* as docker build --build-arg from rendered env (no .env in context required). Redeploy succeeded.


Env verification (at deploy)

Check Expected Result
DATABASE_URL host 192.168.1.102 (onset-db) PASS — not emxrmjysvxeboavhatry
NEXT_PUBLIC_SUPABASE_URL / runtime Supabase http://192.168.1.102:8000 PASS
CLINIC_SESSION_SECRET loaded (32+ byte) PASS
Container bind 192.168.1.101:3000 PASS (onset-clinic-app)

Automated smoke (LAN)

Base URL: http://192.168.1.101:3000

Check Expected Result
GET / 200 200
GET /api/clinic/access (no session) 401 401
OIDC discovery https://auth.last1.id/.well-known/openid-configuration issuer reachable PASS (https://auth.last1.id)
GET /auth/callback 200 (OAuth landing page) 200
POST /api/auth/last1/callback {} 400 (missing params) 400
GET /patients/new (no session) redirect to login 307 → /login (not an auth failure)

Expected known-break (do not fix in Gate C)

/patients/new bootstrap (authenticated path)

patients/new/page.tsx still calls supabase.auth.getUser() (GoTrue) for client-side profile bootstrap. After Last1 ID cutover there is no GoTrue session in the browser, so authenticated /patients/new will fail to load practitioner context even after a successful OAuth callback.

Phase Behavior
Gate C (now) OAuth loop + callback infra verified; practitioner lookup at token exchange may fail (Gate D)
After Gate D Session cookie works but /patients/new bootstrap still broken until page migrated off GoTrue client
Fix scope Post Gate D — not Gate C

Gate C smoke confirms this is the only documented product break among automated checks. No other unexpected 5xx on public/auth routes.


Ryan manual step (Gate C exit criteria)

  1. Open https://onset-app.tailc3bbdc.ts.net/login (Tailscale Serve HTTPS; required for PKCE)
  2. Sign in with Last1 ID (PKCE)
  3. Confirm redirect to auth.last1.id and return to /auth/callback
  4. Expected at Gate C: callback may error on practitioner lookup (completeClinicAuth / Gate D FK remap) — that is OK
  5. Not expected: DNS/prod URL, hosted Supabase in env, or 500 on /auth/callback page load

Full end-to-end portal access waits for Gate D.


Post-deploy fix (2026-08-29): callback "No suitable key or wrong key type"

Ryan hit /auth/callback with a valid OAuth code + state, but the UI showed "No suitable key or wrong key type".

Layer Finding
Last1 ID OAuth Not the failure. Token exchange + RS256 id_token verification succeeded.
Supabase PostgREST Root cause. SUPABASE_SERVICE_ROLE_KEY in clinic env was still the .env.example demo JWT (iss: supabase-demo), signed with a different secret than VM 119 stack JWT_SECRET. PostgREST returned PGRST301 with that exact message.
Fix applied Regenerated ANON_KEY + SERVICE_ROLE_KEY from stack JWT_SECRET, docker compose up -d on onset-db, pushed keys to BWS, re-rendered /opt/onset/.env, recreated onset-clinic-app.
After fix Callback reaches completeClinicAuth; expected Gate C/D message is "No clinic account found…" until identity remap.

setup-supabase-stack.sh now re-signs API keys whenever it rotates JWT_SECRET (prevents recurrence on fresh stacks).


STOP

Gate C automated deploy + smoke complete. Awaiting Ryan OAuth loop verification at LAN URL before Gate D.


References