use std::sync::Arc;
use std::time::{SystemTime, UNIX_EPOCH};
use trusty_common::uds::server::RequestPeer;
use super::delegation_repair::RepairCaller;
use crate::core::agent::Delegation;
use crate::core::session::SessionId;
use crate::core::twin_identity::ClaudeProcess;
use crate::daemon::state::DaemonState;
use crate::daemon::state::session_claude_resume::pane_claude;
use crate::daemon::state::session_claudes::ResumeGrant;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RepairPeer {
Kernel {
pid: u32,
seen_at: u64,
},
Unproven(String),
}
fn unix_secs(at: SystemTime) -> u64 {
at.duration_since(UNIX_EPOCH).map_or(0, |d| d.as_secs())
}
impl RepairPeer {
pub fn from_socket(peer: Option<RequestPeer>) -> Self {
peer.map_or_else(
|| {
Self::Unproven(
"the daemon socket could not read the calling process's pid from the kernel"
.to_string(),
)
},
|peer| Self::Kernel {
pid: peer.pid,
seen_at: unix_secs(peer.accepted_at),
},
)
}
pub fn http() -> Self {
Self::Unproven(
"the request came over HTTP, which carries no kernel-verified caller — \
`tm repair delegation` sends it over the daemon socket (#8531)"
.to_string(),
)
}
}
pub fn establish_caller(
state: &Arc<DaemonState>,
peer: RepairPeer,
matches: impl Fn(&Delegation) -> bool,
) -> RepairCaller {
let mut owners: Vec<SessionId> = Vec::new();
for d in state.all_delegations() {
if matches(&d) && !d.status.is_terminal() && !owners.contains(&d.session) {
owners.push(d.session);
}
}
let claudes = state.session_claudes();
establish_caller_with(
peer,
&owners,
|pid, seen_at| claudes.peer_claude(pid, seen_at),
|s| owner_claude(state, s),
)
}
pub(crate) fn establish_caller_with(
peer: RepairPeer,
owners: &[SessionId],
peer_claude: impl Fn(u32, u64) -> Result<ClaudeProcess, String>,
owner_claude: impl Fn(SessionId) -> Result<ClaudeProcess, String>,
) -> RepairCaller {
let (pid, seen_at) = match peer {
RepairPeer::Kernel { pid, seen_at } => (pid, seen_at),
RepairPeer::Unproven(why) => return RepairCaller::Unestablished(why),
};
let caller = match peer_claude(pid, seen_at) {
Ok(claude) => claude,
Err(e) => return RepairCaller::Unestablished(e),
};
let mut failures: Vec<String> = Vec::new();
for owner in owners {
match owner_claude(*owner) {
Ok(bound) if bound == caller => return RepairCaller::Session(*owner),
Ok(_) => {}
Err(e) => failures.push(e),
}
}
let mut why = format!(
"the calling process runs under claude pid {}, which is not the process of the session \
that owns the record",
caller.pid
);
if !failures.is_empty() {
why.push_str(&format!(
"; the owner's process could not be found: {}",
failures.join("; ")
));
}
RepairCaller::Unestablished(why)
}
pub async fn bind_announcing_claude(
state: &Arc<DaemonState>,
session: SessionId,
peer: RepairPeer,
) -> Result<(), String> {
let walk = |pid, seen_at, held: &DaemonState| held.session_claudes().peer_claude(pid, seen_at);
bind_announcing_claude_with(state, session, peer, walk, pane_claude).await
}
pub(crate) async fn bind_announcing_claude_with(
state: &Arc<DaemonState>,
session: SessionId,
peer: RepairPeer,
walk: impl FnOnce(u32, u64, &DaemonState) -> Result<ClaudeProcess, String> + Send + 'static,
pane: impl Fn(&ResumeGrant) -> Result<ClaudeProcess, String> + Send + 'static,
) -> Result<(), String> {
let (pid, seen_at) = match peer {
RepairPeer::Kernel { pid, seen_at } => (pid, seen_at),
RepairPeer::Unproven(why) => return Err(why),
};
let resumed = state.session_claudes().resume_grant(session).is_some();
let settled = state.session_claudes().is_settled(session);
let held = Arc::clone(state);
let unfinished = |e| format!("the process walk did not finish: {e}");
if settled && !resumed {
let walked = tokio::task::spawn_blocking(move || walk(pid, seen_at, &held))
.await
.map_err(unfinished)?;
match walked {
Ok(claude) => state.session_claudes().note_announcer(session, claude),
Err(e) => tracing::warn!(
session = ?session,
"noted no later announcer of a settled session, its process walk failed: {e} (#9010)"
),
}
return Ok(());
}
tokio::task::spawn_blocking(move || {
let claude = walk(pid, seen_at, &held)?;
if resumed {
held.rebind_resumed_claude_with(session, claude, pane)
} else {
held.bind_session_claude(session, claude)
}
})
.await
.map_err(unfinished)?
}
pub async fn stale_on_owner_session_end(
state: &Arc<DaemonState>,
session: SessionId,
peer: RepairPeer,
) -> Result<usize, String> {
let live = state
.all_delegations()
.iter()
.any(|d| d.session == session && d.status.is_live());
if !live {
return Ok(0);
}
let held = Arc::clone(state);
let caller = tokio::task::spawn_blocking(move || {
establish_caller(&held, peer, |d| d.session == session)
})
.await
.map_err(|e| format!("the process walk did not finish: {e}"))?;
stale_if_owner(state, session, &caller)
}
pub(crate) fn stale_if_owner(
state: &DaemonState,
session: SessionId,
caller: &RepairCaller,
) -> Result<usize, String> {
match caller {
RepairCaller::Session(s) if *s == session => {
Ok(state.stale_delegations_of_dead_session(session))
}
RepairCaller::Session(s) => Err(format!(
"the SessionEnd came from session {}, not the session it names",
s.0
)),
RepairCaller::Unestablished(why) => Err(why.clone()),
}
}
pub(crate) fn owner_claude(
state: &DaemonState,
session: SessionId,
) -> Result<ClaudeProcess, String> {
owner_claude_with(state, session, crate::core::twin_arming::process_facts)
}
pub(crate) fn owner_claude_with(
state: &DaemonState,
session: SessionId,
facts: impl Fn(u32) -> Result<crate::core::twin_arming::ProcessFacts, String>,
) -> Result<ClaudeProcess, String> {
let claudes = state.session_claudes();
let bound = claudes.get(session).ok_or_else(|| match claudes.sealed() {
Some(why) => format!("the session-claude registry is sealed: {why}"),
None => "the owning session never announced its claude process over the daemon socket"
.to_string(),
})?;
match facts(bound.pid) {
Ok(f) if f.start_time == bound.start_time => Ok(bound),
_ => Err(format!(
"the owning session's claude (pid {}) is no longer running",
bound.pid
)),
}
}
#[cfg(test)]
#[path = "delegation_repair_caller_tests.rs"]
mod delegation_repair_caller_tests;