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}
258
259#[derive(Debug, Clone, Deserialize, Serialize)]
261pub struct PolicyRuleConfig {
262 pub effect: PolicyEffect,
264 pub tool: String,
266 #[serde(default)]
268 pub paths: Vec<String>,
269 #[serde(default)]
271 pub env: Vec<String>,
272 pub trust_level: Option<SkillTrustLevel>,
274 pub args_match: Option<String>,
276 #[serde(default)]
278 pub capabilities: Vec<String>,
279}
280
281#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
285#[serde(rename_all = "kebab-case")]
286#[non_exhaustive]
287pub enum SandboxProfile {
288 ReadOnly,
290 #[default]
292 Workspace,
293 #[serde(rename = "network-allow-all", alias = "network")]
295 NetworkAllowAll,
296 Off,
298}
299
300fn default_sandbox_profile() -> SandboxProfile {
301 SandboxProfile::Workspace
302}
303
304#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
309#[serde(rename_all = "kebab-case")]
310#[non_exhaustive]
311pub enum SandboxBackend {
312 #[default]
314 Auto,
315 Seatbelt,
317 LandlockBwrap,
319 Noop,
321}
322
323#[derive(Debug, Clone, Deserialize, Serialize)]
325pub struct SandboxConfig {
326 #[serde(default)]
328 pub enabled: bool,
329 #[serde(default = "default_sandbox_profile")]
331 pub profile: SandboxProfile,
332 #[serde(default)]
334 pub allow_read: Vec<PathBuf>,
335 #[serde(default)]
337 pub allow_write: Vec<PathBuf>,
338 #[serde(default = "default_true")]
340 pub strict: bool,
341 #[serde(default)]
345 pub backend: SandboxBackend,
346 #[serde(default)]
348 pub denied_domains: Vec<String>,
349 #[serde(default)]
351 pub fail_if_unavailable: bool,
352}
353
354impl Default for SandboxConfig {
355 fn default() -> Self {
356 Self {
357 enabled: false,
358 profile: default_sandbox_profile(),
359 allow_read: Vec::new(),
360 allow_write: Vec::new(),
361 strict: true,
362 backend: SandboxBackend::Auto,
363 denied_domains: Vec::new(),
364 fail_if_unavailable: false,
365 }
366 }
367}
368
369#[derive(Debug, Clone, Deserialize, Serialize)]
373pub struct SecurityFilterConfig {
374 #[serde(default = "default_true")]
376 pub enabled: bool,
377 #[serde(default)]
379 pub extra_patterns: Vec<String>,
380}
381
382impl Default for SecurityFilterConfig {
383 fn default() -> Self {
384 Self {
385 enabled: true,
386 extra_patterns: Vec::new(),
387 }
388 }
389}
390
391#[derive(Debug, Clone, Deserialize, Serialize)]
393pub struct FilterConfig {
394 #[serde(default = "default_true")]
396 pub enabled: bool,
397 #[serde(default)]
399 pub security: SecurityFilterConfig,
400 #[serde(default, skip_serializing_if = "Option::is_none")]
402 pub filters_path: Option<PathBuf>,
403}
404
405impl Default for FilterConfig {
406 fn default() -> Self {
407 Self {
408 enabled: true,
409 security: SecurityFilterConfig::default(),
410 filters_path: None,
411 }
412 }
413}
414
415fn default_overflow_threshold() -> usize {
418 50_000
419}
420
421fn default_retention_days() -> u64 {
422 7
423}
424
425fn default_max_overflow_bytes() -> usize {
426 10 * 1024 * 1024
427}
428
429#[derive(Debug, Clone, Deserialize, Serialize)]
431pub struct OverflowConfig {
432 #[serde(default = "default_overflow_threshold")]
434 pub threshold: usize,
435 #[serde(default = "default_retention_days")]
437 pub retention_days: u64,
438 #[serde(default = "default_max_overflow_bytes")]
440 pub max_overflow_bytes: usize,
441}
442
443impl Default for OverflowConfig {
444 fn default() -> Self {
445 Self {
446 threshold: default_overflow_threshold(),
447 retention_days: default_retention_days(),
448 max_overflow_bytes: default_max_overflow_bytes(),
449 }
450 }
451}
452
453fn default_anomaly_window() -> usize {
454 10
455}
456
457fn default_anomaly_error_threshold() -> f64 {
458 0.5
459}
460
461fn default_anomaly_critical_threshold() -> f64 {
462 0.8
463}
464
465#[derive(Debug, Clone, Deserialize, Serialize)]
467pub struct AnomalyConfig {
468 #[serde(default = "default_true")]
470 pub enabled: bool,
471 #[serde(default = "default_anomaly_window")]
473 pub window_size: usize,
474 #[serde(default = "default_anomaly_error_threshold")]
476 pub error_threshold: f64,
477 #[serde(default = "default_anomaly_critical_threshold")]
479 pub critical_threshold: f64,
480 #[serde(default = "default_true")]
482 pub reasoning_model_warning: bool,
483}
484
485impl Default for AnomalyConfig {
486 fn default() -> Self {
487 Self {
488 enabled: true,
489 window_size: default_anomaly_window(),
490 error_threshold: default_anomaly_error_threshold(),
491 critical_threshold: default_anomaly_critical_threshold(),
492 reasoning_model_warning: true,
493 }
494 }
495}
496
497fn default_cache_ttl_secs() -> u64 {
498 300
499}
500
501#[derive(Debug, Clone, Deserialize, Serialize)]
503pub struct ResultCacheConfig {
504 #[serde(default = "default_true")]
506 pub enabled: bool,
507 #[serde(default = "default_cache_ttl_secs")]
509 pub ttl_secs: u64,
510}
511
512impl Default for ResultCacheConfig {
513 fn default() -> Self {
514 Self {
515 enabled: true,
516 ttl_secs: default_cache_ttl_secs(),
517 }
518 }
519}
520
521fn default_tafc_complexity_threshold() -> f64 {
522 0.6
523}
524
525#[derive(Debug, Clone, Deserialize, Serialize)]
527pub struct TafcConfig {
528 #[serde(default)]
530 pub enabled: bool,
531 #[serde(default = "default_tafc_complexity_threshold")]
533 pub complexity_threshold: f64,
534}
535
536impl Default for TafcConfig {
537 fn default() -> Self {
538 Self {
539 enabled: false,
540 complexity_threshold: default_tafc_complexity_threshold(),
541 }
542 }
543}
544
545impl TafcConfig {
546 #[must_use]
548 pub fn validated(mut self) -> Self {
549 if self.complexity_threshold.is_finite() {
550 self.complexity_threshold = self.complexity_threshold.clamp(0.0, 1.0);
551 } else {
552 self.complexity_threshold = 0.6;
553 }
554 self
555 }
556}
557
558fn default_utility_exempt_tools() -> Vec<String> {
559 vec!["invoke_skill".to_string(), "load_skill".to_string()]
560}
561
562fn default_utility_threshold() -> f32 {
563 0.1
564}
565
566fn default_utility_gain_weight() -> f32 {
567 1.0
568}
569
570fn default_utility_cost_weight() -> f32 {
571 0.5
572}
573
574fn default_utility_redundancy_weight() -> f32 {
575 0.3
576}
577
578fn default_utility_uncertainty_bonus() -> f32 {
579 0.2
580}
581
582#[derive(Debug, Clone, Deserialize, Serialize)]
584#[serde(default)]
585pub struct UtilityScoringConfig {
586 pub enabled: bool,
588 #[serde(default = "default_utility_threshold")]
590 pub threshold: f32,
591 #[serde(default = "default_utility_gain_weight")]
593 pub gain_weight: f32,
594 #[serde(default = "default_utility_cost_weight")]
596 pub cost_weight: f32,
597 #[serde(default = "default_utility_redundancy_weight")]
599 pub redundancy_weight: f32,
600 #[serde(default = "default_utility_uncertainty_bonus")]
602 pub uncertainty_bonus: f32,
603 #[serde(default = "default_utility_exempt_tools")]
605 pub exempt_tools: Vec<String>,
606}
607
608impl Default for UtilityScoringConfig {
609 fn default() -> Self {
610 Self {
611 enabled: false,
612 threshold: default_utility_threshold(),
613 gain_weight: default_utility_gain_weight(),
614 cost_weight: default_utility_cost_weight(),
615 redundancy_weight: default_utility_redundancy_weight(),
616 uncertainty_bonus: default_utility_uncertainty_bonus(),
617 exempt_tools: default_utility_exempt_tools(),
618 }
619 }
620}
621
622impl UtilityScoringConfig {
623 pub fn validate(&self) -> Result<(), String> {
629 let fields = [
630 ("threshold", self.threshold),
631 ("gain_weight", self.gain_weight),
632 ("cost_weight", self.cost_weight),
633 ("redundancy_weight", self.redundancy_weight),
634 ("uncertainty_bonus", self.uncertainty_bonus),
635 ];
636 for (name, val) in fields {
637 if !val.is_finite() {
638 return Err(format!("[tools.utility] {name} must be finite, got {val}"));
639 }
640 if val < 0.0 {
641 return Err(format!("[tools.utility] {name} must be >= 0, got {val}"));
642 }
643 }
644 Ok(())
645 }
646}
647
648#[derive(Debug, Clone, Default, Deserialize, Serialize)]
650pub struct ToolDependency {
651 #[serde(default, skip_serializing_if = "Vec::is_empty")]
653 pub requires: Vec<String>,
654 #[serde(default, skip_serializing_if = "Vec::is_empty")]
656 pub prefers: Vec<String>,
657}
658
659fn default_boost_per_dep() -> f32 {
660 0.15
661}
662
663fn default_max_total_boost() -> f32 {
664 0.2
665}
666
667#[derive(Debug, Clone, Deserialize, Serialize)]
669pub struct DependencyConfig {
670 #[serde(default)]
672 pub enabled: bool,
673 #[serde(default = "default_boost_per_dep")]
675 pub boost_per_dep: f32,
676 #[serde(default = "default_max_total_boost")]
678 pub max_total_boost: f32,
679 #[serde(default)]
681 pub rules: HashMap<String, ToolDependency>,
682}
683
684impl Default for DependencyConfig {
685 fn default() -> Self {
686 Self {
687 enabled: false,
688 boost_per_dep: default_boost_per_dep(),
689 max_total_boost: default_max_total_boost(),
690 rules: HashMap::new(),
691 }
692 }
693}
694
695fn default_retry_max_attempts() -> usize {
696 2
697}
698
699fn default_retry_base_ms() -> u64 {
700 500
701}
702
703fn default_retry_max_ms() -> u64 {
704 5_000
705}
706
707fn default_retry_budget_secs() -> u64 {
708 30
709}
710
711#[derive(Debug, Clone, Deserialize, Serialize)]
713pub struct RetryConfig {
714 #[serde(default = "default_retry_max_attempts")]
716 pub max_attempts: usize,
717 #[serde(default = "default_retry_base_ms")]
719 pub base_ms: u64,
720 #[serde(default = "default_retry_max_ms")]
722 pub max_ms: u64,
723 #[serde(default = "default_retry_budget_secs")]
725 pub budget_secs: u64,
726 #[serde(default)]
729 pub parameter_reformat_provider: ProviderName,
730}
731
732impl Default for RetryConfig {
733 fn default() -> Self {
734 Self {
735 max_attempts: default_retry_max_attempts(),
736 base_ms: default_retry_base_ms(),
737 max_ms: default_retry_max_ms(),
738 budget_secs: default_retry_budget_secs(),
739 parameter_reformat_provider: ProviderName::default(),
740 }
741 }
742}
743
744fn default_adversarial_timeout_ms() -> u64 {
745 3_000
746}
747
748#[derive(Debug, Clone, Deserialize, Serialize)]
750pub struct AdversarialPolicyConfig {
751 #[serde(default)]
753 pub enabled: bool,
754 #[serde(default)]
756 pub policy_provider: ProviderName,
757 pub policy_file: Option<String>,
759 #[serde(default)]
761 pub fail_open: bool,
762 #[serde(default = "default_adversarial_timeout_ms")]
764 pub timeout_ms: u64,
765 #[serde(default = "AdversarialPolicyConfig::default_exempt_tools")]
767 pub exempt_tools: Vec<String>,
768}
769
770impl Default for AdversarialPolicyConfig {
771 fn default() -> Self {
772 Self {
773 enabled: false,
774 policy_provider: ProviderName::default(),
775 policy_file: None,
776 fail_open: false,
777 timeout_ms: default_adversarial_timeout_ms(),
778 exempt_tools: Self::default_exempt_tools(),
779 }
780 }
781}
782
783impl AdversarialPolicyConfig {
784 #[must_use]
785 pub fn default_exempt_tools() -> Vec<String> {
786 vec![
787 "memory_save".into(),
788 "memory_search".into(),
789 "read_overflow".into(),
790 "load_skill".into(),
791 "invoke_skill".into(),
792 "schedule_deferred".into(),
793 "list_tasks".into(),
796 ]
797 }
798}
799
800#[derive(Debug, Clone, Default, Deserialize, Serialize)]
805pub struct FileConfig {
806 #[serde(default)]
808 pub deny_read: Vec<String>,
809 #[serde(default)]
811 pub allow_read: Vec<String>,
812}
813
814#[derive(Debug, Clone, Default, Deserialize, Serialize)]
816pub struct AuthorizationConfig {
817 #[serde(default)]
819 pub enabled: bool,
820 #[serde(default)]
822 pub rules: Vec<PolicyRuleConfig>,
823}
824
825#[derive(Debug, Clone, PartialEq, Eq, Default)]
829#[non_exhaustive]
830pub enum AuditDestination {
831 #[default]
833 Stdout,
834 Stderr,
836 File(std::path::PathBuf),
838}
839
840impl AuditDestination {
841 #[must_use]
843 pub fn is_stdout(&self) -> bool {
844 matches!(self, Self::Stdout)
845 }
846}
847
848impl serde::Serialize for AuditDestination {
849 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
850 match self {
851 Self::Stdout => s.serialize_str("stdout"),
852 Self::Stderr => s.serialize_str("stderr"),
853 Self::File(p) => s.serialize_str(&p.display().to_string()),
854 }
855 }
856}
857
858impl<'de> serde::Deserialize<'de> for AuditDestination {
859 fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
860 let s = String::deserialize(d)?;
861 Ok(match s.as_str() {
862 "stdout" => Self::Stdout,
863 "stderr" => Self::Stderr,
864 path => Self::File(std::path::PathBuf::from(path)),
865 })
866 }
867}
868
869#[derive(Debug, Deserialize, Serialize)]
871pub struct AuditConfig {
872 #[serde(default = "default_true")]
874 pub enabled: bool,
875 #[serde(default)]
877 pub destination: AuditDestination,
878 #[serde(default)]
880 pub tool_risk_summary: bool,
881}
882
883impl Default for AuditConfig {
884 fn default() -> Self {
885 Self {
886 enabled: true,
887 destination: AuditDestination::default(),
888 tool_risk_summary: false,
889 }
890 }
891}
892
893fn default_timeout() -> u64 {
894 30
895}
896
897fn default_confirm_patterns() -> Vec<String> {
898 vec![
899 "rm ".into(),
900 "git push -f".into(),
901 "git push --force".into(),
902 "drop table".into(),
903 "drop database".into(),
904 "truncate ".into(),
905 "$(".into(),
906 "`".into(),
907 "<(".into(),
908 ">(".into(),
909 "<<<".into(),
910 "eval ".into(),
911 ]
912}
913
914fn default_max_background_runs() -> usize {
915 8
916}
917
918fn default_background_timeout_secs() -> u64 {
919 1800
920}
921
922#[derive(Debug, Deserialize, Serialize)]
924#[allow(clippy::struct_excessive_bools)]
925pub struct ShellConfig {
926 #[serde(default = "default_timeout")]
928 pub timeout: u64,
929 #[serde(default)]
931 pub blocked_commands: Vec<String>,
932 #[serde(default)]
934 pub allowed_commands: Vec<String>,
935 #[serde(default)]
937 pub allowed_paths: Vec<String>,
938 #[serde(default = "default_true")]
940 pub allow_network: bool,
941 #[serde(default = "default_confirm_patterns")]
943 pub confirm_patterns: Vec<String>,
944 #[serde(default = "ShellConfig::default_env_blocklist")]
946 pub env_blocklist: Vec<String>,
947 #[serde(default)]
949 pub transactional: bool,
950 #[serde(default)]
952 pub transaction_scope: Vec<String>,
953 #[serde(default)]
955 pub auto_rollback: bool,
956 #[serde(default)]
958 pub auto_rollback_exit_codes: Vec<i32>,
959 #[serde(default)]
961 pub snapshot_required: bool,
962 #[serde(default)]
964 pub max_snapshot_bytes: u64,
965 #[serde(default = "default_max_background_runs")]
967 pub max_background_runs: usize,
968 #[serde(default = "default_background_timeout_secs")]
970 pub background_timeout_secs: u64,
971 #[serde(default)]
976 pub risk_chain_threshold: Option<f32>,
977}
978
979impl Default for ShellConfig {
980 fn default() -> Self {
981 Self {
982 timeout: default_timeout(),
983 blocked_commands: Vec::new(),
984 allowed_commands: Vec::new(),
985 allowed_paths: Vec::new(),
986 allow_network: true,
987 confirm_patterns: default_confirm_patterns(),
988 env_blocklist: Self::default_env_blocklist(),
989 transactional: false,
990 transaction_scope: Vec::new(),
991 auto_rollback: false,
992 auto_rollback_exit_codes: Vec::new(),
993 snapshot_required: false,
994 max_snapshot_bytes: 0,
995 max_background_runs: default_max_background_runs(),
996 background_timeout_secs: default_background_timeout_secs(),
997 risk_chain_threshold: None,
998 }
999 }
1000}
1001
1002impl ShellConfig {
1003 #[must_use]
1005 pub fn default_env_blocklist() -> Vec<String> {
1006 vec![
1007 "ZEPH_".into(),
1008 "AWS_".into(),
1009 "AZURE_".into(),
1010 "GCP_".into(),
1011 "GOOGLE_".into(),
1012 "OPENAI_".into(),
1013 "ANTHROPIC_".into(),
1014 "HF_".into(),
1015 "HUGGING".into(),
1016 ]
1017 }
1018}
1019
1020fn default_scrape_timeout() -> u64 {
1021 15
1022}
1023
1024fn default_max_body_bytes() -> usize {
1025 4_194_304
1026}
1027
1028fn default_ipi_filter_threshold() -> f32 {
1029 0.6
1030}
1031
1032#[derive(Debug, Deserialize, Serialize)]
1034pub struct ScrapeConfig {
1035 #[serde(default = "default_scrape_timeout")]
1037 pub timeout: u64,
1038 #[serde(default = "default_max_body_bytes")]
1040 pub max_body_bytes: usize,
1041 #[serde(default)]
1043 pub allowed_domains: Vec<String>,
1044 #[serde(default)]
1046 pub denied_domains: Vec<String>,
1047 #[serde(default = "default_ipi_filter_threshold")]
1050 pub ipi_filter_threshold: f32,
1051}
1052
1053impl Default for ScrapeConfig {
1054 fn default() -> Self {
1055 Self {
1056 timeout: default_scrape_timeout(),
1057 max_body_bytes: default_max_body_bytes(),
1058 allowed_domains: Vec::new(),
1059 denied_domains: Vec::new(),
1060 ipi_filter_threshold: default_ipi_filter_threshold(),
1061 }
1062 }
1063}
1064
1065#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Deserialize, Serialize)]
1067#[serde(rename_all = "kebab-case")]
1068#[non_exhaustive]
1069pub enum SpeculationMode {
1070 #[default]
1072 Off,
1073 Decoding,
1075 Pattern,
1077 Both,
1079}
1080
1081#[derive(Debug, Clone, Deserialize, Serialize)]
1083pub struct SpeculativePatternConfig {
1084 #[serde(default)]
1086 pub enabled: bool,
1087 #[serde(default = "default_min_observations")]
1089 pub min_observations: u32,
1090 #[serde(default = "default_half_life_days")]
1092 pub half_life_days: f64,
1093 #[serde(default)]
1095 pub rerank_provider: ProviderName,
1096}
1097
1098fn default_min_observations() -> u32 {
1099 5
1100}
1101
1102fn default_half_life_days() -> f64 {
1103 14.0
1104}
1105
1106impl Default for SpeculativePatternConfig {
1107 fn default() -> Self {
1108 Self {
1109 enabled: false,
1110 min_observations: default_min_observations(),
1111 half_life_days: default_half_life_days(),
1112 rerank_provider: ProviderName::default(),
1113 }
1114 }
1115}
1116
1117#[derive(Debug, Clone, Default, Deserialize, Serialize)]
1119pub struct SpeculativeAllowlistConfig {
1120 #[serde(default)]
1122 pub shell: Vec<String>,
1123}
1124
1125fn default_max_in_flight() -> usize {
1126 4
1127}
1128
1129fn default_confidence_threshold() -> f32 {
1130 0.55
1131}
1132
1133fn default_max_wasted_per_minute() -> u64 {
1134 100
1135}
1136
1137fn default_ttl_seconds() -> u64 {
1138 30
1139}
1140
1141#[derive(Debug, Clone, Deserialize, Serialize)]
1143pub struct SpeculativeConfig {
1144 #[serde(default)]
1146 pub mode: SpeculationMode,
1147 #[serde(default = "default_max_in_flight")]
1149 pub max_in_flight: usize,
1150 #[serde(default = "default_confidence_threshold")]
1152 pub confidence_threshold: f32,
1153 #[serde(default = "default_max_wasted_per_minute")]
1155 pub max_wasted_per_minute: u64,
1156 #[serde(default = "default_ttl_seconds")]
1158 pub ttl_seconds: u64,
1159 #[serde(default = "default_true")]
1161 pub audit: bool,
1162 #[serde(default)]
1164 pub pattern: SpeculativePatternConfig,
1165 #[serde(default)]
1167 pub allowlist: SpeculativeAllowlistConfig,
1168}
1169
1170impl Default for SpeculativeConfig {
1171 fn default() -> Self {
1172 Self {
1173 mode: SpeculationMode::Off,
1174 max_in_flight: default_max_in_flight(),
1175 confidence_threshold: default_confidence_threshold(),
1176 max_wasted_per_minute: default_max_wasted_per_minute(),
1177 ttl_seconds: default_ttl_seconds(),
1178 audit: true,
1179 pattern: SpeculativePatternConfig::default(),
1180 allowlist: SpeculativeAllowlistConfig::default(),
1181 }
1182 }
1183}
1184
1185#[derive(Debug, Clone, Deserialize, Serialize)]
1187#[serde(default)]
1188#[allow(clippy::struct_excessive_bools)]
1189pub struct EgressConfig {
1190 pub enabled: bool,
1192 pub log_blocked: bool,
1194 pub log_response_bytes: bool,
1196 pub log_hosts_to_tui: bool,
1198}
1199
1200impl Default for EgressConfig {
1201 fn default() -> Self {
1202 Self {
1203 enabled: true,
1204 log_blocked: true,
1205 log_response_bytes: true,
1206 log_hosts_to_tui: true,
1207 }
1208 }
1209}
1210
1211fn default_compression_min_lines() -> usize {
1214 10
1215}
1216
1217fn default_compression_max_rules() -> u32 {
1218 200
1219}
1220
1221fn default_regex_compile_timeout_ms() -> u64 {
1222 500
1223}
1224
1225fn default_evolution_min_interval_secs() -> u64 {
1226 3600
1227}
1228
1229#[derive(Debug, Clone, Deserialize, Serialize)]
1244#[serde(default)]
1245pub struct ToolCompressionConfig {
1246 pub enabled: bool,
1248 #[serde(default = "default_compression_min_lines")]
1250 pub min_lines_to_compress: usize,
1251 #[serde(default)]
1253 pub evolution_provider: ProviderName,
1254 #[serde(default = "default_evolution_min_interval_secs")]
1256 pub evolution_min_interval_secs: u64,
1257 #[serde(default = "default_compression_max_rules")]
1259 pub max_rules: u32,
1260 #[serde(default = "default_regex_compile_timeout_ms")]
1262 pub regex_compile_timeout_ms: u64,
1263}
1264
1265impl Default for ToolCompressionConfig {
1266 fn default() -> Self {
1267 Self {
1268 enabled: false,
1269 min_lines_to_compress: default_compression_min_lines(),
1270 evolution_provider: ProviderName::default(),
1271 evolution_min_interval_secs: default_evolution_min_interval_secs(),
1272 max_rules: default_compression_max_rules(),
1273 regex_compile_timeout_ms: default_regex_compile_timeout_ms(),
1274 }
1275 }
1276}
1277
1278#[derive(Debug, Deserialize, Serialize)]
1286pub struct ToolsConfig {
1287 #[serde(default = "default_true")]
1289 pub enabled: bool,
1290 #[serde(default = "default_true")]
1292 pub summarize_output: bool,
1293 #[serde(default)]
1295 pub shell: ShellConfig,
1296 #[serde(default)]
1298 pub scrape: ScrapeConfig,
1299 #[serde(default)]
1301 pub audit: AuditConfig,
1302 #[serde(default)]
1304 pub permissions: Option<PermissionsConfig>,
1305 #[serde(default)]
1307 pub filters: FilterConfig,
1308 #[serde(default)]
1310 pub overflow: OverflowConfig,
1311 #[serde(default)]
1313 pub anomaly: AnomalyConfig,
1314 #[serde(default)]
1316 pub result_cache: ResultCacheConfig,
1317 #[serde(default)]
1319 pub tafc: TafcConfig,
1320 #[serde(default)]
1322 pub dependencies: DependencyConfig,
1323 #[serde(default)]
1325 pub retry: RetryConfig,
1326 #[serde(default)]
1328 pub policy: PolicyConfig,
1329 #[serde(default)]
1331 pub adversarial_policy: AdversarialPolicyConfig,
1332 #[serde(default)]
1334 pub utility: UtilityScoringConfig,
1335 #[serde(default)]
1337 pub file: FileConfig,
1338 #[serde(default)]
1340 pub authorization: AuthorizationConfig,
1341 #[serde(default)]
1343 pub max_tool_calls_per_session: Option<u32>,
1344 #[serde(default)]
1346 pub speculative: SpeculativeConfig,
1347 #[serde(default)]
1349 pub sandbox: SandboxConfig,
1350 #[serde(default)]
1352 pub egress: EgressConfig,
1353 #[serde(default)]
1355 pub compression: ToolCompressionConfig,
1356}
1357
1358impl Default for ToolsConfig {
1359 fn default() -> Self {
1360 Self {
1361 enabled: true,
1362 summarize_output: true,
1363 shell: ShellConfig::default(),
1364 scrape: ScrapeConfig::default(),
1365 audit: AuditConfig::default(),
1366 permissions: None,
1367 filters: FilterConfig::default(),
1368 overflow: OverflowConfig::default(),
1369 anomaly: AnomalyConfig::default(),
1370 result_cache: ResultCacheConfig::default(),
1371 tafc: TafcConfig::default(),
1372 dependencies: DependencyConfig::default(),
1373 retry: RetryConfig::default(),
1374 policy: PolicyConfig::default(),
1375 adversarial_policy: AdversarialPolicyConfig::default(),
1376 utility: UtilityScoringConfig::default(),
1377 file: FileConfig::default(),
1378 authorization: AuthorizationConfig::default(),
1379 max_tool_calls_per_session: None,
1380 speculative: SpeculativeConfig::default(),
1381 sandbox: SandboxConfig::default(),
1382 egress: EgressConfig::default(),
1383 compression: ToolCompressionConfig::default(),
1384 }
1385 }
1386}
1387
1388#[cfg(test)]
1389mod tests {
1390 use super::*;
1391
1392 #[test]
1393 fn deserialize_default_config() {
1394 let toml_str = r#"
1395 enabled = true
1396
1397 [shell]
1398 timeout = 60
1399 blocked_commands = ["rm -rf /", "sudo"]
1400 "#;
1401
1402 let config: ToolsConfig = toml::from_str(toml_str).unwrap();
1403 assert!(config.enabled);
1404 assert_eq!(config.shell.timeout, 60);
1405 assert_eq!(config.shell.blocked_commands.len(), 2);
1406 }
1407
1408 #[test]
1409 fn empty_blocked_commands() {
1410 let config: ToolsConfig = toml::from_str(r"[shell]\ntimeout = 30\n").unwrap_or_default();
1411 assert!(config.enabled);
1412 }
1413
1414 #[test]
1415 fn default_tools_config() {
1416 let config = ToolsConfig::default();
1417 assert!(config.enabled);
1418 assert!(config.summarize_output);
1419 assert_eq!(config.shell.timeout, 30);
1420 assert!(config.shell.blocked_commands.is_empty());
1421 assert!(config.audit.enabled);
1422 }
1423
1424 #[test]
1425 fn audit_destination_serde_roundtrip() {
1426 let cases = [
1427 ("\"stdout\"", AuditDestination::Stdout),
1428 ("\"stderr\"", AuditDestination::Stderr),
1429 (
1430 "\"/var/log/audit.log\"",
1431 AuditDestination::File("/var/log/audit.log".into()),
1432 ),
1433 ];
1434 for (json_str, expected) in cases {
1435 let got: AuditDestination = serde_json::from_str(json_str).unwrap();
1436 assert_eq!(got, expected);
1437 let serialized = serde_json::to_string(&got).unwrap();
1438 let roundtrip: AuditDestination = serde_json::from_str(&serialized).unwrap();
1439 assert_eq!(roundtrip, expected);
1440 }
1441 }
1442
1443 #[test]
1444 fn audit_destination_toml_in_config() {
1445 let cases = [
1446 (
1447 r#"[audit]
1448destination = "stdout""#,
1449 AuditDestination::Stdout,
1450 ),
1451 (
1452 r#"[audit]
1453destination = "stderr""#,
1454 AuditDestination::Stderr,
1455 ),
1456 (
1457 r#"[audit]
1458destination = "/var/log/zeph-audit.log""#,
1459 AuditDestination::File("/var/log/zeph-audit.log".into()),
1460 ),
1461 ];
1462 for (toml_str, expected) in cases {
1463 let config: ToolsConfig = toml::from_str(toml_str).unwrap();
1464 assert_eq!(config.audit.destination, expected);
1465 }
1466 }
1467}