Audit logs
For production platforms, auditing is a foundational compliance requirement (supporting SOC 2, ISO 27001, and GDPR). The platform provides a tamper-evident, cryptographically chained audit log of every state-changing operation performed by users, services, or external systems acting on your account.
Security & compliance model
Section titled “Security & compliance model”Cryptographic chaining
Section titled “Cryptographic chaining”To prevent log manipulation, deletion, or backdating, the audit log functions as an append-only ledger structured as a hash chain, rooted at the account:
- Every audit event receives a sequential sequence number (
seq) scoped to your account. One account is one chain, and everything that happens on it is in that chain. - The genesis event for an account starts at
seq = 1with aprev_hashofNULL. - Every subsequent event (
seq > 1) computes its cryptographic signature (row_hash) as a SHA-256 hash of its event fields concatenated with therow_hashof the preceding event:row_hash = SHA256(id || account_id || actor_id || action ||resource_kind || resource_id || metadata ||created_at || prev_hash) - If an actor attempts to delete or alter a past audit row, the cryptographic link breaks, invalidating the entire subsequent hash chain. A verification job runs regularly to assert the integrity of every account’s chain.
Atomic transactions (Failed audit = failed operation)
Section titled “Atomic transactions (Failed audit = failed operation)”An audit log is only useful if it is complete. The platform ensures this through strict atomic database bounds:
- Every state mutation and its corresponding audit event insertion are performed inside the same database transaction.
- If writing the audit log fails (e.g., due to database constraints, lock contention, or invalid fields), the transaction is rolled back, aborting the business mutation itself.
- To handle concurrent operations safely without hash chain forks, the platform acquires a transaction-scoped database advisory lock on the account ID. This serializes audit writes for a single account without blocking other tenants.
Actor taxonomy
Section titled “Actor taxonomy”Every audit event is attributed to a specific principal:
| Actor type | Wire representation | Description |
|---|---|---|
| User | <user_id> (bare sub) |
A human operator acting through the portal. |
| Service | service:<name> |
Internal system processes (e.g. the audit-chain sweep or the deletion saga). |
| External | external:<name> |
Third-party services acting via webhooks (e.g., Stripe processing payments). |
Audited actions & resource kinds
Section titled “Audited actions & resource kinds”The platform logs events across a strict, closed vocabulary of verbs and resource types:
Audit actions (Verbs)
Section titled “Audit actions (Verbs)”created: A resource came into existence (e.g. inviting a member).updated: A resource’s state was mutated (e.g. changing a member’s role).deleted: A resource was removed (e.g. removing a member).
Resource kinds
Section titled “Resource kinds”Auditing covers all system resources:
account(the tenancy root and billing container)member(somebody you gave access to your account)token(tor_API tokens)webhook(outbound integrations)plan(subscription tiers)
The set is closed at the writer: it is a Rust enum, so a mutation cannot invent a resource kind that this list does not name.
Reading the log
Section titled “Reading the log”Two ways, and they read one query so they cannot disagree about a row.
In the console, at Audit log under Account, or g then l. The most
recent 100 events, newest first. Every role can read it: a log only one person
can see is not evidence a team can act on.
Through the API, GET /v1/audit with your tor_ API token — your
account’s events, newest first, with a limit that defaults to 50 and is
capped at 200 rather than refused. See API tokens for
creating one.
Both carry prev_hash and row_hash on every row. That is what lets an
exported page be re-verified by somebody who does not trust us, and it is the
difference between an audit log and a list of events we assert is complete.
The log is written whether or not anything is reading it: every mutation commits its audit row in the same transaction as the change it records. This page and the API lane were both removed in September 2026 and restored the same month; not one row was lost, because the record and its surfaces are different things.