@fuzdev/fuz_app 0.140.0 → 0.141.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (182) hide show
  1. package/dist/actions/CLAUDE.md +62 -17
  2. package/dist/actions/action_rpc.d.ts +5 -1
  3. package/dist/actions/action_rpc.d.ts.map +1 -1
  4. package/dist/actions/action_rpc.js +9 -1
  5. package/dist/actions/connection_closer.d.ts +25 -2
  6. package/dist/actions/connection_closer.d.ts.map +1 -1
  7. package/dist/actions/connection_closer.js +8 -1
  8. package/dist/actions/register_action_ws.d.ts +42 -6
  9. package/dist/actions/register_action_ws.d.ts.map +1 -1
  10. package/dist/actions/register_action_ws.js +87 -19
  11. package/dist/actions/register_ws_endpoint.d.ts +15 -6
  12. package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
  13. package/dist/actions/register_ws_endpoint.js +5 -2
  14. package/dist/actions/socket.svelte.d.ts +19 -4
  15. package/dist/actions/socket.svelte.d.ts.map +1 -1
  16. package/dist/actions/socket.svelte.js +31 -4
  17. package/dist/actions/transports.d.ts +30 -1
  18. package/dist/actions/transports.d.ts.map +1 -1
  19. package/dist/actions/transports.js +30 -1
  20. package/dist/actions/transports_ws_auth_guard.d.ts +13 -6
  21. package/dist/actions/transports_ws_auth_guard.d.ts.map +1 -1
  22. package/dist/actions/transports_ws_auth_guard.js +24 -10
  23. package/dist/actions/transports_ws_backend.d.ts +51 -5
  24. package/dist/actions/transports_ws_backend.d.ts.map +1 -1
  25. package/dist/actions/transports_ws_backend.js +62 -9
  26. package/dist/actions/ws_endpoint_spec.d.ts +11 -10
  27. package/dist/actions/ws_endpoint_spec.d.ts.map +1 -1
  28. package/dist/auth/CLAUDE.md +11 -9
  29. package/dist/auth/account_route_schema.d.ts +1 -0
  30. package/dist/auth/account_route_schema.d.ts.map +1 -1
  31. package/dist/auth/account_route_schema.js +4 -0
  32. package/dist/auth/api_token.d.ts +20 -0
  33. package/dist/auth/api_token.d.ts.map +1 -1
  34. package/dist/auth/api_token.js +21 -1
  35. package/dist/auth/api_token_queries.d.ts +22 -4
  36. package/dist/auth/api_token_queries.d.ts.map +1 -1
  37. package/dist/auth/api_token_queries.js +11 -6
  38. package/dist/auth/audit_log_routes.d.ts +10 -0
  39. package/dist/auth/audit_log_routes.d.ts.map +1 -1
  40. package/dist/auth/audit_log_routes.js +20 -0
  41. package/dist/auth/audit_log_schema.d.ts +30 -5
  42. package/dist/auth/audit_log_schema.d.ts.map +1 -1
  43. package/dist/auth/audit_log_schema.js +30 -0
  44. package/dist/auth/bearer_auth.d.ts +4 -2
  45. package/dist/auth/bearer_auth.d.ts.map +1 -1
  46. package/dist/auth/bearer_auth.js +19 -8
  47. package/dist/auth/bootstrap_route_schema.d.ts +1 -0
  48. package/dist/auth/bootstrap_route_schema.d.ts.map +1 -1
  49. package/dist/auth/bootstrap_route_schema.js +1 -0
  50. package/dist/auth/cleanup.d.ts +9 -9
  51. package/dist/auth/cleanup.js +9 -9
  52. package/dist/auth/credential_deadline.d.ts +95 -0
  53. package/dist/auth/credential_deadline.d.ts.map +1 -0
  54. package/dist/auth/credential_deadline.js +139 -0
  55. package/dist/auth/daemon_token_middleware.d.ts.map +1 -1
  56. package/dist/auth/daemon_token_middleware.js +4 -1
  57. package/dist/auth/middleware.d.ts +90 -12
  58. package/dist/auth/middleware.d.ts.map +1 -1
  59. package/dist/auth/middleware.js +115 -16
  60. package/dist/auth/request_context.d.ts +4 -2
  61. package/dist/auth/request_context.d.ts.map +1 -1
  62. package/dist/auth/request_context.js +14 -3
  63. package/dist/auth/resolved_auth.d.ts +13 -2
  64. package/dist/auth/resolved_auth.d.ts.map +1 -1
  65. package/dist/auth/resolved_auth.js +5 -4
  66. package/dist/auth/session_cookie.d.ts +4 -0
  67. package/dist/auth/session_cookie.d.ts.map +1 -1
  68. package/dist/auth/session_cookie.js +3 -2
  69. package/dist/auth/session_middleware.d.ts +1 -1
  70. package/dist/auth/session_middleware.d.ts.map +1 -1
  71. package/dist/auth/session_middleware.js +4 -1
  72. package/dist/auth/session_queries.d.ts +19 -2
  73. package/dist/auth/session_queries.d.ts.map +1 -1
  74. package/dist/auth/session_queries.js +8 -2
  75. package/dist/auth/signup_route_schema.d.ts.map +1 -1
  76. package/dist/auth/signup_route_schema.js +1 -0
  77. package/dist/hono_context.d.ts +23 -0
  78. package/dist/hono_context.d.ts.map +1 -1
  79. package/dist/hono_context.js +19 -0
  80. package/dist/http/CLAUDE.md +41 -15
  81. package/dist/http/middleware_spec.d.ts +16 -0
  82. package/dist/http/middleware_spec.d.ts.map +1 -1
  83. package/dist/http/origin.d.ts +113 -18
  84. package/dist/http/origin.d.ts.map +1 -1
  85. package/dist/http/origin.js +247 -127
  86. package/dist/http/route_spec.d.ts +21 -0
  87. package/dist/http/route_spec.d.ts.map +1 -1
  88. package/dist/http/schema_helpers.d.ts +22 -0
  89. package/dist/http/schema_helpers.d.ts.map +1 -1
  90. package/dist/http/schema_helpers.js +24 -0
  91. package/dist/http/surface.d.ts +6 -1
  92. package/dist/http/surface.d.ts.map +1 -1
  93. package/dist/http/surface.js +12 -5
  94. package/dist/realtime/sse_auth_guard.d.ts +22 -6
  95. package/dist/realtime/sse_auth_guard.d.ts.map +1 -1
  96. package/dist/realtime/sse_auth_guard.js +31 -12
  97. package/dist/realtime/subscriber_registry.d.ts +10 -1
  98. package/dist/realtime/subscriber_registry.d.ts.map +1 -1
  99. package/dist/realtime/subscriber_registry.js +10 -1
  100. package/dist/server/app_server.d.ts +20 -7
  101. package/dist/server/app_server.d.ts.map +1 -1
  102. package/dist/server/app_server.js +8 -3
  103. package/dist/server/env.d.ts +2 -1
  104. package/dist/server/env.d.ts.map +1 -1
  105. package/dist/server/env.js +7 -17
  106. package/dist/server/serve_fact_route.d.ts.map +1 -1
  107. package/dist/server/serve_fact_route.js +1 -0
  108. package/dist/testing/CLAUDE.md +154 -22
  109. package/dist/testing/adversarial_headers.d.ts.map +1 -1
  110. package/dist/testing/adversarial_headers.js +13 -6
  111. package/dist/testing/connection_closer_helpers.d.ts +6 -1
  112. package/dist/testing/connection_closer_helpers.d.ts.map +1 -1
  113. package/dist/testing/connection_closer_helpers.js +4 -0
  114. package/dist/testing/cross_backend/body_size.d.ts.map +1 -1
  115. package/dist/testing/cross_backend/body_size.js +14 -65
  116. package/dist/testing/cross_backend/capabilities.d.ts +26 -2
  117. package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
  118. package/dist/testing/cross_backend/capabilities.js +9 -3
  119. package/dist/testing/cross_backend/conformance_case.d.ts +11 -3
  120. package/dist/testing/cross_backend/conformance_case.d.ts.map +1 -1
  121. package/dist/testing/cross_backend/conformance_case.js +6 -0
  122. package/dist/testing/cross_backend/conformance_table.d.ts.map +1 -1
  123. package/dist/testing/cross_backend/conformance_table.js +20 -19
  124. package/dist/testing/cross_backend/credential_deadline.d.ts +25 -0
  125. package/dist/testing/cross_backend/credential_deadline.d.ts.map +1 -0
  126. package/dist/testing/cross_backend/credential_deadline.js +185 -0
  127. package/dist/testing/cross_backend/default_backend_configs.d.ts.map +1 -1
  128. package/dist/testing/cross_backend/default_backend_configs.js +13 -2
  129. package/dist/testing/cross_backend/full_spine_mount.d.ts +3 -0
  130. package/dist/testing/cross_backend/full_spine_mount.d.ts.map +1 -1
  131. package/dist/testing/cross_backend/full_spine_mount.js +7 -1
  132. package/dist/testing/cross_backend/narrowed_api_token.d.ts +23 -0
  133. package/dist/testing/cross_backend/narrowed_api_token.d.ts.map +1 -0
  134. package/dist/testing/cross_backend/narrowed_api_token.js +40 -0
  135. package/dist/testing/cross_backend/origin.d.ts.map +1 -1
  136. package/dist/testing/cross_backend/origin.js +6 -26
  137. package/dist/testing/cross_backend/origin_conformance.d.ts +152 -0
  138. package/dist/testing/cross_backend/origin_conformance.d.ts.map +1 -0
  139. package/dist/testing/cross_backend/origin_conformance.js +136 -0
  140. package/dist/testing/cross_backend/origin_conformance.json +1679 -0
  141. package/dist/testing/cross_backend/origin_scope.d.ts +16 -0
  142. package/dist/testing/cross_backend/origin_scope.d.ts.map +1 -0
  143. package/dist/testing/cross_backend/origin_scope.js +140 -0
  144. package/dist/testing/cross_backend/role_grant_cross_helpers.d.ts +35 -0
  145. package/dist/testing/cross_backend/role_grant_cross_helpers.d.ts.map +1 -0
  146. package/dist/testing/cross_backend/role_grant_cross_helpers.js +64 -0
  147. package/dist/testing/cross_backend/role_grant_offer_notification_ws.d.ts +8 -0
  148. package/dist/testing/cross_backend/role_grant_offer_notification_ws.d.ts.map +1 -1
  149. package/dist/testing/cross_backend/role_grant_offer_notification_ws.js +44 -33
  150. package/dist/testing/cross_backend/rust_spine_stub_backend_config.d.ts.map +1 -1
  151. package/dist/testing/cross_backend/rust_spine_stub_backend_config.js +6 -4
  152. package/dist/testing/cross_backend/spine_surface_constants.d.ts +19 -0
  153. package/dist/testing/cross_backend/spine_surface_constants.d.ts.map +1 -1
  154. package/dist/testing/cross_backend/spine_surface_constants.js +22 -0
  155. package/dist/testing/cross_backend/testing_backdoor.d.ts.map +1 -1
  156. package/dist/testing/cross_backend/testing_backdoor.js +20 -7
  157. package/dist/testing/cross_backend/testing_reset_actions.d.ts +180 -5
  158. package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
  159. package/dist/testing/cross_backend/testing_reset_actions.js +154 -5
  160. package/dist/testing/cross_backend/token_scope_surface.d.ts.map +1 -1
  161. package/dist/testing/cross_backend/token_scope_surface.js +25 -21
  162. package/dist/testing/cross_backend/transport_composition.d.ts +60 -0
  163. package/dist/testing/cross_backend/transport_composition.d.ts.map +1 -0
  164. package/dist/testing/cross_backend/transport_composition.js +364 -0
  165. package/dist/testing/cross_backend/ts_spine_backend_config.d.ts.map +1 -1
  166. package/dist/testing/cross_backend/ts_spine_backend_config.js +21 -6
  167. package/dist/testing/cross_backend/ws_role_gated.d.ts +38 -0
  168. package/dist/testing/cross_backend/ws_role_gated.d.ts.map +1 -0
  169. package/dist/testing/cross_backend/ws_role_gated.js +142 -0
  170. package/dist/testing/entities.d.ts +23 -0
  171. package/dist/testing/entities.d.ts.map +1 -1
  172. package/dist/testing/entities.js +37 -0
  173. package/dist/testing/integration.d.ts.map +1 -1
  174. package/dist/testing/integration.js +3 -1
  175. package/dist/testing/middleware.d.ts +10 -0
  176. package/dist/testing/middleware.d.ts.map +1 -1
  177. package/dist/testing/middleware.js +31 -7
  178. package/dist/testing/stubs.d.ts +16 -3
  179. package/dist/testing/stubs.d.ts.map +1 -1
  180. package/dist/testing/stubs.js +20 -7
  181. package/dist/testing/surface_invariants.js +2 -2
  182. package/package.json +1 -1
@@ -358,11 +358,12 @@ and `allow_fallback: boolean` (default `true`). Explicit
358
358
 
359
359
  ### WS close codes (`actions/transports.ts`)
360
360
 
361
- - `WS_CLOSE_SESSION_REVOKED = 4001` — server revoked auth (reason `WS_CLOSE_SESSION_REVOKED_REASON`, `Session revoked`); client enters permanent `revoked` state, no reconnect. Also the close for an upgrade refused at admission because its credential was revoked mid-upgrade — indistinguishable on the wire by design.
361
+ - `WS_CLOSE_SESSION_REVOKED = 4001` — server revoked auth (reason `WS_CLOSE_SESSION_REVOKED_REASON`, `Session revoked`); client enters permanent `revoked` state, no reconnect. Also the close for an upgrade refused at admission because its credential was revoked mid-upgrade — indistinguishable on the wire by design — and for a socket whose credential expired while open, with reason `WS_CLOSE_CREDENTIAL_EXPIRED_REASON` (`Credential expired`), which the client treats as the same `revoked` state.
362
362
  - `WS_CLOSE_CLIENT_HEARTBEAT_TIMEOUT = 4002` — client observed receive-silence past its receive timeout (`resolve_heartbeat_receive_timeout`).
363
363
  - `WS_CLOSE_SERVER_HEARTBEAT_TIMEOUT = 4003` — server observed receive-silence past `DEFAULT_SERVER_HEARTBEAT_TIMEOUT` (60s).
364
364
  - `WS_CLOSE_CONNECTION_LIMIT = 4004` — server closed this socket to admit a newer one on the same account, past the per-account connection cap (reason `connection limit`). Not a revocation: the client stays `closed` with `superseded` set and no reconnect, until `connect()` is called. Twin of the Rust spine's constant.
365
365
  - `WS_CLOSE_OUTBOUND_OVERFLOW = 4005` — the server had more waiting to be written to this socket than its outbound budget holds (reason `WS_CLOSE_OUTBOUND_OVERFLOW_REASON`, `outbound queue overflow`); what was waiting is dropped. Not a revocation, and nothing took the connection's place: the client reconnects under its ordinary backoff. Twin of the Rust spine's constant.
366
+ - `WS_CLOSE_PERMISSION_REVOKED = 4006` — the socket's account lost a role its endpoint's `required_roles` lists: a revoked global grant (reason `WS_CLOSE_PERMISSION_REVOKED_REASON`, `Permission revoked`), mid-life or at admission. Nothing is sent after it. Not a revocation of the credential: the client stays `closed` until `connect()` is called, without setting `revoked` or `superseded`, and rejects pending and queued requests `forbidden`. Twin of the Rust spine's constant.
366
367
  - `WS_CLOSE_GOING_AWAY = 1001` — the server is shutting down (reason `WS_CLOSE_GOING_AWAY_REASON`, `Server shutting down`): `close_all_sockets`, which `AppServer.close` runs. Also the close for an upgrade the shutdown refuses — pending at the close-all, or born closed after it. Not a revocation: the client reconnects under its ordinary backoff.
367
368
  - `WS_CLOSE_MESSAGE_TOO_BIG = 1009` — server received a message over its `max_message_bytes` cap (RFC 6455 "Message Too Big").
368
369
  - `WS_CLOSE_POLICY_VIOLATION = 1008` — a socket sent too much while it was still opening (before admission and `on_socket_open` completed): past `MAX_PRE_ADMISSION_FRAMES` frames or the `PRE_ADMISSION_QUEUE_BYTES_FACTOR` byte budget.
@@ -409,8 +410,10 @@ client; `null` skips the client-side measurement.
409
410
  One map keyed by `connection_id` (branded `Uuid`), in registration order:
410
411
  `#connections: Map<Uuid, ConnectionEntry>`, each entry `{ws, identity,
411
412
  admitted, abort_controller, writer}`. `identity` is the `ConnectionIdentity`
412
- `{token_hash, account_id, api_token_id}` (session sets `token_hash`, bearer
413
- sets `api_token_id`, daemon-token sets both null).
413
+ `{token_hash, account_id, api_token_id, required_roles}` (session sets
414
+ `token_hash`, bearer sets `api_token_id`, daemon-token sets both null;
415
+ `required_roles` is what the endpoint gated the upgrade on, per registration,
416
+ since endpoints with different gates can share one transport).
414
417
 
415
418
  The id is the connection's only handle — `register_pending` / `add_connection`
416
419
  return it and `admit` / `remove_connection` / `is_registered` take it. There is
@@ -426,7 +429,7 @@ revocation closes nothing and the socket is then admitted on a dead credential.
426
429
  The transport splits registration to close it (the twin of the Rust
427
430
  `fuz_realtime` `ConnectionRegistry`, same method names):
428
431
 
429
- - `register_pending(ws, token_hash, account_id, api_token_id?, abort_controller?)`
432
+ - `register_pending(ws, token_hash, account_id, api_token_id?, abort_controller?, required_roles?)`
430
433
  inserts the entry **un-admitted**. Every `close_sockets_for_*` matches it
431
434
  from that instant. Otherwise it is inert: no `send` broadcast,
432
435
  `broadcast_filtered` (the predicate never sees it), `send_to_account`, or
@@ -434,6 +437,9 @@ The transport splits registration to close it (the twin of the Rust
434
437
  `is_ready`, and `is_registered` don't see it
435
438
  (`get_pending_connection_count` does); the cap neither counts nor evicts it.
436
439
  - the caller re-reads the credential (`revalidate_resolved_auth`).
440
+ - `take_pending_close_code(connection_id)` — after a refused `admit`, the
441
+ close code the transport closed the pending entry with (4001, 4006, 1001),
442
+ read once; what `register_action_ws` logs the refusal by.
437
443
  - `admit(connection_id)` returns `false` when the entry is gone — a revocation
438
444
  closed it while pending — and evicts nothing; otherwise it enforces the cap
439
445
  (counting admitted connections only) and marks the entry admitted.
@@ -555,6 +561,11 @@ each closed connection's controller):
555
561
  - `close_sockets_for_token(api_token_id)`
556
562
  - `close_sockets_for_account(account_id)` — coarse, covers session + bearer + daemon-token
557
563
 
564
+ `close_sockets_for_role(account_id, role)` closes the account's connections
565
+ whose `required_roles` include `role`, pending included, with
566
+ `WS_CLOSE_PERMISSION_REVOKED` instead — over-closing an any-of gate (the loss
567
+ of any listed role closes, whether or not the account still holds another).
568
+
558
569
  `close_all_sockets()` closes every connection, pending and admitted, with
559
570
  `WS_CLOSE_GOING_AWAY` instead — the shutdown close, removing and aborting each
560
571
  the same way — and leaves the transport **closing** for good (`is_closing()`):
@@ -610,13 +621,17 @@ interface ConnectionCloser {
610
621
  close_sockets_for_session: (session_token_hash: string) => number;
611
622
  close_sockets_for_token: (api_token_id: string) => number;
612
623
  close_sockets_for_account: (account_id: Uuid) => number;
624
+ close_sockets_for_role: (account_id: Uuid, role: string) => number; // a revoked global grant
613
625
  close_all_sockets: () => number; // shutdown, never a revocation
614
626
  }
615
627
  ```
616
628
 
617
629
  `BackendWebsocketTransport` satisfies it structurally;
618
- `create_sse_connection_closer(registry)` (`realtime/sse_auth_guard.ts`) adapts
619
- a `SubscriberRegistry` (`close_all_sockets` is `SubscriberRegistry.close_all`).
630
+ `create_sse_connection_closer(registry, required_role)` (`realtime/sse_auth_guard.ts`) adapts
631
+ a `SubscriberRegistry` (`close_all_sockets` is `SubscriberRegistry.close_all`;
632
+ `close_sockets_for_role` closes the account's streams when `role` is the
633
+ registry's gating role). No handler closes for a role: a role revocation's
634
+ closes come from the audit listeners alone.
620
635
  Closing is idempotent and synchronous. `close_all_sockets` is the one method no
621
636
  revocation calls: `AppServer.close` runs it on `deps.connection_closer` to end
622
637
  every live connection of the backend at shutdown, and it has no
@@ -632,7 +647,8 @@ which puts the close on the request's post-commit queue (`emit_after_commit`).
632
647
  The close runs once the handler's transaction has committed and is discarded
633
648
  when the handler throws — a rolled-back revocation closes nothing. `target` is
634
649
  a `ConnectionCloseTarget` (`{kind: 'session', session_token_hash}` /
635
- `{kind: 'token', api_token_id}` / `{kind: 'account', account_id}`);
650
+ `{kind: 'token', api_token_id}` / `{kind: 'account', account_id}` /
651
+ `{kind: 'role', account_id, role}`);
636
652
  `close_connections(closer, target)` is the immediate form the queue runs. Twin
637
653
  of `queue_socket_close` / `SocketCloseTarget`.
638
654
 
@@ -702,8 +718,14 @@ guard reads too — one declaration, so the two can't drift:
702
718
  - `account` (`session_revoke_all`, `token_revoke_all`, `password_change`,
703
719
  `logout`, `account_delete`, `account_purge`) →
704
720
  `close_sockets_for_account(target_account_id ?? account_id)`
705
- - `role` (`role_grant_revoke`) → nothing, deliberately: per-message dispatch
706
- re-authorizes, so the next message is refused. The SSE guard closes here.
721
+ - `role` (`role_grant_revoke`) → `close_sockets_for_role(target_account_id ??
722
+ account_id, metadata.role)` when the revoked grant was global
723
+ (`to_global_role_revocation`); a scoped revoke closes nothing. Per-message
724
+ dispatch re-authorizes each request against its own action, but nothing
725
+ re-checks what a socket admitted by a role gate receives — every push its
726
+ endpoint sends — so the socket closes, with `WS_CLOSE_PERMISSION_REVOKED`.
727
+ The SSE guard closes its streams on the same rows. No handler closes for a
728
+ role, so this guard is the one closer.
707
729
  - `none` → nothing — every other event, and any event type fuz_app doesn't
708
730
  define
709
731
 
@@ -774,9 +796,8 @@ Cross-surface collisions (same `GET <path>` on both `RouteSpec` and
774
796
  `GET /api/:resource` vs `/api/ws`) is not detected — Hono's specific-before-wildcard routing keeps
775
797
  those working but avoid the overlap.
776
798
 
777
- `auth_guard: true` does NOT close sockets on `role_grant_revoke`
778
- (deliberate — per-connection role tracking out of scope). Compose via
779
- `extra_audit_handlers` when needed. When multiple specs share a transport,
799
+ `auth_guard` is also the one closer of a role revocation: with it off for a
800
+ transport, a revoked role closes none of its sockets. When multiple specs share a transport,
780
801
  **any** spec with `auth_guard !== false` wires the guard for that
781
802
  transport (OR-semantics).
782
803
 
@@ -829,7 +850,11 @@ audit stream shares, read off the keys the auth middleware set), then
829
850
  meanwhile, closes the socket with `WS_CLOSE_SESSION_REVOKED` and the reason a
830
851
  revoked socket gets; a re-check that throws closes it with
831
852
  `WS_CLOSE_INTERNAL_ERROR` (`internal error`) — fail closed, never admitted
832
- unchecked. During shutdown a refusal is the shutdown's instead: an upgrade
853
+ unchecked. An endpoint with `required_roles` re-reads them beside the
854
+ credential — the acting actor's grants afresh (`refresh_role_grants`), a global
855
+ grant of any listed role admitting, as the gate in front did — and refuses a
856
+ lost role with `WS_CLOSE_PERMISSION_REVOKED`; a failed role read is the same
857
+ 1011. During shutdown a refusal is the shutdown's instead: an upgrade
833
858
  pending at the close-all was closed by it with `WS_CLOSE_GOING_AWAY` and its
834
859
  controller aborted, so the refusal after the re-read — even one the closing
835
860
  database failed — writes nothing more; an upgrade that registers after it is
@@ -881,8 +906,9 @@ client withholds its close frame. So every server-side close removes the
881
906
  connection from the transport and aborts the socket's controller **at close
882
907
  time**: the transport does both for a revocation or a cap eviction, and
883
908
  `register_action_ws`'s `end_socket` does both — through
884
- `transport.close_connection` — for a heartbeat timeout, an
885
- oversized message, a failed `on_socket_open`, and a refused admission.
909
+ `transport.close_connection` — for a heartbeat timeout, a credential
910
+ expiry, an oversized message, a failed `on_socket_open`, and a refused
911
+ admission.
886
912
  `onMessage` then drops any frame whose socket has ended — no dispatch, no
887
913
  error reply. A connection the transport no longer holds though nothing closed
888
914
  its socket (a bare `transport.remove_connection`; `transport.is_registered` is
@@ -895,7 +921,10 @@ heartbeat activity.
895
921
  **Per-message authorization phase.** `perform_action` calls
896
922
  `apply_authorization_phase` per-message (HTTP and WS uniformly). Role grant
897
923
  changes during a connection lifetime are picked up on the next message —
898
- no in-place refresh, no socket-close on `role_grant_revoke`. Authentication
924
+ no in-place refresh. A revoked global grant of a role the endpoint's
925
+ `required_roles` lists closes the socket (`WS_CLOSE_PERMISSION_REVOKED`,
926
+ through `create_ws_auth_guard`), since nothing else re-checks what it
927
+ receives. Authentication
899
928
  invalidation (a session or token revoke, `password_change`, a logout, an
900
929
  account delete, a cap eviction) still closes the socket — the revoking
901
930
  handler through `connection_closer`, `create_ws_auth_guard` again on the
@@ -953,6 +982,17 @@ Tick interval is `timeout / 2`, so event-loop blockage pauses the timer itself.
953
982
  A timeout unregisters the connection and aborts its signal along with the
954
983
  `WS_CLOSE_SERVER_HEARTBEAT_TIMEOUT` close.
955
984
 
985
+ **Credential deadline.** From admission, a timer closes the socket when the
986
+ credential it upgraded on expires (`ResolvedAuth.credential_deadline` — the
987
+ earlier of a session's cookie and row expiry, an API token's row; none for an
988
+ eternal token or the daemon token): `WS_CLOSE_SESSION_REVOKED` with
989
+ `WS_CLOSE_CREDENTIAL_EXPIRED_REASON`, through `end_socket`. Expiry writes
990
+ nothing a revocation listener hears, so the socket watches its own deadline.
991
+ The wait is `schedule_credential_deadline`'s chain of timers of at most an
992
+ hour (a 30-day session is past `setTimeout`'s ceiling), each also checking the
993
+ wall clock so a host suspend does not extend the deadline, cancelled when the
994
+ socket ends. Twin of the Rust loop's `WsLoopOptions::credential_deadline`.
995
+
956
996
  Two abort signals composed via `AbortSignal.any`:
957
997
 
958
998
  - `socket_abort_controller` — per-socket, fires when the socket ends: at the server's close (revocation, cap eviction, heartbeat timeout, …) or the client's. Registered with the transport, which aborts it on the closes it makes. Drives every handler's `ctx.signal`.
@@ -1049,11 +1089,16 @@ recent frame each way — every frame, including heartbeats and the responses
1049
1089
  Reconnect policy (exponential backoff): `delay = DEFAULT_RECONNECT_DELAY * DEFAULT_BACKOFF_FACTOR ** (attempts-1)`,
1050
1090
  capped at `DEFAULT_RECONNECT_DELAY_MAX`. Every other close reconnects —
1051
1091
  `WS_CLOSE_OUTBOUND_OVERFLOW` included, the server's notice that this client
1052
- fell behind and missed messages. Two close codes never reconnect:
1092
+ fell behind and missed messages. Three close codes never reconnect:
1053
1093
 
1054
1094
  - `WS_CLOSE_SESSION_REVOKED` is **terminal** — sets `revoked`, no reconnect
1055
1095
  loop on 401, and `connect()` becomes a no-op. Pending and queued requests
1056
1096
  reject `unauthenticated`.
1097
+ - `WS_CLOSE_PERMISSION_REVOKED` is **closed until the user acts** too — status
1098
+ `closed`, no reconnect scheduled (a reconnect would be refused at the role
1099
+ gate), neither `revoked` nor `superseded` set (the credential is still
1100
+ good), pending and queued requests reject `forbidden`. `last_close_code`
1101
+ says why; `connect()` reopens.
1057
1102
  - `WS_CLOSE_CONNECTION_LIMIT` is **closed until the user acts** — sets
1058
1103
  `superseded` (reactive), status `closed`, no reconnect scheduled: the server
1059
1104
  closed this socket to admit a newer one on the same account, and
@@ -286,7 +286,11 @@ export interface CreateRpcEndpointOptions {
286
286
  *
287
287
  * The RouteSpecs use `auth: {type: 'none'}` because auth is checked per-action
288
288
  * inside the dispatcher, and `transaction: false` because transaction scope
289
- * is per-action (mutations get a transaction, reads get pool).
289
+ * is per-action (mutations get a transaction, reads get pool). They are
290
+ * marked `reads_credential`, since the dispatcher reads the resolved
291
+ * credential, so assembly refuses the pair mounted outside the auth
292
+ * middleware's scope (`assert_credential_routes_in_auth_scope`) whether it
293
+ * arrives as `rpc_endpoints` or through `create_route_specs`.
290
294
  *
291
295
  * @param options - endpoint path, actions, logger, and the two action rate limiters
292
296
  * @returns route specs (GET + POST) ready for `apply_route_specs`
@@ -1 +1 @@
1
- {"version":3,"file":"action_rpc.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/action_rpc.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"action_rpc.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/action_rpc.ts"],"names":[],"mappings":"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"}
@@ -114,7 +114,11 @@ const jsonrpc_error_envelope = (id, error) => ({
114
114
  *
115
115
  * The RouteSpecs use `auth: {type: 'none'}` because auth is checked per-action
116
116
  * inside the dispatcher, and `transaction: false` because transaction scope
117
- * is per-action (mutations get a transaction, reads get pool).
117
+ * is per-action (mutations get a transaction, reads get pool). They are
118
+ * marked `reads_credential`, since the dispatcher reads the resolved
119
+ * credential, so assembly refuses the pair mounted outside the auth
120
+ * middleware's scope (`assert_credential_routes_in_auth_scope`) whether it
121
+ * arrives as `rpc_endpoints` or through `create_route_specs`.
118
122
  *
119
123
  * @param options - endpoint path, actions, logger, and the two action rate limiters
120
124
  * @returns route specs (GET + POST) ready for `apply_route_specs`
@@ -260,6 +264,9 @@ export const create_rpc_endpoint = (options) => {
260
264
  method: 'POST',
261
265
  path: endpoint_path,
262
266
  auth: { account: 'none', actor: 'none' }, // per-action auth inside dispatcher
267
+ // the dispatcher reads the resolved credential per action, so the
268
+ // endpoint stays inside the auth middleware's scope however it mounts
269
+ reads_credential: true,
263
270
  handler: post_handler,
264
271
  description: `JSON-RPC 2.0 endpoint — ${actions.length} method${actions.length === 1 ? '' : 's'}`,
265
272
  input: z.null(), // dispatcher owns body parsing; rpc_endpoints surface has the real schemas
@@ -270,6 +277,7 @@ export const create_rpc_endpoint = (options) => {
270
277
  method: 'GET',
271
278
  path: endpoint_path,
272
279
  auth: { account: 'none', actor: 'none' }, // per-action auth inside dispatcher
280
+ reads_credential: true,
273
281
  handler: get_handler,
274
282
  description: `JSON-RPC 2.0 endpoint (cacheable reads) — ${actions.length} method${actions.length === 1 ? '' : 's'}`,
275
283
  input: z.null(), // params from query string, validated by dispatcher
@@ -9,7 +9,10 @@
9
9
  * something closes it. `ConnectionCloser` is that capability, and every
10
10
  * revocation handler holds one: the session and token revokes, the revoke-alls,
11
11
  * account delete and purge, `/logout`, `/password`, and the session and token
12
- * caps for what they evict. The auth cleanup holds one too
12
+ * caps for what they evict. A role revocation is the exception: it closes
13
+ * only the connections an endpoint gated on that role, and only through the
14
+ * audit listeners (`create_ws_auth_guard`, `create_sse_auth_guard`) — no
15
+ * handler holds a closer for it. The auth cleanup holds one too
13
16
  * (`AuthCleanupDeps.connection_closer`): its session sweep closes the
14
17
  * connections of each expired session it deletes, after that delete commits.
15
18
  *
@@ -70,6 +73,20 @@ export interface ConnectionCloser {
70
73
  * credentials.
71
74
  */
72
75
  close_sockets_for_account: (account_id: Uuid) => number;
76
+ /**
77
+ * Close the connections of `account_id` whose endpoint gated them on
78
+ * `role` — the close for a revoked **global** grant of that role (a scoped
79
+ * grant never admits at a role gate, so revoking one closes nothing; the
80
+ * caller filters, `to_global_role_revocation`). A WebSocket gets
81
+ * `WS_CLOSE_PERMISSION_REVOKED`, not a revocation's code: the credential is
82
+ * still good.
83
+ *
84
+ * **Over-closes an any-of gate**: a connection whose endpoint listed
85
+ * several roles closes on the loss of any of them, whether or not the
86
+ * account still holds another. The client reconnects into the gate if it
87
+ * still qualifies.
88
+ */
89
+ close_sockets_for_role: (account_id: Uuid, role: string) => number;
73
90
  /**
74
91
  * Close every connection, whoever opened it — the shutdown close
75
92
  * `AppServer.close` runs. Not a revocation: a WebSocket gets
@@ -82,8 +99,10 @@ export interface ConnectionCloser {
82
99
  }
83
100
  /**
84
101
  * Which connections a revocation closes — the argument of
85
- * `queue_connection_close`, one variant per credential close of
102
+ * `queue_connection_close`, one variant per revocation close of
86
103
  * `ConnectionCloser` (`close_all_sockets` is shutdown, never a revocation).
104
+ * No handler queues `role` today: the audit listeners are a role's only
105
+ * closer.
87
106
  */
88
107
  export type ConnectionCloseTarget = {
89
108
  kind: 'session';
@@ -94,6 +113,10 @@ export type ConnectionCloseTarget = {
94
113
  } | {
95
114
  kind: 'account';
96
115
  account_id: Uuid;
116
+ } | {
117
+ kind: 'role';
118
+ account_id: Uuid;
119
+ role: string;
97
120
  };
98
121
  /**
99
122
  * Close the connections `target` names, now.
@@ -1 +1 @@
1
- {"version":3,"file":"connection_closer.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/connection_closer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,yBAAyB,CAAC;AAGpD,OAAO,EAAqB,KAAK,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AAE5F;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,gBAAgB;IAChC,qGAAqG;IACrG,yBAAyB,EAAE,CAAC,kBAAkB,EAAE,MAAM,KAAK,MAAM,CAAC;IAClE,4EAA4E;IAC5E,uBAAuB,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,MAAM,CAAC;IAC1D;;;;OAIG;IACH,yBAAyB,EAAE,CAAC,UAAU,EAAE,IAAI,KAAK,MAAM,CAAC;IACxD;;;;;;;OAOG;IACH,iBAAiB,EAAE,MAAM,MAAM,CAAC;CAChC;AAED;;;;GAIG;AACH,MAAM,MAAM,qBAAqB,GAC9B;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,kBAAkB,EAAE,MAAM,CAAA;CAAE,GAC/C;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,GACvC;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,UAAU,EAAE,IAAI,CAAA;CAAE,CAAC;AAEzC;;;;;;;GAOG;AACH,eAAO,MAAM,iBAAiB,GAC7B,QAAQ,gBAAgB,EACxB,QAAQ,qBAAqB,KAC3B,MAWF,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,sBAAsB,GAClC,KAAK,sBAAsB,EAC3B,QAAQ,gBAAgB,EACxB,QAAQ,qBAAqB,KAC3B,IAIF,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,sBAAsB,EAAE,gBAKnC,CAAC;AAEH;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,cAAe,SAAQ,gBAAgB;IACvD;;;;;;;;;;;;;;;OAeG;IACH,GAAG,EAAE,CAAC,MAAM,EAAE,gBAAgB,KAAK,MAAM,IAAI,CAAC;IAC9C,2EAA2E;IAC3E,YAAY,EAAE,MAAM,MAAM,CAAC;CAC3B;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,sBAAsB,QAAO,cA4CzC,CAAC"}
1
+ {"version":3,"file":"connection_closer.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/connection_closer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,yBAAyB,CAAC;AAGpD,OAAO,EAAqB,KAAK,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AAE5F;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,gBAAgB;IAChC,qGAAqG;IACrG,yBAAyB,EAAE,CAAC,kBAAkB,EAAE,MAAM,KAAK,MAAM,CAAC;IAClE,4EAA4E;IAC5E,uBAAuB,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,MAAM,CAAC;IAC1D;;;;OAIG;IACH,yBAAyB,EAAE,CAAC,UAAU,EAAE,IAAI,KAAK,MAAM,CAAC;IACxD;;;;;;;;;;;;OAYG;IACH,sBAAsB,EAAE,CAAC,UAAU,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IACnE;;;;;;;OAOG;IACH,iBAAiB,EAAE,MAAM,MAAM,CAAC;CAChC;AAED;;;;;;GAMG;AACH,MAAM,MAAM,qBAAqB,GAC9B;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,kBAAkB,EAAE,MAAM,CAAA;CAAE,GAC/C;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,GACvC;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,UAAU,EAAE,IAAI,CAAA;CAAE,GACrC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAEpD;;;;;;;GAOG;AACH,eAAO,MAAM,iBAAiB,GAC7B,QAAQ,gBAAgB,EACxB,QAAQ,qBAAqB,KAC3B,MAaF,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,sBAAsB,GAClC,KAAK,sBAAsB,EAC3B,QAAQ,gBAAgB,EACxB,QAAQ,qBAAqB,KAC3B,IAIF,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,sBAAsB,EAAE,gBAMnC,CAAC;AAEH;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,cAAe,SAAQ,gBAAgB;IACvD;;;;;;;;;;;;;;;OAeG;IACH,GAAG,EAAE,CAAC,MAAM,EAAE,gBAAgB,KAAK,MAAM,IAAI,CAAC;IAC9C,2EAA2E;IAC3E,YAAY,EAAE,MAAM,MAAM,CAAC;CAC3B;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,sBAAsB,QAAO,cA8CzC,CAAC"}
@@ -9,7 +9,10 @@
9
9
  * something closes it. `ConnectionCloser` is that capability, and every
10
10
  * revocation handler holds one: the session and token revokes, the revoke-alls,
11
11
  * account delete and purge, `/logout`, `/password`, and the session and token
12
- * caps for what they evict. The auth cleanup holds one too
12
+ * caps for what they evict. A role revocation is the exception: it closes
13
+ * only the connections an endpoint gated on that role, and only through the
14
+ * audit listeners (`create_ws_auth_guard`, `create_sse_auth_guard`) — no
15
+ * handler holds a closer for it. The auth cleanup holds one too
13
16
  * (`AuthCleanupDeps.connection_closer`): its session sweep closes the
14
17
  * connections of each expired session it deletes, after that delete commits.
15
18
  *
@@ -60,6 +63,8 @@ export const close_connections = (closer, target) => {
60
63
  return closer.close_sockets_for_token(target.api_token_id);
61
64
  case 'account':
62
65
  return closer.close_sockets_for_account(target.account_id);
66
+ case 'role':
67
+ return closer.close_sockets_for_role(target.account_id, target.role);
63
68
  default:
64
69
  throw new UnreachableError(target);
65
70
  }
@@ -93,6 +98,7 @@ export const noop_connection_closer = Object.freeze({
93
98
  close_sockets_for_session: () => 0,
94
99
  close_sockets_for_token: () => 0,
95
100
  close_sockets_for_account: () => 0,
101
+ close_sockets_for_role: () => 0,
96
102
  close_all_sockets: () => 0
97
103
  });
98
104
  /**
@@ -150,6 +156,7 @@ export const create_realtime_closer = () => {
150
156
  close_sockets_for_session: (session_token_hash) => fan_out((member) => member.close_sockets_for_session(session_token_hash)),
151
157
  close_sockets_for_token: (api_token_id) => fan_out((member) => member.close_sockets_for_token(api_token_id)),
152
158
  close_sockets_for_account: (account_id) => fan_out((member) => member.close_sockets_for_account(account_id)),
159
+ close_sockets_for_role: (account_id, role) => fan_out((member) => member.close_sockets_for_role(account_id, role)),
153
160
  close_all_sockets: () => fan_out((member) => member.close_all_sockets())
154
161
  });
155
162
  };
@@ -54,10 +54,19 @@
54
54
  * admits a revoked credential; `docs/security.md` §Connection Admission says
55
55
  * what that rests on.
56
56
  *
57
+ * An endpoint gated on `required_roles` re-reads them beside the credential:
58
+ * its upgrade passed the role gate before the socket registered, so a role
59
+ * revocation landing in between would close nothing (`close_sockets_for_role`
60
+ * matches registrations), and the socket would be admitted without the role
61
+ * for its lifetime. The re-read reads the acting actor's grants afresh and
62
+ * admits on a global grant of any listed role, as the gate did.
63
+ *
57
64
  * A refused upgrade is closed with `WS_CLOSE_SESSION_REVOKED` (4001, the
58
- * reason a revoked socket gets), or `WS_CLOSE_INTERNAL_ERROR` (1011) when the
59
- * re-read itself failed — fail closed, never admitted unchecked. The handshake
60
- * has already answered `101`, so a refusal can only be a close frame.
65
+ * reason a revoked socket gets) for a dead credential,
66
+ * `WS_CLOSE_PERMISSION_REVOKED` (4006) for a lost role, or
67
+ * `WS_CLOSE_INTERNAL_ERROR` (1011) when either re-read itself failed — fail
68
+ * closed, never admitted unchecked. The handshake has already answered `101`,
69
+ * so a refusal can only be a close frame.
61
70
  *
62
71
  * Once the server is shutting down (`AppServer.close` ran the transport's
63
72
  * `close_all_sockets`) a refusal is `WS_CLOSE_GOING_AWAY` instead, so the
@@ -75,8 +84,17 @@
75
84
  * `PRE_ADMISSION_QUEUE_BYTES_FACTOR` × `max_message_bytes` bytes; past either
76
85
  * the socket is closed.
77
86
  *
78
- * The upgrade's role gate is *not* re-read: a role revocation closes no
79
- * WebSocket in the first place (per-message dispatch re-reads role grants).
87
+ * ## Credential deadline
88
+ *
89
+ * Expiry writes nothing a revocation listener could hear, so the socket
90
+ * watches its own credential's expiry (`ResolvedAuth.credential_deadline`,
91
+ * the earlier of a session's cookie and row, an API token's row, none for a
92
+ * daemon token or an eternal token). From admission, a timer closes it at the
93
+ * deadline with `WS_CLOSE_SESSION_REVOKED` (4001) and
94
+ * `WS_CLOSE_CREDENTIAL_EXPIRED_REASON`. The wait is a chain of timers of at
95
+ * most an hour (`schedule_credential_deadline`), so a session's 30 days — past
96
+ * `setTimeout`'s ceiling — does not fire at once, and each wake-up checks the
97
+ * wall clock too, so a host suspend does not extend the deadline.
80
98
  *
81
99
  * ## A server-closed socket dispatches nothing
82
100
  *
@@ -98,6 +116,7 @@ import type { Hono } from 'hono';
98
116
  import type { UpgradeWebSocket, WSContext } from 'hono/ws';
99
117
  import { type Logger as LoggerType } from '@fuzdev/fuz_util/log.ts';
100
118
  import type { Uuid } from '../primitive_schemas.js';
119
+ import type { RoleName } from '../auth/role_schema.js';
101
120
  import type { RateLimiter } from '../rate_limiter.js';
102
121
  import type { Db } from '../db/db.js';
103
122
  import { type Action } from './action_types.js';
@@ -272,6 +291,19 @@ export interface RegisterActionWsOptions {
272
291
  * emitter owns the pool.
273
292
  */
274
293
  db: Db;
294
+ /**
295
+ * The roles the endpoint's upgrade gate requires (any-of) — what
296
+ * `register_ws_endpoint` mounts as `require_role(required_roles)` ahead of
297
+ * this handler. Recorded on every registration
298
+ * (`ConnectionIdentity.required_roles`), so a revoked global grant of a
299
+ * listed role closes the socket (`close_sockets_for_role`), and re-read at
300
+ * admission against the acting actor's grants, so a revocation that landed
301
+ * between the gate and the registration refuses the upgrade with
302
+ * `WS_CLOSE_PERMISSION_REVOKED`. This handler does not gate on it before
303
+ * the upgrade; mount the 403 gate in front (`register_ws_endpoint` does).
304
+ * Omit, or pass `[]`, for an ungated endpoint.
305
+ */
306
+ required_roles?: ReadonlyArray<RoleName>;
275
307
  /**
276
308
  * Existing transport to register connections with. When omitted, a fresh
277
309
  * one is created and returned in the result. Pass your own to keep a
@@ -411,8 +443,12 @@ export interface RegisterActionWsResult {
411
443
  * on the audit row.
412
444
  * - Admission re-reads the credential once, after the handshake: a socket
413
445
  * whose session or token was revoked while it was upgrading closes with
414
- * `WS_CLOSE_SESSION_REVOKED`, and one whose re-check failed with
446
+ * `WS_CLOSE_SESSION_REVOKED`, one that lost a role `required_roles` lists
447
+ * with `WS_CLOSE_PERMISSION_REVOKED`, and one whose re-check failed with
415
448
  * `WS_CLOSE_INTERNAL_ERROR`. Frames sent before admission wait for it.
449
+ * - A revoked global grant of a role `required_roles` lists closes the
450
+ * account's sockets on this endpoint with `WS_CLOSE_PERMISSION_REVOKED`,
451
+ * through `create_ws_auth_guard`; nothing more is sent on them.
416
452
  * - A socket the server has closed dispatches nothing more, whether or not
417
453
  * its client answers the close.
418
454
  * - Every frame sent — responses, error frames, notifications — goes through
@@ -1 +1 @@
1
- {"version":3,"file":"register_action_ws.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/register_action_ws.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"register_action_ws.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/register_action_ws.ts"],"names":[],"mappings":"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"}