permdock 0.1.0 → 0.2.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 (349) hide show
  1. package/README.md +106 -47
  2. package/dist/a2a/index.d.ts +7 -16
  3. package/dist/a2a/index.js +59 -98
  4. package/dist/adapter-context-mlWsVGGg.js +23 -0
  5. package/dist/ai-sdk/index.d.ts +37 -16
  6. package/dist/ai-sdk/index.js +30 -13
  7. package/dist/approvals/index.d.ts +3 -3
  8. package/dist/approvals/index.js +5 -4
  9. package/dist/{approvals-DaxY-Bdg.js → approvals-DbYAuNkG.js} +17 -11
  10. package/dist/approvers-DT4AVyIg.js +52 -0
  11. package/dist/{arazzo-CTeVuXr5.js → arazzo-BAgw9ap6.js} +2 -2
  12. package/dist/{arazzo-aoTkqYsJ.js → arazzo-cmvX2jDe.js} +2 -2
  13. package/dist/authzen/index.d.ts +4 -13
  14. package/dist/authzen/index.js +16 -50
  15. package/dist/better-auth/index.d.ts +20 -3
  16. package/dist/better-auth/index.js +27 -7
  17. package/dist/better-supabase/index.d.ts +206 -0
  18. package/dist/better-supabase/index.js +620 -0
  19. package/dist/bind-CVy3Espv.d.ts +25 -0
  20. package/dist/bind-VnYn2HKq.js +97 -0
  21. package/dist/boundary-BM8a_P6E.js +13 -0
  22. package/dist/boundary-BmHxVH1J.d.ts +15 -0
  23. package/dist/canonical-json-DemiK4Vp.js +26 -0
  24. package/dist/{capability-B68OFnZx.js → capability-4qPdO9gk.js} +7 -5
  25. package/dist/{capability-BDG_nkxt.d.ts → capability-Dq43tT1u.d.ts} +2 -2
  26. package/dist/catalog/index.d.ts +1 -1
  27. package/dist/catalog/index.js +1 -1
  28. package/dist/catalog-BjZSovXQ.js +2 -0
  29. package/dist/{catalog-doc-BSo_Q_-6.js → catalog-doc-Dsc9y7sd.js} +23 -12
  30. package/dist/{catalog-B0E6bggc.js → catalog-h2tlKFkq.js} +2 -2
  31. package/dist/{catalog-H-vFGwWK.js → catalog-z8q6bi_r.js} +4 -4
  32. package/dist/claude-agent/index.d.ts +5 -14
  33. package/dist/claude-agent/index.js +4 -10
  34. package/dist/clerk/index.d.ts +12 -3
  35. package/dist/clerk/index.js +26 -5
  36. package/dist/cli/bin.js +1 -1
  37. package/dist/cli/index.d.ts +3 -3
  38. package/dist/cli/index.js +2 -2
  39. package/dist/clients-C7zLFuWT.d.ts +12 -0
  40. package/dist/clients-DjU5ABMy.js +23 -0
  41. package/dist/cloud/index.d.ts +4 -5
  42. package/dist/cloud/index.js +20 -6
  43. package/dist/{cloud-BNQTrmrr.js → cloud-BGhHS-lY.js} +2 -2
  44. package/dist/{cloud-Cuxr6Czw.js → cloud-Bo3e9Ak3.js} +7 -6
  45. package/dist/{collect-O-tOtbnk.js → collect-DDDv_ESs.js} +194 -42
  46. package/dist/collect-DLlujdHC.js +83 -0
  47. package/dist/collect-rG2IQ3t0.js +2 -0
  48. package/dist/{commands-BMh_Etm2.js → commands-DWOrSQic.js} +16 -12
  49. package/dist/compile/index.d.ts +3 -0
  50. package/dist/compile/index.js +2 -0
  51. package/dist/{compile-D22iwvuV.js → compile-Ba4blROR.js} +85 -30
  52. package/dist/compile-CNnl6uIt.d.ts +94 -0
  53. package/dist/config-BJiM-xAm.js +157 -0
  54. package/dist/config-Bn00Z3ks.js +36 -0
  55. package/dist/config-report-Dw_JcUqT.js +44 -0
  56. package/dist/{connection-JvqwHODC.js → connection-CU8MQxRi.js} +1 -1
  57. package/dist/{context-CKEAabd7.js → context-Ttp0ekMT.js} +36 -27
  58. package/dist/convex/index.d.ts +9 -4
  59. package/dist/convex/index.js +14 -4
  60. package/dist/create-BWyx3jua.d.ts +245 -0
  61. package/dist/create-CPhDL6qx.js +253 -0
  62. package/dist/{credential-B_rkUNlY.js → credential-8P81ff4u.js} +26 -46
  63. package/dist/delegation-D8pUEuce.js +103 -0
  64. package/dist/{describe-Bt1YD5uS.js → describe-B_gVZEVi.js} +10 -15
  65. package/dist/{describe-DernyItf.d.ts → describe-PdoqESvg.d.ts} +9 -10
  66. package/dist/{diff-x5Il8LVa.js → diff-B8Tunc3o.js} +44 -11
  67. package/dist/{diff-BUzf5Poi.js → diff-CzqLxZ77.js} +2 -2
  68. package/dist/doctor-WpvG6p4k.js +1194 -0
  69. package/dist/doctor-collect-ByfYlNv1.js +664 -0
  70. package/dist/{doctor-Bqo6FwUa.js → doctor-rOr-4ZfS.js} +2 -2
  71. package/dist/drizzle/index.d.ts +4 -3
  72. package/dist/drizzle/index.js +3 -3
  73. package/dist/elysia/index.d.ts +6 -28
  74. package/dist/elysia/index.js +16 -60
  75. package/dist/{errors-DSMnAVNa.js → errors-CWfoNg9N.js} +8 -4
  76. package/dist/{errors-Iy71s7Oj.js → errors-Cz5zn0mW.js} +17 -71
  77. package/dist/{errors-DbZ1YOnb.d.ts → errors-DAqevpNk.d.ts} +8 -21
  78. package/dist/{errors-m3u9TmDu.js → errors-DRnGZHzF.js} +4 -1
  79. package/dist/evaluation-items-CfCCejQd.js +40 -0
  80. package/dist/{evaluations-DsAU9ppl.js → evaluations-tz6D9A2G.js} +28 -46
  81. package/dist/eve/index.d.ts +6 -15
  82. package/dist/eve/index.js +7 -12
  83. package/dist/expiry-BoBJ8Tvc.js +7 -0
  84. package/dist/express/index.d.ts +6 -29
  85. package/dist/express/index.js +28 -58
  86. package/dist/fastify/index.d.ts +6 -29
  87. package/dist/fastify/index.js +16 -65
  88. package/dist/fields-BMl1RxoX.js +26 -0
  89. package/dist/{files-B_wAGYmt.js → files-DWUN9PzI.js} +5 -1
  90. package/dist/{fixtures-Cvaw0gDG.js → fixtures-ht28Cplr.js} +5 -2
  91. package/dist/{freeze-y1_jQL4v.js → freeze-CrU8nQeX.js} +31 -2
  92. package/dist/{from-snapshot-BMGnhmgv.js → from-snapshot-C3sHpZNS.js} +65 -48
  93. package/dist/{grantee-Bv04_Vzf.js → grantee-Cv5Sogfv.js} +88 -7
  94. package/dist/graph-sql-B-dsAT6j.d.ts +29 -0
  95. package/dist/{headers-B5RRv3Xm.js → headers-BZSDieNT.js} +1 -1
  96. package/dist/{headers-C8SHQccD.d.ts → headers-Cf-zWvsO.d.ts} +1 -1
  97. package/dist/helpers-H_KARDVC.js +421 -0
  98. package/dist/hono/index.d.ts +6 -28
  99. package/dist/hono/index.js +11 -43
  100. package/dist/{hosted-BwKQ1BTA.js → hosted-DJQw46hn.js} +6 -5
  101. package/dist/{http-GgVApX1k.js → http-BIhmLfCc.js} +4 -20
  102. package/dist/http-SO7qGttf.js +44 -0
  103. package/dist/ids-DC4V4CFu.js +18 -0
  104. package/dist/{index-LF_hS-yn.d.ts → index-A0YNL8jZ.d.ts} +1 -1
  105. package/dist/index-CtE07CBF.d.ts +6 -0
  106. package/dist/{index-xIxJLwOQ.d.ts → index-D3vbkoSI.d.ts} +75 -7
  107. package/dist/{index-CltcQ4Bf.d.ts → index-D6_cSa1z.d.ts} +68 -5
  108. package/dist/index.d.ts +14 -9
  109. package/dist/index.js +22 -17
  110. package/dist/instance-options-BK_rNOSC.d.ts +11 -0
  111. package/dist/instance-options-Dhhcnm3y.js +22 -0
  112. package/dist/{parse-DPfIvFMU.js → json-schema-BsAflUGW.js} +28 -57
  113. package/dist/jwt/index.d.ts +10 -6
  114. package/dist/jwt/index.js +32 -13
  115. package/dist/kernel-GSQnMz3O.js +208 -0
  116. package/dist/kysely/index.d.ts +4 -3
  117. package/dist/kysely/index.js +3 -3
  118. package/dist/live-options-H2eG8dfJ.js +60 -0
  119. package/dist/{load-BZ24CpZP.js → load-DhWEB9P1.js} +16 -10
  120. package/dist/load-DwJUafYN.js +2 -0
  121. package/dist/manifest-iaoBXHKc.js +783 -0
  122. package/dist/manifest-uDS9y5de.d.ts +206 -0
  123. package/dist/{map-DowSO96j.js → map-AcxK9o_D.js} +4 -15
  124. package/dist/{map-claims-B_6I1Rn-.js → map-claims-Z4QZOiFF.js} +13 -4
  125. package/dist/map-error-pgUWNNtG.js +23 -0
  126. package/dist/may-use-DndQ5tQ-.js +77 -0
  127. package/dist/mcp/index.d.ts +54 -22
  128. package/dist/mcp/index.js +218 -178
  129. package/dist/nest/index.d.ts +26 -31
  130. package/dist/nest/index.js +75 -84
  131. package/dist/next/client.d.ts +7 -8
  132. package/dist/next/client.js +11 -9
  133. package/dist/next/index.d.ts +30 -47
  134. package/dist/next/index.js +45 -60
  135. package/dist/next/plugin.d.ts +1 -1
  136. package/dist/next/plugin.js +32 -1
  137. package/dist/node/index.d.ts +9 -28
  138. package/dist/node/index.js +11 -43
  139. package/dist/openai/index.d.ts +6 -15
  140. package/dist/openai/index.js +7 -20
  141. package/dist/openapi/index.d.ts +87 -4
  142. package/dist/openapi/index.js +3 -3
  143. package/dist/{openapi-CwePaF7C.js → openapi-Bbg4wGVL.js} +3 -3
  144. package/dist/{openapi-DcqM8nU7.js → openapi-BqRBvAng.js} +237 -5
  145. package/dist/{openapi-DWD0Uc_4.js → openapi-CqqEbHsL.js} +5 -5
  146. package/dist/{openapi-import-BWRUNNeW.js → openapi-import-cmwetbzw.js} +1 -1
  147. package/dist/{openapi-schema-D8j_T9tO.js → openapi-schema--ARjyfQr.js} +2 -2
  148. package/dist/orpc/index.d.ts +15 -28
  149. package/dist/orpc/index.js +74 -117
  150. package/dist/otel/index.d.ts +2 -2
  151. package/dist/parse-BuyjxhZV.js +53 -0
  152. package/dist/parse-local-manifest-BkGQNzAh.d.ts +10 -0
  153. package/dist/parse-local-manifest-BrMozJCo.js +36 -0
  154. package/dist/pdp/index.d.ts +2 -2
  155. package/dist/pdp/index.js +14 -127
  156. package/dist/{permdock-yoZ7EjxX.js → permdock-CzqXjCpU.js} +641 -126
  157. package/dist/{permissions-B8vCg_bI.d.ts → permissions-CDgvKG5L.d.ts} +2 -2
  158. package/dist/{permissions-CofRqesP.js → permissions-CpZrivYv.js} +3 -3
  159. package/dist/{permissions-DC7GROPA.js → permissions-DVoF6dMt.js} +77 -15
  160. package/dist/{permissions-B4apnahQ.d.ts → permissions-DttnqqxC.d.ts} +34 -5
  161. package/dist/{pins-CqE1jZuF.js → pins-8ILTftC9.js} +2 -2
  162. package/dist/{policy-CInmNGkM.d.ts → policy-CFZAr_Al.d.ts} +330 -37
  163. package/dist/{policy-Cq2fB8Q5.d.ts → policy-D8UFyERw.d.ts} +2 -2
  164. package/dist/{policy-ANp36PnI.js → policy-xyWg7QdR.js} +173 -22
  165. package/dist/powersync-A8x_Yyvh.js +2 -0
  166. package/dist/powersync-C7kLglqX.js +540 -0
  167. package/dist/powersync-yW1UUFzp.js +62 -0
  168. package/dist/prisma/index.d.ts +3 -2
  169. package/dist/prisma/index.js +45 -20
  170. package/dist/{problem-Cgp9-KZA.js → problem-B43ztKIm.js} +74 -109
  171. package/dist/problem-details-CkL50EEf.d.ts +7 -0
  172. package/dist/problem-details-_AMOYz-w.js +144 -0
  173. package/dist/{protected-BuPAycJT.d.ts → protected-CNsMzrVd.d.ts} +5 -4
  174. package/dist/protected-Cs6kTaHo.js +100 -0
  175. package/dist/protected-bZs6rbT1.d.ts +40 -0
  176. package/dist/{provider-wTAUda2v.d.ts → provider-Cjr62YMV.d.ts} +1 -1
  177. package/dist/provider-DLzpb2Xz.js +52 -0
  178. package/dist/provider-store-DNyC1y48.js +43 -0
  179. package/dist/react/index.d.ts +7 -5
  180. package/dist/react/index.js +5 -4
  181. package/dist/react/provider-client.d.ts +1 -1
  182. package/dist/react/provider-client.js +1 -1
  183. package/dist/react-native/index.d.ts +166 -10
  184. package/dist/react-native/index.js +607 -85
  185. package/dist/{refs-ChLSxbST.js → refs-KYb4ntaz.js} +4 -3
  186. package/dist/{relations-r4soIFZB.js → relations-cjOL01hZ.js} +110 -31
  187. package/dist/replay-BsOyTrdV.js +28 -0
  188. package/dist/replay-sqBGMnNs.d.ts +5 -0
  189. package/dist/resource-ref-CaS9yVPi.js +20 -0
  190. package/dist/revocations-Bhr0aMIC.js +27 -0
  191. package/dist/rls-BMNydpHk.js +2521 -0
  192. package/dist/{rls-BOFU3chG.js → rls-CEvs64Kj.js} +8 -3
  193. package/dist/rls-fields-BMasREhR.js +3502 -0
  194. package/dist/{rls-graph-DxQ39PJj.js → rls-graph-pc9fuXYm.js} +100 -36
  195. package/dist/{rls-import-CXV6LBlS.js → rls-import-D8F7j35Q.js} +7 -6
  196. package/dist/{rls-migrate-DrFPbf8p.js → rls-migrate-7wikfrVI.js} +6 -5
  197. package/dist/rls-ownership-B5Lkq62W.js +843 -0
  198. package/dist/rls-shared-aOQ4hGTO.js +493 -0
  199. package/dist/rls-verify-D8B7dOTh.js +745 -0
  200. package/dist/{row-check-BajTgQZ-.d.ts → row-check-Dt35czLy.d.ts} +12 -15
  201. package/dist/rpc-DOK54_CS.js +89 -0
  202. package/dist/{run-CJ6UvS3a.js → run-YXCJDNYk.js} +31 -12
  203. package/dist/runtime-CB8gQFvS.js +11 -0
  204. package/dist/{saas-BRrZzJkM.js → saas-Bj_FFkjN.js} +17 -7
  205. package/dist/{schema-P8yoma__.js → schema-D3MnDMxa.js} +8 -4
  206. package/dist/scim/index.d.ts +1 -1
  207. package/dist/scim/index.js +1 -1
  208. package/dist/{scim-9NFa0XVZ.js → scim-BFGBBAqR.js} +1 -1
  209. package/dist/{scopes-cKVp_WTw.js → scopes-CVdPrWN-.js} +35 -8
  210. package/dist/server/index.d.ts +22 -11
  211. package/dist/server/index.js +11 -8
  212. package/dist/{credentials-BgRX3WEg.js → server-DlZ28Oz5.js} +1 -1
  213. package/dist/shared-DRu8VFVM.js +123 -0
  214. package/dist/{sink-Cfr9-7YC.d.ts → sink-Gs6TTVa-.d.ts} +1 -1
  215. package/dist/skills-Biq5uup3.js +192 -0
  216. package/dist/{skills-C_lpV4Jx.js → skills-JaymNCdK.js} +10 -4
  217. package/dist/{snapshot-DE8dE_hz.js → snapshot-Enxd0n-s.js} +138 -214
  218. package/dist/snapshot-cache-D6jLelln.js +44 -0
  219. package/dist/snapshot-cache-DwQH8ufx.d.ts +45 -0
  220. package/dist/solid/index.d.ts +39 -9
  221. package/dist/solid/index.js +76 -85
  222. package/dist/sources-B0UvrJ21.js +534 -0
  223. package/dist/sources-BcJgw0lt.d.ts +192 -0
  224. package/dist/{sql-Bll1d7y0.js → sql-xdg5f4Fi.js} +6 -1
  225. package/dist/{src-CTNT-PBW.js → src-CSbUiozF.js} +149 -11
  226. package/dist/ssf/index.d.ts +3 -2
  227. package/dist/ssf/index.js +3 -29
  228. package/dist/store-DMAWwAJV.js +371 -0
  229. package/dist/{store-Bq90395i.d.ts → store-D_pjn91-.d.ts} +13 -4
  230. package/dist/store-options-yO0V6gRn.js +716 -0
  231. package/dist/{stores-CkG2HrSg.js → stores-4_sqiOA1.js} +10 -63
  232. package/dist/{stores-CvOnu_2T.d.ts → stores-DVR5NHLE.d.ts} +20 -9
  233. package/dist/{stream-Q_xlB6Ko.js → stream-DDpvynSB.js} +16 -2
  234. package/dist/{stream-BhMfZOwr.d.ts → stream-DlvG2hs-.d.ts} +2 -2
  235. package/dist/subject-settings-BwBmCdTR.js +128 -0
  236. package/dist/supabase/index.d.ts +122 -6
  237. package/dist/supabase/index.js +4 -3
  238. package/dist/supabase/middleware.d.ts +32 -27
  239. package/dist/supabase/middleware.js +56 -22
  240. package/dist/{supabase-D8UlBA7Z.js → supabase-CwafwxxF.js} +3 -3
  241. package/dist/{supabase-DTxGtYdi.js → supabase-DTBaC20O.js} +345 -367
  242. package/dist/supabase-config-Bs44dare.js +472 -0
  243. package/dist/{supabase-hook-D_OqGb_V.js → supabase-hook-BuxLQLpU.js} +994 -708
  244. package/dist/supabase-hook-D5qVd_D8.js +2 -0
  245. package/dist/supautils-DYZnzRWV.js +91 -0
  246. package/dist/svelte/PermissionBoundary.svelte +37 -0
  247. package/dist/svelte/Protected.svelte +8 -2
  248. package/dist/svelte/index.d.ts +9 -6
  249. package/dist/svelte/index.js +61 -7
  250. package/dist/svelte/runtime.d.ts +4 -3
  251. package/dist/svelte/runtime.js +4 -3
  252. package/dist/svelte/source.d.ts +9 -6
  253. package/dist/svelte/source.js +7 -4
  254. package/dist/terminal/index.d.ts +6 -14
  255. package/dist/terminal/index.js +44 -32
  256. package/dist/testing/index.d.ts +105 -16
  257. package/dist/testing/index.js +504 -40
  258. package/dist/testing/saas/index.d.ts +4 -4
  259. package/dist/testing/saas/index.js +2 -2
  260. package/dist/testing/saas/permissions.d.ts +1 -1
  261. package/dist/testing/saas/permissions.js +1 -1
  262. package/dist/token-CIWxJOCh.js +50 -0
  263. package/dist/trpc/index.d.ts +7 -26
  264. package/dist/trpc/index.js +17 -100
  265. package/dist/types-9AWO9EGt.d.ts +197 -0
  266. package/dist/{types-D6Z6-rKJ.d.ts → types-BPxgszlT.d.ts} +260 -22
  267. package/dist/{types-BvBk1wgn.d.ts → types-BVyImy5I.d.ts} +5 -3
  268. package/dist/{types-CxquUByI.d.ts → types-Bvc4oWT6.d.ts} +1 -1
  269. package/dist/types-C3s9xVJG.d.ts +48 -0
  270. package/dist/{types-BBK-exvJ.d.ts → types-Dnemwd77.d.ts} +7 -1
  271. package/dist/types-kxznc7cn.d.ts +52 -0
  272. package/dist/{types-B8w5PFtS.d.ts → types-vKyAMtcX.d.ts} +17 -1
  273. package/dist/{types-B9dmiiab.d.ts → types-vNSDF7Dq.d.ts} +1 -1
  274. package/dist/{sources-DOpW5Ila.d.ts → types-yF5LmQVD.d.ts} +86 -127
  275. package/dist/unplugin/index.d.ts +1 -1
  276. package/dist/unplugin/index.js +10 -9
  277. package/dist/{usage-1ozMubLY.js → usage-D07N9QJ3.js} +2 -2
  278. package/dist/usage-D82l1CwK.js +2 -0
  279. package/dist/{usage-CUnK6Ou9.js → usage-DG1oeCca.js} +225 -62
  280. package/dist/validation-error-CnjZ4t92.js +33 -0
  281. package/dist/validation-error-CtF23SrC.d.ts +26 -0
  282. package/dist/{validity-D0uRmJKJ.js → validity-uOp8UpYn.js} +57 -36
  283. package/dist/{verifier-B90agnhl.js → verifier-BxBo6I44.js} +10 -5
  284. package/dist/version-BvacVTZR.js +17 -0
  285. package/dist/views-Soe7bGSC.js +95 -0
  286. package/dist/{vocabulary-Dk0fs-Jk.d.ts → vocabulary-DA8a8zif.d.ts} +1 -1
  287. package/dist/vue/index.d.ts +35 -13
  288. package/dist/vue/index.js +86 -93
  289. package/dist/watch-Etoa_Mvr.js +2 -0
  290. package/dist/watch-UihESpsJ.js +128 -0
  291. package/dist/webmcp/index.d.ts +2 -2
  292. package/dist/webmcp/index.js +7 -13
  293. package/package.json +62 -36
  294. package/schemas/approval-request-v1.json +175 -0
  295. package/schemas/catalog-v1.json +29 -1
  296. package/schemas/supabase-claims-v1.json +6 -1
  297. package/schemas/supabase-manifest-v1.json +150 -6
  298. package/skills/permdock/SKILL.md +7 -7
  299. package/skills/permdock-agents/SKILL.md +5 -5
  300. package/skills/permdock-agents/references/runtimes.md +12 -10
  301. package/skills/permdock-approvals/SKILL.md +4 -4
  302. package/skills/permdock-approvals/references/stores-and-resume.md +15 -4
  303. package/skills/permdock-audit/SKILL.md +3 -3
  304. package/skills/permdock-credentials/SKILL.md +4 -4
  305. package/skills/permdock-credentials/references/tokens-keys-links.md +7 -2
  306. package/skills/permdock-data/SKILL.md +4 -4
  307. package/skills/permdock-data/references/queries-and-rls.md +14 -6
  308. package/skills/permdock-data/references/relationships.md +5 -3
  309. package/skills/permdock-tenancy/SKILL.md +9 -6
  310. package/skills/permdock-tenancy/references/ownership-and-custom-roles.md +14 -8
  311. package/skills/permdock-wire/SKILL.md +4 -4
  312. package/skills/permdock-wire/references/adapters.md +42 -30
  313. package/skills/permdock-wire/references/policy-options.md +5 -3
  314. package/dist/approvers-aH0PJkmf.js +0 -20
  315. package/dist/catalog-idsHc0vo.js +0 -2
  316. package/dist/collect-B7_TIfmg.js +0 -49
  317. package/dist/collect-fRPAQIWb.js +0 -2
  318. package/dist/compile-CQbvyIBr.d.ts +0 -30
  319. package/dist/config-BPYmjTOp.js +0 -26
  320. package/dist/context-gtAwAILx.js +0 -6
  321. package/dist/create-BKF3dgFk.d.ts +0 -162
  322. package/dist/create-CSkhDU9T.js +0 -222
  323. package/dist/doctor-BGle8Gqi.js +0 -1314
  324. package/dist/helpers-DnSOrhB9.js +0 -682
  325. package/dist/index-fG6NSXlc.d.ts +0 -9
  326. package/dist/kernel-YOGCWNLf.js +0 -211
  327. package/dist/load-QOOm1z70.js +0 -2
  328. package/dist/manifest-DDQbC6K0.d.ts +0 -118
  329. package/dist/map-error-RJ3ZsUyA.js +0 -16
  330. package/dist/may-use-CnFEjVEp.js +0 -37
  331. package/dist/pg-4GJWDUxG.js +0 -953
  332. package/dist/plugin-4gr7IxFJ.js +0 -65
  333. package/dist/protected-B4K3BKoX.js +0 -138
  334. package/dist/protected-wY7nXhY9.d.ts +0 -23
  335. package/dist/provider-_wx46HpB.js +0 -48
  336. package/dist/rls-CJVja9Ww.js +0 -2119
  337. package/dist/rls-helpers-BxRuif0l.js +0 -1346
  338. package/dist/runtime-DP9ttFh1.js +0 -27
  339. package/dist/skills-Dx3QLnMY.js +0 -110
  340. package/dist/source-DoBBYJi2.js +0 -6
  341. package/dist/sources-DVHM3Mwq.js +0 -284
  342. package/dist/store-C6hFW6ta.js +0 -508
  343. package/dist/subject-settings-xt7V845g.js +0 -82
  344. package/dist/supabase-hook-DW-xP4hU.js +0 -2
  345. package/dist/types-DbWdEfcm.d.ts +0 -87
  346. package/dist/types-bva3i4Gf.d.ts +0 -98
  347. package/dist/types-i4BAOvdb.d.ts +0 -97
  348. package/dist/usage-PyEor6GA.js +0 -2
  349. package/dist/version-CnSh33Fj.js +0 -16
@@ -1,21 +1,31 @@
1
1
  import { n as isReadonlyArray, r as sole, t as compact } from "./compact-y6vfu2_f.js";
2
- import { n as freezeDeep, t as freezeCopy } from "./freeze-y1_jQL4v.js";
3
- import { C as ownGet, S as isForbiddenKey, a as formerKeys, c as getResource, d as isFieldRelation, g as listPermissions, i as findPermission, p as isPrincipalRelation, r as expandRelation, u as isEdgeRelation } from "./permissions-DC7GROPA.js";
4
- import { a as hasConditionOp } from "./refs-ChLSxbST.js";
5
- import { d as role, h as normalizeWhere, i as declaredRoleNames$1, l as normalizeApproval, n as assertApprovalRelations, p as applyQuota, s as grantList, u as normalizeAssurance } from "./policy-ANp36PnI.js";
6
- import { a as resolveScope, c as scopeChain, f as tenantOf, i as normalizeMemberships, l as scopeIdOf, o as rootMembershipId, r as findScope, s as rootScope, u as scopeList } from "./scopes-cKVp_WTw.js";
7
- import { S as isRole, T as synthesiseRole, a as authenticated, b as findRole, f as relationCondition, h as resourceRoleCondition, i as assurance, l as matchGrantee, o as combineWhere, r as asGrantee, s as flattenGrantee, w as listRoles } from "./grantee-Bv04_Vzf.js";
8
- import { a as approvalMessage, i as PermDockValidationError, n as PermDockDeniedError, o as deniedMessage, s as wireDenials, t as PermDockApprovalRequiredError } from "./errors-Iy71s7Oj.js";
9
- import { a as whereFromGrants, c as versionOf, d as delegatedPermissions, f as resourceIdOf, g as evaluateCondition, h as simulateArazzo, i as snapshotGrant, l as canonicalJson, m as isArazzoSimulateInput, o as decisionToken, r as signSnapshot, s as payloadDigest, t as buildSnapshot, u as coveredByDelegation } from "./snapshot-DE8dE_hz.js";
2
+ import { n as freezeDeep, t as freezeCopy } from "./freeze-CrU8nQeX.js";
3
+ import { E as ownGet, T as isForbiddenKey, _ as listPermissions, a as formerKeys, c as getResource, d as isEdgeRelation, f as isFieldRelation, i as findPermission, m as isPrincipalRelation, o as formerScopes, r as expandRelation } from "./permissions-DVoF6dMt.js";
4
+ import { a as hasConditionOp } from "./refs-KYb4ntaz.js";
5
+ import { d as normalizeApproval, f as normalizeAssurance, g as applyQuota, i as declaredRoleNames$1, l as levelCondition, m as role, n as assertApprovalRelations, s as grantList, u as levelNames, v as normalizeWhere } from "./policy-xyWg7QdR.js";
6
+ import { n as PermDockValidationError } from "./validation-error-CnjZ4t92.js";
7
+ import { a as wireDenials } from "./problem-details-_AMOYz-w.js";
8
+ import { a as deniedMessage, i as approvalMessage, n as PermDockDeniedError, t as PermDockApprovalRequiredError } from "./errors-Cz5zn0mW.js";
9
+ import { n as sameId } from "./ids-DC4V4CFu.js";
10
+ import { a as normalizeMemberships, c as rootScope, d as scopeList, f as subjectForPermission, i as keepsPermission, l as scopeChain, m as tenantOf, o as resolveScope, s as rootMembershipId, u as scopeIdOf } from "./scopes-CVdPrWN-.js";
11
+ import { a as whereFromGrants, c as rowIdOf, d as isArazzoSimulateInput, f as simulateArazzo, i as signSnapshot, l as rowValues, p as evaluateCondition, r as snapshotGrant, s as isRowPair, t as buildSnapshot } from "./snapshot-Enxd0n-s.js";
10
12
  import { t as parseDuration } from "./duration-xK2jGFVc.js";
11
- import { a as matchScopedMembership, c as partitionsOf, f as tenantsOf, h as sanitizeContext, i as isMembershipExpired, l as relatesTo, m as pickVisible, p as grantCoversField, r as inTeam, s as nowSeconds, t as isActive, u as resolveActiveTenant } from "./validity-D0uRmJKJ.js";
13
+ import { D as synthesiseRole, E as listRoles, S as findRole, _ as resourceRoleCondition, a as authenticated, d as matchGrantee, i as assurance, m as relationCondition, o as combineWhere, r as asGrantee, s as flattenGrantee, w as isRole } from "./grantee-Cv5Sogfv.js";
14
+ import { t as canonicalJson } from "./canonical-json-DemiK4Vp.js";
15
+ import { n as pickVisible, r as sanitizeContext, t as grantCoversField } from "./fields-BMl1RxoX.js";
16
+ import { a as isMembershipExpired, d as partitionsOf, f as relatesTo, g as tenantsOf, h as scopeField, i as inTeam, o as matchScopedMembership, p as resolveActiveTenant, r as heldRoleNamesIn, t as isActive, u as nowSeconds } from "./validity-uOp8UpYn.js";
12
17
  import { t as requiresApproval } from "./approval-required-TzNB9oQA.js";
13
- import { r as mergeHostedGrants, t as isPortableCondition } from "./hosted-BwKQ1BTA.js";
18
+ import { r as mergeHostedGrants, t as isPortableCondition } from "./hosted-DJQw46hn.js";
14
19
  import { n as isThenable, t as ignoreRejection } from "./thenable-okc2xDsj.js";
15
20
  import { t as byCodePoint } from "./compare-pfyVnBjE.js";
16
- import { a as relationWalk, i as relationReader, n as pendingRelations, o as resolveRelated, r as relationId } from "./relations-r4soIFZB.js";
21
+ import { a as relationWalk, i as relationReader, n as pendingRelations, o as resolveRelated, r as relationId } from "./relations-cjOL01hZ.js";
22
+ import { i as requiresStandIn, n as coversRequirements, r as delegatedPermissions, t as coveredByDelegation } from "./delegation-D8pUEuce.js";
23
+ import { t as resourceIdOf } from "./resource-ref-CaS9yVPi.js";
24
+ import { n as payloadDigest, r as versionOf, t as decisionToken } from "./token-CIWxJOCh.js";
17
25
  import { i as isSubject, r as isPrincipal, t as anonymousSubject } from "./subject-C6dLxqHU.js";
18
26
  //#region src/core/approval-policies.ts
27
+ /** The denial detail when an `ApprovalPolicySource` fails: every call an allow would grant denies with reason `approval`. */
28
+ const APPROVAL_POLICY_UNAVAILABLE = "approval-policy-unavailable";
19
29
  function memoryApprovalPolicies(policies) {
20
30
  const entries = [...policies];
21
31
  return Object.freeze({ approvalPoliciesFor(query) {
@@ -28,17 +38,13 @@ function isRecord(value) {
28
38
  function isStringList(value) {
29
39
  return Array.isArray(value) && value.every((item) => typeof item === "string");
30
40
  }
31
- /**
32
- * One raw entry checked against the policy: `null` when it names no
33
- * permission the policy declares (it applies to nothing), `undefined` when
34
- * it is invalid.
35
- */
36
- function loadOne(policy, raw) {
37
- if (!isRecord(raw) || typeof raw["permission"] !== "string") return;
41
+ function checkOne(policy, raw) {
42
+ if (!isRecord(raw) || typeof raw["permission"] !== "string") return { problem: "invalid" };
38
43
  const permission = findPermission(policy.permissions, raw["permission"]);
39
- if (permission === void 0) return null;
44
+ if (permission === void 0) return { problem: "unknown-permission" };
40
45
  const { tenant, actors, where, check, approval } = raw;
41
- if (tenant !== void 0 && typeof tenant !== "string" || actors !== void 0 && !isStringList(actors) || where !== void 0 && !isPortableCondition(where) || check !== void 0 && !isPortableCondition(check) || isRecord(approval) && approval["escalation"] !== void 0 || approval !== "human" && !isRecord(approval)) return;
46
+ if (tenant !== void 0 && typeof tenant !== "string" || actors !== void 0 && !isStringList(actors) || where !== void 0 && !isPortableCondition(where) || check !== void 0 && !isPortableCondition(check) || approval !== "human" && !isRecord(approval)) return { problem: "invalid" };
47
+ if (where !== void 0 && permission.kind !== "instance") return { problem: "where-on-collection" };
42
48
  let normalized;
43
49
  try {
44
50
  normalized = normalizeApproval(approval, permission.key);
@@ -47,18 +53,47 @@ function loadOne(policy, raw) {
47
53
  approval: normalized
48
54
  }], policy.resources);
49
55
  } catch {
50
- return;
56
+ return { problem: "invalid" };
51
57
  }
52
- if (normalized === void 0) return;
53
- if (normalized !== "human" && normalized.staleOn !== void 0 && (permission.kind !== "instance" || policy.resources.get(permission.resource)?.version === void 0)) return;
54
- return compact({
58
+ if (normalized === void 0) return { problem: "invalid" };
59
+ if (normalized !== "human" && normalized.staleOn !== void 0 && (permission.kind !== "instance" || policy.resources.get(permission.resource)?.version === void 0)) return { problem: "stale-on-without-version" };
60
+ return { entry: compact({
55
61
  permission: permission.key,
56
62
  tenant,
57
63
  actors: actors === void 0 ? void 0 : new Set(actors),
58
64
  where: where === void 0 ? void 0 : normalizeWhere(where),
59
65
  check: check === void 0 ? void 0 : normalizeWhere(check),
60
66
  approval: normalized
61
- });
67
+ }) };
68
+ }
69
+ /**
70
+ * Checks one entry against the policy the way `approvalPoliciesFor` loads
71
+ * it, for an application to refuse a bad entry when it is saved instead of
72
+ * finding every call denied. Never throws.
73
+ */
74
+ function validateApprovalPolicy(policy, entry) {
75
+ try {
76
+ const checked = checkOne(policy, entry);
77
+ return "problem" in checked ? {
78
+ ok: false,
79
+ problem: checked.problem
80
+ } : { ok: true };
81
+ } catch {
82
+ return {
83
+ ok: false,
84
+ problem: "invalid"
85
+ };
86
+ }
87
+ }
88
+ /**
89
+ * One raw entry checked against the policy: `null` when it names no
90
+ * permission the policy declares (it applies to nothing), `undefined` when
91
+ * it does not load.
92
+ */
93
+ function loadOne(policy, raw) {
94
+ const checked = checkOne(policy, raw);
95
+ if ("entry" in checked) return checked.entry;
96
+ return checked.problem === "unknown-permission" ? null : void 0;
62
97
  }
63
98
  function loadAll(policy, raw) {
64
99
  if (!Array.isArray(raw)) return "failed";
@@ -103,6 +138,17 @@ function asStages(approval) {
103
138
  quorum: approval.quorum
104
139
  })];
105
140
  }
141
+ /** An entry's stages, each carrying the entry's escalation unless it has its own. */
142
+ function entryStages(approval) {
143
+ const stages = asStages(approval);
144
+ if (approval === "human" || approval.escalation === void 0) return stages;
145
+ const escalation = approval.escalation;
146
+ return stages.map((stage) => stage.escalation === void 0 ? compact({
147
+ by: stage.by,
148
+ quorum: stage.quorum,
149
+ escalation
150
+ }) : stage);
151
+ }
106
152
  function shorter(a, b) {
107
153
  if (a === void 0) return b;
108
154
  if (b === void 0) return a;
@@ -124,8 +170,9 @@ function tightenApproval(code, entries, input) {
124
170
  let distinct = false;
125
171
  let staleOn;
126
172
  let ttl;
127
- for (const part of parts) {
128
- for (const stage of asStages(part)) {
173
+ for (const [index, part] of parts.entries()) {
174
+ const fromCode = code !== void 0 && index === 0;
175
+ for (const stage of fromCode ? asStages(part) : entryStages(part)) {
129
176
  const key = JSON.stringify(stage);
130
177
  if (!seen.has(key)) {
131
178
  seen.add(key);
@@ -173,7 +220,8 @@ function identity(membership) {
173
220
  membership.member?.group ?? null,
174
221
  [...membership.eligible ?? []].toSorted(),
175
222
  membership.managedBy ?? null,
176
- [...membership.entitlements ?? []].toSorted()
223
+ [...membership.entitlements ?? []].toSorted(),
224
+ membership.keep === void 0 ? null : [...membership.keep].toSorted()
177
225
  ]);
178
226
  }
179
227
  /**
@@ -245,7 +293,8 @@ function asMembershipSource(input) {
245
293
  /**
246
294
  * Trust the memberships the verified token carries, and read `source` only
247
295
  * when the token marks them truncated. With `version`, a token minted before
248
- * the latest membership change is stale for the policy's `fresh` permissions.
296
+ * the latest membership change is stale for the policy's `fresh` permissions,
297
+ * or with `onStale: 'reread'` has its memberships read from `source` instead.
249
298
  */
250
299
  function claimsFirst(source, options = {}) {
251
300
  const inner = asMembershipSource(source);
@@ -254,6 +303,7 @@ function claimsFirst(source, options = {}) {
254
303
  membershipsFor: (principal, query) => inner.membershipsFor(principal, query),
255
304
  ...inner.list === void 0 ? {} : { list: (query) => inner.list?.(query) ?? [] },
256
305
  ...version === void 0 ? {} : { version },
306
+ ...options.onStale === "reread" ? { onStale: "reread" } : {},
257
307
  claimsFirst: true
258
308
  };
259
309
  }
@@ -261,6 +311,33 @@ function claimsFirst(source, options = {}) {
261
311
  function isExternallyManaged(membership) {
262
312
  return membership.managedBy === "idp";
263
313
  }
314
+ /**
315
+ * How many principals hold `role` in one scope instance now, from the
316
+ * source's member list (`MembershipSource.list`): live memberships of exactly
317
+ * that scope and instance, each principal counted once. The `holders` a
318
+ * `decideRoleChange` with `min`, `max` or `transferOnly` needs. `undefined`
319
+ * when the source cannot list or the read fails, which that check denies.
320
+ */
321
+ async function countHolders(source, query) {
322
+ const composed = asMembershipSource(source);
323
+ if (composed.list === void 0) return;
324
+ const role = typeof query.role === "string" ? query.role : query.role.key;
325
+ try {
326
+ const entries = await composed.list({
327
+ scope: query.scope,
328
+ id: query.id
329
+ });
330
+ const now = Math.floor(Date.now() / 1e3);
331
+ const holders = /* @__PURE__ */ new Set();
332
+ for (const entry of entries) {
333
+ const membership = entry.membership;
334
+ if (membership.scope === query.scope && membership.id === query.id && membership.roles.includes(role) && (membership.expiresAt === void 0 || membership.expiresAt > now)) holders.add(entry.principal.id);
335
+ }
336
+ return holders.size;
337
+ } catch {
338
+ return;
339
+ }
340
+ }
264
341
  //#endregion
265
342
  //#region src/core/custom-roles.ts
266
343
  /** The current key for a stored key, current or former; `undefined` when neither. */
@@ -269,7 +346,30 @@ function currentKey(policy, raw) {
269
346
  if (leaf === void 0) return;
270
347
  return leaf.key === raw || formerKeys(leaf).includes(raw) ? leaf.key : void 0;
271
348
  }
272
- const GRANT_KEYS = /* @__PURE__ */ new Set(["permission", "effect"]);
349
+ const GRANT_KEYS = /* @__PURE__ */ new Set([
350
+ "permission",
351
+ "effect",
352
+ "level"
353
+ ]);
354
+ /** `where` narrowed by a level; the empty level (`{}`) leaves it as is. */
355
+ function andWhere(where, level) {
356
+ if (level.op === "and" && level.conditions.length === 0) return where;
357
+ return where === void 0 ? level : {
358
+ op: "and",
359
+ conditions: [where, level]
360
+ };
361
+ }
362
+ /** A ceiling grant narrowed by a level's condition and marked with the level. */
363
+ function leveled(grant, level, condition) {
364
+ const where = andWhere(grant.where, condition);
365
+ const check = grant.check === void 0 ? void 0 : andWhere(grant.check, condition);
366
+ return freezeDeep({
367
+ ...grant,
368
+ level,
369
+ ...where === void 0 ? {} : { where },
370
+ ...check === void 0 ? {} : { check }
371
+ });
372
+ }
273
373
  /**
274
374
  * The scope a custom role is held at: `scope` (a name or alias), the second
275
375
  * scope for the `team` input shape, else the first scope. `undefined` when
@@ -372,8 +472,11 @@ function resolveCustomRole(policy, role) {
372
472
  };
373
473
  const included = [];
374
474
  const includedKeys = /* @__PURE__ */ new Set();
375
- const allowKeys = /* @__PURE__ */ new Set();
475
+ const allowKeys = /* @__PURE__ */ new Map();
376
476
  const denyKeys = /* @__PURE__ */ new Set();
477
+ const allowAll = (key) => {
478
+ allowKeys.set(key, "all");
479
+ };
377
480
  for (const name of Array.isArray(role.includes) ? role.includes : []) {
378
481
  if (typeof name !== "string" || !declared.has(name)) {
379
482
  drop({
@@ -390,7 +493,7 @@ function resolveCustomRole(policy, role) {
390
493
  } else if (ceilingSet.has(grant)) {
391
494
  included.push(grant);
392
495
  includedKeys.add(key);
393
- } else if (ceilingKeys.has(key)) allowKeys.add(key);
496
+ } else if (ceilingKeys.has(key)) allowAll(key);
394
497
  else drop({
395
498
  permission: key,
396
499
  reason: "outside-ceiling"
@@ -421,7 +524,9 @@ function resolveCustomRole(policy, role) {
421
524
  to: key
422
525
  });
423
526
  const effect = entry.effect ?? "allow";
424
- if (Object.keys(entry).some((name) => !GRANT_KEYS.has(name)) || effect !== "allow" && effect !== "deny") {
527
+ const extra = Object.keys(entry).some((name) => !GRANT_KEYS.has(name));
528
+ const level = entry.level;
529
+ if (extra || effect !== "allow" && effect !== "deny" || effect === "deny" && level !== void 0) {
425
530
  drop({
426
531
  permission: key,
427
532
  reason: "condition-not-allowed"
@@ -433,6 +538,21 @@ function resolveCustomRole(policy, role) {
433
538
  denyKeys.add(key);
434
539
  continue;
435
540
  }
541
+ let narrowing;
542
+ if (level !== void 0) {
543
+ const leaf = findPermission(policy.permissions, key);
544
+ const condition = typeof level === "string" && leaf?.kind === "instance" ? levelCondition(policy, leaf.resource, level) : void 0;
545
+ if (condition === void 0) {
546
+ drop({
547
+ permission: key,
548
+ reason: "unknown-level",
549
+ level: String(level)
550
+ });
551
+ denyKeys.add(key);
552
+ continue;
553
+ }
554
+ narrowing = [String(level), condition];
555
+ }
436
556
  if (!ceilingKeys.has(key)) {
437
557
  drop({
438
558
  permission: key,
@@ -440,19 +560,27 @@ function resolveCustomRole(policy, role) {
440
560
  });
441
561
  continue;
442
562
  }
443
- allowKeys.add(key);
563
+ const held = allowKeys.get(key);
564
+ if (narrowing === void 0) allowAll(key);
565
+ else if (held === void 0) allowKeys.set(key, new Map([narrowing]));
566
+ else if (held !== "all") held.set(...narrowing);
444
567
  }
445
568
  const out = [];
446
569
  const push = (grant) => {
447
570
  if (!out.includes(grant)) out.push(grant);
448
571
  };
449
572
  for (const grant of included) if (grant.effect === "deny" || !denyKeys.has(grant.permission.key)) push(grant);
450
- for (const key of allowKeys) {
573
+ for (const [key, levels] of allowKeys) {
451
574
  if (denyKeys.has(key) || includedKeys.has(key)) continue;
452
575
  const sources = /* @__PURE__ */ new Set();
453
- for (const grant of ceiling) if (grant.permission.key === key) {
454
- push(grant);
576
+ for (const grant of ceiling) {
577
+ if (grant.permission.key !== key) continue;
455
578
  sources.add(soleRole(grant) ?? "");
579
+ if (levels === "all") {
580
+ push(grant);
581
+ continue;
582
+ }
583
+ for (const [level, condition] of levels) push(leveled(grant, level, condition));
456
584
  }
457
585
  for (const grant of all) if (grant.effect === "deny" && grant.permission.key === key && sources.has(soleRole(grant) ?? "")) push(grant);
458
586
  }
@@ -490,12 +618,17 @@ function customGrantsFor(policy, roles) {
490
618
  }
491
619
  return out;
492
620
  }
621
+ /** The well-formed tenant (not platform) custom roles; a declared role name is never one. */
622
+ function tenantCustomRoles(policy, roles) {
623
+ const declared = declaredRoleNames$1(policy);
624
+ return roles.filter((role) => wellFormed(role) && !isGlobalCustomRole(role) && !declared.has(role.name));
625
+ }
493
626
  /** A membership at the role's scope, inside its tenant (and instance, when pinned), naming it. */
494
627
  function holdsCustomRole(membership, role, scopes) {
495
628
  if (isGlobalCustomRole(role)) return false;
496
629
  const scope = customRoleScope(role, scopes);
497
630
  const id = customRoleId(role);
498
- return scope !== void 0 && membership.scope === scope && tenantOf(membership, scopes) === role.tenant && (id === void 0 || membership.id === id) && membership.roles.includes(role.name);
631
+ return scope !== void 0 && membership.scope === scope && sameId(tenantOf(membership, scopes), role.tenant) && (id === void 0 || sameId(membership.id, id)) && membership.roles.includes(role.name);
499
632
  }
500
633
  /** Whether `principal.roles` names the platform custom role. */
501
634
  function holdsGlobalCustomRole(roles, role) {
@@ -503,7 +636,7 @@ function holdsGlobalCustomRole(roles, role) {
503
636
  }
504
637
  /** A tenant's custom role inside that tenant, or a platform custom role anywhere. */
505
638
  function isCustomRoleName(name, roles, tenant) {
506
- return roles.some((item) => wellFormed(item) && item.name === name && (isGlobalCustomRole(item) || tenant !== void 0 && item.tenant === tenant));
639
+ return roles.some((item) => wellFormed(item) && item.name === name && (isGlobalCustomRole(item) || tenant !== void 0 && sameId(item.tenant, tenant)));
507
640
  }
508
641
  /**
509
642
  * The compact JWT form of custom roles for the `memberships[].grants` claim
@@ -520,7 +653,14 @@ function customRoleClaim(roles, policy) {
520
653
  const includes = Array.isArray(role.includes) ? role.includes : [];
521
654
  const grants = Array.isArray(role.grants) ? role.grants : [];
522
655
  for (const name of includes) entries.push(`@${name}`);
523
- for (const grant of grants) entries.push(grant.effect === "deny" ? `-${keyOf(grant.permission)}` : keyOf(grant.permission));
656
+ for (const grant of grants) {
657
+ if (grant.effect === "deny") {
658
+ entries.push(`-${keyOf(grant.permission)}`);
659
+ continue;
660
+ }
661
+ const level = grant.level;
662
+ entries.push(typeof level === "string" && level !== "" ? `${keyOf(grant.permission)}@${level}` : keyOf(grant.permission));
663
+ }
524
664
  claim[role.name] = entries;
525
665
  }
526
666
  return freezeDeep(claim);
@@ -704,7 +844,7 @@ function activate(policy, subject, input, now = nowSeconds()) {
704
844
  if (binding === void 0 || activation === void 0) return activationDenied("unknown-role");
705
845
  const scopes = scopeList(policy.scopes);
706
846
  const scope = resolveScope(scopes, input.scope) ?? input.scope;
707
- const eligible = (principal.memberships ?? []).find((membership) => membership.scope === scope && membership.id === input.id && !isMembershipExpired(membership, now) && (membership.eligible ?? []).includes(roleName) && withinAgrees(scopes, membership.within, input.within));
847
+ const eligible = (principal.memberships ?? []).find((membership) => membership.scope === scope && sameId(membership.id, input.id) && !isMembershipExpired(membership, now) && (membership.eligible ?? []).includes(roleName) && withinAgrees(scopes, membership.within, input.within));
708
848
  if (eligible === void 0) return activationDenied("no-membership");
709
849
  const within = eligible.within;
710
850
  if (activation.justification === "required") {
@@ -903,20 +1043,30 @@ function matchWriteScope(match, instance, current, next) {
903
1043
  const moved = match(next);
904
1044
  return moved.ok ? first : moved;
905
1045
  }
906
- function isRowPair(value) {
907
- return value !== null && typeof value === "object" && "current" in value && "next" in value;
908
- }
909
1046
  function isGlobalRole(value) {
910
1047
  return value !== null && typeof value === "object" && Reflect.get(value, "scope") === "global";
911
1048
  }
912
1049
  function onlyGlobal(roles) {
913
1050
  return Array.isArray(roles) ? roles.filter(isGlobalRole) : [];
914
1051
  }
915
- function customRolesFor(source, tenants, auth, signedIn = false) {
1052
+ /** The `customRoles` option as a source: a factory is called with the resolved subject, and a throw reads none. */
1053
+ function roleSourceFor(option, subject, auth) {
1054
+ if (typeof option !== "function") return option;
1055
+ try {
1056
+ return option(subject);
1057
+ } catch {
1058
+ auth.push({
1059
+ reason: "source-threw",
1060
+ source: "customRoles"
1061
+ });
1062
+ return;
1063
+ }
1064
+ }
1065
+ function customRolesFor(source, tenants, auth, globalHeld, heldIn) {
916
1066
  if (source === void 0) return [];
917
1067
  const loaded = [];
918
1068
  for (const tenant of tenants) try {
919
- loaded.push(source.rolesFor(tenant));
1069
+ loaded.push(source.rolesFor(tenant, { held: heldIn(tenant) }));
920
1070
  } catch {
921
1071
  auth.push({
922
1072
  reason: "source-threw",
@@ -924,8 +1074,8 @@ function customRolesFor(source, tenants, auth, signedIn = false) {
924
1074
  });
925
1075
  loaded.push([]);
926
1076
  }
927
- if (signedIn && source.globalRoles !== void 0) try {
928
- const roles = source.globalRoles();
1077
+ if (globalHeld !== void 0 && source.globalRoles !== void 0) try {
1078
+ const roles = source.globalRoles({ held: globalHeld });
929
1079
  loaded.push(isThenable(roles) ? roles.then(onlyGlobal) : onlyGlobal(roles));
930
1080
  } catch {
931
1081
  auth.push({
@@ -989,6 +1139,87 @@ function expandRoleNames(names, declared, custom, tenant) {
989
1139
  function declaredRoleNames(policy) {
990
1140
  return policy.index.declaredRoles;
991
1141
  }
1142
+ const NEVER = {
1143
+ op: "or",
1144
+ conditions: []
1145
+ };
1146
+ function isUnconditionedAllow(grant, now) {
1147
+ return grant.where === void 0 && grant.check === void 0 && grant.closure === void 0 && grant.portable && grant.purpose === void 0 && grant.viaOnly === void 0 && grant.requires === void 0 && isActive(grant.validity, now);
1148
+ }
1149
+ /**
1150
+ * The row condition a grant's `requires` adds: the row's instance of some
1151
+ * scope is one where the subject holds `key` through a role grant without a
1152
+ * row condition, minus the instances a deny of `key` reaches at that scope.
1153
+ * `undefined` when the subject holds `key` globally that way, so every row
1154
+ * passes. The in-process mirror of the SQL `permdock_has_permission` and
1155
+ * `permitted_<scope>_ids_by_permission`.
1156
+ */
1157
+ function requirementCondition(policy, subject, keys, resource, now, customRoles, customGrants) {
1158
+ let where;
1159
+ for (const key of keys) where = combineWhere(where, keyRequirement(policy, subject, key, resource, now, customRoles, customGrants));
1160
+ return where;
1161
+ }
1162
+ function keyRequirement(policy, subject, key, resource, now, customRoles, customGrants) {
1163
+ const principal = subject.principal;
1164
+ if (principal === null) return NEVER;
1165
+ const scopes = scopeList(policy.scopes);
1166
+ const declared = declaredRoleNames(policy);
1167
+ const global = new Set(expandRoleNames(principal.roles ?? [], declared, customRoles, principal.tenant).roles);
1168
+ const held = (principal.memberships ?? []).filter((membership) => !isMembershipExpired(membership, now) && keepsPermission(membership, key)).map((membership) => ({
1169
+ membership,
1170
+ roles: new Set(expandRoleNames(membership.roles, declared, customRoles, tenantOf(membership, scopes)).roles)
1171
+ }));
1172
+ const allowed = /* @__PURE__ */ new Map();
1173
+ const denied = /* @__PURE__ */ new Map();
1174
+ let globalAllow = false;
1175
+ let globalDeny = false;
1176
+ const note = (effect, membership) => {
1177
+ if (membership === void 0) {
1178
+ if (effect === "allow") globalAllow = true;
1179
+ else globalDeny = true;
1180
+ return;
1181
+ }
1182
+ if (membership.scope === void 0 || membership.id === void 0) return;
1183
+ const target = effect === "allow" ? allowed : denied;
1184
+ const ids = target.get(membership.scope) ?? /* @__PURE__ */ new Set();
1185
+ ids.add(membership.id);
1186
+ target.set(membership.scope, ids);
1187
+ };
1188
+ const applies = (grant) => grant.effect === "allow" ? isUnconditionedAllow(grant, now) : isActive(grant.validity, now);
1189
+ for (const grant of policy.index.grantsByKey.get(key) ?? []) {
1190
+ const items = flattenGrantee(grant.to);
1191
+ if (grant.breakGlass !== void 0 || !applies(grant) || items.length === 0 || !items.every((item) => item.kind === "role")) continue;
1192
+ const roles = items.flatMap((item) => item.kind === "role" ? [item] : []);
1193
+ if (!roles.every((item) => item.scope !== "global" || global.has(item.role))) continue;
1194
+ const scoped = roles.filter((item) => item.scope !== "global");
1195
+ if (scoped.length === 0) {
1196
+ note(grant.effect);
1197
+ continue;
1198
+ }
1199
+ for (const entry of held) if (scoped.every((item) => typeof item.scope === "string" && entry.membership.scope === item.scope && entry.roles.has(item.role))) note(grant.effect, entry.membership);
1200
+ }
1201
+ for (const { grant, role } of customGrants) {
1202
+ if (grant.permission.key !== key || !applies(grant)) continue;
1203
+ if (holdsGlobalCustomRole(principal.roles, role)) note(grant.effect);
1204
+ for (const entry of held) if (holdsCustomRole(entry.membership, role, scopes)) note(grant.effect, entry.membership);
1205
+ }
1206
+ if (globalAllow && !globalDeny) return;
1207
+ const parts = [];
1208
+ for (const name of resource === void 0 ? [] : partitionsOf(resource, scopes)) {
1209
+ const field = scopeField(resource, name, scopes);
1210
+ const deniedIds = denied.get(name);
1211
+ const ids = [...allowed.get(name) ?? []].filter((id) => deniedIds?.has(id) !== true).toSorted();
1212
+ if (field !== void 0 && ids.length > 0) parts.push({
1213
+ op: "in",
1214
+ field,
1215
+ value: ids
1216
+ });
1217
+ }
1218
+ return sole(parts) ?? {
1219
+ op: "or",
1220
+ conditions: parts
1221
+ };
1222
+ }
992
1223
  function alternativesFor(policy, permission, subject, now, env) {
993
1224
  return listPermissions(policy.permissions).filter((leaf) => leaf.resource === permission.resource && leaf.key !== permission.key).filter((leaf) => {
994
1225
  return evaluate(policy, subject, leaf, void 0, {
@@ -998,11 +1229,35 @@ function alternativesFor(policy, permission, subject, now, env) {
998
1229
  }, {
999
1230
  ...env,
1000
1231
  emit: false,
1001
- skipAlternatives: true
1232
+ skipAlternatives: true,
1233
+ outcomeOnly: true
1002
1234
  }).outcome === "granted";
1003
1235
  });
1004
1236
  }
1005
- function evaluateGrantCondition(grant, permission, current, next, subject, now, scopes, relations) {
1237
+ function inheritedVerdict(policy, subject, resource, id, key, now, env) {
1238
+ const leaf = findPermission(policy.permissions, key);
1239
+ const loaded = env.relations?.row({
1240
+ resource,
1241
+ id
1242
+ });
1243
+ if (leaf === void 0 || loaded === void 0) return "relation-unavailable";
1244
+ if (loaded === "pending" || loaded === "failed") return "relation-unavailable";
1245
+ if (loaded.row === null) return false;
1246
+ const decision = evaluate(policy, subject, leaf, loaded.row, {
1247
+ trusted: true,
1248
+ source: "simulate",
1249
+ now
1250
+ }, {
1251
+ ...env,
1252
+ emit: false,
1253
+ skipAlternatives: true,
1254
+ outcomeOnly: true
1255
+ });
1256
+ if (decision.outcome === "granted") return true;
1257
+ const reasons = decision.outcome === "denied" ? decision.denials.map((denial) => denial.reason) : [];
1258
+ return reasons.includes("relation-unavailable") ? "relation-unavailable" : reasons.includes("relation-depth") ? "relation-depth" : false;
1259
+ }
1260
+ function evaluateGrantCondition(grant, permission, current, next, subject, now, scopes, relations, permitted) {
1006
1261
  let unknown;
1007
1262
  const onOpaque = () => {
1008
1263
  unknown ??= "opaque-condition";
@@ -1012,7 +1267,7 @@ function evaluateGrantCondition(grant, permission, current, next, subject, now,
1012
1267
  unknown ??= "relation-unavailable";
1013
1268
  return false;
1014
1269
  }
1015
- const verdict = resolveRelated(condition, row, subject, now, relations);
1270
+ const verdict = resolveRelated(condition, row, subject, now, relations, permitted);
1016
1271
  if (verdict === "relation-depth" || verdict === "relation-unavailable") {
1017
1272
  unknown ??= verdict;
1018
1273
  return false;
@@ -1140,12 +1395,12 @@ function traceOf(tracer) {
1140
1395
  skipped: tracer.skipped
1141
1396
  };
1142
1397
  }
1143
- function evaluate(policy, subject, permission, data, options, env) {
1398
+ function evaluate(policy, given, permission, data, options, env) {
1399
+ const subject = subjectForPermission(given, permission.key);
1144
1400
  const now = options.now ?? nowSeconds();
1145
1401
  const trusted = options.trusted === true;
1146
1402
  const resource = getResource(policy.permissions, permission.resource);
1147
- let current = data;
1148
- let next = data;
1403
+ let { current, next } = rowValues(permission, data);
1149
1404
  const tracer = options.explain === true ? {
1150
1405
  evaluated: 0,
1151
1406
  allows: [],
@@ -1153,21 +1408,13 @@ function evaluate(policy, subject, permission, data, options, env) {
1153
1408
  skipped: []
1154
1409
  } : void 0;
1155
1410
  const complete = (decision, membership) => {
1156
- const final = freezeDeep(tracer === void 0 ? decision : {
1411
+ const final = env.outcomeOnly === true ? decision : freezeDeep(tracer === void 0 ? decision : {
1157
1412
  ...decision,
1158
1413
  trace: traceOf(tracer)
1159
1414
  });
1160
- finish(policy, subject, permission, current, final, options, env, trusted, membership);
1415
+ finish(policy, given, permission, current, final, options, env, trusted, membership);
1161
1416
  return final;
1162
1417
  };
1163
- if (permission.kind === "instance" && isRowPair(data)) {
1164
- current = data.current;
1165
- next = data.next;
1166
- }
1167
- if (permission.kind === "collection") {
1168
- current = void 0;
1169
- next = data;
1170
- }
1171
1418
  try {
1172
1419
  if (data !== void 0 || permission.kind === "instance" && policy.validate === "always") {
1173
1420
  const validated = validateBoundary(permission, resource, permission.kind === "instance" && isRowPair(data) ? data.current : data, policy.validate, trusted, options.boundary ?? "manual");
@@ -1323,7 +1570,7 @@ function evaluate(policy, subject, permission, data, options, env) {
1323
1570
  const matchRow = (row) => matchScopedMembership(subject, roleItem.scope, roleItem.role, row, scopes, resource, policy.resources, now, (membership) => {
1324
1571
  if (custom === void 0) return expandRoleNames(membership.roles, declared, env.customRoles, tenantOf(membership, scopes)).roles;
1325
1572
  return holdsCustomRole(membership, custom, scopes) ? [custom.name] : [];
1326
- }, env.team, walkRole);
1573
+ }, env.team, walkRole, permission.kind === "instance", options.scope);
1327
1574
  const scopeMatch = matchWriteScope(matchRow, permission.kind === "instance", current, next);
1328
1575
  if (!scopeMatch.ok) {
1329
1576
  denials.push({
@@ -1347,11 +1594,12 @@ function evaluate(policy, subject, permission, data, options, env) {
1347
1594
  else skip(tracer, grant, permission.key, "validity");
1348
1595
  continue;
1349
1596
  }
1597
+ const requirement = grant.requires === void 0 || grant.effect !== "allow" ? void 0 : requirementCondition(policy, subject, grant.requires, resource, now, env.customRoles, env.customGrants);
1350
1598
  const merged = freezeDeep(compact({
1351
1599
  ...grant,
1352
- where: combineWhere(grant.where, granteeMatch.where)
1600
+ where: combineWhere(combineWhere(grant.where, granteeMatch.where), requirement)
1353
1601
  }));
1354
- const condition = evaluateGrantCondition(merged, permission, current, next, subject, now, scopes, env.relations);
1602
+ const condition = evaluateGrantCondition(merged, permission, current, next, subject, now, scopes, env.relations, (target, id, key) => inheritedVerdict(policy, given, target, id, key, now, env));
1355
1603
  if (!condition.matched && grant.effect === "deny" && isUnevaluable(condition.reason) && grantCoversField(grant.fields, options.field, grant.effect)) {
1356
1604
  if (condition.reason === "closure-error") emitSafe(env.listeners.error, condition.cause ?? /* @__PURE__ */ new Error("closure-error"), env.listeners);
1357
1605
  tracer?.denies.push(matchedOf(merged, permission.key));
@@ -1442,7 +1690,12 @@ function evaluate(policy, subject, permission, data, options, env) {
1442
1690
  }],
1443
1691
  alternatives: env.skipAlternatives ? [] : alternativesFor(policy, permission, subject, now, env)
1444
1692
  });
1445
- const delegationMiss = coveredByDelegation(permission, subject.delegation, resourceIdOf(current), subject.actor !== void 0 && ceiling === void 0);
1693
+ const unscoped = subject.actor !== void 0 && ceiling === void 0;
1694
+ let delegationMiss = coveredByDelegation(permission, subject.delegation, resourceIdOf(current), unscoped);
1695
+ const requirementsCovered = allows.filter((candidate) => coversRequirements(policy.permissions, candidate.grant.requires, subject.delegation, resourceIdOf(current), unscoped));
1696
+ let usable = delegationMiss === void 0 ? requirementsCovered : requirementsCovered.filter((candidate) => requiresStandIn(candidate.grant));
1697
+ if (delegationMiss === "not-delegated" && usable.length > 0) delegationMiss = void 0;
1698
+ if (delegationMiss === void 0 && usable.length === 0) delegationMiss = "not-delegated";
1446
1699
  if (delegationMiss !== void 0) return complete({
1447
1700
  outcome: "denied",
1448
1701
  denials: [{
@@ -1452,12 +1705,12 @@ function evaluate(policy, subject, permission, data, options, env) {
1452
1705
  alternatives: env.skipAlternatives ? [] : alternativesFor(policy, permission, subject, now, env)
1453
1706
  });
1454
1707
  const approvalPolicies = env.approvalPolicies;
1455
- if (approvalPolicies === "failed" && allows.length > 0) return complete({
1708
+ if (approvalPolicies === "failed" && usable.length > 0) return complete({
1456
1709
  outcome: "denied",
1457
1710
  denials: [{
1458
1711
  role: null,
1459
1712
  reason: "approval",
1460
- detail: "approval-policy-unavailable"
1713
+ detail: APPROVAL_POLICY_UNAVAILABLE
1461
1714
  }],
1462
1715
  alternatives: []
1463
1716
  });
@@ -1474,7 +1727,7 @@ function evaluate(policy, subject, permission, data, options, env) {
1474
1727
  let matchedAllow;
1475
1728
  let approval;
1476
1729
  let quotaState = {};
1477
- for (const candidate of allows) {
1730
+ for (const candidate of usable) {
1478
1731
  approval = approvalOf(candidate);
1479
1732
  const consume = !requiresApproval(approval) && shouldConsumeQuota(options.source, env.simulated);
1480
1733
  const quota = applyQuota({
@@ -1509,9 +1762,9 @@ function evaluate(policy, subject, permission, data, options, env) {
1509
1762
  denials: quotaDenials.length > 0 ? quotaDenials : denials,
1510
1763
  alternatives: env.skipAlternatives ? [] : alternativesFor(policy, permission, subject, now, env)
1511
1764
  });
1512
- const resourceId = permission.kind === "collection" ? "*" : current !== null && typeof current === "object" ? String(current[resource?.id ?? "id"] ?? "*") : "*";
1765
+ const resourceId = permission.kind === "collection" ? "*" : rowIdOf(current, resource?.id);
1513
1766
  const version = approval !== void 0 && approval !== "human" && approval.staleOn === "resource-change" && resource?.version !== void 0 ? versionOf(current, resource.version) : void 0;
1514
- const token = env.simulated ? "pd1.simulated" : decisionToken({
1767
+ const token = env.simulated ? "pd1.simulated" : env.outcomeOnly === true ? "pd1.outcome" : decisionToken({
1515
1768
  key: permission.key,
1516
1769
  resourceId,
1517
1770
  principal: subject.principal,
@@ -1647,7 +1900,7 @@ function audiencesOf(policy, names) {
1647
1900
  /** Whether `membership` is the instance itself or one of its ancestors named in `within`. */
1648
1901
  function coversInstance(membership, scopes, scope, id, within) {
1649
1902
  if (membership.scope === void 0 || membership.id === void 0) return false;
1650
- if (membership.scope === scope) return membership.id === id;
1903
+ if (membership.scope === scope) return sameId(membership.id, id);
1651
1904
  if (!scopeChain(scopes, scope).slice(1).includes(membership.scope)) return false;
1652
1905
  const wanted = within === void 0 ? void 0 : Object.entries(within).find(([key]) => resolveScope(scopes, key) === membership.scope)?.[1] ?? void 0;
1653
1906
  return wanted !== void 0 && scopeIdOf(membership, membership.scope) === wanted;
@@ -1661,7 +1914,22 @@ function rolesAtInstance(policy, principal, scopes, scope, id, within, now) {
1661
1914
  }
1662
1915
  /** Whether the principal holds `role` on a live membership of exactly this instance. */
1663
1916
  function holdsAt(principal, role, scope, id, now) {
1664
- return (principal.memberships ?? []).some((membership) => membership.scope === scope && membership.id === id && !isMembershipExpired(membership, now) && membership.roles.includes(role));
1917
+ return (principal.memberships ?? []).some((membership) => membership.scope === scope && sameId(membership.id, id) && !isMembershipExpired(membership, now) && membership.roles.includes(role));
1918
+ }
1919
+ function includedRoles(role) {
1920
+ return isReadonlyArray(role.includes) ? role.includes.filter((name) => typeof name === "string") : [];
1921
+ }
1922
+ /**
1923
+ * The membership kinds a custom role may be held through: those every
1924
+ * included declared role with `for` allows. `undefined` when none restricts.
1925
+ */
1926
+ function customForKinds(policy, role) {
1927
+ let kinds;
1928
+ for (const name of includedRoles(role)) {
1929
+ const listed = bindingOf(policy, name)?.for;
1930
+ if (listed !== void 0) kinds = kinds === void 0 ? listed : kinds.filter((kind) => listed.includes(kind));
1931
+ }
1932
+ return kinds;
1665
1933
  }
1666
1934
  function conflictsWith(policy, role) {
1667
1935
  const out = new Set(bindingOf(policy, role)?.exclusiveWith ?? []);
@@ -1707,7 +1975,7 @@ function heldAnchor(principal, scopes, scope, id, now) {
1707
1975
  * The one rule check behind `permdock.decideRoleChange`. The actor is always
1708
1976
  * the instance's own principal; nothing in `change` can stand in for it.
1709
1977
  */
1710
- function decideRoleChange(policy, principal, scopes, change, authority, now, options = {}) {
1978
+ function decideRoleChange(policy, principal, scopes, change, customRoles, authority, now, options = {}) {
1711
1979
  const name = typeof change?.role === "string" ? change.role : change?.role?.key;
1712
1980
  const denials = [];
1713
1981
  const deny = (reason, detail) => {
@@ -1737,12 +2005,15 @@ function decideRoleChange(policy, principal, scopes, change, authority, now, opt
1737
2005
  return done(null);
1738
2006
  }
1739
2007
  const binding = bindingOf(policy, name);
1740
- if (!declaredRoleNames$1(policy).has(name)) {
2008
+ const declared = declaredRoleNames$1(policy).has(name);
2009
+ const named = declared ? [] : tenantCustomRoles(policy, customRoles).filter((role) => role.name === name);
2010
+ if (!declared && named.length === 0) {
1741
2011
  deny("unknown-role");
1742
2012
  return done(null);
1743
2013
  }
1744
2014
  const scope = resolveScope(scopes, change.scope);
1745
- const on = binding?.on ?? findRole(policy.vocabulary?.roles, name)?.on;
2015
+ const customAt = named.map((role) => customRoleScope(role, scopes));
2016
+ const on = declared ? binding?.on ?? findRole(policy.vocabulary?.roles, name)?.on : customAt.includes(scope) ? scope : customAt[0];
1746
2017
  const heldAt = typeof on === "string" ? resolveScope(scopes, on) : void 0;
1747
2018
  if (scope === void 0 || heldAt === void 0 || heldAt !== scope) {
1748
2019
  deny("scope", { expected: heldAt ?? null });
@@ -1768,6 +2039,11 @@ function decideRoleChange(policy, principal, scopes, change, authority, now, opt
1768
2039
  });
1769
2040
  return done(null);
1770
2041
  }
2042
+ const custom = named.find((role) => sameId(role.tenant, tenant) && customRoleScope(role, scopes) === scope && sameId(customRoleId(role) ?? change.id, change.id));
2043
+ if (!declared && custom === void 0) {
2044
+ deny("unknown-role");
2045
+ return done(null);
2046
+ }
1771
2047
  if (target.managedBy === "idp") {
1772
2048
  deny("externally-managed");
1773
2049
  return done(null);
@@ -1783,19 +2059,21 @@ function decideRoleChange(policy, principal, scopes, change, authority, now, opt
1783
2059
  } else {
1784
2060
  if (self) deny(kind === "revoke" ? "self-demotion" : "not-assignable-by", { self: true });
1785
2061
  const allowed = authority(tenant);
1786
- if (!allowed.assignable.has(name)) deny("not-assignable-by");
1787
- else if (usesAssigns(policy) && !allowed.manage) {
2062
+ const revocable = kind === "revoke" && custom !== void 0 && allowed.inert?.has(name);
2063
+ if (!allowed.assignable.has(name) && revocable !== true) deny("not-assignable-by");
2064
+ else if (custom === void 0 && usesAssigns(policy) && !allowed.manage) {
1788
2065
  const held = rolesAtInstance(policy, principal, scopes, scope, change.id, within, now);
1789
2066
  by = held.find((role) => bindingOf(policy, role)?.assigns?.includes(name) === true) ?? null;
1790
2067
  if (by === null) deny("not-assignable-by", { held });
1791
2068
  }
1792
2069
  }
1793
2070
  if (kind !== "revoke" && !already) {
1794
- if (!mayHoldVia(policy, name, target.via)) deny("not-allowed-for-membership", {
2071
+ const kinds = custom === void 0 ? binding?.for : customForKinds(policy, custom);
2072
+ if (kinds !== void 0 && (target.via === void 0 || !kinds.includes(target.via))) deny("not-allowed-for-membership", {
1795
2073
  via: target.via ?? null,
1796
- for: binding?.for
2074
+ for: kinds
1797
2075
  });
1798
- const conflicts = conflictsWith(policy, name);
2076
+ const conflicts = custom === void 0 ? conflictsWith(policy, name) : new Set(includedRoles(custom).flatMap((include) => Array.from(conflictsWith(policy, include))));
1799
2077
  const clash = targetRoles.filter((role) => conflicts.has(role));
1800
2078
  if (clash.length > 0) deny("conflicting-role", { with: clash });
1801
2079
  }
@@ -1849,7 +2127,7 @@ async function discoverRole(item, allow, ctx) {
1849
2127
  ctx.incomplete();
1850
2128
  return;
1851
2129
  }
1852
- const key = findScope(ctx.scopes, item.scope)?.key;
2130
+ const key = scopeField(ctx.resource, item.scope, ctx.scopes);
1853
2131
  const id = key === void 0 ? void 0 : relationId(ownGet(ctx.row, key));
1854
2132
  if (id === void 0) return;
1855
2133
  const members = await ctx.list(item.scope, id);
@@ -2002,6 +2280,7 @@ function discover(grant, item, ctx) {
2002
2280
  case "plan":
2003
2281
  case "actor":
2004
2282
  case "assurance":
2283
+ case "inherit":
2005
2284
  ctx.incomplete();
2006
2285
  return Promise.resolve();
2007
2286
  default: return unknownKind(item, ctx);
@@ -2069,7 +2348,7 @@ async function whoCan(input) {
2069
2348
  };
2070
2349
  await Promise.all((policy.index.grantsByKey.get(permission.key) ?? []).flatMap((grant) => flattenGrantee(grant.to).map((item) => discover(grant, item, ctx))));
2071
2350
  const root = rootScope(scopes);
2072
- const tenantKey = root === void 0 ? void 0 : findScope(scopes, root)?.key;
2351
+ const tenantKey = root === void 0 ? void 0 : scopeField(getResource(policy.permissions, permission.resource), root, scopes);
2073
2352
  const tenant = tenantKey === void 0 ? void 0 : relationId(ownGet(row, tenantKey));
2074
2353
  const env = {
2075
2354
  emit: false,
@@ -2161,8 +2440,8 @@ function heldRoleNames(policy, subject, tenant, only, now) {
2161
2440
  if (only?.scope !== void 0 && scope === void 0) return [];
2162
2441
  const names = new Set(scope === void 0 ? subject.principal.roles ?? [] : []);
2163
2442
  for (const membership of subject.principal.memberships ?? []) {
2164
- if (isMembershipExpired(membership, now)) continue;
2165
- if (!(scope === void 0 ? tenantOf(membership, scopes) === tenant : membership.scope === scope && (only?.id === void 0 || membership.id === only.id))) continue;
2443
+ if (isMembershipExpired(membership, now) || membership.keep !== void 0) continue;
2444
+ if (!(scope === void 0 ? tenantOf(membership, scopes) === tenant : membership.scope === scope && (only?.id === void 0 || sameId(membership.id, only.id)))) continue;
2166
2445
  for (const role of membership.roles) names.add(role);
2167
2446
  }
2168
2447
  return rankRoles(policy, [...names]);
@@ -2183,11 +2462,12 @@ function roleLeaf(policy, name) {
2183
2462
  function graphAware(grant, where) {
2184
2463
  const combined = combineWhere(grant.where, where);
2185
2464
  const graph = hasConditionOp(combined, "related");
2465
+ const inherits = flattenGrantee(grant.to).some((item) => item.kind === "inherit");
2186
2466
  return freezeDeep(compact({
2187
2467
  ...grant,
2188
2468
  where: combined,
2189
2469
  portable: graph ? false : grant.portable,
2190
- graph: graph && grant.portable ? true : void 0
2470
+ graph: graph && grant.portable && !inherits ? true : void 0
2191
2471
  }));
2192
2472
  }
2193
2473
  /**
@@ -2215,33 +2495,35 @@ function collectSnapshotGrants(policy, subject, customRoles, now = nowSeconds(),
2215
2495
  roles: new Set(expandRoleNames(membership.roles, declared, customRoles, tenantOf(membership, scopes)).roles)
2216
2496
  }));
2217
2497
  const holds = (entry, item) => entry.roles.has(item.role) && (typeof item.scope === "string" ? entry.membership.scope === item.scope : entry.membership.on !== void 0);
2498
+ const requirementOf = (grant, resource) => grant.requires === void 0 || grant.effect !== "allow" ? void 0 : requirementCondition(policy, subject, grant.requires, resource, now, customRoles, customGrants);
2218
2499
  const out = [];
2219
2500
  for (const grant of grantList(policy)) {
2501
+ if (mode === "held" && !coversRequirements(policy.permissions, grant.requires, subject.delegation)) continue;
2220
2502
  const resource = getResource(policy.permissions, grant.permission.resource);
2221
2503
  const match = matchGrantee(grant.to, subject, now, resource, scopes, policy.resources);
2222
2504
  if (skip(grant, match)) continue;
2223
2505
  const roleItems = flattenGrantee(grant.to).filter((item) => item.kind === "role");
2224
2506
  if (roleItems.length > 0 && subject.principal === null) continue;
2225
2507
  if (!roleItems.every((item) => item.scope !== "global" || global.roles.includes(item.role))) continue;
2226
- const merged = graphAware(grant, match.where);
2508
+ const merged = graphAware(grant, combineWhere(match.where, requirementOf(grant, resource)));
2227
2509
  const scoped = roleItems.filter((item) => item.scope !== "global");
2228
2510
  if (scoped.length === 0) {
2229
2511
  out.push({ grant: merged });
2230
2512
  continue;
2231
2513
  }
2232
- for (const entry of held) if (scoped.every((item) => item.kind === "role" && holds(entry, item))) out.push({
2514
+ for (const entry of held) if (keepsPermission(entry.membership, grant.permission.key) && scoped.every((item) => item.kind === "role" && holds(entry, item))) out.push({
2233
2515
  grant: merged,
2234
2516
  membership: entry.membership
2235
2517
  });
2236
2518
  }
2237
2519
  for (const { grant, role } of customGrants) {
2238
2520
  const globalHeld = holdsGlobalCustomRole(subject.principal?.roles, role);
2239
- const holders = held.filter((entry) => holdsCustomRole(entry.membership, role, scopes));
2521
+ const holders = held.filter((entry) => keepsPermission(entry.membership, grant.permission.key) && holdsCustomRole(entry.membership, role, scopes));
2240
2522
  if (holders.length === 0 && !globalHeld) continue;
2241
2523
  const resource = getResource(policy.permissions, grant.permission.resource);
2242
2524
  const match = matchGrantee(grant.to, subject, now, resource, scopes, policy.resources);
2243
2525
  if (skip(grant, match)) continue;
2244
- const merged = graphAware(grant, match.where);
2526
+ const merged = graphAware(grant, combineWhere(match.where, requirementOf(grant, resource)));
2245
2527
  if (globalHeld) out.push({ grant: merged });
2246
2528
  for (const entry of holders) out.push({
2247
2529
  grant: merged,
@@ -2262,6 +2544,20 @@ function assignableCandidates(policy) {
2262
2544
  return rankRoles(policy, [...new Set(names)]).map((name) => roleLeaf(policy, name));
2263
2545
  }
2264
2546
  /**
2547
+ * The levels a held allow of a permission covers: its own `level`, every
2548
+ * level when it has no row condition, or the levels whose condition equals
2549
+ * its condition.
2550
+ */
2551
+ function heldLevels(policy, grant, names) {
2552
+ if (grant.level !== void 0) return [grant.level];
2553
+ if (grant.where === void 0) return names;
2554
+ const held = canonicalJson(grant.where);
2555
+ return names.filter((name) => {
2556
+ const condition = levelCondition(policy, grant.permission.resource, name);
2557
+ return condition !== void 0 && canonicalJson(condition) === held;
2558
+ });
2559
+ }
2560
+ /**
2265
2561
  * What the subject may hand out in `tenant`: declared assignable roles and the
2266
2562
  * tenant custom-role ceiling, narrowed by `allowed` (`RoleSource.assignable`),
2267
2563
  * then intersected with the permissions the subject holds there. Holding a
@@ -2272,7 +2568,9 @@ function assignableIn(policy, subject, customRoles, customGrants, tenant, allowe
2272
2568
  if (principal === null) return {
2273
2569
  roles: [],
2274
2570
  permissions: [],
2275
- manage: false
2571
+ levels: {},
2572
+ manage: false,
2573
+ inert: []
2276
2574
  };
2277
2575
  const scopes = scopeList(policy.scopes);
2278
2576
  const scoped = tenant === void 0 ? subject : freezeDeep(compact({
@@ -2282,7 +2580,8 @@ function assignableIn(policy, subject, customRoles, customGrants, tenant, allowe
2282
2580
  tenant: resolveActiveTenant(principal, tenant, scopes, now)
2283
2581
  })
2284
2582
  }));
2285
- const heldKeys = new Set(collectSnapshotGrants(policy, scoped, customRoles, now, customGrants).filter((item) => item.grant.effect === "allow" && (item.membership === void 0 || !global && tenantOf(item.membership, scopes) === tenant && !isMembershipExpired(item.membership, now))).map((item) => item.grant.permission.key));
2583
+ const heldGrants = collectSnapshotGrants(policy, scoped, customRoles, now, customGrants).filter((item) => item.grant.effect === "allow" && (item.membership === void 0 || !global && tenantOf(item.membership, scopes) === tenant && !isMembershipExpired(item.membership, now))).map((item) => item.grant);
2584
+ const heldKeys = new Set(heldGrants.map((grant) => grant.permission.key));
2286
2585
  const heldNames = heldRoleNames(policy, subject, tenant, void 0, now);
2287
2586
  const quiet = {
2288
2587
  emit: false,
@@ -2313,10 +2612,72 @@ function assignableIn(policy, subject, customRoles, customGrants, tenant, allowe
2313
2612
  });
2314
2613
  const root = global ? "global" : rootScope(scopes);
2315
2614
  const ceiling = new Set((root === void 0 ? [] : ceilingGrants(policy, root, allowed)).map((grant) => grant.permission.key));
2615
+ const permissions = listPermissions(policy.permissions).filter((leaf) => ceiling.has(leaf.key) && (manage || heldKeys.has(leaf.key)));
2616
+ const offeredLevels = (key, names) => {
2617
+ if (manage) return names;
2618
+ const reached = new Set(heldGrants.filter((grant) => grant.permission.key === key).flatMap((grant) => heldLevels(policy, grant, names)));
2619
+ return names.filter((name) => reached.has(name));
2620
+ };
2621
+ const levels = {};
2622
+ for (const leaf of permissions) {
2623
+ const names = leaf.kind === "instance" ? levelNames(policy, leaf.resource) : [];
2624
+ if (names.length > 0) levels[leaf.key] = offeredLevels(leaf.key, names);
2625
+ }
2626
+ const ceilingKeys = /* @__PURE__ */ new Map();
2627
+ const mayHandOut = (scope, grant) => {
2628
+ const key = grant.permission.key;
2629
+ let keys = ceilingKeys.get(scope);
2630
+ if (keys === void 0) {
2631
+ keys = new Set(ceilingGrants(policy, scope, allowed).map((item) => item.permission.key));
2632
+ ceilingKeys.set(scope, keys);
2633
+ }
2634
+ if (!keys.has(key) || !(manage || heldKeys.has(key))) return false;
2635
+ const names = grant.permission.kind === "instance" ? levelNames(policy, grant.permission.resource) : [];
2636
+ if (names.length === 0) return true;
2637
+ const offered = offeredLevels(key, names);
2638
+ return heldLevels(policy, grant, names).every((name) => offered.includes(name));
2639
+ };
2640
+ const custom = global || tenant === void 0 ? {
2641
+ roles: [],
2642
+ inert: []
2643
+ } : assignableCustomRoles(policy, customRoles, tenant, manage ? void 0 : roles, mayHandOut);
2316
2644
  return {
2317
- roles,
2318
- permissions: listPermissions(policy.permissions).filter((leaf) => ceiling.has(leaf.key) && (manage || heldKeys.has(leaf.key))),
2319
- manage
2645
+ roles: [...roles, ...custom.roles],
2646
+ permissions,
2647
+ levels,
2648
+ manage,
2649
+ inert: custom.inert
2650
+ };
2651
+ }
2652
+ /**
2653
+ * The tenant's custom roles the subject may hand out: held at a scope it may
2654
+ * assign declared roles at (any scope with `meta.manageRoles`), allowing at
2655
+ * least one permission after the ceiling, and only permissions and levels it
2656
+ * may hand out at that scope. A role that allows nothing is `inert`.
2657
+ */
2658
+ function assignableCustomRoles(policy, customRoles, tenant, declared, mayHandOut) {
2659
+ const scopes = scopeList(policy.scopes);
2660
+ const root = rootScope(scopes);
2661
+ const assignsAt = declared === void 0 ? void 0 : new Set(declared.map((leaf) => leaf.on === void 0 ? root : resolveScope(scopes, leaf.on)));
2662
+ const out = /* @__PURE__ */ new Map();
2663
+ const inert = /* @__PURE__ */ new Set();
2664
+ for (const role of tenantCustomRoles(policy, customRoles)) {
2665
+ if (role.tenant !== tenant || out.has(role.name)) continue;
2666
+ const at = customRoleScope(role, scopes);
2667
+ if (at === void 0 || assignsAt !== void 0 && !assignsAt.has(at)) continue;
2668
+ const { grants } = resolveCustomRole(policy, role);
2669
+ if (!grants.some((grant) => grant.effect === "allow")) {
2670
+ inert.add(role.name);
2671
+ continue;
2672
+ }
2673
+ if (grants.every((grant) => grant.effect === "deny" || mayHandOut(at, grant))) out.set(role.name, synthesiseRole(role.name, {
2674
+ on: at,
2675
+ assignable: true
2676
+ }));
2677
+ }
2678
+ return {
2679
+ roles: [...out.values()].toSorted((a, b) => a.key.localeCompare(b.key)),
2680
+ inert: [...inert].filter((name) => !out.has(name)).toSorted()
2320
2681
  };
2321
2682
  }
2322
2683
  /** The one path from a resolved subject to a `Snapshot`; pure apart from the default clock. */
@@ -2340,14 +2701,16 @@ function snapshotOf(policy, subject, options) {
2340
2701
  now: Math.floor(now),
2341
2702
  vocabulary: policy.vocabulary,
2342
2703
  scopes: snapshotScopes(policy),
2704
+ idOf: (name) => getResource(policy.permissions, name)?.id,
2343
2705
  delegated: delegatedPermissions(policy.delegations, subject, new Set(roles), now),
2344
2706
  assignable: (tenant) => {
2345
2707
  const found = assignableIn(policy, subject, options.customRoles, customGrants, tenant, options.assignable?.get(tenant), now);
2346
- return {
2708
+ return compact({
2347
2709
  tenant,
2348
2710
  roles: found.roles.map((leaf) => leaf.key),
2349
- permissions: found.permissions
2350
- };
2711
+ permissions: found.permissions,
2712
+ levels: Object.keys(found.levels).length === 0 ? void 0 : found.levels
2713
+ });
2351
2714
  }
2352
2715
  }));
2353
2716
  }
@@ -2356,12 +2719,18 @@ function snapshotScopes(policy) {
2356
2719
  if (policy.scopes.length === 0) return;
2357
2720
  const resources = [...policy.resources.values()];
2358
2721
  return policy.scopes.map((scope) => {
2359
- const partitioned = resources.filter((node) => partitionsOf(node, policy.scopes).includes(scope.name)).map((node) => node.name);
2722
+ const nodes = resources.filter((node) => partitionsOf(node, policy.scopes).includes(scope.name));
2723
+ const partitioned = nodes.map((node) => node.name);
2724
+ const fields = Object.fromEntries(nodes.flatMap((node) => {
2725
+ const field = scopeField(node, scope.name, policy.scopes);
2726
+ return field === void 0 || field === scope.key ? [] : [[node.name, field]];
2727
+ }));
2360
2728
  return compact({
2361
2729
  name: scope.name,
2362
2730
  key: scope.key ?? "",
2363
2731
  within: scope.within,
2364
- resources: partitioned.length === 0 ? void 0 : partitioned
2732
+ resources: partitioned.length === 0 ? void 0 : partitioned,
2733
+ fields: Object.keys(fields).length === 0 ? void 0 : fields
2365
2734
  });
2366
2735
  });
2367
2736
  }
@@ -2457,7 +2826,8 @@ function buildInstance(policy, subject, envBase, team) {
2457
2826
  scope: permission.scope,
2458
2827
  resource: resourceRef,
2459
2828
  subject,
2460
- message: deniedMessage(permission.key, subject.principal?.id, decision.denials, decision.alternatives.map((leaf) => leaf.key))
2829
+ message: deniedMessage(permission.key, subject.principal?.id, decision.denials, decision.alternatives.map((leaf) => leaf.key)),
2830
+ ...data === void 0 || resource === void 0 ? {} : { disclosure: resource.disclosure }
2461
2831
  });
2462
2832
  };
2463
2833
  const instance = {
@@ -2473,7 +2843,10 @@ function buildInstance(policy, subject, envBase, team) {
2473
2843
  let granted = 0;
2474
2844
  let denied = 0;
2475
2845
  let approvalRequired = 0;
2476
- const quiet = envFor(false);
2846
+ const quiet = {
2847
+ ...envFor(false, true),
2848
+ outcomeOnly: true
2849
+ };
2477
2850
  const trusted = options?.trusted ?? true;
2478
2851
  const decideOptions = {
2479
2852
  ...options,
@@ -2533,6 +2906,7 @@ function buildInstance(policy, subject, envBase, team) {
2533
2906
  graph: envBase.relations !== void 0,
2534
2907
  scopes: scopeList(policy.scopes),
2535
2908
  partitioned: (name, key) => relatesTo(getResource(policy.permissions, permission.resource), key, name, scopeList(policy.scopes)),
2909
+ fieldOf: (name) => scopeField(getResource(policy.permissions, permission.resource), name, scopeList(policy.scopes)),
2536
2910
  tenant: subject.principal?.tenant,
2537
2911
  team,
2538
2912
  now: nowSeconds(),
@@ -2615,7 +2989,7 @@ function buildInstance(policy, subject, envBase, team) {
2615
2989
  relationCache
2616
2990
  }, team);
2617
2991
  }),
2618
- snapshot(options) {
2992
+ snapshot: ((options) => {
2619
2993
  const snapshot = snapshotOf(policy, subject, compact({
2620
2994
  customRoles: envBase.customRoles,
2621
2995
  customGrants,
@@ -2626,7 +3000,7 @@ function buildInstance(policy, subject, envBase, team) {
2626
3000
  }));
2627
3001
  if (options?.signer !== void 0) return signSnapshot(snapshot, options.signer, options.audience);
2628
3002
  return snapshot;
2629
- },
3003
+ }),
2630
3004
  on(event, handler) {
2631
3005
  const set = listeners[event];
2632
3006
  set.add(handler);
@@ -2687,24 +3061,111 @@ function buildInstance(policy, subject, envBase, team) {
2687
3061
  const tenant = options?.tenant ?? subject.principal?.tenant;
2688
3062
  return tenant === void 0 ? [] : assignableAt(tenant).permissions;
2689
3063
  },
3064
+ assignableLevels(permission, options) {
3065
+ const of = (found) => (Object.hasOwn(found.levels, permission.key) ? found.levels[permission.key] : void 0) ?? [];
3066
+ if (options?.scope === "global") return of(assignableIn(policy, subject, envBase.customRoles, customGrants, void 0, void 0, nowSeconds(), true));
3067
+ const tenant = options?.tenant ?? subject.principal?.tenant;
3068
+ return tenant === void 0 ? [] : of(assignableAt(tenant));
3069
+ },
2690
3070
  decideRoleChange(change, options) {
2691
- return decideRoleChange(policy, subject.principal, scopeList(policy.scopes), change, (tenant) => {
3071
+ return decideRoleChange(policy, subject.principal, scopeList(policy.scopes), change, envBase.customRoles, (tenant) => {
2692
3072
  const found = assignableAt(tenant);
2693
3073
  return {
2694
3074
  assignable: new Set(found.roles.map((leaf) => leaf.key)),
2695
- manage: found.manage
3075
+ manage: found.manage,
3076
+ inert: new Set(found.inert)
2696
3077
  };
2697
3078
  }, nowSeconds(), options);
2698
3079
  },
2699
3080
  activate(input) {
2700
3081
  return activate(policy, subject, input, nowSeconds());
2701
3082
  },
3083
+ derive(options) {
3084
+ const scopes = scopeList(policy.scopes);
3085
+ const tenants = tenantsOf(subject.principal, scopes);
3086
+ const auth = [];
3087
+ const named = options.customRoles === void 0 ? void 0 : roleSourceFor(options.customRoles, subject, auth);
3088
+ const roleSource = options.customRoles === void 0 ? envBase.roleSource : named;
3089
+ const roles = options.customRoles === void 0 ? envBase.customRoles : customRolesFor(named, tenants, auth, subject.principal === null ? void 0 : subject.principal.roles ?? [], (tenant) => heldRoleNamesIn(subject.principal, scopes, tenant));
3090
+ const names = options.customRoles === void 0 ? envBase.assignable : assignableNamesFor(named, tenants, auth);
3091
+ const approvals = options.approvalPolicies === void 0 ? envBase.approvalPolicies : approvalPoliciesFor(policy, options.approvalPolicies, tenants, auth);
3092
+ const relationSource = options.relations ?? envBase.relations;
3093
+ const build = (loadedRoles, loadedNames, loadedApprovals) => buildInstance(policy, subject, compact({
3094
+ ...envBase,
3095
+ customRoles: loadedRoles,
3096
+ roleSource,
3097
+ assignable: loadedNames,
3098
+ approvalPolicies: loadedApprovals,
3099
+ relations: relationSource,
3100
+ relationCache: options.relations === void 0 ? relationCache : void 0,
3101
+ queuedAuth: auth,
3102
+ queuedErrors: []
3103
+ }), team);
3104
+ if (isThenable(roles) || isThenable(names) || isThenable(approvals)) return Promise.all([
3105
+ roles,
3106
+ names,
3107
+ approvals
3108
+ ]).then(([loadedRoles, loadedNames, loadedApprovals]) => build(loadedRoles, loadedNames, loadedApprovals));
3109
+ return build(roles, names, approvals);
3110
+ },
2702
3111
  subject
2703
3112
  };
2704
3113
  return Object.freeze(instance);
2705
3114
  }
2706
3115
  //#endregion
3116
+ //#region src/core/oauth-scopes.ts
3117
+ /**
3118
+ * `delegation` with every coarse OAuth scope the policy declares in
3119
+ * `oauthScopes` followed by the scopes of the permissions it covers, so the
3120
+ * delegation checks downstream match them. The token's scopes stay first and
3121
+ * unchanged; an undeclared scope expands to nothing.
3122
+ */
3123
+ function expandOAuthScopes(policy, delegation) {
3124
+ const declared = policy.oauthScopes;
3125
+ const scopes = delegation?.scopes;
3126
+ if (declared === void 0 || delegation === void 0 || scopes === void 0) return delegation;
3127
+ const out = new Set(scopes);
3128
+ for (const entry of declared) if (scopes.includes(entry.scope)) for (const scope of entry.scopes) out.add(scope);
3129
+ return out.size === scopes.length ? delegation : {
3130
+ ...delegation,
3131
+ scopes: [...out]
3132
+ };
3133
+ }
3134
+ /** The coarse scopes the policy declares that cover `permission`, in declaration order. */
3135
+ function coarseScopesFor(policy, permission) {
3136
+ return (policy.oauthScopes ?? []).filter((entry) => entry.permissions.includes(permission.key)).map((entry) => entry.scope);
3137
+ }
3138
+ /** Every token scope that reaches `permission`: its own, any it was renamed from, and the coarse scopes covering it. */
3139
+ function scopesReaching(policy, permission) {
3140
+ return [
3141
+ permission.scope,
3142
+ ...formerScopes(permission),
3143
+ ...coarseScopesFor(policy, permission)
3144
+ ];
3145
+ }
3146
+ /**
3147
+ * The scope an `insufficient_scope` challenge names for `permission`: the
3148
+ * first coarse scope that covers it, which the authorization server issues,
3149
+ * else the permission's own scope.
3150
+ */
3151
+ function challengeScope(policy, permission) {
3152
+ return coarseScopesFor(policy, permission)[0] ?? permission.scope;
3153
+ }
3154
+ //#endregion
2707
3155
  //#region src/core/resolve-subject.ts
3156
+ /**
3157
+ * The tenant a user API key holds its owner to (`Credential.tenant`): only
3158
+ * the memberships inside it count, and no global role.
3159
+ */
3160
+ function heldTenantOf(principal) {
3161
+ const credential = principal["credential"];
3162
+ if (credential === null || typeof credential !== "object" || !Object.hasOwn(credential, "tenant") || !Object.hasOwn(credential, "kind")) return;
3163
+ const record = credential;
3164
+ return record.kind === "user" && typeof record.tenant === "string" && record.tenant !== "" ? record.tenant : void 0;
3165
+ }
3166
+ function outsideHeld(held, options) {
3167
+ return held !== void 0 && options.tenant !== void 0 && options.tenant !== held;
3168
+ }
2708
3169
  function assemblePrincipal(policy, user, options, auth) {
2709
3170
  let principal;
2710
3171
  let context = {};
@@ -2712,6 +3173,7 @@ function assemblePrincipal(policy, user, options, auth) {
2712
3173
  let delegation = options.delegation;
2713
3174
  let session = options.session;
2714
3175
  let expiresAt = options.expiresAt;
3176
+ let liveSession;
2715
3177
  try {
2716
3178
  if (user === null || user === void 0) principal = policy.subject(user);
2717
3179
  else if (isSubject(user)) {
@@ -2721,6 +3183,7 @@ function assemblePrincipal(policy, user, options, auth) {
2721
3183
  delegation = user.delegation ?? delegation;
2722
3184
  session = user.session ?? session;
2723
3185
  expiresAt = user.expiresAt ?? expiresAt;
3186
+ liveSession = user.liveSession === true ? true : void 0;
2724
3187
  } else if (isPrincipal(user)) principal = user;
2725
3188
  else principal = policy.subject(user);
2726
3189
  } catch {
@@ -2731,30 +3194,73 @@ function assemblePrincipal(policy, user, options, auth) {
2731
3194
  let live = false;
2732
3195
  const source = options.memberships === void 0 ? void 0 : asMembershipSource(options.memberships);
2733
3196
  const fromClaims = source?.claimsFirst === true && principal?.memberships !== void 0 && principal.membershipsTruncated !== true;
2734
- if (principal !== null && source !== void 0 && !fromClaims) {
2735
- live = true;
3197
+ const read = (from, of) => {
2736
3198
  try {
2737
- memberships = source.membershipsFor(compact({
2738
- id: principal.id,
2739
- kind: principal.kind
3199
+ return from.membershipsFor(compact({
3200
+ id: of.id,
3201
+ kind: of.kind
2740
3202
  }), compact({ tenant: options.tenant }));
2741
3203
  } catch {
2742
3204
  auth.push({
2743
3205
  reason: "source-threw",
2744
3206
  source: "memberships"
2745
3207
  });
2746
- memberships = [];
3208
+ return [];
2747
3209
  }
3210
+ };
3211
+ let stale;
3212
+ if (principal !== null && source !== void 0 && !fromClaims) {
3213
+ live = true;
3214
+ memberships = read(source, principal);
3215
+ } else if (principal?.memberships !== void 0 && fromClaims && source.onStale === "reread" && source.version !== void 0) {
3216
+ const reread = rereadStale(source, principal, principal.memberships, () => read(source, principal), auth);
3217
+ memberships = reread.memberships;
3218
+ stale = reread.stale;
2748
3219
  }
2749
- return {
3220
+ return compact({
2750
3221
  principal,
2751
3222
  context: contextResult,
2752
3223
  actor,
2753
- delegation,
3224
+ delegation: expandOAuthScopes(policy, delegation),
2754
3225
  session,
2755
3226
  expiresAt,
2756
3227
  memberships,
2757
- live
3228
+ live,
3229
+ stale,
3230
+ liveSession: principal === null ? void 0 : liveSession
3231
+ });
3232
+ }
3233
+ function versionQuery(principal) {
3234
+ return compact({
3235
+ id: principal.id,
3236
+ roles: principal.roles,
3237
+ memberships: principal.memberships
3238
+ });
3239
+ }
3240
+ function rereadStale(source, principal, claims, read, auth) {
3241
+ const claimed = principal.authzVersion;
3242
+ const judge = (current) => typeof claimed === "number" && current !== void 0 && claimed >= current ? "fresh" : "reread";
3243
+ const unknown = () => {
3244
+ auth.push({
3245
+ reason: "source-threw",
3246
+ source: "memberships"
3247
+ });
3248
+ return "unknown";
3249
+ };
3250
+ let freshness;
3251
+ try {
3252
+ const current = source.version?.(versionQuery(principal));
3253
+ freshness = isThenable(current) ? Promise.resolve(current).then(judge, unknown) : judge(current);
3254
+ } catch {
3255
+ freshness = unknown();
3256
+ }
3257
+ if (isThenable(freshness)) return {
3258
+ memberships: freshness.then((found) => found === "reread" ? read() : claims),
3259
+ stale: freshness.then((found) => found === "unknown")
3260
+ };
3261
+ return {
3262
+ memberships: freshness === "reread" ? read() : claims,
3263
+ stale: freshness === "unknown"
2758
3264
  };
2759
3265
  }
2760
3266
  function finishSubject(policy, assembled, context, input, options, extra) {
@@ -2766,7 +3272,10 @@ function finishSubject(policy, assembled, context, input, options, extra) {
2766
3272
  expiresAt: assembled.expiresAt
2767
3273
  }));
2768
3274
  const scopes = scopeList(policy.scopes);
2769
- const { roles, memberships } = applyRoleKinds(policy, assembled.principal.roles, normalizeMemberships(input, scopes));
3275
+ const held = heldTenantOf(assembled.principal);
3276
+ const kinds = applyRoleKinds(policy, assembled.principal.roles, normalizeMemberships(input, scopes));
3277
+ const roles = held === void 0 ? kinds.roles : [];
3278
+ const memberships = held === void 0 ? kinds.memberships : outsideHeld(held, options) ? [] : kinds.memberships.filter((membership) => tenantOf(membership, scopes) === held);
2770
3279
  const plans = [.../* @__PURE__ */ new Set([...assembled.principal.plans ?? [], ...extra.plans])];
2771
3280
  const withMemberships = freezeCopy(compact({
2772
3281
  ...assembled.principal,
@@ -2782,11 +3291,14 @@ function finishSubject(policy, assembled, context, input, options, extra) {
2782
3291
  context: freezeCopy(context),
2783
3292
  session: assembled.session,
2784
3293
  expiresAt: assembled.expiresAt,
2785
- stale: extra.stale ? true : void 0
3294
+ stale: extra.stale ? true : void 0,
3295
+ liveSession: assembled.liveSession
2786
3296
  }));
2787
3297
  }
2788
3298
  function activeTenantOf(policy, assembled, memberships, options) {
2789
3299
  if (assembled.principal === null) return;
3300
+ const held = heldTenantOf(assembled.principal);
3301
+ if (held !== void 0) return outsideHeld(held, options) ? void 0 : held;
2790
3302
  return resolveActiveTenant({
2791
3303
  ...assembled.principal,
2792
3304
  memberships
@@ -2807,12 +3319,13 @@ function settle(value, fallback, onError) {
2807
3319
  function staleness(policy, assembled, options, auth) {
2808
3320
  const principal = assembled.principal;
2809
3321
  if ((policy.fresh ?? []).length === 0 || principal === null || assembled.live) return false;
3322
+ if (assembled.stale !== void 0) return assembled.stale;
2810
3323
  const source = options.memberships === void 0 ? void 0 : asMembershipSource(options.memberships);
2811
3324
  const claimed = principal.authzVersion;
2812
3325
  if (source?.version === void 0 || typeof claimed !== "number") return true;
2813
3326
  const compare = (current) => current === void 0 || claimed < current;
2814
3327
  try {
2815
- const current = source.version({ id: principal.id });
3328
+ const current = source.version(versionQuery(principal));
2816
3329
  return isThenable(current) ? settle(Promise.resolve(current).then(compare), true, () => {
2817
3330
  auth.push({
2818
3331
  reason: "source-threw",
@@ -2832,7 +3345,7 @@ function cleanNames(list) {
2832
3345
  }
2833
3346
  function entitlementsOf(assembled, tenant, options, auth) {
2834
3347
  const source = options.entitlements;
2835
- if (source === void 0 || assembled.principal === null) return [];
3348
+ if (source === void 0 || assembled.principal === null || outsideHeld(heldTenantOf(assembled.principal), options)) return [];
2836
3349
  try {
2837
3350
  const found = source.entitlementsFor({ id: assembled.principal.id }, compact({ tenant }));
2838
3351
  return isThenable(found) ? settle(Promise.resolve(found).then(cleanNames), [], () => {
@@ -2921,9 +3434,11 @@ function hostedPolicy(policy, source) {
2921
3434
  }
2922
3435
  function instantiate(codePolicy, subject, options, auth) {
2923
3436
  const { policy, errors } = hostedPolicy(codePolicy, options.policies);
2924
- const tenants = tenantsOf(subject.principal, scopeList(policy.scopes));
2925
- const customRoles = customRolesFor(options.customRoles, tenants, auth, subject.principal !== null);
2926
- const assignable = assignableNamesFor(options.customRoles, tenants, auth);
3437
+ const scopes = scopeList(policy.scopes);
3438
+ const tenants = tenantsOf(subject.principal, scopes);
3439
+ const roleSource = roleSourceFor(options.customRoles, subject, auth);
3440
+ const customRoles = customRolesFor(roleSource, tenants, auth, subject.principal === null ? void 0 : subject.principal.roles ?? [], (tenant) => heldRoleNamesIn(subject.principal, scopes, tenant));
3441
+ const assignable = assignableNamesFor(roleSource, tenants, auth);
2927
3442
  const approvals = approvalPoliciesFor(policy, options.approvalPolicies, tenants, auth);
2928
3443
  const build = (roles, names, approvalPolicies) => buildInstance(policy, subject, compact({
2929
3444
  customRoles: roles,
@@ -2931,7 +3446,7 @@ function instantiate(codePolicy, subject, options, auth) {
2931
3446
  limits: options.limits,
2932
3447
  limitCache: /* @__PURE__ */ new Map(),
2933
3448
  simulated: false,
2934
- roleSource: options.customRoles,
3449
+ roleSource,
2935
3450
  assignable: names,
2936
3451
  queuedAuth: auth,
2937
3452
  queuedErrors: errors,
@@ -2953,4 +3468,4 @@ function createPermDock(policy, user, options = {}) {
2953
3468
  return instantiate(policy, subject, options, auth);
2954
3469
  }
2955
3470
  //#endregion
2956
- export { approvalPoliciesFor as _, snapshotScopes as a, supportAccess as c, membershipsClaim as d, resolveCustomRole as f, isExternallyManaged as g, composeMemberships as h, snapshotOf as i, customRoleClaim as l, claimsFirst as m, resolveSubject as n, declaredRoleNames as o, validateCustomRole as p, collectSnapshotGrants as r, breakGlass as s, createPermDock as t, customRoleScope as u, memoryApprovalPolicies as v };
3471
+ export { countHolders as C, memoryApprovalPolicies as D, approvalPoliciesFor as E, validateApprovalPolicy as O, composeMemberships as S, APPROVAL_POLICY_UNAVAILABLE as T, leveled as _, collectSnapshotGrants as a, validateCustomRole as b, rankRoles as c, breakGlass as d, supportAccess as f, customRoleScope as g, customRoleClaim as h, scopesReaching as i, usesAssigns as l, ceilingGrants as m, resolveSubject as n, snapshotOf as o, andWhere as p, challengeScope as r, snapshotScopes as s, createPermDock as t, declaredRoleNames as u, membershipsClaim as v, isExternallyManaged as w, claimsFirst as x, resolveCustomRole as y };