@@ -62,6 +62,7 @@ pub struct ModelHealth {
6262 pub fallback_cache_hit_rate : f64 ,
6363 pub fallback_cache_size : usize ,
6464 pub fallback_max_latency_ms : f64 ,
65+ pub fallback_sla_met : bool , // Whether cosine fallback meets SLA thresholds
6566}
6667
6768/// Health of training corpus
@@ -105,6 +106,7 @@ impl HealthStatus {
105106 fallback_cache_hit_rate : 0.0 ,
106107 fallback_cache_size : 0 ,
107108 fallback_max_latency_ms : 0.0 ,
109+ fallback_sla_met : true , // Start as met until proven otherwise
108110 } ,
109111 corpus_health : CorpusHealth {
110112 total_proofs : 0 ,
@@ -137,6 +139,15 @@ impl HealthStatus {
137139 if !self . gnn_model_health . is_loaded || !self . gnn_model_health . nDCG_meets_threshold {
138140 // GNN model not available or not meeting quality threshold
139141 self . system_degradation = DegradationMode :: CosineOnly ;
142+ } else if !self . gnn_model_health . fallback_sla_met {
143+ // Fallback cosine similarity is violating SLA (latency or success rate)
144+ // Transition from Normal → IncreasingFallback, or escalate if already degraded
145+ if self . system_degradation == DegradationMode :: Normal {
146+ self . system_degradation = DegradationMode :: IncreasingFallback ;
147+ } else if self . system_degradation == DegradationMode :: IncreasingFallback {
148+ self . system_degradation = DegradationMode :: CosineOnly ;
149+ }
150+ // If already CosineOnly or ReadOnly, stay there (escalation only, no downgrade)
140151 } else if failed_provers >= 3 {
141152 // Too many failed provers
142153 self . system_degradation = DegradationMode :: CosineOnly ;
@@ -295,4 +306,47 @@ mod tests {
295306 assert_eq ! ( critical. len( ) , 1 ) ;
296307 assert_eq ! ( critical[ 0 ] , "coq" ) ;
297308 }
309+
310+ #[ test]
311+ fn test_fallback_sla_enforcement_normal_to_degraded ( ) {
312+ let mut health = HealthStatus :: new ( ) ;
313+
314+ // Start in Normal mode
315+ health. gnn_model_health . is_loaded = true ;
316+ health. gnn_model_health . nDCG_meets_threshold = true ;
317+ health. gnn_model_health . fallback_sla_met = true ;
318+ health. gnn_model_health . fallback_cache_hit_rate = 0.75 ; // Warm cache to avoid IncreasingFallback trigger
319+ health. system_degradation = DegradationMode :: Normal ;
320+
321+ // Add at least 3 provers to avoid ReadOnly trigger
322+ for i in 0 ..5 {
323+ health. prover_health . insert (
324+ format ! ( "prover{}" , i) ,
325+ ProverHealth {
326+ name : format ! ( "prover{}" , i) ,
327+ is_available : true ,
328+ circuit_breaker_state : CircuitBreakerStateSnapshot :: Closed ,
329+ last_successful_proof : None ,
330+ consecutive_failures : 0 ,
331+ avg_latency_ms : 50.0 ,
332+ success_rate : 1.0 ,
333+ total_invocations : 10 ,
334+ total_failures : 0 ,
335+ } ,
336+ ) ;
337+ }
338+
339+ // Verify system is normal
340+ health. compute_degradation_mode ( ) ;
341+ assert_eq ! ( health. system_degradation, DegradationMode :: Normal ) ;
342+
343+ // Fallback SLA violation → transition to IncreasingFallback
344+ health. gnn_model_health . fallback_sla_met = false ;
345+ health. compute_degradation_mode ( ) ;
346+ assert_eq ! ( health. system_degradation, DegradationMode :: IncreasingFallback ) ;
347+
348+ // If already degraded and SLA still violated → escalate
349+ health. compute_degradation_mode ( ) ;
350+ assert_eq ! ( health. system_degradation, DegradationMode :: CosineOnly ) ;
351+ }
298352}
0 commit comments