feat(audit): publish sequenced heartbeat and reconciliation evidence

Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a06ec5-7e2b-7743-ac08-719e1b0f42e2
This commit is contained in:
tegwick 2026-09-05 01:39:48 +02:00
parent fce60099c4
commit b9349782f4
32 changed files with 839 additions and 111 deletions

View file

@ -195,11 +195,11 @@ effect of how it was built, not extra work:
recovery procedure with no data-loss risk (`maximum_data_loss: PT0S`
applies trivially since there is no persisted data).
## 9. Kings Guard mapping (prep now, cheap; enforcement later)
## 9. King's Guard observation mapping
Nothing below requires `kings-guard` to exist yet. It is preparation so
`qonto-assistant` needs zero rework once a sentinel-mesh or decision plane
does exist.
King's Guard has observed the structured allow/deny stream. The source keeps
ownership of its genome and emission claims; the observer independently checks
the records and completeness evidence it receives.
### 9.1 Security Genome record
@ -210,21 +210,29 @@ nothing else), data classification, and recovery expectations.
### 9.2 Audit stream is already observation-shaped
`AuditLogger`'s existing event shape (actor, capability, decision,
deny_reason, latency, upstream HTTP status, policy version — see
`tests/test_audit.py`, `tests/test_audit_parity.py`) already matches Kings
Guard's Immune Observation contract closely enough that no schema rework
should be needed later — just a new consumer pointed at the same stream.
`AuditLogger`'s event shape includes actor, capability, decision, deny reason,
latency, upstream HTTP status, policy version, identity binding, and egress
destination (see `tests/test_audit.py`, `tests/test_audit_parity.py`). Every
record also carries a process-instance id and monotonic stream sequence.
The source-owned declaration in `specs/audit-emission-cadence.yaml` classifies
`audit.deny` as load-bearing and low-volume. A startup heartbeat, periodic
heartbeat (24 hours by default), and best-effort shutdown heartbeat publish
cumulative and window transition counts. Consumers compare those counts and
sequence continuity with observed records. `/v1/audit/reconciliation` exposes
the same non-secret current source counts for an authenticated diagnostic read.
### 9.3 A concrete, actionable-today signal
This does not require any Kings Guard component: repeated
`arg_constraint` or `credential_exfil` deny reasons from the same actor
within a short window is a real, current signal. It should trip a
tightened rate limit or a temporary lockout for that actor now, using the
audit stream and rate limiter that already exist — this is a Fast Local
Loop (`NetKingdomImmuneArchitecture.md` §14.1) response that does not need
to wait for any future component.
tightened rate limit or a temporary lockout for that actor now. The current
`DenyEscalationTracker` branches directly on the same decision path that emits
the audit event; it does not consume the audit stream. This is a Fast Local Loop
(`NetKingdomImmuneArchitecture.md` §14.1) response that does not need to wait
for any future component, while the stream remains load-bearing for estate
observation rather than for the local lockout.
---
@ -235,12 +243,12 @@ to wait for any future component.
| Security Genome record | `qonto-assistant` | can ship now |
| Audit-stream shape review against Immune Observation contract | `qonto-assistant` | can ship now |
| Actor lockout on repeated deny signals | `qonto-assistant` | can ship now |
| `key-cape` token verification in place of bearer token | `qonto-assistant` + `key-cape` | needs `key-cape` client integration support |
| `finance.qonto.read` resource + live decision call | `qonto-assistant` + `flex-auth` | needs the resource registered in `flex-auth` |
| Live tenant-role gate | `qonto-assistant` + `tenant-engine` | needs a `tenant-engine` lookup/cache API call wired in |
| `key-cape` token verification in place of bearer token | `qonto-assistant` + `key-cape` | verifier shipped; live enforcement is runtime-configured |
| `finance.qonto.read` resource + live decision call | `qonto-assistant` + `flex-auth` | client and policy shipped; live use is runtime-configured |
| Live tenant-role gate | `qonto-assistant` + `tenant-engine` | client shipped; live use requires a reachable deployment URL |
| Facade / scale-to-zero activator | new component (home TBD — Railiance or a dedicated repo) | design only so far |
| I1/I2 isolation placement on `railiance01` | Railiance | needs a placement decision |
| Sentinel-mesh / decision-plane consumption of the audit stream | `kings-guard` | does not exist yet — see intake |
| Audit-stream observation and completeness acceptance | `kings-guard` | consuming events; cadence/reconciliation acceptance pending |
Tracked as `QONTO-WP-0004` in this repo, with the `kings-guard`-owned
portion tracked as an intake against `KG-WP-0002` (pilot-lane selection).

View file

@ -9,6 +9,7 @@ Phase 1 ships:
- policy-gated `GET /v1/transactions`
- policy-gated `GET /v1/snapshot`
- structured audit events without secrets
- sequenced audit heartbeat and reconciliation evidence
- env-backed or OpenBao-CLI-backed credential loading
Spend, transfer, card, invoicing, payment-link, and other volume-cost actions
@ -207,6 +208,32 @@ That consumer-side write remains outside this repo.
the current dogfood path remains `legacy_api_key` because that is the proven
BINKY-WP-0005 header mode.
## Audit deny-stream reconciliation
`audit.deny` is a low-volume, load-bearing observation class. Do not infer
completeness from a minimum event rate. The service emits `audit.heartbeat` at
startup, every `QONTO_AUDIT_HEARTBEAT_INTERVAL_SECONDS` while the process is
active (default `86400`), and best-effort at shutdown. Each request event and
heartbeat carries `stream_instance_id` plus a monotonic `stream_sequence`.
For each instance, compare received `audit.allow` / `audit.deny` totals with
the heartbeat's `source_transition_counts` and reject sequence gaps. Window
counts describe transitions since the prior heartbeat. A quiet deny window has
`assertion: nothing-to-report`; a non-quiet one has
`assertion: transitions-reported`. Counters are process-local and reset only
when `stream_instance_id` changes.
The authenticated diagnostic view is:
```text
GET /v1/audit/reconciliation
```
It returns only stream identity, timestamps, sequence, and counts. It does not
write State Hub, query an observer, include actor/bank data, or increment the
stream it describes. The exact contract is declared in
`specs/audit-emission-cadence.yaml`.
## Live authorization gate (flex-auth + tenant-engine)
Off by default (no `QONTO_FLEX_AUTH_URL` set). When configured, every