Phase 7c: ApprovalResolved helper events into manager's inbox

This commit is contained in:
müde 2026-05-15 00:26:42 +02:00
parent 7c1ed07cf2
commit 1ceabae892
3 changed files with 76 additions and 12 deletions

View file

@ -9,7 +9,7 @@ use std::time::Duration;
use anyhow::{Result, bail}; use anyhow::{Result, bail};
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use hive_ag3nt::{DEFAULT_SOCKET, DEFAULT_WEB_PORT, client, web_ui}; use hive_ag3nt::{DEFAULT_SOCKET, DEFAULT_WEB_PORT, client, web_ui};
use hive_sh4re::{ManagerRequest, ManagerResponse}; use hive_sh4re::{HelperEvent, ManagerRequest, ManagerResponse, SYSTEM_SENDER};
#[derive(Parser)] #[derive(Parser)]
#[command(name = "hive-m1nd", about = "hyperhive manager harness")] #[command(name = "hive-m1nd", about = "hyperhive manager harness")]
@ -94,7 +94,14 @@ async fn serve(socket: &Path, interval: Duration) -> Result<()> {
let recv: Result<ManagerResponse> = client::request(socket, &ManagerRequest::Recv).await; let recv: Result<ManagerResponse> = client::request(socket, &ManagerRequest::Recv).await;
match recv { match recv {
Ok(ManagerResponse::Message { from, body }) => { Ok(ManagerResponse::Message { from, body }) => {
tracing::info!(%from, %body, "manager inbox"); if from == SYSTEM_SENDER {
match serde_json::from_str::<HelperEvent>(&body) {
Ok(event) => tracing::info!(?event, "helper event"),
Err(_) => tracing::info!(%from, %body, "system message"),
}
} else {
tracing::info!(%from, %body, "manager inbox");
}
} }
Ok(ManagerResponse::Empty) => {} Ok(ManagerResponse::Empty) => {}
Ok(ManagerResponse::Ok) => { Ok(ManagerResponse::Ok) => {

View file

@ -4,6 +4,9 @@
//! shape they want (HTTP redirect vs JSON). //! shape they want (HTTP redirect vs JSON).
use anyhow::Result; use anyhow::Result;
use hive_sh4re::{
ApprovalStatus, HelperEvent, MANAGER_AGENT, Message, SYSTEM_SENDER,
};
use crate::coordinator::Coordinator; use crate::coordinator::Coordinator;
use crate::lifecycle; use crate::lifecycle;
@ -11,7 +14,8 @@ use crate::lifecycle;
/// Approve a pending request: read the agent.nix at the approval's commit from /// Approve a pending request: read the agent.nix at the approval's commit from
/// the proposed repo, copy into the applied repo, commit there, and rebuild /// the proposed repo, copy into the applied repo, commit there, and rebuild
/// the agent container. On failure marks the approval failed (with the error /// the agent container. On failure marks the approval failed (with the error
/// note) and returns the error. /// note) and returns the error. Either way, an `ApprovalResolved` helper event
/// is pushed into the manager's inbox.
pub async fn approve(coord: &Coordinator, id: i64) -> Result<()> { pub async fn approve(coord: &Coordinator, id: i64) -> Result<()> {
let approval = coord.approvals.mark_approved(id)?; let approval = coord.approvals.mark_approved(id)?;
tracing::info!(%approval.id, %approval.agent, %approval.commit_ref, "approval: applying + rebuilding"); tracing::info!(%approval.id, %approval.agent, %approval.commit_ref, "approval: applying + rebuilding");
@ -30,16 +34,70 @@ pub async fn approve(coord: &Coordinator, id: i64) -> Result<()> {
.await .await
} }
.await; .await;
if let Err(e) = result { match result {
let note = format!("{e:#}"); Ok(()) => {
let _ = coord.approvals.mark_failed(approval.id, &note); notify_manager(
return Err(e); coord,
&HelperEvent::ApprovalResolved {
id: approval.id,
agent: approval.agent.clone(),
commit_ref: approval.commit_ref.clone(),
status: ApprovalStatus::Approved,
note: None,
},
);
Ok(())
}
Err(e) => {
let note = format!("{e:#}");
let _ = coord.approvals.mark_failed(approval.id, &note);
notify_manager(
coord,
&HelperEvent::ApprovalResolved {
id: approval.id,
agent: approval.agent.clone(),
commit_ref: approval.commit_ref.clone(),
status: ApprovalStatus::Failed,
note: Some(note),
},
);
Err(e)
}
}
}
pub fn deny(coord: &Coordinator, id: i64) -> Result<()> {
let approval = coord.approvals.get(id)?;
coord.approvals.mark_denied(id)?;
tracing::info!(%id, "approval denied");
if let Some(a) = approval {
notify_manager(
coord,
&HelperEvent::ApprovalResolved {
id: a.id,
agent: a.agent,
commit_ref: a.commit_ref,
status: ApprovalStatus::Denied,
note: None,
},
);
} }
Ok(()) Ok(())
} }
pub fn deny(coord: &Coordinator, id: i64) -> Result<()> { fn notify_manager(coord: &Coordinator, event: &HelperEvent) {
coord.approvals.mark_denied(id)?; let body = match serde_json::to_string(event) {
tracing::info!(%id, "approval denied"); Ok(s) => s,
Ok(()) Err(e) => {
tracing::warn!(error = ?e, "failed to encode helper event");
return;
}
};
if let Err(e) = coord.broker.send(&Message {
from: SYSTEM_SENDER.to_owned(),
to: MANAGER_AGENT.to_owned(),
body,
}) {
tracing::warn!(error = ?e, "failed to push helper event to manager");
}
} }

View file

@ -66,7 +66,6 @@ impl Approvals {
.map_err(Into::into) .map_err(Into::into)
} }
#[allow(dead_code)] // used by Phase 5b commit verification
pub fn get(&self, id: i64) -> Result<Option<Approval>> { pub fn get(&self, id: i64) -> Result<Option<Approval>> {
let conn = self.conn.lock().unwrap(); let conn = self.conn.lock().unwrap();
conn.query_row( conn.query_row(