Skip to content

Audit metadata and provenance

Every domain row in SteadyOn carries four audit fields:

  • audit.createdAt — when the row was first created.
  • audit.createdByName — who created it (text snapshot).
  • audit.updatedAt — when the row was last modified.
  • audit.updatedByName — who last modified it (text snapshot).

This metadata appears as Created/Modified columns on every list, a Created-by/Modified-by footer on every detail page, and full diffs in the audit log. This page explains why it works the way it does.

When SteadyOn renders an audit row, it shows the actor’s name as it stood at the time of the change. Not the actor’s current name.

That’s a deliberate design choice. The two alternatives both have serious problems:

  • Live join (read the user’s name now). Easy to implement, but rewrites history. If Bob renames himself “Robert”, every change Bob ever made now appears to have been made by Robert. The audit log effectively lies about the past.
  • Live join with fallback to “Deleted user”. Slightly better, but unhelpful. “Deleted user” closed this incident on April 12th is not an answer a regulator accepts.

Snapshots solve both. Bob’s name on April 12th is preserved as Bob. If he renames himself, the new name applies to changes he makes from that point on. Old changes keep the old name.

In a real audit:

  • An inspector asks who closed an incident. The audit log gives a name and a timestamp. The name needs to match the person who was in role at the time. Whether they’ve since left, been renamed, or had their account deleted is irrelevant.
  • A regulator asks for the chain of custody on an action. The same applies. The record needs to show the actor who actually did the thing.
  • An insurance claim asks who verified an inspection. Again — the actor at the time, not whoever shares that login now.

Snapshots make all three queries work without exception cases for “deleted user” or “renamed user”.

SurfaceWhat you see
List columns”Created” and “Modified” timestamps. Hover for the snapshot name.
Detail page footer”Created by [Name] on [date]. Last modified by [Name] on [date].”
Audit log (Log tab)Per-event actor and timestamp, plus the diff for updates.
CSV exportFour columns: Created, Modified, Created by, Modified by.

If a user has never set their name (a brand-new user who hasn’t filled in their profile), the snapshot stores the empty string. The UI renders this as “(no name)” — a prompt to fix.

This is one of the reasons we nudge users to set their name in the Edit your profile flow during onboarding.

You don’t. That’s the whole point of snapshots.

If your name has changed and you want history to reflect the new name retroactively — for example because you’ve changed surnames — the answer is: no, the historical record is preserved. New entries will use your new name from the moment you save.

The closest you can get is: “Bob (now Robert) closed this incident on April 12th”. The Bob name is fixed; you can mention the rename inline in the description if it matters for current readers.

Two layers:

  • Row level. Created and Modified on the row itself. This is what shows in lists and detail pages. It’s a snapshot of the most recent change.
  • Event level. The audit log records every change ever made, with the diff. This is what shows in the Log tab and the global Log page.

Row level is fast (one read per record). Event level is complete but read-on-demand. Both use the same actor-snapshot logic.