54 lines
2.3 KiB
Python
54 lines
2.3 KiB
Python
#!/usr/bin/env python3
|
|
"""Validate all listings against the frozen v1 schemas.
|
|
|
|
Used by CI and locally: python3 scripts/validate.py
|
|
Exits non-zero on any violation. jsonschema required (pip install jsonschema).
|
|
"""
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
from jsonschema import Draft202012Validator
|
|
|
|
ROOT = Path(__file__).resolve().parent.parent
|
|
SOLUTION = Draft202012Validator(json.loads((ROOT / "schemas/solution.schema.json").read_text()))
|
|
LISTING = Draft202012Validator(json.loads((ROOT / "schemas/listing.schema.json").read_text()))
|
|
|
|
def main() -> int:
|
|
failures = []
|
|
for d in sorted((ROOT / "listings").iterdir()):
|
|
if not d.is_dir():
|
|
continue
|
|
for name, validator in (("listing.json", LISTING), ("solution.json", SOLUTION)):
|
|
f = d / name
|
|
if not f.exists():
|
|
failures.append(f"{d.name}: missing {name}")
|
|
continue
|
|
errors = sorted(validator.iter_errors(json.loads(f.read_text())), key=str)
|
|
failures += [f"{d.name}/{name}: {e.json_path}: {e.message}" for e in errors]
|
|
if (d / "listing.json").exists():
|
|
listing = json.loads((d / "listing.json").read_text())
|
|
if listing.get("listing_id") != d.name:
|
|
failures.append(f"{d.name}: directory name != listing_id")
|
|
sol_file = d / "solution.json"
|
|
if sol_file.exists():
|
|
sol = json.loads(sol_file.read_text())
|
|
scope = sol.get("tenant_scope")
|
|
vis = listing.get("tenant_visibility", [])
|
|
# tenant firewall coherence: a tenant-scoped solution must not be
|
|
# visible beyond its tenant (constitution IV)
|
|
if scope and scope != "m2-core" and ("*" in vis or any(t != scope for t in vis)):
|
|
failures.append(
|
|
f"{d.name}: tenant_scope={scope} but tenant_visibility={vis} leaks beyond the tenant"
|
|
)
|
|
if sol.get("solution_id") and listing.get("solution_id") != sol["solution_id"]:
|
|
failures.append(f"{d.name}: listing.solution_id != solution.solution_id")
|
|
if failures:
|
|
print("VALIDATION FAILED:")
|
|
print("\n".join(f" - {f}" for f in failures))
|
|
return 1
|
|
print("all listings valid")
|
|
return 0
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|