Specify and plan M3b: the runner and the tools

A draft spec for the owner's review and 13 offline tasks with their given
tests: shared tool arguments and host rules in proto, the sealed fetch
target (M3a finding 14), the toolkit tools and SOCKS5 egress proxy, and
brokerd's [runner], podman argument lists, runtime and proxy lifecycle. Each
task's tests were run against a reference at that task's end state (560 to
638 tests, clippy clean); the reference is not in the repository. Adds the
runner-unavailable runbook entry and tip T23 (ETXTBSY in script tests).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-22 22:29:27 -07:00
co-authored by Claude Opus 5.5
parent d988edac4a
commit b426ca1958
47 changed files with 4491 additions and 2 deletions
@@ -0,0 +1,140 @@
//! `[runner]` in `brokerd.toml` (M3b spec, section 6). Do not edit.
use std::path::{Path, PathBuf};
use std::time::Duration;
use brokerd::args::ToolName;
use brokerd::config::{Config, ConfigError};
const DIGEST: &str = "localhost/boxmaker-tools@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
fn fixture(name: &str) -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.join("tests/fixtures/config")
.join(name)
}
fn invalid(name: &str) -> String {
match Config::load(&fixture(name)) {
Err(ConfigError::Invalid(_, why)) => why,
other => panic!("{name}: expected Invalid, got {other:?}"),
}
}
#[test]
fn without_a_runner_section_there_is_no_runner() {
assert_eq!(Config::load(&fixture("empty.toml")).unwrap().runner, None);
}
#[test]
fn a_runner_with_only_an_image_gets_every_default() {
let r = Config::load(&fixture("runner_minimal.toml"))
.unwrap()
.runner
.unwrap();
assert_eq!(r.podman, PathBuf::from("podman"));
assert_eq!(r.image, DIGEST);
assert_eq!(r.egress_network, "pasta");
assert_eq!(r.output_cap, 262_144);
assert_eq!(r.memory, "512m");
assert_eq!(r.pids, 128);
assert_eq!(
r.time_limit(ToolName::ReadFile),
Duration::from_millis(30_000)
);
assert_eq!(
r.time_limit(ToolName::WriteFile),
Duration::from_millis(30_000)
);
assert_eq!(
r.time_limit(ToolName::Shell),
Duration::from_millis(100_000)
);
assert_eq!(
r.time_limit(ToolName::HttpFetch),
Duration::from_millis(60_000)
);
}
#[test]
fn every_runner_value_can_be_set() {
let r = Config::load(&fixture("runner_full.toml"))
.unwrap()
.runner
.unwrap();
assert_eq!(r.podman, PathBuf::from("/run/current-system/sw/bin/podman"));
assert_eq!(r.egress_network, "slirp4netns");
assert_eq!((r.output_cap, r.memory.as_str(), r.pids), (1000, "1g", 64));
let limits: Vec<Duration> = ToolName::ALL.iter().map(|t| r.time_limit(*t)).collect();
assert_eq!(limits, [1, 2, 3, 4].map(Duration::from_millis));
}
#[test]
fn a_runner_needs_an_image_named_by_digest() {
assert!(matches!(
Config::load(&fixture("runner_no_image.toml")),
Err(ConfigError::Parse(..))
));
assert!(invalid("runner_tag.toml").contains("by digest"));
}
#[test]
fn unknown_runner_keys_are_errors() {
assert!(matches!(
Config::load(&fixture("runner_unknown_key.toml")),
Err(ConfigError::Parse(..))
));
}
#[test]
fn bad_values_are_errors_that_name_them() {
let dir = std::env::temp_dir().join(format!("bx-runner-cfg-{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
let cases = [
(
format!("image = \"x@sha256:{}\"", "0".repeat(63)),
"by digest",
),
(
format!("image = \"x@sha256:{}\"", "A".repeat(64)),
"by digest",
),
(
format!("image = \"@sha256:{}\"", "0".repeat(64)),
"by digest",
),
(format!("image = \"{DIGEST}\"\nmemory = \"lots\""), "memory"),
(format!("image = \"{DIGEST}\"\nmemory = \"512\""), "memory"),
(format!("image = \"{DIGEST}\"\nmemory = \"m\""), "memory"),
(
format!("image = \"{DIGEST}\"\noutput_cap = 0"),
"output_cap",
),
(format!("image = \"{DIGEST}\"\npids = 0"), "pids"),
(format!("image = \"{DIGEST}\"\nshell_ms = 0"), "shell_ms"),
(
format!("image = \"{DIGEST}\"\nhttp_fetch_ms = 0"),
"http_fetch_ms",
),
(
format!("image = \"{DIGEST}\"\negress_network = \"\""),
"egress_network",
),
(format!("image = \"{DIGEST}\"\npodman = \"\""), "podman"),
];
for (n, (body, word)) in cases.iter().enumerate() {
let path = dir.join(format!("c{n}.toml"));
std::fs::write(&path, format!("[runner]\n{body}\n")).unwrap();
match Config::load(&path) {
Err(ConfigError::Invalid(_, why)) => assert!(why.contains(word), "{body}: {why}"),
other => panic!("{body}: expected Invalid, got {other:?}"),
}
}
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn the_egress_directory_is_under_home() {
let c = Config::parse("[paths]\nhome = \"/h\"\n").unwrap();
assert_eq!(c.egress_dir(), PathBuf::from("/h/run/egress"));
}