From 9a39a548206a052a3d4c1497e16bbaa668dba4a5 Mon Sep 17 00:00:00 2001 From: atlas Date: Tue, 14 Jul 2026 22:29:09 +0200 Subject: [PATCH] refactor(#2449): dashboard + docs for the SetWanted node - builds.js NODE_KIND_LABEL: 'set wanted' (and the previously-missing 'noop') so the new head node renders with a label, not the raw kind. - docs/coordinator.md + templates.rs module doc: the power-op DAG shapes now show the head SetWanted node; SetWanted added to the lease-needing list with the atomicity rationale. --- docs/coordinator.md | 31 +++++++++++++++-------- frontend/packages/dashboard/src/builds.js | 2 ++ 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/docs/coordinator.md b/docs/coordinator.md index acd1f84b..8670e92a 100644 --- a/docs/coordinator.md +++ b/docs/coordinator.md @@ -74,14 +74,19 @@ container build: ### Every operation as a DAG +The power ops write the durable `wanted` intent via a head `SetWanted` +node (not a pre-submit side effect) — it holds the agent lease, so +intent-write + reconcile is atomic per-agent. + ```text rebuild(a): Prebuild(a) → StopForUpdate(a) → Swap(a) →(after-any) Reconcile(a) -graceful-stop(a): [wanted=Offline] Signal(a) → Drain(a) → Reconcile(a) -restart(a): [wanted=Up] StopForUpdate(a) → Reconcile(a) -graceful-restart(a): [wanted=Up] Signal(a) → Drain(a) → StopForUpdate(a) → Reconcile(a) -start(a): [wanted=Up] Reconcile(a) (stale rev ⇒ upgraded to rebuild) -stop(a): [wanted=Offline] Reconcile(a) -spawn(a): [wanted=Up] Create(a) → WriteDropin(a) → Reconcile(a) +graceful-stop(a): SetWanted(a,Off) → Signal(a) → Drain(a) → Reconcile(a) +restart(a): SetWanted(a,Up) → StopForUpdate(a) → Reconcile(a) +graceful-restart(a): SetWanted(a,Up) → Signal(a) → Drain(a) → StopForUpdate(a) → Reconcile(a) +start(a): SetWanted(a,Up) → Reconcile(a) (stale rev ⇒ stale-start below) +stale-start(a): SetWanted(a,Up) → «rebuild subgraph» (prebuild noops — agent is down) +stop(a): SetWanted(a,Off) → Reconcile(a) +spawn(a): [wanted=Up at approve] Create(a) → WriteDropin(a) → Reconcile(a) perm-change(a): WritePermFile(a) → «rebuild subgraph» meta-update(inp): MetaLock(inp) → «fan-out rebuild(a) per affected agent» boot: (if any rev marker stale) MetaLock(hyperhive) → «fan-out rebuild»; @@ -141,11 +146,15 @@ resources are free. Resources: held by nix-heavy nodes for the node's duration. 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` — + all DAGs: acquired at a container-affecting node (`SetWanted`, + `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. (`SetWanted` is a store + write, not a container op, but takes the lease anyway so a power-op DAG's + intent write + reconcile is atomic — two racing ops can't clobber intent + before either reconciles.) **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. diff --git a/frontend/packages/dashboard/src/builds.js b/frontend/packages/dashboard/src/builds.js index 99381a07..80314b4d 100644 --- a/frontend/packages/dashboard/src/builds.js +++ b/frontend/packages/dashboard/src/builds.js @@ -154,6 +154,8 @@ const NODE_KIND_LABEL = { write_dropin: 'dropin', write_perm_file: 'perm file', approval_deploy: 'deploy', + set_wanted: 'set wanted', + noop: 'noop', }; // The currently-running node of a DAG (per-node `step` labels + build