use anyhow::Result;
use serde_json::{json, Value};
fn get_str<'a>(v: &'a Value, k: &str) -> &'a str {
v.get(k).and_then(|x| x.as_str()).unwrap_or("")
}
fn get_u64(v: &Value, k: &str) -> u64 {
v.get(k).and_then(|x| x.as_u64()).unwrap_or(0)
}
pub fn run(file: &str) -> Result<Value> {
let entries = crate::journal::read_entries(file)?;
if entries.is_empty() {
return Ok(json!({
"command": "verify",
"status": "no_journal",
"verified": Value::Null,
}));
}
let current = crate::hash::sha256_file(file)?;
let head = entries.last().expect("non-empty");
let head_result = get_str(head, "result_hash");
let head_match = current == head_result;
let mut breaks = Vec::new();
for i in 1..entries.len() {
let cur = &entries[i];
let prev = &entries[i - 1];
let base = get_str(cur, "base_hash");
let prev_result = get_str(prev, "result_hash");
if base != prev_result || get_u64(cur, "rev") <= get_u64(prev, "rev") {
breaks.push(json!({
"at_rev": cur.get("rev"),
"expected_base": prev_result,
"found_base": base,
}));
}
}
let intact = breaks.is_empty();
let verified = head_match && intact;
Ok(json!({
"command": "verify",
"verified": verified,
"head": { "expected": head_result, "actual": current, "match": head_match },
"chain": { "intact": intact, "breaks": breaks },
"receipts_checked": entries.len(),
}))
}