#!/usr/bin/env python3 """Validate repository navigation and original evidence, create a NEW receipt.""" from datetime import datetime, timezone import gzip import hashlib import json from pathlib import Path import re import subprocess import sys root = Path.cwd().resolve() def sha(p): h = hashlib.sha256() with p.open("rb") as f: for b in iter(lambda:f.read(4*1024*1024), b""): h.update(b) return h.hexdigest() errors = [] cat = root / "registry/catalog-20260929" copies = json.loads((cat / "evidence-index.json").read_text()) for r in copies: if sha(root / r["source_path"]) != r["sha256"] or sha(cat / r["copy_path"]) != r["sha256"]: errors.append("evidence checksum: " + r["source_path"]) links = 0 # New navigation only; old historical reports keep their original links unchanged. pages = [root/"README.md", root/"NOTICE.md", *sorted((root/"docs").rglob("*.md")), root/"references/README.md", root/"references/evidence-map.md"] for p in pages: for link in re.findall(r"\]\(([^)]+)\)", p.read_text(encoding="utf-8")): if re.match(r"[a-z]+://|#", link): continue links += 1 if not (p.parent / link.split("#",1)[0]).exists(): errors.append("missing link: " + str(p.relative_to(root)) + " -> " + link) papers = json.loads((root/"references/papers.json").read_text())["papers"] for p in papers: if p["status"] != "VERIFIED-METADATA" or sha(root/"references"/p["metadata_file"]) != p["metadata_sha256"]: errors.append("bibliography metadata: " + p["doi"]) snapshot = root/"registry/snapshots/bootstrap-20260929/files.jsonl.gz" with gzip.open(snapshot, "rt", encoding="utf-8") as f: originals = {r["path"]:r for r in map(json.loads, f)} tracked = subprocess.check_output(["git", "ls-files", "-z"]).decode().split("\0") models = 0 for path in filter(None, tracked): if path.startswith("models/"): models += 1 old = originals.get(path) blob = subprocess.check_output(["git", "show", ":"+path]) if old is None or hashlib.sha256(blob).hexdigest() != old.get("sha256"): errors.append("staged original model differs: " + path) verification = json.loads((root/"registry/bootstrap-verification.json").read_text()) if not verification["pass"]: errors.append("full scientific snapshot verification failed") guard = subprocess.run([sys.executable, "-B", "tools/repo_guard.py"], capture_output=True, text=True) if guard.returncode: errors.append("staged content guard failed") tests = subprocess.run([sys.executable, "-B", "tools/test_repository_audit.py"], capture_output=True, text=True) if tests.returncode: errors.append("tool unit tests failed") receipt = {"pass":not errors, "errors":errors, "checked_utc":datetime.now(timezone.utc).isoformat(), "evidence_copies_verified":len(copies), "original_model_blobs_verified":models, "navigation_local_links_verified":links, "papers_metadata_verified":len(papers), "scientific_files_verified":verification["checked"], "staged_guard":json.loads(guard.stdout), "tool_tests_exit_code":tests.returncode, "scientific_regressions_reexecuted":False, "data_deleted_moved_or_rewritten":False, "permissions_change":{"scope":"directory ownership only, nonrecursive", "paths":["/data/digital-drosophila","/data/digital-drosophila/docs","/data/digital-drosophila/tools"], "before":"root:root", "after":"qishizhe:qishizhe"}, "large_data_pushed":False, "offsite_full_data_backup_created":False} out = root/"registry/repository-validation.json" with out.open("x", encoding="utf-8") as f: json.dump(receipt, f, ensure_ascii=False, indent=2) f.write("\n") print(json.dumps(receipt, ensure_ascii=False, indent=2)) sys.exit(int(bool(errors)))