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

4.6 KiB

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:

#[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.