use super::*;
use crate::core::agent::{Delegation, DelegationStatus};
use crate::core::paths::FrameworkPaths;
fn hermetic() -> (Arc<DaemonState>, tempfile::TempDir, SessionId) {
let dir = tempfile::tempdir().expect("temp dir");
let paths = FrameworkPaths::under(dir.path());
let state = Arc::new(DaemonState::with_paths(&paths));
(state, dir, SessionId(uuid::Uuid::new_v4()))
}
fn insert(
state: &DaemonState,
session: SessionId,
agent: &str,
cwd: &str,
isolation: Option<&str>,
tool_use_id: Option<&str>,
status: DelegationStatus,
) {
let mut d = Delegation::observed(session, agent, "task", tool_use_id.map(str::to_string));
d.cwd = Some(PathBuf::from(cwd));
d.isolation = isolation.map(str::to_string);
d.status = status;
state.upsert_delegation(d);
}
fn dispatch(
cwd: &str,
agent: &str,
isolation: Option<&str>,
tool_use_id: Option<&str>,
) -> SharedTreeDispatchRequest {
let mut input = serde_json::json!({"subagent_type": agent, "description": "go"});
if let Some(i) = isolation {
input["isolation"] = Value::String(i.to_string());
}
let mut payload = serde_json::json!({"cwd": cwd, "tool": "Agent", "input": input});
if let Some(id) = tool_use_id {
payload["tool_use_id"] = Value::String(id.to_string());
}
SharedTreeDispatchRequest { payload }
}
async fn call(
state: &Arc<DaemonState>,
session: SessionId,
req: SharedTreeDispatchRequest,
) -> SharedTreeWritersResponse {
let Json(body) =
shared_tree_dispatch_route(State(state.clone()), Path(session.0.to_string()), Json(req))
.await
.expect("route succeeds");
body
}
async fn granted_call(
state: &Arc<DaemonState>,
session: SessionId,
req: SharedTreeDispatchRequest,
) -> SharedTreeWritersResponse {
let Json(body) =
granted_worktree_route(State(state.clone()), Path(session.0.to_string()), Json(req))
.await
.expect("route succeeds");
body
}
fn unisolated_hook_payload(tool_use_id: &str) -> Value {
serde_json::json!({
"cwd": "/repo",
"tool": "Agent",
"tool_use_id": tool_use_id,
"input": {"subagent_type": "rust-engineer", "description": "go"},
})
}
fn commit_query(cwd: &str) -> SharedTreeDispatchRequest {
SharedTreeDispatchRequest {
payload: serde_json::json!({
"cwd": cwd,
"tool": "Bash",
"input": {"command": "git commit -m 'docs: snapshot'"},
}),
}
}
#[tokio::test]
async fn the_commit_query_and_the_dispatch_query_disagree_on_one_record() {
let (state, _dir, session) = hermetic();
insert(
&state,
session,
"rust-engineer",
"/repo",
None,
Some("toolu_lost"),
DelegationStatus::Running,
);
let id = state.delegations_for(session)[0].id;
state.mutate_delegation(id, |d| {
d.started_at = Some(chrono::Utc::now());
d.status = DelegationStatus::Stale;
d.stale_by_agent_type = true;
});
let commit = call(&state, session, commit_query("/repo")).await;
assert!(
commit.agents.is_empty(),
"a documents-only commit must not be denied by a type-reconciled record: {:?}",
commit.agents
);
assert_eq!(commit.total, 0);
assert!(!commit.claimed, "a Bash query never claims the tree");
let disp = call(
&state,
session,
dispatch("/repo", "rust-engineer", None, Some("toolu_new")),
)
.await;
assert_eq!(
disp.agents.len(),
1,
"a second file-mutating dispatch must still see the occupant: {:?}",
disp.agents
);
assert_eq!(disp.agents[0].agent, "rust-engineer");
assert!(
!disp.claimed,
"and must not be admitted onto that agent's HEAD"
);
}
#[tokio::test]
async fn a_live_writer_is_reported_to_both_questions() {
let (state, _dir, session) = hermetic();
insert(
&state,
SessionId(uuid::Uuid::new_v4()),
"rust-engineer",
"/repo",
None,
Some("toolu_live"),
DelegationStatus::Running,
);
let commit = call(&state, session, commit_query("/repo")).await;
assert_eq!(
commit.agents.len(),
1,
"a live writer must still deny a documents-only commit (ADR-0049)"
);
let disp = call(
&state,
session,
dispatch("/repo", "rust-engineer", None, Some("toolu_new")),
)
.await;
assert_eq!(disp.agents.len(), 1);
assert!(!disp.claimed);
}
#[tokio::test]
async fn a_grant_and_the_tracker_converge_in_either_order() {
for guard_first in [true, false] {
let (state, _dir, session) = hermetic();
let original = unisolated_hook_payload("toolu_grant");
let granted = dispatch(
"/repo",
"rust-engineer",
Some("worktree"),
Some("toolu_grant"),
);
if guard_first {
let body = granted_call(&state, session, granted).await;
assert!(body.claimed, "an empty checkout must be claimed");
crate::daemon::services::delegation_tracker::observe(
&state,
session,
HookEvent::PreToolUse,
&original,
);
} else {
crate::daemon::services::delegation_tracker::observe(
&state,
session,
HookEvent::PreToolUse,
&original,
);
assert_eq!(state.delegations_for(session)[0].isolation, None);
let body = granted_call(&state, session, granted).await;
assert!(
body.claimed,
"the caller's own record is excluded, so the checkout is still free"
);
}
let records = state.delegations_for(session);
assert_eq!(
records.len(),
1,
"guard_first={guard_first}: one dispatch must leave one record, got {records:?}"
);
assert_eq!(
records[0].isolation.as_deref(),
Some("worktree"),
"guard_first={guard_first}: the granted isolation must survive both orders"
);
assert!(
state
.live_shared_tree_writers(&PathBuf::from("/repo"), None)
.is_empty(),
"guard_first={guard_first}: a granted writer must not be named as writing here"
);
}
}
#[test]
fn a_grant_and_the_tracker_race_without_losing_the_isolation() {
const ROUNDS: usize = 300;
let rt = tokio::runtime::Builder::new_current_thread()
.build()
.expect("runtime");
for round in 0..ROUNDS {
let (state, _dir, session) = hermetic();
let payload = unisolated_hook_payload("toolu_race");
let barrier = std::sync::Barrier::new(2);
std::thread::scope(|scope| {
scope.spawn(|| {
barrier.wait();
crate::daemon::services::delegation_tracker::observe(
&state,
session,
HookEvent::PreToolUse,
&payload,
);
});
barrier.wait();
rt.block_on(granted_call(
&state,
session,
dispatch(
"/repo",
"rust-engineer",
Some("worktree"),
Some("toolu_race"),
),
));
});
let records = state.delegations_for(session);
assert_eq!(
records.len(),
1,
"round {round}: one dispatch must leave one record, got {records:?}"
);
assert_eq!(
records[0].isolation.as_deref(),
Some("worktree"),
"round {round}: the grant must not be lost to the tracker's insert"
);
}
}
#[tokio::test]
async fn granted_worktree_route_records_nothing_without_isolation_or_a_tool_use_id() {
for req in [
dispatch("/repo", "rust-engineer", Some("worktree"), None),
dispatch("/repo", "rust-engineer", None, Some("toolu_x")),
dispatch("/repo", "rust-engineer", Some("nonsense"), Some("toolu_x")),
] {
let (state, _dir, session) = hermetic();
let body = granted_call(&state, session, req).await;
assert!(!body.claimed);
assert!(state.delegations_for(session).is_empty());
}
}
#[tokio::test]
async fn record_granted_isolation_refuses_a_non_separating_mode() {
let (state, _dir, session) = hermetic();
insert(
&state,
session,
"rust-engineer",
"/repo",
Some("worktree"),
Some("toolu_x"),
DelegationStatus::Running,
);
for mode in ["inline", "Worktree", "worktrees", ""] {
let payload = serde_json::json!({
"cwd": "/repo",
"tool": "Agent",
"tool_use_id": "toolu_x",
"input": {"subagent_type": "rust-engineer", "isolation": mode},
});
assert!(
!crate::daemon::services::delegation_tracker::record_granted_isolation(
&state, session, &payload
),
"{mode:?} does not separate the working tree and must not be recorded"
);
}
assert_eq!(
state.delegations_for(session)[0].isolation.as_deref(),
Some("worktree"),
"a correct record must survive every refused call"
);
}
#[tokio::test]
async fn granted_worktree_route_reports_a_live_writer_without_claiming() {
let (state, _dir, session) = hermetic();
insert(
&state,
session,
"python-engineer",
"/repo",
None,
Some("toolu_first"),
DelegationStatus::Running,
);
let body = granted_call(
&state,
session,
dispatch(
"/repo",
"rust-engineer",
Some("worktree"),
Some("toolu_second"),
),
)
.await;
assert_eq!(body.total, 1);
assert_eq!(body.agents[0].agent, "python-engineer");
assert!(!body.claimed, "a denied dispatch must claim nothing");
let all = state.delegations_for(session);
assert_eq!(all.len(), 2, "one occupant plus one tombstone: {all:?}");
let denied = all
.iter()
.find(|d| d.tool_use_id.as_deref() == Some("toolu_second"))
.expect("the denied dispatch has a record");
assert_eq!(denied.status, DelegationStatus::Cancelled);
let live: Vec<_> = all.iter().filter(|d| d.status.is_live()).collect();
assert_eq!(live.len(), 1);
assert_eq!(live[0].agent, "python-engineer");
}
#[tokio::test]
async fn shared_tree_dispatch_route_reports_live_unisolated_writers() {
let (state, _dir, session) = hermetic();
insert(
&state,
session,
"rust-engineer",
"/repo",
None,
Some("toolu_A"),
DelegationStatus::Running,
);
insert(
&state,
session,
"rust-engineer",
"/repo",
Some("worktree"),
Some("toolu_B"),
DelegationStatus::Running,
);
insert(
&state,
session,
"research",
"/repo",
None,
Some("toolu_C"),
DelegationStatus::Running,
);
insert(
&state,
session,
"python-engineer",
"/repo",
None,
Some("toolu_D"),
DelegationStatus::Completed,
);
insert(
&state,
session,
"python-engineer",
"/repo",
None,
Some("toolu_E"),
DelegationStatus::Stale,
);
insert(
&state,
session,
"python-engineer",
"/elsewhere",
None,
Some("toolu_F"),
DelegationStatus::Running,
);
insert(
&state,
SessionId(uuid::Uuid::new_v4()),
"python-engineer",
"/repo",
None,
Some("toolu_G"),
DelegationStatus::Running,
);
let body = call(
&state,
session,
dispatch("/repo", "rust-engineer", None, Some("toolu_NEW")),
)
.await;
assert_eq!(
body.total, 2,
"the live unisolated engineers in this directory, whichever session dispatched them"
);
assert_eq!(body.agents.len(), 2, "{:?}", body.agents);
assert_eq!(body.agents[0].agent, "python-engineer");
assert_eq!(body.agents[1].agent, "rust-engineer");
assert!(!body.claimed, "a denied dispatch must not claim the tree");
}
#[tokio::test]
async fn shared_tree_dispatch_route_excludes_the_callers_own_dispatch() {
let (state, _dir, session) = hermetic();
insert(
&state,
session,
"rust-engineer",
"/repo",
None,
Some("toolu_SELF"),
DelegationStatus::Running,
);
let body = call(
&state,
session,
dispatch("/repo", "rust-engineer", None, Some("toolu_SELF")),
)
.await;
assert_eq!(body.total, 0, "the caller must not find itself");
assert_eq!(state.delegations_for(session).len(), 1);
}
#[tokio::test]
async fn shared_tree_dispatch_route_counts_concurrent_same_agent_dispatches() {
let (state, _dir, session) = hermetic();
for id in ["toolu_1", "toolu_2"] {
insert(
&state,
session,
"rust-engineer",
"/repo",
None,
Some(id),
DelegationStatus::Running,
);
}
let body = call(
&state,
session,
dispatch("/repo", "rust-engineer", None, Some("toolu_3")),
)
.await;
assert_eq!(body.total, 2);
assert_eq!(body.agents.len(), 1, "one row per distinct name");
assert_eq!(body.agents[0].count, 2);
}
#[tokio::test]
async fn shared_tree_dispatch_route_is_empty_for_an_unknown_session() {
let (state, _dir, _session) = hermetic();
let Json(body) = shared_tree_dispatch_route(
State(state),
Path(uuid::Uuid::new_v4().to_string()),
Json(dispatch("/repo", "rust-engineer", None, Some("toolu_X"))),
)
.await
.expect("route succeeds");
assert_eq!(body.total, 0);
}
#[tokio::test]
async fn shared_tree_dispatch_route_rejects_a_malformed_session_id() {
let (state, _dir, _session) = hermetic();
let err = shared_tree_dispatch_route(
State(state),
Path("not-a-uuid".to_string()),
Json(dispatch("/repo", "rust-engineer", None, Some("toolu_X"))),
)
.await
.expect_err("a malformed id is a client error");
assert!(matches!(err, DaemonError::InvalidRequest(_)));
}
#[tokio::test]
async fn shared_tree_dispatch_route_denies_the_second_claim() {
let (state, _dir, session) = hermetic();
let first = call(
&state,
session,
dispatch("/repo", "rust-engineer", None, Some("toolu_A")),
)
.await;
assert_eq!(first.total, 0, "the first dispatch must be admitted");
assert!(first.claimed, "and it must take the directory");
let second = call(
&state,
session,
dispatch("/repo", "python-engineer", None, Some("toolu_B")),
)
.await;
assert_eq!(second.total, 1, "the second must find the first");
assert_eq!(second.agents[0].agent, "rust-engineer");
assert!(!second.claimed);
}
#[tokio::test]
async fn shared_tree_dispatch_route_reserves_the_tree_on_an_empty_answer() {
let (state, _dir, session) = hermetic();
call(
&state,
session,
dispatch("/repo", "rust-engineer", None, Some("toolu_A")),
)
.await;
let records = state.delegations_for(session);
assert_eq!(records.len(), 1);
assert_eq!(records[0].agent, "rust-engineer");
assert_eq!(records[0].tool_use_id.as_deref(), Some("toolu_A"));
assert_eq!(
records[0].cwd.as_deref(),
Some(PathBuf::from("/repo").as_path())
);
assert_eq!(records[0].status, DelegationStatus::Running);
}
#[tokio::test]
async fn shared_tree_dispatch_route_does_not_reserve_when_it_denies() {
let (state, _dir, session) = hermetic();
insert(
&state,
session,
"rust-engineer",
"/repo",
None,
Some("toolu_A"),
DelegationStatus::Running,
);
let body = call(
&state,
session,
dispatch("/repo", "python-engineer", None, Some("toolu_B")),
)
.await;
assert!(!body.claimed);
assert_eq!(
state.shared_tree_occupants(std::path::Path::new("/repo"), None),
vec!["rust-engineer".to_string()],
"the denied dispatch must not occupy the directory"
);
let records = state.delegations_for(session);
assert_eq!(
records.len(),
2,
"the live writer plus exactly one tombstone: {records:?}"
);
let denied = records
.into_iter()
.find(|d| d.tool_use_id.as_deref() == Some("toolu_B"))
.expect("the deny records a tombstone so a late observation cannot revive it");
assert_eq!(
denied.status,
DelegationStatus::Cancelled,
"and that tombstone must be terminal"
);
}
#[tokio::test]
async fn shared_tree_dispatch_route_does_not_reserve_a_read_only_agent() {
for (agent, isolation) in [
("research", None),
("code-critic", None),
("rust-engineer", Some("worktree")),
("rust-engineer", Some("remote")),
("some-project-agent", None),
] {
let (state, _dir, session) = hermetic();
let body = call(
&state,
session,
dispatch("/repo", agent, isolation, Some("toolu_A")),
)
.await;
assert!(
!body.claimed,
"{agent}/{isolation:?} must not claim the tree"
);
assert!(state.delegations_for(session).is_empty());
}
}
#[tokio::test]
async fn shared_tree_dispatch_route_does_not_reserve_a_non_dispatch_tool() {
let (state, _dir, session) = hermetic();
let mut req = dispatch("/repo", "rust-engineer", None, Some("toolu_A"));
req.payload["tool"] = Value::String("Read".into());
let body = call(&state, session, req).await;
assert!(!body.claimed);
assert!(state.delegations_for(session).is_empty());
}
#[tokio::test]
async fn shared_tree_dispatch_route_answers_a_bash_query_without_claiming() {
let (state, _dir, session) = hermetic();
let stranger = SessionId(uuid::Uuid::new_v4());
insert(
&state,
stranger,
"rust-engineer",
"/repo",
None,
Some("toolu_A"),
DelegationStatus::Running,
);
let req = SharedTreeDispatchRequest {
payload: serde_json::json!({
"cwd": "/repo",
"tool": "Bash",
"input": {},
"tool_use_id": "toolu_pull",
}),
};
let body = call(&state, session, req).await;
assert_eq!(body.total, 1, "the live writer must be reported");
assert_eq!(body.agents[0].agent, "rust-engineer");
assert!(!body.claimed, "a Bash query must never claim the tree");
assert!(
state.delegations_for(session).is_empty(),
"a Bash query must add no record for the session that asked"
);
assert_eq!(
state.delegations_for(stranger).len(),
1,
"and must leave the writer's own record exactly as it found it"
);
}
#[tokio::test]
async fn shared_tree_dispatch_route_is_empty_without_a_cwd() {
let (state, _dir, session) = hermetic();
insert(
&state,
session,
"rust-engineer",
"/repo",
None,
Some("toolu_A"),
DelegationStatus::Running,
);
let mut req = dispatch("/repo", "rust-engineer", None, Some("toolu_B"));
req.payload["cwd"] = Value::String(String::new());
let body = call(&state, session, req).await;
assert_eq!(body.total, 0);
assert!(!body.claimed);
assert_eq!(state.delegations_for(session).len(), 1);
}
#[tokio::test]
async fn shared_tree_writers_span_sessions_in_one_checkout() {
let (state, _dir, session_a) = hermetic();
let session_b = SessionId(uuid::Uuid::new_v4());
let session_c = SessionId(uuid::Uuid::new_v4());
insert(
&state,
session_a,
"rust-engineer",
"/repo/mcp-services",
None,
Some("toolu_A"),
DelegationStatus::Running,
);
let body = call(
&state,
session_b,
dispatch(
"/repo/mcp-services",
"python-engineer",
None,
Some("toolu_B"),
),
)
.await;
assert_eq!(
body.total, 1,
"session B must see session A's writer in the same checkout"
);
assert_eq!(body.agents[0].agent, "rust-engineer");
assert!(
!body.claimed,
"a denied dispatch must not also occupy the directory"
);
insert(
&state,
session_b,
"documentation",
"/repo/mcp-services",
None,
Some("toolu_B2"),
DelegationStatus::Running,
);
let body = call(
&state,
session_c,
dispatch("/repo/mcp-services", "qa", None, Some("toolu_C")),
)
.await;
assert_eq!(body.total, 2, "every live writer in the directory counts");
let body = call(
&state,
session_c,
dispatch(
"/repo/mcp-services/.claude/worktrees/w1",
"rust-engineer",
None,
Some("toolu_D"),
),
)
.await;
assert_eq!(
body.total, 0,
"a different directory is a different git HEAD and must stay admitted"
);
assert!(body.claimed, "the first writer in its own tree claims it");
}
#[tokio::test]
async fn shared_tree_dispatch_route_claim_is_idempotent_on_redelivery() {
let (state, _dir, session) = hermetic();
for _ in 0..3 {
let body = call(
&state,
session,
dispatch("/repo", "rust-engineer", None, Some("toolu_A")),
)
.await;
assert_eq!(body.total, 0, "a redelivery must stay admitted");
}
assert_eq!(state.delegations_for(session).len(), 1);
}
#[tokio::test]
async fn head_write_excludes_the_calling_sessions_own_delegation() {
let (state, _dir, session) = hermetic();
insert(
&state,
session,
"rust-engineer",
"/repo",
None,
Some("toolu_own"),
DelegationStatus::Running,
);
let commit = call(&state, session, commit_query("/repo")).await;
assert!(
commit.agents.is_empty(),
"a session's own agent must not be reported to it as a foreign live writer: {:?}",
commit.agents
);
assert_eq!(commit.total, 0);
assert!(!commit.claimed, "a Bash query never claims the tree");
assert!(
!commit.tree_holders,
"no marker sent, so none echoed (#8161)"
);
}
#[tokio::test]
async fn shared_tree_route_tree_holders_counts_the_callers_own_agent() {
let (state, _dir, session) = hermetic();
insert(
&state,
session,
"rust-engineer",
"/repo/.claude/worktrees/agent-parked",
None,
Some("toolu_own"),
DelegationStatus::Running,
);
let mut query = commit_query("/repo/.claude/worktrees/agent-parked");
query.payload[crate::daemon::delegation_routes::TREE_HOLDERS_MARKER] = Value::Bool(true);
let answer = call(&state, session, query).await;
assert_eq!(answer.total, 1, "{:?}", answer.agents);
assert!(
answer.tree_holders,
"the answer must echo the marker it honoured"
);
assert!(!answer.claimed, "a Bash query never claims the tree");
}
#[tokio::test]
async fn head_write_still_reports_another_sessions_delegation() {
let (state, _dir, caller) = hermetic();
let stranger = SessionId(uuid::Uuid::new_v4());
insert(
&state,
stranger,
"rust-engineer",
"/repo",
None,
Some("toolu_other"),
DelegationStatus::Running,
);
let commit = call(&state, caller, commit_query("/repo")).await;
assert_eq!(
commit.agents.len(),
1,
"another session's live writer must still deny: {:?}",
commit.agents
);
assert_eq!(commit.agents[0].agent, "rust-engineer");
assert_eq!(commit.total, 1);
}
#[tokio::test]
async fn a_dispatch_still_sees_the_callers_own_occupant() {
let (state, _dir, session) = hermetic();
insert(
&state,
session,
"rust-engineer",
"/repo",
None,
Some("toolu_own"),
DelegationStatus::Running,
);
let disp = call(
&state,
session,
dispatch("/repo", "qa", None, Some("toolu_second")),
)
.await;
assert_eq!(
disp.total, 1,
"a second unisolated writer must still be denied by the caller's own occupant: {:?}",
disp.agents
);
assert!(
!disp.claimed,
"and the tree must not be claimed for a dispatch that is denied"
);
}
#[tokio::test]
async fn the_two_questions_disagree_about_the_callers_own_record() {
let (state, _dir, session) = hermetic();
insert(
&state,
session,
"rust-engineer",
"/repo",
None,
Some("toolu_own"),
DelegationStatus::Running,
);
let commit = call(&state, session, commit_query("/repo")).await;
let disp = call(
&state,
session,
dispatch("/repo", "qa", None, Some("toolu_second")),
)
.await;
assert_eq!(commit.total, 0, "the commit hears nothing of its own agent");
assert_eq!(disp.total, 1, "the dispatch still hears the occupant");
}
fn launch(state: &Arc<DaemonState>, session: SessionId, tool_use_id: &str, agent_id: &str) {
crate::daemon::services::delegation_tracker::observe(
state,
session,
crate::core::hook::HookEvent::PostToolUse,
&serde_json::json!({
"cwd": "/repo",
"tool": "Agent",
"tool_use_id": tool_use_id,
"tool_response": {"isAsync": true, "agentId": agent_id},
}),
);
}
fn task_stop(state: &Arc<DaemonState>, session: SessionId, task_id: &str, outcome: Option<Value>) {
let mut payload = serde_json::json!({
"cwd": "/repo",
"tool": "TaskStop",
"tool_use_id": "toolu_stop",
"input": {"task_id": task_id},
});
if let Some(response) = outcome {
payload["tool_response"] = response;
}
crate::daemon::services::delegation_tracker::observe(
state,
session,
crate::core::hook::HookEvent::PostToolUse,
&payload,
);
}
fn stop_succeeded() -> Option<Value> {
Some(serde_json::json!({"is_error": false}))
}
fn stop_failed() -> Option<Value> {
Some(serde_json::json!({"is_error": true}))
}
#[tokio::test]
async fn a_task_stop_releases_the_stopped_agents_claim() {
let (state, _dir, session) = hermetic();
let first = call(
&state,
session,
dispatch("/repo", "qa", None, Some("toolu_qa")),
)
.await;
assert!(first.claimed, "the first dispatch takes the tree");
launch(&state, session, "toolu_qa", "agent-qa-1");
let while_running = call(
&state,
session,
dispatch("/repo", "rust-engineer", None, Some("toolu_a")),
)
.await;
assert_eq!(while_running.total, 1, "a live claim must still refuse");
assert!(!while_running.claimed);
task_stop(&state, session, "agent-qa-1", stop_succeeded());
let after_stop = call(
&state,
session,
dispatch("/repo", "rust-engineer", None, Some("toolu_b")),
)
.await;
assert_eq!(
after_stop.total, 0,
"a stopped agent holds nothing; occupants were {:?}",
after_stop.agents
);
assert!(after_stop.claimed, "the tree must be claimable again");
}
#[tokio::test]
async fn a_failed_task_stop_releases_nothing() {
let (state, _dir, session) = hermetic();
call(
&state,
session,
dispatch("/repo", "qa", None, Some("toolu_qa")),
)
.await;
launch(&state, session, "toolu_qa", "agent-qa-1");
task_stop(&state, session, "agent-qa-1", stop_failed());
let after = call(
&state,
session,
dispatch("/repo", "rust-engineer", None, Some("toolu_a")),
)
.await;
assert_eq!(after.total, 1, "a failed stop must not release the tree");
assert!(!after.claimed);
}
#[tokio::test]
async fn a_task_stop_with_no_response_still_releases() {
let (state, _dir, session) = hermetic();
call(
&state,
session,
dispatch("/repo", "qa", None, Some("toolu_qa")),
)
.await;
launch(&state, session, "toolu_qa", "agent-qa-1");
task_stop(&state, session, "agent-qa-1", None);
let after = call(
&state,
session,
dispatch("/repo", "rust-engineer", None, Some("toolu_a")),
)
.await;
assert_eq!(
after.total, 0,
"a response that cannot say either way must not re-lock the tree"
);
assert!(after.claimed);
}
#[tokio::test]
async fn a_task_stop_naming_an_unknown_id_terminalizes_nothing() {
let (state, _dir, session) = hermetic();
call(
&state,
session,
dispatch("/repo", "qa", None, Some("toolu_qa")),
)
.await;
launch(&state, session, "toolu_qa", "agent-qa-1");
task_stop(&state, session, "some-background-shell", stop_succeeded());
let after = call(
&state,
session,
dispatch("/repo", "rust-engineer", None, Some("toolu_a")),
)
.await;
assert_eq!(
after.total, 1,
"a stop naming something else must close nothing — a 'most recent live \
record' guess is what the tracker forbids"
);
}
#[tokio::test]
async fn a_late_observation_of_a_denied_dispatch_records_nothing() {
let (state, _dir, session) = hermetic();
call(
&state,
session,
dispatch("/repo", "qa", None, Some("toolu_qa")),
)
.await;
launch(&state, session, "toolu_qa", "agent-qa-1");
call(
&state,
session,
dispatch("/repo", "rust-engineer", None, Some("toolu_denied")),
)
.await;
crate::daemon::services::delegation_tracker::observe(
&state,
session,
crate::core::hook::HookEvent::PreToolUse,
&unisolated_hook_payload("toolu_denied"),
);
let next = call(
&state,
session,
dispatch("/repo", "rust-engineer", None, Some("toolu_next")),
)
.await;
assert_eq!(
next.total, 1,
"a late observation must not resurrect a refused dispatch's claim. Got {:?}",
next.agents
);
let names: Vec<&str> = next.agents.iter().map(|a| a.agent.as_str()).collect();
assert_eq!(
names,
vec!["qa"],
"a denial must not manufacture an occupant"
);
}
#[tokio::test]
async fn a_denied_dispatch_cancels_a_record_the_tracker_already_wrote() {
let (state, _dir, session) = hermetic();
call(
&state,
session,
dispatch("/repo", "qa", None, Some("toolu_qa")),
)
.await;
launch(&state, session, "toolu_qa", "agent-qa-1");
crate::daemon::services::delegation_tracker::observe(
&state,
session,
crate::core::hook::HookEvent::PreToolUse,
&unisolated_hook_payload("toolu_denied"),
);
call(
&state,
session,
dispatch("/repo", "rust-engineer", None, Some("toolu_denied")),
)
.await;
let next = call(
&state,
session,
dispatch("/repo", "rust-engineer", None, Some("toolu_next")),
)
.await;
assert_eq!(
next.total, 1,
"the deny must cancel the record the tracker had already written. Got {:?}",
next.agents
);
}
fn writer_names(body: &SharedTreeWritersResponse) -> Vec<&str> {
body.agents.iter().map(|a| a.agent.as_str()).collect()
}
#[tokio::test]
async fn a_dispatch_the_grant_path_denies_records_no_claim_7487() {
for tracker_first in [false, true] {
let (state, _dir, session) = hermetic();
insert(
&state,
session,
"version-control",
"/repo",
None,
Some("toolu_vc"),
DelegationStatus::Running,
);
let tracker_hook = || {
crate::daemon::services::delegation_tracker::observe(
&state,
session,
HookEvent::PreToolUse,
&unisolated_hook_payload("toolu_denied"),
);
};
if tracker_first {
tracker_hook();
}
let denied = granted_call(
&state,
session,
dispatch(
"/repo",
"rust-engineer",
Some("worktree"),
Some("toolu_denied"),
),
)
.await;
assert!(!denied.claimed, "tracker_first={tracker_first}");
assert_eq!(writer_names(&denied), vec!["version-control"]);
if !tracker_first {
tracker_hook();
}
assert_eq!(
state.shared_tree_occupants(std::path::Path::new("/repo"), None),
vec!["version-control".to_string()],
"tracker_first={tracker_first}: a refused dispatch must not occupy the checkout"
);
let next = granted_call(
&state,
session,
dispatch(
"/repo",
"documentation",
Some("worktree"),
Some("toolu_doc"),
),
)
.await;
assert_eq!(
writer_names(&next),
vec!["version-control"],
"tracker_first={tracker_first}: the next deny must name only the real occupant"
);
}
}
#[tokio::test]
async fn a_grant_deny_keeps_the_running_occupant_counted_7487() {
let (state, _dir, session) = hermetic();
insert(
&state,
session,
"version-control",
"/repo",
None,
Some("toolu_vc"),
DelegationStatus::Running,
);
launch(&state, session, "toolu_vc", "agent-vc-1");
granted_call(
&state,
session,
dispatch(
"/repo",
"rust-engineer",
Some("worktree"),
Some("toolu_denied"),
),
)
.await;
let unisolated = call(
&state,
session,
dispatch("/repo", "rust-engineer", None, Some("toolu_next")),
)
.await;
assert_eq!(writer_names(&unisolated), vec!["version-control"]);
assert!(!unisolated.claimed, "the live occupant must still refuse");
let other_session = SessionId(uuid::Uuid::new_v4());
let head_write = call(&state, other_session, commit_query("/repo")).await;
assert_eq!(
head_write.total, 1,
"a HEAD write in another session's view must still see the live writer"
);
}
#[tokio::test]
async fn an_admitted_grant_revives_the_record_its_own_deny_tombstoned_7487() {
let (state, _dir, session) = hermetic();
insert(
&state,
session,
"version-control",
"/repo",
None,
Some("toolu_vc"),
DelegationStatus::Running,
);
let denied = granted_call(
&state,
session,
dispatch(
"/repo",
"rust-engineer",
Some("worktree"),
Some("toolu_same"),
),
)
.await;
assert!(!denied.claimed, "the occupant denies the first attempt");
let vc = state
.find_delegation(session, |d| d.tool_use_id.as_deref() == Some("toolu_vc"))
.expect("the occupant has a record");
state.terminate_delegation(vc, DelegationStatus::Completed);
let admitted = granted_call(
&state,
session,
dispatch(
"/repo",
"rust-engineer",
Some("worktree"),
Some("toolu_same"),
),
)
.await;
assert!(admitted.claimed, "the freed checkout admits the grant");
let record = state
.delegations_for(session)
.into_iter()
.find(|d| d.tool_use_id.as_deref() == Some("toolu_same"))
.expect("the admitted dispatch has a record");
assert_eq!(
record.status,
DelegationStatus::Running,
"an admitted dispatch is running, whatever its own deny wrote"
);
assert!(
record.ended_at.is_none(),
"a revived record carries no end time"
);
assert_eq!(record.isolation.as_deref(), Some("worktree"));
}
#[tokio::test]
async fn repair_route_ends_a_stuck_record_7602() {
use crate::core::session::{ControlModel, Session, SessionStatus};
use crate::daemon::services::delegation_repair::{RepairDelegationRequest, RepairOutcome};
let (state, _dir, session) = hermetic();
let mut record = Session::new(session, "/repo", ControlModel::Tmux, None);
record.status = SessionStatus::Stopped;
state.register_session(record);
let mut d = Delegation::observed(session, "rust-engineer", "task", Some("toolu_x".into()));
d.agent_id = Some("af20cc838b2b30a55".to_string());
d.status = DelegationStatus::Running;
state.upsert_delegation(d);
let Json(outcome) = super::repair_delegation_route(
axum::extract::State(Arc::clone(&state)),
Path("af20cc838b2b30a55".to_string()),
Some(Json(RepairDelegationRequest { force: false })),
)
.await;
assert_eq!(outcome, RepairOutcome::Ended { records: 1 });
}
#[tokio::test]
async fn repair_route_refuses_a_live_owner_7602() {
use crate::core::session::{ControlModel, Session, SessionStatus};
use crate::daemon::services::delegation_repair::RepairOutcome;
let (state, _dir, session) = hermetic();
let mut record = Session::new(session, "/repo", ControlModel::Tmux, None);
record.status = SessionStatus::Active;
state.register_session(record);
let mut d = Delegation::observed(session, "rust-engineer", "task", Some("toolu_x".into()));
d.agent_id = Some("agent-live".to_string());
d.status = DelegationStatus::Running;
state.upsert_delegation(d);
let Json(outcome) = super::repair_delegation_route(
axum::extract::State(Arc::clone(&state)),
Path("agent-live".to_string()),
None,
)
.await;
assert!(matches!(outcome, RepairOutcome::Refused { .. }));
assert_eq!(state.all_delegations()[0].status, DelegationStatus::Running);
}
fn type_matched_record(state: &DaemonState, session: SessionId) -> Delegation {
let mut d = Delegation::observed(session, "version-control", "task", Some("toolu_vc".into()));
d.cwd = Some(PathBuf::from("/repo"));
d.status = DelegationStatus::Stale;
d.stale_by_agent_type = true;
state.upsert_delegation(d.clone());
d
}
#[tokio::test]
async fn shared_tree_dispatch_route_names_each_blocking_record_8257() {
let (state, _dir, session) = hermetic();
let d = type_matched_record(&state, session);
let body = call(
&state,
SessionId::new(),
dispatch("/repo", "rust-engineer", None, Some("toolu_new")),
)
.await;
assert_eq!(body.records.len(), 1, "{body:?}");
let r = &body.records[0];
assert_eq!(r.delegation_id, d.id.0.to_string());
assert_eq!(r.agent_id, None);
assert_eq!(r.owner, "a session the daemon holds no record of");
crate::daemon::services::delegation_records::delegation_records_tests::assert_no_uuid(
&serde_json::to_string(&body).expect("json"),
session,
);
assert_eq!(
r.repair_command,
format!("tm repair delegation --delegation-id {}", d.id.0)
);
}
#[tokio::test]
async fn list_route_names_the_blocking_record_8257() {
let (state, _dir, session) = hermetic();
let d = type_matched_record(&state, session);
let Json(listing) = list_delegations_route(
State(state.clone()),
Query(ListDelegationsQuery {
cwd: PathBuf::from("/repo"),
}),
)
.await;
assert_eq!(listing.records.len(), 1, "{listing:?}");
assert_eq!(listing.records[0].delegation_id, d.id.0.to_string());
assert!(listing.records[0].blocks_dispatch);
crate::daemon::services::delegation_records::delegation_records_tests::assert_no_uuid(
&serde_json::to_string(&listing).expect("json"),
session,
);
assert_eq!(
state.all_delegations()[0].status,
DelegationStatus::Stale,
"listing writes nothing"
);
}
#[tokio::test]
async fn repair_by_id_route_ends_a_record_with_no_agent_id_8257() {
use crate::core::session::{ControlModel, Session, SessionStatus};
use crate::daemon::services::delegation_repair::RepairOutcome;
let (state, _dir, session) = hermetic();
state.register_session(Session::new(session, "/repo", ControlModel::Tmux, None));
let d = type_matched_record(&state, session);
let by_id =
|| repair_delegation_by_id_route(State(state.clone()), Path(d.id.0.to_string()), None);
let Json(anonymous) = by_id().await.expect("a well-formed id");
assert!(
matches!(anonymous, RepairOutcome::Refused { .. }),
"{anonymous:?}"
);
let mut stopped = Session::new(session, "/repo", ControlModel::Tmux, None);
stopped.status = SessionStatus::Stopped;
state.register_session(stopped);
let Json(outcome) = by_id().await.expect("a well-formed id");
assert_eq!(outcome, RepairOutcome::Ended { records: 1 });
assert_eq!(
state.all_delegations()[0].status,
DelegationStatus::Cancelled
);
assert!(
call(
&state,
SessionId::new(),
dispatch("/repo", "rust-engineer", None, Some("t2"))
)
.await
.agents
.is_empty(),
"the repaired record no longer blocks a dispatch"
);
}
#[tokio::test]
async fn repair_route_ignores_an_owner_id_the_caller_supplies_8257() {
use crate::core::session::{ControlModel, Session};
use crate::daemon::services::delegation_repair::{RepairDelegationRequest, RepairOutcome};
let (state, _dir, owner) = hermetic();
state.register_session(Session::new(owner, "/repo", ControlModel::Tmux, None));
let mut d = Delegation::observed(owner, "version-control", "task", Some("toolu_c".into()));
d.agent_id = Some("a0wnclaim".to_string());
state.upsert_delegation(d);
let body = || {
let raw = serde_json::json!({
"force": false,
"session": owner.0.to_string(),
"caller_session": owner.0.to_string(),
"owner": owner.0.to_string(),
});
Some(Json(
serde_json::from_value::<RepairDelegationRequest>(raw).expect("request body"),
))
};
let Json(outcome) =
repair_delegation_route(State(state.clone()), Path("a0wnclaim".to_string()), body()).await;
assert!(
matches!(outcome, RepairOutcome::Refused { .. }),
"{outcome:?}"
);
assert!(
state
.all_delegations()
.iter()
.all(|d| !d.status.is_terminal()),
"no caller-supplied owner id may clear the record"
);
}
#[tokio::test]
async fn a_repair_task_that_panics_is_a_refusal_8257() {
use crate::daemon::services::delegation_repair::RepairOutcome;
let outcome = repair_off_worker(|| panic!("synthetic repair panic")).await;
match outcome {
RepairOutcome::Refused { reason } => {
assert!(reason.contains("failed before it answered"), "{reason}");
}
other => panic!("expected a refusal, got {other:?}"),
}
}