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")]
23#[non_exhaustive]
24pub enum AutonomyLevel {
25 ReadOnly,
27 #[default]
29 Supervised,
30 Full,
32}
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
36#[serde(rename_all = "lowercase")]
37#[non_exhaustive]
38pub enum PermissionAction {
39 Allow,
41 Ask,
43 Deny,
45}
46
47#[derive(Debug, Clone, Deserialize, Serialize)]
49pub struct PermissionRule {
50 pub pattern: String,
52 pub action: PermissionAction,
54}
55
56#[derive(Debug, Clone, Deserialize, Serialize, Default)]
58pub struct PermissionsConfig {
59 #[serde(flatten)]
61 pub tools: HashMap<String, Vec<PermissionRule>>,
62}
63
64fn default_true() -> bool {
67 true
68}
69
70fn default_shell_tools() -> Vec<String> {
71 vec![
72 "bash".to_string(),
73 "shell".to_string(),
74 "terminal".to_string(),
75 ]
76}
77
78fn default_guarded_tools() -> Vec<String> {
79 vec!["fetch".to_string(), "web_scrape".to_string()]
80}
81
82#[derive(Debug, Clone, Deserialize, Serialize)]
84pub struct DestructiveVerifierConfig {
85 #[serde(default = "default_true")]
87 pub enabled: bool,
88 #[serde(default)]
90 pub allowed_paths: Vec<String>,
91 #[serde(default)]
93 pub extra_patterns: Vec<String>,
94 #[serde(default = "default_shell_tools")]
96 pub shell_tools: Vec<String>,
97}
98
99impl Default for DestructiveVerifierConfig {
100 fn default() -> Self {
101 Self {
102 enabled: true,
103 allowed_paths: Vec::new(),
104 extra_patterns: Vec::new(),
105 shell_tools: default_shell_tools(),
106 }
107 }
108}
109
110#[derive(Debug, Clone, Deserialize, Serialize)]
112pub struct InjectionVerifierConfig {
113 #[serde(default = "default_true")]
115 pub enabled: bool,
116 #[serde(default)]
118 pub extra_patterns: Vec<String>,
119 #[serde(default)]
121 pub allowlisted_urls: Vec<String>,
122}
123
124impl Default for InjectionVerifierConfig {
125 fn default() -> Self {
126 Self {
127 enabled: true,
128 extra_patterns: Vec::new(),
129 allowlisted_urls: Vec::new(),
130 }
131 }
132}
133
134#[derive(Debug, Clone, Deserialize, Serialize)]
136pub struct UrlGroundingVerifierConfig {
137 #[serde(default = "default_true")]
139 pub enabled: bool,
140 #[serde(default = "default_guarded_tools")]
142 pub guarded_tools: Vec<String>,
143}
144
145impl Default for UrlGroundingVerifierConfig {
146 fn default() -> Self {
147 Self {
148 enabled: true,
149 guarded_tools: default_guarded_tools(),
150 }
151 }
152}
153
154#[derive(Debug, Clone, Deserialize, Serialize)]
156pub struct FirewallVerifierConfig {
157 #[serde(default = "default_true")]
159 pub enabled: bool,
160 #[serde(default)]
162 pub blocked_paths: Vec<String>,
163 #[serde(default)]
165 pub blocked_env_vars: Vec<String>,
166 #[serde(default)]
168 pub exempt_tools: Vec<String>,
169}
170
171impl Default for FirewallVerifierConfig {
172 fn default() -> Self {
173 Self {
174 enabled: true,
175 blocked_paths: Vec::new(),
176 blocked_env_vars: Vec::new(),
177 exempt_tools: Vec::new(),
178 }
179 }
180}
181
182#[derive(Debug, Clone, Deserialize, Serialize)]
184pub struct PreExecutionVerifierConfig {
185 #[serde(default = "default_true")]
187 pub enabled: bool,
188 #[serde(default)]
190 pub destructive_commands: DestructiveVerifierConfig,
191 #[serde(default)]
193 pub injection_patterns: InjectionVerifierConfig,
194 #[serde(default)]
196 pub url_grounding: UrlGroundingVerifierConfig,
197 #[serde(default)]
199 pub firewall: FirewallVerifierConfig,
200}
201
202impl Default for PreExecutionVerifierConfig {
203 fn default() -> Self {
204 Self {
205 enabled: true,
206 destructive_commands: DestructiveVerifierConfig::default(),
207 injection_patterns: InjectionVerifierConfig::default(),
208 url_grounding: UrlGroundingVerifierConfig::default(),
209 firewall: FirewallVerifierConfig::default(),
210 }
211 }
212}
213
214#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
218#[serde(rename_all = "snake_case")]
219#[non_exhaustive]
220pub enum PolicyEffect {
221 Allow,
223 Deny,
225}
226
227#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
229#[serde(rename_all = "lowercase")]
230#[non_exhaustive]
231pub enum DefaultEffect {
232 Allow,
234 #[default]
236 Deny,
237}
238
239fn default_deny() -> DefaultEffect {
240 DefaultEffect::Deny
241}
242
243#[derive(Debug, Clone, Deserialize, Serialize, Default)]
245pub struct PolicyConfig {
246 #[serde(default)]
248 pub enabled: bool,
249 #[serde(default = "default_deny")]
251 pub default_effect: DefaultEffect,
252 #[serde(default)]
254 pub rules: Vec<PolicyRuleConfig>,
255 pub policy_file: Option<String>,
257 #[serde(default)]
259 pub policy_provider: ProviderName,
260}
261
262#[derive(Debug, Clone, Deserialize, Serialize)]
264pub struct PolicyRuleConfig {
265 pub effect: PolicyEffect,
267 pub tool: String,
269 #[serde(default)]
271 pub paths: Vec<String>,
272 #[serde(default)]
274 pub env: Vec<String>,
275 pub trust_level: Option<SkillTrustLevel>,
277 pub args_match: Option<String>,
279 #[serde(default)]
281 pub capabilities: Vec<String>,
282}
283
284#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
288#[serde(rename_all = "kebab-case")]
289#[non_exhaustive]
290pub enum SandboxProfile {
291 ReadOnly,
293 #[default]
295 Workspace,
296 #[serde(rename = "network-allow-all", alias = "network")]
298 NetworkAllowAll,
299 Off,
301}
302
303fn default_sandbox_profile() -> SandboxProfile {
304 SandboxProfile::Workspace
305}
306
307#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
312#[serde(rename_all = "kebab-case")]
313#[non_exhaustive]
314pub enum SandboxBackend {
315 #[default]
317 Auto,
318 Seatbelt,
320 LandlockBwrap,
322 Noop,
324}
325
326#[derive(Debug, Clone, Deserialize, Serialize)]
328pub struct SandboxConfig {
329 #[serde(default)]
331 pub enabled: bool,
332 #[serde(default = "default_sandbox_profile")]
334 pub profile: SandboxProfile,
335 #[serde(default)]
337 pub allow_read: Vec<PathBuf>,
338 #[serde(default)]
340 pub allow_write: Vec<PathBuf>,
341 #[serde(default = "default_true")]
343 pub strict: bool,
344 #[serde(default)]
348 pub backend: SandboxBackend,
349 #[serde(default)]
351 pub denied_domains: Vec<String>,
352 #[serde(default)]
354 pub fail_if_unavailable: bool,
355}
356
357impl Default for SandboxConfig {
358 fn default() -> Self {
359 Self {
360 enabled: false,
361 profile: default_sandbox_profile(),
362 allow_read: Vec::new(),
363 allow_write: Vec::new(),
364 strict: true,
365 backend: SandboxBackend::Auto,
366 denied_domains: Vec::new(),
367 fail_if_unavailable: false,
368 }
369 }
370}
371
372#[derive(Debug, Clone, Deserialize, Serialize)]
376pub struct SecurityFilterConfig {
377 #[serde(default = "default_true")]
379 pub enabled: bool,
380 #[serde(default)]
382 pub extra_patterns: Vec<String>,
383}
384
385impl Default for SecurityFilterConfig {
386 fn default() -> Self {
387 Self {
388 enabled: true,
389 extra_patterns: Vec::new(),
390 }
391 }
392}
393
394#[derive(Debug, Clone, Deserialize, Serialize)]
396pub struct FilterConfig {
397 #[serde(default = "default_true")]
399 pub enabled: bool,
400 #[serde(default)]
402 pub security: SecurityFilterConfig,
403 #[serde(default, skip_serializing_if = "Option::is_none")]
405 pub filters_path: Option<PathBuf>,
406}
407
408impl Default for FilterConfig {
409 fn default() -> Self {
410 Self {
411 enabled: true,
412 security: SecurityFilterConfig::default(),
413 filters_path: None,
414 }
415 }
416}
417
418fn default_overflow_threshold() -> usize {
421 50_000
422}
423
424fn default_retention_days() -> u64 {
425 7
426}
427
428fn default_max_overflow_bytes() -> usize {
429 10 * 1024 * 1024
430}
431
432#[derive(Debug, Clone, Deserialize, Serialize)]
434pub struct OverflowConfig {
435 #[serde(default = "default_overflow_threshold")]
437 pub threshold: usize,
438 #[serde(default = "default_retention_days")]
440 pub retention_days: u64,
441 #[serde(default = "default_max_overflow_bytes")]
443 pub max_overflow_bytes: usize,
444}
445
446impl Default for OverflowConfig {
447 fn default() -> Self {
448 Self {
449 threshold: default_overflow_threshold(),
450 retention_days: default_retention_days(),
451 max_overflow_bytes: default_max_overflow_bytes(),
452 }
453 }
454}
455
456fn default_anomaly_window() -> usize {
457 10
458}
459
460fn default_anomaly_error_threshold() -> f64 {
461 0.5
462}
463
464fn default_anomaly_critical_threshold() -> f64 {
465 0.8
466}
467
468#[derive(Debug, Clone, Deserialize, Serialize)]
470pub struct AnomalyConfig {
471 #[serde(default = "default_true")]
473 pub enabled: bool,
474 #[serde(default = "default_anomaly_window")]
476 pub window_size: usize,
477 #[serde(default = "default_anomaly_error_threshold")]
479 pub error_threshold: f64,
480 #[serde(default = "default_anomaly_critical_threshold")]
482 pub critical_threshold: f64,
483 #[serde(default = "default_true")]
485 pub reasoning_model_warning: bool,
486}
487
488impl Default for AnomalyConfig {
489 fn default() -> Self {
490 Self {
491 enabled: true,
492 window_size: default_anomaly_window(),
493 error_threshold: default_anomaly_error_threshold(),
494 critical_threshold: default_anomaly_critical_threshold(),
495 reasoning_model_warning: true,
496 }
497 }
498}
499
500fn default_cache_ttl_secs() -> u64 {
501 300
502}
503
504#[derive(Debug, Clone, Deserialize, Serialize)]
506pub struct ResultCacheConfig {
507 #[serde(default = "default_true")]
509 pub enabled: bool,
510 #[serde(default = "default_cache_ttl_secs")]
512 pub ttl_secs: u64,
513}
514
515impl Default for ResultCacheConfig {
516 fn default() -> Self {
517 Self {
518 enabled: true,
519 ttl_secs: default_cache_ttl_secs(),
520 }
521 }
522}
523
524fn default_tafc_complexity_threshold() -> f64 {
525 0.6
526}
527
528#[derive(Debug, Clone, Deserialize, Serialize)]
530pub struct TafcConfig {
531 #[serde(default)]
533 pub enabled: bool,
534 #[serde(default = "default_tafc_complexity_threshold")]
536 pub complexity_threshold: f64,
537}
538
539impl Default for TafcConfig {
540 fn default() -> Self {
541 Self {
542 enabled: false,
543 complexity_threshold: default_tafc_complexity_threshold(),
544 }
545 }
546}
547
548impl TafcConfig {
549 #[must_use]
551 pub fn validated(mut self) -> Self {
552 if self.complexity_threshold.is_finite() {
553 self.complexity_threshold = self.complexity_threshold.clamp(0.0, 1.0);
554 } else {
555 self.complexity_threshold = 0.6;
556 }
557 self
558 }
559}
560
561fn default_utility_exempt_tools() -> Vec<String> {
562 vec!["invoke_skill".to_string(), "load_skill".to_string()]
563}
564
565fn default_utility_threshold() -> f32 {
566 0.1
567}
568
569fn default_utility_gain_weight() -> f32 {
570 1.0
571}
572
573fn default_utility_cost_weight() -> f32 {
574 0.5
575}
576
577fn default_utility_redundancy_weight() -> f32 {
578 0.3
579}
580
581fn default_utility_uncertainty_bonus() -> f32 {
582 0.2
583}
584
585#[derive(Debug, Clone, Deserialize, Serialize)]
587#[serde(default)]
588pub struct UtilityScoringConfig {
589 pub enabled: bool,
591 #[serde(default = "default_utility_threshold")]
593 pub threshold: f32,
594 #[serde(default = "default_utility_gain_weight")]
596 pub gain_weight: f32,
597 #[serde(default = "default_utility_cost_weight")]
599 pub cost_weight: f32,
600 #[serde(default = "default_utility_redundancy_weight")]
602 pub redundancy_weight: f32,
603 #[serde(default = "default_utility_uncertainty_bonus")]
605 pub uncertainty_bonus: f32,
606 #[serde(default = "default_utility_exempt_tools")]
608 pub exempt_tools: Vec<String>,
609 #[serde(default)]
614 pub utility_window: usize,
615 #[serde(default)]
625 pub high_gain_tools: Vec<String>,
626}
627
628impl Default for UtilityScoringConfig {
629 fn default() -> Self {
630 Self {
631 enabled: false,
632 threshold: default_utility_threshold(),
633 gain_weight: default_utility_gain_weight(),
634 cost_weight: default_utility_cost_weight(),
635 redundancy_weight: default_utility_redundancy_weight(),
636 uncertainty_bonus: default_utility_uncertainty_bonus(),
637 exempt_tools: default_utility_exempt_tools(),
638 utility_window: 0,
639 high_gain_tools: Vec::new(),
640 }
641 }
642}
643
644impl UtilityScoringConfig {
645 #[must_use = "validation result must be checked"]
651 pub fn validate(&self) -> Result<(), String> {
652 let fields = [
653 ("threshold", self.threshold),
654 ("gain_weight", self.gain_weight),
655 ("cost_weight", self.cost_weight),
656 ("redundancy_weight", self.redundancy_weight),
657 ("uncertainty_bonus", self.uncertainty_bonus),
658 ];
659 for (name, val) in fields {
660 if !val.is_finite() {
661 return Err(format!("[tools.utility] {name} must be finite, got {val}"));
662 }
663 if val < 0.0 {
664 return Err(format!("[tools.utility] {name} must be >= 0, got {val}"));
665 }
666 }
667 Ok(())
668 }
669}
670
671#[derive(Debug, Clone, Default, Deserialize, Serialize)]
673pub struct ToolDependency {
674 #[serde(default, skip_serializing_if = "Vec::is_empty")]
676 pub requires: Vec<String>,
677 #[serde(default, skip_serializing_if = "Vec::is_empty")]
679 pub prefers: Vec<String>,
680}
681
682fn default_boost_per_dep() -> f32 {
683 0.15
684}
685
686fn default_max_total_boost() -> f32 {
687 0.2
688}
689
690#[derive(Debug, Clone, Deserialize, Serialize)]
692pub struct DependencyConfig {
693 #[serde(default)]
695 pub enabled: bool,
696 #[serde(default = "default_boost_per_dep")]
698 pub boost_per_dep: f32,
699 #[serde(default = "default_max_total_boost")]
701 pub max_total_boost: f32,
702 #[serde(default)]
704 pub rules: HashMap<String, ToolDependency>,
705}
706
707impl Default for DependencyConfig {
708 fn default() -> Self {
709 Self {
710 enabled: false,
711 boost_per_dep: default_boost_per_dep(),
712 max_total_boost: default_max_total_boost(),
713 rules: HashMap::new(),
714 }
715 }
716}
717
718fn default_retry_max_attempts() -> usize {
719 2
720}
721
722fn default_retry_base_ms() -> u64 {
723 500
724}
725
726fn default_retry_max_ms() -> u64 {
727 5_000
728}
729
730fn default_retry_budget_secs() -> u64 {
731 30
732}
733
734#[derive(Debug, Clone, Deserialize, Serialize)]
736pub struct RetryConfig {
737 #[serde(default = "default_retry_max_attempts")]
739 pub max_attempts: usize,
740 #[serde(default = "default_retry_base_ms")]
742 pub base_ms: u64,
743 #[serde(default = "default_retry_max_ms")]
745 pub max_ms: u64,
746 #[serde(default = "default_retry_budget_secs")]
748 pub budget_secs: u64,
749 #[serde(default)]
752 pub parameter_reformat_provider: ProviderName,
753}
754
755impl Default for RetryConfig {
756 fn default() -> Self {
757 Self {
758 max_attempts: default_retry_max_attempts(),
759 base_ms: default_retry_base_ms(),
760 max_ms: default_retry_max_ms(),
761 budget_secs: default_retry_budget_secs(),
762 parameter_reformat_provider: ProviderName::default(),
763 }
764 }
765}
766
767fn default_adversarial_timeout_ms() -> u64 {
768 3_000
769}
770
771#[derive(Debug, Clone, Deserialize, Serialize)]
773pub struct AdversarialPolicyConfig {
774 #[serde(default)]
776 pub enabled: bool,
777 #[serde(default)]
779 pub policy_provider: ProviderName,
780 pub policy_file: Option<String>,
782 #[serde(default)]
784 pub fail_open: bool,
785 #[serde(default = "default_adversarial_timeout_ms")]
787 pub timeout_ms: u64,
788 #[serde(default = "AdversarialPolicyConfig::default_exempt_tools")]
790 pub exempt_tools: Vec<String>,
791}
792
793impl Default for AdversarialPolicyConfig {
794 fn default() -> Self {
795 Self {
796 enabled: false,
797 policy_provider: ProviderName::default(),
798 policy_file: None,
799 fail_open: false,
800 timeout_ms: default_adversarial_timeout_ms(),
801 exempt_tools: Self::default_exempt_tools(),
802 }
803 }
804}
805
806impl AdversarialPolicyConfig {
807 #[must_use]
808 pub fn default_exempt_tools() -> Vec<String> {
809 vec![
810 "memory_save".into(),
811 "memory_search".into(),
812 "read_overflow".into(),
813 "load_skill".into(),
814 "invoke_skill".into(),
815 "schedule_deferred".into(),
816 "list_tasks".into(),
819 ]
820 }
821}
822
823#[derive(Debug, Clone, Default, Deserialize, Serialize)]
828pub struct FileConfig {
829 #[serde(default)]
831 pub deny_read: Vec<String>,
832 #[serde(default)]
834 pub allow_read: Vec<String>,
835}
836
837#[derive(Debug, Clone, Default, Deserialize, Serialize)]
839pub struct AuthorizationConfig {
840 #[serde(default)]
842 pub enabled: bool,
843 #[serde(default)]
845 pub rules: Vec<PolicyRuleConfig>,
846}
847
848#[derive(Debug, Clone, PartialEq, Eq, Default)]
852#[non_exhaustive]
853pub enum AuditDestination {
854 #[default]
856 Stdout,
857 Stderr,
859 File(std::path::PathBuf),
861}
862
863impl AuditDestination {
864 #[must_use]
866 pub fn is_stdout(&self) -> bool {
867 matches!(self, Self::Stdout)
868 }
869}
870
871impl serde::Serialize for AuditDestination {
872 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
873 match self {
874 Self::Stdout => s.serialize_str("stdout"),
875 Self::Stderr => s.serialize_str("stderr"),
876 Self::File(p) => s.serialize_str(&p.display().to_string()),
877 }
878 }
879}
880
881impl<'de> serde::Deserialize<'de> for AuditDestination {
882 fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
883 let s = String::deserialize(d)?;
884 Ok(match s.as_str() {
885 "stdout" => Self::Stdout,
886 "stderr" => Self::Stderr,
887 path => Self::File(std::path::PathBuf::from(path)),
888 })
889 }
890}
891
892#[derive(Debug, Deserialize, Serialize)]
894pub struct AuditConfig {
895 #[serde(default = "default_true")]
897 pub enabled: bool,
898 #[serde(default)]
900 pub destination: AuditDestination,
901 #[serde(default)]
903 pub tool_risk_summary: bool,
904}
905
906impl Default for AuditConfig {
907 fn default() -> Self {
908 Self {
909 enabled: true,
910 destination: AuditDestination::default(),
911 tool_risk_summary: false,
912 }
913 }
914}
915
916fn default_timeout() -> u64 {
917 30
918}
919
920fn default_confirm_patterns() -> Vec<String> {
921 vec![
922 "rm ".into(),
923 "git push -f".into(),
924 "git push --force".into(),
925 "drop table".into(),
926 "drop database".into(),
927 "truncate ".into(),
928 "$(".into(),
929 "`".into(),
930 "<(".into(),
931 ">(".into(),
932 "<<<".into(),
933 "eval ".into(),
934 ]
935}
936
937fn default_max_background_runs() -> usize {
938 8
939}
940
941fn default_background_timeout_secs() -> u64 {
942 1800
943}
944
945fn default_max_checkpoints() -> usize {
946 20
947}
948
949#[derive(Debug, Deserialize, Serialize)]
951#[allow(clippy::struct_excessive_bools)]
952pub struct ShellConfig {
953 #[serde(default = "default_timeout")]
955 pub timeout: u64,
956 #[serde(default)]
958 pub blocked_commands: Vec<String>,
959 #[serde(default)]
961 pub allowed_commands: Vec<String>,
962 #[serde(default)]
964 pub allowed_paths: Vec<String>,
965 #[serde(default = "default_true")]
967 pub allow_network: bool,
968 #[serde(default = "default_confirm_patterns")]
970 pub confirm_patterns: Vec<String>,
971 #[serde(default = "ShellConfig::default_env_blocklist")]
973 pub env_blocklist: Vec<String>,
974 #[serde(default)]
976 pub transactional: bool,
977 #[serde(default)]
979 pub transaction_scope: Vec<String>,
980 #[serde(default)]
982 pub auto_rollback: bool,
983 #[serde(default)]
985 pub auto_rollback_exit_codes: Vec<i32>,
986 #[serde(default)]
988 pub snapshot_required: bool,
989 #[serde(default)]
991 pub max_snapshot_bytes: u64,
992 #[serde(default = "default_max_background_runs")]
994 pub max_background_runs: usize,
995 #[serde(default = "default_background_timeout_secs")]
997 pub background_timeout_secs: u64,
998 #[serde(default)]
1003 pub risk_chain_threshold: Option<f32>,
1004 #[serde(default)]
1010 pub checkpoints_enabled: bool,
1011 #[serde(default = "default_max_checkpoints")]
1016 pub max_checkpoints: usize,
1017}
1018
1019impl Default for ShellConfig {
1020 fn default() -> Self {
1021 Self {
1022 timeout: default_timeout(),
1023 blocked_commands: Vec::new(),
1024 allowed_commands: Vec::new(),
1025 allowed_paths: Vec::new(),
1026 allow_network: true,
1027 confirm_patterns: default_confirm_patterns(),
1028 env_blocklist: Self::default_env_blocklist(),
1029 transactional: false,
1030 transaction_scope: Vec::new(),
1031 auto_rollback: false,
1032 auto_rollback_exit_codes: Vec::new(),
1033 snapshot_required: false,
1034 max_snapshot_bytes: 0,
1035 max_background_runs: default_max_background_runs(),
1036 background_timeout_secs: default_background_timeout_secs(),
1037 risk_chain_threshold: None,
1038 checkpoints_enabled: false,
1039 max_checkpoints: default_max_checkpoints(),
1040 }
1041 }
1042}
1043
1044impl ShellConfig {
1045 #[must_use]
1047 pub fn default_env_blocklist() -> Vec<String> {
1048 vec![
1049 "ZEPH_".into(),
1050 "AWS_".into(),
1051 "AZURE_".into(),
1052 "GCP_".into(),
1053 "GOOGLE_".into(),
1054 "OPENAI_".into(),
1055 "ANTHROPIC_".into(),
1056 "HF_".into(),
1057 "HUGGING".into(),
1058 ]
1059 }
1060}
1061
1062fn default_scrape_timeout() -> u64 {
1063 15
1064}
1065
1066fn default_max_body_bytes() -> usize {
1067 4_194_304
1068}
1069
1070fn default_ipi_filter_threshold() -> f32 {
1071 0.6
1072}
1073
1074#[derive(Debug, Deserialize, Serialize)]
1076pub struct ScrapeConfig {
1077 #[serde(default = "default_scrape_timeout")]
1079 pub timeout: u64,
1080 #[serde(default = "default_max_body_bytes")]
1082 pub max_body_bytes: usize,
1083 #[serde(default)]
1085 pub allowed_domains: Vec<String>,
1086 #[serde(default)]
1088 pub denied_domains: Vec<String>,
1089 #[serde(default = "default_ipi_filter_threshold")]
1092 pub ipi_filter_threshold: f32,
1093}
1094
1095impl Default for ScrapeConfig {
1096 fn default() -> Self {
1097 Self {
1098 timeout: default_scrape_timeout(),
1099 max_body_bytes: default_max_body_bytes(),
1100 allowed_domains: Vec::new(),
1101 denied_domains: Vec::new(),
1102 ipi_filter_threshold: default_ipi_filter_threshold(),
1103 }
1104 }
1105}
1106
1107#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Deserialize, Serialize)]
1109#[serde(rename_all = "kebab-case")]
1110#[non_exhaustive]
1111pub enum SpeculationMode {
1112 #[default]
1114 Off,
1115 Decoding,
1117 Pattern,
1119 Both,
1121}
1122
1123#[derive(Debug, Clone, Deserialize, Serialize)]
1125pub struct SpeculativePatternConfig {
1126 #[serde(default)]
1128 pub enabled: bool,
1129 #[serde(default = "default_min_observations")]
1131 pub min_observations: u32,
1132 #[serde(default = "default_half_life_days")]
1134 pub half_life_days: f64,
1135 #[serde(default)]
1137 pub rerank_provider: ProviderName,
1138}
1139
1140fn default_min_observations() -> u32 {
1141 5
1142}
1143
1144fn default_half_life_days() -> f64 {
1145 14.0
1146}
1147
1148impl Default for SpeculativePatternConfig {
1149 fn default() -> Self {
1150 Self {
1151 enabled: false,
1152 min_observations: default_min_observations(),
1153 half_life_days: default_half_life_days(),
1154 rerank_provider: ProviderName::default(),
1155 }
1156 }
1157}
1158
1159#[derive(Debug, Clone, Default, Deserialize, Serialize)]
1161pub struct SpeculativeAllowlistConfig {
1162 #[serde(default)]
1164 pub shell: Vec<String>,
1165}
1166
1167fn default_max_in_flight() -> usize {
1168 4
1169}
1170
1171fn default_confidence_threshold() -> f32 {
1172 0.55
1173}
1174
1175fn default_max_wasted_per_minute() -> u64 {
1176 100
1177}
1178
1179fn default_ttl_seconds() -> u64 {
1180 30
1181}
1182
1183#[derive(Debug, Clone, Deserialize, Serialize)]
1185pub struct SpeculativeConfig {
1186 #[serde(default)]
1188 pub mode: SpeculationMode,
1189 #[serde(default = "default_max_in_flight")]
1191 pub max_in_flight: usize,
1192 #[serde(default = "default_confidence_threshold")]
1194 pub confidence_threshold: f32,
1195 #[serde(default = "default_max_wasted_per_minute")]
1197 pub max_wasted_per_minute: u64,
1198 #[serde(default = "default_ttl_seconds")]
1200 pub ttl_seconds: u64,
1201 #[serde(default = "default_true")]
1203 pub audit: bool,
1204 #[serde(default)]
1206 pub pattern: SpeculativePatternConfig,
1207 #[serde(default)]
1209 pub allowlist: SpeculativeAllowlistConfig,
1210}
1211
1212impl Default for SpeculativeConfig {
1213 fn default() -> Self {
1214 Self {
1215 mode: SpeculationMode::Off,
1216 max_in_flight: default_max_in_flight(),
1217 confidence_threshold: default_confidence_threshold(),
1218 max_wasted_per_minute: default_max_wasted_per_minute(),
1219 ttl_seconds: default_ttl_seconds(),
1220 audit: true,
1221 pattern: SpeculativePatternConfig::default(),
1222 allowlist: SpeculativeAllowlistConfig::default(),
1223 }
1224 }
1225}
1226
1227#[derive(Debug, Clone, Deserialize, Serialize)]
1229#[serde(default)]
1230#[allow(clippy::struct_excessive_bools)]
1231pub struct EgressConfig {
1232 pub enabled: bool,
1234 pub log_blocked: bool,
1236 pub log_response_bytes: bool,
1238 pub log_hosts_to_tui: bool,
1240}
1241
1242impl Default for EgressConfig {
1243 fn default() -> Self {
1244 Self {
1245 enabled: true,
1246 log_blocked: true,
1247 log_response_bytes: true,
1248 log_hosts_to_tui: true,
1249 }
1250 }
1251}
1252
1253fn default_compression_min_lines() -> usize {
1256 10
1257}
1258
1259fn default_compression_max_rules() -> u32 {
1260 200
1261}
1262
1263fn default_regex_compile_timeout_ms() -> u64 {
1264 500
1265}
1266
1267fn default_evolution_min_interval_secs() -> u64 {
1268 3600
1269}
1270
1271#[derive(Debug, Clone, Deserialize, Serialize)]
1286#[serde(default)]
1287pub struct ToolCompressionConfig {
1288 pub enabled: bool,
1290 #[serde(default = "default_compression_min_lines")]
1292 pub min_lines_to_compress: usize,
1293 #[serde(default)]
1295 pub evolution_provider: ProviderName,
1296 #[serde(default = "default_evolution_min_interval_secs")]
1298 pub evolution_min_interval_secs: u64,
1299 #[serde(default = "default_compression_max_rules")]
1301 pub max_rules: u32,
1302 #[serde(default = "default_regex_compile_timeout_ms")]
1304 pub regex_compile_timeout_ms: u64,
1305}
1306
1307impl Default for ToolCompressionConfig {
1308 fn default() -> Self {
1309 Self {
1310 enabled: false,
1311 min_lines_to_compress: default_compression_min_lines(),
1312 evolution_provider: ProviderName::default(),
1313 evolution_min_interval_secs: default_evolution_min_interval_secs(),
1314 max_rules: default_compression_max_rules(),
1315 regex_compile_timeout_ms: default_regex_compile_timeout_ms(),
1316 }
1317 }
1318}
1319
1320#[derive(Debug, Deserialize, Serialize)]
1328pub struct ToolsConfig {
1329 #[serde(default = "default_true")]
1331 pub enabled: bool,
1332 #[serde(default = "default_true")]
1334 pub summarize_output: bool,
1335 #[serde(default)]
1337 pub shell: ShellConfig,
1338 #[serde(default)]
1340 pub scrape: ScrapeConfig,
1341 #[serde(default)]
1343 pub audit: AuditConfig,
1344 #[serde(default)]
1346 pub permissions: Option<PermissionsConfig>,
1347 #[serde(default)]
1349 pub filters: FilterConfig,
1350 #[serde(default)]
1352 pub overflow: OverflowConfig,
1353 #[serde(default)]
1355 pub anomaly: AnomalyConfig,
1356 #[serde(default)]
1358 pub result_cache: ResultCacheConfig,
1359 #[serde(default)]
1361 pub tafc: TafcConfig,
1362 #[serde(default)]
1364 pub dependencies: DependencyConfig,
1365 #[serde(default)]
1367 pub retry: RetryConfig,
1368 #[serde(default)]
1370 pub policy: PolicyConfig,
1371 #[serde(default)]
1373 pub adversarial_policy: AdversarialPolicyConfig,
1374 #[serde(default)]
1376 pub utility: UtilityScoringConfig,
1377 #[serde(default)]
1379 pub file: FileConfig,
1380 #[serde(default)]
1382 pub authorization: AuthorizationConfig,
1383 #[serde(default)]
1385 pub max_tool_calls_per_session: Option<u32>,
1386 #[serde(default)]
1388 pub speculative: SpeculativeConfig,
1389 #[serde(default)]
1391 pub sandbox: SandboxConfig,
1392 #[serde(default)]
1394 pub egress: EgressConfig,
1395 #[serde(default)]
1397 pub compression: ToolCompressionConfig,
1398}
1399
1400impl Default for ToolsConfig {
1401 fn default() -> Self {
1402 Self {
1403 enabled: true,
1404 summarize_output: true,
1405 shell: ShellConfig::default(),
1406 scrape: ScrapeConfig::default(),
1407 audit: AuditConfig::default(),
1408 permissions: None,
1409 filters: FilterConfig::default(),
1410 overflow: OverflowConfig::default(),
1411 anomaly: AnomalyConfig::default(),
1412 result_cache: ResultCacheConfig::default(),
1413 tafc: TafcConfig::default(),
1414 dependencies: DependencyConfig::default(),
1415 retry: RetryConfig::default(),
1416 policy: PolicyConfig::default(),
1417 adversarial_policy: AdversarialPolicyConfig::default(),
1418 utility: UtilityScoringConfig::default(),
1419 file: FileConfig::default(),
1420 authorization: AuthorizationConfig::default(),
1421 max_tool_calls_per_session: None,
1422 speculative: SpeculativeConfig::default(),
1423 sandbox: SandboxConfig::default(),
1424 egress: EgressConfig::default(),
1425 compression: ToolCompressionConfig::default(),
1426 }
1427 }
1428}
1429
1430#[cfg(test)]
1431mod tests {
1432 use super::*;
1433
1434 #[test]
1435 fn deserialize_default_config() {
1436 let toml_str = r#"
1437 enabled = true
1438
1439 [shell]
1440 timeout = 60
1441 blocked_commands = ["rm -rf /", "sudo"]
1442 "#;
1443
1444 let config: ToolsConfig = toml::from_str(toml_str).unwrap();
1445 assert!(config.enabled);
1446 assert_eq!(config.shell.timeout, 60);
1447 assert_eq!(config.shell.blocked_commands.len(), 2);
1448 }
1449
1450 #[test]
1451 fn empty_blocked_commands() {
1452 let config: ToolsConfig = toml::from_str(r"[shell]\ntimeout = 30\n").unwrap_or_default();
1453 assert!(config.enabled);
1454 }
1455
1456 #[test]
1457 fn default_tools_config() {
1458 let config = ToolsConfig::default();
1459 assert!(config.enabled);
1460 assert!(config.summarize_output);
1461 assert_eq!(config.shell.timeout, 30);
1462 assert!(config.shell.blocked_commands.is_empty());
1463 assert!(config.audit.enabled);
1464 }
1465
1466 #[test]
1467 fn audit_destination_serde_roundtrip() {
1468 let cases = [
1469 ("\"stdout\"", AuditDestination::Stdout),
1470 ("\"stderr\"", AuditDestination::Stderr),
1471 (
1472 "\"/var/log/audit.log\"",
1473 AuditDestination::File("/var/log/audit.log".into()),
1474 ),
1475 ];
1476 for (json_str, expected) in cases {
1477 let got: AuditDestination = serde_json::from_str(json_str).unwrap();
1478 assert_eq!(got, expected);
1479 let serialized = serde_json::to_string(&got).unwrap();
1480 let roundtrip: AuditDestination = serde_json::from_str(&serialized).unwrap();
1481 assert_eq!(roundtrip, expected);
1482 }
1483 }
1484
1485 #[test]
1486 fn audit_destination_toml_in_config() {
1487 let cases = [
1488 (
1489 r#"[audit]
1490destination = "stdout""#,
1491 AuditDestination::Stdout,
1492 ),
1493 (
1494 r#"[audit]
1495destination = "stderr""#,
1496 AuditDestination::Stderr,
1497 ),
1498 (
1499 r#"[audit]
1500destination = "/var/log/zeph-audit.log""#,
1501 AuditDestination::File("/var/log/zeph-audit.log".into()),
1502 ),
1503 ];
1504 for (toml_str, expected) in cases {
1505 let config: ToolsConfig = toml::from_str(toml_str).unwrap();
1506 assert_eq!(config.audit.destination, expected);
1507 }
1508 }
1509
1510 #[test]
1511 fn policy_provider_serde_roundtrip() {
1512 let toml_str = r#"
1513 [policy]
1514 enabled = true
1515 policy_provider = "my-llm"
1516 "#;
1517 let config: ToolsConfig = toml::from_str(toml_str).unwrap();
1518 assert_eq!(config.policy.policy_provider.as_str(), "my-llm");
1519
1520 let json = serde_json::to_string(&config.policy).unwrap();
1521 let back: PolicyConfig = serde_json::from_str(&json).unwrap();
1522 assert_eq!(back.policy_provider.as_str(), "my-llm");
1523 }
1524
1525 #[test]
1526 fn policy_provider_default_is_empty() {
1527 let config = PolicyConfig::default();
1528 assert!(config.policy_provider.is_empty());
1529 }
1530
1531 #[test]
1532 fn utility_window_default_is_zero() {
1533 let config = UtilityScoringConfig::default();
1534 assert_eq!(config.utility_window, 0);
1535 }
1536
1537 #[test]
1538 fn utility_window_serde_roundtrip() {
1539 #[derive(serde::Deserialize)]
1540 struct Wrapper {
1541 utility_scoring: UtilityScoringConfig,
1542 }
1543
1544 let toml_str = "[utility_scoring]\nenabled = true\nutility_window = 3\n";
1545 let w: Wrapper = toml::from_str(toml_str).unwrap();
1546 assert_eq!(w.utility_scoring.utility_window, 3);
1547
1548 let json = serde_json::to_string(&w.utility_scoring).unwrap();
1549 let back: UtilityScoringConfig = serde_json::from_str(&json).unwrap();
1550 assert_eq!(back.utility_window, 3);
1551 }
1552
1553 #[test]
1554 fn high_gain_tools_default_is_empty() {
1555 let config = UtilityScoringConfig::default();
1556 assert!(config.high_gain_tools.is_empty());
1557 }
1558
1559 #[test]
1560 fn high_gain_tools_serde_roundtrip() {
1561 #[derive(serde::Deserialize)]
1562 struct Wrapper {
1563 utility_scoring: UtilityScoringConfig,
1564 }
1565
1566 let toml_str =
1567 "[utility_scoring]\nenabled = true\nhigh_gain_tools = [\"github_create_issue\"]\n";
1568 let w: Wrapper = toml::from_str(toml_str).unwrap();
1569 assert_eq!(
1570 w.utility_scoring.high_gain_tools,
1571 vec!["github_create_issue"]
1572 );
1573
1574 let json = serde_json::to_string(&w.utility_scoring).unwrap();
1575 let back: UtilityScoringConfig = serde_json::from_str(&json).unwrap();
1576 assert_eq!(back.high_gain_tools, vec!["github_create_issue"]);
1577 }
1578
1579 #[test]
1580 fn adversarial_policy_provider_default_is_empty() {
1581 let config = AdversarialPolicyConfig::default();
1582 assert!(config.policy_provider.is_empty());
1583 }
1584
1585 #[test]
1586 fn adversarial_policy_provider_serde_roundtrip() {
1587 #[derive(serde::Deserialize)]
1588 struct Wrapper {
1589 adversarial_policy: AdversarialPolicyConfig,
1590 }
1591 let toml_str = r#"
1592 [adversarial_policy]
1593 enabled = true
1594 policy_provider = "fast-llm"
1595 "#;
1596 let w: Wrapper = toml::from_str(toml_str).unwrap();
1597 assert_eq!(w.adversarial_policy.policy_provider.as_str(), "fast-llm");
1598
1599 let json = serde_json::to_string(&w.adversarial_policy).unwrap();
1600 let back: AdversarialPolicyConfig = serde_json::from_str(&json).unwrap();
1601 assert_eq!(back.policy_provider.as_str(), "fast-llm");
1602 }
1603
1604 #[test]
1605 fn adversarial_policy_provider_empty_when_omitted() {
1606 #[derive(serde::Deserialize)]
1607 struct Wrapper {
1608 adversarial_policy: AdversarialPolicyConfig,
1609 }
1610 let toml_str = r"
1611 [adversarial_policy]
1612 enabled = true
1613 ";
1614 let w: Wrapper = toml::from_str(toml_str).unwrap();
1615 assert!(
1616 w.adversarial_policy.policy_provider.is_empty(),
1617 "omitted policy_provider must default to empty (→ primary provider used)"
1618 );
1619 }
1620}