blob: 3457eca7f0b748026d18d9cce78234ca72f8ecbe [file]
//! `amem reenact`: a session run again on its original prompt, at its original commit,
//! against its tape (the paper's live re-execution), to compare harness versions or
//! models on the same task.
//!
//! The memory server answers the re-run's tool calls from the tape while they match, by
//! tool, arguments and occurrence, and writes nothing; the first call that does not
//! match is the first divergence. After it a read is served as the record is now, a
//! write is captured for the report, and a call outside is refused. The working tree is
//! a detached checkout of the original commit beside the repository, removed after the
//! run, and the harness runs confined in it as a launch does, with a private home of
//! its own. Nothing is substituted into the harness's model calls.
//!
//! The terms the owner checked on 11 October 2026 set two rules here. One run on the
//! owner's own login, started by the owner, is ordinary use of the harness; repeated
//! runs belong on an API key or the gateway, so `--runs` above one refuses a
//! subscription. And a recorded session is never used to train a model: the runs are
//! reports, kept under `state/reenact/`, and nothing here exports a tape.
use crate::{
backend_of, runner_for, AuthArg, AuthMode, BackendArg, Credentials, Fallible, Harness,
HarnessArg, RunOptions, Session, SubagentsArg,
};
use clap::{Subcommand, ValueEnum};
use dm_server::tape::{Playback, Played, Tape};
use dm_server::Memory;
use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};
use std::sync::Arc;
type Fallibly<T> = Result<T, Box<dyn std::error::Error>>;
/// Where the runs of a session are a line each, under the root.
const RUNS: &str = "state/reenact";
/// What `amem reenact` does.
#[derive(Subcommand)]
pub enum ReenactOp {
/// The tapes the record keeps: each session's prompt, harness, commit and calls.
Tapes,
/// One session's tape in full.
Show {
/// The session.
session: String,
},
/// Keep a tape past the transcripts' clock, for a benchmark.
Pin {
/// The session.
session: String,
},
/// Let a pinned tape go on the clock again.
Unpin {
/// The session.
session: String,
},
/// Run a session again against its tape, and report how far it kept to it.
Run(RunArgs),
/// The runs of a session, side by side: each harness and model, against the
/// original.
Report {
/// The session.
session: String,
},
}
/// `amem reenact run`.
#[derive(clap::Args)]
pub struct RunArgs {
/// The session to re-enact.
session: String,
/// The harness; the original's by default.
#[arg(long, value_enum)]
harness: Option<HarnessArg>,
/// The model; the original's by default.
#[arg(long)]
model: Option<String>,
/// Whose credential the harness's calls go out on, said every time: a subscription
/// login allows one run, started by you; repeated runs go on a key or the gateway.
#[arg(long, value_enum)]
auth: AuthArg,
/// Where model calls go under --auth key, as for a launch.
#[arg(long)]
provider: Option<String>,
/// How many runs.
#[arg(long, default_value_t = 1)]
runs: usize,
/// The repository the session worked in, which holds its commit; the current
/// directory by default.
#[arg(long)]
repo: Option<PathBuf>,
/// A name for this side in the report, as `claude-2.1.296`.
#[arg(long)]
label: Option<String>,
/// Where the harness runs, as for a launch.
#[arg(long, value_enum, default_value = "auto")]
backend: BackendArg,
/// Run without confinement, as for a launch.
#[arg(long)]
unconfined: bool,
/// Keep the checkout after the run, to look at what the re-run changed.
#[arg(long)]
keep_tree: bool,
}
/// How a run's checkout was made.
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct Tree {
/// The commit, when the session recorded one.
pub commit: Option<String>,
/// Whether the original had changes the commit does not hold.
pub original_dirty: bool,
/// Where it was, when kept.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub kept_at: Option<String>,
}
/// Tokens a run spent, as the egress metered them.
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct Tokens {
/// Model calls.
pub calls: u64,
/// Input, cache reads excluded.
pub input: u64,
/// Output.
pub output: u64,
/// Cache reads.
pub cache_read: u64,
}
/// One re-run, as the report keeps it.
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct RunReport {
/// This run.
pub run: String,
/// The session re-enacted.
pub session: String,
/// When it ran.
pub at: String,
/// The harness, by binary name.
pub harness: String,
/// The model it was given, if any.
pub model: Option<String>,
/// The side's name, when given.
pub label: Option<String>,
/// The re-run's own memory session.
pub rerun_session: String,
/// The harness's exit code.
pub exit: i32,
/// Calls on the tape.
pub tape_calls: usize,
/// Calls the re-run made.
pub calls: usize,
/// Calls before the first divergence.
pub steps_to_divergence: usize,
/// Whether it diverged.
pub diverged: bool,
/// The longest run of tools the two sequences share in order, by tool name.
pub aligned: usize,
/// `2 * aligned / (tape_calls + calls)`: one for the same sequence of tools.
pub alignment: f64,
/// The writes captured after the divergence, which were not applied.
pub captured: Vec<serde_json::Value>,
/// Whether the re-run left a handoff, from the tape or captured.
pub handoff: bool,
/// What the harness answered, its start.
pub answer: String,
/// Tokens.
pub tokens: Tokens,
/// Wall time.
pub seconds: f64,
/// The checkout.
pub tree: Tree,
}
/// Run `amem reenact`.
///
/// # Errors
///
/// Anyone but the owner, a session with no tape, or a run that cannot start.
pub async fn cmd(root: &Path, op: ReenactOp) -> Fallible {
match op {
ReenactOp::Tapes => crate::json(&Memory::open(root)?.tapes().await?),
ReenactOp::Show { session } => crate::json(&Memory::open(root)?.tape(&session).await?),
ReenactOp::Pin { session } => {
crate::json(&Memory::open(root)?.pin_tape(&session, true).await?)
}
ReenactOp::Unpin { session } => {
crate::json(&Memory::open(root)?.pin_tape(&session, false).await?)
}
ReenactOp::Run(args) => run(root, args).await,
ReenactOp::Report { session } => {
print!("{}", report(root, &session)?);
Ok(())
}
}
}
/// The harness a tape names, by its binary name.
fn harness_named(name: &str) -> Option<HarnessArg> {
HarnessArg::value_variants()
.iter()
.copied()
.find(|h| Harness::from(*h).binary() == name)
}
/// The longest common subsequence of two tool sequences, by length.
#[must_use]
pub fn aligned(a: &[String], b: &[String]) -> usize {
let mut row = vec![0usize; b.len() + 1];
for x in a {
let mut diag = 0;
for (j, y) in b.iter().enumerate() {
let up = row[j + 1];
row[j + 1] = if x == y {
diag + 1
} else {
row[j + 1].max(row[j])
};
diag = up;
}
}
row[b.len()]
}
/// What a run did against its tape, as numbers.
fn scored(played: &Played) -> (usize, f64, Vec<serde_json::Value>, bool) {
let tools: Vec<String> = played.steps.iter().map(|s| s.tool.clone()).collect();
let n = aligned(&played.tape_tools, &tools);
let total = played.tape_tools.len() + tools.len();
let alignment = if total == 0 {
1.0
} else {
2.0 * n as f64 / total as f64
};
let captured = played
.steps
.iter()
.filter(|s| s.served == "captured")
.map(|s| serde_json::json!({ "tool": s.tool, "arguments": s.arguments }))
.collect();
let handoff = played.steps.iter().any(|s| {
s.tool == "write"
&& (s.served == "tape" || s.served == "captured")
&& s.arguments
.get("entry_type")
.and_then(serde_json::Value::as_str)
== Some("handoff")
}) || played
.steps
.iter()
.any(|s| s.tool == "complete" && (s.served == "tape" || s.served == "captured"));
(n, alignment, captured, handoff)
}
/// Git, run on the host against a repository a confined harness could write: with an
/// environment of nothing but where git is and whose it is, so neither the launcher's
/// credentials nor a hook's own `GIT_*` reach it, and with the repository's hooks and
/// fsmonitor off, so nothing the harness planted in `.git` runs here.
fn git(repo: &Path, args: &[&str]) -> Fallibly<String> {
let mut cmd = std::process::Command::new("git");
cmd.env_clear();
for keep in ["PATH", "HOME", "SYSTEMROOT", "USERPROFILE"] {
if let Some(v) = std::env::var_os(keep) {
cmd.env(keep, v);
}
}
cmd.args([
"-c",
"core.fsmonitor=false",
"-c",
"core.hooksPath=/dev/null",
"--no-optional-locks",
]);
let out = cmd.arg("-C").arg(repo).args(args).output()?;
if !out.status.success() {
return Err(format!(
"git {}: {}",
args.join(" "),
String::from_utf8_lossy(&out.stderr).trim()
)
.into());
}
Ok(String::from_utf8_lossy(&out.stdout).trim().to_string())
}
/// The commit a working tree is at, and whether it has changes the commit lacks; none
/// for a directory that is not a git checkout.
#[must_use]
pub fn commit_of(workdir: &Path) -> Option<(String, bool)> {
let commit = git(workdir, &["rev-parse", "HEAD"]).ok()?;
let dirty = git(workdir, &["status", "--porcelain"]).is_ok_and(|s| !s.is_empty());
Some((commit, dirty))
}
/// A clone of `repo` at `commit`, detached, beside the repository and named for the run:
/// a repository of its own, objects hardlinked rather than borrowed, so git works inside
/// the sandbox, which mounts the working tree and nothing of the repository it came from.
fn checkout(repo: &Path, commit: &str, run: &str) -> Fallibly<PathBuf> {
let repo = std::fs::canonicalize(repo)?;
let name = repo
.file_name()
.map_or_else(|| "repo".into(), |n| n.to_string_lossy().to_string());
let parent = repo
.parent()
.ok_or("the repository has no parent directory")?;
let into = parent.join(format!("{name}-reenact-{run}"));
git(&repo, &["cat-file", "-e", &format!("{commit}^{{commit}}")]).map_err(|_| {
format!(
"{commit} is not in {}; pass --repo with the repository the session worked in",
repo.display()
)
})?;
if into.exists() {
return Err(format!("{} is in the way", into.display()).into());
}
let to = into.display().to_string();
git(&repo, &["clone", "--quiet", "--no-checkout", ".", &to])?;
if let Err(e) = git(&into, &["checkout", "--quiet", "--detach", commit]) {
remove_checkout(&into);
return Err(e);
}
Ok(into)
}
fn remove_checkout(into: &Path) {
if std::fs::remove_dir_all(into).is_err() {
eprintln!("amem: the checkout at {} was left", into.display());
}
}
fn now() -> String {
time::OffsetDateTime::now_utc()
.format(&time::format_description::well_known::Rfc3339)
.unwrap_or_default()
}
/// The runs, refused where the terms say so, and the side they make.
fn check_runs(args: &RunArgs) -> Fallibly<()> {
if args.runs == 0 {
return Err("--runs is at least one".into());
}
if args.runs > 1 && AuthMode::from(args.auth) == AuthMode::Subscription {
return Err(
"repeated runs belong on an API key or the gateway, not a subscription login: \
pass --auth key or --auth gateway, or run once"
.into(),
);
}
Ok(())
}
async fn run(root: &Path, args: RunArgs) -> Fallible {
check_runs(&args)?;
let tape = Memory::open(root)?.tape(&args.session).await?;
let prompt = tape.header.start.prompt.clone().ok_or_else(|| {
format!(
"{} was interactive: its tape has no prompt to run again",
args.session
)
})?;
let harness = match args.harness {
Some(h) => h,
None => harness_named(&tape.header.start.harness).ok_or_else(|| {
format!(
"the tape names harness {:?}; pass --harness",
tape.header.start.harness
)
})?,
};
let model = args
.model
.clone()
.or_else(|| tape.header.start.model.clone());
let repo = match &args.repo {
Some(r) => r.clone(),
None => std::env::current_dir()?,
};
for i in 1..=args.runs {
let r = one(
root,
&args,
&tape,
&prompt,
harness,
model.clone(),
&repo,
i,
)
.await?;
let dir = root.join(RUNS);
std::fs::create_dir_all(&dir)?;
dm_server::reads::append_line(
&dir.join(format!("{}.jsonl", args.session)),
&serde_json::to_string(&r)?,
);
println!("{}", serde_json::to_string_pretty(&r)?);
}
Ok(())
}
#[allow(clippy::too_many_arguments)] // one per thing a run is given
async fn one(
root: &Path,
args: &RunArgs,
tape: &Tape,
prompt: &str,
harness: HarnessArg,
model: Option<String>,
repo: &Path,
i: usize,
) -> Fallibly<RunReport> {
let stamp = time::OffsetDateTime::now_utc().unix_timestamp();
let run = format!("rx-{stamp}-{}-{i}", std::process::id());
let start = &tape.header.start;
let (workdir, scratch) = tree_for(repo, start.commit.as_deref(), &args.session, &run)?;
let home = root.join(RUNS).join(&args.session).join(&run).join("home");
let play = Arc::new(Playback::new(tape.clone()));
let h: Harness = harness.into();
// the tree is put away however the run ends
let ran = run_once(
root,
args,
h,
model.clone(),
&workdir,
home.clone(),
prompt,
&play,
)
.await;
let kept_at = put_away(&workdir, scratch, args.keep_tree);
// the run's home held its login's copy and a transcript of what it was served
if let Some(run_dir) = home.parent() {
std::fs::remove_dir_all(run_dir).ok();
// and the session's directory, once no other run is in it
if let Some(session_dir) = run_dir.parent() {
std::fs::remove_dir(session_dir).ok();
}
}
let (exit, output, rerun_session, tokens, seconds) = ran?;
let played = play.played();
let (aligned, alignment, captured, handoff) = scored(&played);
Ok(RunReport {
run,
session: args.session.clone(),
at: now(),
harness: h.binary().to_string(),
model,
label: args.label.clone(),
rerun_session,
exit,
tape_calls: played.tape_calls,
calls: played.steps.len(),
steps_to_divergence: played.steps_to_divergence,
diverged: played.diverged,
aligned,
alignment,
captured,
handoff,
answer: output.unwrap_or_default().chars().take(2_000).collect(),
tokens,
seconds,
tree: Tree {
commit: start.commit.clone(),
original_dirty: start.dirty,
kept_at,
},
})
}
/// One run of the harness against `play`, in `workdir`: its exit code, its answer, its
/// memory session, what it spent, and how long it took.
#[allow(clippy::too_many_arguments)] // one per thing a run is given
async fn run_once(
root: &Path,
args: &RunArgs,
h: Harness,
model: Option<String>,
workdir: &Path,
home: PathBuf,
prompt: &str,
play: &Arc<Playback>,
) -> Fallibly<(i32, Option<String>, String, Tokens, f64)> {
std::fs::create_dir_all(&home)?;
let session = Session::start(
root,
h,
Credentials {
auth: args.auth.into(),
provider: args.provider.as_deref(),
server_auth: None,
server_provider: None,
},
workdir.to_path_buf(),
runner_for(backend(args, root, h)?, root)?,
RunOptions::new(model, SubagentsArg::Harness, Vec::new(), None).home(home),
)
.await?;
session.memory().set_playback(play.clone());
session.memory().disable_read_log();
eprintln!(
"amem: re-enacting {} ({} calls on its tape) as {}",
args.session,
play.played().tape_calls,
session.memory().session_id()
);
let began = std::time::Instant::now();
let ran = session.run(&h.headless(prompt), true).await;
let seconds = began.elapsed().as_secs_f64();
let id = session.memory().session_id().to_string();
let tokens = tokens_of(&session, &id);
let code = ran.as_ref().map_or(1, |(c, _)| *c);
session.finish(code).await;
let (exit, output) = ran?;
Ok((exit, output, id, tokens, seconds))
}
/// Where a re-run's harness runs: as a launch's does, except that the `wslc` backend,
/// which a launch hands to its own path, is not one a re-run takes yet; refused rather
/// than run unconfined.
fn backend(args: &RunArgs, root: &Path, h: Harness) -> Fallibly<crate::Backend> {
let b = backend_of(args.backend, args.unconfined, root, h);
if b == crate::Backend::Wslc {
return Err(
"a re-run does not take the wslc backend yet; run it under WSL with \
--backend namespaces or image, or pass --unconfined"
.into(),
);
}
Ok(b)
}
/// The working tree for a run: a checkout of the commit, or an empty directory made for
/// the run when the session recorded none, which is the second of the pair.
fn tree_for(
repo: &Path,
commit: Option<&str>,
session: &str,
run: &str,
) -> Fallibly<(PathBuf, Option<PathBuf>)> {
if let Some(c) = commit {
return Ok((checkout(repo, c, &format!("{session}-{run}"))?, None));
}
eprintln!("amem: {session} recorded no commit; the re-run works in an empty directory");
// as an empty record is made for an evaluation's baseline
let d = std::env::temp_dir().join(format!("amem-reenact-{}-{run}", std::process::id()));
std::fs::create_dir_all(&d)?;
Ok((d.clone(), Some(d)))
}
/// Put a run's tree away: an empty directory goes, a checkout is removed unless asked to
/// stay. Where it stayed, if it did.
fn put_away(workdir: &Path, scratch: Option<PathBuf>, keep: bool) -> Option<String> {
if let Some(d) = scratch {
std::fs::remove_dir_all(d).ok();
return None;
}
if keep {
return Some(workdir.display().to_string());
}
remove_checkout(workdir);
None
}
/// What the egress metered for the re-run's session.
fn tokens_of(session: &Session, id: &str) -> Tokens {
session
.egress()
.telemetry()
.session(id)
.ok()
.flatten()
.map(|t| Tokens {
calls: t.calls,
input: t.input_tokens,
output: t.output_tokens,
cache_read: t.cache_read_tokens,
})
.unwrap_or_default()
}
/// Drop what the re-runs of `session` left under the root: their reports and any run
/// directory an interrupted run did not put away. With the tape, on its clock.
pub fn forget_runs(root: &Path, session: &str) {
if !session
.chars()
.all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_')
{
return;
}
let dir = root.join(RUNS);
std::fs::remove_file(dir.join(format!("{session}.jsonl"))).ok();
std::fs::remove_dir_all(dir.join(session)).ok();
}
/// The runs of a session, read back.
fn runs_of(root: &Path, session: &str) -> Fallibly<Vec<RunReport>> {
if !session
.chars()
.all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_')
{
return Err(format!("{session:?} is not a session id").into());
}
let path = root.join(RUNS).join(format!("{session}.jsonl"));
let text = match std::fs::read_to_string(&path) {
Ok(t) => t,
Err(e) if e.kind() == std::io::ErrorKind::NotFound => String::new(),
Err(e) => return Err(e.into()),
};
Ok(text
.lines()
.filter_map(|l| serde_json::from_str(l).ok())
.collect())
}
fn mean(xs: impl Iterator<Item = f64>) -> f64 {
let (n, sum) = xs.fold((0usize, 0.0), |(n, s), x| (n + 1, s + x));
if n == 0 {
0.0
} else {
sum / n as f64
}
}
/// The report: one line per side, a side being the harness, model and label.
///
/// # Errors
///
/// A run log that cannot be read, or a session id that is not one.
pub fn report(root: &Path, session: &str) -> Fallibly<String> {
let runs = runs_of(root, session)?;
let mut sides: Vec<(String, Vec<&RunReport>)> = Vec::new();
for r in &runs {
let side = format!(
"{}{}{}",
r.harness,
r.model
.as_deref()
.map(|m| format!(" {m}"))
.unwrap_or_default(),
r.label
.as_deref()
.map(|l| format!(" ({l})"))
.unwrap_or_default()
);
match sides.iter_mut().find(|(s, _)| *s == side) {
Some((_, v)) => v.push(r),
None => sides.push((side, vec![r])),
}
}
let mut s = format!("Re-enactments of {session}: {} runs\n", runs.len());
if let Some(first) = runs.first() {
s.push_str(&format!(" the tape: {} calls\n", first.tape_calls));
}
s.push_str(
" side runs to divergence alignment exit 0 handoff tokens in/out seconds\n",
);
for (side, rs) in &sides {
let n = rs.len() as f64;
let ok = rs.iter().filter(|r| r.exit == 0).count();
let handed = rs.iter().filter(|r| r.handoff).count();
s.push_str(&format!(
" {:<37} {:>4} {:>13.1} {:>9.2} {:>3}/{:<2} {:>3}/{:<2} {:>8.0}/{:<8.0} {:>7.1}\n",
side,
rs.len(),
mean(rs.iter().map(|r| r.steps_to_divergence as f64)),
mean(rs.iter().map(|r| r.alignment)),
ok,
n as usize,
handed,
n as usize,
mean(rs.iter().map(|r| r.tokens.input as f64)),
mean(rs.iter().map(|r| r.tokens.output as f64)),
mean(rs.iter().map(|r| r.seconds)),
));
}
if runs.is_empty() {
s.push_str(" no runs yet; amem reenact run <session>\n");
}
Ok(s)
}
#[cfg(test)]
mod tests {
use super::*;
use dm_server::tape::{Kind, TapeCall, TapeHeader, TapeStart};
use serde_json::json;
#[test]
fn alignment_is_the_longest_shared_order_of_tools() {
let v = |xs: &[&str]| xs.iter().map(|s| (*s).to_string()).collect::<Vec<_>>();
assert_eq!(
aligned(
&v(&["t0", "search", "get", "write"]),
&v(&["t0", "get", "write"])
),
3
);
assert_eq!(aligned(&v(&["a", "b"]), &v(&["b", "a"])), 1);
assert_eq!(aligned(&v(&[]), &v(&["a"])), 0);
}
fn tape() -> Tape {
let call = |n: usize, tool: &str, arguments: serde_json::Value| TapeCall {
n,
at: now(),
tool: tool.into(),
arguments,
occurrence: 1,
label: dm_core::Label::Internal,
result: json!({ "content": [] }),
is_error: false,
};
Tape {
header: TapeHeader {
session: "sess-a".into(),
domain: "dom-me".into(),
started_at: now(),
start: TapeStart {
harness: "agy".into(),
prompt: Some("Finish the import.".into()),
..TapeStart::default()
},
pinned: false,
},
calls: vec![
call(1, "t0", json!({})),
call(2, "search", json!({"query": "import"})),
call(
3,
"write",
json!({"body": "Done.", "entry_type": "handoff"}),
),
],
}
}
#[test]
fn a_run_is_scored_against_its_tape() {
let p = Playback::new(tape());
p.answer("t0", &json!({}), Kind::Read);
p.answer("search", &json!({"query": "other"}), Kind::Read);
p.answer(
"write",
&json!({"body": "Stopped at four.", "entry_type": "handoff"}),
Kind::Mutation,
);
let played = p.played();
let (n, alignment, captured, handoff) = scored(&played);
assert_eq!(played.steps_to_divergence, 1);
assert_eq!(n, 3, "the same tools in the same order");
assert!((alignment - 1.0).abs() < f64::EPSILON);
assert_eq!(captured.len(), 1);
assert_eq!(captured[0]["arguments"]["body"], "Stopped at four.");
assert!(handoff, "captured, but left");
}
#[test]
fn a_tape_names_its_harness_by_binary_and_repeated_runs_need_a_key() {
assert!(matches!(
harness_named("agy"),
Some(HarnessArg::Antigravity)
));
assert!(harness_named("nope").is_none());
let args = |runs: usize, auth: AuthArg| RunArgs {
session: "sess-a".into(),
harness: None,
model: None,
auth,
provider: None,
runs,
repo: None,
label: None,
backend: BackendArg::Auto,
unconfined: false,
keep_tree: false,
};
assert!(check_runs(&args(1, AuthArg::Subscription)).is_ok());
let e = check_runs(&args(3, AuthArg::Subscription)).unwrap_err();
assert!(e.to_string().contains("API key or the gateway"), "{e}");
assert!(check_runs(&args(3, AuthArg::Key)).is_ok());
assert!(check_runs(&args(0, AuthArg::Key)).is_err());
}
#[test]
fn the_report_puts_each_side_on_a_line() {
let dir = tempfile::tempdir().unwrap();
let r = |harness: &str, model: &str, steps: usize, exit: i32| RunReport {
run: "rx-1".into(),
session: "sess-a".into(),
at: now(),
harness: harness.into(),
model: Some(model.into()),
label: None,
rerun_session: "sess-b".into(),
exit,
tape_calls: 4,
calls: 4,
steps_to_divergence: steps,
diverged: steps < 4,
aligned: 4,
alignment: 1.0,
captured: Vec::new(),
handoff: true,
answer: String::new(),
tokens: Tokens::default(),
seconds: 2.0,
tree: Tree {
commit: None,
original_dirty: false,
kept_at: None,
},
};
let path = dir.path().join(RUNS).join("sess-a.jsonl");
std::fs::create_dir_all(path.parent().unwrap()).unwrap();
let lines: Vec<String> = [
r("claude", "opus", 4, 0),
r("claude", "opus", 2, 0),
r("codex", "gpt", 1, 1),
]
.iter()
.map(|x| serde_json::to_string(x).unwrap())
.collect();
std::fs::write(&path, lines.join("\n")).unwrap();
let text = report(dir.path(), "sess-a").unwrap();
assert!(
text.contains("3 runs") && text.contains("the tape: 4 calls"),
"{text}"
);
let claude = text.lines().find(|l| l.contains("claude opus")).unwrap();
assert!(
claude.contains(" 2 ") && claude.contains("3.0"),
"{claude}"
);
assert!(
text.lines()
.any(|l| l.contains("codex gpt") && l.contains("0/1")),
"{text}"
);
assert!(report(dir.path(), "../x").is_err());
}
#[cfg(unix)]
#[test]
fn git_on_the_host_runs_nothing_the_harness_planted_and_carries_no_credential() {
use std::os::unix::fs::PermissionsExt;
let dir = tempfile::tempdir().unwrap();
let repo = dir.path().join("r");
std::fs::create_dir_all(&repo).unwrap();
git(&repo, &["init", "-q"]).unwrap();
git(
&repo,
&[
"-c",
"user.name=t",
"-c",
"user.email=t@example.org",
"commit",
"-q",
"--allow-empty",
"-m",
"c",
],
)
.unwrap();
// what a confined harness could leave in the working tree's .git
let marker = dir.path().join("ran");
let script = dir.path().join("planted.sh");
std::fs::write(&script, format!("#!/bin/sh\nenv > {}\n", marker.display())).unwrap();
std::fs::set_permissions(&script, std::fs::Permissions::from_mode(0o755)).unwrap();
let config = repo.join(".git/config");
let mut text = std::fs::read_to_string(&config).unwrap();
text.push_str(&format!("[core]\n\tfsmonitor = {}\n", script.display()));
std::fs::write(&config, text).unwrap();
std::fs::copy(&script, repo.join(".git/hooks/post-checkout")).unwrap();
std::fs::write(repo.join("new.txt"), "x").unwrap();
let (commit, dirty) = commit_of(&repo).unwrap();
assert_eq!(commit.len(), 40);
assert!(dirty);
let into = checkout(&repo, &commit, "t").unwrap();
assert!(!marker.exists(), "a planted fsmonitor or hook ran");
remove_checkout(&into);
}
}