#!/usr/bin/env python3 """Build a reproducibility manifest for every application package (review finding F17). A package folder that contains a PDF looks finished. It is not necessarily reproducible: `output/NATO_AI_ENGINEER/` ships no `resume.cls` at all and only builds on a machine where one happens to be installed elsewhere, and its submitted PDF differs in content from the e2e source's PDF. The existing PDF hid both problems. Records source/class content hashes. Optional PDF snapshots detect changed local artifacts; missing PDFs are allowed because generated PDFs are deliberately not tracked in Git. Filename association is not proof that a PDF was built from the current source. python resume_builder/helpers/package_manifest.py # write the manifest python resume_builder/helpers/package_manifest.py --check # fail if it is stale """ from __future__ import annotations import argparse import hashlib import json import re import sys from pathlib import Path ROOT = Path(__file__).resolve().parents[2] OUTPUT = ROOT / "output" MANIFEST = ROOT / "resume_builder" / "canonical" / "package_manifest.json" REGISTER = ROOT / "resume_builder" / "canonical" / "historical_outputs.json" TODAY_MACRO = re.compile(r"\\today") # Ship with MiKTeX/TeX Live, so their absence from a package folder is not a missing file. # Only project-local classes (resume.cls, cv.cls) have to travel with the package. CTAN_CLASSES = {"article", "report", "book", "letter", "moderncv", "scrartcl", "scrlttr2", "memoir", "extarticle", "europasscv", "europecv", "altacv"} def digest(path: Path) -> str: # Git/Windows may change line endings without changing the TeX source. data = path.read_text(encoding="utf-8").encode("utf-8") if path.suffix in {".tex", ".cls"} else path.read_bytes() return hashlib.sha256(data).hexdigest()[:16] def describe(folder: Path, record_pdfs: bool = False) -> dict: sources = {p.name: digest(p) for p in sorted(folder.glob("*.tex"))} classes = {p.name: digest(p) for p in folder.glob("*.cls")} needs = set() uses_today = [] for name in sources: text = (folder / name).read_text(encoding="utf-8", errors="replace") text = re.sub(r"(? dict: register = json.loads(REGISTER.read_text(encoding="utf-8")) classified = set() for key in ("unsafe_do_not_reuse", "historical_revalidate", "paused_no_package"): for item in register.get(key, []): classified.add(item["folder"] if isinstance(item, dict) else item) packages = {} for folder in sorted(OUTPUT.iterdir()): if not folder.is_dir() or not any(folder.glob("*.tex")): continue # Only APPLICATION packages. A folder holding a report or analysis document is not a # submission and has nothing to classify for reuse. if not any(folder.glob("*resume*.tex")) and not any(folder.glob("*cover*letter*.tex")): continue entry = describe(folder, record_pdfs) entry["reuse_classification"] = ( "recorded" if f"output/{folder.name}" in classified else "UNCLASSIFIED") if f"output/{folder.name}" not in classified: entry["problems"].append( "not classified in historical_outputs.json. Default until someone classifies " "it: treat as historical_revalidate - never reuse a bullet from it without " "re-checking the claim against claims.json first.") packages[folder.name] = entry return { "schema_version": 2, "generated_note": ( "Generated by package_manifest.py (review finding F17). A PDF in a folder is not " "evidence that the package is reproducible. Source and class hashes detect edits; " "optional pdf_artifacts are observations, not verified source-to-PDF mappings. " "Absent PDFs are allowed; present PDFs must match their recorded hash. " "Nothing here authorises rewriting a " "submitted document."), "packages": packages, } def check_manifest(recorded: dict, current: dict, output: Path = OUTPUT) -> list[str]: # Compare the portable source inventory separately from optional local build products. baseline = json.loads(json.dumps(recorded)) errors = [] for name, entry in baseline.get("packages", {}).items(): for filename, expected in entry.pop("pdf_artifacts", {}).items(): path = output / name / filename if path.exists() and digest(path) != expected: errors.append(f"changed PDF artifact: {name}/{filename}; inspect before recording a new baseline") if baseline != current: errors.append("source manifest is stale; inspect changes, then run package_manifest.py") return errors def main() -> int: parser = argparse.ArgumentParser() parser.add_argument("--check", action="store_true", help="fail if the manifest is stale") parser.add_argument("--record-pdfs", action="store_true", help="explicitly snapshot current local PDF hashes (not proof of source association)") args = parser.parse_args() if args.check and args.record_pdfs: parser.error("--check cannot refresh PDF baselines") recorded = json.loads(MANIFEST.read_text(encoding="utf-8")) if MANIFEST.exists() else {} manifest = build(record_pdfs=args.record_pdfs) if args.check: errors = check_manifest(recorded, manifest) if errors: for error in errors: print("ERROR: " + error) return 1 print("Package manifest is current.") return 0 if not args.record_pdfs: for name, entry in manifest["packages"].items(): old = recorded.get("packages", {}).get(name, {}) if "pdf_artifacts" in old: entry["pdf_artifacts"] = old["pdf_artifacts"] MANIFEST.write_text(json.dumps(manifest, indent=2, ensure_ascii=False) + "\n", encoding="utf-8") flagged = {name: entry for name, entry in manifest["packages"].items() if entry["problems"]} print(f"{len(manifest['packages'])} packages described, {len(flagged)} with findings\n") for name, entry in flagged.items(): print(f" {name}") for problem in entry["problems"]: print(f" - {problem[:150]}") return 0 if __name__ == "__main__": sys.exit(main())