permdock 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (515) hide show
  1. package/README.md +37 -35
  2. package/dist/{schema-Bol6gqXA.js → 2020-C9VN-x8c.js} +5 -130
  3. package/dist/a2a/index.d.ts +6 -6
  4. package/dist/a2a/index.js +14 -17
  5. package/dist/{adapter-context-mlWsVGGg.js → adapter-context-CRhqyzCk.js} +1 -1
  6. package/dist/adoption-scan-DCYe60Pi.js +107 -0
  7. package/dist/agent/index.d.ts +3 -0
  8. package/dist/agent/index.js +1 -0
  9. package/dist/ai-sdk/index.d.ts +18 -35
  10. package/dist/ai-sdk/index.js +37 -23
  11. package/dist/{app-data-CV8pqqMi.js → app-data-MuyzVqX8.js} +2 -1
  12. package/dist/approval-header-CJACtHkv.js +23 -0
  13. package/dist/approval-prompt-Dg5LeAcT.js +62 -0
  14. package/dist/approval-prompt-DvSSPQJX.d.ts +57 -0
  15. package/dist/approvals/index.d.ts +5 -3
  16. package/dist/approvals/index.js +8 -5
  17. package/dist/approvals-p1PGoVjb.js +995 -0
  18. package/dist/arazzo-41WuzHou.js +36 -0
  19. package/dist/{arazzo-C4BcskbX.js → arazzo-CaUVBn7D.js} +20 -14
  20. package/dist/args-IIolxxKA.js +197 -0
  21. package/dist/astro/index.d.ts +43 -0
  22. package/dist/astro/index.js +27 -0
  23. package/dist/audience-DabClFLQ.js +55 -0
  24. package/dist/auth-DRLEizbL.js +178 -0
  25. package/dist/authzen/index.d.ts +26 -6
  26. package/dist/authzen/index.js +94 -79
  27. package/dist/automation-BNM3TtDo.d.ts +114 -0
  28. package/dist/automation-DVjOckjV.js +157 -0
  29. package/dist/better-auth/index.d.ts +8 -6
  30. package/dist/better-auth/index.js +9 -8
  31. package/dist/better-supabase/index.d.ts +357 -30
  32. package/dist/better-supabase/index.js +936 -194
  33. package/dist/{bind-kk-uyCwH.js → bind-CAvgUuKs.js} +14 -10
  34. package/dist/{bind-BzqG8SMb.d.ts → bind-DOyrv6zi.d.ts} +12 -10
  35. package/dist/body-CH5of8Ne.js +10 -0
  36. package/dist/body-text-QcXok_SI.js +52 -0
  37. package/dist/{build-instance-DIIV9DbC.js → build-instance-Bu_eDqYw.js} +4 -3
  38. package/dist/{canonical-json-CFKyFOke.js → canonical-json-BC8_nMTw.js} +5 -3
  39. package/dist/{capability-CksyCWDH.d.ts → capability-Drg-1YhL.d.ts} +44 -25
  40. package/dist/{capability-txATtKV4.js → capability-N1K9IpqS.js} +68 -42
  41. package/dist/casbin-Cr7RLtA3.js +30 -0
  42. package/dist/casbin-Dyu2gnUD.js +234 -0
  43. package/dist/casl-64gpT-8x.js +24 -0
  44. package/dist/casl-DjEDhTCP.js +299 -0
  45. package/dist/catalog/index.d.ts +74 -2
  46. package/dist/catalog/index.js +51 -2
  47. package/dist/catalog-BoWglDPI.js +1 -0
  48. package/dist/catalog-DZVbrF-z.js +52 -0
  49. package/dist/{catalog-tZUyOfOv.js → catalog-HD2gf5mY.js} +51 -6
  50. package/dist/{catalog-doc-DWtg0gGR.js → catalog-doc-Cdn7-0pQ.js} +45 -23
  51. package/dist/catalog-fingerprint-CCP0-yQS.js +153 -0
  52. package/dist/chat/index.d.ts +2 -0
  53. package/dist/chat/index.js +224 -0
  54. package/dist/chat-sdk/index.d.ts +87 -0
  55. package/dist/chat-sdk/index.js +197 -0
  56. package/dist/claims-map-7oQ3ue4X.js +13 -0
  57. package/dist/claims-map-Ca-QX8C6.d.ts +32 -0
  58. package/dist/claude-agent/index.d.ts +7 -14
  59. package/dist/claude-agent/index.js +24 -27
  60. package/dist/clerk/index.d.ts +9 -8
  61. package/dist/clerk/index.js +9 -14
  62. package/dist/cli/bin.js +36 -17
  63. package/dist/cli/index.d.ts +19 -5
  64. package/dist/cli/index.js +164 -4
  65. package/dist/cloud/index.d.ts +102 -63
  66. package/dist/cloud/index.js +239 -340
  67. package/dist/cloud-ytQ3uviA.js +87 -0
  68. package/dist/codemod-5mpSpnUY.js +37 -0
  69. package/dist/codemod-ua2hHy7w.js +2084 -0
  70. package/dist/collect-Ci6akVwH.js +55 -0
  71. package/dist/collect-D7eQDffE.js +1 -0
  72. package/dist/{collect-2SrB7lfk.js → collect-mLTY4nwb.js} +75 -11
  73. package/dist/command-L8EstFNm.js +6 -0
  74. package/dist/compile/index.d.ts +1 -1
  75. package/dist/compile/index.js +1 -2
  76. package/dist/{compile-CtTP7eur.d.ts → compile-1bsAezL0.d.ts} +5 -3
  77. package/dist/{compile-CcEu2ZQ1.js → compile-CMqMx_IN.js} +11 -8
  78. package/dist/completion-BTFrHZhX.js +143 -0
  79. package/dist/config-Bj2Ixq3d.js +26 -0
  80. package/dist/{config-BIjREpyE.js → config-bs_kZeYq.js} +12 -7
  81. package/dist/{config-report--Xb9APJS.js → config-report-DSpsLpCE.js} +15 -5
  82. package/dist/{connection-DUsczpB5.js → connection-DjhIJSs2.js} +6 -5
  83. package/dist/{context-DieZVJXu.js → context-B_u3_bDJ.js} +2 -2
  84. package/dist/convex/index.d.ts +11 -10
  85. package/dist/convex/index.js +6 -4
  86. package/dist/create-B30OMUOJ.d.ts +107 -0
  87. package/dist/create-CBMmFie-.d.ts +138 -0
  88. package/dist/create-CGm28gyI.js +130 -0
  89. package/dist/create-CQWIpUG-.js +403 -0
  90. package/dist/{create-BBO24vj6.js → create-DCF2SvoT.js} +31 -32
  91. package/dist/create-D_NmPOdH.d.ts +110 -0
  92. package/dist/{credential-kue0qpbm.js → credential-BcoiejpY.js} +152 -31
  93. package/dist/credentials-DPyHmYz5.js +74 -0
  94. package/dist/delegation-j6-rc37o.js +286 -0
  95. package/dist/delivery/index.d.ts +5 -0
  96. package/dist/delivery/index.js +5 -0
  97. package/dist/delivery-DCAgNyCn.js +499 -0
  98. package/dist/delivery-event-xX4obB8F.js +40 -0
  99. package/dist/{denied-text-DfRk15uw.d.ts → denied-text-CiFQNuMV.d.ts} +5 -5
  100. package/dist/{describe-C3ldA9os.d.ts → describe-B5qgDvEW.d.ts} +2 -2
  101. package/dist/{describe-Cgkp7eP0.js → describe-BybMRY7A.js} +6 -2
  102. package/dist/device-BARQ8eYG.js +193 -0
  103. package/dist/{diff-rpqoe7Ag.js → diff-3jbx6Rm4.js} +60 -54
  104. package/dist/diff-CmuSD3vc.js +37 -0
  105. package/dist/doctor-3ZqNHX_G.js +44 -0
  106. package/dist/{doctor-owlv6ddh.js → doctor-BdZw53-k.js} +245 -50
  107. package/dist/drizzle/index.d.ts +5 -3
  108. package/dist/drizzle/index.js +2 -2
  109. package/dist/elysia/index.d.ts +16 -13
  110. package/dist/elysia/index.js +13 -12
  111. package/dist/env-DJZrPjRi.js +8 -0
  112. package/dist/errors-BDUwg2qC.js +27 -0
  113. package/dist/{errors-drnfxYtm.js → errors-BKy0EfhN.js} +5 -9
  114. package/dist/errors-BWXBMaKK.d.ts +11 -0
  115. package/dist/errors-CWTY6uih.js +143 -0
  116. package/dist/{errors-OD_4c__H.d.ts → errors-DH5QEGXh.d.ts} +2 -2
  117. package/dist/evaluate-CNClBvcj.js +1785 -0
  118. package/dist/evaluations-C_1vdSZc.d.ts +32 -0
  119. package/dist/{evaluations-C9YJsv3H.js → evaluations-beAooV0n.js} +145 -34
  120. package/dist/eve/index.d.ts +12 -16
  121. package/dist/eve/index.js +22 -28
  122. package/dist/event-time-CsegxXxB.js +23 -0
  123. package/dist/expiry-D4_YKRKD.js +52 -0
  124. package/dist/express/index.d.ts +13 -10
  125. package/dist/express/index.js +12 -11
  126. package/dist/fastify/index.d.ts +25 -22
  127. package/dist/fastify/index.js +13 -12
  128. package/dist/fetch-adapter-DD_gfmrC.js +120 -0
  129. package/dist/fetch-adapter-Dw0MTS3D.d.ts +31 -0
  130. package/dist/fields-OYG_JR78.js +203 -0
  131. package/dist/files-C5ZI27eV.js +124 -0
  132. package/dist/{fixtures-h2eG2OZV.js → fixtures-B7XrLeY7.js} +8 -7
  133. package/dist/flags-C3DktBDY.js +86 -0
  134. package/dist/{from-snapshot-7REQZe0H.js → from-snapshot-CTCMsFZk.js} +55 -52
  135. package/dist/{grantee-conditions-MQs5NIuX.js → grantee-conditions-CcXf8hMM.js} +4 -3
  136. package/dist/{grantee-CXY-0C87.js → grantee-hxrUsbpM.js} +16 -6
  137. package/dist/{grantee-match-fxYp9Nmt.js → grantee-match-DbENBHLB.js} +5 -4
  138. package/dist/h3/index.d.ts +37 -0
  139. package/dist/h3/index.js +35 -0
  140. package/dist/headers-BIjfMaoC.js +8 -0
  141. package/dist/headers-CIbDTUTK.d.ts +7 -0
  142. package/dist/{helpers-C9YmyBIa.js → helpers-CBYVMjwh.js} +45 -38
  143. package/dist/hono/index.d.ts +12 -9
  144. package/dist/hono/index.js +13 -12
  145. package/dist/hosted-B46-5LYE.js +315 -0
  146. package/dist/hosted-BPk51-kX.d.ts +78 -0
  147. package/dist/{http-DPuDaChb.d.ts → http-B_LphE5Y.d.ts} +1 -2
  148. package/dist/http-DXJ37RDA.js +46 -0
  149. package/dist/{http-BEbdMZ05.js → http-ujKm0JXE.js} +1 -2
  150. package/dist/{import-UHk1N7vH.js → import-CNV7I2Dw.js} +25 -20
  151. package/dist/{import-BFIlhetD.js → import-wyqXUyjm.js} +190 -53
  152. package/dist/index-B8YjkEZ4.d.ts +210 -0
  153. package/dist/index-C-6Letxg.d.ts +431 -0
  154. package/dist/index-CaxrMD1A.d.ts +1 -0
  155. package/dist/{index-BwMe94Xq.d.ts → index-Ch8LpNDp.d.ts} +34 -16
  156. package/dist/index-Cp_ilKi1.d.ts +91 -0
  157. package/dist/index-CueFnxQc.d.ts +110 -0
  158. package/dist/index-DuWxc5dt.d.ts +909 -0
  159. package/dist/index.d.ts +19 -15
  160. package/dist/index.js +31 -21
  161. package/dist/init-DeMYsnYJ.js +96 -0
  162. package/dist/init-GG6Jh8HD.js +95 -0
  163. package/dist/{instance-options-plkrsAog.d.ts → instance-options-BsVpyGIk.d.ts} +2 -2
  164. package/dist/instance-snapshot-BOEhcqIF.js +601 -0
  165. package/dist/instant-BluqDvyS.js +19 -0
  166. package/dist/internals-Dk8A8fX3.js +17 -0
  167. package/dist/{json-schema-DBkdUuCO.js → json-schema-B-vAwlde.js} +45 -6
  168. package/dist/json-schema-CN_iLFH2.d.ts +40 -0
  169. package/dist/jwt/index.d.ts +18 -11
  170. package/dist/jwt/index.js +156 -135
  171. package/dist/{kernel-CB3LrIJM.js → kernel-D0NVb1cK.js} +52 -24
  172. package/dist/kysely/index.d.ts +5 -3
  173. package/dist/kysely/index.js +2 -2
  174. package/dist/load-BxEtwlBS.js +1 -0
  175. package/dist/{load-C3_oTHyO.js → load-DFS_ry0W.js} +22 -6
  176. package/dist/{load-jose-BL6LH4bL.js → load-jose-DZ4dnjri.js} +1 -4
  177. package/dist/login-Dfe5RlYk.js +68 -0
  178. package/dist/login-imhuwvn3.js +254 -0
  179. package/dist/manifest-C8IzW3Do.js +42 -0
  180. package/dist/manifest-dvL2XEe1.js +370 -0
  181. package/dist/manifest-json-CAGzls-C.js +9 -0
  182. package/dist/{manifest-uDS9y5de.d.ts → manifest-mx5ghSFM.d.ts} +9 -2
  183. package/dist/manifest-schema-C0kK_8V0.js +399 -0
  184. package/dist/{map-claims-AVlBaJoa.js → map-claims-D3oRmTk0.js} +13 -10
  185. package/dist/{map-error-BE27PYkE.js → map-error-Dr70oA_3.js} +3 -3
  186. package/dist/{markers-CiwAOZtO.js → markers-C6joDI3o.js} +2 -1
  187. package/dist/mcp/index.d.ts +22 -12
  188. package/dist/mcp/index.js +57 -39
  189. package/dist/{migrate-BwlB5-Wk.js → migrate-CIq4-E9F.js} +53 -14
  190. package/dist/nest/index.d.ts +12 -9
  191. package/dist/nest/index.js +15 -14
  192. package/dist/next/index.d.ts +18 -21
  193. package/dist/next/index.js +13 -11
  194. package/dist/next/plugin.d.ts +1 -1
  195. package/dist/next/plugin.js +2 -2
  196. package/dist/node/index.d.ts +10 -10
  197. package/dist/node/index.js +7 -9
  198. package/dist/{ocsf-uYFXGVw2.js → ocsf-BFp-PZZA.js} +22 -11
  199. package/dist/openai/index.d.ts +9 -16
  200. package/dist/openai/index.js +17 -24
  201. package/dist/openapi/index.d.ts +3 -3
  202. package/dist/openapi/index.js +1 -1
  203. package/dist/{openapi-DBzHQbov.js → openapi-B7HZ0f-r.js} +8 -8
  204. package/dist/openapi-C4dDTVEd.js +138 -0
  205. package/dist/{openapi-BJdBmYDG.js → openapi-DD1SV-5-.js} +10 -9
  206. package/dist/operation-findings-DgATDpvh.js +36 -0
  207. package/dist/operations-X0VM7F3m.d.ts +59 -0
  208. package/dist/orpc/index.d.ts +13 -10
  209. package/dist/orpc/index.js +20 -15
  210. package/dist/otel/index.d.ts +3 -2
  211. package/dist/otel/index.js +30 -7
  212. package/dist/output-1im__-08.js +88 -0
  213. package/dist/output-files-B3IZJksC.js +1 -0
  214. package/dist/{output-files-CgIsvdSa.js → output-files-DhRBBE4n.js} +24 -26
  215. package/dist/{permission-keys-B26jeyUv.js → ownership-DqaJNJPn.js} +1045 -153
  216. package/dist/{package-root-DtNnpmUd.js → package-root-Dk_ETdkp.js} +1 -1
  217. package/dist/{parse-CVFQHk6c.js → parse-Cxq0WxrA.js} +2 -2
  218. package/dist/{parse-local-manifest-DvYUR0QL.d.ts → parse-local-manifest-5ODwqu8b.d.ts} +1 -1
  219. package/dist/{parse-local-manifest-C-q02Pal.js → parse-local-manifest-CVSs6J9I.js} +1 -1
  220. package/dist/parse-request-D_tUXG6M.js +26 -0
  221. package/dist/paths-DXSHAMIa.js +42 -0
  222. package/dist/pdp/index.d.ts +7 -3
  223. package/dist/pdp/index.js +45 -26
  224. package/dist/{peer-BOr_kJE1.js → peer-C8swJ2GH.js} +23 -4
  225. package/dist/peer-CRvC7Hg1.js +1 -0
  226. package/dist/permdock-BrAscV28.js +868 -0
  227. package/dist/permdock-SV8ZINYT.d.ts +502 -0
  228. package/dist/{permission-registry-BsblpB-z.js → permission-registry-Br4jSNVi.js} +3 -3
  229. package/dist/permissions-B-wCP5ZC.js +30 -0
  230. package/dist/{permissions-CCJ6lmxB.js → permissions-BPxlwVhN.js} +4 -3
  231. package/dist/{permissions-COH-KRgT.d.ts → permissions-u_KHg-sN.d.ts} +1 -2
  232. package/dist/policies-DP2nqRoF.js +295 -0
  233. package/dist/{policy-ur0BBK_e.d.ts → policy-77FSAq_V.d.ts} +2 -2
  234. package/dist/{hosted-CBHgkzDl.js → policy-DbM5YYtV.js} +17 -273
  235. package/dist/policy-from-catalog-C2gMa2Dj.js +450 -0
  236. package/dist/policy-module-CTlwMvLI.js +89 -0
  237. package/dist/{policy-types-sp9P1iIV.d.ts → policy-types-n0uRTsW8.d.ts} +460 -632
  238. package/dist/postgrest-CEgRbefE.d.ts +100 -0
  239. package/dist/postgrest-DAjUkF9w.js +575 -0
  240. package/dist/{powersync-cZe4kzhL.js → powersync-BkWWCS1K.js} +23 -24
  241. package/dist/powersync-DISt6SUe.js +62 -0
  242. package/dist/powersync-MMJ2mI2q.js +1 -0
  243. package/dist/preset/index.d.ts +146 -0
  244. package/dist/preset/index.js +1 -0
  245. package/dist/preset-DZdU0tne.js +1768 -0
  246. package/dist/principal-source-pHneukrx.js +103 -0
  247. package/dist/prisma/index.d.ts +6 -3
  248. package/dist/prisma/index.js +2 -2
  249. package/dist/{problem-CnWraVhC.js → problem-CTviyH5L.js} +13 -23
  250. package/dist/{problem-details-rIIo8B2C.d.ts → problem-details-B45V5d2b.d.ts} +1 -1
  251. package/dist/{problem-details-DECUnXAn.js → problem-details-cn5X30gZ.js} +43 -40
  252. package/dist/problem-types-CJ2325SZ.js +5 -0
  253. package/dist/{prompt-CXYhLfsS.js → prompt-DfZLJLIn.js} +2 -2
  254. package/dist/{protected-BN7BUdY1.js → protected-BlF0F6wL.js} +21 -4
  255. package/dist/{protected-B3_P7xuw.d.ts → protected-RFGpakN9.d.ts} +16 -8
  256. package/dist/{provider-Bi-0HNL3.js → provider-B1al-AZz.js} +2 -2
  257. package/dist/{provider-kwHC9CZD.d.ts → provider-DILL9oPK.d.ts} +1 -1
  258. package/dist/{provider-store-Ctt-81Ni.js → provider-store-DM6DNcta.js} +2 -2
  259. package/dist/pull-CvpINcMK.js +1 -0
  260. package/dist/pull-jEwbf80J.js +256 -0
  261. package/dist/push-BD4f15co.js +116 -0
  262. package/dist/pushed-policy-CqtxkV4G.js +238 -0
  263. package/dist/pushed-policy-DCW91KQU.d.ts +57 -0
  264. package/dist/react/index.d.ts +8 -8
  265. package/dist/react/index.js +6 -6
  266. package/dist/react/provider-client.d.ts +1 -1
  267. package/dist/react/provider-client.js +1 -2
  268. package/dist/react-native/index.d.ts +24 -72
  269. package/dist/react-native/index.js +29 -25
  270. package/dist/{refs-f3imMjNW.js → refs-K6eWUJ0H.js} +1 -1
  271. package/dist/{relation-walk-BXnlLtg2.js → relation-walk-3OyDBF0S.js} +2 -2
  272. package/dist/{relations-CvmSpG67.js → relations-BnCWnJGI.js} +3 -3
  273. package/dist/renames-CGj7YwcQ.js +175 -0
  274. package/dist/{replay-COpmZUfx.d.ts → replay-Bkm-Ne4l.d.ts} +1 -1
  275. package/dist/{replay-BsOyTrdV.js → replay-Du1tJjG7.js} +2 -1
  276. package/dist/{request-context-BVM7aWNs.js → request-context-C81Pip9n.js} +3 -3
  277. package/dist/request-schema-CMreVuFM.js +148 -0
  278. package/dist/resend/index.d.ts +126 -0
  279. package/dist/resend/index.js +379 -0
  280. package/dist/{resource-relations-D1Un3pcz.js → resource-relations-D_jw0BFx.js} +1 -1
  281. package/dist/{retire-BFcxL6un.js → retire-CALjIQ7e.js} +2 -2
  282. package/dist/{revocations-Cy06JNx2.d.ts → revocations-R07lFBnX.d.ts} +2 -0
  283. package/dist/rls-BAqP4q66.js +336 -0
  284. package/dist/{rls-BEktUaqg.js → rls-DTvYd-lI.js} +288 -690
  285. package/dist/{role-builder-B4GbKM-i.js → role-builder-uWQZ4v84.js} +14 -207
  286. package/dist/{rpc-nYC3cR_u.js → rpc-VSl5ylaQ.js} +7 -7
  287. package/dist/run-CUKuz0A8.js +723 -0
  288. package/dist/run-DDwUaJDU.js +1 -0
  289. package/dist/run-ticket-DocwaTKn.d.ts +124 -0
  290. package/dist/run-ticket-sxFI6LAk.js +337 -0
  291. package/dist/{runtime-D_tMXnsq.js → runtime-D3VTAiCA.js} +1 -1
  292. package/dist/{saas-DtaA4JuW.js → saas-BCXRuXca.js} +16 -14
  293. package/dist/scan-qvOe0hS3.js +29 -0
  294. package/dist/{schema-DjQBcAP9.js → schema-B4v0Qrac.js} +102 -8
  295. package/dist/schema-yLuBWWsh.js +130 -0
  296. package/dist/scim/index.d.ts +2 -2
  297. package/dist/scim/index.js +1 -2
  298. package/dist/{scim-vFR1O5oG.js → scim-ClHtpyV3.js} +208 -76
  299. package/dist/{scopes-tvpa34ep.js → scopes-CUPSjo9q.js} +12 -7
  300. package/dist/server/index.d.ts +12 -143
  301. package/dist/server/index.js +12 -9
  302. package/dist/server-C3WXAHZj.js +453 -0
  303. package/dist/{server-types-DdqBwZN_.d.ts → server-types-CKQiCnON.d.ts} +51 -68
  304. package/dist/{sha256-BO0swRXL.js → sha256-D9ihqmvd.js} +8 -1
  305. package/dist/{shared-1uhI-e-b.js → shared-BAK8t4Uo.js} +20 -9
  306. package/dist/{shared-JhMeOsDF.js → shared-DvvFzYXe.js} +8 -9
  307. package/dist/signals-D3NcTKUS.js +32 -0
  308. package/dist/sink-4-eQIRWf.d.ts +225 -0
  309. package/dist/sink-BEZSlMS_.js +957 -0
  310. package/dist/{skills-BZdRQkxs.js → skills-B6lVdD_W.js} +13 -6
  311. package/dist/skills-CTYwkl44.js +51 -0
  312. package/dist/snapshot-B7DkYg6g.js +269 -0
  313. package/dist/{snapshot-cache-Cod40D-X.js → snapshot-cache-DZHQ0KF6.js} +11 -5
  314. package/dist/{snapshot-cache-J5T7t_vD.d.ts → snapshot-cache-PXI-vGpY.d.ts} +7 -5
  315. package/dist/solid/index.d.ts +11 -12
  316. package/dist/solid/index.js +7 -7
  317. package/dist/solid-start/index.d.ts +52 -0
  318. package/dist/solid-start/index.js +49 -0
  319. package/dist/{sources-Dljy06zL.d.ts → sources-Xc4zkszI.d.ts} +7 -13
  320. package/dist/{sources-B1-b-R0t.js → sources-vs5dGOsS.js} +125 -119
  321. package/dist/{sql-t8teWOfo.js → sql-B5T1IL83.js} +13 -1
  322. package/dist/src-Cu2m75uo.js +1860 -0
  323. package/dist/ssf/index.d.ts +4 -4
  324. package/dist/ssf/index.js +384 -47
  325. package/dist/standard-webhooks-DgFFD-Ga.js +228 -0
  326. package/dist/standard-webhooks-rbD7LS6h.d.ts +88 -0
  327. package/dist/storage-C2bWdvJH.js +132 -0
  328. package/dist/{store-ILkLUj_o.js → store-BDutJkDL.js} +40 -53
  329. package/dist/{store-options-CbUHDoSe.js → store-options-u0ycEe8s.js} +26 -21
  330. package/dist/{stores-Cxz_80g-.d.ts → stores-BUGNbJTt.d.ts} +7 -8
  331. package/dist/{stores-BHXdSbw9.js → stores-CGhM8BN_.js} +2 -2
  332. package/dist/{stream-Dwsg6WwE.d.ts → stream-CeCMJmBB.d.ts} +2 -2
  333. package/dist/{stream-BMui98KK.js → stream-CvdUS9V1.js} +9 -2
  334. package/dist/style-DiKWnAvn.js +1 -0
  335. package/dist/{style-CTm1AXDT.js → style-Uao76wO7.js} +3 -2
  336. package/dist/subject-BGoM5o8B.js +17 -0
  337. package/dist/supabase/index.d.ts +23 -106
  338. package/dist/supabase/index.js +5 -4
  339. package/dist/supabase/middleware.d.ts +7 -6
  340. package/dist/supabase/middleware.js +13 -11
  341. package/dist/supabase-By_frk3U.js +92 -0
  342. package/dist/{supabase-hook-Cva3ikXz.js → supabase-hook-BMoEhZir.js} +960 -64
  343. package/dist/supabase-hook-CCw5m35C.js +1 -0
  344. package/dist/{supabase-C1TWlnYt.js → supabase-x5JtR2tc.js} +99 -206
  345. package/dist/support-session-C6xJQYut.js +158 -0
  346. package/dist/svelte/Protected.svelte +4 -4
  347. package/dist/svelte/index.d.ts +7 -7
  348. package/dist/svelte/index.js +10 -10
  349. package/dist/svelte/runtime.d.ts +3 -3
  350. package/dist/svelte/runtime.js +3 -3
  351. package/dist/svelte/source.d.ts +7 -7
  352. package/dist/svelte/source.js +4 -4
  353. package/dist/sveltekit/index.d.ts +51 -0
  354. package/dist/sveltekit/index.js +27 -0
  355. package/dist/tanstack-start/index.d.ts +51 -0
  356. package/dist/tanstack-start/index.js +40 -0
  357. package/dist/target-Bw37TFNA.js +51 -0
  358. package/dist/{tenancy-DshjqY0n.js → tenancy-DN2QAx9z.js} +5 -5
  359. package/dist/terminal/index.d.ts +11 -9
  360. package/dist/terminal/index.js +23 -287
  361. package/dist/testing/index.d.ts +311 -38
  362. package/dist/testing/index.js +3475 -201
  363. package/dist/testing/saas/index.d.ts +9 -8
  364. package/dist/testing/saas/index.js +2 -2
  365. package/dist/testing/saas/permissions.d.ts +1 -1
  366. package/dist/testing/saas/permissions.js +1 -2
  367. package/dist/text-BMFRfBqc.js +8 -0
  368. package/dist/{text-diff-BdkkYSbi.js → text-diff-BQcUapOb.js} +1 -1
  369. package/dist/thenable-yD7SabqG.js +31 -0
  370. package/dist/time-DkWjdL98.js +71 -0
  371. package/dist/{token-Bx9qy30p.js → token-BLZcjQgt.js} +15 -6
  372. package/dist/{tool-refusal-CQ7OEia9.js → tool-refusal-CMLN5xhn.js} +2 -2
  373. package/dist/trpc/index.d.ts +12 -9
  374. package/dist/trpc/index.js +16 -14
  375. package/dist/types-8RnwUAvE.d.ts +107 -0
  376. package/dist/{store-9KO4BlkB.d.ts → types-B3_ooZgZ.d.ts} +100 -75
  377. package/dist/{types-Br9IXUBG.d.ts → types-BdtSDbBO.d.ts} +77 -17
  378. package/dist/types-BtHtcKMr.d.ts +157 -0
  379. package/dist/{types-CqrjB9o6.d.ts → types-BzzaHxt3.d.ts} +23 -11
  380. package/dist/types-C791HFu9.d.ts +332 -0
  381. package/dist/types-C863OQiR.d.ts +57 -0
  382. package/dist/{types-ByUo56un.d.ts → types-CVZSNocA.d.ts} +23 -22
  383. package/dist/{types-DHrmBJLH.d.ts → types-CX7AvRqP.d.ts} +7 -7
  384. package/dist/types-C_IiEs64.d.ts +65 -0
  385. package/dist/{types-7rJVLWe5.d.ts → types-Dfv0aTER.d.ts} +63 -11
  386. package/dist/types-DqzsnlSA.js +67 -0
  387. package/dist/{types-DpWfQdSh.d.ts → types-Druu9wLM.d.ts} +46 -11
  388. package/dist/{types-CIlTojhq.d.ts → types-_WhUP7ud.d.ts} +9 -6
  389. package/dist/{types-CUcFZfpU.d.ts → types-lUlG3TVd.d.ts} +2 -1
  390. package/dist/unplugin/index.d.ts +1 -1
  391. package/dist/unplugin/index.js +2 -2
  392. package/dist/usage-CRVl9V3t.js +32 -0
  393. package/dist/usage-CZpSQP6L.js +1 -0
  394. package/dist/{usage-CG9nOYZw.js → usage-SveTRTyr.js} +54 -11
  395. package/dist/{validation-error-BSSw0l_8.d.ts → validation-error-DHUrAkku.d.ts} +1 -1
  396. package/dist/{validity-dVrUEPJt.js → validity-CZ7-y0Cj.js} +32 -15
  397. package/dist/{verifier-DMr_Zmb_.js → verifier-DHuh-okk.js} +18 -7
  398. package/dist/{verify-BztUB3GX.js → verify-BaAKx7t6.js} +645 -50
  399. package/dist/{version-BEogC-rB.js → version-CEbQ5UeM.js} +4 -3
  400. package/dist/version-DXkYUISx.js +1 -0
  401. package/dist/{views-CJsrBS1f.js → views-DTIIO-oQ.js} +22 -5
  402. package/dist/{permission-types-Dg39z9Kv.d.ts → vocabulary-CN8H_9UF.d.ts} +67 -2
  403. package/dist/vue/index.d.ts +9 -10
  404. package/dist/vue/index.js +8 -8
  405. package/dist/waiter-B986w1oK.d.ts +36 -0
  406. package/dist/waiter-BGUtGYoU.js +67 -0
  407. package/dist/watch--ovlPOc0.js +1 -0
  408. package/dist/{watch-zeucB7eF.js → watch-vea54PRw.js} +44 -5
  409. package/dist/webmcp/index.d.ts +2 -2
  410. package/dist/webmcp/index.js +3 -3
  411. package/dist/where-scope-C9vfEphl.js +306 -0
  412. package/dist/who-can-DWBAcaCQ.js +364 -0
  413. package/dist/wire-delegation-Cv3W2eaM.js +36 -0
  414. package/dist/wire-denial-C7PaVYEM.js +27 -0
  415. package/dist/workflow/index.d.ts +5 -0
  416. package/dist/workflow/index.js +223 -0
  417. package/dist/workflow-sdk/index.d.ts +56 -0
  418. package/dist/workflow-sdk/index.js +78 -0
  419. package/dist/{wrap-Cr93qqdP.d.ts → wrap-C5jC6sKI.d.ts} +2 -1
  420. package/dist/{wrap-DWsU_5Bd.js → wrap-gX61EtpB.js} +7 -6
  421. package/package.json +136 -37
  422. package/schemas/access-event-v1.json +118 -0
  423. package/schemas/approval-request-v1.json +34 -1
  424. package/schemas/approval-signature-v1.json +94 -0
  425. package/schemas/automation-event-v1.json +113 -0
  426. package/schemas/capability-v1.json +84 -0
  427. package/schemas/catalog-event-v1.json +87 -0
  428. package/schemas/catalog-v1.json +123 -12
  429. package/schemas/cli-commands-v1.json +164 -0
  430. package/schemas/cli-result-v1.json +96 -0
  431. package/schemas/cloud-api-v1.json +5407 -0
  432. package/schemas/connector-v1.json +408 -0
  433. package/schemas/credential-event-v1.json +81 -0
  434. package/schemas/credential-v1.json +134 -0
  435. package/schemas/decision-event-v1.json +288 -0
  436. package/schemas/delivery-event-v1.json +117 -0
  437. package/schemas/directory-event-v1.json +85 -0
  438. package/schemas/evidence-export-v1.json +54 -0
  439. package/schemas/membership-event-v1.json +93 -0
  440. package/schemas/policy-v1.json +68 -0
  441. package/schemas/pushed-policy-v1.json +236 -0
  442. package/schemas/run-v1.json +162 -0
  443. package/schemas/snapshot-v1.json +138 -0
  444. package/schemas/supabase-claims-v1.json +4 -4
  445. package/schemas/supabase-manifest-v1.json +5 -1
  446. package/schemas/support-session-v1.json +111 -0
  447. package/skills/permdock/SKILL.md +16 -12
  448. package/skills/permdock-agents/SKILL.md +40 -23
  449. package/skills/permdock-agents/references/operations-automations-chat.md +221 -0
  450. package/skills/permdock-agents/references/runtimes.md +22 -18
  451. package/skills/permdock-approvals/SKILL.md +29 -13
  452. package/skills/permdock-approvals/references/delivery-and-waiters.md +172 -0
  453. package/skills/permdock-approvals/references/stores-and-resume.md +21 -7
  454. package/skills/permdock-audit/SKILL.md +3 -3
  455. package/skills/permdock-credentials/SKILL.md +5 -5
  456. package/skills/permdock-credentials/references/tokens-keys-links.md +14 -10
  457. package/skills/permdock-data/SKILL.md +6 -6
  458. package/skills/permdock-data/references/queries-and-rls.md +10 -9
  459. package/skills/permdock-data/references/relationships.md +3 -3
  460. package/skills/permdock-tenancy/SKILL.md +20 -7
  461. package/skills/permdock-tenancy/references/contacts-and-support-sessions.md +122 -0
  462. package/skills/permdock-tenancy/references/ownership-and-custom-roles.md +6 -4
  463. package/skills/permdock-wire/SKILL.md +17 -11
  464. package/skills/permdock-wire/references/adapters.md +139 -51
  465. package/skills/permdock-wire/references/policy-options.md +5 -5
  466. package/skills/permdock-wire/references/presets-and-recipes.md +58 -0
  467. package/dist/approvals-DZL76lKW.js +0 -240
  468. package/dist/arazzo-CD8d0WLb.js +0 -54
  469. package/dist/catalog-6yWAX6Ps.js +0 -2
  470. package/dist/catalog-WHG4kBq7.js +0 -48
  471. package/dist/cloud-C7bSDgX4.js +0 -50
  472. package/dist/cloud-PeBN3IEn.js +0 -118
  473. package/dist/collect-Bw2Jdq1q.js +0 -2
  474. package/dist/collect-CQx4hX4N.js +0 -83
  475. package/dist/commands-Cpip4ULx.js +0 -97
  476. package/dist/config-DLt6BvV7.js +0 -36
  477. package/dist/diff-Cnghh_Ip.js +0 -48
  478. package/dist/doctor-CuVJvhV_.js +0 -53
  479. package/dist/duration-xK2jGFVc.js +0 -25
  480. package/dist/errors-CWfoNg9N.js +0 -47
  481. package/dist/errors-DRnGZHzF.js +0 -30
  482. package/dist/expiry-BoBJ8Tvc.js +0 -7
  483. package/dist/fields-fwfXN01n.js +0 -1016
  484. package/dist/headers-BZSDieNT.js +0 -6
  485. package/dist/headers-Cf-zWvsO.d.ts +0 -6
  486. package/dist/ids-DC4V4CFu.js +0 -18
  487. package/dist/index-DgM7w96K.d.ts +0 -6
  488. package/dist/index-Dz7didpC.d.ts +0 -201
  489. package/dist/index-UdVQgq48.d.ts +0 -550
  490. package/dist/load-B_shHrmd.js +0 -2
  491. package/dist/manifest-BngWKryL.js +0 -770
  492. package/dist/may-use-BOn_dR_w.js +0 -78
  493. package/dist/openapi-DcQ1Kg40.js +0 -164
  494. package/dist/ownership-BgnbOJFT.js +0 -851
  495. package/dist/permdock-L26udHSk.js +0 -3646
  496. package/dist/powersync-BFSbSElQ.js +0 -62
  497. package/dist/powersync-DCDgZgfZ.js +0 -2
  498. package/dist/rls-55DQTuf5.js +0 -250
  499. package/dist/run-DMcNW_8T.js +0 -174
  500. package/dist/server-ZYEHtNHS.js +0 -268
  501. package/dist/sink-BTtmfPnE.js +0 -141
  502. package/dist/sink-CPN4zLfs.d.ts +0 -104
  503. package/dist/skills-Dle4ZoLF.js +0 -56
  504. package/dist/snapshot-Dc9CHPME.js +0 -1078
  505. package/dist/src-Bb7no-hS.js +0 -639
  506. package/dist/subject-C6dLxqHU.js +0 -24
  507. package/dist/supabase-Dbqr141s.js +0 -83
  508. package/dist/supabase-hook-BULg7Ut_.js +0 -2
  509. package/dist/thenable-QSmvt-vZ.js +0 -65
  510. package/dist/types-C0dee0Qf.d.ts +0 -197
  511. package/dist/types-DvupZE0w.d.ts +0 -9
  512. package/dist/usage-4G6t643F.js +0 -42
  513. package/dist/usage-iFLJ0-rr.js +0 -2
  514. package/dist/vocabulary-hhEFLFoL.d.ts +0 -65
  515. package/dist/watch-C8TpsAl4.js +0 -2
@@ -1,27 +1,32 @@
1
1
  import { t as compact } from "./compact-CAf557ZB.js";
2
- import { d as scopeList, o as resolveScope } from "./scopes-tvpa34ep.js";
3
- import { c as listPermissions, u as renamedKeys } from "./permission-registry-BsblpB-z.js";
4
- import { o as flattenGrantee, v as listRoles } from "./grantee-CXY-0C87.js";
5
- import { p as scopeField } from "./tenancy-DshjqY0n.js";
6
- import { a as exitCodeOf, i as describeError, n as cliErrorKind, o as usageResult } from "./errors-CWfoNg9N.js";
2
+ import { c as listPermissions, u as renamedKeys } from "./permission-registry-Br4jSNVi.js";
3
+ import { i as dateAt } from "./time-DkWjdL98.js";
4
+ import { d as scopeList, o as resolveScope } from "./scopes-CUPSjo9q.js";
5
+ import { o as flattenGrantee, v as listRoles } from "./grantee-hxrUsbpM.js";
6
+ import { p as scopeField } from "./tenancy-DN2QAx9z.js";
7
+ import { a as describeError, i as configError, o as exitCodeOfError, u as usageResult } from "./errors-CWTY6uih.js";
7
8
  import { t as policyRowConditionKeys } from "./row-conditions-h2HythXQ.js";
8
- import { c as requirePolicy } from "./load-C3_oTHyO.js";
9
+ import { c as requirePolicy } from "./load-DFS_ry0W.js";
9
10
  import "./budget-NbEtbJC_.js";
10
- import { l as sqlTextArray, n as escapeSqlIdent, r as qualifySqlTable } from "./sql-t8teWOfo.js";
11
- import { l as roleColumn } from "./sources-B1-b-R0t.js";
12
- import { i as INDEXES_MARKER, n as GRANTS_MARKER, o as SEEDS_MARKER, t as BACKFILL_MARKER } from "./markers-CiwAOZtO.js";
13
- import { n as requirePeer } from "./peer-BOr_kJE1.js";
14
- import { c as rbacScaffold, l as resolveAuthorize, o as supabaseHookSql, p as supabaseConfig, s as parseRbacAuthorize, t as grantsLabel } from "./supabase-hook-Cva3ikXz.js";
15
- import { C as parseMembershipsFlag, E as scopeTypeOf, F as quoteQualified, I as quoteTable, M as qualified, N as quoteIdent$1, O as tenantTypeOf, P as quoteLiteral, _ as subjectClaimJsonSql, a as customRolesOn, c as SESSION_LIVE_HELPER, g as sessionLiveHelperSql, r as HELPERS, t as CUSTOM_ROLES, y as subjectIdSql } from "./shared-1uhI-e-b.js";
16
- import { a as parseSplit, c as seedsMigration, i as isSeedsDirectory, l as writeOutputFiles, n as driftOf, o as partPath, s as pgDeltaPath, t as dataMigrationFile } from "./output-files-CgIsvdSa.js";
11
+ import { i as qualifySqlTable, r as escapeSqlIdent, u as sqlTextArray } from "./sql-B5T1IL83.js";
12
+ import { l as roleColumn } from "./sources-vs5dGOsS.js";
13
+ import { t as policyFromCatalog } from "./policy-from-catalog-C2gMa2Dj.js";
14
+ import { t as beforeFirst } from "./text-BMFRfBqc.js";
15
+ import { i as INDEXES_MARKER, n as GRANTS_MARKER, o as SEEDS_MARKER, t as BACKFILL_MARKER } from "./markers-C6joDI3o.js";
16
+ import { i as requirePeer, n as packageManagerOf, t as addLine } from "./peer-C8swJ2GH.js";
17
+ import { t as POLICY_NOT_PUBLISHED } from "./problem-types-CJ2325SZ.js";
18
+ import { t as fetchProjectPolicy } from "./pull-jEwbf80J.js";
19
+ import { a as isSeedsDirectory, c as parseSplit, d as seedsMigration, f as writeOutputFiles, l as partPath, n as dataMigrationFile, r as driftOf, u as pgDeltaPath } from "./output-files-DhRBBE4n.js";
20
+ import { c as approvalsSql, d as resolveAuthorize, h as schemasRefusal, l as parseRbacAuthorize, m as resolveSchemas, o as supabaseHookSql, p as isSupabaseSchemas, s as permissionHoldersSql, t as grantsLabel, u as rbacScaffold } from "./supabase-hook-BMoEhZir.js";
21
+ import { C as parseMembershipsFlag, E as scopeTypeOf, F as quoteQualified, I as quoteTable, M as qualified, N as quoteIdent$1, O as tenantTypeOf, P as quoteLiteral, _ as subjectClaimJsonSql, a as customRolesOn, c as SESSION_LIVE_HELPER, g as sessionLiveHelperSql, r as HELPERS, t as CUSTOM_ROLES, y as subjectIdSql } from "./shared-BAK8t4Uo.js";
17
22
  import { a as splitUndiffed } from "./deciding-columns-Sq12mEA6.js";
18
- import { B as memberIdsHelper, C as apiKeysPlan, D as roleNames, E as collectGrants, G as scopeSources, K as scopeTable, M as graphHelper, N as graphSqlName, T as breakGlassKey, U as permittedIdsHelper, X as connectPgPool, Y as checkSuspension, a as accessSql, o as helpersSql, r as permittedByPermissionHelper, s as seedSql, t as permissionHelpersSql, v as currentKeyExpr, w as breakGlassHolder } from "./permission-keys-B26jeyUv.js";
19
- import { n as ownershipSql, r as assignmentSql, t as ownershipRules } from "./ownership-BgnbOJFT.js";
20
- import { _ as indexTargets, a as fieldViewsSql, b as readTableIndexFacts, c as assemblePolicies, d as readOnlyActorPolicies, f as branchClauses, g as COLUMNS_SQL, h as tableFor, i as fieldViews, l as orSql, m as compileGrants, n as columnGrantSql, o as rowBranches, r as columnRevokeSql, u as readOnlyActorKinds, v as indexesSql, y as pruneIndexTargets } from "./fields-fwfXN01n.js";
21
- import { a as graphSql, c as callsHelper, i as graphPlan, l as helperCallKeys, n as runRlsVerify, o as HELPER_TABLES, r as closureDepths } from "./verify-BztUB3GX.js";
23
+ import { a as readOnlyActorKinds, c as branchClauses, d as connectPgPool, i as orSql, n as actorMayWriteSql, o as readOnlyActorPolicies, r as assemblePolicies, t as ACTOR_MAY_WRITE } from "./policies-DP2nqRoF.js";
24
+ import { A as collectGrants, C as byKey, D as apiKeysPlan, G as memberIdsHelper, I as graphHelper, J as permittedIdsHelper, L as graphSqlName, O as breakGlassHolder, S as byCodeUnit, X as scopeSources, Z as scopeTable, b as currentKeyExpr, et as checkSuspension, f as permittedByPermissionHelper, g as seedSql, h as helpersSql, i as assignmentSql, j as roleNames, k as breakGlassKey, m as accessSql, n as ownershipSql, t as ownershipRules, u as permissionHelpersSql, x as rolesTableScope } from "./ownership-DqaJNJPn.js";
25
+ import { _ as indexesSql, a as graphSql, c as callsHelper, g as indexTargets, h as COLUMNS_SQL, i as graphPlan, l as helperCallKeys, m as tableFor, n as runRlsVerify, o as HELPER_TABLES, p as compileGrants, r as closureDepths, v as pruneIndexTargets, y as readTableIndexFacts } from "./verify-BaAKx7t6.js";
26
+ import { a as fieldViewsSql, i as fieldViews, n as columnGrantSql, o as rowBranches, r as columnRevokeSql } from "./fields-OYG_JR78.js";
22
27
  import { t as supautilsRejections } from "./supautils-B6HYL9eL.js";
23
28
  import { basename, delimiter, join } from "node:path";
24
- import { spawnSync } from "node:child_process";
29
+ import { spawn } from "node:child_process";
25
30
  //#region src/cli/rls/advisors.ts
26
31
  /** The PermDock doctor check that reports the same problem in migrations. */
27
32
  const ADVISOR_CODES = {
@@ -32,19 +37,41 @@ const ADVISOR_CODES = {
32
37
  anon_security_definer_function_executable: "PD049",
33
38
  authenticated_security_definer_function_executable: "PD046"
34
39
  };
35
- const defaultExec = (command, args, options) => {
36
- const result = spawnSync(command, [...args], {
40
+ const defaultExec = (command, args, options) => new Promise((resolve) => {
41
+ const child = spawn(command, [...args], {
37
42
  cwd: options.cwd,
38
43
  env: options.env,
39
- encoding: "utf8"
44
+ stdio: [
45
+ "ignore",
46
+ "pipe",
47
+ "pipe"
48
+ ],
49
+ ...options.signal === void 0 ? {} : { signal: options.signal }
40
50
  });
41
- return {
42
- status: result.status,
43
- stdout: result.stdout ?? "",
44
- stderr: result.stderr ?? "",
45
- ...result.error === void 0 ? {} : { error: result.error }
46
- };
47
- };
51
+ let stdout = "";
52
+ let stderr = "";
53
+ child.stdout.setEncoding("utf8").on("data", (chunk) => {
54
+ stdout += chunk;
55
+ });
56
+ child.stderr.setEncoding("utf8").on("data", (chunk) => {
57
+ stderr += chunk;
58
+ });
59
+ child.once("error", (error) => {
60
+ resolve({
61
+ status: null,
62
+ stdout,
63
+ stderr,
64
+ error
65
+ });
66
+ });
67
+ child.once("close", (status) => {
68
+ resolve({
69
+ status,
70
+ stdout,
71
+ stderr
72
+ });
73
+ });
74
+ });
48
75
  function text(value, key) {
49
76
  const field = typeof value === "object" && value !== null ? Reflect.get(value, key) : void 0;
50
77
  return typeof field === "string" ? field : "";
@@ -65,16 +92,19 @@ function findingsOf(value) {
65
92
  return code === void 0 ? finding : Object.assign(finding, { code });
66
93
  });
67
94
  }
68
- const INSTALL = "rls verify --advisors runs the Supabase CLI: install it with npm install --save-dev supabase (or brew install supabase/tap/supabase)";
95
+ function installHint(agent) {
96
+ return `rls verify --advisors runs the Supabase CLI: install it with ${addLine(agent, ["supabase"], { dev: true })} (or brew install supabase/tap/supabase)`;
97
+ }
69
98
  /**
70
- * `supabase db advisors --type security` against `--db` or the local stack.
71
- * Exit 1 on a WARN or ERROR row, 2 when the CLI is missing or fails.
99
+ * `supabase db advisors --type security` against `--db-url` or the local stack,
100
+ * from the project's `node_modules/.bin` first, then `PATH`. Exit 1 on a WARN
101
+ * or ERROR row, 2 when the CLI is missing or fails.
72
102
  */
73
- function runRlsAdvisors(input) {
103
+ async function runRlsAdvisors(input) {
74
104
  const exec = input.exec ?? defaultExec;
75
105
  const bin = join(input.cwd, "node_modules", ".bin");
76
106
  const path = input.env["PATH"];
77
- const result = exec("supabase", [
107
+ const result = await exec("supabase", [
78
108
  "db",
79
109
  "advisors",
80
110
  ...input.db === void 0 ? ["--local"] : ["--db-url", input.db],
@@ -87,11 +117,12 @@ function runRlsAdvisors(input) {
87
117
  env: {
88
118
  ...input.env,
89
119
  PATH: path === void 0 ? bin : `${bin}${delimiter}${path}`
90
- }
120
+ },
121
+ ...input.signal === void 0 ? {} : { signal: input.signal }
91
122
  });
92
123
  if (result.error !== void 0) return {
93
124
  code: 2,
94
- output: INSTALL
125
+ output: installHint(await packageManagerOf(input.cwd))
95
126
  };
96
127
  let parsed;
97
128
  try {
@@ -129,6 +160,11 @@ function runRlsAdvisors(input) {
129
160
  }
130
161
  //#endregion
131
162
  //#region src/cli/rls/generate-context.ts
163
+ /** The scopes an `rls.migrate` `role` helper names, so `rls generate` writes their role-filtered member helper. */
164
+ function roleMemberScopesOf(migrate) {
165
+ const scopes = [...new Set(Object.values(migrate?.helpers ?? {}).flatMap((helper) => helper.form === "role" ? [helper.scope] : []))].toSorted();
166
+ return scopes.length === 0 ? {} : { roleMemberScopes: scopes };
167
+ }
132
168
  /** `rls.customRoleWrites.requires` as permission keys, or why it names none. */
133
169
  function writeRequirement(policy, requires) {
134
170
  if (requires === void 0) return {};
@@ -161,7 +197,7 @@ function skippedOwnershipTriggers(triggers, scopes) {
161
197
  if (triggers === false) return "all";
162
198
  return Object.keys(triggers).map((key) => {
163
199
  const name = resolveScope(scopes, key);
164
- if (name === void 0) throw new Error(`PermDock CLI: rls.ownershipTriggers.${key} names a scope the policy does not declare`);
200
+ if (name === void 0) throw configError(`PermDock CLI: rls.ownershipTriggers.${key} names a scope the policy does not declare`);
165
201
  return name;
166
202
  });
167
203
  }
@@ -195,6 +231,8 @@ function buildContext(input, policy, options) {
195
231
  dialect: input.dialect,
196
232
  scopes,
197
233
  tenantClaim: rls?.tenantClaim ?? "tenant_id",
234
+ ...roleMemberScopesOf(rls?.migrate),
235
+ ...rls?.roleValues === void 0 ? {} : { roleValues: rls.roleValues },
198
236
  ...rls?.tenants === "all" ? { tenants: "all" } : {},
199
237
  gucPrefix: input.gucPrefix ?? rls?.gucPrefix ?? "app",
200
238
  ...rls?.actions === void 0 ? {} : { actions: rls.actions },
@@ -272,7 +310,30 @@ function moveUndiffed(sql, label, part) {
272
310
  grants: `-- the privileges and view options supabase db diff drops from the ${part} part\n${moved.join("\n")}`
273
311
  };
274
312
  }
275
- /** The files `generate` writes: one, or one per `--split` part, plus the hook's grants. */
313
+ /** Why `--split` cannot be written for `mode`, or `undefined`. */
314
+ function splitRefusal(mode, plan) {
315
+ const seedsWithoutOut = plan.split.includes("seeds") && plan.seedsOut === void 0;
316
+ const helpersWithoutSeeds = plan.split.includes("helpers") && !plan.split.includes("seeds");
317
+ switch (mode) {
318
+ case "pg-delta":
319
+ if (plan.explicitOut && !plan.placeholder) return `rls generate --split needs {part} in --out, or no --out for pg-delta's per-schema layout (got ${plan.outRel})`;
320
+ if (plan.grantsOut !== void 0) return "rls generate --grants-out is for --schemas migra: pg-delta keeps the grants in each part, so drop --grants-out";
321
+ if (helpersWithoutSeeds) return "rls generate --split helpers under pg-delta needs the seeds part with --seeds-out: pg-delta rejects the role_permissions rows in a declarative file";
322
+ if (seedsWithoutOut) return "rls generate --split seeds under pg-delta needs --seeds-out: pg-delta does not diff the role_permissions rows, so they go in a migration";
323
+ return;
324
+ case "migra":
325
+ if (!plan.placeholder) return `rls generate --split under migra needs {part} in --out, for example supabase/schemas/identity/056_permdock_{part}.sql (got ${plan.outRel})`;
326
+ if (helpersWithoutSeeds) return "rls generate --split helpers under migra needs the seeds part with --seeds-out: supabase db diff does not carry the role_permissions rows from a schema file";
327
+ if (seedsWithoutOut) return "rls generate --split seeds under migra needs --seeds-out: supabase db diff does not carry the role_permissions rows, so they go in a migration";
328
+ return;
329
+ case "migrations":
330
+ if (!plan.placeholder) return `rls generate --split needs {part} in --out, for example supabase/migrations/20260101000000_permdock_{part}.sql (got ${plan.outRel})`;
331
+ if (plan.grantsOut !== void 0) return "rls generate --grants-out is for --schemas migra: a hand-written migration keeps its grants, so drop --grants-out";
332
+ return;
333
+ default: return mode;
334
+ }
335
+ }
336
+ /** The files `generate` writes: one, or one per `--split` part, plus the hook's grants under migra. */
276
337
  function outputFiles(plan) {
277
338
  const { input } = plan;
278
339
  const split = parseSplit(input.split);
@@ -288,17 +349,25 @@ function outputFiles(plan) {
288
349
  }];
289
350
  }
290
351
  if (input.target !== "sql") return "rls generate --split needs --target sql";
291
- const pgDelta = input.out === void 0 && input.config.rls?.out === void 0 ? supabaseConfig(input.cwd).pgDelta : void 0;
292
- if (pgDelta === void 0 && !plan.outRel.includes("{part}")) return `rls generate --split needs {part} in --out, for example supabase/schemas/identity/056_permdock_{part}.sql (got ${plan.outRel})`;
293
- if (pgDelta !== void 0 && split.includes("helpers") && !split.includes("seeds")) return "rls generate --split helpers under pg-delta needs the seeds part with --seeds-out: pg-delta rejects the role_permissions rows in a declarative file";
294
- if (pgDelta !== void 0 && split.includes("seeds") && input.seedsOut === void 0) return "rls generate --split seeds under pg-delta needs --seeds-out: pg-delta does not diff the role_permissions rows, so they go in a migration";
295
- const at = (part, schema = plan.schema) => pgDelta === void 0 ? partPath(plan.outRel, part) : pgDeltaPath(pgDelta.schemaDir, part, schema);
352
+ const schemas = resolveSchemas(input.cwd, input.config, input.schemas);
353
+ const pgDelta = schemas.mode === "pg-delta" ? schemas : void 0;
354
+ const placeholder = plan.outRel.includes("{part}");
355
+ const refused = splitRefusal(schemas.mode, {
356
+ split,
357
+ placeholder,
358
+ explicitOut: input.out !== void 0,
359
+ outRel: plan.outRel,
360
+ grantsOut: input.grantsOut,
361
+ seedsOut: input.seedsOut
362
+ });
363
+ if (refused !== void 0) return refused;
364
+ const at = (part, schema = plan.schema) => pgDelta === void 0 || placeholder ? partPath(plan.outRel, part) : pgDeltaPath(pgDelta.schemaDir, part, schema);
296
365
  if (input.grantsOut !== void 0 && !split.includes("hook") && !split.includes("helpers")) return "rls generate --grants-out needs the helpers or hook part in --split";
297
366
  if (input.seedsOut !== void 0 && !split.includes("seeds")) return "rls generate --seeds-out needs the seeds part in --split";
298
367
  if (split.includes("hook") && input.config.supabase?.hook === void 0) return "rls generate --split hook needs supabase.hook in permdock.config.ts";
299
368
  const sql = plan.sql();
300
369
  const relocated = pgDelta === void 0 ? void 0 : sql.servicePolicies;
301
- if (relocated !== void 0 && split.includes("policies") && !split.includes("seeds")) return "rls generate --split policies under pg-delta with rls.realtime or rls.storage needs the seeds part with --seeds-out: the shadow database has no realtime.messages or storage.objects, so their policies go in that migration";
370
+ if (relocated !== void 0 && split.includes("policies") && !split.includes("seeds")) return "rls generate --split policies under pg-delta with rls.realtime or rls.storage needs the seeds part with --seeds-out: the declarative sync shadow database has no realtime.messages or storage.objects, so their policies go in that migration";
302
371
  const seeds = relocated === void 0 || !split.includes("policies") ? sql.seeds : `${sql.seeds}\n${relocated}`;
303
372
  const files = [];
304
373
  const label = grantsLabel(input.grantsOut);
@@ -326,7 +395,7 @@ function outputFiles(plan) {
326
395
  case "seeds": {
327
396
  const out = input.seedsOut;
328
397
  if (out !== void 0 && isSeedsDirectory(input.cwd, out)) {
329
- const planned = seedsMigration(input.cwd, out, SEEDS_MARKER, seeds, input.io.now?.() ?? /* @__PURE__ */ new Date());
398
+ const planned = seedsMigration(input.cwd, out, SEEDS_MARKER, seeds, input.io.now?.() ?? dateAt());
330
399
  if (!planned.current || input.check) files.push({
331
400
  part,
332
401
  rel: planned.rel,
@@ -377,574 +446,6 @@ function outputFiles(plan) {
377
446
  return files;
378
447
  }
379
448
  //#endregion
380
- //#region src/cli/approval-schema.ts
381
- /** An RFC 3339 instant as `Temporal.Instant` prints it; pg_jsonschema asserts no `format`. */
382
- const INSTANT = String.raw`^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})$`;
383
- const STRINGS = {
384
- type: "array",
385
- items: { type: "string" }
386
- };
387
- /**
388
- * `schemas/approval-request-v1.json`: the `ApprovalRequest` an approval store
389
- * holds. Self-contained (`$defs`, no remote `$ref`) so `rls.jsonSchema` can
390
- * inline it into a pg_jsonschema check constraint.
391
- */
392
- const APPROVAL_REQUEST_SCHEMA = {
393
- $schema: "https://json-schema.org/draft/2020-12/schema",
394
- $id: "https://permdock.com/schemas/approval-request-v1.json",
395
- title: "PermDock approval request",
396
- description: "An ApprovalRequest as an ApprovalStore holds it, wire format v1. Unknown properties are allowed so a v1 minor release can add one.",
397
- type: "object",
398
- required: [
399
- "v",
400
- "token",
401
- "permission",
402
- "scope",
403
- "resource",
404
- "subject",
405
- "detail",
406
- "createdAt",
407
- "expiresAt",
408
- "status"
409
- ],
410
- properties: {
411
- v: { const: 1 },
412
- token: {
413
- type: "string",
414
- minLength: 1
415
- },
416
- permission: {
417
- type: "string",
418
- minLength: 1
419
- },
420
- scope: { type: "string" },
421
- resource: {
422
- type: "object",
423
- required: ["type"],
424
- properties: {
425
- type: { type: "string" },
426
- id: { type: "string" }
427
- }
428
- },
429
- subject: { $ref: "#/$defs/subject" },
430
- membership: {
431
- type: "object",
432
- required: ["roles"],
433
- properties: { roles: STRINGS }
434
- },
435
- approvers: { type: "object" },
436
- detail: { type: "string" },
437
- adapter: { type: "string" },
438
- createdAt: { $ref: "#/$defs/instant" },
439
- expiresAt: { $ref: "#/$defs/instant" },
440
- status: { enum: [
441
- "pending",
442
- "approved",
443
- "rejected",
444
- "expired"
445
- ] },
446
- approvals: {
447
- type: "array",
448
- items: { $ref: "#/$defs/signature" }
449
- },
450
- resolvedAt: { $ref: "#/$defs/instant" },
451
- resolvedBy: { type: "string" },
452
- vouched: { type: "string" },
453
- note: { type: "string" },
454
- consumedAt: { $ref: "#/$defs/instant" }
455
- },
456
- $defs: {
457
- instant: {
458
- type: "string",
459
- pattern: INSTANT
460
- },
461
- subject: {
462
- type: "object",
463
- required: ["principal"],
464
- properties: {
465
- principal: { anyOf: [{ type: "null" }, {
466
- type: "object",
467
- required: ["id", "roles"],
468
- properties: {
469
- id: { type: "string" },
470
- roles: STRINGS,
471
- tenant: { type: "string" }
472
- }
473
- }] },
474
- actor: {
475
- type: "object",
476
- required: ["id", "kind"],
477
- properties: {
478
- id: { type: "string" },
479
- kind: { type: "string" }
480
- }
481
- },
482
- session: { type: "string" },
483
- delegation: { type: "object" }
484
- }
485
- },
486
- signature: {
487
- type: "object",
488
- required: ["by", "at"],
489
- properties: {
490
- by: { type: "string" },
491
- at: { $ref: "#/$defs/instant" },
492
- stage: {
493
- type: "integer",
494
- minimum: 0
495
- },
496
- vouched: { type: "string" }
497
- }
498
- }
499
- }
500
- };
501
- //#endregion
502
- //#region src/cli/rls/approvals.ts
503
- /** The objects `rls.approvals` adds. Names are part of the SQL contract `supabaseApprovalStore` calls. */
504
- const APPROVAL_STORE = {
505
- table: "approval_requests",
506
- open: "permdock_approval_open",
507
- get: "permdock_approval_get",
508
- resolve: "permdock_approval_resolve",
509
- consume: "permdock_approval_consume",
510
- list: "permdock_approval_list",
511
- expire: "permdock_approval_expire",
512
- cancel: "permdock_approval_cancel"
513
- };
514
- /** `body` as the row's filter columns hold it. */
515
- const COLUMNS = `p_request ->> 'token',
516
- p_request,
517
- p_request ->> 'status',
518
- p_request #>> '{subject,principal,tenant}',
519
- p_request #>> '{subject,principal,id}',
520
- p_request #>> '{subject,actor,id}',
521
- p_request #>> '{subject,session}',
522
- coalesce(jsonb_array_length(p_request -> 'approvals'), 0),
523
- p_request ->> 'createdAt',
524
- (p_request ->> 'expiresAt')::timestamptz,
525
- (p_request ->> 'consumedAt')::timestamptz`;
526
- /** The `ApprovalListFilter` in `p_filter`, over the row aliased `a`. */
527
- const FILTER = `(p_filter ->> 'status' is null or a.status = p_filter ->> 'status')
528
- and (p_filter ->> 'tenant' is null or a.tenant = p_filter ->> 'tenant')
529
- and (p_filter ->> 'principalId' is null or a.principal_id = p_filter ->> 'principalId')
530
- and (p_filter ->> 'actorId' is null or a.actor_id = p_filter ->> 'actorId')
531
- and (p_filter ->> 'session' is null or a.session = p_filter ->> 'session')`;
532
- function serverOnly(fn, args) {
533
- return `revoke execute on function ${fn}(${args}) from public, anon, authenticated;`;
534
- }
535
- const BODY_CHECK = `${APPROVAL_STORE.table}_body_schema`;
536
- /**
537
- * `rls.jsonSchema`: a pg_jsonschema check that `body` matches
538
- * `approval-request-v1.json`, added `not valid` and then validated. `auto`
539
- * adds it only where the extension is available.
540
- */
541
- function bodySchemaSql(table, mode, body = "body", check = BODY_CHECK) {
542
- const statements = [
543
- "create schema if not exists extensions;",
544
- "create extension if not exists pg_jsonschema with schema extensions;",
545
- `alter table ${table} drop constraint if exists "${check}";`,
546
- `alter table ${table} add constraint "${check}" check (extensions.jsonb_matches_schema(${quoteLiteral(JSON.stringify(APPROVAL_REQUEST_SCHEMA))}::json, ${body})) not valid;`,
547
- `alter table ${table} validate constraint "${check}";`
548
- ];
549
- const comment = "-- approval store: body matches schemas/approval-request-v1.json through pg_jsonschema";
550
- if (mode === true) return `\n${comment}\n${statements.join("\n")}\n`;
551
- return `\n${comment}, where the extension is available
552
- do $permdock$
553
- begin
554
- if exists (select 1 from pg_available_extensions where name = 'pg_jsonschema') then
555
- ${statements.map((statement) => ` ${statement}`).join("\n")}
556
- end if;
557
- end
558
- $permdock$;\n`;
559
- }
560
- /**
561
- * The approval store (`rls.approvals`): one table of `ApprovalRequest`
562
- * bodies with the columns a list filters on, and one `security definer`
563
- * function per `ApprovalStore` method, each one atomic statement. No client
564
- * role may read the table or execute the functions: approvals are opened and
565
- * resolved by the server, which grants its own role.
566
- */
567
- function approvalStoreSql(ctx, jsonSchema = false) {
568
- const table = qualified(ctx, APPROVAL_STORE.table);
569
- const fn = (name) => qualified(ctx, name);
570
- const index = (name) => `"${APPROVAL_STORE.table}_${name}"`;
571
- return `-- approval store: ApprovalRequest bodies, opened, resolved and consumed through the functions below; supabaseApprovalStore calls them
572
- create table if not exists ${table} (
573
- token text primary key,
574
- body jsonb not null,
575
- status text not null check (status in ('pending', 'approved', 'rejected', 'expired')),
576
- tenant text,
577
- principal_id text,
578
- actor_id text,
579
- session text,
580
- approvals integer not null default 0,
581
- created_at text not null,
582
- expires_at timestamptz not null,
583
- consumed_at timestamptz
584
- );
585
- create index if not exists ${index("page")} on ${table} (created_at collate "C", token collate "C");
586
- create index if not exists ${index("pending")} on ${table} (status, expires_at);
587
- alter table ${table} enable row level security;
588
- revoke all on table ${table} from anon, authenticated, public;
589
- ${jsonSchema === false ? "" : bodySchemaSql(table, jsonSchema)}
590
- -- open a request; a repeated ask keeps the open record and replaces only an expired one
591
- create or replace function ${fn(APPROVAL_STORE.open)}(p_request jsonb)
592
- returns void
593
- language sql
594
- volatile
595
- security definer
596
- set search_path = ''
597
- as $$
598
- insert into ${table} as a (token, body, status, tenant, principal_id, actor_id, session, approvals, created_at, expires_at, consumed_at)
599
- values (
600
- ${COLUMNS}
601
- )
602
- on conflict (token) do update
603
- set body = excluded.body, status = excluded.status, tenant = excluded.tenant,
604
- principal_id = excluded.principal_id, actor_id = excluded.actor_id, session = excluded.session,
605
- approvals = excluded.approvals, created_at = excluded.created_at,
606
- expires_at = excluded.expires_at, consumed_at = excluded.consumed_at
607
- where a.status = 'expired' or a.expires_at <= now()
608
- $$;
609
- ${serverOnly(fn(APPROVAL_STORE.open), "jsonb")}
610
-
611
- create or replace function ${fn(APPROVAL_STORE.get)}(p_token text)
612
- returns jsonb
613
- language sql
614
- stable
615
- security definer
616
- set search_path = ''
617
- as $$
618
- select a.body from ${table} a where a.token = p_token
619
- $$;
620
- ${serverOnly(fn(APPROVAL_STORE.get), "text")}
621
-
622
- -- write a verdict over the record it was computed from: still pending, unexpired, with p_seen approvals
623
- create or replace function ${fn(APPROVAL_STORE.resolve)}(p_token text, p_next jsonb, p_seen integer)
624
- returns jsonb
625
- language sql
626
- volatile
627
- security definer
628
- set search_path = ''
629
- as $$
630
- update ${table} a
631
- set body = p_next,
632
- status = p_next ->> 'status',
633
- approvals = coalesce(jsonb_array_length(p_next -> 'approvals'), 0)
634
- where a.token = p_token
635
- and a.status = 'pending'
636
- and a.expires_at > now()
637
- and a.approvals = p_seen
638
- returning a.body
639
- $$;
640
- ${serverOnly(fn(APPROVAL_STORE.resolve), "text, jsonb, integer")}
641
-
642
- -- consume an approved, unexpired, unconsumed request once
643
- create or replace function ${fn(APPROVAL_STORE.consume)}(p_token text, p_now text)
644
- returns jsonb
645
- language sql
646
- volatile
647
- security definer
648
- set search_path = ''
649
- as $$
650
- update ${table} a
651
- set consumed_at = p_now::timestamptz,
652
- body = a.body || jsonb_build_object('consumedAt', p_now)
653
- where a.token = p_token
654
- and a.status = 'approved'
655
- and a.consumed_at is null
656
- and a.expires_at > p_now::timestamptz
657
- returning a.body
658
- $$;
659
- ${serverOnly(fn(APPROVAL_STORE.consume), "text, text")}
660
-
661
- -- one page in (createdAt, token) order after the cursor position
662
- create or replace function ${fn(APPROVAL_STORE.list)}(p_filter jsonb, p_after_created text, p_after_token text, p_limit integer)
663
- returns setof jsonb
664
- language sql
665
- stable
666
- security definer
667
- set search_path = ''
668
- as $$
669
- select a.body
670
- from ${table} a
671
- where ${FILTER}
672
- and (p_after_created is null
673
- or (a.created_at collate "C", a.token collate "C") > (p_after_created collate "C", p_after_token collate "C"))
674
- order by a.created_at collate "C", a.token collate "C"
675
- limit p_limit
676
- $$;
677
- ${serverOnly(fn(APPROVAL_STORE.list), "jsonb, text, text, integer")}
678
-
679
- create or replace function ${fn(APPROVAL_STORE.expire)}(p_now text)
680
- returns integer
681
- language sql
682
- volatile
683
- security definer
684
- set search_path = ''
685
- as $$
686
- with expired as (
687
- update ${table} a
688
- set status = 'expired', body = a.body || '{"status":"expired"}'::jsonb
689
- where a.status = 'pending' and a.expires_at <= p_now::timestamptz
690
- returning 1
691
- )
692
- select count(*)::integer from expired
693
- $$;
694
- ${serverOnly(fn(APPROVAL_STORE.expire), "text")}
695
-
696
- -- reject every pending request the filter matches, as system:<by>
697
- create or replace function ${fn(APPROVAL_STORE.cancel)}(p_filter jsonb, p_by text, p_note text, p_now text)
698
- returns integer
699
- language sql
700
- volatile
701
- security definer
702
- set search_path = ''
703
- as $$
704
- with cancelled as (
705
- update ${table} a
706
- set status = 'rejected',
707
- body = (a.body - 'note') || jsonb_strip_nulls(jsonb_build_object(
708
- 'status', 'rejected', 'resolvedAt', p_now, 'resolvedBy', 'system:' || p_by, 'note', p_note
709
- ))
710
- where a.status = 'pending'
711
- and ${FILTER.replaceAll("\n ", "\n ")}
712
- returning 1
713
- )
714
- select count(*)::integer from cancelled
715
- $$;
716
- ${serverOnly(fn(APPROVAL_STORE.cancel), "jsonb, text, text, text")}
717
- `;
718
- }
719
- /** The request fields an adopted table may mirror into its own columns, as SQL over a body. */
720
- const MIRRORS = {
721
- status: (body) => `${body} ->> 'status'`,
722
- permission: (body) => `${body} ->> 'permission'`,
723
- tenant: (body) => `${body} #>> '{subject,principal,tenant}'`,
724
- principalId: (body) => `${body} #>> '{subject,principal,id}'`,
725
- actorId: (body) => `${body} #>> '{subject,actor,id}'`,
726
- session: (body) => `${body} #>> '{subject,session}'`,
727
- approvals: (body) => `coalesce(jsonb_array_length(${body} -> 'approvals'), 0)`,
728
- createdAt: (body) => `${body} ->> 'createdAt'`,
729
- expiresAt: (body) => `${body} ->> 'expiresAt'`,
730
- resolvedAt: (body) => `${body} ->> 'resolvedAt'`,
731
- resolvedBy: (body) => `${body} ->> 'resolvedBy'`,
732
- consumedAt: (body) => `${body} ->> 'consumedAt'`
733
- };
734
- function mirrorsOf(adopt) {
735
- const out = [];
736
- for (const [field, column] of Object.entries(adopt.mirror ?? {})) {
737
- if (!Object.hasOwn(MIRRORS, field)) throw new Error(`PermDock CLI: rls.approvals.mirror.${field} is not a request field; use ${Object.keys(MIRRORS).join(", ")}`);
738
- if (typeof column !== "string") throw new TypeError(`PermDock CLI: rls.approvals.mirror.${field} must be a column name`);
739
- out.push([column, field]);
740
- }
741
- return out;
742
- }
743
- /** One row of `table` from `values` (column, SQL), each converted to the column's own type. */
744
- function populate(table, values) {
745
- return `pg_catalog.jsonb_populate_record(null::${table}, pg_catalog.jsonb_build_object(${values.map(([column, sql]) => `${quoteLiteral(column)}, ${sql}`).join(", ")}))`;
746
- }
747
- /** The mirrored columns' values for the request `body`. */
748
- function mirrorRow(table, mirror, body) {
749
- return populate(table, mirror.map(([column, field]) => [column, MIRRORS[field](body)]));
750
- }
751
- /** `, (<mirrored columns>) = (select …)` for an update that writes `body`; empty without mirrors. */
752
- function mirrorSet(table, mirror, body) {
753
- if (mirror.length === 0) return "";
754
- const columns = mirror.map(([column]) => quoteIdent$1(column));
755
- const picked = columns.map((column) => `r.${column}`).join(", ");
756
- return `,\n ${columns.length === 1 ? columns[0] ?? "" : `(${columns.join(", ")})`} = (select ${picked} from ${mirrorRow(table, mirror, body)} r)`;
757
- }
758
- /**
759
- * The approval store over a table the app already has (`rls.approvals:
760
- * { table, token?, body?, mirror? }`): the same `permdock_approval_*`
761
- * functions, reading every field from the request body in `body`, keyed by
762
- * the text column `token`, and copying the fields `mirror` names into the
763
- * app's own columns, converted to their types, on every write. The table's
764
- * grants and policies stay the app's.
765
- */
766
- function adoptedApprovalStoreSql(ctx, adopt, jsonSchema = false) {
767
- const table = quoteQualified(adopt.table);
768
- const base = adopt.table.split(".").at(-1) ?? adopt.table;
769
- const token = quoteIdent$1(adopt.token ?? "token");
770
- const body = quoteIdent$1(adopt.body ?? "body");
771
- const mirror = mirrorsOf(adopt);
772
- for (const [column] of mirror) if (column === (adopt.token ?? "token") || column === (adopt.body ?? "body")) throw new Error(`PermDock CLI: rls.approvals.mirror names the ${column} column, which already holds the token or the body`);
773
- const open = adopt.open ?? "insert";
774
- if (open !== "insert" && open !== "attach") throw new Error(`PermDock CLI: rls.approvals.open must be 'insert' or 'attach', got '${String(open)}'`);
775
- const schema = adopt.schema;
776
- if (schema !== void 0 && (typeof schema !== "string" || schema === "")) throw new Error("PermDock CLI: rls.approvals.schema must be a schema name");
777
- const fn = (name) => schema === void 0 ? qualified(ctx, name) : `${quoteIdent$1(schema)}.${name}`;
778
- const index = (name) => quoteIdent$1(`${base}_permdock_${name}`);
779
- const status = `a.${body} ->> 'status'`;
780
- const expires = `(a.${body} ->> 'expiresAt')::timestamptz`;
781
- const created = `(a.${body} ->> 'createdAt')`;
782
- const filter = `(p_filter ->> 'status' is null or ${status} = p_filter ->> 'status')
783
- and (p_filter ->> 'tenant' is null or a.${body} #>> '{subject,principal,tenant}' = p_filter ->> 'tenant')
784
- and (p_filter ->> 'principalId' is null or a.${body} #>> '{subject,principal,id}' = p_filter ->> 'principalId')
785
- and (p_filter ->> 'actorId' is null or a.${body} #>> '{subject,actor,id}' = p_filter ->> 'actorId')
786
- and (p_filter ->> 'session' is null or a.${body} #>> '{subject,session}' = p_filter ->> 'session')`;
787
- const columns = [
788
- token,
789
- body,
790
- ...mirror.map(([column]) => quoteIdent$1(column))
791
- ];
792
- const inserted = populate(table, [
793
- [adopt.token ?? "token", "p_request ->> 'token'"],
794
- [adopt.body ?? "body", "p_request"],
795
- ...mirror.map(([column, field]) => [column, MIRRORS[field]("p_request")])
796
- ]);
797
- const insertSql = `-- open a request; a repeated ask keeps the open record and replaces only an expired one
798
- create or replace function ${fn(APPROVAL_STORE.open)}(p_request jsonb)
799
- returns void
800
- language sql
801
- volatile
802
- security definer
803
- set search_path = ''
804
- as $$
805
- insert into ${table} as a (${columns.join(", ")})
806
- select ${columns.map((column) => `r.${column}`).join(", ")}
807
- from ${inserted} r
808
- on conflict (${token}) do update
809
- set ${columns.filter((column) => column !== token).map((column) => `${column} = excluded.${column}`).join(", ")}
810
- where ${status} = 'expired' or ${expires} <= now()
811
- $$;`;
812
- const attachSql = `create or replace function ${fn(APPROVAL_STORE.open)}(p_request jsonb)
813
- returns void
814
- language plpgsql
815
- volatile
816
- security definer
817
- set search_path = ''
818
- as $$
819
- begin
820
- update ${table} a
821
- set ${body} = p_request${mirrorSet(table, mirror, "p_request")}
822
- where a.${token} = p_request ->> 'token'
823
- and (a.${body} is null or ${status} = 'expired' or ${expires} <= now());
824
- if not found and not exists (select 1 from ${table} a where a.${token} = p_request ->> 'token') then
825
- raise exception using
826
- errcode = 'P0002',
827
- message = 'permdock: no row of ${adopt.table.replaceAll("'", "''")} holds approval token ' || (p_request ->> 'token'),
828
- hint = 'insert the application row with this token before opening the request';
829
- end if;
830
- end;
831
- $$;`;
832
- return `-- approval store adopted onto ${adopt.table}: ApprovalRequest bodies in ${adopt.body ?? "body"}, keyed by ${adopt.token ?? "token"}; supabaseApprovalStore calls the functions below
833
- alter table ${table} add column if not exists ${token} text;
834
- alter table ${table} add column if not exists ${body} jsonb;
835
- create unique index if not exists ${index("token")} on ${table} (${token});
836
- create index if not exists ${index("page")} on ${table} ((${body} ->> 'createdAt') collate "C", ${token} collate "C");
837
- create index if not exists ${index("status")} on ${table} ((${body} ->> 'status'));
838
- ${jsonSchema === false ? "" : bodySchemaSql(table, jsonSchema, body, `${base}_permdock_body_schema`)}
839
- ${open === "attach" ? attachSql : insertSql}
840
- ${serverOnly(fn(APPROVAL_STORE.open), "jsonb")}
841
-
842
- create or replace function ${fn(APPROVAL_STORE.get)}(p_token text)
843
- returns jsonb
844
- language sql
845
- stable
846
- security definer
847
- set search_path = ''
848
- as $$
849
- select a.${body} from ${table} a where a.${token} = p_token
850
- $$;
851
- ${serverOnly(fn(APPROVAL_STORE.get), "text")}
852
-
853
- -- write a verdict over the record it was computed from: still pending, unexpired, with p_seen approvals
854
- create or replace function ${fn(APPROVAL_STORE.resolve)}(p_token text, p_next jsonb, p_seen integer)
855
- returns jsonb
856
- language sql
857
- volatile
858
- security definer
859
- set search_path = ''
860
- as $$
861
- update ${table} a
862
- set ${body} = p_next${mirrorSet(table, mirror, "p_next")}
863
- where a.${token} = p_token
864
- and ${status} = 'pending'
865
- and ${expires} > now()
866
- and coalesce(jsonb_array_length(a.${body} -> 'approvals'), 0) = p_seen
867
- returning a.${body}
868
- $$;
869
- ${serverOnly(fn(APPROVAL_STORE.resolve), "text, jsonb, integer")}
870
-
871
- -- consume an approved, unexpired, unconsumed request once
872
- create or replace function ${fn(APPROVAL_STORE.consume)}(p_token text, p_now text)
873
- returns jsonb
874
- language sql
875
- volatile
876
- security definer
877
- set search_path = ''
878
- as $$
879
- update ${table} a
880
- set ${body} = a.${body} || jsonb_build_object('consumedAt', p_now)${mirrorSet(table, mirror, `(a.${body} || jsonb_build_object('consumedAt', p_now))`)}
881
- where a.${token} = p_token
882
- and ${status} = 'approved'
883
- and a.${body} ->> 'consumedAt' is null
884
- and ${expires} > p_now::timestamptz
885
- returning a.${body}
886
- $$;
887
- ${serverOnly(fn(APPROVAL_STORE.consume), "text, text")}
888
-
889
- -- one page in (createdAt, token) order after the cursor position
890
- create or replace function ${fn(APPROVAL_STORE.list)}(p_filter jsonb, p_after_created text, p_after_token text, p_limit integer)
891
- returns setof jsonb
892
- language sql
893
- stable
894
- security definer
895
- set search_path = ''
896
- as $$
897
- select a.${body}
898
- from ${table} a
899
- where a.${body} is not null
900
- and ${filter}
901
- and (p_after_created is null
902
- or (${created} collate "C", a.${token} collate "C") > (p_after_created collate "C", p_after_token collate "C"))
903
- order by ${created} collate "C", a.${token} collate "C"
904
- limit p_limit
905
- $$;
906
- ${serverOnly(fn(APPROVAL_STORE.list), "jsonb, text, text, integer")}
907
-
908
- create or replace function ${fn(APPROVAL_STORE.expire)}(p_now text)
909
- returns integer
910
- language sql
911
- volatile
912
- security definer
913
- set search_path = ''
914
- as $$
915
- with expired as (
916
- update ${table} a
917
- set ${body} = a.${body} || '{"status":"expired"}'::jsonb${mirrorSet(table, mirror, `(a.${body} || '{"status":"expired"}'::jsonb)`)}
918
- where ${status} = 'pending' and ${expires} <= p_now::timestamptz
919
- returning 1
920
- )
921
- select count(*)::integer from expired
922
- $$;
923
- ${serverOnly(fn(APPROVAL_STORE.expire), "text")}
924
-
925
- -- reject every pending request the filter matches, as system:<by>
926
- create or replace function ${fn(APPROVAL_STORE.cancel)}(p_filter jsonb, p_by text, p_note text, p_now text)
927
- returns integer
928
- language sql
929
- volatile
930
- security definer
931
- set search_path = ''
932
- as $$
933
- with cancelled as (
934
- update ${table} a
935
- set ${body} = (a.${body} - 'note') || jsonb_strip_nulls(jsonb_build_object(
936
- 'status', 'rejected', 'resolvedAt', p_now, 'resolvedBy', 'system:' || p_by, 'note', p_note
937
- ))${mirrorSet(table, mirror, `((a.${body} - 'note') || jsonb_strip_nulls(jsonb_build_object('status', 'rejected', 'resolvedAt', p_now, 'resolvedBy', 'system:' || p_by, 'note', p_note)))`)}
938
- where ${status} = 'pending'
939
- and ${filter.replaceAll("\n ", "\n ")}
940
- returning 1
941
- )
942
- select count(*)::integer from cancelled
943
- $$;
944
- ${serverOnly(fn(APPROVAL_STORE.cancel), "jsonb, text, text, text")}
945
- `;
946
- }
947
- //#endregion
948
449
  //#region src/cli/rls/backfill.ts
949
450
  const PREFIX$1 = "PermDock CLI";
950
451
  /** A column of the roles-table row `r`. */
@@ -952,16 +453,16 @@ function col(name) {
952
453
  return `r.${quoteIdent$1(name)}`;
953
454
  }
954
455
  function schemaOf(table) {
955
- return table.includes(".") ? table.split(".")[0] ?? "public" : "public";
456
+ return table.includes(".") ? beforeFirst(table, ".") : "public";
956
457
  }
957
458
  /** The roles-table column a reference joins on, after checking it points at `roles`. */
958
459
  function roleRef(role, table, roles, label) {
959
460
  const pairs = Object.entries(role.on);
960
461
  const [pair] = pairs;
961
- if (pair === void 0 || pairs.length !== 1) throw new Error(`${PREFIX$1}: ${label}.on must map exactly one column of ${table} to ${role.through}, for example { role_id: 'id' }`);
462
+ if (pair === void 0 || pairs.length !== 1) throw configError(`${PREFIX$1}: ${label}.on must map exactly one column of ${table} to ${role.through}, for example { role_id: 'id' }`);
962
463
  const through = role.through.includes(".") ? role.through : `${schemaOf(table)}.${role.through}`;
963
- if (qualifySqlTable(through) !== qualifySqlTable(roles.table)) throw new Error(`${PREFIX$1}: ${label}.through must be rls.customRoleWrites.roles.table (${roles.table}), got ${role.through}`);
964
- if (role.column !== roles.key) throw new Error(`${PREFIX$1}: ${label}.column must be rls.customRoleWrites.roles.key (${roles.key}), got ${role.column}`);
464
+ if (qualifySqlTable(through) !== qualifySqlTable(roles.table)) throw configError(`${PREFIX$1}: ${label}.through must be rls.customRoleWrites.roles.table (${roles.table}), got ${role.through}`);
465
+ if (role.column !== roles.key) throw configError(`${PREFIX$1}: ${label}.column must be rls.customRoleWrites.roles.key (${roles.key}), got ${role.column}`);
965
466
  const [ref, id] = pair;
966
467
  return {
967
468
  ref,
@@ -976,10 +477,10 @@ function roleRef(role, table, roles, label) {
976
477
  function legacyKeySql(expr, migrate, renamed) {
977
478
  const exact = new Map(Object.entries(renamed));
978
479
  for (const [from, to] of Object.entries(migrate?.keys ?? {})) exact.set(from, to);
979
- const prefixes = Object.entries(migrate?.prefixes ?? {}).toSorted(([a], [b]) => b.length - a.length || (a < b ? -1 : a > b ? 1 : 0));
480
+ const prefixes = Object.entries(migrate?.prefixes ?? {}).toSorted(([a], [b]) => b.length - a.length || byCodeUnit(a, b));
980
481
  const prefixed = prefixes.length === 0 ? expr : `(case ${prefixes.map(([from, to]) => `when left(${expr}, ${String(from.length)}) = ${quoteLiteral(from)} then ${quoteLiteral(to)} || substr(${expr}, ${String(from.length + 1)})`).join(" ")} else ${expr} end)`;
981
- const mapped = exact.size === 0 ? prefixed : `(case ${expr} ${[...exact.entries()].toSorted(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([from, to]) => `when ${quoteLiteral(from)} then ${quoteLiteral(to)}`).join(" ")} else ${prefixed} end)`;
982
- const renames = Object.entries(renamed).toSorted(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
482
+ const mapped = exact.size === 0 ? prefixed : `(case ${expr} ${[...exact.entries()].toSorted(byKey).map(([from, to]) => `when ${quoteLiteral(from)} then ${quoteLiteral(to)}`).join(" ")} else ${prefixed} end)`;
483
+ const renames = Object.entries(renamed).toSorted(byKey);
983
484
  return currentKeyExpr(renames, mapped);
984
485
  }
985
486
  function sourceSql(source, table, label) {
@@ -999,20 +500,20 @@ function sourceSql(source, table, label) {
999
500
  function backfillSql(ctx, from, migrate) {
1000
501
  const custom = ctx.customRoles;
1001
502
  const root = ctx.scopes[0]?.name;
1002
- if (custom === void 0 || root === void 0) throw new Error(`${PREFIX$1}: rls.customRoles.from needs custom roles on`);
1003
- if (ctx.authorize !== "database") throw new Error(`${PREFIX$1}: rls.customRoles.from needs database mode; in jwt mode custom roles ride the token, so build the grants claim with customRoleClaim`);
503
+ if (custom === void 0 || root === void 0) throw configError(`${PREFIX$1}: rls.customRoles.from needs custom roles on`);
504
+ if (ctx.authorize !== "database") throw configError(`${PREFIX$1}: rls.customRoles.from needs database mode; in jwt mode custom roles ride the token, so build the grants claim with customRoleClaim`);
1004
505
  const roles = custom.table;
1005
- if (roles === void 0) throw new Error(`${PREFIX$1}: rls.customRoles.from needs rls.customRoleWrites.roles, the roles table each row belongs to`);
506
+ if (roles === void 0) throw configError(`${PREFIX$1}: rls.customRoles.from needs rls.customRoleWrites.roles, the roles table each row belongs to`);
1006
507
  const leveled = custom.levels === true;
1007
- if (from.permissions.level !== void 0 && !leveled) throw new Error(`${PREFIX$1}: rls.customRoles.from.permissions.level is set, but no resource declares levels`);
508
+ if (from.permissions.level !== void 0 && !leveled) throw configError(`${PREFIX$1}: rls.customRoles.from.permissions.level is set, but no resource declares levels`);
1008
509
  const tenantType = tenantTypeOf(ctx);
1009
510
  const rolesTable = quoteQualified(roles.table);
1010
511
  const tenant = roles.tenant === void 0 ? `null::${tenantType}` : `${col(roles.tenant)}::${tenantType}`;
1011
- const scope = roles.scope === void 0 ? `case when ${tenant} is null then 'global' else ${quoteLiteral(root)} end` : `case when ${tenant} is null then 'global' else ${col(roles.scope)}::text end`;
512
+ const scope = rolesTableScope(roles, tenant, root, col);
1012
513
  const scopeId = roles.id === void 0 ? "null::text" : `${col(roles.id)}::text`;
1013
514
  const skipped = roles.skip === void 0 ? "false" : `coalesce(${col(roles.skip)}, false)`;
1014
515
  const renamed = custom.renamed ?? {};
1015
- const renames = Object.entries(renamed).toSorted(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
516
+ const renames = Object.entries(renamed).toSorted(byKey);
1016
517
  const current = (expr) => currentKeyExpr(renames, expr);
1017
518
  const known = sqlTextArray([...custom.permissions ?? [], ...renames.map(([former]) => former)]);
1018
519
  const declared = sqlTextArray(custom.declared);
@@ -1195,7 +696,7 @@ function rowsFilter(ctx, entry) {
1195
696
  return arms.length === 0 ? "false" : [...new Set(arms)].join("\n or ");
1196
697
  }
1197
698
  function functionName$1(resource) {
1198
- if (!IDENT$1.test(resource)) throw new Error(`PermDock CLI: unsafe break-glass resource '${resource}'`);
699
+ if (!IDENT$1.test(resource)) throw configError(`PermDock CLI: unsafe break-glass resource '${resource}'`);
1199
700
  return `permdock_break_glass_${resource}`;
1200
701
  }
1201
702
  function auditSql$1(ctx) {
@@ -1234,8 +735,8 @@ begin
1234
735
  or session ->> 'v' <> '1'
1235
736
  or session ->> 'permission' <> p_permission
1236
737
  or coalesce(session ->> 'reason', '') = ''
1237
- or jsonb_typeof(session -> 'expiresAt') <> 'number'
1238
- or (session ->> 'expiresAt')::numeric <= extract(epoch from now())
738
+ or jsonb_typeof(session -> 'expires_at') <> 'number'
739
+ or (session ->> 'expires_at')::numeric <= extract(epoch from now())
1239
740
  then
1240
741
  raise exception 'PermDock: no valid break-glass session for %', p_permission
1241
742
  using errcode = '42501';
@@ -1367,7 +868,7 @@ function pascalCase(name) {
1367
868
  }
1368
869
  const IDENTIFIER = /^[A-Za-z_$][\w$]*$/;
1369
870
  function identifier(name, what) {
1370
- if (!IDENTIFIER.test(name)) throw new Error(`PermDock CLI: ${what} ${JSON.stringify(name)} is not an identifier`);
871
+ if (!IDENTIFIER.test(name)) throw configError(`PermDock CLI: ${what} ${JSON.stringify(name)} is not an identifier`);
1371
872
  return name;
1372
873
  }
1373
874
  function quoted(text) {
@@ -1431,7 +932,7 @@ function emitPrisma(policies, options = {}) {
1431
932
  ""
1432
933
  ];
1433
934
  for (const item of policies) {
1434
- if (item.effect === "deny") throw new Error(`PermDock CLI: Prisma policy blocks are permissive only, so ${item.name} (a deny) needs --target sql`);
935
+ if (item.effect === "deny") throw configError(`PermDock CLI: Prisma policy blocks are permissive only, so ${item.name} (a deny) needs --target sql`);
1435
936
  lines.push(`policy_${item.command} ${identifier(item.name, "policy name")} {`, ` target = ${modelOf(item.table)}`, ` roles = [${item.roles.join(", ")}]`);
1436
937
  if (item.using !== void 0) lines.push(` using = ${JSON.stringify(item.using)}`);
1437
938
  if (item.check !== void 0) lines.push(` withCheck = ${JSON.stringify(item.check)}`);
@@ -1461,18 +962,18 @@ const LITERAL_SEGMENT = /^[^{}:\s]+$/u;
1461
962
  const TOPIC = "(select realtime.topic())";
1462
963
  function policyName(prefix, name, command) {
1463
964
  const full = `permdock_${prefix}_${name.replaceAll(/[^A-Za-z0-9]+/gu, "_").replaceAll(/^_|_$/gu, "")}_${command}`;
1464
- if (new TextEncoder().encode(full).length > 63) throw new Error(`PermDock CLI: the ${prefix} policy name ${full} is longer than Postgres's 63 bytes; shorten '${name}'`);
965
+ if (new TextEncoder().encode(full).length > 63) throw configError(`PermDock CLI: the ${prefix} policy name ${full} is longer than Postgres's 63 bytes; shorten '${name}'`);
1465
966
  return full;
1466
967
  }
1467
968
  function grantKey(facts, ref, where, warnings) {
1468
969
  if (ref === void 0) return;
1469
970
  const key = typeof ref === "object" && ref !== null ? Reflect.get(ref, "key") : ref;
1470
- if (typeof key !== "string" || !facts.keys.has(key)) throw new Error(`PermDock CLI: ${where} must be a permission reference from the definitions (got ${JSON.stringify(key)})`);
971
+ if (typeof key !== "string" || !facts.keys.has(key)) throw configError(`PermDock CLI: ${where} must be a permission reference from the definitions (got ${JSON.stringify(key)})`);
1471
972
  if (facts.rowConditions.has(key)) warnings.push(`${where} is ${key}, whose grants carry row conditions (a relation, a where or another condition): the policy admits only the instances its role allows without a condition reach, through permitted_<scope>_ids_by_permission`);
1472
973
  return key;
1473
974
  }
1474
975
  function declaredScope(facts, scope, where) {
1475
- if (!facts.scopes.has(scope)) throw new Error(`PermDock CLI: ${where} names the scope '${scope}', which definePolicy({ scopes }) does not declare`);
976
+ if (!facts.scopes.has(scope)) throw configError(`PermDock CLI: ${where} names the scope '${scope}', which definePolicy({ scopes }) does not declare`);
1476
977
  return scope;
1477
978
  }
1478
979
  /**
@@ -1498,7 +999,7 @@ function realtimePolicies(ctx, facts, realtime, warnings) {
1498
999
  }];
1499
1000
  });
1500
1001
  const [placeholder] = placeholders;
1501
- if (placeholder === void 0 || placeholders.length > 1 || segments.some((segment, index) => index !== placeholder.index && !LITERAL_SEGMENT.test(segment))) throw new Error(`PermDock CLI: ${where} must be ':'-separated segments with exactly one '{<scope>}' segment, such as 'org:{organization}:chat'`);
1002
+ if (placeholder === void 0 || placeholders.length > 1 || segments.some((segment, index) => index !== placeholder.index && !LITERAL_SEGMENT.test(segment))) throw configError(`PermDock CLI: ${where} must be ':'-separated segments with exactly one '{<scope>}' segment, such as 'org:{organization}:chat'`);
1502
1003
  const scope = declaredScope(facts, placeholder.scope, where);
1503
1004
  const regex = `^${segments.map((segment, index) => index === placeholder.index ? "[^:]+" : escapeRegex(segment)).join(":")}$`;
1504
1005
  const id = `split_part(${TOPIC}, ':', ${String(placeholder.index + 1)})`;
@@ -1507,7 +1008,7 @@ function realtimePolicies(ctx, facts, realtime, warnings) {
1507
1008
  and ${permitted(ctx, id, scope, key)}`;
1508
1009
  const read = grantKey(facts, topic.read, `${where}.read`, warnings);
1509
1010
  const write = grantKey(facts, topic.write, `${where}.write`, warnings);
1510
- if (read === void 0) throw new Error(`PermDock CLI: ${where}.read is required`);
1011
+ if (read === void 0) throw configError(`PermDock CLI: ${where}.read is required`);
1511
1012
  const policies = [{
1512
1013
  name: policyName("realtime", pattern, "select"),
1513
1014
  table: "realtime.messages",
@@ -1529,7 +1030,7 @@ function realtimePolicies(ctx, facts, realtime, warnings) {
1529
1030
  }
1530
1031
  function folderOf(bucket, where) {
1531
1032
  const folder = bucket.folder ?? 1;
1532
- if (!Number.isInteger(folder) || folder < 1) throw new Error(`PermDock CLI: ${where}.folder must be a positive integer (got ${String(folder)})`);
1033
+ if (!Number.isInteger(folder) || folder < 1) throw configError(`PermDock CLI: ${where}.folder must be a positive integer (got ${String(folder)})`);
1533
1034
  return folder;
1534
1035
  }
1535
1036
  function storagePolicies(ctx, facts, storage, warnings) {
@@ -1574,7 +1075,7 @@ function storagePolicies(ctx, facts, storage, warnings) {
1574
1075
  ...command === "select" || command === "delete" ? {} : { check: expression }
1575
1076
  }];
1576
1077
  });
1577
- if (policies.length === 0) throw new Error(`PermDock CLI: ${where} needs read, write or delete`);
1078
+ if (policies.length === 0) throw configError(`PermDock CLI: ${where} needs read, write or delete`);
1578
1079
  return policies;
1579
1080
  });
1580
1081
  }
@@ -1588,11 +1089,11 @@ function storagePolicies(ctx, facts, storage, warnings) {
1588
1089
  */
1589
1090
  function realtimeStoragePolicies(ctx, facts, config, warnings = []) {
1590
1091
  if (config.realtime === void 0 && config.storage === void 0) return [];
1591
- if (ctx.dialect !== "supabase") throw new Error(`PermDock CLI: rls.realtime and rls.storage need --dialect supabase (got ${ctx.dialect})`);
1092
+ if (ctx.dialect !== "supabase") throw configError(`PermDock CLI: rls.realtime and rls.storage need --dialect supabase (got ${ctx.dialect})`);
1592
1093
  const policies = [...config.realtime === void 0 ? [] : realtimePolicies(ctx, facts, config.realtime, warnings), ...config.storage === void 0 ? [] : storagePolicies(ctx, facts, config.storage, warnings)];
1593
1094
  const seen = /* @__PURE__ */ new Set();
1594
1095
  for (const { name } of policies) {
1595
- if (seen.has(name)) throw new Error(`PermDock CLI: two rls.realtime or rls.storage entries both name the policy ${name}; rename one`);
1096
+ if (seen.has(name)) throw configError(`PermDock CLI: two rls.realtime or rls.storage entries both name the policy ${name}; rename one`);
1596
1097
  seen.add(name);
1597
1098
  }
1598
1099
  return policies;
@@ -1603,7 +1104,7 @@ function realtimeStoragePolicies(ctx, facts, config, warnings = []) {
1603
1104
  function rowsHelper(resource) {
1604
1105
  const name = graphSqlName(resource);
1605
1106
  const helper = `permitted_${name ?? resource}_rows`;
1606
- if (name === void 0 || new TextEncoder().encode(`${helper}_for`).length > 63) throw new Error(`PermDock CLI: rls.rowHelpers cannot name a helper for resource '${resource}'; use a SQL name of at most 45 bytes`);
1107
+ if (name === void 0 || new TextEncoder().encode(`${helper}_for`).length > 63) throw configError(`PermDock CLI: rls.rowHelpers cannot name a helper for resource '${resource}'; use a SQL name of at most 45 bytes`);
1607
1108
  return helper;
1608
1109
  }
1609
1110
  /** The branches a row check of a permission reads: its allows and denies on rows that exist, for signed-in callers. */
@@ -1750,7 +1251,7 @@ function rowHelpersSql(ctx, policy, branches, select, tables, warnings) {
1750
1251
  const chunks = called.map((name) => stubSql(ctx, name));
1751
1252
  for (const name of names) {
1752
1253
  const node = policy.resources.get(name);
1753
- if (node === void 0) throw new Error(`PermDock CLI: rls.rowHelpers names '${name}', which the policy does not declare`);
1254
+ if (node === void 0) throw configError(`PermDock CLI: rls.rowHelpers names '${name}', which the policy does not declare`);
1754
1255
  chunks.push(resourceSql(ctx, node, rowBranchesOf(branches, name), tables));
1755
1256
  }
1756
1257
  if (chunks.length > 0 && ctx.dialect === "neon") warnings.push("rls.rowHelpers: the neon dialect reads the subject from auth.session(), which SQL cannot set, so no permitted_<resource>_rows_for is written");
@@ -1807,7 +1308,7 @@ function generatePolicies(input, policy, options, built, warnings) {
1807
1308
  ...policyName === void 0 ? {} : { name: policyName }
1808
1309
  });
1809
1310
  const actorKinds = readOnlyActorKinds(rls?.readOnlyActors);
1810
- if (actorKinds !== void 0 && helpersOnly) warnings.push("rls.readOnlyActors adds policies, and --helpers-only writes none: add the restrictive read-only policies to the hand-written ones");
1311
+ if (actorKinds !== void 0 && helpersOnly) warnings.push(`rls.readOnlyActors with --helpers-only writes no policies: add \`and (select ${qualified(ctx, ACTOR_MAY_WRITE)}())\` to each hand-written insert, update and delete policy, or a restrictive policy that calls it`);
1811
1312
  const withActors = (items) => actorKinds === void 0 ? items : [...items, ...readOnlyActorPolicies(items, subjectClaimJsonSql(ctx, "act"), actorKinds)];
1812
1313
  let tablePolicies;
1813
1314
  try {
@@ -1856,7 +1357,7 @@ function mappedKey(config, renamed) {
1856
1357
  return `coalesce(${quoteLiteral(JSON.stringify(exact))}::jsonb ->> p_key, ${fallback})`;
1857
1358
  }
1858
1359
  function scopeOf(ctx, scope) {
1859
- if (!ctx.scopes.some((item) => item.name === scope)) throw new Error(`PermDock CLI: rls.migrate helper scope '${scope}' is not a declared scope`);
1360
+ if (!ctx.scopes.some((item) => item.name === scope)) throw configError(`PermDock CLI: rls.migrate helper scope '${scope}' is not a declared scope`);
1860
1361
  return scope;
1861
1362
  }
1862
1363
  function addKey(list, key) {
@@ -1953,6 +1454,13 @@ function shim(ctx, config, keys, fn, helper) {
1953
1454
  const type = scopeTypeOf(ctx, scope);
1954
1455
  return shimFunction(fn, `p_id ${type}`, type, "boolean", ` select coalesce(p_id in (select ${qualified(ctx, memberIdsHelper(scope))}()), false)`);
1955
1456
  }
1457
+ case "role": {
1458
+ const scope = scopeOf(ctx, helper.scope);
1459
+ const type = scopeTypeOf(ctx, scope);
1460
+ const values = Object.entries(ctx.roleValues ?? {});
1461
+ const role = values.length === 0 ? "p_role" : `case p_role ${values.map(([from, to]) => `when ${quoteLiteral(from)} then ${quoteLiteral(to)}`).join(" ")} else p_role end`;
1462
+ return shimFunction(fn, `p_id ${type}, p_role text`, `${type}, text`, "boolean", ` select coalesce(p_id in (select ${qualified(ctx, memberIdsHelper(scope))}(array[${role}])), false)`);
1463
+ }
1956
1464
  case "global": return shimFunction(fn, "p_key text", "text", "boolean", ` select ${hasSql(ctx, keys)}`);
1957
1465
  case "scoped": {
1958
1466
  const literals = /* @__PURE__ */ new Map();
@@ -1980,8 +1488,8 @@ function shimsSql(ctx, config, shims, renamed, grants) {
1980
1488
  map: (scope) => `${quoteLiteral(shimMap(grants, scope))}::jsonb`
1981
1489
  };
1982
1490
  const chunks = [`-- permdock shims: legacy helper names over the permdock helpers; drop each once doctor reports no caller (PD056)`];
1983
- for (const [name, helper] of Object.entries(config.helpers).toSorted(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)) {
1984
- if (!NAME.test(name)) throw new Error(`PermDock CLI: unsafe rls.migrate helper name '${name}'`);
1491
+ for (const [name, helper] of Object.entries(config.helpers).toSorted(byKey)) {
1492
+ if (!NAME.test(name)) throw configError(`PermDock CLI: unsafe rls.migrate helper name '${name}'`);
1985
1493
  chunks.push(shim(ctx, config, keys, `${quoteIdent$1(schema)}.${name}`, helper));
1986
1494
  }
1987
1495
  return `${chunks.join("\n\n")}\n`;
@@ -2013,20 +1521,20 @@ function generatedTables(sql, schema) {
2013
1521
  return [...found].toSorted();
2014
1522
  }
2015
1523
  function functionName(name, label) {
2016
- if (typeof name !== "string" || name.split(".").length !== 2) throw new Error(`${PREFIX}: ${label} must be a schema-qualified function such as audit.log_change, got ${JSON.stringify(name)}`);
1524
+ if (typeof name !== "string" || name.split(".").length !== 2) throw configError(`${PREFIX}: ${label} must be a schema-qualified function such as audit.log_change, got ${JSON.stringify(name)}`);
2017
1525
  return quoteTable(name);
2018
1526
  }
2019
1527
  function knownTable(table, tables, label) {
2020
- if (!tables.includes(table)) throw new Error(`${PREFIX}: ${label} names ${table}, which rls generate does not create with this config; it creates ${tables.join(", ")}`);
1528
+ if (!tables.includes(table)) throw configError(`${PREFIX}: ${label} names ${table}, which rls generate does not create with this config; it creates ${tables.join(", ")}`);
2021
1529
  }
2022
1530
  function triggerSql(table, trigger, label) {
2023
- if (typeof trigger.name !== "string" || trigger.name === "" || trigger.name.toLowerCase().startsWith("permdock_")) throw new Error(`${PREFIX}: ${label}.name must be a name outside permdock_, which generated triggers use, got ${JSON.stringify(trigger.name)}`);
2024
- if (trigger.when !== "before" && trigger.when !== "after") throw new Error(`${PREFIX}: ${label}.when must be before or after`);
1531
+ if (typeof trigger.name !== "string" || trigger.name === "" || trigger.name.toLowerCase().startsWith("permdock_")) throw configError(`${PREFIX}: ${label}.name must be a name outside permdock_, which generated triggers use, got ${JSON.stringify(trigger.name)}`);
1532
+ if (trigger.when !== "before" && trigger.when !== "after") throw configError(`${PREFIX}: ${label}.when must be before or after`);
2025
1533
  const events = trigger.events;
2026
- if (events.length === 0 || !events.every((event) => EVENTS.has(event))) throw new Error(`${PREFIX}: ${label}.events must list insert, update, delete or truncate`);
1534
+ if (events.length === 0 || !events.every((event) => EVENTS.has(event))) throw configError(`${PREFIX}: ${label}.events must list insert, update, delete or truncate`);
2027
1535
  const level = trigger.level ?? "row";
2028
- if (level !== "row" && level !== "statement") throw new Error(`${PREFIX}: ${label}.level must be row or statement`);
2029
- if (level === "row" && events.includes("truncate")) throw new Error(`${PREFIX}: ${label} fires on truncate, which Postgres allows only with level: 'statement'`);
1536
+ if (level !== "row" && level !== "statement") throw configError(`${PREFIX}: ${label}.level must be row or statement`);
1537
+ if (level === "row" && events.includes("truncate")) throw configError(`${PREFIX}: ${label} fires on truncate, which Postgres allows only with level: 'statement'`);
2030
1538
  const fn = functionName(trigger.function, `${label}.function`);
2031
1539
  const args = (trigger.args ?? []).map((arg) => quoteLiteral(arg));
2032
1540
  const name = quoteIdent$1(trigger.name);
@@ -2043,7 +1551,7 @@ create trigger ${name}
2043
1551
  function triggersSql(triggers, schema, tables) {
2044
1552
  if (triggers === void 0) return "";
2045
1553
  const chunks = [];
2046
- for (const [table, list] of Object.entries(triggers).toSorted(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)) {
1554
+ for (const [table, list] of Object.entries(triggers).toSorted(byKey)) {
2047
1555
  knownTable(table, tables, `rls.triggers.${table}`);
2048
1556
  const target = `${quoteIdent$1(schema)}.${quoteIdent$1(table)}`;
2049
1557
  for (const [index, trigger] of list.entries()) chunks.push(triggerSql(target, trigger, `rls.triggers.${table}[${String(index)}]`));
@@ -2062,7 +1570,7 @@ function auditSql(audit, schema, tables) {
2062
1570
  if (listed !== void 0) for (const table of listed) knownTable(table, tables, "rls.audit.tables");
2063
1571
  const targets = listed ?? AUDITED.filter((table) => tables.includes(table));
2064
1572
  const args = Object.entries(audit.args ?? {}).map(([name, value]) => {
2065
- if (!IDENT.test(name)) throw new Error(`${PREFIX}: rls.audit.args names ${JSON.stringify(name)}, which is not a lower-case parameter name`);
1573
+ if (!IDENT.test(name)) throw configError(`${PREFIX}: rls.audit.args names ${JSON.stringify(name)}, which is not a lower-case parameter name`);
2066
1574
  return `, ${quoteIdent$1(name)} => ${quoteLiteral(value)}`;
2067
1575
  });
2068
1576
  if (targets.length === 0) return "";
@@ -2121,7 +1629,7 @@ function sectionsSql(input, policy, options, built, generated, jsonSchema, warni
2121
1629
  if (ctx.assignments !== void 0 && (ownership === void 0 || ownership.assigns.length === 0)) warnings.push("rls.assignments needs a role that declares assigns: no assignment trigger is written");
2122
1630
  let approvals;
2123
1631
  try {
2124
- approvals = rls?.approvals === true ? approvalStoreSql(ctx, jsonSchema ?? false) : typeof rls?.approvals === "object" ? adoptedApprovalStoreSql(ctx, rls.approvals, jsonSchema ?? false) : void 0;
1632
+ approvals = approvalsSql(ctx, rls?.approvals, jsonSchema ?? false);
2125
1633
  } catch (cause) {
2126
1634
  return { refusal: describeError(cause) };
2127
1635
  }
@@ -2163,6 +1671,7 @@ function preambleSql(input, policy, options, built, generated, jsonSchema, warni
2163
1671
  const renamed = Object.fromEntries(renamedKeys(policy.vocabulary.permissions));
2164
1672
  const grants = shimGrants(compiled.rolePermissions, compiled.conditionedKeys, renamed);
2165
1673
  const anonExecute = anonExecutes(input, generated);
1674
+ const actorKinds = readOnlyActorKinds(rls?.readOnlyActors);
2166
1675
  const usesLiveSession = compiled.branches.some((branch) => [
2167
1676
  branch.access,
2168
1677
  branch.using,
@@ -2180,6 +1689,8 @@ function preambleSql(input, policy, options, built, generated, jsonSchema, warni
2180
1689
  ...rls?.trustedReaders === void 0 ? {} : { trustedReaders: rls.trustedReaders }
2181
1690
  }),
2182
1691
  permissionHelpersSql(ctx, grants, renamed, anonExecute),
1692
+ permissionHoldersSql(ctx) || void 0,
1693
+ actorKinds === void 0 ? void 0 : actorMayWriteSql(ctx, actorKinds),
2183
1694
  usesLiveSession ? sessionLiveHelperSql(ctx) : void 0,
2184
1695
  ...sections,
2185
1696
  shims,
@@ -2263,7 +1774,7 @@ function emitTarget(input, built, generated, preamble) {
2263
1774
  outRel
2264
1775
  };
2265
1776
  }
2266
- /** The generated SQL for one run, before `--db` prunes the index targets. */
1777
+ /** The generated SQL for one run, before `--db-url-stdin` prunes the index targets. */
2267
1778
  function generateSql(input, policy, options, built, warnings) {
2268
1779
  const rls = input.config.rls;
2269
1780
  const { ctx } = built;
@@ -2299,7 +1810,53 @@ function generateSql(input, policy, options, built, warnings) {
2299
1810
  //#region src/cli/rls/generate.ts
2300
1811
  function loadPolicy(cwd, config, from) {
2301
1812
  const policyPath = from === "drizzle" ? config.policy : from ?? config.policy;
2302
- return requirePolicy(cwd, policyPath, "rls generate needs policy in permdock.config.ts or --from");
1813
+ return requirePolicy(cwd, policyPath, "rls generate needs policy in permdock.config.ts or --policy");
1814
+ }
1815
+ const PROJECT_COMMAND = "permdock rls generate";
1816
+ /**
1817
+ * The policy `policyFromCatalog` rebuilds from the project's pushed catalog
1818
+ * and policy; a project with no pushed policy fails with `policy-not-published`.
1819
+ */
1820
+ async function projectPolicyOf(input, project) {
1821
+ const fetched = await fetchProjectPolicy({
1822
+ command: PROJECT_COMMAND,
1823
+ url: input.url,
1824
+ project,
1825
+ json: input.json === true,
1826
+ env: input.io.env ?? process.env,
1827
+ io: input.io
1828
+ });
1829
+ if (!fetched.ok) return {
1830
+ ...fetched.result,
1831
+ text: ""
1832
+ };
1833
+ const { catalog, policy } = fetched.value;
1834
+ if (policy === void 0) {
1835
+ const detail = `project ${project} has a pushed catalog but no policy; push from a project whose permdock.config.ts names one`;
1836
+ return {
1837
+ code: 1,
1838
+ output: input.json === true ? JSON.stringify({
1839
+ type: POLICY_NOT_PUBLISHED,
1840
+ title: "Policy not published",
1841
+ detail,
1842
+ command: PROJECT_COMMAND,
1843
+ exitCode: 1
1844
+ }, null, 2) : `${PROJECT_COMMAND}: ${detail}`,
1845
+ text: ""
1846
+ };
1847
+ }
1848
+ try {
1849
+ return policyFromCatalog(catalog, policy);
1850
+ } catch (cause) {
1851
+ return {
1852
+ code: 1,
1853
+ output: `${PROJECT_COMMAND}: the pushed policy does not rebuild: ${describeError(cause)}`,
1854
+ text: ""
1855
+ };
1856
+ }
1857
+ }
1858
+ function isOutcome(value) {
1859
+ return "text" in value && "code" in value;
2303
1860
  }
2304
1861
  function refused(output) {
2305
1862
  return {
@@ -2311,7 +1868,14 @@ function refused(output) {
2311
1868
  async function runRlsGenerate(input) {
2312
1869
  const options = generateOptions(input);
2313
1870
  if (isRefusal(options)) return refused(options.refusal);
2314
- const policy = await loadPolicy(input.cwd, input.config, input.from);
1871
+ if (input.project !== void 0 && input.from !== void 0 && input.from !== "drizzle") return refused("rls generate takes the policy from --project or from a --policy module, not both");
1872
+ let policy;
1873
+ if (input.project === void 0) policy = await loadPolicy(input.cwd, input.config, input.from);
1874
+ else {
1875
+ const loaded = await projectPolicyOf(input, input.project);
1876
+ if (isOutcome(loaded)) return loaded;
1877
+ policy = loaded;
1878
+ }
2315
1879
  const built = buildContext(input, policy, options);
2316
1880
  if (isRefusal(built)) return refused(built.refusal);
2317
1881
  const warnings = [];
@@ -2326,7 +1890,7 @@ async function runRlsGenerate(input) {
2326
1890
  warnings.push(...pruned.warnings);
2327
1891
  } catch (cause) {
2328
1892
  return {
2329
- code: exitCodeOf(cliErrorKind(cause)),
1893
+ code: exitCodeOfError(cause),
2330
1894
  output: describeError(cause),
2331
1895
  text: ""
2332
1896
  };
@@ -2377,8 +1941,9 @@ async function runRlsGenerate(input) {
2377
1941
  part: "backfill",
2378
1942
  marker: BACKFILL_MARKER,
2379
1943
  text: generated.backfill,
2380
- now: input.io.now?.() ?? /* @__PURE__ */ new Date(),
2381
- check: input.check
1944
+ now: input.io.now?.() ?? dateAt(),
1945
+ check: input.check,
1946
+ reserved: files.map((other) => other.rel)
2382
1947
  });
2383
1948
  if (file !== void 0) files.push(file);
2384
1949
  }
@@ -2397,10 +1962,11 @@ async function runRlsGenerate(input) {
2397
1962
  };
2398
1963
  }
2399
1964
  const written = writeOutputFiles(input.cwd, files);
2400
- const extra = warnings.length === 0 ? "" : `\n${warnings.join("\n")}`;
1965
+ const notes = [...written.wrote.length === 0 ? [] : [`wrote ${written.wrote.join(", ")}`], ...warnings];
1966
+ if (written.printed !== "") for (const note of notes) input.io.stderr(note);
2401
1967
  return {
2402
1968
  code: 0,
2403
- output: `wrote ${written.wrote.join(", ")}${written.printed === "" ? "" : `\n${written.printed}`}${extra}`,
1969
+ output: written.printed === "" ? notes.join("\n") : written.printed,
2404
1970
  text,
2405
1971
  ...extras
2406
1972
  };
@@ -2591,6 +2157,29 @@ async function introspectRls(db, expected, connect = connectIntrospect) {
2591
2157
  await client.end();
2592
2158
  }
2593
2159
  }
2160
+ const WRITES = [
2161
+ "insert",
2162
+ "update",
2163
+ "delete"
2164
+ ];
2165
+ /**
2166
+ * Each table and write command whose permissive policies reach
2167
+ * `authenticated` (or `public`) without the read-only actor check: no
2168
+ * restrictive policy for the command calls `permdock_actor_may_write()`, and
2169
+ * some permissive one does not.
2170
+ */
2171
+ function unguardedWrites(policies, fn) {
2172
+ const out = [];
2173
+ const tables = [...new Set(policies.map((policy) => policy.table))];
2174
+ for (const table of tables) for (const command of WRITES) {
2175
+ const applying = policies.filter((policy) => policy.table === table && (policy.command === command || policy.command === "all"));
2176
+ const permissive = applying.filter((policy) => policy.restrictive !== true && (policy.roles ?? []).some((role) => role === "authenticated" || role === "public"));
2177
+ if (permissive.length === 0) continue;
2178
+ const calls = (expression) => expression.includes(fn);
2179
+ if (!(applying.some((policy) => policy.restrictive === true && calls(policy.expression)) || permissive.every((policy) => calls(policy.expression)))) out.push(`${table}: ${command} policies for authenticated do not call ${fn}(), so a read-only support or impersonation session can write (rls.readOnlyActors)`);
2180
+ }
2181
+ return out;
2182
+ }
2594
2183
  const SYSTEM_SCHEMAS = [
2595
2184
  "pg_catalog",
2596
2185
  "information_schema",
@@ -2610,7 +2199,7 @@ const SYSTEM_SCHEMAS = [
2610
2199
  "supabase_migrations"
2611
2200
  ];
2612
2201
  function inScope(table) {
2613
- const schema = table.split(".")[0] ?? "";
2202
+ const schema = beforeFirst(table, ".");
2614
2203
  return HELPER_TABLES.some((item) => item === table) || !(SYSTEM_SCHEMAS.some((item) => item === schema) || schema.startsWith("pg_"));
2615
2204
  }
2616
2205
  function seedLine(row) {
@@ -2632,13 +2221,16 @@ function diffMixed(expected, actual) {
2632
2221
  for (const key of helperCallKeys(policy.expression)) if (!permissions.has(key)) drift.push(`${policy.table}: policy ${policy.name} passes ${key}, which the policy does not declare, so it always denies`);
2633
2222
  else if (conditioned.has(key) && blind.has(key)) drift.push(`${policy.table}: policy ${policy.name} passes ${key}, whose grants carry row conditions the helpers do not check: the policy grants more than the application does`);
2634
2223
  }
2224
+ if (expected.readOnlyActors === true) drift.push(...unguardedWrites(actual.policies, ACTOR_MAY_WRITE));
2635
2225
  return {
2636
2226
  drift,
2637
2227
  info: actual.rlsTables.filter((table) => !covered.has(table)).map((table) => `${table}: no policy calls a PermDock helper`)
2638
2228
  };
2639
2229
  }
2640
2230
  const ALL_POLICIES_SQL = `select schemaname || '.' || tablename as target, policyname,
2641
- coalesce(qual, '') || ' ' || coalesce(with_check, '') as expression
2231
+ coalesce(qual, '') || ' ' || coalesce(with_check, '') as expression,
2232
+ lower(cmd) as command, permissive = 'RESTRICTIVE' as restrictive,
2233
+ array_to_string(roles, ',') as roles
2642
2234
  from pg_policies`;
2643
2235
  const RLS_TABLES_SQL = `select n.nspname || '.' || c.relname as target
2644
2236
  from pg_class c
@@ -2665,7 +2257,10 @@ async function introspectMixed(db, schema, connect = connectIntrospect) {
2665
2257
  policies: policies.rows.map((row) => ({
2666
2258
  table: String(row["target"]),
2667
2259
  name: String(row["policyname"]),
2668
- expression: String(row["expression"])
2260
+ expression: String(row["expression"]),
2261
+ command: String(row["command"]),
2262
+ restrictive: row["restrictive"] === true,
2263
+ roles: String(row["roles"] ?? "").split(",").filter((role) => role !== "")
2669
2264
  })).filter((row) => inScope(row.table)),
2670
2265
  rlsTables: tables.rows.map((row) => String(row["target"])).filter(inScope).toSorted()
2671
2266
  };
@@ -2677,24 +2272,12 @@ async function introspectMixed(db, schema, connect = connectIntrospect) {
2677
2272
  //#region src/cli/rls/rls.ts
2678
2273
  const RLS_HELP = `permdock rls generate | import | verify | migrate
2679
2274
 
2680
- generate --target drizzle|sql|prisma --dialect supabase|neon|guc
2681
- [--rbac supabase] [--rbac-schema public] [--authorize database|jwt]
2682
- [--memberships <table>:tenant,user,role]
2683
- [--policy-per-role] [--policy-name '{table}_{op}'] [--tenant-type uuid] [--custom-roles]
2684
- [--capabilities] [--fields views [--revoke-columns]]
2685
- [--out <path>] [--check] [--skip-closures] [--inline-functions] [--force] [--guc-prefix app]
2686
- [--split helpers,seeds,policies,hook --out <dir>/056_permdock_{part}.sql]
2687
- [--grants-out <file>|-] [--seeds-out <file>|-] [--backfill-out <file>|<dir>/|-]
2688
- [--helpers-only] [--shims] [--db $DATABASE_URL]
2689
- import --sql schema.sql | --db $DATABASE_URL --out src/permissions.generated.ts
2690
- [--schema zod|valibot|arktype] [--memberships <table>:tenant,user,role]
2691
- verify [--db $DATABASE_URL] [--fixtures rls.fixtures.ts] [--format pgtap|node] [--tree]
2692
- [--introspect --db $DATABASE_URL, with the generate flags]
2693
- [--advisors [--db $DATABASE_URL] [--json]]: supabase db advisors, security lints
2694
- migrate --sql <dir> [--write] [--json] [--retire-out <file>|<dir>/|-], with the generate flags
2695
- rewrites the rls.migrate helpers' calls in policies onto the generated helpers;
2696
- --retire-out drops each rls.migrate.tables table and its refresh triggers once unread
2275
+ generate Compile the policy into RLS policies and helpers
2276
+ import Turn existing policies (--sql or --db-url-stdin) into a permissions module
2277
+ verify Run the fixtures, or compare a live database with --introspect
2278
+ migrate Rewrite the rls.migrate helpers' calls onto the generated helpers
2697
2279
 
2280
+ Run permdock rls <command> --help for its flags.
2698
2281
  Never emits service_role, except in the execute grant rls.trustedReaders asks for. memberOf compiles through the dialect memberships mapping.
2699
2282
  `;
2700
2283
  function asTarget(value) {
@@ -2719,6 +2302,9 @@ function generateInput(input, target, dialect) {
2719
2302
  io: input.io,
2720
2303
  ...input.out === void 0 ? {} : { out: input.out },
2721
2304
  ...input.from === void 0 ? {} : { from: input.from },
2305
+ ...input.project === void 0 ? {} : { project: input.project },
2306
+ ...input.url === void 0 ? {} : { url: input.url },
2307
+ json: input.json,
2722
2308
  ...input.memberships === void 0 ? {} : { memberships: input.memberships },
2723
2309
  ...input.gucPrefix === void 0 ? {} : { gucPrefix: input.gucPrefix },
2724
2310
  policyPerRole: input.policyPerRole,
@@ -2729,6 +2315,7 @@ function generateInput(input, target, dialect) {
2729
2315
  ...input.fields === void 0 ? {} : { fields: input.fields },
2730
2316
  revokeColumns: input.revokeColumns,
2731
2317
  ...input.split === void 0 ? {} : { split: input.split },
2318
+ ...isSupabaseSchemas(input.schemas) ? { schemas: input.schemas } : {},
2732
2319
  ...input.grantsOut === void 0 ? {} : { grantsOut: input.grantsOut },
2733
2320
  ...input.seedsOut === void 0 ? {} : { seedsOut: input.seedsOut },
2734
2321
  ...input.backfillOut === void 0 ? {} : { backfillOut: input.backfillOut },
@@ -2761,20 +2348,24 @@ async function migrate(input) {
2761
2348
  output: generated.output
2762
2349
  };
2763
2350
  await requirePeer(() => import("pgsql-parser"), "pgsql-parser", "permdock rls migrate", input.cwd);
2764
- const { migrateTarget, runRlsMigrate } = await import("./migrate-BwlB5-Wk.js");
2351
+ const { migrateTarget, runRlsMigrate } = await import("./migrate-CIq4-E9F.js");
2765
2352
  return runRlsMigrate({
2766
2353
  cwd: input.cwd,
2767
2354
  sql: input.sql,
2768
2355
  config,
2769
- target: migrateTarget(generated),
2356
+ target: {
2357
+ ...migrateTarget(generated),
2358
+ ...input.config.rls?.roleValues === void 0 ? {} : { roleValues: input.config.rls.roleValues }
2359
+ },
2770
2360
  write: input.write,
2361
+ force: input.forceRewrite === true,
2771
2362
  json: input.json,
2772
2363
  retireOut: input.retireOut,
2773
2364
  ...input.io.now === void 0 ? {} : { now: input.io.now() }
2774
2365
  });
2775
2366
  }
2776
2367
  /** `verify --introspect` with `--helpers-only`: helpers and seeds exactly, hand-written policies by their keys. */
2777
- async function introspectHelpersOnly(db, expected, generated, connect) {
2368
+ async function introspectHelpersOnly(db, expected, generated, connect, readOnlyActors) {
2778
2369
  const schema = generated.schema ?? "permdock";
2779
2370
  try {
2780
2371
  const actual = await introspectRls(db, expected, connect);
@@ -2784,7 +2375,8 @@ async function introspectHelpersOnly(db, expected, generated, connect) {
2784
2375
  schema,
2785
2376
  seeds: generated.seeds ?? [],
2786
2377
  permissions: generated.keys?.permissions ?? [],
2787
- rowConditions: generated.keys?.rowConditions ?? []
2378
+ rowConditions: generated.keys?.rowConditions ?? [],
2379
+ readOnlyActors
2788
2380
  }, await introspectMixed(db, schema, connect));
2789
2381
  const drift = [...helpers, ...mixed.drift];
2790
2382
  const info = mixed.info.map((line) => `info: ${line}`);
@@ -2811,7 +2403,7 @@ async function introspectHelpersOnly(db, expected, generated, connect) {
2811
2403
  async function introspect(input) {
2812
2404
  if (input.db === void 0) return {
2813
2405
  code: 2,
2814
- output: "PermDock CLI: rls verify --introspect needs --db"
2406
+ output: "PermDock CLI: rls verify --introspect needs a database: --db-url-stdin, DATABASE_URL or SUPABASE_DB_URL"
2815
2407
  };
2816
2408
  const dialect = asDialect(input.dialect ?? input.config.rls?.dialect);
2817
2409
  if (dialect === void 0) return {
@@ -2828,7 +2420,7 @@ async function introspect(input) {
2828
2420
  output: generated.output
2829
2421
  };
2830
2422
  const expected = expectedRls(generated.policies, generated.text, generated.indexes);
2831
- if (generated.helpersOnly === true) return introspectHelpersOnly(input.db, expected, generated, input.connect);
2423
+ if (generated.helpersOnly === true) return introspectHelpersOnly(input.db, expected, generated, input.connect, readOnlyActorKinds(input.config.rls?.readOnlyActors) !== void 0);
2832
2424
  try {
2833
2425
  const actual = await introspectRls(input.db, expected, input.connect);
2834
2426
  const drift = diffRls(expected, actual, { columnGrants: (input.fields ?? input.config.rls?.fields) === "views" });
@@ -2851,6 +2443,11 @@ async function runRls(input) {
2851
2443
  code: 2,
2852
2444
  output: RLS_HELP
2853
2445
  };
2446
+ const badSchemas = schemasRefusal(`rls ${action}`, input.schemas);
2447
+ if (badSchemas !== void 0) return {
2448
+ code: 2,
2449
+ output: badSchemas
2450
+ };
2854
2451
  switch (action) {
2855
2452
  case "generate": {
2856
2453
  const target = asTarget(input.target);
@@ -2871,7 +2468,7 @@ async function runRls(input) {
2871
2468
  }
2872
2469
  case "import": {
2873
2470
  await requirePeer(() => import("pgsql-parser"), "pgsql-parser", "permdock rls import", input.cwd);
2874
- const { runRlsImport } = await import("./import-BFIlhetD.js");
2471
+ const { runRlsImport } = await import("./import-wyqXUyjm.js");
2875
2472
  return runRlsImport({
2876
2473
  cwd: input.cwd,
2877
2474
  config: input.config,
@@ -2889,6 +2486,7 @@ async function runRls(input) {
2889
2486
  cwd: input.cwd,
2890
2487
  json: input.json,
2891
2488
  env: input.io.env ?? process.env,
2489
+ ...input.io.signal === void 0 ? {} : { signal: input.io.signal },
2892
2490
  ...input.db === void 0 ? {} : { db: input.db },
2893
2491
  ...input.exec === void 0 ? {} : { exec: input.exec }
2894
2492
  });