refactor(#2441): update queue consumers + docs for agent-per-node

DagView no longer has a DAG-level agent, so consumers derive it from the
per-node agents:
- hivectl dag_progress.rs: dag_agents(d) helper (distinct node agents,
  comma-joined) in place of d.agent.
- dashboard builds.js: entryAgents(entry) helper likewise for the
  rebuild-queue card + live-log header + cancel confirms.
- docs/coordinator.md: lease prose (node-agent-keyed, global per agent),
  wire shape (NodeView.agent, no DagView.agent), and dropped the removed
  dedup section.
This commit is contained in:
atlas 2026-07-14 21:42:37 +02:00 committed by mara
commit e2b48d2014
3 changed files with 66 additions and 36 deletions

View file

@ -22,7 +22,7 @@ The **DAG** is the unit of dedup / cancel / approval-resolution and the
dashboard group; the **node** is the unit of scheduling / execution /
build-log / step label. Deps are intra-DAG edges only (`AfterOk` by default:
the dep must succeed, a failed/cancelled dep cancels the dependent —
cancel-downstream). Cross-DAG ordering comes from the per-agent lease + dedup,
cancel-downstream). Cross-DAG ordering comes from the per-agent lease,
never from edges between DAGs. Submit-time validation (petgraph `toposort`)
rejects cyclic specs outright, fixing the old queue's "circular dep silently
deadlocks" caveat.
@ -139,11 +139,13 @@ resources are free. Resources:
1. **Build slots**`services.hyperhive.c0re.buildSlots` permits (default 1),
held by nix-heavy nodes for the node's duration.
2. **Per-agent lifecycle lease** — DAG-scoped: acquired at the DAG's first
container-affecting node (`StopForUpdate`, `Swap`, `Signal`, `Drain`,
`Reconcile`, `WriteDropin`, `Create`, `ApprovalDeploy`), held until the DAG
is terminal, so two lifecycle DAGs for one agent never interleave their
container ops. **Lease-exempt**: `Prebuild`, `MetaLock`, `WritePermFile`
2. **Per-agent lifecycle lease** — keyed on the **node's** agent (agent is
per-node; a DAG can span agents) and globally exclusive per agent across
all DAGs: acquired at a container-affecting node (`StopForUpdate`, `Swap`,
`Signal`, `Drain`, `Reconcile`, `WriteDropin`, `Create`, `ApprovalDeploy`),
held by the owning DAG until it's terminal, so two DAGs never interleave
container ops on the same agent. A DAG touching several agents holds one
lease per agent. **Lease-exempt**: `Prebuild`, `MetaLock`, `WritePermFile`
they touch the store / meta, not the running container, which is exactly
why a stop can land while another DAG's prebuild is still building.
@ -156,14 +158,11 @@ The queue is in-memory only and lost on hive-c0re restart — deliberate:
desired state is re-derived at boot from the DB + rev markers (see _Boot
reconcile_), so there is no durable-recovery machinery to go wrong.
### Dedup, cancel, history
### Cancel, history
Dedup at **DAG granularity**: a repeat submit against a DAG whose roll-up is
still `Queued` with the same `(template, agent, parent_id, approval_id)`
plus `inputs` for meta-updates and the perm-type discriminant for perm
changes — returns the existing id and appends an "also requested by …" line.
`parent_id` in the key keeps a cascade child from collapsing into a
standalone or sweep rebuild. Running/terminal DAGs never dedup.
Submit-time dedup was removed with the agent-per-node move (a multi-agent DAG
has no single agent to key a dedup on), so every submit enqueues a fresh DAG;
whether any dedup needs reintroducing is tracked as a follow-up.
Cancel only applies to still-fully-queued DAGs (an in-flight nix build isn't
interruptible); `cancel_children` cancels a parent's still-queued child DAGs.
@ -186,12 +185,14 @@ of dangling it).
### Wire shape
`RebuildQueueChanged { seq, queue: [DagView…] }` (event name kept). Each
`DagView` carries the old entry-level fields (`id`, `kind` = template string,
roll-up `state`, `agent`, `source`, `parent_id`, `reason`, timestamps,
`inputs`, `approval_id`) plus `nodes: [NodeView…]` — per-node `kind`, `deps`,
`state`, `step`, `build_log_id`, timestamps, `error`. Step labels and build
logs are **per-node**; the dashboard renders the node chain on each queue
card and keys the live-log panel off the running node.
`DagView` carries the entry-level fields (`id`, `kind` = template string,
roll-up `state`, `source`, `parent_id`, `reason`, timestamps, `inputs`,
`approval_id`) plus `nodes: [NodeView…]` — per-node `agent`, `kind`, `deps`,
`state`, `step`, `build_log_id`, timestamps, `error`. There is **no
DAG-level `agent`** (agent is per-node, so a DAG can span agents); consumers
derive a DAG's agent(s) from its nodes. Step labels and build logs are
**per-node**; the dashboard renders the node chain on each queue card and
keys the live-log panel off the running node.
---