@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
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Grant-path registry — the surfaces through which a role can be
3
+ * granted to an actor.
4
+ *
5
+ * Four builtins:
6
+ *
7
+ * - `admin` — granted by an admin via `role_grant_offer_create` (subject to
8
+ * the consumer's `authorize` callback) or admin-side direct grant.
9
+ * - `self_service` — toggled by the holder themselves via
10
+ * `self_service_role_set` (allowlisted by `eligible_roles`).
11
+ * - `system` — granted by system code paths (signup, automation, etc.)
12
+ * that don't fit either of the above.
13
+ * - `bootstrap` — granted exactly once during the bootstrap flow
14
+ * (`keeper`, `admin` on a fresh install).
15
+ *
16
+ * Open registry on top so consumers can declare additional paths
17
+ * (e.g. `'invite_only'`, `'sso_assertion'`) without an upstream release.
18
+ * `RoleSpec.grant_paths` references entries from this registry; the
19
+ * default for `admin_actions.grantable_roles` is `grant_paths.includes('admin')`,
20
+ * the default for `self_service_role_actions` eligibility is
21
+ * `grant_paths.includes('self_service')`. Mirrors the open-registry
22
+ * pattern used for `RoleName`, `ScopeKindName`, `CredentialTypeName`,
23
+ * and `AuditEventTypeName`.
24
+ *
25
+ * @module
26
+ */
27
+ import { z } from 'zod';
28
+ /**
29
+ * Letter (lowercase a-z) start and end (or single letter), with letters
30
+ * and underscores in between. Mirrors `RoleName`, `ScopeKindName`,
31
+ * `CredentialTypeName`. Rejects empty strings, leading or trailing
32
+ * underscores, uppercase, and digits.
33
+ */
34
+ export const GRANT_PATH_NAME_REGEX = /^[a-z][a-z_]*[a-z]$|^[a-z]$/;
35
+ /** Zod schema for valid grant-path name strings. */
36
+ export const GrantPathName = z
37
+ .string()
38
+ .regex(GRANT_PATH_NAME_REGEX, 'Grant-path names must be lowercase letters and underscores (a-z_), no leading/trailing underscore');
39
+ // Builtin grant paths — provided by fuz_app, always available.
40
+ /** Admin-mediated grant — `role_grant_offer_create` plus admin-direct flows. */
41
+ export const GRANT_PATH_ADMIN = 'admin';
42
+ /** Self-service grant — caller toggles their own role_grant via `self_service_role_set`. */
43
+ export const GRANT_PATH_SELF_SERVICE = 'self_service';
44
+ /** System-mediated grant — signup hooks, automation, internal service flows. */
45
+ export const GRANT_PATH_SYSTEM = 'system';
46
+ /** Bootstrap grant — one-shot flow during the keep's first-run bootstrap. */
47
+ export const GRANT_PATH_BOOTSTRAP = 'bootstrap';
48
+ /** The builtin grant-path names as a const tuple. */
49
+ export const BUILTIN_GRANT_PATHS = [
50
+ GRANT_PATH_ADMIN,
51
+ GRANT_PATH_SELF_SERVICE,
52
+ GRANT_PATH_SYSTEM,
53
+ GRANT_PATH_BOOTSTRAP,
54
+ ];
55
+ /** Zod enum for builtin grant paths only. */
56
+ export const BuiltinGrantPath = z.enum(BUILTIN_GRANT_PATHS);
57
+ /**
58
+ * Builtin grant-path metadata. Not overridable by consumers.
59
+ *
60
+ * Typed `ReadonlyMap` for the contract — but JS Maps don't honor
61
+ * `Object.freeze` for `.set` / `.delete` / `.clear` (they mutate
62
+ * internal slots, not own properties), so freeze adds no runtime guard
63
+ * here. Read once at startup by `create_grant_path_schema`; runtime
64
+ * mutation has no effect on already-built schemas.
65
+ */
66
+ export const BUILTIN_GRANT_PATH_META = new Map([
67
+ [
68
+ GRANT_PATH_ADMIN,
69
+ {
70
+ description: 'Admin-mediated grant — admin offers via `role_grant_offer_create` or direct grant.',
71
+ },
72
+ ],
73
+ [
74
+ GRANT_PATH_SELF_SERVICE,
75
+ {
76
+ description: 'Self-service grant — caller toggles their own role_grant via `self_service_role_set`.',
77
+ },
78
+ ],
79
+ [
80
+ GRANT_PATH_SYSTEM,
81
+ { description: 'System-mediated grant — signup, automation, or internal service flows.' },
82
+ ],
83
+ [
84
+ GRANT_PATH_BOOTSTRAP,
85
+ { description: 'Bootstrap grant — one-shot flow during the keep’s first-run bootstrap.' },
86
+ ],
87
+ ]);
88
+ /**
89
+ * Create a grant-path schema from the builtin set plus optional
90
+ * consumer-declared additions.
91
+ *
92
+ * Builtins (`admin`, `self_service`, `system`, `bootstrap`) are always
93
+ * present; consumer entries that collide with a builtin name throw at
94
+ * construction. Pass the result into `create_role_schema`'s optional
95
+ * `grant_paths` parameter so each role's `grant_paths` entries are
96
+ * validated against this set at construction time.
97
+ *
98
+ * @param consumer_paths - optional consumer-declared grant-path set with optional metadata
99
+ * @returns `{GrantPath, grant_paths}` — Zod schema and metadata map
100
+ *
101
+ * @throws Error if any `consumer_paths` key fails the `GrantPathName` regex, collides with a builtin name, or appears more than once
102
+ *
103
+ * @example
104
+ * ```ts
105
+ * // simple — builtins only
106
+ * const {GrantPath, grant_paths} = create_grant_path_schema();
107
+ *
108
+ * // with consumer extensions
109
+ * const {GrantPath} = create_grant_path_schema({
110
+ * invite_only: {description: 'Granted by claiming a consumer-issued invite.'},
111
+ * });
112
+ * ```
113
+ */
114
+ export const create_grant_path_schema = (consumer_paths = {}) => {
115
+ const consumer_names = Object.keys(consumer_paths);
116
+ const seen = new Set();
117
+ for (const name of consumer_names) {
118
+ const parsed = GrantPathName.safeParse(name);
119
+ if (!parsed.success) {
120
+ throw new Error(`Invalid grant-path name "${name}": ${parsed.error.issues[0].message}`);
121
+ }
122
+ if (BUILTIN_GRANT_PATH_META.has(name)) {
123
+ throw new Error(`Consumer grant-path "${name}" collides with builtin grant-path`);
124
+ }
125
+ if (seen.has(name)) {
126
+ throw new Error(`Duplicate grant-path name "${name}"`);
127
+ }
128
+ seen.add(name);
129
+ }
130
+ const all_names = [...BUILTIN_GRANT_PATHS, ...consumer_names];
131
+ const GrantPath = z.enum(all_names);
132
+ const grant_paths = new Map(BUILTIN_GRANT_PATH_META);
133
+ for (const name of consumer_names) {
134
+ grant_paths.set(name, consumer_paths[name]);
135
+ }
136
+ return { GrantPath, grant_paths };
137
+ };
@@ -41,13 +41,24 @@ export declare const query_invite_find_unclaimed_match: (deps: QueryDeps, email:
41
41
  /**
42
42
  * Claim an invite by setting the claimed_by and claimed_at fields.
43
43
  *
44
+ * The `_unscoped` suffix is the safety signal — the SQL only checks the
45
+ * row state (`claimed_at IS NULL`), not whether the claiming account's
46
+ * email or username matches the invite. Callers must scope the lookup
47
+ * upstream via `query_invite_find_unclaimed_match`; the production caller
48
+ * (`auth/signup_routes.ts`) does this. Skipping the find step lets a
49
+ * caller claim any unclaimed invite by id.
50
+ *
51
+ * Mirrors the `query_session_revoke_by_hash_unscoped` precedent — there
52
+ * is no scoped sibling because the scoping is provided by a separate
53
+ * find query, not by an alternate variant of this query.
54
+ *
44
55
  * @param deps - query dependencies
45
56
  * @param invite_id - the invite to claim
46
57
  * @param account_id - the account claiming the invite
47
58
  * @returns true if the invite was claimed, false if already claimed or not found
48
59
  * @mutates `invite` row - sets `claimed_by` and `claimed_at` when still unclaimed
49
60
  */
50
- export declare const query_invite_claim: (deps: QueryDeps, invite_id: string, account_id: string) => Promise<boolean>;
61
+ export declare const query_invite_claim_unscoped: (deps: QueryDeps, invite_id: string, account_id: string) => Promise<boolean>;
51
62
  /**
52
63
  * List all invites, newest first.
53
64
  */
@@ -1 +1 @@
1
- {"version":3,"file":"invite_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/invite_queries.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,qBAAqB,CAAC;AAEnD,OAAO,KAAK,EAAC,MAAM,EAAE,iBAAiB,EAAE,uBAAuB,EAAC,MAAM,oBAAoB,CAAC;AAE3F;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB,GAC/B,MAAM,SAAS,EACf,OAAO,iBAAiB,KACtB,OAAO,CAAC,MAAM,CAQhB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,oCAAoC,GAChD,MAAM,SAAS,EACf,OAAO,MAAM,KACX,OAAO,CAAC,MAAM,GAAG,SAAS,CAK5B,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,uCAAuC,GACnD,MAAM,SAAS,EACf,UAAU,MAAM,KACd,OAAO,CAAC,MAAM,GAAG,SAAS,CAK5B,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,iCAAiC,GAC7C,MAAM,SAAS,EACf,OAAO,MAAM,GAAG,IAAI,EACpB,UAAU,MAAM,KACd,OAAO,CAAC,MAAM,GAAG,SAAS,CAe5B,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,kBAAkB,GAC9B,MAAM,SAAS,EACf,WAAW,MAAM,EACjB,YAAY,MAAM,KAChB,OAAO,CAAC,OAAO,CAQjB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,qBAAqB,GAAU,MAAM,SAAS,KAAG,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAElF,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,oCAAoC,GAChD,MAAM,SAAS,KACb,OAAO,CAAC,KAAK,CAAC,uBAAuB,CAAC,CAUxC,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,6BAA6B,GACzC,MAAM,SAAS,EACf,IAAI,MAAM,KACR,OAAO,CAAC,OAAO,CAMjB,CAAC"}
1
+ {"version":3,"file":"invite_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/invite_queries.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,qBAAqB,CAAC;AAEnD,OAAO,KAAK,EAAC,MAAM,EAAE,iBAAiB,EAAE,uBAAuB,EAAC,MAAM,oBAAoB,CAAC;AAE3F;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB,GAC/B,MAAM,SAAS,EACf,OAAO,iBAAiB,KACtB,OAAO,CAAC,MAAM,CAQhB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,oCAAoC,GAChD,MAAM,SAAS,EACf,OAAO,MAAM,KACX,OAAO,CAAC,MAAM,GAAG,SAAS,CAK5B,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,uCAAuC,GACnD,MAAM,SAAS,EACf,UAAU,MAAM,KACd,OAAO,CAAC,MAAM,GAAG,SAAS,CAK5B,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,iCAAiC,GAC7C,MAAM,SAAS,EACf,OAAO,MAAM,GAAG,IAAI,EACpB,UAAU,MAAM,KACd,OAAO,CAAC,MAAM,GAAG,SAAS,CAe5B,CAAC;AAEF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,2BAA2B,GACvC,MAAM,SAAS,EACf,WAAW,MAAM,EACjB,YAAY,MAAM,KAChB,OAAO,CAAC,OAAO,CAQjB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,qBAAqB,GAAU,MAAM,SAAS,KAAG,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAElF,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,oCAAoC,GAChD,MAAM,SAAS,KACb,OAAO,CAAC,KAAK,CAAC,uBAAuB,CAAC,CAUxC,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,6BAA6B,GACzC,MAAM,SAAS,EACf,IAAI,MAAM,KACR,OAAO,CAAC,OAAO,CAMjB,CAAC"}
@@ -61,13 +61,24 @@ export const query_invite_find_unclaimed_match = async (deps, email, username) =
61
61
  /**
62
62
  * Claim an invite by setting the claimed_by and claimed_at fields.
63
63
  *
64
+ * The `_unscoped` suffix is the safety signal — the SQL only checks the
65
+ * row state (`claimed_at IS NULL`), not whether the claiming account's
66
+ * email or username matches the invite. Callers must scope the lookup
67
+ * upstream via `query_invite_find_unclaimed_match`; the production caller
68
+ * (`auth/signup_routes.ts`) does this. Skipping the find step lets a
69
+ * caller claim any unclaimed invite by id.
70
+ *
71
+ * Mirrors the `query_session_revoke_by_hash_unscoped` precedent — there
72
+ * is no scoped sibling because the scoping is provided by a separate
73
+ * find query, not by an alternate variant of this query.
74
+ *
64
75
  * @param deps - query dependencies
65
76
  * @param invite_id - the invite to claim
66
77
  * @param account_id - the account claiming the invite
67
78
  * @returns true if the invite was claimed, false if already claimed or not found
68
79
  * @mutates `invite` row - sets `claimed_by` and `claimed_at` when still unclaimed
69
80
  */
70
- export const query_invite_claim = async (deps, invite_id, account_id) => {
81
+ export const query_invite_claim_unscoped = async (deps, invite_id, account_id) => {
71
82
  const rows = await deps.db.query(`UPDATE invite SET claimed_by = $1, claimed_at = NOW()
72
83
  WHERE id = $2 AND claimed_at IS NULL
73
84
  RETURNING id`, [account_id, invite_id]);
@@ -8,7 +8,7 @@
8
8
  */
9
9
  import { z } from 'zod';
10
10
  import { Uuid } from '@fuzdev/fuz_util/id.js';
11
- import { Username, Email } from './account_schema.js';
11
+ import { Username, Email } from '../primitive_schemas.js';
12
12
  /** Invite row from the database. */
13
13
  export interface Invite {
14
14
  id: Uuid;
@@ -1 +1 @@
1
- {"version":3,"file":"invite_schema.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/invite_schema.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AACtB,OAAO,EAAC,IAAI,EAAC,MAAM,wBAAwB,CAAC;AAE5C,OAAO,EAAC,QAAQ,EAAE,KAAK,EAAC,MAAM,qBAAqB,CAAC;AAEpD,oCAAoC;AACpC,MAAM,WAAW,MAAM;IACtB,EAAE,EAAE,IAAI,CAAC;IACT,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,IAAI,GAAG,IAAI,CAAC;IACxB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,IAAI,GAAG,IAAI,CAAC;CACxB;AAED,8CAA8C;AAC9C,eAAO,MAAM,UAAU;;;;;;;;kBAQrB,CAAC;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAEpD,0EAA0E;AAC1E,eAAO,MAAM,uBAAuB;;;;;;;;;;kBAGlC,CAAC;AACH,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAE9E,oCAAoC;AACpC,MAAM,WAAW,iBAAiB;IACjC,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC;IACrB,QAAQ,CAAC,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC3B,UAAU,EAAE,IAAI,GAAG,IAAI,CAAC;CACxB"}
1
+ {"version":3,"file":"invite_schema.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/invite_schema.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AACtB,OAAO,EAAC,IAAI,EAAC,MAAM,wBAAwB,CAAC;AAE5C,OAAO,EAAC,QAAQ,EAAE,KAAK,EAAC,MAAM,yBAAyB,CAAC;AAExD,oCAAoC;AACpC,MAAM,WAAW,MAAM;IACtB,EAAE,EAAE,IAAI,CAAC;IACT,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,IAAI,GAAG,IAAI,CAAC;IACxB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,IAAI,GAAG,IAAI,CAAC;CACxB;AAED,8CAA8C;AAC9C,eAAO,MAAM,UAAU;;;;;;;;kBAQrB,CAAC;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAEpD,0EAA0E;AAC1E,eAAO,MAAM,uBAAuB;;;;;;;;;;kBAGlC,CAAC;AACH,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAE9E,oCAAoC;AACpC,MAAM,WAAW,iBAAiB;IACjC,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC;IACrB,QAAQ,CAAC,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC3B,UAAU,EAAE,IAAI,GAAG,IAAI,CAAC;CACxB"}
@@ -8,7 +8,7 @@
8
8
  */
9
9
  import { z } from 'zod';
10
10
  import { Uuid } from '@fuzdev/fuz_util/id.js';
11
- import { Username, Email } from './account_schema.js';
11
+ import { Username, Email } from '../primitive_schemas.js';
12
12
  /** Zod schema for client-safe invite data. */
13
13
  export const InviteJson = z.strictObject({
14
14
  id: Uuid,
@@ -1 +1 @@
1
- {"version":3,"file":"middleware.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/middleware.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,WAAW,CAAC;AACvC,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,mBAAmB,CAAC;AACxD,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,oBAAoB,CAAC;AACpD,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,4BAA4B,CAAC;AAG/D;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACrC,eAAe,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC/B,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,yDAAyD;IACzD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,mFAAmF;IACnF,kBAAkB,CAAC,EAAE,gBAAgB,CAAC;IACtC,oFAAoF;IACpF,sBAAsB,EAAE,WAAW,GAAG,IAAI,CAAC;CAC3C;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,4BAA4B,GACxC,MAAM,OAAO,EACb,SAAS,qBAAqB,KAC5B,OAAO,CAAC,KAAK,CAAC,cAAc,CAAC,CAqE/B,CAAC"}
1
+ {"version":3,"file":"middleware.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/middleware.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,WAAW,CAAC;AACvC,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,mBAAmB,CAAC;AACxD,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,oBAAoB,CAAC;AACpD,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,4BAA4B,CAAC;AAG/D;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACrC,eAAe,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC/B,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,yDAAyD;IACzD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,mFAAmF;IACnF,kBAAkB,CAAC,EAAE,gBAAgB,CAAC;IACtC,oFAAoF;IACpF,sBAAsB,EAAE,WAAW,GAAG,IAAI,CAAC;CAC3C;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,4BAA4B,GACxC,MAAM,OAAO,EACb,SAAS,qBAAqB,KAC5B,OAAO,CAAC,KAAK,CAAC,cAAc,CAAC,CAwE/B,CAAC"}
@@ -23,8 +23,11 @@ export const create_auth_middleware_specs = async (deps, options) => {
23
23
  const { keyring, db } = deps;
24
24
  const { allowed_origins, session_options, path = '/api/*', daemon_token_state, bearer_ip_rate_limiter, } = options;
25
25
  const query_deps = { db };
26
- // Dynamic imports to avoid pulling heavy dependencies into this module
27
- // when consumers only need types (MiddlewareSpec, RouteSpec, etc.)
26
+ // Dynamic imports preserve the bundle-split for type-only consumers of
27
+ // this module (`MiddlewareSpec`, `AuthMiddlewareOptions`, etc.). The
28
+ // runtime chain pulls session_queries + blake3 transitively via
29
+ // session_middleware.js, but type-only imports are erased at compile
30
+ // time and stay free.
28
31
  const [{ verify_request_source }, { create_session_middleware }, { create_request_context_middleware }, { create_bearer_auth_middleware },] = await Promise.all([
29
32
  import('../http/origin.js'),
30
33
  import('./session_middleware.js'),
@@ -39,18 +39,33 @@
39
39
  import type { Migration, MigrationNamespace } from '../db/migrate.js';
40
40
  /** Namespace identifier for fuz_app auth migrations. */
41
41
  export declare const AUTH_MIGRATION_NAMESPACE = "fuz_auth";
42
+ /**
43
+ * Migration namespaces reserved by fuz_app. Consumers passing
44
+ * `migration_namespaces` to `create_app_backend` must choose a name not in
45
+ * this list — the runtime check rejects matches with a thrown error. Typed
46
+ * as `ReadonlyArray<string>` (not a literal tuple) so `.includes()` accepts
47
+ * any consumer-supplied namespace string without a cast.
48
+ */
49
+ export declare const RESERVED_MIGRATION_NAMESPACES: ReadonlyArray<string>;
42
50
  /**
43
51
  * Auth schema migrations in order.
44
52
  *
45
- * - v0: Full auth schema — account (with email_verified), actor, permit,
53
+ * - v0: Full auth schema — account (with email_verified), actor, role_grant,
46
54
  * auth_session, api_token, audit_log (with seq), bootstrap_lock, invite,
47
55
  * app_settings, plus all indexes and seeds.
48
- * - v1: `permit_offer` table for consentful grants; adds `scope_id` /
49
- * `source_offer_id` / `revoked_reason` to `permit` and swaps the
50
- * `(actor_id, role)` partial unique index for a scope-aware variant using
51
- * the all-zeros sentinel UUID. The `permit_offer` table carries a
52
- * `superseded_at` terminal state; its partial unique index is scoped by
53
- * `(to_account, role, scope, from_actor)` so multiple grantors may coexist.
56
+ * - v1: `role_grant_offer` table for consentful grants; adds `scope_id` /
57
+ * `scope_kind` / `source_offer_id` / `revoked_reason` to `role_grant` and
58
+ * swaps the `(actor_id, role)` partial unique index for a scope-aware
59
+ * variant using the index-side `'GLOBAL'` token + all-zeros sentinel
60
+ * UUID. The `(scope_kind, scope_id)` pair is enforced paired-null by
61
+ * `role_grant_scope_kind_paired` / `role_grant_offer_scope_kind_paired` CHECK
62
+ * constraints — both null for global, both non-null for scoped. The
63
+ * `role_grant_offer` table carries a `superseded_at` terminal state; its
64
+ * partial unique index is scoped by
65
+ * `(to_account, role, scope_kind, scope, from_actor)` so multiple
66
+ * grantors may coexist. `scope_kind` is informative-only in v1
67
+ * (registry-membership validation against `create_scope_kind_schema`);
68
+ * v2 may add INSERT-time `(role, scope_kind)` enforcement.
54
69
  */
55
70
  export declare const AUTH_MIGRATIONS: Array<Migration>;
56
71
  /** Pre-composed migration namespace for auth tables. */
@@ -1 +1 @@
1
- {"version":3,"file":"migrations.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/migrations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AA6BH,OAAO,KAAK,EAAC,SAAS,EAAE,kBAAkB,EAAC,MAAM,kBAAkB,CAAC;AAEpE,wDAAwD;AACxD,eAAO,MAAM,wBAAwB,aAAa,CAAC;AAEnD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,SAAS,CA6D5C,CAAC;AAEF,wDAAwD;AACxD,eAAO,MAAM,iBAAiB,EAAE,kBAG/B,CAAC"}
1
+ {"version":3,"file":"migrations.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/migrations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AA8BH,OAAO,KAAK,EAAC,SAAS,EAAE,kBAAkB,EAAC,MAAM,kBAAkB,CAAC;AAEpE,wDAAwD;AACxD,eAAO,MAAM,wBAAwB,aAAa,CAAC;AAEnD;;;;;;GAMG;AACH,eAAO,MAAM,6BAA6B,EAAE,aAAa,CAAC,MAAM,CAA8B,CAAC;AAE/F;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,SAAS,CAqF5C,CAAC;AAEF,wDAAwD;AACxD,eAAO,MAAM,iBAAiB,EAAE,kBAG/B,CAAC"}
@@ -36,23 +36,38 @@
36
36
  *
37
37
  * @module
38
38
  */
39
- import { ACCOUNT_SCHEMA, ACCOUNT_EMAIL_INDEX, ACCOUNT_USERNAME_CI_INDEX, ACTOR_SCHEMA, ACTOR_INDEX, PERMIT_SCHEMA, PERMIT_INDEXES, AUTH_SESSION_SCHEMA, AUTH_SESSION_INDEXES, API_TOKEN_SCHEMA, API_TOKEN_INDEX, BOOTSTRAP_LOCK_SCHEMA, BOOTSTRAP_LOCK_SEED, INVITE_SCHEMA, INVITE_INDEXES, APP_SETTINGS_SCHEMA, APP_SETTINGS_SEED, } from './ddl.js';
39
+ import { ACCOUNT_SCHEMA, ACCOUNT_EMAIL_INDEX, ACCOUNT_USERNAME_CI_INDEX, ACTOR_SCHEMA, ACTOR_INDEX, ROLE_GRANT_SCHEMA, ROLE_GRANT_INDEXES, AUTH_SESSION_SCHEMA, AUTH_SESSION_INDEXES, API_TOKEN_SCHEMA, API_TOKEN_INDEX, BOOTSTRAP_LOCK_SCHEMA, BOOTSTRAP_LOCK_SEED, INVITE_SCHEMA, INVITE_INDEXES, APP_SETTINGS_SCHEMA, APP_SETTINGS_SEED, } from './ddl.js';
40
40
  import { AUDIT_LOG_SCHEMA, AUDIT_LOG_INDEXES } from './audit_log_schema.js';
41
- import { PERMIT_OFFER_SCHEMA, PERMIT_OFFER_PENDING_UNIQUE_INDEX, PERMIT_OFFER_INBOX_INDEX, PERMIT_OFFER_SCOPE_SENTINEL_UUID, } from './permit_offer_schema.js';
41
+ import { ROLE_GRANT_OFFER_SCHEMA, ROLE_GRANT_OFFER_PENDING_UNIQUE_INDEX, ROLE_GRANT_OFFER_INBOX_INDEX, ROLE_GRANT_OFFER_SCOPE_SENTINEL_UUID, ROLE_GRANT_OFFER_SCOPE_KIND_GLOBAL_TOKEN, } from './role_grant_offer_schema.js';
42
42
  /** Namespace identifier for fuz_app auth migrations. */
43
43
  export const AUTH_MIGRATION_NAMESPACE = 'fuz_auth';
44
+ /**
45
+ * Migration namespaces reserved by fuz_app. Consumers passing
46
+ * `migration_namespaces` to `create_app_backend` must choose a name not in
47
+ * this list — the runtime check rejects matches with a thrown error. Typed
48
+ * as `ReadonlyArray<string>` (not a literal tuple) so `.includes()` accepts
49
+ * any consumer-supplied namespace string without a cast.
50
+ */
51
+ export const RESERVED_MIGRATION_NAMESPACES = [AUTH_MIGRATION_NAMESPACE];
44
52
  /**
45
53
  * Auth schema migrations in order.
46
54
  *
47
- * - v0: Full auth schema — account (with email_verified), actor, permit,
55
+ * - v0: Full auth schema — account (with email_verified), actor, role_grant,
48
56
  * auth_session, api_token, audit_log (with seq), bootstrap_lock, invite,
49
57
  * app_settings, plus all indexes and seeds.
50
- * - v1: `permit_offer` table for consentful grants; adds `scope_id` /
51
- * `source_offer_id` / `revoked_reason` to `permit` and swaps the
52
- * `(actor_id, role)` partial unique index for a scope-aware variant using
53
- * the all-zeros sentinel UUID. The `permit_offer` table carries a
54
- * `superseded_at` terminal state; its partial unique index is scoped by
55
- * `(to_account, role, scope, from_actor)` so multiple grantors may coexist.
58
+ * - v1: `role_grant_offer` table for consentful grants; adds `scope_id` /
59
+ * `scope_kind` / `source_offer_id` / `revoked_reason` to `role_grant` and
60
+ * swaps the `(actor_id, role)` partial unique index for a scope-aware
61
+ * variant using the index-side `'GLOBAL'` token + all-zeros sentinel
62
+ * UUID. The `(scope_kind, scope_id)` pair is enforced paired-null by
63
+ * `role_grant_scope_kind_paired` / `role_grant_offer_scope_kind_paired` CHECK
64
+ * constraints — both null for global, both non-null for scoped. The
65
+ * `role_grant_offer` table carries a `superseded_at` terminal state; its
66
+ * partial unique index is scoped by
67
+ * `(to_account, role, scope_kind, scope, from_actor)` so multiple
68
+ * grantors may coexist. `scope_kind` is informative-only in v1
69
+ * (registry-membership validation against `create_scope_kind_schema`);
70
+ * v2 may add INSERT-time `(role, scope_kind)` enforcement.
56
71
  */
57
72
  export const AUTH_MIGRATIONS = [
58
73
  // v0: full auth schema — all IF NOT EXISTS, safe for existing databases
@@ -64,8 +79,8 @@ export const AUTH_MIGRATIONS = [
64
79
  await db.query(ACCOUNT_USERNAME_CI_INDEX);
65
80
  await db.query(ACTOR_SCHEMA);
66
81
  await db.query(ACTOR_INDEX);
67
- await db.query(PERMIT_SCHEMA);
68
- for (const sql of PERMIT_INDEXES) {
82
+ await db.query(ROLE_GRANT_SCHEMA);
83
+ for (const sql of ROLE_GRANT_INDEXES) {
69
84
  await db.query(sql);
70
85
  }
71
86
  await db.query(AUTH_SESSION_SCHEMA);
@@ -88,24 +103,48 @@ export const AUTH_MIGRATIONS = [
88
103
  await db.query(APP_SETTINGS_SEED);
89
104
  },
90
105
  },
91
- // v1: consentful permitspermit_offer table + scoped permits
106
+ // v1: consentful role_grantsrole_grant_offer table + scoped role_grants
92
107
  {
93
108
  name: 'permit_offer_and_scoped_permits',
94
109
  up: async (db) => {
95
- await db.query(PERMIT_OFFER_SCHEMA);
96
- await db.query(PERMIT_OFFER_PENDING_UNIQUE_INDEX);
97
- await db.query(PERMIT_OFFER_INBOX_INDEX);
98
- await db.query('ALTER TABLE permit ADD COLUMN IF NOT EXISTS scope_id UUID NULL');
99
- await db.query('ALTER TABLE permit ADD COLUMN IF NOT EXISTS source_offer_id UUID NULL REFERENCES permit_offer(id) ON DELETE SET NULL');
100
- await db.query('ALTER TABLE permit ADD COLUMN IF NOT EXISTS revoked_reason TEXT NULL');
101
- // swap the (actor_id, role) partial unique for a scope-aware variant.
102
- // Existing rows have `scope_id = NULL` and collapse to the sentinel.
103
- await db.query('DROP INDEX IF EXISTS permit_actor_role_active_unique');
104
- await db.query(`CREATE UNIQUE INDEX IF NOT EXISTS permit_actor_role_scope_active_unique
105
- ON permit (actor_id, role, COALESCE(scope_id, '${PERMIT_OFFER_SCOPE_SENTINEL_UUID}'::uuid))
110
+ await db.query(ROLE_GRANT_OFFER_SCHEMA);
111
+ await db.query(ROLE_GRANT_OFFER_PENDING_UNIQUE_INDEX);
112
+ await db.query(ROLE_GRANT_OFFER_INBOX_INDEX);
113
+ await db.query('ALTER TABLE role_grant ADD COLUMN IF NOT EXISTS scope_id UUID NULL');
114
+ await db.query('ALTER TABLE role_grant ADD COLUMN IF NOT EXISTS scope_kind TEXT NULL');
115
+ await db.query('ALTER TABLE role_grant ADD COLUMN IF NOT EXISTS source_offer_id UUID NULL REFERENCES role_grant_offer(id) ON DELETE SET NULL');
116
+ await db.query('ALTER TABLE role_grant ADD COLUMN IF NOT EXISTS revoked_reason TEXT NULL');
117
+ // Paired-null CHECK on `(scope_kind, scope_id)` both null encodes
118
+ // the global case; both non-null encodes a scoped grant. The DO
119
+ // block makes constraint addition idempotent across migration
120
+ // re-runs (Postgres has no `ADD CONSTRAINT IF NOT EXISTS` for
121
+ // CHECK constraints — `pg_constraint` lookup is the established
122
+ // shape).
123
+ await db.query(`DO $$ BEGIN
124
+ IF NOT EXISTS (
125
+ SELECT 1 FROM pg_constraint WHERE conname = 'role_grant_scope_kind_paired'
126
+ ) THEN
127
+ ALTER TABLE role_grant
128
+ ADD CONSTRAINT role_grant_scope_kind_paired
129
+ CHECK ((scope_kind IS NULL) = (scope_id IS NULL));
130
+ END IF;
131
+ END $$`);
132
+ // Swap the (actor_id, role) partial unique for a scope-aware variant.
133
+ // Existing rows have `scope_id = NULL` (and `scope_kind = NULL` per
134
+ // the pair invariant) and collapse to the index-side `'GLOBAL'`
135
+ // token + all-zeros sentinel UUID.
136
+ await db.query('DROP INDEX IF EXISTS role_grant_actor_role_active_unique');
137
+ await db.query('DROP INDEX IF EXISTS role_grant_actor_role_scope_active_unique');
138
+ await db.query(`CREATE UNIQUE INDEX IF NOT EXISTS role_grant_actor_role_scope_active_unique
139
+ ON role_grant (
140
+ actor_id,
141
+ role,
142
+ COALESCE(scope_kind, '${ROLE_GRANT_OFFER_SCOPE_KIND_GLOBAL_TOKEN}'),
143
+ COALESCE(scope_id, '${ROLE_GRANT_OFFER_SCOPE_SENTINEL_UUID}'::uuid)
144
+ )
106
145
  WHERE revoked_at IS NULL`);
107
- await db.query(`CREATE INDEX IF NOT EXISTS permit_scope_active
108
- ON permit (actor_id, role, scope_id)
146
+ await db.query(`CREATE INDEX IF NOT EXISTS role_grant_scope_active
147
+ ON role_grant (actor_id, role, scope_id)
109
148
  WHERE revoked_at IS NULL`);
110
149
  },
111
150
  },