SEC-007 audit-log SIEM dead-letter queue.
When the SIEM forwarder (see utils/notifications.js SIEM target) fails
to deliver an audit event after its retry budget is exhausted, the row's
snapshot is dropped into the audit_dlq table so an operator can inspect
and manually replay it later. The schema is defined by migration 031.
Schema (migration 031_activities_compliance.sql)
| Column | Type | Notes |
|---|---|---|
| id | TEXT PK | Short opaque ID, DLQ-<n> (see below). |
| workspaceId | TEXT | Owning workspace (used for the admin scope). |
| rowSnapshot | TEXT | JSON-stringified activity row. |
| lastError | TEXT | Most recent dispatch error message. |
| attempts | INTEGER | Count of dispatch attempts (incl. retries). |
| createdAt | TEXT | ISO timestamp of the first failure. |
ID convention
The audit_dlq.id column is TEXT PRIMARY KEY, so any unique opaque
string works. We use DLQ-<n> (counter via counterRepo) to match the
project-wide short-ID convention (TC-, RUN-, ACT-, …) — readable in
admin UI lists without exposing internal UUIDs.
Methods
(static) countByWorkspace(workspaceId) → {number}
Count of DLQ rows for a workspace — used by the AuditLog UI to render the "N retry-failed" badge without loading the full list.
Parameters:
| Name | Type | Description |
|---|---|---|
workspaceId |
string |
Returns:
- Type
- number
(static) enqueue(entry) → {Object}
Insert a fresh DLQ row for an activity that could not be delivered to the configured SIEM target after exhausting the retry budget.
Always starts at attempts = 1 — the initial delivery attempt counts as
the first attempt. The forwarder is expected to retry in-process up to
its configured budget before calling this; subsequent UI-triggered
/replay invocations increment via incrementAttempts().
Parameters:
| Name | Type | Description | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
entry |
Object |
Properties
|
Returns:
The created DLQ row (hydrated — rowSnapshot is an
object, not a JSON string).
- Type
- Object
(static) getById(id) → {Object|null}
Fetch a single DLQ row by ID. Returns null when missing. Callers must
still cross-check row.workspaceId === req.workspaceId before acting on
it — the route layer is the trust boundary for cross-workspace access.
Parameters:
| Name | Type | Description |
|---|---|---|
id |
string |
Returns:
- Type
- Object | null
(static) incrementAttempts(id, lastError) → {number}
Increment the attempt counter and record the most recent error after a failed replay. Returns the new attempts count, or 0 if the row no longer exists (e.g. another admin removed it concurrently).
Parameters:
| Name | Type | Description |
|---|---|---|
id |
string | |
lastError |
string |
Returns:
- Type
- number
(static) listByWorkspace(workspaceId, optsopt) → {Array.<Object>}
List DLQ rows for a workspace, newest-first. Caller (route handler) is responsible for the admin gate and workspace-scope assertion.
Parameters:
| Name | Type | Attributes | Description | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
workspaceId |
string | ||||||||||||
opts |
Object |
<optional> |
Properties
|
Returns:
- Type
- Array.<Object>
(static) remove(id) → {boolean}
Delete a DLQ row after a successful replay. Returns true when a row was removed, false when none matched (idempotent for the caller).
Parameters:
| Name | Type | Description |
|---|---|---|
id |
string |
Returns:
- Type
- boolean