1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
//! Guarantor-path test closing the same coverage-gap class TR-2/TR-3/TR-4/
//! TR-6/TR-10's own `tr*_guarantor.rs` files close for their reductions: a
//! LIVE `Agent` (recorder + `ReductionPolicy`, TR-7's `summarize_cleared_turns`
//! ON with an injected deterministic fake `SpanSummarizer`) clears AND
//! summarizes a span; the sidecar and reduction log are persisted, then
//! **reloaded from disk** (never the live `Agent`/in-memory `Session`);
//! `verify_log` and `invert` both run OFFLINE against that reloaded state.
//!
//! What this specifically proves, that `reduce_summaries.rs`'s in-memory
//! `project_messages` unit tests structurally cannot: TR-7's audit fields
//! (`SpanSummary`) and the summarized placeholder survive a real
//! `SidecarWriter` -> disk -> `SessionStore` round trip, and — the crux of
//! B10/TR-7 dev/02 — the RELOADED SIDECAR itself never contains one byte of
//! the LLM summary text (it only ever exists in the projected view's
//! placeholder), and `invert` restores the original cleared turns byte-exact
//! straight from that reloaded sidecar.
//!
//! No real model call anywhere: the injected summarizer is a small
//! deterministic in-process fake.
use std::path::PathBuf;
use std::sync::atomic::{AtomicUsize, Ordering};
use async_trait::async_trait;
use supercode::reduce::summarize::SpanSummarizer;
use supercode::reduce::{invert, project_messages, verify_log, ReductionKind, ReductionPolicy};
use supercode::session::Session;
use supercode::sidecar::SidecarWriter;
use supercode::store::SessionStore;
use supercode::{Agent, ChatMessage, ChatRequest, Config, Provider, Usage};
fn temp_dir(tag: &str) -> PathBuf {
static N: AtomicUsize = AtomicUsize::new(0);
let dir = std::env::temp_dir().join(format!(
"supercode-tr7-guarantor-{tag}-{}-{}",
std::process::id(),
N.fetch_add(1, Ordering::SeqCst)
));
std::fs::create_dir_all(&dir).unwrap();
dir
}
/// A deterministic all-ASCII filler string of exactly `len` bytes — padding
/// so the cleared span comfortably clears TR-7's default cost-guard floor
/// (`expected_summary_bytes(400) * summary_cost_floor_multiple(4)` = 1600
/// bytes) without needing to touch those knobs.
fn filler(len: usize) -> String {
(0..len).map(|i| (b'a' + (i % 26) as u8) as char).collect()
}
/// Marker text unique enough that finding it anywhere outside the projected
/// view's own placeholder is unambiguous proof of a leak.
const SUMMARY_MARKER: &str = "SUMMARY-MARKER-tr7-guarantor-8f2b";
/// The one injected side-call this whole test uses — a small, deterministic,
/// in-process fake. Never a real model call.
struct FakeSummarizer;
impl SpanSummarizer for FakeSummarizer {
fn summarize(&self, span_text: &str) -> supercode::Result<String> {
Ok(format!(
"{SUMMARY_MARKER}: span covered {} chars of transcript",
span_text.len()
))
}
fn model_id(&self) -> &str {
"fake-tr7-guarantor-model-v1"
}
}
/// Every turn: a plain text reply (no tool calls), padded so each turn's
/// serialized bytes add up quickly toward the cost-guard floor.
struct PlainRepliesPadded {
calls: AtomicUsize,
}
#[async_trait]
impl Provider for PlainRepliesPadded {
async fn complete(
&self,
_req: &ChatRequest,
_on_delta: &(dyn for<'a> Fn(&'a str) + Send + Sync),
) -> supercode::Result<(ChatMessage, Usage)> {
let n = self.calls.fetch_add(1, Ordering::SeqCst);
Ok((
ChatMessage::assistant(format!("reply {n}: {}", filler(150))),
Usage::default(),
))
}
}
/// dev/02 + dev/04 (guarantor-path closure): a live agent run with a
/// recorder + `ReductionPolicy` (summaries ON, fake summarizer injected)
/// establishes a summarized `TurnsCleared` reduction; `verify_log` and
/// `invert` then run OFFLINE against the sidecar and reduction log reloaded
/// from disk. Both must pass clean; `invert` must restore the full original
/// cleared turns byte-exact, and the reloaded sidecar must carry ZERO bytes
/// of the summary text.
#[tokio::test]
async fn summarized_span_survives_live_agent_disk_reload_offline_verify_and_invert() {
let dir = temp_dir("dev02-dev04");
let store_dir = dir.join("store");
let store = SessionStore::open(&store_dir).unwrap();
let name = "tr7-dev02-dev04";
let sidecar_path = store.sidecar_path(name);
let config = Config::builder()
.cwd(dir.clone())
.compact_after_messages(8)
.build();
let mut agent = Agent::with_parts(
config,
Box::new(PlainRepliesPadded {
calls: AtomicUsize::new(0),
}),
supercode::tools::ToolRegistry::new(),
);
let empty_session = Session::from_claude_code_str("").unwrap();
let writer = SidecarWriter::create(&sidecar_path, &empty_session).unwrap();
agent.set_recorder(writer);
// Cost-guard knobs deliberately low: this test's job is exercising the
// summarize -> record -> persist -> disk-reload -> verify/invert
// pipeline end to end, not re-proving the floor's exact boundary
// (`reduce_summaries.rs`'s dev/05 already covers that precisely) — a
// trivially-cleared floor here just keeps the fixture's turn count
// small.
let policy = ReductionPolicy {
summarize_cleared_turns: true,
expected_summary_bytes: 10,
summary_cost_floor_multiple: 2,
..ReductionPolicy::default()
};
agent.set_reduction_policy(policy.clone());
agent.set_span_summarizer(FakeSummarizer);
// 6 turns (mirrors `tr12_cap_supersession.rs`'s own turn count): enough
// for `compact_after_messages(8)` to trigger and stay established.
for i in 0..6 {
let reply = agent
.send(format!("turn {i}: {}", filler(150)))
.await
.unwrap();
assert!(
reply.starts_with("reply"),
"unexpected reply at turn {i}: {reply}"
);
}
let log_live = agent.reduction_log().clone();
let cleared = log_live
.reductions
.iter()
.find(|r| matches!(r.kind, ReductionKind::TurnsCleared { .. }))
.expect("a TurnsCleared reduction must have been established");
let (first, last, audit) = match &cleared.kind {
ReductionKind::TurnsCleared {
first,
last,
summary,
} => (
*first,
*last,
summary
.as_ref()
.expect("TR-7 dev/04: a summarized span must carry SpanSummary audit metadata"),
),
_ => unreachable!(),
};
assert_eq!(audit.model_id, "fake-tr7-guarantor-model-v1");
assert_eq!(
audit.prompt_version,
supercode::reduce::summarize::PROMPT_VERSION
);
assert!(
cleared.placeholder.contains(SUMMARY_MARKER),
"the live view's placeholder must carry the LLM summary text: {}",
cleared.placeholder
);
assert!(
cleared
.placeholder
.contains(&format!("expand_reduction(\"{}\")", cleared.id)),
"the honesty banner must name this reduction's own id: {}",
cleared.placeholder
);
// Persist the reduction log the way the CLI does.
store.save_reduction_log(name, &log_live).unwrap();
// --- OFFLINE from here: fresh reads from disk, no live Agent/Session ---
let sidecar_jsonl = store
.load_sidecar(name)
.unwrap()
.expect("sidecar must exist on disk");
let sidecar = Session::from_sidecar_str(&sidecar_jsonl).unwrap();
let log_reloaded = store
.load_reduction_log(name)
.unwrap()
.expect("reduction log must exist on disk");
assert_eq!(
log_reloaded, log_live,
"the reloaded-from-disk log must be identical to the live agent's log"
);
// THE CRUX (B10 / TR-7 dev/02): the reloaded SIDECAR itself — the
// full-fidelity record `invert`/`expand_reduction` restore from — must
// never contain one byte of the LLM summary text. It only ever exists in
// the projected view's placeholder, never in the sidecar.
let sidecar_text = serde_json::to_string(&sidecar.messages).unwrap();
assert!(
!sidecar_text.contains(SUMMARY_MARKER),
"the reloaded sidecar must carry ZERO summary text: {sidecar_text}"
);
// Cross-check directly against the cleared range's own original content.
for m in &sidecar.messages[first..=last] {
let content = m.content.as_deref().unwrap_or("");
assert!(
!content.contains(SUMMARY_MARKER),
"a cleared-range original message in the sidecar must never carry summary text: \
{content}"
);
}
// The exact primitive `cli/main.rs`'s `show-reductions`/`convert`/
// `inspect` all call before doing anything else with a reduced session —
// anchored on sidecar bytes alone, so it passes identically regardless of
// TR-7's summary being present.
verify_log(&log_reloaded, &sidecar)
.expect("verify_log must pass clean against the reloaded-from-disk sidecar");
// Re-projecting from the reloaded sidecar with its own log must not
// invent new reductions and must keep reproducing the SAME (summarized)
// placeholder verbatim — the established `TurnsCleared` range is a
// singleton, reapplied forever, independent of whether a summarizer is
// even installed on this second, offline pass.
let (final_view, reprojected_log) = project_messages(&sidecar.messages, &policy, &log_reloaded);
assert_eq!(
reprojected_log, log_reloaded,
"re-projecting from the reloaded sidecar with its own log must not invent new reductions"
);
let view_placeholder = final_view
.iter()
.find_map(|m| {
supercode::reduce::reduction_id(m).and_then(|id| (id == cleared.id).then_some(m))
})
.and_then(|m| m.content.as_deref())
.expect("the reprojected view must still carry the summarized placeholder");
assert!(
view_placeholder.contains(SUMMARY_MARKER),
"{view_placeholder}"
);
let inverted = invert(&final_view, &log_reloaded, &sidecar)
.expect("invert must pass clean against the reloaded-from-disk sidecar");
assert_eq!(inverted.len(), sidecar.messages.len());
// Byte-exact restore of the ORIGINAL cleared turns, from the
// disk-reloaded sidecar — and no summary text anywhere in the result.
for (i, (restored, original)) in inverted
.iter()
.zip(&sidecar.messages)
.enumerate()
.filter(|(i, _)| *i >= first && *i <= last)
{
assert_eq!(
restored.content, original.content,
"invert must restore cleared-range message {i} byte-exact from the reloaded sidecar"
);
let content = restored.content.as_deref().unwrap_or("");
assert!(
!content.contains(SUMMARY_MARKER),
"invert must never leak summary text into a restored original: {content}"
);
}
std::fs::remove_dir_all(&dir).ok();
}