docs: stop asserting DagView/NodeView after their deletion

The deletion PR removed the types but left ~10 sites still describing
them. Two are real breakage rather than staleness: rustdoc intra-doc
links to deleted items ([NodeView::kind] and [Self::snapshot] in
job_queue/mod.rs). Neither clippy --all-targets -D warnings nor cargo
test resolves intra-doc links, so the tree was green with both already
dangling.

The rest reassert facts the deletion made false: docs/coordinator.md
documented the event as RebuildQueueChanged { seq, queue: [DagView...] }
with a per-node field list, and three sites pointed at the removed
/api/state.rebuild_queue endpoint.

One is pointer rot rather than a rename, and no grep for a deleted name
finds it: SchedulesChanged justified itself as "same snapshot-shape
rationale as RebuildQueueChanged" -- which the deletion turned into the
one event that is not a snapshot. Repointed at TombstonesChanged /
MetaInputsChanged, in both the Rust doc and the dashboard doc.

Two are pre-existing and strictly out of scope, swept under the
pfadfinderregel because the same grep surfaced them: hive-sh4re/README
advertised a jobs module that crate has not had since the host-sock
split, and hive-host-sock/README claimed its own payload types live in
hive-sh4re.

Docs and comments only -- no behaviour, no API, no test changes.
This commit is contained in:
atlas 2026-08-03 21:47:51 +02:00
commit 730c923a97
10 changed files with 67 additions and 47 deletions

View file

@ -441,8 +441,8 @@ the config PR, and for a spawn runs the post-spawn forge bookkeeping.
Two visible consequences: Two visible consequences:
- **Operator dashboard**: after clicking APPR0VE the work-in-progress - **Operator dashboard**: after clicking APPR0VE the work-in-progress
shows up on the *rebuild queue* card (`/api/state.rebuild_queue` shows up on the *rebuild queue* card (`GET /api/jobq/graph`, refetched
+ live `rebuild_queue_changed` events), not on the approvals panel on every `rebuild_queue_changed` tick), not on the approvals panel
(which already moved the row to "approved"). A long meta-update (which already moved the row to "approved"). A long meta-update
cascade renders as a parent DAG with one child rebuild per affected cascade renders as a parent DAG with one child rebuild per affected
agent — see `docs/web-ui.md` for the layout. agent — see `docs/web-ui.md` for the layout.

View file

@ -60,7 +60,7 @@ Cheap — no build slot:
| `Drain` | await the harness clearing the fence, bounded by the 3-min graceful-stop timeout; resolves ok either way | | `Drain` | await the harness clearing the fence, bounded by the 3-min graceful-stop timeout; resolves ok either way |
| `WriteDropin` | `set_nspawn_flags` + `set_resource_limits` + daemon-reload | | `WriteDropin` | `set_nspawn_flags` + `set_resource_limits` + daemon-reload |
| `WritePermFile` | commit `tool-groups.json` / `capabilities.json` (single git commit under `META_LOCK`) + emit the P3RM1SS10NS snapshots | | `WritePermFile` | commit `tool-groups.json` / `capabilities.json` (single git commit under `META_LOCK`) + emit the P3RM1SS10NS snapshots |
| `Reparent` | `set-parent` / `set-parent-bulk`: apply every `(child, new_parent)` move under one `META_LOCK` commit (`meta::bulk_commit_topology`), send the per-agent move notifications, rescan + diff-emit. Agentless like `MetaLock` — a bulk move can span multiple agents, and a reparent touches the meta repo, not any one container. `moves` is `(Ident, Option<Ident>)` pairs, not raw strings — mara: "use Ident type instead of string" (#2719, issuecomment 42691). Rides `Template::MetaUpdate` rather than a dedicated `Template` variant — that enum is on its way out (see `#2665`, still open/blocked on a scope question) and is already internal-only (not on `DagView`'s wire shape), so the stand-in only affects `terminal_hook` dispatch (resolves to no hook either way) and history-retention bucketing | | `Reparent` | `set-parent` / `set-parent-bulk`: apply every `(child, new_parent)` move under one `META_LOCK` commit (`meta::bulk_commit_topology`), send the per-agent move notifications, rescan + diff-emit. Agentless like `MetaLock` — a bulk move can span multiple agents, and a reparent touches the meta repo, not any one container. `moves` is `(Ident, Option<Ident>)` pairs, not raw strings — mara: "use Ident type instead of string" (#2719, issuecomment 42691). Rides `Template::MetaUpdate` rather than a dedicated `Template` variant — that enum is on its way out (see `#2665`, still open/blocked on a scope question) and is already internal-only (it never reaches the graph wire), so the stand-in only affects `terminal_hook` dispatch (resolves to no hook either way) and history-retention bucketing |
There is deliberately **no `GitCommit` node**: `meta.rs` fuses each mutation There is deliberately **no `GitCommit` node**: `meta.rs` fuses each mutation
with its commit under its internal `META_LOCK` mutex, so a standalone commit with its commit under its internal `META_LOCK` mutex, so a standalone commit
@ -252,16 +252,26 @@ cancelled-while-queued, which fails the approval instead of dangling it).
### Wire shape ### Wire shape
`RebuildQueueChanged { seq, queue: [DagView…] }` (event name kept). Each `RebuildQueueChanged { seq }` (event name kept) — **a bare trigger, no
`DagView` carries the entry-level fields (`id`, `kind` = template string, payload.** It says *the queue changed*; a client that wants to know how
roll-up `state`, `source`, `reason`, timestamps, `inputs`, re-fetches `GET /api/jobq/graph`.
`approval_id`) plus `nodes: [NodeView…]` — per-node `agent`, `kind`, `deps`,
`state`, `build_log_id`, timestamps, `error`. There is **no That endpoint serves the graph generically (`hive-jobq-wire`): every node
DAG-level `agent`** (agent is per-node, so a DAG can span agents); consumers carries `id`, `parent`, `deps`, `state`, `label` (the node kind's own wire
derive a DAG's agent(s) from its nodes. The node kind *is* the phase label — string — the kind *is* the phase label, there is no separate sub-step
there is no separate sub-step string; build logs are **per-node**. The string) and free-form `data` for what only some kinds have (`agent`,
dashboard renders the node chain on each queue card and keys the live-log `approval_id`, `inputs`, `build_log_id`). Group roots ride as ordinary
panel off the running node. nodes, so a group's state is just the root's own `state`.
There is **no group-level `agent`** — agent is per-node, so one group can
span agents; consumers derive a group's agent(s) from its nodes. Build logs
are likewise **per-node**: the dashboard renders the node tree and keys the
live-log panel off the running node.
The event used to ship the whole queue as a typed `DagView`/`NodeView`
projection. That was a second rendering of the same graph, kept in
agreement by hand with the endpoint every consumer actually read; it is
gone, and the event's whole job is now telling a client *when* to refetch.
--- ---

View file

@ -225,12 +225,10 @@ does not render the queue itself; it just mounts the element and
listens for its `hive-jobq-graph-update` event to drive the two things listens for its `hive-jobq-graph-update` event to drive the two things
below it that the generic view doesn't show. The component owns below it that the generic view doesn't show. The component owns
fetching, cold and live: `GET /api/jobq/graph` on mount, and fetching, cold and live: `GET /api/jobq/graph` on mount, and
`.refresh()` on every `rebuild_queue_changed` SSE tick (that event `.refresh()` on every `rebuild_queue_changed` SSE tick (that event is a
still carries its own `Vec<QueueEntry>` payload — `DagView`-shaped, bare `{ seq }` trigger — it carried a typed queue snapshot until every
also read by `hivectl`'s own wait/progress loop, a separate migration consumer had moved to the generic endpoint, and now carries none; both
— on the wire, but neither dashboard page reads it anymore; both dashboard pages treat the tick as a pure refetch trigger).
treat the tick as a pure refetch trigger against the generic
endpoint).
Each row is one root graph node (`parent: null`); a multi-step op's Each row is one root graph node (`parent: null`); a multi-step op's
per-agent subgraphs and sub-steps render as nodes within that one per-agent subgraphs and sub-steps render as nodes within that one
@ -900,7 +898,8 @@ fetch entirely.
life). Two consequences for anything rendering it: life). Two consequences for anything rendering it:
- The label vocabulary is **open** — it is the node's own wire tag - The label vocabulary is **open** — it is the node's own wire tag
(`NodeKind::as_str`, the same strings `NodeView.kind` carries), (`NodeKind::as_str`, the same strings the graph wire's node
labels carry),
not a fixed set. Treat it as an opaque display string; do not not a fixed set. Treat it as an opaque display string; do not
switch on specific values. `restarting` in particular no longer switch on specific values. `restarting` in particular no longer
exists, because no node kind is unique to a restart. exists, because no node kind is unique to a restart.
@ -1441,19 +1440,19 @@ payload):
`crash_watch` poll. Client upserts/removes by name; the `crash_watch` poll. Client upserts/removes by name; the
pending overlay is read from `transientsState` since the pending overlay is read from `transientsState` since the
payload doesn't carry it. payload doesn't carry it.
- `rebuild_queue_changed` (seq, queue: `Vec<QueueEntry>`) — - `rebuild_queue_changed` (seq) — **payload-free trigger**, fired on
full snapshot of the rebuild queue on every mutation (enqueue, every queue mutation (enqueue, state transition, dedup collapse,
state transition, dedup collapse, terminal-history trim). terminal-history trim). Unlike the snapshot events below it ships
Same snapshot-over-diff rationale as `tombstones_changed` / no state at all: the client re-fetches `GET /api/jobq/graph`, which
`meta_inputs_changed`: the list is small and the client renders is where it reads the queue from cold too. There is no
each DAG's multi-agent shape from its own `nodes` (no cross-DAG `/api/state.rebuild_queue` — it went with the typed projection.
grouping). Cold-loaded from `/api/state.rebuild_queue`.
- `schedules_changed` (seq, schedules: `Vec<WireSchedule>`) — - `schedules_changed` (seq, schedules: `Vec<WireSchedule>`) —
full snapshot of all scheduled prompts. Emitted after every full snapshot of all scheduled prompts. Emitted after every
operator mutation via the `/api/schedules` surface (new / operator mutation via the `/api/schedules` surface (new /
edit / cancel / fire-now) and after the worker fires or edit / cancel / fire-now) and after the worker fires or
rearms a row. Same snapshot-shape rationale as rearms a row. Same snapshot-shape rationale as
`rebuild_queue_changed`. The SCH3DUL3S tab subscribes and `tombstones_changed` / `meta_inputs_changed` (small list, no
add/remove races). The SCH3DUL3S tab subscribes and
re-renders `schedulesState` on receipt; tab activation still re-renders `schedulesState` on receipt; tab activation still
re-fetches as a safety net for approval-path inserts and re-fetches as a safety net for approval-path inserts and
disconnect windows. disconnect windows.

View file

@ -635,10 +635,14 @@ impl Coordinator {
} }
} }
/// Emit a `RebuildQueueChanged` snapshot event. Called from the /// Emit a `RebuildQueueChanged` tick. Called from the queue mutation
/// queue mutation helpers (`enqueue` / `finish` / `cancel`-adjacent /// helpers (`enqueue` / `finish` / `cancel`-adjacent wrappers below) and
/// wrappers below) and the worker so every state transition /// the worker so every state transition surfaces on the dashboard without
/// surfaces on the dashboard without extra plumbing. /// extra plumbing.
///
/// Carries no queue payload — clients refetch `/api/jobq/graph`. The name
/// keeps `snapshot` because *that* is still what a client ends up with;
/// what changed is who serves it.
pub fn emit_rebuild_queue_snapshot(self: &Arc<Self>) { pub fn emit_rebuild_queue_snapshot(self: &Arc<Self>) {
self.emit_dashboard_event(DashboardEvent::RebuildQueueChanged { self.emit_dashboard_event(DashboardEvent::RebuildQueueChanged {
seq: self.next_seq(), seq: self.next_seq(),

View file

@ -136,7 +136,7 @@ pub(super) async fn get_build_log_full(
/// ///
/// Same `BuildLogFull` JSON (`stdout` / `stderr` + header) as /// Same `BuildLogFull` JSON (`stdout` / `stderr` + header) as
/// `get_build_log_full`; HTTP 404 when the node has no linked log (the /// `get_build_log_full`; HTTP 404 when the node has no linked log (the
/// client gates the request on `NodeView.build_log_id`, but a vacuum /// client gates the request on the wire node's `build_log_id`, but a vacuum
/// race can still 404). This is the on-demand live-log-panel fetch, /// race can still 404). This is the on-demand live-log-panel fetch,
/// distinct from the `build_log_id` on the wire — that id is for /// distinct from the `build_log_id` on the wire — that id is for
/// deep-linking to the BUILD L0GS tab's full history view, not for /// deep-linking to the BUILD L0GS tab's full history view, not for

View file

@ -233,7 +233,8 @@ pub enum DashboardEvent {
/// Full snapshot of all scheduled prompts. Emitted after every /// Full snapshot of all scheduled prompts. Emitted after every
/// operator mutation (new / edit / cancel / fire-now) and after the /// operator mutation (new / edit / cancel / fire-now) and after the
/// worker fires or rearms a row. Same snapshot-shape rationale as /// worker fires or rearms a row. Same snapshot-shape rationale as
/// `RebuildQueueChanged` — the list is small and the client's /// `TombstonesChanged` / `MetaInputsChanged` — the list is small and the
/// client's
/// per-target `last_result` / `last_fired_at_unix` fields are most /// per-target `last_result` / `last_fired_at_unix` fields are most
/// naturally re-derived from the full list. /// naturally re-derived from the full list.
SchedulesChanged { SchedulesChanged {

View file

@ -308,9 +308,10 @@ impl JobQueue {
/// so both are running and both name `a`. Anything keying this set by agent /// so both are running and both name `a`. Anything keying this set by agent
/// alone will silently drop one — see [`super::scheduler`]. /// alone will silently drop one — see [`super::scheduler`].
/// ///
/// `label` is the node's own wire tag ([`NodeKind::as_str`]), the vocabulary /// `label` is the node's own wire tag ([`NodeKind::as_str`]), the same
/// [`NodeView::kind`] already ships, so a pill and a DAG node name an /// vocabulary the graph wire ships as a node's label, so a pill and a
/// operation identically. `takes_container_down` is the crash watcher's /// graph node name an operation identically. `takes_container_down` is the
/// crash watcher's
/// input and does **not** ride the wire to the frontend — a `Start` pill and /// input and does **not** ride the wire to the frontend — a `Start` pill and
/// a `Stop` pill are both pills; only one means a vanished container is /// a `Stop` pill are both pills; only one means a vanished container is
/// expected. /// expected.
@ -381,10 +382,10 @@ impl JobQueue {
/// One or more nodes plus their live subtrees, as generic wire nodes — /// One or more nodes plus their live subtrees, as generic wire nodes —
/// the `QueueNodes` polling surface behind `hivectl`'s wait/progress /// the `QueueNodes` polling surface behind `hivectl`'s wait/progress
/// loop. Sibling of [`Self::snapshot`] (which serves the same graph /// loop. Goes through [`GraphWire::wire_snapshot`] — the same projection
/// through the typed `DagView`/`NodeView` projection for the /// [`Self::graph_snapshot`] serves the dashboard with, differing only in
/// dashboard's `/api/state.rebuild_queue`), this one goes through /// *which* nodes it selects (caller-named ids and their subtrees, rather
/// [`GraphWire::wire_snapshot`] instead — no `Done`-node filtering, no /// than every visible root). No `Done`-node filtering, no
/// roll-up field (a node's own `state` answers that, see /// roll-up field (a node's own `state` answers that, see
/// `hive_jobq_wire`'s doc comment). Looks each id up by identity /// `hive_jobq_wire`'s doc comment). Looks each id up by identity
/// alone — no assumption that it names a DAG container or a root; /// alone — no assumption that it names a DAG container or a root;

View file

@ -322,7 +322,8 @@ impl hive_jobq_wire::WireNode for NodeKind {
} }
impl NodeKind { impl NodeKind {
/// Wire string for `NodeView.kind`. /// Wire string for the node's label on the graph wire
/// ([`hive_jobq_wire::WireNode::label`]).
pub fn as_str(&self) -> &'static str { pub fn as_str(&self) -> &'static str {
match self { match self {
NodeKind::MetaSync { .. } => "meta_sync", NodeKind::MetaSync { .. } => "meta_sync",

View file

@ -15,9 +15,15 @@ thin dependency possible.
## Shape ## Shape
Serde-derived request/response enums for the host admin protocol. The larger Serde-derived request/response enums for the host admin protocol. The larger
shared payload types some variants reference (`Approval`, `AgentStatusRow`, shared payload types some variants reference (`Approval`, `AgentStatusRow`)
`jobs::DagView`) stay in `hive-sh4re` — this crate is only the protocol stay in `hive-sh4re` — this crate is only the protocol envelope, no server or
envelope, no server or client implementation. client implementation.
Its own `jobs` module is the exception: the job-queue vocabulary `hivectl`
needs (`Source`, `State`, `PermPayload`, `NodeId`) is protocol-local. The typed
`DagView`/`NodeView` projection that used to live there is gone — the queue is
served as a generic graph (`hive-jobq-wire`), not as a second hand-written
view.
See `docs/boundary.md` (host admin socket access) for the trust model around who See `docs/boundary.md` (host admin socket access) for the trust model around who
may connect to the socket, and `hive-priv-sock` for the sibling split on the may connect to the socket, and `hive-priv-sock` for the sibling split on the

View file

@ -21,8 +21,6 @@ Those crates re-export or reference the payload types that still live here
## Modules ## Modules
- **`jobs`** — the job-queue wire types (`DagView`) surfaced to the dashboard SSE
stream + hivectl.
- **`wire_time`** — the timestamp convention: wire fields are - **`wire_time`** — the timestamp convention: wire fields are
`chrono::DateTime<Utc>` (serialized RFC 3339), while sqlite storage + input `chrono::DateTime<Utc>` (serialized RFC 3339), while sqlite storage + input
args stay unix-epoch `i64`; this module owns the two boundary conversions. args stay unix-epoch `i64`; this module owns the two boundary conversions.