1use std::num::NonZeroUsize;
5
6use serde::{Deserialize, Serialize};
7
8use crate::defaults::{default_skill_paths, default_true};
9use crate::learning::LearningConfig;
10use crate::providers::ProviderName;
11use crate::security::TrustConfig;
12
13fn default_disambiguation_threshold() -> f32 {
14 0.20
15}
16
17fn default_rl_learning_rate() -> f32 {
18 0.01
19}
20
21fn default_rl_weight() -> f32 {
22 0.3
23}
24
25fn default_rl_persist_interval() -> u32 {
26 10
27}
28
29fn default_rl_warmup_updates() -> u32 {
30 50
31}
32
33fn default_min_injection_score() -> f32 {
34 0.20
35}
36
37fn default_cosine_weight() -> f32 {
38 0.7
39}
40
41fn default_hybrid_search() -> bool {
42 true
43}
44
45fn default_max_active_skills() -> NonZeroUsize {
46 NonZeroUsize::new(5).expect("5 is non-zero")
47}
48
49fn default_index_watch() -> bool {
50 false
55}
56
57fn default_index_search_enabled() -> bool {
58 true
59}
60
61fn default_index_max_chunks() -> usize {
62 12
63}
64
65fn default_index_concurrency() -> usize {
66 4
67}
68
69fn default_index_batch_size() -> usize {
70 32
71}
72
73fn default_index_memory_batch_size() -> usize {
74 32
75}
76
77fn default_index_max_file_bytes() -> usize {
78 512 * 1024
79}
80
81fn default_index_embed_concurrency() -> usize {
82 2
83}
84
85fn default_index_score_threshold() -> f32 {
86 0.25
87}
88
89fn default_index_budget_ratio() -> f32 {
90 0.40
91}
92
93fn default_index_repo_map_tokens() -> usize {
94 500
95}
96
97fn default_repo_map_ttl_secs() -> u64 {
98 300
99}
100
101fn default_vault_backend() -> VaultBackend {
102 VaultBackend::Env
103}
104
105#[non_exhaustive]
107#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
108#[serde(rename_all = "lowercase")]
109pub enum VaultBackend {
110 #[default]
112 Env,
113 Age,
115 Keyring,
117}
118
119impl std::fmt::Display for VaultBackend {
120 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
121 match self {
122 Self::Env => f.write_str("env"),
123 Self::Age => f.write_str("age"),
124 Self::Keyring => f.write_str("keyring"),
125 }
126 }
127}
128
129fn default_max_daily_cents() -> u32 {
130 0
131}
132
133fn default_otlp_endpoint() -> String {
134 "http://localhost:4317".into()
135}
136
137fn default_pid_file() -> String {
138 "~/.zeph/zeph.pid".into()
139}
140
141fn default_health_interval() -> u64 {
142 30
143}
144
145fn default_max_restart_backoff() -> u64 {
146 60
147}
148
149fn default_scheduler_tick_interval() -> u64 {
150 60
151}
152
153fn default_scheduler_max_tasks() -> usize {
154 100
155}
156
157fn default_scheduler_daemon_tick_secs() -> u64 {
158 60
159}
160
161fn default_scheduler_handler_timeout_secs() -> u64 {
162 300
163}
164
165fn default_scheduler_daemon_shutdown_grace_secs() -> u64 {
166 30
167}
168
169fn default_scheduler_daemon_pid_file() -> String {
170 #[cfg(target_os = "macos")]
172 {
173 dirs::data_local_dir()
174 .map_or_else(
175 || std::path::PathBuf::from("~/.zeph/zeph.pid"),
176 |d| d.join("zeph").join("zeph.pid"),
177 )
178 .to_string_lossy()
179 .into_owned()
180 }
181 #[cfg(not(target_os = "macos"))]
182 {
183 dirs::state_dir()
184 .or_else(dirs::data_local_dir)
185 .map_or_else(
186 || std::path::PathBuf::from("~/.zeph/zeph.pid"),
187 |d| d.join("zeph").join("zeph.pid"),
188 )
189 .to_string_lossy()
190 .into_owned()
191 }
192}
193
194fn default_scheduler_daemon_log_file() -> String {
195 #[cfg(target_os = "macos")]
196 {
197 dirs::cache_dir()
199 .map_or_else(
200 || std::path::PathBuf::from("~/.zeph/zeph.log"),
201 |d| d.join("zeph").join("zeph.log"),
202 )
203 .to_string_lossy()
204 .into_owned()
205 }
206 #[cfg(not(target_os = "macos"))]
207 {
208 dirs::state_dir()
209 .or_else(dirs::data_local_dir)
210 .map_or_else(
211 || std::path::PathBuf::from("~/.zeph/zeph.log"),
212 |d| d.join("zeph").join("zeph.log"),
213 )
214 .to_string_lossy()
215 .into_owned()
216 }
217}
218
219fn default_gateway_bind() -> String {
220 "127.0.0.1".into()
221}
222
223fn default_gateway_port() -> u16 {
224 8090
225}
226
227fn default_gateway_rate_limit() -> u32 {
228 120
229}
230
231fn default_gateway_max_body() -> usize {
232 1_048_576
233}
234
235fn default_gateway_webhook_send_timeout_secs() -> u64 {
236 5
237}
238
239#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize, Serialize)]
241#[serde(rename_all = "lowercase")]
242pub enum SkillPromptMode {
243 Full,
244 Compact,
245 #[default]
246 Auto,
247}
248
249#[allow(clippy::struct_excessive_bools)] #[derive(Debug, Deserialize, Serialize)]
265pub struct SkillsConfig {
266 #[serde(default = "default_skill_paths")]
268 pub paths: Vec<String>,
269 #[serde(default = "default_max_active_skills")]
270 pub max_active_skills: NonZeroUsize,
271 #[serde(default = "default_disambiguation_threshold")]
272 pub disambiguation_threshold: f32,
273 #[serde(default = "default_min_injection_score")]
274 pub min_injection_score: f32,
275 #[serde(default = "default_cosine_weight")]
276 pub cosine_weight: f32,
277 #[serde(default = "default_hybrid_search")]
278 pub hybrid_search: bool,
279 #[serde(default)]
280 pub learning: LearningConfig,
281 #[serde(default)]
282 pub trust: TrustConfig,
283 #[serde(default)]
284 pub prompt_mode: SkillPromptMode,
285 #[serde(default)]
288 pub two_stage_matching: bool,
289 #[serde(default)]
292 pub confusability_threshold: f32,
293
294 #[serde(default)]
297 pub rl_routing_enabled: bool,
298 #[serde(default = "default_rl_learning_rate")]
300 pub rl_learning_rate: f32,
301 #[serde(default = "default_rl_weight")]
303 pub rl_weight: f32,
304 #[serde(default = "default_rl_persist_interval")]
306 pub rl_persist_interval: u32,
307 #[serde(default = "default_rl_warmup_updates")]
309 pub rl_warmup_updates: u32,
310 #[serde(default)]
314 pub rl_embed_dim: Option<usize>,
315
316 #[serde(default)]
319 pub generation_provider: ProviderName,
320 #[serde(default = "default_generation_timeout_ms")]
323 pub generation_timeout_ms: u64,
324 #[serde(default)]
326 pub generation_output_dir: Option<String>,
327 #[serde(default)]
329 pub mining: SkillMiningConfig,
330 #[serde(default)]
332 pub evaluation: SkillEvaluationConfig,
333 #[serde(default)]
335 pub proactive_exploration: ProactiveExplorationConfig,
336 #[serde(default)]
342 pub disambiguate_provider: ProviderName,
343
344 #[serde(default)]
353 pub semantic_scan: bool,
354
355 #[serde(default)]
360 pub semantic_scan_provider: ProviderName,
361}
362
363fn default_generation_timeout_ms() -> u64 {
364 60_000
365}
366
367fn default_skill_quality_threshold() -> f32 {
370 0.60
371}
372
373fn default_weight_correctness() -> f32 {
374 0.50
375}
376
377fn default_weight_reusability() -> f32 {
378 0.25
379}
380
381fn default_weight_specificity() -> f32 {
382 0.25
383}
384
385fn default_eval_fail_open() -> bool {
386 true
387}
388
389fn default_skill_eval_timeout_ms() -> u64 {
390 15_000
391}
392
393#[derive(Debug, Deserialize, Serialize)]
415pub struct SkillEvaluationConfig {
416 #[serde(default)]
418 pub enabled: bool,
419 #[serde(default)]
421 pub provider: ProviderName,
422 #[serde(default = "default_skill_quality_threshold")]
424 pub quality_threshold: f32,
425 #[serde(default = "default_weight_correctness")]
427 pub weight_correctness: f32,
428 #[serde(default = "default_weight_reusability")]
430 pub weight_reusability: f32,
431 #[serde(default = "default_weight_specificity")]
433 pub weight_specificity: f32,
434 #[serde(default = "default_eval_fail_open")]
436 pub fail_open_on_error: bool,
437 #[serde(default = "default_skill_eval_timeout_ms")]
439 pub timeout_ms: u64,
440}
441
442impl Default for SkillEvaluationConfig {
443 fn default() -> Self {
444 Self {
445 enabled: false,
446 provider: ProviderName::default(),
447 quality_threshold: default_skill_quality_threshold(),
448 weight_correctness: default_weight_correctness(),
449 weight_reusability: default_weight_reusability(),
450 weight_specificity: default_weight_specificity(),
451 fail_open_on_error: default_eval_fail_open(),
452 timeout_ms: default_skill_eval_timeout_ms(),
453 }
454 }
455}
456
457fn default_proactive_max_chars() -> usize {
460 8_000
461}
462
463fn default_proactive_timeout_ms() -> u64 {
464 30_000
465}
466
467#[derive(Debug, Deserialize, Serialize)]
484pub struct ProactiveExplorationConfig {
485 #[serde(default)]
487 pub enabled: bool,
488 #[serde(default)]
490 pub provider: ProviderName,
491 #[serde(default)]
493 pub output_dir: Option<String>,
494 #[serde(default = "default_proactive_max_chars")]
496 pub max_chars: usize,
497 #[serde(default = "default_proactive_timeout_ms")]
499 pub timeout_ms: u64,
500 #[serde(default)]
503 pub excluded_domains: Vec<String>,
504}
505
506impl Default for ProactiveExplorationConfig {
507 fn default() -> Self {
508 Self {
509 enabled: false,
510 provider: ProviderName::default(),
511 output_dir: None,
512 max_chars: default_proactive_max_chars(),
513 timeout_ms: default_proactive_timeout_ms(),
514 excluded_domains: Vec::new(),
515 }
516 }
517}
518
519fn default_max_repos_per_query() -> usize {
520 20
521}
522
523fn default_dedup_threshold() -> f32 {
524 0.85
525}
526
527fn default_rate_limit_rpm() -> u32 {
528 25
529}
530
531#[derive(Debug, Deserialize, Serialize)]
533pub struct SkillMiningConfig {
534 #[serde(default)]
536 pub queries: Vec<String>,
537 #[serde(default = "default_max_repos_per_query")]
539 pub max_repos_per_query: usize,
540 #[serde(default = "default_dedup_threshold")]
542 pub dedup_threshold: f32,
543 #[serde(default)]
545 pub output_dir: Option<String>,
546 #[serde(default)]
548 pub generation_provider: ProviderName,
549 #[serde(default)]
551 pub embedding_provider: ProviderName,
552 #[serde(default = "default_rate_limit_rpm")]
554 pub rate_limit_rpm: u32,
555 #[serde(default = "default_mining_generation_timeout_ms")]
557 pub generation_timeout_ms: u64,
558}
559
560impl Default for SkillMiningConfig {
561 fn default() -> Self {
562 Self {
563 queries: Vec::new(),
564 max_repos_per_query: default_max_repos_per_query(),
565 dedup_threshold: default_dedup_threshold(),
566 output_dir: None,
567 generation_provider: ProviderName::default(),
568 embedding_provider: ProviderName::default(),
569 rate_limit_rpm: default_rate_limit_rpm(),
570 generation_timeout_ms: default_mining_generation_timeout_ms(),
571 }
572 }
573}
574
575fn default_mining_generation_timeout_ms() -> u64 {
576 30_000
577}
578
579#[derive(Debug, Deserialize, Serialize)]
595#[allow(clippy::struct_excessive_bools)] pub struct IndexConfig {
597 #[serde(default)]
599 pub enabled: bool,
600 #[serde(default = "default_index_search_enabled")]
602 pub search_enabled: bool,
603 #[serde(default = "default_index_watch")]
604 pub watch: bool,
605 #[serde(default = "default_index_max_chunks")]
606 pub max_chunks: usize,
607 #[serde(default = "default_index_score_threshold")]
608 pub score_threshold: f32,
609 #[serde(default = "default_index_budget_ratio")]
610 pub budget_ratio: f32,
611 #[serde(default = "default_index_repo_map_tokens")]
612 pub repo_map_tokens: usize,
613 #[serde(default = "default_repo_map_ttl_secs")]
614 pub repo_map_ttl_secs: u64,
615 #[serde(default)]
619 pub mcp_enabled: bool,
620 #[serde(default)]
623 pub workspace_root: Option<std::path::PathBuf>,
624 #[serde(default = "default_index_concurrency")]
626 pub concurrency: usize,
627 #[serde(default = "default_index_batch_size")]
629 pub batch_size: usize,
630 #[serde(default = "default_index_memory_batch_size")]
634 pub memory_batch_size: usize,
635 #[serde(default = "default_index_max_file_bytes")]
639 pub max_file_bytes: usize,
640 #[serde(default)]
645 pub embed_provider: Option<ProviderName>,
646 #[serde(default = "default_index_embed_concurrency")]
649 pub embed_concurrency: usize,
650}
651
652impl Default for IndexConfig {
653 fn default() -> Self {
654 Self {
655 enabled: false,
656 search_enabled: default_index_search_enabled(),
657 watch: default_index_watch(),
658 max_chunks: default_index_max_chunks(),
659 score_threshold: default_index_score_threshold(),
660 budget_ratio: default_index_budget_ratio(),
661 repo_map_tokens: default_index_repo_map_tokens(),
662 repo_map_ttl_secs: default_repo_map_ttl_secs(),
663 mcp_enabled: false,
664 workspace_root: None,
665 concurrency: default_index_concurrency(),
666 batch_size: default_index_batch_size(),
667 memory_batch_size: default_index_memory_batch_size(),
668 max_file_bytes: default_index_max_file_bytes(),
669 embed_provider: None,
670 embed_concurrency: default_index_embed_concurrency(),
671 }
672 }
673}
674
675#[derive(Debug, Deserialize, Serialize)]
686pub struct VaultConfig {
687 #[serde(default = "default_vault_backend")]
689 pub backend: VaultBackend,
690}
691
692impl Default for VaultConfig {
693 fn default() -> Self {
694 Self {
695 backend: default_vault_backend(),
696 }
697 }
698}
699
700#[derive(Debug, Deserialize, Serialize)]
714pub struct CostConfig {
715 #[serde(default = "default_true")]
717 pub enabled: bool,
718 #[serde(default = "default_max_daily_cents")]
720 pub max_daily_cents: u32,
721}
722
723impl Default for CostConfig {
724 fn default() -> Self {
725 Self {
726 enabled: true,
727 max_daily_cents: default_max_daily_cents(),
728 }
729 }
730}
731
732#[derive(Debug, Clone, Deserialize, Serialize)]
750pub struct GatewayConfig {
751 #[serde(default)]
753 pub enabled: bool,
754 #[serde(default = "default_gateway_bind")]
756 pub bind: String,
757 #[serde(default = "default_gateway_port")]
759 pub port: u16,
760 #[serde(default)]
763 pub auth_token: Option<String>,
764 #[serde(default = "default_gateway_rate_limit")]
766 pub rate_limit: u32,
767 #[serde(default = "default_gateway_max_body")]
769 pub max_body_size: usize,
770 #[serde(default = "default_gateway_webhook_send_timeout_secs")]
773 pub webhook_send_timeout_secs: u64,
774 #[serde(default)]
788 pub trusted_proxy_cidrs: Vec<String>,
789}
790
791impl Default for GatewayConfig {
792 fn default() -> Self {
793 Self {
794 enabled: false,
795 bind: default_gateway_bind(),
796 port: default_gateway_port(),
797 auth_token: None,
798 rate_limit: default_gateway_rate_limit(),
799 max_body_size: default_gateway_max_body(),
800 webhook_send_timeout_secs: default_gateway_webhook_send_timeout_secs(),
801 trusted_proxy_cidrs: Vec::new(),
802 }
803 }
804}
805
806impl GatewayConfig {
807 pub fn validate(&self) -> Result<(), String> {
816 if self.webhook_send_timeout_secs == 0 || self.webhook_send_timeout_secs > 300 {
817 return Err("webhook_send_timeout_secs must be between 1 and 300".to_owned());
818 }
819 if self.max_body_size > 10 * 1024 * 1024 {
820 return Err("max_body_size must be <= 10485760 (10 MiB)".to_owned());
821 }
822 if self.rate_limit == 0 {
823 return Err("rate_limit must be > 0".to_owned());
824 }
825 Ok(())
826 }
827}
828
829#[derive(Debug, Clone, Deserialize, Serialize)]
843pub struct DaemonConfig {
844 #[serde(default)]
846 pub enabled: bool,
847 #[serde(default = "default_pid_file")]
849 pub pid_file: String,
850 #[serde(default = "default_health_interval")]
852 pub health_interval_secs: u64,
853 #[serde(default = "default_max_restart_backoff")]
855 pub max_restart_backoff_secs: u64,
856}
857
858impl Default for DaemonConfig {
859 fn default() -> Self {
860 Self {
861 enabled: false,
862 pid_file: default_pid_file(),
863 health_interval_secs: default_health_interval(),
864 max_restart_backoff_secs: default_max_restart_backoff(),
865 }
866 }
867}
868
869#[derive(Debug, Clone, Deserialize, Serialize)]
896pub struct SchedulerDaemonConfig {
897 #[serde(default = "default_scheduler_daemon_pid_file")]
899 pub pid_file: String,
900 #[serde(default = "default_scheduler_daemon_log_file")]
902 pub log_file: String,
903 #[serde(default = "crate::defaults::default_true")]
906 pub catch_up: bool,
907 #[serde(default = "default_scheduler_daemon_tick_secs")]
909 pub tick_secs: u64,
910 #[serde(default = "default_scheduler_daemon_shutdown_grace_secs")]
913 pub shutdown_grace_secs: u64,
914 #[serde(default = "default_scheduler_handler_timeout_secs")]
917 pub handler_timeout_secs: u64,
918}
919
920impl Default for SchedulerDaemonConfig {
921 fn default() -> Self {
922 Self {
923 pid_file: default_scheduler_daemon_pid_file(),
924 log_file: default_scheduler_daemon_log_file(),
925 catch_up: true,
926 tick_secs: default_scheduler_daemon_tick_secs(),
927 shutdown_grace_secs: default_scheduler_daemon_shutdown_grace_secs(),
928 handler_timeout_secs: default_scheduler_handler_timeout_secs(),
929 }
930 }
931}
932
933#[derive(Debug, Clone, Deserialize, Serialize)]
953pub struct SchedulerConfig {
954 #[serde(default)]
956 pub enabled: bool,
957 #[serde(default = "default_scheduler_tick_interval")]
959 pub tick_interval_secs: u64,
960 #[serde(default = "default_scheduler_max_tasks")]
962 pub max_tasks: usize,
963 #[serde(default)]
965 pub tasks: Vec<ScheduledTaskConfig>,
966 #[serde(default)]
968 pub daemon: SchedulerDaemonConfig,
969}
970
971impl Default for SchedulerConfig {
972 fn default() -> Self {
973 Self {
974 enabled: false,
975 tick_interval_secs: default_scheduler_tick_interval(),
976 max_tasks: default_scheduler_max_tasks(),
977 tasks: Vec::new(),
978 daemon: SchedulerDaemonConfig::default(),
979 }
980 }
981}
982
983#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
987#[serde(rename_all = "snake_case")]
988pub enum ScheduledTaskKind {
989 MemoryCleanup,
990 SkillRefresh,
991 HealthCheck,
992 UpdateCheck,
993 Experiment,
994 Custom(String),
995}
996
997#[derive(Debug, Clone, Deserialize, Serialize)]
1001pub struct ScheduledTaskConfig {
1002 pub name: String,
1004 #[serde(default, skip_serializing_if = "Option::is_none")]
1006 pub cron: Option<String>,
1007 #[serde(default, skip_serializing_if = "Option::is_none")]
1009 pub run_at: Option<String>,
1010 pub kind: ScheduledTaskKind,
1012 #[serde(default)]
1014 pub config: serde_json::Value,
1015}
1016
1017#[cfg(test)]
1018mod tests {
1019 use super::*;
1020
1021 #[test]
1022 fn index_config_defaults() {
1023 let cfg = IndexConfig::default();
1024 assert!(!cfg.enabled);
1025 assert!(cfg.search_enabled);
1026 assert!(!cfg.watch);
1027 assert_eq!(cfg.concurrency, 4);
1028 assert_eq!(cfg.batch_size, 32);
1029 assert!(cfg.workspace_root.is_none());
1030 }
1031
1032 #[test]
1033 fn index_config_serde_roundtrip_with_new_fields() {
1034 let toml = r#"
1035 enabled = true
1036 concurrency = 8
1037 batch_size = 16
1038 workspace_root = "/tmp/myproject"
1039 "#;
1040 let cfg: IndexConfig = toml::from_str(toml).unwrap();
1041 assert!(cfg.enabled);
1042 assert_eq!(cfg.concurrency, 8);
1043 assert_eq!(cfg.batch_size, 16);
1044 assert_eq!(
1045 cfg.workspace_root,
1046 Some(std::path::PathBuf::from("/tmp/myproject"))
1047 );
1048 let serialized = toml::to_string(&cfg).unwrap();
1050 let cfg2: IndexConfig = toml::from_str(&serialized).unwrap();
1051 assert_eq!(cfg2.concurrency, 8);
1052 assert_eq!(cfg2.batch_size, 16);
1053 }
1054
1055 #[test]
1056 fn index_config_backward_compat_old_toml_without_new_fields() {
1057 let toml = "
1060 enabled = true
1061 max_chunks = 20
1062 score_threshold = 0.3
1063 ";
1064 let cfg: IndexConfig = toml::from_str(toml).unwrap();
1065 assert!(cfg.enabled);
1066 assert_eq!(cfg.max_chunks, 20);
1067 assert!(cfg.workspace_root.is_none());
1068 assert_eq!(cfg.concurrency, 4);
1069 assert_eq!(cfg.batch_size, 32);
1070 }
1071
1072 #[test]
1073 fn index_config_workspace_root_none_by_default() {
1074 let cfg: IndexConfig = toml::from_str("enabled = false").unwrap();
1075 assert!(cfg.workspace_root.is_none());
1076 }
1077
1078 #[test]
1079 fn gateway_validate_timeout_zero_is_err() {
1080 let cfg = GatewayConfig {
1081 webhook_send_timeout_secs: 0,
1082 ..GatewayConfig::default()
1083 };
1084 assert!(cfg.validate().is_err());
1085 }
1086
1087 #[test]
1088 fn gateway_validate_timeout_over_limit_is_err() {
1089 let cfg = GatewayConfig {
1090 webhook_send_timeout_secs: 301,
1091 ..GatewayConfig::default()
1092 };
1093 assert!(cfg.validate().is_err());
1094 }
1095
1096 #[test]
1097 fn gateway_validate_max_body_over_limit_is_err() {
1098 let cfg = GatewayConfig {
1099 max_body_size: 10 * 1024 * 1024 + 1,
1100 ..GatewayConfig::default()
1101 };
1102 assert!(cfg.validate().is_err());
1103 }
1104
1105 #[test]
1106 fn gateway_validate_defaults_are_ok() {
1107 assert!(GatewayConfig::default().validate().is_ok());
1108 }
1109
1110 #[test]
1111 fn gateway_validate_rate_limit_zero_is_err() {
1112 let cfg = GatewayConfig {
1113 rate_limit: 0,
1114 ..GatewayConfig::default()
1115 };
1116 assert!(cfg.validate().is_err());
1117 }
1118
1119 #[test]
1120 fn scheduler_config_default_is_disabled() {
1121 let cfg = SchedulerConfig::default();
1122 assert!(
1123 !cfg.enabled,
1124 "scheduler must be opt-in (enabled = false by default)"
1125 );
1126 }
1127}
1128
1129fn default_compression_spectrum_promotion_window() -> usize {
1132 200
1133}
1134
1135fn default_compression_spectrum_min_occurrences() -> u32 {
1136 3
1137}
1138
1139fn default_compression_spectrum_min_sessions() -> u32 {
1140 2
1141}
1142
1143fn default_compression_spectrum_cluster_threshold() -> f32 {
1144 0.85
1145}
1146
1147fn default_retrieval_low_budget_ratio() -> f32 {
1148 0.20
1149}
1150
1151fn default_retrieval_mid_budget_ratio() -> f32 {
1152 0.50
1153}
1154
1155#[derive(Debug, Deserialize, Serialize)]
1171pub struct CompressionSpectrumConfig {
1172 #[serde(default)]
1174 pub enabled: bool,
1175 #[serde(default)]
1177 pub promotion_output_dir: Option<String>,
1178 #[serde(default)]
1180 pub promotion_provider: ProviderName,
1181 #[serde(default = "default_compression_spectrum_promotion_window")]
1184 pub promotion_window: usize,
1185 #[serde(default = "default_compression_spectrum_min_occurrences")]
1188 pub min_occurrences: u32,
1189 #[serde(default = "default_compression_spectrum_min_sessions")]
1191 pub min_sessions: u32,
1192 #[serde(default = "default_compression_spectrum_cluster_threshold")]
1194 pub cluster_threshold: f32,
1195 #[serde(default = "default_retrieval_low_budget_ratio")]
1197 pub retrieval_low_budget_ratio: f32,
1198 #[serde(default = "default_retrieval_mid_budget_ratio")]
1200 pub retrieval_mid_budget_ratio: f32,
1201}
1202
1203impl Default for CompressionSpectrumConfig {
1204 fn default() -> Self {
1205 Self {
1206 enabled: false,
1207 promotion_output_dir: None,
1208 promotion_provider: ProviderName::default(),
1209 promotion_window: default_compression_spectrum_promotion_window(),
1210 min_occurrences: default_compression_spectrum_min_occurrences(),
1211 min_sessions: default_compression_spectrum_min_sessions(),
1212 cluster_threshold: default_compression_spectrum_cluster_threshold(),
1213 retrieval_low_budget_ratio: default_retrieval_low_budget_ratio(),
1214 retrieval_mid_budget_ratio: default_retrieval_mid_budget_ratio(),
1215 }
1216 }
1217}
1218
1219fn default_trace_service_name() -> String {
1220 "zeph".into()
1221}
1222
1223#[derive(Debug, Clone, Deserialize, Serialize)]
1230#[serde(default)]
1231pub struct TraceConfig {
1232 #[serde(default = "default_otlp_endpoint")]
1235 pub otlp_endpoint: String,
1236 #[serde(default = "default_trace_service_name")]
1238 pub service_name: String,
1239 #[serde(default = "default_true")]
1241 pub redact: bool,
1242}
1243
1244impl Default for TraceConfig {
1245 fn default() -> Self {
1246 Self {
1247 otlp_endpoint: default_otlp_endpoint(),
1248 service_name: default_trace_service_name(),
1249 redact: true,
1250 }
1251 }
1252}
1253
1254#[derive(Debug, Clone, Deserialize, Serialize)]
1267#[serde(default)]
1268pub struct DebugConfig {
1269 pub enabled: bool,
1271 #[serde(default = "crate::defaults::default_debug_output_dir")]
1273 pub output_dir: std::path::PathBuf,
1274 pub format: crate::dump_format::DumpFormat,
1276 pub traces: TraceConfig,
1278}
1279
1280impl Default for DebugConfig {
1281 fn default() -> Self {
1282 Self {
1283 enabled: false,
1284 output_dir: super::defaults::default_debug_output_dir(),
1285 format: crate::dump_format::DumpFormat::default(),
1286 traces: TraceConfig::default(),
1287 }
1288 }
1289}