Every consequential action writes an entry: who did it, what they did, which resource it touched, when, and from where. Entries are never updated and never deleted.
What an entry holds#
| Field | What it is |
|---|---|
| Actor | The user id, plus their email as it was at the time |
| Action | A dotted verb, such as execution.start or ssh_key.generate |
| Resource | The type and id of the thing affected |
| Summary | A one-line human description |
| Metadata | Structured detail specific to the action |
| IP address | Where the request came from |
| Timestamp | When it happened |
The email is stored as a snapshot rather than looked up through the user record. That is deliberate: deleting a user must not turn their history into a row of blanks. The trail still says who did it after they have left.
What gets recorded#
Every area that changes state is covered:
- Runs: starting, aborting, and every approval decision
- Approvals: approve, reject, and the plan revision each decision applied to
- Inventory: creating, updating and deleting servers
- Credentials: generating, importing, updating and deleting SSH keys
- Secrets: connecting and disconnecting providers, and reading a secret
- Access: environment grants and revocations, access requests and their decisions
- Membership: invitations, removals, and role changes
- Git: connections, sources, syncs, and webhook state
- Sessions: opening a web SSH session
- Organisation: settings changes, switching, leaving, deletion
Approvals carry the revision#
An approval entry records both the plan revision that was requested and the revision at the moment of decision, plus whether they differed.
Without that pair, the trail can say a run was approved but cannot say what was approved. With it, a plan edited between the request and the decision is visible in the record months later, which is the question an auditor actually asks.
Append-only#
Nothing in Opafra updates or deletes an audit entry. There is no edit path, no cleanup job, and no endpoint that would let one be rewritten.
One honest limitation#
Writing an entry is deliberately allowed to fail without failing the action it describes. If the audit write errors, the error is logged and the original operation still completes.
The reasoning: an audit failure that blocked a production run would turn a bookkeeping problem into an outage, and refusing to run because a log row could not be written is worse than running with a gap. It does mean the trail is not a guaranteed-complete ledger under database failure. It is complete in normal operation, and honest about the edge.
What it does not cover#
Sign-in events are not recorded. The log covers what people did once inside, not the authentication itself. Membership changes, role changes and token creation are all recorded; a successful or failed sign-in is not.
Reading it#
The audit log is Admin and above. Operators change production; reading the record of who changed what is a separate job, matching how the roles are split.
Entries are scoped to your tenant. An organisation sees its own events, a personal workspace sees its own, and neither can see the other.
Filter by action, by resource, by actor, or search the summaries, then narrow by date. The common questions are "what did this person do", "what happened to this server", and "what happened between these two times", and the indexes exist for all three.
What is never in it#
Secret values. Metadata carries identifiers, fingerprints, revision numbers and decisions, never credentials. An SSH key's audit entry names its fingerprint and type, not any part of the key.
Next steps#
- Safety model for how the audit log fits with the other controls
- Organisations and roles for who can read it