"""CI-Gate: Stelle sicher, dass keine Verweise auf die abgeschaffte ``sysadmin``-Rolle bzw. die alten Helper im Codebase mehr existieren. Verbotene Symbole nach Abschluss von ``2026-04-sysadmin-authority-split``: - ``hasSysAdminRole`` (RequestContext property) - ``requireSysAdminRole`` (FastAPI dependency) - ``_hasSysAdminRole`` (Hilfs-Funktion gegen die alte Rolle) Erlaubt sind weiterhin: - ``isSysAdmin`` (User-Flag fuer Infrastruktur-Operator) - ``isPlatformAdmin`` (User-Flag fuer Cross-Mandate-Governance) - ``requireSysAdmin`` / ``requirePlatformAdmin`` (FastAPI Dependencies) Exit-Code: - 0 -> sauber - 1 -> Fundstellen vorhanden (CI bricht ab) Aufruf:: python gateway/scripts/check_no_sysadmin_role.py """ from __future__ import annotations import os import re import sys from pathlib import Path from typing import Iterable, List, Tuple _REPO_ROOT = Path(__file__).resolve().parents[2] _FORBIDDEN_PATTERNS: Tuple[Tuple[str, str], ...] = ( (r"\bhasSysAdminRole\b", "Use ctx.isPlatformAdmin (Governance) or ctx.isSysAdmin (Infra)"), (r"\brequireSysAdminRole\b", "Use requirePlatformAdmin (Governance) or requireSysAdmin (Infra)"), (r"\b_hasSysAdminRole\b", "Use User.isPlatformAdmin flag check directly"), ) _INCLUDE_SUFFIXES = {".py", ".ts", ".tsx", ".js", ".jsx"} _EXCLUDE_DIR_NAMES = { ".git", "node_modules", "dist", "build", "__pycache__", ".venv", "venv", ".pytest_cache", ".mypy_cache", "wiki", "scripts", } def _shouldScan(path: Path) -> bool: if path.suffix not in _INCLUDE_SUFFIXES: return False parts = set(path.parts) if parts & _EXCLUDE_DIR_NAMES: return False return True def _iterFiles(root: Path) -> Iterable[Path]: for dirpath, dirnames, filenames in os.walk(root): dirnames[:] = [d for d in dirnames if d not in _EXCLUDE_DIR_NAMES] for name in filenames: full = Path(dirpath) / name if _shouldScan(full): yield full def _scanFile(path: Path) -> List[Tuple[int, str, str, str]]: findings: List[Tuple[int, str, str, str]] = [] try: text = path.read_text(encoding="utf-8", errors="ignore") except Exception: return findings for pattern, hint in _FORBIDDEN_PATTERNS: compiled = re.compile(pattern) for lineNo, line in enumerate(text.splitlines(), start=1): if compiled.search(line): findings.append((lineNo, pattern, hint, line.rstrip())) return findings def _main() -> int: findings = [] for filePath in _iterFiles(_REPO_ROOT): for entry in _scanFile(filePath): findings.append((filePath, *entry)) if not findings: print("[OK] No legacy sysadmin-role references found.") return 0 print("[FAIL] Found legacy sysadmin-role references:") for filePath, lineNo, pattern, hint, line in findings: rel = filePath.relative_to(_REPO_ROOT) print(f" {rel}:{lineNo}: {pattern}\n hint: {hint}\n line: {line}") print(f"\n[FAIL] {len(findings)} forbidden reference(s).") return 1 if __name__ == "__main__": sys.exit(_main())