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>
101 lines
4.6 KiB
Markdown
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.
|