jobq: delete DagView/NodeView, the second projection of one graph

Two views of the same graph existed: the typed `DagView`/`NodeView`
(`/api/state.rebuild_queue`, the `QueueDag` socket request, and the
`RebuildQueueChanged` payload) and `hive-jobq-wire`'s generic
`GraphNode` (`/api/jobq/graph`, `QueueNodes`). Every consumer has moved
to the generic one, so the typed pair is deleted rather than kept in
agreement with it.

What that removes, beyond the types: the `QueueDag` request and
`HostResponse::dags`; `Queue::snapshot`; `dag_view`, `visible_dags`,
`shown_on_wire`, `dag_finished_at` and `containers`; and the
`rebuild_queue` field on `/api/state`. `RebuildQueueChanged` keeps its
seq and loses its payload — nothing read it, and shipping the graph
both on an event and on an endpoint is the duplication this issue is
about. It stays an event rather than becoming a poll because
push-on-change is what every other live surface here does.

Two behaviours came out simpler for a structural reason. `await_dags`
needed two rules — settled means "gone from the snapshot" *or* "present
with every node terminal" — because the typed view evicted finished
groups; the generic view doesn't, so pending is just "some node isn't
terminal". And `state_of` in the tests no longer derives a roll-up at
all: a group root's own state is the scheduler's answer.

That second one found a bug. `cancelled_dag_still_runs_its_approval
tail` asserted the group reads `Cancelled` while the tail it exists to
protect was still pending — `rollup_state` flattened the surviving
child away and called the group settled. The root reads `Finishing`,
which is what the scheduler documents: own logic done, children still
running. The test now asserts that, with the reasoning inline so it
doesn't get "fixed" back.

Kept: `Source`, `State`, `PermPayload` and the `NodeId` alias in
`hive-host-sock::jobs` — shared vocabulary, still used by hivectl.
This commit is contained in:
atlas 2026-08-03 21:07:58 +02:00 committed by mara
commit f707c60f90
10 changed files with 131 additions and 615 deletions

View file

@ -210,15 +210,9 @@ pub enum HostRequest {
/// matrix GUI disabled). Backs `hivectl open` + the federation
/// peer-config block (which reads the bare `domain`).
Urls,
/// Fetch one job-queue DAG by id — the polling surface behind
/// `hivectl`'s wait/progress loop. A multi-step op is a single DAG
/// (its whole graph in `nodes`). Result: [`HostResponse::dags`].
QueueDag { id: u64 },
/// Fetch one or more job-queue nodes plus their live subtrees, as
/// generic `hive-jobq-wire` nodes — `hivectl`'s wait/progress loop.
/// Sibling of [`Self::QueueDag`]: same graph, through the generic
/// projection instead of the typed `DagView`/`NodeView` (kept for
/// `QueueDag`'s other consumer, `/api/state.rebuild_queue`). No
/// generic `hive-jobq-wire` nodes — `hivectl`'s wait/progress loop,
/// and the only way the queue is served. No
/// assumption that an id names a DAG container or root — whatever
/// node has that id, the backend hands back its subtree as-is. A
/// batch op that submits several independent roots (e.g. one per
@ -542,23 +536,16 @@ pub struct HostResponse {
/// different question".
#[serde(default, skip_serializing_if = "Option::is_none")]
pub agent_exists: Option<bool>,
/// Ids of the job-queue DAGs this request submitted (rebuild /
/// Ids of the job-queue roots this request submitted (rebuild /
/// restart / power ops). Clients poll them via
/// [`HostRequest::QueueDag`]; `None` for non-submitting requests.
/// [`HostRequest::QueueNodes`]; `None` for non-submitting requests.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub queued_dags: Option<Vec<u64>>,
/// `QueueDag` result — the requested DAG followed by its live
/// fan-out children ([`jobs::DagView`]). Empty when the DAG has
/// been evicted from the queue's history tail.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub dags: Option<Vec<jobs::DagView>>,
/// `QueueNodes` result — the requested nodes plus their live subtrees,
/// as generic `hive-jobq-wire` nodes, all roots' subtrees combined in
/// one flat list. `None` for every other request kind. An id with no
/// live node in the graph is silently dropped rather than erroring
/// the whole batch — see `JobQueue::node_subtrees`'s doc comment for
/// why a *completed* DAG's nodes don't vanish the same way
/// `QueueDag`'s do; callers should read each root node's `state` for
/// the whole batch — callers should read each root node's `state` for
/// terminality, not absence.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub nodes: Option<Vec<hive_jobq_wire::GraphNode>>,
@ -652,16 +639,6 @@ impl HostResponse {
}
}
/// `QueueDag` result — the polled DAG + its live children.
#[must_use]
pub fn dags(dags: Vec<jobs::DagView>) -> Self {
Self {
ok: true,
dags: Some(dags),
..Self::default()
}
}
/// `QueueNodes` result — the polled node + its subtree, as generic
/// wire nodes.
#[must_use]