jobq: rename the builder parameters too, not just the alias

Renaming `pub type Job` fixed the definition and left every use site
reading `b` and `job` — including `job: super::JobBuilder`, where the
parameter still asserted it was a job while its type said otherwise.
The propagation is what the issue was about, so the parameters are the
half that matters at a call site.

Two spots deliberately untouched: `auto_update`'s `sort_by(|a, b| …)`
comparator, and the prose that means the job *queue* (main.rs's
"Job-queue scheduler", scheduler.rs's "not this module's job any more",
the "grown job rejected" log).

315 tests pass unchanged.
This commit is contained in:
atlas 2026-08-03 13:09:24 +02:00 committed by mara
commit 684f6da78e
6 changed files with 203 additions and 180 deletions

View file

@ -36,9 +36,9 @@ use super::{Handle, JobBuilder};
///
/// Exactly one runs on a DAG that executed, and neither runs on one the operator
/// dropped — see [`hive_jobq::NodeRef::on_elimination_of`].
fn emit_rebuilt_tails(b: &JobBuilder, agent: &str, roots: &[Handle<'_>]) {
fn emit_rebuilt_tails(builder: &JobBuilder, agent: &str, roots: &[Handle<'_>]) {
let ok = roots.iter().fold(
b.node(NodeKind::EmitRebuilt {
builder.node(NodeKind::EmitRebuilt {
agent: agent.to_owned(),
ok: true,
}),
@ -51,11 +51,12 @@ fn emit_rebuilt_tails(b: &JobBuilder, agent: &str, roots: &[Handle<'_>]) {
// `Reconcile` is still bringing the container back up, so reporting straight
// off the elimination would announce the failure mid-recovery.
let _failed = roots.iter().fold(
b.node(NodeKind::EmitRebuilt {
agent: agent.to_owned(),
ok: false,
})
.on_elimination_of(ok),
builder
.node(NodeKind::EmitRebuilt {
agent: agent.to_owned(),
ok: false,
})
.on_elimination_of(ok),
hive_jobq::NodeRef::after_any,
);
}
@ -65,13 +66,13 @@ fn emit_rebuilt_tails(b: &JobBuilder, agent: &str, roots: &[Handle<'_>]) {
///
/// 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.
fn resolve_approval_tails(b: &JobBuilder, approval_id: i64, root: Handle<'_>) {
fn resolve_approval_tails(builder: &JobBuilder, approval_id: i64, root: Handle<'_>) {
for outcome in [
TerminalState::Done,
TerminalState::Failed,
TerminalState::Cancelled,
] {
let _ = b
let _ = builder
.node(NodeKind::ResolveApproval {
approval_id,
outcome,
@ -90,18 +91,18 @@ fn resolve_approval_tails(b: &JobBuilder, approval_id: i64, root: Handle<'_>) {
///
/// Same reason as [`fanned_out_mechanical`] for living here: this was the
/// second construction site declaring nodes inline in an executor.
pub(crate) fn grown_rebuilds(b: &JobBuilder, agents: &[String], relock: bool) {
pub(crate) fn grown_rebuilds(builder: &JobBuilder, agents: &[String], relock: bool) {
for agent in agents {
rebuild_nodes(b, agent, relock, None);
rebuild_nodes(builder, agent, relock, None);
}
}
/// As [`grown_rebuilds`], but each agent gets its `Signal` → `Drain` window
/// 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.
pub(crate) fn grown_graceful_rebuilds(b: &JobBuilder, agents: &[String], relock: bool) {
pub(crate) fn grown_graceful_rebuilds(builder: &JobBuilder, agents: &[String], relock: bool) {
for agent in agents {
graceful_rebuild_nodes(b, agent, relock, None);
graceful_rebuild_nodes(builder, agent, relock, None);
}
}
@ -117,9 +118,9 @@ pub(crate) fn grown_graceful_rebuilds(b: &JobBuilder, agents: &[String], relock:
/// 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
/// calls itself and would have kept passing if the executor changed.
pub(crate) fn fanned_out_mechanical(b: &JobBuilder, kind: NodeKind) {
pub(crate) fn fanned_out_mechanical(builder: &JobBuilder, kind: NodeKind) {
let lease = Resource::Agent(kind.agent().to_owned());
let _ = b.node(kind).needs(lease);
let _ = builder.node(kind).needs(lease);
}
/// The group-roots a [`rebuild_nodes`] subgraph exposes to its caller: what a
@ -176,7 +177,7 @@ impl<'a> RebuildRoots<'a> {
/// takes a fresh lease; the tiny gap is harmless — `Reconcile` converges to
/// the persisted `wanted` idempotently.
fn rebuild_subtree<'a>(
b: &'a JobBuilder,
builder: &'a JobBuilder,
agent: &str,
relock: bool,
graceful: bool,
@ -184,13 +185,13 @@ fn rebuild_subtree<'a>(
) -> RebuildRoots<'a> {
let a = || agent.to_owned();
let mut meta_sync = b
let mut meta_sync = builder
.node(NodeKind::MetaSync { agent: a(), relock })
.needs(Resource::MetaWindow);
if let Some(after) = after {
meta_sync = meta_sync.after_ok(after);
}
let prebuild = b
let prebuild = builder
.node(NodeKind::Prebuild { agent: a() })
.needs(Resource::BuildSlot)
.after_ok(meta_sync);
@ -198,27 +199,29 @@ fn rebuild_subtree<'a>(
// The stop root hangs off `Prebuild` and owns the agent lease for
// everything below it. `StopForUpdate` parents the swap pair either way.
let stop_for_update = if graceful {
let signal = b
let signal = builder
.node(NodeKind::Signal { agent: a() })
.needs(Resource::Agent(a()))
.part_of(prebuild);
// `Drain` is a *child* of `Signal`, so the parent gate already orders
// it — a child must not dep on its own parent (dep-scope).
let drain = b
let drain = builder
.node(NodeKind::Drain { agent: a() })
.needs(Resource::Agent(a()))
.part_of(signal);
b.node(NodeKind::StopForUpdate { agent: a() })
builder
.node(NodeKind::StopForUpdate { agent: a() })
.needs(Resource::Agent(a()))
.part_of(signal)
.after_ok(drain)
} else {
b.node(NodeKind::StopForUpdate { agent: a() })
builder
.node(NodeKind::StopForUpdate { agent: a() })
.needs(Resource::Agent(a()))
.part_of(prebuild)
};
let swap = b
let swap = builder
.node(NodeKind::Swap { agent: a() })
.needs(Resource::BuildSlot)
.needs(Resource::Agent(a()))
@ -227,13 +230,13 @@ fn rebuild_subtree<'a>(
// `StopForUpdate`, which holds it, so this is a re-entrant borrow — no
// second unit, no deadlock. Declaring it is what stops the requirement
// being true only of this one DAG shape.
let _post_swap = b
let _post_swap = builder
.node(NodeKind::PostSwap { agent: a() })
.needs(Resource::Agent(a()))
.part_of(stop_for_update)
.after_ok(swap);
let reconcile = b
let reconcile = builder
.node(NodeKind::Reconcile { agent: a() })
.needs(Resource::Agent(a()))
.after_any(prebuild);
@ -250,12 +253,12 @@ fn rebuild_subtree<'a>(
/// when given, is the node this subgraph chains behind. See
/// [`rebuild_subtree`] for the structure.
pub(crate) fn rebuild_nodes<'a>(
b: &'a JobBuilder,
builder: &'a JobBuilder,
agent: &str,
relock: bool,
after: Option<Handle<'a>>,
) -> RebuildRoots<'a> {
rebuild_subtree(b, agent, relock, false, after)
rebuild_subtree(builder, agent, relock, false, after)
}
/// As [`rebuild_nodes`], but the agent gets a `Signal` → `Drain` window to
@ -266,12 +269,12 @@ pub(crate) fn rebuild_nodes<'a>(
/// *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.
pub(crate) fn graceful_rebuild_nodes<'a>(
b: &'a JobBuilder,
builder: &'a JobBuilder,
agent: &str,
relock: bool,
after: Option<Handle<'a>>,
) -> RebuildRoots<'a> {
rebuild_subtree(b, agent, relock, true, after)
rebuild_subtree(builder, agent, relock, true, after)
}
/// The rebuild subgraph a [`NodeKind::DeployApply`] grows into its own DAG once
@ -298,9 +301,9 @@ pub(crate) fn graceful_rebuild_nodes<'a>(
/// `DeployWindow`'s subtree — so the `MetaWindow` this subgraph's `MetaSync`
/// and `FinalizeDeploy` declare is re-entered from the ancestor already holding
/// it rather than deadlocking against it.
pub(crate) fn deploy_rebuild_nodes(b: &JobBuilder, agent: &str, approval_id: i64) {
let roots = rebuild_nodes(b, agent, false, None);
let _finalize = b
pub(crate) fn deploy_rebuild_nodes(builder: &JobBuilder, agent: &str, approval_id: i64) {
let roots = rebuild_nodes(builder, agent, false, None);
let _finalize = builder
.node(NodeKind::FinalizeDeploy {
agent: agent.to_owned(),
approval_id,
@ -321,9 +324,9 @@ pub(crate) fn deploy_rebuild_nodes(b: &JobBuilder, agent: &str, approval_id: i64
/// whole `StopForUpdate`→`Swap`→`PostSwap` subtree, so those three cover every
/// 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.
pub fn rebuild(b: &JobBuilder, agent: &str, relock: bool) {
let roots = rebuild_nodes(b, agent, relock, None);
emit_rebuilt_tails(b, agent, &roots.all());
pub fn rebuild(builder: &JobBuilder, agent: &str, relock: bool) {
let roots = rebuild_nodes(builder, agent, relock, None);
emit_rebuilt_tails(builder, agent, &roots.all());
}
/// Approval-driven deploy (`MergeConfigPr`) as a phase subtree rather than the
@ -351,7 +354,7 @@ pub fn rebuild(b: &JobBuilder, agent: &str, relock: bool) {
///
/// The window still spans the container build, as it must: `prepare_deploy`
/// leaves `flake.lock` staged-uncommitted for the build's whole duration.
pub fn approval_deploy(b: &JobBuilder, agent: &str, approval_id: i64) {
pub fn approval_deploy(builder: &JobBuilder, agent: &str, approval_id: i64) {
let a = || agent.to_owned();
// The window is the widest holder in the tree: it brackets a nix
// build (`BuildSlot`), takes the container down across the swap
@ -359,7 +362,7 @@ pub fn approval_deploy(b: &JobBuilder, agent: &str, approval_id: i64) {
// (`MetaWindow`). All three are held for its whole subtree, which
// is what lets the appended rebuild's `MetaSync` and the
// `FinalizeDeploy` re-enter rather than contend.
let window = b
let window = builder
.node(NodeKind::DeployWindow {
agent: a(),
approval_id,
@ -367,20 +370,20 @@ pub fn approval_deploy(b: &JobBuilder, agent: &str, approval_id: i64) {
.needs(Resource::BuildSlot)
.needs(Resource::Agent(a()))
.needs(Resource::MetaWindow);
let verify = b
let verify = builder
.node(NodeKind::MergeVerify {
agent: a(),
approval_id,
})
.part_of(window);
let apply = b
let apply = builder
.node(NodeKind::DeployApply {
agent: a(),
approval_id,
})
.part_of(window)
.after_ok(verify);
let _tail = b
let _tail = builder
.node(NodeKind::DeployTail {
agent: a(),
approval_id,
@ -388,7 +391,7 @@ pub fn approval_deploy(b: &JobBuilder, agent: &str, approval_id: i64) {
.part_of(window)
.after_any(apply);
resolve_approval_tails(b, approval_id, window);
resolve_approval_tails(builder, approval_id, window);
}
/// First-deploy spawn (approval-driven): `Provision` (proposed/applied
@ -402,27 +405,27 @@ pub fn approval_deploy(b: &JobBuilder, agent: &str, approval_id: i64) {
/// 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
/// already carries the whole cascade.
pub fn spawn(b: &JobBuilder, agent: &str, approval_id: i64) {
pub fn spawn(builder: &JobBuilder, agent: &str, approval_id: i64) {
let a = || agent.to_owned();
let provision = b
let provision = builder
.node(NodeKind::Provision { agent: a() })
.needs(Resource::MetaWindow);
let create = b
let create = builder
.node(NodeKind::Create { agent: a() })
.needs(Resource::BuildSlot)
.needs(Resource::Agent(a()))
.part_of(provision);
let dropin = b
let dropin = builder
.node(NodeKind::WriteDropin { agent: a() })
.needs(Resource::Agent(a()))
.part_of(create);
let _reconcile = b
let _reconcile = builder
.node(NodeKind::Reconcile { agent: a() })
.needs(Resource::Agent(a()))
.part_of(create)
.after_ok(dropin);
resolve_approval_tails(b, approval_id, provision);
resolve_approval_tails(builder, approval_id, provision);
}
/// Perm change: commit the JSON file(s), then the rebuild subgraph so
@ -430,16 +433,16 @@ pub fn spawn(b: &JobBuilder, agent: &str, approval_id: i64) {
/// effect in the container. Group-roots are `WritePermFile` plus the rebuild
/// subgraph's `MetaSync` / `Prebuild` / `Reconcile`, so the `EmitRebuilt` tail
/// edges all four.
pub fn perm_change(b: &JobBuilder, agent: &str, payload: PermPayload) {
let write = b
pub fn perm_change(builder: &JobBuilder, agent: &str, payload: PermPayload) {
let write = builder
.node(NodeKind::WritePermFile {
agent: agent.to_owned(),
payload,
})
.needs(Resource::MetaWindow);
let roots = rebuild_nodes(b, agent, true, Some(write));
let roots = rebuild_nodes(builder, agent, true, Some(write));
emit_rebuilt_tails(
b,
builder,
agent,
&[write, roots.meta_sync, roots.prebuild, roots.reconcile],
);
@ -455,8 +458,8 @@ pub fn perm_change(b: &JobBuilder, agent: &str, payload: PermPayload) {
/// so the "hyperhive" pseudo-agent gets no pill), giving each cascade agent
/// crash-watch suppression during its `Swap` — the property the old child
/// `Rebuild` DAGs carried via their own transient.
pub fn meta_update(b: &JobBuilder, inputs: Vec<String>, approval_id: Option<i64>) {
let lock = b
pub fn meta_update(builder: &JobBuilder, inputs: Vec<String>, approval_id: Option<i64>) {
let lock = builder
.node(NodeKind::MetaLock {
sweep: false,
fanout: None,
@ -470,7 +473,7 @@ pub fn meta_update(b: &JobBuilder, inputs: Vec<String>, approval_id: Option<i64>
// group-root — whose roll-up covers the rebuild subgraphs `MetaLock`
// grows into itself.
if let Some(approval_id) = approval_id {
resolve_approval_tails(b, approval_id, lock);
resolve_approval_tails(builder, approval_id, lock);
}
}
@ -483,8 +486,8 @@ pub fn meta_update(b: &JobBuilder, inputs: Vec<String>, approval_id: Option<i64>
/// 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
/// off of) and near-instant. No tail node: the write is the whole effect.
pub fn reparent(b: &JobBuilder, moves: Vec<(hive_types::Ident, Option<hive_types::Ident>)>) {
let _reparent = b
pub fn reparent(builder: &JobBuilder, moves: Vec<(hive_types::Ident, Option<hive_types::Ident>)>) {
let _reparent = builder
.node(NodeKind::Reparent { moves })
.needs(Resource::MetaWindow);
}