lean_ctx/core/context_kernel/
envelope_wiring.rs1use 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#[derive(Debug, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize)]
12pub struct EvidenceSummary {
13 pub proxy_requests: usize,
15 pub mcp_calls: usize,
17 pub total_envelopes: usize,
19 pub chain_entries: usize,
21 pub compression_ratio: f64,
23 pub kernel_hit_rate: f64,
25}
26
27pub 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
61pub 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#[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
118pub 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}