feat(mindmodel): add validator and tests
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
from typing import Dict, Tuple, List, Any
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from scripts.mindmodel import loader
|
||||
from scripts.mindmodel import checks
|
||||
|
||||
|
||||
def validate_manifest(path: str, base_dir: str = None) -> Tuple[int, Dict[str, Any]]:
|
||||
"""Validate a manifest file at `path`.
|
||||
|
||||
Returns a tuple (exit_code, report).
|
||||
|
||||
exit codes:
|
||||
0 - ok (no issues)
|
||||
1 - warnings (only truncated snippets found)
|
||||
2 - critical (missing files, secrets, or parse error)
|
||||
"""
|
||||
report: Dict[str, Any] = {
|
||||
"path": path,
|
||||
"secrets": [],
|
||||
"missing_files": [],
|
||||
"truncated": 0,
|
||||
"constraints": [],
|
||||
}
|
||||
|
||||
p = Path(path)
|
||||
try:
|
||||
raw_text = p.read_text(encoding="utf-8")
|
||||
except Exception as exc:
|
||||
report["load_error"] = f"Manifest file not readable: {exc}"
|
||||
return 2, report
|
||||
|
||||
# scan for secrets in the manifest text
|
||||
secrets = checks.find_potential_secrets(raw_text)
|
||||
report["secrets"] = secrets
|
||||
|
||||
try:
|
||||
manifest = loader.load_manifest(path)
|
||||
except loader.ManifestLoadError as exc:
|
||||
report["load_error"] = str(exc)
|
||||
# treat parse/load errors as critical
|
||||
return 2, report
|
||||
|
||||
constraints = manifest.get("constraints") or []
|
||||
|
||||
for constraint in constraints:
|
||||
c_rep: Dict[str, Any] = {"constraint": constraint, "evidence": []}
|
||||
for ev in (
|
||||
constraint.get("evidence", [])
|
||||
if isinstance(constraint.get("evidence", []), list)
|
||||
else []
|
||||
):
|
||||
text = ev.get("text") if isinstance(ev, dict) else None
|
||||
file_ref = ev.get("file") if isinstance(ev, dict) else None
|
||||
|
||||
exists = True
|
||||
if file_ref:
|
||||
if not checks.file_exists(base_dir or "", file_ref):
|
||||
exists = False
|
||||
report["missing_files"].append(file_ref)
|
||||
|
||||
truncated = False
|
||||
if text:
|
||||
truncated = checks.detect_truncated(text)
|
||||
if truncated:
|
||||
report["truncated"] += 1
|
||||
|
||||
c_rep["evidence"].append(
|
||||
{
|
||||
"text": text,
|
||||
"file": file_ref,
|
||||
"exists": exists,
|
||||
"truncated": truncated,
|
||||
}
|
||||
)
|
||||
|
||||
report["constraints"].append(c_rep)
|
||||
|
||||
# decide exit code
|
||||
if report["secrets"]:
|
||||
return 2, report
|
||||
|
||||
if report["missing_files"]:
|
||||
return 2, report
|
||||
|
||||
if report["truncated"] > 0:
|
||||
return 1, report
|
||||
|
||||
return 0, report
|
||||
|
||||
|
||||
def main(argv: List[str]) -> int:
|
||||
import sys
|
||||
|
||||
if len(argv) < 2:
|
||||
print(json.dumps({"error": "manifest path required"}))
|
||||
return 2
|
||||
|
||||
path = argv[1]
|
||||
base_dir = argv[2] if len(argv) > 2 else None
|
||||
|
||||
code, report = validate_manifest(path, base_dir=base_dir)
|
||||
print(json.dumps(report))
|
||||
return code
|
||||
|
||||
|
||||
# no execution at import time
|
||||
Reference in New Issue
Block a user