WebSocket auth guard — the audit-event listener that closes live
connections when a row reports a revocation.
Why this exists. register_action_ws captures account_id and
credential_type at upgrade time and reuses them for every message.
perform_action's per-message authorization phase reloads role_grants
from the DB, but session and token VALIDITY are not re-queried per message
— they are re-read once, at admission (revalidate_resolved_auth) — a
trade-off that keeps chatty WS connections fast. The cost: nothing in the
dispatch path notices when a session is revoked or a token is rotated, so a
revocation reaches an open connection only when something closes it.
Two things do. The revocation handlers close directly, after their
transaction commits (queue_connection_close in
actions/connection_closer.ts) — that close does not depend on the audit
write. This guard repeats it when the audit row is announced, which covers
a revocation emitted by code that holds no closer. Closing is idempotent, so
the overlap is a repeat, not a conflict. A success row is announced only
after its request's transaction commits (auth/audit_emitter.ts), so the
guard's close is post-commit too.
Which rows close
The guard dispatches on the RevocationScope each audit event declares in
audit_event_revocation_scopes (auth/audit_log_schema.ts), the table the
SSE guard (realtime/sse_auth_guard.ts) reads too — one declaration, so the
two cannot drift into closing a revoked token's socket while leaving its
stream open.
role is the one scope this guard deliberately does not act on:
perform_action re-authorizes every message, so a socket whose account just
lost the gating role is refused at its next message, and closing it would
only change when the caller learns. A stream has no later check, so the SSE
guard closes there.
For standard WS endpoints mounted via AppServerOptions.ws_endpoints,
create_app_server registers the guard automatically per
WsEndpointSpec.auth_guard. For custom wiring, register the handler
inside the consumer's audit_factory body (or via
audit.add_listener(...) post-assembly).
Twin of the Rust spine's register_socket_revocation_listeners.