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();
match &err {
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, 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, 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"
);
}
#[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"));
}