#!/usr/bin/env python3 """Validate config-atlas registry entries. Gate run by agents and CI (ATLAS-WP-0002-T06). Checks: 1. Every registry/surfaces/*.md frontmatter validates against schemas/surface-entry.schema.json (Draft 2020-12). 2. Entries never inline a configuration value or secret value (enforced by additionalProperties:false in the schema). 3. registry/indexes/surfaces.yaml is consistent with the entry files (same id set, and each declared path exists with a matching id). Exit code 0 = pass, 1 = validation failure, 2 = setup error. Dependencies: PyYAML, jsonschema. """ from __future__ import annotations import json import re import sys from pathlib import Path try: import yaml from jsonschema import Draft202012Validator except ImportError as exc: # pragma: no cover print(f"setup error: missing dependency ({exc}). pip install pyyaml jsonschema", file=sys.stderr) raise SystemExit(2) ROOT = Path(__file__).resolve().parent.parent SCHEMA_PATH = ROOT / "schemas" / "surface-entry.schema.json" SURFACES_DIR = ROOT / "registry" / "surfaces" SURFACES_INDEX = ROOT / "registry" / "indexes" / "surfaces.yaml" FRONTMATTER = re.compile(r"^---\n(.*?)\n---\n", re.S) def load_frontmatter(path: Path) -> dict: m = FRONTMATTER.match(path.read_text()) if not m: raise ValueError(f"{path}: missing YAML frontmatter") data = yaml.safe_load(m.group(1)) if not isinstance(data, dict): raise ValueError(f"{path}: frontmatter is not a mapping") return data def main() -> int: if not SCHEMA_PATH.exists(): print(f"setup error: schema not found at {SCHEMA_PATH}", file=sys.stderr) return 2 schema = json.loads(SCHEMA_PATH.read_text()) Draft202012Validator.check_schema(schema) validator = Draft202012Validator(schema) errors: list[str] = [] seen_ids: dict[str, Path] = {} entry_files = sorted(SURFACES_DIR.glob("*.md")) if SURFACES_DIR.exists() else [] for path in entry_files: try: fm = load_frontmatter(path) except ValueError as exc: errors.append(str(exc)) continue for err in sorted(validator.iter_errors(fm), key=lambda e: list(e.path)): loc = "/".join(str(p) for p in err.path) or "(root)" errors.append(f"{path.name}: {loc}: {err.message}") sid = fm.get("id") if sid in seen_ids: errors.append(f"{path.name}: duplicate id '{sid}' (also {seen_ids[sid].name})") elif sid: seen_ids[sid] = path # filename should match the id for discoverability if sid and path.stem != sid: errors.append(f"{path.name}: filename does not match id '{sid}'") # index consistency if SURFACES_INDEX.exists(): index = yaml.safe_load(SURFACES_INDEX.read_text()) or {} indexed = {row.get("id"): row for row in index.get("surfaces", [])} for sid in indexed.keys() - seen_ids.keys(): errors.append(f"surfaces.yaml: id '{sid}' indexed but no entry file found") for sid in seen_ids.keys() - indexed.keys(): errors.append(f"surfaces.yaml: entry '{sid}' present but not indexed") for sid, row in indexed.items(): declared = row.get("path") if declared and not (ROOT / declared).exists(): errors.append(f"surfaces.yaml: '{sid}' path '{declared}' does not exist") elif entry_files: errors.append("registry/indexes/surfaces.yaml missing but surface entries exist") if errors: print(f"FAIL: {len(errors)} problem(s) in registry validation:", file=sys.stderr) for e in errors: print(f" - {e}", file=sys.stderr) return 1 print(f"OK: {len(entry_files)} surface entr{'y' if len(entry_files)==1 else 'ies'} valid; index consistent.") return 0 if __name__ == "__main__": raise SystemExit(main())