SimpleChatbot / scripts /verify_clean_build_equivalence.py
Amin
Deploy: HermesFace finalized project to HF Space
2e658e7
Raw
History Blame Contribute Delete
5.28 kB
#!/usr/bin/env python3
"""Verify deterministic source-build equivalence without requiring Docker.
Two isolated copies are created from the same source tree, compiled, subjected
to a focused startup/build smoke suite, and compared using normalized manifests.
Container-image equivalence remains a separate Desktop/CI check.
"""
from __future__ import annotations
import argparse
import hashlib
import json
import os
import shutil
import subprocess
import sys
import tempfile
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
SKIP_NAMES = {
".git", ".pytest_cache", "__pycache__", ".coverage", "coverage.json", "coverage.xml",
"node_modules", ".venv", "venv", "dist", "build", "evidence_package",
}
SMOKE_TESTS = (
"hermes_overlay/tests/test_build_reproducibility.py",
"hermes_overlay/tests/test_config_validation.py",
"hermes_overlay/tests/test_upstream_integration_preflight.py",
"hermes_overlay/tests/test_repository_build_metadata.py",
)
def _ignore(_directory: str, names: list[str]) -> set[str]:
return {name for name in names if name in SKIP_NAMES or name.endswith((".pyc", ".pyo"))}
def _sha256_lines(lines: list[str]) -> str:
return hashlib.sha256(("\n".join(lines) + "\n").encode("utf-8")).hexdigest()
def _manifest(root: Path) -> list[str]:
lines: list[str] = []
for path in sorted(root.rglob("*")):
if not path.is_file() or any(part in SKIP_NAMES for part in path.relative_to(root).parts):
continue
if path.suffix in {".pyc", ".pyo"}:
continue
digest = hashlib.sha256(path.read_bytes()).hexdigest()
lines.append(f"{digest} {path.relative_to(root).as_posix()}")
return lines
def _run(command: list[str], cwd: Path) -> dict[str, object]:
env = os.environ.copy()
env["PYTHONPATH"] = str(cwd / "hermes_overlay")
env["TRADING_MODE"] = "paper"
env["HERMES_EXECUTION_ENABLED"] = "false"
env["HERMES_NONPAPER_EXECUTION_ENABLED"] = "false"
result = subprocess.run(command, cwd=cwd, env=env, text=True, capture_output=True)
return {
"command": command,
"returncode": result.returncode,
"stdout": result.stdout[-4000:],
"stderr": result.stderr[-4000:],
}
def verify(root: Path = ROOT, *, run_smoke: bool = True) -> dict[str, object]:
root = root.resolve()
with tempfile.TemporaryDirectory(prefix="hermes-clean-build-") as tmp:
base = Path(tmp)
copies = [base / "build-a", base / "build-b"]
results: list[dict[str, object]] = []
manifests: list[list[str]] = []
for copy in copies:
shutil.copytree(root, copy, ignore=_ignore)
commands = [
[sys.executable, "-m", "compileall", "-q", "app.py", "hermes_overlay", "scripts"],
]
if run_smoke:
commands.append([sys.executable, "-m", "pytest", "-q", *SMOKE_TESTS])
command_results = [_run(command, copy) for command in commands]
failed = [item for item in command_results if item["returncode"] != 0]
if failed:
raise RuntimeError(f"clean build smoke failed in {copy.name}: {failed[0]}")
manifests.append(_manifest(copy))
results.append({"copy": copy.name, "commands": command_results})
equivalent = manifests[0] == manifests[1]
if not equivalent:
only_a = sorted(set(manifests[0]) - set(manifests[1]))[:20]
only_b = sorted(set(manifests[1]) - set(manifests[0]))[:20]
raise RuntimeError(f"clean build manifests diverged: only_a={only_a}, only_b={only_b}")
return {
"schemaVersion": 1,
"equivalent": True,
"manifestEntries": len(manifests[0]),
"manifestSha256": _sha256_lines(manifests[0]),
"python": sys.version.split()[0],
"smokeTests": list(SMOKE_TESTS) if run_smoke else [],
"builds": results,
"allowedNondeterminism": [
"temporary directory paths",
"pytest timing output",
"Python bytecode/cache files excluded from normalized manifest",
"container layer timestamps verified separately in Docker-capable CI",
],
}
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--root", default=str(ROOT))
parser.add_argument("--output", default="evidence_package/clean_build_equivalence.json")
parser.add_argument("--no-smoke", action="store_true")
args = parser.parse_args(argv)
try:
result = verify(Path(args.root), run_smoke=not args.no_smoke)
except (OSError, RuntimeError, subprocess.SubprocessError) as exc:
print(f"ERROR: {exc}", file=sys.stderr)
return 1
output = Path(args.output)
if not output.is_absolute():
output = Path(args.root) / output
output.parent.mkdir(parents=True, exist_ok=True)
output.write_text(json.dumps(result, indent=2, sort_keys=True) + "\n", encoding="utf-8")
print(f"clean_build_equivalence=PASS entries={result['manifestEntries']} sha256={result['manifestSha256']}")
return 0
if __name__ == "__main__":
raise SystemExit(main())