agents: the ordered runbook for standing the Gitea surface up

WORKFLOW-gitea.md said what the working surface is and why. It did not say how,
in what order, or how to know a step worked -- so it was a destination with no
route. This is the route.

Seven phases, each with a check, each marked 👤 human or 🤖 me:

  1 identities   two agent users, Write NOT Admin
  2 protection   main behind a PR + 1 approval -- BEFORE tokens exist
  3 tokens       three principals, three tokens, three files
  4 structure    labels and bundles, and deliberately NO Kanban board
  5 MCP          gitea-mcp v1.7.0, per-agent identity, user scope not .mcp.json
  6 items        migrate the live findings only -- not 1,227 historical lines
  7 restart      and verify the three things that must be true

Phase 1 leads because it is not hygiene: Gitea does not let a PR's author
approve it, so while an agent IS `fabi` either the human cannot approve its work
or it can approve its own. The review gate does not exist until the agents are
distinct people. (It also fixes 495 commits of agent work attributed to the
human's email.)

Phase 2's check is a real push and a real PR, not a reading of the settings
page. The reason protection lives in the server rather than in a brief is that
it should not depend on good behaviour -- so verifying it should not either.

Phase 5's install facts are checked, not remembered: gitea-mcp v1.7.0,
`gitea-mcp_Linux_x86_64.tar.gz`, `-t stdio -H <host>`, `GITEA_ACCESS_TOKEN`.
The `--tools` filter is flagged as an EXPERIMENT that might exclude the merge
tool as defence in depth -- explicitly not a substitute for phase 2.

Ends with what is still to build (propose-work, an attachment uploader,
gitea-verify, the wiki landing page) and a "what I have not verified" section:
the approve-your-own-PR behaviour, the --tools names, the Projects API, and the
Gitea version -- the API was unreachable from my sandbox three times running.
This commit is contained in:
MechaCat02
2026-09-04 16:25:10 +02:00
parent a1ac3fa4c1
commit 9652a5ad77
2 changed files with 234 additions and 0 deletions

230
docs/agents/GITEA-SETUP.md Normal file
View File

@@ -0,0 +1,230 @@
# Runbook: standing the Gitea working surface up
**For the human. Work top to bottom — later phases depend on earlier ones.**
[`WORKFLOW-gitea.md`](WORKFLOW-gitea.md) says *what* this is and why; this says
*how*, in order, with a check after each phase.
Steps are marked **👤 you** (a decision or a credential only you can make) or
**🤖 me** (I do it once you have unblocked it).
## Where things stand
*`main` carries the play-tested port work and the rewritten briefs (`cd3a81af`).
* ✅ The briefs already describe this workflow. **They describe tooling that does
not exist yet** — which is why the agents are stopped and must stay stopped
until Phase 7.
* ❌ Nothing exists on the instance: no agent users, no API tokens, no labels, no
branch protection, no MCP.
⚠️ **Do not start an agent before Phase 7.** Its brief will tell it to read
notifications and open issues, and it will have no tool that can.
---
## Phase 1 · Identities 👤
The agents currently push as `fabi`, using your credential. That is the defect
this phase fixes, and it is not cosmetic: **Gitea does not let the author of a
pull request approve it.** While an agent *is* you, either you cannot approve its
PR or it can approve its own — and there is no third possibility. The review gate
does not exist until the agents are distinct people.
Two more reasons, once you are there anyway: 495 commits of decoder work are
currently attributed to **your** email, so blame is wrong; and separate
identities mean revoking one agent does not touch the other or you.
**1.1 — Create two users.** Site Administration → Identity & Access → User
Accounts → *Create User Account*.
| | |
|---|---|
| usernames | `sylph-decoder`, `sylph-port` |
| email | anything you control and can tell apart — `you+decoder@…`, `you+port@…` |
| "require password change on first login" | **off** — they never log in interactively |
**1.2 — Add both to `fabi/Sylpheed` as collaborators.** Repo → Settings →
Collaborators → add each → permission **Write**.
🔴 **Write, not Admin.** Admin can edit branch protection, which would let an
agent remove the rule that stops it merging.
> **Check:** the repo's Collaborators list shows both, each reading `Write`.
---
## Phase 2 · Branch protection 👤
**Do this before the agents hold tokens**, so there is no window in which they
can push to `main`.
Repo → Settings → Branches → *Protected Branches* → add rule for `main`:
| setting | value | why |
|---|---|---|
| Enable Push | **off** | nothing reaches `main` except through a PR |
| Require approvals | **1** | the human gate, made native |
| Dismiss stale approvals | **on** | an approval must describe the code that merges |
| Block merge on rejected reviews | **on** | "changes requested" has to mean something |
> ### Check — and actually run it, do not assume it
>
> The whole point of putting this in protection rather than in a document is
> that it does not depend on anyone's good behaviour. So verify it the same way:
>
> 1. As `sylph-port`, push a throwaway branch and open a PR into `main`.
> 2. Confirm **no Merge button** is offered to that account.
> 3. Confirm **you** can approve it, and that *it* cannot approve itself.
> 4. Close the PR, delete the branch.
>
> If step 2 offers a Merge button, stop — the rest of this runbook assumes it
> does not.
---
## Phase 3 · Tokens 👤
Three principals, three tokens. Settings → Applications → *Generate New Token*
while logged in **as that user**.
| whose | scopes | goes in |
|---|---|---|
| **you** (`fabi`) | `write:issue`, `read:repository` | `~/.sylph-gitea-api-token` |
| `sylph-decoder` | `write:repository`, `write:issue`, `write:notification`, `read:user` | `~/.sylph-gitea-token-decoder` |
| `sylph-port` | same four | `~/.sylph-gitea-token-port` |
```bash
printf '%s\n' '<token>' > ~/.sylph-gitea-api-token && chmod 600 ~/.sylph-gitea-api-token
```
⚠️ **Never paste a token into chat.** The files are mounted read-only into the
containers, exactly like `~/.sylph-claude-token`.
📌 The existing `~/.sylph-git-credentials` is scoped `write:repository` and is
**refused by every issue endpoint** — verified, not assumed:
`required=[read:issue], token scope=write:repository`. It stays as it is; these
are additional.
> **Check:** `tools/gitea-setup --dry-run` prints "would create …" rather than a
> scope error.
---
## Phase 4 · Labels and bundles 🤖
```bash
tools/gitea-setup --dry-run # read it first
tools/gitea-setup # idempotent; safe to re-run
```
Creates 12 labels (the `state/*` set, `agent/*`, `kind/*`) and 4 milestones
(Menus, Title screen, Graphics pipeline, Infrastructure).
**No Kanban board yet, on purpose.** Gitea's board does not follow labels, so it
would be a second copy of the state to keep in sync by hand — which is the exact
failure that produced a 1,227-line `BLOCKED.md`. **Labels are the truth**; a
saved issue filter gives the same view for nothing. Add a board later if the
filter turns out to be insufficient.
> **Check:** the Issues page offers the `state/*` labels, and Milestones lists
> the four bundles.
---
## Phase 5 · The MCP server 🤖
`gitea-mcp` **v1.7.0**`gitea-mcp_Linux_x86_64.tar.gz` from
`https://gitea.com/gitea/gitea-mcp/releases`. Confirmed flags: `-t stdio`,
`-H <host>`, token via `GITEA_ACCESS_TOKEN`.
Three edits per image, which I make:
1. **`Dockerfile`** — fetch and unpack the release binary to `/usr/local/bin`,
pinned to v1.7.0 with a checksum.
2. **`entrypoint.sh`** — register it for that agent's own identity:
```bash
claude mcp add -s user gitea -e "GITEA_ACCESS_TOKEN=$(cat "$GITEA_TOKEN_FILE")" \
-- gitea-mcp -t stdio -H https://git.mc02.dev
```
User scope, not a committed `.mcp.json` — the token differs per agent and none
of it belongs in git.
3. **`sylph-decoder` / `sylph-port`** — mount the matching token file read-only.
Then rebuild both images. ⚠️ `CARGO_BUILD_JOBS=4` and limited `-j`; a
full-parallel build has OOM-crashed this box.
📌 **Worth trying, unverified:** the server takes `--tools` / `GITEA_TOOLS` to
filter which tools it exposes. If a merge tool can be excluded by name, that is
defence in depth behind Phase 2 — belt *and* braces. I have not confirmed the
tool names, so this is an experiment at install time, **not** a substitute for
branch protection.
> **Check:** in each container, `claude mcp list` shows `gitea` connected, and a
> read call returns this repo's labels.
---
## Phase 6 · Seed the first items 🤖 + 👤
I migrate the live findings into issues — **not** the 1,227 historical lines,
only what is actually open:
| bundle | items |
|---|---|
| **Title screen** | F5 (does Ⓐ snap or accelerate?), F6 (`ptloop01/02` sweep onset), re-propose the F5/F6 work left off `main` |
| **Menus** | F1 (held-direction repeat rate — Decoder measures, Port implements), F2 (SFX mix too loud), F3 (missing title audio), re-propose the OPTIONS menu work |
Each gets a bundle, an owner label, a dependency edge where one waits on the
other, and — for anything already written on the port branch — a note that the
code exists and needs re-proposing as a reviewable PR, not rewriting.
**👤 Your part:** approve the *shape* of each (`state/proposed` →
`state/approved`). This is the cheap gate — before effort, not after.
---
## Phase 7 · Restart, and verify the loop 🤖 + 👤
```bash
docker/decoder/sylph-decoder
docker/port/sylph-port
```
> ### Check — the three things that must be true
>
> 1. Each agent's **first iteration reads its notifications.** If it does not,
> nothing addressed to it will ever arrive: **notifications are polled, and
> nothing pushes.**
> 2. Each opens a **pull request**, not a bare branch push, and labels its issue
> `state/needs-human` with a one-line "look at this".
> 3. Neither can merge. (Already proven in Phase 2; confirm it holds for a real
> PR.)
---
## Still to build 🤖
Not blockers for Phase 7, but the workflow is not finished without them:
* **`propose-work`**, superseding `push-work` — push the branch *and* open the PR
with `Closes #N` *and* set the label, in one step. Today `push-work` does the
first third; the other two thirds being manual is how they get skipped. Its
existing refusals stay: no `main`, no force-push.
* **an attachment uploader** — the MCP exposes `attachment_read` only, so putting
a screenshot on an issue needs a direct `POST /repos/{owner}/{repo}/issues/{index}/assets`.
* **`gitea-verify`** — asserts protection is still on and the agents are still
Write-not-Admin. Phase 2 is checked once; this checks it every day.
* **wiki landing page** — bundles in flight and what each agent is on. There is
currently no view of what is happening except container logs.
## What I have not verified
Said plainly, because a runbook that hides its soft spots is worse than one that
does not:
* **that Gitea hides Approve from a PR's own author.** Widely true; Phase 2's
check tests it directly rather than trusting me.
* **the `--tools` filter names** (Phase 5) — an experiment, not a control.
* **Gitea's Projects API**, which is why Phase 4 creates no board.
* **the exact Gitea version** — the API was unreachable from my sandbox on the
last three attempts. Every screen named here has been stable for many
releases, but if a menu is not where I say it is, that is why.

View File

@@ -4,6 +4,10 @@
person directs this project, and replaces `BLOCKED.md` as the way agents track
what is open.
📌 This page is the **what and why**. The ordered **how** — users, branch
protection, tokens, MCP, and the check after each step — is
[`GITEA-SETUP.md`](GITEA-SETUP.md).
## Why not a new tool
We looked. The market has converged on **removing the human from the loop**