Files
rfc-app/README.md
T
Ben Stull 1b0968a9a2 Slice 5: graduation per §13
The §13.3 transactional sequence flips a super-draft to active —
five steps with paired undoes, an in-process orchestrator fed by
an asyncio.Queue, the §17 SSE endpoint streaming step transitions
to the dialog. Each step is a new bot primitive that logs an
`actions` row, bracketed by `graduate_start` / `graduate_complete`
for the linkable audit sequence. Rollback runs the undoes in
reverse from the last completed step; merge_pr has no undo by
design per §13.5.

The §9.8 precondition gate is enforced server-side at the top of
POST /graduate so the §13.3 rollback complexity does not grow.
The §13.4 chat migration is a database semantic no-op — the
(slug, branch_name='main') threads keep their identity, only the
interpretation changes. The §9.8 pre-graduation history surfaces
via a new _is_meta_target(rfc, branch) dispatch helper and lands
as pre_graduation_history on /main.

§13.1 claim flow landed alongside since it's the prerequisite for
non-admin graduation — bot.open_claim_pr plus broadening
api_prs._require_pr to accept meta_claim.

45/45 tests green; ten new integration tests cover the validator,
the §9.8 precondition refusal, happy path with audit verification,
mid-sequence rollback at steps 2 and 3, concurrent refusal,
chat-survives-without-data-movement, pre-graduation history, and
the §13.1 claim PR cycle.

SPEC.md §19.1 rewritten for Slice 6 (notifications); §19.2 grew
four candidates surfaced during the slice.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 21:52:29 -07:00

242 lines
9.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# RFC App
A single-process FastAPI + SQLite + React + Vite + Tiptap app that
materializes the Wiggleverse RFC framework specified in
[`SPEC.md`](./SPEC.md). The framework's mission lives in
[`PHILOSOPHY.md`](./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`](./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.5 `On-behalf-of:` trailer
and records a row in the `actions` audit 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 in `users`, 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:
```sh
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
```sh
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:
```sh
cd backend
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt
```
Frontend:
```sh
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:
```sh
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:
```sh
# Terminal 1 — backend
cd backend
.venv/bin/uvicorn app.main:app --reload --port 8000
```
```sh
# 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 the build lets you do so far
Slices 15 are shipped. End-to-end paths the app supports today:
- **Propose → idea PR → merge → super-draft** (Slice 1, §9.1–§9.3).
- **Super-draft body editing** via meta-repo edit branches, with AI
participation, the change-card panel, manual flushes, threads,
flags, and DiffView (Slice 4, §9.5–§9.7 + §8 inherited).
- **The §8 active-RFC view** in full: per-branch chat, AI
participation through the `<change>` protocol, accept / decline /
edit, manual-edit flushes, sub-threads, flags, DiffView (Slice 2,
§8 in full).
- **The §10 PR flow** against both per-RFC repos and meta-repo edit
branches: open, AI-drafted title and description, the §10.3
review page with the per-user seen-cursor, review threads,
merge, withdraw, the §10.9 conflict-replay path (Slice 3 + Slice 4's
routing-collapse extension, §10 in full).
- **§13 graduation** with the three-field dialog, the precondition
popover for blocking body-edit PRs, the SSE-streamed five-step
sequence, rollback on mid-sequence failure, and the §9.8
pre-graduation history affordance on the new RFC view (Slice 5,
§13 in full).
- **§13.1 ownership claim** as a meta-repo PR adding the claimant
to the entry's `owners:` field; admin/owner merges the PR (Slice 5).
This exercises the §4 cache (webhook + reconciler), the §6
permission model in full, the §1 bot wrapper (every Git write goes
through it, every commit and PR carries the `On-behalf-of:`
trailer), and the §17 routing-collapse rule that lets active and
super-draft surfaces share their endpoints.
Out of scope for the slices shipped so far: notifications (Slice 6,
§15), landing-page and `/philosophy` chrome polish (Slice 7, §14),
the §12 30/90 branch-hygiene timers (Slice 8). The full slicing
plan and the next slice's brief live in
[`docs/DEV.md`](./docs/DEV.md).
## Verifying it worked
After bring-up:
- `http://localhost:8000/docs` lists the API routes the build session
has wired so far.
- `sqlite3 backend/data/rfc-app.db .schema` shows the §5 schema.
- `gitea ls /api/v1/repos/wiggleverse/meta/contents/rfcs` after a
proposal merges should show one new `<slug>.md`.
## Seeding an active RFC for §8 testing
With Slice 5 shipped, the `/graduate` flow in the app is the
canonical path from super-draft to active. The
[`scripts/seed_test_rfc.py`](./scripts/seed_test_rfc.py) shortcut is
still around for dev sessions that want an active RFC without
running the §9.1 propose flow and the §13 graduation dialog by
hand. Sign in once via OAuth so a `users` row exists, then:
```sh
cd backend && .venv/bin/python ../scripts/seed_test_rfc.py \
--slug open-human-model \
--title "Open Human Model"
```
The script creates `wiggleverse/rfc-NNNN-<slug>`, seeds `RFC.md` on
`main`, registers the webhook, and graduates the meta entry as a
bootstrap-only direct write. The §8 surface at `/rfc/<slug>` then
has something real to render.
## 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_URL` exactly, including the protocol and port.
- **The bot can't merge.** The bot needs Maintainer or Owner on the
meta repo (membership in `GITEA_ORG` as Owner gives it both).
## Where to read further
- [`SPEC.md`](./SPEC.md) — the binding contract. Every load-bearing
decision is there.
- [`PHILOSOPHY.md`](./PHILOSOPHY.md) — why this framework exists.
The spec's decisions answer to it.
- [`docs/DEV.md`](./docs/DEV.md) — the build's slicing plan, the
current state, and the next slice's brief.
- [`deploy/DEPLOY.md`](./deploy/DEPLOY.md) — single-host production
deployment behind nginx + Let's Encrypt.