@fuzdev/fuz_app 0.55.0 → 0.56.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 (331) hide show
  1. package/dist/actions/CLAUDE.md +211 -155
  2. package/dist/actions/action_bridge.d.ts +8 -5
  3. package/dist/actions/action_bridge.d.ts.map +1 -1
  4. package/dist/actions/action_bridge.js +1 -11
  5. package/dist/actions/action_codegen.d.ts +19 -0
  6. package/dist/actions/action_codegen.d.ts.map +1 -1
  7. package/dist/actions/action_codegen.js +20 -14
  8. package/dist/actions/action_registry.d.ts.map +1 -1
  9. package/dist/actions/action_registry.js +5 -2
  10. package/dist/actions/action_rpc.d.ts +110 -44
  11. package/dist/actions/action_rpc.d.ts.map +1 -1
  12. package/dist/actions/action_rpc.js +92 -287
  13. package/dist/actions/action_spec.d.ts +55 -16
  14. package/dist/actions/action_spec.d.ts.map +1 -1
  15. package/dist/actions/action_spec.js +16 -11
  16. package/dist/actions/action_types.d.ts +28 -60
  17. package/dist/actions/action_types.d.ts.map +1 -1
  18. package/dist/actions/action_types.js +13 -5
  19. package/dist/actions/broadcast_api.d.ts +2 -2
  20. package/dist/actions/broadcast_api.js +2 -2
  21. package/dist/actions/compile_action_registry.d.ts +50 -0
  22. package/dist/actions/compile_action_registry.d.ts.map +1 -0
  23. package/dist/actions/compile_action_registry.js +69 -0
  24. package/dist/actions/heartbeat.d.ts +8 -4
  25. package/dist/actions/heartbeat.d.ts.map +1 -1
  26. package/dist/actions/heartbeat.js +5 -4
  27. package/dist/actions/perform_action.d.ts +145 -0
  28. package/dist/actions/perform_action.d.ts.map +1 -0
  29. package/dist/actions/perform_action.js +258 -0
  30. package/dist/actions/register_action_ws.d.ts +44 -38
  31. package/dist/actions/register_action_ws.d.ts.map +1 -1
  32. package/dist/actions/register_action_ws.js +101 -159
  33. package/dist/actions/register_ws_endpoint.d.ts +2 -10
  34. package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
  35. package/dist/actions/register_ws_endpoint.js +32 -10
  36. package/dist/actions/transports_ws_auth_guard.d.ts +1 -1
  37. package/dist/actions/transports_ws_auth_guard.js +1 -1
  38. package/dist/actions/transports_ws_backend.d.ts +1 -1
  39. package/dist/actions/transports_ws_backend.js +1 -1
  40. package/dist/auth/CLAUDE.md +673 -442
  41. package/dist/auth/account_action_specs.d.ts +28 -7
  42. package/dist/auth/account_action_specs.d.ts.map +1 -1
  43. package/dist/auth/account_action_specs.js +7 -7
  44. package/dist/auth/account_actions.d.ts +8 -14
  45. package/dist/auth/account_actions.d.ts.map +1 -1
  46. package/dist/auth/account_actions.js +26 -32
  47. package/dist/auth/account_queries.d.ts +46 -13
  48. package/dist/auth/account_queries.d.ts.map +1 -1
  49. package/dist/auth/account_queries.js +73 -33
  50. package/dist/auth/account_routes.d.ts +4 -3
  51. package/dist/auth/account_routes.d.ts.map +1 -1
  52. package/dist/auth/account_routes.js +58 -33
  53. package/dist/auth/account_schema.d.ts +46 -54
  54. package/dist/auth/account_schema.d.ts.map +1 -1
  55. package/dist/auth/account_schema.js +21 -48
  56. package/dist/auth/admin_action_specs.d.ts +55 -21
  57. package/dist/auth/admin_action_specs.d.ts.map +1 -1
  58. package/dist/auth/admin_action_specs.js +42 -26
  59. package/dist/auth/admin_actions.d.ts +14 -21
  60. package/dist/auth/admin_actions.d.ts.map +1 -1
  61. package/dist/auth/admin_actions.js +47 -44
  62. package/dist/auth/audit_emitter.d.ts +160 -0
  63. package/dist/auth/audit_emitter.d.ts.map +1 -0
  64. package/dist/auth/audit_emitter.js +83 -0
  65. package/dist/auth/audit_log_queries.d.ts +17 -87
  66. package/dist/auth/audit_log_queries.d.ts.map +1 -1
  67. package/dist/auth/audit_log_queries.js +17 -96
  68. package/dist/auth/audit_log_routes.d.ts +1 -1
  69. package/dist/auth/audit_log_routes.d.ts.map +1 -1
  70. package/dist/auth/audit_log_routes.js +7 -3
  71. package/dist/auth/audit_log_schema.d.ts +48 -42
  72. package/dist/auth/audit_log_schema.d.ts.map +1 -1
  73. package/dist/auth/audit_log_schema.js +56 -43
  74. package/dist/auth/auth_guard_resolver.d.ts +44 -0
  75. package/dist/auth/auth_guard_resolver.d.ts.map +1 -0
  76. package/dist/auth/auth_guard_resolver.js +56 -0
  77. package/dist/auth/bootstrap_account.d.ts +7 -7
  78. package/dist/auth/bootstrap_account.d.ts.map +1 -1
  79. package/dist/auth/bootstrap_account.js +7 -7
  80. package/dist/auth/bootstrap_routes.d.ts.map +1 -1
  81. package/dist/auth/bootstrap_routes.js +11 -10
  82. package/dist/auth/cleanup.d.ts +20 -26
  83. package/dist/auth/cleanup.d.ts.map +1 -1
  84. package/dist/auth/cleanup.js +33 -47
  85. package/dist/auth/credential_type_schema.d.ts +115 -0
  86. package/dist/auth/credential_type_schema.d.ts.map +1 -0
  87. package/dist/auth/credential_type_schema.js +127 -0
  88. package/dist/auth/daemon_token_middleware.d.ts +1 -1
  89. package/dist/auth/daemon_token_middleware.js +3 -3
  90. package/dist/auth/ddl.d.ts +2 -2
  91. package/dist/auth/ddl.d.ts.map +1 -1
  92. package/dist/auth/ddl.js +6 -6
  93. package/dist/auth/deps.d.ts +7 -32
  94. package/dist/auth/deps.d.ts.map +1 -1
  95. package/dist/auth/grant_path_schema.d.ts +117 -0
  96. package/dist/auth/grant_path_schema.d.ts.map +1 -0
  97. package/dist/auth/grant_path_schema.js +137 -0
  98. package/dist/auth/invite_queries.d.ts +12 -1
  99. package/dist/auth/invite_queries.d.ts.map +1 -1
  100. package/dist/auth/invite_queries.js +12 -1
  101. package/dist/auth/invite_schema.d.ts +1 -1
  102. package/dist/auth/invite_schema.d.ts.map +1 -1
  103. package/dist/auth/invite_schema.js +1 -1
  104. package/dist/auth/middleware.d.ts.map +1 -1
  105. package/dist/auth/middleware.js +5 -2
  106. package/dist/auth/migrations.d.ts +22 -7
  107. package/dist/auth/migrations.d.ts.map +1 -1
  108. package/dist/auth/migrations.js +64 -25
  109. package/dist/auth/request_context.d.ts +157 -170
  110. package/dist/auth/request_context.d.ts.map +1 -1
  111. package/dist/auth/request_context.js +224 -268
  112. package/dist/auth/{permit_offer_action_specs.d.ts → role_grant_offer_action_specs.d.ts} +130 -100
  113. package/dist/auth/role_grant_offer_action_specs.d.ts.map +1 -0
  114. package/dist/auth/role_grant_offer_action_specs.js +262 -0
  115. package/dist/auth/role_grant_offer_actions.d.ts +104 -0
  116. package/dist/auth/role_grant_offer_actions.d.ts.map +1 -0
  117. package/dist/auth/{permit_offer_actions.js → role_grant_offer_actions.js} +153 -140
  118. package/dist/auth/{permit_offer_notifications.d.ts → role_grant_offer_notifications.d.ts} +80 -70
  119. package/dist/auth/role_grant_offer_notifications.d.ts.map +1 -0
  120. package/dist/auth/role_grant_offer_notifications.js +182 -0
  121. package/dist/auth/{permit_offer_queries.d.ts → role_grant_offer_queries.d.ts} +64 -64
  122. package/dist/auth/role_grant_offer_queries.d.ts.map +1 -0
  123. package/dist/auth/{permit_offer_queries.js → role_grant_offer_queries.js} +136 -123
  124. package/dist/auth/role_grant_offer_schema.d.ts +150 -0
  125. package/dist/auth/role_grant_offer_schema.d.ts.map +1 -0
  126. package/dist/auth/{permit_offer_schema.js → role_grant_offer_schema.js} +55 -36
  127. package/dist/auth/role_grant_queries.d.ts +231 -0
  128. package/dist/auth/role_grant_queries.d.ts.map +1 -0
  129. package/dist/auth/role_grant_queries.js +320 -0
  130. package/dist/auth/role_schema.d.ts +150 -40
  131. package/dist/auth/role_schema.d.ts.map +1 -1
  132. package/dist/auth/role_schema.js +144 -45
  133. package/dist/auth/scope_kind_schema.d.ts +96 -0
  134. package/dist/auth/scope_kind_schema.d.ts.map +1 -0
  135. package/dist/auth/scope_kind_schema.js +94 -0
  136. package/dist/auth/self_service_role_action_specs.d.ts +4 -1
  137. package/dist/auth/self_service_role_action_specs.d.ts.map +1 -1
  138. package/dist/auth/self_service_role_action_specs.js +2 -2
  139. package/dist/auth/self_service_role_actions.d.ts +35 -29
  140. package/dist/auth/self_service_role_actions.d.ts.map +1 -1
  141. package/dist/auth/self_service_role_actions.js +58 -48
  142. package/dist/auth/session_cookie.d.ts +43 -6
  143. package/dist/auth/session_cookie.d.ts.map +1 -1
  144. package/dist/auth/session_cookie.js +31 -5
  145. package/dist/auth/session_middleware.d.ts +37 -3
  146. package/dist/auth/session_middleware.d.ts.map +1 -1
  147. package/dist/auth/session_middleware.js +33 -7
  148. package/dist/auth/signup_routes.d.ts.map +1 -1
  149. package/dist/auth/signup_routes.js +48 -19
  150. package/dist/auth/standard_action_specs.d.ts +2 -2
  151. package/dist/auth/standard_action_specs.js +4 -4
  152. package/dist/auth/standard_rpc_actions.d.ts +23 -19
  153. package/dist/auth/standard_rpc_actions.d.ts.map +1 -1
  154. package/dist/auth/standard_rpc_actions.js +12 -12
  155. package/dist/db/migrate.d.ts +1 -1
  156. package/dist/db/migrate.js +1 -1
  157. package/dist/dev/setup.d.ts +2 -2
  158. package/dist/dev/setup.d.ts.map +1 -1
  159. package/dist/dev/setup.js +4 -4
  160. package/dist/env/load.d.ts +1 -1
  161. package/dist/env/load.js +1 -1
  162. package/dist/hono_context.d.ts +27 -45
  163. package/dist/hono_context.d.ts.map +1 -1
  164. package/dist/hono_context.js +14 -28
  165. package/dist/http/CLAUDE.md +235 -121
  166. package/dist/http/auth_shape.d.ts +191 -0
  167. package/dist/http/auth_shape.d.ts.map +1 -0
  168. package/dist/http/auth_shape.js +237 -0
  169. package/dist/http/common_routes.js +3 -3
  170. package/dist/http/db_routes.d.ts +4 -0
  171. package/dist/http/db_routes.d.ts.map +1 -1
  172. package/dist/http/db_routes.js +44 -7
  173. package/dist/http/error_schemas.d.ts +56 -34
  174. package/dist/http/error_schemas.d.ts.map +1 -1
  175. package/dist/http/error_schemas.js +63 -28
  176. package/dist/http/pending_effects.d.ts +71 -18
  177. package/dist/http/pending_effects.d.ts.map +1 -1
  178. package/dist/http/pending_effects.js +87 -18
  179. package/dist/http/proxy.d.ts +52 -5
  180. package/dist/http/proxy.d.ts.map +1 -1
  181. package/dist/http/proxy.js +92 -14
  182. package/dist/http/route_spec.d.ts +89 -75
  183. package/dist/http/route_spec.d.ts.map +1 -1
  184. package/dist/http/route_spec.js +54 -72
  185. package/dist/http/schema_helpers.d.ts +3 -14
  186. package/dist/http/schema_helpers.d.ts.map +1 -1
  187. package/dist/http/schema_helpers.js +2 -14
  188. package/dist/http/surface.d.ts +2 -10
  189. package/dist/http/surface.d.ts.map +1 -1
  190. package/dist/http/surface.js +3 -4
  191. package/dist/http/surface_query.d.ts +39 -35
  192. package/dist/http/surface_query.d.ts.map +1 -1
  193. package/dist/http/surface_query.js +79 -36
  194. package/dist/primitive_schemas.d.ts +39 -0
  195. package/dist/primitive_schemas.d.ts.map +1 -0
  196. package/dist/primitive_schemas.js +40 -0
  197. package/dist/realtime/sse_auth_guard.d.ts +5 -5
  198. package/dist/realtime/sse_auth_guard.js +9 -9
  199. package/dist/runtime/mock.d.ts +1 -1
  200. package/dist/runtime/mock.js +1 -1
  201. package/dist/server/app_backend.d.ts +14 -11
  202. package/dist/server/app_backend.d.ts.map +1 -1
  203. package/dist/server/app_backend.js +12 -8
  204. package/dist/server/app_server.d.ts +7 -7
  205. package/dist/server/app_server.d.ts.map +1 -1
  206. package/dist/server/app_server.js +35 -40
  207. package/dist/server/validate_nginx.d.ts +1 -1
  208. package/dist/server/validate_nginx.js +1 -1
  209. package/dist/testing/CLAUDE.md +50 -38
  210. package/dist/testing/admin_integration.d.ts +5 -6
  211. package/dist/testing/admin_integration.d.ts.map +1 -1
  212. package/dist/testing/admin_integration.js +87 -85
  213. package/dist/testing/app_server.d.ts +11 -14
  214. package/dist/testing/app_server.d.ts.map +1 -1
  215. package/dist/testing/app_server.js +16 -15
  216. package/dist/testing/assertions.d.ts.map +1 -1
  217. package/dist/testing/assertions.js +2 -1
  218. package/dist/testing/attack_surface.d.ts.map +1 -1
  219. package/dist/testing/attack_surface.js +15 -9
  220. package/dist/testing/audit_completeness.d.ts +2 -2
  221. package/dist/testing/audit_completeness.d.ts.map +1 -1
  222. package/dist/testing/audit_completeness.js +36 -36
  223. package/dist/testing/auth_apps.d.ts +5 -4
  224. package/dist/testing/auth_apps.d.ts.map +1 -1
  225. package/dist/testing/auth_apps.js +22 -19
  226. package/dist/testing/data_exposure.d.ts.map +1 -1
  227. package/dist/testing/data_exposure.js +5 -5
  228. package/dist/testing/db.d.ts +1 -1
  229. package/dist/testing/db.d.ts.map +1 -1
  230. package/dist/testing/db.js +4 -4
  231. package/dist/testing/db_entities.d.ts +22 -0
  232. package/dist/testing/db_entities.d.ts.map +1 -0
  233. package/dist/testing/db_entities.js +28 -0
  234. package/dist/testing/entities.d.ts +8 -7
  235. package/dist/testing/entities.d.ts.map +1 -1
  236. package/dist/testing/entities.js +21 -18
  237. package/dist/testing/integration.d.ts.map +1 -1
  238. package/dist/testing/integration.js +13 -14
  239. package/dist/testing/integration_helpers.d.ts +4 -4
  240. package/dist/testing/integration_helpers.d.ts.map +1 -1
  241. package/dist/testing/integration_helpers.js +20 -18
  242. package/dist/testing/middleware.d.ts +4 -4
  243. package/dist/testing/middleware.d.ts.map +1 -1
  244. package/dist/testing/middleware.js +12 -11
  245. package/dist/testing/rpc_attack_surface.d.ts.map +1 -1
  246. package/dist/testing/rpc_attack_surface.js +40 -24
  247. package/dist/testing/rpc_round_trip.d.ts +1 -1
  248. package/dist/testing/rpc_round_trip.d.ts.map +1 -1
  249. package/dist/testing/rpc_round_trip.js +14 -13
  250. package/dist/testing/sse_round_trip.d.ts +3 -4
  251. package/dist/testing/sse_round_trip.d.ts.map +1 -1
  252. package/dist/testing/sse_round_trip.js +7 -11
  253. package/dist/testing/standard.d.ts +1 -1
  254. package/dist/testing/stubs.d.ts +25 -0
  255. package/dist/testing/stubs.d.ts.map +1 -1
  256. package/dist/testing/stubs.js +43 -2
  257. package/dist/testing/surface_invariants.d.ts +2 -2
  258. package/dist/testing/ws_round_trip.d.ts +12 -13
  259. package/dist/testing/ws_round_trip.d.ts.map +1 -1
  260. package/dist/testing/ws_round_trip.js +19 -11
  261. package/dist/ui/AdminAccounts.svelte +23 -20
  262. package/dist/ui/AdminOverview.svelte +15 -13
  263. package/dist/ui/AdminOverview.svelte.d.ts.map +1 -1
  264. package/dist/ui/{AdminPermitHistory.svelte → AdminRoleGrantHistory.svelte} +12 -12
  265. package/dist/ui/AdminRoleGrantHistory.svelte.d.ts +4 -0
  266. package/dist/ui/AdminRoleGrantHistory.svelte.d.ts.map +1 -0
  267. package/dist/ui/BootstrapForm.svelte +1 -1
  268. package/dist/ui/CLAUDE.md +60 -60
  269. package/dist/ui/{PermitOfferForm.svelte → RoleGrantOfferForm.svelte} +27 -26
  270. package/dist/ui/{PermitOfferForm.svelte.d.ts → RoleGrantOfferForm.svelte.d.ts} +7 -7
  271. package/dist/ui/RoleGrantOfferForm.svelte.d.ts.map +1 -0
  272. package/dist/ui/{PermitOfferHistory.svelte → RoleGrantOfferHistory.svelte} +12 -12
  273. package/dist/ui/{PermitOfferHistory.svelte.d.ts → RoleGrantOfferHistory.svelte.d.ts} +4 -4
  274. package/dist/ui/RoleGrantOfferHistory.svelte.d.ts.map +1 -0
  275. package/dist/ui/{PermitOfferInbox.svelte → RoleGrantOfferInbox.svelte} +14 -14
  276. package/dist/ui/{PermitOfferInbox.svelte.d.ts → RoleGrantOfferInbox.svelte.d.ts} +4 -4
  277. package/dist/ui/RoleGrantOfferInbox.svelte.d.ts.map +1 -0
  278. package/dist/ui/SignupForm.svelte +1 -1
  279. package/dist/ui/SurfaceExplorer.svelte +35 -15
  280. package/dist/ui/SurfaceExplorer.svelte.d.ts.map +1 -1
  281. package/dist/ui/account_sessions_state.svelte.d.ts +2 -3
  282. package/dist/ui/account_sessions_state.svelte.d.ts.map +1 -1
  283. package/dist/ui/account_sessions_state.svelte.js +2 -3
  284. package/dist/ui/admin_accounts_state.svelte.d.ts +18 -18
  285. package/dist/ui/admin_accounts_state.svelte.d.ts.map +1 -1
  286. package/dist/ui/admin_accounts_state.svelte.js +16 -16
  287. package/dist/ui/admin_rpc_adapters.d.ts +20 -20
  288. package/dist/ui/admin_rpc_adapters.d.ts.map +1 -1
  289. package/dist/ui/admin_rpc_adapters.js +17 -17
  290. package/dist/ui/admin_sessions_state.svelte.d.ts +2 -2
  291. package/dist/ui/admin_sessions_state.svelte.js +2 -2
  292. package/dist/ui/audit_log_state.svelte.d.ts +7 -7
  293. package/dist/ui/audit_log_state.svelte.d.ts.map +1 -1
  294. package/dist/ui/audit_log_state.svelte.js +6 -6
  295. package/dist/ui/auth_state.svelte.d.ts +3 -3
  296. package/dist/ui/auth_state.svelte.d.ts.map +1 -1
  297. package/dist/ui/auth_state.svelte.js +6 -6
  298. package/dist/ui/format_scope.d.ts +2 -2
  299. package/dist/ui/format_scope.js +2 -2
  300. package/dist/ui/{permit_offers_state.svelte.d.ts → role_grant_offers_state.svelte.d.ts} +30 -30
  301. package/dist/ui/role_grant_offers_state.svelte.d.ts.map +1 -0
  302. package/dist/ui/{permit_offers_state.svelte.js → role_grant_offers_state.svelte.js} +18 -18
  303. package/dist/ui/ui_format.js +2 -2
  304. package/package.json +3 -3
  305. package/dist/auth/permit_offer_action_specs.d.ts.map +0 -1
  306. package/dist/auth/permit_offer_action_specs.js +0 -258
  307. package/dist/auth/permit_offer_actions.d.ts +0 -110
  308. package/dist/auth/permit_offer_actions.d.ts.map +0 -1
  309. package/dist/auth/permit_offer_notifications.d.ts.map +0 -1
  310. package/dist/auth/permit_offer_notifications.js +0 -182
  311. package/dist/auth/permit_offer_queries.d.ts.map +0 -1
  312. package/dist/auth/permit_offer_schema.d.ts +0 -125
  313. package/dist/auth/permit_offer_schema.d.ts.map +0 -1
  314. package/dist/auth/permit_queries.d.ts +0 -222
  315. package/dist/auth/permit_queries.d.ts.map +0 -1
  316. package/dist/auth/permit_queries.js +0 -305
  317. package/dist/auth/require_keeper.d.ts +0 -20
  318. package/dist/auth/require_keeper.d.ts.map +0 -1
  319. package/dist/auth/require_keeper.js +0 -35
  320. package/dist/auth/route_guards.d.ts +0 -27
  321. package/dist/auth/route_guards.d.ts.map +0 -1
  322. package/dist/auth/route_guards.js +0 -38
  323. package/dist/auth/session_lifecycle.d.ts +0 -37
  324. package/dist/auth/session_lifecycle.d.ts.map +0 -1
  325. package/dist/auth/session_lifecycle.js +0 -29
  326. package/dist/ui/AdminPermitHistory.svelte.d.ts +0 -4
  327. package/dist/ui/AdminPermitHistory.svelte.d.ts.map +0 -1
  328. package/dist/ui/PermitOfferForm.svelte.d.ts.map +0 -1
  329. package/dist/ui/PermitOfferHistory.svelte.d.ts.map +0 -1
  330. package/dist/ui/PermitOfferInbox.svelte.d.ts.map +0 -1
  331. package/dist/ui/permit_offers_state.svelte.d.ts.map +0 -1
@@ -2,63 +2,64 @@
2
2
  * WebSocket JSON-RPC dispatch — the low-level WS transport binding.
3
3
  *
4
4
  * Most consumers should mount WS endpoints via `register_ws_endpoint`
5
- * (`actions/register_ws_endpoint.ts`), which wraps this function with the standard
6
- * upgrade stack (origin check + auth + optional role). This module stays
7
- * exported as the lower-level entry point for tests that drive the
8
- * dispatcher directly via `create_ws_test_harness`.
5
+ * (`actions/register_ws_endpoint.ts`), which wraps this function with the
6
+ * standard upgrade stack (origin check + auth + optional role). This
7
+ * module stays exported as the lower-level entry point for tests that
8
+ * drive the dispatcher directly via `create_ws_test_harness`.
9
9
  *
10
10
  * Symmetric to `create_rpc_endpoint` (from `actions/action_rpc.ts`):
11
- * consumer supplies action specs + a handler map, the dispatcher parses the
12
- * envelope, checks per-action auth, validates input, invokes the handler with
13
- * a per-request context, and writes the response.
14
- *
15
- * Extracted from zzz's `register_websocket_actions` to converge pattern drift
16
- * across consumers (zzz, tx, undying). Broadcast-style notifications remain
17
- * domain-shaped today this module only covers per-request dispatch + the
18
- * socket-scoped `ctx.notify` + per-socket `ctx.signal`. See
19
- * `BackendWebsocketTransport.send` for broadcast.
11
+ * both transports parse their wire envelope, then call the shared
12
+ * `perform_action` core (`actions/perform_action.ts`) for the post-parse
13
+ * pipeline. WS-specific concerns connection lifecycle, heartbeat,
14
+ * cancel-notification interception, socket-scoped notify — stay in this
15
+ * module; everything else (auth gates, input validation, authorization
16
+ * phase, rate limiting, transactional dispatch, DEV output validation,
17
+ * thrown-error normalization) is shared.
20
18
  *
21
19
  * ## Auth expectations
22
20
  *
23
- * The consumer is responsible for rejecting unauthenticated upgrades *before*
24
- * routing to this handler (fuz_app's `require_auth` middleware, or
25
- * `register_ws_endpoint` which wires it for you). Inside the dispatcher,
26
- * `require_request_context(c)` enforces the dispatcher invariant and
27
- * per-action auth is enforced on each message.
21
+ * The consumer is responsible for rejecting unauthenticated upgrades
22
+ * *before* routing to this handler (fuz_app's `require_auth` middleware,
23
+ * or `register_ws_endpoint` which wires it for you). Per-action auth
24
+ * runs inside `perform_action` on every message via the same gates HTTP
25
+ * RPC uses.
28
26
  *
29
27
  * @module
30
28
  */
31
- import { DEV } from 'esm-env';
32
29
  import { wait } from '@fuzdev/fuz_util/async.js';
33
30
  import { Logger } from '@fuzdev/fuz_util/log.js';
34
- import { has_role, require_request_context } from '../auth/request_context.js';
31
+ import { get_request_context, require_request_context, } from '../auth/request_context.js';
35
32
  import { hash_session_token } from '../auth/session_queries.js';
36
- import { ROLE_KEEPER } from '../auth/role_schema.js';
37
33
  import { get_client_ip } from '../http/proxy.js';
38
- import { JSONRPC_VERSION } from '../http/jsonrpc.js';
39
- import { jsonrpc_error_messages, ThrownJsonrpcError } from '../http/jsonrpc_errors.js';
40
- import { create_jsonrpc_error_response, create_jsonrpc_error_response_from_thrown, create_jsonrpc_notification, to_jsonrpc_message_id, to_jsonrpc_params, is_jsonrpc_request, } from '../http/jsonrpc_helpers.js';
41
- import { CREDENTIAL_TYPE_KEY, AUTH_API_TOKEN_ID_KEY } from '../hono_context.js';
34
+ import { flush_pending_effects, flush_post_commit_effects } from '../http/pending_effects.js';
35
+ import { jsonrpc_error_messages } from '../http/jsonrpc_errors.js';
36
+ import { create_jsonrpc_error_response, create_jsonrpc_notification, to_jsonrpc_message_id, to_jsonrpc_params, is_jsonrpc_request, } from '../http/jsonrpc_helpers.js';
37
+ import { CREDENTIAL_TYPE_KEY, AUTH_API_TOKEN_ID_KEY, TEST_CONTEXT_PRESET_KEY, } from '../hono_context.js';
42
38
  import {} from './action_types.js';
39
+ import { compile_action_registry } from './compile_action_registry.js';
43
40
  import { cancel_action_spec, CancelNotificationParams } from './cancel.js';
44
41
  import { WS_CLOSE_SERVER_HEARTBEAT_TIMEOUT } from './transports.js';
45
42
  import { BackendWebsocketTransport } from './transports_ws_backend.js';
43
+ import { perform_action, perform_action_result_to_envelope } from './perform_action.js';
46
44
  /** Default inactivity window before the server closes a silent socket. */
47
45
  export const DEFAULT_SERVER_HEARTBEAT_TIMEOUT = 60_000;
48
46
  /**
49
- * Mount a JSON-RPC WebSocket endpoint that dispatches to the supplied handler
50
- * map. Per-request context is built from the base + consumer-provided
51
- * `RegisterActionWsOptions.extend_context`.
47
+ * Mount a JSON-RPC WebSocket endpoint that dispatches via the shared
48
+ * `perform_action` core.
52
49
  *
53
50
  * Wire behavior:
54
51
  * - Batch JSON-RPC is rejected (single-message only).
55
52
  * - Notifications (method + no id) are silently dropped per JSON-RPC spec.
56
- * - Per-action auth: `public` / `authenticated` pass through (upgrade auth
57
- * already verified identity); `keeper` requires `daemon_token` credential
58
- * type *and* the keeper role; role-based `{role}` requires the named role
59
- * via `has_role`, matching the HTTP path in `actions/action_rpc.ts`.
60
- * - DEV mode validates handler output against the spec's `output` schema and
61
- * warns on mismatches.
53
+ * Exception: `cancel` notifications abort the matching pending request's
54
+ * `ctx.signal` before bubbling out.
55
+ * - Per-message dispatch goes through `perform_action`: pre-validation
56
+ * auth (401) input validation (400) authorization phase →
57
+ * post-authorization auth (403) rate limit (429) handler (with
58
+ * transaction wrap iff `spec.side_effects: true`) → DEV output validation.
59
+ * - Authorization phase runs **per message** — role_grant changes during a
60
+ * connection lifetime are picked up on the next message without any
61
+ * in-place refresh. Authentication invalidation closes the socket via
62
+ * `create_ws_auth_guard`.
62
63
  *
63
64
  * @returns the transport (supplied or freshly created) — retain it to wire
64
65
  * `create_ws_auth_guard` or broadcast on audit events.
@@ -67,23 +68,14 @@ export const DEFAULT_SERVER_HEARTBEAT_TIMEOUT = 60_000;
67
68
  * in the transport's internal maps via `add_connection` / `remove_connection`
68
69
  */
69
70
  export const register_action_ws = (options) => {
70
- const { path, app, upgradeWebSocket, actions, extend_context, transport = new BackendWebsocketTransport(), heartbeat = true, artificial_delay = 0, log = new Logger('[ws]'), on_socket_open, on_socket_close, action_ip_rate_limiter = null, action_account_rate_limiter = null, } = options;
71
- // Fan the unified actions array into the two lookups the dispatcher
72
- // consults at message time. Keeping them internal means the composable
73
- // `{spec, handler}` tuple remains the only shape consumers name.
74
- const spec_by_method = new Map();
75
- const handlers = {};
76
- for (const action of actions) {
77
- spec_by_method.set(action.spec.method, action.spec);
78
- if (action.handler)
79
- handlers[action.spec.method] = action.handler;
80
- // Reject account-keyed rate limiting on public actions — the dispatcher
81
- // has no actor to key on. Mirrors the HTTP RPC registration check.
82
- if ((action.spec.rate_limit === 'account' || action.spec.rate_limit === 'both') &&
83
- action.spec.auth === 'public') {
84
- throw new Error(`WS action "${action.spec.method}" declares rate_limit: '${action.spec.rate_limit}' but auth: 'public' — no actor available for account-keyed limiting. Use 'ip' or change auth.`);
85
- }
86
- }
71
+ const { path, app, upgradeWebSocket, actions, db, transport = new BackendWebsocketTransport(), heartbeat = true, artificial_delay = 0, log = new Logger('[ws]'), on_socket_open, on_socket_close, action_ip_rate_limiter = null, action_account_rate_limiter = null, } = options;
72
+ // Build the dispatcher's per-method lookup. Only request_response
73
+ // specs with a handler reach `action_map` perform_action is the
74
+ // only site that calls handlers, and it requires an `RpcAction`.
75
+ // Other kinds (`remote_notification` like `cancel`, `local_call`)
76
+ // are registry-only on WS; the cancel handler reads
77
+ // `cancel_action_spec.method` directly.
78
+ const { action_map } = compile_action_registry(actions, 'WS action');
87
79
  const heartbeat_enabled = heartbeat !== false;
88
80
  const heartbeat_config = typeof heartbeat === 'object' ? heartbeat : {};
89
81
  const heartbeat_timeout = heartbeat_config.timeout ?? DEFAULT_SERVER_HEARTBEAT_TIMEOUT;
@@ -92,14 +84,14 @@ export const register_action_ws = (options) => {
92
84
  // dead-because-unresponsive that closing is arguably correct.
93
85
  const heartbeat_tick_interval = Math.max(100, Math.floor(heartbeat_timeout / 2));
94
86
  app.get(path, upgradeWebSocket((c) => {
95
- // Upgrade-time auth extraction `require_auth` middleware has already
96
- // rejected unauthenticated requests, so request_context is guaranteed
97
- // non-null by the time we get here.
98
- const request_context = require_request_context(c);
99
- const account_id = request_context.account.id;
100
- // Resolved at upgrade every message on this socket shares the
101
- // same client IP, so we capture once and reuse for rate-limit
102
- // keying. `'unknown'` if the proxy middleware wasn't in the stack.
87
+ // Upgrade-time identity capture. `require_auth` middleware has
88
+ // already rejected unauthenticated upgrades, so request_context is
89
+ // non-null here. Per-message dispatch reads `account_id` +
90
+ // `credential_type` from this closure; the live request_context is
91
+ // only used by the test-preset escape hatch (perform_action runs
92
+ // the authorization phase fresh on every message in production).
93
+ const upgrade_context = require_request_context(c);
94
+ const account_id = upgrade_context.account.id;
103
95
  const client_ip = get_client_ip(c);
104
96
  const credential_type = c.get(CREDENTIAL_TYPE_KEY);
105
97
  // Session-based connections have a token hash for targeted revocation.
@@ -110,6 +102,12 @@ export const register_action_ws = (options) => {
110
102
  // `close_sockets_for_token` to tear down just this socket on
111
103
  // `token_revoke` without affecting the account's other sockets.
112
104
  const api_token_id = c.get(AUTH_API_TOKEN_ID_KEY);
105
+ // Test escape hatch — captured once at upgrade. perform_action
106
+ // honors it per-message so harnesses with pre-baked
107
+ // `RequestContext` skip the live authorization phase.
108
+ const upgrade_preset = c.get(TEST_CONTEXT_PRESET_KEY)
109
+ ? { request_context: get_request_context(c) }
110
+ : undefined;
113
111
  // Per-socket abort controller — fires on socket close, chained into
114
112
  // every in-flight handler's per-request controller via
115
113
  // `AbortSignal.any`. Keeping both signals lets the client
@@ -128,9 +126,9 @@ export const register_action_ws = (options) => {
128
126
  // down; `BackendWebsocketTransport.#revoke_connection` clears the
129
127
  // identity map before Hono fires onClose.
130
128
  const identity = { token_hash, account_id, api_token_id };
131
- // Captured on open, consumed on close. Null before onOpen fires or
132
- // when a consumer never opens (e.g. immediate disconnect).
133
- let captured_connection_id = null;
129
+ // Captured on open, consumed on close. Undefined before onOpen
130
+ // fires or when a consumer never opens (e.g. immediate disconnect).
131
+ let captured_connection_id;
134
132
  // Receive-silence watchdog. Seeded to open-time so the first window is
135
133
  // exempt (cold-start grace — avoid killing mid-handshake sockets).
136
134
  // Bumped by onMessage. Any incoming activity counts, not just
@@ -240,80 +238,19 @@ export const register_action_ws = (options) => {
240
238
  return;
241
239
  }
242
240
  const { method, id, params } = json;
243
- // Per-action auth checkenforce auth level from spec.
244
- const spec = spec_by_method.get(method);
245
- if (!spec) {
241
+ // Per-action method lookupreturn method_not_found before
242
+ // we engage the dispatch machinery. Specs without a handler
243
+ // (client-only / dispatcher-handled) miss action_map and
244
+ // surface as method_not_found just like unknown methods.
245
+ const action = action_map.get(method);
246
+ if (!action) {
246
247
  ws.send(JSON.stringify(create_jsonrpc_error_response(id, jsonrpc_error_messages.method_not_found(method))));
247
248
  return;
248
249
  }
249
- const { auth } = spec;
250
- if (auth === 'keeper') {
251
- if (credential_type !== 'daemon_token' || !has_role(request_context, ROLE_KEEPER)) {
252
- ws.send(JSON.stringify(create_jsonrpc_error_response(id, jsonrpc_error_messages.forbidden('keeper actions require daemon_token credential with keeper role'))));
253
- return;
254
- }
255
- }
256
- else if (typeof auth === 'object' && auth !== null) {
257
- if (!has_role(request_context, auth.role)) {
258
- ws.send(JSON.stringify(create_jsonrpc_error_response(id, jsonrpc_error_messages.forbidden(`requires role: ${auth.role}`))));
259
- return;
260
- }
261
- }
262
- // Rate limit — throttle-requests semantics, mirrors the HTTP RPC
263
- // dispatcher. Same limiters are shared across transports so an
264
- // attacker can't bypass the budget by switching from RPC to WS.
265
- const rate_limit = spec.rate_limit;
266
- if (rate_limit) {
267
- const ip_check = action_ip_rate_limiter && (rate_limit === 'ip' || rate_limit === 'both');
268
- const account_check = action_account_rate_limiter && (rate_limit === 'account' || rate_limit === 'both');
269
- const send_rate_limited = (retry_after) => {
270
- ws.send(JSON.stringify(create_jsonrpc_error_response(id, jsonrpc_error_messages.rate_limited('rate limited', { retry_after }))));
271
- };
272
- if (ip_check) {
273
- const result = action_ip_rate_limiter.check(client_ip);
274
- if (!result.allowed) {
275
- send_rate_limited(result.retry_after);
276
- return;
277
- }
278
- }
279
- if (account_check) {
280
- const result = action_account_rate_limiter.check(request_context.account.id);
281
- if (!result.allowed) {
282
- send_rate_limited(result.retry_after);
283
- return;
284
- }
285
- }
286
- if (ip_check)
287
- action_ip_rate_limiter.record(client_ip);
288
- if (account_check)
289
- action_account_rate_limiter.record(request_context.account.id);
290
- }
291
- // Look up handler — method is validated against spec_by_method above.
292
- const handler = handlers[method];
293
- if (!handler) {
294
- ws.send(JSON.stringify(create_jsonrpc_error_response(id, jsonrpc_error_messages.method_not_found(method))));
295
- return;
296
- }
297
- // Validate input against spec schema.
298
- const parsed = spec.input.safeParse(params);
299
- if (!parsed.success) {
300
- ws.send(JSON.stringify(create_jsonrpc_error_response(id, jsonrpc_error_messages.invalid_params(`invalid params for ${method}`, {
301
- issues: parsed.error.issues,
302
- }))));
303
- return;
304
- }
305
- const validated_input = parsed.data;
306
250
  if (artificial_delay > 0) {
307
251
  log.debug(`throttling ${artificial_delay}ms`);
308
252
  await wait(artificial_delay);
309
253
  }
310
- // Socket-scoped notification — routes to originator only, not
311
- // broadcast. Same helper used in `on_socket_open` so both
312
- // paths share one code path for send-and-log-on-failure.
313
- // Future work: other audiences — account-scoped,
314
- // ACL-filtered, broadcast — likely via a transport-level
315
- // policy hook.
316
- const notify = notify_socket(ws);
317
254
  // Per-request controller — fires on explicit `cancel` or on
318
255
  // socket close (via the socket_abort_controller chain below).
319
256
  // Registered before dispatch so a cancel arriving mid-handler
@@ -322,40 +259,45 @@ export const register_action_ws = (options) => {
322
259
  // null-abort the wrong handler.
323
260
  const request_controller = new AbortController();
324
261
  pending_controllers.set(id, request_controller);
325
- const base = {
326
- request_id: id,
327
- // Populated in `onOpen` before any message can dispatch —
328
- // non-null assertion is safe.
329
- connection_id: captured_connection_id,
330
- notify,
331
- signal: AbortSignal.any([socket_abort_controller.signal, request_controller.signal]),
332
- };
333
- const ctx = extend_context(base, c);
262
+ // Per-message side-effect queues. `pending_effects` collects
263
+ // eager fire-and-forget pool writes (audit emits, etc.);
264
+ // `post_commit_effects` collects deferred thunks pushed
265
+ // via `emit_after_commit` (WS notifications). Both flush
266
+ // in the same try/finally so the next message sees a clean
267
+ // slate.
268
+ const pending_effects = [];
269
+ const post_commit_effects = [];
270
+ const notify = notify_socket(ws);
271
+ const signal = AbortSignal.any([
272
+ socket_abort_controller.signal,
273
+ request_controller.signal,
274
+ ]);
334
275
  try {
335
- const output = await handler(validated_input, ctx);
336
- // DEV-only output validation — catches handler bugs during development.
337
- if (DEV) {
338
- const output_parsed = spec.output.safeParse(output);
339
- if (!output_parsed.success) {
340
- log.error(`output validation failed for ${method}:`, output_parsed.error.issues);
341
- }
342
- }
343
- // Send result directly — null stays null, matching the HTTP RPC path.
344
- ws.send(JSON.stringify({ jsonrpc: JSONRPC_VERSION, id, result: output }));
345
- }
346
- catch (error) {
347
- if (error instanceof ThrownJsonrpcError) {
348
- // Expected handler outcome (conflict, not_found, invalid_params, ...).
349
- // Log at debug without the stack — the throw site is part of protocol, not a bug.
350
- log.debug('handler error:', method, `${error.code} ${error.message}`);
351
- }
352
- else {
353
- log.error('handler error:', method, error);
354
- }
355
- ws.send(JSON.stringify(create_jsonrpc_error_response_from_thrown(id, error)));
276
+ const result = await perform_action({
277
+ action,
278
+ raw_params: params,
279
+ request_id: id,
280
+ account_id,
281
+ credential_type,
282
+ client_ip,
283
+ signal,
284
+ notify,
285
+ connection_id: captured_connection_id,
286
+ preset: upgrade_preset,
287
+ }, {
288
+ db,
289
+ pending_effects,
290
+ post_commit_effects,
291
+ log,
292
+ action_ip_rate_limiter,
293
+ action_account_rate_limiter,
294
+ });
295
+ ws.send(JSON.stringify(perform_action_result_to_envelope(id, result)));
356
296
  }
357
297
  finally {
358
298
  pending_controllers.delete(id);
299
+ await flush_pending_effects(pending_effects, log);
300
+ await flush_post_commit_effects(post_commit_effects, log);
359
301
  }
360
302
  },
361
303
  onClose: async (event, ws) => {
@@ -21,23 +21,15 @@
21
21
  * @module
22
22
  */
23
23
  import type { RoleName } from '../auth/role_schema.js';
24
- import type { Db } from '../db/db.js';
25
24
  import { type RegisterActionWsOptions, type RegisterActionWsResult } from './register_action_ws.js';
26
- import type { BaseHandlerContext } from './action_types.js';
27
25
  /** Options for `register_ws_endpoint`. */
28
- export interface RegisterWsEndpointOptions<TCtx extends BaseHandlerContext> extends RegisterActionWsOptions<TCtx> {
26
+ export interface RegisterWsEndpointOptions extends RegisterActionWsOptions {
29
27
  /**
30
28
  * Origin allowlist regexes — typically parsed from the `ALLOWED_ORIGINS`
31
29
  * env var via `parse_allowed_origins`. Passed straight to
32
30
  * `verify_request_source`.
33
31
  */
34
32
  allowed_origins: Array<RegExp>;
35
- /**
36
- * Pool-level database used for upgrade-time actor resolution + permit
37
- * load. Ran once per connection, then the result is reused for every
38
- * message on the socket.
39
- */
40
- db: Db;
41
33
  /**
42
34
  * Role required to upgrade. Omit for any authenticated account
43
35
  * (`require_auth` + actor resolution alone); set to e.g. `ROLE_ADMIN`
@@ -58,5 +50,5 @@ export interface RegisterWsEndpointOptions<TCtx extends BaseHandlerContext> exte
58
50
  * @mutates options.app - applies origin/auth/authorization/role middleware via `app.use`,
59
51
  * then registers the `GET path` route via the inner `register_action_ws`
60
52
  */
61
- export declare const register_ws_endpoint: <TCtx extends BaseHandlerContext>(options: RegisterWsEndpointOptions<TCtx>) => RegisterActionWsResult;
53
+ export declare const register_ws_endpoint: (options: RegisterWsEndpointOptions) => RegisterActionWsResult;
62
54
  //# sourceMappingURL=register_ws_endpoint.d.ts.map
@@ -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;AAOH,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,wBAAwB,CAAC;AACrD,OAAO,KAAK,EAAC,EAAE,EAAC,MAAM,aAAa,CAAC;AACpC,OAAO,EAEN,KAAK,uBAAuB,EAC5B,KAAK,sBAAsB,EAC3B,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,mBAAmB,CAAC;AAE1D,0CAA0C;AAC1C,MAAM,WAAW,yBAAyB,CACzC,IAAI,SAAS,kBAAkB,CAC9B,SAAQ,uBAAuB,CAAC,IAAI,CAAC;IACtC;;;;OAIG;IACH,eAAe,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC/B;;;;OAIG;IACH,EAAE,EAAE,EAAE,CAAC;IACP;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,QAAQ,CAAC;CACzB;AAqBD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,oBAAoB,GAAI,IAAI,SAAS,kBAAkB,EACnE,SAAS,yBAAyB,CAAC,IAAI,CAAC,KACtC,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,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"}
@@ -21,25 +21,47 @@
21
21
  * @module
22
22
  */
23
23
  import { Logger } from '@fuzdev/fuz_util/log.js';
24
- import { apply_authorization_phase, require_auth, require_role } from '../auth/request_context.js';
24
+ import { apply_authorization_phase, REQUEST_CONTEXT_KEY, require_auth, require_role, } from '../auth/request_context.js';
25
25
  import { verify_request_source } from '../http/origin.js';
26
+ import { ACCOUNT_ID_KEY, TEST_CONTEXT_PRESET_KEY } from '../hono_context.js';
26
27
  import { register_action_ws, } from './register_action_ws.js';
28
+ /** Synthesized auth shape for WS upgrade: account + actor both required. */
29
+ const WS_UPGRADE_AUTH = {
30
+ account: 'required',
31
+ actor: 'required',
32
+ };
27
33
  /**
28
34
  * Upgrade-time authorization middleware. Resolves the acting actor for
29
35
  * the WS connection (single-actor default; multi-actor must supply
30
36
  * `?acting=<uuid>`) and builds the `RequestContext` that per-message
31
37
  * dispatch reads. Returns 400 on resolution failure.
38
+ *
39
+ * Sets `REQUEST_CONTEXT_KEY` on resolved outcomes so the inner
40
+ * `register_action_ws` reads the upgrade-time context via
41
+ * `require_request_context(c)`. Honors the test-preset escape hatch the
42
+ * same way the REST and HTTP RPC binders do.
32
43
  */
33
44
  const create_ws_authorization_middleware = (db) => {
34
45
  return async (c, next) => {
46
+ // Test escape hatch — harnesses pre-populate `REQUEST_CONTEXT_KEY`
47
+ // + flag `TEST_CONTEXT_PRESET_KEY = true`. Production middleware
48
+ // never sets this flag.
49
+ if (c.get(TEST_CONTEXT_PRESET_KEY)) {
50
+ await next();
51
+ return;
52
+ }
35
53
  const acting_param = c.req.query('acting');
36
- // `apply_authorization_phase` is a no-op when the test-harness flag
37
- // `TEST_CONTEXT_PRESET_KEY` is set (escape hatch for pre-baked
38
- // `RequestContext`). Failure shape is `{status, body}`; the WS
39
- // upgrade is a plain HTTP response, so bind it the same way REST does.
40
- const failure = await apply_authorization_phase({ db }, c, true, acting_param ?? undefined);
41
- if (failure)
42
- return c.json(failure.body, failure.status);
54
+ const account_id = c.get(ACCOUNT_ID_KEY) ?? null;
55
+ const result = await apply_authorization_phase({ db }, account_id, WS_UPGRADE_AUTH, acting_param ?? undefined);
56
+ if (!result.ok)
57
+ return c.json(result.body, result.status);
58
+ if (result.request_context !== null) {
59
+ c.set(REQUEST_CONTEXT_KEY, result.request_context);
60
+ }
61
+ // `request_context: null` is unreachable here — `WS_UPGRADE_AUTH` is
62
+ // `account: 'required', actor: 'required'`, and `require_auth` ran
63
+ // upstream, so neither the public nor the unauthenticated branch
64
+ // resolves through this middleware.
43
65
  await next();
44
66
  };
45
67
  };
@@ -60,7 +82,7 @@ export const register_ws_endpoint = (options) => {
60
82
  app.use(path, require_auth);
61
83
  app.use(path, create_ws_authorization_middleware(db));
62
84
  if (required_role !== undefined) {
63
- app.use(path, require_role(required_role));
85
+ app.use(path, require_role([required_role]));
64
86
  }
65
- return register_action_ws({ app, path, log, ...rest });
87
+ return register_action_ws({ app, path, db, log, ...rest });
66
88
  };
@@ -31,7 +31,7 @@ export type AuditEventHandler = (event: AuditLogEvent) => void;
31
31
  * - `session_revoke_all` / `token_revoke_all` / `password_change` — close every socket
32
32
  * for the affected account (all credentials invalidated).
33
33
  *
34
- * `permit_revoke` is intentionally omitted: the WS transport does not track
34
+ * `role_grant_revoke` is intentionally omitted: the WS transport does not track
35
35
  * per-connection role requirements, so role-scoped disconnection would
36
36
  * require either closing all sockets (too aggressive) or new tracking
37
37
  * (out of scope). Consumers that need it compose their own callback.
@@ -18,7 +18,7 @@
18
18
  * - `session_revoke_all` / `token_revoke_all` / `password_change` — close every socket
19
19
  * for the affected account (all credentials invalidated).
20
20
  *
21
- * `permit_revoke` is intentionally omitted: the WS transport does not track
21
+ * `role_grant_revoke` is intentionally omitted: the WS transport does not track
22
22
  * per-connection role requirements, so role-scoped disconnection would
23
23
  * require either closing all sockets (too aggressive) or new tracking
24
24
  * (out of scope). Consumers that need it compose their own callback.
@@ -97,7 +97,7 @@ export declare class BackendWebsocketTransport implements FilterableBroadcastTra
97
97
  * Broadcast to connections whose identity satisfies a predicate.
98
98
  *
99
99
  * Used by the broadcast API when a consumer supplies a subscription ACL hook
100
- * (e.g. tx's `tx_run_created` only reaches the account that owns the run).
100
+ * (e.g. zap's `zap_run_created` only reaches the account that owns the run).
101
101
  * When no ACL is needed, callers should prefer `send(message)` / `#broadcast`
102
102
  * to skip the per-connection predicate overhead.
103
103
  *
@@ -146,7 +146,7 @@ export class BackendWebsocketTransport {
146
146
  * Broadcast to connections whose identity satisfies a predicate.
147
147
  *
148
148
  * Used by the broadcast API when a consumer supplies a subscription ACL hook
149
- * (e.g. tx's `tx_run_created` only reaches the account that owns the run).
149
+ * (e.g. zap's `zap_run_created` only reaches the account that owns the run).
150
150
  * When no ACL is needed, callers should prefer `send(message)` / `#broadcast`
151
151
  * to skip the per-connection predicate overhead.
152
152
  *