@fuzdev/fuz_app 0.54.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 (348) hide show
  1. package/dist/actions/CLAUDE.md +214 -103
  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 +32 -0
  6. package/dist/actions/action_codegen.d.ts.map +1 -1
  7. package/dist/actions/action_codegen.js +35 -15
  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 +141 -22
  11. package/dist/actions/action_rpc.d.ts.map +1 -1
  12. package/dist/actions/action_rpc.js +106 -187
  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 +46 -40
  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 +15 -10
  34. package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
  35. package/dist/actions/register_ws_endpoint.js +54 -7
  36. package/dist/actions/transports.d.ts.map +1 -1
  37. package/dist/actions/transports.js +0 -4
  38. package/dist/actions/transports_ws_auth_guard.d.ts +1 -1
  39. package/dist/actions/transports_ws_auth_guard.js +1 -1
  40. package/dist/actions/transports_ws_backend.d.ts +1 -1
  41. package/dist/actions/transports_ws_backend.js +1 -1
  42. package/dist/auth/CLAUDE.md +794 -410
  43. package/dist/auth/account_action_specs.d.ts +28 -7
  44. package/dist/auth/account_action_specs.d.ts.map +1 -1
  45. package/dist/auth/account_action_specs.js +7 -7
  46. package/dist/auth/account_actions.d.ts +7 -13
  47. package/dist/auth/account_actions.d.ts.map +1 -1
  48. package/dist/auth/account_actions.js +26 -35
  49. package/dist/auth/account_queries.d.ts +52 -16
  50. package/dist/auth/account_queries.d.ts.map +1 -1
  51. package/dist/auth/account_queries.js +87 -38
  52. package/dist/auth/account_routes.d.ts +9 -11
  53. package/dist/auth/account_routes.d.ts.map +1 -1
  54. package/dist/auth/account_routes.js +118 -46
  55. package/dist/auth/account_schema.d.ts +46 -35
  56. package/dist/auth/account_schema.d.ts.map +1 -1
  57. package/dist/auth/account_schema.js +21 -28
  58. package/dist/auth/admin_action_specs.d.ts +100 -32
  59. package/dist/auth/admin_action_specs.d.ts.map +1 -1
  60. package/dist/auth/admin_action_specs.js +64 -33
  61. package/dist/auth/admin_actions.d.ts +13 -19
  62. package/dist/auth/admin_actions.d.ts.map +1 -1
  63. package/dist/auth/admin_actions.js +37 -41
  64. package/dist/auth/audit_emitter.d.ts +160 -0
  65. package/dist/auth/audit_emitter.d.ts.map +1 -0
  66. package/dist/auth/audit_emitter.js +83 -0
  67. package/dist/auth/audit_log_queries.d.ts +17 -48
  68. package/dist/auth/audit_log_queries.d.ts.map +1 -1
  69. package/dist/auth/audit_log_queries.js +20 -56
  70. package/dist/auth/audit_log_routes.d.ts +1 -1
  71. package/dist/auth/audit_log_routes.d.ts.map +1 -1
  72. package/dist/auth/audit_log_routes.js +7 -3
  73. package/dist/auth/audit_log_schema.d.ts +92 -32
  74. package/dist/auth/audit_log_schema.d.ts.map +1 -1
  75. package/dist/auth/audit_log_schema.js +75 -46
  76. package/dist/auth/auth_guard_resolver.d.ts +44 -0
  77. package/dist/auth/auth_guard_resolver.d.ts.map +1 -0
  78. package/dist/auth/auth_guard_resolver.js +56 -0
  79. package/dist/auth/bearer_auth.d.ts +9 -7
  80. package/dist/auth/bearer_auth.d.ts.map +1 -1
  81. package/dist/auth/bearer_auth.js +13 -21
  82. package/dist/auth/bootstrap_account.d.ts +7 -7
  83. package/dist/auth/bootstrap_account.d.ts.map +1 -1
  84. package/dist/auth/bootstrap_account.js +7 -7
  85. package/dist/auth/bootstrap_routes.d.ts.map +1 -1
  86. package/dist/auth/bootstrap_routes.js +11 -10
  87. package/dist/auth/cleanup.d.ts +20 -26
  88. package/dist/auth/cleanup.d.ts.map +1 -1
  89. package/dist/auth/cleanup.js +33 -42
  90. package/dist/auth/credential_type_schema.d.ts +115 -0
  91. package/dist/auth/credential_type_schema.d.ts.map +1 -0
  92. package/dist/auth/credential_type_schema.js +127 -0
  93. package/dist/auth/daemon_token_middleware.d.ts +23 -11
  94. package/dist/auth/daemon_token_middleware.d.ts.map +1 -1
  95. package/dist/auth/daemon_token_middleware.js +28 -22
  96. package/dist/auth/ddl.d.ts +2 -2
  97. package/dist/auth/ddl.d.ts.map +1 -1
  98. package/dist/auth/ddl.js +6 -6
  99. package/dist/auth/deps.d.ts +7 -18
  100. package/dist/auth/deps.d.ts.map +1 -1
  101. package/dist/auth/grant_path_schema.d.ts +117 -0
  102. package/dist/auth/grant_path_schema.d.ts.map +1 -0
  103. package/dist/auth/grant_path_schema.js +137 -0
  104. package/dist/auth/invite_queries.d.ts +12 -1
  105. package/dist/auth/invite_queries.d.ts.map +1 -1
  106. package/dist/auth/invite_queries.js +12 -1
  107. package/dist/auth/invite_schema.d.ts +1 -1
  108. package/dist/auth/invite_schema.d.ts.map +1 -1
  109. package/dist/auth/invite_schema.js +1 -1
  110. package/dist/auth/middleware.d.ts.map +1 -1
  111. package/dist/auth/middleware.js +9 -4
  112. package/dist/auth/migrations.d.ts +37 -14
  113. package/dist/auth/migrations.d.ts.map +1 -1
  114. package/dist/auth/migrations.js +79 -32
  115. package/dist/auth/request_context.d.ts +331 -61
  116. package/dist/auth/request_context.d.ts.map +1 -1
  117. package/dist/auth/request_context.js +378 -95
  118. package/dist/auth/{permit_offer_action_specs.d.ts → role_grant_offer_action_specs.d.ts} +163 -94
  119. package/dist/auth/role_grant_offer_action_specs.d.ts.map +1 -0
  120. package/dist/auth/role_grant_offer_action_specs.js +262 -0
  121. package/dist/auth/role_grant_offer_actions.d.ts +104 -0
  122. package/dist/auth/role_grant_offer_actions.d.ts.map +1 -0
  123. package/dist/auth/role_grant_offer_actions.js +473 -0
  124. package/dist/auth/{permit_offer_notifications.d.ts → role_grant_offer_notifications.d.ts} +90 -70
  125. package/dist/auth/role_grant_offer_notifications.d.ts.map +1 -0
  126. package/dist/auth/role_grant_offer_notifications.js +182 -0
  127. package/dist/auth/role_grant_offer_queries.d.ts +242 -0
  128. package/dist/auth/role_grant_offer_queries.d.ts.map +1 -0
  129. package/dist/auth/role_grant_offer_queries.js +533 -0
  130. package/dist/auth/role_grant_offer_schema.d.ts +150 -0
  131. package/dist/auth/role_grant_offer_schema.d.ts.map +1 -0
  132. package/dist/auth/{permit_offer_schema.js → role_grant_offer_schema.js} +60 -36
  133. package/dist/auth/role_grant_queries.d.ts +231 -0
  134. package/dist/auth/role_grant_queries.d.ts.map +1 -0
  135. package/dist/auth/role_grant_queries.js +320 -0
  136. package/dist/auth/role_schema.d.ts +150 -40
  137. package/dist/auth/role_schema.d.ts.map +1 -1
  138. package/dist/auth/role_schema.js +144 -45
  139. package/dist/auth/scope_kind_schema.d.ts +96 -0
  140. package/dist/auth/scope_kind_schema.d.ts.map +1 -0
  141. package/dist/auth/scope_kind_schema.js +94 -0
  142. package/dist/auth/self_service_role_action_specs.d.ts +6 -1
  143. package/dist/auth/self_service_role_action_specs.d.ts.map +1 -1
  144. package/dist/auth/self_service_role_action_specs.js +3 -1
  145. package/dist/auth/self_service_role_actions.d.ts +34 -27
  146. package/dist/auth/self_service_role_actions.d.ts.map +1 -1
  147. package/dist/auth/self_service_role_actions.js +68 -48
  148. package/dist/auth/session_cookie.d.ts +43 -6
  149. package/dist/auth/session_cookie.d.ts.map +1 -1
  150. package/dist/auth/session_cookie.js +31 -5
  151. package/dist/auth/session_middleware.d.ts +37 -3
  152. package/dist/auth/session_middleware.d.ts.map +1 -1
  153. package/dist/auth/session_middleware.js +33 -7
  154. package/dist/auth/signup_routes.d.ts.map +1 -1
  155. package/dist/auth/signup_routes.js +48 -19
  156. package/dist/auth/standard_action_specs.d.ts +2 -2
  157. package/dist/auth/standard_action_specs.js +4 -4
  158. package/dist/auth/standard_rpc_actions.d.ts +23 -19
  159. package/dist/auth/standard_rpc_actions.d.ts.map +1 -1
  160. package/dist/auth/standard_rpc_actions.js +12 -12
  161. package/dist/db/migrate.d.ts +12 -8
  162. package/dist/db/migrate.d.ts.map +1 -1
  163. package/dist/db/migrate.js +10 -7
  164. package/dist/dev/setup.d.ts +2 -2
  165. package/dist/dev/setup.d.ts.map +1 -1
  166. package/dist/dev/setup.js +9 -7
  167. package/dist/env/load.d.ts +1 -1
  168. package/dist/env/load.js +1 -1
  169. package/dist/hono_context.d.ts +64 -5
  170. package/dist/hono_context.d.ts.map +1 -1
  171. package/dist/hono_context.js +38 -2
  172. package/dist/http/CLAUDE.md +264 -87
  173. package/dist/http/auth_shape.d.ts +191 -0
  174. package/dist/http/auth_shape.d.ts.map +1 -0
  175. package/dist/http/auth_shape.js +237 -0
  176. package/dist/http/common_routes.js +3 -3
  177. package/dist/http/db_routes.d.ts +4 -0
  178. package/dist/http/db_routes.d.ts.map +1 -1
  179. package/dist/http/db_routes.js +44 -7
  180. package/dist/http/error_schemas.d.ts +132 -19
  181. package/dist/http/error_schemas.d.ts.map +1 -1
  182. package/dist/http/error_schemas.js +132 -40
  183. package/dist/http/jsonrpc_errors.d.ts +27 -2
  184. package/dist/http/jsonrpc_errors.d.ts.map +1 -1
  185. package/dist/http/jsonrpc_errors.js +26 -2
  186. package/dist/http/pending_effects.d.ts +71 -18
  187. package/dist/http/pending_effects.d.ts.map +1 -1
  188. package/dist/http/pending_effects.js +87 -18
  189. package/dist/http/proxy.d.ts +52 -5
  190. package/dist/http/proxy.d.ts.map +1 -1
  191. package/dist/http/proxy.js +92 -14
  192. package/dist/http/route_spec.d.ts +113 -41
  193. package/dist/http/route_spec.d.ts.map +1 -1
  194. package/dist/http/route_spec.js +130 -52
  195. package/dist/http/schema_helpers.d.ts +3 -2
  196. package/dist/http/schema_helpers.d.ts.map +1 -1
  197. package/dist/http/schema_helpers.js +9 -2
  198. package/dist/http/surface.d.ts +2 -1
  199. package/dist/http/surface.d.ts.map +1 -1
  200. package/dist/http/surface.js +1 -2
  201. package/dist/http/surface_query.d.ts +39 -35
  202. package/dist/http/surface_query.d.ts.map +1 -1
  203. package/dist/http/surface_query.js +79 -36
  204. package/dist/primitive_schemas.d.ts +39 -0
  205. package/dist/primitive_schemas.d.ts.map +1 -0
  206. package/dist/primitive_schemas.js +40 -0
  207. package/dist/realtime/sse_auth_guard.d.ts +5 -5
  208. package/dist/realtime/sse_auth_guard.js +9 -9
  209. package/dist/runtime/mock.d.ts +1 -1
  210. package/dist/runtime/mock.js +1 -1
  211. package/dist/server/app_backend.d.ts +14 -11
  212. package/dist/server/app_backend.d.ts.map +1 -1
  213. package/dist/server/app_backend.js +12 -8
  214. package/dist/server/app_server.d.ts +7 -7
  215. package/dist/server/app_server.d.ts.map +1 -1
  216. package/dist/server/app_server.js +36 -31
  217. package/dist/server/validate_nginx.d.ts +1 -1
  218. package/dist/server/validate_nginx.js +1 -1
  219. package/dist/testing/CLAUDE.md +73 -55
  220. package/dist/testing/admin_integration.d.ts +5 -6
  221. package/dist/testing/admin_integration.d.ts.map +1 -1
  222. package/dist/testing/admin_integration.js +100 -96
  223. package/dist/testing/adversarial_headers.js +1 -1
  224. package/dist/testing/app_server.d.ts +11 -14
  225. package/dist/testing/app_server.d.ts.map +1 -1
  226. package/dist/testing/app_server.js +18 -17
  227. package/dist/testing/assertions.d.ts.map +1 -1
  228. package/dist/testing/assertions.js +2 -1
  229. package/dist/testing/attack_surface.d.ts.map +1 -1
  230. package/dist/testing/attack_surface.js +15 -9
  231. package/dist/testing/audit_completeness.d.ts +2 -2
  232. package/dist/testing/audit_completeness.d.ts.map +1 -1
  233. package/dist/testing/audit_completeness.js +53 -39
  234. package/dist/testing/auth_apps.d.ts +5 -4
  235. package/dist/testing/auth_apps.d.ts.map +1 -1
  236. package/dist/testing/auth_apps.js +28 -22
  237. package/dist/testing/data_exposure.d.ts.map +1 -1
  238. package/dist/testing/data_exposure.js +5 -5
  239. package/dist/testing/db.d.ts +1 -1
  240. package/dist/testing/db.d.ts.map +1 -1
  241. package/dist/testing/db.js +4 -4
  242. package/dist/testing/db_entities.d.ts +22 -0
  243. package/dist/testing/db_entities.d.ts.map +1 -0
  244. package/dist/testing/db_entities.js +28 -0
  245. package/dist/testing/entities.d.ts +10 -8
  246. package/dist/testing/entities.d.ts.map +1 -1
  247. package/dist/testing/entities.js +22 -18
  248. package/dist/testing/integration.d.ts.map +1 -1
  249. package/dist/testing/integration.js +13 -14
  250. package/dist/testing/integration_helpers.d.ts +8 -6
  251. package/dist/testing/integration_helpers.d.ts.map +1 -1
  252. package/dist/testing/integration_helpers.js +29 -23
  253. package/dist/testing/middleware.d.ts +15 -11
  254. package/dist/testing/middleware.d.ts.map +1 -1
  255. package/dist/testing/middleware.js +75 -32
  256. package/dist/testing/rpc_attack_surface.d.ts.map +1 -1
  257. package/dist/testing/rpc_attack_surface.js +40 -24
  258. package/dist/testing/rpc_helpers.d.ts.map +1 -1
  259. package/dist/testing/rpc_helpers.js +3 -1
  260. package/dist/testing/rpc_round_trip.d.ts +1 -1
  261. package/dist/testing/rpc_round_trip.d.ts.map +1 -1
  262. package/dist/testing/rpc_round_trip.js +14 -13
  263. package/dist/testing/sse_round_trip.d.ts +3 -4
  264. package/dist/testing/sse_round_trip.d.ts.map +1 -1
  265. package/dist/testing/sse_round_trip.js +7 -11
  266. package/dist/testing/standard.d.ts +1 -1
  267. package/dist/testing/stubs.d.ts +25 -0
  268. package/dist/testing/stubs.d.ts.map +1 -1
  269. package/dist/testing/stubs.js +43 -2
  270. package/dist/testing/surface_invariants.d.ts +2 -2
  271. package/dist/testing/ws_round_trip.d.ts +12 -13
  272. package/dist/testing/ws_round_trip.d.ts.map +1 -1
  273. package/dist/testing/ws_round_trip.js +24 -12
  274. package/dist/ui/AdminAccounts.svelte +23 -20
  275. package/dist/ui/AdminOverview.svelte +15 -13
  276. package/dist/ui/AdminOverview.svelte.d.ts.map +1 -1
  277. package/dist/ui/{AdminPermitHistory.svelte → AdminRoleGrantHistory.svelte} +12 -12
  278. package/dist/ui/AdminRoleGrantHistory.svelte.d.ts +4 -0
  279. package/dist/ui/AdminRoleGrantHistory.svelte.d.ts.map +1 -0
  280. package/dist/ui/BootstrapForm.svelte +1 -1
  281. package/dist/ui/CLAUDE.md +65 -59
  282. package/dist/ui/{PermitOfferForm.svelte → RoleGrantOfferForm.svelte} +37 -22
  283. package/dist/ui/RoleGrantOfferForm.svelte.d.ts +20 -0
  284. package/dist/ui/RoleGrantOfferForm.svelte.d.ts.map +1 -0
  285. package/dist/ui/{PermitOfferHistory.svelte → RoleGrantOfferHistory.svelte} +12 -12
  286. package/dist/ui/{PermitOfferHistory.svelte.d.ts → RoleGrantOfferHistory.svelte.d.ts} +4 -4
  287. package/dist/ui/RoleGrantOfferHistory.svelte.d.ts.map +1 -0
  288. package/dist/ui/{PermitOfferInbox.svelte → RoleGrantOfferInbox.svelte} +14 -14
  289. package/dist/ui/{PermitOfferInbox.svelte.d.ts → RoleGrantOfferInbox.svelte.d.ts} +4 -4
  290. package/dist/ui/RoleGrantOfferInbox.svelte.d.ts.map +1 -0
  291. package/dist/ui/SignupForm.svelte +1 -1
  292. package/dist/ui/SurfaceExplorer.svelte +35 -15
  293. package/dist/ui/SurfaceExplorer.svelte.d.ts.map +1 -1
  294. package/dist/ui/account_sessions_state.svelte.d.ts +2 -3
  295. package/dist/ui/account_sessions_state.svelte.d.ts.map +1 -1
  296. package/dist/ui/account_sessions_state.svelte.js +2 -3
  297. package/dist/ui/admin_accounts_state.svelte.d.ts +25 -18
  298. package/dist/ui/admin_accounts_state.svelte.d.ts.map +1 -1
  299. package/dist/ui/admin_accounts_state.svelte.js +28 -17
  300. package/dist/ui/admin_rpc_adapters.d.ts +20 -20
  301. package/dist/ui/admin_rpc_adapters.d.ts.map +1 -1
  302. package/dist/ui/admin_rpc_adapters.js +17 -17
  303. package/dist/ui/admin_sessions_state.svelte.d.ts +2 -2
  304. package/dist/ui/admin_sessions_state.svelte.js +2 -2
  305. package/dist/ui/audit_log_state.svelte.d.ts +7 -7
  306. package/dist/ui/audit_log_state.svelte.d.ts.map +1 -1
  307. package/dist/ui/audit_log_state.svelte.js +6 -6
  308. package/dist/ui/auth_state.svelte.d.ts +3 -3
  309. package/dist/ui/auth_state.svelte.d.ts.map +1 -1
  310. package/dist/ui/auth_state.svelte.js +6 -6
  311. package/dist/ui/format_scope.d.ts +2 -2
  312. package/dist/ui/format_scope.js +2 -2
  313. package/dist/ui/{permit_offers_state.svelte.d.ts → role_grant_offers_state.svelte.d.ts} +39 -31
  314. package/dist/ui/role_grant_offers_state.svelte.d.ts.map +1 -0
  315. package/dist/ui/{permit_offers_state.svelte.js → role_grant_offers_state.svelte.js} +25 -19
  316. package/dist/ui/ui_format.js +2 -2
  317. package/package.json +3 -3
  318. package/dist/auth/permit_offer_action_specs.d.ts.map +0 -1
  319. package/dist/auth/permit_offer_action_specs.js +0 -227
  320. package/dist/auth/permit_offer_actions.d.ts +0 -110
  321. package/dist/auth/permit_offer_actions.d.ts.map +0 -1
  322. package/dist/auth/permit_offer_actions.js +0 -452
  323. package/dist/auth/permit_offer_notifications.d.ts.map +0 -1
  324. package/dist/auth/permit_offer_notifications.js +0 -182
  325. package/dist/auth/permit_offer_queries.d.ts +0 -183
  326. package/dist/auth/permit_offer_queries.d.ts.map +0 -1
  327. package/dist/auth/permit_offer_queries.js +0 -408
  328. package/dist/auth/permit_offer_schema.d.ts +0 -103
  329. package/dist/auth/permit_offer_schema.d.ts.map +0 -1
  330. package/dist/auth/permit_queries.d.ts +0 -210
  331. package/dist/auth/permit_queries.d.ts.map +0 -1
  332. package/dist/auth/permit_queries.js +0 -294
  333. package/dist/auth/require_keeper.d.ts +0 -20
  334. package/dist/auth/require_keeper.d.ts.map +0 -1
  335. package/dist/auth/require_keeper.js +0 -35
  336. package/dist/auth/route_guards.d.ts +0 -21
  337. package/dist/auth/route_guards.d.ts.map +0 -1
  338. package/dist/auth/route_guards.js +0 -32
  339. package/dist/auth/session_lifecycle.d.ts +0 -37
  340. package/dist/auth/session_lifecycle.d.ts.map +0 -1
  341. package/dist/auth/session_lifecycle.js +0 -29
  342. package/dist/ui/AdminPermitHistory.svelte.d.ts +0 -4
  343. package/dist/ui/AdminPermitHistory.svelte.d.ts.map +0 -1
  344. package/dist/ui/PermitOfferForm.svelte.d.ts +0 -14
  345. package/dist/ui/PermitOfferForm.svelte.d.ts.map +0 -1
  346. package/dist/ui/PermitOfferHistory.svelte.d.ts.map +0 -1
  347. package/dist/ui/PermitOfferInbox.svelte.d.ts.map +0 -1
  348. package/dist/ui/permit_offers_state.svelte.d.ts.map +0 -1
@@ -8,7 +8,7 @@ dispatcher, every transport adapter, the event state machine, and the
8
8
  reactive frontend client.
9
9
 
10
10
  For narrative context (consumer wiring examples, client-authoritative vs
11
- server-authoritative dispatch, permit-offer UI integration) see
11
+ server-authoritative dispatch, role-grant-offer UI integration) see
12
12
  ../../docs/usage.md §Deriving Route/Event Specs, §Single JSON-RPC 2.0 Endpoint,
13
13
  §WebSocket Endpoint. For DEV-only output validation semantics see
14
14
  ../../docs/architecture.md §DEV-only Output Validation. For the SAES
@@ -28,17 +28,17 @@ codegen helpers are post-SAES-RPC-closeout stable.
28
28
 
29
29
  Canonical source of truth. Three concrete kinds discriminate on `kind`:
30
30
 
31
- | Kind | `auth` | `side_effects` | `output` | `async` |
32
- | --------------------- | ----------------------- | -------------- | ----------- | ------- |
33
- | `request_response` | `ActionAuth` (non-null) | arbitrary | arbitrary | `true` |
34
- | `remote_notification` | `null` | `true` | `z.ZodVoid` | `true` |
35
- | `local_call` | `null` | arbitrary | arbitrary | boolean |
31
+ | Kind | `auth` | `side_effects` | `output` | `async` |
32
+ | --------------------- | ---------------------- | -------------- | ----------- | ------- |
33
+ | `request_response` | `RouteAuth` (non-null) | arbitrary | arbitrary | `true` |
34
+ | `remote_notification` | `null` | `true` | `z.ZodVoid` | `true` |
35
+ | `local_call` | `null` | arbitrary | arbitrary | boolean |
36
36
 
37
37
  Enums + unions:
38
38
 
39
39
  - `ActionKind` — `'request_response' | 'remote_notification' | 'local_call'`
40
40
  - `ActionInitiator` — `'frontend' | 'backend' | 'both'`
41
- - `ActionAuth` — `'public' | 'authenticated' | 'keeper' | {role: string}`
41
+ - `RouteAuth` — flat record `{account, actor, roles?, credential_types?}` from `http/auth_shape.ts`. Each axis (`account`, `actor`) is `'none' | 'optional' | 'required'`; `roles` and `credential_types` are optional any-of arrays. Cross-axis invariants: roles imply `actor: 'required'`; `account: 'none'` implies `actor: 'none'` (no accountless actors in v1); the unrestricted leaf (`account: 'none', actor: 'none'`) cannot declare roles or credential gates. The biconditional `actor !== 'none' ⟺ input declares acting?: ActingActor` is enforced at registration time via `assert_route_auth_acting_biconditional`.
42
42
  - `ActionSpecUnion` — discriminated union of the three variants
43
43
  - `ActionEventPhase` — `'send_request' | 'receive_request' | 'send_response' | 'receive_response' | 'send_error' | 'receive_error' | 'send' | 'receive' | 'execute'`
44
44
  - `is_action_spec(value)` — structural type guard
@@ -53,10 +53,10 @@ declarative metadata for consumers (codegen, UI form-state matching, docs)
53
53
  to read off the spec instead of scanning handler code. No runtime
54
54
  enforcement — drift between declared reasons and what handlers actually
55
55
  throw is caught per-module by source-scanning unit tests (see
56
- `../../test/auth/permit_offer_actions.error_reasons.test.ts`). Reuses
56
+ `../../test/auth/role_grant_offer_actions.error_reasons.test.ts`). Reuses
57
57
  the same `as const` string constants the handler throws (e.g.
58
- `ERROR_OFFER_*` from `../auth/permit_offer_action_specs.ts`,
59
- `ERROR_PERMIT_NOT_FOUND` from `../http/error_schemas.ts`) so call
58
+ `ERROR_ROLE_GRANT_OFFER_*` from `../auth/role_grant_offer_action_specs.ts`,
59
+ `ERROR_ROLE_GRANT_NOT_FOUND` from `../http/error_schemas.ts`) so call
60
60
  sites can import either side. Standard transport errors (validation,
61
61
  auth, rate-limit) stay implicit.
62
62
 
@@ -65,8 +65,10 @@ the dispatcher's per-action rate-limit hook. Same hook fires on the HTTP
65
65
  RPC dispatcher (`create_rpc_endpoint`) and the WebSocket dispatcher
66
66
  (`register_action_ws`) — one budget per action, not per transport.
67
67
  `'ip'` keys on the resolved client IP; `'account'` keys on
68
- `request_context.actor.id` (post-auth) and is rejected at registration
69
- when paired with `auth: 'public'` (no actor to key on); `'both'` runs
68
+ `request_context.account.id` (post-auth, account-grain every
69
+ authenticated action has an account regardless of whether an actor was
70
+ resolved) and is rejected at registration when paired with
71
+ `auth.account !== 'required'` (no account to key on); `'both'` runs
70
72
  both checks. **Throttle-requests semantics** — every invocation records,
71
73
  regardless of outcome (different from REST login's throttle-failures
72
74
  that resets on success). The motivating threat is admin mutation oracles
@@ -147,7 +149,7 @@ not the runtime):
147
149
  ### Primitives
148
150
 
149
151
  - `ImportBuilder` — tracks value / type / namespace imports; emits `import type` when every entry on a module is a type (tree-shaking). Namespace (`* as specs`) entries are emitted verbatim. Public surface: `add`, `add_type`, `add_many`, `add_types`, `build`, `preview`, `has_imports`, `import_count`, `clear`.
150
- - `get_executor_phases(spec, executor)` — phases a given executor (`'frontend' | 'backend'`) participates in for the spec. Deduplicates via `Set` (handles `initiator: 'both'` overlap).
152
+ - `get_executor_phases(spec, executor)` — phases a given executor (`'frontend' | 'backend'`) participates in for the spec. Branch-aware: the backend `can_receive` branch only pushes `send_error` when `!can_send`, so `initiator: 'both'` doesn't double-count and no `Set` dedup is needed.
151
153
  - `get_handler_return_type(spec, phase, imports, collections_path?)` — the TS type a phase handler must return; triggers the `ActionOutputs` import (sourced from `collections_path`, default `'./action_collections.js'`) as a side effect.
152
154
  - `generate_phase_handlers(spec, executor, imports, {action_event_type?, collections_path?})` — emits the typed handler-map fragment for one action; consumers compose these into `ActionHandlers` types. Returns `''` when the spec contributes no phases on the given executor (e.g. a backend-only `local_call` asked for `'frontend'`) so wrappers' `.filter(Boolean)` drops the row entirely instead of emitting a useless `${method}?: never` for a method that doesn't belong on this side.
153
155
  - `generate_actions_api_method_signature(spec, imports, {sync_returns_value?, collections_path?})` — single source of truth for the typed `FrontendActionsApi` method shape. Threads `options?: RpcClientCallOptions` (`{signal?, transport_name?, queue?}`) onto every async method — `request_response`, `remote_notification`, and async `local_call` — and wraps the return in `Promise<Result<...>>`. Registers exactly the imports the emitted line references on `imports` — `ActionInputs` only when the spec has input, `RpcClientCallOptions` only when async, `Result` / `JsonrpcErrorObject` only when the return wraps in `Result`. Mirrors the leaf-level pattern `get_handler_return_type` already follows so wrappers no longer pre-register imports a per-spec emit might not actually use.
@@ -158,6 +160,7 @@ not the runtime):
158
160
  - `DEFAULT_COLLECTIONS_PATH = './action_collections.js'` — shared default for every helper that takes a `collections_path?`.
159
161
  - `DEFAULT_SPECS_MODULE = './action_specs.js'` — shared default for helpers that emit `specs.{method}_action_spec` and need a `* as specs` namespace import.
160
162
  - `DEFAULT_METATYPES_PATH = './action_metatypes.js'` — shared default for the sibling module carrying the generated `ActionMethod` enum.
163
+ - `resolve_spec_qualifier(imports, {specs_module?, qualify_spec?})` — the standard default-vs-callback resolver every multi-source-aware helper in this module uses. With `qualify_spec` set, returns the callback verbatim (consumer owns its namespace setup); otherwise registers `* as specs from specs_module` (default `DEFAULT_SPECS_MODULE`) on `imports` and returns `(s) => 'specs.' + to_action_spec_identifier(s.method)`. Reuse from custom codegen helpers instead of reimplementing the defaulting + import-registration dance.
161
164
 
162
165
  ### High-level helpers
163
166
 
@@ -179,9 +182,9 @@ protocol actions in their typed API.
179
182
  **Consumer tiers and namespace handling.** Single-source consumers (zzz,
180
183
  undying — every spec lives in one local `action_specs.ts`) drop straight
181
184
  into the helpers and accept the default `* as specs from specs_module`
182
- namespace import. Multi-source consumers (tx, visiones — which stitch
185
+ namespace import. Multi-source consumers (zap, visiones — which stitch
183
186
  local specs together with `all_admin_action_specs` /
184
- `all_permit_offer_action_specs` / `all_account_action_specs` /
187
+ `all_role_grant_offer_action_specs` / `all_account_action_specs` /
185
188
  `all_self_service_role_action_specs` from fuz_app) call
186
189
  `create_namespace_qualifier(sources, imports)` once, then pass the
187
190
  returned `qualify_spec` callback to the multi-source helpers
@@ -194,7 +197,7 @@ multi-namespace imports. The helper appends `.input` / `.output` to the
194
197
  qualified identifier in `generate_action_inputs_outputs` automatically;
195
198
  the callback returns the bare spec identifier.
196
199
 
197
- Tier 1 (HTTP-only, e.g. tx/visiones) emits a smaller surface — typically just
200
+ Tier 1 (HTTP-only, e.g. zap/visiones) emits a smaller surface — typically just
198
201
  `ActionMethod` + `FrontendActionsApi` + `ActionInputs` / `ActionOutputs`
199
202
  interfaces — and never calls `generate_typed_action_event_alias` or
200
203
  `generate_frontend_action_handlers`. Tier 2 (`TypedActionEvent`-aware, e.g.
@@ -215,42 +218,69 @@ and `FrontendActionHandlers`.
215
218
  ### Wrapper + multi-source helper
216
219
 
217
220
  - `compose_gen_file({origin_path, imports, blocks})` — encapsulates the per-`*.gen.ts` boilerplate (banner + `imports.build()` + blocks join + template literal). Returns the full file body. Each consumer producer collapses to one `compose_gen_file` call wrapping the helper invocations.
218
- - `create_namespace_qualifier(sources, imports)` — multi-source consumer helper. Takes `ReadonlyArray<{ns, module, specs}>`, registers `import * as ns from module` for each on `imports`, builds the `method_to_ns` lookup with duplicate-method detection, returns `{qualify_spec, all_specs}` ready to thread through the high-level helpers. Closes the per-file boilerplate gap that kept tx + visiones on hand-rolled template strings even after the `qualify_spec?` callback landed (the per-call callback wasn't enough — the import dance + dup-check was the real boilerplate).
221
+ - `create_namespace_qualifier(sources, imports)` — multi-source consumer helper. Takes `ReadonlyArray<{ns, module, specs}>`, registers `import * as ns from module` for each on `imports`, builds the `method_to_ns` lookup with duplicate-method detection, returns `{qualify_spec, all_specs}` ready to thread through the high-level helpers. Closes the per-file boilerplate gap that kept zap + visiones on hand-rolled template strings even after the `qualify_spec?` callback landed (the per-call callback wasn't enough — the import dance + dup-check was the real boilerplate).
219
222
 
220
223
  ## HTTP bridge (`action_bridge.ts`)
221
224
 
222
225
  Derives transport-specific specs from action specs. HTTP-specific concerns
223
226
  (path, handler, errors) come from options, not the action spec.
224
227
 
225
- - `create_action_route_spec(spec, options)` — one action → one `RouteSpec`. HTTP method defaults by `side_effects` (`true` → POST, `false` → GET; override via `options.http_method`). Auth maps via `map_action_auth` (`'public'` `{type: 'none'}`, `'authenticated'` `{type: 'authenticated'}`, `'keeper'` `{type: 'keeper'}`, `{role}` → `{type: 'role', role}`). `options.errors: RouteErrorSchemas` attaches transport-specific (HTTP status–keyed) error shapes. `transaction: spec.side_effects`. Throws if `spec.auth` is null.
228
+ - `create_action_route_spec(spec, options)` — one action → one `RouteSpec`. HTTP method defaults by `side_effects` (`true` → POST, `false` → GET; override via `options.http_method`). `route.auth` is `spec.auth` verbatim (the same `RouteAuth` shape governs both surfaces). `options.errors: RouteErrorSchemas` attaches transport-specific (HTTP status–keyed) error shapes. `transaction: spec.side_effects`. Throws if `spec.auth` is null.
226
229
  - `create_action_event_spec(spec, {channel?})` — one notification action → one `EventSpec` for SSE surface + `create_validated_broadcaster`. Throws on non-`remote_notification` kind.
227
- - `map_action_auth(auth)` / `derive_http_method(side_effects)` — exported for consumers that build custom bridges.
230
+ - `derive_http_method(side_effects)` — exported for consumers that build custom bridges.
228
231
 
229
232
  ## Single JSON-RPC 2.0 endpoint (`action_rpc.ts`)
230
233
 
231
234
  `create_rpc_endpoint({path, actions, log}): RouteSpec[]` produces **two**
232
235
  route specs on the same path (GET + POST) that share one internal
233
236
  dispatcher. Per-action auth lives inside the dispatcher; the outer routes
234
- use `auth: {type: 'none'}` and `transaction: false`.
237
+ use `auth: {account: 'none', actor: 'none'}` and `transaction: false`.
235
238
 
236
- Dispatcher phase order (POST; GET differs only at step 1):
239
+ The HTTP RPC dispatcher is a thin shim around `perform_action`
240
+ (`actions/perform_action.ts`). The shim owns the wire-shape concerns
241
+ (envelope parsing, GET vs POST split, `c.json` binding); the
242
+ auth/validation/dispatch pipeline is shared with the WebSocket
243
+ dispatcher.
244
+
245
+ Phase order: **401 → 400 → 403 → handler** — validate first, authorize
246
+ after. The trade-off is that an unauthorized caller sees the validation
247
+ step; the alternative ordering (403-before-400) was rejected because
248
+ defense-in-depth via attack-surface obscurity is illusory when the
249
+ surface is published in `library.json` codegen anyway.
250
+
251
+ Shim responsibilities:
237
252
 
238
253
  1. **Parse envelope** — POST body as `JsonrpcRequest` (parse errors → JSON-RPC `parse_error` 400). GET reads `method`, `id`, `params` from query string; missing `method`/`id` → 400 `invalid_request`. Integer `id` normalization: `?id=42` matches `{id: 42}`.
239
- 2. **Lookup method** — `Map<method, RpcAction>`. Unknown method → `method_not_found`. Duplicate methods throw at construction.
240
- 3. **GET read restriction** — GET is rejected for `side_effects: true` actions (`invalid_request` with "must use POST").
241
- 4. **Auth check** — via `check_action_auth(spec.auth, request_context, credential_type)`. `keeper` requires `credential_type === 'daemon_token'` AND `has_role(request_context, 'keeper')` the `has_role` alone is insufficient, session/bearer cannot elevate. `{role}` uses `has_role`. Failure `unauthenticated` / `forbidden`.
242
- 5. **Validate params**`spec.input.safeParse(raw_params ?? null-if-null-schema)`. Failure `invalid_params` with `{issues}`.
243
- 6. **Dispatch** — `spec.side_effects` picks transaction (`route.db.transaction(tx => execute(tx))`) vs pool (`route.db`). Handler throws roll back the transaction the catch sits outside the transaction boundary.
244
- 7. **DEV-only output validation** — `spec.output.safeParse(output)` runs only under `DEV` (from `esm-env`). On mismatch: `log.error(...)`, return response unchanged; never throws, never mutates status.
245
-
246
- Error paths: `ThrownJsonrpcError` (duck-typed via `err instanceof Error &&
247
- typeof err.code === 'number'`) preserves code + data verbatim, status via
248
- `jsonrpc_error_code_to_http_status`. Duck-typing avoids cross-copy
249
- `instanceof` misses when consumers throw their own `ThrownJsonrpcError`
250
- (e.g. zzz). Generic thrown errors become `internal_error` 500; message is
251
- the raw error under `DEV`, "internal server error" otherwise.
252
-
253
- Per-request handler shape:
254
+ 2. **Lookup method** — `Map<method, RpcAction>`. Unknown method → `method_not_found`. Duplicate methods throw at construction. Registration also runs `assert_route_auth_acting_biconditional(spec.auth, {input: spec.input}, ...)` to enforce invariant 2 — the helper takes a `{input, query?}` slot set so REST (input + query bi-located) and actions (input-only) share one entry point with surface-appropriate error messages.
255
+ 3. **GET read restriction** — GET is rejected for `side_effects: true` actions (`invalid_request` with "must use POST"). HTTP-only.
256
+ 4. **Build PerformActionInput** — read `account_id` / `credential_type` from `c.var`, resolve `client_ip` via `get_client_ip`, pass `c.req.raw.signal` as `signal`, build a DEV-warn-and-drop `notify`. Test-preset escape hatch reads `TEST_CONTEXT_PRESET_KEY` + `REQUEST_CONTEXT_KEY` and forwards as `preset.request_context`.
257
+ 5. **Call `perform_action`**runs steps 1–6 of the shared pipeline (see §Shared dispatch core below).
258
+ 6. **Bind result** — `perform_action_result_to_envelope(id, result)` builds the JSON-RPC wire envelope; `c.json(envelope, result.status)` returns it.
259
+
260
+ The shared core inside `perform_action` runs:
261
+
262
+ - Pre-validation auth (401), input validation (400), authorization phase (with `apply_authorization_phase` resolving the actor from `validated_input.acting`), post-authorization auth (403 — credential gate first, role gate second), rate limit (429), transactional dispatch + DEV output validation, error normalization.
263
+
264
+ Resolution failures from the authorization phase come back as
265
+ `AuthorizationResult.ok === false` carrying `{status, body}`
266
+ `perform_action` folds this into a JSON-RPC envelope where `error.code`
267
+ maps from `http_status_to_jsonrpc_error_code(result.status)`,
268
+ `error.message` is the reason string, and `error.data: {reason, ...rest}`
269
+ flattens any diagnostic fields (e.g. `available[]` for `actor_required`).
270
+ The two 500 reasons stay distinct: `no_actors_on_account` (signup
271
+ invariant violation — the actor enumeration came back empty);
272
+ `account_vanished` (torn read after resolve). REST emits the same `body`
273
+ directly via `c.json(body, status)` for surface consistency.
274
+
275
+ Error paths: `ThrownJsonrpcError` (duck-typed via `err instanceof Error
276
+ && typeof err.code === 'number'`) preserves code + data verbatim. Duck-
277
+ typing avoids cross-copy `instanceof` misses when consumers throw their
278
+ own `ThrownJsonrpcError` (e.g. zzz). Generic thrown errors become
279
+ `internal_error` 500; message is the raw error under `DEV`, "internal
280
+ server error" otherwise. The HTTP shim's outer `c.json` then binds the
281
+ status.
282
+
283
+ Per-request handler shape (uniform across HTTP RPC + WS):
254
284
 
255
285
  ```ts
256
286
  type ActionHandler<TInput, TOutput> = (
@@ -261,12 +291,14 @@ type ActionHandler<TInput, TOutput> = (
261
291
  interface ActionContext {
262
292
  auth: RequestContext | null; // null for public actions
263
293
  request_id: JsonrpcRequestId;
294
+ connection_id?: Uuid; // populated on WS, undefined on HTTP
264
295
  db: Db; // transaction for mutations, pool for reads
265
- background_db: Db; // always pool — for fire-and-forget outlive
266
- pending_effects: Array<Promise<void>>;
296
+ pending_effects: Array<Promise<void>>; // eager pool writes already in flight see http/CLAUDE.md §Pending Effects
297
+ post_commit_effects: Array<() => void | Promise<void>>; // deferred — push via `emit_after_commit`
298
+ client_ip: string;
267
299
  log: Logger;
268
- notify: (method, params) => void; // HTTP: DEV-mode warn + drop (no streaming channel)
269
- signal: AbortSignal; // c.req.raw.signal fires on client disconnect
300
+ notify: (method, params) => void; // HTTP: DEV-mode warn + drop (no streaming channel); WS: socket-scoped
301
+ signal: AbortSignal; // HTTP: client-disconnect; WS: AbortSignal.any([socket_close, request_cancel])
270
302
  }
271
303
 
272
304
  interface RpcAction {
@@ -275,31 +307,65 @@ interface RpcAction {
275
307
  }
276
308
  ```
277
309
 
278
- ### `rpc_action(spec, handler)` — typed binder
310
+ ### `rpc_action(spec, handler)` — typed binder with conditional `ctx.auth` narrowing
279
311
 
280
312
  `rpc_action<TSpec extends RequestResponseActionSpec>(spec, handler)`
281
- returns a `RpcAction` with the handler's input / output types pinned to
282
- `z.infer<TSpec['input']>` and `z.infer<TSpec['output']>` via the generic.
313
+ returns a `RpcAction` with the handler's input / output types pinned
314
+ to `z.infer<TSpec['input']>` / `z.infer<TSpec['output']>` and the
315
+ handler's `ctx.auth` slot tightened to the narrowest shape the
316
+ dispatcher's runtime guarantee allows. The conditional `HandlerForSpec<TSpec>`
317
+ discriminates on the spec literal:
318
+
319
+ | Spec auth axes | Selected handler type | `ctx.auth` |
320
+ | ------------------------------------------------------ | --------------------- | ------------------------ |
321
+ | `auth.actor === 'required'` | `ActorActionHandler` | `RequestActorContext` |
322
+ | `auth.account === 'required' && auth.actor === 'none'` | `AuthActionHandler` | `RequestContext` |
323
+ | else (public, optional axes) | `ActionHandler` | `RequestContext \| null` |
324
+
283
325
  Use this at every spec → handler binding site so handler-type errors
284
326
  surface at the factory call instead of at runtime:
285
327
 
286
328
  ```ts
287
- export const create_admin_actions = (deps, options) => [
288
- rpc_action(admin_account_list_action_spec, account_list_handler),
289
- rpc_action(admin_session_revoke_all_action_spec, session_revoke_all_handler),
329
+ // actor-implying spec ctx.auth: RequestActorContext (actor non-null)
330
+ rpc_action(role_grant_revoke_action_spec, async (input, ctx) => {
331
+ const revoker_id = ctx.auth.actor.id;
290
332
  // …
291
- ];
333
+ });
334
+
335
+ // account-grain spec → ctx.auth: RequestContext (actor: null)
336
+ rpc_action(account_verify_action_spec, (_input, ctx) => {
337
+ return to_session_account(ctx.auth.account);
338
+ });
292
339
  ```
293
340
 
341
+ The bracketed form `[T] extends ['required']` defeats distributive
342
+ conditionals so a degraded `AuthAxisState` union (when the spec was
343
+ typed without preserving its literal) falls through to the loosest
344
+ tier instead of collapsing to the narrowest. Specs declared with
345
+ `satisfies RequestResponseActionSpec` (canonical) preserve the
346
+ literals — typing a spec directly as `RequestResponseActionSpec`
347
+ widens the axes and silently drops the ergonomic narrowing (the
348
+ binder still compiles; consumers just lose the auto-narrow on
349
+ `ctx.auth`).
350
+
294
351
  zzz uses a codegen-driven `Record<Method, Handler>` map for the same
295
352
  narrowing — ideal when handlers are stateless free functions. fuz_app's
296
- handlers close over factory-captured deps (`log`, `on_audit_event`,
353
+ handlers close over factory-captured deps (`log`, `audit`,
297
354
  `options.app_settings`, `options.max_tokens`), so per-pair typing via
298
355
  `rpc_action()` is the right shape here: the binding happens at
299
356
  construction time and the handler keeps its closure. Applied across
300
- `admin_actions.ts` + `permit_offer_actions.ts` + `account_actions.ts`
301
- each pairs a spec imported from its `*_action_specs.ts` sibling with
302
- a closure-bound handler.
357
+ all four registries `admin_actions.ts`,
358
+ `role_grant_offer_actions.ts`, `self_service_role_actions.ts` (every
359
+ spec there is actor-implying), and `account_actions.ts` (account-grain).
360
+ The conditional auto-selects the right tier per spec; consumers don't
361
+ pick a binder.
362
+
363
+ The earlier two-binder split (`rpc_action` + `rpc_actor_action`) was
364
+ collapsed once the symmetric account-grain narrowing landed. Same
365
+ runtime; the second symbol no longer added information the spec
366
+ literal didn't already carry. Uniform binding keeps a future handler
367
+ that gains `ctx.auth.actor` reads from accidentally landing on a
368
+ looser narrow — the spec literal drives the type either way.
303
369
 
304
370
  ## Transports (`transports.ts`, `transports_http.ts`, `transports_ws.ts`, `transports_ws_backend.ts`)
305
371
 
@@ -380,7 +446,7 @@ Fan-out:
380
446
 
381
447
  - `send(notification)` — broadcasts to every connection (current `send(request)` returns an internal_error "not yet implemented" — backend cannot initiate request-response).
382
448
  - `broadcast_filtered(message, predicate)` — per-connection predicate over `ConnectionIdentity`; skips non-matching. Returns count.
383
- - `send_to_account(account_id, message)` — targeted wrapper over `broadcast_filtered`. Mirrors `close_sockets_for_account` on the send side (every connection for the account). Structurally satisfies the `NotificationSender` interface from `auth/permit_offer_notifications.ts` (see `../auth/CLAUDE.md` §WS notifications).
449
+ - `send_to_account(account_id, message)` — targeted wrapper over `broadcast_filtered`. Mirrors `close_sockets_for_account` on the send side (every connection for the account). Structurally satisfies the `NotificationSender` interface from `auth/role_grant_offer_notifications.ts` (see `../auth/CLAUDE.md` §WS notifications).
384
450
  - `get_connection_count()` — telemetry counter over the connection map.
385
451
 
386
452
  Return values are bookkeeping, not delivery receipts — `0` means no live
@@ -395,7 +461,7 @@ guard in `realtime/sse_auth_guard.ts` but targets the WS transport.
395
461
 
396
462
  `WS_DISCONNECT_EVENT_TYPES` (ReadonlySet): `session_revoke`,
397
463
  `token_revoke`, `session_revoke_all`, `token_revoke_all`, `password_change`.
398
- `permit_revoke` is intentionally **omitted** — the WS transport does not
464
+ `role_grant_revoke` is intentionally **omitted** — the WS transport does not
399
465
  track per-connection role requirements, so role-scoped disconnection would
400
466
  require either closing all sockets (too aggressive) or new per-connection
401
467
  role tracking (out of scope). Consumers that need it compose their own
@@ -440,57 +506,74 @@ Composes the standard upgrade stack:
440
506
 
441
507
  1. `verify_request_source(allowed_origins)`
442
508
  2. `require_auth`
443
- 3. optional `require_role(required_role)`
444
- 4. delegates to `register_action_ws`
509
+ 3. upgrade-time authorization phase — resolves the acting actor and seeds `REQUEST_CONTEXT_KEY` for the inner `register_action_ws`'s upgrade-time identity capture
510
+ 4. optional `require_role([required_role])` (single-element array form)
511
+ 5. delegates to `register_action_ws`
445
512
 
446
- Extends `RegisterActionWsOptions<TCtx>` with `allowed_origins: Array<RegExp>`
513
+ Extends `RegisterActionWsOptions` with `allowed_origins: Array<RegExp>`
447
514
  and optional `required_role: RoleName`. Returns `{transport}`. Note:
448
515
  `required_role` is a **coarse upgrade-time gate** — per-action `auth` in
449
- each spec still applies at dispatch time. (`verify_request_source` and
450
- `require_auth` / `require_role` are from `../auth/`; see
451
- `../auth/CLAUDE.md` §Middleware for their semantics.)
516
+ each spec still applies at dispatch time via `perform_action`.
517
+ (`verify_request_source` and `require_auth` / `require_role` are from
518
+ `../auth/`; see `../auth/CLAUDE.md` §Middleware for their semantics.)
452
519
 
453
520
  ### `register_action_ws` (`register_action_ws.ts`) — lower-level
454
521
 
455
522
  Exposed for tests (`create_ws_test_harness`) that need to drive the
456
523
  dispatcher without the origin/auth front-stack.
457
524
 
458
- Actions are passed as `ReadonlyArray<Action<TCtx>>` — the composable
525
+ Actions are passed as `ReadonlyArray<Action>` — the composable
459
526
  `{spec, handler?}` tuple shared with `create_rpc_client`. The dispatcher
460
- fans the array into a `spec_by_method` map (drives auth + validation) and
461
- a `handlers` record (drives invocation). Spec without handler is fine for
462
- client-only specs (incoming notification specs); spec without handler that
463
- the dispatcher is asked to invoke returns `method_not_found`.
464
-
465
- `extend_context(base, c)` builds the per-request context on every message.
466
- `BaseHandlerContext` (the non-extended minimum, exported from `action_types.ts`):
467
-
468
- ```ts
469
- interface BaseHandlerContext {
470
- request_id: JsonrpcRequestId;
471
- connection_id: Uuid; // stable across messages on this socket
472
- notify: (method, params) => void; // socket-scoped, not broadcast
473
- signal: AbortSignal; // AbortSignal.any([socket_close, per_request_cancel])
474
- }
475
- ```
476
-
477
- `WsActionHandler<TCtx>` is the WS-side handler type (single-context-slot,
478
- returns `unknown`disambiguated from `action_rpc.ts`'s `ActionHandler`).
479
-
480
- Per-message wire behavior:
527
+ fans the array into a `spec_by_method` map (drives envelope-shape
528
+ validation) and an `action_map: Map<string, RpcAction>` (drives
529
+ invocation, only request_response specs with a handler). Specs without
530
+ a handler (client-only / dispatcher-handled like `cancel`) miss
531
+ `action_map` and surface as `method_not_found` if the wire targets them.
532
+
533
+ Required deps: `db: Db` (pool-level, used by `perform_action` for both the
534
+ per-message authorization phase and the transactional dispatch wrap when
535
+ `spec.side_effects: true`). Audit fan-out and other rollback-resilient
536
+ fire-and-forget writes run through `AppDeps.audit` from each action
537
+ factory's closure — the dispatcher never holds a separate pool reference.
538
+
539
+ Per-message dispatch delegates to `perform_action` (`actions/perform_action.ts`)
540
+ the shared core that HTTP RPC also calls. `register_action_ws` only owns
541
+ WS-specific concerns:
542
+
543
+ - **Wire envelope parsing** — JSON.parse → batch rejection → notification interception (cancel, silent drop) → per-message dispatch.
544
+ - **Cancel-notification interception** — `{request_id AbortController}` map; aborts the matching pending controller before the cancel bubbles past the dispatcher.
545
+ - **Socket-scoped notify** `(method, params) => ws.send(notification)`, threaded into `perform_action` as `notify`.
546
+ - **Composed abort signal** — `AbortSignal.any([socket_close, per_request_cancel])`, threaded into `perform_action` as `signal`.
547
+ - **Connection lifecycle** — `transport.add_connection` / `remove_connection`, `on_socket_open` / `_close` hooks, server heartbeat.
548
+
549
+ Per-message authorization phase: `perform_action` calls
550
+ `apply_authorization_phase` per-message (HTTP and WS uniformly). Role grant
551
+ changes during a connection lifetime are picked up on the next message —
552
+ no in-place refresh, no socket-close on `role_grant_revoke`. Authentication
553
+ invalidation (`session_revoke`, `password_change`, `token_revoke_all`)
554
+ still closes the socket via `create_ws_auth_guard`.
555
+
556
+ Per-message wire behavior (every step delegated to `perform_action`
557
+ except the WS-specific framing):
481
558
 
482
559
  - **Batch JSON-RPC rejected** — arrays get `invalid_request`.
483
560
  - **Notifications** — method + no id. Intercepted: `cancel` aborts the matching per-request controller; other notifications are silenced per JSON-RPC spec (no consumer notification handlers yet).
484
- - **Per-action auth** — `public` / `authenticated` pass through (upgrade already verified); `keeper` requires `credential_type === 'daemon_token'` AND `has_role(ROLE_KEEPER)`; `{role}` requires `has_role(role)`. Same shape as `action_rpc.ts`.
485
- - **Input validation** — `spec.input.safeParse(params)`; failure `invalid_params` with `{issues}`.
486
- - **DEV-only output validation** — `spec.output.safeParse(output)` under `DEV`; logs error on mismatch, never throws, sends result unchanged. Uniform with RPC + REST surfaces.
487
- - **Error handling** — `ThrownJsonrpcError` preserves code + data; generic throws are wrapped via `create_jsonrpc_error_response_from_thrown`. `ThrownJsonrpcError` is logged at `debug` (expected protocol outcome); generic errors at `error`.
561
+ - **Per-action auth + validation + dispatch** — uniform with HTTP RPC via `perform_action`: pre-validation auth (401) input validation (400) authorization phase post-authorization auth (403) rate limit (429) handler under transaction (when `side_effects: true`) → DEV output validation.
562
+ - **Error handling** — handler throws normalize via `perform_action`'s thrown-error path. `ThrownJsonrpcError` preserves code + data; generic throws become `internal_error`. The WS shim sends the resulting envelope over the socket.
488
563
 
489
564
  Two abort signals, composed via `AbortSignal.any`:
490
565
 
491
566
  - `socket_abort_controller` — per-socket, fires on close. Drives every handler's `ctx.signal` on that socket.
492
567
  - `pending_controllers: Map<JsonrpcRequestId, AbortController>` — per-request. Registered before dispatch, cleared in `finally` so late cancels for a completed id (or a reused id) can't null-abort the wrong handler. Unknown cancels no-op.
493
568
 
569
+ Per-message side-effect queues: `pending_effects: Array<Promise<void>>`
570
+ (eager) drains via `flush_pending_effects`; `post_commit_effects: Array<() => void | Promise<void>>`
571
+ (deferred — pushed by handlers via `emit_after_commit`) drains via
572
+ `flush_post_commit_effects`. Both flush in the same `try/finally` that
573
+ releases the request controller, so fire-and-forget audit / notification
574
+ effects pushed by the handler complete (or reject visibly) before the
575
+ next message dispatches. See `../http/CLAUDE.md` §Pending Effects.
576
+
494
577
  Lifecycle hooks on `RegisterActionWsOptions`:
495
578
 
496
579
  - `on_socket_open({ws, connection_id, identity, notify, signal})` — fires after `transport.add_connection` but before the first message. Awaited. Throws log + close with `1011 'socket bootstrap failed'` + send an `internal_error` frame.
@@ -850,7 +933,7 @@ silently no-op because `lookup_action_handler` always returns
850
933
 
851
934
  `transport_for_method` and `on_action_event` are pure pass-throughs to
852
935
  `create_rpc_client` — exposed so consumers needing per-method routing
853
- (tx-style WS-for-actions / HTTP-for-rest split) or per-dispatch event
936
+ (zap-style WS-for-actions / HTTP-for-rest split) or per-dispatch event
854
937
  wiring (zzz-style reactive Cells observing `ActionEvent` lifecycle)
855
938
  don't have to drop down to manual `create_rpc_client` construction
856
939
  (which forfeits the bundled `api` / `api_result` pair).
@@ -888,28 +971,56 @@ natural fit when consumers already generate per-method type maps).
888
971
  ## Shared type surface (`action_types.ts`)
889
972
 
890
973
  Sits above `action_spec.ts` (pure Zod) and below the dispatchers
891
- (`register_action_ws.ts`, `action_rpc.ts`). Extracted so composable
892
- primitives (e.g. `heartbeat_action`) can name the types without pulling
893
- in server-only modules.
894
-
895
- - `BaseHandlerContext` `{request_id, connection_id, notify, signal}` (see §WebSocket dispatch for field semantics).
896
- - `WsActionHandler<TCtx>` — `(input, ctx) => unknown`. Disambiguated from HTTP's `ActionHandler`.
897
- - `Action<TCtx>` — `{spec: ActionSpecUnion, handler?: WsActionHandler<TCtx>}`. The composable unit passed to both sides' `actions` arrays. Left open for future fields (rate_limit, ACL, middleware hooks) so additions attach to the action itself instead of scattering parallel arrays.
898
-
899
- Re-exported from `register_action_ws.ts` as `Action`, `BaseHandlerContext`,
900
- `WsActionHandler` for ergonomics.
974
+ (`register_action_ws.ts`, `action_rpc.ts`, `perform_action.ts`).
975
+ Extracted so composable primitives (e.g. `heartbeat_action`) can name the
976
+ types without pulling in server-only modules.
977
+
978
+ This is the polymorphic `Action` shape only. The unified `ActionContext`
979
+ from `action_rpc.ts` is the single handler context across every
980
+ transport; `ActionHandler` is the single handler signature.
981
+
982
+ - `Action<TSpec>` `{spec: TSpec, handler?: ActionHandler}`. The composable unit passed to both sides' `actions` arrays. Polymorphic on `kind`: `request_response` specs require a handler for dispatch; `remote_notification` specs may declare a stub for symmetry but are dispatcher-handled (e.g. `cancel`); `local_call` specs never reach a network dispatcher. The WS dispatcher only invokes handlers on `request_response` actions; everything else is registry-only.
983
+
984
+ `RpcAction = Action<RequestResponseActionSpec> & {handler: ActionHandler}`
985
+ is the narrowing the HTTP RPC dispatcher accepts (`create_rpc_endpoint`)
986
+ and the `rpc_action` binder produces (the actor-axis narrowing now lives
987
+ in `HandlerForSpec<TSpec>` — there's no longer a separate
988
+ `rpc_actor_action`).
989
+
990
+ ## Shared dispatch core (`perform_action.ts`)
991
+
992
+ The transport-agnostic post-parse pipeline shared by HTTP RPC and
993
+ WebSocket. Each transport assembles a `PerformActionInput` from its wire
994
+ envelope + connection identity, calls `perform_action(input, deps)`,
995
+ and binds the discriminated `PerformActionResult` to its wire shape.
996
+
997
+ Pipeline (401 → 400 → 403 → handler):
998
+
999
+ 1. Pre-validation auth (401) — short-circuits unauthenticated callers on `'required'` axes before input validation.
1000
+ 2. Validate params (400) — `spec.input.safeParse` with `z.void()` / `?? {}` rules.
1001
+ 3. Authorization phase — `apply_authorization_phase` against `account_id` + `validated_input.acting`. Test escape hatch lives in the caller — pass `preset.request_context` to skip the live phase.
1002
+ 4. Post-authorization auth (403) — credential-type gate first, role gate second.
1003
+ 5. Rate limit (429) — per-action IP / account throttling, throttle-requests semantics (every invocation records).
1004
+ 6. Dispatch + DEV output validation + error normalization — `spec.side_effects` picks transaction (`deps.db.transaction`) vs pool. Handler throws roll back the transaction; `ThrownJsonrpcError` preserves code + data, generic throws become `internal_error`.
1005
+
1006
+ `PerformActionInput` carries `account_id`, `credential_type`, `client_ip`,
1007
+ `signal`, `notify`, optional `connection_id`, optional `preset`.
1008
+ `PerformActionDeps` carries `db` (pool-level), `pending_effects`, `log`,
1009
+ the two rate limiters. Audit writes are out-of-band: factories close over
1010
+ `AppDeps.audit` independently. `PerformActionResult` is `{kind: 'ok',
1011
+ result} | {kind: 'error', error, status}`; `perform_action_result_to_envelope(id, result)`
1012
+ builds the JSON-RPC wire shape both transports send.
901
1013
 
902
1014
  ## DEV-only output validation — uniform across surfaces
903
1015
 
904
- The critical invariant: all three action-handler surfaces apply DEV-only
905
- output validation and produce the **same failure mode** — log an error,
1016
+ The critical invariant: every action-handler surface applies DEV-only
1017
+ output validation and produces the **same failure mode** — log an error,
906
1018
  return the response unchanged, do not throw, do not mutate status.
907
1019
 
908
- | Surface | Code location | Hot path under production |
909
- | ----------------- | -------------------------------------------------------------------------------------------------------------------------- | ------------------------- |
910
- | REST bridge | `http/route_spec.ts` — `wrap_output_validation` (applied via `apply_route_specs`; inherited by `create_action_route_spec`) | short-circuit (no parse) |
911
- | JSON-RPC endpoint | `action_rpc.ts` — `if (DEV) action.spec.output.safeParse(output)` | short-circuit (no parse) |
912
- | WebSocket | `register_action_ws.ts` — `if (DEV) spec.output.safeParse(output)` | short-circuit (no parse) |
1020
+ | Surface | Code location | Hot path under production |
1021
+ | ----------------------------- | -------------------------------------------------------------------------------------------------------------------------- | ------------------------- |
1022
+ | REST bridge | `http/route_spec.ts` — `wrap_output_validation` (applied via `apply_route_specs`; inherited by `create_action_route_spec`) | short-circuit (no parse) |
1023
+ | HTTP RPC + WebSocket dispatch | `actions/perform_action.ts` — `if (DEV) spec.output.safeParse(output)` inside the shared dispatch core | short-circuit (no parse) |
913
1024
 
914
1025
  Caller-facing `input` schemas are validated **always** (DEV + production) —
915
1026
  they're the contract with external callers. Server-authored `output`
@@ -8,8 +8,9 @@
8
8
  * @module
9
9
  */
10
10
  import type { z } from 'zod';
11
- import type { ActionSpec, ActionAuth as ActionSpecAuth, ActionSideEffects } from './action_spec.js';
12
- import type { RouteSpec, RouteAuth, RouteMethod, RouteHandler } from '../http/route_spec.js';
11
+ import type { ActionSpec, ActionSideEffects } from './action_spec.js';
12
+ import type { RouteSpec, RouteMethod, RouteHandler } from '../http/route_spec.js';
13
+ import type { RouteAuth } from '../http/auth_shape.js';
13
14
  import type { EventSpec } from '../realtime/sse.js';
14
15
  import type { RouteErrorSchemas } from '../http/error_schemas.js';
15
16
  /** Options for deriving a `RouteSpec` from an `ActionSpec`. */
@@ -22,7 +23,11 @@ export interface ActionRouteOptions {
22
23
  query?: z.ZodObject;
23
24
  /** Override the default HTTP method (default: `side_effects` → POST, else GET). */
24
25
  http_method?: RouteMethod;
25
- /** Override the default auth mapping (default: `'public'` → none, `'authenticated'` → authenticated, `'keeper'` → keeper, `{role}` → role). */
26
+ /**
27
+ * Override the route's auth shape — defaults to the action spec's `auth`
28
+ * (the canonical four-axis shape from `http/auth_shape.ts` is shared
29
+ * verbatim between action specs and route specs, so no mapping is needed).
30
+ */
26
31
  auth?: RouteAuth;
27
32
  /** Handler-specific error schemas (HTTP status code → Zod schema). Transport-specific — not on ActionSpec. */
28
33
  errors?: RouteErrorSchemas;
@@ -31,8 +36,6 @@ export interface ActionRouteOptions {
31
36
  export interface ActionEventOptions {
32
37
  channel?: string;
33
38
  }
34
- /** Map an `ActionAuth` value to a `RouteAuth`. */
35
- export declare const map_action_auth: (auth: ActionSpecAuth) => RouteAuth;
36
39
  /** Derive the default HTTP method from side effects. */
37
40
  export declare const derive_http_method: (side_effects: ActionSideEffects) => RouteMethod;
38
41
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"action_bridge.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/action_bridge.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAE3B,OAAO,KAAK,EAAC,UAAU,EAAE,UAAU,IAAI,cAAc,EAAE,iBAAiB,EAAC,MAAM,kBAAkB,CAAC;AAClG,OAAO,KAAK,EAAC,SAAS,EAAE,SAAS,EAAE,WAAW,EAAE,YAAY,EAAC,MAAM,uBAAuB,CAAC;AAC3F,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,oBAAoB,CAAC;AAClD,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,0BAA0B,CAAC;AAEhE,+DAA+D;AAC/D,MAAM,WAAW,kBAAkB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,YAAY,CAAC;IACtB,uGAAuG;IACvG,MAAM,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC;IACrB,6EAA6E;IAC7E,KAAK,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC;IACpB,mFAAmF;IACnF,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,+IAA+I;IAC/I,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,8GAA8G;IAC9G,MAAM,CAAC,EAAE,iBAAiB,CAAC;CAC3B;AAED,gEAAgE;AAChE,MAAM,WAAW,kBAAkB;IAClC,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,kDAAkD;AAClD,eAAO,MAAM,eAAe,GAAI,MAAM,cAAc,KAAG,SAKtD,CAAC;AAEF,wDAAwD;AACxD,eAAO,MAAM,kBAAkB,GAAI,cAAc,iBAAiB,KAAG,WAEpE,CAAC;AAEF;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,wBAAwB,GACpC,MAAM,UAAU,EAChB,SAAS,kBAAkB,KACzB,SAmBF,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,wBAAwB,GACpC,MAAM,UAAU,EAChB,UAAU,kBAAkB,KAC1B,SAYF,CAAC"}
1
+ {"version":3,"file":"action_bridge.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/action_bridge.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAE3B,OAAO,KAAK,EAAC,UAAU,EAAE,iBAAiB,EAAC,MAAM,kBAAkB,CAAC;AACpE,OAAO,KAAK,EAAC,SAAS,EAAE,WAAW,EAAE,YAAY,EAAC,MAAM,uBAAuB,CAAC;AAChF,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,uBAAuB,CAAC;AACrD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,oBAAoB,CAAC;AAClD,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,0BAA0B,CAAC;AAEhE,+DAA+D;AAC/D,MAAM,WAAW,kBAAkB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,YAAY,CAAC;IACtB,uGAAuG;IACvG,MAAM,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC;IACrB,6EAA6E;IAC7E,KAAK,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC;IACpB,mFAAmF;IACnF,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B;;;;OAIG;IACH,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,8GAA8G;IAC9G,MAAM,CAAC,EAAE,iBAAiB,CAAC;CAC3B;AAED,gEAAgE;AAChE,MAAM,WAAW,kBAAkB;IAClC,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wDAAwD;AACxD,eAAO,MAAM,kBAAkB,GAAI,cAAc,iBAAiB,KAAG,WAEpE,CAAC;AAEF;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,wBAAwB,GACpC,MAAM,UAAU,EAChB,SAAS,kBAAkB,KACzB,SAmBF,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,wBAAwB,GACpC,MAAM,UAAU,EAChB,UAAU,kBAAkB,KAC1B,SAYF,CAAC"}
@@ -7,16 +7,6 @@
7
7
  *
8
8
  * @module
9
9
  */
10
- /** Map an `ActionAuth` value to a `RouteAuth`. */
11
- export const map_action_auth = (auth) => {
12
- if (auth === 'public')
13
- return { type: 'none' };
14
- if (auth === 'authenticated')
15
- return { type: 'authenticated' };
16
- if (auth === 'keeper')
17
- return { type: 'keeper' };
18
- return { type: 'role', role: auth.role };
19
- };
20
10
  /** Derive the default HTTP method from side effects. */
21
11
  export const derive_http_method = (side_effects) => {
22
12
  return side_effects ? 'POST' : 'GET';
@@ -45,7 +35,7 @@ export const create_action_route_spec = (spec, options) => {
45
35
  return {
46
36
  method: options.http_method ?? derive_http_method(spec.side_effects),
47
37
  path: options.path,
48
- auth: options.auth ?? map_action_auth(spec.auth),
38
+ auth: options.auth ?? spec.auth,
49
39
  handler: options.handler,
50
40
  description: spec.description,
51
41
  ...(options.params ? { params: options.params } : {}),
@@ -154,6 +154,19 @@ export declare const to_action_spec_output_identifier: (method: string) => strin
154
154
  * follows so wrappers no longer pre-register imports a per-spec emit might
155
155
  * not actually use.
156
156
  *
157
+ * **Optional-input detection.** The emitted parameter is `input?:` (caller
158
+ * may omit the argument) when either (a) the schema accepts `undefined` —
159
+ * `z.optional(z.strictObject(...))` and similar wrappers — or (b) the
160
+ * schema accepts the empty object `{}` — `z.strictObject({acting:
161
+ ActingActor})` and other all-optional-fields strict objects. The second
162
+ * probe mirrors the dispatcher's HTTP convention (`raw_params ?? {}` for
163
+ * non-`z.void()` schemas in `actions/action_rpc.ts` / `http/route_spec.ts`):
164
+ * if a request with no params reaches the handler, this is the value the
165
+ * schema is asked to validate. A schema with required fields fails both
166
+ * probes and stays `input:` (required at the typed surface). Refinements
167
+ * and transforms run as part of `safeParse`, so their accept/reject
168
+ * decisions feed into the optional/required choice naturally.
169
+ *
157
170
  * @param options.sync_returns_value - When true (default), sync `local_call`
158
171
  * methods return the output value directly; when false they're wrapped in
159
172
  * `Result<{value, error}>` like async methods. Set to `false` if your
@@ -170,6 +183,25 @@ export declare const generate_actions_api_method_signature: (spec: ActionSpecUni
170
183
  export type ActionMethodEnumKind = 'all' | 'request_response' | 'remote_notification' | 'local_call' | 'frontend' | 'backend' | 'frontend_handled' | 'backend_handled' | 'broadcast';
171
184
  /** Default emit set — every enum kind. */
172
185
  export declare const ACTION_METHOD_ENUM_KINDS_ALL: ReadonlySet<ActionMethodEnumKind>;
186
+ /**
187
+ * Resolve a per-spec identifier qualifier with the standard default-vs-callback
188
+ * dance. When `qualify_spec` is set, returns the caller's callback verbatim
189
+ * and registers no imports — the caller owns its namespace setup (the
190
+ * multi-source case where specs come from several modules). Otherwise,
191
+ * registers `* as specs from specs_module` (defaulting to
192
+ * `'./action_specs.js'`) on `imports` and returns
193
+ * `(spec) => 'specs.' + to_action_spec_identifier(spec.method)`.
194
+ *
195
+ * Used internally by every multi-source-aware helper in this module
196
+ * (`generate_action_specs_record`, `generate_action_inputs_outputs`,
197
+ * `generate_backend_actions_api`); exported so consumers writing their own
198
+ * codegen helpers can reuse the same defaulting + import-registration
199
+ * behavior instead of reimplementing it.
200
+ */
201
+ export declare const resolve_spec_qualifier: (imports: ImportBuilder, options?: {
202
+ specs_module?: string;
203
+ qualify_spec?: (spec: ActionSpecUnion) => string;
204
+ }) => ((spec: ActionSpecUnion) => string);
173
205
  /**
174
206
  * Emit one or more `z.enum([...])` declarations for action method names —
175
207
  * `ActionMethod`, `RequestResponseActionMethod`, `RemoteNotificationActionMethod`,