mod harness;
pub(super) use harness::*;
use super::advance::{RecLauncher, worker_config};
use super::fixtures::*;
use crate::config::Workflow;
use crate::prompt::dispatch::advance::{AdvanceOutcome, run};
use crate::prompt::inbox::{self, inbox_dir, try_acquire};
use crate::prompt::resolve::WorkerConfig;
use crate::template::RealGit;
use std::path::Path;
use tempfile::TempDir;
#[test]
fn a_child_final_response_revives_the_parent_which_delivers_and_steps() {
let (_holder, ws, parent, parent_wt, child) = dispatched_child();
let launcher = RevivingLauncher::new(parent);
let out = advance_child(&ws, &child, &launcher);
assert!(matches!(out, AdvanceOutcome::Terminal), "{out:?}");
assert_eq!(
*launcher.invocations.borrow(),
vec![child.clone(), parent.into()]
);
assert_eq!(
launcher.outcome.borrow().as_deref(),
Some("Terminal"),
"the revived parent stepped to its own terminal event"
);
assert!(parent_wt.join(format!("messages/001-{child}.md")).exists());
assert_eq!(
std::fs::read_dir(inbox_dir(&ws, parent)).unwrap().count(),
0
);
assert!(
ws.join(format!("steps/{parent}/001/response.json"))
.exists()
);
}
#[test]
fn a_parent_with_a_held_lease_gets_no_second_driver() {
let (_holder, ws, parent, _parent_wt, child) = dispatched_child();
let held = try_acquire(&inbox_dir(&ws, parent)).unwrap().unwrap();
let launcher = RevivingLauncher::new(parent);
advance_child(&ws, &child, &launcher);
assert_eq!(
*launcher.invocations.borrow(),
vec![child.clone()],
"only the self-directed launch: the parent is already driven"
);
let pending: Vec<_> = std::fs::read_dir(inbox_dir(&ws, parent))
.unwrap()
.flatten()
.collect();
assert_eq!(pending.len(), 1);
let body = std::fs::read_to_string(pending[0].path()).unwrap();
assert!(body.contains("epitaph: final-response"), "got {body:?}");
drop(held);
}
pub(super) const CHILD: &str = "20260101-a1-20260102-b2";
const PARENT: &str = "20260101-a1";
pub(super) fn child_with_mail() -> TempDir {
let ws = TempDir::new().unwrap();
let wt = crate::workspace::agent_worktree(ws.path(), CHILD);
std::fs::create_dir_all(wt.join("messages")).unwrap();
std::fs::write(wt.join("goal.md"), "the goal").unwrap();
std::fs::write(wt.join("messages/001-user.md"), "hi").unwrap();
inbox::deposit(
ws.path(),
CHILD,
PARENT,
"go on",
&DescentClock,
&RealGit::new(),
)
.unwrap();
ws
}
fn parent_deposit(ws: &Path) -> String {
let entries: Vec<_> = std::fs::read_dir(inbox_dir(ws, PARENT))
.unwrap()
.flatten()
.collect();
assert_eq!(entries.len(), 1, "exactly one result deposit");
std::fs::read_to_string(entries[0].path()).unwrap()
}
#[test]
fn a_stopped_child_deposits_without_reviving_the_parent() {
let ws = child_with_mail();
let (adapter, sleeper, git) = (unreachable_adapter(), StubSleeper::default(), StubGit::ok());
let (clock, id) = (FixedClock::default(), FixedIdGen);
let tools = StubToolExecutor::ok();
let stop = std::sync::atomic::AtomicBool::new(true);
let rec = RecLauncher::default();
let mut deps = valid_deps(&adapter, &sleeper, &git, &clock, &id, &tools, ws.path());
deps.stop = &stop;
deps.launcher = &rec;
let out = run(ws.path(), CHILD, None, &deps, &mut || Ok(worker_config())).unwrap();
assert!(matches!(out, AdvanceOutcome::Terminal));
assert!(
parent_deposit(ws.path()).contains("epitaph: stopped"),
"the stopped result reached the parent's inbox"
);
assert!(
rec.invocations.borrow().is_empty(),
"neither the branch nor its parent is relaunched"
);
}
#[test]
fn an_exhausted_child_deposits_without_reviving_the_parent() {
let ws = child_with_mail();
let (adapter, sleeper, git) = (unreachable_adapter(), StubSleeper::default(), StubGit::ok());
let (clock, id) = (FixedClock::default(), FixedIdGen);
let tools = StubToolExecutor::ok();
let rec = RecLauncher::default();
let mut deps = valid_deps(&adapter, &sleeper, &git, &clock, &id, &tools, ws.path());
deps.launcher = &rec;
let mut capped = || {
Ok(WorkerConfig {
workflow: Workflow::parse(
"events: {}\nbudgets:\n max_depth: 0\n",
Path::new("workflow.yaml"),
)
.unwrap(),
..worker_config()
})
};
let out = run(ws.path(), CHILD, None, &deps, &mut capped).unwrap();
assert!(matches!(out, AdvanceOutcome::Terminal));
assert!(
parent_deposit(ws.path()).contains("epitaph: budget-exhausted"),
"the exhaustion result reached the parent's inbox"
);
assert!(
rec.invocations.borrow().is_empty(),
"neither the branch nor its parent is relaunched"
);
}