This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Validate starter JSON Schemas and their canonical examples.
|
||||
|
||||
Requires the development-only `jsonschema` package. The core `projectctl.py`
|
||||
remains standard-library-only so repository state can be recovered first.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
def load(path: Path) -> Any:
|
||||
return json.loads(path.read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
def main() -> int:
|
||||
try:
|
||||
from jsonschema import Draft202012Validator
|
||||
from referencing import Registry, Resource
|
||||
except ModuleNotFoundError:
|
||||
print(
|
||||
"ERROR: contract validation requires the development package 'jsonschema'.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return 2
|
||||
|
||||
schema_paths = sorted((ROOT / "specs").glob("*.json"))
|
||||
schemas: dict[str, dict[str, Any]] = {}
|
||||
resources: list[tuple[str, Any]] = []
|
||||
|
||||
for path in schema_paths:
|
||||
schema = load(path)
|
||||
Draft202012Validator.check_schema(schema)
|
||||
schema_id = schema.get("$id")
|
||||
if not isinstance(schema_id, str) or not schema_id:
|
||||
print(f"ERROR: {path.relative_to(ROOT)} has no $id", file=sys.stderr)
|
||||
return 1
|
||||
schemas[path.relative_to(ROOT).as_posix()] = schema
|
||||
resource = Resource.from_contents(schema)
|
||||
resources.append((schema_id, resource))
|
||||
resources.append((path.resolve().as_uri(), resource))
|
||||
|
||||
registry = Registry().with_resources(resources)
|
||||
checks: list[tuple[str, str]] = [
|
||||
("config/metrics/catalog.example.json", "specs/metric-catalog.schema.json"),
|
||||
("config/dashboards/default-overview.example.json", "specs/dashboard.schema.json"),
|
||||
("config/alerts/default-rules.example.json", "specs/alert-rule-set.schema.json"),
|
||||
("config/probes/probe.example.json", "specs/probe.schema.json"),
|
||||
]
|
||||
# The canonical private repository validates its implementation ledger. The
|
||||
# curated public-source export deliberately excludes private planning state.
|
||||
if (ROOT / "planning" / "task-ledger.json").exists():
|
||||
checks.insert(0, ("planning/task-ledger.json", "specs/task-ledger.schema.json"))
|
||||
checks.extend(
|
||||
(path.relative_to(ROOT).as_posix(), "specs/simulator-scenario.schema.json")
|
||||
for path in sorted((ROOT / "fixtures" / "scenarios").glob("*.json"))
|
||||
)
|
||||
|
||||
failures = 0
|
||||
for document_name, schema_name in checks:
|
||||
document = load(ROOT / document_name)
|
||||
schema = schemas[schema_name]
|
||||
validator = Draft202012Validator(schema, registry=registry)
|
||||
errors = sorted(validator.iter_errors(document), key=lambda item: list(item.path))
|
||||
if not errors:
|
||||
print(f"PASS: {document_name}")
|
||||
continue
|
||||
failures += len(errors)
|
||||
print(f"FAIL: {document_name}", file=sys.stderr)
|
||||
for error in errors:
|
||||
location = "/".join(str(part) for part in error.path) or "<root>"
|
||||
print(f"- {location}: {error.message}", file=sys.stderr)
|
||||
|
||||
if failures:
|
||||
print(f"CONTRACT VALIDATION: FAIL ({failures} errors)", file=sys.stderr)
|
||||
return 1
|
||||
print(f"CONTRACT VALIDATION: PASS ({len(schema_paths)} schemas, {len(checks)} documents)")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user