From 6060ea57a112477be772e4f2d7f9f36588197299 Mon Sep 17 00:00:00 2001 From: Keysat Date: Tue, 18 Aug 2026 19:36:57 -0500 Subject: [PATCH] Handoff: record ten31-brand onboarding, the poll-loop liveness recipe, and the branch-protection merge gotcha --- AGENTS.md | 49 ++++++++++++++++++++++++++++++++++++------------- ROADMAP.md | 3 +++ 2 files changed, 39 insertions(+), 13 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 4331939..71a6cee 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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 `/` (+ `~/Projects/` 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 ` ` 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.].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.]` 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. diff --git a/ROADMAP.md b/ROADMAP.md index 9dbc900..541065e 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -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: