Files
the-nexus/bin/generate_provenance.py
Alexander Whitestone 6f1264f6c6
Some checks failed
CI / test (pull_request) Failing after 9s
CI / validate (pull_request) Failing after 12s
Review Approval Gate / verify-review (pull_request) Failing after 4s
WIP: Browser smoke tests (issue #686)
2026-04-10 21:17:44 -04:00

132 lines
3.8 KiB
Python
Executable File

#!/usr/bin/env python3
"""
Generate a provenance manifest for the Nexus browser surface.
Hashes all frontend files so smoke tests can verify the app comes
from a clean Timmy_Foundation/the-nexus checkout, not stale sources.
Usage:
python bin/generate_provenance.py # writes provenance.json
python bin/generate_provenance.py --check # verify existing manifest matches
"""
import hashlib
import json
import subprocess
import sys
import os
from datetime import datetime, timezone
from pathlib import Path
# Files that constitute the browser-facing contract
CONTRACT_FILES = [
"index.html",
"app.js",
"style.css",
"gofai_worker.js",
"server.py",
"portals.json",
"vision.json",
"manifest.json",
]
# Component files imported by app.js
COMPONENT_FILES = [
"nexus/components/spatial-memory.js",
"nexus/components/session-rooms.js",
"nexus/components/timeline-scrubber.js",
"nexus/components/memory-particles.js",
]
ALL_FILES = CONTRACT_FILES + COMPONENT_FILES
def sha256_file(path: Path) -> str:
h = hashlib.sha256()
h.update(path.read_bytes())
return h.hexdigest()
def get_git_info(repo_root: Path) -> dict:
"""Capture git state for provenance."""
def git(*args):
try:
r = subprocess.run(
["git", *args],
cwd=repo_root,
capture_output=True, text=True, timeout=10,
)
return r.stdout.strip() if r.returncode == 0 else None
except Exception:
return None
return {
"commit": git("rev-parse", "HEAD"),
"branch": git("rev-parse", "--abbrev-ref", "HEAD"),
"remote": git("remote", "get-url", "origin"),
"dirty": git("status", "--porcelain") != "",
}
def generate_manifest(repo_root: Path) -> dict:
files = {}
missing = []
for rel in ALL_FILES:
p = repo_root / rel
if p.exists():
files[rel] = {
"sha256": sha256_file(p),
"size": p.stat().st_size,
}
else:
missing.append(rel)
return {
"generated_at": datetime.now(timezone.utc).isoformat(),
"repo": "Timmy_Foundation/the-nexus",
"git": get_git_info(repo_root),
"files": files,
"missing": missing,
"file_count": len(files),
}
def check_manifest(repo_root: Path, existing: dict) -> tuple[bool, list[str]]:
"""Check if current files match the stored manifest. Returns (ok, mismatches)."""
mismatches = []
for rel, expected in existing.get("files", {}).items():
p = repo_root / rel
if not p.exists():
mismatches.append(f"MISSING: {rel}")
elif sha256_file(p) != expected["sha256"]:
mismatches.append(f"CHANGED: {rel}")
return (len(mismatches) == 0, mismatches)
def main():
repo_root = Path(__file__).resolve().parent.parent
manifest_path = repo_root / "provenance.json"
if "--check" in sys.argv:
if not manifest_path.exists():
print("FAIL: provenance.json does not exist")
sys.exit(1)
existing = json.loads(manifest_path.read_text())
ok, mismatches = check_manifest(repo_root, existing)
if ok:
print(f"OK: All {len(existing['files'])} files match provenance manifest")
sys.exit(0)
else:
print(f"FAIL: {len(mismatches)} file(s) differ:")
for m in mismatches:
print(f" {m}")
sys.exit(1)
manifest = generate_manifest(repo_root)
manifest_path.write_text(json.dumps(manifest, indent=2) + "\n")
print(f"Wrote provenance.json: {manifest['file_count']} files hashed")
if manifest["missing"]:
print(f" Missing (not yet created): {', '.join(manifest['missing'])}")
if __name__ == "__main__":
main()