Appearance
JSON-DAT: auditLog.logMetadata
JSONB element of: Audit Log .logMetadata
Description: Structured description of one change. Every payload carries a small mandatory core (before/after/changedFields); anything beyond that is free-form and defined by the feature that wrote the entry.
| Attribute Name | Description | Data Type | Default Value | Required (= Nullable) | Unique | Format | Validations | Index | Example |
|---|---|---|---|---|---|---|---|---|---|
before | The entity's full audited field set as it stood before the change. null for a created event (nothing existed yet). For a deleted event this is the full set — the same shape created carries — never a summary, so the entry is self-contained and a reader never has to replay earlier entries to learn what was lost. | object | - | No | No | - | Keys are the audited entity's declared audited field set (see below), not an ad-hoc subset. | — | { "status": "active", "supplierId": "550e8400-e29b-41d4-a716-446655440001" } |
after | The entity's full audited field set as it stands after the change. null for a deleted event (nothing exists anymore). For a created event this is the full set. | object | - | No | No | - | Keys are the audited entity's declared audited field set. | — | { "status": "inactive", "supplierId": "550e8400-e29b-41d4-a716-446655440001" } |
changedFields | Names of the fields that changed. For created/deleted this is every name in the audited field set (the whole record came into being / ceased to exist) — named explicitly, not as a "*" shorthand, so the set is machine-readable the same way for every event. For updated, only the fields that actually changed; a write that changes nothing produces no entry at all. | array of string | - | No | No | - | Every name is a member of the audited entity's declared audited field set. | — | ["status"] |
The audited field set is declared once per entity, not once per write path
Every audited entity has one audited field set, declared where that entity's own write paths are documented (its Functional Requirements / Write Path), and every created / updated / deleted entry for it uses that same set — never a set re-derived ad hoc per call site. This is what lets created and deleted be compared directly (same keys on both sides) and what keeps two write paths for the same entity from silently recording different things.
- Every exclusion is named where the set is declared, with its reason. Excluding a credential, a secret reference, or a large blob is a legitimate, nameable choice; a field missing because nobody thought of it is exactly the failure this rule exists to catch, and the two look identical after the fact unless the legitimate one is written down.
- A column excluded by value is still named as a key. Its name appears among
changedFields(and, where the entity's own convention calls for it, as a key inbefore/afterwith a placeholder rather than the real value), so a reader can tell "not touched" apart from "touched, value withheld." - Credential-bearing values are redacted, never silently omitted. Where a structured column may carry credential material, the writing feature records the sanitised form, not the raw one.
- Two follow-on decisions belong to each audited entity's own analysis, not to this shared JSON-DAT: whether an excluded structured column (a rule set, a layout blob) ever carries its value, and whether a child row's own lifecycle is filed as its own
created/deletedpair or as anupdatedon the parent with a discriminator. Both are legitimate; what is not legitimate is one entity doing it differently from the rest without saying so.
Free-form remainder
Any additional key the recording feature wants to capture beyond the audited field set — e.g. reason, a related entity id, business context not itself a tracked field. Not enumerated here: each feature documents its own additions next to the write path that adds them, rather than in this shared JSON-DAT.
Never placed here: secrets, credentials, raw unencrypted personal data.
Examples
created — the full audited set as after, before null:
json
{
"before": null,
"after": { "status": "active", "supplierId": "550e8400-e29b-41d4-a716-446655440001" },
"changedFields": ["status", "supplierId"]
}updated — only the fields that changed:
json
{
"before": { "status": "active" },
"after": { "status": "inactive" },
"changedFields": ["status"],
"reason": "customer request"
}deleted — self-contained: the full audited set as before, after null, so a reader learns what was lost without consulting an earlier entry:
json
{
"before": { "status": "inactive", "supplierId": "550e8400-e29b-41d4-a716-446655440001" },
"after": null,
"changedFields": ["status", "supplierId"]
}