lean-ctx 3.9.18

Context Runtime for AI Agents with CCP. 71 MCP tools, 10 read modes, 95+ compression patterns, cross-session memory (CCP), persistent AI knowledge with temporal facts + contradiction detection, multi-agent context sharing, LITM-aware positioning, AAAK compact format, adaptive compression with Thompson Sampling bandits. Supports 24+ AI tools. Reduces LLM token consumption by up to 99%.
Documentation
//! Kernel policy enforcement modes.

use serde::Deserialize;

use super::policy::ContextPolicy;
use super::types::{ContextPlanV1, PlanEntry};

/// Determines whether policy violations are observed or enforced.
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "lowercase")]
pub(crate) enum KernelMode {
    #[default]
    Shadow,
    Enforce,
    Explain,
}

#[derive(Debug, Default, Deserialize)]
struct KernelModeConfig {
    kernel_mode: Option<KernelMode>,
}

/// Resolves the kernel mode from the environment, then the global config.
pub(crate) fn resolve_mode(_project_root: &str) -> KernelMode {
    if let Ok(value) = std::env::var("LEANCTX_KERNEL_MODE")
        && let Some(mode) = parse_mode(&value)
    {
        return mode;
    }

    crate::core::paths::config_dir_member("config.toml")
        .ok()
        .and_then(|path| std::fs::read_to_string(path).ok())
        .and_then(|raw| toml::from_str::<KernelModeConfig>(&raw).ok())
        .and_then(|config| config.kernel_mode)
        .unwrap_or_default()
}

fn parse_mode(value: &str) -> Option<KernelMode> {
    match value.trim().to_ascii_lowercase().as_str() {
        "shadow" => Some(KernelMode::Shadow),
        "enforce" => Some(KernelMode::Enforce),
        "explain" => Some(KernelMode::Explain),
        _ => None,
    }
}

/// Result of applying a policy to a context plan.
#[derive(Debug, Clone)]
pub(crate) struct EnforceResult {
    pub mode: KernelMode,
    pub allowed: Vec<PlanEntry>,
    pub blocked: Vec<BlockedEntry>,
    pub explanation: Option<String>,
}

/// A selected plan entry rejected by policy.
#[derive(Debug, Clone)]
pub(crate) struct BlockedEntry {
    pub object_id: String,
    pub reason: String,
}

/// Applies policy decisions according to the requested kernel mode.
pub(crate) fn enforce_plan(
    plan: &ContextPlanV1,
    policy: &ContextPolicy,
    mode: KernelMode,
) -> EnforceResult {
    let mut allowed: Vec<PlanEntry> = Vec::with_capacity(plan.selected.len());
    let mut blocked: Vec<BlockedEntry> = Vec::new();
    let mut details: Vec<String> = Vec::new();

    for entry in &plan.selected {
        if let Some(reason) = entry_violation(entry, policy) {
            blocked.push(BlockedEntry {
                object_id: entry.object_id.clone(),
                reason: reason.clone(),
            });

            if mode == KernelMode::Shadow {
                allowed.push(entry.clone());
            }
            if mode == KernelMode::Explain {
                details.push(format!("{}: blocked — {reason}", entry.object_id));
            }
        } else {
            allowed.push(entry.clone());
            if mode == KernelMode::Explain {
                details.push(format!("{}: allowed — policy compliant", entry.object_id));
            }
        }
    }

    EnforceResult {
        mode,
        allowed,
        blocked,
        explanation: (mode == KernelMode::Explain).then(|| details.join("\n")),
    }
}

fn entry_violation(entry: &PlanEntry, policy: &ContextPolicy) -> Option<String> {
    if entry.object_id.trim().is_empty() {
        return Some("object id is empty".to_owned());
    }
    if entry.provider.trim().is_empty() {
        return Some("provider is empty".to_owned());
    }
    if entry.view.trim().is_empty() {
        return Some("view is empty".to_owned());
    }
    if !entry.phi.is_finite() {
        return Some("phi is not finite".to_owned());
    }

    policy.violation_reason(entry)
}

#[cfg(test)]
mod tests {
    use std::collections::HashMap;

    use super::{KernelMode, enforce_plan};
    use crate::core::context_kernel::policy::ContextPolicy;
    use crate::core::context_kernel::types::{ContextPlanV1, PlanBudget, PlanEntry};

    fn entry(object_id: &str, provider: &str) -> PlanEntry {
        PlanEntry {
            object_id: object_id.to_owned(),
            provider: provider.to_owned(),
            view: "summary".to_owned(),
            tokens: 20,
            phi: 1.0,
            reason: "relevant".to_owned(),
        }
    }

    fn plan(selected: Vec<PlanEntry>) -> ContextPlanV1 {
        ContextPlanV1 {
            plan_id: "plan:test".to_owned(),
            intent: "test enforcement".to_owned(),
            budget: PlanBudget {
                total_tokens: 100,
                used_tokens: 40,
                remaining_tokens: 60,
            },
            selected,
            excluded: Vec::new(),
            deferred: Vec::new(),
            provider_stats: HashMap::new(),
        }
    }

    #[test]
    fn shadow_mode_allows_all() {
        let plan = plan(vec![entry("valid", "files"), entry("invalid", "")]);
        let result = enforce_plan(&plan, &ContextPolicy::default(), KernelMode::Shadow);

        assert_eq!(result.allowed.len(), 2);
        assert_eq!(result.blocked.len(), 1);
        assert!(result.explanation.is_none());
    }

    #[test]
    fn enforce_mode_blocks_violations() {
        let plan = plan(vec![entry("valid", "files"), entry("invalid", "")]);
        let result = enforce_plan(&plan, &ContextPolicy::default(), KernelMode::Enforce);

        assert_eq!(result.allowed.len(), 1);
        assert_eq!(result.allowed[0].object_id, "valid");
        assert_eq!(result.blocked[0].object_id, "invalid");
    }

    #[test]
    fn explain_mode_includes_reasoning() {
        let plan = plan(vec![entry("valid", "files"), entry("invalid", "")]);
        let result = enforce_plan(&plan, &ContextPolicy::default(), KernelMode::Explain);

        let explanation = result.explanation.expect("explanation should be present");
        assert!(explanation.contains("valid: allowed"));
        assert!(explanation.contains("invalid: blocked"));
        assert!(explanation.contains("provider is empty"));
    }
}