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

@ -16,19 +16,19 @@
//! ```
//!
//! 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`.
//!
//! The hive-wide **power ops** (`stop` / `start` / `restart`) are NOT here:
//! their per-agent shape depends on live running state (an async
//! `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 super::model::{NodeKind, PermPayload};
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
/// 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
/// 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(
b.node(NodeKind::EmitRebuilt {
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
/// 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 [
TerminalState::Done,
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
/// 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 {
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
/// 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: &Job, agents: &[String], relock: bool) {
pub(crate) fn grown_graceful_rebuilds(b: &JobBuilder, agents: &[String], relock: bool) {
for agent in agents {
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
/// 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: &Job, kind: NodeKind) {
pub(crate) fn fanned_out_mechanical(b: &JobBuilder, kind: NodeKind) {
let lease = Resource::Agent(kind.agent().to_owned());
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
/// the persisted `wanted` idempotently.
fn rebuild_subtree<'a>(
b: &'a Job,
b: &'a JobBuilder,
agent: &str,
relock: bool,
graceful: bool,
@ -250,7 +250,7 @@ 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 Job,
b: &'a JobBuilder,
agent: &str,
relock: bool,
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
/// declare it without being handed the internals. Only the boot sweep wants it.
pub(crate) fn graceful_rebuild_nodes<'a>(
b: &'a Job,
b: &'a JobBuilder,
agent: &str,
relock: bool,
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`
/// and `FinalizeDeploy` declare is re-entered from the ancestor already holding
/// 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 _finalize = b
.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
/// 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: &Job, agent: &str, relock: bool) {
pub fn rebuild(b: &JobBuilder, agent: &str, relock: bool) {
let roots = rebuild_nodes(b, agent, relock, None);
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`
/// 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();
// The window is the widest holder in the tree: it brackets a nix
// 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
/// `AfterAny` onto `Provision` — the DAG's only other group-root, so its roll-up
/// 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 provision = b
.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
/// subgraph's `MetaSync` / `Prebuild` / `Reconcile`, so the `EmitRebuilt` tail
/// 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
.node(NodeKind::WritePermFile {
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
/// crash-watch suppression during its `Swap` — the property the old child
/// `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
.node(NodeKind::MetaLock {
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.
/// 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: &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
.node(NodeKind::Reparent { moves })
.needs(Resource::MetaWindow);