feat(hive-c0re): replace rebuild queue with generic job-DAG queue

jobs are now DAGs of primitive nodes (prebuild, stop-for-update, swap,
reconcile, signal, drain, ...) driven by one scheduler with N build
slots + per-agent lifecycle leases. per-agent power intent (wanted
up/offline) is durable in agent_power.sqlite; Reconcile nodes converge
observed state to it. kills the graceful-stop watcher thread, the
deferred-start follow-up, and the cascade pre-enqueue (fan-out on
MetaLock completion instead). tracker: #2166
This commit is contained in:
müde 2026-07-06 20:13:14 +02:00
commit 7946e03fde
25 changed files with 3673 additions and 2731 deletions

View file

@ -299,12 +299,13 @@ struct StateSnapshot {
/// disabled "updating…" state; live transitions arrive via the
/// `MetaUpdateRunning` event.
meta_update_running: bool,
/// Current state of the global rebuild queue — pending + running
/// long-lived ops (rebuild / meta-update / spawn) plus the most
/// recent few terminal entries the queue retains for history.
/// Live transitions arrive via the `RebuildQueueChanged` event.
/// See `rebuild_queue.rs`.
rebuild_queue: Vec<crate::rebuild_queue::QueueEntry>,
/// Current state of the global job queue — pending + running DAGs
/// (rebuild / meta-update / spawn / power ops) with their per-node
/// breakdowns, plus the most recent few terminal DAGs the queue
/// retains for history. Live transitions arrive via the
/// `RebuildQueueChanged` event. See `job_queue/`. Field name kept
/// from the old flat queue for wire compatibility.
rebuild_queue: Vec<crate::job_queue::DagView>,
/// Whether the hive-forge container is up. When true the dashboard
/// links each container's config + each approval's commit into the
/// forge's `agent-configs` repos.
@ -607,7 +608,7 @@ async fn api_state(headers: HeaderMap, State(state): State<AppState>) -> axum::J
question_history,
tombstones,
port_conflicts,
rebuild_queue: state.coord.rebuild_queue.snapshot(),
rebuild_queue: state.coord.job_queue.snapshot(),
forge_present: crate::forge::is_present().await,
matrix_gui_enabled: std::env::var_os("HIVE_MATRIX_GUI_ENABLED").is_some_and(|v| {
// Accept any truthy string ("1", "true", "yes") since the
@ -1602,30 +1603,15 @@ async fn post_meta_update(
return error_response("meta-update: no inputs selected");
}
let inputs_label = inputs.join(", ");
let parent_id = state.coord.rebuild_queue.enqueue_with_inputs(
crate::rebuild_queue::QueueKind::MetaUpdate,
"hyperhive".to_owned(),
crate::rebuild_queue::QueueSource::Manual,
// Cascade rebuild children fan out from the MetaLock node when the
// lock bump lands — appended by the scheduler so they build against
// the post-bump lock, and a failed bump simply fans out nothing.
crate::job_queue::submit::meta_update(
&state.coord,
inputs,
crate::job_queue::Source::Manual,
format!("meta-update via dashboard ({inputs_label})"),
None,
inputs.clone(),
);
// Pre-enqueue cascade rebuilds NOW so they're visible in the queue
// alongside the parent. The worker's MetaUpdate arm
// no longer enqueues children — it just runs the lock bump and
// (on failure) cancels these pre-queued children.
let cascade_agents = crate::rebuild_queue::meta_update_cascade_agents(&inputs).await;
let cascade_reason = format!("meta-update cascade ({inputs_label})");
for name in cascade_agents {
state.coord.rebuild_queue.enqueue(
crate::rebuild_queue::QueueKind::Rebuild,
name,
crate::rebuild_queue::QueueSource::MetaUpdate,
cascade_reason.clone(),
Some(parent_id),
);
}
state.coord.emit_rebuild_queue_snapshot();
(StatusCode::OK, "ok").into_response()
}