diff --git a/hive-c0re/src/job_queue/exec.rs b/hive-c0re/src/job_queue/exec.rs index fe0c4074..d8866327 100644 --- a/hive-c0re/src/job_queue/exec.rs +++ b/hive-c0re/src/job_queue/exec.rs @@ -53,7 +53,7 @@ pub(super) async fn run_node( kind: &NodeKind, ) -> (super::JobBuilder, Result<()>) { // The agent this node targets rides the payload — empty for the agentless - // container kinds (`MetaLock`, `Dag`), which never read it. + // kinds (`MetaLock`, `Reparent`), which never read it. let agent = kind.agent(); // Every arm is `Result<()>`; the three that grow work declare into `builder` // *synchronously*, after their own awaits have finished. Borrowing `&builder` @@ -115,26 +115,21 @@ pub(super) async fn run_node( run_finalize_deploy(coord, *approval_id).await } NodeKind::DeployTail { approval_id, .. } => { - run_deploy_tail(coord, coord.job_queue.dag_of(id), agent, *approval_id).await + run_deploy_tail(coord, coord.job_queue.root_of(id), agent, *approval_id).await } NodeKind::ResolveApproval { approval_id, outcome, - } => run_resolve_approval(coord, coord.job_queue.dag_of(id), *approval_id, *outcome).await, + } => run_resolve_approval(coord, coord.job_queue.root_of(id), *approval_id, *outcome).await, NodeKind::EmitRebuilt { ok, .. } => { - run_emit_rebuilt(coord, agent, coord.job_queue.dag_of(id), *ok).await; + run_emit_rebuilt(coord, agent, coord.job_queue.root_of(id), *ok).await; Ok(()) } NodeKind::SetWanted { up, .. } => run_set_wanted(coord, agent, *up), - // The nodes that carry no work of their own; completing one lets it - // reach `Finishing` so the nodes under it start. - // - `Dag`: pure grouping container. The DAG's terminal side effect, if - // any, is its own tail node in the graph. - // - `DeployWindow` / `AgentWindow`: pure resource holders (braces) — - // what they declare stays held until their subtree settles. - NodeKind::Dag { .. } | NodeKind::DeployWindow { .. } | NodeKind::AgentWindow { .. } => { - Ok(()) - } + // Braces carry no work of their own; completing one lets it reach + // `Finishing` so the nodes under it start. What they declare stays held + // until their whole subtree settles. + NodeKind::DeployWindow { .. } | NodeKind::AgentWindow { .. } => Ok(()), }; (builder, result) } diff --git a/hive-c0re/src/job_queue/mod.rs b/hive-c0re/src/job_queue/mod.rs index 51febed8..24f58559 100644 --- a/hive-c0re/src/job_queue/mod.rs +++ b/hive-c0re/src/job_queue/mod.rs @@ -10,17 +10,16 @@ //! carries the agent it targets ([`NodeKind::agent`]); the two resource //! classes are [`resource::Resource`] (`BuildSlot` node-held, `Agent` lease //! subtree-held), declared per node at its construction site; -//! - a **DAG is a single container node** ([`NodeKind::Dag`], `parent = None`) -//! carrying the group's metadata, with the work nodes hung under it as -//! its subtree (the **parent axis** groups; `deps` order). So the container's -//! `NodeId` is the DAG id, its rolled-up state is the DAG state, and membership -//! is a graph walk — there are no host grouping side-tables. The lease is owned -//! by a subtree root and borrowed by its descendants (continuity); -//! - per-DAG terminal work is an ordinary **tail node** +//! - **a job has no container node.** A template declares its nodes and names +//! the roots it wants back; `insert_job` returns those ids. Grouping is the +//! parent axis (a root's rolled-up state *is* its subtree's), so membership is +//! a graph walk with no host-side side-tables. The lease is owned by a subtree +//! root and borrowed by its descendants (continuity); +//! - terminal work is an ordinary **tail node** //! ([`NodeKind::ResolveApproval`] / [`NodeKind::EmitRebuilt`]) that the builder -//! appends in [`templates`], edged onto the DAG's other group roots by the -//! outcome it reports. Templates emit one tail per outcome and the graph runs -//! exactly one, so nothing branches at runtime. +//! appends in [`templates`], edged onto the job's group roots by the outcome it +//! reports. Templates emit one tail per outcome and the graph runs exactly one, +//! so nothing branches at runtime. //! //! 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 @@ -90,12 +89,10 @@ pub struct RunningTransient { /// The crate scheduler, specialised to this host's node + resource types. /// -/// A **DAG is a single container node** ([`NodeKind::Dag`], `parent = None`) -/// whose subtree is the DAG's work — so the container's `NodeId` is the DAG id, -/// its rolled-up state is the DAG state, and there are no grouping side-tables: -/// membership + meta are graph queries ([`container`] + the `hive_jobq::Graph` -/// accessors, with the meta read straight off the container's payload). One -/// shared crate [`Graph`] holds every DAG. +/// A job is **just its nodes** — no container, no grouping side-tables. A +/// root's rolled-up state is its subtree's, so membership is a graph walk and +/// "which job is this node in" is [`JobQueue::root_of`]. One shared crate +/// [`Graph`] holds every job's nodes. /// /// There is deliberately **no wrapper struct and no per-node side map**. The /// last map held the `build_logs` row id; that link now lives on the log row @@ -229,11 +226,16 @@ impl JobQueue { &self.sched } - /// The DAG container id owning `node`, for log lines and the dashboard. - /// Derived from the graph rather than carried alongside the node — the - /// parent axis already knows it. + /// The id of the **group root** `node` belongs to, for log lines and the + /// dashboard. Derived from the graph rather than carried alongside the node + /// — the parent axis already knows it. + /// + /// Was `dag_of`, when a job's nodes hung under a container node that *was* + /// the group. Without it the parent chain ends at whichever root the + /// template declared, so this answers "which root owns this node", not + /// "which DAG is this in" — there is no longer such a thing. #[must_use] - pub fn dag_of(&self, node: NodeId) -> Option { + pub fn root_of(&self, node: NodeId) -> Option { self.lock().graph().root_of(node).map(NodeId::get) } @@ -418,9 +420,9 @@ fn find_node(sched: &Sched, id: u64) -> Option { /// root, plus the newest [`MAX_HISTORY_DAGS`] settled ones. /// /// Selected *structurally* — a root is a node with no parent. The typed -/// projection this replaced keyed on `NodeKind::Dag` instead, which made the -/// visible set depend on one host node kind; nothing here knows what a node -/// means. +/// projection this replaced keyed on the since-removed container kind instead, +/// which made the visible set depend on one host node kind; nothing here knows +/// what a node means. /// /// **This bound is load-bearing, not tidiness.** Nothing ever removes a node /// from the graph (bounded pruning is a Stage-C follow-up), so serving diff --git a/hive-c0re/src/job_queue/model.rs b/hive-c0re/src/job_queue/model.rs index f1f7936d..fff79617 100644 --- a/hive-c0re/src/job_queue/model.rs +++ b/hive-c0re/src/job_queue/model.rs @@ -280,18 +280,6 @@ pub enum NodeKind { /// `Prebuild`, but that's a no-op there — the agent is down, so prebuild /// is skipped.) SetWanted { agent: String, up: bool }, - /// The **DAG container** node: one per submitted DAG, carrying the group's - /// domain metadata. Every node hangs *under* it (its subtree), so - /// the container's `NodeId` **is** the DAG id and its rolled-up state **is** - /// the DAG state. Pure grouping — lease- and - /// build-slot-exempt; the executor instant-completes it (`Done`) so it - /// reaches `Finishing` and its children start. - /// - /// No `created_at` here: the graph stamps [`hive_jobq::Node::created_at`] on - /// every node at insert, so the container already has one. A second copy in - /// the payload would be the same instant recorded twice, with only this - /// variant's version reachable to a generic viewer. - Dag { source: Source, reason: String }, } /// How a hive-c0re node describes itself to a generic graph viewer. @@ -362,14 +350,13 @@ impl NodeKind { NodeKind::ResolveApproval { .. } => "resolve_approval", NodeKind::EmitRebuilt { .. } => "emit_rebuilt", NodeKind::SetWanted { .. } => "set_wanted", - NodeKind::Dag { .. } => "dag", } } /// The agent this node targets, or `""` for agentless kinds /// ([`NodeKind::MetaLock`] on the `hyperhive` pseudo-agent, - /// [`NodeKind::Reparent`] which can span multiple agents, and the - /// [`NodeKind::Dag`] container). + /// [`NodeKind::Reparent`] which can span multiple agents, and + /// [`NodeKind::ResolveApproval`] which acts on an approval row). #[must_use] pub fn agent(&self) -> &str { match self { @@ -397,8 +384,7 @@ impl NodeKind { | NodeKind::SetWanted { agent, .. } => agent, NodeKind::MetaLock { .. } | NodeKind::Reparent { .. } - | NodeKind::ResolveApproval { .. } - | NodeKind::Dag { .. } => "", + | NodeKind::ResolveApproval { .. } => "", } } diff --git a/hive-c0re/src/job_queue/scheduler.rs b/hive-c0re/src/job_queue/scheduler.rs index 3c19c529..f60f2028 100644 --- a/hive-c0re/src/job_queue/scheduler.rs +++ b/hive-c0re/src/job_queue/scheduler.rs @@ -93,7 +93,7 @@ pub async fn run_worker(coord: Arc) { let coord = node_coord; async move { tracing::info!( - dag = coord.job_queue.dag_of(id).unwrap_or_default(), + dag = coord.job_queue.root_of(id).unwrap_or_default(), node = id.get(), kind = kind.as_str(), agent = %kind.agent(),