Add the PostgreSQL audit backend and a shared conformance suite
AUDIT-WP-0005-T01, built and verified against PostgreSQL 16 locally in Docker; the Railiance cluster was not needed. tests/test_backend_conformance.py is one suite run against every backend, so "the Postgres backend is done" means it satisfies the same contract SQLite already does rather than having its own green tests. It skips cleanly with no server reachable; make pg-test-up and make test-pg run it. Suite 50 -> 71. RetentionPolicy declares immutable=True and earns it: migration 0002 installs a trigger rejecting UPDATE and DELETE on the events table, so a leaked runtime credential can append but cannot rewrite or erase the trail. That materially narrows the residual risk ADR-0001 section 5 called out. tamper_evidence stays False because nothing here would prove a database owner had dropped the trigger - hash-chaining or external anchoring would be needed and is not implemented. Idempotency is one statement (INSERT ... ON CONFLICT DO NOTHING RETURNING), verified to behave identically to the SQLite backend under 12 concurrent submissions of the same event. Migrations are ordered, recorded and idempotent. Replay reconciles rather than duplicating - the piece deferred out of WP-0004-T05 - and is tested to leave exactly one custody record. Backend selection is by AUDIT_CORE_DATABASE_URL; the SQLite fallback logs a warning so a deployment that lost its URL is visible rather than quietly running on the wrong store. Also fixed: ingestion had no __main__ guard, so python -m audit_core.ingestion silently did nothing. Found during end-to-end smoke. Counting semantics documented: occurrences counts transmissions, not stored events, so a retry of a secret-shaped field increments it again. That is the sender behaviour being optimized away. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
576caa2665
commit
bd274f6269
9 changed files with 806 additions and 9 deletions
|
|
@ -49,7 +49,7 @@ requirement and consumes it; it does not implement it here.
|
|||
|
||||
```task
|
||||
id: AUDIT-WP-0005-T01
|
||||
status: todo
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "b1601d0b-922a-40f7-92c0-ea06af6c4468"
|
||||
```
|
||||
|
|
@ -76,6 +76,41 @@ Done when the backend passes the same contract tests as the existing backends,
|
|||
concurrent duplicate submissions produce exactly one record, and a database
|
||||
restart mid-write does not produce an acknowledged-but-absent event.
|
||||
|
||||
Done 2026-08-10, built and verified against PostgreSQL 16 locally in Docker —
|
||||
the Railiance cluster was not needed for any of it.
|
||||
|
||||
`tests/test_backend_conformance.py` is a single suite run against every
|
||||
backend, so "the Postgres backend is done" means it satisfies the same
|
||||
contract SQLite already does rather than having its own tests that happen to
|
||||
be green. It skips cleanly when no server is reachable; `make pg-test-up` and
|
||||
`make test-pg` run it. Suite 50 -> 71.
|
||||
|
||||
`RetentionPolicy` declares `immutable=True`, and that is earned: migration
|
||||
0002 installs a trigger rejecting UPDATE and DELETE on the events table, so a
|
||||
leaked runtime credential can append but cannot rewrite or erase the trail.
|
||||
This materially narrows the residual risk ADR-0001 §5 called out — a leaked
|
||||
credential could previously forge the audit record. `tamper_evidence` stays
|
||||
False, because nothing here would *prove* a database owner had dropped the
|
||||
trigger; hash-chaining or external anchoring would be needed and is not
|
||||
implemented.
|
||||
|
||||
Migrations are ordered, recorded in `schema_migrations`, and idempotent.
|
||||
Replay reconciles rather than duplicating — the piece deferred out of
|
||||
WP-0004-T05 — and is tested to leave exactly one custody record.
|
||||
|
||||
Backend selection is by `AUDIT_CORE_DATABASE_URL`; falling back to SQLite logs
|
||||
a warning, so a deployment that lost its URL is visible rather than quietly
|
||||
running on the wrong store. End-to-end smoke through waitress against Postgres
|
||||
confirmed accept, duplicate, cross-tenant refusal, read/write privilege
|
||||
separation, visible redaction, secret-finding counters, and readiness
|
||||
reporting `custody_class=archive`.
|
||||
|
||||
Also fixed: `audit_core.ingestion` had no `__main__` guard, so `python -m
|
||||
audit_core.ingestion` silently did nothing.
|
||||
|
||||
Not covered here: behaviour across a real database failover, which needs the
|
||||
cluster and belongs to T05.
|
||||
|
||||
## T02 - Provision storage through the platform lane
|
||||
|
||||
```task
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue