permdock 0.4.0 → 0.6.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 (515) hide show
  1. package/README.md +37 -35
  2. package/dist/{schema-Bol6gqXA.js → 2020-C9VN-x8c.js} +5 -130
  3. package/dist/a2a/index.d.ts +6 -6
  4. package/dist/a2a/index.js +14 -17
  5. package/dist/{adapter-context-mlWsVGGg.js → adapter-context-CRhqyzCk.js} +1 -1
  6. package/dist/adoption-scan-DCYe60Pi.js +107 -0
  7. package/dist/agent/index.d.ts +3 -0
  8. package/dist/agent/index.js +1 -0
  9. package/dist/ai-sdk/index.d.ts +18 -35
  10. package/dist/ai-sdk/index.js +37 -23
  11. package/dist/{app-data-CV8pqqMi.js → app-data-MuyzVqX8.js} +2 -1
  12. package/dist/approval-header-CJACtHkv.js +23 -0
  13. package/dist/approval-prompt-Dg5LeAcT.js +62 -0
  14. package/dist/approval-prompt-DvSSPQJX.d.ts +57 -0
  15. package/dist/approvals/index.d.ts +5 -3
  16. package/dist/approvals/index.js +8 -5
  17. package/dist/approvals-p1PGoVjb.js +995 -0
  18. package/dist/arazzo-41WuzHou.js +36 -0
  19. package/dist/{arazzo-C4BcskbX.js → arazzo-CaUVBn7D.js} +20 -14
  20. package/dist/args-IIolxxKA.js +197 -0
  21. package/dist/astro/index.d.ts +43 -0
  22. package/dist/astro/index.js +27 -0
  23. package/dist/audience-DabClFLQ.js +55 -0
  24. package/dist/auth-DRLEizbL.js +178 -0
  25. package/dist/authzen/index.d.ts +26 -6
  26. package/dist/authzen/index.js +94 -79
  27. package/dist/automation-BNM3TtDo.d.ts +114 -0
  28. package/dist/automation-DVjOckjV.js +157 -0
  29. package/dist/better-auth/index.d.ts +8 -6
  30. package/dist/better-auth/index.js +9 -8
  31. package/dist/better-supabase/index.d.ts +357 -30
  32. package/dist/better-supabase/index.js +936 -194
  33. package/dist/{bind-kk-uyCwH.js → bind-CAvgUuKs.js} +14 -10
  34. package/dist/{bind-BzqG8SMb.d.ts → bind-DOyrv6zi.d.ts} +12 -10
  35. package/dist/body-CH5of8Ne.js +10 -0
  36. package/dist/body-text-QcXok_SI.js +52 -0
  37. package/dist/{build-instance-DIIV9DbC.js → build-instance-Bu_eDqYw.js} +4 -3
  38. package/dist/{canonical-json-CFKyFOke.js → canonical-json-BC8_nMTw.js} +5 -3
  39. package/dist/{capability-CksyCWDH.d.ts → capability-Drg-1YhL.d.ts} +44 -25
  40. package/dist/{capability-txATtKV4.js → capability-N1K9IpqS.js} +68 -42
  41. package/dist/casbin-Cr7RLtA3.js +30 -0
  42. package/dist/casbin-Dyu2gnUD.js +234 -0
  43. package/dist/casl-64gpT-8x.js +24 -0
  44. package/dist/casl-DjEDhTCP.js +299 -0
  45. package/dist/catalog/index.d.ts +74 -2
  46. package/dist/catalog/index.js +51 -2
  47. package/dist/catalog-BoWglDPI.js +1 -0
  48. package/dist/catalog-DZVbrF-z.js +52 -0
  49. package/dist/{catalog-tZUyOfOv.js → catalog-HD2gf5mY.js} +51 -6
  50. package/dist/{catalog-doc-DWtg0gGR.js → catalog-doc-Cdn7-0pQ.js} +45 -23
  51. package/dist/catalog-fingerprint-CCP0-yQS.js +153 -0
  52. package/dist/chat/index.d.ts +2 -0
  53. package/dist/chat/index.js +224 -0
  54. package/dist/chat-sdk/index.d.ts +87 -0
  55. package/dist/chat-sdk/index.js +197 -0
  56. package/dist/claims-map-7oQ3ue4X.js +13 -0
  57. package/dist/claims-map-Ca-QX8C6.d.ts +32 -0
  58. package/dist/claude-agent/index.d.ts +7 -14
  59. package/dist/claude-agent/index.js +24 -27
  60. package/dist/clerk/index.d.ts +9 -8
  61. package/dist/clerk/index.js +9 -14
  62. package/dist/cli/bin.js +36 -17
  63. package/dist/cli/index.d.ts +19 -5
  64. package/dist/cli/index.js +164 -4
  65. package/dist/cloud/index.d.ts +102 -63
  66. package/dist/cloud/index.js +239 -340
  67. package/dist/cloud-ytQ3uviA.js +87 -0
  68. package/dist/codemod-5mpSpnUY.js +37 -0
  69. package/dist/codemod-ua2hHy7w.js +2084 -0
  70. package/dist/collect-Ci6akVwH.js +55 -0
  71. package/dist/collect-D7eQDffE.js +1 -0
  72. package/dist/{collect-2SrB7lfk.js → collect-mLTY4nwb.js} +75 -11
  73. package/dist/command-L8EstFNm.js +6 -0
  74. package/dist/compile/index.d.ts +1 -1
  75. package/dist/compile/index.js +1 -2
  76. package/dist/{compile-CtTP7eur.d.ts → compile-1bsAezL0.d.ts} +5 -3
  77. package/dist/{compile-CcEu2ZQ1.js → compile-CMqMx_IN.js} +11 -8
  78. package/dist/completion-BTFrHZhX.js +143 -0
  79. package/dist/config-Bj2Ixq3d.js +26 -0
  80. package/dist/{config-BIjREpyE.js → config-bs_kZeYq.js} +12 -7
  81. package/dist/{config-report--Xb9APJS.js → config-report-DSpsLpCE.js} +15 -5
  82. package/dist/{connection-DUsczpB5.js → connection-DjhIJSs2.js} +6 -5
  83. package/dist/{context-DieZVJXu.js → context-B_u3_bDJ.js} +2 -2
  84. package/dist/convex/index.d.ts +11 -10
  85. package/dist/convex/index.js +6 -4
  86. package/dist/create-B30OMUOJ.d.ts +107 -0
  87. package/dist/create-CBMmFie-.d.ts +138 -0
  88. package/dist/create-CGm28gyI.js +130 -0
  89. package/dist/create-CQWIpUG-.js +403 -0
  90. package/dist/{create-BBO24vj6.js → create-DCF2SvoT.js} +31 -32
  91. package/dist/create-D_NmPOdH.d.ts +110 -0
  92. package/dist/{credential-kue0qpbm.js → credential-BcoiejpY.js} +152 -31
  93. package/dist/credentials-DPyHmYz5.js +74 -0
  94. package/dist/delegation-j6-rc37o.js +286 -0
  95. package/dist/delivery/index.d.ts +5 -0
  96. package/dist/delivery/index.js +5 -0
  97. package/dist/delivery-DCAgNyCn.js +499 -0
  98. package/dist/delivery-event-xX4obB8F.js +40 -0
  99. package/dist/{denied-text-DfRk15uw.d.ts → denied-text-CiFQNuMV.d.ts} +5 -5
  100. package/dist/{describe-C3ldA9os.d.ts → describe-B5qgDvEW.d.ts} +2 -2
  101. package/dist/{describe-Cgkp7eP0.js → describe-BybMRY7A.js} +6 -2
  102. package/dist/device-BARQ8eYG.js +193 -0
  103. package/dist/{diff-rpqoe7Ag.js → diff-3jbx6Rm4.js} +60 -54
  104. package/dist/diff-CmuSD3vc.js +37 -0
  105. package/dist/doctor-3ZqNHX_G.js +44 -0
  106. package/dist/{doctor-owlv6ddh.js → doctor-BdZw53-k.js} +245 -50
  107. package/dist/drizzle/index.d.ts +5 -3
  108. package/dist/drizzle/index.js +2 -2
  109. package/dist/elysia/index.d.ts +16 -13
  110. package/dist/elysia/index.js +13 -12
  111. package/dist/env-DJZrPjRi.js +8 -0
  112. package/dist/errors-BDUwg2qC.js +27 -0
  113. package/dist/{errors-drnfxYtm.js → errors-BKy0EfhN.js} +5 -9
  114. package/dist/errors-BWXBMaKK.d.ts +11 -0
  115. package/dist/errors-CWTY6uih.js +143 -0
  116. package/dist/{errors-OD_4c__H.d.ts → errors-DH5QEGXh.d.ts} +2 -2
  117. package/dist/evaluate-CNClBvcj.js +1785 -0
  118. package/dist/evaluations-C_1vdSZc.d.ts +32 -0
  119. package/dist/{evaluations-C9YJsv3H.js → evaluations-beAooV0n.js} +145 -34
  120. package/dist/eve/index.d.ts +12 -16
  121. package/dist/eve/index.js +22 -28
  122. package/dist/event-time-CsegxXxB.js +23 -0
  123. package/dist/expiry-D4_YKRKD.js +52 -0
  124. package/dist/express/index.d.ts +13 -10
  125. package/dist/express/index.js +12 -11
  126. package/dist/fastify/index.d.ts +25 -22
  127. package/dist/fastify/index.js +13 -12
  128. package/dist/fetch-adapter-DD_gfmrC.js +120 -0
  129. package/dist/fetch-adapter-Dw0MTS3D.d.ts +31 -0
  130. package/dist/fields-OYG_JR78.js +203 -0
  131. package/dist/files-C5ZI27eV.js +124 -0
  132. package/dist/{fixtures-h2eG2OZV.js → fixtures-B7XrLeY7.js} +8 -7
  133. package/dist/flags-C3DktBDY.js +86 -0
  134. package/dist/{from-snapshot-7REQZe0H.js → from-snapshot-CTCMsFZk.js} +55 -52
  135. package/dist/{grantee-conditions-MQs5NIuX.js → grantee-conditions-CcXf8hMM.js} +4 -3
  136. package/dist/{grantee-CXY-0C87.js → grantee-hxrUsbpM.js} +16 -6
  137. package/dist/{grantee-match-fxYp9Nmt.js → grantee-match-DbENBHLB.js} +5 -4
  138. package/dist/h3/index.d.ts +37 -0
  139. package/dist/h3/index.js +35 -0
  140. package/dist/headers-BIjfMaoC.js +8 -0
  141. package/dist/headers-CIbDTUTK.d.ts +7 -0
  142. package/dist/{helpers-C9YmyBIa.js → helpers-CBYVMjwh.js} +45 -38
  143. package/dist/hono/index.d.ts +12 -9
  144. package/dist/hono/index.js +13 -12
  145. package/dist/hosted-B46-5LYE.js +315 -0
  146. package/dist/hosted-BPk51-kX.d.ts +78 -0
  147. package/dist/{http-DPuDaChb.d.ts → http-B_LphE5Y.d.ts} +1 -2
  148. package/dist/http-DXJ37RDA.js +46 -0
  149. package/dist/{http-BEbdMZ05.js → http-ujKm0JXE.js} +1 -2
  150. package/dist/{import-UHk1N7vH.js → import-CNV7I2Dw.js} +25 -20
  151. package/dist/{import-BFIlhetD.js → import-wyqXUyjm.js} +190 -53
  152. package/dist/index-B8YjkEZ4.d.ts +210 -0
  153. package/dist/index-C-6Letxg.d.ts +431 -0
  154. package/dist/index-CaxrMD1A.d.ts +1 -0
  155. package/dist/{index-BwMe94Xq.d.ts → index-Ch8LpNDp.d.ts} +34 -16
  156. package/dist/index-Cp_ilKi1.d.ts +91 -0
  157. package/dist/index-CueFnxQc.d.ts +110 -0
  158. package/dist/index-DuWxc5dt.d.ts +909 -0
  159. package/dist/index.d.ts +19 -15
  160. package/dist/index.js +31 -21
  161. package/dist/init-DeMYsnYJ.js +96 -0
  162. package/dist/init-GG6Jh8HD.js +95 -0
  163. package/dist/{instance-options-plkrsAog.d.ts → instance-options-BsVpyGIk.d.ts} +2 -2
  164. package/dist/instance-snapshot-BOEhcqIF.js +601 -0
  165. package/dist/instant-BluqDvyS.js +19 -0
  166. package/dist/internals-Dk8A8fX3.js +17 -0
  167. package/dist/{json-schema-DBkdUuCO.js → json-schema-B-vAwlde.js} +45 -6
  168. package/dist/json-schema-CN_iLFH2.d.ts +40 -0
  169. package/dist/jwt/index.d.ts +18 -11
  170. package/dist/jwt/index.js +156 -135
  171. package/dist/{kernel-CB3LrIJM.js → kernel-D0NVb1cK.js} +52 -24
  172. package/dist/kysely/index.d.ts +5 -3
  173. package/dist/kysely/index.js +2 -2
  174. package/dist/load-BxEtwlBS.js +1 -0
  175. package/dist/{load-C3_oTHyO.js → load-DFS_ry0W.js} +22 -6
  176. package/dist/{load-jose-BL6LH4bL.js → load-jose-DZ4dnjri.js} +1 -4
  177. package/dist/login-Dfe5RlYk.js +68 -0
  178. package/dist/login-imhuwvn3.js +254 -0
  179. package/dist/manifest-C8IzW3Do.js +42 -0
  180. package/dist/manifest-dvL2XEe1.js +370 -0
  181. package/dist/manifest-json-CAGzls-C.js +9 -0
  182. package/dist/{manifest-uDS9y5de.d.ts → manifest-mx5ghSFM.d.ts} +9 -2
  183. package/dist/manifest-schema-C0kK_8V0.js +399 -0
  184. package/dist/{map-claims-AVlBaJoa.js → map-claims-D3oRmTk0.js} +13 -10
  185. package/dist/{map-error-BE27PYkE.js → map-error-Dr70oA_3.js} +3 -3
  186. package/dist/{markers-CiwAOZtO.js → markers-C6joDI3o.js} +2 -1
  187. package/dist/mcp/index.d.ts +22 -12
  188. package/dist/mcp/index.js +57 -39
  189. package/dist/{migrate-BwlB5-Wk.js → migrate-CIq4-E9F.js} +53 -14
  190. package/dist/nest/index.d.ts +12 -9
  191. package/dist/nest/index.js +15 -14
  192. package/dist/next/index.d.ts +18 -21
  193. package/dist/next/index.js +13 -11
  194. package/dist/next/plugin.d.ts +1 -1
  195. package/dist/next/plugin.js +2 -2
  196. package/dist/node/index.d.ts +10 -10
  197. package/dist/node/index.js +7 -9
  198. package/dist/{ocsf-uYFXGVw2.js → ocsf-BFp-PZZA.js} +22 -11
  199. package/dist/openai/index.d.ts +9 -16
  200. package/dist/openai/index.js +17 -24
  201. package/dist/openapi/index.d.ts +3 -3
  202. package/dist/openapi/index.js +1 -1
  203. package/dist/{openapi-DBzHQbov.js → openapi-B7HZ0f-r.js} +8 -8
  204. package/dist/openapi-C4dDTVEd.js +138 -0
  205. package/dist/{openapi-BJdBmYDG.js → openapi-DD1SV-5-.js} +10 -9
  206. package/dist/operation-findings-DgATDpvh.js +36 -0
  207. package/dist/operations-X0VM7F3m.d.ts +59 -0
  208. package/dist/orpc/index.d.ts +13 -10
  209. package/dist/orpc/index.js +20 -15
  210. package/dist/otel/index.d.ts +3 -2
  211. package/dist/otel/index.js +30 -7
  212. package/dist/output-1im__-08.js +88 -0
  213. package/dist/output-files-B3IZJksC.js +1 -0
  214. package/dist/{output-files-CgIsvdSa.js → output-files-DhRBBE4n.js} +24 -26
  215. package/dist/{permission-keys-B26jeyUv.js → ownership-DqaJNJPn.js} +1045 -153
  216. package/dist/{package-root-DtNnpmUd.js → package-root-Dk_ETdkp.js} +1 -1
  217. package/dist/{parse-CVFQHk6c.js → parse-Cxq0WxrA.js} +2 -2
  218. package/dist/{parse-local-manifest-DvYUR0QL.d.ts → parse-local-manifest-5ODwqu8b.d.ts} +1 -1
  219. package/dist/{parse-local-manifest-C-q02Pal.js → parse-local-manifest-CVSs6J9I.js} +1 -1
  220. package/dist/parse-request-D_tUXG6M.js +26 -0
  221. package/dist/paths-DXSHAMIa.js +42 -0
  222. package/dist/pdp/index.d.ts +7 -3
  223. package/dist/pdp/index.js +45 -26
  224. package/dist/{peer-BOr_kJE1.js → peer-C8swJ2GH.js} +23 -4
  225. package/dist/peer-CRvC7Hg1.js +1 -0
  226. package/dist/permdock-BrAscV28.js +868 -0
  227. package/dist/permdock-SV8ZINYT.d.ts +502 -0
  228. package/dist/{permission-registry-BsblpB-z.js → permission-registry-Br4jSNVi.js} +3 -3
  229. package/dist/permissions-B-wCP5ZC.js +30 -0
  230. package/dist/{permissions-CCJ6lmxB.js → permissions-BPxlwVhN.js} +4 -3
  231. package/dist/{permissions-COH-KRgT.d.ts → permissions-u_KHg-sN.d.ts} +1 -2
  232. package/dist/policies-DP2nqRoF.js +295 -0
  233. package/dist/{policy-ur0BBK_e.d.ts → policy-77FSAq_V.d.ts} +2 -2
  234. package/dist/{hosted-CBHgkzDl.js → policy-DbM5YYtV.js} +17 -273
  235. package/dist/policy-from-catalog-C2gMa2Dj.js +450 -0
  236. package/dist/policy-module-CTlwMvLI.js +89 -0
  237. package/dist/{policy-types-sp9P1iIV.d.ts → policy-types-n0uRTsW8.d.ts} +460 -632
  238. package/dist/postgrest-CEgRbefE.d.ts +100 -0
  239. package/dist/postgrest-DAjUkF9w.js +575 -0
  240. package/dist/{powersync-cZe4kzhL.js → powersync-BkWWCS1K.js} +23 -24
  241. package/dist/powersync-DISt6SUe.js +62 -0
  242. package/dist/powersync-MMJ2mI2q.js +1 -0
  243. package/dist/preset/index.d.ts +146 -0
  244. package/dist/preset/index.js +1 -0
  245. package/dist/preset-DZdU0tne.js +1768 -0
  246. package/dist/principal-source-pHneukrx.js +103 -0
  247. package/dist/prisma/index.d.ts +6 -3
  248. package/dist/prisma/index.js +2 -2
  249. package/dist/{problem-CnWraVhC.js → problem-CTviyH5L.js} +13 -23
  250. package/dist/{problem-details-rIIo8B2C.d.ts → problem-details-B45V5d2b.d.ts} +1 -1
  251. package/dist/{problem-details-DECUnXAn.js → problem-details-cn5X30gZ.js} +43 -40
  252. package/dist/problem-types-CJ2325SZ.js +5 -0
  253. package/dist/{prompt-CXYhLfsS.js → prompt-DfZLJLIn.js} +2 -2
  254. package/dist/{protected-BN7BUdY1.js → protected-BlF0F6wL.js} +21 -4
  255. package/dist/{protected-B3_P7xuw.d.ts → protected-RFGpakN9.d.ts} +16 -8
  256. package/dist/{provider-Bi-0HNL3.js → provider-B1al-AZz.js} +2 -2
  257. package/dist/{provider-kwHC9CZD.d.ts → provider-DILL9oPK.d.ts} +1 -1
  258. package/dist/{provider-store-Ctt-81Ni.js → provider-store-DM6DNcta.js} +2 -2
  259. package/dist/pull-CvpINcMK.js +1 -0
  260. package/dist/pull-jEwbf80J.js +256 -0
  261. package/dist/push-BD4f15co.js +116 -0
  262. package/dist/pushed-policy-CqtxkV4G.js +238 -0
  263. package/dist/pushed-policy-DCW91KQU.d.ts +57 -0
  264. package/dist/react/index.d.ts +8 -8
  265. package/dist/react/index.js +6 -6
  266. package/dist/react/provider-client.d.ts +1 -1
  267. package/dist/react/provider-client.js +1 -2
  268. package/dist/react-native/index.d.ts +24 -72
  269. package/dist/react-native/index.js +29 -25
  270. package/dist/{refs-f3imMjNW.js → refs-K6eWUJ0H.js} +1 -1
  271. package/dist/{relation-walk-BXnlLtg2.js → relation-walk-3OyDBF0S.js} +2 -2
  272. package/dist/{relations-CvmSpG67.js → relations-BnCWnJGI.js} +3 -3
  273. package/dist/renames-CGj7YwcQ.js +175 -0
  274. package/dist/{replay-COpmZUfx.d.ts → replay-Bkm-Ne4l.d.ts} +1 -1
  275. package/dist/{replay-BsOyTrdV.js → replay-Du1tJjG7.js} +2 -1
  276. package/dist/{request-context-BVM7aWNs.js → request-context-C81Pip9n.js} +3 -3
  277. package/dist/request-schema-CMreVuFM.js +148 -0
  278. package/dist/resend/index.d.ts +126 -0
  279. package/dist/resend/index.js +379 -0
  280. package/dist/{resource-relations-D1Un3pcz.js → resource-relations-D_jw0BFx.js} +1 -1
  281. package/dist/{retire-BFcxL6un.js → retire-CALjIQ7e.js} +2 -2
  282. package/dist/{revocations-Cy06JNx2.d.ts → revocations-R07lFBnX.d.ts} +2 -0
  283. package/dist/rls-BAqP4q66.js +336 -0
  284. package/dist/{rls-BEktUaqg.js → rls-DTvYd-lI.js} +288 -690
  285. package/dist/{role-builder-B4GbKM-i.js → role-builder-uWQZ4v84.js} +14 -207
  286. package/dist/{rpc-nYC3cR_u.js → rpc-VSl5ylaQ.js} +7 -7
  287. package/dist/run-CUKuz0A8.js +723 -0
  288. package/dist/run-DDwUaJDU.js +1 -0
  289. package/dist/run-ticket-DocwaTKn.d.ts +124 -0
  290. package/dist/run-ticket-sxFI6LAk.js +337 -0
  291. package/dist/{runtime-D_tMXnsq.js → runtime-D3VTAiCA.js} +1 -1
  292. package/dist/{saas-DtaA4JuW.js → saas-BCXRuXca.js} +16 -14
  293. package/dist/scan-qvOe0hS3.js +29 -0
  294. package/dist/{schema-DjQBcAP9.js → schema-B4v0Qrac.js} +102 -8
  295. package/dist/schema-yLuBWWsh.js +130 -0
  296. package/dist/scim/index.d.ts +2 -2
  297. package/dist/scim/index.js +1 -2
  298. package/dist/{scim-vFR1O5oG.js → scim-ClHtpyV3.js} +208 -76
  299. package/dist/{scopes-tvpa34ep.js → scopes-CUPSjo9q.js} +12 -7
  300. package/dist/server/index.d.ts +12 -143
  301. package/dist/server/index.js +12 -9
  302. package/dist/server-C3WXAHZj.js +453 -0
  303. package/dist/{server-types-DdqBwZN_.d.ts → server-types-CKQiCnON.d.ts} +51 -68
  304. package/dist/{sha256-BO0swRXL.js → sha256-D9ihqmvd.js} +8 -1
  305. package/dist/{shared-1uhI-e-b.js → shared-BAK8t4Uo.js} +20 -9
  306. package/dist/{shared-JhMeOsDF.js → shared-DvvFzYXe.js} +8 -9
  307. package/dist/signals-D3NcTKUS.js +32 -0
  308. package/dist/sink-4-eQIRWf.d.ts +225 -0
  309. package/dist/sink-BEZSlMS_.js +957 -0
  310. package/dist/{skills-BZdRQkxs.js → skills-B6lVdD_W.js} +13 -6
  311. package/dist/skills-CTYwkl44.js +51 -0
  312. package/dist/snapshot-B7DkYg6g.js +269 -0
  313. package/dist/{snapshot-cache-Cod40D-X.js → snapshot-cache-DZHQ0KF6.js} +11 -5
  314. package/dist/{snapshot-cache-J5T7t_vD.d.ts → snapshot-cache-PXI-vGpY.d.ts} +7 -5
  315. package/dist/solid/index.d.ts +11 -12
  316. package/dist/solid/index.js +7 -7
  317. package/dist/solid-start/index.d.ts +52 -0
  318. package/dist/solid-start/index.js +49 -0
  319. package/dist/{sources-Dljy06zL.d.ts → sources-Xc4zkszI.d.ts} +7 -13
  320. package/dist/{sources-B1-b-R0t.js → sources-vs5dGOsS.js} +125 -119
  321. package/dist/{sql-t8teWOfo.js → sql-B5T1IL83.js} +13 -1
  322. package/dist/src-Cu2m75uo.js +1860 -0
  323. package/dist/ssf/index.d.ts +4 -4
  324. package/dist/ssf/index.js +384 -47
  325. package/dist/standard-webhooks-DgFFD-Ga.js +228 -0
  326. package/dist/standard-webhooks-rbD7LS6h.d.ts +88 -0
  327. package/dist/storage-C2bWdvJH.js +132 -0
  328. package/dist/{store-ILkLUj_o.js → store-BDutJkDL.js} +40 -53
  329. package/dist/{store-options-CbUHDoSe.js → store-options-u0ycEe8s.js} +26 -21
  330. package/dist/{stores-Cxz_80g-.d.ts → stores-BUGNbJTt.d.ts} +7 -8
  331. package/dist/{stores-BHXdSbw9.js → stores-CGhM8BN_.js} +2 -2
  332. package/dist/{stream-Dwsg6WwE.d.ts → stream-CeCMJmBB.d.ts} +2 -2
  333. package/dist/{stream-BMui98KK.js → stream-CvdUS9V1.js} +9 -2
  334. package/dist/style-DiKWnAvn.js +1 -0
  335. package/dist/{style-CTm1AXDT.js → style-Uao76wO7.js} +3 -2
  336. package/dist/subject-BGoM5o8B.js +17 -0
  337. package/dist/supabase/index.d.ts +23 -106
  338. package/dist/supabase/index.js +5 -4
  339. package/dist/supabase/middleware.d.ts +7 -6
  340. package/dist/supabase/middleware.js +13 -11
  341. package/dist/supabase-By_frk3U.js +92 -0
  342. package/dist/{supabase-hook-Cva3ikXz.js → supabase-hook-BMoEhZir.js} +960 -64
  343. package/dist/supabase-hook-CCw5m35C.js +1 -0
  344. package/dist/{supabase-C1TWlnYt.js → supabase-x5JtR2tc.js} +99 -206
  345. package/dist/support-session-C6xJQYut.js +158 -0
  346. package/dist/svelte/Protected.svelte +4 -4
  347. package/dist/svelte/index.d.ts +7 -7
  348. package/dist/svelte/index.js +10 -10
  349. package/dist/svelte/runtime.d.ts +3 -3
  350. package/dist/svelte/runtime.js +3 -3
  351. package/dist/svelte/source.d.ts +7 -7
  352. package/dist/svelte/source.js +4 -4
  353. package/dist/sveltekit/index.d.ts +51 -0
  354. package/dist/sveltekit/index.js +27 -0
  355. package/dist/tanstack-start/index.d.ts +51 -0
  356. package/dist/tanstack-start/index.js +40 -0
  357. package/dist/target-Bw37TFNA.js +51 -0
  358. package/dist/{tenancy-DshjqY0n.js → tenancy-DN2QAx9z.js} +5 -5
  359. package/dist/terminal/index.d.ts +11 -9
  360. package/dist/terminal/index.js +23 -287
  361. package/dist/testing/index.d.ts +311 -38
  362. package/dist/testing/index.js +3475 -201
  363. package/dist/testing/saas/index.d.ts +9 -8
  364. package/dist/testing/saas/index.js +2 -2
  365. package/dist/testing/saas/permissions.d.ts +1 -1
  366. package/dist/testing/saas/permissions.js +1 -2
  367. package/dist/text-BMFRfBqc.js +8 -0
  368. package/dist/{text-diff-BdkkYSbi.js → text-diff-BQcUapOb.js} +1 -1
  369. package/dist/thenable-yD7SabqG.js +31 -0
  370. package/dist/time-DkWjdL98.js +71 -0
  371. package/dist/{token-Bx9qy30p.js → token-BLZcjQgt.js} +15 -6
  372. package/dist/{tool-refusal-CQ7OEia9.js → tool-refusal-CMLN5xhn.js} +2 -2
  373. package/dist/trpc/index.d.ts +12 -9
  374. package/dist/trpc/index.js +16 -14
  375. package/dist/types-8RnwUAvE.d.ts +107 -0
  376. package/dist/{store-9KO4BlkB.d.ts → types-B3_ooZgZ.d.ts} +100 -75
  377. package/dist/{types-Br9IXUBG.d.ts → types-BdtSDbBO.d.ts} +77 -17
  378. package/dist/types-BtHtcKMr.d.ts +157 -0
  379. package/dist/{types-CqrjB9o6.d.ts → types-BzzaHxt3.d.ts} +23 -11
  380. package/dist/types-C791HFu9.d.ts +332 -0
  381. package/dist/types-C863OQiR.d.ts +57 -0
  382. package/dist/{types-ByUo56un.d.ts → types-CVZSNocA.d.ts} +23 -22
  383. package/dist/{types-DHrmBJLH.d.ts → types-CX7AvRqP.d.ts} +7 -7
  384. package/dist/types-C_IiEs64.d.ts +65 -0
  385. package/dist/{types-7rJVLWe5.d.ts → types-Dfv0aTER.d.ts} +63 -11
  386. package/dist/types-DqzsnlSA.js +67 -0
  387. package/dist/{types-DpWfQdSh.d.ts → types-Druu9wLM.d.ts} +46 -11
  388. package/dist/{types-CIlTojhq.d.ts → types-_WhUP7ud.d.ts} +9 -6
  389. package/dist/{types-CUcFZfpU.d.ts → types-lUlG3TVd.d.ts} +2 -1
  390. package/dist/unplugin/index.d.ts +1 -1
  391. package/dist/unplugin/index.js +2 -2
  392. package/dist/usage-CRVl9V3t.js +32 -0
  393. package/dist/usage-CZpSQP6L.js +1 -0
  394. package/dist/{usage-CG9nOYZw.js → usage-SveTRTyr.js} +54 -11
  395. package/dist/{validation-error-BSSw0l_8.d.ts → validation-error-DHUrAkku.d.ts} +1 -1
  396. package/dist/{validity-dVrUEPJt.js → validity-CZ7-y0Cj.js} +32 -15
  397. package/dist/{verifier-DMr_Zmb_.js → verifier-DHuh-okk.js} +18 -7
  398. package/dist/{verify-BztUB3GX.js → verify-BaAKx7t6.js} +645 -50
  399. package/dist/{version-BEogC-rB.js → version-CEbQ5UeM.js} +4 -3
  400. package/dist/version-DXkYUISx.js +1 -0
  401. package/dist/{views-CJsrBS1f.js → views-DTIIO-oQ.js} +22 -5
  402. package/dist/{permission-types-Dg39z9Kv.d.ts → vocabulary-CN8H_9UF.d.ts} +67 -2
  403. package/dist/vue/index.d.ts +9 -10
  404. package/dist/vue/index.js +8 -8
  405. package/dist/waiter-B986w1oK.d.ts +36 -0
  406. package/dist/waiter-BGUtGYoU.js +67 -0
  407. package/dist/watch--ovlPOc0.js +1 -0
  408. package/dist/{watch-zeucB7eF.js → watch-vea54PRw.js} +44 -5
  409. package/dist/webmcp/index.d.ts +2 -2
  410. package/dist/webmcp/index.js +3 -3
  411. package/dist/where-scope-C9vfEphl.js +306 -0
  412. package/dist/who-can-DWBAcaCQ.js +364 -0
  413. package/dist/wire-delegation-Cv3W2eaM.js +36 -0
  414. package/dist/wire-denial-C7PaVYEM.js +27 -0
  415. package/dist/workflow/index.d.ts +5 -0
  416. package/dist/workflow/index.js +223 -0
  417. package/dist/workflow-sdk/index.d.ts +56 -0
  418. package/dist/workflow-sdk/index.js +78 -0
  419. package/dist/{wrap-Cr93qqdP.d.ts → wrap-C5jC6sKI.d.ts} +2 -1
  420. package/dist/{wrap-DWsU_5Bd.js → wrap-gX61EtpB.js} +7 -6
  421. package/package.json +136 -37
  422. package/schemas/access-event-v1.json +118 -0
  423. package/schemas/approval-request-v1.json +34 -1
  424. package/schemas/approval-signature-v1.json +94 -0
  425. package/schemas/automation-event-v1.json +113 -0
  426. package/schemas/capability-v1.json +84 -0
  427. package/schemas/catalog-event-v1.json +87 -0
  428. package/schemas/catalog-v1.json +123 -12
  429. package/schemas/cli-commands-v1.json +164 -0
  430. package/schemas/cli-result-v1.json +96 -0
  431. package/schemas/cloud-api-v1.json +5407 -0
  432. package/schemas/connector-v1.json +408 -0
  433. package/schemas/credential-event-v1.json +81 -0
  434. package/schemas/credential-v1.json +134 -0
  435. package/schemas/decision-event-v1.json +288 -0
  436. package/schemas/delivery-event-v1.json +117 -0
  437. package/schemas/directory-event-v1.json +85 -0
  438. package/schemas/evidence-export-v1.json +54 -0
  439. package/schemas/membership-event-v1.json +93 -0
  440. package/schemas/policy-v1.json +68 -0
  441. package/schemas/pushed-policy-v1.json +236 -0
  442. package/schemas/run-v1.json +162 -0
  443. package/schemas/snapshot-v1.json +138 -0
  444. package/schemas/supabase-claims-v1.json +4 -4
  445. package/schemas/supabase-manifest-v1.json +5 -1
  446. package/schemas/support-session-v1.json +111 -0
  447. package/skills/permdock/SKILL.md +16 -12
  448. package/skills/permdock-agents/SKILL.md +40 -23
  449. package/skills/permdock-agents/references/operations-automations-chat.md +221 -0
  450. package/skills/permdock-agents/references/runtimes.md +22 -18
  451. package/skills/permdock-approvals/SKILL.md +29 -13
  452. package/skills/permdock-approvals/references/delivery-and-waiters.md +172 -0
  453. package/skills/permdock-approvals/references/stores-and-resume.md +21 -7
  454. package/skills/permdock-audit/SKILL.md +3 -3
  455. package/skills/permdock-credentials/SKILL.md +5 -5
  456. package/skills/permdock-credentials/references/tokens-keys-links.md +14 -10
  457. package/skills/permdock-data/SKILL.md +6 -6
  458. package/skills/permdock-data/references/queries-and-rls.md +10 -9
  459. package/skills/permdock-data/references/relationships.md +3 -3
  460. package/skills/permdock-tenancy/SKILL.md +20 -7
  461. package/skills/permdock-tenancy/references/contacts-and-support-sessions.md +122 -0
  462. package/skills/permdock-tenancy/references/ownership-and-custom-roles.md +6 -4
  463. package/skills/permdock-wire/SKILL.md +17 -11
  464. package/skills/permdock-wire/references/adapters.md +139 -51
  465. package/skills/permdock-wire/references/policy-options.md +5 -5
  466. package/skills/permdock-wire/references/presets-and-recipes.md +58 -0
  467. package/dist/approvals-DZL76lKW.js +0 -240
  468. package/dist/arazzo-CD8d0WLb.js +0 -54
  469. package/dist/catalog-6yWAX6Ps.js +0 -2
  470. package/dist/catalog-WHG4kBq7.js +0 -48
  471. package/dist/cloud-C7bSDgX4.js +0 -50
  472. package/dist/cloud-PeBN3IEn.js +0 -118
  473. package/dist/collect-Bw2Jdq1q.js +0 -2
  474. package/dist/collect-CQx4hX4N.js +0 -83
  475. package/dist/commands-Cpip4ULx.js +0 -97
  476. package/dist/config-DLt6BvV7.js +0 -36
  477. package/dist/diff-Cnghh_Ip.js +0 -48
  478. package/dist/doctor-CuVJvhV_.js +0 -53
  479. package/dist/duration-xK2jGFVc.js +0 -25
  480. package/dist/errors-CWfoNg9N.js +0 -47
  481. package/dist/errors-DRnGZHzF.js +0 -30
  482. package/dist/expiry-BoBJ8Tvc.js +0 -7
  483. package/dist/fields-fwfXN01n.js +0 -1016
  484. package/dist/headers-BZSDieNT.js +0 -6
  485. package/dist/headers-Cf-zWvsO.d.ts +0 -6
  486. package/dist/ids-DC4V4CFu.js +0 -18
  487. package/dist/index-DgM7w96K.d.ts +0 -6
  488. package/dist/index-Dz7didpC.d.ts +0 -201
  489. package/dist/index-UdVQgq48.d.ts +0 -550
  490. package/dist/load-B_shHrmd.js +0 -2
  491. package/dist/manifest-BngWKryL.js +0 -770
  492. package/dist/may-use-BOn_dR_w.js +0 -78
  493. package/dist/openapi-DcQ1Kg40.js +0 -164
  494. package/dist/ownership-BgnbOJFT.js +0 -851
  495. package/dist/permdock-L26udHSk.js +0 -3646
  496. package/dist/powersync-BFSbSElQ.js +0 -62
  497. package/dist/powersync-DCDgZgfZ.js +0 -2
  498. package/dist/rls-55DQTuf5.js +0 -250
  499. package/dist/run-DMcNW_8T.js +0 -174
  500. package/dist/server-ZYEHtNHS.js +0 -268
  501. package/dist/sink-BTtmfPnE.js +0 -141
  502. package/dist/sink-CPN4zLfs.d.ts +0 -104
  503. package/dist/skills-Dle4ZoLF.js +0 -56
  504. package/dist/snapshot-Dc9CHPME.js +0 -1078
  505. package/dist/src-Bb7no-hS.js +0 -639
  506. package/dist/subject-C6dLxqHU.js +0 -24
  507. package/dist/supabase-Dbqr141s.js +0 -83
  508. package/dist/supabase-hook-BULg7Ut_.js +0 -2
  509. package/dist/thenable-QSmvt-vZ.js +0 -65
  510. package/dist/types-C0dee0Qf.d.ts +0 -197
  511. package/dist/types-DvupZE0w.d.ts +0 -9
  512. package/dist/usage-4G6t643F.js +0 -42
  513. package/dist/usage-iFLJ0-rr.js +0 -2
  514. package/dist/vocabulary-hhEFLFoL.d.ts +0 -65
  515. package/dist/watch-C8TpsAl4.js +0 -2
@@ -1,23 +1,26 @@
1
1
  import { r as isRecord } from "./compact-CAf557ZB.js";
2
- import { a as hasConditionOp } from "./refs-f3imMjNW.js";
3
- import { c as rootScope, d as scopeList, l as scopeChain, o as resolveScope } from "./scopes-tvpa34ep.js";
4
- import { i as describeError } from "./errors-CWfoNg9N.js";
2
+ import { a as hasConditionOp } from "./refs-K6eWUJ0H.js";
3
+ import { c as rootScope, d as scopeList, l as scopeChain, o as resolveScope } from "./scopes-CUPSjo9q.js";
4
+ import { t as APPROVAL_REQUEST_SCHEMA } from "./request-schema-CMreVuFM.js";
5
+ import { a as describeError, i as configError } from "./errors-CWTY6uih.js";
5
6
  import { i as readJsonFile, o as sqlFiles, s as writeFileEnsuringDir } from "./files-D_JIzUrP.js";
6
- import { i as SUPABASE_MANIFEST_SCHEMA } from "./version-BEogC-rB.js";
7
- import { c as requirePolicy } from "./load-C3_oTHyO.js";
8
- import { r as scopeColumn } from "./compile-CcEu2ZQ1.js";
9
- import { t as authorizeSql } from "./supabase-C1TWlnYt.js";
7
+ import { a as SUPABASE_MANIFEST_SCHEMA } from "./version-CEbQ5UeM.js";
8
+ import { c as requirePolicy } from "./load-DFS_ry0W.js";
9
+ import { r as scopeColumn } from "./compile-CMqMx_IN.js";
10
+ import { t as authorizeSql } from "./supabase-x5JtR2tc.js";
10
11
  import "./budget-NbEtbJC_.js";
11
- import { r as qualifySqlTable } from "./sql-t8teWOfo.js";
12
- import { c as hookApiNames, d as keptKeys, t as AUTHZ_VERSION_TABLE, u as roleManifest } from "./sources-B1-b-R0t.js";
13
- import { i as readMemberships } from "./manifest-BngWKryL.js";
14
- import { n as GRANTS_MARKER, r as HOOK_MARKER, s as hookMarkerFields } from "./markers-CiwAOZtO.js";
12
+ import { i as qualifySqlTable } from "./sql-B5T1IL83.js";
13
+ import { a as hookApiNames, d as keptKeys, t as AUTHZ_VERSION_TABLE, u as roleManifest } from "./sources-vs5dGOsS.js";
14
+ import { o as readMemberships } from "./postgrest-DAjUkF9w.js";
15
+ import { t as beforeFirst } from "./text-BMFRfBqc.js";
16
+ import { n as GRANTS_MARKER, r as HOOK_MARKER, s as hookMarkerFields } from "./markers-C6joDI3o.js";
17
+ import { n as formatManifestJson, t as MANIFEST_FILE } from "./manifest-json-CAGzls-C.js";
15
18
  import { n as MIGRATION_DIRS } from "./constants-DcJABbLW.js";
16
- import { E as scopeTypeOf, F as quoteQualified, I as quoteTable, N as quoteIdent, O as tenantTypeOf, P as quoteLiteral, a as customRolesOn, l as USER_ID_HELPER, r as HELPERS } from "./shared-1uhI-e-b.js";
17
- import { l as writeOutputFiles, n as driftOf, o as partPath, s as pgDeltaPath } from "./output-files-CgIsvdSa.js";
19
+ import { f as writeOutputFiles, l as partPath, r as driftOf, u as pgDeltaPath } from "./output-files-DhRBBE4n.js";
20
+ import { E as scopeTypeOf, F as quoteQualified, I as quoteTable, M as qualified, N as quoteIdent, O as tenantTypeOf, P as quoteLiteral, a as customRolesOn, l as USER_ID_HELPER, r as HELPERS, y as subjectIdSql } from "./shared-BAK8t4Uo.js";
18
21
  import { a as splitUndiffed, r as tableKey, t as decidingColumns } from "./deciding-columns-Sq12mEA6.js";
19
- import { B as memberIdsHelper, D as roleNames, F as hasMemberFor, H as permittedForHelper, L as memberForHelper, R as memberForSources, S as apiKeyFields, U as permittedIdsHelper, V as memberRoleOf, Y as checkSuspension, Z as pgConnector, _ as globalRoleSource, b as API_KEY_ALLOWS, n as permissionKeysForHelper, q as activeRowSql, z as memberForTable } from "./permission-keys-B26jeyUv.js";
20
- import { a as ASSIGNMENTS, i as guardedTables, o as OWNERSHIP, t as ownershipRules } from "./ownership-BgnbOJFT.js";
22
+ import { a as readOnlyActorKinds, f as pgConnector, t as ACTOR_MAY_WRITE } from "./policies-DP2nqRoF.js";
23
+ import { B as hasPermittedUsers, E as apiKeyFields, G as memberIdsHelper, H as memberForHelper, J as permittedIdsHelper, K as memberRoleOf, Q as activeRowSql, U as memberForSources, W as memberForTable, Z as scopeTable, a as guardedTables, c as PERMISSION_HELPERS, d as permissionKeysForHelper, et as checkSuspension, f as permittedByPermissionHelper, j as roleNames, l as checkName, o as ASSIGNMENTS, q as permittedForHelper, r as holderSources, s as OWNERSHIP, t as ownershipRules, v as globalRoleRows, w as API_KEY_ALLOWS, y as globalRoleSource, z as hasMemberFor } from "./ownership-DqaJNJPn.js";
21
24
  import { existsSync, readFileSync, readdirSync } from "node:fs";
22
25
  import { dirname, join, posix, resolve } from "node:path";
23
26
  import { parse } from "smol-toml";
@@ -49,16 +52,24 @@ function readSupabaseConfig(cwd) {
49
52
  const apiSchemas = strings(table(root["api"])?.["schemas"]);
50
53
  const pgDelta = table(table(root["experimental"])?.["pgdelta"]);
51
54
  const schemaPath = pgDelta?.["declarative_schema_path"];
55
+ const enabled = pgDelta?.["enabled"];
52
56
  return {
53
57
  ok: true,
54
58
  config: {
55
59
  ...typeof expiry === "number" ? { jwtExpiry: expiry } : {},
56
60
  ...schemaPaths === void 0 ? {} : { schemaPaths },
57
61
  ...apiSchemas === void 0 ? {} : { apiSchemas },
58
- ...pgDelta?.["enabled"] === true ? { pgDelta: { schemaDir: posix.join("supabase", typeof schemaPath === "string" ? schemaPath : "schemas").replace(/\/$/u, "") } } : {}
62
+ ...pgDelta === void 0 ? {} : { pgDelta: {
63
+ ...typeof enabled === "boolean" ? { enabled } : {},
64
+ schemaDir: declarativeSchemaDir(schemaPath)
65
+ } }
59
66
  }
60
67
  };
61
68
  }
69
+ /** `supabase/` joined with `declarative_schema_path`, which defaults to `schemas`. */
70
+ function declarativeSchemaDir(schemaPath) {
71
+ return posix.join("supabase", typeof schemaPath === "string" ? schemaPath : "schemas").replace(/\/$/u, "");
72
+ }
62
73
  /** The parsed config, or `{}` when it is missing or does not parse (PD045 reports that). */
63
74
  function supabaseConfig(cwd) {
64
75
  const read = readSupabaseConfig(cwd);
@@ -72,7 +83,7 @@ function pd045(cwd) {
72
83
  code: "PD045",
73
84
  severity: "warning",
74
85
  message: `${SUPABASE_CONFIG} does not parse: ${read.error.replace(/\n[\s\S]*/u, "")}`,
75
- fix: `fix the TOML syntax in ${SUPABASE_CONFIG}; until then doctor assumes Supabase's defaults for jwt_expiry and schema_paths`
86
+ fix: `fix the TOML syntax in ${SUPABASE_CONFIG}; until then PermDock assumes Supabase's defaults for jwt_expiry, schema_paths and [experimental.pgdelta], and generates for pg-delta`
76
87
  }];
77
88
  }
78
89
  //#endregion
@@ -156,11 +167,11 @@ const PERMDOCK_CLAIMS_SCHEMA = {
156
167
  items: { type: "string" }
157
168
  },
158
169
  via: { type: "string" },
159
- expiresAt: {
160
- description: "Seconds since the epoch.",
170
+ expires_at: {
171
+ description: "NumericDate (RFC 7519): seconds since the epoch.",
161
172
  type: "number"
162
173
  },
163
- grantedBy: { type: "string" },
174
+ granted_by: { type: "string" },
164
175
  reason: { type: "string" },
165
176
  member: {
166
177
  description: "The subgroup a membership source's group fills.",
@@ -171,7 +182,7 @@ const PERMDOCK_CLAIMS_SCHEMA = {
171
182
  minLength: 1
172
183
  } }
173
184
  },
174
- managedBy: { const: "idp" },
185
+ managed_by: { const: "idp" },
175
186
  entitlements: {
176
187
  type: "array",
177
188
  items: { type: "string" }
@@ -309,7 +320,7 @@ function extraClaimsPlan(claims, tenantClaim) {
309
320
  function beforePlan(before) {
310
321
  const list = before === void 0 ? [] : typeof before === "string" ? [before] : before;
311
322
  const bad = list.filter((fn) => typeof fn !== "string" || !CLAIM_FUNCTION.test(fn));
312
- if (bad.length > 0) throw new Error(`PermDock CLI: supabase.hook.before must name schema-qualified functions such as auth_checks.before_token (got ${bad.map(String).join(", ")})`);
323
+ if (bad.length > 0) throw configError(`PermDock CLI: supabase.hook.before must name schema-qualified functions such as auth_checks.before_token (got ${bad.map(String).join(", ")})`);
313
324
  return [...new Set(list)];
314
325
  }
315
326
  function beforeSql(before) {
@@ -397,6 +408,38 @@ function hookClaims(parts) {
397
408
  ];
398
409
  }
399
410
  //#endregion
411
+ //#region src/cli/supabase-schemas.ts
412
+ const MODES = [
413
+ "pg-delta",
414
+ "migra",
415
+ "migrations"
416
+ ];
417
+ function isSupabaseSchemas(value) {
418
+ return MODES.some((mode) => mode === value);
419
+ }
420
+ /** The `--schemas` refusal, or `undefined` when the value is one of the modes. */
421
+ function schemasRefusal(command, value) {
422
+ return value === void 0 || isSupabaseSchemas(value) ? void 0 : `${command} --schemas must be ${MODES.join(", ")} (got ${value})`;
423
+ }
424
+ /**
425
+ * `--schemas`, else `supabase.schemas`, else `migra` when `config.toml`
426
+ * sets `[experimental.pgdelta] enabled = false`, else `pg-delta`: the default
427
+ * of new Supabase projects and of the next Supabase CLI major.
428
+ */
429
+ function resolveSchemas(cwd, config, flag) {
430
+ const toml = supabaseConfig(cwd).pgDelta;
431
+ const mode = flag ?? config.supabase?.schemas ?? (toml?.enabled === false ? "migra" : "pg-delta");
432
+ switch (mode) {
433
+ case "pg-delta": return {
434
+ mode,
435
+ schemaDir: toml?.schemaDir ?? declarativeSchemaDir()
436
+ };
437
+ case "migra":
438
+ case "migrations": return { mode };
439
+ default: return mode;
440
+ }
441
+ }
442
+ //#endregion
400
443
  //#region src/cli/supabase-setup.ts
401
444
  const HELPER_NAME = /^(?:permdock_has|permitted_[a-z][a-z0-9_]*_ids|member_[a-z][a-z0-9_]*_ids(?:_for)?)$/u;
402
445
  function escape(text) {
@@ -435,7 +478,7 @@ function missingHelpersInFiles(cwd, config, manifest) {
435
478
  for (const file of helperSources(cwd, config, manifest.hook.out)) for (const name of helpersDefined(readFileSync(file, "utf8"), manifest.helpers.schema)) found.add(name);
436
479
  return manifest.helpers.functions.filter((name) => !found.has(name));
437
480
  }
438
- async function missingHelpersInDb(db, manifest, connect = pgConnector("permdock supabase hook generate --db")) {
481
+ async function missingHelpersInDb(db, manifest, connect = pgConnector("permdock supabase hook generate --db-url-stdin")) {
439
482
  const client = await connect(db);
440
483
  try {
441
484
  const result = await client.query(`select p.proname from pg_proc p join pg_namespace n on n.oid = p.pronamespace
@@ -493,8 +536,17 @@ function pd039(input) {
493
536
  });
494
537
  const fixture = input.config.doctor?.claims;
495
538
  if (fixture !== void 0) {
496
- const parsed = JSON.parse(readFileSync(resolve(input.cwd, fixture), "utf8"));
497
- const samples = Array.isArray(parsed) ? parsed : [];
539
+ const parsed = readClaimsFixture(resolve(input.cwd, fixture));
540
+ if (!parsed.ok) {
541
+ findings.push({
542
+ code: "PD039",
543
+ severity: "warning",
544
+ message: `doctor.claims ${fixture} is not a JSON file doctor can read (${parsed.message}), so the claim budget went unchecked`,
545
+ fix: "point doctor.claims at a JSON array of sample claims"
546
+ });
547
+ return findings;
548
+ }
549
+ const samples = Array.isArray(parsed.value) ? parsed.value : [];
498
550
  for (const claim of oversizedClaims(samples, input.manifest)) findings.push({
499
551
  code: "PD039",
500
552
  severity: "warning",
@@ -510,6 +562,19 @@ function pd039(input) {
510
562
  }
511
563
  return findings;
512
564
  }
565
+ function readClaimsFixture(path) {
566
+ try {
567
+ return {
568
+ ok: true,
569
+ value: JSON.parse(readFileSync(path, "utf8"))
570
+ };
571
+ } catch (error) {
572
+ return {
573
+ ok: false,
574
+ message: error instanceof Error ? error.message : String(error)
575
+ };
576
+ }
577
+ }
513
578
  //#endregion
514
579
  //#region src/cli/rls/rbac.ts
515
580
  /**
@@ -528,7 +593,7 @@ function resolveAuthorize(config, flags = {}) {
528
593
  function parseRbacAuthorize(raw) {
529
594
  if (raw === void 0) return;
530
595
  if (raw === "database" || raw === "jwt") return raw;
531
- throw new Error(`PermDock CLI: --authorize must be database or jwt, got '${raw}'`);
596
+ throw configError(`PermDock CLI: --authorize must be database or jwt, got '${raw}'`);
532
597
  }
533
598
  function hookUri(schema) {
534
599
  return `pg-functions://postgres/${schema}/custom_access_token_hook`;
@@ -598,6 +663,7 @@ function memberForPlan(input) {
598
663
  dialect: "supabase",
599
664
  ...input
600
665
  }, name));
666
+ const holders = memberFor.filter((name) => hasPermittedUsers(input, name));
601
667
  const entries = /* @__PURE__ */ new Map();
602
668
  for (const name of memberFor) {
603
669
  const mapped = memberForTable(input, name);
@@ -606,10 +672,11 @@ function memberForPlan(input) {
606
672
  }
607
673
  return {
608
674
  memberFor,
675
+ holders,
609
676
  memberships: [...entries.values()]
610
677
  };
611
678
  }
612
- /** An `rls.memberships` table in the manifest's `fromTable` / `fromJunction` shape, for scope `name`. */
679
+ /** An `rls.memberships` table in the manifest's `tableMembershipSource` / `junctionMembershipSource` shape, for scope `name`. */
613
680
  function mappedMembership(mapped, scopes, name) {
614
681
  const id = scopeColumn(mapped, scopes, name) ?? "";
615
682
  const within = {};
@@ -642,7 +709,7 @@ function mappedMembership(mapped, scopes, name) {
642
709
  }
643
710
  function positiveInteger(value, label) {
644
711
  const number = typeof value === "string" ? Number(value) : value;
645
- if (typeof number !== "number" || !Number.isInteger(number) || number <= 0) throw new Error(`PermDock CLI: ${label} must be a positive integer`);
712
+ if (typeof number !== "number" || !Number.isInteger(number) || number <= 0) throw configError(`PermDock CLI: ${label} must be a positive integer`);
646
713
  return number;
647
714
  }
648
715
  /**
@@ -653,12 +720,12 @@ function activeFromSql(input, root) {
653
720
  const spec = input ?? `app_metadata.active_${root}`;
654
721
  if (typeof spec === "string" && spec.startsWith("app_metadata.")) {
655
722
  const key = spec.slice(13);
656
- if (!/^[A-Za-z_][A-Za-z0-9_]*$/u.test(key)) throw new Error(`PermDock CLI: unsafe app_metadata key '${key}'`);
723
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/u.test(key)) throw configError(`PermDock CLI: unsafe app_metadata key '${key}'`);
657
724
  return { sql: `claims -> 'app_metadata' ->> ${quoteLiteral(key)}` };
658
725
  }
659
726
  const parsed = typeof spec === "string" ? (() => {
660
727
  const dot = spec.lastIndexOf(".");
661
- if (dot <= 0) throw new Error(`PermDock CLI: --active-from must be app_metadata.<key> or <table>.<column>, got '${spec}'`);
728
+ if (dot <= 0) throw configError(`PermDock CLI: --active-from must be app_metadata.<key> or <table>.<column>, got '${spec}'`);
662
729
  return {
663
730
  table: spec.slice(0, dot),
664
731
  column: spec.slice(dot + 1)
@@ -684,16 +751,16 @@ function typedUsers(parts) {
684
751
  ].map((line) => `\n ${line}`).join("");
685
752
  }
686
753
  function checkSources(parts) {
687
- if (parts.sources.length === 0) throw new Error("PermDock CLI: supabase.hook.memberships needs at least one fromTable or fromJunction source");
754
+ if (parts.sources.length === 0) throw configError("PermDock CLI: supabase.hook.memberships needs at least one tableMembershipSource or junctionMembershipSource source");
688
755
  for (const source of parts.sources) {
689
- if (source.sql === void 0 || typeof source.sql.select !== "function") throw new Error("PermDock CLI: supabase.hook.memberships takes fromTable / fromJunction sources from permdock/supabase");
756
+ if (source.sql === void 0 || typeof source.sql.select !== "function") throw configError("PermDock CLI: supabase.hook.memberships takes tableMembershipSource / junctionMembershipSource sources from permdock/supabase");
690
757
  const scope = source.sql.scope;
691
758
  if (scope === void 0) continue;
692
- if (resolveScope(parts.scopes, scope) !== scope) throw new Error(`PermDock CLI: the ${source.sql.table} source names scope '${scope}', which the policy does not declare by that name`);
759
+ if (resolveScope(parts.scopes, scope) !== scope) throw configError(`PermDock CLI: the ${source.sql.table} source names scope '${scope}', which the policy does not declare by that name`);
693
760
  const chain = scopeChain(parts.scopes, scope);
694
761
  const holds = new Set(source.sql.holds ?? [scope]);
695
762
  const missing = chain.filter((ancestor) => !holds.has(ancestor));
696
- if (missing.length > 0) throw new Error(`PermDock CLI: the ${source.sql.table} source needs within columns for ${missing.join(", ")}: a ${scope} membership without every ancestor grants nothing`);
763
+ if (missing.length > 0) throw configError(`PermDock CLI: the ${source.sql.table} source needs within columns for ${missing.join(", ")}: a ${scope} membership without every ancestor grants nothing`);
697
764
  }
698
765
  }
699
766
  function entriesSql(parts) {
@@ -702,9 +769,9 @@ function entriesSql(parts) {
702
769
  (${tenant}) as tenant,
703
770
  jsonb_strip_nulls(jsonb_build_object(
704
771
  'scope', s.scope, 'id', s.id, 'within', s.within, 'roles', s.roles, 'via', s.via,
705
- 'expiresAt', s.expires_at, 'grantedBy', s.granted_by, 'reason', s.reason,
772
+ 'expires_at', s.expires_at, 'granted_by', s.granted_by, 'reason', s.reason,
706
773
  'member', case when s.member_group is not null then jsonb_build_object('group', s.member_group) end,
707
- 'managedBy', s.managed_by, 'entitlements', s.seats${source.sql.keeps ? ", 'keep', s.keep" : ""}
774
+ 'managed_by', s.managed_by, 'entitlements', s.seats${source.sql.keeps ? ", 'keep', s.keep" : ""}
708
775
  )) as entry
709
776
  from (
710
777
  ${source.sql.select(`v_user_${String(index)}`).replaceAll(/^/gmu, " ")}
@@ -785,7 +852,7 @@ function apiWrappersSql(parts) {
785
852
  const source = quoteIdent(parts.schema);
786
853
  const wrapper = (name, inner, params, args, types, returns) => {
787
854
  const fn = `${quoteIdent(target)}.${quoteIdent(name)}`;
788
- if (target === parts.schema && name === inner) throw new Error(`PermDock CLI: supabase.hook.api would replace ${source}.${inner}: set api.schema or api.prefix`);
855
+ if (target === parts.schema && name === inner) throw configError(`PermDock CLI: supabase.hook.api would replace ${source}.${inner}: set api.schema or api.prefix`);
789
856
  return `create or replace function ${fn}(${params})
790
857
  returns ${returns}
791
858
  language sql
@@ -856,9 +923,9 @@ as $$
856
923
  'principal', jsonb_build_object('id', s.user_id),
857
924
  'membership', jsonb_strip_nulls(jsonb_build_object(
858
925
  'scope', s.scope, 'id', s.id, 'within', s.within, 'roles', s.roles, 'via', s.via,
859
- 'expiresAt', s.expires_at, 'grantedBy', s.granted_by, 'reason', s.reason,
926
+ 'expires_at', s.expires_at, 'granted_by', s.granted_by, 'reason', s.reason,
860
927
  'member', case when s.member_group is not null then jsonb_build_object('group', s.member_group) end,
861
- 'managedBy', s.managed_by, 'entitlements', s.seats${keep ? ", 'keep', s.keep" : ""}
928
+ 'managed_by', s.managed_by, 'entitlements', s.seats${keep ? ", 'keep', s.keep" : ""}
862
929
  ))
863
930
  ) order by s.user_id, s.roles::text), '[]'::jsonb)
864
931
  from (
@@ -868,9 +935,10 @@ $$;
868
935
  revoke execute on function ${fn}(text, text) from public, anon, authenticated;`;
869
936
  }
870
937
  /**
871
- * The custom roles `p_user` holds: tenant roles named on a membership of the
872
- * scope (and instance) they live at, and platform roles among the global
873
- * roles, each with its grants and includes in `CustomRole` form.
938
+ * The custom roles `p_user` holds: tenant and shared roles named on a
939
+ * membership of the scope (and instance) they live at, and platform roles
940
+ * among the global roles, each with its grants and includes in `CustomRole`
941
+ * form. A shadowed tenant role comes along; `resolveCustomRole`'s callers drop it.
874
942
  */
875
943
  function customRolesOfSql(parts) {
876
944
  const helpers = quoteIdent(parts.helpers.schema);
@@ -881,6 +949,7 @@ function customRolesOfSql(parts) {
881
949
  return `
882
950
  select coalesce(jsonb_agg(jsonb_strip_nulls(jsonb_build_object(
883
951
  'name', c.role,
952
+ 'shared', case when c.tenant_id is null and c.scope <> 'global' then true end,
884
953
  'tenant', c.tenant_id::text,
885
954
  'scope', c.scope,
886
955
  'id', c.scope_id,
@@ -911,11 +980,816 @@ function customRolesOfSql(parts) {
911
980
  from jsonb_array_elements(memberships) m
912
981
  where m -> 'roles' ? c.role
913
982
  and m ->> 'scope' = c.scope
914
- and c.tenant_id::text = case when m ->> 'scope' = ${root} then m ->> 'id' else m -> 'within' ->> ${root} end
983
+ and (
984
+ (c.tenant_id is null and c.scope_id is null)
985
+ or c.tenant_id::text = case when m ->> 'scope' = ${root} then m ->> 'id' else m -> 'within' ->> ${root} end
986
+ )
915
987
  and (c.scope_id is null or c.scope_id = m ->> 'id')
916
988
  );`;
917
989
  }
918
990
  //#endregion
991
+ //#region src/cli/rls/approvals.ts
992
+ /** `rls.approvals`' approver check; better-supabase's `isApprover` template calls it. */
993
+ const IS_APPROVER = "permdock_is_approver";
994
+ /** The objects `rls.approvals` adds. Names are part of the SQL contract `supabaseApprovalStore` calls. */
995
+ const APPROVAL_STORE = {
996
+ table: "approval_requests",
997
+ open: "permdock_approval_open",
998
+ get: "permdock_approval_get",
999
+ resolve: "permdock_approval_resolve",
1000
+ consume: "permdock_approval_consume",
1001
+ list: "permdock_approval_list",
1002
+ expire: "permdock_approval_expire",
1003
+ cancel: "permdock_approval_cancel",
1004
+ isApprover: IS_APPROVER
1005
+ };
1006
+ /** `body` as the row's filter columns hold it. */
1007
+ const COLUMNS = `p_request ->> 'token',
1008
+ p_request,
1009
+ p_request ->> 'status',
1010
+ p_request #>> '{subject,principal,tenant}',
1011
+ p_request #>> '{subject,principal,id}',
1012
+ p_request #>> '{subject,actor,id}',
1013
+ p_request #>> '{subject,session}',
1014
+ coalesce(jsonb_array_length(p_request -> 'approvals'), 0),
1015
+ p_request ->> 'createdAt',
1016
+ (p_request ->> 'expiresAt')::timestamptz,
1017
+ (p_request ->> 'consumedAt')::timestamptz`;
1018
+ /** The `ApprovalListFilter` in `p_filter`, over the row aliased `a`. */
1019
+ const FILTER = `(p_filter ->> 'status' is null or a.status = p_filter ->> 'status')
1020
+ and (p_filter ->> 'tenant' is null or a.tenant = p_filter ->> 'tenant')
1021
+ and (p_filter ->> 'principalId' is null or a.principal_id = p_filter ->> 'principalId')
1022
+ and (p_filter ->> 'actorId' is null or a.actor_id = p_filter ->> 'actorId')
1023
+ and (p_filter ->> 'session' is null or a.session = p_filter ->> 'session')`;
1024
+ function serverOnly(fn, args) {
1025
+ return `revoke execute on function ${fn}(${args}) from public, anon, authenticated;`;
1026
+ }
1027
+ const BODY_CHECK = `${APPROVAL_STORE.table}_body_schema`;
1028
+ /**
1029
+ * `rls.jsonSchema`: a pg_jsonschema check that `body` matches
1030
+ * `approval-request-v1.json`, added `not valid` and then validated. `auto`
1031
+ * adds it only where the extension is available.
1032
+ */
1033
+ function bodySchemaSql(table, mode, body = "body", check = BODY_CHECK) {
1034
+ const statements = [
1035
+ "create schema if not exists extensions;",
1036
+ "create extension if not exists pg_jsonschema with schema extensions;",
1037
+ `alter table ${table} drop constraint if exists "${check}";`,
1038
+ `alter table ${table} add constraint "${check}" check (extensions.jsonb_matches_schema(${quoteLiteral(JSON.stringify(APPROVAL_REQUEST_SCHEMA))}::json, ${body})) not valid;`,
1039
+ `alter table ${table} validate constraint "${check}";`
1040
+ ];
1041
+ const comment = "-- approval store: body matches schemas/approval-request-v1.json through pg_jsonschema";
1042
+ if (mode === true) return `\n${comment}\n${statements.join("\n")}\n`;
1043
+ return `\n${comment}, where the extension is available
1044
+ do $permdock$
1045
+ begin
1046
+ if exists (select 1 from pg_available_extensions where name = 'pg_jsonschema') then
1047
+ ${statements.map((statement) => ` ${statement}`).join("\n")}
1048
+ end if;
1049
+ end
1050
+ $permdock$;\n`;
1051
+ }
1052
+ /**
1053
+ * The approval store (`rls.approvals`): one table of `ApprovalRequest`
1054
+ * bodies with the columns a list filters on, and one `security definer`
1055
+ * function per `ApprovalStore` method, each one atomic statement. No client
1056
+ * role may read the table or execute the functions: approvals are opened and
1057
+ * resolved by the server, which grants its own role.
1058
+ */
1059
+ function approvalStoreSql(ctx, jsonSchema = false) {
1060
+ const table = qualified(ctx, APPROVAL_STORE.table);
1061
+ const fn = (name) => qualified(ctx, name);
1062
+ const index = (name) => `"${APPROVAL_STORE.table}_${name}"`;
1063
+ return `-- approval store: ApprovalRequest bodies, opened, resolved and consumed through the functions below; supabaseApprovalStore calls them
1064
+ create table if not exists ${table} (
1065
+ token text primary key,
1066
+ body jsonb not null,
1067
+ status text not null check (status in ('pending', 'approved', 'rejected', 'expired')),
1068
+ tenant text,
1069
+ principal_id text,
1070
+ actor_id text,
1071
+ session text,
1072
+ approvals integer not null default 0,
1073
+ created_at text not null,
1074
+ expires_at timestamptz not null,
1075
+ consumed_at timestamptz
1076
+ );
1077
+ create index if not exists ${index("page")} on ${table} (created_at collate "C", token collate "C");
1078
+ create index if not exists ${index("pending")} on ${table} (status, expires_at);
1079
+ alter table ${table} enable row level security;
1080
+ revoke all on table ${table} from anon, authenticated, public;
1081
+ ${jsonSchema === false ? "" : bodySchemaSql(table, jsonSchema)}
1082
+ -- open a request; a repeated ask keeps the open record and replaces only an expired one
1083
+ create or replace function ${fn(APPROVAL_STORE.open)}(p_request jsonb)
1084
+ returns void
1085
+ language sql
1086
+ volatile
1087
+ security definer
1088
+ set search_path = ''
1089
+ as $$
1090
+ insert into ${table} as a (token, body, status, tenant, principal_id, actor_id, session, approvals, created_at, expires_at, consumed_at)
1091
+ values (
1092
+ ${COLUMNS}
1093
+ )
1094
+ on conflict (token) do update
1095
+ set body = excluded.body, status = excluded.status, tenant = excluded.tenant,
1096
+ principal_id = excluded.principal_id, actor_id = excluded.actor_id, session = excluded.session,
1097
+ approvals = excluded.approvals, created_at = excluded.created_at,
1098
+ expires_at = excluded.expires_at, consumed_at = excluded.consumed_at
1099
+ where a.status = 'expired' or a.expires_at <= now()
1100
+ $$;
1101
+ ${serverOnly(fn(APPROVAL_STORE.open), "jsonb")}
1102
+
1103
+ create or replace function ${fn(APPROVAL_STORE.get)}(p_token text)
1104
+ returns jsonb
1105
+ language sql
1106
+ stable
1107
+ security definer
1108
+ set search_path = ''
1109
+ as $$
1110
+ select a.body from ${table} a where a.token = p_token
1111
+ $$;
1112
+ ${serverOnly(fn(APPROVAL_STORE.get), "text")}
1113
+
1114
+ -- write a verdict over the record it was computed from: still pending, unexpired, with p_seen approvals
1115
+ create or replace function ${fn(APPROVAL_STORE.resolve)}(p_token text, p_next jsonb, p_seen integer)
1116
+ returns jsonb
1117
+ language sql
1118
+ volatile
1119
+ security definer
1120
+ set search_path = ''
1121
+ as $$
1122
+ update ${table} a
1123
+ set body = p_next,
1124
+ status = p_next ->> 'status',
1125
+ approvals = coalesce(jsonb_array_length(p_next -> 'approvals'), 0)
1126
+ where a.token = p_token
1127
+ and a.status = 'pending'
1128
+ and a.expires_at > now()
1129
+ and a.approvals = p_seen
1130
+ returning a.body
1131
+ $$;
1132
+ ${serverOnly(fn(APPROVAL_STORE.resolve), "text, jsonb, integer")}
1133
+
1134
+ -- consume an approved, unexpired, unconsumed request once
1135
+ create or replace function ${fn(APPROVAL_STORE.consume)}(p_token text, p_now text)
1136
+ returns jsonb
1137
+ language sql
1138
+ volatile
1139
+ security definer
1140
+ set search_path = ''
1141
+ as $$
1142
+ update ${table} a
1143
+ set consumed_at = p_now::timestamptz,
1144
+ body = a.body || jsonb_build_object('consumedAt', p_now)
1145
+ where a.token = p_token
1146
+ and a.status = 'approved'
1147
+ and a.consumed_at is null
1148
+ and a.expires_at > p_now::timestamptz
1149
+ returning a.body
1150
+ $$;
1151
+ ${serverOnly(fn(APPROVAL_STORE.consume), "text, text")}
1152
+
1153
+ -- one page in (createdAt, token) order after the cursor position
1154
+ create or replace function ${fn(APPROVAL_STORE.list)}(p_filter jsonb, p_after_created text, p_after_token text, p_limit integer)
1155
+ returns setof jsonb
1156
+ language sql
1157
+ stable
1158
+ security definer
1159
+ set search_path = ''
1160
+ as $$
1161
+ select a.body
1162
+ from ${table} a
1163
+ where ${FILTER}
1164
+ and (p_after_created is null
1165
+ or (a.created_at collate "C", a.token collate "C") > (p_after_created collate "C", p_after_token collate "C"))
1166
+ order by a.created_at collate "C", a.token collate "C"
1167
+ limit p_limit
1168
+ $$;
1169
+ ${serverOnly(fn(APPROVAL_STORE.list), "jsonb, text, text, integer")}
1170
+
1171
+ create or replace function ${fn(APPROVAL_STORE.expire)}(p_now text)
1172
+ returns integer
1173
+ language sql
1174
+ volatile
1175
+ security definer
1176
+ set search_path = ''
1177
+ as $$
1178
+ with expired as (
1179
+ update ${table} a
1180
+ set status = 'expired', body = a.body || '{"status":"expired"}'::jsonb
1181
+ where a.status = 'pending' and a.expires_at <= p_now::timestamptz
1182
+ returning 1
1183
+ )
1184
+ select count(*)::integer from expired
1185
+ $$;
1186
+ ${serverOnly(fn(APPROVAL_STORE.expire), "text")}
1187
+
1188
+ -- reject every pending request the filter matches, as system:<by>
1189
+ create or replace function ${fn(APPROVAL_STORE.cancel)}(p_filter jsonb, p_by text, p_note text, p_now text)
1190
+ returns integer
1191
+ language sql
1192
+ volatile
1193
+ security definer
1194
+ set search_path = ''
1195
+ as $$
1196
+ with cancelled as (
1197
+ update ${table} a
1198
+ set status = 'rejected',
1199
+ body = (a.body - 'note') || jsonb_strip_nulls(jsonb_build_object(
1200
+ 'status', 'rejected', 'resolvedAt', p_now, 'resolvedBy', 'system:' || p_by, 'note', p_note
1201
+ ))
1202
+ where a.status = 'pending'
1203
+ and ${FILTER.replaceAll("\n ", "\n ")}
1204
+ returning 1
1205
+ )
1206
+ select count(*)::integer from cancelled
1207
+ $$;
1208
+ ${serverOnly(fn(APPROVAL_STORE.cancel), "jsonb, text, text, text")}
1209
+ `;
1210
+ }
1211
+ /**
1212
+ * `permdock_is_approver(p_approvers jsonb, p_tenant text default null)`: true
1213
+ * when the signed-in user matches the serialised approvers of a request
1214
+ * (`approvers.by`), for policies on the app's own approval views. A list
1215
+ * needs every item and `any-of` one of them; `user` compares the user id and
1216
+ * `holder(permission)` reads `permitted_<root>_ids_by_permission` in
1217
+ * `p_tenant`, or `permdock_has_permission` without one. Role, relation and
1218
+ * other grantee approvers need facts the database does not hold for every
1219
+ * membership source, so they match nobody.
1220
+ */
1221
+ function isApproverSql(ctx) {
1222
+ const fn = qualified(ctx, APPROVAL_STORE.isApprover);
1223
+ const root = ctx.scopes[0]?.name;
1224
+ const inTenant = root === void 0 ? "false" : `exists (
1225
+ select 1
1226
+ from ${qualified(ctx, checkName(permittedByPermissionHelper(root)))}(p_approvers ->> 'permission') i(id)
1227
+ where i.id::text = p_tenant
1228
+ )`;
1229
+ return `
1230
+ -- approvers: whether the signed-in user matches a request's approvers.by; policies on approval views call it
1231
+ create or replace function ${fn}(p_approvers jsonb, p_tenant text default null)
1232
+ returns boolean
1233
+ language plpgsql
1234
+ stable
1235
+ security definer
1236
+ set search_path = ''
1237
+ as $$
1238
+ declare
1239
+ v_item jsonb;
1240
+ v_user text := ${subjectIdSql(ctx)}::text;
1241
+ begin
1242
+ if coalesce(v_user, '') = '' or p_approvers is null then
1243
+ return false;
1244
+ end if;
1245
+ if jsonb_typeof(p_approvers) = 'array' then
1246
+ if jsonb_array_length(p_approvers) = 0 then
1247
+ return false;
1248
+ end if;
1249
+ for v_item in select value from jsonb_array_elements(p_approvers) loop
1250
+ if not ${fn}(v_item, p_tenant) then
1251
+ return false;
1252
+ end if;
1253
+ end loop;
1254
+ return true;
1255
+ end if;
1256
+ if jsonb_typeof(p_approvers) <> 'object' then
1257
+ return false;
1258
+ end if;
1259
+ case p_approvers ->> 'kind'
1260
+ when 'user' then
1261
+ return coalesce(p_approvers ->> 'id' = v_user, false);
1262
+ when 'permission' then
1263
+ if p_tenant is null then
1264
+ return coalesce(${qualified(ctx, PERMISSION_HELPERS.has)}(p_approvers ->> 'permission'), false);
1265
+ end if;
1266
+ return ${inTenant};
1267
+ when 'any-of' then
1268
+ if jsonb_typeof(p_approvers -> 'of') <> 'array' then
1269
+ return false;
1270
+ end if;
1271
+ for v_item in select value from jsonb_array_elements(p_approvers -> 'of') loop
1272
+ if ${fn}(v_item, p_tenant) then
1273
+ return true;
1274
+ end if;
1275
+ end loop;
1276
+ return false;
1277
+ else
1278
+ return false;
1279
+ end case;
1280
+ end;
1281
+ $$;
1282
+ revoke execute on function ${fn}(jsonb, text) from public, anon;
1283
+ grant execute on function ${fn}(jsonb, text) to authenticated;
1284
+ `;
1285
+ }
1286
+ /**
1287
+ * Everything `rls.approvals` writes: the generated or adopted store, the
1288
+ * approver check and, with `delegations: true`, the covers.
1289
+ */
1290
+ function approvalsSql(ctx, approvals, jsonSchema) {
1291
+ if (approvals === void 0 || approvals === false) return;
1292
+ if (approvals === true) return approvalStoreSql(ctx, jsonSchema) + isApproverSql(ctx);
1293
+ const { table, delegations } = approvals;
1294
+ if (delegations !== void 0 && typeof delegations !== "boolean") throw new TypeError("PermDock CLI: rls.approvals.delegations must be a boolean");
1295
+ let store;
1296
+ if (table === void 0) {
1297
+ const adoptOnly = [
1298
+ "token",
1299
+ "body",
1300
+ "open",
1301
+ "schema",
1302
+ "mirror"
1303
+ ].find((key) => approvals[key] !== void 0);
1304
+ if (adoptOnly !== void 0) throw configError(`PermDock CLI: rls.approvals.${adoptOnly} adopts a table; set rls.approvals.table too`);
1305
+ store = approvalStoreSql(ctx, jsonSchema);
1306
+ } else store = adoptedApprovalStoreSql(ctx, {
1307
+ ...approvals,
1308
+ table
1309
+ }, jsonSchema);
1310
+ return store + isApproverSql(ctx) + (delegations === true ? approvalDelegationsSql(ctx) : "");
1311
+ }
1312
+ /** The objects `rls.approvals.delegations` adds; `tableApprovalDelegationSource` reads the table. */
1313
+ const DELEGATIONS = {
1314
+ table: "approval_delegations",
1315
+ covers: "permdock_cover_approves"
1316
+ };
1317
+ /**
1318
+ * `rls.approvals.delegations`: one table of out-of-office covers that a
1319
+ * user writes for themselves, and a four-argument `permdock_is_approver`
1320
+ * that also answers for a delegate inside an active cover. One hop: a cover
1321
+ * is checked against the delegator's own approver rights, never the
1322
+ * delegator's covers. A delegator who is the requester never counts. A
1323
+ * `holder()` approver through a cover needs `rls.mode: "database"`, where
1324
+ * the `_for` helpers exist; in `jwt` mode only `user()` approvers pass.
1325
+ */
1326
+ function approvalDelegationsSql(ctx) {
1327
+ const table = qualified(ctx, DELEGATIONS.table);
1328
+ const fn = qualified(ctx, APPROVAL_STORE.isApprover);
1329
+ const covers = qualified(ctx, DELEGATIONS.covers);
1330
+ const user = ctx.dialect === "supabase" ? "uuid" : "text";
1331
+ const me = `${subjectIdSql(ctx)}::text`;
1332
+ const defaultMe = subjectIdSql(ctx).replace(/^\(select (.+)\)$/su, "$1");
1333
+ const root = ctx.scopes[0]?.name;
1334
+ const member = (tenant) => root === void 0 ? "false" : `${tenant} in (select m.id::text from ${qualified(ctx, memberIdsHelper(root))}() m(id))`;
1335
+ const database = ctx.authorize === "database";
1336
+ const holderGlobal = database ? `coalesce(${qualified(ctx, PERMISSION_HELPERS.hasFor)}(p_delegator, p_approvers ->> 'permission'), false)` : "false";
1337
+ const holderTenant = database && root !== void 0 ? `exists (
1338
+ select 1
1339
+ from ${qualified(ctx, checkName(`${permittedByPermissionHelper(root)}_for`))}(p_delegator, p_approvers ->> 'permission') i(id)
1340
+ where i.id::text = p_tenant
1341
+ )` : "false";
1342
+ const policy = (name) => quoteIdent(`${DELEGATIONS.table}_${name}`);
1343
+ const own = `delegator::text = ${me} and (tenant is null or ${member("tenant")})`;
1344
+ return `
1345
+ -- approval delegations: out-of-office covers a user writes for themselves; tableApprovalDelegationSource and permdock_is_approver read them
1346
+ create table if not exists ${table} (
1347
+ id text primary key default gen_random_uuid()::text,
1348
+ delegator ${user} not null default (${defaultMe})::${user},
1349
+ delegate ${user} not null,
1350
+ tenant text,
1351
+ permissions text[],
1352
+ resources text[],
1353
+ "from" timestamptz not null default now(),
1354
+ "until" timestamptz not null,
1355
+ created_at timestamptz not null default now(),
1356
+ constraint ${quoteIdent(`${DELEGATIONS.table}_window`)} check ("from" < "until"),
1357
+ constraint ${quoteIdent(`${DELEGATIONS.table}_distinct`)} check (delegator <> delegate)
1358
+ );
1359
+ create index if not exists ${quoteIdent(`${DELEGATIONS.table}_delegate`)} on ${table} (delegate, "until");
1360
+ alter table ${table} enable row level security;
1361
+ revoke all on table ${table} from anon, public;
1362
+ grant select, insert, update, delete on table ${table} to authenticated;
1363
+ drop policy if exists ${policy("read")} on ${table};
1364
+ create policy ${policy("read")} on ${table}
1365
+ for select to authenticated
1366
+ using (delegator::text = ${me} or delegate::text = ${me});
1367
+ drop policy if exists ${policy("insert")} on ${table};
1368
+ create policy ${policy("insert")} on ${table}
1369
+ for insert to authenticated
1370
+ with check (${own});
1371
+ drop policy if exists ${policy("update")} on ${table};
1372
+ create policy ${policy("update")} on ${table}
1373
+ for update to authenticated
1374
+ using (delegator::text = ${me})
1375
+ with check (${own});
1376
+ drop policy if exists ${policy("delete")} on ${table};
1377
+ create policy ${policy("delete")} on ${table}
1378
+ for delete to authenticated
1379
+ using (delegator::text = ${me});
1380
+
1381
+ -- approval delegations: whether a delegator's own rights match approvers.by; only permdock_is_approver calls it
1382
+ create or replace function ${covers}(p_delegator ${user}, p_approvers jsonb, p_tenant text)
1383
+ returns boolean
1384
+ language plpgsql
1385
+ stable
1386
+ security definer
1387
+ set search_path = ''
1388
+ as $$
1389
+ declare
1390
+ v_item jsonb;
1391
+ begin
1392
+ if p_delegator is null or p_approvers is null then
1393
+ return false;
1394
+ end if;
1395
+ if jsonb_typeof(p_approvers) = 'array' then
1396
+ if jsonb_array_length(p_approvers) = 0 then
1397
+ return false;
1398
+ end if;
1399
+ for v_item in select value from jsonb_array_elements(p_approvers) loop
1400
+ if not ${covers}(p_delegator, v_item, p_tenant) then
1401
+ return false;
1402
+ end if;
1403
+ end loop;
1404
+ return true;
1405
+ end if;
1406
+ if jsonb_typeof(p_approvers) <> 'object' then
1407
+ return false;
1408
+ end if;
1409
+ case p_approvers ->> 'kind'
1410
+ when 'user' then
1411
+ return coalesce(p_approvers ->> 'id' = p_delegator::text, false);
1412
+ when 'permission' then
1413
+ if p_tenant is null then
1414
+ return ${holderGlobal};
1415
+ end if;
1416
+ return ${holderTenant};
1417
+ when 'any-of' then
1418
+ if jsonb_typeof(p_approvers -> 'of') <> 'array' then
1419
+ return false;
1420
+ end if;
1421
+ for v_item in select value from jsonb_array_elements(p_approvers -> 'of') loop
1422
+ if ${covers}(p_delegator, v_item, p_tenant) then
1423
+ return true;
1424
+ end if;
1425
+ end loop;
1426
+ return false;
1427
+ else
1428
+ return false;
1429
+ end case;
1430
+ end;
1431
+ $$;
1432
+ revoke execute on function ${covers}(${user}, jsonb, text) from public, anon, authenticated;
1433
+
1434
+ -- approvers with covers: the signed-in user, or a delegate inside an active cover of a delegator who is not the requester
1435
+ create or replace function ${fn}(p_approvers jsonb, p_tenant text, p_requester text, p_resource text)
1436
+ returns boolean
1437
+ language plpgsql
1438
+ stable
1439
+ security definer
1440
+ set search_path = ''
1441
+ as $$
1442
+ declare
1443
+ v_delegator ${user};
1444
+ v_user text := ${me};
1445
+ begin
1446
+ if ${fn}(p_approvers, p_tenant) then
1447
+ return true;
1448
+ end if;
1449
+ if coalesce(v_user, '') = '' or coalesce(p_requester, '') = '' then
1450
+ return false;
1451
+ end if;
1452
+ if p_tenant is not null and not (${member("p_tenant")}) then
1453
+ return false;
1454
+ end if;
1455
+ for v_delegator in
1456
+ select d.delegator
1457
+ from ${table} d
1458
+ where d.delegate::text = v_user
1459
+ and d.delegator::text <> v_user
1460
+ and d.delegator::text <> p_requester
1461
+ and d."from" <= now()
1462
+ and now() < d."until"
1463
+ and (d.tenant is null or d.tenant = p_tenant)
1464
+ and d.permissions is null
1465
+ and (d.resources is null or p_resource = any (d.resources))
1466
+ loop
1467
+ if ${covers}(v_delegator, p_approvers, p_tenant) then
1468
+ return true;
1469
+ end if;
1470
+ end loop;
1471
+ return false;
1472
+ end;
1473
+ $$;
1474
+ revoke execute on function ${fn}(jsonb, text, text, text) from public, anon;
1475
+ grant execute on function ${fn}(jsonb, text, text, text) to authenticated;
1476
+ `;
1477
+ }
1478
+ /** The request fields an adopted table may mirror into its own columns, as SQL over a body. */
1479
+ const MIRRORS = {
1480
+ status: (body) => `${body} ->> 'status'`,
1481
+ permission: (body) => `${body} ->> 'permission'`,
1482
+ tenant: (body) => `${body} #>> '{subject,principal,tenant}'`,
1483
+ principalId: (body) => `${body} #>> '{subject,principal,id}'`,
1484
+ actorId: (body) => `${body} #>> '{subject,actor,id}'`,
1485
+ session: (body) => `${body} #>> '{subject,session}'`,
1486
+ approvals: (body) => `coalesce(jsonb_array_length(${body} -> 'approvals'), 0)`,
1487
+ createdAt: (body) => `${body} ->> 'createdAt'`,
1488
+ expiresAt: (body) => `${body} ->> 'expiresAt'`,
1489
+ resolvedAt: (body) => `${body} ->> 'resolvedAt'`,
1490
+ resolvedBy: (body) => `${body} ->> 'resolvedBy'`,
1491
+ consumedAt: (body) => `${body} ->> 'consumedAt'`
1492
+ };
1493
+ function mirrorsOf(adopt) {
1494
+ const out = [];
1495
+ for (const [field, column] of Object.entries(adopt.mirror ?? {})) {
1496
+ if (!Object.hasOwn(MIRRORS, field)) throw configError(`PermDock CLI: rls.approvals.mirror.${field} is not a request field; use ${Object.keys(MIRRORS).join(", ")}`);
1497
+ if (typeof column !== "string") throw new TypeError(`PermDock CLI: rls.approvals.mirror.${field} must be a column name`);
1498
+ out.push([column, field]);
1499
+ }
1500
+ return out;
1501
+ }
1502
+ /** One row of `table` from `values` (column, SQL), each converted to the column's own type. */
1503
+ function populate(table, values) {
1504
+ return `pg_catalog.jsonb_populate_record(null::${table}, pg_catalog.jsonb_build_object(${values.map(([column, sql]) => `${quoteLiteral(column)}, ${sql}`).join(", ")}))`;
1505
+ }
1506
+ /** The mirrored columns' values for the request `body`. */
1507
+ function mirrorRow(table, mirror, body) {
1508
+ return populate(table, mirror.map(([column, field]) => [column, MIRRORS[field](body)]));
1509
+ }
1510
+ /** `, (<mirrored columns>) = (select …)` for an update that writes `body`; empty without mirrors. */
1511
+ function mirrorSet(table, mirror, body) {
1512
+ if (mirror.length === 0) return "";
1513
+ const columns = mirror.map(([column]) => quoteIdent(column));
1514
+ const picked = columns.map((column) => `r.${column}`).join(", ");
1515
+ return `,\n ${columns.length === 1 ? columns[0] ?? "" : `(${columns.join(", ")})`} = (select ${picked} from ${mirrorRow(table, mirror, body)} r)`;
1516
+ }
1517
+ /**
1518
+ * The approval store over a table the app already has (`rls.approvals:
1519
+ * { table, token?, body?, mirror? }`): the same `permdock_approval_*`
1520
+ * functions, reading every field from the request body in `body`, keyed by
1521
+ * the text column `token`, and copying the fields `mirror` names into the
1522
+ * app's own columns, converted to their types, on every write. The table's
1523
+ * grants and policies stay the app's.
1524
+ */
1525
+ function adoptedApprovalStoreSql(ctx, adopt, jsonSchema = false) {
1526
+ const table = quoteQualified(adopt.table);
1527
+ const base = adopt.table.split(".").at(-1) ?? adopt.table;
1528
+ const token = quoteIdent(adopt.token ?? "token");
1529
+ const body = quoteIdent(adopt.body ?? "body");
1530
+ const mirror = mirrorsOf(adopt);
1531
+ for (const [column] of mirror) if (column === (adopt.token ?? "token") || column === (adopt.body ?? "body")) throw configError(`PermDock CLI: rls.approvals.mirror names the ${column} column, which already holds the token or the body`);
1532
+ const open = adopt.open ?? "insert";
1533
+ if (open !== "insert" && open !== "attach") throw configError(`PermDock CLI: rls.approvals.open must be 'insert' or 'attach', got '${String(open)}'`);
1534
+ const schema = adopt.schema;
1535
+ if (schema !== void 0 && (typeof schema !== "string" || schema === "")) throw configError("PermDock CLI: rls.approvals.schema must be a schema name");
1536
+ const fn = (name) => schema === void 0 ? qualified(ctx, name) : `${quoteIdent(schema)}.${name}`;
1537
+ const index = (name) => quoteIdent(`${base}_permdock_${name}`);
1538
+ const status = `a.${body} ->> 'status'`;
1539
+ const expires = `(a.${body} ->> 'expiresAt')::timestamptz`;
1540
+ const created = `(a.${body} ->> 'createdAt')`;
1541
+ const filter = `(p_filter ->> 'status' is null or ${status} = p_filter ->> 'status')
1542
+ and (p_filter ->> 'tenant' is null or a.${body} #>> '{subject,principal,tenant}' = p_filter ->> 'tenant')
1543
+ and (p_filter ->> 'principalId' is null or a.${body} #>> '{subject,principal,id}' = p_filter ->> 'principalId')
1544
+ and (p_filter ->> 'actorId' is null or a.${body} #>> '{subject,actor,id}' = p_filter ->> 'actorId')
1545
+ and (p_filter ->> 'session' is null or a.${body} #>> '{subject,session}' = p_filter ->> 'session')`;
1546
+ const columns = [
1547
+ token,
1548
+ body,
1549
+ ...mirror.map(([column]) => quoteIdent(column))
1550
+ ];
1551
+ const inserted = populate(table, [
1552
+ [adopt.token ?? "token", "p_request ->> 'token'"],
1553
+ [adopt.body ?? "body", "p_request"],
1554
+ ...mirror.map(([column, field]) => [column, MIRRORS[field]("p_request")])
1555
+ ]);
1556
+ const insertSql = `-- open a request; a repeated ask keeps the open record and replaces only an expired one
1557
+ create or replace function ${fn(APPROVAL_STORE.open)}(p_request jsonb)
1558
+ returns void
1559
+ language sql
1560
+ volatile
1561
+ security definer
1562
+ set search_path = ''
1563
+ as $$
1564
+ insert into ${table} as a (${columns.join(", ")})
1565
+ select ${columns.map((column) => `r.${column}`).join(", ")}
1566
+ from ${inserted} r
1567
+ on conflict (${token}) do update
1568
+ set ${columns.filter((column) => column !== token).map((column) => `${column} = excluded.${column}`).join(", ")}
1569
+ where ${status} = 'expired' or ${expires} <= now()
1570
+ $$;`;
1571
+ const attachSql = `create or replace function ${fn(APPROVAL_STORE.open)}(p_request jsonb)
1572
+ returns void
1573
+ language plpgsql
1574
+ volatile
1575
+ security definer
1576
+ set search_path = ''
1577
+ as $$
1578
+ begin
1579
+ update ${table} a
1580
+ set ${body} = p_request${mirrorSet(table, mirror, "p_request")}
1581
+ where a.${token} = p_request ->> 'token'
1582
+ and (a.${body} is null or ${status} = 'expired' or ${expires} <= now());
1583
+ if not found and not exists (select 1 from ${table} a where a.${token} = p_request ->> 'token') then
1584
+ raise exception using
1585
+ errcode = 'P0002',
1586
+ message = 'permdock: no row of ${adopt.table.replaceAll("'", "''")} holds approval token ' || (p_request ->> 'token'),
1587
+ hint = 'insert the application row with this token before opening the request';
1588
+ end if;
1589
+ end;
1590
+ $$;`;
1591
+ return `-- approval store adopted onto ${adopt.table}: ApprovalRequest bodies in ${adopt.body ?? "body"}, keyed by ${adopt.token ?? "token"}; supabaseApprovalStore calls the functions below
1592
+ alter table ${table} add column if not exists ${token} text;
1593
+ alter table ${table} add column if not exists ${body} jsonb;
1594
+ create unique index if not exists ${index("token")} on ${table} (${token});
1595
+ create index if not exists ${index("page")} on ${table} ((${body} ->> 'createdAt') collate "C", ${token} collate "C");
1596
+ create index if not exists ${index("status")} on ${table} ((${body} ->> 'status'));
1597
+ ${jsonSchema === false ? "" : bodySchemaSql(table, jsonSchema, body, `${base}_permdock_body_schema`)}
1598
+ ${open === "attach" ? attachSql : insertSql}
1599
+ ${serverOnly(fn(APPROVAL_STORE.open), "jsonb")}
1600
+
1601
+ create or replace function ${fn(APPROVAL_STORE.get)}(p_token text)
1602
+ returns jsonb
1603
+ language sql
1604
+ stable
1605
+ security definer
1606
+ set search_path = ''
1607
+ as $$
1608
+ select a.${body} from ${table} a where a.${token} = p_token
1609
+ $$;
1610
+ ${serverOnly(fn(APPROVAL_STORE.get), "text")}
1611
+
1612
+ -- write a verdict over the record it was computed from: still pending, unexpired, with p_seen approvals
1613
+ create or replace function ${fn(APPROVAL_STORE.resolve)}(p_token text, p_next jsonb, p_seen integer)
1614
+ returns jsonb
1615
+ language sql
1616
+ volatile
1617
+ security definer
1618
+ set search_path = ''
1619
+ as $$
1620
+ update ${table} a
1621
+ set ${body} = p_next${mirrorSet(table, mirror, "p_next")}
1622
+ where a.${token} = p_token
1623
+ and ${status} = 'pending'
1624
+ and ${expires} > now()
1625
+ and coalesce(jsonb_array_length(a.${body} -> 'approvals'), 0) = p_seen
1626
+ returning a.${body}
1627
+ $$;
1628
+ ${serverOnly(fn(APPROVAL_STORE.resolve), "text, jsonb, integer")}
1629
+
1630
+ -- consume an approved, unexpired, unconsumed request once
1631
+ create or replace function ${fn(APPROVAL_STORE.consume)}(p_token text, p_now text)
1632
+ returns jsonb
1633
+ language sql
1634
+ volatile
1635
+ security definer
1636
+ set search_path = ''
1637
+ as $$
1638
+ update ${table} a
1639
+ set ${body} = a.${body} || jsonb_build_object('consumedAt', p_now)${mirrorSet(table, mirror, `(a.${body} || jsonb_build_object('consumedAt', p_now))`)}
1640
+ where a.${token} = p_token
1641
+ and ${status} = 'approved'
1642
+ and a.${body} ->> 'consumedAt' is null
1643
+ and ${expires} > p_now::timestamptz
1644
+ returning a.${body}
1645
+ $$;
1646
+ ${serverOnly(fn(APPROVAL_STORE.consume), "text, text")}
1647
+
1648
+ -- one page in (createdAt, token) order after the cursor position
1649
+ create or replace function ${fn(APPROVAL_STORE.list)}(p_filter jsonb, p_after_created text, p_after_token text, p_limit integer)
1650
+ returns setof jsonb
1651
+ language sql
1652
+ stable
1653
+ security definer
1654
+ set search_path = ''
1655
+ as $$
1656
+ select a.${body}
1657
+ from ${table} a
1658
+ where a.${body} is not null
1659
+ and ${filter}
1660
+ and (p_after_created is null
1661
+ or (${created} collate "C", a.${token} collate "C") > (p_after_created collate "C", p_after_token collate "C"))
1662
+ order by ${created} collate "C", a.${token} collate "C"
1663
+ limit p_limit
1664
+ $$;
1665
+ ${serverOnly(fn(APPROVAL_STORE.list), "jsonb, text, text, integer")}
1666
+
1667
+ create or replace function ${fn(APPROVAL_STORE.expire)}(p_now text)
1668
+ returns integer
1669
+ language sql
1670
+ volatile
1671
+ security definer
1672
+ set search_path = ''
1673
+ as $$
1674
+ with expired as (
1675
+ update ${table} a
1676
+ set ${body} = a.${body} || '{"status":"expired"}'::jsonb${mirrorSet(table, mirror, `(a.${body} || '{"status":"expired"}'::jsonb)`)}
1677
+ where ${status} = 'pending' and ${expires} <= p_now::timestamptz
1678
+ returning 1
1679
+ )
1680
+ select count(*)::integer from expired
1681
+ $$;
1682
+ ${serverOnly(fn(APPROVAL_STORE.expire), "text")}
1683
+
1684
+ -- reject every pending request the filter matches, as system:<by>
1685
+ create or replace function ${fn(APPROVAL_STORE.cancel)}(p_filter jsonb, p_by text, p_note text, p_now text)
1686
+ returns integer
1687
+ language sql
1688
+ volatile
1689
+ security definer
1690
+ set search_path = ''
1691
+ as $$
1692
+ with cancelled as (
1693
+ update ${table} a
1694
+ set ${body} = (a.${body} - 'note') || jsonb_strip_nulls(jsonb_build_object(
1695
+ 'status', 'rejected', 'resolvedAt', p_now, 'resolvedBy', 'system:' || p_by, 'note', p_note
1696
+ ))${mirrorSet(table, mirror, `((a.${body} - 'note') || jsonb_strip_nulls(jsonb_build_object('status', 'rejected', 'resolvedAt', p_now, 'resolvedBy', 'system:' || p_by, 'note', p_note)))`)}
1697
+ where ${status} = 'pending'
1698
+ and ${filter.replaceAll("\n ", "\n ")}
1699
+ returning 1
1700
+ )
1701
+ select count(*)::integer from cancelled
1702
+ $$;
1703
+ ${serverOnly(fn(APPROVAL_STORE.cancel), "jsonb, text, text, text")}
1704
+ `;
1705
+ }
1706
+ //#endregion
1707
+ //#region src/cli/rls/permission-holders.ts
1708
+ /** `permitted_<scope>_users(p_permission, p_id)`. Names are part of the SQL contract. */
1709
+ function permittedUsersHelper(scope) {
1710
+ return checkName(`permitted_${scope}_users`);
1711
+ }
1712
+ /** The users with a membership row on the instance `p_id`, or `undefined` when the scope's memberships are not all in tables. */
1713
+ function candidatesSql(ctx, scope, user) {
1714
+ const mapped = scopeTable(ctx, scope);
1715
+ if (mapped !== void 0) return {
1716
+ sql: ` select m.${quoteIdent(mapped.table.user)}::${user}
1717
+ from ${quoteQualified(mapped.table.table)} m
1718
+ where m.${quoteIdent(mapped.column)} = p_id`,
1719
+ keys: []
1720
+ };
1721
+ const sources = holderSources(ctx, scope);
1722
+ if (sources === void 0 || sources === "not-tables") return;
1723
+ return {
1724
+ sql: sources.map((source, index) => ` select h.user_id::${user}
1725
+ from (
1726
+ ${source.holders.rows(scope, { id: `v_key_${String(index)}` }).replaceAll(/^/gmu, " ")}
1727
+ ) h`).join("\n union\n"),
1728
+ keys: sources.map((source, index) => `v_key_${String(index)} ${source.holders.idType}`)
1729
+ };
1730
+ }
1731
+ /** `language sql`, or PL/pgSQL when a source's id column needs `p_id` in a `%type` variable. */
1732
+ function functionBody(keys, query) {
1733
+ if (keys.length === 0) return `language sql
1734
+ stable
1735
+ security definer
1736
+ set search_path = ''
1737
+ as $$
1738
+ ${query}
1739
+ $$;`;
1740
+ return `language plpgsql
1741
+ stable
1742
+ security definer
1743
+ set search_path = ''
1744
+ as $$
1745
+ declare
1746
+ ${keys.map((key) => ` ${key} := p_id;`).join("\n")}
1747
+ begin
1748
+ return query
1749
+ ${query.replaceAll(/^/gmu, " ")};
1750
+ end
1751
+ $$;`;
1752
+ }
1753
+ /**
1754
+ * One `permitted_<scope>_users(p_permission, p_id)` per scope in `database`
1755
+ * mode: the users who hold a permission key on one instance, read from the
1756
+ * scope's membership rows and the global-roles table and kept when
1757
+ * `permitted_<scope>_ids_by_permission_for` lists the instance or
1758
+ * `permdock_has_permission_for` answers, so denies, custom roles, expiry and
1759
+ * suspension apply as for the user. It names other users, so no client role
1760
+ * may execute it, and a backend role needs its own grant.
1761
+ */
1762
+ function permissionHoldersSql(ctx) {
1763
+ if (ctx.authorize !== "database") return "";
1764
+ const user = ctx.dialect === "supabase" ? "uuid" : "text";
1765
+ const global = globalRoleRows(ctx);
1766
+ const hasFor = qualified(ctx, PERMISSION_HELPERS.hasFor);
1767
+ return `${ctx.scopes.map(({ name }) => {
1768
+ const candidates = hasPermittedUsers(ctx, name) ? candidatesSql(ctx, name, user) : void 0;
1769
+ if (candidates === void 0) return `-- ${name}: no memberships table or table source, so permitted_${name}_users is not written`;
1770
+ const fn = qualified(ctx, permittedUsersHelper(name));
1771
+ const ids = qualified(ctx, checkName(`${permittedByPermissionHelper(name)}_for`));
1772
+ const type = scopeTypeOf(ctx, name);
1773
+ return `-- the users who hold a permission on one ${name}: its members and the global-role holders for whom the _for helpers answer; no client role may execute it
1774
+ create or replace function ${fn}(p_permission text, p_id ${type})
1775
+ returns setof ${user}
1776
+ ${functionBody(candidates.keys, ` select c.user_id
1777
+ from (
1778
+ ${candidates.sql}
1779
+ union
1780
+ select ${global.userSql}::${user}
1781
+ from ${global.from}
1782
+ ) c(user_id)
1783
+ where c.user_id is not null
1784
+ and (
1785
+ p_id in (select ${ids}(c.user_id, p_permission))
1786
+ or ${hasFor}(c.user_id, p_permission)
1787
+ )
1788
+ order by 1`)}
1789
+ revoke execute on function ${fn}(text, ${type}) from public, anon, authenticated;`;
1790
+ }).join("\n\n")}\n`;
1791
+ }
1792
+ //#endregion
919
1793
  //#region src/cli/supabase-hook/sql.ts
920
1794
  const MANAGED_TRIGGER = "permdock_protect_managed";
921
1795
  function hookSql(parts) {
@@ -927,14 +1801,14 @@ function hookSql(parts) {
927
1801
  from ${rows.from}
928
1802
  where ${rows.userSql} = v_roles_user;`;
929
1803
  const plan = parts.attrs;
930
- const fromTable = plan === void 0 || plan.columns.length === 0 || plan.table === void 0 ? "" : `
1804
+ const tableMembershipSource = plan === void 0 || plan.columns.length === 0 || plan.table === void 0 ? "" : `
931
1805
  select jsonb_strip_nulls(jsonb_build_object(${plan.columns.map((column) => `${quoteLiteral(column)}, to_jsonb(p.${quoteIdent(column)})`).join(", ")}))
932
1806
  into attrs
933
1807
  from ${quoteQualified(plan.table)} p
934
1808
  where p.${quoteIdent(plan.id)} = v_attrs_user;`;
935
1809
  const fromMeta = plan === void 0 || plan.meta.length === 0 ? "" : `
936
1810
  attrs := coalesce(attrs, '{}'::jsonb) || jsonb_strip_nulls(jsonb_build_object(${plan.meta.map((key) => `${quoteLiteral(key)}, claims -> 'app_metadata' -> ${quoteLiteral(key)}`).join(", ")}));`;
937
- const attrs = plan === void 0 ? "" : `${fromTable}${fromMeta}
1811
+ const attrs = plan === void 0 ? "" : `${tableMembershipSource}${fromMeta}
938
1812
  if attrs is not null and attrs <> '{}'::jsonb then
939
1813
  used := octet_length(attrs::text);
940
1814
  if used > budget then
@@ -1032,7 +1906,7 @@ function extraGrantsSql(extra) {
1032
1906
  }
1033
1907
  function beforeGrantsSql(before, extra) {
1034
1908
  const granted = new Set(extra.map((entry) => entry.fn.split(".")[0]));
1035
- return [...[...new Set(before.map((fn) => fn.split(".")[0] ?? ""))].filter((name) => !granted.has(name)).map((name) => `\ngrant usage on schema ${quoteIdent(name)} to supabase_auth_admin;`), ...before.map((fn) => `\ngrant execute on function ${quoteTable(fn)}(jsonb) to supabase_auth_admin;`)].join("");
1909
+ return [...[...new Set(before.map((fn) => beforeFirst(fn, ".")))].filter((name) => !granted.has(name)).map((name) => `\ngrant usage on schema ${quoteIdent(name)} to supabase_auth_admin;`), ...before.map((fn) => `\ngrant execute on function ${quoteTable(fn)}(jsonb) to supabase_auth_admin;`)].join("");
1036
1910
  }
1037
1911
  /**
1038
1912
  * Execute on each `member_<scope>_ids_for(uuid)` for the `hook.claims`
@@ -1104,7 +1978,7 @@ uri = "${hookUri(schema)}"`;
1104
1978
  const BUDGET_MEASURE = "octet_length(memberships::text) + octet_length(attrs::text)";
1105
1979
  function hookParts(scopes, config, overrides) {
1106
1980
  const hook = config.supabase?.hook;
1107
- if (hook === void 0) throw new Error("PermDock CLI: supabase hook generate needs supabase.hook in permdock.config.ts");
1981
+ if (hook === void 0) throw configError("PermDock CLI: supabase hook generate needs supabase.hook in permdock.config.ts");
1108
1982
  const root = rootScope(scopes) ?? "tenant";
1109
1983
  const suspension = checkSuspension(hook.suspension ?? config.rls?.suspension, scopes);
1110
1984
  const schema = overrides.schema ?? hook.schema ?? config.rls?.schema ?? "permdock";
@@ -1140,8 +2014,8 @@ function hookParts(scopes, config, overrides) {
1140
2014
  })
1141
2015
  }
1142
2016
  };
1143
- if (parts.attrs !== void 0 && parts.attrs.errors.length > 0) throw new Error(`PermDock CLI: ${parts.attrs.errors.join("; ")}`);
1144
- if (extraPlan.errors.length > 0) throw new Error(`PermDock CLI: ${extraPlan.errors.join("; ")}`);
2017
+ if (parts.attrs !== void 0 && parts.attrs.errors.length > 0) throw configError(`PermDock CLI: ${parts.attrs.errors.join("; ")}`);
2018
+ if (extraPlan.errors.length > 0) throw configError(`PermDock CLI: ${extraPlan.errors.join("; ")}`);
1145
2019
  quoteIdent(parts.tenantClaim);
1146
2020
  return parts;
1147
2021
  }
@@ -1453,11 +2327,14 @@ function trustedHelpers(parts, config, types, tenantType, policy) {
1453
2327
  const forUser = database ? [
1454
2328
  helperEntry(HELPERS.hasFor, "p_user uuid, p_grant text", "boolean", []),
1455
2329
  ...scopeTypes.map(([scope, type]) => helperEntry(permittedForHelper(scope), "p_user uuid, p_grant text", `setof ${type}`, [])),
1456
- ...scopeTypes.map(([scope, type]) => helperEntry(permissionKeysForHelper(scope), `p_user uuid, p_id ${type}`, "setof text", []))
2330
+ ...scopeTypes.map(([scope, type]) => helperEntry(permissionKeysForHelper(scope), `p_user uuid, p_id ${type}`, "setof text", [])),
2331
+ ...scopeTypes.filter(([scope]) => parts.helpers.holders.includes(scope)).map(([scope, type]) => helperEntry(permittedUsersHelper(scope), `p_permission text, p_id ${type}`, "setof uuid", []))
1457
2332
  ] : [];
1458
2333
  const keyed = [
1459
2334
  helperEntry(USER_ID_HELPER, "", "uuid", client),
1460
2335
  ...config.rls?.apiKeys === void 0 ? [] : [helperEntry(API_KEY_ALLOWS, "p_grant text", "boolean", client)],
2336
+ ...readOnlyActorKinds(config.rls?.readOnlyActors) === void 0 ? [] : [helperEntry(ACTOR_MAY_WRITE, "", "boolean", client)],
2337
+ ...!config.rls?.approvals ? [] : [helperEntry(IS_APPROVER, typeof config.rls.approvals === "object" && config.rls.approvals.delegations === true ? "p_approvers jsonb, p_tenant text, p_requester text, p_resource text" : "p_approvers jsonb, p_tenant text", "boolean", client)],
1461
2338
  ...forUser
1462
2339
  ];
1463
2340
  if (!(policy !== void 0 && (ownershipRules(policy, parts.scopes)?.assigns.length ?? 0) > 0)) return keyed;
@@ -1606,25 +2483,25 @@ function defaultOut(config) {
1606
2483
  //#region src/cli/supabase-hook/supabase-hook.ts
1607
2484
  const SUPABASE_HELP = `permdock supabase hook generate | inspect
1608
2485
 
1609
- hook generate [--out supabase/permdock-hook.sql] [--check] [--db <url>]
2486
+ hook generate [--out <file>] [--check] [--db-url-stdin] [--schemas pg-delta|migra|migrations]
1610
2487
  [--active-from app_metadata.active_<scope>|<table>.<column>]
1611
2488
  [--budget 1024] [--schema public] [--grants-out <file>|-]
1612
2489
  inspect [--json] [--out [permdock.manifest.json]] [--check]
1613
2490
 
1614
- Reads supabase.hook from permdock.config.ts: the fromTable / fromJunction sources the app
2491
+ Reads supabase.hook from permdock.config.ts: the tableMembershipSource / junctionMembershipSource sources the app
1615
2492
  passes as memberships. hook generate emits custom_access_token_hook(jsonb), the grants it
1616
- needs (the ones db diff drops in --grants-out, for declarative schemas), the permdock_authz_version table
1617
- and its triggers. Never grants anything to
2493
+ needs, the permdock_authz_version table and its triggers. Under pg-delta (the default) --out defaults to
2494
+ supabase/schemas/<schema>/functions/custom_access_token_hook.sql and the grants stay in it; under
2495
+ --schemas migra, --grants-out takes the privileges supabase db diff drops. Never grants anything to
1618
2496
  service_role unless supabase.hook.api is set, which grants its wrappers to it. inspect prints the manifest: helper schema, names and signatures, tenant claim,
1619
2497
  budget, the claims the hook writes, the membership sources and the columns that decide them.
1620
2498
  --out writes it as JSON, to permdock.manifest.json without a path; --check exits 1 when that
1621
2499
  file differs.
1622
2500
  `;
1623
- const MANIFEST_FILE = "permdock.manifest.json";
1624
- /** `supabase.hook.out`, else pg-delta's per-schema path, else `supabase/permdock-hook.sql`. */
1625
- function hookOut(cwd, config, schema) {
1626
- const pgDelta = supabaseConfig(cwd).pgDelta;
1627
- return config.supabase?.hook?.out !== void 0 || pgDelta === void 0 ? defaultOut(config) : pgDeltaPath(pgDelta.schemaDir, "hook", schema ?? config.supabase?.hook?.schema ?? config.rls?.schema ?? "permdock");
2501
+ /** `supabase.hook.out`, else pg-delta's per-schema path under pg-delta, else `supabase/permdock-hook.sql`. */
2502
+ function hookOut(cwd, config, schema, flag) {
2503
+ const schemas = resolveSchemas(cwd, config, flag);
2504
+ return config.supabase?.hook?.out !== void 0 || schemas.mode !== "pg-delta" ? defaultOut(config) : pgDeltaPath(schemas.schemaDir, "hook", schema ?? config.supabase?.hook?.schema ?? config.rls?.schema ?? "permdock");
1628
2505
  }
1629
2506
  /**
1630
2507
  * The manifest `supabase inspect` prints. With `policy`, `rls.helpers` also
@@ -1632,7 +2509,14 @@ function hookOut(cwd, config, schema) {
1632
2509
  * `assigns`.
1633
2510
  */
1634
2511
  function supabaseHookManifest(scopes, config, overrides = {}, policy) {
1635
- return manifestOf(hookParts(scopes, config, overrides), overrides.out ?? defaultOut(config), config, policy);
2512
+ const manifest = manifestOf(hookParts(scopes, config, overrides), overrides.out ?? defaultOut(config), config, policy);
2513
+ return overrides.schemas === void 0 ? manifest : {
2514
+ ...manifest,
2515
+ hook: {
2516
+ ...manifest.hook,
2517
+ schemas: overrides.schemas
2518
+ }
2519
+ };
1636
2520
  }
1637
2521
  /**
1638
2522
  * The hook migration. With `grantsOut`, the statements `supabase db diff`
@@ -1697,7 +2581,7 @@ function inspectText(manifest) {
1697
2581
  function manifestFile(cwd, rel, manifest, check) {
1698
2582
  const path = resolve(cwd, rel);
1699
2583
  if (!check) {
1700
- writeFileEnsuringDir(path, `${JSON.stringify(manifest, null, 2)}\n`);
2584
+ writeFileEnsuringDir(path, formatManifestJson(manifest));
1701
2585
  return {
1702
2586
  code: 0,
1703
2587
  output: `wrote ${rel}`
@@ -1734,6 +2618,13 @@ function loadHookPolicy(cwd, config) {
1734
2618
  }
1735
2619
  async function runSupabase(input) {
1736
2620
  const [area, action] = input.rest;
2621
+ const badSchemas = schemasRefusal("supabase", input.schemas);
2622
+ if (badSchemas !== void 0) return {
2623
+ code: 2,
2624
+ output: badSchemas
2625
+ };
2626
+ const flag = isSupabaseSchemas(input.schemas) ? input.schemas : void 0;
2627
+ const schemas = resolveSchemas(input.cwd, input.config, flag);
1737
2628
  const overrides = Object.fromEntries(Object.entries({
1738
2629
  activeFrom: input.activeFrom,
1739
2630
  budget: input.budget,
@@ -1743,7 +2634,8 @@ async function runSupabase(input) {
1743
2634
  const policy = await loadHookPolicy(input.cwd, input.config);
1744
2635
  const manifest = supabaseHookManifest(scopeList(policy.scopes), input.config, {
1745
2636
  ...overrides,
1746
- out: hookOut(input.cwd, input.config, input.schema)
2637
+ out: hookOut(input.cwd, input.config, input.schema, flag),
2638
+ schemas: schemas.mode
1747
2639
  }, policy);
1748
2640
  if (input.out !== void 0 || input.check) return manifestFile(input.cwd, typeof input.out === "string" ? input.out : MANIFEST_FILE, manifest, input.check);
1749
2641
  return {
@@ -1755,8 +2647,12 @@ async function runSupabase(input) {
1755
2647
  code: 2,
1756
2648
  output: SUPABASE_HELP
1757
2649
  };
2650
+ if (input.grantsOut !== void 0 && schemas.mode !== "migra") return {
2651
+ code: 2,
2652
+ output: `supabase hook generate --grants-out is for --schemas migra: ${schemas.mode === "pg-delta" ? "pg-delta keeps the grants in the hook file" : "a hand-written migration keeps its grants"}, so drop --grants-out`
2653
+ };
1758
2654
  const { sql, grants, manifest } = supabaseHookSql(await loadScopes(input.cwd, input.config), input.config, overrides, grantsLabel(input.grantsOut));
1759
- const outRel = typeof input.out === "string" ? input.out : hookOut(input.cwd, input.config, input.schema);
2655
+ const outRel = typeof input.out === "string" ? input.out : hookOut(input.cwd, input.config, input.schema, flag);
1760
2656
  const outPath = resolve(input.cwd, outRel);
1761
2657
  const hook = input.config.supabase?.hook;
1762
2658
  const grantsFile = input.grantsOut === void 0 ? [] : [{
@@ -1806,4 +2702,4 @@ async function runSupabase(input) {
1806
2702
  };
1807
2703
  }
1808
2704
  //#endregion
1809
- export { supabaseHookManifest as a, rbacScaffold as c, attrsPlan as d, pd045 as f, runSupabase as i, resolveAuthorize as l, hookOut as n, supabaseHookSql as o, supabaseConfig as p, loadScopes as r, parseRbacAuthorize as s, grantsLabel as t, pd039 as u };
2705
+ export { pd045 as _, supabaseHookManifest as a, approvalsSql as c, resolveAuthorize as d, pd039 as f, attrsPlan as g, schemasRefusal as h, runSupabase as i, parseRbacAuthorize as l, resolveSchemas as m, hookOut as n, supabaseHookSql as o, isSupabaseSchemas as p, loadScopes as r, permissionHoldersSql as s, grantsLabel as t, rbacScaffold as u, readSupabaseConfig as v, supabaseConfig as y };