@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
@@ -15,14 +15,15 @@ import { z } from 'zod';
15
15
  import { DEV } from 'esm-env';
16
16
  import {} from '../http/route_spec.js';
17
17
  import { get_client_ip } from '../http/proxy.js';
18
- import { apply_authorization_phase, get_request_context, has_role, input_schema_declares_acting, is_actor_implying_auth, } from '../auth/request_context.js';
19
- import { ACCOUNT_ID_KEY, CREDENTIAL_TYPE_KEY } from '../hono_context.js';
20
- import { is_null_schema, is_void_schema } from '../http/schema_helpers.js';
18
+ import { get_request_context, } from '../auth/request_context.js';
19
+ import { ACCOUNT_ID_KEY, CREDENTIAL_TYPE_KEY, TEST_CONTEXT_PRESET_KEY } from '../hono_context.js';
20
+ import { compile_action_registry } from './compile_action_registry.js';
21
21
  import { JSONRPC_VERSION, JsonrpcRequest, } from '../http/jsonrpc.js';
22
- import { jsonrpc_error_messages, jsonrpc_error_code_to_http_status, http_status_to_jsonrpc_error_code, JSONRPC_ERROR_CODES, } from '../http/jsonrpc_errors.js';
23
- import { ERROR_INSUFFICIENT_PERMISSIONS, ERROR_KEEPER_REQUIRES_DAEMON_TOKEN, } from '../http/error_schemas.js';
22
+ import { jsonrpc_error_messages, jsonrpc_error_code_to_http_status, JSONRPC_ERROR_CODES, } from '../http/jsonrpc_errors.js';
23
+ import { perform_action, perform_action_result_to_envelope } from './perform_action.js';
24
24
  /**
25
- * Pair a spec with a handler while preserving per-method input/output types.
25
+ * Pair a spec with a handler while preserving per-method input/output types
26
+ * and selecting the narrowest `ctx.auth` shape the spec literal admits.
26
27
  *
27
28
  * Constructing `{spec, handler}` literals widens `handler` to
28
29
  * `ActionHandler<any, any>`, so spec/handler drift (renamed Zod schema,
@@ -31,106 +32,57 @@ import { ERROR_INSUFFICIENT_PERMISSIONS, ERROR_KEEPER_REQUIRES_DAEMON_TOKEN, } f
31
32
  * `(input: z.infer<spec.input>, ctx) => z.infer<spec.output>` via the
32
33
  * generic spec parameter — drift surfaces at the call site.
33
34
  *
35
+ * The `ctx.auth` narrowing follows the spec's `auth.account` /
36
+ * `auth.actor` literals (see `HandlerForSpec`): an actor-implying spec
37
+ * gets `ctx.auth: RequestActorContext`; an account-grain spec gets
38
+ * `ctx.auth: RequestContext`; everything else stays `ctx.auth:
39
+ * RequestContext | null`. Handlers can rely on the dispatcher's
40
+ * runtime guarantee without a manual narrowing call.
41
+ *
34
42
  * Fits fuz_app's factory-closure pattern (handlers close over
35
43
  * `grantable_roles`, `app_settings` ref, `notification_sender`, etc.).
36
44
  * zzz uses a different shape — a codegen-keyed `Record<Method, Handler>`
37
45
  * map typed via generated `ActionInputs`/`ActionOutputs` — which works when
38
46
  * handlers are pure (no closure state) and specs are codegen-enumerated.
39
- * fuz_app's admin + permit-offer actions have neither, so per-pair typing
47
+ * fuz_app's admin + role-grant-offer actions have neither, so per-pair typing
40
48
  * at the registration site is the right fit.
41
- */
42
- export const rpc_action = (spec, handler) => ({
43
- spec,
44
- handler: handler,
45
- });
46
- /**
47
- * Variant of `rpc_action` for handlers whose spec always resolves an
48
- * acting actor — actions with `auth: 'keeper' | {role}` or inputs that
49
- * declare `acting?: ActingActor`. The dispatcher's authorization phase
50
- * runs before the handler, populates `ctx.auth` with a non-null
51
- * `RequestActorContext`, and `rpc_actor_action` reflects that
52
- * guarantee in the handler signature so the handler body skips the
53
- * `require_request_actor(ctx.auth)` narrowing call (and the bug class
54
- * where forgetting that call fails open against a `null` actor).
55
49
  *
56
- * The runtime binding is identical to `rpc_action` — both register the
57
- * same `RpcAction` shape on the action map. Only the compile-time
58
- * handler signature differs.
50
+ * Spec-literal preservation is load-bearing: declare specs with
51
+ * `satisfies RequestResponseActionSpec` (canonical) so `auth.actor`
52
+ * keeps its `'required'` / `'none'` literal type. A spec typed
53
+ * directly as `RequestResponseActionSpec` widens the axes to
54
+ * `AuthAxisState` and the handler defaults to the loosest tier — sound,
55
+ * but loses the ergonomic narrowing.
59
56
  *
60
57
  * @example
61
58
  * ```ts
62
- * rpc_actor_action(permit_revoke_action_spec, async (input, ctx) => {
63
- * // ctx.auth is RequestActorContext no require_request_actor() needed.
64
- * const revoker_id = ctx.auth.actor.id;
65
- * // ...
59
+ * // actor-implying spec ctx.auth: RequestActorContext
60
+ * rpc_action(role_grant_revoke_action_spec, async (input, ctx) => {
61
+ * const revoker_id = ctx.auth.actor.id; // no narrowing needed
62
+ * });
63
+ *
64
+ * // account-grain spec → ctx.auth: RequestContext (actor: null)
65
+ * rpc_action(account_verify_action_spec, (_input, ctx) => {
66
+ * return to_session_account(ctx.auth.account); // no narrowing needed
66
67
  * });
67
68
  * ```
68
69
  */
69
- export const rpc_actor_action = (spec, handler) => ({
70
+ export const rpc_action = (spec, handler) => ({
70
71
  spec,
71
72
  handler: handler,
72
73
  });
73
- const jsonrpc_error_response = (id, error) => ({
74
+ /**
75
+ * Build a JSON-RPC error envelope for transport-shape errors (envelope
76
+ * parse failures, method-not-found, GET-on-mutation rejections). The
77
+ * dispatch core in `perform_action` returns a `PerformActionResult`
78
+ * directly; this helper covers only the wire-shape errors the HTTP shim
79
+ * emits before / after the core runs.
80
+ */
81
+ const jsonrpc_error_envelope = (id, error) => ({
74
82
  jsonrpc: JSONRPC_VERSION,
75
83
  id,
76
84
  error,
77
85
  });
78
- /**
79
- * Pre-validation auth gate — fires before input validation so missing
80
- * credentials short-circuit with `unauthenticated` instead of leaking
81
- * a `invalid_params` error for methods with required input.
82
- *
83
- * Reads `c.var.auth_account_id` (set by the auth middleware). Returns
84
- * `unauthenticated` when `auth !== 'public'` and no account is on the
85
- * request. Role / keeper checks are deferred until after the
86
- * authorization phase populates the request context — see
87
- * `check_action_auth_post_authorization`.
88
- *
89
- * @returns a JSON-RPC error object if no account is on the request, or `null`
90
- */
91
- const check_action_auth_pre_validation = (auth, account_id) => {
92
- if (auth === 'public')
93
- return null;
94
- if (account_id == null)
95
- return jsonrpc_error_messages.unauthenticated();
96
- return null;
97
- };
98
- /**
99
- * Post-authorization auth gate — fires after the dispatcher's authorization
100
- * phase has populated `REQUEST_CONTEXT_KEY` with the resolved actor +
101
- * permits. Enforces `role` and `keeper` requirements; `'public'` and
102
- * `'authenticated'` already cleared the pre-validation gate.
103
- *
104
- * @returns a JSON-RPC error object if permit / credential check fails, or `null`
105
- */
106
- const check_action_auth_post_authorization = (auth, request_context, credential_type) => {
107
- if (auth === 'public' || auth === 'authenticated')
108
- return null;
109
- if (auth === 'keeper') {
110
- // keeper requires daemon_token credential type AND the keeper role.
111
- // API tokens and session cookies cannot access keeper actions even
112
- // if the account has the keeper permit. Attach the credential type
113
- // under `data` so clients can distinguish "wrong credential shape"
114
- // from "missing keeper role" — mirrors REST 403 semantics.
115
- if (credential_type !== 'daemon_token' || !has_role(request_context, 'keeper')) {
116
- return jsonrpc_error_messages.forbidden('forbidden', {
117
- reason: ERROR_KEEPER_REQUIRES_DAEMON_TOKEN,
118
- credential_type,
119
- });
120
- }
121
- return null;
122
- }
123
- // role check — attach `required_role` under `data.required_role` so
124
- // clients can render targeted copy (matches the former REST `PermissionError`
125
- // shape that exposed `required_role` as a top-level field).
126
- if (!has_role(request_context, auth.role)) {
127
- return jsonrpc_error_messages.forbidden(`requires role: ${auth.role}`, {
128
- reason: ERROR_INSUFFICIENT_PERMISSIONS,
129
- required_role: auth.role,
130
- });
131
- }
132
- return null;
133
- };
134
86
  /**
135
87
  * Single JSON-RPC 2.0 endpoint — the canonical RPC transport binding.
136
88
  *
@@ -143,9 +95,9 @@ const check_action_auth_post_authorization = (auth, request_context, credential_
143
95
  * 3. **Pre-validation auth** — short-circuit `unauthenticated` when no
144
96
  * account is on the request, before input validation runs.
145
97
  * 4. **Authorization phase** — resolve the acting actor (when the action's
146
- * auth requires permits or its input declares `acting?: ActingActor`)
98
+ * auth requires role_grants or its input declares `acting?: ActingActor`)
147
99
  * and build the request context. Runs before input validation so
148
- * permit-grain auth checks return 403 before 400 invalid_params;
100
+ * role-grant-grain auth checks return 403 before 400 invalid_params;
149
101
  * `acting` is read from raw params via a string typeguard.
150
102
  * 5. **Post-authorization auth** — enforce role / keeper requirements
151
103
  * against the request context.
@@ -170,215 +122,68 @@ const check_action_auth_post_authorization = (auth, request_context, credential_
170
122
  */
171
123
  export const create_rpc_endpoint = (options) => {
172
124
  const { path: endpoint_path, actions, log, action_ip_rate_limiter = null, action_account_rate_limiter = null, } = options;
173
- const action_map = new Map();
174
- for (const action of actions) {
175
- if (action_map.has(action.spec.method)) {
176
- throw new Error(`Duplicate RPC action method: ${action.spec.method}`);
177
- }
178
- if (is_null_schema(action.spec.input)) {
179
- throw new Error(`RPC action "${action.spec.method}" uses z.null() for input — JSON-RPC 2.0 §4.2 forbids "params": null on the wire (must be omitted or be a Structured value). Use z.void() for parameterless methods.`);
180
- }
181
- // Reject account-keyed rate limiting on public actions — there's no
182
- // actor post-auth, so the account bucket has no key to consume.
183
- if ((action.spec.rate_limit === 'account' || action.spec.rate_limit === 'both') &&
184
- action.spec.auth === 'public') {
185
- throw new Error(`RPC 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.`);
186
- }
187
- action_map.set(action.spec.method, action);
188
- }
125
+ const { action_map } = compile_action_registry(actions, 'RPC action');
189
126
  /**
190
- * Core dispatchershared by GET and POST handlers.
127
+ * HTTP-shape dispatch shim the GET/POST entry points share this:
128
+ *
129
+ * 1. Resolve the action by method name (HTTP-shape `method_not_found` envelope).
130
+ * 2. Reject GET requests for `side_effects: true` actions (HTTP-only constraint).
131
+ * 3. Hand off to `perform_action` for the post-parse pipeline.
132
+ * 4. Bind the result to `c.json` — `'ok'` returns the result envelope,
133
+ * `'error'` returns the error envelope at the `result.status` HTTP code.
191
134
  *
192
135
  * @param restrict_to_reads - `true` for GET (rejects `side_effects: true` actions)
193
136
  */
194
137
  const dispatch = async (c, route, method_name, raw_params, id, restrict_to_reads) => {
195
- // step 2: lookup method
196
138
  const action = action_map.get(method_name);
197
139
  if (!action) {
198
- const error = jsonrpc_error_response(id, jsonrpc_error_messages.method_not_found(method_name));
199
- return c.json(error, jsonrpc_error_code_to_http_status(JSONRPC_ERROR_CODES.method_not_found));
140
+ return c.json(jsonrpc_error_envelope(id, jsonrpc_error_messages.method_not_found(method_name)), jsonrpc_error_code_to_http_status(JSONRPC_ERROR_CODES.method_not_found));
200
141
  }
201
- // GET restriction: only side_effects:false actions
202
142
  if (restrict_to_reads && action.spec.side_effects) {
203
- const error = jsonrpc_error_response(id, jsonrpc_error_messages.invalid_request({
143
+ return c.json(jsonrpc_error_envelope(id, jsonrpc_error_messages.invalid_request({
204
144
  reason: `method '${method_name}' has side effects and must use POST`,
205
- }));
206
- return c.json(error, jsonrpc_error_code_to_http_status(JSONRPC_ERROR_CODES.invalid_request));
207
- }
208
- // step 3: pre-validation auth — short-circuit with `unauthenticated`
209
- // when no account is on the request before input validation runs,
210
- // so callers without credentials don't see `invalid_params` for
211
- // methods with required input.
212
- const action_auth = action.spec.auth;
213
- const account_id = c.get(ACCOUNT_ID_KEY) ?? null;
214
- const pre_validation_auth_error = check_action_auth_pre_validation(action_auth, account_id);
215
- if (pre_validation_auth_error) {
216
- const error = jsonrpc_error_response(id, pre_validation_auth_error);
217
- return c.json(error, jsonrpc_error_code_to_http_status(pre_validation_auth_error.code));
145
+ })), jsonrpc_error_code_to_http_status(JSONRPC_ERROR_CODES.invalid_request));
218
146
  }
219
- // step 4: authorization phase resolves the acting actor and
220
- // builds the request context. Runs before input validation so
221
- // permit-grain auth checks (`role` / `keeper`) surface 403
222
- // before 400 invalid_params. `acting` is read from raw params
223
- // (string typeguard) so multi-actor callers can still pick a
224
- // persona without paying for full validation up front; an
225
- // invalid `acting` shape will be rejected by step 5's input
226
- // validation if it survives the authorization probe.
227
- //
228
- // Resolution failures come back as `{status, body}` so this
229
- // dispatcher can fold them into a JSON-RPC error envelope —
230
- // REST emits the same `body` directly. The reason string lands
231
- // on `error.message` and `error.data.reason`; remaining
232
- // diagnostic fields (e.g. `available[]` for `actor_required`)
233
- // flatten under `error.data` so wire callers see structured
234
- // data instead of a status-coded synthetic envelope.
235
- if (action_auth !== 'public') {
236
- const declares_acting = input_schema_declares_acting(action.spec.input);
237
- const needs_actor = is_actor_implying_auth(action_auth) || declares_acting;
238
- const raw_acting = declares_acting && typeof raw_params === 'object' && raw_params !== null
239
- ? raw_params.acting
240
- : undefined;
241
- const acting_value = typeof raw_acting === 'string' ? raw_acting : undefined;
242
- const failure = await apply_authorization_phase({ db: route.db }, c, needs_actor, acting_value);
243
- if (failure) {
244
- // `error.code` comes from `http_status_to_jsonrpc_error_code(failure.status)` so the
245
- // wire shape stays uniform with every other JSON-RPC failure path. The 400 mapping
246
- // lands on `invalid_params` even though `actor_required` / `actor_not_on_account`
247
- // are not strictly "params malformed" failures — the alternative would be inventing
248
- // a JSON-RPC code outside the http-status mapping just for these two reasons. The
249
- // slight semantic mismatch is acceptable because consumers key on
250
- // `error.data.reason`, never on `error.code` (the in-tree consumers — zzz, tx,
251
- // visiones, mageguild — never match on the actor reason strings via `error.code`).
252
- // The 500 mapping (`internal_error`) for `no_actors_on_account` / `account_vanished`
253
- // is on-the-nose.
254
- const { error: reason, ...rest } = failure.body;
255
- const code = http_status_to_jsonrpc_error_code(failure.status);
256
- const error = jsonrpc_error_response(id, {
257
- code,
258
- message: reason,
259
- data: { reason, ...rest },
260
- });
261
- return c.json(error, failure.status);
262
- }
263
- }
264
- // step 5: post-authorization auth — gate role / keeper requirements
265
- // against the request context populated by the authorization phase.
266
- const request_context = get_request_context(c);
267
- const credential_type = c.get(CREDENTIAL_TYPE_KEY) ?? null;
268
- const post_authorization_auth_error = check_action_auth_post_authorization(action_auth, request_context, credential_type);
269
- if (post_authorization_auth_error) {
270
- const error = jsonrpc_error_response(id, post_authorization_auth_error);
271
- return c.json(error, jsonrpc_error_code_to_http_status(post_authorization_auth_error.code));
272
- }
273
- // step 6: validate params
274
- // Missing `params` on the envelope maps to `undefined` for `z.void()`
275
- // input schemas and `{}` for object inputs (matches HTTP's "empty
276
- // body = empty object" convention so callers of all-optional-object
277
- // RPC methods can omit `params` on the wire). JSON-RPC 2.0 §4.2
278
- // forbids `params: null`, so `z.void()` is the spec-correct schema
279
- // for parameterless methods — registration above rejects `z.null()`
280
- // inputs to keep this branch from having to consider that legacy
281
- // shape. When `raw_params` is present it flows through unchanged so
282
- // contract-violating shapes still fail validation.
283
- const params = is_void_schema(action.spec.input) ? raw_params : (raw_params ?? {});
284
- const parse_result = action.spec.input.safeParse(params);
285
- if (!parse_result.success) {
286
- const error = jsonrpc_error_response(id, jsonrpc_error_messages.invalid_params('invalid params', {
287
- issues: parse_result.error.issues,
288
- }));
289
- return c.json(error, jsonrpc_error_code_to_http_status(JSONRPC_ERROR_CODES.invalid_params));
290
- }
291
- // step 7: rate limit — throttle-requests semantics (record on every
292
- // invocation, no success-reset). Suits admin mutation oracles where
293
- // the *successful* call is the threat. Different from REST login's
294
- // throttle-failures pattern that resets on success. Silent partial
295
- // enforcement: a key is checked iff its bucket's limiter is wired —
296
- // `rate_limit: 'both'` with only one limiter set runs only that side.
297
- // Account-keyed limiting bills the authenticated account: every
298
- // authenticated action has `request_context.account.id`, regardless
299
- // of whether an actor was resolved.
300
- const rate_limit = action.spec.rate_limit;
301
- const client_ip = get_client_ip(c);
302
- if (rate_limit) {
303
- const ip_check = action_ip_rate_limiter && (rate_limit === 'ip' || rate_limit === 'both');
304
- const account_check = action_account_rate_limiter &&
305
- request_context &&
306
- (rate_limit === 'account' || rate_limit === 'both');
307
- const reject = (retry_after) => {
308
- const error = jsonrpc_error_response(id, jsonrpc_error_messages.rate_limited('rate limited', { retry_after }));
309
- return c.json(error, jsonrpc_error_code_to_http_status(JSONRPC_ERROR_CODES.rate_limited));
310
- };
311
- if (ip_check) {
312
- const result = action_ip_rate_limiter.check(client_ip);
313
- if (!result.allowed)
314
- return reject(result.retry_after);
315
- }
316
- if (account_check) {
317
- const result = action_account_rate_limiter.check(request_context.account.id);
318
- if (!result.allowed)
319
- return reject(result.retry_after);
320
- }
321
- if (ip_check)
322
- action_ip_rate_limiter.record(client_ip);
323
- if (account_check)
324
- action_account_rate_limiter.record(request_context.account.id);
325
- }
326
- // step 8: dispatch — transaction for mutations, pool for reads
327
- const use_transaction = action.spec.side_effects;
147
+ // HTTP RPC has no streaming channel; `ctx.notify` warn-and-drops in DEV.
328
148
  const notify = (notify_method, _notify_params) => {
329
149
  if (DEV) {
330
150
  log.warn(`ctx.notify('${notify_method}') called on non-streaming transport; notification dropped (method=${method_name})`);
331
151
  }
332
152
  };
333
- const signal = c.req.raw.signal;
334
- const execute = async (db) => {
335
- const action_context = {
336
- auth: request_context,
337
- request_id: id,
338
- db,
339
- background_db: route.background_db,
340
- pending_effects: route.pending_effects,
341
- client_ip,
342
- log,
343
- notify,
344
- signal,
345
- };
346
- const output = await action.handler(parse_result.data, action_context);
347
- // DEV-only output validation — logs an error on mismatch, does not throw.
348
- if (DEV) {
349
- const output_result = action.spec.output.safeParse(output);
350
- if (!output_result.success) {
351
- log.error(`RPC output schema mismatch: ${method_name}`, output_result.error.issues);
352
- }
353
- }
354
- return c.json({ jsonrpc: JSONRPC_VERSION, id, result: output });
355
- };
356
- // error handling wraps the transaction boundary so handler throws
357
- // cause rollback before the error is formatted as a JSON-RPC response
358
- try {
359
- if (use_transaction) {
360
- return await route.db.transaction((tx) => execute(tx));
361
- }
362
- return await execute(route.db);
363
- }
364
- catch (err) {
365
- // Duck-type check: Error with numeric `code` signals a JSON-RPC error.
366
- // Avoids instanceof which fails when consumers throw their own ThrownJsonrpcError
367
- // (structurally identical but different class identity, e.g. zzz's copy).
368
- const error_like = err;
369
- if (err instanceof Error && typeof error_like.code === 'number') {
370
- const code = error_like.code;
371
- const status = jsonrpc_error_code_to_http_status(code);
372
- const error_json = { code, message: err.message };
373
- if (error_like.data !== undefined)
374
- error_json.data = error_like.data;
375
- return c.json(jsonrpc_error_response(id, error_json), status);
376
- }
377
- // generic error
378
- log.error(`Unhandled RPC handler error: ${method_name}`, err);
379
- const message = DEV && err instanceof Error ? err.message : 'internal server error';
380
- return c.json(jsonrpc_error_response(id, jsonrpc_error_messages.internal_error(message)), 500);
153
+ // Test escape hatch: harnesses pre-populate `REQUEST_CONTEXT_KEY` and
154
+ // flag `TEST_CONTEXT_PRESET_KEY = true`. Production middleware never
155
+ // sets the flag; the shim honors it and `perform_action` trusts the
156
+ // pre-baked context instead of running the live authorization phase.
157
+ const preset = c.get(TEST_CONTEXT_PRESET_KEY)
158
+ ? { request_context: get_request_context(c) }
159
+ : undefined;
160
+ const result = await perform_action({
161
+ action,
162
+ raw_params,
163
+ request_id: id,
164
+ account_id: c.get(ACCOUNT_ID_KEY) ?? null,
165
+ credential_type: c.get(CREDENTIAL_TYPE_KEY) ?? null,
166
+ client_ip: get_client_ip(c),
167
+ signal: c.req.raw.signal,
168
+ notify,
169
+ preset,
170
+ }, {
171
+ db: route.db,
172
+ pending_effects: route.pending_effects,
173
+ post_commit_effects: route.post_commit_effects,
174
+ log,
175
+ action_ip_rate_limiter,
176
+ action_account_rate_limiter,
177
+ });
178
+ const envelope = perform_action_result_to_envelope(id, result);
179
+ if (result.kind === 'error') {
180
+ // `result.status` is one of the JSON-RPC → HTTP status codes the
181
+ // dispatcher emits; Hono types `c.json`'s second arg as
182
+ // `ContentfulStatusCode`, which the cast bridges (the value space
183
+ // is verified by `jsonrpc_error_code_to_http_status`).
184
+ return c.json(envelope, result.status);
381
185
  }
186
+ return c.json(envelope);
382
187
  };
383
188
  // POST handler — parse JSON-RPC envelope from body
384
189
  const post_handler = async (c, route) => {
@@ -388,7 +193,7 @@ export const create_rpc_endpoint = (options) => {
388
193
  body = await c.req.json();
389
194
  }
390
195
  catch {
391
- const error = jsonrpc_error_response(null, jsonrpc_error_messages.parse_error());
196
+ const error = jsonrpc_error_envelope(null, jsonrpc_error_messages.parse_error());
392
197
  return c.json(error, 400);
393
198
  }
394
199
  const envelope = JsonrpcRequest.safeParse(body);
@@ -398,7 +203,7 @@ export const create_rpc_endpoint = (options) => {
398
203
  ? body.id
399
204
  : null;
400
205
  const id = typeof raw_id === 'string' || typeof raw_id === 'number' ? raw_id : null;
401
- const error = jsonrpc_error_response(id, jsonrpc_error_messages.invalid_request({ issues: envelope.error.issues }));
206
+ const error = jsonrpc_error_envelope(id, jsonrpc_error_messages.invalid_request({ issues: envelope.error.issues }));
402
207
  return c.json(error, 400);
403
208
  }
404
209
  return dispatch(c, route, envelope.data.method, envelope.data.params, envelope.data.id, false);
@@ -408,12 +213,12 @@ export const create_rpc_endpoint = (options) => {
408
213
  // step 1: parse from query string
409
214
  const method_name = c.req.query('method');
410
215
  if (!method_name) {
411
- const error = jsonrpc_error_response(null, jsonrpc_error_messages.invalid_request({ reason: 'missing method query parameter' }));
216
+ const error = jsonrpc_error_envelope(null, jsonrpc_error_messages.invalid_request({ reason: 'missing method query parameter' }));
412
217
  return c.json(error, 400);
413
218
  }
414
219
  const id_raw = c.req.query('id');
415
220
  if (!id_raw) {
416
- const error = jsonrpc_error_response(null, jsonrpc_error_messages.invalid_request({ reason: 'missing id query parameter' }));
221
+ const error = jsonrpc_error_envelope(null, jsonrpc_error_messages.invalid_request({ reason: 'missing id query parameter' }));
417
222
  return c.json(error, 400);
418
223
  }
419
224
  // parse integer ids so GET ?id=42 matches POST {id: 42} behavior
@@ -428,7 +233,7 @@ export const create_rpc_endpoint = (options) => {
428
233
  params = JSON.parse(params_raw);
429
234
  }
430
235
  catch {
431
- const error = jsonrpc_error_response(id, jsonrpc_error_messages.invalid_params('params query parameter is not valid JSON'));
236
+ const error = jsonrpc_error_envelope(id, jsonrpc_error_messages.invalid_params('params query parameter is not valid JSON'));
432
237
  return c.json(error, 400);
433
238
  }
434
239
  }
@@ -438,7 +243,7 @@ export const create_rpc_endpoint = (options) => {
438
243
  {
439
244
  method: 'POST',
440
245
  path: endpoint_path,
441
- auth: { type: 'none' }, // per-action auth inside dispatcher
246
+ auth: { account: 'none', actor: 'none' }, // per-action auth inside dispatcher
442
247
  handler: post_handler,
443
248
  description: `JSON-RPC 2.0 endpoint — ${actions.length} method${actions.length === 1 ? '' : 's'}`,
444
249
  input: z.null(), // dispatcher owns body parsing; rpc_endpoints surface has the real schemas
@@ -448,7 +253,7 @@ export const create_rpc_endpoint = (options) => {
448
253
  {
449
254
  method: 'GET',
450
255
  path: endpoint_path,
451
- auth: { type: 'none' }, // per-action auth inside dispatcher
256
+ auth: { account: 'none', actor: 'none' }, // per-action auth inside dispatcher
452
257
  handler: get_handler,
453
258
  description: `JSON-RPC 2.0 endpoint (cacheable reads) — ${actions.length} method${actions.length === 1 ? '' : 's'}`,
454
259
  input: z.null(), // params from query string, validated by dispatcher
@@ -23,10 +23,6 @@ export declare const ActionInitiator: z.ZodEnum<{
23
23
  backend: "backend";
24
24
  }>;
25
25
  export type ActionInitiator = z.infer<typeof ActionInitiator>;
26
- export declare const ActionAuth: z.ZodUnion<readonly [z.ZodLiteral<"public">, z.ZodLiteral<"authenticated">, z.ZodLiteral<"keeper">, z.ZodObject<{
27
- role: z.ZodString;
28
- }, z.core.$strict>]>;
29
- export type ActionAuth = z.infer<typeof ActionAuth>;
30
26
  export declare const ActionSideEffects: z.ZodBoolean;
31
27
  export type ActionSideEffects = z.infer<typeof ActionSideEffects>;
32
28
  export declare const ActionSpec: z.ZodObject<{
@@ -41,9 +37,29 @@ export declare const ActionSpec: z.ZodObject<{
41
37
  frontend: "frontend";
42
38
  backend: "backend";
43
39
  }>;
44
- auth: z.ZodNullable<z.ZodUnion<readonly [z.ZodLiteral<"public">, z.ZodLiteral<"authenticated">, z.ZodLiteral<"keeper">, z.ZodObject<{
45
- role: z.ZodString;
46
- }, z.core.$strict>]>>;
40
+ /**
41
+ * The four-axis auth shape (canonical schema in `http/auth_shape.ts`).
42
+ * `null` for `remote_notification` and `local_call` — those don't
43
+ * dispatch through the request/response auth gate.
44
+ *
45
+ * See `../http/auth_shape.ts` for the design rationale (orthogonal
46
+ * authentication / account-resolution / actor-resolution / role-and-
47
+ * credential authorization axes).
48
+ */
49
+ auth: z.ZodNullable<z.ZodObject<{
50
+ account: z.ZodEnum<{
51
+ optional: "optional";
52
+ none: "none";
53
+ required: "required";
54
+ }>;
55
+ actor: z.ZodEnum<{
56
+ optional: "optional";
57
+ none: "none";
58
+ required: "required";
59
+ }>;
60
+ roles: z.ZodOptional<z.ZodReadonly<z.ZodArray<z.ZodString>>>;
61
+ credential_types: z.ZodOptional<z.ZodReadonly<z.ZodArray<z.ZodString>>>;
62
+ }, z.core.$strict>>;
47
63
  side_effects: z.ZodBoolean;
48
64
  input: z.ZodCustom<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>, z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>;
49
65
  output: z.ZodCustom<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>, z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>;
@@ -62,7 +78,7 @@ export declare const ActionSpec: z.ZodObject<{
62
78
  * failure. Declarative metadata mirroring the `streams` precedent —
63
79
  * codegen, UI form-state matching, and docs read it off the spec instead
64
80
  * of scanning handler implementations. Reuses the same `as const` string
65
- * constants the handler throws (e.g. `ERROR_OFFER_*`) so call sites can
81
+ * constants the handler throws (e.g. `ERROR_ROLE_GRANT_OFFER_*`) so call sites can
66
82
  * import either side. Optional — actions that surface only standard
67
83
  * transport errors leave it unset.
68
84
  */
@@ -72,8 +88,9 @@ export declare const ActionSpec: z.ZodObject<{
72
88
  * actions without it skip the rate-limit hook entirely.
73
89
  *
74
90
  * - `'ip'` — keyed on the resolved client IP (`get_client_ip(c)`).
75
- * - `'account'` — keyed on the post-auth actor id (`request_context.actor.id`).
76
- * Registration-time error if paired with `auth: 'public'` (no actor).
91
+ * - `'account'` — keyed on the post-auth account id (`request_context.account.id`).
92
+ * Registration-time error if paired with `auth.account !== 'required'`
93
+ * (no account to key on).
77
94
  * - `'both'` — both checks run; either can block.
78
95
  *
79
96
  * Throttle-requests semantics — every invocation records, regardless of
@@ -112,9 +129,20 @@ export declare const RequestResponseActionSpec: z.ZodObject<{
112
129
  both: "both";
113
130
  }>>;
114
131
  kind: z.ZodDefault<z.ZodLiteral<"request_response">>;
115
- auth: z.ZodUnion<readonly [z.ZodLiteral<"public">, z.ZodLiteral<"authenticated">, z.ZodLiteral<"keeper">, z.ZodObject<{
116
- role: z.ZodString;
117
- }, z.core.$strict>]>;
132
+ auth: z.ZodObject<{
133
+ account: z.ZodEnum<{
134
+ optional: "optional";
135
+ none: "none";
136
+ required: "required";
137
+ }>;
138
+ actor: z.ZodEnum<{
139
+ optional: "optional";
140
+ none: "none";
141
+ required: "required";
142
+ }>;
143
+ roles: z.ZodOptional<z.ZodReadonly<z.ZodArray<z.ZodString>>>;
144
+ credential_types: z.ZodOptional<z.ZodReadonly<z.ZodArray<z.ZodString>>>;
145
+ }, z.core.$strict>;
118
146
  async: z.ZodDefault<z.ZodLiteral<true>>;
119
147
  }, z.core.$strict>;
120
148
  export type RequestResponseActionSpec = z.infer<typeof RequestResponseActionSpec>;
@@ -187,9 +215,20 @@ export declare const ActionSpecUnion: z.ZodUnion<readonly [z.ZodObject<{
187
215
  both: "both";
188
216
  }>>;
189
217
  kind: z.ZodDefault<z.ZodLiteral<"request_response">>;
190
- auth: z.ZodUnion<readonly [z.ZodLiteral<"public">, z.ZodLiteral<"authenticated">, z.ZodLiteral<"keeper">, z.ZodObject<{
191
- role: z.ZodString;
192
- }, z.core.$strict>]>;
218
+ auth: z.ZodObject<{
219
+ account: z.ZodEnum<{
220
+ optional: "optional";
221
+ none: "none";
222
+ required: "required";
223
+ }>;
224
+ actor: z.ZodEnum<{
225
+ optional: "optional";
226
+ none: "none";
227
+ required: "required";
228
+ }>;
229
+ roles: z.ZodOptional<z.ZodReadonly<z.ZodArray<z.ZodString>>>;
230
+ credential_types: z.ZodOptional<z.ZodReadonly<z.ZodArray<z.ZodString>>>;
231
+ }, z.core.$strict>;
193
232
  async: z.ZodDefault<z.ZodLiteral<true>>;
194
233
  }, z.core.$strict>, z.ZodObject<{
195
234
  method: z.ZodString;
@@ -1 +1 @@
1
- {"version":3,"file":"action_spec.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/action_spec.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAItB,eAAO,MAAM,UAAU;;;;EAAoE,CAAC;AAC5F,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAEpD,eAAO,MAAM,eAAe;;;;EAA0C,CAAC;AACvE,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAE9D,eAAO,MAAM,UAAU;;oBAKrB,CAAC;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAEpD,eAAO,MAAM,iBAAiB,cAAc,CAAC;AAC7C,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAElE,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;IAUtB;;;;;;OAMG;;IAEH;;;;;;;;OAQG;;IAEH;;;;;;;;;;;;;;;;;;OAkBG;;;;;;kBAEF,CAAC;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAEpD,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;;;;;;;kBAIpC,CAAC;AACH,MAAM,MAAM,yBAAyB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAElF,eAAO,MAAM,4BAA4B;;;;;;;;;;;;;;;;;;;;;kBAMvC,CAAC;AACH,MAAM,MAAM,4BAA4B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,4BAA4B,CAAC,CAAC;AAExF;;;GAGG;AACH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;kBAG9B,CAAC;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEtE,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;oBAI1B,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAE9D,gHAAgH;AAChH,eAAO,MAAM,cAAc,GAAI,OAAO,OAAO,KAAG,KAAK,IAAI,eAKoB,CAAC;AAE9E,eAAO,MAAM,gBAAgB;;;;;;;;;;EAU3B,CAAC;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC"}
1
+ {"version":3,"file":"action_spec.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/action_spec.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAKtB,eAAO,MAAM,UAAU;;;;EAAoE,CAAC;AAC5F,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAEpD,eAAO,MAAM,eAAe;;;;EAA0C,CAAC;AACvE,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAE9D,eAAO,MAAM,iBAAiB,cAAc,CAAC;AAC7C,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAElE,eAAO,MAAM,UAAU;;;;;;;;;;;;IAItB;;;;;;;;OAQG;;;;;;;;;;;;;;;;;;;;IAOH;;;;;;OAMG;;IAEH;;;;;;;;OAQG;;IAEH;;;;;;;;;;;;;;;;;;;OAmBG;;;;;;kBAEF,CAAC;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAEpD,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAIpC,CAAC;AACH,MAAM,MAAM,yBAAyB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAElF,eAAO,MAAM,4BAA4B;;;;;;;;;;;;;;;;;;;;;kBAMvC,CAAC;AACH,MAAM,MAAM,4BAA4B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,4BAA4B,CAAC,CAAC;AAExF;;;GAGG;AACH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;kBAG9B,CAAC;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEtE,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;oBAI1B,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAE9D,gHAAgH;AAChH,eAAO,MAAM,cAAc,GAAI,OAAO,OAAO,KAAG,KAAK,IAAI,eAKoB,CAAC;AAE9E,eAAO,MAAM,gBAAgB;;;;;;;;;;EAU3B,CAAC;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC"}