permdock 0.6.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (507) hide show
  1. package/README.md +2 -3
  2. package/dist/{2020-C9VN-x8c.js → 2020-CJbO8Bvz.js} +1 -3
  3. package/dist/a2a/index.d.ts +7 -7
  4. package/dist/a2a/index.js +9 -9
  5. package/dist/action-result-CL-QCITA.d.ts +19 -0
  6. package/dist/{adapter-context-CRhqyzCk.js → adapter-context-CIQpQxbE.js} +8 -2
  7. package/dist/{adoption-scan-DCYe60Pi.js → adoption-scan-BLs67P1l.js} +5 -5
  8. package/dist/agent/index.d.ts +3 -3
  9. package/dist/agent/index.js +1 -1
  10. package/dist/ai-sdk/index.d.ts +31 -10
  11. package/dist/ai-sdk/index.js +22 -12
  12. package/dist/{app-data-MuyzVqX8.js → app-data-UNNm07jV.js} +10 -7
  13. package/dist/{approval-header-CJACtHkv.js → approval-header-8QV6MU4H.js} +6 -3
  14. package/dist/{approval-prompt-Dg5LeAcT.js → approval-prompt-B8hQY69x.js} +5 -6
  15. package/dist/{approval-prompt-DvSSPQJX.d.ts → approval-prompt-PYagyQUj.d.ts} +13 -11
  16. package/dist/approvals/index.d.ts +5 -5
  17. package/dist/approvals/index.js +7 -7
  18. package/dist/{approvals-p1PGoVjb.js → approvals-DnIBTuXE.js} +128 -78
  19. package/dist/{approvers-CqPOLD-o.js → approvers-B4r0N_Uz.js} +8 -2
  20. package/dist/{arazzo-41WuzHou.js → arazzo-3RhGfkAK.js} +2 -2
  21. package/dist/{arazzo-CaUVBn7D.js → arazzo-B5-ulWif.js} +6 -6
  22. package/dist/{args-IIolxxKA.js → args-0FrVs5EH.js} +125 -8
  23. package/dist/astro/index.d.ts +30 -12
  24. package/dist/astro/index.js +2 -2
  25. package/dist/{audience-DabClFLQ.js → audience-BAkBDMqn.js} +8 -8
  26. package/dist/{auth-DRLEizbL.js → auth--yPyZNgr.js} +21 -10
  27. package/dist/authzen/index.d.ts +171 -25
  28. package/dist/authzen/index.js +321 -149
  29. package/dist/{automation-BNM3TtDo.d.ts → automation-DqEoWHBc.d.ts} +42 -32
  30. package/dist/{automation-DVjOckjV.js → automation-x3KEUusw.js} +22 -21
  31. package/dist/base-url-BQbddZO5.js +9 -0
  32. package/dist/better-auth/index.d.ts +2 -2
  33. package/dist/better-auth/index.js +13 -7
  34. package/dist/better-supabase/index.d.ts +392 -220
  35. package/dist/better-supabase/index.js +293 -152
  36. package/dist/{bind-DOyrv6zi.d.ts → bind-Cp2hBHzP.d.ts} +27 -12
  37. package/dist/{bind-CAvgUuKs.js → bind-D_sZSVA3.js} +22 -10
  38. package/dist/{body-CH5of8Ne.js → body-CJQwYVa8.js} +3 -3
  39. package/dist/{body-text-QcXok_SI.js → body-text-DQhcmqdY.js} +8 -4
  40. package/dist/{boundary-BM8a_P6E.js → boundary-BV3O1eud.js} +8 -2
  41. package/dist/{boundary-BmHxVH1J.d.ts → boundary-C9ck-3uK.d.ts} +13 -4
  42. package/dist/{budget-NbEtbJC_.js → budget-BELja5A9.js} +6 -3
  43. package/dist/{budget-JqcRqyhK.d.ts → budget-hYsPz_yE.d.ts} +6 -3
  44. package/dist/{build-instance-Bu_eDqYw.js → build-instance-CNWaYJyr.js} +3 -3
  45. package/dist/{canonical-json-BC8_nMTw.js → canonical-json-DwilZ1iB.js} +6 -3
  46. package/dist/{capability-N1K9IpqS.js → capability-BqX94r07.js} +31 -25
  47. package/dist/{capability-Drg-1YhL.d.ts → capability-ChuiONQU.d.ts} +41 -29
  48. package/dist/{casbin-Dyu2gnUD.js → casbin-H0rKxBx2.js} +16 -8
  49. package/dist/{casbin-Cr7RLtA3.js → casbin-cbSbhT_k.js} +2 -2
  50. package/dist/{casl-64gpT-8x.js → casl-C4OINZHI.js} +2 -2
  51. package/dist/{casl-DjEDhTCP.js → casl-CHHoCldP.js} +19 -10
  52. package/dist/catalog/index.d.ts +104 -23
  53. package/dist/catalog/index.js +117 -8
  54. package/dist/{catalog-HD2gf5mY.js → catalog-BFpz2EmT.js} +57 -26
  55. package/dist/{catalog-DZVbrF-z.js → catalog-DCspHDc8.js} +3 -3
  56. package/dist/{catalog-doc-Cdn7-0pQ.js → catalog-doc-DjMKzJp9.js} +53 -16
  57. package/dist/{catalog-fingerprint-CCP0-yQS.js → catalog-fingerprint-e6aCWosn.js} +19 -15
  58. package/dist/catalog-mRQQQhNK.js +1 -0
  59. package/dist/catalog-operations-Dadrl7Z-.js +60 -0
  60. package/dist/{create-CQWIpUG-.js → challenge-DImNcR7B.js} +157 -39
  61. package/dist/chat/index.d.ts +1 -1
  62. package/dist/chat/index.js +13 -10
  63. package/dist/chat-sdk/index.d.ts +44 -26
  64. package/dist/chat-sdk/index.js +17 -17
  65. package/dist/checks-CGkP3Z7A.js +40 -0
  66. package/dist/{claims-map-7oQ3ue4X.js → claims-map-CKkoAAYF.js} +5 -2
  67. package/dist/{claims-map-Ca-QX8C6.d.ts → claims-map-D1Pb5oRq.d.ts} +8 -5
  68. package/dist/claude-agent/index.d.ts +6 -6
  69. package/dist/claude-agent/index.js +6 -3
  70. package/dist/clerk/index.d.ts +11 -7
  71. package/dist/clerk/index.js +6 -6
  72. package/dist/cli/bin.js +4 -4
  73. package/dist/cli/index.d.ts +82 -7
  74. package/dist/cli/index.js +6 -6
  75. package/dist/{clients-C7zLFuWT.d.ts → clients-C_NOET3z.d.ts} +5 -5
  76. package/dist/{clients-DjU5ABMy.js → clients-DYr3U86j.js} +3 -3
  77. package/dist/cloud/index.d.ts +186 -38
  78. package/dist/cloud/index.js +293 -31
  79. package/dist/{cloud-ytQ3uviA.js → cloud-BFlGMaak.js} +5 -6
  80. package/dist/{codemod-5mpSpnUY.js → codemod-DLQ9Zpo4.js} +3 -4
  81. package/dist/{codemod-ua2hHy7w.js → codemod-DWSN2erG.js} +523 -201
  82. package/dist/collect-B5BL3klN.js +1 -0
  83. package/dist/{collect-mLTY4nwb.js → collect-B7ZFAJU3.js} +36 -64
  84. package/dist/{collect-Ci6akVwH.js → collect-Bzv3le0A.js} +6 -6
  85. package/dist/{compare-pfyVnBjE.js → compare-DTfyUunF.js} +2 -2
  86. package/dist/compile/index.d.ts +2 -2
  87. package/dist/compile/index.js +1 -1
  88. package/dist/{compile-1bsAezL0.d.ts → compile-BFzysW3o.d.ts} +36 -12
  89. package/dist/{compile-CMqMx_IN.js → compile-EzEMB-7v.js} +77 -33
  90. package/dist/{completion-BTFrHZhX.js → completion-saoYiRFO.js} +17 -8
  91. package/dist/config-BwPBfN0t.js +108 -0
  92. package/dist/{config-Bj2Ixq3d.js → config-DBeXXmBF.js} +2 -2
  93. package/dist/{config-report-DSpsLpCE.js → config-report-BipSWA6V.js} +9 -6
  94. package/dist/config-shape-ykV0Vm2n.js +134 -0
  95. package/dist/{connection-DjhIJSs2.js → connection-B72dg5Q2.js} +23 -22
  96. package/dist/connector-schema-CYLQNMYq.js +434 -0
  97. package/dist/{context-B_u3_bDJ.js → context-BnIzxTNE.js} +4 -3
  98. package/dist/convex/index.d.ts +13 -7
  99. package/dist/convex/index.js +9 -6
  100. package/dist/core-H-mbhpZc.js +341 -0
  101. package/dist/core-hWIARPDz.d.ts +206 -0
  102. package/dist/{create-CBMmFie-.d.ts → create-BSNrVuxh.d.ts} +87 -34
  103. package/dist/{create-B30OMUOJ.d.ts → create-BVq1lNQE.d.ts} +49 -28
  104. package/dist/{create-D_NmPOdH.d.ts → create-CM1FciYG.d.ts} +37 -29
  105. package/dist/{create-CGm28gyI.js → create-Cq4Uox0f.js} +13 -9
  106. package/dist/{create-DCF2SvoT.js → create-_noSrY1T.js} +15 -21
  107. package/dist/{credential-BcoiejpY.js → credential-CyyyvCsL.js} +42 -39
  108. package/dist/{credentials-DPyHmYz5.js → credentials-CdDxp7jN.js} +6 -6
  109. package/dist/{deciding-columns-Sq12mEA6.js → deciding-columns-NB4wcyBf.js} +13 -2
  110. package/dist/{delegation-j6-rc37o.js → delegation-CDB6_keF.js} +31 -22
  111. package/dist/delivery/index.d.ts +5 -5
  112. package/dist/delivery/index.js +6 -5
  113. package/dist/{delivery-DCAgNyCn.js → delivery-BupfvJIV.js} +81 -20
  114. package/dist/{delivery-event-xX4obB8F.js → delivery-event-JioK5YbS.js} +6 -6
  115. package/dist/denied-text-CHNlVInN.d.ts +22 -0
  116. package/dist/{describe-BybMRY7A.js → describe-Bb4hGK5d.js} +6 -5
  117. package/dist/{describe-B5qgDvEW.d.ts → describe-ByS9LgHK.d.ts} +5 -5
  118. package/dist/{device-BARQ8eYG.js → device-BwbJNmb2.js} +11 -8
  119. package/dist/{diff-CmuSD3vc.js → diff-CN8IYhC_.js} +2 -2
  120. package/dist/{diff-3jbx6Rm4.js → diff-iZWV65nC.js} +23 -14
  121. package/dist/{digest-B3kc_PgQ.d.ts → digest-8Yx2CchM.d.ts} +3 -3
  122. package/dist/{doctor-BdZw53-k.js → doctor-CHD1M9R5.js} +485 -141
  123. package/dist/{doctor-3ZqNHX_G.js → doctor-D3wo-yuz.js} +4 -4
  124. package/dist/drizzle/index.d.ts +33 -19
  125. package/dist/drizzle/index.js +17 -9
  126. package/dist/elysia/index.d.ts +17 -11
  127. package/dist/elysia/index.js +5 -5
  128. package/dist/{errors-BKy0EfhN.js → errors-B6R_lg2a.js} +13 -4
  129. package/dist/{errors-BWXBMaKK.d.ts → errors-CHwoRRpB.d.ts} +4 -1
  130. package/dist/{errors-CWTY6uih.js → errors-Vpu6rKHl.js} +14 -10
  131. package/dist/{errors-DH5QEGXh.d.ts → errors-zi_4T-rx.d.ts} +22 -6
  132. package/dist/{evaluate-CNClBvcj.js → evaluate-Dd8Ta0Ie.js} +185 -80
  133. package/dist/{evaluations-beAooV0n.js → evaluations-IBceAeKT.js} +41 -20
  134. package/dist/{evaluations-C_1vdSZc.d.ts → evaluations-z8dQFDaN.d.ts} +12 -6
  135. package/dist/eve/index.d.ts +9 -6
  136. package/dist/eve/index.js +4 -4
  137. package/dist/{event-time-CsegxXxB.js → event-time-BGy48400.js} +8 -4
  138. package/dist/{expiry-D4_YKRKD.js → expiry-6XByK0dm.js} +8 -5
  139. package/dist/express/index.d.ts +9 -6
  140. package/dist/express/index.js +9 -6
  141. package/dist/fastify/index.d.ts +16 -10
  142. package/dist/fastify/index.js +8 -5
  143. package/dist/{fetch-adapter-Dw0MTS3D.d.ts → fetch-adapter-C1DCvpqV.d.ts} +9 -6
  144. package/dist/{fetch-adapter-DD_gfmrC.js → fetch-adapter-JL43jD3E.js} +14 -11
  145. package/dist/fields-D0vUI6dq.js +498 -0
  146. package/dist/{files-C5ZI27eV.js → files-B3AA05f1.js} +21 -5
  147. package/dist/{files-D_JIzUrP.js → files-BSjrtIHw.js} +11 -4
  148. package/dist/{fixtures-B7XrLeY7.js → fixtures-A1_sp_tR.js} +15 -6
  149. package/dist/{flags-C3DktBDY.js → flags-DXjoTVbS.js} +2 -2
  150. package/dist/{freeze-CrU8nQeX.js → freeze-jjan0lCv.js} +4 -1
  151. package/dist/{from-snapshot-CTCMsFZk.js → from-snapshot-DCakWRsr.js} +26 -16
  152. package/dist/gen-B5kvbAoZ.js +58 -0
  153. package/dist/gen-DWCYcMQM.js +827 -0
  154. package/dist/generate-CRVV5V7l.js +165 -0
  155. package/dist/generate-CezbCaTI.js +275 -0
  156. package/dist/{grantee-hxrUsbpM.js → grantee-Dke-9rd1.js} +4 -4
  157. package/dist/{grantee-conditions-CcXf8hMM.js → grantee-conditions-BrYPe-w0.js} +12 -12
  158. package/dist/{grantee-match-DbENBHLB.js → grantee-match-7RZZvoT_.js} +12 -6
  159. package/dist/{graph-sql-B-dsAT6j.d.ts → graph-sql-BwFC4Qjs.d.ts} +3 -3
  160. package/dist/h3/index.d.ts +10 -7
  161. package/dist/h3/index.js +3 -3
  162. package/dist/headers-B9jQxjtW.d.ts +10 -0
  163. package/dist/{helpers-CBYVMjwh.js → helpers-DJicaG6f.js} +40 -28
  164. package/dist/hono/index.d.ts +16 -10
  165. package/dist/hono/index.js +5 -5
  166. package/dist/{hosted-BPk51-kX.d.ts → hosted-Cmtxogd0.d.ts} +44 -20
  167. package/dist/{hosted-B46-5LYE.js → hosted-D0IzESu_.js} +68 -25
  168. package/dist/{http-ujKm0JXE.js → http-DH94eh6f.js} +4 -4
  169. package/dist/{http-BBoKefeS.js → http-DKLXrFGk.js} +13 -7
  170. package/dist/{http-DXJ37RDA.js → http-DOL33cO0.js} +11 -8
  171. package/dist/{import-wyqXUyjm.js → import-CST57XLp.js} +64 -51
  172. package/dist/{import-CNV7I2Dw.js → import-_YqhhwvX.js} +6 -6
  173. package/dist/{index-B8YjkEZ4.d.ts → index-B8s204GL.d.ts} +64 -52
  174. package/dist/{index-Ch8LpNDp.d.ts → index-BT5-1VsX.d.ts} +64 -25
  175. package/dist/{index-DuWxc5dt.d.ts → index-BW_0MZOS.d.ts} +388 -149
  176. package/dist/index-BhxEUY9J.d.ts +233 -0
  177. package/dist/index-Bv5iXPqU.d.ts +210 -0
  178. package/dist/{index-C-6Letxg.d.ts → index-_9UiQIkt.d.ts} +164 -77
  179. package/dist/index.d.ts +22 -21
  180. package/dist/index.js +35 -33
  181. package/dist/{init-GG6Jh8HD.js → init-Dz_PsHdv.js} +5 -5
  182. package/dist/{init-DeMYsnYJ.js → init-cE3A8aXq.js} +10 -10
  183. package/dist/{instance-options-BsVpyGIk.d.ts → instance-options-B0KXqTrJ.d.ts} +4 -4
  184. package/dist/{instance-snapshot-BOEhcqIF.js → instance-snapshot--JWiTrvO.js} +81 -56
  185. package/dist/{instant-BluqDvyS.js → instant-DK3Iz83e.js} +2 -2
  186. package/dist/{internals-Dk8A8fX3.js → internals-B3IgHrvo.js} +6 -3
  187. package/dist/{json-schema-B-vAwlde.js → json-schema-BPGa-RjQ.js} +9 -3
  188. package/dist/{json-schema-CN_iLFH2.d.ts → json-schema-BfXgnRNT.d.ts} +4 -4
  189. package/dist/jwt/index.d.ts +125 -17
  190. package/dist/jwt/index.js +157 -16
  191. package/dist/{kernel-D0NVb1cK.js → kernel-C-fYydV2.js} +151 -16
  192. package/dist/kysely/index.d.ts +49 -33
  193. package/dist/kysely/index.js +9 -7
  194. package/dist/{live-options-BaAFCIiu.js → live-options-BBIT68le.js} +2 -2
  195. package/dist/{load-DFS_ry0W.js → load-DjSU5KXO.js} +6 -6
  196. package/dist/load-Dm0pSYH3.js +1 -0
  197. package/dist/{login-Dfe5RlYk.js → login-D-8tMqzo.js} +4 -4
  198. package/dist/{login-imhuwvn3.js → login-DplQ0s84.js} +18 -15
  199. package/dist/{manifest-dvL2XEe1.js → manifest-Cgiq5oqc.js} +67 -37
  200. package/dist/{manifest-mx5ghSFM.d.ts → manifest-CzzWtnAb.d.ts} +144 -28
  201. package/dist/{manifest-schema-C0kK_8V0.js → manifest-schema-2r37XRhD.js} +80 -9
  202. package/dist/{manifest-C8IzW3Do.js → manifest-wg8WkkpD.js} +11 -7
  203. package/dist/{map-claims-D3oRmTk0.js → map-claims-BNWCJtmP.js} +27 -18
  204. package/dist/{map-error-Dr70oA_3.js → map-error-pasIGqMT.js} +7 -7
  205. package/dist/{markers-C6joDI3o.js → markers-BeHZ4x9Q.js} +20 -5
  206. package/dist/mcp/index.d.ts +154 -47
  207. package/dist/mcp/index.js +200 -41
  208. package/dist/{migrate-CIq4-E9F.js → migrate-B7oP_sQU.js} +102 -31
  209. package/dist/module-values-BJTBdo6y.js +107 -0
  210. package/dist/nest/index.d.ts +23 -13
  211. package/dist/nest/index.js +18 -11
  212. package/dist/next/client.d.ts +5 -2
  213. package/dist/next/client.js +1 -1
  214. package/dist/next/index.d.ts +72 -26
  215. package/dist/next/index.js +110 -6
  216. package/dist/next/plugin.d.ts +1 -1
  217. package/dist/next/plugin.js +4 -3
  218. package/dist/node/index.d.ts +5 -5
  219. package/dist/node/index.js +3 -3
  220. package/dist/{ocsf-BFp-PZZA.js → ocsf-TcjRBgIs.js} +15 -5
  221. package/dist/openai/index.d.ts +13 -7
  222. package/dist/openai/index.js +3 -3
  223. package/dist/openapi/index.d.ts +61 -30
  224. package/dist/openapi/index.js +2 -2
  225. package/dist/{openapi-B7HZ0f-r.js → openapi-BOsx0xn9.js} +34 -24
  226. package/dist/{openapi-C4dDTVEd.js → openapi-CBZUhA3l.js} +4 -5
  227. package/dist/{openapi-DD1SV-5-.js → openapi-VeHHU2l9.js} +23 -14
  228. package/dist/{operation-findings-DgATDpvh.js → operation-findings-CXtFzlKJ.js} +3 -3
  229. package/dist/{operations-X0VM7F3m.d.ts → operations-Ci_rT6Uo.d.ts} +10 -7
  230. package/dist/orpc/index.d.ts +68 -14
  231. package/dist/orpc/index.js +62 -13
  232. package/dist/otel/index.d.ts +7 -4
  233. package/dist/otel/index.js +8 -5
  234. package/dist/output-files-BHOIlrfD.js +1 -0
  235. package/dist/{output-files-DhRBBE4n.js → output-files-BIQ_9pIs.js} +29 -17
  236. package/dist/{output-1im__-08.js → output-tkju6U7R.js} +15 -7
  237. package/dist/ownership-B4L59T9u.js +1054 -0
  238. package/dist/parse-1_ZheTW5.js +151 -0
  239. package/dist/parse-local-manifest-BQhTlLQS.d.ts +10 -0
  240. package/dist/parse-local-manifest-DEiCF4ah.js +69 -0
  241. package/dist/{parse-request-D_tUXG6M.js → parse-request-CwCZpyjV.js} +7 -8
  242. package/dist/{paths-DXSHAMIa.js → paths-CUvURzHp.js} +4 -1
  243. package/dist/pdp/index.d.ts +9 -6
  244. package/dist/pdp/index.js +23 -12
  245. package/dist/{peer-CRvC7Hg1.js → peer-C9LvBCcb.js} +1 -1
  246. package/dist/peer-Q0fuZ6hx.js +982 -0
  247. package/dist/{permdock-SV8ZINYT.d.ts → permdock-B6Sf4T_S.d.ts} +270 -117
  248. package/dist/{permdock-BrAscV28.js → permdock-L-_I_3zP.js} +226 -74
  249. package/dist/{permission-registry-Br4jSNVi.js → permission-registry-B8tYxPIt.js} +24 -11
  250. package/dist/{vocabulary-CN8H_9UF.d.ts → permission-types-CAY-GU7e.d.ts} +130 -104
  251. package/dist/{permissions-u_KHg-sN.d.ts → permissions-Bx6UI8n1.d.ts} +6 -2
  252. package/dist/{permissions-B-wCP5ZC.js → permissions-CVXmRILq.js} +3 -3
  253. package/dist/{permissions-BPxlwVhN.js → permissions-mDcTWoBs.js} +8 -5
  254. package/dist/pg-DQTqRrou.js +60 -0
  255. package/dist/{pins-8ILTftC9.js → pins-nqarl3oE.js} +4 -1
  256. package/dist/pkce-CnmmYqUZ.js +21 -0
  257. package/dist/{policy-77FSAq_V.d.ts → policy-Ba4bDhaj.d.ts} +5 -5
  258. package/dist/{policy-DbM5YYtV.js → policy-CLQ8__GF.js} +49 -20
  259. package/dist/{policy-from-catalog-C2gMa2Dj.js → policy-from-catalog-CJgOm6aq.js} +28 -25
  260. package/dist/{policy-module-CTlwMvLI.js → policy-module-xym9fnSq.js} +12 -6
  261. package/dist/{policy-types-n0uRTsW8.d.ts → policy-types-CCiDRZBH.d.ts} +802 -334
  262. package/dist/postgres-store-DcyrPS-r.js +1028 -0
  263. package/dist/{postgrest-DAjUkF9w.js → postgrest-D4nuPXIQ.js} +88 -36
  264. package/dist/{postgrest-CEgRbefE.d.ts → postgrest-DicQyqm-.d.ts} +60 -30
  265. package/dist/powersync-DHNQX06W.js +1117 -0
  266. package/dist/{powersync-DISt6SUe.js → powersync-DXqGuq9A.js} +3 -3
  267. package/dist/powersync-Djf-zCpB.js +1 -0
  268. package/dist/preset/index.d.ts +38 -28
  269. package/dist/preset/index.js +1 -1
  270. package/dist/{preset-DZdU0tne.js → preset-BjLzeFpp.js} +58 -34
  271. package/dist/{principal-source-pHneukrx.js → principal-source-C6N60GtM.js} +10 -10
  272. package/dist/prisma/index.d.ts +69 -41
  273. package/dist/prisma/index.js +21 -17
  274. package/dist/{problem-CTviyH5L.js → problem-BVLHFK33.js} +41 -21
  275. package/dist/{problem-details-cn5X30gZ.js → problem-details-CLDgX1KV.js} +13 -4
  276. package/dist/{problem-types-CJ2325SZ.js → problem-types-DSsRfONQ.js} +4 -1
  277. package/dist/prompt-BYc6drYW.js +938 -0
  278. package/dist/protected-action-BE7N_Luo.d.ts +58 -0
  279. package/dist/protected-action-DUYqb-Ma.js +80 -0
  280. package/dist/{protected-BlF0F6wL.js → protected-action-QEA8arRB.js} +48 -14
  281. package/dist/{provider-B1al-AZz.js → provider-C55OGXJ7.js} +5 -4
  282. package/dist/{provider-DILL9oPK.d.ts → provider-DXfq5c2d.d.ts} +1 -1
  283. package/dist/{provider-store-DM6DNcta.js → provider-store-CaTv00tr.js} +13 -9
  284. package/dist/pull-B65ndFNZ.js +1 -0
  285. package/dist/{pull-jEwbf80J.js → pull-CwpGaQSi.js} +12 -11
  286. package/dist/{push-BD4f15co.js → push-DgssgzkK.js} +62 -16
  287. package/dist/{pushed-policy-DCW91KQU.d.ts → pushed-policy-D1Eoi6NM.d.ts} +12 -6
  288. package/dist/{pushed-policy-CqtxkV4G.js → pushed-policy-ITwYKvvH.js} +10 -7
  289. package/dist/react/index.d.ts +9 -8
  290. package/dist/react/index.js +7 -6
  291. package/dist/react/provider-client.d.ts +1 -1
  292. package/dist/react/provider-client.js +1 -1
  293. package/dist/react-native/index.d.ts +104 -51
  294. package/dist/react-native/index.js +394 -67
  295. package/dist/{refs-K6eWUJ0H.js → refs-C8BKxc_e.js} +17 -3
  296. package/dist/{relation-walk-3OyDBF0S.js → relation-walk-DK_gkgL7.js} +12 -9
  297. package/dist/{relations-BnCWnJGI.js → relations-BxvRvDHK.js} +3 -3
  298. package/dist/{replay-Du1tJjG7.js → replay-BU09ad4L.js} +1 -1
  299. package/dist/{replay-Bkm-Ne4l.d.ts → replay-DNftIb_R.d.ts} +1 -1
  300. package/dist/{request-context-C81Pip9n.js → request-context-C25ilqiJ.js} +8 -8
  301. package/dist/{request-schema-CMreVuFM.js → request-schema-DhX7UbfQ.js} +4 -1
  302. package/dist/resend/index.d.ts +34 -28
  303. package/dist/resend/index.js +29 -29
  304. package/dist/resolve-names-CF7xXLSG.js +201 -0
  305. package/dist/{resource-relations-D_jw0BFx.js → resource-relations-8-pCScBO.js} +18 -9
  306. package/dist/{retire-CALjIQ7e.js → retire-B2xffQgY.js} +15 -6
  307. package/dist/{revocations-R07lFBnX.d.ts → revocations-BaUXHM1N.d.ts} +6 -3
  308. package/dist/{rls-DTvYd-lI.js → rls-CNEH2_BG.js} +743 -624
  309. package/dist/{rls-BAqP4q66.js → rls-Ca8Jz9b5.js} +41 -10
  310. package/dist/{role-builder-uWQZ4v84.js → role-builder-BK2-T6oD.js} +41 -20
  311. package/dist/rolldown-runtime-C3I60J0A.js +4 -0
  312. package/dist/{row-conditions-h2HythXQ.js → row-conditions-BAGHOmVq.js} +4 -3
  313. package/dist/{rpc-VSl5ylaQ.js → rpc-fNqAIqQk.js} +8 -5
  314. package/dist/{run-CUKuz0A8.js → run-Bz26OTfO.js} +26 -23
  315. package/dist/run-D-JE8QWb.js +1 -0
  316. package/dist/{run-ticket-DocwaTKn.d.ts → run-ticket-DbWI9f99.d.ts} +33 -13
  317. package/dist/{run-ticket-sxFI6LAk.js → run-ticket-Dv7aF7Dk.js} +29 -8
  318. package/dist/{runtime-D3VTAiCA.js → runtime-BGnrOkVH.js} +5 -2
  319. package/dist/{saas-BCXRuXca.js → saas-iWnUdwCt.js} +42 -29
  320. package/dist/{scan-qvOe0hS3.js → scan-DCj1KQfn.js} +1 -1
  321. package/dist/{schema-B4v0Qrac.js → schema-BOJjqlWL.js} +3 -2
  322. package/dist/{schema-yLuBWWsh.js → schema-Dst9I9Gs.js} +9 -5
  323. package/dist/scim/index.d.ts +3 -2
  324. package/dist/scim/index.js +3 -1
  325. package/dist/scim-D8YupzX1.js +44 -0
  326. package/dist/{scim-ClHtpyV3.js → scim-jJBJ7g1Z.js} +35 -551
  327. package/dist/{scopes-CUPSjo9q.js → scopes-0Up_UA2t.js} +42 -24
  328. package/dist/server/index.d.ts +12 -12
  329. package/dist/server/index.js +13 -13
  330. package/dist/{server-C3WXAHZj.js → server-3cVlTLde.js} +31 -31
  331. package/dist/{server-types-CKQiCnON.d.ts → server-types-CIlpSA2G.d.ts} +81 -46
  332. package/dist/{sha256-D9ihqmvd.js → sha256-C-4WXKIu.js} +4 -3
  333. package/dist/{shared-BAK8t4Uo.js → shared-Afctg7B0.js} +100 -61
  334. package/dist/{shared-DvvFzYXe.js → shared-l0oGGetg.js} +9 -6
  335. package/dist/{signals-D3NcTKUS.js → signals-B1vHR3fN.js} +2 -2
  336. package/dist/{sink-BEZSlMS_.js → sink-D9_vh372.js} +32 -21
  337. package/dist/{sink-4-eQIRWf.d.ts → sink-Dvi7YZG_.d.ts} +44 -20
  338. package/dist/{skills-B6lVdD_W.js → skills-Bjj7BNEQ.js} +16 -12
  339. package/dist/{skills-CTYwkl44.js → skills-OuoMR49L.js} +3 -3
  340. package/dist/{snapshot-B7DkYg6g.js → snapshot-CXlzi69u.js} +21 -18
  341. package/dist/{snapshot-cache-PXI-vGpY.d.ts → snapshot-cache-BAXZ0KOp.d.ts} +21 -12
  342. package/dist/{snapshot-cache-DZHQ0KF6.js → snapshot-cache-Bx_4xC_6.js} +14 -11
  343. package/dist/solid/index.d.ts +55 -15
  344. package/dist/solid/index.js +46 -12
  345. package/dist/solid-start/index.d.ts +30 -12
  346. package/dist/solid-start/index.js +3 -3
  347. package/dist/sources-B1Os_YUu.d.ts +312 -0
  348. package/dist/{sources-vs5dGOsS.js → sources-CMAM-2A3.js} +156 -51
  349. package/dist/sources-qoIJQrJv.js +11 -0
  350. package/dist/{sql-B5T1IL83.js → sql-0WjxIp9g.js} +32 -7
  351. package/dist/sql-B80DFdpG.d.ts +10 -0
  352. package/dist/{src-Cu2m75uo.js → src-DZ0l9UAg.js} +305 -123
  353. package/dist/ssf/index.d.ts +4 -4
  354. package/dist/ssf/index.js +665 -224
  355. package/dist/{standard-webhooks-rbD7LS6h.d.ts → standard-webhooks-C-qX4mJW.d.ts} +7 -7
  356. package/dist/{standard-webhooks-DgFFD-Ga.js → standard-webhooks-D67a3nzk.js} +5 -5
  357. package/dist/{store-BDutJkDL.js → store-RNFvaJJv.js} +43 -29
  358. package/dist/{store-options-u0ycEe8s.js → store-options-Dil3lraY.js} +105 -15
  359. package/dist/stores-C0Kta5Ft.d.ts +59 -0
  360. package/dist/{stores-CGhM8BN_.js → stores-DxZWDXCs.js} +42 -5
  361. package/dist/{stream-CvdUS9V1.js → stream-CDvF0sJm.js} +5 -5
  362. package/dist/{stream-CeCMJmBB.d.ts → stream-ltuLdtTG.d.ts} +4 -3
  363. package/dist/{style-DiKWnAvn.js → style-DblOtSga.js} +1 -1
  364. package/dist/{style-Uao76wO7.js → style-Ds11Hmk4.js} +3 -1
  365. package/dist/{subject-settings-CkEwrw3W.js → subject-settings-8sH5HlII.js} +8 -4
  366. package/dist/{row-check-Dt35czLy.d.ts → subject-settings-vNW8qmUH.d.ts} +30 -18
  367. package/dist/supabase/index.d.ts +48 -37
  368. package/dist/supabase/index.js +6 -6
  369. package/dist/supabase/middleware.d.ts +19 -13
  370. package/dist/supabase/middleware.js +7 -7
  371. package/dist/{supabase-x5JtR2tc.js → supabase-BL1qJzzY.js} +82 -36
  372. package/dist/{supabase-By_frk3U.js → supabase-D39cuGPt.js} +3 -3
  373. package/dist/{supabase-hook-BMoEhZir.js → supabase-hook-B-yXnzUl.js} +1206 -286
  374. package/dist/supabase-hook-H-MtE9Wd.js +1 -0
  375. package/dist/supautils-BU-pAEpf.js +808 -0
  376. package/dist/{support-session-C6xJQYut.js → support-session-BsmPCbz8.js} +22 -22
  377. package/dist/svelte/PermissionBoundary.svelte +2 -6
  378. package/dist/svelte/Protected.svelte +2 -8
  379. package/dist/svelte/index.d.ts +9 -8
  380. package/dist/svelte/index.js +10 -9
  381. package/dist/svelte/runtime.d.ts +4 -4
  382. package/dist/svelte/runtime.js +4 -4
  383. package/dist/svelte/source.d.ts +9 -8
  384. package/dist/svelte/source.js +6 -5
  385. package/dist/sveltekit/index.d.ts +30 -12
  386. package/dist/sveltekit/index.js +2 -2
  387. package/dist/{ownership-DqaJNJPn.js → tables-DeMNEy6V.js} +1025 -1085
  388. package/dist/tanstack-start/index.d.ts +22 -10
  389. package/dist/tanstack-start/index.js +2 -2
  390. package/dist/{target-Bw37TFNA.js → target-qdEAnnhU.js} +6 -6
  391. package/dist/{tenancy-DN2QAx9z.js → tenancy-5d1zVBvK.js} +36 -21
  392. package/dist/terminal/index.d.ts +23 -14
  393. package/dist/terminal/index.js +17 -20
  394. package/dist/testing/core.d.ts +2 -0
  395. package/dist/testing/core.js +1 -0
  396. package/dist/testing/fixtures/index.d.ts +15 -0
  397. package/dist/testing/fixtures/index.js +831 -0
  398. package/dist/testing/index.d.ts +235 -285
  399. package/dist/testing/index.js +502 -927
  400. package/dist/testing/saas/index.d.ts +43 -27
  401. package/dist/testing/saas/index.js +2 -2
  402. package/dist/testing/saas/permissions.d.ts +1 -1
  403. package/dist/testing/saas/permissions.js +1 -1
  404. package/dist/text-diff-BtfSmdUo.js +444 -0
  405. package/dist/{thenable-yD7SabqG.js → thenable-BNSM1U2R.js} +12 -3
  406. package/dist/{time-DkWjdL98.js → time-nR1JesoH.js} +32 -8
  407. package/dist/{token-BLZcjQgt.js → token-DC9EQiet.js} +15 -13
  408. package/dist/{tool-refusal-CMLN5xhn.js → tool-refusal-Qpjs_W6M.js} +5 -2
  409. package/dist/trpc/index.d.ts +11 -8
  410. package/dist/trpc/index.js +15 -9
  411. package/dist/type-helpers-BIPIDa6a.d.ts +50 -0
  412. package/dist/{types-8RnwUAvE.d.ts → types-AMJ5qoU_.d.ts} +49 -30
  413. package/dist/{types-C_IiEs64.d.ts → types-B2mlGuhW.d.ts} +14 -11
  414. package/dist/{types-_WhUP7ud.d.ts → types-B4uqKDnp.d.ts} +25 -12
  415. package/dist/{types-CX7AvRqP.d.ts → types-BJk0BKRX.d.ts} +24 -14
  416. package/dist/types-B_d_g7T3.d.ts +262 -0
  417. package/dist/{types-BdtSDbBO.d.ts → types-B_tiYvk8.d.ts} +59 -32
  418. package/dist/{types-DqzsnlSA.js → types-CcUXciSj.js} +17 -5
  419. package/dist/{types-Dfv0aTER.d.ts → types-DC1Loeh3.d.ts} +449 -168
  420. package/dist/{types-lUlG3TVd.d.ts → types-DHKPcqzr.d.ts} +2 -2
  421. package/dist/{types-C791HFu9.d.ts → types-DjuMBNF9.d.ts} +68 -32
  422. package/dist/{types-C863OQiR.d.ts → types-HzqOtvsJ.d.ts} +23 -12
  423. package/dist/{types-Druu9wLM.d.ts → types-ZzRGoViD.d.ts} +77 -24
  424. package/dist/{types-BzzaHxt3.d.ts → types-ah9NlU6j.d.ts} +137 -48
  425. package/dist/{types-CVZSNocA.d.ts → types-djVzLlIV.d.ts} +31 -14
  426. package/dist/{types-BtHtcKMr.d.ts → types-yUuPR5Hy.d.ts} +47 -24
  427. package/dist/unplugin/index.d.ts +1 -1
  428. package/dist/unplugin/index.js +2 -2
  429. package/dist/usage-CYeXek0p.js +1 -0
  430. package/dist/{usage-SveTRTyr.js → usage-CxEzS4ey.js} +50 -26
  431. package/dist/{usage-CRVl9V3t.js → usage-ab0PdusX.js} +2 -2
  432. package/dist/{validation-error-ZfUHbKvf.js → validation-error-CYg2kJEw.js} +4 -1
  433. package/dist/{validation-error-DHUrAkku.d.ts → validation-error-YGg2mXyH.d.ts} +5 -2
  434. package/dist/{validity-CZ7-y0Cj.js → validity-Buc7w8E9.js} +13 -10
  435. package/dist/{verifier-DHuh-okk.js → verifier-DUV6pLqZ.js} +10 -4
  436. package/dist/{verify-BaAKx7t6.js → verify-DZeqoWu5.js} +417 -489
  437. package/dist/version-DMQ0dnN5.js +48 -0
  438. package/dist/version-DizhmD72.js +1 -0
  439. package/dist/{views-DTIIO-oQ.js → views-CD8wuL4Z.js} +19 -6
  440. package/dist/vocabulary-DEJ5j4mR.d.ts +70 -0
  441. package/dist/vue/index.d.ts +71 -21
  442. package/dist/vue/index.js +50 -13
  443. package/dist/{waiter-B986w1oK.d.ts → waiter-Bu9zAlE0.d.ts} +13 -10
  444. package/dist/{waiter-BGUtGYoU.js → waiter-aBLM63_f.js} +1 -1
  445. package/dist/{watch-vea54PRw.js → watch-Cx_WciLE.js} +8 -8
  446. package/dist/watch-DB42U1ib.js +1 -0
  447. package/dist/webmcp/index.d.ts +11 -11
  448. package/dist/webmcp/index.js +8 -5
  449. package/dist/{where-scope-C9vfEphl.js → where-scope-DlCdCM_8.js} +56 -30
  450. package/dist/{who-can-DWBAcaCQ.js → who-can-rI7dCR9Q.js} +23 -20
  451. package/dist/workflow/index.d.ts +3 -3
  452. package/dist/workflow/index.js +13 -13
  453. package/dist/workflow-sdk/index.d.ts +19 -13
  454. package/dist/workflow-sdk/index.js +6 -6
  455. package/dist/{wrap-gX61EtpB.js → wrap-B2wziJI_.js} +11 -5
  456. package/dist/{wrap-C5jC6sKI.d.ts → wrap-DlJE3gS_.d.ts} +8 -5
  457. package/package.json +25 -14
  458. package/schemas/catalog-v1.json +3 -0
  459. package/schemas/cli-result-v1.json +1 -0
  460. package/schemas/cloud-api-v1.json +152 -7
  461. package/schemas/connector-v1.json +8 -0
  462. package/schemas/supabase-claims-v1.json +5 -0
  463. package/schemas/supabase-manifest-v1.json +308 -75
  464. package/skills/permdock/SKILL.md +1 -0
  465. package/skills/permdock-agents/references/runtimes.md +3 -1
  466. package/skills/permdock-approvals/references/stores-and-resume.md +2 -1
  467. package/skills/permdock-credentials/references/tokens-keys-links.md +8 -6
  468. package/skills/permdock-data/references/queries-and-rls.md +6 -2
  469. package/skills/permdock-tenancy/SKILL.md +1 -1
  470. package/skills/permdock-tenancy/references/ownership-and-custom-roles.md +2 -1
  471. package/skills/permdock-wire/references/adapters.md +28 -22
  472. package/skills/permdock-wire/references/policy-options.md +1 -0
  473. package/dist/catalog-BoWglDPI.js +0 -1
  474. package/dist/collect-D7eQDffE.js +0 -1
  475. package/dist/config-bs_kZeYq.js +0 -162
  476. package/dist/denied-text-CiFQNuMV.d.ts +0 -19
  477. package/dist/fields-OYG_JR78.js +0 -203
  478. package/dist/generate-CNU1pIOo.js +0 -171
  479. package/dist/headers-BIjfMaoC.js +0 -8
  480. package/dist/headers-CIbDTUTK.d.ts +0 -7
  481. package/dist/index-Cp_ilKi1.d.ts +0 -91
  482. package/dist/index-CueFnxQc.d.ts +0 -110
  483. package/dist/load-BxEtwlBS.js +0 -1
  484. package/dist/output-files-B3IZJksC.js +0 -1
  485. package/dist/package-root-Dk_ETdkp.js +0 -31
  486. package/dist/parse-Cxq0WxrA.js +0 -53
  487. package/dist/parse-local-manifest-5ODwqu8b.d.ts +0 -10
  488. package/dist/parse-local-manifest-CVSs6J9I.js +0 -34
  489. package/dist/peer-C8swJ2GH.js +0 -50
  490. package/dist/policies-DP2nqRoF.js +0 -295
  491. package/dist/powersync-BkWWCS1K.js +0 -538
  492. package/dist/powersync-MMJ2mI2q.js +0 -1
  493. package/dist/problem-details-B45V5d2b.d.ts +0 -7
  494. package/dist/prompt-DfZLJLIn.js +0 -32
  495. package/dist/protected-RFGpakN9.d.ts +0 -38
  496. package/dist/pull-CvpINcMK.js +0 -1
  497. package/dist/run-DDwUaJDU.js +0 -1
  498. package/dist/sources-Xc4zkszI.d.ts +0 -190
  499. package/dist/stores-BUGNbJTt.d.ts +0 -30
  500. package/dist/supabase-hook-CCw5m35C.js +0 -1
  501. package/dist/supautils-B6HYL9eL.js +0 -91
  502. package/dist/text-diff-BQcUapOb.js +0 -15
  503. package/dist/types-B3_ooZgZ.d.ts +0 -172
  504. package/dist/usage-CZpSQP6L.js +0 -1
  505. package/dist/version-CEbQ5UeM.js +0 -18
  506. package/dist/version-DXkYUISx.js +0 -1
  507. package/dist/watch--ovlPOc0.js +0 -1
@@ -1,30 +1,33 @@
1
1
  import { t as compact } from "./compact-CAf557ZB.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";
8
- import { t as policyRowConditionKeys } from "./row-conditions-h2HythXQ.js";
9
- import { c as requirePolicy } from "./load-DFS_ry0W.js";
10
- import "./budget-NbEtbJC_.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";
2
+ import { c as listPermissions, u as renamedKeys } from "./permission-registry-B8tYxPIt.js";
3
+ import { i as dateAt } from "./time-nR1JesoH.js";
4
+ import { p as scopeField } from "./tenancy-5d1zVBvK.js";
5
+ import { a as describeError, i as configError, o as exitCodeOfError, u as usageResult } from "./errors-Vpu6rKHl.js";
6
+ import { t as policyRowConditionKeys } from "./row-conditions-BAGHOmVq.js";
7
+ import { o as sqlFiles } from "./files-BSjrtIHw.js";
8
+ import { c as requirePolicy } from "./load-DjSU5KXO.js";
9
+ import { i as qualifySqlTable, r as escapeSqlIdent } from "./sql-0WjxIp9g.js";
10
+ import "./sources-CMAM-2A3.js";
11
+ import { t as policyFromCatalog } from "./policy-from-catalog-CJgOm6aq.js";
14
12
  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";
22
- import { a as splitUndiffed } from "./deciding-columns-Sq12mEA6.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";
27
- import { t as supautilsRejections } from "./supautils-B6HYL9eL.js";
13
+ import { i as INDEXES_MARKER, n as GRANTS_MARKER, o as SEEDS_MARKER, t as BACKFILL_MARKER } from "./markers-BeHZ4x9Q.js";
14
+ import { a as nonEmptyOr } from "./output-tkju6U7R.js";
15
+ import { i as requirePeer, n as packageManagerOf, t as addLine } from "./peer-Q0fuZ6hx.js";
16
+ import { t as POLICY_NOT_PUBLISHED } from "./problem-types-DSsRfONQ.js";
17
+ import { t as fetchProjectPolicy } from "./pull-CwpGaQSi.js";
18
+ import { n as MIGRATION_DIRS } from "./constants-DcJABbLW.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-BIQ_9pIs.js";
20
+ import { d as schemasRefusal, f as declarativeSql, l as isSupabaseSchemas, o as supabaseHookSql, s as approvalsSql, t as grantsLabel, u as resolveSchemas, v as parseRbacAuthorize, y as rbacScaffold } from "./supabase-hook-B-yXnzUl.js";
21
+ import { A as quoteQualified, D as qualified$1, I as scopeTypeOf, O as quoteIdent$1, _ as subjectClaimJsonSql, c as SESSION_LIVE_HELPER, g as sessionLiveHelperSql, j as quoteTable, k as quoteLiteral, r as HELPERS, t as CUSTOM_ROLES, y as subjectIdSql } from "./shared-Afctg7B0.js";
22
+ import { C as accessSql, G as byKey, T as seedSql, Z as graphHelper, ct as memberIdsHelper, ft as permittedIdsHelper, ht as scopeTable, mt as scopeSources, r as tableNames, s as graphSql, w as helpersSql, x as permittedByPermissionHelper, y as permissionHelpersSql } from "./tables-DeMNEy6V.js";
23
+ import { c as breakGlassKey, i as assignmentSql, l as collectGrants, n as ownershipSql, s as breakGlassHolder } from "./ownership-B4L59T9u.js";
24
+ import { a as splitUndiffed } from "./deciding-columns-NB4wcyBf.js";
25
+ import { t as connectPgPool } from "./pg-DQTqRrou.js";
26
+ import { n as permissionHoldersSql } from "./resolve-names-CF7xXLSG.js";
27
+ import { a as fieldViewsSql, c as ACTOR_MAY_WRITE, f as readOnlyActorKinds, i as fieldViews, l as actorMayWriteSql, n as columnGrantSql, o as rowBranches, p as readOnlyActorPolicies, r as columnRevokeSql, u as assemblePolicies } from "./fields-D0vUI6dq.js";
28
+ import { _ as indexTargets, a as verifyResult, b as readTableIndexFacts, c as callsHelper, g as INDEX_COLUMNS_SQL, h as COLUMNS_SQL, i as verifyIgnores, l as helperCallKeys, m as tableFor, n as runRlsVerify, o as HELPER_TABLES, p as compileGrants, r as rlsFinding, v as indexesSql, y as pruneIndexTargets } from "./verify-DZeqoWu5.js";
29
+ import { a as generateOptions, c as buildContext, i as rowHelpersSql, l as DROPPED_LIST, n as inheritTargets, o as isRefusal, r as levelKeys, s as splitsPart, t as supautilsRejections, u as rebuildPlan } from "./supautils-BU-pAEpf.js";
30
+ import { readFileSync } from "node:fs";
28
31
  import { basename, delimiter, join } from "node:path";
29
32
  import { spawn } from "node:child_process";
30
33
  //#region src/cli/rls/advisors.ts
@@ -136,7 +139,7 @@ async function runRlsAdvisors(input) {
136
139
  const message = [text(error, "message"), text(error, "suggestion")].filter((part) => part !== "").join(" ");
137
140
  return {
138
141
  code: 2,
139
- output: `supabase db advisors failed: ${message === "" ? result.stderr.trim() || `exit ${String(result.status)}` : message}`
142
+ output: `supabase db advisors failed: ${message === "" ? nonEmptyOr(result.stderr.trim(), `exit ${String(result.status)}`) : message}`
140
143
  };
141
144
  }
142
145
  const failing = findings.filter((finding) => finding.level === "WARN" || finding.level === "ERROR");
@@ -159,142 +162,164 @@ async function runRlsAdvisors(input) {
159
162
  };
160
163
  }
161
164
  //#endregion
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
- }
168
- /** `rls.customRoleWrites.requires` as permission keys, or why it names none. */
169
- function writeRequirement(policy, requires) {
170
- if (requires === void 0) return {};
171
- const leaves = listPermissions(policy.permissions);
172
- const keys = requires === "manageRoles" ? leaves.filter((leaf) => leaf.meta.manageRoles === true).map((leaf) => leaf.key) : [...requires];
173
- if (keys.length === 0) return { error: requires === "manageRoles" ? "rls.customRoleWrites.requires is 'manageRoles', but no permission declares meta.manageRoles" : "rls.customRoleWrites.requires names no permission" };
174
- const known = new Set(leaves.map((leaf) => leaf.key));
175
- const unknown = keys.filter((key) => !known.has(key));
176
- if (unknown.length > 0) return { error: `rls.customRoleWrites.requires names undeclared permissions: ${unknown.join(", ")}` };
177
- return { keys: [...new Set(keys)].toSorted() };
178
- }
179
- /** Declared role names, and those a tenant admin may compose into custom roles. */
180
- function customRoleNames(policy, requires) {
181
- const declared = [...roleNames(policy)].toSorted();
182
- const assignable = declared.filter((name) => policy.rolesByName.get(name)?.assignable ?? listRoles(policy.vocabulary.roles).some((leaf) => leaf.key === name && leaf.assignable));
183
- const renamed = renamedKeys(policy.vocabulary.permissions);
184
- const leaves = listPermissions(policy.permissions);
185
- const manage = leaves.filter((leaf) => leaf.meta.manageRoles === true).map((leaf) => leaf.key);
186
- return {
187
- declared,
188
- assignable,
189
- ...renamed.size === 0 ? {} : { renamed: Object.fromEntries(renamed) },
190
- permissions: leaves.map((leaf) => leaf.key).toSorted(),
191
- ...manage.length === 0 ? {} : { manage: manage.toSorted() },
192
- ...requires === void 0 ? {} : { requires },
193
- ...policy.levels === void 0 ? {} : { levels: true }
194
- };
165
+ //#region src/cli/rls/bindings.ts
166
+ /**
167
+ * The table each resource with row policies binds to. With `helpersOnly`, a
168
+ * resource `rls.tables` does not name has no table and no binding.
169
+ */
170
+ function tableBindings(resources, tables, helpersOnly) {
171
+ const out = [];
172
+ for (const resource of [...new Set(resources)].toSorted()) {
173
+ const named = tables !== void 0 && Object.hasOwn(tables, resource);
174
+ if (helpersOnly && !named) continue;
175
+ out.push({
176
+ resource,
177
+ table: qualifySqlTable(tableFor(resource, tables)),
178
+ inferred: !named
179
+ });
180
+ }
181
+ return out;
195
182
  }
196
- function skippedOwnershipTriggers(triggers, scopes) {
197
- if (triggers === false) return "all";
198
- return Object.keys(triggers).map((key) => {
199
- const name = resolveScope(scopes, key);
200
- if (name === void 0) throw configError(`PermDock CLI: rls.ownershipTriggers.${key} names a scope the policy does not declare`);
201
- return name;
202
- });
183
+ /**
184
+ * The generate notes for `bindings`: the inferred ones, and where to see them
185
+ * all.
186
+ */
187
+ function bindingNotes(bindings, withDatabase) {
188
+ const inferred = bindings.filter((binding) => binding.inferred);
189
+ if (inferred.length === 0) return [];
190
+ return [`table bindings (no rls.tables entry, so the table is named after the resource): ${inferred.map((binding) => `${binding.resource} -> ${binding.table}`).join(", ")}${withDatabase ? "" : "; run permdock rls explain, or generate with --db-url-stdin, to check each table exists"}`];
203
191
  }
204
- /** The SQL context every emitter reads, from the flags, `rls` config and the policy. */
205
- function buildContext(input, policy, options) {
206
- const rls = input.config.rls;
207
- const memberships = parseMembershipsFlag(input.memberships) ?? rls?.memberships;
208
- if (input.rbac && input.dialect !== "supabase") return { refusal: `rls generate --rbac supabase needs --dialect supabase (got ${input.dialect})` };
209
- const schema = input.rbacSchema ?? rls?.schema ?? rls?.rbac?.schema ?? "permdock";
210
- const authorize = resolveAuthorize(input.config, {
211
- authorize: input.authorize,
212
- rbac: input.rbac,
213
- memberships
214
- });
215
- const memberSources = rls?.membershipSources ?? input.config.supabase?.hook?.memberships;
216
- const sources = authorize === "database" ? memberSources : void 0;
217
- const scopes = scopeList(policy.scopes);
218
- const suspension = checkSuspension(rls?.suspension, scopes);
219
- const hookRoles = input.config.supabase?.hook?.roles;
220
- const roles = rls?.roles ?? (hookRoles === false ? void 0 : hookRoles);
221
- if (roles !== void 0 && input.rbac) return { refusal: "rls generate --rbac supabase creates its own user_roles (user_id, role app_role); drop rls.roles (or supabase.hook.roles) or the --rbac flag" };
222
- const requires = writeRequirement(policy, rls?.customRoleWrites?.requires);
223
- if (requires.error !== void 0) return { refusal: requires.error };
224
- const readers = rls?.trustedReaders;
225
- if (readers !== void 0 && (!Array.isArray(readers) || !readers.every((name) => typeof name === "string" && name !== ""))) return { refusal: "rls.trustedReaders must list Postgres role names, such as ['service_role']" };
226
- const ownership = ownershipRules(policy, scopes);
227
- const graph = graphPlan(policy);
228
- const apiKeys = apiKeysPlan(rls?.apiKeys, new Set(roleNames(policy)), Object.fromEntries(renamedKeys(policy.vocabulary.permissions)));
229
- return {
230
- ctx: {
231
- dialect: input.dialect,
232
- scopes,
233
- tenantClaim: rls?.tenantClaim ?? "tenant_id",
234
- ...roleMemberScopesOf(rls?.migrate),
235
- ...rls?.roleValues === void 0 ? {} : { roleValues: rls.roleValues },
236
- ...rls?.tenants === "all" ? { tenants: "all" } : {},
237
- gucPrefix: input.gucPrefix ?? rls?.gucPrefix ?? "app",
238
- ...rls?.actions === void 0 ? {} : { actions: rls.actions },
239
- inlineFunctions: input.inlineFunctions || rls?.inlineFunctions === true,
240
- schema,
241
- authorize,
242
- roleClaim: rls?.roleClaim ?? "user_role",
243
- tenantType: input.tenantType ?? rls?.tenantType ?? "uuid",
244
- ...rls?.teamType === void 0 ? {} : { teamType: rls.teamType },
245
- ...rls?.scopeTypes === void 0 ? {} : { scopeTypes: rls.scopeTypes },
246
- ...memberships === void 0 ? {} : { memberships },
247
- ...sources === void 0 || sources.length === 0 ? {} : { sources },
248
- ...memberSources === void 0 || memberSources.length === 0 ? {} : { memberSources },
249
- ...suspension === void 0 ? {} : { suspension },
250
- ...roles === void 0 ? {} : { roles },
251
- ...input.customRoles === true || customRolesOn(rls) ? { customRoles: {
252
- ...customRoleNames(policy, requires.keys),
253
- ...rls?.customRoleWrites?.roles === void 0 ? {} : { table: rls.customRoleWrites.roles }
254
- } } : {},
255
- ...input.capabilities === true || rls?.capabilities === true ? { capabilities: true } : {},
256
- ...apiKeys === void 0 ? {} : { apiKeys },
257
- ...rls?.anonymousSignIns === "deny" ? { anonymousSignIns: "deny" } : {},
258
- ...ownership === void 0 ? {} : { ownership },
259
- ...rls?.ownershipTriggers === void 0 ? {} : { skipOwnershipTriggers: skippedOwnershipTriggers(rls.ownershipTriggers, scopes) },
260
- ...rls?.assignments === void 0 ? {} : { assignments: rls.assignments === true ? { tables: [] } : {
261
- tables: rls.assignments.tables ?? [],
262
- ...rls.assignments.ownRole === void 0 ? {} : { ownRole: rls.assignments.ownRole }
263
- } },
264
- ...options.fieldsMode === void 0 ? {} : { fields: options.fieldsMode },
265
- ...graph.size === 0 ? {} : { graph: {
266
- closures: closureDepths(graph),
267
- resources: policy.resources,
268
- ...rls?.tables === void 0 ? {} : { tables: rls.tables }
269
- } }
270
- },
271
- graph,
272
- ownership,
273
- schema,
274
- authorize
275
- };
192
+ /**
193
+ * The generate notes for the bindings whose table no project SQL file creates.
194
+ * A project with no `create table` in those files gets none: its schema lives
195
+ * elsewhere (an ORM, another repository).
196
+ */
197
+ function unknownTableNotes(bindings, created) {
198
+ if (created.size === 0) return [];
199
+ return bindings.filter((binding) => !created.has(binding.table) && !created.has(binding.table.toLowerCase())).map((binding) => `resource ${binding.resource} is bound to ${binding.table}, which no SQL file in the migration or schema folders creates; name its table in rls.tables, or set rls.tables.${binding.resource}: false when it has none`);
200
+ }
201
+ /** The inferred bindings whose table `facts` shows missing. */
202
+ function missingBindings(bindings, columns) {
203
+ return bindings.filter((binding) => binding.inferred && columns[binding.table] === void 0);
276
204
  }
277
205
  //#endregion
278
- //#region src/cli/rls/generate-options.ts
279
- function isRefusal(value) {
280
- return "refusal" in value;
206
+ //#region src/cli/rls/trigger-notes.ts
207
+ const CREATE = /^create trigger\s+("[^"]+"|[\w$]+)[^;]*?\bon\s+("[^"]+"(?:\."[^"]+")?|[\w$.]+)/gmu;
208
+ function unquote(name) {
209
+ return name.replaceAll("\"", "");
210
+ }
211
+ function reasonOf(name) {
212
+ if (name === "permdock_custom_role_name_guard") return "customRoleWrites.roles: a tenant role cannot take a declared or shared role's name";
213
+ if (name === "permdock_custom_role_cascade") return "customRoleWrites.roles: a rename or delete of a role carries its grants";
214
+ if (name.startsWith("permdock_sync_custom_role_guard")) return "customRoles.from.sync: a signed-in caller writes only entries they may hand out";
215
+ if (name.startsWith("permdock_sync_custom_role")) return "customRoles.from.sync: each write to the legacy table rebuilds the role's grants";
216
+ if (name.startsWith("permdock_holders") || name.startsWith("permdock_transfer_only")) return "ownershipTriggers; set rls.ownershipTriggers.<scope>: false to skip";
217
+ if (name === "permdock_assignment") return "assignments: a role assignment is checked against the caller's own roles";
218
+ if (name.startsWith("permdock_closure")) return "the relationship closure table follows its source table";
219
+ return "generated by rls generate";
220
+ }
221
+ /** The triggers `sql` creates, in order, each with the option behind it. */
222
+ function generatedTriggers(sql) {
223
+ const found = [];
224
+ for (const match of sql.matchAll(CREATE)) {
225
+ const name = unquote(match.slice(1, 2).join(""));
226
+ const table = unquote(match.slice(2, 3).join(""));
227
+ found.push({
228
+ name,
229
+ table,
230
+ reason: reasonOf(name)
231
+ });
232
+ }
233
+ return found;
281
234
  }
282
- /** The checks that need no policy: `--fields` and `--revoke-columns`. */
283
- function generateOptions(input) {
284
- const rls = input.config.rls;
285
- const fieldsMode = input.fields ?? rls?.fields;
286
- if (fieldsMode !== void 0 && fieldsMode !== "views") return { refusal: `rls generate --fields must be views (got ${fieldsMode})` };
287
- const revokeColumns = input.revokeColumns === true || rls?.revokeColumns === true;
288
- if (revokeColumns && fieldsMode === void 0) return { refusal: "rls generate --revoke-columns needs --fields views" };
235
+ /** One generate note per trigger. */
236
+ function triggerNotes(sql) {
237
+ return generatedTriggers(sql).map((trigger) => `generated trigger ${trigger.name} on ${trigger.table} (${trigger.reason})`);
238
+ }
239
+ //#endregion
240
+ //#region src/cli/rls/explain.ts
241
+ function holds(table) {
242
+ if (table.tenant === void 0) return ["global"];
243
+ return [
244
+ "tenant",
245
+ ...table.shared === void 0 ? [] : ["shared"],
246
+ "global"
247
+ ];
248
+ }
249
+ function explainRls(input) {
250
+ const { config, ctx, generated } = input;
251
+ const rls = config.rls;
252
+ const bindings = tableBindings(generated.compiled.rowColumns.map((item) => item.resource), tableNames(rls?.tables), generated.helpersOnly);
253
+ const sources = ctx.memberSources ?? ctx.sources ?? [];
254
+ const roles = rls?.customRoleWrites?.roles;
255
+ const wanted = rls?.rowHelpers;
256
+ const resources = wanted === void 0 ? [] : wanted === true ? [...new Set(generated.compiled.branches.flatMap((branch) => branch.resource === void 0 ? [] : [branch.resource]))].toSorted() : [...new Set(wanted)];
289
257
  return {
290
- fieldsMode,
291
- revokeColumns
258
+ schema: input.schema,
259
+ authorize: input.authorize,
260
+ scopes: ctx.scopes.map((scope) => scope.within === void 0 ? {
261
+ name: scope.name,
262
+ key: scope.key ?? scope.name
263
+ } : {
264
+ name: scope.name,
265
+ key: scope.key ?? scope.name,
266
+ within: scope.within
267
+ }),
268
+ bindings: bindings.map((binding) => {
269
+ const via = ctx.vias?.[binding.resource];
270
+ return via === void 0 ? binding : Object.assign({}, binding, { via: `${via.helper} over ${via.table}.${via.column} (${binding.table}.${via.key})` });
271
+ }),
272
+ membershipSources: sources.map((source) => ({
273
+ table: source.sql.table,
274
+ select: source.sql.select("$1")
275
+ })),
276
+ ...roles === void 0 ? {} : { customRoles: {
277
+ table: roles.table,
278
+ key: roles.key,
279
+ ...roles.tenant === void 0 ? {} : { tenant: roles.tenant },
280
+ ...roles.shared === void 0 ? {} : { shared: roles.shared },
281
+ ...roles.skip === void 0 ? {} : { skip: roles.skip },
282
+ holds: holds(roles)
283
+ } },
284
+ rowHelpers: resources.map((resource) => ({
285
+ resource,
286
+ permissions: [...new Set(generated.compiled.branches.flatMap((branch) => branch.resource === resource && branch.coverage !== true ? [branch.permissionKey] : []))].toSorted()
287
+ })),
288
+ triggers: generatedTriggers(input.text)
292
289
  };
293
290
  }
294
- /** Whether `--split` names `part`, which then leaves the helpers or the warnings. */
295
- function splitsPart(raw, part) {
296
- const split = parseSplit(raw);
297
- return typeof split !== "string" && split?.includes(part) === true;
291
+ /** The text report of `explain`. */
292
+ function describeExplain(explain) {
293
+ const lines = [
294
+ `schema ${explain.schema}, authorize ${explain.authorize}`,
295
+ `scopes: ${explain.scopes.map((scope) => `${scope.name} (${scope.key}${scope.within === void 0 ? "" : `, within ${scope.within}`})`).join(", ")}`,
296
+ "",
297
+ "table bindings:",
298
+ ...explain.bindings.map((binding) => ` ${binding.resource} -> ${binding.table}${binding.inferred ? " (named after the resource; set rls.tables to change)" : ""}${binding.via === void 0 ? "" : ` via ${binding.via}`}${binding.exists === false ? " MISSING" : ""}`)
299
+ ];
300
+ if (explain.membershipSources.length > 0) {
301
+ lines.push("", "membership sources:");
302
+ for (const source of explain.membershipSources) lines.push(` ${source.table}`, ` ${source.select.replaceAll("\n", "\n ")}`);
303
+ }
304
+ if (explain.customRoles !== void 0) {
305
+ const roles = explain.customRoles;
306
+ const columns = [
307
+ `key ${roles.key}`,
308
+ ...roles.tenant === void 0 ? [] : [`tenant ${roles.tenant}`],
309
+ ...roles.shared === void 0 ? [] : [`shared ${roles.shared}`],
310
+ ...roles.skip === void 0 ? [] : [`skip ${roles.skip}`]
311
+ ];
312
+ lines.push("", `custom roles: ${roles.table} (${columns.join(", ")}); holds ${roles.holds.join(", ")} roles`);
313
+ }
314
+ if (explain.rowHelpers.length > 0) {
315
+ lines.push("", "row helpers:");
316
+ for (const helper of explain.rowHelpers) lines.push(` ${helper.resource}: ${helper.permissions.join(", ")}`);
317
+ }
318
+ if (explain.triggers.length > 0) {
319
+ lines.push("", "generated triggers:");
320
+ for (const trigger of explain.triggers) lines.push(` ${trigger.name} on ${trigger.table} (${trigger.reason})`);
321
+ }
322
+ return lines.join("\n");
298
323
  }
299
324
  //#endregion
300
325
  //#region src/cli/rls/generate-output.ts
@@ -312,8 +337,8 @@ function moveUndiffed(sql, label, part) {
312
337
  }
313
338
  /** Why `--split` cannot be written for `mode`, or `undefined`. */
314
339
  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");
340
+ const seedsWithoutOut = plan.split.includes("seeds") && plan.seedsOut === void 0 && !plan.noSeeds && !plan.seedView;
341
+ const helpersWithoutSeeds = plan.split.includes("helpers") && !plan.noSeeds && !plan.split.includes("seeds") && !plan.seedFunction && !plan.seedView;
317
342
  switch (mode) {
318
343
  case "pg-delta":
319
344
  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})`;
@@ -333,7 +358,10 @@ function splitRefusal(mode, plan) {
333
358
  default: return mode;
334
359
  }
335
360
  }
336
- /** The files `generate` writes: one, or one per `--split` part, plus the hook's grants under migra. */
361
+ /**
362
+ * The files `generate` writes: one, or one per `--split` part, plus the hook's
363
+ * grants under migra.
364
+ */
337
365
  function outputFiles(plan) {
338
366
  const { input } = plan;
339
367
  const split = parseSplit(input.split);
@@ -342,6 +370,7 @@ function outputFiles(plan) {
342
370
  if (split === void 0) {
343
371
  if (input.grantsOut !== void 0) return "rls generate --grants-out needs --split with the helpers or hook part";
344
372
  if (input.seedsOut !== void 0) return "rls generate --seeds-out needs --split with the seeds part";
373
+ if (input.noSeeds === true) return "rls generate --no-seeds needs --split: without it the seeds are part of the one file";
345
374
  return [{
346
375
  part: "rls",
347
376
  rel: plan.outRel,
@@ -358,11 +387,15 @@ function outputFiles(plan) {
358
387
  explicitOut: input.out !== void 0,
359
388
  outRel: plan.outRel,
360
389
  grantsOut: input.grantsOut,
361
- seedsOut: input.seedsOut
390
+ seedsOut: input.seedsOut,
391
+ noSeeds: input.noSeeds === true,
392
+ seedFunction: input.config.rls?.seeds === "function",
393
+ seedView: input.config.rls?.seeds === "view"
362
394
  });
363
395
  if (refused !== void 0) return refused;
364
396
  const at = (part, schema = plan.schema) => pgDelta === void 0 || placeholder ? partPath(plan.outRel, part) : pgDeltaPath(pgDelta.schemaDir, part, schema);
365
397
  if (input.grantsOut !== void 0 && !split.includes("hook") && !split.includes("helpers")) return "rls generate --grants-out needs the helpers or hook part in --split";
398
+ if (input.noSeeds === true && input.seedsOut !== void 0) return "rls generate --no-seeds and --seeds-out exclude each other: drop one";
366
399
  if (input.seedsOut !== void 0 && !split.includes("seeds")) return "rls generate --seeds-out needs the seeds part in --split";
367
400
  if (split.includes("hook") && input.config.supabase?.hook === void 0) return "rls generate --split hook needs supabase.hook in permdock.config.ts";
368
401
  const sql = plan.sql();
@@ -393,6 +426,10 @@ function outputFiles(plan) {
393
426
  break;
394
427
  }
395
428
  case "seeds": {
429
+ if (input.noSeeds === true) {
430
+ if (relocated !== void 0 && split.includes("policies")) return "rls generate --no-seeds cannot drop the seeds part under pg-delta with rls.realtime or rls.storage: their policies live in it";
431
+ break;
432
+ }
396
433
  const out = input.seedsOut;
397
434
  if (out !== void 0 && isSeedsDirectory(input.cwd, out)) {
398
435
  const planned = seedsMigration(input.cwd, out, SEEDS_MARKER, seeds, input.io.now?.() ?? dateAt());
@@ -438,6 +475,12 @@ function outputFiles(plan) {
438
475
  }
439
476
  default: return part;
440
477
  }
478
+ if (pgDelta !== void 0 && !placeholder) {
479
+ for (const [index, file] of files.entries()) if (file.part !== "seeds" || input.seedsOut === void 0) files[index] = {
480
+ ...file,
481
+ text: declarativeSql(file.text)
482
+ };
483
+ }
441
484
  if (input.grantsOut !== void 0) files.push({
442
485
  part: "grants",
443
486
  rel: input.grantsOut,
@@ -447,124 +490,18 @@ function outputFiles(plan) {
447
490
  }
448
491
  //#endregion
449
492
  //#region src/cli/rls/backfill.ts
450
- const PREFIX$1 = "PermDock CLI";
451
- /** A column of the roles-table row `r`. */
452
- function col(name) {
453
- return `r.${quoteIdent$1(name)}`;
454
- }
455
- function schemaOf(table) {
456
- return table.includes(".") ? beforeFirst(table, ".") : "public";
457
- }
458
- /** The roles-table column a reference joins on, after checking it points at `roles`. */
459
- function roleRef(role, table, roles, label) {
460
- const pairs = Object.entries(role.on);
461
- const [pair] = pairs;
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' }`);
463
- const through = role.through.includes(".") ? role.through : `${schemaOf(table)}.${role.through}`;
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}`);
466
- const [ref, id] = pair;
467
- return {
468
- ref,
469
- id
470
- };
471
- }
472
493
  /**
473
- * A legacy key as SQL the way `mapKey` maps it: `rls.migrate.keys`, then
474
- * `renamed`, then the longest `rls.migrate.prefixes` match, and a former key
475
- * read as its current key.
476
- */
477
- function legacyKeySql(expr, migrate, renamed) {
478
- const exact = new Map(Object.entries(renamed));
479
- for (const [from, to] of Object.entries(migrate?.keys ?? {})) exact.set(from, to);
480
- const prefixes = Object.entries(migrate?.prefixes ?? {}).toSorted(([a], [b]) => b.length - a.length || byCodeUnit(a, b));
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)`;
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);
484
- return currentKeyExpr(renames, mapped);
485
- }
486
- function sourceSql(source, table, label) {
487
- return roleColumn(source, table, "x", {
488
- label,
489
- prefix: PREFIX$1
490
- }).lookup;
491
- }
492
- /**
493
- * The `--backfill-out` migration: every custom role in the application's
494
- * roles table that has no rows in `custom_role_permissions` or
495
- * `custom_role_includes` yet is saved through
496
- * `permdock_trusted_replace_custom_role_grants`, with the entries PermDock
497
- * cannot hold left out and listed in one warning. Running it again copies
498
- * nothing, so a role edited after the copy keeps its edits.
494
+ * The `--backfill-out` migration: every custom role in the application's roles
495
+ * table that has no rows in `custom_role_permissions` or `custom_role_includes`
496
+ * yet is saved through `permdock_trusted_replace_custom_role_grants`, with the
497
+ * entries PermDock cannot hold left out and listed in one warning. Running it
498
+ * again copies nothing, so a role edited after the copy keeps its edits.
499
499
  */
500
500
  function backfillSql(ctx, from, migrate) {
501
- const custom = ctx.customRoles;
502
- const root = ctx.scopes[0]?.name;
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`);
505
- const roles = custom.table;
506
- if (roles === void 0) throw configError(`${PREFIX$1}: rls.customRoles.from needs rls.customRoleWrites.roles, the roles table each row belongs to`);
507
- const leveled = custom.levels === true;
508
- if (from.permissions.level !== void 0 && !leveled) throw configError(`${PREFIX$1}: rls.customRoles.from.permissions.level is set, but no resource declares levels`);
509
- const tenantType = tenantTypeOf(ctx);
510
- const rolesTable = quoteQualified(roles.table);
511
- const tenant = roles.tenant === void 0 ? `null::${tenantType}` : `${col(roles.tenant)}::${tenantType}`;
512
- const scope = rolesTableScope(roles, tenant, root, col);
513
- const scopeId = roles.id === void 0 ? "null::text" : `${col(roles.id)}::text`;
514
- const skipped = roles.skip === void 0 ? "false" : `coalesce(${col(roles.skip)}, false)`;
515
- const renamed = custom.renamed ?? {};
516
- const renames = Object.entries(renamed).toSorted(byKey);
517
- const current = (expr) => currentKeyExpr(renames, expr);
518
- const known = sqlTextArray([...custom.permissions ?? [], ...renames.map(([former]) => former)]);
519
- const declared = sqlTextArray(custom.declared);
520
- const scopes = sqlTextArray(ctx.scopes.map((item) => item.name));
521
- const ceiling = qualified(ctx, CUSTOM_ROLES.ceiling);
522
- const rp = qualified(ctx, "role_permissions");
523
- const perms = qualified(ctx, CUSTOM_ROLES.permissions);
524
- const includes = qualified(ctx, CUSTOM_ROLES.includes);
525
- const replace = qualified(ctx, CUSTOM_ROLES.trustedReplace);
526
- const p = from.permissions;
527
- const pTable = qualifySqlTable(p.table);
528
- const pRef = roleRef(p.role, p.table, roles, "rls.customRoles.from.permissions.role");
529
- const legacy = sourceSql(p.permission, pTable, "rls.customRoles.from.permissions.permission");
530
- const effect = p.effect === void 0 ? "'allow'" : `coalesce(lower(x.${quoteIdent$1(p.effect)}::text), 'allow')`;
531
- const level = p.level === void 0 ? "null::text" : `nullif(x.${quoteIdent$1(p.level)}::text, '')`;
532
- const inCeiling = (key) => `exists (select 1 from ${ceiling} c where c.scope = v.scope and c.effect = 'allow' and ${current("c.permission")} = ${key})`;
533
- const levelCheck = leveled ? `
534
- when m.effect = 'allow' and m.level is not null and not exists (
535
- select 1 from ${ceiling} c
536
- where c.scope = v.scope and c.effect = 'allow' and ${current("c.permission")} = m.key
537
- and split_part(c.grant_key, '@', 2) = m.level
538
- ) then 'level ' || m.level || ' is not declared'` : "";
539
- const allowEntry = leveled ? "j.key || coalesce('@' || j.level, '')" : "j.key";
540
- const i = from.includes;
541
- const iTable = i === void 0 ? void 0 : qualifySqlTable(i.table);
542
- const iRef = i === void 0 || iTable === void 0 ? void 0 : roleRef(i.role, i.table, roles, "rls.customRoles.from.includes.role");
543
- const included = i === void 0 || iTable === void 0 ? void 0 : sourceSql(i.include, iTable, "rls.customRoles.from.includes.include");
544
- const refs = [`${col(pRef.id)} as permissions_ref`, ...iRef === void 0 ? [] : [`${col(iRef.id)} as includes_ref`]].join(", ");
545
- const includeBlock = i === void 0 || iTable === void 0 || iRef === void 0 || included === void 0 ? " v_include := null;" : ` with raw as (
546
- select ${included} as role
547
- from ${quoteTable(iTable)} x
548
- where x.${quoteIdent$1(iRef.ref)} = v.includes_ref
549
- ),
550
- judged as (
551
- select n.role, case
552
- when n.role is null or not (n.role = any(${declared})) then 'not a declared role'
553
- when exists (
554
- select 1 from ${rp} rp
555
- where rp.role = n.role and rp.effect = 'allow'
556
- and not ${inCeiling(current("rp.permission"))}
557
- ) then 'allows permissions outside the ceiling of ' || v.scope
558
- end as dropped
559
- from raw n
560
- )
561
- select array_agg(distinct j.role) filter (where j.dropped is null),
562
- array_agg(v.role || ': includes ' || coalesce(j.role, 'null') || ', ' || j.dropped) filter (where j.dropped is not null)
563
- into v_include, v_lost
564
- from judged j;
565
- v_dropped := v_dropped || coalesce(v_lost, '{}'::text[]);`;
501
+ const plan = rebuildPlan(ctx, from, migrate);
502
+ const { rolesTable, perms, includes, replace, scopes } = plan;
566
503
  return `${BACKFILL_MARKER} schema=${ctx.schema ?? "permdock"}
567
- -- custom roles copied from ${qualifySqlTable(roles.table)} (rls.customRoles.from) through ${replace}
504
+ -- custom roles copied from ${qualifySqlTable(plan.roles.table)} (rls.customRoles.from) through ${replace}
568
505
  -- a role that already has rows is left as it is; entries PermDock cannot hold are dropped and listed in one warning
569
506
  do $permdock$
570
507
  declare
@@ -578,9 +515,9 @@ declare
578
515
  v_present integer := 0;
579
516
  begin
580
517
  for v in
581
- select ${tenant} as tenant_id, ${scope} as scope, ${scopeId} as scope_id, ${col(roles.key)}::text as role, ${refs}
518
+ ${plan.identity}
582
519
  from ${rolesTable} r
583
- where not ${skipped} and not (${col(roles.key)}::text = any(${declared}))
520
+ where ${plan.eligible}
584
521
  order by 4, 2, 1, 3
585
522
  loop
586
523
  if v.role is null or not (v.scope = 'global' or v.scope = any(${scopes})) then
@@ -597,30 +534,7 @@ begin
597
534
  v_present := v_present + 1;
598
535
  continue;
599
536
  end if;
600
- with raw as (
601
- select ${legacy} as legacy, ${effect} as effect, ${level} as level
602
- from ${quoteTable(pTable)} x
603
- where x.${quoteIdent$1(pRef.ref)} = v.permissions_ref
604
- ),
605
- mapped as (
606
- select n.legacy, ${legacyKeySql("n.legacy", migrate, renamed)} as key, n.effect, n.level
607
- from raw n
608
- ),
609
- judged as (
610
- select m.*, case
611
- when m.effect not in ('allow', 'deny') then 'effect ' || m.effect || ' is not allow or deny'
612
- when m.key is null or not (m.key = any(${known})) then 'not a declared permission'
613
- when m.effect = 'allow' and not ${inCeiling("m.key")} then 'outside the ceiling of ' || v.scope${levelCheck}
614
- end as dropped
615
- from mapped m
616
- )
617
- select array_agg(distinct ${allowEntry}) filter (where j.dropped is null and j.effect = 'allow'),
618
- array_agg(distinct j.key) filter (where j.dropped is null and j.effect = 'deny'),
619
- array_agg(v.role || ': ' || coalesce(j.legacy, 'null') || ', ' || j.dropped) filter (where j.dropped is not null)
620
- into v_allow, v_deny, v_lost
621
- from judged j;
622
- v_dropped := v_dropped || coalesce(v_lost, '{}'::text[]);
623
- ${includeBlock}
537
+ ${plan.collect}
624
538
  if v_allow is null and v_deny is null and v_include is null then
625
539
  continue;
626
540
  end if;
@@ -629,7 +543,7 @@ ${includeBlock}
629
543
  end loop;
630
544
  raise notice 'permdock backfill: copied % custom role(s), % already present', v_copied, v_present;
631
545
  if cardinality(v_dropped) > 0 then
632
- raise warning 'permdock backfill: dropped % entr(ies) PermDock cannot hold: %', cardinality(v_dropped), array_to_string(v_dropped[1:50], '; ');
546
+ raise warning 'permdock backfill: dropped % entr(ies) PermDock cannot hold: %', cardinality(v_dropped), ${DROPPED_LIST};
633
547
  end if;
634
548
  end
635
549
  $permdock$;
@@ -637,9 +551,12 @@ $permdock$;
637
551
  }
638
552
  //#endregion
639
553
  //#region src/cli/rls/break-glass.ts
640
- /** The audit table every generated break-glass read writes to. Part of the SQL contract. */
554
+ /**
555
+ * The audit table every generated break-glass read writes to. Part of the SQL
556
+ * contract.
557
+ */
641
558
  const BREAK_GLASS_AUDIT = "permdock_break_glass_audit";
642
- const IDENT$1 = /^[a-z][a-z0-9_]*$/u;
559
+ const IDENT$2 = /^[a-z][a-z0-9_]*$/u;
643
560
  /**
644
561
  * The resources a `breakGlass` grant targets, with the table each reads. A
645
562
  * break-glass grant is non-portable, so `compileGrants` never turns it into a
@@ -678,17 +595,17 @@ function breakGlassEntries(policy, tables) {
678
595
  }));
679
596
  }
680
597
  /**
681
- * Rows the subject's roles reach for `p_permission`: a break-glass grant
682
- * lifts denies, never the tenant boundary. A grant to no role reads the rows
683
- * of the root scopes the subject is a member of, or every row when the
684
- * policy declares no scopes.
598
+ * Rows the subject's roles reach for `p_permission`: a break-glass grant lifts
599
+ * denies, never the tenant boundary. A grant to no role reads the rows of the
600
+ * root scopes the subject is a member of, or every row when the policy declares
601
+ * no scopes.
685
602
  */
686
603
  function rowsFilter(ctx, entry) {
687
604
  const arms = entry.grants.flatMap((grant) => {
688
605
  const asked = `p_permission = ${quoteLiteral(grant.permission)}`;
689
606
  if (grant.scope === "anyone") {
690
607
  if (grant.root === void 0) return [`(${asked})`];
691
- return grant.column === void 0 ? [] : [`(${asked} and ${quoteIdent$1(grant.column)} in (select ${qualified(ctx, memberIdsHelper(grant.root))}()))`];
608
+ return grant.column === void 0 ? [] : [`(${asked} and ${quoteIdent$1(grant.column)} in (select ${qualified$1(ctx, memberIdsHelper(grant.root))}()))`];
692
609
  }
693
610
  if (grant.scope !== "global" && grant.column === void 0) return [];
694
611
  return [`(${asked} and ${accessSql(ctx, grant.scope, breakGlassKey(grant.permission), grant.column)})`];
@@ -696,11 +613,11 @@ function rowsFilter(ctx, entry) {
696
613
  return arms.length === 0 ? "false" : [...new Set(arms)].join("\n or ");
697
614
  }
698
615
  function functionName$1(resource) {
699
- if (!IDENT$1.test(resource)) throw configError(`PermDock CLI: unsafe break-glass resource '${resource}'`);
616
+ if (!IDENT$2.test(resource)) throw configError(`PermDock CLI: unsafe break-glass resource '${resource}'`);
700
617
  return `permdock_break_glass_${resource}`;
701
618
  }
702
619
  function auditSql$1(ctx) {
703
- const audit = qualified(ctx, quoteIdent$1(BREAK_GLASS_AUDIT));
620
+ const audit = qualified$1(ctx, quoteIdent$1(BREAK_GLASS_AUDIT));
704
621
  return `-- break-glass audit: one row per session that read restricted data
705
622
  -- RLS never compiles a break-glass override, so restricted rows stay denied to a direct read
706
623
  create table if not exists ${audit} (
@@ -715,8 +632,8 @@ alter table ${audit} enable row level security;
715
632
  revoke all on table ${audit} from anon, authenticated, public;`;
716
633
  }
717
634
  function readFunctionSql(ctx, entry) {
718
- const fn = qualified(ctx, functionName$1(entry.resource));
719
- const audit = qualified(ctx, quoteIdent$1(BREAK_GLASS_AUDIT));
635
+ const fn = qualified$1(ctx, functionName$1(entry.resource));
636
+ const audit = qualified$1(ctx, quoteIdent$1(BREAK_GLASS_AUDIT));
720
637
  const table = quoteQualified(entry.table);
721
638
  const session = subjectClaimJsonSql(ctx, "break_glass");
722
639
  const subject = subjectIdSql(ctx);
@@ -756,12 +673,12 @@ revoke execute on function ${fn}(text) from public, anon;
756
673
  grant execute on function ${fn}(text) to authenticated;`;
757
674
  }
758
675
  /**
759
- * The `security definer` read path for every `breakGlass` grant: an audit
760
- * table and one `permdock_break_glass_<resource>(permission)` per resource
761
- * that checks a signed break-glass session, writes an audit row and returns
762
- * the rows in the scope instances where the subject holds a role with the
763
- * break-glass grant (bypassing the table's policies as the definer). Empty
764
- * when the policy has no break-glass grant.
676
+ * The `security definer` read path for every `breakGlass` grant: an audit table
677
+ * and one `permdock_break_glass_<resource>(permission)` per resource that
678
+ * checks a signed break-glass session, writes an audit row and returns the rows
679
+ * in the scope instances where the subject holds a role with the break-glass
680
+ * grant (bypassing the table's policies as the definer). Empty when the policy
681
+ * has no break-glass grant.
765
682
  */
766
683
  function breakGlassSql(ctx, entries) {
767
684
  if (entries.length === 0) return "";
@@ -770,9 +687,9 @@ function breakGlassSql(ctx, entries) {
770
687
  //#endregion
771
688
  //#region src/cli/rls/emit.ts
772
689
  const HEADER = "-- generated by permdock rls generate\n-- fail-closed: never target a bypass role\n";
773
- const TRUSTED_READER_GRANT = /^grant execute on function [^\n]+\.permdock_trusted_role_permissions\([^)\n]*\) to [^\n;]+;$/gmu;
690
+ const TRUSTED_READER_GRANT = /^grant usage on schema "[^"\n]+" to [^\n;]+;\ngrant execute on function [^\n]+\.permdock_trusted_role_permissions(?:_by_id)?\([^)\n]*\) to [^\n;]+;$/gmu;
774
691
  function assertNoServiceRole(text) {
775
- if (/\bservice_role\b/iu.test(text.replace(TRUSTED_READER_GRANT, ""))) throw new Error("PermDock CLI: generated RLS must never emit service_role");
692
+ if (/\bservice_role\b/iu.test(text.replaceAll(TRUSTED_READER_GRANT, ""))) throw new Error("PermDock CLI: generated RLS must never emit service_role");
776
693
  }
777
694
  function assertSupautilsSafe(text) {
778
695
  const [first] = supautilsRejections(text);
@@ -780,8 +697,8 @@ function assertSupautilsSafe(text) {
780
697
  }
781
698
  /**
782
699
  * Neon's Data API and `pg_session_jwt` name the anonymous role `anonymous`;
783
- * Supabase, PostgREST and the GUC pattern use `anon`. Role lists end a line
784
- * or a statement after `to` or `from`, so only those are renamed.
700
+ * Supabase, PostgREST and the GUC pattern use `anon`. Role lists end a line or
701
+ * a statement after `to` or `from`, so only those are renamed.
785
702
  */
786
703
  function dialectRoles(text, dialect) {
787
704
  if (dialect !== "neon") return text;
@@ -790,7 +707,10 @@ function dialectRoles(text, dialect) {
790
707
  function tablesOf(policies) {
791
708
  return [...new Set(policies.map((item) => item.table))];
792
709
  }
793
- /** Enable (and force) row level security, then the table grants for the commands a policy allows. */
710
+ /**
711
+ * Enable (and force) row level security, then the table grants for the commands
712
+ * a policy allows.
713
+ */
794
714
  function tableSql(table, policies, force, views) {
795
715
  const closed = views.find((view) => view.table === table && view.companion !== void 0);
796
716
  const target = quoteQualified(table);
@@ -811,7 +731,10 @@ function tableSql(table, policies, force, views) {
811
731
  if (closed !== void 0) lines.push(columnRevokeSql(closed));
812
732
  return lines.join("\n");
813
733
  }
814
- /** `drop policy if exists` and `create policy` for one policy, without the table's grants. */
734
+ /**
735
+ * `drop policy if exists` and `create policy` for one policy, without the
736
+ * table's grants.
737
+ */
815
738
  function policySql(item) {
816
739
  const target = quoteQualified(item.table);
817
740
  const lines = [
@@ -838,8 +761,9 @@ function emitSql(policies, preamble, force = false, views = []) {
838
761
  return text;
839
762
  }
840
763
  /**
841
- * The SQL a Drizzle or Prisma schema cannot declare: helpers, grants, `force` and field views.
842
- * `rls generate` writes it next to `out` for a custom migration.
764
+ * The SQL a Drizzle or Prisma schema cannot declare: helpers, grants, `force`
765
+ * and field views. `rls generate` writes it next to `out` for a custom
766
+ * migration.
843
767
  */
844
768
  function migrationSql(policies, preamble, force, views) {
845
769
  const parts = [
@@ -857,7 +781,7 @@ function helpersNote(helpers, how) {
857
781
  return helpers === void 0 ? [] : [`// run ${helpers} ${how}`];
858
782
  }
859
783
  function camelCase(name) {
860
- return name.replace(/[_-]+([a-z0-9])/gi, (_, char) => char.toUpperCase());
784
+ return name.replaceAll(/[_-]+([a-z0-9])/giu, (_, char) => char.toUpperCase());
861
785
  }
862
786
  function tableName(table) {
863
787
  return table.slice(table.lastIndexOf(".") + 1);
@@ -866,16 +790,16 @@ function pascalCase(name) {
866
790
  const camel = camelCase(name);
867
791
  return camel.charAt(0).toUpperCase() + camel.slice(1);
868
792
  }
869
- const IDENTIFIER = /^[A-Za-z_$][\w$]*$/;
793
+ const IDENTIFIER = /^[A-Za-z_$][\w$]*$/u;
870
794
  function identifier(name, what) {
871
795
  if (!IDENTIFIER.test(name)) throw configError(`PermDock CLI: ${what} ${JSON.stringify(name)} is not an identifier`);
872
796
  return name;
873
797
  }
874
798
  function quoted(text) {
875
- return `'${text.replace(/[\\']/g, (match) => `\\${match}`)}'`;
799
+ return `'${text.replaceAll(/[\\']/gu, (match) => `\\${match}`)}'`;
876
800
  }
877
801
  function templateText(text) {
878
- return text.replace(/[\\`]|\$\{/g, (match) => `\\${match}`);
802
+ return text.replaceAll(/[\\`]|\$\{/gu, (match) => `\\${match}`);
879
803
  }
880
804
  const AUTH_UID = "(select auth.uid())";
881
805
  function drizzleRoles(list) {
@@ -920,7 +844,10 @@ function emitDrizzle(policies, options) {
920
844
  assertNoServiceRole(text);
921
845
  return text;
922
846
  }
923
- /** Prisma 8 `policy_<command>` blocks; each target model declares `@@rls` itself. */
847
+ /**
848
+ * Prisma 8 `policy_<command>` blocks; each target model declares `@@rls`
849
+ * itself.
850
+ */
924
851
  function emitPrisma(policies, options = {}) {
925
852
  const modelOf = (table) => identifier(options.models?.[table] ?? pascalCase(tableName(table)), "model");
926
853
  const models = [...new Set(policies.map((item) => modelOf(item.table)))];
@@ -977,12 +904,12 @@ function declaredScope(facts, scope, where) {
977
904
  return scope;
978
905
  }
979
906
  /**
980
- * The ids of `scope` where the subject holds `key` through an allow without
981
- * a row condition, minus a deny of it, as text: `<value> in (…)`. A
982
- * relationship or a `where` on the same permission never widens it.
907
+ * The ids of `scope` where the subject holds `key` through an allow without a
908
+ * row condition, minus a deny of it, as text: `<value> in (…)`. A relationship
909
+ * or a `where` on the same permission never widens it.
983
910
  */
984
911
  function permitted(ctx, value, scope, key) {
985
- return `${value} in (select x::text from ${qualified(ctx, permittedByPermissionHelper(scope))}(${quoteLiteral(key)}) x)`;
912
+ return `${value} in (select x::text from ${qualified$1(ctx, permittedByPermissionHelper(scope))}(${quoteLiteral(key)}) x)`;
986
913
  }
987
914
  function escapeRegex(text) {
988
915
  return text.replaceAll(/[.*+?^${}()|[\]\\]/gu, String.raw`\$&`);
@@ -1083,9 +1010,9 @@ function storagePolicies(ctx, facts, storage, warnings) {
1083
1010
  * The `realtime.messages` and `storage.objects` policies `rls.realtime` and
1084
1011
  * `rls.storage` describe. Each one admits a row only where the subject holds
1085
1012
  * the permission in the scope instance its topic segment or folder names,
1086
- * through an allow without a row condition, so the helpers' API-key ceiling
1087
- * and suspension rules apply and a relationship grant on the same permission
1088
- * never reaches a topic or a folder.
1013
+ * through an allow without a row condition, so the helpers' API-key ceiling and
1014
+ * suspension rules apply and a relationship grant on the same permission never
1015
+ * reaches a topic or a folder.
1089
1016
  */
1090
1017
  function realtimeStoragePolicies(ctx, facts, config, warnings = []) {
1091
1018
  if (config.realtime === void 0 && config.storage === void 0) return [];
@@ -1099,172 +1026,12 @@ function realtimeStoragePolicies(ctx, facts, config, warnings = []) {
1099
1026
  return policies;
1100
1027
  }
1101
1028
  //#endregion
1102
- //#region src/cli/rls/rows.ts
1103
- /** `permitted_<resource>_rows(p_permission)`: the resource's row ids the subject may act on with one permission. */
1104
- function rowsHelper(resource) {
1105
- const name = graphSqlName(resource);
1106
- const helper = `permitted_${name ?? resource}_rows`;
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`);
1108
- return helper;
1109
- }
1110
- /** The branches a row check of a permission reads: its allows and denies on rows that exist, for signed-in callers. */
1111
- function rowBranchesOf(branches, resource) {
1112
- const out = /* @__PURE__ */ new Map();
1113
- for (const branch of branches) {
1114
- if (branch.resource !== resource || branch.command === "insert" || branch.coverage === true || !branch.roles.includes("authenticated")) continue;
1115
- out.set(branch.permissionKey, [...out.get(branch.permissionKey) ?? [], branch]);
1116
- }
1117
- return out;
1118
- }
1119
- function usingOf(branches) {
1120
- const parts = branches.map((branch) => branchClauses(branch).using ?? "true");
1121
- return parts.length === 0 ? void 0 : orSql(parts);
1122
- }
1123
- function permissionCase(key, branches) {
1124
- const allow = usingOf(branches.filter((branch) => branch.effect === "allow"));
1125
- if (allow === void 0) return;
1126
- const deny = usingOf(branches.filter((branch) => branch.effect === "deny"));
1127
- const granted = `coalesce((${allow}), false)`;
1128
- return ` when ${quoteLiteral(key)} then ${deny === void 0 ? granted : `${granted} and (${deny}) is not true`}`;
1129
- }
1130
- function forUserSql(ctx, fn, helper) {
1131
- const user = ctx.dialect === "supabase" ? "uuid" : "text";
1132
- const signature = `${user}, text, jsonb`;
1133
- const head = `create or replace function ${fn}(p_user ${user}, p_permission text, p_claims jsonb default '{}')
1134
- returns setof text
1135
- language plpgsql
1136
- volatile
1137
- security definer
1138
- set search_path = ''
1139
- as $$`;
1140
- const tail = `revoke execute on function ${fn}(${signature}) from public, anon, authenticated;`;
1141
- if (ctx.dialect === "supabase") return `${head}
1142
- declare
1143
- v_claims text := pg_catalog.current_setting('request.jwt.claims', true);
1144
- begin
1145
- perform pg_catalog.set_config(
1146
- 'request.jwt.claims',
1147
- (coalesce(p_claims, '{}'::jsonb) || pg_catalog.jsonb_build_object('sub', p_user::text, 'role', 'authenticated'))::text,
1148
- true
1149
- );
1150
- return query select r.id from ${helper}(p_permission) r(id);
1151
- perform pg_catalog.set_config('request.jwt.claims', coalesce(v_claims, ''), true);
1152
- end
1153
- $$;
1154
- ${tail}`;
1155
- const prefix = ctx.gucPrefix;
1156
- return `${head}
1157
- declare
1158
- v_saved jsonb := '{}'::jsonb;
1159
- v_key text;
1160
- v_value text;
1161
- begin
1162
- for v_key, v_value in
1163
- select 'user_id', p_user
1164
- union all
1165
- select c.key, c.value from pg_catalog.jsonb_each_text(coalesce(p_claims, '{}'::jsonb)) c where c.key <> 'user_id'
1166
- loop
1167
- v_saved := v_saved || pg_catalog.jsonb_build_object(v_key, coalesce(pg_catalog.current_setting(${quoteLiteral(`${prefix}.`)} || v_key, true), ''));
1168
- perform pg_catalog.set_config(${quoteLiteral(`${prefix}.`)} || v_key, v_value, true);
1169
- end loop;
1170
- return query select r.id from ${helper}(p_permission) r(id);
1171
- for v_key, v_value in select s.key, s.value from pg_catalog.jsonb_each_text(v_saved) s loop
1172
- perform pg_catalog.set_config(${quoteLiteral(`${prefix}.`)} || v_key, v_value, true);
1173
- end loop;
1174
- end
1175
- $$;
1176
- ${tail}`;
1177
- }
1178
- function inheritTargets(policy) {
1179
- const out = /* @__PURE__ */ new Set();
1180
- for (const grant of policy.grants) for (const item of flattenGrantee(grant.to)) if (item.kind === "inherit") out.add(item.resource);
1181
- return [...out].toSorted();
1182
- }
1183
- function stubSql(ctx, resource) {
1184
- return `create or replace function ${qualified(ctx, rowsHelper(resource))}(p_permission text)
1185
- returns setof text
1186
- language sql
1187
- stable
1188
- security definer
1189
- set search_path = ''
1190
- as $$
1191
- select null::text where false
1192
- $$;`;
1193
- }
1194
- function rowSql(ctx, rows, table, where) {
1195
- const fn = qualified(ctx, rows.replace(/_rows$/u, "_row"));
1196
- return `create or replace function ${fn}(p_row ${table}, p_permission text)
1197
- returns boolean
1198
- language sql
1199
- stable
1200
- security definer
1201
- set search_path = ''
1202
- as $$
1203
- select exists (select 1 from (select (p_row).*) r where ${where})
1204
- $$;
1205
- revoke execute on function ${fn}(${table}, text) from public, anon;
1206
- grant execute on function ${fn}(${table}, text) to authenticated;`;
1207
- }
1208
- function resourceSql(ctx, node, branches, tables) {
1209
- const name = rowsHelper(node.name);
1210
- const fn = qualified(ctx, name);
1211
- const table = quoteQualified(tableFor(node.name, tables));
1212
- const cases = [...branches.keys()].toSorted().flatMap((key) => {
1213
- const sql = permissionCase(key, branches.get(key) ?? []);
1214
- return sql === void 0 ? [] : [sql];
1215
- });
1216
- const where = cases.length === 0 ? "false" : `case p_permission\n${cases.join("\n")}\n else false\n end`;
1217
- const rows = `-- ${node.name}: ids of the rows the caller may act on with p_permission, as the table's policies decide it
1218
- create or replace function ${fn}(p_permission text)
1219
- returns setof text
1220
- language sql
1221
- stable
1222
- security definer
1223
- set search_path = ''
1224
- as $$
1225
- select ${quoteIdent$1(node.id)}::text from ${table}
1226
- where ${where}
1227
- $$;
1228
- revoke execute on function ${fn}(text) from public, anon;
1229
- grant execute on function ${fn}(text) to authenticated;
1230
- ${rowSql(ctx, name, table, where)}`;
1231
- if (ctx.dialect === "neon") return rows;
1232
- return `${rows}
1233
- -- the same rows for a user the caller names, with claims it supplies; no client role may execute it
1234
- ${forUserSql(ctx, qualified(ctx, `${name}_for`), fn)}`;
1235
- }
1236
- /**
1237
- * `permitted_<resource>_rows(p_permission)` for each resource `select`
1238
- * names (or every resource a grant reaches with `true`): the ids of its rows
1239
- * the caller may read, update or delete with that permission, from the same
1240
- * compiled allows and denies the table policies use, so closure walks, link
1241
- * hops, the restricted stop, `includes`, groups and `requires` all apply. A
1242
- * `security definer` helper, so hand-written policies and functions call it
1243
- * without restating the graph. Outside the `neon` dialect a
1244
- * `permitted_<resource>_rows_for(p_user, p_permission, p_claims)` form runs it
1245
- * as the named user, with the claims the caller supplies, for trusted SQL.
1246
- */
1247
- function rowHelpersSql(ctx, policy, branches, select, tables, warnings) {
1248
- const names = select === true ? [...new Set(branches.flatMap((branch) => branch.resource === void 0 ? [] : [branch.resource]))].toSorted() : [...new Set(select)];
1249
- const called = inheritTargets(policy);
1250
- for (const name of called) if (!names.includes(name)) names.push(name);
1251
- const chunks = called.map((name) => stubSql(ctx, name));
1252
- for (const name of names) {
1253
- const node = policy.resources.get(name);
1254
- if (node === void 0) throw configError(`PermDock CLI: rls.rowHelpers names '${name}', which the policy does not declare`);
1255
- chunks.push(resourceSql(ctx, node, rowBranchesOf(branches, name), tables));
1256
- }
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");
1258
- return chunks.length === 0 ? "" : `${chunks.join("\n\n")}\n`;
1259
- }
1260
- //#endregion
1261
1029
  //#region src/cli/rls/generate-policies.ts
1262
1030
  const SERVICE_TABLES_HEADER = "-- Supabase Realtime and Storage: rls.realtime and rls.storage. RLS on both tables is Supabase's.";
1263
1031
  /**
1264
- * A `realtime.messages` or `storage.objects` policy that applies only where
1265
- * the table exists. The Realtime and Storage services create those tables, so
1266
- * a local stack with the service off, and pg-delta's shadow database, have
1267
- * none.
1032
+ * A `realtime.messages` or `storage.objects` policy that applies only where the
1033
+ * table exists. The Realtime and Storage services create those tables, so a
1034
+ * local stack with the service off, and pg-delta's shadow database, have none.
1268
1035
  */
1269
1036
  function guardedPolicySql(item) {
1270
1037
  const body = policySql(item).trimEnd().split("\n").map((line) => ` ${line}`).join("\n");
@@ -1277,7 +1044,10 @@ end
1277
1044
  $permdock$;
1278
1045
  `;
1279
1046
  }
1280
- /** Appends the `realtime.messages` and `storage.objects` policies, which never get `tableSql`. */
1047
+ /**
1048
+ * Appends the `realtime.messages` and `storage.objects` policies, which never
1049
+ * get `tableSql`.
1050
+ */
1281
1051
  function withHelperTables(sql, helperTables) {
1282
1052
  if (helperTables === void 0) return sql;
1283
1053
  const tables = `${helperTables.trimEnd()}\n`;
@@ -1287,18 +1057,22 @@ function databaseModeWarnings(policy, { ctx }, warnings) {
1287
1057
  if (ctx.authorize !== "database") return;
1288
1058
  for (const { name } of ctx.scopes) if (policy.grants.some((grant) => grant.scope === name) && scopeTable(ctx, name) === void 0 && scopeSources(ctx, name).length === 0) warnings.push(`${name}-scoped grants read the ${name} memberships table in database mode: set rls.memberships.scopes.${name}, rls.membershipSources or supabase.hook.memberships (or --memberships for the first scope), otherwise they deny`);
1289
1059
  }
1290
- /** The grants compiled into table policies, and the Realtime and Storage policies beside them. */
1060
+ /**
1061
+ * The grants compiled into table policies, and the Realtime and Storage
1062
+ * policies beside them.
1063
+ */
1291
1064
  function generatePolicies(input, policy, options, built, warnings) {
1292
1065
  const rls = input.config.rls;
1293
1066
  const { ctx } = built;
1294
1067
  const { fieldsMode, revokeColumns } = options;
1295
1068
  databaseModeWarnings(policy, built, warnings);
1296
- const compiled = compileGrants(policy, ctx, rls?.tables, warnings, input.skipClosures, rls?.rowHelpers !== void 0 || inheritTargets(policy).length > 0);
1069
+ const compiled = compileGrants(policy, ctx, tableNames(rls?.tables), warnings, input.skipClosures, rls?.rowHelpers !== void 0 || inheritTargets(policy).length > 0);
1297
1070
  const helpersOnly = input.helpersOnly === true || rls?.helpersOnly === true;
1298
1071
  if (helpersOnly && (input.target !== "sql" || revokeColumns)) return { refusal: "rls generate --helpers-only needs --target sql and no --revoke-columns: the table grants are hand-written, so revoke the restricted columns there" };
1299
1072
  const views = fieldsMode === void 0 ? [] : fieldViews(policy, ctx, compiled.branches, {
1300
1073
  revokeColumns,
1301
- helpersOnly
1074
+ helpersOnly,
1075
+ mask: fieldsMode === "mask"
1302
1076
  }, warnings);
1303
1077
  const force = input.force === true || rls?.force === true;
1304
1078
  if (force && views.some((view) => view.companion !== void 0)) warnings.push("--force with --revoke-columns: each <table>_visible_fields companion reads as its owner, so its owner needs BYPASSRLS or the restricted columns read as null");
@@ -1308,7 +1082,7 @@ function generatePolicies(input, policy, options, built, warnings) {
1308
1082
  ...policyName === void 0 ? {} : { name: policyName }
1309
1083
  });
1310
1084
  const actorKinds = readOnlyActorKinds(rls?.readOnlyActors);
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`);
1085
+ if (actorKinds !== void 0 && helpersOnly) warnings.push(`rls.readOnlyActors with --helpers-only writes no policies: add \`and (select ${qualified$1(ctx, ACTOR_MAY_WRITE)}())\` to each hand-written insert, update and delete policy, or a restrictive policy that calls it`);
1312
1086
  const withActors = (items) => actorKinds === void 0 ? items : [...items, ...readOnlyActorPolicies(items, subjectClaimJsonSql(ctx, "act"), actorKinds)];
1313
1087
  let tablePolicies;
1314
1088
  try {
@@ -1341,10 +1115,10 @@ function generatePolicies(input, policy, options, built, warnings) {
1341
1115
  }
1342
1116
  //#endregion
1343
1117
  //#region src/cli/rls/shims.ts
1344
- const NAME = /^[a-z_][a-z0-9_]*$/u;
1118
+ const NAME$1 = /^[a-z_][a-z0-9_]*$/u;
1345
1119
  /**
1346
- * The key a legacy caller passes, mapped the way `rls migrate` maps it:
1347
- * `keys` and `renamed` exactly, then the longest `prefixes` entry.
1120
+ * The key a legacy caller passes, mapped the way `rls migrate` maps it: `keys`
1121
+ * and `renamed` exactly, then the longest `prefixes` entry.
1348
1122
  */
1349
1123
  function mappedKey(config, renamed) {
1350
1124
  const exact = {
@@ -1365,10 +1139,10 @@ function addKey(list, key) {
1365
1139
  }
1366
1140
  /**
1367
1141
  * The grant keys behind each permission, per scope: the keys of its
1368
- * unconditional allows, and every deny key. A shim cannot apply a row
1369
- * condition or a validity window, so a conditional allow answers nothing and
1370
- * a conditional deny always subtracts; the conditioned keys are listed apart.
1371
- * A break-glass key answers only the break-glass read, so it is left out too.
1142
+ * unconditional allows, and every deny key. A shim cannot apply a row condition
1143
+ * or a validity window, so a conditional allow answers nothing and a
1144
+ * conditional deny always subtracts; the conditioned keys are listed apart. A
1145
+ * break-glass key answers only the break-glass read, so it is left out too.
1372
1146
  * Rows seeded under a former key are left out; the shim maps a former key to
1373
1147
  * the current one first.
1374
1148
  */
@@ -1422,9 +1196,12 @@ grant execute on function ${name}(${argTypes}) to authenticated;`;
1422
1196
  function grantKeys(keys, scope, effect) {
1423
1197
  return `pg_catalog.jsonb_array_elements_text(coalesce(${keys.map(scope)} -> (${keys.key}) -> ${quoteLiteral(effect)}, '[]'::jsonb)) g(grant_key)`;
1424
1198
  }
1425
- /** The instances of `scope` the caller holds an unconditional allow of the key in, minus those it holds a deny in. */
1199
+ /**
1200
+ * The instances of `scope` the caller holds an unconditional allow of the key
1201
+ * in, minus those it holds a deny in.
1202
+ */
1426
1203
  function idsSql(ctx, keys, scope) {
1427
- const helper = qualified(ctx, permittedIdsHelper(scope));
1204
+ const helper = qualified$1(ctx, permittedIdsHelper(scope));
1428
1205
  return ` select a.id
1429
1206
  from ${grantKeys(keys, scope, "allow")}
1430
1207
  cross join lateral ${helper}(g.grant_key) a(id)
@@ -1434,7 +1211,7 @@ function idsSql(ctx, keys, scope) {
1434
1211
  cross join lateral ${helper}(g.grant_key) d(id)`;
1435
1212
  }
1436
1213
  function hasSql(ctx, keys) {
1437
- const has = qualified(ctx, HELPERS.has);
1214
+ const has = qualified$1(ctx, HELPERS.has);
1438
1215
  return `exists (select 1 from ${grantKeys(keys, "global", "allow")} where ${has}(g.grant_key))
1439
1216
  and not exists (select 1 from ${grantKeys(keys, "global", "deny")} where ${has}(g.grant_key))`;
1440
1217
  }
@@ -1452,14 +1229,14 @@ function shim(ctx, config, keys, fn, helper) {
1452
1229
  case "membership": {
1453
1230
  const scope = scopeOf(ctx, helper.scope);
1454
1231
  const type = scopeTypeOf(ctx, scope);
1455
- return shimFunction(fn, `p_id ${type}`, type, "boolean", ` select coalesce(p_id in (select ${qualified(ctx, memberIdsHelper(scope))}()), false)`);
1232
+ return shimFunction(fn, `p_id ${type}`, type, "boolean", ` select coalesce(p_id in (select ${qualified$1(ctx, memberIdsHelper(scope))}()), false)`);
1456
1233
  }
1457
1234
  case "role": {
1458
1235
  const scope = scopeOf(ctx, helper.scope);
1459
1236
  const type = scopeTypeOf(ctx, scope);
1460
1237
  const values = Object.entries(ctx.roleValues ?? {});
1461
1238
  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)`);
1239
+ return shimFunction(fn, `p_id ${type}, p_role text`, `${type}, text`, "boolean", ` select coalesce(p_id in (select ${qualified$1(ctx, memberIdsHelper(scope))}(array[${role}])), false)`);
1463
1240
  }
1464
1241
  case "global": return shimFunction(fn, "p_key text", "text", "boolean", ` select ${hasSql(ctx, keys)}`);
1465
1242
  case "scoped": {
@@ -1476,10 +1253,10 @@ function shim(ctx, config, keys, fn, helper) {
1476
1253
  * One wrapper per `rls.migrate.helpers` entry under its legacy name, so SQL
1477
1254
  * that `rls migrate` has not rewritten yet (function bodies, views, triggers)
1478
1255
  * answers from the PermDock helpers. A wrapper maps the legacy key to the
1479
- * permission key, then to the grant keys `grants` lists for the helper's
1480
- * scope, so a permission whose grants are split by condition still answers.
1481
- * Each is `security definer` with an empty `search_path`, so a caller needs
1482
- * `execute` on the wrapper only, not usage on the helper schema.
1256
+ * permission key, then to the grant keys `grants` lists for the helper's scope,
1257
+ * so a permission whose grants are split by condition still answers. Each is
1258
+ * `security definer` with an empty `search_path`, so a caller needs `execute`
1259
+ * on the wrapper only, not usage on the helper schema.
1483
1260
  */
1484
1261
  function shimsSql(ctx, config, shims, renamed, grants) {
1485
1262
  const schema = shims.schema ?? "public";
@@ -1489,7 +1266,7 @@ function shimsSql(ctx, config, shims, renamed, grants) {
1489
1266
  };
1490
1267
  const chunks = [`-- permdock shims: legacy helper names over the permdock helpers; drop each once doctor reports no caller (PD056)`];
1491
1268
  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}'`);
1269
+ if (!NAME$1.test(name)) throw configError(`PermDock CLI: unsafe rls.migrate helper name '${name}'`);
1493
1270
  chunks.push(shim(ctx, config, keys, `${quoteIdent$1(schema)}.${name}`, helper));
1494
1271
  }
1495
1272
  return `${chunks.join("\n\n")}\n`;
@@ -1508,7 +1285,7 @@ const AUDITED = [
1508
1285
  CUSTOM_ROLES.includes,
1509
1286
  "user_roles"
1510
1287
  ];
1511
- const IDENT = /^[a-z_][a-z0-9_$]*$/u;
1288
+ const IDENT$1 = /^[a-z_][a-z0-9_$]*$/u;
1512
1289
  /** The tables `sql` creates in `schema`, by name without the schema. */
1513
1290
  function generatedTables(sql, schema) {
1514
1291
  const prefix = `create table if not exists ${quoteIdent$1(schema)}.`;
@@ -1560,8 +1337,8 @@ function triggersSql(triggers, schema, tables) {
1560
1337
  }
1561
1338
  /**
1562
1339
  * `rls.audit`: one call of the audit function per generated table, which
1563
- * registers the table with the application's audit module. Statements that
1564
- * run code, so they travel with the seeds.
1340
+ * registers the table with the application's audit module. Statements that run
1341
+ * code, so they travel with the seeds.
1565
1342
  */
1566
1343
  function auditSql(audit, schema, tables) {
1567
1344
  if (audit === void 0) return "";
@@ -1570,7 +1347,7 @@ function auditSql(audit, schema, tables) {
1570
1347
  if (listed !== void 0) for (const table of listed) knownTable(table, tables, "rls.audit.tables");
1571
1348
  const targets = listed ?? AUDITED.filter((table) => tables.includes(table));
1572
1349
  const args = Object.entries(audit.args ?? {}).map(([name, value]) => {
1573
- if (!IDENT.test(name)) throw configError(`${PREFIX}: rls.audit.args names ${JSON.stringify(name)}, which is not a lower-case parameter name`);
1350
+ if (!IDENT$1.test(name)) throw configError(`${PREFIX}: rls.audit.args names ${JSON.stringify(name)}, which is not a lower-case parameter name`);
1574
1351
  return `, ${quoteIdent$1(name)} => ${quoteLiteral(value)}`;
1575
1352
  });
1576
1353
  if (targets.length === 0) return "";
@@ -1580,7 +1357,10 @@ ${targets.map((table) => `select ${fn}(${quoteLiteral(`${quoteIdent$1(schema)}.$
1580
1357
  }
1581
1358
  //#endregion
1582
1359
  //#region src/cli/rls/generate-sql.ts
1583
- /** The checks that need the SQL context: `--backfill-out`, `rls.anonymousSignIns`, `rls.jsonSchema`. */
1360
+ /**
1361
+ * The checks that need the SQL context: `--backfill-out`,
1362
+ * `rls.anonymousSignIns`, `rls.jsonSchema`.
1363
+ */
1584
1364
  function preflight(input, { ctx }) {
1585
1365
  const rls = input.config.rls;
1586
1366
  const from = typeof rls?.customRoles === "object" ? rls.customRoles.from : void 0;
@@ -1633,14 +1413,15 @@ function sectionsSql(input, policy, options, built, generated, jsonSchema, warni
1633
1413
  } catch (cause) {
1634
1414
  return { refusal: describeError(cause) };
1635
1415
  }
1636
- const graphed = graphSql(ctx, graph, rls?.tables);
1416
+ if (typeof rls?.approvals === "object" && rls.approvals.delegations === true && ctx.authorize !== "database") warnings.push("rls.approvals.delegations with --authorize jwt: permdock_is_approver cannot read a delegator's permissions from the caller's token, so in SQL a cover never satisfies a holder() approver; approvalsHandler still does");
1417
+ const graphed = graphSql(ctx, graph, tableNames(rls?.tables));
1637
1418
  let rowHelpers = "";
1638
1419
  if (rls?.rowHelpers !== void 0 || inheritTargets(policy).length > 0) try {
1639
- rowHelpers = rowHelpersSql(ctx, policy, [...options.fieldsMode === void 0 ? compiled.branches : rowBranches(compiled.branches), ...compiled.actionBranches], rls?.rowHelpers ?? [], rls?.tables, warnings);
1420
+ rowHelpers = rowHelpersSql(ctx, policy, [...options.fieldsMode === void 0 ? compiled.branches : rowBranches(compiled.branches), ...compiled.actionBranches], rls?.rowHelpers ?? [], tableNames(rls?.tables), warnings);
1640
1421
  } catch (cause) {
1641
1422
  return { refusal: describeError(cause) };
1642
1423
  }
1643
- const breakGlass = breakGlassSql(ctx, breakGlassEntries(policy, rls?.tables));
1424
+ const breakGlass = breakGlassSql(ctx, breakGlassEntries(policy, tableNames(rls?.tables)));
1644
1425
  return [
1645
1426
  approvals,
1646
1427
  owned === "" ? void 0 : owned,
@@ -1649,7 +1430,10 @@ function sectionsSql(input, policy, options, built, generated, jsonSchema, warni
1649
1430
  rowHelpers === "" ? void 0 : rowHelpers
1650
1431
  ];
1651
1432
  }
1652
- /** Whether `anon` calls a helper: `rls.anonExecute`, an anon field view or an anon branch reading the user id. */
1433
+ /**
1434
+ * Whether `anon` calls a helper: `rls.anonExecute`, an anon field view or an
1435
+ * anon branch reading the user id.
1436
+ */
1653
1437
  function anonExecutes(input, { compiled, views }) {
1654
1438
  return input.config.rls?.anonExecute === true || views.some((view) => view.roles.includes("anon")) || compiled.branches.some((branch) => branch.roles.includes("anon") && [
1655
1439
  branch.access,
@@ -1657,7 +1441,10 @@ function anonExecutes(input, { compiled, views }) {
1657
1441
  branch.check
1658
1442
  ].some((sql) => sql?.includes("permdock_user_id") === true));
1659
1443
  }
1660
- /** Everything before the table policies: helpers, sections, shims, scaffold and triggers. */
1444
+ /**
1445
+ * Everything before the table policies: helpers, sections, shims, scaffold and
1446
+ * triggers.
1447
+ */
1661
1448
  function preambleSql(input, policy, options, built, generated, jsonSchema, warnings) {
1662
1449
  const rls = input.config.rls;
1663
1450
  const { ctx } = built;
@@ -1689,7 +1476,7 @@ function preambleSql(input, policy, options, built, generated, jsonSchema, warni
1689
1476
  ...rls?.trustedReaders === void 0 ? {} : { trustedReaders: rls.trustedReaders }
1690
1477
  }),
1691
1478
  permissionHelpersSql(ctx, grants, renamed, anonExecute),
1692
- permissionHoldersSql(ctx) || void 0,
1479
+ nonEmptyOr(permissionHoldersSql(ctx), void 0),
1693
1480
  actorKinds === void 0 ? void 0 : actorMayWriteSql(ctx, actorKinds),
1694
1481
  usesLiveSession ? sessionLiveHelperSql(ctx) : void 0,
1695
1482
  ...sections,
@@ -1715,6 +1502,14 @@ function preambleSql(input, policy, options, built, generated, jsonSchema, warni
1715
1502
  audit
1716
1503
  };
1717
1504
  }
1505
+ function seedsBody(ctx, schema, rows) {
1506
+ switch (ctx.seeds) {
1507
+ case "function": return `select ${quoteQualified(`${schema}.seed`)}();`;
1508
+ case "view": return "-- rls.seeds: 'view': the role_permissions rows are in the helpers part";
1509
+ case void 0: return seedSql(ctx, rows);
1510
+ default: return ctx.seeds;
1511
+ }
1512
+ }
1718
1513
  function forceWarnings({ graph }, warnings) {
1719
1514
  for (const entry of graph.values()) {
1720
1515
  const own = [...entry.relations].filter((name) => !("edge" in (entry.node.relations[name] ?? {})));
@@ -1722,9 +1517,9 @@ function forceWarnings({ graph }, warnings) {
1722
1517
  }
1723
1518
  }
1724
1519
  /**
1725
- * The row columns that belong to a table. With `rls.tables` set, a resource
1726
- * it does not name is a table only when generated policies are written for
1727
- * it; with `--helpers-only` it has none, so it gets no index.
1520
+ * The row columns that belong to a table. With `rls.tables` set, a resource it
1521
+ * does not name is a table only when generated policies are written for it;
1522
+ * with `--helpers-only` it has none, so it gets no index.
1728
1523
  */
1729
1524
  function tableColumns(rowColumns, tables, helpersOnly, warnings) {
1730
1525
  if (tables === void 0) return rowColumns;
@@ -1774,7 +1569,10 @@ function emitTarget(input, built, generated, preamble) {
1774
1569
  outRel
1775
1570
  };
1776
1571
  }
1777
- /** The generated SQL for one run, before `--db-url-stdin` prunes the index targets. */
1572
+ /**
1573
+ * The generated SQL for one run, before `--db-url-stdin` prunes the index
1574
+ * targets.
1575
+ */
1778
1576
  function generateSql(input, policy, options, built, warnings) {
1779
1577
  const rls = input.config.rls;
1780
1578
  const { ctx } = built;
@@ -1795,18 +1593,163 @@ function generateSql(input, policy, options, built, warnings) {
1795
1593
  ...generated,
1796
1594
  ...emitted,
1797
1595
  backfill: checked.backfill,
1798
- indexes: indexTargets(ctx, tableColumns(generated.compiled.rowColumns, rls?.tables, helpersOnly, warnings)),
1596
+ indexes: indexTargets(ctx, tableColumns(generated.compiled.rowColumns, tableNames(rls?.tables), helpersOnly, warnings)),
1799
1597
  parts: (indexes) => ({
1800
1598
  policies: dialectRoles(withHelperTables(emitSql(policies, "", force, views), helperTables), ctx.dialect),
1801
1599
  declarativePolicies: dialectRoles(emitSql(policies, "", force, views), ctx.dialect),
1802
1600
  ...guardedTables === void 0 ? {} : { servicePolicies: dialectRoles(guardedTables, ctx.dialect) },
1803
1601
  helpers: dialectRoles(emitSql([], preamble, false, helpersOnly ? views : []), ctx.dialect),
1804
- seeds: `${SEEDS_MARKER} schema=${built.schema}\n${seedSql(ctx, generated.compiled.rolePermissions)}\n${seedsSplit ? audit : ""}`,
1602
+ seeds: `${SEEDS_MARKER} schema=${built.schema}\n${seedsBody(ctx, built.schema, generated.compiled.rolePermissions)}\n${seedsSplit ? audit : ""}`,
1805
1603
  indexes: `${INDEXES_MARKER}\n${indexesSql(indexes)}\n`
1806
1604
  })
1807
1605
  };
1808
1606
  }
1809
1607
  //#endregion
1608
+ //#region src/cli/rls/index-scan.ts
1609
+ /**
1610
+ * An index PermDock generated (`permdock_<table>_<columns>_idx`), in the
1611
+ * indexes part or in a migration derived from it: it never counts as serving
1612
+ * its own target.
1613
+ */
1614
+ const GENERATED_INDEX = /\bcreate\s+index\s+(?:if\s+not\s+exists\s+)?"?permdock_\w*_idx"?[^;]*;/giu;
1615
+ const IDENT = String.raw`(?:"(?:[^"]|"")+"|[A-Za-z_][\w$]*)`;
1616
+ const NAME = String.raw`${IDENT}(?:\s*\.\s*${IDENT})?`;
1617
+ const CREATE_INDEX = new RegExp(String.raw`\bcreate\s+(?:unique\s+)?index\s+(?:concurrently\s+)?(?:if\s+not\s+exists\s+)?(?:${IDENT}\s+)?on\s+(?:only\s+)?(${NAME})\s*(?:using\s+\w+\s*)?\(`, "giu");
1618
+ const ADD_KEY = new RegExp(String.raw`\balter\s+table\s+(?:if\s+exists\s+)?(?:only\s+)?(${NAME})\s+add\s+(?:constraint\s+${IDENT}\s+)?(?:unique|primary\s+key)\s*(?:nulls\s+not\s+distinct\s*)?\(`, "giu");
1619
+ const CREATE_TABLE = new RegExp(String.raw`\bcreate\s+(?:unlogged\s+|temp(?:orary)?\s+)?table\s+(?:if\s+not\s+exists\s+)?(${NAME})\s*\(`, "giu");
1620
+ const TABLE_NAME = new RegExp(String.raw`\bcreate\s+(?:unlogged\s+)?table\s+(?:if\s+not\s+exists\s+)?(${NAME})`, "giu");
1621
+ const TABLE_KEY = new RegExp(String.raw`^(?:constraint\s+${IDENT}\s+)?(?:unique|primary\s+key)\s*(?:nulls\s+not\s+distinct\s*)?\(`, "iu");
1622
+ const COLUMN_KEY = new RegExp(String.raw`^(${IDENT})\s+[^,]*?\b(?:primary\s+key|unique)\b`, "iu");
1623
+ const NOT_A_COLUMN = /^(?:constraint|check|foreign|exclude|like)\b/iu;
1624
+ function bare(part) {
1625
+ const trimmed = part.trim();
1626
+ return trimmed.startsWith("\"") ? trimmed.slice(1, -1).replaceAll("\"\"", "\"") : trimmed.toLowerCase();
1627
+ }
1628
+ /** `schema.table`, with `public` for an unqualified name. */
1629
+ function qualified(raw) {
1630
+ const parts = raw.split(".").map(bare);
1631
+ return parts.length === 1 ? `public.${parts.join("")}` : parts.join(".");
1632
+ }
1633
+ function stripComments(sql) {
1634
+ return sql.replaceAll(/\/\*[\s\S]*?\*\//gu, " ").replaceAll(/--[^\n]*/gu, " ");
1635
+ }
1636
+ /**
1637
+ * The text between the `(` that ends `match` and its closing `)`, and the index
1638
+ * after it.
1639
+ */
1640
+ function group(text, open) {
1641
+ let depth = 1;
1642
+ for (let index = open; index < text.length; index += 1) {
1643
+ const char = text[index];
1644
+ if (char === "'" || char === "\"") {
1645
+ const close = text.indexOf(char, index + 1);
1646
+ if (close === -1) return;
1647
+ index = close;
1648
+ } else if (char === "(") depth += 1;
1649
+ else if (char === ")") {
1650
+ depth -= 1;
1651
+ if (depth === 0) return {
1652
+ body: text.slice(open, index),
1653
+ end: index + 1
1654
+ };
1655
+ }
1656
+ }
1657
+ }
1658
+ function splitTop(body) {
1659
+ const items = [];
1660
+ let depth = 0;
1661
+ let from = 0;
1662
+ for (let index = 0; index < body.length; index += 1) {
1663
+ const char = body[index];
1664
+ if (char === "'" || char === "\"") index = Math.max(body.indexOf(char, index + 1), index);
1665
+ else if (char === "(") depth += 1;
1666
+ else if (char === ")") depth -= 1;
1667
+ else if (char === "," && depth === 0) {
1668
+ items.push(body.slice(from, index));
1669
+ from = index + 1;
1670
+ }
1671
+ }
1672
+ items.push(body.slice(from));
1673
+ return items.map((item) => item.trim());
1674
+ }
1675
+ /** The first capture group of a match; every pattern here captures it. */
1676
+ function captured(match) {
1677
+ return match.slice(1, 2).join("");
1678
+ }
1679
+ /** The index just past a match. */
1680
+ function endOf(match) {
1681
+ return match.index + match[0].length;
1682
+ }
1683
+ /** The plain key columns of an index column list, cut at the first expression. */
1684
+ function keyColumns(body) {
1685
+ const columns = [];
1686
+ for (const item of splitTop(body)) {
1687
+ const match = new RegExp(`^(${IDENT})(?:\\s|$)`, "u").exec(item);
1688
+ if (match === null || item.startsWith("(")) break;
1689
+ columns.push(bare(captured(match)));
1690
+ }
1691
+ return columns;
1692
+ }
1693
+ /**
1694
+ * Key columns of each index, unique constraint and primary key the SQL creates,
1695
+ * per `schema.table`.
1696
+ */
1697
+ function indexKeysFromSql(sql) {
1698
+ const text = stripComments(sql);
1699
+ const keys = {};
1700
+ const add = (table, columns) => {
1701
+ if (columns.length > 0) (keys[qualified(table)] ??= []).push(columns);
1702
+ };
1703
+ for (const pattern of [CREATE_INDEX, ADD_KEY]) for (const match of text.matchAll(pattern)) {
1704
+ const found = group(text, endOf(match));
1705
+ if (found !== void 0) add(captured(match), keyColumns(found.body));
1706
+ }
1707
+ for (const match of text.matchAll(CREATE_TABLE)) {
1708
+ const found = group(text, endOf(match));
1709
+ if (found === void 0) continue;
1710
+ for (const item of splitTop(found.body)) if (TABLE_KEY.test(item)) {
1711
+ const inner = group(item, item.indexOf("(") + 1);
1712
+ if (inner !== void 0) add(captured(match), keyColumns(inner.body));
1713
+ } else if (!NOT_A_COLUMN.test(item)) {
1714
+ const column = COLUMN_KEY.exec(item)?.[1];
1715
+ if (column !== void 0) add(captured(match), [bare(column)]);
1716
+ }
1717
+ }
1718
+ return keys;
1719
+ }
1720
+ /**
1721
+ * The index keys of the SQL files under the migration and schema folders of
1722
+ * `cwd`.
1723
+ */
1724
+ function indexKeysFromProject(cwd) {
1725
+ const all = {};
1726
+ for (const file of sqlFiles(cwd, [...MIGRATION_DIRS])) {
1727
+ const keys = indexKeysFromSql(readFileSync(file, "utf8").replaceAll(GENERATED_INDEX, ""));
1728
+ for (const [table, list] of Object.entries(keys)) (all[table] ??= []).push(...list);
1729
+ }
1730
+ return all;
1731
+ }
1732
+ /** Every `schema.table` the SQL creates, temporary tables left out. */
1733
+ function tablesFromSql(sql) {
1734
+ return new Set([...stripComments(sql).matchAll(TABLE_NAME)].map((match) => qualified(captured(match))));
1735
+ }
1736
+ /**
1737
+ * Every `schema.table` the SQL files under the migration and schema folders of
1738
+ * `cwd` create.
1739
+ */
1740
+ function tablesFromProject(cwd) {
1741
+ const all = /* @__PURE__ */ new Set();
1742
+ for (const file of sqlFiles(cwd, [...MIGRATION_DIRS])) for (const table of tablesFromSql(readFileSync(file, "utf8"))) all.add(table);
1743
+ return all;
1744
+ }
1745
+ /**
1746
+ * The targets no index in `keys` leads with: its key columns start with the
1747
+ * target's, as `pruneIndexTargets` reads a database.
1748
+ */
1749
+ function withoutServedTargets(targets, keys) {
1750
+ return targets.filter((target) => !(keys[target.table] ?? []).some((key) => target.columns.every((column, at) => key[at] === column)));
1751
+ }
1752
+ //#endregion
1810
1753
  //#region src/cli/rls/generate.ts
1811
1754
  function loadPolicy(cwd, config, from) {
1812
1755
  const policyPath = from === "drizzle" ? config.policy : from ?? config.policy;
@@ -1814,8 +1757,8 @@ function loadPolicy(cwd, config, from) {
1814
1757
  }
1815
1758
  const PROJECT_COMMAND = "permdock rls generate";
1816
1759
  /**
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`.
1760
+ * The policy `policyFromCatalog` rebuilds from the project's pushed catalog and
1761
+ * policy; a project with no pushed policy fails with `policy-not-published`.
1819
1762
  */
1820
1763
  async function projectPolicyOf(input, project) {
1821
1764
  const fetched = await fetchProjectPolicy({
@@ -1884,8 +1827,19 @@ async function runRlsGenerate(input) {
1884
1827
  const { ctx } = built;
1885
1828
  const { text, migration, migrationRel, helpersOnly } = generated;
1886
1829
  let indexes = generated.indexes;
1830
+ const bindings = tableBindings(generated.compiled.rowColumns.map((item) => item.resource), tableNames(input.config.rls?.tables), helpersOnly);
1831
+ if (input.config.rls?.tables === void 0) warnings.push(...bindingNotes(bindings, input.db !== void 0));
1832
+ warnings.push(...triggerNotes(text));
1833
+ if (ctx.seeds === "function") warnings.push(`run select ${built.schema}.seed(); after each deploy`);
1834
+ if (input.db === void 0) {
1835
+ indexes = withoutServedTargets(indexes, indexKeysFromProject(input.cwd));
1836
+ warnings.push(...unknownTableNotes(bindings, tablesFromProject(input.cwd)));
1837
+ }
1887
1838
  if (input.db !== void 0) try {
1888
- const pruned = pruneIndexTargets(indexes, await readTableIndexFacts(input.db, [...new Set(indexes.map((target) => target.table))], input.connect));
1839
+ const facts = await readTableIndexFacts(input.db, [.../* @__PURE__ */ new Set([...indexes.map((target) => target.table), ...bindings.map((binding) => binding.table)])], input.connect);
1840
+ const missing = missingBindings(bindings, facts.columns);
1841
+ if (missing.length > 0) return refused(`rls generate: ${missing.map((binding) => `resource ${binding.resource} is bound to ${binding.table}, which the database does not have`).join("; ")}; name the table in rls.tables, or set rls.tables.<resource>: false for a resource with no table`);
1842
+ const pruned = pruneIndexTargets(indexes, facts);
1889
1843
  indexes = pruned.targets;
1890
1844
  warnings.push(...pruned.warnings);
1891
1845
  } catch (cause) {
@@ -1907,10 +1861,19 @@ async function runRlsGenerate(input) {
1907
1861
  keys: {
1908
1862
  permissions: listPermissions(policy.vocabulary.permissions).map((leaf) => leaf.key),
1909
1863
  rowConditions: [...policyRowConditionKeys(policy)],
1864
+ levels: levelKeys(policy),
1910
1865
  renamed: Object.fromEntries(renamedKeys(policy.vocabulary.permissions))
1911
1866
  },
1912
1867
  schema: built.schema,
1913
1868
  helpersOnly,
1869
+ explain: explainRls({
1870
+ config: input.config,
1871
+ ctx,
1872
+ schema: built.schema,
1873
+ authorize: built.authorize,
1874
+ generated,
1875
+ text
1876
+ }),
1914
1877
  ...extras
1915
1878
  };
1916
1879
  const planned = outputFiles({
@@ -1974,7 +1937,10 @@ async function runRlsGenerate(input) {
1974
1937
  //#endregion
1975
1938
  //#region src/cli/rls/introspect.ts
1976
1939
  const ROLES = ["anon", "authenticated"];
1977
- /** Neon's `anonymous` is the role `rls generate` writes as `anon` for the other dialects. */
1940
+ /**
1941
+ * Neon's `anonymous` is the role `rls generate` writes as `anon` for the other
1942
+ * dialects.
1943
+ */
1978
1944
  function liveRole(role) {
1979
1945
  return role === "anonymous" ? "anon" : role;
1980
1946
  }
@@ -1990,14 +1956,107 @@ function normalizeTableName(table) {
1990
1956
  return parts.length === 1 ? `public.${parts[0] ?? ""}` : parts.join(".");
1991
1957
  }
1992
1958
  const FUNCTION_HEADER = /create or replace function ("?)([A-Za-z_][\w$]*)\1\.("?)([A-Za-z_][\w$]*)\3\s*\(([\s\S]*?)\nas \$/gu;
1959
+ const TYPE_ALIASES = {
1960
+ int: "integer",
1961
+ int4: "integer",
1962
+ int2: "smallint",
1963
+ int8: "bigint",
1964
+ bool: "boolean",
1965
+ varchar: "character varying",
1966
+ char: "character",
1967
+ float4: "real",
1968
+ float8: "double precision",
1969
+ timestamp: "timestamp without time zone",
1970
+ timestamptz: "timestamp with time zone",
1971
+ time: "time without time zone",
1972
+ timetz: "time with time zone"
1973
+ };
1974
+ /**
1975
+ * A parameter type as `oidvectortypes` prints it: lower case, no quotes, no
1976
+ * schema, no type modifier, aliases spelled out. A `%type` reference is `*`,
1977
+ * which matches any type.
1978
+ */
1979
+ function normalizeParameterType(type) {
1980
+ const text = type.trim().toLowerCase().replaceAll("\"", "");
1981
+ if (text.endsWith("%type")) return "*";
1982
+ const array = /(?:\[\d*\])+$/u.exec(text)?.[0] ?? "";
1983
+ const base = text.slice(0, text.length - array.length).replaceAll(/\([^)]*\)/gu, "").replaceAll(/\s+/gu, " ").trim();
1984
+ const unqualified = /^[\w$]+(?:\.[\w$]+)+$/u.test(base) ? base.split(".").at(-1) ?? base : base;
1985
+ return `${TYPE_ALIASES[unqualified] ?? unqualified}${"[]".repeat(array.split("[").length - 1)}`;
1986
+ }
1987
+ function splitTopLevel(text) {
1988
+ const parts = [];
1989
+ let depth = 0;
1990
+ let quoted = false;
1991
+ let start = 0;
1992
+ for (let index = 0; index < text.length; index += 1) {
1993
+ const char = text[index];
1994
+ if (char === "'") quoted = !quoted;
1995
+ else if (!quoted && char === "(") depth += 1;
1996
+ else if (!quoted && char === ")") depth -= 1;
1997
+ else if (!quoted && depth === 0 && char === ",") {
1998
+ parts.push(text.slice(start, index));
1999
+ start = index + 1;
2000
+ }
2001
+ }
2002
+ parts.push(text.slice(start));
2003
+ return parts.map((part) => part.trim()).filter((part) => part !== "");
2004
+ }
2005
+ /**
2006
+ * The input parameter types of a function header that starts after its opening
2007
+ * parenthesis. Every generated parameter is named, so the first word of a
2008
+ * parameter with more than one is its name.
2009
+ */
2010
+ function parameterTypes(header) {
2011
+ let depth = 0;
2012
+ let end = header.length;
2013
+ for (let index = 0; index < header.length; index += 1) {
2014
+ const char = header[index];
2015
+ if (char === "(") depth += 1;
2016
+ else if (char === ")") {
2017
+ if (depth === 0) {
2018
+ end = index;
2019
+ break;
2020
+ }
2021
+ depth -= 1;
2022
+ }
2023
+ }
2024
+ return splitTopLevel(header.slice(0, end)).flatMap((parameter) => {
2025
+ const words = parameter.replace(/\s+(?:default\b|=)[\s\S]*$/iu, "").split(/\s+/u);
2026
+ const mode = words[0]?.toLowerCase();
2027
+ if (mode === "out") return [];
2028
+ const rest = mode === "in" || mode === "inout" || mode === "variadic" ? words.slice(1) : words;
2029
+ return [normalizeParameterType((rest.length > 1 ? rest.slice(1) : rest).join(" "))];
2030
+ });
2031
+ }
2032
+ /** `schema.name(type, type)`, the key a helper is checked by. */
2033
+ function signatureOf(name, types) {
2034
+ return `${name}(${types.join(", ")})`;
2035
+ }
1993
2036
  function helpersOf(sql) {
1994
2037
  const found = /* @__PURE__ */ new Set();
1995
2038
  for (const match of sql.matchAll(FUNCTION_HEADER)) {
1996
- const [header = "", , schema = "", , name = ""] = match;
1997
- if (/\bsecurity definer\b/u.test(header)) found.add(`${schema}.${name}`);
2039
+ const [header = "", , schema = "", , name = "", rest = ""] = match;
2040
+ if (/\bsecurity definer\b/u.test(header)) found.add(signatureOf(`${schema}.${name}`, parameterTypes(rest)));
1998
2041
  }
1999
2042
  return [...found];
2000
2043
  }
2044
+ function nameOfSignature(signature) {
2045
+ return signature.slice(0, signature.indexOf("("));
2046
+ }
2047
+ function typesOfSignature(signature) {
2048
+ return splitTopLevel(signature.slice(signature.indexOf("(") + 1, signature.lastIndexOf(")"))).map(normalizeParameterType);
2049
+ }
2050
+ /** The actual overload `expected` names; `*` matches any parameter type. */
2051
+ function matchHelper(expected, actual) {
2052
+ if (Object.hasOwn(actual, expected)) return actual[expected];
2053
+ const name = nameOfSignature(expected);
2054
+ const want = typesOfSignature(expected);
2055
+ for (const [signature, helper] of Object.entries(actual)) {
2056
+ const have = typesOfSignature(signature);
2057
+ if (nameOfSignature(signature) === name && have.length === want.length && want.every((type, index) => type === "*" || type === have[index])) return helper;
2058
+ }
2059
+ }
2001
2060
  function expectedRls(policies, sql, indexes = []) {
2002
2061
  const tables = [...new Set(policies.map((item) => normalizeTableName(item.table)))];
2003
2062
  const grants = {};
@@ -2027,14 +2086,14 @@ function expectedRls(policies, sql, indexes = []) {
2027
2086
  };
2028
2087
  }
2029
2088
  /**
2030
- * One warning per index target no index starts with. A missing index slows
2031
- * the policy down but grants nothing, so these do not fail `verify`.
2089
+ * One warning per index target no index starts with. A missing index slows the
2090
+ * policy down but grants nothing, so these do not fail `verify`.
2032
2091
  */
2033
2092
  function missingIndexes(expected, actual) {
2034
2093
  return expected.indexes.flatMap((target) => {
2035
2094
  const column = target.columns[0];
2036
2095
  const columns = actual.columns?.[target.table];
2037
- return column === void 0 || columns !== void 0 && !columns.includes(column) || actual.leadingColumns[target.table]?.includes(column) === true ? [] : [`warning: ${target.table}: no index starts with ${column}, which the policies or helpers filter on; add it, or write it with rls generate --split ...,indexes`];
2096
+ return column === void 0 || columns !== void 0 && !columns.includes(column) || actual.leadingColumns[target.table]?.includes(column) === true ? [] : [rlsFinding("index-missing", target.table, column, `${target.table}: no index starts with ${column}, which the policies or helpers filter on; add it, or write it with rls generate --split ...,indexes`, "warning")];
2038
2097
  });
2039
2098
  }
2040
2099
  function sameList(left, right) {
@@ -2042,42 +2101,45 @@ function sameList(left, right) {
2042
2101
  const b = right.toSorted();
2043
2102
  return a.length === b.length && a.every((item, index) => item === b[index]);
2044
2103
  }
2045
- /** One line per difference; policy expressions are not compared, since Postgres rewrites their text. */
2104
+ /**
2105
+ * One finding per difference; policy expressions are not compared, since
2106
+ * Postgres rewrites their text.
2107
+ */
2046
2108
  function diffRls(expected, actual, options = {}) {
2047
2109
  const out = [];
2048
2110
  const found = new Map(actual.policies.map((item) => [`${item.table}\u0000${item.name}`, item]));
2049
2111
  for (const want of expected.policies) {
2050
2112
  const have = found.get(`${want.table}\u0000${want.name}`);
2051
2113
  if (have === void 0) {
2052
- out.push(`${want.table}: policy ${want.name} is missing`);
2114
+ out.push(rlsFinding("policy-missing", want.table, want.name, `${want.table}: policy ${want.name} is missing`));
2053
2115
  continue;
2054
2116
  }
2055
- if (have.command !== want.command) out.push(`${want.table}: policy ${want.name} is for ${have.command}, expected ${want.command}`);
2056
- if (have.permissive !== want.permissive) out.push(`${want.table}: policy ${want.name} is ${have.permissive ? "permissive" : "restrictive"}, expected ${want.permissive ? "permissive" : "restrictive"}`);
2057
- if (!sameList(have.roles, want.roles)) out.push(`${want.table}: policy ${want.name} applies to ${have.roles.join(", ")}, expected ${want.roles.join(", ")}`);
2117
+ if (have.command !== want.command) out.push(rlsFinding("policy-command", want.table, want.name, `${want.table}: policy ${want.name} is for ${have.command}, expected ${want.command}`));
2118
+ if (have.permissive !== want.permissive) out.push(rlsFinding("policy-mode", want.table, want.name, `${want.table}: policy ${want.name} is ${have.permissive ? "permissive" : "restrictive"}, expected ${want.permissive ? "permissive" : "restrictive"}`));
2119
+ if (!sameList(have.roles, want.roles)) out.push(rlsFinding("policy-roles", want.table, want.name, `${want.table}: policy ${want.name} applies to ${have.roles.join(", ")}, expected ${want.roles.join(", ")}`));
2058
2120
  }
2059
2121
  const wanted = new Set(expected.policies.map((item) => `${item.table}\u0000${item.name}`));
2060
- for (const have of actual.policies) if (expected.tables.includes(have.table) && !wanted.has(`${have.table}\u0000${have.name}`)) out.push(`${have.table}: policy ${have.name} is not generated (${have.permissive ? "permissive" : "restrictive"} ${have.command}); a permissive one widens access`);
2122
+ for (const have of actual.policies) if (expected.tables.includes(have.table) && !wanted.has(`${have.table}\u0000${have.name}`)) out.push(rlsFinding("policy-extra", have.table, have.name, `${have.table}: policy ${have.name} is not generated (${have.permissive ? "permissive" : "restrictive"} ${have.command}); a permissive one widens access`));
2061
2123
  for (const table of expected.tables) {
2062
2124
  if (actual.rlsEnabled[table] !== true) {
2063
- out.push(actual.rlsEnabled[table] === void 0 ? `${table}: table is missing` : `${table}: row level security is disabled`);
2125
+ out.push(actual.rlsEnabled[table] === void 0 ? rlsFinding("table-missing", table, "table", `${table}: table is missing`) : rlsFinding("rls-disabled", table, "table", `${table}: row level security is disabled`));
2064
2126
  continue;
2065
2127
  }
2066
2128
  for (const role of ROLES) {
2067
2129
  const want = expected.grants[table]?.[role] ?? [];
2068
2130
  const have = actual.grants[table]?.[role] ?? [];
2069
- for (const privilege of want) if (!have.includes(privilege) && !(privilege === "select" && options.columnGrants === true)) out.push(`${table}: ${role} lacks ${privilege}, so the policy answers 42501 instead of filtering`);
2070
- for (const privilege of have) if (!want.includes(privilege)) out.push(`${table}: ${role} holds ${privilege}, which no generated policy allows`);
2131
+ for (const privilege of want) if (!have.includes(privilege) && !(privilege === "select" && options.columnGrants === true)) out.push(rlsFinding("grant-missing", table, `${role}.${privilege}`, `${table}: ${role} lacks ${privilege}, so the policy answers 42501 instead of filtering`));
2132
+ for (const privilege of have) if (!want.includes(privilege)) out.push(rlsFinding("grant-extra", table, `${role}.${privilege}`, `${table}: ${role} holds ${privilege}, which no generated policy allows`));
2071
2133
  }
2072
2134
  }
2073
2135
  for (const helper of expected.helpers) {
2074
- const have = actual.helpers[helper];
2136
+ const have = matchHelper(helper, actual.helpers);
2075
2137
  if (have === void 0) {
2076
- out.push(`${helper}: helper is missing`);
2138
+ out.push(rlsFinding("helper-missing", helper, "helper", `${helper}: helper is missing`));
2077
2139
  continue;
2078
2140
  }
2079
- if (!have.securityDefiner) out.push(`${helper}: helper is not security definer`);
2080
- if (!have.emptySearchPath) out.push(`${helper}: helper does not set search_path = ''`);
2141
+ if (!have.securityDefiner) out.push(rlsFinding("helper-definer", helper, "helper", `${helper}: helper is not security definer`));
2142
+ if (!have.emptySearchPath) out.push(rlsFinding("helper-search-path", helper, "helper", `${helper}: helper does not set search_path = ''`));
2081
2143
  }
2082
2144
  return out;
2083
2145
  }
@@ -2093,25 +2155,22 @@ from information_schema.role_table_grants
2093
2155
  where grantee in ('anon', 'anonymous', 'authenticated')
2094
2156
  and lower(privilege_type) in ('select', 'insert', 'update', 'delete')
2095
2157
  and table_schema || '.' || table_name = any($1::text[])`;
2096
- const HELPERS_SQL = `select n.nspname || '.' || p.proname as target, p.prosecdef as definer,
2158
+ const HELPERS_SQL = `select n.nspname || '.' || p.proname || '(' || oidvectortypes(p.proargtypes) || ')' as target, p.prosecdef as definer,
2097
2159
  coalesce(array_to_string(p.proconfig, ','), '') as config
2098
2160
  from pg_proc p
2099
2161
  join pg_namespace n on n.oid = p.pronamespace
2100
2162
  where n.nspname || '.' || p.proname = any($1::text[])`;
2101
- const INDEXES_SQL = `select n.nspname || '.' || c.relname as target, a.attname as leading
2102
- from pg_index i
2103
- join pg_class c on c.oid = i.indrelid
2104
- join pg_namespace n on n.oid = c.relnamespace
2105
- join pg_attribute a on a.attrelid = i.indrelid and a.attnum = i.indkey[0]
2106
- where i.indpred is null and n.nspname || '.' || c.relname = any($1::text[])`;
2107
2163
  function commandOf(cmd) {
2108
- const text = String(cmd ?? "").toLowerCase();
2164
+ const text = typeof cmd === "string" ? cmd.toLowerCase() : "";
2109
2165
  return text === "*" ? "all" : text;
2110
2166
  }
2111
2167
  function connectIntrospect(db) {
2112
2168
  return connectPgPool(db, "permdock rls verify --introspect");
2113
2169
  }
2114
- /** Reads the catalogs for the tables and helpers `expected` names (postgres-meta's queries, trimmed). */
2170
+ /**
2171
+ * Reads the catalogs for the tables and helpers `expected` names
2172
+ * (postgres-meta's queries, trimmed).
2173
+ */
2115
2174
  async function introspectRls(db, expected, connect = connectIntrospect) {
2116
2175
  const client = await connect(db);
2117
2176
  try {
@@ -2121,14 +2180,17 @@ async function introspectRls(db, expected, connect = connectIntrospect) {
2121
2180
  client.query(POLICIES_SQL, [tables]),
2122
2181
  client.query(TABLES_SQL, [tables]),
2123
2182
  client.query(GRANTS_SQL, [tables]),
2124
- client.query(HELPERS_SQL, [[...expected.helpers]]),
2125
- client.query(INDEXES_SQL, [indexed]),
2183
+ client.query(HELPERS_SQL, [[...new Set(expected.helpers.map(nameOfSignature))]]),
2184
+ client.query(INDEX_COLUMNS_SQL, [indexed]),
2126
2185
  client.query(COLUMNS_SQL, [indexed])
2127
2186
  ]);
2128
2187
  const columnsOf = {};
2129
2188
  for (const row of columns.rows) (columnsOf[String(row["target"])] ??= []).push(String(row["name"]));
2130
2189
  const leadingColumns = {};
2131
- for (const row of indexes.rows) (leadingColumns[String(row["target"])] ??= []).push(String(row["leading"]));
2190
+ for (const row of indexes.rows) {
2191
+ const lead = (Array.isArray(row["columns"]) ? row["columns"] : []).at(0);
2192
+ if (typeof lead === "string") (leadingColumns[String(row["target"])] ??= []).push(lead);
2193
+ }
2132
2194
  const byTable = {};
2133
2195
  for (const row of grants.rows) {
2134
2196
  const table = String(row["target"]);
@@ -2146,7 +2208,7 @@ async function introspectRls(db, expected, connect = connectIntrospect) {
2146
2208
  })),
2147
2209
  rlsEnabled: Object.fromEntries(enabled.rows.map((row) => [String(row["target"]), row["enabled"] === true])),
2148
2210
  grants: byTable,
2149
- helpers: Object.fromEntries(helpers.rows.map((row) => [String(row["target"]), {
2211
+ helpers: Object.fromEntries(helpers.rows.map((row) => [signatureOf(nameOfSignature(String(row["target"])), typesOfSignature(String(row["target"]))), {
2150
2212
  securityDefiner: row["definer"] === true,
2151
2213
  emptySearchPath: String(row["config"]).split(",").some((item) => item === "search_path=\"\"" || item === "search_path=''")
2152
2214
  }])),
@@ -2163,10 +2225,10 @@ const WRITES = [
2163
2225
  "delete"
2164
2226
  ];
2165
2227
  /**
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.
2228
+ * Each table and write command whose permissive policies reach `authenticated`
2229
+ * (or `public`) without the read-only actor check: no restrictive policy for
2230
+ * the command calls `permdock_actor_may_write()`, and some permissive one does
2231
+ * not.
2170
2232
  */
2171
2233
  function unguardedWrites(policies, fn) {
2172
2234
  const out = [];
@@ -2176,7 +2238,7 @@ function unguardedWrites(policies, fn) {
2176
2238
  const permissive = applying.filter((policy) => policy.restrictive !== true && (policy.roles ?? []).some((role) => role === "authenticated" || role === "public"));
2177
2239
  if (permissive.length === 0) continue;
2178
2240
  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)`);
2241
+ if (!(applying.some((policy) => policy.restrictive === true && calls(policy.expression)) || permissive.every((policy) => calls(policy.expression)))) out.push(rlsFinding("write-unguarded", table, command, `${table}: ${command} policies for authenticated do not call ${fn}(), so a read-only support or impersonation session can write (rls.readOnlyActors)`));
2180
2242
  }
2181
2243
  return out;
2182
2244
  }
@@ -2205,26 +2267,40 @@ function inScope(table) {
2205
2267
  function seedLine(row) {
2206
2268
  return `${row.role} ${row.effect} ${row.grantKey} on ${row.scope}`;
2207
2269
  }
2208
- /** Drift is an error; `info` names RLS tables whose policies call no PermDock helper. */
2270
+ function seedKey(line) {
2271
+ return line.replace(" on ", " ").replaceAll(" ", "/");
2272
+ }
2273
+ /**
2274
+ * Drift is an error; `info` names RLS tables whose policies call no PermDock
2275
+ * helper.
2276
+ */
2209
2277
  function diffMixed(expected, actual) {
2210
2278
  const drift = [];
2279
+ const seeds = `${expected.schema}.role_permissions`;
2211
2280
  const want = new Set(expected.seeds.map(seedLine));
2212
2281
  const have = new Set(actual.seeds.map(seedLine));
2213
- for (const line of want) if (!have.has(line)) drift.push(`${expected.schema}.role_permissions: missing ${line}`);
2214
- for (const line of have) if (!want.has(line)) drift.push(`${expected.schema}.role_permissions: unexpected ${line}`);
2282
+ for (const line of want) if (!have.has(line)) drift.push(rlsFinding("seed-missing", seeds, seedKey(line), `${seeds}: missing ${line}`));
2283
+ for (const line of have) if (!want.has(line)) drift.push(rlsFinding("seed-extra", seeds, seedKey(line), `${seeds}: unexpected ${line}`));
2215
2284
  const permissions = new Set(expected.permissions);
2216
2285
  const conditioned = new Set(expected.rowConditions);
2286
+ const levels = new Map((expected.levels ?? []).map((level) => [level.key, level]));
2287
+ const notes = [];
2217
2288
  const covered = /* @__PURE__ */ new Set();
2218
2289
  for (const policy of actual.policies) {
2219
2290
  if (callsHelper(policy.expression)) covered.add(policy.table);
2220
2291
  const blind = new Set(helperCallKeys(policy.expression, true));
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`);
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`);
2292
+ for (const key of helperCallKeys(policy.expression)) {
2293
+ const level = levels.get(key);
2294
+ if (level !== void 0) {
2295
+ if (level.conditioned) notes.push(rlsFinding("level-unconditioned", policy.table, `${policy.name}/${key}`, `${policy.table}: policy ${policy.name} passes ${key}, which skips the level's row condition, so holders of it are denied there; use ${level.rowHelper}(${policy.table.slice(policy.table.lastIndexOf(".") + 1)}, '${level.permission}')`, "info"));
2296
+ } else if (!permissions.has(key)) drift.push(rlsFinding("undeclared-key", policy.table, `${policy.name}/${key}`, `${policy.table}: policy ${policy.name} passes ${key}, which the policy does not declare, so it always denies`));
2297
+ else if (conditioned.has(key) && blind.has(key)) drift.push(rlsFinding("condition-blind", policy.table, `${policy.name}/${key}`, `${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`));
2298
+ }
2223
2299
  }
2224
2300
  if (expected.readOnlyActors === true) drift.push(...unguardedWrites(actual.policies, ACTOR_MAY_WRITE));
2225
2301
  return {
2226
2302
  drift,
2227
- info: actual.rlsTables.filter((table) => !covered.has(table)).map((table) => `${table}: no policy calls a PermDock helper`)
2303
+ info: [...notes, ...actual.rlsTables.filter((table) => !covered.has(table)).map((table) => rlsFinding("helper-uncalled", table, "table", `${table}: no policy calls a PermDock helper`, "info"))]
2228
2304
  };
2229
2305
  }
2230
2306
  const ALL_POLICIES_SQL = `select schemaname || '.' || tablename as target, policyname,
@@ -2237,7 +2313,10 @@ from pg_class c
2237
2313
  join pg_namespace n on n.oid = c.relnamespace
2238
2314
  where c.relkind in ('r', 'p') and c.relrowsecurity`;
2239
2315
  const quoteIdent = escapeSqlIdent;
2240
- /** Reads `role_permissions`, `pg_policies` and the RLS-enabled tables for {@link diffMixed}. */
2316
+ /**
2317
+ * Reads `role_permissions`, `pg_policies` and the RLS-enabled tables for
2318
+ * {@link diffMixed}.
2319
+ */
2241
2320
  async function introspectMixed(db, schema, connect = connectIntrospect) {
2242
2321
  const client = await connect(db);
2243
2322
  try {
@@ -2260,7 +2339,7 @@ async function introspectMixed(db, schema, connect = connectIntrospect) {
2260
2339
  expression: String(row["expression"]),
2261
2340
  command: String(row["command"]),
2262
2341
  restrictive: row["restrictive"] === true,
2263
- roles: String(row["roles"] ?? "").split(",").filter((role) => role !== "")
2342
+ roles: (typeof row["roles"] === "string" ? row["roles"] : "").split(",").filter((role) => role !== "")
2264
2343
  })).filter((row) => inScope(row.table)),
2265
2344
  rlsTables: tables.rows.map((row) => String(row["target"])).filter(inScope).toSorted()
2266
2345
  };
@@ -2270,12 +2349,15 @@ async function introspectMixed(db, schema, connect = connectIntrospect) {
2270
2349
  }
2271
2350
  //#endregion
2272
2351
  //#region src/cli/rls/rls.ts
2273
- const RLS_HELP = `permdock rls generate | import | verify | migrate
2352
+ /** `rls migrate` and `rls import` read the pgsql-parser 18 AST. */
2353
+ const PGSQL_PARSER_MAJOR = 18;
2354
+ const RLS_HELP = `permdock rls generate | import | verify | migrate | explain
2274
2355
 
2275
2356
  generate Compile the policy into RLS policies and helpers
2276
2357
  import Turn existing policies (--sql or --db-url-stdin) into a permissions module
2277
2358
  verify Run the fixtures, or compare a live database with --introspect
2278
2359
  migrate Rewrite the rls.migrate helpers' calls onto the generated helpers
2360
+ explain Show the tables, membership sources, custom roles and triggers the SQL binds to
2279
2361
 
2280
2362
  Run permdock rls <command> --help for its flags.
2281
2363
  Never emits service_role, except in the execute grant rls.trustedReaders asks for. memberOf compiles through the dialect memberships mapping.
@@ -2318,11 +2400,48 @@ function generateInput(input, target, dialect) {
2318
2400
  ...isSupabaseSchemas(input.schemas) ? { schemas: input.schemas } : {},
2319
2401
  ...input.grantsOut === void 0 ? {} : { grantsOut: input.grantsOut },
2320
2402
  ...input.seedsOut === void 0 ? {} : { seedsOut: input.seedsOut },
2403
+ ...input.noSeeds === true ? { noSeeds: true } : {},
2321
2404
  ...input.backfillOut === void 0 ? {} : { backfillOut: input.backfillOut },
2322
2405
  helpersOnly: input.helpersOnly,
2323
2406
  shims: input.shims === true
2324
2407
  };
2325
2408
  }
2409
+ async function explain(input) {
2410
+ const dialect = asDialect(input.dialect ?? input.config.rls?.dialect);
2411
+ if (dialect === void 0) return {
2412
+ code: 2,
2413
+ output: "rls explain --dialect (or rls.dialect) must be supabase, neon or guc"
2414
+ };
2415
+ const generated = await runRlsGenerate({
2416
+ ...generateInput(input, "sql", dialect),
2417
+ check: false,
2418
+ write: false
2419
+ });
2420
+ if (generated.explain === void 0) return {
2421
+ code: generated.code,
2422
+ output: generated.output
2423
+ };
2424
+ let report = generated.explain;
2425
+ if (input.db !== void 0) try {
2426
+ const facts = await readTableIndexFacts(input.db, report.bindings.map((binding) => binding.table), input.connect);
2427
+ report = {
2428
+ ...report,
2429
+ bindings: report.bindings.map((binding) => ({
2430
+ ...binding,
2431
+ exists: facts.columns[binding.table] !== void 0
2432
+ }))
2433
+ };
2434
+ } catch (cause) {
2435
+ return {
2436
+ code: exitCodeOfError(cause),
2437
+ output: describeError(cause)
2438
+ };
2439
+ }
2440
+ return {
2441
+ code: 0,
2442
+ output: input.json ? JSON.stringify(report, null, 2) : describeExplain(report)
2443
+ };
2444
+ }
2326
2445
  async function migrate(input) {
2327
2446
  const config = input.config.rls?.migrate;
2328
2447
  if (config === void 0) return {
@@ -2347,8 +2466,8 @@ async function migrate(input) {
2347
2466
  code: 2,
2348
2467
  output: generated.output
2349
2468
  };
2350
- await requirePeer(() => import("pgsql-parser"), "pgsql-parser", "permdock rls migrate", input.cwd);
2351
- const { migrateTarget, runRlsMigrate } = await import("./migrate-CIq4-E9F.js");
2469
+ await requirePeer(() => import("pgsql-parser"), "pgsql-parser", "permdock rls migrate", input.cwd, PGSQL_PARSER_MAJOR);
2470
+ const { migrateTarget, runRlsMigrate } = await import("./migrate-B7oP_sQU.js");
2352
2471
  return runRlsMigrate({
2353
2472
  cwd: input.cwd,
2354
2473
  sql: input.sql,
@@ -2364,8 +2483,11 @@ async function migrate(input) {
2364
2483
  ...input.io.now === void 0 ? {} : { now: input.io.now() }
2365
2484
  });
2366
2485
  }
2367
- /** `verify --introspect` with `--helpers-only`: helpers and seeds exactly, hand-written policies by their keys. */
2368
- async function introspectHelpersOnly(db, expected, generated, connect, readOnlyActors) {
2486
+ /**
2487
+ * `verify --introspect` with `--helpers-only`: helpers and seeds exactly,
2488
+ * hand-written policies by their keys.
2489
+ */
2490
+ async function introspectHelpersOnly(db, expected, generated, connect, readOnlyActors, ignore) {
2369
2491
  const schema = generated.schema ?? "permdock";
2370
2492
  try {
2371
2493
  const actual = await introspectRls(db, expected, connect);
@@ -2376,31 +2498,28 @@ async function introspectHelpersOnly(db, expected, generated, connect, readOnlyA
2376
2498
  seeds: generated.seeds ?? [],
2377
2499
  permissions: generated.keys?.permissions ?? [],
2378
2500
  rowConditions: generated.keys?.rowConditions ?? [],
2501
+ levels: generated.keys?.levels ?? [],
2379
2502
  readOnlyActors
2380
2503
  }, await introspectMixed(db, schema, connect));
2381
- const drift = [...helpers, ...mixed.drift];
2382
- const info = mixed.info.map((line) => `info: ${line}`);
2383
- return drift.length > 0 ? {
2384
- code: 1,
2385
- output: [
2386
- ...drift,
2387
- ...warnings,
2388
- ...info
2389
- ].join("\n")
2390
- } : {
2391
- code: 0,
2392
- output: [
2504
+ return verifyResult({
2505
+ findings: [
2506
+ ...helpers,
2507
+ ...mixed.drift,
2393
2508
  ...warnings,
2394
- ...info,
2395
- `introspected ${String(expected.helpers.length)} helper(s) and ${String(generated.seeds?.length ?? 0)} seeded row(s), helpers only: no drift`
2396
- ].join("\n")
2397
- };
2509
+ ...mixed.info
2510
+ ],
2511
+ ignore,
2512
+ summary: `introspected ${String(expected.helpers.length)} helper(s) and ${String(generated.seeds?.length ?? 0)} seeded row(s), helpers only: no drift`
2513
+ });
2398
2514
  } catch (cause) {
2399
2515
  return usageResult(cause);
2400
2516
  }
2401
2517
  }
2402
- /** `verify --introspect`: the catalogs against what `generate` would write with the same flags. */
2403
- async function introspect(input) {
2518
+ /**
2519
+ * `verify --introspect`: the catalogs against what `generate` would write with
2520
+ * the same flags.
2521
+ */
2522
+ async function introspect(input, ignore) {
2404
2523
  if (input.db === void 0) return {
2405
2524
  code: 2,
2406
2525
  output: "PermDock CLI: rls verify --introspect needs a database: --db-url-stdin, DATABASE_URL or SUPABASE_DB_URL"
@@ -2420,19 +2539,15 @@ async function introspect(input) {
2420
2539
  output: generated.output
2421
2540
  };
2422
2541
  const expected = expectedRls(generated.policies, generated.text, generated.indexes);
2423
- if (generated.helpersOnly === true) return introspectHelpersOnly(input.db, expected, generated, input.connect, readOnlyActorKinds(input.config.rls?.readOnlyActors) !== void 0);
2542
+ if (generated.helpersOnly === true) return introspectHelpersOnly(input.db, expected, generated, input.connect, readOnlyActorKinds(input.config.rls?.readOnlyActors) !== void 0, ignore);
2424
2543
  try {
2425
2544
  const actual = await introspectRls(input.db, expected, input.connect);
2426
- const drift = diffRls(expected, actual, { columnGrants: (input.fields ?? input.config.rls?.fields) === "views" });
2427
- const warnings = missingIndexes(expected, actual);
2428
- if (drift.length > 0) return {
2429
- code: 1,
2430
- output: [...drift, ...warnings].join("\n")
2431
- };
2432
- return {
2433
- code: 0,
2434
- output: `${warnings.map((line) => `${line}\n`).join("")}introspected ${String(expected.policies.length)} policies on ${String(expected.tables.length)} table(s) and ${String(expected.helpers.length)} helper(s): no drift`
2435
- };
2545
+ const drift = diffRls(expected, actual, { columnGrants: (input.fields ?? input.config.rls?.fields) !== void 0 });
2546
+ return verifyResult({
2547
+ findings: [...drift, ...missingIndexes(expected, actual)],
2548
+ ignore,
2549
+ summary: `introspected ${String(expected.policies.length)} policies on ${String(expected.tables.length)} table(s) and ${String(expected.helpers.length)} helper(s): no drift`
2550
+ });
2436
2551
  } catch (cause) {
2437
2552
  return usageResult(cause);
2438
2553
  }
@@ -2467,8 +2582,8 @@ async function runRls(input) {
2467
2582
  });
2468
2583
  }
2469
2584
  case "import": {
2470
- await requirePeer(() => import("pgsql-parser"), "pgsql-parser", "permdock rls import", input.cwd);
2471
- const { runRlsImport } = await import("./import-wyqXUyjm.js");
2585
+ await requirePeer(() => import("pgsql-parser"), "pgsql-parser", "permdock rls import", input.cwd, PGSQL_PARSER_MAJOR);
2586
+ const { runRlsImport } = await import("./import-CST57XLp.js");
2472
2587
  return runRlsImport({
2473
2588
  cwd: input.cwd,
2474
2589
  config: input.config,
@@ -2490,7 +2605,8 @@ async function runRls(input) {
2490
2605
  ...input.db === void 0 ? {} : { db: input.db },
2491
2606
  ...input.exec === void 0 ? {} : { exec: input.exec }
2492
2607
  });
2493
- if (input.introspect) return introspect(input);
2608
+ const ignore = verifyIgnores(input.config, input.ignore);
2609
+ if (input.introspect) return introspect(input, ignore);
2494
2610
  const format = input.format ?? "node";
2495
2611
  if (format !== "node" && format !== "pgtap") return {
2496
2612
  code: 2,
@@ -2502,13 +2618,16 @@ async function runRls(input) {
2502
2618
  format,
2503
2619
  tree: input.tree,
2504
2620
  io: input.io,
2621
+ ...input.out === void 0 ? {} : { out: input.out },
2505
2622
  ...input.fixtures === void 0 ? {} : { fixtures: input.fixtures },
2506
2623
  ...input.db === void 0 ? {} : { db: input.db },
2507
2624
  ...input.from === void 0 ? {} : { from: input.from },
2508
- ...input.connect === void 0 ? {} : { connect: input.connect }
2625
+ ...input.connect === void 0 ? {} : { connect: input.connect },
2626
+ ignore
2509
2627
  });
2510
2628
  }
2511
2629
  case "migrate": return migrate(input);
2630
+ case "explain": return explain(input);
2512
2631
  default: return {
2513
2632
  code: 2,
2514
2633
  output: RLS_HELP