Skip to main content

memory_core/
config.rs

1use std::collections::BTreeMap;
2
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Default, Serialize, Deserialize)]
6#[serde(default)]
7pub struct Config {
8    pub storage: StorageConfig,
9    pub search: SearchConfig,
10    pub validation: ValidationConfig,
11    pub privacy: PrivacyConfig,
12}
13
14#[derive(Debug, Clone, Serialize, Deserialize)]
15#[serde(default)]
16pub struct StorageConfig {
17    pub retention_days: u32,
18    pub vacuum_interval_secs: u64,
19    pub max_db_size_mb: u64,
20    pub busy_timeout_ms: u32,
21    pub cache_size_kb: u32,
22    pub dedup_window_secs: u64,
23    pub encryption_enabled: bool,
24    /// Minimum information score (0.0–1.0) for non-explicit saves.
25    /// Set to 0.0 to disable filtering. Default: 0.35.
26    pub entropy_threshold: f64,
27}
28
29impl Default for StorageConfig {
30    fn default() -> Self {
31        Self {
32            retention_days: 90,
33            vacuum_interval_secs: 604800,
34            max_db_size_mb: 500,
35            busy_timeout_ms: 5000,
36            cache_size_kb: 2048,
37            dedup_window_secs: 900,
38            encryption_enabled: false,
39            entropy_threshold: 0.35,
40        }
41    }
42}
43
44#[derive(Debug, Clone, Serialize, Deserialize)]
45#[serde(default)]
46pub struct SearchConfig {
47    pub default_limit: u32,
48    pub max_limit: u32,
49    /// BM25 score cutoff. When set, only results with `bm25() < threshold` are returned.
50    /// BM25 returns negative values for rare-term matches and positive values for common-term
51    /// matches in large corpora. Example: `Some(-0.3)` drops near-zero noise in production.
52    /// Default `None` means no threshold filtering (safe for small/test corpora).
53    pub min_relevance_score: Option<f64>,
54    /// Preview length in chars for the best match (position 0).
55    pub preview_max_chars: usize,
56    /// Preview length in chars for weak matches (position 3+).
57    pub preview_min_chars: usize,
58    /// Per-column BM25 weights. Keys are FTS5 column names; unknown keys are ignored.
59    /// Defaults: key=10, value=1, tags=5, source_type=0.5, scope=0.5.
60    pub column_weights: BTreeMap<String, f64>,
61}
62
63fn default_column_weights() -> BTreeMap<String, f64> {
64    [
65        ("key".to_string(), 10.0),
66        ("value".to_string(), 1.0),
67        ("tags".to_string(), 5.0),
68        ("source_type".to_string(), 0.5),
69        ("scope".to_string(), 0.5),
70    ]
71    .into_iter()
72    .collect()
73}
74
75impl Default for SearchConfig {
76    fn default() -> Self {
77        Self {
78            default_limit: 10,
79            max_limit: 50,
80            min_relevance_score: None,
81            preview_max_chars: 400,
82            preview_min_chars: 80,
83            column_weights: default_column_weights(),
84        }
85    }
86}
87
88#[derive(Debug, Clone, Serialize, Deserialize)]
89#[serde(default)]
90pub struct ValidationConfig {
91    pub max_key_length: usize,
92    pub max_value_length: usize,
93    pub max_tags: usize,
94    pub max_tag_length: usize,
95}
96
97impl Default for ValidationConfig {
98    fn default() -> Self {
99        Self {
100            max_key_length: 256,
101            max_value_length: 2000,
102            max_tags: 20,
103            max_tag_length: 64,
104        }
105    }
106}
107
108#[derive(Debug, Clone, Serialize, Deserialize)]
109#[serde(default)]
110pub struct PrivacyConfig {
111    pub secret_patterns: Vec<String>,
112    pub extra_patterns: Vec<String>,
113    pub replace_defaults: bool,
114    pub file_deny_list: Vec<String>,
115}
116
117impl Default for PrivacyConfig {
118    fn default() -> Self {
119        Self {
120            secret_patterns: default_secret_patterns(),
121            extra_patterns: Vec::new(),
122            replace_defaults: false,
123            file_deny_list: vec![
124                ".env".into(),
125                ".env.*".into(),
126                "*.pem".into(),
127                "*.key".into(),
128                "*.p12".into(),
129                "*.pfx".into(),
130                "id_rsa".into(),
131                "id_ed25519".into(),
132                "id_ecdsa".into(),
133                "*.secret".into(),
134                "credentials.json".into(),
135            ],
136        }
137    }
138}
139
140pub fn default_secret_patterns() -> Vec<String> {
141    vec![
142        r"AKIA[0-9A-Z]{16}".into(),
143        r"-----BEGIN [A-Z ]*PRIVATE KEY-----".into(),
144        r"(?i)(api[_-]?key|token|secret|password)\s*[:=]\s*\S+".into(),
145        r"(?i)mongodb(\+srv)?://[^\s]+".into(),
146        r"(?i)postgres(ql)?://[^\s]+".into(),
147        r"(?i)mysql://[^\s]+".into(),
148        r"(?i)redis://[^\s]+".into(),
149        r"ghp_[a-zA-Z0-9]{36}".into(),
150        r"sk-[a-zA-Z0-9]{48}".into(),
151        r"xoxb-[0-9]+-[0-9]+-[a-zA-Z0-9]+".into(),
152    ]
153}