@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
@@ -1,71 +1,76 @@
1
1
  /**
2
- * Permit offer RPC action handlers — the consentful-permits action surface.
2
+ * Role grant offer RPC action handlers — the consentful-role-grants action surface.
3
3
  *
4
4
  * Seven actions: six offer-lifecycle methods (create / accept / decline /
5
- * retract / list / history) plus `permit_revoke` (admin-only). All mount
5
+ * retract / list / history) plus `role_grant_revoke` (admin-only). All mount
6
6
  * on a consumer's JSON-RPC endpoint via `create_rpc_endpoint`. The action
7
- * specs themselves live in `auth/permit_offer_action_specs.ts`. Mutations
7
+ * specs themselves live in `auth/role_grant_offer_action_specs.ts`. Mutations
8
8
  * declare `side_effects: true` so the RPC dispatcher wraps the handler in
9
- * a DB transaction; `permit_offer_list` and `permit_offer_history` declare
9
+ * a DB transaction; `role_grant_offer_list` and `role_grant_offer_history` declare
10
10
  * `side_effects: false` so they are addressable via GET.
11
11
  *
12
12
  * Authorization:
13
- * - `permit_offer_create` — the grantor must hold an active permit for the
14
- * role being offered, and that role must be `web_grantable`. Consumers
15
- * needing a richer policy (e.g., "teacher may offer student in *their*
16
- * classroom") pass an `authorize` callback that overrides the default.
17
- * - `permit_offer_accept` / `permit_offer_decline` — keyed to the caller's
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
18
  * account; `query_*` helpers enforce the IDOR guard.
19
- * - `permit_offer_retract` — keyed to the caller's actor.
20
- * - `permit_offer_list` / `permit_offer_history` — self by default;
19
+ * - `role_grant_offer_retract` — keyed to the caller's actor.
20
+ * - `role_grant_offer_list` / `role_grant_offer_history` — self by default;
21
21
  * `{account_id}` is admin-only.
22
- * - `permit_revoke` — spec-level `auth: {role: 'admin'}`; the RPC
22
+ * - `role_grant_revoke` — spec-level `auth: {role: 'admin'}`; the RPC
23
23
  * dispatcher rejects non-admin callers before the handler runs.
24
- * `web_grantable` gate prevents revoking keeper/daemon-scoped roles
25
- * via this surface. Keys on `actor_id` to survive multi-actor accounts.
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
26
  *
27
27
  * Audit events are emitted in-transaction by the query layer (atomic with
28
- * the permit write on accept/revoke) or by the handler via
29
- * `audit_log_fire_and_forget` for single-event lifecycle transitions.
30
- * `on_audit_event` (SSE broadcast) fires post-commit in both paths.
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.
31
32
  *
32
33
  * WS notifications fan out post-commit via `emit_after_commit` when a
33
34
  * `notification_sender` is wired: offer lifecycle transitions notify the
34
- * counterparty, `permit_revoke` notifies the revokee plus each superseded
35
+ * counterparty, `role_grant_revoke` notifies the revokee plus each superseded
35
36
  * pending offer's grantor.
36
37
  *
37
38
  * @module
38
39
  */
39
- import { rpc_actor_action, } from '../actions/action_rpc.js';
40
+ import { rpc_action, } from '../actions/action_rpc.js';
40
41
  import { jsonrpc_errors } from '../http/jsonrpc_errors.js';
41
42
  import { emit_after_commit } from '../http/pending_effects.js';
42
- import { BUILTIN_ROLE_OPTIONS, ROLE_ADMIN } from './role_schema.js';
43
- import { PERMIT_OFFER_DEFAULT_TTL_MS, to_permit_offer_json } from './permit_offer_schema.js';
44
- import { query_permit_offer_create, query_permit_offer_decline, query_permit_offer_retract, query_permit_offer_list, query_permit_offer_history_for_account, query_accept_offer, PermitOfferActorAccountMismatchError, PermitOfferActorMismatchError, PermitOfferAlreadyTerminalError, PermitOfferExpiredError, PermitOfferNotFoundError, PermitOfferSelfTargetError, } from './permit_offer_queries.js';
45
- import { query_permit_find_active_role_for_actor, query_revoke_permit } from './permit_queries.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';
46
48
  import { query_actor_by_id } from './account_queries.js';
47
- import { emit_permit_target_event } from './audit_log_queries.js';
48
- import { has_role, has_scoped_role, } from './request_context.js';
49
- import { build_permit_offer_accepted_notification, build_permit_offer_declined_notification, build_permit_offer_received_notification, build_permit_offer_retracted_notification, build_permit_offer_supersede_notification, build_permit_revoke_notification, } from './permit_offer_notifications.js';
50
- import { ERROR_PERMIT_NOT_FOUND, ERROR_ROLE_NOT_WEB_GRANTABLE } from '../http/error_schemas.js';
51
- import { ERROR_OFFER_ACTOR_ACCOUNT_MISMATCH, ERROR_OFFER_ACTOR_MISMATCH, ERROR_OFFER_EXPIRED, ERROR_OFFER_NOT_AUTHORIZED, ERROR_OFFER_NOT_FOUND, ERROR_OFFER_ROLE_NOT_GRANTABLE, ERROR_OFFER_SELF_TARGET, ERROR_OFFER_TERMINAL, permit_offer_create_action_spec, permit_offer_accept_action_spec, permit_offer_decline_action_spec, permit_offer_retract_action_spec, permit_offer_list_action_spec, permit_offer_history_action_spec, permit_revoke_action_spec, } from './permit_offer_action_specs.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';
52
53
  // -- Helpers ----------------------------------------------------------------
53
- /** Fire `on_audit_event` for each event — used by accept, whose events were written in-transaction. */
54
- const fan_out_audit_events = (events, on_audit_event, log) => {
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) => {
55
65
  for (const event of events) {
56
- try {
57
- on_audit_event(event);
58
- }
59
- catch (err) {
60
- log.error('on_audit_event callback failed:', err);
61
- }
66
+ audit.notify(event);
62
67
  }
63
68
  };
64
69
  // eslint-disable-next-line @typescript-eslint/require-await
65
70
  const default_authorize = async (auth, input, _deps, _ctx) => {
66
- // Caller must hold an active permit for the offered role. Global (no scope)
71
+ // Caller must hold an active role_grant for the offered role. Global (no scope)
67
72
  // check — the scope-aware "only in this classroom" policy is consumer-level.
68
- // Reads from the in-memory `auth.permits` snapshot loaded once per request
73
+ // Reads from the in-memory `auth.role_grants` snapshot loaded once per request
69
74
  // by `create_request_context_middleware`; no DB roundtrip needed.
70
75
  return has_scoped_role(auth, input.role, null);
71
76
  };
@@ -73,9 +78,10 @@ const default_authorize = async (auth, input, _deps, _ctx) => {
73
78
  * Authorization callback that admits any admin and otherwise falls back to
74
79
  * the symmetric default (caller must hold the offered role globally).
75
80
  *
76
- * The `web_grantable` filter in `create_handler` runs **before** the
77
- * `authorize` callback, so this never sees non-web-grantable roles. Drop
78
- * into `create_permit_offer_actions({authorize: authorize_admin_or_holder})`
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})`
79
85
  * (or any factory that forwards `authorize`, e.g. `create_standard_rpc_actions`)
80
86
  * for the common "admins offer anything; users offer what they hold"
81
87
  * pattern. Scope-aware policies (e.g. classroom_teacher offering
@@ -83,31 +89,36 @@ const default_authorize = async (auth, input, _deps, _ctx) => {
83
89
  * before delegating.
84
90
  */
85
91
  export const authorize_admin_or_holder = async (auth, input, _deps, _ctx) => {
86
- if (has_role(auth, ROLE_ADMIN))
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))
87
97
  return true;
88
98
  return has_scoped_role(auth, input.role, null);
89
99
  };
100
+ // -- Action factory ---------------------------------------------------------
90
101
  /**
91
- * Create the seven permit-offer RPC actions (six offer-lifecycle methods
92
- * plus `permit_revoke`).
102
+ * Create the seven role-grant-offer RPC actions (six offer-lifecycle methods
103
+ * plus `role_grant_revoke`).
93
104
  *
94
- * @param deps - `PermitOfferActionDeps` `log`, `on_audit_event`, optional `audit_log_config` (slice of `AppDeps`); optional `notification_sender` for WS fan-out
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)
95
106
  * @param options - role schema, default TTL, authorization override
96
107
  * @returns the `RpcAction` array to spread into a `create_rpc_endpoint` call
97
108
  */
98
- export const create_permit_offer_actions = (deps, options = {}) => {
99
- const { on_audit_event, log, notification_sender = null } = deps;
100
- const role_options = options.roles?.role_options ?? BUILTIN_ROLE_OPTIONS;
101
- const default_ttl_ms = options.default_ttl_ms ?? PERMIT_OFFER_DEFAULT_TTL_MS;
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;
102
113
  const authorize = options.authorize ?? default_authorize;
103
- // Four denial paths (web_grantable, authorize, self-target,
114
+ // Four denial paths (admin-grant-path gate, authorize, self-target,
104
115
  // actor-account mismatch) all emit the same failure-outcome audit
105
116
  // event. `target_actor_id` is populated when the caller supplied a
106
117
  // `to_actor_id` so failure rows match the success-shape envelope of
107
118
  // actor-targeted offers.
108
119
  const emit_create_failure_audit = (ctx, auth, input) => {
109
- void emit_permit_target_event(ctx, auth, deps, {
110
- event_type: 'permit_offer_create',
120
+ audit.emit_role_grant_target(ctx, auth, {
121
+ event_type: 'role_grant_offer_create',
111
122
  outcome: 'failure',
112
123
  target_account_id: input.to_account_id,
113
124
  target_actor_id: input.to_actor_id ?? null,
@@ -119,16 +130,15 @@ export const create_permit_offer_actions = (deps, options = {}) => {
119
130
  });
120
131
  };
121
132
  // Returns {offer} only — no auto-accept. Recipient must call
122
- // permit_offer_accept; admin tests materialize permits via
133
+ // role_grant_offer_accept; admin tests materialize role_grants via
123
134
  // query_accept_offer (see testing/admin_integration.ts `offer_and_accept`).
124
135
  const create_handler = async (input, ctx) => {
125
136
  const auth = ctx.auth;
126
- // Role must be web_grantable — same gate as admin direct-grant.
127
- const rc = role_options.get(input.role);
128
- if (!rc?.web_grantable) {
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)) {
129
139
  emit_create_failure_audit(ctx, auth, input);
130
140
  throw jsonrpc_errors.forbidden('role not grantable', {
131
- reason: ERROR_OFFER_ROLE_NOT_GRANTABLE,
141
+ reason: ERROR_ROLE_GRANT_OFFER_ROLE_NOT_GRANTABLE,
132
142
  });
133
143
  }
134
144
  const authorized = await authorize(auth, {
@@ -139,32 +149,33 @@ export const create_permit_offer_actions = (deps, options = {}) => {
139
149
  if (!authorized) {
140
150
  emit_create_failure_audit(ctx, auth, input);
141
151
  throw jsonrpc_errors.forbidden('not authorized to offer this role', {
142
- reason: ERROR_OFFER_NOT_AUTHORIZED,
152
+ reason: ERROR_ROLE_GRANT_OFFER_NOT_AUTHORIZED,
143
153
  });
144
154
  }
145
155
  let offer;
146
156
  try {
147
- offer = await query_permit_offer_create(ctx, {
157
+ offer = await query_role_grant_offer_create(ctx, {
148
158
  from_actor_id: auth.actor.id,
149
159
  to_account_id: input.to_account_id,
150
160
  to_actor_id: input.to_actor_id ?? null,
151
161
  role: input.role,
162
+ scope_kind: input.scope_kind ?? null,
152
163
  scope_id: input.scope_id ?? null,
153
164
  message: input.message ?? null,
154
165
  expires_at: new Date(Date.now() + default_ttl_ms),
155
166
  });
156
167
  }
157
168
  catch (err) {
158
- if (err instanceof PermitOfferSelfTargetError) {
169
+ if (err instanceof RoleGrantOfferSelfTargetError) {
159
170
  emit_create_failure_audit(ctx, auth, input);
160
171
  throw jsonrpc_errors.invalid_params('cannot offer to self', {
161
- reason: ERROR_OFFER_SELF_TARGET,
172
+ reason: ERROR_ROLE_GRANT_OFFER_SELF_TARGET,
162
173
  });
163
174
  }
164
- if (err instanceof PermitOfferActorAccountMismatchError) {
175
+ if (err instanceof RoleGrantOfferActorAccountMismatchError) {
165
176
  emit_create_failure_audit(ctx, auth, input);
166
177
  throw jsonrpc_errors.invalid_params('to_actor_id does not belong to to_account_id', {
167
- reason: ERROR_OFFER_ACTOR_ACCOUNT_MISMATCH,
178
+ reason: ERROR_ROLE_GRANT_OFFER_ACTOR_ACCOUNT_MISMATCH,
168
179
  });
169
180
  }
170
181
  throw err;
@@ -173,8 +184,8 @@ export const create_permit_offer_actions = (deps, options = {}) => {
173
184
  // (per the offer's `to_actor_id`), null for account-grain offers
174
185
  // — closes the audit hole where offer-shape events used to leave
175
186
  // actor-grain forensics blank even when the binding was known.
176
- void emit_permit_target_event(ctx, auth, deps, {
177
- event_type: 'permit_offer_create',
187
+ audit.emit_role_grant_target(ctx, auth, {
188
+ event_type: 'role_grant_offer_create',
178
189
  target_account_id: input.to_account_id,
179
190
  target_actor_id: offer.to_actor_id,
180
191
  metadata: {
@@ -184,10 +195,10 @@ export const create_permit_offer_actions = (deps, options = {}) => {
184
195
  to_account_id: offer.to_account_id,
185
196
  },
186
197
  });
187
- const offer_json = to_permit_offer_json(offer);
198
+ const offer_json = to_role_grant_offer_json(offer);
188
199
  if (notification_sender) {
189
200
  emit_after_commit(ctx, () => {
190
- notification_sender.send_to_account(offer.to_account_id, build_permit_offer_received_notification({ offer: offer_json }));
201
+ notification_sender.send_to_account(offer.to_account_id, build_role_grant_offer_received_notification({ offer: offer_json }));
191
202
  });
192
203
  }
193
204
  return { offer: offer_json };
@@ -204,18 +215,18 @@ export const create_permit_offer_actions = (deps, options = {}) => {
204
215
  });
205
216
  }
206
217
  catch (err) {
207
- if (err instanceof PermitOfferNotFoundError) {
208
- throw jsonrpc_errors.not_found('offer', { reason: ERROR_OFFER_NOT_FOUND });
218
+ if (err instanceof RoleGrantOfferNotFoundError) {
219
+ throw jsonrpc_errors.not_found('offer', { reason: ERROR_ROLE_GRANT_OFFER_NOT_FOUND });
209
220
  }
210
- if (err instanceof PermitOfferAlreadyTerminalError) {
211
- throw jsonrpc_errors.invalid_request({ reason: ERROR_OFFER_TERMINAL });
221
+ if (err instanceof RoleGrantOfferAlreadyTerminalError) {
222
+ throw jsonrpc_errors.invalid_request({ reason: ERROR_ROLE_GRANT_OFFER_TERMINAL });
212
223
  }
213
- if (err instanceof PermitOfferExpiredError) {
214
- throw jsonrpc_errors.invalid_request({ reason: ERROR_OFFER_EXPIRED });
224
+ if (err instanceof RoleGrantOfferExpiredError) {
225
+ throw jsonrpc_errors.invalid_request({ reason: ERROR_ROLE_GRANT_OFFER_EXPIRED });
215
226
  }
216
- if (err instanceof PermitOfferActorMismatchError) {
227
+ if (err instanceof RoleGrantOfferActorMismatchError) {
217
228
  throw jsonrpc_errors.forbidden('offer is targeted to a different actor', {
218
- reason: ERROR_OFFER_ACTOR_MISMATCH,
229
+ reason: ERROR_ROLE_GRANT_OFFER_ACTOR_MISMATCH,
219
230
  });
220
231
  }
221
232
  throw err;
@@ -228,24 +239,24 @@ export const create_permit_offer_actions = (deps, options = {}) => {
228
239
  ? await query_actor_by_id(ctx, result.offer.from_actor_id)
229
240
  : null;
230
241
  const grantor_account_id = grantor_actor?.account_id ?? null;
231
- const offer_json = to_permit_offer_json(result.offer);
242
+ const offer_json = to_role_grant_offer_json(result.offer);
232
243
  const supersede_payloads = result.superseded_offers.map((sib) => ({
233
- offer: to_permit_offer_json(sib),
244
+ offer: to_role_grant_offer_json(sib),
234
245
  from_account_id: sib.from_account_id,
235
246
  }));
236
247
  // Audit events are written in-transaction by query_accept_offer; wire
237
- // them through on_audit_event post-commit so SSE broadcasts fire.
248
+ // them through `audit.notify` post-commit so SSE/WS broadcasts fire.
238
249
  // WS notifications piggyback on the same post-commit microtask so the
239
250
  // grantor sees "accepted" and each superseded grantor sees
240
251
  // "supersede" only once the accept has durably committed.
241
252
  emit_after_commit(ctx, () => {
242
- fan_out_audit_events(result.audit_events, on_audit_event, ctx.log);
253
+ fan_out_audit_events(result.audit_events, audit);
243
254
  if (notification_sender && grantor_account_id) {
244
- notification_sender.send_to_account(grantor_account_id, build_permit_offer_accepted_notification({ offer: offer_json }));
255
+ notification_sender.send_to_account(grantor_account_id, build_role_grant_offer_accepted_notification({ offer: offer_json }));
245
256
  }
246
257
  if (notification_sender) {
247
258
  for (const sib of supersede_payloads) {
248
- notification_sender.send_to_account(sib.from_account_id, build_permit_offer_supersede_notification({
259
+ notification_sender.send_to_account(sib.from_account_id, build_role_grant_offer_supersede_notification({
249
260
  offer: sib.offer,
250
261
  reason: 'sibling_accepted',
251
262
  cause_id: result.offer.id,
@@ -254,7 +265,7 @@ export const create_permit_offer_actions = (deps, options = {}) => {
254
265
  }
255
266
  });
256
267
  return {
257
- permit_id: result.permit.id,
268
+ role_grant_id: result.role_grant.id,
258
269
  offer: offer_json,
259
270
  superseded_offer_ids: result.superseded_offers.map((o) => o.id),
260
271
  };
@@ -263,24 +274,24 @@ export const create_permit_offer_actions = (deps, options = {}) => {
263
274
  const auth = ctx.auth;
264
275
  let declined;
265
276
  try {
266
- declined = await query_permit_offer_decline(ctx, input.offer_id, auth.account.id, input.reason ?? null);
277
+ declined = await query_role_grant_offer_decline(ctx, input.offer_id, auth.account.id, input.reason ?? null);
267
278
  }
268
279
  catch (err) {
269
- if (err instanceof PermitOfferAlreadyTerminalError) {
270
- throw jsonrpc_errors.invalid_request({ reason: ERROR_OFFER_TERMINAL });
280
+ if (err instanceof RoleGrantOfferAlreadyTerminalError) {
281
+ throw jsonrpc_errors.invalid_request({ reason: ERROR_ROLE_GRANT_OFFER_TERMINAL });
271
282
  }
272
283
  throw err;
273
284
  }
274
285
  if (!declined) {
275
- throw jsonrpc_errors.not_found('offer', { reason: ERROR_OFFER_NOT_FOUND });
286
+ throw jsonrpc_errors.not_found('offer', { reason: ERROR_ROLE_GRANT_OFFER_NOT_FOUND });
276
287
  }
277
- // `permit_offer_decline` is *to* the offering actor — populate both
288
+ // `role_grant_offer_decline` is *to* the offering actor — populate both
278
289
  // `target_actor_id` (the grantor actor) and `target_account_id`
279
290
  // (the grantor account, joined in the decline RETURNING via CTE).
280
291
  // The "both populated → same account" invariant holds: the
281
292
  // grantor's actor↔account binding is 1:1 by definition of `actor`.
282
- void emit_permit_target_event(ctx, auth, deps, {
283
- event_type: 'permit_offer_decline',
293
+ audit.emit_role_grant_target(ctx, auth, {
294
+ event_type: 'role_grant_offer_decline',
284
295
  target_account_id: declined.from_account_id,
285
296
  target_actor_id: declined.from_actor_id,
286
297
  metadata: {
@@ -295,9 +306,9 @@ export const create_permit_offer_actions = (deps, options = {}) => {
295
306
  // the decline RETURNING — no second SELECT needed. The decline
296
307
  // reason rides along on `offer.decline_reason` — the DB set it
297
308
  // in the RETURNING above.
298
- const offer_json = to_permit_offer_json(declined);
309
+ const offer_json = to_role_grant_offer_json(declined);
299
310
  emit_after_commit(ctx, () => {
300
- notification_sender.send_to_account(declined.from_account_id, build_permit_offer_declined_notification({ offer: offer_json }));
311
+ notification_sender.send_to_account(declined.from_account_id, build_role_grant_offer_declined_notification({ offer: offer_json }));
301
312
  });
302
313
  }
303
314
  return { ok: true };
@@ -306,23 +317,23 @@ export const create_permit_offer_actions = (deps, options = {}) => {
306
317
  const auth = ctx.auth;
307
318
  let retracted;
308
319
  try {
309
- retracted = await query_permit_offer_retract(ctx, input.offer_id, auth.actor.id);
320
+ retracted = await query_role_grant_offer_retract(ctx, input.offer_id, auth.actor.id);
310
321
  }
311
322
  catch (err) {
312
- if (err instanceof PermitOfferAlreadyTerminalError) {
313
- throw jsonrpc_errors.invalid_request({ reason: ERROR_OFFER_TERMINAL });
323
+ if (err instanceof RoleGrantOfferAlreadyTerminalError) {
324
+ throw jsonrpc_errors.invalid_request({ reason: ERROR_ROLE_GRANT_OFFER_TERMINAL });
314
325
  }
315
326
  throw err;
316
327
  }
317
328
  if (!retracted) {
318
- throw jsonrpc_errors.not_found('offer', { reason: ERROR_OFFER_NOT_FOUND });
329
+ throw jsonrpc_errors.not_found('offer', { reason: ERROR_ROLE_GRANT_OFFER_NOT_FOUND });
319
330
  }
320
- // `permit_offer_retract` is *from* the recipient inbox —
331
+ // `role_grant_offer_retract` is *from* the recipient inbox —
321
332
  // `target_account_id` is the recipient account; `target_actor_id`
322
333
  // inherits the offer's `to_actor_id` (set on actor-targeted
323
334
  // offers, null on account-grain offers).
324
- void emit_permit_target_event(ctx, auth, deps, {
325
- event_type: 'permit_offer_retract',
335
+ audit.emit_role_grant_target(ctx, auth, {
336
+ event_type: 'role_grant_offer_retract',
326
337
  target_account_id: retracted.to_account_id,
327
338
  target_actor_id: retracted.to_actor_id,
328
339
  metadata: {
@@ -332,9 +343,9 @@ export const create_permit_offer_actions = (deps, options = {}) => {
332
343
  },
333
344
  });
334
345
  if (notification_sender) {
335
- const offer_json = to_permit_offer_json(retracted);
346
+ const offer_json = to_role_grant_offer_json(retracted);
336
347
  emit_after_commit(ctx, () => {
337
- notification_sender.send_to_account(retracted.to_account_id, build_permit_offer_retracted_notification({ offer: offer_json }));
348
+ notification_sender.send_to_account(retracted.to_account_id, build_role_grant_offer_retracted_notification({ offer: offer_json }));
338
349
  });
339
350
  }
340
351
  return { ok: true };
@@ -342,65 +353,67 @@ export const create_permit_offer_actions = (deps, options = {}) => {
342
353
  const list_handler = async (input, ctx) => {
343
354
  const auth = ctx.auth;
344
355
  const target = input.account_id ?? auth.account.id;
345
- if (target !== auth.account.id && !has_role(auth, ROLE_ADMIN)) {
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)) {
346
359
  throw jsonrpc_errors.forbidden('admin required to inspect another account');
347
360
  }
348
- const offers = await query_permit_offer_list(ctx, target);
349
- return { offers: offers.map(to_permit_offer_json) };
361
+ const offers = await query_role_grant_offer_list(ctx, target);
362
+ return { offers: offers.map(to_role_grant_offer_json) };
350
363
  };
351
364
  const history_handler = async (input, ctx) => {
352
365
  const auth = ctx.auth;
353
366
  const target = input.account_id ?? auth.account.id;
354
- if (target !== auth.account.id && !has_role(auth, ROLE_ADMIN)) {
367
+ if (target !== auth.account.id && !has_scoped_role(auth, ROLE_ADMIN, null)) {
355
368
  throw jsonrpc_errors.forbidden('admin required to inspect another account');
356
369
  }
357
- const offers = await query_permit_offer_history_for_account(ctx, target, input.limit ?? undefined, input.offset ?? undefined);
358
- return { offers: offers.map(to_permit_offer_json) };
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) };
359
372
  };
360
373
  const revoke_handler = async (input, ctx) => {
361
374
  const auth = ctx.auth;
362
375
  // IDOR guard + role lookup + actor → account JOIN. One SELECT —
363
- // returns null when the permit is revoked, missing, or belongs
376
+ // returns null when the role_grant is revoked, missing, or belongs
364
377
  // to a different actor. The JOIN supplies `account_id` for the
365
378
  // audit envelope's `target_account_id` and the post-commit
366
- // SSE/WS socket-close fan-out target. `permit_revoke` is the
379
+ // SSE/WS socket-close fan-out target. `role_grant_revoke` is the
367
380
  // canonical actor-bound-subject event: `target_actor_id` is the
368
- // permit's grantee (input.actor_id); `target_account_id` is the
381
+ // role_grant's grantee (input.actor_id); `target_account_id` is the
369
382
  // account hosting that actor (sessions remain account-grain
370
383
  // after multi-actor lands).
371
- const permit_row = await query_permit_find_active_role_for_actor(ctx, input.permit_id, input.actor_id);
372
- if (!permit_row) {
373
- throw jsonrpc_errors.not_found('permit', { reason: ERROR_PERMIT_NOT_FOUND });
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 });
374
387
  }
375
- const target_account_id = permit_row.account_id;
388
+ const target_account_id = role_grant_row.account_id;
376
389
  const target_actor_id = input.actor_id;
377
- // web_grantable gate — keeper/daemon-scoped roles stay CLI-only.
378
- const rc = role_options.get(permit_row.role);
379
- if (!rc?.web_grantable) {
380
- void emit_permit_target_event(ctx, auth, deps, {
381
- event_type: 'permit_revoke',
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',
382
395
  outcome: 'failure',
383
396
  target_account_id,
384
397
  target_actor_id,
385
- metadata: { role: permit_row.role, permit_id: input.permit_id },
398
+ metadata: { role: role_grant_row.role, role_grant_id: input.role_grant_id },
386
399
  });
387
400
  throw jsonrpc_errors.forbidden('role not web-grantable', {
388
401
  reason: ERROR_ROLE_NOT_WEB_GRANTABLE,
389
402
  });
390
403
  }
391
- const result = await query_revoke_permit(ctx, input.permit_id, input.actor_id, auth.actor.id, input.reason ?? null);
404
+ const result = await query_revoke_role_grant(ctx, input.role_grant_id, input.actor_id, auth.actor.id, input.reason ?? null);
392
405
  if (!result) {
393
- // Raced with another revoker or the permit was revoked between
406
+ // Raced with another revoker or the role_grant was revoked between
394
407
  // the IDOR check and the UPDATE.
395
- throw jsonrpc_errors.not_found('permit', { reason: ERROR_PERMIT_NOT_FOUND });
408
+ throw jsonrpc_errors.not_found('role_grant', { reason: ERROR_ROLE_GRANT_NOT_FOUND });
396
409
  }
397
- void emit_permit_target_event(ctx, auth, deps, {
398
- event_type: 'permit_revoke',
410
+ audit.emit_role_grant_target(ctx, auth, {
411
+ event_type: 'role_grant_revoke',
399
412
  target_account_id,
400
413
  target_actor_id,
401
414
  metadata: {
402
415
  role: result.role,
403
- permit_id: result.id,
416
+ role_grant_id: result.id,
404
417
  scope_id: result.scope_id,
405
418
  reason: input.reason ?? undefined,
406
419
  },
@@ -410,37 +423,37 @@ export const create_permit_offer_actions = (deps, options = {}) => {
410
423
  // `target_actor_id` inherits the offer's `to_actor_id` (actor-grain
411
424
  // when the superseded offer was actor-targeted, null otherwise).
412
425
  for (const offer of result.superseded_offers) {
413
- void emit_permit_target_event(ctx, auth, deps, {
414
- event_type: 'permit_offer_supersede',
426
+ audit.emit_role_grant_target(ctx, auth, {
427
+ event_type: 'role_grant_offer_supersede',
415
428
  target_account_id: offer.to_account_id,
416
429
  target_actor_id: offer.to_actor_id,
417
430
  metadata: {
418
431
  offer_id: offer.id,
419
432
  role: offer.role,
420
433
  scope_id: offer.scope_id,
421
- reason: 'permit_revoked',
434
+ reason: 'role_grant_revoked',
422
435
  cause_id: result.id,
423
436
  },
424
437
  });
425
438
  }
426
439
  if (notification_sender) {
427
440
  const superseded = result.superseded_offers.map((o) => ({
428
- offer: to_permit_offer_json(o),
441
+ offer: to_role_grant_offer_json(o),
429
442
  from_account_id: o.from_account_id,
430
443
  }));
431
444
  const cause_id = result.id;
432
445
  const reason = input.reason ?? null;
433
446
  emit_after_commit(ctx, () => {
434
- notification_sender.send_to_account(target_account_id, build_permit_revoke_notification({
435
- permit_id: result.id,
447
+ notification_sender.send_to_account(target_account_id, build_role_grant_revoke_notification({
448
+ role_grant_id: result.id,
436
449
  role: result.role,
437
450
  scope_id: result.scope_id,
438
451
  reason,
439
452
  }));
440
453
  for (const sib of superseded) {
441
- notification_sender.send_to_account(sib.from_account_id, build_permit_offer_supersede_notification({
454
+ notification_sender.send_to_account(sib.from_account_id, build_role_grant_offer_supersede_notification({
442
455
  offer: sib.offer,
443
- reason: 'permit_revoked',
456
+ reason: 'role_grant_revoked',
444
457
  cause_id,
445
458
  }));
446
459
  }
@@ -449,12 +462,12 @@ export const create_permit_offer_actions = (deps, options = {}) => {
449
462
  return { ok: true, revoked: true };
450
463
  };
451
464
  return [
452
- rpc_actor_action(permit_offer_create_action_spec, create_handler),
453
- rpc_actor_action(permit_offer_accept_action_spec, accept_handler),
454
- rpc_actor_action(permit_offer_decline_action_spec, decline_handler),
455
- rpc_actor_action(permit_offer_retract_action_spec, retract_handler),
456
- rpc_actor_action(permit_offer_list_action_spec, list_handler),
457
- rpc_actor_action(permit_offer_history_action_spec, history_handler),
458
- rpc_actor_action(permit_revoke_action_spec, revoke_handler),
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),
459
472
  ];
460
473
  };