Skip to main content

lens_core/adversarial/
mod.rs

1//! # Adversarial Audit Module
2//!
3//! Implements comprehensive adversarial testing suites to validate system robustness
4//! as specified in TODO.md Step 4 - Adversarial audit.
5//!
6//! Test Suites:
7//! - Clone-heavy repositories (duplicate content stress testing)
8//! - Vendored bloat scenarios (large dependency noise)
9//! - Large JSON/data files (non-code content filtering)
10//!
11//! Validation Gates:
12//! - span=100% (complete corpus coverage)
13//! - SLA-Recall@50 flat (no degradation under adversarial conditions)
14//! - p99/p95 ≤ 2.0 (latency stability under stress)
15
16pub mod clone_suite;
17pub mod bloat_suite;
18pub mod noise_suite;
19pub mod adversarial_orchestrator;
20pub mod stress_harness;
21
22pub use clone_suite::{CloneSuite, CloneTestConfig, CloneResult};
23pub use bloat_suite::{BloatSuite, BloatTestConfig, BloatResult};
24pub use noise_suite::{NoiseSuite, NoiseTestConfig, NoiseResult};
25pub use adversarial_orchestrator::{AdversarialOrchestrator, AdversarialConfig};
26pub use stress_harness::{StressHarness, StressResult};
27
28use anyhow::Result;
29use serde::{Deserialize, Serialize};
30use std::collections::HashMap;
31
32/// Adversarial test result aggregation
33#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct AdversarialAuditResult {
35    pub clone_results: CloneResult,
36    pub bloat_results: BloatResult,
37    pub noise_results: NoiseResult,
38    pub overall_metrics: OverallMetrics,
39    pub gate_validation: GateValidation,
40    pub stress_profile: StressProfile,
41}
42
43/// Overall performance metrics across all adversarial tests
44#[derive(Debug, Clone, Serialize, Deserialize)]
45pub struct OverallMetrics {
46    pub span_coverage_pct: f32,
47    pub sla_recall_at_50: f32,
48    pub p99_latency_ms: f32,
49    pub p95_latency_ms: f32,
50    pub degradation_factor: f32,
51    pub robustness_score: f32,
52}
53
54/// Gate validation results for adversarial audit
55#[derive(Debug, Clone, Serialize, Deserialize)]
56pub struct GateValidation {
57    pub span_coverage_gate: bool,    // Must be 100%
58    pub sla_recall_gate: bool,       // Must be flat (no degradation)
59    pub latency_stability_gate: bool, // p99/p95 ≤ 2.0
60    pub overall_pass: bool,
61    pub violations: Vec<String>,
62}
63
64/// System stress profile under adversarial conditions
65#[derive(Debug, Clone, Serialize, Deserialize)]
66pub struct StressProfile {
67    pub memory_peak_mb: f32,
68    pub cpu_utilization_pct: f32,
69    pub disk_io_ops_per_sec: f32,
70    pub network_bandwidth_mbps: f32,
71    pub gc_pressure_score: f32,
72    pub resource_exhaustion_risk: RiskLevel,
73}
74
75#[derive(Debug, Clone, Serialize, Deserialize)]
76pub enum RiskLevel {
77    Low,
78    Medium,
79    High,
80    Critical,
81}
82
83/// Validate adversarial audit gates
84pub fn validate_adversarial_gates(result: &AdversarialAuditResult) -> Result<bool> {
85    const SPAN_COVERAGE_THRESHOLD: f32 = 100.0;
86    const LATENCY_STABILITY_RATIO: f32 = 2.0;
87    const MIN_SLA_RECALL: f32 = 0.50; // Flat requirement - no degradation allowed
88    
89    let mut violations = Vec::new();
90    let metrics = &result.overall_metrics;
91    
92    // Gate 1: span=100% (complete corpus coverage)
93    let span_gate = metrics.span_coverage_pct >= SPAN_COVERAGE_THRESHOLD;
94    if !span_gate {
95        violations.push(format!(
96            "Span coverage gate failed: {:.1}% < {:.1}% required",
97            metrics.span_coverage_pct, SPAN_COVERAGE_THRESHOLD
98        ));
99    }
100    
101    // Gate 2: SLA-Recall@50 flat (no degradation)
102    let sla_recall_gate = metrics.sla_recall_at_50 >= MIN_SLA_RECALL;
103    if !sla_recall_gate {
104        violations.push(format!(
105            "SLA-Recall@50 gate failed: {:.3} < {:.3} required",
106            metrics.sla_recall_at_50, MIN_SLA_RECALL
107        ));
108    }
109    
110    // Gate 3: p99/p95 ≤ 2.0 (latency stability)
111    let latency_ratio = metrics.p99_latency_ms / metrics.p95_latency_ms;
112    let latency_gate = latency_ratio <= LATENCY_STABILITY_RATIO;
113    if !latency_gate {
114        violations.push(format!(
115            "Latency stability gate failed: p99/p95 = {:.2} > {:.1} allowed",
116            latency_ratio, LATENCY_STABILITY_RATIO
117        ));
118    }
119    
120    let overall_pass = span_gate && sla_recall_gate && latency_gate;
121    
122    if !overall_pass {
123        tracing::warn!(
124            "Adversarial audit gate failures: {}",
125            violations.join("; ")
126        );
127    }
128    
129    Ok(overall_pass)
130}
131
132/// Calculate robustness score based on adversarial performance
133pub fn calculate_robustness_score(result: &AdversarialAuditResult) -> f32 {
134    let metrics = &result.overall_metrics;
135    
136    // Weighted scoring across key robustness dimensions
137    let span_score = (metrics.span_coverage_pct / 100.0).min(1.0);
138    let recall_score = metrics.sla_recall_at_50.min(1.0);
139    let stability_score = (2.0 / (metrics.p99_latency_ms / metrics.p95_latency_ms)).min(1.0);
140    let degradation_score = (2.0 / (1.0 + metrics.degradation_factor)).min(1.0);
141    
142    // Geometric mean for conservative scoring
143    let robustness = (span_score * recall_score * stability_score * degradation_score).powf(0.25);
144    
145    (robustness * 100.0).round() / 100.0
146}
147
148#[cfg(test)]
149mod tests {
150    use super::*;
151
152    #[test]
153    fn test_adversarial_gate_validation() {
154        let passing_result = AdversarialAuditResult {
155            clone_results: CloneResult::default(),
156            bloat_results: BloatResult::default(),
157            noise_results: NoiseResult::default(),
158            overall_metrics: OverallMetrics {
159                span_coverage_pct: 100.0,
160                sla_recall_at_50: 0.52,
161                p99_latency_ms: 190.0,
162                p95_latency_ms: 140.0, // ratio = 1.36 ≤ 2.0 ✓
163                degradation_factor: 1.15,
164                robustness_score: 0.85,
165            },
166            gate_validation: GateValidation {
167                span_coverage_gate: true,
168                sla_recall_gate: true,
169                latency_stability_gate: true,
170                overall_pass: true,
171                violations: vec![],
172            },
173            stress_profile: StressProfile {
174                memory_peak_mb: 2048.0,
175                cpu_utilization_pct: 75.0,
176                disk_io_ops_per_sec: 1250.0,
177                network_bandwidth_mbps: 15.0,
178                gc_pressure_score: 0.3,
179                resource_exhaustion_risk: RiskLevel::Low,
180            },
181        };
182        
183        assert!(validate_adversarial_gates(&passing_result).unwrap());
184    }
185
186    #[test]
187    fn test_failing_span_coverage_gate() {
188        let mut failing_result = AdversarialAuditResult {
189            clone_results: CloneResult::default(),
190            bloat_results: BloatResult::default(),
191            noise_results: NoiseResult::default(),
192            overall_metrics: OverallMetrics {
193                span_coverage_pct: 97.5, // Below 100% threshold
194                sla_recall_at_50: 0.52,
195                p99_latency_ms: 180.0,
196                p95_latency_ms: 130.0,
197                degradation_factor: 1.10,
198                robustness_score: 0.80,
199            },
200            gate_validation: GateValidation {
201                span_coverage_gate: false,
202                sla_recall_gate: true,
203                latency_stability_gate: true,
204                overall_pass: false,
205                violations: vec!["Span coverage insufficient".to_string()],
206            },
207            stress_profile: StressProfile {
208                memory_peak_mb: 1800.0,
209                cpu_utilization_pct: 70.0,
210                disk_io_ops_per_sec: 1100.0,
211                network_bandwidth_mbps: 12.0,
212                gc_pressure_score: 0.25,
213                resource_exhaustion_risk: RiskLevel::Low,
214            },
215        };
216        
217        assert!(!validate_adversarial_gates(&failing_result).unwrap());
218    }
219
220    #[test]
221    fn test_robustness_score_calculation() {
222        let result = AdversarialAuditResult {
223            clone_results: CloneResult::default(),
224            bloat_results: BloatResult::default(),
225            noise_results: NoiseResult::default(),
226            overall_metrics: OverallMetrics {
227                span_coverage_pct: 100.0,
228                sla_recall_at_50: 0.52,
229                p99_latency_ms: 180.0,
230                p95_latency_ms: 140.0,
231                degradation_factor: 1.2,
232                robustness_score: 0.0, // Will be calculated
233            },
234            gate_validation: GateValidation {
235                span_coverage_gate: true,
236                sla_recall_gate: true,
237                latency_stability_gate: true,
238                overall_pass: true,
239                violations: vec![],
240            },
241            stress_profile: StressProfile {
242                memory_peak_mb: 2000.0,
243                cpu_utilization_pct: 72.0,
244                disk_io_ops_per_sec: 1200.0,
245                network_bandwidth_mbps: 14.0,
246                gc_pressure_score: 0.28,
247                resource_exhaustion_risk: RiskLevel::Low,
248            },
249        };
250        
251        let score = calculate_robustness_score(&result);
252        assert!(score > 0.7); // Should be reasonably high for good metrics
253        assert!(score <= 1.0); // Should be normalized
254    }
255}