80 lines
3.8 KiB
Python
80 lines
3.8 KiB
Python
#!/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)))
|