permdock 0.6.0 → 0.8.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 (507) hide show
  1. package/README.md +2 -3
  2. package/dist/{2020-C9VN-x8c.js → 2020-CJbO8Bvz.js} +1 -3
  3. package/dist/a2a/index.d.ts +7 -7
  4. package/dist/a2a/index.js +9 -9
  5. package/dist/action-result-CL-QCITA.d.ts +19 -0
  6. package/dist/{adapter-context-CRhqyzCk.js → adapter-context-CIQpQxbE.js} +8 -2
  7. package/dist/{adoption-scan-DCYe60Pi.js → adoption-scan-BLs67P1l.js} +5 -5
  8. package/dist/agent/index.d.ts +3 -3
  9. package/dist/agent/index.js +1 -1
  10. package/dist/ai-sdk/index.d.ts +31 -10
  11. package/dist/ai-sdk/index.js +22 -12
  12. package/dist/{app-data-MuyzVqX8.js → app-data-UNNm07jV.js} +10 -7
  13. package/dist/{approval-header-CJACtHkv.js → approval-header-8QV6MU4H.js} +6 -3
  14. package/dist/{approval-prompt-Dg5LeAcT.js → approval-prompt-B8hQY69x.js} +5 -6
  15. package/dist/{approval-prompt-DvSSPQJX.d.ts → approval-prompt-PYagyQUj.d.ts} +13 -11
  16. package/dist/approvals/index.d.ts +5 -5
  17. package/dist/approvals/index.js +7 -7
  18. package/dist/{approvals-p1PGoVjb.js → approvals-DnIBTuXE.js} +128 -78
  19. package/dist/{approvers-CqPOLD-o.js → approvers-B4r0N_Uz.js} +8 -2
  20. package/dist/{arazzo-41WuzHou.js → arazzo-3RhGfkAK.js} +2 -2
  21. package/dist/{arazzo-CaUVBn7D.js → arazzo-B5-ulWif.js} +6 -6
  22. package/dist/{args-IIolxxKA.js → args-0FrVs5EH.js} +125 -8
  23. package/dist/astro/index.d.ts +30 -12
  24. package/dist/astro/index.js +2 -2
  25. package/dist/{audience-DabClFLQ.js → audience-BAkBDMqn.js} +8 -8
  26. package/dist/{auth-DRLEizbL.js → auth--yPyZNgr.js} +21 -10
  27. package/dist/authzen/index.d.ts +171 -25
  28. package/dist/authzen/index.js +321 -149
  29. package/dist/{automation-BNM3TtDo.d.ts → automation-DqEoWHBc.d.ts} +42 -32
  30. package/dist/{automation-DVjOckjV.js → automation-x3KEUusw.js} +22 -21
  31. package/dist/base-url-BQbddZO5.js +9 -0
  32. package/dist/better-auth/index.d.ts +2 -2
  33. package/dist/better-auth/index.js +13 -7
  34. package/dist/better-supabase/index.d.ts +392 -220
  35. package/dist/better-supabase/index.js +293 -152
  36. package/dist/{bind-DOyrv6zi.d.ts → bind-Cp2hBHzP.d.ts} +27 -12
  37. package/dist/{bind-CAvgUuKs.js → bind-D_sZSVA3.js} +22 -10
  38. package/dist/{body-CH5of8Ne.js → body-CJQwYVa8.js} +3 -3
  39. package/dist/{body-text-QcXok_SI.js → body-text-DQhcmqdY.js} +8 -4
  40. package/dist/{boundary-BM8a_P6E.js → boundary-BV3O1eud.js} +8 -2
  41. package/dist/{boundary-BmHxVH1J.d.ts → boundary-C9ck-3uK.d.ts} +13 -4
  42. package/dist/{budget-NbEtbJC_.js → budget-BELja5A9.js} +6 -3
  43. package/dist/{budget-JqcRqyhK.d.ts → budget-hYsPz_yE.d.ts} +6 -3
  44. package/dist/{build-instance-Bu_eDqYw.js → build-instance-CNWaYJyr.js} +3 -3
  45. package/dist/{canonical-json-BC8_nMTw.js → canonical-json-DwilZ1iB.js} +6 -3
  46. package/dist/{capability-N1K9IpqS.js → capability-BqX94r07.js} +31 -25
  47. package/dist/{capability-Drg-1YhL.d.ts → capability-ChuiONQU.d.ts} +41 -29
  48. package/dist/{casbin-Dyu2gnUD.js → casbin-H0rKxBx2.js} +16 -8
  49. package/dist/{casbin-Cr7RLtA3.js → casbin-cbSbhT_k.js} +2 -2
  50. package/dist/{casl-64gpT-8x.js → casl-C4OINZHI.js} +2 -2
  51. package/dist/{casl-DjEDhTCP.js → casl-CHHoCldP.js} +19 -10
  52. package/dist/catalog/index.d.ts +104 -23
  53. package/dist/catalog/index.js +117 -8
  54. package/dist/{catalog-HD2gf5mY.js → catalog-BFpz2EmT.js} +57 -26
  55. package/dist/{catalog-DZVbrF-z.js → catalog-DCspHDc8.js} +3 -3
  56. package/dist/{catalog-doc-Cdn7-0pQ.js → catalog-doc-DjMKzJp9.js} +53 -16
  57. package/dist/{catalog-fingerprint-CCP0-yQS.js → catalog-fingerprint-e6aCWosn.js} +19 -15
  58. package/dist/catalog-mRQQQhNK.js +1 -0
  59. package/dist/catalog-operations-Dadrl7Z-.js +60 -0
  60. package/dist/{create-CQWIpUG-.js → challenge-DImNcR7B.js} +157 -39
  61. package/dist/chat/index.d.ts +1 -1
  62. package/dist/chat/index.js +13 -10
  63. package/dist/chat-sdk/index.d.ts +44 -26
  64. package/dist/chat-sdk/index.js +17 -17
  65. package/dist/checks-CGkP3Z7A.js +40 -0
  66. package/dist/{claims-map-7oQ3ue4X.js → claims-map-CKkoAAYF.js} +5 -2
  67. package/dist/{claims-map-Ca-QX8C6.d.ts → claims-map-D1Pb5oRq.d.ts} +8 -5
  68. package/dist/claude-agent/index.d.ts +6 -6
  69. package/dist/claude-agent/index.js +6 -3
  70. package/dist/clerk/index.d.ts +11 -7
  71. package/dist/clerk/index.js +6 -6
  72. package/dist/cli/bin.js +4 -4
  73. package/dist/cli/index.d.ts +82 -7
  74. package/dist/cli/index.js +6 -6
  75. package/dist/{clients-C7zLFuWT.d.ts → clients-C_NOET3z.d.ts} +5 -5
  76. package/dist/{clients-DjU5ABMy.js → clients-DYr3U86j.js} +3 -3
  77. package/dist/cloud/index.d.ts +186 -38
  78. package/dist/cloud/index.js +293 -31
  79. package/dist/{cloud-ytQ3uviA.js → cloud-BFlGMaak.js} +5 -6
  80. package/dist/{codemod-5mpSpnUY.js → codemod-DLQ9Zpo4.js} +3 -4
  81. package/dist/{codemod-ua2hHy7w.js → codemod-DWSN2erG.js} +523 -201
  82. package/dist/collect-B5BL3klN.js +1 -0
  83. package/dist/{collect-mLTY4nwb.js → collect-B7ZFAJU3.js} +36 -64
  84. package/dist/{collect-Ci6akVwH.js → collect-Bzv3le0A.js} +6 -6
  85. package/dist/{compare-pfyVnBjE.js → compare-DTfyUunF.js} +2 -2
  86. package/dist/compile/index.d.ts +2 -2
  87. package/dist/compile/index.js +1 -1
  88. package/dist/{compile-1bsAezL0.d.ts → compile-BFzysW3o.d.ts} +36 -12
  89. package/dist/{compile-CMqMx_IN.js → compile-EzEMB-7v.js} +77 -33
  90. package/dist/{completion-BTFrHZhX.js → completion-saoYiRFO.js} +17 -8
  91. package/dist/config-BwPBfN0t.js +108 -0
  92. package/dist/{config-Bj2Ixq3d.js → config-DBeXXmBF.js} +2 -2
  93. package/dist/{config-report-DSpsLpCE.js → config-report-BipSWA6V.js} +9 -6
  94. package/dist/config-shape-ykV0Vm2n.js +134 -0
  95. package/dist/{connection-DjhIJSs2.js → connection-B72dg5Q2.js} +23 -22
  96. package/dist/connector-schema-CYLQNMYq.js +434 -0
  97. package/dist/{context-B_u3_bDJ.js → context-BnIzxTNE.js} +4 -3
  98. package/dist/convex/index.d.ts +13 -7
  99. package/dist/convex/index.js +9 -6
  100. package/dist/core-H-mbhpZc.js +341 -0
  101. package/dist/core-hWIARPDz.d.ts +206 -0
  102. package/dist/{create-CBMmFie-.d.ts → create-BSNrVuxh.d.ts} +87 -34
  103. package/dist/{create-B30OMUOJ.d.ts → create-BVq1lNQE.d.ts} +49 -28
  104. package/dist/{create-D_NmPOdH.d.ts → create-CM1FciYG.d.ts} +37 -29
  105. package/dist/{create-CGm28gyI.js → create-Cq4Uox0f.js} +13 -9
  106. package/dist/{create-DCF2SvoT.js → create-_noSrY1T.js} +15 -21
  107. package/dist/{credential-BcoiejpY.js → credential-CyyyvCsL.js} +42 -39
  108. package/dist/{credentials-DPyHmYz5.js → credentials-CdDxp7jN.js} +6 -6
  109. package/dist/{deciding-columns-Sq12mEA6.js → deciding-columns-NB4wcyBf.js} +13 -2
  110. package/dist/{delegation-j6-rc37o.js → delegation-CDB6_keF.js} +31 -22
  111. package/dist/delivery/index.d.ts +5 -5
  112. package/dist/delivery/index.js +6 -5
  113. package/dist/{delivery-DCAgNyCn.js → delivery-BupfvJIV.js} +81 -20
  114. package/dist/{delivery-event-xX4obB8F.js → delivery-event-JioK5YbS.js} +6 -6
  115. package/dist/denied-text-CHNlVInN.d.ts +22 -0
  116. package/dist/{describe-BybMRY7A.js → describe-Bb4hGK5d.js} +6 -5
  117. package/dist/{describe-B5qgDvEW.d.ts → describe-ByS9LgHK.d.ts} +5 -5
  118. package/dist/{device-BARQ8eYG.js → device-BwbJNmb2.js} +11 -8
  119. package/dist/{diff-CmuSD3vc.js → diff-CN8IYhC_.js} +2 -2
  120. package/dist/{diff-3jbx6Rm4.js → diff-iZWV65nC.js} +23 -14
  121. package/dist/{digest-B3kc_PgQ.d.ts → digest-8Yx2CchM.d.ts} +3 -3
  122. package/dist/{doctor-BdZw53-k.js → doctor-CHD1M9R5.js} +485 -141
  123. package/dist/{doctor-3ZqNHX_G.js → doctor-D3wo-yuz.js} +4 -4
  124. package/dist/drizzle/index.d.ts +33 -19
  125. package/dist/drizzle/index.js +17 -9
  126. package/dist/elysia/index.d.ts +17 -11
  127. package/dist/elysia/index.js +5 -5
  128. package/dist/{errors-BKy0EfhN.js → errors-B6R_lg2a.js} +13 -4
  129. package/dist/{errors-BWXBMaKK.d.ts → errors-CHwoRRpB.d.ts} +4 -1
  130. package/dist/{errors-CWTY6uih.js → errors-Vpu6rKHl.js} +14 -10
  131. package/dist/{errors-DH5QEGXh.d.ts → errors-zi_4T-rx.d.ts} +22 -6
  132. package/dist/{evaluate-CNClBvcj.js → evaluate-Dd8Ta0Ie.js} +185 -80
  133. package/dist/{evaluations-beAooV0n.js → evaluations-IBceAeKT.js} +41 -20
  134. package/dist/{evaluations-C_1vdSZc.d.ts → evaluations-z8dQFDaN.d.ts} +12 -6
  135. package/dist/eve/index.d.ts +9 -6
  136. package/dist/eve/index.js +4 -4
  137. package/dist/{event-time-CsegxXxB.js → event-time-BGy48400.js} +8 -4
  138. package/dist/{expiry-D4_YKRKD.js → expiry-6XByK0dm.js} +8 -5
  139. package/dist/express/index.d.ts +9 -6
  140. package/dist/express/index.js +9 -6
  141. package/dist/fastify/index.d.ts +16 -10
  142. package/dist/fastify/index.js +8 -5
  143. package/dist/{fetch-adapter-Dw0MTS3D.d.ts → fetch-adapter-C1DCvpqV.d.ts} +9 -6
  144. package/dist/{fetch-adapter-DD_gfmrC.js → fetch-adapter-JL43jD3E.js} +14 -11
  145. package/dist/fields-D0vUI6dq.js +498 -0
  146. package/dist/{files-C5ZI27eV.js → files-B3AA05f1.js} +21 -5
  147. package/dist/{files-D_JIzUrP.js → files-BSjrtIHw.js} +11 -4
  148. package/dist/{fixtures-B7XrLeY7.js → fixtures-A1_sp_tR.js} +15 -6
  149. package/dist/{flags-C3DktBDY.js → flags-DXjoTVbS.js} +2 -2
  150. package/dist/{freeze-CrU8nQeX.js → freeze-jjan0lCv.js} +4 -1
  151. package/dist/{from-snapshot-CTCMsFZk.js → from-snapshot-DCakWRsr.js} +26 -16
  152. package/dist/gen-B5kvbAoZ.js +58 -0
  153. package/dist/gen-DWCYcMQM.js +827 -0
  154. package/dist/generate-CRVV5V7l.js +165 -0
  155. package/dist/generate-CezbCaTI.js +275 -0
  156. package/dist/{grantee-hxrUsbpM.js → grantee-Dke-9rd1.js} +4 -4
  157. package/dist/{grantee-conditions-CcXf8hMM.js → grantee-conditions-BrYPe-w0.js} +12 -12
  158. package/dist/{grantee-match-DbENBHLB.js → grantee-match-7RZZvoT_.js} +12 -6
  159. package/dist/{graph-sql-B-dsAT6j.d.ts → graph-sql-BwFC4Qjs.d.ts} +3 -3
  160. package/dist/h3/index.d.ts +10 -7
  161. package/dist/h3/index.js +3 -3
  162. package/dist/headers-B9jQxjtW.d.ts +10 -0
  163. package/dist/{helpers-CBYVMjwh.js → helpers-DJicaG6f.js} +40 -28
  164. package/dist/hono/index.d.ts +16 -10
  165. package/dist/hono/index.js +5 -5
  166. package/dist/{hosted-BPk51-kX.d.ts → hosted-Cmtxogd0.d.ts} +44 -20
  167. package/dist/{hosted-B46-5LYE.js → hosted-D0IzESu_.js} +68 -25
  168. package/dist/{http-ujKm0JXE.js → http-DH94eh6f.js} +4 -4
  169. package/dist/{http-BBoKefeS.js → http-DKLXrFGk.js} +13 -7
  170. package/dist/{http-DXJ37RDA.js → http-DOL33cO0.js} +11 -8
  171. package/dist/{import-wyqXUyjm.js → import-CST57XLp.js} +64 -51
  172. package/dist/{import-CNV7I2Dw.js → import-_YqhhwvX.js} +6 -6
  173. package/dist/{index-B8YjkEZ4.d.ts → index-B8s204GL.d.ts} +64 -52
  174. package/dist/{index-Ch8LpNDp.d.ts → index-BT5-1VsX.d.ts} +64 -25
  175. package/dist/{index-DuWxc5dt.d.ts → index-BW_0MZOS.d.ts} +388 -149
  176. package/dist/index-BhxEUY9J.d.ts +233 -0
  177. package/dist/index-Bv5iXPqU.d.ts +210 -0
  178. package/dist/{index-C-6Letxg.d.ts → index-_9UiQIkt.d.ts} +164 -77
  179. package/dist/index.d.ts +22 -21
  180. package/dist/index.js +35 -33
  181. package/dist/{init-GG6Jh8HD.js → init-Dz_PsHdv.js} +5 -5
  182. package/dist/{init-DeMYsnYJ.js → init-cE3A8aXq.js} +10 -10
  183. package/dist/{instance-options-BsVpyGIk.d.ts → instance-options-B0KXqTrJ.d.ts} +4 -4
  184. package/dist/{instance-snapshot-BOEhcqIF.js → instance-snapshot--JWiTrvO.js} +81 -56
  185. package/dist/{instant-BluqDvyS.js → instant-DK3Iz83e.js} +2 -2
  186. package/dist/{internals-Dk8A8fX3.js → internals-B3IgHrvo.js} +6 -3
  187. package/dist/{json-schema-B-vAwlde.js → json-schema-BPGa-RjQ.js} +9 -3
  188. package/dist/{json-schema-CN_iLFH2.d.ts → json-schema-BfXgnRNT.d.ts} +4 -4
  189. package/dist/jwt/index.d.ts +125 -17
  190. package/dist/jwt/index.js +157 -16
  191. package/dist/{kernel-D0NVb1cK.js → kernel-C-fYydV2.js} +151 -16
  192. package/dist/kysely/index.d.ts +49 -33
  193. package/dist/kysely/index.js +9 -7
  194. package/dist/{live-options-BaAFCIiu.js → live-options-BBIT68le.js} +2 -2
  195. package/dist/{load-DFS_ry0W.js → load-DjSU5KXO.js} +6 -6
  196. package/dist/load-Dm0pSYH3.js +1 -0
  197. package/dist/{login-Dfe5RlYk.js → login-D-8tMqzo.js} +4 -4
  198. package/dist/{login-imhuwvn3.js → login-DplQ0s84.js} +18 -15
  199. package/dist/{manifest-dvL2XEe1.js → manifest-Cgiq5oqc.js} +67 -37
  200. package/dist/{manifest-mx5ghSFM.d.ts → manifest-CzzWtnAb.d.ts} +144 -28
  201. package/dist/{manifest-schema-C0kK_8V0.js → manifest-schema-2r37XRhD.js} +80 -9
  202. package/dist/{manifest-C8IzW3Do.js → manifest-wg8WkkpD.js} +11 -7
  203. package/dist/{map-claims-D3oRmTk0.js → map-claims-BNWCJtmP.js} +27 -18
  204. package/dist/{map-error-Dr70oA_3.js → map-error-pasIGqMT.js} +7 -7
  205. package/dist/{markers-C6joDI3o.js → markers-BeHZ4x9Q.js} +20 -5
  206. package/dist/mcp/index.d.ts +154 -47
  207. package/dist/mcp/index.js +200 -41
  208. package/dist/{migrate-CIq4-E9F.js → migrate-B7oP_sQU.js} +102 -31
  209. package/dist/module-values-BJTBdo6y.js +107 -0
  210. package/dist/nest/index.d.ts +23 -13
  211. package/dist/nest/index.js +18 -11
  212. package/dist/next/client.d.ts +5 -2
  213. package/dist/next/client.js +1 -1
  214. package/dist/next/index.d.ts +72 -26
  215. package/dist/next/index.js +110 -6
  216. package/dist/next/plugin.d.ts +1 -1
  217. package/dist/next/plugin.js +4 -3
  218. package/dist/node/index.d.ts +5 -5
  219. package/dist/node/index.js +3 -3
  220. package/dist/{ocsf-BFp-PZZA.js → ocsf-TcjRBgIs.js} +15 -5
  221. package/dist/openai/index.d.ts +13 -7
  222. package/dist/openai/index.js +3 -3
  223. package/dist/openapi/index.d.ts +61 -30
  224. package/dist/openapi/index.js +2 -2
  225. package/dist/{openapi-B7HZ0f-r.js → openapi-BOsx0xn9.js} +34 -24
  226. package/dist/{openapi-C4dDTVEd.js → openapi-CBZUhA3l.js} +4 -5
  227. package/dist/{openapi-DD1SV-5-.js → openapi-VeHHU2l9.js} +23 -14
  228. package/dist/{operation-findings-DgATDpvh.js → operation-findings-CXtFzlKJ.js} +3 -3
  229. package/dist/{operations-X0VM7F3m.d.ts → operations-Ci_rT6Uo.d.ts} +10 -7
  230. package/dist/orpc/index.d.ts +68 -14
  231. package/dist/orpc/index.js +62 -13
  232. package/dist/otel/index.d.ts +7 -4
  233. package/dist/otel/index.js +8 -5
  234. package/dist/output-files-BHOIlrfD.js +1 -0
  235. package/dist/{output-files-DhRBBE4n.js → output-files-BIQ_9pIs.js} +29 -17
  236. package/dist/{output-1im__-08.js → output-tkju6U7R.js} +15 -7
  237. package/dist/ownership-B4L59T9u.js +1054 -0
  238. package/dist/parse-1_ZheTW5.js +151 -0
  239. package/dist/parse-local-manifest-BQhTlLQS.d.ts +10 -0
  240. package/dist/parse-local-manifest-DEiCF4ah.js +69 -0
  241. package/dist/{parse-request-D_tUXG6M.js → parse-request-CwCZpyjV.js} +7 -8
  242. package/dist/{paths-DXSHAMIa.js → paths-CUvURzHp.js} +4 -1
  243. package/dist/pdp/index.d.ts +9 -6
  244. package/dist/pdp/index.js +23 -12
  245. package/dist/{peer-CRvC7Hg1.js → peer-C9LvBCcb.js} +1 -1
  246. package/dist/peer-Q0fuZ6hx.js +982 -0
  247. package/dist/{permdock-SV8ZINYT.d.ts → permdock-B6Sf4T_S.d.ts} +270 -117
  248. package/dist/{permdock-BrAscV28.js → permdock-L-_I_3zP.js} +226 -74
  249. package/dist/{permission-registry-Br4jSNVi.js → permission-registry-B8tYxPIt.js} +24 -11
  250. package/dist/{vocabulary-CN8H_9UF.d.ts → permission-types-CAY-GU7e.d.ts} +130 -104
  251. package/dist/{permissions-u_KHg-sN.d.ts → permissions-Bx6UI8n1.d.ts} +6 -2
  252. package/dist/{permissions-B-wCP5ZC.js → permissions-CVXmRILq.js} +3 -3
  253. package/dist/{permissions-BPxlwVhN.js → permissions-mDcTWoBs.js} +8 -5
  254. package/dist/pg-DQTqRrou.js +60 -0
  255. package/dist/{pins-8ILTftC9.js → pins-nqarl3oE.js} +4 -1
  256. package/dist/pkce-CnmmYqUZ.js +21 -0
  257. package/dist/{policy-77FSAq_V.d.ts → policy-Ba4bDhaj.d.ts} +5 -5
  258. package/dist/{policy-DbM5YYtV.js → policy-CLQ8__GF.js} +49 -20
  259. package/dist/{policy-from-catalog-C2gMa2Dj.js → policy-from-catalog-CJgOm6aq.js} +28 -25
  260. package/dist/{policy-module-CTlwMvLI.js → policy-module-xym9fnSq.js} +12 -6
  261. package/dist/{policy-types-n0uRTsW8.d.ts → policy-types-CCiDRZBH.d.ts} +802 -334
  262. package/dist/postgres-store-DcyrPS-r.js +1028 -0
  263. package/dist/{postgrest-DAjUkF9w.js → postgrest-D4nuPXIQ.js} +88 -36
  264. package/dist/{postgrest-CEgRbefE.d.ts → postgrest-DicQyqm-.d.ts} +60 -30
  265. package/dist/powersync-DHNQX06W.js +1117 -0
  266. package/dist/{powersync-DISt6SUe.js → powersync-DXqGuq9A.js} +3 -3
  267. package/dist/powersync-Djf-zCpB.js +1 -0
  268. package/dist/preset/index.d.ts +38 -28
  269. package/dist/preset/index.js +1 -1
  270. package/dist/{preset-DZdU0tne.js → preset-BjLzeFpp.js} +58 -34
  271. package/dist/{principal-source-pHneukrx.js → principal-source-C6N60GtM.js} +10 -10
  272. package/dist/prisma/index.d.ts +69 -41
  273. package/dist/prisma/index.js +21 -17
  274. package/dist/{problem-CTviyH5L.js → problem-BVLHFK33.js} +41 -21
  275. package/dist/{problem-details-cn5X30gZ.js → problem-details-CLDgX1KV.js} +13 -4
  276. package/dist/{problem-types-CJ2325SZ.js → problem-types-DSsRfONQ.js} +4 -1
  277. package/dist/prompt-BYc6drYW.js +938 -0
  278. package/dist/protected-action-BE7N_Luo.d.ts +58 -0
  279. package/dist/protected-action-DUYqb-Ma.js +80 -0
  280. package/dist/{protected-BlF0F6wL.js → protected-action-QEA8arRB.js} +48 -14
  281. package/dist/{provider-B1al-AZz.js → provider-C55OGXJ7.js} +5 -4
  282. package/dist/{provider-DILL9oPK.d.ts → provider-DXfq5c2d.d.ts} +1 -1
  283. package/dist/{provider-store-DM6DNcta.js → provider-store-CaTv00tr.js} +13 -9
  284. package/dist/pull-B65ndFNZ.js +1 -0
  285. package/dist/{pull-jEwbf80J.js → pull-CwpGaQSi.js} +12 -11
  286. package/dist/{push-BD4f15co.js → push-DgssgzkK.js} +62 -16
  287. package/dist/{pushed-policy-DCW91KQU.d.ts → pushed-policy-D1Eoi6NM.d.ts} +12 -6
  288. package/dist/{pushed-policy-CqtxkV4G.js → pushed-policy-ITwYKvvH.js} +10 -7
  289. package/dist/react/index.d.ts +9 -8
  290. package/dist/react/index.js +7 -6
  291. package/dist/react/provider-client.d.ts +1 -1
  292. package/dist/react/provider-client.js +1 -1
  293. package/dist/react-native/index.d.ts +104 -51
  294. package/dist/react-native/index.js +394 -67
  295. package/dist/{refs-K6eWUJ0H.js → refs-C8BKxc_e.js} +17 -3
  296. package/dist/{relation-walk-3OyDBF0S.js → relation-walk-DK_gkgL7.js} +12 -9
  297. package/dist/{relations-BnCWnJGI.js → relations-BxvRvDHK.js} +3 -3
  298. package/dist/{replay-Du1tJjG7.js → replay-BU09ad4L.js} +1 -1
  299. package/dist/{replay-Bkm-Ne4l.d.ts → replay-DNftIb_R.d.ts} +1 -1
  300. package/dist/{request-context-C81Pip9n.js → request-context-C25ilqiJ.js} +8 -8
  301. package/dist/{request-schema-CMreVuFM.js → request-schema-DhX7UbfQ.js} +4 -1
  302. package/dist/resend/index.d.ts +34 -28
  303. package/dist/resend/index.js +29 -29
  304. package/dist/resolve-names-CF7xXLSG.js +201 -0
  305. package/dist/{resource-relations-D_jw0BFx.js → resource-relations-8-pCScBO.js} +18 -9
  306. package/dist/{retire-CALjIQ7e.js → retire-B2xffQgY.js} +15 -6
  307. package/dist/{revocations-R07lFBnX.d.ts → revocations-BaUXHM1N.d.ts} +6 -3
  308. package/dist/{rls-DTvYd-lI.js → rls-CNEH2_BG.js} +743 -624
  309. package/dist/{rls-BAqP4q66.js → rls-Ca8Jz9b5.js} +41 -10
  310. package/dist/{role-builder-uWQZ4v84.js → role-builder-BK2-T6oD.js} +41 -20
  311. package/dist/rolldown-runtime-C3I60J0A.js +4 -0
  312. package/dist/{row-conditions-h2HythXQ.js → row-conditions-BAGHOmVq.js} +4 -3
  313. package/dist/{rpc-VSl5ylaQ.js → rpc-fNqAIqQk.js} +8 -5
  314. package/dist/{run-CUKuz0A8.js → run-Bz26OTfO.js} +26 -23
  315. package/dist/run-D-JE8QWb.js +1 -0
  316. package/dist/{run-ticket-DocwaTKn.d.ts → run-ticket-DbWI9f99.d.ts} +33 -13
  317. package/dist/{run-ticket-sxFI6LAk.js → run-ticket-Dv7aF7Dk.js} +29 -8
  318. package/dist/{runtime-D3VTAiCA.js → runtime-BGnrOkVH.js} +5 -2
  319. package/dist/{saas-BCXRuXca.js → saas-iWnUdwCt.js} +42 -29
  320. package/dist/{scan-qvOe0hS3.js → scan-DCj1KQfn.js} +1 -1
  321. package/dist/{schema-B4v0Qrac.js → schema-BOJjqlWL.js} +3 -2
  322. package/dist/{schema-yLuBWWsh.js → schema-Dst9I9Gs.js} +9 -5
  323. package/dist/scim/index.d.ts +3 -2
  324. package/dist/scim/index.js +3 -1
  325. package/dist/scim-D8YupzX1.js +44 -0
  326. package/dist/{scim-ClHtpyV3.js → scim-jJBJ7g1Z.js} +35 -551
  327. package/dist/{scopes-CUPSjo9q.js → scopes-0Up_UA2t.js} +42 -24
  328. package/dist/server/index.d.ts +12 -12
  329. package/dist/server/index.js +13 -13
  330. package/dist/{server-C3WXAHZj.js → server-3cVlTLde.js} +31 -31
  331. package/dist/{server-types-CKQiCnON.d.ts → server-types-CIlpSA2G.d.ts} +81 -46
  332. package/dist/{sha256-D9ihqmvd.js → sha256-C-4WXKIu.js} +4 -3
  333. package/dist/{shared-BAK8t4Uo.js → shared-Afctg7B0.js} +100 -61
  334. package/dist/{shared-DvvFzYXe.js → shared-l0oGGetg.js} +9 -6
  335. package/dist/{signals-D3NcTKUS.js → signals-B1vHR3fN.js} +2 -2
  336. package/dist/{sink-BEZSlMS_.js → sink-D9_vh372.js} +32 -21
  337. package/dist/{sink-4-eQIRWf.d.ts → sink-Dvi7YZG_.d.ts} +44 -20
  338. package/dist/{skills-B6lVdD_W.js → skills-Bjj7BNEQ.js} +16 -12
  339. package/dist/{skills-CTYwkl44.js → skills-OuoMR49L.js} +3 -3
  340. package/dist/{snapshot-B7DkYg6g.js → snapshot-CXlzi69u.js} +21 -18
  341. package/dist/{snapshot-cache-PXI-vGpY.d.ts → snapshot-cache-BAXZ0KOp.d.ts} +21 -12
  342. package/dist/{snapshot-cache-DZHQ0KF6.js → snapshot-cache-Bx_4xC_6.js} +14 -11
  343. package/dist/solid/index.d.ts +55 -15
  344. package/dist/solid/index.js +46 -12
  345. package/dist/solid-start/index.d.ts +30 -12
  346. package/dist/solid-start/index.js +3 -3
  347. package/dist/sources-B1Os_YUu.d.ts +312 -0
  348. package/dist/{sources-vs5dGOsS.js → sources-CMAM-2A3.js} +156 -51
  349. package/dist/sources-qoIJQrJv.js +11 -0
  350. package/dist/{sql-B5T1IL83.js → sql-0WjxIp9g.js} +32 -7
  351. package/dist/sql-B80DFdpG.d.ts +10 -0
  352. package/dist/{src-Cu2m75uo.js → src-DZ0l9UAg.js} +305 -123
  353. package/dist/ssf/index.d.ts +4 -4
  354. package/dist/ssf/index.js +665 -224
  355. package/dist/{standard-webhooks-rbD7LS6h.d.ts → standard-webhooks-C-qX4mJW.d.ts} +7 -7
  356. package/dist/{standard-webhooks-DgFFD-Ga.js → standard-webhooks-D67a3nzk.js} +5 -5
  357. package/dist/{store-BDutJkDL.js → store-RNFvaJJv.js} +43 -29
  358. package/dist/{store-options-u0ycEe8s.js → store-options-Dil3lraY.js} +105 -15
  359. package/dist/stores-C0Kta5Ft.d.ts +59 -0
  360. package/dist/{stores-CGhM8BN_.js → stores-DxZWDXCs.js} +42 -5
  361. package/dist/{stream-CvdUS9V1.js → stream-CDvF0sJm.js} +5 -5
  362. package/dist/{stream-CeCMJmBB.d.ts → stream-ltuLdtTG.d.ts} +4 -3
  363. package/dist/{style-DiKWnAvn.js → style-DblOtSga.js} +1 -1
  364. package/dist/{style-Uao76wO7.js → style-Ds11Hmk4.js} +3 -1
  365. package/dist/{subject-settings-CkEwrw3W.js → subject-settings-8sH5HlII.js} +8 -4
  366. package/dist/{row-check-Dt35czLy.d.ts → subject-settings-vNW8qmUH.d.ts} +30 -18
  367. package/dist/supabase/index.d.ts +48 -37
  368. package/dist/supabase/index.js +6 -6
  369. package/dist/supabase/middleware.d.ts +19 -13
  370. package/dist/supabase/middleware.js +7 -7
  371. package/dist/{supabase-x5JtR2tc.js → supabase-BL1qJzzY.js} +82 -36
  372. package/dist/{supabase-By_frk3U.js → supabase-D39cuGPt.js} +3 -3
  373. package/dist/{supabase-hook-BMoEhZir.js → supabase-hook-B-yXnzUl.js} +1206 -286
  374. package/dist/supabase-hook-H-MtE9Wd.js +1 -0
  375. package/dist/supautils-BU-pAEpf.js +808 -0
  376. package/dist/{support-session-C6xJQYut.js → support-session-BsmPCbz8.js} +22 -22
  377. package/dist/svelte/PermissionBoundary.svelte +2 -6
  378. package/dist/svelte/Protected.svelte +2 -8
  379. package/dist/svelte/index.d.ts +9 -8
  380. package/dist/svelte/index.js +10 -9
  381. package/dist/svelte/runtime.d.ts +4 -4
  382. package/dist/svelte/runtime.js +4 -4
  383. package/dist/svelte/source.d.ts +9 -8
  384. package/dist/svelte/source.js +6 -5
  385. package/dist/sveltekit/index.d.ts +30 -12
  386. package/dist/sveltekit/index.js +2 -2
  387. package/dist/{ownership-DqaJNJPn.js → tables-DeMNEy6V.js} +1025 -1085
  388. package/dist/tanstack-start/index.d.ts +22 -10
  389. package/dist/tanstack-start/index.js +2 -2
  390. package/dist/{target-Bw37TFNA.js → target-qdEAnnhU.js} +6 -6
  391. package/dist/{tenancy-DN2QAx9z.js → tenancy-5d1zVBvK.js} +36 -21
  392. package/dist/terminal/index.d.ts +23 -14
  393. package/dist/terminal/index.js +17 -20
  394. package/dist/testing/core.d.ts +2 -0
  395. package/dist/testing/core.js +1 -0
  396. package/dist/testing/fixtures/index.d.ts +15 -0
  397. package/dist/testing/fixtures/index.js +831 -0
  398. package/dist/testing/index.d.ts +235 -285
  399. package/dist/testing/index.js +502 -927
  400. package/dist/testing/saas/index.d.ts +43 -27
  401. package/dist/testing/saas/index.js +2 -2
  402. package/dist/testing/saas/permissions.d.ts +1 -1
  403. package/dist/testing/saas/permissions.js +1 -1
  404. package/dist/text-diff-BtfSmdUo.js +444 -0
  405. package/dist/{thenable-yD7SabqG.js → thenable-BNSM1U2R.js} +12 -3
  406. package/dist/{time-DkWjdL98.js → time-nR1JesoH.js} +32 -8
  407. package/dist/{token-BLZcjQgt.js → token-DC9EQiet.js} +15 -13
  408. package/dist/{tool-refusal-CMLN5xhn.js → tool-refusal-Qpjs_W6M.js} +5 -2
  409. package/dist/trpc/index.d.ts +11 -8
  410. package/dist/trpc/index.js +15 -9
  411. package/dist/type-helpers-BIPIDa6a.d.ts +50 -0
  412. package/dist/{types-8RnwUAvE.d.ts → types-AMJ5qoU_.d.ts} +49 -30
  413. package/dist/{types-C_IiEs64.d.ts → types-B2mlGuhW.d.ts} +14 -11
  414. package/dist/{types-_WhUP7ud.d.ts → types-B4uqKDnp.d.ts} +25 -12
  415. package/dist/{types-CX7AvRqP.d.ts → types-BJk0BKRX.d.ts} +24 -14
  416. package/dist/types-B_d_g7T3.d.ts +262 -0
  417. package/dist/{types-BdtSDbBO.d.ts → types-B_tiYvk8.d.ts} +59 -32
  418. package/dist/{types-DqzsnlSA.js → types-CcUXciSj.js} +17 -5
  419. package/dist/{types-Dfv0aTER.d.ts → types-DC1Loeh3.d.ts} +449 -168
  420. package/dist/{types-lUlG3TVd.d.ts → types-DHKPcqzr.d.ts} +2 -2
  421. package/dist/{types-C791HFu9.d.ts → types-DjuMBNF9.d.ts} +68 -32
  422. package/dist/{types-C863OQiR.d.ts → types-HzqOtvsJ.d.ts} +23 -12
  423. package/dist/{types-Druu9wLM.d.ts → types-ZzRGoViD.d.ts} +77 -24
  424. package/dist/{types-BzzaHxt3.d.ts → types-ah9NlU6j.d.ts} +137 -48
  425. package/dist/{types-CVZSNocA.d.ts → types-djVzLlIV.d.ts} +31 -14
  426. package/dist/{types-BtHtcKMr.d.ts → types-yUuPR5Hy.d.ts} +47 -24
  427. package/dist/unplugin/index.d.ts +1 -1
  428. package/dist/unplugin/index.js +2 -2
  429. package/dist/usage-CYeXek0p.js +1 -0
  430. package/dist/{usage-SveTRTyr.js → usage-CxEzS4ey.js} +50 -26
  431. package/dist/{usage-CRVl9V3t.js → usage-ab0PdusX.js} +2 -2
  432. package/dist/{validation-error-ZfUHbKvf.js → validation-error-CYg2kJEw.js} +4 -1
  433. package/dist/{validation-error-DHUrAkku.d.ts → validation-error-YGg2mXyH.d.ts} +5 -2
  434. package/dist/{validity-CZ7-y0Cj.js → validity-Buc7w8E9.js} +13 -10
  435. package/dist/{verifier-DHuh-okk.js → verifier-DUV6pLqZ.js} +10 -4
  436. package/dist/{verify-BaAKx7t6.js → verify-DZeqoWu5.js} +417 -489
  437. package/dist/version-DMQ0dnN5.js +48 -0
  438. package/dist/version-DizhmD72.js +1 -0
  439. package/dist/{views-DTIIO-oQ.js → views-CD8wuL4Z.js} +19 -6
  440. package/dist/vocabulary-DEJ5j4mR.d.ts +70 -0
  441. package/dist/vue/index.d.ts +71 -21
  442. package/dist/vue/index.js +50 -13
  443. package/dist/{waiter-B986w1oK.d.ts → waiter-Bu9zAlE0.d.ts} +13 -10
  444. package/dist/{waiter-BGUtGYoU.js → waiter-aBLM63_f.js} +1 -1
  445. package/dist/{watch-vea54PRw.js → watch-Cx_WciLE.js} +8 -8
  446. package/dist/watch-DB42U1ib.js +1 -0
  447. package/dist/webmcp/index.d.ts +11 -11
  448. package/dist/webmcp/index.js +8 -5
  449. package/dist/{where-scope-C9vfEphl.js → where-scope-DlCdCM_8.js} +56 -30
  450. package/dist/{who-can-DWBAcaCQ.js → who-can-rI7dCR9Q.js} +23 -20
  451. package/dist/workflow/index.d.ts +3 -3
  452. package/dist/workflow/index.js +13 -13
  453. package/dist/workflow-sdk/index.d.ts +19 -13
  454. package/dist/workflow-sdk/index.js +6 -6
  455. package/dist/{wrap-gX61EtpB.js → wrap-B2wziJI_.js} +11 -5
  456. package/dist/{wrap-C5jC6sKI.d.ts → wrap-DlJE3gS_.d.ts} +8 -5
  457. package/package.json +25 -14
  458. package/schemas/catalog-v1.json +3 -0
  459. package/schemas/cli-result-v1.json +1 -0
  460. package/schemas/cloud-api-v1.json +152 -7
  461. package/schemas/connector-v1.json +8 -0
  462. package/schemas/supabase-claims-v1.json +5 -0
  463. package/schemas/supabase-manifest-v1.json +308 -75
  464. package/skills/permdock/SKILL.md +1 -0
  465. package/skills/permdock-agents/references/runtimes.md +3 -1
  466. package/skills/permdock-approvals/references/stores-and-resume.md +2 -1
  467. package/skills/permdock-credentials/references/tokens-keys-links.md +8 -6
  468. package/skills/permdock-data/references/queries-and-rls.md +6 -2
  469. package/skills/permdock-tenancy/SKILL.md +1 -1
  470. package/skills/permdock-tenancy/references/ownership-and-custom-roles.md +2 -1
  471. package/skills/permdock-wire/references/adapters.md +28 -22
  472. package/skills/permdock-wire/references/policy-options.md +1 -0
  473. package/dist/catalog-BoWglDPI.js +0 -1
  474. package/dist/collect-D7eQDffE.js +0 -1
  475. package/dist/config-bs_kZeYq.js +0 -162
  476. package/dist/denied-text-CiFQNuMV.d.ts +0 -19
  477. package/dist/fields-OYG_JR78.js +0 -203
  478. package/dist/generate-CNU1pIOo.js +0 -171
  479. package/dist/headers-BIjfMaoC.js +0 -8
  480. package/dist/headers-CIbDTUTK.d.ts +0 -7
  481. package/dist/index-Cp_ilKi1.d.ts +0 -91
  482. package/dist/index-CueFnxQc.d.ts +0 -110
  483. package/dist/load-BxEtwlBS.js +0 -1
  484. package/dist/output-files-B3IZJksC.js +0 -1
  485. package/dist/package-root-Dk_ETdkp.js +0 -31
  486. package/dist/parse-Cxq0WxrA.js +0 -53
  487. package/dist/parse-local-manifest-5ODwqu8b.d.ts +0 -10
  488. package/dist/parse-local-manifest-CVSs6J9I.js +0 -34
  489. package/dist/peer-C8swJ2GH.js +0 -50
  490. package/dist/policies-DP2nqRoF.js +0 -295
  491. package/dist/powersync-BkWWCS1K.js +0 -538
  492. package/dist/powersync-MMJ2mI2q.js +0 -1
  493. package/dist/problem-details-B45V5d2b.d.ts +0 -7
  494. package/dist/prompt-DfZLJLIn.js +0 -32
  495. package/dist/protected-RFGpakN9.d.ts +0 -38
  496. package/dist/pull-CvpINcMK.js +0 -1
  497. package/dist/run-DDwUaJDU.js +0 -1
  498. package/dist/sources-Xc4zkszI.d.ts +0 -190
  499. package/dist/stores-BUGNbJTt.d.ts +0 -30
  500. package/dist/supabase-hook-CCw5m35C.js +0 -1
  501. package/dist/supautils-B6HYL9eL.js +0 -91
  502. package/dist/text-diff-BQcUapOb.js +0 -15
  503. package/dist/types-B3_ooZgZ.d.ts +0 -172
  504. package/dist/usage-CZpSQP6L.js +0 -1
  505. package/dist/version-CEbQ5UeM.js +0 -18
  506. package/dist/version-DXkYUISx.js +0 -1
  507. package/dist/watch--ovlPOc0.js +0 -1
@@ -1,312 +1,70 @@
1
- import { t as require__2020 } from "../2020-C9VN-x8c.js";
2
1
  import { r as isRecord } from "../compact-CAf557ZB.js";
3
- import { n as freezeDeep } from "../freeze-CrU8nQeX.js";
4
- import { r as principal } from "../refs-K6eWUJ0H.js";
5
- import { n as role, r as allow } from "../role-builder-uWQZ4v84.js";
6
- import { E as memoryCustomRoleSource, _ as limits, s as snapshotFor, v as memoryLimitStore, x as approvalPoliciesFor } from "../src-Cu2m75uo.js";
2
+ import { n as freezeDeep } from "../freeze-jjan0lCv.js";
3
+ import { i as principal } from "../refs-C8BKxc_e.js";
4
+ import { n as role, r as allow } from "../role-builder-BK2-T6oD.js";
5
+ import { A as memoryCustomRoleSource, S as memoryLimitStore, T as approvalPoliciesFor, d as snapshotFor, x as limits } from "../src-DZ0l9UAg.js";
7
6
  import { t as memoryRevocationFeed } from "../revocations-Bhr0aMIC.js";
8
- import { n as loadPrincipalByIdentity, t as loadPrincipal } from "../principal-source-pHneukrx.js";
9
- import { _ as resource, c as listPermissions } from "../permission-registry-Br4jSNVi.js";
10
- import { d as isoNow, f as nowMillis, p as nowSeconds, u as isoAt } from "../time-DkWjdL98.js";
11
- import { r as parseAutomation } from "../automation-DVjOckjV.js";
12
- import { a as normalizeMemberships, d as scopeList } from "../scopes-CUPSjo9q.js";
13
- import { u as evaluateCondition } from "../delegation-j6-rc37o.js";
14
- import { a as authenticated } from "../grantee-hxrUsbpM.js";
15
- import { n as definePolicy } from "../policy-DbM5YYtV.js";
16
- import { t as byCodePoint } from "../compare-pfyVnBjE.js";
17
- import { a as mergeHostedGrants, o as parsePolicyDocument } from "../hosted-B46-5LYE.js";
18
- import { t as definePermissions } from "../permissions-B-wCP5ZC.js";
19
- import { C as validateCustomRole, y as membershipsClaim } from "../evaluate-CNClBvcj.js";
20
- import { n as parseSnapshot } from "../snapshot-B7DkYg6g.js";
21
- import { n as fromSnapshot } from "../from-snapshot-CTCMsFZk.js";
22
- import { t as getPermDock } from "../permdock-BrAscV28.js";
23
- import { t as APPROVAL_HEADER } from "../types-DqzsnlSA.js";
24
- import { i as memoryApprovalStore } from "../store-BDutJkDL.js";
25
- import { t as catalogFingerprint } from "../catalog-fingerprint-CCP0-yQS.js";
26
- import { s as parseCredential } from "../credential-BcoiejpY.js";
27
- import { s as problemFromDecision } from "../problem-CTviyH5L.js";
28
- import { o as resolveApproval } from "../helpers-CBYVMjwh.js";
29
- import { t as APPROVAL_REQUEST_SCHEMA } from "../request-schema-CMreVuFM.js";
30
- import { t as catalogSchema } from "../schema-B4v0Qrac.js";
31
- import { c as parseApiKey } from "../server-C3WXAHZj.js";
32
- import { n as discoverAuthorizationServer } from "../device-BARQ8eYG.js";
33
- import { T as sha256Hex, n as directoryMembershipSource, r as scimHandler } from "../scim-ClHtpyV3.js";
34
- import { a as approverHeaders, r as cloudEndpoints, t as cloud } from "../create-CQWIpUG-.js";
35
- import { t as supabaseMembershipsBudget } from "../budget-NbEtbJC_.js";
36
- import { c as quoteSqlTable, n as defaultSqlCommand, o as quoteSqlIdent, t as SQL_IDENT } from "../sql-B5T1IL83.js";
37
- import { t as supabaseManifestSchema } from "../manifest-schema-C0kK_8V0.js";
38
- import { t as PUSHED_POLICY_SCHEMA } from "../pushed-policy-CqtxkV4G.js";
39
- import { i as saasPermissions } from "../permissions-BPxlwVhN.js";
40
- import { C as saasOrg, E as saasSeed, S as saasMemberships, a as saasProject, g as verifySaasSession, h as signSaasToken, k as saasPolicy, t as saasDoc, w as saasPrincipal, x as saasCustomRoles } from "../saas-BCXRuXca.js";
41
- import { t as POLICY_NOT_PUBLISHED } from "../problem-types-CJ2325SZ.js";
42
- import { afterAll, beforeAll, describe, expect, expectTypeOf, it, vi } from "vitest";
7
+ import { n as loadPrincipalByIdentity, t as loadPrincipal } from "../principal-source-C6N60GtM.js";
8
+ import { v as resource } from "../permission-registry-B8tYxPIt.js";
9
+ import { d as isoNow, f as nowMillis, p as nowSeconds, u as isoAt } from "../time-nR1JesoH.js";
10
+ import { r as parseAutomation } from "../automation-x3KEUusw.js";
11
+ import { f as scopeList, o as normalizeMemberships } from "../scopes-0Up_UA2t.js";
12
+ import { u as evaluateCondition } from "../delegation-CDB6_keF.js";
13
+ import { a as authenticated } from "../grantee-Dke-9rd1.js";
14
+ import { t as byCodePoint } from "../compare-DTfyUunF.js";
15
+ import { r as definePolicy } from "../policy-CLQ8__GF.js";
16
+ import { c as mergeHostedGrants, l as parsePolicyDocument } from "../hosted-D0IzESu_.js";
17
+ import { t as definePermissions } from "../permissions-CVXmRILq.js";
18
+ import { T as validateCustomRole } from "../evaluate-Dd8Ta0Ie.js";
19
+ import { n as parseSnapshot } from "../snapshot-CXlzi69u.js";
20
+ import { n as fromSnapshot } from "../from-snapshot-DCakWRsr.js";
21
+ import { t as getPermDock } from "../permdock-L-_I_3zP.js";
22
+ import { t as APPROVAL_HEADER } from "../types-CcUXciSj.js";
23
+ import { i as memoryApprovalStore } from "../store-RNFvaJJv.js";
24
+ import { t as catalogFingerprint } from "../catalog-fingerprint-e6aCWosn.js";
25
+ import { s as parseCredential } from "../credential-CyyyvCsL.js";
26
+ import { c as problemFromDecision } from "../problem-BVLHFK33.js";
27
+ import { o as resolveApproval } from "../helpers-DJicaG6f.js";
28
+ import { t as APPROVAL_REQUEST_SCHEMA } from "../request-schema-DhX7UbfQ.js";
29
+ import { t as catalogSchema } from "../schema-BOJjqlWL.js";
30
+ import { c as parseApiKey } from "../server-3cVlTLde.js";
31
+ import { n as discoverAuthorizationServer } from "../device-BwbJNmb2.js";
32
+ import { t as CONNECTOR_SCHEMA } from "../connector-schema-CYLQNMYq.js";
33
+ import { l as sha256Hex, n as scimHandler, t as directoryMembershipSource } from "../scim-jJBJ7g1Z.js";
34
+ import { c as quoteSqlTable, o as quoteSqlIdent } from "../sql-0WjxIp9g.js";
35
+ import { i as cloudEndpoints, n as cloud, s as approverHeaders, t as bearerChallenge } from "../challenge-DImNcR7B.js";
36
+ import { t as supabaseMembershipsBudget } from "../budget-BELja5A9.js";
37
+ import { t as supabaseManifestSchema } from "../manifest-schema-2r37XRhD.js";
38
+ import { t as PUSHED_POLICY_SCHEMA } from "../pushed-policy-ITwYKvvH.js";
39
+ import { i as policyMatrixLayout, n as testRlsParity, r as policyMatrix, t as testOrmParity } from "../core-H-mbhpZc.js";
40
+ import { i as saasPermissions } from "../permissions-mDcTWoBs.js";
41
+ import { C as saasOrg, E as saasSeed, S as saasMemberships, a as saasProject, g as verifySaasSession, h as signSaasToken, k as saasPolicy, t as saasDoc, w as saasPrincipal, x as saasCustomRoles } from "../saas-iWnUdwCt.js";
42
+ import { t as POLICY_NOT_PUBLISHED } from "../problem-types-DSsRfONQ.js";
43
+ import { t as require__2020 } from "../2020-CJbO8Bvz.js";
44
+ import { afterAll, assert, beforeAll, describe, expect, expectTypeOf, it, vi } from "vitest";
43
45
  //#region src/testing/describe-policy.ts
44
- function isOutcomeCell(value) {
45
- return value === "granted" || value === "denied" || value === "approval-required" || value !== null && typeof value === "object" && ("denials" in value || "outcome" in value || "alternatives" in value || "obligations" in value || "deniedBy" in value);
46
- }
47
- /** Asserts `deniedBy` against the trace of a fresh `explain` call for the same arguments. */
48
- function assertDeniedBy(instance, permission, data, cell) {
49
- if (typeof cell !== "object" || cell.deniedBy === void 0) return;
50
- const explained = instance.explain(permission, data);
51
- expect(explained.outcome).toBe("denied");
52
- expect(explained.trace.denies[0]?.name).toBe(cell.deniedBy);
53
- }
54
- function expectedOutcome(cell) {
55
- return typeof cell === "string" ? cell : cell.outcome ?? "denied";
56
- }
57
- function assertCell(decision, cell) {
58
- expect(decision.outcome).toBe(expectedOutcome(cell));
59
- if (typeof cell === "object" && cell.denials !== void 0 && decision.outcome === "denied") for (const denial of cell.denials) expect(decision.denials.some((item) => item.reason === denial.reason && (denial.role === void 0 || item.role === denial.role))).toBe(true);
60
- if (typeof cell === "object" && cell.alternatives !== void 0 && decision.outcome === "denied") expect(decision.alternatives.map((leaf) => leaf.key)).toEqual(cell.alternatives);
61
- if (typeof cell === "object" && cell.obligations !== void 0 && decision.outcome === "granted") expect((decision.obligations ?? []).map((item) => item.kind)).toEqual(cell.obligations);
62
- }
63
- function assertSnapshotCell(instance, permission, data, cell) {
64
- const outcome = expectedOutcome(cell);
65
- if (outcome === "approval-required") return;
66
- const client = fromSnapshot(JSON.parse(JSON.stringify(instance.snapshot())));
67
- expect(client.can(permission, data), "snapshot client disagrees with the server").toBe(outcome === "granted");
68
- }
46
+ /** `policyMatrix` under Vitest: one test per cell, plus the exhaustive check. */
69
47
  function describePolicy(policy, config) {
70
48
  describe("policy matrix", () => {
71
- const permissions = listPermissions(policy.permissions);
72
- const permdocks = /* @__PURE__ */ new Map();
49
+ const state = {};
73
50
  beforeAll(async () => {
74
- for (const [name, user] of Object.entries(config.subjects)) permdocks.set(name, await getPermDock(policy, user, config.options));
51
+ state.report = await policyMatrix(policy, config);
75
52
  });
76
53
  it("covers every permission", () => {
77
- if (config.exhaustive === false) return;
78
- for (const permission of permissions) expect(config.matrix[permission.key], `missing matrix for ${permission.key}`).toBeDefined();
79
- });
80
- for (const permission of permissions) {
81
- const spec = config.matrix[permission.key];
82
- if (spec === void 0) continue;
83
- describe(permission.key, () => {
84
- if (!Object.values(spec).some((value) => !isOutcomeCell(value))) {
85
- for (const [subjectName, cell] of Object.entries(spec)) it(`${subjectName}`, async () => {
86
- const instance = permdocks.get(subjectName) ?? await getPermDock(policy, config.subjects[subjectName], config.options);
87
- const data = permission.kind === "instance" ? Object.values(config.fixtures ?? {})[0] : void 0;
88
- assertCell(instance.decide(permission, data), cell);
89
- assertDeniedBy(instance, permission, data, cell);
90
- if (config.snapshot === true) assertSnapshotCell(instance, permission, data, cell);
91
- });
92
- return;
93
- }
94
- for (const [fixtureName, row] of Object.entries(spec)) {
95
- if (isOutcomeCell(row)) continue;
96
- for (const [subjectName, cell] of Object.entries(row)) it(`${fixtureName} / ${subjectName}`, async () => {
97
- const instance = permdocks.get(subjectName) ?? await getPermDock(policy, config.subjects[subjectName], config.options);
98
- const fixture = config.fixtures?.[fixtureName];
99
- assertCell(instance.decide(permission, fixture), cell);
100
- assertDeniedBy(instance, permission, fixture, cell);
101
- if (config.snapshot === true) assertSnapshotCell(instance, permission, fixture, cell);
102
- });
103
- }
104
- });
105
- }
106
- });
107
- }
108
- //#endregion
109
- //#region src/testing/rls-parity.ts
110
- function toSubject(input) {
111
- return {
112
- principal: {
113
- id: input.id,
114
- roles: input.roles ?? [],
115
- ...input.tenant === void 0 ? {} : { tenant: input.tenant },
116
- memberships: input.memberships ?? [],
117
- ...input.claims === void 0 ? {} : { claims: input.claims }
118
- },
119
- context: {}
120
- };
121
- }
122
- function rowId(row) {
123
- return row["id"];
124
- }
125
- function setting(name, value) {
126
- return {
127
- sql: "select set_config($1, $2, true)",
128
- values: [name, value]
129
- };
130
- }
131
- /**
132
- * The session state the generated helpers read: JWT claims for `supabase`,
133
- * GUCs for `guc` (roles as a comma list, memberships as JSON).
134
- */
135
- function subjectSettings(dialect, subject, gucPrefix, tenantClaim, roleClaim, customRoles, scopes) {
136
- const roles = subject.roles ?? [];
137
- const memberships = membershipsClaim(subject.memberships ?? [], customRoles, scopes);
138
- if (dialect === "supabase" || dialect === "neon") {
139
- const claims = {
140
- ...subject.claims,
141
- sub: subject.id,
142
- role: "authenticated",
143
- [roleClaim]: roles.length === 1 ? roles[0] : roles,
144
- [tenantClaim]: subject.tenant,
145
- memberships
146
- };
147
- return [setting("request.jwt.claims", JSON.stringify(claims))];
148
- }
149
- const settings = [
150
- ...Object.entries(subject.claims ?? {}).map(([name, value]) => {
151
- if (!SQL_IDENT.test(name)) throw new Error(`PermDock: unsafe claim name '${name}'`);
152
- return setting(`${gucPrefix}.${name}`, typeof value === "string" ? value : JSON.stringify(value));
153
- }),
154
- setting(`${gucPrefix}.user_id`, subject.id),
155
- setting(`${gucPrefix}.${roleClaim}`, roles.join(",")),
156
- setting(`${gucPrefix}.memberships`, JSON.stringify(memberships))
157
- ];
158
- return subject.tenant === void 0 ? settings : [...settings, setting(`${gucPrefix}.${tenantClaim}`, subject.tenant)];
159
- }
160
- /**
161
- * The statement a case runs. With field views it reads back only the key, so
162
- * a base table whose restricted columns are revoked does not reject it.
163
- */
164
- function statementSql(action, table, keyOnly) {
165
- const quoted = quoteSqlTable(table);
166
- const id = quoteSqlIdent("id");
167
- const back = keyOnly ? id : "*";
168
- const command = defaultSqlCommand(action) ?? "select";
169
- switch (command) {
170
- case "select": return `select ${back} from ${quoted} where ${id} = $1`;
171
- case "update": return `update ${quoted} set ${id} = ${id} where ${id} = $1 returning ${back}`;
172
- case "insert": return `insert into ${quoted} (${id}) values ($1) returning ${back}`;
173
- case "delete": return `delete from ${quoted} where ${id} = $1 returning ${back}`;
174
- default: return command;
175
- }
176
- }
177
- function valued(row) {
178
- if (row === null || typeof row !== "object") return [];
179
- const record = row;
180
- return Object.keys(record).filter((name) => record[name] !== null && record[name] !== void 0).toSorted();
181
- }
182
- function isRead(action) {
183
- return action === "read" || action === "get" || action === "list";
184
- }
185
- async function viewColumns(query, table, key) {
186
- const id = quoteSqlIdent("id");
187
- await query("savepoint permdock_fields");
188
- const view = await query(`select * from ${quoteSqlTable(`${table}_visible`)} where ${id} = $1`, [key]);
189
- if (view.code === void 0) return valued(view.rows[0]);
190
- await query("rollback to savepoint permdock_fields");
191
- if (view.code !== "42P01") return view.code;
192
- const base = await query(`select * from ${quoteSqlTable(table)} where ${id} = $1`, [key]);
193
- return base.code ?? valued(base.rows[0]);
194
- }
195
- function dbOutcome(result) {
196
- if (result.code === "42501") return "rejected";
197
- return (result.rowCount ?? result.rows.length) > 0 ? "allowed" : "filtered";
198
- }
199
- async function testRlsParity(policy, options) {
200
- const dialect = options.dialect ?? "guc";
201
- const gucPrefix = options.gucPrefix ?? "app";
202
- const tenantClaim = options.tenantClaim ?? "tenant_id";
203
- const roleClaim = options.roleClaim ?? "user_role";
204
- const role = options.role ?? "authenticated";
205
- const customRoles = options.customRoles ?? [];
206
- const scopes = scopeList(policy.scopes);
207
- async function runCase(fixture) {
208
- const permdock = await getPermDock(policy, toSubject(fixture.subject), { customRoles: memoryCustomRoleSource(customRoles) });
209
- const granted = fixture.permission.kind === "collection" ? permdock.can(fixture.permission, fixture.row) : permdock.can(fixture.permission, fixture.row);
210
- let fromClient;
211
- if (options.snapshot === true) {
212
- const snapshot = permdock.snapshot();
213
- if (typeof snapshot !== "object" || snapshot instanceof Promise) throw new TypeError("PermDock: testRlsParity needs an unsigned snapshot");
214
- fromClient = fromSnapshot(parseSnapshot(JSON.stringify(snapshot))).can(fixture.permission, fixture.row);
215
- }
216
- await options.query("begin");
217
- try {
218
- await options.query(`set local role ${quoteSqlIdent(role === "anon" && dialect === "neon" ? "anonymous" : role)}`);
219
- for (const item of subjectSettings(dialect, fixture.subject, gucPrefix, tenantClaim, roleClaim, customRoles, scopes)) await options.query(item.sql, item.values);
220
- const database = dbOutcome(await options.query(statementSql(fixture.permission.action, fixture.table, options.fieldViews === true), [rowId(fixture.row)]));
221
- const agrees = granted ? database === "allowed" : database === "filtered" || database === "rejected";
222
- let fields;
223
- if (options.fieldViews === true && fixture.permission.kind === "instance" && isRead(fixture.permission.action)) {
224
- const key = policy.resources.get(fixture.permission.resource)?.id ?? "id";
225
- const kept = new Set(granted ? valued(permdock.pick(fixture.permission, fixture.row)) : []);
226
- if (granted && fixture.row[key] !== null && fixture.row[key] !== void 0) kept.add(key);
227
- fields = {
228
- app: [...kept].toSorted(),
229
- database: await viewColumns(options.query, fixture.table, rowId(fixture.row))
230
- };
54
+ for (const key of state.report?.missing ?? []) expect.fail(`missing matrix for ${key}`);
55
+ });
56
+ const layout = policyMatrixLayout(policy, config);
57
+ for (const permission of new Set(layout.map((cell) => cell.permission))) describe(permission, () => {
58
+ for (const cell of layout.filter((item) => item.permission === permission)) {
59
+ const title = cell.fixture === void 0 ? cell.subject : `${cell.fixture} / ${cell.subject}`;
60
+ it(title, () => {
61
+ const decided = state.report?.cells.find((item) => item.name === cell.name);
62
+ expect(decided, `cell ${cell.name} was not decided`).toBeDefined();
63
+ expect(decided?.failures).toStrictEqual([]);
64
+ });
231
65
  }
232
- const fieldsOk = fields === void 0 || typeof fields.database !== "string" && fields.database.join("\0") === fields.app.join("\0");
233
- const ok = agrees && fieldsOk && (fromClient === void 0 || fromClient === granted);
234
- return {
235
- name: fixture.name,
236
- granted,
237
- database,
238
- ...fromClient === void 0 ? {} : { snapshot: fromClient },
239
- ...fields === void 0 ? {} : { fields },
240
- ok
241
- };
242
- } finally {
243
- await options.query("rollback");
244
- }
245
- }
246
- async function runAll(remaining, acc) {
247
- const [head, ...tail] = remaining;
248
- if (head === void 0) return acc;
249
- return runAll(tail, [...acc, await runCase(head)]);
250
- }
251
- const results = await runAll(options.fixtures, []);
252
- return {
253
- ok: results.every((item) => item.ok),
254
- results
255
- };
256
- }
257
- //#endregion
258
- //#region src/testing/orm-parity.ts
259
- function sortedIds(values) {
260
- return values.map(String).toSorted();
261
- }
262
- function subsetOf(small, large) {
263
- return small.every((value) => large.includes(value));
264
- }
265
- /**
266
- * ORM parity: for each scenario, the rows `permdock.filter()` keeps in memory
267
- * must be exactly the rows the database returns for `toWhere(permdock.where())`.
268
- * With a `relations` option, the graph is loaded for the rows first, so the
269
- * in-memory side walks the same graph the database query does.
270
- * A partial `where()` (a closure grant it cannot compile) may be refused or
271
- * return fewer rows, never more.
272
- */
273
- async function testOrmParity(policy, scenarios, options) {
274
- const idField = options.id ?? "id";
275
- async function runCase(scenario) {
276
- const permdock = await getPermDock(policy, scenario.user, scenario.options);
277
- if (scenario.options?.relations !== void 0) await permdock.prefetchRelations(scenario.permission, scenario.rows);
278
- const expected = sortedIds(permdock.filter(scenario.permission, scenario.rows).map((row) => row[idField]));
279
- const where = permdock.where(scenario.permission);
280
- try {
281
- const actual = sortedIds(await options.run({
282
- scenario,
283
- where
284
- }));
285
- const ok = where.partial ? subsetOf(actual, expected) : actual.length === expected.length && subsetOf(actual, expected);
286
- return {
287
- name: scenario.name,
288
- expected,
289
- actual,
290
- partial: where.partial,
291
- ok
292
- };
293
- } catch (error) {
294
- return {
295
- name: scenario.name,
296
- expected,
297
- actual: [],
298
- partial: where.partial,
299
- error: error instanceof Error ? error.message : String(error),
300
- ok: where.partial
301
- };
302
- }
303
- }
304
- const results = [];
305
- for (const scenario of scenarios) results.push(await runCase(scenario));
306
- return {
307
- ok: results.every((item) => item.ok),
308
- results
309
- };
66
+ });
67
+ });
310
68
  }
311
69
  //#endregion
312
70
  //#region src/testing/client-parity.ts
@@ -322,8 +80,8 @@ async function outcomes(policy, entry, options) {
322
80
  };
323
81
  }
324
82
  /**
325
- * Asserts that `fromSnapshot(permdock.snapshot())` never grants what the
326
- * server denies, and matches it exactly unless a case is marked `stricter`.
83
+ * Asserts that `fromSnapshot(permdock.snapshot())` never grants what the server
84
+ * denies, and matches it exactly unless a case is marked `stricter`.
327
85
  */
328
86
  function testClientParity(policy, cases, options = {}) {
329
87
  for (const entry of cases) it(`client parity: ${entry.name}`, async () => {
@@ -338,9 +96,9 @@ function testClientParity(policy, cases, options = {}) {
338
96
  //#endregion
339
97
  //#region src/testing/legacy-parity.ts
340
98
  /**
341
- * Asserts that the policy answers each case as the application's legacy
342
- * check does, or as the case's `expect` says where the difference is
343
- * intended. Run it before enforcing a permission with `shadowMode`.
99
+ * Asserts that the policy answers each case as the application's legacy check
100
+ * does, or as the case's `expect` says where the difference is intended. Run it
101
+ * before enforcing a permission with `shadowMode`.
344
102
  */
345
103
  function testLegacyParity(policy, legacy, cases) {
346
104
  for (const entry of cases) it(`legacy parity: ${entry.name}`, async () => {
@@ -354,7 +112,7 @@ function testLegacyParity(policy, legacy, cases) {
354
112
  expect({
355
113
  permission: entry.permission.key,
356
114
  granted: ours
357
- }).toEqual({
115
+ }).toStrictEqual({
358
116
  permission: entry.permission.key,
359
117
  granted: entry.expect ?? theirs
360
118
  });
@@ -460,9 +218,9 @@ function unreachable() {
460
218
  }
461
219
  /**
462
220
  * Race and cache scenarios for a client snapshot store: tenant switches,
463
- * `replace` and `refresh`, logout and a second user during a refresh, stale
464
- * and signed snapshots, and endpoint answers cached per tenant. Runs against
465
- * the saas domain; `createStore` wraps the store an adapter ships.
221
+ * `replace` and `refresh`, logout and a second user during a refresh, stale and
222
+ * signed snapshots, and endpoint answers cached per tenant. Runs against the
223
+ * saas domain; `createStore` wraps the store an adapter ships.
466
224
  */
467
225
  function testClientStore(name, createStore) {
468
226
  const acmeProject = saasProject("p1");
@@ -494,7 +252,7 @@ function testClientStore(name, createStore) {
494
252
  fetch: net.fetch
495
253
  });
496
254
  await store.get().refresh({ tenant: "globex" });
497
- expect(net.calls.map((call) => call.url)).toEqual(["/api/snapshot?v=1&tenant=globex"]);
255
+ expect(net.calls.map((call) => call.url)).toStrictEqual(["/api/snapshot?v=1&tenant=globex"]);
498
256
  expect(store.get().subject.principal?.tenant).toBe("acme");
499
257
  expect(store.get().can(p.project.update, acmeProject)).toBe(true);
500
258
  expect(store.get().status()).toBe("stale");
@@ -742,15 +500,36 @@ function testClientStore(name, createStore) {
742
500
  async function snapshotFixture(policy, subject, options = {}) {
743
501
  const instance = await getPermDock(policy, subject, options.tenant === void 0 ? {} : { tenant: options.tenant });
744
502
  const target = options.simulated === true ? instance.simulate({}) : instance;
745
- if (Array.isArray(target)) throw new Error("PermDock: snapshotFixture expected a PermDock instance");
503
+ if (Array.isArray(target)) throw new TypeError("PermDock: snapshotFixture expected a PermDock instance");
746
504
  const snapshot = options.include === void 0 && options.tenants === void 0 ? target.snapshot() : target.snapshot({
747
505
  ...options.include === void 0 ? {} : { include: options.include },
748
506
  ...options.tenants === void 0 ? {} : { tenants: options.tenants }
749
507
  });
750
- if (typeof snapshot === "string" || snapshot instanceof Promise) throw new Error("PermDock: snapshotFixture expected a JSON snapshot");
508
+ if (typeof snapshot === "string" || snapshot instanceof Promise) throw new TypeError("PermDock: snapshotFixture expected a JSON snapshot");
751
509
  return snapshot;
752
510
  }
753
511
  //#endregion
512
+ //#region src/testing/seed-rows.ts
513
+ /**
514
+ * Inserts `factories.*` rows through the `query` function `testRlsParity`
515
+ * takes, one parameterised `insert` per row with the row's own keys as quoted
516
+ * column names. `undefined` fields are left to the column default. Run it as
517
+ * the table owner, before RLS applies. Resolves to the number of rows
518
+ * inserted.
519
+ */
520
+ async function seedRows(query, rows) {
521
+ let inserted = 0;
522
+ for (const [table, list] of Object.entries(rows)) {
523
+ const target = quoteSqlTable(table);
524
+ for (const row of list) {
525
+ const columns = Object.keys(row).filter((key) => row[key] !== void 0);
526
+ await query(columns.length === 0 ? `insert into ${target} default values` : `insert into ${target} (${columns.map((column) => quoteSqlIdent(column)).join(", ")}) values (${columns.map((_, index) => `$${String(index + 1)}`).join(", ")})`, columns.map((column) => row[column]));
527
+ inserted += 1;
528
+ }
529
+ }
530
+ return inserted;
531
+ }
532
+ //#endregion
754
533
  //#region src/testing/http-adapter/domain.ts
755
534
  function project$1(id) {
756
535
  const row = saasSeed.projects.find((candidate) => candidate.id === id);
@@ -1071,11 +850,12 @@ function testHttpAdapter(options) {
1071
850
  return eventStream(await mounted.fetch(new Request(`http://app.test/${org}/projects/${id}/events`, { headers })));
1072
851
  };
1073
852
  scenario("tenant", "resolves the tenant from the path on every protect", async () => {
1074
- expect((await send("alice", {
853
+ const { status } = await send("alice", {
1075
854
  op: "project.get",
1076
855
  org: "acme",
1077
856
  id: "p2"
1078
- })).status).toBe(200);
857
+ });
858
+ expect(status).toBe(200);
1079
859
  expect((await send("alice", {
1080
860
  op: "project.get",
1081
861
  org: "globex",
@@ -1098,11 +878,12 @@ function testHttpAdapter(options) {
1098
878
  })).status).toBe(401);
1099
879
  });
1100
880
  scenario("roles", "applies the role held in the requested org", async () => {
1101
- expect((await send("alice", {
881
+ const { status } = await send("alice", {
1102
882
  op: "project.update",
1103
883
  org: "acme",
1104
884
  id: "p1"
1105
- })).status).toBe(200);
885
+ });
886
+ expect(status).toBe(200);
1106
887
  expect((await send("alice", {
1107
888
  op: "project.update",
1108
889
  org: "globex",
@@ -1127,11 +908,12 @@ function testHttpAdapter(options) {
1127
908
  name: "Contract",
1128
909
  archived: false
1129
910
  };
1130
- expect((await send("dave", {
911
+ const { status } = await send("dave", {
1131
912
  op: "project.create",
1132
913
  org: "acme",
1133
914
  body
1134
- })).status).toBe(201);
915
+ });
916
+ expect(status).toBe(201);
1135
917
  expect((await send("hank", {
1136
918
  op: "project.create",
1137
919
  org: "acme",
@@ -1139,29 +921,34 @@ function testHttpAdapter(options) {
1139
921
  })).status).toBe(403);
1140
922
  });
1141
923
  scenario("plans", "gates a feature on the plan of the requested org", async () => {
1142
- expect((await send("erin", {
924
+ const { status } = await send("erin", {
1143
925
  op: "analytics.read",
1144
926
  org: "globex"
1145
- })).status).toBe(200);
927
+ });
928
+ expect(status).toBe(200);
1146
929
  const upgrade = await send("erin", {
1147
930
  op: "analytics.read",
1148
931
  org: "acme"
1149
932
  });
1150
933
  expect(upgrade.status).toBe(403);
1151
- expect(new Set(reasonsOf(upgrade.body))).toEqual(/* @__PURE__ */ new Set(["not-entitled"]));
934
+ expect(new Set(reasonsOf(upgrade.body))).toStrictEqual(/* @__PURE__ */ new Set(["not-entitled"]));
1152
935
  });
1153
936
  scenario("expired", "ignores an expired membership", async () => {
1154
- expect((await send("frank", {
937
+ const { status } = await send("frank", {
1155
938
  op: "project.get",
1156
939
  org: "acme",
1157
940
  id: "p1"
1158
- })).status).toBe(403);
941
+ });
942
+ expect(status).toBe(403);
1159
943
  });
1160
944
  scenario("limits", "counts a quota per subject and tenant", async () => {
1161
- for (let index = 0; index < 5; index += 1) expect((await send("erin", {
1162
- op: "apiKey.create",
1163
- org: "acme"
1164
- })).status).toBe(201);
945
+ for (let index = 0; index < 5; index += 1) {
946
+ const { status } = await send("erin", {
947
+ op: "apiKey.create",
948
+ org: "acme"
949
+ });
950
+ expect(status).toBe(201);
951
+ }
1165
952
  const exhausted = await send("erin", {
1166
953
  op: "apiKey.create",
1167
954
  org: "acme"
@@ -1181,11 +968,12 @@ function testHttpAdapter(options) {
1181
968
  name: "New",
1182
969
  archived: false
1183
970
  };
1184
- expect((await send("bob", {
971
+ const { status } = await send("bob", {
1185
972
  op: "project.create",
1186
973
  org: "acme",
1187
974
  body: valid
1188
- })).status).toBe(201);
975
+ });
976
+ expect(status).toBe(201);
1189
977
  const invalid = await send("bob", {
1190
978
  op: "project.create",
1191
979
  org: "acme",
@@ -1221,12 +1009,13 @@ function testHttpAdapter(options) {
1221
1009
  "title",
1222
1010
  "permission",
1223
1011
  "denials"
1224
- ]) expect(field(denied.body, key)).toEqual(field(fromProtect.body, key));
1225
- expect((await send("bob", {
1012
+ ]) expect(field(denied.body, key)).toStrictEqual(field(fromProtect.body, key));
1013
+ const { status } = await send("bob", {
1226
1014
  op: "project.delete",
1227
1015
  org: "acme",
1228
1016
  id: "p3"
1229
- })).status).toBe(204);
1017
+ });
1018
+ expect(status).toBe(204);
1230
1019
  expect((await send("bob", {
1231
1020
  op: "project.delete",
1232
1021
  org: "acme",
@@ -1241,7 +1030,7 @@ function testHttpAdapter(options) {
1241
1030
  expect(asked.status).toBe(403);
1242
1031
  expect(field(asked.body, "type")).toBe("https://permdock.com/problems/approval-required");
1243
1032
  const token = field(asked.body, "token");
1244
- expect(typeof token).toBe("string");
1033
+ expect(token).toBeTypeOf("string");
1245
1034
  await resolveApproval(domain.store, String(token), {
1246
1035
  status: "approved",
1247
1036
  by: await approverSubject("carol", "acme")
@@ -1260,11 +1049,12 @@ function testHttpAdapter(options) {
1260
1049
  })).status).toBe(204);
1261
1050
  });
1262
1051
  scenario("stale-approval", "ignores an approval header meant for another request", async () => {
1263
- expect((await send("bob", {
1052
+ const { status } = await send("bob", {
1264
1053
  op: "project.get",
1265
1054
  org: "acme",
1266
1055
  id: "p1"
1267
- }, "pd1.not-a-real-token")).status).toBe(200);
1056
+ }, "pd1.not-a-real-token");
1057
+ expect(status).toBe(200);
1268
1058
  });
1269
1059
  scenario("evaluations", "answers the decision endpoint in the path tenant", async () => {
1270
1060
  const result = await send("bob", {
@@ -1288,7 +1078,7 @@ function testHttpAdapter(options) {
1288
1078
  });
1289
1079
  expect(result.status).toBe(200);
1290
1080
  const rows = field(result.body, "evaluations");
1291
- expect(Array.isArray(rows) ? rows.map((row) => field(row, "decision")) : rows).toEqual([true, false]);
1081
+ expect(Array.isArray(rows) ? rows.map((row) => field(row, "decision")) : rows).toStrictEqual([true, false]);
1292
1082
  });
1293
1083
  scenario("upload", "leaves a multipart body to the framework parser", async () => {
1294
1084
  const file = {
@@ -1302,22 +1092,24 @@ function testHttpAdapter(options) {
1302
1092
  file
1303
1093
  });
1304
1094
  expect(uploaded.status).toBe(201);
1305
- expect(uploaded.body).toEqual({
1095
+ expect(uploaded.body).toStrictEqual({
1306
1096
  name: "notes.txt",
1307
1097
  size: 5
1308
1098
  });
1309
- expect((await send("bob", {
1099
+ const { status } = await send("bob", {
1310
1100
  op: "project.upload",
1311
1101
  org: "acme",
1312
1102
  id: "p2",
1313
1103
  file
1314
- })).status).toBe(403);
1104
+ });
1105
+ expect(status).toBe(403);
1315
1106
  });
1316
1107
  scenario("sub-app", "protects a route inside a sub-app", async () => {
1317
- expect((await send("alice", {
1108
+ const { status } = await send("alice", {
1318
1109
  op: "admin.members",
1319
1110
  org: "acme"
1320
- })).status).toBe(200);
1111
+ });
1112
+ expect(status).toBe(200);
1321
1113
  expect((await send("bob", {
1322
1114
  op: "admin.members",
1323
1115
  org: "acme"
@@ -1334,7 +1126,7 @@ function testHttpAdapter(options) {
1334
1126
  org: entry.org,
1335
1127
  id: entry.id
1336
1128
  })).status));
1337
- expect(statuses).toEqual(calls.map((entry) => entry.status));
1129
+ expect(statuses).toStrictEqual(calls.map((entry) => entry.status));
1338
1130
  });
1339
1131
  scenario("stream-revoked", "closes an open stream when the session is revoked", async () => {
1340
1132
  expect((await openStream(null, "acme", "p1")).status).toBe(401);
@@ -1389,7 +1181,7 @@ function testHttpAdapter(options) {
1389
1181
  expect(frame?.event).toBe("message");
1390
1182
  ids.push(String(field(JSON.parse(frame?.data ?? "{}"), "id")));
1391
1183
  }
1392
- expect(new Set(ids)).toEqual(/* @__PURE__ */ new Set([
1184
+ expect(new Set(ids)).toStrictEqual(/* @__PURE__ */ new Set([
1393
1185
  "p1",
1394
1186
  "p3",
1395
1187
  "p4"
@@ -1716,10 +1508,11 @@ function testAgentRuntime(options) {
1716
1508
  const listed = await agent().allowedOperations(alice);
1717
1509
  expect(listed.has("project_read")).toBe(true);
1718
1510
  expect(listed.has("drop_database")).toBe(false);
1719
- expect([...await agent().allowedOperations(mallory)]).toEqual([]);
1511
+ expect([...await agent().allowedOperations(mallory)]).toStrictEqual([]);
1720
1512
  });
1721
1513
  scenario("model-gate", "grants a model by its permission and denies one the map does not name", async () => {
1722
- expect((await agent().decideModel("frontier", erin)).outcome).toBe("granted");
1514
+ const { outcome } = await agent().decideModel("frontier", erin);
1515
+ expect(outcome).toBe("granted");
1723
1516
  expect((await agent().decideModel("frontier", alice)).outcome).toBe("denied");
1724
1517
  expect((await agent().decideModel("unlisted", erin)).outcome).toBe("denied");
1725
1518
  expect((await agent().decideModel("standard", {
@@ -1728,15 +1521,15 @@ function testAgentRuntime(options) {
1728
1521
  })).outcome).toBe("denied");
1729
1522
  });
1730
1523
  scenario("permitted-models", "returns the granted models in declaration order", async () => {
1731
- expect(await agent().permittedModels(erin)).toEqual(["standard", "frontier"]);
1732
- expect(await agent().permittedModels(alice)).toEqual(["standard"]);
1733
- expect(await agent().permittedModels(mallory)).toEqual([]);
1524
+ expect(await agent().permittedModels(erin)).toStrictEqual(["standard", "frontier"]);
1525
+ expect(await agent().permittedModels(alice)).toStrictEqual(["standard"]);
1526
+ expect(await agent().permittedModels(mallory)).toStrictEqual([]);
1734
1527
  });
1735
1528
  scenario("results-filter", "keeps the rows the results permission grants, and none for an unknown operation", async () => {
1736
1529
  const rows = projects("p1", "g1", "p2");
1737
- expect((await agent().filterResults("project_search", rows, alice)).map((row) => row.id)).toEqual(["p1", "p2"]);
1738
- expect(await agent().filterResults("project_search", rows, mallory)).toEqual([]);
1739
- expect(await agent().filterResults("drop_database", rows, alice)).toEqual([]);
1530
+ expect((await agent().filterResults("project_search", rows, alice)).map((row) => row.id)).toStrictEqual(["p1", "p2"]);
1531
+ expect(await agent().filterResults("project_search", rows, mallory)).toStrictEqual([]);
1532
+ expect(await agent().filterResults("drop_database", rows, alice)).toStrictEqual([]);
1740
1533
  });
1741
1534
  scenario("gateway-options", "attributes gateway usage to the principal, and to nobody for an anonymous call", async () => {
1742
1535
  const attributed = await agent().gatewayOptions(alice);
@@ -1786,7 +1579,7 @@ function testAuthzen(handle, options) {
1786
1579
  }
1787
1580
  for (const [index, vector] of (options.vectors.evaluations ?? []).entries()) it(`evaluations ${index}: ${vector.request.evaluations.length} items`, async () => {
1788
1581
  const body = await post("/access/v1/evaluations", vector.request);
1789
- expect(body.evaluations.map((row) => ({ decision: row.decision }))).toEqual(vector.expected);
1582
+ expect(body.evaluations.map((row) => ({ decision: row.decision }))).toStrictEqual(vector.expected);
1790
1583
  });
1791
1584
  const search = options.vectors.search ?? {};
1792
1585
  for (const kind of [
@@ -1795,10 +1588,13 @@ function testAuthzen(handle, options) {
1795
1588
  "action"
1796
1589
  ]) for (const [index, vector] of (search[kind] ?? []).entries()) it(`search/${kind} ${index}`, async () => {
1797
1590
  const body = await post(`/access/v1/search/${kind}`, vector.request);
1798
- expect(body.results.map(sortKey).toSorted()).toEqual(vector.expected.results.map(sortKey).toSorted());
1591
+ expect(body.results.map(sortKey).toSorted()).toStrictEqual(vector.expected.results.map(sortKey).toSorted());
1799
1592
  });
1800
1593
  }
1801
- /** The AuthZEN interop Todo directory: each user is addressable by email or by identity. */
1594
+ /**
1595
+ * The AuthZEN interop Todo directory: each user is addressable by email or by
1596
+ * identity.
1597
+ */
1802
1598
  const authzenTodoUsers = Object.freeze([
1803
1599
  {
1804
1600
  id: "rick@the-citadel.com",
@@ -1843,7 +1639,10 @@ const authzenTodoPermissions = definePermissions({
1843
1639
  });
1844
1640
  const todo = authzenTodoPermissions.todo;
1845
1641
  const everyone = [allow(authzenTodoPermissions.user.can_read_user), allow(todo.can_read_todos)];
1846
- /** The interop Todo rules: viewers read, editors write their own, admins delete and evil geniuses update anything. */
1642
+ /**
1643
+ * The interop Todo rules: viewers read, editors write their own, admins delete
1644
+ * and evil geniuses update anything.
1645
+ */
1847
1646
  const authzenTodoPolicy = definePolicy(authzenTodoPermissions, {
1848
1647
  roles: [
1849
1648
  role("viewer", everyone),
@@ -1878,7 +1677,10 @@ const TODOS = Object.freeze(authzenTodoUsers.map((user, index) => ({
1878
1677
  id: `todo-${String(index + 1)}`,
1879
1678
  ownerID: user.id
1880
1679
  })));
1881
- /** Rows for `resources.todo.list` and records for `subjects.list` in the Todo domain. */
1680
+ /**
1681
+ * Rows for `resources.todo.list` and records for `subjects.list` in the Todo
1682
+ * domain.
1683
+ */
1882
1684
  const authzenTodoData = Object.freeze({
1883
1685
  todos: TODOS,
1884
1686
  users: Object.freeze(authzenTodoUsers.map((user) => ({ id: user.id })))
@@ -1896,9 +1698,9 @@ function expectedFor(user, action, ownerID) {
1896
1698
  }
1897
1699
  }
1898
1700
  /**
1899
- * PermDock's vectors over the interop Todo domain, in the interop layout:
1900
- * every user against every action on their own and on Rick's todo, one batch
1901
- * per user, and the three searches.
1701
+ * PermDock's vectors over the interop Todo domain, in the interop layout: every
1702
+ * user against every action on their own and on Rick's todo, one batch per
1703
+ * user, and the three searches.
1902
1704
  */
1903
1705
  function authzenTodoVectors() {
1904
1706
  const rick = authzenTodoUsers[0];
@@ -2098,9 +1900,9 @@ function testApprovalStore(store, options = {}) {
2098
1900
  });
2099
1901
  const [first, second] = await Promise.all([store.consume("once-token"), store.consume("once-token")]);
2100
1902
  expect([first, second].filter((item) => item !== null)).toHaveLength(1);
2101
- expect((first ?? second)?.consumedAt).toEqual(expect.any(String));
1903
+ expect((first ?? second)?.consumedAt).toStrictEqual(expect.any(String));
2102
1904
  expect(await store.consume("once-token")).toBeNull();
2103
- expect((await store.get("once-token"))?.consumedAt).toEqual(expect.any(String));
1905
+ expect((await store.get("once-token"))?.consumedAt).toStrictEqual(expect.any(String));
2104
1906
  });
2105
1907
  it("refuses an approver from another tenant", async () => {
2106
1908
  await store.create(tenantApproval("tenant-token", "o_1"));
@@ -2116,17 +1918,31 @@ function testApprovalStore(store, options = {}) {
2116
1918
  status: "approved",
2117
1919
  by: approver
2118
1920
  });
1921
+ assert.isDefined(options.reopen);
2119
1922
  const reopened = await options.reopen();
2120
1923
  expect((await reopened.get("restart-token"))?.status).toBe("approved");
2121
1924
  expect(await reopened.consume("restart-token")).not.toBeNull();
2122
1925
  expect(await store.consume("restart-token")).toBeNull();
2123
1926
  });
1927
+ it.skipIf(options.capabilities?.expire === void 0)("replaces an expired request on a new ask", async () => {
1928
+ await store.create(sampleApproval("renew-token"));
1929
+ assert.isDefined(options.capabilities?.expire);
1930
+ await options.capabilities.expire("renew-token");
1931
+ const renewed = {
1932
+ ...sampleApproval("renew-token"),
1933
+ id: "apr_renewed"
1934
+ };
1935
+ await store.create(renewed);
1936
+ const loaded = await store.get("renew-token");
1937
+ expect(loaded?.id).toBe("apr_renewed");
1938
+ expect(loaded?.status).toBe("pending");
1939
+ });
2124
1940
  it("creates, gets, lists, resolves and expires", async () => {
2125
1941
  const request = sampleApproval("opaque-token");
2126
1942
  await store.create(request);
2127
1943
  const loaded = await store.get("opaque-token");
2128
- expect(loaded).toEqual(request);
2129
- expect(JSON.parse(JSON.stringify(loaded))).toEqual(request);
1944
+ expect(loaded).toStrictEqual(request);
1945
+ expect(JSON.parse(JSON.stringify(loaded))).toStrictEqual(request);
2130
1946
  const listed = await store.list({ status: "pending" });
2131
1947
  expect(listed.items.some((item) => item.token === "opaque-token")).toBe(true);
2132
1948
  const resolved = await store.resolve("opaque-token", {
@@ -2137,7 +1953,7 @@ function testApprovalStore(store, options = {}) {
2137
1953
  await expect(Promise.resolve().then(() => store.resolve("opaque-token", {
2138
1954
  status: "rejected",
2139
1955
  by: approver
2140
- }))).rejects.toThrow(/not pending/);
1956
+ }))).rejects.toThrow(/not pending/u);
2141
1957
  await store.create(sampleApproval("stale-token"));
2142
1958
  const expired = await store.expire({ epochMilliseconds: nowMillis() + 72e5 });
2143
1959
  expect(expired).toBeGreaterThanOrEqual(1);
@@ -2160,19 +1976,20 @@ function testApprovalStore(store, options = {}) {
2160
1976
  principalId: "u_pager",
2161
1977
  limit: 2
2162
1978
  });
2163
- expect(first.items.map((item) => item.token)).toEqual(["page-a", "page-b"]);
2164
- expect(typeof first.next).toBe("string");
1979
+ expect(first.items.map((item) => item.token)).toStrictEqual(["page-a", "page-b"]);
1980
+ expect(first.next).toBeTypeOf("string");
1981
+ assert.isDefined(first.next);
2165
1982
  const second = await store.list({
2166
1983
  principalId: "u_pager",
2167
1984
  limit: 2,
2168
1985
  cursor: first.next
2169
1986
  });
2170
- expect(second.items.map((item) => item.token)).toEqual(["page-c"]);
1987
+ expect(second.items.map((item) => item.token)).toStrictEqual(["page-c"]);
2171
1988
  expect(second.next).toBeUndefined();
2172
1989
  expect((await store.list({
2173
1990
  principalId: "u_pager",
2174
1991
  cursor: "not a cursor"
2175
- })).items).toEqual([]);
1992
+ })).items).toStrictEqual([]);
2176
1993
  });
2177
1994
  it("refuses an approver who does not match request.approvers", async () => {
2178
1995
  const request = {
@@ -2194,7 +2011,7 @@ function testApprovalStore(store, options = {}) {
2194
2011
  await expect(Promise.resolve().then(() => store.resolve("gated-token", {
2195
2012
  status: "approved",
2196
2013
  by: member
2197
- }))).rejects.toThrow(/eligible|not pending|not found/);
2014
+ }))).rejects.toThrow(/eligible|not pending|not found/u);
2198
2015
  });
2199
2016
  it("refuses the principal as approver unless the grant sets distinct: false", async () => {
2200
2017
  const principal = {
@@ -2249,7 +2066,7 @@ function testApprovalStore(store, options = {}) {
2249
2066
  by: approver
2250
2067
  });
2251
2068
  expect(first.status).toBe("pending");
2252
- expect(first.approvals).toEqual([{
2069
+ expect(first.approvals).toStrictEqual([{
2253
2070
  by: "u_9",
2254
2071
  at: expect.any(String)
2255
2072
  }]);
@@ -2263,7 +2080,7 @@ function testApprovalStore(store, options = {}) {
2263
2080
  by: secondApprover
2264
2081
  });
2265
2082
  expect(second.status).toBe("approved");
2266
- expect(second.approvals?.map((item) => item.by)).toEqual(["u_9", "u_10"]);
2083
+ expect(second.approvals?.map((item) => item.by)).toStrictEqual(["u_9", "u_10"]);
2267
2084
  expect(second.resolvedBy).toBe("u_10");
2268
2085
  expect(await store.consume("quorum-token")).not.toBeNull();
2269
2086
  });
@@ -2299,7 +2116,7 @@ function testApprovalStore(store, options = {}) {
2299
2116
  });
2300
2117
  expect(resolved.status).toBe("approved");
2301
2118
  expect(resolved.vouched).toBe("app-rule");
2302
- expect(resolved.approvals).toEqual([{
2119
+ expect(resolved.approvals).toStrictEqual([{
2303
2120
  by: "u_outsider",
2304
2121
  at: expect.any(String),
2305
2122
  vouched: "app-rule"
@@ -2382,7 +2199,7 @@ function testApprovalStore(store, options = {}) {
2382
2199
  by: secondApprover
2383
2200
  });
2384
2201
  expect(first.status).toBe("pending");
2385
- expect(first.approvals).toEqual([{
2202
+ expect(first.approvals).toStrictEqual([{
2386
2203
  by: "u_10",
2387
2204
  at: expect.any(String),
2388
2205
  stage: 0
@@ -2392,7 +2209,7 @@ function testApprovalStore(store, options = {}) {
2392
2209
  by: approver
2393
2210
  });
2394
2211
  expect(second.status).toBe("approved");
2395
- expect(second.approvals?.map((item) => item.stage)).toEqual([0, 1]);
2212
+ expect(second.approvals?.map((item) => item.stage)).toStrictEqual([0, 1]);
2396
2213
  });
2397
2214
  it("matches a relation approver only through the verdict's relations", async () => {
2398
2215
  const manager = {
@@ -2423,21 +2240,21 @@ function testApprovalStore(store, options = {}) {
2423
2240
  }
2424
2241
  /**
2425
2242
  * Checks an `ApprovalPolicySource`: entries are plain JSON, belong to the
2426
- * queried tenant or to none, and every entry loads against `policy` (an
2427
- * entry that does not would deny every call).
2243
+ * queried tenant or to none, and every entry loads against `policy` (an entry
2244
+ * that does not would deny every call).
2428
2245
  */
2429
2246
  function testApprovalPolicySource(source, options) {
2430
2247
  it("answers per tenant with plain entries that load against the policy", async () => {
2431
2248
  const entries = await source.approvalPoliciesFor({ tenants: [options.tenant] });
2432
2249
  expect(Array.isArray(entries)).toBe(true);
2433
2250
  for (const entry of entries) {
2434
- expect(JSON.parse(JSON.stringify(entry))).toEqual(entry);
2251
+ expect(JSON.parse(JSON.stringify(entry))).toStrictEqual(entry);
2435
2252
  expect([void 0, options.tenant]).toContain(entry.tenant);
2436
2253
  }
2437
2254
  const loaded = await approvalPoliciesFor(options.policy, source, [options.tenant], []);
2438
2255
  expect(loaded).not.toBe("failed");
2439
2256
  const other = await source.approvalPoliciesFor({ tenants: [options.unknown ?? "__permdock_unknown__"] });
2440
- expect(other.filter((entry) => entry.tenant !== void 0)).toEqual([]);
2257
+ expect(other.filter((entry) => entry.tenant !== void 0)).toStrictEqual([]);
2441
2258
  });
2442
2259
  }
2443
2260
  const reviewItem = {
@@ -2482,7 +2299,7 @@ function testAccessReviewStore(store) {
2482
2299
  limit: 1
2483
2300
  });
2484
2301
  expect(page.items).toHaveLength(1);
2485
- expect(page.next).toEqual(expect.any(String));
2302
+ expect(page.next).toStrictEqual(expect.any(String));
2486
2303
  const kept = await store.decide("rev", reviewItem.id, {
2487
2304
  status: "kept",
2488
2305
  decidedBy: "u_9",
@@ -2523,7 +2340,7 @@ function testApprovalDelegationSource(source, options) {
2523
2340
  expect(entries.some((entry) => entry.delegate === options.delegate && entry.delegator === options.delegator)).toBe(true);
2524
2341
  const instant = Date.parse(at);
2525
2342
  for (const entry of entries) {
2526
- expect(JSON.parse(JSON.stringify(entry))).toEqual(entry);
2343
+ expect(JSON.parse(JSON.stringify(entry))).toStrictEqual(entry);
2527
2344
  expect(entry.delegate).toBe(options.delegate);
2528
2345
  expect(Date.parse(entry.from)).toBeLessThanOrEqual(instant);
2529
2346
  expect(Date.parse(entry.until)).toBeGreaterThan(instant);
@@ -2532,13 +2349,13 @@ function testApprovalDelegationSource(source, options) {
2532
2349
  delegate: "__permdock_unknown__",
2533
2350
  ...options.tenant === void 0 ? {} : { tenant: options.tenant },
2534
2351
  at
2535
- })).toEqual([]);
2352
+ })).toStrictEqual([]);
2536
2353
  });
2537
2354
  }
2538
2355
  /**
2539
- * Checks an `IdempotencyStore`: a stored response comes back as written
2540
- * until it expires, keys never share an entry, and a later `set` replaces an
2541
- * earlier one.
2356
+ * Checks an `IdempotencyStore`: a stored response comes back as written until
2357
+ * it expires, keys never share an entry, and a later `set` replaces an earlier
2358
+ * one.
2542
2359
  */
2543
2360
  function testIdempotencyStore(store) {
2544
2361
  it("replays a stored response per key and keeps keys apart", async () => {
@@ -2553,14 +2370,14 @@ function testIdempotencyStore(store) {
2553
2370
  const later = nowMillis() + 36e5;
2554
2371
  expect(await store.get(key)).toBeUndefined();
2555
2372
  await store.set(key, response, later);
2556
- expect(await store.get(key)).toEqual(response);
2373
+ expect(await store.get(key)).toStrictEqual(response);
2557
2374
  expect(await store.get(other)).toBeUndefined();
2558
2375
  const replaced = {
2559
2376
  ...response,
2560
2377
  status: 201
2561
2378
  };
2562
2379
  await store.set(key, replaced, later);
2563
- expect(await store.get(key)).toEqual(replaced);
2380
+ expect(await store.get(key)).toStrictEqual(replaced);
2564
2381
  });
2565
2382
  }
2566
2383
  //#endregion
@@ -2578,7 +2395,7 @@ function testLimitStore(store) {
2578
2395
  expect(first.remaining).toBeGreaterThanOrEqual(0);
2579
2396
  const peeked = store.remaining(input);
2580
2397
  if (peeked !== void 0) {
2581
- expect(typeof peeked.remaining).toBe("number");
2398
+ expect(peeked.remaining).toBeTypeOf("number");
2582
2399
  expect(peeked !== null && typeof peeked === "object" && "then" in peeked && typeof peeked.then === "function").toBe(false);
2583
2400
  }
2584
2401
  await store.consume(input);
@@ -2622,7 +2439,7 @@ function testPolicySource(source, options = {}) {
2622
2439
  };
2623
2440
  it("returns null or a v1 policy document synchronously", () => {
2624
2441
  const document = readCurrent();
2625
- if (document !== null) expect(parsePolicyDocument(document)).toEqual(document);
2442
+ if (document !== null) expect(parsePolicyDocument(document)).toStrictEqual(document);
2626
2443
  });
2627
2444
  it("refreshes without rejecting and keeps current() synchronous", async () => {
2628
2445
  await expect(Promise.resolve(source.refresh())).resolves.toBeUndefined();
@@ -2649,7 +2466,9 @@ const OAUTH_FORM$1 = "application/x-www-form-urlencoded";
2649
2466
  const PLATFORM_SCOPES = {
2650
2467
  "projects:read": "List and read the projects the person belongs to",
2651
2468
  "policy:read": "Read a project's pushed catalog, manifest and policy",
2652
- "catalog:write": "Push a catalog and policy with permdock cloud push"
2469
+ "catalog:write": "Push a catalog and policy with permdock cloud push",
2470
+ "evidence:read": "Read a project's decision log and exports, the evidence an access review uses",
2471
+ "approvals:read": "Read a project's approval requests and their verdicts"
2653
2472
  };
2654
2473
  const ref$1 = (name) => ({ $ref: `#/components/schemas/${name}` });
2655
2474
  const RATE_LIMIT_HEADERS = {
@@ -2683,14 +2502,22 @@ const TOO_MANY = problem$1("Rate limited; retry after Retry-After seconds", {
2683
2502
  schema: { type: "string" }
2684
2503
  }
2685
2504
  });
2505
+ /**
2506
+ * The RFC 6750 section 3 challenge a refused read carries, so a client tells a
2507
+ * dead token from a missing scope.
2508
+ */
2509
+ const challenge = (example) => ({ "WWW-Authenticate": {
2510
+ description: `RFC 6750 Bearer challenge (\`${example}\`)`,
2511
+ schema: { type: "string" }
2512
+ } });
2686
2513
  const READ_SECURITY = (scope) => [{ platform: [scope] }, { cloudToken: [] }];
2687
2514
  const read = (scope, summary, responses) => ({ get: {
2688
2515
  security: READ_SECURITY(scope),
2689
2516
  summary,
2690
2517
  responses: {
2691
2518
  ...responses,
2692
- "401": problem$1("Missing, expired or revoked token"),
2693
- "403": problem$1("The token may not read this project; a PERMDOCK_CLOUD_TOKEN reads only its own environment"),
2519
+ "401": problem$1("Missing, expired or revoked token; a refused token carries `error=\"invalid_token\"`", challenge("Bearer realm=\"permdock\", error=\"invalid_token\"")),
2520
+ "403": problem$1(`The token lacks ${scope} (\`error="insufficient_scope"\` with \`scope\`), or may not read this project; a PERMDOCK_CLOUD_TOKEN reads only its own environment`, challenge(`Bearer realm="permdock", error="insufficient_scope", scope="${scope}"`)),
2694
2521
  "429": TOO_MANY
2695
2522
  }
2696
2523
  } });
@@ -2776,6 +2603,10 @@ const PLATFORM_SCHEMAS = {
2776
2603
  minLength: 1
2777
2604
  },
2778
2605
  name: { type: "string" },
2606
+ slug: {
2607
+ type: "string",
2608
+ description: "The project's URL-safe name, unique in its organization"
2609
+ },
2779
2610
  environment: {
2780
2611
  type: "string",
2781
2612
  minLength: 1
@@ -2794,6 +2625,11 @@ const PLATFORM_SCHEMAS = {
2794
2625
  },
2795
2626
  fingerprint: { type: "string" }
2796
2627
  }
2628
+ },
2629
+ updatedAt: {
2630
+ type: "string",
2631
+ format: "date-time",
2632
+ description: "When the project last changed, RFC 3339"
2797
2633
  }
2798
2634
  }
2799
2635
  },
@@ -2842,13 +2678,24 @@ const PROJECT_ID = {
2842
2678
  minLength: 1
2843
2679
  }
2844
2680
  };
2845
- /** Routes of the platform issuer and its read API; they sit at the API root, outside /v1/environments/{env}. */
2681
+ /**
2682
+ * Routes of the platform issuer and its read API; they sit at the API root,
2683
+ * outside /v1/environments/{env}.
2684
+ */
2846
2685
  const PLATFORM_PATHS = {
2847
2686
  "/.well-known/oauth-authorization-server": { get: {
2848
2687
  security: [],
2849
2688
  summary: "RFC 8414 metadata of the platform issuer. For the issuer https://permdock.com/api it is served at https://permdock.com/.well-known/oauth-authorization-server/api (section 3.1) and here.",
2850
2689
  responses: { "200": oauthJson("The metadata", ref$1("AuthorizationServerMetadata")) }
2851
2690
  } },
2691
+ "/openapi.json": { get: {
2692
+ security: [],
2693
+ summary: "This document, the contract of https://permdock.com/api. permdock/schemas/cloud-api-v1.json ships the same document in the npm package.",
2694
+ responses: { "200": oauthJson("The OpenAPI 3.1 document", {
2695
+ type: "object",
2696
+ required: ["openapi", "paths"]
2697
+ }) }
2698
+ } },
2852
2699
  "/oauth/authorize": { get: {
2853
2700
  security: [],
2854
2701
  summary: "Authorization code with PKCE S256 for public clients, including the RFC 8252 loopback redirect http://127.0.0.1:{port}/callback",
@@ -2981,17 +2828,25 @@ const PLATFORM_PATHS = {
2981
2828
  "400": oauthJson("invalid_redirect_uri or invalid_client_metadata", ref$1("OAuthError"))
2982
2829
  }
2983
2830
  } },
2984
- "/v1/projects": read("projects:read", "The projects the token reads", { "200": limited("The projects", {
2985
- type: "object",
2986
- required: ["items"],
2987
- properties: {
2988
- items: {
2989
- type: "array",
2990
- items: ref$1("Project")
2991
- },
2992
- next: { type: "string" }
2993
- }
2994
- }) }),
2831
+ "/v1/projects": {
2832
+ parameters: [{
2833
+ name: "cursor",
2834
+ in: "query",
2835
+ description: "The `next` of the previous page",
2836
+ schema: { type: "string" }
2837
+ }],
2838
+ ...read("projects:read", "The projects the token reads", { "200": limited("The projects", {
2839
+ type: "object",
2840
+ required: ["items"],
2841
+ properties: {
2842
+ items: {
2843
+ type: "array",
2844
+ items: ref$1("Project")
2845
+ },
2846
+ next: { type: "string" }
2847
+ }
2848
+ }) })
2849
+ },
2995
2850
  "/v1/projects/{id}": {
2996
2851
  parameters: [PROJECT_ID],
2997
2852
  ...read("projects:read", "One project", {
@@ -3027,7 +2882,10 @@ const PLATFORM_SECURITY_SCHEMES = {
3027
2882
  //#endregion
3028
2883
  //#region src/testing/cloud-api/platform-checks.ts
3029
2884
  const ITEM = /^"[^"]+"(?:;[a-z]+=-?\d+)+$/u;
3030
- /** Whether a RateLimit or RateLimit-Policy field value lists items with the parameters `keys` (draft-ietf-httpapi-ratelimit-headers-11). */
2885
+ /**
2886
+ * Whether a RateLimit or RateLimit-Policy field value lists items with the
2887
+ * parameters `keys` (draft-ietf-httpapi-ratelimit-headers-11).
2888
+ */
3031
2889
  function rateLimitField(value, keys) {
3032
2890
  if (value === null || value.trim() === "") return false;
3033
2891
  return value.split(",").every((raw) => {
@@ -3038,6 +2896,7 @@ function rateLimitField(value, keys) {
3038
2896
  function rateLimited(response) {
3039
2897
  return rateLimitField(response.headers.get("ratelimit"), ["r", "t"]) && rateLimitField(response.headers.get("ratelimit-policy"), ["q", "w"]);
3040
2898
  }
2899
+ const challengeOf = (response) => bearerChallenge(response.headers.get("www-authenticate"));
3041
2900
  function problemType(response, body) {
3042
2901
  return (response.headers.get("content-type") ?? "").startsWith("application/problem+json") && isRecord(body) ? body["type"] : void 0;
3043
2902
  }
@@ -3051,14 +2910,14 @@ function testCloudPlatform(url, environment, options, fetchFn) {
3051
2910
  body: new URLSearchParams(fields).toString()
3052
2911
  });
3053
2912
  it("publishes RFC 8414 metadata for the platform issuer", async () => {
3054
- const metadata = await discoverAuthorizationServer(url, { fetch: (input, init) => fetchFn(String(input), init) });
2913
+ const metadata = await discoverAuthorizationServer(url, { fetch: (input, init) => fetchFn(input instanceof Request ? input.url : input.toString(), init) });
3055
2914
  expect(metadata).not.toBeNull();
3056
2915
  expect(metadata).toMatchObject({
3057
2916
  issuer: url,
3058
2917
  code_challenge_methods_supported: ["S256"]
3059
2918
  });
3060
2919
  const scopes = metadata?.["scopes_supported"];
3061
- expect(scopes).toEqual(expect.arrayContaining(Object.keys(PLATFORM_SCOPES)));
2920
+ expect(scopes).toStrictEqual(expect.arrayContaining(Object.keys(PLATFORM_SCOPES)));
3062
2921
  });
3063
2922
  it("lists and reads the project and its policy with RateLimit fields", async () => {
3064
2923
  const list = await read("/v1/projects", options.token);
@@ -3106,6 +2965,38 @@ function testCloudPlatform(url, environment, options, fetchFn) {
3106
2965
  expect(response.status).toBe(401);
3107
2966
  expect(response.headers.get("content-type")).toMatch(/^application\/problem\+json/u);
3108
2967
  });
2968
+ it("answers 401 with an error=\"invalid_token\" challenge to an unknown token", async () => {
2969
+ const response = await read("/v1/projects", `unknown-${globalThis.crypto.randomUUID()}`);
2970
+ expect(response.status).toBe(401);
2971
+ expect(challengeOf(response)).toMatchObject({
2972
+ scheme: "bearer",
2973
+ params: { error: "invalid_token" }
2974
+ });
2975
+ });
2976
+ it("answers 403 with an error=\"insufficient_scope\" challenge naming the scope", async (context) => {
2977
+ if (options.projectsOnly === void 0) {
2978
+ context.skip();
2979
+ return;
2980
+ }
2981
+ const response = await read(`/v1/projects/${encodeURIComponent(project)}/policy`, options.projectsOnly);
2982
+ expect(response.status).toBe(403);
2983
+ expect(challengeOf(response)).toMatchObject({
2984
+ scheme: "bearer",
2985
+ params: {
2986
+ error: "insufficient_scope",
2987
+ scope: "policy:read"
2988
+ }
2989
+ });
2990
+ });
2991
+ it("serves its OpenAPI document at /openapi.json", async () => {
2992
+ const response = await read("/openapi.json", null);
2993
+ expect(response.status).toBe(200);
2994
+ const body = await response.json();
2995
+ expect(body).toMatchObject({
2996
+ openapi: expect.stringMatching(/^3\.[1-9]\d*\./u),
2997
+ paths: expect.objectContaining({ "/v1/projects": expect.any(Object) })
2998
+ });
2999
+ });
3109
3000
  it("refuses a catalog push with the read-only token", async () => {
3110
3001
  const response = await fetchFn(`${url}/v1/environments/${encodeURIComponent(project)}/catalog`, {
3111
3002
  method: "POST",
@@ -3145,436 +3036,13 @@ function testCloudPlatform(url, environment, options, fetchFn) {
3145
3036
  const loopback = await register("http://127.0.0.1:33418/callback");
3146
3037
  expect(loopback.status).toBe(201);
3147
3038
  expect(await loopback.json()).toMatchObject({ client_id: expect.any(String) });
3148
- expect((await register("http://mcp.example.invalid/callback")).status).toBe(400);
3039
+ const { status } = await register("http://mcp.example.invalid/callback");
3040
+ expect(status).toBe(400);
3149
3041
  });
3150
3042
  }
3151
3043
  //#endregion
3152
- //#region src/testing/cloud-api/connector-schema.ts
3153
- var import__2020 = require__2020();
3154
- /**
3155
- * `schemas/connector-v1.json`: the body of a PermDock Cloud connector, one
3156
- * `oneOf` branch per kind with a closed `config`. No branch holds a secret.
3157
- */
3158
- const CONNECTOR_SCHEMA = freezeDeep({
3159
- $schema: "https://json-schema.org/draft/2020-12/schema",
3160
- $id: "https://permdock.com/schemas/connector-v1.json",
3161
- title: "PermDock Cloud connector",
3162
- description: "A connector on PermDock Cloud's /connectors route, wire format v1: the closed list of kinds, one branch per kind. Trusted issuers have their own route (/trusted-issuers).",
3163
- oneOf: [
3164
- {
3165
- description: "CloudEvents batches signed with Standard Webhooks.",
3166
- type: "object",
3167
- required: [
3168
- "kind",
3169
- "name",
3170
- "config"
3171
- ],
3172
- properties: {
3173
- id: {
3174
- type: "string",
3175
- minLength: 1,
3176
- maxLength: 256
3177
- },
3178
- kind: { const: "webhook" },
3179
- name: {
3180
- type: "string",
3181
- minLength: 1,
3182
- maxLength: 200
3183
- },
3184
- config: {
3185
- type: "object",
3186
- additionalProperties: false,
3187
- required: ["url"],
3188
- properties: {
3189
- url: {
3190
- type: "string",
3191
- pattern: "^https://"
3192
- },
3193
- types: {
3194
- type: "array",
3195
- items: {
3196
- type: "string",
3197
- minLength: 1
3198
- },
3199
- uniqueItems: true,
3200
- description: "CloudEvents types to deliver; all when absent."
3201
- }
3202
- }
3203
- }
3204
- }
3205
- },
3206
- {
3207
- description: "OTLP/HTTP log records and spans with the permdock.* attributes.",
3208
- type: "object",
3209
- required: [
3210
- "kind",
3211
- "name",
3212
- "config"
3213
- ],
3214
- properties: {
3215
- id: {
3216
- type: "string",
3217
- minLength: 1,
3218
- maxLength: 256
3219
- },
3220
- kind: { const: "otlp" },
3221
- name: {
3222
- type: "string",
3223
- minLength: 1,
3224
- maxLength: 200
3225
- },
3226
- config: {
3227
- type: "object",
3228
- additionalProperties: false,
3229
- required: ["endpoint"],
3230
- properties: {
3231
- endpoint: {
3232
- type: "string",
3233
- pattern: "^https://"
3234
- },
3235
- headers: {
3236
- type: "object",
3237
- additionalProperties: { type: "string" }
3238
- },
3239
- signals: {
3240
- type: "array",
3241
- items: { enum: ["logs", "traces"] },
3242
- minItems: 1,
3243
- uniqueItems: true
3244
- }
3245
- }
3246
- }
3247
- }
3248
- },
3249
- {
3250
- description: "The OCSF projection posted to a SIEM ingest API.",
3251
- type: "object",
3252
- required: [
3253
- "kind",
3254
- "name",
3255
- "config"
3256
- ],
3257
- properties: {
3258
- id: {
3259
- type: "string",
3260
- minLength: 1,
3261
- maxLength: 256
3262
- },
3263
- kind: { const: "siem" },
3264
- name: {
3265
- type: "string",
3266
- minLength: 1,
3267
- maxLength: 200
3268
- },
3269
- config: {
3270
- type: "object",
3271
- additionalProperties: false,
3272
- required: ["destination", "endpoint"],
3273
- properties: {
3274
- destination: { enum: [
3275
- "splunk-hec",
3276
- "sentinel",
3277
- "security-lake",
3278
- "google-secops",
3279
- "elastic",
3280
- "datadog"
3281
- ] },
3282
- endpoint: {
3283
- type: "string",
3284
- pattern: "^https://"
3285
- }
3286
- }
3287
- }
3288
- }
3289
- },
3290
- {
3291
- description: "A scheduled decision-log export.",
3292
- type: "object",
3293
- required: [
3294
- "kind",
3295
- "name",
3296
- "config"
3297
- ],
3298
- properties: {
3299
- id: {
3300
- type: "string",
3301
- minLength: 1,
3302
- maxLength: 256
3303
- },
3304
- kind: { const: "evidence" },
3305
- name: {
3306
- type: "string",
3307
- minLength: 1,
3308
- maxLength: 200
3309
- },
3310
- config: {
3311
- type: "object",
3312
- additionalProperties: false,
3313
- required: ["format", "destination"],
3314
- properties: {
3315
- format: { enum: [
3316
- "jws",
3317
- "ocsf",
3318
- "csv"
3319
- ] },
3320
- destination: {
3321
- type: "string",
3322
- pattern: "^https://"
3323
- },
3324
- schedule: { enum: ["hourly", "daily"] }
3325
- }
3326
- }
3327
- }
3328
- },
3329
- {
3330
- description: "A hosted SCIM 2.0 relay for one tenant, replayed to the application's scimHandler.",
3331
- type: "object",
3332
- required: [
3333
- "kind",
3334
- "name",
3335
- "config"
3336
- ],
3337
- properties: {
3338
- id: {
3339
- type: "string",
3340
- minLength: 1,
3341
- maxLength: 256
3342
- },
3343
- kind: { const: "scim" },
3344
- name: {
3345
- type: "string",
3346
- minLength: 1,
3347
- maxLength: 200
3348
- },
3349
- config: {
3350
- type: "object",
3351
- additionalProperties: false,
3352
- required: ["tenant", "target"],
3353
- properties: {
3354
- tenant: {
3355
- type: "string",
3356
- minLength: 1,
3357
- maxLength: 256
3358
- },
3359
- target: {
3360
- type: "string",
3361
- pattern: "^https://"
3362
- },
3363
- groupRoles: {
3364
- type: "object",
3365
- description: "Group id to declared assignable role.",
3366
- additionalProperties: {
3367
- type: "string",
3368
- minLength: 1,
3369
- maxLength: 256
3370
- }
3371
- }
3372
- }
3373
- }
3374
- }
3375
- },
3376
- {
3377
- description: "An SSF transmitter whose SETs the Cloud receives (POST /ssf/receive/:connector, or poll).",
3378
- type: "object",
3379
- required: [
3380
- "kind",
3381
- "name",
3382
- "config"
3383
- ],
3384
- properties: {
3385
- id: {
3386
- type: "string",
3387
- minLength: 1,
3388
- maxLength: 256
3389
- },
3390
- kind: { const: "caep-transmitter" },
3391
- name: {
3392
- type: "string",
3393
- minLength: 1,
3394
- maxLength: 200
3395
- },
3396
- config: {
3397
- type: "object",
3398
- additionalProperties: false,
3399
- required: ["issuer", "delivery"],
3400
- properties: {
3401
- issuer: {
3402
- type: "string",
3403
- pattern: "^https://"
3404
- },
3405
- jwks_uri: {
3406
- type: "string",
3407
- pattern: "^https://"
3408
- },
3409
- delivery: { enum: ["push", "poll"] },
3410
- poll_endpoint: {
3411
- type: "string",
3412
- pattern: "^https://"
3413
- }
3414
- }
3415
- }
3416
- }
3417
- },
3418
- {
3419
- description: "An OIDC upstream the Cloud issuer federates to with authorization code plus PKCE.",
3420
- type: "object",
3421
- required: [
3422
- "kind",
3423
- "name",
3424
- "config"
3425
- ],
3426
- properties: {
3427
- id: {
3428
- type: "string",
3429
- minLength: 1,
3430
- maxLength: 256
3431
- },
3432
- kind: { const: "federated-upstream" },
3433
- name: {
3434
- type: "string",
3435
- minLength: 1,
3436
- maxLength: 200
3437
- },
3438
- config: {
3439
- type: "object",
3440
- additionalProperties: false,
3441
- required: ["discovery", "client_id"],
3442
- properties: {
3443
- discovery: {
3444
- type: "string",
3445
- pattern: "^https://"
3446
- },
3447
- client_id: {
3448
- type: "string",
3449
- minLength: 1,
3450
- maxLength: 256
3451
- },
3452
- scopes: {
3453
- type: "array",
3454
- items: {
3455
- type: "string",
3456
- minLength: 1
3457
- },
3458
- uniqueItems: true
3459
- }
3460
- }
3461
- }
3462
- }
3463
- },
3464
- {
3465
- description: "Approval cards through the Vercel Chat SDK.",
3466
- type: "object",
3467
- required: [
3468
- "kind",
3469
- "name",
3470
- "config"
3471
- ],
3472
- properties: {
3473
- id: {
3474
- type: "string",
3475
- minLength: 1,
3476
- maxLength: 256
3477
- },
3478
- kind: { const: "chat" },
3479
- name: {
3480
- type: "string",
3481
- minLength: 1,
3482
- maxLength: 200
3483
- },
3484
- config: {
3485
- type: "object",
3486
- additionalProperties: false,
3487
- required: ["platform", "workspace"],
3488
- properties: {
3489
- platform: { enum: ["slack", "teams"] },
3490
- workspace: {
3491
- type: "string",
3492
- minLength: 1,
3493
- maxLength: 256
3494
- }
3495
- }
3496
- }
3497
- }
3498
- },
3499
- {
3500
- description: "The Cloud's hosted email sender for approval and delivery messages.",
3501
- type: "object",
3502
- required: [
3503
- "kind",
3504
- "name",
3505
- "config"
3506
- ],
3507
- properties: {
3508
- id: {
3509
- type: "string",
3510
- minLength: 1,
3511
- maxLength: 256
3512
- },
3513
- kind: { const: "email" },
3514
- name: {
3515
- type: "string",
3516
- minLength: 1,
3517
- maxLength: 200
3518
- },
3519
- config: {
3520
- type: "object",
3521
- additionalProperties: false,
3522
- required: ["from"],
3523
- properties: { from: {
3524
- type: "string",
3525
- pattern: "^[^@\\s]+@[^@\\s]+$"
3526
- } }
3527
- }
3528
- }
3529
- },
3530
- {
3531
- description: "Vercel OIDC federation tokens accepted as hosted ADS callers.",
3532
- type: "object",
3533
- required: [
3534
- "kind",
3535
- "name",
3536
- "config"
3537
- ],
3538
- properties: {
3539
- id: {
3540
- type: "string",
3541
- minLength: 1,
3542
- maxLength: 256
3543
- },
3544
- kind: { const: "vercel-oidc" },
3545
- name: {
3546
- type: "string",
3547
- minLength: 1,
3548
- maxLength: 200
3549
- },
3550
- config: {
3551
- type: "object",
3552
- additionalProperties: false,
3553
- required: ["team_id"],
3554
- properties: {
3555
- team_id: {
3556
- type: "string",
3557
- minLength: 1,
3558
- maxLength: 256
3559
- },
3560
- project_id: {
3561
- type: "string",
3562
- minLength: 1,
3563
- maxLength: 256
3564
- },
3565
- environment: { enum: [
3566
- "production",
3567
- "preview",
3568
- "development"
3569
- ] }
3570
- }
3571
- }
3572
- }
3573
- }
3574
- ]
3575
- });
3576
- //#endregion
3577
3044
  //#region src/testing/cloud-api/document.ts
3045
+ var import__2020 = require__2020();
3578
3046
  const ENV = "/v1/environments/{env}";
3579
3047
  const COMPACT_JWS = String.raw`^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]*\.[A-Za-z0-9_-]+$`;
3580
3048
  const APPROVAL_REQUEST = "https://permdock.com/schemas/approval-request-v1.json";
@@ -4073,6 +3541,31 @@ const APPROVALS = {
4073
3541
  }) }
4074
3542
  };
4075
3543
  const RUNTIME = {
3544
+ "/v1/environment": { get: client({
3545
+ summary: "The environment the key belongs to; cloud() calls it when no environment is configured",
3546
+ responses: { "200": json("The key's environment", {
3547
+ type: "object",
3548
+ required: [
3549
+ "environment",
3550
+ "issuer",
3551
+ "jwks_uri"
3552
+ ],
3553
+ properties: {
3554
+ environment: {
3555
+ type: "string",
3556
+ minLength: 1
3557
+ },
3558
+ issuer: {
3559
+ type: "string",
3560
+ format: "uri"
3561
+ },
3562
+ jwks_uri: {
3563
+ type: "string",
3564
+ format: "uri"
3565
+ }
3566
+ }
3567
+ }) }
3568
+ }) },
4076
3569
  [`${ENV}/decisions`]: { post: client({
4077
3570
  summary: "sink.write and sink.flush",
4078
3571
  requestBody: body({
@@ -4563,7 +4056,8 @@ const bearer = (description) => ({
4563
4056
  });
4564
4057
  /**
4565
4058
  * `schemas/cloud-api-v1.json`: the PermDock Cloud HTTP API as an OpenAPI 3.1
4566
- * document. The route tables in `adapters/cloud.mdx` and `docs:drift` keep it whole.
4059
+ * document. The route tables in `adapters/cloud.mdx` and `docs:drift` keep it
4060
+ * whole.
4567
4061
  */
4568
4062
  const CLOUD_API_DOCUMENT = freezeDeep({
4569
4063
  openapi: "3.1.1",
@@ -4666,7 +4160,10 @@ function routesOf(paths) {
4666
4160
  function pointer(parts) {
4667
4161
  return parts.map((part) => part.replaceAll("~", "~0").replaceAll("/", "~1")).join("/");
4668
4162
  }
4669
- /** A checker for responses of the PermDock Cloud API, compiled once from `CLOUD_API_DOCUMENT`. */
4163
+ /**
4164
+ * A checker for responses of the PermDock Cloud API, compiled once from
4165
+ * `CLOUD_API_DOCUMENT`.
4166
+ */
4670
4167
  function cloudApiValidator() {
4671
4168
  const ajv = new import__2020.Ajv2020({
4672
4169
  strict: false,
@@ -4776,28 +4273,26 @@ async function problemCode(response) {
4776
4273
  * Runs the conformance runners and the HTTP contract checks against one
4777
4274
  * PermDock Cloud environment, and validates every response body against
4778
4275
  * `cloud-api-v1.json`. Point it at a test environment: it creates approvals,
4779
- * sends decision events and deliveries to `.invalid` addresses, and pushes
4780
- * the catalog the environment already holds.
4276
+ * sends decision events and deliveries to `.invalid` addresses, and pushes the
4277
+ * catalog the environment already holds.
4781
4278
  */
4782
4279
  function testCloudApi(baseUrl, keys, options) {
4783
4280
  const fetchFn = options.fetch ?? globalThis.fetch.bind(globalThis);
4784
4281
  const url = trimSlash(baseUrl);
4785
4282
  const prefix = trimSlash(new URL(url).pathname);
4283
+ const environment = options.environment ?? "production";
4786
4284
  const { issuer } = cloudEndpoints({
4787
4285
  url,
4788
- ...options.environment === void 0 ? {} : { environment: options.environment }
4286
+ environment
4789
4287
  });
4790
4288
  const check = cloudApiValidator();
4791
- const problems = [];
4792
4289
  const checks = [];
4793
4290
  const recording = async (input, init) => {
4794
4291
  const request = new Request(input, init);
4795
4292
  const response = await fetchFn(request);
4796
4293
  const { pathname } = new URL(request.url);
4797
4294
  const path = pathname === `${METADATA}${prefix}` ? METADATA : pathname.slice(prefix.length);
4798
- checks.push(check(request.method, path, response.clone()).then((found) => {
4799
- problems.push(...found);
4800
- }));
4295
+ checks.push(check(request.method, path, response.clone()));
4801
4296
  return response;
4802
4297
  };
4803
4298
  const client = cloud({
@@ -4805,7 +4300,7 @@ function testCloudApi(baseUrl, keys, options) {
4805
4300
  key: keys.client,
4806
4301
  fetch: recording,
4807
4302
  approver: options.approver,
4808
- ...options.environment === void 0 ? {} : { environment: options.environment },
4303
+ environment,
4809
4304
  ...options.verifier === void 0 ? {} : { verifier: options.verifier }
4810
4305
  });
4811
4306
  const call = (path, key, init = {}) => {
@@ -4851,21 +4346,33 @@ function testCloudApi(baseUrl, keys, options) {
4851
4346
  origin: issuer,
4852
4347
  ...options.authzen.headers === void 0 ? {} : { headers: options.authzen.headers }
4853
4348
  });
4854
- if (options.platform !== void 0) testCloudPlatform(url, options.environment ?? "production", options.platform, recording);
4349
+ if (options.platform !== void 0) testCloudPlatform(url, environment, options.platform, recording);
4855
4350
  it("answers 401 Problem Details without a key", async () => {
4856
4351
  const response = await call("/approvals", null);
4857
4352
  expect(response.status).toBe(401);
4858
4353
  expect(response.headers.get("content-type")).toMatch(/^application\/problem\+json/u);
4859
4354
  });
4355
+ it("names the client key's environment at GET /v1/environment", async () => {
4356
+ const response = await recording(`${url}/v1/environment`, { headers: { authorization: `Bearer ${keys.client}` } });
4357
+ expect(response.status).toBe(200);
4358
+ expect(await response.json()).toMatchObject({
4359
+ environment,
4360
+ issuer,
4361
+ jwks_uri: `${issuer}/.well-known/jwks.json`
4362
+ });
4363
+ const { status } = await recording(`${url}/v1/environment`);
4364
+ expect(status).toBe(401);
4365
+ });
4860
4366
  it("answers 403 to a key of another kind", async () => {
4861
- expect((await call("/hosted-grants", keys.client)).status).toBe(403);
4367
+ const { status } = await call("/hosted-grants", keys.client);
4368
+ expect(status).toBe(403);
4862
4369
  if (keys.export !== void 0) expect((await call("/approvals", keys.export)).status).toBe(403);
4863
4370
  });
4864
4371
  it("sends an ETag on the approval read and refuses an approver token with a stale one", async () => {
4865
4372
  const token = tag();
4866
4373
  await client.approvals.create(sample(token));
4867
4374
  const { request, etag } = await current(token);
4868
- expect(etag).toEqual(expect.any(String));
4375
+ expect(etag).toStrictEqual(expect.any(String));
4869
4376
  const stale = await resolveRaw(token, {
4870
4377
  status: "approved",
4871
4378
  by: admin
@@ -4926,7 +4433,7 @@ function testCloudApi(baseUrl, keys, options) {
4926
4433
  aud: issuer
4927
4434
  })
4928
4435
  });
4929
- expect([typ, response.status]).toEqual([typ, 422]);
4436
+ expect([typ, response.status]).toStrictEqual([typ, 422]);
4930
4437
  }
4931
4438
  });
4932
4439
  it("repeats a delivery for a repeated Idempotency-Key and requires one", async () => {
@@ -4954,7 +4461,8 @@ function testCloudApi(baseUrl, keys, options) {
4954
4461
  expect(first.ok && second.ok).toBe(true);
4955
4462
  const [a, b] = await Promise.all([first.json(), second.json()]);
4956
4463
  expect(isRecord(a) && isRecord(b) && a["id"] === b["id"]).toBe(true);
4957
- expect((await send({})).status).toBe(400);
4464
+ const { status } = await send({});
4465
+ expect(status).toBe(400);
4958
4466
  });
4959
4467
  it("answers unchanged for the catalog it already holds", async (context) => {
4960
4468
  const response = await call("/catalog", keys.client);
@@ -4991,12 +4499,12 @@ function testCloudApi(baseUrl, keys, options) {
4991
4499
  const first = await get({ "permdock-subject": token });
4992
4500
  expect(first.status).toBe(200);
4993
4501
  expect(first.headers.get("content-type")).toMatch(/^application\/jwt/u);
4994
- expect((await get({ "permdock-subject": token })).status).toBe(401);
4502
+ const { status } = await get({ "permdock-subject": token });
4503
+ expect(status).toBe(401);
4995
4504
  expect((await get({})).status).toBe(401);
4996
4505
  });
4997
4506
  it("answers every request in the shapes cloud-api-v1.json documents", async () => {
4998
- await Promise.all(checks);
4999
- expect(problems).toEqual([]);
4507
+ expect((await Promise.all(checks)).flat()).toStrictEqual([]);
5000
4508
  });
5001
4509
  }
5002
4510
  //#endregion
@@ -5157,10 +4665,10 @@ const WHERE_SUBJECT = {
5157
4665
  context: {}
5158
4666
  };
5159
4667
  /**
5160
- * `matches` answers whether the compiled output selects `row`, for example
5161
- * by running it against a table holding only that row. With it, every case
5162
- * must select the rows the in-memory evaluator keeps, including rows whose
5163
- * fields are NULL.
4668
+ * `matches` answers whether the compiled output selects `row`, for example by
4669
+ * running it against a table holding only that row. With it, every case must
4670
+ * select the rows the in-memory evaluator keeps, including rows whose fields
4671
+ * are NULL.
5164
4672
  */
5165
4673
  function testWhereCompiler(compiler, options) {
5166
4674
  const { matches } = options;
@@ -5169,7 +4677,7 @@ function testWhereCompiler(compiler, options) {
5169
4677
  const compiled = compiler(condition, options.target);
5170
4678
  const selected = [];
5171
4679
  for (const row of WHERE_ROWS) if (await matches(compiled, row)) selected.push(row["id"]);
5172
- expect(selected).toEqual(WHERE_ROWS.filter((row) => evaluateCondition(condition, row, WHERE_SUBJECT)).map((row) => row["id"]));
4680
+ expect(selected).toStrictEqual(WHERE_ROWS.filter((row) => evaluateCondition(condition, row, WHERE_SUBJECT)).map((row) => row["id"]));
5173
4681
  });
5174
4682
  it("fails closed on an empty allow set", () => {
5175
4683
  const compiled = compiler({
@@ -5237,7 +4745,7 @@ function testReplayStore(store) {
5237
4745
  }
5238
4746
  const expiresAt = nowSeconds() + 60;
5239
4747
  const results = await Promise.all([store.claim("claim-key", expiresAt), store.claim("claim-key", expiresAt)]);
5240
- expect(results.toSorted()).toEqual([false, true]);
4748
+ expect(results.toSorted()).toStrictEqual([false, true]);
5241
4749
  expect(await store.seen("claim-key")).toBe(true);
5242
4750
  await store.release("claim-key");
5243
4751
  expect(await store.claim("claim-key", expiresAt)).toBe(true);
@@ -5248,18 +4756,18 @@ function testSetQueue(queue) {
5248
4756
  await queue.enqueue("stream-a", "a1", "set-a1");
5249
4757
  await queue.enqueue("stream-a", "a2", "set-a2");
5250
4758
  await queue.enqueue("stream-b", "b1", "set-b1");
5251
- expect(await queue.pending("stream-a", 10)).toEqual([{
4759
+ expect(await queue.pending("stream-a", 10)).toStrictEqual([{
5252
4760
  jti: "a1",
5253
4761
  set: "set-a1"
5254
4762
  }, {
5255
4763
  jti: "a2",
5256
4764
  set: "set-a2"
5257
4765
  }]);
5258
- expect(await queue.pending("stream-a", 1)).toEqual([{
4766
+ expect(await queue.pending("stream-a", 1)).toStrictEqual([{
5259
4767
  jti: "a1",
5260
4768
  set: "set-a1"
5261
4769
  }]);
5262
- expect(await queue.pending("stream-b", 10)).toEqual([{
4770
+ expect(await queue.pending("stream-b", 10)).toStrictEqual([{
5263
4771
  jti: "b1",
5264
4772
  set: "set-b1"
5265
4773
  }]);
@@ -5274,12 +4782,76 @@ function testSetQueue(queue) {
5274
4782
  description: "unknown kid"
5275
4783
  });
5276
4784
  await queue.ack("stream-other", "c3");
5277
- expect(await queue.pending("stream-c", 10)).toEqual([{
4785
+ expect(await queue.pending("stream-c", 10)).toStrictEqual([{
5278
4786
  jti: "c3",
5279
4787
  set: "set-c3"
5280
4788
  }]);
5281
4789
  });
5282
4790
  }
4791
+ function streamConfiguration(id, description) {
4792
+ return {
4793
+ stream_id: id,
4794
+ iss: "https://transmitter.example",
4795
+ aud: "receiver",
4796
+ events_requested: [],
4797
+ delivery: { method: "urn:ietf:rfc:8936" },
4798
+ ...description === void 0 ? {} : { description }
4799
+ };
4800
+ }
4801
+ function testSsfStreamStore(store) {
4802
+ const id = (suffix) => `stream-${suffix}-${crypto.randomUUID()}`;
4803
+ it("creates an enabled stream and reads it back by id and by owner", async () => {
4804
+ const first = id("a");
4805
+ const second = id("b");
4806
+ const owner = `owner-${crypto.randomUUID()}`;
4807
+ await store.create(owner, streamConfiguration(first));
4808
+ await store.create(owner, streamConfiguration(second));
4809
+ await store.create("someone-else", streamConfiguration(id("c")));
4810
+ expect(await store.get(first)).toStrictEqual({
4811
+ owner,
4812
+ configuration: streamConfiguration(first)
4813
+ });
4814
+ expect(await store.status(first)).toStrictEqual({
4815
+ stream_id: first,
4816
+ status: "enabled"
4817
+ });
4818
+ const listed = await store.list(owner);
4819
+ expect(listed.map((stream) => stream.configuration.stream_id)).toStrictEqual([first, second]);
4820
+ const all = await store.list();
4821
+ expect(all.map((stream) => stream.configuration.stream_id)).toStrictEqual(expect.arrayContaining([first, second]));
4822
+ });
4823
+ it("answers null for a stream that does not exist", async () => {
4824
+ expect(await store.get(id("missing"))).toBeNull();
4825
+ expect(await store.status(id("missing"))).toBeNull();
4826
+ });
4827
+ it("replaces the configuration and keeps the owner and status", async () => {
4828
+ const stream = id("update");
4829
+ await store.create("owner-update", streamConfiguration(stream));
4830
+ await store.setStatus({
4831
+ stream_id: stream,
4832
+ status: "paused",
4833
+ reason: "maintenance"
4834
+ });
4835
+ await store.update(streamConfiguration(stream, "renamed"));
4836
+ expect(await store.get(stream)).toStrictEqual({
4837
+ owner: "owner-update",
4838
+ configuration: streamConfiguration(stream, "renamed")
4839
+ });
4840
+ expect(await store.status(stream)).toStrictEqual({
4841
+ stream_id: stream,
4842
+ status: "paused",
4843
+ reason: "maintenance"
4844
+ });
4845
+ });
4846
+ it("forgets a deleted stream and its status", async () => {
4847
+ const stream = id("delete");
4848
+ await store.create("owner-delete", streamConfiguration(stream));
4849
+ await store.delete(stream);
4850
+ expect(await store.get(stream)).toBeNull();
4851
+ expect(await store.status(stream)).toBeNull();
4852
+ expect((await store.list("owner-delete")).some((entry) => entry.configuration.stream_id === stream)).toBe(false);
4853
+ });
4854
+ }
5283
4855
  function testRevocationFeed(feed) {
5284
4856
  it("delivers each event to every subscriber until it unsubscribes", async () => {
5285
4857
  const first = [];
@@ -5297,8 +4869,8 @@ function testRevocationFeed(feed) {
5297
4869
  };
5298
4870
  await feed.revoke(revoked);
5299
4871
  await vi.waitFor(() => {
5300
- expect(first).toEqual([revoked]);
5301
- expect(second).toEqual([revoked]);
4872
+ expect(first).toStrictEqual([revoked]);
4873
+ expect(second).toStrictEqual([revoked]);
5302
4874
  });
5303
4875
  stopFirst();
5304
4876
  const changed = {
@@ -5308,9 +4880,9 @@ function testRevocationFeed(feed) {
5308
4880
  };
5309
4881
  await feed.revoke(changed);
5310
4882
  await vi.waitFor(() => {
5311
- expect(second).toEqual([revoked, changed]);
4883
+ expect(second).toStrictEqual([revoked, changed]);
5312
4884
  });
5313
- expect(first).toEqual([revoked]);
4885
+ expect(first).toStrictEqual([revoked]);
5314
4886
  stopSecond();
5315
4887
  });
5316
4888
  it("keeps delivering when a listener throws", async () => {
@@ -5327,7 +4899,7 @@ function testRevocationFeed(feed) {
5327
4899
  };
5328
4900
  await feed.revoke(event);
5329
4901
  await vi.waitFor(() => {
5330
- expect(seen).toEqual([event]);
4902
+ expect(seen).toStrictEqual([event]);
5331
4903
  });
5332
4904
  stopThrowing();
5333
4905
  stop();
@@ -5346,7 +4918,7 @@ function testRevocationFeed(feed) {
5346
4918
  }]) await expect((async () => {
5347
4919
  await feed.revoke(event);
5348
4920
  })()).rejects.toThrow(TypeError);
5349
- expect(seen).toEqual([]);
4921
+ expect(seen).toStrictEqual([]);
5350
4922
  stop();
5351
4923
  });
5352
4924
  }
@@ -5421,8 +4993,8 @@ function flipLast(value) {
5421
4993
  }
5422
4994
  /**
5423
4995
  * `key` is a live key the verifier knows, or a function issuing one (called
5424
- * once). `revoke`, when given, revokes it; the runner then expects the key
5425
- * to stop verifying.
4996
+ * once). `revoke`, when given, revokes it; the runner then expects the key to
4997
+ * stop verifying.
5426
4998
  */
5427
4999
  function testCredentialVerifier(verifier, options) {
5428
5000
  let issued;
@@ -5438,7 +5010,7 @@ function testCredentialVerifier(verifier, options) {
5438
5010
  const credential = parseCredential(await verifier.verify(key));
5439
5011
  expect(credential).toBeDefined();
5440
5012
  expect(credential?.id).toBe(parts?.id);
5441
- expect(parseCredential(await verifier.verify(key))).toEqual(credential);
5013
+ expect(parseCredential(await verifier.verify(key))).toStrictEqual(credential);
5442
5014
  });
5443
5015
  it("never throws and answers null for every other key", async () => {
5444
5016
  const key = await live();
@@ -5468,7 +5040,7 @@ function testCredentialVerifier(verifier, options) {
5468
5040
  const id = parseApiKey(key)?.id ?? "";
5469
5041
  await verifier.touch?.(id, { epochMilliseconds: nowMillis() });
5470
5042
  await verifier.touch?.("unknown-id", { epochMilliseconds: nowMillis() });
5471
- expect(parseCredential(await verifier.verify(key))).toEqual(before);
5043
+ expect(parseCredential(await verifier.verify(key))).toStrictEqual(before);
5472
5044
  });
5473
5045
  if (options.revoke !== void 0) {
5474
5046
  const revoke = options.revoke;
@@ -5489,7 +5061,7 @@ function testTokenSigner(signer, options) {
5489
5061
  audience: "https://app.example.com"
5490
5062
  });
5491
5063
  const header = decodeHeader(token);
5492
- expect(Object.keys(header).toSorted()).toEqual([
5064
+ expect(Object.keys(header).toSorted()).toStrictEqual([
5493
5065
  "alg",
5494
5066
  "kid",
5495
5067
  "typ"
@@ -5535,7 +5107,7 @@ function testDecisionProvider(provider, options) {
5535
5107
  local: LOCAL
5536
5108
  });
5537
5109
  it("names itself and claims the permission synchronously", () => {
5538
- expect(typeof provider.name).toBe("string");
5110
+ expect(provider.name).toBeTypeOf("string");
5539
5111
  expect(provider.name.length).toBeGreaterThan(0);
5540
5112
  expect(provider.handles(options.permission)).toBe(true);
5541
5113
  });
@@ -5611,12 +5183,12 @@ function testRelationSource(source, options) {
5611
5183
  });
5612
5184
  expect(Array.isArray(full.ancestors)).toBe(true);
5613
5185
  for (const ancestor of full.ancestors) {
5614
- expect(typeof ancestor.id).toBe("string");
5186
+ expect(ancestor.id).toBeTypeOf("string");
5615
5187
  expect(ancestor.id).not.toBe("");
5616
5188
  }
5617
5189
  expect(full.ancestors.length).toBeLessThanOrEqual(32);
5618
5190
  const expected = options.expect?.ancestors?.[`${object.resource}:${object.id}`];
5619
- if (expected !== void 0) expect(full.ancestors.map((ancestor) => ancestor.id)).toEqual(expected);
5191
+ if (expected !== void 0) expect(full.ancestors.map((ancestor) => ancestor.id)).toStrictEqual(expected);
5620
5192
  if (full.ancestors.length > 1) {
5621
5193
  const short = await source.ancestors({
5622
5194
  resource: object.resource,
@@ -5624,7 +5196,7 @@ function testRelationSource(source, options) {
5624
5196
  through: "parent",
5625
5197
  depth: 1
5626
5198
  });
5627
- expect(short.ancestors.map((ancestor) => ancestor.id)).toEqual([full.ancestors[0]?.id]);
5199
+ expect(short.ancestors.map((ancestor) => ancestor.id)).toStrictEqual([full.ancestors[0]?.id]);
5628
5200
  expect(short.truncated).toBe(true);
5629
5201
  }
5630
5202
  const none = await source.ancestors({
@@ -5633,7 +5205,7 @@ function testRelationSource(source, options) {
5633
5205
  through: "parent",
5634
5206
  depth: 0
5635
5207
  });
5636
- expect(none.ancestors).toEqual([]);
5208
+ expect(none.ancestors).toStrictEqual([]);
5637
5209
  }
5638
5210
  });
5639
5211
  it("returns well-formed holders", async () => {
@@ -5651,14 +5223,14 @@ function testRelationSource(source, options) {
5651
5223
  holder.group.id,
5652
5224
  holder.group.relation
5653
5225
  ]) {
5654
- expect(typeof part).toBe("string");
5226
+ expect(part).toBeTypeOf("string");
5655
5227
  expect(part).not.toBe("");
5656
5228
  }
5657
- else expect(typeof holder.principal.id).toBe("string");
5229
+ else expect(holder.principal.id).toBeTypeOf("string");
5658
5230
  for (const instant of [holder.startsAt, holder.expiresAt]) expect(instant === void 0 || typeof instant === "number" && Number.isFinite(instant)).toBe(true);
5659
5231
  }
5660
5232
  const expected = options.expect?.holders?.[`${object.resource}:${object.id}#${object.relation}`];
5661
- if (expected !== void 0) expect(holders.map(holderKey).toSorted()).toEqual([...expected].toSorted());
5233
+ if (expected !== void 0) expect(holders.map(holderKey).toSorted()).toStrictEqual([...expected].toSorted());
5662
5234
  }
5663
5235
  });
5664
5236
  it("follows each link to exactly one instance", async () => {
@@ -5672,7 +5244,7 @@ function testRelationSource(source, options) {
5672
5244
  through,
5673
5245
  depth: 1
5674
5246
  });
5675
- expect(linked.ancestors.map((ancestor) => ancestor.id)).toEqual([id]);
5247
+ expect(linked.ancestors.map((ancestor) => ancestor.id)).toStrictEqual([id]);
5676
5248
  }
5677
5249
  });
5678
5250
  it("reads each object's row as an object, or null when there is none", async () => {
@@ -5702,11 +5274,11 @@ function testRelationSource(source, options) {
5702
5274
  ...missing,
5703
5275
  through: "parent",
5704
5276
  depth: 16
5705
- })).ancestors).toEqual([]);
5277
+ })).ancestors).toStrictEqual([]);
5706
5278
  if (first.relation !== void 0) expect(await source.related({
5707
5279
  ...missing,
5708
5280
  relation: first.relation
5709
- })).toEqual([]);
5281
+ })).toStrictEqual([]);
5710
5282
  });
5711
5283
  }
5712
5284
  //#endregion
@@ -5718,8 +5290,8 @@ function isFrozenDeep(value) {
5718
5290
  /**
5719
5291
  * Checks a `PrincipalSource`: `principal` answers each listed id with the
5720
5292
  * expected principal or `null`, never another id; `principalByIdentity`
5721
- * resolves each listed identity to the expected principal id or `null`, and
5722
- * an email matches trimmed and in any case.
5293
+ * resolves each listed identity to the expected principal id or `null`, and an
5294
+ * email matches trimmed and in any case.
5723
5295
  */
5724
5296
  function testPrincipalSource(source, options) {
5725
5297
  it("returns the principal with the asked id, or null", async () => {
@@ -5731,8 +5303,8 @@ function testPrincipalSource(source, options) {
5731
5303
  found = null;
5732
5304
  }
5733
5305
  expect(found === null || typeof found === "object").toBe(true);
5734
- if (found !== null) expect(await loadPrincipal(source, entry.id, entry.tenant)).toEqual(found);
5735
- if (entry.expect !== void 0) expect(found).toEqual(entry.expect);
5306
+ if (found !== null) expect(await loadPrincipal(source, entry.id, entry.tenant)).toStrictEqual(found);
5307
+ if (entry.expect !== void 0) expect(found).toStrictEqual(entry.expect);
5736
5308
  }
5737
5309
  });
5738
5310
  it("returns principals a caller cannot change", async () => {
@@ -5775,10 +5347,10 @@ async function automationOf(source, id, tenant) {
5775
5347
  }
5776
5348
  }
5777
5349
  /**
5778
- * Checks an `AutomationSource`: `automation(id)` answers each listed id
5779
- * with a valid Automation v1 record of that id or `null`, never another id;
5780
- * a record held to one tenant is `null` when asked for another; an empty or
5781
- * unknown id is `null`.
5350
+ * Checks an `AutomationSource`: `automation(id)` answers each listed id with a
5351
+ * valid Automation v1 record of that id or `null`, never another id; a record
5352
+ * held to one tenant is `null` when asked for another; an empty or unknown id
5353
+ * is `null`.
5782
5354
  */
5783
5355
  function testAutomationSource(source, options) {
5784
5356
  it("returns a valid v1 record with the asked id, or null", async () => {
@@ -5786,10 +5358,10 @@ function testAutomationSource(source, options) {
5786
5358
  const found = await automationOf(source, entry.id, entry.tenant);
5787
5359
  if (found !== null) {
5788
5360
  const parsed = parseAutomation(found);
5789
- expect(parsed).toEqual(found);
5361
+ expect(parsed).toStrictEqual(found);
5790
5362
  expect(parsed?.id).toBe(entry.id);
5791
5363
  }
5792
- if (entry.expect !== void 0) expect(found).toEqual(entry.expect);
5364
+ if (entry.expect !== void 0) expect(found).toStrictEqual(entry.expect);
5793
5365
  }
5794
5366
  });
5795
5367
  it("refuses a record held to another tenant", async () => {
@@ -5812,9 +5384,9 @@ async function sessionOf(source, id) {
5812
5384
  }
5813
5385
  }
5814
5386
  /**
5815
- * Checks a `SupportSessionSource`: `session(id)` answers each listed id
5816
- * with a record of that id or `null`, never another id; `ended` is a
5817
- * boolean when present; an empty or unknown id is `null`.
5387
+ * Checks a `SupportSessionSource`: `session(id)` answers each listed id with a
5388
+ * record of that id or `null`, never another id; `ended` is a boolean when
5389
+ * present; an empty or unknown id is `null`.
5818
5390
  */
5819
5391
  function testSupportSessionSource(source, options) {
5820
5392
  it("returns the session with the asked id, or null", async () => {
@@ -5839,7 +5411,10 @@ function testSupportSessionSource(source, options) {
5839
5411
  });
5840
5412
  }
5841
5413
  const SETTLED = Symbol("settled");
5842
- /** Whether `promise` settles within `ms` milliseconds; a rejection counts as settled. */
5414
+ /**
5415
+ * Whether `promise` settles within `ms` milliseconds; a rejection counts as
5416
+ * settled.
5417
+ */
5843
5418
  async function settlesWithin(promise, ms) {
5844
5419
  let timer;
5845
5420
  const timeout = new Promise((resolve) => {
@@ -5854,12 +5429,12 @@ async function settlesWithin(promise, ms) {
5854
5429
  }
5855
5430
  }
5856
5431
  /**
5857
- * Checks an `ApprovalWaiter`: a wait ends when its token is woken and not
5858
- * when another token is; waking a token nobody waits on resolves; a wait
5859
- * whose `expiresAt` has passed ends; an aborted `signal` rejects the wait with its reason.
5860
- * Pass `expiry: false` or `abort: false` for a waiter whose runner offers no
5861
- * timer or no cancellation, `pending` when the waiter reads a store, and
5862
- * `parked` when a wait parks asynchronously.
5432
+ * Checks an `ApprovalWaiter`: a wait ends when its token is woken and not when
5433
+ * another token is; waking a token nobody waits on resolves; a wait whose
5434
+ * `expiresAt` has passed ends; an aborted `signal` rejects the wait with its
5435
+ * reason. Pass `expiry: false` or `abort: false` for a waiter whose runner
5436
+ * offers no timer or no cancellation, `pending` when the waiter reads a store,
5437
+ * and `parked` when a wait parks asynchronously.
5863
5438
  */
5864
5439
  function testApprovalWaiter(waiter, options = {}) {
5865
5440
  const timeout = options.timeout ?? 1e3;
@@ -5921,9 +5496,9 @@ function key(suffix) {
5921
5496
  }
5922
5497
  /**
5923
5498
  * Conformance for a `DeliveryTransport`: it sends a checked target with its
5924
- * idempotency key, keeps the key on a retry, refuses a target without a
5925
- * granted decision before calling the provider, and maps provider errors to
5926
- * retryable or final failures without rejecting.
5499
+ * idempotency key, keeps the key on a retry, refuses a target without a granted
5500
+ * decision before calling the provider, and maps provider errors to retryable
5501
+ * or final failures without rejecting.
5927
5502
  */
5928
5503
  function testDeliveryTransport(name, options) {
5929
5504
  const targetWith = async (decision) => ({
@@ -5937,14 +5512,14 @@ function testDeliveryTransport(name, options) {
5937
5512
  it("names its channel and provider", () => {
5938
5513
  const { transport } = options.create();
5939
5514
  expect(transport.channel).toBe(options.channel);
5940
- expect(typeof transport.name).toBe("string");
5515
+ expect(transport.name).toBeTypeOf("string");
5941
5516
  expect(transport.name).not.toBe("");
5942
5517
  });
5943
5518
  it("sends a checked target with its idempotency key", async () => {
5944
5519
  const harness = options.create();
5945
5520
  const result = await send(harness, await targetWith(await grantedDecision()), "a");
5946
5521
  expect(result.status).toBe("sent");
5947
- expect(await harness.received()).toEqual([{ idempotencyKey: "pdd_runner_a" }]);
5522
+ expect(await harness.received()).toStrictEqual([{ idempotencyKey: "pdd_runner_a" }]);
5948
5523
  });
5949
5524
  it("passes the same key on a retry of the same delivery", async () => {
5950
5525
  const harness = options.create();
@@ -5970,7 +5545,7 @@ function testDeliveryTransport(name, options) {
5970
5545
  cause: "invalid-recipient",
5971
5546
  retryable: false
5972
5547
  });
5973
- expect(await harness.received()).toEqual([]);
5548
+ expect(await harness.received()).toStrictEqual([]);
5974
5549
  });
5975
5550
  it("maps a retryable provider error to a retryable failure", async () => {
5976
5551
  const harness = options.create();
@@ -6001,7 +5576,7 @@ function testDeliveryTransport(name, options) {
6001
5576
  status: "failed",
6002
5577
  retryable: true
6003
5578
  });
6004
- expect(await harness.received()).toEqual([]);
5579
+ expect(await harness.received()).toStrictEqual([]);
6005
5580
  });
6006
5581
  });
6007
5582
  }
@@ -6039,7 +5614,7 @@ function testMembershipSource(source, options) {
6039
5614
  if (options.policy !== void 0) expect(normalizeMemberships([membership], scopeList(options.policy.scopes))).toHaveLength(1);
6040
5615
  }
6041
5616
  const expected = options.expect?.[principal.id];
6042
- if (expected !== void 0) expect(memberships).toEqual(expected);
5617
+ if (expected !== void 0) expect(memberships).toStrictEqual(expected);
6043
5618
  }
6044
5619
  });
6045
5620
  if (source.list !== void 0) it("lists every member of an instance it returns a membership for", async () => {
@@ -6071,9 +5646,9 @@ function testEntitlementSource(source, options) {
6071
5646
  it("returns plan names for a tenant and none without one", async () => {
6072
5647
  const found = await source.entitlementsFor(options.principal, { tenant: options.tenant });
6073
5648
  expect(Array.isArray(found)).toBe(true);
6074
- for (const name of found) expect(typeof name).toBe("string");
6075
- if (options.expect !== void 0) expect([...found].toSorted()).toEqual([...options.expect].toSorted());
6076
- expect(await source.entitlementsFor(options.principal, {})).toEqual([]);
5649
+ for (const name of found) expect(name).toBeTypeOf("string");
5650
+ if (options.expect !== void 0) expect([...found].toSorted()).toStrictEqual([...options.expect].toSorted());
5651
+ expect(await source.entitlementsFor(options.principal, {})).toStrictEqual([]);
6077
5652
  });
6078
5653
  }
6079
5654
  function testCustomRoleSource(source, options) {
@@ -6083,7 +5658,7 @@ function testCustomRoleSource(source, options) {
6083
5658
  for (const role of roles) {
6084
5659
  expect(role.tenant).toBe(options.tenant);
6085
5660
  for (const included of role.includes ?? []) expect(declared).toContain(included);
6086
- if (options.policy !== void 0) expect(validateCustomRole(options.policy, role).dropped).toEqual([]);
5661
+ if (options.policy !== void 0) expect(validateCustomRole(options.policy, role).dropped).toStrictEqual([]);
6087
5662
  }
6088
5663
  if (source.assignable !== void 0) {
6089
5664
  const assignable = await source.assignable(options.tenant);
@@ -6093,7 +5668,7 @@ function testCustomRoleSource(source, options) {
6093
5668
  const every = options.every;
6094
5669
  if (every !== void 0) it("returns every custom role of the tenant, held or not", async () => {
6095
5670
  const roles = await source.rolesFor(options.tenant, { held: [] });
6096
- expect([...new Set(roles.map((role) => role.name))].toSorted()).toEqual([...new Set(every)].toSorted());
5671
+ expect([...new Set(roles.map((role) => role.name))].toSorted()).toStrictEqual([...new Set(every)].toSorted());
6097
5672
  });
6098
5673
  }
6099
5674
  function testDirectoryStore(store, options) {
@@ -6123,7 +5698,7 @@ function testDirectoryStore(store, options) {
6123
5698
  created: "",
6124
5699
  lastModified: ""
6125
5700
  }
6126
- })).rejects.toThrow(/userName/);
5701
+ })).rejects.toThrow(/userName/u);
6127
5702
  const group = await store.putGroup(home, {
6128
5703
  id: "g_editors",
6129
5704
  displayName: "Editors",
@@ -6134,11 +5709,11 @@ function testDirectoryStore(store, options) {
6134
5709
  lastModified: ""
6135
5710
  }
6136
5711
  });
6137
- expect(await store.groupsFor(home, created.id)).toEqual([expect.objectContaining({
5712
+ expect(await store.groupsFor(home, created.id)).toStrictEqual([expect.objectContaining({
6138
5713
  id: group.id,
6139
5714
  displayName: "Editors"
6140
5715
  })]);
6141
- expect(await store.groupsFor(other, created.id)).toEqual([]);
5716
+ expect(await store.groupsFor(other, created.id)).toStrictEqual([]);
6142
5717
  const patched = await store.patchUser(home, created.id, [{
6143
5718
  op: "replace",
6144
5719
  path: "active",
@@ -6146,13 +5721,13 @@ function testDirectoryStore(store, options) {
6146
5721
  }]);
6147
5722
  expect(patched.active).toBe(false);
6148
5723
  const source = directoryMembershipSource(store, { assignable: ["editor"] });
6149
- expect(await source.membershipsFor({ id: "00u1" }, { tenant: home })).toEqual([]);
5724
+ expect(await source.membershipsFor({ id: "00u1" }, { tenant: home })).toStrictEqual([]);
6150
5725
  await store.patchUser(home, created.id, [{
6151
5726
  op: "replace",
6152
5727
  path: "active",
6153
5728
  value: true
6154
5729
  }]);
6155
- expect(await source.membershipsFor({ id: "00u1" }, { tenant: home })).toEqual([{
5730
+ expect(await source.membershipsFor({ id: "00u1" }, { tenant: home })).toStrictEqual([{
6156
5731
  tenant: home,
6157
5732
  roles: ["editor"],
6158
5733
  via: `group:${group.id}`,
@@ -6163,7 +5738,7 @@ function testDirectoryStore(store, options) {
6163
5738
  path: "members",
6164
5739
  value: [{ value: created.id }]
6165
5740
  }]);
6166
- expect(await store.groupsFor(home, created.id)).toEqual([]);
5741
+ expect(await store.groupsFor(home, created.id)).toStrictEqual([]);
6167
5742
  });
6168
5743
  it("ends sessions when scimHandler deactivates a user in this store", async () => {
6169
5744
  const feed = memoryRevocationFeed();
@@ -6216,7 +5791,7 @@ function testDirectoryStore(store, options) {
6216
5791
  kind: "session-revoked"
6217
5792
  });
6218
5793
  expect(seen.every((event) => event.kind === "session-revoked")).toBe(true);
6219
- expect(kinds).toEqual(["changed", "session-revoked"]);
5794
+ expect(kinds).toStrictEqual(["changed", "session-revoked"]);
6220
5795
  });
6221
5796
  }
6222
5797
  /** Settings for `tenant`, or `undefined`; a thrown source fails the runner. */
@@ -6224,13 +5799,13 @@ function testSettingsSource(source, options) {
6224
5799
  it("answers per tenant with plain settings and nothing for an unknown tenant", async () => {
6225
5800
  const settings = await source.settingsFor(options.tenant);
6226
5801
  if (settings !== void 0) {
6227
- expect(typeof settings).toBe("object");
5802
+ expect(settings).toBeTypeOf("object");
6228
5803
  const credentials = settings.credentials;
6229
5804
  if (credentials !== void 0) {
6230
5805
  expect(credentials.maxTtl === void 0 || Number.isFinite(credentials.maxTtl) && credentials.maxTtl >= 0).toBe(true);
6231
5806
  expect(credentials.kinds === void 0 || credentials.kinds.every((kind) => kind === "user" || kind === "service" || kind === "feed")).toBe(true);
6232
5807
  }
6233
- expect(JSON.parse(JSON.stringify(settings))).toEqual(settings);
5808
+ expect(JSON.parse(JSON.stringify(settings))).toStrictEqual(settings);
6234
5809
  }
6235
5810
  expect(await source.settingsFor(options.unknown ?? "__permdock_unknown__")).toBeUndefined();
6236
5811
  });
@@ -6665,8 +6240,8 @@ const supabaseClaimVectors = {
6665
6240
  /**
6666
6241
  * The `permdock supabase inspect --json` manifest for a policy with one
6667
6242
  * `tenant` scope, the default `supabase.hook` and a `features` claim from
6668
- * `public.feature_claims`. A package that reads the manifest tests its
6669
- * parser against this value.
6243
+ * `public.feature_claims`. A package that reads the manifest tests its parser
6244
+ * against this value.
6670
6245
  */
6671
6246
  const supabaseHookManifestFixture = {
6672
6247
  $schema: "https://permdock.com/schemas/supabase-manifest-v1.json",
@@ -6957,4 +6532,4 @@ const PROBLEM_EXAMPLES = Object.freeze({
6957
6532
  }
6958
6533
  });
6959
6534
  //#endregion
6960
- export { AGENT_THROWING_ROW, AGENT_THROWING_USER, PROBLEM_EXAMPLES, authzenTodoData, authzenTodoPermissions, authzenTodoPolicy, authzenTodoUsers, authzenTodoVectors, describePolicy, expectTypeOf, jwtFixtureAudience, jwtFixtureIssuer, jwtFixtureJwks, jwtFixtureTokens, snapshotFixture, supabaseClaimFixtures, supabaseClaimVectors, supabaseHookManifestFixture, supabaseMembershipsBudget, testAccessReviewStore, testAgentAdapter, testAgentRuntime, testApprovalDelegationSource, testApprovalPolicySource, testApprovalStore, testApprovalWaiter, testAuthzen, testAutomationSource, testClientParity, testClientStore, testCloudApi, testCredentialVerifier, testCustomRoleSource, testDecisionProvider, testDecisionSink, testDeliveryTransport, testDirectoryStore, testEntitlementSource, testHttpAdapter, testIdempotencyStore, testLegacyParity, testLimitStore, testMembershipSource, testOrmParity, testPolicySource, testPrincipalSource, testRelationSource, testReplayStore, testRevocationFeed, testRlsParity, testSetQueue, testSettingsSource, testSnapshotSource, testSnapshotStorage, testSubjectResolver, testSupportSessionSource, testTokenSigner, testTokenVerifier, testWhereCompiler };
6535
+ export { AGENT_THROWING_ROW, AGENT_THROWING_USER, PROBLEM_EXAMPLES, authzenTodoData, authzenTodoPermissions, authzenTodoPolicy, authzenTodoUsers, authzenTodoVectors, describePolicy, expectTypeOf, jwtFixtureAudience, jwtFixtureIssuer, jwtFixtureJwks, jwtFixtureTokens, policyMatrix, seedRows, snapshotFixture, supabaseClaimFixtures, supabaseClaimVectors, supabaseHookManifestFixture, supabaseMembershipsBudget, testAccessReviewStore, testAgentAdapter, testAgentRuntime, testApprovalDelegationSource, testApprovalPolicySource, testApprovalStore, testApprovalWaiter, testAuthzen, testAutomationSource, testClientParity, testClientStore, testCloudApi, testCredentialVerifier, testCustomRoleSource, testDecisionProvider, testDecisionSink, testDeliveryTransport, testDirectoryStore, testEntitlementSource, testHttpAdapter, testIdempotencyStore, testLegacyParity, testLimitStore, testMembershipSource, testOrmParity, testPolicySource, testPrincipalSource, testRelationSource, testReplayStore, testRevocationFeed, testRlsParity, testSetQueue, testSettingsSource, testSnapshotSource, testSnapshotStorage, testSsfStreamStore, testSubjectResolver, testSupportSessionSource, testTokenSigner, testTokenVerifier, testWhereCompiler };