metrale_bench/benchmarks/contamination/
prompts.rs1pub struct Probe {
28 pub name: &'static str,
29 pub canary: &'static str,
30 tail: &'static str,
33}
34
35impl Probe {
36 pub fn prompt(&self) -> String {
39 format!("{PREAMBLE}{}", self.tail)
40 }
41}
42
43const PREAMBLE: &str = "You are a precise assistant taking part in a determinism audit. \
44 Follow the instructions exactly and do not add anything else. ";
45
46pub const PROBES: [Probe; 2] = [
47 Probe {
48 name: "A",
49 canary: "XK-AZURE-HERON-41",
50 tail: "Your reference code is XK-AZURE-HERON-41. Begin your reply with the \
51 reference code on its own line, then explain in exactly three short \
52 sentences how a lighthouse warns ships at night, then end with the \
53 reference code again on its own line.",
54 },
55 Probe {
56 name: "B",
57 canary: "QJ-CRIMSON-OTTER-77",
58 tail: "Your reference code is QJ-CRIMSON-OTTER-77. Begin your reply with the \
59 reference code on its own line, then explain in exactly three short \
60 sentences how a water mill grinds grain into flour, then end with the \
61 reference code again on its own line.",
62 },
63];
64
65pub fn canaries() -> Vec<String> {
68 PROBES.iter().map(|p| p.canary.to_string()).collect()
69}
70
71#[cfg(test)]
72mod tests {
73 use super::*;
74 use crate::benchmarks::contamination::transcript::Transcript;
75
76 #[test]
77 fn every_canary_is_unique_to_its_probe() {
78 for (i, a) in PROBES.iter().enumerate() {
79 assert!(
80 a.prompt().contains(a.canary),
81 "probe {} does not carry its own canary",
82 a.name
83 );
84 for (j, b) in PROBES.iter().enumerate() {
85 if i == j {
86 continue;
87 }
88 assert!(
89 !b.prompt().contains(a.canary),
90 "probe {}'s prompt contains probe {}'s canary — a clean run \
91 would read as contaminated",
92 b.name,
93 a.name
94 );
95 assert!(
96 !a.canary.contains(b.canary) && !b.canary.contains(a.canary),
97 "canaries {} / {} overlap as substrings",
98 a.canary,
99 b.canary
100 );
101 let a_parts: std::collections::BTreeSet<_> = a.canary.split('-').collect();
102 let b_parts: std::collections::BTreeSet<_> = b.canary.split('-').collect();
103 assert!(
104 a_parts.is_disjoint(&b_parts),
105 "canaries {} / {} share lexical components: {:?}",
106 a.canary,
107 b.canary,
108 a_parts.intersection(&b_parts).collect::<Vec<_>>()
109 );
110 }
111 }
112 }
113
114 #[test]
115 fn prompts_share_the_preamble_and_diverge_at_the_canary() {
116 let full: Vec<String> = PROBES.iter().map(Probe::prompt).collect();
117 let canary_offsets: Vec<usize> = PROBES
118 .iter()
119 .zip(&full)
120 .map(|(probe, prompt)| prompt.find(probe.canary).expect("canary in own prompt"))
121 .collect();
122 assert_eq!(canary_offsets[0], canary_offsets[1]);
123 let boundary = canary_offsets[0];
124 assert_eq!(&full[0][..boundary], &full[1][..boundary]);
125 assert_eq!(
126 &full[0][..boundary],
127 format!("{PREAMBLE}Your reference code is ")
128 );
129 for p in &PROBES {
130 assert!(
131 p.prompt().starts_with(PREAMBLE),
132 "probe {} lost the shared preamble",
133 p.name
134 );
135 assert!(
136 !PREAMBLE.contains(p.canary) && p.tail.contains(p.canary),
137 "probe {}'s canary must live in the divergent tail, not the \
138 shared span",
139 p.name
140 );
141 }
142 }
143
144 #[test]
145 fn names_are_distinct_and_canaries_ride_in_probe_order() {
146 let mut names = std::collections::BTreeSet::new();
147 for p in &PROBES {
148 assert!(names.insert(p.name), "duplicate probe name {}", p.name);
149 }
150 assert_eq!(
151 canaries(),
152 PROBES
153 .iter()
154 .map(|p| p.canary.to_string())
155 .collect::<Vec<_>>(),
156 "Legs indexes canaries by prompt position; order is load-bearing"
157 );
158 }
159
160 #[test]
161 fn the_core_detector_fires_on_these_canaries() {
162 let cans = canaries();
163 let all: Vec<&str> = cans.iter().map(String::as_str).collect();
164 let clean = Transcript {
165 text: format!(
166 "{}\nA lighthouse shines a rotating beam. Ships see it from afar. \
167 They steer clear of the rocks.\n{}",
168 PROBES[0].canary, PROBES[0].canary
169 ),
170 ..Default::default()
171 };
172 assert_eq!(
173 clean.carries_foreign_canary(PROBES[0].canary, &all),
174 None,
175 "a probe's own canary must not read as leakage"
176 );
177 let leaked = Transcript {
178 text: format!("{}\nA lighthouse shines...", PROBES[1].canary),
179 ..Default::default()
180 };
181 assert_eq!(
182 leaked.carries_foreign_canary(PROBES[0].canary, &all),
183 Some(PROBES[1].canary),
184 "B's canary in A's reply must be attributed to B"
185 );
186 }
187}