use std::cell::Cell;
use shepherd_core::plan::{
CommandObservation, InterfaceObservation, PathKind, PathState, PlanCheckReport,
PlanEnvironmentProbe, PlanProbeManifest, ProbeExpectation, SourceProbe, VerifiedPlanSeed,
check_plan_environment, parse_plan, render_lane, render_topology, validate_plan_structure,
};
fn list(values: impl IntoIterator<Item = String>) -> String {
format!("[{}]", values.into_iter().collect::<Vec<_>>().join(", "))
}
fn valid_plan(lane_count: usize) -> String {
let lanes = (0..lane_count).map(|index| format!("lane-{index}"));
let deliverables = (0..lane_count).map(|index| format!("deliverable-{index}"));
let cargo_targets = (0..lane_count).map(|index| format!("lane-{index}=plan-v2-{index}"));
let conductors = (0..lane_count).map(|index| format!("lane-{index}=conductor"));
let schedule = if lane_count >= 4 {
(0..lane_count)
.collect::<Vec<_>>()
.chunks(2)
.map(|chunk| {
let lanes = chunk
.iter()
.map(|index| format!("lane-{index}"))
.collect::<Vec<_>>()
.join("+");
format!("{lanes}@2")
})
.collect::<Vec<_>>()
} else {
Vec::new()
};
let scale_outcome = if lane_count >= 6 {
"outcome-scale"
} else {
"none"
};
let mut plan = format!(
"# Plan: v657\n\
## Scope contract\n\
- Schema: shepherd.plan/2\n\
- Run: v657\n\
- Seed: .shepherd/runs/v657/seed.md\n\
- Mesh: .shepherd/runs/v657/mesh.md\n\
- Planning evidence: .shepherd/runs/v657/phase0.md\n\
- Goal: ship measured outcomes\n\
- Deliverables: {}\n\
- Lanes: {}\n\
- Root roles: [shepherd, planter]\n\
- Child lead roles: [engineer, conductor]\n\
- Planning lead: engineer\n\
- Engineer count: 1\n\
- Review rejection limit: 3\n\
- Fourth rejection: malignant-revoke-quarantine-preserve-evidence-no-resume-root-lineage-replacement\n\
- Root continuation: fresh-root-preferred-new-run-binding-clears-child-authority\n\
- Exclusions: [native state, unowned work]\n\
## Assumptions and decisions\n\
| id | statement | source | owner | blocking | evidence/disposition |\n\
| a-1 | source exists | phase0 | engineer | false | verified |\n\
## Interfaces\n\
| id | version | producer | consumers | acceptance |\n\
| source | 1 | baseline | nodes | exact |\n\
## Phases\n\
| phase | predecessor frontier | node ids | disjointness proof |\n\
| build | baseline | all | exact paths |\n\
## Capacity\n\
```yaml\n\
logical_lane_limit: {}\n\
host_process_ceiling: 2\n\
project_spawn_max_parallel: 3\n\
plan_process_ceiling: 2\n\
parent_role_cap: 2\n\
run_budget: 2\n\
simultaneous_process_ceiling: 2\n\
per_lane_child_wave_ceiling: 2\n\
disk_min_mib: 4096\n\
model_quota: 2\n\
backpressure: queue-fair\n\
cargo_targets: {}\n\
conductors: {}\n\
schedule: {}\n\
scale_outcome: {scale_outcome}\n\
```\n\
## Nodes\n",
list(deliverables),
list(lanes),
lane_count.clamp(1, 2),
list(cargo_targets),
list(conductors),
list(schedule),
);
for index in 0..lane_count {
plan.push_str(&format!(
"### node-{index}\n\
```yaml\n\
id: node-{index}\n\
seed_deliverables: [deliverable-{index}]\n\
lane: lane-{index}\n\
role: coder\n\
work_kind: production\n\
outcome: lane {index} accepted\n\
owns: [src/lane-{index}.rs]\n\
forbidden: [.shepherd, src/lane-{}]\n\
consumes: [source@1]\n\
produces: [lane-{index}@1]\n\
depends_on: []\n\
red: {{command: [cargo, test, -p, lane-{index}], expects: failure, reason: regression proves absence}}\n\
green: {{command: [cargo, test, -p, lane-{index}], expects: success, reason: regression proves behavior}}\n\
eval: {{command: [cargo, test, -p, lane-{index}, --test, eval], threshold: 80}}\n\
evidence: .shepherd/runs/v657/lanes/lane-{index}/evidence.json\n\
review: auditor: exact lane acceptance\n\
failure_route: conductor-to-root\n\
rollback: revert lane-{index} owned paths\n\
```\n",
(index + 1) % lane_count.max(1)
));
}
plan
}
fn seed(lane_count: usize) -> VerifiedPlanSeed {
VerifiedPlanSeed {
run: "v657".into(),
relative_path: ".shepherd/runs/v657/seed.md".into(),
mesh: ".shepherd/runs/v657/mesh.md".into(),
deliverables: (0..lane_count)
.map(|index| format!("deliverable-{index}"))
.collect(),
outcomes: vec!["outcome-scale".into()],
}
}
fn validate(source: &str, lane_count: usize) -> Result<shepherd_core::plan::PlanTopology, String> {
let document = parse_plan(source).map_err(|error| error.to_string())?;
validate_plan_structure(&document, &seed(lane_count)).map_err(|error| error.to_string())
}
#[test]
fn plan_v2_parses_validates_and_renders_byte_identically() {
let topology = validate(&valid_plan(2), 2).expect("valid plan-v2");
assert_eq!(topology.schema, "shepherd.plan-topology/2");
let first = render_topology(&topology);
let second = render_topology(&topology);
assert_eq!(first, second);
assert!(first.ends_with(b"\n"));
#[cfg(feature = "json")]
{
let value: serde_json::Value =
serde_json::from_slice(&first).expect("topology is valid JSON");
assert_eq!(value["nodes"].as_array().map(Vec::len), Some(2));
}
let topology_text = String::from_utf8(first).expect("utf8 topology");
for marker in [
"\"schema\": \"shepherd.plan-topology/2\"",
"\"cargo_targets\"",
"\"conductors\"",
"\"schedule\"",
"\"nodes\"",
"\"red\"",
"\"review\"",
] {
assert!(
topology_text.contains(marker),
"missing topology marker {marker}"
);
}
let lane = render_lane(&topology, "lane-0").expect("lane slice");
assert_eq!(lane, render_lane(&topology, "lane-0").expect("repeat"));
let lane = String::from_utf8(lane).expect("utf8 lane");
assert!(lane.contains("# Lane: lane-0"));
assert!(lane.contains("node-0"));
assert!(!lane.contains("node-1"));
}
#[test]
fn plan_v2_rejects_unknown_missing_and_placeholder_sections() {
let valid = valid_plan(2);
for (name, subject) in [
(
"unknown",
valid.replace("## Nodes", "## Surprise\ntext\n## Nodes"),
),
(
"missing",
valid.replace("## Interfaces\n", "## Interfacez\n"),
),
(
"placeholder",
valid.replace("ship measured outcomes", "TODO <measurable outcome>"),
),
] {
let error = validate(&subject, 2).expect_err(name);
assert!(
error.contains("section") || error.contains("placeholder"),
"{name}: {error}"
);
}
}
#[test]
fn plan_v2_rejects_cycles_dangling_edges_and_unreachable_nodes() {
let valid = valid_plan(2);
let cycle = valid
.replacen("depends_on: []", "depends_on: [node-1]", 1)
.replacen("depends_on: []", "depends_on: [node-0]", 1);
assert!(validate(&cycle, 2).expect_err("cycle").contains("cycle"));
let dangling = valid.replacen("depends_on: []", "depends_on: [absent-node]", 1);
assert!(
validate(&dangling, 2)
.expect_err("dangling")
.contains("dangling")
);
let orphan = valid.replace(
"seed_deliverables: [deliverable-1]",
"seed_deliverables: []",
);
let error = validate(&orphan, 2).expect_err("unreachable");
assert!(error.contains("unreachable") || error.contains("uncovered"));
}
#[test]
fn plan_v2_rejects_uncovered_duplicate_and_interface_producers() {
let valid = valid_plan(2);
let uncovered = valid.replace(
"seed_deliverables: [deliverable-1]",
"seed_deliverables: [deliverable-0]",
);
let error = validate(&uncovered, 2).expect_err("uncovered/duplicate");
assert!(error.contains("deliverable"));
let duplicate_interface = valid.replace("produces: [lane-1@1]", "produces: [lane-0@1]");
assert!(
validate(&duplicate_interface, 2)
.expect_err("duplicate interface")
.contains("interface producer")
);
}
#[test]
fn plan_v2_rejects_concurrent_path_overlap_but_allows_ordered_handoffs() {
let valid = valid_plan(2);
let overlap = valid.replace("owns: [src/lane-1.rs]", "owns: [src/lane-0.rs]");
assert!(
validate(&overlap, 2)
.expect_err("concurrent overlap")
.contains("overlap")
);
let ordered = overlap.replace(
"produces: [lane-1@1]\ndepends_on: []",
"produces: [lane-1@1]\ndepends_on: [node-0]",
);
validate(&ordered, 2).expect("ordered ownership handoff");
}
#[test]
fn plan_v2_rejects_invalid_roles_work_kinds_and_conductor_work() {
let valid = valid_plan(2);
for (name, subject) in [
(
"root-role",
valid.replacen("role: coder", "role: shepherd", 1),
),
(
"invalid-role",
valid.replacen("role: coder", "role: wizard", 1),
),
(
"invalid-work-kind",
valid.replacen("work_kind: production", "work_kind: magic", 1),
),
(
"conductor-planning",
valid
.replacen("role: coder", "role: conductor", 1)
.replacen("work_kind: production", "work_kind: planning", 1),
),
(
"conductor-implementation",
valid.replacen("role: coder", "role: conductor", 1),
),
] {
let error = validate(&subject, 2).expect_err(name);
assert!(
error.contains("role") || error.contains("work kind") || error.contains("Conductor"),
"{name}: {error}"
);
}
}
#[test]
fn plan_v2_requires_red_green_eval_review_failure_and_rollback() {
let valid = valid_plan(2);
for field in [
"red",
"green",
"eval",
"evidence",
"review",
"failure_route",
"rollback",
] {
let needle = valid
.lines()
.find(|line| line.trim_start().starts_with(&format!("{field}:")))
.expect("fixture field");
let subject = valid.replacen(&format!("{needle}\n"), "", 1);
let error = validate(&subject, 2).expect_err(field);
assert!(error.contains(field), "{field}: {error}");
}
}
#[test]
fn plan_v2_capacity_is_explicit_disjoint_and_scale_bound() {
let valid = valid_plan(6);
validate(&valid, 6).expect("six-lane schedule");
for (name, subject) in [
(
"zero",
valid.replace("logical_lane_limit: 2", "logical_lane_limit: 0"),
),
(
"unsafe-process",
valid.replace(
"simultaneous_process_ceiling: 2",
"simultaneous_process_ceiling: 3",
),
),
(
"duplicate-target",
valid.replace("lane-1=plan-v2-1", "lane-1=plan-v2-0"),
),
(
"overlap-schedule",
valid.replace("lane-2+lane-3@2", "lane-1+lane-2@2"),
),
(
"missing-scale-binding",
valid.replace("scale_outcome: outcome-scale", "scale_outcome: none"),
),
] {
let error = validate(&subject, 6).expect_err(name);
assert!(
error.contains("capacity")
|| error.contains("schedule")
|| error.contains("cargo target")
|| error.contains("scale outcome"),
"{name}: {error}"
);
}
}
#[test]
fn plan_v2_records_pressure_bands_without_a_global_lane_cap() {
for (lanes, marker) in [
(2, "routine"),
(3, "beefy"),
(4, "beefy-mega"),
(5, "mega"),
(6, "mega-exceptional"),
(8, "exceptional OS-scale"),
(9, "no global cap"),
] {
let topology = validate(&valid_plan(lanes), lanes).expect("capacity band plan");
assert!(
topology.capacity_policy.contains(marker),
"{lanes}: {}",
topology.capacity_policy
);
}
}
#[test]
fn plan_v2_queues_logically_ready_children_above_the_live_process_ceiling() {
let plan = valid_plan(4).replace(
"per_lane_child_wave_ceiling: 2",
"per_lane_child_wave_ceiling: 50",
);
let topology = validate(&plan, 4).expect("logical ready-work exceeds resident processes");
assert_eq!(topology.capacity.per_lane_child_wave_ceiling, 50);
assert_eq!(topology.capacity.simultaneous_process_ceiling, 2);
assert_eq!(topology.capacity.project_spawn_max_parallel, 3);
}
#[test]
fn one_logical_lane_can_budget_a_resident_conductor_and_two_children() {
let plan = valid_plan(1)
.replace("host_process_ceiling: 2", "host_process_ceiling: 3")
.replace("plan_process_ceiling: 2", "plan_process_ceiling: 3")
.replace("parent_role_cap: 2", "parent_role_cap: 3")
.replace("run_budget: 2", "run_budget: 3")
.replace(
"simultaneous_process_ceiling: 2",
"simultaneous_process_ceiling: 3",
)
.replace("model_quota: 2", "model_quota: 3");
let topology = validate(&plan, 1).expect("one lane may contain three resident processes");
assert_eq!(topology.capacity.logical_lane_limit, 1);
assert_eq!(topology.capacity.simultaneous_process_ceiling, 3);
assert_eq!(topology.capacity.per_lane_child_wave_ceiling, 2);
assert!(
validate(
&plan.replace(
"simultaneous_process_ceiling: 3",
"simultaneous_process_ceiling: 4"
),
1
)
.is_err(),
"the minimum of the five live ceilings still applies"
);
}
#[test]
fn small_plans_allow_optional_complete_nonoverlapping_capacity_schedules() {
for lane_count in 1..=3 {
let schedule = (0..lane_count)
.map(|index| format!("lane-{index}@2"))
.collect::<Vec<_>>()
.join(", ");
let plan =
valid_plan(lane_count).replace("schedule: []", &format!("schedule: [{schedule}]"));
let topology = validate(&plan, lane_count)
.expect("small plan may schedule bounded resident leads and children");
assert_eq!(topology.capacity.schedule.len(), lane_count);
let duplicate = plan.replace(
&format!("schedule: [{schedule}]"),
&format!("schedule: [{schedule}, lane-0@2]"),
);
assert!(
validate(&duplicate, lane_count)
.expect_err("duplicate scheduled lane")
.contains("overlaps")
);
let unsafe_wave = plan.replace("lane-0@2", "lane-0@3");
assert!(
validate(&unsafe_wave, lane_count)
.expect_err("unsafe wave")
.contains("unsafe wave")
);
if lane_count > 1 {
let incomplete =
plan.replace(&format!("schedule: [{schedule}]"), "schedule: [lane-0@2]");
assert!(
validate(&incomplete, lane_count)
.expect_err("incomplete explicit schedule")
.contains("cover every lane")
);
}
validate(&valid_plan(lane_count), lane_count).expect("small schedules remain optional");
}
}
struct FakeProbe {
identity: &'static str,
baseline: &'static str,
path: PathState,
symbols: usize,
interface: InterfaceObservation,
command: CommandObservation,
disk_mib: u64,
model_quota: usize,
project_spawn_max_parallel: usize,
host_process_ceiling: usize,
command_calls: Cell<usize>,
}
impl Default for FakeProbe {
fn default() -> Self {
Self {
identity: "dev:42:ino:7",
baseline: "0123456789abcdef",
path: PathState::File,
symbols: 1,
interface: InterfaceObservation {
schema: "shepherd.example/1".into(),
version: "1".into(),
},
command: CommandObservation {
exit: 0,
stdout: "semantic-ok\n".into(),
stderr: String::new(),
},
disk_mib: 8192,
model_quota: 4,
project_spawn_max_parallel: 3,
host_process_ceiling: 4,
command_calls: Cell::new(0),
}
}
}
impl SourceProbe for FakeProbe {
type Error = String;
fn worktree_identity(&self) -> Result<String, Self::Error> {
Ok(self.identity.into())
}
fn baseline(&self) -> Result<String, Self::Error> {
Ok(self.baseline.into())
}
fn path_state(&self, _path: &str) -> Result<PathState, Self::Error> {
Ok(self.path)
}
fn symbol_matches(&self, _path: &str, _symbol: &str) -> Result<usize, Self::Error> {
Ok(self.symbols)
}
fn interface(&self, _path: &str) -> Result<InterfaceObservation, Self::Error> {
Ok(self.interface.clone())
}
fn run(&self, _argv: &[String]) -> Result<CommandObservation, Self::Error> {
self.command_calls.set(self.command_calls.get() + 1);
Ok(self.command.clone())
}
fn available_disk_mib(&self) -> Result<u64, Self::Error> {
Ok(self.disk_mib)
}
fn model_quota(&self) -> Result<usize, Self::Error> {
Ok(self.model_quota)
}
fn project_spawn_max_parallel(&self) -> Result<usize, Self::Error> {
Ok(self.project_spawn_max_parallel)
}
fn host_process_ceiling(&self) -> Result<usize, Self::Error> {
Ok(self.host_process_ceiling)
}
}
fn probes() -> PlanProbeManifest {
PlanProbeManifest {
schema: "shepherd.plan-probes/1".into(),
worktree_identity: "dev:42:ino:7".into(),
baseline: "0123456789abcdef".into(),
probes: vec![
PlanEnvironmentProbe::Path {
path: "src/lane-0.rs".into(),
expectation: ProbeExpectation::Modify,
kind: PathKind::File,
},
PlanEnvironmentProbe::Symbol {
path: "src/lane-0.rs".into(),
symbol: "accepted_symbol".into(),
expected_matches: 1,
},
PlanEnvironmentProbe::Interface {
path: "contracts/example.json".into(),
schema: "shepherd.example/1".into(),
version: "1".into(),
},
PlanEnvironmentProbe::Command {
argv: vec![
"git".into(),
"rev-parse".into(),
"--verify".into(),
"HEAD".into(),
],
expected_exit: 0,
semantic_marker: "semantic-ok".into(),
},
],
}
}
fn environment_check(
probe: &FakeProbe,
manifest: &PlanProbeManifest,
) -> Result<PlanCheckReport, String> {
let topology = validate(&valid_plan(2), 2)?;
check_plan_environment(&topology, manifest, probe).map_err(|error| error.to_string())
}
#[test]
fn environment_check_is_explicit_nonmutating_and_fail_closed() {
let probe = FakeProbe::default();
let report = environment_check(&probe, &probes()).expect("environment check");
assert_eq!(report.probe_count, 4);
assert_eq!(probe.command_calls.get(), 1);
for argv in [
vec!["bash", "-c", "printf unsafe"],
vec!["git", "clean", "-fdx"],
vec!["git", "diff", "--output=stolen", "HEAD"],
vec!["git", "show", "--output=stolen", "HEAD"],
vec![
"git",
"-c",
"alias.rev-parse=!touch stolen",
"rev-parse",
"HEAD",
],
vec!["rg", "--pre", "touch stolen", "needle", "."],
vec!["cargo", "check", "--manifest-path", "elsewhere/Cargo.toml"],
vec!["cargo", "test", "--config", "alias.test='run stolen'"],
vec!["cargo", "@response-file"],
vec!["git", "rev-parse", "--verify", "HEAD;touch stolen"],
vec!["git", "rev-parse", "--verify", "$(touch stolen)"],
] {
let probe = FakeProbe::default();
let mut manifest = probes();
manifest.probes = vec![PlanEnvironmentProbe::Command {
argv: argv.into_iter().map(str::to_owned).collect(),
expected_exit: 0,
semantic_marker: "semantic-ok".into(),
}];
let error = environment_check(&probe, &manifest).expect_err("unsafe command");
assert!(error.contains("nonmutating argv"), "{error}");
assert_eq!(probe.command_calls.get(), 0, "unsafe command was executed");
}
}
#[test]
fn plan_and_probe_paths_reject_cross_platform_escape_and_alias_forms() {
let valid = valid_plan(2);
for path in [
"C:/escape",
"C:escape",
"//server/share",
r"server\share",
"src/file.rs:stream",
"src/../escape",
"src/./file.rs",
"src//file.rs",
"src/trailing/",
"src/trailing.",
"src/short~1.rs",
"src/café.rs",
"Src/file.rs",
"src/*.rs",
"NUL",
"src/COM1.txt",
"src/control\0name",
] {
let subject = valid.replacen("owns: [src/lane-0.rs]", &format!("owns: [{path}]"), 1);
let error = validate(&subject, 2).expect_err(path);
assert!(error.contains("path"), "{path}: {error}");
}
let topology = validate(&valid, 2).expect("valid topology");
for path in [
"C:/escape",
"//server/share",
r"server\share",
"src/file.rs:stream",
"src/../escape",
"src/./file.rs",
"src//file.rs",
"src/trailing/",
"src/trailing.",
"src/trailing ",
"src/short~1.rs",
"src/café.rs",
"Src/file.rs",
"src/*.rs",
"NUL",
"src/COM1.txt",
"src/control\0name",
] {
let probe = FakeProbe::default();
let mut manifest = probes();
manifest.probes = vec![PlanEnvironmentProbe::Path {
path: path.into(),
expectation: ProbeExpectation::Modify,
kind: PathKind::File,
}];
let error = check_plan_environment(&topology, &manifest, &probe)
.expect_err(path)
.to_string();
assert!(error.contains("invalid probe path"), "{path}: {error}");
}
}
#[test]
fn environment_check_rejects_root_path_symbol_interface_exit_marker_and_capacity_drift() {
let cases: Vec<(&str, FakeProbe, PlanProbeManifest)> = vec![
(
"root",
FakeProbe {
identity: "replacement",
..FakeProbe::default()
},
probes(),
),
(
"baseline",
FakeProbe {
baseline: "drift",
..FakeProbe::default()
},
probes(),
),
(
"path",
FakeProbe {
path: PathState::Directory,
..FakeProbe::default()
},
probes(),
),
(
"symbol",
FakeProbe {
symbols: 0,
..FakeProbe::default()
},
probes(),
),
(
"interface",
FakeProbe {
interface: InterfaceObservation {
schema: "wrong".into(),
version: "1".into(),
},
..FakeProbe::default()
},
probes(),
),
(
"exit",
FakeProbe {
command: CommandObservation {
exit: 1,
stdout: "semantic-ok".into(),
stderr: String::new(),
},
..FakeProbe::default()
},
probes(),
),
(
"marker",
FakeProbe {
command: CommandObservation {
exit: 0,
stdout: "prose only".into(),
stderr: String::new(),
},
..FakeProbe::default()
},
probes(),
),
(
"disk",
FakeProbe {
disk_mib: 1,
..FakeProbe::default()
},
probes(),
),
(
"quota",
FakeProbe {
model_quota: 1,
..FakeProbe::default()
},
probes(),
),
(
"host",
FakeProbe {
host_process_ceiling: 1,
..FakeProbe::default()
},
probes(),
),
];
for (name, probe, manifest) in cases {
environment_check(&probe, &manifest).expect_err(name);
}
}
#[test]
fn create_path_probe_requires_absence_and_rejects_links() {
let mut manifest = probes();
manifest.probes = vec![PlanEnvironmentProbe::Path {
path: "src/new.rs".into(),
expectation: ProbeExpectation::Create,
kind: PathKind::File,
}];
let missing = FakeProbe {
path: PathState::Missing,
..FakeProbe::default()
};
environment_check(&missing, &manifest).expect("absent create target");
let link = FakeProbe {
path: PathState::Symlink,
..FakeProbe::default()
};
assert!(
environment_check(&link, &manifest)
.expect_err("link")
.contains("symlink")
);
}