From 0ee4647d1ef651328e8cd8993b5986e642a9e5fb Mon Sep 17 00:00:00 2001 From: atlas Date: Thu, 23 Jul 2026 17:56:00 +0200 Subject: [PATCH 1/2] =?UTF-8?q?refactor(#2591):=20drop=20the=20client-side?= =?UTF-8?q?=20DAG=20label=20map=20=E2=80=94=20show=20source=20+=20node=20k?= =?UTF-8?q?inds?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per mara (#2660): the client shouldn't re-derive a friendly name from node kinds — that re-bakes the domain knowledge the raw-graph redesign removed. The queue card / hivectl header now shows what the backend sends (the DAG's source + the raw node kinds); the node chain conveys the operation. Removes the DagView::label() helper (hivectl was its only consumer after iris dropped the frontend map in #2660) and points hivectl's header at d.source instead. --- hive-sh4re/src/jobs.rs | 28 ---------------------------- hivectl/src/dag_progress.rs | 16 ++++++++-------- 2 files changed, 8 insertions(+), 36 deletions(-) diff --git a/hive-sh4re/src/jobs.rs b/hive-sh4re/src/jobs.rs index fc854fa0..a3075977 100644 --- a/hive-sh4re/src/jobs.rs +++ b/hive-sh4re/src/jobs.rs @@ -192,32 +192,4 @@ impl DagView { State::Done } } - - /// A short human label for the DAG, derived from its node kinds — the - /// shared replacement for the old `Template` wire string now that the kind - /// isn't sent. Priority-ordered so the most distinctive node wins (an - /// approval deploy reads as "deploy" even though it also rebuilds). Purely - /// cosmetic (progress/queue display); consumers that need exactness inspect - /// the node kinds directly. - #[must_use] - pub fn label(&self) -> &'static str { - let has = |k: &str| self.nodes.iter().any(|n| n.kind == k); - if has("approval_deploy") { - "deploy" - } else if has("meta_lock") { - "meta-update" - } else if has("create") || has("provision") { - "spawn" - } else if has("swap") || has("prebuild") || has("post_swap") { - "rebuild" - } else if has("write_perm_file") { - "perm-change" - } else if has("signal") || has("drain") { - "graceful" - } else if has("set_wanted") || has("stop_for_update") { - "power" - } else { - "reconcile" - } - } } diff --git a/hivectl/src/dag_progress.rs b/hivectl/src/dag_progress.rs index 89ff638c..284a5adc 100644 --- a/hivectl/src/dag_progress.rs +++ b/hivectl/src/dag_progress.rs @@ -67,7 +67,7 @@ async fn wait_for_dags_plain(socket: &Path, ids: Vec) -> Result<()> { // sees whether the agent came back. if d.nodes.iter().all(|n| n.state.is_terminal()) { if d.rollup_state() == hive_sh4re::jobs::State::Failed { - failed.push(format!("{} {}", d.label(), dag_agents(d))); + failed.push(format!("{} {}", d.source.as_str(), dag_agents(d))); } } else { all_terminal = false; @@ -135,7 +135,7 @@ async fn wait_for_dags_animated(socket: &Path, ids: Vec) -> Result<()> { hdr.set_message(format!( "{} {} {} · {}", state_glyph(d.rollup_state()), - d.label(), + d.source.as_str(), dag_agents(d), fmt_dur(dag_elapsed(d, now)), )); @@ -164,7 +164,7 @@ async fn wait_for_dags_animated(socket: &Path, ids: Vec) -> Result<()> { } if d.nodes.iter().all(|n| n.state.is_terminal()) { if d.rollup_state() == hive_sh4re::jobs::State::Failed { - failed.push(format!("{} {}", d.label(), dag_agents(d))); + failed.push(format!("{} {}", d.source.as_str(), dag_agents(d))); } } else { all_terminal = false; @@ -290,16 +290,16 @@ fn state_glyph(state: hive_sh4re::jobs::State) -> &'static str { } } -/// One progress line for a DAG: roll-up glyph, derived label, agents, then -/// the node chain — the CLI twin of the dashboard's queue card. Both the -/// roll-up state and the label are derived from the node set (the wire no -/// longer carries them). Used by the plain (non-TTY) path. +/// One progress line for a DAG: roll-up glyph, `source`, agents, then the +/// node chain — the CLI twin of the dashboard's queue card. The header shows +/// what the backend sends (`source` + the raw node kinds); only the roll-up +/// state glyph is derived from the node set. Used by the plain (non-TTY) path. fn render_dag_line(d: &hive_sh4re::jobs::DagView) -> String { use std::fmt::Write as _; let mut out = format!( "{} {} {:<12}", state_glyph(d.rollup_state()), - d.label(), + d.source.as_str(), dag_agents(d) ); for (i, n) in d.nodes.iter().enumerate() { From c7feeb7f5b8d88e94fc299692b2ff091c317b196 Mon Sep 17 00:00:00 2001 From: atlas Date: Thu, 23 Jul 2026 17:57:32 +0200 Subject: [PATCH 2/2] test(#2591): assert hivectl header shows source, not a derived label --- hivectl/src/dag_progress.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/hivectl/src/dag_progress.rs b/hivectl/src/dag_progress.rs index 284a5adc..4d257800 100644 --- a/hivectl/src/dag_progress.rs +++ b/hivectl/src/dag_progress.rs @@ -337,10 +337,12 @@ mod tests { } #[test] - fn render_dag_line_shows_chain_with_derived_label_and_state() { - // Roll-up state (Running) + label ("rebuild") are derived from the node - // set — the wire no longer carries them. (`Done` nodes are included - // here to exercise glyph rendering; production filters them off.) + fn render_dag_line_shows_source_and_chain() { + // The header shows what the backend sends: the roll-up state glyph + // (derived — Running here) + the DAG `source` ("manual"); the operation + // is read off the node chain, not a client-side label. (`Done` nodes + // are included here to exercise glyph rendering; production filters + // them off.) let dag = DagView { id: 7, source: Source::Manual, @@ -356,7 +358,7 @@ mod tests { ], }; let line = render_dag_line(&dag); - assert!(line.starts_with("▶ rebuild alice"), "{line}"); + assert!(line.starts_with("▶ manual alice"), "{line}"); assert!( line.contains("✔ prebuild → ✔ stop_for_update → ▶ swap → ⏸ reconcile"), "{line}" @@ -377,7 +379,7 @@ mod tests { nodes: vec![failed], }; let line = render_dag_line(&dag); - assert!(line.contains("✖ rebuild"), "{line}"); + assert!(line.contains("✖ manual"), "{line}"); assert!(line.contains("— nix build exploded"), "{line}"); } }