#!/usr/bin/env python3 """ Cross-platform hash comparison for determinism verification. Sprint: SPRINT_20251226_007_BE_determinism_gaps Task: DET-GAP-13 - Cross-platform hash comparison report generation """ import argparse import json import sys from datetime import datetime, timezone from pathlib import Path from typing import Any def load_hashes(path: str) -> dict[str, str]: """Load hash file from path.""" with open(path) as f: data = json.load(f) return data.get("hashes", data) def compare_hashes( linux: dict[str, str], windows: dict[str, str], macos: dict[str, str] ) -> tuple[list[dict], list[str]]: """ Compare hashes across platforms. Returns (divergences, matched_keys). """ all_keys = set(linux.keys()) | set(windows.keys()) | set(macos.keys()) divergences = [] matched = [] for key in sorted(all_keys): linux_hash = linux.get(key, "MISSING") windows_hash = windows.get(key, "MISSING") macos_hash = macos.get(key, "MISSING") if linux_hash == windows_hash == macos_hash: matched.append(key) else: divergences.append({ "key": key, "linux": linux_hash, "windows": windows_hash, "macos": macos_hash }) return divergences, matched def generate_markdown_report( divergences: list[dict], matched: list[str], linux_path: str, windows_path: str, macos_path: str ) -> str: """Generate Markdown report.""" lines = [ f"**Generated:** {datetime.now(timezone.utc).isoformat()}", "", "### Summary", "", f"- ✅ **Matched:** {len(matched)} hashes", f"- {'❌' if divergences else '✅'} **Divergences:** {len(divergences)} hashes", "", ] if divergences: lines.extend([ "### Divergences", "", "| Key | Linux | Windows | macOS |", "|-----|-------|---------|-------|", ]) for d in divergences: linux_short = d["linux"][:16] + "..." if len(d["linux"]) > 16 else d["linux"] windows_short = d["windows"][:16] + "..." if len(d["windows"]) > 16 else d["windows"] macos_short = d["macos"][:16] + "..." if len(d["macos"]) > 16 else d["macos"] lines.append(f"| `{d['key']}` | `{linux_short}` | `{windows_short}` | `{macos_short}` |") lines.append("") lines.extend([ "### Matched Hashes", "", f"
Show {len(matched)} matched hashes", "", ]) for key in matched[:50]: # Limit display lines.append(f"- `{key}`") if len(matched) > 50: lines.append(f"- ... and {len(matched) - 50} more") lines.extend(["", "
", ""]) return "\n".join(lines) def main(): parser = argparse.ArgumentParser(description="Compare determinism hashes across platforms") parser.add_argument("--linux", required=True, help="Path to Linux hashes JSON") parser.add_argument("--windows", required=True, help="Path to Windows hashes JSON") parser.add_argument("--macos", required=True, help="Path to macOS hashes JSON") parser.add_argument("--output", required=True, help="Output JSON report path") parser.add_argument("--markdown", required=True, help="Output Markdown report path") args = parser.parse_args() # Load hashes linux_hashes = load_hashes(args.linux) windows_hashes = load_hashes(args.windows) macos_hashes = load_hashes(args.macos) # Compare divergences, matched = compare_hashes(linux_hashes, windows_hashes, macos_hashes) # Generate reports report = { "timestamp": datetime.now(timezone.utc).isoformat(), "sources": { "linux": args.linux, "windows": args.windows, "macos": args.macos }, "summary": { "matched": len(matched), "divergences": len(divergences), "total": len(matched) + len(divergences) }, "divergences": divergences, "matched": matched } # Write JSON report Path(args.output).parent.mkdir(parents=True, exist_ok=True) with open(args.output, "w") as f: json.dump(report, f, indent=2) # Write Markdown report markdown = generate_markdown_report( divergences, matched, args.linux, args.windows, args.macos ) with open(args.markdown, "w") as f: f.write(markdown) # Print summary print(f"Comparison complete:") print(f" Matched: {len(matched)}") print(f" Divergences: {len(divergences)}") # Exit with error if divergences found if divergences: print("\nERROR: Hash divergences detected!") sys.exit(1) if __name__ == "__main__": main()