fix(#962): top_level_agents = parentless agents, not children of manager
Per mara's design: the role grants access to every agent with no parent in the topology (parent=None), derived purely from structure. No agent name is hardcoded. In normal operation this is just the manager; any additional parentless agents the operator creates are also covered. Update ROLE docstring, lifecycle.rs comment, and unit tests accordingly. Add a multi-root test to document the behaviour with multiple parentless agents.
This commit is contained in:
parent
aae4be19bd
commit
fd87cf9924
2 changed files with 29 additions and 18 deletions
|
|
@ -1149,10 +1149,9 @@ fn set_nspawn_flags(
|
|||
}
|
||||
|
||||
// `can_manage_top_level_agents` role: additionally mount every
|
||||
// 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.
|
||||
// parentless agent in the topology as a virtual child. Enables
|
||||
// recovery — a role holder can update those agents' configs even
|
||||
// when they are down. Also grants RO access to /applied and /meta.
|
||||
if crate::topology::has_role(
|
||||
agent_name,
|
||||
crate::topology::ROLE_CAN_MANAGE_TOP_LEVEL_AGENTS,
|
||||
|
|
|
|||
|
|
@ -74,13 +74,13 @@ pub fn children_of_in(
|
|||
.collect()
|
||||
}
|
||||
|
||||
/// 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.
|
||||
/// Return every agent that has no parent in the topology. These are the
|
||||
/// "top-level" agents a `can_manage_top_level_agents` role holder is
|
||||
/// granted access to. No agent name is hardcoded — the set is derived
|
||||
/// purely from topology structure.
|
||||
///
|
||||
/// Callers outside `topology` should use this instead of
|
||||
/// `children_of(MANAGER_NAME)` so the manager's logical name stays
|
||||
/// confined to the topology module.
|
||||
/// In normal operation this is just the manager, but any agent the
|
||||
/// operator explicitly places outside the hierarchy is also included.
|
||||
#[must_use]
|
||||
pub fn top_level_agents() -> Vec<String> {
|
||||
top_level_agents_in(&read())
|
||||
|
|
@ -89,7 +89,9 @@ 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> {
|
||||
children_of_in(topo, crate::lifecycle::MANAGER_NAME)
|
||||
topo.iter()
|
||||
.filter_map(|(name, parent)| if parent.is_none() { Some(name.clone()) } else { None })
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Resolve a magic recipient sentinel (currently just
|
||||
|
|
@ -309,10 +311,10 @@ pub fn reconcile(agent_names: &[String]) -> std::io::Result<bool> {
|
|||
// Roles
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Agents with this role have the top-level agents (direct children of the
|
||||
/// manager) added as virtual children for bind-mount and config-change
|
||||
/// purposes. Enables recovery: if a top-level agent is down, a role holder
|
||||
/// can still read its state and update its config.
|
||||
/// Agents with this role have every parentless agent in the topology
|
||||
/// (see `top_level_agents`) added as virtual children for bind-mount
|
||||
/// and config-change purposes. Enables recovery: if a top-level agent
|
||||
/// is down, a role holder can still read its state and update its config.
|
||||
///
|
||||
/// The manager receives this role by default on first `reconcile_roles`
|
||||
/// call; operators can revoke it with `set_role`.
|
||||
|
|
@ -642,11 +644,21 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn top_level_agents_in_returns_children_of_root() {
|
||||
fn top_level_agents_in_returns_parentless_agents() {
|
||||
let topo = topo_three_level();
|
||||
// root's children = [alice]; bob+carol are under alice.
|
||||
// Only the manager has no parent (alice/bob/carol all have parents).
|
||||
let top = top_level_agents_in(&topo);
|
||||
assert_eq!(top, vec!["alice"]);
|
||||
assert_eq!(top, vec![crate::lifecycle::MANAGER_NAME]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn top_level_agents_in_multi_root_returns_all_parentless() {
|
||||
let mut topo = topo_three_level();
|
||||
// Simulate a second parentless agent alongside the manager.
|
||||
topo.insert("orphan".to_owned(), None);
|
||||
let mut top = top_level_agents_in(&topo);
|
||||
top.sort();
|
||||
assert_eq!(top, vec![crate::lifecycle::MANAGER_NAME, "orphan"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
Loading…
Reference in a new issue