audit-service is the platform’s immutable audit log. It is a Quarkus service backed by append-only PostgreSQL storage. Domain services never write to audit storage directly — they publish audit.event Kafka events, and audit-service consumes and persists them. This decoupling means audit recording never adds latency to the originating request.
Retention and Storage#
Audit records are retained for 7 years in PostgreSQL. There is no external archival tier — all records remain in the database for the full retention window.
The audit_events table is range-partitioned by occurred_at, with one partition per calendar month (84 partitions over 7 years). PostgreSQL partition pruning means queries with a time range only scan the relevant month partitions, keeping query performance predictable regardless of overall record volume.
A scheduled job running on the 1st of each month creates the next two months’ partitions as a rolling lookahead. No partition is ever dropped.
Kafka Event Schema#
Every state-mutating action in the platform publishes an audit.event Kafka message. The schema is defined in ss3-protos at io/shopstar/audit/v1/events.proto.
| Field | Type | Notes |
|---|---|---|
event_id | UUID | Generated by the publishing service |
store_id | UUID | Required |
actor_id | string | UUID, ANONYMISED, or empty for SYSTEM actors |
actor_type | enum | STAFF, CUSTOMER, or SYSTEM |
action | string | SCREAMING_SNAKE_CASE — e.g. PRODUCT_UPDATED |
target_type | string | Entity type — e.g. PRODUCT, ORDER |
target_id | string | Entity primary key |
service_name | string | Originating service |
trace_id | string | W3C OTel trace ID |
occurred_at | Timestamp | |
before_state | Struct | Optional; PII stripped by publisher |
after_state | Struct | Optional; PII stripped by publisher |
Action naming convention#
Actions follow the pattern {ENTITY_TYPE}_{PAST_TENSE_VERB} in SCREAMING_SNAKE_CASE:
PRODUCT_CREATED, PRODUCT_UPDATED, PRODUCT_DELETED, ORDER_PLACED, ORDER_CANCELLED, STAFF_ROLE_GRANTED, STAFF_ROLE_REVOKED, CUSTOMER_ERASED, FEATURE_TOGGLED, etc.
Publishing from Domain Services#
The ss3-quarkus extension provides an AuditEventPublisher CDI bean. Services inject it and call publish() after any state-mutating operation:
auditPublisher.publish(
"PRODUCT_UPDATED",
"PRODUCT",
product.getId().toString(),
previousProduct, // before — @AuditExclude fields stripped automatically
updatedProduct // after — @AuditExclude fields stripped automatically
);The publisher extracts actor identity and trace ID from RequestContext automatically. Services do not construct the event directly.
PII in snapshots#
Before/after snapshots must not contain raw PII. Publishers annotate sensitive fields with @AuditExclude; the SnapshotBuilder utility (also in ss3-quarkus) strips them before the event is serialised.
public class Customer {
public String id;
@AuditExclude public String email; // stripped from snapshots
@AuditExclude public String phoneNumber; // stripped from snapshots
public String status;
}GDPR Erasure#
On customer.erasure_requested, audit-service runs:
UPDATE audit_events
SET actor_id = 'ANONYMISED'
WHERE actor_id = :customerId
AND actor_type = 'CUSTOMER';This runs across all 84 partitions. The action record, target entity reference, and snapshots are untouched — only the customer’s actor_id is replaced with the sentinel value.
Staff actor_id is never anonymised. Staff actions on financial records must remain attributable for compliance.
Query API#
The admin SPA queries audit history through a REST API on audit-service:
GET /api/v1/audit-events
?storeId= required
&targetType= e.g. PRODUCT
&targetId= entity primary key
&actorId= staff or customer UUID
&action= e.g. PRODUCT_UPDATED
&from= ISO 8601 datetime
&to= ISO 8601 datetime
&page= default 0
&size= default 50, max 200Query responses include an actorDisplayName and targetDisplayName field resolved lazily via gRPC (identity-service and the relevant domain service respectively) and cached in Redis with a 5-minute TTL. Both entity IDs are always present in the response regardless of display name resolution success.
Compliance Export#
Auditors and regulators can request a full export via:
GET /api/v1/audit-events/export?storeId=&from=&to=The response streams NDJSON (one event per line) directly from PostgreSQL using a server-side cursor. No full result set is loaded into heap. The Content-Disposition header prompts a file download. This endpoint is gated to staff with the compliance role.
SIEM and Ops Alerting#
audit-service has no coupling to SIEM or alerting systems. External consumers — a SIEM Kafka connector and a platform ops alerting consumer — tail the audit.event topic directly in their own consumer groups:
| Consumer group | Purpose |
|---|---|
audit-service-consumer | Persist to PostgreSQL |
siem-connector | Forward events to SIEM in real time |
ops-alerting-consumer | Trigger alerts on high-risk actions (mass delete, privilege escalation, bulk price change) |
Kafka Topics#
Consumed#
| Topic | Action |
|---|---|
audit.event | INSERT into audit_events |
customer.erasure_requested | Anonymise actor_id for CUSTOMER actors |
Published#
None — audit-service is a sink.
Vault Configuration#
ss3/kv/audit-service/
db.url
db.username
db.password