Decisions and roadmap
ADR 0022: Leave Railway for Render + 1Password broker
Numbering and status (2026-10-25). Renumbered from 0016 to 0022 when #232 merged: ADR 0016 is "approved stack required" and the runtime decision is ADR 0017 "Render runtime" (Samir, 2026-10-24 01:36 EDT: "Render is the way"); 0018-0021 are taken by other open branches. This file is the Railway-exit record and the broker pilot. The current plan, cutover order (Construct first), costs and the Needs Samir list are in
docs/runbooks/render-cutover/README.md, which supersedes the D0-D2 order below.
- Status: proposed (draft PR; nothing deployed, no account created, no DNS changed)
- Date: 2026-10-22 (revised 2026-10-22 22:00 EDT with Samir's decisions: Render, Observatory staging-only, Interspace last; recompressed 2026-10-22 22:43 EDT to 2-3 days: Samir, "move faster ... crank this out in the next 2-3 days or less", while the Render Pro account is being created)
- Decided by: Samir (owner). Host decided: Render. The pilot settles only the broker question.
- Relates to: ADR 0014 (Exo Secure: secrets from 1Password at boot), ADR 0015 (key rotation, on the stacked rotation PR), Construct's own Render plan (construct PR #406), runbook render-pilot-exo-staging.md
Context
Railway disclosed on 2026-10-21 that an attacker read decrypted customer environment variables and
paused deploys (disclosure).
ADR 0014 already moves EXO's secrets into 1Password, but on Railway the one bootstrap token
(OP_SERVICE_ACCOUNT_TOKEN) still sits in the same env store that leaked, and Railway has no
workload identity (OIDC) to replace it. Every Interspace app is hosted there: EXO, arch-router,
Observatory, Cosmograph, Universe, Construct (which also serves Interspace at
interspace.ventures), Hadron/Dropship and Moonshot.
The goal Samir asked for: leave Railway, and ideally reach a host where no long-lived secret is stored at all, using the 1Password Credential Broker (workload OIDC token in, 1Password Environment variables out).
What is true today (2026-10-22):
| Fact | Source |
|---|---|
| Credential Broker is a public preview for 1Password Business only. Custom OIDC workflows are beta and JS SDK only. | 1password.dev/brokered-access, custom workflow |
A broker workload still holds OP_INTEGRATION_KEY (secret), OP_WORKLOAD_ID and an Environment ID. The admin's integration fixes the expected audience; the workload must present a token for it. SDK 0.5.0 asks for it via oidcFetcher(audience). | same; @1password/sdk 0.5.0 types (ClientConfiguration.oidcFetcher, WorkloadDetails) |
| Samir is on 1Password Families. Business is $8.99/user/mo. Families service accounts: 1,000 reads/hour per token, 1,000/day per account (Teams 5,000/day, Business 50,000/day). | 1Password pricing and service-account limits |
| Render OIDC needs a Pro workspace ($25/mo) plus compute ($7 for 0.5 CPU/512 MB, $25 for 1 CPU/2 GB). | render.com/pricing, platform features by plan |
Render mints tokens only for provider audiences: AWS STS (sts.amazonaws.com, via AWS_ROLE_ARN -> AWS_WEB_IDENTITY_TOKEN_FILE), OpenAI (api.openai.com), Anthropic (api.anthropic.com), GCP. Issuer https://oidc.render.com/{tea-…}. No OIDC at build time for Dockerfile builds. | render.com/docs/oidc |
| Fly.io and Cloud Run mint OIDC tokens for any audience. | Fly.io /.fly/api OIDC tokens; Cloud Run metadata server identity tokens |
| 1Password for Agents (launched 2026-10-22) is consumer sign-in approval for personal agents; free on Individual and Families. It does not deliver workload secrets. | 1Password blog |
So a Render service can probably not get a token 1Password will accept. That is unverified and is pilot test #1. It is cheap to settle: when an admin creates a custom OIDC integration, 1Password shows the expected audience. If it is not one of Render's four, the broker cannot run on Render.
Decision
- Render is the host (decided by Samir, 2026-10-22). Every app that stays with Interspace moves from Railway to Render, all staging environments first (D0), then the productions (D1-D2), then Interspace (D2-D3), in 2-3 days (timeline below). Render because it is the fastest exit: Blueprints in git, PR previews, health-checked zero-downtime deploys, Docker and native Node, Pro-plan OIDC for AWS, OpenAI and Anthropic, and Construct already has a Render plan (#406). For the record: the broker fits Fly.io or Cloud Run better, because they mint OIDC tokens for any audience.
- Observatory moves staging only; it has no production cutover. Observatory production
(
observatory.bio, Railway) is retired after Samir hands the code to his dad. See "Observatory" below. Nothing is deleted without Samir's explicit yes. - Ship on the fallback, not the broker. Each app environment gets one read-only 1Password
service account token scoped to that app's vault for that environment
(
OP_SERVICE_ACCOUNT_TOKEN, Rendersync: false). That is ADR 0014 unchanged, moved to a host that has not just leaked its env store, with tokens minted fresh (never copied from Railway). - The broker is a pilot item, not a dependency. The loader learns
EXO_SECURE_SOURCE=broker|autonow (src/lib/exo-secure/broker.ts), so trying it later is a config change.autotries broker, then service account, then file (development) or env. Nobody buys 1Password Business until pilot test #1 says Render can present the right token. - Blueprints are the only way services are defined.
render.yamlholds no secret values, onlysync: falsedeclarations. EXO Verify's newrender-blueprintcheck fails the gate if one does. - The pilot is EXO staging, not Observatory staging. EXO staging already has the Exo Secure
loader (#225) and its Blueprint (this PR), so it can deploy on D0; Observatory staging would first
need a loader-port PR in another repo, which puts it on the critical path of a three-day staging
window. The stakes stay low: the pilot runs on
exo-staging.onrender.comwhilestaging.exo.nowkeeps pointing at Railway until go/no-go. Runbook: render-pilot-exo-staging.md.
Pushback, stated plainly
- Render does not deliver "no stored secret". With the broker unavailable, each service still stores one long-lived secret. It is read-only and scoped to one vault, and Render is not the host that just leaked, but it is the same shape of risk we had on Railway. Moving hosts is not the security fix; rotation is.
- Rotation matters more than the move. Every value that was on Railway is compromised. Copying them to Render unrotated carries the breach with us. Rotate into 1Password first (ADR 0015 and the secrets inventory), then point Render at the vault.
- Paying for a preview. Business at $8.99/user/mo is cheap, but it buys a public-preview broker whose custom-OIDC path is beta, for a team of one. Do not upgrade on spec.
- Two to three days is very tight. Render is not the slow part. Secret rotation (every value must
be new before its vault is filled), the Universe Postgres move, DNS, and the repo gap for Moonshot,
Hadron and Dropship (parallel track below) are. The plan holds by: staging all on D0 behind
onrender.comURLs, TTLs at 300 s from D0, no app's 7-day rollback window ever blocking the next cutover, "red smoke test stops that app, not the line", and production on a weekend morning (lowest traffic, but slower vendor support). The real risk is rotation: an app whose vault is not filled with new values simply waits; it never ships with the leaked ones. - Render does remove some keys entirely. Its managed OIDC for OpenAI and Anthropic means an app
that calls those APIs directly can drop
OPENAI_API_KEY/ANTHROPIC_API_KEYfrom its vault once its client supports workload identity. That is a real win the broker was never going to give us.
Alternatives considered
| Option | Verdict |
|---|---|
| Stay on Railway, rotate, harden | Rejected. No workload identity; the bootstrap token stays in the store that leaked; deploys were paused. |
| Render + service-account token (chosen) | Fastest exit, Blueprints, previews. One read-only token per app env. Broker probably impossible (audience). |
| Render + Credential Broker | Blocked on audience (pilot #1) and Business plan. Kept behind EXO_SECURE_SOURCE=broker. |
| Fly.io + Credential Broker | Not chosen. More ops (fly.toml, machines); no Blueprint-style previews. |
| Cloud Run + Credential Broker | Not chosen. Heaviest migration (GCP project, IAM, Artifact Registry). |
| Vercel | Fine for Next front ends, wrong for arch-router (long-running CPU model) and Express APIs; secrets still env vars. |
| Cloudflare (Workers/Containers) | Rewrites for Node-specific servers; not a one-week move. |
Cost
Render Pro workspace $25/mo once, shared by every app, plus compute per service. Plans:
starter 0.5 CPU / 512 MB $7, standard 1 CPU / 2 GB $25. Sizes below are estimates, not
benchmarks; arch-router at 1 CPU / 2 GB especially (CPU only; Render has no GPUs). Pro includes 15
custom domains, enough for every hostname below.
| App | Staging | Production | Compute / mo | + Pro share (1/8) | Per app / mo |
|---|---|---|---|---|---|
| EXO | standard $25 | standard $25 | $50 | $3.13 | $53.13 |
| arch-router | (one shared instance) | standard $25 | $25 | $3.13 | $28.13 |
| Observatory | starter $7 | none on Render (retired on Railway after handoff) | $7 | $3.13 | $10.13 |
| Cosmograph | starter $7 | standard $25 | $32 | $3.13 | $35.13 |
| Universe | starter $7 | standard $25 (RC env dropped; PR previews replace it) | $32 | $3.13 | $35.13 |
| Construct (+ Interspace) | starter $7 | standard $25 | $32 | $3.13 | $35.13 |
| Hadron (incl. Dropship) | starter $7 | not yet | $7 | $3.13 | $10.13 |
| Moonshot | starter $7 | not yet | $7 | $3.13 | $10.13 |
| Total | $192 | $25 | $217/mo ($2,604/yr) |
Plus, outside Render: PR previews at $7 each while open (manual generation, so near zero); Neon for Universe (Free or Launch; usage-based); 1Password stays Families (no change) unless the broker pilot passes, then Business at $8.99/user/mo for one seat. Render Key Value for Hadron's Valkey only if it turns out to be needed (price to check). Observatory production keeps its Railway bill until it is retired.
Break-even and margin:
- Against Railway: Railway's actual bill could not be pulled (the Railway API token was invalidated after the incident). Fill in last month's invoice here before approving; if it was under ~$215/mo, Render costs more and we are paying for the exit, not saving.
- Against revenue: Cosmograph is the only app with prices set ($9.99/yr or $29.99 lifetime). Its $35.13/mo ($422/yr) breaks even at about 43 annual members or 15 lifetime sales a year, before payment fees. Every other app is pre-revenue; their hosting is pure cost.
- Cheapest honest variant: all staging on
starter, prod onstandardonly for EXO and arch-router, the reststarteruntil load says otherwise: about $145/mo. Starter (512 MB) is tight for a Next build server; watch memory in the pilot before choosing it for production.
Migration order
Staging first, all of it, on D0; then production; Interspace last. Record-by-record DNS: render-cutover/dns-changes.md. Vaults and items: render-cutover/vaults-and-items.md. D0 commands: render-cutover/commands-d0.md. Samir's batched yes list: render-cutover/approval-list.md.
Staging (D0)
- EXO staging (pilot, first) — root
render.yamlin this PR; runbookdocs/runbooks/render-pilot-exo-staging.md. Proves Blueprint, 1Password service-account boot, health check and previews onexo-staging.onrender.com; pilot test #1 is recorded but no longer gates the rest. Go for the others after two healthy deploys (about an hour), thenstaging.exo.nowflips the same day. The staging gateway's*_STAGING_ORIGINs move to Render URLs as each app below lands. - arch-router — Docker web service (Convex calls it from outside Render, so not a private
service).
llama-serveris a C++ binary, so its key arrives through anop runwrapper entrypoint (child-process env only, ADR 0014's documented exception); that entrypoint and its Blueprint entry are a small follow-up PR, not in this one. One instance shared by staging and production: staging Convex'sARCH_ROUTER_URLmoves on D0, production Convex's with EXO production (D1). The Railway instance keeps serving production until then. - Cosmograph staging — Neon staging branch stays; new role password.
- Universe staging — Railway Postgres staging -> Neon
stagingbranch (below). - Construct staging — per construct PR #406 and tonight's render.yaml PR;
staging.construct.pageandstaging.construct.codesflip on D0. - Observatory staging — with its loader-port and render.yaml PR (opening tonight). No DNS
change: it sits behind
staging.exo.now/observatory. Staging only; see "Observatory".
Production (D1-D2)
- EXO production (D1) —
exo.nowapex andwww. Production ConvexARCH_ROUTER_URL-> Render. - Construct production (D1) —
construct.codes,www.construct.codes,construct.page. It must not take overinterspace.venturesyet: Railway's Construct production keeps running and serving Interspace (and, unless Samir moves them on D2,structured.glass,foundation.financial,bumblebee.nyc). - Cosmograph production (D2) —
cosmograph.space(+ a newwww). - Universe production (D2) — Railway Postgres -> Neon
production, thenrelease.universe.audio(new, the canary) anduniverse.audio(today an old Replit build at Google Frontend, not Railway).
Last (D2-D3, ~06:00 ET)
- Interspace —
interspace.ventures+www, served by Construct production on Render. Zero-downtime cutover below.
Parallel track (not on the critical path): Moonshot, Hadron and Dropship need git repos first. See "Parallel track".
Timeline (2-3 days)
D0 is the day the Render account exists: tonight (Thu 2026-10-22) or Fri 2026-10-23. Dates below assume D0 = Fri 10-23; if it lands later every row slides by the same amount. Times are ET.
| Day | Date | What |
|---|---|---|
| D0 | Thu night / Fri 10-23 | Samir's approval items 1-10: rotate exposed keys; Render GitHub App; Render API key; 10 vaults + 10 read-only service accounts; merge #225, #227 and the per-repo render.yaml PRs into staging. All staging on Render: EXO staging pilot first, then arch-router (staging Convex URL), Cosmograph, Universe (Neon staging), Construct, Observatory staging. TTLs to 300 s on every production record that moves. Flip staging.exo.now, staging.construct.page, staging.construct.codes. Railway staging paused, not deleted. Ask Render support about early certificate issuance for interspace.ventures. |
| D1 | Sat 10-24, morning | EXO production (exo.now, www; production Convex ARCH_ROUTER_URL). Construct production (construct.codes, www, construct.page), not interspace.ventures. Interspace checked on the Render Construct service via its onrender.com URL and a Host header, DNS untouched. |
| D2 | Sun 10-25, morning | Cosmograph production (cosmograph.space). Universe production (Neon production, release.universe.audio, then universe.audio). Optionally Construct's other domains. |
| D2-D3 | Sun 10-25 or Mon 10-26, ~06:00 | Interspace (interspace.ventures, www), last, zero downtime. Sunday only if Construct production has been green 12 h and every other cutover is green; otherwise Monday. |
| +7 days each | from Fri 10-30 to ~Tue 11-03 | Each Railway service stays paused 7 days after its own cutover, then is deleted only with Samir's yes. Runs in the background; never blocks the next cutover. |
| parallel | from D0 | Repos for Moonshot, Hadron (and Dropship if it does not fold into Hadron); their staging moves whenever a repo exists. |
Rules: one hostname group at a time, each with Samir's yes. A red smoke test stops that app and rolls it back; it does not stop apps that are already green or unrelated. Interspace never goes while any other production cutover is red. An app whose vault does not yet hold freshly rotated values waits; it never ships with Railway's values.
Observatory
- Staging only on Render.
observatory-stagingmoves on D0 with its Exo Secure loader port. It never gets a Render production service, anObservatory Productionvault, or a production service account. - No production cutover. Observatory production (
observatory.bio, Railway) keeps running on Railway until Samir hands the code to his dad, then it is retired. Noobservatory.bioDNS record changes as part of this ADR. - Nothing is deleted without Samir's explicit yes. That covers the Railway Observatory production
project, its database credential, the
observatory.biorecords (including_railway-verify), and the Railway Observatory staging project after its 7-day window. Retirement is a separate step Samir starts, after the handoff. - Until retirement, production still runs on the leaked host. Its Railway env holds exposed values. Recommendation: rotate its provider keys in place (OpenAI, OpenRouter, Resend, intake secret) so the exposed ones stop working, even though the app is leaving. Samir's call.
- Cost drops to one
starterinstance on Render ($7/mo plus the Pro share).
What stays in Convex
Nothing about Convex moves. Convex functions, data, file storage, Exo Key (Exo ID) and Convex Auth,
and every Convex-side secret stay in the Convex deployment env exactly as ADR 0014's migration table
says: BIRD_API_KEY, EXO_ID_PRIVATE_KEY, AUTH_PRIVATE_KEY, AUTH_*_CLIENT_SECRET,
ARCH_ROUTER_KEY, ARTIFICIAL_ANALYSIS_API_KEY, EXO_AI_BROKER_TOKEN, EXO_INTAKE_SECRET_*,
EXO_VERIFY_TOKEN (verifier side), EXO_GATEWAY_CLIENT_SECRET_SHA256. Convex pushes keep running
from GitHub Actions, never from a Render build. The only Convex edits the move needs are
non-secret URLs: ARCH_ROUTER_URL and any *_STAGING_ORIGIN or app origin Convex calls.
Postgres and Neon
Neon is the Postgres home: it is host-independent, branches per environment, and Cosmograph already uses it. No Render Postgres.
| Database | Today | Move |
|---|---|---|
| Cosmograph prod + staging | Neon | No data move. Reset both role passwords (exposed), store the new DATABASE_URLs in Cosmograph Production / Cosmograph Staging. If Neon IP allow is on, add Render's Virginia outbound IPs. |
| Universe staging + prod (+ RC) | Railway Postgres | New Neon project with staging and production branches. Freeze writes; pg_dump --format=custom from Railway (temporary TCP proxy or railway connect), pg_restore --no-owner into Neon; compare row counts per table; switch DATABASE_URL in the vault; unfreeze. RC is dropped. |
| Observatory | DATABASE_URL set on Railway (legacy data backend) | Staging: confirm the Convex backend is live and Postgres unread; if so do not migrate. Production stays on Railway until retirement. Delete the credential only with Samir's yes. |
| Construct | Legacy Postgres/Payload vars on Railway | Not migrated (Convex is the content authority). Delete after confirming production no longer reads them. |
Old exobase Neon project | Neon, Payload content | Delete (deletion already pending Samir) or reset its password. |
| Hadron/Dropship Valkey | Railway | If it is only a cache, drop it; otherwise Render Key Value. Decide at that step. |
DNS cutover at Spaceship
All six domains are hosted at Spaceship: exo.now, observatory.bio, cosmograph.space,
construct.page, interspace.ventures, moonshot.supply. Today: exo.now apex A 69.46.46.38
(Railway), www CNAME vlc5968k.up.railway.app, staging CNAME jai6l6x5.up.railway.app;
_railway-verify TXT on exo.now, staging.exo.now and observatory.bio.
Hostnames that move: staging.exo.now, exo.now + www, cosmograph.space, construct.page,
interspace.ventures (last). observatory.bio does not move (Observatory is staging only);
moonshot.supply waits for the parallel track.
Per hostname, with Samir's approval at each flip:
- T-24 h: record the current value of every record you will touch (screenshot or export), then lower its TTL to 300 s.
- Add the domain in Render (dashboard service -> Settings -> Custom Domains, or
POST /v1/services/{serviceId}/custom-domains). Render shows the target. - Check CAA and AAAA: if the domain has CAA records, add
issueandissuewildforletsencrypt.organdpki.goog; remove any AAAA records (Render is IPv4) (render.com/docs/custom-domains). Adding an apex in Render also addswwwas a redirect to it, and the reverse. - Flip at Spaceship:
- Subdomain (
staging,www): replace the Railway CNAME with CNAME<service>.onrender.com. - Apex: use ALIAS/ANAME
<service>.onrender.comif Spaceship offers it for the zone; otherwise replace A69.46.46.38with A216.24.57.1.
- Subdomain (
- Verify in Render (
POST /v1/services/{serviceId}/custom-domains/{id}/verify), wait for the certificate, then run the smoke tests against the real hostname. - Leave
_railway-verifyTXT records in place during the soak (harmless; needed for rollback), delete them when the Railway project is deleted. - Raise TTLs back to 3600 s after 24 h green.
Interspace (interspace.ventures): zero-downtime cutover, last
Interspace (interspace.ventures) is served by the root Construct app (not services/interspace, which is staging-only) and must stay up. The rule: both origins serve the site
for the whole flip, so whichever answer a resolver holds, it gets a working page.
- D0 (Samir's yes): export every
interspace.venturesrecord. Lower the TTL of the apex andwwwrecords to 300 s, at least 24 h before the flip. Touch nothing else: MX, SPF, DKIM, DMARC and site-verification TXT records stay exactly as they are. - D1-D2: Construct production is live on Render and
construct.codeshas been green for at least 12 h. Addinterspace.ventures(Render addswww) as a custom domain on the Render Construct production service. Add CAA entries forletsencrypt.organdpki.googif the zone has CAA. Remove AAAA records only at flip time, and only if any exist. - Verify before flipping: on the
onrender.comURL, check the Interspace routes Construct serves for that host, e.g.curl -fsS -H 'Host: interspace.ventures' https://<construct-prod>.onrender.com/and the same for the key pages and/api/health. Compare with the live Railway page (status, title, canonical links, assets). Construct's host routing must accept theonrender.comhost or theHostoverride; if it does not, stop and fix that before the flip. - D2 or D3, ~06:00 ET, flip: apex to ALIAS/ANAME
<construct-prod>.onrender.com(or A216.24.57.1),wwwto CNAME<construct-prod>.onrender.com. Keep Railway's Construct running and servinginterspace.venturesthroughout. Immediately press Verify in Render and flush the Google and Cloudflare public DNS caches. - Watch for 30 minutes:
curlthe real hostname every minute from two networks; check the certificate issuer and expiry; check/api/health. - Known gap, unverified: Render documents issuing the TLS certificate only after DNS verifies,
so a resolver that picks up the new record in the first minutes may see a TLS error or a 502.
On D0, ask Render support whether they can pre-issue the certificate (as they do for
wildcard domains via an
_acme-challengeCNAME). If they cannot, the 300 s TTL and an early-morning flip keep the window to minutes; if the certificate is not issued within 10 minutes, put the records back. (interspace.venturesalready has an_acme-challengeTXT of unknown origin; a Render_acme-challengeCNAME would need it removed first.) - After 24 h green: raise TTLs to 3600 s. Railway's Construct stays paused, not deleted, for 7 days.
Rollback
- Every service gets a 7-day rollback window after its own cutover, staging and production alike: the Railway service is paused, not deleted, env untouched (it is all compromised and rotated anyway, so it is only useful for the rollback itself). The window runs in the background and does not block the next app's cutover.
- To roll back a hostname: set the Spaceship record back to its recorded Railway value (A
69.46.46.38, CNAMEvlc5968k.up.railway.app/jai6l6x5.up.railway.app, and so on), resume the Railway service, and wait out the 300 s TTL. If the rotated secrets differ, point the Railway service at the same 1Password vault (it reads the same items through its own, freshly minted read-only token) rather than pasting values. universe.audiorolls back to its current Replit origin (A34.111.179.208,replit-verifyTXT kept), not to Railway, which never served it. Keep that Replit deployment untouched until Universe production has been green for 7 days.- Rolling back one app does not roll back the others. The exception is arch-router: if it rolls
back, set both Convex deployments'
ARCH_ROUTER_URLback to the Railway instance. - Interspace rolls back the same way, and stays rollback-ready longest because Railway's Construct keeps serving it until the day after its flip.
- A Render deploy that fails its health check never takes traffic; the previous deploy keeps serving. Production also fails closed when 1Password cannot be read.
- Nothing is deleted without Samir's explicit yes: no Railway project, database, credential or DNS record, after 7 green days or ever. Observatory production is not on this clock at all; it is retired only after the handoff (see "Observatory").
Parallel track: repos for Moonshot, Hadron and Dropship
Render builds only from a git repo. Three apps have none it can use, so they run alongside the main week and never block it:
| App | Today | Needs |
|---|---|---|
| Moonshot | a local folder deployed with railway up | a new GitHub repo in Interspace-Ventures, the folder pushed to it (after a secret scan), then a Blueprint |
| Hadron | no GitHub repo | a new GitHub repo, then a Blueprint |
| Dropship | code moving into Hadron | no repo of its own if the move into Hadron lands first; otherwise its own repo until it does |
Samir must OK creating each repo (name, visibility). Nobody creates a repo, a Render service or a
DNS record for these on their own. Each moves to Render staging whenever its repo exists, and stays
on Railway until then; moonshot.supply does not change in this ADR's week.
How each app receives its secrets
One rule everywhere: the host stores one read-only, vault-scoped bootstrap token per app per
environment, as Render sync: false. Nothing else secret lives in Render env, in render.yaml, or in
a build. Builds need no secret (and on Docker builds there is no OIDC anyway); never declare a Docker
ARG for a secret. PR previews inherit staging's token and vault, never production's.
| App | Vaults (read-only service account each) | How the process gets values |
|---|---|---|
| EXO | Exo Staging, Exo Production | src/lib/exo-secure in-process at boot, EXO_SECURE_SOURCE=auto (resolves to the service account on Render) |
| arch-router | Arch Router | entrypoint op run -- /opt/llama/llama-server … with LLAMA_API_KEY=op://Arch Router/llama/credential; values exist only in the child process |
| Observatory | Observatory Staging only (production stays on Railway until retired) | copy of the Exo Secure loader (port PR in the observatory repo, D0); OpenAI key is a candidate for Render's managed OpenAI OIDC later |
| Cosmograph | Cosmograph Staging, Cosmograph Production | loader copy in api-server, or op run wrapper if the server is not easily patched |
| Universe | Universe Staging, Universe Production | loader copy or op run wrapper |
| Construct (+ Interspace) | Construct Staging, Construct Production | per #406: loader port, one token per env |
| Hadron (incl. Dropship), Moonshot | Hadron Staging, Moonshot Staging | op run wrapper until they adopt the loader; needs a repo first (parallel track) |
| Convex (all apps) | n/a | Convex deployment env, unchanged |
If pilot test #1 ever passes, that app's bootstrap changes from
OP_SERVICE_ACCOUNT_TOKEN to OP_INTEGRATION_KEY + OP_WORKLOAD_ID + EXO_SECURE_ENVIRONMENT_ID
with EXO_SECURE_SOURCE=broker (or auto), and its secrets move from vault items to a 1Password
Environment. OP_INTEGRATION_KEY is still a stored secret, but it is useless without a fresh token
from the host's issuer that matches 1Password's trust policy.
1Password for Agents
1Password for Agents, launched 2026-10-22, lets a personal AI agent ask to use a login from Samir's vault; he approves, and 1Password fills the sign-in form without the agent ever seeing the password. It is free on Individual and Families, so it costs nothing to turn on. It is about people's sign-ins, not servers: it does not hand secrets to a running workload, so it replaces neither the service-account token nor the broker. Where it helps here is the migration itself: the agent doing Render, Spaceship and Neon dashboard steps can sign in with Samir's approval instead of a saved password, which shrinks what an agent session could leak. Treat it as an operator-side improvement, separate from this decision.
Consequences
- Railway leaves the critical path in 2-3 days from D0 (all staging on D0, productions D1-D2, Interspace D2-D3, last). Railway projects are deleted, with Samir's yes, no sooner than 7 days after each service's cutover. Observatory production stays on Railway until it is retired after the handoff.
- Hosting costs about $217/mo at the sizes above (about $145/mo lean).
- The stored-secret risk shrinks (one read-only, vault-scoped, freshly minted token per app env) but does not reach zero on Render.
EXO_SECURE_SOURCEgainsbrokerandauto; the default staysenvuntil each app's cutover setsauto.- EXO Verify fails any
render.yamlthat holds a secret value;OP_INTEGRATION_KEY,OP_WORKLOAD_ID,EXO_SECURE_ENVIRONMENT_IDandEXO_SECURE_OIDC_TOKEN_FILEjoin the platform-env allow list (the first is a bootstrap secret, the rest are identifiers).
Open items for the owner
One batched, numbered list with a link per item: render-cutover/approval-list.md. In short: rotate the exposed keys before reconnecting anything; Render GitHub App and API key; the 10 vaults and their read-only service accounts; merging #225, #227 and the per-repo render.yaml PRs; Convex device approvals; the DNS changes by day (D0 TTLs + staging, D1 EXO and Construct, D2 Cosmograph and Universe, D2-D3 Interspace); the extra Construct domains; Render's custom-domain allowance; the Moonshot and Hadron repos; and, later, each deletion (Railway after its 7 days, Observatory production after the handoff). Still open beyond that list: Railway's last invoice (break-even), and 1Password Business only if pilot test #1 ever passes.
Source: docs/adr/0022-leave-railway-for-render-and-1password-broker.md