//! The approval queue wire shape: one row (`Approval`) per pending/resolved //! operator decision, its `kind` discriminator, and the terminal-state enum. //! `ReminderStats` lives here too — small enough not to earn its own file, //! and unrelated to any other topic module. use chrono::{DateTime, Utc}; use hive_types::Ident; use serde::{Deserialize, Serialize}; /// One row in the approval queue. `commit_ref` is overloaded per /// `kind` — see `docs/approvals.md::Approval kinds (wire shapes)` /// for the encoding table and lifecycle. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Approval { pub id: i64, pub agent: Ident, #[serde(default)] pub kind: ApprovalKind, /// Kind-specific payload (git sha / inputs array / schedule /// payload / empty). See the Approval struct doc. pub commit_ref: String, /// The canonical hive-c0re-vouched sha. For `MergeConfigPr`: the /// reviewed PR head pinned at submit; if the PR head drifts off it /// before merge, hive-c0re cancels the stale approval and re-queues a /// fresh one for re-review. #[serde(default, skip_serializing_if = "Option::is_none")] pub fetched_sha: Option, pub requested_at: DateTime, pub status: ApprovalStatus, #[serde(default, skip_serializing_if = "Option::is_none")] pub resolved_at: Option>, #[serde(default, skip_serializing_if = "Option::is_none")] pub note: Option, /// Free-text description the manager attached at submission time; /// shown on the dashboard approval card. #[serde(default, skip_serializing_if = "Option::is_none")] pub description: Option, } /// What action the approval, when granted, will trigger. /// Variant-specific payload encoding + flow lives in /// `docs/approvals.md::Approval kinds (wire shapes)`. #[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum ApprovalKind { /// Create + start a new sub-agent container with the given name /// (under the default `agent.nix` template). Spawn, /// Create an agent's config repo and seed it from the default /// template (step 1 of the two-step spawn flow). Creating it is the /// whole of this step — tailoring what the template seeded is not a /// separate mechanism, it's a `MergeConfigPr` like every later /// change. InitConfig, /// Run `nix flake update [inputs...]` on the meta flake and commit /// the resulting lock changes. UpdateMetaInputs, /// Add a scheduled prompt to the broker queue. SchedulePrompt, /// Merge an operator-reviewed config PR: hive-c0re verifies the /// reviewed PR head, fast-forwards the forge config repo's `main` /// to it, marks the PR merged, then runs the deploy tail. This is the /// sole config-change flow — a manager opens a PR on its /// `agent-configs/` repo and the operator reviews + approves it. /// `commit_ref` = PR number; `fetched_sha` = the reviewed PR head /// pinned at submit. See `docs/approvals.md`. #[default] MergeConfigPr, } impl ApprovalKind { /// Wire/UI string — the same value serde's `snake_case` rename /// produces. The single source of truth for every place that needs /// the kind as a `&'static str` (sqlite storage, dashboard events), /// so adding a variant can't silently miss a hand-rolled match. #[must_use] pub fn as_str(self) -> &'static str { match self { ApprovalKind::Spawn => "spawn", ApprovalKind::InitConfig => "init_config", ApprovalKind::UpdateMetaInputs => "update_meta_inputs", ApprovalKind::SchedulePrompt => "schedule_prompt", ApprovalKind::MergeConfigPr => "merge_config_pr", } } } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum ApprovalStatus { Pending, Approved, Denied, Failed, /// Manager withdrew the request before the operator acted on it. /// Distinct from `Denied` (operator decision) and `Failed` /// (post-approval lifecycle error). See /// `docs/approvals.md::Withdrawing a pending approval`. Cancelled, } /// Reminder activity statistics for an agent over a time window. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ReminderStats { /// Total reminders scheduled in the window (`created_at` >= cutoff). pub scheduled: u64, /// Reminders that have been delivered in the window (`sent_at` IS NOT NULL). pub delivered: u64, /// Reminders still pending in the window (`sent_at` IS NULL). pub pending: u64, }