wire types: add sha + tag to Approval and HelperEvent

approval grows fetched_sha (canonical hive-c0re-vouched sha,
distinct from manager-supplied commit_ref). helperevent
{approvalresolved,spawned,rebuilt} grow optional sha + tag so
the manager can git-show the exact tree it's hearing about
(against the upcoming /agents/<n>/applied.git RO mount) and
know which terminal tag landed. all serde-defaulted; existing
construction sites pass none until the tag-driven flow lands.
This commit is contained in:
müde 2026-05-15 22:47:39 +02:00
parent 497cd15137
commit 871e7bf3fa
5 changed files with 55 additions and 1 deletions

View file

@ -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(())

View file

@ -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<Approval> {
status,
resolved_at: row.get(6)?,
note: row.get(7)?,
fetched_sha: None,
})
}

View file

@ -78,6 +78,8 @@ pub async fn rebuild_agent(coord: &Arc<Coordinator>, 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<Coordinator>, name: &str, current_rev: &s
agent: name.to_owned(),
ok: false,
note: Some(format!("{e:#}")),
sha: None,
tag: None,
});
}
}

View file

@ -83,6 +83,7 @@ async fn dispatch(req: &HostRequest, coord: Arc<Coordinator>) -> HostResponse {
agent: name.clone(),
ok: true,
note: None,
sha: None,
});
}
Err(e) => {
@ -92,6 +93,7 @@ async fn dispatch(req: &HostRequest, coord: Arc<Coordinator>) -> HostResponse {
agent: name.clone(),
ok: false,
note: Some(format!("{e:#}")),
sha: None,
});
return Err(e);
}

View file

@ -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/<id>`. 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<String>,
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<String>,
/// Canonical sha hive-c0re fetched into applied at submission
/// time. `git show <sha>` against `/agents/<n>/applied.git`
/// inside the manager container yields the exact tree being
/// referenced.
#[serde(default, skip_serializing_if = "Option::is_none")]
sha: Option<String>,
/// Terminal tag name in the applied repo for this approval —
/// `deployed/<id>`, `failed/<id>`, or `denied/<id>` (and
/// `approved/<id>` for the rare bare-approval case where
/// no underlying action runs).
#[serde(default, skip_serializing_if = "Option::is_none")]
tag: Option<String>,
},
/// 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<String>,
/// 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<String>,
},
/// 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<String>,
/// Sha that ended up at `deployed/<id>` on success, or the
/// proposal sha that just got tagged `failed/<id>` 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<String>,
/// `deployed/<id>` or `failed/<id>` 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<String>,
},
/// A sub-agent's container was stopped (the systemd unit is down;
/// persistent state is unchanged).