use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use std::process::Command;
use serde_json::json;
use tempfile::TempDir;
use super::breakers::ResourceBudget;
use super::providers::{ScriptedSpec, ScriptedVerify};
use super::*;
use crate::harness::{
CancelToken, ChunkRequest, ChunkResult, CodeHarness, HarnessCapabilities, HarnessError, Usage,
};
use crate::pipeline::{Action, Decider, DeciderVerdict, DecisionContext};
use octl_core::plan::Tier;
fn git(dir: &Path, args: &[&str]) {
let out = Command::new("git")
.arg("-C")
.arg(dir)
.args([
"-c",
"user.email=t@t",
"-c",
"user.name=t",
"-c",
"commit.gpgsign=false",
])
.args(args)
.output()
.expect("git runs");
assert!(
out.status.success(),
"git {args:?} failed: {}",
String::from_utf8_lossy(&out.stderr)
);
}
fn git_out(dir: &Path, args: &[&str]) -> String {
let out = Command::new("git")
.arg("-C")
.arg(dir)
.args(args)
.output()
.expect("git runs");
String::from_utf8_lossy(&out.stdout).trim().to_string()
}
fn init_repo() -> TempDir {
let dir = TempDir::new().unwrap();
let p = dir.path();
git(p, &["init", "-q", "-b", "main"]);
std::fs::write(p.join("seed.txt"), "seed\n").unwrap();
git(p, &["add", "."]);
git(p, &["commit", "-qm", "seed"]);
dir
}
struct CommitFake {
files: BTreeMap<String, String>,
}
impl CommitFake {
fn new(files: &[(&str, &str)]) -> Self {
Self {
files: files
.iter()
.map(|(k, v)| ((*k).to_string(), (*v).to_string()))
.collect(),
}
}
}
impl CodeHarness for CommitFake {
fn capabilities(&self) -> HarnessCapabilities {
HarnessCapabilities {
can_author_tests: true,
reports_usage: false,
honors_file_scope: false,
runs_checks: false,
}
}
fn run_chunk(
&self,
req: &ChunkRequest,
_cancel: &CancelToken,
) -> Result<ChunkResult, HarnessError> {
let wt = &req.worktree_path;
for (rel, content) in &self.files {
let dest = wt.join(rel);
if let Some(parent) = dest.parent() {
std::fs::create_dir_all(parent).unwrap();
}
std::fs::write(&dest, content).unwrap();
}
git(wt, &["add", "-A"]);
git(wt, &["commit", "-qm", "chunk edit"]);
let head = git_out(wt, &["rev-parse", "HEAD"]);
let changed: Vec<PathBuf> = self.files.keys().map(PathBuf::from).collect();
Ok(ChunkResult::committed(head, changed))
}
}
fn one_chunk_plan(files: &[&str], check_run: &str, acceptance_run: &str) -> serde_json::Value {
json!({
"acceptance": [{"kind": "check", "desc": "feature exists", "run": acceptance_run}],
"chunks": [{
"id": "c1",
"title": "the feature",
"tier": "code",
"brief": "implement the feature",
"files_touched": files,
"checks": [{"desc": "chunk check", "run": check_run}],
}],
})
}
fn config(repo: &Path, workdir: &Path, plan_files: &[&str]) -> PipelineConfig {
PipelineConfig {
repo: repo.to_path_buf(),
intent: "Add a feature file".to_string(),
source_branch: "main".to_string(),
files: plan_files.iter().map(PathBuf::from).collect(),
slug: Some("demo".to_string()),
test_cmd: r#"printf '{"reason":"build-finished","success":true}\n'"#.to_string(),
clippy_cmd: r#"printf '{"reason":"build-finished","success":true}\n'"#.to_string(),
workdir: workdir.to_path_buf(),
file_scope_slack: 0,
keep: false,
chunk_timeout: None,
max_build_concurrency: 1,
fix_loop: super::fixloop::FixLoopConfig::OFF,
budget: super::breakers::ResourceBudget::UNLIMITED,
}
}
#[test]
fn happy_path_merges_feature_into_source() {
let repo = init_repo();
let workdir = TempDir::new().unwrap();
let cfg = config(repo.path(), workdir.path(), &["feature.txt"]);
let spec = ScriptedSpec::new(one_chunk_plan(
&["feature.txt"],
"test -f feature.txt",
"test -f feature.txt",
));
let code = CommitFake::new(&[("feature.txt", "hi\n")]);
let verify = ScriptedVerify::passing();
let report = run_pipeline(&cfg, &spec, &code, &verify).expect("pipeline runs");
assert_eq!(report.status, "merged", "{report:#?}");
assert!(report.merged);
assert!(report.final_commit.is_some());
assert_eq!(report.chunks.len(), 1);
assert!(report.chunks[0].merged);
assert_eq!(report.chunks[0].floor_passed, Some(true));
assert!(report.verify.as_ref().unwrap().passed);
let main_files = git_out(repo.path(), &["show", "--stat", "main:feature.txt"]);
assert_eq!(main_files, "hi", "feature.txt content on main");
assert!(!super::git::branch_exists(repo.path(), "feat/demo"));
let spec_dec = report.decisions.iter().find(|d| d.actor == "spec").unwrap();
assert_eq!(spec_dec.decision_tier, DecisionTier::Decider);
let merge_dec = report
.decisions
.iter()
.find(|d| d.actor == "supervisor")
.unwrap();
assert_eq!(merge_dec.decision_tier, DecisionTier::Coordinator);
}
#[test]
fn floor_blocks_an_out_of_scope_merge() {
let repo = init_repo();
let workdir = TempDir::new().unwrap();
let cfg = config(repo.path(), workdir.path(), &["feature.txt"]);
let spec = ScriptedSpec::new(one_chunk_plan(&["feature.txt"], "true", "true"));
let code = CommitFake::new(&[("feature.txt", "hi\n"), ("secret.txt", "leak\n")]);
let verify = ScriptedVerify::passing();
let report = run_pipeline(&cfg, &spec, &code, &verify).expect("pipeline runs");
assert_eq!(report.status, "chunk_floor_blocked", "{report:#?}");
assert!(!report.merged);
assert_eq!(report.chunks[0].floor_passed, Some(false));
assert!(!report.chunks[0].merged);
let main_has = Command::new("git")
.arg("-C")
.arg(repo.path())
.args(["cat-file", "-e", "main:feature.txt"])
.status()
.unwrap()
.success();
assert!(!main_has, "a floor-blocked chunk must never reach main");
let branch = report.chunks[0].branch_preserved.as_ref().unwrap();
assert!(super::git::branch_exists(repo.path(), branch));
assert!(!super::git::branch_exists(repo.path(), "feat/demo"));
}
#[test]
fn verify_failure_blocks_the_merge() {
let repo = init_repo();
let workdir = TempDir::new().unwrap();
let cfg = config(repo.path(), workdir.path(), &["feature.txt"]);
let spec = ScriptedSpec::new(one_chunk_plan(&["feature.txt"], "true", "true"));
let code = CommitFake::new(&[("feature.txt", "hi\n")]);
let verify = ScriptedVerify::new(providers::VerifyJudgment {
passed: false,
summary: "does not match intent".to_string(),
findings: vec!["missing edge case".to_string()],
disposition: providers::VerifyDisposition::Fix,
});
let report = run_pipeline(&cfg, &spec, &code, &verify).expect("pipeline runs");
assert_eq!(report.status, "verify_failed");
assert!(!report.merged);
assert!(
report.chunks[0].merged,
"the chunk still merged into the integration branch"
);
assert!(!report.verify.as_ref().unwrap().passed);
let main_has = Command::new("git")
.arg("-C")
.arg(repo.path())
.args(["cat-file", "-e", "main:feature.txt"])
.status()
.unwrap()
.success();
assert!(!main_has);
}
#[test]
fn acceptance_check_failure_blocks_verify() {
let repo = init_repo();
let workdir = TempDir::new().unwrap();
let cfg = config(repo.path(), workdir.path(), &["feature.txt"]);
let spec = ScriptedSpec::new(one_chunk_plan(&["feature.txt"], "true", "false"));
let code = CommitFake::new(&[("feature.txt", "hi\n")]);
let verify = ScriptedVerify::passing();
let report = run_pipeline(&cfg, &spec, &code, &verify).expect("pipeline runs");
assert_eq!(report.status, "verify_failed");
let v = report.verify.as_ref().unwrap();
assert!(!v.acceptance_checks_passed);
assert!(v.judged_passed);
assert!(!v.passed);
assert!(!report.merged);
}
fn plan_missing_acceptance(files: &[&str]) -> serde_json::Value {
json!({
"chunks": [{
"id": "c1", "title": "t", "tier": "code", "brief": "b",
"files_touched": files,
"checks": [{"desc": "d", "run": "true"}],
}],
})
}
#[test]
fn persistently_invalid_plan_fails_with_raw_persisted_and_error_surfaced() {
let repo = init_repo();
let workdir = TempDir::new().unwrap();
let cfg = config(repo.path(), workdir.path(), &["feature.txt"]);
let spec = ScriptedSpec::sequence(vec![
plan_missing_acceptance(&["feature.txt"]),
plan_missing_acceptance(&["feature.txt"]),
]);
let code = CommitFake::new(&[("feature.txt", "hi\n")]);
let verify = ScriptedVerify::passing();
let err = run_pipeline(&cfg, &spec, &code, &verify).unwrap_err();
assert!(
err.report.is_none(),
"pre-plan failure should carry no report"
);
match &err.error {
PipelineError::PlanInvalid(msg) => {
assert!(
msg.contains("acceptance"),
"validator error not surfaced: {msg}"
);
assert!(
msg.contains("plan.invalid.json"),
"persisted path not named: {msg}"
);
}
other => panic!("expected PlanInvalid, got {other:?}"),
}
let calls = spec.repair_calls();
assert_eq!(calls.len(), 1, "expected exactly one repair re-prompt");
assert!(
calls[0].1.contains("acceptance"),
"repair was not fed the validator error: {}",
calls[0].1
);
let persisted = workdir.path().join("plan.invalid.json");
assert!(persisted.is_file(), "invalid plan not persisted");
let saved: serde_json::Value =
serde_json::from_str(&std::fs::read_to_string(&persisted).unwrap()).unwrap();
assert!(
saved.get("acceptance").is_none(),
"persisted plan should be the raw invalid one"
);
assert!(saved.get("chunks").is_some());
assert!(!super::git::branch_exists(repo.path(), "feat/demo"));
}
#[test]
fn repair_loop_feeds_validator_error_back_and_succeeds() {
let repo = init_repo();
let workdir = TempDir::new().unwrap();
let cfg = config(repo.path(), workdir.path(), &["feature.txt"]);
let spec = ScriptedSpec::sequence(vec![
plan_missing_acceptance(&["feature.txt"]),
one_chunk_plan(&["feature.txt"], "true", "true"),
]);
let code = CommitFake::new(&[("feature.txt", "hi\n")]);
let verify = ScriptedVerify::passing();
let report = run_pipeline(&cfg, &spec, &code, &verify).expect("pipeline runs");
assert_eq!(report.status, "merged", "{report:#?}");
let calls = spec.repair_calls();
assert_eq!(calls.len(), 1, "expected exactly one repair re-prompt");
let (invalid, error) = &calls[0];
assert!(error.contains("acceptance"), "error not fed back: {error}");
assert!(
invalid.get("acceptance").is_none() && invalid.get("chunks").is_some(),
"the invalid JSON produced was not fed back: {invalid}"
);
assert!(!workdir.path().join("plan.invalid.json").exists());
}
#[test]
fn repair_call_failure_persists_the_prior_invalid_plan() {
let repo = init_repo();
let workdir = TempDir::new().unwrap();
let cfg = config(repo.path(), workdir.path(), &["feature.txt"]);
let spec = ScriptedSpec::sequence_then_error(vec![plan_missing_acceptance(&["feature.txt"])]);
let code = CommitFake::new(&[("feature.txt", "hi\n")]);
let verify = ScriptedVerify::passing();
let err = run_pipeline(&cfg, &spec, &code, &verify).unwrap_err();
assert!(matches!(err.error, PipelineError::Spec(_)), "{err:?}");
let persisted = workdir.path().join("plan.invalid.json");
assert!(
persisted.is_file(),
"prior invalid plan not persisted on repair failure"
);
let saved: serde_json::Value =
serde_json::from_str(&std::fs::read_to_string(&persisted).unwrap()).unwrap();
assert!(saved.get("acceptance").is_none() && saved.get("chunks").is_some());
assert_eq!(spec.repair_calls().len(), 1);
}
#[test]
fn invalid_then_valid_plan_recovers_on_repair() {
let repo = init_repo();
let workdir = TempDir::new().unwrap();
let cfg = config(repo.path(), workdir.path(), &["feature.txt"]);
let spec = ScriptedSpec::sequence(vec![
json!({"nonsense": true}), one_chunk_plan(&["feature.txt"], "true", "true"),
]);
let code = CommitFake::new(&[("feature.txt", "hi\n")]);
let verify = ScriptedVerify::passing();
let report = run_pipeline(&cfg, &spec, &code, &verify).expect("pipeline runs");
assert_eq!(report.status, "merged", "{report:#?}");
}
#[test]
fn refuses_to_reuse_an_existing_integration_branch() {
let repo = init_repo();
let workdir = TempDir::new().unwrap();
git(repo.path(), &["branch", "feat/demo"]);
let cfg = config(repo.path(), workdir.path(), &["feature.txt"]);
let spec = ScriptedSpec::new(one_chunk_plan(&["feature.txt"], "true", "true"));
let code = CommitFake::new(&[("feature.txt", "hi\n")]);
let verify = ScriptedVerify::passing();
let err = run_pipeline(&cfg, &spec, &code, &verify).unwrap_err();
assert!(matches!(err.error, PipelineError::Setup(_)), "{err:?}");
}
#[test]
fn two_chunk_dag_stacks_and_merges() {
let repo = init_repo();
let workdir = TempDir::new().unwrap();
let mut cfg = config(repo.path(), workdir.path(), &["a.txt", "b.txt"]);
cfg.intent = "two chunk feature".to_string();
let plan = json!({
"acceptance": [{"kind": "check", "desc": "both exist", "run": "test -f a.txt && test -f b.txt"}],
"chunks": [
{"id": "c1", "title": "a", "tier": "code", "brief": "make a",
"files_touched": ["a.txt"], "checks": [{"desc": "a", "run": "true"}]},
{"id": "c2", "title": "b", "tier": "code", "brief": "make b", "deps": ["c1"],
"files_touched": ["b.txt"], "checks": [{"desc": "b", "run": "true"}]},
],
});
struct PerChunk;
impl CodeHarness for PerChunk {
fn capabilities(&self) -> HarnessCapabilities {
HarnessCapabilities {
can_author_tests: true,
reports_usage: false,
honors_file_scope: false,
runs_checks: false,
}
}
fn run_chunk(
&self,
req: &ChunkRequest,
_cancel: &CancelToken,
) -> Result<ChunkResult, HarnessError> {
let file = if req.chunk_id == "c1" {
"a.txt"
} else {
"b.txt"
};
std::fs::write(req.worktree_path.join(file), "x\n").unwrap();
git(&req.worktree_path, &["add", "-A"]);
git(&req.worktree_path, &["commit", "-qm", "edit"]);
let head = git_out(&req.worktree_path, &["rev-parse", "HEAD"]);
Ok(ChunkResult::committed(head, vec![PathBuf::from(file)]))
}
}
let report = run_pipeline(
&cfg,
&ScriptedSpec::new(plan),
&PerChunk,
&ScriptedVerify::passing(),
)
.expect("pipeline runs");
assert_eq!(report.status, "merged", "{report:#?}");
assert_eq!(report.chunks.len(), 2);
assert!(report.chunks.iter().all(|c| c.merged));
for f in ["a.txt", "b.txt"] {
assert!(
Command::new("git")
.arg("-C")
.arg(repo.path())
.args(["cat-file", "-e", &format!("main:{f}")])
.status()
.unwrap()
.success(),
"{f} must be on main"
);
}
}
#[test]
fn later_chunk_failure_preserves_the_integration_branch() {
let repo = init_repo();
let workdir = TempDir::new().unwrap();
let cfg = config(repo.path(), workdir.path(), &["a.txt"]);
let plan = json!({
"acceptance": [{"kind": "check", "desc": "a", "run": "true"}],
"chunks": [
{"id": "c1", "title": "a", "tier": "code", "brief": "make a",
"files_touched": ["a.txt"], "checks": [{"desc": "a", "run": "true"}]},
{"id": "c2", "title": "b", "tier": "code", "brief": "make b", "deps": ["c1"],
"files_touched": ["b.txt"], "checks": [{"desc": "b", "run": "true"}]},
],
});
struct PerChunk;
impl CodeHarness for PerChunk {
fn capabilities(&self) -> HarnessCapabilities {
HarnessCapabilities {
can_author_tests: true,
reports_usage: false,
honors_file_scope: false,
runs_checks: false,
}
}
fn run_chunk(
&self,
req: &ChunkRequest,
_cancel: &CancelToken,
) -> Result<ChunkResult, HarnessError> {
let files: Vec<&str> = if req.chunk_id == "c1" {
vec!["a.txt"]
} else {
vec!["b.txt", "stray.txt"]
};
for f in &files {
std::fs::write(req.worktree_path.join(f), "x\n").unwrap();
}
git(&req.worktree_path, &["add", "-A"]);
git(&req.worktree_path, &["commit", "-qm", "edit"]);
let head = git_out(&req.worktree_path, &["rev-parse", "HEAD"]);
Ok(ChunkResult::committed(
head,
files.iter().map(PathBuf::from).collect(),
))
}
}
let report = run_pipeline(
&cfg,
&ScriptedSpec::new(plan),
&PerChunk,
&ScriptedVerify::passing(),
)
.expect("pipeline runs");
assert_eq!(report.status, "chunk_floor_blocked", "{report:#?}");
assert!(report.chunks[0].merged, "c1 merged");
assert!(!report.chunks[1].merged, "c2 floor-blocked");
assert!(super::git::branch_exists(repo.path(), "feat/demo"));
assert!(!Command::new("git")
.arg("-C")
.arg(repo.path())
.args(["cat-file", "-e", "main:a.txt"])
.status()
.unwrap()
.success());
}
#[test]
fn lying_harness_no_commit_is_blocked_not_merged() {
let repo = init_repo();
let workdir = TempDir::new().unwrap();
let cfg = config(repo.path(), workdir.path(), &["feature.txt"]);
let spec = ScriptedSpec::new(one_chunk_plan(&["feature.txt"], "true", "true"));
let verify = ScriptedVerify::passing();
struct Liar;
impl CodeHarness for Liar {
fn capabilities(&self) -> HarnessCapabilities {
HarnessCapabilities {
can_author_tests: true,
reports_usage: false,
honors_file_scope: false,
runs_checks: false,
}
}
fn run_chunk(
&self,
req: &ChunkRequest,
_cancel: &CancelToken,
) -> Result<ChunkResult, HarnessError> {
std::fs::write(req.worktree_path.join("feature.txt"), "hi\n").unwrap();
Ok(ChunkResult::committed(
req.base_commit.clone(),
vec![PathBuf::from("feature.txt")],
))
}
}
let report = run_pipeline(&cfg, &spec, &Liar, &verify).expect("pipeline runs");
assert_eq!(report.status, "chunk_failed", "{report:#?}");
assert!(!report.merged);
assert!(!report.chunks[0].merged);
assert!(!Command::new("git")
.arg("-C")
.arg(repo.path())
.args(["cat-file", "-e", "main:feature.txt"])
.status()
.unwrap()
.success());
}
#[test]
fn empty_commit_is_blocked_not_merged() {
let repo = init_repo();
let workdir = TempDir::new().unwrap();
let cfg = config(repo.path(), workdir.path(), &["feature.txt"]);
let spec = ScriptedSpec::new(one_chunk_plan(&["feature.txt"], "true", "true"));
let verify = ScriptedVerify::passing();
struct EmptyCommitter;
impl CodeHarness for EmptyCommitter {
fn capabilities(&self) -> HarnessCapabilities {
HarnessCapabilities {
can_author_tests: true,
reports_usage: false,
honors_file_scope: false,
runs_checks: false,
}
}
fn run_chunk(
&self,
req: &ChunkRequest,
_cancel: &CancelToken,
) -> Result<ChunkResult, HarnessError> {
git(
&req.worktree_path,
&["commit", "-q", "--allow-empty", "-m", "empty"],
);
let head = git_out(&req.worktree_path, &["rev-parse", "HEAD"]);
Ok(ChunkResult::committed(head, vec![]))
}
}
let report = run_pipeline(&cfg, &spec, &EmptyCommitter, &verify).expect("pipeline runs");
assert_eq!(report.status, "chunk_failed", "{report:#?}");
assert!(!report.merged);
}
use super::fixloop::FixLoopConfig;
use std::sync::atomic::{AtomicU32, Ordering};
struct StrayOnFirstAttempt;
impl CodeHarness for StrayOnFirstAttempt {
fn capabilities(&self) -> HarnessCapabilities {
HarnessCapabilities {
can_author_tests: true,
reports_usage: false,
honors_file_scope: false,
runs_checks: false,
}
}
fn run_chunk(&self, req: &ChunkRequest, _c: &CancelToken) -> Result<ChunkResult, HarnessError> {
let wt = &req.worktree_path;
std::fs::write(wt.join("feature.txt"), "hi\n").unwrap();
let mut changed = vec![PathBuf::from("feature.txt")];
if req.attempt_id == "a1" {
std::fs::write(wt.join("stray.txt"), "leak\n").unwrap();
changed.push(PathBuf::from("stray.txt"));
}
git(wt, &["add", "-A"]);
git(wt, &["commit", "-qm", "edit"]);
Ok(ChunkResult::committed(
git_out(wt, &["rev-parse", "HEAD"]),
changed,
))
}
}
struct AlwaysStray;
impl CodeHarness for AlwaysStray {
fn capabilities(&self) -> HarnessCapabilities {
HarnessCapabilities {
can_author_tests: true,
reports_usage: false,
honors_file_scope: false,
runs_checks: false,
}
}
fn run_chunk(&self, req: &ChunkRequest, _c: &CancelToken) -> Result<ChunkResult, HarnessError> {
let wt = &req.worktree_path;
std::fs::write(wt.join("feature.txt"), "hi\n").unwrap();
std::fs::write(wt.join("stray.txt"), "leak\n").unwrap();
git(wt, &["add", "-A"]);
git(wt, &["commit", "-qm", "edit"]);
Ok(ChunkResult::committed(
git_out(wt, &["rev-parse", "HEAD"]),
vec![PathBuf::from("feature.txt"), PathBuf::from("stray.txt")],
))
}
}
struct IncrementingFeature {
calls: AtomicU32,
}
impl IncrementingFeature {
fn new() -> Self {
Self {
calls: AtomicU32::new(0),
}
}
}
impl CodeHarness for IncrementingFeature {
fn capabilities(&self) -> HarnessCapabilities {
HarnessCapabilities {
can_author_tests: true,
reports_usage: false,
honors_file_scope: false,
runs_checks: false,
}
}
fn run_chunk(&self, req: &ChunkRequest, _c: &CancelToken) -> Result<ChunkResult, HarnessError> {
let n = self.calls.fetch_add(1, Ordering::SeqCst) + 1;
let wt = &req.worktree_path;
std::fs::write(wt.join("feature.txt"), format!("version {n}\n")).unwrap();
git(wt, &["add", "-A"]);
git(wt, &["commit", "-qm", "edit"]);
Ok(ChunkResult::committed(
git_out(wt, &["rev-parse", "HEAD"]),
vec![PathBuf::from("feature.txt")],
))
}
}
#[test]
fn floor_blocked_chunk_recodes_then_merges() {
let repo = init_repo();
let workdir = TempDir::new().unwrap();
let mut cfg = config(repo.path(), workdir.path(), &["feature.txt"]);
cfg.fix_loop = FixLoopConfig {
max_recode_per_chunk: 1,
max_fix_iterations: 0,
max_respec: 0,
max_promotions: 0,
};
let spec = ScriptedSpec::new(one_chunk_plan(
&["feature.txt"],
"true",
"test -f feature.txt",
));
let report = run_pipeline(
&cfg,
&spec,
&StrayOnFirstAttempt,
&ScriptedVerify::passing(),
)
.expect("pipeline runs");
assert_eq!(report.status, "merged", "{report:#?}");
assert!(report.merged);
assert_eq!(report.recode_count, 1, "exactly one RE_CODE happened");
assert!(report.chunks[0].merged);
assert_eq!(report.chunks[0].floor_passed, Some(true));
assert!(main_has(&repo, "feature.txt"));
assert!(!main_has(&repo, "stray.txt"));
let recode = report
.decisions
.iter()
.find(|d| d.reason.contains("re-code chunk"))
.expect("a re-code decision is recorded");
assert_eq!(recode.decision_tier, DecisionTier::Coordinator);
}
#[test]
fn persistent_floor_failure_trips_the_circuit_breaker() {
let repo = init_repo();
let workdir = TempDir::new().unwrap();
let mut cfg = config(repo.path(), workdir.path(), &["feature.txt"]);
cfg.fix_loop = FixLoopConfig {
max_recode_per_chunk: 2,
max_fix_iterations: 0,
max_respec: 0,
max_promotions: 0,
};
let spec = ScriptedSpec::new(one_chunk_plan(&["feature.txt"], "true", "true"));
let report =
run_pipeline(&cfg, &spec, &AlwaysStray, &ScriptedVerify::passing()).expect("pipeline runs");
assert_eq!(report.status, "circuit_breaker", "{report:#?}");
assert!(!report.merged);
assert!(report.circuit_breaker.is_some(), "breaker reason recorded");
assert_eq!(report.recode_count, 2, "both re-codes were attempted");
let branch = report.chunks[0].branch_preserved.as_ref().unwrap();
assert!(super::git::branch_exists(repo.path(), branch));
assert!(!main_has(&repo, "feature.txt"));
}
#[test]
fn verify_failure_recodes_then_merges() {
let repo = init_repo();
let workdir = TempDir::new().unwrap();
let mut cfg = config(repo.path(), workdir.path(), &["feature.txt"]);
cfg.fix_loop = FixLoopConfig {
max_recode_per_chunk: 0,
max_fix_iterations: 2,
max_respec: 0,
max_promotions: 0,
};
let spec = ScriptedSpec::new(one_chunk_plan(&["feature.txt"], "true", "true"));
let verify = ScriptedVerify::sequence(vec![
providers::VerifyJudgment {
passed: false,
summary: "not yet".to_string(),
findings: vec!["needs the greeting".to_string()],
disposition: providers::VerifyDisposition::FixChunks {
chunk_ids: vec!["c1".to_string()],
},
},
providers::VerifyJudgment {
passed: true,
summary: "matches intent".to_string(),
findings: vec![],
disposition: providers::VerifyDisposition::Fix,
},
]);
let report =
run_pipeline(&cfg, &spec, &IncrementingFeature::new(), &verify).expect("pipeline runs");
assert_eq!(report.status, "merged", "{report:#?}");
assert!(report.merged);
assert_eq!(report.recode_count, 1, "verify FIX drove one re-code");
assert!(report.verify.as_ref().unwrap().passed);
assert!(main_has(&repo, "feature.txt"));
}
#[test]
fn verify_fix_loop_exhaustion_trips_the_breaker() {
let repo = init_repo();
let workdir = TempDir::new().unwrap();
let mut cfg = config(repo.path(), workdir.path(), &["feature.txt"]);
cfg.fix_loop = FixLoopConfig {
max_recode_per_chunk: 0,
max_fix_iterations: 1,
max_respec: 0,
max_promotions: 0,
};
let spec = ScriptedSpec::new(one_chunk_plan(&["feature.txt"], "true", "true"));
let verify = ScriptedVerify::new(providers::VerifyJudgment {
passed: false,
summary: "never happy".to_string(),
findings: vec!["still wrong".to_string()],
disposition: providers::VerifyDisposition::Fix,
});
let report =
run_pipeline(&cfg, &spec, &IncrementingFeature::new(), &verify).expect("pipeline runs");
assert_eq!(report.status, "circuit_breaker", "{report:#?}");
assert!(!report.merged);
assert!(report.circuit_breaker.is_some());
}
#[test]
fn spec_flaw_triggers_respec_then_merges() {
let repo = init_repo();
let workdir = TempDir::new().unwrap();
let mut cfg = config(repo.path(), workdir.path(), &["feature.txt"]);
cfg.fix_loop = FixLoopConfig {
max_recode_per_chunk: 0,
max_fix_iterations: 2,
max_respec: 1,
max_promotions: 0,
};
let v2 = json!({
"acceptance": [{"kind": "check", "desc": "exists", "run": "test -f feature.txt"}],
"chunks": [{
"id": "c1", "title": "the feature", "tier": "code",
"brief": "implement the feature CORRECTLY this time",
"files_touched": ["feature.txt"],
"checks": [{"desc": "chunk check", "run": "true"}],
}],
});
let spec = ScriptedSpec::sequence(vec![
one_chunk_plan(&["feature.txt"], "true", "test -f feature.txt"),
v2,
]);
let verify = ScriptedVerify::sequence(vec![
providers::VerifyJudgment {
passed: false,
summary: "the plan cannot meet intent".to_string(),
findings: vec!["wrong approach".to_string()],
disposition: providers::VerifyDisposition::SpecFlaw {
reason: "the plan cannot meet intent".to_string(),
chunk_ids: vec!["c1".to_string()],
},
},
providers::VerifyJudgment {
passed: true,
summary: "now matches".to_string(),
findings: vec![],
disposition: providers::VerifyDisposition::Fix,
},
]);
let report =
run_pipeline(&cfg, &spec, &IncrementingFeature::new(), &verify).expect("pipeline runs");
assert_eq!(report.status, "merged", "{report:#?}");
assert!(report.merged);
assert_eq!(report.respec_count, 1, "one re-spec happened");
assert_eq!(report.plan_rev, 2, "plan advanced to v2");
let calls = spec.respec_calls();
assert_eq!(calls.len(), 1);
assert!(
calls[0].1.contains("cannot meet intent"),
"reason fed: {}",
calls[0].1
);
let respec = report
.decisions
.iter()
.find(|d| d.reason.contains("re-spec to plan.v2"))
.expect("a re-spec decision is recorded");
assert_eq!(respec.decision_tier, DecisionTier::Decider);
assert!(workdir.path().join("plan.v2.json").is_file());
assert!(main_has(&repo, "feature.txt"));
}
#[test]
fn verify_fix_with_only_unknown_chunk_ids_does_not_blast_all_chunks() {
let repo = init_repo();
let workdir = TempDir::new().unwrap();
let mut cfg = config(repo.path(), workdir.path(), &["feature.txt"]);
cfg.fix_loop = FixLoopConfig {
max_recode_per_chunk: 0,
max_fix_iterations: 2,
max_respec: 0,
max_promotions: 0,
};
let spec = ScriptedSpec::new(one_chunk_plan(&["feature.txt"], "true", "true"));
let verify = ScriptedVerify::new(providers::VerifyJudgment {
passed: false,
summary: "nope".to_string(),
findings: vec!["x".to_string()],
disposition: providers::VerifyDisposition::FixChunks {
chunk_ids: vec!["ghost-chunk".to_string()],
},
});
let report =
run_pipeline(&cfg, &spec, &IncrementingFeature::new(), &verify).expect("pipeline runs");
assert_eq!(report.status, "verify_failed", "{report:#?}");
assert_eq!(report.recode_count, 0, "no chunk should be re-coded");
assert!(!report.merged);
}
#[test]
fn acceptance_check_failure_recodes_with_the_check_as_a_finding() {
let repo = init_repo();
let workdir = TempDir::new().unwrap();
let mut cfg = config(repo.path(), workdir.path(), &["feature.txt", "marker.txt"]);
cfg.fix_loop = FixLoopConfig {
max_recode_per_chunk: 0,
max_fix_iterations: 2,
max_respec: 0,
max_promotions: 0,
};
let spec = ScriptedSpec::new(one_chunk_plan(
&["feature.txt", "marker.txt"],
"true",
"test -f marker.txt",
));
struct MarkerOnRecode {
calls: AtomicU32,
}
impl CodeHarness for MarkerOnRecode {
fn capabilities(&self) -> HarnessCapabilities {
HarnessCapabilities {
can_author_tests: true,
reports_usage: false,
honors_file_scope: false,
runs_checks: false,
}
}
fn run_chunk(
&self,
req: &ChunkRequest,
_c: &CancelToken,
) -> Result<ChunkResult, HarnessError> {
let n = self.calls.fetch_add(1, Ordering::SeqCst) + 1;
let wt = &req.worktree_path;
std::fs::write(wt.join("feature.txt"), format!("v{n}\n")).unwrap();
let mut changed = vec![PathBuf::from("feature.txt")];
if n >= 2 {
std::fs::write(wt.join("marker.txt"), "m\n").unwrap();
changed.push(PathBuf::from("marker.txt"));
}
git(wt, &["add", "-A"]);
git(wt, &["commit", "-qm", "edit"]);
Ok(ChunkResult::committed(
git_out(wt, &["rev-parse", "HEAD"]),
changed,
))
}
}
let verify = ScriptedVerify::passing();
let report = run_pipeline(
&cfg,
&spec,
&MarkerOnRecode {
calls: AtomicU32::new(0),
},
&verify,
)
.expect("pipeline runs");
assert_eq!(report.status, "merged", "{report:#?}");
assert_eq!(
report.recode_count, 1,
"the acceptance failure drove one re-code"
);
assert!(main_has(&repo, "marker.txt"));
}
#[test]
fn recode_rebrief_carries_the_prior_failing_diff() {
let repo = init_repo();
let workdir = TempDir::new().unwrap();
let mut cfg = config(repo.path(), workdir.path(), &["feature.txt"]);
cfg.fix_loop = FixLoopConfig {
max_recode_per_chunk: 1,
max_fix_iterations: 0,
max_respec: 0,
max_promotions: 0,
};
let spec = ScriptedSpec::new(one_chunk_plan(
&["feature.txt"],
"true",
"test -f feature.txt",
));
struct BriefRecorder {
briefs: std::sync::Mutex<Vec<String>>,
}
impl CodeHarness for BriefRecorder {
fn capabilities(&self) -> HarnessCapabilities {
HarnessCapabilities {
can_author_tests: true,
reports_usage: false,
honors_file_scope: false,
runs_checks: false,
}
}
fn run_chunk(
&self,
req: &ChunkRequest,
_c: &CancelToken,
) -> Result<ChunkResult, HarnessError> {
self.briefs.lock().unwrap().push(req.brief.clone());
let wt = &req.worktree_path;
std::fs::write(wt.join("feature.txt"), "FAILING_CONTENT_MARKER\n").unwrap();
let mut changed = vec![PathBuf::from("feature.txt")];
if req.attempt_id == "a1" {
std::fs::write(wt.join("stray.txt"), "leak\n").unwrap();
changed.push(PathBuf::from("stray.txt"));
}
git(wt, &["add", "-A"]);
git(wt, &["commit", "-qm", "edit"]);
Ok(ChunkResult::committed(
git_out(wt, &["rev-parse", "HEAD"]),
changed,
))
}
}
let harness = BriefRecorder {
briefs: std::sync::Mutex::new(Vec::new()),
};
let report =
run_pipeline(&cfg, &spec, &harness, &ScriptedVerify::passing()).expect("pipeline runs");
assert_eq!(report.status, "merged", "{report:#?}");
let briefs = harness.briefs.into_inner().unwrap();
assert_eq!(briefs.len(), 2, "one initial attempt + one re-code");
assert!(
!briefs[0].contains("previous attempt's diff"),
"{}",
briefs[0]
);
assert!(
briefs[1].contains("previous attempt's diff"),
"re-brief missing the diff section: {}",
briefs[1]
);
assert!(briefs[1].contains("```diff"), "{}", briefs[1]);
assert!(
briefs[1].contains("FAILING_CONTENT_MARKER"),
"re-brief lost the failing diff content: {}",
briefs[1]
);
assert!(briefs[1].contains("stray.txt"), "{}", briefs[1]);
}
struct ScriptedFloor {
calls: AtomicU32,
fail_on: Vec<u32>,
}
impl CodeHarness for ScriptedFloor {
fn capabilities(&self) -> HarnessCapabilities {
HarnessCapabilities {
can_author_tests: true,
reports_usage: false,
honors_file_scope: false,
runs_checks: false,
}
}
fn run_chunk(&self, req: &ChunkRequest, _c: &CancelToken) -> Result<ChunkResult, HarnessError> {
let n = self.calls.fetch_add(1, Ordering::SeqCst);
let wt = &req.worktree_path;
std::fs::write(wt.join("feature.txt"), format!("version {n}\n")).unwrap();
let mut changed = vec![PathBuf::from("feature.txt")];
if self.fail_on.contains(&n) {
std::fs::write(wt.join("stray.txt"), format!("leak {n}\n")).unwrap();
changed.push(PathBuf::from("stray.txt"));
}
git(wt, &["add", "-A"]);
git(wt, &["commit", "-qm", "edit"]);
Ok(ChunkResult::committed(
git_out(wt, &["rev-parse", "HEAD"]),
changed,
))
}
}
#[test]
fn cumulative_recode_budget_caps_across_verify_iterations() {
let repo = init_repo();
let workdir = TempDir::new().unwrap();
let mut cfg = config(repo.path(), workdir.path(), &["feature.txt"]);
cfg.fix_loop = FixLoopConfig {
max_recode_per_chunk: 1,
max_fix_iterations: 2,
max_respec: 0,
max_promotions: 0,
};
let spec = ScriptedSpec::new(one_chunk_plan(
&["feature.txt"],
"true",
"test -f feature.txt",
));
let verify = ScriptedVerify::sequence(vec![
providers::VerifyJudgment {
passed: false,
summary: "not yet".to_string(),
findings: vec!["needs work".to_string()],
disposition: providers::VerifyDisposition::FixChunks {
chunk_ids: vec!["c1".to_string()],
},
},
providers::VerifyJudgment {
passed: true,
summary: "ok".to_string(),
findings: vec![],
disposition: providers::VerifyDisposition::Fix,
},
]);
let harness = ScriptedFloor {
calls: AtomicU32::new(0),
fail_on: vec![0, 2], };
let report = run_pipeline(&cfg, &spec, &harness, &verify).expect("pipeline runs");
assert_eq!(
report.status, "circuit_breaker",
"cumulative re-code budget must deny the second visit's floor re-code: {report:#?}"
);
assert!(!report.merged);
assert_eq!(
report.recode_count, 2,
"the cumulative budget capped the floor re-codes across visits"
);
}
#[test]
fn respec_that_removes_a_chunk_rolls_its_code_off_feat() {
let repo = init_repo();
let workdir = TempDir::new().unwrap();
let mut cfg = config(repo.path(), workdir.path(), &["a.txt", "b.txt"]);
cfg.fix_loop = FixLoopConfig {
max_recode_per_chunk: 0,
max_fix_iterations: 2,
max_respec: 1,
max_promotions: 0,
};
let v1 = json!({
"acceptance": [{"kind": "check", "desc": "a", "run": "test -f a.txt"}],
"chunks": [
{"id": "c1", "title": "a", "tier": "code", "brief": "make a",
"files_touched": ["a.txt"], "checks": [{"desc": "a", "run": "true"}]},
{"id": "c2", "title": "b", "tier": "code", "brief": "make b", "deps": ["c1"],
"files_touched": ["b.txt"], "checks": [{"desc": "b", "run": "true"}]},
],
});
let v2 = json!({
"acceptance": [{"kind": "check", "desc": "a", "run": "test -f a.txt"}],
"chunks": [
{"id": "c1", "title": "a", "tier": "code", "brief": "make a",
"files_touched": ["a.txt"], "checks": [{"desc": "a", "run": "true"}]},
],
});
let spec = ScriptedSpec::sequence(vec![v1, v2]);
let verify = ScriptedVerify::sequence(vec![
providers::VerifyJudgment {
passed: false,
summary: "c2 is wrong; drop it".to_string(),
findings: vec!["remove b".to_string()],
disposition: providers::VerifyDisposition::SpecFlaw {
reason: "c2 is wrong; drop it".to_string(),
chunk_ids: vec!["c2".to_string()],
},
},
providers::VerifyJudgment {
passed: true,
summary: "now matches".to_string(),
findings: vec![],
disposition: providers::VerifyDisposition::Fix,
},
]);
struct PerChunk;
impl CodeHarness for PerChunk {
fn capabilities(&self) -> HarnessCapabilities {
HarnessCapabilities {
can_author_tests: true,
reports_usage: false,
honors_file_scope: false,
runs_checks: false,
}
}
fn run_chunk(
&self,
req: &ChunkRequest,
_cancel: &CancelToken,
) -> Result<ChunkResult, HarnessError> {
let file = if req.chunk_id == "c1" {
"a.txt"
} else {
"b.txt"
};
std::fs::write(req.worktree_path.join(file), "x\n").unwrap();
git(&req.worktree_path, &["add", "-A"]);
git(&req.worktree_path, &["commit", "-qm", "edit"]);
let head = git_out(&req.worktree_path, &["rev-parse", "HEAD"]);
Ok(ChunkResult::committed(head, vec![PathBuf::from(file)]))
}
}
let report = run_pipeline(&cfg, &spec, &PerChunk, &verify).expect("pipeline runs");
assert_eq!(report.status, "merged", "{report:#?}");
assert_eq!(report.plan_rev, 2, "plan advanced to v2");
assert_eq!(report.respec_count, 1, "one re-spec happened");
assert!(
main_has(&repo, "a.txt"),
"kept chunk c1's file must reach main"
);
assert!(
!main_has(&repo, "b.txt"),
"removed chunk c2's code must be rolled off feat, not stranded on it"
);
}
struct SharedFileChunks;
impl CodeHarness for SharedFileChunks {
fn capabilities(&self) -> HarnessCapabilities {
HarnessCapabilities {
can_author_tests: true,
reports_usage: false,
honors_file_scope: false,
runs_checks: false,
}
}
fn run_chunk(&self, req: &ChunkRequest, _c: &CancelToken) -> Result<ChunkResult, HarnessError> {
let wt = &req.worktree_path;
let content = if req.chunk_id == "c1" {
"base\n"
} else {
"base\nextra\n"
};
std::fs::write(wt.join("shared.txt"), content).unwrap();
git(wt, &["add", "-A"]);
git(wt, &["commit", "-qm", "edit"]);
Ok(ChunkResult::committed(
git_out(wt, &["rev-parse", "HEAD"]),
vec![PathBuf::from("shared.txt")],
))
}
}
#[test]
fn verify_fix_rollback_reverts_transitive_dependents() {
let repo = init_repo();
let workdir = TempDir::new().unwrap();
let mut cfg = config(repo.path(), workdir.path(), &["shared.txt"]);
cfg.fix_loop = FixLoopConfig {
max_recode_per_chunk: 0,
max_fix_iterations: 2,
max_respec: 0,
max_promotions: 0,
};
let plan = json!({
"acceptance": [{"kind": "check", "desc": "exists", "run": "test -f shared.txt"}],
"chunks": [
{"id": "c1", "title": "a", "tier": "code", "brief": "make base",
"files_touched": ["shared.txt"], "checks": [{"desc": "a", "run": "true"}]},
{"id": "c2", "title": "b", "tier": "code", "brief": "append", "deps": ["c1"],
"files_touched": ["shared.txt"], "checks": [{"desc": "b", "run": "true"}]},
],
});
let verify = ScriptedVerify::sequence(vec![
providers::VerifyJudgment {
passed: false,
summary: "fix c1".to_string(),
findings: vec!["c1 wrong".to_string()],
disposition: providers::VerifyDisposition::FixChunks {
chunk_ids: vec!["c1".to_string()],
},
},
providers::VerifyJudgment {
passed: true,
summary: "ok".to_string(),
findings: vec![],
disposition: providers::VerifyDisposition::Fix,
},
]);
let report = run_pipeline(&cfg, &ScriptedSpec::new(plan), &SharedFileChunks, &verify)
.expect("pipeline runs");
assert_eq!(report.status, "merged", "{report:#?}");
assert!(report.merged);
assert_eq!(report.recode_count, 1);
assert_eq!(
git_out(repo.path(), &["show", "main:shared.txt"]),
"base\nextra"
);
}
#[test]
fn rollback_conflict_yields_a_terminal_report_naming_the_chunk() {
let repo = init_repo();
let workdir = TempDir::new().unwrap();
let mut cfg = config(repo.path(), workdir.path(), &["shared.txt"]);
cfg.fix_loop = FixLoopConfig {
max_recode_per_chunk: 0,
max_fix_iterations: 2,
max_respec: 0,
max_promotions: 0,
};
let plan = json!({
"acceptance": [{"kind": "check", "desc": "exists", "run": "test -f shared.txt"}],
"chunks": [
{"id": "c1", "title": "a", "tier": "code", "brief": "make base",
"files_touched": ["shared.txt"], "checks": [{"desc": "a", "run": "true"}]},
{"id": "c2", "title": "b", "tier": "code", "brief": "append",
"files_touched": ["shared.txt"], "checks": [{"desc": "b", "run": "true"}]},
],
});
let verify = ScriptedVerify::new(providers::VerifyJudgment {
passed: false,
summary: "fix c1".to_string(),
findings: vec!["c1 wrong".to_string()],
disposition: providers::VerifyDisposition::FixChunks {
chunk_ids: vec!["c1".to_string()],
},
});
let report = run_pipeline(&cfg, &ScriptedSpec::new(plan), &SharedFileChunks, &verify)
.expect("a replay conflict yields a terminal report, not a hard error");
assert_eq!(report.status, "rollback_conflict", "{report:#?}");
assert!(!report.merged);
let failure = report.failure.as_deref().unwrap_or("");
assert!(
failure.contains("`c2`"),
"failure should name the conflicting chunk c2: {failure:?}"
);
assert!(super::git::branch_exists(repo.path(), "feat/demo"));
assert_eq!(
git_out(repo.path(), &["show", "feat/demo:shared.txt"]),
"base\nextra",
"feat must hold the intact pre-rollback content, not be reset to fork"
);
}
#[test]
fn respec_rollback_conflict_leaves_run_state_consistent_with_the_old_branch() {
let repo = init_repo();
let workdir = TempDir::new().unwrap();
let mut cfg = config(repo.path(), workdir.path(), &["shared.txt"]);
cfg.fix_loop = FixLoopConfig {
max_recode_per_chunk: 0,
max_fix_iterations: 2,
max_respec: 1,
max_promotions: 0,
};
let v1 = json!({
"acceptance": [{"kind": "check", "desc": "exists", "run": "test -f shared.txt"}],
"chunks": [
{"id": "c1", "title": "a", "tier": "code", "brief": "make base",
"files_touched": ["shared.txt"], "checks": [{"desc": "a", "run": "true"}]},
{"id": "c2", "title": "b", "tier": "code", "brief": "append",
"files_touched": ["shared.txt"], "checks": [{"desc": "b", "run": "true"}]},
],
});
let v2 = json!({
"acceptance": [{"kind": "check", "desc": "exists", "run": "test -f shared.txt"}],
"chunks": [
{"id": "c2", "title": "b", "tier": "code", "brief": "append",
"files_touched": ["shared.txt"], "checks": [{"desc": "b", "run": "true"}]},
],
});
let spec = ScriptedSpec::sequence(vec![v1, v2]);
let verify = ScriptedVerify::new(providers::VerifyJudgment {
passed: false,
summary: "c1 is a spec flaw; drop it".to_string(),
findings: vec!["remove c1".to_string()],
disposition: providers::VerifyDisposition::SpecFlaw {
reason: "c1 is a spec flaw; drop it".to_string(),
chunk_ids: vec!["c1".to_string()],
},
});
let report = run_pipeline(&cfg, &spec, &SharedFileChunks, &verify)
.expect("terminal report, not a crash");
assert_eq!(report.status, "rollback_conflict", "{report:#?}");
assert!(!report.merged);
assert_eq!(
report.plan_rev, 1,
"old plan revision, re-spec never landed"
);
assert_eq!(
report.respec_count, 0,
"respec_count must not advance when the rollback aborted the re-spec"
);
assert_eq!(
report.chunks.len(),
2,
"both old-plan chunks still reported"
);
assert!(report.chunks.iter().any(|c| c.id == "c1"));
assert!(report.chunks.iter().any(|c| c.id == "c2"));
assert!(super::git::branch_exists(repo.path(), "feat/demo"));
assert_eq!(
git_out(repo.path(), &["show", "feat/demo:shared.txt"]),
"base\nextra"
);
}
struct DisjointChunks;
impl CodeHarness for DisjointChunks {
fn capabilities(&self) -> HarnessCapabilities {
HarnessCapabilities {
can_author_tests: true,
reports_usage: false,
honors_file_scope: false,
runs_checks: false,
}
}
fn run_chunk(&self, req: &ChunkRequest, _c: &CancelToken) -> Result<ChunkResult, HarnessError> {
let wt = &req.worktree_path;
let file = format!("{}.txt", req.chunk_id);
std::fs::write(wt.join(&file), format!("{} content\n", req.chunk_id)).unwrap();
git(wt, &["add", "-A"]);
git(wt, &["commit", "-qm", "edit"]);
Ok(ChunkResult::committed(
git_out(wt, &["rev-parse", "HEAD"]),
vec![PathBuf::from(file)],
))
}
}
#[test]
fn replayed_chunk_report_keeps_authored_commit_and_flags_replayed() {
let repo = init_repo();
let workdir = TempDir::new().unwrap();
let mut cfg = config(repo.path(), workdir.path(), &["c1.txt", "c2.txt", "c3.txt"]);
cfg.fix_loop = FixLoopConfig {
max_recode_per_chunk: 0,
max_fix_iterations: 2,
max_respec: 0,
max_promotions: 0,
};
let plan = json!({
"acceptance": [{"kind": "check", "desc": "all", "run": "test -f c1.txt && test -f c2.txt && test -f c3.txt"}],
"chunks": [
{"id": "c1", "title": "a", "tier": "code", "brief": "make c1",
"files_touched": ["c1.txt"], "checks": [{"desc": "a", "run": "true"}]},
{"id": "c2", "title": "b", "tier": "code", "brief": "make c2", "deps": ["c1"],
"files_touched": ["c2.txt"], "checks": [{"desc": "b", "run": "true"}]},
{"id": "c3", "title": "c", "tier": "code", "brief": "make c3",
"files_touched": ["c3.txt"], "checks": [{"desc": "c", "run": "true"}]},
],
});
let verify = ScriptedVerify::sequence(vec![
providers::VerifyJudgment {
passed: false,
summary: "fix c3".to_string(),
findings: vec!["c3 wrong".to_string()],
disposition: providers::VerifyDisposition::FixChunks {
chunk_ids: vec!["c3".to_string()],
},
},
providers::VerifyJudgment {
passed: true,
summary: "ok".to_string(),
findings: vec![],
disposition: providers::VerifyDisposition::Fix,
},
]);
let report = run_pipeline(&cfg, &ScriptedSpec::new(plan), &DisjointChunks, &verify)
.expect("pipeline runs");
assert_eq!(report.status, "merged", "{report:#?}");
let c1 = report.chunks.iter().find(|c| c.id == "c1").unwrap();
let c2 = report.chunks.iter().find(|c| c.id == "c2").unwrap();
let c3 = report.chunks.iter().find(|c| c.id == "c3").unwrap();
assert!(c1.replayed, "c1 was kept through the rollback → replayed");
assert!(c2.replayed, "c2 was kept through the rollback → replayed");
assert!(!c3.replayed, "c3 was re-coded, not replayed");
assert!(c2.commit.is_some() && c2.merge_commit.is_some());
assert_ne!(
c2.commit, c2.merge_commit,
"authored commit must be preserved distinct from the replayed on-branch commit"
);
assert!(
Command::new("git")
.arg("-C")
.arg(repo.path())
.args(["cat-file", "-e", c2.commit.as_deref().unwrap()])
.status()
.unwrap()
.success(),
"authored commit oid must remain a valid object"
);
}
fn prov_refs(repo: &Path) -> Vec<String> {
git_out(
repo,
&[
"for-each-ref",
"--format=%(refname)",
"refs/pipeline/prov/demo/",
],
)
.lines()
.filter(|l| !l.is_empty())
.map(str::to_string)
.collect()
}
#[test]
fn rollback_pins_durable_provenance_refs_that_survive_gc() {
let repo = init_repo();
let workdir = TempDir::new().unwrap();
let mut cfg = config(repo.path(), workdir.path(), &["c1.txt", "c2.txt", "c3.txt"]);
cfg.keep = true;
cfg.fix_loop = FixLoopConfig {
max_recode_per_chunk: 0,
max_fix_iterations: 2,
max_respec: 0,
max_promotions: 0,
};
let plan = json!({
"acceptance": [{"kind": "check", "desc": "all", "run": "test -f c1.txt && test -f c2.txt && test -f c3.txt"}],
"chunks": [
{"id": "c1", "title": "a", "tier": "code", "brief": "make c1",
"files_touched": ["c1.txt"], "checks": [{"desc": "a", "run": "true"}]},
{"id": "c2", "title": "b", "tier": "code", "brief": "make c2", "deps": ["c1"],
"files_touched": ["c2.txt"], "checks": [{"desc": "b", "run": "true"}]},
{"id": "c3", "title": "c", "tier": "code", "brief": "make c3",
"files_touched": ["c3.txt"], "checks": [{"desc": "c", "run": "true"}]},
],
});
let verify = ScriptedVerify::sequence(vec![
providers::VerifyJudgment {
passed: false,
summary: "fix c3".to_string(),
findings: vec!["c3 wrong".to_string()],
disposition: providers::VerifyDisposition::FixChunks {
chunk_ids: vec!["c3".to_string()],
},
},
providers::VerifyJudgment {
passed: true,
summary: "ok".to_string(),
findings: vec![],
disposition: providers::VerifyDisposition::Fix,
},
]);
let report = run_pipeline(&cfg, &ScriptedSpec::new(plan), &DisjointChunks, &verify)
.expect("pipeline runs");
assert_eq!(report.status, "merged", "{report:#?}");
let c1 = report.chunks.iter().find(|c| c.id == "c1").unwrap();
let c2 = report.chunks.iter().find(|c| c.id == "c2").unwrap();
let c1_authored = c1.commit.clone().expect("c1 has an authored commit");
let c2_authored = c2.commit.clone().expect("c2 has an authored commit");
assert_eq!(
prov_refs(repo.path()),
vec![
"refs/pipeline/prov/demo/c1".to_string(),
"refs/pipeline/prov/demo/c2".to_string(),
],
"both kept chunks pinned under refs/pipeline/prov/demo/"
);
assert_eq!(
git_out(repo.path(), &["rev-parse", "refs/pipeline/prov/demo/c1"]),
c1_authored,
"c1's prov ref points at its authored commit"
);
assert_eq!(
git_out(repo.path(), &["rev-parse", "refs/pipeline/prov/demo/c2"]),
c2_authored,
"c2's prov ref points at its authored commit"
);
assert_ne!(c1.commit, c1.merge_commit);
assert_ne!(c2.commit, c2.merge_commit);
git(
repo.path(),
&[
"reflog",
"expire",
"--expire=now",
"--expire-unreachable=now",
"--all",
],
);
git(repo.path(), &["gc", "--prune=now"]);
for oid in [&c1_authored, &c2_authored] {
assert!(
Command::new("git")
.arg("-C")
.arg(repo.path())
.args(["cat-file", "-e", oid])
.status()
.unwrap()
.success(),
"authored commit {oid} must survive gc --prune=now via its provenance ref"
);
}
assert_eq!(prov_refs(repo.path()).len(), 2, "prov refs survive the gc");
assert_eq!(
git_out(repo.path(), &["rev-parse", "refs/pipeline/prov/demo/c1"]),
c1_authored
);
}
#[test]
fn teardown_prunes_a_merged_runs_provenance_refs() {
let repo = init_repo();
let workdir = TempDir::new().unwrap();
let mut cfg = config(repo.path(), workdir.path(), &["c1.txt", "c2.txt", "c3.txt"]);
cfg.fix_loop = FixLoopConfig {
max_recode_per_chunk: 0,
max_fix_iterations: 2,
max_respec: 0,
max_promotions: 0,
};
let plan = json!({
"acceptance": [{"kind": "check", "desc": "all", "run": "test -f c1.txt && test -f c2.txt && test -f c3.txt"}],
"chunks": [
{"id": "c1", "title": "a", "tier": "code", "brief": "make c1",
"files_touched": ["c1.txt"], "checks": [{"desc": "a", "run": "true"}]},
{"id": "c2", "title": "b", "tier": "code", "brief": "make c2", "deps": ["c1"],
"files_touched": ["c2.txt"], "checks": [{"desc": "b", "run": "true"}]},
{"id": "c3", "title": "c", "tier": "code", "brief": "make c3",
"files_touched": ["c3.txt"], "checks": [{"desc": "c", "run": "true"}]},
],
});
let verify = ScriptedVerify::sequence(vec![
providers::VerifyJudgment {
passed: false,
summary: "fix c3".to_string(),
findings: vec!["c3 wrong".to_string()],
disposition: providers::VerifyDisposition::FixChunks {
chunk_ids: vec!["c3".to_string()],
},
},
providers::VerifyJudgment {
passed: true,
summary: "ok".to_string(),
findings: vec![],
disposition: providers::VerifyDisposition::Fix,
},
]);
let report = run_pipeline(&cfg, &ScriptedSpec::new(plan), &DisjointChunks, &verify)
.expect("pipeline runs");
assert_eq!(report.status, "merged", "{report:#?}");
assert!(
prov_refs(repo.path()).is_empty(),
"a merged run's provenance refs are pruned on teardown: {:?}",
prov_refs(repo.path())
);
}
#[test]
fn teardown_keeps_a_preserved_runs_provenance_refs() {
let repo = init_repo();
let workdir = TempDir::new().unwrap();
let mut cfg = config(repo.path(), workdir.path(), &["shared.txt"]);
cfg.fix_loop = FixLoopConfig {
max_recode_per_chunk: 0,
max_fix_iterations: 2,
max_respec: 0,
max_promotions: 0,
};
let plan = json!({
"acceptance": [{"kind": "check", "desc": "exists", "run": "test -f shared.txt"}],
"chunks": [
{"id": "c1", "title": "a", "tier": "code", "brief": "make base",
"files_touched": ["shared.txt"], "checks": [{"desc": "a", "run": "true"}]},
{"id": "c2", "title": "b", "tier": "code", "brief": "append",
"files_touched": ["shared.txt"], "checks": [{"desc": "b", "run": "true"}]},
],
});
let verify = ScriptedVerify::new(providers::VerifyJudgment {
passed: false,
summary: "fix c1".to_string(),
findings: vec!["c1 wrong".to_string()],
disposition: providers::VerifyDisposition::FixChunks {
chunk_ids: vec!["c1".to_string()],
},
});
let report = run_pipeline(&cfg, &ScriptedSpec::new(plan), &SharedFileChunks, &verify)
.expect("terminal report, not a crash");
assert_eq!(report.status, "rollback_conflict", "{report:#?}");
assert!(!report.merged);
assert!(
super::git::branch_exists(repo.path(), "feat/demo"),
"the preserved branch is retained"
);
assert_eq!(
prov_refs(repo.path()),
vec!["refs/pipeline/prov/demo/c2".to_string()],
"a preserved run keeps its provenance refs"
);
}
#[test]
fn prov_ref_tracks_the_original_authored_commit_across_two_rollbacks() {
let repo = init_repo();
let workdir = TempDir::new().unwrap();
let mut cfg = config(repo.path(), workdir.path(), &["c0.txt", "c1.txt", "c2.txt"]);
cfg.keep = true;
cfg.fix_loop = FixLoopConfig {
max_recode_per_chunk: 0,
max_fix_iterations: 3,
max_respec: 0,
max_promotions: 0,
};
let plan = json!({
"acceptance": [{"kind": "check", "desc": "all", "run": "test -f c0.txt && test -f c1.txt && test -f c2.txt"}],
"chunks": [
{"id": "c0", "title": "a", "tier": "code", "brief": "make c0",
"files_touched": ["c0.txt"], "checks": [{"desc": "a", "run": "true"}]},
{"id": "c1", "title": "b", "tier": "code", "brief": "make c1", "deps": ["c0"],
"files_touched": ["c1.txt"], "checks": [{"desc": "b", "run": "true"}]},
{"id": "c2", "title": "c", "tier": "code", "brief": "make c2",
"files_touched": ["c2.txt"], "checks": [{"desc": "c", "run": "true"}]},
],
});
let verify = ScriptedVerify::sequence(vec![
providers::VerifyJudgment {
passed: false,
summary: "fix c2".to_string(),
findings: vec!["c2 wrong".to_string()],
disposition: providers::VerifyDisposition::FixChunks {
chunk_ids: vec!["c2".to_string()],
},
},
providers::VerifyJudgment {
passed: false,
summary: "fix c2 again".to_string(),
findings: vec!["c2 still wrong".to_string()],
disposition: providers::VerifyDisposition::FixChunks {
chunk_ids: vec!["c2".to_string()],
},
},
providers::VerifyJudgment {
passed: true,
summary: "ok".to_string(),
findings: vec![],
disposition: providers::VerifyDisposition::Fix,
},
]);
let report = run_pipeline(&cfg, &ScriptedSpec::new(plan), &DisjointChunks, &verify)
.expect("pipeline runs");
assert_eq!(report.status, "merged", "{report:#?}");
let c1 = report.chunks.iter().find(|c| c.id == "c1").unwrap();
let c1_authored = c1.commit.clone().expect("c1 has an authored commit");
assert!(c1.replayed, "c1 was kept through the rollbacks → replayed");
assert_ne!(c1.commit, c1.merge_commit);
assert_eq!(
git_out(repo.path(), &["rev-parse", "refs/pipeline/prov/demo/c1"]),
c1_authored,
"the prov ref pins the original authored commit across both rollbacks"
);
git(
repo.path(),
&[
"reflog",
"expire",
"--expire=now",
"--expire-unreachable=now",
"--all",
],
);
git(repo.path(), &["gc", "--prune=now"]);
assert!(
Command::new("git")
.arg("-C")
.arg(repo.path())
.args(["cat-file", "-e", &c1_authored])
.status()
.unwrap()
.success(),
"the original authored commit {c1_authored} must survive gc via its prov ref"
);
}
#[test]
fn prov_ref_pins_a_legitimately_dotted_chunk_id_verbatim() {
let repo = init_repo();
let workdir = TempDir::new().unwrap();
let mut cfg = config(repo.path(), workdir.path(), &["a.b.txt", "t3.txt"]);
cfg.keep = true;
cfg.fix_loop = FixLoopConfig {
max_recode_per_chunk: 0,
max_fix_iterations: 2,
max_respec: 0,
max_promotions: 0,
};
let plan = json!({
"acceptance": [{"kind": "check", "desc": "all", "run": "test -f a.b.txt && test -f t3.txt"}],
"chunks": [
{"id": "a.b", "title": "a", "tier": "code", "brief": "make a.b",
"files_touched": ["a.b.txt"], "checks": [{"desc": "a", "run": "true"}]},
{"id": "t3", "title": "c", "tier": "code", "brief": "make t3",
"files_touched": ["t3.txt"], "checks": [{"desc": "c", "run": "true"}]},
],
});
let verify = ScriptedVerify::sequence(vec![
providers::VerifyJudgment {
passed: false,
summary: "fix t3".to_string(),
findings: vec!["t3 wrong".to_string()],
disposition: providers::VerifyDisposition::FixChunks {
chunk_ids: vec!["t3".to_string()],
},
},
providers::VerifyJudgment {
passed: true,
summary: "ok".to_string(),
findings: vec![],
disposition: providers::VerifyDisposition::Fix,
},
]);
let report = run_pipeline(&cfg, &ScriptedSpec::new(plan), &DisjointChunks, &verify)
.expect("pipeline runs");
assert_eq!(report.status, "merged", "{report:#?}");
let kept = report.chunks.iter().find(|c| c.id == "a.b").unwrap();
let authored = kept.commit.clone().expect("a.b has an authored commit");
assert_eq!(
prov_refs(repo.path()),
vec!["refs/pipeline/prov/demo/a.b".to_string()],
"a legitimately dotted chunk id pins a verbatim ref name"
);
assert_eq!(
git_out(repo.path(), &["rev-parse", "refs/pipeline/prov/demo/a.b"]),
authored,
"the verbatim ref points at the dotted chunk's authored commit"
);
}
#[test]
fn nonlinear_chunk_history_is_rejected_at_gate() {
let repo = init_repo();
let workdir = TempDir::new().unwrap();
let cfg = config(repo.path(), workdir.path(), &["feature.txt"]);
let spec = ScriptedSpec::new(one_chunk_plan(&["feature.txt"], "true", "true"));
struct MergingHarness;
impl CodeHarness for MergingHarness {
fn capabilities(&self) -> HarnessCapabilities {
HarnessCapabilities {
can_author_tests: true,
reports_usage: false,
honors_file_scope: false,
runs_checks: false,
}
}
fn run_chunk(
&self,
req: &ChunkRequest,
_c: &CancelToken,
) -> Result<ChunkResult, HarnessError> {
let wt = &req.worktree_path;
let base = git_out(wt, &["rev-parse", "HEAD"]);
std::fs::write(wt.join("feature.txt"), "main line\n").unwrap();
git(wt, &["add", "-A"]);
git(wt, &["commit", "-qm", "mainline"]);
git(wt, &["checkout", "-q", "-b", "side", &base]);
std::fs::write(wt.join("other.txt"), "side\n").unwrap();
git(wt, &["add", "-A"]);
git(wt, &["commit", "-qm", "side"]);
git(wt, &["checkout", "-q", "-"]);
git(
wt,
&["merge", "--no-ff", "--no-edit", "-m", "merge side", "side"],
);
Ok(ChunkResult::committed(
git_out(wt, &["rev-parse", "HEAD"]),
vec![PathBuf::from("feature.txt"), PathBuf::from("other.txt")],
))
}
}
let report = run_pipeline(&cfg, &spec, &MergingHarness, &ScriptedVerify::passing())
.expect("pipeline runs");
assert_eq!(report.status, "chunk_failed", "{report:#?}");
assert!(!report.merged);
let reason = report.chunks[0].reason.as_deref().unwrap_or("");
assert!(
reason.contains("merge commit"),
"block reason should name the non-linear merge-commit history: {reason:?}"
);
assert!(!main_has(&repo, "feature.txt"));
}
#[test]
fn verify_fix_rollback_carries_reverted_chunks_prior_diff() {
let repo = init_repo();
let workdir = TempDir::new().unwrap();
let mut cfg = config(repo.path(), workdir.path(), &["feature.txt"]);
cfg.fix_loop = FixLoopConfig {
max_recode_per_chunk: 0,
max_fix_iterations: 2,
max_respec: 0,
max_promotions: 0,
};
let spec = ScriptedSpec::new(one_chunk_plan(
&["feature.txt"],
"true",
"test -f feature.txt",
));
let verify = ScriptedVerify::sequence(vec![
providers::VerifyJudgment {
passed: false,
summary: "fix c1".to_string(),
findings: vec!["needs work".to_string()],
disposition: providers::VerifyDisposition::FixChunks {
chunk_ids: vec!["c1".to_string()],
},
},
providers::VerifyJudgment {
passed: true,
summary: "ok".to_string(),
findings: vec![],
disposition: providers::VerifyDisposition::Fix,
},
]);
struct BriefRecorder {
briefs: std::sync::Mutex<Vec<String>>,
n: AtomicU32,
}
impl CodeHarness for BriefRecorder {
fn capabilities(&self) -> HarnessCapabilities {
HarnessCapabilities {
can_author_tests: true,
reports_usage: false,
honors_file_scope: false,
runs_checks: false,
}
}
fn run_chunk(
&self,
req: &ChunkRequest,
_c: &CancelToken,
) -> Result<ChunkResult, HarnessError> {
self.briefs.lock().unwrap().push(req.brief.clone());
let n = self.n.fetch_add(1, Ordering::SeqCst);
let wt = &req.worktree_path;
let marker = if n == 0 {
"REVERTED_ATTEMPT_MARKER"
} else {
"RECODED_MARKER"
};
std::fs::write(wt.join("feature.txt"), format!("{marker}\n")).unwrap();
git(wt, &["add", "-A"]);
git(wt, &["commit", "-qm", "edit"]);
Ok(ChunkResult::committed(
git_out(wt, &["rev-parse", "HEAD"]),
vec![PathBuf::from("feature.txt")],
))
}
}
let harness = BriefRecorder {
briefs: std::sync::Mutex::new(Vec::new()),
n: AtomicU32::new(0),
};
let report = run_pipeline(&cfg, &spec, &harness, &verify).expect("pipeline runs");
assert_eq!(report.status, "merged", "{report:#?}");
let briefs = harness.briefs.into_inner().unwrap();
assert_eq!(
briefs.len(),
2,
"one initial attempt + one post-rollback re-code"
);
assert!(
!briefs[0].contains("previous attempt's diff"),
"initial brief must not carry a prior diff: {}",
briefs[0]
);
assert!(
briefs[1].contains("previous attempt's diff"),
"post-rollback re-brief missing the carried prior diff: {}",
briefs[1]
);
assert!(
briefs[1].contains("REVERTED_ATTEMPT_MARKER"),
"the carried diff must contain the reverted attempt's content: {}",
briefs[1]
);
}
fn main_has(repo: &TempDir, path: &str) -> bool {
Command::new("git")
.arg("-C")
.arg(repo.path())
.args(["cat-file", "-e", &format!("main:{path}")])
.status()
.unwrap()
.success()
}
struct TwoTier<'a> {
base: &'a dyn CodeHarness,
promoted: &'a dyn CodeHarness,
promoted_ran: &'a std::sync::atomic::AtomicBool,
}
impl TierHarness for TwoTier<'_> {
fn harness(&self, tier: Tier) -> &dyn CodeHarness {
if tier == Tier::Code {
self.base
} else {
self.promoted_ran.store(true, Ordering::SeqCst);
self.promoted
}
}
}
struct SpyDecider {
seen: std::cell::RefCell<Vec<String>>,
}
impl SpyDecider {
fn new() -> Self {
Self {
seen: std::cell::RefCell::new(Vec::new()),
}
}
}
impl Decider for SpyDecider {
fn decide_consequential(
&self,
_ctx: &DecisionContext,
proposed: &crate::pipeline::CoordinatorProposal,
) -> DeciderVerdict {
self.seen
.borrow_mut()
.push(proposed.action.name().to_string());
DeciderVerdict {
action: proposed.action.clone(),
reason: proposed.reason.clone(),
input_artifacts: proposed.input_artifacts.clone(),
}
}
fn model(&self) -> String {
"spy-opus".to_string()
}
fn prompt_version(&self) -> String {
"v1".to_string()
}
}
struct EscalatingDecider;
impl Decider for EscalatingDecider {
fn decide_consequential(
&self,
_ctx: &DecisionContext,
proposed: &crate::pipeline::CoordinatorProposal,
) -> DeciderVerdict {
DeciderVerdict {
action: Action::Escalate {
reason: "decider withheld the consequential decision".to_string(),
},
reason: "not actually done".to_string(),
input_artifacts: proposed.input_artifacts.clone(),
}
}
fn model(&self) -> String {
"escalating-opus".to_string()
}
fn prompt_version(&self) -> String {
"v1".to_string()
}
}
struct FullLadder<'a>(&'a dyn CodeHarness);
impl TierHarness for FullLadder<'_> {
fn harness(&self, _tier: Tier) -> &dyn CodeHarness {
self.0
}
}
#[test]
fn repeat_fail_promotes_chunk_to_a_higher_tier() {
let repo = init_repo();
let workdir = TempDir::new().unwrap();
let mut cfg = config(repo.path(), workdir.path(), &["feature.txt"]);
cfg.fix_loop = FixLoopConfig {
max_recode_per_chunk: 1,
max_fix_iterations: 0,
max_respec: 0,
max_promotions: 1,
};
let spec = ScriptedSpec::new(one_chunk_plan(
&["feature.txt"],
"true",
"test -f feature.txt",
));
let base = AlwaysStray; let promoted = CommitFake::new(&[("feature.txt", "hi\n")]); let promoted_ran = std::sync::atomic::AtomicBool::new(false);
let harnesses = TwoTier {
base: &base,
promoted: &promoted,
promoted_ran: &promoted_ran,
};
let decider = crate::pipeline::ScriptedDecider::confirming();
let report = run_pipeline_tiered(
&cfg,
&spec,
&harnesses,
&ScriptedVerify::passing(),
&decider,
)
.expect("pipeline runs");
assert_eq!(report.status, "merged", "{report:#?}");
assert_eq!(report.promote_count, 1, "exactly one PROMOTE_TIER happened");
assert!(
promoted_ran.load(Ordering::SeqCst),
"the promoted (higher-tier) harness must have run"
);
assert_eq!(report.chunks[0].tier, "mid");
let promote = report
.decisions
.iter()
.find(|d| d.reason.contains("promote chunk"))
.expect("a promote decision is recorded");
assert_eq!(promote.decision_tier, DecisionTier::Coordinator);
}
#[test]
fn promotion_is_bounded_by_max_promotions_then_the_breaker_trips() {
let repo = init_repo();
let workdir = TempDir::new().unwrap();
let mut cfg = config(repo.path(), workdir.path(), &["feature.txt"]);
cfg.fix_loop = FixLoopConfig {
max_recode_per_chunk: 1,
max_fix_iterations: 0,
max_respec: 0,
max_promotions: 0,
};
let spec = ScriptedSpec::new(one_chunk_plan(
&["feature.txt"],
"true",
"test -f feature.txt",
));
let promoted = CommitFake::new(&[("feature.txt", "hi\n")]);
let promoted_ran = std::sync::atomic::AtomicBool::new(false);
let base = AlwaysStray;
let harnesses = TwoTier {
base: &base,
promoted: &promoted,
promoted_ran: &promoted_ran,
};
let decider = crate::pipeline::ScriptedDecider::confirming();
let report = run_pipeline_tiered(
&cfg,
&spec,
&harnesses,
&ScriptedVerify::passing(),
&decider,
)
.expect("pipeline runs");
assert_eq!(report.status, "circuit_breaker", "{report:#?}");
assert_eq!(report.promote_count, 0, "no promotion with budget 0");
assert!(
!promoted_ran.load(Ordering::SeqCst),
"the higher tier must never run when promotion is disabled"
);
assert!(!report.merged);
}
#[test]
fn routine_decisions_never_reach_the_decider_only_converge_does() {
let repo = init_repo();
let workdir = TempDir::new().unwrap();
let mut cfg = config(repo.path(), workdir.path(), &["feature.txt"]);
cfg.fix_loop = FixLoopConfig {
max_recode_per_chunk: 1,
max_fix_iterations: 0,
max_respec: 0,
max_promotions: 1,
};
let spec = ScriptedSpec::new(one_chunk_plan(
&["feature.txt"],
"true",
"test -f feature.txt",
));
let base = AlwaysStray;
let promoted = CommitFake::new(&[("feature.txt", "hi\n")]);
let promoted_ran = std::sync::atomic::AtomicBool::new(false);
let harnesses = TwoTier {
base: &base,
promoted: &promoted,
promoted_ran: &promoted_ran,
};
let decider = SpyDecider::new();
let report = run_pipeline_tiered(
&cfg,
&spec,
&harnesses,
&ScriptedVerify::passing(),
&decider,
)
.expect("pipeline runs");
assert_eq!(report.status, "merged", "{report:#?}");
assert_eq!(report.recode_count, 1);
assert_eq!(report.promote_count, 1);
let seen = decider.seen.into_inner();
assert_eq!(seen, vec!["declare_converged".to_string()], "{seen:?}");
}
#[test]
fn decider_may_override_converge_with_escalate() {
let repo = init_repo();
let workdir = TempDir::new().unwrap();
let cfg = config(repo.path(), workdir.path(), &["feature.txt"]);
let spec = ScriptedSpec::new(one_chunk_plan(
&["feature.txt"],
"true",
"test -f feature.txt",
));
let code = CommitFake::new(&[("feature.txt", "hi\n")]);
let harnesses = SingleTierHarness(&code);
let report = run_pipeline_tiered(
&cfg,
&spec,
&harnesses,
&ScriptedVerify::passing(),
&EscalatingDecider,
)
.expect("pipeline runs");
assert_eq!(report.status, "escalated", "{report:#?}");
assert!(!report.merged, "an escalated feature must not land");
assert!(!main_has(&repo, "feature.txt"));
let escalate = report
.decisions
.iter()
.find(|d| d.reason.contains("not actually done"))
.expect("the decider's escalate verdict is recorded");
assert_eq!(escalate.decision_tier, DecisionTier::Decider);
assert!(
super::git::branch_exists(repo.path(), &report.integration_branch),
"escalated work must remain on the preserved integration branch"
);
}
#[test]
fn promotion_climbs_the_whole_ladder_then_the_breaker_trips() {
let repo = init_repo();
let workdir = TempDir::new().unwrap();
let mut cfg = config(repo.path(), workdir.path(), &["feature.txt"]);
cfg.fix_loop = FixLoopConfig {
max_recode_per_chunk: 0, max_fix_iterations: 0,
max_respec: 0,
max_promotions: 5, };
let spec = ScriptedSpec::new(one_chunk_plan(
&["feature.txt"],
"true",
"test -f feature.txt",
));
let failing = AlwaysStray;
let harnesses = FullLadder(&failing);
let decider = crate::pipeline::ScriptedDecider::confirming();
let report = run_pipeline_tiered(
&cfg,
&spec,
&harnesses,
&ScriptedVerify::passing(),
&decider,
)
.expect("pipeline runs");
assert_eq!(report.status, "circuit_breaker", "{report:#?}");
assert_eq!(
report.promote_count, 2,
"promoted to the ceiling, no further"
);
assert!(!report.merged);
}
#[test]
fn decider_may_override_respec_with_escalate() {
let repo = init_repo();
let workdir = TempDir::new().unwrap();
let mut cfg = config(repo.path(), workdir.path(), &["feature.txt"]);
cfg.fix_loop = FixLoopConfig {
max_recode_per_chunk: 0,
max_fix_iterations: 2,
max_respec: 1,
max_promotions: 0,
};
let spec = ScriptedSpec::new(one_chunk_plan(
&["feature.txt"],
"true",
"test -f feature.txt",
));
let verify = ScriptedVerify::new(providers::VerifyJudgment {
passed: false,
summary: "the plan cannot meet intent".to_string(),
findings: vec!["wrong approach".to_string()],
disposition: providers::VerifyDisposition::SpecFlaw {
reason: "the plan cannot meet intent".to_string(),
chunk_ids: vec!["c1".to_string()],
},
});
let code = CommitFake::new(&[("feature.txt", "hi\n")]);
let harnesses = SingleTierHarness(&code);
let report = run_pipeline_tiered(&cfg, &spec, &harnesses, &verify, &EscalatingDecider)
.expect("pipeline runs");
assert_eq!(report.status, "escalated", "{report:#?}");
assert!(!report.merged);
assert_eq!(
report.respec_count, 0,
"an escalated re-spec is not counted"
);
assert_eq!(report.plan_rev, 1, "no new plan revision was produced");
assert!(spec.respec_calls().is_empty());
}
#[test]
fn slugify_produces_safe_slugs() {
assert_eq!(
slugify("Add CSV export for users!"),
"add-csv-export-for-users"
);
assert_eq!(slugify(" "), "feature");
assert_eq!(slugify("!!!"), "feature");
assert_eq!(slugify("Fix\nsecond line"), "fix");
assert!(slugify(&"x ".repeat(100)).len() <= 48);
}
#[test]
fn resolve_intent_reads_file_or_literal() {
let dir = TempDir::new().unwrap();
let f = dir.path().join("intent.md");
std::fs::write(&f, "from a file").unwrap();
assert_eq!(resolve_intent(f.to_str().unwrap()).unwrap(), "from a file");
assert_eq!(
resolve_intent(&format!("@{}", f.display())).unwrap(),
"from a file"
);
assert_eq!(
resolve_intent("a literal intent").unwrap(),
"a literal intent"
);
assert!(resolve_intent(" ").is_err());
}
#[test]
fn topo_order_respects_deps() {
let plan = plan::parse_and_validate_plan(&json!({
"schema_version": 3, "plan_rev": 1, "intent_rev": 1,
"feature": {"slug": "f", "source_branch": "main", "integration_branch": "feat/f"},
"baseline": {"ref": "feat/f@fork", "commit_oid": "0123456789abcdef0123456789abcdef01234567", "toolchain": "rustc 1.97.1", "test_passlist_hash": "h", "clippy_warnings_hash": "h", "enumerated_targets_hash": "h"},
"acceptance": [{"kind": "check", "desc": "e2e", "run": "true"}],
"chunks": [
{"id": "c2", "title": "t", "tier": "code", "brief": "b", "deps": ["c1"],
"files_touched": ["b.rs"], "checks": [{"desc": "d", "run": "true"}]},
{"id": "c1", "title": "t", "tier": "code", "brief": "b",
"files_touched": ["a.rs"], "checks": [{"desc": "d", "run": "true"}]},
],
}))
.unwrap();
let order = topo_order(&plan.chunks);
let pos_c1 = order
.iter()
.position(|&i| plan.chunks[i].id == "c1")
.unwrap();
let pos_c2 = order
.iter()
.position(|&i| plan.chunks[i].id == "c2")
.unwrap();
assert!(pos_c1 < pos_c2);
}
#[test]
fn live_end_to_end_smoke() {
if std::env::var("OCTL_PIPELINE_LIVE").as_deref() != Ok("1") {
eprintln!("skipping live pipeline test (set OCTL_PIPELINE_LIVE=1 to run)");
return;
}
let repo = init_repo();
let workdir = TempDir::new().unwrap();
let mut cfg = config(repo.path(), workdir.path(), &["hello.txt"]);
cfg.slug = None;
cfg.intent = "Create a file hello.txt containing the text 'hello world'.".to_string();
let spec = providers::ClaudeSpecProvider;
let verify = providers::ClaudeVerifyProvider;
let code = crate::harness::claude::ClaudeHarness::deepseek("flash");
let report = run_pipeline(&cfg, &spec, &code, &verify).expect("live pipeline runs");
eprintln!("live report: {report:#?}");
assert!(
report.chunk_count >= 1,
"spec must produce at least one chunk"
);
assert!(
[
"merged",
"chunk_floor_blocked",
"chunk_failed",
"chunk_merge_conflict",
"verify_failed",
"floor_blocked",
"escalated",
"merge_conflict",
]
.contains(&report.status.as_str()),
"unexpected terminal status: {}",
report.status
);
if report.status == "merged" {
assert!(report.merged && report.final_commit.is_some());
}
}
struct MeteredFake {
files: Vec<(&'static str, &'static str)>,
usage: Usage,
}
impl CodeHarness for MeteredFake {
fn capabilities(&self) -> HarnessCapabilities {
HarnessCapabilities {
can_author_tests: true,
reports_usage: true,
honors_file_scope: false,
runs_checks: false,
}
}
fn run_chunk(&self, req: &ChunkRequest, _c: &CancelToken) -> Result<ChunkResult, HarnessError> {
let wt = &req.worktree_path;
let mut changed = Vec::new();
for (rel, content) in &self.files {
std::fs::write(wt.join(rel), content).unwrap();
changed.push(PathBuf::from(*rel));
}
git(wt, &["add", "-A"]);
git(wt, &["commit", "-qm", "edit"]);
let mut res = ChunkResult::committed(git_out(wt, &["rev-parse", "HEAD"]), changed);
res.usage = Some(self.usage.clone());
Ok(res)
}
}
fn usage(tokens: u64, cost: f64) -> Usage {
Usage {
input_tokens: None,
output_tokens: None,
total_tokens: Some(tokens),
cost_usd: Some(cost),
}
}
#[test]
fn cost_ceiling_breaker_aborts_regardless_of_convergence() {
let repo = init_repo();
let workdir = TempDir::new().unwrap();
let mut cfg = config(repo.path(), workdir.path(), &["feature.txt"]);
cfg.budget = ResourceBudget {
max_cost_usd: Some(1.0),
..ResourceBudget::UNLIMITED
};
let spec = ScriptedSpec::new(one_chunk_plan(&["feature.txt"], "true", "true"));
let code = MeteredFake {
files: vec![("feature.txt", "hi\n")],
usage: usage(15, 5.0), };
let report = run_pipeline(&cfg, &spec, &code, &ScriptedVerify::passing()).expect("runs");
assert_eq!(report.status, "circuit_breaker", "{report:#?}");
assert!(
report
.circuit_breaker
.as_deref()
.unwrap_or_default()
.contains("cost ceiling"),
"{report:#?}"
);
assert!(
!report.merged,
"the cost breaker aborts before the feature merges"
);
assert!(
report.resources.cost_usd >= 5.0,
"spend was metered from Usage"
);
}
#[test]
fn token_ceiling_breaker_aborts() {
let repo = init_repo();
let workdir = TempDir::new().unwrap();
let mut cfg = config(repo.path(), workdir.path(), &["feature.txt"]);
cfg.budget = ResourceBudget {
max_total_tokens: Some(1_000),
..ResourceBudget::UNLIMITED
};
let spec = ScriptedSpec::new(one_chunk_plan(&["feature.txt"], "true", "true"));
let code = MeteredFake {
files: vec![("feature.txt", "hi\n")],
usage: usage(5_000, 0.0), };
let report = run_pipeline(&cfg, &spec, &code, &ScriptedVerify::passing()).expect("runs");
assert_eq!(report.status, "circuit_breaker", "{report:#?}");
assert!(
report
.circuit_breaker
.as_deref()
.unwrap_or_default()
.contains("token ceiling"),
"{report:#?}"
);
assert!(report.resources.total_tokens >= 5_000);
}
#[test]
fn wall_time_ceiling_breaker_aborts() {
let repo = init_repo();
let workdir = TempDir::new().unwrap();
let mut cfg = config(repo.path(), workdir.path(), &["feature.txt"]);
cfg.budget = ResourceBudget {
max_wall_time: Some(std::time::Duration::from_nanos(1)),
..ResourceBudget::UNLIMITED
};
let spec = ScriptedSpec::new(one_chunk_plan(&["feature.txt"], "true", "true"));
let code = CommitFake::new(&[("feature.txt", "hi\n")]);
let report = run_pipeline(&cfg, &spec, &code, &ScriptedVerify::passing()).expect("runs");
assert_eq!(report.status, "circuit_breaker", "{report:#?}");
assert!(
report
.circuit_breaker
.as_deref()
.unwrap_or_default()
.contains("wall-time"),
"{report:#?}"
);
assert!(!report.merged);
}
#[test]
fn process_count_ceiling_breaker_aborts() {
let repo = init_repo();
let workdir = TempDir::new().unwrap();
let mut cfg = config(repo.path(), workdir.path(), &["feature.txt"]);
cfg.budget = ResourceBudget {
max_processes: Some(1),
..ResourceBudget::UNLIMITED
};
let spec = ScriptedSpec::new(one_chunk_plan(&["feature.txt"], "true", "true"));
let code = CommitFake::new(&[("feature.txt", "hi\n")]);
let report = run_pipeline(&cfg, &spec, &code, &ScriptedVerify::passing()).expect("runs");
assert_eq!(report.status, "circuit_breaker", "{report:#?}");
assert!(
report
.circuit_breaker
.as_deref()
.unwrap_or_default()
.contains("process-count"),
"{report:#?}"
);
assert!(report.resources.processes >= 2, "spec + chunk were counted");
}
#[test]
fn storage_ceiling_breaker_aborts() {
let repo = init_repo();
let workdir = TempDir::new().unwrap();
let mut cfg = config(repo.path(), workdir.path(), &["feature.txt"]);
cfg.budget = ResourceBudget {
max_storage_bytes: Some(1),
..ResourceBudget::UNLIMITED
};
let spec = ScriptedSpec::new(one_chunk_plan(&["feature.txt"], "true", "true"));
let code = CommitFake::new(&[("feature.txt", "hi\n")]);
let report = run_pipeline(&cfg, &spec, &code, &ScriptedVerify::passing()).expect("runs");
assert_eq!(report.status, "circuit_breaker", "{report:#?}");
assert!(
report
.circuit_breaker
.as_deref()
.unwrap_or_default()
.contains("storage ceiling"),
"{report:#?}"
);
assert!(
report.resources.storage_bytes > 1,
"workdir size was measured"
);
}
#[test]
fn repeated_identical_failure_breaker_aborts_before_exhausting_recode_budget() {
let repo = init_repo();
let workdir = TempDir::new().unwrap();
let mut cfg = config(repo.path(), workdir.path(), &["feature.txt"]);
cfg.fix_loop = FixLoopConfig {
max_recode_per_chunk: 5,
max_fix_iterations: 0,
max_respec: 0,
max_promotions: 0,
};
cfg.budget = ResourceBudget {
max_identical_failures: Some(2),
..ResourceBudget::UNLIMITED
};
let spec = ScriptedSpec::new(one_chunk_plan(&["feature.txt"], "true", "true"));
let report = run_pipeline(&cfg, &spec, &AlwaysStray, &ScriptedVerify::passing()).expect("runs");
assert_eq!(report.status, "circuit_breaker", "{report:#?}");
assert!(
report
.circuit_breaker
.as_deref()
.unwrap_or_default()
.contains("repeated-identical-failure"),
"{report:#?}"
);
assert_eq!(report.recode_count, 1, "{report:#?}");
assert!(!report.merged);
}
#[test]
fn unlimited_budget_does_not_disturb_a_clean_merge() {
let repo = init_repo();
let workdir = TempDir::new().unwrap();
let cfg = config(repo.path(), workdir.path(), &["feature.txt"]);
let spec = ScriptedSpec::new(one_chunk_plan(&["feature.txt"], "true", "true"));
let code = MeteredFake {
files: vec![("feature.txt", "hi\n")],
usage: usage(42, 0.01),
};
let report = run_pipeline(&cfg, &spec, &code, &ScriptedVerify::passing()).expect("runs");
assert_eq!(report.status, "merged", "{report:#?}");
assert!(report.circuit_breaker.is_none());
assert_eq!(
report.resources.total_tokens, 42,
"tally surfaced on the report"
);
assert!(
report.resources.processes >= 3,
"spec + chunk + verify counted"
);
}
#[test]
fn resolve_ceilings_map_zero_to_disabled_and_reject_nonfinite() {
assert_eq!(super::resolve_u64_ceiling(Some(0), Some(99)), None);
assert_eq!(super::resolve_u64_ceiling(Some(5), Some(99)), Some(5));
assert_eq!(super::resolve_u64_ceiling(None, Some(99)), Some(99));
assert_eq!(super::resolve_u32_ceiling(Some(0), Some(3)), None);
assert_eq!(super::resolve_u32_ceiling(None, Some(3)), Some(3));
assert_eq!(
super::resolve_f64_ceiling(Some(10.0), Some(1.0)),
Some(10.0)
);
assert_eq!(super::resolve_f64_ceiling(Some(0.0), Some(1.0)), None);
assert_eq!(super::resolve_f64_ceiling(Some(-5.0), Some(1.0)), None);
assert_eq!(super::resolve_f64_ceiling(Some(f64::NAN), Some(1.0)), None);
assert_eq!(
super::resolve_f64_ceiling(Some(f64::INFINITY), Some(1.0)),
None
);
assert_eq!(super::resolve_f64_ceiling(None, Some(1.0)), Some(1.0));
}
#[test]
fn verify_stage_breach_aborts_instead_of_merging() {
let repo = init_repo();
let workdir = TempDir::new().unwrap();
let mut cfg = config(repo.path(), workdir.path(), &["feature.txt"]);
cfg.budget = ResourceBudget {
max_processes: Some(2),
..ResourceBudget::UNLIMITED
};
let spec = ScriptedSpec::new(one_chunk_plan(&["feature.txt"], "true", "true"));
let code = CommitFake::new(&[("feature.txt", "hi\n")]);
let report = run_pipeline(&cfg, &spec, &code, &ScriptedVerify::passing()).expect("runs");
assert_eq!(report.status, "circuit_breaker", "{report:#?}");
assert!(
report
.circuit_breaker
.as_deref()
.unwrap_or_default()
.contains("process-count"),
"{report:#?}"
);
assert!(!report.merged, "verify crossed the ceiling → no merge");
assert!(
report.resources.processes >= 3,
"spec + chunk + verify counted"
);
}
#[test]
fn capture_snapshot_allocates_a_fresh_target_dir_per_call() {
use std::os::unix::fs::PermissionsExt;
let repo = init_repo();
let workdir = TempDir::new().unwrap();
let log = workdir.path().join("seen-target-dirs.log");
let script = workdir.path().join("record.sh");
std::fs::write(
&script,
format!(
"#!/bin/sh\nprintf '%s\\n' \"$CARGO_TARGET_DIR\" >> '{}'\nprintf '{{\"reason\":\"build-finished\",\"success\":true}}\\n'\n",
log.display()
),
)
.unwrap();
let mut perms = std::fs::metadata(&script).unwrap().permissions();
perms.set_mode(0o755);
std::fs::set_permissions(&script, perms).unwrap();
let mut cfg = config(repo.path(), workdir.path(), &["feature.txt"]);
cfg.test_cmd = script.to_string_lossy().into_owned();
cfg.clippy_cmd = script.to_string_lossy().into_owned();
super::capture_snapshot(&cfg, repo.path()).unwrap();
super::capture_snapshot(&cfg, repo.path()).unwrap();
let recorded = std::fs::read_to_string(&log).unwrap();
let dirs: Vec<&str> = recorded.lines().collect();
assert_eq!(dirs.len(), 4, "each capture runs test + clippy: {recorded}");
let unique: std::collections::BTreeSet<&str> = dirs.iter().copied().collect();
assert_eq!(
unique.len(),
4,
"every capture must get its own target dir: {recorded}"
);
assert!(dirs.iter().all(|d| !d.is_empty()));
}
fn plan_with_chunks(chunks: serde_json::Value) -> octl_core::plan::Plan {
let doc = json!({
"schema_version": 3,
"plan_rev": 1,
"intent_rev": 1,
"feature": {"slug": "demo", "source_branch": "main", "integration_branch": "feat/demo"},
"baseline": {
"ref": "feat/demo@fork",
"commit_oid": "0000000000000000000000000000000000000000",
"toolchain": "rustc 1.0.0",
"test_passlist_hash": "h",
"clippy_warnings_hash": "h",
"enumerated_targets_hash": "h"
},
"acceptance": [{"kind": "check", "desc": "d", "run": "true"}],
"chunks": chunks,
});
serde_json::from_value(doc).expect("valid plan document")
}
fn wave_ids<'a>(plan: &'a octl_core::plan::Plan, waves: &[Vec<usize>]) -> Vec<Vec<&'a str>> {
waves
.iter()
.map(|w| w.iter().map(|&i| plan.chunks[i].id.as_str()).collect())
.collect()
}
#[test]
fn ready_waves_groups_independent_and_serialises_dependent() {
let plan = plan_with_chunks(json!([
{"id": "c1", "title": "", "tier": "code", "brief": "", "files_touched": ["a"], "checks": [{"desc": "", "run": "true"}]},
{"id": "c2", "title": "", "tier": "code", "brief": "", "files_touched": ["b"], "checks": [{"desc": "", "run": "true"}]},
{"id": "c3", "title": "", "tier": "code", "brief": "", "deps": ["c1", "c2"], "files_touched": ["c"], "checks": [{"desc": "", "run": "true"}]},
{"id": "c4", "title": "", "tier": "code", "brief": "", "deps": ["c3"], "files_touched": ["d"], "checks": [{"desc": "", "run": "true"}]},
]));
let waves = ready_waves(&plan, &BTreeMap::new());
assert_eq!(
wave_ids(&plan, &waves),
vec![vec!["c1", "c2"], vec!["c3"], vec!["c4"]]
);
}
#[test]
fn ready_waves_treats_already_merged_chunks_as_satisfied_deps() {
let plan = plan_with_chunks(json!([
{"id": "c1", "title": "", "tier": "code", "brief": "", "files_touched": ["a"], "checks": [{"desc": "", "run": "true"}]},
{"id": "c2", "title": "", "tier": "code", "brief": "", "deps": ["c1"], "files_touched": ["b"], "checks": [{"desc": "", "run": "true"}]},
]));
let mut status = BTreeMap::new();
status.insert("c1".to_string(), LiveChunkStatus::Merged);
let waves = ready_waves(&plan, &status);
assert_eq!(wave_ids(&plan, &waves), vec![vec!["c2"]]);
}
struct RecordingHarness {
files: BTreeMap<String, String>,
content: BTreeMap<String, String>,
calls: std::sync::Arc<std::sync::Mutex<Vec<String>>>,
}
impl CodeHarness for RecordingHarness {
fn capabilities(&self) -> HarnessCapabilities {
HarnessCapabilities {
can_author_tests: true,
reports_usage: false,
honors_file_scope: false,
runs_checks: false,
}
}
fn run_chunk(&self, req: &ChunkRequest, _c: &CancelToken) -> Result<ChunkResult, HarnessError> {
self.calls.lock().unwrap().push(req.chunk_id.clone());
let rel = self
.files
.get(&req.chunk_id)
.cloned()
.unwrap_or_else(|| format!("{}.txt", req.chunk_id));
let body = self
.content
.get(&req.chunk_id)
.cloned()
.unwrap_or_else(|| format!("{}\n", req.chunk_id));
std::fs::write(req.worktree_path.join(&rel), body).unwrap();
git(&req.worktree_path, &["add", "-A"]);
git(&req.worktree_path, &["commit", "-qm", "edit"]);
let head = git_out(&req.worktree_path, &["rev-parse", "HEAD"]);
Ok(ChunkResult::committed(head, vec![PathBuf::from(rel)]))
}
}
fn on_main(repo: &Path, path: &str) -> bool {
Command::new("git")
.arg("-C")
.arg(repo)
.args(["cat-file", "-e", &format!("main:{path}")])
.status()
.unwrap()
.success()
}
#[test]
fn independent_chunks_build_concurrently_and_merge_deterministically() {
let repo = init_repo();
let workdir = TempDir::new().unwrap();
let mut cfg = config(repo.path(), workdir.path(), &["a.txt", "b.txt"]);
cfg.intent = "two independent chunks".to_string();
cfg.max_build_concurrency = 2;
let plan = json!({
"acceptance": [{"kind": "check", "desc": "both", "run": "test -f a.txt && test -f b.txt"}],
"chunks": [
{"id": "c1", "title": "a", "tier": "code", "brief": "a", "files_touched": ["a.txt"], "checks": [{"desc": "a", "run": "true"}]},
{"id": "c2", "title": "b", "tier": "code", "brief": "b", "files_touched": ["b.txt"], "checks": [{"desc": "b", "run": "true"}]},
],
});
let harness = RecordingHarness {
files: [("c1", "a.txt"), ("c2", "b.txt")]
.iter()
.map(|(k, v)| (k.to_string(), v.to_string()))
.collect(),
content: BTreeMap::new(),
calls: std::sync::Arc::new(std::sync::Mutex::new(Vec::new())),
};
let report = run_pipeline(
&cfg,
&ScriptedSpec::new(plan),
&harness,
&ScriptedVerify::passing(),
)
.expect("pipeline runs");
assert_eq!(report.status, "merged", "{report:#?}");
assert!(report.chunks.iter().all(|c| c.merged));
let order: Vec<&str> = report.chunks.iter().map(|c| c.id.as_str()).collect();
assert_eq!(order, vec!["c1", "c2"], "merge order must be deterministic");
assert!(on_main(repo.path(), "a.txt") && on_main(repo.path(), "b.txt"));
}
#[test]
fn concurrent_same_file_conflict_triggers_rebase_and_fix() {
let repo = init_repo();
let workdir = TempDir::new().unwrap();
let mut cfg = config(repo.path(), workdir.path(), &["shared.txt"]);
cfg.intent = "same-file concurrent chunks".to_string();
cfg.max_build_concurrency = 2;
let plan = json!({
"acceptance": [{"kind": "check", "desc": "exists", "run": "test -f shared.txt"}],
"chunks": [
{"id": "c1", "title": "a", "tier": "code", "brief": "a", "files_touched": ["shared.txt"], "checks": [{"desc": "a", "run": "true"}]},
{"id": "c2", "title": "b", "tier": "code", "brief": "b", "files_touched": ["shared.txt"], "checks": [{"desc": "b", "run": "true"}]},
],
});
let calls = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
let harness = RecordingHarness {
files: [("c1", "shared.txt"), ("c2", "shared.txt")]
.iter()
.map(|(k, v)| (k.to_string(), v.to_string()))
.collect(),
content: [("c1", "c1\n"), ("c2", "c2\n")]
.iter()
.map(|(k, v)| (k.to_string(), v.to_string()))
.collect(),
calls: calls.clone(),
};
let report = run_pipeline(
&cfg,
&ScriptedSpec::new(plan),
&harness,
&ScriptedVerify::passing(),
)
.expect("pipeline runs");
assert_eq!(report.status, "merged", "{report:#?}");
assert!(report.chunks.iter().all(|c| c.merged));
let recorded = calls.lock().unwrap();
assert_eq!(
recorded.iter().filter(|c| c.as_str() == "c2").count(),
2,
"c2 must rebase-and-fix after the merge conflict: {recorded:?}"
);
assert_eq!(
recorded.iter().filter(|c| c.as_str() == "c1").count(),
1,
"c1 merges cleanly, no rebuild: {recorded:?}"
);
assert!(
report
.decisions
.iter()
.any(|d| d.reason.contains("rebase&fix")),
"a rebase&fix decision must be recorded"
);
let content = git_out(repo.path(), &["show", "main:shared.txt"]);
assert_eq!(content, "c2", "the rebased chunk's content is the tip");
}
#[test]
fn wave_preserves_unmerged_builds_when_a_mid_merge_rebase_and_fix_blocks() {
let repo = init_repo();
let workdir = TempDir::new().unwrap();
let mut cfg = config(repo.path(), workdir.path(), &["shared.txt", "c3.txt"]);
cfg.intent = "invariant-5 mid-merge stop".to_string();
cfg.max_build_concurrency = 3;
let plan = json!({
"acceptance": [{"kind": "check", "desc": "exists", "run": "test -f shared.txt"}],
"chunks": [
{"id": "c1", "title": "a", "tier": "code", "brief": "a", "files_touched": ["shared.txt"], "checks": [{"desc": "a", "run": "true"}]},
{"id": "c2", "title": "b", "tier": "code", "brief": "b", "files_touched": ["shared.txt"], "checks": [{"desc": "b", "run": "true"}]},
{"id": "c3", "title": "c", "tier": "code", "brief": "c", "files_touched": ["c3.txt"], "checks": [{"desc": "c", "run": "true"}]},
],
});
struct FailC2OnRebuild {
calls: std::sync::Arc<std::sync::Mutex<Vec<String>>>,
}
impl CodeHarness for FailC2OnRebuild {
fn capabilities(&self) -> HarnessCapabilities {
HarnessCapabilities {
can_author_tests: true,
reports_usage: false,
honors_file_scope: false,
runs_checks: false,
}
}
fn run_chunk(
&self,
req: &ChunkRequest,
_c: &CancelToken,
) -> Result<ChunkResult, HarnessError> {
let n = {
let mut calls = self.calls.lock().unwrap();
calls.push(req.chunk_id.clone());
calls.iter().filter(|c| c.as_str() == req.chunk_id).count()
};
if req.chunk_id == "c2" && n >= 2 {
return Ok(ChunkResult::failed("rebuild boom"));
}
let rel = if req.chunk_id == "c3" {
"c3.txt"
} else {
"shared.txt"
};
std::fs::write(req.worktree_path.join(rel), format!("{}\n", req.chunk_id)).unwrap();
git(&req.worktree_path, &["add", "-A"]);
git(&req.worktree_path, &["commit", "-qm", "edit"]);
let head = git_out(&req.worktree_path, &["rev-parse", "HEAD"]);
Ok(ChunkResult::committed(head, vec![PathBuf::from(rel)]))
}
}
let calls = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
let report = run_pipeline(
&cfg,
&ScriptedSpec::new(plan),
&FailC2OnRebuild {
calls: calls.clone(),
},
&ScriptedVerify::passing(),
)
.expect("pipeline runs");
assert!(!report.merged, "{report:#?}");
let c3 = report
.chunks
.iter()
.find(|c| c.id == "c3")
.expect("c3 has a report");
assert!(!c3.merged, "c3 was not merged");
assert!(
c3.branch_preserved.is_some(),
"c3's un-merged build must be preserved (invariant 5): {c3:#?}"
);
let branches = git_out(repo.path(), &["branch", "--list", "demo/chunk-c3"]);
assert!(
branches.contains("demo/chunk-c3"),
"c3's branch must survive teardown: {branches:?}"
);
}
#[test]
fn dependent_chunks_serialise_even_at_high_concurrency() {
let repo = init_repo();
let workdir = TempDir::new().unwrap();
let mut cfg = config(repo.path(), workdir.path(), &["a.txt", "b.txt"]);
cfg.intent = "dependent chunks".to_string();
cfg.max_build_concurrency = 8;
let plan = json!({
"acceptance": [{"kind": "check", "desc": "both", "run": "test -f a.txt && test -f b.txt"}],
"chunks": [
{"id": "c1", "title": "a", "tier": "code", "brief": "a", "files_touched": ["a.txt"], "checks": [{"desc": "a", "run": "true"}]},
{"id": "c2", "title": "b", "tier": "code", "brief": "b", "deps": ["c1"], "files_touched": ["b.txt"], "checks": [{"desc": "b", "run": "true"}]},
],
});
let harness = RecordingHarness {
files: [("c1", "a.txt"), ("c2", "b.txt")]
.iter()
.map(|(k, v)| (k.to_string(), v.to_string()))
.collect(),
content: BTreeMap::new(),
calls: std::sync::Arc::new(std::sync::Mutex::new(Vec::new())),
};
let report = run_pipeline(
&cfg,
&ScriptedSpec::new(plan),
&harness,
&ScriptedVerify::passing(),
)
.expect("pipeline runs");
assert_eq!(report.status, "merged", "{report:#?}");
assert!(report.chunks.iter().all(|c| c.merged));
assert!(on_main(repo.path(), "a.txt") && on_main(repo.path(), "b.txt"));
}
#[test]
fn wave_build_exhaustion_re_queues_to_sequential_drain_for_promotion() {
let repo = init_repo();
let workdir = TempDir::new().unwrap();
let mut cfg = config(repo.path(), workdir.path(), &["a.txt", "b.txt"]);
cfg.intent = "wave exhaustion promotes via the sequential drain".to_string();
cfg.max_build_concurrency = 2;
cfg.fix_loop = FixLoopConfig {
max_recode_per_chunk: 1,
max_fix_iterations: 0,
max_respec: 0,
max_promotions: 1,
};
let plan = json!({
"acceptance": [{"kind": "check", "desc": "both", "run": "test -f a.txt && test -f b.txt"}],
"chunks": [
{"id": "c1", "title": "a", "tier": "code", "brief": "a", "files_touched": ["a.txt"], "checks": [{"desc": "a", "run": "true"}]},
{"id": "c2", "title": "b", "tier": "code", "brief": "b", "files_touched": ["b.txt"], "checks": [{"desc": "b", "run": "true"}]},
],
});
struct BaseTier;
impl CodeHarness for BaseTier {
fn capabilities(&self) -> HarnessCapabilities {
HarnessCapabilities {
can_author_tests: true,
reports_usage: false,
honors_file_scope: false,
runs_checks: false,
}
}
fn run_chunk(
&self,
req: &ChunkRequest,
_c: &CancelToken,
) -> Result<ChunkResult, HarnessError> {
let wt = &req.worktree_path;
if req.chunk_id == "c1" {
std::fs::write(wt.join("a.txt"), "a\n").unwrap();
git(wt, &["add", "-A"]);
git(wt, &["commit", "-qm", "edit"]);
return Ok(ChunkResult::committed(
git_out(wt, &["rev-parse", "HEAD"]),
vec![PathBuf::from("a.txt")],
));
}
std::fs::write(wt.join("b.txt"), "b\n").unwrap();
std::fs::write(wt.join("stray.txt"), "leak\n").unwrap();
git(wt, &["add", "-A"]);
git(wt, &["commit", "-qm", "edit"]);
Ok(ChunkResult::committed(
git_out(wt, &["rev-parse", "HEAD"]),
vec![PathBuf::from("b.txt"), PathBuf::from("stray.txt")],
))
}
}
let base = BaseTier;
let promoted = CommitFake::new(&[("b.txt", "b\n")]); let promoted_ran = std::sync::atomic::AtomicBool::new(false);
let harnesses = TwoTier {
base: &base,
promoted: &promoted,
promoted_ran: &promoted_ran,
};
let decider = crate::pipeline::ScriptedDecider::confirming();
let report = run_pipeline_tiered(
&cfg,
&ScriptedSpec::new(plan),
&harnesses,
&ScriptedVerify::passing(),
&decider,
)
.expect("pipeline runs");
assert_eq!(report.status, "merged", "{report:#?}");
assert!(report.chunks.iter().all(|c| c.merged), "{report:#?}");
assert!(
promoted_ran.load(Ordering::SeqCst),
"c2's promoted (higher-tier) harness must have run"
);
assert_eq!(report.promote_count, 1, "exactly one PROMOTE_TIER happened");
let c2 = report.chunks.iter().find(|c| c.id == "c2").unwrap();
assert_eq!(c2.tier, "mid", "c2 merged at the promoted tier: {c2:#?}");
assert!(
on_main(repo.path(), "a.txt") && on_main(repo.path(), "b.txt"),
"{report:#?}"
);
assert!(
report
.decisions
.iter()
.any(|d| d.reason.contains("re-queued to the sequential drain")),
"a re-queue-to-sequential-drain decision must be recorded: {:#?}",
report.decisions
);
let branches = git_out(repo.path(), &["branch", "--list", "demo/chunk-c2*"]);
assert!(
branches.is_empty(),
"no orphaned c2 build branch may survive: {branches:?}"
);
let leftover: Vec<_> = std::fs::read_dir(workdir.path())
.unwrap()
.filter_map(std::result::Result::ok)
.filter(|e| e.file_name().to_string_lossy().starts_with("chunk-c2"))
.collect();
assert!(
leftover.is_empty(),
"no orphaned c2 worktree dir may survive: {leftover:?}"
);
}
#[test]
fn wave_build_worker_panic_preserves_sibling_builds() {
let repo = init_repo();
let workdir = TempDir::new().unwrap();
let mut cfg = config(repo.path(), workdir.path(), &["a.txt", "b.txt"]);
cfg.intent = "wave worker panic preserves siblings".to_string();
cfg.max_build_concurrency = 2;
let plan = json!({
"acceptance": [{"kind": "check", "desc": "both", "run": "test -f a.txt && test -f b.txt"}],
"chunks": [
{"id": "c1", "title": "a", "tier": "code", "brief": "a", "files_touched": ["a.txt"], "checks": [{"desc": "a", "run": "true"}]},
{"id": "c2", "title": "b", "tier": "code", "brief": "b", "files_touched": ["b.txt"], "checks": [{"desc": "b", "run": "true"}]},
],
});
struct PanicOnC2;
impl CodeHarness for PanicOnC2 {
fn capabilities(&self) -> HarnessCapabilities {
HarnessCapabilities {
can_author_tests: true,
reports_usage: false,
honors_file_scope: false,
runs_checks: false,
}
}
fn run_chunk(
&self,
req: &ChunkRequest,
_c: &CancelToken,
) -> Result<ChunkResult, HarnessError> {
assert_ne!(req.chunk_id, "c2", "simulated build-thread panic on c2");
let wt = &req.worktree_path;
std::fs::write(wt.join("a.txt"), "a\n").unwrap();
git(wt, &["add", "-A"]);
git(wt, &["commit", "-qm", "edit"]);
Ok(ChunkResult::committed(
git_out(wt, &["rev-parse", "HEAD"]),
vec![PathBuf::from("a.txt")],
))
}
}
let report = run_pipeline(
&cfg,
&ScriptedSpec::new(plan),
&PanicOnC2,
&ScriptedVerify::passing(),
)
.expect("pipeline returns a terminal report, not a panic");
assert!(!report.merged, "{report:#?}");
let c1 = report
.chunks
.iter()
.find(|c| c.id == "c1")
.expect("c1 has a report");
assert!(!c1.merged, "c1 was not merged");
assert!(
c1.branch_preserved.is_some(),
"c1's un-merged build must be preserved despite the sibling panic: {c1:#?}"
);
let branches = git_out(repo.path(), &["branch", "--list", "demo/chunk-c1"]);
assert!(
branches.contains("demo/chunk-c1"),
"c1's branch must survive teardown: {branches:?}"
);
assert!(
report
.decisions
.iter()
.any(|d| d.reason.contains("build thread panicked")
&& d.reason.contains("simulated build-thread panic on c2")),
"the panic (with its payload message) must be recorded as a decision: {:#?}",
report.decisions
);
}
struct WaveFailInjector {
hard: &'static [&'static str],
panic: &'static [&'static str],
files: BTreeMap<String, String>,
}
impl CodeHarness for WaveFailInjector {
fn capabilities(&self) -> HarnessCapabilities {
HarnessCapabilities {
can_author_tests: true,
reports_usage: false,
honors_file_scope: false,
runs_checks: false,
}
}
fn run_chunk(&self, req: &ChunkRequest, _c: &CancelToken) -> Result<ChunkResult, HarnessError> {
assert!(
!self.panic.contains(&req.chunk_id.as_str()),
"simulated build-thread panic on {}",
req.chunk_id
);
if self.hard.contains(&req.chunk_id.as_str()) {
return Err(HarnessError::ProviderFailure {
message: format!("simulated hard error on {}", req.chunk_id),
});
}
let rel = self
.files
.get(&req.chunk_id)
.unwrap_or_else(|| panic!("no scoped file for {}", req.chunk_id));
let wt = &req.worktree_path;
std::fs::write(wt.join(rel), format!("{}\n", req.chunk_id)).unwrap();
git(wt, &["add", "-A"]);
git(wt, &["commit", "-qm", "edit"]);
Ok(ChunkResult::committed(
git_out(wt, &["rev-parse", "HEAD"]),
vec![PathBuf::from(rel)],
))
}
}
#[test]
fn wave_hard_error_returns_typed_err_and_leaves_committed_siblings_intact() {
let repo = init_repo();
let workdir = TempDir::new().unwrap();
let mut cfg = config(repo.path(), workdir.path(), &["a.txt", "b.txt"]);
cfg.intent = "wave worker hard error".to_string();
cfg.max_build_concurrency = 2;
let plan = json!({
"acceptance": [{"kind": "check", "desc": "both", "run": "test -f a.txt && test -f b.txt"}],
"chunks": [
{"id": "c1", "title": "a", "tier": "code", "brief": "a", "files_touched": ["a.txt"], "checks": [{"desc": "a", "run": "true"}]},
{"id": "c2", "title": "b", "tier": "code", "brief": "b", "files_touched": ["b.txt"], "checks": [{"desc": "b", "run": "true"}]},
],
});
let err = run_pipeline(
&cfg,
&ScriptedSpec::new(plan),
&WaveFailInjector {
hard: &["c2"],
panic: &[],
files: [("c1".to_string(), "a.txt".to_string())]
.into_iter()
.collect(),
},
&ScriptedVerify::passing(),
)
.unwrap_err();
assert!(matches!(err.error, PipelineError::Harness(_)), "{err:?}");
assert!(
err.error.to_string().contains("simulated hard error on c2"),
"the hard error must carry its own message: {}",
err.error
);
let report = err.report.expect("hard wave failure must carry a report");
let c1 = report
.chunks
.iter()
.find(|c| c.id == "c1")
.expect("c1 must appear in the failure report");
assert!(!c1.merged, "c1 never merged");
assert_eq!(
c1.branch_preserved.as_deref(),
Some("demo/chunk-c1"),
"c1's preserved branch must be auditable in the report: {c1:?}"
);
let branches = git_out(repo.path(), &["branch", "--list", "demo/chunk-c1"]);
assert!(
branches.contains("demo/chunk-c1"),
"c1's branch must survive the hard-error teardown: {branches:?}"
);
let blob = git_out(repo.path(), &["cat-file", "-p", "demo/chunk-c1:a.txt"]);
assert_eq!(blob, "c1", "c1's committed work must survive: {blob:?}");
}
#[test]
fn wave_hard_error_dominates_and_surfaces_co_occurring_panic() {
let repo = init_repo();
let workdir = TempDir::new().unwrap();
let mut cfg = config(repo.path(), workdir.path(), &["a.txt", "b.txt", "c.txt"]);
cfg.intent = "wave hard error + panic".to_string();
cfg.max_build_concurrency = 3;
let plan = json!({
"acceptance": [{"kind": "check", "desc": "a", "run": "test -f a.txt"}],
"chunks": [
{"id": "c1", "title": "a", "tier": "code", "brief": "a", "files_touched": ["a.txt"], "checks": [{"desc": "a", "run": "true"}]},
{"id": "c2", "title": "b", "tier": "code", "brief": "b", "files_touched": ["b.txt"], "checks": [{"desc": "b", "run": "true"}]},
{"id": "c3", "title": "c", "tier": "code", "brief": "c", "files_touched": ["c.txt"], "checks": [{"desc": "c", "run": "true"}]},
],
});
let err = run_pipeline(
&cfg,
&ScriptedSpec::new(plan),
&WaveFailInjector {
hard: &["c3"],
panic: &["c2"],
files: [("c1".to_string(), "a.txt".to_string())]
.into_iter()
.collect(),
},
&ScriptedVerify::passing(),
)
.unwrap_err();
assert!(matches!(err.error, PipelineError::Harness(_)), "{err:?}");
let msg = err.error.to_string();
assert!(
msg.contains("simulated hard error on c3"),
"the dominant hard error must be preserved: {msg:?}"
);
assert!(
msg.contains("also panicked on chunk c2")
&& msg.contains("simulated build-thread panic on c2"),
"the co-occurring panic must be surfaced in the returned error, not swallowed: {msg:?}"
);
let report = err.report.expect("hard wave failure must carry a report");
let c1 = report
.chunks
.iter()
.find(|c| c.id == "c1")
.expect("c1 must appear in the failure report");
assert_eq!(
c1.branch_preserved.as_deref(),
Some("demo/chunk-c1"),
"c1's preserved branch must be auditable in the report: {c1:?}"
);
let branches = git_out(repo.path(), &["branch", "--list", "demo/chunk-c1"]);
assert!(
branches.contains("demo/chunk-c1"),
"c1's branch must survive teardown: {branches:?}"
);
let blob = git_out(repo.path(), &["cat-file", "-p", "demo/chunk-c1:a.txt"]);
assert_eq!(blob, "c1", "c1's committed work must survive: {blob:?}");
}
struct CommitThenFail {
hard: &'static [&'static str],
panic: &'static [&'static str],
files: BTreeMap<String, String>,
}
impl CodeHarness for CommitThenFail {
fn capabilities(&self) -> HarnessCapabilities {
HarnessCapabilities {
can_author_tests: true,
reports_usage: false,
honors_file_scope: false,
runs_checks: false,
}
}
fn run_chunk(&self, req: &ChunkRequest, _c: &CancelToken) -> Result<ChunkResult, HarnessError> {
let rel = self
.files
.get(&req.chunk_id)
.unwrap_or_else(|| panic!("no scoped file for {}", req.chunk_id));
let wt = &req.worktree_path;
std::fs::write(wt.join(rel), format!("{}\n", req.chunk_id)).unwrap();
git(wt, &["add", "-A"]);
git(wt, &["commit", "-qm", "edit"]);
let head = git_out(wt, &["rev-parse", "HEAD"]);
assert!(
!self.panic.contains(&req.chunk_id.as_str()),
"simulated post-commit panic on {}",
req.chunk_id
);
if self.hard.contains(&req.chunk_id.as_str()) {
return Err(HarnessError::ProviderFailure {
message: format!("simulated post-commit hard error on {}", req.chunk_id),
});
}
Ok(ChunkResult::committed(head, vec![PathBuf::from(rel)]))
}
}
#[test]
fn wave_worker_that_commits_then_panics_audits_its_own_branch() {
let repo = init_repo();
let workdir = TempDir::new().unwrap();
let mut cfg = config(repo.path(), workdir.path(), &["a.txt", "b.txt"]);
cfg.intent = "wave worker commits then panics".to_string();
cfg.max_build_concurrency = 2;
let plan = json!({
"acceptance": [{"kind": "check", "desc": "both", "run": "test -f a.txt && test -f b.txt"}],
"chunks": [
{"id": "c1", "title": "a", "tier": "code", "brief": "a", "files_touched": ["a.txt"], "checks": [{"desc": "a", "run": "true"}]},
{"id": "c2", "title": "b", "tier": "code", "brief": "b", "files_touched": ["b.txt"], "checks": [{"desc": "b", "run": "true"}]},
],
});
let report = run_pipeline(
&cfg,
&ScriptedSpec::new(plan),
&CommitThenFail {
hard: &[],
panic: &["c2"],
files: [
("c1".to_string(), "a.txt".to_string()),
("c2".to_string(), "b.txt".to_string()),
]
.into_iter()
.collect(),
},
&ScriptedVerify::passing(),
)
.expect("a post-commit panic returns a terminal report, not a panic");
assert!(!report.merged, "the panic stopped the stage: {report:#?}");
let c2 = report
.chunks
.iter()
.find(|c| c.id == "c2")
.expect("the terminal worker's own artifact must be in the report");
assert_eq!(
c2.branch_preserved.as_deref(),
Some("demo/chunk-c2"),
"c2's committed-but-unmerged branch must be named, not orphaned: {c2:?}"
);
assert!(!c2.merged, "c2 never merged");
assert!(
c2.commit.is_some(),
"the audited artifact records its committed oid: {c2:?}"
);
assert_eq!(
c2.floor, None,
"the crashing attempt was never floor-gated, so it is not vouched for: {c2:?}"
);
assert_eq!(c2.floor_passed, None, "{c2:?}");
assert!(
c2.reason
.as_deref()
.is_some_and(|r| r.contains("panicked after committing")),
"the audit reason must record WHY the branch is unaudited: {c2:?}"
);
assert!(
super::git::branch_exists(repo.path(), "demo/chunk-c2"),
"c2's branch must survive teardown"
);
let blob = git_out(repo.path(), &["cat-file", "-p", "demo/chunk-c2:b.txt"]);
assert_eq!(blob, "c2", "c2's committed work must survive: {blob:?}");
}
#[test]
fn wave_worker_that_commits_then_hard_errors_audits_its_own_branch() {
let repo = init_repo();
let workdir = TempDir::new().unwrap();
let mut cfg = config(repo.path(), workdir.path(), &["a.txt", "b.txt"]);
cfg.intent = "wave worker commits then hard-errors".to_string();
cfg.max_build_concurrency = 2;
let plan = json!({
"acceptance": [{"kind": "check", "desc": "both", "run": "test -f a.txt && test -f b.txt"}],
"chunks": [
{"id": "c1", "title": "a", "tier": "code", "brief": "a", "files_touched": ["a.txt"], "checks": [{"desc": "a", "run": "true"}]},
{"id": "c2", "title": "b", "tier": "code", "brief": "b", "files_touched": ["b.txt"], "checks": [{"desc": "b", "run": "true"}]},
],
});
let err = run_pipeline(
&cfg,
&ScriptedSpec::new(plan),
&CommitThenFail {
hard: &["c2"],
panic: &[],
files: [
("c1".to_string(), "a.txt".to_string()),
("c2".to_string(), "b.txt".to_string()),
]
.into_iter()
.collect(),
},
&ScriptedVerify::passing(),
)
.unwrap_err();
assert!(matches!(err.error, PipelineError::Harness(_)), "{err:?}");
assert!(
err.error
.to_string()
.contains("simulated post-commit hard error on c2"),
"the hard error carries its own message: {}",
err.error
);
let report = err.report.expect("hard wave failure must carry a report");
let c2 = report
.chunks
.iter()
.find(|c| c.id == "c2")
.expect("the terminal worker's own artifact must be in the failure report");
assert_eq!(
c2.branch_preserved.as_deref(),
Some("demo/chunk-c2"),
"c2's committed-but-unmerged branch must be named, not orphaned: {c2:?}"
);
assert!(c2.commit.is_some(), "{c2:?}");
assert_eq!(
c2.floor, None,
"the crashing attempt was never floor-gated: {c2:?}"
);
assert!(
c2.reason
.as_deref()
.is_some_and(|r| r.contains("hard-errored after committing")),
"the audit reason must record WHY the branch is unaudited: {c2:?}"
);
assert!(super::git::branch_exists(repo.path(), "demo/chunk-c2"));
let blob = git_out(repo.path(), &["cat-file", "-p", "demo/chunk-c2:b.txt"]);
assert_eq!(blob, "c2", "c2's committed work must survive: {blob:?}");
}
#[test]
fn wave_mixed_hard_error_and_panic_audit_each_with_its_own_reason() {
let repo = init_repo();
let workdir = TempDir::new().unwrap();
let mut cfg = config(repo.path(), workdir.path(), &["a.txt", "b.txt"]);
cfg.intent = "wave mixed hard-error + panic, both commit first".to_string();
cfg.max_build_concurrency = 2;
let plan = json!({
"acceptance": [{"kind": "check", "desc": "both", "run": "test -f a.txt && test -f b.txt"}],
"chunks": [
{"id": "c1", "title": "a", "tier": "code", "brief": "a", "files_touched": ["a.txt"], "checks": [{"desc": "a", "run": "true"}]},
{"id": "c2", "title": "b", "tier": "code", "brief": "b", "files_touched": ["b.txt"], "checks": [{"desc": "b", "run": "true"}]},
],
});
let err = run_pipeline(
&cfg,
&ScriptedSpec::new(plan),
&CommitThenFail {
hard: &["c1"],
panic: &["c2"],
files: [
("c1".to_string(), "a.txt".to_string()),
("c2".to_string(), "b.txt".to_string()),
]
.into_iter()
.collect(),
},
&ScriptedVerify::passing(),
)
.unwrap_err();
assert!(matches!(err.error, PipelineError::Harness(_)), "{err:?}");
let report = err.report.expect("mixed wave failure must carry a report");
let c1 = report
.chunks
.iter()
.find(|c| c.id == "c1")
.expect("c1's own artifact must be audited");
assert_eq!(c1.branch_preserved.as_deref(), Some("demo/chunk-c1"));
assert!(
c1.reason
.as_deref()
.is_some_and(|r| r.contains("hard-errored after committing")),
"c1 must be audited with the hard-error reason: {c1:?}"
);
let c2 = report
.chunks
.iter()
.find(|c| c.id == "c2")
.expect("c2's own artifact must be audited");
assert_eq!(c2.branch_preserved.as_deref(), Some("demo/chunk-c2"));
assert!(
c2.reason
.as_deref()
.is_some_and(|r| r.contains("panicked after committing")),
"c2 must keep its OWN panic reason, not the sibling's hard-error reason: {c2:?}"
);
assert!(super::git::branch_exists(repo.path(), "demo/chunk-c1"));
assert!(super::git::branch_exists(repo.path(), "demo/chunk-c2"));
}
struct StrayThenRecodePanic {
files: BTreeMap<String, String>,
}
impl CodeHarness for StrayThenRecodePanic {
fn capabilities(&self) -> HarnessCapabilities {
HarnessCapabilities {
can_author_tests: true,
reports_usage: false,
honors_file_scope: false,
runs_checks: false,
}
}
fn run_chunk(&self, req: &ChunkRequest, _c: &CancelToken) -> Result<ChunkResult, HarnessError> {
let rel = self
.files
.get(&req.chunk_id)
.unwrap_or_else(|| panic!("no scoped file for {}", req.chunk_id));
let wt = &req.worktree_path;
std::fs::write(wt.join(rel), format!("{}\n", req.chunk_id)).unwrap();
let mut changed = vec![PathBuf::from(rel)];
if req.chunk_id == "c2" && req.attempt_id == "a1" {
std::fs::write(wt.join("stray.txt"), "leak\n").unwrap();
changed.push(PathBuf::from("stray.txt"));
}
git(wt, &["add", "-A"]);
git(wt, &["commit", "-qm", "edit"]);
let head = git_out(wt, &["rev-parse", "HEAD"]);
assert!(
!(req.chunk_id == "c2" && req.attempt_id == "a2"),
"simulated post-commit panic on c2 recode attempt"
);
Ok(ChunkResult::committed(head, changed))
}
}
#[test]
fn wave_worker_that_commits_then_panics_on_a_recode_attempt_audits_the_latest_branch() {
let repo = init_repo();
let workdir = TempDir::new().unwrap();
let mut cfg = config(repo.path(), workdir.path(), &["a.txt", "b.txt"]);
cfg.intent = "wave recode then panic".to_string();
cfg.max_build_concurrency = 2;
cfg.fix_loop = FixLoopConfig {
max_recode_per_chunk: 1,
max_fix_iterations: 0,
max_respec: 0,
max_promotions: 0,
};
let plan = json!({
"acceptance": [{"kind": "check", "desc": "both", "run": "test -f a.txt && test -f b.txt"}],
"chunks": [
{"id": "c1", "title": "a", "tier": "code", "brief": "a", "files_touched": ["a.txt"], "checks": [{"desc": "a", "run": "true"}]},
{"id": "c2", "title": "b", "tier": "code", "brief": "b", "files_touched": ["b.txt"], "checks": [{"desc": "b", "run": "true"}]},
],
});
let report = run_pipeline(
&cfg,
&ScriptedSpec::new(plan),
&StrayThenRecodePanic {
files: [
("c1".to_string(), "a.txt".to_string()),
("c2".to_string(), "b.txt".to_string()),
]
.into_iter()
.collect(),
},
&ScriptedVerify::passing(),
)
.expect("a post-commit panic on a re-code attempt returns a terminal report");
assert!(!report.merged, "{report:#?}");
let c2 = report
.chunks
.iter()
.find(|c| c.id == "c2")
.expect("the recode-crashed worker's own artifact must be audited");
assert_eq!(
c2.branch_preserved.as_deref(),
Some("demo/chunk-c2-a2"),
"the audit must name the LATEST (`-a2`) attempt's branch: {c2:?}"
);
assert!(
c2.reason
.as_deref()
.is_some_and(|r| r.contains("panicked after committing")),
"{c2:?}"
);
assert!(
super::git::branch_exists(repo.path(), "demo/chunk-c2-a2"),
"the latest attempt's branch must be preserved"
);
assert!(
!super::git::branch_exists(repo.path(), "demo/chunk-c2"),
"the superseded first attempt must have been torn down, not preserved"
);
let blob = git_out(repo.path(), &["cat-file", "-p", "demo/chunk-c2-a2:b.txt"]);
assert_eq!(
blob, "c2",
"the re-code attempt's committed work must survive: {blob:?}"
);
}