use super::{World, ball, fake_bl, fake_fail};
use crate::binding::{work_worktree_path, workspace_path};
use crate::cli_outbound::Cli;
use crate::projects::join::JoinState;
use crate::start::{BallSpec, Deps, Payload, prepare};
use std::path::Path;
fn deps(w: &World, bl: &Cli, litany: &Cli) -> Deps {
Deps {
bl: bl.clone(),
litany: litany.clone(),
state_root: w.state.path().to_path_buf(),
yog_binary: std::path::PathBuf::from("/no/yog"),
}
}
fn mark_seeded(w: &World) {
let litany = crate::world::layout_under(w.yog.path()).litany;
std::fs::create_dir_all(&litany).unwrap();
std::fs::write(litany.join("models.yaml"), b"models: {}\n").unwrap();
}
#[test]
fn prepare_bare_bootstrap_seeds_and_news_under_the_default_name() {
let w = World::new();
let litany = w.litany();
let bl = Cli::new("/no/bl"); let inputs = w.inputs(crate::names::DEFAULT_NAME, Payload::Bare);
let p = prepare(&deps(&w, &bl, &litany), &inputs, "TS").unwrap();
assert_eq!(
p.workspace, "home",
"the bootstrap names without asking (§3.1)"
);
assert_eq!(p.binding, None, "the bare rung binds no work target");
assert_eq!(p.goal, "", "the operator types the bare payload");
assert_eq!(w.verbs(), vec!["prime", "new", "config"]);
let landed = workspace_path(w.yog.path(), &p.workspace);
assert!(landed.join("repo.git").is_dir(), "the birth landed");
let ops = w.ops();
let born = std::path::Path::new(&ops[1].argv[2]);
assert_eq!(born.parent(), landed.parent());
assert!(crate::scratch::is_temp(
&born.file_name().unwrap().to_string_lossy()
));
}
#[test]
fn prepare_seeds_the_world_bl_shim_before_the_prompt() {
let w = World::new();
let litany = w.litany();
let bl = Cli::new("/some/bl");
let inputs = w.inputs(crate::names::DEFAULT_NAME, Payload::Bare);
prepare(&deps(&w, &bl, &litany), &inputs, "TS").unwrap();
let tools = crate::world::layout_under(w.yog.path()).tools;
let shim = tools.join(crate::world::tools::BL);
assert_eq!(
std::fs::read_to_string(&shim).unwrap(),
crate::world::tools::shim_script(crate::world::tools::BL, &bl.exec_words()),
);
assert_eq!(
crate::world::tools::prepend_path(&tools, Some("/bin".to_owned())),
format!("{}:/bin", tools.display()),
"the seeded dir is the one the world PATH fronts",
);
}
#[test]
fn prepare_prompt_into_existing_skips_prime_new_and_mint() {
let w = World::new();
mark_seeded(&w);
let name = "cobalt-gecko";
std::fs::create_dir_all(workspace_path(w.yog.path(), name).join("repo.git")).unwrap();
let litany = Cli::new("/no/litany"); let bl = Cli::new("/no/bl");
let inputs = w.inputs(name, Payload::Bare);
let p = prepare(&deps(&w, &bl, &litany), &inputs, "TS").unwrap();
assert_eq!(p.workspace, name);
assert!(
w.ops().is_empty(),
"seeded + existing → nothing spawns (S1-T1)"
);
}
#[test]
fn prepare_path_rung_composes_the_target_and_runs_no_bl() {
let w = World::new();
let litany = w.litany();
let bl = Cli::new("/no/bl");
let dir = w.home.path().join("work");
let inputs = w.inputs("cobalt-gecko", Payload::Path { dir: dir.clone() });
let p = prepare(&deps(&w, &bl, &litany), &inputs, "TS").unwrap();
assert_eq!(
p.binding.as_ref(),
Some(&dir),
"the binding is the directory"
);
assert!(p.goal.contains(&dir.display().to_string()));
assert!(!w.verbs().iter().any(|v| v == "claim" || v == "create"));
}
#[test]
fn prepare_ball_ready_claims_after_new() {
let w = World::new();
let canonical = work_worktree_path(w.balls.path(), w.project.path(), "bl-r", None);
let bl = Cli::new(fake_bl(w.bin.path(), "x", &canonical));
let litany = w.litany();
let inputs = w.inputs(
"cobalt-gecko",
ball(w.project.path(), "bl-r", JoinState::ReadyStartable),
);
let p = prepare(&deps(&w, &bl, &litany), &inputs, "TS").unwrap();
assert_eq!(
p.binding.as_ref(),
Some(&canonical),
"the binding is the claim's worktree"
);
assert!(p.goal.contains("Ball bl-r: T"));
assert_eq!(w.verbs(), vec!["prime", "new", "config", "claim"]);
assert_eq!(
&w.ops()[3].argv[1..],
&["claim", "bl-r", "--as", "cobalt-gecko"],
"claim stamped with the workspace name"
);
}
#[test]
fn prepare_new_ball_creates_then_converges_to_one_claim() {
let w = World::new();
let canonical = work_worktree_path(w.balls.path(), w.project.path(), "bl-mint", None);
let bl = Cli::new(fake_bl(w.bin.path(), "bl-mint", &canonical));
let litany = w.litany();
let payload = Payload::Ball {
project: crate::naming::leaf(w.project.path()),
ball: BallSpec::New {
title: "Fresh".to_owned(),
body: "New body".to_owned(),
},
};
let inputs = w.inputs("cobalt-gecko", payload);
let p = prepare(&deps(&w, &bl, &litany), &inputs, "TS").unwrap();
assert!(
p.goal.contains("Ball bl-mint: Fresh"),
"re-planned as existing"
);
assert_eq!(
w.verbs(),
vec!["prime", "new", "config", "create", "config", "claim"]
);
}
#[test]
fn prepare_bound_ball_resumes_without_a_claim_or_mint() {
let w = World::new();
mark_seeded(&w);
std::fs::create_dir_all(workspace_path(w.yog.path(), "cobalt-gecko").join("repo.git")).unwrap();
let bl = Cli::new(fake_fail(w.bin.path(), "bl", "should not run"));
let litany = Cli::new("/no/litany");
let inputs = w.inputs(
"cobalt-gecko",
ball(w.project.path(), "bl-c", JoinState::Bound),
);
let p = prepare(&deps(&w, &bl, &litany), &inputs, "TS").unwrap();
assert_eq!(
p.binding,
Some(work_worktree_path(
w.balls.path(),
w.project.path(),
"bl-c",
None
))
);
assert!(w.ops().is_empty(), "resume: no claim, no mint, no re-seed");
}
#[test]
fn a_fresh_workspace_gains_only_the_one_name_the_template_cannot_carry() {
let w = World::new();
let litany = w.litany();
let bl = Cli::new("/no/bl");
let inputs = w.inputs(crate::names::DEFAULT_NAME, Payload::Bare);
let p = prepare(&deps(&w, &bl, &litany), &inputs, "TS").unwrap();
assert_eq!(w.verbs(), vec!["prime", "new", "config"]);
let committed = crate::config_edit::branch::config_file(
&workspace_path(w.yog.path(), &p.workspace),
"config/default",
"providers.yaml",
)
.unwrap();
let said = String::from_utf8_lossy(&committed);
assert_eq!(
said,
crate::test_support::TEMPLATE_PROVIDERS,
"the fake `litany config` commits nothing, so what is on the branch is \
still litany's own first commit — what this beat pins is that the pass \
RAN, and `start::grant` pins what it would have written",
);
assert!(
said.contains("message") && said.contains("dispatch"),
"the worker role already carries litany's own agent-to-agent primitives",
);
}
#[test]
fn prepare_ball_aborts_before_any_bl_on_a_substrate_failure() {
let w = World::new();
let bl = Cli::new(fake_bl(w.bin.path(), "x", Path::new("/wt")));
let litany = Cli::new(fake_fail(w.bin.path(), "litany", "no seed"));
let inputs = w.inputs(
"cobalt-gecko",
ball(w.project.path(), "bl-r", JoinState::ReadyStartable),
);
assert!(prepare(&deps(&w, &bl, &litany), &inputs, "TS").is_err());
assert_eq!(
w.verbs(),
vec!["prime"],
"aborted at the seed — no bl mutation"
);
}