1use anyhow::{anyhow, Context, Result};
28use serde::{Deserialize, Serialize};
29use std::collections::HashMap;
30use std::path::{Path, PathBuf};
31use std::time::Duration;
32
33#[derive(Debug, Default)]
35pub struct ConfigBuilder {
36 nats_url: Option<String>,
37 http_port: Option<u16>,
38 executor_work_root: Option<PathBuf>,
39 log_level: Option<String>,
40 environment: Option<ConfigEnvironment>,
41}
42
43#[derive(Debug, Clone, Copy)]
45pub enum ConfigEnvironment {
46 Development,
47 Production,
48 Testing,
49}
50
51impl ConfigBuilder {
52 pub fn new() -> Self {
54 Self::default()
55 }
56
57 pub fn with_nats_url(mut self, url: impl Into<String>) -> Self {
59 self.nats_url = Some(url.into());
60 self
61 }
62
63 pub fn with_http_port(mut self, port: u16) -> Self {
65 self.http_port = Some(port);
66 self
67 }
68
69 pub fn with_executor_work_root(mut self, path: impl Into<PathBuf>) -> Self {
71 self.executor_work_root = Some(path.into());
72 self
73 }
74
75 pub fn with_log_level(mut self, level: impl Into<String>) -> Self {
77 self.log_level = Some(level.into());
78 self
79 }
80
81 pub fn for_environment(mut self, env: ConfigEnvironment) -> Self {
83 self.environment = Some(env);
84 self
85 }
86
87 pub fn build(self) -> Config {
89 let mut config = match self.environment.unwrap_or(ConfigEnvironment::Development) {
90 ConfigEnvironment::Development => Config::development(),
91 ConfigEnvironment::Production => Config::production(),
92 ConfigEnvironment::Testing => Config::testing(),
93 };
94
95 if let Some(url) = self.nats_url {
97 config.nats.url = url;
98 }
99 if let Some(port) = self.http_port {
100 config.http.port = port;
101 }
102 if let Some(path) = self.executor_work_root {
103 config.executor.work_root = path;
104 }
105 if let Some(level) = self.log_level {
106 config.logging.level = level;
107 }
108
109 config
110 }
111}
112
113pub mod app;
114pub mod behavior;
115pub mod diff;
116pub mod executor;
117pub mod http;
118pub mod manifest;
119pub mod mcp;
120pub mod nats;
121pub mod nats_adapter;
122pub mod observability;
123pub mod shell;
124
125pub use behavior::{BehaviorMode, BehaviorPack, BehaviorPackManager, EnabledCapabilities};
126pub use diff::{BehaviorPackDiff, DiffSummary, RiskLevel};
127pub use executor::{
128 CgroupLimits, ExecutorConfig, ExecutorNatsConfig, LandlockProfile, PolicyDerivations,
129};
130pub use http::HttpConfig;
131pub use mcp::{McpConfig, McpServerConfig};
132pub use nats::NatsConfig;
133pub use nats_adapter::{AdapterConfig as NatsAdapterConfig, QueueConfig as NatsQueueConfig};
134pub use observability::{
135 ClickHouseConfig, CollectorConfig, HyperDxConfig, ObservabilityConfig, PerformanceThresholds,
136 PhoenixConfig, RedactionLevel, SamplingStrategy,
137};
138pub use shell::{ShellConfig, ShellSpecificConfig};
139
140#[derive(Debug, Clone, Serialize, Deserialize, Default)]
142pub struct Config {
143 pub nats: NatsConfig,
145
146 pub nats_adapter: nats_adapter::AdapterConfig,
148
149 pub http: HttpConfig,
151
152 pub executor: ExecutorConfig,
154
155 pub shell: ShellConfig,
157
158 pub logging: LoggingConfig,
160
161 pub metrics: MetricsConfig,
163
164 pub behavior: BehaviorConfig,
166
167 pub monitoring: MonitoringConfig,
169
170 pub core: CoreConfig,
172
173 pub admission: AdmissionConfig,
175
176 pub attestation: AttestationConfig,
178
179 pub mcp: McpConfig,
181
182 pub observability: ObservabilityConfig,
184}
185
186#[derive(Debug, Clone, Serialize, Deserialize)]
188pub struct MonitoringConfig {
189 pub bind_addr: String,
191
192 pub port: u16,
194
195 pub chaos_enabled: bool,
197
198 pub sla_monitoring_enabled: bool,
200
201 pub health_check_interval: u64,
203
204 pub metrics_collection_interval: u64,
206}
207
208#[derive(Debug, Clone, Serialize, Deserialize)]
210pub struct CoreConfig {
211 pub bind_addr: String,
213
214 pub port: u16,
216}
217
218#[derive(Debug, Clone, Serialize, Deserialize)]
220pub struct AdmissionConfig {
221 pub bind_addr: String,
223
224 pub port: u16,
226}
227
228#[derive(Debug, Clone, Serialize, Deserialize, Default)]
230pub struct AttestationConfig {
231 pub enabled: bool,
233}
234
235#[derive(Debug, Clone, Serialize, Deserialize)]
237pub struct LoggingConfig {
238 pub level: String,
240
241 pub json_format: bool,
243
244 pub log_requests: bool,
246
247 pub log_performance: bool,
249
250 pub log_file: Option<PathBuf>,
252
253 pub nats: NatsLoggingConfig,
255}
256
257#[derive(Debug, Clone, Serialize, Deserialize)]
259pub struct NatsLoggingConfig {
260 pub enabled: bool,
262
263 pub buffer_size: usize,
265
266 pub max_retries: u32,
268
269 pub publish_timeout_ms: u64,
271
272 pub target_filters: Vec<String>,
274
275 pub level_filter: Option<String>,
277
278 pub rate_limit: u64,
280
281 pub batch_enabled: bool,
283
284 pub batch_size: usize,
286
287 pub batch_timeout_ms: u64,
289
290 pub include_spans: bool,
292
293 pub include_traces: bool,
295
296 pub fallback_to_console: bool,
298}
299
300#[derive(Debug, Clone, Serialize, Deserialize)]
302pub struct BehaviorConfig {
303 pub config_dir: PathBuf,
305
306 pub default_pack: String,
308
309 pub poll_interval_seconds: u64,
311
312 pub enable_hot_reload: bool,
314
315 pub max_file_size_bytes: u64,
317}
318
319impl Default for BehaviorConfig {
320 fn default() -> Self {
321 Self {
322 config_dir: PathBuf::from("config/behavior"),
323 default_pack: "prod-stable".to_string(),
324 poll_interval_seconds: 5,
325 enable_hot_reload: true,
326 max_file_size_bytes: 1024 * 1024, }
328 }
329}
330
331impl Default for MonitoringConfig {
332 fn default() -> Self {
333 Self {
334 bind_addr: "0.0.0.0".to_string(),
335 port: 8082,
336 chaos_enabled: false,
337 sla_monitoring_enabled: true,
338 health_check_interval: 10,
339 metrics_collection_interval: 15,
340 }
341 }
342}
343
344impl Default for CoreConfig {
345 fn default() -> Self {
346 Self {
347 bind_addr: "0.0.0.0".to_string(),
348 port: 8083,
349 }
350 }
351}
352
353impl Default for AdmissionConfig {
354 fn default() -> Self {
355 Self {
356 bind_addr: "0.0.0.0".to_string(),
357 port: 8080,
358 }
359 }
360}
361
362#[derive(Debug, Clone, Serialize, Deserialize)]
364pub struct MetricsConfig {
365 pub enabled: bool,
367
368 pub prefix: String,
370
371 pub port: Option<u16>,
373
374 pub interval_seconds: u64,
376
377 pub labels: HashMap<String, String>,
379}
380
381impl Default for NatsLoggingConfig {
382 fn default() -> Self {
383 Self {
384 enabled: false,
385 buffer_size: 1000,
386 max_retries: 3,
387 publish_timeout_ms: 1000,
388 target_filters: Vec::new(),
389 level_filter: None,
390 rate_limit: 0, batch_enabled: true,
392 batch_size: 50,
393 batch_timeout_ms: 100,
394 include_spans: true,
395 include_traces: false,
396 fallback_to_console: true,
397 }
398 }
399}
400
401impl NatsLoggingConfig {
402 pub fn development() -> Self {
404 Self {
405 enabled: true,
406 buffer_size: 500, max_retries: 3,
408 publish_timeout_ms: 500,
409 target_filters: vec![
410 "smith".to_string(), "executor".to_string(),
412 "architect".to_string(),
413 ],
414 level_filter: Some("debug".to_string()),
415 rate_limit: 0, batch_enabled: false, batch_size: 10,
418 batch_timeout_ms: 50,
419 include_spans: true,
420 include_traces: true, fallback_to_console: true,
422 }
423 }
424
425 pub fn production() -> Self {
427 Self {
428 enabled: true,
429 buffer_size: 2000, max_retries: 5,
431 publish_timeout_ms: 2000,
432 target_filters: vec![
433 "smith".to_string(),
434 "executor".to_string(),
435 "architect".to_string(),
436 ],
437 level_filter: Some("info".to_string()),
438 rate_limit: 100, batch_enabled: true, batch_size: 100,
441 batch_timeout_ms: 200,
442 include_spans: true,
443 include_traces: false, fallback_to_console: true,
445 }
446 }
447
448 pub fn testing() -> Self {
450 Self {
451 enabled: false, buffer_size: 100,
453 max_retries: 1,
454 publish_timeout_ms: 100,
455 target_filters: Vec::new(),
456 level_filter: Some("warn".to_string()),
457 rate_limit: 0,
458 batch_enabled: false,
459 batch_size: 5,
460 batch_timeout_ms: 10,
461 include_spans: false,
462 include_traces: false,
463 fallback_to_console: true,
464 }
465 }
466}
467
468impl Default for LoggingConfig {
469 fn default() -> Self {
470 Self {
471 level: "info".to_string(),
472 json_format: false,
473 log_requests: true,
474 log_performance: true,
475 log_file: None,
476 nats: NatsLoggingConfig::default(),
477 }
478 }
479}
480
481impl Default for MetricsConfig {
482 fn default() -> Self {
483 Self {
484 enabled: true,
485 prefix: "smith".to_string(),
486 port: Some(9090),
487 interval_seconds: 15,
488 labels: HashMap::new(),
489 }
490 }
491}
492
493impl Config {
494 pub fn builder() -> ConfigBuilder {
496 ConfigBuilder::new()
497 }
498
499 #[cfg(feature = "env")]
504 pub fn from_env() -> Result<Self> {
505 use figment::{
506 providers::{Env, Format, Serialized, Toml},
507 Figment,
508 };
509
510 let mut figment = Figment::from(Serialized::defaults(Config::development()));
511
512 if let Some(config_path) =
513 std::env::var_os("SMITH_CONFIG_FILE").or_else(|| std::env::var_os("SMITH_CONFIG_PATH"))
514 {
515 let path = PathBuf::from(&config_path);
516 if !path.exists() {
517 return Err(anyhow!(
518 "Configuration file specified by SMITH_CONFIG_FILE does not exist: {}",
519 path.display()
520 ));
521 }
522 figment = figment.merge(Toml::file(path));
523 } else if Path::new("smith.toml").exists() {
524 figment = figment.merge(Toml::file("smith.toml")); }
526
527 figment = figment.merge(Env::prefixed("SMITH_").split("_"));
528
529 figment
530 .extract()
531 .context("Failed to load configuration from environment")
532 }
533
534 #[cfg(not(feature = "env"))]
536 pub fn from_env() -> Result<Self> {
537 let mut config = Self::default();
538 Self::apply_all_env_overrides(&mut config)?;
539 Ok(config)
540 }
541
542 #[allow(dead_code)]
544 fn apply_all_env_overrides(config: &mut Config) -> Result<()> {
545 config.apply_nats_env_overrides()?;
546 config.apply_nats_adapter_env_overrides()?;
547 config.apply_http_env_overrides()?;
548 config.apply_executor_env_overrides()?;
549 config.apply_logging_env_overrides()?;
550 config.apply_metrics_env_overrides()?;
551 config.apply_observability_env_overrides()?;
552 Ok(())
553 }
554
555 pub fn apply_env_overrides(&mut self) -> Result<()> {
557 Self::apply_all_env_overrides(self)
558 }
559
560 #[allow(dead_code)]
562 fn apply_nats_env_overrides(&mut self) -> Result<()> {
563 Self::apply_env_string("SMITH_NATS_URL", &mut self.nats.url);
564 Self::apply_env_string(
565 "SMITH_NATS_JETSTREAM_DOMAIN",
566 &mut self.nats.jetstream_domain,
567 );
568 Ok(())
569 }
570
571 fn apply_nats_adapter_env_overrides(&mut self) -> Result<()> {
573 let security = &mut self.nats_adapter.security;
574 Self::apply_env_parse(
575 "SMITH_NATS_ADAPTER_REQUIRE_AUTH",
576 &mut security.require_authentication,
577 )?;
578
579 if let Ok(token) = std::env::var("SMITH_NATS_ADAPTER_AUTH_TOKEN") {
580 security.auth_token = Some(token);
581 }
582 if let Ok(username) = std::env::var("SMITH_NATS_ADAPTER_USERNAME") {
583 security.username = Some(username);
584 }
585 if let Ok(password) = std::env::var("SMITH_NATS_ADAPTER_PASSWORD") {
586 security.password = Some(password);
587 }
588 if let Ok(jwt) = std::env::var("SMITH_NATS_ADAPTER_JWT") {
589 security.jwt_token = Some(jwt);
590 }
591 if let Ok(nkey_seed) = std::env::var("SMITH_NATS_ADAPTER_NKEY_SEED") {
592 security.nkey_seed = Some(nkey_seed);
593 }
594
595 Self::apply_env_parse("SMITH_NATS_ADAPTER_TLS_ENABLED", &mut security.tls.enabled)?;
596 Self::apply_env_parse(
597 "SMITH_NATS_ADAPTER_TLS_REQUIRED",
598 &mut security.tls.required,
599 )?;
600 Self::apply_env_parse(
601 "SMITH_NATS_ADAPTER_TLS_SKIP_VERIFY",
602 &mut security.tls.insecure_skip_verify,
603 )?;
604 if let Ok(server_name) = std::env::var("SMITH_NATS_ADAPTER_TLS_SERVER_NAME") {
605 security.tls.server_name = Some(server_name);
606 }
607
608 if let Ok(allowed_ips) = std::env::var("SMITH_NATS_ADAPTER_ALLOWED_IPS") {
609 security.allowed_ips = allowed_ips
610 .split(',')
611 .map(|s| s.trim().to_string())
612 .filter(|s| !s.is_empty())
613 .collect();
614 }
615
616 let topics = &mut self.nats_adapter.topics;
617 if let Ok(prefix) = std::env::var("SMITH_NATS_ADAPTER_TOPIC_PREFIX") {
618 topics.prefix = prefix;
619 }
620 if let Ok(command) = std::env::var("SMITH_NATS_ADAPTER_COMMAND_SUBJECT") {
621 topics.command_subject = command;
622 }
623 if let Ok(event) = std::env::var("SMITH_NATS_ADAPTER_EVENT_SUBJECT") {
624 topics.event_subject = event;
625 }
626 if let Ok(patterns) = std::env::var("SMITH_NATS_ADAPTER_ALLOWED_PATTERNS") {
627 let values = patterns
628 .split(',')
629 .map(|s| s.trim().to_string())
630 .filter(|s| !s.is_empty())
631 .collect();
632 topics.allowed_patterns = values;
633 }
634
635 let queues = &mut self.nats_adapter.queues;
636 Self::apply_env_parse(
637 "SMITH_NATS_ADAPTER_COMMAND_QUEUE_SIZE",
638 &mut queues.command_queue_size,
639 )?;
640 Self::apply_env_parse(
641 "SMITH_NATS_ADAPTER_EVENT_QUEUE_SIZE",
642 &mut queues.event_queue_size,
643 )?;
644 Self::apply_env_parse(
645 "SMITH_NATS_ADAPTER_PROCESSING_QUEUE_SIZE",
646 &mut queues.processing_queue_size,
647 )?;
648
649 let performance = &mut self.nats_adapter.performance;
650 Self::apply_env_parse(
651 "SMITH_NATS_ADAPTER_MAX_MESSAGES_PER_SECOND",
652 &mut performance.max_messages_per_second,
653 )?;
654 Self::apply_env_parse(
655 "SMITH_NATS_ADAPTER_TARGET_LATENCY_MS",
656 &mut performance.target_latency_ms,
657 )?;
658 Self::apply_env_parse(
659 "SMITH_NATS_ADAPTER_MAX_MESSAGE_SIZE",
660 &mut performance.max_message_size,
661 )?;
662 Self::apply_env_parse(
663 "SMITH_NATS_ADAPTER_CONNECTION_POOL_SIZE",
664 &mut performance.connection_pool_size,
665 )?;
666 Self::apply_env_parse(
667 "SMITH_NATS_ADAPTER_ENABLE_COMPRESSION",
668 &mut performance.enable_compression,
669 )?;
670 Self::apply_env_parse("SMITH_NATS_ADAPTER_BATCH_SIZE", &mut performance.batch_size)?;
671 if let Ok(flush_interval) = std::env::var("SMITH_NATS_ADAPTER_FLUSH_INTERVAL_MS") {
672 let millis: u64 = flush_interval
673 .parse()
674 .context("Invalid SMITH_NATS_ADAPTER_FLUSH_INTERVAL_MS value")?;
675 performance.flush_interval = Duration::from_millis(millis);
676 }
677
678 if let Ok(subject_allow) = std::env::var("SMITH_NATS_ADAPTER_SUBJECT_ALLOW") {
679 let values: Vec<String> = subject_allow
680 .split(',')
681 .map(|s| s.trim().to_string())
682 .filter(|s| !s.is_empty())
683 .collect();
684 if !values.is_empty() {
685 security.subject_permissions.publish_allow = values.clone().into_iter().collect();
686 security.subject_permissions.subscribe_allow = values.into_iter().collect();
687 }
688 }
689
690 let rate_limits = &mut security.rate_limits;
691 Self::apply_env_parse(
692 "SMITH_NATS_ADAPTER_RATE_MESSAGES_PER_SECOND",
693 &mut rate_limits.messages_per_second,
694 )?;
695 Self::apply_env_parse(
696 "SMITH_NATS_ADAPTER_RATE_BYTES_PER_SECOND",
697 &mut rate_limits.bytes_per_second,
698 )?;
699 Self::apply_env_parse(
700 "SMITH_NATS_ADAPTER_RATE_MAX_SUBSCRIPTIONS",
701 &mut rate_limits.max_subscriptions,
702 )?;
703 Self::apply_env_parse(
704 "SMITH_NATS_ADAPTER_RATE_MAX_PAYLOAD",
705 &mut rate_limits.max_payload_size,
706 )?;
707
708 Ok(())
709 }
710
711 #[allow(dead_code)]
713 fn apply_http_env_overrides(&mut self) -> Result<()> {
714 Self::apply_env_parse("SMITH_HTTP_PORT", &mut self.http.port)?;
715 Self::apply_env_string("SMITH_HTTP_BIND", &mut self.http.bind_address);
716 Ok(())
717 }
718
719 #[allow(dead_code)]
721 fn apply_executor_env_overrides(&mut self) -> Result<()> {
722 if let Ok(work_root) = std::env::var("SMITH_EXECUTOR_WORK_ROOT") {
723 self.executor.work_root = PathBuf::from(work_root);
724 }
725 Self::apply_env_string("SMITH_EXECUTOR_NODE_NAME", &mut self.executor.node_name);
726 Ok(())
727 }
728
729 #[allow(dead_code)]
731 fn apply_logging_env_overrides(&mut self) -> Result<()> {
732 Self::apply_env_string("SMITH_LOG_LEVEL", &mut self.logging.level);
733 Self::apply_env_parse("SMITH_LOG_JSON", &mut self.logging.json_format)?;
734
735 Self::apply_env_parse("SMITH_LOG_NATS_ENABLED", &mut self.logging.nats.enabled)?;
737 Self::apply_env_parse(
738 "SMITH_LOG_NATS_BUFFER_SIZE",
739 &mut self.logging.nats.buffer_size,
740 )?;
741 Self::apply_env_parse(
742 "SMITH_LOG_NATS_MAX_RETRIES",
743 &mut self.logging.nats.max_retries,
744 )?;
745 Self::apply_env_parse(
746 "SMITH_LOG_NATS_TIMEOUT",
747 &mut self.logging.nats.publish_timeout_ms,
748 )?;
749 Self::apply_env_parse(
750 "SMITH_LOG_NATS_RATE_LIMIT",
751 &mut self.logging.nats.rate_limit,
752 )?;
753 Self::apply_env_parse(
754 "SMITH_LOG_NATS_BATCH_ENABLED",
755 &mut self.logging.nats.batch_enabled,
756 )?;
757 Self::apply_env_parse(
758 "SMITH_LOG_NATS_BATCH_SIZE",
759 &mut self.logging.nats.batch_size,
760 )?;
761 Self::apply_env_parse(
762 "SMITH_LOG_NATS_INCLUDE_SPANS",
763 &mut self.logging.nats.include_spans,
764 )?;
765 Self::apply_env_parse(
766 "SMITH_LOG_NATS_INCLUDE_TRACES",
767 &mut self.logging.nats.include_traces,
768 )?;
769 Self::apply_env_parse(
770 "SMITH_LOG_NATS_FALLBACK_CONSOLE",
771 &mut self.logging.nats.fallback_to_console,
772 )?;
773
774 if let Ok(level_filter) = std::env::var("SMITH_LOG_NATS_LEVEL_FILTER") {
776 self.logging.nats.level_filter = Some(level_filter);
777 }
778
779 if let Ok(filters) = std::env::var("SMITH_LOG_NATS_TARGET_FILTERS") {
781 self.logging.nats.target_filters = filters
782 .split(',')
783 .map(|s| s.trim().to_string())
784 .filter(|s| !s.is_empty())
785 .collect();
786 }
787
788 Ok(())
789 }
790
791 #[allow(dead_code)]
793 fn apply_metrics_env_overrides(&mut self) -> Result<()> {
794 Self::apply_env_parse("SMITH_METRICS_ENABLED", &mut self.metrics.enabled)?;
795 if let Ok(port_str) = std::env::var("SMITH_METRICS_PORT") {
796 let port: u16 = port_str
797 .parse()
798 .context("Invalid SMITH_METRICS_PORT value")?;
799 self.metrics.port = Some(port);
800 }
801 Ok(())
802 }
803
804 #[allow(dead_code)]
806 fn apply_observability_env_overrides(&mut self) -> Result<()> {
807 if let Ok(enabled) = std::env::var("OBSERVABILITY_ENABLED") {
808 self.observability.enabled = enabled
809 .parse()
810 .context("Invalid OBSERVABILITY_ENABLED value")?;
811 }
812 if let Ok(redaction) = std::env::var("OBS_REDACTION_LEVEL") {
813 self.observability.redaction_level = Self::parse_redaction_level(&redaction)?;
814 }
815 if let Ok(service_name) = std::env::var("SMITH_OBSERVABILITY_SERVICE_NAME") {
816 self.observability.service_name = service_name;
817 }
818 if let Ok(service_version) = std::env::var("SMITH_OBSERVABILITY_SERVICE_VERSION") {
819 self.observability.service_version = service_version;
820 }
821 if let Ok(env) = std::env::var("SMITH_OBSERVABILITY_ENVIRONMENT") {
822 self.observability.deployment_environment = env;
823 }
824 Ok(())
825 }
826
827 #[allow(dead_code)]
829 fn parse_redaction_level(value: &str) -> Result<RedactionLevel> {
830 match value {
831 "strict" => Ok(RedactionLevel::Strict),
832 "balanced" => Ok(RedactionLevel::Balanced),
833 "permissive" => Ok(RedactionLevel::Permissive),
834 _ => Err(anyhow::anyhow!(
835 "Invalid OBS_REDACTION_LEVEL: must be 'strict', 'balanced', or 'permissive'"
836 )),
837 }
838 }
839
840 #[allow(dead_code)]
842 fn apply_env_string(var_name: &str, target: &mut String) {
843 if let Ok(value) = std::env::var(var_name) {
844 *target = value;
845 }
846 }
847
848 #[allow(dead_code)]
850 fn apply_env_parse<T>(var_name: &str, target: &mut T) -> Result<()>
851 where
852 T: std::str::FromStr,
853 T::Err: std::fmt::Display + Send + Sync + std::error::Error + 'static,
854 {
855 if let Ok(value) = std::env::var(var_name) {
856 *target = value
857 .parse()
858 .with_context(|| format!("Invalid {} value", var_name))?;
859 }
860 Ok(())
861 }
862
863 fn validate_port(port: u16, service_name: &str) -> Result<()> {
865 if port < 1024 {
866 return Err(anyhow::anyhow!(
867 "Invalid {} port: {}. Must be between 1024 and 65535",
868 service_name,
869 port
870 ));
871 }
872 Ok(())
873 }
874
875 fn development_bind_addr() -> String {
877 "127.0.0.1".to_string()
878 }
879
880 fn production_bind_addr() -> String {
882 "0.0.0.0".to_string()
883 }
884
885 pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
887 let content = std::fs::read_to_string(path.as_ref())
888 .with_context(|| format!("Failed to read config file: {}", path.as_ref().display()))?;
889
890 let config: Config = toml::from_str(&content)
891 .with_context(|| format!("Failed to parse TOML config: {}", path.as_ref().display()))?;
892
893 config.validate()?;
894 Ok(config)
895 }
896
897 pub fn validate(&self) -> Result<()> {
899 Self::validate_core_services(self)?;
900 Self::validate_platform_services(self)?;
901 Self::validate_system_configs(self)?;
902 Ok(())
903 }
904
905 fn validate_core_services(config: &Config) -> Result<()> {
907 config
908 .nats
909 .validate()
910 .context("NATS configuration validation failed")?;
911 config
912 .http
913 .validate()
914 .context("HTTP configuration validation failed")?;
915 config
916 .executor
917 .validate()
918 .context("Executor configuration validation failed")?;
919 config
920 .shell
921 .validate()
922 .context("Shell configuration validation failed")?;
923 Ok(())
924 }
925
926 fn validate_platform_services(config: &Config) -> Result<()> {
928 config
929 .nats_adapter
930 .validate()
931 .context("NATS adapter configuration validation failed")?;
932 config
933 .monitoring
934 .validate()
935 .context("Monitoring configuration validation failed")?;
936 config
937 .core
938 .validate()
939 .context("Core configuration validation failed")?;
940 config
941 .admission
942 .validate()
943 .context("Admission configuration validation failed")?;
944 config
945 .observability
946 .validate()
947 .context("Observability configuration validation failed")?;
948 Ok(())
949 }
950
951 fn validate_system_configs(config: &Config) -> Result<()> {
953 config
954 .logging
955 .validate()
956 .context("Logging configuration validation failed")?;
957 config
958 .metrics
959 .validate()
960 .context("Metrics configuration validation failed")?;
961 config
962 .behavior
963 .validate()
964 .context("Behavior configuration validation failed")?;
965 Ok(())
966 }
967
968 pub fn development() -> Self {
970 Self::create_environment_config(ConfigEnvironment::Development)
971 }
972
973 pub fn production() -> Self {
975 Self::create_environment_config(ConfigEnvironment::Production)
976 }
977
978 pub fn testing() -> Self {
980 Self::create_environment_config(ConfigEnvironment::Testing)
981 }
982
983 fn create_environment_config(env: ConfigEnvironment) -> Self {
985 match env {
986 ConfigEnvironment::Development => Self {
987 nats: NatsConfig::development(),
988 nats_adapter: nats_adapter::AdapterConfig::development(),
989 http: HttpConfig::development(),
990 executor: ExecutorConfig::development(),
991 shell: ShellConfig::development(),
992 logging: LoggingConfig::development(),
993 metrics: MetricsConfig::development(),
994 behavior: BehaviorConfig::development(),
995 monitoring: MonitoringConfig::development(),
996 core: CoreConfig::development(),
997 admission: AdmissionConfig::development(),
998 attestation: AttestationConfig::development(),
999 mcp: McpConfig::development(),
1000 observability: ObservabilityConfig::development(),
1001 },
1002 ConfigEnvironment::Production => Self {
1003 nats: NatsConfig::production(),
1004 nats_adapter: nats_adapter::AdapterConfig::production(),
1005 http: HttpConfig::production(),
1006 executor: ExecutorConfig::production(),
1007 shell: ShellConfig::production(),
1008 logging: LoggingConfig::production(),
1009 metrics: MetricsConfig::production(),
1010 behavior: BehaviorConfig::production(),
1011 monitoring: MonitoringConfig::production(),
1012 core: CoreConfig::production(),
1013 admission: AdmissionConfig::production(),
1014 attestation: AttestationConfig::production(),
1015 mcp: McpConfig::production(),
1016 observability: ObservabilityConfig::production(),
1017 },
1018 ConfigEnvironment::Testing => Self {
1019 nats: NatsConfig::testing(),
1020 nats_adapter: nats_adapter::AdapterConfig::testing(),
1021 http: HttpConfig::testing(),
1022 executor: ExecutorConfig::testing(),
1023 shell: ShellConfig::testing(),
1024 logging: LoggingConfig::testing(),
1025 metrics: MetricsConfig::testing(),
1026 behavior: BehaviorConfig::testing(),
1027 monitoring: MonitoringConfig::testing(),
1028 core: CoreConfig::testing(),
1029 admission: AdmissionConfig::testing(),
1030 attestation: AttestationConfig::testing(),
1031 mcp: McpConfig::default(), observability: ObservabilityConfig::testing(),
1033 },
1034 }
1035 }
1036}
1037
1038impl LoggingConfig {
1039 pub fn validate(&self) -> Result<()> {
1041 let valid_levels = ["error", "warn", "info", "debug", "trace"];
1042 if !valid_levels.contains(&self.level.as_str()) {
1043 return Err(anyhow::anyhow!(
1044 "Invalid log level: {}. Must be one of: {}",
1045 self.level,
1046 valid_levels.join(", ")
1047 ));
1048 }
1049
1050 if let Some(ref log_file) = self.log_file {
1051 if let Some(parent) = log_file.parent() {
1052 if !parent.exists() {
1053 return Err(anyhow::anyhow!(
1054 "Log file parent directory does not exist: {}",
1055 parent.display()
1056 ));
1057 }
1058 }
1059 }
1060
1061 Ok(())
1062 }
1063
1064 pub fn development() -> Self {
1066 Self {
1067 level: "debug".to_string(),
1068 json_format: false,
1069 log_requests: true,
1070 log_performance: true,
1071 log_file: None,
1072 nats: NatsLoggingConfig::development(),
1073 }
1074 }
1075
1076 pub fn production() -> Self {
1078 Self {
1079 level: "info".to_string(),
1080 json_format: true,
1081 log_requests: false, log_performance: true,
1083 log_file: Some(PathBuf::from("/var/log/smith/smith.log")),
1084 nats: NatsLoggingConfig::production(),
1085 }
1086 }
1087
1088 pub fn testing() -> Self {
1090 Self {
1091 level: "warn".to_string(), json_format: false,
1093 log_requests: false,
1094 log_performance: false,
1095 log_file: None,
1096 nats: NatsLoggingConfig::testing(),
1097 }
1098 }
1099}
1100
1101impl MetricsConfig {
1102 pub fn validate(&self) -> Result<()> {
1104 if self.prefix.is_empty() {
1105 return Err(anyhow::anyhow!("Metrics prefix cannot be empty"));
1106 }
1107
1108 if let Some(port) = self.port {
1109 Config::validate_port(port, "metrics")?;
1110 }
1111
1112 if self.interval_seconds == 0 {
1113 return Err(anyhow::anyhow!("Metrics interval cannot be zero"));
1114 }
1115
1116 if self.interval_seconds > 300 {
1117 tracing::warn!("Metrics interval > 5 minutes may not provide adequate observability");
1118 }
1119
1120 Ok(())
1121 }
1122
1123 pub fn development() -> Self {
1125 Self {
1126 enabled: true,
1127 prefix: "smith_dev".to_string(),
1128 port: Some(9090),
1129 interval_seconds: 5, labels: [("env".to_string(), "development".to_string())]
1131 .into_iter()
1132 .collect(),
1133 }
1134 }
1135
1136 pub fn production() -> Self {
1138 Self {
1139 enabled: true,
1140 prefix: "smith".to_string(),
1141 port: Some(9090),
1142 interval_seconds: 15,
1143 labels: [("env".to_string(), "production".to_string())]
1144 .into_iter()
1145 .collect(),
1146 }
1147 }
1148
1149 pub fn testing() -> Self {
1151 Self {
1152 enabled: false, prefix: "smith_test".to_string(),
1154 port: None,
1155 interval_seconds: 60,
1156 labels: HashMap::new(),
1157 }
1158 }
1159}
1160
1161impl BehaviorConfig {
1162 pub fn validate(&self) -> Result<()> {
1164 if self.default_pack.is_empty() {
1165 return Err(anyhow::anyhow!(
1166 "Default behavior pack name cannot be empty"
1167 ));
1168 }
1169
1170 if self.poll_interval_seconds == 0 {
1171 return Err(anyhow::anyhow!("Poll interval cannot be zero"));
1172 }
1173
1174 if self.poll_interval_seconds > 300 {
1175 tracing::warn!("Poll interval > 5 minutes may cause slow behavior pack updates");
1176 }
1177
1178 if self.max_file_size_bytes == 0 {
1179 return Err(anyhow::anyhow!("Maximum file size cannot be zero"));
1180 }
1181
1182 if self.max_file_size_bytes > 10 * 1024 * 1024 {
1183 tracing::warn!(
1184 "Large maximum file size ({}MB) for behavior packs",
1185 self.max_file_size_bytes / (1024 * 1024)
1186 );
1187 }
1188
1189 Ok(())
1190 }
1191
1192 pub fn development() -> Self {
1194 Self {
1195 config_dir: PathBuf::from("config/behavior"),
1196 default_pack: "eng-alpha".to_string(), poll_interval_seconds: 2, enable_hot_reload: true,
1199 max_file_size_bytes: 1024 * 1024,
1200 }
1201 }
1202
1203 pub fn production() -> Self {
1205 Self {
1206 config_dir: PathBuf::from("config/behavior"),
1207 default_pack: "prod-stable".to_string(),
1208 poll_interval_seconds: 30, enable_hot_reload: false, max_file_size_bytes: 512 * 1024, }
1212 }
1213
1214 pub fn testing() -> Self {
1216 Self {
1217 config_dir: PathBuf::from("config/behavior"),
1218 default_pack: "shadow-test".to_string(), poll_interval_seconds: 60, enable_hot_reload: false,
1221 max_file_size_bytes: 256 * 1024,
1222 }
1223 }
1224}
1225
1226impl MonitoringConfig {
1227 pub fn validate(&self) -> Result<()> {
1229 Config::validate_port(self.port, "monitoring")?;
1230
1231 if self.health_check_interval == 0 {
1232 return Err(anyhow::anyhow!("Health check interval cannot be zero"));
1233 }
1234
1235 if self.metrics_collection_interval == 0 {
1236 return Err(anyhow::anyhow!(
1237 "Metrics collection interval cannot be zero"
1238 ));
1239 }
1240
1241 Ok(())
1242 }
1243
1244 pub fn development() -> Self {
1246 Self {
1247 bind_addr: Config::development_bind_addr(),
1248 port: 8082,
1249 chaos_enabled: true, sla_monitoring_enabled: true,
1251 health_check_interval: 5, metrics_collection_interval: 10,
1253 }
1254 }
1255
1256 pub fn production() -> Self {
1258 Self {
1259 bind_addr: Config::production_bind_addr(),
1260 port: 8082,
1261 chaos_enabled: false, sla_monitoring_enabled: true,
1263 health_check_interval: 15,
1264 metrics_collection_interval: 30,
1265 }
1266 }
1267
1268 pub fn testing() -> Self {
1270 Self {
1271 bind_addr: Config::development_bind_addr(), port: 8082,
1273 chaos_enabled: false,
1274 sla_monitoring_enabled: false, health_check_interval: 60,
1276 metrics_collection_interval: 60,
1277 }
1278 }
1279}
1280
1281impl CoreConfig {
1282 pub fn validate(&self) -> Result<()> {
1284 Config::validate_port(self.port, "core service")?;
1285 Ok(())
1286 }
1287
1288 pub fn development() -> Self {
1290 Self {
1291 bind_addr: Config::development_bind_addr(),
1292 port: 8083,
1293 }
1294 }
1295
1296 pub fn production() -> Self {
1298 Self {
1299 bind_addr: Config::production_bind_addr(),
1300 port: 8083,
1301 }
1302 }
1303
1304 pub fn testing() -> Self {
1306 Self {
1307 bind_addr: Config::development_bind_addr(), port: 8083,
1309 }
1310 }
1311}
1312
1313impl AdmissionConfig {
1314 pub fn validate(&self) -> Result<()> {
1316 Config::validate_port(self.port, "admission service")?;
1317 Ok(())
1318 }
1319
1320 pub fn development() -> Self {
1322 Self {
1323 bind_addr: Config::development_bind_addr(),
1324 port: 8080,
1325 }
1326 }
1327
1328 pub fn production() -> Self {
1330 Self {
1331 bind_addr: Config::production_bind_addr(),
1332 port: 8080,
1333 }
1334 }
1335
1336 pub fn testing() -> Self {
1338 Self {
1339 bind_addr: Config::development_bind_addr(), port: 8080,
1341 }
1342 }
1343}
1344
1345impl AttestationConfig {
1346 pub fn validate(&self) -> Result<()> {
1348 Ok(())
1350 }
1351
1352 pub fn development() -> Self {
1354 Self {
1355 enabled: false, }
1357 }
1358
1359 pub fn production() -> Self {
1361 Self {
1362 enabled: true, }
1364 }
1365
1366 pub fn testing() -> Self {
1368 Self {
1369 enabled: false, }
1371 }
1372}
1373
1374#[cfg(test)]
1375mod tests {
1376 use super::*;
1377 use std::io::Write;
1378 use tempfile::{tempdir, NamedTempFile};
1379
1380 #[test]
1381 fn test_default_config() {
1382 let _config = Config::default();
1383 }
1387
1388 #[test]
1389 fn test_environment_profiles() {
1390 let dev_config = Config::development();
1391 let prod_config = Config::production();
1392 let test_config = Config::testing();
1393
1394 assert_eq!(dev_config.logging.level, "debug");
1400 assert_eq!(prod_config.logging.level, "info");
1401 assert_eq!(test_config.logging.level, "warn");
1402 }
1403
1404 #[test]
1405 fn test_logging_validation() {
1406 let mut config = LoggingConfig::default();
1407
1408 assert!(config.validate().is_ok());
1410
1411 config.level = "invalid".to_string();
1413 assert!(config.validate().is_err());
1414 }
1415
1416 #[test]
1417 fn test_metrics_validation() {
1418 let mut config = MetricsConfig::default();
1419
1420 assert!(config.validate().is_ok());
1422
1423 config.prefix = "".to_string();
1425 assert!(config.validate().is_err());
1426
1427 config.prefix = "smith".to_string();
1429 config.port = Some(80); assert!(config.validate().is_err());
1431 }
1432
1433 #[test]
1434 fn test_config_builder() {
1435 let config = Config::builder()
1436 .with_nats_url("nats://test:4222")
1437 .with_http_port(8080)
1438 .with_log_level("debug")
1439 .for_environment(ConfigEnvironment::Development)
1440 .build();
1441
1442 assert_eq!(config.nats.url, "nats://test:4222");
1443 assert_eq!(config.http.port, 8080);
1444 assert_eq!(config.logging.level, "debug");
1445 }
1446
1447 #[test]
1448 fn test_port_validation() {
1449 assert!(Config::validate_port(8080, "test").is_ok());
1451
1452 assert!(Config::validate_port(80, "test").is_err());
1454 assert!(Config::validate_port(1023, "test").is_err());
1455 }
1456
1457 #[test]
1458 fn test_bind_address_helpers() {
1459 assert_eq!(Config::development_bind_addr(), "127.0.0.1");
1460 assert_eq!(Config::production_bind_addr(), "0.0.0.0");
1461 }
1462
1463 #[test]
1464 fn test_redaction_level_parsing() {
1465 assert!(matches!(
1466 Config::parse_redaction_level("strict"),
1467 Ok(RedactionLevel::Strict)
1468 ));
1469 assert!(matches!(
1470 Config::parse_redaction_level("balanced"),
1471 Ok(RedactionLevel::Balanced)
1472 ));
1473 assert!(matches!(
1474 Config::parse_redaction_level("permissive"),
1475 Ok(RedactionLevel::Permissive)
1476 ));
1477 assert!(Config::parse_redaction_level("invalid").is_err());
1478 }
1479
1480 #[test]
1481 fn test_structured_validation() {
1482 let config = Config::development();
1483 assert!(config.validate().is_ok() || config.validate().is_err()); }
1486
1487 #[test]
1490 fn test_config_builder_comprehensive() {
1491 let default_config = ConfigBuilder::new().build();
1493 assert_eq!(default_config.logging.level, "debug"); let config = ConfigBuilder::new()
1497 .with_nats_url("nats://custom:4222")
1498 .with_http_port(9999)
1499 .with_executor_work_root("/custom/path")
1500 .with_log_level("trace")
1501 .for_environment(ConfigEnvironment::Production)
1502 .build();
1503
1504 assert_eq!(config.nats.url, "nats://custom:4222");
1505 assert_eq!(config.http.port, 9999);
1506 assert_eq!(config.executor.work_root, PathBuf::from("/custom/path"));
1507 assert_eq!(config.logging.level, "trace");
1508
1509 let dev_config = ConfigBuilder::new()
1511 .for_environment(ConfigEnvironment::Development)
1512 .build();
1513 assert_eq!(dev_config.logging.level, "debug");
1514
1515 let prod_config = ConfigBuilder::new()
1516 .for_environment(ConfigEnvironment::Production)
1517 .build();
1518 assert_eq!(prod_config.logging.level, "info");
1519
1520 let test_config = ConfigBuilder::new()
1521 .for_environment(ConfigEnvironment::Testing)
1522 .build();
1523 assert_eq!(test_config.logging.level, "warn");
1524 }
1525
1526 #[test]
1527 fn test_config_environment_variations() {
1528 let environments = [
1529 ConfigEnvironment::Development,
1530 ConfigEnvironment::Production,
1531 ConfigEnvironment::Testing,
1532 ];
1533
1534 for env in environments {
1535 let config = Config::create_environment_config(env);
1536
1537 assert!(!config.nats.url.is_empty());
1539 match env {
1541 ConfigEnvironment::Testing => {
1542 assert_eq!(
1543 config.http.port, 0,
1544 "Testing environment should use OS-assigned port"
1545 );
1546 }
1547 _ => {
1548 assert!(
1549 config.http.port > 0,
1550 "Port is {} for environment {:?}",
1551 config.http.port,
1552 env
1553 );
1554 }
1555 }
1556 assert!(!config.logging.level.is_empty());
1557
1558 match env {
1560 ConfigEnvironment::Development => {
1561 assert_eq!(config.logging.level, "debug");
1562 assert!(!config.logging.json_format);
1563 assert!(config.behavior.enable_hot_reload);
1564 assert_eq!(config.behavior.poll_interval_seconds, 2);
1565 }
1566 ConfigEnvironment::Production => {
1567 assert_eq!(config.logging.level, "info");
1568 assert!(config.logging.json_format);
1569 assert!(!config.behavior.enable_hot_reload);
1570 assert_eq!(config.behavior.poll_interval_seconds, 30);
1571 }
1572 ConfigEnvironment::Testing => {
1573 assert_eq!(config.logging.level, "warn");
1574 assert!(!config.logging.json_format);
1575 assert!(!config.behavior.enable_hot_reload);
1576 assert_eq!(config.behavior.poll_interval_seconds, 60);
1577 }
1578 }
1579 }
1580 }
1581
1582 #[test]
1583 fn test_logging_config_comprehensive() {
1584 let dev_config = LoggingConfig::development();
1586 assert_eq!(dev_config.level, "debug");
1587 assert!(!dev_config.json_format);
1588 assert!(dev_config.log_requests);
1589 assert!(dev_config.log_performance);
1590 assert!(dev_config.log_file.is_none());
1591
1592 let prod_config = LoggingConfig::production();
1593 assert_eq!(prod_config.level, "info");
1594 assert!(prod_config.json_format);
1595 assert!(!prod_config.log_requests); assert!(prod_config.log_performance);
1597 assert!(prod_config.log_file.is_some());
1598
1599 let test_config = LoggingConfig::testing();
1600 assert_eq!(test_config.level, "warn");
1601 assert!(!test_config.json_format);
1602 assert!(!test_config.log_requests);
1603 assert!(!test_config.log_performance);
1604 assert!(test_config.log_file.is_none());
1605 }
1606
1607 #[test]
1608 fn test_logging_validation_comprehensive() {
1609 let mut config = LoggingConfig::default();
1610
1611 let valid_levels = ["error", "warn", "info", "debug", "trace"];
1613 for level in &valid_levels {
1614 config.level = level.to_string();
1615 assert!(config.validate().is_ok(), "Level {} should be valid", level);
1616 }
1617
1618 let invalid_levels = ["INVALID", "warning", "ERROR", "DEBUG", "verbose", ""];
1620 for level in &invalid_levels {
1621 config.level = level.to_string();
1622 assert!(
1623 config.validate().is_err(),
1624 "Level {} should be invalid",
1625 level
1626 );
1627 }
1628
1629 config.level = "info".to_string();
1631 config.log_file = Some(PathBuf::from("/nonexistent/directory/log.txt"));
1632 assert!(config.validate().is_err());
1633
1634 let temp_dir = tempdir().unwrap();
1636 let log_file = temp_dir.path().join("test.log");
1637 config.log_file = Some(log_file);
1638 assert!(config.validate().is_ok());
1639 }
1640
1641 #[test]
1642 fn test_nats_logging_config_comprehensive() {
1643 let dev_config = NatsLoggingConfig::development();
1645 assert!(dev_config.enabled);
1646 assert_eq!(dev_config.buffer_size, 500);
1647 assert_eq!(dev_config.level_filter, Some("debug".to_string()));
1648 assert!(!dev_config.batch_enabled); assert!(dev_config.include_traces); let prod_config = NatsLoggingConfig::production();
1653 assert!(prod_config.enabled);
1654 assert_eq!(prod_config.buffer_size, 2000);
1655 assert_eq!(prod_config.level_filter, Some("info".to_string()));
1656 assert_eq!(prod_config.rate_limit, 100);
1657 assert!(prod_config.batch_enabled);
1658 assert!(!prod_config.include_traces); let test_config = NatsLoggingConfig::testing();
1662 assert!(!test_config.enabled); assert_eq!(test_config.level_filter, Some("warn".to_string()));
1664 assert!(!test_config.batch_enabled);
1665 assert!(!test_config.include_spans);
1666 assert!(!test_config.include_traces);
1667
1668 let default_config = NatsLoggingConfig::default();
1670 assert!(!default_config.enabled);
1671 assert_eq!(default_config.buffer_size, 1000);
1672 assert_eq!(default_config.max_retries, 3);
1673 assert_eq!(default_config.publish_timeout_ms, 1000);
1674 assert!(default_config.target_filters.is_empty());
1675 assert_eq!(default_config.level_filter, None);
1676 assert_eq!(default_config.rate_limit, 0);
1677 assert!(default_config.batch_enabled);
1678 assert_eq!(default_config.batch_size, 50);
1679 assert_eq!(default_config.batch_timeout_ms, 100);
1680 assert!(default_config.include_spans);
1681 assert!(!default_config.include_traces);
1682 assert!(default_config.fallback_to_console);
1683 }
1684
1685 #[test]
1686 fn test_metrics_config_comprehensive() {
1687 let dev_config = MetricsConfig::development();
1689 assert!(dev_config.enabled);
1690 assert_eq!(dev_config.prefix, "smith_dev");
1691 assert_eq!(dev_config.port, Some(9090));
1692 assert_eq!(dev_config.interval_seconds, 5);
1693 assert_eq!(
1694 dev_config.labels.get("env"),
1695 Some(&"development".to_string())
1696 );
1697
1698 let prod_config = MetricsConfig::production();
1700 assert!(prod_config.enabled);
1701 assert_eq!(prod_config.prefix, "smith");
1702 assert_eq!(prod_config.port, Some(9090));
1703 assert_eq!(prod_config.interval_seconds, 15);
1704 assert_eq!(
1705 prod_config.labels.get("env"),
1706 Some(&"production".to_string())
1707 );
1708
1709 let test_config = MetricsConfig::testing();
1711 assert!(!test_config.enabled); assert_eq!(test_config.prefix, "smith_test");
1713 assert_eq!(test_config.port, None);
1714 assert_eq!(test_config.interval_seconds, 60);
1715 assert!(test_config.labels.is_empty());
1716 }
1717
1718 #[test]
1719 fn test_metrics_validation_comprehensive() {
1720 let mut config = MetricsConfig::default();
1721
1722 assert!(config.validate().is_ok());
1724
1725 config.prefix = "".to_string();
1727 assert!(config.validate().is_err());
1728
1729 config.prefix = "valid_prefix".to_string();
1731 assert!(config.validate().is_ok());
1732
1733 config.port = Some(0);
1735 assert!(config.validate().is_err());
1736
1737 config.port = Some(1023);
1738 assert!(config.validate().is_err());
1739
1740 config.port = Some(1024);
1741 assert!(config.validate().is_ok());
1742
1743 config.port = Some(65535);
1744 assert!(config.validate().is_ok());
1745
1746 config.port = None;
1748 assert!(config.validate().is_ok());
1749
1750 config.interval_seconds = 0;
1752 assert!(config.validate().is_err());
1753
1754 config.interval_seconds = 1;
1756 assert!(config.validate().is_ok());
1757
1758 config.interval_seconds = 300;
1759 assert!(config.validate().is_ok());
1760
1761 config.interval_seconds = 301;
1763 assert!(config.validate().is_ok());
1764 }
1765
1766 #[test]
1767 fn test_behavior_config_comprehensive() {
1768 let dev_config = BehaviorConfig::development();
1770 assert_eq!(dev_config.default_pack, "eng-alpha");
1771 assert_eq!(dev_config.poll_interval_seconds, 2);
1772 assert!(dev_config.enable_hot_reload);
1773 assert_eq!(dev_config.max_file_size_bytes, 1024 * 1024);
1774
1775 let prod_config = BehaviorConfig::production();
1777 assert_eq!(prod_config.default_pack, "prod-stable");
1778 assert_eq!(prod_config.poll_interval_seconds, 30);
1779 assert!(!prod_config.enable_hot_reload);
1780 assert_eq!(prod_config.max_file_size_bytes, 512 * 1024);
1781
1782 let test_config = BehaviorConfig::testing();
1784 assert_eq!(test_config.default_pack, "shadow-test");
1785 assert_eq!(test_config.poll_interval_seconds, 60);
1786 assert!(!test_config.enable_hot_reload);
1787 assert_eq!(test_config.max_file_size_bytes, 256 * 1024);
1788
1789 let default_config = BehaviorConfig::default();
1791 assert_eq!(default_config.default_pack, "prod-stable");
1792 assert_eq!(default_config.poll_interval_seconds, 5);
1793 assert!(default_config.enable_hot_reload);
1794 assert_eq!(default_config.max_file_size_bytes, 1024 * 1024);
1795 }
1796
1797 #[test]
1798 fn test_behavior_validation_comprehensive() {
1799 let mut config = BehaviorConfig::default();
1800
1801 assert!(config.validate().is_ok());
1803
1804 config.default_pack = "".to_string();
1806 assert!(config.validate().is_err());
1807
1808 config.default_pack = "valid-pack".to_string();
1810 assert!(config.validate().is_ok());
1811
1812 config.poll_interval_seconds = 0;
1814 assert!(config.validate().is_err());
1815
1816 config.poll_interval_seconds = 1;
1818 assert!(config.validate().is_ok());
1819
1820 config.poll_interval_seconds = 300;
1821 assert!(config.validate().is_ok());
1822
1823 config.poll_interval_seconds = 301;
1825 assert!(config.validate().is_ok());
1826
1827 config.max_file_size_bytes = 0;
1829 assert!(config.validate().is_err());
1830
1831 config.max_file_size_bytes = 1024;
1833 assert!(config.validate().is_ok());
1834
1835 config.max_file_size_bytes = 10 * 1024 * 1024;
1836 assert!(config.validate().is_ok());
1837
1838 config.max_file_size_bytes = 11 * 1024 * 1024;
1840 assert!(config.validate().is_ok());
1841 }
1842
1843 #[test]
1844 fn test_monitoring_config_comprehensive() {
1845 let dev_config = MonitoringConfig::development();
1847 assert_eq!(dev_config.bind_addr, "127.0.0.1");
1848 assert_eq!(dev_config.port, 8082);
1849 assert!(dev_config.chaos_enabled);
1850 assert!(dev_config.sla_monitoring_enabled);
1851 assert_eq!(dev_config.health_check_interval, 5);
1852 assert_eq!(dev_config.metrics_collection_interval, 10);
1853
1854 let prod_config = MonitoringConfig::production();
1856 assert_eq!(prod_config.bind_addr, "0.0.0.0");
1857 assert_eq!(prod_config.port, 8082);
1858 assert!(!prod_config.chaos_enabled);
1859 assert!(prod_config.sla_monitoring_enabled);
1860 assert_eq!(prod_config.health_check_interval, 15);
1861 assert_eq!(prod_config.metrics_collection_interval, 30);
1862
1863 let test_config = MonitoringConfig::testing();
1865 assert_eq!(test_config.bind_addr, "127.0.0.1");
1866 assert_eq!(test_config.port, 8082);
1867 assert!(!test_config.chaos_enabled);
1868 assert!(!test_config.sla_monitoring_enabled);
1869 assert_eq!(test_config.health_check_interval, 60);
1870 assert_eq!(test_config.metrics_collection_interval, 60);
1871 }
1872
1873 #[test]
1874 fn test_monitoring_validation_comprehensive() {
1875 let mut config = MonitoringConfig::default();
1876
1877 assert!(config.validate().is_ok());
1879
1880 config.port = 1023;
1882 assert!(config.validate().is_err());
1883
1884 config.port = 8082;
1885 assert!(config.validate().is_ok());
1886
1887 config.health_check_interval = 0;
1889 assert!(config.validate().is_err());
1890
1891 config.health_check_interval = 1;
1892 assert!(config.validate().is_ok());
1893
1894 config.metrics_collection_interval = 0;
1896 assert!(config.validate().is_err());
1897
1898 config.metrics_collection_interval = 1;
1899 assert!(config.validate().is_ok());
1900 }
1901
1902 #[test]
1903 fn test_core_config_comprehensive() {
1904 let dev_config = CoreConfig::development();
1906 assert_eq!(dev_config.bind_addr, "127.0.0.1");
1907 assert_eq!(dev_config.port, 8083);
1908
1909 let prod_config = CoreConfig::production();
1910 assert_eq!(prod_config.bind_addr, "0.0.0.0");
1911 assert_eq!(prod_config.port, 8083);
1912
1913 let test_config = CoreConfig::testing();
1914 assert_eq!(test_config.bind_addr, "127.0.0.1");
1915 assert_eq!(test_config.port, 8083);
1916
1917 assert!(dev_config.validate().is_ok());
1919 assert!(prod_config.validate().is_ok());
1920 assert!(test_config.validate().is_ok());
1921 }
1922
1923 #[test]
1924 fn test_admission_config_comprehensive() {
1925 let dev_config = AdmissionConfig::development();
1927 assert_eq!(dev_config.bind_addr, "127.0.0.1");
1928 assert_eq!(dev_config.port, 8080);
1929
1930 let prod_config = AdmissionConfig::production();
1931 assert_eq!(prod_config.bind_addr, "0.0.0.0");
1932 assert_eq!(prod_config.port, 8080);
1933
1934 let test_config = AdmissionConfig::testing();
1935 assert_eq!(test_config.bind_addr, "127.0.0.1");
1936 assert_eq!(test_config.port, 8080);
1937
1938 assert!(dev_config.validate().is_ok());
1940 assert!(prod_config.validate().is_ok());
1941 assert!(test_config.validate().is_ok());
1942 }
1943
1944 #[test]
1945 fn test_attestation_config_comprehensive() {
1946 let dev_config = AttestationConfig::development();
1948 assert!(!dev_config.enabled);
1949
1950 let prod_config = AttestationConfig::production();
1951 assert!(prod_config.enabled);
1952
1953 let test_config = AttestationConfig::testing();
1954 assert!(!test_config.enabled);
1955
1956 assert!(dev_config.validate().is_ok());
1958 assert!(prod_config.validate().is_ok());
1959 assert!(test_config.validate().is_ok());
1960
1961 let default_config = AttestationConfig::default();
1963 assert!(!default_config.enabled);
1964 assert!(default_config.validate().is_ok());
1965 }
1966
1967 #[test]
1968 fn test_port_validation_comprehensive() {
1969 assert!(Config::validate_port(1024, "test").is_ok()); assert!(Config::validate_port(65535, "test").is_ok()); assert!(Config::validate_port(0, "test").is_err());
1975 assert!(Config::validate_port(1, "test").is_err());
1976 assert!(Config::validate_port(80, "test").is_err());
1977 assert!(Config::validate_port(443, "test").is_err());
1978 assert!(Config::validate_port(1023, "test").is_err());
1979
1980 let valid_ports = [1024, 3000, 8080, 8443, 9090, 65535];
1982 for port in &valid_ports {
1983 assert!(Config::validate_port(*port, "test").is_ok());
1984 }
1985 }
1986
1987 #[test]
1988 fn test_redaction_level_parsing_comprehensive() {
1989 assert!(matches!(
1991 Config::parse_redaction_level("strict"),
1992 Ok(RedactionLevel::Strict)
1993 ));
1994 assert!(matches!(
1995 Config::parse_redaction_level("balanced"),
1996 Ok(RedactionLevel::Balanced)
1997 ));
1998 assert!(matches!(
1999 Config::parse_redaction_level("permissive"),
2000 Ok(RedactionLevel::Permissive)
2001 ));
2002
2003 assert!(Config::parse_redaction_level("Strict").is_err());
2005 assert!(Config::parse_redaction_level("STRICT").is_err());
2006 assert!(Config::parse_redaction_level("Balanced").is_err());
2007 assert!(Config::parse_redaction_level("BALANCED").is_err());
2008 assert!(Config::parse_redaction_level("Permissive").is_err());
2009 assert!(Config::parse_redaction_level("PERMISSIVE").is_err());
2010
2011 let invalid_values = ["", "invalid", "none", "all", "normal"];
2013 for value in &invalid_values {
2014 assert!(Config::parse_redaction_level(value).is_err());
2015 }
2016 }
2017
2018 #[test]
2019 fn test_config_serialization() {
2020 let config = Config::development();
2021
2022 let json = serde_json::to_string(&config).unwrap();
2024 assert!(!json.is_empty());
2025
2026 let deserialized: Config = serde_json::from_str(&json).unwrap();
2028 assert_eq!(config.logging.level, deserialized.logging.level);
2029 assert_eq!(config.http.port, deserialized.http.port);
2030 assert_eq!(config.nats.url, deserialized.nats.url);
2031 }
2032
2033 #[test]
2034 fn test_config_toml_serialization() {
2035 let config = Config::testing();
2036
2037 let toml_str = toml::to_string(&config).unwrap();
2039 assert!(!toml_str.is_empty());
2040
2041 let deserialized: Config = toml::from_str(&toml_str).unwrap();
2043 assert_eq!(config.logging.level, deserialized.logging.level);
2044 assert_eq!(config.metrics.enabled, deserialized.metrics.enabled);
2045 }
2046
2047 #[test]
2048 fn test_config_from_file_error_handling() {
2049 let result = Config::from_file("/nonexistent/file.toml");
2051 assert!(result.is_err());
2052 let error = result.unwrap_err();
2053 assert!(error.to_string().contains("Failed to read config file"));
2054
2055 let mut temp_file = NamedTempFile::new().unwrap();
2057 writeln!(temp_file, "invalid toml content [unclosed section").unwrap();
2058
2059 let result = Config::from_file(temp_file.path());
2060 assert!(result.is_err());
2061 let error = result.unwrap_err();
2062 assert!(error.to_string().contains("Failed to parse TOML config"));
2063 }
2064
2065 #[test]
2066 fn test_config_from_file_success() {
2067 let mut temp_file = NamedTempFile::new().unwrap();
2069 writeln!(
2070 temp_file,
2071 r#"
2072[http]
2073port = 9999
2074bind_address = "127.0.0.1"
2075
2076[logging]
2077level = "debug"
2078json_format = true
2079 "#
2080 )
2081 .unwrap();
2082
2083 let result = Config::from_file(temp_file.path());
2086
2087 assert!(result.is_err());
2089 let error = result.unwrap_err();
2090 assert!(
2091 error.to_string().contains("Failed to parse TOML config")
2092 || error.to_string().contains("missing field")
2093 );
2094
2095 let default_config = Config::default();
2097
2098 let toml_content = toml::to_string(&default_config).unwrap();
2100 let parsed_config: Config = toml::from_str(&toml_content).unwrap();
2101
2102 assert_eq!(parsed_config.http.port, default_config.http.port);
2103 assert_eq!(parsed_config.logging.level, default_config.logging.level);
2104 }
2105
2106 #[test]
2107 fn test_config_validation_failure_cascade() {
2108 let mut config = Config::default();
2109
2110 config.logging.level = "invalid_level".to_string();
2112 config.metrics.prefix = "".to_string();
2113 config.behavior.default_pack = "".to_string();
2114
2115 let result = config.validate();
2117 assert!(result.is_err());
2118
2119 let error_msg = result.unwrap_err().to_string();
2121 assert!(error_msg.contains("configuration validation failed"));
2122 }
2123
2124 #[test]
2125 fn test_environment_config_consistency() {
2126 let dev = Config::development();
2128 let prod = Config::production();
2129 let test = Config::testing();
2130
2131 assert!(!dev.nats.url.is_empty());
2133 assert!(!prod.nats.url.is_empty());
2134 assert!(!test.nats.url.is_empty());
2135
2136 assert!(dev.http.port > 0);
2137 assert!(prod.http.port > 0);
2138 assert_eq!(test.http.port, 0); assert!(!dev.logging.level.is_empty());
2141 assert!(!prod.logging.level.is_empty());
2142 assert!(!test.logging.level.is_empty());
2143
2144 assert_ne!(dev.logging.level, prod.logging.level);
2146 assert_ne!(prod.logging.level, test.logging.level);
2147 }
2148
2149 #[test]
2150 fn test_config_clone_and_default() {
2151 let config = Config::development();
2152 let cloned = config.clone();
2153
2154 assert_eq!(config.nats.url, cloned.nats.url);
2156 assert_eq!(config.http.port, cloned.http.port);
2157 assert_eq!(config.logging.level, cloned.logging.level);
2158
2159 let default_config = Config::default();
2161 assert!(!default_config.nats.url.is_empty());
2162 assert!(default_config.http.port > 0);
2163 }
2164
2165 #[test]
2166 fn test_config_debug_format() {
2167 let config = Config::testing();
2168 let debug_str = format!("{:?}", config);
2169
2170 assert!(debug_str.contains("Config"));
2172 assert!(debug_str.contains("nats"));
2173 assert!(debug_str.contains("http"));
2174 assert!(debug_str.contains("logging"));
2175 }
2176
2177 #[test]
2178 fn test_config_builder_edge_cases() {
2179 let minimal_config = ConfigBuilder::new().build();
2181 assert_eq!(minimal_config.logging.level, "debug"); let config = ConfigBuilder::new()
2185 .with_nats_url("")
2186 .with_http_port(0)
2187 .with_log_level("")
2188 .build();
2189
2190 assert_eq!(config.nats.url, "");
2192 assert_eq!(config.http.port, 0);
2193 assert_eq!(config.logging.level, "");
2194
2195 assert!(config.validate().is_err());
2197 }
2198
2199 #[test]
2200 #[ignore] fn test_complex_config_scenarios() {
2202 let mut config = Config::development();
2204 config
2205 .metrics
2206 .labels
2207 .insert("datacenter".to_string(), "us-west-2".to_string());
2208 config
2209 .metrics
2210 .labels
2211 .insert("version".to_string(), "v1.2.3".to_string());
2212
2213 assert_eq!(config.metrics.labels.len(), 3); assert!(config.validate().is_ok());
2215
2216 config.logging.nats.enabled = true;
2218 config.logging.nats.target_filters = vec![
2219 "smith".to_string(),
2220 "executor".to_string(),
2221 "custom_module".to_string(),
2222 ];
2223 config.logging.nats.level_filter = Some("trace".to_string());
2224
2225 assert!(config.validate().is_ok());
2226 }
2227}
2228
2229#[cfg(test)]
2231mod simple_coverage_tests;
2232
2233#[cfg(test)]
2234mod env_and_io_tests;
2235
2236#[cfg(test)]
2237mod behavior_tests;
2238
2239#[cfg(test)]
2240mod diff_tests;