auto_update: rebuild all on startup, needs_update = applied HEAD vs deployed sha

This commit is contained in:
damocles 2026-05-21 19:40:41 +02:00
parent 4814aaefdb
commit 433bc85b91
4 changed files with 44 additions and 139 deletions

View file

@ -1,5 +1,5 @@
//! Per-container state watcher. Polls every managed container on a
//! fixed interval, tracks three orthogonal state-sets across ticks,
//! fixed interval, tracks two orthogonal state-sets across ticks,
//! and emits a `HelperEvent` to the manager on each transition:
//!
//! - **running**: container is up. running → stopped without an
@ -8,9 +8,9 @@
//! - **logged-in**: claude session dir is populated. ! → ✓ →
//! `LoggedIn`; ✓ → ! → `NeedsLogin` (rare — usually only fires
//! on a fresh spawn / purge).
//! - **up-to-date**: agent's recorded flake rev matches current. ✓
//! → ! → `NeedsUpdate`. The reverse direction (`NeedsUpdate`
//! resolved) is covered by `Rebuilt`, so no separate event.
//!
//! `NeedsUpdate` events are now fired from the apply-commit path
//! directly rather than via rev-marker polling (issue #179 cleanup).
//!
//! D-Bus subscription would be lower-latency for the first axis,
//! but polling is simpler and a 10s detection delay is fine.
@ -30,14 +30,11 @@ pub fn spawn(coord: Arc<Coordinator>) {
tokio::spawn(async move {
let mut prev_running: HashSet<String> = HashSet::new();
let mut prev_logged_in: HashSet<String> = HashSet::new();
let mut prev_updated: HashSet<String> = HashSet::new();
let mut seeded = false;
loop {
let raw = lifecycle::list().await.unwrap_or_default();
let current_rev = crate::auto_update::current_flake_rev(&coord.hyperhive_flake);
let mut current_running = HashSet::new();
let mut current_logged_in = HashSet::new();
let mut current_updated = HashSet::new();
let mut sub_agents: Vec<String> = Vec::new();
for c in &raw {
let logical = if c == MANAGER_NAME {
@ -58,17 +55,11 @@ pub fn spawn(coord: Arc<Coordinator>) {
{
current_logged_in.insert(logical.clone());
}
if let Some(rev) = current_rev.as_deref()
&& !crate::auto_update::agent_needs_update(&logical, rev)
{
current_updated.insert(logical.clone());
}
}
if seeded {
emit_crash_transitions(&coord, &prev_running, &current_running);
emit_login_transitions(&coord, &prev_logged_in, &current_logged_in, &sub_agents);
emit_update_transitions(&coord, &prev_updated, &current_updated, &sub_agents);
}
// Periodic container rescan — catches state flips that
// happen outside our mutation surface (operator runs
@ -78,7 +69,6 @@ pub fn spawn(coord: Arc<Coordinator>) {
coord.rescan_containers_and_emit().await;
prev_running = current_running;
prev_logged_in = current_logged_in;
prev_updated = current_updated;
seeded = true;
tokio::select! {
@ -151,28 +141,3 @@ fn emit_login_transitions(
}
}
fn emit_update_transitions(
coord: &Coordinator,
prev_updated: &HashSet<String>,
current_updated: &HashSet<String>,
sub_agents: &[String],
) {
// Fired on the "was up-to-date, now isn't" transition. The
// reverse (rebuilt) is already covered by HelperEvent::Rebuilt.
let prev_stale: HashSet<&str> = sub_agents
.iter()
.map(String::as_str)
.filter(|n| !prev_updated.contains(*n))
.collect();
let current_stale: HashSet<&str> = sub_agents
.iter()
.map(String::as_str)
.filter(|n| !current_updated.contains(*n))
.collect();
for agent in current_stale.difference(&prev_stale) {
tracing::info!(%agent, "agent needs update");
coord.notify_manager(&hive_sh4re::HelperEvent::NeedsUpdate {
agent: (*agent).to_owned(),
});
}
}