//! Host admin socket wire types (`/run/hyperhive/host.sock`). //! //! The host-control protocol spoken between `hivectl` and the `hive-c0re` //! daemon. Re-homed out of `hive-sh4re` so a standalone `hivectl` can depend //! on just this protocol crate instead of the whole daemon crate. The //! job-queue wire types ([`jobs`]) travel on this socket and the dashboard //! channels `hive-c0re` serves off the same snapshot, and on nothing else — //! their whole consumer set is `hive-c0re` + `hivectl` + this crate — so they //! live here rather than in the shared crate. The payload types genuinely //! shared with the *agent* sockets (`Approval`, `AgentStatusRow`) stay in //! `hive-sh4re`. use std::path::PathBuf; use hive_sh4re::approvals::Approval; use hive_sh4re::container::AgentStatusRow; use hive_types::Ident; use serde::{Deserialize, Serialize}; pub mod jobs; // ── Shared hive layout facts ────────────────────────────────────────────── // Paths + names both the `hive-c0re` daemon and the host-side `hivectl` CLI // must agree on. Homed here (the protocol crate both sides already depend on) // so a standalone `hivectl` can reach them without linking the whole daemon // crate. `hive-c0re`'s `paths` / `lifecycle` modules re-export these, staying // the daemon's single-source facade for its own callsites. /// Default host admin socket (`/run/hyperhive/host.sock`). Used as the /// `--socket` / `--host-socket` clap `default_value` in the daemon and /// `hivectl`. pub const HOST_SOCKET: &str = "/run/hyperhive/host.sock"; /// `agents/` — per-agent persistent state root (one subdir per agent, /// bind-mounted into each container as `/agents/`). // nix: agent container bind-mount source (harness modules / agent.nix template) — must match. // priv-sock: `hive_priv_sock::AGENT_STATE_ROOT` is the same value and must stay in sync; // the privsep boundary prevents importing across the crate. pub const AGENTS_ROOT: &str = "/var/lib/hyperhive/agents"; /// `agents/` — one agent's persistent state root. /// /// Takes a validated [`Ident`] (not a raw `&str`) so a per-agent state path /// can never be built from an unvalidated name — the `../` traversal guard is /// the type, enforced at the one place every agent path is rooted. #[must_use] pub fn agent_state_dir(name: &Ident) -> PathBuf { PathBuf::from(AGENTS_ROOT).join(name.as_str()) } /// nginx basic-auth credential store for the operator dashboard vhost. /// `hivectl`'s `--htpasswd-file` clap default. /// /// Under the gateway's own state dir rather than c0re's, so that nginx /// can read it without being given access to everything else c0re keeps. // nix: read by the gateway's nginx on the host (hive-gateway/) — must match. pub const GATEWAY_HTPASSWD: &str = "/var/lib/hive-gateway/conf/gateway.htpasswd"; /// `/run/hive-agent` — per-agent runtime socket dir root (web UI unix /// socket + bound marker), one subdir per agent. The gateway's nginx /// `proxy_pass`es to `agent_web_socket(name)` directly; `hivectl` dials /// the same socket for host-side tooling that needs to talk to a running /// agent's web UI without going through the gateway (e.g. `agent /// watch`). // nix: agent container bind-mount / `RuntimeDirectory` (the harness nix modules) — must match. pub const AGENT_SOCKET_DIR: &str = "/run/hive-agent"; /// Per-agent web UI unix socket path — `AGENT_SOCKET_DIR//web.sock`. /// Same socket the gateway's nginx upstream and the harness's /// `HIVE_WEB_SOCKET` bind both derive from; see /// `docs/gateway.md::Per-agent unix-socket upstream`. #[must_use] pub fn agent_web_socket(name: &Ident) -> PathBuf { PathBuf::from(AGENT_SOCKET_DIR) .join(name.as_str()) .join("web.sock") } /// Per-agent in-container todo socket path — /// `AGENT_SOCKET_DIR//agent.sock`. Same socket the harness's /// `HIVE_AGENT_SOCKET` binds and in-container producers (matrix/bash/ /// forge-notify) already dial — `hive-c0re` uses this to push a todo /// directly into a *running* agent's local store (see /// `Coordinator::push_todo`); the container being down (socket absent) /// means the push is simply skipped, not queued. #[must_use] pub fn agent_todo_socket(name: &Ident) -> PathBuf { PathBuf::from(AGENT_SOCKET_DIR) .join(name.as_str()) .join("agent.sock") } /// nspawn machine-name prefix for agent containers (`h-`). A single /// `starts_with(AGENT_PREFIX)` filter enumerates managed containers. pub const AGENT_PREFIX: &str = "h-"; /// Map an agent's logical name to its nspawn machine name (`h-`). #[must_use] pub fn container_name(name: &str) -> String { format!("{AGENT_PREFIX}{name}") } /// Which way to reconcile an agent's config branches /// ([`HostRequest::ReconcileConfigApply`]). #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum ReconcileDirection { /// Reset the local applied checkout to forge `main`. Forge, /// Advance forge `main` from local — not supported yet. Local, } /// Requests on the host admin socket. /// /// Wire format: one JSON object per line. #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(tag = "cmd", rename_all = "snake_case")] pub enum HostRequest { /// Create and start a brand-new sub-agent container directly (full /// first-time provisioning: proposed/applied repos, state subvolume, /// meta-flake sync, `nixos-container create`), bypassing the approval /// queue. Privileged-context only. Exposed on the CLI as `hivectl /// agent create`. See `docs/approvals.md::Approval kinds /// (wire shapes)`. Wire name kept as `Spawn` (unrenamed underneath /// the CLI-verb rename — `hivectl agent start` reuses the /// existing scope-based [`HostRequest::Start`] below instead of a /// new per-agent variant, see its doc comment). Spawn { name: Ident }, /// Submit a first-creation request for the operator to approve. See /// `docs/approvals.md::Approval kinds (wire shapes)` (`Spawn`). /// Exposed on the CLI as `hivectl agent request-create`. RequestSpawn { name: Ident }, /// Hard stop a managed container. Exposed on the CLI as `hivectl /// agent kill` — kept distinct from the new graceful-only /// `hivectl agent stop` (which reuses the scope-based /// [`HostRequest::Stop`] below with `graceful: true`); `Kill` /// escalates to a hard stop, `Stop` never does. Wire name kept as /// `Kill`. Kill { name: Ident }, /// Tear down a sub-agent container, optionally purging state. /// See `docs/approvals.md::Destroy semantics`. Destroy { name: Ident, #[serde(default)] purge: bool, }, /// Stop and start a managed container without rebuilding config. /// For "kick the container" operations that don't touch the flake or /// nspawn flags. Mirrors `lifecycle::restart` (kill + start). Restart { name: Ident }, /// Park (or un-park) an agent's turn loop without touching its /// container: `hivectl pause|resume ` and the dashboard /// toggle. Writes/removes the marker file the harness polls, so the /// container stays up and keeps serving its web UI and MCP daemons /// while burning no tokens. Inbox messages queue unacked and the /// backlog drains on resume. Not a lifecycle DAG — it's a single /// marker write, so it applies immediately and works on a stopped /// container too (the pause is sticky and takes effect at next boot). SetPaused { name: Ident, /// `true` pauses, `false` resumes. Idempotent either way. paused: bool, }, /// Restart containers hive-wide (`hivectl restart`), scoped like /// `Stop`/`Start`. Each targeted agent rides exactly one DAG /// server-side — the `Restart` template (mechanical stop + reconcile), /// or, when `graceful` is set, the `GracefulRestart` template (signal → /// drain → mechanical stop → reconcile) — rather than the old /// client-side stop-then-start composition (and, briefly, a server-side /// "submit stop DAG, await it, submit start DAG" composition): a /// dropped `hivectl` connection mid-way, or a crash between the two /// submits, used to leave the agent stopped with no automatic /// follow-up, since nothing durable remembered "finish the restart" /// once the calling process/turn was gone. `GracefulRestart` closes /// that gap the same way `Restart` already does — one DAG, queued up /// front, that owns the whole sequence. Infra containers have no /// lease/DAG and restart synchronously (stop then start), same as /// before. Scope semantics match `Stop`/`Start` (all-false = /// everything). RestartScoped { #[serde(default)] scope: LifecycleScope, #[serde(default)] graceful: bool, }, /// Apply pending config to a managed container. Rebuild { name: Ident }, /// List managed containers. List, /// Report whether `name` is a managed agent, i.e. whether it has a /// persistent state dir under the agents root. Answered daemon-side /// because that root is `0700 hive-core`: a client stat-ing it /// without root gets EACCES, so the pre-flight "does this agent /// exist?" guard in front of `hivectl agent choom` / `subvol` used /// to fail with a permission error instead of an answer. The daemon /// already runs as the owning user and does the same check for its /// own provisioning paths. Result: [`HostResponse::agent_exists`]. /// /// State dir, not the live container list — a destroyed-but-kept /// agent is still an agent, and re-provisioning one should drop its /// credentials into the existing state tree. AgentExists { name: Ident }, /// List managed agents with their full status + technical state /// (running / needs-login / needs-update / deployed sha / parent / /// pending reminders) — the `hivectl list-agents` roster view. /// Reuses the dashboard's per-agent `ContainerView` aggregation. AgentStatus, /// Report this hive's canonical DNS domain /// (`services.hyperhive.domain`) plus the browser-facing home / /// forge / matrix URLs, daemon-sourced so custom forge/matrix /// domains resolve correctly. Each URL is `None` when its subsystem /// is unreachable from a browser (e.g. forge not behind the gateway, /// matrix GUI disabled). Backs `hivectl open` + the federation /// peer-config block (which reads the bare `domain`). Urls, /// Fetch one or more job-queue nodes plus their live subtrees, as /// generic `hive-jobq-wire` nodes — `hivectl`'s wait/progress loop, /// and the only way the queue is served. No /// assumption that an id names a DAG container or root — whatever /// node has that id, the backend hands back its subtree as-is. A /// batch op that submits several independent roots (e.g. one per /// agent on a hive-wide restart) is a single request naming all of /// them, not one request per id — the caller owns bundling `ids`, /// this request just answers whatever it's asked. Result: /// [`HostResponse::nodes`]. QueueNodes { ids: Vec }, /// List pending approval requests. Pending, /// Approve a pending request by id; the action runs immediately. Approve { id: i64 }, /// Deny a pending request by id. Deny { id: i64 }, /// Move an agent in the topology tree. `new_parent = None` /// promotes the agent to root, `Some(name)` sets a new parent. /// Validation rules + bind-mount caveat documented in /// `docs/agent-hierarchy.md::Current state`. SetParent { child: Ident, new_parent: Option, }, /// Declare an agent's CPU/memory overrides for the per-container /// systemd drop-in, persisted to `meta/resource-limits.json`. /// /// **Replace, not merge** — the pair given here becomes the agent's /// entire entry, matching how tool-groups/capabilities are set. A /// `None` field falls back to the hive-wide /// `agentCpuQuota` / `agentMemoryMax`, so passing both as `None` /// removes the entry entirely (reset to hive defaults). /// /// Values are passed verbatim to systemd, so the server validates /// their shape before persisting: a malformed `CPUQuota=` makes /// systemd reject the unit, which would stop the container starting. SetResourceLimits { name: Ident, cpu_quota: Option, memory_max: Option, }, /// Stop managed containers hive-wide in one operator action /// (`hivectl stop`): agents plus the selected infra containers. `scope` /// selects which classes; an all-false scope means **everything** (the /// bare `hivectl stop`). `graceful` runs the per-agent quiesce (graceful /// agent stop, issue tracker `graceful agent stop`) instead of a hard /// stop. Agents stop via the lifecycle path; infra containers via the /// host `container@` units. Stop { #[serde(default)] scope: LifecycleScope, #[serde(default)] graceful: bool, }, /// Start managed containers hive-wide — the inverse of `Stop` /// (`hivectl start`). Same `scope` semantics (all-false = everything); /// no graceful flag (start is unconditional). Start { #[serde(default)] scope: LifecycleScope, }, /// Create or refresh a matrix account + access token for `name`. /// The daemon runs the provisioning (it holds the register + admin /// tokens and the matrix creds dir) and returns the operator-facing /// results (persisted-token path for agents, or the freshly-minted /// token + password for non-agent accounts) in /// [`HostResponse::messages`]. `password` is resolved by the client /// (inline flag or stdin) and `None` requests a random throwaway. MatrixCreateUser { name: Ident, #[serde(default)] password: Option, }, /// Provision (or re-provision) the hive system admin matrix account. /// Daemon-side equivalent of `hivectl matrix sync-admin`. MatrixSyncAdmin, /// Promote a matrix user to homeserver admin via the admin API. /// Uses the daemon's system admin token; `server_name` is discovered /// from the running homeserver. MatrixPromoteUser { name: Ident }, /// Reset a matrix user's password via the admin API and persist the /// new password to the matrix creds dir so a later token mint can /// re-login. Returns the outcome in [`HostResponse::messages`]. MatrixResetPassword { name: Ident }, /// Invite a matrix user to the hive Space (default) or a specific /// `room`. Uses the daemon's admin token; idempotent /// (already-member / already-invited is a no-op). MatrixInvite { user: String, #[serde(default)] room: Option, }, /// Create or refresh a forge account + API token for `name`. Daemon-side /// equivalent of `hivectl forge create-user`: for an existing agent it /// provisions the account and persists the token to `/forge-token`; /// for a non-agent (operator/human) it mints a user and returns the token /// in [`HostResponse::messages`]. `password` is resolved client-side /// (inline flag or stdin) and only meaningful for non-agent accounts. ForgeCreateUser { name: Ident, #[serde(default)] password: Option, }, /// Report the divergence between agent `agent`'s local applied config /// checkout and its forge `agent-configs/` `main`. The daemon /// fetches forge `main` read-only and returns a human-readable report /// (ahead/behind counts, commit-range summary, `git diff --stat`, and /// the full diff when `verbose`) in [`HostResponse::messages`]. Read-only /// — never mutates either side. Backs `hivectl forge reconcile-config` /// (the diff it always shows first). ReconcileConfigStatus { agent: Ident, #[serde(default)] verbose: bool, }, /// Reconcile agent `agent`'s config branches in `direction`. /// `Forge` resets the local applied checkout to forge `main` (takes /// effect on the next deploy); `Local` is not supported yet and returns /// an [`HostResponse::error`] (advancing the protected forge `main` from /// local needs lifting branch protection — resolve via a config PR). /// Backs `hivectl forge reconcile-config --from `. ReconcileConfigApply { agent: Ident, direction: ReconcileDirection, }, /// Add or update a gateway HTTP-Basic user in the daemon's htpasswd file /// (`paths::GATEWAY_HTPASSWD`). Daemon-side equivalent of `hivectl gateway /// create-user`: the daemon bcrypt-hashes `password` (cost 12, remapped to /// the `$2y$` prefix nginx accepts) and writes the entry, so hivectl never /// touches the file. `password` is read client-side (inline flag or stdin). /// Returns a confirmation line in [`HostResponse::messages`]. GatewayCreateUser { username: String, password: String }, /// Remove a gateway HTTP-Basic user from the daemon's htpasswd file. /// Daemon-side equivalent of `hivectl gateway delete-user`. Errors if the /// user isn't present so a no-op is detectable. GatewayDeleteUser { username: String }, /// List the gateway HTTP-Basic usernames in the daemon's htpasswd file. /// Daemon-side equivalent of `hivectl gateway list-users`; the usernames /// come back in [`HostResponse::messages`], one per line. GatewayListUsers, /// Write (or overwrite) an agent's GitHub PAT under its state dir, via /// the privileged helper. Daemon-side equivalent of `hivectl github /// set-token`. `token` is resolved + non-empty-validated client-side /// (inline flag or stdin); the daemon just persists it. Read live by /// the agent's `gh` wrapper / git credential helper — no rebuild needed. SetAgentGithubToken { agent: Ident, token: String }, /// Turn on btrfs qgroup accounting on the agent-state filesystem, via /// the privileged helper. Daemon-side equivalent of `hivectl /// quota-enable`. Returns advisory lines in [`HostResponse::messages`]. QuotaEnable, /// Set (or, with `limit = None`, clear) an agent's btrfs disk quota via /// the privileged helper. Daemon-side equivalent of `hivectl agent /// quota set`; the size string is parsed to bytes client-side. /// Returns a bare success — the client prints the confirmation from /// the value it sent. QuotaLimit { name: Ident, #[serde(default)] limit: Option, }, /// Report btrfs qgroup usage (`hivectl agent quota show`, or all /// kept state dirs when `name` is absent — no CLI path reaches the /// absent case today, but the daemon still supports it). The daemon /// resolves the agent set and reads each subvolume's referenced/ /// exclusive usage via the privileged helper. Result rows land in /// [`HostResponse::quota`]; a "btrfs quota not enabled" error /// short-circuits the whole sweep as a plain [`HostResponse::error`] /// so the client can print the enable hint. QuotaShow { #[serde(default)] name: Option, }, /// Migrate an agent's plain state dir to a btrfs subvolume via the /// privileged helper (`hivectl agent subvol upgrade`). The agent /// MUST already be stopped — the client orchestrates stop → this → /// start. Returns a bare success; the client prints its own progress /// lines. UpgradeSubvolume { name: Ident }, /// Create a read-only btrfs snapshot of an agent's state subvolume /// (`hivectl agent subvol snapshot create`). `label` is /// validated client-side AND by hive-priv. Returns the snapshot's /// host path in [`HostResponse::messages`]. SnapshotSubvolume { name: Ident, label: String }, /// Delete a snapshot created by `SnapshotSubvolume` (`hivectl agent subvol /// snapshot delete`). Bare success; the client prints the confirmation. DeleteSnapshot { name: Ident, label: String }, /// Export a snapshot to a local file via `btrfs send` (`hivectl agent subvol /// snapshot send`). `dest` is a bare filename (hive-priv rejects paths); /// `parent` names an optional parent snapshot for an incremental send. /// Returns the written file's host path in [`HostResponse::messages`]. SendSnapshot { name: Ident, label: String, #[serde(default)] parent: Option, dest: String, }, /// Push a snapshot to the swarm's snapshot store over the WireGuard /// mesh (`hivectl agent subvol snapshot push`). The network /// sibling of [`HostRequest::SendSnapshot`]: same snapshot and /// optional incremental `parent`, but the stream goes to the store's /// receiver instead of a local file. /// /// There is no destination field: a swarm has exactly one store, and /// the daemon reads its address from /// `services.hyperhive.swarm.snapshotStore`, failing if none is /// configured. Bare success — nothing is written on this host to /// report a path for. PushSnapshot { name: Ident, label: String, #[serde(default)] parent: Option, }, } /// One agent's btrfs qgroup usage row — the [`HostRequest::QuotaShow`] /// result unit. `referenced` / `exclusive` are byte counts when the agent /// has a live qgroup; both are `None` (with an explanatory `note`) for a /// plain-dir agent that has no subvolume to account. The client formats the /// byte counts into human-readable columns. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct QuotaRow { pub agent: String, #[serde(default, skip_serializing_if = "Option::is_none")] pub referenced: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub exclusive: Option, /// Set instead of the byte counts when the agent has no qgroup data /// (plain dir, or a non-"quota not enabled" read error), so the client /// can print an inline explanation and keep going. #[serde(default, skip_serializing_if = "Option::is_none")] pub note: Option, } /// Selects which container classes a hive-wide [`HostRequest::Stop`] / /// [`HostRequest::Start`] touches. An all-false scope means **everything** /// (the bare `hivectl stop` / `start`); set individual fields to restrict /// (e.g. only `agents` → just the sub-agent containers). `agents` covers /// every managed sub-agent container; the rest are the named infra /// services (`hive-ci`, `hive-forge`, `hive-gateway`, `hive-matrix`) — /// containers, bar the gateway, which is the host's nginx unit. // // A flat bag of independent flag toggles — one bool per selectable // container class — is exactly the right shape here: each maps 1:1 to a // `hivectl` `--ci` / `--forge` / `--gateway` / `--matrix` flag, and they're // orthogonal (any subset is valid), so a state machine or two-variant enums // would only obscure the mapping. Hence the `struct_excessive_bools` allow. #[allow(clippy::struct_excessive_bools)] #[derive(Debug, Clone, Default, Serialize, Deserialize)] pub struct LifecycleScope { /// All sub-agent containers (`--agents`). #[serde(default)] pub agents: bool, /// Specific sub-agents by logical name (`--agent `, repeatable). /// Additive with the rest of the scope; redundant when `agents` is set /// (which already covers every sub-agent). #[serde(default)] pub agent_names: Vec, #[serde(default)] pub ci: bool, #[serde(default)] pub forge: bool, #[serde(default)] pub gateway: bool, #[serde(default)] pub matrix: bool, } impl LifecycleScope { /// True when nothing is explicitly selected — interpreted as "all /// classes" (the bare `hivectl stop` / `start` with no scope flags). pub fn is_everything(&self) -> bool { !(self.agents || self.ci || self.forge || self.gateway || self.matrix) && self.agent_names.is_empty() } } /// This hive's canonical domain plus the browser-facing URLs for its /// web surfaces — the `Urls` request result. Every field is `None` when /// the corresponding surface can't be reached from a browser (domain /// unset, forge not behind the gateway, matrix GUI disabled), so the CLI /// can give a precise hint instead of opening a dead link. #[derive(Debug, Clone, Default, Serialize, Deserialize)] pub struct HiveUrls { /// Canonical hive domain (`services.hyperhive.domain`). #[serde(default, skip_serializing_if = "Option::is_none")] pub domain: Option, /// Operator dashboard root (`https:///`). #[serde(default, skip_serializing_if = "Option::is_none")] pub home: Option, /// Forge browser URL (`HIVE_FORGE_PUBLIC_URL`) — only the /// behind-gateway public URL; `None` on direct-port forge deploys. #[serde(default, skip_serializing_if = "Option::is_none")] pub forge: Option, /// Matrix GUI (fluffychat) browser URL — `None` when the matrix GUI /// is disabled. #[serde(default, skip_serializing_if = "Option::is_none")] pub matrix: Option, } #[derive(Debug, Clone, Default, Serialize, Deserialize)] pub struct HostResponse { pub ok: bool, #[serde(default, skip_serializing_if = "Option::is_none")] pub error: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub agents: Option>, #[serde(default, skip_serializing_if = "Option::is_none")] pub approvals: Option>, /// `Urls` result — this hive's domain plus the browser-facing /// home / forge / matrix URLs. `None` for every other request kind. #[serde(default, skip_serializing_if = "Option::is_none")] pub urls: Option, /// `AgentStatus` result — one row per managed agent with its /// running/health flags + technical state. `None` for every other /// request kind. #[serde(default, skip_serializing_if = "Option::is_none")] pub agent_statuses: Option>, /// `AgentExists` result — whether the named agent has a state dir /// under the agents root. `None` for every other request kind, which /// is why it's an `Option` and not a bare `bool`: a client must /// be able to tell "the daemon said no" from "the daemon answered a /// different question". #[serde(default, skip_serializing_if = "Option::is_none")] pub agent_exists: Option, /// Ids of the job-queue roots this request submitted (rebuild / /// restart / power ops). Clients poll them via /// [`HostRequest::QueueNodes`]; `None` for non-submitting requests. #[serde(default, skip_serializing_if = "Option::is_none")] pub queued_dags: Option>, /// `QueueNodes` result — the requested nodes plus their live subtrees, /// as generic `hive-jobq-wire` nodes, all roots' subtrees combined in /// one flat list. `None` for every other request kind. An id with no /// live node in the graph is silently dropped rather than erroring /// the whole batch — callers should read each root node's `state` for /// terminality, not absence. #[serde(default, skip_serializing_if = "Option::is_none")] pub nodes: Option>, /// Free-form operator-facing output lines the client prints verbatim /// (one per line). Carries results a request produced daemon-side that /// have no structured home — e.g. a freshly-minted matrix token, a /// reset password, or an invited room id from the `Matrix*` requests. /// Empty for requests that produce no such output. #[serde(default, skip_serializing_if = "Vec::is_empty")] pub messages: Vec, /// `QuotaShow` result — one row per agent with its btrfs qgroup usage. /// `None` for every other request kind. #[serde(default, skip_serializing_if = "Option::is_none")] pub quota: Option>, } impl HostResponse { #[must_use] pub fn success() -> Self { Self { ok: true, ..Self::default() } } #[must_use] pub fn error(message: impl Into) -> Self { Self { ok: false, error: Some(message.into()), ..Self::default() } } #[must_use] pub fn list(agents: Vec) -> Self { Self { ok: true, agents: Some(agents), ..Self::default() } } #[must_use] pub fn pending(approvals: Vec) -> Self { Self { ok: true, approvals: Some(approvals), ..Self::default() } } /// `Urls` result — this hive's domain + browser-facing web URLs. #[must_use] pub fn urls(urls: HiveUrls) -> Self { Self { ok: true, urls: Some(urls), ..Self::default() } } /// `AgentStatus` result — one row per managed agent. #[must_use] pub fn agent_statuses(rows: Vec) -> Self { Self { ok: true, agent_statuses: Some(rows), ..Self::default() } } /// `AgentExists` result — whether the named agent has a state dir. #[must_use] pub fn agent_exists(exists: bool) -> Self { Self { ok: true, agent_exists: Some(exists), ..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) -> Self { Self { ok: true, queued_dags: Some(ids), ..Self::default() } } /// `QueueNodes` result — the polled node + its subtree, as generic /// wire nodes. #[must_use] pub fn nodes(nodes: Vec) -> Self { Self { ok: true, nodes: Some(nodes), ..Self::default() } } /// A success carrying operator-facing output lines the client prints /// verbatim — the result shape for the `Matrix*` provisioning requests. #[must_use] pub fn messages(messages: Vec) -> Self { Self { ok: true, messages, ..Self::default() } } /// `QuotaShow` result — one usage row per agent. #[must_use] pub fn quota(rows: Vec) -> Self { Self { ok: true, quota: Some(rows), ..Self::default() } } }