Skip to main content

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 !== SYSTEM writes straight to MongoDB with w:'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/):

FileRole
audit.service.tsPublic API: emit(input, explicitContext?)
audit-queue.service.tsAsync queue producer
audit-event-processor.service.tsAsync queue consumer
audit-event.repository.tsMongoose repository
audit-event.schema.tsPersistence schema
audit-context.factory.ts / audit-context.setup.tsRequest-scoped audit context (CLS)
audit-id.factory.tsAudit id factory
audit-queue.enum.tsQueue/operation enums
audit.constants.tsModule constants (including AUDIT_CLS_KEY)
audit.module.tsNestJS module wiring
Sensitive event registrySENSITIVE_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 org
  • action — typed enum
  • resource — what was acted on
  • context — 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 engineer
  • target_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.