permdock 0.4.0 → 0.5.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 (514) 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-B4R5fnMU.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-Djo_PyZc.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-DJ6Yd5z1.js +995 -0
  18. package/dist/arazzo-B6lebmiQ.js +36 -0
  19. package/dist/{arazzo-C4BcskbX.js → arazzo-BWhNF-k0.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-Dg8XsRaO.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-BMZPIVUM.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-BzqG8SMb.d.ts → bind--9EFVYWF.d.ts} +12 -10
  34. package/dist/{bind-kk-uyCwH.js → bind-CZzhx-Vj.js} +14 -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-BMnAudHn.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-BSslw1QV.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-tZUyOfOv.js → catalog-BECMPrwc.js} +51 -6
  48. package/dist/catalog-DjhMYYCq.js +1 -0
  49. package/dist/catalog-R_A__RsH.js +52 -0
  50. package/dist/{catalog-doc-DWtg0gGR.js → catalog-doc-C9wjsfNY.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-CIoHNevm.js +87 -0
  68. package/dist/codemod-Bmj9dWMH.js +37 -0
  69. package/dist/codemod-Cxvvc70T.js +2084 -0
  70. package/dist/{collect-2SrB7lfk.js → collect-CrZJJnEF.js} +75 -11
  71. package/dist/collect-DloHbDVa.js +55 -0
  72. package/dist/collect-FlXJlhus.js +1 -0
  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-CcEu2ZQ1.js → compile-CMqMx_IN.js} +11 -8
  77. package/dist/{compile-CtTP7eur.d.ts → compile-Ckm2HqBZ.d.ts} +5 -3
  78. package/dist/completion-BqhRBJD8.js +143 -0
  79. package/dist/config-B46rm9sc.js +26 -0
  80. package/dist/{config-BIjREpyE.js → config-bs_kZeYq.js} +12 -7
  81. package/dist/{config-report--Xb9APJS.js → config-report-DUgj62Ai.js} +15 -5
  82. package/dist/{connection-DUsczpB5.js → connection-DjhIJSs2.js} +6 -5
  83. package/dist/{context-DieZVJXu.js → context-bbFHR-0D.js} +2 -2
  84. package/dist/convex/index.d.ts +11 -10
  85. package/dist/convex/index.js +6 -4
  86. package/dist/create-0VZ7Pdiu.d.ts +107 -0
  87. package/dist/create-BDS7wc3y.d.ts +138 -0
  88. package/dist/create-BFfm17UA.d.ts +110 -0
  89. package/dist/create-BWRSXgOF.js +403 -0
  90. package/dist/create-C3b4B5eF.js +130 -0
  91. package/dist/{create-BBO24vj6.js → create-DnrmLnLW.js} +31 -32
  92. package/dist/{credential-kue0qpbm.js → credential-DM2um3ha.js} +152 -31
  93. package/dist/credentials-DPyHmYz5.js +74 -0
  94. package/dist/delegation-DaXVdDhM.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-D8jDHgsl.d.ts} +5 -5
  100. package/dist/{describe-C3ldA9os.d.ts → describe-BZUsixiL.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-CxZo9pTq.js +37 -0
  104. package/dist/{diff-rpqoe7Ag.js → diff-DCePiN6M.js} +60 -54
  105. package/dist/doctor-CxiwSfqH.js +44 -0
  106. package/dist/{doctor-owlv6ddh.js → doctor-DFjUxf2f.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-OD_4c__H.d.ts → errors-C2PXIv4c.d.ts} +2 -2
  116. package/dist/errors-CWTY6uih.js +143 -0
  117. package/dist/evaluate-_R8QkWaf.js +1732 -0
  118. package/dist/evaluations-16UqqQ6W.d.ts +32 -0
  119. package/dist/{evaluations-C9YJsv3H.js → evaluations-DZ2CbV5k.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-g0ypLkID.js +120 -0
  129. package/dist/fetch-adapter-qKJBuRqy.d.ts +31 -0
  130. package/dist/fields-D9tPVUUS.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-D2lZt4Dd.js} +55 -52
  135. package/dist/{grantee-CXY-0C87.js → grantee-Cn1HyUsI.js} +3 -3
  136. package/dist/{grantee-conditions-MQs5NIuX.js → grantee-conditions-CcXf8hMM.js} +4 -3
  137. package/dist/{grantee-match-fxYp9Nmt.js → grantee-match-DHArqY82.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-DTcZOzhF.js} +45 -38
  143. package/dist/hono/index.d.ts +12 -9
  144. package/dist/hono/index.js +13 -12
  145. package/dist/hosted-BZIAYvon.js +315 -0
  146. package/dist/hosted-DBDX0M-P.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-DMmOG27O.js} +190 -53
  152. package/dist/index-BcHcPyIo.d.ts +110 -0
  153. package/dist/index-CaxrMD1A.d.ts +1 -0
  154. package/dist/index-Chrs5fQ-.d.ts +210 -0
  155. package/dist/index-CxGUswIC.d.ts +431 -0
  156. package/dist/index-D8EovfsP.d.ts +91 -0
  157. package/dist/{index-UdVQgq48.d.ts → index-DOVf7RJv.d.ts} +431 -81
  158. package/dist/{index-BwMe94Xq.d.ts → index-SGdIQyzc.d.ts} +34 -16
  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-Omfd793B.d.ts} +2 -2
  164. package/dist/instance-snapshot-Cxq4ygFc.js +600 -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-5JatXyND.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-CtKR3GXL.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-TCAdw-eW.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-BShP7Vf4.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-BJdBmYDG.js → openapi-DTaEmTSW.js} +10 -9
  205. package/dist/openapi-ahr8rHu3.js +138 -0
  206. package/dist/operation-findings-DgATDpvh.js +36 -0
  207. package/dist/operations-CxybZErY.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-Dyw8fyPF.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-BatFgAA4.js} +1698 -869
  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-C-q02Pal.js → parse-local-manifest-CVSs6J9I.js} +1 -1
  219. package/dist/{parse-local-manifest-DvYUR0QL.d.ts → parse-local-manifest-DgqtruPn.d.ts} +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-B9JxkBwC.js +868 -0
  227. package/dist/permdock-D7eeLJER.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-CZKiUfFM.js} +4 -3
  231. package/dist/{permissions-COH-KRgT.d.ts → permissions-oEWfl-kl.d.ts} +1 -2
  232. package/dist/policies-CO1oOymN.js +295 -0
  233. package/dist/{policy-ur0BBK_e.d.ts → policy-Czo4n3Hs.d.ts} +2 -2
  234. package/dist/{hosted-CBHgkzDl.js → policy-DLZJ5gAp.js} +17 -273
  235. package/dist/policy-from-catalog-DzhEc3Y9.js +450 -0
  236. package/dist/policy-module-CTlwMvLI.js +89 -0
  237. package/dist/{policy-types-sp9P1iIV.d.ts → policy-types-DzwivBgR.d.ts} +434 -631
  238. package/dist/postgrest-CW0IqC-U.js +566 -0
  239. package/dist/postgrest-NUqZT-RB.d.ts +100 -0
  240. package/dist/powersync-BHfuehf3.js +62 -0
  241. package/dist/powersync-CbzkqL_M.js +1 -0
  242. package/dist/{powersync-cZe4kzhL.js → powersync-bYUDLOAO.js} +23 -24
  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-DXlt-vks.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-B3_P7xuw.d.ts → protected-DEPHc2xV.d.ts} +16 -8
  255. package/dist/{protected-BN7BUdY1.js → protected-DfjY5mLA.js} +21 -4
  256. package/dist/{provider-Bi-0HNL3.js → provider-CC-u3bYP.js} +2 -2
  257. package/dist/{provider-kwHC9CZD.d.ts → provider-DMVt6yYn.d.ts} +1 -1
  258. package/dist/{provider-store-Ctt-81Ni.js → provider-store-B5Yrkhzr.js} +2 -2
  259. package/dist/pull-BgIfno6w.js +256 -0
  260. package/dist/pull-ByOks_Tg.js +1 -0
  261. package/dist/push-CmFVBOeI.js +116 -0
  262. package/dist/pushed-policy-BImMTTIM.d.ts +57 -0
  263. package/dist/pushed-policy-DobP7AHI.js +238 -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 +24 -22
  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-D7iKcLFe.d.ts} +1 -1
  275. package/dist/{replay-BsOyTrdV.js → replay-Du1tJjG7.js} +2 -1
  276. package/dist/{request-context-BVM7aWNs.js → request-context-BTRTG7OT.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-BA8an5Ap.js +336 -0
  284. package/dist/{rls-BEktUaqg.js → rls-j6t43t7n.js} +287 -689
  285. package/dist/{role-builder-B4GbKM-i.js → role-builder-CXbis5-X.js} +14 -207
  286. package/dist/{rpc-nYC3cR_u.js → rpc-aPyNi6tA.js} +7 -7
  287. package/dist/run-CNsApDpd.js +1 -0
  288. package/dist/run-DOfMtc9V.js +723 -0
  289. package/dist/run-ticket-BjhTOi6u.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-FO-Q8oj0.js} +16 -14
  293. package/dist/scan-BOI5tJ-6.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-DqCT4CgZ.js +453 -0
  303. package/dist/{server-types-DdqBwZN_.d.ts → server-types-BLd4MnBX.d.ts} +51 -68
  304. package/dist/{sha256-BO0swRXL.js → sha256-D9ihqmvd.js} +8 -1
  305. package/dist/{shared-1uhI-e-b.js → shared-CD11nBbC.js} +18 -8
  306. package/dist/{shared-JhMeOsDF.js → shared-DvvFzYXe.js} +8 -9
  307. package/dist/signals-D3NcTKUS.js +32 -0
  308. package/dist/sink-BEZSlMS_.js +957 -0
  309. package/dist/sink-Bj_lDUDq.d.ts +225 -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-CXUWfsNy.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-DqK9eDmM.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-BV9j0DeZ.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-DLfUWFAI.js +1851 -0
  323. package/dist/ssf/index.d.ts +4 -4
  324. package/dist/ssf/index.js +384 -47
  325. package/dist/standard-webhooks-6lg8v_is.d.ts +88 -0
  326. package/dist/standard-webhooks-DgFFD-Ga.js +228 -0
  327. package/dist/storage-C2bWdvJH.js +132 -0
  328. package/dist/{store-ILkLUj_o.js → store-4u_hxjWa.js} +40 -53
  329. package/dist/{store-options-CbUHDoSe.js → store-options-BqEakYRa.js} +26 -21
  330. package/dist/{stores-BHXdSbw9.js → stores-BbsD4fAR.js} +2 -2
  331. package/dist/{stores-Cxz_80g-.d.ts → stores-ClZUgeoP.d.ts} +7 -8
  332. package/dist/{stream-Dwsg6WwE.d.ts → stream-7-xmnf3U.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-9pqI57pW.js +92 -0
  342. package/dist/{supabase-C1TWlnYt.js → supabase-Dxlp8A94.js} +98 -205
  343. package/dist/supabase-hook-CXcGpHqq.js +1 -0
  344. package/dist/{supabase-hook-Cva3ikXz.js → supabase-hook-DdDQhMlw.js} +951 -60
  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-7rJVLWe5.d.ts → types-5Ut_pbKy.d.ts} +61 -11
  376. package/dist/{store-9KO4BlkB.d.ts → types-BNORmDM8.d.ts} +100 -75
  377. package/dist/types-BjV-fqcA.d.ts +57 -0
  378. package/dist/{types-CUcFZfpU.d.ts → types-BqAKcRNW.d.ts} +2 -1
  379. package/dist/types-C8-KNsFf.d.ts +107 -0
  380. package/dist/{types-Br9IXUBG.d.ts → types-CCpc2Dcv.d.ts} +77 -17
  381. package/dist/{types-DpWfQdSh.d.ts → types-CZTnPFzM.d.ts} +46 -11
  382. package/dist/{types-CqrjB9o6.d.ts → types-CdlxsYQh.d.ts} +23 -11
  383. package/dist/{types-CIlTojhq.d.ts → types-ClZaWzfb.d.ts} +9 -6
  384. package/dist/types-DX0LaKTb.d.ts +157 -0
  385. package/dist/types-DiQjupG2.d.ts +332 -0
  386. package/dist/types-DqzsnlSA.js +67 -0
  387. package/dist/types-_sHy8BQf.d.ts +65 -0
  388. package/dist/{types-ByUo56un.d.ts → types-fbZ7JEXr.d.ts} +23 -22
  389. package/dist/{types-DHrmBJLH.d.ts → types-tIgIeIKJ.d.ts} +7 -7
  390. package/dist/unplugin/index.d.ts +1 -1
  391. package/dist/unplugin/index.js +2 -2
  392. package/dist/usage-CHOtwL19.js +1 -0
  393. package/dist/usage-DHtE8j4E.js +32 -0
  394. package/dist/{usage-CG9nOYZw.js → usage-T5mXq98v.js} +54 -11
  395. package/dist/{validation-error-BSSw0l_8.d.ts → validation-error-Bg5Tpluk.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-DbCL9bnB.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-D6lPdb4Z.d.ts} +65 -2
  403. package/dist/vue/index.d.ts +9 -10
  404. package/dist/vue/index.js +8 -8
  405. package/dist/waiter-BGUtGYoU.js +67 -0
  406. package/dist/waiter-CKy4MQSY.d.ts +36 -0
  407. package/dist/watch-Cb-NcaUw.js +1 -0
  408. package/dist/{watch-zeucB7eF.js → watch-CszoTJMq.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-Dv-WTP-w.js +306 -0
  412. package/dist/who-can-D0QoYbEb.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-Bd1Tejur.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 +9 -8
  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 +5 -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/load-B_shHrmd.js +0 -2
  490. package/dist/manifest-BngWKryL.js +0 -770
  491. package/dist/may-use-BOn_dR_w.js +0 -78
  492. package/dist/openapi-DcQ1Kg40.js +0 -164
  493. package/dist/ownership-BgnbOJFT.js +0 -851
  494. package/dist/permdock-L26udHSk.js +0 -3646
  495. package/dist/powersync-BFSbSElQ.js +0 -62
  496. package/dist/powersync-DCDgZgfZ.js +0 -2
  497. package/dist/rls-55DQTuf5.js +0 -250
  498. package/dist/run-DMcNW_8T.js +0 -174
  499. package/dist/server-ZYEHtNHS.js +0 -268
  500. package/dist/sink-BTtmfPnE.js +0 -141
  501. package/dist/sink-CPN4zLfs.d.ts +0 -104
  502. package/dist/skills-Dle4ZoLF.js +0 -56
  503. package/dist/snapshot-Dc9CHPME.js +0 -1078
  504. package/dist/src-Bb7no-hS.js +0 -639
  505. package/dist/subject-C6dLxqHU.js +0 -24
  506. package/dist/supabase-Dbqr141s.js +0 -83
  507. package/dist/supabase-hook-BULg7Ut_.js +0 -2
  508. package/dist/thenable-QSmvt-vZ.js +0 -65
  509. package/dist/types-C0dee0Qf.d.ts +0 -197
  510. package/dist/types-DvupZE0w.d.ts +0 -9
  511. package/dist/usage-4G6t643F.js +0 -42
  512. package/dist/usage-iFLJ0-rr.js +0 -2
  513. package/dist/vocabulary-hhEFLFoL.d.ts +0 -65
  514. 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-Dxlp8A94.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-CW0IqC-U.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-CD11nBbC.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-CO1oOymN.js";
23
+ import { $ as checkSuspension, A as roleNames, C as API_KEY_ALLOWS, G as memberRoleOf, H as memberForSources, K as permittedForHelper, R as hasMemberFor, T as apiKeyFields, U as memberForTable, V as memberForHelper, W as memberIdsHelper, X as scopeTable, Z as activeRowSql, a as guardedTables, b as globalRoleSource, c as PERMISSION_HELPERS, d as permissionKeysForHelper, f as permittedByPermissionHelper, l as checkName, o as ASSIGNMENTS, q as permittedIdsHelper, r as holderSources, s as OWNERSHIP, t as ownershipRules, y as globalRoleRows, z as hasPermittedUsers } from "./ownership-BatFgAA4.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 (
@@ -916,6 +983,808 @@ function customRolesOfSql(parts) {
916
983
  );`;
917
984
  }
918
985
  //#endregion
986
+ //#region src/cli/rls/approvals.ts
987
+ /** `rls.approvals`' approver check; better-supabase's `isApprover` template calls it. */
988
+ const IS_APPROVER = "permdock_is_approver";
989
+ /** The objects `rls.approvals` adds. Names are part of the SQL contract `supabaseApprovalStore` calls. */
990
+ const APPROVAL_STORE = {
991
+ table: "approval_requests",
992
+ open: "permdock_approval_open",
993
+ get: "permdock_approval_get",
994
+ resolve: "permdock_approval_resolve",
995
+ consume: "permdock_approval_consume",
996
+ list: "permdock_approval_list",
997
+ expire: "permdock_approval_expire",
998
+ cancel: "permdock_approval_cancel",
999
+ isApprover: IS_APPROVER
1000
+ };
1001
+ /** `body` as the row's filter columns hold it. */
1002
+ const COLUMNS = `p_request ->> 'token',
1003
+ p_request,
1004
+ p_request ->> 'status',
1005
+ p_request #>> '{subject,principal,tenant}',
1006
+ p_request #>> '{subject,principal,id}',
1007
+ p_request #>> '{subject,actor,id}',
1008
+ p_request #>> '{subject,session}',
1009
+ coalesce(jsonb_array_length(p_request -> 'approvals'), 0),
1010
+ p_request ->> 'createdAt',
1011
+ (p_request ->> 'expiresAt')::timestamptz,
1012
+ (p_request ->> 'consumedAt')::timestamptz`;
1013
+ /** The `ApprovalListFilter` in `p_filter`, over the row aliased `a`. */
1014
+ const FILTER = `(p_filter ->> 'status' is null or a.status = p_filter ->> 'status')
1015
+ and (p_filter ->> 'tenant' is null or a.tenant = p_filter ->> 'tenant')
1016
+ and (p_filter ->> 'principalId' is null or a.principal_id = p_filter ->> 'principalId')
1017
+ and (p_filter ->> 'actorId' is null or a.actor_id = p_filter ->> 'actorId')
1018
+ and (p_filter ->> 'session' is null or a.session = p_filter ->> 'session')`;
1019
+ function serverOnly(fn, args) {
1020
+ return `revoke execute on function ${fn}(${args}) from public, anon, authenticated;`;
1021
+ }
1022
+ const BODY_CHECK = `${APPROVAL_STORE.table}_body_schema`;
1023
+ /**
1024
+ * `rls.jsonSchema`: a pg_jsonschema check that `body` matches
1025
+ * `approval-request-v1.json`, added `not valid` and then validated. `auto`
1026
+ * adds it only where the extension is available.
1027
+ */
1028
+ function bodySchemaSql(table, mode, body = "body", check = BODY_CHECK) {
1029
+ const statements = [
1030
+ "create schema if not exists extensions;",
1031
+ "create extension if not exists pg_jsonschema with schema extensions;",
1032
+ `alter table ${table} drop constraint if exists "${check}";`,
1033
+ `alter table ${table} add constraint "${check}" check (extensions.jsonb_matches_schema(${quoteLiteral(JSON.stringify(APPROVAL_REQUEST_SCHEMA))}::json, ${body})) not valid;`,
1034
+ `alter table ${table} validate constraint "${check}";`
1035
+ ];
1036
+ const comment = "-- approval store: body matches schemas/approval-request-v1.json through pg_jsonschema";
1037
+ if (mode === true) return `\n${comment}\n${statements.join("\n")}\n`;
1038
+ return `\n${comment}, where the extension is available
1039
+ do $permdock$
1040
+ begin
1041
+ if exists (select 1 from pg_available_extensions where name = 'pg_jsonschema') then
1042
+ ${statements.map((statement) => ` ${statement}`).join("\n")}
1043
+ end if;
1044
+ end
1045
+ $permdock$;\n`;
1046
+ }
1047
+ /**
1048
+ * The approval store (`rls.approvals`): one table of `ApprovalRequest`
1049
+ * bodies with the columns a list filters on, and one `security definer`
1050
+ * function per `ApprovalStore` method, each one atomic statement. No client
1051
+ * role may read the table or execute the functions: approvals are opened and
1052
+ * resolved by the server, which grants its own role.
1053
+ */
1054
+ function approvalStoreSql(ctx, jsonSchema = false) {
1055
+ const table = qualified(ctx, APPROVAL_STORE.table);
1056
+ const fn = (name) => qualified(ctx, name);
1057
+ const index = (name) => `"${APPROVAL_STORE.table}_${name}"`;
1058
+ return `-- approval store: ApprovalRequest bodies, opened, resolved and consumed through the functions below; supabaseApprovalStore calls them
1059
+ create table if not exists ${table} (
1060
+ token text primary key,
1061
+ body jsonb not null,
1062
+ status text not null check (status in ('pending', 'approved', 'rejected', 'expired')),
1063
+ tenant text,
1064
+ principal_id text,
1065
+ actor_id text,
1066
+ session text,
1067
+ approvals integer not null default 0,
1068
+ created_at text not null,
1069
+ expires_at timestamptz not null,
1070
+ consumed_at timestamptz
1071
+ );
1072
+ create index if not exists ${index("page")} on ${table} (created_at collate "C", token collate "C");
1073
+ create index if not exists ${index("pending")} on ${table} (status, expires_at);
1074
+ alter table ${table} enable row level security;
1075
+ revoke all on table ${table} from anon, authenticated, public;
1076
+ ${jsonSchema === false ? "" : bodySchemaSql(table, jsonSchema)}
1077
+ -- open a request; a repeated ask keeps the open record and replaces only an expired one
1078
+ create or replace function ${fn(APPROVAL_STORE.open)}(p_request jsonb)
1079
+ returns void
1080
+ language sql
1081
+ volatile
1082
+ security definer
1083
+ set search_path = ''
1084
+ as $$
1085
+ insert into ${table} as a (token, body, status, tenant, principal_id, actor_id, session, approvals, created_at, expires_at, consumed_at)
1086
+ values (
1087
+ ${COLUMNS}
1088
+ )
1089
+ on conflict (token) do update
1090
+ set body = excluded.body, status = excluded.status, tenant = excluded.tenant,
1091
+ principal_id = excluded.principal_id, actor_id = excluded.actor_id, session = excluded.session,
1092
+ approvals = excluded.approvals, created_at = excluded.created_at,
1093
+ expires_at = excluded.expires_at, consumed_at = excluded.consumed_at
1094
+ where a.status = 'expired' or a.expires_at <= now()
1095
+ $$;
1096
+ ${serverOnly(fn(APPROVAL_STORE.open), "jsonb")}
1097
+
1098
+ create or replace function ${fn(APPROVAL_STORE.get)}(p_token text)
1099
+ returns jsonb
1100
+ language sql
1101
+ stable
1102
+ security definer
1103
+ set search_path = ''
1104
+ as $$
1105
+ select a.body from ${table} a where a.token = p_token
1106
+ $$;
1107
+ ${serverOnly(fn(APPROVAL_STORE.get), "text")}
1108
+
1109
+ -- write a verdict over the record it was computed from: still pending, unexpired, with p_seen approvals
1110
+ create or replace function ${fn(APPROVAL_STORE.resolve)}(p_token text, p_next jsonb, p_seen integer)
1111
+ returns jsonb
1112
+ language sql
1113
+ volatile
1114
+ security definer
1115
+ set search_path = ''
1116
+ as $$
1117
+ update ${table} a
1118
+ set body = p_next,
1119
+ status = p_next ->> 'status',
1120
+ approvals = coalesce(jsonb_array_length(p_next -> 'approvals'), 0)
1121
+ where a.token = p_token
1122
+ and a.status = 'pending'
1123
+ and a.expires_at > now()
1124
+ and a.approvals = p_seen
1125
+ returning a.body
1126
+ $$;
1127
+ ${serverOnly(fn(APPROVAL_STORE.resolve), "text, jsonb, integer")}
1128
+
1129
+ -- consume an approved, unexpired, unconsumed request once
1130
+ create or replace function ${fn(APPROVAL_STORE.consume)}(p_token text, p_now text)
1131
+ returns jsonb
1132
+ language sql
1133
+ volatile
1134
+ security definer
1135
+ set search_path = ''
1136
+ as $$
1137
+ update ${table} a
1138
+ set consumed_at = p_now::timestamptz,
1139
+ body = a.body || jsonb_build_object('consumedAt', p_now)
1140
+ where a.token = p_token
1141
+ and a.status = 'approved'
1142
+ and a.consumed_at is null
1143
+ and a.expires_at > p_now::timestamptz
1144
+ returning a.body
1145
+ $$;
1146
+ ${serverOnly(fn(APPROVAL_STORE.consume), "text, text")}
1147
+
1148
+ -- one page in (createdAt, token) order after the cursor position
1149
+ create or replace function ${fn(APPROVAL_STORE.list)}(p_filter jsonb, p_after_created text, p_after_token text, p_limit integer)
1150
+ returns setof jsonb
1151
+ language sql
1152
+ stable
1153
+ security definer
1154
+ set search_path = ''
1155
+ as $$
1156
+ select a.body
1157
+ from ${table} a
1158
+ where ${FILTER}
1159
+ and (p_after_created is null
1160
+ or (a.created_at collate "C", a.token collate "C") > (p_after_created collate "C", p_after_token collate "C"))
1161
+ order by a.created_at collate "C", a.token collate "C"
1162
+ limit p_limit
1163
+ $$;
1164
+ ${serverOnly(fn(APPROVAL_STORE.list), "jsonb, text, text, integer")}
1165
+
1166
+ create or replace function ${fn(APPROVAL_STORE.expire)}(p_now text)
1167
+ returns integer
1168
+ language sql
1169
+ volatile
1170
+ security definer
1171
+ set search_path = ''
1172
+ as $$
1173
+ with expired as (
1174
+ update ${table} a
1175
+ set status = 'expired', body = a.body || '{"status":"expired"}'::jsonb
1176
+ where a.status = 'pending' and a.expires_at <= p_now::timestamptz
1177
+ returning 1
1178
+ )
1179
+ select count(*)::integer from expired
1180
+ $$;
1181
+ ${serverOnly(fn(APPROVAL_STORE.expire), "text")}
1182
+
1183
+ -- reject every pending request the filter matches, as system:<by>
1184
+ create or replace function ${fn(APPROVAL_STORE.cancel)}(p_filter jsonb, p_by text, p_note text, p_now text)
1185
+ returns integer
1186
+ language sql
1187
+ volatile
1188
+ security definer
1189
+ set search_path = ''
1190
+ as $$
1191
+ with cancelled as (
1192
+ update ${table} a
1193
+ set status = 'rejected',
1194
+ body = (a.body - 'note') || jsonb_strip_nulls(jsonb_build_object(
1195
+ 'status', 'rejected', 'resolvedAt', p_now, 'resolvedBy', 'system:' || p_by, 'note', p_note
1196
+ ))
1197
+ where a.status = 'pending'
1198
+ and ${FILTER.replaceAll("\n ", "\n ")}
1199
+ returning 1
1200
+ )
1201
+ select count(*)::integer from cancelled
1202
+ $$;
1203
+ ${serverOnly(fn(APPROVAL_STORE.cancel), "jsonb, text, text, text")}
1204
+ `;
1205
+ }
1206
+ /**
1207
+ * `permdock_is_approver(p_approvers jsonb, p_tenant text default null)`: true
1208
+ * when the signed-in user matches the serialised approvers of a request
1209
+ * (`approvers.by`), for policies on the app's own approval views. A list
1210
+ * needs every item and `any-of` one of them; `user` compares the user id and
1211
+ * `holder(permission)` reads `permitted_<root>_ids_by_permission` in
1212
+ * `p_tenant`, or `permdock_has_permission` without one. Role, relation and
1213
+ * other grantee approvers need facts the database does not hold for every
1214
+ * membership source, so they match nobody.
1215
+ */
1216
+ function isApproverSql(ctx) {
1217
+ const fn = qualified(ctx, APPROVAL_STORE.isApprover);
1218
+ const root = ctx.scopes[0]?.name;
1219
+ const inTenant = root === void 0 ? "false" : `exists (
1220
+ select 1
1221
+ from ${qualified(ctx, checkName(permittedByPermissionHelper(root)))}(p_approvers ->> 'permission') i(id)
1222
+ where i.id::text = p_tenant
1223
+ )`;
1224
+ return `
1225
+ -- approvers: whether the signed-in user matches a request's approvers.by; policies on approval views call it
1226
+ create or replace function ${fn}(p_approvers jsonb, p_tenant text default null)
1227
+ returns boolean
1228
+ language plpgsql
1229
+ stable
1230
+ security definer
1231
+ set search_path = ''
1232
+ as $$
1233
+ declare
1234
+ v_item jsonb;
1235
+ v_user text := ${subjectIdSql(ctx)}::text;
1236
+ begin
1237
+ if coalesce(v_user, '') = '' or p_approvers is null then
1238
+ return false;
1239
+ end if;
1240
+ if jsonb_typeof(p_approvers) = 'array' then
1241
+ if jsonb_array_length(p_approvers) = 0 then
1242
+ return false;
1243
+ end if;
1244
+ for v_item in select value from jsonb_array_elements(p_approvers) loop
1245
+ if not ${fn}(v_item, p_tenant) then
1246
+ return false;
1247
+ end if;
1248
+ end loop;
1249
+ return true;
1250
+ end if;
1251
+ if jsonb_typeof(p_approvers) <> 'object' then
1252
+ return false;
1253
+ end if;
1254
+ case p_approvers ->> 'kind'
1255
+ when 'user' then
1256
+ return coalesce(p_approvers ->> 'id' = v_user, false);
1257
+ when 'permission' then
1258
+ if p_tenant is null then
1259
+ return coalesce(${qualified(ctx, PERMISSION_HELPERS.has)}(p_approvers ->> 'permission'), false);
1260
+ end if;
1261
+ return ${inTenant};
1262
+ when 'any-of' then
1263
+ if jsonb_typeof(p_approvers -> 'of') <> 'array' then
1264
+ return false;
1265
+ end if;
1266
+ for v_item in select value from jsonb_array_elements(p_approvers -> 'of') loop
1267
+ if ${fn}(v_item, p_tenant) then
1268
+ return true;
1269
+ end if;
1270
+ end loop;
1271
+ return false;
1272
+ else
1273
+ return false;
1274
+ end case;
1275
+ end;
1276
+ $$;
1277
+ revoke execute on function ${fn}(jsonb, text) from public, anon;
1278
+ grant execute on function ${fn}(jsonb, text) to authenticated;
1279
+ `;
1280
+ }
1281
+ /**
1282
+ * Everything `rls.approvals` writes: the generated or adopted store, the
1283
+ * approver check and, with `delegations: true`, the covers.
1284
+ */
1285
+ function approvalsSql(ctx, approvals, jsonSchema) {
1286
+ if (approvals === void 0 || approvals === false) return;
1287
+ if (approvals === true) return approvalStoreSql(ctx, jsonSchema) + isApproverSql(ctx);
1288
+ const { table, delegations } = approvals;
1289
+ if (delegations !== void 0 && typeof delegations !== "boolean") throw new TypeError("PermDock CLI: rls.approvals.delegations must be a boolean");
1290
+ let store;
1291
+ if (table === void 0) {
1292
+ const adoptOnly = [
1293
+ "token",
1294
+ "body",
1295
+ "open",
1296
+ "schema",
1297
+ "mirror"
1298
+ ].find((key) => approvals[key] !== void 0);
1299
+ if (adoptOnly !== void 0) throw configError(`PermDock CLI: rls.approvals.${adoptOnly} adopts a table; set rls.approvals.table too`);
1300
+ store = approvalStoreSql(ctx, jsonSchema);
1301
+ } else store = adoptedApprovalStoreSql(ctx, {
1302
+ ...approvals,
1303
+ table
1304
+ }, jsonSchema);
1305
+ return store + isApproverSql(ctx) + (delegations === true ? approvalDelegationsSql(ctx) : "");
1306
+ }
1307
+ /** The objects `rls.approvals.delegations` adds; `tableApprovalDelegationSource` reads the table. */
1308
+ const DELEGATIONS = {
1309
+ table: "approval_delegations",
1310
+ covers: "permdock_cover_approves"
1311
+ };
1312
+ /**
1313
+ * `rls.approvals.delegations`: one table of out-of-office covers that a
1314
+ * user writes for themselves, and a four-argument `permdock_is_approver`
1315
+ * that also answers for a delegate inside an active cover. One hop: a cover
1316
+ * is checked against the delegator's own approver rights, never the
1317
+ * delegator's covers. A delegator who is the requester never counts. A
1318
+ * `holder()` approver through a cover needs `rls.mode: "database"`, where
1319
+ * the `_for` helpers exist; in `jwt` mode only `user()` approvers pass.
1320
+ */
1321
+ function approvalDelegationsSql(ctx) {
1322
+ const table = qualified(ctx, DELEGATIONS.table);
1323
+ const fn = qualified(ctx, APPROVAL_STORE.isApprover);
1324
+ const covers = qualified(ctx, DELEGATIONS.covers);
1325
+ const user = ctx.dialect === "supabase" ? "uuid" : "text";
1326
+ const me = `${subjectIdSql(ctx)}::text`;
1327
+ const defaultMe = subjectIdSql(ctx).replace(/^\(select (.+)\)$/su, "$1");
1328
+ const root = ctx.scopes[0]?.name;
1329
+ const member = (tenant) => root === void 0 ? "false" : `${tenant} in (select m.id::text from ${qualified(ctx, memberIdsHelper(root))}() m(id))`;
1330
+ const database = ctx.authorize === "database";
1331
+ const holderGlobal = database ? `coalesce(${qualified(ctx, PERMISSION_HELPERS.hasFor)}(p_delegator, p_approvers ->> 'permission'), false)` : "false";
1332
+ const holderTenant = database && root !== void 0 ? `exists (
1333
+ select 1
1334
+ from ${qualified(ctx, checkName(`${permittedByPermissionHelper(root)}_for`))}(p_delegator, p_approvers ->> 'permission') i(id)
1335
+ where i.id::text = p_tenant
1336
+ )` : "false";
1337
+ const policy = (name) => quoteIdent(`${DELEGATIONS.table}_${name}`);
1338
+ const own = `delegator::text = ${me} and (tenant is null or ${member("tenant")})`;
1339
+ return `
1340
+ -- approval delegations: out-of-office covers a user writes for themselves; tableApprovalDelegationSource and permdock_is_approver read them
1341
+ create table if not exists ${table} (
1342
+ id text primary key default gen_random_uuid()::text,
1343
+ delegator ${user} not null default (${defaultMe})::${user},
1344
+ delegate ${user} not null,
1345
+ tenant text,
1346
+ permissions text[],
1347
+ resources text[],
1348
+ "from" timestamptz not null default now(),
1349
+ "until" timestamptz not null,
1350
+ created_at timestamptz not null default now(),
1351
+ constraint ${quoteIdent(`${DELEGATIONS.table}_window`)} check ("from" < "until"),
1352
+ constraint ${quoteIdent(`${DELEGATIONS.table}_distinct`)} check (delegator <> delegate)
1353
+ );
1354
+ create index if not exists ${quoteIdent(`${DELEGATIONS.table}_delegate`)} on ${table} (delegate, "until");
1355
+ alter table ${table} enable row level security;
1356
+ revoke all on table ${table} from anon, public;
1357
+ grant select, insert, update, delete on table ${table} to authenticated;
1358
+ drop policy if exists ${policy("read")} on ${table};
1359
+ create policy ${policy("read")} on ${table}
1360
+ for select to authenticated
1361
+ using (delegator::text = ${me} or delegate::text = ${me});
1362
+ drop policy if exists ${policy("insert")} on ${table};
1363
+ create policy ${policy("insert")} on ${table}
1364
+ for insert to authenticated
1365
+ with check (${own});
1366
+ drop policy if exists ${policy("update")} on ${table};
1367
+ create policy ${policy("update")} on ${table}
1368
+ for update to authenticated
1369
+ using (delegator::text = ${me})
1370
+ with check (${own});
1371
+ drop policy if exists ${policy("delete")} on ${table};
1372
+ create policy ${policy("delete")} on ${table}
1373
+ for delete to authenticated
1374
+ using (delegator::text = ${me});
1375
+
1376
+ -- approval delegations: whether a delegator's own rights match approvers.by; only permdock_is_approver calls it
1377
+ create or replace function ${covers}(p_delegator ${user}, p_approvers jsonb, p_tenant text)
1378
+ returns boolean
1379
+ language plpgsql
1380
+ stable
1381
+ security definer
1382
+ set search_path = ''
1383
+ as $$
1384
+ declare
1385
+ v_item jsonb;
1386
+ begin
1387
+ if p_delegator is null or p_approvers is null then
1388
+ return false;
1389
+ end if;
1390
+ if jsonb_typeof(p_approvers) = 'array' then
1391
+ if jsonb_array_length(p_approvers) = 0 then
1392
+ return false;
1393
+ end if;
1394
+ for v_item in select value from jsonb_array_elements(p_approvers) loop
1395
+ if not ${covers}(p_delegator, v_item, p_tenant) then
1396
+ return false;
1397
+ end if;
1398
+ end loop;
1399
+ return true;
1400
+ end if;
1401
+ if jsonb_typeof(p_approvers) <> 'object' then
1402
+ return false;
1403
+ end if;
1404
+ case p_approvers ->> 'kind'
1405
+ when 'user' then
1406
+ return coalesce(p_approvers ->> 'id' = p_delegator::text, false);
1407
+ when 'permission' then
1408
+ if p_tenant is null then
1409
+ return ${holderGlobal};
1410
+ end if;
1411
+ return ${holderTenant};
1412
+ when 'any-of' then
1413
+ if jsonb_typeof(p_approvers -> 'of') <> 'array' then
1414
+ return false;
1415
+ end if;
1416
+ for v_item in select value from jsonb_array_elements(p_approvers -> 'of') loop
1417
+ if ${covers}(p_delegator, v_item, p_tenant) then
1418
+ return true;
1419
+ end if;
1420
+ end loop;
1421
+ return false;
1422
+ else
1423
+ return false;
1424
+ end case;
1425
+ end;
1426
+ $$;
1427
+ revoke execute on function ${covers}(${user}, jsonb, text) from public, anon, authenticated;
1428
+
1429
+ -- approvers with covers: the signed-in user, or a delegate inside an active cover of a delegator who is not the requester
1430
+ create or replace function ${fn}(p_approvers jsonb, p_tenant text, p_requester text, p_resource text)
1431
+ returns boolean
1432
+ language plpgsql
1433
+ stable
1434
+ security definer
1435
+ set search_path = ''
1436
+ as $$
1437
+ declare
1438
+ v_delegator ${user};
1439
+ v_user text := ${me};
1440
+ begin
1441
+ if ${fn}(p_approvers, p_tenant) then
1442
+ return true;
1443
+ end if;
1444
+ if coalesce(v_user, '') = '' or coalesce(p_requester, '') = '' then
1445
+ return false;
1446
+ end if;
1447
+ if p_tenant is not null and not (${member("p_tenant")}) then
1448
+ return false;
1449
+ end if;
1450
+ for v_delegator in
1451
+ select d.delegator
1452
+ from ${table} d
1453
+ where d.delegate::text = v_user
1454
+ and d.delegator::text <> v_user
1455
+ and d.delegator::text <> p_requester
1456
+ and d."from" <= now()
1457
+ and now() < d."until"
1458
+ and (d.tenant is null or d.tenant = p_tenant)
1459
+ and d.permissions is null
1460
+ and (d.resources is null or p_resource = any (d.resources))
1461
+ loop
1462
+ if ${covers}(v_delegator, p_approvers, p_tenant) then
1463
+ return true;
1464
+ end if;
1465
+ end loop;
1466
+ return false;
1467
+ end;
1468
+ $$;
1469
+ revoke execute on function ${fn}(jsonb, text, text, text) from public, anon;
1470
+ grant execute on function ${fn}(jsonb, text, text, text) to authenticated;
1471
+ `;
1472
+ }
1473
+ /** The request fields an adopted table may mirror into its own columns, as SQL over a body. */
1474
+ const MIRRORS = {
1475
+ status: (body) => `${body} ->> 'status'`,
1476
+ permission: (body) => `${body} ->> 'permission'`,
1477
+ tenant: (body) => `${body} #>> '{subject,principal,tenant}'`,
1478
+ principalId: (body) => `${body} #>> '{subject,principal,id}'`,
1479
+ actorId: (body) => `${body} #>> '{subject,actor,id}'`,
1480
+ session: (body) => `${body} #>> '{subject,session}'`,
1481
+ approvals: (body) => `coalesce(jsonb_array_length(${body} -> 'approvals'), 0)`,
1482
+ createdAt: (body) => `${body} ->> 'createdAt'`,
1483
+ expiresAt: (body) => `${body} ->> 'expiresAt'`,
1484
+ resolvedAt: (body) => `${body} ->> 'resolvedAt'`,
1485
+ resolvedBy: (body) => `${body} ->> 'resolvedBy'`,
1486
+ consumedAt: (body) => `${body} ->> 'consumedAt'`
1487
+ };
1488
+ function mirrorsOf(adopt) {
1489
+ const out = [];
1490
+ for (const [field, column] of Object.entries(adopt.mirror ?? {})) {
1491
+ if (!Object.hasOwn(MIRRORS, field)) throw configError(`PermDock CLI: rls.approvals.mirror.${field} is not a request field; use ${Object.keys(MIRRORS).join(", ")}`);
1492
+ if (typeof column !== "string") throw new TypeError(`PermDock CLI: rls.approvals.mirror.${field} must be a column name`);
1493
+ out.push([column, field]);
1494
+ }
1495
+ return out;
1496
+ }
1497
+ /** One row of `table` from `values` (column, SQL), each converted to the column's own type. */
1498
+ function populate(table, values) {
1499
+ return `pg_catalog.jsonb_populate_record(null::${table}, pg_catalog.jsonb_build_object(${values.map(([column, sql]) => `${quoteLiteral(column)}, ${sql}`).join(", ")}))`;
1500
+ }
1501
+ /** The mirrored columns' values for the request `body`. */
1502
+ function mirrorRow(table, mirror, body) {
1503
+ return populate(table, mirror.map(([column, field]) => [column, MIRRORS[field](body)]));
1504
+ }
1505
+ /** `, (<mirrored columns>) = (select …)` for an update that writes `body`; empty without mirrors. */
1506
+ function mirrorSet(table, mirror, body) {
1507
+ if (mirror.length === 0) return "";
1508
+ const columns = mirror.map(([column]) => quoteIdent(column));
1509
+ const picked = columns.map((column) => `r.${column}`).join(", ");
1510
+ return `,\n ${columns.length === 1 ? columns[0] ?? "" : `(${columns.join(", ")})`} = (select ${picked} from ${mirrorRow(table, mirror, body)} r)`;
1511
+ }
1512
+ /**
1513
+ * The approval store over a table the app already has (`rls.approvals:
1514
+ * { table, token?, body?, mirror? }`): the same `permdock_approval_*`
1515
+ * functions, reading every field from the request body in `body`, keyed by
1516
+ * the text column `token`, and copying the fields `mirror` names into the
1517
+ * app's own columns, converted to their types, on every write. The table's
1518
+ * grants and policies stay the app's.
1519
+ */
1520
+ function adoptedApprovalStoreSql(ctx, adopt, jsonSchema = false) {
1521
+ const table = quoteQualified(adopt.table);
1522
+ const base = adopt.table.split(".").at(-1) ?? adopt.table;
1523
+ const token = quoteIdent(adopt.token ?? "token");
1524
+ const body = quoteIdent(adopt.body ?? "body");
1525
+ const mirror = mirrorsOf(adopt);
1526
+ 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`);
1527
+ const open = adopt.open ?? "insert";
1528
+ if (open !== "insert" && open !== "attach") throw configError(`PermDock CLI: rls.approvals.open must be 'insert' or 'attach', got '${String(open)}'`);
1529
+ const schema = adopt.schema;
1530
+ if (schema !== void 0 && (typeof schema !== "string" || schema === "")) throw configError("PermDock CLI: rls.approvals.schema must be a schema name");
1531
+ const fn = (name) => schema === void 0 ? qualified(ctx, name) : `${quoteIdent(schema)}.${name}`;
1532
+ const index = (name) => quoteIdent(`${base}_permdock_${name}`);
1533
+ const status = `a.${body} ->> 'status'`;
1534
+ const expires = `(a.${body} ->> 'expiresAt')::timestamptz`;
1535
+ const created = `(a.${body} ->> 'createdAt')`;
1536
+ const filter = `(p_filter ->> 'status' is null or ${status} = p_filter ->> 'status')
1537
+ and (p_filter ->> 'tenant' is null or a.${body} #>> '{subject,principal,tenant}' = p_filter ->> 'tenant')
1538
+ and (p_filter ->> 'principalId' is null or a.${body} #>> '{subject,principal,id}' = p_filter ->> 'principalId')
1539
+ and (p_filter ->> 'actorId' is null or a.${body} #>> '{subject,actor,id}' = p_filter ->> 'actorId')
1540
+ and (p_filter ->> 'session' is null or a.${body} #>> '{subject,session}' = p_filter ->> 'session')`;
1541
+ const columns = [
1542
+ token,
1543
+ body,
1544
+ ...mirror.map(([column]) => quoteIdent(column))
1545
+ ];
1546
+ const inserted = populate(table, [
1547
+ [adopt.token ?? "token", "p_request ->> 'token'"],
1548
+ [adopt.body ?? "body", "p_request"],
1549
+ ...mirror.map(([column, field]) => [column, MIRRORS[field]("p_request")])
1550
+ ]);
1551
+ const insertSql = `-- open a request; a repeated ask keeps the open record and replaces only an expired one
1552
+ create or replace function ${fn(APPROVAL_STORE.open)}(p_request jsonb)
1553
+ returns void
1554
+ language sql
1555
+ volatile
1556
+ security definer
1557
+ set search_path = ''
1558
+ as $$
1559
+ insert into ${table} as a (${columns.join(", ")})
1560
+ select ${columns.map((column) => `r.${column}`).join(", ")}
1561
+ from ${inserted} r
1562
+ on conflict (${token}) do update
1563
+ set ${columns.filter((column) => column !== token).map((column) => `${column} = excluded.${column}`).join(", ")}
1564
+ where ${status} = 'expired' or ${expires} <= now()
1565
+ $$;`;
1566
+ const attachSql = `create or replace function ${fn(APPROVAL_STORE.open)}(p_request jsonb)
1567
+ returns void
1568
+ language plpgsql
1569
+ volatile
1570
+ security definer
1571
+ set search_path = ''
1572
+ as $$
1573
+ begin
1574
+ update ${table} a
1575
+ set ${body} = p_request${mirrorSet(table, mirror, "p_request")}
1576
+ where a.${token} = p_request ->> 'token'
1577
+ and (a.${body} is null or ${status} = 'expired' or ${expires} <= now());
1578
+ if not found and not exists (select 1 from ${table} a where a.${token} = p_request ->> 'token') then
1579
+ raise exception using
1580
+ errcode = 'P0002',
1581
+ message = 'permdock: no row of ${adopt.table.replaceAll("'", "''")} holds approval token ' || (p_request ->> 'token'),
1582
+ hint = 'insert the application row with this token before opening the request';
1583
+ end if;
1584
+ end;
1585
+ $$;`;
1586
+ return `-- approval store adopted onto ${adopt.table}: ApprovalRequest bodies in ${adopt.body ?? "body"}, keyed by ${adopt.token ?? "token"}; supabaseApprovalStore calls the functions below
1587
+ alter table ${table} add column if not exists ${token} text;
1588
+ alter table ${table} add column if not exists ${body} jsonb;
1589
+ create unique index if not exists ${index("token")} on ${table} (${token});
1590
+ create index if not exists ${index("page")} on ${table} ((${body} ->> 'createdAt') collate "C", ${token} collate "C");
1591
+ create index if not exists ${index("status")} on ${table} ((${body} ->> 'status'));
1592
+ ${jsonSchema === false ? "" : bodySchemaSql(table, jsonSchema, body, `${base}_permdock_body_schema`)}
1593
+ ${open === "attach" ? attachSql : insertSql}
1594
+ ${serverOnly(fn(APPROVAL_STORE.open), "jsonb")}
1595
+
1596
+ create or replace function ${fn(APPROVAL_STORE.get)}(p_token text)
1597
+ returns jsonb
1598
+ language sql
1599
+ stable
1600
+ security definer
1601
+ set search_path = ''
1602
+ as $$
1603
+ select a.${body} from ${table} a where a.${token} = p_token
1604
+ $$;
1605
+ ${serverOnly(fn(APPROVAL_STORE.get), "text")}
1606
+
1607
+ -- write a verdict over the record it was computed from: still pending, unexpired, with p_seen approvals
1608
+ create or replace function ${fn(APPROVAL_STORE.resolve)}(p_token text, p_next jsonb, p_seen integer)
1609
+ returns jsonb
1610
+ language sql
1611
+ volatile
1612
+ security definer
1613
+ set search_path = ''
1614
+ as $$
1615
+ update ${table} a
1616
+ set ${body} = p_next${mirrorSet(table, mirror, "p_next")}
1617
+ where a.${token} = p_token
1618
+ and ${status} = 'pending'
1619
+ and ${expires} > now()
1620
+ and coalesce(jsonb_array_length(a.${body} -> 'approvals'), 0) = p_seen
1621
+ returning a.${body}
1622
+ $$;
1623
+ ${serverOnly(fn(APPROVAL_STORE.resolve), "text, jsonb, integer")}
1624
+
1625
+ -- consume an approved, unexpired, unconsumed request once
1626
+ create or replace function ${fn(APPROVAL_STORE.consume)}(p_token text, p_now text)
1627
+ returns jsonb
1628
+ language sql
1629
+ volatile
1630
+ security definer
1631
+ set search_path = ''
1632
+ as $$
1633
+ update ${table} a
1634
+ set ${body} = a.${body} || jsonb_build_object('consumedAt', p_now)${mirrorSet(table, mirror, `(a.${body} || jsonb_build_object('consumedAt', p_now))`)}
1635
+ where a.${token} = p_token
1636
+ and ${status} = 'approved'
1637
+ and a.${body} ->> 'consumedAt' is null
1638
+ and ${expires} > p_now::timestamptz
1639
+ returning a.${body}
1640
+ $$;
1641
+ ${serverOnly(fn(APPROVAL_STORE.consume), "text, text")}
1642
+
1643
+ -- one page in (createdAt, token) order after the cursor position
1644
+ create or replace function ${fn(APPROVAL_STORE.list)}(p_filter jsonb, p_after_created text, p_after_token text, p_limit integer)
1645
+ returns setof jsonb
1646
+ language sql
1647
+ stable
1648
+ security definer
1649
+ set search_path = ''
1650
+ as $$
1651
+ select a.${body}
1652
+ from ${table} a
1653
+ where a.${body} is not null
1654
+ and ${filter}
1655
+ and (p_after_created is null
1656
+ or (${created} collate "C", a.${token} collate "C") > (p_after_created collate "C", p_after_token collate "C"))
1657
+ order by ${created} collate "C", a.${token} collate "C"
1658
+ limit p_limit
1659
+ $$;
1660
+ ${serverOnly(fn(APPROVAL_STORE.list), "jsonb, text, text, integer")}
1661
+
1662
+ create or replace function ${fn(APPROVAL_STORE.expire)}(p_now text)
1663
+ returns integer
1664
+ language sql
1665
+ volatile
1666
+ security definer
1667
+ set search_path = ''
1668
+ as $$
1669
+ with expired as (
1670
+ update ${table} a
1671
+ set ${body} = a.${body} || '{"status":"expired"}'::jsonb${mirrorSet(table, mirror, `(a.${body} || '{"status":"expired"}'::jsonb)`)}
1672
+ where ${status} = 'pending' and ${expires} <= p_now::timestamptz
1673
+ returning 1
1674
+ )
1675
+ select count(*)::integer from expired
1676
+ $$;
1677
+ ${serverOnly(fn(APPROVAL_STORE.expire), "text")}
1678
+
1679
+ -- reject every pending request the filter matches, as system:<by>
1680
+ create or replace function ${fn(APPROVAL_STORE.cancel)}(p_filter jsonb, p_by text, p_note text, p_now text)
1681
+ returns integer
1682
+ language sql
1683
+ volatile
1684
+ security definer
1685
+ set search_path = ''
1686
+ as $$
1687
+ with cancelled as (
1688
+ update ${table} a
1689
+ set ${body} = (a.${body} - 'note') || jsonb_strip_nulls(jsonb_build_object(
1690
+ 'status', 'rejected', 'resolvedAt', p_now, 'resolvedBy', 'system:' || p_by, 'note', p_note
1691
+ ))${mirrorSet(table, mirror, `((a.${body} - 'note') || jsonb_strip_nulls(jsonb_build_object('status', 'rejected', 'resolvedAt', p_now, 'resolvedBy', 'system:' || p_by, 'note', p_note)))`)}
1692
+ where ${status} = 'pending'
1693
+ and ${filter.replaceAll("\n ", "\n ")}
1694
+ returning 1
1695
+ )
1696
+ select count(*)::integer from cancelled
1697
+ $$;
1698
+ ${serverOnly(fn(APPROVAL_STORE.cancel), "jsonb, text, text, text")}
1699
+ `;
1700
+ }
1701
+ //#endregion
1702
+ //#region src/cli/rls/permission-holders.ts
1703
+ /** `permitted_<scope>_users(p_permission, p_id)`. Names are part of the SQL contract. */
1704
+ function permittedUsersHelper(scope) {
1705
+ return checkName(`permitted_${scope}_users`);
1706
+ }
1707
+ /** The users with a membership row on the instance `p_id`, or `undefined` when the scope's memberships are not all in tables. */
1708
+ function candidatesSql(ctx, scope, user) {
1709
+ const mapped = scopeTable(ctx, scope);
1710
+ if (mapped !== void 0) return {
1711
+ sql: ` select m.${quoteIdent(mapped.table.user)}::${user}
1712
+ from ${quoteQualified(mapped.table.table)} m
1713
+ where m.${quoteIdent(mapped.column)} = p_id`,
1714
+ keys: []
1715
+ };
1716
+ const sources = holderSources(ctx, scope);
1717
+ if (sources === void 0 || sources === "not-tables") return;
1718
+ return {
1719
+ sql: sources.map((source, index) => ` select h.user_id::${user}
1720
+ from (
1721
+ ${source.holders.rows(scope, { id: `v_key_${String(index)}` }).replaceAll(/^/gmu, " ")}
1722
+ ) h`).join("\n union\n"),
1723
+ keys: sources.map((source, index) => `v_key_${String(index)} ${source.holders.idType}`)
1724
+ };
1725
+ }
1726
+ /** `language sql`, or PL/pgSQL when a source's id column needs `p_id` in a `%type` variable. */
1727
+ function functionBody(keys, query) {
1728
+ if (keys.length === 0) return `language sql
1729
+ stable
1730
+ security definer
1731
+ set search_path = ''
1732
+ as $$
1733
+ ${query}
1734
+ $$;`;
1735
+ return `language plpgsql
1736
+ stable
1737
+ security definer
1738
+ set search_path = ''
1739
+ as $$
1740
+ declare
1741
+ ${keys.map((key) => ` ${key} := p_id;`).join("\n")}
1742
+ begin
1743
+ return query
1744
+ ${query.replaceAll(/^/gmu, " ")};
1745
+ end
1746
+ $$;`;
1747
+ }
1748
+ /**
1749
+ * One `permitted_<scope>_users(p_permission, p_id)` per scope in `database`
1750
+ * mode: the users who hold a permission key on one instance, read from the
1751
+ * scope's membership rows and the global-roles table and kept when
1752
+ * `permitted_<scope>_ids_by_permission_for` lists the instance or
1753
+ * `permdock_has_permission_for` answers, so denies, custom roles, expiry and
1754
+ * suspension apply as for the user. It names other users, so no client role
1755
+ * may execute it, and a backend role needs its own grant.
1756
+ */
1757
+ function permissionHoldersSql(ctx) {
1758
+ if (ctx.authorize !== "database") return "";
1759
+ const user = ctx.dialect === "supabase" ? "uuid" : "text";
1760
+ const global = globalRoleRows(ctx);
1761
+ const hasFor = qualified(ctx, PERMISSION_HELPERS.hasFor);
1762
+ return `${ctx.scopes.map(({ name }) => {
1763
+ const candidates = hasPermittedUsers(ctx, name) ? candidatesSql(ctx, name, user) : void 0;
1764
+ if (candidates === void 0) return `-- ${name}: no memberships table or table source, so permitted_${name}_users is not written`;
1765
+ const fn = qualified(ctx, permittedUsersHelper(name));
1766
+ const ids = qualified(ctx, checkName(`${permittedByPermissionHelper(name)}_for`));
1767
+ const type = scopeTypeOf(ctx, name);
1768
+ 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
1769
+ create or replace function ${fn}(p_permission text, p_id ${type})
1770
+ returns setof ${user}
1771
+ ${functionBody(candidates.keys, ` select c.user_id
1772
+ from (
1773
+ ${candidates.sql}
1774
+ union
1775
+ select ${global.userSql}::${user}
1776
+ from ${global.from}
1777
+ ) c(user_id)
1778
+ where c.user_id is not null
1779
+ and (
1780
+ p_id in (select ${ids}(c.user_id, p_permission))
1781
+ or ${hasFor}(c.user_id, p_permission)
1782
+ )
1783
+ order by 1`)}
1784
+ revoke execute on function ${fn}(text, ${type}) from public, anon, authenticated;`;
1785
+ }).join("\n\n")}\n`;
1786
+ }
1787
+ //#endregion
919
1788
  //#region src/cli/supabase-hook/sql.ts
920
1789
  const MANAGED_TRIGGER = "permdock_protect_managed";
921
1790
  function hookSql(parts) {
@@ -927,14 +1796,14 @@ function hookSql(parts) {
927
1796
  from ${rows.from}
928
1797
  where ${rows.userSql} = v_roles_user;`;
929
1798
  const plan = parts.attrs;
930
- const fromTable = plan === void 0 || plan.columns.length === 0 || plan.table === void 0 ? "" : `
1799
+ const tableMembershipSource = plan === void 0 || plan.columns.length === 0 || plan.table === void 0 ? "" : `
931
1800
  select jsonb_strip_nulls(jsonb_build_object(${plan.columns.map((column) => `${quoteLiteral(column)}, to_jsonb(p.${quoteIdent(column)})`).join(", ")}))
932
1801
  into attrs
933
1802
  from ${quoteQualified(plan.table)} p
934
1803
  where p.${quoteIdent(plan.id)} = v_attrs_user;`;
935
1804
  const fromMeta = plan === void 0 || plan.meta.length === 0 ? "" : `
936
1805
  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}
1806
+ const attrs = plan === void 0 ? "" : `${tableMembershipSource}${fromMeta}
938
1807
  if attrs is not null and attrs <> '{}'::jsonb then
939
1808
  used := octet_length(attrs::text);
940
1809
  if used > budget then
@@ -1032,7 +1901,7 @@ function extraGrantsSql(extra) {
1032
1901
  }
1033
1902
  function beforeGrantsSql(before, extra) {
1034
1903
  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("");
1904
+ 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
1905
  }
1037
1906
  /**
1038
1907
  * Execute on each `member_<scope>_ids_for(uuid)` for the `hook.claims`
@@ -1104,7 +1973,7 @@ uri = "${hookUri(schema)}"`;
1104
1973
  const BUDGET_MEASURE = "octet_length(memberships::text) + octet_length(attrs::text)";
1105
1974
  function hookParts(scopes, config, overrides) {
1106
1975
  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");
1976
+ if (hook === void 0) throw configError("PermDock CLI: supabase hook generate needs supabase.hook in permdock.config.ts");
1108
1977
  const root = rootScope(scopes) ?? "tenant";
1109
1978
  const suspension = checkSuspension(hook.suspension ?? config.rls?.suspension, scopes);
1110
1979
  const schema = overrides.schema ?? hook.schema ?? config.rls?.schema ?? "permdock";
@@ -1140,8 +2009,8 @@ function hookParts(scopes, config, overrides) {
1140
2009
  })
1141
2010
  }
1142
2011
  };
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("; ")}`);
2012
+ if (parts.attrs !== void 0 && parts.attrs.errors.length > 0) throw configError(`PermDock CLI: ${parts.attrs.errors.join("; ")}`);
2013
+ if (extraPlan.errors.length > 0) throw configError(`PermDock CLI: ${extraPlan.errors.join("; ")}`);
1145
2014
  quoteIdent(parts.tenantClaim);
1146
2015
  return parts;
1147
2016
  }
@@ -1453,11 +2322,14 @@ function trustedHelpers(parts, config, types, tenantType, policy) {
1453
2322
  const forUser = database ? [
1454
2323
  helperEntry(HELPERS.hasFor, "p_user uuid, p_grant text", "boolean", []),
1455
2324
  ...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", []))
2325
+ ...scopeTypes.map(([scope, type]) => helperEntry(permissionKeysForHelper(scope), `p_user uuid, p_id ${type}`, "setof text", [])),
2326
+ ...scopeTypes.filter(([scope]) => parts.helpers.holders.includes(scope)).map(([scope, type]) => helperEntry(permittedUsersHelper(scope), `p_permission text, p_id ${type}`, "setof uuid", []))
1457
2327
  ] : [];
1458
2328
  const keyed = [
1459
2329
  helperEntry(USER_ID_HELPER, "", "uuid", client),
1460
2330
  ...config.rls?.apiKeys === void 0 ? [] : [helperEntry(API_KEY_ALLOWS, "p_grant text", "boolean", client)],
2331
+ ...readOnlyActorKinds(config.rls?.readOnlyActors) === void 0 ? [] : [helperEntry(ACTOR_MAY_WRITE, "", "boolean", client)],
2332
+ ...!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
2333
  ...forUser
1462
2334
  ];
1463
2335
  if (!(policy !== void 0 && (ownershipRules(policy, parts.scopes)?.assigns.length ?? 0) > 0)) return keyed;
@@ -1606,25 +2478,25 @@ function defaultOut(config) {
1606
2478
  //#region src/cli/supabase-hook/supabase-hook.ts
1607
2479
  const SUPABASE_HELP = `permdock supabase hook generate | inspect
1608
2480
 
1609
- hook generate [--out supabase/permdock-hook.sql] [--check] [--db <url>]
2481
+ hook generate [--out <file>] [--check] [--db-url-stdin] [--schemas pg-delta|migra|migrations]
1610
2482
  [--active-from app_metadata.active_<scope>|<table>.<column>]
1611
2483
  [--budget 1024] [--schema public] [--grants-out <file>|-]
1612
2484
  inspect [--json] [--out [permdock.manifest.json]] [--check]
1613
2485
 
1614
- Reads supabase.hook from permdock.config.ts: the fromTable / fromJunction sources the app
2486
+ Reads supabase.hook from permdock.config.ts: the tableMembershipSource / junctionMembershipSource sources the app
1615
2487
  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
2488
+ needs, the permdock_authz_version table and its triggers. Under pg-delta (the default) --out defaults to
2489
+ supabase/schemas/<schema>/functions/custom_access_token_hook.sql and the grants stay in it; under
2490
+ --schemas migra, --grants-out takes the privileges supabase db diff drops. Never grants anything to
1618
2491
  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
2492
  budget, the claims the hook writes, the membership sources and the columns that decide them.
1620
2493
  --out writes it as JSON, to permdock.manifest.json without a path; --check exits 1 when that
1621
2494
  file differs.
1622
2495
  `;
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");
2496
+ /** `supabase.hook.out`, else pg-delta's per-schema path under pg-delta, else `supabase/permdock-hook.sql`. */
2497
+ function hookOut(cwd, config, schema, flag) {
2498
+ const schemas = resolveSchemas(cwd, config, flag);
2499
+ 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
2500
  }
1629
2501
  /**
1630
2502
  * The manifest `supabase inspect` prints. With `policy`, `rls.helpers` also
@@ -1632,7 +2504,14 @@ function hookOut(cwd, config, schema) {
1632
2504
  * `assigns`.
1633
2505
  */
1634
2506
  function supabaseHookManifest(scopes, config, overrides = {}, policy) {
1635
- return manifestOf(hookParts(scopes, config, overrides), overrides.out ?? defaultOut(config), config, policy);
2507
+ const manifest = manifestOf(hookParts(scopes, config, overrides), overrides.out ?? defaultOut(config), config, policy);
2508
+ return overrides.schemas === void 0 ? manifest : {
2509
+ ...manifest,
2510
+ hook: {
2511
+ ...manifest.hook,
2512
+ schemas: overrides.schemas
2513
+ }
2514
+ };
1636
2515
  }
1637
2516
  /**
1638
2517
  * The hook migration. With `grantsOut`, the statements `supabase db diff`
@@ -1697,7 +2576,7 @@ function inspectText(manifest) {
1697
2576
  function manifestFile(cwd, rel, manifest, check) {
1698
2577
  const path = resolve(cwd, rel);
1699
2578
  if (!check) {
1700
- writeFileEnsuringDir(path, `${JSON.stringify(manifest, null, 2)}\n`);
2579
+ writeFileEnsuringDir(path, formatManifestJson(manifest));
1701
2580
  return {
1702
2581
  code: 0,
1703
2582
  output: `wrote ${rel}`
@@ -1734,6 +2613,13 @@ function loadHookPolicy(cwd, config) {
1734
2613
  }
1735
2614
  async function runSupabase(input) {
1736
2615
  const [area, action] = input.rest;
2616
+ const badSchemas = schemasRefusal("supabase", input.schemas);
2617
+ if (badSchemas !== void 0) return {
2618
+ code: 2,
2619
+ output: badSchemas
2620
+ };
2621
+ const flag = isSupabaseSchemas(input.schemas) ? input.schemas : void 0;
2622
+ const schemas = resolveSchemas(input.cwd, input.config, flag);
1737
2623
  const overrides = Object.fromEntries(Object.entries({
1738
2624
  activeFrom: input.activeFrom,
1739
2625
  budget: input.budget,
@@ -1743,7 +2629,8 @@ async function runSupabase(input) {
1743
2629
  const policy = await loadHookPolicy(input.cwd, input.config);
1744
2630
  const manifest = supabaseHookManifest(scopeList(policy.scopes), input.config, {
1745
2631
  ...overrides,
1746
- out: hookOut(input.cwd, input.config, input.schema)
2632
+ out: hookOut(input.cwd, input.config, input.schema, flag),
2633
+ schemas: schemas.mode
1747
2634
  }, policy);
1748
2635
  if (input.out !== void 0 || input.check) return manifestFile(input.cwd, typeof input.out === "string" ? input.out : MANIFEST_FILE, manifest, input.check);
1749
2636
  return {
@@ -1755,8 +2642,12 @@ async function runSupabase(input) {
1755
2642
  code: 2,
1756
2643
  output: SUPABASE_HELP
1757
2644
  };
2645
+ if (input.grantsOut !== void 0 && schemas.mode !== "migra") return {
2646
+ code: 2,
2647
+ 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`
2648
+ };
1758
2649
  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);
2650
+ const outRel = typeof input.out === "string" ? input.out : hookOut(input.cwd, input.config, input.schema, flag);
1760
2651
  const outPath = resolve(input.cwd, outRel);
1761
2652
  const hook = input.config.supabase?.hook;
1762
2653
  const grantsFile = input.grantsOut === void 0 ? [] : [{
@@ -1806,4 +2697,4 @@ async function runSupabase(input) {
1806
2697
  };
1807
2698
  }
1808
2699
  //#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 };
2700
+ 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 };