from __future__ import annotations import argparse import json from pathlib import Path from typing import Any def read_json(path: Path) -> dict[str, Any]: return json.loads(path.read_text(encoding="utf-8")) def fail(message: str, failures: list[str]) -> None: failures.append(message) def check_registry(registry: dict[str, Any]) -> dict[str, Any]: failures: list[str] = [] if registry.get("schema") != "codex_1c_moxel_schema_registry.v1": fail("unexpected schema", failures) rules = [rule for rule in registry.get("rules") or [] if isinstance(rule, dict)] if not rules: fail("registry must contain rules", failures) verified = [rule for rule in rules if rule.get("read_status") == "verified_read"] if not verified: fail("registry must contain at least one verified_read rule", failures) write_enabled = [rule for rule in rules if rule.get("write_status") == "verified_roundtrip"] if write_enabled: fail("write rules must stay disabled until explicit round-trip evidence is implemented", failures) counts = registry.get("counts") if isinstance(registry.get("counts"), dict) else {} expected_counts = { "rules": len(rules), "verified_read": len(verified), "candidate_read": sum(1 for rule in rules if rule.get("read_status") == "candidate_read"), "write_enabled": len(write_enabled), } for key, expected in expected_counts.items(): if counts.get(key) != expected: fail(f"counts.{key} must be {expected}, got {counts.get(key)}", failures) inline = [rule for rule in rules if rule.get("target") == "moxel.inline_text_cell.column"] if not inline: fail("missing moxel.inline_text_cell.column rule", failures) else: rule = inline[0] evidence = rule.get("evidence") if isinstance(rule.get("evidence"), dict) else {} if rule.get("read_status") != "verified_read": fail("inline column rule must be verified_read", failures) if evidence.get("ok") != evidence.get("total") or not isinstance(evidence.get("total"), int) or evidence.get("total") <= 0: fail("inline column rule evidence must be complete", failures) for rule in rules: if not rule.get("target"): fail(f"rule {rule.get('id')} has no target", failures) if rule.get("read_status") not in {"verified_read", "candidate_read", "needs_more_evidence"}: fail(f"rule {rule.get('id')} has unsupported read_status", failures) if rule.get("write_status") not in {"blocked_until_roundtrip", "blocked_until_verified_read", "verified_roundtrip"}: fail(f"rule {rule.get('id')} has unsupported write_status", failures) return { "schema": "codex_1c_moxel_schema_registry_check.v1", "status": "ok" if not failures else "failed", "failures": failures, "counts": { **expected_counts, }, } def main() -> int: parser = argparse.ArgumentParser(description="Validate the 1C MOXCEL schema registry safety contract.") parser.add_argument("--registry", default="plugins/1c/metadata/moxel-schema-registry.json") parser.add_argument("--output", default="reports/1c-template-baselines/moxel-schema-registry-check.json") args = parser.parse_args() report = check_registry(read_json(Path(args.registry))) output_path = Path(args.output) output_path.parent.mkdir(parents=True, exist_ok=True) output_path.write_text(json.dumps(report, ensure_ascii=False, indent=2), encoding="utf-8") print(json.dumps(report, ensure_ascii=False, indent=2)) return 0 if report["status"] == "ok" else 1 if __name__ == "__main__": raise SystemExit(main())