Skip to content

SPRINT-7 T-0 Audit — last1.app + RadioCheck + Shared Supabase

Date: 2026-08-29
Scope: Read-only reconnaissance. No provisioning, no Supabase changes, no teardown.
Author: agent-01 (repo + docs audit; no live Supabase/Railway/BWS secret reads)
Ryan gate: Review this document before any T-1 provisioning.


Executive summary

Hosted Supabase project tcthzakppndkykatoefx (“Last 1 App + Radio Check”) is a three-way coupling:

Consumer How it uses the project
last1.app Railway Node monolith; GoTrue JWT auth; direct Postgres (DATABASE_URL, session store); reads/writes org/funder tables
RadioCheck iOS app (hardcoded hosted URL); 22 Edge Functions; pg_cron → Edge; Realtime; Storage
last1-admin (VM 115, paused) BFF proxies same project for admin auth + RadioCheck dashboards (LAST1ADMIN_APP_PROD_*)

No fourth runtime consumer of tcthzakppndkykatoefx was found in workspace grep (docs + muster/tracker.yaml only elsewhere). radiocheck-web uses a separate project (yloemokdrxfjkfwkskim) and is out of this bundle.

Schema is one public namespace, not separate Postgres schemas. auth.users is shared. last1.app and RadioCheck cannot be logically split on R940 without a prior DB split migration. SPRINT-7 must treat this as one cutover with an Onset-style iOS lag (backend/DNS first, App Store build second).


1. Shared-DB coupling map

1.1 Confirm: same project, not lookalikes

Evidence Finding
Supabase dashboard name “Last 1 App + Radio Check” (last1.app/supabase/.temp/linked-project.json)
last1.app linked ref tcthzakppndkykatoefx
RadioCheck iOS defaults https://tcthzakppndkykatoefx.supabase.co (RadioCheck/lib/supabase.ts)
Admin BWS (validated 2026-08-29) LAST1ADMIN_APP_PROD_SUPABASE_URL → same ref (ADMIN_BWS_DB_VALIDATION.md)
Separate project? No for prod mobile/app/admin auth. yloemokdrxfjkfwkskim is radiocheck-web marketing/Stripe only.

1.2 Three-way coupling (admin)

                    ┌─────────────────────────────────────┐
                    │  Hosted Supabase tcthzakppndkykatoefx │
                    │  Postgres + GoTrue + Edge + Storage   │
                    └──────────────┬──────────────────────┘
           ┌───────────────────────┼───────────────────────┐
           │                       │                       │
    last1.app (Railway)     RadioCheck iOS + Edge     last1-admin (VM 115 PAUSED)
    Node monolith           Functions + pg_cron         LAST1ADMIN_APP_PROD_* URLs
    SUPABASE_* + DATABASE   EXPO_PUBLIC_* defaults    service-role proxy reads

Admin does not own a DB VM; it holds service-role keys and HTTP URLs to this project. Even paused, BWS still encodes the hosted URL as intentional until bundle cutover. SPRINT-8 “reconnect” assumes upstream sovereign URLs exist first.

1.3 Other consumers (repo grep)

Location Reference
lastvet-gates/docs/architecture/* Planning docs
muster/tracker.yaml Sprint tracker
last1.app, RadioCheck clones Runtime/config
last.vet-api, angel, onset-ios, etc. None

Blast radius: Any migration/teardown of tcthzakppndkykatoefx hits last1.app + RadioCheck + admin auth proxy simultaneously unless admin is repointed to sovereign stack first.

1.4 Schema separation within the shared DB

Question Answer
Separate Postgres schemas per product? No. All migrations target public.
Migration ownership 88 SQL migrations in RadioCheck/supabase/migrations; 0 checked-in migrations in last1.app/supabase/ (linked project only). Schema history lives in RadioCheck repo.
Table namespaces Prefix convention, not schema isolation: last1_* (platform/org), RadioCheck core (users, units, check_ins, …), shared bridge tables

last1.app–leaning tables (examples):

  • organizations, organization_members, user_profiles, beneficiaries
  • irs_exempt_org, irs_status_changes, validation/audit tables
  • last1_organizations, last1_programs, last1_beneficiaries, last1_check_ins, … (20260129_1326_001_last1_integration.sql)
  • last1_trust_event_outbox, last1_webhook_deliveries
  • Drizzle-defined tables in last1.app/shared/schema.ts (orgs, programs, IRS, sessions)

RadioCheck–leaning tables (examples):

  • public.users (extends auth.users), units, check-ins, SMS threads, outreach, streaks
  • organization_settings, org SMS usage, push tokens, widgets

Cross-app coupling points (read/write both sides):

Coupling Mechanism
auth.users Single GoTrue store. last1.app web users, RadioCheck phone/email users, org admins share one auth table.
user_profiles last1.app reads is_system_admin, identity fields; RadioCheck writes last1_identity_id, onboarding flags
organizations RadioCheck org/VSO features + last1.app org validation/IRS
sync-last1 / sync-user-from-last1 Edge Functions RadioCheck ↔ last1.app webhook/sync paths
revoke-from-last1 Edge Function last1.id revocation → shared DB
last1.app storage.ts Reads RadioCheck instances by phone (radiocheck_instances)
Trust event outbox last1.app Node drainer → auth.last1.id; rows in shared DB

Conclusion: Co-located on R940 as one last1app-db stack until an explicit split migration. Not separable at cutover without data model work.

1.5 auth.users: shared or disjoint?

SHARED. One GoTrue project. Evidence:

  • RadioCheck public.users.id → auth.users(id) FK
  • last1.app customAuth.ts verifies Bearer JWT via supabaseAdmin.auth.getUser(token) on same project
  • Apple review seeds query auth.users by email (seed_apple_review.sql)
  • Identity dedupe migrations reconcile auth.users across phone/email (20260529060000_identity_dedupe_and_sync_reconcile.sql)

Populations overlap by design (org staff on last1.app may be veterans on RadioCheck; same UUID when linked).


2. last1.app — infrastructure

Field Finding
Repo Last-1-Enterprises/last1.app
Frontend + backend Single Railway Node monolith (Vite SPA + Express API). Not CF Pages/Vercel for app tier.
Railway railway.toml: Dockerfile, port 5000, build args VITE_SUPABASE_*, Stripe, optional admin flags
Domains (documented Aug 2026) Apex last1.app / www.last1.app → Railway (k1v3g8rr.up.railway.app per consolidation plan). admin.last1.app = separate Railway build (VITE_ADMIN_ONLY, VITE_API_BASE=https://last1.app) — distinct from admin.last1.enterprises (last1-admin product).
Supabase tcthzakppndkykatoefx via SUPABASE_URL, pooler DATABASE_URL / SUPABASE_DATABASE_URL
Edge functions (shared project) None in last1.app repo. All Edge Functions live in RadioCheck repo. last1.app uses Node routes + Supabase client/service-role.
pg_cron (shared project) None attributable to last1.app in repo. Scheduled work: in-process (e.g. trust event drainer, IRS validation engine) on Railway Node.
Storage buckets (shared project) No last1.app-specific bucket migrations found. App uses Supabase Storage API via service role where needed; RadioCheck owns bucket DDL (profile-photos, unit-chat-images).
Auth pattern Supabase GoTrue (primary): email/password, Google OAuth (configured in Supabase dashboard per .env.example). Optional Last1 ID OAuth (LAST1_OAUTH_ENABLED, /api/auth/last1/*). Express sessions stored in sessions table via SUPABASE_DATABASE_URL. Not on auth.last1.id at steady state today.
Integrations Stripe, SendGrid (noreply@last1.app), Twilio, Anthropic Claude API, last1.id API (https://last1.id / sovereign auth.last1.id for new identity work)

3. last1.app — user reality & data sensitivity

Topic Assessment
User counts Unknown without DB query (no Supabase dashboard access in this audit).
Real vs test Platform targets real nonprofits/funders; no formal “pre-user only” gate like Onset clinic. Likely mix of production orgs + internal/test accounts. Not assessed live.
PHI? No clinical PHI (not LastVet/Onset). Yes sensitive civilian PII: org staff emails, beneficiary contact fields (encrypted columns noted in schema comments), IRS/EIN validation data, grant/outcome metadata.
Compliance posture Org verification, 42 CFR not in scope; treat as PII + financial (Stripe) data.

T-1 requirement: Ryan or agent with read-only SQL should run user/org counts before cutover (mirror Onset T-0 sub-audit).


4. RadioCheck — infrastructure

Field Finding
Mobile repo Last-1-Enterprises/RadioCheck (React Native / Expo + native iOS)
Bundle ID com.last1.radiocheck (+ widget com.last1.radiocheck.widget)
App Store (repo) Marketing 1.1.14, build 6. App Store URL in .env.example: The Radio Check
Backend Hosted Supabase on tcthzakppndkykatoefx (Auth, PostgREST, Realtime, Edge, Storage). No Railway for mobile backend.
Web/marketing radiocheck-web → separate Supabase yloemokdrxfjkfwkskim (Stripe checkout, form notify). Not in shared-db bundle.
Domains App deep links (radiocheck://); Twilio/webhooks hit *.supabase.co/functions/v1/*
iOS backend pointer (production) Hardcoded in RadioCheck/lib/supabase.ts: https://tcthzakppndkykatoefx.supabase.co + embedded anon JWT default. Override via EXPO_PUBLIC_SUPABASE_* at build time.
Auth pattern Phone OTP on shared GoTrue (supabaseLast1 client, same URL). Also email/password, Google, Apple SIWA on same project. Last1 ID Connect: separate client supabaseLast1Identity — docs still reference deleted hosted nztgswcpnwggymncrnyx; runtime should use auth.last1.id publishable key (SPRINT-1 done).
Last1 ID callback Edge last1-id-callback on shared project

4.1 Edge Functions (shared project — RadioCheck repo)

22 deployed function names (from repo):

check-in-proof-events, delete-account, last1-id-callback, org-sms, org-sms-cron, quick-check-in, revoke-from-last1, send-push, silence-engine, sms-checkin, sms-cron, sms-daily-check, sms-send, sms-webhook, stripe-checkout, stripe-portal, stripe-webhook, sync-last1, sync-user-from-last1, trigger-check-ins, unit-request-actions, verify-org-email

4.2 pg_cron jobs (shared project)

Job name Target Notes
check-in-proof-events-cron check-in-proof-events Every ~5 min; Vault/RADIOCHECK_CRON_SECRET pattern
sms-checkin-cron sms-cron Daily SMS check-ins
org-sms-cron org-sms-cron Org SMS batch
~~check-in-proof-outbox~~ — Removed (broken app.settings.* pattern)

All pg_cron attributable to RadioCheck, not last1.app.

4.3 Storage buckets (shared project)

Bucket Owner Purpose
profile-photos RadioCheck Public profile images
unit-chat-images RadioCheck Unit thread chat media

5. RadioCheck — user reality

Topic Assessment
User counts Unknown (no DB access). App Store live ⇒ non-zero production users on 1.1.14 (6) unless pulled from sale.
Real vs test seed_apple_review.sql: review@radiocheck.app, orgadmin@radiocheck.app, demo org siloed via RLS (20260421234500_silo_demo_units_for_apple_review.sql). Production users isolated from Apple demo units by policy, not separate DB.
Who’s on live build All App Store users on current store version point at hosted tcthzakppndkykatoefx until new binary shipped.
Data sensitivity Veteran check-in, unit/SMS content, org admin data. Not clinical EHR/PHI, but high-sensitivity veteran wellbeing data + phone numbers.

6. GitGuardian / secrets (SPRINT-7 rotation scope)

Source: GITGUARDIAN_CATEGORY_D_REFINEMENT_2026-08-27.md

6.1 D-live — rotate during SPRINT-7 (before hosted teardown)

GG ID Service Repos Notes
29160801 Twilio (valid) last1.app, last1.id .env Shared Twilio; used by RadioCheck SMS Edge + last1.app
29615732 Claude API (valid) last1.app Anthropic
29615733 Stripe (valid) last1.app Payments
29615730 Stripe webhook secret last1.app Rotate with Stripe endpoint
29615727 SendGrid last1.app GG marked invalid; verify dashboard anyway

D-live? (identify then rotate): 29615725, 29615731, 29615734 (last1.app .env high-entropy)

6.2 Shared DB connection strings — rotate at sovereign cutover

Class GG IDs (defer label) Action in SPRINT-7
Supabase JWT (anon/service) 29160796, 33887929, 33887930, D4 bundle New keys on last1app-db; update BWS LAST1APP_DB_PROD_*, RADIOCHECK_*, Railway, Edge secrets, iOS build
Postgres pooler URLs 29160797, 29615726, 29615728 Replace with LAN/last1-api.* URLs in BWS; retire hosted pooler

6.3 D4 RadioCheck — mostly bundle JWT/Postgres (B-defer-T6 → now SPRINT-7)

Five incidents in RadioCheck + shared .env — close after sovereign sign-off, not before rotation.

6.4 BWS keys (three consumers — naming per R940_NAMING_CONVENTION_v1.md)

Consumer Expected BWS pattern Shared DB note
Stack owner LAST1APP_DB_PROD_* (SUPABASE_URL, JWT_SECRET, DATABASE_URL, anon/service) Single source of truth for Postgres
last1.app app LAST1APP_APP_PROD_* (Stripe, SendGrid, Twilio, Claude, session, VITE_*) Mirrors DB anon for builds
RadioCheck Edge/iOS RADIOCHECK_APP_PROD_* / function secrets (RADIOCHECK_CRON_SECRET, Twilio, Stripe, SendGrid, APNs/FCM) Same DATABASE_URL owner as LAST1APP_DB_PROD_*
Admin (paused) LAST1ADMIN_APP_PROD_SUPABASE_URL, *_RADIOCHECK_SUPABASE_URL Repoint to sovereign public URL in SPRINT-7 cutover or explicit decision to leave on hosted rollback until SPRINT-8

Additional secret inventory (RadioCheck .env.example, not GG): LAST1_WEBHOOK_SECRET, RADIOCHECK_APP_SECRET, UNIT_REQUEST_ACTION_SECRET, FCM/APNs keys, Stripe price IDs.


7. Proposed R940 VM topology

Follows R940_NAMING_CONVENTION_v1.md and R940_PRODUCT_ISOLATION_PRINCIPLES_v1.md. LAN IPs proposed — next free after Onset .101/.102 (R940_LAN_NETWORK_INVENTORY.md).

VMID (TBD) Hostname LAN IP (proposed) Role
TBD last1app-db 192.168.1.103 Self-hosted Supabase stack: Postgres, Kong :8000, GoTrue (interim), Edge Runtime, Storage (MinIO), pg_cron
TBD last1app-app 192.168.1.104 last1.app Node monolith :5000 + last1app-edge cloudflared

Not a separate radiocheck-app VM: RadioCheck backend is Edge + Auth on last1app-db. iOS is client-only.

Public DNS (proposed — Ryan confirm):

Hostname Target Purpose
last1.app last1app-edge → app :5000 Web monolith (move off Railway)
last1-api.last1.app (or similar) last1app-edge → Kong on last1app-db Supabase API for browsers + RadioCheck iOS + Edge webhooks (Twilio, Stripe)

Staging (naming convention): last1app-app.last1.enterprises, last1app-db.last1.enterprises

admin-app (VM 115): Stays paused through SPRINT-7 cutover. BWS URLs should still be updated to sovereign when hosted teardown is authorized so SPRINT-8 reconnect is flip-only.

radiocheck-web: Separate sprint/project (yloemokdrxfjkfwkskim); not blocking shared-db cutover.


8. Sequencing recommendation

8.1 Single shared-DB cutover (mandatory)

last1.app and RadioCheck must migrate in one database cutover (dump/restore or logical replication) because:

  • One auth.users
  • Cross-table FKs and sync Edge Functions
  • Admin proxy expects one Supabase URL

Do not split last1.app DB to R940 while leaving RadioCheck on hosted (or vice versa) without a designed split migration.

8.2 iOS App Store clock (Onset pattern)

Phase Work User impact
T-1 … T-4 Provision last1app-db + last1app-app, restore DB, deploy Edge, smoke on staging None
T-5 backend Public last1-api.* + DNS; last1.app Railway → R940; iOS still on hosted URL via old binary last1.app web on sovereign; mobile unchanged
T-5-iOS Step 1 TestFlight build pointing at last1-api.*; Ryan journey TestFlight only
T-5-iOS Step 2–3 Production xcconfig + App Store submit Review clock ~1–7 days
T-5-2 Hosted Supabase rollback net Keep hosted until App Store live
T-6 Teardown hosted tcthzakppndkykatoefx + Railway last1.app After Ryan sign-off

RadioCheck requires clinic-first-style split: backend/public API first, iOS later. Shipping only a server cutover breaks App Store users until new binary is live.

8.3 Admin dependency — SPRINT-7 vs SPRINT-8

Aspect SPRINT-7 SPRINT-8
Hosted DB teardown Blocked until bundle + iOS live Final admin reconnect
Admin BWS URL update Should happen at sovereign cutover (even if VM paused) Resume compute, E2E
Admin compute move Out of scope (already on VM 115, paused) Reconnect + decommission hosted refs

SPRINT-7 must account for admin in the shared DB URL sense. SPRINT-8 owns resuming admin and full multi-product upstream realignment.

8.4 Suggested gate order (within SPRINT-7)

  1. T-0 — This audit (Ryan sign-off)
  2. T-1 — Provision VMs, BWS LAST1APP_DB_PROD_*, restore dump, apply 88 migrations
  3. T-2 — Edge deploy + pg_cron + secret rotation (Twilio/Stripe/SendGrid/Claude)
  4. T-3 — last1app-edge + DNS staging smoke
  5. T-4 — last1.app Railway cutover to R940
  6. T-5 — Public Supabase API hostname + last1.app production DNS
  7. T-5-iOS — TestFlight → App Store (Ryan Mac)
  8. T-6 — Hosted teardown + GG defer close

9. Open questions (Ryan decisions)

  1. Public Supabase hostname: last1-api.last1.app vs api.last1.app vs Kong on same apex? (Twilio/Stripe webhooks must be updated.)
  2. GoTrue vs Last1 ID unification: Steady-state per R940_PRODUCT_ISOLATION_PRINCIPLES_v1.md is auth.last1.id only. Does SPRINT-7 retire GoTrue on this stack or run interim dual auth through cutover?
  3. RadioCheck supabaseLast1Identity: Repoint from deleted nztgswcpnwggymncrnyx defaults to auth.last1.id keys in same iOS release as API flip?
  4. Hardcoded anon key in RadioCheck/lib/supabase.ts: Remove/redact and rely on BWS-rendered xcconfig before App Store build?
  5. User census: Approve read-only SQL on hosted project for org/user counts before T-1?
  6. admin BWS at cutover: Update LAST1ADMIN_APP_PROD_* to sovereign URL while VM stays paused, or keep hosted as admin-only rollback until SPRINT-8?
  7. radiocheck-web (yloemokdrxfjkfwkskim): Migrate in SPRINT-7 or park?
  8. Marketing split: last1.app monolith → CF Pages deferred, or bundled into SPRINT-7 scope?
  9. Real user communication: RadioCheck App Store users — in-app force-upgrade vs silent backend compatibility window?
  10. VMIDs / IPs: Confirm .103/.104 assignment on pve-r940.

10. Evidence gaps (read-only constraints)

Gap Impact
No Supabase MCP / dashboard No live MAU, disk size, extension list, cron job_run_details
No Railway CLI No live memory/replica/env verification
No BWS secret reads URLs/keys inferred from docs + repo only
No production DB query User counts marked unknown

T-1 prep should close gaps 1, 3, 4 before restore/cutover.


11. References


STOP. No T-1 provisioning until Ryan reviews and resolves §9 open questions.