Skip to main content

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