lean_ctx/core/context_kernel/
kernel_config.rs1use std::sync::{Mutex, MutexGuard, OnceLock};
4
5#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
7pub struct KernelFeatures {
8 pub enabled: bool,
10 pub proxy_etpao: bool,
12 pub mcp_etpao: bool,
14 pub content_dedup: bool,
16 pub schema_optimization: bool,
18 pub receipt_chain: bool,
20 pub usage_tracking: bool,
22 pub identity_tracking: bool,
24 pub max_kernel_budget: usize,
26 pub dedup_capacity: usize,
28}
29
30impl Default for KernelFeatures {
31 fn default() -> Self {
32 Self {
33 enabled: true,
34 proxy_etpao: true,
35 mcp_etpao: true,
36 content_dedup: true,
37 schema_optimization: true,
38 receipt_chain: true,
39 usage_tracking: true,
40 identity_tracking: true,
41 max_kernel_budget: 150,
42 dedup_capacity: 1024,
43 }
44 }
45}
46
47static FEATURES: OnceLock<Mutex<KernelFeatures>> = OnceLock::new();
48
49fn feature_guard() -> MutexGuard<'static, KernelFeatures> {
50 FEATURES
51 .get_or_init(|| Mutex::new(KernelFeatures::default()))
52 .lock()
53 .unwrap_or_else(std::sync::PoisonError::into_inner)
54}
55
56fn env_bool(name: &str, default: bool) -> bool {
57 match std::env::var(name) {
58 Ok(value) => match value.trim().to_ascii_lowercase().as_str() {
59 "1" | "true" | "yes" | "on" => true,
60 "0" | "false" | "no" | "off" => false,
61 _ => default,
62 },
63 Err(_) => default,
64 }
65}
66
67fn env_usize(name: &str, default: usize) -> usize {
68 std::env::var(name)
69 .ok()
70 .and_then(|value| value.trim().parse().ok())
71 .unwrap_or(default)
72}
73
74#[must_use]
76pub fn features() -> KernelFeatures {
77 feature_guard().clone()
78}
79
80pub fn update_features(features: KernelFeatures) {
82 *feature_guard() = features;
83}
84
85#[must_use]
87pub fn is_enabled() -> bool {
88 feature_guard().enabled
89}
90
91#[must_use]
93pub fn is_feature_enabled(feature: &str) -> bool {
94 let features = feature_guard();
95 features.enabled
96 && match feature {
97 "enabled" => true,
98 "proxy_etpao" => features.proxy_etpao,
99 "mcp_etpao" => features.mcp_etpao,
100 "content_dedup" => features.content_dedup,
101 "schema_optimization" => features.schema_optimization,
102 "receipt_chain" => features.receipt_chain,
103 "usage_tracking" => features.usage_tracking,
104 "identity_tracking" => features.identity_tracking,
105 _ => false,
106 }
107}
108
109pub fn reset_features() {
111 update_features(KernelFeatures::default());
112}
113
114#[must_use]
116pub fn from_env() -> KernelFeatures {
117 let defaults = KernelFeatures::default();
118 KernelFeatures {
119 enabled: env_bool("LEAN_CTX_KERNEL_ENABLED", defaults.enabled),
120 content_dedup: env_bool("LEAN_CTX_KERNEL_DEDUP", defaults.content_dedup),
121 schema_optimization: env_bool("LEAN_CTX_KERNEL_SCHEMA_OPT", defaults.schema_optimization),
122 max_kernel_budget: env_usize("LEAN_CTX_KERNEL_MAX_BUDGET", defaults.max_kernel_budget),
123 ..defaults
124 }
125}
126
127#[cfg(test)]
129pub static KERNEL_TEST_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
130#[cfg(test)]
131mod tests {
132 use super::{
133 KernelFeatures, features, from_env, is_enabled, is_feature_enabled, reset_features,
134 update_features,
135 };
136 use std::sync::MutexGuard;
137
138 fn setup() -> MutexGuard<'static, ()> {
139 let guard = super::KERNEL_TEST_LOCK
140 .lock()
141 .unwrap_or_else(std::sync::PoisonError::into_inner);
142 reset_features();
143 guard
144 }
145
146 #[test]
147 fn default_all_enabled() {
148 let _guard = setup();
149 let features = KernelFeatures::default();
150 assert!(features.enabled);
151 assert!(features.proxy_etpao);
152 assert!(features.mcp_etpao);
153 assert!(features.content_dedup);
154 assert!(features.schema_optimization);
155 assert!(features.receipt_chain);
156 assert!(features.usage_tracking);
157 assert!(features.identity_tracking);
158 }
159
160 #[test]
161 fn update_persists() {
162 let _guard = setup();
163 let changed = KernelFeatures {
164 content_dedup: false,
165 max_kernel_budget: 77,
166 ..KernelFeatures::default()
167 };
168 update_features(changed);
169 let current = features();
170 assert!(!current.content_dedup);
171 assert_eq!(current.max_kernel_budget, 77);
172 }
173
174 #[test]
175 fn is_enabled_master_switch() {
176 let _guard = setup();
177 let changed = KernelFeatures {
178 enabled: false,
179 dedup_capacity: 1,
180 ..KernelFeatures::default()
181 };
182 update_features(changed);
183 assert!(!is_enabled());
184 assert!(!is_feature_enabled("content_dedup"));
185 }
186
187 #[test]
188 fn is_feature_by_name() {
189 let _guard = setup();
190 assert!(is_feature_enabled("content_dedup"));
191 }
192
193 #[test]
194 fn disabled_named_feature_is_false() {
195 let _guard = setup();
196 let changed = KernelFeatures {
197 schema_optimization: false,
198 ..KernelFeatures::default()
199 };
200 update_features(changed);
201 assert!(!is_feature_enabled("schema_optimization"));
202 }
203
204 #[test]
205 fn unknown_feature_false() {
206 let _guard = setup();
207 assert!(!is_feature_enabled("nonexistent"));
208 }
209
210 #[test]
211 fn from_env_respects_vars() {
212 let _guard = setup();
213 let _env_guard = crate::core::data_dir::test_env_lock();
214 let old_enabled = std::env::var_os("LEAN_CTX_KERNEL_ENABLED");
215 let old_budget = std::env::var_os("LEAN_CTX_KERNEL_MAX_BUDGET");
216 crate::test_env::set_var("LEAN_CTX_KERNEL_ENABLED", "false");
217 crate::test_env::set_var("LEAN_CTX_KERNEL_MAX_BUDGET", "321");
218
219 let configured = from_env();
220
221 match old_enabled {
222 Some(value) => crate::test_env::set_var("LEAN_CTX_KERNEL_ENABLED", value),
223 None => crate::test_env::remove_var("LEAN_CTX_KERNEL_ENABLED"),
224 }
225 match old_budget {
226 Some(value) => crate::test_env::set_var("LEAN_CTX_KERNEL_MAX_BUDGET", value),
227 None => crate::test_env::remove_var("LEAN_CTX_KERNEL_MAX_BUDGET"),
228 }
229 assert!(!configured.enabled);
230 assert_eq!(configured.max_kernel_budget, 321);
231 }
232
233 #[test]
234 fn reset_restores_defaults() {
235 let _guard = setup();
236 let changed = KernelFeatures {
237 enabled: false,
238 dedup_capacity: 1,
239 ..KernelFeatures::default()
240 };
241 update_features(changed);
242 reset_features();
243 let current = features();
244 assert!(current.enabled);
245 assert_eq!(current.dedup_capacity, 1024);
246 }
247}