use std::path::{Path, PathBuf};
use tempfile::TempDir;
use trusty_mpm::core::disk_usage_guard::{DiskGate, MeasuredMount};
use trusty_mpm::daemon::managed_routes::inproject;
use trusty_mpm::project::{Project, ProjectRegistry};
use trusty_mpm::session_manager::ManagedSessionId;
use super::{
LaunchDir, ManagedWorkspace, provision_for_fallback, provision_for_launch,
provision_for_launch_gated,
};
fn empty_disk() -> DiskGate {
DiskGate::Pinned(Some(MeasuredMount {
mount_point: "/fixture-mount".to_string(),
usage_pct: 0.0,
bytes: None,
}))
}
fn full_disk() -> DiskGate {
DiskGate::Pinned(Some(MeasuredMount {
mount_point: "/fixture-mount".to_string(),
usage_pct: 100.0,
bytes: None,
}))
}
struct ReposRootGuard {
prev: Option<std::ffi::OsString>,
}
impl ReposRootGuard {
fn set(path: &Path) -> Self {
let prev = std::env::var_os(inproject::REPOS_ROOT_ENV);
unsafe { std::env::set_var(inproject::REPOS_ROOT_ENV, path) };
Self { prev }
}
}
impl Drop for ReposRootGuard {
fn drop(&mut self) {
match self.prev.take() {
Some(v) => unsafe { std::env::set_var(inproject::REPOS_ROOT_ENV, v) },
None => unsafe { std::env::remove_var(inproject::REPOS_ROOT_ENV) },
}
}
}
async fn registry_with(repo_url: &str, worktree: Option<bool>) -> TempDir {
let dir = tempfile::tempdir().unwrap();
let registry = ProjectRegistry::load(dir.path()).await.unwrap();
registry
.register(Project {
name: "fixture".into(),
repo_url: repo_url.into(),
default_branch: "main".into(),
stack_hint: None,
tags: vec![],
description: None,
gh_user: None,
gh_account: None,
github: None,
commit_name: None,
commit_email: None,
worktree,
})
.await
.unwrap();
assert!(
dir.path().join("projects.json").is_file(),
"fixture precondition: projects.json must exist at {}",
dir.path().display()
);
dir
}
fn init_git_repo(path: &Path) {
std::fs::create_dir_all(path).unwrap();
let run = |args: &[&str]| {
let status = std::process::Command::new("git")
.args(args)
.current_dir(path)
.status()
.unwrap_or_else(|e| panic!("git {args:?} failed to spawn: {e}"));
assert!(
status.success(),
"git {args:?} failed in {}",
path.display()
);
};
run(&["init"]);
run(&["config", "user.email", "ci@test.invalid"]);
run(&["config", "user.name", "CI"]);
run(&["commit", "--allow-empty", "-m", "init"]);
}
fn assert_nothing_provisioned(base: &Path) {
assert!(
!base.exists(),
"#4300: base clone dir must NOT be created for an opted-out project: {}",
base.display()
);
assert!(
!base.join(".git").exists(),
"#4300: base clone must NOT be initialised: {}",
base.join(".git").display()
);
assert!(
!base.join(".worktrees").exists(),
"#4300: worktree dir must NOT be created: {}",
base.join(".worktrees").display()
);
}
fn expected_worktree(base: &Path, session_id: &ManagedSessionId) -> PathBuf {
base.join(".worktrees").join(session_id.to_string())
}
#[tokio::test]
async fn provision_for_launch_ignores_a_registered_worktree_true_project() {
let origin = "https://github.invalid/fixture-owner/isolated-repo";
let _registry = registry_with(
"https://github.invalid/fixture-owner/isolated-repo",
Some(true),
)
.await;
let repos_root = tempfile::tempdir().unwrap();
let base = repos_root
.path()
.join("fixture-owner")
.join("isolated-repo");
init_git_repo(&base);
let live = tempfile::tempdir().unwrap();
let session_id = ManagedSessionId::new();
let workspace = provision_for_launch(
origin,
&base,
live.path(),
false,
LaunchDir::OperatorCwd,
&session_id,
None, )
.await
.unwrap();
assert_eq!(
workspace,
ManagedWorkspace::MainCheckout(base.clone()),
"#5274 row 1: a `worktree: true` project must not relocate the session \
into a WORKTREE; ADR-0037 (2026-08-17): the main checkout it runs in is \
the managed one, not the unmanaged directory `tm` was typed in"
);
assert!(
!expected_worktree(&base, &session_id).exists(),
"#5274: no per-session worktree may be created without an explicit request: {}",
expected_worktree(&base, &session_id).display()
);
}
#[tokio::test]
async fn provision_for_launch_without_a_request_uses_the_main_checkout() {
let origin = "git@github.invalid:fixture-owner/optout-repo.git";
let _registry = registry_with(
"https://github.invalid/fixture-owner/optout-repo",
Some(false),
)
.await;
let repos_root = tempfile::tempdir().unwrap();
let base = repos_root
.path()
.canonicalize()
.unwrap()
.join("fixture-owner")
.join("optout-repo");
init_git_repo(&base);
let session_id = ManagedSessionId::new();
let workspace = provision_for_launch(
origin,
&base,
&base,
false,
LaunchDir::OperatorCwd,
&session_id,
None, )
.await
.expect(
"no worktree request must not attempt a clone at all — a clone error \
here means the request was never consulted",
);
assert_eq!(
workspace,
ManagedWorkspace::MainCheckout(base.clone()),
"#5274 row 2: a session with no worktree request runs in the managed checkout"
);
assert!(
!expected_worktree(&base, &session_id).exists(),
"#4300: the per-session worktree must NOT exist"
);
}
#[tokio::test]
async fn provision_for_launch_redirects_an_unmanaged_launch_to_the_managed_checkout() {
let origin = "https://github.invalid/fixture-owner/optout-repo";
let repos_root = tempfile::tempdir().unwrap();
let base = repos_root.path().join("fixture-owner").join("optout-repo");
init_git_repo(&base);
let unmanaged = tempfile::tempdir().unwrap();
init_git_repo(unmanaged.path());
let session_id = ManagedSessionId::new();
let workspace = provision_for_launch(
origin,
&base,
unmanaged.path(),
false,
LaunchDir::OperatorCwd,
&session_id,
None, )
.await
.expect("the managed checkout already exists, so the redirect reuses it");
assert_eq!(
workspace,
ManagedWorkspace::MainCheckout(base.clone()),
"an unmanaged launch directory must switch to the managed checkout"
);
assert!(
!unmanaged.path().join(".claude").exists(),
"the unmanaged launch directory must never be written to: {}",
unmanaged.path().display()
);
}
#[tokio::test]
async fn provision_for_launch_explicit_request_creates_worktree() {
let origin = "https://github.invalid/fixture-owner/isolated-repo";
let repos_root = tempfile::tempdir().unwrap();
let base = repos_root
.path()
.join("fixture-owner")
.join("isolated-repo");
init_git_repo(&base);
let live = tempfile::tempdir().unwrap();
let session_id = ManagedSessionId::new();
let workspace = provision_for_launch_gated(
origin,
&base,
live.path(),
true,
LaunchDir::OperatorCwd,
&session_id,
&empty_disk(),
Some(tempfile::tempdir().unwrap().path()), )
.await
.unwrap();
let expected = expected_worktree(&base, &session_id);
assert_eq!(
workspace,
ManagedWorkspace::Worktree(expected.clone()),
"#5274 row 3: an explicit `--worktree` request must provision one"
);
assert!(
expected.is_dir(),
"the worktree directory must exist at {}",
expected.display()
);
}
#[tokio::test]
async fn provision_for_launch_from_subdirectory_targets_repo_root() {
let origin = "https://github.invalid/fixture-owner/optout-repo.git";
let repos_root = tempfile::tempdir().unwrap();
let base = repos_root
.path()
.canonicalize()
.unwrap()
.join("fixture-owner")
.join("optout-repo");
init_git_repo(&base);
let subdir = base.join("crates").join("inner");
std::fs::create_dir_all(&subdir).unwrap();
let session_id = ManagedSessionId::new();
let workspace = provision_for_launch(
origin,
&base,
&subdir,
false,
LaunchDir::OperatorCwd,
&session_id,
None, )
.await
.expect("no worktree request must not attempt a clone at all");
assert_eq!(
workspace,
ManagedWorkspace::MainCheckout(base.clone()),
"`tm launch` from a subdirectory must target the repo ROOT"
);
assert_ne!(
workspace.path(),
subdir.as_path(),
"the subdirectory must NEVER become the deploy target"
);
assert!(
!subdir.join(".claude").exists(),
"nothing may be provisioned inside the subdirectory: {}",
subdir.display()
);
assert!(
!expected_worktree(&base, &session_id).exists(),
"no worktree may be created without an explicit request"
);
}
#[tokio::test]
async fn provision_for_launch_keeps_a_caller_resolved_placement() {
let origin = "https://github.invalid/fixture-owner/resolved-repo";
let repos_root = tempfile::tempdir().unwrap();
let base = repos_root
.path()
.canonicalize()
.unwrap()
.join("fixture-owner")
.join("resolved-repo");
let resolved = tempfile::tempdir().unwrap();
let resolved_root = resolved.path().canonicalize().unwrap();
init_git_repo(&resolved_root);
let session_id = ManagedSessionId::new();
let workspace = provision_for_launch(
origin,
&base,
&resolved_root,
false,
LaunchDir::CallerResolved,
&session_id,
None, )
.await
.expect("a caller-resolved placement must not attempt a clone at all");
assert_eq!(
workspace,
ManagedWorkspace::MainCheckout(resolved_root.clone()),
"the caller's placement must survive `provision_for_launch`"
);
assert_nothing_provisioned(&base);
}
#[tokio::test]
#[serial_test::serial]
async fn provision_for_fallback_opted_out_creates_no_clone_and_no_worktree() {
let origin = "https://github.invalid/fixture-owner/fallback-repo.git";
let registry = registry_with(
"https://github.invalid/fixture-owner/fallback-repo",
Some(false),
)
.await;
let repos_root = tempfile::tempdir().unwrap();
let _guard = ReposRootGuard::set(repos_root.path());
let git_root = tempfile::tempdir().unwrap();
let session_id = ManagedSessionId::new();
let workspace = provision_for_fallback(
registry.path(),
origin,
git_root.path(),
&session_id,
&empty_disk(),
Some(tempfile::tempdir().unwrap().path()), )
.await
.expect(
"#4300: an opted-out project must not attempt a clone at all — a clone \
error here means the opt-out was never consulted",
);
assert_eq!(
workspace,
ManagedWorkspace::MainCheckout(git_root.path().to_path_buf()),
"#4300: the daemon-unreachable fallback must launch in the repo root"
);
assert_nothing_provisioned(
&repos_root
.path()
.join("fixture-owner")
.join("fallback-repo"),
);
assert_eq!(
std::fs::read_dir(repos_root.path()).unwrap().count(),
0,
"#4300: the managed repos root must stay empty for an opted-out project"
);
}
#[tokio::test]
#[serial_test::serial]
async fn provision_for_fallback_unset_creates_worktree_not_live_checkout() {
let origin = "https://github.invalid/fixture-owner/fallback-repo.git";
let registry = registry_with("https://github.invalid/fixture-owner/fallback-repo", None).await;
let repos_root = tempfile::tempdir().unwrap();
let _guard = ReposRootGuard::set(repos_root.path());
let base = repos_root
.path()
.join("fixture-owner")
.join("fallback-repo");
init_git_repo(&base);
let git_root = tempfile::tempdir().unwrap();
let session_id = ManagedSessionId::new();
let workspace = provision_for_fallback(
registry.path(),
origin,
git_root.path(),
&session_id,
&empty_disk(),
Some(tempfile::tempdir().unwrap().path()), )
.await
.unwrap();
let expected = expected_worktree(&base, &session_id);
assert_eq!(
workspace,
ManagedWorkspace::Worktree(expected.clone()),
"an unset `worktree` key must still redirect to a protected worktree"
);
assert!(
expected.is_dir(),
"worktree must exist at {}",
expected.display()
);
assert!(
!git_root.path().join(".worktrees").exists(),
"#1724: nothing may be provisioned inside the live checkout"
);
}
#[tokio::test]
#[serial_test::serial]
async fn provision_for_fallback_other_projects_optout_does_not_leak() {
let registry = registry_with(
"https://github.invalid/fixture-owner/optout-repo",
Some(false),
)
.await;
let repos_root = tempfile::tempdir().unwrap();
let _guard = ReposRootGuard::set(repos_root.path());
let base = repos_root
.path()
.join("fixture-owner")
.join("fallback-repo");
init_git_repo(&base);
let git_root = tempfile::tempdir().unwrap();
let session_id = ManagedSessionId::new();
let workspace = provision_for_fallback(
registry.path(),
"https://github.invalid/fixture-owner/fallback-repo.git",
git_root.path(),
&session_id,
&empty_disk(),
Some(tempfile::tempdir().unwrap().path()), )
.await
.unwrap();
let expected = expected_worktree(&base, &session_id);
assert_eq!(
workspace,
ManagedWorkspace::Worktree(expected.clone()),
"an unrelated project's opt-out must not disable isolation here"
);
assert!(
expected.is_dir(),
"worktree must exist at {}",
expected.display()
);
}
#[tokio::test]
async fn a_pinned_over_threshold_gate_refuses_a_launch_worktree() {
let origin = "https://github.invalid/fixture-owner/isolated-repo";
let repos_root = tempfile::tempdir().unwrap();
let base = repos_root
.path()
.join("fixture-owner")
.join("isolated-repo");
init_git_repo(&base);
let live = tempfile::tempdir().unwrap();
let session_id = ManagedSessionId::new();
let err = provision_for_launch_gated(
origin,
&base,
live.path(),
true,
LaunchDir::OperatorCwd,
&session_id,
&full_disk(),
Some(tempfile::tempdir().unwrap().path()), )
.await
.expect_err("a pinned 100% mount is at or above every accepted threshold");
let message = err.to_string();
assert!(
message.contains("refusing to create a worktree")
&& message.contains("/fixture-mount")
&& message.contains("disk.max_usage_pct"),
"#7603: the refusal must be the disk gate's own, naming the PINNED \
mount — got: {message}"
);
assert!(
!expected_worktree(&base, &session_id).exists(),
"#7497: a refused gate must create nothing"
);
}
#[tokio::test]
async fn a_launch_worktree_is_refused_when_the_ledger_cannot_record() {
let origin = "https://github.invalid/fixture-owner/isolated-repo";
let repos_root = tempfile::tempdir().unwrap();
let base = repos_root
.path()
.join("fixture-owner")
.join("isolated-repo");
init_git_repo(&base);
let live = tempfile::tempdir().unwrap();
let session_id = ManagedSessionId::new();
let home = tempfile::tempdir().unwrap();
let ledger = trusty_mpm::core::worktree_ledger::WorktreeLedger::under_home(home.path());
std::fs::create_dir_all(ledger.path()).unwrap();
let err = provision_for_launch_gated(
origin,
&base,
live.path(),
true,
LaunchDir::OperatorCwd,
&session_id,
&empty_disk(),
Some(home.path()),
)
.await
.expect_err("an unwritable ledger must refuse the launch worktree");
assert!(err.to_string().contains("worktree ledger"), "{err}");
assert!(
!expected_worktree(&base, &session_id).exists(),
"#8994: nothing may be created when the ledger cannot record it"
);
}
#[tokio::test]
async fn a_launch_worktree_is_recorded_in_the_ledger() {
use trusty_mpm::core::worktree_ledger::{Origin, WorktreeLedger, fold::fold, ledger_key};
let origin = "https://github.invalid/fixture-owner/isolated-repo";
let repos_root = tempfile::tempdir().unwrap();
let base = repos_root
.path()
.join("fixture-owner")
.join("isolated-repo");
init_git_repo(&base);
let live = tempfile::tempdir().unwrap();
let session_id = ManagedSessionId::new();
let home = tempfile::tempdir().unwrap();
provision_for_launch_gated(
origin,
&base,
live.path(),
true,
LaunchDir::OperatorCwd,
&session_id,
&empty_disk(),
Some(home.path()),
)
.await
.expect("provision");
let state = fold(
&WorktreeLedger::under_home(home.path())
.read()
.unwrap()
.events,
);
let entry = &state.live[&ledger_key(&expected_worktree(&base, &session_id))];
assert_eq!(entry.origin, Origin::TmCli);
assert_eq!(
entry.session.as_deref(),
Some(session_id.to_string().as_str())
);
}
#[tokio::test]
#[serial_test::serial]
async fn a_pinned_over_threshold_gate_refuses_a_fallback_worktree() {
let origin = "https://github.invalid/fixture-owner/fallback-repo.git";
let registry = registry_with("https://github.invalid/fixture-owner/fallback-repo", None).await;
let repos_root = tempfile::tempdir().unwrap();
let _guard = ReposRootGuard::set(repos_root.path());
let base = repos_root
.path()
.join("fixture-owner")
.join("fallback-repo");
init_git_repo(&base);
let git_root = tempfile::tempdir().unwrap();
let session_id = ManagedSessionId::new();
let err = provision_for_fallback(
registry.path(),
origin,
git_root.path(),
&session_id,
&full_disk(),
Some(tempfile::tempdir().unwrap().path()), )
.await
.expect_err("a pinned 100% mount must refuse on the fallback path too");
assert!(
err.to_string().contains("refusing to create a worktree"),
"#7603: expected the disk gate's refusal, got: {err}"
);
assert!(
!expected_worktree(&base, &session_id).exists(),
"#7497: a refused gate must create nothing"
);
assert!(
!git_root.path().join(".worktrees").exists(),
"#1724: the live checkout must stay untouched on a refusal too"
);
}
#[tokio::test]
async fn a_pinned_gate_keeps_a_launch_worktree_off_the_hosts_disk() {
let origin = "https://github.invalid/fixture-owner/isolated-repo";
let repos_root = tempfile::tempdir().unwrap();
let base = repos_root
.path()
.join("fixture-owner")
.join("isolated-repo");
init_git_repo(&base);
let live = tempfile::tempdir().unwrap();
let session_id = ManagedSessionId::new();
let workspace = provision_for_launch_gated(
origin,
&base,
live.path(),
true,
LaunchDir::OperatorCwd,
&session_id,
&empty_disk(),
Some(tempfile::tempdir().unwrap().path()), )
.await
.expect(
"#7603: a pinned 0% measurement must decide this call, whatever the \
host's volume and the operator's disk.max_usage_pct say",
);
assert_eq!(
workspace,
ManagedWorkspace::Worktree(expected_worktree(&base, &session_id)),
"the pinned measurement must be the one the gate applied"
);
}