lean_ctx/proxy/cache_safety.rs
1//! Cache-preservation telemetry for the proxy's frozen-region prose rewrites
2//! (#710).
3//!
4//! The proxy only ever rewrites prose inside the cache-safe frozen window
5//! `[cached_prefix_len, boundary)` — never inside the client-cached prefix and
6//! never in the live tail. This module turns that invariant into a *measurable*
7//! production signal: every request that performs a frozen-region prose rewrite
8//! reports whether the rewrite stayed cache-safe, and `/status` surfaces the
9//! resulting ratio (`1.0` = every rewrite was provably cache-safe, the
10//! healthy steady state). A value below `1.0` is a regression signal.
11
12use std::sync::atomic::{AtomicU64, Ordering};
13
14use serde::{Deserialize, Serialize};
15
16/// Total prose segments (text fields) compressed across all requests.
17static PROSE_SEGMENTS: AtomicU64 = AtomicU64::new(0);
18/// Requests that performed at least one frozen-region prose rewrite.
19static PROSE_REQUESTS: AtomicU64 = AtomicU64::new(0);
20/// Of those, the requests whose every rewrite was cache-safe.
21static CACHE_SAFE_REQUESTS: AtomicU64 = AtomicU64::new(0);
22
23/// Record one request's frozen-region prose activity.
24///
25/// `segments` is how many prose fields were compressed this request; `all_safe`
26/// is `true` when *every* rewrite landed strictly inside the cache-safe frozen
27/// window. A no-op request (`segments == 0`) is not counted, so the ratio
28/// reflects only requests that actually mutated prose.
29pub fn record(segments: u64, all_safe: bool) {
30 if segments == 0 {
31 return;
32 }
33 PROSE_SEGMENTS.fetch_add(segments, Ordering::Relaxed);
34 PROSE_REQUESTS.fetch_add(1, Ordering::Relaxed);
35 if all_safe {
36 CACHE_SAFE_REQUESTS.fetch_add(1, Ordering::Relaxed);
37 }
38}
39
40/// Cache-preservation ratio: `safe / total`, or `1.0` when nothing has been
41/// rewritten yet (the trivially-safe empty state). Pure, so it is unit-tested
42/// independently of the global counters.
43#[must_use]
44pub fn ratio(safe: u64, total: u64) -> f64 {
45 if total == 0 {
46 return 1.0;
47 }
48 safe as f64 / total as f64
49}
50
51/// Point-in-time view of the cache-safety counters for `/status`.
52#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
53pub struct CacheSafety {
54 /// Prose segments compressed in the frozen region (cumulative).
55 pub prose_segments_compressed: u64,
56 /// Requests that performed at least one frozen-region prose rewrite.
57 pub prose_requests: u64,
58 /// Fraction of those requests whose every rewrite was cache-safe (`1.0` is
59 /// the healthy steady state; the proxy only rewrites inside the cache-safe
60 /// window by construction).
61 pub cache_safe_ratio: f64,
62}
63
64#[must_use]
65pub fn snapshot() -> CacheSafety {
66 let prose_requests = PROSE_REQUESTS.load(Ordering::Relaxed);
67 let safe = CACHE_SAFE_REQUESTS.load(Ordering::Relaxed);
68 CacheSafety {
69 prose_segments_compressed: PROSE_SEGMENTS.load(Ordering::Relaxed),
70 prose_requests,
71 cache_safe_ratio: ratio(safe, prose_requests),
72 }
73}
74
75#[cfg(test)]
76mod tests {
77 use super::*;
78
79 #[test]
80 fn ratio_is_one_when_empty() {
81 assert_eq!(ratio(0, 0), 1.0);
82 }
83
84 #[test]
85 fn ratio_reflects_unsafe_rewrites() {
86 assert_eq!(ratio(3, 3), 1.0);
87 assert_eq!(ratio(2, 4), 0.5);
88 assert_eq!(ratio(0, 2), 0.0);
89 }
90}