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,50 @@
//! Host names and patterns, shared by `brokerd` and `toolkit`'s egress proxy. The full tables are
//! in `crates/brokerd/tests/args.rs`, which reaches these through `brokerd::args`. Do not edit.
use proto::hosts::{host_matches, valid_host, valid_host_pattern};
#[test]
fn hosts() {
for good in ["example.com", "a.b.example.com", "x-1.example.org", "a.b"] {
assert!(valid_host(good), "{good}");
}
for bad in [
"",
"example",
"Example.com",
"-a.com",
"a-.com",
"a..com",
".a.com",
"a.com.",
"127.0.0.1",
"127.1",
"1.2.3.4x",
"[::1]",
"a_b.com",
"a.com:443",
"*.a.com",
] {
assert!(!valid_host(bad), "{bad}");
}
}
#[test]
fn patterns() {
assert!(valid_host_pattern("example.com"));
assert!(valid_host_pattern("*.example.com"));
for bad in ["*", "*.", "*.*.a.com", "a.*.com", "**.a.com", "*a.com"] {
assert!(!valid_host_pattern(bad), "{bad}");
}
}
#[test]
fn matching() {
assert!(host_matches("example.com", "example.com"));
assert!(!host_matches("example.com", "www.example.com"));
assert!(host_matches("*.example.com", "www.example.com"));
assert!(host_matches("*.example.com", "a.b.example.com"));
assert!(!host_matches("*.example.com", "example.com"));
assert!(!host_matches("*.example.com", "badexample.com"));
assert!(!host_matches("*.example.com", ".example.com"));
}
@@ -0,0 +1,60 @@
//! The four tools' arguments: `brokerd` writes them to a container's standard input, `toolkit`
//! reads them back, and both use these types. Do not edit.
use proto::tools::{HttpFetchArgs, ReadFileArgs, ShellArgs, WriteFileArgs};
#[test]
fn each_type_round_trips_in_field_order() {
let read = ReadFileArgs {
path: "/n/a.md".to_string(),
};
assert_eq!(
serde_json::to_string(&read).unwrap(),
r#"{"path":"/n/a.md"}"#
);
let write = WriteFileArgs {
path: "/n/a.md".to_string(),
content: "hi\n".to_string(),
};
let text = serde_json::to_string(&write).unwrap();
assert_eq!(text, r#"{"path":"/n/a.md","content":"hi\n"}"#);
assert_eq!(serde_json::from_str::<WriteFileArgs>(&text).unwrap(), write);
let fetch = HttpFetchArgs {
url: "https://example.com/".to_string(),
};
assert_eq!(
serde_json::to_string(&fetch).unwrap(),
r#"{"url":"https://example.com/"}"#
);
}
#[test]
fn a_shell_cwd_is_left_out_when_absent_and_may_be_null_or_missing() {
let bare = ShellArgs {
command: "ls".to_string(),
cwd: None,
};
assert_eq!(serde_json::to_string(&bare).unwrap(), r#"{"command":"ls"}"#);
for text in [r#"{"command":"ls"}"#, r#"{"command":"ls","cwd":null}"#] {
assert_eq!(serde_json::from_str::<ShellArgs>(text).unwrap(), bare);
}
let with = ShellArgs {
command: "ls".to_string(),
cwd: Some("/n".to_string()),
};
assert_eq!(
serde_json::to_string(&with).unwrap(),
r#"{"command":"ls","cwd":"/n"}"#
);
}
#[test]
fn unknown_and_missing_fields_are_refused() {
assert!(serde_json::from_str::<ReadFileArgs>(r#"{"path":"/a","mode":"x"}"#).is_err());
assert!(serde_json::from_str::<ReadFileArgs>(r#"{}"#).is_err());
assert!(serde_json::from_str::<WriteFileArgs>(r#"{"path":"/a"}"#).is_err());
assert!(serde_json::from_str::<ShellArgs>(r#"{"command":"ls","env":{}}"#).is_err());
assert!(
serde_json::from_str::<HttpFetchArgs>(r#"{"url":"https://a.b/","method":"POST"}"#).is_err()
);
}