Skip to content

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

  1. Create Cloudflare DNS for auth.last1.id → Cloudflare Tunnel (lastvet-edge / lastvet-r940, route prepared Week 1).
  2. Confirm last1.id → Cloudflare Pages (marketing) per migration plan.
  3. Browser test: https://auth.last1.id/auth/v1/health from Mac (not hosts file).
  4. Optional: lower TTL 24h before cutover (migration plan §6.1).

Cursor / infra

  1. Activate tunnel route for auth.last1.id.
  2. 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 discovery issuer and JWKS on auth.last1.id.
  3. Set AWS ECS LAST1_ID_API_BASE=https://auth.last1.id on production last.vet-api.
  4. Rebuild provider.last.vet with NEXT_PUBLIC_LAST1_ID_ISSUER=https://auth.last1.id (update cf-build-prod.sh).
  5. Queue iOS rebuild with Configuration.last1OAuthIssuer → https://auth.last1.id (TestFlight before App Store if needed).
  6. Verify DNS: dig auth.last1.id, JWKS curl https://auth.last1.id/.well-known/jwks.json → 200.
  7. 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

  1. Review Finding 1 conclusion (no code fix).
  2. Review §3 coordinated cutover checklist (issuer alignment is the main risk, not Railway URL strings).
  3. Review Postgres failure behavior (§4).
  4. Approve or adjust W6B plan, then create Cloudflare DNS for auth.last1.id.

References