From 09f6bc3166edc5311f0e12654bec580d59b3b56b Mon Sep 17 00:00:00 2001 From: Affaan Mustafa Date: Wed, 8 Apr 2026 03:35:16 -0700 Subject: [PATCH] feat: surface ecc2 recovery events --- ecc2/src/session/store.rs | 22 ++++++++++++++++++++++ ecc2/src/tui/dashboard.rs | 15 ++++++++++++--- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/ecc2/src/session/store.rs b/ecc2/src/session/store.rs index 4249e98b..e07b3796 100644 --- a/ecc2/src/session/store.rs +++ b/ecc2/src/session/store.rs @@ -46,6 +46,22 @@ impl DaemonActivity { pub fn dispatch_cooloff_active(&self) -> bool { self.prefers_rebalance_first() && self.last_dispatch_deferred >= 2 } + + pub fn chronic_saturation_cleared_at( + &self, + ) -> Option<&chrono::DateTime> { + if self.prefers_rebalance_first() { + return None; + } + + match ( + self.last_dispatch_at.as_ref(), + self.last_recovery_dispatch_at.as_ref(), + ) { + (Some(dispatch_at), Some(recovery_at)) if recovery_at > dispatch_at => Some(recovery_at), + _ => None, + } + } } impl StateStore { @@ -1068,6 +1084,7 @@ mod tests { let clear = DaemonActivity::default(); assert!(!clear.prefers_rebalance_first()); assert!(!clear.dispatch_cooloff_active()); + assert!(clear.chronic_saturation_cleared_at().is_none()); let unresolved = DaemonActivity { last_dispatch_at: Some(now), @@ -1083,6 +1100,7 @@ mod tests { }; assert!(unresolved.prefers_rebalance_first()); assert!(unresolved.dispatch_cooloff_active()); + assert!(unresolved.chronic_saturation_cleared_at().is_none()); let recovered = DaemonActivity { last_recovery_dispatch_at: Some(now + chrono::Duration::seconds(1)), @@ -1091,5 +1109,9 @@ mod tests { }; assert!(!recovered.prefers_rebalance_first()); assert!(!recovered.dispatch_cooloff_active()); + assert_eq!( + recovered.chronic_saturation_cleared_at(), + recovered.last_recovery_dispatch_at.as_ref() + ); } } diff --git a/ecc2/src/tui/dashboard.rs b/ecc2/src/tui/dashboard.rs index 402a69e9..af7943ef 100644 --- a/ecc2/src/tui/dashboard.rs +++ b/ecc2/src/tui/dashboard.rs @@ -1498,6 +1498,13 @@ impl Dashboard { } )); + if let Some(cleared_at) = self.daemon_activity.chronic_saturation_cleared_at() { + lines.push(format!( + "Chronic saturation cleared @ {}", + self.short_timestamp(&cleared_at.to_rfc3339()) + )); + } + if let Some(last_dispatch_at) = self.daemon_activity.last_dispatch_at.as_ref() { lines.push(format!( "Last daemon dispatch {} routed / {} deferred across {} lead(s) @ {}", @@ -2131,6 +2138,7 @@ mod tests { #[test] fn selected_session_metrics_text_includes_daemon_activity() { + let now = Utc::now(); let mut dashboard = test_dashboard( vec![sample_session( "focus-12345678", @@ -2143,20 +2151,21 @@ mod tests { 0, ); dashboard.daemon_activity = DaemonActivity { - last_dispatch_at: Some(Utc::now()), + last_dispatch_at: Some(now), last_dispatch_routed: 4, last_dispatch_deferred: 2, last_dispatch_leads: 2, - last_recovery_dispatch_at: Some(Utc::now()), + last_recovery_dispatch_at: Some(now + chrono::Duration::seconds(1)), last_recovery_dispatch_routed: 1, last_recovery_dispatch_leads: 1, - last_rebalance_at: Some(Utc::now()), + last_rebalance_at: Some(now + chrono::Duration::seconds(2)), last_rebalance_rerouted: 1, last_rebalance_leads: 1, }; let text = dashboard.selected_session_metrics_text(); assert!(text.contains("Coordination mode dispatch-first")); + assert!(text.contains("Chronic saturation cleared @")); assert!(text.contains("Last daemon dispatch 4 routed / 2 deferred across 2 lead(s)")); assert!(text.contains("Last daemon recovery dispatch 1 handoff(s) across 1 lead(s)")); assert!(text.contains("Last daemon rebalance 1 handoff(s) across 1 lead(s)"));