From f04a0cee92f9928d9b7a5acd654ac904805de15c Mon Sep 17 00:00:00 2001 From: atlas Date: Tue, 4 Aug 2026 11:27:26 +0200 Subject: [PATCH] wip(#3001): convert remaining unblocked call sites; sweep docs 21 of 28 non-test call sites now insert directly. power.rs compiles. The only remaining errors are server.rs's 5, which are blocked: those sites feed the returned id into HostResponse::queued -> `queued_dags`, a wire field hivectl polls via QueueDag. Removing the container without answering that breaks hivectl's wait/progress loop; asked on the issue. Also swept the deleted symbol out of prose, not just code: - docs/coordinator.md: "the submit layer (job_queue/submit.rs)" -> the power layer (job_queue/power.rs), and "submits" -> "inserts". - templates.rs module doc: points at super::power for the power ops. - lifecycle_ops.rs module doc: says which path each op takes now. - mod.rs's insert_group comment restated the open issue verbatim ("a DAG is addressed by its container node, which submit inserts itself"). Replaced with what is actually true for that path. Dashboard behaviour deltas worth review: insert failures are now logged per agent instead of swallowed, and UPDATE-ALL emits one queue snapshot after the loop rather than one per agent. --- docs/coordinator.md | 8 +- hive-c0re/src/dashboard/lifecycle_ops.rs | 95 +++++++++++------------- hive-c0re/src/dashboard/topology.rs | 1 - hive-c0re/src/job_queue/mod.rs | 6 +- hive-c0re/src/job_queue/power.rs | 5 +- hive-c0re/src/job_queue/templates.rs | 4 +- 6 files changed, 54 insertions(+), 65 deletions(-) diff --git a/docs/coordinator.md b/docs/coordinator.md index 115d8ec2..b4e6b4c2 100644 --- a/docs/coordinator.md +++ b/docs/coordinator.md @@ -106,7 +106,7 @@ subgraph each (independent roots, run concurrently on their own leases), not N separate DAGs. **These are built dynamically from each agent's live running state** (an -async `lifecycle::is_running` read), so they live in `job_queue/submit.rs`, +async `lifecycle::is_running` read), so they live in `job_queue/power.rs`, not the pure/sync `templates.rs`. Per-agent shape rule: `stop`/`start` carry a head `SetWanted` (intent) — `restart` does not; the tail `Reconcile` (convergence guarantee — cheap, noops when already converged) is ALWAYS @@ -161,12 +161,12 @@ Notable collapses: Per-agent power *intent* — `wanted: Up | Offline` — is durable as the `agent_power` table in the coordinator DB (`hive-c0re/src/stores/power.rs`). `container_view` remains the observed *status*; `Reconcile` nodes converge the -two. Setting `wanted` is never a queued node: the submit layer -(`job_queue/submit.rs`) writes the row synchronously, then submits the DAG +two. Setting `wanted` is never a queued node: the power layer +(`job_queue/power.rs`) writes the row synchronously, then inserts the DAG whose `Reconcile` reads the fresh value — rapid toggles are last-writer-wins. Power toggles never commit to the meta repo. Every operator power surface — dashboard buttons, the MCP tools, and `hivectl stop/start/restart/kill` — -rides the queue through that submit layer, so intent, lease serialization, +rides the queue through that power layer, so intent, lease serialization, and crash-watch suppression can't drift per surface; the only direct starts left are the root-agent bootstrap and infra containers (no lease, no harness). Cancelling a still-queued power DAG reverts `wanted` to the diff --git a/hive-c0re/src/dashboard/lifecycle_ops.rs b/hive-c0re/src/dashboard/lifecycle_ops.rs index 87e7c2d0..b4f399e0 100644 --- a/hive-c0re/src/dashboard/lifecycle_ops.rs +++ b/hive-c0re/src/dashboard/lifecycle_ops.rs @@ -1,11 +1,12 @@ //! Container lifecycle endpoints for the dashboard. //! //! Rebuild / restart / start / stop (hard + graceful) / update-all all -//! submit DAGs to the job queue (`job_queue::submit`), so each shows a -//! visible queued→running transient on the dashboard — a direct -//! sub-second start/stop only flashed the badge. Start/stop also -//! persist the agent's `wanted` power intent before submitting; the -//! DAG's `Reconcile` converges to it. Destroy delegates to +//! insert DAGs into the job queue — the power ops via +//! [`crate::job_queue::power`], the static shapes straight through +//! `JobQueue::insert` — so each shows a visible queued→running transient on +//! the dashboard; a direct sub-second start/stop only flashed the badge. +//! Start/stop also persist the agent's `wanted` power intent before +//! inserting; the DAG's `Reconcile` converges to it. Destroy delegates to //! `actions::destroy` (optionally purging). use axum::{ @@ -27,7 +28,6 @@ pub(super) struct GracefulParams { } use super::{AppState, Ident, error_response, guard_agent_name, strip_container_prefix}; -use crate::job_queue::{Source, submit}; use crate::{actions, lifecycle}; /// Queue a rebuild DAG for `name`. @@ -50,12 +50,13 @@ pub(super) async fn post_rebuild( if let Some(reject) = guard_agent_name(&state, &logical).await { return reject; } - submit::rebuild( - &state.coord, - &logical, - Source::Manual, - "manual via dashboard ↻ R3BU1LD button".to_owned(), - ); + if let Err(e) = state.coord.job_queue.insert(|b| { + crate::job_queue::templates::rebuild(b, &logical, true); + Vec::new() + }) { + tracing::error!(agent = %logical, error = ?e, "rebuild: insert failed"); + } + state.coord.emit_rebuild_queue_snapshot(); (StatusCode::OK, "ok").into_response() } @@ -92,13 +93,11 @@ pub(super) async fn post_kill( // timeout fallback to a hard stop). The agent's lifecycle // lease keeps it from racing an in-flight rebuild for the same // agent, and per-node progress surfaces on the queue snapshot. - submit::graceful_stop( - &state.coord, - &logical, - Source::Manual, - "manual via dashboard graceful stop".to_owned(), - ) - .await; + if let Err(e) = + crate::job_queue::power::stop_many(&state.coord, &[logical.clone()], true).await + { + tracing::error!(agent = %logical, error = ?e, "graceful stop: insert failed"); + } return (StatusCode::OK, "ok").into_response(); } // Manager is stoppable from the dashboard like any other @@ -111,13 +110,10 @@ pub(super) async fn post_kill( // `socket_server.rs::Request::Kill` stays in place: a // manager calling Kill on its own container is self-suicide // mid-call, not a legitimate operator action. - submit::stop( - &state.coord, - &logical, - Source::Manual, - "manual via dashboard stop".to_owned(), - ) - .await; + if let Err(e) = crate::job_queue::power::stop_many(&state.coord, &[logical.clone()], false).await + { + tracing::error!(agent = %logical, error = ?e, "stop: insert failed"); + } (StatusCode::OK, "ok").into_response() } @@ -149,22 +145,18 @@ pub(super) async fn post_restart( return reject; } if params.graceful { - submit::graceful_restart( - &state.coord, - &logical, - Source::Manual, - "manual via dashboard graceful restart".to_owned(), - ) - .await; + if let Err(e) = + crate::job_queue::power::restart_many(&state.coord, &[logical.clone()], true).await + { + tracing::error!(agent = %logical, error = ?e, "graceful restart: insert failed"); + } return (StatusCode::OK, "ok").into_response(); } - submit::restart( - &state.coord, - &logical, - Source::Manual, - "manual via dashboard ↺ R3START button".to_owned(), - ) - .await; + if let Err(e) = + crate::job_queue::power::restart_many(&state.coord, &[logical.clone()], false).await + { + tracing::error!(agent = %logical, error = ?e, "restart: insert failed"); + } (StatusCode::OK, "ok").into_response() } @@ -226,13 +218,9 @@ pub(super) async fn post_start( return (StatusCode::OK, "ok").into_response(); } } - submit::start( - &state.coord, - &logical, - Source::Manual, - "manual via dashboard start".to_owned(), - ) - .await; + if let Err(e) = crate::job_queue::power::start_many(&state.coord, &[logical.clone()]).await { + tracing::error!(agent = %logical, error = ?e, "start: insert failed"); + } (StatusCode::OK, "ok").into_response() } @@ -411,13 +399,14 @@ pub(super) async fn post_update_all(State(state): State) -> Response { else { continue; }; - submit::rebuild( - &state.coord, - &logical, - Source::Manual, - "manual via dashboard 🌀 UPDATE ALL".to_owned(), - ); + if let Err(e) = state.coord.job_queue.insert(|b| { + crate::job_queue::templates::rebuild(b, &logical, true); + Vec::new() + }) { + tracing::error!(agent = %logical, error = ?e, "update-all: insert failed"); + } } + state.coord.emit_rebuild_queue_snapshot(); (StatusCode::OK, "ok").into_response() } diff --git a/hive-c0re/src/dashboard/topology.rs b/hive-c0re/src/dashboard/topology.rs index d007f446..79ac83a2 100644 --- a/hive-c0re/src/dashboard/topology.rs +++ b/hive-c0re/src/dashboard/topology.rs @@ -21,7 +21,6 @@ use utoipa::ToSchema; use problem_details::ProblemDetails; use super::{AppState, error_problem}; -use crate::job_queue::{Source, submit}; /// `POST /api/topology/set-parent` body. `child` is required. /// `new_parent` may be: diff --git a/hive-c0re/src/job_queue/mod.rs b/hive-c0re/src/job_queue/mod.rs index 655ad636..4c5e74f2 100644 --- a/hive-c0re/src/job_queue/mod.rs +++ b/hive-c0re/src/job_queue/mod.rs @@ -161,9 +161,9 @@ fn insert_group( inner .insert_job(group_parent, |b| { declare(b); - // c0re names no handles: a DAG is addressed by its container node, - // which `submit` inserts itself, and nothing downstream looks an - // individual step up by id. + // A runtime-appended subgraph is addressed by the node that emitted + // it (`group_parent`), so this path names nothing. Callers that DO + // want a handle use `JobQueue::insert` and name the node there. Vec::new() }) .map_err(|e| anyhow::anyhow!("job_queue: graph insert failed: {e}"))?; diff --git a/hive-c0re/src/job_queue/power.rs b/hive-c0re/src/job_queue/power.rs index 1bb26b95..67d2650e 100644 --- a/hive-c0re/src/job_queue/power.rs +++ b/hive-c0re/src/job_queue/power.rs @@ -16,8 +16,9 @@ use std::sync::Arc; use hive_jobq::NodeId; -use super::JobBuilder; -use super::templates; +use super::resource::Resource; +use super::templates::rebuild_nodes; +use super::{JobBuilder, NodeKind}; use crate::coordinator::Coordinator; use crate::lifecycle; diff --git a/hive-c0re/src/job_queue/templates.rs b/hive-c0re/src/job_queue/templates.rs index 536093fc..cf110437 100644 --- a/hive-c0re/src/job_queue/templates.rs +++ b/hive-c0re/src/job_queue/templates.rs @@ -17,8 +17,8 @@ //! //! 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`]). +//! `lifecycle::is_running` read), so [`super::power`] assembles them out of +//! the primitives this module exports ([`rebuild_nodes`]). use hive_jobq::TerminalState;