diff --git a/hive-c0re/src/agent_config/tool_groups.rs b/hive-c0re/src/agent_config/tool_groups.rs index 6d688542..ead66c9c 100644 --- a/hive-c0re/src/agent_config/tool_groups.rs +++ b/hive-c0re/src/agent_config/tool_groups.rs @@ -35,9 +35,45 @@ pub fn tool_groups_path() -> PathBuf { /// Read the per-agent tool-group map. An absent file is the empty map — /// callers treat a missing entry as "use role default". A file that -/// exists but can't be read or parsed is an error. +/// exists but can't be read or parsed is an error. Any entry that isn't a +/// recognised [`hive_sh4re::permissions::ToolGroup`] is dropped (with a +/// `warn!`) from what's returned — a stale or renamed name is never +/// honoured — but `read` itself never writes; unknown names stay on disk +/// until the next `set_groups` rewrites the file. pub fn read() -> std::io::Result>> { - super::read_map(&tool_groups_path()) + let mut map = super::read_map(&tool_groups_path())?; + prune_unknown(&mut map); + Ok(map) +} + +/// True if `name` matches a recognised [`hive_sh4re::permissions::ToolGroup`] +/// exactly (same comparison [`validate_groups`] uses). +fn is_known(name: &str) -> bool { + hive_sh4re::permissions::ToolGroup::ALL + .iter() + .any(|g| <&str>::from(*g) == name) +} + +/// Drop any tool-group string in `map` that isn't a recognised +/// [`hive_sh4re::permissions::ToolGroup`], warning per dropped string so a +/// stale or renamed grant doesn't look like it's still in effect. An agent +/// left with no groups is removed entirely, matching [`set_groups`]'s +/// empty-vec behaviour (reverts to the harness role default). Returns +/// whether anything was dropped. +fn prune_unknown(map: &mut BTreeMap>) -> bool { + let mut dropped_any = false; + map.retain(|agent, groups| { + groups.retain(|g| { + let known = is_known(g); + if !known { + dropped_any = true; + tracing::warn!(agent, tool_group = %g, "dropping unrecognised tool group"); + } + known + }); + !groups.is_empty() + }); + dropped_any } /// Look up the configured tool groups for one agent. Returns an empty @@ -51,14 +87,10 @@ pub fn groups_for(name: &str) -> std::io::Result> { /// Returns `Ok(())` when all names are known, or `Err` listing the /// unrecognised names so callers can surface a useful error message. pub fn validate_groups(groups: &[String]) -> anyhow::Result<()> { - let valid: std::collections::BTreeSet<&str> = hive_sh4re::permissions::ToolGroup::ALL - .iter() - .map(|g| <&str>::from(*g)) - .collect(); let unknown: Vec<&str> = groups .iter() .map(String::as_str) - .filter(|s| !valid.contains(s)) + .filter(|s| !is_known(s)) .collect(); if unknown.is_empty() { Ok(())