Skip to main content

lean_ctx/core/context_kernel/
envelope_wiring.rs

1//! Activation wiring for the kernel evidence pipeline.
2
3use super::{
4    accounting_fix, kernel_config, outcome_signal, receipt_chain, token_envelope, usage_normalizer,
5};
6
7const PROXY_SOURCE: &str = "proxy";
8const MCP_SOURCE: &str = "mcp";
9
10/// Aggregated evidence recorded by the active kernel pipeline.
11#[derive(Debug, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize)]
12pub struct EvidenceSummary {
13    /// Proxy requests represented in the receipt chain.
14    pub proxy_requests: usize,
15    /// MCP calls represented in the receipt chain.
16    pub mcp_calls: usize,
17    /// Canonical token envelopes recorded by the usage normalizer.
18    pub total_envelopes: usize,
19    /// Delivery lifecycles recorded in the receipt chain.
20    pub chain_entries: usize,
21    /// Aggregate saved-token ratio across recorded envelopes.
22    pub compression_ratio: f64,
23    /// Fraction of receipt entries supplemented by the kernel.
24    pub kernel_hit_rate: f64,
25}
26
27/// Records proxy usage and delivery evidence when the kernel is enabled.
28pub fn process_proxy_evidence(
29    data: &super::proxy_bridge::ProxyRequestData,
30    result: &super::proxy_bridge::ProxyKernelResult,
31) {
32    if !kernel_config::is_enabled() {
33        return;
34    }
35
36    let envelope = token_envelope::from_proxy_data(data);
37    if kernel_config::is_feature_enabled("usage_tracking") {
38        usage_normalizer::record_envelope(&envelope);
39    }
40
41    let accounting = accounting_fix::compute_honest_accounting(
42        data.input_tokens.saturating_add(data.tokens_saved),
43        data.input_tokens,
44        0,
45        0,
46    );
47    if kernel_config::is_feature_enabled("receipt_chain") {
48        receipt_chain::record_chain_entry(
49            PROXY_SOURCE,
50            envelope,
51            accounting,
52            result.outcome_signal.outcome,
53            false,
54            0,
55        );
56    }
57
58    tracing::trace!(source = PROXY_SOURCE, "processed kernel evidence");
59}
60
61/// Records MCP usage and delivery evidence when the kernel is enabled.
62pub fn process_mcp_evidence(data: &super::mcp_bridge::McpCallData) {
63    if !kernel_config::is_enabled() {
64        return;
65    }
66
67    let envelope = token_envelope::from_mcp_call(data);
68    if kernel_config::is_feature_enabled("usage_tracking") {
69        usage_normalizer::record_envelope(&envelope);
70    }
71
72    let accounting =
73        accounting_fix::compute_honest_accounting(data.input_tokens, data.output_tokens, 0, 0);
74    if kernel_config::is_feature_enabled("receipt_chain") {
75        let call_number = if data.is_retry {
76            data.call_number.max(2)
77        } else {
78            data.call_number
79        };
80        let outcome = outcome_signal::infer_outcome(call_number, data.is_retry, data.output_tokens);
81        receipt_chain::record_chain_entry(
82            MCP_SOURCE,
83            envelope,
84            accounting,
85            outcome.outcome,
86            false,
87            0,
88        );
89    }
90
91    tracing::trace!(source = MCP_SOURCE, "processed kernel evidence");
92}
93
94/// Returns aggregate usage and receipt evidence for the current process.
95#[must_use]
96pub fn evidence_summary() -> EvidenceSummary {
97    let usage = usage_normalizer::session_usage();
98    let compression = usage_normalizer::compression_overview();
99    let chain = receipt_chain::chain_summary();
100    let entries = receipt_chain::chain_entries();
101
102    EvidenceSummary {
103        proxy_requests: entries
104            .iter()
105            .filter(|entry| entry.source == PROXY_SOURCE)
106            .count(),
107        mcp_calls: entries
108            .iter()
109            .filter(|entry| entry.source == MCP_SOURCE)
110            .count(),
111        total_envelopes: usage.total_requests,
112        chain_entries: chain.total_entries,
113        compression_ratio: compression.avg_compression_ratio,
114        kernel_hit_rate: receipt_chain::kernel_hit_rate(),
115    }
116}
117
118/// Clears all evidence and restores the kernel feature defaults.
119pub fn reset_evidence() {
120    usage_normalizer::reset_usage();
121    receipt_chain::reset_chain();
122    kernel_config::reset_features();
123}
124
125#[cfg(test)]
126mod tests {
127    use std::sync::MutexGuard;
128
129    use super::{
130        EvidenceSummary, evidence_summary, process_mcp_evidence, process_proxy_evidence,
131        reset_evidence,
132    };
133    use crate::core::context_kernel::kernel_config::{self, KernelFeatures};
134    use crate::core::context_kernel::mcp_bridge::McpCallData;
135    use crate::core::context_kernel::proxy_bridge::{self, ProxyRequestData};
136    use crate::core::context_kernel::{receipt_chain, usage_normalizer};
137
138    fn isolated() -> MutexGuard<'static, ()> {
139        let guard = crate::core::context_kernel::kernel_config::KERNEL_TEST_LOCK
140            .lock()
141            .unwrap_or_else(std::sync::PoisonError::into_inner);
142        reset_evidence();
143        crate::core::context_kernel::proxy_bridge::reset_state();
144        crate::core::context_kernel::mcp_bridge::reset_mcp_state();
145        guard
146    }
147
148    fn proxy_data() -> ProxyRequestData {
149        ProxyRequestData {
150            input_tokens: 100,
151            output_tokens: 20,
152            tokens_saved: 50,
153            request_count: 1,
154            ..ProxyRequestData::default()
155        }
156    }
157
158    fn process_proxy() {
159        let data = proxy_data();
160        let result = proxy_bridge::process_proxy_request(&data);
161        process_proxy_evidence(&data, &result);
162    }
163
164    fn mcp_data(number: usize) -> McpCallData {
165        McpCallData {
166            tool_name: "ctx_read".to_owned(),
167            input_tokens: 80,
168            output_tokens: 20,
169            call_number: number,
170            ..McpCallData::default()
171        }
172    }
173
174    #[test]
175    fn proxy_evidence_records_envelope() {
176        let _guard = isolated();
177        process_proxy();
178        assert_eq!(usage_normalizer::session_usage().total_requests, 1);
179    }
180
181    #[test]
182    fn proxy_evidence_records_chain() {
183        let _guard = isolated();
184        process_proxy();
185        assert!(receipt_chain::chain_length() > 0);
186    }
187
188    #[test]
189    fn mcp_evidence_records() {
190        let _guard = isolated();
191        process_mcp_evidence(&mcp_data(1));
192        assert_eq!(usage_normalizer::session_usage().total_requests, 1);
193    }
194
195    #[test]
196    fn disabled_kernel_skips() {
197        let _guard = isolated();
198        let features = KernelFeatures {
199            enabled: false,
200            ..KernelFeatures::default()
201        };
202        kernel_config::update_features(features);
203        process_proxy();
204        assert_eq!(evidence_summary(), EvidenceSummary::default());
205    }
206
207    #[test]
208    fn evidence_summary_aggregates() {
209        let _guard = isolated();
210        for _ in 0..3 {
211            process_proxy();
212        }
213        for number in 1..=2 {
214            process_mcp_evidence(&mcp_data(number));
215        }
216        let summary = evidence_summary();
217        assert_eq!(summary.proxy_requests, 3);
218        assert_eq!(summary.mcp_calls, 2);
219        assert_eq!(summary.total_envelopes, 5);
220        assert_eq!(summary.chain_entries, 5);
221    }
222
223    #[test]
224    fn reset_clears_all() {
225        let _guard = isolated();
226        process_proxy();
227        process_mcp_evidence(&mcp_data(1));
228        reset_evidence();
229        crate::core::context_kernel::proxy_bridge::reset_state();
230        crate::core::context_kernel::mcp_bridge::reset_mcp_state();
231        assert_eq!(evidence_summary(), EvidenceSummary::default());
232        assert!(kernel_config::is_enabled());
233    }
234}