Every event the trail records names the principal that caused it. There are five kinds, and which one an event carries is what most queries filter on first.
The five
| Actor type | Who |
|---|---|
user | An authenticated person |
service | A service account or a CI token |
system | An internal job. No external actor |
anonymous | An unauthenticated request |
control-plane | A management client, calling in under the control-plane strategy |
actorId is the principal’s stable id — a user id, a service or token name. Null for system and anonymous, because there is nobody to name.
Why the control plane is its own kind
It could have been a flavor of service. It is not, and the reason is the question an adopter actually asks.
“What did the management client do” is a different question from “what did my users do.”
A control-plane caller is not a user of the application at all — it holds no session and owns no user row. Folding it into user or service would make that question unanswerable from the trail, and it is precisely the question somebody asks after granting a dashboard access to their production environment.
Filtering the trail on actorType: "control-plane" is what separates a management client’s actions from the adopter’s own users’.
Outcome is not severity
Two orthogonal axes, and conflating them loses information both ways.
Outcome — what happened:
success | It worked |
failure | It was attempted and errored |
denied | An authorization gate blocked it |
denied is first class. Blocked attempts are recorded, not only successes — which is the entire value of a security trail. A trail of things that worked cannot show you somebody trying.
Severity — how serious:
info | Routine |
warning | Notable |
critical | Alert-worthy |
A successful action can be critical (a production secret rotated). A failure can be routine (a mistyped password). Neither axis predicts the other, which is why both are stored.
Correlation fields
| Field | |
|---|---|
sessionId | Ties a chain of actions together |
ip | The client address, for correlation |
userAgent | The client string |
requestId | Ties this event to one request or trace |
resourceType, resourceId | What the action targeted |
All nullable. Null means not known, not zero and not empty. The reference has each column with its type.
What a denied event should carry
The code, not a sentence. Auth’s sign-in refusals record only the Better Auth error code — never the message, which can carry the submitted email address or other request context.
That is the general rule for this trail: no personal data in the reason. The reason is a discriminator; the identity lives in the actor and tenant columns where it is scoped by a read scope.