Slice 1: scaffolding + propose-to-super-draft vertical
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>
This commit is contained in:
@@ -0,0 +1,199 @@
|
||||
# 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 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`](./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`.
|
||||
|
||||
## 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.
|
||||
Reference in New Issue
Block a user