From 40ed9c7f6a80728143dcbe50275d14fa3e5bc1d8 Mon Sep 17 00:00:00 2001 From: Affaan Mustafa Date: Wed, 8 Apr 2026 03:37:48 -0700 Subject: [PATCH] feat: surface ecc2 stabilized mode --- ecc2/src/session/store.rs | 34 ++++++++++++++++++++++++++++++++ ecc2/src/tui/dashboard.rs | 41 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/ecc2/src/session/store.rs b/ecc2/src/session/store.rs index e07b3796..15eef24e 100644 --- a/ecc2/src/session/store.rs +++ b/ecc2/src/session/store.rs @@ -62,6 +62,22 @@ impl DaemonActivity { _ => None, } } + + pub fn stabilized_after_recovery_at( + &self, + ) -> Option<&chrono::DateTime> { + if self.last_dispatch_deferred != 0 { + return None; + } + + match ( + self.last_dispatch_at.as_ref(), + self.last_recovery_dispatch_at.as_ref(), + ) { + (Some(dispatch_at), Some(recovery_at)) if dispatch_at > recovery_at => Some(dispatch_at), + _ => None, + } + } } impl StateStore { @@ -1085,6 +1101,7 @@ mod tests { assert!(!clear.prefers_rebalance_first()); assert!(!clear.dispatch_cooloff_active()); assert!(clear.chronic_saturation_cleared_at().is_none()); + assert!(clear.stabilized_after_recovery_at().is_none()); let unresolved = DaemonActivity { last_dispatch_at: Some(now), @@ -1101,6 +1118,7 @@ mod tests { assert!(unresolved.prefers_rebalance_first()); assert!(unresolved.dispatch_cooloff_active()); assert!(unresolved.chronic_saturation_cleared_at().is_none()); + assert!(unresolved.stabilized_after_recovery_at().is_none()); let recovered = DaemonActivity { last_recovery_dispatch_at: Some(now + chrono::Duration::seconds(1)), @@ -1113,5 +1131,21 @@ mod tests { recovered.chronic_saturation_cleared_at(), recovered.last_recovery_dispatch_at.as_ref() ); + assert!(recovered.stabilized_after_recovery_at().is_none()); + + let stabilized = DaemonActivity { + last_dispatch_at: Some(now + chrono::Duration::seconds(2)), + last_dispatch_routed: 2, + last_dispatch_deferred: 0, + last_dispatch_leads: 1, + ..recovered + }; + assert!(!stabilized.prefers_rebalance_first()); + assert!(!stabilized.dispatch_cooloff_active()); + assert!(stabilized.chronic_saturation_cleared_at().is_none()); + assert_eq!( + stabilized.stabilized_after_recovery_at(), + stabilized.last_dispatch_at.as_ref() + ); } } diff --git a/ecc2/src/tui/dashboard.rs b/ecc2/src/tui/dashboard.rs index af7943ef..31630fe5 100644 --- a/ecc2/src/tui/dashboard.rs +++ b/ecc2/src/tui/dashboard.rs @@ -1493,6 +1493,8 @@ impl Dashboard { "rebalance-cooloff (chronic saturation)" } else if self.daemon_activity.prefers_rebalance_first() { "rebalance-first (chronic saturation)" + } else if self.daemon_activity.stabilized_after_recovery_at().is_some() { + "dispatch-first (stabilized)" } else { "dispatch-first" } @@ -1505,6 +1507,13 @@ impl Dashboard { )); } + if let Some(stabilized_at) = self.daemon_activity.stabilized_after_recovery_at() { + lines.push(format!( + "Recovery stabilized @ {}", + self.short_timestamp(&stabilized_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) @ {}", @@ -2231,6 +2240,38 @@ mod tests { assert!(text.contains("Coordination mode rebalance-cooloff (chronic saturation)")); } + #[test] + fn selected_session_metrics_text_shows_stabilized_dispatch_mode_after_recovery() { + let now = Utc::now(); + let mut dashboard = test_dashboard( + vec![sample_session( + "focus-12345678", + "planner", + SessionState::Running, + Some("ecc/focus"), + 512, + 42, + )], + 0, + ); + dashboard.daemon_activity = DaemonActivity { + last_dispatch_at: Some(now + chrono::Duration::seconds(2)), + last_dispatch_routed: 2, + last_dispatch_deferred: 0, + last_dispatch_leads: 1, + last_recovery_dispatch_at: Some(now + chrono::Duration::seconds(1)), + last_recovery_dispatch_routed: 1, + last_recovery_dispatch_leads: 1, + last_rebalance_at: Some(now), + last_rebalance_rerouted: 1, + last_rebalance_leads: 1, + }; + + let text = dashboard.selected_session_metrics_text(); + assert!(text.contains("Coordination mode dispatch-first (stabilized)")); + assert!(text.contains("Recovery stabilized @")); + } + #[test] fn aggregate_cost_summary_mentions_total_cost() { let db = StateStore::open(Path::new(":memory:")).unwrap();