kings-guard/tests/test_stream_admission.py
tegwick 31e9963933 Admit source evidence snapshots and harden stream completeness
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a06e89-93a2-7aa2-82b3-ce5ccd2682e6
2026-09-05 00:42:19 +02:00

88 lines
3.5 KiB
Python

from copy import deepcopy
import pytest
from kings_guard.cadence import load_qonto_assistant_source_cadence
from kings_guard.contracts import ReconciliationView, StreamCompleteness, StreamHeartbeat
from kings_guard.live import _confirm_stream
from kings_guard.stream import evaluate_stream
NOW = "2026-09-05T12:00:00Z"
COUNTS = {"audit.allow": 0, "audit.deny": 0}
EVENT = {
"stream_id": "qonto.audit", "stream_instance_id": "instance-1", "stream_sequence": 1,
"event_class": "audit.heartbeat", "timestamp": NOW, "assertion": "nothing-to-report",
"source_transition_counts": COUNTS,
}
SNAPSHOT = {
"stream_id": "qonto.audit", "stream_instance_id": "instance-1",
"last_stream_sequence": 1, "source_transition_counts": COUNTS,
}
@pytest.mark.parametrize("change", [
{"stream_instance_id": "instance-2"}, {"stream_id": "other.audit"},
{"last_stream_sequence": 2}, {"last_stream_sequence": True},
])
def test_reconciliation_must_belong_to_captured_stream(change):
assert _confirm_stream([EVENT], {**SNAPSHOT, **change})[-1]
@pytest.mark.parametrize("sequence", [None, True, "1", -1])
def test_malformed_sequences_are_rejected(sequence):
with pytest.raises(ValueError, match="stream_sequence"):
_confirm_stream([{**EVENT, "stream_sequence": sequence}], SNAPSHOT)
def test_empty_capture_is_rejected_without_index_error():
with pytest.raises(ValueError, match="empty"):
_confirm_stream([], SNAPSHOT)
def test_missing_counts_cannot_be_normalized_to_zero():
snapshot = deepcopy(SNAPSHOT)
snapshot["source_transition_counts"] = {"audit.allow": 0}
with pytest.raises(ValueError, match="counts"):
_confirm_stream([EVENT], snapshot)
@pytest.mark.parametrize("counts", [{}, {"audit.deny": -1}, {"audit.deny": True}])
def test_absent_or_invalid_reconciliation_counts_leave_completeness_unknown(counts):
result = evaluate_stream(
(), load_qonto_assistant_source_cadence(), now=NOW,
heartbeats=[StreamHeartbeat("qonto-assistant", NOW, "audit.heartbeat",
"nothing-to-report")],
reconciliation=ReconciliationView(counts, counts),
)
assert result.completeness is StreamCompleteness.UNKNOWN
@pytest.mark.parametrize(("source", "timestamp"), [
("other-source", NOW), ("qonto-assistant", "2026-09-07T12:00:00Z"),
])
def test_unrelated_or_future_heartbeat_cannot_satisfy_cadence(source, timestamp):
result = evaluate_stream(
(), load_qonto_assistant_source_cadence(), now=NOW,
watching_since="2026-09-03T12:00:00Z",
heartbeats=[StreamHeartbeat(source, timestamp, "audit.heartbeat", "nothing-to-report")],
reconciliation=ReconciliationView(COUNTS, COUNTS),
)
assert result.completeness is StreamCompleteness.DEGRADED
assert "stream:heartbeat_missing:audit.deny" in result.findings
def test_excess_evidence_is_also_reconciliation_divergence():
result = evaluate_stream(
(), load_qonto_assistant_source_cadence(), now=NOW,
heartbeats=[StreamHeartbeat("qonto-assistant", NOW, "audit.heartbeat",
"nothing-to-report")],
reconciliation=ReconciliationView({"audit.deny": 0}, {"audit.deny": 1}),
)
assert "stream:reconciliation_divergence:audit.deny" in result.findings
def test_heartbeat_counts_must_match_events_already_captured():
event = {**EVENT, "source_transition_counts": {"audit.allow": 0, "audit.deny": 1}}
assert "heartbeat counts do not match preceding request events" in (
_confirm_stream([event], SNAPSHOT)[-1]
)