**Motivations:** - Implémenter le workflow complet de démonstration Collatz (commandes.md) - Permettre la reprise après interruption au palier D20 **Evolutions:** - Scripts 01-12 et run-full-workflow alignés sur commandes.md sections 1-10 - collatz_recover_noyau.py : recréation de noyau_post_D20 à partir du CSV candidats - Option --resume-from D20 dans collatz_k_pipeline pour reprendre sans recalculer D18-D19-F15 - Détection automatique : si candidats_D20 existe sans noyau_post_D20, récupération puis poursuite - Filtres --cible=critique et --modulo dans collatz_fusion_pipeline - ROOT par défaut = collatz_k_scripts (plus data/source vide) **Pages affectées:** - .gitignore (__pycache__, out/) - applications/collatz/collatz_k_scripts/*.py - applications/collatz/scripts/*.sh - applications/collatz/scripts/README.md
107 lines
3.2 KiB
Python
107 lines
3.2 KiB
Python
#!/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()
|