What changed, who changed it, and what it was before

Every write through the data layer records the actor, the account, the values before the change, the values written, and how many rows it matched.

What it is

An audit log with real attribution and a real diff. Each row carries a durable user id and a denormalised email so it still identifies the actor after they leave, a flag for whether it was done by vendor staff, the account it happened in, a bounded snapshot of the rows before the write, the values written, and the true affected count. For a delete, the before-snapshot is the only surviving copy of the data.

Also called: audit trail · change history · who deleted this · before and after

See it
What changed, who changed it, and what it was before
Area
Operations
Group
Audit
System
Multi-Tenancy & Identity
Solves
3 named problems
01Before an update or delete runs, the same filters and the same scoping are used to snapshot the rows it is about to change.
02The snapshot is bounded — the first 25 rows, capped by size — and reports the true count, because 'an audit system that writes megabytes per event is one that gets switched off.'
03The write's actor is recorded as a durable id plus email plus a platform-staff flag.
An audit row expanded into a before/after diff with the affected-row count and the actor's email. src/app/platform/audit/page.tsx. Sample data — no customer information appears here.
How it works
  1. 1Before an update or delete runs, the same filters and the same scoping are used to snapshot the rows it is about to change.
  2. 2The snapshot is bounded — the first 25 rows, capped by size — and reports the true count, because 'an audit system that writes megabytes per event is one that gets switched off.'
  3. 3The write's actor is recorded as a durable id plus email plus a platform-staff flag.
  4. 4The account id is written adjacent to the insert so the CI scoping check reads it correctly.
  5. 5Failures are logged but never thrown, so a logging error can't fail the request it describes.
  6. 6High-volume, zero-value tables are explicitly exempt so they don't flood the trail.
Why we built it

Two comments here are unusually blunt. On attribution: the interface accepted a user id 'and then silently dropped it — there was no column — so every audit row was attributed by display name alone. Names change and collide; you cannot audit a person you cannot identify.' On silence: the insert used to omit a required id, 'so EVERY call it ever made failed the not-null constraint — and the catch below swallowed it. The table went silent in May and nobody noticed, because a logger that fails quietly looks exactly like a system where nothing happened.' The rule that came out of it is written into the code: 'Never throw — but never be silent either.' And on why a filter is not enough: without a snapshot the trail records only 'something matching id=41 was deleted', which 'answers none of the questions asked after a problem.'

The problem
  • Attribution by display name alone can't identify a person after they leave or when names collide.
  • A filter-only log can't answer what was in the record that was deleted.
  • A logger that fails silently is indistinguishable from a quiet system.
Sound familiar?
What you get
Deleted data has one surviving copy in the trail.
Every change names a durable identity and whether it was vendor staff.
Logging failures are visible in the server log instead of hiding a dead audit table.

See it on your own jobs

Twenty minutes, your numbers, no slide deck. We’ll build one of your real buildings in front of you and send you the estimate link at the end — yours to keep either way.

or keep browsing features →