fix(#962): top_level_agents_in delegates to children_of(MANAGER_NAME)

The previous parent=None heuristic was wrong — the manager is not
required to be the structural topology root (it can have a parent).
Delegate to children_of_in(MANAGER_NAME) directly; topology.rs is the
right place for this knowledge. Update comment in lifecycle.rs to say
"direct child of the manager" instead of "direct child of root".
This commit is contained in:
atlas 2026-06-01 19:00:19 +02:00 committed by mara
commit a4e0628ba1
2 changed files with 11 additions and 20 deletions

View file

@ -1149,9 +1149,10 @@ fn set_nspawn_flags(
}
// `can_manage_top_level_agents` role: additionally mount every
// top-level agent (direct child of root) as a virtual child. Enables
// recovery — a role holder can update a top-level agent's config even
// when that agent is down. Also grants RO access to /applied and /meta.
// top-level agent (direct child of the manager) as a virtual child.
// Enables recovery — a role holder can update a top-level agent's
// config even when that agent is down. Also grants RO access to
// /applied and /meta.
if crate::topology::has_role(
agent_name,
crate::topology::ROLE_CAN_MANAGE_TOP_LEVEL_AGENTS,

View file

@ -74,14 +74,13 @@ 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.
/// Return the direct children of the manager agent — the "top-level"
/// agents that role holders with `can_manage_top_level_agents` are
/// granted access to.
///
/// 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.
/// Callers outside `topology` should use this instead of
/// `children_of(MANAGER_NAME)` so the manager's logical name stays
/// confined to the topology module.
#[must_use]
pub fn top_level_agents() -> Vec<String> {
top_level_agents_in(&read())
@ -90,16 +89,7 @@ pub fn top_level_agents() -> Vec<String> {
/// 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![],
}
children_of_in(topo, crate::lifecycle::MANAGER_NAME)
}
/// Resolve a magic recipient sentinel (currently just