AUDIT-WP-0018-T03: report bounded-executor runs that started without a receipt
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 3s

A sender may declare closing_classes; nk-bounded-executor declares
execution.run.started closed by execution.attribution.receipt within
7200s. GET /v1/stream-findings reports opening_without_closing past the
bound, counted from the receiver's accepted_at plus the heartbeat grace
allowance; a per-run data.window.not_after tightens but never extends.
Identifiers and timing only. The overlay may add or shorten, never
remove, rebind or lengthen. Both backends: 403 passed (1 unrelated
live-catalog env failure).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 482098@bnt-lap001
Assistant-Session: 9643c95a-a398-4f4a-b56a-6119be4e845e
This commit is contained in:
tegwick 2026-11-03 22:04:14 +01:00
parent 9fb167871b
commit 2b8c9097ad
13 changed files with 654 additions and 6 deletions

View file

@ -246,6 +246,34 @@ class SQLiteAuditBackend:
sql += " GROUP BY action ORDER BY action"
return [{"class": action, "count": count} for action, count in self._query(sql, params)]
def unclosed_openings(
self, source: str, opening: str, closing: str, since: str, limit: int = 500
) -> list[dict]:
"""Opening-class events with no closing-class event on the same
``correlation_id`` (AUDIT-WP-0018-T03).
Returns identifiers and timing only, never payloads. An opening event
without a correlation id can never be closed and is returned as such.
"""
rows = self._query(
"SELECT o.event_id, o.correlation_id, o.accepted_at, "
"json_extract(o.record, '$.details.data.window.not_after') "
"FROM events o "
"WHERE json_extract(o.record, '$.source') = ? "
"AND json_extract(o.record, '$.action') = ? AND o.accepted_at >= ? "
"AND NOT EXISTS (SELECT 1 FROM events c "
"WHERE c.correlation_id = o.correlation_id "
"AND json_extract(c.record, '$.source') = ? "
"AND json_extract(c.record, '$.action') = ?) "
"ORDER BY o.accepted_at, o.event_id LIMIT ?",
(source, opening, since, source, closing, int(limit)),
)
return [
{"event_id": eid, "correlation_id": cid, "accepted_at": at,
"window_not_after": na if isinstance(na, str) else None}
for eid, cid, at, na in rows
]
def last_heartbeats(self, source: str) -> dict[str, str]:
"""Most recent heartbeat per class for one source."""
rows = self._query(