generated from john/python-template
This commit is contained in:
@@ -18,6 +18,7 @@ ACTIVE_CONTRACT_FILES = (
|
||||
".github/instructions/services.instructions.md",
|
||||
".github/instructions/ui.instructions.md",
|
||||
".github/instructions/error-handling.instructions.md",
|
||||
".github/instructions/providers.instructions.md",
|
||||
".github/skills/python-code-reviewer/skill.md",
|
||||
".github/skills/evidence-provenance-auditor/skill.md",
|
||||
".github/skills/test-effectiveness-auditor/skill.md",
|
||||
@@ -237,6 +238,12 @@ def test_canonical_authority_references_are_present():
|
||||
"docs/error_handling.md",
|
||||
"docs/requirements.md",
|
||||
),
|
||||
".github/instructions/providers.instructions.md": (
|
||||
"docs/invariant/ai_evidence_and_provenance.md",
|
||||
"docs/schema.md",
|
||||
"tests/test_provider_boundaries.py",
|
||||
"SAFE_RESPONSE_HEADERS",
|
||||
),
|
||||
".github/skills/python-code-reviewer/skill.md": (
|
||||
"docs/*",
|
||||
"docs/schema.md",
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
"""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.<package>` 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
|
||||
Reference in New Issue
Block a user