#!/usr/bin/env python3 """P11-T07: Produce final technical evidence package (file-based artifacts).""" from __future__ import annotations import datetime import hashlib import os import subprocess import sys import tempfile from pathlib import Path def sha256_file(path: Path) -> str: h = hashlib.sha256() with open(path, "rb") as f: for chunk in iter(lambda: f.read(65536), b""): h.update(chunk) return h.hexdigest() def main() -> int: root = Path(__file__).resolve().parents[1] out_dir = root / "evidence_package" out_dir.mkdir(exist_ok=True) ts = datetime.datetime.now(datetime.timezone.utc).strftime("%Y%m%dT%H%M%SZ") lines = [ f"# Technical Evidence Package", f"Generated (UTC): {ts}", f"Project root: {root}", "", "## Safety posture", "- TRADING_MODE default paper", "- HERMES_EXECUTION_ENABLED default false", "- Agent mutation tools unregistered", "- GAP-001 kill switch closed", "", "## Automated checks (full local suite)", ] env = os.environ.copy() env["PYTHONPATH"] = str(root / "hermes_overlay") # Write test output to a real file rather than a PIPE. Some integration # subprocesses can inherit pipe descriptors and delay EOF even after the # pytest parent exits; a temporary file keeps evidence generation bounded. with tempfile.TemporaryFile(mode="w+t", encoding="utf-8") as test_output: try: r = subprocess.run( [sys.executable, "-m", "pytest", "hermes_overlay/tests", "-vv"], cwd=str(root), env=env, stdout=test_output, stderr=subprocess.STDOUT, text=True, timeout=300, ) returncode = r.returncode except subprocess.TimeoutExpired: returncode = 124 test_output.write("\nERROR: full local suite exceeded 300 seconds\n") test_output.seek(0) captured = test_output.read() lines.append("```") lines.append(captured[-2500:] if captured else "") lines.append(f"exit={returncode}") lines.append("```") lines.append("") # runbook validator v = subprocess.run([sys.executable, "scripts/validate_runbook_consistency.py", "--skip-evidence-hashes"], cwd=str(root), capture_output=True, text=True) lines.append("## Runbook consistency") lines.append("```") lines.append(v.stdout) lines.append("```") lines.append("") lines.append("## Key artifact hashes") for rel in [ "hermes_overlay/trading/futures_execution.py", "hermes_overlay/trading/domain/execution_service.py", "scripts/backup_allowlist.py", "HERMES_V5_CLOUD_DESKTOP_MASTER_RUNBOOK.md", ]: path = root / rel if path.is_file(): lines.append(f"- `{rel}`: `{sha256_file(path)}`") lines.append("") lines.append("## Deferred (require operator / external)") lines.append("- OP-P0-01 credential rotation") lines.append("- OP-P1-01 upstream approval") lines.append("- P11-T05 Testnet lifecycle") lines.append("- P11-T06 independent human audit") lines.append("- P11-T08 program sign-off") lines.append("- S1-T05/T06 strategy Testnet comparison and approval") lines.append("") lines.append("## Release gates") lines.append("All gates A–F remain NOT_REVIEWED. This package does not approve Testnet or Live.") report = out_dir / f"evidence_{ts}.md" report.write_text("\n".join(lines), encoding="utf-8") latest = out_dir / "EVIDENCE_LATEST.md" latest.write_text(report.read_text(encoding="utf-8"), encoding="utf-8") print(f"wrote {report}") return 0 if returncode == 0 else 1 if __name__ == "__main__": sys.exit(main())