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:
@@ -41,6 +41,7 @@ The structural choices are recorded as ADRs and summarized below. Any change to
|
||||
- **Identity:** multi-tenant Microsoft Entra ID with B2B invitation for workforce in v1, dual-audience design ready for future External ID activation — see [ADR-0008](decisions/0008-identity-model-entra-workforce-dual-audience.md).
|
||||
- **Authentication flow:** OIDC Authorization Code + PKCE via `@azure/msal-node`, executed entirely on the BFF; SPA never holds tokens; `__Host-` prefixed cookies, double-submit CSRF, RP-initiated logout — see [ADR-0009](decisions/0009-auth-flow-oidc-pkce-msal-node.md).
|
||||
- **Observability:** Pino + `nestjs-pino` for structured JSON logs, OpenTelemetry SDK + auto-instrumentations for traces, W3C Trace Context propagation across SPA → BFF → DB → Redis, `nestjs-cls` for request-scoped context (`trace_id`, `session_id`, `user_id_hash`, `audience`), 100 % sampling at the app with tail sampling deferred to the OTel Collector, stdout + OTLP shipping — see [ADR-0012](decisions/0012-observability-pino-opentelemetry.md).
|
||||
- **Audit trail:** dedicated `audit.events` schema in the same Postgres instance, append-only by Postgres role grants (`audit_writer` INSERT, `audit_reader` SELECT, `audit_archiver` DELETE older than retention; no `UPDATE`/`TRUNCATE` to anyone); 365-day retention default; cross-referenced with app logs via `trace_id` and `actor_id_hash` (same salt); blocking writes (no audit ⇒ no action) — see [ADR-0013](decisions/0013-audit-trail-separated-postgres-append-only.md).
|
||||
- **Local quality gates:** Husky + lint-staged + commitlint with Conventional Commits — see [ADR-0007](decisions/0007-pre-commit-hooks-and-conventional-commits.md).
|
||||
- **Runtime:** Node.js latest LTS major.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user