#!/usr/bin/env python3 """Ensure Authority policy client configs use the fine-grained scope set.""" from __future__ import annotations import sys from pathlib import Path EXPECTED_SCOPES = ( "policy:read", "policy:author", "policy:review", "policy:simulate", "findings:read", ) def extract_scopes(lines: list[str], start_index: int) -> tuple[str, ...] | None: for offset in range(1, 12): if start_index + offset >= len(lines): break line = lines[start_index + offset].strip() if not line: continue if line.startswith("scopes:"): try: raw = line.split("[", 1)[1].rsplit("]", 1)[0] except IndexError: return None scopes = tuple(scope.strip().strip('"') for scope in raw.split(",")) scopes = tuple(scope for scope in scopes if scope) return scopes return None def validate(path: Path) -> list[str]: errors: list[str] = [] try: text = path.read_text(encoding="utf-8") except FileNotFoundError: return [f"{path}: missing file"] if "policy:write" in text or "policy:submit" in text: errors.append(f"{path}: contains legacy policy scope names (policy:write/policy:submit)") lines = text.splitlines() client_indices = [idx for idx, line in enumerate(lines) if 'clientId: "policy-cli"' in line] if not client_indices: errors.append(f"{path}: policy-cli client registration not found") return errors for idx in client_indices: scopes = extract_scopes(lines, idx) if scopes is None: errors.append(f"{path}: unable to parse scopes for policy-cli client") continue if tuple(sorted(scopes)) != tuple(sorted(EXPECTED_SCOPES)): errors.append( f"{path}: unexpected policy-cli scopes {scopes}; expected {EXPECTED_SCOPES}" ) return errors def main(argv: list[str]) -> int: repo_root = Path(__file__).resolve().parents[1] targets = [ repo_root / "etc" / "authority.yaml", repo_root / "etc" / "authority.yaml.sample", ] failures: list[str] = [] for target in targets: failures.extend(validate(target)) if failures: for message in failures: print(f"error: {message}", file=sys.stderr) return 1 print("policy scope verification passed") return 0 if __name__ == "__main__": raise SystemExit(main(sys.argv))