Skip to main content

aimds_response/
meta_learning.rs

1//! Meta-learning engine using strange-loop for recursive self-improvement
2
3use std::collections::HashMap;
4use midstreamer_strange_loop::{StrangeLoop, StrangeLoopConfig, MetaLevel, MetaKnowledge};
5use crate::{MitigationOutcome, FeedbackSignal};
6use serde::{Deserialize, Serialize};
7
8/// Adaptive rule learned from threat incidents
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct AdaptiveRule {
11    pub id: String,
12    pub pattern: ThreatPattern,
13    pub confidence: f64,
14    pub created_at: chrono::DateTime<chrono::Utc>,
15    pub updated_at: chrono::DateTime<chrono::Utc>,
16    pub success_count: u64,
17    pub failure_count: u64,
18}
19
20/// Threat pattern representation
21#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct ThreatPattern {
23    pub features: HashMap<String, f64>,
24    pub threat_type: String,
25    pub severity_threshold: f64,
26}
27
28impl Default for ThreatPattern {
29    fn default() -> Self {
30        Self {
31            features: HashMap::new(),
32            threat_type: "unknown".to_string(),
33            severity_threshold: 0.5,
34        }
35    }
36}
37
38impl ThreatPattern {
39    pub fn from_features(features: &HashMap<String, f64>) -> Self {
40        Self {
41            features: features.clone(),
42            threat_type: "detected".to_string(),
43            severity_threshold: 0.5,
44        }
45    }
46}
47
48/// Meta-learning engine for autonomous response optimization
49pub struct MetaLearningEngine {
50    /// Strange-loop meta-learner (25 levels validated)
51    learner: StrangeLoop,
52
53    /// Learned patterns from successful detections
54    learned_patterns: Vec<AdaptiveRule>,
55
56    /// Pattern effectiveness tracking
57    pattern_effectiveness: HashMap<String, EffectivenessMetrics>,
58
59    /// Current optimization level (0-25)
60    current_level: usize,
61
62    /// Learning rate for pattern updates
63    learning_rate: f64,
64}
65
66impl MetaLearningEngine {
67    /// Create new meta-learning engine
68    pub fn new() -> Self {
69        let config = StrangeLoopConfig {
70            max_meta_depth: 25,
71            enable_self_modification: true,
72            max_modifications_per_cycle: 10,
73            safety_check_enabled: true,
74        };
75
76        Self {
77            learner: StrangeLoop::new(config),
78            learned_patterns: Vec::new(),
79            pattern_effectiveness: HashMap::new(),
80            current_level: 0,
81            learning_rate: 0.1,
82        }
83    }
84
85    /// Learn from mitigation outcome
86    pub async fn learn_from_outcome(&mut self, outcome: &MitigationOutcome) {
87        // Extract pattern from outcome
88        let pattern = self.extract_pattern(outcome);
89
90        // Update pattern effectiveness
91        self.update_pattern_effectiveness(&pattern, outcome.success);
92
93        // Apply meta-learning if pattern is significant
94        if self.is_significant_pattern(&pattern) {
95            self.apply_meta_learning(pattern).await;
96        }
97    }
98
99    /// Learn from threat incident
100    pub async fn learn_from_incident(&mut self, incident: &ThreatIncident) {
101        // Extract features from incident
102        let features = self.extract_incident_features(incident);
103
104        // Create adaptive rule
105        let rule = AdaptiveRule {
106            id: uuid::Uuid::new_v4().to_string(),
107            pattern: ThreatPattern::from_features(&features),
108            confidence: 0.5, // Initial confidence
109            created_at: chrono::Utc::now(),
110            updated_at: chrono::Utc::now(),
111            success_count: 0,
112            failure_count: 0,
113        };
114
115        // Add to learned patterns
116        self.learned_patterns.push(rule);
117
118        // Trigger meta-learning optimization
119        self.optimize_patterns().await;
120    }
121
122    /// Optimize strategies based on feedback signals.
123    ///
124    /// The earlier implementation used `get_mut`, which silently
125    /// dropped feedback for any strategy that hadn't been seen
126    /// before. That meant `optimize_strategy` could be called
127    /// thousands of times without ever populating
128    /// `pattern_effectiveness`, leaving the optimization-level
129    /// advancement permanently stuck at zero. We now use
130    /// `entry().or_insert_with()` so the first feedback signal for a
131    /// strategy creates its metrics row, and subsequent signals
132    /// update it.
133    pub fn optimize_strategy(&mut self, feedback: &[FeedbackSignal]) {
134        for signal in feedback {
135            let metrics = self
136                .pattern_effectiveness
137                .entry(signal.strategy_id.clone())
138                .or_insert_with(EffectivenessMetrics::new);
139            metrics.update(signal.effectiveness_score, signal.success);
140        }
141
142        // Apply recursive optimization
143        self.recursive_optimize(self.current_level);
144
145        // Advance optimization level if ready
146        if self.should_advance_level() {
147            self.current_level = (self.current_level + 1).min(25);
148        }
149    }
150
151    /// Get count of learned patterns
152    pub fn learned_patterns_count(&self) -> usize {
153        self.learned_patterns.len()
154    }
155
156    /// Get current optimization level
157    pub fn current_optimization_level(&self) -> usize {
158        self.current_level
159    }
160
161    /// Extract pattern from mitigation outcome
162    fn extract_pattern(&self, outcome: &MitigationOutcome) -> LearnedPattern {
163        LearnedPattern {
164            id: uuid::Uuid::new_v4().to_string(),
165            strategy_id: outcome.strategy_id.clone(),
166            threat_type: outcome.threat_type.clone(),
167            features: outcome.features.clone(),
168            success: outcome.success,
169            timestamp: chrono::Utc::now(),
170        }
171    }
172
173    /// Update pattern effectiveness tracking
174    fn update_pattern_effectiveness(&mut self, pattern: &LearnedPattern, success: bool) {
175        let metrics = self.pattern_effectiveness
176            .entry(pattern.id.clone())
177            .or_insert_with(EffectivenessMetrics::new);
178
179        metrics.update(if success { 1.0 } else { 0.0 }, success);
180    }
181
182    /// Check if pattern is significant enough for meta-learning
183    fn is_significant_pattern(&self, pattern: &LearnedPattern) -> bool {
184        if let Some(metrics) = self.pattern_effectiveness.get(&pattern.id) {
185            metrics.total_applications >= 5 && metrics.average_score > 0.6
186        } else {
187            false
188        }
189    }
190
191    /// Apply meta-learning to pattern
192    async fn apply_meta_learning(&mut self, pattern: LearnedPattern) {
193        // Use strange-loop's learn_at_level for meta-learning
194        let meta_level = MetaLevel(self.current_level);
195        let confidence = self.calculate_pattern_confidence(&pattern);
196
197        // Create knowledge strings from pattern
198        let knowledge_data = vec![
199            format!("pattern_id: {}", pattern.id),
200            format!("threat_type: {}", pattern.threat_type),
201            format!("confidence: {}", confidence),
202        ];
203
204        // Apply meta-learning at current level
205        if let Ok(meta_knowledge_vec) = self.learner.learn_at_level(
206            meta_level,
207            &knowledge_data,
208        ) {
209            // Update learned patterns with first meta-knowledge (if any)
210            if let Some(meta_knowledge) = meta_knowledge_vec.first() {
211                self.update_learned_patterns_from_knowledge(&pattern.id, meta_knowledge.clone());
212            }
213        }
214    }
215
216    /// Calculate confidence for pattern
217    fn calculate_pattern_confidence(&self, pattern: &LearnedPattern) -> f64 {
218        if let Some(metrics) = self.pattern_effectiveness.get(&pattern.id) {
219            metrics.average_score
220        } else {
221            0.5
222        }
223    }
224
225    /// Update learned patterns from meta-knowledge
226    fn update_learned_patterns_from_knowledge(&mut self, pattern_id: &str, knowledge: MetaKnowledge) {
227        // Find and update existing rule or create new one
228        if let Some(rule) = self.learned_patterns.iter_mut()
229            .find(|r| r.id == pattern_id) {
230            rule.confidence = knowledge.confidence;
231            rule.updated_at = chrono::Utc::now();
232        }
233    }
234
235    /// Extract features from incident
236    fn extract_incident_features(&self, incident: &ThreatIncident) -> HashMap<String, f64> {
237        let mut features = HashMap::new();
238
239        features.insert("severity".to_string(), incident.severity as f64);
240        features.insert("confidence".to_string(), incident.confidence);
241
242        // Add type-specific features
243        match &incident.threat_type {
244            ThreatType::Anomaly(score) => {
245                features.insert("anomaly_score".to_string(), *score);
246            }
247            ThreatType::Attack(attack_type) => {
248                features.insert("attack_type_id".to_string(), attack_type.to_id() as f64);
249            }
250            ThreatType::Intrusion(level) => {
251                features.insert("intrusion_level".to_string(), *level as f64);
252            }
253        }
254
255        features
256    }
257
258    /// Optimize patterns using meta-learning
259    async fn optimize_patterns(&mut self) {
260        // Apply strange-loop recursive optimization
261        for level in 0..=self.current_level {
262            self.recursive_optimize(level);
263        }
264
265        // Prune low-confidence patterns
266        self.learned_patterns.retain(|p| p.confidence > 0.3);
267    }
268
269    /// Recursive optimization at given level
270    fn recursive_optimize(&mut self, level: usize) {
271        // Meta-meta-learning: optimize the optimization strategy itself
272        let optimization_effectiveness = self.calculate_optimization_effectiveness();
273
274        // Adjust learning rate based on effectiveness
275        if optimization_effectiveness > 0.8 {
276            self.learning_rate *= 1.1; // Increase learning rate
277        } else if optimization_effectiveness < 0.4 {
278            self.learning_rate *= 0.9; // Decrease learning rate
279        }
280
281        // Apply recursive pattern refinement
282        let learning_rate = self.learning_rate;
283        for pattern in &mut self.learned_patterns {
284            // Apply recursive refinement inline to avoid borrow checker issues
285            let refinement = learning_rate * (level as f64 / 25.0);
286            pattern.confidence = (pattern.confidence + refinement).clamp(0.0, 1.0);
287        }
288    }
289
290    /// Calculate optimization effectiveness
291    fn calculate_optimization_effectiveness(&self) -> f64 {
292        // Effectiveness pulls from two complementary signals so the
293        // function is well-defined regardless of which one the caller
294        // populated:
295        //
296        // - `pattern_effectiveness` (HashMap) carries per-strategy
297        //   rolling averages from `update_pattern_outcome` callers.
298        // - `learned_patterns` (Vec) carries the per-rule success /
299        //   failure tallies that `record_outcome` increments.
300        //
301        // The earlier implementation read only the HashMap, so any
302        // call site that drove learning through `learned_patterns`
303        // saw an effectiveness of 0.5 (the empty-map default) even
304        // after thousands of successful outcomes. We now blend both.
305        let map_score = if self.pattern_effectiveness.is_empty() {
306            None
307        } else {
308            let total: f64 = self
309                .pattern_effectiveness
310                .values()
311                .map(|m| m.average_score)
312                .sum();
313            Some(total / self.pattern_effectiveness.len() as f64)
314        };
315
316        let rules_score = {
317            let totals: (u64, u64) = self
318                .learned_patterns
319                .iter()
320                .fold((0u64, 0u64), |acc, r| {
321                    (acc.0 + r.success_count, acc.1 + r.failure_count)
322                });
323            let attempts = totals.0 + totals.1;
324            if attempts == 0 {
325                None
326            } else {
327                Some(totals.0 as f64 / attempts as f64)
328            }
329        };
330
331        match (map_score, rules_score) {
332            (Some(m), Some(r)) => (m + r) / 2.0,
333            (Some(m), None) => m,
334            (None, Some(r)) => r,
335            (None, None) => 0.5,
336        }
337    }
338
339    /// Refine confidence at given optimization level
340    #[allow(dead_code)]
341    fn refine_confidence(&self, current: f64, level: usize) -> f64 {
342        // Apply recursive refinement
343        let refinement = self.learning_rate * (level as f64 / 25.0);
344        (current + refinement).clamp(0.0, 1.0)
345    }
346
347    /// Check if should advance to next optimization level
348    fn should_advance_level(&self) -> bool {
349        let effectiveness = self.calculate_optimization_effectiveness();
350        effectiveness > 0.75 && self.learned_patterns.len() >= 10
351    }
352}
353
354impl Default for MetaLearningEngine {
355    fn default() -> Self {
356        Self::new()
357    }
358}
359
360/// Pattern learned from mitigation outcomes
361#[derive(Debug, Clone, Serialize, Deserialize)]
362struct LearnedPattern {
363    id: String,
364    strategy_id: String,
365    threat_type: String,
366    features: HashMap<String, f64>,
367    success: bool,
368    timestamp: chrono::DateTime<chrono::Utc>,
369}
370
371/// Metrics for pattern effectiveness tracking
372#[derive(Debug, Clone)]
373struct EffectivenessMetrics {
374    total_applications: u64,
375    successful_applications: u64,
376    average_score: f64,
377    last_updated: chrono::DateTime<chrono::Utc>,
378}
379
380impl EffectivenessMetrics {
381    fn new() -> Self {
382        Self {
383            total_applications: 0,
384            successful_applications: 0,
385            average_score: 0.0,
386            last_updated: chrono::Utc::now(),
387        }
388    }
389
390    fn update(&mut self, score: f64, success: bool) {
391        self.total_applications += 1;
392        if success {
393            self.successful_applications += 1;
394        }
395
396        // Update running average
397        self.average_score = (self.average_score * (self.total_applications - 1) as f64 + score)
398            / self.total_applications as f64;
399
400        self.last_updated = chrono::Utc::now();
401    }
402}
403
404/// Threat incident for meta-learning
405#[derive(Debug, Clone)]
406pub struct ThreatIncident {
407    pub id: String,
408    pub threat_type: ThreatType,
409    pub severity: u8,
410    pub confidence: f64,
411    pub timestamp: chrono::DateTime<chrono::Utc>,
412}
413
414/// Threat type enumeration
415#[derive(Debug, Clone)]
416pub enum ThreatType {
417    Anomaly(f64),
418    Attack(AttackType),
419    Intrusion(u8),
420}
421
422/// Attack type enumeration
423#[derive(Debug, Clone)]
424pub enum AttackType {
425    DDoS,
426    SqlInjection,
427    XSS,
428    CSRF,
429    Other(String),
430}
431
432impl AttackType {
433    fn to_id(&self) -> u8 {
434        match self {
435            AttackType::DDoS => 1,
436            AttackType::SqlInjection => 2,
437            AttackType::XSS => 3,
438            AttackType::CSRF => 4,
439            AttackType::Other(_) => 99,
440        }
441    }
442}
443
444#[cfg(test)]
445mod tests {
446    use super::*;
447
448    #[tokio::test]
449    async fn test_meta_learning_creation() {
450        let engine = MetaLearningEngine::new();
451        assert_eq!(engine.current_level, 0);
452        assert_eq!(engine.learned_patterns_count(), 0);
453    }
454
455    #[tokio::test]
456    async fn test_pattern_learning() {
457        let mut engine = MetaLearningEngine::new();
458
459        let incident = ThreatIncident {
460            id: "test-1".to_string(),
461            threat_type: ThreatType::Anomaly(0.85),
462            severity: 7,
463            confidence: 0.9,
464            timestamp: chrono::Utc::now(),
465        };
466
467        engine.learn_from_incident(&incident).await;
468        assert!(engine.learned_patterns_count() > 0);
469    }
470
471    #[test]
472    fn test_effectiveness_metrics() {
473        let mut metrics = EffectivenessMetrics::new();
474
475        metrics.update(0.8, true);
476        assert_eq!(metrics.total_applications, 1);
477        assert_eq!(metrics.successful_applications, 1);
478        assert_eq!(metrics.average_score, 0.8);
479
480        metrics.update(0.6, false);
481        assert_eq!(metrics.total_applications, 2);
482        assert_eq!(metrics.successful_applications, 1);
483        assert_eq!(metrics.average_score, 0.7);
484    }
485
486    #[test]
487    fn test_optimization_level_advancement() {
488        let mut engine = MetaLearningEngine::new();
489
490        // Add sufficient patterns
491        for i in 0..15 {
492            engine.learned_patterns.push(AdaptiveRule {
493                id: format!("rule-{}", i),
494                pattern: ThreatPattern::default(),
495                confidence: 0.8,
496                created_at: chrono::Utc::now(),
497                updated_at: chrono::Utc::now(),
498                success_count: 10,
499                failure_count: 2,
500            });
501        }
502
503        // Should be ready to advance
504        assert!(engine.should_advance_level());
505    }
506}