ADR-0032: The audit log, read from its end¶
Status: Accepted Date: 2026-10-13 Deciders: Alex Nodeland
Context¶
A read of the audit log pages forward from after_seq, oldest first, kept to some types or one subject (ADR-0030). portalr's admin shows a tenant's audit log, and an admin opens it on what changed lately. Read forward, the newest entries are on its last page, and reaching it means reading every page before it.
artifactr's event log already reads from its end (artifactr ADR-0005):
- a read takes the window
after_seq < seq < before_seq; - it returns the first
limitof the window, or the lastlast, still oldest first; - a client pages back with
before_seqset to the oldestseqit holds.
Decision¶
- The audit log reads as artifactr's log does:
before_seqcloses the windowafter_seq < seq < before_seq.lastreads the last so many of the window, still oldest first.limitreads the first so many, as before. A read that gives neither reads the first 100, and one that gives both isInvalid, a 422 on the API, like artifactr's_check_page.- A client pages back with
lastand its oldest entry'sseqasbefore_seq, with the same filter. A page shorter thanlastholds all there is. - It catches up with its newest entry's
seqasafter_seq, oldest first, and a page shorter than it asked for holds every new entry. - Both compose with the filter's types and subject.
- Every surface takes them:
Grants.audit;- the storage port's
audit, and both adapters; GET /auditandGET /tenants/{tenant}/audit, asbefore_seq=andlast=.
- SQL reads the last backwards. It orders by
seqdescending withlastas its limit, and turns the rows around, as artifactr's SQL adapter does. No index is added: the tenant's, the type's and the maker's each end inseq.- On PostgreSQL a page back by type, or by maker, is that index's range read backwards. A PostgreSQL test reads
EXPLAINon a long log for each. - SQLite may read the tenant's index and filter instead, once the window has two ends. Its logs are a contributor's or a test's, so that isn't tuned.
- On PostgreSQL a page back by type, or by maker, is that index's range read backwards. A PostgreSQL test reads
- The tests run on memory, SQLite, PostgreSQL and row-level security, as the storage contract does: the last, paged back to the first, with a type and with a subject, the last of a window between two entries, catching up, and both counts refused. The router's tests read both logs from their end, a window, and refuse both counts.
- portalr's client is regenerated from the committed schema.
Options considered¶
| Option | For | Against |
|---|---|---|
before_seq and last, as artifactr's log (chosen) |
One convention across lattice's logs; a page is oldest first wherever it's read from | A newest-first view turns each page around |
A newest_first flag, paging back by before_seq |
Pages arrive in display order | A second convention beside artifactr's, for the same need |
| A cursor, opaque to the caller | Free to change how it pages | A token to decode where a sequence number says it plainly |
| Read forward to the last page in the UI | Nothing in grantr | Every page read to show the newest |
Consequences¶
- Easier: an admin opens the log on what changed last, and a client that reads artifactr's log already knows how to page grantr's.
- Harder: on SQLite, a page back by type or maker may read the tenant's index and filter; only PostgreSQL's plan is held by a test.