LAST1.ID Week 6A Report — Pre-Cutover Audit¶
Date: 2026-08-25
Scope: LastVet repo audit (Finding 1 + hardcoded URLs) and Postgres failure-mode probe (Finding 2). No DNS cutover. Railway unchanged.
Status: W6A complete — STOP for Ryan approval before W6B (DNS cutover).
Executive summary¶
| Audit | Result | Cutover impact |
|---|---|---|
Finding 1 — id_token.sub vs auth.users.id |
No LastVet code fix needed. LastVet already treats id_token.sub as canonical identity key. |
Proceed |
| Hardcoded / default last1.id URLs | No Railway hostnames in LastVet client code. Defaults point at https://last1.id, which becomes marketing Pages after cutover. Coordinated env + sovereign issuer update required at W6B. |
Action required at W6B (see §3) |
| Finding 2 — Postgres failure probe | GoTrue does not crash when Postgres stops. /health stays 200; signup returns 500 with connection-refused error; full recovery after Postgres restart. |
Operational awareness documented |
Recommendation: Approve W6B cutover conditional on executing the coordinated issuer/env checklist in §3 in the same window as DNS (not before Ryan sign-off).
1. Finding 1 — id_token.sub scoping audit¶
Question¶
Does any LastVet code expect id_token.sub to equal auth.users.id (Supabase internal UUID)?
Method¶
Repo-wide search in last.vet-api, last.vet-web, last.vet-provider, last.vet-ios for:
- id_token.sub, verified.sub, auth.users.id, sub-to-user-id comparisons
- Integration docs and migration comments
Findings¶
| Repo | Uses sub? |
Expects sub == auth.users.id? |
Notes |
|---|---|---|---|
| last.vet-api | Yes | No | verified.sub → providers.last1_identity_id / veterans.last1_identity_id in auth-last1.ts, oauth-onset.ts. Docs: "id_token.sub is the canonical key" (LAST1_ID_INTEGRATION.md). |
| last.vet-provider | Indirect | No | Portal forwards OAuth code to API; no local sub parsing. |
| last.vet-ios | No | No | OAuth code handed to API; server verifies id_token. iOS does not compare sub to user id. |
| last.vet-web | N/A | N/A | No Last1 OIDC integration. |
Week 5 confirmed partner-scoped id_token.sub ≠ auth.users.id (pairwise OIDC subject). LastVet binding column last1_identity_id is designed for the OAuth sub claim, not Supabase internal ids.
Conclusion — PROCEED¶
No LastVet code change required for Finding 1. Existing linked providers continue to bind via the same partner-scoped sub UUID issued before and after cutover (same signing key, same identity rows).
2. Hardcoded URL / issuer audit¶
Question¶
Are there hardcoded Railway last1.id URLs, or hostname defaults that break after DNS split (last1.id → Pages, auth.last1.id → R940)?
Method¶
Grep across four LastVet repos for last1.id, auth.last1.id, railway.app, LAST1_ID_*, issuer env vars, and build scripts.
Findings¶
| Repo | Location | Current value | Railway-specific? | W6B action |
|---|---|---|---|---|
| last.vet-api | src/config/environment.ts |
Default LAST1_ID_API_BASE=https://last1.id |
No (hostname) | Set AWS ECS env LAST1_ID_API_BASE=https://auth.last1.id before/at cutover. iss validation requires match. |
| last.vet-api | .env.example |
https://last1.id |
No | Doc only |
| last.vet-api | Smoke scripts | *.up.railway.app for LastVet API staging, not last1.id |
Staging only | No cutover change |
| last.vet-provider | src/lib/last1Auth.ts |
Default NEXT_PUBLIC_LAST1_ID_ISSUER=https://last1.id |
No | Rebuild Pages deploy with NEXT_PUBLIC_LAST1_ID_ISSUER=https://auth.last1.id |
| last.vet-provider | scripts/cf-build-prod.sh |
Hardcoded NEXT_PUBLIC_LAST1_ID_ISSUER="https://last1.id" |
No | Update script + rebuild at cutover |
| last.vet-provider | scripts/cf-build-staging.sh |
Hardcoded staging issuer https://last1.id |
No | Update when staging cutover desired |
| last.vet-ios | LastVet/App/Configuration.swift |
Hardcoded last1OAuthIssuer: https://last1.id |
No | App rebuild with https://auth.last1.id (no runtime env today) |
| last.vet-web | — | No integration | N/A | None |
No *.railway.app or Supabase project URLs appear in LastVet OAuth client configuration. Traffic today reaches Railway only because last1.id DNS points at Railway, not because apps embed Railway hostnames.
Sovereign stack issuer gap (critical for W6B)¶
| Component | Current | Required at cutover |
|---|---|---|
GoTrue (GOTRUE_SITE_URL, API_EXTERNAL_URL) |
https://auth.last1.id |
Already correct |
Partner OIDC discovery (http://127.0.0.1:5000/.well-known/openid-configuration) |
issuer=https://last1.id, JWKS at https://last1.id/.well-known/jwks.json |
issuer=https://auth.last1.id via LAST1_ID_ISSUER_URL / PUBLIC_BASE_URL on sovereign app + BWS refresh |
LastVet API LAST1_ID_API_BASE |
Default / prod likely https://last1.id |
https://auth.last1.id |
| Provider portal build | https://last1.id |
https://auth.last1.id |
| iOS | https://last1.id |
https://auth.last1.id |
After cutover, last1.id serves Cloudflare Pages marketing only. OAuth authorize, token, JWKS, and GoTrue paths must live on auth.last1.id. Issuer claim, JWKS URL, and LastVet env must align in the same window.
Partner redirect URIs unchanged (https://provider.last.vet/auth/callback, https://last.vet/auth/last1/callback, etc.).
3. Proposed W6B cutover checklist (Ryan approval required)¶
Execute in one coordinated window after DNS record created:
Ryan¶
- Create Cloudflare DNS for
auth.last1.id→ Cloudflare Tunnel (lastvet-edge/lastvet-r940, route prepared Week 1). - Confirm
last1.id→ Cloudflare Pages (marketing) per migration plan. - Browser test:
https://auth.last1.id/auth/v1/healthfrom Mac (not hosts file). - Optional: lower TTL 24h before cutover (migration plan §6.1).
Cursor / infra¶
- Activate tunnel route for
auth.last1.id. - Update sovereign BWS + app env:
LAST1_ID_ISSUER_URL,PUBLIC_BASE_URL→https://auth.last1.id; redeploy last1.id app on VM 114; verify OIDC discoveryissuerand JWKS onauth.last1.id. - Set AWS ECS
LAST1_ID_API_BASE=https://auth.last1.idon productionlast.vet-api. - Rebuild provider.last.vet with
NEXT_PUBLIC_LAST1_ID_ISSUER=https://auth.last1.id(updatecf-build-prod.sh). - Queue iOS rebuild with
Configuration.last1OAuthIssuer→https://auth.last1.id(TestFlight before App Store if needed). - Verify DNS:
dig auth.last1.id, JWKScurl https://auth.last1.id/.well-known/jwks.json→ 200. - Run LastVet synthetic OIDC test once during soak.
Do NOT (constraints)¶
- Do not touch Railway/Supabase during soak.
- Do not disable old GCP client (
645620802422-…) until Week 7. - Do not delete Railway after soak (Week 7 explicit approval).
- Rollback = revert DNS + restore prior env vars.
4. Finding 2 — Postgres failure-mode probe¶
Method¶
Week 5 host iptables probe was inconclusive (Docker egress bypass). W6A used real Postgres stop on sovereign-db-last1id:
sudo systemctl stop postgresql@17-main # on 192.168.1.95
# observe GoTrue on 192.168.1.96
sudo systemctl start postgresql@17-main
Script: scripts/last1-id/week6a-postgres-probe.sh
Executed: 2026-08-25T18:23–18:24Z
Observed behavior¶
| Phase | /auth/v1/health |
POST /auth/v1/signup |
GoTrue container | Notes |
|---|---|---|---|---|
| Baseline | 200 | 200 | healthy / running | Normal |
| Postgres stopped (~4s) | 200 | 500 | healthy / running | Did not crash |
| Postgres restored (~8s) | 200 | 200 | healthy / running | Full recovery |
GoTrue log during failure (representative):
error="unable to find identity by email for duplicates: ... failed to connect to
`host=192.168.1.95 user=last1id_app database=last1id_prod`: dial error
(dial tcp 192.168.1.95:5432: connect: connection refused)"
status=500 path=/signup
Operational interpretation¶
| Behavior | Detail |
|---|---|
| Crash? | No. Container stayed running / Docker health healthy. |
| Graceful degradation? | Partial. Liveness /health still returns 200 (likely no deep DB check). Write paths fail with HTTP 500 and explicit DB connection errors in logs. |
| Recovery? | Automatic once Postgres restarted; no GoTrue container restart required. |
| Alerting gap | Health endpoint alone is insufficient for DB outage detection. Rely on Postgres exporter (192.168.1.95:9187), signup/error-rate probes, or Kong 5xx during soak. |
Note: Postgres on sovereign-db-last1id is systemd postgresql@17-main, not a Docker container. Week 5 "pause container" wording maps to service stop on VM 113.
5. Gate decision¶
| Sub-task | Status |
|---|---|
| W6A Pre-cutover audit | Complete |
| W6B DNS cutover + tunnel | BLOCKED — awaiting Ryan approval of this report |
| W6C 24–48h soak | Not started |
| W6D Soak clean declaration | Not started |
Ryan actions now¶
- Review Finding 1 conclusion (no code fix).
- Review §3 coordinated cutover checklist (issuer alignment is the main risk, not Railway URL strings).
- Review Postgres failure behavior (§4).
- Approve or adjust W6B plan, then create Cloudflare DNS for
auth.last1.id.
References¶
- LAST1_ID_WEEK5_REPORT.md
- LAST1_ID_MIGRATION_PLAN.md § Week 6
- LAST1_ID_WEEK1_REPORT.md (tunnel route prepared, DNS not live)
last.vet-api/docs/api/LAST1_ID_INTEGRATION.md(identity contract)- Scripts:
week6a-postgres-probe.sh,week5-failure-modes.sh