use crate::context::token_encoding_family_for_model;
use serde::Serialize;
use std::{io::Write, path::Path};
mod codecs;
mod scanner;
const PROTOTYPE_LABEL: &str = "offline_compression_measurement_v0";
const SCOPE_LABEL: &str = "active_primary_and_subagents";
const MEASUREMENT_UNIT: &str = "unique_stored_tool_results";
pub(crate) const DEFAULT_MAX_FILES: usize = 100;
pub(crate) const MAX_SESSION_FILES: usize = 10_000;
pub(crate) const MIN_MAX_BYTES: u64 = 1024 * 1024;
pub(crate) const DEFAULT_MAX_BYTES: u64 = 128 * 1024 * 1024;
pub(crate) const MAX_MAX_BYTES: u64 = 1024 * 1024 * 1024;
const MAX_JSONL_LINES: usize = 100_000;
const MAX_MODEL_BYTES: usize = 128;
const NON_TEXT_MODALITY_SEGMENTS: &[&str] = &[
"embedding",
"embeddings",
"audio",
"realtime",
"transcribe",
"transcription",
"tts",
"image",
"images",
];
const JSON_GUARANTEE: &str = "lexical_json_value_preserving";
const EXACT_ROUND_TRIP_GUARANTEE: &str = "exact_round_trip";
const WARNING_ACTIVE_ONLY: &str =
"scope is limited to active primary sessions and direct subagent sessions";
const WARNING_SAMPLING: &str = "sampling limits excluded eligible active session files";
const WARNING_FEW_RESULTS: &str = "fewer than 100 measured tool results";
const WARNING_FEW_FILES: &str = "fewer than 10 session files considered";
const WARNING_INPUT_SKIPPED: &str = "some session input was malformed or skipped";
const WARNING_ROOT_MISSING: &str = "session root was not present";
const WARNING_INPUT_LIMIT: &str = "some session input reached a measurement limit";
const WARNING_UNSAFE_STORAGE: &str = "session storage layout was unsafe or unreadable";
const WARNING_JSON_LOW: &str = "JSON minification had low applicability";
const WARNING_JSON_NO_WINS: &str = "JSON minification had no strict wins";
const WARNING_RLE_LOW: &str = "line RLE had low applicability";
const WARNING_RLE_NO_WINS: &str = "line RLE had no strict wins";
const WARNING_TEMPLATE_LOW: &str = "template folding had low applicability";
const WARNING_TEMPLATE_NO_WINS: &str = "template folding had no strict wins";
#[derive(Debug, Clone, Serialize, PartialEq, Eq, Default)]
struct FileReport {
considered: u64,
scanned: u64,
sampled_out: u64,
skipped: u64,
unreadable: u64,
unstable: u64,
limit_hit: u64,
}
#[derive(Debug, Clone, Serialize, PartialEq, Eq, Default)]
struct JsonlReport {
lines: u64,
valid_events: u64,
malformed: u64,
oversized: u64,
limit_hit: u64,
}
#[derive(Debug, Clone, Serialize, PartialEq, Eq, Default)]
struct ToolResultReport {
found: u64,
measured: u64,
legacy: u64,
empty: u64,
invalid: u64,
skipped_limit: u64,
}
#[derive(Debug, Clone, Serialize, PartialEq, Eq, Default)]
struct TokenReport {
bytes: u64,
tokens: u64,
}
#[derive(Debug, Clone, Serialize, PartialEq)]
struct CodecReport {
guarantee: &'static str,
attempted: u64,
applicable_validation_passed: u64,
baseline_tokens_when_applicable: u64,
strict_improvements: u64,
saved_tokens_when_strict_improvement: u64,
savings_percent: f64,
selected: u64,
candidate_tokens_when_applicable: u64,
saved_tokens_when_selected: u64,
}
impl CodecReport {
fn new(guarantee: &'static str) -> Self {
Self {
guarantee,
attempted: 0,
applicable_validation_passed: 0,
baseline_tokens_when_applicable: 0,
strict_improvements: 0,
saved_tokens_when_strict_improvement: 0,
savings_percent: 0.0,
selected: 0,
candidate_tokens_when_applicable: 0,
saved_tokens_when_selected: 0,
}
}
}
#[derive(Debug, Clone, Serialize, PartialEq)]
struct PortfolioReport {
selected_tokens: u64,
saved_tokens: u64,
savings_percent: f64,
outputs_transformed: u64,
}
impl Default for PortfolioReport {
fn default() -> Self {
Self {
selected_tokens: 0,
saved_tokens: 0,
savings_percent: 0.0,
outputs_transformed: 0,
}
}
}
#[derive(Debug, Clone, Serialize, PartialEq)]
struct CodecReports {
json_minification: CodecReport,
line_rle: CodecReport,
template_folding: CodecReport,
}
impl Default for CodecReports {
fn default() -> Self {
Self {
json_minification: CodecReport::new(JSON_GUARANTEE),
line_rle: CodecReport::new(EXACT_ROUND_TRIP_GUARANTEE),
template_folding: CodecReport::new(EXACT_ROUND_TRIP_GUARANTEE),
}
}
}
#[derive(Debug, Clone, Serialize, PartialEq, Eq, Default)]
struct SamplingReport {
max_files: u64,
max_bytes: u64,
discovered_files: u64,
selected_files: u64,
sampled_out_files: u64,
discovered_bytes: u64,
planned_bytes: u64,
scanned_bytes: u64,
sampled_out_bytes: u64,
unscanned_selected_bytes: u64,
}
#[derive(Debug, Clone, Serialize, PartialEq)]
struct MeasurementReport {
schema_version: u32,
prototype: &'static str,
model: String,
encoding_family: &'static str,
scope: &'static str,
exploratory: bool,
measurement_unit: &'static str,
request_weighted: bool,
replay_compaction_applied: bool,
sampling: SamplingReport,
files: FileReport,
jsonl: JsonlReport,
tool_results: ToolResultReport,
baseline: TokenReport,
codecs: CodecReports,
portfolio: PortfolioReport,
warnings: Vec<String>,
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct MeasurementLimits {
pub(crate) max_files: usize,
pub(crate) max_bytes: u64,
}
impl MeasurementLimits {
pub(crate) const fn new(max_files: usize, max_bytes: u64) -> Self {
Self {
max_files,
max_bytes,
}
}
pub(crate) const fn is_valid_file_count(value: usize) -> bool {
value > 0 && value <= MAX_SESSION_FILES
}
pub(crate) const fn is_valid_byte_limit(value: u64) -> bool {
value >= MIN_MAX_BYTES && value <= MAX_MAX_BYTES
}
}
impl Default for MeasurementLimits {
fn default() -> Self {
Self::new(DEFAULT_MAX_FILES, DEFAULT_MAX_BYTES)
}
}
#[derive(Debug, Default)]
struct FileAggregate {
jsonl: JsonlReport,
tool_results: ToolResultReport,
baseline: TokenReport,
codecs: CodecReports,
portfolio: PortfolioReport,
limit_hit: bool,
}
pub(crate) fn run<W: Write>(
root: &Path,
model: &str,
limits: MeasurementLimits,
writer: &mut W,
) -> anyhow::Result<()> {
let report = measure_sessions_with_limits(root, model, limits)?;
serde_json::to_writer(&mut *writer, &report)
.map_err(|_| anyhow::anyhow!("could not write compression measurement report"))?;
writer
.write_all(b"\n")
.map_err(|_| anyhow::anyhow!("could not write compression measurement report"))?;
Ok(())
}
pub(crate) fn is_valid_measurement_model(model: &str) -> bool {
!model.is_empty()
&& model.len() <= MAX_MODEL_BYTES
&& !model
.chars()
.any(|character| character.is_control() || character.is_whitespace())
&& is_supported_measurement_text_model(model)
&& token_encoding_family_for_model(model).is_some()
}
fn is_supported_measurement_text_model(model: &str) -> bool {
let model = model.to_ascii_lowercase();
!contains_non_text_modality(&model)
&& (matches_model_family(&model, "gpt-5", true)
|| matches_model_family(&model, "gpt-4.1", false)
|| matches_model_family(&model, "gpt-4o", false)
|| matches_model_family(&model, "gpt-4.5", false)
|| matches_model_family(&model, "o1", false)
|| matches_model_family(&model, "o3", false)
|| matches_model_family(&model, "o4", false)
|| is_codex_measurement_model(&model)
|| matches_model_family(&model, "gpt-4", false)
|| matches_model_family(&model, "gpt-3.5-turbo", false))
}
fn matches_model_family(model: &str, family: &str, allow_dot_suffix: bool) -> bool {
let Some(suffix) = model.strip_prefix(family) else {
return false;
};
if suffix.is_empty() {
return true;
}
let suffix = if suffix.starts_with('-') || (allow_dot_suffix && suffix.starts_with('.')) {
&suffix[1..]
} else {
return false;
};
is_valid_model_identifier(suffix)
}
fn is_codex_measurement_model(model: &str) -> bool {
model
.strip_prefix("codex-")
.is_some_and(is_valid_model_identifier)
}
fn is_valid_model_identifier(identifier: &str) -> bool {
!identifier.is_empty()
&& identifier.split(['-', '.', '_']).all(|segment| {
!segment.is_empty() && segment.bytes().all(|byte| byte.is_ascii_alphanumeric())
})
}
fn contains_non_text_modality(model: &str) -> bool {
model
.split(['-', '.', '_'])
.any(|segment| NON_TEXT_MODALITY_SEGMENTS.contains(&segment))
}
#[cfg(test)]
fn measure_sessions(root: &Path, model: &str) -> anyhow::Result<MeasurementReport> {
let root = root.canonicalize().unwrap_or_else(|_| root.to_path_buf());
measure_sessions_with_limits(&root, model, MeasurementLimits::default())
}
fn measure_sessions_with_limits(
root: &Path,
model: &str,
limits: MeasurementLimits,
) -> anyhow::Result<MeasurementReport> {
if !is_valid_measurement_model(model) {
anyhow::bail!("unsupported model for offline compression measurement");
}
if !MeasurementLimits::is_valid_file_count(limits.max_files)
|| !MeasurementLimits::is_valid_byte_limit(limits.max_bytes)
{
anyhow::bail!("invalid offline compression measurement limits");
}
let family = token_encoding_family_for_model(model)
.ok_or_else(|| anyhow::anyhow!("unsupported model for offline compression measurement"))?;
let discovery = scanner::discover_files(root, limits);
let root_missing = discovery.root_missing;
let unsafe_storage = discovery.unsafe_storage;
let mut report = MeasurementReport {
schema_version: 1,
prototype: PROTOTYPE_LABEL,
model: model.to_string(),
encoding_family: family.as_str(),
scope: SCOPE_LABEL,
exploratory: true,
measurement_unit: MEASUREMENT_UNIT,
request_weighted: false,
replay_compaction_applied: false,
sampling: discovery.sampling,
files: discovery.files,
jsonl: JsonlReport::default(),
tool_results: ToolResultReport::default(),
baseline: TokenReport::default(),
codecs: CodecReports::default(),
portfolio: PortfolioReport::default(),
warnings: Vec::new(),
};
for target in discovery.targets {
match scanner::scan_file(&target, model) {
scanner::FileScanResult::Scanned { aggregate, bytes } => {
report.files.scanned = report.files.scanned.saturating_add(1);
report.sampling.scanned_bytes = report.sampling.scanned_bytes.saturating_add(bytes);
merge_file_aggregate(&mut report, *aggregate);
}
scanner::FileScanResult::Unreadable { bytes } => {
report.files.unreadable = report.files.unreadable.saturating_add(1);
report.sampling.unscanned_selected_bytes = report
.sampling
.unscanned_selected_bytes
.saturating_add(bytes);
}
scanner::FileScanResult::Unstable { bytes } => {
report.files.unstable = report.files.unstable.saturating_add(1);
report.sampling.unscanned_selected_bytes = report
.sampling
.unscanned_selected_bytes
.saturating_add(bytes);
}
}
}
debug_assert_eq!(
report.sampling.unscanned_selected_bytes,
report
.sampling
.planned_bytes
.saturating_sub(report.sampling.scanned_bytes)
);
finalize_codec_reports(&mut report.codecs);
if root_missing {
report.warnings.push(WARNING_ROOT_MISSING.to_string());
}
if unsafe_storage {
report.warnings.push(WARNING_UNSAFE_STORAGE.to_string());
}
if report.sampling.sampled_out_files > 0 {
report.warnings.push(WARNING_SAMPLING.to_string());
}
if report.tool_results.measured < 100 {
report.warnings.push(WARNING_FEW_RESULTS.to_string());
}
if report.files.considered < 10 {
report.warnings.push(WARNING_FEW_FILES.to_string());
}
if report.files.limit_hit > 0 || report.jsonl.limit_hit > 0 {
report.warnings.push(WARNING_INPUT_LIMIT.to_string());
}
if report.jsonl.malformed > 0
|| report.jsonl.oversized > 0
|| report.files.skipped > 0
|| report.files.unreadable > 0
|| report.files.unstable > 0
|| report.tool_results.invalid > 0
|| report.tool_results.skipped_limit > 0
{
report.warnings.push(WARNING_INPUT_SKIPPED.to_string());
}
append_codec_warnings(&mut report.warnings, &report.codecs);
report.warnings.push(WARNING_ACTIVE_ONLY.to_string());
report.portfolio.savings_percent =
savings_percent(report.portfolio.saved_tokens, report.baseline.tokens);
Ok(report)
}
fn finalize_codec_reports(codecs: &mut CodecReports) {
for codec in [
&mut codecs.json_minification,
&mut codecs.line_rle,
&mut codecs.template_folding,
] {
codec.savings_percent = savings_percent(
codec.saved_tokens_when_strict_improvement,
codec.baseline_tokens_when_applicable,
);
}
}
fn append_codec_warnings(warnings: &mut Vec<String>, codecs: &CodecReports) {
append_one_codec_warnings(
warnings,
&codecs.json_minification,
WARNING_JSON_LOW,
WARNING_JSON_NO_WINS,
);
append_one_codec_warnings(
warnings,
&codecs.line_rle,
WARNING_RLE_LOW,
WARNING_RLE_NO_WINS,
);
append_one_codec_warnings(
warnings,
&codecs.template_folding,
WARNING_TEMPLATE_LOW,
WARNING_TEMPLATE_NO_WINS,
);
}
fn append_one_codec_warnings(
warnings: &mut Vec<String>,
codec: &CodecReport,
low_message: &str,
no_wins_message: &str,
) {
if codec.attempted == 0
|| codec.applicable_validation_passed.saturating_mul(2) < codec.attempted
{
warnings.push(low_message.to_string());
}
if codec.strict_improvements == 0 {
warnings.push(no_wins_message.to_string());
}
}
fn savings_percent(saved: u64, baseline: u64) -> f64 {
if baseline == 0 {
return 0.0;
}
let saved = u128::from(saved.min(baseline));
let baseline = u128::from(baseline);
let hundredths = saved
.saturating_mul(10_000)
.saturating_add(baseline / 2)
.checked_div(baseline)
.unwrap_or(0)
.min(10_000);
hundredths as f64 / 100.0
}
fn merge_file_aggregate(report: &mut MeasurementReport, file: FileAggregate) {
if file.limit_hit {
report.files.limit_hit = report.files.limit_hit.saturating_add(1);
}
merge_jsonl(&mut report.jsonl, &file.jsonl);
merge_tool_results(&mut report.tool_results, &file.tool_results);
report.baseline.bytes = report.baseline.bytes.saturating_add(file.baseline.bytes);
report.baseline.tokens = report.baseline.tokens.saturating_add(file.baseline.tokens);
merge_codec(
&mut report.codecs.json_minification,
&file.codecs.json_minification,
);
merge_codec(&mut report.codecs.line_rle, &file.codecs.line_rle);
merge_codec(
&mut report.codecs.template_folding,
&file.codecs.template_folding,
);
report.portfolio.selected_tokens = report
.portfolio
.selected_tokens
.saturating_add(file.portfolio.selected_tokens);
report.portfolio.saved_tokens = report
.portfolio
.saved_tokens
.saturating_add(file.portfolio.saved_tokens);
report.portfolio.outputs_transformed = report
.portfolio
.outputs_transformed
.saturating_add(file.portfolio.outputs_transformed);
}
fn merge_jsonl(target: &mut JsonlReport, source: &JsonlReport) {
target.lines = target.lines.saturating_add(source.lines);
target.valid_events = target.valid_events.saturating_add(source.valid_events);
target.malformed = target.malformed.saturating_add(source.malformed);
target.oversized = target.oversized.saturating_add(source.oversized);
target.limit_hit = target.limit_hit.saturating_add(source.limit_hit);
}
fn merge_tool_results(target: &mut ToolResultReport, source: &ToolResultReport) {
target.found = target.found.saturating_add(source.found);
target.measured = target.measured.saturating_add(source.measured);
target.legacy = target.legacy.saturating_add(source.legacy);
target.empty = target.empty.saturating_add(source.empty);
target.invalid = target.invalid.saturating_add(source.invalid);
target.skipped_limit = target.skipped_limit.saturating_add(source.skipped_limit);
}
fn merge_codec(target: &mut CodecReport, source: &CodecReport) {
target.attempted = target.attempted.saturating_add(source.attempted);
target.applicable_validation_passed = target
.applicable_validation_passed
.saturating_add(source.applicable_validation_passed);
target.baseline_tokens_when_applicable = target
.baseline_tokens_when_applicable
.saturating_add(source.baseline_tokens_when_applicable);
target.strict_improvements = target
.strict_improvements
.saturating_add(source.strict_improvements);
target.saved_tokens_when_strict_improvement = target
.saved_tokens_when_strict_improvement
.saturating_add(source.saved_tokens_when_strict_improvement);
target.selected = target.selected.saturating_add(source.selected);
target.candidate_tokens_when_applicable = target
.candidate_tokens_when_applicable
.saturating_add(source.candidate_tokens_when_applicable);
target.saved_tokens_when_selected = target
.saved_tokens_when_selected
.saturating_add(source.saved_tokens_when_selected);
}
fn u64_from_usize(value: usize) -> u64 {
u64::try_from(value).unwrap_or(u64::MAX)
}
#[cfg(test)]
mod tests {
use super::*;
use scanner::{MAX_JSONL_LINE_BYTES, MAX_SESSION_FILE_BYTES};
use serde_json::{Value, json};
use std::fs;
use tempfile::TempDir;
fn event_with_content(content: &str) -> String {
serde_json::to_string(&json!({
"event_type": "tool_result",
"timestamp": "2025-01-01T00:00:00Z",
"session_id": "private-session-id",
"cwd": "/private/session-cwd",
"payload": {
"call_id": "private-call-id",
"result": {
"tool_name": "private-tool-name",
"success": true,
"content": content
}
}
}))
.unwrap()
}
fn event_with_legacy_output(output: &str) -> String {
serde_json::to_string(&json!({
"event_type": "tool_result",
"timestamp": "2025-01-01T00:00:00Z",
"session_id": "private-session-id",
"cwd": "/private/session-cwd",
"payload": {
"call_id": "private-call-id",
"result": {
"tool_name": "private-tool-name",
"success": true,
"output": output
}
}
}))
.unwrap()
}
fn event_with_payload(payload: Value) -> String {
serde_json::to_string(&json!({
"event_type": "tool_result",
"timestamp": "2025-01-01T00:00:00Z",
"session_id": "private-session-id",
"cwd": "/private/session-cwd",
"payload": payload
}))
.unwrap()
}
fn write_session(root: &Path, name: &str, lines: &[String]) {
fs::create_dir_all(root).unwrap();
fs::write(root.join(name), format!("{}\n", lines.join("\n"))).unwrap();
}
fn measure_one(content: &str) -> MeasurementReport {
let temp = TempDir::new().unwrap();
let sessions = temp.path().join("sessions");
write_session(&sessions, "one.jsonl", &[event_with_content(content)]);
measure_sessions(&sessions, "gpt-4o").unwrap()
}
#[test]
fn invalid_tool_results_are_counted_without_falling_back_to_ambiguous_fields() {
let temp = TempDir::new().unwrap();
let sessions = temp.path().join("sessions");
let mut malformed =
serde_json::from_str::<Value>(&event_with_content("malformed")).unwrap();
malformed.as_object_mut().unwrap().remove("cwd");
let lines = vec![
event_with_payload(json!({"call_id":"missing-result"})),
event_with_payload(json!({
"call_id":"missing-success",
"result":{"tool_name":"read","content":"bad"}
})),
event_with_payload(json!({
"call_id":"ambiguous",
"result":{"tool_name":"read","success":true,"content":"new","output":"old"}
})),
event_with_payload(json!({
"call_id":"valid-failure",
"result":{"tool_name":"read","success":false,"content":"kept"}
})),
serde_json::to_string(&malformed).unwrap(),
];
write_session(&sessions, "one.jsonl", &lines);
let report = measure_sessions(&sessions, "gpt-4o").unwrap();
assert_eq!(report.jsonl.lines, 5);
assert_eq!(report.jsonl.valid_events, 4);
assert_eq!(report.jsonl.malformed, 1);
assert_eq!(report.tool_results.found, 4);
assert_eq!(report.tool_results.invalid, 3);
assert_eq!(report.tool_results.measured, 1);
}
#[test]
fn measurement_report_separates_discovered_and_sampled_input() {
let temp = TempDir::new().unwrap();
let sessions = temp.path().join("sessions");
for name in ["first.jsonl", "second.jsonl", "third.jsonl"] {
write_session(&sessions, name, &[event_with_content("sample")]);
}
let sessions = fs::canonicalize(&sessions).unwrap();
let report = measure_sessions_with_limits(
&sessions,
"gpt-4o",
MeasurementLimits::new(2, MIN_MAX_BYTES),
)
.unwrap();
assert_eq!(report.files.considered, 3);
assert_eq!(report.files.sampled_out, 1);
assert_eq!(report.sampling.discovered_files, 3);
assert_eq!(report.sampling.selected_files, 2);
assert_eq!(report.sampling.sampled_out_files, 1);
assert!(report.sampling.planned_bytes <= MIN_MAX_BYTES);
assert_eq!(
report.sampling.sampled_out_bytes,
report
.sampling
.discovered_bytes
.saturating_sub(report.sampling.planned_bytes)
);
assert_eq!(report.sampling.scanned_bytes, report.sampling.planned_bytes);
assert_eq!(report.sampling.unscanned_selected_bytes, 0);
assert_eq!(
report.sampling.discovered_bytes,
report
.sampling
.planned_bytes
.saturating_add(report.sampling.sampled_out_bytes)
);
assert_eq!(
report.files.considered,
report
.files
.scanned
.saturating_add(report.files.sampled_out)
);
}
#[test]
fn empty_tool_result_is_found_empty_and_measured_without_codec_candidate() {
let report = measure_one("");
assert_eq!(report.tool_results.found, 1);
assert_eq!(report.tool_results.empty, 1);
assert_eq!(report.tool_results.measured, 1);
assert!(report.baseline.tokens > 0);
assert_eq!(report.portfolio.outputs_transformed, 0);
assert_eq!(report.portfolio.saved_tokens, 0);
assert_eq!(report.portfolio.selected_tokens, report.baseline.tokens);
for codec in [
&report.codecs.json_minification,
&report.codecs.line_rle,
&report.codecs.template_folding,
] {
assert_eq!(codec.attempted, 1);
assert_eq!(codec.applicable_validation_passed, 0);
}
}
#[test]
fn envelope_overhead_keeps_short_repetition_as_baseline() {
let report = measure_one("A\nA\n");
assert_eq!(report.codecs.line_rle.applicable_validation_passed, 1);
assert!(report.codecs.line_rle.candidate_tokens_when_applicable > report.baseline.tokens);
assert_eq!(report.portfolio.outputs_transformed, 0);
assert_eq!(report.portfolio.saved_tokens, 0);
assert_eq!(report.portfolio.selected_tokens, report.baseline.tokens);
}
#[test]
fn long_repetition_has_one_independent_strict_winner() {
let report = measure_one(&"repeated tool row\n".repeat(512));
assert_eq!(report.codecs.line_rle.strict_improvements, 1);
assert_eq!(report.codecs.line_rle.selected, 1);
assert_eq!(report.portfolio.outputs_transformed, 1);
assert!(report.portfolio.saved_tokens > 0);
assert!(report.portfolio.selected_tokens < report.baseline.tokens);
}
#[test]
fn oversized_file_is_skipped_without_inventing_a_tool_result_skip() {
let temp = TempDir::new().unwrap();
let sessions = temp.path().join("sessions");
fs::create_dir_all(&sessions).unwrap();
let file = fs::File::create(sessions.join("large.jsonl")).unwrap();
file.set_len(MAX_SESSION_FILE_BYTES + 1).unwrap();
let report = measure_sessions(&sessions, "gpt-4o").unwrap();
assert_eq!(report.files.considered, 0);
assert_eq!(report.files.scanned, 0);
assert_eq!(report.files.skipped, 1);
assert_eq!(report.files.limit_hit, 1);
assert_eq!(report.sampling.discovered_files, 0);
assert_eq!(report.sampling.selected_files, 0);
assert_eq!(report.sampling.planned_bytes, 0);
assert_eq!(report.sampling.scanned_bytes, 0);
assert!(
report
.warnings
.iter()
.any(|warning| warning == WARNING_INPUT_LIMIT)
);
assert_eq!(report.sampling.sampled_out_bytes, 0);
assert_eq!(report.sampling.unscanned_selected_bytes, 0);
}
#[test]
fn scanner_reads_primary_and_child_only_and_keeps_report_private() {
let temp = TempDir::new().unwrap();
let sessions = temp.path().join("sessions");
let primary = event_with_content("private-secret-content\nprivate/path\n");
let legacy = event_with_legacy_output("legacy-output");
write_session(&sessions, "primary.jsonl", &[primary]);
write_session(&sessions.join("subagents"), "child.jsonl", &[legacy]);
fs::write(sessions.join("archive.jsonl.tmp"), b"not used").unwrap();
fs::write(sessions.join("child.metadata.json"), b"not used").unwrap();
fs::create_dir_all(sessions.join(".history")).unwrap();
fs::write(sessions.join(".history").join("old.jsonl"), b"not used").unwrap();
let report = measure_sessions(&sessions, "gpt-4o").unwrap();
let output = serde_json::to_string(&report).unwrap();
assert_eq!(report.files.considered, 2);
assert_eq!(report.files.scanned, 2);
assert_eq!(report.files.skipped, 0);
assert!(
!report
.warnings
.iter()
.any(|warning| warning == WARNING_INPUT_SKIPPED)
);
assert_eq!(report.tool_results.found, 2);
assert_eq!(report.tool_results.legacy, 1);
assert!(!output.contains("private-secret-content"));
assert!(!output.contains("private/path"));
assert!(!output.contains("private-call-id"));
assert!(!output.contains("private-tool-name"));
assert!(!output.contains("primary.jsonl"));
assert_eq!(
serde_json::to_value(&report).unwrap(),
serde_json::from_str::<Value>(&output).unwrap()
);
}
#[test]
fn scanner_counts_malformed_and_oversized_lines_without_leaking_them() {
let temp = TempDir::new().unwrap();
let sessions = temp.path().join("sessions");
fs::create_dir_all(&sessions).unwrap();
let oversized = "secret-oversized-content".to_string() + &"x".repeat(MAX_JSONL_LINE_BYTES);
let content = format!("{oversized}\n{}\n{{bad\n", event_with_content("ok"));
fs::write(sessions.join("one.jsonl"), content).unwrap();
let report = measure_sessions(&sessions, "gpt-4o").unwrap();
let output = serde_json::to_string(&report).unwrap();
assert_eq!(report.jsonl.lines, 3);
assert_eq!(report.jsonl.oversized, 1);
assert_eq!(report.jsonl.malformed, 1);
assert_eq!(report.tool_results.found, 1);
assert!(!output.contains("secret-oversized-content"));
}
#[test]
fn scanner_missing_root_is_zero_and_read_only() {
let temp = TempDir::new().unwrap();
let root = fs::canonicalize(temp.path()).unwrap().join("missing");
let report = measure_sessions(&root, "gpt-4o").unwrap();
assert_eq!(report.files, FileReport::default());
assert!(
report
.warnings
.iter()
.any(|warning| warning == WARNING_ROOT_MISSING)
);
assert!(!root.exists());
}
#[test]
fn unsupported_model_fails_before_scanning() {
let temp = TempDir::new().unwrap();
let root = temp.path().join("does-not-matter");
let error = measure_sessions(&root, "unsupported-model").unwrap_err();
assert_eq!(
error.to_string(),
"unsupported model for offline compression measurement"
);
assert!(!root.exists());
}
#[test]
fn measurement_model_validation_accepts_text_families_and_rejects_near_misses() {
for model in [
"gpt-5",
"gpt-5.5",
"gpt-5-mini",
"gpt-4.1",
"gpt-4.1-mini",
"gpt-4.1-2025-04-14",
"gpt-4o",
"gpt-4o-mini",
"gpt-4o-2024-08-06",
"gpt-4.5-preview",
"o1",
"o1-mini",
"o3",
"o3-mini",
"o4",
"o4-mini",
"codex-mini-latest",
"gpt-4",
"gpt-4-turbo",
"gpt-4-0613",
"gpt-3.5-turbo",
"gpt-3.5-turbo-0125",
] {
assert!(is_valid_measurement_model(model), "{model}");
}
for model in [
"text-embedding-ada-002",
"text-embedding-3-small",
"text-embedding-3-large",
"gpt-4evil",
"o123",
"gpt-5-embedding",
"codex-",
"gpt-5-",
"gpt-4.1evil",
"gpt-4oevil",
"gpt-4.5evil",
"gpt-3.5-turbosomething",
"codex--mini",
"codex-embedding",
"gpt-4o-audio-preview",
"gpt-4o-realtime-preview",
"gpt-4o-mini-transcribe",
"gpt-4o-mini-tts",
"gpt-4o-image-preview",
] {
assert!(!is_valid_measurement_model(model), "{model}");
}
}
#[cfg(unix)]
#[test]
fn scanner_excludes_symlinked_files_and_subagent_directory() {
use std::os::unix::fs::symlink;
let temp = TempDir::new().unwrap();
let sessions = temp.path().join("sessions");
fs::create_dir_all(&sessions).unwrap();
fs::write(sessions.join("real.jsonl"), event_with_content("real")).unwrap();
symlink(sessions.join("real.jsonl"), sessions.join("link.jsonl")).unwrap();
symlink(sessions.join("real.jsonl"), sessions.join("subagents")).unwrap();
let report = measure_sessions(&sessions, "gpt-4o").unwrap();
assert_eq!(report.files.considered, 1);
assert_eq!(report.files.scanned, 1);
assert!(report.files.skipped >= 2);
assert!(
report
.warnings
.iter()
.any(|warning| warning == WARNING_INPUT_SKIPPED)
);
assert!(
report
.warnings
.iter()
.any(|warning| warning == WARNING_UNSAFE_STORAGE)
);
}
#[test]
#[cfg(unix)]
fn scanner_rejects_symlinked_ancestor_without_scanning_target() {
use std::os::unix::fs::symlink;
let temp = TempDir::new().unwrap();
let base = fs::canonicalize(temp.path()).unwrap();
let real_root = base.join("real-parent").join("sessions");
fs::create_dir_all(&real_root).unwrap();
fs::write(real_root.join("one.jsonl"), event_with_content("private")).unwrap();
let symlinked_parent = base.join("symlinked-parent");
symlink(base.join("real-parent"), &symlinked_parent).unwrap();
let report = measure_sessions_with_limits(
&symlinked_parent.join("sessions"),
"gpt-4o",
MeasurementLimits::default(),
)
.unwrap();
assert_eq!(report.files, FileReport::default());
assert!(
report
.warnings
.iter()
.any(|warning| warning == WARNING_UNSAFE_STORAGE)
);
assert!(
!report
.warnings
.iter()
.any(|warning| warning == WARNING_ROOT_MISSING)
);
assert!(!serde_json::to_string(&report).unwrap().contains("private"));
}
#[test]
fn savings_percentage_is_finite_and_rounded_deterministically() {
assert_eq!(savings_percent(0, 0), 0.0);
assert_eq!(savings_percent(1, 3), 33.33);
assert_eq!(savings_percent(2, 3), 66.67);
assert_eq!(savings_percent(4, 3), 100.0);
assert!(savings_percent(u64::MAX, u64::MAX).is_finite());
}
#[test]
fn file_limit_does_not_invent_tool_result_skips() {
let mut report = MeasurementReport {
schema_version: 1,
prototype: PROTOTYPE_LABEL,
model: "gpt-4o".to_string(),
encoding_family: "o200k_base",
scope: SCOPE_LABEL,
exploratory: true,
measurement_unit: MEASUREMENT_UNIT,
request_weighted: false,
replay_compaction_applied: false,
sampling: SamplingReport::default(),
files: FileReport::default(),
jsonl: JsonlReport::default(),
tool_results: ToolResultReport::default(),
baseline: TokenReport::default(),
codecs: CodecReports::default(),
portfolio: PortfolioReport::default(),
warnings: Vec::new(),
};
let file = FileAggregate {
limit_hit: true,
..FileAggregate::default()
};
merge_file_aggregate(&mut report, file);
assert_eq!(report.files.limit_hit, 1);
assert_eq!(report.tool_results.skipped_limit, 0);
}
#[test]
fn codec_merge_keeps_independent_metrics_and_recomputes_savings() {
let mut target = CodecReport::new(EXACT_ROUND_TRIP_GUARANTEE);
target.attempted = 1;
target.applicable_validation_passed = 1;
target.baseline_tokens_when_applicable = 10;
target.strict_improvements = 1;
target.saved_tokens_when_strict_improvement = 2;
target.selected = 1;
target.candidate_tokens_when_applicable = 8;
target.saved_tokens_when_selected = 2;
let mut source = CodecReport::new(EXACT_ROUND_TRIP_GUARANTEE);
source.attempted = 2;
source.applicable_validation_passed = 1;
source.baseline_tokens_when_applicable = 20;
source.candidate_tokens_when_applicable = 21;
source.selected = 1;
merge_codec(&mut target, &source);
assert_eq!(target.attempted, 3);
assert_eq!(target.applicable_validation_passed, 2);
assert_eq!(target.baseline_tokens_when_applicable, 30);
assert_eq!(target.strict_improvements, 1);
assert_eq!(target.saved_tokens_when_strict_improvement, 2);
assert_eq!(target.selected, 2);
assert_eq!(target.candidate_tokens_when_applicable, 29);
assert_eq!(target.saved_tokens_when_selected, 2);
let mut reports = CodecReports {
line_rle: target,
..CodecReports::default()
};
finalize_codec_reports(&mut reports);
}
}