Skip to main content

lean_ctx/core/context_kernel/
envelope_e2e.rs

1//! End-to-end conformance tests for the R25 evidence pipeline.
2
3#[cfg(test)]
4mod tests {
5    use std::sync::MutexGuard;
6
7    use super::super::accounting_fix;
8    use super::super::live_dashboard;
9    use super::super::mcp_bridge::{self, McpCallData};
10    use super::super::proxy_bridge::{self, ProxyRequestData};
11    use super::super::receipt_chain;
12    use super::super::token_envelope::{self, ProviderKind, TokenEnvelope};
13    use super::super::types::ReceiptOutcome;
14    use super::super::usage_normalizer;
15
16    fn isolated_test() -> MutexGuard<'static, ()> {
17        let guard = crate::core::context_kernel::kernel_config::KERNEL_TEST_LOCK
18            .lock()
19            .unwrap_or_else(std::sync::PoisonError::into_inner);
20        proxy_bridge::reset_state();
21        mcp_bridge::reset_mcp_state();
22        usage_normalizer::reset_usage();
23        receipt_chain::reset_chain();
24        crate::core::context_kernel::kernel_config::reset_features();
25        guard
26    }
27
28    fn proxy_request(model: &str, input_tokens: usize, tokens_saved: usize) -> ProxyRequestData {
29        ProxyRequestData {
30            headers: vec![("x-user-id".to_owned(), "e2e-user".to_owned())],
31            input_tokens,
32            output_tokens: 100,
33            reasoning_tokens: 25,
34            tokens_saved,
35            model: Some(model.to_owned()),
36            provider: Some("OpenAI".to_owned()),
37            request_count: 1,
38            ..ProxyRequestData::default()
39        }
40    }
41
42    fn mcp_call(tool_name: &str) -> McpCallData {
43        McpCallData {
44            tool_name: tool_name.to_owned(),
45            input_tokens: 400,
46            output_tokens: 80,
47            is_retry: false,
48            call_number: 1,
49        }
50    }
51
52    fn accounting() -> accounting_fix::PostDeliveryAccounting {
53        accounting_fix::compute_honest_accounting(1_000, 700, 50, 20)
54    }
55
56    fn record_receipt(envelope: &TokenEnvelope, outcome: ReceiptOutcome, kernel_hit: bool) {
57        receipt_chain::record_chain_entry(
58            "e2e",
59            envelope.clone(),
60            accounting(),
61            outcome,
62            kernel_hit,
63            usize::from(kernel_hit) * 50,
64        );
65    }
66
67    #[test]
68    fn proxy_to_envelope_to_normalizer() {
69        let _guard = isolated_test();
70        let request = proxy_request("gpt-5", 1_000, 300);
71
72        let envelope = token_envelope::from_proxy_data(&request);
73        assert_eq!(envelope.provider, ProviderKind::OpenAi);
74
75        usage_normalizer::record_envelope(&envelope);
76        assert_eq!(usage_normalizer::session_usage().total_requests, 1);
77        assert!(
78            usage_normalizer::model_breakdown()
79                .iter()
80                .any(|(model, _)| model == "gpt-5")
81        );
82    }
83
84    #[test]
85    fn mcp_to_envelope_pipeline() {
86        let _guard = isolated_test();
87        let call = mcp_call("ctx_read");
88
89        let envelope = token_envelope::from_mcp_call(&call);
90        assert_eq!(envelope.provider, ProviderKind::Unknown);
91        assert!(envelope.model.is_empty(), "MCP calls have no model");
92
93        usage_normalizer::record_envelope(&envelope);
94        assert_eq!(usage_normalizer::session_usage().total_requests, 1);
95    }
96
97    #[test]
98    fn full_receipt_chain() {
99        let _guard = isolated_test();
100        for index in 0..5 {
101            let envelope = token_envelope::from_proxy_data(&proxy_request("gpt-5", 1_000, 300));
102            let accepted = index < 3;
103            let outcome = if accepted {
104                ReceiptOutcome::Accepted
105            } else {
106                ReceiptOutcome::Rejected
107            };
108            record_receipt(&envelope, outcome, accepted);
109        }
110
111        let summary = receipt_chain::chain_summary();
112        assert_eq!(summary.total_entries, 5);
113        assert_eq!(summary.accepted, 3);
114        assert_eq!(summary.rejected, 2);
115        let kernel_hit_rate = summary.kernel_supplemented as f64 / summary.total_entries as f64;
116        assert!((kernel_hit_rate - 0.6).abs() < f64::EPSILON);
117    }
118
119    #[test]
120    fn dashboard_aggregates_all() {
121        let _guard = isolated_test();
122        for _ in 0..3 {
123            let request = proxy_request("gpt-5", 1_000, 300);
124            let _ = proxy_bridge::process_proxy_request(&request);
125            let envelope = token_envelope::from_proxy_data(&request);
126            usage_normalizer::record_envelope(&envelope);
127            record_receipt(&envelope, ReceiptOutcome::Accepted, true);
128        }
129        for tool in ["ctx_read", "ctx_search"] {
130            let call = mcp_call(tool);
131            mcp_bridge::record_mcp_call(&call);
132            let envelope = token_envelope::from_mcp_call(&call);
133            usage_normalizer::record_envelope(&envelope);
134            record_receipt(&envelope, ReceiptOutcome::Accepted, true);
135        }
136
137        let snapshot = live_dashboard::snapshot();
138        assert!(snapshot.proxy_etpao > 0.0);
139        assert_eq!(snapshot.usage.total_requests, 5);
140        assert_eq!(snapshot.chain.total_entries, 5);
141    }
142
143    #[test]
144    fn compression_overview_accurate() {
145        let _guard = isolated_test();
146        for _ in 0..5 {
147            usage_normalizer::record_envelope(&token_envelope::from_proxy_data(&proxy_request(
148                "gpt-5-mini",
149                1_000,
150                100,
151            )));
152            usage_normalizer::record_envelope(&token_envelope::from_proxy_data(&proxy_request(
153                "gpt-5", 1_000, 500,
154            )));
155        }
156
157        let overview = usage_normalizer::compression_overview();
158        let expected = 3_000.0 / 13_000.0;
159        assert!((overview.avg_compression_ratio - expected).abs() < 1.0e-9);
160        assert_eq!(overview.best_model.as_deref(), Some("gpt-5"));
161    }
162
163    #[test]
164    fn receipt_chain_honest_accounting() {
165        let _guard = isolated_test();
166        let envelope = token_envelope::from_proxy_data(&proxy_request("gpt-5", 1_000, 300));
167        let accounting = accounting_fix::compute_honest_accounting(1_000, 600, 250, 100);
168        receipt_chain::record_chain_entry(
169            "proxy",
170            envelope,
171            accounting,
172            ReceiptOutcome::Accepted,
173            true,
174            250,
175        );
176
177        assert!(receipt_chain::chain_summary().total_phantom_savings_pct > 0.0);
178    }
179
180    #[test]
181    fn end_to_end_evidence_pipeline() {
182        let _guard = isolated_test();
183        let request = proxy_request("gpt-5", 1_000, 300);
184        let _ = proxy_bridge::process_proxy_request(&request);
185        let envelope = token_envelope::from_proxy_data(&request);
186        usage_normalizer::record_envelope(&envelope);
187        record_receipt(&envelope, ReceiptOutcome::Accepted, true);
188
189        let snapshot = live_dashboard::snapshot();
190        assert_eq!(snapshot.usage.total_requests, 1);
191        assert_eq!(snapshot.usage.total_tokens, envelope.total_tokens());
192        assert_eq!(snapshot.chain.total_entries, 1);
193        assert_eq!(snapshot.chain.accepted, 1);
194        assert!(snapshot.proxy_etpao > 0.0);
195    }
196
197    #[test]
198    fn envelope_merge_is_additive() {
199        let _guard = isolated_test();
200        let envelopes = [
201            token_envelope::from_proxy_data(&proxy_request("gpt-5", 100, 10)),
202            token_envelope::from_proxy_data(&proxy_request("gpt-5", 200, 20)),
203            token_envelope::from_proxy_data(&proxy_request("gpt-5", 300, 30)),
204        ];
205        let expected = envelopes
206            .iter()
207            .map(TokenEnvelope::total_tokens)
208            .sum::<usize>();
209
210        let merged = TokenEnvelope::merge(&envelopes);
211        assert_eq!(merged.total_tokens(), expected);
212    }
213}