diff --git a/hive-c0re/src/dashboard/permissions.rs b/hive-c0re/src/dashboard/permissions.rs index d66bf2fb..1c41637c 100644 --- a/hive-c0re/src/dashboard/permissions.rs +++ b/hive-c0re/src/dashboard/permissions.rs @@ -313,7 +313,7 @@ pub(super) async fn post_permissions( } /// Agent names that have explicit capability/tool-group entries but are -/// not in the live container roster AND not in the kept-state tombstone +/// not in the live container roster AND not in the kept-state directory /// list (i.e. truly gone — renamed or destroyed agents whose JSON entries /// persisted). The client uses this to drive the "stale permission entries" /// sub-section in K3PT ST4T3 without having to fetch three separate @@ -335,13 +335,16 @@ pub(super) async fn get_stale_permissions( .into_iter() .map(|c| c.name) .collect(); - // Kept-state directories — any name here is a tombstone, not a ghost. - let tombstones: std::collections::HashSet = + // Kept-state directories: every agent that ever had a state dir on disk, + // including both live containers (already in `live`) and soft-deleted + // tombstones (destroyed but kept). A name present here is either live + // or a properly-removed tombstone — neither is a ghost. + let kept: std::collections::HashSet = crate::coordinator::Coordinator::kept_state_names() .into_iter() .collect(); - // Known = live roster ∪ tombstones. - let known: std::collections::HashSet<&String> = live.iter().chain(tombstones.iter()).collect(); + // Known = live roster ∪ kept-state names. + let known: std::collections::HashSet<&String> = live.iter().chain(kept.iter()).collect(); // Explicit entries in either JSON file. let caps = crate::capabilities::read(); let tgs = crate::tool_groups::read();