use crate::calibration::{
CalibrationResult, CalibrationSample,
feature_flags::{CalibV22FeatureFlag, RolloutStage, StageTransition, StageMetrics},
sla_tripwires::{SlaTripwires, PerformanceMetrics, AutoRevertAction, RevertEvent},
drift_monitor::{DriftMonitor, WeeklyHealthArtifacts, HealthStatus, AlertEvent, AlertType},
shared_binning_core::SharedBinningCore,
};
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, VecDeque};
use std::sync::{Arc, RwLock, atomic::{AtomicU64, AtomicBool, Ordering}};
use std::time::{Duration, Instant, SystemTime};
use chrono::{DateTime, Utc};
use anyhow::{Result, Context as AnyhowContext};
use tracing::{info, warn, error, debug};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CanaryControllerConfig {
pub auto_promotion_enabled: bool,
pub auto_rollback_enabled: bool,
pub sla_validation: SlaValidationConfig,
pub progression_rules: ProgressionRules,
pub monitoring_config: CanaryMonitoringConfig,
pub config_fingerprint: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SlaValidationConfig {
pub p99_latency_sla_us: f64,
pub aece_tau_threshold: f64,
pub max_confidence_shift: f64,
pub require_zero_sla_recall_change: bool,
pub evaluation_window_minutes: u32,
pub breach_detection: BreachDetectionConfig,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BreachDetectionConfig {
pub consecutive_breach_threshold: u32,
pub window_duration_minutes: u32,
pub grace_period_minutes: u32,
pub max_breach_rate: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProgressionRules {
pub min_observation_hours: HashMap<String, u32>,
pub success_rate_thresholds: HashMap<String, f64>,
pub required_health_status: HealthStatus,
pub min_sample_counts: HashMap<String, u64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CanaryMonitoringConfig {
pub real_time_metrics: bool,
pub collection_interval_sec: u32,
pub alert_thresholds: AlertThresholds,
pub baseline_tracking: BaselineTrackingConfig,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AlertThresholds {
pub latency_degradation_threshold: f64,
pub error_rate_threshold: f64,
pub aece_degradation_threshold: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BaselineTrackingConfig {
pub enabled: bool,
pub baseline_lookback_days: u32,
pub require_green_baseline: bool,
pub significance_level: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EvaluationWindow {
pub start_time: DateTime<Utc>,
pub end_time: DateTime<Utc>,
pub metrics: WindowMetrics,
pub sla_results: SlaGateResults,
pub passed: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WindowMetrics {
pub p99_latency_us: f64,
pub mean_latency_us: f64,
pub aece_value: f64,
pub confidence_shift: f64,
pub sla_recall_50_change: f64,
pub success_rate: f64,
pub sample_count: u64,
pub health_status: HealthStatus,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SlaGateResults {
pub p99_latency_gate: GateResult,
pub aece_tau_gate: GateResult,
pub confidence_shift_gate: GateResult,
pub sla_recall_gate: GateResult,
pub overall_passed: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GateResult {
pub passed: bool,
pub measured_value: f64,
pub threshold_value: f64,
pub gate_name: String,
pub violation_message: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CanaryDecision {
pub timestamp: DateTime<Utc>,
pub decision_type: CanaryDecisionType,
pub current_stage: String,
pub target_stage: Option<String>,
pub reason: String,
pub metrics: StageMetrics,
pub config_fingerprint: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum CanaryDecisionType {
Continue,
Promote,
Rollback,
EmergencyStop,
ManualIntervention,
}
pub struct CanaryController {
config: Arc<RwLock<CanaryControllerConfig>>,
feature_flag: Arc<CalibV22FeatureFlag>,
sla_tripwires: Arc<RwLock<SlaTripwires>>,
drift_monitor: Arc<RwLock<DriftMonitor>>,
current_window: Arc<RwLock<Option<EvaluationWindow>>>,
window_history: Arc<RwLock<VecDeque<EvaluationWindow>>>,
consecutive_breaches: Arc<AtomicU64>,
last_promotion_time: Arc<RwLock<Option<Instant>>>,
metrics_buffer: Arc<RwLock<VecDeque<PerformanceMetrics>>>,
baseline_metrics: Arc<RwLock<Option<WindowMetrics>>>,
decision_history: Arc<RwLock<Vec<CanaryDecision>>>,
monitoring_active: Arc<AtomicBool>,
collection_thread_handle: Arc<RwLock<Option<std::thread::JoinHandle<()>>>>,
}
impl CanaryController {
pub fn new(
config: CanaryControllerConfig,
feature_flag: Arc<CalibV22FeatureFlag>,
sla_tripwires: Arc<RwLock<SlaTripwires>>,
drift_monitor: Arc<RwLock<DriftMonitor>>,
) -> Result<Self> {
info!("🚀 Initializing Canary Deployment Controller");
info!("Config fingerprint: {}", config.config_fingerprint);
info!("Auto-promotion: {}, Auto-rollback: {}",
config.auto_promotion_enabled, config.auto_rollback_enabled);
let controller = Self {
config: Arc::new(RwLock::new(config)),
feature_flag,
sla_tripwires,
drift_monitor,
current_window: Arc::new(RwLock::new(None)),
window_history: Arc::new(RwLock::new(VecDeque::with_capacity(100))),
consecutive_breaches: Arc::new(AtomicU64::new(0)),
last_promotion_time: Arc::new(RwLock::new(None)),
metrics_buffer: Arc::new(RwLock::new(VecDeque::with_capacity(1000))),
baseline_metrics: Arc::new(RwLock::new(None)),
decision_history: Arc::new(RwLock::new(Vec::new())),
monitoring_active: Arc::new(AtomicBool::new(false)),
collection_thread_handle: Arc::new(RwLock::new(None)),
};
Ok(controller)
}
pub fn start_monitoring(&self) -> Result<()> {
if self.monitoring_active.load(Ordering::Relaxed) {
return Ok(()); }
self.monitoring_active.store(true, Ordering::Relaxed);
let config = self.config.clone();
let metrics_buffer = self.metrics_buffer.clone();
let sla_tripwires = self.sla_tripwires.clone();
let monitoring_active = self.monitoring_active.clone();
let handle = std::thread::spawn(move || {
let config = config.read().unwrap();
let interval = Duration::from_secs(config.monitoring_config.collection_interval_sec as u64);
drop(config);
while monitoring_active.load(Ordering::Relaxed) {
if let Ok(tripwires) = sla_tripwires.read() {
let metrics = PerformanceMetrics {
timestamp: Utc::now(),
latency_p99_us: 850.0, latency_mean_us: 420.0,
hot_path_allocations: 0,
throughput_ops_per_sec: 1200.0,
memory_usage_bytes: 1024 * 1024 * 64, cpu_usage_percent: 45.0,
};
if let Ok(mut buffer) = metrics_buffer.write() {
buffer.push_back(metrics);
if buffer.len() > 1000 {
buffer.pop_front();
}
}
}
std::thread::sleep(interval);
}
});
{
let mut handle_guard = self.collection_thread_handle.write().unwrap();
*handle_guard = Some(handle);
}
info!("📊 Canary monitoring started");
Ok(())
}
pub fn stop_monitoring(&self) -> Result<()> {
self.monitoring_active.store(false, Ordering::Relaxed);
if let Ok(mut handle_guard) = self.collection_thread_handle.write() {
if let Some(handle) = handle_guard.take() {
let _ = handle.join();
}
}
info!("📊 Canary monitoring stopped");
Ok(())
}
pub fn evaluate_stage(&self) -> Result<CanaryDecision> {
let config = self.config.read().unwrap();
let current_metrics = self.compute_current_metrics()?;
let current_stage = self.get_current_stage()?;
let evaluation_window = self.create_evaluation_window(¤t_metrics)?;
let sla_results = self.validate_sla_gates(&evaluation_window, &config.sla_validation)?;
let breach_detected = !sla_results.overall_passed;
if breach_detected {
self.consecutive_breaches.fetch_add(1, Ordering::Relaxed);
} else {
self.consecutive_breaches.store(0, Ordering::Relaxed);
}
let decision = self.make_canary_decision(
¤t_stage,
¤t_metrics,
&sla_results,
&config
)?;
if config.auto_promotion_enabled || config.auto_rollback_enabled {
self.execute_decision(&decision)?;
}
self.record_decision(&decision)?;
self.update_evaluation_window(evaluation_window)?;
Ok(decision)
}
fn validate_sla_gates(
&self,
window: &EvaluationWindow,
sla_config: &SlaValidationConfig
) -> Result<SlaGateResults> {
let metrics = &window.metrics;
let p99_gate = GateResult {
passed: metrics.p99_latency_us <= sla_config.p99_latency_sla_us,
measured_value: metrics.p99_latency_us,
threshold_value: sla_config.p99_latency_sla_us,
gate_name: "P99_LATENCY".to_string(),
violation_message: if metrics.p99_latency_us > sla_config.p99_latency_sla_us {
Some(format!("P99 latency {:.0}μs exceeds SLA {:.0}μs",
metrics.p99_latency_us, sla_config.p99_latency_sla_us))
} else {
None
},
};
let aece_gate = GateResult {
passed: metrics.aece_value <= sla_config.aece_tau_threshold,
measured_value: metrics.aece_value,
threshold_value: sla_config.aece_tau_threshold,
gate_name: "AECE_TAU".to_string(),
violation_message: if metrics.aece_value > sla_config.aece_tau_threshold {
Some(format!("AECE-τ {:.4} exceeds threshold {:.4}",
metrics.aece_value, sla_config.aece_tau_threshold))
} else {
None
},
};
let confidence_gate = GateResult {
passed: metrics.confidence_shift <= sla_config.max_confidence_shift,
measured_value: metrics.confidence_shift,
threshold_value: sla_config.max_confidence_shift,
gate_name: "CONFIDENCE_SHIFT".to_string(),
violation_message: if metrics.confidence_shift > sla_config.max_confidence_shift {
Some(format!("Confidence shift {:.4} exceeds threshold {:.4}",
metrics.confidence_shift, sla_config.max_confidence_shift))
} else {
None
},
};
let sla_recall_gate = GateResult {
passed: if sla_config.require_zero_sla_recall_change {
metrics.sla_recall_50_change.abs() < 0.001 } else {
true
},
measured_value: metrics.sla_recall_50_change,
threshold_value: 0.0,
gate_name: "SLA_RECALL_50".to_string(),
violation_message: if sla_config.require_zero_sla_recall_change &&
metrics.sla_recall_50_change.abs() >= 0.001 {
Some(format!("SLA-Recall@50 change {:.4} violates zero-change requirement",
metrics.sla_recall_50_change))
} else {
None
},
};
let overall_passed = p99_gate.passed && aece_gate.passed &&
confidence_gate.passed && sla_recall_gate.passed;
Ok(SlaGateResults {
p99_latency_gate: p99_gate,
aece_tau_gate: aece_gate,
confidence_shift_gate: confidence_gate,
sla_recall_gate: sla_recall_gate,
overall_passed,
})
}
fn make_canary_decision(
&self,
current_stage: &str,
metrics: &StageMetrics,
sla_results: &SlaGateResults,
config: &CanaryControllerConfig,
) -> Result<CanaryDecision> {
let now = Utc::now();
let consecutive_breaches = self.consecutive_breaches.load(Ordering::Relaxed);
if consecutive_breaches >= config.sla_validation.breach_detection.consecutive_breach_threshold as u64 {
return Ok(CanaryDecision {
timestamp: now,
decision_type: CanaryDecisionType::EmergencyStop,
current_stage: current_stage.to_string(),
target_stage: Some("Disabled".to_string()),
reason: format!("Emergency rollback: {} consecutive SLA breaches", consecutive_breaches),
metrics: metrics.clone(),
config_fingerprint: config.config_fingerprint.clone(),
});
}
if !sla_results.overall_passed && config.auto_rollback_enabled {
let violations: Vec<String> = vec![
&sla_results.p99_latency_gate,
&sla_results.aece_tau_gate,
&sla_results.confidence_shift_gate,
&sla_results.sla_recall_gate,
]
.iter()
.filter(|gate| !gate.passed)
.filter_map(|gate| gate.violation_message.as_ref())
.cloned()
.collect();
return Ok(CanaryDecision {
timestamp: now,
decision_type: CanaryDecisionType::Rollback,
current_stage: current_stage.to_string(),
target_stage: self.get_previous_stage(current_stage),
reason: format!("SLA violations: {}", violations.join("; ")),
metrics: metrics.clone(),
config_fingerprint: config.config_fingerprint.clone(),
});
}
if self.should_promote_stage(current_stage, metrics, config)? {
let next_stage = self.get_next_stage(current_stage);
if let Some(target) = next_stage {
return Ok(CanaryDecision {
timestamp: now,
decision_type: CanaryDecisionType::Promote,
current_stage: current_stage.to_string(),
target_stage: Some(target),
reason: "Promotion criteria met - all SLA gates passed".to_string(),
metrics: metrics.clone(),
config_fingerprint: config.config_fingerprint.clone(),
});
}
}
Ok(CanaryDecision {
timestamp: now,
decision_type: CanaryDecisionType::Continue,
current_stage: current_stage.to_string(),
target_stage: None,
reason: "Monitoring ongoing - no action required".to_string(),
metrics: metrics.clone(),
config_fingerprint: config.config_fingerprint.clone(),
})
}
fn execute_decision(&self, decision: &CanaryDecision) -> Result<()> {
match &decision.decision_type {
CanaryDecisionType::Promote => {
if let Some(target_stage) = &decision.target_stage {
self.promote_to_stage(target_stage, &decision.reason)?;
}
}
CanaryDecisionType::Rollback => {
if let Some(target_stage) = &decision.target_stage {
self.rollback_to_stage(target_stage, &decision.reason)?;
}
}
CanaryDecisionType::EmergencyStop => {
self.emergency_stop(&decision.reason)?;
}
CanaryDecisionType::Continue => {
debug!("Continuing current stage: {}", decision.reason);
}
CanaryDecisionType::ManualIntervention => {
warn!("Manual intervention required: {}", decision.reason);
}
}
Ok(())
}
fn promote_to_stage(&self, target_stage: &str, reason: &str) -> Result<()> {
info!("🎯 CANARY PROMOTION: Advancing to {}", target_stage);
info!("Reason: {}", reason);
{
let mut last_promotion = self.last_promotion_time.write().unwrap();
*last_promotion = Some(Instant::now());
}
self.consecutive_breaches.store(0, Ordering::Relaxed);
Ok(())
}
fn rollback_to_stage(&self, target_stage: &str, reason: &str) -> Result<()> {
warn!("⚠️ CANARY ROLLBACK: Rolling back to {}", target_stage);
warn!("Reason: {}", reason);
Ok(())
}
fn emergency_stop(&self, reason: &str) -> Result<()> {
error!("🚨 CANARY EMERGENCY STOP: {}", reason);
{
let tripwires = self.sla_tripwires.read().unwrap();
tripwires.emergency_shutdown(reason)?;
}
Ok(())
}
fn should_promote_stage(
&self,
current_stage: &str,
metrics: &StageMetrics,
config: &CanaryControllerConfig,
) -> Result<bool> {
let min_hours = config.progression_rules.min_observation_hours
.get(current_stage)
.unwrap_or(&4);
if metrics.observation_hours < *min_hours as f64 {
return Ok(false);
}
let min_success_rate = config.progression_rules.success_rate_thresholds
.get(current_stage)
.unwrap_or(&0.99);
if metrics.success_rate < *min_success_rate {
return Ok(false);
}
if metrics.health_status != config.progression_rules.required_health_status {
return Ok(false);
}
let min_samples = config.progression_rules.min_sample_counts
.get(current_stage)
.unwrap_or(&1000);
if metrics.sample_count < *min_samples {
return Ok(false);
}
Ok(true)
}
fn get_current_stage(&self) -> Result<String> {
let status = self.feature_flag.get_status()?;
Ok(status["current_stage"].as_str().unwrap_or("Unknown").to_string())
}
fn get_next_stage(&self, current_stage: &str) -> Option<String> {
match current_stage {
"Canary" => Some("Limited".to_string()),
"Limited" => Some("Major".to_string()),
"Major" => Some("Full".to_string()),
"Full" => None, _ => Some("Canary".to_string()), }
}
fn get_previous_stage(&self, current_stage: &str) -> Option<String> {
match current_stage {
"Full" => Some("Major".to_string()),
"Major" => Some("Limited".to_string()),
"Limited" => Some("Canary".to_string()),
"Canary" => Some("Disabled".to_string()),
_ => Some("Disabled".to_string()),
}
}
fn compute_current_metrics(&self) -> Result<StageMetrics> {
let metrics_buffer = self.metrics_buffer.read().unwrap();
if metrics_buffer.is_empty() {
return Ok(StageMetrics {
aece_value: 0.015,
p99_latency_us: 950.0,
success_rate: 0.999,
confidence_shift: 0.01,
health_status: HealthStatus::Green,
observation_hours: 0.0,
sample_count: 0,
});
}
let recent_metrics: Vec<&PerformanceMetrics> = metrics_buffer
.iter()
.rev()
.take(100) .collect();
let p99_latency = recent_metrics
.iter()
.map(|m| m.latency_p99_us)
.fold(0.0f64, |acc, x| acc.max(x));
let mean_latency = recent_metrics
.iter()
.map(|m| m.latency_mean_us)
.sum::<f64>() / recent_metrics.len() as f64;
Ok(StageMetrics {
aece_value: 0.012, p99_latency_us: p99_latency,
success_rate: 0.998, confidence_shift: 0.015, health_status: HealthStatus::Green, observation_hours: 2.5, sample_count: recent_metrics.len() as u64,
})
}
fn create_evaluation_window(&self, metrics: &StageMetrics) -> Result<EvaluationWindow> {
let now = Utc::now();
let config = self.config.read().unwrap();
let window_duration = Duration::from_secs(
config.sla_validation.evaluation_window_minutes as u64 * 60
);
let window_metrics = WindowMetrics {
p99_latency_us: metrics.p99_latency_us,
mean_latency_us: metrics.p99_latency_us * 0.6, aece_value: metrics.aece_value,
confidence_shift: metrics.confidence_shift,
sla_recall_50_change: 0.0, success_rate: metrics.success_rate,
sample_count: metrics.sample_count,
health_status: metrics.health_status,
};
Ok(EvaluationWindow {
start_time: now - chrono::Duration::from_std(window_duration)?,
end_time: now,
metrics: window_metrics,
sla_results: SlaGateResults {
p99_latency_gate: GateResult {
passed: true,
measured_value: 0.0,
threshold_value: 0.0,
gate_name: "placeholder".to_string(),
violation_message: None,
},
aece_tau_gate: GateResult {
passed: true,
measured_value: 0.0,
threshold_value: 0.0,
gate_name: "placeholder".to_string(),
violation_message: None,
},
confidence_shift_gate: GateResult {
passed: true,
measured_value: 0.0,
threshold_value: 0.0,
gate_name: "placeholder".to_string(),
violation_message: None,
},
sla_recall_gate: GateResult {
passed: true,
measured_value: 0.0,
threshold_value: 0.0,
gate_name: "placeholder".to_string(),
violation_message: None,
},
overall_passed: true,
},
passed: true,
})
}
fn update_evaluation_window(&self, mut window: EvaluationWindow) -> Result<()> {
let config = self.config.read().unwrap();
window.sla_results = self.validate_sla_gates(&window, &config.sla_validation)?;
window.passed = window.sla_results.overall_passed;
{
let mut history = self.window_history.write().unwrap();
history.push_back(window);
let history_len = history.len();
if history_len > 100 {
history.pop_front();
}
}
Ok(())
}
fn record_decision(&self, decision: &CanaryDecision) -> Result<()> {
let mut history = self.decision_history.write().unwrap();
history.push(decision.clone());
let history_len = history.len();
if history_len > 1000 {
history.drain(0..history_len - 1000);
}
match &decision.decision_type {
CanaryDecisionType::Promote => {
info!("🎯 Canary Decision: PROMOTE {} -> {} ({})",
decision.current_stage,
decision.target_stage.as_ref().unwrap_or(&"Unknown".to_string()),
decision.reason);
}
CanaryDecisionType::Rollback => {
warn!("⚠️ Canary Decision: ROLLBACK {} -> {} ({})",
decision.current_stage,
decision.target_stage.as_ref().unwrap_or(&"Unknown".to_string()),
decision.reason);
}
CanaryDecisionType::EmergencyStop => {
error!("🚨 Canary Decision: EMERGENCY STOP ({})", decision.reason);
}
CanaryDecisionType::Continue => {
debug!("📊 Canary Decision: CONTINUE {} ({})",
decision.current_stage, decision.reason);
}
CanaryDecisionType::ManualIntervention => {
warn!("👥 Canary Decision: MANUAL INTERVENTION REQUIRED ({})", decision.reason);
}
}
Ok(())
}
pub fn get_status(&self) -> Result<serde_json::Value> {
let config = self.config.read().unwrap();
let window_history = self.window_history.read().unwrap();
let decision_history = self.decision_history.read().unwrap();
let consecutive_breaches = self.consecutive_breaches.load(Ordering::Relaxed);
let current_metrics = self.compute_current_metrics()?;
let current_stage = self.get_current_stage()?;
let last_decision = decision_history.last();
Ok(serde_json::json!({
"monitoring_active": self.monitoring_active.load(Ordering::Relaxed),
"current_stage": current_stage,
"consecutive_breaches": consecutive_breaches,
"metrics": current_metrics,
"config": {
"auto_promotion_enabled": config.auto_promotion_enabled,
"auto_rollback_enabled": config.auto_rollback_enabled,
"config_fingerprint": config.config_fingerprint,
},
"evaluation_windows": {
"total_count": window_history.len(),
"recent_windows": window_history.iter().rev().take(10).collect::<Vec<_>>(),
},
"decisions": {
"total_count": decision_history.len(),
"last_decision": last_decision,
},
}))
}
pub fn force_decision(&self, decision_type: CanaryDecisionType, reason: String) -> Result<CanaryDecision> {
let current_stage = self.get_current_stage()?;
let metrics = self.compute_current_metrics()?;
let config = self.config.read().unwrap();
let target_stage = match &decision_type {
CanaryDecisionType::Promote => self.get_next_stage(¤t_stage),
CanaryDecisionType::Rollback => self.get_previous_stage(¤t_stage),
CanaryDecisionType::EmergencyStop => Some("Disabled".to_string()),
_ => None,
};
let decision = CanaryDecision {
timestamp: Utc::now(),
decision_type,
current_stage: current_stage.clone(),
target_stage,
reason,
metrics,
config_fingerprint: config.config_fingerprint.clone(),
};
self.execute_decision(&decision)?;
self.record_decision(&decision)?;
Ok(decision)
}
}
impl Default for CanaryControllerConfig {
fn default() -> Self {
let mut min_observation_hours = HashMap::new();
min_observation_hours.insert("Canary".to_string(), 2);
min_observation_hours.insert("Limited".to_string(), 4);
min_observation_hours.insert("Major".to_string(), 6);
min_observation_hours.insert("Full".to_string(), 12);
let mut success_rate_thresholds = HashMap::new();
success_rate_thresholds.insert("Canary".to_string(), 0.995);
success_rate_thresholds.insert("Limited".to_string(), 0.998);
success_rate_thresholds.insert("Major".to_string(), 0.999);
success_rate_thresholds.insert("Full".to_string(), 0.9995);
let mut min_sample_counts = HashMap::new();
min_sample_counts.insert("Canary".to_string(), 500);
min_sample_counts.insert("Limited".to_string(), 2000);
min_sample_counts.insert("Major".to_string(), 5000);
min_sample_counts.insert("Full".to_string(), 10000);
Self {
auto_promotion_enabled: true,
auto_rollback_enabled: true,
sla_validation: SlaValidationConfig {
p99_latency_sla_us: 1000.0, aece_tau_threshold: 0.01,
max_confidence_shift: 0.02,
require_zero_sla_recall_change: true,
evaluation_window_minutes: 15,
breach_detection: BreachDetectionConfig {
consecutive_breach_threshold: 2,
window_duration_minutes: 15,
grace_period_minutes: 5,
max_breach_rate: 0.1,
},
},
progression_rules: ProgressionRules {
min_observation_hours,
success_rate_thresholds,
required_health_status: HealthStatus::Green,
min_sample_counts,
},
monitoring_config: CanaryMonitoringConfig {
real_time_metrics: true,
collection_interval_sec: 30,
alert_thresholds: AlertThresholds {
latency_degradation_threshold: 0.1, error_rate_threshold: 0.01, aece_degradation_threshold: 0.005,
},
baseline_tracking: BaselineTrackingConfig {
enabled: true,
baseline_lookback_days: 7,
require_green_baseline: true,
significance_level: 0.05,
},
},
config_fingerprint: "default".to_string(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::calibration::feature_flags::CalibV22Config;
use std::sync::Arc;
fn create_test_setup() -> (Arc<CalibV22FeatureFlag>, Arc<RwLock<SlaTripwires>>, Arc<RwLock<DriftMonitor>>) {
todo!("Implement test setup with proper mocking")
}
#[test]
fn test_canary_controller_creation() {
let config = CanaryControllerConfig::default();
assert!(config.auto_promotion_enabled);
assert!(config.auto_rollback_enabled);
assert_eq!(config.sla_validation.p99_latency_sla_us, 1000.0);
}
#[test]
fn test_stage_progression() {
let controller = CanaryController::new(
CanaryControllerConfig::default(),
todo!(), todo!(),
todo!(),
).unwrap();
assert_eq!(controller.get_next_stage("Canary"), Some("Limited".to_string()));
assert_eq!(controller.get_next_stage("Limited"), Some("Major".to_string()));
assert_eq!(controller.get_next_stage("Major"), Some("Full".to_string()));
assert_eq!(controller.get_next_stage("Full"), None);
}
#[test]
fn test_sla_gate_validation() {
let config = SlaValidationConfig {
p99_latency_sla_us: 1000.0,
aece_tau_threshold: 0.01,
max_confidence_shift: 0.02,
require_zero_sla_recall_change: true,
evaluation_window_minutes: 15,
breach_detection: BreachDetectionConfig::default(),
};
}
}