Files
git.stella-ops.org/scripts/determinism/compare-platform-hashes.py
StellaOps Bot 907783f625 Add property-based tests for SBOM/VEX document ordering and Unicode normalization determinism
- Implement `SbomVexOrderingDeterminismProperties` for testing component list and vulnerability metadata hash consistency.
- Create `UnicodeNormalizationDeterminismProperties` to validate NFC normalization and Unicode string handling.
- Add project file for `StellaOps.Testing.Determinism.Properties` with necessary dependencies.
- Introduce CI/CD template validation tests including YAML syntax checks and documentation content verification.
- Create validation script for CI/CD templates ensuring all required files and structures are present.
2025-12-26 15:17:58 +02:00

161 lines
4.8 KiB
Python

#!/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"<details><summary>Show {len(matched)} matched hashes</summary>",
"",
])
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(["", "</details>", ""])
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()