Handoff: record ten31-brand onboarding, the poll-loop liveness recipe, and the branch-protection merge gotcha

This commit is contained in:
Keysat
2026-08-18 19:36:57 -05:00
parent e8144ae872
commit 6060ea57a1
2 changed files with 39 additions and 13 deletions
+36 -13
View File
@@ -50,10 +50,28 @@ flat JSON in a writable `state/` mount.
`immense-voyage.local` block already carries matrix-bridge's key.
- **Onboard a repo:** create a Matrix room named like the repo, invite this bot → it auto-joins,
maps the room to `<owner>/<roomname>` (+ `~/Projects/<roomname>` on the Mac), and posts an
onboarding message. Pick review agents in-chat: `agents +reviewer +security -adjudicator`.
onboarding message. Pick review agents in-chat: `agents +reviewer +security -adjudicator` (a new
room's panel starts **empty** — the lead review still runs, it just convenes no subagents).
**No restart needed:** the poll loop re-reads `list(rooms.keys())` every cycle, so a room mapped
mid-run is polled within one interval. The startup log line ("polling N mapped repo(s)") is stale
from then on — it is printed once and never re-printed; don't read it as the live room count.
- **In a review room:** `merge` / `reject` / `clear` (inside a PR's thread, or `<cmd> <n>` by number),
`yes`/`no` to confirm a merge, `agents …` to toggle the subagent panel. `clear` (alias `resolve`)
redacts an orphaned thread without touching the PR.
- **Merging under branch protection:** these repos protect `master` with push + merge whitelists
(`grant` only) and `required_approvals = 1`. The bot never posts an approving *review* to Gitea, so
a collaborator's PR page shows as blocked pending approval — the in-thread `merge` still lands it,
because the bot merges with `force_merge: true` (proven on ten31-site PR #4 against this exact rule
shape). Tell a collaborator that "1 approval required" is expected, not stuck. Post-merge publishing
is opt-in per repo (`[repo.<name>].deploy_on_merge`); without it a merge merges and redacts, nothing more.
- **Prove the poll loop is alive** (silence is normal — the loop logs only on a review or an error, so
a healthy bot can go days without a line). Sample the Spark's host socket table fast enough to catch
a poll: `end=$((SECONDS+130)); while [ $SECONDS -lt $end ]; do case "$(cat /proc/net/tcp)" in *EE73*)
echo hit;; esac; sleep 0.01; done` — `EE73` is Gitea's port 61043 in hex, and **two hits ~60s apart =
healthy**. Why the fast sampling: the poll socket lives milliseconds and leaves no `TIME_WAIT` (Gitea
closes first), so 1–2s polling misses it entirely and reads as a false wedge. Also: the container has
no `ps`, `sudo` on the Spark wants a password (no `tcpdump`), and host networking means the container's
`/proc/1/net/tcp` *is* the host's. This is the observation the no-heartbeat decision waits on.
## Layout
@@ -67,7 +85,8 @@ flat JSON in a writable `state/` mount.
redacts without touching the PR.
- `config.example.toml` — homeserver, `[mac]` (ssh alias + the reused matrix-bridge wrapper paths),
`[gitea]` (api_base/owner/verify_tls), `[defaults]`, optional `[repo.<name>]` deploy overrides.
- `.env.example` — `MATRIX_*` + `GITEA_TOKEN` (real `.env` gitignored).
- `.env.example` — `MATRIX_*` + `GITEA_TOKEN` (real `.env` gitignored). The live token is scoped
`write:repository` only: repo/PR calls work, but `/user` and anything needing `read:user` 403s.
- `Dockerfile` · `docker-compose.yml` · `docker-entrypoint.sh` — the Spark container (generic image;
secrets/config via read-only mounts; entrypoint writes `~/.ssh/config` for the Mac alias).
- `state/` — gitignored runtime JSON (`rooms.json`: room map + agents + heads + thread roots).
@@ -103,21 +122,25 @@ revisit this — local inference via Spark Control would be the path.
## Current state
**LIVE and in steady use.** The bot runs on the Spark, maps each review room to its repo, and is proven
end-to-end on **ten31-site PR #4** (2026-06-28): threaded review → `merge` + `yes` → force_merge →
auto-publish → thread redacted, with headless `claude -p` genuinely spawning the reviewer / adjudicator /
security subagents. ten31-site is the only repo onboarded so far.
**LIVE and in steady use**, now serving **two** review rooms. Verified 2026-08-18: the container has been
up since 08-13 with zero restarts on `063e694`, and the poll loop is genuinely alive (two Gitea connections
observed exactly 60s apart — see the liveness recipe in Commands). `master` is only docs ahead, so **no
redeploy is pending**. No test suite: verification means exercising the bot in a real review room.
Running on the Spark at `063e694` (a merge always redacts its thread; a failed post-merge deploy warns at
top level rather than orphaning one; `clear`/`resolve` clears an orphan without touching the PR). `master`
is only docs ahead of that, so **no redeploy is pending**. No test suite: verification means exercising
the bot in a real review room.
- **ten31-site** — proven end-to-end on PR #4 (2026-06-28: review → `merge` + `yes` → force_merge →
auto-publish → redacted). **PR #5 "Update index.html" is still open**, reviewed 2026-06-30, its thread
live in the room and waiting on a `merge`/`reject`.
- **ten31-brand** — onboarded 2026-08-18 to watch the interactive deck
(`design/deck/interactive/index.html`). Panel is empty; no path filter (deliberate — the repo is small
enough that every PR is worth seeing). It has had **zero PRs ever**: all work went straight to `master`,
and the bot sees only pull requests. Collaborator `jonathan` has write access but is blocked from
pushing to `master`, so his changes must arrive as PRs; the owner's own pushes still bypass the bot.
**Next steps, in priority order:**
1. **Fix the orphaned-thread prune** (`src/bot.py` ~L594): the poll loop drops a PR's thread root from
state when the PR leaves the open list *without redacting*, so a PR merged or closed in the Gitea web
UI strands its thread and `clear` can no longer reach it. Redact before dropping the root.
UI strands its thread and `clear` can no longer reach it. More likely to bite now with two rooms.
2. **`/triage` the 3 untriaged inbox items** tagged for this repo — adjudication never reads the inbox, so
their content is still unknown here.
3. Onboard a 2nd/3rd repo, then watch one poll cycle (`docker compose logs -f`) for a long review
head-of-line-blocking the next room — ROADMAP Phase 3's verify item, and the real scaling limit.
3. With a 2nd repo mapped, ROADMAP Phase 3's verify is unblocked: next time both rooms have open PRs at
once, watch a cycle (`docker compose logs -f`) for a long review head-of-line-blocking the other room.
+3
View File
@@ -12,6 +12,9 @@ Phases beyond the first milestone (threaded single-flow review + merge/reject/de
task walks the rooms in turn), so "no interleaving" is trivially satisfied; the thing actually worth
watching at 3+ repos is a long review **head-of-line-blocking** the next room's poll, i.e. the latency
problem the deferred poll→webhook item anticipates.)*
- **Status 2026-08-18: 2 of 3 onboarded** — ten31-site and ten31-brand. Mapping a room mid-run needed
no restart. The head-of-line-blocking watch above is now possible but still unobserved: it needs open
PRs in both rooms at the same time.
## Fold-ins — do opportunistically, never a session of their own
- *(fold-in, not a session of its own)* Next time `src/bot.py`'s SSH or Matrix-send path is edited: