lean_ctx/core/context_kernel/
config_bridge.rs1use super::kernel_config::{self, KernelFeatures};
4use crate::core::config::Config;
5
6const KERNEL_ENV_VARS: &[&str] = &[
7 "LEAN_CTX_KERNEL_ENABLED",
8 "LEAN_CTX_KERNEL_DEDUP",
9 "LEAN_CTX_KERNEL_SCHEMA_OPT",
10 "LEAN_CTX_KERNEL_MAX_BUDGET",
11];
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize)]
15pub enum ConfigSource {
16 Default,
18 EnvVar,
20 ConfigFile,
22 Runtime,
24}
25
26#[derive(Debug, Clone, serde::Serialize)]
28pub struct ConfigReport {
29 pub features: KernelFeatures,
31 pub source: ConfigSource,
33 pub env_vars_detected: Vec<String>,
35}
36
37#[must_use]
42pub fn from_config(cfg: &Config) -> KernelFeatures {
43 let _ = cfg;
44 kernel_config::from_env()
45}
46
47pub fn apply_config() {
49 kernel_config::update_features(from_config(&Config::load()));
50}
51
52#[must_use]
54pub fn effective_config() -> (KernelFeatures, ConfigSource) {
55 let configured = from_config(&Config::load());
56 let current = kernel_config::features();
57
58 if !features_match(¤t, &configured)
59 && !features_match(¤t, &KernelFeatures::default())
60 {
61 return (current, ConfigSource::Runtime);
62 }
63
64 let source = if !detected_env_vars().is_empty() {
65 ConfigSource::EnvVar
66 } else if config_file_has_kernel_settings() {
67 ConfigSource::ConfigFile
68 } else {
69 ConfigSource::Default
70 };
71 (configured, source)
72}
73
74#[must_use]
76pub fn config_report() -> ConfigReport {
77 let (features, source) = effective_config();
78 ConfigReport {
79 features,
80 source,
81 env_vars_detected: detected_env_vars(),
82 }
83}
84
85pub fn reset() {
87 kernel_config::reset_features();
88}
89
90fn detected_env_vars() -> Vec<String> {
91 KERNEL_ENV_VARS
92 .iter()
93 .filter(|name| std::env::var_os(name).is_some())
94 .map(|name| (*name).to_string())
95 .collect()
96}
97
98fn config_file_has_kernel_settings() -> bool {
99 let provenance = Config::provenance();
100 [provenance.config_path, provenance.local_path]
101 .into_iter()
102 .flatten()
103 .any(|path| {
104 std::fs::read_to_string(path)
105 .ok()
106 .and_then(|raw| raw.parse::<toml::Table>().ok())
107 .and_then(|table| table.get("kernel").and_then(toml::Value::as_table).cloned())
108 .is_some_and(|kernel| !kernel.is_empty())
109 })
110}
111
112fn features_match(left: &KernelFeatures, right: &KernelFeatures) -> bool {
113 left.enabled == right.enabled
114 && left.proxy_etpao == right.proxy_etpao
115 && left.mcp_etpao == right.mcp_etpao
116 && left.content_dedup == right.content_dedup
117 && left.schema_optimization == right.schema_optimization
118 && left.receipt_chain == right.receipt_chain
119 && left.usage_tracking == right.usage_tracking
120 && left.identity_tracking == right.identity_tracking
121 && left.max_kernel_budget == right.max_kernel_budget
122 && left.dedup_capacity == right.dedup_capacity
123}
124
125#[cfg(test)]
126mod tests {
127 use super::*;
128
129 struct EnvGuard(Vec<(&'static str, Option<std::ffi::OsString>)>);
130
131 impl EnvGuard {
132 fn clear() -> Self {
133 let saved = KERNEL_ENV_VARS
134 .iter()
135 .map(|&name| (name, std::env::var_os(name)))
136 .collect();
137 for name in KERNEL_ENV_VARS {
138 crate::test_env::remove_var(name);
139 }
140 Self(saved)
141 }
142 }
143
144 impl Drop for EnvGuard {
145 fn drop(&mut self) {
146 for (name, value) in self.0.drain(..) {
147 match value {
148 Some(value) => crate::test_env::set_var(name, value),
149 None => crate::test_env::remove_var(name),
150 }
151 }
152 }
153 }
154
155 fn setup() -> (
156 std::sync::MutexGuard<'static, ()>,
157 crate::core::data_dir::TestEnvGuard,
158 EnvGuard,
159 ) {
160 let kernel = kernel_config::KERNEL_TEST_LOCK
161 .lock()
162 .unwrap_or_else(std::sync::PoisonError::into_inner);
163 let env = crate::core::data_dir::test_env_lock();
164 let vars = EnvGuard::clear();
165 reset();
166 (kernel, env, vars)
167 }
168
169 #[test]
170 fn default_when_no_env() {
171 let _guards = setup();
172 assert_eq!(effective_config().1, ConfigSource::Default);
173 }
174
175 #[test]
176 fn env_overrides_default() {
177 let _guards = setup(); crate::test_env::set_var("LEAN_CTX_KERNEL_ENABLED", "false");
179 let (features, source) = effective_config();
180 assert!(!features.enabled);
181 assert_eq!(source, ConfigSource::EnvVar);
182 }
183
184 #[test]
185 fn apply_updates_global() {
186 let _guards = setup(); crate::test_env::set_var("LEAN_CTX_KERNEL_DEDUP", "false");
188 apply_config();
189 assert!(!kernel_config::features().content_dedup);
190 }
191
192 #[test]
193 fn report_lists_env_vars() {
194 let _guards = setup(); crate::test_env::set_var("LEAN_CTX_KERNEL_MAX_BUDGET", "42");
196 assert_eq!(
197 config_report().env_vars_detected,
198 vec!["LEAN_CTX_KERNEL_MAX_BUDGET"]
199 );
200 }
201}