Audit and observability
Wrkbelt is a SOC 2 environment. Every action that touches another tenant's data — or any sensitive control-plane operation — is recorded in an immutable audit trail. Beyond audit, the platform surfaces operational signals through structured logging consumed by CloudWatch alarms, plus Sentry distributed traces.
This page covers the two-tier audit write strategy, the audit-event schema, dual-identity tracking, the structured-log key contract, and the Sentry integration.
Two-tier audit writes
AuditService exposes a single entry point: async emit(input, explicitContext?) at libs/api/services-api/src/lib/core/audit/audit.service.ts:46. Internally it routes to one of two paths based on event type:
- Synchronous (sensitive user events) —
SENSITIVE_AUDIT_EVENT_TYPES.has(event_type) && actor !== SYSTEMwrites straight to MongoDB withw:'majority'. A failure throws, so the caller can abort the business operation. Used for privilege changes, impersonation start/stop, credential mutations, etc. - Asynchronous (routine + system events) — everything else (including all system-actor events regardless of type) is published to SQS for batch persistence; failures are logged but do not propagate. The asymmetry is deliberate: routine flows emit at high cardinality (e.g. integration token-refresh cycles emit one audit per refresh), and pinning their tail latency to MongoDB acknowledge time would back-pressure the originating services for no compliance benefit.
Context resolution chain is explicit → CLS (HTTP) → system fallback. Every call produces an audit record; events are never dropped.
Source files (libs/api/services-api/src/lib/core/audit/):
| File | Role |
|---|---|
audit.service.ts | Public API: emit(input, explicitContext?) |
audit-queue.service.ts | Async queue producer |
audit-event-processor.service.ts | Async queue consumer |
audit-event.repository.ts | Mongoose repository |
audit-event.schema.ts | Persistence schema |
audit-context.factory.ts / audit-context.setup.ts | Request-scoped audit context (CLS) |
audit-id.factory.ts | Audit id factory |
audit-queue.enum.ts | Queue/operation enums |
audit.constants.ts | Module constants (including AUDIT_CLS_KEY) |
audit.module.ts | NestJS module wiring |
| Sensitive event registry | SENSITIVE_AUDIT_EVENT_TYPES in @wrkbelt/shared/types-data |
OpenSpec contract: openspec/specs/audit-service/spec.md.
Audit-event schema
Each audit event captures: who, what, when, where, with what context. Types live at libs/shared/types-data/src/lib/core/audit-event/audit-event.enum.ts (action enums) and the schema at audit-event.schema.ts.
Key fields:
actor_identity— who actually performed the action (the real session user, even during impersonation)target_identity— the impersonated user, if any (null otherwise)organization_id— the target orgaction— typed enumresource— what was acted oncontext— request-scoped metadata (correlation id, IP, user-agent)created_at— timestamp (immutable)
Dual-identity tracking
When a support engineer is impersonating a user, every audit record carries both identities:
actor_identity= the support engineertarget_identity= the impersonated user
This is non-negotiable for SOC 2 — the audit trail must show who actually pressed the buttons, even when they did so on behalf of someone else.
For why this is dual-identity instead of session-overlay, see Decisions → Shipped → Impersonation dual identity.
Audit retention (current)
Audit events ship with a 24-hour TTL index in v0.6.0. This is a walking-skeleton placeholder; the SOC 2-grade 1-year+ retention via MongoDB Atlas Online Archive is tracked in Future state → Audit archival.
The 24-hour window is enough for incident response within a development window; it is not enough for compliance audits. Treat the current retention as a development convenience that will be replaced.
Structured logging
All API-level operational signals flow through structured logs with stable keys. CloudWatch metric filters parse the keys to populate metrics; alarms trigger on metric thresholds.
The redis-side primitives emit a defined key set with a CI-enforced contract — see Cluster coordination pattern → Operational signals for the full alarm table, the structured-log key list, and the test that fails CI on unmonitored additions.
CloudWatch alarms ship in copilot/api/addons/observability.yml.
Sentry distributed tracing
Sentry captures uncaught exceptions and request traces. Spans are auto-instrumented around HTTP requests, Mongoose operations, and outbound HTTP calls. Custom spans are added for cross-cutting flows (booking-session pipeline, migration boot, audit dispatch).
Sentry session replay is wired on the UI side for production sessions; see the UI section of the dev-docs for the client integration.
Related
- OpenSpec contract:
openspec/specs/audit-service/spec.md - Future archival: Future state → Audit archival
- Operational signals: Cluster coordination pattern → Operational signals
- Why two-tier? Decisions → Shipped → Audit CQRS sync+async
- Why dual-identity audit? Decisions → Shipped → Impersonation dual identity