@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
@@ -5,59 +5,62 @@
5
5
  *
6
6
  * - Account management: `admin_account_list`, `admin_session_list`,
7
7
  * `admin_session_revoke_all`, `admin_token_revoke_all`.
8
- * - Audit log reads: `audit_log_list`, `audit_log_permit_history`.
8
+ * - Audit log reads: `audit_log_list`, `audit_log_role_grant_history`.
9
9
  * - Invite CRUD: `invite_create`, `invite_list`, `invite_delete`.
10
10
  * - App settings: `app_settings_get`, `app_settings_update` (registered only
11
11
  * when `AdminActionOptions.app_settings` is provided — the mutable ref is
12
12
  * owned by the server context and shared with signup middleware).
13
13
  *
14
14
  * The action specs themselves live in `auth/admin_action_specs.ts`. Mutations
15
- * emit matching audit events via `audit_log_fire_and_forget`.
15
+ * emit matching audit events via `deps.audit.emit`.
16
16
  *
17
17
  * Authorization is declared at the spec level (`auth: {role: 'admin'}`) so
18
18
  * the RPC dispatcher enforces it before the handler runs and the generated
19
- * surface accurately reports the requirement. `permit_revoke` in
20
- * `auth/permit_offer_actions.ts` uses the same spec-level pattern even though its
19
+ * surface accurately reports the requirement. `role_grant_revoke` in
20
+ * `auth/role_grant_offer_actions.ts` uses the same spec-level pattern even though its
21
21
  * sibling methods are authenticated-but-not-admin — the dispatcher checks
22
22
  * auth per-spec, so mixed-auth endpoints compose cleanly. Handler-level
23
23
  * gates are reserved for input-dependent elevation (e.g.
24
- * `permit_offer_list`/`_history` elevate to admin only when the caller
24
+ * `role_grant_offer_list`/`_history` elevate to admin only when the caller
25
25
  * passes an `account_id` other than their own — an input-dependent check
26
26
  * the spec can't express).
27
27
  *
28
28
  * @module
29
29
  */
30
- import { rpc_actor_action } from '../actions/action_rpc.js';
30
+ import { rpc_action } from '../actions/action_rpc.js';
31
31
  import { jsonrpc_errors } from '../http/jsonrpc_errors.js';
32
- import { BUILTIN_ROLE_OPTIONS } from './role_schema.js';
32
+ import { BUILTIN_ROLE_SPECS_BY_NAME, list_roles_with_grant_path, } from './role_schema.js';
33
+ import { GRANT_PATH_ADMIN } from './grant_path_schema.js';
33
34
  import { query_account_by_email, query_account_by_id, query_account_by_username, query_admin_account_list, } from './account_queries.js';
34
35
  import { query_session_list_all_active, query_session_revoke_all_for_account, } from './session_queries.js';
35
36
  import { query_revoke_all_api_tokens_for_account } from './api_token_queries.js';
36
- import { audit_log_fire_and_forget, query_audit_log_list_permit_history, query_audit_log_list_with_usernames, } from './audit_log_queries.js';
37
+ import { query_audit_log_list_role_grant_history, query_audit_log_list_with_usernames, } from './audit_log_queries.js';
37
38
  import { AUDIT_LOG_DEFAULT_LIMIT } from './audit_log_schema.js';
38
39
  import { query_create_invite, query_invite_delete_unclaimed, query_invite_list_all_with_usernames, } from './invite_queries.js';
39
40
  import {} from './app_settings_schema.js';
40
41
  import { query_app_settings_load_with_username, query_app_settings_update, } from './app_settings_queries.js';
41
42
  import { is_pg_unique_violation } from '../db/pg_error.js';
42
43
  import { ERROR_ACCOUNT_NOT_FOUND, ERROR_INVITE_ACCOUNT_EXISTS_EMAIL, ERROR_INVITE_ACCOUNT_EXISTS_USERNAME, ERROR_INVITE_DUPLICATE, ERROR_INVITE_MISSING_IDENTIFIER, ERROR_INVITE_NOT_FOUND, } from '../http/error_schemas.js';
43
- import { admin_account_list_action_spec, admin_session_list_action_spec, admin_session_revoke_all_action_spec, admin_token_revoke_all_action_spec, audit_log_list_action_spec, audit_log_permit_history_action_spec, invite_create_action_spec, invite_list_action_spec, invite_delete_action_spec, app_settings_get_action_spec, app_settings_update_action_spec, } from './admin_action_specs.js';
44
+ import { admin_account_list_action_spec, admin_session_list_action_spec, admin_session_revoke_all_action_spec, admin_token_revoke_all_action_spec, audit_log_list_action_spec, audit_log_role_grant_history_action_spec, invite_create_action_spec, invite_list_action_spec, invite_delete_action_spec, app_settings_get_action_spec, app_settings_update_action_spec, } from './admin_action_specs.js';
44
45
  /**
45
46
  * Create the admin-only RPC actions.
46
47
  *
47
- * @param deps - `AdminActionDeps` slice of `AppDeps` (`log`, `on_audit_event`, optional `audit_log_config`)
48
+ * @param deps - `RouteFactoryDeps` (`log`, `audit`, …). `log` drives RPC-
49
+ * internal error logging; `audit.emit` writes audit rows via the captured
50
+ * pool. The bound emitter encapsulates `on_audit_event` fan-out and the
51
+ * optional `AuditLogConfig`.
48
52
  * @param options - role schema for `grantable_roles` derivation
49
53
  * @returns the `RpcAction` array to spread into a `create_rpc_endpoint` call
50
54
  * @mutates `options.app_settings` ref - `app_settings_update` writes `open_signup`, `updated_at`, and `updated_by` so signup middleware reads without a DB round trip
51
55
  */
52
56
  export const create_admin_actions = (deps, options = {}) => {
53
- const role_options = options.roles?.role_options ?? BUILTIN_ROLE_OPTIONS;
54
- const grantable_roles = [];
55
- for (const [name, rc] of role_options) {
56
- if (rc.web_grantable)
57
- grantable_roles.push(name);
58
- }
59
- const account_list_handler = async (_input, ctx) => {
60
- const accounts = await query_admin_account_list(ctx);
57
+ const role_specs = options.roles?.role_specs ?? BUILTIN_ROLE_SPECS_BY_NAME;
58
+ const grantable_roles = list_roles_with_grant_path(role_specs, GRANT_PATH_ADMIN);
59
+ const account_list_handler = async (input, ctx) => {
60
+ const accounts = await query_admin_account_list(ctx, {
61
+ limit: input.limit,
62
+ offset: input.offset,
63
+ });
61
64
  return { accounts, grantable_roles };
62
65
  };
63
66
  const session_list_handler = async (_input, ctx) => {
@@ -68,7 +71,7 @@ export const create_admin_actions = (deps, options = {}) => {
68
71
  const auth = ctx.auth;
69
72
  const account = await query_account_by_id(ctx, input.account_id);
70
73
  if (!account) {
71
- void audit_log_fire_and_forget(ctx, {
74
+ deps.audit.emit(ctx, {
72
75
  event_type: 'session_revoke_all',
73
76
  outcome: 'failure',
74
77
  account_id: auth.account.id,
@@ -81,24 +84,24 @@ export const create_admin_actions = (deps, options = {}) => {
81
84
  reason: ERROR_ACCOUNT_NOT_FOUND,
82
85
  attempted_account_id: input.account_id,
83
86
  },
84
- }, deps);
87
+ });
85
88
  throw jsonrpc_errors.not_found('account', { reason: ERROR_ACCOUNT_NOT_FOUND });
86
89
  }
87
90
  const count = await query_session_revoke_all_for_account(ctx, input.account_id);
88
- void audit_log_fire_and_forget(ctx, {
91
+ deps.audit.emit(ctx, {
89
92
  event_type: 'session_revoke_all',
90
93
  account_id: auth.account.id,
91
94
  target_account_id: input.account_id,
92
95
  ip: ctx.client_ip,
93
96
  metadata: { count },
94
- }, deps);
97
+ });
95
98
  return { ok: true, count };
96
99
  };
97
100
  const token_revoke_all_handler = async (input, ctx) => {
98
101
  const auth = ctx.auth;
99
102
  const account = await query_account_by_id(ctx, input.account_id);
100
103
  if (!account) {
101
- void audit_log_fire_and_forget(ctx, {
104
+ deps.audit.emit(ctx, {
102
105
  event_type: 'token_revoke_all',
103
106
  outcome: 'failure',
104
107
  account_id: auth.account.id,
@@ -110,17 +113,17 @@ export const create_admin_actions = (deps, options = {}) => {
110
113
  reason: ERROR_ACCOUNT_NOT_FOUND,
111
114
  attempted_account_id: input.account_id,
112
115
  },
113
- }, deps);
116
+ });
114
117
  throw jsonrpc_errors.not_found('account', { reason: ERROR_ACCOUNT_NOT_FOUND });
115
118
  }
116
119
  const count = await query_revoke_all_api_tokens_for_account(ctx, input.account_id);
117
- void audit_log_fire_and_forget(ctx, {
120
+ deps.audit.emit(ctx, {
118
121
  event_type: 'token_revoke_all',
119
122
  account_id: auth.account.id,
120
123
  target_account_id: input.account_id,
121
124
  ip: ctx.client_ip,
122
125
  metadata: { count },
123
- }, deps);
126
+ });
124
127
  return { ok: true, count };
125
128
  };
126
129
  const audit_log_list_handler = async (input, ctx) => {
@@ -134,8 +137,8 @@ export const create_admin_actions = (deps, options = {}) => {
134
137
  });
135
138
  return { events };
136
139
  };
137
- const audit_log_permit_history_handler = async (input, ctx) => {
138
- const events = await query_audit_log_list_permit_history(ctx, input.limit ?? AUDIT_LOG_DEFAULT_LIMIT, input.offset ?? 0);
140
+ const audit_log_role_grant_history_handler = async (input, ctx) => {
141
+ const events = await query_audit_log_list_role_grant_history(ctx, input.limit ?? AUDIT_LOG_DEFAULT_LIMIT, input.offset ?? 0);
139
142
  return { events };
140
143
  };
141
144
  const invite_create_handler = async (input, ctx) => {
@@ -179,12 +182,12 @@ export const create_admin_actions = (deps, options = {}) => {
179
182
  }
180
183
  throw err;
181
184
  }
182
- void audit_log_fire_and_forget(ctx, {
185
+ deps.audit.emit(ctx, {
183
186
  event_type: 'invite_create',
184
187
  account_id: auth.account.id,
185
188
  ip: ctx.client_ip,
186
189
  metadata: { invite_id: invite.id, email, username },
187
- }, deps);
190
+ });
188
191
  return { ok: true, invite };
189
192
  };
190
193
  const invite_list_handler = async (_input, ctx) => {
@@ -197,24 +200,24 @@ export const create_admin_actions = (deps, options = {}) => {
197
200
  if (!deleted) {
198
201
  throw jsonrpc_errors.not_found('invite', { reason: ERROR_INVITE_NOT_FOUND });
199
202
  }
200
- void audit_log_fire_and_forget(ctx, {
203
+ deps.audit.emit(ctx, {
201
204
  event_type: 'invite_delete',
202
205
  account_id: auth.account.id,
203
206
  ip: ctx.client_ip,
204
207
  metadata: { invite_id: input.invite_id },
205
- }, deps);
208
+ });
206
209
  return { ok: true };
207
210
  };
208
211
  const actions = [
209
- rpc_actor_action(admin_account_list_action_spec, account_list_handler),
210
- rpc_actor_action(admin_session_list_action_spec, session_list_handler),
211
- rpc_actor_action(admin_session_revoke_all_action_spec, session_revoke_all_handler),
212
- rpc_actor_action(admin_token_revoke_all_action_spec, token_revoke_all_handler),
213
- rpc_actor_action(audit_log_list_action_spec, audit_log_list_handler),
214
- rpc_actor_action(audit_log_permit_history_action_spec, audit_log_permit_history_handler),
215
- rpc_actor_action(invite_create_action_spec, invite_create_handler),
216
- rpc_actor_action(invite_list_action_spec, invite_list_handler),
217
- rpc_actor_action(invite_delete_action_spec, invite_delete_handler),
212
+ rpc_action(admin_account_list_action_spec, account_list_handler),
213
+ rpc_action(admin_session_list_action_spec, session_list_handler),
214
+ rpc_action(admin_session_revoke_all_action_spec, session_revoke_all_handler),
215
+ rpc_action(admin_token_revoke_all_action_spec, token_revoke_all_handler),
216
+ rpc_action(audit_log_list_action_spec, audit_log_list_handler),
217
+ rpc_action(audit_log_role_grant_history_action_spec, audit_log_role_grant_history_handler),
218
+ rpc_action(invite_create_action_spec, invite_create_handler),
219
+ rpc_action(invite_list_action_spec, invite_list_handler),
220
+ rpc_action(invite_delete_action_spec, invite_delete_handler),
218
221
  ];
219
222
  const { app_settings } = options;
220
223
  if (app_settings) {
@@ -231,7 +234,7 @@ export const create_admin_actions = (deps, options = {}) => {
231
234
  app_settings.open_signup = updated.open_signup;
232
235
  app_settings.updated_at = updated.updated_at;
233
236
  app_settings.updated_by = updated.updated_by;
234
- void audit_log_fire_and_forget(ctx, {
237
+ deps.audit.emit(ctx, {
235
238
  event_type: 'app_settings_update',
236
239
  account_id: auth.account.id,
237
240
  ip: ctx.client_ip,
@@ -240,11 +243,11 @@ export const create_admin_actions = (deps, options = {}) => {
240
243
  old_value,
241
244
  new_value: input.open_signup,
242
245
  },
243
- }, deps);
246
+ });
244
247
  const settings = await query_app_settings_load_with_username(ctx);
245
248
  return { ok: true, settings };
246
249
  };
247
- actions.push(rpc_actor_action(app_settings_get_action_spec, app_settings_get_handler), rpc_actor_action(app_settings_update_action_spec, app_settings_update_handler));
250
+ actions.push(rpc_action(app_settings_get_action_spec, app_settings_get_handler), rpc_action(app_settings_update_action_spec, app_settings_update_handler));
248
251
  }
249
252
  return actions;
250
253
  };
@@ -0,0 +1,160 @@
1
+ /**
2
+ * Bound audit-emit capability.
3
+ *
4
+ * `AuditEmitter` closes over the pool-level `Db`, the `on_audit_event`
5
+ * subscriber chain, and the optional `AuditLogConfig` at backend-assembly
6
+ * time. Consumers reach for `deps.audit.emit(ctx, input)` and never see the
7
+ * pool — handlers cannot accidentally emit an audit event against the
8
+ * request's transactional `db` (which would be rolled back with the parent
9
+ * on a handler throw).
10
+ *
11
+ * Four methods cover every fan-out shape the auth domain needs:
12
+ *
13
+ * - `emit(ctx, input)` — fire-and-forget pool write. Pushes the in-flight
14
+ * promise onto `ctx.pending_effects` for post-response flushing. Errors are
15
+ * logged, never thrown. Returns `void` so callers don't pile up `void`
16
+ * keywords or accidentally `await` something whose handle is already in
17
+ * `pending_effects`.
18
+ * - `emit_role_grant_target(ctx, auth, input)` — wrapper that lifts the
19
+ * `actor_id` / `account_id` / `ip` boilerplate every role-grant-shape audit
20
+ * site repeated. Delegates to `emit`.
21
+ * - `emit_pool(input)` — awaitable pool write for code paths without a
22
+ * `pending_effects` queue (cleanup sweeps, ad-hoc maintenance scripts).
23
+ * Same write-then-notify semantics as `emit`, just synchronous-with-await.
24
+ * - `notify(event)` — fan out an already-written audit row (e.g. rows
25
+ * returned by `query_accept_offer` that were inserted in-transaction by
26
+ * the query layer). Runs every listener on the chain; per-listener throws
27
+ * are isolated.
28
+ *
29
+ * The chain is mutable so server assembly can append additional listeners
30
+ * (e.g. the audit-log SSE registry composed by `create_app_server`) after
31
+ * the backend is built but before the first request runs.
32
+ *
33
+ * @module
34
+ */
35
+ import type { Logger } from '@fuzdev/fuz_util/log.js';
36
+ import type { Uuid } from '@fuzdev/fuz_util/id.js';
37
+ import type { Db } from '../db/db.js';
38
+ import type { RequestActorContext } from './request_context.js';
39
+ import { type AuditLogConfig, type AuditLogEvent, type AuditLogInput } from './audit_log_schema.js';
40
+ /**
41
+ * Per-request context required by `AuditEmitter.emit` — just the eager
42
+ * `pending_effects` queue. The bound emitter carries its own `log`
43
+ * reference inside the closure, so per-call contexts don't need one.
44
+ *
45
+ * Audit emits are eager-only by design: the bound emitter fires the
46
+ * pool write immediately and pushes the in-flight `Promise<void>` here.
47
+ * They never go through `emit_after_commit` — pool-routed audit writes
48
+ * are already rollback-resilient because they run outside the request
49
+ * transaction, so the post-commit timing the deferred queue provides
50
+ * would only delay forensic visibility without any safety benefit.
51
+ *
52
+ * Both `RouteContext` and `ActionContext` structurally satisfy this
53
+ * shape (they each carry `pending_effects`), so handlers pass `route`
54
+ * / `ctx` directly.
55
+ */
56
+ export interface AuditEmitterContext {
57
+ pending_effects: Array<Promise<void>>;
58
+ }
59
+ /**
60
+ * Context required by `AuditEmitter.emit_role_grant_target` — adds
61
+ * `client_ip` so the helper can lift the `ip: ctx.client_ip`
62
+ * boilerplate every role-grant-shape emit site repeated.
63
+ */
64
+ export interface AuditEmitRoleGrantContext extends AuditEmitterContext {
65
+ /** Resolved client IP from the trusted-proxy middleware — `'unknown'` if not resolved. */
66
+ client_ip: string;
67
+ }
68
+ /**
69
+ * Bound audit-emit capability. Built once at backend assembly via
70
+ * `create_audit_emitter`; lives on `AppDeps.audit` so factories never see
71
+ * the pool.
72
+ */
73
+ export interface AuditEmitter {
74
+ /**
75
+ * Fire-and-forget audit write via the captured pool.
76
+ *
77
+ * The in-flight promise is pushed onto `ctx.pending_effects` so tests
78
+ * with `await_pending_effects: true` can assert side effects inline.
79
+ * Errors are logged, never thrown. Successful writes fan out to every
80
+ * listener on the chain (`notify`).
81
+ *
82
+ * Returns `void` deliberately — the in-flight promise is already on
83
+ * `ctx.pending_effects`, and exposing it would tempt callers to `await`
84
+ * (sequencing audit writes onto the response hot path) or sprinkle
85
+ * `void` to placate `no-floating-promises`. For awaitable writes from
86
+ * code paths without `pending_effects`, use `emit_pool`.
87
+ *
88
+ * @mutates `audit_log` table - inserts the row via the captured pool
89
+ * @mutates `ctx.pending_effects` - appends the in-flight settled promise
90
+ */
91
+ emit<T extends string>(ctx: AuditEmitterContext, input: AuditLogInput<T>): void;
92
+ /**
93
+ * Emit a role-grant-shape audit event with `actor_id` / `account_id` /
94
+ * `ip` lifted from `auth` + `ctx`. Delegates to `emit`.
95
+ *
96
+ * Use for any event populating one of the `target_*_id` columns.
97
+ * Reach for the lower-level `emit` only when the event is non-role-grant
98
+ * shape (e.g. `app_settings_update`, bootstrap, signup).
99
+ */
100
+ emit_role_grant_target<T extends string>(ctx: AuditEmitRoleGrantContext, auth: RequestActorContext, input: {
101
+ event_type: T;
102
+ target_account_id: Uuid | null;
103
+ target_actor_id: Uuid | null;
104
+ metadata: AuditLogInput<T>['metadata'];
105
+ outcome?: 'success' | 'failure';
106
+ }): void;
107
+ /**
108
+ * Awaitable pool write for code paths without a `pending_effects` queue.
109
+ *
110
+ * Same write-then-notify semantics as `emit`. Errors are logged and
111
+ * swallowed (resolved void), so callers can sequence sweeps with
112
+ * `await audit.emit_pool(...)` without try/catch boilerplate. The
113
+ * primary user is `auth/cleanup.ts` — sweeps have no per-request
114
+ * `pending_effects` to attach to.
115
+ *
116
+ * @mutates `audit_log` table - inserts the row via the captured pool
117
+ */
118
+ emit_pool<T extends string>(input: AuditLogInput<T>): Promise<void>;
119
+ /**
120
+ * Fan out an already-written audit row to the listener chain.
121
+ *
122
+ * Use only when the row was inserted in-transaction by a query helper
123
+ * that returned the `AuditLogEvent` (e.g. `query_accept_offer.audit_events`).
124
+ * Per-listener exceptions are caught and logged; one failing listener
125
+ * does not starve siblings.
126
+ */
127
+ notify(event: AuditLogEvent): void;
128
+ /**
129
+ * Mutable subscriber chain. Append at server assembly to compose the
130
+ * factory-managed audit-log SSE on top of the consumer's
131
+ * `on_audit_event` callback without shallow-copying `AppDeps`.
132
+ */
133
+ readonly on_event_chain: Array<(event: AuditLogEvent) => void>;
134
+ }
135
+ /** Options for `create_audit_emitter`. */
136
+ export interface CreateAuditEmitterOptions {
137
+ /** Pool-level `Db`. Captured by every emit call. */
138
+ db: Db;
139
+ /** Logger for write + listener-callback failures. */
140
+ log: Logger;
141
+ /**
142
+ * Initial subscriber appended to `on_event_chain`. Omit for backends
143
+ * that compose listeners post-assembly (e.g. via `audit_log_sse`).
144
+ */
145
+ on_audit_event?: ((event: AuditLogEvent) => void) | null;
146
+ /**
147
+ * Audit-log config. Defaults to `BUILTIN_AUDIT_LOG_CONFIG`. Consumer-
148
+ * extended configs from `create_audit_log_config({extra_events})` get
149
+ * registered here once at backend assembly.
150
+ */
151
+ audit_log_config?: AuditLogConfig;
152
+ }
153
+ /**
154
+ * Build a bound `AuditEmitter`. Called once at `create_app_backend` time.
155
+ *
156
+ * @param options - pool, logger, optional initial subscriber, optional config
157
+ * @returns the bound emitter; closes over the pool + config + listener chain
158
+ */
159
+ export declare const create_audit_emitter: (options: CreateAuditEmitterOptions) => AuditEmitter;
160
+ //# sourceMappingURL=audit_emitter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit_emitter.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/audit_emitter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AACpD,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,wBAAwB,CAAC;AAEjD,OAAO,KAAK,EAAC,EAAE,EAAC,MAAM,aAAa,CAAC;AACpC,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,sBAAsB,CAAC;AAE9D,OAAO,EAEN,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,MAAM,uBAAuB,CAAC;AAE/B;;;;;;;;;;;;;;;GAeG;AACH,MAAM,WAAW,mBAAmB;IACnC,eAAe,EAAE,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;CACtC;AAED;;;;GAIG;AACH,MAAM,WAAW,yBAA0B,SAAQ,mBAAmB;IACrE,0FAA0F;IAC1F,SAAS,EAAE,MAAM,CAAC;CAClB;AAED;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC5B;;;;;;;;;;;;;;;;OAgBG;IACH,IAAI,CAAC,CAAC,SAAS,MAAM,EAAE,GAAG,EAAE,mBAAmB,EAAE,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IAChF;;;;;;;OAOG;IACH,sBAAsB,CAAC,CAAC,SAAS,MAAM,EACtC,GAAG,EAAE,yBAAyB,EAC9B,IAAI,EAAE,mBAAmB,EACzB,KAAK,EAAE;QACN,UAAU,EAAE,CAAC,CAAC;QACd,iBAAiB,EAAE,IAAI,GAAG,IAAI,CAAC;QAC/B,eAAe,EAAE,IAAI,GAAG,IAAI,CAAC;QAC7B,QAAQ,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;QACvC,OAAO,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;KAChC,GACC,IAAI,CAAC;IACR;;;;;;;;;;OAUG;IACH,SAAS,CAAC,CAAC,SAAS,MAAM,EAAE,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpE;;;;;;;OAOG;IACH,MAAM,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI,CAAC;IACnC;;;;OAIG;IACH,QAAQ,CAAC,cAAc,EAAE,KAAK,CAAC,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC,CAAC;CAC/D;AAED,0CAA0C;AAC1C,MAAM,WAAW,yBAAyB;IACzC,oDAAoD;IACpD,EAAE,EAAE,EAAE,CAAC;IACP,qDAAqD;IACrD,GAAG,EAAE,MAAM,CAAC;IACZ;;;OAGG;IACH,cAAc,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC;IACzD;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,cAAc,CAAC;CAClC;AAED;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,GAAI,SAAS,yBAAyB,KAAG,YAoDzE,CAAC"}
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Bound audit-emit capability.
3
+ *
4
+ * `AuditEmitter` closes over the pool-level `Db`, the `on_audit_event`
5
+ * subscriber chain, and the optional `AuditLogConfig` at backend-assembly
6
+ * time. Consumers reach for `deps.audit.emit(ctx, input)` and never see the
7
+ * pool — handlers cannot accidentally emit an audit event against the
8
+ * request's transactional `db` (which would be rolled back with the parent
9
+ * on a handler throw).
10
+ *
11
+ * Four methods cover every fan-out shape the auth domain needs:
12
+ *
13
+ * - `emit(ctx, input)` — fire-and-forget pool write. Pushes the in-flight
14
+ * promise onto `ctx.pending_effects` for post-response flushing. Errors are
15
+ * logged, never thrown. Returns `void` so callers don't pile up `void`
16
+ * keywords or accidentally `await` something whose handle is already in
17
+ * `pending_effects`.
18
+ * - `emit_role_grant_target(ctx, auth, input)` — wrapper that lifts the
19
+ * `actor_id` / `account_id` / `ip` boilerplate every role-grant-shape audit
20
+ * site repeated. Delegates to `emit`.
21
+ * - `emit_pool(input)` — awaitable pool write for code paths without a
22
+ * `pending_effects` queue (cleanup sweeps, ad-hoc maintenance scripts).
23
+ * Same write-then-notify semantics as `emit`, just synchronous-with-await.
24
+ * - `notify(event)` — fan out an already-written audit row (e.g. rows
25
+ * returned by `query_accept_offer` that were inserted in-transaction by
26
+ * the query layer). Runs every listener on the chain; per-listener throws
27
+ * are isolated.
28
+ *
29
+ * The chain is mutable so server assembly can append additional listeners
30
+ * (e.g. the audit-log SSE registry composed by `create_app_server`) after
31
+ * the backend is built but before the first request runs.
32
+ *
33
+ * @module
34
+ */
35
+ import { query_audit_log } from './audit_log_queries.js';
36
+ import { BUILTIN_AUDIT_LOG_CONFIG, } from './audit_log_schema.js';
37
+ /**
38
+ * Build a bound `AuditEmitter`. Called once at `create_app_backend` time.
39
+ *
40
+ * @param options - pool, logger, optional initial subscriber, optional config
41
+ * @returns the bound emitter; closes over the pool + config + listener chain
42
+ */
43
+ export const create_audit_emitter = (options) => {
44
+ const { db, log, audit_log_config = BUILTIN_AUDIT_LOG_CONFIG } = options;
45
+ const on_event_chain = [];
46
+ if (options.on_audit_event)
47
+ on_event_chain.push(options.on_audit_event);
48
+ const notify = (event) => {
49
+ for (const listener of on_event_chain) {
50
+ try {
51
+ listener(event);
52
+ }
53
+ catch (err) {
54
+ log.error('Audit log listener failed:', err);
55
+ }
56
+ }
57
+ };
58
+ const emit_pool = async (input) => {
59
+ try {
60
+ const event = await query_audit_log({ db }, input, audit_log_config);
61
+ notify(event);
62
+ }
63
+ catch (err) {
64
+ log.error('Audit log write failed:', err);
65
+ }
66
+ };
67
+ const emit = (ctx, input) => {
68
+ ctx.pending_effects.push(emit_pool(input));
69
+ };
70
+ const emit_role_grant_target = (ctx, auth, input) => {
71
+ emit(ctx, {
72
+ event_type: input.event_type,
73
+ actor_id: auth.actor.id,
74
+ account_id: auth.account.id,
75
+ outcome: input.outcome,
76
+ target_account_id: input.target_account_id,
77
+ target_actor_id: input.target_actor_id,
78
+ ip: ctx.client_ip,
79
+ metadata: input.metadata,
80
+ });
81
+ };
82
+ return { emit, emit_role_grant_target, emit_pool, notify, on_event_chain };
83
+ };
@@ -1,22 +1,18 @@
1
1
  /**
2
2
  * Audit log database queries.
3
3
  *
4
- * Records and retrieves auth mutation events for security monitoring.
5
- * All write operations should use `audit_log_fire_and_forget` to
6
- * ensure audit logging never blocks or breaks auth flows.
7
- *
8
- * Rollback resilience: `audit_log_fire_and_forget` writes to `background_db`
9
- * (pool-level), not the handler's transaction-scoped `db`, so audit entries
10
- * persist even when the request transaction rolls back.
4
+ * Records and retrieves auth mutation events for security monitoring. The
5
+ * canonical fire-and-forget entry point is `AppDeps.audit.emit(ctx, input)`
6
+ * (see `auth/audit_emitter.ts`) it closes over the pool so audit rows
7
+ * persist even when the request transaction rolls back. This module only
8
+ * exposes the in-transaction `query_*` primitives and the drift counters;
9
+ * the bound emitter writes through `query_audit_log` against its captured
10
+ * pool.
11
11
  *
12
12
  * @module
13
13
  */
14
14
  import type { QueryDeps } from '../db/query_deps.js';
15
- import type { RouteContext } from '../http/route_spec.js';
16
- import type { Uuid } from '@fuzdev/fuz_util/id.js';
17
- import type { AuditEmitDeps } from './deps.js';
18
- import type { RequestActorContext } from './request_context.js';
19
- import { type AuditLogConfig, type AuditLogEvent, type AuditLogInput, type AuditLogListOptions, type AuditLogEventWithUsernamesJson, type PermitHistoryEventJson } from './audit_log_schema.js';
15
+ import { type AuditLogConfig, type AuditLogEvent, type AuditLogInput, type AuditLogListOptions, type AuditLogEventWithUsernamesJson, type RoleGrantHistoryEventJson } from './audit_log_schema.js';
20
16
  /** Number of audit metadata validation failures observed since process start. */
21
17
  export declare const get_audit_metadata_validation_failures: () => number;
22
18
  /** Reset the counter — for tests only. */
@@ -34,6 +30,12 @@ export declare const reset_audit_unknown_event_type_failures: () => void;
34
30
  * but write the row anyway. Consumers extend the recognized set via
35
31
  * `create_audit_log_config({extra_events})`.
36
32
  *
33
+ * In-transaction call site for query helpers that must atomically write the
34
+ * row alongside other mutations (e.g. `query_accept_offer`). Fire-and-forget
35
+ * call sites should reach for `AppDeps.audit.emit` instead — that wrapper
36
+ * closes over the pool so audit rows persist when the parent transaction
37
+ * rolls back.
38
+ *
37
39
  * @param deps - query dependencies
38
40
  * @param input - the audit event to record
39
41
  * @param config - audit-log config. Defaults to `BUILTIN_AUDIT_LOG_CONFIG`.
@@ -59,22 +61,14 @@ export declare const query_audit_log_list: (deps: QueryDeps, options?: AuditLogL
59
61
  */
60
62
  export declare const query_audit_log_list_with_usernames: (deps: QueryDeps, options?: AuditLogListOptions) => Promise<Array<AuditLogEventWithUsernamesJson>>;
61
63
  /**
62
- * List audit log entries related to an account (as actor or target).
63
- *
64
- * @param deps - query dependencies
65
- * @param account_id - the account to query for
66
- * @param limit - maximum entries to return
67
- */
68
- export declare const query_audit_log_list_for_account: (deps: QueryDeps, account_id: string, limit?: number) => Promise<Array<AuditLogEvent>>;
69
- /**
70
- * List permit grant/revoke events with resolved usernames.
64
+ * List role_grant grant/revoke events with resolved usernames.
71
65
  *
72
66
  * @param deps - query dependencies
73
67
  * @param limit - maximum entries to return
74
68
  * @param offset - number of entries to skip
75
- * @returns permit history events with `username` and `target_username`
69
+ * @returns role_grant history events with `username` and `target_username`
76
70
  */
77
- export declare const query_audit_log_list_permit_history: (deps: QueryDeps, limit?: number, offset?: number) => Promise<Array<PermitHistoryEventJson>>;
71
+ export declare const query_audit_log_list_role_grant_history: (deps: QueryDeps, limit?: number, offset?: number) => Promise<Array<RoleGrantHistoryEventJson>>;
78
72
  /**
79
73
  * Delete audit log entries older than the given date.
80
74
  *
@@ -84,68 +78,4 @@ export declare const query_audit_log_list_permit_history: (deps: QueryDeps, limi
84
78
  * @mutates `audit_log` table - deletes every row with `created_at < before`
85
79
  */
86
80
  export declare const query_audit_log_cleanup_before: (deps: QueryDeps, before: Date) => Promise<number>;
87
- /**
88
- * Log an audit event without blocking the caller.
89
- *
90
- * Errors are logged — audit logging never breaks auth flows. Uses
91
- * `background_db` so entries persist even when the request transaction
92
- * rolls back. Write and `on_audit_event` callback failures are logged separately.
93
- *
94
- * `deps` is the shared `AuditEmitDeps` bundle (`log`, `on_audit_event`,
95
- * optional `audit_log_config`) so call sites pass the surrounding deps
96
- * object directly. The bundled shape replaces the prior `(log,
97
- * on_audit_event, config?)` positional args — consumers that forgot the
98
- * trailing `config` would silently fall back to `BUILTIN_AUDIT_LOG_CONFIG`
99
- * and skip metadata validation for their own event types.
100
- *
101
- * @param route - `background_db` and `pending_effects` from the route context
102
- * @param input - the audit event to record
103
- * @param deps - logger, `on_audit_event` callback, and optional `audit_log_config`
104
- * @returns the settled promise (callers may ignore it)
105
- * @mutates `audit_log` table - inserts a row via `background_db` (independent of the request transaction)
106
- * @mutates `route.pending_effects` - pushes the in-flight settled promise for test flushing
107
- */
108
- export declare const audit_log_fire_and_forget: <T extends string>(route: Pick<RouteContext, "background_db" | "pending_effects">, input: AuditLogInput<T>, deps: AuditEmitDeps) => Promise<void>;
109
- /**
110
- * Per-request context required by `emit_permit_target_event` —
111
- * `RouteContext` plus the resolved `client_ip` (lives on `ActionContext`
112
- * for RPC handlers and on the route's Hono context for REST). Declared
113
- * locally rather than reaching into `actions/action_rpc.ts` so the helper
114
- * stays usable from REST handlers that haven't promoted to RPC yet.
115
- */
116
- export type EmitPermitTargetEventContext = Pick<RouteContext, 'background_db' | 'pending_effects'> & {
117
- client_ip: string;
118
- };
119
- /**
120
- * Stamp a permit-shape audit event with both `target_account_id` (drives
121
- * SSE/WS socket-close — sessions are account-grain) and `target_actor_id`
122
- * (the actor-grain forensic field). Both target fields nullable so emit
123
- * sites without a recipient binding (e.g. `permit_revoke` on a missing
124
- * account, offer-shape events with no `to_actor_id`) can call through
125
- * uniformly.
126
- *
127
- * Lifts the six-site `{actor_id: auth.actor.id, account_id: auth.account.id,
128
- * ip: ctx.client_ip, ...}` boilerplate around `audit_log_fire_and_forget`
129
- * so callers thread auth + ctx + deps once and the event metadata once,
130
- * without re-derivable plumbing.
131
- *
132
- * Outcome defaults to `'success'`; pass `'failure'` for denial-shape
133
- * events. Other audit envelope shapes (target_*-by-actor-id-only events,
134
- * non-permit-shape events) should call `audit_log_fire_and_forget`
135
- * directly — this helper deliberately narrows to the permit-target shape.
136
- *
137
- * @param ctx - request context with `background_db`, `pending_effects`, `client_ip`
138
- * @param auth - the resolved `RequestActorContext` for the current handler — actor invariant captured in the type so the helper stops needing `auth.actor!`
139
- * @param deps - `log`, `on_audit_event`, optional `audit_log_config`
140
- * @param input - event type, target columns, metadata, optional outcome
141
- * @returns the settled promise (callers may ignore it)
142
- * @mutates `audit_log` table - inserts a row via `background_db`
143
- */
144
- export declare const emit_permit_target_event: <T extends string>(ctx: EmitPermitTargetEventContext, auth: RequestActorContext, deps: AuditEmitDeps, input: {
145
- event_type: T;
146
- target_account_id: Uuid | null;
147
- target_actor_id: Uuid | null;
148
- metadata: AuditLogInput<T>["metadata"];
149
- outcome?: "success" | "failure";
150
- }) => Promise<void>;
151
81
  //# sourceMappingURL=audit_log_queries.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"audit_log_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/audit_log_queries.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,qBAAqB,CAAC;AAEnD,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,uBAAuB,CAAC;AACxD,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,wBAAwB,CAAC;AACjD,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,WAAW,CAAC;AAC7C,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAGN,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,mBAAmB,EACxB,KAAK,8BAA8B,EACnC,KAAK,sBAAsB,EAC3B,MAAM,uBAAuB,CAAC;AAa/B,iFAAiF;AACjF,eAAO,MAAM,sCAAsC,QAAO,MACvB,CAAC;AAEpC,0CAA0C;AAC1C,eAAO,MAAM,wCAAwC,QAAO,IAE3D,CAAC;AAYF,gFAAgF;AAChF,eAAO,MAAM,qCAAqC,QAAO,MACvB,CAAC;AAEnC,0CAA0C;AAC1C,eAAO,MAAM,uCAAuC,QAAO,IAE1D,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,eAAe,GAAU,CAAC,SAAS,MAAM,EACrD,MAAM,SAAS,EACf,OAAO,aAAa,CAAC,CAAC,CAAC,EACvB,SAAQ,cAAyC,KAC/C,OAAO,CAAC,aAAa,CAoCvB,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,GAChC,MAAM,SAAS,EACf,UAAU,mBAAmB,KAC3B,OAAO,CAAC,KAAK,CAAC,aAAa,CAAC,CAwC9B,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,mCAAmC,GAC/C,MAAM,SAAS,EACf,UAAU,mBAAmB,KAC3B,OAAO,CAAC,KAAK,CAAC,8BAA8B,CAAC,CA8C/C,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,gCAAgC,GAC5C,MAAM,SAAS,EACf,YAAY,MAAM,EAClB,cAA+B,KAC7B,OAAO,CAAC,KAAK,CAAC,aAAa,CAAC,CAO9B,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,mCAAmC,GAC/C,MAAM,SAAS,EACf,cAA+B,EAC/B,eAAU,KACR,OAAO,CAAC,KAAK,CAAC,sBAAsB,CAAC,CAYvC,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,8BAA8B,GAC1C,MAAM,SAAS,EACf,QAAQ,IAAI,KACV,OAAO,CAAC,MAAM,CAMhB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,yBAAyB,GAAI,CAAC,SAAS,MAAM,EACzD,OAAO,IAAI,CAAC,YAAY,EAAE,eAAe,GAAG,iBAAiB,CAAC,EAC9D,OAAO,aAAa,CAAC,CAAC,CAAC,EACvB,MAAM,aAAa,KACjB,OAAO,CAAC,IAAI,CAed,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,MAAM,4BAA4B,GAAG,IAAI,CAC9C,YAAY,EACZ,eAAe,GAAG,iBAAiB,CACnC,GAAG;IACH,SAAS,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,wBAAwB,GAAI,CAAC,SAAS,MAAM,EACxD,KAAK,4BAA4B,EACjC,MAAM,mBAAmB,EACzB,MAAM,aAAa,EACnB,OAAO;IACN,UAAU,EAAE,CAAC,CAAC;IACd,iBAAiB,EAAE,IAAI,GAAG,IAAI,CAAC;IAC/B,eAAe,EAAE,IAAI,GAAG,IAAI,CAAC;IAC7B,QAAQ,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;IACvC,OAAO,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;CAChC,KACC,OAAO,CAAC,IAAI,CAcb,CAAC"}
1
+ {"version":3,"file":"audit_log_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/audit_log_queries.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,qBAAqB,CAAC;AAEnD,OAAO,EAGN,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,mBAAmB,EACxB,KAAK,8BAA8B,EACnC,KAAK,yBAAyB,EAC9B,MAAM,uBAAuB,CAAC;AAa/B,iFAAiF;AACjF,eAAO,MAAM,sCAAsC,QAAO,MACvB,CAAC;AAEpC,0CAA0C;AAC1C,eAAO,MAAM,wCAAwC,QAAO,IAE3D,CAAC;AAYF,gFAAgF;AAChF,eAAO,MAAM,qCAAqC,QAAO,MACvB,CAAC;AAEnC,0CAA0C;AAC1C,eAAO,MAAM,uCAAuC,QAAO,IAE1D,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,eAAe,GAAU,CAAC,SAAS,MAAM,EACrD,MAAM,SAAS,EACf,OAAO,aAAa,CAAC,CAAC,CAAC,EACvB,SAAQ,cAAyC,KAC/C,OAAO,CAAC,aAAa,CAoCvB,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,GAChC,MAAM,SAAS,EACf,UAAU,mBAAmB,KAC3B,OAAO,CAAC,KAAK,CAAC,aAAa,CAAC,CAwC9B,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,mCAAmC,GAC/C,MAAM,SAAS,EACf,UAAU,mBAAmB,KAC3B,OAAO,CAAC,KAAK,CAAC,8BAA8B,CAAC,CA8C/C,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,uCAAuC,GACnD,MAAM,SAAS,EACf,cAA+B,EAC/B,eAAU,KACR,OAAO,CAAC,KAAK,CAAC,yBAAyB,CAAC,CAY1C,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,8BAA8B,GAC1C,MAAM,SAAS,EACf,QAAQ,IAAI,KACV,OAAO,CAAC,MAAM,CAMhB,CAAC"}