@fuzdev/fuz_app 0.140.0 → 0.142.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 (311) hide show
  1. package/dist/actions/CLAUDE.md +72 -22
  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 +44 -4
  6. package/dist/actions/connection_closer.d.ts.map +1 -1
  7. package/dist/actions/connection_closer.js +33 -3
  8. package/dist/actions/perform_action.d.ts +10 -0
  9. package/dist/actions/perform_action.d.ts.map +1 -1
  10. package/dist/actions/perform_action.js +23 -9
  11. package/dist/actions/register_action_ws.d.ts +42 -6
  12. package/dist/actions/register_action_ws.d.ts.map +1 -1
  13. package/dist/actions/register_action_ws.js +87 -19
  14. package/dist/actions/register_ws_endpoint.d.ts +15 -6
  15. package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
  16. package/dist/actions/register_ws_endpoint.js +10 -4
  17. package/dist/actions/socket.svelte.d.ts +19 -4
  18. package/dist/actions/socket.svelte.d.ts.map +1 -1
  19. package/dist/actions/socket.svelte.js +31 -4
  20. package/dist/actions/transports.d.ts +30 -1
  21. package/dist/actions/transports.d.ts.map +1 -1
  22. package/dist/actions/transports.js +30 -1
  23. package/dist/actions/transports_ws_auth_guard.d.ts +13 -6
  24. package/dist/actions/transports_ws_auth_guard.d.ts.map +1 -1
  25. package/dist/actions/transports_ws_auth_guard.js +24 -10
  26. package/dist/actions/transports_ws_backend.d.ts +51 -5
  27. package/dist/actions/transports_ws_backend.d.ts.map +1 -1
  28. package/dist/actions/transports_ws_backend.js +62 -9
  29. package/dist/actions/ws_endpoint_spec.d.ts +11 -10
  30. package/dist/actions/ws_endpoint_spec.d.ts.map +1 -1
  31. package/dist/auth/CLAUDE.md +44 -22
  32. package/dist/auth/account_actions.d.ts.map +1 -1
  33. package/dist/auth/account_actions.js +12 -1
  34. package/dist/auth/account_route_schema.d.ts +1 -0
  35. package/dist/auth/account_route_schema.d.ts.map +1 -1
  36. package/dist/auth/account_route_schema.js +12 -0
  37. package/dist/auth/account_routes.d.ts +7 -2
  38. package/dist/auth/account_routes.d.ts.map +1 -1
  39. package/dist/auth/account_routes.js +240 -187
  40. package/dist/auth/admin_action_specs.d.ts +6 -1
  41. package/dist/auth/admin_action_specs.d.ts.map +1 -1
  42. package/dist/auth/admin_action_specs.js +6 -1
  43. package/dist/auth/admin_actions.d.ts.map +1 -1
  44. package/dist/auth/admin_actions.js +14 -2
  45. package/dist/auth/api_token.d.ts +20 -0
  46. package/dist/auth/api_token.d.ts.map +1 -1
  47. package/dist/auth/api_token.js +21 -1
  48. package/dist/auth/api_token_queries.d.ts +22 -4
  49. package/dist/auth/api_token_queries.d.ts.map +1 -1
  50. package/dist/auth/api_token_queries.js +11 -6
  51. package/dist/auth/audit_log_routes.d.ts +10 -0
  52. package/dist/auth/audit_log_routes.d.ts.map +1 -1
  53. package/dist/auth/audit_log_routes.js +20 -0
  54. package/dist/auth/audit_log_schema.d.ts +30 -5
  55. package/dist/auth/audit_log_schema.d.ts.map +1 -1
  56. package/dist/auth/audit_log_schema.js +30 -0
  57. package/dist/auth/bearer_auth.d.ts +4 -2
  58. package/dist/auth/bearer_auth.d.ts.map +1 -1
  59. package/dist/auth/bearer_auth.js +19 -8
  60. package/dist/auth/bootstrap_route_schema.d.ts +1 -0
  61. package/dist/auth/bootstrap_route_schema.d.ts.map +1 -1
  62. package/dist/auth/bootstrap_route_schema.js +1 -0
  63. package/dist/auth/bootstrap_routes.d.ts.map +1 -1
  64. package/dist/auth/bootstrap_routes.js +85 -76
  65. package/dist/auth/cell_action_specs.d.ts +7 -3
  66. package/dist/auth/cell_action_specs.d.ts.map +1 -1
  67. package/dist/auth/cell_action_specs.js +9 -5
  68. package/dist/auth/cell_actions.d.ts +3 -3
  69. package/dist/auth/cell_actions.d.ts.map +1 -1
  70. package/dist/auth/cell_actions.js +67 -38
  71. package/dist/auth/cell_authorize.d.ts +69 -8
  72. package/dist/auth/cell_authorize.d.ts.map +1 -1
  73. package/dist/auth/cell_authorize.js +89 -9
  74. package/dist/auth/cell_field_action_specs.d.ts +5 -4
  75. package/dist/auth/cell_field_action_specs.d.ts.map +1 -1
  76. package/dist/auth/cell_field_action_specs.js +7 -7
  77. package/dist/auth/cell_field_actions.d.ts +7 -5
  78. package/dist/auth/cell_field_actions.d.ts.map +1 -1
  79. package/dist/auth/cell_field_actions.js +24 -22
  80. package/dist/auth/cell_grant_action_specs.d.ts +6 -0
  81. package/dist/auth/cell_grant_action_specs.d.ts.map +1 -1
  82. package/dist/auth/cell_grant_action_specs.js +6 -0
  83. package/dist/auth/cell_grant_actions.d.ts +3 -1
  84. package/dist/auth/cell_grant_actions.d.ts.map +1 -1
  85. package/dist/auth/cell_grant_actions.js +16 -2
  86. package/dist/auth/cell_item_action_specs.d.ts +5 -4
  87. package/dist/auth/cell_item_action_specs.d.ts.map +1 -1
  88. package/dist/auth/cell_item_action_specs.js +7 -7
  89. package/dist/auth/cell_item_actions.d.ts +6 -5
  90. package/dist/auth/cell_item_actions.d.ts.map +1 -1
  91. package/dist/auth/cell_item_actions.js +23 -22
  92. package/dist/auth/cell_relation_list_limit.d.ts +20 -0
  93. package/dist/auth/cell_relation_list_limit.d.ts.map +1 -0
  94. package/dist/auth/cell_relation_list_limit.js +27 -0
  95. package/dist/auth/cell_relation_visibility.d.ts +4 -3
  96. package/dist/auth/cell_relation_visibility.d.ts.map +1 -1
  97. package/dist/auth/cell_relation_visibility.js +6 -5
  98. package/dist/auth/cell_steward.d.ts +45 -0
  99. package/dist/auth/cell_steward.d.ts.map +1 -0
  100. package/dist/auth/cell_steward.js +53 -0
  101. package/dist/auth/cleanup.d.ts +9 -9
  102. package/dist/auth/cleanup.js +9 -9
  103. package/dist/auth/credential_deadline.d.ts +95 -0
  104. package/dist/auth/credential_deadline.d.ts.map +1 -0
  105. package/dist/auth/credential_deadline.js +139 -0
  106. package/dist/auth/daemon_token_middleware.d.ts.map +1 -1
  107. package/dist/auth/daemon_token_middleware.js +4 -1
  108. package/dist/auth/middleware.d.ts +90 -12
  109. package/dist/auth/middleware.d.ts.map +1 -1
  110. package/dist/auth/middleware.js +115 -16
  111. package/dist/auth/request_context.d.ts +8 -2
  112. package/dist/auth/request_context.d.ts.map +1 -1
  113. package/dist/auth/request_context.js +20 -4
  114. package/dist/auth/resolved_auth.d.ts +13 -2
  115. package/dist/auth/resolved_auth.d.ts.map +1 -1
  116. package/dist/auth/resolved_auth.js +5 -4
  117. package/dist/auth/role_grant_offer_action_specs.d.ts +5 -0
  118. package/dist/auth/role_grant_offer_action_specs.d.ts.map +1 -1
  119. package/dist/auth/role_grant_offer_action_specs.js +6 -1
  120. package/dist/auth/role_grant_offer_actions.d.ts.map +1 -1
  121. package/dist/auth/role_grant_offer_actions.js +23 -16
  122. package/dist/auth/role_grant_offer_queries.d.ts +8 -6
  123. package/dist/auth/role_grant_offer_queries.d.ts.map +1 -1
  124. package/dist/auth/role_grant_offer_queries.js +16 -8
  125. package/dist/auth/session_cookie.d.ts +4 -0
  126. package/dist/auth/session_cookie.d.ts.map +1 -1
  127. package/dist/auth/session_cookie.js +3 -2
  128. package/dist/auth/session_middleware.d.ts +1 -1
  129. package/dist/auth/session_middleware.d.ts.map +1 -1
  130. package/dist/auth/session_middleware.js +4 -1
  131. package/dist/auth/session_queries.d.ts +19 -2
  132. package/dist/auth/session_queries.d.ts.map +1 -1
  133. package/dist/auth/session_queries.js +8 -2
  134. package/dist/auth/signup_route_schema.d.ts.map +1 -1
  135. package/dist/auth/signup_route_schema.js +1 -0
  136. package/dist/auth/signup_routes.d.ts +4 -3
  137. package/dist/auth/signup_routes.d.ts.map +1 -1
  138. package/dist/auth/signup_routes.js +157 -134
  139. package/dist/auth/token_scope.d.ts +7 -1
  140. package/dist/auth/token_scope.d.ts.map +1 -1
  141. package/dist/auth/token_scope.js +8 -2
  142. package/dist/db/CLAUDE.md +32 -19
  143. package/dist/db/cell_ddl.d.ts +1 -2
  144. package/dist/db/cell_ddl.d.ts.map +1 -1
  145. package/dist/db/cell_ddl.js +1 -2
  146. package/dist/db/cell_field_queries.d.ts +20 -18
  147. package/dist/db/cell_field_queries.d.ts.map +1 -1
  148. package/dist/db/cell_field_queries.js +28 -22
  149. package/dist/db/cell_item_queries.d.ts +20 -14
  150. package/dist/db/cell_item_queries.d.ts.map +1 -1
  151. package/dist/db/cell_item_queries.js +28 -18
  152. package/dist/db/cell_moderation.d.ts +67 -0
  153. package/dist/db/cell_moderation.d.ts.map +1 -0
  154. package/dist/db/cell_moderation.js +66 -0
  155. package/dist/db/cell_queries.d.ts +44 -14
  156. package/dist/db/cell_queries.d.ts.map +1 -1
  157. package/dist/db/cell_queries.js +68 -45
  158. package/dist/db/cell_view_sql.d.ts +102 -0
  159. package/dist/db/cell_view_sql.d.ts.map +1 -0
  160. package/dist/db/cell_view_sql.js +112 -0
  161. package/dist/db/db_pg.d.ts +1 -1
  162. package/dist/db/db_pg.js +1 -1
  163. package/dist/hono_context.d.ts +23 -0
  164. package/dist/hono_context.d.ts.map +1 -1
  165. package/dist/hono_context.js +19 -0
  166. package/dist/http/CLAUDE.md +41 -15
  167. package/dist/http/auth_shape.d.ts +5 -2
  168. package/dist/http/auth_shape.d.ts.map +1 -1
  169. package/dist/http/auth_shape.js +5 -2
  170. package/dist/http/ip_canonical.d.ts +30 -3
  171. package/dist/http/ip_canonical.d.ts.map +1 -1
  172. package/dist/http/ip_canonical.js +50 -3
  173. package/dist/http/middleware_spec.d.ts +16 -0
  174. package/dist/http/middleware_spec.d.ts.map +1 -1
  175. package/dist/http/origin.d.ts +113 -18
  176. package/dist/http/origin.d.ts.map +1 -1
  177. package/dist/http/origin.js +247 -127
  178. package/dist/http/route_spec.d.ts +21 -0
  179. package/dist/http/route_spec.d.ts.map +1 -1
  180. package/dist/http/schema_helpers.d.ts +22 -0
  181. package/dist/http/schema_helpers.d.ts.map +1 -1
  182. package/dist/http/schema_helpers.js +24 -0
  183. package/dist/http/surface.d.ts +6 -1
  184. package/dist/http/surface.d.ts.map +1 -1
  185. package/dist/http/surface.js +12 -5
  186. package/dist/primitive_schemas.d.ts +10 -1
  187. package/dist/primitive_schemas.d.ts.map +1 -1
  188. package/dist/primitive_schemas.js +11 -2
  189. package/dist/rate_limiter.d.ts +179 -12
  190. package/dist/rate_limiter.d.ts.map +1 -1
  191. package/dist/rate_limiter.js +352 -42
  192. package/dist/realtime/sse_auth_guard.d.ts +22 -6
  193. package/dist/realtime/sse_auth_guard.d.ts.map +1 -1
  194. package/dist/realtime/sse_auth_guard.js +31 -12
  195. package/dist/realtime/subscriber_registry.d.ts +10 -1
  196. package/dist/realtime/subscriber_registry.d.ts.map +1 -1
  197. package/dist/realtime/subscriber_registry.js +10 -1
  198. package/dist/server/app_server.d.ts +20 -7
  199. package/dist/server/app_server.d.ts.map +1 -1
  200. package/dist/server/app_server.js +8 -3
  201. package/dist/server/env.d.ts +2 -1
  202. package/dist/server/env.d.ts.map +1 -1
  203. package/dist/server/env.js +7 -17
  204. package/dist/server/serve_fact_route.d.ts +8 -4
  205. package/dist/server/serve_fact_route.d.ts.map +1 -1
  206. package/dist/server/serve_fact_route.js +16 -6
  207. package/dist/testing/CLAUDE.md +164 -25
  208. package/dist/testing/adversarial_headers.d.ts.map +1 -1
  209. package/dist/testing/adversarial_headers.js +13 -6
  210. package/dist/testing/connection_closer_helpers.d.ts +6 -1
  211. package/dist/testing/connection_closer_helpers.d.ts.map +1 -1
  212. package/dist/testing/connection_closer_helpers.js +4 -0
  213. package/dist/testing/cross_backend/account_lifecycle.d.ts.map +1 -1
  214. package/dist/testing/cross_backend/account_lifecycle.js +37 -2
  215. package/dist/testing/cross_backend/body_size.d.ts.map +1 -1
  216. package/dist/testing/cross_backend/body_size.js +14 -65
  217. package/dist/testing/cross_backend/capabilities.d.ts +26 -2
  218. package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
  219. package/dist/testing/cross_backend/capabilities.js +9 -3
  220. package/dist/testing/cross_backend/cell_gated_create.d.ts +38 -0
  221. package/dist/testing/cross_backend/cell_gated_create.d.ts.map +1 -1
  222. package/dist/testing/cross_backend/cell_gated_create.js +401 -5
  223. package/dist/testing/cross_backend/cell_relations.d.ts.map +1 -1
  224. package/dist/testing/cross_backend/cell_relations.js +145 -2
  225. package/dist/testing/cross_backend/conformance_case.d.ts +11 -3
  226. package/dist/testing/cross_backend/conformance_case.d.ts.map +1 -1
  227. package/dist/testing/cross_backend/conformance_case.js +6 -0
  228. package/dist/testing/cross_backend/conformance_table.d.ts.map +1 -1
  229. package/dist/testing/cross_backend/conformance_table.js +20 -19
  230. package/dist/testing/cross_backend/credential_deadline.d.ts +25 -0
  231. package/dist/testing/cross_backend/credential_deadline.d.ts.map +1 -0
  232. package/dist/testing/cross_backend/credential_deadline.js +185 -0
  233. package/dist/testing/cross_backend/default_backend_configs.d.ts.map +1 -1
  234. package/dist/testing/cross_backend/default_backend_configs.js +13 -2
  235. package/dist/testing/cross_backend/expected_schema.json +0 -1
  236. package/dist/testing/cross_backend/full_spine_mount.d.ts +3 -0
  237. package/dist/testing/cross_backend/full_spine_mount.d.ts.map +1 -1
  238. package/dist/testing/cross_backend/full_spine_mount.js +7 -1
  239. package/dist/testing/cross_backend/login_security.d.ts +16 -0
  240. package/dist/testing/cross_backend/login_security.d.ts.map +1 -1
  241. package/dist/testing/cross_backend/login_security.js +154 -0
  242. package/dist/testing/cross_backend/narrowed_api_token.d.ts +23 -0
  243. package/dist/testing/cross_backend/narrowed_api_token.d.ts.map +1 -0
  244. package/dist/testing/cross_backend/narrowed_api_token.js +40 -0
  245. package/dist/testing/cross_backend/origin.d.ts.map +1 -1
  246. package/dist/testing/cross_backend/origin.js +6 -26
  247. package/dist/testing/cross_backend/origin_conformance.d.ts +152 -0
  248. package/dist/testing/cross_backend/origin_conformance.d.ts.map +1 -0
  249. package/dist/testing/cross_backend/origin_conformance.js +136 -0
  250. package/dist/testing/cross_backend/origin_conformance.json +1679 -0
  251. package/dist/testing/cross_backend/origin_scope.d.ts +16 -0
  252. package/dist/testing/cross_backend/origin_scope.d.ts.map +1 -0
  253. package/dist/testing/cross_backend/origin_scope.js +140 -0
  254. package/dist/testing/cross_backend/query_shape.d.ts.map +1 -1
  255. package/dist/testing/cross_backend/query_shape.js +24 -2
  256. package/dist/testing/cross_backend/role_grant_cross_helpers.d.ts +35 -0
  257. package/dist/testing/cross_backend/role_grant_cross_helpers.d.ts.map +1 -0
  258. package/dist/testing/cross_backend/role_grant_cross_helpers.js +64 -0
  259. package/dist/testing/cross_backend/role_grant_offer_notification_ws.d.ts +8 -0
  260. package/dist/testing/cross_backend/role_grant_offer_notification_ws.d.ts.map +1 -1
  261. package/dist/testing/cross_backend/role_grant_offer_notification_ws.js +44 -33
  262. package/dist/testing/cross_backend/role_grant_participation.d.ts.map +1 -1
  263. package/dist/testing/cross_backend/role_grant_participation.js +64 -0
  264. package/dist/testing/cross_backend/rust_spine_stub_backend_config.d.ts.map +1 -1
  265. package/dist/testing/cross_backend/rust_spine_stub_backend_config.js +6 -4
  266. package/dist/testing/cross_backend/spine_surface_constants.d.ts +20 -1
  267. package/dist/testing/cross_backend/spine_surface_constants.d.ts.map +1 -1
  268. package/dist/testing/cross_backend/spine_surface_constants.js +23 -1
  269. package/dist/testing/cross_backend/testing_backdoor.d.ts.map +1 -1
  270. package/dist/testing/cross_backend/testing_backdoor.js +20 -7
  271. package/dist/testing/cross_backend/testing_reset_actions.d.ts +180 -5
  272. package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
  273. package/dist/testing/cross_backend/testing_reset_actions.js +154 -5
  274. package/dist/testing/cross_backend/token_scope_surface.d.ts.map +1 -1
  275. package/dist/testing/cross_backend/token_scope_surface.js +25 -21
  276. package/dist/testing/cross_backend/transport_composition.d.ts +60 -0
  277. package/dist/testing/cross_backend/transport_composition.d.ts.map +1 -0
  278. package/dist/testing/cross_backend/transport_composition.js +364 -0
  279. package/dist/testing/cross_backend/ts_spine_backend_config.d.ts.map +1 -1
  280. package/dist/testing/cross_backend/ts_spine_backend_config.js +21 -6
  281. package/dist/testing/cross_backend/ws_action_rate_limit.d.ts.map +1 -1
  282. package/dist/testing/cross_backend/ws_action_rate_limit.js +21 -1
  283. package/dist/testing/cross_backend/ws_role_gated.d.ts +38 -0
  284. package/dist/testing/cross_backend/ws_role_gated.d.ts.map +1 -0
  285. package/dist/testing/cross_backend/ws_role_gated.js +142 -0
  286. package/dist/testing/entities.d.ts +23 -0
  287. package/dist/testing/entities.d.ts.map +1 -1
  288. package/dist/testing/entities.js +37 -0
  289. package/dist/testing/integration.d.ts.map +1 -1
  290. package/dist/testing/integration.js +3 -1
  291. package/dist/testing/middleware.d.ts +10 -0
  292. package/dist/testing/middleware.d.ts.map +1 -1
  293. package/dist/testing/middleware.js +31 -7
  294. package/dist/testing/schema_introspect.js +1 -1
  295. package/dist/testing/schema_parity.d.ts +4 -3
  296. package/dist/testing/schema_parity.d.ts.map +1 -1
  297. package/dist/testing/schema_parity.js +4 -3
  298. package/dist/testing/schema_ready_fixture.d.ts +2 -4
  299. package/dist/testing/schema_ready_fixture.d.ts.map +1 -1
  300. package/dist/testing/schema_ready_fixture.js +2 -4
  301. package/dist/testing/stubs.d.ts +16 -3
  302. package/dist/testing/stubs.d.ts.map +1 -1
  303. package/dist/testing/stubs.js +20 -7
  304. package/dist/testing/surface_invariants.js +2 -2
  305. package/dist/ui/auth_state.svelte.d.ts +10 -0
  306. package/dist/ui/auth_state.svelte.d.ts.map +1 -1
  307. package/dist/ui/auth_state.svelte.js +22 -4
  308. package/package.json +1 -1
  309. package/dist/db/cell_history_ddl.d.ts +0 -38
  310. package/dist/db/cell_history_ddl.d.ts.map +0 -1
  311. package/dist/db/cell_history_ddl.js +0 -61
@@ -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
@@ -626,15 +641,21 @@ closed) — so a closed transport or registry hosts nothing more. Its Rust
626
641
  counterpart is `ShutdownCloser::close_all_sockets`, a supertrait of
627
642
  `SocketRevoker`.
628
643
 
629
- **Every revocation closes after its commit.** A handler never calls a closer
630
- inline: it calls `queue_connection_close(ctx, deps.connection_closer, target)`,
644
+ **Every revocation closes after its commit.** A handler inside a route or
645
+ action transaction never calls a closer inline: it calls
646
+ `queue_connection_close(ctx, deps.connection_closer, target)`,
631
647
  which puts the close on the request's post-commit queue (`emit_after_commit`).
632
648
  The close runs once the handler's transaction has committed and is discarded
633
649
  when the handler throws — a rolled-back revocation closes nothing. `target` is
634
650
  a `ConnectionCloseTarget` (`{kind: 'session', session_token_hash}` /
635
- `{kind: 'token', api_token_id}` / `{kind: 'account', account_id}`);
636
- `close_connections(closer, target)` is the immediate form the queue runs. Twin
637
- of `queue_socket_close` / `SocketCloseTarget`.
651
+ `{kind: 'token', api_token_id}` / `{kind: 'account', account_id}` /
652
+ `{kind: 'role', account_id, role}`);
653
+ `close_connections(closer, target)` is the immediate form the queue runs. A
654
+ handler with no wrapping transaction (`/login`, `/password`) opens its own
655
+ around its writes and closes straight after that commit with
656
+ `close_connections_after_commit(closer, target, log)`, which logs a failing
657
+ close as the post-commit flush does. Twin of `queue_socket_close` /
658
+ `SocketCloseTarget`.
638
659
 
639
660
  The order is load-bearing for admission: a connection registers, then re-reads
640
661
  its credential, so a close that missed the registration must belong to a
@@ -702,8 +723,14 @@ guard reads too — one declaration, so the two can't drift:
702
723
  - `account` (`session_revoke_all`, `token_revoke_all`, `password_change`,
703
724
  `logout`, `account_delete`, `account_purge`) →
704
725
  `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.
726
+ - `role` (`role_grant_revoke`) → `close_sockets_for_role(target_account_id ??
727
+ account_id, metadata.role)` when the revoked grant was global
728
+ (`to_global_role_revocation`); a scoped revoke closes nothing. Per-message
729
+ dispatch re-authorizes each request against its own action, but nothing
730
+ re-checks what a socket admitted by a role gate receives — every push its
731
+ endpoint sends — so the socket closes, with `WS_CLOSE_PERMISSION_REVOKED`.
732
+ The SSE guard closes its streams on the same rows. No handler closes for a
733
+ role, so this guard is the one closer.
707
734
  - `none` → nothing — every other event, and any event type fuz_app doesn't
708
735
  define
709
736
 
@@ -774,9 +801,8 @@ Cross-surface collisions (same `GET <path>` on both `RouteSpec` and
774
801
  `GET /api/:resource` vs `/api/ws`) is not detected — Hono's specific-before-wildcard routing keeps
775
802
  those working but avoid the overlap.
776
803
 
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,
804
+ `auth_guard` is also the one closer of a role revocation: with it off for a
805
+ transport, a revoked role closes none of its sockets. When multiple specs share a transport,
780
806
  **any** spec with `auth_guard !== false` wires the guard for that
781
807
  transport (OR-semantics).
782
808
 
@@ -791,7 +817,7 @@ Composes the standard upgrade stack:
791
817
  1. `verify_request_source(allowed_origins)`
792
818
  2. `require_auth`
793
819
  3. `require_token_scope('surface:ws_upgrade')` — a narrowed token cannot hold a socket
794
- 4. Upgrade-time authorization phase — resolves the acting actor, seeds `REQUEST_CONTEXT_KEY` for the inner `register_action_ws`
820
+ 4. Upgrade-time authorization phase — resolves the acting actor (`?acting=` through `parse_acting`; malformed reads as omitted), seeds `REQUEST_CONTEXT_KEY` for the inner `register_action_ws`
795
821
  5. Optional `require_role(required_roles)` — any-of disjunction (coarse upgrade-time gate; per-action `auth` in each spec still applies at dispatch time)
796
822
  6. Delegates to `register_action_ws`
797
823
 
@@ -829,7 +855,11 @@ audit stream shares, read off the keys the auth middleware set), then
829
855
  meanwhile, closes the socket with `WS_CLOSE_SESSION_REVOKED` and the reason a
830
856
  revoked socket gets; a re-check that throws closes it with
831
857
  `WS_CLOSE_INTERNAL_ERROR` (`internal error`) — fail closed, never admitted
832
- unchecked. During shutdown a refusal is the shutdown's instead: an upgrade
858
+ unchecked. An endpoint with `required_roles` re-reads them beside the
859
+ credential — the acting actor's grants afresh (`refresh_role_grants`), a global
860
+ grant of any listed role admitting, as the gate in front did — and refuses a
861
+ lost role with `WS_CLOSE_PERMISSION_REVOKED`; a failed role read is the same
862
+ 1011. During shutdown a refusal is the shutdown's instead: an upgrade
833
863
  pending at the close-all was closed by it with `WS_CLOSE_GOING_AWAY` and its
834
864
  controller aborted, so the refusal after the re-read — even one the closing
835
865
  database failed — writes nothing more; an upgrade that registers after it is
@@ -881,8 +911,9 @@ client withholds its close frame. So every server-side close removes the
881
911
  connection from the transport and aborts the socket's controller **at close
882
912
  time**: the transport does both for a revocation or a cap eviction, and
883
913
  `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.
914
+ `transport.close_connection` — for a heartbeat timeout, a credential
915
+ expiry, an oversized message, a failed `on_socket_open`, and a refused
916
+ admission.
886
917
  `onMessage` then drops any frame whose socket has ended — no dispatch, no
887
918
  error reply. A connection the transport no longer holds though nothing closed
888
919
  its socket (a bare `transport.remove_connection`; `transport.is_registered` is
@@ -895,7 +926,10 @@ heartbeat activity.
895
926
  **Per-message authorization phase.** `perform_action` calls
896
927
  `apply_authorization_phase` per-message (HTTP and WS uniformly). Role grant
897
928
  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
929
+ no in-place refresh. A revoked global grant of a role the endpoint's
930
+ `required_roles` lists closes the socket (`WS_CLOSE_PERMISSION_REVOKED`,
931
+ through `create_ws_auth_guard`), since nothing else re-checks what it
932
+ receives. Authentication
899
933
  invalidation (a session or token revoke, `password_change`, a logout, an
900
934
  account delete, a cap eviction) still closes the socket — the revoking
901
935
  handler through `connection_closer`, `create_ws_auth_guard` again on the
@@ -953,6 +987,17 @@ Tick interval is `timeout / 2`, so event-loop blockage pauses the timer itself.
953
987
  A timeout unregisters the connection and aborts its signal along with the
954
988
  `WS_CLOSE_SERVER_HEARTBEAT_TIMEOUT` close.
955
989
 
990
+ **Credential deadline.** From admission, a timer closes the socket when the
991
+ credential it upgraded on expires (`ResolvedAuth.credential_deadline` — the
992
+ earlier of a session's cookie and row expiry, an API token's row; none for an
993
+ eternal token or the daemon token): `WS_CLOSE_SESSION_REVOKED` with
994
+ `WS_CLOSE_CREDENTIAL_EXPIRED_REASON`, through `end_socket`. Expiry writes
995
+ nothing a revocation listener hears, so the socket watches its own deadline.
996
+ The wait is `schedule_credential_deadline`'s chain of timers of at most an
997
+ hour (a 30-day session is past `setTimeout`'s ceiling), each also checking the
998
+ wall clock so a host suspend does not extend the deadline, cancelled when the
999
+ socket ends. Twin of the Rust loop's `WsLoopOptions::credential_deadline`.
1000
+
956
1001
  Two abort signals composed via `AbortSignal.any`:
957
1002
 
958
1003
  - `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 +1094,16 @@ recent frame each way — every frame, including heartbeats and the responses
1049
1094
  Reconnect policy (exponential backoff): `delay = DEFAULT_RECONNECT_DELAY * DEFAULT_BACKOFF_FACTOR ** (attempts-1)`,
1050
1095
  capped at `DEFAULT_RECONNECT_DELAY_MAX`. Every other close reconnects —
1051
1096
  `WS_CLOSE_OUTBOUND_OVERFLOW` included, the server's notice that this client
1052
- fell behind and missed messages. Two close codes never reconnect:
1097
+ fell behind and missed messages. Three close codes never reconnect:
1053
1098
 
1054
1099
  - `WS_CLOSE_SESSION_REVOKED` is **terminal** — sets `revoked`, no reconnect
1055
1100
  loop on 401, and `connect()` becomes a no-op. Pending and queued requests
1056
1101
  reject `unauthenticated`.
1102
+ - `WS_CLOSE_PERMISSION_REVOKED` is **closed until the user acts** too — status
1103
+ `closed`, no reconnect scheduled (a reconnect would be refused at the role
1104
+ gate), neither `revoked` nor `superseded` set (the credential is still
1105
+ good), pending and queued requests reject `forbidden`. `last_close_code`
1106
+ says why; `connect()` reopens.
1057
1107
  - `WS_CLOSE_CONNECTION_LIMIT` is **closed until the user acts** — sets
1058
1108
  `superseded` (reactive), status `closed`, no reconnect scheduled: the server
1059
1109
  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
  *
@@ -43,6 +46,7 @@
43
46
  * @module
44
47
  */
45
48
  import type { Uuid } from '../primitive_schemas.js';
49
+ import type { Logger } from '@fuzdev/fuz_util/log.ts';
46
50
  import { type EmitAfterCommitContext } from '../http/pending_effects.js';
47
51
  /**
48
52
  * Close live connections by identity, or all of them.
@@ -70,6 +74,20 @@ export interface ConnectionCloser {
70
74
  * credentials.
71
75
  */
72
76
  close_sockets_for_account: (account_id: Uuid) => number;
77
+ /**
78
+ * Close the connections of `account_id` whose endpoint gated them on
79
+ * `role` — the close for a revoked **global** grant of that role (a scoped
80
+ * grant never admits at a role gate, so revoking one closes nothing; the
81
+ * caller filters, `to_global_role_revocation`). A WebSocket gets
82
+ * `WS_CLOSE_PERMISSION_REVOKED`, not a revocation's code: the credential is
83
+ * still good.
84
+ *
85
+ * **Over-closes an any-of gate**: a connection whose endpoint listed
86
+ * several roles closes on the loss of any of them, whether or not the
87
+ * account still holds another. The client reconnects into the gate if it
88
+ * still qualifies.
89
+ */
90
+ close_sockets_for_role: (account_id: Uuid, role: string) => number;
73
91
  /**
74
92
  * Close every connection, whoever opened it — the shutdown close
75
93
  * `AppServer.close` runs. Not a revocation: a WebSocket gets
@@ -82,8 +100,10 @@ export interface ConnectionCloser {
82
100
  }
83
101
  /**
84
102
  * Which connections a revocation closes — the argument of
85
- * `queue_connection_close`, one variant per credential close of
103
+ * `queue_connection_close`, one variant per revocation close of
86
104
  * `ConnectionCloser` (`close_all_sockets` is shutdown, never a revocation).
105
+ * No handler queues `role` today: the audit listeners are a role's only
106
+ * closer.
87
107
  */
88
108
  export type ConnectionCloseTarget = {
89
109
  kind: 'session';
@@ -94,6 +114,10 @@ export type ConnectionCloseTarget = {
94
114
  } | {
95
115
  kind: 'account';
96
116
  account_id: Uuid;
117
+ } | {
118
+ kind: 'role';
119
+ account_id: Uuid;
120
+ role: string;
97
121
  };
98
122
  /**
99
123
  * Close the connections `target` names, now.
@@ -104,6 +128,21 @@ export type ConnectionCloseTarget = {
104
128
  * @returns how many connections closed
105
129
  */
106
130
  export declare const close_connections: (closer: ConnectionCloser, target: ConnectionCloseTarget) => number;
131
+ /**
132
+ * Close the connections `target` names, now, for a revocation that has
133
+ * already committed — logging a close that throws instead of throwing it.
134
+ *
135
+ * For a handler with no route transaction, closing straight after its own
136
+ * commit: a throw there would fail a request whose revocation stands, and skip
137
+ * the work after the close (the response, the audit row). A closer over several
138
+ * transports closes the rest before it rethrows, so catching loses no close.
139
+ * The twin of the post-commit flush, which catches and logs the same way.
140
+ *
141
+ * @param closer - the closer to run, `deps.connection_closer` in a handler
142
+ * @param target - the connections to close
143
+ * @param log - where a failing close is reported
144
+ */
145
+ export declare const close_connections_after_commit: (closer: ConnectionCloser, target: ConnectionCloseTarget, log: Logger) => void;
107
146
  /**
108
147
  * Queue a connection close on `ctx`'s post-commit queue, so it runs only after
109
148
  * the request's transaction commits and never when the handler throws.
@@ -112,8 +151,9 @@ export declare const close_connections: (closer: ConnectionCloser, target: Conne
112
151
  * why an in-transaction revocation must not close inline.
113
152
  *
114
153
  * In a handler with no wrapping transaction a throw still drops the queued
115
- * close while an autocommit write stays; close with `close_connections`
116
- * straight after the write there.
154
+ * close while an autocommit write stays; such a handler closes inline straight
155
+ * after its own commit instead, with `close_connections_after_commit`, which
156
+ * catches and logs a failing close as the post-commit flush does.
117
157
  *
118
158
  * @param ctx - the route or action context of the request doing the revoking
119
159
  * @param closer - the closer to run, `deps.connection_closer` in a handler
@@ -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;AAEpD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAEtD,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;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,8BAA8B,GAC1C,QAAQ,gBAAgB,EACxB,QAAQ,qBAAqB,EAC7B,KAAK,MAAM,KACT,IAMF,CAAC;AAEF;;;;;;;;;;;;;;;;GAgBG;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,10 +63,34 @@ 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
  }
66
71
  };
72
+ /**
73
+ * Close the connections `target` names, now, for a revocation that has
74
+ * already committed — logging a close that throws instead of throwing it.
75
+ *
76
+ * For a handler with no route transaction, closing straight after its own
77
+ * commit: a throw there would fail a request whose revocation stands, and skip
78
+ * the work after the close (the response, the audit row). A closer over several
79
+ * transports closes the rest before it rethrows, so catching loses no close.
80
+ * The twin of the post-commit flush, which catches and logs the same way.
81
+ *
82
+ * @param closer - the closer to run, `deps.connection_closer` in a handler
83
+ * @param target - the connections to close
84
+ * @param log - where a failing close is reported
85
+ */
86
+ export const close_connections_after_commit = (closer, target, log) => {
87
+ try {
88
+ close_connections(closer, target);
89
+ }
90
+ catch (err) {
91
+ log.error('connection close after commit failed:', err);
92
+ }
93
+ };
67
94
  /**
68
95
  * Queue a connection close on `ctx`'s post-commit queue, so it runs only after
69
96
  * the request's transaction commits and never when the handler throws.
@@ -72,8 +99,9 @@ export const close_connections = (closer, target) => {
72
99
  * why an in-transaction revocation must not close inline.
73
100
  *
74
101
  * In a handler with no wrapping transaction a throw still drops the queued
75
- * close while an autocommit write stays; close with `close_connections`
76
- * straight after the write there.
102
+ * close while an autocommit write stays; such a handler closes inline straight
103
+ * after its own commit instead, with `close_connections_after_commit`, which
104
+ * catches and logs a failing close as the post-commit flush does.
77
105
  *
78
106
  * @param ctx - the route or action context of the request doing the revoking
79
107
  * @param closer - the closer to run, `deps.connection_closer` in a handler
@@ -93,6 +121,7 @@ export const noop_connection_closer = Object.freeze({
93
121
  close_sockets_for_session: () => 0,
94
122
  close_sockets_for_token: () => 0,
95
123
  close_sockets_for_account: () => 0,
124
+ close_sockets_for_role: () => 0,
96
125
  close_all_sockets: () => 0
97
126
  });
98
127
  /**
@@ -150,6 +179,7 @@ export const create_realtime_closer = () => {
150
179
  close_sockets_for_session: (session_token_hash) => fan_out((member) => member.close_sockets_for_session(session_token_hash)),
151
180
  close_sockets_for_token: (api_token_id) => fan_out((member) => member.close_sockets_for_token(api_token_id)),
152
181
  close_sockets_for_account: (account_id) => fan_out((member) => member.close_sockets_for_account(account_id)),
182
+ close_sockets_for_role: (account_id, role) => fan_out((member) => member.close_sockets_for_role(account_id, role)),
153
183
  close_all_sockets: () => fan_out((member) => member.close_all_sockets())
154
184
  });
155
185
  };
@@ -163,6 +163,16 @@ export type PerformActionResult = {
163
163
  * the harness omits `account_id`.
164
164
  */
165
165
  export declare const perform_action: (input: PerformActionInput, deps: PerformActionDeps) => Promise<PerformActionResult>;
166
+ /**
167
+ * The credential-type 403 — one builder, so the dispatcher's gate below and a
168
+ * handler that gates a credential channel on its input (`account_delete`'s
169
+ * self path) emit a byte-identical error. Twin of the Rust
170
+ * `fuz_auth::credential_type_required`.
171
+ *
172
+ * @param required_credential_types - the channels that would be admitted
173
+ * @returns the `forbidden` error object with reason `credential_type_required`
174
+ */
175
+ export declare const credential_type_required_error: (required_credential_types: ReadonlyArray<string>) => JsonrpcErrorObject;
166
176
  /**
167
177
  * Build a JSON-RPC response envelope from a `PerformActionResult` for
168
178
  * transports that wire over the JSON-RPC 2.0 message shape (HTTP RPC + WS).
@@ -1 +1 @@
1
- {"version":3,"file":"perform_action.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/perform_action.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"perform_action.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/perform_action.ts"],"names":[],"mappings":"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"}
@@ -58,6 +58,7 @@ import { DEV } from 'esm-env';
58
58
  import { apply_authorization_phase, has_any_scoped_role } from '../auth/request_context.js';
59
59
  import {} from '../hono_context.js';
60
60
  import { to_input_params } from '../http/schema_helpers.js';
61
+ import { rate_limit_ip_key } from '../http/ip_canonical.js';
61
62
  import { dispatch_with_post_commit_rollback } from '../http/pending_effects.js';
62
63
  import { JSONRPC_VERSION } from '../http/jsonrpc.js';
63
64
  import { jsonrpc_error_messages, jsonrpc_error_code_to_http_status, http_status_to_jsonrpc_error_code, JSONRPC_ERROR_CODES, dev_only } from '../http/jsonrpc_errors.js';
@@ -119,14 +120,17 @@ export const perform_action = async (input, deps) => {
119
120
  // budget it should, and so a throttled caller pays no schema work.
120
121
  const rate_limit = spec.rate_limit;
121
122
  if (rate_limit) {
122
- const ip_check = action_ip_rate_limiter && (rate_limit === 'ip' || rate_limit === 'both');
123
+ const ip_limiter = rate_limit === 'ip' || rate_limit === 'both' ? action_ip_rate_limiter : null;
123
124
  const account_keyed_context = action_account_rate_limiter &&
124
125
  request_context !== null &&
125
126
  (rate_limit === 'account' || rate_limit === 'both')
126
127
  ? request_context
127
128
  : null;
128
- if (ip_check) {
129
- const result = action_ip_rate_limiter.check(client_ip);
129
+ // keyed by the address's rate-limit key (an IPv6 client's /64); the
130
+ // audit trail keeps `client_ip` whole
131
+ const ip_key = ip_limiter ? rate_limit_ip_key(client_ip) : '';
132
+ if (ip_limiter) {
133
+ const result = ip_limiter.check(ip_key);
130
134
  if (!result.allowed)
131
135
  return rate_limited_result(result.retry_after);
132
136
  }
@@ -135,8 +139,8 @@ export const perform_action = async (input, deps) => {
135
139
  if (!result.allowed)
136
140
  return rate_limited_result(result.retry_after);
137
141
  }
138
- if (ip_check)
139
- action_ip_rate_limiter.record(client_ip);
142
+ if (ip_limiter)
143
+ ip_limiter.record(ip_key);
140
144
  if (account_keyed_context) {
141
145
  action_account_rate_limiter.record(account_keyed_context.account.id);
142
146
  }
@@ -262,6 +266,19 @@ const check_action_auth_pre_authorization = (auth, account_id) => {
262
266
  }
263
267
  return null;
264
268
  };
269
+ /**
270
+ * The credential-type 403 — one builder, so the dispatcher's gate below and a
271
+ * handler that gates a credential channel on its input (`account_delete`'s
272
+ * self path) emit a byte-identical error. Twin of the Rust
273
+ * `fuz_auth::credential_type_required`.
274
+ *
275
+ * @param required_credential_types - the channels that would be admitted
276
+ * @returns the `forbidden` error object with reason `credential_type_required`
277
+ */
278
+ export const credential_type_required_error = (required_credential_types) => jsonrpc_error_messages.forbidden('forbidden', {
279
+ reason: ERROR_CREDENTIAL_TYPE_REQUIRED,
280
+ required_credential_types
281
+ });
265
282
  /**
266
283
  * Post-authorization auth gate — fires after the authorization phase
267
284
  * resolved the actor + role_grants. Enforces `auth.credential_types` and
@@ -289,10 +306,7 @@ const check_action_auth_pre_authorization = (auth, account_id) => {
289
306
  const check_action_auth_post_authorization = (auth, request_context, credential_type, method, token_scope) => {
290
307
  if (auth.credential_types?.length) {
291
308
  if (!credential_type || !auth.credential_types.includes(credential_type)) {
292
- return jsonrpc_error_messages.forbidden('forbidden', {
293
- reason: ERROR_CREDENTIAL_TYPE_REQUIRED,
294
- required_credential_types: auth.credential_types
295
- });
309
+ return credential_type_required_error(auth.credential_types);
296
310
  }
297
311
  }
298
312
  // Token-scope gate — second of three. See the doc comment for why the order