"""AUDIT-WP-0009-T05 — declared emission cadence, evaluated by the observer.""" import copy import json import pytest from audit_core.emission_cadence import ( BELOW_CADENCE, CONTRACT_DIGEST, duration_seconds, evaluate, parse_declaration, ) from audit_core.ingestion import IngestionApplication from audit_core.senders import SenderIdentity, SenderRegistry, _identity_from from audit_core.sqlite_backend import SQLiteAuditBackend from tests.test_stream_findings import emit, invoke RATE = { "schema_version": "0.1", "declaration_id": "approval-engine.audit.v1", "source": "approval-engine", "stream_id": "approval-engine.audit", "sources": [ { "source_id": "approval-engine.issued", "event_class": "approval.issued", "form": "expected-rate", "window": "PT1H", "expected_min": 2, "drop_below": "finding", }, { "source_id": "approval-engine.revocation", "event_class": "approval.revocation", "form": "heartbeat-or-reconciliation", "heartbeat": { "event_class": "approval-engine.heartbeat", "interval": "24h", "assertion": "nothing-to-report", "missing": "finding", }, }, ], } # net-kingdom commit 116643f, local-identity/emission-cadence.yaml, as JSON. # The one real source-owned declaration published so far. NET_KINGDOM_LOCAL_IDENTITY = { "schema_version": "0.1", "declaration_id": "net-kingdom.local-identity.audit.v1", "source": "net-kingdom", "stream_id": "net-kingdom.local-identity.audit", "extensions": {"netkingdom": {"contract_digest": "972c0b6701d1693f", "contract_document_version": "0.2.0"}}, "sources": [ { "source_id": f"net-kingdom.local-identity.audit.{name}", "source_system": "local-identity", "event_class": cls, "form": "heartbeat-or-reconciliation", "reconciliation": { "compare_local": f"audit_log_counts.{cls}", "compare_observed": f"evidence_counts.{cls}", "divergence": "finding", }, "extensions": {"netkingdom": {"evidence_class": "load-bearing", "rate_monitoring": "forbidden", "completeness_claimed": False, "heartbeat_emitted": False}}, } for name, cls in (("token-issued", "serve/token.token_issued"), ("token-revoked", "revoke-token")) ], } def _sender(declaration=RATE, sources=("approval-engine",)): return SenderIdentity( name="approval-engine", tokens=("approval",), sources=frozenset(sources), tenants=frozenset({"tenant:platform"}), emission_cadence=parse_declaration(declaration), ) @pytest.mark.parametrize("text,seconds", [ ("PT1H", 3600), ("P1D", 86400), ("P1DT30M", 88200), ("90s", 90), ("5m", 300), ("1d", 86400), ]) def test_durations_follow_the_schema_pattern(text, seconds): assert duration_seconds(text) == seconds @pytest.mark.parametrize("text", ["P", "PT", "PT0S", "0s", "1w", "3600", "P1H"]) def test_durations_the_schema_refuses_are_refused(text): with pytest.raises(ValueError): duration_seconds(text) def test_both_forms_parse_and_only_rates_are_indexed_for_evaluation(): declaration = parse_declaration(RATE) assert [(r.event_class, r.window_seconds, r.expected_min) for r in declaration.rates] == [ ("approval.issued", 3600, 2) ] assert declaration.summary()["heartbeat_or_reconciliation_classes"] == ["approval.revocation"] assert declaration.summary()["contract_digest"] == CONTRACT_DIGEST def test_the_published_net_kingdom_declaration_is_accepted(): """Observer evaluation of the one real declaration: it fits the contract.""" declaration = parse_declaration(NET_KINGDOM_LOCAL_IDENTITY) assert declaration.rates == () assert len(declaration.other_entries) == 2 def _broken(mutate): doc = copy.deepcopy(RATE) mutate(doc) return doc @pytest.mark.parametrize("mutate", [ lambda d: d.update(schema_version="0.2"), lambda d: d.pop("declaration_id"), lambda d: d.update(sources=[]), lambda d: d.update(unknown=1), lambda d: d["sources"][0].pop("expected_min"), lambda d: d["sources"][0].pop("drop_below"), lambda d: d["sources"][0].update(window_seconds=3600), # both window forms lambda d: d["sources"][0].pop("window"), # neither lambda d: d["sources"][0].update(expected_min=-1), lambda d: d["sources"][0].update(heartbeat=RATE["sources"][1]["heartbeat"]), lambda d: d["sources"][0].update(form="rate"), lambda d: d["sources"][1].pop("heartbeat"), # neither half lambda d: d["sources"][1].update(window="PT1H"), lambda d: d["sources"][1]["heartbeat"].pop("missing"), lambda d: d["sources"][1]["heartbeat"].update(interval_seconds=60), lambda d: d["sources"].append(copy.deepcopy(d["sources"][0])), # duplicate class ]) def test_what_the_schema_refuses_is_refused(mutate): with pytest.raises(ValueError): parse_declaration(_broken(mutate)) def _counter(counts): return lambda source, since, until: [{"class": c, "count": n} for c, n in counts.items()] def test_a_stream_below_its_declared_rate_is_a_finding(): findings = evaluate([_sender()], _counter({"approval.issued": 1})) assert len(findings) == 1 finding = findings[0].as_dict() assert finding["kind"] == BELOW_CADENCE assert (finding["observed"], finding["expected_min"]) == (1, 2) assert "not proof of suppression" in finding["means"] def test_a_stream_at_its_declared_rate_is_not_a_finding(): assert evaluate([_sender()], _counter({"approval.issued": 2})) == [] def test_an_undeclared_class_is_never_a_finding(): """Heartbeat-or-reconciliation classes are T04/T06's, not rate-monitored.""" findings = evaluate([_sender()], _counter({"approval.issued": 5})) assert all(f.event_class != "approval.revocation" for f in findings) def test_a_wildcard_source_is_not_held_to_a_rate(): assert evaluate([_sender(sources=("*",))], _counter({})) == [] def test_the_registration_carries_the_declaration(): identity = _identity_from({ "name": "approval-engine", "token": "t", "sources": ["approval-engine"], "emission_cadence": RATE, }) assert identity.evidence_declaration()["emission_cadence"]["declaration_id"] == ( "approval-engine.audit.v1" ) def test_an_invalid_declaration_refuses_the_registration(): with pytest.raises(ValueError): _identity_from({ "name": "x", "token": "t", "sources": ["x"], "emission_cadence": _broken(lambda d: d.pop("sources")), }) def test_the_overlay_may_not_touch_the_declaration(tmp_path): base = {"name": "approval-engine", "token": "t", "sources": ["approval-engine"], "emission_cadence": RATE} scope = tmp_path / "scope.json" scope.write_text(json.dumps([{"name": "approval-engine", "emission_cadence": RATE}])) with pytest.raises(ValueError, match="emission_cadence"): SenderRegistry.from_env({ "AUDIT_CORE_SENDERS": json.dumps([base]), "AUDIT_CORE_SENDERS_SCOPE_PATH": str(scope), }) def test_the_findings_surface_reports_a_cadence_miss_and_clears_on_delivery(tmp_path): operator = SenderIdentity( name="operator", tokens=("operator",), sources=frozenset({"approval-engine"}), tenants=frozenset({"*"}), may_write=False, may_read=True, ) app = IngestionApplication( SQLiteAuditBackend(str(tmp_path / "s.db")), SenderRegistry([_sender(), operator]) ) def misses(): status, body = invoke(app, "/v1/stream-findings", token="operator") assert status.startswith("200") return [f for f in body["stream_findings"] if f["kind"] == BELOW_CADENCE] assert [f["observed"] for f in misses()] == [0] for n in range(2): assert emit(app, f"evt-{n}", "approval.issued")[0].startswith("202") assert misses() == []