diff --git a/hive-c0re/src/actions.rs b/hive-c0re/src/actions.rs index 00318fd..bd90f0e 100644 --- a/hive-c0re/src/actions.rs +++ b/hive-c0re/src/actions.rs @@ -105,6 +105,8 @@ fn finish_approval( commit_ref: approval.commit_ref.clone(), status, note: note.clone(), + sha: approval.fetched_sha.clone(), + tag: None, }); // For spawn/rebuild approvals, also surface the underlying action so // the manager knows whether the container actually came up. The @@ -116,11 +118,14 @@ fn finish_approval( agent: approval.agent.clone(), ok, note, + sha: approval.fetched_sha.clone(), }), ApprovalKind::ApplyCommit => coord.notify_manager(&HelperEvent::Rebuilt { agent: approval.agent.clone(), ok, note, + sha: approval.fetched_sha.clone(), + tag: None, }), } result @@ -183,12 +188,15 @@ pub fn deny(coord: &Coordinator, id: i64, note: Option<&str>) -> Result<()> { coord.approvals.mark_denied(id, note)?; tracing::info!(%id, note, "approval denied"); if let Some(a) = approval { + let sha = a.fetched_sha.clone(); coord.notify_manager(&HelperEvent::ApprovalResolved { id: a.id, agent: a.agent, commit_ref: a.commit_ref, status: ApprovalStatus::Denied, note: note.map(String::from), + sha, + tag: None, }); } Ok(()) diff --git a/hive-c0re/src/approvals.rs b/hive-c0re/src/approvals.rs index fbc06ab..b770178 100644 --- a/hive-c0re/src/approvals.rs +++ b/hive-c0re/src/approvals.rs @@ -139,6 +139,7 @@ impl Approvals { status: ApprovalStatus::Approved, resolved_at: Some(resolved_at), note: None, + fetched_sha: None, }) } @@ -214,6 +215,7 @@ fn row_to_approval(row: &rusqlite::Row<'_>) -> rusqlite::Result { status, resolved_at: row.get(6)?, note: row.get(7)?, + fetched_sha: None, }) } diff --git a/hive-c0re/src/auto_update.rs b/hive-c0re/src/auto_update.rs index 16a562a..bce2871 100644 --- a/hive-c0re/src/auto_update.rs +++ b/hive-c0re/src/auto_update.rs @@ -78,6 +78,8 @@ pub async fn rebuild_agent(coord: &Arc, name: &str, current_rev: &s agent: name.to_owned(), ok: true, note: None, + sha: None, + tag: None, }); } Err(e) => { @@ -85,6 +87,8 @@ pub async fn rebuild_agent(coord: &Arc, name: &str, current_rev: &s agent: name.to_owned(), ok: false, note: Some(format!("{e:#}")), + sha: None, + tag: None, }); } } diff --git a/hive-c0re/src/server.rs b/hive-c0re/src/server.rs index 80f3551..40e76a1 100644 --- a/hive-c0re/src/server.rs +++ b/hive-c0re/src/server.rs @@ -83,6 +83,7 @@ async fn dispatch(req: &HostRequest, coord: Arc) -> HostResponse { agent: name.clone(), ok: true, note: None, + sha: None, }); } Err(e) => { @@ -92,6 +93,7 @@ async fn dispatch(req: &HostRequest, coord: Arc) -> HostResponse { agent: name.clone(), ok: false, note: Some(format!("{e:#}")), + sha: None, }); return Err(e); } diff --git a/hive-sh4re/src/lib.rs b/hive-sh4re/src/lib.rs index 7a96322..63b1493 100644 --- a/hive-sh4re/src/lib.rs +++ b/hive-sh4re/src/lib.rs @@ -65,8 +65,18 @@ pub struct Approval { pub agent: String, #[serde(default)] pub kind: ApprovalKind, - /// For `ApplyCommit`: the git sha to apply. For `Spawn`: empty. + /// For `ApplyCommit`: the git sha the manager submitted. For `Spawn`: + /// empty. Note that this is the manager's *claimed* ref — the + /// canonical, hive-c0re-vouched sha after the proposal fetch lives + /// in `fetched_sha`. pub commit_ref: String, + /// The sha hive-c0re fetched from the proposed repo into applied at + /// submission time, then tagged `proposal/`. Stable for the + /// lifetime of the approval — manager amends in proposed don't + /// change what gets built. Only set for `ApplyCommit` after the + /// successful fetch. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub fetched_sha: Option, pub requested_at: i64, pub status: ApprovalStatus, #[serde(default, skip_serializing_if = "Option::is_none")] @@ -236,6 +246,18 @@ pub enum HelperEvent { status: ApprovalStatus, #[serde(default, skip_serializing_if = "Option::is_none")] note: Option, + /// Canonical sha hive-c0re fetched into applied at submission + /// time. `git show ` against `/agents//applied.git` + /// inside the manager container yields the exact tree being + /// referenced. + #[serde(default, skip_serializing_if = "Option::is_none")] + sha: Option, + /// Terminal tag name in the applied repo for this approval — + /// `deployed/`, `failed/`, or `denied/` (and + /// `approved/` for the rare bare-approval case where + /// no underlying action runs). + #[serde(default, skip_serializing_if = "Option::is_none")] + tag: Option, }, /// A new container was spawned (post-approval or via the admin CLI /// bypass path). `ok=false` means the spawn failed. @@ -244,6 +266,10 @@ pub enum HelperEvent { ok: bool, #[serde(default, skip_serializing_if = "Option::is_none")] note: Option, + /// Sha of the `deployed/0` commit seeded by hive-c0re on + /// first spawn (Some on success, None on failure). + #[serde(default, skip_serializing_if = "Option::is_none")] + sha: Option, }, /// A container was rebuilt (auto-update on flake rev change, or a /// manual rebuild from CLI/dashboard). @@ -252,6 +278,18 @@ pub enum HelperEvent { ok: bool, #[serde(default, skip_serializing_if = "Option::is_none")] note: Option, + /// Sha that ended up at `deployed/` on success, or the + /// proposal sha that just got tagged `failed/` on + /// failure. None for the (rare) rebuild path that doesn't go + /// through an approval (e.g. auto_update::rebuild_agent + /// reapplying the existing main). + #[serde(default, skip_serializing_if = "Option::is_none")] + sha: Option, + /// `deployed/` or `failed/` for approval-driven + /// rebuilds; None for auto-update / dashboard rebuilds that + /// don't change the deployed commit. + #[serde(default, skip_serializing_if = "Option::is_none")] + tag: Option, }, /// A sub-agent's container was stopped (the systemd unit is down; /// persistent state is unchanged).