jobq: the core alias is a JobBuilder, not a Job

A JobBuilder holds pending nodes that are not in the graph yet — it is
the thing you declare into. Naming the alias Job claimed it was the work
itself, and the name propagated into every parameter derived from it
(job: super::Job in run_node read as if it carried the DAG).

Prose uses meaning the job *queue* are left alone: main.rs's "Job-queue
scheduler" comment and the docs/coordinator.md reference.

315 tests pass unchanged.
This commit is contained in:
atlas 2026-08-03 12:53:52 +02:00 committed by mara
commit 379c9bb570
6 changed files with 42 additions and 42 deletions

View file

@ -36,7 +36,7 @@ pub const GRACEFUL_STOP_TIMEOUT: std::time::Duration = std::time::Duration::from
/// ///
/// ⚠️ Taken **by value and handed back**, not by reference. A `JobBuilder` is /// ⚠️ Taken **by value and handed back**, not by reference. A `JobBuilder` is
/// `RefCell`-backed: owned it is `Send`, but `&JobBuilder` is not (a shared ref /// `RefCell`-backed: owned it is `Send`, but `&JobBuilder` is not (a shared ref
/// is `Send` only if the referent is `Sync`, and `RefCell` never is). A `&Job` /// is `Send` only if the referent is `Sync`, and `RefCell` never is). A `&JobBuilder`
/// parameter would be live across every `.await` in this fn and make the whole /// parameter would be live across every `.await` in this fn and make the whole
/// future non-`Send`, which the scheduler's `tokio::spawn` rejects. So the /// future non-`Send`, which the scheduler's `tokio::spawn` rejects. So the
/// growth executors below return *what to grow* and the declaration happens /// growth executors below return *what to grow* and the declaration happens
@ -48,10 +48,10 @@ pub const GRACEFUL_STOP_TIMEOUT: std::time::Duration = std::time::Duration::from
/// themselves. Nothing here needs a claim to exist as a type. /// themselves. Nothing here needs a claim to exist as a type.
pub(super) async fn run_node( pub(super) async fn run_node(
coord: &Arc<Coordinator>, coord: &Arc<Coordinator>,
job: super::Job, job: super::JobBuilder,
id: NodeId, id: NodeId,
kind: &NodeKind, kind: &NodeKind,
) -> (super::Job, Result<()>) { ) -> (super::JobBuilder, Result<()>) {
// The agent this node targets rides the payload — empty for the agentless // The agent this node targets rides the payload — empty for the agentless
// container kinds (`MetaLock`, `Dag`), which never read it. // container kinds (`MetaLock`, `Dag`), which never read it.
let agent = kind.agent(); let agent = kind.agent();

View file

@ -25,7 +25,7 @@
//! The queue is runtime-only (no persistence): an empty graph on boot; desired //! The queue is runtime-only (no persistence): an empty graph on boot; desired
//! state is re-derived by the reconcile sweep. A single scheduler task //! state is re-derived by the reconcile sweep. A single scheduler task
//! ([`scheduler::run_worker`]) drives it; concurrency comes from the build-slot //! ([`scheduler::run_worker`]) drives it; concurrency comes from the build-slot
//! capacity, not multiple workers. Design: `docs/coordinator.md::Job queue`. //! capacity, not multiple workers. Design: `docs/coordinator.md::JobBuilder queue`.
pub mod exec; pub mod exec;
pub mod model; pub mod model;
@ -53,7 +53,7 @@ use resource::Resource;
/// A job under construction: `hive_jobq`'s builder over this queue's payload /// A job under construction: `hive_jobq`'s builder over this queue's payload
/// ([`NodeKind`]) and resource ([`Resource`]) types. Templates declare into a /// ([`NodeKind`]) and resource ([`Resource`]) types. Templates declare into a
/// borrowed one; only `hive_jobq` can make or insert it. /// borrowed one; only `hive_jobq` can make or insert it.
pub type Job = hive_jobq::JobBuilder<NodeKind, Resource>; pub type JobBuilder = hive_jobq::JobBuilder<NodeKind, Resource>;
/// A handle to one node a template declared — where its edges, grouping and /// A handle to one node a template declared — where its edges, grouping and
/// resources are declared. `Copy`; naming a node as a dependency does not /// resources are declared. `Copy`; naming a node as a dependency does not
@ -156,7 +156,7 @@ fn outcome_of(result: Result<(), String>) -> Outcome {
/// Propagates a crate graph-insert error (malformed dep/parent / dep-scope). /// Propagates a crate graph-insert error (malformed dep/parent / dep-scope).
fn insert_group( fn insert_group(
inner: &mut Sched, inner: &mut Sched,
declare: impl FnOnce(&Job), declare: impl FnOnce(&JobBuilder),
group_parent: Option<NodeId>, group_parent: Option<NodeId>,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
inner inner
@ -213,7 +213,7 @@ impl JobQueue {
&self, &self,
source: Source, source: Source,
reason: String, reason: String,
declare: impl FnOnce(&Job), declare: impl FnOnce(&JobBuilder),
) -> anyhow::Result<u64> { ) -> anyhow::Result<u64> {
let mut inner = self.lock(); let mut inner = self.lock();
let container = inner let container = inner

View file

@ -6,7 +6,7 @@
//! state, which needs an async `lifecycle::is_running` read that a pure/sync //! state, which needs an async `lifecycle::is_running` read that a pure/sync
//! template can't do. So these fns are async — they read each agent's state, //! template can't do. So these fns are async — they read each agent's state,
//! assemble a per-agent subgraph out of the shared pure primitives //! assemble a per-agent subgraph out of the shared pure primitives
//! (`Job::node` + `templates::rebuild_nodes`), all declaring into ONE job //! (`JobBuilder::node` + `templates::rebuild_nodes`), all declaring into ONE job
//! (independent per-agent roots, concurrent on their own leases). //! (independent per-agent roots, concurrent on their own leases).
//! //!
//! Dynamic shape rule: `stop`/`start` carry a head `SetWanted(w)` (durable //! Dynamic shape rule: `stop`/`start` carry a head `SetWanted(w)` (durable
@ -27,7 +27,7 @@ use std::sync::Arc;
use super::model::NodeKind; use super::model::NodeKind;
use super::resource::Resource; use super::resource::Resource;
use super::templates::rebuild_nodes; use super::templates::rebuild_nodes;
use super::{Job, Source, templates}; use super::{JobBuilder, Source, templates};
use crate::coordinator::Coordinator; use crate::coordinator::Coordinator;
use crate::lifecycle; use crate::lifecycle;
@ -35,7 +35,7 @@ fn submit_and_emit(
coord: &Arc<Coordinator>, coord: &Arc<Coordinator>,
source: Source, source: Source,
reason: String, reason: String,
declare: impl FnOnce(&Job), declare: impl FnOnce(&JobBuilder),
) -> u64 { ) -> u64 {
let id = coord let id = coord
.job_queue .job_queue
@ -67,7 +67,7 @@ pub fn rebuild(coord: &Arc<Coordinator>, agent: &str, source: Source, reason: St
/// actually running (nothing to drain on a down container). The `Reconcile` /// actually running (nothing to drain on a down container). The `Reconcile`
/// stays even for a down agent so a race-up between the state read and exec /// stays even for a down agent so a race-up between the state read and exec
/// is still stopped in-DAG. /// is still stopped in-DAG.
fn stop_chain(b: &Job, agent: &str, graceful: bool, running: bool) { fn stop_chain(b: &JobBuilder, agent: &str, graceful: bool, running: bool) {
// `SetWanted` is the group root and owns the agent lease; the mechanical // `SetWanted` is the group root and owns the agent lease; the mechanical
// steps are its children (borrow the lease, run once it reaches `Finishing`, // steps are its children (borrow the lease, run once it reaches `Finishing`,
// dep-ordered among themselves). // dep-ordered among themselves).
@ -107,7 +107,7 @@ fn stop_chain(b: &Job, agent: &str, graceful: bool, running: bool) {
/// agent gets the rebuild subgraph (its tail `Reconcile` starts it on /// agent gets the rebuild subgraph (its tail `Reconcile` starts it on
/// current derivations), otherwise a plain `Reconcile` (which starts a down /// current derivations), otherwise a plain `Reconcile` (which starts a down
/// agent and noops an already-running one). /// agent and noops an already-running one).
fn start_chain(b: &Job, agent: &str, running: bool, stale: bool) { fn start_chain(b: &JobBuilder, agent: &str, running: bool, stale: bool) {
let wanted = b let wanted = b
.node(NodeKind::SetWanted { .node(NodeKind::SetWanted {
agent: agent.to_owned(), agent: agent.to_owned(),
@ -138,7 +138,7 @@ fn start_chain(b: &Job, agent: &str, running: bool, stale: bool) {
/// before `Reconcile`; a down agent gets just `Reconcile`, which /// before `Reconcile`; a down agent gets just `Reconcile`, which
/// converges to intent — a stopped (`wanted = Off`) agent stays stopped, /// converges to intent — a stopped (`wanted = Off`) agent stays stopped,
/// a crashed (`wanted = Up`) agent comes back up. /// a crashed (`wanted = Up`) agent comes back up.
fn restart_chain(b: &Job, agent: &str, graceful: bool, running: bool) { fn restart_chain(b: &JobBuilder, agent: &str, graceful: bool, running: bool) {
let a = || agent.to_owned(); let a = || agent.to_owned();
if !running { if !running {
// Nothing to bounce — a lone Reconcile converges to intent. // Nothing to bounce — a lone Reconcile converges to intent.
@ -198,7 +198,7 @@ fn restart_chain(b: &Job, agent: &str, graceful: bool, running: bool) {
// subgraph's indices onto another's used to be a function. // subgraph's indices onto another's used to be a function.
/// Declare the stop DAG from explicit `(agent, running)` targets. /// Declare the stop DAG from explicit `(agent, running)` targets.
pub(crate) fn stop_nodes(b: &Job, targets: &[(String, bool)], graceful: bool) { pub(crate) fn stop_nodes(b: &JobBuilder, targets: &[(String, bool)], graceful: bool) {
for (agent, running) in targets { for (agent, running) in targets {
stop_chain(b, agent, graceful, *running); stop_chain(b, agent, graceful, *running);
} }
@ -210,14 +210,14 @@ pub(crate) fn stop_nodes(b: &Job, targets: &[(String, bool)], graceful: bool) {
/// running under its lease, so a down+stale agent that grew a rebuild subgraph /// running under its lease, so a down+stale agent that grew a rebuild subgraph
/// reports `rebuilding` during its swap and `starting` at its reconcile, /// reports `rebuilding` during its swap and `starting` at its reconcile,
/// without the DAG having to guess one label covering every target. /// without the DAG having to guess one label covering every target.
pub(crate) fn start_nodes(b: &Job, targets: &[(String, bool, bool)]) { pub(crate) fn start_nodes(b: &JobBuilder, targets: &[(String, bool, bool)]) {
for (agent, running, stale) in targets { for (agent, running, stale) in targets {
start_chain(b, agent, *running, *stale); start_chain(b, agent, *running, *stale);
} }
} }
/// Declare the restart DAG from explicit `(agent, running)` targets. /// Declare the restart DAG from explicit `(agent, running)` targets.
pub(crate) fn restart_nodes(b: &Job, targets: &[(String, bool)], graceful: bool) { pub(crate) fn restart_nodes(b: &JobBuilder, targets: &[(String, bool)], graceful: bool) {
for (agent, running) in targets { for (agent, running) in targets {
restart_chain(b, agent, graceful, *running); restart_chain(b, agent, graceful, *running);
} }

View file

@ -16,19 +16,19 @@
//! ``` //! ```
//! //!
//! Nodes are **named, not counted** — a template holds the handle //! Nodes are **named, not counted** — a template holds the handle
//! [`Job::node`] hands back, so an edge says which node it waits on. Why that //! [`JobBuilder::node`] hands back, so an edge says which node it waits on. Why that
//! removes submit-time cycle validation: `docs/coordinator.md`. //! removes submit-time cycle validation: `docs/coordinator.md`.
//! //!
//! The hive-wide **power ops** (`stop` / `start` / `restart`) are NOT here: //! The hive-wide **power ops** (`stop` / `start` / `restart`) are NOT here:
//! their per-agent shape depends on live running state (an async //! their per-agent shape depends on live running state (an async
//! `lifecycle::is_running` read), so `submit.rs` assembles them out of the //! `lifecycle::is_running` read), so `submit.rs` assembles them out of the
//! primitives this module exports ([`rebuild_nodes`]) over `Job::node`. //! primitives this module exports ([`rebuild_nodes`]) over `JobBuilder::node`.
use hive_jobq::TerminalState; use hive_jobq::TerminalState;
use super::model::{NodeKind, PermPayload}; use super::model::{NodeKind, PermPayload};
use super::resource::Resource; use super::resource::Resource;
use super::{Handle, Job}; use super::{Handle, JobBuilder};
/// The `Rebuilt`-reporting tail pair for a rebuild-shaped DAG: the success node /// The `Rebuilt`-reporting tail pair for a rebuild-shaped DAG: the success node
/// gated on every group-root in `roots`, and the failure node gated on *its* /// gated on every group-root in `roots`, and the failure node gated on *its*
@ -36,7 +36,7 @@ use super::{Handle, Job};
/// ///
/// Exactly one runs on a DAG that executed, and neither runs on one the operator /// Exactly one runs on a DAG that executed, and neither runs on one the operator
/// dropped — see [`hive_jobq::NodeRef::on_elimination_of`]. /// dropped — see [`hive_jobq::NodeRef::on_elimination_of`].
fn emit_rebuilt_tails(b: &Job, agent: &str, roots: &[Handle<'_>]) { fn emit_rebuilt_tails(b: &JobBuilder, agent: &str, roots: &[Handle<'_>]) {
let ok = roots.iter().fold( let ok = roots.iter().fold(
b.node(NodeKind::EmitRebuilt { b.node(NodeKind::EmitRebuilt {
agent: agent.to_owned(), agent: agent.to_owned(),
@ -65,7 +65,7 @@ fn emit_rebuilt_tails(b: &Job, agent: &str, roots: &[Handle<'_>]) {
/// ///
/// The `Cancelled` node is what keeps a dropped approval DAG from dangling its /// The `Cancelled` node is what keeps a dropped approval DAG from dangling its
/// row forever — its edge is the only one [`super::JobQueue::cancel`] spares. /// row forever — its edge is the only one [`super::JobQueue::cancel`] spares.
fn resolve_approval_tails(b: &Job, approval_id: i64, root: Handle<'_>) { fn resolve_approval_tails(b: &JobBuilder, approval_id: i64, root: Handle<'_>) {
for outcome in [ for outcome in [
TerminalState::Done, TerminalState::Done,
TerminalState::Failed, TerminalState::Failed,
@ -90,7 +90,7 @@ fn resolve_approval_tails(b: &Job, approval_id: i64, root: Handle<'_>) {
/// ///
/// Same reason as [`fanned_out_mechanical`] for living here: this was the /// Same reason as [`fanned_out_mechanical`] for living here: this was the
/// second construction site declaring nodes inline in an executor. /// second construction site declaring nodes inline in an executor.
pub(crate) fn grown_rebuilds(b: &Job, agents: &[String], relock: bool) { pub(crate) fn grown_rebuilds(b: &JobBuilder, agents: &[String], relock: bool) {
for agent in agents { for agent in agents {
rebuild_nodes(b, agent, relock, None); rebuild_nodes(b, agent, relock, None);
} }
@ -99,7 +99,7 @@ pub(crate) fn grown_rebuilds(b: &Job, agents: &[String], relock: bool) {
/// As [`grown_rebuilds`], but each agent gets its `Signal` → `Drain` window /// As [`grown_rebuilds`], but each agent gets its `Signal` → `Drain` window
/// before being stopped. The boot sweep's flavour: it stops agents that were /// before being stopped. The boot sweep's flavour: it stops agents that were
/// mid-turn when the host came up, so they drain rather than being cut off. /// mid-turn when the host came up, so they drain rather than being cut off.
pub(crate) fn grown_graceful_rebuilds(b: &Job, agents: &[String], relock: bool) { pub(crate) fn grown_graceful_rebuilds(b: &JobBuilder, agents: &[String], relock: bool) {
for agent in agents { for agent in agents {
graceful_rebuild_nodes(b, agent, relock, None); graceful_rebuild_nodes(b, agent, relock, None);
} }
@ -117,7 +117,7 @@ pub(crate) fn grown_graceful_rebuilds(b: &Job, agents: &[String], relock: bool)
/// declaration does: this is the one construction site that was hiding in an /// declaration does: this is the one construction site that was hiding in an
/// executor, which meant the only test of it had to re-declare the same two /// executor, which meant the only test of it had to re-declare the same two
/// calls itself and would have kept passing if the executor changed. /// calls itself and would have kept passing if the executor changed.
pub(crate) fn fanned_out_mechanical(b: &Job, kind: NodeKind) { pub(crate) fn fanned_out_mechanical(b: &JobBuilder, kind: NodeKind) {
let lease = Resource::Agent(kind.agent().to_owned()); let lease = Resource::Agent(kind.agent().to_owned());
let _ = b.node(kind).needs(lease); let _ = b.node(kind).needs(lease);
} }
@ -176,7 +176,7 @@ impl<'a> RebuildRoots<'a> {
/// takes a fresh lease; the tiny gap is harmless — `Reconcile` converges to /// takes a fresh lease; the tiny gap is harmless — `Reconcile` converges to
/// the persisted `wanted` idempotently. /// the persisted `wanted` idempotently.
fn rebuild_subtree<'a>( fn rebuild_subtree<'a>(
b: &'a Job, b: &'a JobBuilder,
agent: &str, agent: &str,
relock: bool, relock: bool,
graceful: bool, graceful: bool,
@ -250,7 +250,7 @@ fn rebuild_subtree<'a>(
/// when given, is the node this subgraph chains behind. See /// when given, is the node this subgraph chains behind. See
/// [`rebuild_subtree`] for the structure. /// [`rebuild_subtree`] for the structure.
pub(crate) fn rebuild_nodes<'a>( pub(crate) fn rebuild_nodes<'a>(
b: &'a Job, b: &'a JobBuilder,
agent: &str, agent: &str,
relock: bool, relock: bool,
after: Option<Handle<'a>>, after: Option<Handle<'a>>,
@ -266,7 +266,7 @@ pub(crate) fn rebuild_nodes<'a>(
/// *prepend* nodes — it **re-parents** the stop root, so a caller cannot /// *prepend* nodes — it **re-parents** the stop root, so a caller cannot
/// declare it without being handed the internals. Only the boot sweep wants it. /// declare it without being handed the internals. Only the boot sweep wants it.
pub(crate) fn graceful_rebuild_nodes<'a>( pub(crate) fn graceful_rebuild_nodes<'a>(
b: &'a Job, b: &'a JobBuilder,
agent: &str, agent: &str,
relock: bool, relock: bool,
after: Option<Handle<'a>>, after: Option<Handle<'a>>,
@ -298,7 +298,7 @@ pub(crate) fn graceful_rebuild_nodes<'a>(
/// `DeployWindow`'s subtree — so the `MetaWindow` this subgraph's `MetaSync` /// `DeployWindow`'s subtree — so the `MetaWindow` this subgraph's `MetaSync`
/// and `FinalizeDeploy` declare is re-entered from the ancestor already holding /// and `FinalizeDeploy` declare is re-entered from the ancestor already holding
/// it rather than deadlocking against it. /// it rather than deadlocking against it.
pub(crate) fn deploy_rebuild_nodes(b: &Job, agent: &str, approval_id: i64) { pub(crate) fn deploy_rebuild_nodes(b: &JobBuilder, agent: &str, approval_id: i64) {
let roots = rebuild_nodes(b, agent, false, None); let roots = rebuild_nodes(b, agent, false, None);
let _finalize = b let _finalize = b
.node(NodeKind::FinalizeDeploy { .node(NodeKind::FinalizeDeploy {
@ -321,7 +321,7 @@ pub(crate) fn deploy_rebuild_nodes(b: &Job, agent: &str, approval_id: i64) {
/// whole `StopForUpdate`→`Swap`→`PostSwap` subtree, so those three cover every /// whole `StopForUpdate`→`Swap`→`PostSwap` subtree, so those three cover every
/// node. Edging `Reconcile` alone would not do: it is `AfterAny` `Prebuild`, so /// node. Edging `Reconcile` alone would not do: it is `AfterAny` `Prebuild`, so
/// it reaches `Done` even after a failed swap and the tail would report success. /// it reaches `Done` even after a failed swap and the tail would report success.
pub fn rebuild(b: &Job, agent: &str, relock: bool) { pub fn rebuild(b: &JobBuilder, agent: &str, relock: bool) {
let roots = rebuild_nodes(b, agent, relock, None); let roots = rebuild_nodes(b, agent, relock, None);
emit_rebuilt_tails(b, agent, &roots.all()); emit_rebuilt_tails(b, agent, &roots.all());
} }
@ -351,7 +351,7 @@ pub fn rebuild(b: &Job, agent: &str, relock: bool) {
/// ///
/// The window still spans the container build, as it must: `prepare_deploy` /// The window still spans the container build, as it must: `prepare_deploy`
/// leaves `flake.lock` staged-uncommitted for the build's whole duration. /// leaves `flake.lock` staged-uncommitted for the build's whole duration.
pub fn approval_deploy(b: &Job, agent: &str, approval_id: i64) { pub fn approval_deploy(b: &JobBuilder, agent: &str, approval_id: i64) {
let a = || agent.to_owned(); let a = || agent.to_owned();
// The window is the widest holder in the tree: it brackets a nix // The window is the widest holder in the tree: it brackets a nix
// build (`BuildSlot`), takes the container down across the swap // build (`BuildSlot`), takes the container down across the swap
@ -402,7 +402,7 @@ pub fn approval_deploy(b: &Job, agent: &str, approval_id: i64) {
/// container was never created). Closed by a `ResolveApproval` tail root edged /// container was never created). Closed by a `ResolveApproval` tail root edged
/// `AfterAny` onto `Provision` — the DAG's only other group-root, so its roll-up /// `AfterAny` onto `Provision` — the DAG's only other group-root, so its roll-up
/// already carries the whole cascade. /// already carries the whole cascade.
pub fn spawn(b: &Job, agent: &str, approval_id: i64) { pub fn spawn(b: &JobBuilder, agent: &str, approval_id: i64) {
let a = || agent.to_owned(); let a = || agent.to_owned();
let provision = b let provision = b
.node(NodeKind::Provision { agent: a() }) .node(NodeKind::Provision { agent: a() })
@ -430,7 +430,7 @@ pub fn spawn(b: &Job, agent: &str, approval_id: i64) {
/// effect in the container. Group-roots are `WritePermFile` plus the rebuild /// effect in the container. Group-roots are `WritePermFile` plus the rebuild
/// subgraph's `MetaSync` / `Prebuild` / `Reconcile`, so the `EmitRebuilt` tail /// subgraph's `MetaSync` / `Prebuild` / `Reconcile`, so the `EmitRebuilt` tail
/// edges all four. /// edges all four.
pub fn perm_change(b: &Job, agent: &str, payload: PermPayload) { pub fn perm_change(b: &JobBuilder, agent: &str, payload: PermPayload) {
let write = b let write = b
.node(NodeKind::WritePermFile { .node(NodeKind::WritePermFile {
agent: agent.to_owned(), agent: agent.to_owned(),
@ -455,7 +455,7 @@ pub fn perm_change(b: &Job, agent: &str, payload: PermPayload) {
/// so the "hyperhive" pseudo-agent gets no pill), giving each cascade agent /// so the "hyperhive" pseudo-agent gets no pill), giving each cascade agent
/// crash-watch suppression during its `Swap` — the property the old child /// crash-watch suppression during its `Swap` — the property the old child
/// `Rebuild` DAGs carried via their own transient. /// `Rebuild` DAGs carried via their own transient.
pub fn meta_update(b: &Job, inputs: Vec<String>, approval_id: Option<i64>) { pub fn meta_update(b: &JobBuilder, inputs: Vec<String>, approval_id: Option<i64>) {
let lock = b let lock = b
.node(NodeKind::MetaLock { .node(NodeKind::MetaLock {
sweep: false, sweep: false,
@ -483,7 +483,7 @@ pub fn meta_update(b: &Job, inputs: Vec<String>, approval_id: Option<i64>) {
/// checks), so a parent move needs no container rebuild to take effect. /// checks), so a parent move needs no container rebuild to take effect.
/// No transient pill either — the node is agentless (no lease to hang one /// No transient pill either — the node is agentless (no lease to hang one
/// off of) and near-instant. No tail node: the write is the whole effect. /// off of) and near-instant. No tail node: the write is the whole effect.
pub fn reparent(b: &Job, moves: Vec<(hive_types::Ident, Option<hive_types::Ident>)>) { pub fn reparent(b: &JobBuilder, moves: Vec<(hive_types::Ident, Option<hive_types::Ident>)>) {
let _reparent = b let _reparent = b
.node(NodeKind::Reparent { moves }) .node(NodeKind::Reparent { moves })
.needs(Resource::MetaWindow); .needs(Resource::MetaWindow);

View file

@ -20,7 +20,7 @@ use super::*;
/// Submit a declared shape with the metadata every mechanics test uses. /// Submit a declared shape with the metadata every mechanics test uses.
/// `Source::Manual` because none of these exercise provenance — the tests that /// `Source::Manual` because none of these exercise provenance — the tests that
/// do name their own source at the call site. /// do name their own source at the call site.
fn submit(q: &JobQueue, reason: &str, declare: impl FnOnce(&Job)) -> u64 { fn submit(q: &JobQueue, reason: &str, declare: impl FnOnce(&JobBuilder)) -> u64 {
q.submit(Source::Manual, reason.to_owned(), declare) q.submit(Source::Manual, reason.to_owned(), declare)
.expect("valid shape") .expect("valid shape")
} }
@ -29,7 +29,7 @@ fn ident(s: &str) -> hive_types::Ident {
hive_types::Ident::parse(s).expect("valid test ident") hive_types::Ident::parse(s).expect("valid test ident")
} }
fn rebuild(b: &Job, agent: &str) { fn rebuild(b: &JobBuilder, agent: &str) {
templates::rebuild(b, agent, true); templates::rebuild(b, agent, true);
} }
@ -37,14 +37,14 @@ fn rebuild(b: &Job, agent: &str) {
/// shape (`[Signal→Drain→] StopForUpdate → Reconcile`, no `SetWanted` head) /// shape (`[Signal→Drain→] StopForUpdate → Reconcile`, no `SetWanted` head)
/// most queue-mechanics tests assume. Mirrors the pre-dynamic /// most queue-mechanics tests assume. Mirrors the pre-dynamic
/// `templates::restart` (which is now the state-aware `submit::restart_nodes`). /// `templates::restart` (which is now the state-aware `submit::restart_nodes`).
fn restart_online(b: &Job, agents: &[&str], graceful: bool) { fn restart_online(b: &JobBuilder, agents: &[&str], graceful: bool) {
let targets: Vec<(String, bool)> = agents.iter().map(|a| ((*a).to_owned(), true)).collect(); let targets: Vec<(String, bool)> = agents.iter().map(|a| ((*a).to_owned(), true)).collect();
submit::restart_nodes(b, &targets, graceful); submit::restart_nodes(b, &targets, graceful);
} }
/// Stop shape with every agent treated as **running** — the online shape /// Stop shape with every agent treated as **running** — the online shape
/// (`SetWanted → [Signal→Drain→](graceful) Reconcile`). /// (`SetWanted → [Signal→Drain→](graceful) Reconcile`).
fn stop_online(b: &Job, agents: &[&str], graceful: bool) { fn stop_online(b: &JobBuilder, agents: &[&str], graceful: bool) {
let targets: Vec<(String, bool)> = agents.iter().map(|a| ((*a).to_owned(), true)).collect(); let targets: Vec<(String, bool)> = agents.iter().map(|a| ((*a).to_owned(), true)).collect();
submit::stop_nodes(b, &targets, graceful); submit::stop_nodes(b, &targets, graceful);
} }
@ -426,7 +426,7 @@ fn rebuild_chain_is_declared_serial() {
fn graceful_rebuild_chain_drains_before_stopping() { fn graceful_rebuild_chain_drains_before_stopping() {
let q = JobQueue::new(1); let q = JobQueue::new(1);
let id = q let id = q
.submit(Source::AutoUpdate, "sweep".to_owned(), |b: &Job| { .submit(Source::AutoUpdate, "sweep".to_owned(), |b: &JobBuilder| {
templates::graceful_rebuild_nodes(b, "agent-a", true, None); templates::graceful_rebuild_nodes(b, "agent-a", true, None);
}) })
.expect("valid shape"); .expect("valid shape");
@ -737,7 +737,7 @@ fn boot_sweep_nodes_declare_their_own_resources() {
// compile; only an exhaustive caller list would have caught it. // compile; only an exhaustive caller list would have caught it.
let q = JobQueue::new(4); let q = JobQueue::new(4);
let id = q let id = q
.submit(Source::AutoUpdate, "boot".to_owned(), |b: &Job| { .submit(Source::AutoUpdate, "boot".to_owned(), |b: &JobBuilder| {
crate::workers::auto_update::boot_nodes( crate::workers::auto_update::boot_nodes(
b, b,
true, true,
@ -954,7 +954,7 @@ fn a_meta_lock_grows_one_rebuild_subgraph_per_agent() {
let q = JobQueue::new(4); let q = JobQueue::new(4);
let agents = vec!["alice".to_owned(), "bob".to_owned()]; let agents = vec!["alice".to_owned(), "bob".to_owned()];
let id = q let id = q
.submit(Source::AutoUpdate, "sweep".to_owned(), |b: &Job| { .submit(Source::AutoUpdate, "sweep".to_owned(), |b: &JobBuilder| {
templates::grown_graceful_rebuilds(b, &agents, true); templates::grown_graceful_rebuilds(b, &agents, true);
}) })
.expect("valid shape"); .expect("valid shape");

View file

@ -327,7 +327,7 @@ pub async fn run(coord: Arc<Coordinator>) -> Result<()> {
/// kind-derived resources were removed, which drops the agent lease a boot /// kind-derived resources were removed, which drops the agent lease a boot
/// reconcile needs to not race another DAG's container ops. /// reconcile needs to not race another DAG's container ops.
pub(crate) fn boot_nodes( pub(crate) fn boot_nodes(
b: &crate::job_queue::Job, b: &crate::job_queue::JobBuilder,
any_stale: bool, any_stale: bool,
fanout: Vec<String>, fanout: Vec<String>,
drifted: Vec<String>, drifted: Vec<String>,