metrale_bench/benchmarks/ssm_poison/
probe.rs1use serde_json::{Value, json};
14
15use crate::benchmarks::transcript::Transcript;
16
17pub const LONG_PREFIX: &str = "\
20SYSTEM DOCUMENT 7741-C, revision 9, frozen text — quote from it, never extend it.
21
22Section 1, Invariants. The ledger defines three invariants that every batch
23must preserve end to end. First, monotonic sequence: each record carries a
24sequence number exactly one greater than its predecessor, and gaps are
25treated as corruption, never as reordering. Second, bounded drift: the
26clock offset between any two nodes in the batch must stay under forty
27milliseconds for the duration of the batch, and an offset beyond that bound
28invalidates every record written after the breach. Third, closed membership:
29a node joins the batch through a single signed admission record and leaves
30through a single signed departure record, and no record may reference a node
31outside its admission and departure interval.
32
33Section 2, Batch lifecycle. A batch opens when the coordinator writes the
34genesis record, assigning the batch id and the initial sequence window.
35Nodes acknowledge the genesis record, after which the coordinator admits
36writes. The batch runs until either the coordinator writes the seal record
37or the drift invariant is breached. When a batch seals, every node flushes
38its local ledger segment to the archive tier and reports the segment digest.
39The digest is the hash of the concatenated record envelopes in sequence
40order, excluding payloads. Two nodes that sealed the same batch must report
41the same digest; a mismatch is escalated as a split-brain event and the
42batch is quarantined.
43
44Section 3, Recovery. Recovery replays the archived segments in sequence
45order against an empty ledger until the digest matches the sealed value.
46Replay is deterministic by construction: the envelope fixes the record
47bytes, and the sequence order fixes their application order. Recovery never
48consults a live node; a replay that cannot reproduce the sealed digest
49marks the segment as untrusted and routes it to manual review. The recovery
50window is bounded at ninety minutes per segment, and a segment that exceeds
51the window is split at the nearest checkpoint boundary and retried in
52halves.
53
54Section 4, Checksum rule. The envelope checksum is computed over the header
55fields in their serialized order: batch id, sequence number, node id,
56timestamp, then payload length. The checksum excludes the payload itself,
57which is carried separately and verified against the declared length only.
58When a record is archived, the archive tier recomputes the checksum from the
59stored header and refuses any record whose recomputed value differs from
60the recorded one; such a record is quarantined with its recomputed value
61attached. The checksum is sixteen bytes and is never truncated in transit.
62
63Section 5, Escalation ladder. A drift breach escalates to the coordinator
64within one heartbeat interval. A digest mismatch escalates immediately and
65pauses admission. A split-brain event escalates to the on-call engineer and
66freezes the archive tier until the quarantined batch is resolved. Escalation
67records carry the breached invariant by name, the observed values, and the
68bounds from this document, in that order.
69
70Section 6, Admission and departure. Admission is a two-phase exchange. The
71candidate node first presents a signed intent record naming the batch and its
72own node id; the coordinator validates the signature against the roster key
73and, if the batch is open, returns an admission record carrying the assigned
74sequence window. The candidate becomes a member only after it acknowledges
75the admission record. Departure is symmetric: a member submits a departure
76intent, the coordinator drains the member's outstanding writes, then issues
77the departure record. A node that leaves before its writes are drained is
78marked delinquent, and its unacknowledged records are re-assigned to the
79coordinator for replay. Membership changes are appended to the batch journal
80in order and are themselves covered by the envelope checksum, so a roster
81tamper is detected at the next archive recomputation.
82
83Section 7, Archival layout. The archive tier stores segments in fixed-size
84blocks aligned on checksum boundaries; a segment smaller than one block is
85padded with a terminal envelope carrying a zero payload length. Block
86headers repeat the batch id and the first and last sequence numbers of the
87contained records, which lets a recovery scan locate a segment by sequence
88number alone without reading payloads. The tier keeps two copies of every
89block on distinct media and compares them on read; a copy disagreement
90triggers a re-fetch from the member that sealed the batch, and if that
91member is gone the block is marked degraded and excluded from digest
92recomputation until restored. Degraded blocks are reported in the daily
93integrity summary with their batch id, block offset, and the media that
94disagreed.";
95
96pub const TURNS: [&str; 4] = [
100 "Reply with exactly one line: ACK 7741-C and the number of sections \
102 listed in the document.",
103 "List the three invariants from Section 1 of the document, numbered 1 \
105 to 3, one per line, each in at most ten words.",
106 "Rewrite Section 4 of the document as exactly two sentences, preserving \
108 every rule it states.",
109 "Answer from the document: (a) what does the envelope checksum cover, in \
111 field order; (b) what is excluded from it; (c) what happens when the \
112 archive tier recomputes a mismatching checksum. One short paragraph.",
113];
114
115pub(super) fn request_body(model: &str, messages: &[Value], max_tokens: usize) -> Value {
121 json!({
122 "model": model,
123 "stream": true,
124 "stream_options": {"include_usage": true},
125 "temperature": 0.0,
126 "seed": 0,
127 "max_tokens": max_tokens,
128 "messages": messages,
129 })
130}
131
132pub(super) fn first_turn() -> String {
134 format!("{LONG_PREFIX}\n\n{}", TURNS[0])
135}
136
137fn section_count() -> usize {
140 (1..)
141 .take_while(|n| LONG_PREFIX.contains(&format!("Section {n},")))
142 .count()
143}
144
145fn contains_token(text: &str, expected: &str) -> bool {
146 text.split(|c: char| !c.is_alphanumeric())
147 .any(|token| token.eq_ignore_ascii_case(expected))
148}
149
150fn has_number_prefix(line: &str, number: usize) -> bool {
151 line.strip_prefix(&number.to_string())
152 .and_then(|rest| rest.chars().next())
153 .is_some_and(|c| c.is_whitespace() || matches!(c, '.' | ')' | ':'))
154}
155
156fn contains_in_order(text: &str, phrases: &[&str]) -> bool {
157 let text = text.to_lowercase();
158 let mut cursor = 0;
159 phrases.iter().all(|phrase| {
160 let Some(found) = text[cursor..].find(phrase) else {
161 return false;
162 };
163 cursor += found + phrase.len();
164 true
165 })
166}
167
168pub(super) fn validate_reference(reference: &[Transcript]) -> Vec<String> {
176 let mut violations = Vec::new();
177 if reference.len() != TURNS.len() {
178 violations.push(format!(
179 "reference has {} turn(s), script has {}",
180 reference.len(),
181 TURNS.len()
182 ));
183 return violations;
184 }
185 for (i, t) in reference.iter().enumerate() {
186 match t.finish_reason.as_deref() {
187 Some("stop") => {}
188 Some("length") => violations.push(format!(
189 "turn {}: reference hit the token budget (finish_reason=length) — a \
190 truncated reference cannot anchor the collapse ratios",
191 i + 1
192 )),
193 other => violations.push(format!(
194 "turn {}: reference did not finish normally (finish_reason={other:?})",
195 i + 1
196 )),
197 }
198 }
199 let t1 = &reference[0].text;
200 if !t1.contains("ACK 7741-C") {
201 violations.push("turn 1: missing the demanded 'ACK 7741-C' acknowledgement".into());
202 }
203 let sections = section_count();
206 let words = [
207 "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine",
208 ];
209 let stripped = t1.replace("7741-C", "");
210 let has_digit = contains_token(&stripped, §ions.to_string());
211 let has_word = words
212 .get(sections)
213 .is_some_and(|word| contains_token(&stripped, word));
214 if !has_digit && !has_word {
215 violations.push(format!(
216 "turn 1: does not state the document's section count ({sections})"
217 ));
218 }
219 let lines: Vec<&str> = reference[1]
220 .text
221 .lines()
222 .map(str::trim)
223 .filter(|l| !l.is_empty())
224 .collect();
225 if lines.len() != 3 {
226 violations.push(format!(
227 "turn 2: expected exactly 3 numbered lines, got {}",
228 lines.len()
229 ));
230 } else {
231 for (i, line) in lines.iter().enumerate() {
232 if !has_number_prefix(line, i + 1) {
233 violations.push(format!(
234 "turn 2: line {} does not start with its number: {:?}",
235 i + 1,
236 line
237 ));
238 }
239 }
240 }
241 let t3 = reference[2].text.to_lowercase();
242 let t3_sentences = t3
243 .split_terminator(['.', '!', '?'])
244 .filter(|sentence| !sentence.trim().is_empty())
245 .count();
246 let t3_fields = contains_in_order(
249 &t3,
250 &[
251 "batch id",
252 "sequence number",
253 "node id",
254 "timestamp",
255 "payload length",
256 ],
257 );
258 let t3_exclusion = t3.contains("exclud") && t3.contains("payload");
261 let t3_recompute = t3.contains("recompute");
262 let t3_quarantine = t3.contains("quarantin");
263 if t3_sentences != 2 || !t3_fields || !t3_exclusion || !t3_recompute || !t3_quarantine {
264 violations.push(format!(
265 "turn 3: Section 4 rewrite failed an anchor (sentences={t3_sentences}, \
266 fields_in_order={t3_fields}, exclusion={t3_exclusion}, \
267 recompute={t3_recompute}, quarantine={t3_quarantine})"
268 ));
269 }
270 let t4 = reference[3].text.to_lowercase();
271 if !contains_in_order(
272 &t4,
273 &[
274 "batch id",
275 "sequence number",
276 "node id",
277 "timestamp",
278 "payload length",
279 ],
280 ) || !t4.contains("exclud")
282 || !t4.contains("payload")
283 || !t4.contains("recompute")
284 || !t4.contains("quarantin")
285 {
286 violations.push("turn 4: does not answer every checksum question".into());
287 }
288 violations
289}
290
291#[cfg(test)]
292#[path = "probe_tests.rs"]
293mod probe_tests;