kings-guard/tests/test_heartbeat_class_scope.py
tegwick f5bb0f434a Bind heartbeat observations to explicitly covered classes
Assistant: codex
Assistant-Model: gpt-6.1-sol
Assistant-Session: 01a13eab-1e6a-7b33-9c37-372e90ada3a2
2026-10-15 19:54:51 +02:00

138 lines
4.1 KiB
Python

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