#!/usr/bin/env python3 """Validate a latest 1C saved-state watch run lookup.""" from __future__ import annotations import argparse import json from pathlib import Path from typing import Any EXPECTED_SCHEMA = "onec_saved_state_latest_watch_run.v1" def load_json(path: Path) -> dict[str, Any]: return json.loads(path.read_text(encoding="utf-8-sig")) def write_json(path: Path, data: dict[str, Any]) -> None: path.parent.mkdir(parents=True, exist_ok=True) path.write_text(json.dumps(data, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") def issue(severity: str, code: str, message: str, *, path: Path | str | None = None, detail: dict[str, Any] | None = None) -> dict[str, Any]: result: dict[str, Any] = {"severity": severity, "code": code, "message": message} if path is not None: result["path"] = str(path) if detail: result["detail"] = detail return result def linked_path(latest_path: Path, value: Any) -> Path | None: if not value: return None path = Path(str(value)) if path.is_absolute(): return path return (latest_path.parent / path).resolve() def check_linked_check(latest_path: Path, run: dict[str, Any], key: str, passed_key: str, findings: list[dict[str, Any]]) -> None: path = linked_path(latest_path, run.get(key)) if path is None: return if not path.exists(): findings.append(issue("error", f"missing_{key}", f"Linked {key} file is missing.", path=path)) return linked = load_json(path) if run.get(passed_key) != linked.get("passed"): findings.append(issue("error", f"{passed_key}_mismatch", f"{passed_key} differs from linked check.", path=path)) def has_delta_changes(run: dict[str, Any]) -> bool: counts = run.get("counts") or {} return any(int(counts.get(key) or 0) > 0 for key in ("delta_objects_added", "delta_objects_removed", "delta_objects_changed")) def check_latest(latest_path: Path) -> dict[str, Any]: findings: list[dict[str, Any]] = [] if not latest_path.exists(): findings.append(issue("error", "missing_latest", "Latest watch lookup file is missing.", path=latest_path)) return build_result(latest_path, findings) data = load_json(latest_path) if data.get("schema") != EXPECTED_SCHEMA: findings.append(issue("error", "invalid_schema", "Latest watch lookup schema is invalid.", detail={"schema": data.get("schema")})) safety = data.get("safety") or {} if safety.get("read_only") is not True: findings.append(issue("error", "not_read_only", "Latest watch lookup safety.read_only must be true.")) if safety.get("sql_write_performed") is not False: findings.append(issue("error", "sql_write_flag", "Latest watch lookup safety.sql_write_performed must be false.")) markdown = linked_path(latest_path, data.get("markdown")) if data.get("markdown") and (markdown is None or not markdown.exists()): findings.append(issue("error", "missing_markdown", "Linked latest Markdown file is missing.", path=markdown or "")) found = data.get("found") latest = data.get("latest") if found is True: if not isinstance(latest, dict): findings.append(issue("error", "missing_latest_run", "found=true requires latest object.")) return build_result(latest_path, findings) run_dir = linked_path(latest_path, latest.get("run_dir")) if run_dir is None or not run_dir.exists(): findings.append(issue("error", "missing_run_dir", "Latest run directory is missing.", path=run_dir or "")) for key in ("report", "manifest_check"): path = linked_path(latest_path, latest.get(key)) if path is not None and not path.exists(): findings.append(issue("error", f"missing_{key}", f"Linked {key} is missing.", path=path)) check_linked_check(latest_path, latest, "manifest_check", "manifest_check_passed", findings) check_linked_check(latest_path, latest, "delta_check", "delta_check_passed", findings) if data.get("require_delta") is True and not latest.get("delta"): findings.append(issue("error", "required_delta_missing", "require_delta=true but latest run has no delta.")) if data.get("require_changed") is True and not has_delta_changes(latest): findings.append(issue("error", "required_changed_missing", "require_changed=true but latest run has no delta changes.")) elif found is False: if latest is not None: findings.append(issue("error", "unexpected_latest", "found=false requires latest=null.")) else: findings.append(issue("error", "invalid_found", "found must be boolean.")) counts = data.get("counts") if not isinstance(counts, dict): findings.append(issue("error", "missing_counts", "Latest lookup must include counts.")) return build_result(latest_path, findings) def build_result(latest_path: Path, findings: list[dict[str, Any]]) -> dict[str, Any]: errors = [row for row in findings if row.get("severity") == "error"] warnings = [row for row in findings if row.get("severity") == "warning"] return { "schema": "onec_saved_state_latest_watch_run_check.v1", "latest": str(latest_path), "passed": not errors, "findings": findings, "counts": {"errors": len(errors), "warnings": len(warnings)}, "safety": {"read_only": True, "sql_write_performed": False}, } def main() -> int: parser = argparse.ArgumentParser(description="Validate latest 1C saved-state watch run lookup.") parser.add_argument("--latest", type=Path, required=True) parser.add_argument("--output", type=Path) args = parser.parse_args() result = check_latest(args.latest) if args.output: write_json(args.output, result) print(json.dumps({"output": str(args.output) if args.output else None, "passed": result["passed"], "counts": result["counts"]}, ensure_ascii=False)) return 0 if result["passed"] else 2 if __name__ == "__main__": raise SystemExit(main())