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, deposit_result};
use crate::prompt::resolve::WorkerConfig;
use crate::prompt::{Deps, IdGen, SystemClock};
use crate::template::{GitRunner, RealGit};
use crate::workspace::{agent_worktree, fixture};
use brazen::FinishReason;
use std::cell::Cell;
use std::path::Path;
use std::sync::atomic::AtomicBool;
struct SeqIdGen(Cell<u32>);
impl IdGen for SeqIdGen {
fn short(&self) -> String {
let n = self.0.get();
self.0.set(n + 1);
format!("id{n:04}")
}
}
const GATE_WORKFLOW: &str = "events:\n \
worker_return:\n - dispatch(verifier)\n - gate_return_on(verifier.approve)\n \
verifier_approve:\n - deliver_result\n";
fn gate_cfg(role: &str, workflow: &str) -> WorkerConfig {
WorkerConfig {
role: role.into(),
workflow: Workflow::parse(workflow, Path::new("workflow.yaml")).unwrap(),
..worker_config()
}
}
#[allow(clippy::too_many_arguments)]
fn deps<'a>(
adapter: &'a StubAdapter,
sleeper: &'a StubSleeper,
git: &'a RealGit,
clock: &'a SystemClock,
id: &'a SeqIdGen,
tools: &'a StubToolExecutor,
stop: &'a AtomicBool,
launcher: &'a RecLauncher,
cfg_root: &'a Path,
) -> Deps<'a> {
Deps {
adapter,
sleeper,
git,
clock,
id_gen: id,
tool_executor: tools,
config_root: cfg_root,
adapter_target: None,
stop,
launcher,
}
}
#[test]
fn a_verifier_gates_a_workers_return_end_to_end_config_only() {
let (_h, ws) = fixture::workspace();
fixture::amend_config(&ws, &[("souls/verifier.md", "You are a verifier.")]);
let parent = "20260101-a1";
let parent_wt = fixture::spawn_root(&ws, parent);
let git = RealGit::new();
let clock = SystemClock;
let id = SeqIdGen(Cell::new(1));
let tools = StubToolExecutor::ok();
let sleeper = StubSleeper::default();
let stop = AtomicBool::new(false);
let rec = RecLauncher::default();
let cfg_root = tempfile::TempDir::new().unwrap();
use crate::prompt::child_dispatch::{ChildDispatchRequest, run as dispatch_child};
let wreq = ChildDispatchRequest {
repo: &ws,
parent_branch: parent,
parent_worktree: &parent_wt,
role: "worker",
goal: "do it",
fork_point: None,
};
let worker = dispatch_child(&wreq, &git, &clock, &id, &rec).unwrap();
let worker_wt = agent_worktree(&ws, &worker);
std::fs::write(worker_wt.join("out.txt"), "the work\n").unwrap();
git.run(&worker_wt, &["add", "-A"]).unwrap();
git.run(&worker_wt, &["commit", "-m", "work"]).unwrap();
let wtip = git.run_capture(&worker_wt, &["rev-parse", "HEAD"]).unwrap();
deposit_result(
&ws,
parent,
&worker,
inbox::Epitaph::FinalResponse,
wtip.trim(),
Some("done"),
&clock,
)
.unwrap();
let hold = unreachable_adapter();
let d1 = deps(
&hold,
&sleeper,
&git,
&clock,
&id,
&tools,
&stop,
&rec,
cfg_root.path(),
);
let out = run(&ws, parent, None, &d1, &mut || {
Ok(gate_cfg("worker", GATE_WORKFLOW))
})
.unwrap();
assert!(
matches!(out, AdvanceOutcome::NothingToDo),
"held, no parent step: {out:?}"
);
assert!(
inbox::inbox_dir(&ws, parent)
.join(format!("{worker}-001.md"))
.exists()
);
let verifier = rec
.invocations
.borrow()
.iter()
.find(|a| **a != worker)
.cloned()
.expect("verifier launched");
assert!(
git.run(
&parent_wt,
&[
"merge-base",
"--is-ancestor",
wtip.trim(),
&crate::workspace::agent_ref(&verifier)
]
)
.is_ok()
);
let approve = StubAdapter::scripted([StubAdapter::reply_ok(&stream_of(
FinishReason::Stop,
&[Block::Text("APPROVE")],
))]);
let d2 = deps(
&approve,
&sleeper,
&git,
&clock,
&id,
&tools,
&stop,
&rec,
cfg_root.path(),
);
let out = run(&ws, &verifier, None, &d2, &mut || {
Ok(gate_cfg("verifier", "events: {}\n"))
})
.unwrap();
assert!(matches!(out, AdvanceOutcome::Terminal), "{out:?}");
let deposited =
std::fs::read_to_string(inbox::inbox_dir(&ws, parent).join(format!("{verifier}-001.md")))
.unwrap();
assert!(
deposited.contains("epitaph: final-response"),
"{deposited:?}"
);
let react = StubAdapter::scripted([StubAdapter::reply_ok(&happy_response_bytes())]);
let d3 = deps(
&react,
&sleeper,
&git,
&clock,
&id,
&tools,
&stop,
&rec,
cfg_root.path(),
);
let out = run(&ws, parent, None, &d3, &mut || {
Ok(gate_cfg("worker", GATE_WORKFLOW))
})
.unwrap();
assert!(matches!(out, AdvanceOutcome::Terminal), "{out:?}");
assert!(parent_wt.join(format!("messages/001-{worker}.md")).exists());
assert_eq!(
std::fs::read_to_string(parent_wt.join("out.txt")).unwrap(),
"the work\n"
);
assert!(
!inbox::inbox_dir(&ws, parent)
.join(format!("{worker}-001.md"))
.exists()
);
assert!(
!inbox::inbox_dir(&ws, parent)
.join(format!("{verifier}-001.md"))
.exists()
);
}