"""AUDIT-WP-0009-T08 — the §9.4 approval-validity prohibition, asserted. The prohibition has until now been honoured by absence, which is not the estate's idiom: §6.4 obligation 3 requires a published stance to equal shipped behaviour *asserted by test*. A prohibition stated in `INTENT.md`, `SCOPE.md` and `layer.yaml` is worth asserting in `tests/`. Audit Core records approval events. It renders no verdict on whether an approval is still valid — a consumer branching on such an answer would route an authorization decision through the audit fabric, and `access-engine` is the estate's only decision point (§6). Operative approval state belongs to `approval-engine`. These tests are deliberately written against *shapes* rather than a fixed list of today's routes, so that adding a validity surface later fails here rather than passing quietly. """ from __future__ import annotations import inspect from pathlib import Path import pytest from audit_core import interface from audit_core.ingestion import IngestionApplication from audit_core.sqlite_backend import SQLiteAuditBackend from test_ingestion import invoke REPO = Path(__file__).resolve().parents[1] # Paths a consumer looking for a verdict would plausibly try. None may answer. VERDICT_PATHS = [ "/v1/approvals", "/v1/approvals/appr-1", "/v1/approvals/appr-1/valid", "/v1/approvals/appr-1/validity", "/v1/approvals/appr-1/status", "/v1/approval-validity", "/v1/approval-status", "/v1/is-approved", ] # Method-name fragments that would mean a backend answers the question. VERDICT_NAME_FRAGMENTS = ( "approval_valid", "approval_validity", "is_approved", "approval_status", "check_approval", "approval_state", ) @pytest.fixture def app(tmp_path): return IngestionApplication(SQLiteAuditBackend(str(tmp_path / "events.db")), "opaque") @pytest.mark.parametrize("path", VERDICT_PATHS) def test_no_route_answers_whether_an_approval_is_valid(app, path): status, _ = invoke(app, None, path=path, method="GET", body=b"") # 404 is the right answer: the surface does not exist. Anything in the 2xx # range would be a verdict, and a 403 would imply one exists behind auth. assert status.startswith("404"), f"{path} answered {status}" def test_no_backend_exposes_an_approval_verdict_method(): """The contract and every implementation, not only the one in use.""" from audit_core import mock_file_backend, sqlite_backend modules = [interface, sqlite_backend, mock_file_backend] try: # optional dependency; skipping it would weaken the assertion silently from audit_core import postgres_backend modules.append(postgres_backend) except ImportError: pass offenders = [] for module in modules: for _, obj in inspect.getmembers(module, inspect.isclass): if obj.__module__ != module.__name__: continue for name in dir(obj): lowered = name.lower() if any(fragment in lowered for fragment in VERDICT_NAME_FRAGMENTS): offenders.append(f"{module.__name__}.{obj.__name__}.{name}") assert not offenders, f"approval-validity surface appeared: {offenders}" def test_the_prohibition_is_declared_where_a_reader_would_look(): """Shipped behaviour above is only half of §6.4 obligation 3.""" layer = (REPO / "layer.yaml").read_text() assert "approval_validity_query: forbidden" in layer intent = (REPO / "INTENT.md").read_text() assert "no approval-validity query" in intent.lower() scope = (REPO / "SCOPE.md").read_text() assert "approval-validity query" in scope.lower() def test_recording_an_approval_event_is_still_allowed(app): """The prohibition bounds the verdict, not the record. Guards over-reading. Emitted here from the one registered sender: `approval-engine` is not yet admitted as a source (AUDIT-WP-0009-T09), and this test asserts that an approval-shaped *event class* is ingestible, not that its future sender is already registered. A 400 `source_not_allowed` is sender admission working, which is a different control. """ from test_ingestion import event status, _ = invoke( app, event(id="evt-approval-1", type="approval.revoked"), key="evt-approval-1", ) assert status.startswith("202")