refactor(#2591): make NodeKind the queue payload — drop JobPayload, agent into variants

This commit is contained in:
atlas 2026-07-20 23:00:20 +02:00 committed by mara
commit be2dfa8cd3
8 changed files with 233 additions and 177 deletions

View file

@ -56,12 +56,12 @@ pub enum NodeKind {
/// skipped when the container is already down — it only exists to
/// shrink the swap's downtime, which a stopped agent doesn't need
/// (the sync + dir prep still run; `Swap` builds inline).
Prebuild { relock: bool },
Prebuild { agent: String, relock: bool },
/// `nixos-container update` profile-swap (requires the container
/// stopped). Re-applies nspawn flags + resource limits first —
/// rebuild is the reconcile verb. The post-rebuild bookkeeping tail
/// lives in the sibling `PostSwap` node.
Swap,
Swap { agent: String },
/// The post-`Swap` bookkeeping tail as a first-class node: rev marker,
/// forge + matrix sync, manager kick, container rescan, meta-inputs
/// snapshot. Split out of `Swap` for dashboard visibility + retry
@ -71,16 +71,16 @@ pub enum NodeKind {
/// recovery still runs. Store/forge/matrix work only — no nix build, so
/// build-slot-exempt; the agent lease taken at `Swap` is held across the
/// whole chain until `Reconcile` settles, so it's not re-declared here.
PostSwap,
PostSwap { agent: String },
/// First-spawn pre-create provisioning: proposed/applied repos,
/// state subvolume, and meta registration (`sync_agents`). Runs
/// ahead of `Create` so the `nixos-container create --flake
/// meta#<name>` ref resolves. Store/meta-only — no container yet —
/// so it's lease- and build-slot-exempt like `Prebuild`.
Provision,
Provision { agent: String },
/// First-spawn `nixos-container create` proper. Assumes the
/// upstream `Provision` node already registered the agent in meta.
Create,
Create { agent: String },
/// Meta flake lock bump. `sweep = false`: `meta::lock_update`
/// (commit fused, under `META_LOCK`) with the DAG's `inputs`;
/// `sweep = true`: `meta::lock_update_hyperhive`, *non-fatal* (a
@ -97,36 +97,36 @@ pub enum NodeKind {
/// `Offline` & up, else noop). The mechanical work is not done in
/// this node — it fans a child [`NodeKind::Start`] / [`NodeKind::Stop`]
/// DAG out at runtime so the sub-step is a first-class DAG node.
Reconcile,
Reconcile { agent: String },
/// Mechanical container start: the start preamble (runtime dir +
/// drop-ins), `start_with_fallback`, MCP listener registration, and
/// the manager kick. Fanned out by a [`NodeKind::Reconcile`] that
/// observed `wanted = Up` and the container down.
Start,
Start { agent: String },
/// Mechanical container stop: `nixos-container` kill, MCP listener
/// unregister, and the `Killed` manager notify. Fanned out by a
/// [`NodeKind::Reconcile`] that observed `wanted = Offline` and up.
Stop,
Stop { agent: String },
/// Mechanical `nixos-container stop` for the profile swap. Never
/// touches `wanted`. Noop if already stopped.
StopForUpdate,
StopForUpdate { agent: String },
/// Set the graceful-stop fence + kick the harness so it runs one
/// stop-checkpoint turn.
Signal,
Signal { agent: String },
/// Await the harness clearing the fence, bounded by
/// `GRACEFUL_STOP_TIMEOUT`. Resolves ok either way — the
/// downstream `Reconcile` performs the actual stop.
Drain,
Drain { agent: String },
/// `set_nspawn_flags` + `set_resource_limits` + daemon-reload.
WriteDropin,
WriteDropin { agent: String },
/// Commit `tool-groups.json` / `capabilities.json` per the DAG's
/// `perm_payload` (commit fused under `META_LOCK`).
WritePermFile,
WritePermFile { agent: String },
/// Opaque approval deploy pipeline (`MergeConfigPr`): the two-phase
/// prepare/finalize/abort meta deploy stays inside `actions.rs` in v1 —
/// deliberately not
/// modeled as scheduler nodes (see the design doc §9).
ApprovalDeploy,
ApprovalDeploy { agent: String },
/// Write the agent's durable power intent (`wanted = Up` when `up`, else
/// `Offline`) as a first-class DAG node, at the head of a power-op
/// template so the downstream `Reconcile` reads it. Replaces the old
@ -140,7 +140,7 @@ pub enum NodeKind {
/// the DAG. (In `stale_start` the lease is thus held across the head
/// `Prebuild`, but that's a no-op there — the agent is down, so prebuild
/// is skipped.)
SetWanted { up: bool },
SetWanted { agent: String, up: bool },
/// The **DAG container** node: one per submitted DAG, carrying the group's
/// domain metadata. Every template node hangs *under* it (its subtree), so
/// the container's `NodeId` **is** the DAG id, its rolled-up state **is** the
@ -166,35 +166,60 @@ impl NodeKind {
pub fn as_str(&self) -> &'static str {
match self {
NodeKind::Prebuild { .. } => "prebuild",
NodeKind::Swap => "swap",
NodeKind::PostSwap => "post_swap",
NodeKind::Provision => "provision",
NodeKind::Create => "create",
NodeKind::Swap { .. } => "swap",
NodeKind::PostSwap { .. } => "post_swap",
NodeKind::Provision { .. } => "provision",
NodeKind::Create { .. } => "create",
NodeKind::MetaLock { .. } => "meta_lock",
NodeKind::Reconcile => "reconcile",
NodeKind::Start => "start",
NodeKind::Stop => "stop",
NodeKind::StopForUpdate => "stop_for_update",
NodeKind::Signal => "signal",
NodeKind::Drain => "drain",
NodeKind::WriteDropin => "write_dropin",
NodeKind::WritePermFile => "write_perm_file",
NodeKind::ApprovalDeploy => "approval_deploy",
NodeKind::Reconcile { .. } => "reconcile",
NodeKind::Start { .. } => "start",
NodeKind::Stop { .. } => "stop",
NodeKind::StopForUpdate { .. } => "stop_for_update",
NodeKind::Signal { .. } => "signal",
NodeKind::Drain { .. } => "drain",
NodeKind::WriteDropin { .. } => "write_dropin",
NodeKind::WritePermFile { .. } => "write_perm_file",
NodeKind::ApprovalDeploy { .. } => "approval_deploy",
NodeKind::SetWanted { .. } => "set_wanted",
NodeKind::Dag { .. } => "dag",
}
}
/// The agent this node targets, or `""` for agentless kinds
/// ([`NodeKind::MetaLock`] on the `hyperhive` pseudo-agent, and the
/// [`NodeKind::Dag`] container).
#[must_use]
pub fn agent(&self) -> &str {
match self {
NodeKind::Prebuild { agent, .. }
| NodeKind::Swap { agent }
| NodeKind::PostSwap { agent }
| NodeKind::Provision { agent }
| NodeKind::Create { agent }
| NodeKind::Reconcile { agent }
| NodeKind::Start { agent }
| NodeKind::Stop { agent }
| NodeKind::StopForUpdate { agent }
| NodeKind::Signal { agent }
| NodeKind::Drain { agent }
| NodeKind::WriteDropin { agent }
| NodeKind::WritePermFile { agent }
| NodeKind::ApprovalDeploy { agent }
| NodeKind::SetWanted { agent, .. } => agent,
NodeKind::MetaLock { .. } | NodeKind::Dag { .. } => "",
}
}
/// Nix-heavy kinds hold one of the `buildSlots` semaphore permits
/// for the node's duration.
pub fn needs_build_slot(&self) -> bool {
matches!(
self,
NodeKind::Prebuild { .. }
| NodeKind::Swap
| NodeKind::Create
| NodeKind::Swap { .. }
| NodeKind::Create { .. }
| NodeKind::MetaLock { .. }
| NodeKind::ApprovalDeploy
| NodeKind::ApprovalDeploy { .. }
)
}
@ -209,14 +234,14 @@ impl NodeKind {
pub fn needs_lease(&self) -> bool {
matches!(
self,
NodeKind::Swap
| NodeKind::Create
| NodeKind::Reconcile
| NodeKind::StopForUpdate
| NodeKind::Signal
| NodeKind::Drain
| NodeKind::WriteDropin
| NodeKind::ApprovalDeploy
NodeKind::Swap { .. }
| NodeKind::Create { .. }
| NodeKind::Reconcile { .. }
| NodeKind::StopForUpdate { .. }
| NodeKind::Signal { .. }
| NodeKind::Drain { .. }
| NodeKind::WriteDropin { .. }
| NodeKind::ApprovalDeploy { .. }
| NodeKind::SetWanted { .. }
)
}
@ -225,9 +250,9 @@ impl NodeKind {
/// Submit-time spec for one node.
#[derive(Debug, Clone)]
pub struct NodeSpec {
/// The agent this node's work targets. Built by the `templates.rs` `node`
/// helper, which stamps the template's agent onto every node.
pub agent: String,
/// The node's payload — [`NodeKind`] is the queue's payload type directly,
/// and each variant carries the agent it targets (a DAG can span agents;
/// the queue derives per-agent leasing from [`NodeKind::agent`]).
pub kind: NodeKind,
pub deps: Vec<Dep>,
/// The **structural parent** axis — the spec-local index of this node's