//! Centralized configuration management for all platform services //! //! The ConfigManager handles loading, validating, and caching configurations //! for all platform services. It provides fail-fast validation on startup //! to prevent cascading crashes. use std::collections::HashMap; use std::sync::RwLock; use anyhow::Result; use serde_json::Value; use tracing::{debug, error, info}; /// Central config manager for all platform services /// /// This struct manages loading and validating configs for all platform /// services. It caches loaded configs to avoid repeated parsing and provides /// fail-fast validation on startup. pub struct ConfigManager { /// Cache of loaded configs (JSON representation) /// Key format: "{service_name}.{mode}" (e.g., "orchestrator.solo") cache: RwLock>, /// Current deployment mode mode: String, } impl ConfigManager { /// Creates a new ConfigManager with the current deployment mode pub fn new() -> Self { let mode = std::env::var("PROVISIONING_MODE").unwrap_or_else(|_| "solo".to_string()); Self { cache: RwLock::new(HashMap::new()), mode, } } /// Creates a ConfigManager with a specific mode pub fn with_mode>(mode: S) -> Self { Self { cache: RwLock::new(HashMap::new()), mode: mode.into(), } } /// Loads a service config as JSON (generic, without type information) /// /// This is useful for validation without knowing the concrete type. pub fn load_as_json(&self, service_name: &str) -> Result { let cache_key = format!("{}.{}", service_name, self.mode); // Check cache first { let cache_read = self.cache.read().unwrap(); if let Some(cached) = cache_read.get(&cache_key) { debug!("Config cache hit for {}", cache_key); return Ok(cached.clone()); } } // Cache miss - load from disk debug!("Config cache miss for {} - loading from disk", cache_key); let path = platform_config::find_config_file(service_name, &self.mode).ok_or_else(|| { anyhow::anyhow!("Config file not found for {}.{}", service_name, self.mode) })?; let json_value: Value = platform_config::format::load_config(&path) .map_err(|e| anyhow::anyhow!("Failed to load config for {}: {}", service_name, e))?; // Store in cache { let mut cache_write = self.cache.write().unwrap(); cache_write.insert(cache_key.clone(), json_value.clone()); } Ok(json_value) } /// Validates that ALL known platform services have valid configs /// /// This is the main validation entry point for pre-flight checks. /// Returns Ok if all configs are valid, Err with detailed failure info /// otherwise. pub fn validate_all_services(&self) -> Result<()> { let services = vec![ "orchestrator", "control-center", "mcp-server", "ai-service", "extension-registry", "rag", "vault-service", "provisioning-daemon", ]; info!( "Validating configs for {} services (mode: {})", services.len(), self.mode ); let mut errors = Vec::new(); for service in services { match self.load_as_json(service) { Ok(_) => { debug!("✅ {}.{} config valid", service, self.mode); } Err(e) => { let error_msg = format!("❌ {}.{} config invalid: {}", service, self.mode, e); error!("{}", error_msg); errors.push(error_msg); } } } if !errors.is_empty() { let summary = format!( "Config validation failed for {} services:\n{}", errors.len(), errors.join("\n") ); error!("{}", summary); return Err(anyhow::anyhow!("{}", summary)); } info!("✅ All service configs validated successfully"); Ok(()) } /// Validates a subset of services pub fn validate_services(&self, services: &[&str]) -> Result<()> { debug!( "Validating {} specific services (mode: {})", services.len(), self.mode ); let mut errors = Vec::new(); for service in services { match self.load_as_json(service) { Ok(_) => { debug!("✅ {}.{} config valid", service, self.mode); } Err(e) => { errors.push(format!("❌ {}: {}", service, e)); } } } if !errors.is_empty() { let summary = errors.join("\n"); error!("{}", summary); return Err(anyhow::anyhow!("Validation failed:\n{}", summary)); } Ok(()) } /// Clears the entire config cache pub fn clear_cache(&self) { let mut cache = self.cache.write().unwrap(); cache.clear(); debug!("Config cache cleared"); } /// Invalidates cache for a specific service pub fn invalidate_service(&self, service_name: &str) { let cache_key = format!("{}.{}", service_name, self.mode); let mut cache = self.cache.write().unwrap(); cache.remove(&cache_key); debug!("Invalidated cache for {}", cache_key); } /// Gets the current deployment mode pub fn mode(&self) -> &str { &self.mode } /// Gets the number of cached configs pub fn cache_size(&self) -> usize { self.cache.read().unwrap().len() } } impl Default for ConfigManager { fn default() -> Self { Self::new() } } #[cfg(test)] mod tests { use super::*; #[test] fn test_config_manager_creation() { let mgr = ConfigManager::new(); assert!(!mgr.mode().is_empty()); } #[test] fn test_config_manager_with_mode() { let mgr = ConfigManager::with_mode("test"); assert_eq!(mgr.mode(), "test"); } #[test] fn test_cache_operations() { let mgr = ConfigManager::new(); assert_eq!(mgr.cache_size(), 0); // Clear doesn't fail even when empty mgr.clear_cache(); assert_eq!(mgr.cache_size(), 0); } }