@fuzdev/fuz_app 0.55.0 → 0.57.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 (333) 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 +72 -39
  174. package/dist/http/error_schemas.d.ts.map +1 -1
  175. package/dist/http/error_schemas.js +81 -33
  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 +14 -6
  258. package/dist/testing/surface_invariants.d.ts.map +1 -1
  259. package/dist/testing/surface_invariants.js +119 -43
  260. package/dist/testing/ws_round_trip.d.ts +12 -13
  261. package/dist/testing/ws_round_trip.d.ts.map +1 -1
  262. package/dist/testing/ws_round_trip.js +19 -11
  263. package/dist/ui/AdminAccounts.svelte +23 -20
  264. package/dist/ui/AdminOverview.svelte +15 -13
  265. package/dist/ui/AdminOverview.svelte.d.ts.map +1 -1
  266. package/dist/ui/{AdminPermitHistory.svelte → AdminRoleGrantHistory.svelte} +12 -12
  267. package/dist/ui/AdminRoleGrantHistory.svelte.d.ts +4 -0
  268. package/dist/ui/AdminRoleGrantHistory.svelte.d.ts.map +1 -0
  269. package/dist/ui/BootstrapForm.svelte +1 -1
  270. package/dist/ui/CLAUDE.md +60 -60
  271. package/dist/ui/{PermitOfferForm.svelte → RoleGrantOfferForm.svelte} +27 -26
  272. package/dist/ui/{PermitOfferForm.svelte.d.ts → RoleGrantOfferForm.svelte.d.ts} +7 -7
  273. package/dist/ui/RoleGrantOfferForm.svelte.d.ts.map +1 -0
  274. package/dist/ui/{PermitOfferHistory.svelte → RoleGrantOfferHistory.svelte} +12 -12
  275. package/dist/ui/{PermitOfferHistory.svelte.d.ts → RoleGrantOfferHistory.svelte.d.ts} +4 -4
  276. package/dist/ui/RoleGrantOfferHistory.svelte.d.ts.map +1 -0
  277. package/dist/ui/{PermitOfferInbox.svelte → RoleGrantOfferInbox.svelte} +14 -14
  278. package/dist/ui/{PermitOfferInbox.svelte.d.ts → RoleGrantOfferInbox.svelte.d.ts} +4 -4
  279. package/dist/ui/RoleGrantOfferInbox.svelte.d.ts.map +1 -0
  280. package/dist/ui/SignupForm.svelte +1 -1
  281. package/dist/ui/SurfaceExplorer.svelte +35 -15
  282. package/dist/ui/SurfaceExplorer.svelte.d.ts.map +1 -1
  283. package/dist/ui/account_sessions_state.svelte.d.ts +2 -3
  284. package/dist/ui/account_sessions_state.svelte.d.ts.map +1 -1
  285. package/dist/ui/account_sessions_state.svelte.js +2 -3
  286. package/dist/ui/admin_accounts_state.svelte.d.ts +18 -18
  287. package/dist/ui/admin_accounts_state.svelte.d.ts.map +1 -1
  288. package/dist/ui/admin_accounts_state.svelte.js +16 -16
  289. package/dist/ui/admin_rpc_adapters.d.ts +20 -20
  290. package/dist/ui/admin_rpc_adapters.d.ts.map +1 -1
  291. package/dist/ui/admin_rpc_adapters.js +17 -17
  292. package/dist/ui/admin_sessions_state.svelte.d.ts +2 -2
  293. package/dist/ui/admin_sessions_state.svelte.js +2 -2
  294. package/dist/ui/audit_log_state.svelte.d.ts +7 -7
  295. package/dist/ui/audit_log_state.svelte.d.ts.map +1 -1
  296. package/dist/ui/audit_log_state.svelte.js +6 -6
  297. package/dist/ui/auth_state.svelte.d.ts +3 -3
  298. package/dist/ui/auth_state.svelte.d.ts.map +1 -1
  299. package/dist/ui/auth_state.svelte.js +6 -6
  300. package/dist/ui/format_scope.d.ts +2 -2
  301. package/dist/ui/format_scope.js +2 -2
  302. package/dist/ui/{permit_offers_state.svelte.d.ts → role_grant_offers_state.svelte.d.ts} +30 -30
  303. package/dist/ui/role_grant_offers_state.svelte.d.ts.map +1 -0
  304. package/dist/ui/{permit_offers_state.svelte.js → role_grant_offers_state.svelte.js} +18 -18
  305. package/dist/ui/ui_format.js +2 -2
  306. package/package.json +3 -3
  307. package/dist/auth/permit_offer_action_specs.d.ts.map +0 -1
  308. package/dist/auth/permit_offer_action_specs.js +0 -258
  309. package/dist/auth/permit_offer_actions.d.ts +0 -110
  310. package/dist/auth/permit_offer_actions.d.ts.map +0 -1
  311. package/dist/auth/permit_offer_notifications.d.ts.map +0 -1
  312. package/dist/auth/permit_offer_notifications.js +0 -182
  313. package/dist/auth/permit_offer_queries.d.ts.map +0 -1
  314. package/dist/auth/permit_offer_schema.d.ts +0 -125
  315. package/dist/auth/permit_offer_schema.d.ts.map +0 -1
  316. package/dist/auth/permit_queries.d.ts +0 -222
  317. package/dist/auth/permit_queries.d.ts.map +0 -1
  318. package/dist/auth/permit_queries.js +0 -305
  319. package/dist/auth/require_keeper.d.ts +0 -20
  320. package/dist/auth/require_keeper.d.ts.map +0 -1
  321. package/dist/auth/require_keeper.js +0 -35
  322. package/dist/auth/route_guards.d.ts +0 -27
  323. package/dist/auth/route_guards.d.ts.map +0 -1
  324. package/dist/auth/route_guards.js +0 -38
  325. package/dist/auth/session_lifecycle.d.ts +0 -37
  326. package/dist/auth/session_lifecycle.d.ts.map +0 -1
  327. package/dist/auth/session_lifecycle.js +0 -29
  328. package/dist/ui/AdminPermitHistory.svelte.d.ts +0 -4
  329. package/dist/ui/AdminPermitHistory.svelte.d.ts.map +0 -1
  330. package/dist/ui/PermitOfferForm.svelte.d.ts.map +0 -1
  331. package/dist/ui/PermitOfferHistory.svelte.d.ts.map +0 -1
  332. package/dist/ui/PermitOfferInbox.svelte.d.ts.map +0 -1
  333. package/dist/ui/permit_offers_state.svelte.d.ts.map +0 -1
@@ -12,7 +12,7 @@
12
12
  * @module
13
13
  */
14
14
  import { z } from 'zod';
15
- import type { RouteAuth } from './route_spec.js';
15
+ import { type RouteAuth } from './auth_shape.js';
16
16
  /** Request body failed Zod validation. */
17
17
  export declare const ERROR_INVALID_REQUEST_BODY: "invalid_request_body";
18
18
  /** Request body is not valid JSON or not an object. */
@@ -25,6 +25,16 @@ export declare const ERROR_INVALID_QUERY_PARAMS: "invalid_query_params";
25
25
  export declare const ERROR_AUTHENTICATION_REQUIRED: "authentication_required";
26
26
  /** Authenticated but missing required role. */
27
27
  export declare const ERROR_INSUFFICIENT_PERMISSIONS: "insufficient_permissions";
28
+ /**
29
+ * Route requires a credential type the request didn't arrive on.
30
+ * Symmetric with `ERROR_INSUFFICIENT_PERMISSIONS` + `required_roles`:
31
+ * the body carries `required_credential_types: ReadonlyArray<string>`
32
+ * — what the route demanded, not what arrived. Today the only
33
+ * credential gate is keeper (`['daemon_token']`); future gates
34
+ * (`agent_token`, `group_actor_token`) reuse the same literal and
35
+ * label themselves through the array.
36
+ */
37
+ export declare const ERROR_CREDENTIAL_TYPE_REQUIRED: "credential_type_required";
28
38
  /** Rate limiter rejected the request. */
29
39
  export declare const ERROR_RATE_LIMIT_EXCEEDED: "rate_limit_exceeded";
30
40
  /** Username or password is wrong (intentionally vague for enumeration prevention). */
@@ -74,8 +84,6 @@ export declare const ERROR_NO_ACTORS_ON_ACCOUNT: "no_actors_on_account";
74
84
  * `ERROR_NO_ACTORS_ON_ACCOUNT` (the actor list enumerated empty).
75
85
  */
76
86
  export declare const ERROR_ACCOUNT_VANISHED: "account_vanished";
77
- /** Keeper routes require daemon_token credential type. */
78
- export declare const ERROR_KEEPER_REQUIRES_DAEMON_TOKEN: "keeper_requires_daemon_token";
79
87
  /** Daemon token header present but malformed or not matching current/previous token. */
80
88
  export declare const ERROR_INVALID_DAEMON_TOKEN: "invalid_daemon_token";
81
89
  /** Daemon token valid but keeper account not yet resolved (pre-bootstrap). */
@@ -104,8 +112,8 @@ export declare const ERROR_INVITE_ACCOUNT_EXISTS_USERNAME: "invite_account_exist
104
112
  export declare const ERROR_INVITE_ACCOUNT_EXISTS_EMAIL: "invite_account_exists_email";
105
113
  /** Admin tried to grant a role that is not web-grantable. */
106
114
  export declare const ERROR_ROLE_NOT_WEB_GRANTABLE: "role_not_web_grantable";
107
- /** Permit ID not found or not owned by the target actor. */
108
- export declare const ERROR_PERMIT_NOT_FOUND: "permit_not_found";
115
+ /** Role grant ID not found or not owned by the target actor. */
116
+ export declare const ERROR_ROLE_GRANT_NOT_FOUND: "role_grant_not_found";
109
117
  /** Query parameter `event_type` is not a valid audit event type. */
110
118
  export declare const ERROR_INVALID_EVENT_TYPE: "invalid_event_type";
111
119
  /** DELETE blocked by a foreign key constraint. */
@@ -124,31 +132,61 @@ export declare const ApiError: z.ZodObject<{
124
132
  }, z.core.$loose>;
125
133
  export type ApiError = z.infer<typeof ApiError>;
126
134
  /**
127
- * Input validation error — returned when the request body fails Zod parsing.
135
+ * Input validation error — returned when params / query / body fails Zod
136
+ * parsing, or when the request body is not valid JSON.
128
137
  *
129
- * `issues` contains the Zod validation issues for diagnostic display.
138
+ * `error` is one of the four validation codes the framework emits.
139
+ * `issues` carries Zod's validation issues for diagnostic display on the
140
+ * three schema-failure cases (`invalid_request_body`,
141
+ * `invalid_route_params`, `invalid_query_params`). The `invalid_json_body`
142
+ * case (request body parse failure or non-object root) emits no `issues`,
143
+ * so the field is optional.
130
144
  */
131
145
  export declare const ValidationError: z.ZodObject<{
132
- error: z.ZodString;
133
- issues: z.ZodArray<z.ZodObject<{
146
+ error: z.ZodEnum<{
147
+ invalid_request_body: "invalid_request_body";
148
+ invalid_json_body: "invalid_json_body";
149
+ invalid_route_params: "invalid_route_params";
150
+ invalid_query_params: "invalid_query_params";
151
+ }>;
152
+ issues: z.ZodOptional<z.ZodArray<z.ZodObject<{
134
153
  code: z.ZodString;
135
154
  message: z.ZodString;
136
155
  path: z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
137
- }, z.core.$loose>>;
156
+ }, z.core.$loose>>>;
138
157
  }, z.core.$loose>;
139
158
  export type ValidationError = z.infer<typeof ValidationError>;
140
- /** Permission error — returned by `require_role()` when the required role is missing. */
159
+ /**
160
+ * Permission error — returned by `require_role()` and the dispatcher's
161
+ * post-authorization role gate when the actor's role_grants don't include any
162
+ * of the route's `auth.roles`.
163
+ *
164
+ * `required_roles` carries the full disjunction the route declared
165
+ * (`auth.roles` from the new flat-record shape). Single-role specs surface
166
+ * as a one-element array; multi-role disjunctions show every admittable
167
+ * role so clients can render targeted copy ("requires admin or steward").
168
+ */
141
169
  export declare const PermissionError: z.ZodObject<{
142
170
  error: z.ZodLiteral<"insufficient_permissions">;
143
- required_role: z.ZodString;
171
+ required_roles: z.ZodReadonly<z.ZodArray<z.ZodString>>;
144
172
  }, z.core.$loose>;
145
173
  export type PermissionError = z.infer<typeof PermissionError>;
146
- /** Keeper credential error — returned by `require_keeper` when credential type is wrong. */
147
- export declare const KeeperError: z.ZodObject<{
148
- error: z.ZodLiteral<"keeper_requires_daemon_token">;
149
- credential_type: z.ZodString;
174
+ /**
175
+ * Credential-type error — returned by the dispatcher's post-authorization
176
+ * credential gate (and the `require_credential_types` REST middleware) when
177
+ * the request's credential type isn't in the route's
178
+ * `auth.credential_types` allowlist.
179
+ *
180
+ * `required_credential_types` carries what the route declared
181
+ * (`['daemon_token']` for keeper; future gates carry their own labels).
182
+ * Symmetric with `PermissionError`'s `required_roles`: clients see what
183
+ * the route demanded, not what their credential is.
184
+ */
185
+ export declare const CredentialTypeRequiredError: z.ZodObject<{
186
+ error: z.ZodLiteral<"credential_type_required">;
187
+ required_credential_types: z.ZodReadonly<z.ZodArray<z.ZodString>>;
150
188
  }, z.core.$loose>;
151
- export type KeeperError = z.infer<typeof KeeperError>;
189
+ export type CredentialTypeRequiredError = z.infer<typeof CredentialTypeRequiredError>;
152
190
  /** Rate limit error — returned when a rate limiter rejects the request. */
153
191
  export declare const RateLimitError: z.ZodObject<{
154
192
  error: z.ZodLiteral<"rate_limit_exceeded">;
@@ -169,7 +207,7 @@ export type ForeignKeyError = z.infer<typeof ForeignKeyError>;
169
207
  * Authorization-phase failure shapes. Surfaced when the dispatcher's
170
208
  * `apply_authorization_phase` rejects a request before the handler runs —
171
209
  * the route is acting-aware (input declares `acting?: ActingActor` or
172
- * auth requires permits), but actor resolution failed.
210
+ * auth requires role_grants), but actor resolution failed.
173
211
  *
174
212
  * 400: `actor_required` (with `available[]`) for unspecified-actor on
175
213
  * a multi-actor account; `actor_not_on_account` for a supplied actor
@@ -180,7 +218,7 @@ export type ForeignKeyError = z.infer<typeof ForeignKeyError>;
180
218
  * race (account/actor row deleted between credential validation and
181
219
  * the dispatcher's follow-up read).
182
220
  *
183
- * Used by `derive_error_schemas` when `acting_aware` is true so the
221
+ * Used by `derive_error_schemas` when `auth.actor !== 'none'` so the
184
222
  * merged error surface matches what the dispatcher actually emits.
185
223
  */
186
224
  export declare const ActorRequiredError: z.ZodObject<{
@@ -232,24 +270,20 @@ export type RateLimitKey = z.infer<typeof RateLimitKey>;
232
270
  * Route handlers can declare additional error schemas via `RouteSpec.errors`;
233
271
  * explicit entries override auto-derived ones for the same status code.
234
272
  *
235
- * Derivation rules:
236
- * - **Has input schema** (non-null) or **has params schema** or **has query schema**: 400 (validation error with issues)
237
- * - **auth: authenticated**: 401
238
- * - **auth: role**: 401 + 403 (with `required_role`)
239
- * - **auth: keeper**: 401 + 403 (keeper-specific)
240
- * - **rate_limit**: 429 (rate limit exceeded with `retry_after`)
241
- * - **acting_aware**: extends 400 with `ActorRequiredError` / `ActorNotOnAccountError`
242
- * and adds 500 union of `NoActorsOnAccountError` / `AccountVanishedError`. The
243
- * dispatcher's authorization phase emits these on routes whose input declares
244
- * `acting?: ActingActor` or whose auth requires permits (`role` / `keeper`); the
245
- * route's surface must reflect them so DEV-mode error-schema validation in
246
- * `wrap_output_validation` doesn't fail when the auth phase fires before the
247
- * handler. See `http/CLAUDE.md` § Three-layer error-schema merge.
248
- *
249
- * `acting_aware` is computed at the merge call site (it requires inspecting
250
- * the input schema for `acting?: ActingActor`, which lives in `auth/`). This
251
- * keeps `http/` auth-agnostic — the per-route flag flows in via the optional
252
- * `is_acting_aware` callback on `apply_route_specs` / `generate_app_surface`.
273
+ * Derivation rules under the new flat-record auth shape:
274
+ * - **Has input / params / query schema**: 400 (`ValidationError`).
275
+ * - **`auth.account === 'required'`** or **`auth.actor === 'required'`**: 401
276
+ * (`ApiError`) — pre-validation 401 fires when the credential isn't there.
277
+ * `'optional'` does not derive 401.
278
+ * - **`auth.roles?.length`**: 403 (`PermissionError` carrying `required_roles`).
279
+ * - **`auth.credential_types?.length`**: 403 (`CredentialTypeRequiredError`
280
+ * carrying `required_credential_types` symmetric with `PermissionError`).
281
+ * Today the only credential gate is keeper; future gates reuse the literal.
282
+ * - **`auth.actor !== 'none'`** (`'optional'` or `'required'`): extends 400
283
+ * with `ActorRequiredError` / `ActorNotOnAccountError` and adds 500 union
284
+ * of `NoActorsOnAccountError` / `AccountVanishedError`. The dispatcher's
285
+ * authorization phase emits these whenever it tries to resolve an actor.
286
+ * - **rate_limit**: 429 (`RateLimitError` with `retry_after`).
253
287
  */
254
288
  export interface DeriveErrorSchemasOptions {
255
289
  auth: RouteAuth;
@@ -257,7 +291,6 @@ export interface DeriveErrorSchemasOptions {
257
291
  has_params?: boolean;
258
292
  has_query?: boolean;
259
293
  rate_limit?: RateLimitKey;
260
- acting_aware?: boolean;
261
294
  }
262
- export declare const derive_error_schemas: ({ auth, has_input, has_params, has_query, rate_limit, acting_aware, }: DeriveErrorSchemasOptions) => RouteErrorSchemas;
295
+ export declare const derive_error_schemas: ({ auth, has_input, has_params, has_query, rate_limit, }: DeriveErrorSchemasOptions) => RouteErrorSchemas;
263
296
  //# sourceMappingURL=error_schemas.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"error_schemas.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/http/error_schemas.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"error_schemas.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/http/error_schemas.ts"],"names":[],"mappings":"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"}
@@ -12,6 +12,7 @@
12
12
  * @module
13
13
  */
14
14
  import { z } from 'zod';
15
+ import { needs_actor } from './auth_shape.js';
15
16
  // --- Core: Validation (auto-derived by route spec middleware) ---
16
17
  /** Request body failed Zod validation. */
17
18
  export const ERROR_INVALID_REQUEST_BODY = 'invalid_request_body';
@@ -26,6 +27,16 @@ export const ERROR_INVALID_QUERY_PARAMS = 'invalid_query_params';
26
27
  export const ERROR_AUTHENTICATION_REQUIRED = 'authentication_required';
27
28
  /** Authenticated but missing required role. */
28
29
  export const ERROR_INSUFFICIENT_PERMISSIONS = 'insufficient_permissions';
30
+ /**
31
+ * Route requires a credential type the request didn't arrive on.
32
+ * Symmetric with `ERROR_INSUFFICIENT_PERMISSIONS` + `required_roles`:
33
+ * the body carries `required_credential_types: ReadonlyArray<string>`
34
+ * — what the route demanded, not what arrived. Today the only
35
+ * credential gate is keeper (`['daemon_token']`); future gates
36
+ * (`agent_token`, `group_actor_token`) reuse the same literal and
37
+ * label themselves through the array.
38
+ */
39
+ export const ERROR_CREDENTIAL_TYPE_REQUIRED = 'credential_type_required';
29
40
  /** Rate limiter rejected the request. */
30
41
  export const ERROR_RATE_LIMIT_EXCEEDED = 'rate_limit_exceeded';
31
42
  /** Username or password is wrong (intentionally vague for enumeration prevention). */
@@ -77,8 +88,6 @@ export const ERROR_NO_ACTORS_ON_ACCOUNT = 'no_actors_on_account';
77
88
  */
78
89
  export const ERROR_ACCOUNT_VANISHED = 'account_vanished';
79
90
  // --- Keeper / daemon token ---
80
- /** Keeper routes require daemon_token credential type. */
81
- export const ERROR_KEEPER_REQUIRES_DAEMON_TOKEN = 'keeper_requires_daemon_token';
82
91
  /** Daemon token header present but malformed or not matching current/previous token. */
83
92
  export const ERROR_INVALID_DAEMON_TOKEN = 'invalid_daemon_token';
84
93
  /** Daemon token valid but keeper account not yet resolved (pre-bootstrap). */
@@ -110,8 +119,8 @@ export const ERROR_INVITE_ACCOUNT_EXISTS_EMAIL = 'invite_account_exists_email';
110
119
  // --- Admin routes ---
111
120
  /** Admin tried to grant a role that is not web-grantable. */
112
121
  export const ERROR_ROLE_NOT_WEB_GRANTABLE = 'role_not_web_grantable';
113
- /** Permit ID not found or not owned by the target actor. */
114
- export const ERROR_PERMIT_NOT_FOUND = 'permit_not_found';
122
+ /** Role grant ID not found or not owned by the target actor. */
123
+ export const ERROR_ROLE_GRANT_NOT_FOUND = 'role_grant_not_found';
115
124
  /** Query parameter `event_type` is not a valid audit event type. */
116
125
  export const ERROR_INVALID_EVENT_TYPE = 'invalid_event_type';
117
126
  // --- DB table browser ---
@@ -130,27 +139,59 @@ export const ERROR_DATABASE_CONNECTION_FAILED = 'database_connection_failed';
130
139
  /** Base API error — all JSON error responses have at least `{error: string}`. */
131
140
  export const ApiError = z.looseObject({ error: z.string() });
132
141
  /**
133
- * Input validation error — returned when the request body fails Zod parsing.
142
+ * Input validation error — returned when params / query / body fails Zod
143
+ * parsing, or when the request body is not valid JSON.
134
144
  *
135
- * `issues` contains the Zod validation issues for diagnostic display.
145
+ * `error` is one of the four validation codes the framework emits.
146
+ * `issues` carries Zod's validation issues for diagnostic display on the
147
+ * three schema-failure cases (`invalid_request_body`,
148
+ * `invalid_route_params`, `invalid_query_params`). The `invalid_json_body`
149
+ * case (request body parse failure or non-object root) emits no `issues`,
150
+ * so the field is optional.
136
151
  */
137
152
  export const ValidationError = z.looseObject({
138
- error: z.string(),
139
- issues: z.array(z.looseObject({
153
+ error: z.enum([
154
+ ERROR_INVALID_REQUEST_BODY,
155
+ ERROR_INVALID_JSON_BODY,
156
+ ERROR_INVALID_ROUTE_PARAMS,
157
+ ERROR_INVALID_QUERY_PARAMS,
158
+ ]),
159
+ issues: z
160
+ .array(z.looseObject({
140
161
  code: z.string(),
141
162
  message: z.string(),
142
163
  path: z.array(z.union([z.string(), z.number()])),
143
- })),
164
+ }))
165
+ .optional(),
144
166
  });
145
- /** Permission error — returned by `require_role()` when the required role is missing. */
167
+ /**
168
+ * Permission error — returned by `require_role()` and the dispatcher's
169
+ * post-authorization role gate when the actor's role_grants don't include any
170
+ * of the route's `auth.roles`.
171
+ *
172
+ * `required_roles` carries the full disjunction the route declared
173
+ * (`auth.roles` from the new flat-record shape). Single-role specs surface
174
+ * as a one-element array; multi-role disjunctions show every admittable
175
+ * role so clients can render targeted copy ("requires admin or steward").
176
+ */
146
177
  export const PermissionError = z.looseObject({
147
178
  error: z.literal(ERROR_INSUFFICIENT_PERMISSIONS),
148
- required_role: z.string(),
179
+ required_roles: z.array(z.string()).readonly(),
149
180
  });
150
- /** Keeper credential error — returned by `require_keeper` when credential type is wrong. */
151
- export const KeeperError = z.looseObject({
152
- error: z.literal(ERROR_KEEPER_REQUIRES_DAEMON_TOKEN),
153
- credential_type: z.string(),
181
+ /**
182
+ * Credential-type error — returned by the dispatcher's post-authorization
183
+ * credential gate (and the `require_credential_types` REST middleware) when
184
+ * the request's credential type isn't in the route's
185
+ * `auth.credential_types` allowlist.
186
+ *
187
+ * `required_credential_types` carries what the route declared
188
+ * (`['daemon_token']` for keeper; future gates carry their own labels).
189
+ * Symmetric with `PermissionError`'s `required_roles`: clients see what
190
+ * the route demanded, not what their credential is.
191
+ */
192
+ export const CredentialTypeRequiredError = z.looseObject({
193
+ error: z.literal(ERROR_CREDENTIAL_TYPE_REQUIRED),
194
+ required_credential_types: z.array(z.string()).readonly(),
154
195
  });
155
196
  /** Rate limit error — returned when a rate limiter rejects the request. */
156
197
  export const RateLimitError = z.looseObject({
@@ -169,7 +210,7 @@ export const ForeignKeyError = z.looseObject({
169
210
  * Authorization-phase failure shapes. Surfaced when the dispatcher's
170
211
  * `apply_authorization_phase` rejects a request before the handler runs —
171
212
  * the route is acting-aware (input declares `acting?: ActingActor` or
172
- * auth requires permits), but actor resolution failed.
213
+ * auth requires role_grants), but actor resolution failed.
173
214
  *
174
215
  * 400: `actor_required` (with `available[]`) for unspecified-actor on
175
216
  * a multi-actor account; `actor_not_on_account` for a supplied actor
@@ -180,7 +221,7 @@ export const ForeignKeyError = z.looseObject({
180
221
  * race (account/actor row deleted between credential validation and
181
222
  * the dispatcher's follow-up read).
182
223
  *
183
- * Used by `derive_error_schemas` when `acting_aware` is true so the
224
+ * Used by `derive_error_schemas` when `auth.actor !== 'none'` so the
184
225
  * merged error surface matches what the dispatcher actually emits.
185
226
  */
186
227
  export const ActorRequiredError = z.looseObject({
@@ -207,10 +248,10 @@ export const AccountVanishedError = z.looseObject({
207
248
  * - `'both'` — both keys.
208
249
  */
209
250
  export const RateLimitKey = z.enum(['ip', 'account', 'both']);
210
- export const derive_error_schemas = ({ auth, has_input = false, has_params = false, has_query = false, rate_limit, acting_aware = false, }) => {
251
+ export const derive_error_schemas = ({ auth, has_input = false, has_params = false, has_query = false, rate_limit, }) => {
211
252
  const errors = {};
212
253
  const has_validation = has_input || has_params || has_query;
213
- if (acting_aware) {
254
+ if (needs_actor(auth)) {
214
255
  errors[400] = has_validation
215
256
  ? z.union([ValidationError, ActorRequiredError, ActorNotOnAccountError])
216
257
  : z.union([ActorRequiredError, ActorNotOnAccountError]);
@@ -219,20 +260,27 @@ export const derive_error_schemas = ({ auth, has_input = false, has_params = fal
219
260
  else if (has_validation) {
220
261
  errors[400] = ValidationError;
221
262
  }
222
- switch (auth.type) {
223
- case 'none':
224
- break;
225
- case 'authenticated':
226
- errors[401] = ApiError;
227
- break;
228
- case 'role':
229
- errors[401] = ApiError;
230
- errors[403] = PermissionError;
231
- break;
232
- case 'keeper':
233
- errors[401] = ApiError;
234
- errors[403] = KeeperError;
235
- break;
263
+ // 401 fires when the dispatcher's pre-validation gate rejects an
264
+ // unauthenticated caller — `account === 'required'` (no credential) or
265
+ // `actor === 'required'` (no credential to resolve an actor against,
266
+ // per registry-time invariant 3 forbidding accountless actors in v1).
267
+ if (auth.account === 'required' || auth.actor === 'required') {
268
+ errors[401] = ApiError;
269
+ }
270
+ // 403 fires when `auth.roles` or `auth.credential_types` rejects a
271
+ // resolved request context. With both axes set, the 403 body could be
272
+ // either shape — emit the union so DEV-mode error-schema validation
273
+ // accepts whichever the dispatcher produced.
274
+ const has_role_gate = !!auth.roles?.length;
275
+ const has_credential_gate = !!auth.credential_types?.length;
276
+ if (has_role_gate && has_credential_gate) {
277
+ errors[403] = z.union([PermissionError, CredentialTypeRequiredError]);
278
+ }
279
+ else if (has_role_gate) {
280
+ errors[403] = PermissionError;
281
+ }
282
+ else if (has_credential_gate) {
283
+ errors[403] = CredentialTypeRequiredError;
236
284
  }
237
285
  if (rate_limit) {
238
286
  errors[429] = RateLimitError;
@@ -1,33 +1,86 @@
1
1
  /**
2
- * Shared post-commit side-effect helper.
2
+ * Two-queue side-effect machinery for request handlers.
3
3
  *
4
- * WS sends and `on_audit_event` SSE broadcasts must never fire mid-transaction —
5
- * a rollback would leak state that never existed. Anything pushed onto
6
- * `pending_effects` runs after the response is sent (see the request-context
7
- * middleware), so this helper is the canonical home for post-commit fan-out.
4
+ * Handlers register fire-and-forget work in one of two queues, distinguished
5
+ * by their timing contract:
8
6
  *
9
- * Satisfied by both `RouteContext` (HTTP routes) and `ActionContext` (RPC
10
- * actions) they share `{log, pending_effects}` by convention, so this
11
- * module stays in `http/` (both depend on it).
7
+ * - `pending_effects: Array<Promise<void>>` eager. Producers push pool
8
+ * writes that are already in flight (audit emits, session-touch UPDATE,
9
+ * api-token usage tracking). The pool write is rollback-resilient by
10
+ * virtue of running outside the request transaction; pushing the
11
+ * in-flight handle lets test mode (`await_pending_effects: true`) await
12
+ * it.
13
+ * - `post_commit_effects: Array<() => void | Promise<void>>` — deferred.
14
+ * Producers go through `emit_after_commit(ctx, fn)`; the flush
15
+ * middleware is the only site that ever invokes the thunk, and it does
16
+ * so after the request handler (and its wrapping `db.transaction`)
17
+ * returns. Used for WS sends and any work that must observe a committed
18
+ * transaction.
19
+ *
20
+ * The split exists because the two shapes encode different contracts:
21
+ * eager pushers are saying "wait for this work that's already started";
22
+ * thunk pushers are saying "run this after the handler returns." Burying
23
+ * both behind one `Array<PendingEffect>` made `c.var.pending_effects.push(x)`
24
+ * ambiguous at the call site. With separate queues, the field name is
25
+ * the contract.
26
+ *
27
+ * Both `RouteContext` (HTTP routes) and `ActionContext` (RPC + WS
28
+ * actions) carry both queues by convention, so this module stays in
29
+ * `http/` (every transport depends on it).
12
30
  *
13
31
  * @module
14
32
  */
15
33
  import type { Logger } from '@fuzdev/fuz_util/log.js';
16
- /** Minimal structural context required by `emit_after_commit`. */
17
- export interface PendingEffectsContext {
34
+ /**
35
+ * Minimal structural context required by `emit_after_commit`. Both
36
+ * `RouteContext` and `ActionContext` satisfy this — they each carry
37
+ * `log` and `post_commit_effects`.
38
+ */
39
+ export interface EmitAfterCommitContext {
18
40
  log: Logger;
19
- pending_effects: Array<Promise<void>>;
41
+ post_commit_effects: Array<() => void | Promise<void>>;
20
42
  }
21
43
  /**
22
44
  * Defer a side effect until after the handler's transaction commits.
23
45
  *
24
- * Exceptions thrown by `fn` are caught and logged via `ctx.log.error`, so one
25
- * failed send cannot corrupt the already-committed response or starve other
26
- * queued effects in the same tick.
46
+ * Pushes a raw thunk onto `ctx.post_commit_effects` the flush
47
+ * middleware (in `server/app_server.ts` and the per-message WS dispatcher)
48
+ * is the only site that ever invokes `fn`. This is load-bearing: a
49
+ * previous implementation queued `Promise.resolve().then(fn)`, which
50
+ * JavaScript's microtask scheduler drains before the wrapping
51
+ * `await db.query('COMMIT')` resumes — `fn` fired mid-transaction and a
52
+ * rollback would leak a notification for state that never landed.
53
+ *
54
+ * The thunk shape closes that gap by deferring the work to flush time.
55
+ * The flush owns the per-thunk `try/catch` + `log.error` so any
56
+ * directly-pushed thunk (tests included) cannot escape the safety net.
57
+ *
58
+ * @param ctx - context carrying `log` and the `post_commit_effects` queue
59
+ * @param fn - side effect to run after commit; may return `void` or `Promise<void>`
60
+ * @mutates `ctx.post_commit_effects` - appends `fn` verbatim
61
+ */
62
+ export declare const emit_after_commit: (ctx: EmitAfterCommitContext, fn: () => void | Promise<void>) => void;
63
+ /**
64
+ * Drain an eager `pending_effects` queue: `Promise.allSettled` the
65
+ * in-flight handles, route every rejection through `log.error`, and
66
+ * fan out to `on_rejection` when supplied (production wires this to
67
+ * `on_effect_error` for monitoring).
68
+ *
69
+ * Returned promise resolves once every effect has settled. Never
70
+ * rejects. No-op when `effects` is empty (common on read-only
71
+ * requests).
72
+ *
73
+ * Symmetric with `flush_post_commit_effects` for the deferred queue.
74
+ */
75
+ export declare const flush_pending_effects: (effects: ReadonlyArray<Promise<void>>, log: Logger, on_rejection?: (reason: unknown) => void) => Promise<void>;
76
+ /**
77
+ * Drain a `post_commit_effects` queue: invoke each thunk under
78
+ * `try/catch`, collect any returned promises, and `Promise.allSettled`
79
+ * them. Synchronous throws and async rejections are routed through
80
+ * `log.error` so one failing effect cannot starve siblings.
27
81
  *
28
- * @param ctx - context carrying `log` and the `pending_effects` queue
29
- * @param fn - synchronous side effect to run after commit
30
- * @mutates `ctx.pending_effects` - appends a never-rejecting promise wrapping `fn`
82
+ * Returned promise resolves once every thunk has finished. Never
83
+ * rejects.
31
84
  */
32
- export declare const emit_after_commit: (ctx: PendingEffectsContext, fn: () => void) => void;
85
+ export declare const flush_post_commit_effects: (effects: ReadonlyArray<() => void | Promise<void>>, log: Logger) => Promise<void>;
33
86
  //# sourceMappingURL=pending_effects.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"pending_effects.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/http/pending_effects.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAEpD,kEAAkE;AAClE,MAAM,WAAW,qBAAqB;IACrC,GAAG,EAAE,MAAM,CAAC;IACZ,eAAe,EAAE,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;CACtC;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,iBAAiB,GAAI,KAAK,qBAAqB,EAAE,IAAI,MAAM,IAAI,KAAG,IAU9E,CAAC"}
1
+ {"version":3,"file":"pending_effects.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/http/pending_effects.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAEpD;;;;GAIG;AACH,MAAM,WAAW,sBAAsB;IACtC,GAAG,EAAE,MAAM,CAAC;IACZ,mBAAmB,EAAE,KAAK,CAAC,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;CACvD;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,iBAAiB,GAC7B,KAAK,sBAAsB,EAC3B,IAAI,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAC5B,IAEF,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,qBAAqB,GACjC,SAAS,aAAa,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,EACrC,KAAK,MAAM,EACX,eAAe,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,KACtC,OAAO,CAAC,IAAI,CASd,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,yBAAyB,GACrC,SAAS,aAAa,CAAC,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,EAClD,KAAK,MAAM,KACT,OAAO,CAAC,IAAI,CAiBd,CAAC"}
@@ -1,35 +1,104 @@
1
1
  /**
2
- * Shared post-commit side-effect helper.
2
+ * Two-queue side-effect machinery for request handlers.
3
3
  *
4
- * WS sends and `on_audit_event` SSE broadcasts must never fire mid-transaction —
5
- * a rollback would leak state that never existed. Anything pushed onto
6
- * `pending_effects` runs after the response is sent (see the request-context
7
- * middleware), so this helper is the canonical home for post-commit fan-out.
4
+ * Handlers register fire-and-forget work in one of two queues, distinguished
5
+ * by their timing contract:
8
6
  *
9
- * Satisfied by both `RouteContext` (HTTP routes) and `ActionContext` (RPC
10
- * actions) they share `{log, pending_effects}` by convention, so this
11
- * module stays in `http/` (both depend on it).
7
+ * - `pending_effects: Array<Promise<void>>` eager. Producers push pool
8
+ * writes that are already in flight (audit emits, session-touch UPDATE,
9
+ * api-token usage tracking). The pool write is rollback-resilient by
10
+ * virtue of running outside the request transaction; pushing the
11
+ * in-flight handle lets test mode (`await_pending_effects: true`) await
12
+ * it.
13
+ * - `post_commit_effects: Array<() => void | Promise<void>>` — deferred.
14
+ * Producers go through `emit_after_commit(ctx, fn)`; the flush
15
+ * middleware is the only site that ever invokes the thunk, and it does
16
+ * so after the request handler (and its wrapping `db.transaction`)
17
+ * returns. Used for WS sends and any work that must observe a committed
18
+ * transaction.
19
+ *
20
+ * The split exists because the two shapes encode different contracts:
21
+ * eager pushers are saying "wait for this work that's already started";
22
+ * thunk pushers are saying "run this after the handler returns." Burying
23
+ * both behind one `Array<PendingEffect>` made `c.var.pending_effects.push(x)`
24
+ * ambiguous at the call site. With separate queues, the field name is
25
+ * the contract.
26
+ *
27
+ * Both `RouteContext` (HTTP routes) and `ActionContext` (RPC + WS
28
+ * actions) carry both queues by convention, so this module stays in
29
+ * `http/` (every transport depends on it).
12
30
  *
13
31
  * @module
14
32
  */
15
33
  /**
16
34
  * Defer a side effect until after the handler's transaction commits.
17
35
  *
18
- * Exceptions thrown by `fn` are caught and logged via `ctx.log.error`, so one
19
- * failed send cannot corrupt the already-committed response or starve other
20
- * queued effects in the same tick.
36
+ * Pushes a raw thunk onto `ctx.post_commit_effects` the flush
37
+ * middleware (in `server/app_server.ts` and the per-message WS dispatcher)
38
+ * is the only site that ever invokes `fn`. This is load-bearing: a
39
+ * previous implementation queued `Promise.resolve().then(fn)`, which
40
+ * JavaScript's microtask scheduler drains before the wrapping
41
+ * `await db.query('COMMIT')` resumes — `fn` fired mid-transaction and a
42
+ * rollback would leak a notification for state that never landed.
21
43
  *
22
- * @param ctx - context carrying `log` and the `pending_effects` queue
23
- * @param fn - synchronous side effect to run after commit
24
- * @mutates `ctx.pending_effects` - appends a never-rejecting promise wrapping `fn`
44
+ * The thunk shape closes that gap by deferring the work to flush time.
45
+ * The flush owns the per-thunk `try/catch` + `log.error` so any
46
+ * directly-pushed thunk (tests included) cannot escape the safety net.
47
+ *
48
+ * @param ctx - context carrying `log` and the `post_commit_effects` queue
49
+ * @param fn - side effect to run after commit; may return `void` or `Promise<void>`
50
+ * @mutates `ctx.post_commit_effects` - appends `fn` verbatim
25
51
  */
26
52
  export const emit_after_commit = (ctx, fn) => {
27
- ctx.pending_effects.push(Promise.resolve().then(() => {
53
+ ctx.post_commit_effects.push(fn);
54
+ };
55
+ /**
56
+ * Drain an eager `pending_effects` queue: `Promise.allSettled` the
57
+ * in-flight handles, route every rejection through `log.error`, and
58
+ * fan out to `on_rejection` when supplied (production wires this to
59
+ * `on_effect_error` for monitoring).
60
+ *
61
+ * Returned promise resolves once every effect has settled. Never
62
+ * rejects. No-op when `effects` is empty (common on read-only
63
+ * requests).
64
+ *
65
+ * Symmetric with `flush_post_commit_effects` for the deferred queue.
66
+ */
67
+ export const flush_pending_effects = async (effects, log, on_rejection) => {
68
+ if (effects.length === 0)
69
+ return;
70
+ const results = await Promise.allSettled(effects);
71
+ for (const result of results) {
72
+ if (result.status === 'rejected') {
73
+ log.error('pending effect rejected:', result.reason);
74
+ on_rejection?.(result.reason);
75
+ }
76
+ }
77
+ };
78
+ /**
79
+ * Drain a `post_commit_effects` queue: invoke each thunk under
80
+ * `try/catch`, collect any returned promises, and `Promise.allSettled`
81
+ * them. Synchronous throws and async rejections are routed through
82
+ * `log.error` so one failing effect cannot starve siblings.
83
+ *
84
+ * Returned promise resolves once every thunk has finished. Never
85
+ * rejects.
86
+ */
87
+ export const flush_post_commit_effects = async (effects, log) => {
88
+ const promises = [];
89
+ for (const fn of effects) {
28
90
  try {
29
- fn();
91
+ const result = fn();
92
+ if (result instanceof Promise) {
93
+ promises.push(result.catch((err) => {
94
+ log.error('post-commit side effect failed:', err);
95
+ }));
96
+ }
30
97
  }
31
98
  catch (err) {
32
- ctx.log.error('post-commit side effect failed:', err);
99
+ log.error('post-commit side effect failed:', err);
33
100
  }
34
- }));
101
+ }
102
+ if (promises.length)
103
+ await Promise.allSettled(promises);
35
104
  };