#!/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())