diff --git a/docs/agents/GITEA-SETUP.md b/docs/agents/GITEA-SETUP.md index 7a81b432..6b3abeb6 100644 --- a/docs/agents/GITEA-SETUP.md +++ b/docs/agents/GITEA-SETUP.md @@ -127,11 +127,32 @@ these, not a substitute: an agent still has a browser-shaped API token. > button**, now that an approval exists. This is the step that tests the > merge whitelist rather than the absence of an approval — without it, steps > 2 and 3 pass on an instance where the agents can merge each other's work. -> 5. Close the PR, delete the branch. +> 5. As **yourself**, try `git push origin main` with a throwaway commit. It +> should be **refused** — see below. +> 6. Close the PR, delete the branch, drop the commit. > > If step 2 or step 4 offers a Merge button, stop — the rest of this runbook > assumes neither does. +### ⚠️ Your own pushes to `main` stop too + +Not a side effect — the rule working. `enable_push: false` compiles to +`CanUserPush`, which in Gitea's `models/git/protected_branch.go` returns false +with **no bypass for repository admins or the owner**: + +```go +if !protectBranch.CanPush { + return false +} +``` + +Three commits reached `main` by direct push on the day this was written, so the +first time you notice will be the first time you reach for it. From Phase 2 on, +**human changes go through pull requests like everything else** — and merging +them is what the admin override above is for. `--verify` asserts this state +rather than tolerating it: a verifier that excused your push would be excusing +the gate. + --- ## Phase 3 · Tokens 👤 @@ -139,11 +160,21 @@ these, not a substitute: an agent still has a browser-shaped API token. 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` | +| whose | scopes | goes in | on which machine | +|---|---|---|---| +| **you** (`fabi`) | `write:issue`, `read:repository` | `~/.sylph-gitea-api-token` | **the Pi** | +| `sylph-decoder` | `write:repository`, `write:issue`, `write:notification`, `read:user` | `~/.sylph-gitea-token-decoder` | the agent box | +| `sylph-port` | same four | `~/.sylph-gitea-token-port` | the agent box | + +📌 **Three machines, and the split is by tooling, not by capability.** Gitea runs +on the Pi, published through a VPS — so `git.mc02.dev` resolves to a hosted +address and a DNS lookup tells you nothing about the origin. The agent +containers run on the x86_64 desktop, which reaches the Gitea API perfectly well +(`GET /api/v1/version` → `200 {"version":"1.25.5"}`, run from there). + +The `fabi` token lives on the Pi because that is where `tools/gitea-setup` runs, +and that is where the session driving Phases 4 and 6 sits. It is **not** a +reachability constraint, and an earlier draft that said so was wrong. ```bash printf '%s\n' '' > ~/.sylph-gitea-api-token && chmod 600 ~/.sylph-gitea-api-token @@ -157,6 +188,14 @@ containers, exactly like `~/.sylph-claude-token`. `required=[read:issue], token scope=write:repository`. It stays as it is; these are additional. +🔴 **Do not add `write:repository` to the `fabi` token**, even though Phase 2's +API path might look as though it needs it. **A `write:repository` token *is* a +push credential** — that is the scope git checks for receive-pack — so adding it +would give the Pi push rights over `main`, in order to avoid giving the Pi push +rights. `gitea-protect` sidesteps it entirely by running on the agent box +against the credential already there. This warning exists because that advice +was given, in chat, by the same author as this file. + > **Check:** `tools/gitea-setup --dry-run` prints "would create …" rather than a > scope error. @@ -320,11 +359,38 @@ does not: which is what the approvals whitelist is for. * **Gitea's Projects API**, which is why Phase 4 creates no board. -Two things on this list have since been settled rather than assumed: +Settled since, rather than assumed: * ~~the `--tools` filter names~~ — read out of the pinned release, and the binary's `--help` run directly. Phase 5 lists the set. -* ~~the exact Gitea version~~ — **1.25.5**. `enable_merge_whitelist`, - `enable_approvals_whitelist` and `block_admin_merge_override` are all present - in this instance's own API schema, so the Phase 2 settings exist under those - names on the Branches screen. +* ~~the exact Gitea version~~ — **1.25.5**, confirmed independently from *both* + machines. `enable_merge_whitelist`, `enable_approvals_whitelist` and + `block_admin_merge_override` are all present in this instance's own API + schema, so the Phase 2 settings exist under those names on the Branches screen. +* ~~which machine can reach what~~ — the desktop reaches the Gitea API fine. + The token split in Phase 3 is about which session runs which script, and an + earlier draft that justified it as a network constraint was wrong. + +### Wrong, not merely unverified + +Kept separate, because "I had not checked" and "I asserted the opposite" are +different failures and only the second is worth a heading: + +* **that requiring an approval closes the gate.** It does not. Merging ignores + the push whitelist entirely, and any Write collaborator is an official + reviewer — so the first version of Phase 2 would have let the two agents + approve each other and merge. Both whitelists exist because of it. +* **that the check could catch that.** It could not: with the approval + requirement unmet, Gitea offers *nobody* a merge button, so the original + steps 1–3 pass on a completely unprotected instance. Step 4 is the test. +* **that the `fabi` token should gain `write:repository`.** That scope is a push + credential. +* **that the desktop could not reach Gitea.** It can; `curl` was being refused + by a local permission prompt, which is not the same thing and was read as if + it were. + +The first two were caught by the other agent. The pattern in all four is one +thing: **a property was inferred from something adjacent to it** — protection +from a settings page, reachability from a DNS record — instead of being tested +directly. That is the same failure the port's frozen-splash instruments made, +in a document about avoiding it.