Initial SQL-only 1C adapter baseline
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Validate a one-shot 1C saved-state watch manifest."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
|
||||
EXPECTED_SCHEMA = "onec_saved_state_watch_once.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) -> dict[str, Any]:
|
||||
result = {"severity": severity, "code": code, "message": message}
|
||||
if path is not None:
|
||||
result["path"] = str(path)
|
||||
return result
|
||||
|
||||
|
||||
def linked_path(manifest_path: Path, value: Any) -> Path | None:
|
||||
if not value:
|
||||
return None
|
||||
path = Path(str(value))
|
||||
if path.is_absolute():
|
||||
return path
|
||||
return (manifest_path.parent / path).resolve()
|
||||
|
||||
|
||||
def check_existing(manifest_path: Path, data: dict[str, Any], key: str, findings: list[dict[str, Any]], *, required: bool = True) -> Path | None:
|
||||
path = linked_path(manifest_path, data.get(key))
|
||||
if path is None:
|
||||
if required:
|
||||
findings.append(issue("error", f"missing_{key}", f"Manifest field {key} is missing."))
|
||||
return None
|
||||
if not path.exists():
|
||||
findings.append(issue("error" if required else "warning", f"missing_{key}_file", f"Linked {key} file is missing.", path=path))
|
||||
return path
|
||||
|
||||
|
||||
def check_manifest(manifest_path: Path) -> dict[str, Any]:
|
||||
findings: list[dict[str, Any]] = []
|
||||
if not manifest_path.exists():
|
||||
findings.append(issue("error", "missing_manifest", "Watch manifest is missing.", path=manifest_path))
|
||||
return build_result(manifest_path, findings)
|
||||
data = load_json(manifest_path)
|
||||
if data.get("schema") != EXPECTED_SCHEMA:
|
||||
findings.append(issue("error", "invalid_schema", "Watch manifest schema is invalid."))
|
||||
|
||||
safety = data.get("safety") or {}
|
||||
if safety.get("read_only") is not True:
|
||||
findings.append(issue("error", "not_read_only", "Watch manifest safety.read_only must be true."))
|
||||
if safety.get("sql_write_performed") is not False:
|
||||
findings.append(issue("error", "sql_write_flag", "Watch manifest safety.sql_write_performed must be false."))
|
||||
if safety.get("secrets_in_report") is not False:
|
||||
findings.append(issue("error", "secrets_flag", "Watch manifest safety.secrets_in_report must be false."))
|
||||
|
||||
report_path = check_existing(manifest_path, data, "report", findings)
|
||||
check_existing(manifest_path, data, "markdown", findings, required=False)
|
||||
report_check_path = check_existing(manifest_path, data, "check", findings)
|
||||
check_existing(manifest_path, data, "manifest_markdown", findings, required=False)
|
||||
delta_path = check_existing(manifest_path, data, "delta", findings, required=False)
|
||||
check_existing(manifest_path, data, "delta_markdown", findings, required=False)
|
||||
delta_check_path = check_existing(manifest_path, data, "delta_check", findings, required=False)
|
||||
|
||||
if report_path and report_path.exists():
|
||||
report = load_json(report_path)
|
||||
counts = data.get("counts") or {}
|
||||
report_counts = report.get("counts") or {}
|
||||
if counts.get("object_changes") != report_counts.get("object_changes"):
|
||||
findings.append(issue("error", "object_count_mismatch", "Watch object_changes count differs from report."))
|
||||
if counts.get("system_changes") != report_counts.get("system_changes"):
|
||||
findings.append(issue("error", "system_count_mismatch", "Watch system_changes count differs from report."))
|
||||
|
||||
if report_check_path and report_check_path.exists():
|
||||
report_check = load_json(report_check_path)
|
||||
if report_check.get("passed") is not True:
|
||||
findings.append(issue("error", "report_check_failed", "Linked saved-state report check did not pass.", path=report_check_path))
|
||||
|
||||
if delta_path and delta_path.exists():
|
||||
delta = load_json(delta_path)
|
||||
counts = data.get("counts") or {}
|
||||
delta_counts = delta.get("counts") or {}
|
||||
mapping = {
|
||||
"delta_objects_added": "objects_added",
|
||||
"delta_objects_removed": "objects_removed",
|
||||
"delta_objects_changed": "objects_changed",
|
||||
"delta_objects_unchanged": "objects_unchanged",
|
||||
}
|
||||
for watch_key, delta_key in mapping.items():
|
||||
if counts.get(watch_key) != delta_counts.get(delta_key):
|
||||
findings.append(issue("error", "delta_count_mismatch", f"Watch {watch_key} differs from delta {delta_key}."))
|
||||
if delta_check_path and delta_check_path.exists():
|
||||
delta_check = load_json(delta_check_path)
|
||||
if delta_check.get("passed") is not True:
|
||||
findings.append(issue("error", "delta_check_failed", "Linked delta check did not pass.", path=delta_check_path))
|
||||
|
||||
return build_result(manifest_path, findings)
|
||||
|
||||
|
||||
def build_result(manifest_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_watch_once_check.v1",
|
||||
"manifest": str(manifest_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 a one-shot 1C saved-state watch manifest.")
|
||||
parser.add_argument("--manifest", type=Path, required=True)
|
||||
parser.add_argument("--output", type=Path)
|
||||
args = parser.parse_args()
|
||||
|
||||
result = check_manifest(args.manifest)
|
||||
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())
|
||||
Reference in New Issue
Block a user