Audit trail
An entity keeps no history of its changes unless you ask. Give it an
audit: section and it keeps a history of every change: who made it,
when, through which action and request, and each changed field's value
before and after. retention: says how long each entry is kept:
// domains/billing/entities/invoice.jadpoentity Invoice { ... audit: { retention: { after: config.audit_retention } }}
- Each entry is written in the same transaction as the change, in the same store, so a change can't happen without its entry.
- "Who" is the principal, the signed-in caller. For schedules and subscribers it is the application, together with the request that emitted the event, so a change made later is traced back to the click that caused it.
- Fields marked
personal:are stored as keyed hashes, as they are in logs (Marking personal fields). The history shows that an email changed, not what it was. Server-only fields are left out. Invoice.history(invoice_id)is a generated query returning a page of entries. It needs thereadgrant on the record, and fields the caller can't read are left out.- Entries are deleted when their retention ends, and with their record when it is erased. The permissions report lists which entities are audited.
The audit trail records changes to data. Who exported or erased a person, and who read sensitive fields, is kept as log events instead (Access records).
Sending audit entries elsewhere
When something subscribes to the built-in AuditEntryRecorded event, or
publishing names it, each entry is also emitted as that event, in the
same transaction, and travels through the outbox like any event
(Delivery: at least once, safe to repeat).
A SIEM or log archive receives it through publishing. With nothing
listening the event is never emitted. The database stays the record of
truth; the event is a copy, carrying the same hashed personal fields.
Markdown for agents: /docs/audit-trail.md.Built from Jadpo main at a707efb.