use std::sync::atomic::{AtomicUsize, Ordering};
use supercode_harness::reduce::summarize::{SpanSummarizer, PROMPT_VERSION};
use supercode_harness::reduce::{
content_hash, export_session, invert, prepare_cleared_turns_summary, project_messages,
verify_log, ReductionError, ReductionKind, ReductionLog, ReductionPolicy, ReductionResult,
};
use supercode_harness::session::SessionFormat;
use supercode_harness::{ChatMessage, Session};
fn filler(len: usize) -> String {
(0..len).map(|i| (b'a' + (i % 26) as u8) as char).collect()
}
fn empty_session() -> Session {
Session::from_claude_code_str("").unwrap()
}
fn append_padded_turns(session: &mut Session, n: usize) {
let pad = filler(120);
for i in 0..n {
session
.messages
.push(ChatMessage::user(format!("user turn {i}: {pad}")));
session.messages.push(ChatMessage::assistant(format!(
"assistant reply {i}: {pad}"
)));
}
}
fn messages_identical(a: &[ChatMessage], b: &[ChatMessage]) -> bool {
a.len() == b.len()
&& a.iter().zip(b).all(|(x, y)| {
x.role == y.role
&& x.content == y.content
&& x.content_parts == y.content_parts
&& x.tool_call_id == y.tool_call_id
&& x.name == y.name
&& x.metadata == y.metadata
})
}
struct FakeSummarizer {
text: &'static str,
model: &'static str,
}
impl SpanSummarizer for FakeSummarizer {
fn summarize(&self, _span_text: &str) -> ReductionResult<String> {
Ok(self.text.to_string())
}
fn model_id(&self) -> &str {
self.model
}
}
struct FailingSummarizer;
impl SpanSummarizer for FailingSummarizer {
fn summarize(&self, _span_text: &str) -> ReductionResult<String> {
Err(ReductionError::new("simulated summarization timeout"))
}
fn model_id(&self) -> &str {
"unreachable-model"
}
}
struct CountingSummarizer {
calls: AtomicUsize,
text: &'static str,
}
impl SpanSummarizer for CountingSummarizer {
fn summarize(&self, _span_text: &str) -> ReductionResult<String> {
self.calls.fetch_add(1, Ordering::SeqCst);
Ok(self.text.to_string())
}
fn model_id(&self) -> &str {
"counting-model"
}
}
fn find_cleared(log: &ReductionLog) -> (usize, usize) {
log.reductions
.iter()
.find_map(|r| match r.kind {
ReductionKind::TurnsCleared { first, last, .. } => Some((first, last)),
_ => None,
})
.expect("expected exactly one TurnsCleared reduction")
}
#[test]
fn dev01_summaries_off_by_default_matches_the_pre_tr7_deterministic_stub() {
let mut session = empty_session();
append_padded_turns(&mut session, 10); let msgs = session.messages.clone();
let policy = ReductionPolicy {
clear_turns_older_than: Some(8),
..ReductionPolicy::default()
};
assert!(
!policy.summarize_cleared_turns,
"summaries must default to OFF"
);
let prior = ReductionLog::default();
let (view, log) = project_messages(&msgs, &policy, &prior);
let (first, last) = find_cleared(&log);
let r = log
.reductions
.iter()
.find(|r| matches!(r.kind, ReductionKind::TurnsCleared { .. }))
.unwrap();
assert_eq!(first, 0);
assert_eq!(last, 15);
assert!(
r.placeholder.starts_with(&format!(
"[sc-reduced turns-cleared {}: turns {first}..{} cleared (16 messages: 8 user, 8 \
assistant, 0 tool; ",
r.id,
last + 1,
)),
"{}",
r.placeholder
);
assert!(
!r.placeholder.contains("sc-summary"),
"off by default must never carry a summary banner: {}",
r.placeholder
);
assert!(
r.placeholder.starts_with("[sc-reduced turns-cleared "),
"{}",
r.placeholder
);
assert!(
r.placeholder.contains("full turns in session sidecar"),
"{}",
r.placeholder
);
assert!(matches!(
r.kind,
ReductionKind::TurnsCleared { summary: None, .. }
));
let mut policy_with_stray_data = policy.clone();
let fake = FakeSummarizer {
text: "should never be used",
model: "never-called-model",
};
policy_with_stray_data.cleared_turns_summary = prepare_cleared_turns_summary(
&msgs,
&{
let mut p = policy.clone();
p.summarize_cleared_turns = true;
p
},
&prior,
&fake,
);
let (_, log_stray) = project_messages(&msgs, &policy_with_stray_data, &prior);
let r_stray = log_stray
.reductions
.iter()
.find(|r| matches!(r.kind, ReductionKind::TurnsCleared { .. }))
.unwrap();
assert_eq!(
r_stray.placeholder, r.placeholder,
"policy gate off must win even with a prepared summary sitting in the policy"
);
let _ = view;
}
#[test]
fn dev01_summaries_on_projects_banner_llm_summary_and_reduction_id() {
let mut session = empty_session();
append_padded_turns(&mut session, 10);
let msgs = session.messages.clone();
let mut policy = ReductionPolicy {
clear_turns_older_than: Some(8),
summarize_cleared_turns: true,
..ReductionPolicy::default()
};
let prior = ReductionLog::default();
let fake = FakeSummarizer {
text: "The user asked about widgets; the assistant explained the API.",
model: "fake-summary-model-v1",
};
let prepared = prepare_cleared_turns_summary(&msgs, &policy, &prior, &fake)
.expect("a large-enough span above the cost floor must be prepared");
policy.cleared_turns_summary = Some(prepared);
let (view, log) = project_messages(&msgs, &policy, &prior);
let (first, last) = find_cleared(&log);
let r = log
.reductions
.iter()
.find(|r| matches!(r.kind, ReductionKind::TurnsCleared { .. }))
.unwrap();
assert!(
r.placeholder.contains("widgets"),
"the LLM summary text must appear in the placeholder: {}",
r.placeholder
);
assert!(
r.placeholder.contains(&format!("sc-summary of {}", r.id)),
"{}",
r.placeholder
);
assert!(
r.placeholder
.contains(&format!("expand_reduction(\"{}\")", r.id)),
"the honesty banner must spell out the exact escape hatch: {}",
r.placeholder
);
assert!(
r.placeholder.contains("original 16 messages in sidecar"),
"{}",
r.placeholder
);
assert!(matches!(
r.kind,
ReductionKind::TurnsCleared {
summary: Some(_),
..
}
));
let placeholder_msg = &view[first];
assert_eq!(
supercode_harness::reduce::reduction_id(placeholder_msg),
Some(r.id.as_str())
);
let _ = last;
}
#[test]
fn dev02_invert_restores_original_byte_exact_and_never_carries_summary_text() {
for summaries_on in [false, true] {
let mut session = empty_session();
append_padded_turns(&mut session, 10);
let msgs = session.messages.clone();
let mut policy = ReductionPolicy {
clear_turns_older_than: Some(8),
summarize_cleared_turns: summaries_on,
..ReductionPolicy::default()
};
let prior = ReductionLog::default();
if summaries_on {
let fake = FakeSummarizer {
text: "UNIQUE-SUMMARY-MARKER-9f3c should never survive invert or export",
model: "fake-model",
};
policy.cleared_turns_summary =
prepare_cleared_turns_summary(&msgs, &policy, &prior, &fake);
assert!(policy.cleared_turns_summary.is_some());
}
let (view, log) = project_messages(&msgs, &policy, &prior);
if summaries_on {
let r = log
.reductions
.iter()
.find(|r| matches!(r.kind, ReductionKind::TurnsCleared { .. }))
.unwrap();
assert!(r.placeholder.contains("UNIQUE-SUMMARY-MARKER-9f3c"));
}
verify_log(&log, &session)
.unwrap_or_else(|e| panic!("verify_log must pass (summaries_on={summaries_on}): {e}"));
let inverted = invert(&view, &log, &session)
.unwrap_or_else(|e| panic!("invert must pass (summaries_on={summaries_on}): {e}"));
assert!(
messages_identical(&inverted, &msgs),
"invert must restore the original byte-exact (summaries_on={summaries_on})"
);
for m in &inverted {
let content = m.content.as_deref().unwrap_or("");
assert!(
!content.contains("UNIQUE-SUMMARY-MARKER-9f3c"),
"invert leaked summary text into a restored original (summaries_on={summaries_on})"
);
}
let sidecar_jsonl = empty_session().to_native_jsonl_v2(&msgs);
let exported = export_session(&sidecar_jsonl, SessionFormat::ClaudeCode)
.unwrap_or_else(|e| panic!("export_session failed (summaries_on={summaries_on}): {e}"));
assert!(
!exported.contains("UNIQUE-SUMMARY-MARKER-9f3c"),
"export leaked summary text (summaries_on={summaries_on}):\n{exported}"
);
assert!(!exported.contains(supercode_harness::reduce::REDUCTION_SENTINEL));
}
}
#[test]
fn dev03_erroring_summarizer_falls_back_to_the_deterministic_stub() {
let mut session = empty_session();
append_padded_turns(&mut session, 10);
let msgs = session.messages.clone();
let mut policy = ReductionPolicy {
clear_turns_older_than: Some(8),
summarize_cleared_turns: true,
..ReductionPolicy::default()
};
let prior = ReductionLog::default();
let prepared = prepare_cleared_turns_summary(&msgs, &policy, &prior, &FailingSummarizer);
assert!(
prepared.is_none(),
"an erroring summarizer must never be treated as prepared"
);
policy.cleared_turns_summary = prepared;
let (_, log) = project_messages(&msgs, &policy, &prior);
let r = log
.reductions
.iter()
.find(|r| matches!(r.kind, ReductionKind::TurnsCleared { .. }))
.unwrap();
assert!(
!r.placeholder.contains("sc-summary"),
"failure must fall back to the deterministic stub, never a partial/blocked one: {}",
r.placeholder
);
assert!(matches!(
r.kind,
ReductionKind::TurnsCleared { summary: None, .. }
));
struct BlankSummarizer;
impl SpanSummarizer for BlankSummarizer {
fn summarize(&self, _: &str) -> ReductionResult<String> {
Ok(" \n ".to_string())
}
fn model_id(&self) -> &str {
"blank-model"
}
}
let prepared_blank = prepare_cleared_turns_summary(&msgs, &policy, &prior, &BlankSummarizer);
assert!(
prepared_blank.is_none(),
"a blank/whitespace-only summary must also fall back to the deterministic stub"
);
}
#[test]
fn dev04_audit_trail_records_model_id_prompt_version_and_summary_hash() {
let mut session = empty_session();
append_padded_turns(&mut session, 10);
let msgs = session.messages.clone();
let mut policy = ReductionPolicy {
clear_turns_older_than: Some(8),
summarize_cleared_turns: true,
..ReductionPolicy::default()
};
let prior = ReductionLog::default();
let fake = FakeSummarizer {
text: "audit-trail summary text",
model: "audit-model-42",
};
policy.cleared_turns_summary = prepare_cleared_turns_summary(&msgs, &policy, &prior, &fake);
assert!(policy.cleared_turns_summary.is_some());
let (_, log) = project_messages(&msgs, &policy, &prior);
let r = log
.reductions
.iter()
.find(|r| matches!(r.kind, ReductionKind::TurnsCleared { .. }))
.unwrap();
let audit = match &r.kind {
ReductionKind::TurnsCleared { summary, .. } => summary
.as_ref()
.expect("a summarized span must carry audit metadata"),
_ => unreachable!(),
};
assert_eq!(audit.model_id, "audit-model-42");
assert_eq!(audit.prompt_version, PROMPT_VERSION);
assert_eq!(
audit.summary_hash,
content_hash("audit-trail summary text".as_bytes()),
"summary_hash must hash the summarizer's own text, independent of the placeholder's \
surrounding banner/id punctuation"
);
let json = serde_json::to_string(&log).unwrap();
let reloaded: ReductionLog = serde_json::from_str(&json).unwrap();
assert_eq!(reloaded, log);
verify_log(&log, &session).expect("verify_log must pass — anchored on sidecar bytes only");
}
#[test]
fn dev05_spans_at_or_below_the_floor_are_never_summarized_no_side_call_at_all() {
let mut session = empty_session();
append_padded_turns(&mut session, 10);
let msgs = session.messages.clone();
let prior = ReductionLog::default();
let counting = CountingSummarizer {
calls: AtomicUsize::new(0),
text: "should not be produced",
};
let policy_above_floor = ReductionPolicy {
clear_turns_older_than: Some(8),
summarize_cleared_turns: true,
expected_summary_bytes: 1_000_000,
summary_cost_floor_multiple: 1,
..ReductionPolicy::default()
};
let prepared = prepare_cleared_turns_summary(&msgs, &policy_above_floor, &prior, &counting);
assert!(
prepared.is_none(),
"a span below the cost-guard floor must never be prepared"
);
assert_eq!(
counting.calls.load(Ordering::SeqCst),
0,
"the summarizer must never be CALLED for a below-floor span — not just have its result discarded"
);
let counting2 = CountingSummarizer {
calls: AtomicUsize::new(0),
text: "now above the floor",
};
let policy_below_floor = ReductionPolicy {
clear_turns_older_than: Some(8),
summarize_cleared_turns: true,
expected_summary_bytes: 1,
summary_cost_floor_multiple: 1,
..ReductionPolicy::default()
};
let prepared2 = prepare_cleared_turns_summary(&msgs, &policy_below_floor, &prior, &counting2);
assert!(
prepared2.is_some(),
"the identical span must be prepared once it clears the floor"
);
assert_eq!(counting2.calls.load(Ordering::SeqCst), 1);
let probe = CountingSummarizer {
calls: AtomicUsize::new(0),
text: "probe",
};
let policy_probe = ReductionPolicy {
clear_turns_older_than: Some(8),
summarize_cleared_turns: true,
expected_summary_bytes: 1,
summary_cost_floor_multiple: 1,
..ReductionPolicy::default()
};
let _ = prepare_cleared_turns_summary(&msgs, &policy_probe, &prior, &probe);
let policy_off = ReductionPolicy {
clear_turns_older_than: Some(8),
..ReductionPolicy::default()
};
let (_, log_off) = project_messages(&msgs, &policy_off, &prior);
let r = log_off
.reductions
.iter()
.find(|r| matches!(r.kind, ReductionKind::TurnsCleared { .. }))
.unwrap();
let n_bytes: usize = r
.placeholder
.split(';')
.nth(1)
.and_then(|s| s.split("B)").next())
.map(|s| s.trim().replace(',', ""))
.and_then(|s| s.parse().ok())
.expect("deterministic stub must carry a parseable `; <N>B)` clause");
let counting_at_floor = CountingSummarizer {
calls: AtomicUsize::new(0),
text: "at floor",
};
let policy_at_floor = ReductionPolicy {
clear_turns_older_than: Some(8),
summarize_cleared_turns: true,
expected_summary_bytes: n_bytes,
summary_cost_floor_multiple: 1, ..ReductionPolicy::default()
};
let prepared_at_floor =
prepare_cleared_turns_summary(&msgs, &policy_at_floor, &prior, &counting_at_floor);
assert!(
prepared_at_floor.is_none(),
"a span exactly AT the floor (not strictly above) must still be skipped"
);
assert_eq!(counting_at_floor.calls.load(Ordering::SeqCst), 0);
let counting_above_floor = CountingSummarizer {
calls: AtomicUsize::new(0),
text: "one byte above the floor",
};
let policy_one_above = ReductionPolicy {
clear_turns_older_than: Some(8),
summarize_cleared_turns: true,
expected_summary_bytes: n_bytes - 1,
summary_cost_floor_multiple: 1, ..ReductionPolicy::default()
};
let prepared_one_above =
prepare_cleared_turns_summary(&msgs, &policy_one_above, &prior, &counting_above_floor);
assert!(
prepared_one_above.is_some(),
"a span one byte above the floor must be summarized"
);
assert_eq!(counting_above_floor.calls.load(Ordering::SeqCst), 1);
}