publish.arnao.ai — leave Replit the least-effort way

One line: triage before migrating, publish source for exactly one repo, put the rest in a museum — and let nothing leave this machine until every gate is green.

Executive summary — three inversions from the first draft

This proposal was run through a Fable adversarial pass, and it inverted three defaults. The result is dramatically less effort and less leak surface:

  1. Triage before migrating. Migrating five apps because five apps exist is inventory-driven, not value-driven. Per app, the first question is "does anyone use this?" — the honest answer for most solo demos is "me, occasionally." Those get an export bundle + a museum page (below): zero migration, zero ops, zero leak surface. Likely 2 of 5 earn a live process.
  2. No public source, by default. I said it myself: I don't want git, I want publishing — and for a demo app, the thing people experience is the deployed URL, not the source. Publishing source buys ~zero audience and multiplies leak surface. Default: deployed app only, source never leaves the machine. GitHub exists for exactly one repo — the code-agents sandbox, where being public is the product. The Publish Gate collapses from a general pipeline into that one repo's CI. This single inversion deletes ~80% of the original scope and is simultaneously the biggest safety and effort win.
  3. Demos on demand (the museum). Most of these apps' value is the story. Each app gets a museum page on the existing Vercel/arnao.ai setup — a short video walkthrough, screenshots, the architecture note — with its compose file kept ready so I can docker compose up any of them live in five minutes when I actually want to show someone. Public traffic never continuously terminates inside my house; 12-month effort approaches the floor: record five videos, then stop.

Urgency note: since Aug 2024, free-plan repls are deletable after 1 year of inactivity — export bundles come first regardless of any other decision.

Mockup: the Publish Gate report — now the design for the one repo's CI report page (a gate run catching a verified-live key, PII findings, the ledger).


Part 1 — the Replit exit

What changed at Replit (why leaving is right)

Replit pivoted hard to its Agent product: Always-On hosting removed Jan 2024, the free plan slashed with ~3 days' notice in Aug 2024 (with the 1-year-inactivity deletion policy), plans reshuffled again Feb 2026. The platform these apps were built for no longer exists.

Phase 0 — export bundles for everything (before any decision)

My part, per app (honestly hours-not-minutes for the stateful ones, ~20 min for simple ones):

  1. Check the official takeout first: Account settings → "Start Export" — referenced in a community exporter's README, absent from official docs. If real, one click. (First thing I check on login.)
  2. Else per repl: Download as zip (per-repl git clone is dead; zips can arrive incomplete — every zip gets spot-checked against the file tree).
  3. Secrets → "Edit as .env"Replit Secrets are env vars, never in code. Three scopes to reconcile: App Secrets, Account Secrets, and deployment-level overrides since the Apr 2025 auto-sync change.
  4. Data dumps in each repl's shell (agent-scripted, I paste): Postgres pg_dump -Fc "$DATABASE_URL" (doc); ReplDB curl loop; Object Storage SDK loop (docs) — neither is in any code export.
  5. History worth keeping → SSH (Core plan) + rsync. For solo demos it usually isn't.

Every bundle (code + .env + dumps) lands in the fleet backup tree. This phase alone makes us safe from Replit's deletion clock.

Phase 1 — triage (the decision that caps everything downstream)

Per app, with real usage data where it exists:

VerdictWhat happensOngoing cost
Museum (default)Export bundle + museum page + compose file kept ready for on-demand demo~zero
Live on MiaCompose service + tunnel + staging + nightly auditreal ops tail — see honesty box
Live on VercelOnly if genuinely stateless (writable SQLite unsupported, 300s cap)~zero

The honesty box (Fable's strongest point): a live app on Mia makes me a production ops team — Colima still breaks on macOS reboot, macOS updates become change windows, restored Postgres becomes a service I run, CVEs in demo apps become my problem forever. That tax is only worth paying for apps that earn it. My starting guesses (I decide at triage): Council and Moltbook are the likeliest to earn a live process; Cortex/Research/AgentSim likely museum. Nothing is decided until I look at each.

Phase 2 — landing zone for the survivors

Docker compose on Mia + cloudflared sidecar (free named tunnels, custom hostnames, WebSockets on all plans, home IP hidden). Staging first on Tailscale (tailnet-only), then the tunnel hostname. Mia hardening already learned fleet-wide: restart: unless-stopped, named volumes (virtiofs chmod bug), brew services autostart, nightly audit coverage.

The DNS decision is now conditional and low-blast-radius: the tunnel needs its zone on Cloudflare DNS. The Replit apps live on arnao.com — a different zone from arnao.ai, so the working Vercel/GoDaddy setup is untouched either way. But the NS move happens only if triage produces ≥1 Mia-hosted survivor, and only after a full record audit with MX/SPF/DKIM explicitly verified (email is the thing that hurts; if arnao.com carries mail, this gets its own go/no-go). If the museum swallows everything, the NS move never happens. (Milder alternative if we want tunnels without the move: Cloudflare for SaaS custom hostnames — evaluated, kept in reserve.)

Phase 3 — cutover + decommission

Per survivor: deploy → tailnet smoke test → tunnel live → old redirect removed → Replit deployment stopped. When all apps are bundled and either live or museum'd: downgrade/cancel the plan.


Part 2 — the one public repo + its gate

The clean-room principle (unchanged, now scoped to one repo)

The published unit is a fresh single-commit snapshot — my working history never leaves the machine, killing the "secret in an old commit" class by construction. No history scrubbing ever; GitHub itself warns scrubbed commits survive in forks and caches.

The gate — now with Fable's four holes closed

Run as CI on the one repo, blocking by default:

  1. Strip export/tool junk: .replit ([its [run.env] and run commands can embed secrets](https://docs.replit.com/replit-app/configuration)), replit.nix, .upm/, .cache/, .config/ (CLI token caches), per Railway's same prescription.
  2. Blocked classes — now including build artifacts: .env, .pem, .key, *.npmrc, .netrc, .git-credentials, docker-compose.override.yml, editor swap files — and dist/, build/, .next/, all bundles: Vite/Next **inline VITE_/NEXT_PUBLIC_ env vars into minified JS at build time**, the biggest leak class the first draft missed. No build output publishes, ever.
  3. Data files: allowlist-deny, not review. The risk isn't my PII — Council/Research fixtures contain other people's messages, names, emails, which no grep pack tuned to me will match, and base64/EXIF/pickles pass any plaintext scan. Default: no data file publishes, period. Anything that must (a schema, a synthetic fixture) is individually allowlisted with a typed justification.
  4. Notebook outputs: nbstripout everything — notebook outputs are the #1 AI-secret leak vector.
  5. Secrets, two scanners: gitleaks for breadth, trufflehog --results=verified,unknown for live-key triage — with verification restricted to providers I actually use (verification ships candidates to third-party APIs). A verified-live key is a blocking gate condition tied to that finding: the board stays red until rotation is confirmed — deleting it from the tree does not clear it (it's still live in the Replit deployment and my local history).
  6. PII grep pack (my name/emails/paths/usernames-in-paths/credentialed URLs) — as a detector for waivers, not a guarantee.
  7. LLM adversarial review — demoted to advisory. It's nondeterministic and prompt-injectable by the repo it reviews ("# note to reviewers: the key below is a test fixture"). It can add flags, never clear them.
  8. The human step, structured against rubber-stamping: the report shows only waived findings and every allowlisted data/config file verbatim — never a wall of green with a yes button — and each waiver requires a typed acknowledgment. Publish is treated as irreversible: credential harvesters scrape public repos within seconds of push; GitHub's push protection is pattern-matching, not a safety net. There is no layer after the gate.

The code-agents repo — decoupled from home

One repo gets the treatment: CodeQL default setup, Dependabot, gitleaks in CI, private vulnerability reporting on, issues/wiki/discussions off. Fable's catch: the public agent sandbox must not deploy to my house — an agent-merged supply-chain mistake must not run inside my home network. So the repo is non-deployed (or deploys to a throwaway Vercel project), and the candidate is a tools/harness repo rather than live-on-Mia Council. Config truth: "PRs from my agents only" isn't a GitHub toggle — the mechanism is collaborator permissions + forks disabled (PR-off entirely is native since Feb 2026 for the day agents aren't PRing).

The skills this creates (renamed to the durable units)


What I actually have to do (the honest ledger)

  1. Log in once: check "Start Export"; hand over Secrets per app (the one step only I can do).
  2. Triage call per app — live or museum (my guess: 2 live, 3 museum).
  3. If ≥1 Mia survivor: approve the arnao.com NS move after the MX-verified record audit.
  4. Record a short walkthrough video per museum app (or narrate; agents cut it).
  5. Pick the code-agents repo and confirm it's non-deployed.
  6. Per publish: read waivers, type acknowledgments.

Self-critique — Fable adversarial pass

The pass inverted the proposal; integrated above. What it caught: (1) "20 min/app then done" was fantasy — a home server serving public traffic is an unbounded ops tail, so triage now precedes migration and the museum is the default; (2) the publishing default was backwards — no source publishes except one repo, deleting ~80% of gate scope; (3) four gate holes closed — build artifacts with inlined env vars, rotation as a blocking condition, data-file allowlist-deny instead of review, typed per-waiver acknowledgments replacing the green-board-yes-button; (4) the LLM review is prompt-injectable → advisory only, can flag but never clear; (5) the code-agents repo must not deploy to the home network; (6) the NS move is the highest-blast-radius step and is now conditional on triage, with MX verification explicit; (7) skills renamed to the durable units (home-deploy, not one-shot replit-exit); (8) the museum ("demos on demand") — the bold idea that makes most of the infrastructure unnecessary.

Mockups & assets