@fuzdev/fuz_app 0.54.0 → 0.56.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (348) hide show
  1. package/dist/actions/CLAUDE.md +214 -103
  2. package/dist/actions/action_bridge.d.ts +8 -5
  3. package/dist/actions/action_bridge.d.ts.map +1 -1
  4. package/dist/actions/action_bridge.js +1 -11
  5. package/dist/actions/action_codegen.d.ts +32 -0
  6. package/dist/actions/action_codegen.d.ts.map +1 -1
  7. package/dist/actions/action_codegen.js +35 -15
  8. package/dist/actions/action_registry.d.ts.map +1 -1
  9. package/dist/actions/action_registry.js +5 -2
  10. package/dist/actions/action_rpc.d.ts +141 -22
  11. package/dist/actions/action_rpc.d.ts.map +1 -1
  12. package/dist/actions/action_rpc.js +106 -187
  13. package/dist/actions/action_spec.d.ts +55 -16
  14. package/dist/actions/action_spec.d.ts.map +1 -1
  15. package/dist/actions/action_spec.js +16 -11
  16. package/dist/actions/action_types.d.ts +28 -60
  17. package/dist/actions/action_types.d.ts.map +1 -1
  18. package/dist/actions/action_types.js +13 -5
  19. package/dist/actions/broadcast_api.d.ts +2 -2
  20. package/dist/actions/broadcast_api.js +2 -2
  21. package/dist/actions/compile_action_registry.d.ts +50 -0
  22. package/dist/actions/compile_action_registry.d.ts.map +1 -0
  23. package/dist/actions/compile_action_registry.js +69 -0
  24. package/dist/actions/heartbeat.d.ts +8 -4
  25. package/dist/actions/heartbeat.d.ts.map +1 -1
  26. package/dist/actions/heartbeat.js +5 -4
  27. package/dist/actions/perform_action.d.ts +145 -0
  28. package/dist/actions/perform_action.d.ts.map +1 -0
  29. package/dist/actions/perform_action.js +258 -0
  30. package/dist/actions/register_action_ws.d.ts +46 -40
  31. package/dist/actions/register_action_ws.d.ts.map +1 -1
  32. package/dist/actions/register_action_ws.js +101 -159
  33. package/dist/actions/register_ws_endpoint.d.ts +15 -10
  34. package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
  35. package/dist/actions/register_ws_endpoint.js +54 -7
  36. package/dist/actions/transports.d.ts.map +1 -1
  37. package/dist/actions/transports.js +0 -4
  38. package/dist/actions/transports_ws_auth_guard.d.ts +1 -1
  39. package/dist/actions/transports_ws_auth_guard.js +1 -1
  40. package/dist/actions/transports_ws_backend.d.ts +1 -1
  41. package/dist/actions/transports_ws_backend.js +1 -1
  42. package/dist/auth/CLAUDE.md +794 -410
  43. package/dist/auth/account_action_specs.d.ts +28 -7
  44. package/dist/auth/account_action_specs.d.ts.map +1 -1
  45. package/dist/auth/account_action_specs.js +7 -7
  46. package/dist/auth/account_actions.d.ts +7 -13
  47. package/dist/auth/account_actions.d.ts.map +1 -1
  48. package/dist/auth/account_actions.js +26 -35
  49. package/dist/auth/account_queries.d.ts +52 -16
  50. package/dist/auth/account_queries.d.ts.map +1 -1
  51. package/dist/auth/account_queries.js +87 -38
  52. package/dist/auth/account_routes.d.ts +9 -11
  53. package/dist/auth/account_routes.d.ts.map +1 -1
  54. package/dist/auth/account_routes.js +118 -46
  55. package/dist/auth/account_schema.d.ts +46 -35
  56. package/dist/auth/account_schema.d.ts.map +1 -1
  57. package/dist/auth/account_schema.js +21 -28
  58. package/dist/auth/admin_action_specs.d.ts +100 -32
  59. package/dist/auth/admin_action_specs.d.ts.map +1 -1
  60. package/dist/auth/admin_action_specs.js +64 -33
  61. package/dist/auth/admin_actions.d.ts +13 -19
  62. package/dist/auth/admin_actions.d.ts.map +1 -1
  63. package/dist/auth/admin_actions.js +37 -41
  64. package/dist/auth/audit_emitter.d.ts +160 -0
  65. package/dist/auth/audit_emitter.d.ts.map +1 -0
  66. package/dist/auth/audit_emitter.js +83 -0
  67. package/dist/auth/audit_log_queries.d.ts +17 -48
  68. package/dist/auth/audit_log_queries.d.ts.map +1 -1
  69. package/dist/auth/audit_log_queries.js +20 -56
  70. package/dist/auth/audit_log_routes.d.ts +1 -1
  71. package/dist/auth/audit_log_routes.d.ts.map +1 -1
  72. package/dist/auth/audit_log_routes.js +7 -3
  73. package/dist/auth/audit_log_schema.d.ts +92 -32
  74. package/dist/auth/audit_log_schema.d.ts.map +1 -1
  75. package/dist/auth/audit_log_schema.js +75 -46
  76. package/dist/auth/auth_guard_resolver.d.ts +44 -0
  77. package/dist/auth/auth_guard_resolver.d.ts.map +1 -0
  78. package/dist/auth/auth_guard_resolver.js +56 -0
  79. package/dist/auth/bearer_auth.d.ts +9 -7
  80. package/dist/auth/bearer_auth.d.ts.map +1 -1
  81. package/dist/auth/bearer_auth.js +13 -21
  82. package/dist/auth/bootstrap_account.d.ts +7 -7
  83. package/dist/auth/bootstrap_account.d.ts.map +1 -1
  84. package/dist/auth/bootstrap_account.js +7 -7
  85. package/dist/auth/bootstrap_routes.d.ts.map +1 -1
  86. package/dist/auth/bootstrap_routes.js +11 -10
  87. package/dist/auth/cleanup.d.ts +20 -26
  88. package/dist/auth/cleanup.d.ts.map +1 -1
  89. package/dist/auth/cleanup.js +33 -42
  90. package/dist/auth/credential_type_schema.d.ts +115 -0
  91. package/dist/auth/credential_type_schema.d.ts.map +1 -0
  92. package/dist/auth/credential_type_schema.js +127 -0
  93. package/dist/auth/daemon_token_middleware.d.ts +23 -11
  94. package/dist/auth/daemon_token_middleware.d.ts.map +1 -1
  95. package/dist/auth/daemon_token_middleware.js +28 -22
  96. package/dist/auth/ddl.d.ts +2 -2
  97. package/dist/auth/ddl.d.ts.map +1 -1
  98. package/dist/auth/ddl.js +6 -6
  99. package/dist/auth/deps.d.ts +7 -18
  100. package/dist/auth/deps.d.ts.map +1 -1
  101. package/dist/auth/grant_path_schema.d.ts +117 -0
  102. package/dist/auth/grant_path_schema.d.ts.map +1 -0
  103. package/dist/auth/grant_path_schema.js +137 -0
  104. package/dist/auth/invite_queries.d.ts +12 -1
  105. package/dist/auth/invite_queries.d.ts.map +1 -1
  106. package/dist/auth/invite_queries.js +12 -1
  107. package/dist/auth/invite_schema.d.ts +1 -1
  108. package/dist/auth/invite_schema.d.ts.map +1 -1
  109. package/dist/auth/invite_schema.js +1 -1
  110. package/dist/auth/middleware.d.ts.map +1 -1
  111. package/dist/auth/middleware.js +9 -4
  112. package/dist/auth/migrations.d.ts +37 -14
  113. package/dist/auth/migrations.d.ts.map +1 -1
  114. package/dist/auth/migrations.js +79 -32
  115. package/dist/auth/request_context.d.ts +331 -61
  116. package/dist/auth/request_context.d.ts.map +1 -1
  117. package/dist/auth/request_context.js +378 -95
  118. package/dist/auth/{permit_offer_action_specs.d.ts → role_grant_offer_action_specs.d.ts} +163 -94
  119. package/dist/auth/role_grant_offer_action_specs.d.ts.map +1 -0
  120. package/dist/auth/role_grant_offer_action_specs.js +262 -0
  121. package/dist/auth/role_grant_offer_actions.d.ts +104 -0
  122. package/dist/auth/role_grant_offer_actions.d.ts.map +1 -0
  123. package/dist/auth/role_grant_offer_actions.js +473 -0
  124. package/dist/auth/{permit_offer_notifications.d.ts → role_grant_offer_notifications.d.ts} +90 -70
  125. package/dist/auth/role_grant_offer_notifications.d.ts.map +1 -0
  126. package/dist/auth/role_grant_offer_notifications.js +182 -0
  127. package/dist/auth/role_grant_offer_queries.d.ts +242 -0
  128. package/dist/auth/role_grant_offer_queries.d.ts.map +1 -0
  129. package/dist/auth/role_grant_offer_queries.js +533 -0
  130. package/dist/auth/role_grant_offer_schema.d.ts +150 -0
  131. package/dist/auth/role_grant_offer_schema.d.ts.map +1 -0
  132. package/dist/auth/{permit_offer_schema.js → role_grant_offer_schema.js} +60 -36
  133. package/dist/auth/role_grant_queries.d.ts +231 -0
  134. package/dist/auth/role_grant_queries.d.ts.map +1 -0
  135. package/dist/auth/role_grant_queries.js +320 -0
  136. package/dist/auth/role_schema.d.ts +150 -40
  137. package/dist/auth/role_schema.d.ts.map +1 -1
  138. package/dist/auth/role_schema.js +144 -45
  139. package/dist/auth/scope_kind_schema.d.ts +96 -0
  140. package/dist/auth/scope_kind_schema.d.ts.map +1 -0
  141. package/dist/auth/scope_kind_schema.js +94 -0
  142. package/dist/auth/self_service_role_action_specs.d.ts +6 -1
  143. package/dist/auth/self_service_role_action_specs.d.ts.map +1 -1
  144. package/dist/auth/self_service_role_action_specs.js +3 -1
  145. package/dist/auth/self_service_role_actions.d.ts +34 -27
  146. package/dist/auth/self_service_role_actions.d.ts.map +1 -1
  147. package/dist/auth/self_service_role_actions.js +68 -48
  148. package/dist/auth/session_cookie.d.ts +43 -6
  149. package/dist/auth/session_cookie.d.ts.map +1 -1
  150. package/dist/auth/session_cookie.js +31 -5
  151. package/dist/auth/session_middleware.d.ts +37 -3
  152. package/dist/auth/session_middleware.d.ts.map +1 -1
  153. package/dist/auth/session_middleware.js +33 -7
  154. package/dist/auth/signup_routes.d.ts.map +1 -1
  155. package/dist/auth/signup_routes.js +48 -19
  156. package/dist/auth/standard_action_specs.d.ts +2 -2
  157. package/dist/auth/standard_action_specs.js +4 -4
  158. package/dist/auth/standard_rpc_actions.d.ts +23 -19
  159. package/dist/auth/standard_rpc_actions.d.ts.map +1 -1
  160. package/dist/auth/standard_rpc_actions.js +12 -12
  161. package/dist/db/migrate.d.ts +12 -8
  162. package/dist/db/migrate.d.ts.map +1 -1
  163. package/dist/db/migrate.js +10 -7
  164. package/dist/dev/setup.d.ts +2 -2
  165. package/dist/dev/setup.d.ts.map +1 -1
  166. package/dist/dev/setup.js +9 -7
  167. package/dist/env/load.d.ts +1 -1
  168. package/dist/env/load.js +1 -1
  169. package/dist/hono_context.d.ts +64 -5
  170. package/dist/hono_context.d.ts.map +1 -1
  171. package/dist/hono_context.js +38 -2
  172. package/dist/http/CLAUDE.md +264 -87
  173. package/dist/http/auth_shape.d.ts +191 -0
  174. package/dist/http/auth_shape.d.ts.map +1 -0
  175. package/dist/http/auth_shape.js +237 -0
  176. package/dist/http/common_routes.js +3 -3
  177. package/dist/http/db_routes.d.ts +4 -0
  178. package/dist/http/db_routes.d.ts.map +1 -1
  179. package/dist/http/db_routes.js +44 -7
  180. package/dist/http/error_schemas.d.ts +132 -19
  181. package/dist/http/error_schemas.d.ts.map +1 -1
  182. package/dist/http/error_schemas.js +132 -40
  183. package/dist/http/jsonrpc_errors.d.ts +27 -2
  184. package/dist/http/jsonrpc_errors.d.ts.map +1 -1
  185. package/dist/http/jsonrpc_errors.js +26 -2
  186. package/dist/http/pending_effects.d.ts +71 -18
  187. package/dist/http/pending_effects.d.ts.map +1 -1
  188. package/dist/http/pending_effects.js +87 -18
  189. package/dist/http/proxy.d.ts +52 -5
  190. package/dist/http/proxy.d.ts.map +1 -1
  191. package/dist/http/proxy.js +92 -14
  192. package/dist/http/route_spec.d.ts +113 -41
  193. package/dist/http/route_spec.d.ts.map +1 -1
  194. package/dist/http/route_spec.js +130 -52
  195. package/dist/http/schema_helpers.d.ts +3 -2
  196. package/dist/http/schema_helpers.d.ts.map +1 -1
  197. package/dist/http/schema_helpers.js +9 -2
  198. package/dist/http/surface.d.ts +2 -1
  199. package/dist/http/surface.d.ts.map +1 -1
  200. package/dist/http/surface.js +1 -2
  201. package/dist/http/surface_query.d.ts +39 -35
  202. package/dist/http/surface_query.d.ts.map +1 -1
  203. package/dist/http/surface_query.js +79 -36
  204. package/dist/primitive_schemas.d.ts +39 -0
  205. package/dist/primitive_schemas.d.ts.map +1 -0
  206. package/dist/primitive_schemas.js +40 -0
  207. package/dist/realtime/sse_auth_guard.d.ts +5 -5
  208. package/dist/realtime/sse_auth_guard.js +9 -9
  209. package/dist/runtime/mock.d.ts +1 -1
  210. package/dist/runtime/mock.js +1 -1
  211. package/dist/server/app_backend.d.ts +14 -11
  212. package/dist/server/app_backend.d.ts.map +1 -1
  213. package/dist/server/app_backend.js +12 -8
  214. package/dist/server/app_server.d.ts +7 -7
  215. package/dist/server/app_server.d.ts.map +1 -1
  216. package/dist/server/app_server.js +36 -31
  217. package/dist/server/validate_nginx.d.ts +1 -1
  218. package/dist/server/validate_nginx.js +1 -1
  219. package/dist/testing/CLAUDE.md +73 -55
  220. package/dist/testing/admin_integration.d.ts +5 -6
  221. package/dist/testing/admin_integration.d.ts.map +1 -1
  222. package/dist/testing/admin_integration.js +100 -96
  223. package/dist/testing/adversarial_headers.js +1 -1
  224. package/dist/testing/app_server.d.ts +11 -14
  225. package/dist/testing/app_server.d.ts.map +1 -1
  226. package/dist/testing/app_server.js +18 -17
  227. package/dist/testing/assertions.d.ts.map +1 -1
  228. package/dist/testing/assertions.js +2 -1
  229. package/dist/testing/attack_surface.d.ts.map +1 -1
  230. package/dist/testing/attack_surface.js +15 -9
  231. package/dist/testing/audit_completeness.d.ts +2 -2
  232. package/dist/testing/audit_completeness.d.ts.map +1 -1
  233. package/dist/testing/audit_completeness.js +53 -39
  234. package/dist/testing/auth_apps.d.ts +5 -4
  235. package/dist/testing/auth_apps.d.ts.map +1 -1
  236. package/dist/testing/auth_apps.js +28 -22
  237. package/dist/testing/data_exposure.d.ts.map +1 -1
  238. package/dist/testing/data_exposure.js +5 -5
  239. package/dist/testing/db.d.ts +1 -1
  240. package/dist/testing/db.d.ts.map +1 -1
  241. package/dist/testing/db.js +4 -4
  242. package/dist/testing/db_entities.d.ts +22 -0
  243. package/dist/testing/db_entities.d.ts.map +1 -0
  244. package/dist/testing/db_entities.js +28 -0
  245. package/dist/testing/entities.d.ts +10 -8
  246. package/dist/testing/entities.d.ts.map +1 -1
  247. package/dist/testing/entities.js +22 -18
  248. package/dist/testing/integration.d.ts.map +1 -1
  249. package/dist/testing/integration.js +13 -14
  250. package/dist/testing/integration_helpers.d.ts +8 -6
  251. package/dist/testing/integration_helpers.d.ts.map +1 -1
  252. package/dist/testing/integration_helpers.js +29 -23
  253. package/dist/testing/middleware.d.ts +15 -11
  254. package/dist/testing/middleware.d.ts.map +1 -1
  255. package/dist/testing/middleware.js +75 -32
  256. package/dist/testing/rpc_attack_surface.d.ts.map +1 -1
  257. package/dist/testing/rpc_attack_surface.js +40 -24
  258. package/dist/testing/rpc_helpers.d.ts.map +1 -1
  259. package/dist/testing/rpc_helpers.js +3 -1
  260. package/dist/testing/rpc_round_trip.d.ts +1 -1
  261. package/dist/testing/rpc_round_trip.d.ts.map +1 -1
  262. package/dist/testing/rpc_round_trip.js +14 -13
  263. package/dist/testing/sse_round_trip.d.ts +3 -4
  264. package/dist/testing/sse_round_trip.d.ts.map +1 -1
  265. package/dist/testing/sse_round_trip.js +7 -11
  266. package/dist/testing/standard.d.ts +1 -1
  267. package/dist/testing/stubs.d.ts +25 -0
  268. package/dist/testing/stubs.d.ts.map +1 -1
  269. package/dist/testing/stubs.js +43 -2
  270. package/dist/testing/surface_invariants.d.ts +2 -2
  271. package/dist/testing/ws_round_trip.d.ts +12 -13
  272. package/dist/testing/ws_round_trip.d.ts.map +1 -1
  273. package/dist/testing/ws_round_trip.js +24 -12
  274. package/dist/ui/AdminAccounts.svelte +23 -20
  275. package/dist/ui/AdminOverview.svelte +15 -13
  276. package/dist/ui/AdminOverview.svelte.d.ts.map +1 -1
  277. package/dist/ui/{AdminPermitHistory.svelte → AdminRoleGrantHistory.svelte} +12 -12
  278. package/dist/ui/AdminRoleGrantHistory.svelte.d.ts +4 -0
  279. package/dist/ui/AdminRoleGrantHistory.svelte.d.ts.map +1 -0
  280. package/dist/ui/BootstrapForm.svelte +1 -1
  281. package/dist/ui/CLAUDE.md +65 -59
  282. package/dist/ui/{PermitOfferForm.svelte → RoleGrantOfferForm.svelte} +37 -22
  283. package/dist/ui/RoleGrantOfferForm.svelte.d.ts +20 -0
  284. package/dist/ui/RoleGrantOfferForm.svelte.d.ts.map +1 -0
  285. package/dist/ui/{PermitOfferHistory.svelte → RoleGrantOfferHistory.svelte} +12 -12
  286. package/dist/ui/{PermitOfferHistory.svelte.d.ts → RoleGrantOfferHistory.svelte.d.ts} +4 -4
  287. package/dist/ui/RoleGrantOfferHistory.svelte.d.ts.map +1 -0
  288. package/dist/ui/{PermitOfferInbox.svelte → RoleGrantOfferInbox.svelte} +14 -14
  289. package/dist/ui/{PermitOfferInbox.svelte.d.ts → RoleGrantOfferInbox.svelte.d.ts} +4 -4
  290. package/dist/ui/RoleGrantOfferInbox.svelte.d.ts.map +1 -0
  291. package/dist/ui/SignupForm.svelte +1 -1
  292. package/dist/ui/SurfaceExplorer.svelte +35 -15
  293. package/dist/ui/SurfaceExplorer.svelte.d.ts.map +1 -1
  294. package/dist/ui/account_sessions_state.svelte.d.ts +2 -3
  295. package/dist/ui/account_sessions_state.svelte.d.ts.map +1 -1
  296. package/dist/ui/account_sessions_state.svelte.js +2 -3
  297. package/dist/ui/admin_accounts_state.svelte.d.ts +25 -18
  298. package/dist/ui/admin_accounts_state.svelte.d.ts.map +1 -1
  299. package/dist/ui/admin_accounts_state.svelte.js +28 -17
  300. package/dist/ui/admin_rpc_adapters.d.ts +20 -20
  301. package/dist/ui/admin_rpc_adapters.d.ts.map +1 -1
  302. package/dist/ui/admin_rpc_adapters.js +17 -17
  303. package/dist/ui/admin_sessions_state.svelte.d.ts +2 -2
  304. package/dist/ui/admin_sessions_state.svelte.js +2 -2
  305. package/dist/ui/audit_log_state.svelte.d.ts +7 -7
  306. package/dist/ui/audit_log_state.svelte.d.ts.map +1 -1
  307. package/dist/ui/audit_log_state.svelte.js +6 -6
  308. package/dist/ui/auth_state.svelte.d.ts +3 -3
  309. package/dist/ui/auth_state.svelte.d.ts.map +1 -1
  310. package/dist/ui/auth_state.svelte.js +6 -6
  311. package/dist/ui/format_scope.d.ts +2 -2
  312. package/dist/ui/format_scope.js +2 -2
  313. package/dist/ui/{permit_offers_state.svelte.d.ts → role_grant_offers_state.svelte.d.ts} +39 -31
  314. package/dist/ui/role_grant_offers_state.svelte.d.ts.map +1 -0
  315. package/dist/ui/{permit_offers_state.svelte.js → role_grant_offers_state.svelte.js} +25 -19
  316. package/dist/ui/ui_format.js +2 -2
  317. package/package.json +3 -3
  318. package/dist/auth/permit_offer_action_specs.d.ts.map +0 -1
  319. package/dist/auth/permit_offer_action_specs.js +0 -227
  320. package/dist/auth/permit_offer_actions.d.ts +0 -110
  321. package/dist/auth/permit_offer_actions.d.ts.map +0 -1
  322. package/dist/auth/permit_offer_actions.js +0 -452
  323. package/dist/auth/permit_offer_notifications.d.ts.map +0 -1
  324. package/dist/auth/permit_offer_notifications.js +0 -182
  325. package/dist/auth/permit_offer_queries.d.ts +0 -183
  326. package/dist/auth/permit_offer_queries.d.ts.map +0 -1
  327. package/dist/auth/permit_offer_queries.js +0 -408
  328. package/dist/auth/permit_offer_schema.d.ts +0 -103
  329. package/dist/auth/permit_offer_schema.d.ts.map +0 -1
  330. package/dist/auth/permit_queries.d.ts +0 -210
  331. package/dist/auth/permit_queries.d.ts.map +0 -1
  332. package/dist/auth/permit_queries.js +0 -294
  333. package/dist/auth/require_keeper.d.ts +0 -20
  334. package/dist/auth/require_keeper.d.ts.map +0 -1
  335. package/dist/auth/require_keeper.js +0 -35
  336. package/dist/auth/route_guards.d.ts +0 -21
  337. package/dist/auth/route_guards.d.ts.map +0 -1
  338. package/dist/auth/route_guards.js +0 -32
  339. package/dist/auth/session_lifecycle.d.ts +0 -37
  340. package/dist/auth/session_lifecycle.d.ts.map +0 -1
  341. package/dist/auth/session_lifecycle.js +0 -29
  342. package/dist/ui/AdminPermitHistory.svelte.d.ts +0 -4
  343. package/dist/ui/AdminPermitHistory.svelte.d.ts.map +0 -1
  344. package/dist/ui/PermitOfferForm.svelte.d.ts +0 -14
  345. package/dist/ui/PermitOfferForm.svelte.d.ts.map +0 -1
  346. package/dist/ui/PermitOfferHistory.svelte.d.ts.map +0 -1
  347. package/dist/ui/PermitOfferInbox.svelte.d.ts.map +0 -1
  348. package/dist/ui/permit_offers_state.svelte.d.ts.map +0 -1
@@ -0,0 +1,473 @@
1
+ /**
2
+ * Role grant offer RPC action handlers — the consentful-role-grants action surface.
3
+ *
4
+ * Seven actions: six offer-lifecycle methods (create / accept / decline /
5
+ * retract / list / history) plus `role_grant_revoke` (admin-only). All mount
6
+ * on a consumer's JSON-RPC endpoint via `create_rpc_endpoint`. The action
7
+ * specs themselves live in `auth/role_grant_offer_action_specs.ts`. Mutations
8
+ * declare `side_effects: true` so the RPC dispatcher wraps the handler in
9
+ * a DB transaction; `role_grant_offer_list` and `role_grant_offer_history` declare
10
+ * `side_effects: false` so they are addressable via GET.
11
+ *
12
+ * Authorization:
13
+ * - `role_grant_offer_create` — the grantor must hold an active role_grant for the
14
+ * role being offered, and that role's `grant_paths` must include `'admin'`.
15
+ * Consumers needing a richer policy (e.g., "teacher may offer student in
16
+ * *their* classroom") pass an `authorize` callback that overrides the default.
17
+ * - `role_grant_offer_accept` / `role_grant_offer_decline` — keyed to the caller's
18
+ * account; `query_*` helpers enforce the IDOR guard.
19
+ * - `role_grant_offer_retract` — keyed to the caller's actor.
20
+ * - `role_grant_offer_list` / `role_grant_offer_history` — self by default;
21
+ * `{account_id}` is admin-only.
22
+ * - `role_grant_revoke` — spec-level `auth: {role: 'admin'}`; the RPC
23
+ * dispatcher rejects non-admin callers before the handler runs.
24
+ * The admin-grant-path gate prevents revoking keeper / daemon-scoped
25
+ * roles via this surface. Keys on `actor_id` to survive multi-actor accounts.
26
+ *
27
+ * Audit events are emitted in-transaction by the query layer (atomic with
28
+ * the role_grant write on accept/revoke) or by the handler via the bound
29
+ * `deps.audit.emit_role_grant_target` helper for single-event lifecycle
30
+ * transitions. `audit.notify` (SSE/WS broadcast) fires post-commit in both
31
+ * paths.
32
+ *
33
+ * WS notifications fan out post-commit via `emit_after_commit` when a
34
+ * `notification_sender` is wired: offer lifecycle transitions notify the
35
+ * counterparty, `role_grant_revoke` notifies the revokee plus each superseded
36
+ * pending offer's grantor.
37
+ *
38
+ * @module
39
+ */
40
+ import { rpc_action, } from '../actions/action_rpc.js';
41
+ import { jsonrpc_errors } from '../http/jsonrpc_errors.js';
42
+ import { emit_after_commit } from '../http/pending_effects.js';
43
+ import { BUILTIN_ROLE_SPECS_BY_NAME, ROLE_ADMIN, role_has_grant_path, } from './role_schema.js';
44
+ import { GRANT_PATH_ADMIN } from './grant_path_schema.js';
45
+ import { ROLE_GRANT_OFFER_DEFAULT_TTL_MS, to_role_grant_offer_json, } from './role_grant_offer_schema.js';
46
+ import { query_role_grant_offer_create, query_role_grant_offer_decline, query_role_grant_offer_retract, query_role_grant_offer_list, query_role_grant_offer_history_for_account, query_accept_offer, RoleGrantOfferActorAccountMismatchError, RoleGrantOfferActorMismatchError, RoleGrantOfferAlreadyTerminalError, RoleGrantOfferExpiredError, RoleGrantOfferNotFoundError, RoleGrantOfferSelfTargetError, } from './role_grant_offer_queries.js';
47
+ import { query_role_grant_find_active_role_for_actor, query_revoke_role_grant, } from './role_grant_queries.js';
48
+ import { query_actor_by_id } from './account_queries.js';
49
+ import { has_scoped_role } from './request_context.js';
50
+ import { build_role_grant_offer_accepted_notification, build_role_grant_offer_declined_notification, build_role_grant_offer_received_notification, build_role_grant_offer_retracted_notification, build_role_grant_offer_supersede_notification, build_role_grant_revoke_notification, } from './role_grant_offer_notifications.js';
51
+ import { ERROR_ROLE_GRANT_NOT_FOUND, ERROR_ROLE_NOT_WEB_GRANTABLE } from '../http/error_schemas.js';
52
+ import { ERROR_ROLE_GRANT_OFFER_ACTOR_ACCOUNT_MISMATCH, ERROR_ROLE_GRANT_OFFER_ACTOR_MISMATCH, ERROR_ROLE_GRANT_OFFER_EXPIRED, ERROR_ROLE_GRANT_OFFER_NOT_AUTHORIZED, ERROR_ROLE_GRANT_OFFER_NOT_FOUND, ERROR_ROLE_GRANT_OFFER_ROLE_NOT_GRANTABLE, ERROR_ROLE_GRANT_OFFER_SELF_TARGET, ERROR_ROLE_GRANT_OFFER_TERMINAL, role_grant_offer_create_action_spec, role_grant_offer_accept_action_spec, role_grant_offer_decline_action_spec, role_grant_offer_retract_action_spec, role_grant_offer_list_action_spec, role_grant_offer_history_action_spec, role_grant_revoke_action_spec, } from './role_grant_offer_action_specs.js';
53
+ // -- Helpers ----------------------------------------------------------------
54
+ /**
55
+ * Fan out a batch of pre-written audit rows to the bound emitter's
56
+ * `notify` listener chain. Used by accept, whose events were written
57
+ * in-transaction by `query_accept_offer` — the rows are already in the DB,
58
+ * we just need SSE/WS subscribers to see them.
59
+ *
60
+ * Per-listener exceptions are isolated inside `audit.notify`; one failing
61
+ * subscriber does not starve siblings, and a failure on the first event
62
+ * does not skip the rest.
63
+ */
64
+ const fan_out_audit_events = (events, audit) => {
65
+ for (const event of events) {
66
+ audit.notify(event);
67
+ }
68
+ };
69
+ // eslint-disable-next-line @typescript-eslint/require-await
70
+ const default_authorize = async (auth, input, _deps, _ctx) => {
71
+ // Caller must hold an active role_grant for the offered role. Global (no scope)
72
+ // check — the scope-aware "only in this classroom" policy is consumer-level.
73
+ // Reads from the in-memory `auth.role_grants` snapshot loaded once per request
74
+ // by `create_request_context_middleware`; no DB roundtrip needed.
75
+ return has_scoped_role(auth, input.role, null);
76
+ };
77
+ /**
78
+ * Authorization callback that admits any admin and otherwise falls back to
79
+ * the symmetric default (caller must hold the offered role globally).
80
+ *
81
+ * The admin-grant-path filter in `create_handler` runs **before** the
82
+ * `authorize` callback, so this never sees roles whose `grant_paths`
83
+ * omits `'admin'`. Drop into
84
+ * `create_role_grant_offer_actions({authorize: authorize_admin_or_holder})`
85
+ * (or any factory that forwards `authorize`, e.g. `create_standard_rpc_actions`)
86
+ * for the common "admins offer anything; users offer what they hold"
87
+ * pattern. Scope-aware policies (e.g. classroom_teacher offering
88
+ * classroom_student in their own scope) wrap this and short-circuit `true`
89
+ * before delegating.
90
+ */
91
+ export const authorize_admin_or_holder = async (auth, input, _deps, _ctx) => {
92
+ // Admin bypass keys on **global** admin role_grants only — `has_scoped_role(_, _, null)`
93
+ // rejects scoped admin role_grants. Without this, a `{role: 'admin', scope_id: scope_X}`
94
+ // role_grant would let the holder offer any admin-grant-path role without holding it
95
+ // themselves, escalating scoped admin to global authority over the offer surface.
96
+ if (has_scoped_role(auth, ROLE_ADMIN, null))
97
+ return true;
98
+ return has_scoped_role(auth, input.role, null);
99
+ };
100
+ // -- Action factory ---------------------------------------------------------
101
+ /**
102
+ * Create the seven role-grant-offer RPC actions (six offer-lifecycle methods
103
+ * plus `role_grant_revoke`).
104
+ *
105
+ * @param deps - `RouteFactoryDeps` (`log`, `audit`, …) plus optional `notification_sender` for WS fan-out — when absent, WS fan-out is silently skipped (DB-only side effects still happen). Consumers wiring `BackendWebsocketTransport` assign its instance directly (the transport's `send_to_account` signature accepts the broader `JsonrpcMessageFromServerToClient`, which is contravariantly compatible)
106
+ * @param options - role schema, default TTL, authorization override
107
+ * @returns the `RpcAction` array to spread into a `create_rpc_endpoint` call
108
+ */
109
+ export const create_role_grant_offer_actions = (deps, options = {}) => {
110
+ const { log, audit, notification_sender = null } = deps;
111
+ const role_specs = options.roles?.role_specs ?? BUILTIN_ROLE_SPECS_BY_NAME;
112
+ const default_ttl_ms = options.default_ttl_ms ?? ROLE_GRANT_OFFER_DEFAULT_TTL_MS;
113
+ const authorize = options.authorize ?? default_authorize;
114
+ // Four denial paths (admin-grant-path gate, authorize, self-target,
115
+ // actor-account mismatch) all emit the same failure-outcome audit
116
+ // event. `target_actor_id` is populated when the caller supplied a
117
+ // `to_actor_id` so failure rows match the success-shape envelope of
118
+ // actor-targeted offers.
119
+ const emit_create_failure_audit = (ctx, auth, input) => {
120
+ audit.emit_role_grant_target(ctx, auth, {
121
+ event_type: 'role_grant_offer_create',
122
+ outcome: 'failure',
123
+ target_account_id: input.to_account_id,
124
+ target_actor_id: input.to_actor_id ?? null,
125
+ metadata: {
126
+ role: input.role,
127
+ scope_id: input.scope_id ?? null,
128
+ to_account_id: input.to_account_id,
129
+ },
130
+ });
131
+ };
132
+ // Returns {offer} only — no auto-accept. Recipient must call
133
+ // role_grant_offer_accept; admin tests materialize role_grants via
134
+ // query_accept_offer (see testing/admin_integration.ts `offer_and_accept`).
135
+ const create_handler = async (input, ctx) => {
136
+ const auth = ctx.auth;
137
+ // Role must include the admin grant path — same gate as admin direct-grant.
138
+ if (!role_has_grant_path(role_specs, input.role, GRANT_PATH_ADMIN)) {
139
+ emit_create_failure_audit(ctx, auth, input);
140
+ throw jsonrpc_errors.forbidden('role not grantable', {
141
+ reason: ERROR_ROLE_GRANT_OFFER_ROLE_NOT_GRANTABLE,
142
+ });
143
+ }
144
+ const authorized = await authorize(auth, {
145
+ to_account_id: input.to_account_id,
146
+ role: input.role,
147
+ scope_id: input.scope_id ?? null,
148
+ }, { log }, ctx);
149
+ if (!authorized) {
150
+ emit_create_failure_audit(ctx, auth, input);
151
+ throw jsonrpc_errors.forbidden('not authorized to offer this role', {
152
+ reason: ERROR_ROLE_GRANT_OFFER_NOT_AUTHORIZED,
153
+ });
154
+ }
155
+ let offer;
156
+ try {
157
+ offer = await query_role_grant_offer_create(ctx, {
158
+ from_actor_id: auth.actor.id,
159
+ to_account_id: input.to_account_id,
160
+ to_actor_id: input.to_actor_id ?? null,
161
+ role: input.role,
162
+ scope_kind: input.scope_kind ?? null,
163
+ scope_id: input.scope_id ?? null,
164
+ message: input.message ?? null,
165
+ expires_at: new Date(Date.now() + default_ttl_ms),
166
+ });
167
+ }
168
+ catch (err) {
169
+ if (err instanceof RoleGrantOfferSelfTargetError) {
170
+ emit_create_failure_audit(ctx, auth, input);
171
+ throw jsonrpc_errors.invalid_params('cannot offer to self', {
172
+ reason: ERROR_ROLE_GRANT_OFFER_SELF_TARGET,
173
+ });
174
+ }
175
+ if (err instanceof RoleGrantOfferActorAccountMismatchError) {
176
+ emit_create_failure_audit(ctx, auth, input);
177
+ throw jsonrpc_errors.invalid_params('to_actor_id does not belong to to_account_id', {
178
+ reason: ERROR_ROLE_GRANT_OFFER_ACTOR_ACCOUNT_MISMATCH,
179
+ });
180
+ }
181
+ throw err;
182
+ }
183
+ // `target_actor_id` is populated when the offer is actor-targeted
184
+ // (per the offer's `to_actor_id`), null for account-grain offers
185
+ // — closes the audit hole where offer-shape events used to leave
186
+ // actor-grain forensics blank even when the binding was known.
187
+ audit.emit_role_grant_target(ctx, auth, {
188
+ event_type: 'role_grant_offer_create',
189
+ target_account_id: input.to_account_id,
190
+ target_actor_id: offer.to_actor_id,
191
+ metadata: {
192
+ offer_id: offer.id,
193
+ role: offer.role,
194
+ scope_id: offer.scope_id,
195
+ to_account_id: offer.to_account_id,
196
+ },
197
+ });
198
+ const offer_json = to_role_grant_offer_json(offer);
199
+ if (notification_sender) {
200
+ emit_after_commit(ctx, () => {
201
+ notification_sender.send_to_account(offer.to_account_id, build_role_grant_offer_received_notification({ offer: offer_json }));
202
+ });
203
+ }
204
+ return { offer: offer_json };
205
+ };
206
+ const accept_handler = async (input, ctx) => {
207
+ const auth = ctx.auth;
208
+ let result;
209
+ try {
210
+ result = await query_accept_offer(ctx, {
211
+ offer_id: input.offer_id,
212
+ to_account_id: auth.account.id,
213
+ actor_id: auth.actor.id,
214
+ ip: ctx.client_ip,
215
+ });
216
+ }
217
+ catch (err) {
218
+ if (err instanceof RoleGrantOfferNotFoundError) {
219
+ throw jsonrpc_errors.not_found('offer', { reason: ERROR_ROLE_GRANT_OFFER_NOT_FOUND });
220
+ }
221
+ if (err instanceof RoleGrantOfferAlreadyTerminalError) {
222
+ throw jsonrpc_errors.invalid_request({ reason: ERROR_ROLE_GRANT_OFFER_TERMINAL });
223
+ }
224
+ if (err instanceof RoleGrantOfferExpiredError) {
225
+ throw jsonrpc_errors.invalid_request({ reason: ERROR_ROLE_GRANT_OFFER_EXPIRED });
226
+ }
227
+ if (err instanceof RoleGrantOfferActorMismatchError) {
228
+ throw jsonrpc_errors.forbidden('offer is targeted to a different actor', {
229
+ reason: ERROR_ROLE_GRANT_OFFER_ACTOR_MISMATCH,
230
+ });
231
+ }
232
+ throw err;
233
+ }
234
+ // Look up the grantor's account_id inside the transaction so the
235
+ // post-commit notification has a valid target. One cheap SELECT by
236
+ // PK — the alternative (widening `query_accept_offer` again) would
237
+ // bleed transport concerns into the query layer.
238
+ const grantor_actor = notification_sender
239
+ ? await query_actor_by_id(ctx, result.offer.from_actor_id)
240
+ : null;
241
+ const grantor_account_id = grantor_actor?.account_id ?? null;
242
+ const offer_json = to_role_grant_offer_json(result.offer);
243
+ const supersede_payloads = result.superseded_offers.map((sib) => ({
244
+ offer: to_role_grant_offer_json(sib),
245
+ from_account_id: sib.from_account_id,
246
+ }));
247
+ // Audit events are written in-transaction by query_accept_offer; wire
248
+ // them through `audit.notify` post-commit so SSE/WS broadcasts fire.
249
+ // WS notifications piggyback on the same post-commit microtask so the
250
+ // grantor sees "accepted" and each superseded grantor sees
251
+ // "supersede" only once the accept has durably committed.
252
+ emit_after_commit(ctx, () => {
253
+ fan_out_audit_events(result.audit_events, audit);
254
+ if (notification_sender && grantor_account_id) {
255
+ notification_sender.send_to_account(grantor_account_id, build_role_grant_offer_accepted_notification({ offer: offer_json }));
256
+ }
257
+ if (notification_sender) {
258
+ for (const sib of supersede_payloads) {
259
+ notification_sender.send_to_account(sib.from_account_id, build_role_grant_offer_supersede_notification({
260
+ offer: sib.offer,
261
+ reason: 'sibling_accepted',
262
+ cause_id: result.offer.id,
263
+ }));
264
+ }
265
+ }
266
+ });
267
+ return {
268
+ role_grant_id: result.role_grant.id,
269
+ offer: offer_json,
270
+ superseded_offer_ids: result.superseded_offers.map((o) => o.id),
271
+ };
272
+ };
273
+ const decline_handler = async (input, ctx) => {
274
+ const auth = ctx.auth;
275
+ let declined;
276
+ try {
277
+ declined = await query_role_grant_offer_decline(ctx, input.offer_id, auth.account.id, input.reason ?? null);
278
+ }
279
+ catch (err) {
280
+ if (err instanceof RoleGrantOfferAlreadyTerminalError) {
281
+ throw jsonrpc_errors.invalid_request({ reason: ERROR_ROLE_GRANT_OFFER_TERMINAL });
282
+ }
283
+ throw err;
284
+ }
285
+ if (!declined) {
286
+ throw jsonrpc_errors.not_found('offer', { reason: ERROR_ROLE_GRANT_OFFER_NOT_FOUND });
287
+ }
288
+ // `role_grant_offer_decline` is *to* the offering actor — populate both
289
+ // `target_actor_id` (the grantor actor) and `target_account_id`
290
+ // (the grantor account, joined in the decline RETURNING via CTE).
291
+ // The "both populated → same account" invariant holds: the
292
+ // grantor's actor↔account binding is 1:1 by definition of `actor`.
293
+ audit.emit_role_grant_target(ctx, auth, {
294
+ event_type: 'role_grant_offer_decline',
295
+ target_account_id: declined.from_account_id,
296
+ target_actor_id: declined.from_actor_id,
297
+ metadata: {
298
+ offer_id: declined.id,
299
+ role: declined.role,
300
+ scope_id: declined.scope_id,
301
+ reason: input.reason ?? undefined,
302
+ },
303
+ });
304
+ if (notification_sender) {
305
+ // Grantor's account_id rides on `declined.from_account_id` from
306
+ // the decline RETURNING — no second SELECT needed. The decline
307
+ // reason rides along on `offer.decline_reason` — the DB set it
308
+ // in the RETURNING above.
309
+ const offer_json = to_role_grant_offer_json(declined);
310
+ emit_after_commit(ctx, () => {
311
+ notification_sender.send_to_account(declined.from_account_id, build_role_grant_offer_declined_notification({ offer: offer_json }));
312
+ });
313
+ }
314
+ return { ok: true };
315
+ };
316
+ const retract_handler = async (input, ctx) => {
317
+ const auth = ctx.auth;
318
+ let retracted;
319
+ try {
320
+ retracted = await query_role_grant_offer_retract(ctx, input.offer_id, auth.actor.id);
321
+ }
322
+ catch (err) {
323
+ if (err instanceof RoleGrantOfferAlreadyTerminalError) {
324
+ throw jsonrpc_errors.invalid_request({ reason: ERROR_ROLE_GRANT_OFFER_TERMINAL });
325
+ }
326
+ throw err;
327
+ }
328
+ if (!retracted) {
329
+ throw jsonrpc_errors.not_found('offer', { reason: ERROR_ROLE_GRANT_OFFER_NOT_FOUND });
330
+ }
331
+ // `role_grant_offer_retract` is *from* the recipient inbox —
332
+ // `target_account_id` is the recipient account; `target_actor_id`
333
+ // inherits the offer's `to_actor_id` (set on actor-targeted
334
+ // offers, null on account-grain offers).
335
+ audit.emit_role_grant_target(ctx, auth, {
336
+ event_type: 'role_grant_offer_retract',
337
+ target_account_id: retracted.to_account_id,
338
+ target_actor_id: retracted.to_actor_id,
339
+ metadata: {
340
+ offer_id: retracted.id,
341
+ role: retracted.role,
342
+ scope_id: retracted.scope_id,
343
+ },
344
+ });
345
+ if (notification_sender) {
346
+ const offer_json = to_role_grant_offer_json(retracted);
347
+ emit_after_commit(ctx, () => {
348
+ notification_sender.send_to_account(retracted.to_account_id, build_role_grant_offer_retracted_notification({ offer: offer_json }));
349
+ });
350
+ }
351
+ return { ok: true };
352
+ };
353
+ const list_handler = async (input, ctx) => {
354
+ const auth = ctx.auth;
355
+ const target = input.account_id ?? auth.account.id;
356
+ // Cross-account inspection requires **global** admin — a scoped admin
357
+ // role_grant must not be able to read another account's offer list.
358
+ if (target !== auth.account.id && !has_scoped_role(auth, ROLE_ADMIN, null)) {
359
+ throw jsonrpc_errors.forbidden('admin required to inspect another account');
360
+ }
361
+ const offers = await query_role_grant_offer_list(ctx, target);
362
+ return { offers: offers.map(to_role_grant_offer_json) };
363
+ };
364
+ const history_handler = async (input, ctx) => {
365
+ const auth = ctx.auth;
366
+ const target = input.account_id ?? auth.account.id;
367
+ if (target !== auth.account.id && !has_scoped_role(auth, ROLE_ADMIN, null)) {
368
+ throw jsonrpc_errors.forbidden('admin required to inspect another account');
369
+ }
370
+ const offers = await query_role_grant_offer_history_for_account(ctx, target, input.limit ?? undefined, input.offset ?? undefined);
371
+ return { offers: offers.map(to_role_grant_offer_json) };
372
+ };
373
+ const revoke_handler = async (input, ctx) => {
374
+ const auth = ctx.auth;
375
+ // IDOR guard + role lookup + actor → account JOIN. One SELECT —
376
+ // returns null when the role_grant is revoked, missing, or belongs
377
+ // to a different actor. The JOIN supplies `account_id` for the
378
+ // audit envelope's `target_account_id` and the post-commit
379
+ // SSE/WS socket-close fan-out target. `role_grant_revoke` is the
380
+ // canonical actor-bound-subject event: `target_actor_id` is the
381
+ // role_grant's grantee (input.actor_id); `target_account_id` is the
382
+ // account hosting that actor (sessions remain account-grain
383
+ // after multi-actor lands).
384
+ const role_grant_row = await query_role_grant_find_active_role_for_actor(ctx, input.role_grant_id, input.actor_id);
385
+ if (!role_grant_row) {
386
+ throw jsonrpc_errors.not_found('role_grant', { reason: ERROR_ROLE_GRANT_NOT_FOUND });
387
+ }
388
+ const target_account_id = role_grant_row.account_id;
389
+ const target_actor_id = input.actor_id;
390
+ // Admin-grant-path gate — keeper / daemon-scoped roles stay CLI-only
391
+ // (their `grant_paths` does not include `'admin'`).
392
+ if (!role_has_grant_path(role_specs, role_grant_row.role, GRANT_PATH_ADMIN)) {
393
+ audit.emit_role_grant_target(ctx, auth, {
394
+ event_type: 'role_grant_revoke',
395
+ outcome: 'failure',
396
+ target_account_id,
397
+ target_actor_id,
398
+ metadata: { role: role_grant_row.role, role_grant_id: input.role_grant_id },
399
+ });
400
+ throw jsonrpc_errors.forbidden('role not web-grantable', {
401
+ reason: ERROR_ROLE_NOT_WEB_GRANTABLE,
402
+ });
403
+ }
404
+ const result = await query_revoke_role_grant(ctx, input.role_grant_id, input.actor_id, auth.actor.id, input.reason ?? null);
405
+ if (!result) {
406
+ // Raced with another revoker or the role_grant was revoked between
407
+ // the IDOR check and the UPDATE.
408
+ throw jsonrpc_errors.not_found('role_grant', { reason: ERROR_ROLE_GRANT_NOT_FOUND });
409
+ }
410
+ audit.emit_role_grant_target(ctx, auth, {
411
+ event_type: 'role_grant_revoke',
412
+ target_account_id,
413
+ target_actor_id,
414
+ metadata: {
415
+ role: result.role,
416
+ role_grant_id: result.id,
417
+ scope_id: result.scope_id,
418
+ reason: input.reason ?? undefined,
419
+ },
420
+ });
421
+ // Supersede cascade — the recipient is known (`offer.to_account_id`),
422
+ // so populate `target_account_id` rather than leaving it null;
423
+ // `target_actor_id` inherits the offer's `to_actor_id` (actor-grain
424
+ // when the superseded offer was actor-targeted, null otherwise).
425
+ for (const offer of result.superseded_offers) {
426
+ audit.emit_role_grant_target(ctx, auth, {
427
+ event_type: 'role_grant_offer_supersede',
428
+ target_account_id: offer.to_account_id,
429
+ target_actor_id: offer.to_actor_id,
430
+ metadata: {
431
+ offer_id: offer.id,
432
+ role: offer.role,
433
+ scope_id: offer.scope_id,
434
+ reason: 'role_grant_revoked',
435
+ cause_id: result.id,
436
+ },
437
+ });
438
+ }
439
+ if (notification_sender) {
440
+ const superseded = result.superseded_offers.map((o) => ({
441
+ offer: to_role_grant_offer_json(o),
442
+ from_account_id: o.from_account_id,
443
+ }));
444
+ const cause_id = result.id;
445
+ const reason = input.reason ?? null;
446
+ emit_after_commit(ctx, () => {
447
+ notification_sender.send_to_account(target_account_id, build_role_grant_revoke_notification({
448
+ role_grant_id: result.id,
449
+ role: result.role,
450
+ scope_id: result.scope_id,
451
+ reason,
452
+ }));
453
+ for (const sib of superseded) {
454
+ notification_sender.send_to_account(sib.from_account_id, build_role_grant_offer_supersede_notification({
455
+ offer: sib.offer,
456
+ reason: 'role_grant_revoked',
457
+ cause_id,
458
+ }));
459
+ }
460
+ });
461
+ }
462
+ return { ok: true, revoked: true };
463
+ };
464
+ return [
465
+ rpc_action(role_grant_offer_create_action_spec, create_handler),
466
+ rpc_action(role_grant_offer_accept_action_spec, accept_handler),
467
+ rpc_action(role_grant_offer_decline_action_spec, decline_handler),
468
+ rpc_action(role_grant_offer_retract_action_spec, retract_handler),
469
+ rpc_action(role_grant_offer_list_action_spec, list_handler),
470
+ rpc_action(role_grant_offer_history_action_spec, history_handler),
471
+ rpc_action(role_grant_revoke_action_spec, revoke_handler),
472
+ ];
473
+ };