1b0968a9a2
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>
242 lines
9.1 KiB
Markdown
242 lines
9.1 KiB
Markdown
# 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 1–5 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.
|