refactor(#962): replace children_of(MANAGER_NAME) with top_level_agents()

Add topology::top_level_agents_in(topo) and top_level_agents() which
find the topology root by structure (parent=None) rather than by name,
then return its children. Lifecycle.rs role logic now uses this instead
of children_of(MANAGER_NAME), removing the hardcoded manager-name
reference from the bind-mount logic.

Also adds two unit tests for top_level_agents_in.
This commit is contained in:
atlas 2026-06-01 18:54:37 +02:00 committed by mara
commit 828be8e2c8
2 changed files with 43 additions and 1 deletions

View file

@ -1156,7 +1156,7 @@ fn set_nspawn_flags(
agent_name,
crate::topology::ROLE_CAN_MANAGE_TOP_LEVEL_AGENTS,
) {
let top_level = crate::topology::children_of(MANAGER_NAME);
let top_level = crate::topology::top_level_agents();
for tl in &top_level {
if !direct_children.contains(tl) {
bind_child_agent_dirs(tl, &mut binds);

View file

@ -74,6 +74,34 @@ pub fn children_of_in(
.collect()
}
/// Return the agents that are direct children of the topology root
/// (the unique agent whose own parent is `None`). These are the
/// "top-level" agents — one layer below the auto-managed manager.
///
/// Callers should use this instead of `children_of(MANAGER_NAME)` so
/// the manager's logical name is not hardcoded outside lifecycle.rs.
/// If the topology has no root agent (e.g. empty file on cold boot),
/// returns an empty vec.
#[must_use]
pub fn top_level_agents() -> Vec<String> {
top_level_agents_in(&read())
}
/// Pure form of [`top_level_agents`] for unit tests.
#[must_use]
pub fn top_level_agents_in(topo: &BTreeMap<String, Option<String>>) -> Vec<String> {
// Find the unique root (parent = None). If none or multiple exist,
// fall back to empty — the topology is malformed or uninitialised.
let roots: Vec<&str> = topo
.iter()
.filter_map(|(name, parent)| if parent.is_none() { Some(name.as_str()) } else { None })
.collect();
match roots.as_slice() {
[root] => children_of_in(topo, root),
_ => vec![],
}
}
/// Resolve a magic recipient sentinel (currently just
/// [`hive_sh4re::PARENT_RECIPIENT`]) to a real broker recipient at
/// send time. Returns an owned `String` so callers can plug it
@ -623,6 +651,20 @@ mod tests {
assert!(children_of_in(&topo, "nobody").is_empty());
}
#[test]
fn top_level_agents_in_returns_children_of_root() {
let topo = topo_three_level();
// root's children = [alice]; bob+carol are under alice.
let top = top_level_agents_in(&topo);
assert_eq!(top, vec!["alice"]);
}
#[test]
fn top_level_agents_in_empty_topo_returns_empty() {
let topo = BTreeMap::new();
assert!(top_level_agents_in(&topo).is_empty());
}
// -----------------------------------------------------------------------
// Roles tests (no disk I/O — use the pure `has_role_in` / in-memory maps)
// -----------------------------------------------------------------------