use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Mutex;
use std::time::Duration;
use io_harness::policy::Policy;
use io_harness::provider::{CompletionRequest, CompletionResponse, ToolCall, ToolSpec};
use io_harness::{run_with, ApproveAll, Provider, RunOutcome, Store, TaskContract, Verification};
use serde_json::json;
struct MockScript {
steps: Vec<Vec<ToolCall>>,
at: AtomicUsize,
offered: Mutex<Vec<ToolSpec>>,
}
impl MockScript {
fn new(steps: Vec<Vec<ToolCall>>) -> Self {
Self {
steps,
at: AtomicUsize::new(0),
offered: Mutex::new(Vec::new()),
}
}
fn spec(&self, name: &str) -> Option<ToolSpec> {
self.offered
.lock()
.unwrap()
.iter()
.find(|t| t.name == name)
.cloned()
}
}
impl Provider for MockScript {
async fn complete(&self, req: CompletionRequest) -> io_harness::Result<CompletionResponse> {
*self.offered.lock().unwrap() = req.tools.clone();
let i = self.at.fetch_add(1, Ordering::SeqCst);
Ok(CompletionResponse {
tool_calls: self.steps.get(i).cloned().unwrap_or_default(),
..Default::default()
})
}
}
fn exec_call(argv: &[&str]) -> ToolCall {
ToolCall {
name: "exec".into(),
arguments: json!({ "argv": argv }),
}
}
fn fixture() -> tempfile::TempDir {
let dir = tempfile::tempdir().unwrap();
std::fs::create_dir_all(dir.path().join("src")).unwrap();
std::fs::write(
dir.path().join("src/lib.rs"),
"pub fn answer() -> u32 { 42 }\n",
)
.unwrap();
dir
}
fn contract(root: &std::path::Path) -> TaskContract {
TaskContract::workspace("build the project", root, Verification::None).with_max_steps(6)
}
fn permissive() -> Policy {
Policy::permissive()
}
#[tokio::test]
async fn a_command_runs_in_the_workspace_root_and_its_result_reaches_the_next_turn() {
let dir = fixture();
let store = Store::memory().unwrap();
let provider = MockScript::new(vec![vec![exec_call(&[
"rustc",
"--crate-type",
"lib",
"src/lib.rs",
"--out-dir",
".",
])]]);
let result = run_with(
&contract(dir.path()),
&provider,
&store,
&permissive(),
&ApproveAll,
)
.await
.unwrap();
assert!(
dir.path().join("libsrc.rlib").exists()
|| std::fs::read_dir(dir.path())
.unwrap()
.flatten()
.any(|e| e.file_name().to_string_lossy().ends_with(".rlib")),
"the build ran in the workspace root, so its relative output landed there"
);
let steps = store.steps(result.run_id).unwrap();
let ran = &steps[0];
assert!(
ran.decision.contains("exec rustc") && ran.decision.contains("exit 0"),
"the exit status is in the trace: {:?}",
ran.decision
);
let next = &steps[1];
assert!(
next.prompt
.contains("[exec `rustc --crate-type lib src/lib.rs --out-dir .` exit 0]"),
"the command and its exit status reached the following request: {}",
next.prompt
);
}
#[tokio::test]
async fn a_command_that_fails_reports_its_status_and_its_stderr_to_the_agent() {
let dir = fixture();
let store = Store::memory().unwrap();
let provider = MockScript::new(vec![vec![exec_call(&["rustc", "src/nope.rs"])]]);
let result = run_with(
&contract(dir.path()),
&provider,
&store,
&permissive(),
&ApproveAll,
)
.await
.unwrap();
let steps = store.steps(result.run_id).unwrap();
assert!(
!steps[0].decision.contains("exit 0"),
"a failing command is not reported as a success: {:?}",
steps[0].decision
);
assert!(
steps[1].prompt.contains("nope.rs"),
"the compiler's own complaint reached the agent: {}",
steps[1].prompt
);
}
#[tokio::test]
async fn a_program_this_machine_does_not_have_is_an_observation_not_a_failed_run() {
let dir = fixture();
let store = Store::memory().unwrap();
let provider = MockScript::new(vec![vec![exec_call(&["io-harness-no-such-program"])]]);
let result = run_with(
&contract(dir.path()),
&provider,
&store,
&permissive(),
&ApproveAll,
)
.await
.unwrap();
assert!(matches!(result.outcome, RunOutcome::Finished { .. }));
assert!(store.steps(result.run_id).unwrap()[1]
.prompt
.contains("[exec unavailable]"));
}
#[tokio::test]
async fn a_denied_program_is_refused_before_it_is_spawned_and_the_refusal_is_attributed() {
let dir = fixture();
let store = Store::memory().unwrap();
let policy = Policy::permissive().layer("ops").deny_exec("rustc");
let provider = MockScript::new(vec![vec![exec_call(&[
"rustc",
"--crate-type",
"lib",
"src/lib.rs",
"--out-dir",
".",
])]]);
let result = run_with(
&contract(dir.path()),
&provider,
&store,
&policy,
&ApproveAll,
)
.await
.unwrap();
assert!(
!std::fs::read_dir(dir.path())
.unwrap()
.flatten()
.any(|e| e.file_name().to_string_lossy().ends_with(".rlib")),
"nothing was spawned, so nothing was built"
);
let refusals: Vec<_> = store
.events(result.run_id)
.unwrap()
.into_iter()
.filter(|e| e.kind == "refusal" && e.act == "exec")
.collect();
assert!(
refusals
.iter()
.any(|e| e.rule.as_deref() == Some("rustc") && e.layer.as_deref() == Some("ops")),
"the refusal names the rule and the layer that produced it: {refusals:?}"
);
}
#[tokio::test]
async fn the_same_command_under_a_policy_that_allows_it_executes() {
let dir = fixture();
let store = Store::memory().unwrap();
let policy = Policy::permissive().layer("ops").allow_exec("rustc");
let provider = MockScript::new(vec![vec![exec_call(&[
"rustc",
"--crate-type",
"lib",
"src/lib.rs",
"--out-dir",
".",
])]]);
let result = run_with(
&contract(dir.path()),
&provider,
&store,
&policy,
&ApproveAll,
)
.await
.unwrap();
assert!(
std::fs::read_dir(dir.path())
.unwrap()
.flatten()
.any(|e| e.file_name().to_string_lossy().ends_with(".rlib")),
"the allowed command ran"
);
assert!(store.steps(result.run_id).unwrap()[0]
.decision
.contains("exit 0"));
}
const HARMLESS: &[&str] = &["rustc", "--version"];
const FORBIDDEN: &[&str] = &["rustc", "--print", "sysroot"];
#[tokio::test]
async fn a_rule_can_allow_one_subcommand_and_deny_another_of_the_same_program() {
let dir = fixture();
let store = Store::memory().unwrap();
let policy = Policy::permissive()
.layer("ops")
.allow_exec("rustc --version*")
.deny_exec("rustc --print*");
let provider = MockScript::new(vec![vec![exec_call(HARMLESS)], vec![exec_call(FORBIDDEN)]]);
let result = run_with(
&contract(dir.path()),
&provider,
&store,
&policy,
&ApproveAll,
)
.await
.unwrap();
let steps = store.steps(result.run_id).unwrap();
assert!(
steps[0].decision.contains("exit 0"),
"the allowed argv ran: {:?}",
steps[0].decision
);
assert!(
steps[1].decision.contains("refused"),
"the denied argv did not: {:?}",
steps[1].decision
);
let refusals: Vec<_> = store
.events(result.run_id)
.unwrap()
.into_iter()
.filter(|e| e.kind == "refusal")
.collect();
assert!(
refusals
.iter()
.any(|e| e.rule.as_deref() == Some("rustc --print*")
&& e.layer.as_deref() == Some("ops")),
"attributed to the argv rule, not to the program: {refusals:?}"
);
}
#[tokio::test]
async fn a_policy_allowing_both_argvs_runs_both() {
let dir = fixture();
let store = Store::memory().unwrap();
let policy = Policy::permissive().layer("ops").allow_exec("rustc*");
let provider = MockScript::new(vec![vec![exec_call(HARMLESS)], vec![exec_call(FORBIDDEN)]]);
let result = run_with(
&contract(dir.path()),
&provider,
&store,
&policy,
&ApproveAll,
)
.await
.unwrap();
let steps = store.steps(result.run_id).unwrap();
for (i, step) in steps.iter().take(2).enumerate() {
assert!(
step.decision.contains("exit 0"),
"step {i} ran: {:?}",
step.decision
);
}
}
#[tokio::test]
async fn the_tool_schema_accepts_only_an_array_of_strings() {
let dir = fixture();
let store = Store::memory().unwrap();
let provider = MockScript::new(vec![vec![exec_call(&["rustc", "--version"])]]);
run_with(
&contract(dir.path()),
&provider,
&store,
&permissive(),
&ApproveAll,
)
.await
.unwrap();
let spec = provider.spec("exec").expect("exec is offered to the model");
let argv = &spec.parameters["properties"]["argv"];
assert_eq!(argv["type"], "array", "{:?}", spec.parameters);
assert_eq!(argv["items"]["type"], "string", "{:?}", spec.parameters);
assert_eq!(spec.parameters["required"], json!(["argv"]));
let props = spec.parameters["properties"].as_object().unwrap();
assert_eq!(
props.keys().collect::<Vec<_>>(),
vec!["argv"],
"one parameter, and it is the array"
);
}
#[tokio::test]
async fn shell_metacharacters_reach_the_program_as_one_literal_argument() {
let dir = fixture();
let store = Store::memory().unwrap();
let nasty = "a;b && c $(id) `whoami` | d > e.rs";
let provider = MockScript::new(vec![vec![exec_call(&["rustc", nasty])]]);
let result = run_with(
&contract(dir.path()),
&provider,
&store,
&permissive(),
&ApproveAll,
)
.await
.unwrap();
let observed = &store.steps(result.run_id).unwrap()[1].prompt;
assert!(
observed.contains(nasty),
"the child received the argument byte for byte: {observed}"
);
assert!(!dir.path().join("e.rs").exists(), "nothing was redirected");
}
#[tokio::test]
async fn a_wedged_command_is_killed_and_reported_as_a_timeout_naming_itself() {
let dir = fixture();
let store = Store::memory().unwrap();
let argv: Vec<&str> = if cfg!(windows) {
vec!["ping", "-n", "30", "127.0.0.1"]
} else {
vec!["sleep", "30"]
};
let provider = MockScript::new(vec![vec![exec_call(&argv)]]);
let contract = contract(dir.path()).with_exec_timeout(Duration::from_millis(300));
let started = std::time::Instant::now();
let result = run_with(&contract, &provider, &store, &permissive(), &ApproveAll)
.await
.unwrap();
assert!(
started.elapsed() < Duration::from_secs(20),
"the run got its turn back rather than waiting out the command"
);
let steps = store.steps(result.run_id).unwrap();
assert!(
steps[0].decision.contains("timed out"),
"the trace says what happened: {:?}",
steps[0].decision
);
assert!(
steps[1].prompt.contains("[exec timed out]") && steps[1].prompt.contains(argv[0]),
"the observation names the command that hung: {}",
steps[1].prompt
);
assert!(
matches!(result.outcome, RunOutcome::Finished { .. }),
"{:?}",
result.outcome
);
}
#[tokio::test]
async fn a_detected_project_puts_its_ecosystem_and_commands_in_the_recorded_prompt() {
let dir = tempfile::tempdir().unwrap();
std::fs::write(dir.path().join("package.json"), "{}").unwrap();
std::fs::write(dir.path().join("pnpm-lock.yaml"), "").unwrap();
let store = Store::memory().unwrap();
let provider = MockScript::new(vec![]);
let result = run_with(
&contract(dir.path()),
&provider,
&store,
&permissive(),
&ApproveAll,
)
.await
.unwrap();
let prompt = &store.steps(result.run_id).unwrap()[0].prompt;
assert!(prompt.contains("node"), "the ecosystem: {prompt}");
assert!(prompt.contains("package.json"), "the marker: {prompt}");
assert!(prompt.contains("test: pnpm test"), "the commands: {prompt}");
}
#[tokio::test]
async fn an_undetected_project_says_nothing_about_its_toolchain() {
let dir = tempfile::tempdir().unwrap();
std::fs::write(dir.path().join("notes.txt"), "no build system here").unwrap();
let store = Store::memory().unwrap();
let provider = MockScript::new(vec![]);
let result = run_with(
&contract(dir.path()),
&provider,
&store,
&permissive(),
&ApproveAll,
)
.await
.unwrap();
let prompt = &store.steps(result.run_id).unwrap()[0].prompt;
assert!(
!prompt.contains("Project:"),
"no detection, so no sentence about one: {prompt}"
);
}