permdock 0.3.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -35
- package/dist/{openapi-schema--ARjyfQr.js → 2020-C9VN-x8c.js} +5 -132
- package/dist/a2a/index.d.ts +8 -6
- package/dist/a2a/index.js +21 -31
- package/dist/{adapter-context-mlWsVGGg.js → adapter-context-CRhqyzCk.js} +1 -1
- package/dist/adoption-scan-B4R5fnMU.js +107 -0
- package/dist/agent/index.d.ts +3 -0
- package/dist/agent/index.js +1 -0
- package/dist/ai-sdk/index.d.ts +18 -23
- package/dist/ai-sdk/index.js +38 -21
- package/dist/app-data-MuyzVqX8.js +60 -0
- package/dist/approval-header-CJACtHkv.js +23 -0
- package/dist/approval-prompt-Dg5LeAcT.js +62 -0
- package/dist/approval-prompt-Djo_PyZc.d.ts +57 -0
- package/dist/approvals/index.d.ts +5 -3
- package/dist/approvals/index.js +8 -5
- package/dist/approvals-DJ6Yd5z1.js +995 -0
- package/dist/{approvers-DT4AVyIg.js → approvers-CqPOLD-o.js} +1 -1
- package/dist/arazzo-B6lebmiQ.js +36 -0
- package/dist/{arazzo-BAgw9ap6.js → arazzo-BWhNF-k0.js} +24 -17
- package/dist/args-IIolxxKA.js +197 -0
- package/dist/astro/index.d.ts +43 -0
- package/dist/astro/index.js +27 -0
- package/dist/audience-Dg8XsRaO.js +55 -0
- package/dist/auth-DRLEizbL.js +178 -0
- package/dist/authzen/index.d.ts +26 -6
- package/dist/authzen/index.js +228 -75
- package/dist/automation-BMZPIVUM.d.ts +114 -0
- package/dist/automation-DVjOckjV.js +157 -0
- package/dist/better-auth/index.d.ts +8 -6
- package/dist/better-auth/index.js +11 -23
- package/dist/better-supabase/index.d.ts +438 -86
- package/dist/better-supabase/index.js +1027 -184
- package/dist/bind--9EFVYWF.d.ts +38 -0
- package/dist/{bind-CuzGytRf.js → bind-CZzhx-Vj.js} +22 -10
- package/dist/body-CH5of8Ne.js +10 -0
- package/dist/body-text-QcXok_SI.js +52 -0
- package/dist/build-instance-BMnAudHn.js +52 -0
- package/dist/{canonical-json-DemiK4Vp.js → canonical-json-BC8_nMTw.js} +6 -6
- package/dist/{capability-2kYTMWDv.d.ts → capability-BSslw1QV.d.ts} +44 -25
- package/dist/{capability-4qPdO9gk.js → capability-N1K9IpqS.js} +69 -42
- package/dist/casbin-Cr7RLtA3.js +30 -0
- package/dist/casbin-Dyu2gnUD.js +234 -0
- package/dist/casl-64gpT-8x.js +24 -0
- package/dist/casl-DjEDhTCP.js +299 -0
- package/dist/catalog/index.d.ts +74 -2
- package/dist/catalog/index.js +51 -2
- package/dist/catalog-BECMPrwc.js +100 -0
- package/dist/catalog-DjhMYYCq.js +1 -0
- package/dist/catalog-R_A__RsH.js +52 -0
- package/dist/{catalog-doc-DdGm82XT.js → catalog-doc-C9wjsfNY.js} +54 -25
- package/dist/catalog-fingerprint-CCP0-yQS.js +153 -0
- package/dist/chat/index.d.ts +2 -0
- package/dist/chat/index.js +224 -0
- package/dist/chat-sdk/index.d.ts +87 -0
- package/dist/chat-sdk/index.js +197 -0
- package/dist/claims-map-7oQ3ue4X.js +13 -0
- package/dist/claims-map-Ca-QX8C6.d.ts +32 -0
- package/dist/claude-agent/index.d.ts +7 -14
- package/dist/claude-agent/index.js +26 -30
- package/dist/clerk/index.d.ts +9 -8
- package/dist/clerk/index.js +15 -45
- package/dist/cli/bin.js +36 -17
- package/dist/cli/index.d.ts +20 -6
- package/dist/cli/index.js +164 -4
- package/dist/cloud/index.d.ts +102 -63
- package/dist/cloud/index.js +240 -347
- package/dist/cloud-CIoHNevm.js +87 -0
- package/dist/codemod-Bmj9dWMH.js +37 -0
- package/dist/codemod-Cxvvc70T.js +2084 -0
- package/dist/{collect-DSTE3ml9.js → collect-CrZJJnEF.js} +79 -17
- package/dist/collect-DloHbDVa.js +55 -0
- package/dist/collect-FlXJlhus.js +1 -0
- package/dist/command-L8EstFNm.js +6 -0
- package/dist/{compact-y6vfu2_f.js → compact-CAf557ZB.js} +11 -1
- package/dist/compile/index.d.ts +1 -1
- package/dist/compile/index.js +1 -2
- package/dist/{compile-Ba4blROR.js → compile-CMqMx_IN.js} +13 -9
- package/dist/{compile-BkRyBDBw.d.ts → compile-Ckm2HqBZ.d.ts} +5 -3
- package/dist/completion-BqhRBJD8.js +143 -0
- package/dist/config-B46rm9sc.js +26 -0
- package/dist/{config-BJiM-xAm.js → config-bs_kZeYq.js} +15 -10
- package/dist/{config-report-CdCgbRnV.js → config-report-DUgj62Ai.js} +17 -8
- package/dist/{connection-CU8MQxRi.js → connection-DjhIJSs2.js} +8 -6
- package/dist/constants-DcJABbLW.js +20 -0
- package/dist/{context-Bcy85rGi.js → context-bbFHR-0D.js} +16 -5
- package/dist/convex/index.d.ts +11 -10
- package/dist/convex/index.js +18 -18
- package/dist/create-0VZ7Pdiu.d.ts +107 -0
- package/dist/create-BDS7wc3y.d.ts +138 -0
- package/dist/create-BFfm17UA.d.ts +110 -0
- package/dist/create-BWRSXgOF.js +403 -0
- package/dist/create-C3b4B5eF.js +130 -0
- package/dist/create-DnrmLnLW.js +323 -0
- package/dist/{credential-8P81ff4u.js → credential-DM2um3ha.js} +153 -32
- package/dist/credentials-DPyHmYz5.js +74 -0
- package/dist/deciding-columns-Sq12mEA6.js +123 -0
- package/dist/delegation-DaXVdDhM.js +286 -0
- package/dist/delegation-input-FTxMK3ji.js +19 -0
- package/dist/delivery/index.d.ts +5 -0
- package/dist/delivery/index.js +5 -0
- package/dist/delivery-DCAgNyCn.js +499 -0
- package/dist/delivery-event-xX4obB8F.js +40 -0
- package/dist/denied-text-D8jDHgsl.d.ts +19 -0
- package/dist/{describe-DMYiCjJV.d.ts → describe-BZUsixiL.d.ts} +11 -2
- package/dist/{describe-B_gVZEVi.js → describe-BybMRY7A.js} +10 -5
- package/dist/device-BARQ8eYG.js +193 -0
- package/dist/diff-CxZo9pTq.js +37 -0
- package/dist/{diff-2VaeChzX.js → diff-DCePiN6M.js} +61 -55
- package/dist/doctor-CxiwSfqH.js +44 -0
- package/dist/doctor-DFjUxf2f.js +2361 -0
- package/dist/drizzle/index.d.ts +5 -3
- package/dist/drizzle/index.js +6 -14
- package/dist/elysia/index.d.ts +16 -13
- package/dist/elysia/index.js +14 -13
- package/dist/env-DJZrPjRi.js +8 -0
- package/dist/errors-BDUwg2qC.js +27 -0
- package/dist/{errors-Cz5zn0mW.js → errors-BKy0EfhN.js} +6 -10
- package/dist/errors-BWXBMaKK.d.ts +11 -0
- package/dist/{errors-DPdqI50v.d.ts → errors-C2PXIv4c.d.ts} +2 -2
- package/dist/errors-CWTY6uih.js +143 -0
- package/dist/evaluate-_R8QkWaf.js +1732 -0
- package/dist/evaluations-16UqqQ6W.d.ts +32 -0
- package/dist/{evaluations-tz6D9A2G.js → evaluations-DZ2CbV5k.js} +181 -32
- package/dist/eve/index.d.ts +12 -16
- package/dist/eve/index.js +23 -29
- package/dist/event-time-CsegxXxB.js +23 -0
- package/dist/expiry-D4_YKRKD.js +52 -0
- package/dist/express/index.d.ts +13 -10
- package/dist/express/index.js +14 -12
- package/dist/fastify/index.d.ts +26 -13
- package/dist/fastify/index.js +17 -36
- package/dist/fetch-adapter-g0ypLkID.js +120 -0
- package/dist/fetch-adapter-qKJBuRqy.d.ts +31 -0
- package/dist/fields-D9tPVUUS.js +203 -0
- package/dist/files-C5ZI27eV.js +124 -0
- package/dist/{files-DWUN9PzI.js → files-D_JIzUrP.js} +12 -3
- package/dist/{fixtures-ht28Cplr.js → fixtures-B7XrLeY7.js} +12 -12
- package/dist/flags-C3DktBDY.js +86 -0
- package/dist/{from-snapshot-CTLijvdm.js → from-snapshot-D2lZt4Dd.js} +78 -65
- package/dist/{generate-CH7ThJ14.js → generate-CNU1pIOo.js} +1 -3
- package/dist/grantee-Cn1HyUsI.js +254 -0
- package/dist/grantee-conditions-CcXf8hMM.js +271 -0
- package/dist/grantee-match-DHArqY82.js +142 -0
- package/dist/h3/index.d.ts +37 -0
- package/dist/h3/index.js +35 -0
- package/dist/headers-BIjfMaoC.js +8 -0
- package/dist/headers-CIbDTUTK.d.ts +7 -0
- package/dist/{helpers-H_KARDVC.js → helpers-DTcZOzhF.js} +47 -39
- package/dist/hono/index.d.ts +12 -9
- package/dist/hono/index.js +14 -13
- package/dist/{hosted-DJQw46hn.js → hosted-BZIAYvon.js} +64 -15
- package/dist/hosted-DBDX0M-P.d.ts +78 -0
- package/dist/{http-SO7qGttf.js → http-BBoKefeS.js} +35 -2
- package/dist/{http-DPuDaChb.d.ts → http-B_LphE5Y.d.ts} +1 -2
- package/dist/http-DXJ37RDA.js +46 -0
- package/dist/{http-BIhmLfCc.js → http-ujKm0JXE.js} +3 -26
- package/dist/{openapi-import-cmwetbzw.js → import-CNV7I2Dw.js} +30 -26
- package/dist/{rls-import-4lLW_pbb.js → import-DMmOG27O.js} +271 -128
- package/dist/index-BcHcPyIo.d.ts +110 -0
- package/dist/index-CaxrMD1A.d.ts +1 -0
- package/dist/index-Chrs5fQ-.d.ts +210 -0
- package/dist/index-CxGUswIC.d.ts +431 -0
- package/dist/index-D8EovfsP.d.ts +91 -0
- package/dist/index-DOVf7RJv.d.ts +900 -0
- package/dist/{index-C8z4RjQV.d.ts → index-SGdIQyzc.d.ts} +34 -16
- package/dist/index.d.ts +20 -14
- package/dist/index.js +33 -23
- package/dist/init-DeMYsnYJ.js +96 -0
- package/dist/init-GG6Jh8HD.js +95 -0
- package/dist/{instance-options-jARdwfPN.d.ts → instance-options-Omfd793B.d.ts} +2 -2
- package/dist/instance-snapshot-Cxq4ygFc.js +600 -0
- package/dist/instant-BluqDvyS.js +19 -0
- package/dist/internals-Dk8A8fX3.js +17 -0
- package/dist/{json-schema-BsAflUGW.js → json-schema-B-vAwlde.js} +46 -9
- package/dist/json-schema-CN_iLFH2.d.ts +40 -0
- package/dist/jwt/index.d.ts +18 -11
- package/dist/jwt/index.js +166 -145
- package/dist/{kernel-_RRHmnMY.js → kernel-5JatXyND.js} +120 -43
- package/dist/kysely/index.d.ts +5 -3
- package/dist/kysely/index.js +6 -14
- package/dist/{live-options-H2eG8dfJ.js → live-options-BaAFCIiu.js} +16 -3
- package/dist/load-BxEtwlBS.js +1 -0
- package/dist/{load-DhWEB9P1.js → load-DFS_ry0W.js} +31 -7
- package/dist/{load-jose-BL6LH4bL.js → load-jose-DZ4dnjri.js} +1 -4
- package/dist/login-Dfe5RlYk.js +68 -0
- package/dist/login-imhuwvn3.js +254 -0
- package/dist/manifest-C8IzW3Do.js +42 -0
- package/dist/manifest-CtKR3GXL.js +370 -0
- package/dist/manifest-json-CAGzls-C.js +9 -0
- package/dist/{manifest-uDS9y5de.d.ts → manifest-mx5ghSFM.d.ts} +9 -2
- package/dist/manifest-schema-C0kK_8V0.js +399 -0
- package/dist/{map-claims-Z4QZOiFF.js → map-claims-D3oRmTk0.js} +19 -25
- package/dist/{map-error-pgUWNNtG.js → map-error-TCAdw-eW.js} +5 -5
- package/dist/{markers-B0hYLvMD.js → markers-C6joDI3o.js} +8 -3
- package/dist/mcp/index.d.ts +36 -9
- package/dist/mcp/index.js +476 -448
- package/dist/{rls-migrate-CN4TlJTO.js → migrate-BShP7Vf4.js} +119 -20
- package/dist/nest/index.d.ts +12 -9
- package/dist/nest/index.js +20 -20
- package/dist/next/index.d.ts +40 -20
- package/dist/next/index.js +37 -39
- package/dist/next/plugin.d.ts +1 -1
- package/dist/next/plugin.js +2 -2
- package/dist/node/index.d.ts +10 -10
- package/dist/node/index.js +7 -9
- package/dist/ocsf-BFp-PZZA.js +126 -0
- package/dist/openai/index.d.ts +9 -16
- package/dist/openai/index.js +18 -25
- package/dist/openapi/index.d.ts +3 -3
- package/dist/openapi/index.js +1 -1
- package/dist/{openapi-BqRBvAng.js → openapi-B7HZ0f-r.js} +9 -12
- package/dist/{openapi-CqqEbHsL.js → openapi-DTaEmTSW.js} +23 -26
- package/dist/openapi-ahr8rHu3.js +138 -0
- package/dist/operation-findings-DgATDpvh.js +36 -0
- package/dist/operations-CxybZErY.d.ts +59 -0
- package/dist/orpc/index.d.ts +13 -10
- package/dist/orpc/index.js +23 -20
- package/dist/otel/index.d.ts +3 -2
- package/dist/otel/index.js +53 -27
- package/dist/output-Dyw8fyPF.js +88 -0
- package/dist/output-files-B3IZJksC.js +1 -0
- package/dist/output-files-DhRBBE4n.js +138 -0
- package/dist/{rls-fields-CZOMlwHl.js → ownership-BatFgAA4.js} +2393 -2349
- package/dist/{package-root-zGkxO3oN.js → package-root-Dk_ETdkp.js} +4 -3
- package/dist/{parse-BuyjxhZV.js → parse-Cxq0WxrA.js} +3 -3
- package/dist/{parse-local-manifest-BrMozJCo.js → parse-local-manifest-CVSs6J9I.js} +2 -4
- package/dist/{parse-local-manifest-JLnaBH0S.d.ts → parse-local-manifest-DgqtruPn.d.ts} +1 -1
- package/dist/parse-request-D_tUXG6M.js +26 -0
- package/dist/paths-DXSHAMIa.js +42 -0
- package/dist/pdp/index.d.ts +7 -3
- package/dist/pdp/index.js +47 -29
- package/dist/{peer-BOr_kJE1.js → peer-C8swJ2GH.js} +23 -4
- package/dist/peer-CRvC7Hg1.js +1 -0
- package/dist/permdock-B9JxkBwC.js +868 -0
- package/dist/permdock-D7eeLJER.d.ts +502 -0
- package/dist/permission-registry-Br4jSNVi.js +385 -0
- package/dist/permissions-B-wCP5ZC.js +30 -0
- package/dist/{permissions-RJrovw98.js → permissions-CZKiUfFM.js} +4 -3
- package/dist/{permissions-CDgvKG5L.d.ts → permissions-oEWfl-kl.d.ts} +1 -2
- package/dist/policies-CO1oOymN.js +295 -0
- package/dist/{policy-D2W7uv19.d.ts → policy-Czo4n3Hs.d.ts} +2 -2
- package/dist/policy-DLZJ5gAp.js +399 -0
- package/dist/policy-from-catalog-DzhEc3Y9.js +450 -0
- package/dist/policy-module-CTlwMvLI.js +89 -0
- package/dist/{policy-IB6yxpC3.d.ts → policy-types-DzwivBgR.d.ts} +939 -1057
- package/dist/postgrest-CW0IqC-U.js +566 -0
- package/dist/postgrest-NUqZT-RB.d.ts +100 -0
- package/dist/powersync-BHfuehf3.js +62 -0
- package/dist/powersync-CbzkqL_M.js +1 -0
- package/dist/{powersync-Dv1_aWF7.js → powersync-bYUDLOAO.js} +30 -32
- package/dist/preset/index.d.ts +146 -0
- package/dist/preset/index.js +1 -0
- package/dist/preset-DZdU0tne.js +1768 -0
- package/dist/principal-source-pHneukrx.js +103 -0
- package/dist/prisma/index.d.ts +6 -3
- package/dist/prisma/index.js +6 -12
- package/dist/{problem-B43ztKIm.js → problem-CTviyH5L.js} +18 -27
- package/dist/{problem-details-DX075l9V.d.ts → problem-details-DXlt-vks.d.ts} +1 -1
- package/dist/{problem-details-_AMOYz-w.js → problem-details-cn5X30gZ.js} +45 -42
- package/dist/problem-types-CJ2325SZ.js +5 -0
- package/dist/{prompt-CXYhLfsS.js → prompt-DfZLJLIn.js} +2 -2
- package/dist/protected-DEPHc2xV.d.ts +38 -0
- package/dist/{protected-utU-SLuy.js → protected-DfjY5mLA.js} +37 -8
- package/dist/{provider-CSQd7m92.js → provider-CC-u3bYP.js} +23 -10
- package/dist/{provider-DZY281jO.d.ts → provider-DMVt6yYn.d.ts} +1 -1
- package/dist/{provider-store-Cfbvo4DE.js → provider-store-B5Yrkhzr.js} +6 -3
- package/dist/pull-BgIfno6w.js +256 -0
- package/dist/pull-ByOks_Tg.js +1 -0
- package/dist/push-CmFVBOeI.js +116 -0
- package/dist/pushed-policy-BImMTTIM.d.ts +57 -0
- package/dist/pushed-policy-DobP7AHI.js +238 -0
- package/dist/react/index.d.ts +8 -8
- package/dist/react/index.js +6 -6
- package/dist/react/provider-client.d.ts +1 -1
- package/dist/react/provider-client.js +1 -2
- package/dist/react-native/index.d.ts +24 -63
- package/dist/react-native/index.js +45 -27
- package/dist/{refs-KYb4ntaz.js → refs-K6eWUJ0H.js} +1 -1
- package/dist/relation-walk-3OyDBF0S.js +186 -0
- package/dist/relations-BnCWnJGI.js +163 -0
- package/dist/renames-CGj7YwcQ.js +175 -0
- package/dist/{replay-CYyiLiwO.d.ts → replay-D7iKcLFe.d.ts} +1 -1
- package/dist/{replay-BsOyTrdV.js → replay-Du1tJjG7.js} +2 -1
- package/dist/request-context-BTRTG7OT.js +71 -0
- package/dist/request-context-C2_1lchh.d.ts +5 -0
- package/dist/request-schema-CMreVuFM.js +148 -0
- package/dist/resend/index.d.ts +126 -0
- package/dist/resend/index.js +379 -0
- package/dist/resource-relations-D_jw0BFx.js +243 -0
- package/dist/retire-CALjIQ7e.js +240 -0
- package/dist/{revocations-Cy06JNx2.d.ts → revocations-R07lFBnX.d.ts} +2 -0
- package/dist/rls-BA8an5Ap.js +336 -0
- package/dist/{rls-DiinlRML.js → rls-j6t43t7n.js} +1066 -1068
- package/dist/role-builder-CXbis5-X.js +623 -0
- package/dist/{rpc-DL9Sqvb1.js → rpc-aPyNi6tA.js} +18 -8
- package/dist/run-CNsApDpd.js +1 -0
- package/dist/run-DOfMtc9V.js +723 -0
- package/dist/run-ticket-BjhTOi6u.d.ts +124 -0
- package/dist/run-ticket-sxFI6LAk.js +337 -0
- package/dist/{runtime-CB8gQFvS.js → runtime-D3VTAiCA.js} +1 -1
- package/dist/{saas-DhpJjnBA.js → saas-FO-Q8oj0.js} +18 -15
- package/dist/scan-BOI5tJ-6.js +29 -0
- package/dist/{schema-D3MnDMxa.js → schema-B4v0Qrac.js} +106 -10
- package/dist/schema-yLuBWWsh.js +130 -0
- package/dist/scim/index.d.ts +2 -2
- package/dist/scim/index.js +1 -2
- package/dist/{scim-BFGBBAqR.js → scim-ClHtpyV3.js} +213 -87
- package/dist/{scopes-CVdPrWN-.js → scopes-CUPSjo9q.js} +20 -7
- package/dist/server/index.d.ts +13 -134
- package/dist/server/index.js +13 -10
- package/dist/server-DqCT4CgZ.js +453 -0
- package/dist/{create-B1d519XK.d.ts → server-types-BLd4MnBX.d.ts} +89 -70
- package/dist/{sha256-BO0swRXL.js → sha256-D9ihqmvd.js} +8 -1
- package/dist/shared-CD11nBbC.js +304 -0
- package/dist/{shared-DRu8VFVM.js → shared-DvvFzYXe.js} +8 -9
- package/dist/signals-D3NcTKUS.js +32 -0
- package/dist/sink-BEZSlMS_.js +957 -0
- package/dist/sink-Bj_lDUDq.d.ts +225 -0
- package/dist/{skills-Biq5uup3.js → skills-B6lVdD_W.js} +16 -8
- package/dist/skills-CTYwkl44.js +51 -0
- package/dist/snapshot-CXUWfsNy.js +269 -0
- package/dist/{snapshot-cache-D6jLelln.js → snapshot-cache-DZHQ0KF6.js} +11 -5
- package/dist/{snapshot-cache-B-LxUahj.d.ts → snapshot-cache-DqK9eDmM.d.ts} +7 -5
- package/dist/solid/index.d.ts +24 -25
- package/dist/solid/index.js +41 -11
- package/dist/solid-start/index.d.ts +52 -0
- package/dist/solid-start/index.js +49 -0
- package/dist/{sources-BdOYLw9j.d.ts → sources-BV9j0DeZ.d.ts} +11 -13
- package/dist/{sources-4p0-4xQm.js → sources-vs5dGOsS.js} +147 -142
- package/dist/sql-B5T1IL83.js +55 -0
- package/dist/src-DLfUWFAI.js +1851 -0
- package/dist/ssf/index.d.ts +4 -4
- package/dist/ssf/index.js +440 -106
- package/dist/standard-webhooks-6lg8v_is.d.ts +88 -0
- package/dist/standard-webhooks-DgFFD-Ga.js +228 -0
- package/dist/storage-C2bWdvJH.js +132 -0
- package/dist/{store-DMAWwAJV.js → store-4u_hxjWa.js} +42 -55
- package/dist/{store-options-ByhZq1jV.js → store-options-BqEakYRa.js} +38 -24
- package/dist/{stores-BT2pSCub.js → stores-BbsD4fAR.js} +7 -3
- package/dist/stores-ClZUgeoP.d.ts +30 -0
- package/dist/{stream-D1RXubw2.d.ts → stream-7-xmnf3U.d.ts} +2 -2
- package/dist/{stream-DDpvynSB.js → stream-CvdUS9V1.js} +10 -2
- package/dist/style-DiKWnAvn.js +1 -0
- package/dist/{style-CTm1AXDT.js → style-Uao76wO7.js} +3 -2
- package/dist/subject-BGoM5o8B.js +17 -0
- package/dist/{subject-settings-BwBmCdTR.js → subject-settings-CkEwrw3W.js} +23 -5
- package/dist/supabase/index.d.ts +23 -106
- package/dist/supabase/index.js +5 -4
- package/dist/supabase/middleware.d.ts +13 -6
- package/dist/supabase/middleware.js +16 -11
- package/dist/supabase-9pqI57pW.js +92 -0
- package/dist/{supabase-C0qESOa-.js → supabase-Dxlp8A94.js} +124 -247
- package/dist/supabase-hook-CXcGpHqq.js +1 -0
- package/dist/{supabase-hook-ntETYCQu.js → supabase-hook-DdDQhMlw.js} +1659 -819
- package/dist/{supautils-u8jXTGrt.js → supautils-B6HYL9eL.js} +2 -2
- package/dist/support-session-C6xJQYut.js +158 -0
- package/dist/svelte/Protected.svelte +11 -4
- package/dist/svelte/index.d.ts +7 -7
- package/dist/svelte/index.js +26 -12
- package/dist/svelte/runtime.d.ts +10 -3
- package/dist/svelte/runtime.js +4 -3
- package/dist/svelte/source.d.ts +7 -7
- package/dist/svelte/source.js +5 -5
- package/dist/sveltekit/index.d.ts +51 -0
- package/dist/sveltekit/index.js +27 -0
- package/dist/tanstack-start/index.d.ts +51 -0
- package/dist/tanstack-start/index.js +40 -0
- package/dist/target-Bw37TFNA.js +51 -0
- package/dist/{validity-uOp8UpYn.js → tenancy-DN2QAx9z.js} +6 -42
- package/dist/terminal/index.d.ts +11 -9
- package/dist/terminal/index.js +36 -316
- package/dist/testing/index.d.ts +391 -106
- package/dist/testing/index.js +4574 -1286
- package/dist/testing/saas/index.d.ts +9 -8
- package/dist/testing/saas/index.js +2 -2
- package/dist/testing/saas/permissions.d.ts +1 -1
- package/dist/testing/saas/permissions.js +1 -2
- package/dist/text-BMFRfBqc.js +8 -0
- package/dist/{text-diff-BdkkYSbi.js → text-diff-BQcUapOb.js} +1 -1
- package/dist/thenable-yD7SabqG.js +31 -0
- package/dist/time-DkWjdL98.js +71 -0
- package/dist/{token-CIWxJOCh.js → token-BLZcjQgt.js} +15 -6
- package/dist/tool-refusal-CMLN5xhn.js +41 -0
- package/dist/trpc/index.d.ts +12 -9
- package/dist/trpc/index.js +18 -17
- package/dist/{types-DbjRN55U.d.ts → types-5Ut_pbKy.d.ts} +141 -13
- package/dist/types-BNORmDM8.d.ts +172 -0
- package/dist/types-BjV-fqcA.d.ts +57 -0
- package/dist/{types-QCuYJh4q.d.ts → types-BqAKcRNW.d.ts} +2 -1
- package/dist/types-C8-KNsFf.d.ts +107 -0
- package/dist/{types-aqOHTHcp.d.ts → types-CCpc2Dcv.d.ts} +77 -17
- package/dist/{types-Dnemwd77.d.ts → types-CZTnPFzM.d.ts} +50 -11
- package/dist/{types-C97IskBw.d.ts → types-CdlxsYQh.d.ts} +23 -11
- package/dist/{types-DxiCGmmd.d.ts → types-ClZaWzfb.d.ts} +9 -6
- package/dist/types-DX0LaKTb.d.ts +157 -0
- package/dist/types-DiQjupG2.d.ts +332 -0
- package/dist/types-DqzsnlSA.js +67 -0
- package/dist/types-_sHy8BQf.d.ts +65 -0
- package/dist/types-fbZ7JEXr.d.ts +89 -0
- package/dist/types-tIgIeIKJ.d.ts +66 -0
- package/dist/unplugin/index.d.ts +1 -1
- package/dist/unplugin/index.js +2 -2
- package/dist/usage-CHOtwL19.js +1 -0
- package/dist/usage-DHtE8j4E.js +32 -0
- package/dist/{usage-ItS7nLcl.js → usage-T5mXq98v.js} +56 -13
- package/dist/{validation-error-Ba264OaJ.d.ts → validation-error-Bg5Tpluk.d.ts} +1 -1
- package/dist/{validation-error-CnjZ4t92.js → validation-error-ZfUHbKvf.js} +1 -1
- package/dist/validity-CZ7-y0Cj.js +79 -0
- package/dist/{verifier-BxBo6I44.js → verifier-DHuh-okk.js} +68 -21
- package/dist/verify-DbCL9bnB.js +1770 -0
- package/dist/{version-BvacVTZR.js → version-CEbQ5UeM.js} +4 -3
- package/dist/version-DXkYUISx.js +1 -0
- package/dist/{views-Soe7bGSC.js → views-DTIIO-oQ.js} +41 -2
- package/dist/{permissions-DttnqqxC.d.ts → vocabulary-D6lPdb4Z.d.ts} +120 -29
- package/dist/vue/index.d.ts +25 -21
- package/dist/vue/index.js +25 -9
- package/dist/waiter-BGUtGYoU.js +67 -0
- package/dist/waiter-CKy4MQSY.d.ts +36 -0
- package/dist/watch-Cb-NcaUw.js +1 -0
- package/dist/{watch-DVBJ9cBq.js → watch-CszoTJMq.js} +47 -8
- package/dist/webmcp/index.d.ts +2 -2
- package/dist/webmcp/index.js +13 -37
- package/dist/where-scope-Dv-WTP-w.js +306 -0
- package/dist/who-can-D0QoYbEb.js +364 -0
- package/dist/wire-delegation-Cv3W2eaM.js +36 -0
- package/dist/wire-denial-C7PaVYEM.js +27 -0
- package/dist/workflow/index.d.ts +5 -0
- package/dist/workflow/index.js +223 -0
- package/dist/workflow-sdk/index.d.ts +56 -0
- package/dist/workflow-sdk/index.js +78 -0
- package/dist/wrap-Bd1Tejur.d.ts +15 -0
- package/dist/wrap-gX61EtpB.js +27 -0
- package/package.json +140 -41
- package/schemas/access-event-v1.json +118 -0
- package/schemas/approval-request-v1.json +34 -1
- package/schemas/approval-signature-v1.json +94 -0
- package/schemas/automation-event-v1.json +113 -0
- package/schemas/capability-v1.json +84 -0
- package/schemas/catalog-event-v1.json +87 -0
- package/schemas/catalog-v1.json +129 -12
- package/schemas/cli-commands-v1.json +164 -0
- package/schemas/cli-result-v1.json +96 -0
- package/schemas/cloud-api-v1.json +5407 -0
- package/schemas/connector-v1.json +408 -0
- package/schemas/credential-event-v1.json +81 -0
- package/schemas/credential-v1.json +134 -0
- package/schemas/decision-event-v1.json +288 -0
- package/schemas/delivery-event-v1.json +117 -0
- package/schemas/directory-event-v1.json +85 -0
- package/schemas/evidence-export-v1.json +54 -0
- package/schemas/membership-event-v1.json +93 -0
- package/schemas/policy-v1.json +68 -0
- package/schemas/pushed-policy-v1.json +236 -0
- package/schemas/run-v1.json +162 -0
- package/schemas/snapshot-v1.json +138 -0
- package/schemas/supabase-claims-v1.json +4 -4
- package/schemas/supabase-manifest-v1.json +5 -1
- package/schemas/support-session-v1.json +111 -0
- package/skills/permdock/SKILL.md +16 -12
- package/skills/permdock-agents/SKILL.md +40 -23
- package/skills/permdock-agents/references/operations-automations-chat.md +221 -0
- package/skills/permdock-agents/references/runtimes.md +22 -18
- package/skills/permdock-approvals/SKILL.md +29 -13
- package/skills/permdock-approvals/references/delivery-and-waiters.md +172 -0
- package/skills/permdock-approvals/references/stores-and-resume.md +21 -7
- package/skills/permdock-audit/SKILL.md +3 -3
- package/skills/permdock-credentials/SKILL.md +5 -5
- package/skills/permdock-credentials/references/tokens-keys-links.md +19 -10
- package/skills/permdock-data/SKILL.md +6 -6
- package/skills/permdock-data/references/queries-and-rls.md +13 -8
- package/skills/permdock-data/references/relationships.md +3 -3
- package/skills/permdock-tenancy/SKILL.md +20 -7
- package/skills/permdock-tenancy/references/contacts-and-support-sessions.md +122 -0
- package/skills/permdock-tenancy/references/ownership-and-custom-roles.md +6 -4
- package/skills/permdock-wire/SKILL.md +18 -12
- package/skills/permdock-wire/references/adapters.md +146 -50
- package/skills/permdock-wire/references/policy-options.md +15 -6
- package/skills/permdock-wire/references/presets-and-recipes.md +58 -0
- package/dist/approvals-DbYAuNkG.js +0 -224
- package/dist/arazzo-C9chp3ST.js +0 -54
- package/dist/bind-C_ob3LKa.d.ts +0 -25
- package/dist/catalog-CRW5Mx_B.js +0 -2
- package/dist/catalog-CYj-mX2O.js +0 -48
- package/dist/catalog-DWF5cYqB.js +0 -55
- package/dist/cloud-CfwZ39Rl.js +0 -50
- package/dist/cloud-DWLXO2af.js +0 -118
- package/dist/collect-CHB22Sx4.js +0 -83
- package/dist/collect-DSWOk19R.js +0 -2
- package/dist/commands-BeaGNj_o.js +0 -97
- package/dist/config-ir40y8m0.js +0 -36
- package/dist/create-CMwIl-w2.js +0 -253
- package/dist/delegation-D8pUEuce.js +0 -103
- package/dist/diff-C9RDjjhs.js +0 -48
- package/dist/doctor-Djh6JDEk.js +0 -53
- package/dist/doctor-DvH0IQff.js +0 -1194
- package/dist/doctor-collect-CjdO0wQX.js +0 -665
- package/dist/duration-xK2jGFVc.js +0 -25
- package/dist/errors-CWfoNg9N.js +0 -47
- package/dist/errors-DRnGZHzF.js +0 -30
- package/dist/evaluation-items-CfCCejQd.js +0 -40
- package/dist/expiry-BoBJ8Tvc.js +0 -7
- package/dist/fields-BMl1RxoX.js +0 -26
- package/dist/grantee-Cv5Sogfv.js +0 -602
- package/dist/headers-BZSDieNT.js +0 -6
- package/dist/headers-Cf-zWvsO.d.ts +0 -6
- package/dist/ids-DC4V4CFu.js +0 -18
- package/dist/index-0nNqHspG.d.ts +0 -6
- package/dist/index-BNc7Wer_.d.ts +0 -483
- package/dist/index-C7Tw7MaW.d.ts +0 -201
- package/dist/instance-options-Dhhcnm3y.js +0 -22
- package/dist/load-DwJUafYN.js +0 -2
- package/dist/manifest-iaoBXHKc.js +0 -783
- package/dist/map-AcxK9o_D.js +0 -147
- package/dist/may-use-DndQ5tQ-.js +0 -77
- package/dist/openapi-CQTsgKoB.js +0 -164
- package/dist/permdock-CbgvLnuo.js +0 -3479
- package/dist/permissions-DVoF6dMt.js +0 -662
- package/dist/policy-xyWg7QdR.js +0 -1122
- package/dist/powersync-CrBX8hY6.js +0 -2
- package/dist/powersync-DE19b9dT.js +0 -62
- package/dist/protected-BdnIEFkY.d.ts +0 -27
- package/dist/protected-xAr24FEC.d.ts +0 -40
- package/dist/relations-cjOL01hZ.js +0 -505
- package/dist/rls-P-BeBx0n.js +0 -238
- package/dist/rls-graph-CHwx0V8q.js +0 -435
- package/dist/rls-ownership-DcYlZ09B.js +0 -843
- package/dist/rls-shared-Cewd_-zI.js +0 -495
- package/dist/rls-verify-CMv9H3wV.js +0 -746
- package/dist/run-BWc9qqvE.js +0 -174
- package/dist/server-DlZ28Oz5.js +0 -268
- package/dist/sink-B1fZzIMf.d.ts +0 -104
- package/dist/sink-Bd9PSyfq.js +0 -141
- package/dist/skills-C5CbhhpG.js +0 -56
- package/dist/snapshot-Enxd0n-s.js +0 -975
- package/dist/sql-xdg5f4Fi.js +0 -27
- package/dist/src-BDwEZWrZ.js +0 -568
- package/dist/store-jOBYpiXC.d.ts +0 -36
- package/dist/stores-C0IMQrGI.d.ts +0 -52
- package/dist/subject-C6dLxqHU.js +0 -24
- package/dist/supabase-config-Bj3iDH2I.js +0 -472
- package/dist/supabase-hook-gd3s8bxB.js +0 -2
- package/dist/supabase-s4o9s1O1.js +0 -83
- package/dist/thenable-okc2xDsj.js +0 -10
- package/dist/types-BnIEz1sU.d.ts +0 -48
- package/dist/types-Bvc4oWT6.d.ts +0 -9
- package/dist/types-CKNSyIf_.d.ts +0 -52
- package/dist/types-Cq_H8N_X.d.ts +0 -197
- package/dist/usage-B0YuTQRO.js +0 -42
- package/dist/usage-DorAJiU_.js +0 -2
- package/dist/vocabulary-DA8a8zif.d.ts +0 -56
- package/dist/watch-BwxLTYMZ.js +0 -2
|
@@ -5,14 +5,19 @@ Owning pages: [JWT adapter](https://permdock.com/docs/adapters/jwt), [authentica
|
|
|
5
5
|
## Access tokens — `permdock/jwt`
|
|
6
6
|
|
|
7
7
|
```ts
|
|
8
|
-
import {
|
|
8
|
+
import { jwtSubjectResolver } from "permdock/jwt";
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
// One resolver per issuer for the life of the process: it caches Discovery and the JWKS.
|
|
11
|
+
const resolve = jwtSubjectResolver({
|
|
11
12
|
discovery: "https://issuer.example.com",
|
|
12
13
|
audience: "https://api.example.com",
|
|
13
14
|
});
|
|
15
|
+
|
|
16
|
+
const subject = await resolve(token, request);
|
|
14
17
|
```
|
|
15
18
|
|
|
19
|
+
`subjectFromJwt(token, options)` takes the same options but builds a new verifier per call, so each call fetches Discovery and the JWKS again. Use it for a one-off check such as a CLI or a test.
|
|
20
|
+
|
|
16
21
|
- Prefer `discovery: '<issuer>'` over a hand-copied `jwks` URL. Set `audience` to the resource identifier. Write algorithms as `Ed25519`, `ES256` or `PS256`; never `EdDSA`, `none` or `RSA1_5`.
|
|
17
22
|
- Keep the default `accept: 'access-token'`, so an ID token never authorises an API call.
|
|
18
23
|
- `jose` is an optional peer. A failure is the anonymous subject, never a throw; the cause is reported through `on('auth')`. A claimed `act` chain that does not nest is anonymous with cause `invalid-chain`.
|
|
@@ -34,7 +39,9 @@ const decision = await decideCredential(
|
|
|
34
39
|
tenant: "o_1",
|
|
35
40
|
roles: ["developer"],
|
|
36
41
|
permissions: [permissions.repo.read],
|
|
37
|
-
expiresAt:
|
|
42
|
+
expiresAt: Temporal.Now.instant()
|
|
43
|
+
.add({ hours: 30 * 24 })
|
|
44
|
+
.toString(), // RFC 3339
|
|
38
45
|
},
|
|
39
46
|
{ settings },
|
|
40
47
|
);
|
|
@@ -51,27 +58,28 @@ if (decision.outcome === "granted") {
|
|
|
51
58
|
|
|
52
59
|
- Guard the endpoint that creates keys with its own permission (`apiKey.create`).
|
|
53
60
|
- Every key lists at least one permission, with no upper bound. A `user` key is its owner's live rights narrowed to the key, and with `tenant` to the owner's memberships inside that tenant and no global role (never put a tenant id in `ids`, which holds resource ids); a `service` key is a `kind: 'service'` principal whose tenant, roles and permissions must be within the creator's `assignableRoles` / `assignablePermissions` (otherwise `exceeds-creator`). A key cannot mint keys.
|
|
54
|
-
- Tenant rules come from a `SettingsSource` (`
|
|
61
|
+
- Tenant rules come from a `SettingsSource` (`memorySettingsSource({ o_1: { credentials: { maxTtl, kinds, approval, allowNoExpiry } } })`). A key without `expiresAt` needs `allowNoExpiry`. `approval: true` makes creation `approval-required`; after approval, call `decideCredential` again with `approved: token`.
|
|
55
62
|
- Store only `hashApiKey(key)`. The key is `pdk_<id>_<secret><checksum>`; `parseApiKey` rejects a bad checksum before any lookup.
|
|
63
|
+
- A feed is `kind: 'feed'` with `on: { resource, id }`, `roles` and `permissions`. `generateFeedKey` returns `pdfeed_<id>_<secret><checksum>`, all lowercase. `expiresAt` is optional. `subjectFromApiKey(key, { kinds: ['feed'] })` accepts feed keys only. A feed cannot approve or create a key. See [feeds](https://permdock.com/docs/concepts/credentials#feeds).
|
|
56
64
|
|
|
57
65
|
Resolve keys on each request:
|
|
58
66
|
|
|
59
67
|
```ts
|
|
60
68
|
import { apiKeyVerifier, subjectFromApiKey } from "permdock/server";
|
|
61
69
|
|
|
62
|
-
const
|
|
70
|
+
const keys = {
|
|
63
71
|
verifier: apiKeyVerifier({ find: (id) => findKeyRow(id) }), // returns { credential, hash }
|
|
64
72
|
permissions,
|
|
65
|
-
|
|
73
|
+
principals, // a PrincipalSource: the live owner of a user key
|
|
66
74
|
revoked: (id) => isRevoked(id),
|
|
67
75
|
settings,
|
|
68
76
|
sink,
|
|
69
|
-
}
|
|
77
|
+
};
|
|
70
78
|
|
|
71
|
-
const subject = await
|
|
79
|
+
const subject = await subjectFromApiKey(bearer, { ...keys, tenant });
|
|
72
80
|
```
|
|
73
81
|
|
|
74
|
-
Never pass a `memberships` source to `createPermDock` for a service-key subject: its one membership comes from the credential. `
|
|
82
|
+
Never pass a `memberships` source to `createPermDock` for a service-key subject: its one membership comes from the credential. `memoryCredentialStore()` is an in-process store for tests.
|
|
75
83
|
|
|
76
84
|
When the backend then queries Postgres for the key, put the key's permission keys (and, for a service key, its tenant and roles) in an `api_key` claim and set `rls.apiKeys` in `permdock.config.ts`: the generated helpers cap every allow at the key's permissions and hold a key that names a tenant to that tenant, so RLS agrees with `can()`. The key itself never goes to the database.
|
|
77
85
|
|
|
@@ -114,7 +122,8 @@ const subject = await subjectFromCapability(token, {
|
|
|
114
122
|
|
|
115
123
|
- The result is a `kind: 'link'` principal with one `{ on, roles, via: 'link' }` membership. It never holds a scope or global role.
|
|
116
124
|
- `linkPolicy` returns the tenant's `LinkPolicy` (`maxLifetime`, `redeemers`, `once`). Passing the same policy to `signCapability` refuses to mint a link the resolver would refuse.
|
|
117
|
-
- Never an unguessable id without expiry, and never a security-definer RPC
|
|
125
|
+
- Never an unguessable id without expiry, and never a security-definer RPC that reads the guest page's rows.
|
|
126
|
+
- The one exception is resolving a stored link. When links live in better-supabase's share-links block, its `resolve_share_link` is `security definer` and reads only the link table. `subjectFromShareLink(token, { shareLinks, links, ttl })` from `permdock/better-supabase` turns the row into the same link subject. The page's rows are then read through `exchangeCapability` and RLS, as for a signed link. `links` maps each `subjectType` to a resource and maps the row's role to PermDock roles. An unmapped type or role, and a revoked or expired row, are anonymous. A link pinned to a version is refused, because a capability reads the current rows; read it through better-supabase's `versions.getShared(token)`. See [row-backed links](https://permdock.com/docs/concepts/capabilities#row-backed-links).
|
|
118
127
|
|
|
119
128
|
With Supabase, let RLS serve the link through an exchange, never the link token itself:
|
|
120
129
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: permdock-data
|
|
3
|
-
description: Enforces PermDock permissions in database queries and Postgres row-level security. Use when listing rows with permdock.where() and filter(), compiling conditions with toWhere from permdock/drizzle, permdock/prisma or permdock/kysely, guarding Convex functions with permdock/convex, generating or verifying RLS with permdock rls generate, verify or migrate, modelling folders, sub-teams or reporting lines with parent and relation() through a RelationSource,
|
|
3
|
+
description: Enforces PermDock permissions in database queries and Postgres row-level security. Use when listing rows with permdock.where() and filter(), compiling conditions with toWhere from permdock/drizzle, permdock/prisma or permdock/kysely, guarding Convex functions with permdock/convex, generating or verifying RLS with permdock rls generate, verify or migrate, modelling folders, sub-teams or reporting lines with parent and relation() through a RelationSource, prefetchRelations or whoCan, or proving query and database parity with testOrmParity and testRlsParity. Also for doctor PD027, PD028, PD031 and PD032.
|
|
4
4
|
license: MIT
|
|
5
5
|
metadata:
|
|
6
6
|
author: ScaleDockHQ
|
|
@@ -32,13 +32,13 @@ The same portable conditions decide `can()` in memory, narrow list queries throu
|
|
|
32
32
|
|
|
33
33
|
1. **Narrow list queries.** Compile `permdock.where(permission)` with the ORM's `toWhere`, then run `filter` and `pick` on the rows. -> [references/queries-and-rls.md](references/queries-and-rls.md)
|
|
34
34
|
✓ A user with no grant gets an empty list, and the query compiles without `non-portable-condition`.
|
|
35
|
-
2. **Model hierarchies, when the product has them.** Give the resource a `parent`, declare the relations principals hold, add `restricted` where rows must not inherit, and grant with `relation(resource, name, { through: 'parent' })`. Pass a `RelationSource` as `relations` and call `
|
|
35
|
+
2. **Model hierarchies, when the product has them.** Give the resource a `parent`, declare the relations principals hold, add `restricted` where rows must not inherit, and grant with `relation(resource, name, { through: 'parent' })`. Pass a `RelationSource` as `relations` and call `prefetchRelations` before `can` / `filter` when it is async. Use `whoCan` for share dialogs. -> [references/relationships.md](references/relationships.md)
|
|
36
36
|
✓ `permdock doctor --only graph` is clean, and a viewer of a parent folder can read a child document.
|
|
37
37
|
3. **Generate or adopt RLS.** Run `permdock rls generate` from the policy, or keep SQL as the authority with `sqlFunction` twins and `rls.functions`, or move hand-written helpers with `permdock rls migrate`. -> [references/queries-and-rls.md](references/queries-and-rls.md#generate-and-verify-rls)
|
|
38
38
|
✓ `permdock rls generate --check` passes in CI.
|
|
39
|
-
4. **Verify against a real database.** Run `permdock rls verify
|
|
39
|
+
4. **Verify against a real database.** Run `permdock rls verify` with fixtures, `--tree` for graph grants, and `--introspect` against a deployed database.
|
|
40
40
|
✓ Every verify run exits `0`.
|
|
41
|
-
5. **Prove parity in tests.** Add `
|
|
41
|
+
5. **Prove parity in tests.** Add `testOrmParity(policy, scenarios, { run, id })` for each list endpoint and `testRlsParity(policy, options)` for RLS tables, from `permdock/testing` ([testing adapter](https://permdock.com/docs/adapters/testing)). Run `testRelationSource` on a custom `RelationSource`.
|
|
42
42
|
✓ The query and the database return exactly the rows `filter()` keeps.
|
|
43
43
|
|
|
44
44
|
## Verify before done
|
|
@@ -48,10 +48,10 @@ The same portable conditions decide `can()` in memory, narrow list queries throu
|
|
|
48
48
|
- [ ] No `opaque` RLS grant wraps a named helper; use `sqlFunction` with a twin and map it in `rls.functions`.
|
|
49
49
|
- [ ] No condition the database must enforce reads `context.*` (PD027); on Supabase, `supabase.hook.attrs` lists only server-owned values (PD028).
|
|
50
50
|
- [ ] Graph declarations are walked by a grant (PD031) and nameable in SQL (PD032).
|
|
51
|
-
- [ ] Every list endpoint has an `
|
|
51
|
+
- [ ] Every list endpoint has an `testOrmParity` run, every RLS table an `testRlsParity` or `rls verify --db-url-stdin` run, and a custom `RelationSource` passes `testRelationSource`.
|
|
52
52
|
|
|
53
53
|
## Reference index
|
|
54
54
|
|
|
55
55
|
- [references/queries-and-rls.md](references/queries-and-rls.md): `toWhere` per ORM, `checkRow`, `withSubject`, Convex, `rls generate` / `import` / `verify` / `migrate` flags and config, field views, SQL as the authority.
|
|
56
|
-
- [references/relationships.md](references/relationships.md): `parent`, relation kinds, `restricted`, `links`, `relation(..., { through })`, `RelationSource`, `
|
|
56
|
+
- [references/relationships.md](references/relationships.md): `parent`, relation kinds, `restricted`, `links`, `relation(..., { through })`, `RelationSource`, `prefetchRelations`, `whoCan`, the closure table and `--tree`.
|
|
57
57
|
- Docs: [conditions](https://permdock.com/docs/concepts/conditions), [relationships](https://permdock.com/docs/concepts/relationships), [RLS adapter](https://permdock.com/docs/adapters/rls), [`permdock rls`](https://permdock.com/docs/cli/rls), [Postgres RLS](https://permdock.com/docs/standards/postgres-rls).
|
|
@@ -31,11 +31,11 @@ pnpm exec permdock rls generate --target sql --dialect supabase --out migrations
|
|
|
31
31
|
pnpm exec permdock rls generate --target drizzle --dialect supabase
|
|
32
32
|
pnpm exec permdock rls import --sql migrations/rls.sql --out src/permissions.generated.ts
|
|
33
33
|
pnpm exec permdock rls verify --fixtures rls.fixtures.json
|
|
34
|
-
pnpm exec permdock rls verify --db
|
|
35
|
-
pnpm exec permdock rls verify --introspect
|
|
34
|
+
printf %s "$DATABASE_URL" | pnpm exec permdock rls verify --db-url-stdin --fixtures rls.fixtures.json
|
|
35
|
+
pnpm exec permdock rls verify --introspect
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
-
- List the scope instances a permission reaches in process with `permittedIds(
|
|
38
|
+
- List the scope instances a permission reaches in process with `permdock.permittedIds(permission, scope, { within })`, never a loop of `can` over memberships with a made-up row.
|
|
39
39
|
- Generated policies call `permdock_has('<key>')` and one `permitted_<scope>_ids('<key>')` per declared scope, which Postgres runs once per statement. Hand-written policies that only need membership (an organization switcher) call `member_<scope>_ids()` instead of inventing a permission key. Trusted SQL that acts for a stored user (a job, a trigger, a deferred approval) calls `permdock_has_for(p_user, '<key>')` and `permitted_<scope>_ids_for(p_user, '<key>')` in `database` mode, or the generated `permitted_<resource>_rows_for`, never a hand-written function that rewrites `request.jwt.claims`. With `rls.rowHelpers`, a hand-written policy or function that needs the rows a permission reaches through the graph calls `permitted_<resource>_rows('<permission key>')` instead of restating closure walks, link hops and `includes`. A table's own `select` or `insert` policy calls the row-valued `permitted_<resource>_row(<table>, '<permission key>')` instead, so `insert ... returning` sees the new row. Hand-written policies and functions that apply no row condition call `permitted_<scope>_ids_by_permission('<permission key>')` or `permdock_has_permission('<permission key>')` instead of spelling a positional `#n` grant key. A screen or RPC that checks many permissions on one organization calls `permitted_<scope>_permission_keys(<id>)` once (or `(<id>, array['<key>', ...])` for a few keys) instead of one helper call per key. A role editor or admin screen lists a role's keys with `permdock_role_permissions(p_role, p_scope, p_tenant, p_scope_id)` (declared roles, and custom roles of the caller's tenant in `database` mode) and the catalog with `permdock_permission_keys()` (`permdock_permission_keys('<scope>')` for the keys a role on that scope can hold), instead of joining `role_permissions` and the custom-role tables by hand; server code reads any tenant's roles with `permdock_trusted_role_permissions`, granted through `rls.trustedReaders`. Set `--tenant-type` (or `rls.tenantType`, `rls.scopeTypes`) to the scope columns' types.
|
|
40
40
|
- `--authorize database` (default) reads the membership tables per statement; map each under `rls.memberships.scopes.<scope>` with `columns` for its id and its ancestors', and `via` so roles with `for` apply: a column name, or `{ value: 'staff' }` when every row has that kind (never add a generated column for it). `--authorize jwt` reads token claims and stays stale until the token refreshes; keep the token lifetime at 3600 seconds or less (PD019).
|
|
41
41
|
- The helpers narrow to the tenant claim (`rls.tenantClaim`) whenever the token carries one; the Supabase hook writes it from `activeFrom`. When the app takes the tenant from the URL and one session spans tenants, set `rls.tenants: 'all'` so the database admits every member tenant, and keep filtering each query by the URL's tenant.
|
|
@@ -43,20 +43,25 @@ pnpm exec permdock rls verify --introspect --db $DATABASE_URL
|
|
|
43
43
|
- `--force` (or `rls.force: true`) only when the app connects as the table owner. Create views over RLS tables `with (security_invoker = true)` (PD022).
|
|
44
44
|
- When read grants set `fields` and clients read tables directly, add `--fields views --revoke-columns`: clients read `<table>_visible`, whose restricted columns are null unless a grant covers them. PD030 names columns still readable.
|
|
45
45
|
- `rls.suspension` drops suspended users' roles and memberships of suspended scope instances, live in both modes. `scopes.<scope>.keep: [permissions.org.restore]` lets members of a suspended instance still hold those permissions (restore, cancel deletion, export) in the helpers, `authorize()`, the token hook and in process, where the membership carries `keep`; `doctor` PD061 lists them.
|
|
46
|
-
- When support or impersonation tokens (an `act` claim) reach the Data API, set `rls.readOnlyActors: true`: each table and write command the policies grant gets a restrictive `{table}_{op}_read_only_actors` policy that refuses the write unless `act.read_only` is `false`. Never hand-write that policy per table.
|
|
46
|
+
- When support or impersonation tokens (an `act` claim) reach the Data API, set `rls.readOnlyActors: true`: each table and write command the policies grant gets a restrictive `{table}_{op}_read_only_actors` policy that refuses the write unless `act.read_only` is `false`. Never hand-write that policy per table. With `--helpers-only`, every hand-written insert, update and delete policy calls `(select permdock.permdock_actor_may_write())`, and `rls verify --introspect` names the ones that do not.
|
|
47
|
+
- To list who holds a permission in one organization (notification fan-out, an approver list), server SQL calls `permitted_<scope>_users('<permission key>', <id>)` in `database` mode, never a hand-written join of memberships and `role_permissions`. No client role may execute it; grant it to the backend's Postgres role.
|
|
47
48
|
- When a backend queries Postgres for a verified API key, set `rls.apiKeys` and put the key in the `api_key` claim (`scopes` as permission keys, `tenant` and `roles` for a key that belongs to a tenant): the helpers cap every allow at the scopes and treat a tenant key without `sub` as a service principal of that tenant. A user key whose claim names a `tenant` is held to that tenant even with `rls.tenants: 'all'`, and `permdock_has` is false for it. Never write a `has_scope` check per policy.
|
|
48
49
|
- On Supabase every generated helper and policy reads the subject through `permdock_user_id()`: the `sub` in `request.jwt.claims`, and null for the empty `sub` of a tenant service key. It ignores the legacy `request.jwt.claim.sub` setting; SQL that impersonates a user sets `request.jwt.claims`. A hand-written policy such a token reaches calls `(select permdock.permdock_user_id())`, never `auth.uid()`, which fails the uuid cast.
|
|
49
50
|
- `rls.assignments` puts a `permdock_assignment` trigger on each mapped membership table, the global-roles table `rls.roles` and each table in `tables`; `ownRole: 'refuse'` (or a map of tables) also refuses a client write to the caller's own row. `doctor` PD064 names a guarded table no migration puts the trigger on.
|
|
50
51
|
- SQL that assigns roles calls `permdock_can_assign_any(role, tenant, scope, scope_id)`, which covers declared and custom roles; SQL acting later for a stored user (an invitation accept re-checking the inviter) calls `permdock_can_assign_any_for(user, ...)`. `permdock supabase inspect` lists both in the manifest's `rls.helpers`.
|
|
51
|
-
- Index suggestions (the `indexes` part of `--split`, or `index suggestion` warnings) cover only the columns the policies and helpers look rows up by.
|
|
52
|
+
- Index suggestions (the `indexes` part of `--split`, or `index suggestion` warnings) cover only the columns the policies and helpers look rows up by. Pipe the URL to `generate --db-url-stdin` so it leaves out indexes an existing index already leads with and columns the table lacks; never hand-copy a suggestion that duplicates an existing composite index.
|
|
52
53
|
- Never emit `service_role`. Fixtures may carry `memberships`, `tenant` and `customRoles`.
|
|
53
54
|
|
|
54
55
|
## SQL as the authority
|
|
55
56
|
|
|
56
|
-
When the database already holds the rules, skip `generate`. Map each helper in `rls.functions`, write `sqlFunction` twins in the policy, and fail CI on `rls verify --db`. `--inline-functions` inlines the twin for targets that cannot call a SQL function.
|
|
57
|
+
When the database already holds the rules, skip `generate`. Map each helper in `rls.functions`, write `sqlFunction` twins in the policy, and fail CI on `rls verify --db-url-stdin`. `--inline-functions` inlines the twin for targets that cannot call a SQL function.
|
|
57
58
|
|
|
58
|
-
When hand-written policies call the app's own helpers (`org_ids_with_permission`, `has_org_permission`), map each under `rls.migrate.helpers` with its `form` (`ids`, `row`, `scoped`, `global`, `membership`) and the legacy keys under `rls.migrate.keys` or `prefixes`. Run `permdock rls migrate`, read the skipped calls, and apply
|
|
59
|
+
When hand-written policies call the app's own helpers (`org_ids_with_permission`, `has_org_permission`), map each under `rls.migrate.helpers` with its `form` (`ids`, `row`, `scoped`, `global`, `membership`) and the legacy keys under `rls.migrate.keys` or `prefixes`. Run `permdock rls migrate --dry-run`, read the skipped calls, and apply by running it without `--dry-run`. Generate with `--helpers-only` while the policies stay hand-written. A `row-conditions` skip means that table's policy should be generated, not migrated.
|
|
60
|
+
|
|
61
|
+
When the old helpers read a permissions table that triggers keep in step (`user_permissions`), list it under `rls.migrate.tables`. `permdock rls migrate` then reports its refresh triggers and every policy, view or function that still reads it, and `permdock doctor` reports PD066 while the migrations still create it. Rewrite each reader, then run `permdock rls migrate --retire-out <migrations dir>/`, which exits `1` and writes nothing while a reader remains. Never hand-write the drop with `cascade` ([retire a materialised table](https://permdock.com/docs/cli/rls#retire-a-materialised-table)).
|
|
62
|
+
|
|
63
|
+
Triggers the app owns on generated tables (an audit trigger, a notification) go in `rls.triggers` or `rls.audit`, so they live in the generated files; never in a migration that patches the generated tables afterwards ([triggers and audit](https://permdock.com/docs/cli/rls#triggers-and-audit)).
|
|
59
64
|
|
|
60
65
|
## Attributes RLS cannot see
|
|
61
66
|
|
|
62
|
-
A grant condition on `context.*` cannot be enforced by the database (PD027). Compare with a server-set `principal.claims.*` claim instead, never one from `user_metadata`. On Supabase, `supabase.hook.attrs` lists only server-owned columns or `app_metadata.<key>` entries clients cannot update (PD028). A check that must refuse a sign-in (single sign-on enforcement, a locked account) goes in `supabase.hook.before` as a schema-qualified `(event jsonb) returns jsonb` function the generated hook calls first; never a second hook or a hand edit of the generated one. The hook, declarative schemas
|
|
67
|
+
A grant condition on `context.*` cannot be enforced by the database (PD027). Compare with a server-set `principal.claims.*` claim instead, never one from `user_metadata`. On Supabase, `supabase.hook.attrs` lists only server-owned columns or `app_metadata.<key>` entries clients cannot update (PD028). A check that must refuse a sign-in (single sign-on enforcement, a locked account) goes in `supabase.hook.before` as a schema-qualified `(event jsonb) returns jsonb` function the generated hook calls first; never a second hook or a hand edit of the generated one. Declarative schemas (`--split`) default to pg-delta (Declarative Schemas 2.0); `supabase.schemas: 'migra'` or `'migrations'` opts out ([schema workflow](https://permdock.com/docs/cli/rls#schema-workflow)). The hook, declarative schemas and `tableMembershipSource` / `junctionMembershipSource` membership sources are on the [Supabase adapter](https://permdock.com/docs/adapters/supabase) and [Supabase hook](https://permdock.com/docs/adapters/supabase-hook) pages.
|
|
@@ -49,18 +49,18 @@ Add `requires: permissions.file.read` to a graph grant to count a share only whe
|
|
|
49
49
|
|
|
50
50
|
## Load
|
|
51
51
|
|
|
52
|
-
Pass `relations:
|
|
52
|
+
Pass `relations: memoryRelationSource(permissions, { rows, edges })` in tests, or a `RelationSource` (`ancestors`, `related`) over the app's tables, to `createPermDock`. Evaluation stays synchronous; with an async source, call `await permdock.prefetchRelations(permission, rows)` before `can` or `filter`. Without a source, or with one that threw, graph checks deny with `relation-unavailable`; a chain past `depth` denies with `relation-depth`. Answers are cached per instance, never at module level.
|
|
53
53
|
|
|
54
54
|
## Share dialogs
|
|
55
55
|
|
|
56
|
-
`const { holders, complete } = await
|
|
56
|
+
`const { holders, complete } = await whoCan(permdock, permission, row)` lists who holds a permission on one object and how. Show `complete: false` honestly: some holders could not be listed. `whoCan` never grants.
|
|
57
57
|
|
|
58
58
|
## In Postgres
|
|
59
59
|
|
|
60
60
|
`permdock rls generate` emits the `permdock_closure` table, its statement triggers and `permitted_<resource>_ids(relation)` helpers. Snapshots carry no graph: graph grants are `portable: false` on the client and go to the decision endpoint. Check with:
|
|
61
61
|
|
|
62
62
|
```bash
|
|
63
|
-
pnpm exec permdock rls verify --tree
|
|
63
|
+
pnpm exec permdock rls verify --tree
|
|
64
64
|
pnpm exec permdock doctor --only graph
|
|
65
65
|
```
|
|
66
66
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: permdock-tenancy
|
|
3
|
-
description: Models tenants, memberships and
|
|
3
|
+
description: Models tenants, memberships, contacts and support sessions in PermDock. Use when declaring named scopes (organizations, workspaces, customers inside an organization), loading memberships through a MembershipSource, putting a role on one scope, keeping an owner per organization with min, max or transferOnly, controlling who hands out which role with assigns and for, checking a membership change with decideRoleChange, letting tenant admins define custom roles with customRoles and validateCustomRole, giving external customer contacts portal access as kind contact principals found through a PrincipalSource and principalByIdentity, letting support staff view the app as a customer with decideSupportSession, signSupportSession and subjectFromSupportSession, or fixing doctor PD023, PD025 or PD026 and denials such as tenant-mismatch, last-holder, not-assignable-by or reason-required.
|
|
4
4
|
license: MIT
|
|
5
5
|
metadata:
|
|
6
6
|
author: ScaleDockHQ
|
|
@@ -19,14 +19,18 @@ A policy declares its scopes in order (an organization, the customers inside it)
|
|
|
19
19
|
- The membership kinds (`via`): staff, contact, guest, partner, support.
|
|
20
20
|
- Which role manages each scope, who may hand out which role, and which roles a contact or guest may never hold.
|
|
21
21
|
- Whether tenant admins define their own roles.
|
|
22
|
+
- Whether external people (customer contacts, portal users) are accounts in the auth provider or rows in the app's own contacts table, and whether email delivery or a chat bot must reach them.
|
|
23
|
+
- Whether support staff view the app as a customer, who approves that, and where the app records a session ending early.
|
|
22
24
|
|
|
23
25
|
## Invariants
|
|
24
26
|
|
|
25
|
-
1. Memberships and custom roles come only from a `subjectFrom*` provider, the policy's `subject` or `context` function, a `MembershipSource` or a `
|
|
27
|
+
1. Memberships and custom roles come only from a `subjectFrom*` provider, the policy's `subject` or `context` function, a `MembershipSource` or a `CustomRoleSource`. Never from a request body, an unsigned header, a model argument or a CLI flag.
|
|
26
28
|
2. A requested tenant with no matching membership is no tenant, never a default one. Team and tenant ids are identifiers, never display names.
|
|
27
29
|
3. No cascade. An organization owner sees no customer portal unless they also hold a contact membership, and a global role never reaches scoped rows. An instance check without a row answers from first-scope memberships only; a nested membership needs the row or `permdock.team(id)`. A collection check (`create`, `list`) still answers from any membership in the tenant; for a staff-only guard pass `{ scope: '<first scope>' }` as the decide option.
|
|
28
30
|
4. Role rules live on the role (`min`, `assigns`, `for`), not in handlers. `decideRoleChange` takes the actor from the instance and never writes.
|
|
29
31
|
5. A custom role never exceeds the ceiling of declared `assignable` roles in its scope. Its grants carry no condition, approval or limit of their own; a `level` picks a condition the resource declares in code.
|
|
32
|
+
6. A `kind: 'contact'` principal keeps only its `{ on }` resource memberships and is read-only unless a role's `for` lists `'contact'`; a scope role such as the `customer`-scope `contact` role below never reaches it. It never approves or creates keys. An email address or chat author with no identity link the app wrote is no principal, never a guest.
|
|
33
|
+
7. A support session keeps the customer as principal and the staff member as a `support` actor, needs a reason and, by default, an approval by someone else, and is checked live on every request. A support actor never approves.
|
|
30
34
|
|
|
31
35
|
## Workflow
|
|
32
36
|
|
|
@@ -58,11 +62,15 @@ A policy declares its scopes in order (an organization, the customers inside it)
|
|
|
58
62
|
|
|
59
63
|
An organisation role that reaches only the principal's teams is an organisation role with `where: { team_id: { in: principal.claims.team_ids } }`, not a team role. On Supabase, write the claim with `supabase.hook.claims` ([team reach](https://permdock.com/docs/concepts/tenancy#organisation-roles-with-team-reach)).
|
|
60
64
|
|
|
61
|
-
4. **Put ownership on the roles.** Set `min: 1` on the role that manages each scope, `assigns` for who hands out what, `for: ['staff']` on admin-like roles, and `meta.audience` for the surface. Every server action that changes a membership calls `
|
|
65
|
+
4. **Put ownership on the roles.** Set `min: 1` on the role that manages each scope, `assigns` for who hands out what, `for: ['staff']` on admin-like roles, and `meta.audience` for the surface. Every server action that changes a membership calls `decideRoleChange(permdock, ...)` with the target's current membership and the holder count before it writes. -> [references/ownership-and-custom-roles.md](references/ownership-and-custom-roles.md)
|
|
62
66
|
✓ PD026 is clean, and removing the last owner is denied with `last-holder`.
|
|
63
|
-
5. **Custom roles, when tenant admins define roles.** Keep declared roles small and mark them in `defineRoles` (`editor: { on: 'organization', assignable: true }`). Pass a `
|
|
67
|
+
5. **Custom roles, when tenant admins define roles.** Keep declared roles small and mark them in `defineRoles` (`editor: { on: 'organization', assignable: true }`). Pass a `CustomRoleSource` as `customRoles`, build the editor from `useAssignablePermissions()`, and save through a server action that runs `validateCustomRole`. -> [references/ownership-and-custom-roles.md](references/ownership-and-custom-roles.md#custom-roles)
|
|
64
68
|
✓ PD023 is clean, and saving a role with a permission outside the ceiling is refused.
|
|
65
|
-
6. **
|
|
69
|
+
6. **Contact principals, when external people sign in or receive messages.** Pick the model: a signed-in user with a `via: 'contact'` membership on the `customer` scope, or a `kind: 'contact'` principal with `{ on: { resource, id }, roles }` memberships on the rows it is a party to. Give a contact a write only through a resource role with `for: ['contact']`. Pass a `PrincipalSource` as `principals` (`memoryPrincipalSource({ principals, identities })` in tests, `principalSource` from `permdock/better-supabase` there) and implement `principalByIdentity` over identity links the app writes after a signed-in link flow or a verified address, so [delivery](https://permdock.com/docs/concepts/delivery) and [chat](https://permdock.com/docs/concepts/chat) reach the contact ([contact principals](https://permdock.com/docs/concepts/subject#contact-principals)). -> [references/contacts-and-support-sessions.md](references/contacts-and-support-sessions.md)
|
|
70
|
+
✓ A contact's write without a `for: ['contact']` role denies with `not-delegated`, and an unlinked address resolves to `null`.
|
|
71
|
+
7. **Support sessions, when staff view the app as a customer.** Start each session with `decideSupportSession(staff, { id, principal, reason })`, record an approval by someone else and decide again with `{ approved: token, approvedBy }`. Sign the granted record with `signSupportSession(session, signer)` and resolve each request with `subjectFromSupportSession(token, { verifier, principals, permissions, sessions })` from `permdock/server`, where `sessions` is a `SupportSessionSource` that ends a session early. Send `supportSessionEvent` to the sink on start and end. On better-supabase, pass `supportPolicy` as the `authorize` option of `supportSessions` ([support sessions](https://permdock.com/docs/concepts/support-sessions)). -> [references/contacts-and-support-sessions.md](references/contacts-and-support-sessions.md#support-sessions)
|
|
72
|
+
✓ An empty reason denies with `reason-required`, and a token for a session the source reports `ended` resolves to the anonymous subject.
|
|
73
|
+
8. **Test.** Add `describePolicy` rows for a member of another tenant, a contact reaching an organization row, a contact principal writing without an opt-in role, and a role change that breaks `min` ([scenario testing](https://permdock.com/docs/guides/scenario-testing)). Run `testMembershipSource`, `testCustomRoleSource`, `testPrincipalSource` and `testSupportSessionSource` from `permdock/testing` on custom sources. For list queries and RLS over scoped tables, follow the `permdock-data` skill.
|
|
66
74
|
✓ The tests pass.
|
|
67
75
|
|
|
68
76
|
## Verify before done
|
|
@@ -72,9 +80,14 @@ A policy declares its scopes in order (an organization, the customers inside it)
|
|
|
72
80
|
- [ ] No `assigns` list lets a role appoint a role ranked above it.
|
|
73
81
|
- [ ] Every server action that writes a membership role calls `decideRoleChange` with a `holders` count from the store, never a guess.
|
|
74
82
|
- [ ] Every custom-role save runs `validateCustomRole` and checks `assignablePermissions()`. No single assignable `admin` role makes the ceiling the whole product.
|
|
75
|
-
- [ ] Custom `MembershipSource` and `
|
|
83
|
+
- [ ] Custom `MembershipSource`, `CustomRoleSource`, `PrincipalSource` and `SupportSessionSource` implementations pass `testMembershipSource` / `testCustomRoleSource` / `testPrincipalSource` / `testSupportSessionSource`.
|
|
84
|
+
- [ ] A `kind: 'contact'` principal gets portal access only through `{ on }` memberships, and every write it may make comes from a role whose `for` lists `'contact'`. When contacts query the database directly, their memberships are kept out of the tables the RLS functions read, or writes are gated in the application.
|
|
85
|
+
- [ ] `principalByIdentity` reads only identity links the app wrote after a signed-in link flow or a verified address. No code creates a principal, a guest or a default role for an unlinked address or chat author.
|
|
86
|
+
- [ ] Every support session goes through `decideSupportSession` with a reason, and approval stays on unless the app approves elsewhere; `approvedBy` is never the staff member.
|
|
87
|
+
- [ ] Session requests resolve with `subjectFromSupportSession` and a `SupportSessionSource`, never by signing in as the customer, and `supportSessionEvent` is sent on start and end.
|
|
76
88
|
|
|
77
89
|
## Reference index
|
|
78
90
|
|
|
79
91
|
- [references/ownership-and-custom-roles.md](references/ownership-and-custom-roles.md): role options, `decideRoleChange` input and denial reasons, the SQL objects RLS uses, and the custom-role shape, ceiling and editor.
|
|
80
|
-
-
|
|
92
|
+
- [references/contacts-and-support-sessions.md](references/contacts-and-support-sessions.md): the two contact models, contact principal rules, `PrincipalSource` and identity links for delivery and chat, the support-session start, token, live check and events, `supportPolicy`, and how support sessions differ from `supportAccess`.
|
|
93
|
+
- Docs: [named scopes](https://permdock.com/docs/concepts/scopes), [tenancy](https://permdock.com/docs/concepts/tenancy), [ownership](https://permdock.com/docs/concepts/ownership), [custom roles](https://permdock.com/docs/concepts/custom-roles), [contact principals](https://permdock.com/docs/concepts/subject#contact-principals), [support sessions](https://permdock.com/docs/concepts/support-sessions), [elevated access](https://permdock.com/docs/concepts/elevated-access), [extension interfaces](https://permdock.com/docs/concepts/extension-interfaces).
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# Contact principals and support sessions
|
|
2
|
+
|
|
3
|
+
Owning pages: [contact principals](https://permdock.com/docs/concepts/subject#contact-principals), [support sessions](https://permdock.com/docs/concepts/support-sessions), [extension interfaces](https://permdock.com/docs/concepts/extension-interfaces#principalsource).
|
|
4
|
+
|
|
5
|
+
## Two kinds of contact
|
|
6
|
+
|
|
7
|
+
| Who | Principal | Membership | Reaches |
|
|
8
|
+
| ----------------------------------------------------------- | ----------------- | ----------------------------------------------------------------------- | ------------------------------------------------------------------- |
|
|
9
|
+
| A signed-in app user holding a portal role on a scope | `kind: 'user'` | `{ scope: 'customer', id, within, roles: ['contact'], via: 'contact' }` | What the `customer`-scope role grants |
|
|
10
|
+
| An external person the app stores in its own contacts table | `kind: 'contact'` | `{ on: { resource, id }, roles }` | Only the rows those resource memberships name, read-only by default |
|
|
11
|
+
|
|
12
|
+
A `kind: 'contact'` principal keeps only its `{ on }` memberships, each rewritten to `via: 'contact'`. Its scope memberships, global `roles`, `plans`, membership `entitlements` and `tenant` are dropped, so `permdock.tenants()` is empty and a `customer`-scope role never applies to it. Model a customer contact this way when they are not an account in the app's auth provider, or when they must never see more than the rows they are a party to.
|
|
13
|
+
|
|
14
|
+
## Contact principal rules
|
|
15
|
+
|
|
16
|
+
- Only `{ on }` memberships count. Grant portal access with a role on a resource and a membership on one row of it.
|
|
17
|
+
- An allow reaches a contact only when the permission is read-only (`meta.readOnly`, or the `read` and `list` actions), or the role's `for` lists `'contact'`. Every other allow denies with `not-delegated`. Top-level grants and relation grants never open a write to a contact.
|
|
18
|
+
- A contact never approves, reviews access or accepts an approval delegation: `assertApprover` throws `approver-not-eligible`.
|
|
19
|
+
- A contact never creates a key: `decideCredential` denies with `exceeds-creator` and `detail.creator: 'contact'`.
|
|
20
|
+
- A contact never publishes a policy, a catalog or a hosted document.
|
|
21
|
+
- The narrowing applies to `can()`, `where()`, `snapshot()` and `decideFor()`. Generated RLS does not know the principal kind: when contacts query the database directly, keep their memberships out of the tables the RLS functions read, or gate writes in the application.
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
role("payer", [allow(permissions.invoice.pay)], {
|
|
25
|
+
on: permissions.invoice,
|
|
26
|
+
for: ["contact"], // the only way a contact gets a write
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const contact: Principal = {
|
|
30
|
+
id: "c_42",
|
|
31
|
+
kind: "contact",
|
|
32
|
+
memberships: [{ on: { resource: "invoice", id: "inv_1" }, roles: ["payer"] }],
|
|
33
|
+
};
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Reaching a contact
|
|
37
|
+
|
|
38
|
+
A contact is loaded through the `PrincipalSource` passed as `principals`. It is the one identity lookup for `decideFor`, delivery and chat:
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
type PrincipalSource = {
|
|
42
|
+
principal(
|
|
43
|
+
id: string,
|
|
44
|
+
options?: { tenant?: string },
|
|
45
|
+
): Principal | null | Promise<Principal | null>;
|
|
46
|
+
principalByIdentity?(identity: {
|
|
47
|
+
kind: "email" | "chat";
|
|
48
|
+
provider?: string; // the chat platform; absent for email
|
|
49
|
+
value: string; // the address or the platform user id
|
|
50
|
+
workspace?: string;
|
|
51
|
+
}): Principal | null | Promise<Principal | null>;
|
|
52
|
+
};
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
- `null`, a throw, a rejection or a missing `principalByIdentity` is no principal. Emails compare trimmed and case-insensitively.
|
|
56
|
+
- An identity link is app data: one row per `kind`, `provider`, `value` and `workspace`, written when a signed-in person connects a chat account or verifies an address. Never create a link from an inbound message.
|
|
57
|
+
- `permdock.decideFor(principalId, permission, row)` decides for a contact in the caller's tenant even though the contact holds no tenant membership.
|
|
58
|
+
- [Delivery](https://permdock.com/docs/concepts/delivery): a `{ principal, channel, address }` recipient is decided with `decideFor`; a `{ channel, address }` recipient resolves through `principalByIdentity`, and an address with no link fails with `unlinked`.
|
|
59
|
+
- [Chat](https://permdock.com/docs/concepts/chat): `subjectFromChat(author, { principals })` resolves the author through `principalByIdentity({ kind: 'chat', provider, value, workspace })`. An unlinked author is the anonymous subject, so every check denies with `anonymous`.
|
|
60
|
+
- An unlinked address or chat author is never a guest. Do not invent a fallback contact or a default portal role for it.
|
|
61
|
+
|
|
62
|
+
Sources:
|
|
63
|
+
|
|
64
|
+
- `memoryPrincipalSource({ principals, identities })` from `permdock` holds principals and links (`{ identity, principal: '<id>' }`) in process, for tests and single-replica apps.
|
|
65
|
+
- On better-supabase, `principalSource({ claims, identities?, subject? })` from `permdock/better-supabase` loads users from the claims better-supabase mints. `identities(identity)` returns `{ user: id }` or `{ contact: principal }`; a contact link is accepted only for `kind: 'contact'` ([better-supabase principals](https://permdock.com/docs/adapters/better-supabase#principals)).
|
|
66
|
+
- Run `testPrincipalSource(source, { principals, identities })` from `permdock/testing` on a custom source.
|
|
67
|
+
|
|
68
|
+
## Support sessions
|
|
69
|
+
|
|
70
|
+
A support session lets a staff member view the app as one person, inside that person's tenant, for a stated reason and a bounded time. The person stays the principal and the staff member is an actor with `kind: 'support'`.
|
|
71
|
+
|
|
72
|
+
### Start
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
import { decideSupportSession } from "permdock";
|
|
76
|
+
const decision = await decideSupportSession(
|
|
77
|
+
staff,
|
|
78
|
+
{
|
|
79
|
+
id: crypto.randomUUID(),
|
|
80
|
+
principal: "u_customer",
|
|
81
|
+
reason: "Ticket 4411: invoice totals look wrong",
|
|
82
|
+
// tenant?: defaults to the staff member's active tenant
|
|
83
|
+
// permissions?: defaults to every readOnlyHint permission the staff member can use
|
|
84
|
+
// expiresIn?: seconds, default 3600, at most 86400
|
|
85
|
+
// reference?: the app's ticket id
|
|
86
|
+
},
|
|
87
|
+
{ settings, approved, approvedBy }, // approval: false only when the app approves elsewhere
|
|
88
|
+
);
|
|
89
|
+
// { outcome: 'granted', session } | { outcome: 'approval-required', session, token } | { outcome: 'denied', denials }
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
- The staff member must be a `user` acting for themselves (no credential, run ticket or actor), with `exceeds-creator` otherwise. They must be a member of the tenant (`tenant-mismatch`) and able to use every listed permission (`exceeds-creator`).
|
|
93
|
+
- An empty reason denies with `reason-required`.
|
|
94
|
+
- Approval is on by default. Store the request, let an approver who is not the staff member accept, then decide again with `{ approved: token, approvedBy }`. `approvedBy` equal to the staff member denies.
|
|
95
|
+
- The tenant's `supportSessions` setting from a `SettingsSource` only tightens: `disabled` refuses every session, `maxTtl` caps `expiresIn`, `readOnly` refuses a write permission with `not-delegated`.
|
|
96
|
+
|
|
97
|
+
### Use
|
|
98
|
+
|
|
99
|
+
- `signSupportSession(session, signer)` from `permdock/server` signs a granted record as a `vnd.permdock.support-session+jwt` (`sub` the viewed principal, `act` the staff member, `jti` the session id) that expires with the session.
|
|
100
|
+
- `subjectFromSupportSession(token, { verifier, principals, permissions, sessions? })` resolves it on every request: the viewed principal and the staff member are loaded live through `principals`, the actor is `{ id, kind: 'support' }`, the session's permissions become the `delegation`, and `subject.context.supportSession` carries the id, reason, `approvedBy` and `reference` for audit. Every failure is the anonymous subject, reported through `onAuth` with a cause such as `expired`, `session-ended`, `principal-unavailable` or `actor-unavailable`.
|
|
101
|
+
- A `SupportSessionSource` (`session(id)` returning `{ id, ended? }` or `null`) passed as `sessions` lets the app end a session before it expires. An unknown id, `ended: true` or a throw is `session-ended`. Check it with `testSupportSessionSource(source, { sessions })` from `permdock/testing`.
|
|
102
|
+
- A support actor never approves: `assertApprover` throws `approver-not-eligible`.
|
|
103
|
+
- A policy delegation to `actor('support')` narrows what any session reaches.
|
|
104
|
+
|
|
105
|
+
### Events
|
|
106
|
+
|
|
107
|
+
`supportSessionEvent({ operation, session })` builds an `access` event with `via: 'support-session'`, the session id and the permissions. Sinks send it as `com.permdock.support-session.started` or `com.permdock.support-session.ended`; a session ended early (`revoked`) goes out as `.ended` and keeps the difference in `data.operation`.
|
|
108
|
+
|
|
109
|
+
### better-supabase
|
|
110
|
+
|
|
111
|
+
`supportPolicy({ policy, subject?, options?, writable?, approval?, approved? })` from `permdock/better-supabase` is the `authorize` option of better-supabase's `supportSessions`. It decides each start with `decideSupportSession` as the admin; a `readOnly: false` start needs `writable`, and an `approval-required` start passes once `approved(input, pending)` returns `{ approvedBy }` for `pending.token` ([better-supabase support sessions](https://permdock.com/docs/adapters/better-supabase#support-sessions)).
|
|
112
|
+
|
|
113
|
+
### Support sessions and supportAccess
|
|
114
|
+
|
|
115
|
+
`supportAccess` ([elevated access](https://permdock.com/docs/concepts/elevated-access#support-access-with-tenant-consent)) is the other model: the vendor is the principal and holds a consented, time-bound `{ via: 'support' }` membership that a tenant owner grants through an `ApprovalRequest`. Use it for a vendor team working inside a tenant. Use a support session to see what one person sees.
|
|
116
|
+
|
|
117
|
+
| Model | Principal | Actor | Granted by |
|
|
118
|
+
| --------------- | ----------------------- | ----------------------------- | --------------------------------------- |
|
|
119
|
+
| Support session | The viewed person | Staff, `kind: 'support'` | `decideSupportSession` plus an approval |
|
|
120
|
+
| `supportAccess` | The vendor staff member | Required with `actorRequired` | Tenant owner consent |
|
|
121
|
+
|
|
122
|
+
Both share the `com.permdock.support-session.*` event family. A support-session actor satisfies `actorRequired`, which accepts any actor.
|
|
@@ -42,7 +42,8 @@ roles: [
|
|
|
42
42
|
## Checking a role change
|
|
43
43
|
|
|
44
44
|
```ts
|
|
45
|
-
|
|
45
|
+
import { decideRoleChange } from "permdock";
|
|
46
|
+
const result = decideRoleChange(permdock, {
|
|
46
47
|
kind: "assign", // 'assign' | 'revoke' | 'transfer'
|
|
47
48
|
role: roles.admin,
|
|
48
49
|
scope: "organization",
|
|
@@ -61,7 +62,7 @@ The actor is the instance's own subject; nothing in the change stands in for it.
|
|
|
61
62
|
|
|
62
63
|
For a role on a nested scope that the subject holds nothing at, pass `within` and `{ trusted: true }` only after loading the ancestors from the app's own store.
|
|
63
64
|
|
|
64
|
-
Generated RLS applies the same rules at commit, with triggers on the scope's `rls.memberships` table or on the tables of its `
|
|
65
|
+
Generated RLS applies the same rules at commit, with triggers on the scope's `rls.memberships` table or on the tables of its `tableMembershipSource` / `junctionMembershipSource` sources (base tables, not views). Map `rls.memberships.scopes.<scope>.via` so the helpers apply `for`, and use `permdock_can_assign(role, organization_id::text)` in the membership table's own insert policy, or `permdock_can_assign_any(role, organization_id, 'organization', organization_id::text)` when the role may be a custom one. Set `rls.assignments` to have triggers check every client write to the membership tables, the global-roles table `rls.roles` and an invitations table, and `ownRole: 'refuse'` to refuse a client write to the caller's own row; never keep a hand-written trigger for either. The SQL is owned by the `permdock-data` skill.
|
|
65
66
|
|
|
66
67
|
## Custom roles
|
|
67
68
|
|
|
@@ -80,10 +81,11 @@ type CustomRole = {
|
|
|
80
81
|
- An own allow inherits the conditions of the ceiling grants for that permission. To keep a narrower condition ("own posts only"), include the declared role that has it.
|
|
81
82
|
- A resource may declare `levels: { own: { ownerId: principal.id }, team: { teamId: { in: principal.teamIds } }, all: {} }`. A grant `{ permission: 'job.read', level: 'team' }` ANDs that condition into the ceiling grant. An undeclared level, or a level on a collection action, is dropped as `unknown-level` and removes the permission; a level on a deny is `condition-not-allowed`. `permdock.assignableLevels(leaf)` lists the levels the subject may hand out.
|
|
82
83
|
- An own deny removes the permission from this custom role only. Deny it in code when it must win across roles.
|
|
83
|
-
- Pass a `
|
|
84
|
+
- Pass a `CustomRoleSource` (`memoryCustomRoleSource(roles)` in tests), or a `CustomRoleSourceFactory` `(subject) => CustomRoleSource` when the source reads one principal's roles, as `customRoles` on `createPermDock` or any adapter. To add every role of a tenant on a role-management request, `await permdock.derive({ customRoles })` instead of building a second instance. Build it with `customRoleSource({ rolesFor })`, where `rolesFor(tenant)` reads every custom role of the tenant, not only the held ones: `assignableRoles` and `decideRoleChange` see only what the source returns. `{ read: 'held', policy }` skips the read on requests that never manage roles. Check it with `testCustomRoleSource(source, { every })`.
|
|
84
85
|
- `permdock.assignableRoles({ tenant })` lists declared roles, then the tenant's custom roles whose every permission and level the subject may hand out at the custom role's scope. `decideRoleChange` assigns and revokes a custom role like a declared one, with no holder count; it inherits `for` and `exclusiveWith` from its included roles.
|
|
85
86
|
- `permdock.assignablePermissions({ tenant })` is the ceiling intersected with what the subject holds. `useAssignablePermissions()` in `permdock/react` builds the editor from it.
|
|
86
87
|
- The save action runs `validateCustomRole(policy, role)` and refuses the role when `ok` is false or `permissions` contains a key outside `assignablePermissions()`. `dropped` names each entry left out (`unknown-permission`, `outside-ceiling`, `condition-not-allowed`, `unknown-level`, `unknown-role`).
|
|
87
|
-
- A platform custom role (`scope: 'global'`, no `tenant`, `team` or `id`) is held through `principal.roles` and capped by the allows of declared global roles marked `assignable`. `
|
|
88
|
+
- A platform custom role (`scope: 'global'`, no `tenant`, `team` or `id`) is held through `principal.roles` and capped by the allows of declared global roles marked `assignable`. `CustomRoleSource.globalRoles({ held })` returns them; `customRoleSource(reader, { read: 'held', policy })` skips that read while every held global role is declared.
|
|
88
89
|
- A stored grant under a key renamed with `definePermissions(..., { renamed })` still resolves; `validateCustomRole` lists it in `renamed` as `{ from, to }`, and `permdock doctor` PD055 prints the `update` that rewrites it.
|
|
89
90
|
- For generated RLS, add `--custom-roles` (or `rls.customRoles: true`) to `permdock rls generate`. Platform roles are rows with `scope = 'global'` and a null `tenant_id`, or the top-level `role_grants` claim in `jwt` mode. With levels, `custom_role_permissions` gains a `level` column and the claim writes `key@level`.
|
|
91
|
+
- To adopt custom roles the app already stores in its own tables, set `rls.customRoles.from` and `rls.customRoleWrites.roles`, then run `permdock rls generate --backfill-out <migrations dir>/`. The migration saves each role through the trusted replace function, drops entries outside the ceiling with a warning, and skips roles that already have rows; never write the copy by hand ([backfill](https://permdock.com/docs/cli/rls#backfill-from-existing-tables)).
|
|
@@ -14,10 +14,11 @@ Three files and one guard: `src/permissions.ts` (definitions), `src/policy.ts` (
|
|
|
14
14
|
|
|
15
15
|
## Inputs (find out, or ask before starting)
|
|
16
16
|
|
|
17
|
-
- The framework: Next.js, Hono, Express, Fastify, Elysia, Nest, Node, tRPC, oRPC, an AuthZEN PDP, Vite with React, Vue, Svelte or Solid, React Native, or a collect-only framework (
|
|
17
|
+
- The framework: Next.js, Hono, Express, Fastify, Elysia, Nest, Node, tRPC, oRPC, SvelteKit, Astro, SolidStart, TanStack Start, h3 (also Nitro and Nuxt server routes), an AuthZEN PDP, Vite with React, Vue, Svelte or Solid, React Native, or a collect-only framework (React Router, Effect).
|
|
18
18
|
- The Standard Schema validator already in the repo: Zod, Valibot, ArkType, Effect Schema.
|
|
19
19
|
- Where the signed-in user comes from (the session or a verified token) and which roles and plans exist.
|
|
20
20
|
- The first path to guard.
|
|
21
|
+
- Whether the app is new (start from a preset) or already has its own checks (adopt beside them).
|
|
21
22
|
|
|
22
23
|
## Invariants
|
|
23
24
|
|
|
@@ -29,9 +30,11 @@ Three files and one guard: `src/permissions.ts` (definitions), `src/policy.ts` (
|
|
|
29
30
|
|
|
30
31
|
## Workflow
|
|
31
32
|
|
|
32
|
-
1. **
|
|
33
|
+
1. **Start.** A new app starts from a preset: the user's code from [permdock.com/create](https://permdock.com/create), or flags. Run `npx permdock init --preset <code> --dry-run`, show the file list, then run it without `--dry-run`; it never overwrites a file unless `--force` is passed. It writes the owned files the steps below describe; review them instead of writing them. An app with its own checks keeps them and adopts beside them with `shadowMode` and `permdock scan` ([existing apps](https://permdock.com/docs/getting-started/existing-apps)). -> [references/presets-and-recipes.md](references/presets-and-recipes.md)
|
|
34
|
+
✓ Either `permdock init` wrote the files and printed the install line, or the legacy checks are listed and none was removed.
|
|
35
|
+
2. **Detect.** Find the framework and the validator. Collect-only frameworks use `permdock/unplugin` for collect and `permdock/server` (or the matching HTTP adapter) at runtime; Nuxt collects the same way and runs on `permdock/h3`. Never add a per-vendor package.
|
|
33
36
|
✓ The adapter import path (`permdock/<framework>`) and the validator import are named.
|
|
34
|
-
|
|
37
|
+
3. **Define.** Create `src/permissions.ts`. Importable everywhere; no rules, no secrets.
|
|
35
38
|
|
|
36
39
|
```ts
|
|
37
40
|
import {
|
|
@@ -41,6 +44,7 @@ Three files and one guard: `src/permissions.ts` (definitions), `src/policy.ts` (
|
|
|
41
44
|
resource,
|
|
42
45
|
crud,
|
|
43
46
|
} from "permdock";
|
|
47
|
+
|
|
44
48
|
import { Post } from "./schemas";
|
|
45
49
|
|
|
46
50
|
export const permissions = definePermissions({
|
|
@@ -52,10 +56,11 @@ Three files and one guard: `src/permissions.ts` (definitions), `src/policy.ts` (
|
|
|
52
56
|
|
|
53
57
|
✓ Every resource the first guard needs has a leaf; instance actions sit in `actions`, list and create in `collection`.
|
|
54
58
|
|
|
55
|
-
|
|
59
|
+
4. **Policy.** Create `src/policy.ts`. Prefer `grants` with `to:` selectors (`anyone()`, `authenticated()`, `relation()`, a role or plan leaf, `actor()`, `assurance()`); `role(roles.member, …)` sugar also works.
|
|
56
60
|
|
|
57
61
|
```ts
|
|
58
62
|
import { definePolicy, role, allow, principal, relation } from "permdock";
|
|
63
|
+
|
|
59
64
|
import { permissions, roles } from "./permissions";
|
|
60
65
|
|
|
61
66
|
const member = role(roles.member, [
|
|
@@ -75,7 +80,7 @@ Three files and one guard: `src/permissions.ts` (definitions), `src/policy.ts` (
|
|
|
75
80
|
{ permissions, roles },
|
|
76
81
|
{
|
|
77
82
|
roles: [member],
|
|
78
|
-
|
|
83
|
+
subject: (user) => user && { id: user.id, roles: user.roles },
|
|
79
84
|
},
|
|
80
85
|
);
|
|
81
86
|
```
|
|
@@ -83,19 +88,19 @@ Three files and one guard: `src/permissions.ts` (definitions), `src/policy.ts` (
|
|
|
83
88
|
Short policy features that need no other skill -> [references/policy-options.md](references/policy-options.md): usage limits, `disclosure: 'hide'`, plan gates, `validFrom` / `validUntil`, step-up with `assurance()`.
|
|
84
89
|
✓ At least one role grants the first guard's permission, and no client entry imports `policy.ts`.
|
|
85
90
|
|
|
86
|
-
|
|
91
|
+
5. **Factory.** Create `src/permdock/server.ts` (or the adapter's documented factory file) with `createPermDock` from `permdock/<framework>`. Copy the exact return names. -> [references/adapters.md](references/adapters.md)
|
|
87
92
|
✓ The factory file compiles and exports the adapter's public members.
|
|
88
|
-
|
|
93
|
+
6. **Guard.** Add one check on the path the user asked for: `assert` or `protect` / `getPermission` with a permission reference on the server, `<Protected permission={permissions.post.update}>` or `useGate` inside `PermDockProvider` in React. For a gated button, approval dialog, tenant switcher or role picker, add the recipe from the `@permdock` registry rather than writing one. -> [references/presets-and-recipes.md](references/presets-and-recipes.md#recipes)
|
|
89
94
|
✓ That path cannot run without a `granted` decision, and the adapter handles a denial (Problem Details or fallback UI).
|
|
90
|
-
|
|
95
|
+
7. **Check.** Run `permdock collect`, `permdock catalog`, `permdock usage` and `permdock doctor`, and add `permdock collect --check` to CI. For a Vite SPA, Expo or another framework without `'use client'`, set `doctor.clientEntries` to the client source globs so PD001 checks them. When client code lives outside `collect.srcPath`, list every source folder in `doctor.srcPath` instead of widening the catalog. Add a `describePolicy` suite from `permdock/testing` ([scenario testing](https://permdock.com/docs/guides/scenario-testing)). -> [references/policy-options.md](references/policy-options.md#cli-starting-points)
|
|
91
96
|
✓ Collect and doctor findings are fixed, or the three files typecheck and one matrix test passes.
|
|
92
97
|
|
|
93
98
|
## Next skills
|
|
94
99
|
|
|
95
100
|
Install each with `npx skills add ScaleDockHQ/PermDock --skill <name>` when the app needs it:
|
|
96
101
|
|
|
97
|
-
- `permdock-agents`: AI SDK, Claude Agent SDK, Eve, OpenAI Agents SDK, MCP, A2A and WebMCP tool maps, actors and `delegation
|
|
98
|
-
- `permdock-approvals`: `approval: { by }`, quorum, escalation, `staleOn`, approval stores
|
|
102
|
+
- `permdock-agents`: AI SDK, Claude Agent SDK, Eve, OpenAI Agents SDK, MCP, A2A and WebMCP tool maps, actors and `delegation`, automations (`permdock/workflow`, `permdock/workflow-sdk`) and chat bots (`permdock/chat`, `permdock/chat-sdk`).
|
|
103
|
+
- `permdock-approvals`: `approval: { by }`, quorum, escalation, `staleOn`, approval stores, the resume flow, and approval prompts by email or chat (`permdock/delivery`, `permdock/resend`, `permdock/chat-sdk`).
|
|
99
104
|
- `permdock-tenancy`: named scopes, memberships, ownership rules (`min`, `assigns`, `for`) and custom roles.
|
|
100
105
|
- `permdock-data`: `where()` and `filter()` in ORM queries, generated RLS, relationship graphs, parity tests.
|
|
101
106
|
- `permdock-credentials`: `subjectFromJwt`, API keys and service accounts, share links.
|
|
@@ -110,7 +115,8 @@ Install each with `npx skills add ScaleDockHQ/PermDock --skill <name>` when the
|
|
|
110
115
|
|
|
111
116
|
## Reference index
|
|
112
117
|
|
|
113
|
-
- [references/adapters.md](references/adapters.md): factory shapes for Next.js, HTTP frameworks, RPC frameworks, UI frameworks, AuthZEN, OpenAPI, OpenTelemetry, SCIM, Cloud, Better Auth, Clerk, Supabase, the remote PDP
|
|
114
|
-
- [references/
|
|
118
|
+
- [references/adapters.md](references/adapters.md): factory shapes for Next.js, HTTP frameworks, meta-framework servers (SvelteKit, Astro, SolidStart, TanStack Start, h3 for Nitro and Nuxt), RPC frameworks, UI frameworks, AuthZEN, OpenAPI, OpenTelemetry, SCIM, Cloud, the Resend, Chat SDK and Workflow SDK implementation entries, Better Auth, Clerk, Supabase and better-supabase, the remote PDP, collect-only frameworks, and the `deniedResponse`, `context` and `wrap` adapter hooks.
|
|
119
|
+
- [references/presets-and-recipes.md](references/presets-and-recipes.md): `permdock init` and preset codes, what the package owns and what the app owns, and the `@permdock` shadcn recipes for React and React Native.
|
|
120
|
+
- [references/policy-options.md](references/policy-options.md): limits, disclosure, plans, validity windows, step-up, app data and app obligations, and CLI starting points (OpenAPI import, Arazzo, Cloud push).
|
|
115
121
|
- The `permdock` skill: the mental model, `explain` for an unexpected denial, and the docs MCP.
|
|
116
122
|
- Docs: [policies](https://permdock.com/docs/concepts/policies), [conditions](https://permdock.com/docs/concepts/conditions), [decisions](https://permdock.com/docs/concepts/decisions), [adapters](https://permdock.com/docs/adapters).
|