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