permdock 0.1.0 → 0.3.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 (350) 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-CKwwqFWz.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 +223 -0
  18. package/dist/better-supabase/index.js +648 -0
  19. package/dist/bind-C_ob3LKa.d.ts +25 -0
  20. package/dist/bind-CuzGytRf.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-BDG_nkxt.d.ts → capability-2kYTMWDv.d.ts} +2 -2
  25. package/dist/{capability-B68OFnZx.js → capability-4qPdO9gk.js} +7 -5
  26. package/dist/catalog/index.d.ts +1 -1
  27. package/dist/catalog/index.js +1 -1
  28. package/dist/catalog-CRW5Mx_B.js +2 -0
  29. package/dist/{catalog-H-vFGwWK.js → catalog-DWF5cYqB.js} +4 -4
  30. package/dist/{catalog-B0E6bggc.js → catalog-dFZdWCGN.js} +2 -2
  31. package/dist/{catalog-doc-BSo_Q_-6.js → catalog-doc-DdGm82XT.js} +20 -32
  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-CB_ELYuX.js} +2 -2
  44. package/dist/{cloud-Cuxr6Czw.js → cloud-DWLXO2af.js} +7 -6
  45. package/dist/collect-DClJsheJ.js +83 -0
  46. package/dist/{collect-O-tOtbnk.js → collect-DSTE3ml9.js} +194 -42
  47. package/dist/collect-DSWOk19R.js +2 -0
  48. package/dist/{commands-BMh_Etm2.js → commands-DSxCqG9A.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-BkRyBDBw.d.ts +94 -0
  53. package/dist/config-BJiM-xAm.js +157 -0
  54. package/dist/config-Dnszbv-6.js +36 -0
  55. package/dist/config-report-DMvWKUxL.js +44 -0
  56. package/dist/{connection-JvqwHODC.js → connection-CU8MQxRi.js} +1 -1
  57. package/dist/{context-CKEAabd7.js → context-Bcy85rGi.js} +36 -27
  58. package/dist/convex/index.d.ts +9 -4
  59. package/dist/convex/index.js +14 -4
  60. package/dist/create-B1d519XK.d.ts +245 -0
  61. package/dist/create-CMwIl-w2.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-DMYiCjJV.d.ts} +9 -10
  66. package/dist/{diff-x5Il8LVa.js → diff-2VaeChzX.js} +44 -11
  67. package/dist/{diff-BUzf5Poi.js → diff-DBIhz_f2.js} +2 -2
  68. package/dist/{doctor-Bqo6FwUa.js → doctor-BwCAMLx_.js} +2 -2
  69. package/dist/doctor-collect-CHk6XWOA.js +665 -0
  70. package/dist/doctor-igIiM_Hl.js +1194 -0
  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-DPdqI50v.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-CTLijvdm.js} +66 -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-0nNqHspG.d.ts +6 -0
  105. package/dist/{index-xIxJLwOQ.d.ts → index-BNc7Wer_.d.ts} +75 -7
  106. package/dist/{index-CltcQ4Bf.d.ts → index-C7Tw7MaW.d.ts} +68 -5
  107. package/dist/{index-LF_hS-yn.d.ts → index-C8z4RjQV.d.ts} +1 -1
  108. package/dist/index.d.ts +14 -9
  109. package/dist/index.js +22 -17
  110. package/dist/instance-options-Dhhcnm3y.js +22 -0
  111. package/dist/instance-options-jARdwfPN.d.ts +11 -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-_RRHmnMY.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-DcqM8nU7.js → openapi-BqRBvAng.js} +237 -5
  144. package/dist/{openapi-DWD0Uc_4.js → openapi-CqqEbHsL.js} +5 -5
  145. package/dist/{openapi-import-BWRUNNeW.js → openapi-import-cmwetbzw.js} +1 -1
  146. package/dist/{openapi-CwePaF7C.js → openapi-l0xYHkcv.js} +3 -3
  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-BrMozJCo.js +36 -0
  153. package/dist/parse-local-manifest-JLnaBH0S.d.ts +10 -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-CbgvLnuo.js} +672 -149
  157. package/dist/{permissions-B8vCg_bI.d.ts → permissions-CDgvKG5L.d.ts} +2 -2
  158. package/dist/{permissions-DC7GROPA.js → permissions-DVoF6dMt.js} +77 -15
  159. package/dist/{permissions-B4apnahQ.d.ts → permissions-DttnqqxC.d.ts} +34 -5
  160. package/dist/{permissions-CofRqesP.js → permissions-RJrovw98.js} +3 -3
  161. package/dist/{pins-CqE1jZuF.js → pins-8ILTftC9.js} +2 -2
  162. package/dist/{policy-Cq2fB8Q5.d.ts → policy-D2W7uv19.d.ts} +2 -2
  163. package/dist/{policy-CInmNGkM.d.ts → policy-IB6yxpC3.d.ts} +351 -42
  164. package/dist/{policy-ANp36PnI.js → policy-xyWg7QdR.js} +173 -22
  165. package/dist/powersync-BylawD_U.js +540 -0
  166. package/dist/powersync-DliZK_-y.js +62 -0
  167. package/dist/powersync-e_AZrKbg.js +2 -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-DX075l9V.d.ts +7 -0
  172. package/dist/problem-details-_AMOYz-w.js +144 -0
  173. package/dist/{protected-BuPAycJT.d.ts → protected-BdnIEFkY.d.ts} +5 -4
  174. package/dist/protected-utU-SLuy.js +100 -0
  175. package/dist/protected-xAr24FEC.d.ts +40 -0
  176. package/dist/provider-CSQd7m92.js +52 -0
  177. package/dist/{provider-wTAUda2v.d.ts → provider-DZY281jO.d.ts} +1 -1
  178. package/dist/provider-store-Cfbvo4DE.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-CYyiLiwO.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-BOFU3chG.js → rls-B4ur8nKQ.js} +8 -3
  192. package/dist/rls-DQtdgTHB.js +2521 -0
  193. package/dist/rls-fields-74oZPXWL.js +3582 -0
  194. package/dist/{rls-graph-DxQ39PJj.js → rls-graph-CHwx0V8q.js} +100 -36
  195. package/dist/{rls-import-CXV6LBlS.js → rls-import-BTFVk3jH.js} +7 -6
  196. package/dist/{rls-migrate-DrFPbf8p.js → rls-migrate-CN4TlJTO.js} +6 -5
  197. package/dist/rls-ownership-RuFqaqGz.js +843 -0
  198. package/dist/rls-shared-Cewd_-zI.js +495 -0
  199. package/dist/rls-verify-CVQTHTh5.js +746 -0
  200. package/dist/{row-check-BajTgQZ-.d.ts → row-check-Dt35czLy.d.ts} +12 -15
  201. package/dist/row-conditions-h2HythXQ.js +27 -0
  202. package/dist/rpc-DL9Sqvb1.js +89 -0
  203. package/dist/{run-CJ6UvS3a.js → run-DKDbM4Du.js} +31 -12
  204. package/dist/runtime-CB8gQFvS.js +11 -0
  205. package/dist/{saas-BRrZzJkM.js → saas-DhpJjnBA.js} +17 -7
  206. package/dist/{schema-P8yoma__.js → schema-D3MnDMxa.js} +8 -4
  207. package/dist/scim/index.d.ts +1 -1
  208. package/dist/scim/index.js +1 -1
  209. package/dist/{scim-9NFa0XVZ.js → scim-BFGBBAqR.js} +1 -1
  210. package/dist/{scopes-cKVp_WTw.js → scopes-CVdPrWN-.js} +35 -8
  211. package/dist/server/index.d.ts +22 -11
  212. package/dist/server/index.js +11 -8
  213. package/dist/{credentials-BgRX3WEg.js → server-DlZ28Oz5.js} +1 -1
  214. package/dist/shared-DRu8VFVM.js +123 -0
  215. package/dist/{sink-Cfr9-7YC.d.ts → sink-B1fZzIMf.d.ts} +1 -1
  216. package/dist/skills-Biq5uup3.js +192 -0
  217. package/dist/{skills-C_lpV4Jx.js → skills-Bx-U0irG.js} +10 -4
  218. package/dist/{snapshot-DE8dE_hz.js → snapshot-Enxd0n-s.js} +138 -214
  219. package/dist/snapshot-cache-B-LxUahj.d.ts +45 -0
  220. package/dist/snapshot-cache-D6jLelln.js +44 -0
  221. package/dist/solid/index.d.ts +39 -9
  222. package/dist/solid/index.js +76 -85
  223. package/dist/sources-4p0-4xQm.js +545 -0
  224. package/dist/sources-BdOYLw9j.d.ts +192 -0
  225. package/dist/{sql-Bll1d7y0.js → sql-xdg5f4Fi.js} +6 -1
  226. package/dist/{src-CTNT-PBW.js → src-BDwEZWrZ.js} +149 -11
  227. package/dist/ssf/index.d.ts +3 -2
  228. package/dist/ssf/index.js +3 -29
  229. package/dist/store-DMAWwAJV.js +371 -0
  230. package/dist/{store-Bq90395i.d.ts → store-jOBYpiXC.d.ts} +13 -4
  231. package/dist/store-options-ByhZq1jV.js +716 -0
  232. package/dist/{stores-CkG2HrSg.js → stores-BT2pSCub.js} +10 -63
  233. package/dist/{stores-CvOnu_2T.d.ts → stores-C0IMQrGI.d.ts} +20 -9
  234. package/dist/{stream-BhMfZOwr.d.ts → stream-D1RXubw2.d.ts} +2 -2
  235. package/dist/{stream-Q_xlB6Ko.js → stream-DDpvynSB.js} +16 -2
  236. package/dist/subject-settings-BwBmCdTR.js +128 -0
  237. package/dist/supabase/index.d.ts +133 -6
  238. package/dist/supabase/index.js +4 -3
  239. package/dist/supabase/middleware.d.ts +32 -27
  240. package/dist/supabase/middleware.js +56 -22
  241. package/dist/{supabase-D8UlBA7Z.js → supabase-Boqb9a_Y.js} +3 -3
  242. package/dist/{supabase-DTxGtYdi.js → supabase-C0qESOa-.js} +347 -367
  243. package/dist/supabase-config-BpPXt3x1.js +472 -0
  244. package/dist/supabase-hook-6sYLXg_u.js +2 -0
  245. package/dist/{supabase-hook-D_OqGb_V.js → supabase-hook-CSLQ4zJ3.js} +1037 -705
  246. package/dist/supautils-Cn4R4cJ4.js +91 -0
  247. package/dist/svelte/PermissionBoundary.svelte +37 -0
  248. package/dist/svelte/Protected.svelte +8 -2
  249. package/dist/svelte/index.d.ts +9 -6
  250. package/dist/svelte/index.js +61 -7
  251. package/dist/svelte/runtime.d.ts +4 -3
  252. package/dist/svelte/runtime.js +4 -3
  253. package/dist/svelte/source.d.ts +9 -6
  254. package/dist/svelte/source.js +7 -4
  255. package/dist/terminal/index.d.ts +6 -14
  256. package/dist/terminal/index.js +44 -32
  257. package/dist/testing/index.d.ts +105 -16
  258. package/dist/testing/index.js +504 -40
  259. package/dist/testing/saas/index.d.ts +4 -4
  260. package/dist/testing/saas/index.js +2 -2
  261. package/dist/testing/saas/permissions.d.ts +1 -1
  262. package/dist/testing/saas/permissions.js +1 -1
  263. package/dist/token-CIWxJOCh.js +50 -0
  264. package/dist/trpc/index.d.ts +7 -26
  265. package/dist/trpc/index.js +17 -100
  266. package/dist/types-BnIEz1sU.d.ts +48 -0
  267. package/dist/{types-CxquUByI.d.ts → types-Bvc4oWT6.d.ts} +1 -1
  268. package/dist/{sources-DOpW5Ila.d.ts → types-C97IskBw.d.ts} +86 -127
  269. package/dist/types-CKNSyIf_.d.ts +52 -0
  270. package/dist/types-Cq_H8N_X.d.ts +197 -0
  271. package/dist/{types-D6Z6-rKJ.d.ts → types-DbjRN55U.d.ts} +273 -22
  272. package/dist/{types-BBK-exvJ.d.ts → types-Dnemwd77.d.ts} +7 -1
  273. package/dist/{types-BvBk1wgn.d.ts → types-DxiCGmmd.d.ts} +5 -3
  274. package/dist/{types-B9dmiiab.d.ts → types-QCuYJh4q.d.ts} +1 -1
  275. package/dist/{types-B8w5PFtS.d.ts → types-aqOHTHcp.d.ts} +17 -1
  276. package/dist/unplugin/index.d.ts +1 -1
  277. package/dist/unplugin/index.js +10 -9
  278. package/dist/usage-DorAJiU_.js +2 -0
  279. package/dist/{usage-1ozMubLY.js → usage-GTixvDDU.js} +2 -2
  280. package/dist/{usage-CUnK6Ou9.js → usage-ItS7nLcl.js} +225 -62
  281. package/dist/validation-error-Ba264OaJ.d.ts +26 -0
  282. package/dist/validation-error-CnjZ4t92.js +33 -0
  283. package/dist/{validity-D0uRmJKJ.js → validity-uOp8UpYn.js} +57 -36
  284. package/dist/{verifier-B90agnhl.js → verifier-BxBo6I44.js} +10 -5
  285. package/dist/version-BvacVTZR.js +17 -0
  286. package/dist/views-Soe7bGSC.js +95 -0
  287. package/dist/{vocabulary-Dk0fs-Jk.d.ts → vocabulary-DA8a8zif.d.ts} +1 -1
  288. package/dist/vue/index.d.ts +35 -13
  289. package/dist/vue/index.js +86 -93
  290. package/dist/watch-BwxLTYMZ.js +2 -0
  291. package/dist/watch-DVBJ9cBq.js +128 -0
  292. package/dist/webmcp/index.d.ts +2 -2
  293. package/dist/webmcp/index.js +7 -13
  294. package/package.json +62 -36
  295. package/schemas/approval-request-v1.json +175 -0
  296. package/schemas/catalog-v1.json +29 -1
  297. package/schemas/supabase-claims-v1.json +6 -1
  298. package/schemas/supabase-manifest-v1.json +150 -6
  299. package/skills/permdock/SKILL.md +7 -7
  300. package/skills/permdock-agents/SKILL.md +5 -5
  301. package/skills/permdock-agents/references/runtimes.md +12 -10
  302. package/skills/permdock-approvals/SKILL.md +4 -4
  303. package/skills/permdock-approvals/references/stores-and-resume.md +15 -4
  304. package/skills/permdock-audit/SKILL.md +3 -3
  305. package/skills/permdock-credentials/SKILL.md +4 -4
  306. package/skills/permdock-credentials/references/tokens-keys-links.md +7 -2
  307. package/skills/permdock-data/SKILL.md +4 -4
  308. package/skills/permdock-data/references/queries-and-rls.md +14 -6
  309. package/skills/permdock-data/references/relationships.md +5 -3
  310. package/skills/permdock-tenancy/SKILL.md +9 -6
  311. package/skills/permdock-tenancy/references/ownership-and-custom-roles.md +14 -8
  312. package/skills/permdock-wire/SKILL.md +4 -4
  313. package/skills/permdock-wire/references/adapters.md +42 -30
  314. package/skills/permdock-wire/references/policy-options.md +5 -3
  315. package/dist/approvers-aH0PJkmf.js +0 -20
  316. package/dist/catalog-idsHc0vo.js +0 -2
  317. package/dist/collect-B7_TIfmg.js +0 -49
  318. package/dist/collect-fRPAQIWb.js +0 -2
  319. package/dist/compile-CQbvyIBr.d.ts +0 -30
  320. package/dist/config-BPYmjTOp.js +0 -26
  321. package/dist/context-gtAwAILx.js +0 -6
  322. package/dist/create-BKF3dgFk.d.ts +0 -162
  323. package/dist/create-CSkhDU9T.js +0 -222
  324. package/dist/doctor-BGle8Gqi.js +0 -1314
  325. package/dist/helpers-DnSOrhB9.js +0 -682
  326. package/dist/index-fG6NSXlc.d.ts +0 -9
  327. package/dist/kernel-YOGCWNLf.js +0 -211
  328. package/dist/load-QOOm1z70.js +0 -2
  329. package/dist/manifest-DDQbC6K0.d.ts +0 -118
  330. package/dist/map-error-RJ3ZsUyA.js +0 -16
  331. package/dist/may-use-CnFEjVEp.js +0 -37
  332. package/dist/pg-4GJWDUxG.js +0 -953
  333. package/dist/plugin-4gr7IxFJ.js +0 -65
  334. package/dist/protected-B4K3BKoX.js +0 -138
  335. package/dist/protected-wY7nXhY9.d.ts +0 -23
  336. package/dist/provider-_wx46HpB.js +0 -48
  337. package/dist/rls-CJVja9Ww.js +0 -2119
  338. package/dist/rls-helpers-BxRuif0l.js +0 -1346
  339. package/dist/runtime-DP9ttFh1.js +0 -27
  340. package/dist/skills-Dx3QLnMY.js +0 -110
  341. package/dist/source-DoBBYJi2.js +0 -6
  342. package/dist/sources-DVHM3Mwq.js +0 -284
  343. package/dist/store-C6hFW6ta.js +0 -508
  344. package/dist/subject-settings-xt7V845g.js +0 -82
  345. package/dist/supabase-hook-DW-xP4hU.js +0 -2
  346. package/dist/types-DbWdEfcm.d.ts +0 -87
  347. package/dist/types-bva3i4Gf.d.ts +0 -98
  348. package/dist/types-i4BAOvdb.d.ts +0 -97
  349. package/dist/usage-PyEor6GA.js +0 -2
  350. 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) => {
@@ -1732,40 +2000,54 @@ function decideRoleChange(policy, principal, scopes, change, authority, now, opt
1732
2000
  }
1733
2001
  const kind = change?.kind;
1734
2002
  const target = change?.target;
1735
- if (typeof name !== "string" || kind !== "assign" && kind !== "revoke" && kind !== "transfer" || typeof change.id !== "string" || change.id === "" || target === null || typeof target !== "object" || typeof target.id !== "string" || target.id === "") {
2003
+ const global = change?.scope === "global";
2004
+ if (typeof name !== "string" || kind !== "assign" && kind !== "revoke" && kind !== "transfer" || (global ? change.id !== void 0 : typeof change.id !== "string" || change.id === "") || target === null || typeof target !== "object" || typeof target.id !== "string" || target.id === "") {
1736
2005
  deny("validation");
1737
2006
  return done(null);
1738
2007
  }
2008
+ const id = change.id ?? "";
1739
2009
  const binding = bindingOf(policy, name);
1740
- if (!declaredRoleNames$1(policy).has(name)) {
2010
+ const declared = declaredRoleNames$1(policy).has(name);
2011
+ const named = declared || global ? [] : tenantCustomRoles(policy, customRoles).filter((role) => role.name === name);
2012
+ if (!declared && named.length === 0) {
1741
2013
  deny("unknown-role");
1742
2014
  return done(null);
1743
2015
  }
1744
- const scope = resolveScope(scopes, change.scope);
1745
- const on = binding?.on ?? findRole(policy.vocabulary?.roles, name)?.on;
1746
- const heldAt = typeof on === "string" ? resolveScope(scopes, on) : void 0;
2016
+ const scope = global ? "global" : resolveScope(scopes, change.scope);
2017
+ const customAt = named.map((role) => customRoleScope(role, scopes));
2018
+ const on = declared ? binding?.on ?? findRole(policy.vocabulary?.roles, name)?.on : customAt.includes(scope) ? scope : customAt[0];
2019
+ const heldAt = typeof on === "string" ? resolveScope(scopes, on) : global ? "global" : void 0;
1747
2020
  if (scope === void 0 || heldAt === void 0 || heldAt !== scope) {
1748
2021
  deny("scope", { expected: heldAt ?? null });
1749
2022
  return done(null);
1750
2023
  }
1751
- const root = scopes[0]?.name;
1752
- const supplied = change.within === void 0 ? void 0 : Object.entries(change.within).find(([key]) => resolveScope(scopes, key) === root)?.[1];
1753
2024
  let tenant;
1754
2025
  let within = change.within;
1755
- if (scope === root) tenant = change.id;
1756
- else {
1757
- const anchor = heldAnchor(principal, scopes, scope, change.id, now);
1758
- const held = anchor === void 0 ? void 0 : tenantOf(anchor, scopes);
1759
- if (held !== void 0) {
1760
- tenant = supplied === void 0 || supplied === held ? held : void 0;
1761
- within = anchor?.within;
1762
- } else if (options.trusted === true) tenant = supplied;
1763
- }
1764
- if (tenant === void 0) {
1765
- deny("no-membership", {
1766
- scope,
1767
- id: change.id
1768
- });
2026
+ let custom;
2027
+ if (!global) {
2028
+ const root = scopes[0]?.name;
2029
+ const supplied = change.within === void 0 ? void 0 : Object.entries(change.within).find(([key]) => resolveScope(scopes, key) === root)?.[1];
2030
+ if (scope === root) tenant = id;
2031
+ else {
2032
+ const anchor = heldAnchor(principal, scopes, scope, id, now);
2033
+ const held = anchor === void 0 ? void 0 : tenantOf(anchor, scopes);
2034
+ if (held !== void 0) {
2035
+ tenant = supplied === void 0 || supplied === held ? held : void 0;
2036
+ within = anchor?.within;
2037
+ } else if (options.trusted === true) tenant = supplied;
2038
+ }
2039
+ if (tenant === void 0) {
2040
+ deny("no-membership", {
2041
+ scope,
2042
+ id
2043
+ });
2044
+ return done(null);
2045
+ }
2046
+ const found = tenant;
2047
+ custom = named.find((role) => sameId(role.tenant, found) && customRoleScope(role, scopes) === scope && sameId(customRoleId(role) ?? id, id));
2048
+ }
2049
+ if (!declared && custom === void 0) {
2050
+ deny("unknown-role");
1769
2051
  return done(null);
1770
2052
  }
1771
2053
  if (target.managedBy === "idp") {
@@ -1778,24 +2060,26 @@ function decideRoleChange(policy, principal, scopes, change, authority, now, opt
1778
2060
  let by = null;
1779
2061
  if (kind === "transfer") {
1780
2062
  if (self) deny("self-demotion");
1781
- if (holdsAt(principal, name, scope, change.id, now)) by = name;
2063
+ if (global ? (principal.roles ?? []).includes(name) : holdsAt(principal, name, scope, id, now)) by = name;
1782
2064
  else deny("not-assignable-by", { holds: false });
1783
2065
  } else {
1784
2066
  if (self) deny(kind === "revoke" ? "self-demotion" : "not-assignable-by", { self: true });
1785
2067
  const allowed = authority(tenant);
1786
- if (!allowed.assignable.has(name)) deny("not-assignable-by");
1787
- else if (usesAssigns(policy) && !allowed.manage) {
1788
- const held = rolesAtInstance(policy, principal, scopes, scope, change.id, within, now);
2068
+ const revocable = kind === "revoke" && custom !== void 0 && allowed.inert?.has(name);
2069
+ if (!allowed.assignable.has(name) && revocable !== true) deny("not-assignable-by");
2070
+ else if (custom === void 0 && usesAssigns(policy) && !allowed.manage) {
2071
+ const held = global ? rankRoles(policy, (principal.roles ?? []).filter((role) => declaredRoleNames$1(policy).has(role))) : rolesAtInstance(policy, principal, scopes, scope, id, within, now);
1789
2072
  by = held.find((role) => bindingOf(policy, role)?.assigns?.includes(name) === true) ?? null;
1790
2073
  if (by === null) deny("not-assignable-by", { held });
1791
2074
  }
1792
2075
  }
1793
2076
  if (kind !== "revoke" && !already) {
1794
- if (!mayHoldVia(policy, name, target.via)) deny("not-allowed-for-membership", {
2077
+ const kinds = custom === void 0 ? binding?.for : customForKinds(policy, custom);
2078
+ if (kinds !== void 0 && (target.via === void 0 || !kinds.includes(target.via))) deny("not-allowed-for-membership", {
1795
2079
  via: target.via ?? null,
1796
- for: binding?.for
2080
+ for: kinds
1797
2081
  });
1798
- const conflicts = conflictsWith(policy, name);
2082
+ const conflicts = custom === void 0 ? conflictsWith(policy, name) : new Set(includedRoles(custom).flatMap((include) => Array.from(conflictsWith(policy, include))));
1799
2083
  const clash = targetRoles.filter((role) => conflicts.has(role));
1800
2084
  if (clash.length > 0) deny("conflicting-role", { with: clash });
1801
2085
  }
@@ -1849,7 +2133,7 @@ async function discoverRole(item, allow, ctx) {
1849
2133
  ctx.incomplete();
1850
2134
  return;
1851
2135
  }
1852
- const key = findScope(ctx.scopes, item.scope)?.key;
2136
+ const key = scopeField(ctx.resource, item.scope, ctx.scopes);
1853
2137
  const id = key === void 0 ? void 0 : relationId(ownGet(ctx.row, key));
1854
2138
  if (id === void 0) return;
1855
2139
  const members = await ctx.list(item.scope, id);
@@ -2002,6 +2286,7 @@ function discover(grant, item, ctx) {
2002
2286
  case "plan":
2003
2287
  case "actor":
2004
2288
  case "assurance":
2289
+ case "inherit":
2005
2290
  ctx.incomplete();
2006
2291
  return Promise.resolve();
2007
2292
  default: return unknownKind(item, ctx);
@@ -2069,7 +2354,7 @@ async function whoCan(input) {
2069
2354
  };
2070
2355
  await Promise.all((policy.index.grantsByKey.get(permission.key) ?? []).flatMap((grant) => flattenGrantee(grant.to).map((item) => discover(grant, item, ctx))));
2071
2356
  const root = rootScope(scopes);
2072
- const tenantKey = root === void 0 ? void 0 : findScope(scopes, root)?.key;
2357
+ const tenantKey = root === void 0 ? void 0 : scopeField(getResource(policy.permissions, permission.resource), root, scopes);
2073
2358
  const tenant = tenantKey === void 0 ? void 0 : relationId(ownGet(row, tenantKey));
2074
2359
  const env = {
2075
2360
  emit: false,
@@ -2161,8 +2446,8 @@ function heldRoleNames(policy, subject, tenant, only, now) {
2161
2446
  if (only?.scope !== void 0 && scope === void 0) return [];
2162
2447
  const names = new Set(scope === void 0 ? subject.principal.roles ?? [] : []);
2163
2448
  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;
2449
+ if (isMembershipExpired(membership, now) || membership.keep !== void 0) continue;
2450
+ if (!(scope === void 0 ? tenantOf(membership, scopes) === tenant : membership.scope === scope && (only?.id === void 0 || sameId(membership.id, only.id)))) continue;
2166
2451
  for (const role of membership.roles) names.add(role);
2167
2452
  }
2168
2453
  return rankRoles(policy, [...names]);
@@ -2183,11 +2468,12 @@ function roleLeaf(policy, name) {
2183
2468
  function graphAware(grant, where) {
2184
2469
  const combined = combineWhere(grant.where, where);
2185
2470
  const graph = hasConditionOp(combined, "related");
2471
+ const inherits = flattenGrantee(grant.to).some((item) => item.kind === "inherit");
2186
2472
  return freezeDeep(compact({
2187
2473
  ...grant,
2188
2474
  where: combined,
2189
2475
  portable: graph ? false : grant.portable,
2190
- graph: graph && grant.portable ? true : void 0
2476
+ graph: graph && grant.portable && !inherits ? true : void 0
2191
2477
  }));
2192
2478
  }
2193
2479
  /**
@@ -2215,33 +2501,35 @@ function collectSnapshotGrants(policy, subject, customRoles, now = nowSeconds(),
2215
2501
  roles: new Set(expandRoleNames(membership.roles, declared, customRoles, tenantOf(membership, scopes)).roles)
2216
2502
  }));
2217
2503
  const holds = (entry, item) => entry.roles.has(item.role) && (typeof item.scope === "string" ? entry.membership.scope === item.scope : entry.membership.on !== void 0);
2504
+ const requirementOf = (grant, resource) => grant.requires === void 0 || grant.effect !== "allow" ? void 0 : requirementCondition(policy, subject, grant.requires, resource, now, customRoles, customGrants);
2218
2505
  const out = [];
2219
2506
  for (const grant of grantList(policy)) {
2507
+ if (mode === "held" && !coversRequirements(policy.permissions, grant.requires, subject.delegation)) continue;
2220
2508
  const resource = getResource(policy.permissions, grant.permission.resource);
2221
2509
  const match = matchGrantee(grant.to, subject, now, resource, scopes, policy.resources);
2222
2510
  if (skip(grant, match)) continue;
2223
2511
  const roleItems = flattenGrantee(grant.to).filter((item) => item.kind === "role");
2224
2512
  if (roleItems.length > 0 && subject.principal === null) continue;
2225
2513
  if (!roleItems.every((item) => item.scope !== "global" || global.roles.includes(item.role))) continue;
2226
- const merged = graphAware(grant, match.where);
2514
+ const merged = graphAware(grant, combineWhere(match.where, requirementOf(grant, resource)));
2227
2515
  const scoped = roleItems.filter((item) => item.scope !== "global");
2228
2516
  if (scoped.length === 0) {
2229
2517
  out.push({ grant: merged });
2230
2518
  continue;
2231
2519
  }
2232
- for (const entry of held) if (scoped.every((item) => item.kind === "role" && holds(entry, item))) out.push({
2520
+ for (const entry of held) if (keepsPermission(entry.membership, grant.permission.key) && scoped.every((item) => item.kind === "role" && holds(entry, item))) out.push({
2233
2521
  grant: merged,
2234
2522
  membership: entry.membership
2235
2523
  });
2236
2524
  }
2237
2525
  for (const { grant, role } of customGrants) {
2238
2526
  const globalHeld = holdsGlobalCustomRole(subject.principal?.roles, role);
2239
- const holders = held.filter((entry) => holdsCustomRole(entry.membership, role, scopes));
2527
+ const holders = held.filter((entry) => keepsPermission(entry.membership, grant.permission.key) && holdsCustomRole(entry.membership, role, scopes));
2240
2528
  if (holders.length === 0 && !globalHeld) continue;
2241
2529
  const resource = getResource(policy.permissions, grant.permission.resource);
2242
2530
  const match = matchGrantee(grant.to, subject, now, resource, scopes, policy.resources);
2243
2531
  if (skip(grant, match)) continue;
2244
- const merged = graphAware(grant, match.where);
2532
+ const merged = graphAware(grant, combineWhere(match.where, requirementOf(grant, resource)));
2245
2533
  if (globalHeld) out.push({ grant: merged });
2246
2534
  for (const entry of holders) out.push({
2247
2535
  grant: merged,
@@ -2262,6 +2550,20 @@ function assignableCandidates(policy) {
2262
2550
  return rankRoles(policy, [...new Set(names)]).map((name) => roleLeaf(policy, name));
2263
2551
  }
2264
2552
  /**
2553
+ * The levels a held allow of a permission covers: its own `level`, every
2554
+ * level when it has no row condition, or the levels whose condition equals
2555
+ * its condition.
2556
+ */
2557
+ function heldLevels(policy, grant, names) {
2558
+ if (grant.level !== void 0) return [grant.level];
2559
+ if (grant.where === void 0) return names;
2560
+ const held = canonicalJson(grant.where);
2561
+ return names.filter((name) => {
2562
+ const condition = levelCondition(policy, grant.permission.resource, name);
2563
+ return condition !== void 0 && canonicalJson(condition) === held;
2564
+ });
2565
+ }
2566
+ /**
2265
2567
  * What the subject may hand out in `tenant`: declared assignable roles and the
2266
2568
  * tenant custom-role ceiling, narrowed by `allowed` (`RoleSource.assignable`),
2267
2569
  * then intersected with the permissions the subject holds there. Holding a
@@ -2272,7 +2574,9 @@ function assignableIn(policy, subject, customRoles, customGrants, tenant, allowe
2272
2574
  if (principal === null) return {
2273
2575
  roles: [],
2274
2576
  permissions: [],
2275
- manage: false
2577
+ levels: {},
2578
+ manage: false,
2579
+ inert: []
2276
2580
  };
2277
2581
  const scopes = scopeList(policy.scopes);
2278
2582
  const scoped = tenant === void 0 ? subject : freezeDeep(compact({
@@ -2282,7 +2586,8 @@ function assignableIn(policy, subject, customRoles, customGrants, tenant, allowe
2282
2586
  tenant: resolveActiveTenant(principal, tenant, scopes, now)
2283
2587
  })
2284
2588
  }));
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));
2589
+ 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);
2590
+ const heldKeys = new Set(heldGrants.map((grant) => grant.permission.key));
2286
2591
  const heldNames = heldRoleNames(policy, subject, tenant, void 0, now);
2287
2592
  const quiet = {
2288
2593
  emit: false,
@@ -2313,10 +2618,72 @@ function assignableIn(policy, subject, customRoles, customGrants, tenant, allowe
2313
2618
  });
2314
2619
  const root = global ? "global" : rootScope(scopes);
2315
2620
  const ceiling = new Set((root === void 0 ? [] : ceilingGrants(policy, root, allowed)).map((grant) => grant.permission.key));
2621
+ const permissions = listPermissions(policy.permissions).filter((leaf) => ceiling.has(leaf.key) && (manage || heldKeys.has(leaf.key)));
2622
+ const offeredLevels = (key, names) => {
2623
+ if (manage) return names;
2624
+ const reached = new Set(heldGrants.filter((grant) => grant.permission.key === key).flatMap((grant) => heldLevels(policy, grant, names)));
2625
+ return names.filter((name) => reached.has(name));
2626
+ };
2627
+ const levels = {};
2628
+ for (const leaf of permissions) {
2629
+ const names = leaf.kind === "instance" ? levelNames(policy, leaf.resource) : [];
2630
+ if (names.length > 0) levels[leaf.key] = offeredLevels(leaf.key, names);
2631
+ }
2632
+ const ceilingKeys = /* @__PURE__ */ new Map();
2633
+ const mayHandOut = (scope, grant) => {
2634
+ const key = grant.permission.key;
2635
+ let keys = ceilingKeys.get(scope);
2636
+ if (keys === void 0) {
2637
+ keys = new Set(ceilingGrants(policy, scope, allowed).map((item) => item.permission.key));
2638
+ ceilingKeys.set(scope, keys);
2639
+ }
2640
+ if (!keys.has(key) || !(manage || heldKeys.has(key))) return false;
2641
+ const names = grant.permission.kind === "instance" ? levelNames(policy, grant.permission.resource) : [];
2642
+ if (names.length === 0) return true;
2643
+ const offered = offeredLevels(key, names);
2644
+ return heldLevels(policy, grant, names).every((name) => offered.includes(name));
2645
+ };
2646
+ const custom = global || tenant === void 0 ? {
2647
+ roles: [],
2648
+ inert: []
2649
+ } : assignableCustomRoles(policy, customRoles, tenant, manage ? void 0 : roles, mayHandOut);
2316
2650
  return {
2317
- roles,
2318
- permissions: listPermissions(policy.permissions).filter((leaf) => ceiling.has(leaf.key) && (manage || heldKeys.has(leaf.key))),
2319
- manage
2651
+ roles: [...roles, ...custom.roles],
2652
+ permissions,
2653
+ levels,
2654
+ manage,
2655
+ inert: custom.inert
2656
+ };
2657
+ }
2658
+ /**
2659
+ * The tenant's custom roles the subject may hand out: held at a scope it may
2660
+ * assign declared roles at (any scope with `meta.manageRoles`), allowing at
2661
+ * least one permission after the ceiling, and only permissions and levels it
2662
+ * may hand out at that scope. A role that allows nothing is `inert`.
2663
+ */
2664
+ function assignableCustomRoles(policy, customRoles, tenant, declared, mayHandOut) {
2665
+ const scopes = scopeList(policy.scopes);
2666
+ const root = rootScope(scopes);
2667
+ const assignsAt = declared === void 0 ? void 0 : new Set(declared.map((leaf) => leaf.on === void 0 ? root : resolveScope(scopes, leaf.on)));
2668
+ const out = /* @__PURE__ */ new Map();
2669
+ const inert = /* @__PURE__ */ new Set();
2670
+ for (const role of tenantCustomRoles(policy, customRoles)) {
2671
+ if (role.tenant !== tenant || out.has(role.name)) continue;
2672
+ const at = customRoleScope(role, scopes);
2673
+ if (at === void 0 || assignsAt !== void 0 && !assignsAt.has(at)) continue;
2674
+ const { grants } = resolveCustomRole(policy, role);
2675
+ if (!grants.some((grant) => grant.effect === "allow")) {
2676
+ inert.add(role.name);
2677
+ continue;
2678
+ }
2679
+ if (grants.every((grant) => grant.effect === "deny" || mayHandOut(at, grant))) out.set(role.name, synthesiseRole(role.name, {
2680
+ on: at,
2681
+ assignable: true
2682
+ }));
2683
+ }
2684
+ return {
2685
+ roles: [...out.values()].toSorted((a, b) => a.key.localeCompare(b.key)),
2686
+ inert: [...inert].filter((name) => !out.has(name)).toSorted()
2320
2687
  };
2321
2688
  }
2322
2689
  /** The one path from a resolved subject to a `Snapshot`; pure apart from the default clock. */
@@ -2340,14 +2707,16 @@ function snapshotOf(policy, subject, options) {
2340
2707
  now: Math.floor(now),
2341
2708
  vocabulary: policy.vocabulary,
2342
2709
  scopes: snapshotScopes(policy),
2710
+ idOf: (name) => getResource(policy.permissions, name)?.id,
2343
2711
  delegated: delegatedPermissions(policy.delegations, subject, new Set(roles), now),
2344
2712
  assignable: (tenant) => {
2345
2713
  const found = assignableIn(policy, subject, options.customRoles, customGrants, tenant, options.assignable?.get(tenant), now);
2346
- return {
2714
+ return compact({
2347
2715
  tenant,
2348
2716
  roles: found.roles.map((leaf) => leaf.key),
2349
- permissions: found.permissions
2350
- };
2717
+ permissions: found.permissions,
2718
+ levels: Object.keys(found.levels).length === 0 ? void 0 : found.levels
2719
+ });
2351
2720
  }
2352
2721
  }));
2353
2722
  }
@@ -2356,12 +2725,18 @@ function snapshotScopes(policy) {
2356
2725
  if (policy.scopes.length === 0) return;
2357
2726
  const resources = [...policy.resources.values()];
2358
2727
  return policy.scopes.map((scope) => {
2359
- const partitioned = resources.filter((node) => partitionsOf(node, policy.scopes).includes(scope.name)).map((node) => node.name);
2728
+ const nodes = resources.filter((node) => partitionsOf(node, policy.scopes).includes(scope.name));
2729
+ const partitioned = nodes.map((node) => node.name);
2730
+ const fields = Object.fromEntries(nodes.flatMap((node) => {
2731
+ const field = scopeField(node, scope.name, policy.scopes);
2732
+ return field === void 0 || field === scope.key ? [] : [[node.name, field]];
2733
+ }));
2360
2734
  return compact({
2361
2735
  name: scope.name,
2362
2736
  key: scope.key ?? "",
2363
2737
  within: scope.within,
2364
- resources: partitioned.length === 0 ? void 0 : partitioned
2738
+ resources: partitioned.length === 0 ? void 0 : partitioned,
2739
+ fields: Object.keys(fields).length === 0 ? void 0 : fields
2365
2740
  });
2366
2741
  });
2367
2742
  }
@@ -2373,6 +2748,7 @@ function buildInstance(policy, subject, envBase, team) {
2373
2748
  const relationCache = envBase.relationCache ?? /* @__PURE__ */ new Map();
2374
2749
  const relations = relationReader(envBase.relations, relationCache, policy.resources);
2375
2750
  const assignableAt = (tenant) => assignableIn(policy, subject, envBase.customRoles, customGrants, tenant, tenant === void 0 ? void 0 : envBase.assignable?.get(tenant));
2751
+ const assignableGlobal = () => assignableIn(policy, subject, envBase.customRoles, customGrants, void 0, void 0, nowSeconds(), true);
2376
2752
  const envFor = (emit, skipAlternatives = false) => ({
2377
2753
  emit,
2378
2754
  simulated: envBase.simulated,
@@ -2457,7 +2833,8 @@ function buildInstance(policy, subject, envBase, team) {
2457
2833
  scope: permission.scope,
2458
2834
  resource: resourceRef,
2459
2835
  subject,
2460
- message: deniedMessage(permission.key, subject.principal?.id, decision.denials, decision.alternatives.map((leaf) => leaf.key))
2836
+ message: deniedMessage(permission.key, subject.principal?.id, decision.denials, decision.alternatives.map((leaf) => leaf.key)),
2837
+ ...data === void 0 || resource === void 0 ? {} : { disclosure: resource.disclosure }
2461
2838
  });
2462
2839
  };
2463
2840
  const instance = {
@@ -2473,7 +2850,10 @@ function buildInstance(policy, subject, envBase, team) {
2473
2850
  let granted = 0;
2474
2851
  let denied = 0;
2475
2852
  let approvalRequired = 0;
2476
- const quiet = envFor(false);
2853
+ const quiet = {
2854
+ ...envFor(false, true),
2855
+ outcomeOnly: true
2856
+ };
2477
2857
  const trusted = options?.trusted ?? true;
2478
2858
  const decideOptions = {
2479
2859
  ...options,
@@ -2533,6 +2913,7 @@ function buildInstance(policy, subject, envBase, team) {
2533
2913
  graph: envBase.relations !== void 0,
2534
2914
  scopes: scopeList(policy.scopes),
2535
2915
  partitioned: (name, key) => relatesTo(getResource(policy.permissions, permission.resource), key, name, scopeList(policy.scopes)),
2916
+ fieldOf: (name) => scopeField(getResource(policy.permissions, permission.resource), name, scopeList(policy.scopes)),
2536
2917
  tenant: subject.principal?.tenant,
2537
2918
  team,
2538
2919
  now: nowSeconds(),
@@ -2615,7 +2996,7 @@ function buildInstance(policy, subject, envBase, team) {
2615
2996
  relationCache
2616
2997
  }, team);
2617
2998
  }),
2618
- snapshot(options) {
2999
+ snapshot: ((options) => {
2619
3000
  const snapshot = snapshotOf(policy, subject, compact({
2620
3001
  customRoles: envBase.customRoles,
2621
3002
  customGrants,
@@ -2626,7 +3007,7 @@ function buildInstance(policy, subject, envBase, team) {
2626
3007
  }));
2627
3008
  if (options?.signer !== void 0) return signSnapshot(snapshot, options.signer, options.audience);
2628
3009
  return snapshot;
2629
- },
3010
+ }),
2630
3011
  on(event, handler) {
2631
3012
  const set = listeners[event];
2632
3013
  set.add(handler);
@@ -2680,31 +3061,119 @@ function buildInstance(policy, subject, envBase, team) {
2680
3061
  return audiencesOf(policy, heldRoleNames(policy, subject, subject.principal?.tenant));
2681
3062
  },
2682
3063
  assignableRoles(options) {
3064
+ if (options?.scope === "global") return assignableGlobal().roles.filter((leaf) => leaf.on === void 0);
2683
3065
  return assignableAt(options?.tenant ?? subject.principal?.tenant).roles;
2684
3066
  },
2685
3067
  assignablePermissions(options) {
2686
- if (options?.scope === "global") return assignableIn(policy, subject, envBase.customRoles, customGrants, void 0, void 0, nowSeconds(), true).permissions;
3068
+ if (options?.scope === "global") return assignableGlobal().permissions;
2687
3069
  const tenant = options?.tenant ?? subject.principal?.tenant;
2688
3070
  return tenant === void 0 ? [] : assignableAt(tenant).permissions;
2689
3071
  },
3072
+ assignableLevels(permission, options) {
3073
+ const of = (found) => (Object.hasOwn(found.levels, permission.key) ? found.levels[permission.key] : void 0) ?? [];
3074
+ if (options?.scope === "global") return of(assignableGlobal());
3075
+ const tenant = options?.tenant ?? subject.principal?.tenant;
3076
+ return tenant === void 0 ? [] : of(assignableAt(tenant));
3077
+ },
2690
3078
  decideRoleChange(change, options) {
2691
- return decideRoleChange(policy, subject.principal, scopeList(policy.scopes), change, (tenant) => {
2692
- const found = assignableAt(tenant);
3079
+ return decideRoleChange(policy, subject.principal, scopeList(policy.scopes), change, envBase.customRoles, (tenant) => {
3080
+ const found = tenant === void 0 ? assignableGlobal() : assignableAt(tenant);
2693
3081
  return {
2694
3082
  assignable: new Set(found.roles.map((leaf) => leaf.key)),
2695
- manage: found.manage
3083
+ manage: found.manage,
3084
+ inert: new Set(found.inert)
2696
3085
  };
2697
3086
  }, nowSeconds(), options);
2698
3087
  },
2699
3088
  activate(input) {
2700
3089
  return activate(policy, subject, input, nowSeconds());
2701
3090
  },
3091
+ derive(options) {
3092
+ const scopes = scopeList(policy.scopes);
3093
+ const tenants = tenantsOf(subject.principal, scopes);
3094
+ const auth = [];
3095
+ const named = options.customRoles === void 0 ? void 0 : roleSourceFor(options.customRoles, subject, auth);
3096
+ const roleSource = options.customRoles === void 0 ? envBase.roleSource : named;
3097
+ 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));
3098
+ const names = options.customRoles === void 0 ? envBase.assignable : assignableNamesFor(named, tenants, auth);
3099
+ const approvals = options.approvalPolicies === void 0 ? envBase.approvalPolicies : approvalPoliciesFor(policy, options.approvalPolicies, tenants, auth);
3100
+ const relationSource = options.relations ?? envBase.relations;
3101
+ const build = (loadedRoles, loadedNames, loadedApprovals) => buildInstance(policy, subject, compact({
3102
+ ...envBase,
3103
+ customRoles: loadedRoles,
3104
+ roleSource,
3105
+ assignable: loadedNames,
3106
+ approvalPolicies: loadedApprovals,
3107
+ relations: relationSource,
3108
+ relationCache: options.relations === void 0 ? relationCache : void 0,
3109
+ queuedAuth: auth,
3110
+ queuedErrors: []
3111
+ }), team);
3112
+ if (isThenable(roles) || isThenable(names) || isThenable(approvals)) return Promise.all([
3113
+ roles,
3114
+ names,
3115
+ approvals
3116
+ ]).then(([loadedRoles, loadedNames, loadedApprovals]) => build(loadedRoles, loadedNames, loadedApprovals));
3117
+ return build(roles, names, approvals);
3118
+ },
2702
3119
  subject
2703
3120
  };
2704
3121
  return Object.freeze(instance);
2705
3122
  }
2706
3123
  //#endregion
3124
+ //#region src/core/oauth-scopes.ts
3125
+ /**
3126
+ * `delegation` with every coarse OAuth scope the policy declares in
3127
+ * `oauthScopes` followed by the scopes of the permissions it covers, so the
3128
+ * delegation checks downstream match them. The token's scopes stay first and
3129
+ * unchanged; an undeclared scope expands to nothing.
3130
+ */
3131
+ function expandOAuthScopes(policy, delegation) {
3132
+ const declared = policy.oauthScopes;
3133
+ const scopes = delegation?.scopes;
3134
+ if (declared === void 0 || delegation === void 0 || scopes === void 0) return delegation;
3135
+ const out = new Set(scopes);
3136
+ for (const entry of declared) if (scopes.includes(entry.scope)) for (const scope of entry.scopes) out.add(scope);
3137
+ return out.size === scopes.length ? delegation : {
3138
+ ...delegation,
3139
+ scopes: [...out]
3140
+ };
3141
+ }
3142
+ /** The coarse scopes the policy declares that cover `permission`, in declaration order. */
3143
+ function coarseScopesFor(policy, permission) {
3144
+ return (policy.oauthScopes ?? []).filter((entry) => entry.permissions.includes(permission.key)).map((entry) => entry.scope);
3145
+ }
3146
+ /** Every token scope that reaches `permission`: its own, any it was renamed from, and the coarse scopes covering it. */
3147
+ function scopesReaching(policy, permission) {
3148
+ return [
3149
+ permission.scope,
3150
+ ...formerScopes(permission),
3151
+ ...coarseScopesFor(policy, permission)
3152
+ ];
3153
+ }
3154
+ /**
3155
+ * The scope an `insufficient_scope` challenge names for `permission`: the
3156
+ * first coarse scope that covers it, which the authorization server issues,
3157
+ * else the permission's own scope.
3158
+ */
3159
+ function challengeScope(policy, permission) {
3160
+ return coarseScopesFor(policy, permission)[0] ?? permission.scope;
3161
+ }
3162
+ //#endregion
2707
3163
  //#region src/core/resolve-subject.ts
3164
+ /**
3165
+ * The tenant a user API key holds its owner to (`Credential.tenant`): only
3166
+ * the memberships inside it count, and no global role.
3167
+ */
3168
+ function heldTenantOf(principal) {
3169
+ const credential = principal["credential"];
3170
+ if (credential === null || typeof credential !== "object" || !Object.hasOwn(credential, "tenant") || !Object.hasOwn(credential, "kind")) return;
3171
+ const record = credential;
3172
+ return record.kind === "user" && typeof record.tenant === "string" && record.tenant !== "" ? record.tenant : void 0;
3173
+ }
3174
+ function outsideHeld(held, options) {
3175
+ return held !== void 0 && options.tenant !== void 0 && options.tenant !== held;
3176
+ }
2708
3177
  function assemblePrincipal(policy, user, options, auth) {
2709
3178
  let principal;
2710
3179
  let context = {};
@@ -2712,6 +3181,7 @@ function assemblePrincipal(policy, user, options, auth) {
2712
3181
  let delegation = options.delegation;
2713
3182
  let session = options.session;
2714
3183
  let expiresAt = options.expiresAt;
3184
+ let liveSession;
2715
3185
  try {
2716
3186
  if (user === null || user === void 0) principal = policy.subject(user);
2717
3187
  else if (isSubject(user)) {
@@ -2721,6 +3191,7 @@ function assemblePrincipal(policy, user, options, auth) {
2721
3191
  delegation = user.delegation ?? delegation;
2722
3192
  session = user.session ?? session;
2723
3193
  expiresAt = user.expiresAt ?? expiresAt;
3194
+ liveSession = user.liveSession === true ? true : void 0;
2724
3195
  } else if (isPrincipal(user)) principal = user;
2725
3196
  else principal = policy.subject(user);
2726
3197
  } catch {
@@ -2731,30 +3202,73 @@ function assemblePrincipal(policy, user, options, auth) {
2731
3202
  let live = false;
2732
3203
  const source = options.memberships === void 0 ? void 0 : asMembershipSource(options.memberships);
2733
3204
  const fromClaims = source?.claimsFirst === true && principal?.memberships !== void 0 && principal.membershipsTruncated !== true;
2734
- if (principal !== null && source !== void 0 && !fromClaims) {
2735
- live = true;
3205
+ const read = (from, of) => {
2736
3206
  try {
2737
- memberships = source.membershipsFor(compact({
2738
- id: principal.id,
2739
- kind: principal.kind
3207
+ return from.membershipsFor(compact({
3208
+ id: of.id,
3209
+ kind: of.kind
2740
3210
  }), compact({ tenant: options.tenant }));
2741
3211
  } catch {
2742
3212
  auth.push({
2743
3213
  reason: "source-threw",
2744
3214
  source: "memberships"
2745
3215
  });
2746
- memberships = [];
3216
+ return [];
2747
3217
  }
3218
+ };
3219
+ let stale;
3220
+ if (principal !== null && source !== void 0 && !fromClaims) {
3221
+ live = true;
3222
+ memberships = read(source, principal);
3223
+ } else if (principal?.memberships !== void 0 && fromClaims && source.onStale === "reread" && source.version !== void 0) {
3224
+ const reread = rereadStale(source, principal, principal.memberships, () => read(source, principal), auth);
3225
+ memberships = reread.memberships;
3226
+ stale = reread.stale;
2748
3227
  }
2749
- return {
3228
+ return compact({
2750
3229
  principal,
2751
3230
  context: contextResult,
2752
3231
  actor,
2753
- delegation,
3232
+ delegation: expandOAuthScopes(policy, delegation),
2754
3233
  session,
2755
3234
  expiresAt,
2756
3235
  memberships,
2757
- live
3236
+ live,
3237
+ stale,
3238
+ liveSession: principal === null ? void 0 : liveSession
3239
+ });
3240
+ }
3241
+ function versionQuery(principal) {
3242
+ return compact({
3243
+ id: principal.id,
3244
+ roles: principal.roles,
3245
+ memberships: principal.memberships
3246
+ });
3247
+ }
3248
+ function rereadStale(source, principal, claims, read, auth) {
3249
+ const claimed = principal.authzVersion;
3250
+ const judge = (current) => typeof claimed === "number" && current !== void 0 && claimed >= current ? "fresh" : "reread";
3251
+ const unknown = () => {
3252
+ auth.push({
3253
+ reason: "source-threw",
3254
+ source: "memberships"
3255
+ });
3256
+ return "unknown";
3257
+ };
3258
+ let freshness;
3259
+ try {
3260
+ const current = source.version?.(versionQuery(principal));
3261
+ freshness = isThenable(current) ? Promise.resolve(current).then(judge, unknown) : judge(current);
3262
+ } catch {
3263
+ freshness = unknown();
3264
+ }
3265
+ if (isThenable(freshness)) return {
3266
+ memberships: freshness.then((found) => found === "reread" ? read() : claims),
3267
+ stale: freshness.then((found) => found === "unknown")
3268
+ };
3269
+ return {
3270
+ memberships: freshness === "reread" ? read() : claims,
3271
+ stale: freshness === "unknown"
2758
3272
  };
2759
3273
  }
2760
3274
  function finishSubject(policy, assembled, context, input, options, extra) {
@@ -2766,7 +3280,10 @@ function finishSubject(policy, assembled, context, input, options, extra) {
2766
3280
  expiresAt: assembled.expiresAt
2767
3281
  }));
2768
3282
  const scopes = scopeList(policy.scopes);
2769
- const { roles, memberships } = applyRoleKinds(policy, assembled.principal.roles, normalizeMemberships(input, scopes));
3283
+ const held = heldTenantOf(assembled.principal);
3284
+ const kinds = applyRoleKinds(policy, assembled.principal.roles, normalizeMemberships(input, scopes));
3285
+ const roles = held === void 0 ? kinds.roles : [];
3286
+ const memberships = held === void 0 ? kinds.memberships : outsideHeld(held, options) ? [] : kinds.memberships.filter((membership) => tenantOf(membership, scopes) === held);
2770
3287
  const plans = [.../* @__PURE__ */ new Set([...assembled.principal.plans ?? [], ...extra.plans])];
2771
3288
  const withMemberships = freezeCopy(compact({
2772
3289
  ...assembled.principal,
@@ -2782,11 +3299,14 @@ function finishSubject(policy, assembled, context, input, options, extra) {
2782
3299
  context: freezeCopy(context),
2783
3300
  session: assembled.session,
2784
3301
  expiresAt: assembled.expiresAt,
2785
- stale: extra.stale ? true : void 0
3302
+ stale: extra.stale ? true : void 0,
3303
+ liveSession: assembled.liveSession
2786
3304
  }));
2787
3305
  }
2788
3306
  function activeTenantOf(policy, assembled, memberships, options) {
2789
3307
  if (assembled.principal === null) return;
3308
+ const held = heldTenantOf(assembled.principal);
3309
+ if (held !== void 0) return outsideHeld(held, options) ? void 0 : held;
2790
3310
  return resolveActiveTenant({
2791
3311
  ...assembled.principal,
2792
3312
  memberships
@@ -2807,12 +3327,13 @@ function settle(value, fallback, onError) {
2807
3327
  function staleness(policy, assembled, options, auth) {
2808
3328
  const principal = assembled.principal;
2809
3329
  if ((policy.fresh ?? []).length === 0 || principal === null || assembled.live) return false;
3330
+ if (assembled.stale !== void 0) return assembled.stale;
2810
3331
  const source = options.memberships === void 0 ? void 0 : asMembershipSource(options.memberships);
2811
3332
  const claimed = principal.authzVersion;
2812
3333
  if (source?.version === void 0 || typeof claimed !== "number") return true;
2813
3334
  const compare = (current) => current === void 0 || claimed < current;
2814
3335
  try {
2815
- const current = source.version({ id: principal.id });
3336
+ const current = source.version(versionQuery(principal));
2816
3337
  return isThenable(current) ? settle(Promise.resolve(current).then(compare), true, () => {
2817
3338
  auth.push({
2818
3339
  reason: "source-threw",
@@ -2832,7 +3353,7 @@ function cleanNames(list) {
2832
3353
  }
2833
3354
  function entitlementsOf(assembled, tenant, options, auth) {
2834
3355
  const source = options.entitlements;
2835
- if (source === void 0 || assembled.principal === null) return [];
3356
+ if (source === void 0 || assembled.principal === null || outsideHeld(heldTenantOf(assembled.principal), options)) return [];
2836
3357
  try {
2837
3358
  const found = source.entitlementsFor({ id: assembled.principal.id }, compact({ tenant }));
2838
3359
  return isThenable(found) ? settle(Promise.resolve(found).then(cleanNames), [], () => {
@@ -2921,9 +3442,11 @@ function hostedPolicy(policy, source) {
2921
3442
  }
2922
3443
  function instantiate(codePolicy, subject, options, auth) {
2923
3444
  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);
3445
+ const scopes = scopeList(policy.scopes);
3446
+ const tenants = tenantsOf(subject.principal, scopes);
3447
+ const roleSource = roleSourceFor(options.customRoles, subject, auth);
3448
+ const customRoles = customRolesFor(roleSource, tenants, auth, subject.principal === null ? void 0 : subject.principal.roles ?? [], (tenant) => heldRoleNamesIn(subject.principal, scopes, tenant));
3449
+ const assignable = assignableNamesFor(roleSource, tenants, auth);
2927
3450
  const approvals = approvalPoliciesFor(policy, options.approvalPolicies, tenants, auth);
2928
3451
  const build = (roles, names, approvalPolicies) => buildInstance(policy, subject, compact({
2929
3452
  customRoles: roles,
@@ -2931,7 +3454,7 @@ function instantiate(codePolicy, subject, options, auth) {
2931
3454
  limits: options.limits,
2932
3455
  limitCache: /* @__PURE__ */ new Map(),
2933
3456
  simulated: false,
2934
- roleSource: options.customRoles,
3457
+ roleSource,
2935
3458
  assignable: names,
2936
3459
  queuedAuth: auth,
2937
3460
  queuedErrors: errors,
@@ -2953,4 +3476,4 @@ function createPermDock(policy, user, options = {}) {
2953
3476
  return instantiate(policy, subject, options, auth);
2954
3477
  }
2955
3478
  //#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 };
3479
+ 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 };