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¶
- Provision paths on VM 118:
/opt/onset/src,/opt/onset/scripts,/opt/onset/.env - Rsync
clinic.theonset.appmain→/opt/onset/src(excl.node_modules,.next) - Install
bwsCLI on VM 118 - Render env:
render-onset-env-app.sh→/opt/onset/.env(51 keys) - 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)¶
- Open https://onset-app.tailc3bbdc.ts.net/login (Tailscale Serve HTTPS; required for PKCE)
- Sign in with Last1 ID (PKCE)
- Confirm redirect to auth.last1.id and return to
/auth/callback - Expected at Gate C: callback may error on practitioner lookup (
completeClinicAuth/ Gate D FK remap) — that is OK - Not expected: DNS/prod URL, hosted Supabase in env, or 500 on
/auth/callbackpage 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¶
- R940_MIGRATION_PLAYBOOK.md §4 (recreate not restart), §5b (tenant filters)
- TIER_4_ONSET_GATE_A_REPORT.md (OIDC client + LAN callback)
- ONSET_BWS_DB_VALIDATION.md