Files
kyleandClaude Opus 5 e3f37da232 Hand over the M3a plan: 22 tasks, their files, and the check record
Task files, the files they copy in (byte-identical to the reference on
m3a-ref), each area's check record, and a README with the per-task
table of what each check exposed. The handoff note is done with.

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

176 lines
5.5 KiB
Rust

//! The runner seam: what `run` puts in the `RunSpec` for each tool, and what it answers. Do not
//! edit.
#[path = "support/build.rs"]
mod build;
#[path = "support/runtime.rs"]
mod runtime;
use brokerd::args::{ToolArgs, ToolName};
use brokerd::policy::{Decision, Outcome, SessionState, decide};
use brokerd::runner::{Mount, REFUSING, Refusing, RunError, RunOutput, run};
use build::{grant, now, read, request, set};
use proto::{DataClass, Mode, ToolRequest, ToolResponse};
use runtime::Recording;
fn allowed(grants: Vec<build::Build>, request: ToolRequest) -> Decision {
match decide(request, &set(grants), SessionState::default(), now()) {
Outcome::Allowed(decision) => decision,
other => panic!("the test's call is not allowed: {other:?}"),
}
}
fn mount(path: &str, writable: bool) -> Mount {
Mount {
path: path.to_string(),
writable,
}
}
#[test]
fn read_file_mounts_the_matched_path_read_only_and_has_no_network() {
let d = allowed(
vec![grant("notes", "read_file", Mode::Auto).paths(&["/h/notes", "/h/notes/deep"])],
read("/h/notes/deep/a.md"),
);
let rt = Recording::answering("text");
run(d, rt.as_ref());
let seen = rt.seen();
assert_eq!(seen.len(), 1);
assert_eq!(seen[0].tool, ToolName::ReadFile);
assert_eq!(
seen[0].arguments,
ToolArgs::ReadFile {
path: "/h/notes/deep/a.md".to_string()
}
);
// The longest path that holds the argument, and only that one.
assert_eq!(seen[0].mounts, [mount("/h/notes/deep", false)]);
assert_eq!(seen[0].egress, None);
}
#[test]
fn write_file_mounts_the_matched_path_writable() {
// A grant path equal to the argument does not count, so `/s/out` is written through `/s`.
let d = allowed(
vec![grant("s", "write_file", Mode::Auto).paths(&["/s", "/s/out"])],
request("write_file", r#"{"path":"/s/out","content":"x"}"#),
);
let rt = Recording::answering("");
run(d, rt.as_ref());
let seen = rt.seen();
assert_eq!(seen[0].tool, ToolName::WriteFile);
assert_eq!(seen[0].mounts, [mount("/s", true)]);
assert_eq!(seen[0].egress, None);
}
#[test]
fn shell_mounts_every_path_of_the_grant_writable() {
let d = allowed(
vec![grant("sh", "shell", Mode::Auto).paths(&["/a", "/b/c"])],
request("shell", r#"{"command":"ls","cwd":"/b/c/d"}"#),
);
let rt = Recording::answering("");
run(d, rt.as_ref());
let seen = rt.seen();
assert_eq!(seen[0].tool, ToolName::Shell);
assert_eq!(seen[0].mounts, [mount("/a", true), mount("/b/c", true)]);
assert_eq!(seen[0].egress, None);
}
#[test]
fn shell_without_paths_mounts_nothing() {
let d = allowed(
vec![grant("sh", "shell", Mode::Auto)],
request("shell", r#"{"command":"date"}"#),
);
let rt = Recording::answering("");
run(d, rt.as_ref());
let seen = rt.seen();
assert_eq!(seen[0].mounts, []);
assert_eq!(seen[0].egress, None);
}
#[test]
fn http_fetch_mounts_nothing_and_may_reach_the_grants_hosts_only() {
let d = allowed(
vec![grant("web", "http_fetch", Mode::Auto).hosts(&["example.com", "*.example.org"])],
request("http_fetch", r#"{"url":"https://www.example.org/x"}"#),
);
let rt = Recording::answering("");
run(d, rt.as_ref());
let seen = rt.seen();
assert_eq!(seen[0].tool, ToolName::HttpFetch);
assert_eq!(seen[0].mounts, []);
assert_eq!(
seen[0].egress,
Some(vec!["example.com".to_string(), "*.example.org".to_string()])
);
}
#[test]
fn a_result_carries_the_label_combined_over_every_matching_grant() {
// `b-keys` has the longer path and wins the mount; the label is the highest class of both
// grants, and untrusted because `a-home` says so.
let d = allowed(
vec![
grant("a-home", "read_file", Mode::Auto)
.paths(&["/home/kyle"])
.class(DataClass::Private),
grant("b-keys", "read_file", Mode::Auto)
.paths(&["/home/kyle/keys"])
.class(DataClass::Secret)
.trusted(),
],
read("/home/kyle/keys/id"),
);
let rt = Recording::with(Ok(RunOutput {
content: "key".to_string(),
truncated: true,
}));
let answer = run(d, rt.as_ref());
assert_eq!(
answer,
ToolResponse::Result {
content: "key".to_string(),
class: DataClass::Secret,
untrusted: true,
truncated: true,
}
);
assert_eq!(rt.seen()[0].mounts, [mount("/home/kyle/keys", false)]);
}
#[test]
fn a_run_error_is_a_failure_with_the_runtimes_sentence() {
for error in [
RunError::Failed("the tool timed out".to_string()),
RunError::Unavailable("the container could not start".to_string()),
] {
let d = allowed(
vec![grant("n", "read_file", Mode::Auto).paths(&["/n"])],
read("/n/a"),
);
let text = match &error {
RunError::Failed(t) | RunError::Unavailable(t) => t.clone(),
};
let rt = Recording::with(Err(error));
assert_eq!(run(d, rt.as_ref()), ToolResponse::Failed { message: text });
}
}
#[test]
fn the_production_runtime_refuses_every_call() {
assert_eq!(REFUSING, "the runner arrives in M3b");
let d = allowed(
vec![grant("n", "read_file", Mode::Auto).paths(&["/n"])],
read("/n/a"),
);
assert_eq!(
run(d, &Refusing),
ToolResponse::Failed {
message: REFUSING.to_string()
}
);
}