# `gate` + `audit` ## Purpose Central rate-limit / authorization / audit pipeline. The gateway calls `gate(...)` once before every mutating action (room create, broadcast, channel subscribe, channel publish, presence update, unicast). Splitting this out of the main daemon file keeps `gateway.ts` under the 300-line cap and makes the gate behaviour unit-testable without booting a server. ## Features - Strict ordering: rate-limit first, authorization second. DoS traffic never exercises the (potentially expensive) authorization callback. - Typed denials: `RATE_LIMITED` and `AUTH_REJECTED` codes surface the exact failure reason to the caller. - Every decision records an OTel `recordAction(action, outcome)` counter increment AND a structured audit-log entry through the injected `AuditLogger`. - `audit(...)` swallows any thrown logger error so observability bugs cannot crash the daemon. ## Usage: Basic Example ```ts import { gate } from "@suaveplan/websocket-gateway"; await gate( { rateLimitGate, authorizationCheck, auditLogger, clock: Date.now }, ctx, "room.broadcast" ); ``` ## Usage: Advanced Example ```ts import { audit, gate } from "@suaveplan/websocket-gateway"; try { await gate(deps, ctx, "channel.publish"); // … perform the publish } catch (err) { if (err instanceof WsGatewayError && err.code === WS_GATEWAY_CODES.RATE_LIMITED) { // The audit logger has already received a "denied" record; the // caller may emit an additional client-side denial event. audit(deps.auditLogger, "channel.publish", ctx, "denied", "client-handled", Date.now()); } throw err; } ``` ## API Reference - `gate(deps, ctx, action): Promise` — runs rate-limit + authz. Throws `WsGatewayError` on denial. - `audit(logger, action, ctx, outcome, reason, at): void` — pushes one record through the audit logger; swallows errors. ## Implementation Notes The function is intentionally a free function (not a class method) so the daemon can pass it any composition of callbacks — a future tier might want to gate-only without auditing, or audit-only without gating. Decoupling the four hooks from each other matches the proposal's "Hooks for unmigrated deps" stance: each callback is replaceable independently when the matching `@suaveplan/*` package lands.