Skip to content

Tier 3 T3-1 Report — RealOutcomes Planning + BWS + VMIDs

Date: 2026-08-28
Status: T3-1 COMPLETE. STOP here. Await Ryan authorization for T3-2 (VM provision + Gate B).
Parent audit: TIER_3_REALOUTCOMES_MIGRATION_AUDIT.md (T3-0 approved)
Execution plan: REALOUTCOMES_MIGRATION_PLAN.md


Executive summary

T3-1 confirms Option A (self-hosted Supabase full stack) is feasible on R940 with known gaps (Edge Function cron replatforming, first portfolio Supabase Docker ops). Recommended two-VM layout matching last1.id split: VM 117 hosts Supabase platform + Postgres; VM 116 hosts Express monolith + IRS cron.

Auth unification with auth.last1.id: Defer to a separate workstream (estimated 2–4 days, not ~1 day). Tier 3 keeps product-local GoTrue migrated via auth.users dump.

Marketing split: Deferred post T3-7 per Ryan decision.

BWS: Use existing realoutcomes project with new REALOUTCOMES_R940_PROD_* keys; D-live rotations scheduled during T3-2 BWS population.


1. Self-hosted Supabase feasibility (Option A)

Verdict: GO, with T3-2 proof tasks

Official Supabase Docker Compose (supabase/supabase/docker) ships Edge Runtime, Storage, GoTrue, PostgREST, Kong, pg_cron-capable Postgres, and documents function deployment to volumes/functions/. This matches RealOutcomes' hosted surface.

Capability Feasibility Notes
Edge Runtime (8 functions) Yes Standard Deno functions in repo; copy to volumes/functions/; restart/recreate functions service. All use verify_jwt = false + service role (same trust model).
Storage (uploads) Yes 0 objects in hosted bucket today. Local filesystem backend sufficient; optional MinIO/S3 later. App uses server-side signed URLs via service role (server/fileStorage.ts).
GoTrue Auth Yes Lift auth.users (3 rows) in Gate B dump. Configure Google OAuth on self-hosted GoTrue (separate GCP client or defer Google until post-cutover).
pg_cron (5 jobs) Yes Extension already enabled on hosted DB. Supabase Postgres image supports pg_cron; replay job SQL from hosted export (T3-0 captured schedules).
Postgres 17 + pg_trgm Yes Hosted on 17.6.1; ~1.3 GB / ~2M IRS rows. Plan 150 GB ZFS data disk.
Realtime N/A No client Realtime usage found in repo. Disable in compose to save RAM.
RLS Low risk All 40 public tables use service_role-only policies; app uses Drizzle + direct Postgres (not PostgREST user JWT). No auth.uid() policies.

Gaps before committing (T3-2 must prove)

Gap Mitigation
Edge Function schedules Hosted uses Supabase Dashboard cron (not in repo). Repo comment: "scheduled functions, not pg_cron HTTP calls" (migrations/20260217_status_change_alerts.sql). T3-2: export hosted schedules from dashboard; implement systemd timers on VM 117 curling http://127.0.0.1:8000/functions/v1/<name> with service role Bearer.
First Supabase Docker ops on R940 last1.id used custom GoTrue/Kong/bare Postgres, not official compose. Pin release via .supabase-version; document run.sh upgrade path.
Function secrets compute-score (OpenAI), run-crawl (Firecrawl), send-alert-digests (SendGrid) need .env.functions / compose env on VM 117.
Resource sizing Supabase docs: 8 GB+ RAM recommended for full stack. co-locate only platform on VM 117; keep app on VM 116.
Network App VM reaches Supabase via LAN IP (Tailscale east-west Postgres blocked per portfolio ACL; same as last1.id).
Option B fallback If T3-2 Edge Runtime fails on Deno deps: port functions to Express routes + systemd (estimate +3–5 days). Do not trigger unless Option A proof fails.

Docker Compose recipe

Yes — working official recipe exists:

  1. curl -fsSL https://supabase.link/setup.sh | sh (or sparse-clone supabase/docker)
  2. Configure SUPABASE_PUBLIC_URL, API_EXTERNAL_URL, SITE_URL for internal LAN + later prod
  3. sh run.sh start
  4. Deploy functions: sync RealOutcomes/supabase/functions/ → volumes/functions/<name>/index.ts
  5. Docs: Self-Hosting with Docker, Self-Hosted Functions, Self-Hosted S3

2. Auth unification feasibility (auth.last1.id)

Current state

Item Detail
Auth Native Supabase Auth (GoTrue on product project)
Users 3 (ryan@last1.org, rydaunicorn@gmail.com, ryan@last1.enterprises)
Client supabase.auth.signInWithPassword, OTP, Google OAuth (client/src/pages/login.tsx)
Server supabaseAdmin.auth.getUser(token) (server/auth.ts)
RLS Service-role only; no auth.uid() dependency
User data public.users FK = auth.users.id; 1 profile, 1 watchlist, 0 Stripe subs

Unification scope (if pursued)

Work item Effort
Register RealOutcomes OIDC client on auth.last1.id 0.5 d
Replace client login with OIDC redirect + callback 1 d
Replace server token validation (JWKS from auth.last1.id) 0.5–1 d
Remap or re-create 3 users + saved data FKs 0.5 d
Google OAuth dedup / redirect URI matrix 0.5 d
Parallel soak + admin gateway regression 0.5–1 d
Total ~2–4 days (not bounded ~1 day)

Recommendation: DEFER (separate "identity unification" workstream)

Rationale:

  1. Tier 3 goal is compute + DB sovereignty, not portfolio SSO.
  2. Self-hosted GoTrue migration is straightforward (include auth schema in dump; 3 users can re-login or reset password).
  3. Unification adds cross-product coupling during the first full-stack Supabase Docker migration (already the critical path).
  4. RLS does not block deferral (service-role architecture).

If deferred: Tier 3 ships R940 GoTrue at http://192.168.1.98:8000/auth/v1 with new JWT keys at cutover (closes GG 29169497 naturally).


3. VM planning

Recommendation: separate compute + platform VMs (do not consolidate)

Matches last1.id pattern (VM 113 db + VM 114 app). Supabase stack is memory-heavy; IRS import + Docker build on app VM should not compete with Edge Runtime + Postgres.

VM VMID Hostname LAN IP Spec Role
App 116 realoutcomes-r940 192.168.1.99/24 2 vCPU, 4 GB RAM, 50 GB boot Express Docker :5000, monthly-irs-sync systemd timer, node_exporter
Supabase platform 117 realoutcomes-supabase 192.168.1.98/24 4 vCPU, 16 GB RAM, 50 GB boot + 150 GB ZFS data Supabase Docker Compose, Postgres 17, Edge Functions, Storage, GoTrue, Kong :8000, pg_cron, function cron timers, postgres_exporter

IPs: .98/.99 next after admin .97 and auth-last1id .96. Confirm no collision on pve-r940 before provision (T3-2).

Tailscale: Join both VMs to tailnet; BWS DATABASE_URL uses LAN per portfolio convention.

Ingress: lastvet-edge tunnel → VM 116 :5000 at cutover (same pattern as n8n/admin).


4. Railway env audit

Source: Railway GraphQL read-only (LAST1_PLATFORM_RAILWAY_PROD_API_TOKEN).
Project: disciplined-beauty (0cb8d8fa-5216-479a-b1f5-00d2dfa7c88b)
Environment: production (12ba2dc7-8e11-4ab4-a4d9-9ecfecb47533)

Service ID Domain
RealOutcomes.app b048dff1-e5d7-446f-8eb4-87d57950aca1 realoutcomes.app
monthly-irs-sync 938c60c8-68e5-45e9-bac0-e49b07d03ad9 (none)

Web service env keys (24, excl. RAILWAY_*)

ADMIN_GATEWAY_SECRET, DATABASE_URL, FEC_API_KEY, FIRECRAWL_API_KEY, LAST1_CERT_REGISTRY_URL, NODE_ENV, OPENAI_API_KEY, PUBLIC_URL, SENDGRID_API_KEY, SENDGRID_FROM_EMAIL, SESSION_SECRET, STRIPE_FUNDER_MONTHLY_PRICE_ID, STRIPE_FUNDER_YEARLY_PRICE_ID, STRIPE_INVESTIGATOR_MONTHLY_PRICE_ID, STRIPE_INVESTIGATOR_YEARLY_PRICE_ID, STRIPE_PRO_MONTHLY_PRICE_ID, STRIPE_PRO_YEARLY_PRICE_ID, STRIPE_PUBLISHABLE_KEY, STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET, SUPABASE_ANON_KEY, SUPABASE_SERVICE_ROLE_KEY, SUPABASE_URL, VITE_SUPABASE_ANON_KEY, VITE_SUPABASE_URL

Cron service env keys (2)

DATABASE_URL, NODE_ENV (inherits DB creds; full set not duplicated on Railway)

Constraint honored: No Railway changes in T3-1.

Sizing note: Railway API did not return memory limits. App VM 2 vCPU / 4 GB matches admin + n8n precedent. Supabase VM sized up for Postgres + 8 Edge Functions.


5. BWS project

Option Verdict
New project Not needed
Existing realoutcomes (788ed6ec-0284-4bbc-b57c-b4840004688e) Recommended

Current keys (3): realoutcomes_url, realoutcomes_web, realoutcomes_server (marketing/deploy metadata).

Add alongside (do not delete legacy until T3-7): REALOUTCOMES_R940_PROD_* per Tier 2 naming pattern (LAST1_ID_R940_PROD_* precedent in Last1.id project).

D-live rotation window (approved)

GG ID Secret T3 action
29169495 Stripe webhook Rotate in Stripe Dashboard → BWS before T3-5
29169499 SendGrid Rotate → BWS
29169500 Firecrawl Rotate → BWS
29169498 Postgres Replaced by R940 creds at stack init
29169496 OpenAI Invalid in GG; reissue if production usage confirmed
29169497 Supabase JWT New keys from R940 stack at cutover

Do not rotate ADMIN_GATEWAY_SECRET independently (shared with last1-admin until Tier 6).

T3-2 action: Run Railway→BWS sync script (to be added); Ryan rotates vendor keys; store rotated values in REALOUTCOMES_R940_PROD_*.


6. Ryan decisions captured (2026-08-28)

# Decision T3-1 disposition
1 Option A self-hosted Supabase Accepted; gaps listed above
2 Marketing split deferred Reflected in plan + muster
3 D-live rotation during BWS Accepted; mapped to GG IDs
4 Auth unification Defer; recommendation documented

7. STOP — Next gate

Phase Status Ryan action
T3-0 Audit Done Approved
T3-1 Planning Done Approve T3-2 kickoff
T3-2 DB + Supabase stack Blocked Authorize VM 116/117 provision + Gate B
T3-3 App parallel soak Blocked —
T3-4 Edge/cron/Stripe Blocked —
T3-5 DNS cutover Blocked —
T3-6 Soak Blocked —
T3-7 Teardown Blocked —

T3-2 will: provision VMs 116/117, deploy Supabase Docker, Gate B dump/restore rehearsal, populate BWS REALOUTCOMES_R940_*, without DNS cutover or hosted teardown.