Brings the §1 bot wrapper, the §4 cache (webhook + reconciler), the §5 schema (six numbered migrations), Gitea OAuth + §6 user provisioning, the §7 catalog left pane, and the propose-to-merge vertical: propose modal opens an idea PR against the meta repo, an owner merges from the pending-idea view, the cache picks it up via webhook or reconciler sweep, and the catalog renders the new super-draft. Per §1 the bot is the only Git writer; every commit, branch creation, and PR merge carries the §6.5 On-behalf-of: trailer and an `actions` audit row. Per §4 the cache is never written from a user action — it's webhook+reconciler only. Covered by `backend/tests/test_propose_vertical.py` against an in-process Gitea simulator. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
7.2 KiB
RFC App
A single-process FastAPI + SQLite + React + Vite + Tiptap app that
materializes the Wiggleverse RFC framework specified in
SPEC.md. The framework's mission lives in
PHILOSOPHY.md; the spec is the binding contract;
this README is how to bring the app up against a local Gitea instance
and exercise the slice the build session has shipped so far.
The implementation is in progress. See docs/DEV.md
for the slicing plan and the current state.
What the app expects to talk to
- A Gitea instance at
GITEA_URL. The instance hosts the meta repository and (eventually) one repository per graduated RFC. - A bot service account in that Gitea, with a personal access
token in
GITEA_BOT_TOKEN. Per §1 the bot is the only writer in the system — every commit, branch, and PR the app produces flows through one wrapper that applies the §6.5On-behalf-of:trailer and records a row in theactionsaudit log. - An OAuth2 application registered against that Gitea, with the
callback URL set to
{APP_URL}/auth/callback. Real human users authenticate via Gitea OAuth (the §18 carryover); the app reads their Gitea profile, provisions a row inusers, and layers §6's app-owned permission model on top.
Local bring-up
The shortest path from a clean checkout to a working app is:
1. Stand up a local Gitea
Anything that exposes the Gitea REST API works. The fastest path is Docker:
docker run -d --name gitea \
-p 3000:3000 -p 222:22 \
-v gitea-data:/data \
gitea/gitea:1.21
Open http://localhost:3000, walk through the install wizard
(SQLite, default port), and create your owner-zero account.
2. Create the bot service account
In Gitea, sign in as your owner account and Site Administration →
User Accounts → Create User Account. Give it a name like rfc-bot
and an email. Then sign in as the bot, open Settings → Applications
→ Generate New Token, and grant it the write:repository,
write:user, and write:admin scopes (admin is needed because the
bot will create per-RFC repos on graduation; in v1 you can scope down
to repo and org if you want to defer admin until Slice 5).
Copy the token; you will paste it into .env.
3. Create the org that will host the meta repo
The seed script creates the meta repo inside an org. Create the org
(e.g. wiggleverse) in Gitea and add rfc-bot to it as an
Owner.
4. Register the OAuth2 application
In Gitea: Site Administration → Integrations → OAuth2 Applications
→ Create. Name it whatever you like, set the redirect URI to
http://localhost:8000/auth/callback. Copy the client id and client
secret — they go into .env.
5. Configure the app
cd backend
cp .env.example .env
$EDITOR .env # fill in every variable
Required values:
| Variable | What it is |
|---|---|
GITEA_URL |
Base URL of the Gitea instance (no trailing slash). |
GITEA_BOT_USER |
The bot account's login. |
GITEA_BOT_TOKEN |
The bot account's access token. |
GITEA_ORG |
The org that owns the meta repo. |
META_REPO |
The meta repo's name (default meta). |
OAUTH_CLIENT_ID |
From the OAuth app you registered. |
OAUTH_CLIENT_SECRET |
Likewise. |
APP_URL |
The URL the app is reachable at locally. |
SECRET_KEY |
A long random string for cookie signing. |
OWNER_GITEA_LOGIN |
Your owner-zero Gitea login — gets the owner role on first sign-in. |
GITEA_WEBHOOK_SECRET |
A shared secret for the §4.1 webhook signature. |
The LLM-provider settings (ENABLED_MODELS, ANTHROPIC_API_KEY,
etc.) are not exercised by Slice 1 but are wired through config.py
so the next slice can pick them up.
6. Install dependencies
Backend:
cd backend
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt
Frontend:
cd ../frontend
npm install
7. Seed the meta repo
The seed script creates wiggleverse/meta if it does not exist,
populates it with PHILOSOPHY.md, README.md, CONTRIBUTING.md,
the regenerate-index workflow placeholder, and an empty rfcs/
directory, and registers the Gitea webhook the app needs:
cd backend
.venv/bin/python ../scripts/seed_meta_repo.py
Re-running is safe — every step is upsert-shaped.
8. Run the app
In two terminals:
# Terminal 1 — backend
cd backend
.venv/bin/uvicorn app.main:app --reload --port 8000
# Terminal 2 — frontend
cd frontend
npm run dev
Open http://localhost:5173. Sign in with your owner-zero Gitea
account. The catalog should appear empty; the + Propose New RFC
button at the bottom opens the propose modal.
What slice 1 lets you do
End-to-end: propose a new RFC → an idea PR opens against the meta repo → an owner merges from the pending-idea view → the super-draft appears in the catalog → opening it renders the body.
This exercises the §4 cache (webhook + reconciler), the §6 permission
model (the owner-only merge button, the contributor-only propose
modal), the §1 bot wrapper (every Git write goes through it, every
commit and PR carries the On-behalf-of: trailer), and the §9
propose-merge-render path.
Out of scope for slice 1: the active-RFC view (§8), per-branch chat,
AI participation, the change-card panel, PRs against per-RFC repos,
graduation, notifications, the landing page's full polish. Those
slices are listed in docs/DEV.md.
Verifying it worked
After bring-up:
http://localhost:8000/docslists the API routes the build session has wired so far.sqlite3 backend/data/rfc-app.db .schemashows the §5 schema.gitea ls /api/v1/repos/wiggleverse/meta/contents/rfcsafter a proposal merges should show one new<slug>.md.
Troubleshooting
- The catalog stays empty after a merge. Check that the webhook
is reaching the app: the reconciler runs every five minutes and
will catch up, but a missing or misconfigured webhook is the most
common reason for sub-second freshness to fail. The seed script
registers the webhook for you; if you bring up Gitea on a different
host (e.g. a Codespace, a tunnel), re-run the seed against the new
APP_URL. - OAuth callback errors. The redirect URI in Gitea has to match
APP_URLexactly, including the protocol and port. - The bot can't merge. The bot needs Maintainer or Owner on the
meta repo (membership in
GITEA_ORGas Owner gives it both).
Where to read further
SPEC.md— the binding contract. Every load-bearing decision is there.PHILOSOPHY.md— why this framework exists. The spec's decisions answer to it.docs/DEV.md— the build's slicing plan, the current state, and the next slice's brief.