#![allow(
clippy::expect_used,
clippy::unwrap_used,
clippy::tests_outside_test_module,
reason = "integration test: fail-fast unwrap/expect are idiomatic, and test fns live at crate root by construction"
)]
use std::path::Path;
use std::process::{Command, Output};
const BIN: &str = env!("CARGO_BIN_EXE_doctrine");
fn git(dir: &Path, args: &[&str]) -> String {
let out = Command::new("git")
.arg("-C")
.arg(dir)
.args(args)
.output()
.expect("spawn git");
assert!(
out.status.success(),
"git {args:?} failed: {}",
String::from_utf8_lossy(&out.stderr)
);
String::from_utf8_lossy(&out.stdout).trim().to_string()
}
fn commit(dir: &Path, path: &str, content: &str, msg: &str) -> String {
let full = dir.join(path);
std::fs::create_dir_all(full.parent().unwrap()).unwrap();
std::fs::write(&full, content).unwrap();
git(dir, &["add", path]);
git(dir, &["commit", "-q", "-m", msg]);
git(dir, &["rev-parse", "HEAD"])
}
struct Fixture {
base: String,
code_end_1: String,
code_end_2: String,
}
fn build_fixture(dir: &Path) -> Fixture {
std::fs::create_dir_all(dir).unwrap();
git(dir, &["init", "-q", "-b", "main"]);
git(dir, &["config", "user.email", "t@example.com"]);
git(dir, &["config", "user.name", "Test"]);
let base = commit(dir, "trunk.txt", "trunk", "base");
git(dir, &["checkout", "-q", "-b", "dispatch/064"]);
let code_end_1 = commit(dir, "src1.txt", "a", "phase1 code");
let code_end_2 = commit(dir, "src2.txt", "b", "phase2 code");
commit(
dir,
".doctrine/slice/064/slice-064.md",
"scope",
"authored entity",
);
commit(dir, "ahead.txt", "ahead", "orthogonal ahead-projected");
commit(dir, "notahead.txt", "notahead", "orthogonal failed");
let boundaries = format!(
"[[boundary]]\nphase = \"PHASE-01\"\ncode_start_oid = \"{base}\"\ncode_end_oid = \"{code_end_1}\"\n\
[[boundary]]\nphase = \"PHASE-02\"\ncode_start_oid = \"{code_end_1}\"\ncode_end_oid = \"{code_end_2}\"\n\
[[boundary]]\nphase = \"PHASE-03\"\ncode_start_oid = \"{code_end_2}\"\ncode_end_oid = \"{code_end_2}\"\n"
);
let orthogonal = "[[mark]]\nentity = \"ahead\"\npath = \"ahead.txt\"\nstatus = \"verified\"\n\
[[mark]]\nentity = \"notahead\"\npath = \"notahead.txt\"\nstatus = \"failed\"\n";
std::fs::create_dir_all(dir.join(".doctrine/dispatch/064")).unwrap();
std::fs::write(
dir.join(".doctrine/dispatch/064/boundaries.toml"),
&boundaries,
)
.unwrap();
std::fs::write(
dir.join(".doctrine/dispatch/064/orthogonal.toml"),
orthogonal,
)
.unwrap();
git(dir, &["add", ".doctrine/dispatch/064"]);
git(dir, &["commit", "-q", "-m", "ledger fixtures"]);
git(dir, &["checkout", "-q", "main"]);
Fixture {
base,
code_end_1,
code_end_2,
}
}
fn run(cwd: &Path, worker: Option<bool>, args: &[&str]) -> Output {
let mut cmd = Command::new(BIN);
cmd.args(args).current_dir(cwd);
match worker {
Some(true) => {
cmd.env("DOCTRINE_WORKER", "1");
}
Some(false) | None => {
cmd.env_remove("DOCTRINE_WORKER");
}
}
cmd.output().expect("spawn doctrine")
}
fn stderr(out: &Output) -> String {
String::from_utf8_lossy(&out.stderr).into_owned()
}
fn ref_exists(dir: &Path, refname: &str) -> bool {
Command::new("git")
.arg("-C")
.arg(dir)
.args(["rev-parse", "--verify", "--quiet", refname])
.output()
.expect("spawn git")
.status
.success()
}
fn prepare_review(cwd: &Path) -> Output {
run(
cwd,
None,
&[
"dispatch",
"sync",
"--prepare-review",
"--slice",
"64",
"-p",
cwd.to_str().unwrap(),
],
)
}
#[test]
fn prepare_review_creates_review_ref_and_leaves_trunk_unchanged() {
let repo = tempfile::tempdir().unwrap();
let dir = repo.path();
build_fixture(dir);
let main_before = git(dir, &["rev-parse", "main"]);
let out = prepare_review(dir);
assert!(
out.status.success(),
"prepare-review ok; stderr: {}",
stderr(&out)
);
assert!(ref_exists(dir, "review/064"), "review/064 created");
assert_eq!(
git(dir, &["rev-parse", "main"]),
main_before,
"trunk untouched"
);
assert!(!ref_exists(dir, "edge"), "edge never written");
}
#[test]
fn review_bundle_excludes_ledger_and_verified_orthogonal_retains_impl() {
let repo = tempfile::tempdir().unwrap();
let dir = repo.path();
let fixture = build_fixture(dir);
let out = prepare_review(dir);
assert!(
out.status.success(),
"prepare-review ok; stderr: {}",
stderr(&out)
);
let listing = git(dir, &["ls-tree", "-r", "--name-only", "review/064"]);
let paths: Vec<&str> = listing.lines().collect();
for kept in [
"trunk.txt",
"src1.txt",
"src2.txt",
".doctrine/slice/064/slice-064.md",
] {
assert!(paths.contains(&kept), "review retains {kept}: {listing}");
}
assert!(
!paths
.iter()
.any(|p| p.starts_with(".doctrine/dispatch/064")),
"review excludes the run-ledger dir: {listing}"
);
assert!(
!paths.contains(&"ahead.txt"),
"verified-orthogonal path excluded: {listing}"
);
assert!(
paths.contains(&"notahead.txt"),
"failed-mark path falls back into the bundle: {listing}"
);
let parent = git(dir, &["rev-parse", "review/064^"]);
assert_eq!(parent, fixture.base, "review parented to the trunk base");
}
#[test]
fn phase_refs_are_exact_code_deltas_with_no_doctrine_and_skip_empty() {
let repo = tempfile::tempdir().unwrap();
let dir = repo.path();
let fixture = build_fixture(dir);
let out = prepare_review(dir);
assert!(
out.status.success(),
"prepare-review ok; stderr: {}",
stderr(&out)
);
assert!(ref_exists(dir, "phase/064-01"), "phase 01 cut");
assert!(ref_exists(dir, "phase/064-02"), "phase 02 cut");
assert!(
!ref_exists(dir, "phase/064-03"),
"empty-code phase 03 emits no ref"
);
assert_eq!(
git(dir, &["diff", "--name-only", &fixture.base, "phase/064-01"]),
"src1.txt",
"phase 01 diff is exactly its code delta"
);
assert_eq!(
git(
dir,
&["diff", "--name-only", "phase/064-01", "phase/064-02"]
),
"src2.txt",
"phase 02 diff is exactly its code delta"
);
let p1_listing = git(dir, &["ls-tree", "-r", "--name-only", "phase/064-01"]);
assert!(
!p1_listing.lines().any(|p| p.starts_with(".doctrine")),
"phase ref carries no .doctrine/ path: {p1_listing}"
);
let _ = &fixture.code_end_1;
let _ = &fixture.code_end_2;
}
#[test]
fn journal_recorded_on_branch_with_verified_status() {
let repo = tempfile::tempdir().unwrap();
let dir = repo.path();
build_fixture(dir);
let out = prepare_review(dir);
assert!(
out.status.success(),
"prepare-review ok; stderr: {}",
stderr(&out)
);
let journal = git(
dir,
&["show", "dispatch/064:.doctrine/dispatch/064/journal.toml"],
);
assert!(
journal.contains("review/064"),
"journal records the review ref: {journal}"
);
assert!(
journal.contains("phase/064-01"),
"journal records a phase ref: {journal}"
);
assert!(
journal.contains("verified"),
"applied rows are verified: {journal}"
);
assert!(
!journal.contains("pending"),
"no rows left pending after a clean run: {journal}"
);
}
#[test]
fn prepare_review_refused_under_worker_mode() {
let repo = tempfile::tempdir().unwrap();
let dir = repo.path();
build_fixture(dir);
let holder = tempfile::tempdir().unwrap();
let base = git(dir, &["rev-parse", "HEAD"]);
let linked = holder.path().join("fork");
git(
dir,
&[
"worktree",
"add",
"-b",
"wkr-guard",
linked.to_str().unwrap(),
&base,
],
);
let marker_dir = linked.join(".doctrine/state/dispatch");
std::fs::create_dir_all(&marker_dir).unwrap();
std::fs::write(marker_dir.join("worker"), b"").unwrap();
let out = run(
&linked,
None,
&[
"dispatch",
"sync",
"--prepare-review",
"--slice",
"64",
"-p",
dir.to_str().unwrap(),
],
);
assert!(
!out.status.success(),
"refused from a marked linked worktree"
);
assert!(
stderr(&out).contains("dispatch-sync"),
"refusal names the verb: {}",
stderr(&out)
);
assert!(
!ref_exists(dir, "review/064"),
"refused run creates no external ref"
);
let out = run(
dir,
Some(true),
&[
"dispatch",
"sync",
"--prepare-review",
"--slice",
"64",
"-p",
dir.to_str().unwrap(),
],
);
assert!(!out.status.success(), "refused when DOCTRINE_WORKER set");
assert!(
stderr(&out).contains("DOCTRINE_WORKER"),
"carries the dual-cause: {}",
stderr(&out)
);
assert!(!ref_exists(dir, "review/064"), "still no external ref");
}
#[test]
fn stale_review_ref_is_reported_not_clobbered() {
let repo = tempfile::tempdir().unwrap();
let dir = repo.path();
build_fixture(dir);
let bogus = git(dir, &["rev-parse", "main"]);
git(dir, &["update-ref", "refs/heads/review/064", &bogus]);
let out = prepare_review(dir);
assert!(
!out.status.success(),
"stale ref makes the run report failure"
);
assert!(
stderr(&out).contains("not clobbered"),
"stale ref is reported, not clobbered: {}",
stderr(&out)
);
assert_eq!(
git(dir, &["rev-parse", "review/064"]),
bogus,
"the stale review/064 is left untouched"
);
}
#[test]
fn refused_row_persists_failed_status_in_committed_journal() {
let repo = tempfile::tempdir().unwrap();
let dir = repo.path();
build_fixture(dir);
let bogus = git(dir, &["rev-parse", "main"]);
git(dir, &["update-ref", "refs/heads/review/064", &bogus]);
let out = prepare_review(dir);
assert!(!out.status.success(), "stale ref makes the run bail");
let journal = git(
dir,
&["show", "dispatch/064:.doctrine/dispatch/064/journal.toml"],
);
assert!(
journal.contains("review/064"),
"journal records the refused review row: {journal}"
);
assert!(
journal.contains("failed"),
"the refused row persisted status=failed (recovery commit ran): {journal}"
);
}
fn integrate(cwd: &Path, extra: &[&str]) -> Output {
let mut args = vec![
"dispatch",
"sync",
"--integrate",
"--slice",
"64",
"-p",
cwd.to_str().unwrap(),
];
args.extend_from_slice(extra);
run(cwd, None, &args)
}
#[test]
fn integrate_default_replays_prepared_refs_no_checkout_no_trunk_write() {
let repo = tempfile::tempdir().unwrap();
let dir = repo.path();
build_fixture(dir);
assert!(prepare_review(dir).status.success());
let trunk_before = git(dir, &["rev-parse", "main"]);
let review_before = git(dir, &["rev-parse", "review/064"]);
let phase_before = git(dir, &["rev-parse", "phase/064-02"]);
assert_eq!(git(dir, &["rev-parse", "--abbrev-ref", "HEAD"]), "main");
let out = integrate(dir, &[]);
assert!(
out.status.success(),
"default integrate replays cleanly; stderr: {}",
stderr(&out)
);
assert_eq!(
git(dir, &["rev-parse", "main"]),
trunk_before,
"trunk untouched"
);
assert_eq!(git(dir, &["rev-parse", "review/064"]), review_before);
assert_eq!(git(dir, &["rev-parse", "phase/064-02"]), phase_before);
let journal = git(
dir,
&["show", "dispatch/064:.doctrine/dispatch/064/journal.toml"],
);
assert!(
!journal.contains("pending"),
"no pending rows after replay: {journal}"
);
}
#[test]
fn integrate_trunk_fast_forwards_then_is_idempotent() {
let repo = tempfile::tempdir().unwrap();
let dir = repo.path();
build_fixture(dir);
assert!(prepare_review(dir).status.success());
let phase_tip = git(dir, &["rev-parse", "phase/064-02"]);
let out = integrate(dir, &["--trunk", "refs/heads/main"]);
assert!(
out.status.success(),
"ff trunk integrate; stderr: {}",
stderr(&out)
);
assert_eq!(
git(dir, &["rev-parse", "main"]),
phase_tip,
"trunk fast-forwarded to the cumulative code tip"
);
let out2 = integrate(dir, &["--trunk", "refs/heads/main"]);
assert!(
out2.status.success(),
"idempotent re-run; stderr: {}",
stderr(&out2)
);
assert_eq!(
git(dir, &["rev-parse", "main"]),
phase_tip,
"no second advance"
);
}
#[test]
fn integrate_trunk_refuses_non_fast_forward() {
let repo = tempfile::tempdir().unwrap();
let dir = repo.path();
build_fixture(dir);
assert!(prepare_review(dir).status.success());
let moved = commit(dir, "trunk2.txt", "moved", "trunk advanced divergently");
assert_eq!(git(dir, &["rev-parse", "main"]), moved);
let out = integrate(dir, &["--trunk", "refs/heads/main"]);
assert!(!out.status.success(), "non-ff trunk is refused");
assert!(
stderr(&out).contains("fast-forward") || stderr(&out).contains("trunk moved"),
"refusal names the non-ff/moved-trunk cause: {}",
stderr(&out)
);
assert_eq!(
git(dir, &["rev-parse", "main"]),
moved,
"trunk left untouched"
);
}
#[test]
fn prepare_review_projects_off_pinned_fork_point_not_moved_trunk() {
let repo = tempfile::tempdir().unwrap();
let dir = repo.path();
let fixture = build_fixture(dir);
let foreign = commit(dir, "foreign.txt", "foreign", "foreign trunk advance");
assert_eq!(git(dir, &["rev-parse", "main"]), foreign);
assert_ne!(
foreign, fixture.base,
"trunk genuinely moved off the fork-point"
);
let out = prepare_review(dir);
assert!(
out.status.success(),
"prepare-review ok despite moved trunk; stderr: {}",
stderr(&out)
);
assert_eq!(
git(dir, &["rev-parse", "phase/064-01^"]),
fixture.base,
"phase parented on the pinned fork-point, not the moved trunk tip"
);
assert_eq!(
git(dir, &["diff", "--name-only", &fixture.base, "phase/064-01"]),
"src1.txt",
"phase diff is the exact code delta — foreign trunk file excluded"
);
assert!(
!git(dir, &["ls-tree", "-r", "--name-only", "phase/064-02"]).contains("foreign.txt"),
"the foreign trunk file never leaks into the phase bundle"
);
let out = integrate(dir, &["--trunk", "refs/heads/main"]);
assert!(
!out.status.success(),
"integrate --trunk refuses the moved trunk; stderr: {}",
stderr(&out)
);
assert!(
stderr(&out).contains("fast-forward") || stderr(&out).contains("trunk moved"),
"refusal names the non-ff cause: {}",
stderr(&out)
);
assert_eq!(
git(dir, &["rev-parse", "main"]),
foreign,
"trunk left untouched on refusal"
);
}
#[test]
fn integrate_refuses_clobbered_prepared_ref() {
let repo = tempfile::tempdir().unwrap();
let dir = repo.path();
build_fixture(dir);
assert!(prepare_review(dir).status.success());
let bogus = git(dir, &["rev-parse", "main"]);
git(dir, &["update-ref", "refs/heads/review/064", &bogus]);
let out = integrate(dir, &[]);
assert!(
!out.status.success(),
"diverged prepared ref refuses replay"
);
assert!(
stderr(&out).contains("moved target") || stderr(&out).contains("not clobbered"),
"divergence is reported: {}",
stderr(&out)
);
assert_eq!(
git(dir, &["rev-parse", "review/064"]),
bogus,
"the clobbered ref is left untouched, never force-resolved"
);
}
#[test]
fn integrate_edge_is_opt_in_and_aggregates_the_review_bundle() {
let repo = tempfile::tempdir().unwrap();
let dir = repo.path();
build_fixture(dir);
assert!(prepare_review(dir).status.success());
let review_tip = git(dir, &["rev-parse", "review/064"]);
assert!(integrate(dir, &[]).status.success());
assert!(
!ref_exists(dir, "refs/heads/edge"),
"edge is opt-in, default off"
);
let out = integrate(dir, &["--edge", "refs/heads/edge"]);
assert!(
out.status.success(),
"edge projection; stderr: {}",
stderr(&out)
);
assert_eq!(
git(dir, &["rev-parse", "edge"]),
review_tip,
"edge aggregates the review/<slice> bundle"
);
}
#[test]
fn integrate_trunk_checked_out_ff_leaves_clean_tree() {
let repo = tempfile::tempdir().unwrap();
let dir = repo.path();
build_fixture(dir);
assert!(prepare_review(dir).status.success());
let phase_tip = git(dir, &["rev-parse", "phase/064-02"]);
assert_eq!(git(dir, &["rev-parse", "--abbrev-ref", "HEAD"]), "main");
let out = integrate(dir, &["--trunk", "refs/heads/main"]);
assert!(
out.status.success(),
"ff integrate on a checked-out trunk; stderr: {}",
stderr(&out)
);
assert_eq!(
git(dir, &["rev-parse", "main"]),
phase_tip,
"trunk ref advanced to the cumulative code tip"
);
assert_eq!(
git(dir, &["rev-parse", "HEAD"]),
phase_tip,
"HEAD advanced with the live checkout"
);
assert!(
git(dir, &["status", "--porcelain"]).is_empty(),
"index + worktree resynced — no phantom reverse-diff (ISS-022/030)",
);
assert!(
dir.join("src1.txt").exists() && dir.join("src2.txt").exists(),
"the advanced code is materialised in the live worktree",
);
}
#[test]
fn integrate_trunk_not_checked_out_advances_ref_leaves_live_checkout_clean() {
let repo = tempfile::tempdir().unwrap();
let dir = repo.path();
let fixture = build_fixture(dir);
assert!(prepare_review(dir).status.success());
let phase_tip = git(dir, &["rev-parse", "phase/064-02"]);
git(dir, &["update-ref", "refs/heads/release", &fixture.base]);
let out = integrate(dir, &["--trunk", "refs/heads/release"]);
assert!(
out.status.success(),
"ff integrate on a not-checked-out ref; stderr: {}",
stderr(&out)
);
assert_eq!(
git(dir, &["rev-parse", "release"]),
phase_tip,
"the unchecked-out ref advanced by CAS"
);
assert!(
git(dir, &["status", "--porcelain"]).is_empty(),
"the live main checkout is untouched by a pure-ref advance",
);
}
#[test]
fn integrate_dirty_checked_out_target_refuses_zero_refs_moved() {
let repo = tempfile::tempdir().unwrap();
let dir = repo.path();
build_fixture(dir);
assert!(prepare_review(dir).status.success());
let main_before = git(dir, &["rev-parse", "main"]);
let dispatch_before = git(dir, &["rev-parse", "dispatch/064"]);
let review_before = git(dir, &["rev-parse", "review/064"]);
let phase_before = git(dir, &["rev-parse", "phase/064-02"]);
std::fs::write(dir.join("trunk.txt"), "locally dirtied").unwrap();
let out = integrate(dir, &["--trunk", "refs/heads/main"]);
assert!(!out.status.success(), "a dirty checked-out target refuses");
assert!(
stderr(&out).contains("integrate-dirty-worktree"),
"refusal names the dirty-worktree token: {}",
stderr(&out),
);
assert_eq!(
git(dir, &["rev-parse", "main"]),
main_before,
"trunk unmoved"
);
assert_eq!(
git(dir, &["rev-parse", "dispatch/064"]),
dispatch_before,
"coordination ref unmoved (gate precedes the first commit_journal, M4)"
);
assert_eq!(
git(dir, &["rev-parse", "review/064"]),
review_before,
"review unmoved"
);
assert_eq!(
git(dir, &["rev-parse", "phase/064-02"]),
phase_before,
"phase unmoved"
);
}
#[test]
fn integrate_nonff_checked_out_edge_refuses_and_persists_failed() {
let repo = tempfile::tempdir().unwrap();
let dir = repo.path();
let fixture = build_fixture(dir);
assert!(prepare_review(dir).status.success());
git(dir, &["update-ref", "refs/heads/edge", &fixture.base]);
let holder = tempfile::tempdir().unwrap();
let linked = holder.path().join("edgewt");
git(dir, &["worktree", "add", linked.to_str().unwrap(), "edge"]);
std::fs::write(linked.join("divergent.txt"), "x").unwrap();
git(&linked, &["add", "divergent.txt"]);
git(&linked, &["commit", "-q", "-m", "edge divergent"]);
let edge_before = git(dir, &["rev-parse", "edge"]);
let out = integrate(dir, &["--edge", "refs/heads/edge"]);
assert!(!out.status.success(), "a non-ff checked-out edge refuses");
assert!(
stderr(&out).contains("integrate-nonff-checkout"),
"refusal names the non-ff-checkout token: {}",
stderr(&out),
);
assert_eq!(
git(dir, &["rev-parse", "edge"]),
edge_before,
"edge ref left untouched (never reset --hard a live ref)"
);
let journal = git(
dir,
&["show", "dispatch/064:.doctrine/dispatch/064/journal.toml"],
);
assert!(
journal.contains("failed"),
"the refused edge row persisted status=failed: {journal}"
);
}
#[test]
fn integrate_report_emits_disposition_and_preserves_stdout_reflist() {
let repo = tempfile::tempdir().unwrap();
let dir = repo.path();
build_fixture(dir);
assert!(prepare_review(dir).status.success());
let out = integrate(dir, &["--trunk", "refs/heads/main"]);
assert!(out.status.success(), "stderr: {}", stderr(&out));
let so = String::from_utf8_lossy(&out.stdout);
let se = stderr(&out);
assert!(
so.lines().any(|l| l == "refs/heads/main"),
"stdout carries the advanced trunk ref verbatim: {so:?}",
);
assert!(
se.contains("(advanced+resynced)"),
"stderr names the exact advanced+resynced disposition: {se}",
);
assert!(
se.contains("integrate: refs/heads/main "),
"stderr carries the per-row trunk detail line: {se}",
);
assert!(
se.contains("ref(s) replayed"),
"the trailing summary line is preserved: {se}",
);
assert!(
se.contains("(no-op)"),
"no-op rows are reported with the exact token: {se}",
);
}
#[test]
fn integrate_refused_under_worker_mode() {
let repo = tempfile::tempdir().unwrap();
let dir = repo.path();
build_fixture(dir);
assert!(prepare_review(dir).status.success());
let trunk_before = git(dir, &["rev-parse", "main"]);
let holder = tempfile::tempdir().unwrap();
let base = git(dir, &["rev-parse", "HEAD"]);
let linked = holder.path().join("fork");
git(
dir,
&[
"worktree",
"add",
"-b",
"wkr-int",
linked.to_str().unwrap(),
&base,
],
);
let marker_dir = linked.join(".doctrine/state/dispatch");
std::fs::create_dir_all(&marker_dir).unwrap();
std::fs::write(marker_dir.join("worker"), b"").unwrap();
let out = run(
&linked,
None,
&[
"dispatch",
"sync",
"--integrate",
"--trunk",
"refs/heads/main",
"--slice",
"64",
"-p",
dir.to_str().unwrap(),
],
);
assert!(
!out.status.success(),
"refused from a marked linked worktree"
);
assert!(
stderr(&out).contains("dispatch-sync"),
"refusal names the verb: {}",
stderr(&out)
);
assert_eq!(
git(dir, &["rev-parse", "main"]),
trunk_before,
"no trunk write"
);
let out = run(
dir,
Some(true),
&[
"dispatch",
"sync",
"--integrate",
"--trunk",
"refs/heads/main",
"--slice",
"64",
"-p",
dir.to_str().unwrap(),
],
);
assert!(!out.status.success(), "refused when DOCTRINE_WORKER set");
assert!(
stderr(&out).contains("DOCTRINE_WORKER"),
"carries the dual-cause: {}",
stderr(&out)
);
assert_eq!(
git(dir, &["rev-parse", "main"]),
trunk_before,
"still no trunk write"
);
}
fn record_boundary(cwd: &Path, root: &Path, phase: &str, start: &str, end: &str) -> Output {
run(
cwd,
None,
&[
"dispatch",
"record-boundary",
"--slice",
"64",
"--phase",
phase,
"--code-start",
start,
"--code-end",
end,
"-p",
root.to_str().unwrap(),
],
)
}
#[test]
fn record_boundary_appends_row_at_canonical_padded_ledger_path() {
let repo = tempfile::tempdir().unwrap();
let dir = repo.path();
let fx = build_fixture(dir);
let ledger = dir.join(".doctrine/dispatch/064/boundaries.toml");
let out = record_boundary(dir, dir, "PHASE-09", &fx.base, &fx.code_end_1);
assert!(
out.status.success(),
"record-boundary ok; stderr: {}",
stderr(&out)
);
let body = std::fs::read_to_string(&ledger).expect("ledger written at padded path");
assert!(body.contains("[[boundary]]"), "row header: {body}");
assert!(body.contains("phase = \"PHASE-09\""), "phase row: {body}");
assert!(body.contains(&fx.code_end_1), "code_end oid: {body}");
let out = record_boundary(dir, dir, "PHASE-10", &fx.code_end_1, &fx.code_end_2);
assert!(out.status.success(), "second record ok: {}", stderr(&out));
let body = std::fs::read_to_string(&ledger).unwrap();
assert!(
body.contains("PHASE-09") && body.contains("PHASE-10"),
"both rows present: {body}"
);
}
#[test]
fn record_boundary_refused_under_worker_mode() {
let repo = tempfile::tempdir().unwrap();
let dir = repo.path();
let fx = build_fixture(dir);
let ledger = dir.join(".doctrine/dispatch/064/boundaries.toml");
let holder = tempfile::tempdir().unwrap();
let base = git(dir, &["rev-parse", "HEAD"]);
let linked = holder.path().join("fork");
git(
dir,
&[
"worktree",
"add",
"-b",
"wkr-guard-rb",
linked.to_str().unwrap(),
&base,
],
);
let marker_dir = linked.join(".doctrine/state/dispatch");
std::fs::create_dir_all(&marker_dir).unwrap();
std::fs::write(marker_dir.join("worker"), b"").unwrap();
let out = record_boundary(&linked, dir, "PHASE-09", &fx.base, &fx.code_end_1);
assert!(
!out.status.success(),
"refused from a marked linked worktree"
);
assert!(
stderr(&out).contains("dispatch-record-boundary"),
"refusal names the verb: {}",
stderr(&out)
);
assert!(!ledger.exists(), "refused run records nothing");
let out = run(
dir,
Some(true),
&[
"dispatch",
"record-boundary",
"--slice",
"64",
"--phase",
"PHASE-09",
"--code-start",
&fx.base,
"--code-end",
&fx.code_end_1,
"-p",
dir.to_str().unwrap(),
],
);
assert!(!out.status.success(), "refused when DOCTRINE_WORKER set");
assert!(
stderr(&out).contains("DOCTRINE_WORKER"),
"carries the dual-cause: {}",
stderr(&out)
);
assert!(!ledger.exists(), "still records nothing");
}
fn stdout(out: &Output) -> String {
String::from_utf8_lossy(&out.stdout).trim().to_string()
}
fn show_journal_trunk_oid(cwd: &Path, trunk: &str) -> Output {
run(
cwd,
None,
&[
"dispatch",
"sync",
"--show-journal-trunk-oid",
"--slice",
"64",
"--trunk",
trunk,
"-p",
cwd.to_str().unwrap(),
],
)
}
#[test]
fn show_journal_trunk_oid_returns_committed_planned_oid_from_any_checkout() {
let repo = tempfile::tempdir().unwrap();
let dir = repo.path();
build_fixture(dir);
assert!(prepare_review(dir).status.success());
let phase_tip = git(dir, &["rev-parse", "phase/064-02"]);
assert!(
integrate(dir, &["--trunk", "refs/heads/main"])
.status
.success(),
"trunk integrate journals the trunk row"
);
assert_eq!(git(dir, &["rev-parse", "--abbrev-ref", "HEAD"]), "main");
let out = show_journal_trunk_oid(dir, "refs/heads/main");
assert!(
out.status.success(),
"read surface succeeds; stderr: {}",
stderr(&out)
);
assert_eq!(
stdout(&out),
phase_tip,
"prints the trunk row's full planned_new_oid (== the projected tip)"
);
}
#[test]
fn show_journal_trunk_oid_errors_when_no_trunk_row() {
let repo = tempfile::tempdir().unwrap();
let dir = repo.path();
build_fixture(dir);
assert!(prepare_review(dir).status.success());
let out = show_journal_trunk_oid(dir, "refs/heads/main");
assert!(
!out.status.success(),
"no trunk row in the journal ⇒ refused"
);
assert!(
stderr(&out).contains("show-journal-trunk-oid"),
"refusal names the read surface: {}",
stderr(&out)
);
assert!(
stdout(&out).is_empty(),
"no oid emitted on the error path: {:?}",
stdout(&out)
);
}
#[test]
fn show_journal_trunk_oid_requires_trunk() {
let repo = tempfile::tempdir().unwrap();
let dir = repo.path();
build_fixture(dir);
let out = run(
dir,
None,
&[
"dispatch",
"sync",
"--show-journal-trunk-oid",
"--slice",
"64",
"-p",
dir.to_str().unwrap(),
],
);
assert!(!out.status.success(), "missing --trunk is a parse error");
assert!(
stderr(&out).contains("--trunk"),
"clap names the required --trunk: {}",
stderr(&out)
);
}
fn write_reconcile_slice_64(dir: &Path) {
let body = "id = 64\n\
slug = \"dispatched-slice\"\n\
title = \"Dispatched slice\"\n\
status = \"reconcile\"\n\
created = \"2026-06-20\"\n\
updated = \"2026-06-20\"\n\
\n[relationships]\nneeds = []\nafter = []\n";
let path = dir.join(".doctrine/slice/064/slice-064.toml");
std::fs::create_dir_all(path.parent().unwrap()).unwrap();
std::fs::write(&path, body).unwrap();
}
fn slice_status_done(dir: &Path) -> Output {
run(
dir,
None,
&["slice", "status", "64", "done", "-p", dir.to_str().unwrap()],
)
}
#[test]
fn vt7_close_integration_succeeds_after_real_trunk_integrate() {
let repo = tempfile::tempdir().unwrap();
let dir = repo.path();
build_fixture(dir);
assert!(prepare_review(dir).status.success());
let out = integrate(dir, &["--trunk", "refs/heads/main"]);
assert!(
out.status.success(),
"trunk integrate; stderr: {}",
stderr(&out)
);
write_reconcile_slice_64(dir);
let out = slice_status_done(dir);
assert!(
out.status.success(),
"integrated slice closes; stderr: {}",
stderr(&out)
);
let toml = std::fs::read_to_string(dir.join(".doctrine/slice/064/slice-064.toml")).unwrap();
assert!(
toml.contains("status = \"done\""),
"close wrote the terminal status: {toml}"
);
}
#[test]
fn vt7_close_integration_refused_without_trunk_integrate() {
let repo = tempfile::tempdir().unwrap();
let dir = repo.path();
build_fixture(dir);
assert!(prepare_review(dir).status.success());
assert!(integrate(dir, &[]).status.success());
write_reconcile_slice_64(dir);
let out = slice_status_done(dir);
assert!(
!out.status.success(),
"an unintegrated dispatched slice is refused close"
);
assert!(
stderr(&out).contains("not integrated to trunk")
&& stderr(&out).contains("integrate --trunk never completed"),
"refusal names the no-trunk-row anomaly: {}",
stderr(&out)
);
let toml = std::fs::read_to_string(dir.join(".doctrine/slice/064/slice-064.toml")).unwrap();
assert!(
toml.contains("status = \"reconcile\""),
"no write on refusal: {toml}"
);
}