SPEC, DEV docs, and code comments still talked about the codebase as a rewrite-in-progress against an external prototype. With v1 shipped the framing reads oddly — it implies code is provisional when it's the production thing. Recast §18 as "the technical stack," strip "carryover from the prototype" comments across backend (api.py, chat.py, providers.py) and frontend (DiffView, PromptBar, SelectionTooltip, modelStyles), and rework SPEC §1 / §18 to introduce OHM up front rather than as a follow-on to a prototype reference. Also: - RUNBOOK: bump Python prereq to 3.11+ to match the production VM (was 3.13). - Remove IMPLEMENTATION-PROMPT.md — the original implementation brief is no longer load-bearing. - Add deploy/DEPLOY-NEW-SESSION-PROMPT.md as the durable deploy-handoff prompt for new sessions.
12 KiB
RFC App — Deployment Reference & New-Session Prompt
Use this document as:
- A reference for the current
rfc.wiggleverse.orgdeployment - A prompt to paste into a new Claude session to deploy a new version
What This App Is
The RFC App is a single-process FastAPI + SQLite + React + Vite + Tiptap web application that hosts the Wiggleverse RFC framework — a platform for proposing, discussing, editing, and graduating formal RFCs (Requests for Comments) that define vocabulary for digital representations of humans. It is the primary interface for the Open Human Model (OHM) working group.
The v1 build is complete (8 slices shipped, 125 passing integration tests). New sessions extend it by picking from the §19.2 backlog.
Infrastructure Overview
Host
The RFC app runs on its own dedicated GCP VM in a separate project from the Gitea VM. The two coexist under wiggleverse.org but are otherwise unrelated infrastructure.
| Property | Value |
|---|---|
| GCP project | wiggleverse-rfc |
| VM name | rfc-app |
| VM type | e2-small |
| Zone | us-central1-a |
| OS | Debian 12 (bookworm) |
| Static IP | 34.132.29.41 |
| Linux user (OS Login) | benstull |
For reference, the separate Gitea VM is wiggleverse project / gitea VM / 34.55.46.221.
DNS
| Record | Type | Value | Proxy |
|---|---|---|---|
rfc.wiggleverse.org |
A | 34.132.29.41 | DNS-only (gray cloud) |
_dmarc.wiggleverse.org |
TXT | v=DMARC1; p=none; rua=mailto:ben@wiggleverse.org |
n/a |
Note:
rfc.wiggleverse.orguses Let's Encrypt via certbot directly on the VM. Keep the A record DNS only (gray cloud) — Cloudflare Flexible SSL would conflict with certbot.
SPF (v=spf1 include:_spf.google.com ~all) and DKIM (google._domainkey) for wiggleverse.org are already in place via Workspace.
Software Stack
| Component | Details |
|---|---|
| Backend | Python 3.11, FastAPI, uvicorn (single process) |
| Database | SQLite in WAL mode at /opt/rfc-app/backend/data/rfc-app.db |
| Frontend | React 19, Vite 8, Tiptap 3, React Router 7 |
| Web server | nginx — serves frontend/dist/ as static SPA, proxies /api/ and /auth/ to uvicorn on 127.0.0.1:8000 |
| Process manager | systemd unit rfc-app.service, runs as rfc-app system user |
| TLS | Let's Encrypt via certbot |
| Git backend | Gitea at git.wiggleverse.org, bot service account rfc-bot |
Google Workspace SMTP relay (smtp-relay.gmail.com:587), AUTH'd as ben@wiggleverse.org, From notifications@wiggleverse.org |
Key Paths on the VM
| Path | Contents |
|---|---|
/opt/rfc-app/ |
App root (owned by rfc-app user) |
/opt/rfc-app/backend/.env |
All secrets and config (mode 0600) |
/opt/rfc-app/backend/data/rfc-app.db |
SQLite database |
/opt/rfc-app/frontend/dist/ |
Built React SPA (served by nginx) |
/etc/nginx/sites-available/rfc.wiggleverse.org |
nginx vhost config |
/etc/systemd/system/rfc-app.service |
systemd unit |
Architecture Invariants
- The bot is the only Git writer. Every commit, branch, and PR flows through
backend/app/bot.py. No module calls Gitea's write API directly. Every action is audited in theactionstable with anOn-behalf-of:commit trailer. - Git is truth; SQLite is the cache. The
cached_*tables are written only by the Gitea webhook receiver or the 5-minute background reconciler. User actions trigger Git ops; the cache follows. - Single process, single SQLite file. Never set
--workers > 1on uvicorn. If scale is needed, the spec calls for a Postgres migration first.
Gitea Setup (one-time)
These are already done for rfc.wiggleverse.org. Document here for replication.
Bot service account
Created in Gitea as rfc-bot. Token scopes: write:repository, write:user, write:admin. Token stored in .env as GITEA_BOT_TOKEN.
Org
wiggleverse org exists in Gitea. rfc-bot is an Owner of the org.
Meta repo
wiggleverse/meta — seeded by scripts/seed_meta_repo.py. Contains PHILOSOPHY.md, README.md, CONTRIBUTING.md, and rfcs/ directory. Gitea webhook registered to https://rfc.wiggleverse.org/api/webhooks/gitea.
OAuth2 app
Registered in Gitea Site Administration → Integrations → OAuth2 Applications:
- Name:
RFC App - Redirect URI:
https://rfc.wiggleverse.org/auth/callback - Client ID and secret stored in
.env
Workspace Setup (one-time, for email)
Email sends via Google Workspace SMTP relay. Already configured for wiggleverse.org; document here for replication.
- Admin console → Apps → Google Workspace → Gmail → Routing → SMTP relay service with a rule named
RFC App:- Allowed senders: "Only addresses in my domains"
- Authentication: Require SMTP Authentication ☑ (AUTH path — current setup), OR allowlist 34.132.29.41 (no-AUTH alternative)
- Encryption: Require TLS encryption ☑
- Google Group
notifications@wiggleverse.org(Access type: Custom, External posters allowed so reply mail lands; member delivery set to "No email"). - App password generated on
ben@wiggleverse.org. Critical: the Workspace account here is linked to a personalbenstull@gmail.comGoogle account; when generating an app password, watch the avatar in the top-right of myaccount.google.com — it silently switches back to the personal account, and consumer-Gmail app passwords are rejected by Workspace's SMTP relay with535 5.7.8 BadCredentials. Confirm the avatar shows the Workspace account every time before generating. - DMARC TXT record at
_dmarc.wiggleverse.org(added; see DNS table above).
Environment Variables
Full .env for production (file lives at /opt/rfc-app/backend/.env, mode 0600):
# Gitea
GITEA_URL=https://git.wiggleverse.org
GITEA_BOT_USER=rfc-bot
GITEA_BOT_TOKEN=<bot token from Gitea>
GITEA_ORG=wiggleverse
META_REPO=meta
# OAuth
OAUTH_CLIENT_ID=<from Gitea OAuth app>
OAUTH_CLIENT_SECRET=<from Gitea OAuth app>
# App
APP_URL=https://rfc.wiggleverse.org
SECRET_KEY=<openssl rand -hex 32>
DATABASE_PATH=/opt/rfc-app/backend/data/rfc-app.db
OWNER_GITEA_LOGIN=ben.stull
GITEA_WEBHOOK_SECRET=<openssl rand -hex 32>
# LLM
ENABLED_MODELS=claude
ANTHROPIC_API_KEY=<key>
# Email — Google Workspace SMTP relay (§15.4)
# Strip the spaces Google shows in the 16-char app password (or quote
# the value) — sourced as shell, spaces split the value.
# Alternative: leave SMTP_USER/SMTP_PASSWORD empty and switch the relay
# rule to IP-based; the app skips SMTP AUTH when SMTP_USER is empty.
SMTP_HOST=smtp-relay.gmail.com
SMTP_PORT=587
SMTP_USER=ben@wiggleverse.org
SMTP_PASSWORD=<app password, no spaces>
SMTP_STARTTLS=1
EMAIL_FROM=notifications@wiggleverse.org
EMAIL_FROM_NAME=Wiggleverse
EMAIL_ENABLED=1
EMAIL_BUNDLE_THRESHOLD=5
WEBHOOK_EMAIL_BOUNCE_SECRET=
# Hygiene scheduler
HYGIENE_TICK_SECONDS=3600
Deploying a New Version
SSH into the VM:
gcloud compute ssh rfc-app --zone=us-central1-a --project=wiggleverse-rfc
Pull the latest code, reinstall deps, restart:
sudo -u rfc-app git -C /opt/rfc-app pull
sudo -u rfc-app /opt/rfc-app/backend/.venv/bin/pip install \
-r /opt/rfc-app/backend/requirements.txt
sudo systemctl restart rfc-app
For frontend changes, build on the VM directly (Node 20+ is already there):
cd /opt/rfc-app/frontend && sudo -u rfc-app npm install
sudo -u rfc-app npm run build
The output lands in /opt/rfc-app/frontend/dist/ owned by rfc-app — nginx serves it directly, no copy step needed.
(Building locally and gcloud compute scp-ing the dist also works. Plain rsync -e ssh from the Mac fails because OS Login uses short-lived SSH certs that only the gcloud wrapper can mint interactively.)
Schema migrations run automatically on restart (append-only, safe to re-run).
First-Time Deployment (new server)
1. Add DNS record
Add rfc.wiggleverse.org → 34.132.29.41 as an A record in Cloudflare, DNS only (gray cloud). Do not proxy — certbot needs to reach the VM directly.
2. Host prep
sudo useradd --system --shell /usr/sbin/nologin --home-dir /opt/rfc-app rfc-app
sudo mkdir -p /opt/rfc-app
sudo chown rfc-app:rfc-app /opt/rfc-app
sudo -u rfc-app git clone https://git.wiggleverse.org/ben.stull/rfc-app.git /opt/rfc-app
3. Python venv
sudo -u rfc-app python3 -m venv /opt/rfc-app/backend/.venv
sudo -u rfc-app /opt/rfc-app/backend/.venv/bin/pip install \
-r /opt/rfc-app/backend/requirements.txt
4. Write .env
sudo -u rfc-app cp /opt/rfc-app/backend/.env.example /opt/rfc-app/backend/.env
sudoedit /opt/rfc-app/backend/.env
sudo chmod 600 /opt/rfc-app/backend/.env
sudo chown rfc-app:rfc-app /opt/rfc-app/backend/.env
5. Seed meta repo
sudo -u rfc-app -H bash -c \
'cd /opt/rfc-app/backend && .venv/bin/python ../scripts/seed_meta_repo.py'
6. Build the frontend (on the VM)
cd /opt/rfc-app/frontend && sudo -u rfc-app npm install
sudo -u rfc-app npm run build
7. nginx
sudo cp /opt/rfc-app/deploy/nginx/rfc.wiggleverse.org.conf \
/etc/nginx/sites-available/rfc.wiggleverse.org
sudo ln -s /etc/nginx/sites-available/rfc.wiggleverse.org \
/etc/nginx/sites-enabled/
sudo usermod -a -G rfc-app www-data
sudo chmod -R g+rX /opt/rfc-app/frontend/dist
sudo nginx -t && sudo systemctl reload nginx
8. Let's Encrypt
sudo certbot --nginx -d rfc.wiggleverse.org
9. systemd
sudo cp /opt/rfc-app/deploy/systemd/rfc-app.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now rfc-app
sudo systemctl status rfc-app
10. Smoke test
Visit https://rfc.wiggleverse.org:
- Landing page renders with sign-in button
- Sign in with Gitea OAuth → catalog loads
+ Propose New RFCopens the propose modal/adminloads the four-tab home base/settings/notificationsrenders all five sections
Day-2 Operations
Logs
sudo journalctl -u rfc-app -f
sudo journalctl -u rfc-app -p err
Database backup
sqlite3 /opt/rfc-app/backend/data/rfc-app.db \
".backup /opt/rfc-app/backend/data/backup-$(date +%F).db"
Restart
sudo systemctl restart rfc-app
Rollback
sudo -u rfc-app git -C /opt/rfc-app checkout <prior-commit>
sudo systemctl restart rfc-app
New Session Prompt
Paste the following into a new Claude session to continue development:
I'm working on the Wiggleverse RFC App — a FastAPI + SQLite + React + Vite application deployed at
rfc.wiggleverse.orgon a GCP e2-small VM (rfc-appin thewiggleverse-rfcproject; separate from thegiteaVM inwiggleversethat runs Gitea atgit.wiggleverse.org). The app is the primary interface for the Open Human Model (OHM) RFC working group.Stack:
- Backend: Python 3.11, FastAPI, uvicorn (single process), SQLite WAL mode
- Frontend: React 19, Vite 8, Tiptap 3 (rich text editor), React Router 7
- Infrastructure: nginx (static SPA + API proxy), systemd, Let's Encrypt TLS
- Git backend: Gitea at
git.wiggleverse.org, bot service accountrfc-botis the only Git writer- Email: Google Workspace SMTP relay (
smtp-relay.gmail.com), Fromnotifications@wiggleverse.orgKey invariants:
- The bot (
backend/app/bot.py) is the only Git writer — every commit carries anOn-behalf-of:trailer and audits to theactionstable- Git is truth;
cached_*SQLite tables are written only by the Gitea webhook receiver or the 5-minute background reconciler- Single process, single SQLite file — never use multiple uvicorn workers
- 10 append-only schema migrations in
backend/migrations/Deployment:
- SSH:
gcloud compute ssh rfc-app --zone=us-central1-a --project=wiggleverse-rfc- Code at
/opt/rfc-app/on the VM, owned byrfc-appsystem user.envat/opt/rfc-app/backend/.env(mode 0600)- Frontend built on the VM (Node 20 is installed there) with
npm run builddirectly into/opt/rfc-app/frontend/dist/- Restart to deploy:
sudo systemctl restart rfc-app- Migrations run automatically on startup
Source is at
~/git/rfc-app/.The v1 build is complete (8 slices, 125 passing integration tests). I want to [DESCRIBE WHAT YOU WANT TO DO — e.g. "add X feature from the §19.2 backlog" or "deploy the current version to the VM"].