use crate::dispatch::{Provenance, dispatch_identity, dispatch_ref, funnel};
use crate::dispatch_config::ForbiddenWrites;
use crate::funnel_machine::{
self, IllegalTransition, PhaseRow, Position, Transition, TransitionKind,
};
use crate::git;
use crate::slice::SelectorIntent;
use crate::verify::{CheckKind, CheckPlan, VerificationConfig, resolve_check};
use crate::worktree::{
CLAUDE_PREFIX, DOCTRINE_PREFIX, DispatchRecord, ForkBinding, ForkExpect, ResolveRefusal,
gather_worktree_delta_paths, require_binding, resolve_agent,
};
use anyhow::{Context, bail};
use serde::Serialize;
use std::io::Write as _;
use std::path::Path;
const EMPTY_DELTA: &str = "empty-delta";
const FORBIDDEN_ZONE: &str = "forbidden-zone";
const NOT_AT_BASE: &str = "not-at-base";
const COMMIT_GATE_RED: &str = "commit-gate-red";
const LATE_RECOMMIT: &str = "late-recommit";
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub(crate) enum WorkerCommitOutput {
Committed {
oid: String,
base: String,
undeclared: Vec<String>,
},
Refused { reason: String, detail: String },
}
fn refused(reason: &str, detail: String) -> WorkerCommitOutput {
WorkerCommitOutput::Refused {
reason: reason.to_owned(),
detail,
}
}
fn is_forbidden_zone(path: &str, forbidden: &ForbiddenWrites) -> bool {
path.starts_with(DOCTRINE_PREFIX)
|| path.starts_with(CLAUDE_PREFIX)
|| forbidden.is_forbidden(path)
}
fn classify_scope(
delta_paths: &[String],
forbidden: &ForbiddenWrites,
selectors: &[String],
) -> Result<Vec<String>, String> {
if let Some(hit) = delta_paths.iter().find(|p| is_forbidden_zone(p, forbidden)) {
return Err(hit.clone());
}
let paths: Vec<&str> = delta_paths.iter().map(String::as_str).collect();
Ok(crate::conformance::undeclared_paths(selectors, &paths))
}
fn head_at_base(head: &str, base: &str) -> bool {
head == base
}
enum GateOutcome {
Green,
Red(String),
}
fn run_commit_gate(dir: &Path, cfg: &VerificationConfig) -> anyhow::Result<GateOutcome> {
let argv = match resolve_check(cfg, CheckKind::Commit) {
CheckPlan::Run(argv) => argv,
CheckPlan::Empty(kind) => {
bail!(
"worker_commit: [verification].{} is empty — cannot run the commit gate",
kind.key()
)
}
CheckPlan::Noop(_) => {
bail!("worker_commit: the commit gate resolved to a no-op — cannot gate the commit")
}
};
let (program, rest) = argv
.split_first()
.ok_or_else(|| anyhow::anyhow!("worker_commit: resolved commit-gate argv is empty"))?;
let had_marker = crate::worktree::marker_present(dir);
if had_marker {
crate::worktree::remove_marker(dir).context("clear worker marker for the commit gate")?;
}
let spawned = std::process::Command::new(program)
.args(rest)
.current_dir(dir)
.env_remove("DOCTRINE_WORKER")
.env("DOCTRINE_DISPATCH_GATE", "1")
.output()
.with_context(|| format!("spawning the worker commit gate: {}", argv.join(" ")));
if had_marker {
crate::worktree::write_marker(dir)
.context("restore worker marker after the commit gate")?;
}
let output = spawned?;
if output.status.success() {
Ok(GateOutcome::Green)
} else {
let mut detail = String::from_utf8_lossy(&output.stdout).into_owned();
detail.push_str(&String::from_utf8_lossy(&output.stderr));
Ok(GateOutcome::Red(detail))
}
}
fn stage_and_commit(dir: &Path, message: &str, paths: &[String]) -> anyhow::Result<()> {
let mut add_args: Vec<&str> = vec!["add", "--"];
add_args.extend(paths.iter().map(String::as_str));
git::git_text(dir, &add_args).context("stage the classified worker delta")?;
let mut commit_args: Vec<&str> = vec!["commit", "-q", "-m", message, "--"];
commit_args.extend(paths.iter().map(String::as_str));
git::git_text(dir, &commit_args).context("create the gated worker commit")?;
Ok(())
}
fn design_target_selectors(coord: &Path, slice: u32) -> anyhow::Result<Vec<String>> {
crate::slice::selectors(coord, slice, Some(SelectorIntent::DesignTarget))
}
struct FunnelCtx {
coord: std::path::PathBuf,
coord_ref: String,
slice: u32,
phase: String,
fork: String,
base_oid: String,
tip: String,
row: Option<PhaseRow>,
}
impl FunnelCtx {
fn open(record: &DispatchRecord, binding: &ForkBinding) -> anyhow::Result<Self> {
let coord_ref = dispatch_ref(binding.slice);
let tip = commit_oid(&record.coord, &coord_ref)
.with_context(|| format!("resolve the coordination ref {coord_ref}"))?;
let funnel_record = funnel::read_funnel_at(&record.coord, &tip, binding.slice)?;
Ok(Self {
coord: record.coord.clone(),
coord_ref,
slice: binding.slice,
phase: binding.phase.clone(),
fork: record.branch.clone(),
base_oid: record.base.clone(),
row: funnel_record.row(&binding.phase).cloned(),
tip,
})
}
fn position(&self) -> Option<Position> {
self.row.as_ref().map(|r| r.position)
}
fn ladder(&self, fork_tip: &str) -> Vec<Transition> {
let record = Transition::RecordWorkerCommit {
fork_tip: fork_tip.to_owned(),
};
match self.position() {
None => vec![
Transition::Spawn {
fork: self.fork.clone(),
base_oid: self.base_oid.clone(),
},
record,
],
Some(_) => vec![record],
}
}
fn preflight(&self) -> Result<(), IllegalTransition> {
let kind = match self.position() {
None => TransitionKind::Spawn,
Some(_) => TransitionKind::RecordWorkerCommit,
};
funnel_machine::preflight(self.position(), kind)
}
fn record_worker_commit(&self, fork_tip: &str) -> anyhow::Result<()> {
let ts = self.ladder(fork_tip);
let at = crate::clock::now_timestamp()?;
let prov = Provenance::Conclude {
who: dispatch_identity(),
};
let mut tip = self.tip.clone();
for _ in 0..LAND_ATTEMPTS {
match funnel::land_funnel_transitions(
&self.coord,
&self.coord_ref,
&tip,
self.slice,
&self.phase,
&ts,
None,
None,
&crate::dispatch::funnel_message(self.slice, &self.phase),
&at,
&prov,
)? {
funnel::FunnelLanding::Landed { .. } | funnel::FunnelLanding::Replayed { .. } => {
return Ok(());
}
funnel::FunnelLanding::Illegal(illegal) => bail!("{illegal}"),
funnel::FunnelLanding::CommitRefused(refusal) => {
if refusal != crate::dispatch::CommitRefusal::LostRefRace {
bail!("record worker-commit: {}", refusal.token());
}
tip = commit_oid(&self.coord, &self.coord_ref)?;
}
}
}
bail!("record worker-commit: lost the ref race {LAND_ATTEMPTS} times running")
}
}
const LAND_ATTEMPTS: u32 = 3;
fn refused_illegal(illegal: &IllegalTransition) -> WorkerCommitOutput {
refused(illegal.reason, illegal.to_string())
}
fn commit_oid(dir: &Path, rev: &str) -> anyhow::Result<String> {
git::git_text(dir, &["rev-parse", &format!("{rev}^{{commit}}")])
.with_context(|| format!("rev-parse {rev} in {}", dir.display()))
}
pub(crate) fn run_worker_commit(
root: &Path,
agent: &str,
message: &str,
) -> anyhow::Result<WorkerCommitOutput> {
match resolve_agent(root, agent, ForkExpect::AtBase) {
Ok(record) => first_commit_leg(root, &record, message),
Err(ResolveRefusal::StaleRecord) => {
match resolve_agent(root, agent, ForkExpect::Advanced) {
Ok(record) => retry_leg(root, &record),
Err(_) => Ok(refused(ResolveRefusal::StaleRecord.token(), String::new())),
}
}
Err(refusal) => Ok(refused(refusal.token(), String::new())),
}
}
struct Belts {
cfg: crate::dtoml::DoctrineToml,
forbidden: ForbiddenWrites,
selectors: Vec<String>,
}
impl Belts {
fn gather(root: &Path, record: &DispatchRecord, slice: u32) -> anyhow::Result<Self> {
let cfg = crate::dtoml::load_doctrine_toml(root)?;
Ok(Self {
forbidden: cfg.dispatch.forbidden_writes(),
selectors: design_target_selectors(&record.coord, slice).unwrap_or_default(),
cfg,
})
}
}
fn first_commit_leg(
root: &Path,
record: &DispatchRecord,
message: &str,
) -> anyhow::Result<WorkerCommitOutput> {
let binding = match require_binding(record) {
Ok(binding) => binding,
Err(refusal) => return Ok(refused(refusal.token(), record.branch.clone())),
};
let ctx = FunnelCtx::open(record, &binding)?;
if let Err(illegal) = ctx.preflight() {
return Ok(refused_illegal(&illegal));
}
let delta = gather_worktree_delta_paths(&record.dir)?;
if delta.is_empty() {
return Ok(refused(EMPTY_DELTA, String::new()));
}
let belts = Belts::gather(root, record, binding.slice)?;
let undeclared = match classify_scope(&delta, &belts.forbidden, &belts.selectors) {
Ok(undeclared) => undeclared,
Err(path) => return Ok(refused(FORBIDDEN_ZONE, path)),
};
let base = commit_oid(&record.dir, &record.base)?;
let head = commit_oid(&record.dir, "HEAD")?;
if !head_at_base(&head, &base) {
return Ok(refused(NOT_AT_BASE, String::new()));
}
match run_commit_gate(&record.dir, &belts.cfg.verification)? {
GateOutcome::Green => {}
GateOutcome::Red(detail) => return Ok(refused(COMMIT_GATE_RED, detail)),
}
stage_and_commit(&record.dir, message, &delta)?;
let oid = commit_oid(&record.dir, "HEAD")?;
assert_one_commit_past_base(&record.dir, &oid, &base)?;
warn_if_unrecorded(&ctx, &oid);
Ok(WorkerCommitOutput::Committed {
oid,
base,
undeclared,
})
}
fn retry_leg(root: &Path, record: &DispatchRecord) -> anyhow::Result<WorkerCommitOutput> {
let binding = match require_binding(record) {
Ok(binding) => binding,
Err(refusal) => return Ok(refused(refusal.token(), record.branch.clone())),
};
let dirt = gather_worktree_delta_paths(&record.dir)?;
if !dirt.is_empty() {
return Ok(refused(LATE_RECOMMIT, dirt.join(", ")));
}
let base = commit_oid(&record.dir, &record.base)?;
let c = commit_oid(&record.dir, "HEAD")?;
let ctx = FunnelCtx::open(record, &binding)?;
let fold = match funnel_machine::fold_transitions(
ctx.row.as_ref(),
&ctx.phase,
&ctx.ladder(&c),
&ctx.tip,
None,
&crate::clock::now_timestamp()?,
) {
Ok(Some(fold)) => fold,
Ok(None) => return Ok(refused(EMPTY_DELTA, String::new())),
Err(illegal) => return Ok(refused_illegal(&illegal)),
};
if fold.replay {
return Ok(WorkerCommitOutput::Committed {
oid: c,
base,
undeclared: Vec::new(),
});
}
let delta = commit_delta_paths(&record.dir, &base, &c)?;
if delta.is_empty() {
return Ok(refused(EMPTY_DELTA, String::new()));
}
let belts = Belts::gather(root, record, binding.slice)?;
let undeclared = match classify_scope(&delta, &belts.forbidden, &belts.selectors) {
Ok(undeclared) => undeclared,
Err(path) => return Ok(refused(FORBIDDEN_ZONE, path)),
};
match run_commit_gate(&record.dir, &belts.cfg.verification)? {
GateOutcome::Green => {}
GateOutcome::Red(detail) => return Ok(refused(COMMIT_GATE_RED, detail)),
}
assert_one_commit_past_base(&record.dir, &c, &base)?;
ctx.record_worker_commit(&c)?;
Ok(WorkerCommitOutput::Committed {
oid: c,
base,
undeclared,
})
}
fn commit_delta_paths(dir: &Path, base: &str, commit: &str) -> anyhow::Result<Vec<String>> {
let out = git::git_text(dir, &["diff", "--name-only", base, commit])
.with_context(|| format!("diff {base}..{commit} in {}", dir.display()))?;
Ok(out
.lines()
.map(str::trim)
.filter(|l| !l.is_empty())
.map(str::to_owned)
.collect())
}
fn assert_one_commit_past_base(dir: &Path, oid: &str, base: &str) -> anyhow::Result<()> {
match git::parents(dir, oid)?.as_slice() {
[parent] if parent == base => Ok(()),
[parent] => bail!("worker_commit parent {parent} != base {base} (C^ != B)"),
other => bail!(
"worker_commit produced a commit with {} parents (expected exactly 1)",
other.len()
),
}
}
fn warn_if_unrecorded(ctx: &FunnelCtx, oid: &str) {
if let Err(cause) = ctx.record_worker_commit(oid) {
drop(writeln!(
std::io::stderr(),
"warning: worker-commit row not landed for {} {} ({cause:#}) — the commit {oid} IS durable; a re-drive or import heals the row forward",
ctx.slice,
ctx.phase
));
}
}
#[cfg(test)]
#[expect(
clippy::unwrap_used,
reason = "tests: fail-fast unwrap on fixture setup is idiomatic"
)]
mod tests {
use super::*;
use crate::dispatch_config::DispatchConfig;
use crate::worktree::{Apply, Refusal, classify_import, provision_dispatch_record};
use std::fs;
use std::path::PathBuf;
use std::process::Command;
fn forbidden_from(lines: &[&str]) -> ForbiddenWrites {
let cfg = DispatchConfig {
worker_forbidden_writes: lines.iter().map(|s| (*s).to_string()).collect(),
..DispatchConfig::default()
};
cfg.forbidden_writes()
}
#[test]
fn classify_scope_hard_refuses_the_doctrine_and_claude_floors() {
let fw = forbidden_from(&[]);
assert_eq!(
classify_scope(&[".doctrine/state/x".to_string()], &fw, &[]),
Err(".doctrine/state/x".to_string())
);
assert_eq!(
classify_scope(&[".claude/agents/w.md".to_string()], &fw, &[]),
Err(".claude/agents/w.md".to_string())
);
}
#[test]
fn classify_scope_hard_refuses_a_config_forbidden_agent_def_or_flake() {
let fw = forbidden_from(&["flake.nix", "install/agents/**"]);
assert_eq!(
classify_scope(&["flake.nix".to_string()], &fw, &[]),
Err("flake.nix".to_string())
);
assert_eq!(
classify_scope(
&["install/agents/claude/dispatch-worker.md".to_string()],
&fw,
&[]
),
Err("install/agents/claude/dispatch-worker.md".to_string())
);
}
#[test]
fn classify_scope_soft_reports_undeclared_without_blocking() {
let fw = forbidden_from(&[]);
let selectors = vec!["src/allowed.rs".to_string()];
let delta = vec!["src/allowed.rs".to_string(), "src/other.rs".to_string()];
assert_eq!(
classify_scope(&delta, &fw, &selectors),
Ok(vec!["src/other.rs".to_string()])
);
}
#[test]
fn classify_scope_forbidden_wins_even_alongside_an_undeclared_path() {
let fw = forbidden_from(&[]);
let selectors = vec!["src/allowed.rs".to_string()];
let delta = vec!["src/other.rs".to_string(), ".doctrine/x".to_string()];
assert_eq!(
classify_scope(&delta, &fw, &selectors),
Err(".doctrine/x".to_string())
);
}
#[test]
fn worker_commit_and_classify_import_agree_on_the_hard_verdict() {
let fw = forbidden_from(&[]);
for hard in [".doctrine/state/x", ".claude/agents/w.md"] {
let delta = vec![hard.to_string()];
assert!(
classify_scope(&delta, &fw, &[]).is_err(),
"worker_commit rejects {hard}"
);
assert!(
classify_import(true, true, true, &delta, &[]).is_err(),
"classify_import rejects {hard}"
);
}
let benign = vec!["src/lib.rs".to_string()];
assert!(classify_scope(&benign, &fw, &[]).is_ok());
assert_eq!(
classify_import(true, true, true, &benign, &[]),
Ok(Apply::Ok)
);
assert_eq!(
classify_import(true, true, true, &[".doctrine/x".to_string()], &[]),
Err(Refusal::DoctrineTouch)
);
}
#[test]
fn head_at_base_is_exact_equality() {
assert!(head_at_base("abc123", "abc123"));
assert!(!head_at_base("abc123", "def456"));
}
fn git_run(dir: &Path, args: &[&str]) -> String {
let out = Command::new("git")
.arg("-C")
.arg(dir)
.args(args)
.output()
.unwrap();
assert!(
out.status.success(),
"git {args:?} failed: {}",
String::from_utf8_lossy(&out.stderr)
);
String::from_utf8_lossy(&out.stdout).trim().to_string()
}
const SLICE: u32 = 199;
const PHASE: &str = "PHASE-01";
fn fixture_binding() -> ForkBinding {
ForkBinding {
slice: SLICE,
phase: PHASE.to_owned(),
}
}
fn worker_fixture(
commit_override: &str,
seed_files: &[(&str, &str)],
) -> (tempfile::TempDir, PathBuf, PathBuf, String, String) {
let tmp = tempfile::tempdir().unwrap();
let primary = fs::canonicalize(tmp.path()).unwrap().join("primary");
fs::create_dir_all(&primary).unwrap();
git_run(&primary, &["init", "-q", "-b", "main"]);
git_run(&primary, &["config", "user.email", "t@t"]);
git_run(&primary, &["config", "user.name", "t"]);
for (rel, body) in seed_files {
let path = primary.join(rel);
fs::create_dir_all(path.parent().unwrap()).unwrap();
fs::write(&path, body).unwrap();
}
fs::write(primary.join("seed"), "base\n").unwrap();
fs::write(primary.join(".gitignore"), ".doctrine/state/\n").unwrap();
git_run(&primary, &["add", "-A"]);
git_run(&primary, &["commit", "-q", "-m", "base"]);
let base = git_run(&primary, &["rev-parse", "HEAD^{commit}"]);
fs::create_dir_all(primary.join(".doctrine")).unwrap();
fs::write(
primary.join(".doctrine/doctrine.toml"),
format!("[verification]\ncommit = {commit_override}\n"),
)
.unwrap();
let coord = fs::canonicalize(tmp.path()).unwrap().join("coord");
git_run(
&primary,
&[
"worktree",
"add",
"-q",
"-b",
&format!("dispatch/{SLICE:03}"),
coord.to_str().unwrap(),
&base,
],
);
let coord = fs::canonicalize(&coord).unwrap();
let agent = "wk1".to_string();
let wt = coord.join(".worktrees").join(&agent);
fs::create_dir_all(coord.join(".worktrees")).unwrap();
git_run(
&primary,
&[
"worktree",
"add",
"-q",
"-b",
&format!("dispatch/{agent}"),
wt.to_str().unwrap(),
&base,
],
);
let wt = fs::canonicalize(&wt).unwrap();
provision_dispatch_record(
&coord,
&agent,
&base,
&wt,
&format!("dispatch/{agent}"),
Some(&fixture_binding()),
)
.unwrap();
land(
&coord,
&Transition::Spawn {
fork: format!("dispatch/{agent}"),
base_oid: base.clone(),
},
);
(tmp, primary, wt, agent, base)
}
fn land(coord: &Path, t: &Transition) {
let coord_ref = dispatch_ref(SLICE);
let tip = git_run(coord, &["rev-parse", &format!("{coord_ref}^{{commit}}")]);
match funnel::land_funnel_transition(
coord,
&coord_ref,
&tip,
SLICE,
PHASE,
t,
None,
"2026-07-26T00:00:00Z",
&Provenance::Conclude {
who: dispatch_identity(),
},
)
.unwrap()
{
funnel::FunnelLanding::Landed { .. } => {}
other => panic!("fixture: expected Landed, got {other:?}"),
}
}
fn coord_of(wt: &Path) -> PathBuf {
wt.parent()
.expect("wt has a .worktrees parent")
.parent()
.expect(".worktrees has a coord parent")
.to_path_buf()
}
fn funnel_row(coord: &Path) -> Option<PhaseRow> {
let coord_ref = dispatch_ref(SLICE);
let tip = git_run(coord, &["rev-parse", &format!("{coord_ref}^{{commit}}")]);
funnel::read_funnel_at(coord, &tip, SLICE)
.unwrap()
.row(PHASE)
.cloned()
}
#[test]
fn worker_commit_unknown_agent_refuses() {
let (_tmp, primary, _wt, _agent, _base) = worker_fixture("[\"true\"]", &[]);
let out = run_worker_commit(&primary, "nosuchagent", "msg").unwrap();
assert_eq!(
out,
refused("unknown-agent", String::new()),
"an unresolvable agent refuses unknown-agent"
);
}
#[test]
fn worker_commit_empty_delta_refuses() {
let (_tmp, primary, _wt, agent, _base) = worker_fixture("[\"true\"]", &[]);
let out = run_worker_commit(&primary, &agent, "msg").unwrap();
assert_eq!(out, refused(EMPTY_DELTA, String::new()));
}
#[test]
fn worker_commit_gate_red_refuses_and_leaves_head_at_base() {
let (_tmp, primary, wt, agent, base) = worker_fixture("[\"false\"]", &[]);
fs::write(wt.join("seed"), "worker change\n").unwrap();
let out = run_worker_commit(&primary, &agent, "msg").unwrap();
match out {
WorkerCommitOutput::Refused { reason, .. } => assert_eq!(reason, COMMIT_GATE_RED),
other => panic!("expected commit-gate-red, got {other:?}"),
}
assert_eq!(git_run(&wt, &["rev-parse", "HEAD^{commit}"]), base);
}
#[test]
fn worker_commit_gate_clears_marker_and_unsets_env_then_restores() {
let gate = r#"["sh", "-c", "test ! -f .doctrine/state/dispatch/worker && test -z \"$DOCTRINE_WORKER\""]"#;
let (_tmp, primary, wt, agent, base) = worker_fixture(gate, &[]);
crate::worktree::write_marker(&wt).unwrap();
fs::write(wt.join("seed"), "worker change\n").unwrap();
let out = run_worker_commit(&primary, &agent, "msg").unwrap();
match out {
WorkerCommitOutput::Committed { base: out_base, .. } => assert_eq!(out_base, base),
other => panic!("gate must see a cleared marker + unset env and pass; got {other:?}"),
}
assert!(
crate::worktree::marker_present(&wt),
"the worker marker must be restored after the gate"
);
}
#[test]
fn worker_commit_gate_carries_dispatch_gate_signal() {
let gate = r#"["sh", "-c", "test \"$DOCTRINE_DISPATCH_GATE\" = 1"]"#;
let (_tmp, primary, wt, agent, base) = worker_fixture(gate, &[]);
fs::write(wt.join("seed"), "worker change\n").unwrap();
let out = run_worker_commit(&primary, &agent, "msg").unwrap();
match out {
WorkerCommitOutput::Committed { base: out_base, .. } => assert_eq!(out_base, base),
other => panic!("gate must see DOCTRINE_DISPATCH_GATE=1 and pass; got {other:?}"),
}
}
#[test]
fn worker_commit_forbidden_zone_refuses() {
let (_tmp, primary, wt, agent, base) = worker_fixture("[\"true\"]", &[]);
fs::create_dir_all(wt.join(".doctrine/adr")).unwrap();
fs::write(wt.join(".doctrine/adr/x"), "sneaky\n").unwrap();
let out = run_worker_commit(&primary, &agent, "msg").unwrap();
match out {
WorkerCommitOutput::Refused { reason, detail } => {
assert_eq!(reason, FORBIDDEN_ZONE);
assert!(
detail.contains(".doctrine/adr/x"),
"names the path: {detail}"
);
}
other => panic!("expected forbidden-zone, got {other:?}"),
}
assert_eq!(git_run(&wt, &["rev-parse", "HEAD^{commit}"]), base);
}
#[test]
fn worker_commit_happy_path_lands_one_non_merge_commit() {
let (_tmp, primary, wt, agent, base) = worker_fixture("[\"true\"]", &[]);
fs::write(wt.join("seed"), "worker change\n").unwrap();
let out = run_worker_commit(&primary, &agent, "the message").unwrap();
let (oid, out_base) = match out {
WorkerCommitOutput::Committed { oid, base, .. } => (oid, base),
other => panic!("expected Committed, got {other:?}"),
};
assert_eq!(out_base, base, "returned base == B");
assert_eq!(git_run(&wt, &["rev-parse", "HEAD^{commit}"]), oid);
let parents = git_run(&wt, &["rev-list", "--parents", "-n", "1", &oid]);
let cols: Vec<&str> = parents.split_whitespace().collect();
assert_eq!(cols.len(), 2, "exactly one parent: {parents}");
assert_eq!(cols[1], base, "C^ == B");
assert_eq!(git_run(&wt, &["log", "-1", "--format=%s"]), "the message");
}
#[test]
fn worker_commit_stages_only_the_in_scope_path_after_the_gate_fmt() {
let (_tmp, primary, wt, agent, _base) = worker_fixture(
"[\"sh\", \"-c\", \"printf reformatted > outofscope.txt\"]",
&[
("outofscope.txt", "misformatted\n"),
("src/feature.rs", "fn f(){}\n"),
],
);
fs::write(wt.join("src/feature.rs"), "fn f() {}\n").unwrap();
let out = run_worker_commit(&primary, &agent, "in-scope only").unwrap();
let oid = match out {
WorkerCommitOutput::Committed { oid, .. } => oid,
other => panic!("expected Committed, got {other:?}"),
};
let touched = git_run(&wt, &["show", "--name-only", "--format=", &oid]);
let names: Vec<&str> = touched.lines().filter(|l| !l.is_empty()).collect();
assert_eq!(
names,
vec!["src/feature.rs"],
"commit contains ONLY the in-scope path, not the gate-reformatted file"
);
}
#[test]
fn worker_commit_soft_undeclared_still_commits() {
let (_tmp, primary, wt, agent, _base) = worker_fixture("[\"true\"]", &[]);
fs::write(wt.join("seed"), "worker change\n").unwrap();
let out = run_worker_commit(&primary, &agent, "msg").unwrap();
match out {
WorkerCommitOutput::Committed { undeclared, .. } => {
assert!(
undeclared.contains(&"seed".to_string()),
"the out-of-selector src path is reported undeclared: {undeclared:?}"
);
}
other => panic!("expected Committed, got {other:?}"),
}
}
#[test]
fn worker_commit_wrong_base_revive_refuses_before_any_tip_moves() {
let (_tmp, primary, wt, agent, base) = worker_fixture("[\"true\"]", &[]);
let coord = coord_of(&wt);
fs::write(wt.join("seed"), "prior worker work\n").unwrap();
git_run(&wt, &["commit", "-aq", "-m", "prior worker commit"]);
let fork_tip = git_run(&wt, &["rev-parse", "HEAD^{commit}"]);
assert_ne!(
fork_tip, base,
"fork_tip must have advanced past the original base"
);
git_run(&wt, &["reset", "--hard", &base]);
fs::write(wt.join("seed"), "revive edit at the wrong base\n").unwrap();
provision_dispatch_record(
&coord,
&agent,
&fork_tip,
&wt,
&format!("dispatch/{agent}"),
Some(&fixture_binding()),
)
.unwrap();
let out = run_worker_commit(&primary, &agent, "revive commit").unwrap();
match out {
WorkerCommitOutput::Refused { reason, .. } => {
assert!(
reason == NOT_AT_BASE || reason == "stale-record",
"a wrong-base revive must refuse via the base-check family \
(head_at_base / the resolver's HEAD==base consistency check), \
never commit: got reason {reason}"
);
}
other => panic!(
"wrong-base revive MUST be refused before any tip moves — there is no \
prompt-obedience substitute for the base-check; got {other:?}"
),
}
assert_eq!(
git_run(&wt, &["rev-parse", "HEAD^{commit}"]),
base,
"no commit landed — HEAD did not advance past the wrong base"
);
}
fn raw_commit_on_fork(wt: &Path, files: &[(&str, &str)]) -> String {
for (rel, body) in files {
let path = wt.join(rel);
fs::create_dir_all(path.parent().unwrap()).unwrap();
fs::write(&path, body).unwrap();
}
git_run(wt, &["add", "-A"]);
git_run(wt, &["commit", "-q", "-m", "raw"]);
git_run(wt, &["rev-parse", "HEAD^{commit}"])
}
#[test]
fn first_commit_leg_lands_the_class_2_worker_commit_row_after_the_act() {
let (_tmp, primary, wt, agent, _base) = worker_fixture("[\"true\"]", &[]);
let coord = coord_of(&wt);
assert_eq!(
funnel_row(&coord).map(|r| r.position),
Some(Position::Spawned),
"the phase stands at spawned before the worker commits"
);
fs::write(wt.join("seed"), "worker change\n").unwrap();
let oid = match run_worker_commit(&primary, &agent, "msg").unwrap() {
WorkerCommitOutput::Committed { oid, .. } => oid,
other => panic!("expected Committed, got {other:?}"),
};
let row = funnel_row(&coord).expect("a row was landed");
assert_eq!(row.position, Position::WorkerCommitted);
assert_eq!(
row.worker_commit.map(|w| w.fork_tip),
Some(oid),
"the row names the tip that actually landed"
);
}
fn drop_the_spawn_row(coord: &Path, base: &str) {
git_run(coord, &["update-ref", &dispatch_ref(SLICE), base]);
assert!(funnel_row(coord).is_none(), "fixture: the row is gone");
}
#[test]
fn a_pre_spawn_row_is_healed_in_the_same_splice_as_the_worker_commit_row() {
let (_tmp, primary, wt, agent, base) = worker_fixture("[\"true\"]", &[]);
let coord = coord_of(&wt);
drop_the_spawn_row(&coord, &base);
fs::write(wt.join("seed"), "worker change\n").unwrap();
let oid = match run_worker_commit(&primary, &agent, "msg").unwrap() {
WorkerCommitOutput::Committed { oid, .. } => oid,
other => panic!("expected Committed, got {other:?}"),
};
let row = funnel_row(&coord).expect("the healed row");
assert_eq!(row.position, Position::WorkerCommitted);
assert_eq!(
row.spawn.expect("the Spawn rung healed").fork,
format!("dispatch/{agent}"),
"the missing create-fork row is filled from the durable record"
);
assert_eq!(row.worker_commit.map(|w| w.fork_tip), Some(oid));
let tip = git_run(&coord, &["rev-parse", &dispatch_ref(SLICE)]);
assert_eq!(
git_run(&coord, &["rev-list", "--count", &format!("{base}..{tip}")]),
"1"
);
}
#[test]
fn the_retry_leg_also_heals_a_pre_spawn_row_under_the_same_belts() {
let (_tmp, primary, wt, agent, base) = worker_fixture("[\"true\"]", &[]);
let coord = coord_of(&wt);
let c = raw_commit_on_fork(&wt, &[("seed", "ungated but benign\n")]);
drop_the_spawn_row(&coord, &base);
match run_worker_commit(&primary, &agent, "msg").unwrap() {
WorkerCommitOutput::Committed { oid, .. } => {
assert_eq!(oid, c, "the landed commit is adopted, not re-made");
}
other => panic!("expected Committed, got {other:?}"),
}
let row = funnel_row(&coord).expect("the healed row");
assert_eq!(row.position, Position::WorkerCommitted);
assert!(
row.spawn.is_some(),
"the Spawn rung healed on the retry leg too"
);
}
#[test]
fn an_unbound_fork_refuses_unprovable_fork_and_commits_nothing() {
let (_tmp, primary, wt, agent, base) = worker_fixture("[\"true\"]", &[]);
let coord = coord_of(&wt);
provision_dispatch_record(
&coord,
&agent,
&base,
&wt,
&format!("dispatch/{agent}"),
None,
)
.unwrap();
fs::write(wt.join("seed"), "worker change\n").unwrap();
match run_worker_commit(&primary, &agent, "msg").unwrap() {
WorkerCommitOutput::Refused { reason, detail } => {
assert_eq!(reason, "unprovable-fork");
assert_eq!(detail, format!("dispatch/{agent}"), "names the fork");
}
other => panic!("expected unprovable-fork, got {other:?}"),
}
assert_eq!(
git_run(&wt, &["rev-parse", "HEAD^{commit}"]),
base,
"nothing committed"
);
}
#[test]
fn a_lost_response_retry_is_a_recorded_no_op_replay_naming_the_landed_tip() {
let (_tmp, primary, wt, agent, base) = worker_fixture("[\"true\"]", &[]);
let coord = coord_of(&wt);
fs::write(wt.join("seed"), "worker change\n").unwrap();
let first = run_worker_commit(&primary, &agent, "msg").unwrap();
let oid = match first {
WorkerCommitOutput::Committed { ref oid, .. } => oid.clone(),
other => panic!("expected Committed, got {other:?}"),
};
let coord_tip_before = git_run(&coord, &["rev-parse", &dispatch_ref(SLICE)]);
match run_worker_commit(&primary, &agent, "msg").unwrap() {
WorkerCommitOutput::Committed {
oid: again,
base: again_base,
undeclared,
} => {
assert_eq!(again, oid, "the replay NAMES the already-landed tip");
assert_eq!(again_base, base);
assert!(undeclared.is_empty(), "a replay re-classifies nothing");
}
other => panic!("expected a no-op replay, got {other:?}"),
}
assert_eq!(
git_run(&wt, &["rev-parse", "HEAD^{commit}"]),
oid,
"no second commit was minted"
);
assert_eq!(
git_run(&coord, &["rev-parse", &dispatch_ref(SLICE)]),
coord_tip_before,
"a replay writes NOTHING — the coord ref did not move"
);
}
#[test]
fn a_kill_between_the_fork_commit_and_the_coord_record_self_records_on_re_drive() {
let (_tmp, primary, wt, agent, _base) = worker_fixture("[\"true\"]", &[]);
let coord = coord_of(&wt);
let c = raw_commit_on_fork(&wt, &[("seed", "ungated but benign\n")]);
assert_eq!(
funnel_row(&coord).map(|r| r.position),
Some(Position::Spawned),
"the row lags the act — exactly the crash window"
);
match run_worker_commit(&primary, &agent, "msg").unwrap() {
WorkerCommitOutput::Committed { oid, .. } => {
assert_eq!(oid, c, "the landed commit is adopted, not re-made");
}
other => panic!("expected the belt-gated self-record, got {other:?}"),
}
let row = funnel_row(&coord).expect("the lagging row was landed");
assert_eq!(row.position, Position::WorkerCommitted);
assert_eq!(row.worker_commit.map(|w| w.fork_tip), Some(c));
}
#[test]
fn the_self_record_leg_refuses_naming_the_belt_an_ungated_commit_violates() {
let (_tmp, primary, wt, agent, _base) = worker_fixture("[\"true\"]", &[]);
let coord = coord_of(&wt);
let c = raw_commit_on_fork(&wt, &[(".doctrine/adr/x", "sneaky\n")]);
match run_worker_commit(&primary, &agent, "msg").unwrap() {
WorkerCommitOutput::Refused { reason, detail } => {
assert_eq!(reason, FORBIDDEN_ZONE, "the SCOPE belt is named");
assert!(
detail.contains(".doctrine/adr/x"),
"names the path: {detail}"
);
}
other => panic!("expected forbidden-zone, got {other:?}"),
}
assert_eq!(
funnel_row(&coord).map(|r| r.position),
Some(Position::Spawned),
"never adopted, never recorded — the row still lags"
);
assert_eq!(
git_run(&wt, &["rev-parse", "HEAD^{commit}"]),
c,
"the fork is left exactly as found (a triage beat, not a rescue)"
);
}
#[test]
fn the_self_record_leg_refuses_a_gate_red_ungated_commit() {
let (_tmp, primary, wt, agent, _base) = worker_fixture("[\"false\"]", &[]);
let coord = coord_of(&wt);
raw_commit_on_fork(&wt, &[("seed", "ungated\n")]);
match run_worker_commit(&primary, &agent, "msg").unwrap() {
WorkerCommitOutput::Refused { reason, .. } => assert_eq!(reason, COMMIT_GATE_RED),
other => panic!("expected commit-gate-red, got {other:?}"),
}
assert_eq!(
funnel_row(&coord).map(|r| r.position),
Some(Position::Spawned),
"a red gate adopts nothing"
);
}
#[test]
fn a_dirty_advanced_fork_is_a_late_recommit_not_a_retry() {
let (_tmp, primary, wt, agent, _base) = worker_fixture("[\"true\"]", &[]);
let c = raw_commit_on_fork(&wt, &[("seed", "first\n")]);
fs::write(wt.join("seed"), "a second, later edit\n").unwrap();
match run_worker_commit(&primary, &agent, "msg").unwrap() {
WorkerCommitOutput::Refused { reason, detail } => {
assert_eq!(reason, LATE_RECOMMIT);
assert!(detail.contains("seed"), "names the dirt: {detail}");
}
other => panic!("expected late-recommit, got {other:?}"),
}
assert_eq!(
git_run(&wt, &["rev-parse", "HEAD^{commit}"]),
c,
"no second commit"
);
}
#[test]
fn an_imported_phase_refuses_already_imported_on_both_legs() {
let (_tmp, primary, wt, agent, _base) = worker_fixture("[\"true\"]", &[]);
let coord = coord_of(&wt);
let fork = format!("dispatch/{agent}");
land(
&coord,
&Transition::RecordWorkerCommit {
fork_tip: "deadbeef".to_owned(),
},
);
land(
&coord,
&Transition::Import {
fork_tip: "deadbeef".to_owned(),
onto: "cafe".to_owned(),
},
);
fs::write(wt.join("seed"), "too late\n").unwrap();
match run_worker_commit(&primary, &agent, "msg").unwrap() {
WorkerCommitOutput::Refused { reason, detail } => {
assert_eq!(reason, "already-imported");
assert!(
detail.contains("imported"),
"the payload IS the recovery procedure: {detail}"
);
}
other => panic!("expected already-imported, got {other:?}"),
}
let (_tmp2, primary2, wt2, agent2, _base2) = worker_fixture("[\"true\"]", &[]);
let coord2 = coord_of(&wt2);
let c = raw_commit_on_fork(&wt2, &[("seed", "landed\n")]);
land(
&coord2,
&Transition::RecordWorkerCommit {
fork_tip: c.clone(),
},
);
land(
&coord2,
&Transition::Import {
fork_tip: c,
onto: "cafe".to_owned(),
},
);
match run_worker_commit(&primary2, &agent2, "msg").unwrap() {
WorkerCommitOutput::Refused { reason, .. } => assert_eq!(reason, "already-imported"),
other => panic!("expected already-imported, got {other:?}"),
}
drop(fork);
}
}