The batches that write records in bulk or for review, and the proposals staged on them. Core.
changes is how records arrive other than by a person filling in a form: an agent turn, a spreadsheet import, a feed run, a storefront sync, a purge. Each one is a change batch.
A batch is a row in change_batches, with its channel, its status, its counts and its origin — what it read from. Every table a feed or an import can land carries a pointer back to the batch, so a batch opens its records as an ordinary grid filter composed with the tenant's own filters and pagination. The rows a batch refused are its rejects, each with a reason and the payload as it arrived.
Generated data does not land at all until someone applies it. An AI or imported write is staged as a proposal — a row in change_proposals carrying what it would write, revisable while it waits — so an unreviewed product has no row rather than a hidden one. Applying it writes the real tables and the audit history records what happened; rejecting it leaves the proposal as the record that someone declined. Who applied or rejected it is a step on the proposal, and a tenant can require that person to be someone other than the one who staged it.
Guardrails — a price ceiling, protected fields — are settings data the write path reads, checked when a change is staged and again when it is applied.
| Route | Content |
|---|---|
/changes | One row per batch, what it proposes, and apply or reject. |
/changes/<id> | The rows that batch refused, and why. |
changes.
changes.read to see batches and proposals; changes.manage to change a batch. Applying and rejecting are admin-only.
Never. It is core: every tenant has the assistant, and every assistant write stages here.