job_queue: stop inventing wire data just to preserve the old source tag

This commit is contained in:
damocles 2026-08-03 20:25:47 +02:00 committed by mara
commit b04e7d985d
2 changed files with 19 additions and 38 deletions

View file

@ -308,15 +308,6 @@ impl hive_jobq_wire::WireNode for NodeKind {
{ {
data.insert("inputs".to_owned(), inputs.clone().into()); data.insert("inputs".to_owned(), inputs.clone().into());
} }
// The DAG container's own metadata — nowhere else on the wire, since
// `GraphNode` carries no DAG-level fields (a group root is an
// ordinary node). `hivectl` needs `source` for its progress line;
// `reason` rides along for free rather than adding a second variant
// later for the one field the first pass missed.
if let NodeKind::Dag { source, reason, .. } = self {
data.insert("source".to_owned(), source.as_str().into());
data.insert("reason".to_owned(), reason.clone().into());
}
// Not in the payload at all — the build log is keyed on node identity // Not in the payload at all — the build log is keyed on node identity
// in a side table, which is why `data` is handed the id. // in a side table, which is why `data` is handed the id.
if let Some(log) = crate::build_logs::global().and_then(|h| h.id_for_node(id)) { if let Some(log) = crate::build_logs::global().and_then(|h| h.id_for_node(id)) {

View file

@ -7,10 +7,11 @@
//! and a plain line-on-change stream otherwise. //! and a plain line-on-change stream otherwise.
//! //!
//! Consumes `hive-jobq-wire`'s generic [`GraphNode`]/`NodePayload`: a //! Consumes `hive-jobq-wire`'s generic [`GraphNode`]/`NodePayload`: a
//! node's kind comes from `payload.label`, and node-specific extras //! node's kind (and, for a root, what submitted it) comes straight off
//! (`source`, `agent`) ride in `payload.data`'s opaque kvps — the shape //! `payload.label`; node-specific extras (`agent`) ride in
//! `hive-c0re`'s `impl WireNode for NodeKind` produces (see its doc //! `payload.data`'s opaque kvps — the shape `hive-c0re`'s
//! comment). There's no separate roll-up field on the wire: a node's own //! `impl WireNode for NodeKind` produces (see its doc comment). There's
//! no separate roll-up field on the wire: a node's own
//! `state` already reflects everything below it (see `hive_jobq_wire`'s //! `state` already reflects everything below it (see `hive_jobq_wire`'s
//! doc comment), so nothing here re-derives a roll-up or waits for every //! doc comment), so nothing here re-derives a roll-up or waits for every
//! node to go terminal before reading a result. //! node to go terminal before reading a result.
@ -110,7 +111,7 @@ async fn wait_for_nodes_plain(socket: &Path, ids: Vec<u64>) -> Result<()> {
} }
if root.state.is_terminal() { if root.state.is_terminal() {
if root.state == State::Failed { if root.state == State::Failed {
failed.push(format!("{} {}", node_source(root), node_agents(nodes))); failed.push(format!("{} {}", root.payload.label, node_agents(nodes)));
} }
pending.remove(&id); pending.remove(&id);
} }
@ -174,7 +175,7 @@ async fn wait_for_nodes_animated(socket: &Path, ids: Vec<u64>) -> Result<()> {
hdr.set_message(format!( hdr.set_message(format!(
"{} {} {} · {}", "{} {} {} · {}",
state_glyph(root.state), state_glyph(root.state),
node_source(root), root.payload.label,
node_agents(nodes), node_agents(nodes),
fmt_dur(node_elapsed(root, now)), fmt_dur(node_elapsed(root, now)),
)); ));
@ -203,7 +204,7 @@ async fn wait_for_nodes_animated(socket: &Path, ids: Vec<u64>) -> Result<()> {
} }
if root.state.is_terminal() { if root.state.is_terminal() {
if root.state == State::Failed { if root.state == State::Failed {
failed.push(format!("{} {}", node_source(root), node_agents(nodes))); failed.push(format!("{} {}", root.payload.label, node_agents(nodes)));
} }
pending.remove(&id); pending.remove(&id);
} }
@ -236,18 +237,6 @@ fn finish_wait(mut failed: Vec<String>) -> Result<()> {
} }
} }
/// A root's `source` tag (`"manual"`, `"meta_update"`, …), read from its
/// `payload.data` — see `hive-c0re`'s `impl WireNode for NodeKind`'s
/// `NodeKind::Dag` arm, the one place it's put on the wire. Falls back to
/// the label when absent (defensive; every real root sets it).
fn node_source(root: &GraphNode) -> &str {
root.payload
.data
.get("source")
.and_then(serde_json::Value::as_str)
.unwrap_or(&root.payload.label)
}
/// Distinct agents across a root's nodes, comma-joined for display. Each /// Distinct agents across a root's nodes, comma-joined for display. Each
/// node's `agent` (when it targets one) rides in `payload.data["agent"]` — /// node's `agent` (when it targets one) rides in `payload.data["agent"]` —
/// an opaque kvp, not a typed field, since `GraphNode` carries nothing /// an opaque kvp, not a typed field, since `GraphNode` carries nothing
@ -348,9 +337,9 @@ fn state_glyph(state: State) -> &'static str {
} }
} }
/// One progress line for a root: roll-up glyph, `source`, agents, then the /// One progress line for a root: roll-up glyph, label, agents, then the
/// node chain — the CLI twin of the dashboard's queue card. The glyph and /// node chain — the CLI twin of the dashboard's queue card. The glyph and
/// `source` come off `root`; everything else comes straight off the wire. /// label come off `root`; everything else comes straight off the wire.
/// Used by the plain (non-TTY) path. /// Used by the plain (non-TTY) path.
/// ///
/// The chain shows every entry `wire_snapshot` sends, `Done` ones /// The chain shows every entry `wire_snapshot` sends, `Done` ones
@ -363,7 +352,7 @@ fn render_node_line(root: &GraphNode, nodes: &[GraphNode]) -> String {
let mut out = format!( let mut out = format!(
"{} {} {:<12}", "{} {} {:<12}",
state_glyph(root.state), state_glyph(root.state),
node_source(root), root.payload.label,
node_agents(nodes) node_agents(nodes)
); );
let children: Vec<&GraphNode> = nodes.iter().filter(|n| n.id != root.id).collect(); let children: Vec<&GraphNode> = nodes.iter().filter(|n| n.id != root.id).collect();
@ -386,7 +375,7 @@ mod tests {
use super::render_node_line; use super::render_node_line;
fn root_node(id: u64, source: &str, state: State) -> GraphNode { fn root_node(id: u64, label: &str, state: State) -> GraphNode {
GraphNode { GraphNode {
id, id,
parent: None, parent: None,
@ -397,8 +386,8 @@ mod tests {
finished_at: None, finished_at: None,
error: None, error: None,
payload: NodePayload { payload: NodePayload {
label: "job".to_owned(), label: label.to_owned(),
data: json!({ "source": source }), data: json!({}),
}, },
} }
} }
@ -421,11 +410,12 @@ mod tests {
} }
#[test] #[test]
fn render_node_line_shows_source_and_chain() { fn render_node_line_shows_label_and_chain() {
// The header shows what the backend sends: the glyph off `root`'s // The header shows what the backend sends: the glyph off `root`'s
// own `state` (`Running` here) + `source` ("manual"); the operation // own `state` (`Running` here) + `root`'s own `payload.label`
// is read off the node chain, not a client-side label. `Done` // ("manual"); the operation is read off the node chain, not any
// nodes stay in the chain — nothing here filters them off. // extra metadata. `Done` nodes stay in the chain — nothing here
// filters them off.
let root = root_node(7, "manual", State::Running); let root = root_node(7, "manual", State::Running);
let nodes = vec![ let nodes = vec![
root.clone(), root.clone(),