#!/usr/bin/env python3 """Fail when the documented Pulse API and registered HTTP routes drift apart.""" from __future__ import annotations import json import re import sys from pathlib import Path ROOT = Path(__file__).resolve().parents[1] MANIFEST = ROOT / "specs" / "api-routes.json" CONTRACT = ROOT / "docs" / "architecture" / "API_CONTRACT.md" ROUTERS = (ROOT / "cmd" / "api" / "main.go", ROOT / "internal" / "service" / "health.go") TABLE_ROUTE = re.compile(r"^\| `(?PGET|POST|PUT|PATCH|DELETE) (?P/[^`]+)` \|", re.MULTILINE) REGISTERED = re.compile(r"\.Handle(?:Func)?\(\s*\"(?P/[^\"]+)\"") def fail(messages: list[str]) -> int: for message in messages: print(f"API CONTRACT: FAIL: {message}", file=sys.stderr) return 1 def main() -> int: document = json.loads(MANIFEST.read_text(encoding="utf-8")) routes = document.get("routes", []) errors: list[str] = [] keys: list[tuple[str, str]] = [] covered_registrations: set[str] = set() for index, route in enumerate(routes): missing = {"method", "path", "registration", "access", "implementation"} - route.keys() if missing: errors.append(f"route {index} misses fields: {', '.join(sorted(missing))}") continue key = (route["method"], route["path"]) if key in keys: errors.append(f"duplicate manifest route: {key[0]} {key[1]}") keys.append(key) covered_registrations.add(route["registration"]) implementation = ROOT / route["implementation"] if not implementation.is_file(): errors.append(f"implementation does not exist for {key[0]} {key[1]}: {implementation.relative_to(ROOT)}") markdown = CONTRACT.read_text(encoding="utf-8") documented = [(match.group("method"), match.group("path")) for match in TABLE_ROUTE.finditer(markdown)] manifest_set = set(keys) documented_set = set(documented) for method, path in sorted(manifest_set - documented_set): errors.append(f"manifest route is not documented: {method} {path}") for method, path in sorted(documented_set - manifest_set): errors.append(f"documented route is not in manifest: {method} {path}") if len(documented) != len(documented_set): errors.append("the route table contains duplicate method/path rows") registered: set[str] = set() for source in ROUTERS: registered.update(match.group("path") for match in REGISTERED.finditer(source.read_text(encoding="utf-8"))) for path in sorted(registered - covered_registrations): errors.append(f"registered router path is not represented in the manifest: {path}") for path in sorted(covered_registrations - registered): errors.append(f"manifest registration does not exist in the router: {path}") if errors: return fail(errors) print( "API CONTRACT: PASS " f"({len(manifest_set)} method/path contracts, {len(registered)} router registrations, no drift)" ) return 0 if __name__ == "__main__": raise SystemExit(main())