agents: name what branch protection does not gate, and stop the tool contradicting it

Two things that read as protection while being none.

Phase 2's rule binds everyone who reaches Gitea through the API or the web, and
does not bind anyone with `gitea admin` in the container -- which includes the
supervising agent that created the agent accounts and minted their tokens. From
that shell the rule is editable and an admin token is one command away. That is
the boundary of what the phase buys, not a hole to plug there, and the document
read as though the gate were universal. Phases 1 and 2 gate the two CONTAINERISED
agents, whose design assumption is that policy lives where they cannot reach it;
a supervisor with a host shell is not in that set.

And `gitea-setup` finished by telling the reader to go and build a Gitea project
board by hand, four sections after the doc explains that a board is a second copy
of the state to hand-sync and is precisely the failure that produced a 1,227-line
BLOCKED.md. A tool instructing you to do the thing its own documentation argues
against is the drift this whole surface exists to end.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01McNbzUeq1KRBWs4G6X2YVj
This commit is contained in:
2026-09-04 17:48:58 +02:00
parent 799fa93383
commit b67b6243e6
2 changed files with 28 additions and 3 deletions

View File

@@ -115,6 +115,26 @@ Neither whitelist is decoration, and neither replaces the other:
Withholding the review tool from the agents (Phase 5) is defence in depth behind
these, not a substitute: an agent still has a browser-shaped API token.
### 🔴 What this rule does not gate, said plainly
It binds everyone who reaches Gitea through the API or the web. **It does not
bind anyone who can run `gitea admin` inside the container** — and that includes
the supervising agent on the Pi, the one that created the agent accounts and
minted their tokens. From that shell you can issue an admin token or edit this
rule, and nothing here would stop you.
That is not a hole to plug here; it is the boundary of what Phase 2 buys, and it
should be written down rather than discovered. **Phases 1 and 2 gate the two
containerised agents** — the ones that run unattended on a loop, whose whole
design assumption is that policy lives somewhere they cannot reach. A supervisor
with a shell on the host is not in that set, and the protection above should not
be read as universal.
The distinction is exactly the one Phase 1.2 draws with **Write, not Admin**: the
looping agents get a permission level that cannot edit the rule that binds them.
`tools/gitea-protect --verify` asserts that level on every run, which is the
check that keeps this true rather than merely stated.
> ### Check — and actually run it, do not assume it
>
> The whole point of putting this in protection rather than in a document is

View File

@@ -113,7 +113,12 @@ if [ "$DRY" = 1 ]; then
else
echo "labels and bundles are in place at https://$HOST/$REPO/issues"
echo
echo "Remaining, by hand in the web UI (the API for Projects lags it):"
echo " Projects -> New Project -> columns:"
echo " Proposed | Approved | In progress | Needs human | Done"
# No board, and this used to say the opposite. Gitea's project board does not
# follow labels, so it would be a SECOND copy of the state to hand-sync -- the
# exact failure that produced a 1,227-line BLOCKED.md. Labels are the truth and
# a saved issue filter gives the same view for nothing. Leaving the old
# "remaining, by hand: Projects -> New Project" line here would have had the
# tool instructing the reader to build the thing the doc argues against.
echo "No project board, deliberately -- labels are the truth. See"
echo "docs/agents/GITEA-SETUP.md Phase 4. Use a saved issue filter instead."
fi