Admit source evidence snapshots and harden stream completeness

Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a06e89-93a2-7aa2-82b3-ce5ccd2682e6
This commit is contained in:
tegwick 2026-09-05 00:42:19 +02:00
parent 824fb1b966
commit 31e9963933
34 changed files with 1057 additions and 122 deletions

View file

@ -1,7 +1,12 @@
from pathlib import Path
import pytest
from kings_guard.cadence import load_qonto_assistant_source_cadence
ROOT = Path(__file__).resolve().parents[1]
DRAFT = ROOT / "specs" / "EmissionCadenceDeclaration.md"
QONTO_SOURCE_DECLARATION = Path("/home/worsch/qonto-assistant/specs/audit-emission-cadence.yaml")
def test_emission_cadence_draft_exists_for_taxonomy_handover() -> None:
@ -17,3 +22,62 @@ def test_emission_cadence_draft_exists_for_taxonomy_handover() -> None:
assert "reconciliation" in text.lower()
assert "alongside the security genome" in text.lower() or "alongside the genome" in text.lower()
assert "security_genome" in text
def test_qonto_source_declaration_is_consumed_separately_from_the_taxonomy_draft() -> None:
cadence = load_qonto_assistant_source_cadence()
assert cadence.status == "source-declared"
assert cadence.owner == "qonto-assistant"
assert cadence.reference_instance == "QONTO-WP-0005"
assert cadence.forms() == {"heartbeat-or-reconciliation"}
assert cadence.rates == ()
assert cadence.heartbeats[0].interval.total_seconds() == 86400
@pytest.mark.skipif(
not QONTO_SOURCE_DECLARATION.is_file(),
reason="qonto-assistant source declaration checkout is unavailable",
)
def test_qonto_source_cadence_fixture_matches_the_source_owned_declaration() -> None:
yaml = pytest.importorskip("yaml")
source = yaml.safe_load(QONTO_SOURCE_DECLARATION.read_text(encoding="utf-8"))[
"audit_emission_cadence"
]
cadence = load_qonto_assistant_source_cadence()
assert cadence.source_system == source["source"]
assert cadence.heartbeats[0].event_class == source["heartbeat"]["event_class"]
assert (
cadence.heartbeats[0].interval.total_seconds()
== source["heartbeat"]["default_interval_seconds"]
)
assert source["event_classes"]["audit.deny"]["form"] in cadence.forms()
assert source["event_classes"]["audit.allow"]["completeness_claimed"] is False
assert cadence.rates == ()
@pytest.mark.parametrize("filename", [
"qonto_assistant_cadence.json", "qonto_assistant_source_cadence.json",
])
def test_cadence_fixtures_conform_to_owner_schema(filename):
import json
import jsonschema
import yaml
schema_path = Path(
"/home/worsch/info-tech-canon/infospace/schemas/emission-cadence.schema.yaml"
)
if not schema_path.is_file():
pytest.skip("canonical InfoTechCanon schema checkout is unavailable")
schema = yaml.safe_load(schema_path.read_text())
payload = json.loads((ROOT / "src/kings_guard/fixtures" / filename).read_text())
jsonschema.Draft202012Validator(schema).validate(payload)
@pytest.mark.parametrize(("value", "seconds"), [("P1DT2H30M5S", 95405), ("P1D", 86400)])
def test_canonical_compound_durations_are_consumed(value, seconds):
from kings_guard.cadence import parse_interval
assert parse_interval(value).total_seconds() == seconds

View file

@ -1,3 +1,5 @@
from helpers import load_pilot, observation_from_fixture
from kings_guard.cadence import load_qonto_assistant_cadence
from kings_guard.contracts import (
PostureAssessment,
@ -9,8 +11,6 @@ from kings_guard.contracts import (
)
from kings_guard.posture import PostureEvaluator
from helpers import load_pilot, observation_from_fixture
def _healthy_assessment(*, completeness: StreamCompleteness, confidence: int) -> PostureAssessment:
return PostureAssessment(
@ -33,7 +33,10 @@ def test_completeness_is_separated_from_record_richness() -> None:
assert evaluation.assessment.confidence_score >= 85
assert evaluation.assessment.stream_completeness is StreamCompleteness.UNKNOWN
assert "record" in evaluation.assessment.completeness_reason
assert "cannot vouch" in evaluation.assessment.rationale or "unknown" in evaluation.assessment.rationale
assert (
"cannot vouch" in evaluation.assessment.rationale
or "unknown" in evaluation.assessment.rationale
)
def test_unmet_cadence_degrades_completeness_and_says_so_in_words() -> None:
@ -47,7 +50,9 @@ def test_unmet_cadence_degrades_completeness_and_says_so_in_words() -> None:
now="2026-07-24T10:10:00Z",
watching_since="2026-07-22T09:10:00Z",
heartbeats=(),
reconciliation=ReconciliationView(source_counts={"audit.deny": 1}, evidence_counts={"audit.deny": 1}),
reconciliation=ReconciliationView(
source_counts={"audit.deny": 1}, evidence_counts={"audit.deny": 1}
),
)
assert evaluation.assessment.stream_completeness is StreamCompleteness.DEGRADED
@ -56,12 +61,8 @@ def test_unmet_cadence_degrades_completeness_and_says_so_in_words() -> None:
def test_incomplete_stream_is_never_more_trustworthy_than_a_complete_one() -> None:
rich_incomplete = _healthy_assessment(
completeness=StreamCompleteness.DEGRADED, confidence=95
)
sparse_complete = _healthy_assessment(
completeness=StreamCompleteness.COMPLETE, confidence=70
)
rich_incomplete = _healthy_assessment(completeness=StreamCompleteness.DEGRADED, confidence=95)
sparse_complete = _healthy_assessment(completeness=StreamCompleteness.COMPLETE, confidence=70)
unknown = _healthy_assessment(completeness=StreamCompleteness.UNKNOWN, confidence=95)
assert assessment_trust_key(rich_incomplete) < assessment_trust_key(sparse_complete)
@ -103,7 +104,9 @@ def test_complete_stream_keeps_richness_and_states_completeness() -> None:
counts={"audit.deny": 1},
),
),
reconciliation=ReconciliationView(source_counts={"audit.deny": 1}, evidence_counts={"audit.deny": 1}),
reconciliation=ReconciliationView(
source_counts={"audit.deny": 1}, evidence_counts={"audit.deny": 1}
),
)
assert evaluation.assessment.stream_completeness is StreamCompleteness.COMPLETE

View file

@ -1,8 +1,8 @@
from helpers import load_pilot, observation_from_fixture
from kings_guard.contracts import PostureLevel, SecurityGenome, as_jsonable
from kings_guard.posture import PostureEvaluator
from helpers import load_pilot, observation_from_fixture
def test_qonto_fixture_loads_a_normalized_genome() -> None:
fixture = load_pilot()
@ -25,6 +25,18 @@ def test_qonto_audit_event_normalizes_to_immune_observation() -> None:
assert observation.deny_reason == "credential_exfil"
def test_qonto_source_identity_and_egress_override_legacy_mapping_hints() -> None:
fixture = load_pilot()
event = dict(fixture.audit_event)
event["identity_binding"] = "key_cape_jwt"
event["egress_destination"] = "qonto-proxy"
observation = observation_from_fixture(fixture, event)
assert observation.identity_binding == "key_cape_jwt"
assert observation.egress_destination == "qonto-proxy"
def test_posture_evaluation_is_jsonable() -> None:
fixture = load_pilot()
observation = observation_from_fixture(fixture)
@ -34,3 +46,11 @@ def test_posture_evaluation_is_jsonable() -> None:
assert payload["assessment"]["posture"] == PostureLevel.INFLAMED.value
assert payload["signals"][0]["signal_kind"] == "posture_hint"
assert payload["assessment"]["stream_completeness"] == "unknown"
def test_explicitly_missing_source_context_does_not_recover_legacy_hints() -> None:
fixture = load_pilot()
event = dict(fixture.audit_event, identity_binding=None, egress_destination=None)
observation = observation_from_fixture(fixture, event)
assert observation.identity_binding is None
assert observation.egress_destination is None

View file

@ -1,6 +1,6 @@
from kings_guard.contracts import StreamCompleteness
from helpers import load_pilot, observation_from_fixture
from kings_guard.contracts import StreamCompleteness
from kings_guard.posture import PostureEvaluator

View file

@ -1,10 +1,9 @@
import pytest
from helpers import load_pilot, observation_from_fixture
from kings_guard.adapters import observation_from_audit_event
from kings_guard.contracts import DeclaredEvidenceSource, EvidenceClass
from helpers import load_pilot, observation_from_fixture
def test_genome_declares_both_evidence_classes() -> None:
genome = load_pilot().genome

View file

@ -5,6 +5,7 @@ Tooling-layer client. A test that only ran the checker against a clean tree
would prove nothing it would pass just as happily if the checker were broken.
So the negative case is exercised too, on a synthetic tree.
"""
from __future__ import annotations
import subprocess

View file

@ -1,12 +1,13 @@
from __future__ import annotations
import pytest
from helpers import load_pilot, observation_from_fixture
from kings_guard.cadence import load_qonto_assistant_source_cadence
from kings_guard.contracts import StreamCompleteness
from kings_guard.live import capture_qonto_assistant_events, qonto_assistant_available
from kings_guard.posture import PostureEvaluator
from helpers import load_pilot, observation_from_fixture
pytestmark = pytest.mark.skipif(
not qonto_assistant_available(),
reason="qonto-assistant checkout is required for live observation",
@ -28,7 +29,10 @@ def test_real_emitted_qonto_events_reach_the_evaluator() -> None:
assert "allow" in decisions
assert "deny" in decisions
assert capture.mapping_notes
assert all(observation.source_system == "qonto-assistant" for observation in capture.observations)
assert capture.stream_notes
assert all(
observation.source_system == "qonto-assistant" for observation in capture.observations
)
deny = next(item for item in capture.observations if item.decision.value == "deny")
evaluation = PostureEvaluator().evaluate(fixture.genome, deny)
@ -39,8 +43,25 @@ def test_real_emitted_qonto_events_reach_the_evaluator() -> None:
for request in signal.effector_requests:
assert request.authority_boundary in {"advisory_only", "metadata_only"}
# Mapping confirmed against the real emit path; remaining gaps are source omissions,
# not adapter drift.
assert any("identity_binding" in item for item in capture.corrections_for_source)
assert any("egress_destination" in item for item in capture.corrections_for_source)
assert not any("mapping drifted" in item for item in capture.corrections_for_source)
# The source now emits identity binding and egress directly. The adapter must
# preserve those values instead of masking them with its legacy mapping hints.
for event, observation in zip(capture.events, capture.observations, strict=True):
assert observation.identity_binding == event["identity_binding"]
assert observation.egress_destination == event["egress_destination"]
assert capture.corrections_for_source == ()
assert capture.stream_corrections == ()
cadence = load_qonto_assistant_source_cadence()
stream_evaluation = PostureEvaluator().evaluate_with_stream(
fixture.genome,
deny,
cadence,
now=capture.heartbeats[-1].timestamp,
watching_since=capture.heartbeats[0].timestamp,
observations=capture.observations,
heartbeats=capture.heartbeats,
reconciliation=capture.reconciliation,
)
assert stream_evaluation.assessment.stream_completeness is StreamCompleteness.COMPLETE
assert stream_evaluation.stream is not None
assert stream_evaluation.stream.findings == ()

View file

@ -1,7 +1,7 @@
from kings_guard.posture import PostureEvaluator
from helpers import load_pilot, observation_from_fixture
from kings_guard.posture import PostureEvaluator
def test_qonto_pilot_produces_inflamed_posture_with_tolerance_context() -> None:
fixture = load_pilot()

View file

@ -0,0 +1,83 @@
from copy import deepcopy
from dataclasses import asdict
import pytest
from kings_guard.adapters.secrets_engine import snapshot_from_engine
from kings_guard.contracts import StreamCompleteness
NOW = "2026-09-05T12:00:00Z"
PAYLOAD = {
"surface": "secret-use-evidence", "as_of": NOW, "completeness_claimed": False,
"cadence": {"form": "heartbeat", "interval": "1d"},
"lanes": [{"as_of": NOW, "catalog_id": "lane-1", "stage": "test", "kind": "kv"}],
}
BINDINGS = {"lane-1": ("tenant-1", "subject-1")}
def admit(payload, now=NOW, bindings=BINDINGS):
return snapshot_from_engine(payload, lane_bindings=bindings, now=now)
def test_catalog_only_snapshot_does_not_invent_evidence_or_completeness():
result = admit(PAYLOAD)
assert result.lanes[0].ready is None
assert result.lanes[0].revocation_succeeded is None
assert result.lanes[0].evidence_kind is None
assert result.completeness is StreamCompleteness.UNKNOWN
assert result.heartbeat_interval.total_seconds() == 86400
def test_evidence_preserves_false_and_drops_authorization_and_sensitive_fields():
payload = deepcopy(PAYLOAD)
payload["lanes"][0].update(ready=False, revocation_succeeded=False,
decision_id="dec-1", session_handle="session-1",
secret="never-retained", lifecycle_operation="revoke")
result = admit(payload)
assert result.lanes[0].ready is False
assert result.lanes[0].revocation_succeeded is False
assert result.lanes[0].lifecycle_operation == "revoke"
fields = asdict(result.lanes[0])
assert {"decision_id", "session_handle", "secret"}.isdisjoint(fields)
assert result.completeness is StreamCompleteness.UNKNOWN
@pytest.mark.parametrize(("now", "findings"), [
("2026-09-06T12:00:00Z", ()),
("2026-09-06T12:00:01Z", ("snapshot:stale",)),
("2026-09-05T11:59:59Z", ("snapshot:future_timestamp",)),
])
def test_freshness_does_not_upgrade_completeness(now, findings):
result = admit(PAYLOAD, now)
assert result.findings == findings
assert result.completeness is StreamCompleteness.UNKNOWN
def test_unbound_or_duplicate_lanes_cannot_cross_scope():
with pytest.raises(ValueError, match="unbound"):
admit(PAYLOAD, bindings={})
payload = deepcopy(PAYLOAD)
payload["lanes"].append(payload["lanes"][0])
with pytest.raises(ValueError, match="duplicate"):
admit(payload)
@pytest.mark.parametrize("change", [
{"completeness_claimed": True}, {"completeness_claimed": None},
{"cadence": {"form": "heartbeat", "interval": "0s"}},
{"surface": "authorization"}, {"lanes": None},
])
def test_invalid_envelope_is_rejected(change):
with pytest.raises(ValueError):
admit({**PAYLOAD, **change})
@pytest.mark.parametrize("change", [
{"ready": "false"}, {"revocation_attempted": 0},
{"as_of": "2026-09-04T12:00:00Z"}, {"evidence_kind": "guessed"},
])
def test_invalid_lane_is_rejected(change):
payload = deepcopy(PAYLOAD)
payload["lanes"][0].update(change)
with pytest.raises(ValueError):
admit(payload)

View file

@ -1,3 +1,5 @@
from helpers import load_pilot, observation_from_fixture
from kings_guard.cadence import load_qonto_assistant_cadence
from kings_guard.contracts import (
ReconciliationView,
@ -7,8 +9,6 @@ from kings_guard.contracts import (
)
from kings_guard.posture import PostureEvaluator, is_stream_finding
from helpers import load_pilot, observation_from_fixture
def test_cadence_draft_covers_both_forms_against_qonto() -> None:
cadence = load_qonto_assistant_cadence()
@ -36,7 +36,9 @@ def test_unmet_declared_rate_is_a_stream_finding() -> None:
counts={"audit.deny": 0},
),
),
reconciliation=ReconciliationView(source_counts={"audit.deny": 0}, evidence_counts={"audit.deny": 0}),
reconciliation=ReconciliationView(
source_counts={"audit.deny": 0}, evidence_counts={"audit.deny": 0}
),
)
assert any(item.startswith("stream:cadence_unmet:audit.allow") for item in stream.findings)
@ -54,7 +56,9 @@ def test_missing_heartbeat_is_a_stream_finding() -> None:
now="2026-07-24T10:10:00Z",
watching_since="2026-07-22T09:10:00Z",
heartbeats=(),
reconciliation=ReconciliationView(source_counts={"audit.deny": 1}, evidence_counts={"audit.deny": 1}),
reconciliation=ReconciliationView(
source_counts={"audit.deny": 1}, evidence_counts={"audit.deny": 1}
),
)
assert any(item.startswith("stream:heartbeat_missing:audit.deny") for item in stream.findings)
@ -103,7 +107,9 @@ def test_stream_findings_are_distinguishable_from_content_findings() -> None:
now="2026-07-24T10:10:00Z",
watching_since="2026-07-22T09:10:00Z",
heartbeats=(),
reconciliation=ReconciliationView(source_counts={"audit.deny": 1}, evidence_counts={"audit.deny": 1}),
reconciliation=ReconciliationView(
source_counts={"audit.deny": 1}, evidence_counts={"audit.deny": 1}
),
)
content_findings = set(evaluation.assessment.findings)
@ -127,7 +133,9 @@ def test_heartbeat_not_yet_due_does_not_false_alarm() -> None:
now=observation.timestamp,
watching_since=observation.timestamp,
heartbeats=(),
reconciliation=ReconciliationView(source_counts={"audit.deny": 1}, evidence_counts={"audit.deny": 1}),
reconciliation=ReconciliationView(
source_counts={"audit.deny": 1}, evidence_counts={"audit.deny": 1}
),
)
assert not any(item.startswith("stream:heartbeat_missing") for item in stream.findings)

View file

@ -0,0 +1,88 @@
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]
)