Skip to main content

lean_ctx/core/context_kernel/
config_bridge.rs

1//! Bridge between the main lean-ctx configuration and Context Kernel features.
2
3use 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/// Origin of the effective Context Kernel configuration.
14#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize)]
15pub enum ConfigSource {
16    /// Built-in defaults.
17    Default,
18    /// One or more `LEAN_CTX_KERNEL_*` environment variables.
19    EnvVar,
20    /// A kernel section in a configuration file.
21    ConfigFile,
22    /// Features changed through the runtime API.
23    Runtime,
24}
25
26/// Effective Context Kernel configuration and its source details.
27#[derive(Debug, Clone, serde::Serialize)]
28pub struct ConfigReport {
29    /// Effective feature values.
30    pub features: KernelFeatures,
31    /// Highest-precedence source that supplied the values.
32    pub source: ConfigSource,
33    /// Supported kernel environment variables present in the process.
34    pub env_vars_detected: Vec<String>,
35}
36
37/// Builds kernel features from the main configuration.
38///
39/// The main [`Config`] currently has no kernel section, so environment-backed
40/// features are returned until typed config fields are introduced.
41#[must_use]
42pub fn from_config(cfg: &Config) -> KernelFeatures {
43    let _ = cfg;
44    kernel_config::from_env()
45}
46
47/// Loads the main configuration and applies its kernel feature values globally.
48pub fn apply_config() {
49    kernel_config::update_features(from_config(&Config::load()));
50}
51
52/// Returns the effective kernel features and their detected source.
53#[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(&current, &configured)
59        && !features_match(&current, &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/// Returns a serializable report of effective kernel configuration.
75#[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
85/// Restores kernel configuration state for tests and runtime reinitialization.
86pub 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(); // holds test_env_lock for the test's lifetime
178        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(); // holds test_env_lock for the test's lifetime
187        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(); // holds test_env_lock for the test's lifetime
195        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}