# audit (/product/modules/audit)



`audit` answers two questions for every stateful row: who or what created it, and what has changed since. An agent writing hundreds of rows from one chat turn is what makes the second question urgent.

## Three tiers

**Row origin.** Every table that holds state carries created-by, updated-by, created-at and updated-at. The actor columns are filled by triggers, never trusted from the caller, so a client cannot post someone else's id and be believed. They are nullable: an ops script or a seed has no person behind it, and null says so.

**The run.** A batch that wrote records, an agent turn, a spreadsheet import, a storefront sync, is a row in `audit_runs`, with its channel, its status, its counts and its origin. Every table a feed or an import can land carries a pointer back to the run, so a batch opens its records as an ordinary grid filter composed with the tenant's own filters and pagination.

**Change history.** `audit_entries` is append-only, written solely by a trigger. An insert records the row, an update the changed column pairs, a delete the whole vanished row. Each entry carries the **source** of that change: manual, ai, import, api, sync, migration, system. An AI edit to a hand-typed product leaves the insert entry manual and the update entry ai, so one column answers both where a record came from and which fields a model wrote.

Wiring is derived, not listed: a table with the attribution columns gets the triggers. The one derived exclusion is a table signed-in users cannot read, because an entry holds the changed values.

## Approval

Generated data lands unapproved. An imported or AI-written row with no explicit status is a draft, invisible on the storefront until a person approves it, and a re-import never reverts a status a person decided. Approval is a guarded transition, not a table; the entries already record who, when and the from-to pair. A tenant can require a second person.

## Routes

| Route                  | Content                                                       |
| ---------------------- | ------------------------------------------------------------- |
| `/audit`               | Every entry: who changed what, when, and the change's source. |
| `/audit/batches`       | One row per run, linking to the records it produced.          |
| A record's history tab | That record's own entries.                                    |

## Settings scope

`audit`.

## Permissions

`audit.read`. Every read scopes through the same row-level security as the tables beneath.

## Switched off

Never. It is core, cross-cutting, with its own sidebar row and permission.
