feat(hivectl): queue-routed lifecycle verbs with wait + DAG progress

every agent lifecycle verb on the admin socket (rebuild / restart /
restart-all / kill / stop / start) now submits job-queue DAGs and
returns their ids; hivectl polls the new HostRequest::QueueDag and
prints a live node-chain progress line per DAG (fan-out children
included), exiting non-zero on failure — --no-wait opts out. DagView
and the queue wire enums move to hive_sh4re::jobs (wire types live in
the shared crate); the last fused rebuild path (lifecycle::rebuild)
is gone. tracker: #2166
This commit is contained in:
müde 2026-07-06 22:30:49 +02:00
commit b489454dc2
9 changed files with 641 additions and 443 deletions

View file

@ -4,6 +4,7 @@ use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
pub mod assets;
pub mod jobs;
pub mod paths;
pub mod priv_proto;
pub mod wire_time;
@ -69,6 +70,10 @@ pub enum HostRequest {
/// matrix GUI disabled). Backs `hivectl open` + the federation
/// peer-config block (which reads the bare `domain`).
Urls,
/// Fetch one job-queue DAG (plus its live fan-out children, linked
/// via `parent_id`) by id — the polling surface behind `hivectl`'s
/// wait/progress loop. Result: [`HostResponse::dags`].
QueueDag { id: u64 },
/// List pending approval requests.
Pending,
/// Approve a pending request by id; the action runs immediately.
@ -170,7 +175,7 @@ pub struct HiveUrls {
pub matrix: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct HostResponse {
pub ok: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
@ -188,6 +193,16 @@ pub struct HostResponse {
/// request kind.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub agent_statuses: Option<Vec<AgentStatusRow>>,
/// Ids of the job-queue DAGs this request submitted (rebuild /
/// restart / power ops). Clients poll them via
/// [`HostRequest::QueueDag`]; `None` for non-submitting requests.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub queued_dags: Option<Vec<u64>>,
/// `QueueDag` result — the requested DAG followed by its live
/// fan-out children ([`jobs::DagView`]). Empty when the DAG has
/// been evicted from the queue's history tail.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub dags: Option<Vec<jobs::DagView>>,
}
/// One row in the approval queue. `commit_ref` is overloaded per
@ -298,11 +313,7 @@ impl HostResponse {
pub fn success() -> Self {
Self {
ok: true,
error: None,
agents: None,
approvals: None,
urls: None,
agent_statuses: None,
..Self::default()
}
}
@ -311,10 +322,7 @@ impl HostResponse {
Self {
ok: false,
error: Some(message.into()),
agents: None,
approvals: None,
urls: None,
agent_statuses: None,
..Self::default()
}
}
@ -322,11 +330,8 @@ impl HostResponse {
pub fn list(agents: Vec<String>) -> Self {
Self {
ok: true,
error: None,
agents: Some(agents),
approvals: None,
urls: None,
agent_statuses: None,
..Self::default()
}
}
@ -334,11 +339,8 @@ impl HostResponse {
pub fn pending(approvals: Vec<Approval>) -> Self {
Self {
ok: true,
error: None,
agents: None,
approvals: Some(approvals),
urls: None,
agent_statuses: None,
..Self::default()
}
}
@ -347,11 +349,8 @@ impl HostResponse {
pub fn urls(urls: HiveUrls) -> Self {
Self {
ok: true,
error: None,
agents: None,
approvals: None,
urls: Some(urls),
agent_statuses: None,
..Self::default()
}
}
@ -360,11 +359,29 @@ impl HostResponse {
pub fn agent_statuses(rows: Vec<AgentStatusRow>) -> Self {
Self {
ok: true,
error: None,
agents: None,
approvals: None,
urls: None,
agent_statuses: Some(rows),
..Self::default()
}
}
/// A request that submitted job-queue DAGs — carries their ids for
/// the client's wait/progress loop.
#[must_use]
pub fn queued(ids: Vec<u64>) -> Self {
Self {
ok: true,
queued_dags: Some(ids),
..Self::default()
}
}
/// `QueueDag` result — the polled DAG + its live children.
#[must_use]
pub fn dags(dags: Vec<jobs::DagView>) -> Self {
Self {
ok: true,
dags: Some(dags),
..Self::default()
}
}
}