Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a13eab-1e6a-7b33-9c37-372e90ada3a2
242 lines
10 KiB
Python
242 lines
10 KiB
Python
"""Offline assessment of owner-delivered deployed Qonto evidence (no clients)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import hashlib
|
|
import json
|
|
from collections import Counter
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
from kings_guard.cadence import parse_timestamp as _parse_timestamp
|
|
from kings_guard.live import _confirm_stream, _read_jsonl
|
|
|
|
|
|
def parse_timestamp(value: str):
|
|
if not isinstance(value, str) or not value.strip():
|
|
raise ValueError("timestamp must be a timezone-qualified string")
|
|
from datetime import datetime
|
|
|
|
if datetime.fromisoformat(value.replace("Z", "+00:00")).tzinfo is None:
|
|
raise ValueError("timestamp must include a timezone")
|
|
return _parse_timestamp(value)
|
|
|
|
|
|
def assess_capture(
|
|
events: list[dict[str, Any]],
|
|
snapshot: dict[str, Any],
|
|
receiver: dict[str, Any],
|
|
receipts: list[dict[str, Any]],
|
|
*,
|
|
source: str,
|
|
tenant: str,
|
|
frame_seconds: int,
|
|
now: str,
|
|
) -> dict[str, Any]:
|
|
"""Assess actual supplied evidence; caller provenance remains an owner claim.
|
|
|
|
Receiver time bounds are acceptance time, while source sequence bounds run
|
|
from startup to snapshot. Requiring one receipt per sequence links them.
|
|
"""
|
|
if type(frame_seconds) is not int or frame_seconds <= 0:
|
|
raise ValueError("frame_seconds must be a positive integer")
|
|
if not source or not tenant:
|
|
raise ValueError("exact source and tenant are required")
|
|
_, _, notes, corrections = _confirm_stream(events, snapshot)
|
|
findings = list(corrections)
|
|
now_dt = parse_timestamp(now)
|
|
instance = snapshot["stream_instance_id"]
|
|
times = [parse_timestamp(event["timestamp"]) for event in events]
|
|
snapshot_at = parse_timestamp(snapshot["snapshot_at"])
|
|
if times != sorted(times) or times[-1] > snapshot_at or snapshot_at > now_dt:
|
|
findings.append("source timestamps are unordered or outside snapshot/evaluation bounds")
|
|
beats = [event for event in events if event["event_class"] == "audit.heartbeat"]
|
|
if not beats or beats[0].get("reason") != "startup" or events[0] != beats[0]:
|
|
findings.append("capture does not start with the source startup heartbeat")
|
|
periodic = [event for event in beats if event.get("reason") == "periodic"]
|
|
if (
|
|
not periodic
|
|
or not beats
|
|
or not any(
|
|
(parse_timestamp(event["timestamp"]) - times[0]).total_seconds() >= frame_seconds
|
|
for event in periodic
|
|
)
|
|
):
|
|
findings.append("a real periodic heartbeat after one full frame is missing")
|
|
beat_times = [parse_timestamp(event["timestamp"]) for event in beats]
|
|
if any(
|
|
(b - a).total_seconds() > 2 * frame_seconds
|
|
for a, b in zip(beat_times, beat_times[1:], strict=False)
|
|
):
|
|
findings.append("source heartbeat gap exceeds registered two-frame wait")
|
|
if not any(event["event_class"] in {"audit.allow", "audit.deny"} for event in events):
|
|
findings.append("capture has no request transition")
|
|
if not any(event["event_class"] == "audit.deny" for event in events):
|
|
findings.append("capture has no denial transition")
|
|
stale = not beat_times or (now_dt - beat_times[-1]).total_seconds() > 3 * frame_seconds
|
|
if stale:
|
|
findings.append("process heartbeat is stale beyond effective three-frame deadline")
|
|
if any(
|
|
receiver.get(key) != value
|
|
for key, value in (("source", source), ("tenant", tenant), ("stream_instance_id", instance))
|
|
):
|
|
findings.append("receiver source/tenant/instance binding does not match")
|
|
since, until = parse_timestamp(receiver["since"]), parse_timestamp(receiver["until"])
|
|
if since > until or until > now_dt:
|
|
findings.append("receiver acceptance window is invalid or in the future")
|
|
rows = receiver.get("counts", [])
|
|
received: dict[str, int] = {}
|
|
for row in rows:
|
|
key, value = row["class"], row["count"]
|
|
if key in received or type(value) is not int or value < 0:
|
|
raise ValueError("receiver counts must be unique nonnegative integers")
|
|
received[key] = value
|
|
expected = Counter(
|
|
"audit-core.heartbeat" if e["event_class"] == "audit.heartbeat" else e["event_class"]
|
|
for e in events
|
|
)
|
|
if {k: v for k, v in received.items() if v} != dict(expected):
|
|
findings.append("receiver accepted counts diverge from captured instance")
|
|
by_sequence: dict[int, dict[str, Any]] = {}
|
|
ids: set[str] = set()
|
|
lags: list[float] = []
|
|
for receipt in receipts:
|
|
sequence = receipt["stream_sequence"]
|
|
if type(sequence) is not int or sequence in by_sequence or not 1 <= sequence <= len(events):
|
|
raise ValueError("receipt sequences must be unique and within the source capture")
|
|
by_sequence[sequence] = receipt
|
|
event = events[sequence - 1]
|
|
expected_type = (
|
|
"audit-core.heartbeat"
|
|
if event["event_class"] == "audit.heartbeat"
|
|
else event["event_class"]
|
|
)
|
|
accepted_at = parse_timestamp(receipt["accepted_at"])
|
|
event_id = receipt.get("event_id")
|
|
if not event_id or event_id in ids:
|
|
findings.append("receipt event ID is missing or duplicated")
|
|
ids.add(event_id)
|
|
if (
|
|
receipt.get("stream_instance_id") != instance
|
|
or receipt.get("source") != source
|
|
or receipt.get("tenant") != tenant
|
|
or receipt.get("type") != expected_type
|
|
or receipt.get("status_code") not in (200, 202)
|
|
or not since <= accepted_at <= until
|
|
):
|
|
findings.append("receipt binding/status/acceptance window is invalid")
|
|
if event["event_class"] == "audit.heartbeat" and receipt.get("covered_event_classes") != [
|
|
"audit.deny"
|
|
]:
|
|
findings.append("heartbeat receipt must retain exact audit.deny coverage")
|
|
lag = (accepted_at - times[sequence - 1]).total_seconds()
|
|
if receipt.get("status_code") != 202:
|
|
findings.append("first-acceptance 202 receipt is required to assess delivery lag")
|
|
else:
|
|
lags.append(lag)
|
|
if lag < 0 or lag > frame_seconds:
|
|
findings.append("delivery lag exceeds one frame or clocks are inconsistent")
|
|
if set(by_sequence) != set(range(1, len(events) + 1)):
|
|
findings.append("durable acceptance receipt missing for source sequence")
|
|
findings = list(dict.fromkeys(findings))
|
|
return {
|
|
"schema_version": "kings-guard-capture-assessment/v1",
|
|
"result": "pass" if not findings else "fail",
|
|
"findings": findings,
|
|
"source": source,
|
|
"tenant": tenant,
|
|
"stream_instance_id": instance,
|
|
"frame_seconds": frame_seconds,
|
|
"effective_deadline_seconds": 3 * frame_seconds,
|
|
"evaluated_at": now,
|
|
"max_delivery_lag_upper_bound_seconds": max(lags, default=None),
|
|
"source_event_count": len(events),
|
|
"notes": list(notes),
|
|
"scope": (
|
|
"supplied deployed capture only; provenance and missing-heartbeat "
|
|
"injection are separate owner receipts"
|
|
),
|
|
"means": (
|
|
"Receipt time bounds acceptance from above; count agreement does not "
|
|
"prove completeness or omitted event absence."
|
|
),
|
|
}
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
for name in ("events", "snapshot", "receiver", "receipts"):
|
|
parser.add_argument(f"--{name}", required=True, type=Path)
|
|
parser.add_argument("--source", required=True)
|
|
parser.add_argument("--tenant", required=True)
|
|
parser.add_argument("--frame-seconds", required=True, type=int)
|
|
parser.add_argument("--now", required=True, help="Explicit timezone-qualified observation time")
|
|
args = parser.parse_args()
|
|
try:
|
|
result = assess_capture(
|
|
_read_jsonl(args.events),
|
|
json.loads(args.snapshot.read_text()),
|
|
json.loads(args.receiver.read_text()),
|
|
_read_jsonl(args.receipts),
|
|
source=args.source,
|
|
tenant=args.tenant,
|
|
frame_seconds=args.frame_seconds,
|
|
now=args.now,
|
|
)
|
|
result["input_sha256"] = {
|
|
name: hashlib.sha256(getattr(args, name).read_bytes()).hexdigest()
|
|
for name in ("events", "snapshot", "receiver", "receipts")
|
|
}
|
|
except (ValueError, KeyError, TypeError, IndexError, OSError) as exc:
|
|
result = {"result": "invalid", "error": str(exc)}
|
|
print(json.dumps(result, indent=2, sort_keys=True))
|
|
return 0 if result["result"] == "pass" else 1
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|
|
|
|
|
|
def assess_class_silence(
|
|
*,
|
|
source: str,
|
|
sender: str,
|
|
registered_wait_seconds: dict[str, int],
|
|
last_seen: dict[str, str | None],
|
|
findings: list[dict[str, Any]],
|
|
now: str,
|
|
) -> dict[str, Any]:
|
|
"""Compare owner-provided receiver observations with independent class clocks.
|
|
|
|
This cannot attest that supplied observations came from production. Each
|
|
class needs an explicit last-seen entry (None is an observed never-seen
|
|
value). Healthy unrelated classes cannot cover a missing one.
|
|
"""
|
|
now_dt = parse_timestamp(now)
|
|
expected: set[str] = set()
|
|
for event_class, wait in registered_wait_seconds.items():
|
|
if type(wait) is not int or wait <= 0 or event_class not in last_seen:
|
|
raise ValueError("each registered class needs a positive wait and explicit last_seen")
|
|
value = last_seen[event_class]
|
|
stamp = parse_timestamp(value) if value is not None else None
|
|
if stamp is not None and stamp > now_dt:
|
|
raise ValueError("heartbeat last_seen cannot be in the future")
|
|
if stamp is None or (now_dt - stamp).total_seconds() > wait * 1.5:
|
|
expected.add(event_class)
|
|
actual = {
|
|
item["class"]
|
|
for item in findings
|
|
if item.get("source") == source
|
|
and item.get("sender") == sender
|
|
and item.get("kind") in {"missing_heartbeat", "no_heartbeat_since_registration"}
|
|
}
|
|
return {
|
|
"result": "pass" if actual == expected else "fail",
|
|
"source": source,
|
|
"sender": sender,
|
|
"evaluated_at": now,
|
|
"expected_missing_classes": sorted(expected),
|
|
"actual_missing_classes": sorted(actual),
|
|
"means": "Independent evaluation of supplied class clocks; no suppression inference.",
|
|
}
|