Skip to content

SPRINT-3 Execution Plan — realoutcomes-edge + Production Cutover

Status: Plan for Ryan review. No execution.
Date: 2026-08-28
Prerequisite: SPRINT-2 CLOSED (Gate F)
Principles: R940_PRODUCT_ISOLATION_PRINCIPLES_v1.md


Executive summary

SPRINT-3 puts production traffic on R940 RealOutcomes by creating a dedicated Cloudflare Tunnel (realoutcomes-edge) on VM 116 (192.168.1.99) and cutting realoutcomes.app DNS to it.

Sprint mode: production cutover directly. No staging hostname on lastvet-edge. No resurrecting T3-4 scripts that added RealOutcomes routes to lastvet-edge or public Supabase Kong for GoTrue (obsolete after SPRINT-2).

Auth is already unified on auth.last1.id (SPRINT-2). SPRINT-3 is ingress + PUBLIC_URL alignment, not auth rework.


Target architecture (steady state after SPRINT-3)

Browser ──► Cloudflare (proxied)
              │
              │  realoutcomes.app CNAME → realoutcomes-edge tunnel
              ▼
         VM 116 realoutcomes-app :5000  (Express monolith)
              │
              ├── OAuth ──► auth.last1.id (VM 114 / last1id-edge)  [already live]
              │
              └── DATABASE_URL ──► hosted Supabase (until SPRINT-5 DB cutover)
                        optional future: VM 117 Postgres via LAN

VM 117 realoutcomes-db :8000  (Envoy)
              │
              ├── Edge Function timers (systemd → LAN :8000/functions/v1)
              ├── Storage (LAN)
              └── Postgres + pg_cron (LAN)
              └── **No public tunnel required for SPRINT-3** (see §4)

Phase map and Ryan gates

Phase Work Ryan gate
S3-1 Create Cloudflare tunnel realoutcomes-edge + credentials Review tunnel UUID
S3-2 Install cloudflared on VM 116 (realoutcomes-app) —
S3-3 Ingress: realoutcomes.app (+ www if used) → http://127.0.0.1:5000 Review config before DNS
S3-4 Update app env: PUBLIC_URL, OAuth redirect URIs, Stripe webhook URL prep Approve env diff
S3-5 Ryan DNS: apex realoutcomes.app CNAME → {tunnel-id}.cfargotunnel.com (proxied) Required before traffic
S3-6 PF checks + automated smoke Cursor report
S3-7 Ryan browser E2E on prod hostname Gate S3-R — PASS before SPRINT-4
S3-8 Deprecate Railway origin traffic (keep rollback net briefly) Ryan sign-off

Cursor will not change Cloudflare DNS. Ryan performs DNS (same pattern as SPRINT-1).


S3-1 — Cloudflare tunnel creation

Item Target
Tunnel name realoutcomes-edge
Connector host VM 116 realoutcomes-app (.99)
Credentials Store in BWS: REALOUTCOMES_APP_EDGE_CLOUDFLARED_TUNNEL_CREDENTIALS
CNAME target {tunnel-uuid}.cfargotunnel.com

Script (to add): scripts/realoutcomes/install-realoutcomes-edge-cloudflared.sh (mirror scripts/last1-id/install-last1id-edge-cloudflared.sh).


S3-2 / S3-3 — cloudflared on VM 116

Install location: VM 116 only (Option A per isolation principles).

Proposed /etc/cloudflared/config.yml:

tunnel: realoutcomes-edge
credentials-file: /etc/cloudflared/{TUNNEL_UUID}.json

ingress:
  - hostname: realoutcomes.app
    service: http://127.0.0.1:5000
  - hostname: www.realoutcomes.app
    service: http://127.0.0.1:5000
  - service: http_status:404

Validate before DNS:

cloudflared tunnel ingress validate
curl -sf -o /dev/null -w '%{http_code}\n' http://127.0.0.1:5000/

PF checks (to add): scripts/realoutcomes/verify-realoutcomes-edge.sh (mirror last1id PF pattern).


S3-4 — App configuration updates (VM 116)

Variable Current (typical) After SPRINT-3
PUBLIC_URL http://192.168.1.99:5000 or staging https://realoutcomes.app
LAST1_OAUTH_REDIRECT_URI LAN callback https://realoutcomes.app/auth/callback
app_clients.redirect_uris (auth.last1.id) includes LAN Add prod URI; remove LAN after Ryan confirms prod-only dev workflow
Stripe webhook Railway URL https://realoutcomes.app/api/stripe/webhook (update in Stripe dashboard at cutover)

Deploy: bash /opt/realoutcomes/deploy-realoutcomes-app.sh after env render from BWS.

Do not add RealOutcomes hostnames to lastvet-edge.


S3-5 — Ryan DNS action

Record Type Target Proxy
realoutcomes.app (apex) CNAME {TUNNEL_UUID}.cfargotunnel.com Proxied (orange cloud)
www.realoutcomes.app (if used) CNAME same Proxied

Note: Cloudflare apex CNAME flattening applies. Confirm SSL mode Full (strict) once origin serves HTTPS via tunnel (tunnel terminates TLS at edge).

Rollback: repoint DNS to Railway CNAME/target documented in pre-cutover snapshot; tunnel stays up but unused.


S3-6 — Automated smoke (Cursor)

Test Pass criteria
GET https://realoutcomes.app/ 200 (after DNS)
GET /api/auth/user (no bearer) 401
GET /api/directory?limit=1 200
GET https://auth.last1.id/.well-known/openid-configuration 200
OAuth start POST /api/auth/last1/start Redirect to auth.last1.id with client_id=realoutcomes
VM 117 POST /functions/v1/process-score-runs (LAN, service role) 200
VM 117 GET /storage/v1/status (LAN) 200
supabase-auth container Absent

§4 — VM 117 Supabase Kong: public exposure decision

Question

After auth unification, is a public Supabase Kong hostname (realoutcomes-db… / tunnel to 192.168.1.98:8000) still required?

Recommendation: LAN-only sufficient for SPRINT-3

Path Needs public Kong?
User login No — auth.last1.id only (SPRINT-2 verified)
App API / Drizzle No — server uses DATABASE_URL directly
Edge Function schedules No — systemd on VM 117 curls 127.0.0.1:8000/functions/v1
Browser @supabase/supabase-js auth N/A — retired with GoTrue
Browser-direct Storage upload Audit before S3 — if client uploads via Supabase JS to Storage API, either (a) proxy through Express, or (b) add optional second ingress hostname on realoutcomes-edge pointing to VM 117 :8000 storage routes only

T3-4 staging plan (configure-tunnel-staging-db.sh, GoTrue redirects) is obsolete. Do not execute.

Default SPRINT-3 scope: app hostname only. Revisit public Kong only if SPRINT-4 browser E2E finds a client-side Storage/Functions call that cannot use server proxy.

Security posture: VM 117 Postgres/Envoy remain LAN + UFW (app VM .99 → .98:8000/5432 already provisioned). No product routes on lastvet-edge.


Obsolete artifacts (do not run)

Script / doc Reason
configure-tunnel-staging-app.sh (lastvet-edge) Violates product isolation; superseded by realoutcomes-edge
configure-tunnel-staging-db.sh Was for browser GoTrue/Kong; GoTrue retired
configure-gotrue-staging-redirects.sh GoTrue retired
T3-4 § Ryan R-2/R-3 (Supabase public + GoTrue allow-list) Superseded

Rollback

Point Action
After DNS cutover, before Railway delete Repoint realoutcomes.app DNS to Railway; revert Stripe webhook
Tunnel misconfig Stop cloudflared on VM 116; DNS unchanged
Auth regression Restore VM 117 GoTrue from Gate E backup — only if Last1 ID path breaks; not expected

Pre-cutover snapshot: Railway DNS target + Stripe webhook URL + BWS PUBLIC_URL values.


SPRINT-3 vs Tier 3 phase mapping

Legacy Tier 3 SPRINT-3 equivalent
T3-4 staging tunnel on lastvet-edge Replaced — production realoutcomes-edge
T3-5 DNS cutover SPRINT-3 S3-5 (direct prod, no staging)
T3-6 soak SPRINT-4 E2E + monitoring
T3-7 teardown SPRINT-5

Ryan authorization

STOP. Review this plan and reply:

  • SPRINT-3 go — authorize Cursor to execute S3-1..S3-4 and prepare PF scripts; Ryan performs S3-5 DNS when prompted.
  • SPRINT-3 go through S3-4 only — tunnel + config without DNS.
  • Adjustments — specify (e.g. include www, defer Stripe webhook, require Storage audit first).

References


END SPRINT-3 plan