Files
boxmaker/docs/plans/M3b/09-brokerd-runner-config.md
kyleandClaude Opus 5.5 5e55fe4c66 M3b plan: every task's git add includes Cargo.lock
Task 04 added dependencies to toolkit and its git add line left out the lock
file, so the driver stopped on an unclean tree. The lock change is folded into
task 04's commit.

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

101 lines
4.6 KiB
Markdown

# M3b task 09: `[runner]` in `brokerd.toml`
**Branch:** `m3b` (run `git switch m3b`; `git status --short` must be empty, otherwise stop)
**Commit subject:** `brokerd: the [runner] section`
## Goal
`[runner]` says how tools run: which `podman`, which image, and the limits. Without the section,
`brokerd` keeps M3a's runtime, which refuses every call (task 13 does that choice). The image must
be named **by digest**, so what runs is exactly what was built. Spec section 6, "Configuration".
## Files
- Copy: `crates/brokerd/tests/config_runner.rs`, the five files
`crates/brokerd/tests/fixtures/config/runner_*.toml`, and `crates/brokerd/tests/support/rig.rs`
(replaces the old one: it builds a `Config` with a struct literal, which now needs
`runner: None`)
- Modify: `crates/brokerd/src/config.rs`, `docs/implementer-log.md`
## Interfaces
In `config.rs`:
```rust
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Runner {
#[serde(default = "default_podman")] pub podman: PathBuf, // "podman"
pub image: String, // required, no default
#[serde(default = "default_egress_network")] pub egress_network: String, // "pasta"
#[serde(default = "default_output_cap")] pub output_cap: u64, // 262_144
#[serde(default = "default_memory")] pub memory: String, // "512m"
#[serde(default = "default_pids")] pub pids: u32, // 128
#[serde(default = "default_read_file_ms")] pub read_file_ms: u64, // 30_000
#[serde(default = "default_write_file_ms")] pub write_file_ms: u64, // 30_000
#[serde(default = "default_shell_ms")] pub shell_ms: u64, // 100_000
#[serde(default = "default_http_fetch_ms")] pub http_fetch_ms: u64, // 60_000
}
impl Runner {
/// The time limit for one call of `tool`: the matching `_ms` field.
pub fn time_limit(&self, tool: crate::args::ToolName) -> std::time::Duration;
}
pub struct Config {
// paths, sockets, approvals as now, then:
#[serde(default)]
pub runner: Option<Runner>,
}
impl Config {
// … as now, plus:
/// `<home>/run/egress`: where each `http_fetch` call gets a directory for the proxy's socket.
pub fn egress_dir(&self) -> PathBuf;
}
```
Write one small private `fn default_…() -> …` per defaulted field (serde needs a function path).
Put the struct attributes on separate lines as usual; the table above only saves space.
## Checks in `Config::load`
After the `ttl_ms` check, if `runner` is `Some`, check it and return the first problem as
`ConfigError::Invalid(path, text)`. In this order:
1. `image` is not `<name>@sha256:<hex>` where `<name>` is not empty and `<hex>` is exactly 64 of
`0-9a-f` (lowercase). Split with `rsplit_once("@sha256:")`. Text:
`[runner] image is {image:?}; it must be named by digest: <name>@sha256:<64 hex digits>`.
2. `memory` is not digits followed by one of `b`, `k`, `m`, `g` (at least one digit). Text:
`[runner] memory is {memory:?}; it must be a number and one of b, k, m, g`.
3. `egress_network` or `podman` is empty. Text:
`[runner] podman and egress_network must not be empty`.
4. Any of `output_cap`, `pids`, `read_file_ms`, `write_file_ms`, `shell_ms`, `http_fetch_ms` is 0,
checked in that order. Text: `[runner] <name> must be at least 1`, with the field's name.
A missing `image` or an unknown key is already a `ConfigError::Parse` from serde; do not add
checks for those. `Config::parse` (the string version the tests use for plain values) does not run
these checks; only `load` does, as for `ttl_ms` today.
## Steps
- [ ] **1. Copy.** `git switch m3b`, then
`cp docs/plans/M3b/files/crates/brokerd/tests/config_runner.rs crates/brokerd/tests/`,
`cp docs/plans/M3b/files/crates/brokerd/tests/fixtures/config/runner_*.toml crates/brokerd/tests/fixtures/config/`,
`cp docs/plans/M3b/files/crates/brokerd/tests/support/rig.rs crates/brokerd/tests/support/`
- [ ] **2. See it fail.** `cargo test -p brokerd --test config_runner`. Expected: it does not
compile.
- [ ] **3. Write the code.** Run `cargo fmt --all`.
- [ ] **4. See it pass.** `cargo test -p brokerd --test config_runner --test config`. Expected: 7
and 7 passed. Then `cargo test -p brokerd`: everything passes.
- [ ] **5. Run the gate.** `make gate`. Expected last line: `gate: ok`.
- [ ] **6. Log and commit.** `git add crates/brokerd docs/implementer-log.md Cargo.lock && git commit`
## Done when
- `cargo test -p brokerd` passes; `make gate` prints `gate: ok`.
## Stop and report if
- A test wants an image named by tag (`:latest`) to be accepted.