docs: add ADR-0013 for the audit trail (dedicated append-only Postgres schema)
Pin the audit-trail architecture: a dedicated 'audit' schema in the same Postgres instance as business data, with three Postgres roles enforcing append-only at the database layer - audit_writer (INSERT only), audit_reader (SELECT only), audit_archiver (DELETE only on rows past the retention threshold). No role anywhere holds UPDATE or TRUNCATE; the BFF verifies this at startup with a deliberate failing UPDATE probe. The audit stream is decoupled from the application logs (different sink, different access controls, different retention) but cross-referenced via trace_id (ADR-0012) and actor_id_hash, which uses the same salt as the app logs so an investigator joins the two streams without re-hashing. Events captured in v1 cover the auth and session lifecycle: sign_in (success/failure), sign_out, session.expired, session.revoked, token.validation.failed, mfa.assertion.failed, authz.deny. Hooks for admin actions and sensitive data access are designed-in but inert until v1+ features call them - kept alive by tests so they don't drift. Failure semantics are blocking - if the audit INSERT fails, the in-flight operation fails with 503. Trade-off acknowledged: the audit DB is part of the trust path. Mitigation is HA Postgres in prod, deferred to the infrastructure ADR. Retention defaults to 365 days, env-overridable, enforced by a daily purge running under audit_archiver. The retention default is engineering prudence, not legal advice - the org-side legal review of the actual applicable retention regime is explicitly owed and noted in the ADR. Cryptographic chaining and WORM storage are deferred unless a compliance regime demands them. decisions/README.md index updated. CLAUDE.md gains an explicit 'Audit trail' line pointing to ADR-0013.
This commit is contained in:
@@ -56,3 +56,4 @@ ADRs are listed in numerical order. To slice by topic, filter on the `Tags` colu
|
||||
| [0010](0010-session-management-redis.md) | Session management — opaque session IDs in cookies, payload in self-hosted Redis with AES-GCM at rest | accepted | `security`, `backend`, `infrastructure` | 2026-04-29 |
|
||||
| [0011](0011-mfa-enforcement-entra-conditional-access.md) | MFA enforcement — Entra ID Conditional Access baseline, BFF claim sanity-check, step-up hooks designed-in | accepted | `security` | 2026-04-29 |
|
||||
| [0012](0012-observability-pino-opentelemetry.md) | Observability — Pino structured logs + OpenTelemetry tracing, W3C Trace Context propagation, stdout + collector | accepted | `observability`, `backend`, `frontend` | 2026-04-29 |
|
||||
| [0013](0013-audit-trail-separated-postgres-append-only.md) | Audit trail — separated append-only Postgres schema, decoupled from app logs | accepted | `security`, `observability`, `data` | 2026-04-29 |
|
||||
|
||||
Reference in New Issue
Block a user