diff --git a/hive-c0re/src/dashboard.rs b/hive-c0re/src/dashboard.rs index d8e89d0..0ee6062 100644 --- a/hive-c0re/src/dashboard.rs +++ b/hive-c0re/src/dashboard.rs @@ -20,7 +20,6 @@ use axum::{ routing::{get, post}, }; use hive_sh4re::Approval; -use tokio::process::Command; use tokio_stream::wrappers::BroadcastStream; use tokio_stream::{Stream, StreamExt}; @@ -169,7 +168,7 @@ async fn approval_diff(agent: &str, commit_ref: &str) -> String { } async fn git_show(proposed_dir: &Path, commit_ref: &str) -> Result { - let out = Command::new("git") + let out = lifecycle::git_command() .current_dir(proposed_dir) .args(["show", &format!("{commit_ref}:agent.nix")]) .output() diff --git a/hive-c0re/src/lifecycle.rs b/hive-c0re/src/lifecycle.rs index b85d9ad..be88be6 100644 --- a/hive-c0re/src/lifecycle.rs +++ b/hive-c0re/src/lifecycle.rs @@ -198,7 +198,7 @@ pub async fn setup_applied(applied_dir: &Path, name: &str, hyperhive_flake: &str /// proposed repo, write it into the applied repo, commit. Hive-c0re alone /// advances `applied`'s `main`; the manager only sees `proposed/`. pub async fn apply_commit(applied_dir: &Path, proposed_dir: &Path, commit_ref: &str) -> Result<()> { - let out = Command::new("git") + let out = git_command() .current_dir(proposed_dir) .args(["show", &format!("{commit_ref}:agent.nix")]) .output() @@ -243,8 +243,16 @@ async fn git_commit(dir: &Path, message: &str) -> Result<()> { .await } +/// Spawn `git` honoring the `HYPERHIVE_GIT` env var (absolute path baked in +/// by the NixOS module), falling back to bare `git` (PATH lookup) otherwise. +#[must_use] +pub fn git_command() -> Command { + let exe = std::env::var("HYPERHIVE_GIT").unwrap_or_else(|_| "git".into()); + Command::new(exe) +} + async fn git(dir: &Path, args: &[&str]) -> Result<()> { - let out = Command::new("git") + let out = git_command() .current_dir(dir) .args(args) .output() @@ -263,7 +271,7 @@ async fn git(dir: &Path, args: &[&str]) -> Result<()> { /// Returns true if the command exits 0. async fn git_status(dir: &Path, args: &[&str]) -> Result { - let st = Command::new("git") + let st = git_command() .current_dir(dir) .args(args) .status() diff --git a/hive-sh4re/src/lib.rs b/hive-sh4re/src/lib.rs index 5f60963..c5848cc 100644 --- a/hive-sh4re/src/lib.rs +++ b/hive-sh4re/src/lib.rs @@ -145,6 +145,27 @@ pub enum AgentResponse { /// Logical name the broker uses for the manager. pub const MANAGER_AGENT: &str = "manager"; +/// Sender hive-c0re uses for events it pushes into the manager's inbox. +/// Manager harness recognises this and parses the body as a `HelperEvent`. +pub const SYSTEM_SENDER: &str = "system"; + +/// Out-of-band events the host-side daemon pushes to the manager's inbox. +/// Serialised as JSON in `Message::body` (sender = `SYSTEM_SENDER`). +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(tag = "event", rename_all = "snake_case")] +pub enum HelperEvent { + /// An approval was approved or denied; if approved, the rebuild has + /// already run (status = Approved on success, Failed on error). + ApprovalResolved { + id: i64, + agent: String, + commit_ref: String, + status: ApprovalStatus, + #[serde(default, skip_serializing_if = "Option::is_none")] + note: Option, + }, +} + /// Requests on the manager socket. Manager has the agent surface (send/recv) /// plus privileged lifecycle verbs. #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/nix/modules/hive-c0re.nix b/nix/modules/hive-c0re.nix index 9852d4a..6b95ffd 100644 --- a/nix/modules/hive-c0re.nix +++ b/nix/modules/hive-c0re.nix @@ -61,6 +61,7 @@ in pkgs.git "/run/current-system/sw" ]; + environment.HYPERHIVE_GIT = "${pkgs.git}/bin/git"; serviceConfig = { ExecStart = "${cfg.package}/bin/hive-c0re --socket /run/hyperhive/host.sock serve --hyperhive-flake ${cfg.hyperhiveFlake} --dashboard-port ${toString cfg.dashboardPort}"; Restart = "on-failure";