Phase 5 of docs/agents/GITEA-SETUP.md, plus a correction to Phase 2 that the runbook could not have known it needed. gitea-mcp v1.7.0 goes into both images, pinned by the sha256 the release publishes and smoke-tested with `--version` at build time, so a bad pin fails the build instead of the agent. Each entrypoint registers it at user scope for that container's own identity, remove-then-add so a restart is idempotent. The token is passed BY PATH. `-e GITEA_ACCESS_TOKEN=$(cat …)` would write it in cleartext into ~/.claude.json, which every session in the container reads; GITEA_ACCESS_TOKEN_FILE is new in the pinned version and leaves the secret in its read-only mount. Verified against the binary's own --help, not assumed. The tool filter stops being an experiment. The names are in the release README: each agent gets issues, notifications, labels, milestones and pull requests, and NOT `pull_request_review_write`. That one matters because separate identities open a hole the runbook did not name: Gitea refuses to let an author approve their own pull request, and does nothing about sylph-decoder approving sylph-port's. Two agents could satisfy `required_approvals = 1` between themselves and then merge, since branch protection blocks pushes to main and never blocked merges. Withholding the tool is defence in depth; the controls are in branch protection, and both docs now say so: approvals whitelisted to the human so an agent's approval does not count, merges whitelisted to the human so an approved PR is still merged by a person. Phase 2's check gains the step that actually tests it -- approve the throwaway PR yourself, then confirm the agent STILL has no merge button. Without that step, the check passes on an instance where the agents can merge each other's work. Also settles two entries on the runbook's own "not verified" list: the tool filter names, and the Gitea version (1.25.5, whose API schema carries enable_merge_whitelist and enable_approvals_whitelist under those names). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01McNbzUeq1KRBWs4G6X2YVj
146 lines
7.3 KiB
Markdown
146 lines
7.3 KiB
Markdown
# The working surface: Gitea issues, pull requests, and where things live
|
|
|
|
**Set by the human, 2026-09-04.** Replaces chat and Remote Control as the way a
|
|
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** —
|
|
`agent-kanban`'s own tagline is *"Take human out of the loop"* — and this project
|
|
is built entirely around a human gate. Meanwhile every candidate adds a second
|
|
store of truth to keep in sync with git, and **documents drifting out of sync is
|
|
this project's defining failure mode**: a 1,227-line `BLOCKED.md` whose
|
|
anti-staleness convention was constant by construction, 41 % of citations not
|
|
resolving, 21 inter-agent messages sent into a void with no delivery feedback.
|
|
|
|
Gitea is already deployed, already holds the code, and its first-party MCP server
|
|
(`gitea/gitea-mcp` v1.7.0) exposes issues, labels, milestones, pull requests,
|
|
attachments and notifications. So: **no new store.**
|
|
|
|
## The four surfaces, and what belongs in each
|
|
|
|
| surface | holds | why not somewhere else |
|
|
|---|---|---|
|
|
| **Issues** | work items, asks between agents, defects | durable, stateful, owned, and **dependency edges close themselves** when the blocking issue closes — the thing prose could never do |
|
|
| **Pull requests** | every change to `main` | the human gate becomes **native** instead of a label convention |
|
|
| **Git (`docs/`)** | RE findings, decisions, evidence | a finding must be versioned **with the code that consumes it** |
|
|
| **Wiki** | orientation for a person: runbook, navigation, container notes | browsable and branch-independent, but **unreviewed** — see below |
|
|
|
|
### Issues = bundles and items
|
|
|
|
Milestones are **bundles** (the human defines them). Issues are **items** (agents
|
|
propose, the human approves). Labels carry the state:
|
|
|
|
```
|
|
state/proposed → state/approved → state/in-progress → state/needs-human → closed
|
|
↘ state/blocked
|
|
```
|
|
|
|
`state/needs-human` is the one the whole model turns on, and the one no
|
|
off-the-shelf tool models. Its issue body must say **what to look at** and **what
|
|
pass and fail look like** — a person should be able to judge it in under a minute
|
|
without reading anything else.
|
|
|
|
⚠️ **`state/blocked` uses Gitea's dependency edges, not prose.** *"The Port is
|
|
blocked on the Decoder answering X"* becomes a queryable link that resolves
|
|
itself. That is the single highest-value change here after PRs.
|
|
|
|
### Pull requests = how work reaches `main`
|
|
|
|
**Adopted 2026-09-04, the human's proposal, and it is a bigger improvement than
|
|
it looks.** Today agents commit to long-lived `auto/*` branches that a human
|
|
merges by hand — and those branches have drifted **280 and 373 commits** apart,
|
|
which is unreviewable by construction.
|
|
|
|
One PR per item, closing its issue:
|
|
|
|
* the review surface is a **diff in a browser**, not a human reading commits in a
|
|
terminal;
|
|
* `Closes #123` binds the change to the item, so "what did this fix" stops being
|
|
archaeology;
|
|
* **PRs enforce the sizing rule.** An item too big to review in one sitting was
|
|
too big to be an item. The discipline stops depending on an agent's judgement.
|
|
|
|
🔴 **Agents must not merge their own pull requests.** The MCP's
|
|
`pull_request_write` includes `merge` and the tool cannot be split, so this
|
|
cannot be left to instruction — it goes in **branch protection on `main`**. Same
|
|
principle that fixed the build-jobs cap: policy belongs where the agent cannot
|
|
reach it, not in a document asking it not to.
|
|
|
|
⚠️ **"Requiring review" is not the rule that does it.** Gitea stops an author
|
|
approving their own pull request; it does not stop *the other agent* approving
|
|
it, and it never blocked merging in the first place — `Enable Push: off` blocks
|
|
pushes. The rule that holds is the pair of whitelists: **approvals whitelisted to
|
|
the human**, so an agent's approval does not count, and **merges whitelisted to
|
|
the human**, so an approved PR is still merged by a person. See
|
|
[`GITEA-SETUP.md`](GITEA-SETUP.md) Phase 2.
|
|
|
|
### 🔴 The wiki is NOT for the RE corpus
|
|
|
|
The human suggested it for RE findings. **Half right, and the wrong half is worth
|
|
saying plainly**, because it would undo two things we paid for:
|
|
|
|
1. **A finding's value is that it sits next to its evidence, versioned with the
|
|
code that consumes it.** *"Decoded, with a disc-wide check"* is backed by a
|
|
test in this repository. A wiki is a **separate git repo**, so a decode
|
|
correction and the exporter change that depends on it could never be one
|
|
atomic commit, or one reviewable PR.
|
|
2. **Wiki edits bypass review.** The `REFUTED.md` R1 reclassification changed the
|
|
file both agents read to decide what *not* to try. It was a reviewed commit
|
|
with a stated rationale. As a wiki edit it would have been an unreviewed
|
|
mutation of shared ground truth by whoever typed last.
|
|
|
|
So the corpus stays in `docs/`, reached through PRs.
|
|
|
|
**What the wiki IS good for** — human-facing orientation that is not evidence and
|
|
should not be branch-dependent:
|
|
|
|
* the runbook (`docs/port/RUNNING.md`'s content — how to actually play the port)
|
|
* `docs/game/navigation.md` — how the game is navigated, written for a person
|
|
* container notes, credentials setup, the things a human reads once
|
|
* a landing page: current bundles, what each agent is on, links into git
|
|
|
|
That last one addresses a real gap: **there is no view of what is happening**
|
|
except container logs and multi-megabyte transcripts.
|
|
|
|
### Where files go — three needs, three homes
|
|
|
|
Currently everything transient goes to `/exchange`, and a human cannot browse it
|
|
at all.
|
|
|
|
| the file is | goes to |
|
|
|---|---|
|
|
| agent → agent, transient, no human involved | **`/exchange`** via `share`, unchanged — it records sender, time, commit and dirty-tree |
|
|
| **evidence a human must look at** (a screenshot, a film, a capture behind a `state/needs-human` item) | **attached to the issue** it is evidence for |
|
|
| evidence a finding cites | **git**, beside the finding. It is the proof |
|
|
|
|
Attaching to the issue is strictly better than both alternatives for the middle
|
|
case: it travels with the item, a person sees it in the browser, and it cannot be
|
|
orphaned from the claim it supports.
|
|
|
|
⚠️ The MCP exposes `attachment_read` only — **uploading needs a direct REST call**
|
|
(`POST /repos/{owner}/{repo}/issues/{index}/assets`). Worth a small helper rather
|
|
than each agent re-deriving it.
|
|
|
|
### Notifications = the wake-up, with delivery you can inspect
|
|
|
|
`notification_read` / `notification_write` replace the message channel that lost
|
|
**21 consecutive messages to a stale session ID with no error of any kind**. An
|
|
`@mention` on an issue is durable, addressed by *name*, and has a read state a
|
|
supervisor can inspect. The old rule still stands and gets easier: **the message
|
|
carries a pointer — now an issue number — and the repository holds what was
|
|
found.**
|
|
|
|
## What does not change
|
|
|
|
* Findings are still classified **decoded / measured / undecodable**.
|
|
* An agent still cannot verify its way out of its own role.
|
|
* `REFUTED.md` is still the file to grep before proposing anything, and entries
|
|
still name their `⟨instrument⟩`.
|
|
* The oracle is still the real game in Xenia Canary.
|