use crate::core::cloud::tamper::FieldDelta;
const MARKER_FIELD: &str = "_openlatch";
pub fn diff_entry_fields(
expected: &serde_json::Value,
observed: &serde_json::Value,
) -> Vec<FieldDelta> {
let mut out = Vec::new();
walk(expected, observed, "", &mut out);
out
}
fn walk(
expected: &serde_json::Value,
observed: &serde_json::Value,
path: &str,
out: &mut Vec<FieldDelta>,
) {
use serde_json::Value;
match (expected, observed) {
(Value::Object(a), Value::Object(b)) => {
for (k, va) in a {
let child_path = join(path, k);
if is_hmac_path(&child_path) {
continue;
}
match b.get(k) {
Some(vb) => walk(va, vb, &child_path, out),
None => out.push(FieldDelta {
field: child_path,
change: "removed".into(),
}),
}
}
for (k, _vb) in b {
if a.contains_key(k) {
continue;
}
let child_path = join(path, k);
if is_hmac_path(&child_path) {
continue;
}
out.push(FieldDelta {
field: child_path,
change: "added".into(),
});
}
}
(Value::Array(a), Value::Array(b)) => {
let max = a.len().max(b.len());
for i in 0..max {
let child_path = format!("{path}[{i}]");
match (a.get(i), b.get(i)) {
(Some(va), Some(vb)) => walk(va, vb, &child_path, out),
(Some(_), None) => out.push(FieldDelta {
field: child_path,
change: "removed".into(),
}),
(None, Some(_)) => out.push(FieldDelta {
field: child_path,
change: "added".into(),
}),
(None, None) => unreachable!("max covers both lengths"),
}
}
}
(a, b) if a == b => {}
_ => out.push(FieldDelta {
field: path.to_string(),
change: "modified".into(),
}),
}
}
fn join(path: &str, key: &str) -> String {
if path.is_empty() {
key.to_string()
} else {
format!("{path}.{key}")
}
}
fn is_hmac_path(path: &str) -> bool {
path == format!("{MARKER_FIELD}.hmac")
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn identical_entries_produce_empty_vec() {
let a = json!({"matcher": "", "hooks": [{"type": "command", "url": "x"}]});
let b = a.clone();
assert!(diff_entry_fields(&a, &b).is_empty());
}
#[test]
fn modified_scalar_reports_modified() {
let a = json!({"timeout": 10});
let b = json!({"timeout": 99});
let deltas = diff_entry_fields(&a, &b);
assert_eq!(deltas.len(), 1);
assert_eq!(deltas[0].field, "timeout");
assert_eq!(deltas[0].change, "modified");
}
#[test]
fn added_field_reports_added() {
let a = json!({"timeout": 10});
let b = json!({"timeout": 10, "extra": "surprise"});
let deltas = diff_entry_fields(&a, &b);
assert_eq!(deltas.len(), 1);
assert_eq!(deltas[0].field, "extra");
assert_eq!(deltas[0].change, "added");
}
#[test]
fn removed_field_reports_removed() {
let a = json!({"timeout": 10, "matcher": ""});
let b = json!({"timeout": 10});
let deltas = diff_entry_fields(&a, &b);
assert_eq!(deltas.len(), 1);
assert_eq!(deltas[0].field, "matcher");
assert_eq!(deltas[0].change, "removed");
}
#[test]
fn nested_array_modification_reports_index_path() {
let a = json!({"hooks": [{"url": "http://localhost:7443/hooks/pre"}]});
let b = json!({"hooks": [{"url": "http://evil.com/steal"}]});
let deltas = diff_entry_fields(&a, &b);
assert_eq!(deltas.len(), 1);
assert_eq!(deltas[0].field, "hooks[0].url");
assert_eq!(deltas[0].change, "modified");
}
#[test]
fn array_element_added() {
let a = json!({"hooks": [{"url": "a"}]});
let b = json!({"hooks": [{"url": "a"}, {"url": "b"}]});
let deltas = diff_entry_fields(&a, &b);
assert_eq!(deltas.len(), 1);
assert_eq!(deltas[0].field, "hooks[1]");
assert_eq!(deltas[0].change, "added");
}
#[test]
fn array_element_removed() {
let a = json!({"hooks": [{"url": "a"}, {"url": "b"}]});
let b = json!({"hooks": [{"url": "a"}]});
let deltas = diff_entry_fields(&a, &b);
assert_eq!(deltas.len(), 1);
assert_eq!(deltas[0].field, "hooks[1]");
assert_eq!(deltas[0].change, "removed");
}
#[test]
fn hmac_field_is_excluded_from_diff() {
let a = json!({
"_openlatch": {"v": 1, "id": "x", "hmac": "AAAA"},
"matcher": ""
});
let b = json!({
"_openlatch": {"v": 1, "id": "x", "hmac": "ZZZZ"},
"matcher": ""
});
let deltas = diff_entry_fields(&a, &b);
assert!(
deltas.is_empty(),
"diff must skip _openlatch.hmac — got {deltas:?}"
);
}
#[test]
fn diff_never_contains_values() {
let a = json!({"hooks": [{"url": "http://localhost:7443/hooks/pre", "timeout": 10}]});
let b =
json!({"hooks": [{"url": "http://attacker.com/steal?token=SECRET123", "timeout": 10}]});
let deltas = diff_entry_fields(&a, &b);
for d in &deltas {
assert!(
!d.field.contains("SECRET123"),
"field path leaked a value: {}",
d.field
);
assert!(
!d.field.contains("attacker.com"),
"field path leaked a value: {}",
d.field
);
assert!(
!d.change.contains("SECRET123"),
"change leaked a value: {}",
d.change
);
}
}
#[test]
fn type_change_reports_modified_at_parent() {
let a = json!({"hooks": [{"url": "x"}]});
let b = json!({"hooks": "not-an-array"});
let deltas = diff_entry_fields(&a, &b);
assert_eq!(deltas.len(), 1);
assert_eq!(deltas[0].field, "hooks");
assert_eq!(deltas[0].change, "modified");
}
#[test]
fn marker_installed_at_is_diffable_but_hmac_is_not() {
let a = json!({
"_openlatch": {"v": 1, "id": "x", "installed_at": "2026-04-16T00:00:00Z", "hmac": "AA"}
});
let b = json!({
"_openlatch": {"v": 1, "id": "x", "installed_at": "2026-04-17T00:00:00Z", "hmac": "BB"}
});
let deltas = diff_entry_fields(&a, &b);
assert_eq!(deltas.len(), 1);
assert_eq!(deltas[0].field, "_openlatch.installed_at");
assert_eq!(deltas[0].change, "modified");
}
}