Loading…
Loading…
Describes the repository for writing audit events to the s2r_audit_event table, including tenancy model, event structure, and secret redaction.
The Audit Event Repository is a service component responsible for creating a persistent, append-only log of significant actions within the platform. It writes structured event data to the s2r_audit_event database table.
This component solves the problem of tracking "who did what, to what, and when" for both tenant-specific activities and platform-level administrative operations. It is called by other services to record the outcome of actions, capturing the actor, the entity acted upon, the state before and after the action, and whether the action succeeded or failed.
Key features of the audit repository include:
before_state, after_state), and a correlation ID to link related operations.The repository provides a straightforward mechanism for recording successful and failed operations. The core logic handles tenancy context and data serialization before persisting the event to the database.
The process of writing an audit event follows these steps:
success() or failure() method with details of the completed action.entityType.beforeState and afterState objects are serialized into JSON format. If serialization fails, the corresponding field is populated with a {"serialization":"failed"} object.INSERT statement is executed against the s2r_audit_event table. A new UUID is generated for the event's primary key, and the occurred_at timestamp is set to the current time.The following diagram illustrates the high-level data flow for event creation:
flowchart TD
A[Action occurs in the system] --> B[AuditRepository method is called];
B --> C{Check entity type};
C -- "tenant" or "fleet" --> D[Write as Platform Event];
C -- Other types --> E[Write as Tenant Event];
D --> F[INSERT into s2r_audit_event with tenant_id = NULL];
E --> G[INSERT into s2r_audit_event with tenant_id = current tenant's UUID];
F --> H[Event persisted];
G --> H;The audit repository implements a strict multi-tenancy model to isolate tenant data.
Tenant Events: By default, all audit events are associated with the tenant that initiated the action. The INSERT operation omits the tenant_id column, relying on a database DEFAULT that populates it from the connection's app.tenant_id session setting. Database Row-Level Security (RLS) policies ensure that queries only return rows matching the active tenant context.
Platform Events: Actions related to core platform management are recorded as platform events. These events have a NULL tenant_id and are not visible to any tenant. An event is classified as a platform event if its entityType is one of the following:
tenant (e.g., tenant provisioning)fleet (e.g., fleet reconciliation)When writing a platform event, the repository runs the database insertion within a special platform context where the app.tenant_id session setting is unset, causing the DEFAULT to yield NULL.
IMPORTANT
All read queries against the s2r_audit_event table must be correctly scoped by tenant. The repository provides a canonical SQL fragment, TENANT_SCOPE_SQL, for this purpose:
tenant_id = nullif(current_setting('app.tenant_id', true), '')::uuid
This predicate ensures that queries only match the connection's bound tenant and that platform events (tenant_id IS NULL) are never returned to a tenant. An unset tenant context will match no rows ("fail-closed").
To prevent accidental leakage of sensitive information, the repository implements a multi-layered secret redaction strategy for system_setting audit events.
A one-time scrubbing operation is available to remove secrets from historical audit records written before redaction was enforced. The scrubLegacySecretSettingValues() method executes an UPDATE statement that finds all system_setting audit events for the current tenant containing secret keys in their before_state or after_state fields.
For each matched row, it rewrites the state objects by:
secret_key_set: true) to indicate that a value was present without exposing the value itself.This operation is idempotent; running it a second time on the same data results in no changes.
As a defense-in-depth measure, all reads of audit data are expected to apply redaction logic at query time. The repository provides a helper, redactedStateSql(String column), that generates a SQL expression to perform the same redaction as the legacy scrub. This ensures that even if a row has not yet been scrubbed, its secrets are never exposed to a client reading the audit log.
The repository exposes methods for writing audit events and managing legacy data.
success(...) and failure(...)These methods write a new audit event to the s2r_audit_event table. The only difference is the value of the result column, which is set to "success" or "failure" respectively.
| Parameter | Type | Required | Description |
|---|---|---|---|
actorEmail | String | No | The email of the user who performed the action. Defaults to system@local if null or blank. |
actorRole | String | No | The role of the actor. Defaults to system if null or blank. |
actionType | String | Yes | The type of action performed (e.g., create, update, delete). |
entityType | String | Yes | The type of the entity that was acted upon (e.g., api_key, system_setting). |
entityId | UUID | Yes | The unique identifier of the entity. |
beforeState | Object | No | A serializable object representing the state of the entity before the action. Serialized to JSON. |
afterState | Object | No | A serializable object representing the state of the entity after the action. Serialized to JSON. |
correlationId | UUID | No | A unique identifier to group related events in a single logical operation. |
message | String | No | A human-readable message describing the event or the reason for failure. |
scrubLegacySecretSettingValues()This method executes a one-time cleanup of historical system_setting audit events to redact secret values. It operates only on the events belonging to the tenant of the current request context.
int – The number of audit rows that were updated.s2r_audit_event TableAll audit events are persisted to the s2r_audit_event table. The repository's insert method populates the following columns.
| Column | Type | Description |
|---|---|---|
id | uuid | The primary key for the event, generated as a random UUID. |
occurred_at | timestamptz | The timestamp when the event was recorded. |
tenant_id | uuid | The tenant associated with the event. Populated by a database DEFAULT from the session context. NULL for platform events. |
actor_email | text | The email of the actor. Defaults to system@local. |
actor_role | text | The role of the actor. Defaults to system. |
action_type | text | The type of action (e.g., create). |
entity_type | text | The type of entity (e.g., tenant). |
entity_id | uuid | The identifier of the entity. |
before_state | jsonb | A JSON representation of the entity's state before the action. |
after_state | jsonb | A JSON representation of the entity's state after the action. |
correlation_id | uuid | An ID to correlate multiple events from a single logical operation. |
result | text | The outcome of the action: success or failure. |
message | text | An optional, human-readable message with more details. |
The before_state and after_state fields in the s2r_audit_event table are populated by serializing Java objects to JSON. If this serialization process fails for any reason, the repository does not fail the write operation. Instead, it writes a specific JSON object to the corresponding column to indicate the failure:
{"serialization":"failed"}
If you encounter this value when reading the audit log, it means the state object provided by the calling service could not be converted to a valid JSON string.
When querying for audit events, if you receive an empty result set unexpectedly, verify that the database connection has the correct tenant context (app.tenant_id) set. The security model is "fail-closed," meaning that if the tenant ID is missing from the session, the query will correctly return zero rows rather than leaking platform events or data from other tenants.