#!/usr/bin/env python3 """CAIN-42 generic evidence-bundle verifier (ZERO CAIN imports). Ships inside every published bundle as verify_e.py.txt. It: 1. recomputes SHA-256 of every file listed in manifest.json and requires an exact match (INTACT), and 2. imports the bundle's own clean-room verifier (cleanroom/verify_*.py, which imports no CAIN code) and runs it on the bundle's fixture.json, requiring the recorded result. python3 verify_e27.py BUNDLE_DIR Exit 0 = INTACT, 1 = broken/failed, 2 = usage/IO. It reads published data only; it never contacts the CAIN service. """ from __future__ import annotations import hashlib import importlib.util import json import os import sys from pathlib import Path def sha_file(p: Path) -> str: h = hashlib.sha256() with open(p, "rb") as fh: for c in iter(lambda: fh.read(65536), b""): h.update(c) return h.hexdigest() def load_module(path: Path, name: str): spec = importlib.util.spec_from_file_location(name, str(path)) mod = importlib.util.module_from_spec(spec) spec.loader.exec_module(mod) return mod def main(argv) -> int: if len(argv) < 2: print("usage: verify_eNN.py BUNDLE_DIR"); return 2 d = Path(argv[1]) manifest = d / "manifest.json" if not manifest.exists(): print(json.dumps({"result": "BROKEN", "reason": "NO_MANIFEST"})); return 1 man = json.loads(manifest.read_text()) problems, checks = [], 0 for rel, want in (man.get("files") or {}).items(): p = d / rel checks += 1 if not p.exists(): problems.append("MISSING:" + rel); continue if sha_file(p) != want: problems.append("HASH_MISMATCH:" + rel) # run the shipped clean-room semantic verifier on the shipped fixture clean = next((d / "cleanroom").glob("verify_*.py"), None) fixture = d / "fixture.json" semantic = {"status": "NOT_RUN"} if clean and fixture.exists(): mod = load_module(clean, "bundle_cleanroom") try: semantic = mod.verify(json.loads(fixture.read_text())) except Exception as e: # noqa: BLE001 problems.append("CLEANROOM_EXCEPTION:" + type(e).__name__) checks += 1 marker = str(semantic.get("status") or semantic.get("result") or "").upper() if marker not in ("VALID", "INTACT", "VERIFIED", "ALLOW"): problems.append("CLEANROOM_NOT_VALID:" + marker) out = {"result": "INTACT" if not problems else "BROKEN", "passed": checks if not problems else checks - len(problems), "checks": checks, "bundle": d.name, "cleanroom": semantic, "problems": sorted(set(problems))} print(json.dumps(out, indent=2)) return 0 if not problems else 1 if __name__ == "__main__": sys.exit(main(sys.argv))