Files
yvgude--lean-ctx/rust/tests/outcome_pricing_e2e.rs
T
wehub-resource-sync 26382a7ac6
CI / Clippy (push) Failing after 15m13s
CI / Test (ubuntu-latest) (push) Failing after 16m1s
CI / Test (macos-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Build (no embeddings / no ORT) (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / Cookbook (Node) (push) Has been cancelled
CI / Pi Extension (Node) (push) Has been cancelled
CI / Rust SDK (lean-ctx-client) (push) Has been cancelled
CI / Embed SDK (lean-ctx-sdk) (push) Has been cancelled
CI / Python SDK (leanctx) (push) Has been cancelled
CI / Hermes Plugin (Python) (push) Has been cancelled
CI / SDK Conformance Matrix (push) Has been cancelled
CI / Coverage (push) Has been cancelled
CI / cargo-deny (push) Has been cancelled
CI / Adversarial Safety (push) Has been cancelled
CI / Benchmarks (push) Has been cancelled
CI / Output-Quality Gate (eval A/B) (push) Has been cancelled
CI / Documentation (push) Has been cancelled
CI / CI Green (push) Has been cancelled
JetBrains Plugin / Actionlint (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
CodeQL / Analyze (rust) (push) Has been cancelled
JetBrains Plugin / Validation (push) Has been cancelled
JetBrains Plugin / Build (push) Has been cancelled
JetBrains Plugin / Test (push) Has been cancelled
Security Check / Security Scan (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:35:30 +08:00

172 lines
7.4 KiB
Rust

//! End-to-end "durchspielbar" proof for **Outcome-Based Pricing** (Epic #671).
//!
//! This is the open-engine half of the success-fee golden path, exercised with
//! REAL data — there are no fabricated token counts or savings anywhere:
//!
//! 1. real shell-output compression (`shell::compress`, the exact path
//! `lean-ctx -c` runs), measured with the real tokenizer;
//! 2. recorded into the append-only, SHA-256 hash-chained **savings ledger**;
//! 3. exported as an Ed25519 **signed batch** and verified **offline** (plus a
//! tamper check that must break the signature);
//! 4. metered into a **billable usage** record (`is_billable = signed &&
//! chain_valid`);
//! 5. rendered as a **FOCUS** `FinOps` export carrying the savings as a Credit row.
//!
//! The private control-plane (`lean-ctx-cloud`) consumes the verified total from
//! steps 4/5 to raise the Stripe success-fee invoice. That commercial half is
//! proven in the cloud repo and is deliberately absent here
//! (`oss-plane-separation-v1`).
use lean_ctx::core::billing::metering::metered_usage;
use lean_ctx::core::finops_export::{self, DateRange};
use lean_ctx::core::savings_ledger::{self, SignedSavingsBatchV1, signed_batch};
use lean_ctx::core::tokens::count_tokens;
use lean_ctx::shell::compress::compress_if_beneficial_pub;
const AGENT_ID: &str = "pilot-e2e";
/// A realistic, reliably-compressible `git status` — the exact thing a coding
/// agent runs constantly. Built from real git status lines so the saving is a
/// genuine measurement of the engine compressor, never a hand-picked number.
fn realistic_git_status(modified: usize, untracked: usize) -> String {
let mut out = String::from(
"On branch main\nYour branch is up to date with 'origin/main'.\n\n\
Changes not staged for commit:\n \
(use \"git add <file>...\" to update what will be committed)\n \
(use \"git restore <file>...\" to discard changes in working directory)\n",
);
for i in 0..modified {
out.push_str(&format!("\tmodified: rust/src/core/module_{i:02}.rs\n"));
}
out.push_str(
"\nUntracked files:\n (use \"git add <file>...\" to include in what will be committed)\n",
);
for i in 0..untracked {
out.push_str(&format!("\trust/src/core/new_module_{i:02}.rs\n"));
}
out.push_str("\nno changes added to commit (use \"git add\" and/or \"git commit -a\")\n");
out
}
/// Record one measured shell-compression event. Returns `(baseline, actual)`
/// token counts so the caller can assert a real saving occurred.
fn record_real_compression(command: &str, raw: &str) -> (usize, usize) {
let compressed = compress_if_beneficial_pub(command, raw);
let baseline = count_tokens(raw);
let actual = count_tokens(&compressed);
savings_ledger::record_tool_event("cli_shell", baseline, actual);
(baseline, actual)
}
#[test]
fn outcome_pricing_golden_path_is_real_and_billable() {
// Isolated data dir → the savings ledger AND the Ed25519 keystore both live
// in a throwaway temp dir (`agent_identity::key_path` resolves via the data
// dir), so signing is real but touches nothing on the developer machine.
let tmp = std::env::temp_dir().join(format!("lctx-outcome-e2e-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&tmp);
std::fs::create_dir_all(&tmp).expect("mkdir temp data dir");
// SAFETY: set before any cached resolver runs; this is a single-threaded
// integration-test binary and the env is torn down at the end.
unsafe {
std::env::set_var("LEAN_CTX_DATA_DIR", &tmp);
std::env::set_var("LEAN_CTX_AGENT_ID", AGENT_ID);
std::env::set_var("LEAN_CTX_MODEL", "gpt-4o");
std::env::set_var("LEAN_CTX_SAVINGS_LEDGER", "on");
}
// ── 1 + 2. Real compression → measured savings → signed hash chain ────────
let (b1, a1) = record_real_compression("git status", &realistic_git_status(40, 25));
let (b2, a2) = record_real_compression("git status", &realistic_git_status(18, 9));
assert!(
b1 > a1,
"git status must compress (baseline {b1} > actual {a1})"
);
assert!(
b2 > a2,
"git status must compress (baseline {b2} > actual {a2})"
);
let summary = savings_ledger::summary();
assert!(summary.total_events >= 2, "two measured events recorded");
assert!(
summary.net_saved_tokens() > 0,
"real net token savings recorded"
);
assert!(
summary.saved_usd > 0.0,
"savings carry a USD value at the pinned model price"
);
// Chain integrity — the tamper-evidence the whole meter rests on.
assert!(
savings_ledger::verify().valid,
"SHA-256 chain verifies intact"
);
// ── 3. Signed batch + OFFLINE verification + tamper check ─────────────────
let mut batch = SignedSavingsBatchV1::build_all(AGENT_ID);
batch.sign(AGENT_ID).expect("sign with machine identity");
let artifact = tmp.join("signed-batch.json");
signed_batch::write_artifact(&batch, &artifact).expect("write artifact");
let loaded = signed_batch::load_artifact(&artifact).expect("load artifact");
assert!(
loaded.verify().signature_valid,
"signed batch verifies offline, without the raw ledger"
);
assert_eq!(
loaded.totals.net_saved_tokens,
summary.net_saved_tokens(),
"the signed totals commit the same savings the ledger reports"
);
// A single flipped byte in the totals must break the signature.
let mut tampered = loaded.clone();
tampered.totals.net_saved_tokens += 1;
assert!(
!tampered.verify().signature_valid,
"tampering with the totals invalidates the signature"
);
// ── 4. Billable usage meter (is_billable = signed && chain_valid) ─────────
let usage = metered_usage(AGENT_ID);
assert!(
usage.is_billable(),
"usage is billable: {}",
usage.headline()
);
assert!(usage.signed && usage.chain_valid);
assert_eq!(
usage.net_saved_tokens,
summary.net_saved_tokens(),
"the billed quantity is exactly the verified, signed savings"
);
// ── 5. FOCUS FinOps export — savings as a Credit row ──────────────────────
let rows = finops_export::aggregate(&DateRange::default());
assert!(!rows.is_empty(), "ledger aggregates into FOCUS rows");
let csv = finops_export::focus::to_csv(&rows);
assert!(
csv.contains("Credit"),
"FOCUS export carries a Credit (savings) row"
);
assert!(csv.contains("LeanCTX"), "provider is attributed");
// Credit rows put the savings in BilledCost (column 0) as a negative value,
// so exactly those rows — and not the header or Usage rows — begin with '-'.
assert!(
csv.lines().any(|l| l.starts_with('-')),
"a credit row with negative BilledCost is present"
);
// ── teardown ──────────────────────────────────────────────────────────────
unsafe {
std::env::remove_var("LEAN_CTX_DATA_DIR");
std::env::remove_var("LEAN_CTX_AGENT_ID");
std::env::remove_var("LEAN_CTX_MODEL");
std::env::remove_var("LEAN_CTX_SAVINGS_LEDGER");
}
let _ = std::fs::remove_dir_all(&tmp);
}