Entity Audited
A row of an audited table in CNA One was inserted, updated or deleted, with the old and new values. Infrastructure is wired but no entity opts in and nobody consumes it.
Overview
Fact: a row of an audited table changed. The payload records which table and row, the operation, the values before and after, and who did it.
When it is published. CNA OneCNA OneServicev1.0.0Franchise operating system (franchises and employees, pricing, products and learning books, school operations). Publishe...PublishesGrantApplicationAccess, EmployeeCreated +6SubscribesInviteUser, FranchiseCreated +9Ownercna-platformMapRepoView docs has a TypeORM subscriber (AuditSubscriber) hooked on afterInsert, afterUpdate and afterRemove of every entity marked with the @Auditable() decorator. For an audited entity it builds the payload and calls auditService.register, which publishes the event. The call is not awaited: publication starts inside the write transaction, before commit, and a failure is swallowed by the producer. The subscriber is disabled in tests.
Today no entity carries @Auditable(), so the subscriber never fires and no message reaches the topic. The decorator accepts ignore (fields dropped from the values) and sensitive (fields replaced by ***).
Values. entityName is the table name (for example employees), not the class name. oldValues / newValues are keyed by property name: on insert only newValues, on delete only oldValues, on update only the columns whose value changed (an update with no change publishes nothing). userId and ip come from the request audit context, which nothing populates today, so both are null. timestamp is a Date in code and an ISO-8601 string on the wire.
Consumers. None in cna-auth, cna-nexus, cna-one or cna-placement.
Kafka
Topic (aggregateRoot) | Audit |
Message key (routingKey) | payload.entityId |
| Contract owner | AUDIT |
metadata.event | EntityAudited |
| Consumer group | none |
Payload schema
Source of truth
import { AuditData } from "audit/types";
class EntityAudited extends Event<AuditData> { public static owner = "AUDIT"; public static readonly aggregateRoot = "Audit"; public static readonly routingKey = "entityId";}
// api/app/audit/types.tsexport enum AuditOperation { INSERT = "INSERT", UPDATE = "UPDATE", DELETE = "DELETE",}
export type AuditData = { entityName: string; entityId: string; operation: AuditOperation; oldValues: Record<string, unknown> | null; newValues: Record<string, unknown> | null; userId: string | null; ip: string | null; timestamp: Date;};Known drift
- No entity opts in with
@Auditable(): the event is defined and wired but never published. Decide which entities to audit, or remove the topic. - No consumer anywhere. If an external sink is intended, document it as an external service.
owner = 'AUDIT'is not an application;metadata.owneron this topic readsAUDIT, withproducedByONEonce the envelope change lands.- Publication happens inside the transaction, unawaited, so a rolled-back write can still emit an audit message and a failed publish is lost.
Custom properties
| Property | Value |
|---|---|
| Contract Ownerx-contract-owner | AUDIT |
| Kafka Topicx-kafka-topic | Audit |
| Message Keyx-message-key | entityId |
| Sourcex-source | cna-one api/app/events/one/audit/EntityAudited.ts |
| Driftx-drift | Never published in practice - no cna-one entity carries @Auditable(). No consumer in any application. The class declares owner AUDIT, which is not an application. |
Table name of the audited entity (TypeORM tableName, not schema-qualified), e.g. employees.
Id of the audited row. Kafka message key.
Kind of change (cna-one AuditOperation).
INSERT UPDATE DELETEValues before the change, keyed by property name. Null on INSERT. On UPDATE only the changed columns. Sensitive fields are replaced by ***.
Values after the change, keyed by property name. Null on DELETE. On UPDATE only the changed columns. Sensitive fields are replaced by ***.
User who made the change, from the request audit context. Always null today.
Client IP, from the request audit context. Always null today.
When the change was recorded. A Date in code, serialised as ISO-8601.