@fuzdev/fuz_app 0.63.0 → 0.65.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 (181) hide show
  1. package/dist/actions/CLAUDE.md +525 -827
  2. package/dist/actions/broadcast_api.d.ts +1 -1
  3. package/dist/actions/broadcast_api.js +1 -1
  4. package/dist/actions/cancel.d.ts +2 -2
  5. package/dist/actions/cancel.js +3 -3
  6. package/dist/actions/connection_closer.d.ts +65 -0
  7. package/dist/actions/connection_closer.d.ts.map +1 -0
  8. package/dist/actions/connection_closer.js +38 -0
  9. package/dist/actions/register_action_ws.d.ts +2 -2
  10. package/dist/actions/register_action_ws.d.ts.map +1 -1
  11. package/dist/actions/register_action_ws.js +23 -2
  12. package/dist/actions/register_ws_endpoint.d.ts +12 -10
  13. package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
  14. package/dist/actions/register_ws_endpoint.js +5 -5
  15. package/dist/actions/transports_ws_auth_guard.d.ts +25 -10
  16. package/dist/actions/transports_ws_auth_guard.d.ts.map +1 -1
  17. package/dist/actions/transports_ws_auth_guard.js +24 -9
  18. package/dist/actions/ws_endpoint_spec.d.ts +119 -0
  19. package/dist/actions/ws_endpoint_spec.d.ts.map +1 -0
  20. package/dist/actions/ws_endpoint_spec.js +13 -0
  21. package/dist/auth/CLAUDE.md +592 -1808
  22. package/dist/auth/account_action_specs.d.ts +1 -1
  23. package/dist/auth/account_actions.d.ts +13 -0
  24. package/dist/auth/account_actions.d.ts.map +1 -1
  25. package/dist/auth/account_actions.js +31 -1
  26. package/dist/auth/account_routes.d.ts +12 -2
  27. package/dist/auth/account_routes.d.ts.map +1 -1
  28. package/dist/auth/account_routes.js +55 -8
  29. package/dist/auth/account_schema.d.ts +4 -4
  30. package/dist/auth/account_schema.d.ts.map +1 -1
  31. package/dist/auth/admin_action_specs.d.ts +8 -8
  32. package/dist/auth/admin_actions.d.ts +11 -0
  33. package/dist/auth/admin_actions.d.ts.map +1 -1
  34. package/dist/auth/admin_actions.js +25 -0
  35. package/dist/auth/api_token_queries.js +1 -1
  36. package/dist/auth/audit_emitter.d.ts +56 -12
  37. package/dist/auth/audit_emitter.d.ts.map +1 -1
  38. package/dist/auth/audit_emitter.js +38 -12
  39. package/dist/auth/audit_log_ddl.d.ts +1 -1
  40. package/dist/auth/audit_log_ddl.d.ts.map +1 -1
  41. package/dist/auth/audit_log_ddl.js +1 -1
  42. package/dist/auth/audit_log_schema.d.ts +5 -3
  43. package/dist/auth/audit_log_schema.d.ts.map +1 -1
  44. package/dist/auth/audit_log_schema.js +5 -3
  45. package/dist/auth/bootstrap_account.d.ts.map +1 -1
  46. package/dist/auth/bootstrap_account.js +1 -5
  47. package/dist/auth/bootstrap_routes.d.ts +8 -2
  48. package/dist/auth/bootstrap_routes.d.ts.map +1 -1
  49. package/dist/auth/bootstrap_routes.js +15 -11
  50. package/dist/auth/invite_schema.d.ts +2 -2
  51. package/dist/auth/keyring.d.ts +6 -6
  52. package/dist/auth/keyring.js +8 -8
  53. package/dist/auth/role_grant_offer_actions.d.ts.map +1 -1
  54. package/dist/auth/role_grant_offer_actions.js +4 -2
  55. package/dist/auth/signup_routes.d.ts +1 -1
  56. package/dist/auth/standard_rpc_actions.d.ts +1 -0
  57. package/dist/auth/standard_rpc_actions.d.ts.map +1 -1
  58. package/dist/auth/standard_rpc_actions.js +1 -0
  59. package/dist/db/create_db.d.ts.map +1 -1
  60. package/dist/db/create_db.js +13 -0
  61. package/dist/dev/setup.d.ts +2 -2
  62. package/dist/dev/setup.js +3 -3
  63. package/dist/http/CLAUDE.md +225 -483
  64. package/dist/http/error_schemas.d.ts +0 -4
  65. package/dist/http/error_schemas.d.ts.map +1 -1
  66. package/dist/http/error_schemas.js +0 -4
  67. package/dist/http/ip_canonical.d.ts +100 -0
  68. package/dist/http/ip_canonical.d.ts.map +1 -0
  69. package/dist/http/ip_canonical.js +195 -0
  70. package/dist/http/origin.d.ts +14 -6
  71. package/dist/http/origin.d.ts.map +1 -1
  72. package/dist/http/origin.js +14 -32
  73. package/dist/http/pending_effects.d.ts +1 -1
  74. package/dist/http/pending_effects.js +1 -1
  75. package/dist/http/proxy.d.ts +13 -5
  76. package/dist/http/proxy.d.ts.map +1 -1
  77. package/dist/http/proxy.js +15 -23
  78. package/dist/http/surface.d.ts +50 -0
  79. package/dist/http/surface.d.ts.map +1 -1
  80. package/dist/http/surface.js +27 -1
  81. package/dist/primitive_schemas.d.ts +20 -4
  82. package/dist/primitive_schemas.d.ts.map +1 -1
  83. package/dist/primitive_schemas.js +25 -4
  84. package/dist/realtime/sse_auth_guard.d.ts +16 -4
  85. package/dist/realtime/sse_auth_guard.d.ts.map +1 -1
  86. package/dist/realtime/sse_auth_guard.js +15 -3
  87. package/dist/runtime/mock.js +1 -1
  88. package/dist/server/app_backend.d.ts +66 -19
  89. package/dist/server/app_backend.d.ts.map +1 -1
  90. package/dist/server/app_backend.js +57 -34
  91. package/dist/server/app_server.d.ts +101 -10
  92. package/dist/server/app_server.d.ts.map +1 -1
  93. package/dist/server/app_server.js +105 -6
  94. package/dist/server/env.d.ts +7 -7
  95. package/dist/server/env.d.ts.map +1 -1
  96. package/dist/server/env.js +14 -14
  97. package/dist/server/startup.d.ts.map +1 -1
  98. package/dist/server/startup.js +12 -0
  99. package/dist/server/static.d.ts +4 -4
  100. package/dist/server/static.js +7 -7
  101. package/dist/testing/CLAUDE.md +269 -59
  102. package/dist/testing/admin_integration.d.ts +18 -23
  103. package/dist/testing/admin_integration.d.ts.map +1 -1
  104. package/dist/testing/admin_integration.js +159 -202
  105. package/dist/testing/adversarial_headers.d.ts +6 -0
  106. package/dist/testing/adversarial_headers.d.ts.map +1 -1
  107. package/dist/testing/adversarial_headers.js +13 -5
  108. package/dist/testing/app_server.d.ts +148 -60
  109. package/dist/testing/app_server.d.ts.map +1 -1
  110. package/dist/testing/app_server.js +143 -54
  111. package/dist/testing/attack_surface.d.ts +8 -7
  112. package/dist/testing/attack_surface.d.ts.map +1 -1
  113. package/dist/testing/attack_surface.js +12 -8
  114. package/dist/testing/audit_completeness.d.ts +23 -22
  115. package/dist/testing/audit_completeness.d.ts.map +1 -1
  116. package/dist/testing/audit_completeness.js +199 -158
  117. package/dist/testing/audit_drift_guard.d.ts +116 -0
  118. package/dist/testing/audit_drift_guard.d.ts.map +1 -0
  119. package/dist/testing/audit_drift_guard.js +134 -0
  120. package/dist/testing/bootstrap_success.d.ts +28 -0
  121. package/dist/testing/bootstrap_success.d.ts.map +1 -0
  122. package/dist/testing/bootstrap_success.js +144 -0
  123. package/dist/testing/connection_closer_helpers.d.ts +44 -0
  124. package/dist/testing/connection_closer_helpers.d.ts.map +1 -0
  125. package/dist/testing/connection_closer_helpers.js +48 -0
  126. package/dist/testing/cross_backend/capabilities.d.ts +64 -0
  127. package/dist/testing/cross_backend/capabilities.d.ts.map +1 -0
  128. package/dist/testing/cross_backend/capabilities.js +47 -0
  129. package/dist/testing/cross_backend/setup.d.ts +215 -0
  130. package/dist/testing/cross_backend/setup.d.ts.map +1 -0
  131. package/dist/testing/cross_backend/setup.js +101 -0
  132. package/dist/testing/data_exposure.d.ts +14 -15
  133. package/dist/testing/data_exposure.d.ts.map +1 -1
  134. package/dist/testing/data_exposure.js +127 -146
  135. package/dist/testing/db_entities.d.ts +11 -1
  136. package/dist/testing/db_entities.d.ts.map +1 -1
  137. package/dist/testing/db_entities.js +13 -1
  138. package/dist/testing/integration.d.ts +35 -21
  139. package/dist/testing/integration.d.ts.map +1 -1
  140. package/dist/testing/integration.js +231 -293
  141. package/dist/testing/integration_helpers.d.ts +16 -6
  142. package/dist/testing/integration_helpers.d.ts.map +1 -1
  143. package/dist/testing/integration_helpers.js +7 -7
  144. package/dist/testing/mock_fs.d.ts.map +1 -1
  145. package/dist/testing/mock_fs.js +0 -2
  146. package/dist/testing/rate_limiting.d.ts.map +1 -1
  147. package/dist/testing/rate_limiting.js +13 -4
  148. package/dist/testing/role_grant_helpers.d.ts +31 -0
  149. package/dist/testing/role_grant_helpers.d.ts.map +1 -0
  150. package/dist/testing/role_grant_helpers.js +46 -0
  151. package/dist/testing/round_trip.d.ts +21 -16
  152. package/dist/testing/round_trip.d.ts.map +1 -1
  153. package/dist/testing/round_trip.js +65 -86
  154. package/dist/testing/rpc_helpers.d.ts +2 -1
  155. package/dist/testing/rpc_helpers.d.ts.map +1 -1
  156. package/dist/testing/rpc_round_trip.d.ts +24 -21
  157. package/dist/testing/rpc_round_trip.d.ts.map +1 -1
  158. package/dist/testing/rpc_round_trip.js +91 -106
  159. package/dist/testing/schema_introspect.d.ts +106 -0
  160. package/dist/testing/schema_introspect.d.ts.map +1 -0
  161. package/dist/testing/schema_introspect.js +123 -0
  162. package/dist/testing/schema_parity.d.ts +144 -0
  163. package/dist/testing/schema_parity.d.ts.map +1 -0
  164. package/dist/testing/schema_parity.js +233 -0
  165. package/dist/testing/sse_round_trip.d.ts.map +1 -1
  166. package/dist/testing/sse_round_trip.js +12 -6
  167. package/dist/testing/standard.d.ts +57 -25
  168. package/dist/testing/standard.d.ts.map +1 -1
  169. package/dist/testing/standard.js +62 -5
  170. package/dist/testing/stubs.d.ts +22 -3
  171. package/dist/testing/stubs.d.ts.map +1 -1
  172. package/dist/testing/stubs.js +28 -21
  173. package/dist/testing/surface_invariants.d.ts +66 -1
  174. package/dist/testing/surface_invariants.d.ts.map +1 -1
  175. package/dist/testing/surface_invariants.js +103 -1
  176. package/dist/testing/transports/surface_source.d.ts +51 -0
  177. package/dist/testing/transports/surface_source.d.ts.map +1 -0
  178. package/dist/testing/transports/surface_source.js +19 -0
  179. package/dist/ui/SurfaceExplorer.svelte +161 -2
  180. package/dist/ui/SurfaceExplorer.svelte.d.ts.map +1 -1
  181. package/package.json +4 -4
@@ -13,7 +13,7 @@
13
13
  * `ctx.notify` inside a handler — it's socket-scoped, not broadcast.
14
14
  *
15
15
  * Extracted from zzz's `backend_actions_api.ts` to stop the pattern from
16
- * drifting across zzz, zap, and undying.
16
+ * drifting across consumers.
17
17
  *
18
18
  * @module
19
19
  */
@@ -13,7 +13,7 @@
13
13
  * `ctx.notify` inside a handler — it's socket-scoped, not broadcast.
14
14
  *
15
15
  * Extracted from zzz's `backend_actions_api.ts` to stop the pattern from
16
- * drifting across zzz, zap, and undying.
16
+ * drifting across consumers.
17
17
  *
18
18
  * @module
19
19
  */
@@ -23,8 +23,8 @@
23
23
  * Wire format is snake_case `cancel` with `{request_id}`, not MCP's
24
24
  * `$/cancelRequest` with `{requestId}` — fuz_app's WS transport isn't MCP,
25
25
  * and adopting MCP's convention would leak protocol-specific framing into
26
- * the base transport. When MCP elicitation (Phase 5) lands, a translation
27
- * layer at the MCP adapter is the right seam.
26
+ * the base transport. If an MCP adapter is ever built, the translation
27
+ * layer at the adapter is the right seam.
28
28
  *
29
29
  * @module
30
30
  */
@@ -23,8 +23,8 @@
23
23
  * Wire format is snake_case `cancel` with `{request_id}`, not MCP's
24
24
  * `$/cancelRequest` with `{requestId}` — fuz_app's WS transport isn't MCP,
25
25
  * and adopting MCP's convention would leak protocol-specific framing into
26
- * the base transport. When MCP elicitation (Phase 5) lands, a translation
27
- * layer at the MCP adapter is the right seam.
26
+ * the base transport. If an MCP adapter is ever built, the translation
27
+ * layer at the adapter is the right seam.
28
28
  *
29
29
  * @module
30
30
  */
@@ -63,7 +63,7 @@ export const cancel_action_spec = {
63
63
  * tuple shape; the dispatcher short-circuits cancel notifications before any
64
64
  * handler lookup happens.
65
65
  */
66
- export const cancel_handler = () => { }; // eslint-disable-line @typescript-eslint/no-empty-function
66
+ export const cancel_handler = () => { };
67
67
  /**
68
68
  * Protocol-action tuple — spread into the server's `actions` array (or via
69
69
  * `protocol_actions` from `actions/protocol.ts`) so the dispatcher registers the
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Narrow structural capability for closing live WebSocket connections
3
+ * tied to a session token hash, API token id, or account id.
4
+ *
5
+ * **Why this exists.** Per-message authorization phase on WebSocket
6
+ * (`actions/perform_action.ts`) reloads role_grants from the DB on every
7
+ * message but does NOT re-query session / token validity — that
8
+ * trade-off keeps chatty connections fast. The cost: revocation
9
+ * doesn't actually disconnect open sockets unless something closes
10
+ * them. `transports_ws_auth_guard.ts` is the listener-based seam
11
+ * (audit-event → close), but it only fires after the audit INSERT
12
+ * succeeds — if the INSERT fails (DB error, pool exhausted, handler
13
+ * dies mid-flight) the listener never runs and the live socket keeps
14
+ * working with a stale `RequestContext` until disconnect.
15
+ *
16
+ * Used by self-service revocation handlers (`account_session_revoke` /
17
+ * `_revoke_all`, `account_token_revoke`, `logout`, `password`) and the
18
+ * admin revoke-all handlers (`admin_session_revoke_all`,
19
+ * `admin_token_revoke_all`) to eagerly drop affected sockets BEFORE
20
+ * emitting the corresponding audit event. The audit listener stays as
21
+ * a fail-safe for out-of-band emit sites (admin tools, scheduled
22
+ * jobs, SSE-driven flows). `close_sockets_for_*` is idempotent so the
23
+ * second pass is a no-op.
24
+ *
25
+ * Mirrors `zzz_server`'s `close_sockets_for_*` calls in
26
+ * `account.rs::logout_inner` / `_password_inner` /
27
+ * `handlers/account.rs::handle_account_session_revoke[_all]` /
28
+ * `_token_revoke` (landed 2026-05-16).
29
+ *
30
+ * `BackendWebsocketTransport` satisfies this interface structurally,
31
+ * so consumers pass their transport instance directly (same shape as
32
+ * `NotificationSender`). The interface stays local so handlers don't
33
+ * couple to the concrete transport, and tests can inject a capturing
34
+ * stub with no WS machinery.
35
+ *
36
+ * @module
37
+ */
38
+ /**
39
+ * Narrow capability — three idempotent socket-close methods, each
40
+ * returning the number of sockets actually closed (zero when none
41
+ * matched). Callers typically ignore the return value (used by
42
+ * telemetry / tests).
43
+ */
44
+ export interface ConnectionCloser {
45
+ /**
46
+ * Close every connection authenticated with a session whose blake3
47
+ * hash matches `session_token_hash`. Idempotent — calling on an
48
+ * already-closed session is a no-op.
49
+ */
50
+ close_sockets_for_session: (session_token_hash: string) => number;
51
+ /**
52
+ * Close every connection authenticated with the given API token id.
53
+ * Idempotent — calling on an already-revoked token is a no-op.
54
+ */
55
+ close_sockets_for_token: (api_token_id: string) => number;
56
+ /**
57
+ * Close every connection bound to `account_id`, regardless of
58
+ * credential type (session / api_token / daemon_token). Coarse
59
+ * closure used when every credential on an account is invalidated
60
+ * — password change, session-revoke-all, token-revoke-all, logout.
61
+ * Idempotent.
62
+ */
63
+ close_sockets_for_account: (account_id: string) => number;
64
+ }
65
+ //# sourceMappingURL=connection_closer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connection_closer.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/connection_closer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAEH;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAChC;;;;OAIG;IACH,yBAAyB,EAAE,CAAC,kBAAkB,EAAE,MAAM,KAAK,MAAM,CAAC;IAClE;;;OAGG;IACH,uBAAuB,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,MAAM,CAAC;IAC1D;;;;;;OAMG;IACH,yBAAyB,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,MAAM,CAAC;CAC1D"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Narrow structural capability for closing live WebSocket connections
3
+ * tied to a session token hash, API token id, or account id.
4
+ *
5
+ * **Why this exists.** Per-message authorization phase on WebSocket
6
+ * (`actions/perform_action.ts`) reloads role_grants from the DB on every
7
+ * message but does NOT re-query session / token validity — that
8
+ * trade-off keeps chatty connections fast. The cost: revocation
9
+ * doesn't actually disconnect open sockets unless something closes
10
+ * them. `transports_ws_auth_guard.ts` is the listener-based seam
11
+ * (audit-event → close), but it only fires after the audit INSERT
12
+ * succeeds — if the INSERT fails (DB error, pool exhausted, handler
13
+ * dies mid-flight) the listener never runs and the live socket keeps
14
+ * working with a stale `RequestContext` until disconnect.
15
+ *
16
+ * Used by self-service revocation handlers (`account_session_revoke` /
17
+ * `_revoke_all`, `account_token_revoke`, `logout`, `password`) and the
18
+ * admin revoke-all handlers (`admin_session_revoke_all`,
19
+ * `admin_token_revoke_all`) to eagerly drop affected sockets BEFORE
20
+ * emitting the corresponding audit event. The audit listener stays as
21
+ * a fail-safe for out-of-band emit sites (admin tools, scheduled
22
+ * jobs, SSE-driven flows). `close_sockets_for_*` is idempotent so the
23
+ * second pass is a no-op.
24
+ *
25
+ * Mirrors `zzz_server`'s `close_sockets_for_*` calls in
26
+ * `account.rs::logout_inner` / `_password_inner` /
27
+ * `handlers/account.rs::handle_account_session_revoke[_all]` /
28
+ * `_token_revoke` (landed 2026-05-16).
29
+ *
30
+ * `BackendWebsocketTransport` satisfies this interface structurally,
31
+ * so consumers pass their transport instance directly (same shape as
32
+ * `NotificationSender`). The interface stays local so handlers don't
33
+ * couple to the concrete transport, and tests can inject a capturing
34
+ * stub with no WS machinery.
35
+ *
36
+ * @module
37
+ */
38
+ export {};
@@ -42,8 +42,8 @@ export declare const DEFAULT_SERVER_HEARTBEAT_TIMEOUT = 60000;
42
42
  *
43
43
  * Fires after the transport has registered the new connection (so
44
44
  * `connection_id` is valid) but before any client message can dispatch.
45
- * Consumers use this to bootstrap per-socket domain state — e.g. undying
46
- * spawns the per-account spirit unit and pushes an initial state snapshot.
45
+ * Consumers use this to bootstrap per-socket domain state — e.g.
46
+ * spawning a per-account unit and pushing an initial state snapshot.
47
47
  */
48
48
  export interface SocketOpenContext {
49
49
  /** The raw WebSocket context — exposed for edge cases; prefer `notify` for sends. */
@@ -1 +1 @@
1
- {"version":3,"file":"register_action_ws.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/register_action_ws.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,MAAM,CAAC;AAC/B,OAAO,KAAK,EAAC,gBAAgB,EAAE,SAAS,EAAC,MAAM,SAAS,CAAC;AAEzD,OAAO,EAAS,KAAK,MAAM,IAAI,UAAU,EAAC,MAAM,yBAAyB,CAAC;AAC1E,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,wBAAwB,CAAC;AAUjD,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,oBAAoB,CAAC;AAgBpD,OAAO,KAAK,EAAC,EAAE,EAAC,MAAM,aAAa,CAAC;AACpC,OAAO,EAAC,KAAK,MAAM,EAAC,MAAM,mBAAmB,CAAC;AAI9C,OAAO,EAAC,yBAAyB,EAAE,KAAK,kBAAkB,EAAC,MAAM,4BAA4B,CAAC;AAG9F,YAAY,EAAC,MAAM,EAAC,CAAC;AAErB,0EAA0E;AAC1E,eAAO,MAAM,gCAAgC,QAAS,CAAC;AAEvD;;;;;;;GAOG;AACH,MAAM,WAAW,iBAAiB;IACjC,qFAAqF;IACrF,EAAE,EAAE,SAAS,CAAC;IACd,4EAA4E;IAC5E,aAAa,EAAE,IAAI,CAAC;IACpB,oDAAoD;IACpD,QAAQ,EAAE,kBAAkB,CAAC;IAC7B;;;OAGG;IACH,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IAClD,wFAAwF;IACxF,MAAM,EAAE,WAAW,CAAC;CACpB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,kBAAkB;IAClC,+CAA+C;IAC/C,EAAE,EAAE,SAAS,CAAC;IACd,2CAA2C;IAC3C,aAAa,EAAE,IAAI,CAAC;IACpB,kGAAkG;IAClG,QAAQ,EAAE,kBAAkB,CAAC;CAC7B;AAED,MAAM,WAAW,sBAAsB;IACtC;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wCAAwC;AACxC,MAAM,WAAW,uBAAuB;IACvC,oCAAoC;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,gCAAgC;IAChC,GAAG,EAAE,IAAI,CAAC;IACV,iEAAiE;IACjE,gBAAgB,EAAE,gBAAgB,CAAC;IACnC;;;;;;;OAOG;IACH,OAAO,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC/B;;;;;;;;;OASG;IACH,EAAE,EAAE,EAAE,CAAC;IACP;;;;OAIG;IACH,SAAS,CAAC,EAAE,yBAAyB,CAAC;IACtC;;;;;OAKG;IACH,SAAS,CAAC,EAAE,OAAO,GAAG,sBAAsB,CAAC;IAC7C,+EAA+E;IAC/E,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,qDAAqD;IACrD,GAAG,CAAC,EAAE,UAAU,CAAC;IACjB;;;;;OAKG;IACH,cAAc,CAAC,EAAE,CAAC,GAAG,EAAE,iBAAiB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClE;;;;;OAKG;IACH,eAAe,CAAC,EAAE,CAAC,GAAG,EAAE,kBAAkB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpE;;;;;;OAMG;IACH,sBAAsB,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IAC5C;;;;;OAKG;IACH,2BAA2B,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;CACjD;AAED,sCAAsC;AACtC,MAAM,WAAW,sBAAsB;IACtC,yEAAyE;IACzE,SAAS,EAAE,yBAAyB,CAAC;CACrC;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,kBAAkB,GAAI,SAAS,uBAAuB,KAAG,sBAuUrE,CAAC"}
1
+ {"version":3,"file":"register_action_ws.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/register_action_ws.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,MAAM,CAAC;AAC/B,OAAO,KAAK,EAAC,gBAAgB,EAAE,SAAS,EAAC,MAAM,SAAS,CAAC;AAEzD,OAAO,EAAS,KAAK,MAAM,IAAI,UAAU,EAAC,MAAM,yBAAyB,CAAC;AAC1E,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,wBAAwB,CAAC;AAUjD,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,oBAAoB,CAAC;AAgBpD,OAAO,KAAK,EAAC,EAAE,EAAC,MAAM,aAAa,CAAC;AACpC,OAAO,EAAC,KAAK,MAAM,EAAC,MAAM,mBAAmB,CAAC;AAI9C,OAAO,EAAC,yBAAyB,EAAE,KAAK,kBAAkB,EAAC,MAAM,4BAA4B,CAAC;AAG9F,YAAY,EAAC,MAAM,EAAC,CAAC;AAErB,0EAA0E;AAC1E,eAAO,MAAM,gCAAgC,QAAS,CAAC;AAEvD;;;;;;;GAOG;AACH,MAAM,WAAW,iBAAiB;IACjC,qFAAqF;IACrF,EAAE,EAAE,SAAS,CAAC;IACd,4EAA4E;IAC5E,aAAa,EAAE,IAAI,CAAC;IACpB,oDAAoD;IACpD,QAAQ,EAAE,kBAAkB,CAAC;IAC7B;;;OAGG;IACH,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IAClD,wFAAwF;IACxF,MAAM,EAAE,WAAW,CAAC;CACpB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,kBAAkB;IAClC,+CAA+C;IAC/C,EAAE,EAAE,SAAS,CAAC;IACd,2CAA2C;IAC3C,aAAa,EAAE,IAAI,CAAC;IACpB,kGAAkG;IAClG,QAAQ,EAAE,kBAAkB,CAAC;CAC7B;AAED,MAAM,WAAW,sBAAsB;IACtC;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wCAAwC;AACxC,MAAM,WAAW,uBAAuB;IACvC,oCAAoC;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,gCAAgC;IAChC,GAAG,EAAE,IAAI,CAAC;IACV,iEAAiE;IACjE,gBAAgB,EAAE,gBAAgB,CAAC;IACnC;;;;;;;OAOG;IACH,OAAO,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC/B;;;;;;;;;OASG;IACH,EAAE,EAAE,EAAE,CAAC;IACP;;;;OAIG;IACH,SAAS,CAAC,EAAE,yBAAyB,CAAC;IACtC;;;;;OAKG;IACH,SAAS,CAAC,EAAE,OAAO,GAAG,sBAAsB,CAAC;IAC7C,+EAA+E;IAC/E,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,qDAAqD;IACrD,GAAG,CAAC,EAAE,UAAU,CAAC;IACjB;;;;;OAKG;IACH,cAAc,CAAC,EAAE,CAAC,GAAG,EAAE,iBAAiB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClE;;;;;OAKG;IACH,eAAe,CAAC,EAAE,CAAC,GAAG,EAAE,kBAAkB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpE;;;;;;OAMG;IACH,sBAAsB,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IAC5C;;;;;OAKG;IACH,2BAA2B,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;CACjD;AAED,sCAAsC;AACtC,MAAM,WAAW,sBAAsB;IACtC,yEAAyE;IACzE,SAAS,EAAE,yBAAyB,CAAC;CACrC;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,kBAAkB,GAAI,SAAS,uBAAuB,KAAG,sBA4VrE,CAAC"}
@@ -90,6 +90,14 @@ export const register_action_ws = (options) => {
90
90
  // `credential_type` from this closure; the live request_context is
91
91
  // only used by the test-preset escape hatch (perform_action runs
92
92
  // the authorization phase fresh on every message in production).
93
+ //
94
+ // Per-message dispatch reloads role_grants via the authorization
95
+ // phase but does NOT re-query session / token validity — those
96
+ // are checked once at upgrade. Revocation enforcement therefore
97
+ // lives outside this dispatcher, in the audit-driven WS auth
98
+ // guard (`transports_ws_auth_guard.ts`). Without that guard wired
99
+ // into the audit chain, `session_revoke` / `token_revoke` are
100
+ // no-ops for existing WS connections.
93
101
  const upgrade_context = require_request_context(c);
94
102
  const account_id = upgrade_context.account.id;
95
103
  const client_ip = get_client_ip(c);
@@ -263,8 +271,21 @@ export const register_action_ws = (options) => {
263
271
  // eager fire-and-forget pool writes (audit emits, etc.);
264
272
  // `post_commit_effects` collects deferred thunks pushed
265
273
  // via `emit_after_commit` (WS notifications). Both flush
266
- // in the same try/finally so the next message sees a clean
267
- // slate.
274
+ // in the `finally` so the next message sees a clean slate.
275
+ //
276
+ // Ordering invariant — reply-before-flush is load-bearing.
277
+ // Handlers that revoke their own credential
278
+ // (`session_revoke_all`, `token_revoke` of the calling
279
+ // bearer) audit-emit events whose listener chain — wired
280
+ // by the WS auth guard in `transports_ws_auth_guard.ts` —
281
+ // closes this socket when the audit row writes. The
282
+ // synchronous `ws.send` on the success path returns
283
+ // before any close can fire (the DB write that triggers
284
+ // the chain is async — even in production with
285
+ // `await_pending_effects: false`, the listener chain only
286
+ // runs after the row lands). Inverting the order —
287
+ // flushing the queues before the send — would silently
288
+ // strand the caller without a reply.
268
289
  const pending_effects = [];
269
290
  const post_commit_effects = [];
270
291
  const notify = notify_socket(ws);
@@ -12,8 +12,8 @@
12
12
  * and build the `RequestContext` that per-message dispatch reads.
13
13
  * Multi-actor accounts must supply `?acting` to pick a persona;
14
14
  * single-actor accounts work without it.
15
- * 4. Optional `require_role(required_role)` — for endpoints gated to a
16
- * specific role.
15
+ * 4. Optional `require_role(required_roles)` — for endpoints gated to a
16
+ * non-empty any-of set of roles.
17
17
  *
18
18
  * Then delegates to `register_action_ws` for per-message JSON-RPC
19
19
  * dispatch.
@@ -25,19 +25,21 @@ import { type RegisterActionWsOptions, type RegisterActionWsResult } from './reg
25
25
  /** Options for `register_ws_endpoint`. */
26
26
  export interface RegisterWsEndpointOptions extends RegisterActionWsOptions {
27
27
  /**
28
- * Origin allowlist regexes — typically parsed from the `ALLOWED_ORIGINS`
28
+ * Origin allowlist regexes — typically parsed from the `FUZ_ALLOWED_ORIGINS`
29
29
  * env var via `parse_allowed_origins`. Passed straight to
30
30
  * `verify_request_source`.
31
31
  */
32
- allowed_origins: Array<RegExp>;
32
+ allowed_origins: ReadonlyArray<RegExp>;
33
33
  /**
34
- * Role required to upgrade. Omit for any authenticated account
35
- * (`require_auth` + actor resolution alone); set to e.g. `ROLE_ADMIN`
36
- * to gate the endpoint behind a role. The per-action `auth` in each
37
- * spec still applies at dispatch time this is a coarse upgrade-time
38
- * gate.
34
+ * Roles permitted to upgrade any-of disjunction (matches the
35
+ * underlying `require_role` semantics). Omit (or pass `[]`) for any
36
+ * authenticated account (`require_auth` + actor resolution alone);
37
+ * set to e.g. `[ROLE_ADMIN]` to gate the endpoint behind a single role
38
+ * or `[ROLE_ADMIN, ROLE_KEEPER]` to permit either. The per-action
39
+ * `auth` in each spec still applies at dispatch time — this is a coarse
40
+ * upgrade-time gate.
39
41
  */
40
- required_role?: RoleName;
42
+ required_roles?: ReadonlyArray<RoleName>;
41
43
  }
42
44
  /**
43
45
  * Mount a WebSocket endpoint with the standard upgrade stack (origin check
@@ -1 +1 @@
1
- {"version":3,"file":"register_ws_endpoint.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/register_ws_endpoint.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAYH,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,wBAAwB,CAAC;AAGrD,OAAO,EAEN,KAAK,uBAAuB,EAC5B,KAAK,sBAAsB,EAC3B,MAAM,yBAAyB,CAAC;AAEjC,0CAA0C;AAC1C,MAAM,WAAW,yBAA0B,SAAQ,uBAAuB;IACzE;;;;OAIG;IACH,eAAe,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC/B;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,QAAQ,CAAC;CACzB;AAgDD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,oBAAoB,GAChC,SAAS,yBAAyB,KAChC,sBAmBF,CAAC"}
1
+ {"version":3,"file":"register_ws_endpoint.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/register_ws_endpoint.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAYH,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,wBAAwB,CAAC;AAGrD,OAAO,EAEN,KAAK,uBAAuB,EAC5B,KAAK,sBAAsB,EAC3B,MAAM,yBAAyB,CAAC;AAEjC,0CAA0C;AAC1C,MAAM,WAAW,yBAA0B,SAAQ,uBAAuB;IACzE;;;;OAIG;IACH,eAAe,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACvC;;;;;;;;OAQG;IACH,cAAc,CAAC,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAC;CACzC;AAgDD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,oBAAoB,GAChC,SAAS,yBAAyB,KAChC,sBAmBF,CAAC"}
@@ -12,8 +12,8 @@
12
12
  * and build the `RequestContext` that per-message dispatch reads.
13
13
  * Multi-actor accounts must supply `?acting` to pick a persona;
14
14
  * single-actor accounts work without it.
15
- * 4. Optional `require_role(required_role)` — for endpoints gated to a
16
- * specific role.
15
+ * 4. Optional `require_role(required_roles)` — for endpoints gated to a
16
+ * non-empty any-of set of roles.
17
17
  *
18
18
  * Then delegates to `register_action_ws` for per-message JSON-RPC
19
19
  * dispatch.
@@ -77,12 +77,12 @@ const create_ws_authorization_middleware = (db) => {
77
77
  * then registers the `GET path` route via the inner `register_action_ws`
78
78
  */
79
79
  export const register_ws_endpoint = (options) => {
80
- const { app, path, allowed_origins, db, required_role, log = new Logger('[ws]'), ...rest } = options;
80
+ const { app, path, allowed_origins, db, required_roles, log = new Logger('[ws]'), ...rest } = options;
81
81
  app.use(path, verify_request_source(allowed_origins));
82
82
  app.use(path, require_auth);
83
83
  app.use(path, create_ws_authorization_middleware(db));
84
- if (required_role !== undefined) {
85
- app.use(path, require_role([required_role]));
84
+ if (required_roles?.length) {
85
+ app.use(path, require_role(required_roles));
86
86
  }
87
87
  return register_action_ws({ app, path, db, log, ...rest });
88
88
  };
@@ -1,12 +1,26 @@
1
1
  /**
2
2
  * WebSocket auth guard — bridges audit events to `BackendWebsocketTransport`.
3
3
  *
4
- * Mirror of `realtime/sse_auth_guard.ts` for the backend WebSocket transport.
5
- * Dispatches audit events to the right `close_sockets_for_*` method so
6
- * consumers do not re-implement the switch themselves.
4
+ * **Why this exists.** `register_action_ws` captures `account_id` and
5
+ * `credential_type` at upgrade time and reuses them for every message.
6
+ * `perform_action`'s per-message authorization phase reloads role_grants
7
+ * from the DB, but session and token VALIDITY are not re-queried — that
8
+ * trade-off keeps chatty WS connections fast. The cost: nothing in the
9
+ * dispatch path notices when a session is revoked or a token is rotated.
10
+ * This guard is the enforcement mechanism — it listens on the audit
11
+ * chain and closes affected sockets when revocation events fire, so
12
+ * revocation actually takes effect on existing connections. Without it,
13
+ * `session_revoke` / `token_revoke` are no-ops for open WS connections.
7
14
  *
8
- * Consumers wire it as `on_audit_event` on their `AppBackend` (or compose
9
- * it with other callbacks via `create_app_server`'s `audit_log_sse` path).
15
+ * Mirror of `realtime/sse_auth_guard.ts` for the backend WebSocket
16
+ * transport. Dispatches audit events to the right `close_sockets_for_*`
17
+ * method so consumers do not re-implement the switch themselves.
18
+ *
19
+ * For standard WS endpoints mounted via `AppServerOptions.ws_endpoints`,
20
+ * `create_app_server` composes the guard automatically per
21
+ * `WsEndpointSpec.auth_guard`. For custom wiring, append the handler
22
+ * inside the consumer's `audit_factory` body (or via
23
+ * `audit.on_event_chain.push(...)` post-assembly).
10
24
  *
11
25
  * @module
12
26
  */
@@ -14,7 +28,7 @@ import type { Logger } from '@fuzdev/fuz_util/log.js';
14
28
  import type { AuditLogEvent } from '../auth/audit_log_schema.js';
15
29
  import type { BackendWebsocketTransport } from './transports_ws_backend.js';
16
30
  /**
17
- * Audit-event callback shape — the function `CreateAppBackendOptions.on_audit_event`
31
+ * Audit-event callback shape — the function `CreateAuditEmitterOptions.on_audit_event`
18
32
  * accepts and that the helpers in this module return.
19
33
  *
20
34
  * Exported so consumers composing multiple handlers (typically
@@ -46,8 +60,10 @@ export declare const ws_disconnect_event_types: ReadonlySet<string>;
46
60
  * user close another user's socket by guessing a session hash or token id.
47
61
  *
48
62
  * @param log - logger for disconnect events (info level on non-zero closures)
49
- * @returns an `on_audit_event` callback suitable for `CreateAppBackendOptions`.
50
- * The returned callback mutates `transport` (closing matching sockets via
63
+ * @returns an `on_audit_event` callback suitable for `create_audit_emitter`'s
64
+ * `on_audit_event` slot, or for appending onto
65
+ * `audit.on_event_chain` post-assembly. The returned callback mutates
66
+ * `transport` (closing matching sockets via
51
67
  * `close_sockets_for_session` / `_token` / `_account`) on every relevant event.
52
68
  */
53
69
  export declare const create_ws_auth_guard: (transport: BackendWebsocketTransport, log: Logger) => AuditEventHandler;
@@ -58,8 +74,7 @@ export declare const create_ws_auth_guard: (transport: BackendWebsocketTransport
58
74
  * Sibling helper to `create_ws_auth_guard` — kept separate because
59
75
  * `ws_disconnect_event_types` deliberately omits `logout` (admin-initiated
60
76
  * revocations use `session_revoke`, while `logout` is the user-initiated
61
- * case). Three consumers (tx, undying, zzz) hand-rolled this same branch
62
- * before extraction.
77
+ * case). Multiple consumers hand-rolled this same branch before extraction.
63
78
  *
64
79
  * Compose with `create_ws_auth_guard` to handle both kinds of disconnect:
65
80
  *
@@ -1 +1 @@
1
- {"version":3,"file":"transports_ws_auth_guard.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/transports_ws_auth_guard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAEpD,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,6BAA6B,CAAC;AAC/D,OAAO,KAAK,EAAC,yBAAyB,EAAC,MAAM,4BAA4B,CAAC;AAE1E;;;;;;;;GAQG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;AAE/D;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,yBAAyB,EAAE,WAAW,CAAC,MAAM,CAMxD,CAAC;AAEH;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,oBAAoB,GAChC,WAAW,yBAAyB,EACpC,KAAK,MAAM,KACT,iBA6CF,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,eAAO,MAAM,uBAAuB,GACnC,WAAW,yBAAyB,EACpC,KAAK,MAAM,KACT,iBAaF,CAAC"}
1
+ {"version":3,"file":"transports_ws_auth_guard.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/transports_ws_auth_guard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAEpD,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,6BAA6B,CAAC;AAC/D,OAAO,KAAK,EAAC,yBAAyB,EAAC,MAAM,4BAA4B,CAAC;AAE1E;;;;;;;;GAQG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;AAE/D;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,yBAAyB,EAAE,WAAW,CAAC,MAAM,CAMxD,CAAC;AAEH;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,oBAAoB,GAChC,WAAW,yBAAyB,EACpC,KAAK,MAAM,KACT,iBA6CF,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,eAAO,MAAM,uBAAuB,GACnC,WAAW,yBAAyB,EACpC,KAAK,MAAM,KACT,iBAaF,CAAC"}
@@ -1,12 +1,26 @@
1
1
  /**
2
2
  * WebSocket auth guard — bridges audit events to `BackendWebsocketTransport`.
3
3
  *
4
- * Mirror of `realtime/sse_auth_guard.ts` for the backend WebSocket transport.
5
- * Dispatches audit events to the right `close_sockets_for_*` method so
6
- * consumers do not re-implement the switch themselves.
4
+ * **Why this exists.** `register_action_ws` captures `account_id` and
5
+ * `credential_type` at upgrade time and reuses them for every message.
6
+ * `perform_action`'s per-message authorization phase reloads role_grants
7
+ * from the DB, but session and token VALIDITY are not re-queried — that
8
+ * trade-off keeps chatty WS connections fast. The cost: nothing in the
9
+ * dispatch path notices when a session is revoked or a token is rotated.
10
+ * This guard is the enforcement mechanism — it listens on the audit
11
+ * chain and closes affected sockets when revocation events fire, so
12
+ * revocation actually takes effect on existing connections. Without it,
13
+ * `session_revoke` / `token_revoke` are no-ops for open WS connections.
7
14
  *
8
- * Consumers wire it as `on_audit_event` on their `AppBackend` (or compose
9
- * it with other callbacks via `create_app_server`'s `audit_log_sse` path).
15
+ * Mirror of `realtime/sse_auth_guard.ts` for the backend WebSocket
16
+ * transport. Dispatches audit events to the right `close_sockets_for_*`
17
+ * method so consumers do not re-implement the switch themselves.
18
+ *
19
+ * For standard WS endpoints mounted via `AppServerOptions.ws_endpoints`,
20
+ * `create_app_server` composes the guard automatically per
21
+ * `WsEndpointSpec.auth_guard`. For custom wiring, append the handler
22
+ * inside the consumer's `audit_factory` body (or via
23
+ * `audit.on_event_chain.push(...)` post-assembly).
10
24
  *
11
25
  * @module
12
26
  */
@@ -39,8 +53,10 @@ export const ws_disconnect_event_types = new Set([
39
53
  * user close another user's socket by guessing a session hash or token id.
40
54
  *
41
55
  * @param log - logger for disconnect events (info level on non-zero closures)
42
- * @returns an `on_audit_event` callback suitable for `CreateAppBackendOptions`.
43
- * The returned callback mutates `transport` (closing matching sockets via
56
+ * @returns an `on_audit_event` callback suitable for `create_audit_emitter`'s
57
+ * `on_audit_event` slot, or for appending onto
58
+ * `audit.on_event_chain` post-assembly. The returned callback mutates
59
+ * `transport` (closing matching sockets via
44
60
  * `close_sockets_for_session` / `_token` / `_account`) on every relevant event.
45
61
  */
46
62
  export const create_ws_auth_guard = (transport, log) => {
@@ -92,8 +108,7 @@ export const create_ws_auth_guard = (transport, log) => {
92
108
  * Sibling helper to `create_ws_auth_guard` — kept separate because
93
109
  * `ws_disconnect_event_types` deliberately omits `logout` (admin-initiated
94
110
  * revocations use `session_revoke`, while `logout` is the user-initiated
95
- * case). Three consumers (tx, undying, zzz) hand-rolled this same branch
96
- * before extraction.
111
+ * case). Multiple consumers hand-rolled this same branch before extraction.
97
112
  *
98
113
  * Compose with `create_ws_auth_guard` to handle both kinds of disconnect:
99
114
  *
@@ -0,0 +1,119 @@
1
+ /**
2
+ * `WsEndpointSpec` — the canonical WebSocket endpoint declaration consumed
3
+ * by `create_app_server`'s `ws_endpoints` option (mirror of `RpcEndpointSpec`
4
+ * for HTTP RPC).
5
+ *
6
+ * Lives in its own module so both `server/app_server.ts` (which mounts
7
+ * endpoints from these specs) and `http/surface.ts` (which threads the
8
+ * resolved spec list into surface generation) can import it without a
9
+ * cycle between the two.
10
+ *
11
+ * @module
12
+ */
13
+ import type { Action } from './action_types.js';
14
+ import type { RoleName } from '../auth/role_schema.js';
15
+ import type { BackendWebsocketTransport } from './transports_ws_backend.js';
16
+ import type { ServerHeartbeatOptions, SocketCloseContext, SocketOpenContext } from './register_action_ws.js';
17
+ import type { AuditEventHandler } from './transports_ws_auth_guard.js';
18
+ /**
19
+ * Declarative description of a WebSocket endpoint to be auto-mounted by
20
+ * `create_app_server`.
21
+ *
22
+ * Single source of truth for mount + surface — the same array drives
23
+ * `register_ws_endpoint`-style upgrade wiring AND the `surface.ws_endpoints`
24
+ * slot emitted into `AppSurface`, so consumers cannot drift their declared
25
+ * actions from what dispatch actually serves.
26
+ */
27
+ export interface WsEndpointSpec {
28
+ /** Hono mount path (e.g. `/api/ws`). */
29
+ path: string;
30
+ /**
31
+ * Origin allowlist regexes — typically parsed via `parse_allowed_origins`.
32
+ * Passed straight to `verify_request_source` on upgrade.
33
+ */
34
+ allowed_origins: ReadonlyArray<RegExp>;
35
+ /**
36
+ * The actions registered on this endpoint. Spread `protocol_actions`
37
+ * from `actions/protocol.ts` first to complete the
38
+ * disconnect-detection + per-request cancel pairing with the frontend
39
+ * client.
40
+ */
41
+ actions: ReadonlyArray<Action>;
42
+ /**
43
+ * Roles permitted to upgrade — any-of disjunction. Omit (or pass `[]`)
44
+ * to skip the upgrade-time role gate; per-action `auth` on each spec
45
+ * still applies at dispatch time via `perform_action`. Pass
46
+ * `[ROLE_ADMIN]` for a zap-style admin-only WS endpoint.
47
+ */
48
+ required_roles?: ReadonlyArray<RoleName>;
49
+ /**
50
+ * Existing transport to register connections with. Auto-created when
51
+ * omitted. Either way the mounted transport is reachable on
52
+ * `AppServer.ws_endpoints[path]` for broadcast / fan-out.
53
+ */
54
+ transport?: BackendWebsocketTransport;
55
+ /**
56
+ * Server-side heartbeat policy. Default-on (60s receive-silence
57
+ * timeout). Set `false` only when an upstream stack (TCP keepalive,
58
+ * Cloudflare idle timeout) already owns disconnect detection.
59
+ */
60
+ heartbeat?: boolean | ServerHeartbeatOptions;
61
+ /** Optional per-message delay for testing loading states. */
62
+ artificial_delay?: number;
63
+ /**
64
+ * Called once per socket after `transport.add_connection` but before
65
+ * the first message dispatches. See
66
+ * `RegisterActionWsOptions.on_socket_open`.
67
+ */
68
+ on_socket_open?: (ctx: SocketOpenContext) => void | Promise<void>;
69
+ /**
70
+ * Called once per socket on close, before `transport.remove_connection`.
71
+ * See `RegisterActionWsOptions.on_socket_close`.
72
+ */
73
+ on_socket_close?: (ctx: SocketCloseContext) => void | Promise<void>;
74
+ /**
75
+ * Default `true` — auto-composes `create_ws_auth_guard` +
76
+ * `create_ws_logout_closer` against this endpoint's transport and
77
+ * appends them to `deps.audit.on_event_chain`. Wiring is deduped by
78
+ * transport **reference identity** (`WeakSet<BackendWebsocketTransport>`),
79
+ * so two `WsEndpointSpec`s sharing the exact same instance get a
80
+ * single pair of listeners.
81
+ *
82
+ * **Shared-transport OR-semantics.** When multiple `WsEndpointSpec`s
83
+ * share one transport, the guard is wired iff **any** of those specs
84
+ * has `auth_guard !== false`. To opt out for a shared transport,
85
+ * every sibling spec must pass `auth_guard: false`. The default is
86
+ * "fail safe" — easier to enable than disable, and predictable
87
+ * regardless of spec order.
88
+ *
89
+ * Reference-identity dedupe means **wrapped or proxied transports
90
+ * dedupe as separate entries** — a consumer threading every
91
+ * transport through a tracing / DI / metrics shim will get a fresh
92
+ * pair of listeners per shimmed reference, even when the underlying
93
+ * transport is the same. If you wrap or proxy, set `auth_guard:
94
+ * false` on the duplicate `WsEndpointSpec`s and compose
95
+ * `create_ws_auth_guard` / `create_ws_logout_closer` against the
96
+ * underlying transport once.
97
+ *
98
+ * Set `false` when a consumer needs to compose their own callback
99
+ * from scratch — or to opt out of the auto-wiring entirely.
100
+ *
101
+ * NOTE: does NOT close sockets on `role_grant_revoke` — that omission
102
+ * is deliberate (per-connection role tracking is out of scope). A user
103
+ * whose admin role is revoked keeps their socket open; the next message
104
+ * gets `forbidden` from the per-message authorization phase. Consumers
105
+ * wanting role-revoke disconnection use `extra_audit_handlers`.
106
+ */
107
+ auth_guard?: boolean;
108
+ /**
109
+ * Extra audit-event handlers appended to `deps.audit.on_event_chain`
110
+ * AFTER the standard `auth_guard` wiring (when enabled). By the time
111
+ * these run, the standard guards may have already closed sockets. Use
112
+ * for role-revoke disconnection, custom analytics, etc.
113
+ *
114
+ * Never deduped — consumer-owned; pass the same handler twice and it
115
+ * fires twice.
116
+ */
117
+ extra_audit_handlers?: ReadonlyArray<AuditEventHandler>;
118
+ }
119
+ //# sourceMappingURL=ws_endpoint_spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ws_endpoint_spec.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/ws_endpoint_spec.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,mBAAmB,CAAC;AAC9C,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,wBAAwB,CAAC;AACrD,OAAO,KAAK,EAAC,yBAAyB,EAAC,MAAM,4BAA4B,CAAC;AAC1E,OAAO,KAAK,EACX,sBAAsB,EACtB,kBAAkB,EAClB,iBAAiB,EACjB,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,+BAA+B,CAAC;AAErE;;;;;;;;GAQG;AACH,MAAM,WAAW,cAAc;IAC9B,wCAAwC;IACxC,IAAI,EAAE,MAAM,CAAC;IACb;;;OAGG;IACH,eAAe,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACvC;;;;;OAKG;IACH,OAAO,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC/B;;;;;OAKG;IACH,cAAc,CAAC,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAC;IACzC;;;;OAIG;IACH,SAAS,CAAC,EAAE,yBAAyB,CAAC;IACtC;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,GAAG,sBAAsB,CAAC;IAC7C,6DAA6D;IAC7D,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;OAIG;IACH,cAAc,CAAC,EAAE,CAAC,GAAG,EAAE,iBAAiB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClE;;;OAGG;IACH,eAAe,CAAC,EAAE,CAAC,GAAG,EAAE,kBAAkB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAgCG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;;;;;OAQG;IACH,oBAAoB,CAAC,EAAE,aAAa,CAAC,iBAAiB,CAAC,CAAC;CACxD"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * `WsEndpointSpec` — the canonical WebSocket endpoint declaration consumed
3
+ * by `create_app_server`'s `ws_endpoints` option (mirror of `RpcEndpointSpec`
4
+ * for HTTP RPC).
5
+ *
6
+ * Lives in its own module so both `server/app_server.ts` (which mounts
7
+ * endpoints from these specs) and `http/surface.ts` (which threads the
8
+ * resolved spec list into surface generation) can import it without a
9
+ * cycle between the two.
10
+ *
11
+ * @module
12
+ */
13
+ export {};