hive-c0re: remove the approvals subsystem
Nothing submits an approval since scheduled prompts left hive-c0re, so the
queue only acted on rows written before that.
- hive-c0re: the approvals store and its migrations, approve/deny/
resolve actions, the ResolveApproval tail node (and meta_update's
approval_id), POST /api/approve and /api/deny, the approval views on
/api/state, the approval_resolved dashboard event, the orphan GC,
destroy/purge failing pending rows, and the approval rows in
get_loose_ends. DestroyBookkeeping loses the purge flag, which only
worded the approval-failure note.
- The CancelLooseEnd request goes: its only hive-c0re arm was the
approval one. cancel_loose_end handles reminder and todo in the harness.
- Wire types: hive_sh4re::approvals (ReminderStats moves to inbox),
LooseEnd::Approval, CancelLooseEndKind, HelperEvent::ApprovalResolved,
ToolGroup::Approvals, HostRequest::{Pending,Approve,Deny} and
HostResponse.approvals. HOST_SOCK_VERSION goes to 2 for the removed
field.
- hivectl loses the approvals verbs; docs/tools/hivectl-cli.md is
regenerated.
- The system prompt drops approval_resolved.
- The approvals table stays in broker.sqlite, unread, the same way the
operator_questions table was left when its store was removed.
- An `approvals` entry still in tool-groups.json is skipped with a warning
by ToolGroup::parse_list, like any unknown group.
- docs/agent-lifecycle/approvals.md is renamed to config-changes.md so the
code's doc pointers resolve; its content follows in the docs commit.
This commit is contained in:
parent
99bd912ae0
commit
58ef299c3c
60 changed files with 153 additions and 1828 deletions
|
|
@ -1,7 +1,7 @@
|
|||
# hive-sh4re
|
||||
|
||||
The **shared payload vocabulary** between `hive-c0re` and the in-container
|
||||
harness — the common types (`Message`, `Approval`, `LooseEnd`, `HelperEvent`, …)
|
||||
harness — the common types (`Message`, `LooseEnd`, `HelperEvent`, …)
|
||||
that the per-socket wire protocols are built from. The request/response
|
||||
_envelopes_ themselves now live in the per-socket crates (below); this crate
|
||||
holds the payloads they carry.
|
||||
|
|
@ -17,7 +17,7 @@ in all of `hive-sh4re`:
|
|||
- `hive-priv-sock` — the privileged-helper socket
|
||||
|
||||
Those crates re-export or reference the payload types that still live here
|
||||
(`Approval`, `Message`, `LooseEnd`, …).
|
||||
(`Message`, `LooseEnd`, …).
|
||||
|
||||
## Modules
|
||||
|
||||
|
|
|
|||
|
|
@ -1,68 +0,0 @@
|
|||
//! 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/agent-lifecycle/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,
|
||||
pub kind: ApprovalKind,
|
||||
/// Kind-specific payload (git sha / inputs array / empty). See the
|
||||
/// Approval struct doc.
|
||||
pub commit_ref: String,
|
||||
pub requested_at: DateTime<Utc>,
|
||||
pub status: ApprovalStatus,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub resolved_at: Option<DateTime<Utc>>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub note: Option<String>,
|
||||
/// 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<String>,
|
||||
}
|
||||
|
||||
/// What action the approval, when granted, will trigger.
|
||||
/// Variant-specific payload encoding + flow lives in
|
||||
/// `docs/agent-lifecycle/approvals.md::Approval kinds (wire shapes)`.
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, strum::IntoStaticStr)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
pub enum ApprovalKind {
|
||||
/// Run `nix flake update [inputs...]` on the meta flake and commit
|
||||
/// the resulting lock changes.
|
||||
UpdateMetaInputs,
|
||||
}
|
||||
|
||||
#[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/agent-lifecycle/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,
|
||||
}
|
||||
|
|
@ -117,6 +117,17 @@ pub enum ReminderTiming {
|
|||
At { unix_timestamp: i64 },
|
||||
}
|
||||
|
||||
/// 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,
|
||||
}
|
||||
|
||||
/// One row in the response to `GetLooseEnds`. Tagged enum so new
|
||||
/// thread kinds can land without breaking existing handlers.
|
||||
/// Per-flavour scoping + per-variant fields + clock-anomaly
|
||||
|
|
@ -125,15 +136,6 @@ pub enum ReminderTiming {
|
|||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(tag = "kind", rename_all = "snake_case")]
|
||||
pub enum LooseEnd {
|
||||
/// A pending approval row.
|
||||
Approval {
|
||||
id: i64,
|
||||
agent: String,
|
||||
commit_ref: String,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
description: Option<String>,
|
||||
age_seconds: u64,
|
||||
},
|
||||
/// A scheduled but un-delivered reminder row.
|
||||
Reminder {
|
||||
id: i64,
|
||||
|
|
@ -190,9 +192,8 @@ pub enum LooseEnd {
|
|||
|
||||
/// Age in seconds between two unix timestamps, clamped to 0 when `then`
|
||||
/// is in the future. This is what backs the `age_seconds` promise on
|
||||
/// every [`LooseEnd`] variant above, so it lives beside the enum rather
|
||||
/// than in either producer: hive-c0re derives approval ages here, and
|
||||
/// hive-agent's in-agent socket server derives reminder + todo ages.
|
||||
/// every [`LooseEnd`] variant above, so it lives beside the enum;
|
||||
/// hive-agent's in-agent socket server derives reminder + todo ages with it.
|
||||
///
|
||||
/// Both clamps are load-bearing and neither substitutes for the other:
|
||||
/// `saturating_sub` keeps the subtraction itself from overflowing on
|
||||
|
|
@ -205,17 +206,6 @@ pub fn saturating_age(now: i64, then: i64) -> u64 {
|
|||
u64::try_from(delta).unwrap_or(0)
|
||||
}
|
||||
|
||||
/// Kind discriminator for `CancelLooseEnd`. Per-kind store +
|
||||
/// authorisation rules live in
|
||||
/// `docs/process/conventions.md::Loose-ends wire shape`.
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum CancelLooseEndKind {
|
||||
Reminder,
|
||||
/// Withdraw a pending approval (manager surface only).
|
||||
Approval,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
//! Wire types shared between `hive-c0re` and the in-container harness.
|
||||
|
||||
pub mod approvals;
|
||||
pub mod assets;
|
||||
pub mod bash_task;
|
||||
pub mod container;
|
||||
|
|
|
|||
|
|
@ -6,8 +6,6 @@
|
|||
use hive_types::Ident;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::approvals::ApprovalStatus;
|
||||
|
||||
/// Logical name the broker uses for the manager.
|
||||
pub const MANAGER_AGENT: &str = "ruth";
|
||||
|
||||
|
|
@ -42,19 +40,10 @@ pub fn trusted_sender(s: &str) -> Ident {
|
|||
/// Out-of-band events the host-side daemon pushes to the manager's inbox.
|
||||
/// Serialised as JSON in `Message::body` (sender = `SYSTEM_SENDER`).
|
||||
/// Per-variant triggers + the optional `sha`/`tag` semantics live in
|
||||
/// `docs/agent-lifecycle/approvals.md::Helper events to the submitting agent`.
|
||||
/// `docs/agent-lifecycle/config-changes.md::Helper events`.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(tag = "event", rename_all = "snake_case")]
|
||||
pub enum HelperEvent {
|
||||
/// An approval transitioned to a terminal state.
|
||||
ApprovalResolved {
|
||||
id: i64,
|
||||
agent: String,
|
||||
commit_ref: String,
|
||||
status: ApprovalStatus,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
note: Option<String>,
|
||||
},
|
||||
/// A sub-agent's recorded flake rev is stale relative to hyperhive.
|
||||
NeedsUpdate { agent: String },
|
||||
/// Container exited without an operator-initiated stop (crash).
|
||||
|
|
|
|||
|
|
@ -157,11 +157,6 @@ pub enum ToolGroup {
|
|||
/// `meta/capabilities.json` grants still parse; `tools()` returns
|
||||
/// `&[]`.
|
||||
Lifecycle,
|
||||
/// Grants no MCP tool today — `request_update_meta_inputs`, its only
|
||||
/// member, was removed with no replacement. Still a live
|
||||
/// server-side gate: `cancel_loose_end`'s approval-cancel arm
|
||||
/// requires it (`socket_server::check_can_cancel_approval`).
|
||||
Approvals,
|
||||
/// Gates no tool today — `create_repo`, its only member, was removed
|
||||
/// with no replacement. Kept so existing `meta/capabilities.json`
|
||||
/// grants still parse; `tools()` returns `&[]`.
|
||||
|
|
@ -219,15 +214,15 @@ impl ToolGroup {
|
|||
|
||||
/// The MCP tool names (without the `mcp__hyperhive__` prefix) in this group.
|
||||
/// Returns `&[]` for `WebTools` (it enables Claude built-in tools, not MCP
|
||||
/// tools — see `builtin_tools()`) and for `Lifecycle` / `Approvals` /
|
||||
/// `Forge` (their tools were removed); see each variant's doc comment.
|
||||
/// tools — see `builtin_tools()`) and for `Lifecycle` / `Forge` (their
|
||||
/// tools were removed); see each variant's doc comment.
|
||||
#[must_use]
|
||||
pub fn tools(self) -> &'static [&'static str] {
|
||||
match self {
|
||||
Self::Messaging => &["send", "recv", "ack_until"],
|
||||
Self::Meta => &["get_agent_meta"],
|
||||
Self::Inbox => &["get_loose_ends", "cancel_loose_end", "remind"],
|
||||
// All five empty, for different reasons — see each
|
||||
// All four empty, for different reasons — see each
|
||||
// variant's own doc comment above. `Execution` grants the
|
||||
// out-of-process `bash` MCP server
|
||||
// (`mcp__bash__run`/`status`/`kill`), gated at config-render
|
||||
|
|
@ -236,15 +231,11 @@ impl ToolGroup {
|
|||
// out-of-process server has no later enforcement point, so
|
||||
// that gate is the actual security boundary. `WebTools`
|
||||
// grants Claude built-in tools, not MCP ones; see
|
||||
// `builtin_tools()`. `Lifecycle`, `Approvals` and `Forge` each
|
||||
// listed exactly one tool — `list_containers`,
|
||||
// `request_update_meta_inputs` and `create_repo` respectively
|
||||
// — and all three tools were removed outright; the variants
|
||||
// stay so existing grants parse, and `Approvals` still gates
|
||||
// `cancel_loose_end`'s approval-cancel arm server-side.
|
||||
Self::Lifecycle | Self::Approvals | Self::Forge | Self::Execution | Self::WebTools => {
|
||||
&[]
|
||||
}
|
||||
// `builtin_tools()`. `Lifecycle` and `Forge` each listed
|
||||
// exactly one tool — `list_containers` and `create_repo`
|
||||
// respectively — and both tools were removed outright; the
|
||||
// variants stay so existing grants parse.
|
||||
Self::Lifecycle | Self::Forge | Self::Execution | Self::WebTools => &[],
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -297,7 +288,6 @@ impl ToolGroup {
|
|||
Self::Meta,
|
||||
Self::Inbox,
|
||||
Self::Lifecycle,
|
||||
Self::Approvals,
|
||||
Self::Execution,
|
||||
];
|
||||
|
||||
|
|
@ -309,7 +299,6 @@ impl ToolGroup {
|
|||
Self::Meta,
|
||||
Self::Inbox,
|
||||
Self::Lifecycle,
|
||||
Self::Approvals,
|
||||
Self::Forge,
|
||||
Self::Execution,
|
||||
Self::WebTools,
|
||||
|
|
@ -325,9 +314,6 @@ impl ToolGroup {
|
|||
}
|
||||
Self::Inbox => "get_loose_ends, cancel_loose_end, remind — self-scheduling",
|
||||
Self::Lifecycle => "no tools — vestigial since list_containers was removed",
|
||||
Self::Approvals => {
|
||||
"no tools — grants cancel_loose_end's approval-cancel arm (privileged)"
|
||||
}
|
||||
Self::Forge => "no tools — vestigial since create_repo was removed",
|
||||
Self::Execution => {
|
||||
"run, status — run shell commands via mcp__bash__run / mcp__bash__status"
|
||||
|
|
|
|||
Loading…
Reference in a new issue