Skip to content

feat(gateway): audit read API + chain verification (Step 6.6a) - #153

Merged
officialCodeWork merged 1 commit into
mainfrom
build/phase-6/step-6.6a-audit-read-api
Jun 7, 2026
Merged

feat(gateway): audit read API + chain verification (Step 6.6a)#153
officialCodeWork merged 1 commit into
mainfrom
build/phase-6/step-6.6a-audit-read-api

Conversation

@officialCodeWork

Copy link
Copy Markdown
Owner

Summary

First of two slices for Step 6.6 — Immutable audit log. Step 0.7c shipped the foundation (append-only AuditStore SPI, NoopAuditStore SHA-256 hash chain, AuditWriter), but the log was write-only — only the corpus router wrote corpus.route, into a store nothing could read or verify. 6.6a makes it usable + provably intact.

  • One shared audit store. build_app creates a single AuditWriter over a hash-chain store, exposes app.state.audit_store / audit_writer / audit_enabled, and hands the same writer to the corpus router — so events the writers append land in exactly the store the read API serves (before this, the corpus router held a private NoopAuditStore the gateway couldn't read). New AuditWriter.store accessor; default in-memory NoopAuditStore, production injects a durable one.
  • GET /v1/audittenant-scoped (a principal sees only its own tenant's events, filtered by ctx.tenant_id at the boundary, same pattern as GET /v1/query/{id}/trace), newest-first, bounded by limit ([1,1000]), optional action / outcome filters. AuditListResponse carries chain_verified so one read both returns the events and attests the log is untampered.
  • GET /v1/audit/verifywhole-log hash-chain integrity (the chain is one global sequence across tenants), returning {ok, event_count} — a boolean + total, no event content, so global verification leaks nothing cross-tenant.
  • The hash chain is the tamper-evidence mechanism (no second scheme); immutability at rest (the WORM signed export) is Step 6.6b.
  • Read API on by default via cfg.audit.enabled (a passive, tenant-scoped compliance record, unlike the behaviour-changing ACL/PII toggles); off → 404 (AuditNotFoundError), events still recorded. Adds AuditListResponse / AuditVerifyResponse wire types + AuditError / AuditNotFoundError.

Scope / deferred: read + verify only (WORM signed export is 6.6b). Today the populated event is corpus.route (every query); expanding audit coverage (ACL/PII/ingest decisions as audit events) is a follow-up — the surface + chain are in place for it.

Tests & gates

  • ~10 gateway tests (apps/gateway/tests/test_audit.py): tenant-scoped list, cross-tenant isolation, verify ok + tamper detection (corrupt a stored chain hash → ok=False, chain_verified=False), action/outcome filters, limit/total, disabled→404, no-auth→401, shared-store wiring (a query's corpus.route event is visible via GET /v1/audit).
  • Gateway suite 290 passed; full tests/ packages/ 2315 passed, 1 skipped. Green: ruff, mypy --strict (298 files), RAG001, policy-coverage, log-schema, schema-drift + openapi-drift (dist/openapi.* + dist/rag.schema.* regenerated for the new endpoints + AuditConfig).

Documentation

Test plan

  • uv run pytest apps/gateway/tests — 290 passed
  • uv run pytest tests/ packages/ -m "not integration" — 2315 passed, 1 skipped
  • ruff + mypy --strict (298 files) + RAG001 + policy-coverage + log-schema
  • schema-drift + openapi-drift gates (regenerated artifacts committed)
  • Cross-tenant isolation + tamper detection verified

🤖 Generated with Claude Code

Make the Step 0.7c hash-chain audit log usable + provably intact. build_app now
creates one AuditWriter over a tamper-evident SHA-256 hash-chain store, exposes
app.state.audit_store / audit_writer / audit_enabled, and hands the same writer
to the corpus router — so the events the writers append land in exactly the store
the read API serves (previously the corpus router held a private store the
gateway couldn't read). New AuditWriter.store property is the read accessor.

GET /v1/audit is tenant-scoped (a principal sees only its own tenant's events,
newest-first, with chain_verified inline), bounded by limit and filterable by
action / outcome. GET /v1/audit/verify reports whole-log hash-chain integrity
{ok, event_count} — content-free, so global verification leaks nothing
cross-tenant. The chain is the tamper-evidence mechanism; immutability at rest
(the WORM signed export) is Step 6.6b.

Read API on by default via cfg.audit.enabled (a passive, tenant-scoped
compliance record, unlike the behaviour-changing ACL/PII toggles); off → 404,
events still recorded. Adds AuditListResponse / AuditVerifyResponse wire types +
AuditError / AuditNotFoundError (→404). First of two slices for Step 6.6.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@officialCodeWork
officialCodeWork merged commit 73796cc into main Jun 7, 2026
12 of 17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant