KemalIdentity::SecurityEvent
One thing that happened, on its way to a SIEM.
Why the correlation fields are typed and the rest is not
blueprints/0025-maturity-validation-results.md (OPS-02) measured what a consumer could
actually build: a Log::Backend subscribed to kemal_identity.* receives every event, and
the fields arrive in a loosely-typed bag. So an adapter matches on message strings and reads
keys by name, a rename is a silent breakage, and nothing tells it which fields it can rely
on.
What a SIEM correlates on is a short, stable list: who, which credential, which tenant, from
where, and why. Those are getters here, so a rename is a compile error at every consumer.
The event-specific remainder — a role name, a factor id, a count — stays in data, because
typing forty event shapes would freeze forty things to gain nothing an adapter uses.
What is structurally absent
No raw credential, no digest, no password, no token. docs/02-security-model.md requires it
and the emitting call sites never had them; nothing here can reintroduce one, because every
field is a String? the shard populated deliberately.
Constructors
Instance methods
Whether this is an event an operator should be woken for. The severities the shard uses
deliberately: error for something broken, warn for something suspicious.
The credential that proved the request, when one did — a session id, a token id, a jti.
Never the credential itself.
The source address, when the caller passed one in. Absent rather than guessed: the shard does not read a proxy header to invent it.
The event name, as README.md's catalogue lists it: "authentication.failed",
"session.rotated", "authz.denied".
Why, for the events that carry a reason: a FailureReason, an Authz::DenialReason, or an
application authorizer's own code. Audit only — no response varies with it.