use std::collections::HashMap;
use std::path::Path;
use super::worktree_ownership::{
AgentDelegationState, AgentWorktreeOwner, SentinelOwner, is_harness_agent_worktree,
read_sentinel_owner,
};
use super::worktree_reclaim_claim::{ClaimLiveness, ClaimState};
use super::worktree_registry::{HarnessLockState, harness_lock_state};
pub(crate) type AgentStateProbe<'a> = &'a dyn Fn(&AgentWorktreeOwner) -> AgentDelegationState;
pub(crate) fn agent_ownership_blocks(
path: &Path,
agent_state: AgentStateProbe<'_>,
) -> Option<String> {
match read_sentinel_owner(path) {
SentinelOwner::Agent(owner, _) => match agent_state(&owner) {
AgentDelegationState::Live => Some(format!(
"owned by dispatched agent {} — a delegation naming it has not ended, so it is \
still working in this tree (#5661)",
owner.agent_id
)),
AgentDelegationState::Unknown => match harness_lock_state(path) {
HarnessLockState::Released => None,
HarnessLockState::Held => Some(format!(
"owned by dispatched agent {} and git still reports the harness's \
agent-lifetime lock on this worktree (#6561)",
owner.agent_id
)),
HarnessLockState::Undeterminable => Some(format!(
"owned by dispatched agent {} — the delegation registry holds no record of \
that agent, and git could not be asked whether the harness still holds the \
worktree. Two silences are not an answer: undeterminable, not absent \
(#5661, #6561, ADR-0045)",
owner.agent_id
)),
},
AgentDelegationState::Ended => None,
},
SentinelOwner::Unknown if is_harness_agent_worktree(path) => Some(
"names no owner inside the harness agent-worktree store — the sentinel is absent, \
empty, malformed or unreadable, so nothing attributes this tree to an agent and \
nothing says whether one is still working in it. An unanswerable ownership \
question on a destructive path is undeterminable, not absent (#5661, #6561, \
ADR-0045)"
.to_string(),
),
SentinelOwner::Known(..) | SentinelOwner::Unknown => None,
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub(crate) struct SessionOwners {
by_id: Option<HashMap<String, ClaimLiveness>>,
}
impl SessionOwners {
pub(crate) fn observed(records: impl IntoIterator<Item = (String, ClaimLiveness)>) -> Self {
let mut by_id: HashMap<String, ClaimLiveness> = HashMap::new();
for (id, liveness) in records {
let entry = by_id.entry(id).or_insert(liveness);
if liveness == ClaimLiveness::Live {
*entry = ClaimLiveness::Live;
}
}
Self { by_id: Some(by_id) }
}
}
pub(crate) fn session_ownership_blocks(path: &Path, owners: &SessionOwners) -> Option<String> {
let SentinelOwner::Known(owner, _) = read_sentinel_owner(path) else {
return None;
};
if is_harness_agent_worktree(path) {
match harness_lock_state(path) {
HarnessLockState::Released => {}
HarnessLockState::Held => {
return Some(format!(
"sentinel names session {owner}, but this tree is in the harness agent store \
and git still reports the harness's agent-lifetime lock on it — an agent is \
still working here, whatever that session's state (#7652, #6561)"
));
}
HarnessLockState::Undeterminable => {
return Some(format!(
"sentinel names session {owner}, this tree is in the harness agent store, and \
git could not be asked whether the harness still holds it — undeterminable, \
not absent (#7652, #6561, ADR-0045)"
));
}
}
}
let Some(by_id) = &owners.by_id else {
return Some(format!(
"owned by session {owner}, and the session store was not read, so nothing can show \
that session has ended (#7652, ADR-0045)"
));
};
match by_id.get(&owner.to_string()) {
Some(ClaimLiveness::SessionGone) => None,
Some(ClaimLiveness::Live) => Some(format!(
"owned by session {owner}, which tmux still lists, or whose liveness the tmux probe \
could not establish (#7652)"
)),
None => Some(format!(
"owned by session {owner}, and no stored session record names it, so nothing can \
show that session has ended — unrecorded is not dead. If that session's record was \
compacted by `tm sessions prune`, no later run will recover it: confirm nobody is \
working in this tree and remove it yourself with `git worktree remove` (#7652, \
ADR-0045)"
)),
}
}
pub(crate) fn unattributed_nested_blocks(path: &Path, claim: &ClaimState) -> Option<String> {
let ClaimState::ForeignNested { session, .. } = claim else {
return None;
};
match read_sentinel_owner(path) {
SentinelOwner::Unknown => Some(format!(
"session {session} claims a workspace containing this worktree, and the ownership \
sentinel is absent, empty, malformed or unreadable, so nothing attributes the tree \
to anyone else — the live claim stands (#7652, ADR-0045)"
)),
SentinelOwner::Known(..) | SentinelOwner::Agent(..) => None,
}
}