from dataclasses import replace from datetime import timedelta import pytest from kings_guard.cadence import HeartbeatCadence, load_qonto_assistant_source_cadence from kings_guard.contracts import EvidenceClass, StreamHeartbeat from kings_guard.stream import evaluate_stream NOW = "2026-10-15T12:00:00Z" def approval_cadence(): return replace( load_qonto_assistant_source_cadence(), source_system="approval-engine", heartbeats=tuple( HeartbeatCadence( "audit-core.heartbeat", name, EvidenceClass.LOAD_BEARING, timedelta(hours=24), "nothing-to-report", ) for name in ("issuance", "revocation") ), reconciliations=(), ) def assess(*beats): return evaluate_stream( (), approval_cadence(), now=NOW, watching_since="2026-10-12T12:00:00Z", heartbeats=beats ) def test_unscoped_shared_transport_heartbeat_cannot_vouch_for_transition_classes(): beat = StreamHeartbeat( "approval-engine", NOW, "audit-core.heartbeat", "nothing-to-report", counts={"issuance": 0, "revocation": 0}, ) result = assess(beat) assert "stream:heartbeat_missing:issuance" in result.findings assert "stream:heartbeat_missing:revocation" in result.findings def test_fresh_issuance_heartbeat_does_not_hide_stale_revocation(): fresh = StreamHeartbeat( "approval-engine", NOW, "audit-core.heartbeat", "nothing-to-report", covered_event_classes=("issuance",), ) stale = StreamHeartbeat( "approval-engine", "2026-10-12T12:00:00Z", "audit-core.heartbeat", "nothing-to-report", covered_event_classes=("revocation",), ) result = assess(stale, fresh) assert "stream:heartbeat_missing:revocation" in result.findings assert "stream:heartbeat_missing:issuance" not in result.findings def test_fresh_heartbeats_for_both_classes_satisfy_their_own_obligations(): beats = tuple( StreamHeartbeat( "approval-engine", NOW, "audit-core.heartbeat", "nothing-to-report", covered_event_classes=(name,), ) for name in ("issuance", "revocation") ) assert not assess(*beats).findings def test_explicit_coverage_cannot_override_wrong_transport_event_class(): beat = StreamHeartbeat( "approval-engine", NOW, "other.heartbeat", "nothing-to-report", covered_event_classes=("issuance", "revocation"), ) assert len(assess(beat).findings) == 2 @pytest.mark.parametrize( "coverage", ["revocation", ["revocation"], ("",), ("revocation", "revocation"), (None,)] ) def test_malformed_coverage_is_refused_instead_of_matching_by_substring(coverage): with pytest.raises(ValueError, match="coverage"): StreamHeartbeat( "approval-engine", NOW, "audit-core.heartbeat", "nothing-to-report", covered_event_classes=coverage, ) def test_qonto_source_adapter_publishes_its_shared_heartbeat_coverage(): from kings_guard.live import _confirm_stream counts = {"audit.allow": 0, "audit.deny": 0} event = { "stream_id": "qonto-assistant.audit", "stream_instance_id": "instance", "stream_sequence": 1, "event_class": "audit.heartbeat", "timestamp": NOW, "assertion": "nothing-to-report", "source_transition_counts": counts, } snapshot = { "stream_id": event["stream_id"], "stream_instance_id": "instance", "last_stream_sequence": 1, "source_transition_counts": counts, } beats, reconciliation, _, corrections = _confirm_stream([event], snapshot) assert not corrections assert beats[0].covered_event_classes == ("audit.allow", "audit.deny", "audit.heartbeat") result = evaluate_stream( (), load_qonto_assistant_source_cadence(), now=NOW, heartbeats=beats, reconciliation=reconciliation, ) assert not result.findings