pub mod combined;
pub mod event;
pub mod evidence_projection;
pub mod push;
pub mod roi;
pub mod signed_batch;
pub mod store;
#[cfg(test)]
mod migration_tests;
pub use combined::CombinedSavingsReport;
pub use event::{MECHANISM_CACHING, MECHANISM_COMPRESSION, MECHANISM_ROUTING, SavingsEvent};
pub use evidence_projection::{
LedgerAttributionLinkV2, LedgerEvidenceProjectionV2, LedgerEvidenceSourceBindingV2,
LedgerProjectionErrorV2, VerifiedLedgerSnapshotV2, load_projection_artifact_v2,
project_settlement_attribution_v2,
};
pub use roi::{RoiReport, roi_report};
pub use signed_batch::{BatchVerifyResult, SignedSavingsBatchV1};
pub use store::{
LedgerSnapshotReadErrorV2, LedgerSummary, VerifyResult, read_verified_snapshot_v2,
};
use std::sync::OnceLock;
use crate::core::ocla::unified_ledger::{FileUnifiedLedger, UnifiedLedger};
fn enabled() -> bool {
enabled_from(std::env::var("LEAN_CTX_SAVINGS_LEDGER").ok().as_deref())
}
fn enabled_from(value: Option<&str>) -> bool {
match value {
Some(v) => !matches!(
v.trim().to_lowercase().as_str(),
"off" | "0" | "false" | "no"
),
None => true,
}
}
fn model_and_price() -> &'static (String, f64) {
static CACHE: OnceLock<(String, f64)> = OnceLock::new();
CACHE.get_or_init(|| {
let resolved = std::env::var("LEAN_CTX_MODEL")
.or_else(|_| std::env::var("LCTX_MODEL"))
.ok()
.filter(|s| !s.trim().is_empty())
.or_else(crate::proxy::usage_meter::persisted_dominant_model);
let quote =
crate::core::gain::model_pricing::ModelPricing::load().quote(resolved.as_deref());
(quote.model_key, quote.cost.input_per_m)
})
}
fn repo_hash() -> &'static str {
static CACHE: OnceLock<String> = OnceLock::new();
CACHE.get_or_init(|| {
use sha2::{Digest, Sha256};
let cwd = std::env::current_dir()
.map(|p| p.to_string_lossy().into_owned())
.unwrap_or_default();
let mut hasher = Sha256::new();
hasher.update(cwd.as_bytes());
let hex = crate::core::agent_identity::hex_encode(&hasher.finalize());
hex.get(..16).unwrap_or(&hex).to_string()
})
}
fn agent_id() -> &'static str {
crate::core::agent_identity::current_agent_id()
}
pub(crate) fn ledger_family() -> crate::core::tokens::TokenizerFamily {
static CACHE: OnceLock<crate::core::tokens::TokenizerFamily> = OnceLock::new();
*CACHE.get_or_init(|| crate::core::tokens::detect_tokenizer(&model_and_price().0))
}
pub fn count_for_ledger(text: &str) -> usize {
crate::core::tokens::count_tokens_for(text, ledger_family())
}
fn tokenizer() -> &'static str {
static CACHE: OnceLock<String> = OnceLock::new();
CACHE.get_or_init(|| ledger_family().to_string())
}
fn new_event(tool: &str) -> SavingsEvent {
let (model_id, price_per_m) = model_and_price();
let evidence_class = if tool == "proxy_route" {
event::EvidenceClass::Approximated
} else {
event::EvidenceClass::Measured
};
SavingsEvent {
ts: chrono::Utc::now().to_rfc3339(),
tool: tool.to_string(),
mechanism: event::MECHANISM_COMPRESSION.to_string(),
model_id: model_id.clone(),
tokenizer: tokenizer().to_string(),
baseline_tokens: 0,
actual_tokens: 0,
saved_tokens: 0,
bounce_adjustment: 0,
unit_price_per_m_usd: *price_per_m,
saved_usd: 0.0,
repo_hash: repo_hash().to_string(),
agent_id: agent_id().to_string(),
prev_hash: String::new(),
entry_hash: String::new(),
version: env!("CARGO_PKG_VERSION").to_string(),
intent_tag: None,
outcome: None,
model_original: None,
model_routed: None,
routing_savings: None,
response_original_tokens: None,
response_delivered_tokens: None,
agent_chain_id: None,
chain_depth: None,
measurement_method: Some(event::MeasurementMethod::DirectCount),
evidence_class: Some(evidence_class),
confidence: None,
request_id: None,
session_id: None,
trace_id: None,
quality_signal: None,
attribution_group: None,
attribution_id: None,
baseline_ref: None,
price_version: None,
customer_approval: None,
settlement_status: None,
is_first_inject: None,
cache_read_per_m_usd: None,
cache_write_per_m_usd: None,
}
}
fn append_with_unified(path: &std::path::Path, event: SavingsEvent, efficiency_etpao: Option<u64>) {
let Ok(event) = store::append(path, event) else {
return;
};
let unified = FileUnifiedLedger::from_savings_event(&event).and_then(|mut event| {
event.efficiency_etpao = efficiency_etpao;
FileUnifiedLedger::from_data_dir()?.record_unified(event)
});
if let Err(error) = unified {
tracing::warn!(%error, "failed to dual-write unified savings event");
}
}
fn compression_quality_signal(original: usize, compressed: usize) -> Option<String> {
if original == 0 {
return None;
}
let ratio = 1.0 - (compressed as f64 / original as f64);
let signal = match ratio {
r if r >= 0.7 => "excellent",
r if r >= 0.5 => "good",
r if r >= 0.3 => "moderate",
_ => "marginal",
};
Some(signal.to_owned())
}
pub fn record_read_event(
original_tokens: usize,
saved_tokens: usize,
quality_signal: Option<&str>,
efficiency_etpao: Option<u64>,
) {
record_tool_event(
"ctx_read",
original_tokens,
original_tokens.saturating_sub(saved_tokens),
quality_signal,
efficiency_etpao,
);
}
pub fn record_tool_event(
tool: &str,
baseline_tokens: usize,
actual_tokens: usize,
quality_signal: Option<&str>,
efficiency_etpao: Option<u64>,
) {
let saved = baseline_tokens.saturating_sub(actual_tokens);
if saved == 0 || !enabled() {
return;
}
let Some(path) = store::default_path() else {
return;
};
let mut event = new_event(tool);
event.attribution_group = Some(
crate::core::attribution::attribution_group_for_mechanism(&event.mechanism).to_string(),
);
event.baseline_tokens = baseline_tokens as u64;
event.actual_tokens = actual_tokens as u64;
event.saved_tokens = saved as u64;
event.saved_usd = saved as f64 / 1_000_000.0 * event.unit_price_per_m_usd;
event.confidence = Some(1.0);
event.quality_signal = quality_signal
.map(str::to_owned)
.or_else(|| compression_quality_signal(baseline_tokens, actual_tokens));
append_with_unified(&path, event, efficiency_etpao);
}
pub fn record_tool_event_with_stream(
tool: &str,
baseline_tokens: usize,
actual_tokens: usize,
is_first_inject: bool,
) {
let saved = baseline_tokens.saturating_sub(actual_tokens);
if saved == 0 || !enabled() {
return;
}
let Some(path) = store::default_path() else {
return;
};
let quote = crate::core::gain::model_pricing::ModelPricing::load().quote(None);
let mut event = new_event(tool);
event.attribution_group = Some(
crate::core::attribution::attribution_group_for_mechanism(&event.mechanism).to_string(),
);
event.baseline_tokens = baseline_tokens as u64;
event.actual_tokens = actual_tokens as u64;
event.saved_tokens = saved as u64;
event.saved_usd = saved as f64 / 1_000_000.0 * event.unit_price_per_m_usd;
event.confidence = Some(1.0);
event.is_first_inject = Some(is_first_inject);
event.cache_read_per_m_usd = Some(quote.cost.cache_read_per_m);
event.cache_write_per_m_usd = Some(quote.cost.cache_write_per_m);
append_with_unified(&path, event, None);
}
pub fn record_routing_event(requested_model: &str, serving_model: &str, input_tokens: u64) {
if input_tokens == 0 || requested_model == serving_model || !enabled() {
return;
}
let Some(path) = store::default_path() else {
return;
};
let pricing = crate::core::gain::model_pricing::ModelPricing::load();
let saved_usd = crate::core::eval_ab::routing_eval::routing_saving_usd(
&pricing,
requested_model,
serving_model,
input_tokens,
);
if saved_usd == 0.0 {
return; }
let mut event = new_event("proxy_route");
event.mechanism = event::MECHANISM_ROUTING.to_string();
event.attribution_group = Some(
crate::core::attribution::attribution_group_for_mechanism(event::MECHANISM_ROUTING)
.to_string(),
);
let quote = pricing.quote(Some(serving_model));
event.model_id = quote.model_key;
event.unit_price_per_m_usd = quote.cost.input_per_m;
event.model_original = Some(requested_model.to_string());
event.model_routed = Some(serving_model.to_string());
event.baseline_tokens = input_tokens;
event.actual_tokens = input_tokens;
event.routing_savings = Some(event.baseline_tokens.saturating_sub(event.actual_tokens));
event.saved_usd = saved_usd;
append_with_unified(&path, event, None);
}
pub fn record_caching_event(model: &str, cache_read_tokens: u64, discount_usd: f64) {
if cache_read_tokens == 0 || discount_usd <= 0.0 || !enabled() {
return;
}
let Some(path) = store::default_path() else {
return;
};
let mut event = new_event("proxy_cache");
event.mechanism = event::MECHANISM_CACHING.to_string();
event.attribution_group = Some(
crate::core::attribution::attribution_group_for_mechanism(event::MECHANISM_CACHING)
.to_string(),
);
let quote = crate::core::gain::model_pricing::ModelPricing::load().quote(Some(model));
event.model_id = quote.model_key;
event.unit_price_per_m_usd = quote.cost.input_per_m;
event.baseline_tokens = cache_read_tokens;
event.actual_tokens = cache_read_tokens;
event.saved_usd = discount_usd;
append_with_unified(&path, event, None);
}
pub fn record_bounce_event(wasted_tokens: usize) {
if wasted_tokens == 0 || !enabled() {
return;
}
let Some(path) = store::default_path() else {
return;
};
let wasted = wasted_tokens as u64;
let mut event = new_event("bounce");
event.attribution_group = Some(
crate::core::attribution::attribution_group_for_mechanism(&event.mechanism).to_string(),
);
event.baseline_tokens = wasted;
event.actual_tokens = wasted;
event.bounce_adjustment = wasted;
event.saved_usd = -(wasted as f64 / 1_000_000.0 * event.unit_price_per_m_usd);
append_with_unified(&path, event, None);
}
pub fn bounce_tokens(days: Option<u32>) -> u64 {
let Some(path) = store::default_path() else {
return 0;
};
store::bounce_tokens_since(&path, days)
}
pub fn summary() -> LedgerSummary {
store::default_path()
.map(|p| store::summarize(&p))
.unwrap_or_default()
}
pub fn daily_bounce_trend(days: u32) -> Vec<(String, u64, u64)> {
store::default_path()
.map(|p| store::daily_bounce_trend(&p, days))
.unwrap_or_default()
}
pub fn verify() -> VerifyResult {
store::default_path().map_or_else(VerifyResult::empty, |p| store::verify(&p))
}
pub fn rechain() -> std::io::Result<usize> {
match store::default_path() {
Some(p) if p.exists() => store::rechain(&p),
_ => Ok(0),
}
}
pub fn all_events() -> Vec<SavingsEvent> {
store::default_path()
.map(|p| store::load(&p))
.unwrap_or_default()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn opt_out_logic_is_correct() {
assert!(enabled_from(None), "enabled by default when unset");
assert!(enabled_from(Some("on")));
assert!(enabled_from(Some("1")));
assert!(!enabled_from(Some("off")));
assert!(!enabled_from(Some("0")));
assert!(!enabled_from(Some("false")));
assert!(!enabled_from(Some(" No ")), "trim + case-insensitive");
}
#[test]
fn repo_hash_is_truncated_hex() {
let h = repo_hash();
assert_eq!(h.len(), 16);
assert!(h.chars().all(|c| c.is_ascii_hexdigit()));
}
#[test]
fn count_for_ledger_is_a_real_bpe_count_for_resolved_family() {
let text = "fn honest_accounting(n: u64) -> u64 { n }";
assert_eq!(
count_for_ledger(text),
crate::core::tokens::count_tokens_for(text, ledger_family())
);
assert!(count_for_ledger(text) > 0);
assert_eq!(count_for_ledger(""), 0);
}
#[test]
fn tokenizer_label_matches_ledger_family() {
assert_eq!(tokenizer(), ledger_family().to_string());
}
#[test]
fn test_quality_signal_excellent() {
assert_eq!(
compression_quality_signal(100, 30).as_deref(),
Some("excellent")
);
}
#[test]
fn test_quality_signal_good() {
assert_eq!(compression_quality_signal(100, 50).as_deref(), Some("good"));
assert_eq!(compression_quality_signal(100, 31).as_deref(), Some("good"));
}
#[test]
fn test_quality_signal_moderate() {
assert_eq!(
compression_quality_signal(100, 70).as_deref(),
Some("moderate")
);
assert_eq!(
compression_quality_signal(100, 51).as_deref(),
Some("moderate")
);
}
#[test]
fn test_quality_signal_marginal() {
assert_eq!(
compression_quality_signal(100, 71).as_deref(),
Some("marginal")
);
assert_eq!(compression_quality_signal(0, 0), None);
}
#[test]
fn test_record_tool_event_carries_quality() {
let _lock = crate::core::data_dir::test_env_lock();
let dir =
std::env::temp_dir().join(format!("lctx-ledger-quality-test-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).expect("mkdir");
crate::test_env::set_var("LEAN_CTX_DATA_DIR", dir.to_str().unwrap());
record_tool_event("cli_shell", 100, 20, Some("verified"), Some(800));
let ledger = dir.join("savings").join("ledger.jsonl");
let content = std::fs::read_to_string(&ledger).expect("ledger written");
let unified_path = dir.join("savings").join("unified_ledger.jsonl");
let unified_content =
std::fs::read_to_string(&unified_path).expect("unified ledger written");
crate::test_env::remove_var("LEAN_CTX_DATA_DIR");
let _ = std::fs::remove_dir_all(&dir);
let event: SavingsEvent =
serde_json::from_str(content.lines().next().unwrap()).expect("valid event JSON");
assert_eq!(event.quality_signal.as_deref(), Some("verified"));
let unified: crate::core::ocla::unified_ledger::UnifiedSavingsEventV2 =
serde_json::from_str(unified_content.lines().next().unwrap())
.expect("valid unified event JSON");
assert_eq!(unified.efficiency_etpao, Some(800));
}
#[test]
fn record_tool_event_skips_zero_and_inverted_savings() {
record_tool_event("cli_shell", 100, 100, None, None);
record_tool_event("ctx_search", 50, 80, None, None);
record_tool_event("cli_shell", 0, 0, None, None);
}
#[test]
fn record_tool_event_appends_measured_event() {
let _lock = crate::core::data_dir::test_env_lock();
let dir = std::env::temp_dir().join(format!("lctx-ledger-test-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).expect("mkdir");
crate::test_env::set_var("LEAN_CTX_DATA_DIR", dir.to_str().unwrap());
record_tool_event("cli_shell", 5000, 800, None, None);
let ledger = dir.join("savings").join("ledger.jsonl");
let content = std::fs::read_to_string(&ledger).expect("ledger written");
let unified_path = dir.join("savings").join("unified_ledger.jsonl");
let unified_content =
std::fs::read_to_string(&unified_path).expect("unified ledger written");
crate::test_env::remove_var("LEAN_CTX_DATA_DIR");
let _ = std::fs::remove_dir_all(&dir);
let last = content.lines().last().expect("one event");
let ev: SavingsEvent = serde_json::from_str(last).expect("valid event JSON");
assert_eq!(ev.tool, "cli_shell");
assert_eq!(ev.mechanism, MECHANISM_COMPRESSION);
assert_eq!(ev.baseline_tokens, 5000, "raw baseline, no estimate factor");
assert_eq!(ev.actual_tokens, 800);
assert_eq!(ev.saved_tokens, 4200);
assert_eq!(
ev.measurement_method,
Some(event::MeasurementMethod::DirectCount)
);
assert_eq!(ev.evidence_class, Some(event::EvidenceClass::Measured));
assert_eq!(ev.confidence, Some(1.0));
let unified: crate::core::ocla::unified_ledger::UnifiedSavingsEventV2 =
serde_json::from_str(unified_content.lines().next().unwrap()).unwrap();
assert_eq!(unified.tool_name, ev.tool);
assert_eq!(unified.mode, ev.mechanism);
assert_eq!(unified.original_tokens, ev.baseline_tokens);
assert_eq!(unified.compressed_tokens, ev.actual_tokens);
assert_eq!(unified.saved_tokens, ev.saved_tokens);
assert_eq!(unified.content_hash, ev.repo_hash);
assert_eq!(unified.prev_hash, ev.prev_hash);
assert_eq!(unified.event_hash, ev.entry_hash);
assert_eq!(unified.agent_id.as_deref(), Some(ev.agent_id.as_str()));
assert_eq!(unified.attribution_id, ev.repo_hash);
}
#[test]
fn record_routing_event_appends_rate_delta() {
let _lock = crate::core::data_dir::test_env_lock();
let dir = std::env::temp_dir().join(format!("lctx-ledger-route-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).expect("mkdir");
crate::test_env::set_var("LEAN_CTX_DATA_DIR", dir.to_str().unwrap());
record_routing_event("claude-opus-4.5", "claude-opus-4.5", 10_000); record_routing_event("claude-opus-4.5", "phi-4", 0); record_routing_event("claude-opus-4.5", "phi-4", 10_000);
let ledger = dir.join("savings").join("ledger.jsonl");
let content = std::fs::read_to_string(&ledger).expect("ledger written");
crate::test_env::remove_var("LEAN_CTX_DATA_DIR");
let _ = std::fs::remove_dir_all(&dir);
assert_eq!(
content.lines().count(),
1,
"only the real route is recorded"
);
let ev: SavingsEvent =
serde_json::from_str(content.lines().next().unwrap()).expect("valid JSON");
assert_eq!(ev.tool, "proxy_route");
assert_eq!(ev.mechanism, MECHANISM_ROUTING);
assert_eq!(ev.model_id, "phi-4", "denominated in the serving model");
assert_eq!(ev.model_original.as_deref(), Some("claude-opus-4.5"));
assert_eq!(ev.model_routed.as_deref(), Some("phi-4"));
assert_eq!(ev.routing_savings, Some(0));
assert_eq!(
ev.measurement_method,
Some(event::MeasurementMethod::DirectCount)
);
assert_eq!(ev.evidence_class, Some(event::EvidenceClass::Approximated));
assert_eq!(ev.saved_tokens, 0, "routing saves dollars, not tokens");
assert!((ev.saved_usd - 0.048_75).abs() < 1e-9);
}
#[test]
fn record_tool_event_sets_attribution_group() {
let _lock = crate::core::data_dir::test_env_lock();
let dir = std::env::temp_dir().join(format!("lctx-ledger-attr-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).expect("mkdir");
crate::test_env::set_var("LEAN_CTX_DATA_DIR", dir.to_str().unwrap());
record_tool_event("ctx_read", 1000, 200, None, None);
let ledger = dir.join("savings").join("ledger.jsonl");
let content = std::fs::read_to_string(&ledger).expect("ledger written");
crate::test_env::remove_var("LEAN_CTX_DATA_DIR");
let _ = std::fs::remove_dir_all(&dir);
let ev: SavingsEvent =
serde_json::from_str(content.lines().next().unwrap()).expect("valid event JSON");
assert_eq!(
ev.attribution_group.as_deref(),
Some("input_optimization"),
"compression events must have attribution_group set"
);
}
}