permdock 0.7.0 → 0.9.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 (483) hide show
  1. package/README.md +1 -3
  2. package/dist/a2a/index.d.ts +7 -7
  3. package/dist/a2a/index.js +8 -8
  4. package/dist/action-result-BcudBQOF.d.ts +19 -0
  5. package/dist/{adapter-context-CRhqyzCk.js → adapter-context-CIQpQxbE.js} +8 -2
  6. package/dist/{adoption-scan-D-feE7BE.js → adoption-scan-Ch07MdxN.js} +5 -5
  7. package/dist/agent/index.d.ts +2 -2
  8. package/dist/agent/index.js +1 -1
  9. package/dist/ai-sdk/index.d.ts +19 -13
  10. package/dist/ai-sdk/index.js +6 -6
  11. package/dist/{app-data-MuyzVqX8.js → app-data-UNNm07jV.js} +10 -7
  12. package/dist/{approval-header-CJACtHkv.js → approval-header-8QV6MU4H.js} +6 -3
  13. package/dist/{approval-prompt-K0faOLFp.d.ts → approval-prompt-3BqWczTt.d.ts} +13 -11
  14. package/dist/{approval-prompt-Dg5LeAcT.js → approval-prompt-B8hQY69x.js} +5 -6
  15. package/dist/approvals/index.d.ts +4 -4
  16. package/dist/approvals/index.js +6 -6
  17. package/dist/{approvals-CMRfsbBh.js → approvals-DnIBTuXE.js} +66 -42
  18. package/dist/{approvers-CqPOLD-o.js → approvers-B4r0N_Uz.js} +8 -2
  19. package/dist/{arazzo-ClrF3PVb.js → arazzo-BpS7LnV0.js} +2 -2
  20. package/dist/{arazzo-rdeJ8n1J.js → arazzo-CmQaGPkM.js} +6 -6
  21. package/dist/{args-CC3OwyHW.js → args-0FrVs5EH.js} +14 -7
  22. package/dist/astro/index.d.ts +30 -12
  23. package/dist/astro/index.js +2 -2
  24. package/dist/{audience-BrMs8c4o.js → audience-DTjR8xet.js} +8 -8
  25. package/dist/{auth-CscUX8Sy.js → auth--yPyZNgr.js} +19 -7
  26. package/dist/authzen/index.d.ts +34 -28
  27. package/dist/authzen/index.js +23 -20
  28. package/dist/{automation-DOtAFWdy.d.ts → automation-C6Jg75xq.d.ts} +42 -32
  29. package/dist/{automation-ySsxp_Yp.js → automation-x3KEUusw.js} +22 -21
  30. package/dist/better-auth/index.d.ts +2 -2
  31. package/dist/better-auth/index.js +13 -7
  32. package/dist/better-supabase/index.d.ts +357 -227
  33. package/dist/better-supabase/index.js +219 -140
  34. package/dist/{bind-D1nO6fiC.js → bind-BNQA9JK9.js} +26 -10
  35. package/dist/{bind-JDuvxjuW.d.ts → bind-YYjvfpB9.d.ts} +32 -12
  36. package/dist/{body-CH5of8Ne.js → body-CJQwYVa8.js} +3 -3
  37. package/dist/{body-text-QcXok_SI.js → body-text-DQhcmqdY.js} +8 -4
  38. package/dist/{boundary-BM8a_P6E.js → boundary-BV3O1eud.js} +8 -2
  39. package/dist/{boundary-BmHxVH1J.d.ts → boundary-C9ck-3uK.d.ts} +13 -4
  40. package/dist/{budget-NbEtbJC_.js → budget-BELja5A9.js} +6 -3
  41. package/dist/{budget-JqcRqyhK.d.ts → budget-hYsPz_yE.d.ts} +6 -3
  42. package/dist/{build-instance-DxE7czo9.js → build-instance-BWXqwQYU.js} +3 -3
  43. package/dist/{capability-L7NnfhiE.js → capability-BqX94r07.js} +31 -25
  44. package/dist/{capability-D4UC9TTW.d.ts → capability-Di9tiUvi.d.ts} +41 -29
  45. package/dist/{casbin-BHijLuHY.js → casbin-B7_yS-oo.js} +2 -2
  46. package/dist/{casbin-BW9pS47J.js → casbin-bHyIxTNQ.js} +16 -8
  47. package/dist/{casl-CmJlgziS.js → casl-C4OINZHI.js} +2 -2
  48. package/dist/{casl-UbI8v-Wq.js → casl-CHHoCldP.js} +19 -10
  49. package/dist/catalog/index.d.ts +51 -30
  50. package/dist/catalog/index.js +18 -15
  51. package/dist/{catalog-D69WhFii.js → catalog-CGlSwU6j.js} +20 -19
  52. package/dist/catalog-D3oS2GH3.js +1 -0
  53. package/dist/{catalog-BaMQfQ6Y.js → catalog-amPDr0JQ.js} +3 -3
  54. package/dist/{catalog-doc-CV9s6B1S.js → catalog-doc-DjMKzJp9.js} +28 -14
  55. package/dist/{catalog-fingerprint-C144oW4M.js → catalog-fingerprint-e6aCWosn.js} +18 -14
  56. package/dist/catalog-operations-DeHTrJSV.js +60 -0
  57. package/dist/{challenge-nGWma08S.js → challenge-DImNcR7B.js} +48 -27
  58. package/dist/chat/index.d.ts +1 -1
  59. package/dist/chat/index.js +13 -10
  60. package/dist/chat-sdk/index.d.ts +44 -26
  61. package/dist/chat-sdk/index.js +17 -17
  62. package/dist/{checks-BC_0mPIq.js → checks-CGkP3Z7A.js} +4 -1
  63. package/dist/{claims-map-7oQ3ue4X.js → claims-map-CKkoAAYF.js} +5 -2
  64. package/dist/{claims-map-Ca-QX8C6.d.ts → claims-map-D1Pb5oRq.d.ts} +8 -5
  65. package/dist/claude-agent/index.d.ts +6 -6
  66. package/dist/claude-agent/index.js +6 -3
  67. package/dist/clerk/index.d.ts +11 -7
  68. package/dist/clerk/index.js +6 -6
  69. package/dist/cli/bin.js +4 -4
  70. package/dist/cli/index.d.ts +82 -7
  71. package/dist/cli/index.js +6 -6
  72. package/dist/{clients-C7zLFuWT.d.ts → clients-C_NOET3z.d.ts} +5 -5
  73. package/dist/{clients-DjU5ABMy.js → clients-DYr3U86j.js} +3 -3
  74. package/dist/cloud/index.d.ts +84 -52
  75. package/dist/cloud/index.js +53 -39
  76. package/dist/{cloud-CO8pYtwr.js → cloud-3kWfjnYy.js} +4 -5
  77. package/dist/{codemod-BP6n8N9L.js → codemod-Bv5i-Mzo.js} +523 -201
  78. package/dist/{codemod-i4N2BhDc.js → codemod-D9Aiii1r.js} +3 -4
  79. package/dist/{collect-DNm3oKOk.js → collect-B80GYEot.js} +4 -5
  80. package/dist/collect-D-eroIfV.js +1 -0
  81. package/dist/{collect-BaWeMjwV.js → collect-WCl8cJQV.js} +26 -63
  82. package/dist/{compare-pfyVnBjE.js → compare-DTfyUunF.js} +2 -2
  83. package/dist/compile/index.d.ts +2 -2
  84. package/dist/compile/index.js +1 -1
  85. package/dist/{compile-COM2VKuN.d.ts → compile-BEklRUYo.d.ts} +36 -12
  86. package/dist/{compile-BAEO5bOk.js → compile-EzEMB-7v.js} +77 -33
  87. package/dist/{completion-BNdVf7X2.js → completion-_bc0OUQ-.js} +17 -8
  88. package/dist/config-BwPBfN0t.js +108 -0
  89. package/dist/{config-DGZzMV1B.js → config-D5XrLWpk.js} +2 -2
  90. package/dist/{config-report-Cm7UITNA.js → config-report-BmWIskD7.js} +9 -6
  91. package/dist/config-shape-ykV0Vm2n.js +134 -0
  92. package/dist/{connection-DjhIJSs2.js → connection-B72dg5Q2.js} +23 -22
  93. package/dist/{connector-schema-_VCSsdtD.js → connector-schema-CYLQNMYq.js} +3 -2
  94. package/dist/{context--uM9V3Ui.js → context-BnIzxTNE.js} +4 -3
  95. package/dist/convex/index.d.ts +13 -7
  96. package/dist/convex/index.js +9 -6
  97. package/dist/{core-CQkE8Esr.d.ts → core-CFepFiDP.d.ts} +49 -22
  98. package/dist/{core-B5-w6Kw9.js → core-DzVINFbU.js} +26 -20
  99. package/dist/{create-Cb86Z8Im.js → create-6EwRQlig.js} +10 -7
  100. package/dist/{create-DJrlhEp9.js → create-BLPKnJKa.js} +35 -25
  101. package/dist/{create-UTnUVETj.d.ts → create-BPUopdfL.d.ts} +70 -37
  102. package/dist/{create-QHuuPw3q.d.ts → create-BamXmeob.d.ts} +37 -29
  103. package/dist/{create-B2-CQqCI.d.ts → create-DBvUsgzK.d.ts} +49 -28
  104. package/dist/{credential-CIM7wHwA.js → credential-CyyyvCsL.js} +41 -38
  105. package/dist/{credentials-BpuTwd7a.js → credentials-CdDxp7jN.js} +6 -6
  106. package/dist/{deciding-columns-DRo2QM6E.js → deciding-columns-NB4wcyBf.js} +2 -2
  107. package/dist/{delegation-C5rOdTp5.js → delegation-CDB6_keF.js} +31 -22
  108. package/dist/delivery/index.d.ts +4 -4
  109. package/dist/delivery/index.js +5 -5
  110. package/dist/{delivery-rwOKLWRm.js → delivery-BupfvJIV.js} +34 -22
  111. package/dist/{delivery-event-xX4obB8F.js → delivery-event-JioK5YbS.js} +6 -6
  112. package/dist/denied-text-DHldhVUv.d.ts +22 -0
  113. package/dist/{describe-DZlnEtLy.js → describe-Bb4hGK5d.js} +5 -5
  114. package/dist/{describe-O0SwYIK8.d.ts → describe-BbFPBSms.d.ts} +5 -5
  115. package/dist/{device-BARQ8eYG.js → device-BwbJNmb2.js} +11 -8
  116. package/dist/{diff-Btr1c1Ow.js → diff-BwR1JdP2.js} +2 -2
  117. package/dist/{diff-DzT-xD13.js → diff-CQegtBAo.js} +22 -13
  118. package/dist/{digest-B3kc_PgQ.d.ts → digest-8Yx2CchM.d.ts} +3 -3
  119. package/dist/{doctor-BU1szn63.js → doctor-ClRVAtXQ.js} +287 -135
  120. package/dist/{doctor-BoK_y0KO.js → doctor-LPMYjSqO.js} +4 -4
  121. package/dist/drizzle/index.d.ts +33 -19
  122. package/dist/drizzle/index.js +17 -9
  123. package/dist/elysia/index.d.ts +17 -11
  124. package/dist/elysia/index.js +5 -5
  125. package/dist/{errors-BKy0EfhN.js → errors-B6R_lg2a.js} +13 -4
  126. package/dist/{errors-B_Qg9D3e.d.ts → errors-BIM4MFsJ.d.ts} +22 -6
  127. package/dist/{errors-BWXBMaKK.d.ts → errors-CHwoRRpB.d.ts} +4 -1
  128. package/dist/{errors-DpP_U6Bf.js → errors-Vpu6rKHl.js} +13 -10
  129. package/dist/{evaluate-B_oqFbWC.js → evaluate-BAmf_AQd.js} +153 -82
  130. package/dist/{evaluations-BCddEOfO.d.ts → evaluations-DOWuBpWv.d.ts} +11 -5
  131. package/dist/{evaluations-DReoiVBw.js → evaluations-IBceAeKT.js} +40 -19
  132. package/dist/eve/index.d.ts +9 -6
  133. package/dist/eve/index.js +4 -4
  134. package/dist/{event-time-CsegxXxB.js → event-time-BGy48400.js} +8 -4
  135. package/dist/{expiry-D4_YKRKD.js → expiry-6XByK0dm.js} +8 -5
  136. package/dist/express/index.d.ts +9 -6
  137. package/dist/express/index.js +9 -6
  138. package/dist/fastify/index.d.ts +16 -10
  139. package/dist/fastify/index.js +8 -5
  140. package/dist/{fetch-adapter-CuhyYwC1.d.ts → fetch-adapter-BIbBAtPZ.d.ts} +9 -6
  141. package/dist/{fetch-adapter-De-u6vhJ.js → fetch-adapter-dPTgJVHd.js} +14 -11
  142. package/dist/fields-D0vUI6dq.js +498 -0
  143. package/dist/{files-acUbNwWt.js → files-B3AA05f1.js} +8 -5
  144. package/dist/{files-D_JIzUrP.js → files-BSjrtIHw.js} +11 -4
  145. package/dist/{fixtures-BTMW6zgU.js → fixtures-A1_sp_tR.js} +15 -6
  146. package/dist/{flags-aaeRAdiD.js → flags-DXjoTVbS.js} +2 -2
  147. package/dist/{freeze-CrU8nQeX.js → freeze-jjan0lCv.js} +4 -1
  148. package/dist/{from-snapshot-Bz469dIh.js → from-snapshot-DCakWRsr.js} +18 -15
  149. package/dist/gen-BFfzZSLH.js +58 -0
  150. package/dist/gen-BtfFWGbz.js +827 -0
  151. package/dist/{generate-3FNO5P5Q.js → generate-CRVV5V7l.js} +14 -8
  152. package/dist/generate-CezbCaTI.js +275 -0
  153. package/dist/{grantee-BrRANrpT.js → grantee-Dke-9rd1.js} +4 -4
  154. package/dist/{grantee-conditions-C6P1Tvol.js → grantee-conditions-BrYPe-w0.js} +12 -12
  155. package/dist/{grantee-match-0RtHWQDx.js → grantee-match-7RZZvoT_.js} +12 -6
  156. package/dist/{graph-sql-B-dsAT6j.d.ts → graph-sql-BwFC4Qjs.d.ts} +3 -3
  157. package/dist/h3/index.d.ts +10 -7
  158. package/dist/h3/index.js +3 -3
  159. package/dist/headers-B9jQxjtW.d.ts +10 -0
  160. package/dist/{helpers-DY3vXuGs.js → helpers-DJicaG6f.js} +40 -28
  161. package/dist/hono/index.d.ts +16 -10
  162. package/dist/hono/index.js +5 -5
  163. package/dist/{hosted-XBgU6RGT.js → hosted-D0IzESu_.js} +42 -21
  164. package/dist/{hosted-BmjzxQhp.d.ts → hosted-DVxlLZMe.d.ts} +33 -21
  165. package/dist/{http-ujKm0JXE.js → http-DH94eh6f.js} +4 -4
  166. package/dist/{http-BBoKefeS.js → http-DKLXrFGk.js} +13 -7
  167. package/dist/{http-Ca3Sr5x1.js → http-DOL33cO0.js} +11 -8
  168. package/dist/{import-DB2EG_iy.js → import-ACkOrv6g.js} +6 -6
  169. package/dist/{import-Bz0GefWg.js → import-CST57XLp.js} +64 -51
  170. package/dist/{index-CMiPH2Tv.d.ts → index-BinlXeU-.d.ts} +64 -37
  171. package/dist/{index-BSfRl0hZ.d.ts → index-CV-7DWeD.d.ts} +64 -52
  172. package/dist/{index-BE8y3moU.d.ts → index-DJwEgYzA.d.ts} +127 -82
  173. package/dist/{index-DH9DUDEM.d.ts → index-Dj2qF9VC.d.ts} +54 -30
  174. package/dist/{index-BTkwrK7v.d.ts → index-vYjMrXyK.d.ts} +336 -173
  175. package/dist/{index-DLwcuCN_.d.ts → index-ymept53A.d.ts} +51 -30
  176. package/dist/index.d.ts +22 -22
  177. package/dist/index.js +34 -34
  178. package/dist/{init-B-4t1NzC.js → init-BjuFDYT1.js} +10 -10
  179. package/dist/{init-5TXNji88.js → init-txYTuW9u.js} +5 -5
  180. package/dist/{instance-options-CU0YQCLH.d.ts → instance-options-CoHn-0e8.d.ts} +4 -4
  181. package/dist/{instance-snapshot-CjcwnEiV.js → instance-snapshot-DuWf1cT3.js} +86 -61
  182. package/dist/{instant-BluqDvyS.js → instant-DK3Iz83e.js} +2 -2
  183. package/dist/{internals-Dk8A8fX3.js → internals-B3IgHrvo.js} +6 -3
  184. package/dist/{json-schema-B-vAwlde.js → json-schema-BPGa-RjQ.js} +9 -3
  185. package/dist/{json-schema-CN_iLFH2.d.ts → json-schema-BfXgnRNT.d.ts} +4 -4
  186. package/dist/jwt/index.d.ts +57 -27
  187. package/dist/jwt/index.js +24 -21
  188. package/dist/{kernel-DVExUwkp.js → kernel-qv4cr9w8.js} +32 -23
  189. package/dist/kysely/index.d.ts +49 -33
  190. package/dist/kysely/index.js +9 -7
  191. package/dist/{live-options-BaAFCIiu.js → live-options-BBIT68le.js} +2 -2
  192. package/dist/{load-DxroEzH1.js → load-DjSU5KXO.js} +6 -6
  193. package/dist/load-Dm0pSYH3.js +1 -0
  194. package/dist/{login-ClSJh5t5.js → login-CfImTJGb.js} +4 -4
  195. package/dist/{login-WC9HPJpU.js → login-CgfCTO3F.js} +16 -9
  196. package/dist/{manifest-Dv6q4i5s.js → manifest-B2QPh19C.js} +49 -38
  197. package/dist/{manifest-BVhExYhw.d.ts → manifest-CzzWtnAb.d.ts} +126 -31
  198. package/dist/{manifest-schema-Cv492Qev.js → manifest-schema-2r37XRhD.js} +44 -3
  199. package/dist/{manifest-B79YuF2T.js → manifest-wg8WkkpD.js} +6 -6
  200. package/dist/{map-claims-etGIS_Dv.js → map-claims-C-nv08_n.js} +16 -9
  201. package/dist/{map-error-BQGyDm_M.js → map-error-pasIGqMT.js} +7 -7
  202. package/dist/{markers-C6joDI3o.js → markers-BeHZ4x9Q.js} +20 -5
  203. package/dist/mcp/index.d.ts +114 -61
  204. package/dist/mcp/index.js +70 -40
  205. package/dist/{migrate-Dodel0IJ.js → migrate-B7oP_sQU.js} +35 -17
  206. package/dist/module-values-BJTBdo6y.js +107 -0
  207. package/dist/nest/index.d.ts +23 -13
  208. package/dist/nest/index.js +18 -11
  209. package/dist/next/client.d.ts +5 -2
  210. package/dist/next/client.js +1 -1
  211. package/dist/next/index.d.ts +79 -26
  212. package/dist/next/index.js +121 -10
  213. package/dist/next/plugin.d.ts +1 -1
  214. package/dist/next/plugin.js +4 -3
  215. package/dist/node/index.d.ts +5 -5
  216. package/dist/node/index.js +3 -3
  217. package/dist/{ocsf-BFp-PZZA.js → ocsf-TcjRBgIs.js} +15 -5
  218. package/dist/openai/index.d.ts +13 -7
  219. package/dist/openai/index.js +3 -3
  220. package/dist/openapi/index.d.ts +51 -30
  221. package/dist/openapi/index.js +2 -2
  222. package/dist/{openapi-ztVaux6j.js → openapi-BGeuunWC.js} +23 -14
  223. package/dist/{openapi-DMocl1bh.js → openapi-BIpw3Mb7.js} +4 -5
  224. package/dist/{openapi-Ddb4628T.js → openapi-BOsx0xn9.js} +33 -18
  225. package/dist/{operation-findings-kRcEp8lE.js → operation-findings-CXtFzlKJ.js} +3 -3
  226. package/dist/{operations-BNLes61x.d.ts → operations-Ci_rT6Uo.d.ts} +10 -7
  227. package/dist/orpc/index.d.ts +68 -14
  228. package/dist/orpc/index.js +62 -13
  229. package/dist/otel/index.d.ts +7 -4
  230. package/dist/otel/index.js +8 -5
  231. package/dist/{output-DgSDLrta.js → output-DV5dwOTs.js} +9 -5
  232. package/dist/output-files-BHOIlrfD.js +1 -0
  233. package/dist/{output-files-CCMdcFWb.js → output-files-BIQ_9pIs.js} +29 -17
  234. package/dist/ownership-DA5YdYTm.js +1059 -0
  235. package/dist/{parse-Ddhz2xf4.js → parse-1_ZheTW5.js} +16 -13
  236. package/dist/parse-local-manifest-CCmbhnxP.d.ts +10 -0
  237. package/dist/{parse-local-manifest-T1Q8X3A2.js → parse-local-manifest-DEiCF4ah.js} +19 -7
  238. package/dist/{parse-request-D_tUXG6M.js → parse-request-CwCZpyjV.js} +7 -8
  239. package/dist/{paths-DXSHAMIa.js → paths-CUvURzHp.js} +4 -1
  240. package/dist/pdp/index.d.ts +9 -6
  241. package/dist/pdp/index.js +20 -13
  242. package/dist/{peer-DOzzMfZL.js → peer-C9LvBCcb.js} +1 -1
  243. package/dist/{peer-12Q5I_RH.js → peer-Q0fuZ6hx.js} +9 -3
  244. package/dist/{permdock-BfWEeMqk.js → permdock-BvjivVb9.js} +200 -140
  245. package/dist/{permdock-Dco6dyCK.d.ts → permdock-CprAPN3w.d.ts} +260 -129
  246. package/dist/{permission-registry-DpPtSKNZ.js → permission-registry-B8tYxPIt.js} +23 -10
  247. package/dist/{permission-types-DhNEmoRG.d.ts → permission-types-CAY-GU7e.d.ts} +127 -41
  248. package/dist/{permissions-CoqCOfJ3.d.ts → permissions-Bx6UI8n1.d.ts} +6 -3
  249. package/dist/{permissions-CyH2MV_B.js → permissions-CVXmRILq.js} +3 -3
  250. package/dist/{permissions-DArULThq.js → permissions-CYcHj_w7.js} +8 -5
  251. package/dist/pg-DQTqRrou.js +60 -0
  252. package/dist/{pins-8ILTftC9.js → pins-nqarl3oE.js} +4 -1
  253. package/dist/{pkce-5h8NgMQJ.js → pkce-CnmmYqUZ.js} +1 -1
  254. package/dist/{policy-B7dg_yzu.d.ts → policy-BycVKnXv.d.ts} +5 -5
  255. package/dist/{policy-DaJy4nK4.js → policy-CLQ8__GF.js} +43 -23
  256. package/dist/{policy-from-catalog-DFWYkNdu.js → policy-from-catalog-CU3An8uP.js} +28 -25
  257. package/dist/{policy-module-CTlwMvLI.js → policy-module-xym9fnSq.js} +12 -6
  258. package/dist/{policy-types-B-F-qAZk.d.ts → policy-types-DGIO_gxq.d.ts} +782 -357
  259. package/dist/{postgres-store-BL9M_CA1.js → postgres-store-DcyrPS-r.js} +43 -16
  260. package/dist/{postgrest-r5faDIzD.js → postgrest-5Dr3W64L.js} +84 -86
  261. package/dist/{postgrest-CYacM_u7.d.ts → postgrest-D5nUGm-c.d.ts} +55 -35
  262. package/dist/powersync-B701tvhE.js +1 -0
  263. package/dist/{powersync-13odBwiM.js → powersync-Bg8u-JcR.js} +3 -3
  264. package/dist/{powersync-53ryOZip.js → powersync-CJrT1DSc.js} +127 -69
  265. package/dist/preset/index.d.ts +38 -28
  266. package/dist/preset/index.js +1 -1
  267. package/dist/{preset-CHTbd-DQ.js → preset-BjLzeFpp.js} +56 -32
  268. package/dist/{principal-source-pHneukrx.js → principal-source-C6N60GtM.js} +10 -10
  269. package/dist/prisma/index.d.ts +69 -41
  270. package/dist/prisma/index.js +21 -17
  271. package/dist/{problem-CTviyH5L.js → problem-BVLHFK33.js} +41 -21
  272. package/dist/{problem-details-cn5X30gZ.js → problem-details-CLDgX1KV.js} +13 -4
  273. package/dist/{problem-types-CJ2325SZ.js → problem-types-DSsRfONQ.js} +4 -1
  274. package/dist/{prompt-DwtVemER.js → prompt-BYc6drYW.js} +4 -1
  275. package/dist/protected-action-D8x73IGm.d.ts +58 -0
  276. package/dist/protected-action-DUYqb-Ma.js +80 -0
  277. package/dist/{protected-DXE3cQYg.js → protected-action-QEA8arRB.js} +48 -14
  278. package/dist/{provider-BQsTYYy0.js → provider-C55OGXJ7.js} +5 -4
  279. package/dist/{provider-Csk-THAn.d.ts → provider-DfLeek9O.d.ts} +1 -1
  280. package/dist/{provider-store-IgqQipCO.js → provider-store-CaTv00tr.js} +13 -9
  281. package/dist/{pull-CN5OLxrF.js → pull-2nJr0qg_.js} +12 -11
  282. package/dist/pull-C3EEGzSw.js +1 -0
  283. package/dist/{push-tVdG8dUL.js → push-Bn-lMEmI.js} +19 -13
  284. package/dist/{pushed-policy-CmYeSQqq.js → pushed-policy-CDuOL2de.js} +10 -7
  285. package/dist/{pushed-policy-BbcY9r_4.d.ts → pushed-policy-CryQheKq.d.ts} +12 -6
  286. package/dist/react/index.d.ts +9 -8
  287. package/dist/react/index.js +7 -6
  288. package/dist/react/provider-client.d.ts +1 -1
  289. package/dist/react/provider-client.js +1 -1
  290. package/dist/react-native/index.d.ts +94 -55
  291. package/dist/react-native/index.js +200 -61
  292. package/dist/{refs-CNJBiTFo.js → refs-C8BKxc_e.js} +2 -2
  293. package/dist/{relation-walk-3OyDBF0S.js → relation-walk-DK_gkgL7.js} +12 -9
  294. package/dist/{relations-BnCWnJGI.js → relations-BxvRvDHK.js} +3 -3
  295. package/dist/{replay-Du1tJjG7.js → replay-BU09ad4L.js} +1 -1
  296. package/dist/{replay-DCDD_ZfL.d.ts → replay-Bs9Df2fQ.d.ts} +1 -1
  297. package/dist/{request-context-BykKLmyE.js → request-context-Duzdf0cd.js} +8 -8
  298. package/dist/{request-schema-CMreVuFM.js → request-schema-DhX7UbfQ.js} +4 -1
  299. package/dist/resend/index.d.ts +34 -28
  300. package/dist/resend/index.js +29 -29
  301. package/dist/resolve-names-ConCSj1y.js +201 -0
  302. package/dist/{resource-relations-D_jw0BFx.js → resource-relations-8-pCScBO.js} +18 -9
  303. package/dist/{retire-Wj6dkyDE.js → retire-B2xffQgY.js} +15 -6
  304. package/dist/{revocations-R07lFBnX.d.ts → revocations-BaUXHM1N.d.ts} +6 -3
  305. package/dist/{rls-Ck_PocSp.js → rls-Ba9wN0SP.js} +13 -6
  306. package/dist/{rls-CxBZb4rZ.js → rls-Cx0dtsB7.js} +310 -128
  307. package/dist/{role-builder-C1VJzvcr.js → role-builder-BK2-T6oD.js} +41 -20
  308. package/dist/{row-conditions-h2HythXQ.js → row-conditions-BAGHOmVq.js} +4 -3
  309. package/dist/{rpc-Bk1bE-ch.js → rpc-vHIwyXCp.js} +8 -5
  310. package/dist/run-Ck22anC2.js +1 -0
  311. package/dist/{run-uLD2aWuO.js → run-PbRlXTfh.js} +26 -23
  312. package/dist/{run-ticket-BpNi7BCi.d.ts → run-ticket-B2o2Kxz8.d.ts} +29 -14
  313. package/dist/{run-ticket-B5d_63Ma.js → run-ticket-Dv7aF7Dk.js} +9 -9
  314. package/dist/{runtime-Vif3_-zf.js → runtime-BGnrOkVH.js} +5 -2
  315. package/dist/{saas-Az_Kz-4D.js → saas-DK4V-3Is.js} +42 -29
  316. package/dist/{scan-DSljDE1Y.js → scan-CRTj6fke.js} +1 -1
  317. package/dist/{schema-B4v0Qrac.js → schema-BOJjqlWL.js} +3 -2
  318. package/dist/{schema-gIcCx7zM.js → schema-BTsE1WRT.js} +8 -4
  319. package/dist/scim/index.d.ts +2 -2
  320. package/dist/scim/index.js +2 -2
  321. package/dist/{scim-C7JYhySg.js → scim-D8YupzX1.js} +2 -2
  322. package/dist/{scim-YNieBO1U.js → scim-jJBJ7g1Z.js} +33 -15
  323. package/dist/{scopes-BHsc-wp-.js → scopes-0Up_UA2t.js} +41 -23
  324. package/dist/server/index.d.ts +11 -11
  325. package/dist/server/index.js +12 -12
  326. package/dist/{server-Bnga6I50.js → server-3cVlTLde.js} +31 -31
  327. package/dist/{server-types-C4gZUEZy.d.ts → server-types-CyRN0-Cd.d.ts} +88 -46
  328. package/dist/{sha256-D9ihqmvd.js → sha256-C-4WXKIu.js} +4 -3
  329. package/dist/{shared-fMKOv8hP.js → shared-Afctg7B0.js} +65 -32
  330. package/dist/{shared-C8LjUdVU.js → shared-l0oGGetg.js} +9 -6
  331. package/dist/{signals-D3NcTKUS.js → signals-B1vHR3fN.js} +2 -2
  332. package/dist/{sink-Cp_Eot9W.d.ts → sink-B5zoUW_O.d.ts} +44 -20
  333. package/dist/{sink-BEZSlMS_.js → sink-D9_vh372.js} +32 -21
  334. package/dist/{skills-g_3BS9NC.js → skills-C9uKfSF5.js} +3 -3
  335. package/dist/{skills-Dcjw9_1C.js → skills-CQQcuGBi.js} +12 -8
  336. package/dist/{snapshot-CbGmYiJx.js → snapshot-CXlzi69u.js} +21 -18
  337. package/dist/{snapshot-cache-Pahd3IYJ.d.ts → snapshot-cache-BjAWkchD.d.ts} +21 -12
  338. package/dist/{snapshot-cache-DT2Kx1F8.js → snapshot-cache-Bx_4xC_6.js} +14 -11
  339. package/dist/solid/index.d.ts +55 -16
  340. package/dist/solid/index.js +46 -12
  341. package/dist/solid-start/index.d.ts +30 -12
  342. package/dist/solid-start/index.js +3 -3
  343. package/dist/{sources-B-zYB4GS.js → sources-CMAM-2A3.js} +46 -27
  344. package/dist/{sources-DEEuGb8S.d.ts → sources-CWG32C0G.d.ts} +126 -53
  345. package/dist/{sql-B96movIM.js → sql-0WjxIp9g.js} +28 -7
  346. package/dist/{sql-CDEPCA0g.d.ts → sql-B80DFdpG.d.ts} +2 -2
  347. package/dist/{src-DLFrtrrC.js → src-C_pi2Rnh.js} +292 -101
  348. package/dist/ssf/index.d.ts +3 -3
  349. package/dist/ssf/index.js +47 -35
  350. package/dist/{standard-webhooks-DgFFD-Ga.js → standard-webhooks-D67a3nzk.js} +5 -5
  351. package/dist/{standard-webhooks-B_qRg0ws.d.ts → standard-webhooks-DOYEuOLx.d.ts} +7 -7
  352. package/dist/{store-DSP9oGvh.js → store-RNFvaJJv.js} +32 -26
  353. package/dist/{store-options-B_poyYvg.js → store-options-Dil3lraY.js} +105 -15
  354. package/dist/stores-DZJvxFIZ.d.ts +59 -0
  355. package/dist/{stores-BqEuHC05.js → stores-DxZWDXCs.js} +42 -5
  356. package/dist/{stream-2Vwhl7Er.d.ts → stream-9y9qw51m.d.ts} +4 -3
  357. package/dist/{stream-CvdUS9V1.js → stream-CDvF0sJm.js} +5 -5
  358. package/dist/{style-Ds1gNm77.js → style-DblOtSga.js} +1 -1
  359. package/dist/{style-Cqo7-73x.js → style-Ds11Hmk4.js} +2 -1
  360. package/dist/{subject-settings-CkEwrw3W.js → subject-settings-8sH5HlII.js} +8 -4
  361. package/dist/{row-check-Dt35czLy.d.ts → subject-settings-vNW8qmUH.d.ts} +30 -18
  362. package/dist/supabase/index.d.ts +48 -38
  363. package/dist/supabase/index.js +5 -5
  364. package/dist/supabase/middleware.d.ts +19 -13
  365. package/dist/supabase/middleware.js +7 -7
  366. package/dist/{supabase--0aJdLEd.js → supabase-DnMMg5Fn.js} +79 -35
  367. package/dist/{supabase-Bqp7iIY2.js → supabase-SBs3MYUE.js} +3 -3
  368. package/dist/supabase-hook-C0FyEACZ.js +1 -0
  369. package/dist/{supabase-hook-Dg0SrQWR.js → supabase-hook-CrgU06WH.js} +175 -196
  370. package/dist/{supautils-MESd81bF.js → supautils-ByezN3b8.js} +385 -219
  371. package/dist/{support-session-Cna49-3f.js → support-session-BsmPCbz8.js} +21 -21
  372. package/dist/svelte/PermissionBoundary.svelte +2 -6
  373. package/dist/svelte/Protected.svelte +2 -8
  374. package/dist/svelte/index.d.ts +9 -8
  375. package/dist/svelte/index.js +10 -9
  376. package/dist/svelte/runtime.d.ts +4 -4
  377. package/dist/svelte/runtime.js +4 -4
  378. package/dist/svelte/source.d.ts +9 -8
  379. package/dist/svelte/source.js +6 -5
  380. package/dist/sveltekit/index.d.ts +30 -12
  381. package/dist/sveltekit/index.js +2 -2
  382. package/dist/{ownership-Z2LpxsGa.js → tables-BqrxBCir.js} +641 -1104
  383. package/dist/tanstack-start/index.d.ts +22 -10
  384. package/dist/tanstack-start/index.js +2 -2
  385. package/dist/{target-Bw37TFNA.js → target-qdEAnnhU.js} +6 -6
  386. package/dist/{tenancy-CmeBQ6Nv.js → tenancy-5d1zVBvK.js} +25 -19
  387. package/dist/terminal/index.d.ts +23 -14
  388. package/dist/terminal/index.js +17 -20
  389. package/dist/testing/core.d.ts +1 -1
  390. package/dist/testing/core.js +1 -1
  391. package/dist/testing/fixtures/index.d.ts +8 -5
  392. package/dist/testing/fixtures/index.js +78 -17
  393. package/dist/testing/index.d.ts +225 -141
  394. package/dist/testing/index.js +352 -234
  395. package/dist/testing/saas/index.d.ts +43 -27
  396. package/dist/testing/saas/index.js +2 -2
  397. package/dist/testing/saas/permissions.d.ts +1 -1
  398. package/dist/testing/saas/permissions.js +1 -1
  399. package/dist/{text-diff-DhITNCnD.js → text-diff-BtfSmdUo.js} +2 -2
  400. package/dist/{thenable-yD7SabqG.js → thenable-BNSM1U2R.js} +12 -3
  401. package/dist/{time-DkWjdL98.js → time-nR1JesoH.js} +32 -8
  402. package/dist/{token-2qYDvXz2.js → token-DC9EQiet.js} +14 -12
  403. package/dist/{tool-refusal-reAhIzdl.js → tool-refusal-Qpjs_W6M.js} +5 -2
  404. package/dist/trpc/index.d.ts +11 -8
  405. package/dist/trpc/index.js +15 -9
  406. package/dist/type-helpers-c7Xo_2Cu.d.ts +50 -0
  407. package/dist/{types-CfiKC5aN.d.ts → types-B2efzoSX.d.ts} +14 -11
  408. package/dist/{types-CIeZcG8v.d.ts → types-B6ExllNo.d.ts} +2 -2
  409. package/dist/{types-DBHftjgS.d.ts → types-BB-zWDcR.d.ts} +45 -33
  410. package/dist/{types-BfVmNJO5.d.ts → types-BMVDHvI0.d.ts} +128 -50
  411. package/dist/{types-DXmwu78Z.d.ts → types-CEinc15V.d.ts} +31 -14
  412. package/dist/{types-g7Oy6Zg4.d.ts → types-CN0LsqbK.d.ts} +47 -24
  413. package/dist/{types-CeIscIKp.d.ts → types-CWBsAwqn.d.ts} +23 -12
  414. package/dist/{types-DqzsnlSA.js → types-CcUXciSj.js} +17 -5
  415. package/dist/{types-BWZybqs9.d.ts → types-CzOeVvcC.d.ts} +384 -203
  416. package/dist/{types-DC44-hqb.d.ts → types-DLvhVm1r.d.ts} +59 -32
  417. package/dist/{types-D3A19w4A.d.ts → types-DlelvQk4.d.ts} +22 -13
  418. package/dist/{types-cTos-H7p.d.ts → types-DsHGPtkR.d.ts} +68 -32
  419. package/dist/{types-BXMYB7BY.d.ts → types-ZzRGoViD.d.ts} +77 -24
  420. package/dist/{types-CQVEQawv.d.ts → types-rCRV4Ge-.d.ts} +24 -14
  421. package/dist/types-tQB2CEnY.d.ts +262 -0
  422. package/dist/unplugin/index.d.ts +1 -1
  423. package/dist/unplugin/index.js +2 -2
  424. package/dist/usage-0hjZPIRX.js +1 -0
  425. package/dist/{usage-Bybmck7f.js → usage-B9EtHWLa.js} +50 -26
  426. package/dist/{usage-CDaLXCfL.js → usage-CBzgwfIs.js} +2 -2
  427. package/dist/{validation-error-ZfUHbKvf.js → validation-error-CYg2kJEw.js} +4 -1
  428. package/dist/{validation-error-CQpoquwj.d.ts → validation-error-uvp-kyfO.d.ts} +5 -2
  429. package/dist/{validity-CZ7-y0Cj.js → validity-Buc7w8E9.js} +13 -10
  430. package/dist/{verifier-DHuh-okk.js → verifier-DUV6pLqZ.js} +10 -4
  431. package/dist/{verify-pe8rL_Qw.js → verify-DlUcTDBz.js} +148 -487
  432. package/dist/version-DMQ0dnN5.js +48 -0
  433. package/dist/version-DizhmD72.js +1 -0
  434. package/dist/{views-BB8E9dTP.js → views-CD8wuL4Z.js} +19 -6
  435. package/dist/{vocabulary-Bg3eeT2H.d.ts → vocabulary-DEJ5j4mR.d.ts} +7 -4
  436. package/dist/vue/index.d.ts +71 -22
  437. package/dist/vue/index.js +50 -13
  438. package/dist/{waiter-DwEyouan.d.ts → waiter-CqUKX6CY.d.ts} +13 -10
  439. package/dist/{waiter-BGUtGYoU.js → waiter-aBLM63_f.js} +1 -1
  440. package/dist/{watch-C-4VmFlL.js → watch-BFDKqmUa.js} +8 -8
  441. package/dist/watch-ClLuO2TL.js +1 -0
  442. package/dist/webmcp/index.d.ts +11 -11
  443. package/dist/webmcp/index.js +8 -5
  444. package/dist/{where-scope-wq2coopv.js → where-scope-DlCdCM_8.js} +24 -18
  445. package/dist/{who-can-BwNsI_Js.js → who-can-BoDJm0Nc.js} +23 -20
  446. package/dist/workflow/index.d.ts +3 -3
  447. package/dist/workflow/index.js +13 -13
  448. package/dist/workflow-sdk/index.d.ts +19 -13
  449. package/dist/workflow-sdk/index.js +6 -6
  450. package/dist/{wrap-CiSc_KCq.js → wrap-B2wziJI_.js} +4 -4
  451. package/dist/{wrap-B6R0cMRL.d.ts → wrap-DxXB54Wu.d.ts} +8 -5
  452. package/package.json +8 -8
  453. package/schemas/catalog-v1.json +3 -0
  454. package/schemas/supabase-manifest-v1.json +33 -1
  455. package/skills/permdock-credentials/references/tokens-keys-links.md +8 -6
  456. package/skills/permdock-data/references/queries-and-rls.md +2 -1
  457. package/skills/permdock-tenancy/references/ownership-and-custom-roles.md +2 -2
  458. package/skills/permdock-wire/references/adapters.md +16 -23
  459. package/dist/catalog-BCCjSAdH.js +0 -1
  460. package/dist/collect-k_Dulxr5.js +0 -1
  461. package/dist/config-CoQHDrDi.js +0 -166
  462. package/dist/denied-text-D4qkEtFz.d.ts +0 -19
  463. package/dist/fields-ClcQLrVc.js +0 -238
  464. package/dist/generate-CNU1pIOo.js +0 -171
  465. package/dist/headers-BIjfMaoC.js +0 -8
  466. package/dist/headers-CIbDTUTK.d.ts +0 -7
  467. package/dist/load-DrMHXzgN.js +0 -1
  468. package/dist/output-files-DPAY7-GX.js +0 -1
  469. package/dist/package-root-Dk_ETdkp.js +0 -31
  470. package/dist/parse-local-manifest-e4mdwAKd.d.ts +0 -10
  471. package/dist/policies-B2lZIurj.js +0 -295
  472. package/dist/powersync-CExtTLyA.js +0 -1
  473. package/dist/problem-details-Tcfmj3bV.d.ts +0 -7
  474. package/dist/protected-BwZYL1X_.d.ts +0 -39
  475. package/dist/pull-Cty6Ovyu.js +0 -1
  476. package/dist/run-Bz7DXGEv.js +0 -1
  477. package/dist/stores-BAZwSYZA.d.ts +0 -31
  478. package/dist/supabase-hook-DJxC4PoP.js +0 -1
  479. package/dist/types-CKV71kKh.d.ts +0 -172
  480. package/dist/usage-y-kBNdqs.js +0 -1
  481. package/dist/version-CEbQ5UeM.js +0 -18
  482. package/dist/version-DXkYUISx.js +0 -1
  483. package/dist/watch-Bd9mx3rd.js +0 -1
@@ -1,19 +1,15 @@
1
- import { a as sole, n as isReadonlyArray, r as isRecord } from "./compact-CAf557ZB.js";
2
- import { n as isForbiddenKey } from "./paths-DXSHAMIa.js";
3
- import { u as isSqlFunctionField } from "./refs-CNJBiTFo.js";
4
- import { c as listPermissions, h as formerKeys } from "./permission-registry-DpPtSKNZ.js";
5
- import { u as isoAt } from "./time-DkWjdL98.js";
6
- import { f as scopeList, l as rootScope, s as resolveScope, u as scopeChain } from "./scopes-BHsc-wp-.js";
7
- import { t as isoInstant } from "./instant-BluqDvyS.js";
8
- import { m as findRole, v as listRoles } from "./grantee-BrRANrpT.js";
9
- import { r as relationCondition, t as inheritCondition } from "./grantee-conditions-C6P1Tvol.js";
10
- import { l as partitionsOf, p as scopeField } from "./tenancy-CmeBQ6Nv.js";
11
- import { a as delegatedScopeMap } from "./permdock-BfWEeMqk.js";
12
- import { i as configError } from "./errors-DpP_U6Bf.js";
13
- import { d as sqlTextArray, i as qualifySqlTable, t as SQL_IDENT } from "./sql-B96movIM.js";
14
- import { i as scopeMembershipTable, o as relatedSql, r as scopeColumn } from "./compile-BAEO5bOk.js";
15
- import { d as keptKeys, l as roleColumn, u as roleManifest } from "./sources-B-zYB4GS.js";
16
- import { A as quoteQualified, D as qualified, E as helperSchema, F as scopeTypeOf, I as sqlType, L as tenantTypeOf, M as textArrayLiteral, O as quoteIdent, S as CLAIM, T as declaredArray, _ as subjectClaimJsonSql, b as tenantClaimSql, d as keyTenantSql, f as kindFilterSql, h as roleKindSql, i as allowEntry, j as quoteTable, k as quoteLiteral, m as memberVia, n as GLOBAL_ROLES, o as rootName, p as liveSessionSql, r as HELPERS, s as signedIn, t as CUSTOM_ROLES, u as globalKindFilterSql, v as subjectClaimSql, w as CLI, x as userIdHelperSql, y as subjectIdSql } from "./shared-fMKOv8hP.js";
1
+ import { c as listPermissions, h as formerKeys } from "./permission-registry-B8tYxPIt.js";
2
+ import { a as expandRelation, c as isEdgeRelation, d as isSelfParented, l as isFieldRelation, o as groupEntries, p as restrictedFor, u as isPrincipalRelation } from "./resource-relations-8-pCScBO.js";
3
+ import { f as scopeList, l as rootScope, s as resolveScope, u as scopeChain } from "./scopes-0Up_UA2t.js";
4
+ import { o as flattenGrantee } from "./grantee-Dke-9rd1.js";
5
+ import { r as relationCondition, t as inheritCondition } from "./grantee-conditions-BrYPe-w0.js";
6
+ import { l as partitionsOf, p as scopeField } from "./tenancy-5d1zVBvK.js";
7
+ import { a as delegatedScopeMap } from "./permdock-BvjivVb9.js";
8
+ import { i as configError } from "./errors-Vpu6rKHl.js";
9
+ import { d as sqlTextArray, i as qualifySqlTable, t as SQL_IDENT } from "./sql-0WjxIp9g.js";
10
+ import { a as scopeMembershipTable, d as restrictedRowsSql, i as scopeColumn, l as relationArmSql } from "./compile-EzEMB-7v.js";
11
+ import { d as keptKeys, l as roleColumn, u as roleManifest } from "./sources-CMAM-2A3.js";
12
+ import { A as quoteQualified, D as qualified, E as helperSchema, I as scopeTypeOf, O as quoteIdent, R as tenantTypeOf, _ as subjectClaimJsonSql, d as keyTenantSql, f as kindFilterSql, h as roleKindSql, i as allowEntry, j as quoteTable, k as quoteLiteral, m as memberVia, n as GLOBAL_ROLES, o as rootName, r as HELPERS, s as signedIn, t as CUSTOM_ROLES, u as globalKindFilterSql, v as subjectClaimSql, w as CLI, x as userIdHelperSql, y as subjectIdSql } from "./shared-Afctg7B0.js";
17
13
  //#region src/cli/sql/active.ts
18
14
  function checkActiveRow(label, row) {
19
15
  quoteTable(row.table);
@@ -86,16 +82,19 @@ function activeUserSql(ctx, user = subjectIdSql(ctx)) {
86
82
  const row = ctx.suspension?.users;
87
83
  return row === void 0 ? [] : [activeRowSql(row, user)];
88
84
  }
89
- /** The permission of a grant key held in `grant`: `key#group` and `key@level` name `key`. */
85
+ /**
86
+ * The permission of a grant key held in `grant`: `key#group` and `key@level`
87
+ * name `key`.
88
+ */
90
89
  function grantPermissionSql(grant) {
91
90
  return { sql: `split_part(split_part(${grant}, '#', 1), '@', 1)` };
92
91
  }
93
92
  /**
94
- * The active-instance check for every suspendable scope on `scope`'s chain:
95
- * its own and each ancestor's. `idOf` gives the SQL for the id the membership
96
- * holds for a scope on that chain. A scope's `keep` lets `permission` through
97
- * a suspended instance: a kept key known now drops its check, and one read
98
- * at run time is compared with the kept keys.
93
+ * The active-instance check for every suspendable scope on `scope`'s chain: its
94
+ * own and each ancestor's. `idOf` gives the SQL for the id the membership holds
95
+ * for a scope on that chain. A scope's `keep` lets `permission` through a
96
+ * suspended instance: a kept key known now drops its check, and one read at run
97
+ * time is compared with the kept keys.
99
98
  */
100
99
  function activeInstancesSql(ctx, scope, idOf, permission = ctx.permission === void 0 ? void 0 : { key: ctx.permission }) {
101
100
  const parts = [];
@@ -117,8 +116,8 @@ function activeInstancesSql(ctx, scope, idOf, permission = ctx.permission === vo
117
116
  const CLOSURE = { table: "permdock_closure" };
118
117
  /**
119
118
  * The snake_case SQL name of a resource or link (`chatThread` becomes
120
- * `chat_thread`), or `undefined` when it is not made of ASCII letters,
121
- * digits and underscores starting with a letter.
119
+ * `chat_thread`), or `undefined` when it is not made of ASCII letters, digits
120
+ * and underscores starting with a letter.
122
121
  */
123
122
  function graphSqlName(name) {
124
123
  if (!/^[A-Za-z][A-Za-z0-9_]*$/u.test(name)) return;
@@ -129,21 +128,30 @@ function sqlNameOf(name, label) {
129
128
  if (sql === void 0) throw configError(`PermDock CLI: ${label} is not a SQL name (letters, digits and underscores, starting with a letter), so rls generate cannot name its helper`);
130
129
  return sql;
131
130
  }
132
- /** The helper returning the ids of `resource` the subject holds a relation on: `permitted_<snake_case resource>_ids`. */
131
+ /**
132
+ * The helper returning the ids of `resource` the subject holds a relation on:
133
+ * `permitted_<snake_case resource>_ids`.
134
+ */
133
135
  function graphHelper(resource) {
134
136
  return permittedIdsHelper(sqlNameOf(resource, `resource '${resource}'`));
135
137
  }
136
138
  function inheritedRowsHelper(resource) {
137
139
  return `permitted_${sqlNameOf(resource, `resource '${resource}'`)}_rows`;
138
140
  }
139
- /** The helper returning the ids of `resource` whose `link` points into the ids it is given. */
141
+ /**
142
+ * The helper returning the ids of `resource` whose `link` points into the ids
143
+ * it is given.
144
+ */
140
145
  function linkHelper(resource, link) {
141
146
  return `permdock_link_${sqlNameOf(resource, `resource '${resource}'`)}_${sqlNameOf(link, `link '${link}' on ${resource}`)}`;
142
147
  }
143
148
  function restrictedHelper(resource) {
144
149
  return `permdock_restricted_${sqlNameOf(resource, `resource '${resource}'`)}`;
145
150
  }
146
- /** Graph SQL parts as RLS text: values inline as literals, the subject from the dialect's claim. */
151
+ /**
152
+ * Graph SQL parts as RLS text: values inline as literals, the subject from the
153
+ * dialect's claim.
154
+ */
147
155
  function graphSqlText(parts, ctx) {
148
156
  return parts.map((part) => {
149
157
  if ("text" in part) return part.text;
@@ -170,23 +178,51 @@ function permittedIdsHelper(name) {
170
178
  if (!/^[a-z][a-z0-9_]*$/u.test(name)) throw configError(`PermDock CLI: unsafe scope name '${name}'`);
171
179
  return `permitted_${name}_ids`;
172
180
  }
173
- /** `permitted_<scope>_ids_for(p_user, p_grant)`: the same ids for a user the caller names. */
181
+ /**
182
+ * `permitted_<scope>_ids_for(p_user, p_grant)`: the same ids for a user the
183
+ * caller names.
184
+ */
174
185
  function permittedForHelper(name) {
175
186
  return `${permittedIdsHelper(name)}_for`;
176
187
  }
177
- /** The helper returning the ids of scope `name` the subject holds any live membership of. */
188
+ /**
189
+ * The helper returning the ids of scope `name` the subject holds any live
190
+ * membership of.
191
+ */
178
192
  function memberIdsHelper(name) {
179
193
  if (!/^[a-z][a-z0-9_]*$/u.test(name)) throw configError(`PermDock CLI: unsafe scope name '${name}'`);
180
194
  return `member_${name}_ids`;
181
195
  }
182
- /** `member_<scope>_ids_for(p_user uuid)`: the same rows for a user the caller names; the token hook's helper. */
196
+ /**
197
+ * `member_<scope>_ids_for(p_user uuid)`: the same rows for a user the caller
198
+ * names; the token hook's helper.
199
+ */
183
200
  function memberForHelper(name) {
184
201
  return `${memberIdsHelper(name)}_for`;
185
202
  }
186
203
  /**
187
- * Whether `member_<scope>_ids_for` is generated for `name`: Supabase only
188
- * (its one caller is `supabase_auth_admin`), and only where `memberships` maps
189
- * a table for the scope or a membership source can hold it.
204
+ * `member_<scope>_ids_of(p_user uuid)`: `member_<scope>_ids_for(p_user)` when
205
+ * `p_user` is the caller and no rows otherwise, so `authenticated` may execute
206
+ * it; better-supabase's `teamIdsOf` template calls it.
207
+ */
208
+ function memberOfHelper(name) {
209
+ return `${memberIdsHelper(name)}_of`;
210
+ }
211
+ /** A scope better-supabase reads as its teams: `team` or `teams` (doctor BS417). */
212
+ function isTeamScope(name) {
213
+ return /^teams?$/u.test(name);
214
+ }
215
+ /**
216
+ * Whether `member_<scope>_ids_of` is generated: a team scope with
217
+ * `member_<scope>_ids_for`.
218
+ */
219
+ function hasMemberOf(input, name) {
220
+ return isTeamScope(name) && hasMemberFor(input, name);
221
+ }
222
+ /**
223
+ * Whether `member_<scope>_ids_for` is generated for `name`: Supabase only (its
224
+ * one caller is `supabase_auth_admin`), and only where `memberships` maps a
225
+ * table for the scope or a membership source can hold it.
190
226
  */
191
227
  function hasMemberFor(input, name) {
192
228
  if (input.dialect !== "supabase") return false;
@@ -201,26 +237,36 @@ function hasPermittedUsers(input, name) {
201
237
  const sources = memberForSources(input, name);
202
238
  return sources.length > 0 && sources.every((source) => source.sql.holders !== void 0);
203
239
  }
204
- /** The table `member_<scope>_ids_for` reads for scope `name`, when `memberships` maps one with its id column. */
240
+ /**
241
+ * The table `member_<scope>_ids_for` reads for scope `name`, when `memberships`
242
+ * maps one with its id column.
243
+ */
205
244
  function memberForTable(input, name) {
206
245
  const table = scopeMembershipTable(input.memberships, input.scopes, name);
207
246
  return table !== void 0 && scopeColumn(table, input.scopes, name) !== void 0 ? table : void 0;
208
247
  }
209
- /** The sources `member_<scope>_ids_for` reads: none when a table is mapped, as in `database` mode. */
248
+ /**
249
+ * The sources `member_<scope>_ids_for` reads: none when a table is mapped, as
250
+ * in `database` mode.
251
+ */
210
252
  function memberForSources(input, name) {
211
253
  if (memberForTable(input, name) !== void 0) return [];
212
254
  return (input.memberSources ?? []).filter((source) => source.sql.scope === void 0 || source.sql.scope === name);
213
255
  }
214
256
  /**
215
- * The membership sources that can hold scope `name` when `memberships` maps
216
- * no table for it: every `tableMembershipSource` source and the `junctionMembershipSource` sources of
217
- * that scope. Empty outside `database` mode.
257
+ * The membership sources that can hold scope `name` when `memberships` maps no
258
+ * table for it: every `tableMembershipSource` source and the
259
+ * `junctionMembershipSource` sources of that scope. Empty outside `database`
260
+ * mode.
218
261
  */
219
262
  function scopeSources(ctx, name) {
220
263
  if (ctx.authorize !== "database" || scopeTable(ctx, name) !== void 0) return [];
221
264
  return (ctx.sources ?? []).filter((source) => source.sql.scope === void 0 || source.sql.scope === name);
222
265
  }
223
- /** The membership table mapped for scope `name`, with the column of its id and of the first scope's id. */
266
+ /**
267
+ * The membership table mapped for scope `name`, with the column of its id and
268
+ * of the first scope's id.
269
+ */
224
270
  function scopeTable(ctx, name) {
225
271
  const table = scopeMembershipTable(ctx.memberships, ctx.scopes, name);
226
272
  if (table === void 0) return;
@@ -238,481 +284,6 @@ function scopeTable(ctx, name) {
238
284
  };
239
285
  }
240
286
  //#endregion
241
- //#region src/cli/sql/conditions.ts
242
- const MAX_CLAIM_DEPTH = 8;
243
- /**
244
- * The claim path of a `principal.claim.*` / `principal.claims.*` ref, one
245
- * segment per JSON key; `undefined` for any other ref.
246
- */
247
- function claimPath(ref) {
248
- const prefix = ["principal.claim.", "principal.claims."].find((item) => ref.startsWith(item));
249
- if (prefix === void 0) return;
250
- const segments = ref.slice(prefix.length).split(".");
251
- if (segments.length > MAX_CLAIM_DEPTH) throw configError(`PermDock CLI: claim path '${ref}' is deeper than ${MAX_CLAIM_DEPTH}`);
252
- for (const segment of segments) if (!CLAIM.test(segment) || isForbiddenKey(segment)) throw configError(`PermDock CLI: unsafe claim name '${segment}'`);
253
- return segments;
254
- }
255
- function jsonPath(base, keys, last) {
256
- let sql = base;
257
- for (const [index, key] of keys.entries()) sql += ` ${index === keys.length - 1 ? last : "->"} ${quoteLiteral(key)}`;
258
- return sql;
259
- }
260
- /**
261
- * The JSON document a claim path starts from and the keys to walk in it. The
262
- * `guc` dialect keeps each top-level claim in its own setting as JSON text.
263
- */
264
- function claimRoot(ctx, path) {
265
- const [head, ...rest] = path;
266
- if (head === void 0) throw configError("PermDock CLI: empty claim path");
267
- switch (ctx.dialect) {
268
- case "supabase": return {
269
- root: "(select auth.jwt())",
270
- keys: path
271
- };
272
- case "neon": return {
273
- root: "(select auth.session())",
274
- keys: path
275
- };
276
- case "guc": return {
277
- root: subjectClaimJsonSql(ctx, head),
278
- keys: rest
279
- };
280
- default: return ctx.dialect;
281
- }
282
- }
283
- /** The claim at `path` as `jsonb`. */
284
- function claimJsonSql(ctx, path) {
285
- const { root, keys } = claimRoot(ctx, path);
286
- return keys.length === 0 ? root : `(${jsonPath(root, keys, "->")})`;
287
- }
288
- /** The claim at `path` as text: `->>` on the last key, or the plain setting for a one-segment `guc` claim. */
289
- function claimTextSql(ctx, path) {
290
- const [head] = path;
291
- if (path.length === 1 && head !== void 0) return subjectClaimSql(ctx, head);
292
- const { root, keys } = claimRoot(ctx, path);
293
- return `(${jsonPath(root, keys, "->>")})`;
294
- }
295
- /** The JSON kind a claim must have to compare with a column of `type`. */
296
- function jsonKindOf(type) {
297
- if (type === "numeric") return "number";
298
- return type === "boolean" ? "boolean" : "string";
299
- }
300
- /**
301
- * A claim compared with a column of `type`. Numbers and booleans must be JSON
302
- * of that kind (anything else is `null`, so the comparison is false, as in
303
- * memory); other types cast the text form. A one-segment `guc` claim is text
304
- * and is cast as is.
305
- */
306
- function typedClaimSql(ctx, path, type) {
307
- const text = claimTextSql(ctx, path);
308
- if (type === void 0 || type === "text") return text;
309
- const cast = `${text}::${sqlType(type)}`;
310
- const kind = jsonKindOf(type);
311
- if (kind === "string" || ctx.dialect === "guc" && path.length === 1) return `(${cast})`;
312
- return `(case when jsonb_typeof(${claimJsonSql(ctx, path)}) = '${kind}' then ${cast} end)`;
313
- }
314
- /**
315
- * The elements of an array claim as a Postgres array of the column's type,
316
- * built once per statement (an uncorrelated `array(select ...)` is an InitPlan).
317
- * A missing or non-array claim is the empty array; elements of another JSON
318
- * kind are left out.
319
- */
320
- function claimArraySql(ctx, path, type) {
321
- const json = claimJsonSql(ctx, path);
322
- return `array(select ${type === void 0 || type === "text" ? `(e #>> '{}')` : `(e #>> '{}')::${sqlType(type)}`} from jsonb_array_elements(case when jsonb_typeof(${json}) = 'array' then ${json} else '[]'::jsonb end) e where jsonb_typeof(e) = '${jsonKindOf(type)}')`;
323
- }
324
- function sqlValue(value, ctx, field) {
325
- if (value !== null && typeof value === "object" && "ref" in value) return compileRef(value.ref, ctx, field);
326
- if (value !== null && typeof value === "object" && "date" in value) {
327
- const instant = isoInstant(value.date);
328
- return instant === void 0 ? "null" : quoteLiteral(isoAt(instant));
329
- }
330
- if (isReadonlyArray(value)) return `array[${value.map((item) => sqlValue(item, ctx)).join(", ")}]`;
331
- if (value === null) return "null";
332
- if (typeof value === "boolean") return value ? "true" : "false";
333
- if (typeof value === "number") return Number.isFinite(value) ? String(value) : "null";
334
- if (typeof value === "string") return quoteLiteral(value);
335
- throw configError("PermDock CLI: non-portable condition value");
336
- }
337
- function compileRef(ref, ctx, field) {
338
- if (ref === "principal.id") return subjectIdSql(ctx);
339
- if (ref === "now") return "now()";
340
- const path = claimPath(ref);
341
- if (ref === "principal.tenant" || path?.length === 1 && path[0] === ctx.tenantClaim) return tenantClaimSql(ctx);
342
- if (path !== void 0) return typedClaimSql(ctx, path, field === void 0 ? void 0 : ctx.columnTypes?.[field]);
343
- if (ref === "context" || ref.startsWith("context.")) throw configError(`PermDock CLI: '${ref}' is request context, which is not in the token; RLS cannot read it (permdock doctor PD027)`);
344
- throw configError(`PermDock CLI: non-portable subject ref '${ref}'`);
345
- }
346
- /** `value` with `\`, `%` and `_` escaped for `like ... escape '\'`, as `escapeLike` does in process. */
347
- function likeLiteralSql(value) {
348
- return `replace(replace(replace(${value}, '\\', '\\\\'), '%', '\\%'), '_', '\\_')`;
349
- }
350
- function compareSql(op, field, value) {
351
- const left = quoteIdent(field);
352
- switch (op) {
353
- case "eq": return `${left} = ${value}`;
354
- case "ne": return `${left} <> ${value}`;
355
- case "gt": return `${left} > ${value}`;
356
- case "gte": return `${left} >= ${value}`;
357
- case "lt": return `${left} < ${value}`;
358
- case "lte": return `${left} <= ${value}`;
359
- case "contains": return `${left}::text like '%' || ${likeLiteralSql(`${value}::text`)} || '%' escape '\\'`;
360
- default: return op;
361
- }
362
- }
363
- /**
364
- * Whether the row's `rowField` is among the ids in `table.rowColumn` the
365
- * subject holds a matching membership on. The row field stays outside the
366
- * subquery: inside it, an unqualified name resolves to the membership table
367
- * first, so `m.org_id = "org_id"` would compare the membership with itself.
368
- */
369
- function membershipSql(table, rowColumn, rowField, roles, ctx, tenantColumn, scope) {
370
- const parts = [`m.${quoteIdent(table.user)} = ${subjectIdSql(ctx)}`];
371
- const role = memberRoleOf(table);
372
- if (roles.length > 0) {
373
- const held = role.through === void 0 && !role.lateral ? `m.${quoteIdent(role.column)}` : role.lookup;
374
- parts.push(`${held} = any(${textArrayLiteral(roles)})`);
375
- const via = memberVia(table);
376
- const single = sole(roles);
377
- const kind = single === void 0 ? kindFilterSql(ctx, role.lookup, via) : roleKindSql(ctx, single, via);
378
- if (kind !== void 0) parts.push(kind);
379
- }
380
- if (table.expiresAt !== void 0) parts.push(`(m.${quoteIdent(table.expiresAt)} is null or m.${quoteIdent(table.expiresAt)} > now())`);
381
- parts.push(...activeMembershipSql(ctx, table.disabledAt === void 0 ? void 0 : `m.${quoteIdent(table.disabledAt)}`));
382
- if (tenantColumn !== void 0 && ctx.tenants !== "all") parts.push(`m.${quoteIdent(tenantColumn)} = ${tenantClaimSql(ctx)}`);
383
- parts.push(...activeUserSql(ctx));
384
- if (scope !== void 0) {
385
- const tenantOf = scope === rootScope(ctx.scopes) ? rowColumn : tenantColumn;
386
- const keyed = keyTenantSql(ctx, tenantOf === void 0 ? void 0 : `m.${quoteIdent(tenantOf)}`);
387
- if (keyed !== void 0) parts.push(keyed);
388
- parts.push(...activeInstancesSql(ctx, scope, (name) => {
389
- const held = scopeColumn(table, ctx.scopes, name);
390
- return held === void 0 ? void 0 : `m.${quoteIdent(held)}`;
391
- }));
392
- }
393
- const expand = roles.length > 0 && role.lateral ? role.join : "";
394
- return `${quoteIdent(rowField)} in (select m.${quoteIdent(rowColumn)} from ${quoteTable(table.table)} m${expand} where ${parts.join(" and ")})`;
395
- }
396
- function compileMemberOf(condition, ctx) {
397
- const scope = condition.scope === "resource" ? void 0 : resolveScope(ctx.scopes, condition.scope);
398
- if (condition.scope !== "resource" && scope === void 0) throw configError(`PermDock CLI: memberOf ${condition.scope} names a scope the policy does not declare`);
399
- const mapping = scope === void 0 ? condition.resource === void 0 ? void 0 : ctx.memberships?.resource?.[condition.resource] : scopeMembershipTable(ctx.memberships, ctx.scopes, scope);
400
- if (mapping !== void 0) {
401
- const named = scope === void 0 ? void 0 : scopeTable(ctx, scope);
402
- const rowColumn = scope === void 0 ? mapping.id : named?.column;
403
- if (rowColumn === void 0) throw configError(`PermDock CLI: memberships mapping for ${condition.scope} is missing the row column`);
404
- const tenantColumn = scope === void 0 || scope === rootScope(ctx.scopes) ? void 0 : named?.tenantColumn;
405
- const primary = membershipSql(mapping, rowColumn, condition.field, condition.roles, ctx, tenantColumn, scope);
406
- if (condition.scope !== "resource" || condition.parents === void 0) return primary;
407
- return `(${[primary, ...condition.parents.flatMap((parent) => {
408
- if (typeof parent === "string") return [membershipSql(mapping, rowColumn, parent, condition.roles, ctx, tenantColumn)];
409
- const hopTable = ctx.memberships?.resource?.[parent.resource];
410
- return hopTable?.id === void 0 ? [] : [membershipSql(hopTable, hopTable.id, parent.field, condition.roles, ctx)];
411
- })].join(" or ")})`;
412
- }
413
- if (scope !== void 0 && condition.roles.length === 0 && scopeSources(ctx, scope).length > 0) return `${quoteIdent(condition.field)} in (select ${qualified(ctx, memberIdsHelper(scope))}())`;
414
- if (scope !== void 0 && scope === rootScope(ctx.scopes)) {
415
- const parts = [
416
- ctx.tenants === "all" ? `${quoteIdent(condition.field)} in (select ${qualified(ctx, memberIdsHelper(scope))}())` : `${quoteIdent(condition.field)} = ${tenantClaimSql(ctx)}`,
417
- ...ctx.tenants === "all" ? [] : [keyTenantSql(ctx, quoteIdent(condition.field))].filter((part) => part !== void 0),
418
- ...activeUserSql(ctx),
419
- ...activeInstancesSql(ctx, scope, () => quoteIdent(condition.field))
420
- ];
421
- return parts.length === 1 ? parts.join("") : `(${parts.join(" and ")})`;
422
- }
423
- throw configError(`PermDock CLI: memberOf ${condition.scope} needs a memberships table mapping`);
424
- }
425
- function compileConditionSql(condition, ctx) {
426
- switch (condition.op) {
427
- case "eq":
428
- case "ne":
429
- case "gt":
430
- case "gte":
431
- case "lt":
432
- case "lte": return compareSql(condition.op, condition.field, sqlValue(condition.value, ctx, condition.field));
433
- case "contains": {
434
- const element = ctx.arrayColumns?.[condition.field];
435
- if (element !== void 0) return `${sqlValue(condition.value, {
436
- ...ctx,
437
- columnTypes: {
438
- ...ctx.columnTypes,
439
- [condition.field]: element
440
- }
441
- }, condition.field)} = any(${quoteIdent(condition.field)})`;
442
- return compareSql(condition.op, condition.field, sqlValue(condition.value, ctx, condition.field));
443
- }
444
- case "in":
445
- case "notIn": {
446
- const keyword = condition.op === "in" ? "in" : "not in";
447
- if (!Array.isArray(condition.value) && typeof condition.value === "object" && "ref" in condition.value) {
448
- const path = claimPath(condition.value.ref);
449
- if (path === void 0) {
450
- compileRef(condition.value.ref, ctx);
451
- throw configError(`PermDock CLI: non-portable ${condition.op} against '${condition.value.ref}'`);
452
- }
453
- const column = quoteIdent(condition.field);
454
- const list = claimArraySql(ctx, path, ctx.columnTypes?.[condition.field]);
455
- return condition.op === "in" ? `${column} = any (${list})` : `(${column} is not null and not (${column} = any (${list})))`;
456
- }
457
- const values = condition.value.map((item) => sqlValue(item, ctx));
458
- if (values.length === 0) return condition.op === "in" ? "false" : `${quoteIdent(condition.field)} is not null`;
459
- return `${quoteIdent(condition.field)} ${keyword} (${values.join(", ")})`;
460
- }
461
- case "isNull": return `${quoteIdent(condition.field)} is ${condition.value ? "" : "not "}null`;
462
- case "and": return `(${condition.conditions.map((item) => compileConditionSql(item, ctx)).join(" and ")})`;
463
- case "or":
464
- if (condition.conditions.length === 0) return "false";
465
- return `(${condition.conditions.map((item) => compileConditionSql(item, ctx)).join(" or ")})`;
466
- case "not": return `(${compileConditionSql(condition.condition, ctx)}) is not true`;
467
- case "memberOf": return compileMemberOf(condition, ctx);
468
- case "related": return compileRelatedSql(condition, ctx);
469
- case "sqlFunction":
470
- if (ctx.inlineFunctions === true) return compileConditionSql(condition.twin, ctx);
471
- return `${quoteTable(condition.name)}(${condition.args.map((arg) => isSqlFunctionField(arg) ? quoteIdent(arg.field) : sqlValue(arg, ctx)).join(", ")})`;
472
- case "opaque": return condition.sql;
473
- case "liveSession": return liveSessionSql(ctx);
474
- default: return condition;
475
- }
476
- }
477
- function andConditions(left, right) {
478
- if (left === void 0) return right;
479
- if (right === void 0) return left;
480
- return {
481
- op: "and",
482
- conditions: [left, right]
483
- };
484
- }
485
- function valueContextRefs(value) {
486
- if (Array.isArray(value)) return value.flatMap(valueContextRefs);
487
- if (isRecord(value) && typeof value["ref"] === "string" && (value["ref"] === "context" || value["ref"].startsWith("context."))) return [value["ref"]];
488
- return [];
489
- }
490
- /**
491
- * The `context.*` refs a condition reads. The request context is not in the
492
- * token, so RLS cannot evaluate them.
493
- */
494
- function contextRefs(condition) {
495
- if (condition === void 0) return [];
496
- switch (condition.op) {
497
- case "eq":
498
- case "ne":
499
- case "gt":
500
- case "gte":
501
- case "lt":
502
- case "lte":
503
- case "contains":
504
- case "in":
505
- case "notIn": return valueContextRefs(condition.value);
506
- case "and":
507
- case "or": return condition.conditions.flatMap((child) => contextRefs(child));
508
- case "not": return contextRefs(condition.condition);
509
- case "sqlFunction": return [...condition.args.flatMap(valueContextRefs), ...contextRefs(condition.twin)];
510
- case "isNull":
511
- case "memberOf":
512
- case "related":
513
- case "opaque":
514
- case "liveSession": return [];
515
- default: return condition;
516
- }
517
- }
518
- function sqlFunctionNames(condition) {
519
- if (condition === void 0) return [];
520
- switch (condition.op) {
521
- case "sqlFunction": return [condition.name, ...sqlFunctionNames(condition.twin)];
522
- case "and":
523
- case "or": return condition.conditions.flatMap((child) => sqlFunctionNames(child));
524
- case "not": return sqlFunctionNames(condition.condition);
525
- case "eq":
526
- case "ne":
527
- case "gt":
528
- case "gte":
529
- case "lt":
530
- case "lte":
531
- case "contains":
532
- case "in":
533
- case "notIn":
534
- case "isNull":
535
- case "memberOf":
536
- case "related":
537
- case "opaque":
538
- case "liveSession": return [];
539
- default: return condition;
540
- }
541
- }
542
- /**
543
- * A `related` condition as uncorrelated subqueries, so Postgres runs the
544
- * helper once per statement: the row's field in the closure's descendants of
545
- * the ids the subject holds the relation on (or those ids alone at depth 0).
546
- * The helper sits in `array(...)` so it stays an InitPlan; a plain `in` gets
547
- * pulled into a join that can rescan it per closure row.
548
- */
549
- function compileRelatedSql(condition, ctx) {
550
- if (condition.hops !== void 0 && condition.hops.length > 0) return compileHoppedSql(condition, ctx);
551
- const type = ctx.columnTypes?.[condition.field];
552
- const cast = type === void 0 || type === "text" ? "" : `::${type}`;
553
- const column = type === void 0 ? `${quoteIdent(condition.field)}::text` : quoteIdent(condition.field);
554
- const helper = condition.permission === void 0 ? `${qualified(ctx, graphHelper(condition.resource))}(${quoteLiteral(condition.relation)})` : `${qualified(ctx, inheritedRowsHelper(condition.resource))}(${quoteLiteral(condition.permission)})`;
555
- const cap = ctx.graph?.closures[condition.resource];
556
- let inner;
557
- if (condition.depth > 0 && cap !== void 0) {
558
- const depth = condition.depth < cap ? ` and depth <= ${String(condition.depth)}` : "";
559
- inner = `select descendant${cast} from ${qualified(ctx, CLOSURE.table)} where resource = ${quoteLiteral(condition.resource)}${depth} and ancestor = any (array(select ${helper}))`;
560
- } else inner = cast === "" ? `select ${helper}` : `select p.id${cast} from ${helper} as p(id)`;
561
- const reach = `${column} in (${inner})`;
562
- if (condition.parent === true && condition.restricted !== void 0) return `(${reach} and ${quoteIdent(condition.restricted)} is not true)`;
563
- return reach;
564
- }
565
- /**
566
- * A `related` condition that crosses link hops: the reached instances of the
567
- * last resource, carried back over each link by its security definer helper,
568
- * so the hop reads a table the subject may not see.
569
- */
570
- function compileHoppedSql(condition, ctx) {
571
- const resources = ctx.graph?.resources;
572
- if (resources === void 0) throw configError("PermDock CLI: a related condition with link hops needs the policy resources in the RLS context");
573
- const schema = quoteIdent(helperSchema(ctx));
574
- return graphSqlText(relatedSql(condition, {
575
- resources,
576
- ...ctx.graph?.tables === void 0 ? {} : { tables: ctx.graph.tables },
577
- closure: `${helperSchema(ctx)}.${CLOSURE.table}`,
578
- closureDepths: ctx.graph?.closures ?? {},
579
- qualify: qualifySqlTable,
580
- holders: (resource, relation) => [{ text: `select ${schema}.${graphHelper(resource)}(${quoteLiteral(relation)})` }],
581
- permitted: (resource, permission) => [{ text: `select ${schema}.${inheritedRowsHelper(resource)}(${quoteLiteral(permission)})` }],
582
- restrictedRows: (resource) => [{ text: `select ${schema}.${restrictedHelper(resource)}()` }],
583
- linked: (resource, link, targets) => [
584
- { text: `select ${schema}.${linkHelper(resource, link)}(array(` },
585
- ...targets,
586
- { text: "))" }
587
- ]
588
- }), ctx);
589
- }
590
- //#endregion
591
- //#region src/cli/sql/grants.ts
592
- /**
593
- * The `role_permissions` grant key of a break-glass grant. It differs from
594
- * the permission key, so `authorize()` and the table policies never count it;
595
- * only the break-glass read asks for it.
596
- */
597
- function breakGlassKey(permission) {
598
- return `${permission}#break-glass`;
599
- }
600
- /**
601
- * The role and scope holding a break-glass grant. `breakGlass()` sets
602
- * `to: authenticated`, so inside `role()` the role is on the grant itself.
603
- */
604
- function breakGlassHolder(item) {
605
- if (item.access.kind === "role") return {
606
- role: item.access.role,
607
- scope: item.access.scope
608
- };
609
- const { role, scope } = item.grant;
610
- return role === null || typeof scope !== "string" ? void 0 : {
611
- role,
612
- scope
613
- };
614
- }
615
- function granteeItems(to) {
616
- return Array.isArray(to) ? to : [to];
617
- }
618
- function relationWhere(policy, grant, grantee) {
619
- const node = policy.resources.get(grant.permission.resource);
620
- const where = relationCondition(grantee, node, scopeList(policy.scopes), {
621
- resources: policy.resources,
622
- now: { ref: "now" }
623
- });
624
- if (where === void 0) throw configError(`PermDock CLI: grant ${grant.permission.key} names relation '${grantee.relation}', which ${grant.permission.resource} does not declare`);
625
- return where;
626
- }
627
- function inheritWhere(policy, grant, grantee) {
628
- const where = inheritCondition(grantee, policy.resources.get(grant.permission.resource), policy.resources);
629
- if (where === void 0) throw configError(`PermDock CLI: grant ${grant.permission.key} inherits ${grantee.permission}, which ${grant.permission.resource} cannot reach`);
630
- return where;
631
- }
632
- /** Only a deny for `oauth-client` actors compiles: Postgres sees `client_id` and `act`, not other actor kinds. */
633
- function actorAccess(grant, grantee, others) {
634
- if (grant.effect !== "deny" || grantee.actor !== "oauth-client" || others > 0) throw configError(`PermDock CLI: grant ${grant.permission.key} is limited to an actor grantee; RLS compiles only deny(…, { to: actor('oauth-client') }) on its own, so enforce it in the application`);
635
- return {
636
- kind: "actor",
637
- actor: "oauth-client"
638
- };
639
- }
640
- function accessOf(grant, roles, anyone) {
641
- const [held, ...more] = roles;
642
- if (more.length > 0) throw configError(`PermDock CLI: grant ${grant.permission.key} requires several roles at once (${roles.map((item) => item.role).join(", ")}); RLS compiles one role per grant`);
643
- if (held === void 0) return anyone ? { kind: "anyone" } : { kind: "authenticated" };
644
- if (typeof held.scope === "object") return {
645
- kind: "resource",
646
- role: held.role,
647
- resource: held.scope.resource
648
- };
649
- return {
650
- kind: "role",
651
- role: held.role,
652
- scope: held.scope
653
- };
654
- }
655
- /**
656
- * Every grant RLS must enforce: role grants and top-level
657
- * `definePolicy({ grants })` alike (`policy.grants` holds both). A grantee
658
- * Postgres cannot see (plan, actor, assurance) fails with the permission named.
659
- */
660
- function collectGrants(policy) {
661
- return policy.grants.map((grant) => {
662
- const items = granteeItems(grant.to);
663
- const roles = [];
664
- let anyone = true;
665
- let where = grant.where;
666
- let delegated;
667
- for (const item of items) switch (item.kind) {
668
- case "role":
669
- roles.push(item);
670
- break;
671
- case "anyone": break;
672
- case "authenticated":
673
- anyone = false;
674
- break;
675
- case "relation":
676
- anyone = false;
677
- where = andConditions(where, relationWhere(policy, grant, item));
678
- break;
679
- case "inherit":
680
- anyone = false;
681
- where = andConditions(where, inheritWhere(policy, grant, item));
682
- break;
683
- case "actor":
684
- delegated = actorAccess(grant, item, items.length - 1);
685
- break;
686
- case "plan":
687
- case "assurance": throw configError(`PermDock CLI: grant ${grant.permission.key} is limited to a ${item.kind} grantee, which RLS cannot compile; enforce it in the application`);
688
- default: return item;
689
- }
690
- const access = delegated ?? accessOf(grant, roles, anyone);
691
- const label = access.kind === "role" || access.kind === "resource" ? access.role : access.kind;
692
- return where === void 0 ? {
693
- grant,
694
- label,
695
- access
696
- } : {
697
- grant,
698
- label,
699
- access,
700
- where
701
- };
702
- });
703
- }
704
- /** The declared roles a custom role may draw grants from: the custom-role ceiling. */
705
- function assignableRoles(policy) {
706
- return [...roleNames(policy)].toSorted().filter((name) => policy.rolesByName.get(name)?.assignable ?? listRoles(policy.vocabulary.roles).some((leaf) => leaf.key === name && leaf.assignable));
707
- }
708
- /** Declared role names (bindings and `defineRoles` leaves) plus any role a top-level grant names. */
709
- function roleNames(policy) {
710
- const names = new Set(policy.roles.map((item) => item.name));
711
- for (const leaf of listRoles(policy.vocabulary.roles)) names.add(leaf.key);
712
- for (const item of collectGrants(policy)) if (item.access.kind === "role" || item.access.kind === "resource") names.add(item.access.role);
713
- return [...names];
714
- }
715
- //#endregion
716
287
  //#region src/cli/rls/api-keys.ts
717
288
  const API_KEY_ALLOWS = "permdock_api_key_allows";
718
289
  const DEFAULTS = {
@@ -838,7 +409,10 @@ ${body}
838
409
  }
839
410
  //#endregion
840
411
  //#region src/cli/shared/compare.ts
841
- /** UTF-16 code-unit order, never `localeCompare`: generated SQL must match byte for byte on every machine. */
412
+ /**
413
+ * UTF-16 code-unit order, never `localeCompare`: generated SQL must match byte
414
+ * for byte on every machine.
415
+ */
842
416
  function byCodeUnit(a, b) {
843
417
  return a < b ? -1 : a > b ? 1 : 0;
844
418
  }
@@ -854,10 +428,10 @@ const READERS = {
854
428
  trusted: "permdock_trusted_role_permissions"
855
429
  };
856
430
  /**
857
- * The custom-role objects: the tables (`database` mode), the ceiling view
858
- * over the `assignable` declared roles, and `permdock_custom_keys`, which
859
- * resolves one custom role to the grant keys it holds in a scope. A row in
860
- * the tables can never reach a key outside the view.
431
+ * The custom-role objects: the tables (`database` mode), the ceiling view over
432
+ * the `assignable` declared roles, and `permdock_custom_keys`, which resolves
433
+ * one custom role to the grant keys it holds in a scope. A row in the tables
434
+ * can never reach a key outside the view.
861
435
  */
862
436
  function customRolesSql(ctx) {
863
437
  const custom = ctx.customRoles;
@@ -984,7 +558,10 @@ revoke execute on function ${keys}(text[], text[], text[], text) from public, an
984
558
  if (shared !== "") chunks.push(shared);
985
559
  return chunks.join("\n\n");
986
560
  }
987
- /** `permdock_is_shared_role(p_scope, p_role)` (`database` mode): whether a shared custom role of that name exists at the scope. */
561
+ /**
562
+ * `permdock_is_shared_role(p_scope, p_role)` (`database` mode): whether a
563
+ * shared custom role of that name exists at the scope.
564
+ */
988
565
  function sharedRoleSql(ctx) {
989
566
  if (ctx.authorize !== "database") return "";
990
567
  const perms = qualified(ctx, CUSTOM_ROLES.permissions);
@@ -1006,8 +583,8 @@ $$;
1006
583
  revoke execute on function ${shared}(text, text) from public, anon, authenticated;`;
1007
584
  }
1008
585
  /**
1009
- * `permdock_custom_keys` when resources declare levels. An allow entry is
1010
- * `key` or `key@level`. A level reaches only the ceiling's `<grant key>@<level>`
586
+ * `permdock_custom_keys` when resources declare levels. An allow entry is `key`
587
+ * or `key@level`. A level reaches only the ceiling's `<grant key>@<level>`
1011
588
  * keys, plus the ceiling denies of that permission; a level no ceiling key
1012
589
  * carries removes the permission, as `resolveCustomRole` drops it with
1013
590
  * `unknown-level`.
@@ -1112,8 +689,8 @@ function currentKeysSql(array) {
1112
689
  }
1113
690
  /**
1114
691
  * SQL fragments that read a stored former key as its current key inside
1115
- * `permdock_custom_keys`. Without renames they are the plain column and
1116
- * array reads.
692
+ * `permdock_custom_keys`. Without renames they are the plain column and array
693
+ * reads.
1117
694
  */
1118
695
  function renamesSql(renames) {
1119
696
  if (renames.length === 0) return {
@@ -1137,14 +714,14 @@ function renamesSql(renames) {
1137
714
  }
1138
715
  /**
1139
716
  * The catalog reader `permdock_permission_keys()` and the role reader
1140
- * `permdock_role_permissions(p_role, p_scope, p_tenant, p_scope_id)`, both
1141
- * for `authenticated`. A declared role answers from `role_permissions`, which
1142
- * is the catalog. A custom role (`database` mode) answers from the tenant's
1143
- * own rows through `permdock_custom_keys`, only to a member of the tenant or
1144
- * a holder of a `meta.manageRoles` permission for a platform role; a shared
1145
- * role (no tenant below global) to any signed-in caller. The
1146
- * effect is `deny` for a deny the role carries after the ceiling, as
1147
- * `resolveCustomRole` returns it, never for a deny that merely removes an allow.
717
+ * `permdock_role_permissions(p_role, p_scope, p_tenant, p_scope_id)`, both for
718
+ * `authenticated`. A declared role answers from `role_permissions`, which is
719
+ * the catalog. A custom role (`database` mode) answers from the tenant's own
720
+ * rows through `permdock_custom_keys`, only to a member of the tenant or a
721
+ * holder of a `meta.manageRoles` permission for a platform role; a shared role
722
+ * (no tenant below global) to any signed-in caller. The effect is `deny` for a
723
+ * deny the role carries after the ceiling, as `resolveCustomRole` returns it,
724
+ * never for a deny that merely removes an allow.
1148
725
  */
1149
726
  function roleReadersSql(ctx, permissions, trustedReaders) {
1150
727
  const rp = qualified(ctx, "role_permissions");
@@ -1267,33 +844,42 @@ $$;
1267
844
  revoke execute on function ${trusted}(${signature}) from public, anon, authenticated;${grants}`);
1268
845
  return chunks.join("\n\n");
1269
846
  }
1270
- /** The stored definition of `p_role` at `p_scope` for `p_tenant`: the shared rows when a shared role has the name. */
847
+ /**
848
+ * The stored definition of `p_role` at `p_scope` for `p_tenant`: the shared
849
+ * rows when a shared role has the name.
850
+ */
1271
851
  function storedTenant(ctx) {
1272
852
  return `case when ${qualified(ctx, CUSTOM_ROLES.shared)}(p_scope, p_role) then c.tenant_id is null else c.tenant_id is not distinct from p_tenant end`;
1273
853
  }
1274
854
  /**
1275
- * The tenant half of a custom-role row match for a membership: the
1276
- * membership's tenant, or no tenant when a shared role of that name exists at
1277
- * the scope, so a shared role shadows a tenant role of the same name.
855
+ * The tenant half of a custom-role row match for a membership: the membership's
856
+ * tenant, or no tenant when a shared role of that name exists at the scope, so
857
+ * a shared role shadows a tenant role of the same name.
1278
858
  */
1279
859
  function customTenantMatch(ctx, tenant, scope, role) {
1280
860
  return `case when ${qualified(ctx, CUSTOM_ROLES.shared)}(${quoteLiteral(scope)}, ${role}) then c.tenant_id is null else c.tenant_id::text = ${tenant} end`;
1281
861
  }
1282
862
  /**
1283
- * The scope of a row of the application's roles table: its scope column (or
1284
- * the first scope) with a tenant; with no tenant, the same scope when the
1285
- * `shared` column is true, else `'global'`.
863
+ * The scope of a row of the application's roles table: its scope column (or the
864
+ * first scope) with a tenant; with no tenant, the same scope when the `shared`
865
+ * column is true, else `'global'`.
1286
866
  */
1287
867
  function rolesTableScope(table, tenant, root, column) {
1288
868
  const own = table.scope === void 0 ? quoteLiteral(root) : `${column(table.scope)}::text`;
1289
869
  return `case when ${tenant} is null then ${table.shared === void 0 ? "'global'" : `case when coalesce(${column(table.shared)}, false) then ${own} else 'global' end`} else ${own} end`;
1290
870
  }
1291
- /** `expr` read as its current key: a former key maps to the key it was renamed to. */
871
+ /**
872
+ * `expr` read as its current key: a former key maps to the key it was renamed
873
+ * to.
874
+ */
1292
875
  function currentKeyExpr(renames, expr) {
1293
876
  if (renames.length === 0) return expr;
1294
877
  return `(case ${expr} ${renames.map(([former, current]) => `when ${quoteLiteral(former)} then ${quoteLiteral(current)}`).join(" ")} else ${expr} end)`;
1295
878
  }
1296
- /** `case p_scope when '<scope>' then <sql for scope> ... else false end` over the declared scopes. */
879
+ /**
880
+ * `case p_scope when '<scope>' then <sql for scope> ... else false end` over
881
+ * the declared scopes.
882
+ */
1297
883
  function perScope(ctx, sql) {
1298
884
  const arms = ctx.scopes.map((scope) => `when ${quoteLiteral(scope.name)} then ${sql(scope.name)}`).join(" ");
1299
885
  return arms === "" ? "false" : `(case p_scope ${arms} else false end)`;
@@ -1309,8 +895,8 @@ function hasCustomRoleChecksFor(ctx) {
1309
895
  }
1310
896
  /**
1311
897
  * The grants a `rls.trustedReaders` role needs for one reader: usage on the
1312
- * helper schema, which no client role reaches through these lines, and
1313
- * execute on the function.
898
+ * helper schema, which no client role reaches through these lines, and execute
899
+ * on the function.
1314
900
  */
1315
901
  function trustedReaderGrants(ctx, fn, readers) {
1316
902
  const to = readers.map(quoteIdent).join(", ");
@@ -1326,7 +912,10 @@ function throughTenant(through, roles) {
1326
912
  if (through === void 0 || roles?.tenant === void 0) return;
1327
913
  return qualify(roles.table, "public") === through.table ? roles.tenant : void 0;
1328
914
  }
1329
- /** Resolves `source` against `schema`, aliasing the table `alias` and the roles table `<alias>k`. */
915
+ /**
916
+ * Resolves `source` against `schema`, aliasing the table `alias` and the roles
917
+ * table `<alias>k`.
918
+ */
1330
919
  function globalRoleSource(source, schema, alias, roles) {
1331
920
  const table = qualify(source.table, schema);
1332
921
  const user = source.user ?? "user_id";
@@ -1360,11 +949,17 @@ function keyTenantLine(ctx, scope, tenant) {
1360
949
  const keyed = keyTenantSql(ctx, underRoot(ctx, scope) ? tenant : void 0);
1361
950
  return keyed === void 0 ? "" : `\n and ${keyed}`;
1362
951
  }
1363
- /** Whether the helper for `scope` narrows to the tenant claim: unless `rls.tenants` is `'all'`, when the first scope is on its chain. */
952
+ /**
953
+ * Whether the helper for `scope` narrows to the tenant claim: unless
954
+ * `rls.tenants` is `'all'`, when the first scope is on its chain.
955
+ */
1364
956
  function narrowsTo(ctx, scope) {
1365
957
  return ctx.tenants !== "all" && underRoot(ctx, scope);
1366
958
  }
1367
- /** Whether the active tenant narrows memberships of `scope`: the first scope is on its chain. */
959
+ /**
960
+ * Whether the active tenant narrows memberships of `scope`: the first scope is
961
+ * on its chain.
962
+ */
1368
963
  function underRoot(ctx, scope) {
1369
964
  const root = ctx.scopes[0]?.name;
1370
965
  let current = ctx.scopes.find((item) => item.name === scope);
@@ -1377,11 +972,17 @@ function underRoot(ctx, scope) {
1377
972
  }
1378
973
  return false;
1379
974
  }
1380
- /** `and exists (...)` lines for an active user; empty without `rls.suspension.users`. */
975
+ /**
976
+ * `and exists (...)` lines for an active user; empty without
977
+ * `rls.suspension.users`.
978
+ */
1381
979
  function userActive(ctx, indent, user) {
1382
980
  return activeUserSql(ctx, user).map((part) => `${indent}and ${part}`);
1383
981
  }
1384
- /** `and exists (...)` lines for every suspendable instance on the chain of `scope`'s membership. */
982
+ /**
983
+ * `and exists (...)` lines for every suspendable instance on the chain of
984
+ * `scope`'s membership.
985
+ */
1385
986
  function instancesActive(ctx, scope, idOf, indent, permission) {
1386
987
  return activeInstancesSql(ctx, scope, idOf, permission).map((part) => `${indent}and ${part}`);
1387
988
  }
@@ -1520,7 +1121,10 @@ function hasBody(ctx) {
1520
1121
  ${customKeysSql(ctx, "global", claimEntries("left(e, 1) not in ('-', '@')", "e"), claimEntries("left(e, 1) = '-'", "substr(e, 2)"), claimEntries("left(e, 1) = '@'", "substr(e, 2)")).trimStart()}
1521
1122
  ))`;
1522
1123
  }
1523
- /** The claim `jwt` mode reads platform custom roles from: `customRoleClaim(globalRoles)`. */
1124
+ /**
1125
+ * The claim `jwt` mode reads platform custom roles from:
1126
+ * `customRoleClaim(globalRoles)`.
1127
+ */
1524
1128
  const GLOBAL_GRANTS_CLAIM = "role_grants";
1525
1129
  function memberColumn(name) {
1526
1130
  return `m.${quoteIdent(name)}`;
@@ -1663,8 +1267,8 @@ ${customKeysSql(ctx, scope, claimEntries("left(e, 1) not in ('-', '@')", "e"), c
1663
1267
  }
1664
1268
  /**
1665
1269
  * The rows the membership sources select for the subject, one per instance
1666
- * (`scope`, `id`, `within`, `roles`, `via`), with each source's own expiry
1667
- * and suspension filters: the statements the token hook runs.
1270
+ * (`scope`, `id`, `within`, `roles`, `via`), with each source's own expiry and
1271
+ * suspension filters: the statements the token hook runs.
1668
1272
  */
1669
1273
  function sourcesKeep(sources) {
1670
1274
  return sources.some((source) => source.sql.keeps);
@@ -1688,7 +1292,10 @@ function sourcesBodyOf(sql, sources, user) {
1688
1292
  vars: sources.map((source, index) => `${sourceUser(index)} ${source.sql.userType} := ${user};`)
1689
1293
  };
1690
1294
  }
1691
- /** `language sql`, or `plpgsql` returning the query when the body declares variables. */
1295
+ /**
1296
+ * `language sql`, or `plpgsql` returning the query when the body declares
1297
+ * variables.
1298
+ */
1692
1299
  function functionBody(body) {
1693
1300
  if (body.vars.length === 0) return `language sql
1694
1301
  stable
@@ -1774,7 +1381,10 @@ function scopedBody(ctx, scope, type) {
1774
1381
  }
1775
1382
  //#endregion
1776
1383
  //#region src/cli/rls/capabilities.ts
1777
- /** The helper `--capabilities` adds: ids of one resource a link capability claim reaches. Part of the SQL contract. */
1384
+ /**
1385
+ * The helper `--capabilities` adds: ids of one resource a link capability claim
1386
+ * reaches. Part of the SQL contract.
1387
+ */
1778
1388
  const CAPABILITIES = { ids: "permdock_capability_ids" };
1779
1389
  /**
1780
1390
  * The `anon` branch access for a resource-scoped grant reached through a link
@@ -1824,8 +1434,8 @@ const col = (name) => `r.${quoteIdent(name)}`;
1824
1434
  * (`rls.customRoleWrites.roles.primaryKey`): the tenant, scope, instance and
1825
1435
  * name come from the row, never from the caller. The writers delegate to
1826
1436
  * `permdock_replace_custom_role_grants` (or its trusted variant), so every
1827
- * caller and definition check applies, and keep the stored denies and
1828
- * includes. The reader returns declared allow keys only and is executable by
1437
+ * caller and definition check applies, and keep the stored denies and includes.
1438
+ * The reader returns declared allow keys only and is executable by
1829
1439
  * `rls.trustedReaders` alone.
1830
1440
  */
1831
1441
  function customRoleByIdSql(ctx, trustedReaders) {
@@ -1959,12 +1569,11 @@ function cascadeMatch(prefix) {
1959
1569
  /**
1960
1570
  * The trigger on the application's roles table (`rls.customRoleWrites.roles`):
1961
1571
  * a rename, a move to another tenant, scope or instance, or a delete of a
1962
- * custom role's row carries its grants and includes along. A signed-in
1963
- * caller at trigger depth 1 passes the write functions' checks: authority
1964
- * over the stored definition where it was, and, after a move, membership
1965
- * and authority where it lands and entries inside that scope's ceiling.
1966
- * Other paths (a migration, a job, a nested trigger) move the rows as the
1967
- * function owner.
1572
+ * custom role's row carries its grants and includes along. A signed-in caller
1573
+ * at trigger depth 1 passes the write functions' checks: authority over the
1574
+ * stored definition where it was, and, after a move, membership and authority
1575
+ * where it lands and entries inside that scope's ceiling. Other paths (a
1576
+ * migration, a job, a nested trigger) move the rows as the function owner.
1968
1577
  */
1969
1578
  function customRoleCascadeSql(ctx) {
1970
1579
  const custom = ctx.customRoles;
@@ -2069,9 +1678,9 @@ function column(name) {
2069
1678
  /**
2070
1679
  * The trigger on the application's roles table (`rls.customRoleWrites.roles`)
2071
1680
  * that refuses a tenant row named like a declared role (`declared-role`) or
2072
- * like a shared custom role (`name-taken`). A row the `skip` column marks is
2073
- * a system role of the tenant and passes. Empty without a tenant column: then
2074
- * no row is a tenant row.
1681
+ * like a shared custom role (`name-taken`). A row the `skip` column marks is a
1682
+ * system role of the tenant and passes. Empty without a tenant column: then no
1683
+ * row is a tenant row.
2075
1684
  */
2076
1685
  function customRoleNameGuardSql(ctx) {
2077
1686
  const custom = ctx.customRoles;
@@ -2238,8 +1847,8 @@ revoke execute on function ${guard}(${userArg}${tenantType}, text, text, text) f
2238
1847
  };
2239
1848
  }
2240
1849
  /**
2241
- * The functions an application calls to save, rename and delete a custom
2242
- * role in \`database\` mode, with the rules of \`validateCustomRole\` and
1850
+ * The functions an application calls to save, rename and delete a custom role
1851
+ * in \`database\` mode, with the rules of \`validateCustomRole\` and
2243
1852
  * \`assignablePermissions\`: entries inside the ceiling of the role's scope,
2244
1853
  * includes naming declared roles, and only permissions and levels the caller
2245
1854
  * may hand out, for the new definition and the stored one alike. A platform
@@ -2477,9 +2086,9 @@ end;`, false)}`;
2477
2086
  //#region src/cli/rls/global-roles.ts
2478
2087
  /**
2479
2088
  * `permdock_replace_global_roles(p_user, p_roles)`: sets the global roles of
2480
- * one user in `rls.roles` in one statement pair. It runs as the invoker, so
2481
- * the table's policies and the `rls.assignments` trigger judge every row it
2482
- * deletes or inserts.
2089
+ * one user in `rls.roles` in one statement pair. It runs as the invoker, so the
2090
+ * table's policies and the `rls.assignments` trigger judge every row it deletes
2091
+ * or inserts.
2483
2092
  */
2484
2093
  function globalRolesSql(ctx) {
2485
2094
  if (ctx.roles === void 0) return "";
@@ -2552,9 +2161,9 @@ grant execute on function ${fn}(${args}) to authenticated;`;
2552
2161
  //#endregion
2553
2162
  //#region src/cli/rls/helper-members.ts
2554
2163
  /**
2555
- * The body of `member_<scope>_ids()`: instances of `scope` the subject holds
2556
- * a live membership of, with any role. Not narrowed to the active tenant, and
2557
- * no cascade: a membership of a child scope is not one of its parent.
2164
+ * The body of `member_<scope>_ids()`: instances of `scope` the subject holds a
2165
+ * live membership of, with any role. Not narrowed to the active tenant, and no
2166
+ * cascade: a membership of a child scope is not one of its parent.
2558
2167
  */
2559
2168
  function memberBody(ctx, scope, type, roles) {
2560
2169
  if (ctx.authorize !== "database") return sqlBody(` select distinct (m ->> 'id')::${type}
@@ -2654,8 +2263,8 @@ ${grants}`;
2654
2263
  /**
2655
2264
  * `member_<scope>_ids_for(p_user uuid)`: `member_<scope>_ids()` for a user the
2656
2265
  * caller names, read from the membership sources (or the mapped table) in
2657
- * either mode, so a token hook can call it before any claim exists. Naming
2658
- * the user is why no client role may execute it; the hook's grants give
2266
+ * either mode, so a token hook can call it before any claim exists. Naming the
2267
+ * user is why no client role may execute it; the hook's grants give
2659
2268
  * `supabase_auth_admin` execute.
2660
2269
  */
2661
2270
  function memberForFunction(ctx, scope, type) {
@@ -2665,10 +2274,28 @@ returns setof ${type}
2665
2274
  ${functionBody(memberRowsBody(ctx, scope, type, "p_user", memberForSources(ctx, scope)))}
2666
2275
  revoke execute on function ${fn}(uuid) from public, anon, authenticated;`;
2667
2276
  }
2277
+ /**
2278
+ * `member_<scope>_ids_of(p_user uuid)`: `member_<scope>_ids_for(p_user)` for
2279
+ * the caller only. Any other `p_user` returns no rows, so a client role may
2280
+ * execute it without learning another user's memberships.
2281
+ */
2282
+ function memberOfFunction(ctx, scope, type) {
2283
+ const fn = qualified(ctx, memberOfHelper(scope));
2284
+ return `create or replace function ${fn}(p_user uuid)
2285
+ returns setof ${type}
2286
+ ${functionBody(sqlBody(` select m.id
2287
+ from ${qualified(ctx, memberForHelper(scope))}(p_user) as m(id)
2288
+ where p_user = ${subjectIdSql(ctx)}`))}
2289
+ revoke execute on function ${fn}(uuid) from public, anon;
2290
+ grant execute on function ${fn}(uuid) to authenticated;`;
2291
+ }
2668
2292
  //#endregion
2669
2293
  //#region src/cli/rls/token-scopes.ts
2670
2294
  const TOKEN_SCOPE_ALLOWS = "permdock_token_scope_allows";
2671
- /** `rls.tokenScopes` as the SQL generator needs it, or `undefined` when it is off. */
2295
+ /**
2296
+ * `rls.tokenScopes` as the SQL generator needs it, or `undefined` when it is
2297
+ * off.
2298
+ */
2672
2299
  function tokenScopesPlan(setting, policy) {
2673
2300
  if (setting !== true) return;
2674
2301
  const permissions = listPermissions(policy.permissions).map((leaf) => ({
@@ -2681,7 +2308,10 @@ function tokenScopesPlan(setting, policy) {
2681
2308
  function tokenScopeAllowsCall(ctx, grant) {
2682
2309
  return `${qualified(ctx, TOKEN_SCOPE_ALLOWS)}(${grant})`;
2683
2310
  }
2684
- /** The token's scopes as rows of `s(scope)`: a space-separated string or, outside `guc`, a JSON array. */
2311
+ /**
2312
+ * The token's scopes as rows of `s(scope)`: a space-separated string or,
2313
+ * outside `guc`, a JSON array.
2314
+ */
2685
2315
  function tokenScopeRows(ctx) {
2686
2316
  if (ctx.dialect === "guc") return `regexp_split_to_table(btrim(coalesce(${subjectClaimSql(ctx, "scope")}, '')), '\\s+') s(scope)`;
2687
2317
  const scope = subjectClaimJsonSql(ctx, "scope");
@@ -2735,9 +2365,9 @@ ${grants}`;
2735
2365
  //#endregion
2736
2366
  //#region src/cli/rls/helpers.ts
2737
2367
  /**
2738
- * The policy-side call for a grant key. Every form is uncorrelated, so
2739
- * Postgres runs the helper once per statement (an InitPlan, or a hashed
2740
- * SubPlan for `in`), never once per row.
2368
+ * The policy-side call for a grant key. Every form is uncorrelated, so Postgres
2369
+ * runs the helper once per statement (an InitPlan, or a hashed SubPlan for
2370
+ * `in`), never once per row.
2741
2371
  */
2742
2372
  function accessSql(ctx, scope, grantKey, column, helper) {
2743
2373
  const key = quoteLiteral(grantKey);
@@ -2796,11 +2426,11 @@ ${grants}`;
2796
2426
  * `permdock_has_for(p_user, p_grant)` and one
2797
2427
  * `permitted_<scope>_ids_for(p_user, p_grant)` per scope in `database` mode:
2798
2428
  * the bodies of `permdock_has` and `permitted_<scope>_ids` with `p_user` for
2799
- * the signed-in user and no tenant-claim narrowing, since a named user has
2800
- * no token. For trusted SQL that acts for a stored user (jobs, triggers,
2801
- * approvals resolved later). Naming the user is why no client role may
2802
- * execute them: `security definer` callers run them as their owner, and a
2803
- * backend role needs its own grant.
2429
+ * the signed-in user and no tenant-claim narrowing, since a named user has no
2430
+ * token. For trusted SQL that acts for a stored user (jobs, triggers, approvals
2431
+ * resolved later). Naming the user is why no client role may execute them:
2432
+ * `security definer` callers run them as their owner, and a backend role needs
2433
+ * its own grant.
2804
2434
  */
2805
2435
  function forUserSql(ctx) {
2806
2436
  if (ctx.authorize !== "database") return "";
@@ -2823,8 +2453,8 @@ function userIdType(ctx) {
2823
2453
  return ctx.dialect === "supabase" ? "uuid not null references auth.users on delete cascade" : "text not null";
2824
2454
  }
2825
2455
  /**
2826
- * The `role_permissions` rows the policy compiles to: an upsert and a delete
2827
- * of every other row. Data, not schema, so `supabase db diff` drops it from a
2456
+ * The `role_permissions` rows the policy compiles to: an upsert and a delete of
2457
+ * every other row. Data, not schema, so `supabase db diff` drops it from a
2828
2458
  * declarative schema; `--split ...,seeds` writes it as its own migration.
2829
2459
  */
2830
2460
  function seedSql(ctx, rows) {
@@ -2868,7 +2498,10 @@ ${rows.length === 0 ? `select null::text, null::text, null::text, null::text, nu
2868
2498
  ].map(quoteLiteral).join(", ")})`).join(",\n")}\n) as v(${columns})`};
2869
2499
  revoke all on table ${view} from anon, authenticated, public;`;
2870
2500
  }
2871
- /** `<schema>.seed()`: the same upsert and delete as `seedSql`, for a declarative schema that carries no rows. */
2501
+ /**
2502
+ * `<schema>.seed()`: the same upsert and delete as `seedSql`, for a declarative
2503
+ * schema that carries no rows.
2504
+ */
2872
2505
  function seedFunctionSql(ctx, rows) {
2873
2506
  const fn = qualified(ctx, "seed");
2874
2507
  return `-- rls.seeds: 'function': the role_permissions rows are data, so a declarative diff leaves them out; run select ${fn}(); after each deploy
@@ -2884,8 +2517,8 @@ $permdock$;
2884
2517
  revoke execute on function ${fn}() from public, anon, authenticated;`;
2885
2518
  }
2886
2519
  /**
2887
- * `role_permissions`, its seeds, and the three `security definer` helpers
2888
- * with `search_path = ''`. `execute` goes to `authenticated` only.
2520
+ * `role_permissions`, its seeds, and the three `security definer` helpers with
2521
+ * `search_path = ''`. `execute` goes to `authenticated` only.
2889
2522
  */
2890
2523
  function helpersSql(ctx, rows, options) {
2891
2524
  const schema = helperSchema(ctx);
@@ -2937,6 +2570,7 @@ revoke all on table ${ur} from anon, authenticated, public;`);
2937
2570
  chunks.push(memberFunction(ctx, scope.name, type, anon));
2938
2571
  if (ctx.roleMemberScopes?.includes(scope.name) === true) chunks.push(memberRolesFunction(ctx, scope.name, type, anon));
2939
2572
  if (hasMemberFor(ctx, scope.name)) chunks.push(memberForFunction(ctx, scope.name, type));
2573
+ if (hasMemberOf(ctx, scope.name)) chunks.push(memberOfFunction(ctx, scope.name, type));
2940
2574
  }
2941
2575
  chunks.push(...viaHelpersSql(ctx, anon));
2942
2576
  const forUser = forUserSql(ctx);
@@ -3002,18 +2636,27 @@ function forUserContext(ctx) {
3002
2636
  }
3003
2637
  //#endregion
3004
2638
  //#region src/cli/rls/permission-keys.ts
3005
- /** The helpers that take a permission key instead of a grant key. Names are part of the SQL contract. */
2639
+ /**
2640
+ * The helpers that take a permission key instead of a grant key. Names are part
2641
+ * of the SQL contract.
2642
+ */
3006
2643
  const PERMISSION_HELPERS = {
3007
2644
  grantKeys: "grant_keys",
3008
2645
  has: "permdock_has_permission",
3009
2646
  hasFor: "permdock_has_permission_for"
3010
2647
  };
3011
- /** `permitted_<scope>_permission_keys(p_id)`: every permission key the subject holds on one instance. */
2648
+ /**
2649
+ * `permitted_<scope>_permission_keys(p_id)`: every permission key the subject
2650
+ * holds on one instance.
2651
+ */
3012
2652
  function permissionKeysHelper(scope) {
3013
2653
  permittedIdsHelper(scope);
3014
2654
  return `permitted_${scope}_permission_keys`;
3015
2655
  }
3016
- /** `permitted_<scope>_permission_keys_for(p_user, p_id)`, written in `database` mode. */
2656
+ /**
2657
+ * `permitted_<scope>_permission_keys_for(p_user, p_id)`, written in `database`
2658
+ * mode.
2659
+ */
3017
2660
  function permissionKeysForHelper(scope) {
3018
2661
  return `${permissionKeysHelper(scope)}_for`;
3019
2662
  }
@@ -3033,7 +2676,10 @@ function requiresSql(ctx, key, resource) {
3033
2676
  return `${quoteIdent(field)} in (select ${qualified(ctx, checkName(permittedByPermissionHelper(scope)))}(${literal}))`;
3034
2677
  })].join(" or ")})`;
3035
2678
  }
3036
- /** Postgres truncates an identifier past 63 bytes, which would merge two helpers. */
2679
+ /**
2680
+ * Postgres truncates an identifier past 63 bytes, which would merge two
2681
+ * helpers.
2682
+ */
3037
2683
  function checkName(name) {
3038
2684
  if (new TextEncoder().encode(name).length > 63) throw configError(`PermDock CLI: the helper name ${name} is longer than 63 bytes; use a shorter scope name`);
3039
2685
  return name;
@@ -3084,17 +2730,17 @@ ${access}`;
3084
2730
  }
3085
2731
  /**
3086
2732
  * The permission-key forms of the helpers: `grant_keys`, which maps a
3087
- * permission key to the grant keys of its unconditional allows on a scope
3088
- * (or every deny key, or the conditioned allow or deny keys),
2733
+ * permission key to the grant keys of its unconditional allows on a scope (or
2734
+ * every deny key, or the conditioned allow or deny keys),
3089
2735
  * `permdock_has_permission(p_permission)` and one
3090
2736
  * `permitted_<scope>_ids_by_permission(p_permission)` per scope, plus their
3091
2737
  * `_for(p_user, ...)` forms in `database` mode. They answer with the
3092
- * unconditional allows minus any deny, so a permission whose allows all carry
3093
- * a row condition answers nothing. The
2738
+ * unconditional allows minus any deny, so a permission whose allows all carry a
2739
+ * row condition answers nothing. The
3094
2740
  * `permitted_<scope>_ids_by_permission(p_permission, p_conditioned)` overload
3095
2741
  * with `true` also lists the instances a conditioned allow reaches and
3096
- * subtracts only unconditional denies, leaving the row condition to the
3097
- * caller, as `permittedIds(..., { conditioned: true })` does in process.
2742
+ * subtracts only unconditional denies, leaving the row condition to the caller,
2743
+ * as `permittedIds(..., { conditioned: true })` does in process.
3098
2744
  */
3099
2745
  function permissionHelpersSql(ctx, grants, renamed, anonExecute) {
3100
2746
  const keys = qualified(ctx, PERMISSION_HELPERS.grantKeys);
@@ -3218,7 +2864,10 @@ ${keysFunction(keysFor, `p_user ${user}, p_id ${type}, p_keys text[]`, keysBody(
3218
2864
  }
3219
2865
  //#endregion
3220
2866
  //#region src/cli/rls/ownership-names.ts
3221
- /** Objects the ownership rules add next to the helpers. Names are part of the SQL contract. */
2867
+ /**
2868
+ * Objects the ownership rules add next to the helpers. Names are part of the
2869
+ * SQL contract.
2870
+ */
3222
2871
  const OWNERSHIP = {
3223
2872
  canAssign: "permdock_can_assign",
3224
2873
  canAssignFor: "permdock_can_assign_for",
@@ -3234,231 +2883,14 @@ const ASSIGNMENTS = {
3234
2883
  trigger: "permdock_assignment"
3235
2884
  };
3236
2885
  //#endregion
3237
- //#region src/cli/rls/assignments.ts
3238
- /** The client roles whose writes the assignment triggers check; any other role is a trusted path. */
3239
- const CLIENT_ROLES = [
3240
- "anon",
3241
- "anonymous",
3242
- "authenticated"
3243
- ];
3244
- /** The mapped membership tables, `rls.assignments.tables` and the global-roles table `rls.roles`. */
3245
- function assignedTables(ctx) {
3246
- const out = [];
3247
- for (const { name } of ctx.scopes) {
3248
- const mapped = scopeTable(ctx, name);
3249
- if (mapped === void 0) continue;
3250
- out.push({
3251
- table: mapped.table.table,
3252
- scope: name,
3253
- id: mapped.column,
3254
- tenant: mapped.tenantColumn ?? mapped.column,
3255
- role: mapped.table.role,
3256
- user: mapped.table.user
3257
- });
3258
- }
3259
- if (ctx.roles !== void 0) out.push({
3260
- table: ctx.roles.table,
3261
- scope: "global",
3262
- role: ctx.roles.role ?? "role",
3263
- user: ctx.roles.user ?? "user_id"
3264
- });
3265
- for (const extra of ctx.assignments?.tables ?? []) {
3266
- const scope = resolveScope(ctx.scopes, extra.scope);
3267
- if (scope === void 0) throw configError(`PermDock CLI: rls.assignments.tables names scope '${extra.scope}', which the policy does not declare`);
3268
- if (scope !== ctx.scopes[0]?.name && extra.tenant === void 0) throw configError(`PermDock CLI: rls.assignments.tables entry ${extra.table} assigns at ${scope}, below the first scope: name its tenant column`);
3269
- out.push({
3270
- table: extra.table,
3271
- scope,
3272
- id: extra.id,
3273
- tenant: extra.tenant ?? extra.id,
3274
- role: extra.role,
3275
- ...extra.user === void 0 ? {} : { user: extra.user }
3276
- });
3277
- }
3278
- return out;
3279
- }
3280
- /** The tables `rls.assignments.ownRole` refuses a caller's own rows on, by table key. */
3281
- function ownRoleTables(ctx, tables) {
3282
- const own = ctx.assignments?.ownRole;
3283
- if (own === void 0) return /* @__PURE__ */ new Set();
3284
- if (own === "refuse") return new Set(tables.filter((entry) => entry.user !== void 0).map((entry) => qualifySqlTable(entry.table)));
3285
- const out = /* @__PURE__ */ new Set();
3286
- for (const [name, value] of Object.entries(own)) {
3287
- const entry = tables.find((table) => qualifySqlTable(table.table) === qualifySqlTable(name));
3288
- if (entry === void 0) throw configError(`PermDock CLI: rls.assignments.ownRole names ${name}, which no assignment trigger guards`);
3289
- if (value !== "refuse") throw configError(`PermDock CLI: rls.assignments.ownRole.${name} must be 'refuse'`);
3290
- if (entry.user === void 0) throw configError(`PermDock CLI: rls.assignments.ownRole names ${name}, which has no user column: set its user in rls.assignments.tables`);
3291
- out.add(qualifySqlTable(entry.table));
3292
- }
3293
- return out;
3294
- }
3295
- /**
3296
- * The tables `rls.assignments` guards, schema-qualified: each scope's mapped
3297
- * membership table, the global-roles table (`rls.roles`, else
3298
- * `supabase.hook.roles`) and `rls.assignments.tables`. Empty without
3299
- * `rls.assignments`.
3300
- */
3301
- function guardedTables(config, scopes) {
3302
- const rls = config.rls;
3303
- if (rls?.assignments === void 0) return [];
3304
- const hook = config.supabase?.hook?.roles;
3305
- const roles = rls.roles ?? (hook === false ? void 0 : hook);
3306
- const tables = [
3307
- ...scopes.flatMap((scope) => {
3308
- const mapped = memberForTable({
3309
- scopes,
3310
- ...rls.memberships === void 0 ? {} : { memberships: rls.memberships }
3311
- }, scope.name);
3312
- return mapped === void 0 ? [] : [mapped.table];
3313
- }),
3314
- ...roles === void 0 ? [] : [roles.table],
3315
- ...(rls.assignments === true ? [] : rls.assignments.tables ?? []).map((entry) => entry.table)
3316
- ];
3317
- return [...new Set(tables.map(qualifySqlTable))];
3318
- }
3319
- function isRoleList(role) {
3320
- return Array.isArray(role);
3321
- }
3322
- /** The role keys a row variable holds, one scalar per role source. */
3323
- function rowRoles(entry, row) {
3324
- return (isRoleList(entry.role) ? entry.role : [entry.role]).map((part) => roleColumn(part, qualifySqlTable(entry.table), row, {
3325
- label: `rls.assignments ${entry.table} role`,
3326
- prefix: "PermDock CLI"
3327
- }).lookup);
3328
- }
3329
- /**
3330
- * `permdock_can_assign_custom_role`: whether the signed-in caller may assign a
3331
- * custom role of the tenant at the instance, by the checks the custom-role
3332
- * write functions run on its stored definition.
3333
- */
3334
- function canAssignCustomSql(ctx, forUser = false) {
3335
- const tenantType = tenantTypeOf(ctx);
3336
- const fn = qualified(ctx, forUser ? ASSIGNMENTS.customFor : ASSIGNMENTS.custom);
3337
- const perms = qualified(ctx, CUSTOM_ROLES.permissions);
3338
- const includes = qualified(ctx, CUSTOM_ROLES.includes);
3339
- const guard = qualified(ctx, forUser ? CUSTOM_ROLES.guardFor : CUSTOM_ROLES.guard);
3340
- const userParam = forUser ? "p_user uuid, " : "";
3341
- const userArg = forUser ? "uuid, " : "";
3342
- const stored = `${storedTenant(ctx)} and c.scope = p_scope and c.role = p_role and (c.scope_id is null or c.scope_id = p_scope_id)`;
3343
- const grants = forUser ? `revoke execute on function ${fn}(${userArg}${tenantType}, text, text, text) from public, anon, authenticated;` : `revoke execute on function ${fn}(${tenantType}, text, text, text) from public, anon;
3344
- grant execute on function ${fn}(${tenantType}, text, text, text) to authenticated;`;
3345
- return `-- whether ${forUser ? "user p_user" : "the caller"} may assign custom role p_role at instance p_scope_id (a platform role: null tenant, scope global): the custom-role write checks on its stored definition${forUser ? "; no client role may execute it" : ""}
3346
- create or replace function ${fn}(${userParam}p_tenant ${tenantType}, p_scope text, p_scope_id text, p_role text)
3347
- returns boolean
3348
- language plpgsql
3349
- stable
3350
- security definer
3351
- set search_path = ''
3352
- as $$
3353
- declare
3354
- v_scope_id text;
3355
- v_found boolean := false;
3356
- begin
3357
- select true, c.scope_id into v_found, v_scope_id
3358
- from (
3359
- select c.scope_id from ${perms} c where ${stored}
3360
- union all
3361
- select c.scope_id from ${includes} c where ${stored}
3362
- ) c
3363
- order by c.scope_id nulls first
3364
- limit 1;
3365
- if not coalesce(v_found, false) then
3366
- return false;
3367
- end if;
3368
- perform ${guard}(${forUser ? "p_user, " : ""}p_tenant, p_scope, v_scope_id, p_role);
3369
- return true;
3370
- exception
3371
- when insufficient_privilege or invalid_parameter_value then
3372
- return false;
3373
- end;
3374
- $$;
3375
- ${grants}`;
3376
- }
3377
- /**
3378
- * The assignment triggers (\`rls.assignments\`): before a client role inserts,
3379
- * updates or deletes a row that assigns a role, every role the old and new
3380
- * row hold must be one the caller may assign at that instance, by
3381
- * \`permdock_can_assign\` for a declared role and
3382
- * \`permdock_can_assign_custom_role\` for a custom one. The functions run as
3383
- * the invoker, so \`current_user\` tells a client write from a trusted one.
3384
- */
3385
- function assignmentSql(ctx) {
3386
- const own = ctx.ownership;
3387
- if (ctx.assignments === void 0 || own === void 0 || own.assigns.length === 0) return "";
3388
- const canAssign = qualified(ctx, OWNERSHIP.canAssign);
3389
- const custom = ctx.customRoles !== void 0 && ctx.authorize === "database";
3390
- const declared = ctx.customRoles?.declared;
3391
- const tenantType = tenantTypeOf(ctx);
3392
- const chunks = [];
3393
- const clients = `current_user::text = any(array[${CLIENT_ROLES.map(quoteLiteral).join(", ")}])`;
3394
- const tables = assignedTables(ctx);
3395
- const ownRole = ownRoleTables(ctx, tables);
3396
- for (const entry of tables) {
3397
- const table = quoteQualified(entry.table);
3398
- const suffix = entry.table.replaceAll(/[^a-z0-9_]/gu, "_");
3399
- const fn = qualified(ctx, quoteIdent(`${ASSIGNMENTS.trigger}_${suffix}`));
3400
- const ownUser = entry.user !== void 0 && ownRole.has(qualifySqlTable(entry.table)) ? entry.user : void 0;
3401
- const ownCheck = (row, reason) => ownUser === void 0 ? "" : ` if ${row}.${quoteIdent(ownUser)}::text = (${subjectIdSql(ctx)})::text then
3402
- raise exception using
3403
- errcode = '42501',
3404
- message = 'permdock: the caller may not change their own roles',
3405
- hint = '${reason}';
3406
- end if;
3407
- `;
3408
- const check = (row) => {
3409
- const id = entry.id === void 0 ? "null::text" : `${row}.${quoteIdent(entry.id)}::text`;
3410
- const customFn = qualified(ctx, ASSIGNMENTS.custom);
3411
- let allowed = `${canAssign}(v_role, ${id})`;
3412
- if (custom && declared !== void 0) {
3413
- const platform = `${customFn}(null, 'global', null, v_role)`;
3414
- allowed = entry.id === void 0 || entry.tenant === void 0 ? `case when v_role = any(${declaredArray(declared)}) then ${canAssign}(v_role, ${id}) else ${platform} end` : `case when v_role = any(${declaredArray(declared)}) then ${canAssign}(v_role, ${id}) when ${id} is null then ${platform} else ${customFn}(${row}.${quoteIdent(entry.tenant)}::${tenantType}, ${quoteLiteral(entry.scope)}, ${id}, v_role) end`;
3415
- }
3416
- return ` foreach v_role in array array[${rowRoles(entry, row).join(", ")}]::text[] loop
3417
- if v_role is not null and not coalesce(${allowed}, false) then
3418
- raise exception using
3419
- errcode = '42501',
3420
- message = 'permdock: the caller may not assign ' || v_role || ' in ' || coalesce(${id}, 'null'),
3421
- hint = 'not-assignable-by';
3422
- end if;
3423
- end loop;`;
3424
- };
3425
- const what = entry.id === void 0 ? "global roles" : `${entry.scope} roles`;
3426
- chunks.push(`-- ${entry.table}: a client role may write only the ${what} it may assign${ownUser === void 0 ? "" : ", and none on its own rows"}
3427
- create or replace function ${fn}()
3428
- returns trigger
3429
- language plpgsql
3430
- volatile
3431
- set search_path = ''
3432
- as $$
3433
- declare
3434
- v_role text;
3435
- begin
3436
- if not (${clients}) then
3437
- return coalesce(new, old);
3438
- end if;
3439
- if tg_op in ('UPDATE', 'DELETE') then
3440
- ${ownCheck("old", "self-demotion")}${check("old")}
3441
- end if;
3442
- if tg_op in ('INSERT', 'UPDATE') then
3443
- ${ownCheck("new", "not-assignable-by")}${check("new")}
3444
- end if;
3445
- return coalesce(new, old);
3446
- end;
3447
- $$;
3448
- revoke execute on function ${fn}() from public, anon, authenticated;
3449
- drop trigger if exists "permdock_assignment" on ${table};
3450
- create trigger "permdock_assignment"
3451
- before insert or update or delete on ${table}
3452
- for each row execute function ${fn}();`);
3453
- }
3454
- return `${chunks.join("\n\n")}\n`;
3455
- }
3456
- //#endregion
3457
2886
  //#region src/cli/rls/ownership-triggers.ts
3458
2887
  function objectName(prefix, scope) {
3459
2888
  return quoteIdent(`${prefix}_${scope}`);
3460
2889
  }
3461
- /** Live holders of `roleExpr` in instance `idExpr`, with the same kind and expiry rules as the helpers. */
2890
+ /**
2891
+ * Live holders of `roleExpr` in instance `idExpr`, with the same kind and
2892
+ * expiry rules as the helpers.
2893
+ */
3462
2894
  function holdersSql(ctx, table, column, idExpr, role, into) {
3463
2895
  const roleExpr = typeof role === "string" ? quoteLiteral(role) : role.expr;
3464
2896
  const held = memberRoleOf(table, "m", " ");
@@ -3535,10 +2967,10 @@ create constraint trigger ${objectName(OWNERSHIP.holders, scope)}
3535
2967
  for each row execute function ${fn}();`;
3536
2968
  }
3537
2969
  /**
3538
- * `transferOnly` as statement triggers over transition tables: a statement
3539
- * that changes a role's holder count in an instance that had holders before
3540
- * and still has some after is refused. Creating the first holder and removing
3541
- * the last (which `min` guards) are not transfers.
2970
+ * `transferOnly` as statement triggers over transition tables: a statement that
2971
+ * changes a role's holder count in an instance that had holders before and
2972
+ * still has some after is refused. Creating the first holder and removing the
2973
+ * last (which `min` guards) are not transfers.
3542
2974
  */
3543
2975
  function transferTriggerSql(ctx, scope, table, column, roles) {
3544
2976
  const fn = qualified(ctx, `${OWNERSHIP.transferOnly}_${scope}`);
@@ -3601,8 +3033,8 @@ ${trigger("delete")}`;
3601
3033
  //#endregion
3602
3034
  //#region src/cli/rls/ownership-sources.ts
3603
3035
  /**
3604
- * The membership sources holding scope `name`: `undefined` when there are
3605
- * none, `'not-tables'` when one of them carries no table to put a trigger on.
3036
+ * The membership sources holding scope `name`: `undefined` when there are none,
3037
+ * `'not-tables'` when one of them carries no table to put a trigger on.
3606
3038
  */
3607
3039
  function holderSources(ctx, name) {
3608
3040
  const sources = memberForSources(ctx, name);
@@ -3620,7 +3052,10 @@ function holderSources(ctx, name) {
3620
3052
  function indent(sql, by) {
3621
3053
  return sql.replaceAll(/^/gmu, by);
3622
3054
  }
3623
- /** Every source's rows of one instance, each compared with its own typed `v_key_<n>`. */
3055
+ /**
3056
+ * Every source's rows of one instance, each compared with its own typed
3057
+ * `v_key_<n>`.
3058
+ */
3624
3059
  function sourceRowsOf(scope, sources) {
3625
3060
  return sources.map((source, index) => indent(source.holders.rows(scope, { id: `v_key_${String(index)}` }), " ")).join("\n union all\n");
3626
3061
  }
@@ -3636,7 +3071,10 @@ function regclassOf(table) {
3636
3071
  function sourceTables(sources) {
3637
3072
  return [...new Set(sources.map((source) => quoteQualified(source.table)))];
3638
3073
  }
3639
- /** `min` and `max` over membership sources: the same commit-time check as on a mapped table, counted over every source of the scope. */
3074
+ /**
3075
+ * `min` and `max` over membership sources: the same commit-time check as on a
3076
+ * mapped table, counted over every source of the scope.
3077
+ */
3640
3078
  function sourceHoldersTriggerSql(ctx, scope, sources, counted) {
3641
3079
  const fn = qualified(ctx, `${OWNERSHIP.holders}_${scope}`);
3642
3080
  const keys = sourceKeys(sources);
@@ -3709,7 +3147,10 @@ ${triggers.join("\n")}`;
3709
3147
  function collectChangesSql(changed) {
3710
3148
  return ` v_changes := v_changes || coalesce((select jsonb_agg(to_jsonb(c)) from (${changed}) c), '[]'::jsonb);`;
3711
3149
  }
3712
- /** `transferOnly` over membership sources: each source table's statement triggers diff its transition tables, and the count after reads every source. */
3150
+ /**
3151
+ * `transferOnly` over membership sources: each source table's statement
3152
+ * triggers diff its transition tables, and the count after reads every source.
3153
+ */
3713
3154
  function sourceTransferTriggerSql(ctx, scope, sources, roles) {
3714
3155
  const fn = qualified(ctx, `${OWNERSHIP.transferOnly}_${scope}`);
3715
3156
  const keys = sourceKeys(sources);
@@ -3787,314 +3228,410 @@ function sourceRowsSql(sources, user) {
3787
3228
  return sources.map((source) => source.sql.select(user(source), keep).replaceAll(/^/gmu, " ")).join("\n union all\n");
3788
3229
  }
3789
3230
  //#endregion
3790
- //#region src/cli/rls/can-assign.ts
3791
- /** Whether the signed-in user (or `p_user` in the `_for` form) holds, in instance `p_scope_id`, a role whose `assigns` lists `p_role`. */
3792
- function canAssignSql(base, own, forUser = false) {
3793
- const ctx = forUser ? {
3794
- ...base,
3795
- subjectId: "p_user",
3796
- tenants: "all"
3797
- } : base;
3798
- const fn = qualified(ctx, forUser ? OWNERSHIP.canAssignFor : OWNERSHIP.canAssign);
3799
- const parts = [];
3800
- const used = [];
3801
- const userOf = (source) => {
3802
- const index = used.includes(source) ? used.indexOf(source) : used.push(source) - 1;
3803
- return `v_user_${String(index)}`;
3231
+ //#region src/cli/rls/graph.ts
3232
+ function tableFor(resource, tables) {
3233
+ return tables?.[resource] ?? resource;
3234
+ }
3235
+ /**
3236
+ * Every `related` condition the policy's grants compile to, with the resource
3237
+ * graph they need, groups included.
3238
+ */
3239
+ function graphPlan(policy) {
3240
+ const scopes = scopeList(policy.scopes);
3241
+ const plan = /* @__PURE__ */ new Map();
3242
+ const entryFor = (node) => {
3243
+ const entry = plan.get(node.name) ?? {
3244
+ node,
3245
+ relations: /* @__PURE__ */ new Set(),
3246
+ links: /* @__PURE__ */ new Set()
3247
+ };
3248
+ plan.set(node.name, entry);
3249
+ return entry;
3804
3250
  };
3805
- const global = own.assigns.filter((pair) => pair.scope === "global");
3806
- for (const [instance, pairs] of [["p_scope_id is not null", global.filter((pair) => pair.at !== "global")], ["p_scope_id is null", global.filter((pair) => pair.at === "global")]]) {
3807
- if (pairs.length === 0) continue;
3808
- if (ctx.authorize === "database") {
3809
- const ur = globalRoleRows(ctx);
3810
- const kind = globalKindFilterSql(ctx, ur.roleSql);
3811
- parts.push(`${instance} and exists (
3812
- select 1 from ${ur.from}
3813
- where ${ur.userSql} = ${subjectIdSql(ctx)}
3814
- and (${ur.roleSql}, p_role) in (${pairsSql(pairs)})${kind === void 0 ? "" : `\n and ${kind}`}
3815
- )`);
3816
- } else {
3817
- const kind = globalKindFilterSql(ctx, "r.role");
3818
- parts.push(`${instance} and exists (
3819
- select 1 from ${roleRows(ctx)}
3820
- where (r.role, p_role) in (${pairsSql(pairs)})${kind === void 0 ? "" : `\n and ${kind}`}
3821
- )`);
3251
+ const addRelation = (node, relation) => {
3252
+ const entry = entryFor(node);
3253
+ if (entry.relations.has(relation)) return;
3254
+ entry.relations.add(relation);
3255
+ for (const name of expandRelation(node, relation)) {
3256
+ const spec = node.relations[name];
3257
+ if (!isEdgeRelation(spec) || spec.groups === void 0) continue;
3258
+ for (const { resource, relation: groupRelation } of groupEntries(spec)) {
3259
+ const target = policy.resources.get(resource);
3260
+ if (target !== void 0 && target.name !== node.name) addRelation(target, groupRelation);
3261
+ }
3822
3262
  }
3823
- }
3824
- for (const { name } of ctx.scopes) {
3825
- const pairs = own.assigns.filter((pair) => pair.scope === name);
3826
- if (pairs.length === 0) continue;
3827
- const sources = scopeSources(ctx, name);
3828
- if (sources.length > 0) {
3829
- const kind = kindFilterSql(ctx, "r.role", "ms.via");
3830
- parts.push(`exists (
3831
- select 1
3832
- from (
3833
- ${sourceRowsSql(sources, userOf)}
3834
- ) ms
3835
- cross join lateral jsonb_array_elements_text(
3836
- case jsonb_typeof(ms.roles) when 'array' then ms.roles else '[]'::jsonb end
3837
- ) r(role)
3838
- where ms.scope = ${quoteLiteral(name)}
3839
- and ms.id = p_scope_id
3840
- and (r.role, p_role) in (${pairsSql(pairs)})${kind === void 0 ? "" : `\n and ${kind}`}${sourceFilters(ctx, name, void 0, void 0, sourcesKeep(sources)).replaceAll("\n and ", "\n and ")}
3841
- )`);
3842
- continue;
3263
+ };
3264
+ const addLinks = (hops) => {
3265
+ for (let index = 1; index < hops.length; index += 1) {
3266
+ const from = policy.resources.get(hops[index - 1]?.resource ?? "");
3267
+ const hop = hops[index];
3268
+ if (from !== void 0 && hop !== void 0) entryFor(from).links.add(hop.link);
3843
3269
  }
3844
- if (ctx.authorize === "database") {
3845
- const mapped = scopeTable(ctx, name);
3846
- if (mapped === void 0) continue;
3847
- const { table, column } = mapped;
3848
- const role = memberRoleOf(table, "m", " ");
3849
- const filters = [
3850
- `${memberColumn(table.user)} = ${subjectIdSql(ctx)}`,
3851
- `${memberColumn(column)}::text = p_scope_id`,
3852
- `(${role.sql}, p_role) in (${pairsSql(pairs)})`
3853
- ];
3854
- if (table.expiresAt !== void 0) {
3855
- const expires = memberColumn(table.expiresAt);
3856
- filters.push(`(${expires} is null or ${expires} > now())`);
3270
+ };
3271
+ const addAbove = (above) => {
3272
+ const node = above === void 0 ? void 0 : policy.resources.get(above.resource);
3273
+ if (above === void 0 || node === void 0) return;
3274
+ const entry = entryFor(node);
3275
+ entry.closure = Math.max(entry.closure ?? 0, above.depth);
3276
+ entry.restrictedAncestors = above;
3277
+ };
3278
+ for (const grant of policy.grants) {
3279
+ const row = policy.resources.get(grant.permission.resource);
3280
+ for (const item of flattenGrantee(grant.to)) {
3281
+ if (item.kind === "inherit") {
3282
+ const inherited = inheritCondition(item, row, policy.resources);
3283
+ addLinks(inherited?.hops ?? []);
3284
+ addAbove(inherited?.restrictedAncestors);
3285
+ continue;
3857
3286
  }
3858
- const disabled = disabledColumn(table);
3859
- if (disabled !== void 0) filters.push(`${disabled} is null`);
3860
- const kind = kindFilterSql(ctx, role.sql, memberVia(table));
3861
- if (kind !== void 0) filters.push(kind);
3862
- parts.push(`exists (
3863
- select 1 from ${quoteQualified(table.table)} m${role.join}
3864
- where ${filters.join("\n and ")}
3865
- )`);
3866
- } else {
3867
- const kind = kindFilterSql(ctx, "r.role", "m ->> 'via'");
3868
- parts.push(`exists (
3869
- select 1 from ${membershipRows(ctx)}
3870
- where m ->> 'scope' = ${quoteLiteral(name)}
3871
- and m ->> 'id' = p_scope_id
3872
- and (r.role, p_role) in (${pairsSql(pairs)})
3873
- and case jsonb_typeof(m -> 'expires_at')
3874
- when 'number' then (m ->> 'expires_at')::numeric > extract(epoch from now())
3875
- else true
3876
- end${kind === void 0 ? "" : `\n and ${kind}`}${claimKeptLines(ctx, " ").map((line) => `\n${line}`).join("")}
3877
- )`);
3287
+ if (item.kind !== "relation") continue;
3288
+ const condition = relationCondition(item, row, scopes, { resources: policy.resources });
3289
+ if (condition?.op !== "related") continue;
3290
+ const node = policy.resources.get(condition.resource);
3291
+ if (node === void 0) continue;
3292
+ addRelation(node, condition.relation);
3293
+ const entry = entryFor(node);
3294
+ if (condition.depth > 0 && isSelfParented(node)) entry.closure = Math.max(entry.closure ?? 0, condition.depth);
3295
+ addLinks(condition.hops ?? []);
3296
+ addAbove(condition.restrictedAncestors);
3878
3297
  }
3879
3298
  }
3880
- const body = parts.length === 0 ? "false" : parts.join("\n or ");
3881
- const language = used.length === 0 ? `language sql
3299
+ return plan;
3300
+ }
3301
+ /** The closure depth per walked resource, as `RlsSqlContext.graph` carries it. */
3302
+ function closureDepths(plan) {
3303
+ const out = {};
3304
+ for (const [name, entry] of plan) if (entry.closure !== void 0) out[name] = entry.closure;
3305
+ return out;
3306
+ }
3307
+ /**
3308
+ * The `permitted_<resource>_ids(relation)` arm for one concrete relation: ids
3309
+ * of `resource` the subject holds it on, directly or through a group. `asked`
3310
+ * are the relation names that include it, so a call for any of them (or for
3311
+ * all, with null) reads this arm.
3312
+ */
3313
+ function relationArm(ctx, plan, node, relation, asked, tables) {
3314
+ const spec = node.relations[relation];
3315
+ if (!isEdgeRelation(spec) && !isPrincipalRelation(spec) && (!isFieldRelation(spec) || spec.memberOf !== void 0)) throw configError(`PermDock CLI: relation '${relation}' on ${node.name} is not a graph relation RLS can compile`);
3316
+ const guard = asked.length === 1 ? `(p_relation is null or p_relation = ${quoteLiteral(asked[0] ?? relation)})` : `(p_relation is null or p_relation in (${asked.map(quoteLiteral).join(", ")}))`;
3317
+ return ` select a.id from (${graphSqlText(relationArmSql(node.name, relation, {
3318
+ resources: new Map([...plan].map(([name, item]) => [name, item.node])),
3319
+ ...tables === void 0 ? {} : { tables },
3320
+ qualify: qualifySqlTable,
3321
+ holders: (resource, groupRelation) => [{ text: `select ${qualified(ctx, graphHelper(resource))}(${quoteLiteral(groupRelation)})` }]
3322
+ }), ctx)}) a\n where ${guard}${activeUserSql(ctx).map((part) => ` and ${part}`).join("")}`;
3323
+ }
3324
+ function permittedSql(ctx, plan, entry, tables) {
3325
+ const fn = qualified(ctx, graphHelper(entry.node.name));
3326
+ const asked = [...entry.relations].toSorted();
3327
+ const arms = [...new Set(asked.flatMap((name) => expandRelation(entry.node, name)))].toSorted().map((relation) => relationArm(ctx, plan, entry.node, relation, asked.filter((name) => expandRelation(entry.node, name).includes(relation)), tables));
3328
+ if (arms.length === 0) arms.push(" select null::text where false");
3329
+ return `-- ${entry.node.name}: ids the subject holds a relation on (all relations when p_relation is null)
3330
+ create or replace function ${fn}(p_relation text)
3331
+ returns setof text
3332
+ language sql
3882
3333
  stable
3883
3334
  security definer
3884
3335
  set search_path = ''
3885
3336
  as $$
3886
- select ${signedIn(ctx)} and (
3887
- ${body}
3888
- )
3889
- $$;` : `language plpgsql
3337
+ ${arms.join("\n union\n")}
3338
+ $$;
3339
+ revoke execute on function ${fn}(text) from public, anon;
3340
+ grant execute on function ${fn}(text) to authenticated;`;
3341
+ }
3342
+ /**
3343
+ * `permdock_link_<resource>_<link>(ids)`: ids of `resource` whose link points
3344
+ * into `ids`, read past the table's own policies.
3345
+ */
3346
+ function linkSql(ctx, node, link, tables) {
3347
+ const spec = Object.hasOwn(node.links, link) ? node.links[link] : void 0;
3348
+ if (spec === void 0) throw configError(`PermDock CLI: ${node.name} has no link '${link}'`);
3349
+ const fn = qualified(ctx, linkHelper(node.name, link));
3350
+ return `-- ${node.name}.${link}: rows whose ${spec.field} points at one of the given ${spec.resource} ids
3351
+ create or replace function ${fn}(p_ids text[])
3352
+ returns setof text
3353
+ language sql
3890
3354
  stable
3891
3355
  security definer
3892
3356
  set search_path = ''
3357
+ as $$
3358
+ select t.${quoteIdent(node.id)}::text from ${quoteQualified(tableFor(node.name, tables))} t
3359
+ where t.${quoteIdent(spec.field)}::text = any(p_ids)
3360
+ $$;
3361
+ revoke execute on function ${fn}(text[]) from public, anon;
3362
+ grant execute on function ${fn}(text[]) to authenticated;`;
3363
+ }
3364
+ function closureTableSql(ctx) {
3365
+ const table = qualified(ctx, CLOSURE.table);
3366
+ return `-- the object graph: one row per (ancestor, descendant) within each walked resource's depth,
3367
+ -- never crossing a restricted row; kept current by statement triggers on each self-parented table
3368
+ create table if not exists ${table} (
3369
+ resource text not null,
3370
+ ancestor text not null,
3371
+ descendant text not null,
3372
+ depth integer not null check (depth >= 0),
3373
+ primary key (resource, descendant, ancestor)
3374
+ );
3375
+ create index if not exists ${quoteIdent(`${CLOSURE.table}_ancestor`)} on ${table} (resource, ancestor);
3376
+ alter table ${table} enable row level security;
3377
+ revoke all on table ${table} from anon, authenticated, public;
3378
+ grant select on table ${table} to authenticated;`;
3379
+ }
3380
+ function closureObjectsSql(ctx, entry, depth, tables) {
3381
+ const { node } = entry;
3382
+ const parent = node.parent;
3383
+ if (parent === void 0) return "";
3384
+ const name = node.name;
3385
+ const sqlName = graphSqlName(name) ?? name;
3386
+ const closure = qualified(ctx, CLOSURE.table);
3387
+ const table = quoteQualified(tableFor(name, tables));
3388
+ const id = quoteIdent(node.id);
3389
+ const up = quoteIdent(parent.field);
3390
+ const restricted = restrictedFor(node, "parent");
3391
+ const stop = restricted === void 0 ? "false" : `coalesce(t.${quoteIdent(restricted)}, false)`;
3392
+ const stopParent = restricted === void 0 ? "false" : `coalesce(p.${quoteIdent(restricted)}, false)`;
3393
+ const refresh = qualified(ctx, `${CLOSURE.table}_${sqlName}`);
3394
+ const changed = qualified(ctx, `${CLOSURE.table}_${sqlName}_changed`);
3395
+ const literal = quoteLiteral(name);
3396
+ const restrictedChanged = restricted === void 0 ? "" : ` or n.${quoteIdent(restricted)} is distinct from o.${quoteIdent(restricted)}`;
3397
+ const trigger = (suffix) => quoteIdent(`${CLOSURE.table}_${sqlName}_${suffix}`);
3398
+ return `-- ${name}: ${restricted === void 0 ? `rows reach their ancestors up to depth ${String(depth)}` : `rows reach their ancestors up to depth ${String(depth)}, stopping at a restricted row`}
3399
+ drop policy if exists ${quoteIdent(`${CLOSURE.table}_${sqlName}`)} on ${closure};
3400
+ create policy ${quoteIdent(`${CLOSURE.table}_${sqlName}`)} on ${closure}
3401
+ for select to authenticated
3402
+ using (resource = ${literal} and ancestor in (select ${qualified(ctx, graphHelper(name))}(null)));
3403
+ create or replace function ${refresh}(p_ids text[])
3404
+ returns void
3405
+ language plpgsql
3406
+ security definer
3407
+ set search_path = ''
3893
3408
  as $$
3894
3409
  declare
3895
- ${used.map((source, index) => ` v_user_${String(index)} ${source.sql.userType} := ${subjectIdSql(ctx)};`).join("\n")}
3410
+ v_cycle text;
3411
+ v_affected text[];
3896
3412
  begin
3897
- return ${signedIn(ctx)} and (
3898
- ${body}
3899
- );
3900
- end;
3901
- $$;`;
3902
- if (forUser) {
3903
- const user = ctx.dialect === "supabase" ? "uuid" : "text";
3904
- return `-- who may assign, for a user the caller names: trusted SQL acting later for a stored user; no client role may execute it
3905
- create or replace function ${fn}(p_user ${user}, p_role text, p_scope_id text)
3906
- returns boolean
3907
- ${language}
3908
- revoke execute on function ${fn}(${user}, text, text) from public, anon, authenticated;`;
3909
- }
3910
- return `-- who may assign: a held role whose assigns lists p_role, in instance p_scope_id (its scope or an ancestor's); a null p_scope_id assigns a global role, which only global assigners may
3911
- create or replace function ${fn}(p_role text, p_scope_id text)
3912
- returns boolean
3913
- ${language}
3914
- revoke execute on function ${fn}(text, text) from public, anon;
3915
- grant execute on function ${fn}(text, text) to authenticated;`;
3916
- }
3917
- /** Whether the helpers keep custom roles in tables, so assigning one is checked against its stored definition. */
3918
- function assignsCustomRoles(ctx) {
3919
- return ctx.customRoles !== void 0 && ctx.authorize === "database";
3920
- }
3921
- /** Whether `permdock_can_assign_any_for` is written: every function it calls has a `_for` form. */
3922
- function hasCanAssignAnyFor(ctx) {
3923
- return ctx.authorize === "database" && (!assignsCustomRoles(ctx) || hasCustomRoleChecksFor(ctx));
3924
- }
3925
- /**
3926
- * `permdock_can_assign_any`: one check for a declared or a custom role, so
3927
- * SQL that assigns roles calls one function: `permdock_can_assign` for a
3928
- * declared role, `permdock_can_assign_custom_role` for a custom one. `p_scope`
3929
- * `'global'` (no tenant, no instance) assigns a platform role.
3930
- */
3931
- function canAssignAnySql(ctx, custom, forUser = false) {
3932
- const tenantType = tenantTypeOf(ctx);
3933
- const fn = qualified(ctx, forUser ? OWNERSHIP.canAssignAnyFor : OWNERSHIP.canAssignAny);
3934
- const declaredFn = qualified(ctx, forUser ? OWNERSHIP.canAssignFor : OWNERSHIP.canAssign);
3935
- const customFn = qualified(ctx, forUser ? ASSIGNMENTS.customFor : ASSIGNMENTS.custom);
3936
- const userType = ctx.dialect === "supabase" ? "uuid" : "text";
3937
- const userParam = forUser ? `p_user ${userType}, ` : "";
3938
- const userArg = forUser ? `${userType}, ` : "";
3939
- const user = forUser ? "p_user, " : "";
3940
- const declared = `${declaredFn}(${user}p_role, case when p_scope = 'global' then null else p_scope_id end)`;
3941
- const body = custom && ctx.customRoles !== void 0 ? `case
3942
- when p_role = any(${declaredArray(ctx.customRoles.declared)}) then ${declared}
3943
- when p_scope = 'global' then ${customFn}(${user}null, 'global', null, p_role)
3944
- else ${customFn}(${user}p_tenant, p_scope, p_scope_id, p_role)
3945
- end` : declared;
3946
- const grants = forUser ? `revoke execute on function ${fn}(${userArg}text, ${tenantType}, text, text) from public, anon, authenticated;` : `revoke execute on function ${fn}(text, ${tenantType}, text, text) from public, anon;
3947
- grant execute on function ${fn}(text, ${tenantType}, text, text) to authenticated;`;
3948
- return `-- whether ${forUser ? "user p_user" : "the caller"} may assign p_role at instance p_scope_id of p_scope in tenant p_tenant, declared or custom (p_scope 'global' for a platform role)${forUser ? "; no client role may execute it" : ""}
3949
- create or replace function ${fn}(${userParam}p_role text, p_tenant ${tenantType}, p_scope text, p_scope_id text)
3950
- returns boolean
3413
+ with recursive walk(start, next, path) as (
3414
+ select t.${id}, t.${up}, array[t.${id}::text]
3415
+ from ${table} t
3416
+ where t.${id}::text = any(p_ids)
3417
+ union all
3418
+ select walk.start, t.${up}, walk.path || t.${id}::text
3419
+ from walk join ${table} t on t.${id} = walk.next
3420
+ where not (walk.next::text = any(walk.path))
3421
+ )
3422
+ select walk.start::text into v_cycle
3423
+ from walk
3424
+ where walk.next::text = any(walk.path)
3425
+ limit 1;
3426
+ if v_cycle is not null then
3427
+ raise exception 'permdock: % % is its own ancestor', ${literal}, v_cycle
3428
+ using errcode = 'check_violation';
3429
+ end if;
3430
+ with recursive down(id, level) as (
3431
+ select t.${id}, 0
3432
+ from ${table} t
3433
+ where t.${id}::text = any(p_ids) or t.${up}::text = any(p_ids)
3434
+ union
3435
+ select t.${id}, down.level + 1
3436
+ from down join ${table} t on t.${up} = down.id
3437
+ where down.level < ${String(depth)}
3438
+ )
3439
+ select coalesce(array_agg(distinct down.id::text), '{}') into v_affected from down;
3440
+ delete from ${closure} c
3441
+ where c.resource = ${literal}
3442
+ and (c.descendant = any(p_ids) or c.descendant = any(v_affected));
3443
+ insert into ${closure} (resource, ancestor, descendant, depth)
3444
+ with recursive walk(descendant, ancestor, parent, depth, stop) as (
3445
+ select t.${id}::text, t.${id}, t.${up}, 0, ${stop}
3446
+ from ${table} t
3447
+ where t.${id}::text = any(v_affected)
3448
+ union all
3449
+ select walk.descendant, p.${id}, p.${up}, walk.depth + 1, ${stopParent}
3450
+ from walk join ${table} p on p.${id} = walk.parent
3451
+ where not walk.stop and walk.depth < ${String(depth)}
3452
+ )
3453
+ select ${literal}, walk.ancestor::text, walk.descendant, walk.depth from walk
3454
+ on conflict do nothing;
3455
+ end
3456
+ $$;
3457
+ revoke execute on function ${refresh}(text[]) from public, anon, authenticated;
3458
+ create or replace function ${changed}()
3459
+ returns trigger
3460
+ language plpgsql
3461
+ security definer
3462
+ set search_path = ''
3463
+ as $$
3464
+ begin
3465
+ if tg_op = 'TRUNCATE' then
3466
+ delete from ${closure} where resource = ${literal};
3467
+ elsif tg_op = 'INSERT' then
3468
+ perform ${refresh}(array(select n.${id}::text from new_rows n));
3469
+ elsif tg_op = 'DELETE' then
3470
+ perform ${refresh}(array(select o.${id}::text from old_rows o));
3471
+ else
3472
+ perform ${refresh}(array(
3473
+ select n.${id}::text
3474
+ from new_rows n left join old_rows o on o.${id} = n.${id}
3475
+ where o.${id} is null or n.${up} is distinct from o.${up}${restrictedChanged}
3476
+ union
3477
+ select o.${id}::text
3478
+ from old_rows o left join new_rows n on n.${id} = o.${id}
3479
+ where n.${id} is null
3480
+ ));
3481
+ end if;
3482
+ return null;
3483
+ end
3484
+ $$;
3485
+ revoke execute on function ${changed}() from public, anon, authenticated;
3486
+ drop trigger if exists ${trigger("insert")} on ${table};
3487
+ create trigger ${trigger("insert")} after insert on ${table}
3488
+ referencing new table as new_rows
3489
+ for each statement execute function ${changed}();
3490
+ drop trigger if exists ${trigger("update")} on ${table};
3491
+ create trigger ${trigger("update")} after update on ${table}
3492
+ referencing old table as old_rows new table as new_rows
3493
+ for each statement execute function ${changed}();
3494
+ drop trigger if exists ${trigger("delete")} on ${table};
3495
+ create trigger ${trigger("delete")} after delete on ${table}
3496
+ referencing old table as old_rows
3497
+ for each statement execute function ${changed}();
3498
+ drop trigger if exists ${trigger("truncate")} on ${table};
3499
+ create trigger ${trigger("truncate")} after truncate on ${table}
3500
+ for each statement execute function ${changed}();
3501
+ select ${refresh}(array(select t.${id}::text from ${table} t));`;
3502
+ }
3503
+ function restrictedRowsHelperSql(ctx, plan, entry, tables) {
3504
+ const above = entry.restrictedAncestors;
3505
+ if (above === void 0) return "";
3506
+ const fn = qualified(ctx, restrictedHelper(entry.node.name));
3507
+ const body = graphSqlText(restrictedRowsSql(above, {
3508
+ resources: new Map([...plan].map(([name, item]) => [name, item.node])),
3509
+ ...tables === void 0 ? {} : { tables },
3510
+ qualify: qualifySqlTable,
3511
+ closure: `${helperSchema(ctx)}.${CLOSURE.table}`,
3512
+ closureDepths: closureDepths(plan)
3513
+ }), ctx);
3514
+ return `-- ${entry.node.name}: rows a stopped link does not reach below: restricted, under a restricted row within ${String(above.depth - 1)} levels, or deeper
3515
+ create or replace function ${fn}()
3516
+ returns setof text
3951
3517
  language sql
3952
3518
  stable
3953
3519
  security definer
3954
3520
  set search_path = ''
3955
3521
  as $$
3956
- select coalesce(${body}, false)
3522
+ ${body}
3957
3523
  $$;
3958
- ${grants}`;
3524
+ revoke execute on function ${fn}() from public, anon;
3525
+ grant execute on function ${fn}() to authenticated;`;
3959
3526
  }
3960
- //#endregion
3961
- //#region src/cli/rls/ownership.ts
3962
- function roleScope(policy, scopes, name) {
3963
- const on = policy.rolesByName.get(name)?.on ?? findRole(policy.vocabulary.roles, name)?.on;
3964
- return typeof on === "string" ? resolveScope(scopes, on) : void 0;
3965
- }
3966
- function isGlobalRole(policy, name) {
3967
- const binding = policy.rolesByName.get(name);
3968
- if (binding !== void 0) return binding.on === void 0;
3969
- const leaf = findRole(policy.vocabulary.roles, name);
3970
- return leaf !== void 0 && leaf.on === void 0;
3971
- }
3972
- /** The rules `rls generate` enforces; `undefined` when no role declares one. */
3973
- function ownershipRules(policy, scopes) {
3974
- const kinds = {};
3975
- const assigns = [];
3976
- const counted = [];
3977
- for (const binding of policy.roles) {
3978
- const scope = roleScope(policy, scopes, binding.name);
3979
- if (binding.for !== void 0) kinds[binding.name] = binding.for;
3980
- const assigner = scope ?? (binding.on === void 0 ? "global" : void 0);
3981
- for (const target of binding.assigns ?? []) {
3982
- const at = roleScope(policy, scopes, target);
3983
- if (assigner !== void 0 && at !== void 0 && (assigner === "global" || scopeChain(scopes, at).includes(assigner))) assigns.push({
3984
- assigner: binding.name,
3985
- scope: assigner,
3986
- role: target,
3987
- at
3988
- });
3989
- else if (assigner === "global" && isGlobalRole(policy, target)) assigns.push({
3990
- assigner: binding.name,
3991
- scope: "global",
3992
- role: target,
3993
- at: "global"
3994
- });
3995
- }
3996
- const min = binding.min ?? 0;
3997
- const transferOnly = binding.transferOnly === true;
3998
- if (scope !== void 0 && (min > 0 || binding.max !== void 0 || transferOnly)) counted.push(binding.max === void 0 ? {
3999
- role: binding.name,
4000
- scope,
4001
- min,
4002
- transferOnly
4003
- } : {
4004
- role: binding.name,
4005
- scope,
4006
- min,
4007
- max: binding.max,
4008
- transferOnly
4009
- });
3527
+ /** Group resources a resource's asked relations name, other than itself. */
3528
+ function groupTargets(entry) {
3529
+ const out = /* @__PURE__ */ new Set();
3530
+ for (const asked of entry.relations) for (const name of expandRelation(entry.node, asked)) {
3531
+ const spec = entry.node.relations[name];
3532
+ const resources = isEdgeRelation(spec) ? groupEntries(spec).map((group) => group.resource) : [];
3533
+ for (const resource of resources) if (resource !== entry.node.name) out.add(resource);
4010
3534
  }
4011
- if (Object.keys(kinds).length === 0 && assigns.length === 0 && counted.length === 0) return;
4012
- return {
4013
- kinds,
4014
- assigns,
4015
- counted
4016
- };
3535
+ return [...out].toSorted();
4017
3536
  }
4018
3537
  /**
4019
- * The roles whose `for` lists kinds that no membership source of the role's
4020
- * scope can hold: such a role resolves to no one. A `via` read from a column
4021
- * can hold any kind, and so does a source with no `via` (it is not
4022
- * compared) and a scope with no table or source.
3538
+ * Plan entries by name, each after the group resources its helper calls (SQL
3539
+ * function bodies are checked on create).
4023
3540
  */
4024
- function unsatisfiedKinds(ctx, policy) {
3541
+ function dependencyOrder(plan) {
4025
3542
  const out = [];
4026
- for (const [role, kinds] of Object.entries(ctx.ownership?.kinds ?? {})) {
4027
- const scope = roleScope(policy, ctx.scopes, role);
4028
- if (scope === void 0) continue;
4029
- const held = [];
4030
- const mapped = scopeTable(ctx, scope)?.table;
4031
- if (mapped !== void 0) {
4032
- const via = memberVia(mapped);
4033
- held.push({
4034
- label: mapped.table,
4035
- via: typeof via === "string" ? void 0 : via.value ?? void 0
4036
- });
4037
- } else for (const source of ctx.memberSources ?? ctx.sources ?? []) {
4038
- if (source.sql.scope !== void 0 && source.sql.scope !== scope) continue;
4039
- const via = source.sql.manifest.via;
4040
- held.push({
4041
- label: source.sql.table,
4042
- via: via === void 0 ? void 0 : "value" in via && typeof via.value === "string" ? via.value : void 0
4043
- });
4044
- }
4045
- if (held.length === 0 || held.some((entry) => entry.via === void 0 || kinds.includes(entry.via ?? ""))) continue;
4046
- out.push({
4047
- role,
4048
- for: kinds,
4049
- sources: held.map((entry) => `${entry.label} (via ${entry.via ?? ""})`)
4050
- });
4051
- }
3543
+ const done = /* @__PURE__ */ new Set();
3544
+ const visit = (name) => {
3545
+ const entry = plan.get(name);
3546
+ if (entry === void 0 || done.has(name)) return;
3547
+ done.add(name);
3548
+ for (const target of groupTargets(entry)) visit(target);
3549
+ out.push(entry);
3550
+ };
3551
+ for (const name of [...plan.keys()].toSorted()) visit(name);
4052
3552
  return out;
4053
3553
  }
4054
3554
  /**
4055
- * The ownership objects: holder-count and transfer-only triggers on each
4056
- * scope's membership table, and `permdock_can_assign` for the application's
4057
- * own policies on those tables. Empty when no role declares a rule.
3555
+ * The graph preamble: `permitted_<resource>_ids` for every resource a graph
3556
+ * grant reads, and the closure table with its triggers for every self-parented
3557
+ * resource a grant walks. Empty when no grant is a graph grant.
4058
3558
  */
4059
- function ownershipSql(ctx) {
4060
- const own = ctx.ownership;
4061
- if (own === void 0) return "";
4062
- const chunks = [];
4063
- const skipped = ctx.skipOwnershipTriggers;
4064
- for (const { name } of ctx.scopes) {
4065
- const counted = own.counted.filter((rule) => rule.scope === name);
4066
- if (counted.length === 0) continue;
4067
- if (skipped === "all" || skipped?.includes(name) === true) {
4068
- chunks.push(`-- ${name}: rls.ownershipTriggers leaves out the triggers, so min, max and transferOnly are checked only by decideRoleChange`);
4069
- continue;
4070
- }
4071
- const transfer = counted.filter((rule) => rule.transferOnly).map((rule) => rule.role);
4072
- const mapped = scopeTable(ctx, name);
4073
- if (mapped === void 0) {
4074
- const sources = holderSources(ctx, name);
4075
- if (sources === void 0) chunks.push(`-- ${name}: no memberships table configured, so min, max and transferOnly are checked only by decideRoleChange`);
4076
- else if (sources === "not-tables") chunks.push(`-- ${name}: a membership source is not a table, so min, max and transferOnly are checked only by decideRoleChange`);
4077
- else {
4078
- chunks.push(sourceHoldersTriggerSql(ctx, name, sources, counted));
4079
- if (transfer.length > 0) chunks.push(sourceTransferTriggerSql(ctx, name, sources, transfer));
4080
- }
4081
- continue;
4082
- }
4083
- chunks.push(holdersTriggerSql(ctx, name, mapped.table, mapped.column, counted));
4084
- if (transfer.length > 0) chunks.push(transferTriggerSql(ctx, name, mapped.table, mapped.column, transfer));
3559
+ function graphSql(ctx, plan, tables) {
3560
+ if (plan.size === 0) return "";
3561
+ const sqlNames = /* @__PURE__ */ new Map();
3562
+ for (const name of plan.keys()) {
3563
+ const helper = graphHelper(name);
3564
+ const sql = graphSqlName(name) ?? name;
3565
+ if (ctx.scopes.some((scope) => scope.name === sql)) throw configError(`PermDock CLI: resource '${name}' has graph relations and shares its SQL name with the ${sql} scope, so ${helper} would replace the scope helper; rename the resource (permdock doctor PD032)`);
3566
+ const other = sqlNames.get(sql);
3567
+ if (other !== void 0) throw configError(`PermDock CLI: resources '${other}' and '${name}' both name the SQL helper ${helper}; rename one (permdock doctor PD032)`);
3568
+ sqlNames.set(sql, name);
4085
3569
  }
4086
- if (own.assigns.length > 0) {
4087
- chunks.push(canAssignSql(ctx, own));
4088
- if (ctx.authorize === "database") chunks.push(canAssignSql(ctx, own, true));
4089
- const custom = assignsCustomRoles(ctx);
4090
- if (custom) {
4091
- chunks.push(canAssignCustomSql(ctx));
4092
- if (hasCustomRoleChecksFor(ctx)) chunks.push(canAssignCustomSql(ctx, true));
3570
+ const entries = dependencyOrder(plan);
3571
+ const chunks = entries.flatMap((entry) => [...entry.relations.size === 0 && entry.closure === void 0 ? [] : [permittedSql(ctx, plan, entry, tables)], ...[...entry.links].toSorted().map((link) => linkSql(ctx, entry.node, link, tables))]);
3572
+ const walked = entries.filter((entry) => entry.closure !== void 0);
3573
+ if (walked.length > 0) {
3574
+ chunks.push(closureTableSql(ctx));
3575
+ for (const entry of walked) chunks.push(closureObjectsSql(ctx, entry, entry.closure ?? 0, tables));
3576
+ for (const entry of walked) {
3577
+ const helper = restrictedRowsHelperSql(ctx, plan, entry, tables);
3578
+ if (helper !== "") chunks.push(helper);
4093
3579
  }
4094
- chunks.push(canAssignAnySql(ctx, custom));
4095
- if (hasCanAssignAnyFor(ctx)) chunks.push(canAssignAnySql(ctx, custom, true));
4096
3580
  }
4097
- return chunks.length === 0 ? "" : `${chunks.join("\n\n")}\n`;
3581
+ return `${chunks.join("\n\n")}\n`;
3582
+ }
3583
+ //#endregion
3584
+ //#region src/cli/sql/tables.ts
3585
+ /**
3586
+ * The `schema.table` an `rls.tables` entry names, or `resource` when it names
3587
+ * none or is `false`.
3588
+ */
3589
+ function tableNameOf(entry, resource) {
3590
+ if (entry === void 0 || entry === false) return resource;
3591
+ return typeof entry === "string" ? entry : entry.table ?? resource;
3592
+ }
3593
+ /**
3594
+ * `rls.tables` as resource to table name, the `via` objects reduced to their
3595
+ * `table` and the `false` entries left out.
3596
+ */
3597
+ function tableNames(tables) {
3598
+ return tables === void 0 ? void 0 : Object.fromEntries(Object.entries(tables).filter(([, entry]) => entry !== false).map(([resource, entry]) => [resource, tableNameOf(entry, resource)]));
3599
+ }
3600
+ /** The resources `rls.tables` marks `false`: no table, so no policies. */
3601
+ function tablelessResources(tables) {
3602
+ return new Set(Object.entries(tables ?? {}).filter(([, entry]) => entry === false).map(([resource]) => resource));
3603
+ }
3604
+ function singular(name) {
3605
+ if (name.endsWith("ies")) return `${name.slice(0, -3)}y`;
3606
+ if (name.endsWith("sses")) return name.slice(0, -2);
3607
+ return name.endsWith("s") && !name.endsWith("ss") ? name.slice(0, -1) : name;
3608
+ }
3609
+ /** The `via` entries of `rls.tables`, by resource. */
3610
+ function resolveVias(tables, scopes, dialect) {
3611
+ const out = {};
3612
+ for (const [resource, entry] of Object.entries(tables ?? {})) {
3613
+ if (typeof entry === "string" || entry === false) continue;
3614
+ const { via } = entry;
3615
+ const scope = via.scope ?? scopes[0]?.name;
3616
+ if (scope === void 0 || !scopes.some((item) => item.name === scope)) throw configError(`PermDock CLI: rls.tables.${resource}.via.scope '${scope ?? ""}' is not a scope of the policy (${scopes.map((item) => item.name).join(", ")})`);
3617
+ const name = singular(via.table.slice(via.table.lastIndexOf(".") + 1));
3618
+ out[resource] = {
3619
+ helper: permittedIdsHelper(name),
3620
+ key: via.key ?? `${name}_id`,
3621
+ scope,
3622
+ table: via.table.includes(".") ? via.table : `public.${via.table}`,
3623
+ column: via.column,
3624
+ id: via.id ?? "id",
3625
+ type: via.type ?? (dialect === "supabase" ? "uuid" : "text")
3626
+ };
3627
+ }
3628
+ const helpers = /* @__PURE__ */ new Map();
3629
+ for (const [resource, via] of Object.entries(out)) {
3630
+ const seen = helpers.get(via.helper);
3631
+ if (seen !== void 0 && (seen.table !== via.table || seen.column !== via.column || seen.id !== via.id || seen.scope !== via.scope || seen.type !== via.type)) throw configError(`PermDock CLI: rls.tables.${resource}.via names ${via.helper}, which another resource defines over a different parent; give the parents different table names`);
3632
+ helpers.set(via.helper, via);
3633
+ }
3634
+ return Object.keys(out).length === 0 ? void 0 : out;
4098
3635
  }
4099
3636
  //#endregion
4100
- export { permittedForHelper as $, apiKeyFields as A, sqlFunctionNames as B, globalRoleSource as C, byKey as D, byCodeUnit as E, collectGrants as F, hasMemberFor as G, graphHelper as H, roleNames as I, memberForHelper as J, hasPermittedUsers as K, andConditions as L, assignableRoles as M, breakGlassHolder as N, API_KEY_ALLOWS as O, breakGlassKey as P, memberRoleOf as Q, compileConditionSql as R, globalRoleRows as S, rolesTableScope as T, graphSqlName as U, CLOSURE as V, graphSqlText as W, memberForTable as X, memberForSources as Y, memberIdsHelper as Z, seedSql as _, assignmentSql as a, activeUserSql as at, tokenScopesPlan as b, OWNERSHIP as c, permissionHelpersSql as d, permittedIdsHelper as et, permissionKeysForHelper as f, helpersSql as g, accessSql as h, holderSources as i, activeRowSql as it, apiKeysPlan as j, apiKeyAllowsCall as k, PERMISSION_HELPERS as l, requiresSql as m, ownershipSql as n, scopeSources as nt, guardedTables as o, checkSuspension as ot, permittedByPermissionHelper as p, linkHelper as q, unsatisfiedKinds as r, scopeTable as rt, ASSIGNMENTS as s, ownershipRules as t, restrictedHelper as tt, checkName as u, TOKEN_SCOPE_ALLOWS as v, currentKeyExpr as w, capabilityAccessSql as x, tokenScopeAllowsCall as y, contextRefs as z };
3637
+ export { graphSqlText as $, claimKeptLines as A, hasCustomRoleChecksFor as B, accessSql as C, tokenScopeAllowsCall as D, TOKEN_SCOPE_ALLOWS as E, globalRoleRows as F, byKey as G, rolesTableScope as H, membershipRows as I, apiKeyFields as J, API_KEY_ALLOWS as K, roleRows as L, memberColumn as M, sourceFilters as N, tokenScopesPlan as O, sourcesKeep as P, graphSqlName as Q, globalRoleSource as R, requiresSql as S, seedSql as T, storedTenant as U, raiseSql as V, byCodeUnit as W, CLOSURE as X, apiKeysPlan as Y, graphHelper as Z, PERMISSION_HELPERS as _, activeMembershipSql as _t, closureDepths as a, memberForHelper as at, permissionKeysForHelper as b, checkSuspension as bt, holderSources as c, memberIdsHelper as ct, sourceRowsSql as d, permittedForHelper as dt, hasMemberFor as et, sourceTransferTriggerSql as f, permittedIdsHelper as ft, OWNERSHIP as g, activeInstancesSql as gt, ASSIGNMENTS as h, scopeTable as ht, tablelessResources as i, linkHelper as it, disabledColumn as j, capabilityAccessSql as k, pairsSql as l, memberOfHelper as lt, transferTriggerSql as m, scopeSources as mt, tableNameOf as n, inheritedRowsHelper as nt, graphPlan as o, memberForSources as ot, holdersTriggerSql as p, restrictedHelper as pt, apiKeyAllowsCall as q, tableNames as r, isTeamScope as rt, graphSql as s, memberForTable as st, resolveVias as t, hasPermittedUsers as tt, sourceHoldersTriggerSql as u, memberRoleOf as ut, checkName as v, activeRowSql as vt, helpersSql as w, permittedByPermissionHelper as x, permissionHelpersSql as y, activeUserSql as yt, currentKeyExpr as z };