From 54d144b64768036da8bc1048445615c61914a816 Mon Sep 17 00:00:00 2001 From: atlas Date: Thu, 23 Jul 2026 14:25:00 +0200 Subject: [PATCH] refactor(#2591): slim the job-queue wire types to a raw-graph projection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit WIP (hive-sh4re only; hive-c0re construction follows). Reshapes the dashboard/hivectl queue wire per mara's redesign (#2637): - DagView is now a thin projection: { id, source, reason, created_at, nodes }. Dropped the rolled-up kind (Template)/state/started_at/finished_at/inputs/ approval_id — the client derives label + roll-up state + DAG timestamps from the node set. - NodeView drops step + inline build_log_id; started_at/finished_at are chrono DateTime (off the hive_jobq Node); non-derivable per-node payload rides the owning node (approval_id on the approval node, inputs on the meta_lock node). - Deleted the Template enum entirely. Done nodes are excluded from the wire (a fully-done DAG disappears; a failed DAG lingers until the history cap). Build logs move to an on-demand GET /api/build-log/ query (next commits). --- hive-sh4re/src/jobs.rs | 121 +++++++++++------------------------------ 1 file changed, 33 insertions(+), 88 deletions(-) diff --git a/hive-sh4re/src/jobs.rs b/hive-sh4re/src/jobs.rs index a429b72b..10b4c5e0 100644 --- a/hive-sh4re/src/jobs.rs +++ b/hive-sh4re/src/jobs.rs @@ -6,68 +6,9 @@ //! live in `hive-c0re::job_queue`; these are the serialized views it //! produces. Semantics: `docs/coordinator.md::Job queue`. +use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; -/// What a DAG *means* — the request-level shape. Wire strings match -/// the pre-DAG queue's `kind` values so dashboards key off the same -/// tags. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -pub enum Template { - /// Rebuild one agent's container (prebuild → stop → profile-swap → - /// reconcile). - Rebuild, - /// Bump meta flake locks; grows a rebuild subgraph per affected - /// agent into the same DAG on completion. - MetaUpdate, - /// First-deploy spawn (approval-driven). - Spawn, - /// Reserved for a future destroy integration. - Destroy, - /// Mechanical stop + converge to `wanted = Up` (a restart). - Restart, - /// Signal → drain → mechanical stop → converge to `wanted = Up` — a - /// graceful restart as one atomic DAG (drains the harness before the - /// stop, same as `GracefulStop`, but then reconciles back up instead - /// of staying down). - GracefulRestart, - /// Perm-file commit followed by the rebuild subgraph. - PermChange, - /// Quiesce the harness, drain, then stop (`wanted = Offline`). - GracefulStop, - /// Converge to `wanted = Up`. - Start, - /// Converge to `wanted = Offline`. - Stop, - /// Bare converge of observed power state to the persisted intent - /// (boot reconcile). - Reconcile, - /// Boot-time config sweep as one DAG: a hyperhive lock bump that grows - /// a rebuild subgraph per stale agent, plus a `Reconcile` per drifted - /// agent — all in a single DAG (no anchor node, no child DAGs). - Boot, -} - -impl Template { - #[must_use] - pub fn as_str(self) -> &'static str { - match self { - Template::Rebuild => "rebuild", - Template::MetaUpdate => "meta_update", - Template::Spawn => "spawn", - Template::Destroy => "destroy", - Template::Restart => "restart", - Template::GracefulRestart => "graceful_restart", - Template::PermChange => "perm_change", - Template::GracefulStop => "graceful_stop", - Template::Start => "start", - Template::Stop => "stop", - Template::Reconcile => "reconcile", - Template::Boot => "boot", - } - } -} - /// Where the submit request originated — drives the "why" chip on the /// dashboard. #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] @@ -140,9 +81,12 @@ pub enum PermPayload { /// so the widening from the old dag-local `u32` is transparent. pub type NodeId = u64; -/// One node of a queued DAG, as serialized. Step labels, build-log -/// links, errors, and timestamps are per-node; the DAG-level `state` -/// is a roll-up. +/// One node of a queued DAG, serialized near-raw from the scheduler +/// graph. Lifecycle (`state` / `started_at` / `finished_at` / `error`) +/// comes straight off the `hive_jobq::Node`. The client derives DAG-level +/// roll-ups (label, state, timestamps) from the node set — nothing is +/// rolled up host-side. Build logs are fetched on demand by node id +/// (`GET /api/build-log/`), not carried inline. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct NodeView { pub id: NodeId, @@ -156,44 +100,45 @@ pub struct NodeView { /// `"drain"`, `"write_dropin"`, `"write_perm_file"`, /// `"approval_deploy"`. pub kind: String, - /// Ids of the nodes this one waits for. + /// Ids of the nodes this one waits for. May reference an already-`Done` + /// node that's been filtered out of the wire — the client treats a dep + /// on an absent node as satisfied. #[serde(default)] pub deps: Vec, pub state: State, #[serde(default, skip_serializing_if = "Option::is_none")] - pub step: Option, + pub started_at: Option>, #[serde(default, skip_serializing_if = "Option::is_none")] - pub build_log_id: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub started_at: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub finished_at: Option, + pub finished_at: Option>, #[serde(default, skip_serializing_if = "Option::is_none")] pub error: Option, + /// Approval-queue row id — present only on the `approval_deploy` node. + /// The client links a DAG to its pending approval through this (it is + /// not derivable from the graph, so it rides the node that owns it). + #[serde(default, skip_serializing_if = "Option::is_none")] + pub approval_id: Option, + /// Meta-flake inputs being bumped — present only on the `meta_lock` + /// node. Display-only payload, not derivable from the graph. + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub inputs: Vec, } -/// A queued/running/recent DAG. `kind` = template string, roll-up -/// `state`; everything per-node appears exactly once, inside `nodes`. -/// There is no DAG-level `agent` — a DAG can span agents, so agent lives -/// on each [`NodeView`]; consumers group nodes by `NodeView::agent`. +/// A queued / running / failed DAG — a thin projection of one container +/// node plus its (non-`Done`) subtree from the scheduler graph. Only +/// non-derivable facts live here: `id`, `source`, `reason`, `created_at`, +/// and the node set. The client derives the card label, roll-up state, and +/// DAG timestamps from `nodes` (per-node `kind` + lifecycle) — nothing is +/// rolled up host-side. There is no DAG-level `agent`: a DAG can span +/// agents, so agent is per-[`NodeView`]; consumers group by `NodeView::agent`. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct DagView { pub id: u64, - /// Template wire string — same values the old `kind` field used. - pub kind: Template, - /// Roll-up: `failed` if any node failed, else `running` / - /// `queued` / `cancelled` / `done`. - pub state: State, pub source: Source, pub reason: String, - pub enqueued_at: i64, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub started_at: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub finished_at: Option, - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub inputs: Vec, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub approval_id: Option, + /// When the DAG was enqueued. + pub created_at: DateTime, + /// Nodes of this DAG with `Done` ones excluded. A DAG whose nodes are + /// all `Done` is omitted from the snapshot entirely; a `Failed` DAG + /// lingers until aged out by the history cap. pub nodes: Vec, }