diff --git a/hive-c0re/src/actions.rs b/hive-c0re/src/actions.rs index 9036b2bd..877543f6 100644 --- a/hive-c0re/src/actions.rs +++ b/hive-c0re/src/actions.rs @@ -89,7 +89,7 @@ pub async fn approve(coord: Arc, id: i64) -> Result<()> { let inputs: Vec = serde_json::from_str(&approval.commit_ref).unwrap_or_default(); let result = crate::meta::lock_update(&inputs).await; - finish_approval(&coord, &approval, result, None) + finish_approval(&coord, &approval, result, None, false) } ApprovalKind::Spawn => { // Run the spawn in the background so the approve POST returns diff --git a/hive-c0re/src/crash_watch.rs b/hive-c0re/src/crash_watch.rs index a23083e3..99bd3b01 100644 --- a/hive-c0re/src/crash_watch.rs +++ b/hive-c0re/src/crash_watch.rs @@ -30,6 +30,7 @@ pub fn spawn(coord: Arc) { tokio::spawn(async move { let mut prev_running: HashSet = HashSet::new(); let mut prev_logged_in: HashSet = HashSet::new(); + let mut prev_sub_agents: HashSet = HashSet::new(); let mut seeded = false; loop { let raw = lifecycle::list().await.unwrap_or_default(); @@ -59,7 +60,13 @@ pub fn spawn(coord: Arc) { if seeded { emit_crash_transitions(&coord, &prev_running, ¤t_running); - emit_login_transitions(&coord, &prev_logged_in, ¤t_logged_in, &sub_agents); + emit_login_transitions( + &coord, + &prev_logged_in, + ¤t_logged_in, + &sub_agents, + &prev_sub_agents, + ); } // Periodic container rescan — catches state flips that // happen outside our mutation surface (operator runs @@ -69,6 +76,7 @@ pub fn spawn(coord: Arc) { coord.rescan_containers_and_emit().await; prev_running = current_running; prev_logged_in = current_logged_in; + prev_sub_agents = sub_agents.into_iter().collect(); seeded = true; tokio::select! { @@ -110,6 +118,7 @@ fn emit_login_transitions( prev: &HashSet, current: &HashSet, sub_agents: &[String], + prev_sub_agents: &HashSet, ) { for agent in current.difference(prev) { tracing::info!(%agent, "agent logged in"); @@ -117,13 +126,16 @@ fn emit_login_transitions( agent: agent.clone(), }); } - // Only count NeedsLogin transitions for agents that exist and - // are *not* logged in — the difference set above already gives - // us "was in prev, gone from current" but we also want to fire - // for agents that newly appeared as not-logged-in (post-spawn / - // post-purge). Treat sub_agents minus current as the - // currently-needs-login set; emit when an agent enters it. - let prev_needs: HashSet<&str> = sub_agents + // Detect transitions into "needs login": an agent that was previously + // logged-in goes unsigned (credentials deleted), OR a brand-new agent + // appears without a session. + // + // prev_needs uses prev_sub_agents (the agent set from the last tick) so + // that a newly-spawned agent — which does not appear in prev_sub_agents — + // is absent from prev_needs even though it's not in prev_logged_in. + // Without this, new agents land in both prev_needs and current_needs and + // the set difference is empty, silently dropping the event. + let prev_needs: HashSet<&str> = prev_sub_agents .iter() .map(String::as_str) .filter(|n| !prev.contains(*n))