blob: 4e2f0e888a921b158903614ddb1805edcae19e44 [file]
//! The launcher, driven with a fake harness: the unconfined path everywhere, the confined
//! path where this kernel allows unprivileged namespaces.
// An integration test is test code; a panic here is the failure being reported.
#![allow(clippy::unwrap_used)]
// The fake harness is a shell script and the egress socket is a unix socket.
#![cfg(unix)]
use std::path::{Path, PathBuf};
use std::process::{Command, Output, Stdio};
fn memory() -> Command {
Command::new(env!("CARGO_BIN_EXE_amem"))
}
/// A fake `claude` that writes its environment and arguments to a file in the working
/// tree, asks the egress for T0 over MCP, and exits with a distinctive code.
fn fake_claude(dir: &Path) -> PathBuf {
let bins = dir.join("bins");
std::fs::create_dir_all(&bins).unwrap();
let script = r#"#!/bin/sh
{
echo "cwd=$(pwd)"
env | grep -E '^(ANTHROPIC_BASE_URL|ANTHROPIC_AUTH_TOKEN|HOME|PATH)=' | sort
echo "args=$*"
base="${ANTHROPIC_BASE_URL%/anthropic}"
echo "mcp=$(curl -s -o /dev/null -w '%{http_code}' -X POST "$base/mcp" -H 'content-type: application/json' -H 'accept: application/json, text/event-stream' -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"fake","version":"0"}}}')"
echo "nowhere=$(curl -s -o /dev/null -w '%{http_code}' "$base/nowhere/x")"
echo "ifaces=$(tail -n +3 /proc/net/dev 2>/dev/null | cut -d: -f1 | tr -d ' ' | tr '\n' ',')"
echo "hosthome=$(ls /home 2>/dev/null | tr '\n' ',')"
} > ./fake-claude.out 2>&1
exit 42
"#;
let p = bins.join("claude");
std::fs::write(&p, script).unwrap();
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
std::fs::set_permissions(&p, std::fs::Permissions::from_mode(0o755)).unwrap();
}
bins
}
fn path_with(bins: &Path) -> std::ffi::OsString {
let old = std::env::var_os("PATH").unwrap_or_default();
let mut paths = vec![bins.to_path_buf()];
paths.extend(std::env::split_paths(&old));
std::env::join_paths(paths).unwrap()
}
fn init_root(dir: &Path) -> PathBuf {
let root = dir.join("memory");
let out = memory()
.args([
"--root",
root.to_str().unwrap(),
"init",
"--owner",
"human:test:me",
"--domain",
"dom-me",
])
.output()
.unwrap();
assert!(
out.status.success(),
"{}",
String::from_utf8_lossy(&out.stderr)
);
root
}
fn launch(root: &Path, bins: &Path, work: &Path, extra: &[&str]) -> Output {
let mut cmd = memory();
cmd.args([
"--root",
root.to_str().unwrap(),
"launch",
"--harness",
"claude",
"--auth",
"key",
]);
cmd.args(["--workdir", work.to_str().unwrap()]);
cmd.args(extra);
cmd.args(["--", "-p", "hello"]);
cmd.env("PATH", path_with(bins));
cmd.env("ANTHROPIC_API_KEY", "sk-ant-placeholder");
cmd.stdin(Stdio::null());
cmd.output().unwrap()
}
fn read_out(work: &Path) -> String {
std::fs::read_to_string(work.join("fake-claude.out")).unwrap_or_default()
}
#[test]
fn unconfined_launch_wires_the_egress_and_propagates_the_exit_code() {
let dir = tempfile::tempdir().unwrap();
let root = init_root(dir.path());
let bins = fake_claude(dir.path());
let work = dir.path().join("work");
std::fs::create_dir_all(&work).unwrap();
let out = launch(&root, &bins, &work, &["--unconfined"]);
assert_eq!(
out.status.code(),
Some(42),
"{}",
String::from_utf8_lossy(&out.stderr)
);
let text = read_out(&work);
assert!(
text.contains("ANTHROPIC_BASE_URL=http://127.0.0.1:"),
"{text}"
);
assert!(
text.contains("ANTHROPIC_AUTH_TOKEN=egress"),
"a placeholder, not the key: {text}"
);
assert!(
!text.contains("sk-ant-placeholder"),
"the key never reaches the harness: {text}"
);
assert!(
text.contains("mcp=200"),
"memory over MCP through the egress: {text}"
);
assert!(text.contains("nowhere=404"), "{text}");
assert!(text.contains("args=--mcp-config"), "{text}");
assert!(text.contains("-p hello"), "{text}");
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
stderr.contains("model calls via anthropic at ceiling internal"),
"{stderr}"
);
let usage = memory()
.args(["--root", root.to_str().unwrap(), "usage"])
.output()
.unwrap();
assert!(usage.status.success());
assert!(String::from_utf8_lossy(&usage.stdout).contains("no model calls recorded"));
the_run_is_kept_with_what_it_cost_the_host(&root);
}
/// One run, kept with its wall clock and what the kernel counted, and shown by `cost`.
fn the_run_is_kept_with_what_it_cost_the_host(root: &Path) {
let cost = memory()
.args(["--root", root.to_str().unwrap(), "cost", "--json"])
.output()
.unwrap();
assert!(cost.status.success());
let r: serde_json::Value = serde_json::from_slice(&cost.stdout).unwrap();
let s = &r["sessions"][0];
assert_eq!(s["runs"], 1, "{r}");
assert_eq!(s["measured"], true, "{r}");
assert!(s["wall_ms"].as_u64().unwrap() > 0, "{r}");
assert!(s["max_rss_kib"].as_u64().unwrap() > 0, "{r}");
let text = memory()
.args(["--root", root.to_str().unwrap(), "cost"])
.output()
.unwrap();
assert!(String::from_utf8_lossy(&text.stdout).contains("host resources"));
}
#[test]
fn launch_refuses_without_the_credential_its_mode_needs() {
let dir = tempfile::tempdir().unwrap();
let root = init_root(dir.path());
let bins = fake_claude(dir.path());
let out = memory()
.args([
"--root",
root.to_str().unwrap(),
"launch",
"--harness",
"claude",
"--auth",
"key",
"--unconfined",
])
.env("PATH", path_with(&bins))
.env_remove("ANTHROPIC_API_KEY")
.output()
.unwrap();
assert!(!out.status.success());
assert!(String::from_utf8_lossy(&out.stderr).contains("ANTHROPIC_API_KEY"));
let out = memory()
.args([
"--root",
root.to_str().unwrap(),
"launch",
"--harness",
"claude",
"--auth",
"gateway",
"--unconfined",
])
.env("PATH", path_with(&bins))
.env_remove("LLMAO_KEY")
.output()
.unwrap();
assert!(String::from_utf8_lossy(&out.stderr).contains("LLMAO_KEY"));
}
/// Whether this kernel lets an unprivileged process create user and network namespaces.
/// Ubuntu's AppArmor restriction says no by default; a confined test is then reported as
/// skipped rather than failed, except on CI, which is set up to run it and fails instead.
#[cfg(target_os = "linux")]
fn namespaces_available() -> bool {
let available = Command::new("unshare")
.args(["--user", "--map-root-user", "--net", "--mount", "true"])
.stdout(Stdio::null())
.stderr(Stdio::null())
.status()
.is_ok_and(|s| s.success());
assert!(
available || std::env::var_os("CI").is_none(),
"unprivileged namespaces are not available, and on CI the confined tests must run"
);
available
}
#[cfg(target_os = "linux")]
#[test]
fn confined_launch_has_no_network_and_no_host_home() {
if !namespaces_available() {
eprintln!("skipping: unprivileged namespaces are not available here");
return;
}
let dir = tempfile::tempdir().unwrap();
let root = init_root(dir.path());
// the fake harness has to live where the sandbox can see it: in the working tree
let work = dir.path().join("work");
std::fs::create_dir_all(&work).unwrap();
let bins = fake_claude(&work);
let out = launch(&root, &bins, &work, &[]);
let stderr = String::from_utf8_lossy(&out.stderr);
assert_eq!(out.status.code(), Some(42), "{stderr}");
let text = read_out(&work);
assert!(
text.contains("mcp=200"),
"the egress is reachable through the relay: {text}"
);
assert!(text.contains("ifaces=lo,"), "only loopback inside: {text}");
assert!(!text.contains("ifaces=lo,eth"), "{text}");
assert!(
text.contains("hosthome=user,"),
"the host's /home is not there: {text}"
);
assert!(text.contains("HOME=/home/user"), "{text}");
assert!(text.contains(&format!("cwd={}", work.display())), "{text}");
assert!(stderr.contains("confined to"), "{stderr}");
let left: Vec<_> = std::fs::read_dir(root.join("state/run"))
.unwrap()
.flatten()
.map(|e| e.file_name().to_string_lossy().into_owned())
.filter(|n| n.starts_with("egress-"))
.collect();
assert!(
left.is_empty(),
"the session takes its socket away: {left:?}"
);
}
#[cfg(target_os = "linux")]
#[test]
fn a_session_s_projection_holds_its_own_render_and_nothing_another_left() {
if !namespaces_available() {
eprintln!("skipping: unprivileged namespaces are not available here");
return;
}
let dir = tempfile::tempdir().unwrap();
let root = init_root(dir.path());
// what another session of the same harness, at a higher ceiling, left in the
// directory sessions of this harness once shared
let shared = root.join("state/projection/claude/dom-other");
std::fs::create_dir_all(&shared).unwrap();
std::fs::write(
shared.join("README.md"),
"- [The key](mem-x.md) · restricted: the signing key lives in the vault\n",
)
.unwrap();
let work = dir.path().join("work");
std::fs::create_dir_all(&work).unwrap();
let bins = fake_harness(
&work,
"{ echo \"seen=$(ls /memory | tr '\\n' ',')\"; cat /memory/dom-other/README.md 2>&1; } > ./fake-claude.out\nexit 0",
);
let out = launch(&root, &bins, &work, &[]);
let stderr = String::from_utf8_lossy(&out.stderr);
assert_eq!(out.status.code(), Some(0), "{stderr}");
let text = read_out(&work);
assert!(
text.contains("seen=dom-me,"),
"its own render is mounted: {text}"
);
assert!(
!text.contains("signing key"),
"another session's render is not this one's to read: {text}"
);
// and the run takes its own render away with it
let left: Vec<_> = std::fs::read_dir(root.join("state/projection/claude"))
.unwrap()
.flatten()
.map(|e| e.file_name().to_string_lossy().into_owned())
.collect();
assert_eq!(left, vec!["dom-other".to_string()], "{left:?}");
}
#[cfg(target_os = "linux")]
#[test]
fn a_launch_that_fails_after_rendering_leaves_no_render_behind() {
if !namespaces_available() {
eprintln!("skipping: unprivileged namespaces are not available here");
return;
}
let dir = tempfile::tempdir().unwrap();
let root = init_root(dir.path());
let work = dir.path().join("work");
std::fs::create_dir_all(&work).unwrap();
// no claude anywhere: the render is made, then the plan cannot find the harness
let empty = dir.path().join("empty-bin");
std::fs::create_dir_all(&empty).unwrap();
let mut cmd = memory();
cmd.args([
"--root",
root.to_str().unwrap(),
"launch",
"--harness",
"claude",
])
.args(["--auth", "key", "--workdir", work.to_str().unwrap()])
.env("PATH", &empty)
.env("ANTHROPIC_API_KEY", "sk-ant-placeholder")
.stdin(Stdio::null());
let out = cmd.output().unwrap();
assert!(!out.status.success());
let left: Vec<_> = std::fs::read_dir(root.join("state/projection/claude"))
.map(|d| d.flatten().map(|e| e.path()).collect())
.unwrap_or_default();
assert!(left.is_empty(), "a render outlived its session: {left:?}");
}
/// A root whose egress socket, `state/run/egress-<pid>.sock`, is longer than any system
/// lets a unix socket path be.
fn root_too_long_for_its_socket(dir: &Path) -> PathBuf {
let deep = dir.join("d".repeat(120));
std::fs::create_dir_all(&deep).unwrap();
init_root(&deep)
}
#[cfg(target_os = "linux")]
#[test]
fn a_confined_launch_whose_socket_cannot_be_bound_fails_and_says_why() {
if !namespaces_available() {
eprintln!("skipping: unprivileged namespaces are not available here");
return;
}
let dir = tempfile::tempdir().unwrap();
let root = root_too_long_for_its_socket(dir.path());
let work = dir.path().join("work");
std::fs::create_dir_all(&work).unwrap();
let bins = fake_claude(&work);
let out = launch(&root, &bins, &work, &[]);
let stderr = String::from_utf8_lossy(&out.stderr);
assert_eq!(out.status.code(), Some(1), "{stderr}");
assert!(
!work.join("fake-claude.out").exists(),
"the harness did not start: {stderr}"
);
let prefix = root.join("state/run/egress-").display().to_string();
let at = stderr.find(&prefix).expect(&stderr);
let end = stderr[at..].find(".sock").expect(&stderr) + ".sock".len();
let socket = &stderr[at..at + end];
assert!(
stderr.contains(&format!("{} bytes", socket.len())),
"the error names the socket's length: {stderr}"
);
}
#[test]
fn an_unconfined_launch_needs_no_socket_and_runs_from_a_long_root() {
let dir = tempfile::tempdir().unwrap();
let root = root_too_long_for_its_socket(dir.path());
let bins = fake_claude(dir.path());
let work = dir.path().join("work");
std::fs::create_dir_all(&work).unwrap();
let out = launch(&root, &bins, &work, &["--unconfined"]);
assert_eq!(
out.status.code(),
Some(42),
"{}",
String::from_utf8_lossy(&out.stderr)
);
assert!(read_out(&work).contains("mcp=200"), "{}", read_out(&work));
}
#[test]
fn an_unconfined_launch_binds_no_socket() {
let dir = tempfile::tempdir().unwrap();
let root = init_root(dir.path());
let run = root.join("state/run");
let bins = fake_harness(
dir.path(),
&format!("ls -a '{}' > ./run.ls; exit 0", run.display()),
);
let work = dir.path().join("work");
std::fs::create_dir_all(&work).unwrap();
let out = launch(&root, &bins, &work, &["--unconfined"]);
let stderr = String::from_utf8_lossy(&out.stderr);
assert_eq!(out.status.code(), Some(0), "{stderr}");
let seen = std::fs::read_to_string(work.join("run.ls")).unwrap();
assert!(
!seen.contains("egress-"),
"state/run while the harness ran: {seen}"
);
}
/// `amem inside` with this requests directory, a subscription login in a host home, and
/// a prompt; the caller answers the requests.
fn inside(root: &Path, requests: &Path, host_home: &Path) -> std::process::Child {
std::fs::create_dir_all(host_home.join(".claude")).unwrap();
std::fs::write(host_home.join(".claude/.credentials.json"), "{}").unwrap();
memory()
.args([
"--root",
root.to_str().unwrap(),
"inside",
"--harness",
"claude",
"--auth",
"subscription",
])
.args([
"--requests",
requests.to_str().unwrap(),
"--host-home",
host_home.to_str().unwrap(),
])
.args(["--prompt", "say the answer", "--no-handoff"])
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.unwrap()
}
#[test]
fn inside_with_a_requests_directory_too_long_for_its_socket_fails_and_says_why() {
let dir = tempfile::tempdir().unwrap();
let root = init_root(dir.path());
let requests = dir.path().join("v".repeat(120));
std::fs::create_dir_all(&requests).unwrap();
let socket = requests.join("egress.sock");
let out = inside(&root, &requests, &dir.path().join("host-home"))
.wait_with_output()
.unwrap();
let stderr = String::from_utf8_lossy(&out.stderr);
assert_eq!(out.status.code(), Some(1), "{stderr}");
assert!(
stderr.contains(&format!("{} bytes", socket.as_os_str().len())),
"the error names the socket's length: {stderr}"
);
assert!(
!requests.join("req-0.json").exists(),
"no run was asked for"
);
}
#[test]
fn a_relay_that_cannot_listen_does_not_run_the_harness() {
let dir = tempfile::tempdir().unwrap();
let taken = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
let listen = taken.local_addr().unwrap().to_string();
let marker = dir.path().join("ran");
let out = memory()
.args(["relay", "--socket"])
.arg(dir.path().join("egress.sock"))
.args(["--listen", &listen, "--", "touch"])
.arg(&marker)
.stdin(Stdio::null())
.output()
.unwrap();
let stderr = String::from_utf8_lossy(&out.stderr);
assert_eq!(out.status.code(), Some(1), "{stderr}");
assert!(!marker.exists(), "the harness ran without a route out");
assert!(stderr.contains(&listen), "{stderr}");
}
// ---- non-interactive: a prompt, and a task from the ledger ----
/// A fake harness from a shell body. It receives the launcher's arguments; `$*` holds
/// the prompt among them.
fn fake_harness(dir: &Path, body: &str) -> PathBuf {
let bins = dir.join("bins");
std::fs::create_dir_all(&bins).unwrap();
let p = bins.join("claude");
std::fs::write(&p, format!("#!/bin/sh\n{body}\n")).unwrap();
use std::os::unix::fs::PermissionsExt;
std::fs::set_permissions(&p, std::fs::Permissions::from_mode(0o755)).unwrap();
bins
}
/// How many times `stderr` says that a process died of `signal`, numbered `number`.
fn deaths_by(stderr: &str, signal: &str, number: i32) -> usize {
stderr
.lines()
.filter(|l| l.contains(&format!("killed by {signal} (signal {number})")))
.count()
}
#[test]
fn an_unconfined_harness_killed_by_a_signal_exits_128_and_its_number_and_says_so() {
let dir = tempfile::tempdir().unwrap();
let root = init_root(dir.path());
let bins = fake_harness(dir.path(), "kill -ABRT $$");
let work = dir.path().join("work");
std::fs::create_dir_all(&work).unwrap();
let out = launch(&root, &bins, &work, &["--unconfined"]);
let stderr = String::from_utf8_lossy(&out.stderr);
assert_eq!(out.status.code(), Some(128 + 6), "{stderr}");
assert_eq!(deaths_by(&stderr, "SIGABRT", 6), 1, "{stderr}");
}
#[test]
fn the_relay_reports_a_harness_killed_by_a_signal_as_128_and_its_number() {
let dir = tempfile::tempdir().unwrap();
let out = memory()
.args(["relay", "--socket"])
.arg(dir.path().join("egress.sock"))
.args(["--listen", "127.0.0.1:0", "--", "sh", "-c", "kill -KILL $$"])
.stdin(Stdio::null())
.output()
.unwrap();
let stderr = String::from_utf8_lossy(&out.stderr);
assert_eq!(out.status.code(), Some(128 + 9), "{stderr}");
assert_eq!(deaths_by(&stderr, "SIGKILL", 9), 1, "{stderr}");
}
#[cfg(target_os = "linux")]
#[test]
fn a_confined_harness_killed_by_a_signal_exits_128_and_its_number_and_says_so_once() {
if !namespaces_available() {
eprintln!("skipping: unprivileged namespaces are not available here");
return;
}
let dir = tempfile::tempdir().unwrap();
let root = init_root(dir.path());
let work = dir.path().join("work");
std::fs::create_dir_all(&work).unwrap();
let bins = fake_harness(&work, "kill -ABRT $$");
let out = launch(&root, &bins, &work, &[]);
let stderr = String::from_utf8_lossy(&out.stderr);
assert_eq!(out.status.code(), Some(128 + 6), "{stderr}");
assert_eq!(deaths_by(&stderr, "SIGABRT", 6), 1, "{stderr}");
}
#[test]
fn a_harness_ended_by_sigint_or_sigpipe_exits_128_and_its_number_quietly() {
for (signal, number) in [("INT", 2), ("PIPE", 13)] {
let dir = tempfile::tempdir().unwrap();
let out = memory()
.args(["relay", "--socket"])
.arg(dir.path().join("egress.sock"))
.args(["--listen", "127.0.0.1:0", "--", "sh", "-c"])
.arg(format!("kill -{signal} $$"))
.stdin(Stdio::null())
.output()
.unwrap();
let stderr = String::from_utf8_lossy(&out.stderr);
assert_eq!(
out.status.code(),
Some(128 + number),
"SIG{signal}: {stderr}"
);
assert!(!stderr.contains("killed by"), "SIG{signal}: {stderr}");
}
}
/// The pid of the relay whose harness command line names `harness`, from `/proc`.
#[cfg(target_os = "linux")]
fn relay_of(harness: &Path) -> Option<String> {
let harness = harness.display().to_string();
std::fs::read_dir("/proc").ok()?.flatten().find_map(|e| {
let pid = e.file_name().to_string_lossy().into_owned();
let cmdline = std::fs::read(e.path().join("cmdline")).ok()?;
let args: Vec<String> = cmdline
.split(|b| *b == 0)
.map(|a| String::from_utf8_lossy(a).into_owned())
.collect();
(args.get(1).is_some_and(|a| a == "relay") && args.iter().any(|a| a.contains(&harness)))
.then_some(pid)
})
}
#[cfg(target_os = "linux")]
#[test]
fn a_confined_relay_killed_by_a_signal_exits_128_and_its_number_and_says_so_once() {
if !namespaces_available() {
eprintln!("skipping: unprivileged namespaces are not available here");
return;
}
let dir = tempfile::tempdir().unwrap();
let root = init_root(dir.path());
let work = dir.path().join("work");
std::fs::create_dir_all(&work).unwrap();
let bins = fake_harness(&work, "touch ./started; exec sleep 60");
let mut cmd = memory();
cmd.args(["--root", root.to_str().unwrap(), "launch"])
.args(["--harness", "claude", "--auth", "key"])
.args(["--workdir", work.to_str().unwrap(), "--", "-p", "hello"])
.env("PATH", path_with(&bins))
.env("ANTHROPIC_API_KEY", "sk-ant-placeholder")
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::piped());
let child = cmd.spawn().unwrap();
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(30);
while !work.join("started").exists() && std::time::Instant::now() < deadline {
std::thread::sleep(std::time::Duration::from_millis(50));
}
let relay = relay_of(&bins).expect("the relay is running");
let killed = Command::new("kill")
.args(["-KILL", &relay])
.status()
.unwrap();
assert!(killed.success());
let out = child.wait_with_output().unwrap();
let stderr = String::from_utf8_lossy(&out.stderr);
assert_eq!(out.status.code(), Some(128 + 9), "{stderr}");
let said = stderr
.lines()
.filter(|l| l.contains("the relay was killed by SIGKILL (signal 9)"))
.count();
assert_eq!(said, 1, "{stderr}");
}
/// Shell that speaks MCP to the egress with curl: initialize, write a handoff, complete
/// the task named in the prompt.
const COMPLETES_ITS_TASK: &str = r#"
task=$(printf '%s' "$*" | grep -o 'task-[a-z0-9]*-[a-z0-9]*' | head -1)
base="${ANTHROPIC_BASE_URL%/anthropic}"
H1='content-type: application/json'; H2='accept: application/json, text/event-stream'
sid=$(curl -s -D - -o /dev/null -X POST "$base/mcp" -H "$H1" -H "$H2" -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"fake","version":"0"}}}' | tr -d '\r' | awk 'tolower($1)=="mcp-session-id:"{print $2}')
curl -s -o /dev/null -X POST "$base/mcp" -H "$H1" -H "$H2" -H "mcp-session-id: $sid" -d '{"jsonrpc":"2.0","method":"notifications/initialized"}'
hid=$(curl -s -X POST "$base/mcp" -H "$H1" -H "$H2" -H "mcp-session-id: $sid" -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"write","arguments":{"body":"Import finished, all five steps.","entry_type":"handoff","title":"Import done"}}}' | grep -o 'mem-[a-z0-9]*-[a-z0-9]*' | head -1)
curl -s -o /dev/null -X POST "$base/mcp" -H "$H1" -H "$H2" -H "mcp-session-id: $sid" -d "{\"jsonrpc\":\"2.0\",\"id\":3,\"method\":\"tools/call\",\"params\":{\"name\":\"complete\",\"arguments\":{\"id\":\"$task\",\"handoff\":\"$hid\"}}}"
echo "finished $task with $hid"
"#;
fn cli_text(root: &Path, args: &[&str]) -> String {
let out = memory()
.args(["--root", root.to_str().unwrap()])
.args(args)
.output()
.unwrap();
assert!(
out.status.success(),
"{}",
String::from_utf8_lossy(&out.stderr)
);
String::from_utf8_lossy(&out.stdout).into_owned()
}
fn cli(root: &Path, args: &[&str]) -> serde_json::Value {
let text = cli_text(root, args);
serde_json::from_str(&text).unwrap_or(serde_json::Value::Null)
}
fn file_task(root: &Path, title: &str) -> String {
let t = cli(
root,
&[
"task",
"create",
title,
"--priority",
"5",
"--body",
"Steps 3 to 5.",
],
);
let id = t["id"].as_str().unwrap().to_string();
cli(root, &["task", "admit", &id]);
id
}
fn work(root: &Path, bins: &Path, workdir: &Path) -> Output {
let mut cmd = memory();
cmd.args([
"--root",
root.to_str().unwrap(),
"work",
"--harness",
"claude",
"--auth",
"key",
"--unconfined",
]);
cmd.args([
"--workdir",
workdir.to_str().unwrap(),
"--lease-seconds",
"60",
]);
cmd.env("PATH", path_with(bins));
cmd.env("ANTHROPIC_API_KEY", "sk-ant-placeholder");
cmd.stdin(Stdio::null());
cmd.output().unwrap()
}
#[test]
fn work_leaves_a_completed_task_complete() {
let dir = tempfile::tempdir().unwrap();
let root = init_root(dir.path());
let bins = fake_harness(dir.path(), COMPLETES_ITS_TASK);
let work_dir = dir.path().join("work");
std::fs::create_dir_all(&work_dir).unwrap();
let id = file_task(&root, "Finish the import");
let out = work(&root, &bins, &work_dir);
let stderr = String::from_utf8_lossy(&out.stderr);
let stdout = String::from_utf8_lossy(&out.stdout);
assert_eq!(out.status.code(), Some(0), "{stderr}\n{stdout}");
assert!(
stderr.contains(&format!("claimed {id}: Finish the import")),
"{stderr}"
);
assert!(stderr.contains("completed, handoff mem-"), "{stderr}");
let t = cli(&root, &["task", "get", &id]);
assert_eq!(t["status"], "completed");
let h = t["handoff"].as_str().unwrap();
let e = cli_text(&root, &["get", h]);
assert!(e.contains("all five steps"), "{e}");
}
#[test]
fn work_files_the_output_and_releases_when_the_harness_stops_short() {
let dir = tempfile::tempdir().unwrap();
let root = init_root(dir.path());
let bins = fake_harness(
dir.path(),
"echo \"args=$*\" > ./fake.out; echo 'I looked at step 3 and stopped.'; exit 0",
);
let work_dir = dir.path().join("work");
std::fs::create_dir_all(&work_dir).unwrap();
let id = file_task(&root, "Finish the import");
let out = work(&root, &bins, &work_dir);
let stderr = String::from_utf8_lossy(&out.stderr);
assert_eq!(out.status.code(), Some(4), "{stderr}");
assert!(
stderr.contains("released; its output is filed as handoff"),
"{stderr}"
);
let t = cli(&root, &["task", "get", &id]);
assert_eq!(t["status"], "admitted", "back in the pool: {t}");
let h = t["handoff"]
.as_str()
.expect("the handoff rides with the task");
let e = cli_text(&root, &["get", h]);
assert!(e.contains("I looked at step 3"), "{e}");
// the harness saw its headless form, the task, and T0
let args = std::fs::read_to_string(work_dir.join("fake.out")).unwrap();
assert!(args.contains("-p "), "{args}");
assert!(args.contains(&id), "{args}");
assert!(
args.contains("--dangerously-skip-permissions"),
"the sandbox is the boundary: {args}"
);
assert!(args.contains("# T0 for dom-me"), "{args}");
assert!(args.contains("do not call `heartbeat`"), "{args}");
// the next claimant is told where the last attempt stopped
let out = work(&root, &bins, &work_dir);
assert_eq!(out.status.code(), Some(4));
let args = std::fs::read_to_string(work_dir.join("fake.out")).unwrap();
assert!(args.contains("Where the last attempt stopped"), "{args}");
assert!(args.contains("I looked at step 3"), "{args}");
}
#[test]
fn work_with_nothing_claimable_says_so() {
let dir = tempfile::tempdir().unwrap();
let root = init_root(dir.path());
let bins = fake_harness(dir.path(), "exit 0");
let work_dir = dir.path().join("work");
std::fs::create_dir_all(&work_dir).unwrap();
let out = work(&root, &bins, &work_dir);
assert_eq!(out.status.code(), Some(3));
assert!(String::from_utf8_lossy(&out.stderr).contains("nothing claimable"));
}
#[test]
fn a_prompted_launch_runs_headless_and_insists_on_a_handoff() {
let dir = tempfile::tempdir().unwrap();
let root = init_root(dir.path());
let bins = fake_harness(dir.path(), "echo \"args=$*\"; echo 'the answer'; exit 0");
let work_dir = dir.path().join("work");
std::fs::create_dir_all(&work_dir).unwrap();
let run = |extra: &[&str]| {
let mut cmd = memory();
cmd.args([
"--root",
root.to_str().unwrap(),
"launch",
"--harness",
"claude",
"--auth",
"key",
"--unconfined",
]);
cmd.args([
"--workdir",
work_dir.to_str().unwrap(),
"--prompt",
"say the answer",
]);
cmd.args(extra);
cmd.env("PATH", path_with(&bins));
cmd.env("ANTHROPIC_API_KEY", "sk-ant-placeholder");
cmd.stdin(Stdio::null());
cmd.output().unwrap()
};
let out = run(&[]);
let stdout = String::from_utf8_lossy(&out.stdout);
let stderr = String::from_utf8_lossy(&out.stderr);
assert_eq!(out.status.code(), Some(4), "{stderr}");
assert!(
stdout.contains("args=") && stdout.contains("-p say the answer"),
"{stdout}"
);
assert!(
stdout.contains("the answer"),
"the harness's output is printed: {stdout}"
);
assert!(stderr.contains("left no handoff"), "{stderr}");
let out = run(&["--no-handoff"]);
assert_eq!(
out.status.code(),
Some(0),
"{}",
String::from_utf8_lossy(&out.stderr)
);
}
/// A confined prompted run captures the harness's standard output through a pipe, which
/// holds 64 KiB. A harness that writes more than that finishes, and all of what it wrote
/// reaches the launcher's own output. Bounded in time: a launch that never returns is
/// this test failing, and its process group is killed so nothing outlives the test.
#[cfg(target_os = "linux")]
#[test]
fn a_confined_prompted_run_captures_more_than_a_pipe_holds() {
use std::os::unix::process::CommandExt;
use std::time::{Duration, Instant};
if !namespaces_available() {
eprintln!("skipping: unprivileged namespaces are not available here");
return;
}
let dir = tempfile::tempdir().unwrap();
let _reap = Reap(dir.path().to_path_buf());
let root = init_root(dir.path());
let work = dir.path().join("work");
std::fs::create_dir_all(&work).unwrap();
let bins = fake_harness(
&work,
"printf 'begin\\n'; head -c 200000 /dev/zero | tr '\\0' a; printf '\\nend\\n'; exit 0",
);
let stdout = dir.path().join("stdout");
let stderr = dir.path().join("stderr");
let mut cmd = memory();
cmd.args([
"--root",
root.to_str().unwrap(),
"launch",
"--harness",
"claude",
"--auth",
"key",
])
.args(["--workdir", work.to_str().unwrap()])
.args(["--prompt", "hello", "--no-handoff"])
.env("PATH", path_with(&bins))
.env("ANTHROPIC_API_KEY", "sk-ant-placeholder")
.stdin(Stdio::null())
.stdout(std::fs::File::create(&stdout).unwrap())
.stderr(std::fs::File::create(&stderr).unwrap())
.process_group(0);
let mut child = cmd.spawn().unwrap();
let deadline = Instant::now() + Duration::from_secs(90);
let status = loop {
if let Some(s) = child.try_wait().unwrap() {
break s;
}
if Instant::now() > deadline {
Command::new("kill")
.args(["-KILL", "--", &format!("-{}", child.id())])
.status()
.ok();
child.wait().ok();
panic!(
"the launch had not returned after 90 s, with {} bytes out: {}",
std::fs::metadata(&stdout).map(|m| m.len()).unwrap_or(0),
std::fs::read_to_string(&stderr).unwrap_or_default()
);
}
std::thread::sleep(Duration::from_millis(100));
};
let err = std::fs::read_to_string(&stderr).unwrap();
assert_eq!(status.code(), Some(0), "{err}");
assert!(err.contains("confined to"), "{err}");
let out = std::fs::read_to_string(&stdout).unwrap();
let want = format!("begin\n{}\nend\n", "a".repeat(200_000));
assert!(
out.contains(&want),
"the harness's output, whole: {} bytes out",
out.len()
);
}
// ---- a launcher told to stop, and one killed ----
/// A confined prompted launch, with its temporary
/// directory at `tmp`, so the scratch directory it makes is where the test looks. Its
/// output goes to `<work>.out` and `<work>.err`.
#[cfg(target_os = "linux")]
fn start_confined(root: &Path, work: &Path, tmp: &Path) -> std::process::Child {
start_amem(
root,
work,
tmp,
&["launch", "--prompt", "hello", "--no-handoff"],
)
}
/// `amem <args>` for the claude harness on a key, in `work`, as [`start_confined`] runs it.
#[cfg(target_os = "linux")]
fn start_amem(root: &Path, work: &Path, tmp: &Path, args: &[&str]) -> std::process::Child {
amem_in(root, work, tmp, args)
.stdout(std::fs::File::create(work.with_extension("out")).unwrap())
.spawn()
.unwrap()
}
/// As [`start_amem`], with standard output a pipe whose reader is already gone: a
/// launcher piped into a program that has exited.
#[cfg(target_os = "linux")]
fn start_amem_into_a_closed_pipe(
root: &Path,
work: &Path,
tmp: &Path,
args: &[&str],
) -> std::process::Child {
let mut child = amem_in(root, work, tmp, args)
.stdout(Stdio::piped())
.spawn()
.unwrap();
drop(child.stdout.take());
child
}
/// The command [`start_amem`] runs, its standard output not yet set.
#[cfg(target_os = "linux")]
fn amem_in(root: &Path, work: &Path, tmp: &Path, args: &[&str]) -> Command {
let mut cmd = memory();
cmd.args(["--root", root.to_str().unwrap()])
.args(args)
.args(["--harness", "claude", "--auth", "key"])
.args(["--workdir", work.to_str().unwrap()]);
cmd.env("PATH", path_with(&work.join("bins")))
.env("ANTHROPIC_API_KEY", "sk-ant-placeholder")
.env("TMPDIR", tmp)
.stdin(Stdio::null())
.stderr(std::fs::File::create(work.with_extension("err")).unwrap());
cmd
}
/// A working tree whose harness says it started, then runs until it is killed.
#[cfg(target_os = "linux")]
fn sleeper(dir: &Path, name: &str) -> PathBuf {
let work = dir.join(name);
std::fs::create_dir_all(&work).unwrap();
fake_harness(&work, "touch ./started\nsleep 300");
work
}
/// A launch whose harness has started and runs until it is killed, and its working tree.
#[cfg(target_os = "linux")]
fn running_sleeper(
root: &Path,
dir: &Path,
name: &str,
tmp: &Path,
) -> (std::process::Child, PathBuf) {
let work = sleeper(dir, name);
let launch = start_confined(root, &work, tmp);
assert!(
started(&work, 60),
"{}",
std::fs::read_to_string(work.with_extension("err")).unwrap()
);
(launch, work)
}
/// Send `child` the signal named `sig`, as `kill` names it.
#[cfg(target_os = "linux")]
fn signal(child: &std::process::Child, sig: &str) {
let ok = Command::new("kill")
.args([&format!("-{sig}"), &child.id().to_string()])
.status()
.unwrap();
assert!(ok.success());
}
/// Wait for `child` to exit, `secs` at most.
#[cfg(target_os = "linux")]
fn exited(child: &mut std::process::Child, secs: u64) -> Option<std::process::ExitStatus> {
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(secs);
while std::time::Instant::now() < deadline {
if let Some(s) = child.try_wait().unwrap() {
return Some(s);
}
std::thread::sleep(std::time::Duration::from_millis(100));
}
None
}
/// Wait for the harness in `work` to have started, `secs` at most.
#[cfg(target_os = "linux")]
fn started(work: &Path, secs: u64) -> bool {
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(secs);
while std::time::Instant::now() < deadline {
if work.join("started").exists() {
return true;
}
std::thread::sleep(std::time::Duration::from_millis(100));
}
false
}
/// The processes whose command line names something under `dir`, or whose working
/// directory is under it, as pid and command line.
#[cfg(target_os = "linux")]
fn processes_under(dir: &Path) -> Vec<(String, String)> {
let needle = dir.to_string_lossy().into_owned();
std::fs::read_dir("/proc")
.unwrap()
.flatten()
.map(|e| e.file_name().to_string_lossy().into_owned())
.filter(|pid| pid.bytes().all(|b| b.is_ascii_digit()))
.filter_map(|pid| {
let at = Path::new("/proc").join(&pid);
let c = std::fs::read(at.join("cmdline")).ok()?;
let c = String::from_utf8_lossy(&c).replace('\0', " ");
// or working there: a harness's child whose command line names nothing
let cwd = std::fs::read_link(at.join("cwd")).ok();
(c.contains(&needle) || cwd.is_some_and(|d| d.starts_with(dir))).then_some((pid, c))
})
.collect()
}
/// Whether no process names anything under `dir` within `secs`.
#[cfg(target_os = "linux")]
fn gone_within(dir: &Path, secs: u64) -> bool {
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(secs);
while std::time::Instant::now() < deadline {
if processes_under(dir).is_empty() {
return true;
}
std::thread::sleep(std::time::Duration::from_millis(100));
}
false
}
/// The mounts under `dir`, from this process's mount table: no mountpoint is touched,
/// since one a launcher left may not answer.
#[cfg(target_os = "linux")]
fn mounts_under(dir: &Path) -> Vec<String> {
let needle = format!("{}/", dir.display());
std::fs::read_to_string("/proc/self/mountinfo")
.unwrap()
.lines()
.filter_map(|l| l.split(' ').nth(4))
.filter(|m| m.starts_with(&needle))
.map(str::to_string)
.collect()
}
/// The names in `dir`, listed without a stat of any of them.
#[cfg(target_os = "linux")]
fn names_in(dir: &Path) -> Vec<String> {
let mut names: Vec<String> = std::fs::read_dir(dir)
.map(|d| {
d.flatten()
.map(|e| e.file_name().to_string_lossy().into_owned())
.collect()
})
.unwrap_or_default();
names.sort();
names
}
/// Kills, when dropped, every process whose command line names something under its
/// directory, and lazily unmounts what is left there: a test that fails leaves nothing
/// running or mounted. A run's harness goes with its relay, pid 1 of its namespace.
#[cfg(target_os = "linux")]
struct Reap(PathBuf);
#[cfg(target_os = "linux")]
impl Drop for Reap {
fn drop(&mut self) {
for (pid, _) in processes_under(&self.0) {
Command::new("kill")
.args(["-KILL", &pid])
.stderr(Stdio::null())
.status()
.ok();
}
std::thread::sleep(std::time::Duration::from_millis(500));
for m in mounts_under(&self.0) {
Command::new("fusermount3").args(["-uz", &m]).status().ok();
}
}
}
/// What a launch leaves: the processes of its run, its mount, its scratch directory,
/// its socket and its render. A SIGTERM ends the run and removes every one, as a run
/// that ends of itself does, and the launcher exits as a process the signal ended.
#[cfg(target_os = "linux")]
#[test]
fn a_launcher_told_to_stop_ends_its_run_and_leaves_nothing() {
if !namespaces_available() {
eprintln!("skipping: unprivileged namespaces are not available here");
return;
}
let dir = tempfile::tempdir().unwrap();
let root = init_root(dir.path());
let tmp = dir.path().join("tmp");
std::fs::create_dir_all(&tmp).unwrap();
let _reap = Reap(dir.path().to_path_buf());
let (mut launch, work) = running_sleeper(&root, dir.path(), "work", &tmp);
assert!(
!mounts_under(&root).is_empty(),
"the projection is a live mount"
);
assert_eq!(names_in(&tmp).len(), 1, "the run has its scratch directory");
signal(&launch, "TERM");
let status = exited(&mut launch, 30);
let err = std::fs::read_to_string(work.with_extension("err")).unwrap();
assert_eq!(status.and_then(|s| s.code()), Some(128 + 15), "{err}");
// said once, as the stop; the kill that ends the sandbox is not a death to report
assert_eq!(err.matches("SIGTERM").count(), 1, "{err}");
assert!(!err.contains("killed by"), "{err}");
assert_eq!(processes_under(dir.path()), Vec::new());
assert_eq!(mounts_under(&root), Vec::<String>::new());
assert_eq!(
names_in(&tmp),
Vec::<String>::new(),
"the scratch directory"
);
assert_eq!(names_in(&root.join("state/run")), Vec::<String>::new());
assert_eq!(
names_in(&root.join("state/mount/claude")),
Vec::<String>::new()
);
assert_eq!(
names_in(&root.join("state/projection/claude")),
Vec::<String>::new()
);
}
/// SIGKILL `killed`, a running launch in `work`, and check what it leaves: no process of
/// its run, which dies with it, and its scratch directory, socket and mount.
#[cfg(target_os = "linux")]
fn killed_by_sigkill(root: &Path, tmp: &Path, killed: &mut std::process::Child, work: &Path) {
signal(killed, "KILL");
killed.wait().unwrap();
// the sandbox dies with its launcher: the intermediate, the relay, the harness
assert!(gone_within(work, 5), "{:?}", processes_under(work));
let killed_socket = format!("egress-{}.sock", killed.id());
assert!(names_in(&root.join("state/run")).contains(&killed_socket));
let killed_scratch = format!("memory-sandbox-{}", killed.id());
assert!(names_in(tmp).contains(&killed_scratch));
assert_eq!(
mounts_under(root).len(),
2,
"the killed launch's mount is left"
);
}
/// SIGKILL cannot be caught: a launcher killed by it leaves its scratch directory and its
/// mount, with nothing running to serve it. The next launch sweeps both, and what a
/// killed launcher of long ago left, and keeps what a live launcher holds.
#[cfg(target_os = "linux")]
#[test]
fn the_next_launch_sweeps_what_a_killed_one_left_and_keeps_a_live_one_s() {
if !namespaces_available() {
eprintln!("skipping: unprivileged namespaces are not available here");
return;
}
let dir = tempfile::tempdir().unwrap();
let root = init_root(dir.path());
let tmp = dir.path().join("tmp");
std::fs::create_dir_all(&tmp).unwrap();
// left by a launcher long gone: its pid is no process now
let mut gone = Command::new("/bin/sh").args(["-c", ":"]).spawn().unwrap();
let gone_pid = gone.id();
gone.wait().unwrap();
std::fs::create_dir(tmp.join(format!("memory-sandbox-{gone_pid}"))).unwrap();
let _reap = Reap(dir.path().to_path_buf());
let (mut live, _) = running_sleeper(&root, dir.path(), "live", &tmp);
let live_mounts = mounts_under(&root);
assert_eq!(live_mounts.len(), 1);
let (mut killed, killed_work) = running_sleeper(&root, dir.path(), "killed", &tmp);
assert_eq!(mounts_under(&root).len(), 2);
killed_by_sigkill(&root, &tmp, &mut killed, &killed_work);
let next_work = dir.path().join("next");
std::fs::create_dir_all(&next_work).unwrap();
fake_harness(&next_work, "exit 0");
let mut next = start_confined(&root, &next_work, &tmp);
let status = exited(&mut next, 60);
let err = std::fs::read_to_string(next_work.with_extension("err")).unwrap();
assert_eq!(status.and_then(|s| s.code()), Some(0), "{err}");
assert_eq!(
names_in(&tmp),
vec![format!("memory-sandbox-{}", live.id())],
"the killed launch's scratch directory and the old one are swept"
);
assert_eq!(
mounts_under(&root),
live_mounts,
"the killed launch's mount is swept and the live launch's stays"
);
let live_session = Path::new(&live_mounts[0]).file_name().unwrap();
let live_session = live_session.to_string_lossy().into_owned();
assert_eq!(
names_in(&root.join("state/mount/claude")),
vec![live_session.clone(), format!("{live_session}.lock")],
"the live launch's mountpoint and its record"
);
assert_eq!(
names_in(&root.join("state/run")),
vec![format!("egress-{}.sock", live.id())],
"the killed launch's socket is swept and the live launch's stays"
);
assert!(
live.try_wait().unwrap().is_none(),
"the live launch runs on"
);
// and it still ends cleanly
signal(&live, "TERM");
assert_eq!(exited(&mut live, 30).and_then(|s| s.code()), Some(128 + 15));
assert_eq!(mounts_under(&root), Vec::<String>::new());
assert_eq!(names_in(&tmp), Vec::<String>::new());
}
/// A terminal that hangs up sends SIGHUP to its session's leader, and refuses every write
/// after it. The launcher still ends the run and cleans up: no panic on a write to the
/// terminal that is gone stops it first.
#[cfg(target_os = "linux")]
#[test]
fn a_launcher_whose_terminal_hangs_up_still_cleans_up() {
if !namespaces_available() {
eprintln!("skipping: unprivileged namespaces are not available here");
return;
}
if !script_available() {
return;
}
let dir = tempfile::tempdir().unwrap();
let root = init_root(dir.path());
let tmp = dir.path().join("tmp");
std::fs::create_dir_all(&tmp).unwrap();
let work = sleeper(dir.path(), "work");
let _reap = Reap(dir.path().to_path_buf());
// the launcher as the session leader of a terminal of its own, which script makes
let line = format!(
"exec {} --root {} launch --prompt hello --no-handoff --harness claude --auth key --workdir {}",
env!("CARGO_BIN_EXE_amem"),
root.display(),
work.display()
);
let mut terminal = Command::new("script")
.args(["-qfec", &line, "/dev/null"])
.env("PATH", path_with(&work.join("bins")))
.env("ANTHROPIC_API_KEY", "sk-ant-placeholder")
.env("TMPDIR", &tmp)
.stdin(Stdio::piped())
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
.unwrap();
assert!(started(&work, 60));
assert!(!mounts_under(&root).is_empty());
// the terminal goes: its other end closes, and the kernel hangs it up
signal(&terminal, "KILL");
terminal.wait().unwrap();
assert!(
gone_within(dir.path(), 30),
"{:?}",
processes_under(dir.path())
);
assert_eq!(mounts_under(&root), Vec::<String>::new());
assert_eq!(
names_in(&tmp),
Vec::<String>::new(),
"the scratch directory"
);
assert_eq!(
names_in(&root.join("state/run")),
Vec::<String>::new(),
"the socket"
);
assert_eq!(
names_in(&root.join("state/mount/claude")),
Vec::<String>::new()
);
assert_eq!(
names_in(&root.join("state/projection/claude")),
Vec::<String>::new(),
"the render"
);
}
/// An unconfined run is told to stop as a confined one is: at once, with 143.
#[cfg(target_os = "linux")]
#[test]
fn an_unconfined_launcher_told_to_stop_ends_its_run_at_once() {
let dir = tempfile::tempdir().unwrap();
let root = init_root(dir.path());
let tmp = dir.path().join("tmp");
std::fs::create_dir_all(&tmp).unwrap();
let work = dir.path().join("work");
std::fs::create_dir_all(&work).unwrap();
// exec, so the process the launcher spawned is the one that sleeps
fake_harness(&work, "touch ./started\nexec sleep 300");
let _reap = Reap(dir.path().to_path_buf());
let mut launch = start_amem(
&root,
&work,
&tmp,
&[
"launch",
"--unconfined",
"--prompt",
"hello",
"--no-handoff",
],
);
assert!(
started(&work, 60),
"{}",
std::fs::read_to_string(work.with_extension("err")).unwrap()
);
signal(&launch, "TERM");
let status = exited(&mut launch, 10);
let err = std::fs::read_to_string(work.with_extension("err")).unwrap();
assert_eq!(status.and_then(|s| s.code()), Some(128 + 15), "{err}");
assert_eq!(names_in(&root.join("state/run")), Vec::<String>::new());
}
/// A launcher that is stopped (Ctrl-Z) does not answer for its mount. Another launch
/// does not ask it anything: it starts, runs and ends, and the stopped launcher's mount
/// is kept for it.
#[cfg(target_os = "linux")]
#[test]
fn a_stopped_launcher_s_mount_is_kept_and_not_asked() {
if !namespaces_available() {
eprintln!("skipping: unprivileged namespaces are not available here");
return;
}
let dir = tempfile::tempdir().unwrap();
let root = init_root(dir.path());
let tmp = dir.path().join("tmp");
std::fs::create_dir_all(&tmp).unwrap();
let _reap = Reap(dir.path().to_path_buf());
let (mut stopped, _) = running_sleeper(&root, dir.path(), "stopped", &tmp);
let its_mounts = mounts_under(&root);
assert_eq!(its_mounts.len(), 1);
signal(&stopped, "STOP");
let next_work = dir.path().join("next");
std::fs::create_dir_all(&next_work).unwrap();
fake_harness(&next_work, "exit 0");
let mut next = start_confined(&root, &next_work, &tmp);
let status = exited(&mut next, 60);
signal(&stopped, "CONT");
let err = std::fs::read_to_string(next_work.with_extension("err")).unwrap();
assert_eq!(status.and_then(|s| s.code()), Some(0), "{err}");
assert_eq!(
mounts_under(&root),
its_mounts,
"the stopped launcher's mount stays"
);
signal(&stopped, "TERM");
assert_eq!(
exited(&mut stopped, 30).and_then(|s| s.code()),
Some(128 + 15)
);
assert_eq!(mounts_under(&root), Vec::<String>::new());
}
/// `amem work --loop` told to stop mid-run settles the task it holds, as when the harness
/// stops of itself: back in the pool with a handoff. It claims nothing more.
#[cfg(target_os = "linux")]
#[test]
fn a_worker_told_to_stop_releases_its_task_with_a_handoff_and_claims_no_other() {
if !namespaces_available() {
eprintln!("skipping: unprivileged namespaces are not available here");
return;
}
let dir = tempfile::tempdir().unwrap();
let root = init_root(dir.path());
let tmp = dir.path().join("tmp");
std::fs::create_dir_all(&tmp).unwrap();
let ids = [file_task(&root, "First"), file_task(&root, "Second")];
let work = sleeper(dir.path(), "work");
let _reap = Reap(dir.path().to_path_buf());
let mut worker = start_amem(
&root,
&work,
&tmp,
&["work", "--loop", "--lease-seconds", "60"],
);
assert!(
started(&work, 60),
"{}",
std::fs::read_to_string(work.with_extension("err")).unwrap()
);
signal(&worker, "TERM");
let status = exited(&mut worker, 30);
let err = std::fs::read_to_string(work.with_extension("err")).unwrap();
assert_eq!(status.and_then(|s| s.code()), Some(128 + 15), "{err}");
let claimed: Vec<&String> = ids
.iter()
.filter(|id| err.contains(&format!("claimed {id}")))
.collect();
assert_eq!(claimed.len(), 1, "one task claimed: {err}");
for id in &ids {
let t = cli(&root, &["task", "get", id]);
assert_eq!(t["status"], "admitted", "in the pool: {t}");
if claimed.contains(&id) {
assert!(t["handoff"].as_str().is_some(), "with a handoff: {t}");
} else {
assert!(t["handoff"].is_null(), "never claimed: {t}");
}
}
}
/// A worker in `dir/work` whose harness prints, says it started, then runs until killed;
/// one task filed for it.
#[cfg(target_os = "linux")]
fn a_task_and_a_printing_sleeper(root: &Path, dir: &Path) -> (String, PathBuf) {
let id = file_task(root, "First");
let work = dir.join("work");
std::fs::create_dir_all(&work).unwrap();
fake_harness(&work, "echo working\ntouch ./started\nsleep 300");
(id, work)
}
/// Whether task `id` is back in the pool, and with a handoff.
#[cfg(target_os = "linux")]
fn released(root: &Path, id: &str) -> (bool, bool) {
let t = cli(root, &["task", "get", id]);
(t["status"] == "admitted", t["handoff"].as_str().is_some())
}
/// A launcher piped into a reader that has gone: the answer it prints after a stop
/// goes nowhere, and the worker settles its task and the launch finishes all the same.
#[cfg(target_os = "linux")]
#[test]
fn a_launcher_whose_output_reader_has_gone_still_settles_and_cleans_up() {
if !namespaces_available() {
eprintln!("skipping: unprivileged namespaces are not available here");
return;
}
let dir = tempfile::tempdir().unwrap();
let root = init_root(dir.path());
let tmp = dir.path().join("tmp");
std::fs::create_dir_all(&tmp).unwrap();
let (id, work) = a_task_and_a_printing_sleeper(&root, dir.path());
let _reap = Reap(dir.path().to_path_buf());
for args in [
&["work", "--loop", "--lease-seconds", "60"][..],
&["launch", "--prompt", "hello", "--no-handoff"][..],
] {
std::fs::remove_file(work.join("started")).ok();
let mut launch = start_amem_into_a_closed_pipe(&root, &work, &tmp, args);
assert!(started(&work, 60), "{args:?}");
signal(&launch, "TERM");
let status = exited(&mut launch, 30);
let err = std::fs::read_to_string(work.with_extension("err")).unwrap();
assert_eq!(
status.and_then(|s| s.code()),
Some(128 + 15),
"{args:?}: {err}"
);
assert_eq!(
names_in(&root.join("state/run")),
Vec::<String>::new(),
"{err}"
);
assert_eq!(
names_in(&root.join("state/projection/claude")),
Vec::<String>::new()
);
}
assert_eq!(released(&root, &id), (true, true), "back with a handoff");
}
/// A launcher started to ignore SIGHUP, under `nohup` or by a shell without job control,
/// goes on ignoring it: the run is not stopped.
#[cfg(target_os = "linux")]
#[test]
fn a_launcher_started_to_ignore_sighup_is_not_stopped_by_it() {
if !namespaces_available() {
eprintln!("skipping: unprivileged namespaces are not available here");
return;
}
let dir = tempfile::tempdir().unwrap();
let root = init_root(dir.path());
let tmp = dir.path().join("tmp");
std::fs::create_dir_all(&tmp).unwrap();
let work = sleeper(dir.path(), "work");
let _reap = Reap(dir.path().to_path_buf());
let line = format!(
"trap '' HUP; exec {} --root {} launch --prompt hello --no-handoff --harness claude --auth key --workdir {}",
env!("CARGO_BIN_EXE_amem"),
root.display(),
work.display()
);
let mut launch = Command::new("/bin/sh")
.args(["-c", &line])
.env("PATH", path_with(&work.join("bins")))
.env("ANTHROPIC_API_KEY", "sk-ant-placeholder")
.env("TMPDIR", &tmp)
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(std::fs::File::create(work.with_extension("err")).unwrap())
.spawn()
.unwrap();
assert!(started(&work, 60));
signal(&launch, "HUP");
std::thread::sleep(std::time::Duration::from_secs(1));
let err = std::fs::read_to_string(work.with_extension("err")).unwrap();
assert!(
launch.try_wait().unwrap().is_none(),
"the run goes on: {err}"
);
assert_eq!(mounts_under(&root).len(), 1);
signal(&launch, "TERM");
assert_eq!(
exited(&mut launch, 30).and_then(|s| s.code()),
Some(128 + 15)
);
}
/// A headless unconfined run that left a child in the background, holding its output
/// open: a stop kills the child too, and the worker settles its task and exits.
#[cfg(target_os = "linux")]
#[test]
fn an_unconfined_worker_told_to_stop_ends_what_its_harness_left_running() {
let dir = tempfile::tempdir().unwrap();
let root = init_root(dir.path());
let tmp = dir.path().join("tmp");
std::fs::create_dir_all(&tmp).unwrap();
let id = file_task(&root, "First");
let work = dir.path().join("work");
std::fs::create_dir_all(&work).unwrap();
fake_harness(
&work,
"touch ./started\nsh -c \"sleep 300; echo $PWD\" &\nwait",
);
let _reap = Reap(dir.path().to_path_buf());
let mut worker = start_amem(
&root,
&work,
&tmp,
&["work", "--unconfined", "--loop", "--lease-seconds", "60"],
);
assert!(
started(&work, 60),
"{}",
std::fs::read_to_string(work.with_extension("err")).unwrap()
);
signal(&worker, "TERM");
let status = exited(&mut worker, 15);
let err = std::fs::read_to_string(work.with_extension("err")).unwrap();
assert_eq!(status.and_then(|s| s.code()), Some(128 + 15), "{err}");
assert!(gone_within(&work, 5), "{:?}", processes_under(&work));
assert_eq!(released(&root, &id), (true, true), "back with a handoff");
}
/// Whether script(1) is here to make a terminal with; on CI it must be.
#[cfg(target_os = "linux")]
fn script_available() -> bool {
let here = Command::new("script").arg("--version").output().is_ok();
assert!(
here || std::env::var_os("CI").is_none(),
"util-linux script makes the terminal here, and on CI it must be there"
);
if !here {
eprintln!("skipping: no script(1) to make a terminal with");
}
here
}
/// `amem <args>` for the claude harness in `work`, as the session leader of a terminal
/// of its own that script(1) makes, its standard error to `<work>.err`. script exits
/// with the launcher's code, and keeps what the terminal showed in `<work>.tty`.
#[cfg(target_os = "linux")]
fn on_a_terminal(root: &Path, work: &Path, tmp: &Path, args: &str) -> std::process::Child {
let line = format!(
"exec {} --root {} {args} --harness claude --auth key --workdir {} 2>{}",
env!("CARGO_BIN_EXE_amem"),
root.display(),
work.display(),
work.with_extension("err").display()
);
Command::new("script")
.args(["-qfec", &line])
.arg(work.with_extension("tty"))
.env("PATH", path_with(&work.join("bins")))
.env("ANTHROPIC_API_KEY", "sk-ant-placeholder")
.env("TMPDIR", tmp)
.stdin(Stdio::piped())
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
.unwrap()
}
/// The pid of the launcher running on `root`.
#[cfg(target_os = "linux")]
fn launcher_of(root: &Path) -> String {
let bin = env!("CARGO_BIN_EXE_amem");
let found: Vec<String> = processes_under(root)
.into_iter()
.filter(|(_, c)| c.starts_with(bin) && c.contains(" --root "))
.map(|(pid, _)| pid)
.collect();
assert_eq!(found.len(), 1, "{found:?}");
found[0].clone()
}
/// A headless unconfined run on a terminal stays in the terminal's foreground group: a
/// harness that sets the terminal, as a password prompt does, is not stopped for it.
#[cfg(target_os = "linux")]
#[test]
fn a_headless_unconfined_run_on_a_terminal_may_use_the_terminal() {
if !script_available() {
return;
}
let dir = tempfile::tempdir().unwrap();
let root = init_root(dir.path());
let tmp = dir.path().join("tmp");
std::fs::create_dir_all(&tmp).unwrap();
let work = dir.path().join("work");
std::fs::create_dir_all(&work).unwrap();
fake_harness(
&work,
"stty -echo </dev/tty; stty echo </dev/tty; echo answered",
);
let _reap = Reap(dir.path().to_path_buf());
let mut terminal = on_a_terminal(
&root,
&work,
&tmp,
"launch --unconfined --prompt hello --no-handoff",
);
let status = exited(&mut terminal, 30);
let err = std::fs::read_to_string(work.with_extension("err")).unwrap_or_default();
assert_eq!(status.and_then(|s| s.code()), Some(0), "{err}");
let shown = std::fs::read_to_string(work.with_extension("tty")).unwrap_or_default();
assert!(shown.contains("answered"), "{shown}");
}
/// A worker on a terminal whose harness left a child in the background, holding the
/// run's output open: its task and a launcher in `dir/work`.
#[cfg(target_os = "linux")]
fn a_worker_on_a_terminal_with_a_background_child(
root: &Path,
dir: &Path,
tmp: &Path,
) -> (String, PathBuf, std::process::Child) {
let id = file_task(root, "First");
let work = dir.join("work");
std::fs::create_dir_all(&work).unwrap();
fake_harness(
&work,
"touch ./started\nsh -c \"sleep 300; echo $PWD\" &\nwait",
);
let terminal = on_a_terminal(
root,
&work,
tmp,
"work --unconfined --loop --lease-seconds 60",
);
assert!(
started(&work, 60),
"{}",
std::fs::read_to_string(work.with_extension("err")).unwrap_or_default()
);
(id, work, terminal)
}
/// On a terminal the harness is in the terminal's group, which a stop does not kill
/// whole; what it left in the background, holding the output open, does not keep the
/// worker from settling its task and exiting.
#[cfg(target_os = "linux")]
#[test]
fn a_worker_on_a_terminal_told_to_stop_ends_though_its_harness_left_a_child() {
if !script_available() {
return;
}
let dir = tempfile::tempdir().unwrap();
let root = init_root(dir.path());
let tmp = dir.path().join("tmp");
std::fs::create_dir_all(&tmp).unwrap();
let _reap = Reap(dir.path().to_path_buf());
let (id, work, mut terminal) =
a_worker_on_a_terminal_with_a_background_child(&root, dir.path(), &tmp);
let launcher = launcher_of(&root);
Command::new("kill")
.args(["-TERM", &launcher])
.status()
.unwrap();
let status = exited(&mut terminal, 15);
let err = std::fs::read_to_string(work.with_extension("err")).unwrap_or_default();
assert_eq!(status.and_then(|s| s.code()), Some(128 + 15), "{err}");
assert_eq!(released(&root, &id), (true, true), "back with a handoff");
}
/// A headless unconfined harness that exits at once, leaving a child that holds its
/// output and ignores it: the run is still in flight while its output is read, so a stop
/// then kills the child with the harness's process group.
#[cfg(target_os = "linux")]
#[test]
fn a_stop_kills_what_an_exited_harness_left_holding_its_output() {
let dir = tempfile::tempdir().unwrap();
let root = init_root(dir.path());
let tmp = dir.path().join("tmp");
std::fs::create_dir_all(&tmp).unwrap();
let id = file_task(&root, "First");
let work = dir.path().join("work");
std::fs::create_dir_all(&work).unwrap();
fake_harness(
&work,
"sh -c \"sleep 300; echo $PWD\" &\ntouch ./started\nexit 0",
);
let _reap = Reap(dir.path().to_path_buf());
let mut worker = start_amem(
&root,
&work,
&tmp,
&["work", "--unconfined", "--loop", "--lease-seconds", "60"],
);
assert!(started(&work, 60));
// the harness has gone; its child holds the output
std::thread::sleep(std::time::Duration::from_millis(300));
let child = |dir: &Path| {
processes_under(dir)
.into_iter()
.any(|(_, c)| c.contains("sleep 300; echo"))
};
assert!(child(&work), "{:?}", processes_under(&work));
signal(&worker, "TERM");
let status = exited(&mut worker, 15);
let err = std::fs::read_to_string(work.with_extension("err")).unwrap();
assert_eq!(status.and_then(|s| s.code()), Some(128 + 15), "{err}");
assert!(gone_within(&work, 5), "{:?}", processes_under(&work));
assert_eq!(released(&root, &id), (true, true), "back with a handoff");
}
/// Whether the harness in `work`, which wrote its pid to `./pid`, has exited within
/// `secs`: gone, or a zombie its parent has not reaped.
#[cfg(target_os = "linux")]
fn exited_unreaped(work: &Path, secs: u64) -> bool {
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(secs);
while std::time::Instant::now() < deadline {
if let Ok(pid) = std::fs::read_to_string(work.join("pid")) {
let stat = std::fs::read_to_string(format!("/proc/{}/stat", pid.trim()));
// the state follows the command name, which ends at the last ')'
let state = stat
.as_deref()
.ok()
.and_then(|s| s.rsplit_once(')'))
.and_then(|(_, rest)| rest.trim_start().chars().next());
if matches!(state, None | Some('Z')) {
return true;
}
}
std::thread::sleep(std::time::Duration::from_millis(5));
}
false
}
/// A harness killed by SIGKILL before any stop, an out-of-memory kill say, is said to
/// have been, though a stop comes while what it left still holds its output.
#[cfg(target_os = "linux")]
#[test]
fn a_harness_killed_before_a_stop_is_said_to_have_been_killed() {
let dir = tempfile::tempdir().unwrap();
let root = init_root(dir.path());
let tmp = dir.path().join("tmp");
std::fs::create_dir_all(&tmp).unwrap();
let work = dir.path().join("work");
std::fs::create_dir_all(&work).unwrap();
fake_harness(
&work,
"sh -c \"sleep 300; echo $PWD\" &\necho $$ > ./pid\ntouch ./started\nkill -9 $$",
);
let _reap = Reap(dir.path().to_path_buf());
let mut launch = start_amem(
&root,
&work,
&tmp,
&[
"launch",
"--unconfined",
"--prompt",
"hello",
"--no-handoff",
],
);
assert!(started(&work, 60));
// the stop after the harness's death, which leaves it a zombie until it is reaped
assert!(exited_unreaped(&work, 30), "the harness did not die");
signal(&launch, "TERM");
let status = exited(&mut launch, 15);
let err = std::fs::read_to_string(work.with_extension("err")).unwrap();
assert_eq!(status.and_then(|s| s.code()), Some(128 + 15), "{err}");
assert!(err.contains("was killed by SIGKILL"), "{err}");
}
/// Told twice to stop, a worker does not wait for the run's cleanup, but it leaves
/// nothing no sweep removes: its task back in the pool, its render and its socket gone.
/// The second signal comes as soon as the first is said, before the task is settled: the
/// task comes back with no handoff, which only this path does.
#[cfg(target_os = "linux")]
#[test]
fn a_worker_told_twice_to_stop_releases_its_task_and_removes_its_render() {
if !script_available() {
return;
}
let dir = tempfile::tempdir().unwrap();
let root = init_root(dir.path());
let tmp = dir.path().join("tmp");
std::fs::create_dir_all(&tmp).unwrap();
let _reap = Reap(dir.path().to_path_buf());
let (id, work, mut terminal) =
a_worker_on_a_terminal_with_a_background_child(&root, dir.path(), &tmp);
use nix::sys::signal::{kill, Signal};
let launcher = nix::unistd::Pid::from_raw(launcher_of(&root).parse().unwrap());
kill(launcher, Signal::SIGINT).unwrap();
// the second from the loop that sees the first in progress, with no process to
// start between: it comes a millisecond or so after the stop is said, before the
// cleanup that follows it, the reap and the settling of the task, is done. The read
// of the output that what the harness left holds open can only make that longer.
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(10);
let read_err = || std::fs::read_to_string(work.with_extension("err")).unwrap_or_default();
while !read_err().contains("ending the run and cleaning up") {
assert!(std::time::Instant::now() < deadline, "{}", read_err());
std::thread::sleep(std::time::Duration::from_millis(1));
}
kill(launcher, Signal::SIGINT).unwrap();
let status = exited(&mut terminal, 30);
let err = read_err();
assert_eq!(status.and_then(|s| s.code()), Some(128 + 2), "{err}");
assert_eq!(
released(&root, &id),
(true, false),
"back, with no handoff: {err}"
);
assert_eq!(
names_in(&root.join("state/projection/claude")),
Vec::<String>::new(),
"the render"
);
assert_eq!(
names_in(&root.join("state/run")),
Vec::<String>::new(),
"the socket"
);
}
/// Under umask 000 the launcher's own scratch directory is still its own: a confined run
/// is not refused for it.
#[cfg(target_os = "linux")]
#[test]
fn a_confined_run_under_umask_000_runs() {
if !namespaces_available() {
eprintln!("skipping: unprivileged namespaces are not available here");
return;
}
let dir = tempfile::tempdir().unwrap();
let root = init_root(dir.path());
let tmp = dir.path().join("tmp");
std::fs::create_dir_all(&tmp).unwrap();
let work = dir.path().join("work");
std::fs::create_dir_all(&work).unwrap();
fake_harness(&work, "exit 0");
let _reap = Reap(dir.path().to_path_buf());
let line = format!(
"umask 000; exec {} --root {} launch --prompt hello --no-handoff --harness claude --auth key --workdir {}",
env!("CARGO_BIN_EXE_amem"),
root.display(),
work.display()
);
let out = Command::new("/bin/sh")
.args(["-c", &line])
.env("PATH", path_with(&work.join("bins")))
.env("ANTHROPIC_API_KEY", "sk-ant-placeholder")
.env("TMPDIR", &tmp)
.stdin(Stdio::null())
.output()
.unwrap();
let err = String::from_utf8_lossy(&out.stderr);
assert_eq!(out.status.code(), Some(0), "{err}");
assert!(err.contains("confined to"), "{err}");
}
// ---- the wslc backend's inside half, driven by a fake host ----
/// `amem inside` writes run requests for the host and waits for exit codes. This fake
/// host answers them from the test, which proves the protocol and the plan the inside
/// builds for the harness image without a container runtime.
/// The host's side of the protocol: wait for the request file.
fn await_request(req_path: &Path) -> serde_json::Value {
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(20);
while !req_path.exists() && std::time::Instant::now() < deadline {
std::thread::sleep(std::time::Duration::from_millis(50));
}
serde_json::from_str(&std::fs::read_to_string(req_path).unwrap()).unwrap()
}
/// What a request for the harness image must say.
fn check_image_request(req: &serde_json::Value) {
let argv: Vec<&str> = req["argv"]
.as_array()
.unwrap()
.iter()
.map(|v| v.as_str().unwrap())
.collect();
assert_eq!(argv[0], "/opt/memory/memory", "{argv:?}");
assert!(
argv.contains(&"/usr/local/bin/claude"),
"the harness at the image's path: {argv:?}"
);
assert!(
argv.contains(&"-p") && argv.contains(&"say the answer"),
"{argv:?}"
);
assert_eq!(req["cwd"], "/work");
assert_eq!(req["home"], "state/sandbox/claude");
assert_eq!(req["interactive"], false);
assert_eq!(
req["env"]["ANTHROPIC_BASE_URL"],
"http://127.0.0.1:4100/anthropic"
);
assert_eq!(req["env"]["HOME"], "/home/user");
}
#[test]
fn inside_writes_image_requests_and_reports_done() {
let dir = tempfile::tempdir().unwrap();
let root = init_root(dir.path());
let requests = dir.path().join("volume");
std::fs::create_dir_all(&requests).unwrap();
let child = inside(&root, &requests, &dir.path().join("host-home"));
let req = await_request(&requests.join("req-0.json"));
check_image_request(&req);
std::fs::write(requests.join("req-0.out"), "the answer\n").unwrap();
std::fs::write(requests.join("req-0.exit"), "0\n").unwrap();
let out = child.wait_with_output().unwrap();
let stdout = String::from_utf8_lossy(&out.stdout);
let stderr = String::from_utf8_lossy(&out.stderr);
assert_eq!(out.status.code(), Some(0), "{stderr}");
assert!(
stdout.contains("the answer"),
"the captured output is printed: {stdout}"
);
assert!(
stderr.contains("in the harness image, working tree at /work"),
"{stderr}"
);
assert_eq!(
std::fs::read_to_string(requests.join("done"))
.unwrap()
.trim(),
"0"
);
assert!(
!requests.join("egress.sock").exists(),
"the session takes its socket away with it"
);
}
// ---- hooks: a handoff the harness did not write ----
/// A fake harness that behaves like Claude Code's hooks: before "compaction" and at
/// session end it runs `amem hook claude` with the event on standard input and a
/// transcript on disk, and writes no handoff of its own.
const FIRES_HOOKS: &str = r#"
t=./transcript.jsonl
printf '%s\n' '{"type":"user","message":{"role":"user","content":"Finish the import."}}' '{"type":"assistant","message":{"role":"assistant","content":[{"type":"text","text":"Step 3 done, starting step 4."}]}}' > "$t"
printf '{"hook_event_name":"PreCompact","session_id":"fake-session-1","transcript_path":"%s","trigger":"auto"}' "$t" | "$MEMORY_BIN" hook claude
printf '{"hook_event_name":"SessionEnd","session_id":"fake-session-1","transcript_path":"%s","reason":"other"}' "$t" | "$MEMORY_BIN" hook claude
echo "the answer"
"#;
#[test]
fn hook_notes_become_handoffs_when_the_run_left_none() {
let dir = tempfile::tempdir().unwrap();
let root = init_root(dir.path());
let bins = fake_harness(dir.path(), FIRES_HOOKS);
let work_dir = dir.path().join("work");
std::fs::create_dir_all(&work_dir).unwrap();
let mut cmd = memory();
cmd.args([
"--root",
root.to_str().unwrap(),
"launch",
"--harness",
"claude",
"--auth",
"key",
"--unconfined",
]);
cmd.args([
"--workdir",
work_dir.to_str().unwrap(),
"--prompt",
"finish the import",
]);
cmd.env("PATH", path_with(&bins));
cmd.env("ANTHROPIC_API_KEY", "sk-ant-placeholder");
cmd.env("MEMORY_BIN", env!("CARGO_BIN_EXE_amem"));
cmd.stdin(Stdio::null());
let out = cmd.output().unwrap();
let stderr = String::from_utf8_lossy(&out.stderr);
assert_eq!(
out.status.code(),
Some(0),
"the session-end note is the handoff of last resort: {stderr}"
);
assert!(
stderr.contains("from the harness's hook (precompact)"),
"{stderr}"
);
assert!(
stderr.contains("from the harness's hook (sessionend)"),
"{stderr}"
);
let list = cli(&root, &["list", "--kind", "handoff"]);
let titles: Vec<String> = list
.as_array()
.unwrap()
.iter()
.map(|e| e["title"].as_str().unwrap().to_string())
.collect();
assert!(
titles
.iter()
.any(|t| t.starts_with("Session fake-ses before compaction (auto)")),
"{titles:?}"
);
assert!(
titles
.iter()
.any(|t| t.starts_with("Session fake-ses at session end (other)")),
"{titles:?}"
);
let id = list[0]["id"].as_str().unwrap();
let body = cli_text(&root, &["get", id]);
assert!(
body.contains("Step 3 done, starting step 4."),
"the transcript's tail is in the handoff: {body}"
);
assert!(body.contains("Left by the launcher's hook"), "{body}");
// nothing left waiting
assert!(
std::fs::read_dir(root.join("state/sandbox/claude/.memory-outbox"))
.map(|d| d.count() == 0)
.unwrap_or(true)
);
}
// ---- mining through the harness itself, as a subscription login requires ----
/// A fake harness: a normal prompt gets a normal answer; a mining run, recognisable by the
/// model flag, gets a JSON array of proposals, as a model would print it.
const MINES_WHEN_ASKED: &str = r#"
case "$*" in
*--model*) printf '%s\n' '[{"kind":"fact","title":"The import has five steps","body":"Steps one to five; the fourth, digests, is the slow one and failed before."}]' ;;
*) echo "the answer" ;;
esac
"#;
#[test]
fn mining_can_go_through_the_harness_on_a_small_model() {
let dir = tempfile::tempdir().unwrap();
let root = init_root(dir.path());
let bins = fake_harness(dir.path(), MINES_WHEN_ASKED);
let work_dir = dir.path().join("work");
std::fs::create_dir_all(&work_dir).unwrap();
// a transcript for the mining run to read, where Claude Code would leave one
let projects = root.join("state/sandbox/claude/.claude/projects/p");
std::fs::create_dir_all(&projects).unwrap();
std::fs::write(
projects.join("s.jsonl"),
r#"{"type":"user","message":{"role":"user","content":"Finish the import; step four is digests."}}"#,
)
.unwrap();
let mut cmd = memory();
cmd.args([
"--root",
root.to_str().unwrap(),
"launch",
"--harness",
"claude",
"--auth",
"key",
"--unconfined",
]);
cmd.args([
"--workdir",
work_dir.to_str().unwrap(),
"--prompt",
"finish the import",
"--no-handoff",
]);
cmd.args([
"--mine",
"--mine-via",
"harness",
"--mine-model",
"claude-haiku-4-5-20251001",
]);
cmd.env("PATH", path_with(&bins));
cmd.env("ANTHROPIC_API_KEY", "sk-ant-placeholder");
cmd.stdin(Stdio::null());
let out = cmd.output().unwrap();
let stderr = String::from_utf8_lossy(&out.stderr);
assert_eq!(out.status.code(), Some(0), "{stderr}");
assert!(stderr.contains("mined fact"), "{stderr}");
assert!(stderr.contains("mined 1 entries"), "{stderr}");
let list = cli(&root, &["list", "--kind", "fact"]);
let titles: Vec<String> = list
.as_array()
.unwrap()
.iter()
.map(|e| e["title"].as_str().unwrap().to_string())
.collect();
assert!(
titles.iter().any(|t| t == "The import has five steps"),
"{titles:?}"
);
// mined first, then taken out of the home: the record holds it from here
assert!(!projects.join("s.jsonl").exists(), "{stderr}");
assert!(stderr.contains("removed; the record keeps it"), "{stderr}");
}
// ---- re-enactment: a prompted session run again against its tape ----
/// Shell that speaks MCP to the egress: T0, then a handoff carrying what the working
/// tree says.
const READS_THE_TREE: &str = r#"
base="${ANTHROPIC_BASE_URL%/anthropic}"
H1='content-type: application/json'; H2='accept: application/json, text/event-stream'
sid=$(curl -s -D - -o /dev/null -X POST "$base/mcp" -H "$H1" -H "$H2" -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"fake","version":"0"}}}' | tr -d '\r' | awk 'tolower($1)=="mcp-session-id:"{print $2}')
curl -s -o /dev/null -X POST "$base/mcp" -H "$H1" -H "$H2" -H "mcp-session-id: $sid" -d '{"jsonrpc":"2.0","method":"notifications/initialized"}'
curl -s -o /dev/null -X POST "$base/mcp" -H "$H1" -H "$H2" -H "mcp-session-id: $sid" -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"t0","arguments":{}}}'
steps=$(cat steps.txt)
curl -s -X POST "$base/mcp" -H "$H1" -H "$H2" -H "mcp-session-id: $sid" -d "{\"jsonrpc\":\"2.0\",\"id\":3,\"method\":\"tools/call\",\"params\":{\"name\":\"write\",\"arguments\":{\"body\":\"The import has $steps.\",\"entry_type\":\"handoff\",\"title\":\"Import\"}}}" | grep -o 'mem-[a-z0-9]*-[a-z0-9]*' | head -1
"#;
/// `git` on the repository named, whatever a hook running the tests set for its own.
fn git_cmd() -> Command {
let mut cmd = Command::new("git");
for v in [
"GIT_DIR",
"GIT_INDEX_FILE",
"GIT_WORK_TREE",
"GIT_COMMON_DIR",
"GIT_PREFIX",
] {
cmd.env_remove(v);
}
cmd
}
fn git_in(dir: &Path, args: &[&str]) {
let out = git_cmd()
.arg("-C")
.arg(dir)
.args(["-c", "user.name=t", "-c", "user.email=t@example.org"])
.args(args)
.output()
.unwrap();
assert!(
out.status.success(),
"{}",
String::from_utf8_lossy(&out.stderr)
);
}
/// A git repository under `dir` whose one commit says the import has five steps; it and
/// its commit.
fn repo_of_five_steps(dir: &Path) -> (PathBuf, String) {
let repo = dir.join("work");
std::fs::create_dir_all(&repo).unwrap();
git_in(&repo, &["init", "-q"]);
std::fs::write(repo.join("steps.txt"), "five steps").unwrap();
git_in(&repo, &["add", "steps.txt"]);
git_in(&repo, &["commit", "-q", "-m", "five"]);
let head = git_cmd()
.args(["-C", repo.to_str().unwrap(), "rev-parse", "HEAD"])
.output()
.unwrap()
.stdout;
(repo, String::from_utf8(head).unwrap().trim().to_string())
}
/// `amem` on `root` with the fake harness first on the path and a placeholder key.
fn amem_with(root: &Path, bins: &Path, args: &[&str]) -> Output {
let mut cmd = memory();
cmd.args(["--root", root.to_str().unwrap()]).args(args);
cmd.env("PATH", path_with(bins));
cmd.env("ANTHROPIC_API_KEY", "sk-ant-placeholder");
cmd.stdin(Stdio::null());
cmd.output().unwrap()
}
/// A prompted launch in `repo` and the session its tape names, checked against `head`.
fn taped_session(root: &Path, bins: &Path, repo: &Path, head: &str) -> String {
let out = amem_with(
root,
bins,
&[
"launch",
"--harness",
"claude",
"--auth",
"key",
"--unconfined",
"--workdir",
repo.to_str().unwrap(),
"--prompt",
"finish the import",
],
);
assert_eq!(
out.status.code(),
Some(0),
"{}",
String::from_utf8_lossy(&out.stderr)
);
let tapes = cli(root, &["reenact", "tapes"]);
let tape = &tapes[0];
assert_eq!(tape["prompt"], "finish the import");
assert_eq!(tape["harness"], "claude");
assert_eq!(tape["commit"], head);
assert_eq!(tape["calls"], 2, "{tapes}");
tape["session"].as_str().unwrap().to_string()
}
fn re_enact(
root: &Path,
bins: &Path,
session: &str,
repo: &Path,
more: &[&str],
) -> serde_json::Value {
let mut args = vec![
"reenact",
"run",
session,
"--auth",
"key",
"--unconfined",
"--repo",
repo.to_str().unwrap(),
];
args.extend_from_slice(more);
let out = amem_with(root, bins, &args);
assert_eq!(
out.status.code(),
Some(0),
"{}",
String::from_utf8_lossy(&out.stderr)
);
serde_json::from_slice(&out.stdout).unwrap()
}
#[test]
fn a_prompted_session_is_re_enacted_at_its_commit_against_its_tape() {
let dir = tempfile::tempdir().unwrap();
let root = init_root(dir.path());
let bins = fake_harness(dir.path(), READS_THE_TREE);
let (repo, head) = repo_of_five_steps(dir.path());
let session = taped_session(&root, &bins, &repo, &head);
// the tree moves on; the re-run works at the session's commit all the same
std::fs::write(repo.join("steps.txt"), "six steps").unwrap();
git_in(&repo, &["commit", "-q", "-am", "six"]);
let r = re_enact(&root, &bins, &session, &repo, &[]);
assert_eq!(r["diverged"], false, "{r}");
assert_eq!(
(r["steps_to_divergence"].as_u64(), r["calls"].as_u64()),
(Some(2), Some(2))
);
assert_eq!(
(&r["handoff"], &r["exit"], &r["alignment"]),
(&true.into(), &0.into(), &1.0.into())
);
assert_eq!(r["tree"]["commit"], head.as_str());
// nothing written: one handoff, the original's
assert_eq!(
cli(&root, &["list", "--kind", "handoff"])
.as_array()
.unwrap()
.len(),
1
);
// the checkout is gone, and the re-run kept no tape of its own
let siblings: Vec<String> = std::fs::read_dir(dir.path())
.unwrap()
.flatten()
.map(|e| e.file_name().to_string_lossy().to_string())
.filter(|n| n.contains("reenact"))
.collect();
assert!(siblings.is_empty(), "{siblings:?}");
assert_eq!(
cli(&root, &["reenact", "tapes"]).as_array().unwrap().len(),
1
);
let report = cli_text(&root, &["reenact", "report", &session]);
assert!(
report.contains("1 runs") && report.contains("claude"),
"{report}"
);
}
#[test]
fn a_kept_tree_is_the_commit_and_repeated_runs_are_not_a_subscription_s() {
let dir = tempfile::tempdir().unwrap();
let root = init_root(dir.path());
let bins = fake_harness(dir.path(), READS_THE_TREE);
let (repo, head) = repo_of_five_steps(dir.path());
let session = taped_session(&root, &bins, &repo, &head);
std::fs::write(repo.join("steps.txt"), "six steps").unwrap();
git_in(&repo, &["commit", "-q", "-am", "six"]);
let r = re_enact(&root, &bins, &session, &repo, &["--keep-tree"]);
let kept = PathBuf::from(r["tree"]["kept_at"].as_str().unwrap());
assert_eq!(
std::fs::read_to_string(kept.join("steps.txt")).unwrap(),
"five steps"
);
// a repository of its own, so git works in it where only it is mounted
assert!(kept.join(".git").is_dir());
std::fs::remove_dir_all(&kept).unwrap();
// the run's home is gone with the run
let runs = root.join("state/reenact").join(&session);
assert!(!runs.exists(), "{}", runs.display());
let out = amem_with(
&root,
&bins,
&[
"reenact",
"run",
&session,
"--auth",
"subscription",
"--runs",
"2",
],
);
assert!(!out.status.success());
assert!(String::from_utf8_lossy(&out.stderr).contains("API key or the gateway"));
}
/// The projection as a live mount: a shell `cat` inside the sandbox, which no hook
/// sees, is a `get` the memory serves and logs. Where this host has no FUSE the launch
/// says the projection is a render, and the test says it skipped the rest; on CI, which
/// has FUSE, that is a failure.
#[cfg(target_os = "linux")]
#[test]
fn a_file_read_inside_the_sandbox_is_a_get_the_memory_serves() {
if !namespaces_available() {
eprintln!("skipping: unprivileged namespaces are not available here");
return;
}
let dir = tempfile::tempdir().unwrap();
let root = init_root(dir.path());
let written = cli(
&root,
&[
"write",
"--title",
"The vault",
"--body",
"The signing key lives in the vault.",
],
);
let id = written["id"].as_str().unwrap().to_string();
let work = dir.path().join("work");
std::fs::create_dir_all(&work).unwrap();
let bins = fake_harness(
&work,
&format!(
"{{ cat /memory/dom-me/{id}.md; echo \"write=$(echo x 2>/dev/null > /memory/dom-me/new.md && echo ok || echo refused)\"; }} > ./fake-claude.out 2>&1\nexit 0"
),
);
let out = launch(&root, &bins, &work, &[]);
let stderr = String::from_utf8_lossy(&out.stderr);
assert_eq!(out.status.code(), Some(0), "{stderr}");
let text = read_out(&work);
assert!(
text.contains("The signing key lives in the vault."),
"the entry is read as a file: {text}"
);
assert!(text.contains("write=refused"), "{text}");
let reads = dm_server::reads::all(&root).unwrap();
let gets: Vec<_> = reads.iter().filter(|r| r.tool == "get").collect();
if stderr.contains("is a render:") {
assert!(
std::env::var_os("CI").is_none(),
"on CI the projection must be a live mount: {stderr}"
);
eprintln!("skipping the rest: {stderr}");
assert!(gets.is_empty(), "no hook, no mount, no get: {gets:?}");
return;
}
assert!(stderr.contains("is a live mount"), "{stderr}");
// the fake harness has no hooks: only the mount could have made this get
assert_eq!(gets.len(), 1, "{reads:?}");
assert_eq!(gets[0].ids, vec![id]);
// and the mountpoint went with the session
let left = std::fs::read_dir(root.join("state/mount/claude"))
.map(|d| d.count())
.unwrap_or(0);
assert_eq!(left, 0, "a mountpoint outlived its session");
}