topology: meta-repo agent hierarchy + ContainerView.parent (#361)

This commit is contained in:
damocles 2026-05-24 04:47:55 +02:00
parent e931c08739
commit 0b03d5bcfb
6 changed files with 403 additions and 3 deletions

View file

@ -87,6 +87,14 @@ pub struct ContainerView {
/// status is set.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub status_set_at: Option<i64>,
/// Name of this agent's parent in the agent hierarchy (#361). `None`
/// marks the agent as root-level; the dashboard renders it without
/// indentation. Sourced from `meta/topology.json` (single source of
/// truth, hive-c0re-owned) — NOT from per-agent agent.nix, because
/// an agent shouldn't be able to unilaterally declare its own place
/// in the tree.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub parent: Option<String>,
}
/// Build the full container list. Wraps `lifecycle::list()` and
@ -94,6 +102,10 @@ pub struct ContainerView {
pub async fn build_all(coord: &Coordinator) -> Vec<ContainerView> {
let raw = lifecycle::list().await.unwrap_or_default();
let locked = read_meta_locked_revs();
// Pull the topology map once and look up each agent's parent below.
// Empty / absent topology.json → every agent root-level (matches
// the pre-#361 status quo for fresh installs).
let topology = crate::topology::read();
let mut out = Vec::new();
for c in &raw {
let (logical, is_manager) = if c == MANAGER_NAME {
@ -130,6 +142,7 @@ pub async fn build_all(coord: &Coordinator) -> Vec<ContainerView> {
let rate_limited = is_rate_limited(&logical);
let extra_links = read_dashboard_links(&logical);
let (status_text, status_set_at) = read_status(&logical);
let parent = topology.get(&logical).cloned().flatten();
out.push(ContainerView {
port: lifecycle::agent_web_port(&logical),
running: lifecycle::is_running(&logical).await,
@ -146,6 +159,7 @@ pub async fn build_all(coord: &Coordinator) -> Vec<ContainerView> {
extra_links,
status_text,
status_set_at,
parent,
});
}
out