Skip to main content

supercode_interchange/
fidelity.rs

1//! Behavioral translation-fidelity measurement.
2//!
3//! These metrics describe what survives an actual canonical-session export
4//! and reload. They are deliberately separate from regression floors: a
5//! stable, expected loss is still loss against supercode's parity goal.
6
7use std::collections::BTreeSet;
8
9use serde::{Deserialize, Serialize};
10use serde_json::Value;
11
12use crate::{ChatMessage, Role};
13
14/// How faithfully a reconstruction reproduces its source.
15///
16/// One vocabulary for every surface that has to state what it gave up. It was
17/// introduced for session ARTIFACTS (`harness.v1.sessions.export` reports a
18/// level plus a named residue list); session LOADS report the same pair,
19/// because a read-only VIEW of a session is allowed to settle for
20/// [`Fidelity::Semantic`] where a continuation is not (see
21/// a session loader's explicitly semantic/read-only mode).
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
23#[serde(rename_all = "snake_case")]
24pub enum Fidelity {
25    /// The reconstruction reproduces the source bytes exactly.
26    ByteLossless,
27    /// Every value survives; only the container bytes were re-synthesized.
28    ValueLossless,
29    /// Meaning survives; the named residue says exactly what did not.
30    Semantic,
31}
32
33impl Fidelity {
34    /// Whether this level tolerates named loss.
35    ///
36    /// Only [`Fidelity::Semantic`] does — every stricter level must fail
37    /// loudly instead of degrading, which is what keeps continuation,
38    /// transfer and export guarantees intact.
39    pub fn tolerates_residue(self) -> bool {
40        matches!(self, Self::Semantic)
41    }
42}
43
44/// Compare semantic message fields shared by the supported harnesses.
45pub fn messages_equal(a: &ChatMessage, b: &ChatMessage) -> bool {
46    if a.role != b.role || a.content != b.content || a.tool_call_id != b.tool_call_id {
47        return false;
48    }
49    let (a_calls, b_calls) = (a.tool_calls(), b.tool_calls());
50    a_calls.len() == b_calls.len()
51        && a_calls.iter().zip(b_calls).all(|(a_call, b_call)| {
52            a_call.id == b_call.id
53                && a_call.function.name == b_call.function.name
54                && a_call.function.parsed_arguments().ok()
55                    == b_call.function.parsed_arguments().ok()
56        })
57}
58
59/// Compare semantics plus multimodal parts and tool names.
60pub fn messages_equal_multimodal(a: &ChatMessage, b: &ChatMessage) -> bool {
61    if !messages_equal(a, b) || a.name != b.name {
62        return false;
63    }
64    let empty = Vec::new();
65    let a_parts = a.content_parts.as_ref().unwrap_or(&empty);
66    let b_parts = b.content_parts.as_ref().unwrap_or(&empty);
67    a_parts.len() == b_parts.len()
68        && a_parts
69            .iter()
70            .zip(b_parts)
71            .all(|(a_part, b_part)| normalize_part(a_part) == normalize_part(b_part))
72}
73
74fn normalize_part(part: &Value) -> (String, Option<String>, Option<Vec<u8>>) {
75    let kind = part
76        .get("type")
77        .and_then(Value::as_str)
78        .unwrap_or("")
79        .to_owned();
80    let url = part
81        .get("image_url")
82        .and_then(|value| value.get("url"))
83        .and_then(Value::as_str);
84    match url {
85        Some(url) if url.starts_with("data:") => {
86            let rest = &url["data:".len()..];
87            let (metadata, data) = rest.split_once(',').unwrap_or((rest, ""));
88            let mime = metadata
89                .strip_suffix(";base64")
90                .unwrap_or(metadata)
91                .to_owned();
92            (kind, Some(mime), decode_base64(data))
93        }
94        Some(url) => (kind, Some(url.to_owned()), None),
95        None => (kind, None, None),
96    }
97}
98
99fn decode_base64(input: &str) -> Option<Vec<u8>> {
100    fn digit(byte: u8) -> Option<u8> {
101        match byte {
102            b'A'..=b'Z' => Some(byte - b'A'),
103            b'a'..=b'z' => Some(byte - b'a' + 26),
104            b'0'..=b'9' => Some(byte - b'0' + 52),
105            b'+' => Some(62),
106            b'/' => Some(63),
107            _ => None,
108        }
109    }
110    let bytes = input
111        .bytes()
112        .filter(|byte| *byte != b'\n' && *byte != b'\r')
113        .collect::<Vec<_>>();
114    let mut output = Vec::with_capacity(bytes.len() / 4 * 3 + 3);
115    let mut chunk = [0_u8; 4];
116    let mut chunk_len = 0;
117    let mut padding = 0;
118    for byte in bytes {
119        if byte == b'=' {
120            padding += 1;
121            chunk[chunk_len] = 0;
122        } else {
123            chunk[chunk_len] = digit(byte)?;
124        }
125        chunk_len += 1;
126        if chunk_len == 4 {
127            let value = ((chunk[0] as u32) << 18)
128                | ((chunk[1] as u32) << 12)
129                | ((chunk[2] as u32) << 6)
130                | chunk[3] as u32;
131            output.push((value >> 16) as u8);
132            if padding < 2 {
133                output.push((value >> 8) as u8);
134            }
135            if padding < 1 {
136                output.push(value as u8);
137            }
138            chunk_len = 0;
139            padding = 0;
140        }
141    }
142    Some(output)
143}
144
145/// Canonical messages participating in cross-format fidelity scoring.
146pub fn core_messages(messages: &[ChatMessage]) -> Vec<ChatMessage> {
147    messages
148        .iter()
149        .filter(|message| message.role != Role::System)
150        .cloned()
151        .collect()
152}
153
154/// Whether a source message was intentionally outside the replayable slice.
155pub fn replay_excluded(message: &ChatMessage) -> bool {
156    message.metadata.get("compacted_out").map(String::as_str) == Some("true")
157        || message
158            .metadata
159            .get("pi_exclude_from_context")
160            .map(String::as_str)
161            == Some("true")
162}
163
164/// The replayable subsequence of a canonical transcript.
165pub fn replay_eligible(messages: &[ChatMessage]) -> Vec<ChatMessage> {
166    messages
167        .iter()
168        .filter(|message| !replay_excluded(message))
169        .cloned()
170        .collect()
171}
172
173/// Measured residue of one actual export/reload cell.
174#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
175pub struct FidelityResidue {
176    /// Correctly excluded pre-compaction or explicitly non-context messages.
177    pub compacted_out_excluded: usize,
178    /// Replay-eligible source messages with no semantic match after reload.
179    pub other_dropped_messages: usize,
180    /// Metadata keys whose source value was absent or changed after reload.
181    ///
182    /// The historical field name is retained because the frozen conformance
183    /// suite serializes this structure, but fidelity requires value equality,
184    /// not merely key presence.
185    pub dropped_metadata_keys: BTreeSet<String>,
186}
187
188impl FidelityResidue {
189    /// Whether the cell lost no replay-eligible message or metadata key.
190    pub fn is_semantically_lossless(&self) -> bool {
191        self.other_dropped_messages == 0 && self.dropped_metadata_keys.is_empty()
192    }
193
194    /// Total measured residue items, including intentional exclusions.
195    pub fn count(&self) -> usize {
196        self.compacted_out_excluded + self.other_dropped_messages + self.dropped_metadata_keys.len()
197    }
198}
199
200/// Result of measuring one actual translation cell.
201#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
202pub struct FidelityMetric {
203    /// Order-preserving semantic matches.
204    pub matched: usize,
205    /// Source canonical messages, including intentional replay exclusions.
206    pub total: usize,
207    /// Exact measured residue categories.
208    pub residue: FidelityResidue,
209}
210
211impl FidelityMetric {
212    /// Percentage of all canonical source messages that matched.
213    pub fn percent(&self) -> f64 {
214        if self.total == 0 {
215            100.0
216        } else {
217            self.matched as f64 / self.total as f64 * 100.0
218        }
219    }
220
221    /// Compatibility spelling used by the frozen conformance suite.
222    pub fn pct(&self) -> f64 {
223        self.percent()
224    }
225
226    /// Whether all replay-eligible messages and metadata key names survived.
227    pub fn is_semantically_lossless(&self) -> bool {
228        self.matched + self.residue.compacted_out_excluded == self.total
229            && self.residue.is_semantically_lossless()
230    }
231}
232
233/// Measure an export/reload cell without applying a regression floor.
234pub fn measure_fidelity(source: &[ChatMessage], reloaded: &[ChatMessage]) -> FidelityMetric {
235    let mut residue = FidelityResidue::default();
236    let mut matched = 0;
237    let mut reload_index = 0;
238    for source_message in source {
239        let found = (reload_index..reloaded.len())
240            .find(|index| messages_equal_multimodal(source_message, &reloaded[*index]));
241        match found {
242            Some(index) => {
243                matched += 1;
244                for (key, value) in &source_message.metadata {
245                    if reloaded[index].metadata.get(key) != Some(value) {
246                        residue.dropped_metadata_keys.insert(key.clone());
247                    }
248                }
249                reload_index = index + 1;
250            }
251            None if replay_excluded(source_message) => residue.compacted_out_excluded += 1,
252            None => residue.other_dropped_messages += 1,
253        }
254    }
255    FidelityMetric {
256        matched,
257        total: source.len(),
258        residue,
259    }
260}
261
262#[cfg(test)]
263mod tests {
264    use super::*;
265
266    #[test]
267    fn semantic_losslessness_ignores_only_explicit_replay_exclusions() {
268        let mut excluded = ChatMessage::user("old");
269        excluded
270            .metadata
271            .insert("compacted_out".into(), "true".into());
272        let kept = ChatMessage::user("new");
273        let metric = measure_fidelity(&[excluded, kept.clone()], &[kept]);
274        assert_eq!(metric.percent(), 50.0);
275        assert!(metric.is_semantically_lossless());
276    }
277
278    #[test]
279    fn changed_metadata_values_are_semantic_residue() {
280        let mut source = ChatMessage::user("hello");
281        source.metadata.insert("model".into(), "alpha".into());
282        let mut reloaded = source.clone();
283        reloaded.metadata.insert("model".into(), "beta".into());
284
285        let metric = measure_fidelity(&[source], &[reloaded]);
286
287        assert_eq!(metric.matched, 1);
288        assert_eq!(
289            metric.residue.dropped_metadata_keys,
290            BTreeSet::from(["model".into()])
291        );
292        assert!(!metric.is_semantically_lossless());
293    }
294}