Tier 4 Onset T-3 Gate E Report — Partial GoTrue Retirement (Clinic Off, iOS On)¶
Status: PASS — Ryan signed 2026-08-30 (Gate F closeout)
Date: 2026-08-29 (E-3 device pass); closeout 2026-08-30
Prior: TIER_4_ONSET_T3_GATE_D_REPORT.md (PASS — curryrg@gmail.com dashboard login)
Contrast: SPRINT-2_GATE_E_REPORT.md (RealOutcomes full GoTrue stop — not applicable to Onset)
Executive summary¶
Gate E for Onset is verification + documentation, not container teardown. The clinic portal authenticates exclusively via auth.last1.id and the encrypted clinic_last1_session cookie (Gates B–D). supabase-auth remains running on VM 119 for iOS/patient GoTrue until App Store release (Q4 lock). Kong /auth/v1/* routes unchanged (14 auth-v1 services in kong.yml).
E-3 outcome: Real Sign in with Apple on TestFlight against R940 GoTrue (onset-db.tailc3bbdc.ts.net over Tailscale, key TU6S85XABQ) — not just an endpoint probe. This proves the iOS-on-R940 auth path and de-risks T-5.
Clinic GoTrue login path: removed (no browser signInWith* / GoTrue session cookies).
Remaining clinic GoTrue touchpoints: /patients/new client bootstrap (supabase.auth.getUser) — deferred; plus server-side auth.admin.* on dormant signup/team paths.
1. What Gate E did (Onset vs RealOutcomes)¶
| Action | RealOutcomes Gate E | Onset Gate E |
|---|---|---|
Stop supabase-auth |
Yes | No — explicitly forbidden |
Remove Kong/Envoy /auth/v1/* |
Yes | No — iOS depends on routes |
| Confirm clinic uses Last1 ID only | N/A (only product) | Yes — audited deployed source on VM 118 |
| Confirm iOS GoTrue path alive | N/A | Yes — health + token probe |
| Document dual-auth window | N/A | Yes — §4 |
No infrastructure changes executed. Stack state unchanged from post–Gate D.
2. Clinic portal GoTrue dependency audit¶
Source: /opt/onset/src on VM 118 (onset-clinic-app), merged PR #7 / Gate C deploy.
2.1 Clinic login/session (Last1 ID — no GoTrue)¶
| Component | Auth mechanism |
|---|---|
| Middleware | clinic_last1_session cookie only (src/middleware.ts) |
| Login UI | Last1 ID OIDC redirect (src/app/login/page.tsx) |
| Callback | /api/auth/last1/callback → completeClinicAuth → session cookie |
| Portal SSR/API identity | getClinicSessionFromCookies() / service role |
| MFA UI | Stub card — "managed by Last1 ID" (owner-mfa-card.tsx) |
2.2 Browser-side GoTrue calls (clinic portal)¶
| File | Call | Gate E disposition |
|---|---|---|
(portal)/patients/new/page.tsx |
supabase.auth.getUser() |
GAP — deferred (only active clinic browser GoTrue call) |
lib/clinic-mfa.ts |
supabase.auth.mfa.* |
Dead code — zero imports in deployed tree |
2.3 Server-side GoTrue admin API (not clinic login)¶
These use service-role auth.admin.* against local GoTrue/Postgres — not browser practitioner login. Signup/team flows were stubbed/deferred in Gate B; they do not participate in Ryan's Last1 clinic login path.
| File | API | Notes |
|---|---|---|
api/clinic/register/route.ts |
auth.admin.createUser |
New owner signup deferred |
api/clinic/team/route.ts |
auth.admin.inviteUserById, getUserById |
Team invite deferred |
lib/clinic-data-lifecycle.ts |
auth.admin.getUserById, updateUserById |
Account lifecycle admin |
2.4 Intentional non-clinic GoTrue JWT path¶
| File | Call | Consumer |
|---|---|---|
api/protocols/v2/[patientId]/latest/route.ts |
supabase.auth.getUser() |
iOS patient Bearer JWT — must stay on GoTrue until T-5 |
2.5 Secondary anomaly (not supabase.auth, but post–Gate B risk)¶
| File | Pattern | Risk |
|---|---|---|
(portal)/patients/[id]/notes-editor.tsx |
Browser anon createClient() → direct .from() |
No GoTrue session; RLS may block writes |
(portal)/templates/template-actions.tsx |
Same | Same |
Gate E does not fix these. Track under portal SSR migration backlog (service-role API routes), separate from GoTrue retirement.
3. iOS GoTrue path — confirmed intact¶
VM 119 (192.168.1.102):
| Check | Result |
|---|---|
supabase-auth container |
running / healthy |
Kong auth-v1 routes in kong.yml |
14 services present |
GET /auth/v1/health (no apikey) |
401 — Kong gateway (route exists) |
GET /auth/v1/health (+ apikey) |
200 — GoTrue v2.189.0 |
POST /auth/v1/token?grant_type=password for hello@unicornryda.com |
400 invalid_credentials (probe password — proves token endpoint live) |
hello@unicornryda.com in auth.users |
present — 26b42005-344b-4f4b-9db7-58c19c3ca037 |
Note: RealOutcomes post-retirement showed 401 on bare /auth/v1/health because routes were removed. Onset 401 without apikey + 200 with apikey means GoTrue is UP, not retired.
4. Cross-stack auth state (this window)¶
┌─────────────────────────┬──────────────────────────┬─────────────────────────┐
│ Surface │ Identity │ Auth backend │
├─────────────────────────┼──────────────────────────┼─────────────────────────┤
│ Clinic portal (VM 118) │ curryrg@gmail.com → │ auth.last1.id (OIDC) │
│ │ id_token.sub = practitioner.user_id │
│ │ session: clinic_last1_session cookie │
├─────────────────────────┼──────────────────────────┼─────────────────────────┤
│ iOS / patient app │ hello@unicornryda.com + │ **R940 GoTrue proven** │
│ (Release / App Store) │ demo patients │ Hosted prod until T-4/ │
│ │ │ T-5 cutover │
│ │ │ (emxrmjysvxeboavhatry) │
├─────────────────────────┼──────────────────────────┼─────────────────────────┤
│ iOS Onset Staging │ TestFlight / Debug │ **Hosted staging** │
│ (xcconfig default) │ │ lzpyfohgfdohrtlamzek │
│ │ │ (reverted post–Gate E) │
├─────────────────────────┼──────────────────────────┼─────────────────────────┤
│ Edge Functions │ Service role + optional │ VM 119 stack │
│ │ user JWT (GoTrue) │ │
├─────────────────────────┼──────────────────────────┼─────────────────────────┤
│ `/api/protocols/v2/*` │ Patient GoTrue JWT │ Local GoTrue (iOS path) │
│ patient branch │ │ │
└─────────────────────────┴──────────────────────────┴─────────────────────────┘
Gate E proof path (temporary, now reverted): Onset Staging TestFlight was pointed at R940 (onset-db.tailc3bbdc.ts.net) for E-3 device verification only. Staging.xcconfig reverted to hosted on closeout so staging builds again match docs/expectation.
Coexistence rule: Clinic Last1 sessions never call local GoTrue for login. iOS never calls auth.last1.id for patient auth in this window. Both stacks share one Postgres (auth.users + practitioner FKs).
T-5 de-risk: E-3 proved end-to-end iOS → R940 GoTrue → Apple id_token exchange. App Store cutover is now an xcconfig/host swap, not an unknown auth integration.
Full GoTrue retirement: T-5 only, after iOS App Store ships on R940 (Q4 lock). See ONSET_MIGRATION_PLAN.md.
5. Automated verification (agent)¶
| Test | Result |
|---|---|
supabase-auth running |
PASS |
/auth/v1/health + apikey → 200 |
PASS |
hello@unicornryda.com token endpoint |
PASS (400 invalid_credentials) |
Edge: practitioner-notifications |
PASS (200) |
Edge: last1-connect-assertion |
PASS (401 reachable — expected without valid assertion) |
GET https://onset-app.tailc3bbdc.ts.net/login |
200 |
GET https://auth.last1.id/.well-known/openid-configuration |
200 |
Script: scripts/onset/gate-e-verify-partial-gotrue.sh (run on VM 119)
6. Manual verification (Ryan)¶
| # | Test | Result |
|---|---|---|
| E-1 | Clinic Last1 login (curryrg@gmail.com) |
PASS (Gate D repeat) |
| E-2 | GoTrue health + apikey → 200 | PASS |
| E-3 | iOS Sign in with Apple as hello@unicornryda.com |
PASS — real device auth on R940 GoTrue (see §6.1) |
| E-4 | supabase-auth still Up (healthy) on VM 119 |
PASS |
6.1 E-3 — real Apple Sign-In pass (2026-08-29)¶
Ryan confirmed Onset Staging TestFlight Sign in with Apple as hello@unicornryda.com.
| Item | Value |
|---|---|
| Build | Onset Staging (Release-Staging), 1.2.22 build 7 |
| Backend (proof window) | R940 onset-db.tailc3bbdc.ts.net over Tailscale |
| Apple key | TU6S85XABQ (Apple Auth Key TU6S85XABQ.p8) in BWS + R940 GoTrue |
| GoTrue settings | apple: true, anonymous_users: true |
| Identity | hello@unicornryda.com — apple row restored (26b42005-344b-4f4b-9db7-58c19c3ca037) |
Stronger than endpoint probe: Full iOS → Apple → GoTrue id_token → session path verified on device.
6.2 E-3 fix history (blocker → resolved)¶
Ryan initially reported Apple Sign-In failure during E-3.
| Check | R940 onset-db (192.168.1.102:8000) |
Hosted prod (emxrmjysvxeboavhatry) |
|---|---|---|
/auth/v1/settings → apple |
was false → fixed true |
true |
/auth/v1/settings → anonymous_users |
was false → fixed true |
true |
Root cause: R940 stack provisioned in T-2 without Apple OAuth secrets (hosted-only at the time). Not a Gate E regression.
Fix applied:
| Step | Status |
|---|---|
BWS ONSET_DB_PROD_APPLE_* (team, key id, services id, PEM) |
Created — key TU6S85XABQ via push-onset-apple-bws.py |
VM 119 configure-onset-gotrue-apple.sh |
PASS |
hello@unicornryda.com auth.identities repair |
Applied — gate-e-repair-hello-apple-identity.sql |
| Device re-test | PASS — Ryan sign-off 2026-08-29 |
Prior doc referenced key RV26UG9967 from hosted config; Gate E rotation uses TU6S85XABQ (see §9).
7. Gate E closeout — temporary state reverted (2026-08-30)¶
Gate E required a temporary R940 pointer in Staging.xcconfig for E-3 device proof. Leaving it would make staging silently authenticate against R940 while docs say hosted — the drift pattern that caused confusion twice this week. Reverted before Ryan formal sign-off.
| Item | Action | Evidence |
|---|---|---|
Staging.xcconfig → hosted |
Reverted | onset-ios 9825d3f — SUPABASE_HOST = lzpyfohgfdohrtlamzek.supabase.co, publishable anon key restored |
GitHub secret APPLE_PRIVATE_KEY |
Removed | Temp secret added 2026-08-30 during aborted hosted push attempt; deleted on closeout |
| R940 GoTrue Apple config | Retained | BWS + VM 119 — required for T-5; not a staging xcconfig concern |
Current expectation: Onset Staging / Debug builds → hosted staging (lzpyfohgfdohrtlamzek). App Store Release → hosted prod (emxrmjysvxeboavhatry) until T-4/T-5 cutover. R940 auth path is proven, not default.
8. Gate E disposition summary¶
| Question | Answer |
|---|---|
| Clinic GoTrue login removed? | Yes — Last1 ID + clinic_last1_session only |
| Clinic GoTrue dependency fully zero? | No — /patients/new getUser() gap deferred |
| iOS GoTrue path intact? | Yes — health/token UP; Apple provider enabled on R940 |
| E-3 Apple Sign-In on R940? | Yes — device pass (not endpoint-only) |
| iOS-on-R940 auth proven? | Yes — de-risks T-5 App Store cutover |
supabase-auth stopped? |
No — by design |
Kong /auth/v1/* removed? |
No — by design |
| Temporary xcconfig / GH secret drift? | Cleared — §7 |
| Anomalies | notes-editor/template-actions anon RLS risk; dead clinic-mfa.ts; deferred register/team admin API |
9. Apple key rotation vs GitGuardian incidents¶
These are separate secret classes. Do not conflate Apple Sign-In keys with Supabase JWT gg incidents.
| Secret | Key / incident | Gate E action | GG status |
|---|---|---|---|
| Apple Sign-In (.p8) | TU6S85XABQ (new) replaces prior RV26UG9967 |
Loaded into BWS + R940 GoTrue for E-3 | Not a GG incident — Apple keys are not in onset-ios repo |
| Supabase JWT (prod) | GG 29340466 — Production.xcconfig |
Unchanged — still hosted prod anon key | TRIGGERED — RESOLVE at T-5 when R940 prod keys ship in App Store |
| Supabase JWT (staging) | GG 34103515 — Staging.xcconfig |
Unchanged — reverted to hosted staging publishable key | TRIGGERED — RESOLVE when hosted staging deleted at T-5 |
TU6S85XABQ does not close GG 29340466 or 34103515. Those track Supabase anon JWTs in xcconfig, not Apple .p8 keys.
Apple Developer action: Confirm RV26UG9967 is revoked in Apple Developer → Keys if not already done when TU6S85XABQ was issued. R940 GoTrue and BWS now use TU6S85XABQ only. Hosted prod Apple config was not updated during Gate E (R940 was the test target); update hosted when prod Release cutover is planned.
10. Unblocked after Gate E pass¶
| Gate | Work |
|---|---|
| F | T-3 closeout report — clinic auth unification complete for portal; iOS GoTrue carry documented; T-5 cutover plan inherits proven R940 iOS auth path |
END Gate E report