QONTO-WP-0003-T05: pin REST/MCP audit-schema parity with a test

CapabilityService._emit_audit was already the single audit call site for
both transports since T02, but nothing failed if a future change diverged
one transport's shape. Add tests/test_audit_parity.py: same capability
called through protocol="rest" and protocol="mcp" (allow path and deny
path) must produce identical audit events except request_id/timestamp/
latency_ms (expected to vary) and protocol (expected to differ). Also pins
down that no audit event ever contains a secret-shaped field name.

Confirmed via grep: no State Hub coupling anywhere in src/qonto_assistant/
-- the only audit sink is AuditLogger, so there's no per-call hot-path
write to accidentally wire up.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
tegwick 2026-07-23 11:01:16 +02:00
parent 0b268faf05
commit c3e69373ca
2 changed files with 122 additions and 1 deletions

View file

@ -218,7 +218,7 @@ its own scope note. `pytest` → `28 passed` (unchanged).
```task
id: QONTO-WP-0003-T05
status: todo
status: done
priority: medium
state_hub_task_id: "b7d70c3e-fe55-47d9-9180-cd4c0bedc31a"
```
@ -232,6 +232,26 @@ hot-path events — do not wire per-invocation State Hub writes.
Done when: a test asserts REST and MCP audit records for the same capability
call are schema-identical modulo protocol field.
**Done 2026-07-23:** This was already true structurally since T02
(`CapabilityService._emit_audit` is the single audit call site for both
transports), but it wasn't pinned down by a test — a future change could
have special-cased one transport's shape without anything failing. Added
`tests/test_audit_parity.py`: calls the same capability through
`protocol="rest"` and `protocol="mcp"` for both an allow path
(`org_summary`) and a deny path (`list_transactions` oversized `page_size`
`arg_constraint`), then asserts the two events have identical key sets
and identical values on every field except `request_id`/`timestamp`/
`latency_ms` (expected to vary per call) and `protocol` (expected to
differ by design). A third test asserts no audit event ever contains a
secret-shaped field name (`api_key`, `authorization`, `token`, etc.) —
belt-and-suspenders alongside `tests/test_audit.py`'s existing redaction
test.
Confirmed (`grep`) there is no State Hub coupling anywhere in
`src/qonto_assistant/`: the only audit sink is `AuditLogger` (structured
stdout JSON, or a test-injected `sink` callable) — no per-call State Hub
write exists to accidentally wire up. `pytest``31 passed`.
## Task: MCP smoke path + operator runbook update
```task