Files
boxmaker/docs/plans/M3b/README.md
T
kyleandClaude Opus 5.5 e87d876f26 M3b plan: task 08 says not to join handler threads and to skip argv[0]
The first attempt at task 08 did both and stopped without a commit. Also asks
the implementer to debug inside the repository, since OpenCode refuses /tmp.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-23 00:46:23 -07:00

76 lines
5.0 KiB
Markdown

# M3b implementation plan: the runner and the tools
> **For the implementing model:** do not work from this file. The owner gives you one task file at
> a time (`01-…` to `13-…`). This file is the index for the owner and the reviewer.
**Goal:** an allowed tool call runs in a fresh rootless Podman container, with no network unless
it is `http_fetch`, which reaches the grant's hosts only through an egress proxy of our own. The
four tools and the proxy are programs in `toolkit`; `brokerd` builds the `podman` argument lists
and runs them.
**Architecture:** shared definitions move to `proto` first (01), the fetch target is sealed (02),
and grant paths that cannot be mounted are refused (03). `toolkit` gets its four tools (04 to 06),
the public-address check (07) and the proxy (08). `brokerd` gets `[runner]` (09), the argument
lists (10), the runtime (11), the proxy's lifecycle (12) and the wiring in `serve` (13).
**Spec:** `docs/specs/2026-09-22-m3b-runner.md`. Brief: `docs/design.md`. Every fail-closed
message ends with a pointer into `docs/runbook.md`; the `runner-unavailable` entry is already
there.
**No task needs Podman, Nix or a network.** Everything runs against fakes: fake `curl` and
`podman` scripts, a fake resolver, `UnixStream::pair()`. The image and the checks on straylight
are done by the design model afterwards.
## Global constraints
- Everything in `AGENTS.md`, including "Lessons from earlier reviews".
- No new dependency. `toolkit` gains `serde` and `serde_json`, both already vetted (task 04).
- Branch `m3b`. One task, one fresh OpenCode session, one commit. Run `cargo fmt --all` before the
gate. Review happens once, after task 13.
- Tests that write a script and run it take a lock (`serial()`): otherwise another test's fork
can make running it fail with "text file busy". Keep that pattern in anything you add.
## Tasks
The last column is how the given tests were checked before hand-over (decision of 2026-09-18, tip
T17). For M3b every task had a reference implementation: the tests of each task were run against
it at that task's end state, then the reference was deleted so it cannot be read (tip T18).
| # | File | Delivers | Tests | Check |
|---|---|---|---|---|
| 01 | `01-proto-tools-hosts.md` | `proto::tools`, `proto::hosts`; `brokerd::args` uses them | `proto/tests/tools.rs`, `hosts.rs` | reference; the given test pinned an accepted serde weakness, removed |
| 02 | `02-brokerd-fetch-url.md` | `ToolArgs::HttpFetch(FetchUrl)`, sealed (M3a finding 14) | `brokerd/tests/args.rs` (changed), a `compile_fail` doctest | reference |
| 03 | `03-brokerd-grant-mount-rule.md` | grant paths with `:` or `,` are invalid | `brokerd/tests/grants_mount.rs` | reference |
| 04 | `04-toolkit-files.md` | the `toolkit` program, `read_file`, `write_file` | `toolkit/tests/files.rs`, `support/mod.rs` | reference |
| 05 | `05-toolkit-shell.md` | `shell` | `toolkit/tests/shell.rs` | reference |
| 06 | `06-toolkit-fetch.md` | `http_fetch` through `curl` | `toolkit/tests/fetch.rs` | reference; found the ETXTBSY race (1 in 7 runs), fixed with `serial()` |
| 07 | `07-toolkit-addr.md` | `is_public` | `toolkit/tests/addr.rs` | reference; found `::/96` missing from the spec, added |
| 08 | `08-toolkit-egress-proxy.md` | the SOCKS5 egress proxy | `toolkit/tests/egress.rs` | reference; 20 runs clean |
| 09 | `09-brokerd-runner-config.md` | `[runner]` | `config_runner.rs`, 5 fixtures, `support/rig.rs` (changed) | reference |
| 10 | `10-brokerd-podman-args.md` | the `podman` argument lists; `RunSpec` gains session and call | `podman_args.rs`, 6 golden files | reference |
| 11 | `11-brokerd-container.md` | the Podman runtime | `container.rs`, `support/fake_podman.rs` | reference; 20 runs clean |
| 12 | `12-brokerd-egress.md` | the proxy's lifecycle for `http_fetch` | `container_egress.rs` | reference; 20 runs clean |
| 13 | `13-brokerd-serve-runner.md` | `brokerd serve` uses the runtime | `serve_runner.rs` | reference |
At the end: `make gate` prints `gate: ok` with about 638 tests.
## Changes during the run
- 2026-09-23, after task 04: task 04 adds dependencies to `toolkit`, which changes `Cargo.lock`, but
its `git add` line left the lock out, so the driver stopped on an unclean tree. The design model
folded the lock into task 04's commit and added `Cargo.lock` to every task's `git add` (a no-op
when it has not changed). The owner resumes from task 05.
- 2026-09-23, task 08: the first attempt passed 13 of 15 tests and ended without a commit, after
OpenCode refused a command in `/tmp`. Its two defects: `main` did not skip the program name, so
`egress-proxy` never matched; and `serve` joined each handler thread, serving one connection at a
time. The attempt is saved in `.state/runs/M3b/08-first-attempt.diff`; the tree was reset, and the
task now says both things explicitly and to debug inside the repository. Resume from task 08.
## Running it
```sh
BOXMAKER_MODEL=straylight/ornith-1.5-35b-a3b tools/run-plan.sh docs/plans/M3b
```
Keep the OpenCode TUI closed while it runs.