From a3a0b34668987d93f9a517d866eb07a8c9f3feec Mon Sep 17 00:00:00 2001 From: atlas Date: Sun, 2 Aug 2026 21:49:55 +0200 Subject: [PATCH] jobq: completion is pub(crate); the DAG container is not a special case `submit` used to complete the container node by hand, right after inserting it, so it would park in `Finishing` and its children unblock. That was the last caller of `Scheduler::complete` outside the crate, and the justification was that the container "never needs claiming or executing". It does, though, in the sense that matters: it is a node with no logic of its own, and the scheduler already knows what to do with one. It declares no resources, so it is claimable the moment it is inserted; `run_node`'s `Dag` arm already returns `Ok(())`, exactly as it does for `DeployWindow`, which is the same shape and was never special-cased. Deleting the inline completion costs one claim round-trip and removes the only reason the crate had to expose completion at all. `complete` is `pub(crate)` now. Completion is reachable only from inside the future `claim_next` hands back, so a node cannot be finished without the claim it answers, and cannot be claimed without the future that finishes it. That was the point of the seam. --- hive-c0re/src/job_queue/mod.rs | 11 ++++++----- hive-jobq/src/scheduler.rs | 12 ++++-------- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/hive-c0re/src/job_queue/mod.rs b/hive-c0re/src/job_queue/mod.rs index a812d4ec..597a564f 100644 --- a/hive-c0re/src/job_queue/mod.rs +++ b/hive-c0re/src/job_queue/mod.rs @@ -201,6 +201,12 @@ impl JobQueue { /// roots re-parented to the container). Returns the container's id as the /// DAG id — its rolled-up state is the DAG state. /// + /// The container is an ordinary node: it declares no resources, so the + /// scheduler claims it on the next pass, runs its (empty) logic and parks + /// it in `Finishing`, at which point its children become runnable. Nothing + /// here completes it by hand — a node with no work of its own still goes + /// the way every other node goes. + /// /// Takes the spec's recipe by generic, not as a boxed closure: a spec /// travels from the template that built it directly into this call, so /// there is nothing to allocate for. @@ -222,11 +228,6 @@ impl JobQueue { ) .map_err(|e| anyhow::anyhow!("job_queue: container insert failed: {e}"))?; insert_group(&mut inner, spec.declare, Some(container))?; - // Settle the container's own (no-op) logic immediately so it parks in - // `Finishing` and its children become runnable — it never needs claiming - // or executing, and stays out of `claim_ready`. It rolls up terminal when - // its whole subtree settles (that's the DAG-done signal). - inner.complete(container, Outcome::Done); drop(inner); self.notify.notify_one(); Ok(container.get()) diff --git a/hive-jobq/src/scheduler.rs b/hive-jobq/src/scheduler.rs index dadbac0d..1ab6015d 100644 --- a/hive-jobq/src/scheduler.rs +++ b/hive-jobq/src/scheduler.rs @@ -306,14 +306,10 @@ impl Scheduler { /// propagates up the parent chain. Claim again afterwards to start /// newly-unblocked work. /// - /// ⚠️ **Still `pub` for one caller**, and that caller is the last hole in - /// this wall: a host inserting a group root with no logic of its own - /// completes it immediately so it parks in `Finishing` and its children - /// become runnable. That is a statement about the *node* ("this one has no - /// work"), not an event to report, and it wants to be expressible at - /// insert time so completion can go `pub(crate)` alongside - /// [`Self::complete_growing`]. - pub fn complete(&mut self, id: NodeId, outcome: Outcome) { + /// `pub(crate)`: completion is reachable only from inside the future + /// [`Self::claim_next`] hands back, so it is not expressible without the + /// claim it answers. + pub(crate) fn complete(&mut self, id: NodeId, outcome: Outcome) { match outcome { Outcome::Failed(error) => { // Record the reason before the terminal transition so it's set