review loop: working repositories, git socket, push and review verdicts, row 93 (#1587)

Commits move as v2 bundles over bus-host/git.sock (decision 135). The bus
host keeps one bare working repository per business and project, seeds main
from the project root under a per-repository lock, fetches a pushed bundle
with fsck into a temp ref, requires history shared with main, and moves the
task's work ref only as a fast-forward. Host and runner git run with no
system or global config, hooks at /dev/null and local transports only. The
socket binds only for roles holding git.push.working or review.verdict.

The broker records a push as the action.allowed for git.push.working with
its commit, takes review.request for the latest push and one review.verdict
per request, and refuses task.close on a pushed task without an approve
verdict for the latest push. recordEvent reserves both shapes, so agents
can't forge either.

Reviewed by Dewey (28196) and Sage (28197), both approve. Gate on 7a729aef:
14 node suites and 9 scripts green.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-13 10:48:34 -05:00
co-authored by Claude Opus 5.5
parent 7a729aefa3
commit 4da6815293
29 changed files with 2149 additions and 55 deletions
+38 -1
View File
@@ -191,7 +191,8 @@ No capability appears in argv, stdout, the environment or a file.
uses: `bindLaunch(record)` binds a launched run's process identity
(`pid`, `startTime`) and returns `{business, run, cap}`; `op(message)` sends
one of the broker process's launch ops (`identity`, `authorizeLaunch`,
`refuse`, `endLaunch`, `credentialStatus`); `beforeClose(fn)` runs `fn`
`refuse`, `endLaunch`, `credentialStatus`, and for the git socket
`checkPush`, `recordPush`, `reviewFetch`); `beforeClose(fn)` runs `fn`
before the broker closes, so the launcher stops its sessions first.
Requests to the broker process go one at a time. Each carries an id, and
the broker echoes it on the reply. If a reply doesn't arrive within 10 s, or
@@ -306,6 +307,42 @@ the recorded start time and that its command line is the runner's, then
sends SIGTERM until it exits (30 s). A stale entry prints "not running" and
exits 0; an unknown run exits 2.
### Git socket
The launcher also serves `<dataRoot>/bus-host/git.sock` (0600,
`src/git-socket.mjs`, row 93, #1587). A coder's runner pushes work to the
business's working repository through it, and a reviewer's runner fetches a
commit it was asked to review (`push_working` and `fetch_review`,
`packages/harness/README.md`). The sandbox binds it only for a role that
holds git.push.working or review.verdict, and a session's `session.json`
names it as `gitSocket` only then.
Only a git bundle crosses. The host never runs git in a session's clone
and never opens a path in its workspace; `packages/seat/README.md` ("The
working repositories") has what it does with the bundle. A request is one
JSON line of at most 4096 bytes:
| Op | Request | Reply |
|---|---|---|
| push | `{cap, op: "push", task_ref, project, branch, size}` | `{ok: true, send: <size>}`; the runner sends exactly that many bundle bytes; then `{ok: true, result: {ref, commit, previous}}` |
| fetch | `{cap, op: "fetch", task_ref, request, have}` | `{ok: true, result: {project, ref, commit, size}}`, then that many bytes of a bundle whose one ref is `refs/mosaic/review` |
The host checks the caller's identity (a role of this business), then the
request (task ref, project, branch name, a size of at most 32 MiB), then
the broker: `checkPush` for a push, `reviewFetch` for a fetch. Only
then does it read bundle bytes. The broker records a push with
`recordPush` under the repository's lock once the ref moved; when it
refuses, the ref goes back. A refusal is `{ok: false, error: <code>}` and
ends the connection. The host's own refusals go through the broker's
`refuse` with the task ref as subject, so each is an `action.refused`
event on the task's trail. Eight connections are served at once; more
refuse `git-busy`.
A bundle file lives in `bus-host/` (0700) under a random name and is
deleted on every path. On close the launcher drops the git socket's
connections and waits for any push in flight to finish under its lock
before the broker stops.
### Trackers
`config.trackers[<business>]` is `{baseUrl, project, pollSeconds,