use std::sync::{Mutex, MutexGuard, OnceLock};
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub(crate) struct KernelFeatures {
pub enabled: bool,
pub proxy_etpao: bool,
pub mcp_etpao: bool,
pub content_dedup: bool,
pub schema_optimization: bool,
pub receipt_chain: bool,
pub usage_tracking: bool,
pub identity_tracking: bool,
pub max_kernel_budget: usize,
pub dedup_capacity: usize,
}
impl Default for KernelFeatures {
fn default() -> Self {
Self {
enabled: true,
proxy_etpao: true,
mcp_etpao: true,
content_dedup: true,
schema_optimization: true,
receipt_chain: true,
usage_tracking: true,
identity_tracking: true,
max_kernel_budget: 150,
dedup_capacity: 1024,
}
}
}
static FEATURES: OnceLock<Mutex<KernelFeatures>> = OnceLock::new();
fn feature_guard() -> MutexGuard<'static, KernelFeatures> {
FEATURES
.get_or_init(|| Mutex::new(KernelFeatures::default()))
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
fn env_bool(name: &str, default: bool) -> bool {
match std::env::var(name) {
Ok(value) => match value.trim().to_ascii_lowercase().as_str() {
"1" | "true" | "yes" | "on" => true,
"0" | "false" | "no" | "off" => false,
_ => default,
},
Err(_) => default,
}
}
fn env_usize(name: &str, default: usize) -> usize {
std::env::var(name)
.ok()
.and_then(|value| value.trim().parse().ok())
.unwrap_or(default)
}
#[must_use]
pub(crate) fn features() -> KernelFeatures {
feature_guard().clone()
}
pub(crate) fn update_features(features: KernelFeatures) {
*feature_guard() = features;
}
#[must_use]
pub(crate) fn is_enabled() -> bool {
feature_guard().enabled
}
#[must_use]
pub(crate) fn is_feature_enabled(feature: &str) -> bool {
let features = feature_guard();
features.enabled
&& match feature {
"enabled" => true,
"proxy_etpao" => features.proxy_etpao,
"mcp_etpao" => features.mcp_etpao,
"content_dedup" => features.content_dedup,
"schema_optimization" => features.schema_optimization,
"receipt_chain" => features.receipt_chain,
"usage_tracking" => features.usage_tracking,
"identity_tracking" => features.identity_tracking,
_ => false,
}
}
pub(crate) fn reset_features() {
update_features(KernelFeatures::default());
}
#[must_use]
pub(crate) fn from_env() -> KernelFeatures {
let defaults = KernelFeatures::default();
KernelFeatures {
enabled: env_bool("LEAN_CTX_KERNEL_ENABLED", defaults.enabled),
content_dedup: env_bool("LEAN_CTX_KERNEL_DEDUP", defaults.content_dedup),
schema_optimization: env_bool("LEAN_CTX_KERNEL_SCHEMA_OPT", defaults.schema_optimization),
max_kernel_budget: env_usize("LEAN_CTX_KERNEL_MAX_BUDGET", defaults.max_kernel_budget),
..defaults
}
}
#[cfg(test)]
pub(crate) static KERNEL_TEST_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
#[cfg(test)]
mod tests {
use super::{
KernelFeatures, features, from_env, is_enabled, is_feature_enabled, reset_features,
update_features,
};
use std::sync::MutexGuard;
fn setup() -> MutexGuard<'static, ()> {
let guard = super::KERNEL_TEST_LOCK
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
reset_features();
guard
}
#[test]
fn default_all_enabled() {
let _guard = setup();
let features = KernelFeatures::default();
assert!(features.enabled);
assert!(features.proxy_etpao);
assert!(features.mcp_etpao);
assert!(features.content_dedup);
assert!(features.schema_optimization);
assert!(features.receipt_chain);
assert!(features.usage_tracking);
assert!(features.identity_tracking);
}
#[test]
fn update_persists() {
let _guard = setup();
let changed = KernelFeatures {
content_dedup: false,
max_kernel_budget: 77,
..KernelFeatures::default()
};
update_features(changed);
let current = features();
assert!(!current.content_dedup);
assert_eq!(current.max_kernel_budget, 77);
}
#[test]
fn is_enabled_master_switch() {
let _guard = setup();
let changed = KernelFeatures {
enabled: false,
dedup_capacity: 1,
..KernelFeatures::default()
};
update_features(changed);
assert!(!is_enabled());
assert!(!is_feature_enabled("content_dedup"));
}
#[test]
fn is_feature_by_name() {
let _guard = setup();
assert!(is_feature_enabled("content_dedup"));
}
#[test]
fn disabled_named_feature_is_false() {
let _guard = setup();
let changed = KernelFeatures {
schema_optimization: false,
..KernelFeatures::default()
};
update_features(changed);
assert!(!is_feature_enabled("schema_optimization"));
}
#[test]
fn unknown_feature_false() {
let _guard = setup();
assert!(!is_feature_enabled("nonexistent"));
}
#[test]
fn from_env_respects_vars() {
let _guard = setup();
let _env_guard = crate::core::data_dir::test_env_lock();
let old_enabled = std::env::var_os("LEAN_CTX_KERNEL_ENABLED");
let old_budget = std::env::var_os("LEAN_CTX_KERNEL_MAX_BUDGET");
crate::test_env::set_var("LEAN_CTX_KERNEL_ENABLED", "false");
crate::test_env::set_var("LEAN_CTX_KERNEL_MAX_BUDGET", "321");
let configured = from_env();
match old_enabled {
Some(value) => crate::test_env::set_var("LEAN_CTX_KERNEL_ENABLED", value),
None => crate::test_env::remove_var("LEAN_CTX_KERNEL_ENABLED"),
}
match old_budget {
Some(value) => crate::test_env::set_var("LEAN_CTX_KERNEL_MAX_BUDGET", value),
None => crate::test_env::remove_var("LEAN_CTX_KERNEL_MAX_BUDGET"),
}
assert!(!configured.enabled);
assert_eq!(configured.max_kernel_budget, 321);
}
#[test]
fn reset_restores_defaults() {
let _guard = setup();
let changed = KernelFeatures {
enabled: false,
dedup_capacity: 1,
..KernelFeatures::default()
};
update_features(changed);
reset_features();
let current = features();
assert!(current.enabled);
assert_eq!(current.dedup_capacity, 1024);
}
}