codex_memory/memory/
importance_assessment_config.rs

1use crate::memory::importance_assessment::{
2    CircuitBreakerConfig, ImportanceAssessmentConfig, ImportancePattern, PerformanceConfig,
3    ReferenceEmbedding, Stage1Config, Stage2Config, Stage3Config,
4};
5use anyhow::{Context, Result};
6use serde::{Deserialize, Serialize};
7use std::fs;
8use std::path::Path;
9use tracing::{info, warn};
10
11/// Configuration loader for importance assessment pipeline
12pub struct ImportanceAssessmentConfigLoader;
13
14impl ImportanceAssessmentConfigLoader {
15    /// Load configuration from a TOML file
16    pub fn load_from_file<P: AsRef<Path>>(path: P) -> Result<ImportanceAssessmentConfig> {
17        let content = fs::read_to_string(&path)
18            .with_context(|| format!("Failed to read config file: {}", path.as_ref().display()))?;
19
20        let config: ImportanceAssessmentConfigFile = toml::from_str(&content)
21            .with_context(|| format!("Failed to parse config file: {}", path.as_ref().display()))?;
22
23        info!(
24            "Loaded importance assessment config from: {}",
25            path.as_ref().display()
26        );
27        Self::validate_and_convert(config)
28    }
29
30    /// Load configuration from environment variables with defaults
31    pub fn load_from_env() -> Result<ImportanceAssessmentConfig> {
32        let mut config = ImportanceAssessmentConfig::default();
33
34        // Stage 1 configuration
35        if let Ok(threshold) = std::env::var("CODEX_STAGE1_CONFIDENCE_THRESHOLD") {
36            config.stage1.confidence_threshold = threshold
37                .parse()
38                .context("Invalid CODEX_STAGE1_CONFIDENCE_THRESHOLD")?;
39        }
40
41        if let Ok(max_time) = std::env::var("CODEX_STAGE1_MAX_TIME_MS") {
42            config.stage1.max_processing_time_ms = max_time
43                .parse()
44                .context("Invalid CODEX_STAGE1_MAX_TIME_MS")?;
45        }
46
47        // Stage 2 configuration
48        if let Ok(threshold) = std::env::var("CODEX_STAGE2_CONFIDENCE_THRESHOLD") {
49            config.stage2.confidence_threshold = threshold
50                .parse()
51                .context("Invalid CODEX_STAGE2_CONFIDENCE_THRESHOLD")?;
52        }
53
54        if let Ok(max_time) = std::env::var("CODEX_STAGE2_MAX_TIME_MS") {
55            config.stage2.max_processing_time_ms = max_time
56                .parse()
57                .context("Invalid CODEX_STAGE2_MAX_TIME_MS")?;
58        }
59
60        if let Ok(cache_ttl) = std::env::var("CODEX_STAGE2_CACHE_TTL_SECONDS") {
61            config.stage2.embedding_cache_ttl_seconds = cache_ttl
62                .parse()
63                .context("Invalid CODEX_STAGE2_CACHE_TTL_SECONDS")?;
64        }
65
66        if let Ok(similarity_threshold) = std::env::var("CODEX_STAGE2_SIMILARITY_THRESHOLD") {
67            config.stage2.similarity_threshold = similarity_threshold
68                .parse()
69                .context("Invalid CODEX_STAGE2_SIMILARITY_THRESHOLD")?;
70        }
71
72        // Stage 3 configuration
73        if let Ok(max_time) = std::env::var("CODEX_STAGE3_MAX_TIME_MS") {
74            config.stage3.max_processing_time_ms = max_time
75                .parse()
76                .context("Invalid CODEX_STAGE3_MAX_TIME_MS")?;
77        }
78
79        if let Ok(endpoint) = std::env::var("CODEX_STAGE3_LLM_ENDPOINT") {
80            config.stage3.llm_endpoint = endpoint;
81        }
82
83        if let Ok(max_concurrent) = std::env::var("CODEX_STAGE3_MAX_CONCURRENT") {
84            config.stage3.max_concurrent_requests = max_concurrent
85                .parse()
86                .context("Invalid CODEX_STAGE3_MAX_CONCURRENT")?;
87        }
88
89        if let Ok(usage_pct) = std::env::var("CODEX_STAGE3_TARGET_USAGE_PCT") {
90            config.stage3.target_usage_percentage = usage_pct
91                .parse()
92                .context("Invalid CODEX_STAGE3_TARGET_USAGE_PCT")?;
93        }
94
95        // Circuit breaker configuration
96        if let Ok(failure_threshold) = std::env::var("CODEX_CB_FAILURE_THRESHOLD") {
97            config.circuit_breaker.failure_threshold = failure_threshold
98                .parse()
99                .context("Invalid CODEX_CB_FAILURE_THRESHOLD")?;
100        }
101
102        if let Ok(failure_window) = std::env::var("CODEX_CB_FAILURE_WINDOW_SECONDS") {
103            config.circuit_breaker.failure_window_seconds = failure_window
104                .parse()
105                .context("Invalid CODEX_CB_FAILURE_WINDOW_SECONDS")?;
106        }
107
108        if let Ok(recovery_timeout) = std::env::var("CODEX_CB_RECOVERY_TIMEOUT_SECONDS") {
109            config.circuit_breaker.recovery_timeout_seconds = recovery_timeout
110                .parse()
111                .context("Invalid CODEX_CB_RECOVERY_TIMEOUT_SECONDS")?;
112        }
113
114        if let Ok(min_requests) = std::env::var("CODEX_CB_MINIMUM_REQUESTS") {
115            config.circuit_breaker.minimum_requests = min_requests
116                .parse()
117                .context("Invalid CODEX_CB_MINIMUM_REQUESTS")?;
118        }
119
120        // Performance configuration
121        if let Ok(target_ms) = std::env::var("CODEX_PERF_STAGE1_TARGET_MS") {
122            config.performance.stage1_target_ms = target_ms
123                .parse()
124                .context("Invalid CODEX_PERF_STAGE1_TARGET_MS")?;
125        }
126
127        if let Ok(target_ms) = std::env::var("CODEX_PERF_STAGE2_TARGET_MS") {
128            config.performance.stage2_target_ms = target_ms
129                .parse()
130                .context("Invalid CODEX_PERF_STAGE2_TARGET_MS")?;
131        }
132
133        if let Ok(target_ms) = std::env::var("CODEX_PERF_STAGE3_TARGET_MS") {
134            config.performance.stage3_target_ms = target_ms
135                .parse()
136                .context("Invalid CODEX_PERF_STAGE3_TARGET_MS")?;
137        }
138
139        info!("Loaded importance assessment config from environment variables");
140        Self::validate_config(&config)?;
141        Ok(config)
142    }
143
144    /// Load configuration with fallback: file -> env -> defaults
145    pub fn load_with_fallback<P: AsRef<Path>>(
146        config_path: Option<P>,
147    ) -> Result<ImportanceAssessmentConfig> {
148        // Try to load from file first
149        if let Some(path) = config_path {
150            if path.as_ref().exists() {
151                match Self::load_from_file(path) {
152                    Ok(config) => return Ok(config),
153                    Err(e) => {
154                        warn!(
155                            "Failed to load config from file, falling back to environment: {}",
156                            e
157                        );
158                    }
159                }
160            } else {
161                info!(
162                    "Config file not found: {}, using environment/defaults",
163                    path.as_ref().display()
164                );
165            }
166        }
167
168        // Fall back to environment variables
169        Self::load_from_env()
170    }
171
172    /// Create a production-ready configuration
173    pub fn production_config() -> ImportanceAssessmentConfig {
174        let mut config = ImportanceAssessmentConfig::default();
175
176        // Production tuning for performance and reliability
177        config.stage1.confidence_threshold = 0.7; // Higher threshold for production
178        config.stage1.max_processing_time_ms = 5; // Very fast Stage 1
179
180        config.stage2.confidence_threshold = 0.8; // Higher threshold for production
181        config.stage2.max_processing_time_ms = 50; // Fast Stage 2
182        config.stage2.embedding_cache_ttl_seconds = 7200; // 2 hour cache
183        config.stage2.similarity_threshold = 0.75; // Higher similarity requirement
184
185        config.stage3.max_processing_time_ms = 500; // Faster Stage 3 timeout
186        config.stage3.max_concurrent_requests = 3; // Conservative concurrency
187        config.stage3.target_usage_percentage = 15.0; // Lower Stage 3 usage
188
189        config.circuit_breaker.failure_threshold = 3; // More sensitive circuit breaker
190        config.circuit_breaker.failure_window_seconds = 30; // Shorter failure window
191        config.circuit_breaker.recovery_timeout_seconds = 60; // Longer recovery time
192        config.circuit_breaker.minimum_requests = 2; // Lower minimum requests
193
194        config.performance.stage1_target_ms = 5; // Aggressive Stage 1 target
195        config.performance.stage2_target_ms = 50; // Aggressive Stage 2 target
196        config.performance.stage3_target_ms = 500; // Aggressive Stage 3 target
197
198        info!("Created production-optimized importance assessment config");
199        config
200    }
201
202    /// Create a development-friendly configuration
203    pub fn development_config() -> ImportanceAssessmentConfig {
204        let mut config = ImportanceAssessmentConfig::default();
205
206        // Development tuning for easier debugging and testing
207        config.stage1.confidence_threshold = 0.5; // Lower threshold for development
208        config.stage1.max_processing_time_ms = 20; // More lenient timing
209
210        config.stage2.confidence_threshold = 0.6; // Lower threshold for development
211        config.stage2.max_processing_time_ms = 200; // More lenient timing
212        config.stage2.embedding_cache_ttl_seconds = 1800; // 30 minute cache
213        config.stage2.similarity_threshold = 0.6; // Lower similarity requirement
214
215        config.stage3.max_processing_time_ms = 2000; // Generous Stage 3 timeout
216        config.stage3.max_concurrent_requests = 10; // Higher concurrency for testing
217        config.stage3.target_usage_percentage = 30.0; // Higher Stage 3 usage for testing
218
219        config.circuit_breaker.failure_threshold = 10; // Less sensitive circuit breaker
220        config.circuit_breaker.failure_window_seconds = 120; // Longer failure window
221        config.circuit_breaker.recovery_timeout_seconds = 30; // Shorter recovery time
222        config.circuit_breaker.minimum_requests = 5; // Higher minimum requests
223
224        config.performance.stage1_target_ms = 20; // Lenient Stage 1 target
225        config.performance.stage2_target_ms = 200; // Lenient Stage 2 target
226        config.performance.stage3_target_ms = 2000; // Lenient Stage 3 target
227
228        info!("Created development-optimized importance assessment config");
229        config
230    }
231
232    /// Export configuration to TOML file
233    pub fn export_to_file<P: AsRef<Path>>(
234        config: &ImportanceAssessmentConfig,
235        path: P,
236    ) -> Result<()> {
237        let config_file = Self::convert_to_file_format(config);
238        let toml_content =
239            toml::to_string_pretty(&config_file).context("Failed to serialize config to TOML")?;
240
241        fs::write(&path, toml_content)
242            .with_context(|| format!("Failed to write config file: {}", path.as_ref().display()))?;
243
244        info!(
245            "Exported importance assessment config to: {}",
246            path.as_ref().display()
247        );
248        Ok(())
249    }
250
251    fn validate_and_convert(
252        config_file: ImportanceAssessmentConfigFile,
253    ) -> Result<ImportanceAssessmentConfig> {
254        let config = ImportanceAssessmentConfig {
255            stage1: Stage1Config {
256                confidence_threshold: config_file.stage1.confidence_threshold,
257                pattern_library: config_file
258                    .stage1
259                    .patterns
260                    .into_iter()
261                    .map(|p| ImportancePattern {
262                        name: p.name,
263                        pattern: p.pattern,
264                        weight: p.weight,
265                        context_boosters: p.context_boosters.unwrap_or_default(),
266                        category: p.category,
267                    })
268                    .collect(),
269                max_processing_time_ms: config_file.stage1.max_processing_time_ms,
270            },
271            stage2: Stage2Config {
272                confidence_threshold: config_file.stage2.confidence_threshold,
273                max_processing_time_ms: config_file.stage2.max_processing_time_ms,
274                embedding_cache_ttl_seconds: config_file.stage2.embedding_cache_ttl_seconds,
275                embedding_cache_max_size: config_file
276                    .stage2
277                    .embedding_cache_max_size
278                    .unwrap_or(10000),
279                cache_eviction_threshold: config_file
280                    .stage2
281                    .cache_eviction_threshold
282                    .unwrap_or(0.8),
283                similarity_threshold: config_file.stage2.similarity_threshold,
284                reference_embeddings: config_file
285                    .stage2
286                    .reference_embeddings
287                    .unwrap_or_default()
288                    .into_iter()
289                    .map(|r| ReferenceEmbedding {
290                        name: r.name,
291                        embedding: r.embedding,
292                        weight: r.weight,
293                        category: r.category,
294                    })
295                    .collect(),
296            },
297            stage3: Stage3Config {
298                max_processing_time_ms: config_file.stage3.max_processing_time_ms,
299                llm_endpoint: config_file.stage3.llm_endpoint,
300                max_concurrent_requests: config_file.stage3.max_concurrent_requests,
301                prompt_template: config_file.stage3.prompt_template,
302                target_usage_percentage: config_file.stage3.target_usage_percentage,
303            },
304            circuit_breaker: CircuitBreakerConfig {
305                failure_threshold: config_file.circuit_breaker.failure_threshold,
306                failure_window_seconds: config_file.circuit_breaker.failure_window_seconds,
307                recovery_timeout_seconds: config_file.circuit_breaker.recovery_timeout_seconds,
308                minimum_requests: config_file.circuit_breaker.minimum_requests,
309            },
310            performance: PerformanceConfig {
311                stage1_target_ms: config_file.performance.stage1_target_ms,
312                stage2_target_ms: config_file.performance.stage2_target_ms,
313                stage3_target_ms: config_file.performance.stage3_target_ms,
314            },
315        };
316
317        Self::validate_config(&config)?;
318        Ok(config)
319    }
320
321    fn convert_to_file_format(
322        config: &ImportanceAssessmentConfig,
323    ) -> ImportanceAssessmentConfigFile {
324        ImportanceAssessmentConfigFile {
325            stage1: Stage1ConfigFile {
326                confidence_threshold: config.stage1.confidence_threshold,
327                max_processing_time_ms: config.stage1.max_processing_time_ms,
328                patterns: config
329                    .stage1
330                    .pattern_library
331                    .iter()
332                    .map(|p| ImportancePatternFile {
333                        name: p.name.clone(),
334                        pattern: p.pattern.clone(),
335                        weight: p.weight,
336                        context_boosters: if p.context_boosters.is_empty() {
337                            None
338                        } else {
339                            Some(p.context_boosters.clone())
340                        },
341                        category: p.category.clone(),
342                    })
343                    .collect(),
344            },
345            stage2: Stage2ConfigFile {
346                confidence_threshold: config.stage2.confidence_threshold,
347                max_processing_time_ms: config.stage2.max_processing_time_ms,
348                embedding_cache_ttl_seconds: config.stage2.embedding_cache_ttl_seconds,
349                embedding_cache_max_size: Some(config.stage2.embedding_cache_max_size),
350                cache_eviction_threshold: Some(config.stage2.cache_eviction_threshold),
351                similarity_threshold: config.stage2.similarity_threshold,
352                reference_embeddings: if config.stage2.reference_embeddings.is_empty() {
353                    None
354                } else {
355                    Some(
356                        config
357                            .stage2
358                            .reference_embeddings
359                            .iter()
360                            .map(|r| ReferenceEmbeddingFile {
361                                name: r.name.clone(),
362                                embedding: r.embedding.clone(),
363                                weight: r.weight,
364                                category: r.category.clone(),
365                            })
366                            .collect(),
367                    )
368                },
369            },
370            stage3: Stage3ConfigFile {
371                max_processing_time_ms: config.stage3.max_processing_time_ms,
372                llm_endpoint: config.stage3.llm_endpoint.clone(),
373                max_concurrent_requests: config.stage3.max_concurrent_requests,
374                prompt_template: config.stage3.prompt_template.clone(),
375                target_usage_percentage: config.stage3.target_usage_percentage,
376            },
377            circuit_breaker: CircuitBreakerConfigFile {
378                failure_threshold: config.circuit_breaker.failure_threshold,
379                failure_window_seconds: config.circuit_breaker.failure_window_seconds,
380                recovery_timeout_seconds: config.circuit_breaker.recovery_timeout_seconds,
381                minimum_requests: config.circuit_breaker.minimum_requests,
382            },
383            performance: PerformanceConfigFile {
384                stage1_target_ms: config.performance.stage1_target_ms,
385                stage2_target_ms: config.performance.stage2_target_ms,
386                stage3_target_ms: config.performance.stage3_target_ms,
387            },
388        }
389    }
390
391    fn validate_config(config: &ImportanceAssessmentConfig) -> Result<()> {
392        // Validate Stage 1
393        if config.stage1.confidence_threshold < 0.0 || config.stage1.confidence_threshold > 1.0 {
394            return Err(anyhow::anyhow!(
395                "Stage 1 confidence threshold must be between 0.0 and 1.0"
396            ));
397        }
398
399        if config.stage1.max_processing_time_ms == 0 {
400            return Err(anyhow::anyhow!(
401                "Stage 1 max processing time must be greater than 0"
402            ));
403        }
404
405        if config.stage1.pattern_library.is_empty() {
406            return Err(anyhow::anyhow!("Stage 1 must have at least one pattern"));
407        }
408
409        for pattern in &config.stage1.pattern_library {
410            if pattern.weight < 0.0 || pattern.weight > 1.0 {
411                return Err(anyhow::anyhow!(
412                    "Pattern '{}' weight must be between 0.0 and 1.0",
413                    pattern.name
414                ));
415            }
416
417            // Test regex compilation
418            if let Err(e) = regex::Regex::new(&pattern.pattern) {
419                return Err(anyhow::anyhow!(
420                    "Pattern '{}' has invalid regex: {}",
421                    pattern.name,
422                    e
423                ));
424            }
425        }
426
427        // Validate Stage 2
428        if config.stage2.confidence_threshold < 0.0 || config.stage2.confidence_threshold > 1.0 {
429            return Err(anyhow::anyhow!(
430                "Stage 2 confidence threshold must be between 0.0 and 1.0"
431            ));
432        }
433
434        if config.stage2.max_processing_time_ms == 0 {
435            return Err(anyhow::anyhow!(
436                "Stage 2 max processing time must be greater than 0"
437            ));
438        }
439
440        if config.stage2.similarity_threshold < 0.0 || config.stage2.similarity_threshold > 1.0 {
441            return Err(anyhow::anyhow!(
442                "Stage 2 similarity threshold must be between 0.0 and 1.0"
443            ));
444        }
445
446        for reference in &config.stage2.reference_embeddings {
447            if reference.weight < 0.0 || reference.weight > 1.0 {
448                return Err(anyhow::anyhow!(
449                    "Reference '{}' weight must be between 0.0 and 1.0",
450                    reference.name
451                ));
452            }
453
454            if reference.embedding.is_empty() {
455                return Err(anyhow::anyhow!(
456                    "Reference '{}' embedding cannot be empty",
457                    reference.name
458                ));
459            }
460        }
461
462        // Validate Stage 3
463        if config.stage3.max_processing_time_ms == 0 {
464            return Err(anyhow::anyhow!(
465                "Stage 3 max processing time must be greater than 0"
466            ));
467        }
468
469        if config.stage3.max_concurrent_requests == 0 {
470            return Err(anyhow::anyhow!(
471                "Stage 3 max concurrent requests must be greater than 0"
472            ));
473        }
474
475        if config.stage3.target_usage_percentage < 0.0
476            || config.stage3.target_usage_percentage > 100.0
477        {
478            return Err(anyhow::anyhow!(
479                "Stage 3 target usage percentage must be between 0.0 and 100.0"
480            ));
481        }
482
483        // Validate Circuit Breaker
484        if config.circuit_breaker.failure_threshold == 0 {
485            return Err(anyhow::anyhow!(
486                "Circuit breaker failure threshold must be greater than 0"
487            ));
488        }
489
490        if config.circuit_breaker.failure_window_seconds == 0 {
491            return Err(anyhow::anyhow!(
492                "Circuit breaker failure window must be greater than 0"
493            ));
494        }
495
496        if config.circuit_breaker.recovery_timeout_seconds == 0 {
497            return Err(anyhow::anyhow!(
498                "Circuit breaker recovery timeout must be greater than 0"
499            ));
500        }
501
502        // Validate Performance
503        if config.performance.stage1_target_ms == 0 {
504            return Err(anyhow::anyhow!(
505                "Stage 1 target time must be greater than 0"
506            ));
507        }
508
509        if config.performance.stage2_target_ms == 0 {
510            return Err(anyhow::anyhow!(
511                "Stage 2 target time must be greater than 0"
512            ));
513        }
514
515        if config.performance.stage3_target_ms == 0 {
516            return Err(anyhow::anyhow!(
517                "Stage 3 target time must be greater than 0"
518            ));
519        }
520
521        // Validate logical constraints
522        if config.stage1.confidence_threshold >= config.stage2.confidence_threshold {
523            warn!(
524                "Stage 1 confidence threshold ({}) should be lower than Stage 2 ({})",
525                config.stage1.confidence_threshold, config.stage2.confidence_threshold
526            );
527        }
528
529        if config.performance.stage1_target_ms >= config.performance.stage2_target_ms {
530            warn!(
531                "Stage 1 target time ({}) should be lower than Stage 2 ({})",
532                config.performance.stage1_target_ms, config.performance.stage2_target_ms
533            );
534        }
535
536        if config.performance.stage2_target_ms >= config.performance.stage3_target_ms {
537            warn!(
538                "Stage 2 target time ({}) should be lower than Stage 3 ({})",
539                config.performance.stage2_target_ms, config.performance.stage3_target_ms
540            );
541        }
542
543        Ok(())
544    }
545}
546
547// File format structures for TOML serialization
548#[derive(Debug, Serialize, Deserialize)]
549struct ImportanceAssessmentConfigFile {
550    stage1: Stage1ConfigFile,
551    stage2: Stage2ConfigFile,
552    stage3: Stage3ConfigFile,
553    circuit_breaker: CircuitBreakerConfigFile,
554    performance: PerformanceConfigFile,
555}
556
557#[derive(Debug, Serialize, Deserialize)]
558struct Stage1ConfigFile {
559    confidence_threshold: f64,
560    max_processing_time_ms: u64,
561    patterns: Vec<ImportancePatternFile>,
562}
563
564#[derive(Debug, Serialize, Deserialize)]
565struct ImportancePatternFile {
566    name: String,
567    pattern: String,
568    weight: f64,
569    context_boosters: Option<Vec<String>>,
570    category: String,
571}
572
573#[derive(Debug, Serialize, Deserialize)]
574struct Stage2ConfigFile {
575    confidence_threshold: f64,
576    max_processing_time_ms: u64,
577    embedding_cache_ttl_seconds: u64,
578    embedding_cache_max_size: Option<usize>,
579    cache_eviction_threshold: Option<f64>,
580    similarity_threshold: f32,
581    reference_embeddings: Option<Vec<ReferenceEmbeddingFile>>,
582}
583
584#[derive(Debug, Serialize, Deserialize)]
585struct ReferenceEmbeddingFile {
586    name: String,
587    embedding: Vec<f32>,
588    weight: f64,
589    category: String,
590}
591
592#[derive(Debug, Serialize, Deserialize)]
593struct Stage3ConfigFile {
594    max_processing_time_ms: u64,
595    llm_endpoint: String,
596    max_concurrent_requests: usize,
597    prompt_template: String,
598    target_usage_percentage: f64,
599}
600
601#[derive(Debug, Serialize, Deserialize)]
602struct CircuitBreakerConfigFile {
603    failure_threshold: usize,
604    failure_window_seconds: u64,
605    recovery_timeout_seconds: u64,
606    minimum_requests: usize,
607}
608
609#[derive(Debug, Serialize, Deserialize)]
610struct PerformanceConfigFile {
611    stage1_target_ms: u64,
612    stage2_target_ms: u64,
613    stage3_target_ms: u64,
614}
615
616#[cfg(test)]
617mod tests {
618    use super::*;
619    use tempfile::NamedTempFile;
620
621    #[test]
622    fn test_production_config_validation() {
623        let config = ImportanceAssessmentConfigLoader::production_config();
624        assert!(ImportanceAssessmentConfigLoader::validate_config(&config).is_ok());
625
626        // Verify production settings
627        assert!(config.stage1.confidence_threshold > 0.6);
628        assert!(config.stage2.confidence_threshold > 0.7);
629        assert!(config.stage3.target_usage_percentage < 20.0);
630        assert!(config.circuit_breaker.failure_threshold <= 5);
631    }
632
633    #[test]
634    fn test_development_config_validation() {
635        let config = ImportanceAssessmentConfigLoader::development_config();
636        assert!(ImportanceAssessmentConfigLoader::validate_config(&config).is_ok());
637
638        // Verify development settings
639        assert!(config.stage1.confidence_threshold <= 0.6);
640        assert!(config.stage2.confidence_threshold <= 0.7);
641        assert!(config.stage3.target_usage_percentage >= 25.0);
642        assert!(config.circuit_breaker.failure_threshold >= 5);
643    }
644
645    #[test]
646    fn test_config_export_import() -> Result<()> {
647        let original_config = ImportanceAssessmentConfigLoader::production_config();
648
649        let temp_file = NamedTempFile::new()?;
650        ImportanceAssessmentConfigLoader::export_to_file(&original_config, temp_file.path())?;
651
652        let loaded_config = ImportanceAssessmentConfigLoader::load_from_file(temp_file.path())?;
653
654        // Verify key settings match
655        assert_eq!(
656            original_config.stage1.confidence_threshold,
657            loaded_config.stage1.confidence_threshold
658        );
659        assert_eq!(
660            original_config.stage2.confidence_threshold,
661            loaded_config.stage2.confidence_threshold
662        );
663        assert_eq!(
664            original_config.stage3.target_usage_percentage,
665            loaded_config.stage3.target_usage_percentage
666        );
667
668        Ok(())
669    }
670
671    #[test]
672    fn test_invalid_config_validation() {
673        let mut config = ImportanceAssessmentConfig::default();
674
675        // Test invalid confidence threshold
676        config.stage1.confidence_threshold = 1.5;
677        assert!(ImportanceAssessmentConfigLoader::validate_config(&config).is_err());
678
679        config.stage1.confidence_threshold = 0.6;
680
681        // Test invalid similarity threshold
682        config.stage2.similarity_threshold = 1.5;
683        assert!(ImportanceAssessmentConfigLoader::validate_config(&config).is_err());
684
685        config.stage2.similarity_threshold = 0.7;
686
687        // Test zero processing time
688        config.stage1.max_processing_time_ms = 0;
689        assert!(ImportanceAssessmentConfigLoader::validate_config(&config).is_err());
690    }
691
692    #[test]
693    fn test_env_config_loading() {
694        // Set test environment variables
695        std::env::set_var("CODEX_STAGE1_CONFIDENCE_THRESHOLD", "0.8");
696        std::env::set_var("CODEX_STAGE2_MAX_TIME_MS", "150");
697        std::env::set_var("CODEX_STAGE3_TARGET_USAGE_PCT", "25.0");
698
699        let config = ImportanceAssessmentConfigLoader::load_from_env().unwrap();
700
701        assert_eq!(config.stage1.confidence_threshold, 0.8);
702        assert_eq!(config.stage2.max_processing_time_ms, 150);
703        assert_eq!(config.stage3.target_usage_percentage, 25.0);
704
705        // Clean up
706        std::env::remove_var("CODEX_STAGE1_CONFIDENCE_THRESHOLD");
707        std::env::remove_var("CODEX_STAGE2_MAX_TIME_MS");
708        std::env::remove_var("CODEX_STAGE3_TARGET_USAGE_PCT");
709    }
710}