#!/usr/bin/env python3 """Verify every immutable file declared by the v1 release manifest.""" from __future__ import annotations import hashlib import json from pathlib import Path import sys RELEASE_ROOT = Path(__file__).resolve().parent MANIFEST_PATH = RELEASE_ROOT / "MANIFEST.json" def sha256(path: Path) -> str: digest = hashlib.sha256() with path.open("rb") as handle: for chunk in iter(lambda: handle.read(1024 * 1024), b""): digest.update(chunk) return digest.hexdigest() def main() -> int: manifest = json.loads(MANIFEST_PATH.read_text(encoding="utf-8")) failures: list[str] = [] checked: list[dict[str, object]] = [] declared_paths = { Path(record["path"]).as_posix() for record in manifest["files"] } actual_paths = { path.relative_to(RELEASE_ROOT).as_posix() for path in RELEASE_ROOT.rglob("*") if ( path.is_file() and path != MANIFEST_PATH and "__pycache__" not in path.relative_to(RELEASE_ROOT).parts and path.suffix != ".pyc" ) } undeclared = sorted(actual_paths - declared_paths) absent = sorted(declared_paths - actual_paths) if undeclared: failures.extend(f"undeclared package file: {path}" for path in undeclared) if absent: failures.extend(f"declared package file is absent: {path}" for path in absent) for record in manifest["files"]: relative = Path(record["path"]) path = (RELEASE_ROOT / relative).resolve() if not path.is_relative_to(RELEASE_ROOT): failures.append(f"path escapes release root: {relative}") continue if not path.is_file(): failures.append(f"missing: {relative}") continue if path.is_symlink(): failures.append(f"release file must not be a symlink: {relative}") continue actual_size = path.stat().st_size actual_hash = sha256(path) if actual_size != record["size"]: failures.append( f"size mismatch {relative}: {actual_size} != {record['size']}" ) if actual_hash != record["sha256"]: failures.append( f"SHA-256 mismatch {relative}: {actual_hash} != " f"{record['sha256']}" ) checked.append( { "path": str(relative), "size": actual_size, "sha256": actual_hash, } ) result = { "schema_version": manifest["schema_version"], "status": "pass" if not failures else "fail", "checked_file_count": len(checked), "failures": failures, } print(json.dumps(result, indent=2, sort_keys=True)) return 0 if not failures else 1 if __name__ == "__main__": raise SystemExit(main())