validate_config_excel_sync.py 1.8 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152
  1. """Validate Excel↔JSON byte-equal sync (V2-M1D).
  2. Thin wrapper over build_config_from_excel: regenerate JSON from Excel and assert
  3. byte-equal with the committed runtime JSON. Equivalent to
  4. `build_config_from_excel.py --check`, exposed as a uniform validator for the
  5. config gate.
  6. """
  7. from __future__ import annotations
  8. import json
  9. import sys
  10. from pathlib import Path
  11. ROOT = Path(__file__).resolve().parents[1]
  12. if str(ROOT) not in sys.path:
  13. sys.path.insert(0, str(ROOT))
  14. from scripts.build_config_from_excel import build, governance_report
  15. from scripts.check_config_json_canonical import canonical_dumps
  16. def main() -> int:
  17. findings = []
  18. for path_str, obj in build().items():
  19. path = Path(path_str)
  20. ok = canonical_dumps(obj) == path.read_text(encoding="utf-8")
  21. finding = {"level": "fail" if not ok else "info", "check_id": "excel_json_sync",
  22. "config_path": str(path.relative_to(ROOT)), "byte_equal": ok}
  23. if not ok:
  24. finding["message"] = "Excel diverges from JSON; run build_config_from_excel.py --sync-excel (JSON wins) or --write (Excel wins)"
  25. findings.append(finding)
  26. governance = governance_report()
  27. for row in governance:
  28. findings.append(
  29. {
  30. "level": "info",
  31. "check_id": "excel_governance_sheet",
  32. "config_path": row["workbook"],
  33. "sheet": row["sheet"],
  34. "mode": row["mode"],
  35. "row_count": row["row_count"],
  36. "runtime_row_count": row["runtime_row_count"],
  37. }
  38. )
  39. status = "fail" if any(f["level"] == "fail" for f in findings) else "pass"
  40. print(json.dumps({"status": status, "findings": findings}, ensure_ascii=False, indent=2))
  41. return 1 if status == "fail" else 0
  42. if __name__ == "__main__":
  43. sys.exit(main())