event

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.

Event Topic: AuditKey: entityIdNo consumerNot published today

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 ownerAUDIT
metadata.eventEntityAudited
Consumer groupnone

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.ts
export 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.owner on this topic reads AUDIT, with producedBy ONE once 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

PropertyValue
Contract Ownerx-contract-ownerAUDIT
Kafka Topicx-kafka-topicAudit
Message Keyx-message-keyentityId
Sourcex-sourcecna-one api/app/events/one/audit/EntityAudited.ts
Driftx-driftNever published in practice - no cna-one entity carries @Auditable(). No consumer in any application. The class declares owner AUDIT, which is not an application.
8 properties
entityNamestring
required

Table name of the audited entity (TypeORM tableName, not schema-qualified), e.g. employees.

entityIdstring
required

Id of the audited row. Kafka message key.

operationstring
required

Kind of change (cna-one AuditOperation).

Allowed values: INSERT UPDATE DELETE
oldValuesobject | null
required

Values before the change, keyed by property name. Null on INSERT. On UPDATE only the changed columns. Sensitive fields are replaced by ***.

newValuesobject | null
required

Values after the change, keyed by property name. Null on DELETE. On UPDATE only the changed columns. Sensitive fields are replaced by ***.

userIdstring | null
required

User who made the change, from the request audit context. Always null today.

ipstring | null
required

Client IP, from the request audit context. Always null today.

timestampstring<date-time>
required

When the change was recorded. A Date in code, serialised as ISO-8601.