Watch
0
0
Fork
You've already forked hyperhive
0

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:
atlas 2026-10-04 23:55:03 +02:00
commit 58ef299c3c
60 changed files with 153 additions and 1828 deletions

View file

@ -10,7 +10,7 @@ use std::sync::{Arc, Mutex, OnceLock};
use anyhow::{Context as _, Result};
use hive_jobq::{NodeId, TerminalState};
use hive_jobq::NodeId;
use super::model::NodeKind;
use crate::coordinator::Coordinator;
@ -79,8 +79,8 @@ pub(super) async fn run_node(
run_purge_state(agent).await;
Ok(())
}
NodeKind::DestroyBookkeeping { purge, .. } => {
run_destroy_bookkeeping(coord, agent, *purge).await;
NodeKind::DestroyBookkeeping { .. } => {
run_destroy_bookkeeping(coord, agent).await;
Ok(())
}
NodeKind::MetaLock {
@ -121,13 +121,6 @@ pub(super) async fn run_node(
// takes it directly instead of re-matching the kind behind a `bail!`
// that could never fire.
NodeKind::WritePermFile { payload, .. } => run_write_perm_file(coord, agent, payload).await,
NodeKind::ResolveApproval {
approval_id,
outcome,
} => {
run_resolve_approval(coord, coord.job_queue.root_of(id), *approval_id, *outcome);
Ok(())
}
NodeKind::EmitRebuilt { ok, .. } => {
run_emit_rebuilt(coord, agent, coord.job_queue.root_of(id), *ok).await;
Ok(())
@ -266,25 +259,6 @@ async fn run_wanted_pull(coord: &Arc<Coordinator>) -> Result<()> {
crate::workers::wanted::pull(coord).await
}
/// Resolve the DAG's approval row the way this node's own `outcome` says.
///
/// Nothing is inspected: a template emits one of these per outcome, each edged to
/// accept only that one, so *which* node the scheduler let run already is the
/// answer. Best-effort — a resolution failure is logged inside
/// [`crate::actions::resolve_approval_dag`], never surfaced as a node failure,
/// since the work already happened and failing the tail would only misreport it.
fn run_resolve_approval(
coord: &Arc<Coordinator>,
dag_id: Option<u64>,
approval_id: i64,
outcome: TerminalState,
) {
let reason = (outcome == TerminalState::Failed)
.then(|| dag_id.and_then(|dag| coord.job_queue.first_error(dag)))
.flatten();
crate::actions::resolve_approval_dag(coord, approval_id, outcome, reason.as_deref());
}
/// Rerender the meta flake from whatever containers still exist on disk.
/// Idempotent — a no-op when nothing changed. Lives here because the destroy
/// tail is its only caller; it moved with `destroy` when that became a DAG.
@ -349,23 +323,13 @@ async fn run_purge_state(agent: &str) {
/// none of this is undoable — a failed meta sync or power-store write is a
/// bookkeeping drift to log, not a reason to red a DAG whose container is
/// already gone.
async fn run_destroy_bookkeeping(coord: &Arc<Coordinator>, agent: &str, purge: bool) {
async fn run_destroy_bookkeeping(coord: &Arc<Coordinator>, agent: &str) {
// Meta flake: drop the agent's input + nixosConfiguration so a future spawn
// under the same name re-seeds cleanly, and so the meta lock doesn't
// reference a vanished applied repo.
if let Err(e) = sync_meta_after_lifecycle(coord).await {
tracing::warn!(error = ?e, %agent, "meta sync after destroy failed");
}
if let Err(e) = coord.approvals.fail_pending_for_agent(
agent,
if purge {
"agent purged"
} else {
"agent destroyed"
},
) {
tracing::warn!(%agent, error = ?e, "approvals: failing pending on destroy failed");
}
// Drop the durable power intent — a future agent of the same name seeds
// fresh from its observed state.
if let Err(e) = coord.power.remove(agent) {