1use std::collections::HashMap;
11use std::path::PathBuf;
12
13use serde::{Deserialize, Serialize};
14
15use crate::providers::ProviderName;
16use zeph_common::SkillTrustLevel;
17
18#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize, Serialize)]
22#[serde(rename_all = "lowercase")]
23pub enum AutonomyLevel {
24 ReadOnly,
26 #[default]
28 Supervised,
29 Full,
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
35#[serde(rename_all = "lowercase")]
36pub enum PermissionAction {
37 Allow,
39 Ask,
41 Deny,
43}
44
45#[derive(Debug, Clone, Deserialize, Serialize)]
47pub struct PermissionRule {
48 pub pattern: String,
50 pub action: PermissionAction,
52}
53
54#[derive(Debug, Clone, Deserialize, Serialize, Default)]
56pub struct PermissionsConfig {
57 #[serde(flatten)]
59 pub tools: HashMap<String, Vec<PermissionRule>>,
60}
61
62fn default_true() -> bool {
65 true
66}
67
68fn default_shell_tools() -> Vec<String> {
69 vec![
70 "bash".to_string(),
71 "shell".to_string(),
72 "terminal".to_string(),
73 ]
74}
75
76fn default_guarded_tools() -> Vec<String> {
77 vec!["fetch".to_string(), "web_scrape".to_string()]
78}
79
80#[derive(Debug, Clone, Deserialize, Serialize)]
82pub struct DestructiveVerifierConfig {
83 #[serde(default = "default_true")]
85 pub enabled: bool,
86 #[serde(default)]
88 pub allowed_paths: Vec<String>,
89 #[serde(default)]
91 pub extra_patterns: Vec<String>,
92 #[serde(default = "default_shell_tools")]
94 pub shell_tools: Vec<String>,
95}
96
97impl Default for DestructiveVerifierConfig {
98 fn default() -> Self {
99 Self {
100 enabled: true,
101 allowed_paths: Vec::new(),
102 extra_patterns: Vec::new(),
103 shell_tools: default_shell_tools(),
104 }
105 }
106}
107
108#[derive(Debug, Clone, Deserialize, Serialize)]
110pub struct InjectionVerifierConfig {
111 #[serde(default = "default_true")]
113 pub enabled: bool,
114 #[serde(default)]
116 pub extra_patterns: Vec<String>,
117 #[serde(default)]
119 pub allowlisted_urls: Vec<String>,
120}
121
122impl Default for InjectionVerifierConfig {
123 fn default() -> Self {
124 Self {
125 enabled: true,
126 extra_patterns: Vec::new(),
127 allowlisted_urls: Vec::new(),
128 }
129 }
130}
131
132#[derive(Debug, Clone, Deserialize, Serialize)]
134pub struct UrlGroundingVerifierConfig {
135 #[serde(default = "default_true")]
137 pub enabled: bool,
138 #[serde(default = "default_guarded_tools")]
140 pub guarded_tools: Vec<String>,
141}
142
143impl Default for UrlGroundingVerifierConfig {
144 fn default() -> Self {
145 Self {
146 enabled: true,
147 guarded_tools: default_guarded_tools(),
148 }
149 }
150}
151
152#[derive(Debug, Clone, Deserialize, Serialize)]
154pub struct FirewallVerifierConfig {
155 #[serde(default = "default_true")]
157 pub enabled: bool,
158 #[serde(default)]
160 pub blocked_paths: Vec<String>,
161 #[serde(default)]
163 pub blocked_env_vars: Vec<String>,
164 #[serde(default)]
166 pub exempt_tools: Vec<String>,
167}
168
169impl Default for FirewallVerifierConfig {
170 fn default() -> Self {
171 Self {
172 enabled: true,
173 blocked_paths: Vec::new(),
174 blocked_env_vars: Vec::new(),
175 exempt_tools: Vec::new(),
176 }
177 }
178}
179
180#[derive(Debug, Clone, Deserialize, Serialize)]
182pub struct PreExecutionVerifierConfig {
183 #[serde(default = "default_true")]
185 pub enabled: bool,
186 #[serde(default)]
188 pub destructive_commands: DestructiveVerifierConfig,
189 #[serde(default)]
191 pub injection_patterns: InjectionVerifierConfig,
192 #[serde(default)]
194 pub url_grounding: UrlGroundingVerifierConfig,
195 #[serde(default)]
197 pub firewall: FirewallVerifierConfig,
198}
199
200impl Default for PreExecutionVerifierConfig {
201 fn default() -> Self {
202 Self {
203 enabled: true,
204 destructive_commands: DestructiveVerifierConfig::default(),
205 injection_patterns: InjectionVerifierConfig::default(),
206 url_grounding: UrlGroundingVerifierConfig::default(),
207 firewall: FirewallVerifierConfig::default(),
208 }
209 }
210}
211
212#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
216#[serde(rename_all = "snake_case")]
217pub enum PolicyEffect {
218 Allow,
220 Deny,
222}
223
224#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
226#[serde(rename_all = "lowercase")]
227pub enum DefaultEffect {
228 Allow,
230 #[default]
232 Deny,
233}
234
235fn default_deny() -> DefaultEffect {
236 DefaultEffect::Deny
237}
238
239#[derive(Debug, Clone, Deserialize, Serialize, Default)]
241pub struct PolicyConfig {
242 #[serde(default)]
244 pub enabled: bool,
245 #[serde(default = "default_deny")]
247 pub default_effect: DefaultEffect,
248 #[serde(default)]
250 pub rules: Vec<PolicyRuleConfig>,
251 pub policy_file: Option<String>,
253}
254
255#[derive(Debug, Clone, Deserialize, Serialize)]
257pub struct PolicyRuleConfig {
258 pub effect: PolicyEffect,
260 pub tool: String,
262 #[serde(default)]
264 pub paths: Vec<String>,
265 #[serde(default)]
267 pub env: Vec<String>,
268 pub trust_level: Option<SkillTrustLevel>,
270 pub args_match: Option<String>,
272 #[serde(default)]
274 pub capabilities: Vec<String>,
275}
276
277#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
281#[serde(rename_all = "kebab-case")]
282pub enum SandboxProfile {
283 ReadOnly,
285 #[default]
287 Workspace,
288 #[serde(rename = "network-allow-all", alias = "network")]
290 NetworkAllowAll,
291 Off,
293}
294
295fn default_sandbox_profile() -> SandboxProfile {
296 SandboxProfile::Workspace
297}
298
299#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
304#[serde(rename_all = "kebab-case")]
305pub enum SandboxBackend {
306 #[default]
308 Auto,
309 Seatbelt,
311 LandlockBwrap,
313 Noop,
315}
316
317#[derive(Debug, Clone, Deserialize, Serialize)]
319pub struct SandboxConfig {
320 #[serde(default)]
322 pub enabled: bool,
323 #[serde(default = "default_sandbox_profile")]
325 pub profile: SandboxProfile,
326 #[serde(default)]
328 pub allow_read: Vec<PathBuf>,
329 #[serde(default)]
331 pub allow_write: Vec<PathBuf>,
332 #[serde(default = "default_true")]
334 pub strict: bool,
335 #[serde(default)]
339 pub backend: SandboxBackend,
340 #[serde(default)]
342 pub denied_domains: Vec<String>,
343 #[serde(default)]
345 pub fail_if_unavailable: bool,
346}
347
348impl Default for SandboxConfig {
349 fn default() -> Self {
350 Self {
351 enabled: false,
352 profile: default_sandbox_profile(),
353 allow_read: Vec::new(),
354 allow_write: Vec::new(),
355 strict: true,
356 backend: SandboxBackend::Auto,
357 denied_domains: Vec::new(),
358 fail_if_unavailable: false,
359 }
360 }
361}
362
363#[derive(Debug, Clone, Deserialize, Serialize)]
367pub struct SecurityFilterConfig {
368 #[serde(default = "default_true")]
370 pub enabled: bool,
371 #[serde(default)]
373 pub extra_patterns: Vec<String>,
374}
375
376impl Default for SecurityFilterConfig {
377 fn default() -> Self {
378 Self {
379 enabled: true,
380 extra_patterns: Vec::new(),
381 }
382 }
383}
384
385#[derive(Debug, Clone, Deserialize, Serialize)]
387pub struct FilterConfig {
388 #[serde(default = "default_true")]
390 pub enabled: bool,
391 #[serde(default)]
393 pub security: SecurityFilterConfig,
394 #[serde(default, skip_serializing_if = "Option::is_none")]
396 pub filters_path: Option<PathBuf>,
397}
398
399impl Default for FilterConfig {
400 fn default() -> Self {
401 Self {
402 enabled: true,
403 security: SecurityFilterConfig::default(),
404 filters_path: None,
405 }
406 }
407}
408
409fn default_overflow_threshold() -> usize {
412 50_000
413}
414
415fn default_retention_days() -> u64 {
416 7
417}
418
419fn default_max_overflow_bytes() -> usize {
420 10 * 1024 * 1024
421}
422
423#[derive(Debug, Clone, Deserialize, Serialize)]
425pub struct OverflowConfig {
426 #[serde(default = "default_overflow_threshold")]
428 pub threshold: usize,
429 #[serde(default = "default_retention_days")]
431 pub retention_days: u64,
432 #[serde(default = "default_max_overflow_bytes")]
434 pub max_overflow_bytes: usize,
435}
436
437impl Default for OverflowConfig {
438 fn default() -> Self {
439 Self {
440 threshold: default_overflow_threshold(),
441 retention_days: default_retention_days(),
442 max_overflow_bytes: default_max_overflow_bytes(),
443 }
444 }
445}
446
447fn default_anomaly_window() -> usize {
448 10
449}
450
451fn default_anomaly_error_threshold() -> f64 {
452 0.5
453}
454
455fn default_anomaly_critical_threshold() -> f64 {
456 0.8
457}
458
459#[derive(Debug, Clone, Deserialize, Serialize)]
461pub struct AnomalyConfig {
462 #[serde(default = "default_true")]
464 pub enabled: bool,
465 #[serde(default = "default_anomaly_window")]
467 pub window_size: usize,
468 #[serde(default = "default_anomaly_error_threshold")]
470 pub error_threshold: f64,
471 #[serde(default = "default_anomaly_critical_threshold")]
473 pub critical_threshold: f64,
474 #[serde(default = "default_true")]
476 pub reasoning_model_warning: bool,
477}
478
479impl Default for AnomalyConfig {
480 fn default() -> Self {
481 Self {
482 enabled: true,
483 window_size: default_anomaly_window(),
484 error_threshold: default_anomaly_error_threshold(),
485 critical_threshold: default_anomaly_critical_threshold(),
486 reasoning_model_warning: true,
487 }
488 }
489}
490
491fn default_cache_ttl_secs() -> u64 {
492 300
493}
494
495#[derive(Debug, Clone, Deserialize, Serialize)]
497pub struct ResultCacheConfig {
498 #[serde(default = "default_true")]
500 pub enabled: bool,
501 #[serde(default = "default_cache_ttl_secs")]
503 pub ttl_secs: u64,
504}
505
506impl Default for ResultCacheConfig {
507 fn default() -> Self {
508 Self {
509 enabled: true,
510 ttl_secs: default_cache_ttl_secs(),
511 }
512 }
513}
514
515fn default_tafc_complexity_threshold() -> f64 {
516 0.6
517}
518
519#[derive(Debug, Clone, Deserialize, Serialize)]
521pub struct TafcConfig {
522 #[serde(default)]
524 pub enabled: bool,
525 #[serde(default = "default_tafc_complexity_threshold")]
527 pub complexity_threshold: f64,
528}
529
530impl Default for TafcConfig {
531 fn default() -> Self {
532 Self {
533 enabled: false,
534 complexity_threshold: default_tafc_complexity_threshold(),
535 }
536 }
537}
538
539impl TafcConfig {
540 #[must_use]
542 pub fn validated(mut self) -> Self {
543 if self.complexity_threshold.is_finite() {
544 self.complexity_threshold = self.complexity_threshold.clamp(0.0, 1.0);
545 } else {
546 self.complexity_threshold = 0.6;
547 }
548 self
549 }
550}
551
552fn default_utility_exempt_tools() -> Vec<String> {
553 vec!["invoke_skill".to_string(), "load_skill".to_string()]
554}
555
556fn default_utility_threshold() -> f32 {
557 0.1
558}
559
560fn default_utility_gain_weight() -> f32 {
561 1.0
562}
563
564fn default_utility_cost_weight() -> f32 {
565 0.5
566}
567
568fn default_utility_redundancy_weight() -> f32 {
569 0.3
570}
571
572fn default_utility_uncertainty_bonus() -> f32 {
573 0.2
574}
575
576#[derive(Debug, Clone, Deserialize, Serialize)]
578#[serde(default)]
579pub struct UtilityScoringConfig {
580 pub enabled: bool,
582 #[serde(default = "default_utility_threshold")]
584 pub threshold: f32,
585 #[serde(default = "default_utility_gain_weight")]
587 pub gain_weight: f32,
588 #[serde(default = "default_utility_cost_weight")]
590 pub cost_weight: f32,
591 #[serde(default = "default_utility_redundancy_weight")]
593 pub redundancy_weight: f32,
594 #[serde(default = "default_utility_uncertainty_bonus")]
596 pub uncertainty_bonus: f32,
597 #[serde(default = "default_utility_exempt_tools")]
599 pub exempt_tools: Vec<String>,
600}
601
602impl Default for UtilityScoringConfig {
603 fn default() -> Self {
604 Self {
605 enabled: false,
606 threshold: default_utility_threshold(),
607 gain_weight: default_utility_gain_weight(),
608 cost_weight: default_utility_cost_weight(),
609 redundancy_weight: default_utility_redundancy_weight(),
610 uncertainty_bonus: default_utility_uncertainty_bonus(),
611 exempt_tools: default_utility_exempt_tools(),
612 }
613 }
614}
615
616impl UtilityScoringConfig {
617 pub fn validate(&self) -> Result<(), String> {
623 let fields = [
624 ("threshold", self.threshold),
625 ("gain_weight", self.gain_weight),
626 ("cost_weight", self.cost_weight),
627 ("redundancy_weight", self.redundancy_weight),
628 ("uncertainty_bonus", self.uncertainty_bonus),
629 ];
630 for (name, val) in fields {
631 if !val.is_finite() {
632 return Err(format!("[tools.utility] {name} must be finite, got {val}"));
633 }
634 if val < 0.0 {
635 return Err(format!("[tools.utility] {name} must be >= 0, got {val}"));
636 }
637 }
638 Ok(())
639 }
640}
641
642#[derive(Debug, Clone, Default, Deserialize, Serialize)]
644pub struct ToolDependency {
645 #[serde(default, skip_serializing_if = "Vec::is_empty")]
647 pub requires: Vec<String>,
648 #[serde(default, skip_serializing_if = "Vec::is_empty")]
650 pub prefers: Vec<String>,
651}
652
653fn default_boost_per_dep() -> f32 {
654 0.15
655}
656
657fn default_max_total_boost() -> f32 {
658 0.2
659}
660
661#[derive(Debug, Clone, Deserialize, Serialize)]
663pub struct DependencyConfig {
664 #[serde(default)]
666 pub enabled: bool,
667 #[serde(default = "default_boost_per_dep")]
669 pub boost_per_dep: f32,
670 #[serde(default = "default_max_total_boost")]
672 pub max_total_boost: f32,
673 #[serde(default)]
675 pub rules: HashMap<String, ToolDependency>,
676}
677
678impl Default for DependencyConfig {
679 fn default() -> Self {
680 Self {
681 enabled: false,
682 boost_per_dep: default_boost_per_dep(),
683 max_total_boost: default_max_total_boost(),
684 rules: HashMap::new(),
685 }
686 }
687}
688
689fn default_retry_max_attempts() -> usize {
690 2
691}
692
693fn default_retry_base_ms() -> u64 {
694 500
695}
696
697fn default_retry_max_ms() -> u64 {
698 5_000
699}
700
701fn default_retry_budget_secs() -> u64 {
702 30
703}
704
705#[derive(Debug, Clone, Deserialize, Serialize)]
707pub struct RetryConfig {
708 #[serde(default = "default_retry_max_attempts")]
710 pub max_attempts: usize,
711 #[serde(default = "default_retry_base_ms")]
713 pub base_ms: u64,
714 #[serde(default = "default_retry_max_ms")]
716 pub max_ms: u64,
717 #[serde(default = "default_retry_budget_secs")]
719 pub budget_secs: u64,
720 #[serde(default)]
723 pub parameter_reformat_provider: ProviderName,
724}
725
726impl Default for RetryConfig {
727 fn default() -> Self {
728 Self {
729 max_attempts: default_retry_max_attempts(),
730 base_ms: default_retry_base_ms(),
731 max_ms: default_retry_max_ms(),
732 budget_secs: default_retry_budget_secs(),
733 parameter_reformat_provider: ProviderName::default(),
734 }
735 }
736}
737
738fn default_adversarial_timeout_ms() -> u64 {
739 3_000
740}
741
742#[derive(Debug, Clone, Deserialize, Serialize)]
744pub struct AdversarialPolicyConfig {
745 #[serde(default)]
747 pub enabled: bool,
748 #[serde(default)]
750 pub policy_provider: ProviderName,
751 pub policy_file: Option<String>,
753 #[serde(default)]
755 pub fail_open: bool,
756 #[serde(default = "default_adversarial_timeout_ms")]
758 pub timeout_ms: u64,
759 #[serde(default = "AdversarialPolicyConfig::default_exempt_tools")]
761 pub exempt_tools: Vec<String>,
762}
763
764impl Default for AdversarialPolicyConfig {
765 fn default() -> Self {
766 Self {
767 enabled: false,
768 policy_provider: ProviderName::default(),
769 policy_file: None,
770 fail_open: false,
771 timeout_ms: default_adversarial_timeout_ms(),
772 exempt_tools: Self::default_exempt_tools(),
773 }
774 }
775}
776
777impl AdversarialPolicyConfig {
778 #[must_use]
779 pub fn default_exempt_tools() -> Vec<String> {
780 vec![
781 "memory_save".into(),
782 "memory_search".into(),
783 "read_overflow".into(),
784 "load_skill".into(),
785 "invoke_skill".into(),
786 "schedule_deferred".into(),
787 ]
788 }
789}
790
791#[derive(Debug, Clone, Default, Deserialize, Serialize)]
796pub struct FileConfig {
797 #[serde(default)]
799 pub deny_read: Vec<String>,
800 #[serde(default)]
802 pub allow_read: Vec<String>,
803}
804
805#[derive(Debug, Clone, Default, Deserialize, Serialize)]
807pub struct AuthorizationConfig {
808 #[serde(default)]
810 pub enabled: bool,
811 #[serde(default)]
813 pub rules: Vec<PolicyRuleConfig>,
814}
815
816#[derive(Debug, Clone, PartialEq, Eq, Default)]
820pub enum AuditDestination {
821 #[default]
823 Stdout,
824 Stderr,
826 File(std::path::PathBuf),
828}
829
830impl AuditDestination {
831 #[must_use]
833 pub fn is_stdout(&self) -> bool {
834 matches!(self, Self::Stdout)
835 }
836}
837
838impl serde::Serialize for AuditDestination {
839 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
840 match self {
841 Self::Stdout => s.serialize_str("stdout"),
842 Self::Stderr => s.serialize_str("stderr"),
843 Self::File(p) => s.serialize_str(&p.display().to_string()),
844 }
845 }
846}
847
848impl<'de> serde::Deserialize<'de> for AuditDestination {
849 fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
850 let s = String::deserialize(d)?;
851 Ok(match s.as_str() {
852 "stdout" => Self::Stdout,
853 "stderr" => Self::Stderr,
854 path => Self::File(std::path::PathBuf::from(path)),
855 })
856 }
857}
858
859#[derive(Debug, Deserialize, Serialize)]
861pub struct AuditConfig {
862 #[serde(default = "default_true")]
864 pub enabled: bool,
865 #[serde(default)]
867 pub destination: AuditDestination,
868 #[serde(default)]
870 pub tool_risk_summary: bool,
871}
872
873impl Default for AuditConfig {
874 fn default() -> Self {
875 Self {
876 enabled: true,
877 destination: AuditDestination::default(),
878 tool_risk_summary: false,
879 }
880 }
881}
882
883fn default_timeout() -> u64 {
884 30
885}
886
887fn default_confirm_patterns() -> Vec<String> {
888 vec![
889 "rm ".into(),
890 "git push -f".into(),
891 "git push --force".into(),
892 "drop table".into(),
893 "drop database".into(),
894 "truncate ".into(),
895 "$(".into(),
896 "`".into(),
897 "<(".into(),
898 ">(".into(),
899 "<<<".into(),
900 "eval ".into(),
901 ]
902}
903
904fn default_max_background_runs() -> usize {
905 8
906}
907
908fn default_background_timeout_secs() -> u64 {
909 1800
910}
911
912#[derive(Debug, Deserialize, Serialize)]
914#[allow(clippy::struct_excessive_bools)]
915pub struct ShellConfig {
916 #[serde(default = "default_timeout")]
918 pub timeout: u64,
919 #[serde(default)]
921 pub blocked_commands: Vec<String>,
922 #[serde(default)]
924 pub allowed_commands: Vec<String>,
925 #[serde(default)]
927 pub allowed_paths: Vec<String>,
928 #[serde(default = "default_true")]
930 pub allow_network: bool,
931 #[serde(default = "default_confirm_patterns")]
933 pub confirm_patterns: Vec<String>,
934 #[serde(default = "ShellConfig::default_env_blocklist")]
936 pub env_blocklist: Vec<String>,
937 #[serde(default)]
939 pub transactional: bool,
940 #[serde(default)]
942 pub transaction_scope: Vec<String>,
943 #[serde(default)]
945 pub auto_rollback: bool,
946 #[serde(default)]
948 pub auto_rollback_exit_codes: Vec<i32>,
949 #[serde(default)]
951 pub snapshot_required: bool,
952 #[serde(default)]
954 pub max_snapshot_bytes: u64,
955 #[serde(default = "default_max_background_runs")]
957 pub max_background_runs: usize,
958 #[serde(default = "default_background_timeout_secs")]
960 pub background_timeout_secs: u64,
961 #[serde(default)]
966 pub risk_chain_threshold: Option<f32>,
967}
968
969impl Default for ShellConfig {
970 fn default() -> Self {
971 Self {
972 timeout: default_timeout(),
973 blocked_commands: Vec::new(),
974 allowed_commands: Vec::new(),
975 allowed_paths: Vec::new(),
976 allow_network: true,
977 confirm_patterns: default_confirm_patterns(),
978 env_blocklist: Self::default_env_blocklist(),
979 transactional: false,
980 transaction_scope: Vec::new(),
981 auto_rollback: false,
982 auto_rollback_exit_codes: Vec::new(),
983 snapshot_required: false,
984 max_snapshot_bytes: 0,
985 max_background_runs: default_max_background_runs(),
986 background_timeout_secs: default_background_timeout_secs(),
987 risk_chain_threshold: None,
988 }
989 }
990}
991
992impl ShellConfig {
993 #[must_use]
995 pub fn default_env_blocklist() -> Vec<String> {
996 vec![
997 "ZEPH_".into(),
998 "AWS_".into(),
999 "AZURE_".into(),
1000 "GCP_".into(),
1001 "GOOGLE_".into(),
1002 "OPENAI_".into(),
1003 "ANTHROPIC_".into(),
1004 "HF_".into(),
1005 "HUGGING".into(),
1006 ]
1007 }
1008}
1009
1010fn default_scrape_timeout() -> u64 {
1011 15
1012}
1013
1014fn default_max_body_bytes() -> usize {
1015 4_194_304
1016}
1017
1018fn default_ipi_filter_threshold() -> f32 {
1019 0.6
1020}
1021
1022#[derive(Debug, Deserialize, Serialize)]
1024pub struct ScrapeConfig {
1025 #[serde(default = "default_scrape_timeout")]
1027 pub timeout: u64,
1028 #[serde(default = "default_max_body_bytes")]
1030 pub max_body_bytes: usize,
1031 #[serde(default)]
1033 pub allowed_domains: Vec<String>,
1034 #[serde(default)]
1036 pub denied_domains: Vec<String>,
1037 #[serde(default = "default_ipi_filter_threshold")]
1040 pub ipi_filter_threshold: f32,
1041}
1042
1043impl Default for ScrapeConfig {
1044 fn default() -> Self {
1045 Self {
1046 timeout: default_scrape_timeout(),
1047 max_body_bytes: default_max_body_bytes(),
1048 allowed_domains: Vec::new(),
1049 denied_domains: Vec::new(),
1050 ipi_filter_threshold: default_ipi_filter_threshold(),
1051 }
1052 }
1053}
1054
1055#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Deserialize, Serialize)]
1057#[serde(rename_all = "kebab-case")]
1058pub enum SpeculationMode {
1059 #[default]
1061 Off,
1062 Decoding,
1064 Pattern,
1066 Both,
1068}
1069
1070#[derive(Debug, Clone, Deserialize, Serialize)]
1072pub struct SpeculativePatternConfig {
1073 #[serde(default)]
1075 pub enabled: bool,
1076 #[serde(default = "default_min_observations")]
1078 pub min_observations: u32,
1079 #[serde(default = "default_half_life_days")]
1081 pub half_life_days: f64,
1082 #[serde(default)]
1084 pub rerank_provider: ProviderName,
1085}
1086
1087fn default_min_observations() -> u32 {
1088 5
1089}
1090
1091fn default_half_life_days() -> f64 {
1092 14.0
1093}
1094
1095impl Default for SpeculativePatternConfig {
1096 fn default() -> Self {
1097 Self {
1098 enabled: false,
1099 min_observations: default_min_observations(),
1100 half_life_days: default_half_life_days(),
1101 rerank_provider: ProviderName::default(),
1102 }
1103 }
1104}
1105
1106#[derive(Debug, Clone, Default, Deserialize, Serialize)]
1108pub struct SpeculativeAllowlistConfig {
1109 #[serde(default)]
1111 pub shell: Vec<String>,
1112}
1113
1114fn default_max_in_flight() -> usize {
1115 4
1116}
1117
1118fn default_confidence_threshold() -> f32 {
1119 0.55
1120}
1121
1122fn default_max_wasted_per_minute() -> u64 {
1123 100
1124}
1125
1126fn default_ttl_seconds() -> u64 {
1127 30
1128}
1129
1130#[derive(Debug, Clone, Deserialize, Serialize)]
1132pub struct SpeculativeConfig {
1133 #[serde(default)]
1135 pub mode: SpeculationMode,
1136 #[serde(default = "default_max_in_flight")]
1138 pub max_in_flight: usize,
1139 #[serde(default = "default_confidence_threshold")]
1141 pub confidence_threshold: f32,
1142 #[serde(default = "default_max_wasted_per_minute")]
1144 pub max_wasted_per_minute: u64,
1145 #[serde(default = "default_ttl_seconds")]
1147 pub ttl_seconds: u64,
1148 #[serde(default = "default_true")]
1150 pub audit: bool,
1151 #[serde(default)]
1153 pub pattern: SpeculativePatternConfig,
1154 #[serde(default)]
1156 pub allowlist: SpeculativeAllowlistConfig,
1157}
1158
1159impl Default for SpeculativeConfig {
1160 fn default() -> Self {
1161 Self {
1162 mode: SpeculationMode::Off,
1163 max_in_flight: default_max_in_flight(),
1164 confidence_threshold: default_confidence_threshold(),
1165 max_wasted_per_minute: default_max_wasted_per_minute(),
1166 ttl_seconds: default_ttl_seconds(),
1167 audit: true,
1168 pattern: SpeculativePatternConfig::default(),
1169 allowlist: SpeculativeAllowlistConfig::default(),
1170 }
1171 }
1172}
1173
1174#[derive(Debug, Clone, Deserialize, Serialize)]
1176#[serde(default)]
1177#[allow(clippy::struct_excessive_bools)]
1178pub struct EgressConfig {
1179 pub enabled: bool,
1181 pub log_blocked: bool,
1183 pub log_response_bytes: bool,
1185 pub log_hosts_to_tui: bool,
1187}
1188
1189impl Default for EgressConfig {
1190 fn default() -> Self {
1191 Self {
1192 enabled: true,
1193 log_blocked: true,
1194 log_response_bytes: true,
1195 log_hosts_to_tui: true,
1196 }
1197 }
1198}
1199
1200fn default_compression_min_lines() -> usize {
1203 10
1204}
1205
1206fn default_compression_max_rules() -> u32 {
1207 200
1208}
1209
1210fn default_regex_compile_timeout_ms() -> u64 {
1211 500
1212}
1213
1214fn default_evolution_min_interval_secs() -> u64 {
1215 3600
1216}
1217
1218#[derive(Debug, Clone, Deserialize, Serialize)]
1233#[serde(default)]
1234pub struct ToolCompressionConfig {
1235 pub enabled: bool,
1237 #[serde(default = "default_compression_min_lines")]
1239 pub min_lines_to_compress: usize,
1240 #[serde(default)]
1242 pub evolution_provider: ProviderName,
1243 #[serde(default = "default_evolution_min_interval_secs")]
1245 pub evolution_min_interval_secs: u64,
1246 #[serde(default = "default_compression_max_rules")]
1248 pub max_rules: u32,
1249 #[serde(default = "default_regex_compile_timeout_ms")]
1251 pub regex_compile_timeout_ms: u64,
1252}
1253
1254impl Default for ToolCompressionConfig {
1255 fn default() -> Self {
1256 Self {
1257 enabled: false,
1258 min_lines_to_compress: default_compression_min_lines(),
1259 evolution_provider: ProviderName::default(),
1260 evolution_min_interval_secs: default_evolution_min_interval_secs(),
1261 max_rules: default_compression_max_rules(),
1262 regex_compile_timeout_ms: default_regex_compile_timeout_ms(),
1263 }
1264 }
1265}
1266
1267#[derive(Debug, Deserialize, Serialize)]
1275pub struct ToolsConfig {
1276 #[serde(default = "default_true")]
1278 pub enabled: bool,
1279 #[serde(default = "default_true")]
1281 pub summarize_output: bool,
1282 #[serde(default)]
1284 pub shell: ShellConfig,
1285 #[serde(default)]
1287 pub scrape: ScrapeConfig,
1288 #[serde(default)]
1290 pub audit: AuditConfig,
1291 #[serde(default)]
1293 pub permissions: Option<PermissionsConfig>,
1294 #[serde(default)]
1296 pub filters: FilterConfig,
1297 #[serde(default)]
1299 pub overflow: OverflowConfig,
1300 #[serde(default)]
1302 pub anomaly: AnomalyConfig,
1303 #[serde(default)]
1305 pub result_cache: ResultCacheConfig,
1306 #[serde(default)]
1308 pub tafc: TafcConfig,
1309 #[serde(default)]
1311 pub dependencies: DependencyConfig,
1312 #[serde(default)]
1314 pub retry: RetryConfig,
1315 #[serde(default)]
1317 pub policy: PolicyConfig,
1318 #[serde(default)]
1320 pub adversarial_policy: AdversarialPolicyConfig,
1321 #[serde(default)]
1323 pub utility: UtilityScoringConfig,
1324 #[serde(default)]
1326 pub file: FileConfig,
1327 #[serde(default)]
1329 pub authorization: AuthorizationConfig,
1330 #[serde(default)]
1332 pub max_tool_calls_per_session: Option<u32>,
1333 #[serde(default)]
1335 pub speculative: SpeculativeConfig,
1336 #[serde(default)]
1338 pub sandbox: SandboxConfig,
1339 #[serde(default)]
1341 pub egress: EgressConfig,
1342 #[serde(default)]
1344 pub compression: ToolCompressionConfig,
1345}
1346
1347impl Default for ToolsConfig {
1348 fn default() -> Self {
1349 Self {
1350 enabled: true,
1351 summarize_output: true,
1352 shell: ShellConfig::default(),
1353 scrape: ScrapeConfig::default(),
1354 audit: AuditConfig::default(),
1355 permissions: None,
1356 filters: FilterConfig::default(),
1357 overflow: OverflowConfig::default(),
1358 anomaly: AnomalyConfig::default(),
1359 result_cache: ResultCacheConfig::default(),
1360 tafc: TafcConfig::default(),
1361 dependencies: DependencyConfig::default(),
1362 retry: RetryConfig::default(),
1363 policy: PolicyConfig::default(),
1364 adversarial_policy: AdversarialPolicyConfig::default(),
1365 utility: UtilityScoringConfig::default(),
1366 file: FileConfig::default(),
1367 authorization: AuthorizationConfig::default(),
1368 max_tool_calls_per_session: None,
1369 speculative: SpeculativeConfig::default(),
1370 sandbox: SandboxConfig::default(),
1371 egress: EgressConfig::default(),
1372 compression: ToolCompressionConfig::default(),
1373 }
1374 }
1375}
1376
1377#[cfg(test)]
1378mod tests {
1379 use super::*;
1380
1381 #[test]
1382 fn deserialize_default_config() {
1383 let toml_str = r#"
1384 enabled = true
1385
1386 [shell]
1387 timeout = 60
1388 blocked_commands = ["rm -rf /", "sudo"]
1389 "#;
1390
1391 let config: ToolsConfig = toml::from_str(toml_str).unwrap();
1392 assert!(config.enabled);
1393 assert_eq!(config.shell.timeout, 60);
1394 assert_eq!(config.shell.blocked_commands.len(), 2);
1395 }
1396
1397 #[test]
1398 fn empty_blocked_commands() {
1399 let config: ToolsConfig = toml::from_str(r"[shell]\ntimeout = 30\n").unwrap_or_default();
1400 assert!(config.enabled);
1401 }
1402
1403 #[test]
1404 fn default_tools_config() {
1405 let config = ToolsConfig::default();
1406 assert!(config.enabled);
1407 assert!(config.summarize_output);
1408 assert_eq!(config.shell.timeout, 30);
1409 assert!(config.shell.blocked_commands.is_empty());
1410 assert!(config.audit.enabled);
1411 }
1412
1413 #[test]
1414 fn audit_destination_serde_roundtrip() {
1415 let cases = [
1416 ("\"stdout\"", AuditDestination::Stdout),
1417 ("\"stderr\"", AuditDestination::Stderr),
1418 (
1419 "\"/var/log/audit.log\"",
1420 AuditDestination::File("/var/log/audit.log".into()),
1421 ),
1422 ];
1423 for (json_str, expected) in cases {
1424 let got: AuditDestination = serde_json::from_str(json_str).unwrap();
1425 assert_eq!(got, expected);
1426 let serialized = serde_json::to_string(&got).unwrap();
1427 let roundtrip: AuditDestination = serde_json::from_str(&serialized).unwrap();
1428 assert_eq!(roundtrip, expected);
1429 }
1430 }
1431
1432 #[test]
1433 fn audit_destination_toml_in_config() {
1434 let cases = [
1435 (
1436 r#"[audit]
1437destination = "stdout""#,
1438 AuditDestination::Stdout,
1439 ),
1440 (
1441 r#"[audit]
1442destination = "stderr""#,
1443 AuditDestination::Stderr,
1444 ),
1445 (
1446 r#"[audit]
1447destination = "/var/log/zeph-audit.log""#,
1448 AuditDestination::File("/var/log/zeph-audit.log".into()),
1449 ),
1450 ];
1451 for (toml_str, expected) in cases {
1452 let config: ToolsConfig = toml::from_str(toml_str).unwrap();
1453 assert_eq!(config.audit.destination, expected);
1454 }
1455 }
1456}