hive-c0re: default new agents to root instead of under the bootstrap container
This commit is contained in:
parent
08afa49c96
commit
513c828ab2
2 changed files with 32 additions and 39 deletions
|
|
@ -67,9 +67,11 @@ where system-level facts live.
|
||||||
empty map → every agent treated as root (safe degradation for
|
empty map → every agent treated as root (safe degradation for
|
||||||
fresh installs that haven't run `meta::sync_agents` yet).
|
fresh installs that haven't run `meta::sync_agents` yet).
|
||||||
2. **Reconcile**: `meta::sync_agents` calls `topology::reconcile`
|
2. **Reconcile**: `meta::sync_agents` calls `topology::reconcile`
|
||||||
alongside its `flake.nix` regeneration. New agents land at their
|
alongside its `flake.nix` regeneration. New agents default to root
|
||||||
default position (manager as parent, manager itself as root);
|
(null parent) — an agent-requested sub-agent already carries an
|
||||||
removed agents drop. Existing entries are preserved as-is so
|
explicit requester-as-parent edge from its `init_config` approval, so
|
||||||
|
only user/operator-initiated spawns hit this default, and those are
|
||||||
|
roots; removed agents drop. Existing entries are preserved as-is so
|
||||||
operator overrides stick across regenerations. Pending-init agents
|
operator overrides stick across regenerations. Pending-init agents
|
||||||
(an operator-approved proposed config repo but no container yet —
|
(an operator-approved proposed config repo but no container yet —
|
||||||
`Coordinator::pending_init_names`) are kept too, so the
|
`Coordinator::pending_init_names`) are kept too, so the
|
||||||
|
|
|
||||||
|
|
@ -175,9 +175,12 @@ pub fn write(topology: &BTreeMap<String, Option<String>>) -> std::io::Result<()>
|
||||||
std::fs::write(&path, format!("{text}\n"))
|
std::fs::write(&path, format!("{text}\n"))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compute the default topology for a fresh install: every non-manager
|
/// Compute the default topology for a fresh install: every agent is a
|
||||||
/// agent has the manager as parent; manager itself is root. Used by
|
/// root (parent = null). There is no structural "manager" — agents
|
||||||
/// `meta::sync_agents` on first call to seed `topology.json`.
|
/// arrange themselves via explicit parent edges (an agent-requested
|
||||||
|
/// sub-agent gets a requester-as-parent edge at `init_config`; the
|
||||||
|
/// operator reparents via the dashboard / `RequestSetParent` API).
|
||||||
|
/// Used by `meta::sync_agents` on first call to seed `topology.json`.
|
||||||
///
|
///
|
||||||
/// As soon as an explicit write lands (dashboard / `RequestSetParent`
|
/// As soon as an explicit write lands (dashboard / `RequestSetParent`
|
||||||
/// API), this seeding stops touching pre-existing entries —
|
/// API), this seeding stops touching pre-existing entries —
|
||||||
|
|
@ -192,14 +195,7 @@ pub fn write(topology: &BTreeMap<String, Option<String>>) -> std::io::Result<()>
|
||||||
pub fn default_seed(agent_names: &[String]) -> BTreeMap<String, Option<String>> {
|
pub fn default_seed(agent_names: &[String]) -> BTreeMap<String, Option<String>> {
|
||||||
let mut out = BTreeMap::new();
|
let mut out = BTreeMap::new();
|
||||||
for name in agent_names {
|
for name in agent_names {
|
||||||
if name == crate::lifecycle::MANAGER_NAME {
|
out.insert(name.clone(), None);
|
||||||
out.insert(name.clone(), None);
|
|
||||||
} else {
|
|
||||||
out.insert(
|
|
||||||
name.clone(),
|
|
||||||
Some(crate::lifecycle::MANAGER_NAME.to_owned()),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
out
|
out
|
||||||
}
|
}
|
||||||
|
|
@ -355,12 +351,14 @@ pub fn apply_reconcile(
|
||||||
let mut changed = false;
|
let mut changed = false;
|
||||||
for name in agent_names {
|
for name in agent_names {
|
||||||
if !next.contains_key(name) {
|
if !next.contains_key(name) {
|
||||||
let parent = if name == crate::lifecycle::MANAGER_NAME {
|
// A new agent with no declared parent defaults to root
|
||||||
None
|
// (parent = null). Agent-requested sub-agents always carry an
|
||||||
} else {
|
// explicit requester-as-parent edge (written at init_config
|
||||||
Some(crate::lifecycle::MANAGER_NAME.to_owned())
|
// approval), so they never hit this default — only
|
||||||
};
|
// user/operator-initiated spawns do, and those are roots. No
|
||||||
next.insert(name.clone(), parent);
|
// agent is structurally privileged here: "root-ness" is just
|
||||||
|
// a null parent.
|
||||||
|
next.insert(name.clone(), None);
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -491,26 +489,19 @@ mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn default_seed_makes_manager_root_others_children() {
|
fn default_seed_makes_every_agent_root() {
|
||||||
|
// No structural manager: every agent defaults to root (null
|
||||||
|
// parent). Explicit edges (init_config / dashboard) are layered
|
||||||
|
// on later.
|
||||||
let agents = vec![
|
let agents = vec![
|
||||||
"alice".to_owned(),
|
"alice".to_owned(),
|
||||||
crate::lifecycle::MANAGER_NAME.to_owned(),
|
crate::lifecycle::MANAGER_NAME.to_owned(),
|
||||||
"bob".to_owned(),
|
"bob".to_owned(),
|
||||||
];
|
];
|
||||||
let seed = default_seed(&agents);
|
let seed = default_seed(&agents);
|
||||||
assert_eq!(
|
assert_eq!(seed.get(crate::lifecycle::MANAGER_NAME), Some(&None));
|
||||||
seed.get(crate::lifecycle::MANAGER_NAME),
|
assert_eq!(seed.get("alice"), Some(&None));
|
||||||
Some(&None),
|
assert_eq!(seed.get("bob"), Some(&None));
|
||||||
"manager should be root"
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
seed.get("alice"),
|
|
||||||
Some(&Some(crate::lifecycle::MANAGER_NAME.to_owned()))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
seed.get("bob"),
|
|
||||||
Some(&Some(crate::lifecycle::MANAGER_NAME.to_owned()))
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -665,17 +656,17 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn apply_reconcile_adds_missing_live_agent_under_manager() {
|
fn apply_reconcile_adds_missing_live_agent_as_root() {
|
||||||
|
// A live agent with no prior topology entry defaults to root
|
||||||
|
// (null parent) — no structural manager to hang it under.
|
||||||
let live = vec![
|
let live = vec![
|
||||||
crate::lifecycle::MANAGER_NAME.to_owned(),
|
crate::lifecycle::MANAGER_NAME.to_owned(),
|
||||||
"newbie".to_owned(),
|
"newbie".to_owned(),
|
||||||
];
|
];
|
||||||
let (next, changed) = apply_reconcile(&BTreeMap::new(), &live, &[]);
|
let (next, changed) = apply_reconcile(&BTreeMap::new(), &live, &[]);
|
||||||
assert!(changed);
|
assert!(changed);
|
||||||
assert_eq!(
|
assert_eq!(next.get("newbie"), Some(&None));
|
||||||
next.get("newbie"),
|
assert_eq!(next.get(crate::lifecycle::MANAGER_NAME), Some(&None));
|
||||||
Some(&Some(crate::lifecycle::MANAGER_NAME.to_owned()))
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue