coulomb-social/scripts/verify-bubble-visual-preservation.py
tegwick 0c714d3c56 Preserve captured Bubble Visuals in private review trees
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a18e2d-f513-7742-b8e7-12af0b744ff3
2026-10-31 14:54:06 +01:00

115 lines
4.1 KiB
Python

#!/usr/bin/env python3
"""Independently verify Visual-only frontmatter changes against the original private bundle."""
import argparse
import hashlib
import json
import os
import re
from pathlib import Path
def read(root, name):
path = root / name
if (
Path(name).is_absolute()
or ".." in Path(name).parts
or any(p.is_symlink() for p in (path, *path.parents))
):
raise ValueError("unsafe source path")
info = path.stat()
if (
not path.is_file()
or info.st_uid != os.getuid()
or info.st_mode & 0o077
or info.st_nlink != 1
or info.st_size > 16 * 1024 * 1024
):
raise ValueError("private bounded source required")
return path.read_bytes()
def verify(source, out):
if not __debug__:
raise ValueError("verification assertions required")
old = json.loads(read(source, "reconciliation.json"))
new = json.loads(read(out, "reconciliation.json"))
def sha(raw):
return hashlib.sha256(raw).hexdigest()
assert new["original_report_sha256"] == sha(read(source, "reconciliation.json"))
assert read(out, "source-capture/reconciliation-before-visuals.json") == read(
source, "reconciliation.json"
)
for name, digest in new["file_sha256"].items():
assert sha(read(out, name)) == digest
changed = 0
for name, digest in old["file_sha256"].items():
original = read(source, name)
current = read(out, name)
assert sha(original) == digest
if current != original:
assert "/pages/" in name and name.endswith(".md")
before, body = original.split(b"\n---\n", 1)
after, after_body = current.split(b"\n---\n", 1)
assert body == after_body
assert (
len(re.findall(rb'^visual: "assets/[a-f0-9]{64}\.(?:png|jpg|webp)"$', after, re.M))
== 1
)
if name.endswith("/index.md"):
restored = re.sub(
rb'\nvisual: "assets/[a-f0-9]{64}\.(?:png|jpg|webp)"$', b"", after
)
else:
restored = re.sub(
rb'^visual: "assets/[a-f0-9]{64}\.(?:png|jpg|webp)"$',
b'visual: ""',
after,
flags=re.M,
)
assert restored == before
changed += 1
for axis in ["unassigned_article_ids", "source_sha256"]:
assert new[axis] == old[axis]
assert len(old["spaces"]) == len(new["spaces"])
for a, b in zip(old["spaces"], new["spaces"], strict=True):
assert a["blockers"] == b["blockers"] and not b["import_ready"]
for key in a:
if key != "articles":
assert a[key] == b[key], key
for x, y in zip(a["articles"], b["articles"], strict=True):
assert {k: v for k, v in x.items() if k != "file_sha256"} == {
k: v for k, v in y.items() if k != "file_sha256"
}
for p in out.rglob("*"):
assert p.stat().st_mode & 0o077 == 0
return {
"schema": "coulomb.visual-preservation-proof.v1",
"verified_output_files": len(new["file_sha256"]),
"verified_original_files": len(old["file_sha256"]),
"frontmatter_only_changes": changed,
"all_source_bodies_and_dates_preserved": True,
"spaces_preserved": len(new["spaces"]),
"original_blockers_preserved": True,
"unassigned_records_preserved": True,
"private_modes_verified": True,
"private_bundle_sha256": sha(read(out, "reconciliation.json")),
"production_imported": False,
}
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("original", type=Path)
parser.add_argument("prepared", type=Path)
args = parser.parse_args()
try:
print(json.dumps(verify(args.original, args.prepared)))
except Exception:
print("Private visual preservation verification failed; no acceptance inferred.")
return 1
return 0
if __name__ == "__main__":
raise SystemExit(main())