"""Publish screenshot originals as public copies (Safari captures cropped to remove the browser chrome/bookmarks bar; Terminal captures unchanged), write evidence/manifests/screenshots.csv and the LaTeX gallery appendix.""" import os, sys, csv, json, subprocess, hashlib sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src")); sys.path.insert(0, os.path.dirname(__file__)) from common import REPO from texutil import tex as T from pgar.state import StateStore from pgar.capture import publish def sha(p): return hashlib.sha256(open(p, "rb").read()).hexdigest() def main(): store = StateStore(os.path.join(REPO, "runs", "state.sqlite")) rows = store.query("SELECT * FROM screenshots ORDER BY screenshot_id") pub_dir = os.path.join(REPO, "evidence", "public_screenshots"); os.makedirs(pub_dir, exist_ok=True); os.makedirs(os.path.join(REPO, "evidence", "manifests"), exist_ok=True) tex = [r"\section{Visual evidence gallery}", r"\label{app:gallery}", "Every image below is an unmodified or cropped capture of a real surface, recorded in the screenshot manifest (\\texttt{evidence/manifests/screenshots.csv}) with its capture mode, tool, UTC time, SHA-256 of the original and of the public copy, and the run and event it documents. Capture failures are listed with their reason and have no image. Hashes attest file integrity relative to the manifest; they do not independently prove the moment of capture, which is triangulated with the session and event timestamps in the state store."] man = [] for r in rows: failed = "CAPTURE FAILURE" in (r.get("caption") or "") pub = None if not failed and r.get("original_path") and os.path.exists(os.path.join(REPO, r["original_path"])): name = os.path.basename(r["original_path"]); pub = os.path.join(pub_dir, name) if r["capture_mode"] in ("dashboard_live", "viewer_postrun") and "Safari" in (r.get("source_surface") or ""): # crop the top 150 logical px (browser chrome and bookmarks bar) → 2x retina = 300 px import matplotlib; matplotlib.use("Agg"); import matplotlib.image as mpimg img = mpimg.imread(os.path.join(REPO, r["original_path"])); mpimg.imsave(pub, img[300:, :, :]) desc = f"cropped: top 300 px of {img.shape[0]} (browser toolbar and bookmarks bar) removed; page content unmodified" else: subprocess.run(["cp", os.path.join(REPO, r["original_path"]), pub], check=True); desc = "copied without modification" with store.tx() as c: c.execute("UPDATE screenshots SET redacted_public_path=?, redacted_sha256=?, redaction_description=? WHERE screenshot_id=?", (os.path.relpath(pub, REPO), sha(pub), desc, r["screenshot_id"])) r2 = store.query("SELECT * FROM screenshots WHERE screenshot_id=?", (r["screenshot_id"],))[0]; man.append(r2) cap = T(r2.get("caption") or "") meta = f"\\textbf{{{r2['screenshot_id']}}} --- mode \\texttt{{{T(r2['capture_mode'])}}}; {r2['capture_timestamp_utc'][:19]} UTC; run {T(r2.get('run_id') or '--')}; tool {T(r2.get('capture_tool'))}; original SHA-256 {r2['original_sha256'][:16]}\\ldots; public SHA-256 {(r2.get('redacted_sha256') or '--')[:16]}\\ldots; {T(r2.get('redaction_description') or '')}. {cap}" if r2.get("redacted_public_path") and not failed: tex += [r"\begin{figure}[htbp]\centering", f"\\includegraphics[width=0.92\\textwidth]{{../{r2['redacted_public_path']}}}", f"\\caption{{{meta}}}", r"\end{figure}", r"\clearpage"] else: tex += [f"\\paragraph{{{r2['screenshot_id']} (no image)}} {meta}", ""] with open(os.path.join(REPO, "evidence", "manifests", "screenshots.csv"), "w", newline="") as fh: w = csv.DictWriter(fh, fieldnames=list(man[0].keys())); w.writeheader(); w.writerows(man) open(os.path.join(REPO, "paper", "appendices", "gallery.tex"), "w").write("\n".join(tex) + "\n"); print(len(man), "screenshots in manifest") if __name__ == "__main__": main()