#!/usr/bin/env python3 """Standalone verifier for the CAIN-42 AGI live-run evidence chain. Python standard library only; imports nothing from CAIN. Usage: python3 verify_chain.py evidence_chain.json report.json Checks: (1) first run: sha256sum -c report.sha256; (2) every record's record_hash is the sha256 of its canonical JSON fields; (3) each prev_hash links to the previous record (genesis = 64 zeros); (4) each finalization_hash covers the recorded outcome; (5) the decisions in report.json are exactly the records in the chain.""" import hashlib, json, sys def digest(obj): return hashlib.sha256(json.dumps(obj, sort_keys=True, separators=(",", ":"), default=str).encode()).hexdigest() db, report = sys.argv[1], json.load(open(sys.argv[2])) rows = sorted(json.load(open(db))["rows"], key=lambda r: r["sequence_number"]) problems, prev = [], "0" * 64 for r in rows: if r["prev_hash"] != prev: problems.append(f"seq {r['sequence_number']}: broken link") fields = {k: r[k] for k in ("record_id", "tenant", "sequence_number", "agent_id", "proposal_id", "node_id", "intent", "action_verb", "target_resource", "decision_class", "pcd_decision_id", "prev_hash", "created_at")} fields.update(capability_check=json.loads(r["capability_check_json"]), policy_risk=json.loads(r["policy_risk_json"]), trust=json.loads(r["trust_json"])) if digest(fields) != r["record_hash"]: problems.append(f"seq {r['sequence_number']}: record_hash mismatch") if r["finalization_hash"]: load = lambda c: json.loads(r[c]) if r[c] else None fin = {"record_id": r["record_id"], "enforcement_result": load("enforcement_result_json"), "execution_result": load("execution_result_json"), "postcondition": load("postcondition_json")} if digest(fin) != r["finalization_hash"]: problems.append(f"seq {r['sequence_number']}: finalization_hash mismatch") prev = r["record_hash"] reported = [] for a in report["attempts"]: reported.append(a["decision"]["record_id"]) if a.get("decision_after_approval"): reported.append(a["decision_after_approval"]["record_id"]) if sorted(x for x in reported if x) != sorted(r["record_id"] for r in rows): problems.append("report.json decisions do not match the chain records") print(json.dumps({"records": len(rows), "chain_intact": not problems, "problems": problems}, indent=2)) sys.exit(1 if problems else 0)