refactor(#2591): slim the job-queue wire types to a raw-graph projection
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<Utc> (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/<node_id> query (next commits).
This commit is contained in:
parent
a7f0f3d231
commit
54d144b647
1 changed files with 33 additions and 88 deletions
|
|
@ -6,68 +6,9 @@
|
||||||
//! live in `hive-c0re::job_queue`; these are the serialized views it
|
//! live in `hive-c0re::job_queue`; these are the serialized views it
|
||||||
//! produces. Semantics: `docs/coordinator.md::Job queue`.
|
//! produces. Semantics: `docs/coordinator.md::Job queue`.
|
||||||
|
|
||||||
|
use chrono::{DateTime, Utc};
|
||||||
use serde::{Deserialize, Serialize};
|
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
|
/// Where the submit request originated — drives the "why" chip on the
|
||||||
/// dashboard.
|
/// dashboard.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
#[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.
|
/// so the widening from the old dag-local `u32` is transparent.
|
||||||
pub type NodeId = u64;
|
pub type NodeId = u64;
|
||||||
|
|
||||||
/// One node of a queued DAG, as serialized. Step labels, build-log
|
/// One node of a queued DAG, serialized near-raw from the scheduler
|
||||||
/// links, errors, and timestamps are per-node; the DAG-level `state`
|
/// graph. Lifecycle (`state` / `started_at` / `finished_at` / `error`)
|
||||||
/// is a roll-up.
|
/// 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/<id>`), not carried inline.
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct NodeView {
|
pub struct NodeView {
|
||||||
pub id: NodeId,
|
pub id: NodeId,
|
||||||
|
|
@ -156,44 +100,45 @@ pub struct NodeView {
|
||||||
/// `"drain"`, `"write_dropin"`, `"write_perm_file"`,
|
/// `"drain"`, `"write_dropin"`, `"write_perm_file"`,
|
||||||
/// `"approval_deploy"`.
|
/// `"approval_deploy"`.
|
||||||
pub kind: String,
|
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)]
|
#[serde(default)]
|
||||||
pub deps: Vec<NodeId>,
|
pub deps: Vec<NodeId>,
|
||||||
pub state: State,
|
pub state: State,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub step: Option<String>,
|
pub started_at: Option<DateTime<Utc>>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub build_log_id: Option<i64>,
|
pub finished_at: Option<DateTime<Utc>>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
||||||
pub started_at: Option<i64>,
|
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
||||||
pub finished_at: Option<i64>,
|
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub error: Option<String>,
|
pub error: Option<String>,
|
||||||
|
/// 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<i64>,
|
||||||
|
/// 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<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A queued/running/recent DAG. `kind` = template string, roll-up
|
/// A queued / running / failed DAG — a thin projection of one container
|
||||||
/// `state`; everything per-node appears exactly once, inside `nodes`.
|
/// node plus its (non-`Done`) subtree from the scheduler graph. Only
|
||||||
/// There is no DAG-level `agent` — a DAG can span agents, so agent lives
|
/// non-derivable facts live here: `id`, `source`, `reason`, `created_at`,
|
||||||
/// on each [`NodeView`]; consumers group nodes by `NodeView::agent`.
|
/// 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)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct DagView {
|
pub struct DagView {
|
||||||
pub id: u64,
|
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 source: Source,
|
||||||
pub reason: String,
|
pub reason: String,
|
||||||
pub enqueued_at: i64,
|
/// When the DAG was enqueued.
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
pub created_at: DateTime<Utc>,
|
||||||
pub started_at: Option<i64>,
|
/// Nodes of this DAG with `Done` ones excluded. A DAG whose nodes are
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
/// all `Done` is omitted from the snapshot entirely; a `Failed` DAG
|
||||||
pub finished_at: Option<i64>,
|
/// lingers until aged out by the history cap.
|
||||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
|
||||||
pub inputs: Vec<String>,
|
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
||||||
pub approval_id: Option<i64>,
|
|
||||||
pub nodes: Vec<NodeView>,
|
pub nodes: Vec<NodeView>,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue