#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ collatz_verifier_minimal.py Verifies a Collatz certificate JSON structure. Loads certificate, validates that clauses have required fields. Outputs simple MD: OK or list of errors. Usage: python collatz_verifier_minimal.py --certificat JSON_PATH --output MD_PATH """ from __future__ import annotations import argparse import json from pathlib import Path from typing import Any REQUIRED_CLAUSE_FIELDS = frozenset({"word", "k", "s", "mod", "residue", "a", "B", "N0"}) def load_certificate(path: str) -> dict[str, Any]: """Load and parse JSON certificate.""" p = Path(path) if not p.exists(): raise FileNotFoundError(f"Certificate not found: {path}") text = p.read_text(encoding="utf-8") return json.loads(text) def get_clauses(data: dict[str, Any]) -> list[dict[str, Any]]: """Extract clauses from certificate (support 'closed' or 'clauses'). Returns empty list if not found.""" if "closed" in data: return data["closed"] if "clauses" in data: return data["clauses"] return [] def validate_clause(clause: Any, index: int) -> list[str]: """Validate a single clause. Returns list of error messages.""" errors: list[str] = [] if isinstance(clause, (int, float)): return [] if not isinstance(clause, dict): errors.append(f"Clause {index}: not a dict (got {type(clause).__name__})") return errors for field in REQUIRED_CLAUSE_FIELDS: if field not in clause: errors.append(f"Clause {index}: missing required field '{field}'") if isinstance(clause.get("word"), str): for c in clause["word"]: if c not in ("0", "1"): errors.append(f"Clause {index}: word must be binary string") break return errors def validate_certificate(data: dict[str, Any]) -> list[str]: """Validate certificate structure.""" errors: list[str] = [] clauses = get_clauses(data) if not clauses: errors.append("Certificate has no 'closed' or 'clauses' array") return errors if isinstance(clauses[0], (int, float)): return [] for i, c in enumerate(clauses): errors.extend(validate_clause(c, i)) return errors def run(cert_path: str, output_path: str) -> None: """Run verification and write output.""" errors: list[str] = [] try: data = load_certificate(cert_path) errors = validate_certificate(data) except FileNotFoundError as e: errors.append(str(e)) except json.JSONDecodeError as e: errors.append(f"Invalid JSON: {e}") if errors: content = "# Verification failed\n\n" + "\n".join(f"- {e}" for e in errors) else: content = "# Verification OK\n\n" Path(output_path).write_text(content, encoding="utf-8") def main() -> None: parser = argparse.ArgumentParser(description="Verify Collatz certificate structure") parser.add_argument("--certificat", required=True, help="Path to certificate JSON") parser.add_argument("--output", required=True, help="Path to output MD file") args = parser.parse_args() run(args.certificat, args.output) if __name__ == "__main__": main()