remove operator as target for scheduled prompts

This commit is contained in:
damocles 2026-09-11 22:36:40 +02:00 committed by mara
commit b4dc09ff93
7 changed files with 130 additions and 86 deletions

View file

@ -47,6 +47,15 @@ pub(super) fn handle_request_schedule_prompt(
message: "schedule must have at least one target".into(),
};
}
if payload
.targets
.iter()
.any(|t| t == hive_sh4re::manager::OPERATOR_RECIPIENT)
{
return Response::Err {
message: "operator is not a valid schedule target".into(),
};
}
if payload.body.trim().is_empty() {
return Response::Err {
message: "schedule body must be non-empty".into(),
@ -310,8 +319,7 @@ pub fn schedule_to_wire_public(
/// the dashboard's schedule table doesn't render ghost columns for
/// destroyed agents. `live` is the set of logical agent names from the
/// last `nixos-container list` scan (stopped agents included, destroyed
/// ones absent); the `operator` pseudo-target is always retained since
/// it isn't a container. Applied only to the dashboard wire paths
/// ones absent). Applied only to the dashboard wire paths
/// (`api_schedules` + the `SchedulesChanged` SSE emit) — `list_schedules`
/// doesn't apply this filter (it still returns every live target, ghost
/// or not, on the schedules the requester is authorized to see) so an
@ -323,9 +331,7 @@ pub(crate) fn filter_ghost_schedule_targets(
live: &std::collections::HashSet<String>,
) {
for s in schedules.iter_mut() {
s.targets.retain(|t| {
t.target == hive_sh4re::manager::OPERATOR_RECIPIENT || live.contains(&t.target)
});
s.targets.retain(|t| live.contains(&t.target));
}
}
@ -417,19 +423,19 @@ mod tests {
}
#[test]
fn ghost_filter_drops_dead_agents_keeps_live_and_operator() {
fn ghost_filter_drops_dead_agents_keeps_live() {
let live: std::collections::HashSet<String> = ["iris".to_owned(), "damocles".to_owned()]
.into_iter()
.collect();
let mut schedules = vec![schedule(&["iris", "ghost", "operator", "damocles"])];
let mut schedules = vec![schedule(&["iris", "ghost", "damocles"])];
filter_ghost_schedule_targets(&mut schedules, &live);
let kept: Vec<&str> = schedules[0]
.targets
.iter()
.map(|t| t.target.as_str())
.collect();
// `ghost` (destroyed) dropped; live agents + operator pseudo-target kept.
assert_eq!(kept, vec!["iris", "operator", "damocles"]);
// `ghost` (destroyed) dropped; live agents kept.
assert_eq!(kept, vec!["iris", "damocles"]);
}
#[test]
@ -437,8 +443,6 @@ mod tests {
let live: std::collections::HashSet<String> = std::collections::HashSet::new();
let mut schedules = vec![schedule(&["gone1", "gone2"])];
filter_ghost_schedule_targets(&mut schedules, &live);
// operator is never in the live set but is always retained; here
// there's no operator target, so everything drops.
assert!(schedules[0].targets.is_empty());
}
}