job_queue: retire the now-off-wire step sub-step label

The `step` label was taken off the wire in #2661, when each deploy phase
became a first-class DAG node. Since then it has been written but never
read: `NodeRuntime` derives only `Debug, Default, Clone` — no serde — so
the field could not reach any client, and the only reads of it were the
dedup checks inside its own setters. This deletes the machinery.

Removed:

- `NodeRuntime.step`, `set_step`, `set_step_running`, and the
  `rt.step = None` clear in `complete_node`. `NodeRuntime` keeps its
  remaining `build_log_id` field (deliberately still a struct — collapsing
  it to a bare `Option<i64>` would churn every call site for no gain).
- `Ctx::step` and its ~15 call sites in `job_queue/exec.rs`. `Ctx` itself
  stays: it is the build-log sink, which `run_prebuild` and `run_swap`
  still use.
- `Coordinator::set_queue_step` and its 11 callers in `actions.rs`.
- `JobQueue::running_node_of`, reachable only from `set_queue_step`.
- `swap_update`'s `on_step` parameter and its one body call.
- The `set_step_only_on_running_and_signals_change` test.

Dropping the calls orphaned parameters, which are removed with their call
sites: `ctx` on ten executors that used it only as a step sink, and
`queue_entry_id` on `run_deploy_merge_verify` / `run_deploy_apply` /
`run_finalize_deploy` plus both `coord` and `queue_entry_id` on
`prepare_applied_target`. `run_deploy_tail` KEEPS its `queue_entry_id` —
that one has a genuine surviving use (the build-log link in the failure
comment posted to the PR).

One behavioural change, called out so it is not mistaken for a dropped
dashboard refresh: `Ctx::step` and `set_queue_step` each emitted a
`rebuild_queue_changed` snapshot when the label changed, and those
emissions go away with them. This is safe — the snapshot payload has no
step field, so those pushes carried nothing a client could observe. Real
state transitions still emit from the scheduler's claim and completion
paths, from `submit`, and from the three `actions.rs` sites. Net effect is
strictly fewer redundant SSE pushes.

Docs: `docs/coordinator.md` still listed `step` as a `NodeView` wire field
and `docs/web-ui/dashboard.md` documented a cyan `↳ <step>` sub-line under
each queue row. Neither has existed since #2661 — both corrected here, plus
the `job_queue/model.rs` module doc.

Not touched: `frontend/packages/dashboard/src/system-sections.css` has a
dead `.rqe-step` rule with no JS referencing it. Left for the frontend
owner rather than deleted here.

Closes: #2664
This commit is contained in:
atlas 2026-07-26 14:53:19 +02:00 committed by mara
commit 1db3cc32a1
9 changed files with 51 additions and 210 deletions

View file

@ -99,11 +99,10 @@ pub struct TerminalDag {
/// Per-node runtime metadata the crate graph doesn't carry. Lifecycle
/// (`started_at` / `finished_at` / `error`) lives on the `hive_jobq::Node`
/// itself now, so only the two host-side extras remain: the live sub-step
/// label and the build-log row link (the client fetches the log by node id).
/// itself now, so only the build-log row link remains host-side (the
/// client fetches the log by node id).
#[derive(Debug, Default, Clone)]
struct NodeRuntime {
step: Option<String>,
build_log_id: Option<i64>,
}
@ -129,8 +128,8 @@ struct DagMeta {
/// [`QueueInner::dag_meta`]). One shared crate [`Graph`] holds every DAG.
struct QueueInner {
sched: Scheduler<NodeKind, Resource>,
/// Per-node runtime metadata (build-log id, step, timestamps, error) —
/// mutable after insert, so it can't ride the immutable node payload.
/// Per-node runtime metadata (the build-log id) — mutable after
/// insert, so it can't ride the immutable node payload.
node_rt: HashMap<NodeId, NodeRuntime>,
}
@ -405,14 +404,11 @@ impl JobQueue {
let mut inner = self.lock();
// The failure reason + `finished_at` are stamped onto the graph `Node`
// by the scheduler (the reason rides `Outcome::Failed`); no host-side
// copy. We only clear the live sub-step label here.
// copy, so there is nothing to clear here.
let outcome = match result {
Ok(()) => Outcome::Done,
Err(e) => Outcome::Failed(truncate_error(&e)),
};
if let Some(rt) = inner.node_rt.get_mut(&node_id) {
rt.step = None;
}
let container = inner.dag_of(node_id);
inner.sched.complete(node_id, outcome);
// If this completion rolled the DAG's container up to a terminal state,
@ -461,35 +457,6 @@ impl JobQueue {
terminal
}
/// Set the step label on a `Running` node. Returns `true` when it changed.
pub fn set_step(&self, dag_id: u64, node_id: NodeId, step: &str) -> bool {
let mut inner = self.lock();
if inner.dag_of(node_id).map(NodeId::get) != Some(dag_id) || !inner.node_running(node_id) {
return false;
}
let rt = inner.node_rt.entry(node_id).or_default();
if rt.step.as_deref() == Some(step) {
return false;
}
rt.step = Some(step.to_owned());
true
}
/// Set the step label on the DAG's currently-running node — the DAG-id-only
/// compatibility surface for the opaque approval pipeline.
pub fn set_step_running(&self, dag_id: u64, step: &str) -> bool {
let mut inner = self.lock();
let Some(node_id) = inner.running_node_of(dag_id) else {
return false;
};
let rt = inner.node_rt.entry(node_id).or_default();
if rt.step.as_deref() == Some(step) {
return false;
}
rt.step = Some(step.to_owned());
true
}
/// Link a `build_logs` row to a specific `Running` node.
pub fn set_build_log_id(&self, dag_id: u64, node_id: NodeId, log_id: i64) -> bool {
let mut inner = self.lock();
@ -670,15 +637,6 @@ impl QueueInner {
})
}
/// The DAG's currently-running work node, if any (the opaque approval
/// pipeline's single-node DAGs make this exact).
fn running_node_of(&self, dag_id: u64) -> Option<NodeId> {
let container = self.container(dag_id)?;
self.subtree(container)
.into_iter()
.find(|&id| self.node_running(id))
}
/// Roll-up state over a DAG's work nodes: `Failed` if any failed; else
/// `Running` if any running; else `Queued` if any queued; else `Cancelled`
/// if any cancelled; else `Done`. (Kept eager over the subtree — a failed