@fuzdev/fuz_app 0.54.0 → 0.56.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (348) hide show
  1. package/dist/actions/CLAUDE.md +214 -103
  2. package/dist/actions/action_bridge.d.ts +8 -5
  3. package/dist/actions/action_bridge.d.ts.map +1 -1
  4. package/dist/actions/action_bridge.js +1 -11
  5. package/dist/actions/action_codegen.d.ts +32 -0
  6. package/dist/actions/action_codegen.d.ts.map +1 -1
  7. package/dist/actions/action_codegen.js +35 -15
  8. package/dist/actions/action_registry.d.ts.map +1 -1
  9. package/dist/actions/action_registry.js +5 -2
  10. package/dist/actions/action_rpc.d.ts +141 -22
  11. package/dist/actions/action_rpc.d.ts.map +1 -1
  12. package/dist/actions/action_rpc.js +106 -187
  13. package/dist/actions/action_spec.d.ts +55 -16
  14. package/dist/actions/action_spec.d.ts.map +1 -1
  15. package/dist/actions/action_spec.js +16 -11
  16. package/dist/actions/action_types.d.ts +28 -60
  17. package/dist/actions/action_types.d.ts.map +1 -1
  18. package/dist/actions/action_types.js +13 -5
  19. package/dist/actions/broadcast_api.d.ts +2 -2
  20. package/dist/actions/broadcast_api.js +2 -2
  21. package/dist/actions/compile_action_registry.d.ts +50 -0
  22. package/dist/actions/compile_action_registry.d.ts.map +1 -0
  23. package/dist/actions/compile_action_registry.js +69 -0
  24. package/dist/actions/heartbeat.d.ts +8 -4
  25. package/dist/actions/heartbeat.d.ts.map +1 -1
  26. package/dist/actions/heartbeat.js +5 -4
  27. package/dist/actions/perform_action.d.ts +145 -0
  28. package/dist/actions/perform_action.d.ts.map +1 -0
  29. package/dist/actions/perform_action.js +258 -0
  30. package/dist/actions/register_action_ws.d.ts +46 -40
  31. package/dist/actions/register_action_ws.d.ts.map +1 -1
  32. package/dist/actions/register_action_ws.js +101 -159
  33. package/dist/actions/register_ws_endpoint.d.ts +15 -10
  34. package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
  35. package/dist/actions/register_ws_endpoint.js +54 -7
  36. package/dist/actions/transports.d.ts.map +1 -1
  37. package/dist/actions/transports.js +0 -4
  38. package/dist/actions/transports_ws_auth_guard.d.ts +1 -1
  39. package/dist/actions/transports_ws_auth_guard.js +1 -1
  40. package/dist/actions/transports_ws_backend.d.ts +1 -1
  41. package/dist/actions/transports_ws_backend.js +1 -1
  42. package/dist/auth/CLAUDE.md +794 -410
  43. package/dist/auth/account_action_specs.d.ts +28 -7
  44. package/dist/auth/account_action_specs.d.ts.map +1 -1
  45. package/dist/auth/account_action_specs.js +7 -7
  46. package/dist/auth/account_actions.d.ts +7 -13
  47. package/dist/auth/account_actions.d.ts.map +1 -1
  48. package/dist/auth/account_actions.js +26 -35
  49. package/dist/auth/account_queries.d.ts +52 -16
  50. package/dist/auth/account_queries.d.ts.map +1 -1
  51. package/dist/auth/account_queries.js +87 -38
  52. package/dist/auth/account_routes.d.ts +9 -11
  53. package/dist/auth/account_routes.d.ts.map +1 -1
  54. package/dist/auth/account_routes.js +118 -46
  55. package/dist/auth/account_schema.d.ts +46 -35
  56. package/dist/auth/account_schema.d.ts.map +1 -1
  57. package/dist/auth/account_schema.js +21 -28
  58. package/dist/auth/admin_action_specs.d.ts +100 -32
  59. package/dist/auth/admin_action_specs.d.ts.map +1 -1
  60. package/dist/auth/admin_action_specs.js +64 -33
  61. package/dist/auth/admin_actions.d.ts +13 -19
  62. package/dist/auth/admin_actions.d.ts.map +1 -1
  63. package/dist/auth/admin_actions.js +37 -41
  64. package/dist/auth/audit_emitter.d.ts +160 -0
  65. package/dist/auth/audit_emitter.d.ts.map +1 -0
  66. package/dist/auth/audit_emitter.js +83 -0
  67. package/dist/auth/audit_log_queries.d.ts +17 -48
  68. package/dist/auth/audit_log_queries.d.ts.map +1 -1
  69. package/dist/auth/audit_log_queries.js +20 -56
  70. package/dist/auth/audit_log_routes.d.ts +1 -1
  71. package/dist/auth/audit_log_routes.d.ts.map +1 -1
  72. package/dist/auth/audit_log_routes.js +7 -3
  73. package/dist/auth/audit_log_schema.d.ts +92 -32
  74. package/dist/auth/audit_log_schema.d.ts.map +1 -1
  75. package/dist/auth/audit_log_schema.js +75 -46
  76. package/dist/auth/auth_guard_resolver.d.ts +44 -0
  77. package/dist/auth/auth_guard_resolver.d.ts.map +1 -0
  78. package/dist/auth/auth_guard_resolver.js +56 -0
  79. package/dist/auth/bearer_auth.d.ts +9 -7
  80. package/dist/auth/bearer_auth.d.ts.map +1 -1
  81. package/dist/auth/bearer_auth.js +13 -21
  82. package/dist/auth/bootstrap_account.d.ts +7 -7
  83. package/dist/auth/bootstrap_account.d.ts.map +1 -1
  84. package/dist/auth/bootstrap_account.js +7 -7
  85. package/dist/auth/bootstrap_routes.d.ts.map +1 -1
  86. package/dist/auth/bootstrap_routes.js +11 -10
  87. package/dist/auth/cleanup.d.ts +20 -26
  88. package/dist/auth/cleanup.d.ts.map +1 -1
  89. package/dist/auth/cleanup.js +33 -42
  90. package/dist/auth/credential_type_schema.d.ts +115 -0
  91. package/dist/auth/credential_type_schema.d.ts.map +1 -0
  92. package/dist/auth/credential_type_schema.js +127 -0
  93. package/dist/auth/daemon_token_middleware.d.ts +23 -11
  94. package/dist/auth/daemon_token_middleware.d.ts.map +1 -1
  95. package/dist/auth/daemon_token_middleware.js +28 -22
  96. package/dist/auth/ddl.d.ts +2 -2
  97. package/dist/auth/ddl.d.ts.map +1 -1
  98. package/dist/auth/ddl.js +6 -6
  99. package/dist/auth/deps.d.ts +7 -18
  100. package/dist/auth/deps.d.ts.map +1 -1
  101. package/dist/auth/grant_path_schema.d.ts +117 -0
  102. package/dist/auth/grant_path_schema.d.ts.map +1 -0
  103. package/dist/auth/grant_path_schema.js +137 -0
  104. package/dist/auth/invite_queries.d.ts +12 -1
  105. package/dist/auth/invite_queries.d.ts.map +1 -1
  106. package/dist/auth/invite_queries.js +12 -1
  107. package/dist/auth/invite_schema.d.ts +1 -1
  108. package/dist/auth/invite_schema.d.ts.map +1 -1
  109. package/dist/auth/invite_schema.js +1 -1
  110. package/dist/auth/middleware.d.ts.map +1 -1
  111. package/dist/auth/middleware.js +9 -4
  112. package/dist/auth/migrations.d.ts +37 -14
  113. package/dist/auth/migrations.d.ts.map +1 -1
  114. package/dist/auth/migrations.js +79 -32
  115. package/dist/auth/request_context.d.ts +331 -61
  116. package/dist/auth/request_context.d.ts.map +1 -1
  117. package/dist/auth/request_context.js +378 -95
  118. package/dist/auth/{permit_offer_action_specs.d.ts → role_grant_offer_action_specs.d.ts} +163 -94
  119. package/dist/auth/role_grant_offer_action_specs.d.ts.map +1 -0
  120. package/dist/auth/role_grant_offer_action_specs.js +262 -0
  121. package/dist/auth/role_grant_offer_actions.d.ts +104 -0
  122. package/dist/auth/role_grant_offer_actions.d.ts.map +1 -0
  123. package/dist/auth/role_grant_offer_actions.js +473 -0
  124. package/dist/auth/{permit_offer_notifications.d.ts → role_grant_offer_notifications.d.ts} +90 -70
  125. package/dist/auth/role_grant_offer_notifications.d.ts.map +1 -0
  126. package/dist/auth/role_grant_offer_notifications.js +182 -0
  127. package/dist/auth/role_grant_offer_queries.d.ts +242 -0
  128. package/dist/auth/role_grant_offer_queries.d.ts.map +1 -0
  129. package/dist/auth/role_grant_offer_queries.js +533 -0
  130. package/dist/auth/role_grant_offer_schema.d.ts +150 -0
  131. package/dist/auth/role_grant_offer_schema.d.ts.map +1 -0
  132. package/dist/auth/{permit_offer_schema.js → role_grant_offer_schema.js} +60 -36
  133. package/dist/auth/role_grant_queries.d.ts +231 -0
  134. package/dist/auth/role_grant_queries.d.ts.map +1 -0
  135. package/dist/auth/role_grant_queries.js +320 -0
  136. package/dist/auth/role_schema.d.ts +150 -40
  137. package/dist/auth/role_schema.d.ts.map +1 -1
  138. package/dist/auth/role_schema.js +144 -45
  139. package/dist/auth/scope_kind_schema.d.ts +96 -0
  140. package/dist/auth/scope_kind_schema.d.ts.map +1 -0
  141. package/dist/auth/scope_kind_schema.js +94 -0
  142. package/dist/auth/self_service_role_action_specs.d.ts +6 -1
  143. package/dist/auth/self_service_role_action_specs.d.ts.map +1 -1
  144. package/dist/auth/self_service_role_action_specs.js +3 -1
  145. package/dist/auth/self_service_role_actions.d.ts +34 -27
  146. package/dist/auth/self_service_role_actions.d.ts.map +1 -1
  147. package/dist/auth/self_service_role_actions.js +68 -48
  148. package/dist/auth/session_cookie.d.ts +43 -6
  149. package/dist/auth/session_cookie.d.ts.map +1 -1
  150. package/dist/auth/session_cookie.js +31 -5
  151. package/dist/auth/session_middleware.d.ts +37 -3
  152. package/dist/auth/session_middleware.d.ts.map +1 -1
  153. package/dist/auth/session_middleware.js +33 -7
  154. package/dist/auth/signup_routes.d.ts.map +1 -1
  155. package/dist/auth/signup_routes.js +48 -19
  156. package/dist/auth/standard_action_specs.d.ts +2 -2
  157. package/dist/auth/standard_action_specs.js +4 -4
  158. package/dist/auth/standard_rpc_actions.d.ts +23 -19
  159. package/dist/auth/standard_rpc_actions.d.ts.map +1 -1
  160. package/dist/auth/standard_rpc_actions.js +12 -12
  161. package/dist/db/migrate.d.ts +12 -8
  162. package/dist/db/migrate.d.ts.map +1 -1
  163. package/dist/db/migrate.js +10 -7
  164. package/dist/dev/setup.d.ts +2 -2
  165. package/dist/dev/setup.d.ts.map +1 -1
  166. package/dist/dev/setup.js +9 -7
  167. package/dist/env/load.d.ts +1 -1
  168. package/dist/env/load.js +1 -1
  169. package/dist/hono_context.d.ts +64 -5
  170. package/dist/hono_context.d.ts.map +1 -1
  171. package/dist/hono_context.js +38 -2
  172. package/dist/http/CLAUDE.md +264 -87
  173. package/dist/http/auth_shape.d.ts +191 -0
  174. package/dist/http/auth_shape.d.ts.map +1 -0
  175. package/dist/http/auth_shape.js +237 -0
  176. package/dist/http/common_routes.js +3 -3
  177. package/dist/http/db_routes.d.ts +4 -0
  178. package/dist/http/db_routes.d.ts.map +1 -1
  179. package/dist/http/db_routes.js +44 -7
  180. package/dist/http/error_schemas.d.ts +132 -19
  181. package/dist/http/error_schemas.d.ts.map +1 -1
  182. package/dist/http/error_schemas.js +132 -40
  183. package/dist/http/jsonrpc_errors.d.ts +27 -2
  184. package/dist/http/jsonrpc_errors.d.ts.map +1 -1
  185. package/dist/http/jsonrpc_errors.js +26 -2
  186. package/dist/http/pending_effects.d.ts +71 -18
  187. package/dist/http/pending_effects.d.ts.map +1 -1
  188. package/dist/http/pending_effects.js +87 -18
  189. package/dist/http/proxy.d.ts +52 -5
  190. package/dist/http/proxy.d.ts.map +1 -1
  191. package/dist/http/proxy.js +92 -14
  192. package/dist/http/route_spec.d.ts +113 -41
  193. package/dist/http/route_spec.d.ts.map +1 -1
  194. package/dist/http/route_spec.js +130 -52
  195. package/dist/http/schema_helpers.d.ts +3 -2
  196. package/dist/http/schema_helpers.d.ts.map +1 -1
  197. package/dist/http/schema_helpers.js +9 -2
  198. package/dist/http/surface.d.ts +2 -1
  199. package/dist/http/surface.d.ts.map +1 -1
  200. package/dist/http/surface.js +1 -2
  201. package/dist/http/surface_query.d.ts +39 -35
  202. package/dist/http/surface_query.d.ts.map +1 -1
  203. package/dist/http/surface_query.js +79 -36
  204. package/dist/primitive_schemas.d.ts +39 -0
  205. package/dist/primitive_schemas.d.ts.map +1 -0
  206. package/dist/primitive_schemas.js +40 -0
  207. package/dist/realtime/sse_auth_guard.d.ts +5 -5
  208. package/dist/realtime/sse_auth_guard.js +9 -9
  209. package/dist/runtime/mock.d.ts +1 -1
  210. package/dist/runtime/mock.js +1 -1
  211. package/dist/server/app_backend.d.ts +14 -11
  212. package/dist/server/app_backend.d.ts.map +1 -1
  213. package/dist/server/app_backend.js +12 -8
  214. package/dist/server/app_server.d.ts +7 -7
  215. package/dist/server/app_server.d.ts.map +1 -1
  216. package/dist/server/app_server.js +36 -31
  217. package/dist/server/validate_nginx.d.ts +1 -1
  218. package/dist/server/validate_nginx.js +1 -1
  219. package/dist/testing/CLAUDE.md +73 -55
  220. package/dist/testing/admin_integration.d.ts +5 -6
  221. package/dist/testing/admin_integration.d.ts.map +1 -1
  222. package/dist/testing/admin_integration.js +100 -96
  223. package/dist/testing/adversarial_headers.js +1 -1
  224. package/dist/testing/app_server.d.ts +11 -14
  225. package/dist/testing/app_server.d.ts.map +1 -1
  226. package/dist/testing/app_server.js +18 -17
  227. package/dist/testing/assertions.d.ts.map +1 -1
  228. package/dist/testing/assertions.js +2 -1
  229. package/dist/testing/attack_surface.d.ts.map +1 -1
  230. package/dist/testing/attack_surface.js +15 -9
  231. package/dist/testing/audit_completeness.d.ts +2 -2
  232. package/dist/testing/audit_completeness.d.ts.map +1 -1
  233. package/dist/testing/audit_completeness.js +53 -39
  234. package/dist/testing/auth_apps.d.ts +5 -4
  235. package/dist/testing/auth_apps.d.ts.map +1 -1
  236. package/dist/testing/auth_apps.js +28 -22
  237. package/dist/testing/data_exposure.d.ts.map +1 -1
  238. package/dist/testing/data_exposure.js +5 -5
  239. package/dist/testing/db.d.ts +1 -1
  240. package/dist/testing/db.d.ts.map +1 -1
  241. package/dist/testing/db.js +4 -4
  242. package/dist/testing/db_entities.d.ts +22 -0
  243. package/dist/testing/db_entities.d.ts.map +1 -0
  244. package/dist/testing/db_entities.js +28 -0
  245. package/dist/testing/entities.d.ts +10 -8
  246. package/dist/testing/entities.d.ts.map +1 -1
  247. package/dist/testing/entities.js +22 -18
  248. package/dist/testing/integration.d.ts.map +1 -1
  249. package/dist/testing/integration.js +13 -14
  250. package/dist/testing/integration_helpers.d.ts +8 -6
  251. package/dist/testing/integration_helpers.d.ts.map +1 -1
  252. package/dist/testing/integration_helpers.js +29 -23
  253. package/dist/testing/middleware.d.ts +15 -11
  254. package/dist/testing/middleware.d.ts.map +1 -1
  255. package/dist/testing/middleware.js +75 -32
  256. package/dist/testing/rpc_attack_surface.d.ts.map +1 -1
  257. package/dist/testing/rpc_attack_surface.js +40 -24
  258. package/dist/testing/rpc_helpers.d.ts.map +1 -1
  259. package/dist/testing/rpc_helpers.js +3 -1
  260. package/dist/testing/rpc_round_trip.d.ts +1 -1
  261. package/dist/testing/rpc_round_trip.d.ts.map +1 -1
  262. package/dist/testing/rpc_round_trip.js +14 -13
  263. package/dist/testing/sse_round_trip.d.ts +3 -4
  264. package/dist/testing/sse_round_trip.d.ts.map +1 -1
  265. package/dist/testing/sse_round_trip.js +7 -11
  266. package/dist/testing/standard.d.ts +1 -1
  267. package/dist/testing/stubs.d.ts +25 -0
  268. package/dist/testing/stubs.d.ts.map +1 -1
  269. package/dist/testing/stubs.js +43 -2
  270. package/dist/testing/surface_invariants.d.ts +2 -2
  271. package/dist/testing/ws_round_trip.d.ts +12 -13
  272. package/dist/testing/ws_round_trip.d.ts.map +1 -1
  273. package/dist/testing/ws_round_trip.js +24 -12
  274. package/dist/ui/AdminAccounts.svelte +23 -20
  275. package/dist/ui/AdminOverview.svelte +15 -13
  276. package/dist/ui/AdminOverview.svelte.d.ts.map +1 -1
  277. package/dist/ui/{AdminPermitHistory.svelte → AdminRoleGrantHistory.svelte} +12 -12
  278. package/dist/ui/AdminRoleGrantHistory.svelte.d.ts +4 -0
  279. package/dist/ui/AdminRoleGrantHistory.svelte.d.ts.map +1 -0
  280. package/dist/ui/BootstrapForm.svelte +1 -1
  281. package/dist/ui/CLAUDE.md +65 -59
  282. package/dist/ui/{PermitOfferForm.svelte → RoleGrantOfferForm.svelte} +37 -22
  283. package/dist/ui/RoleGrantOfferForm.svelte.d.ts +20 -0
  284. package/dist/ui/RoleGrantOfferForm.svelte.d.ts.map +1 -0
  285. package/dist/ui/{PermitOfferHistory.svelte → RoleGrantOfferHistory.svelte} +12 -12
  286. package/dist/ui/{PermitOfferHistory.svelte.d.ts → RoleGrantOfferHistory.svelte.d.ts} +4 -4
  287. package/dist/ui/RoleGrantOfferHistory.svelte.d.ts.map +1 -0
  288. package/dist/ui/{PermitOfferInbox.svelte → RoleGrantOfferInbox.svelte} +14 -14
  289. package/dist/ui/{PermitOfferInbox.svelte.d.ts → RoleGrantOfferInbox.svelte.d.ts} +4 -4
  290. package/dist/ui/RoleGrantOfferInbox.svelte.d.ts.map +1 -0
  291. package/dist/ui/SignupForm.svelte +1 -1
  292. package/dist/ui/SurfaceExplorer.svelte +35 -15
  293. package/dist/ui/SurfaceExplorer.svelte.d.ts.map +1 -1
  294. package/dist/ui/account_sessions_state.svelte.d.ts +2 -3
  295. package/dist/ui/account_sessions_state.svelte.d.ts.map +1 -1
  296. package/dist/ui/account_sessions_state.svelte.js +2 -3
  297. package/dist/ui/admin_accounts_state.svelte.d.ts +25 -18
  298. package/dist/ui/admin_accounts_state.svelte.d.ts.map +1 -1
  299. package/dist/ui/admin_accounts_state.svelte.js +28 -17
  300. package/dist/ui/admin_rpc_adapters.d.ts +20 -20
  301. package/dist/ui/admin_rpc_adapters.d.ts.map +1 -1
  302. package/dist/ui/admin_rpc_adapters.js +17 -17
  303. package/dist/ui/admin_sessions_state.svelte.d.ts +2 -2
  304. package/dist/ui/admin_sessions_state.svelte.js +2 -2
  305. package/dist/ui/audit_log_state.svelte.d.ts +7 -7
  306. package/dist/ui/audit_log_state.svelte.d.ts.map +1 -1
  307. package/dist/ui/audit_log_state.svelte.js +6 -6
  308. package/dist/ui/auth_state.svelte.d.ts +3 -3
  309. package/dist/ui/auth_state.svelte.d.ts.map +1 -1
  310. package/dist/ui/auth_state.svelte.js +6 -6
  311. package/dist/ui/format_scope.d.ts +2 -2
  312. package/dist/ui/format_scope.js +2 -2
  313. package/dist/ui/{permit_offers_state.svelte.d.ts → role_grant_offers_state.svelte.d.ts} +39 -31
  314. package/dist/ui/role_grant_offers_state.svelte.d.ts.map +1 -0
  315. package/dist/ui/{permit_offers_state.svelte.js → role_grant_offers_state.svelte.js} +25 -19
  316. package/dist/ui/ui_format.js +2 -2
  317. package/package.json +3 -3
  318. package/dist/auth/permit_offer_action_specs.d.ts.map +0 -1
  319. package/dist/auth/permit_offer_action_specs.js +0 -227
  320. package/dist/auth/permit_offer_actions.d.ts +0 -110
  321. package/dist/auth/permit_offer_actions.d.ts.map +0 -1
  322. package/dist/auth/permit_offer_actions.js +0 -452
  323. package/dist/auth/permit_offer_notifications.d.ts.map +0 -1
  324. package/dist/auth/permit_offer_notifications.js +0 -182
  325. package/dist/auth/permit_offer_queries.d.ts +0 -183
  326. package/dist/auth/permit_offer_queries.d.ts.map +0 -1
  327. package/dist/auth/permit_offer_queries.js +0 -408
  328. package/dist/auth/permit_offer_schema.d.ts +0 -103
  329. package/dist/auth/permit_offer_schema.d.ts.map +0 -1
  330. package/dist/auth/permit_queries.d.ts +0 -210
  331. package/dist/auth/permit_queries.d.ts.map +0 -1
  332. package/dist/auth/permit_queries.js +0 -294
  333. package/dist/auth/require_keeper.d.ts +0 -20
  334. package/dist/auth/require_keeper.d.ts.map +0 -1
  335. package/dist/auth/require_keeper.js +0 -35
  336. package/dist/auth/route_guards.d.ts +0 -21
  337. package/dist/auth/route_guards.d.ts.map +0 -1
  338. package/dist/auth/route_guards.js +0 -32
  339. package/dist/auth/session_lifecycle.d.ts +0 -37
  340. package/dist/auth/session_lifecycle.d.ts.map +0 -1
  341. package/dist/auth/session_lifecycle.js +0 -29
  342. package/dist/ui/AdminPermitHistory.svelte.d.ts +0 -4
  343. package/dist/ui/AdminPermitHistory.svelte.d.ts.map +0 -1
  344. package/dist/ui/PermitOfferForm.svelte.d.ts +0 -14
  345. package/dist/ui/PermitOfferForm.svelte.d.ts.map +0 -1
  346. package/dist/ui/PermitOfferHistory.svelte.d.ts.map +0 -1
  347. package/dist/ui/PermitOfferInbox.svelte.d.ts.map +0 -1
  348. package/dist/ui/permit_offers_state.svelte.d.ts.map +0 -1
@@ -1,103 +0,0 @@
1
- /**
2
- * Permit offer DDL, types, and client-safe schemas.
3
- *
4
- * An offer is a pending grant awaiting recipient consent. Lifecycle states
5
- * are mutually exclusive via a CHECK constraint (`permit_offer_single_terminal`):
6
- * at most one of `accepted_at` / `declined_at` / `retracted_at` may be set.
7
- * On accept, the offer's `resulting_permit_id` links to the permit row
8
- * produced by `query_accept_offer`.
9
- *
10
- * @module
11
- */
12
- import { z } from 'zod';
13
- import { Uuid } from '@fuzdev/fuz_util/id.js';
14
- /** Sentinel UUID used inside the partial unique indexes to collapse `scope_id IS NULL` into a comparable value. */
15
- export declare const PERMIT_OFFER_SCOPE_SENTINEL_UUID = "00000000-0000-0000-0000-000000000000";
16
- /** Maximum length of the optional message attached to an offer. */
17
- export declare const PERMIT_OFFER_MESSAGE_LENGTH_MAX = 500;
18
- /** Default TTL for a newly created offer — 30 days. Matches GitHub org-invite expiry. */
19
- export declare const PERMIT_OFFER_DEFAULT_TTL_MS: number;
20
- export declare const PERMIT_OFFER_SCHEMA = "\nCREATE TABLE IF NOT EXISTS permit_offer (\n id UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n from_actor_id UUID NOT NULL REFERENCES actor(id) ON DELETE CASCADE,\n to_account_id UUID NOT NULL REFERENCES account(id) ON DELETE CASCADE,\n role TEXT NOT NULL,\n scope_id UUID NULL,\n message TEXT NULL,\n created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n expires_at TIMESTAMPTZ NOT NULL,\n accepted_at TIMESTAMPTZ NULL,\n declined_at TIMESTAMPTZ NULL,\n decline_reason TEXT NULL,\n retracted_at TIMESTAMPTZ NULL,\n superseded_at TIMESTAMPTZ NULL,\n resulting_permit_id UUID NULL REFERENCES permit(id) ON DELETE SET NULL,\n CONSTRAINT permit_offer_single_terminal CHECK (\n (accepted_at IS NOT NULL)::int\n + (declined_at IS NOT NULL)::int\n + (retracted_at IS NOT NULL)::int\n + (superseded_at IS NOT NULL)::int\n <= 1\n ),\n CONSTRAINT permit_offer_permit_iff_accepted CHECK (\n (accepted_at IS NOT NULL) = (resulting_permit_id IS NOT NULL)\n ),\n CONSTRAINT permit_offer_reason_iff_declined CHECK (\n decline_reason IS NULL OR declined_at IS NOT NULL\n )\n)";
21
- /**
22
- * At most one pending offer per (to_account, role, scope, from_actor).
23
- *
24
- * Including `from_actor_id` in the tuple lets multiple grantors coexist —
25
- * teacher A and teacher B can each have a pending `classroom_student` offer
26
- * for the same student and scope. A same-grantor re-offer upserts the
27
- * existing pending row. `COALESCE` collapses `NULL` scopes into the
28
- * sentinel UUID so Postgres's NULL-in-unique-index quirk does not allow
29
- * duplicate global pending offers. The ON CONFLICT target in
30
- * `query_permit_offer_create` must match this expression literally.
31
- */
32
- export declare const PERMIT_OFFER_PENDING_UNIQUE_INDEX = "\nCREATE UNIQUE INDEX IF NOT EXISTS permit_offer_pending_unique\n ON permit_offer (\n to_account_id,\n role,\n COALESCE(scope_id, '00000000-0000-0000-0000-000000000000'::uuid),\n from_actor_id\n )\n WHERE accepted_at IS NULL\n AND declined_at IS NULL\n AND retracted_at IS NULL\n AND superseded_at IS NULL";
33
- /** Inbox lookup — pending offers for an account, ordered by soonest expiry. */
34
- export declare const PERMIT_OFFER_INBOX_INDEX = "\nCREATE INDEX IF NOT EXISTS permit_offer_inbox\n ON permit_offer (to_account_id, expires_at)\n WHERE accepted_at IS NULL\n AND declined_at IS NULL\n AND retracted_at IS NULL\n AND superseded_at IS NULL";
35
- /** Permit offer row as returned by the database. */
36
- export interface PermitOffer {
37
- id: Uuid;
38
- from_actor_id: Uuid;
39
- to_account_id: Uuid;
40
- role: string;
41
- scope_id: Uuid | null;
42
- message: string | null;
43
- created_at: string;
44
- expires_at: string;
45
- accepted_at: string | null;
46
- declined_at: string | null;
47
- decline_reason: string | null;
48
- retracted_at: string | null;
49
- /**
50
- * Set when the offer was obsoleted by an external event — a sibling
51
- * offer was accepted (yielding the permit this offer's role+scope maps to)
52
- * or the resulting permit for this (to_account, role, scope) was revoked.
53
- * Closes the "accept a pre-revoke offer to bypass the revoke" path.
54
- */
55
- superseded_at: string | null;
56
- resulting_permit_id: Uuid | null;
57
- }
58
- /**
59
- * A superseded offer row annotated with the grantor's `account_id`.
60
- *
61
- * Carried by `superseded_offers` in accept/revoke query results so callers
62
- * can fan out `permit_offer_supersede` notifications to the grantor's
63
- * sockets without a second round-trip. Populated via a CTE join on `actor`
64
- * in the supersede UPDATE.
65
- */
66
- export interface SupersededOffer extends PermitOffer {
67
- from_account_id: Uuid;
68
- }
69
- /**
70
- * Input for `query_permit_offer_create`.
71
- *
72
- * `expires_at` must be supplied — the query layer does not apply a default,
73
- * so callers can thread their own TTL (typically `PERMIT_OFFER_DEFAULT_TTL_MS`).
74
- */
75
- export interface CreatePermitOfferInput {
76
- from_actor_id: Uuid;
77
- to_account_id: Uuid;
78
- role: string;
79
- scope_id?: Uuid | null;
80
- message?: string | null;
81
- expires_at: Date;
82
- }
83
- /** Zod schema for client-safe permit offer data. */
84
- export declare const PermitOfferJson: z.ZodObject<{
85
- id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
86
- from_actor_id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
87
- to_account_id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
88
- role: z.ZodString;
89
- scope_id: z.ZodNullable<z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">>;
90
- message: z.ZodNullable<z.ZodString>;
91
- created_at: z.ZodString;
92
- expires_at: z.ZodString;
93
- accepted_at: z.ZodNullable<z.ZodString>;
94
- declined_at: z.ZodNullable<z.ZodString>;
95
- decline_reason: z.ZodNullable<z.ZodString>;
96
- retracted_at: z.ZodNullable<z.ZodString>;
97
- superseded_at: z.ZodNullable<z.ZodString>;
98
- resulting_permit_id: z.ZodNullable<z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">>;
99
- }, z.core.$strict>;
100
- export type PermitOfferJson = z.infer<typeof PermitOfferJson>;
101
- /** Convert a `PermitOffer` row to its JSON payload shape. */
102
- export declare const to_permit_offer_json: (offer: PermitOffer) => PermitOfferJson;
103
- //# sourceMappingURL=permit_offer_schema.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"permit_offer_schema.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/permit_offer_schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AACtB,OAAO,EAAC,IAAI,EAAC,MAAM,wBAAwB,CAAC;AAI5C,mHAAmH;AACnH,eAAO,MAAM,gCAAgC,yCAAyC,CAAC;AAEvF,mEAAmE;AACnE,eAAO,MAAM,+BAA+B,MAAM,CAAC;AAEnD,yFAAyF;AACzF,eAAO,MAAM,2BAA2B,QAA2B,CAAC;AAEpE,eAAO,MAAM,mBAAmB,6kCA6B9B,CAAC;AAEH;;;;;;;;;;GAUG;AACH,eAAO,MAAM,iCAAiC,8UAWhB,CAAC;AAE/B,+EAA+E;AAC/E,eAAO,MAAM,wBAAwB,0NAMP,CAAC;AAE/B,oDAAoD;AACpD,MAAM,WAAW,WAAW;IAC3B,EAAE,EAAE,IAAI,CAAC;IACT,aAAa,EAAE,IAAI,CAAC;IACpB,aAAa,EAAE,IAAI,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,IAAI,GAAG,IAAI,CAAC;IACtB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B;;;;;OAKG;IACH,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,mBAAmB,EAAE,IAAI,GAAG,IAAI,CAAC;CACjC;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,eAAgB,SAAQ,WAAW;IACnD,eAAe,EAAE,IAAI,CAAC;CACtB;AAED;;;;;GAKG;AACH,MAAM,WAAW,sBAAsB;IACtC,aAAa,EAAE,IAAI,CAAC;IACpB,aAAa,EAAE,IAAI,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,UAAU,EAAE,IAAI,CAAC;CACjB;AAED,oDAAoD;AACpD,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;kBA4CyD,CAAC;AACtF,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAE9D,6DAA6D;AAC7D,eAAO,MAAM,oBAAoB,GAAI,OAAO,WAAW,KAAG,eAexD,CAAC"}
@@ -1,210 +0,0 @@
1
- /**
2
- * Permit database queries.
3
- *
4
- * Permits are time-bounded, revocable grants of a role to an actor.
5
- * The system is safe by default — no permit, no capability.
6
- *
7
- * @module
8
- */
9
- import type { Uuid } from '@fuzdev/fuz_util/id.js';
10
- import type { QueryDeps } from '../db/query_deps.js';
11
- import type { Permit, GrantPermitInput } from './account_schema.js';
12
- import { type SupersededOffer } from './permit_offer_schema.js';
13
- /**
14
- * Grant a permit to an actor.
15
- * Idempotent — if an active permit already exists for this actor, role, and
16
- * scope, returns the existing permit instead of creating a duplicate.
17
- *
18
- * The `ON CONFLICT` target and the fallback `SELECT` both collapse `NULL`
19
- * scopes via the same sentinel used by the partial unique index
20
- * (`permit_actor_role_scope_active_unique`). The `IS NOT DISTINCT FROM`
21
- * form on the fallback is deliberate — plain `=` would miss the
22
- * NULL-scope case where the conflict fired.
23
- *
24
- * @param deps - query dependencies
25
- * @param input - the permit fields
26
- * @returns the created or existing active permit
27
- * @mutates `permit` table - inserts a row when no active permit matches `(actor_id, role, scope_id)`
28
- */
29
- export declare const query_grant_permit: (deps: QueryDeps, input: GrantPermitInput) => Promise<Permit>;
30
- /**
31
- * Look up the role of an active permit, constrained to a specific actor.
32
- *
33
- * Used by admin routes to inspect the permit's role before acting
34
- * (e.g., enforcing `web_grantable` on revoke). The actor constraint
35
- * mirrors `query_revoke_permit` so IDOR protection is consistent:
36
- * a caller can only see permits belonging to the target actor.
37
- *
38
- * Returns `null` if the permit is not found, already revoked, or
39
- * belongs to a different actor.
40
- *
41
- * @param deps - query dependencies
42
- * @param permit_id - the permit id to look up
43
- * @param actor_id - the actor that must own the permit
44
- * @returns `{role}` on a match, or `null`
45
- */
46
- export declare const query_permit_find_active_role_for_actor: (deps: QueryDeps, permit_id: string, actor_id: string) => Promise<{
47
- role: string;
48
- } | null>;
49
- /** Result of `query_revoke_permit` — the revoked permit plus any pending offers superseded by the revoke. */
50
- export interface RevokePermitResult {
51
- id: Uuid;
52
- role: string;
53
- scope_id: Uuid | null;
54
- /**
55
- * Pending offers for the revoked permit's `(account, role, scope)` that
56
- * were marked superseded as a side effect. Each entry carries its
57
- * grantor's `from_account_id` so callers can fan out
58
- * `permit_offer_supersede` notifications without a second round-trip.
59
- * The caller is responsible for emitting a `permit_offer_supersede`
60
- * audit event per entry (with `reason: 'permit_revoked'` and
61
- * `cause_id: <revoked permit id>`).
62
- */
63
- superseded_offers: Array<SupersededOffer>;
64
- }
65
- /**
66
- * Revoke a permit by id, constrained to a specific actor.
67
- *
68
- * Requires `actor_id` to prevent cross-account revocation (IDOR guard).
69
- * Returns `null` if the permit is not found, already revoked, or belongs
70
- * to a different actor.
71
- *
72
- * Supersedes any pending offers for the revoked permit's
73
- * `(to_account, role, scope)` in the same transaction. Prevents the
74
- * "accept a pre-revoke offer to bypass the revoke" path — any stale
75
- * offer becomes terminal at revoke time. A fresh post-revoke grant
76
- * requires the grantor to call `query_permit_offer_create` again.
77
- *
78
- * @param deps - query dependencies
79
- * @param permit_id - the permit to revoke
80
- * @param actor_id - the actor that must own the permit
81
- * @param revoked_by - the actor who revoked it (for audit trail)
82
- * @param reason - optional free-form reason, stamped on `permit.revoked_reason` and surfaced to the revokee notification.
83
- * @mutates `permit` row - sets `revoked_at`, `revoked_by`, and `revoked_reason`
84
- * @mutates `permit_offer` rows - stamps `superseded_at` on every pending sibling for the same `(account, role, scope)`
85
- */
86
- export declare const query_revoke_permit: (deps: QueryDeps, permit_id: Uuid, actor_id: Uuid, revoked_by: Uuid | null, reason?: string | null) => Promise<RevokePermitResult | null>;
87
- /**
88
- * Find all active (non-revoked, non-expired) permits for an actor.
89
- */
90
- export declare const query_permit_find_active_for_actor: (deps: QueryDeps, actor_id: string) => Promise<Array<Permit>>;
91
- /**
92
- * Check if an actor has an active permit for a given role.
93
- *
94
- * The `scope_id` parameter selects between global and scoped checks:
95
- * - Omitted or `null` — matches a global permit (`scope_id IS NULL`).
96
- * Pre-scope callers keep their existing semantics.
97
- * - A scope uuid — matches a permit bound to that exact scope.
98
- *
99
- * The `IS NOT DISTINCT FROM` comparison handles the NULL case uniformly.
100
- */
101
- export declare const query_permit_has_role: (deps: QueryDeps, actor_id: string, role: string, scope_id?: string | null) => Promise<boolean>;
102
- /**
103
- * List all permits for an actor (including revoked/expired).
104
- */
105
- export declare const query_permit_list_for_actor: (deps: QueryDeps, actor_id: string) => Promise<Array<Permit>>;
106
- /**
107
- * Find the account ID of an account that holds an active permit for a given role.
108
- *
109
- * Joins permit → actor → account. Returns the first match, or `null` if none.
110
- *
111
- * @param deps - query dependencies
112
- * @param role - the role to search for
113
- * @returns the account ID, or `null`
114
- */
115
- export declare const query_permit_find_account_id_for_role: (deps: QueryDeps, role: string) => Promise<string | null>;
116
- /** Result of `query_permit_revoke_for_scope` — every permit revoked plus every pending offer superseded by the scope-wide cascade. */
117
- export interface RevokeForScopeResult {
118
- /**
119
- * One entry per permit revoked by this call. Carries the revokee's
120
- * `account_id` so callers can fan out a `permit_revoke` notification per
121
- * permit. Empty array means no active permit was bound to the scope.
122
- */
123
- revoked: Array<{
124
- permit_id: Uuid;
125
- role: string;
126
- scope_id: Uuid;
127
- account_id: Uuid;
128
- }>;
129
- /**
130
- * Every pending offer at the scope — tuple-matched and orphan, undifferentiated
131
- * — superseded in the same cascade. Each entry carries its grantor's
132
- * `from_account_id` for `permit_offer_supersede` notification fan-out.
133
- *
134
- * The caller is responsible for emitting `permit_offer_supersede` audit
135
- * events with `reason: 'scope_destroyed'` and `cause_id: <destroyed scope row id>`
136
- * per entry — the cause of every supersede here is the scope deletion,
137
- * not any individual permit revoke (the revokes are themselves
138
- * consequences of the scope going away).
139
- */
140
- superseded_offers: Array<SupersededOffer>;
141
- }
142
- /**
143
- * Revoke every active permit bound to a scope and supersede every pending
144
- * offer at the scope, in one cascade.
145
- *
146
- * Use this from a consumer's parent-scope delete handler (e.g., classroom
147
- * deletion) — `permit.scope_id` and `permit_offer.scope_id` are polymorphic
148
- * with no FK constraint by design, so a parent row deletion would otherwise
149
- * orphan permits and offers. The cascade is **role-agnostic**: anything
150
- * attached to the destroyed scope is cleaned up.
151
- *
152
- * Both updates run as separate statements inside the caller's transaction
153
- * (mirrors `query_permit_revoke_role`'s shape). The two halves are
154
- * independent — orphan pending offers can exist at a scope with no active
155
- * permits, so the supersede half always runs even when no permit was
156
- * revoked.
157
- *
158
- * @param deps - query dependencies
159
- * @param scope_id - the scope whose permits and offers to terminate
160
- * @param revoked_by - the actor performing the cascade (audit trail)
161
- * @param reason - optional free-form reason, stamped on `permit.revoked_reason`.
162
- * @returns the revoked permits (with `account_id` for fan-out) and superseded offers (with `from_account_id` for fan-out)
163
- * @mutates `permit` table - sets `revoked_at`/`revoked_by`/`revoked_reason` on every active row at `scope_id`
164
- * @mutates `permit_offer` table - stamps `superseded_at` on every pending row at `scope_id`
165
- */
166
- export declare const query_permit_revoke_for_scope: (deps: QueryDeps, scope_id: Uuid, revoked_by: Uuid | null, reason?: string | null) => Promise<RevokeForScopeResult>;
167
- /** Result of `query_permit_revoke_role` — every permit revoked plus the pending offers superseded by the bulk revoke. */
168
- export interface RevokeRoleResult {
169
- /**
170
- * One entry per permit revoked by this call. Carries the revokee's
171
- * `account_id` so callers can fan out a `permit_revoke` notification per
172
- * scope-instance. Empty array means nothing was active for `(actor, role)`.
173
- */
174
- revoked: Array<{
175
- permit_id: string;
176
- role: string;
177
- scope_id: string | null;
178
- account_id: string;
179
- }>;
180
- /**
181
- * Pending offers for the actor's account+role (all scopes) superseded by
182
- * the bulk revoke. Each entry carries its grantor's `from_account_id` so
183
- * callers can fan out `permit_offer_supersede` notifications without a
184
- * second round-trip.
185
- */
186
- superseded_offers: Array<SupersededOffer>;
187
- }
188
- /**
189
- * Revoke every active permit an actor holds for a given role.
190
- *
191
- * With scoped permits a single actor+role tuple can hold several active
192
- * permits (one per scope), so this revokes all of them. Pass
193
- * `query_revoke_permit(permit_id, ...)` when a single scoped permit
194
- * is the target.
195
- *
196
- * Also supersedes pending offers for the actor's account across every
197
- * scope of this role (the actor can no longer hold the role, so any
198
- * pending offer of the same role is a bypass vector).
199
- *
200
- * @param deps - query dependencies
201
- * @param actor_id - the actor whose permits to revoke
202
- * @param role - the role to revoke
203
- * @param revoked_by - the actor who revoked it (for audit trail)
204
- * @param reason - optional free-form reason, stamped on `permit.revoked_reason`.
205
- * @returns the list of revoked permits (empty if none were active) and superseded pending offers
206
- * @mutates `permit` table - sets `revoked_at`/`revoked_by`/`revoked_reason` on every active row for `(actor, role)`
207
- * @mutates `permit_offer` table - stamps `superseded_at` on every matching pending offer
208
- */
209
- export declare const query_permit_revoke_role: (deps: QueryDeps, actor_id: string, role: string, revoked_by: string | null, reason?: string | null) => Promise<RevokeRoleResult>;
210
- //# sourceMappingURL=permit_queries.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"permit_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/permit_queries.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,wBAAwB,CAAC;AAEjD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,qBAAqB,CAAC;AACnD,OAAO,KAAK,EAAC,MAAM,EAAE,gBAAgB,EAAC,MAAM,qBAAqB,CAAC;AAElE,OAAO,EAAmC,KAAK,eAAe,EAAC,MAAM,0BAA0B,CAAC;AAEhG;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,kBAAkB,GAC9B,MAAM,SAAS,EACf,OAAO,gBAAgB,KACrB,OAAO,CAAC,MAAM,CA4BhB,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,uCAAuC,GACnD,MAAM,SAAS,EACf,WAAW,MAAM,EACjB,UAAU,MAAM,KACd,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAC,GAAG,IAAI,CAO/B,CAAC;AAEF,6GAA6G;AAC7G,MAAM,WAAW,kBAAkB;IAClC,EAAE,EAAE,IAAI,CAAC;IACT,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,IAAI,GAAG,IAAI,CAAC;IACtB;;;;;;;;OAQG;IACH,iBAAiB,EAAE,KAAK,CAAC,eAAe,CAAC,CAAC;CAC1C;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,mBAAmB,GAC/B,MAAM,SAAS,EACf,WAAW,IAAI,EACf,UAAU,IAAI,EACd,YAAY,IAAI,GAAG,IAAI,EACvB,SAAS,MAAM,GAAG,IAAI,KACpB,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAsCnC,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,kCAAkC,GAC9C,MAAM,SAAS,EACf,UAAU,MAAM,KACd,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CASvB,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,qBAAqB,GACjC,MAAM,SAAS,EACf,UAAU,MAAM,EAChB,MAAM,MAAM,EACZ,WAAW,MAAM,GAAG,IAAI,KACtB,OAAO,CAAC,OAAO,CAajB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,2BAA2B,GACvC,MAAM,SAAS,EACf,UAAU,MAAM,KACd,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAKvB,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,qCAAqC,GACjD,MAAM,SAAS,EACf,MAAM,MAAM,KACV,OAAO,CAAC,MAAM,GAAG,IAAI,CAavB,CAAC;AAEF,sIAAsI;AACtI,MAAM,WAAW,oBAAoB;IACpC;;;;OAIG;IACH,OAAO,EAAE,KAAK,CAAC;QAAC,SAAS,EAAE,IAAI,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,IAAI,CAAC;QAAC,UAAU,EAAE,IAAI,CAAA;KAAC,CAAC,CAAC;IAClF;;;;;;;;;;OAUG;IACH,iBAAiB,EAAE,KAAK,CAAC,eAAe,CAAC,CAAC;CAC1C;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,6BAA6B,GACzC,MAAM,SAAS,EACf,UAAU,IAAI,EACd,YAAY,IAAI,GAAG,IAAI,EACvB,SAAS,MAAM,GAAG,IAAI,KACpB,OAAO,CAAC,oBAAoB,CA2C9B,CAAC;AAEF,yHAAyH;AACzH,MAAM,WAAW,gBAAgB;IAChC;;;;OAIG;IACH,OAAO,EAAE,KAAK,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAC,CAAC,CAAC;IAC/F;;;;;OAKG;IACH,iBAAiB,EAAE,KAAK,CAAC,eAAe,CAAC,CAAC;CAC1C;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,wBAAwB,GACpC,MAAM,SAAS,EACf,UAAU,MAAM,EAChB,MAAM,MAAM,EACZ,YAAY,MAAM,GAAG,IAAI,EACzB,SAAS,MAAM,GAAG,IAAI,KACpB,OAAO,CAAC,gBAAgB,CA2C1B,CAAC"}
@@ -1,294 +0,0 @@
1
- /**
2
- * Permit database queries.
3
- *
4
- * Permits are time-bounded, revocable grants of a role to an actor.
5
- * The system is safe by default — no permit, no capability.
6
- *
7
- * @module
8
- */
9
- import { assert_row } from '../db/assert_row.js';
10
- import { PERMIT_OFFER_SCOPE_SENTINEL_UUID } from './permit_offer_schema.js';
11
- /**
12
- * Grant a permit to an actor.
13
- * Idempotent — if an active permit already exists for this actor, role, and
14
- * scope, returns the existing permit instead of creating a duplicate.
15
- *
16
- * The `ON CONFLICT` target and the fallback `SELECT` both collapse `NULL`
17
- * scopes via the same sentinel used by the partial unique index
18
- * (`permit_actor_role_scope_active_unique`). The `IS NOT DISTINCT FROM`
19
- * form on the fallback is deliberate — plain `=` would miss the
20
- * NULL-scope case where the conflict fired.
21
- *
22
- * @param deps - query dependencies
23
- * @param input - the permit fields
24
- * @returns the created or existing active permit
25
- * @mutates `permit` table - inserts a row when no active permit matches `(actor_id, role, scope_id)`
26
- */
27
- export const query_grant_permit = async (deps, input) => {
28
- const inserted = await deps.db.query_one(`INSERT INTO permit (actor_id, role, scope_id, expires_at, granted_by, source_offer_id)
29
- VALUES ($1, $2, $3, $4, $5, $6)
30
- ON CONFLICT (actor_id, role, COALESCE(scope_id, '${PERMIT_OFFER_SCOPE_SENTINEL_UUID}'::uuid))
31
- WHERE revoked_at IS NULL
32
- DO NOTHING
33
- RETURNING *`, [
34
- input.actor_id,
35
- input.role,
36
- input.scope_id ?? null,
37
- input.expires_at?.toISOString() ?? null,
38
- input.granted_by ?? null,
39
- input.source_offer_id ?? null,
40
- ]);
41
- if (inserted)
42
- return inserted;
43
- // Active permit already exists — return it (idempotent grant).
44
- const existing = await deps.db.query_one(`SELECT * FROM permit
45
- WHERE actor_id = $1
46
- AND role = $2
47
- AND scope_id IS NOT DISTINCT FROM $3
48
- AND revoked_at IS NULL`, [input.actor_id, input.role, input.scope_id ?? null]);
49
- return assert_row(existing, 'idempotent permit grant');
50
- };
51
- /**
52
- * Look up the role of an active permit, constrained to a specific actor.
53
- *
54
- * Used by admin routes to inspect the permit's role before acting
55
- * (e.g., enforcing `web_grantable` on revoke). The actor constraint
56
- * mirrors `query_revoke_permit` so IDOR protection is consistent:
57
- * a caller can only see permits belonging to the target actor.
58
- *
59
- * Returns `null` if the permit is not found, already revoked, or
60
- * belongs to a different actor.
61
- *
62
- * @param deps - query dependencies
63
- * @param permit_id - the permit id to look up
64
- * @param actor_id - the actor that must own the permit
65
- * @returns `{role}` on a match, or `null`
66
- */
67
- export const query_permit_find_active_role_for_actor = async (deps, permit_id, actor_id) => {
68
- const row = await deps.db.query_one(`SELECT role FROM permit
69
- WHERE id = $1 AND actor_id = $2 AND revoked_at IS NULL`, [permit_id, actor_id]);
70
- return row ?? null;
71
- };
72
- /**
73
- * Revoke a permit by id, constrained to a specific actor.
74
- *
75
- * Requires `actor_id` to prevent cross-account revocation (IDOR guard).
76
- * Returns `null` if the permit is not found, already revoked, or belongs
77
- * to a different actor.
78
- *
79
- * Supersedes any pending offers for the revoked permit's
80
- * `(to_account, role, scope)` in the same transaction. Prevents the
81
- * "accept a pre-revoke offer to bypass the revoke" path — any stale
82
- * offer becomes terminal at revoke time. A fresh post-revoke grant
83
- * requires the grantor to call `query_permit_offer_create` again.
84
- *
85
- * @param deps - query dependencies
86
- * @param permit_id - the permit to revoke
87
- * @param actor_id - the actor that must own the permit
88
- * @param revoked_by - the actor who revoked it (for audit trail)
89
- * @param reason - optional free-form reason, stamped on `permit.revoked_reason` and surfaced to the revokee notification.
90
- * @mutates `permit` row - sets `revoked_at`, `revoked_by`, and `revoked_reason`
91
- * @mutates `permit_offer` rows - stamps `superseded_at` on every pending sibling for the same `(account, role, scope)`
92
- */
93
- export const query_revoke_permit = async (deps, permit_id, actor_id, revoked_by, reason) => {
94
- const rows = await deps.db.query(`UPDATE permit SET revoked_at = NOW(), revoked_by = $3, revoked_reason = $4
95
- WHERE id = $1 AND actor_id = $2 AND revoked_at IS NULL
96
- RETURNING id, role, scope_id`, [permit_id, actor_id, revoked_by ?? null, reason ?? null]);
97
- const revoked = rows[0];
98
- if (!revoked)
99
- return null;
100
- // CTE joins `actor` after the UPDATE so each superseded row carries the
101
- // grantor's `account_id` — callers fan out `permit_offer_supersede`
102
- // notifications to that account without a second round-trip.
103
- const superseded_offers = await deps.db.query(`WITH updated AS (
104
- UPDATE permit_offer o
105
- SET superseded_at = NOW()
106
- FROM actor a
107
- WHERE a.id = $1
108
- AND o.to_account_id = a.account_id
109
- AND o.role = $2
110
- AND o.scope_id IS NOT DISTINCT FROM $3
111
- AND o.accepted_at IS NULL
112
- AND o.declined_at IS NULL
113
- AND o.retracted_at IS NULL
114
- AND o.superseded_at IS NULL
115
- RETURNING o.*
116
- )
117
- SELECT u.*, grantor.account_id AS from_account_id
118
- FROM updated u
119
- JOIN actor grantor ON grantor.id = u.from_actor_id`, [actor_id, revoked.role, revoked.scope_id]);
120
- return {
121
- id: revoked.id,
122
- role: revoked.role,
123
- scope_id: revoked.scope_id,
124
- superseded_offers,
125
- };
126
- };
127
- /**
128
- * Find all active (non-revoked, non-expired) permits for an actor.
129
- */
130
- export const query_permit_find_active_for_actor = async (deps, actor_id) => {
131
- return deps.db.query(`SELECT * FROM permit
132
- WHERE actor_id = $1
133
- AND revoked_at IS NULL
134
- AND (expires_at IS NULL OR expires_at > NOW())
135
- ORDER BY created_at`, [actor_id]);
136
- };
137
- /**
138
- * Check if an actor has an active permit for a given role.
139
- *
140
- * The `scope_id` parameter selects between global and scoped checks:
141
- * - Omitted or `null` — matches a global permit (`scope_id IS NULL`).
142
- * Pre-scope callers keep their existing semantics.
143
- * - A scope uuid — matches a permit bound to that exact scope.
144
- *
145
- * The `IS NOT DISTINCT FROM` comparison handles the NULL case uniformly.
146
- */
147
- export const query_permit_has_role = async (deps, actor_id, role, scope_id) => {
148
- const row = await deps.db.query_one(`SELECT EXISTS(
149
- SELECT 1 FROM permit
150
- WHERE actor_id = $1
151
- AND role = $2
152
- AND scope_id IS NOT DISTINCT FROM $3
153
- AND revoked_at IS NULL
154
- AND (expires_at IS NULL OR expires_at > NOW())
155
- ) AS exists`, [actor_id, role, scope_id ?? null]);
156
- return row?.exists ?? false;
157
- };
158
- /**
159
- * List all permits for an actor (including revoked/expired).
160
- */
161
- export const query_permit_list_for_actor = async (deps, actor_id) => {
162
- return deps.db.query(`SELECT * FROM permit WHERE actor_id = $1 ORDER BY created_at DESC`, [actor_id]);
163
- };
164
- /**
165
- * Find the account ID of an account that holds an active permit for a given role.
166
- *
167
- * Joins permit → actor → account. Returns the first match, or `null` if none.
168
- *
169
- * @param deps - query dependencies
170
- * @param role - the role to search for
171
- * @returns the account ID, or `null`
172
- */
173
- export const query_permit_find_account_id_for_role = async (deps, role) => {
174
- const row = await deps.db.query_one(`SELECT a.id AS account_id
175
- FROM permit p
176
- JOIN actor act ON act.id = p.actor_id
177
- JOIN account a ON a.id = act.account_id
178
- WHERE p.role = $1
179
- AND p.revoked_at IS NULL
180
- AND (p.expires_at IS NULL OR p.expires_at > NOW())
181
- LIMIT 1`, [role]);
182
- return row?.account_id ?? null;
183
- };
184
- /**
185
- * Revoke every active permit bound to a scope and supersede every pending
186
- * offer at the scope, in one cascade.
187
- *
188
- * Use this from a consumer's parent-scope delete handler (e.g., classroom
189
- * deletion) — `permit.scope_id` and `permit_offer.scope_id` are polymorphic
190
- * with no FK constraint by design, so a parent row deletion would otherwise
191
- * orphan permits and offers. The cascade is **role-agnostic**: anything
192
- * attached to the destroyed scope is cleaned up.
193
- *
194
- * Both updates run as separate statements inside the caller's transaction
195
- * (mirrors `query_permit_revoke_role`'s shape). The two halves are
196
- * independent — orphan pending offers can exist at a scope with no active
197
- * permits, so the supersede half always runs even when no permit was
198
- * revoked.
199
- *
200
- * @param deps - query dependencies
201
- * @param scope_id - the scope whose permits and offers to terminate
202
- * @param revoked_by - the actor performing the cascade (audit trail)
203
- * @param reason - optional free-form reason, stamped on `permit.revoked_reason`.
204
- * @returns the revoked permits (with `account_id` for fan-out) and superseded offers (with `from_account_id` for fan-out)
205
- * @mutates `permit` table - sets `revoked_at`/`revoked_by`/`revoked_reason` on every active row at `scope_id`
206
- * @mutates `permit_offer` table - stamps `superseded_at` on every pending row at `scope_id`
207
- */
208
- export const query_permit_revoke_for_scope = async (deps, scope_id, revoked_by, reason) => {
209
- // Revoke every active permit at the scope. CTE pulls `account_id` via a
210
- // join on `actor` so callers fan out `permit_revoke` notifications without
211
- // an extra round-trip.
212
- const revoked = await deps.db.query(`WITH updated AS (
213
- UPDATE permit
214
- SET revoked_at = NOW(), revoked_by = $2, revoked_reason = $3
215
- WHERE scope_id = $1 AND revoked_at IS NULL
216
- RETURNING id, role, scope_id, actor_id
217
- )
218
- SELECT u.id AS permit_id, u.role, u.scope_id, a.account_id
219
- FROM updated u
220
- JOIN actor a ON a.id = u.actor_id`, [scope_id, revoked_by ?? null, reason ?? null]);
221
- // Supersede every pending offer at the scope — tuple-matched or orphan,
222
- // no distinction. The cause of every supersede in this cascade is the
223
- // scope deletion; offers tuple-matched to a revoked permit are not
224
- // tagged separately because the revoke is itself a consequence of the
225
- // scope going away.
226
- const superseded_offers = await deps.db.query(`WITH updated AS (
227
- UPDATE permit_offer o
228
- SET superseded_at = NOW()
229
- WHERE o.scope_id = $1
230
- AND o.accepted_at IS NULL
231
- AND o.declined_at IS NULL
232
- AND o.retracted_at IS NULL
233
- AND o.superseded_at IS NULL
234
- RETURNING o.*
235
- )
236
- SELECT u.*, grantor.account_id AS from_account_id
237
- FROM updated u
238
- JOIN actor grantor ON grantor.id = u.from_actor_id`, [scope_id]);
239
- return { revoked, superseded_offers };
240
- };
241
- /**
242
- * Revoke every active permit an actor holds for a given role.
243
- *
244
- * With scoped permits a single actor+role tuple can hold several active
245
- * permits (one per scope), so this revokes all of them. Pass
246
- * `query_revoke_permit(permit_id, ...)` when a single scoped permit
247
- * is the target.
248
- *
249
- * Also supersedes pending offers for the actor's account across every
250
- * scope of this role (the actor can no longer hold the role, so any
251
- * pending offer of the same role is a bypass vector).
252
- *
253
- * @param deps - query dependencies
254
- * @param actor_id - the actor whose permits to revoke
255
- * @param role - the role to revoke
256
- * @param revoked_by - the actor who revoked it (for audit trail)
257
- * @param reason - optional free-form reason, stamped on `permit.revoked_reason`.
258
- * @returns the list of revoked permits (empty if none were active) and superseded pending offers
259
- * @mutates `permit` table - sets `revoked_at`/`revoked_by`/`revoked_reason` on every active row for `(actor, role)`
260
- * @mutates `permit_offer` table - stamps `superseded_at` on every matching pending offer
261
- */
262
- export const query_permit_revoke_role = async (deps, actor_id, role, revoked_by, reason) => {
263
- // CTE pulls the revokee's `account_id` via a join on `actor` so callers
264
- // can address the revokee without an extra round-trip.
265
- const revoked = await deps.db.query(`WITH updated AS (
266
- UPDATE permit
267
- SET revoked_at = NOW(), revoked_by = $3, revoked_reason = $4
268
- WHERE actor_id = $1 AND role = $2 AND revoked_at IS NULL
269
- RETURNING id, role, scope_id, actor_id
270
- )
271
- SELECT u.id AS permit_id, u.role, u.scope_id, a.account_id
272
- FROM updated u
273
- JOIN actor a ON a.id = u.actor_id`, [actor_id, role, revoked_by ?? null, reason ?? null]);
274
- if (revoked.length === 0) {
275
- return { revoked: [], superseded_offers: [] };
276
- }
277
- const superseded_offers = await deps.db.query(`WITH updated AS (
278
- UPDATE permit_offer o
279
- SET superseded_at = NOW()
280
- FROM actor a
281
- WHERE a.id = $1
282
- AND o.to_account_id = a.account_id
283
- AND o.role = $2
284
- AND o.accepted_at IS NULL
285
- AND o.declined_at IS NULL
286
- AND o.retracted_at IS NULL
287
- AND o.superseded_at IS NULL
288
- RETURNING o.*
289
- )
290
- SELECT u.*, grantor.account_id AS from_account_id
291
- FROM updated u
292
- JOIN actor grantor ON grantor.id = u.from_actor_id`, [actor_id, role]);
293
- return { revoked, superseded_offers };
294
- };
@@ -1,20 +0,0 @@
1
- /**
2
- * Keeper credential type guard.
3
- *
4
- * Two-part check:
5
- * 1. Credential type must be `daemon_token` (not session cookie, not API token).
6
- * 2. Account must hold active keeper permit.
7
- *
8
- * Both must pass. A session cookie from the bootstrap account still fails check #1.
9
- *
10
- * @module
11
- */
12
- import type { MiddlewareHandler } from 'hono';
13
- /**
14
- * Middleware that requires keeper credentials.
15
- *
16
- * Returns 401 if unauthenticated, 403 if credential type is not
17
- * `daemon_token` or if the keeper role is missing.
18
- */
19
- export declare const require_keeper: MiddlewareHandler;
20
- //# sourceMappingURL=require_keeper.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"require_keeper.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/require_keeper.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,MAAM,CAAC;AAW5C;;;;;GAKG;AACH,eAAO,MAAM,cAAc,EAAE,iBAmB5B,CAAC"}