"""Structural rules for the providers package. `.github/instructions/providers.instructions.md` requires provider specifics to stop at the adapter boundary: adapters translate an external API into `TranscriptionResult` and `ProviderError`, and know nothing about persistence, services, or the UI. Without this guard the rule is only advice, and a single convenience import of a service or model would invert the dependency direction the architecture depends on. """ from __future__ import annotations import ast from pathlib import Path PROVIDERS_DIR = Path(__file__).resolve().parents[1] / "src" / "transcription" / "providers" # Application packages an adapter must never reach into. `config` is intentionally absent: # adapters read Settings for timeouts and credentials. FORBIDDEN_PACKAGES = frozenset({"services", "db", "ui", "api", "worker", "worker_service"}) def _module_paths() -> list[Path]: return sorted(PROVIDERS_DIR.glob("*.py")) def _imported_application_packages(tree: ast.Module) -> set[str]: """Return first-level `transcription.` names imported by this module.""" imported: set[str] = set() for node in ast.walk(tree): if isinstance(node, ast.ImportFrom) and node.module: parts = node.module.split(".") if node.level == 0 and parts[0] == "transcription" and len(parts) > 1: imported.add(parts[1]) elif isinstance(node, ast.Import): for alias in node.names: parts = alias.name.split(".") if parts[0] == "transcription" and len(parts) > 1: imported.add(parts[1]) return imported def test_provider_modules_are_discovered(): """Guard the guard: the rules below are meaningless if nothing is scanned.""" assert {path.stem for path in _module_paths()} >= {"base", "evidence", "openrouter"} def test_provider_modules_do_not_import_application_layers(): """A provider adapter must not depend on services, persistence, UI, API, or the worker.""" violations: dict[str, list[str]] = {} for path in _module_paths(): tree = ast.parse(path.read_text(encoding="utf-8")) found = sorted(_imported_application_packages(tree) & FORBIDDEN_PACKAGES) if found: violations[path.name] = found assert violations == {} def test_response_headers_are_filtered_through_the_allowlist(): """Header persistence must be allowlist-based, not capture-then-redact.""" from transcription.providers.evidence import SAFE_RESPONSE_HEADERS from transcription.providers.evidence import filter_safe_response_headers filtered = filter_safe_response_headers( { "Content-Type": "application/json", "Authorization": "Bearer super-secret", "Set-Cookie": "session=super-secret", "X-Unknown-Future-Header": "unreviewed", } ) assert filtered == {"content-type": "application/json"} assert "authorization" not in SAFE_RESPONSE_HEADERS assert "set-cookie" not in SAFE_RESPONSE_HEADERS