permdock 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -35
- package/dist/{schema-Bol6gqXA.js → 2020-C9VN-x8c.js} +5 -130
- package/dist/a2a/index.d.ts +6 -6
- package/dist/a2a/index.js +14 -17
- 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 -35
- package/dist/ai-sdk/index.js +37 -23
- package/dist/{app-data-CV8pqqMi.js → app-data-MuyzVqX8.js} +2 -1
- 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/arazzo-B6lebmiQ.js +36 -0
- package/dist/{arazzo-C4BcskbX.js → arazzo-BWhNF-k0.js} +20 -14
- 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 +94 -79
- 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 +9 -8
- package/dist/better-supabase/index.d.ts +357 -30
- package/dist/better-supabase/index.js +936 -194
- package/dist/{bind-BzqG8SMb.d.ts → bind--9EFVYWF.d.ts} +12 -10
- package/dist/{bind-kk-uyCwH.js → bind-CZzhx-Vj.js} +14 -10
- package/dist/body-CH5of8Ne.js +10 -0
- package/dist/body-text-QcXok_SI.js +52 -0
- package/dist/{build-instance-DIIV9DbC.js → build-instance-BMnAudHn.js} +4 -3
- package/dist/{canonical-json-CFKyFOke.js → canonical-json-BC8_nMTw.js} +5 -3
- package/dist/{capability-CksyCWDH.d.ts → capability-BSslw1QV.d.ts} +44 -25
- package/dist/{capability-txATtKV4.js → capability-N1K9IpqS.js} +68 -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-tZUyOfOv.js → catalog-BECMPrwc.js} +51 -6
- package/dist/catalog-DjhMYYCq.js +1 -0
- package/dist/catalog-R_A__RsH.js +52 -0
- package/dist/{catalog-doc-DWtg0gGR.js → catalog-doc-C9wjsfNY.js} +45 -23
- 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 +24 -27
- package/dist/clerk/index.d.ts +9 -8
- package/dist/clerk/index.js +9 -14
- package/dist/cli/bin.js +36 -17
- package/dist/cli/index.d.ts +19 -5
- package/dist/cli/index.js +164 -4
- package/dist/cloud/index.d.ts +102 -63
- package/dist/cloud/index.js +239 -340
- package/dist/cloud-CIoHNevm.js +87 -0
- package/dist/codemod-Bmj9dWMH.js +37 -0
- package/dist/codemod-Cxvvc70T.js +2084 -0
- package/dist/{collect-2SrB7lfk.js → collect-CrZJJnEF.js} +75 -11
- package/dist/collect-DloHbDVa.js +55 -0
- package/dist/collect-FlXJlhus.js +1 -0
- package/dist/command-L8EstFNm.js +6 -0
- package/dist/compile/index.d.ts +1 -1
- package/dist/compile/index.js +1 -2
- package/dist/{compile-CcEu2ZQ1.js → compile-CMqMx_IN.js} +11 -8
- package/dist/{compile-CtTP7eur.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-BIjREpyE.js → config-bs_kZeYq.js} +12 -7
- package/dist/{config-report--Xb9APJS.js → config-report-DUgj62Ai.js} +15 -5
- package/dist/{connection-DUsczpB5.js → connection-DjhIJSs2.js} +6 -5
- package/dist/{context-DieZVJXu.js → context-bbFHR-0D.js} +2 -2
- package/dist/convex/index.d.ts +11 -10
- package/dist/convex/index.js +6 -4
- 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-BBO24vj6.js → create-DnrmLnLW.js} +31 -32
- package/dist/{credential-kue0qpbm.js → credential-DM2um3ha.js} +152 -31
- package/dist/credentials-DPyHmYz5.js +74 -0
- package/dist/delegation-DaXVdDhM.js +286 -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-DfRk15uw.d.ts → denied-text-D8jDHgsl.d.ts} +5 -5
- package/dist/{describe-C3ldA9os.d.ts → describe-BZUsixiL.d.ts} +2 -2
- package/dist/{describe-Cgkp7eP0.js → describe-BybMRY7A.js} +6 -2
- package/dist/device-BARQ8eYG.js +193 -0
- package/dist/diff-CxZo9pTq.js +37 -0
- package/dist/{diff-rpqoe7Ag.js → diff-DCePiN6M.js} +60 -54
- package/dist/doctor-CxiwSfqH.js +44 -0
- package/dist/{doctor-owlv6ddh.js → doctor-DFjUxf2f.js} +245 -50
- package/dist/drizzle/index.d.ts +5 -3
- package/dist/drizzle/index.js +2 -2
- package/dist/elysia/index.d.ts +16 -13
- package/dist/elysia/index.js +13 -12
- package/dist/env-DJZrPjRi.js +8 -0
- package/dist/errors-BDUwg2qC.js +27 -0
- package/dist/{errors-drnfxYtm.js → errors-BKy0EfhN.js} +5 -9
- package/dist/errors-BWXBMaKK.d.ts +11 -0
- package/dist/{errors-OD_4c__H.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-C9YJsv3H.js → evaluations-DZ2CbV5k.js} +145 -34
- package/dist/eve/index.d.ts +12 -16
- package/dist/eve/index.js +22 -28
- 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 +12 -11
- package/dist/fastify/index.d.ts +25 -22
- package/dist/fastify/index.js +13 -12
- 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/{fixtures-h2eG2OZV.js → fixtures-B7XrLeY7.js} +8 -7
- package/dist/flags-C3DktBDY.js +86 -0
- package/dist/{from-snapshot-7REQZe0H.js → from-snapshot-D2lZt4Dd.js} +55 -52
- package/dist/{grantee-CXY-0C87.js → grantee-Cn1HyUsI.js} +3 -3
- package/dist/{grantee-conditions-MQs5NIuX.js → grantee-conditions-CcXf8hMM.js} +4 -3
- package/dist/{grantee-match-fxYp9Nmt.js → grantee-match-DHArqY82.js} +5 -4
- 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-C9YmyBIa.js → helpers-DTcZOzhF.js} +45 -38
- package/dist/hono/index.d.ts +12 -9
- package/dist/hono/index.js +13 -12
- package/dist/hosted-BZIAYvon.js +315 -0
- package/dist/hosted-DBDX0M-P.d.ts +78 -0
- package/dist/{http-DPuDaChb.d.ts → http-B_LphE5Y.d.ts} +1 -2
- package/dist/http-DXJ37RDA.js +46 -0
- package/dist/{http-BEbdMZ05.js → http-ujKm0JXE.js} +1 -2
- package/dist/{import-UHk1N7vH.js → import-CNV7I2Dw.js} +25 -20
- package/dist/{import-BFIlhetD.js → import-DMmOG27O.js} +190 -53
- 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-UdVQgq48.d.ts → index-DOVf7RJv.d.ts} +431 -81
- package/dist/{index-BwMe94Xq.d.ts → index-SGdIQyzc.d.ts} +34 -16
- package/dist/index.d.ts +19 -15
- package/dist/index.js +31 -21
- package/dist/init-DeMYsnYJ.js +96 -0
- package/dist/init-GG6Jh8HD.js +95 -0
- package/dist/{instance-options-plkrsAog.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-DBkdUuCO.js → json-schema-B-vAwlde.js} +45 -6
- package/dist/json-schema-CN_iLFH2.d.ts +40 -0
- package/dist/jwt/index.d.ts +18 -11
- package/dist/jwt/index.js +156 -135
- package/dist/{kernel-CB3LrIJM.js → kernel-5JatXyND.js} +52 -24
- package/dist/kysely/index.d.ts +5 -3
- package/dist/kysely/index.js +2 -2
- package/dist/load-BxEtwlBS.js +1 -0
- package/dist/{load-C3_oTHyO.js → load-DFS_ry0W.js} +22 -6
- 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-AVlBaJoa.js → map-claims-D3oRmTk0.js} +13 -10
- package/dist/{map-error-BE27PYkE.js → map-error-TCAdw-eW.js} +3 -3
- package/dist/{markers-CiwAOZtO.js → markers-C6joDI3o.js} +2 -1
- package/dist/mcp/index.d.ts +22 -12
- package/dist/mcp/index.js +57 -39
- package/dist/{migrate-BwlB5-Wk.js → migrate-BShP7Vf4.js} +53 -14
- package/dist/nest/index.d.ts +12 -9
- package/dist/nest/index.js +15 -14
- package/dist/next/index.d.ts +18 -21
- package/dist/next/index.js +13 -11
- 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-uYFXGVw2.js → ocsf-BFp-PZZA.js} +22 -11
- package/dist/openai/index.d.ts +9 -16
- package/dist/openai/index.js +17 -24
- package/dist/openapi/index.d.ts +3 -3
- package/dist/openapi/index.js +1 -1
- package/dist/{openapi-DBzHQbov.js → openapi-B7HZ0f-r.js} +8 -8
- package/dist/{openapi-BJdBmYDG.js → openapi-DTaEmTSW.js} +10 -9
- 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 +20 -15
- package/dist/otel/index.d.ts +3 -2
- package/dist/otel/index.js +30 -7
- package/dist/output-Dyw8fyPF.js +88 -0
- package/dist/output-files-B3IZJksC.js +1 -0
- package/dist/{output-files-CgIsvdSa.js → output-files-DhRBBE4n.js} +24 -26
- package/dist/{permission-keys-B26jeyUv.js → ownership-BatFgAA4.js} +1698 -869
- package/dist/{package-root-DtNnpmUd.js → package-root-Dk_ETdkp.js} +1 -1
- package/dist/{parse-CVFQHk6c.js → parse-Cxq0WxrA.js} +2 -2
- package/dist/{parse-local-manifest-C-q02Pal.js → parse-local-manifest-CVSs6J9I.js} +1 -1
- package/dist/{parse-local-manifest-DvYUR0QL.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 +45 -26
- 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-BsblpB-z.js → permission-registry-Br4jSNVi.js} +3 -3
- package/dist/permissions-B-wCP5ZC.js +30 -0
- package/dist/{permissions-CCJ6lmxB.js → permissions-CZKiUfFM.js} +4 -3
- package/dist/{permissions-COH-KRgT.d.ts → permissions-oEWfl-kl.d.ts} +1 -2
- package/dist/policies-CO1oOymN.js +295 -0
- package/dist/{policy-ur0BBK_e.d.ts → policy-Czo4n3Hs.d.ts} +2 -2
- package/dist/{hosted-CBHgkzDl.js → policy-DLZJ5gAp.js} +17 -273
- package/dist/policy-from-catalog-DzhEc3Y9.js +450 -0
- package/dist/policy-module-CTlwMvLI.js +89 -0
- package/dist/{policy-types-sp9P1iIV.d.ts → policy-types-DzwivBgR.d.ts} +434 -631
- 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-cZe4kzhL.js → powersync-bYUDLOAO.js} +23 -24
- 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 +2 -2
- package/dist/{problem-CnWraVhC.js → problem-CTviyH5L.js} +13 -23
- package/dist/{problem-details-rIIo8B2C.d.ts → problem-details-DXlt-vks.d.ts} +1 -1
- package/dist/{problem-details-DECUnXAn.js → problem-details-cn5X30gZ.js} +43 -40
- package/dist/problem-types-CJ2325SZ.js +5 -0
- package/dist/{prompt-CXYhLfsS.js → prompt-DfZLJLIn.js} +2 -2
- package/dist/{protected-B3_P7xuw.d.ts → protected-DEPHc2xV.d.ts} +16 -8
- package/dist/{protected-BN7BUdY1.js → protected-DfjY5mLA.js} +21 -4
- package/dist/{provider-Bi-0HNL3.js → provider-CC-u3bYP.js} +2 -2
- package/dist/{provider-kwHC9CZD.d.ts → provider-DMVt6yYn.d.ts} +1 -1
- package/dist/{provider-store-Ctt-81Ni.js → provider-store-B5Yrkhzr.js} +2 -2
- 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 -72
- package/dist/react-native/index.js +24 -22
- package/dist/{refs-f3imMjNW.js → refs-K6eWUJ0H.js} +1 -1
- package/dist/{relation-walk-BXnlLtg2.js → relation-walk-3OyDBF0S.js} +2 -2
- package/dist/{relations-CvmSpG67.js → relations-BnCWnJGI.js} +3 -3
- package/dist/renames-CGj7YwcQ.js +175 -0
- package/dist/{replay-COpmZUfx.d.ts → replay-D7iKcLFe.d.ts} +1 -1
- package/dist/{replay-BsOyTrdV.js → replay-Du1tJjG7.js} +2 -1
- package/dist/{request-context-BVM7aWNs.js → request-context-BTRTG7OT.js} +3 -3
- 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-D1Un3pcz.js → resource-relations-D_jw0BFx.js} +1 -1
- package/dist/{retire-BFcxL6un.js → retire-CALjIQ7e.js} +2 -2
- package/dist/{revocations-Cy06JNx2.d.ts → revocations-R07lFBnX.d.ts} +2 -0
- package/dist/rls-BA8an5Ap.js +336 -0
- package/dist/{rls-BEktUaqg.js → rls-j6t43t7n.js} +287 -689
- package/dist/{role-builder-B4GbKM-i.js → role-builder-CXbis5-X.js} +14 -207
- package/dist/{rpc-nYC3cR_u.js → rpc-aPyNi6tA.js} +7 -7
- 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-D_tMXnsq.js → runtime-D3VTAiCA.js} +1 -1
- package/dist/{saas-DtaA4JuW.js → saas-FO-Q8oj0.js} +16 -14
- package/dist/scan-BOI5tJ-6.js +29 -0
- package/dist/{schema-DjQBcAP9.js → schema-B4v0Qrac.js} +102 -8
- 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-vFR1O5oG.js → scim-ClHtpyV3.js} +208 -76
- package/dist/{scopes-tvpa34ep.js → scopes-CUPSjo9q.js} +12 -7
- package/dist/server/index.d.ts +12 -143
- package/dist/server/index.js +12 -9
- package/dist/server-DqCT4CgZ.js +453 -0
- package/dist/{server-types-DdqBwZN_.d.ts → server-types-BLd4MnBX.d.ts} +51 -68
- package/dist/{sha256-BO0swRXL.js → sha256-D9ihqmvd.js} +8 -1
- package/dist/{shared-1uhI-e-b.js → shared-CD11nBbC.js} +18 -8
- package/dist/{shared-JhMeOsDF.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-BZdRQkxs.js → skills-B6lVdD_W.js} +13 -6
- package/dist/skills-CTYwkl44.js +51 -0
- package/dist/snapshot-CXUWfsNy.js +269 -0
- package/dist/{snapshot-cache-Cod40D-X.js → snapshot-cache-DZHQ0KF6.js} +11 -5
- package/dist/{snapshot-cache-J5T7t_vD.d.ts → snapshot-cache-DqK9eDmM.d.ts} +7 -5
- package/dist/solid/index.d.ts +11 -12
- package/dist/solid/index.js +7 -7
- package/dist/solid-start/index.d.ts +52 -0
- package/dist/solid-start/index.js +49 -0
- package/dist/{sources-Dljy06zL.d.ts → sources-BV9j0DeZ.d.ts} +7 -13
- package/dist/{sources-B1-b-R0t.js → sources-vs5dGOsS.js} +125 -119
- package/dist/{sql-t8teWOfo.js → sql-B5T1IL83.js} +13 -1
- package/dist/src-DLfUWFAI.js +1851 -0
- package/dist/ssf/index.d.ts +4 -4
- package/dist/ssf/index.js +384 -47
- 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-ILkLUj_o.js → store-4u_hxjWa.js} +40 -53
- package/dist/{store-options-CbUHDoSe.js → store-options-BqEakYRa.js} +26 -21
- package/dist/{stores-BHXdSbw9.js → stores-BbsD4fAR.js} +2 -2
- package/dist/{stores-Cxz_80g-.d.ts → stores-ClZUgeoP.d.ts} +7 -8
- package/dist/{stream-Dwsg6WwE.d.ts → stream-7-xmnf3U.d.ts} +2 -2
- package/dist/{stream-BMui98KK.js → stream-CvdUS9V1.js} +9 -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/supabase/index.d.ts +23 -106
- package/dist/supabase/index.js +5 -4
- package/dist/supabase/middleware.d.ts +7 -6
- package/dist/supabase/middleware.js +13 -11
- package/dist/supabase-9pqI57pW.js +92 -0
- package/dist/{supabase-C1TWlnYt.js → supabase-Dxlp8A94.js} +98 -205
- package/dist/supabase-hook-CXcGpHqq.js +1 -0
- package/dist/{supabase-hook-Cva3ikXz.js → supabase-hook-DdDQhMlw.js} +951 -60
- package/dist/support-session-C6xJQYut.js +158 -0
- package/dist/svelte/Protected.svelte +4 -4
- package/dist/svelte/index.d.ts +7 -7
- package/dist/svelte/index.js +10 -10
- package/dist/svelte/runtime.d.ts +3 -3
- package/dist/svelte/runtime.js +3 -3
- package/dist/svelte/source.d.ts +7 -7
- package/dist/svelte/source.js +4 -4
- 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/{tenancy-DshjqY0n.js → tenancy-DN2QAx9z.js} +5 -5
- package/dist/terminal/index.d.ts +11 -9
- package/dist/terminal/index.js +23 -287
- package/dist/testing/index.d.ts +311 -38
- package/dist/testing/index.js +3475 -201
- 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-Bx9qy30p.js → token-BLZcjQgt.js} +15 -6
- package/dist/{tool-refusal-CQ7OEia9.js → tool-refusal-CMLN5xhn.js} +2 -2
- package/dist/trpc/index.d.ts +12 -9
- package/dist/trpc/index.js +16 -14
- package/dist/{types-7rJVLWe5.d.ts → types-5Ut_pbKy.d.ts} +61 -11
- package/dist/{store-9KO4BlkB.d.ts → types-BNORmDM8.d.ts} +100 -75
- package/dist/types-BjV-fqcA.d.ts +57 -0
- package/dist/{types-CUcFZfpU.d.ts → types-BqAKcRNW.d.ts} +2 -1
- package/dist/types-C8-KNsFf.d.ts +107 -0
- package/dist/{types-Br9IXUBG.d.ts → types-CCpc2Dcv.d.ts} +77 -17
- package/dist/{types-DpWfQdSh.d.ts → types-CZTnPFzM.d.ts} +46 -11
- package/dist/{types-CqrjB9o6.d.ts → types-CdlxsYQh.d.ts} +23 -11
- package/dist/{types-CIlTojhq.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-ByUo56un.d.ts → types-fbZ7JEXr.d.ts} +23 -22
- package/dist/{types-DHrmBJLH.d.ts → types-tIgIeIKJ.d.ts} +7 -7
- 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-CG9nOYZw.js → usage-T5mXq98v.js} +54 -11
- package/dist/{validation-error-BSSw0l_8.d.ts → validation-error-Bg5Tpluk.d.ts} +1 -1
- package/dist/{validity-dVrUEPJt.js → validity-CZ7-y0Cj.js} +32 -15
- package/dist/{verifier-DMr_Zmb_.js → verifier-DHuh-okk.js} +18 -7
- package/dist/{verify-BztUB3GX.js → verify-DbCL9bnB.js} +645 -50
- package/dist/{version-BEogC-rB.js → version-CEbQ5UeM.js} +4 -3
- package/dist/version-DXkYUISx.js +1 -0
- package/dist/{views-CJsrBS1f.js → views-DTIIO-oQ.js} +22 -5
- package/dist/{permission-types-Dg39z9Kv.d.ts → vocabulary-D6lPdb4Z.d.ts} +65 -2
- package/dist/vue/index.d.ts +9 -10
- package/dist/vue/index.js +8 -8
- 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-zeucB7eF.js → watch-CszoTJMq.js} +44 -5
- package/dist/webmcp/index.d.ts +2 -2
- package/dist/webmcp/index.js +3 -3
- 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-Cr93qqdP.d.ts → wrap-Bd1Tejur.d.ts} +2 -1
- package/dist/{wrap-DWsU_5Bd.js → wrap-gX61EtpB.js} +7 -6
- package/package.json +136 -37
- 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 +123 -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 +14 -10
- package/skills/permdock-data/SKILL.md +6 -6
- package/skills/permdock-data/references/queries-and-rls.md +9 -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 +5 -4
- package/skills/permdock-wire/SKILL.md +17 -11
- package/skills/permdock-wire/references/adapters.md +139 -51
- package/skills/permdock-wire/references/policy-options.md +5 -5
- package/skills/permdock-wire/references/presets-and-recipes.md +58 -0
- package/dist/approvals-DZL76lKW.js +0 -240
- package/dist/arazzo-CD8d0WLb.js +0 -54
- package/dist/catalog-6yWAX6Ps.js +0 -2
- package/dist/catalog-WHG4kBq7.js +0 -48
- package/dist/cloud-C7bSDgX4.js +0 -50
- package/dist/cloud-PeBN3IEn.js +0 -118
- package/dist/collect-Bw2Jdq1q.js +0 -2
- package/dist/collect-CQx4hX4N.js +0 -83
- package/dist/commands-Cpip4ULx.js +0 -97
- package/dist/config-DLt6BvV7.js +0 -36
- package/dist/diff-Cnghh_Ip.js +0 -48
- package/dist/doctor-CuVJvhV_.js +0 -53
- package/dist/duration-xK2jGFVc.js +0 -25
- package/dist/errors-CWfoNg9N.js +0 -47
- package/dist/errors-DRnGZHzF.js +0 -30
- package/dist/expiry-BoBJ8Tvc.js +0 -7
- package/dist/fields-fwfXN01n.js +0 -1016
- 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-DgM7w96K.d.ts +0 -6
- package/dist/index-Dz7didpC.d.ts +0 -201
- package/dist/load-B_shHrmd.js +0 -2
- package/dist/manifest-BngWKryL.js +0 -770
- package/dist/may-use-BOn_dR_w.js +0 -78
- package/dist/openapi-DcQ1Kg40.js +0 -164
- package/dist/ownership-BgnbOJFT.js +0 -851
- package/dist/permdock-L26udHSk.js +0 -3646
- package/dist/powersync-BFSbSElQ.js +0 -62
- package/dist/powersync-DCDgZgfZ.js +0 -2
- package/dist/rls-55DQTuf5.js +0 -250
- package/dist/run-DMcNW_8T.js +0 -174
- package/dist/server-ZYEHtNHS.js +0 -268
- package/dist/sink-BTtmfPnE.js +0 -141
- package/dist/sink-CPN4zLfs.d.ts +0 -104
- package/dist/skills-Dle4ZoLF.js +0 -56
- package/dist/snapshot-Dc9CHPME.js +0 -1078
- package/dist/src-Bb7no-hS.js +0 -639
- package/dist/subject-C6dLxqHU.js +0 -24
- package/dist/supabase-Dbqr141s.js +0 -83
- package/dist/supabase-hook-BULg7Ut_.js +0 -2
- package/dist/thenable-QSmvt-vZ.js +0 -65
- package/dist/types-C0dee0Qf.d.ts +0 -197
- package/dist/types-DvupZE0w.d.ts +0 -9
- package/dist/usage-4G6t643F.js +0 -42
- package/dist/usage-iFLJ0-rr.js +0 -2
- package/dist/vocabulary-hhEFLFoL.d.ts +0 -65
- package/dist/watch-C8TpsAl4.js +0 -2
|
@@ -0,0 +1,1851 @@
|
|
|
1
|
+
import { n as isReadonlyArray, r as isRecord, t as compact } from "./compact-CAf557ZB.js";
|
|
2
|
+
import { n as freezeDeep } from "./freeze-CrU8nQeX.js";
|
|
3
|
+
import { n as isForbiddenKey, r as ownGet } from "./paths-DXSHAMIa.js";
|
|
4
|
+
import { f as normalizeApproval, g as assertSqlFunctionName, h as assertSqlFunctionArg, m as assertPortableTwin, p as normalizeWhere, u as assertApprovalRelations } from "./role-builder-CXbis5-X.js";
|
|
5
|
+
import { n as PermDockDeniedError } from "./errors-BKy0EfhN.js";
|
|
6
|
+
import { o as sameId } from "./expiry-D4_YKRKD.js";
|
|
7
|
+
import { n as internalsOf } from "./internals-Dk8A8fX3.js";
|
|
8
|
+
import { c as listPermissions, i as getRegistry, n as findPermission, o as isPermission } from "./permission-registry-Br4jSNVi.js";
|
|
9
|
+
import { n as isThenable$1 } from "./thenable-yD7SabqG.js";
|
|
10
|
+
import { c as isEdgeRelation, m as restrictedStops, o as groupEntries, s as isComputedRelation, u as isPrincipalRelation } from "./resource-relations-D_jw0BFx.js";
|
|
11
|
+
import { a as durationOf, g as secondsOf, m as rfc3339, p as nowSeconds } from "./time-DkWjdL98.js";
|
|
12
|
+
import { d as scopeList, l as scopeChain, m as tenantOf, o as resolveScope, u as scopeIdOf } from "./scopes-CUPSjo9q.js";
|
|
13
|
+
import { u as evaluateCondition } from "./delegation-DaXVdDhM.js";
|
|
14
|
+
import { _ as listPlans, a as authenticated, m as findRole, o as flattenGrantee } from "./grantee-Cn1HyUsI.js";
|
|
15
|
+
import { m as tenantsOf, r as isMembershipExpired } from "./tenancy-DN2QAx9z.js";
|
|
16
|
+
import { o as levelNames, r as grantList, t as declaredRoleNames } from "./policy-DLZJ5gAp.js";
|
|
17
|
+
import { r as isPortableCondition } from "./hosted-BZIAYvon.js";
|
|
18
|
+
import { n as OPERATION_RISKS, r as isOperationRisk } from "./permissions-B-wCP5ZC.js";
|
|
19
|
+
import { b as tenantCustomRoles, f as customRoleId, l as ceilingGrants, p as customRoleScope, v as resolveCustomRole } from "./evaluate-_R8QkWaf.js";
|
|
20
|
+
import { d as rankRoles, f as usesAssigns, i as collectSnapshotGrants, n as snapshotScopes, t as snapshotOf, u as bindingOf } from "./instance-snapshot-Cxq4ygFc.js";
|
|
21
|
+
import { n as relationId } from "./relations-BnCWnJGI.js";
|
|
22
|
+
import { r as snapshotGrant, t as buildSnapshot } from "./snapshot-CXUWfsNy.js";
|
|
23
|
+
import { n as LOCAL_CUSTOM_ROLE } from "./parse-local-manifest-CVSs6J9I.js";
|
|
24
|
+
import { r as resolveSubject, s as APP } from "./permdock-B9JxkBwC.js";
|
|
25
|
+
import { i as memoryApprovalStore } from "./store-4u_hxjWa.js";
|
|
26
|
+
import { n as wrapPermDock } from "./wrap-gX61EtpB.js";
|
|
27
|
+
//#region src/conditions/sql-function.ts
|
|
28
|
+
function sqlFunction(name, options) {
|
|
29
|
+
assertSqlFunctionName(name);
|
|
30
|
+
const args = options.args ?? [];
|
|
31
|
+
for (const arg of args) assertSqlFunctionArg(arg);
|
|
32
|
+
const twin = normalizeWhere(options.twin);
|
|
33
|
+
assertPortableTwin(twin);
|
|
34
|
+
return freezeDeep({
|
|
35
|
+
op: "sqlFunction",
|
|
36
|
+
name,
|
|
37
|
+
args,
|
|
38
|
+
twin
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
//#endregion
|
|
42
|
+
//#region src/core/memory-sources.ts
|
|
43
|
+
function memoryEntitlementSource(entries) {
|
|
44
|
+
return { entitlementsFor(_principal, options) {
|
|
45
|
+
return options.tenant === void 0 ? [] : [...entries[options.tenant] ?? []];
|
|
46
|
+
} };
|
|
47
|
+
}
|
|
48
|
+
function memorySettingsSource(settings) {
|
|
49
|
+
const byTenant = new Map(Object.entries(settings));
|
|
50
|
+
return Object.freeze({ settingsFor(tenant) {
|
|
51
|
+
return byTenant.get(tenant);
|
|
52
|
+
} });
|
|
53
|
+
}
|
|
54
|
+
/** Memberships keyed by principal id; `tenant` is not a filter, as for a database source. */
|
|
55
|
+
function memoryMembershipSource(entries) {
|
|
56
|
+
const byPrincipal = new Map(Object.entries(entries));
|
|
57
|
+
return Object.freeze({
|
|
58
|
+
membershipsFor(principal) {
|
|
59
|
+
return [...byPrincipal.get(principal.id) ?? []];
|
|
60
|
+
},
|
|
61
|
+
list(query) {
|
|
62
|
+
const members = [];
|
|
63
|
+
for (const [id, memberships] of byPrincipal) for (const membership of memberships) if (membership.scope === query.scope && membership.id === query.id) members.push({
|
|
64
|
+
principal: { id },
|
|
65
|
+
membership
|
|
66
|
+
});
|
|
67
|
+
return members;
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
/** A snapshot held in memory; `set` replaces it and notifies every subscriber. */
|
|
72
|
+
function memorySnapshotSource(initial) {
|
|
73
|
+
let current = initial;
|
|
74
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
75
|
+
return Object.freeze({
|
|
76
|
+
get: () => current,
|
|
77
|
+
subscribe(listener) {
|
|
78
|
+
listeners.add(listener);
|
|
79
|
+
return () => {
|
|
80
|
+
listeners.delete(listener);
|
|
81
|
+
};
|
|
82
|
+
},
|
|
83
|
+
set(snapshot) {
|
|
84
|
+
current = snapshot;
|
|
85
|
+
for (const listener of listeners) listener();
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
function memoryCustomRoleSource(customRoles) {
|
|
90
|
+
const byTenant = /* @__PURE__ */ new Map();
|
|
91
|
+
const global = [];
|
|
92
|
+
for (const role of customRoles) {
|
|
93
|
+
if (role.tenant === void 0) {
|
|
94
|
+
global.push(role);
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
const list = byTenant.get(role.tenant) ?? [];
|
|
98
|
+
list.push(role);
|
|
99
|
+
byTenant.set(role.tenant, list);
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
rolesFor(tenant) {
|
|
103
|
+
return [...byTenant.get(tenant) ?? []];
|
|
104
|
+
},
|
|
105
|
+
globalRoles() {
|
|
106
|
+
return [...global];
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
//#endregion
|
|
111
|
+
//#region src/core/operations.ts
|
|
112
|
+
const OPERATION_EFFECTS = Object.freeze([
|
|
113
|
+
"read",
|
|
114
|
+
"write",
|
|
115
|
+
"delete",
|
|
116
|
+
"send",
|
|
117
|
+
"spend",
|
|
118
|
+
"external"
|
|
119
|
+
]);
|
|
120
|
+
/** MCP tool-name characters (MCP 2025-11-25, Tools: Tool names), so a name works as a tool name unchanged. */
|
|
121
|
+
const NAME = /^[A-Za-z0-9_.-]{1,128}$/u;
|
|
122
|
+
const META_KEYS = /* @__PURE__ */ new Set([
|
|
123
|
+
"risk",
|
|
124
|
+
"effects",
|
|
125
|
+
"title"
|
|
126
|
+
]);
|
|
127
|
+
function isOperationEffect(value) {
|
|
128
|
+
return OPERATION_EFFECTS.some((effect) => effect === value);
|
|
129
|
+
}
|
|
130
|
+
function readMeta(name, value) {
|
|
131
|
+
if (value === void 0) return Object.freeze({});
|
|
132
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) throw new TypeError(`Operation "${name}": meta must be an object.`);
|
|
133
|
+
for (const key of Object.keys(value)) if (!META_KEYS.has(key)) throw new TypeError(`Operation "${name}": meta.${key} is not one of risk, effects, title.`);
|
|
134
|
+
const meta = {};
|
|
135
|
+
const risk = Reflect.get(value, "risk");
|
|
136
|
+
if (risk !== void 0) {
|
|
137
|
+
if (!isOperationRisk(risk)) throw new TypeError(`Operation "${name}": meta.risk must be one of ${OPERATION_RISKS.join(", ")}.`);
|
|
138
|
+
meta.risk = risk;
|
|
139
|
+
}
|
|
140
|
+
const effects = Reflect.get(value, "effects");
|
|
141
|
+
if (effects !== void 0) {
|
|
142
|
+
if (!Array.isArray(effects) || !effects.every(isOperationEffect) || new Set(effects).size !== effects.length) throw new TypeError(`Operation "${name}": meta.effects must list distinct values of ${OPERATION_EFFECTS.join(", ")}.`);
|
|
143
|
+
meta.effects = Object.freeze([...effects]);
|
|
144
|
+
}
|
|
145
|
+
const title = Reflect.get(value, "title");
|
|
146
|
+
if (title !== void 0) {
|
|
147
|
+
if (typeof title !== "string" || title.trim() === "") throw new TypeError(`Operation "${name}": meta.title must be a non-empty string.`);
|
|
148
|
+
meta.title = title;
|
|
149
|
+
}
|
|
150
|
+
return Object.freeze(meta);
|
|
151
|
+
}
|
|
152
|
+
function treeLeaf(keys, name, field, value) {
|
|
153
|
+
if (!isPermission(value)) throw new TypeError(`Operation "${name}": ${field} must be a permission from the tree.`);
|
|
154
|
+
if (!keys.has(value.key)) throw new TypeError(`Operation "${name}": ${field} ${value.key} is not a permission of the tree passed to defineOperations.`);
|
|
155
|
+
return value;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Binds each named operation to a permission of `permissions`. Throws at
|
|
159
|
+
* definition time for a permission or `results` outside the tree, a name that
|
|
160
|
+
* is not 1 to 128 of `A-Z a-z 0-9 _ . -` or repeats another case-insensitively,
|
|
161
|
+
* a `data` that is not a function, or meta outside the closed lists.
|
|
162
|
+
*/
|
|
163
|
+
function defineOperations(permissions, definitions) {
|
|
164
|
+
const keys = new Map(listPermissions(permissions).map((leaf) => [leaf.key, leaf]));
|
|
165
|
+
const seen = /* @__PURE__ */ new Map();
|
|
166
|
+
const out = {};
|
|
167
|
+
for (const name of Object.keys(definitions)) {
|
|
168
|
+
if (!NAME.test(name) || isForbiddenKey(name)) throw new TypeError(`Operation name "${name}" must be 1 to 128 of A-Z a-z 0-9 _ . -.`);
|
|
169
|
+
const folded = name.toLowerCase();
|
|
170
|
+
const previous = seen.get(folded);
|
|
171
|
+
if (previous !== void 0) throw new TypeError(`Operation name "${name}" is a duplicate of "${previous}".`);
|
|
172
|
+
seen.set(folded, name);
|
|
173
|
+
const definition = definitions[name];
|
|
174
|
+
if (definition === null || typeof definition !== "object" || Array.isArray(definition)) throw new TypeError(`Operation "${name}" must be an object.`);
|
|
175
|
+
const permission = treeLeaf(keys, name, "permission", Reflect.get(definition, "permission"));
|
|
176
|
+
const value = Reflect.get(definition, "data");
|
|
177
|
+
if (value !== void 0 && typeof value !== "function") throw new TypeError(`Operation "${name}": data must be a function.`);
|
|
178
|
+
const data = value;
|
|
179
|
+
const resultsValue = Reflect.get(definition, "results");
|
|
180
|
+
const results = resultsValue === void 0 ? void 0 : treeLeaf(keys, name, "results", resultsValue);
|
|
181
|
+
const meta = readMeta(name, Reflect.get(definition, "meta"));
|
|
182
|
+
out[name] = Object.freeze({
|
|
183
|
+
name,
|
|
184
|
+
permission,
|
|
185
|
+
...data === void 0 ? {} : { data },
|
|
186
|
+
...results === void 0 ? {} : { results },
|
|
187
|
+
meta
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
return Object.freeze(out);
|
|
191
|
+
}
|
|
192
|
+
//#endregion
|
|
193
|
+
//#region src/core/approval-policies.ts
|
|
194
|
+
/** The denial detail when an `ApprovalPolicySource` fails: every call an allow would grant denies with reason `approval`. */
|
|
195
|
+
/**
|
|
196
|
+
* The in-process `ApprovalPolicySource`, usable directly as the
|
|
197
|
+
* `approvalPolicies` option: it is the source and its feature at once.
|
|
198
|
+
*/
|
|
199
|
+
function memoryApprovalPolicySource(policies) {
|
|
200
|
+
const entries = [...policies];
|
|
201
|
+
const source = { approvalPoliciesFor(query) {
|
|
202
|
+
return entries.filter((entry) => entry.tenant === void 0 || query.tenants.includes(entry.tenant));
|
|
203
|
+
} };
|
|
204
|
+
return Object.freeze({
|
|
205
|
+
...source,
|
|
206
|
+
...approvalPolicies(source)
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
function approvalPolicies(source) {
|
|
210
|
+
return Object.freeze({
|
|
211
|
+
feature: "approval-policies",
|
|
212
|
+
load: (policy, tenants, auth) => approvalPoliciesFor(policy, source, tenants, auth)
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
function isStringList(value) {
|
|
216
|
+
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
217
|
+
}
|
|
218
|
+
function metaList(value, is) {
|
|
219
|
+
if (value === void 0) return;
|
|
220
|
+
if (!Array.isArray(value) || value.length === 0 || !value.every(is)) return "invalid";
|
|
221
|
+
return new Set(value);
|
|
222
|
+
}
|
|
223
|
+
/** An entry with no permission, matched on operation meta alone. */
|
|
224
|
+
function checkMetaOnly(raw, risk, effects) {
|
|
225
|
+
const { tenant, actors, where, check, approval } = raw;
|
|
226
|
+
if (risk === void 0 && effects === void 0 || where !== void 0 || tenant !== void 0 && typeof tenant !== "string" || actors !== void 0 && !isStringList(actors) || check !== void 0 && !isPortableCondition(check) || approval !== "human" && !isRecord(approval)) return { problem: "invalid" };
|
|
227
|
+
let normalized;
|
|
228
|
+
try {
|
|
229
|
+
normalized = normalizeApproval(approval);
|
|
230
|
+
} catch {
|
|
231
|
+
return { problem: "invalid" };
|
|
232
|
+
}
|
|
233
|
+
if (normalized === void 0 || normalized !== "human" && normalized.staleOn !== void 0) return { problem: "invalid" };
|
|
234
|
+
return { entry: compact({
|
|
235
|
+
risk,
|
|
236
|
+
effects,
|
|
237
|
+
tenant,
|
|
238
|
+
actors: actors === void 0 ? void 0 : new Set(actors),
|
|
239
|
+
check: check === void 0 ? void 0 : normalizeWhere(check),
|
|
240
|
+
approval: normalized
|
|
241
|
+
}) };
|
|
242
|
+
}
|
|
243
|
+
function checkOne(policy, raw) {
|
|
244
|
+
if (!isRecord(raw)) return { problem: "invalid" };
|
|
245
|
+
const risk = metaList(raw["risk"], isOperationRisk);
|
|
246
|
+
const effects = metaList(raw["effects"], isOperationEffect);
|
|
247
|
+
if (risk === "invalid" || effects === "invalid") return { problem: "invalid" };
|
|
248
|
+
if (raw["permission"] === void 0) return checkMetaOnly(raw, risk, effects);
|
|
249
|
+
if (typeof raw["permission"] !== "string") return { problem: "invalid" };
|
|
250
|
+
const permission = findPermission(policy.permissions, raw["permission"]);
|
|
251
|
+
if (permission === void 0) return { problem: "unknown-permission" };
|
|
252
|
+
const { tenant, actors, where, check, approval } = raw;
|
|
253
|
+
if (tenant !== void 0 && typeof tenant !== "string" || actors !== void 0 && !isStringList(actors) || where !== void 0 && !isPortableCondition(where) || check !== void 0 && !isPortableCondition(check) || approval !== "human" && !isRecord(approval)) return { problem: "invalid" };
|
|
254
|
+
if (where !== void 0 && permission.kind !== "instance") return { problem: "where-on-collection" };
|
|
255
|
+
let normalized;
|
|
256
|
+
try {
|
|
257
|
+
normalized = normalizeApproval(approval, permission.key);
|
|
258
|
+
if (normalized !== void 0) assertApprovalRelations([{
|
|
259
|
+
permission,
|
|
260
|
+
approval: normalized
|
|
261
|
+
}], policy.resources);
|
|
262
|
+
} catch {
|
|
263
|
+
return { problem: "invalid" };
|
|
264
|
+
}
|
|
265
|
+
if (normalized === void 0) return { problem: "invalid" };
|
|
266
|
+
if (normalized !== "human" && normalized.staleOn !== void 0 && (permission.kind !== "instance" || policy.resources.get(permission.resource)?.version === void 0)) return { problem: "stale-on-without-version" };
|
|
267
|
+
return { entry: compact({
|
|
268
|
+
permission: permission.key,
|
|
269
|
+
risk,
|
|
270
|
+
effects,
|
|
271
|
+
tenant,
|
|
272
|
+
actors: actors === void 0 ? void 0 : new Set(actors),
|
|
273
|
+
where: where === void 0 ? void 0 : normalizeWhere(where),
|
|
274
|
+
check: check === void 0 ? void 0 : normalizeWhere(check),
|
|
275
|
+
approval: normalized
|
|
276
|
+
}) };
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Checks one entry against the policy the way `approvalPoliciesFor` loads
|
|
280
|
+
* it, for an application to refuse a bad entry when it is saved instead of
|
|
281
|
+
* finding every call denied. Never throws.
|
|
282
|
+
*/
|
|
283
|
+
function validateApprovalPolicy(policy, entry) {
|
|
284
|
+
try {
|
|
285
|
+
const checked = checkOne(policy, entry);
|
|
286
|
+
return "problem" in checked ? {
|
|
287
|
+
ok: false,
|
|
288
|
+
problem: checked.problem
|
|
289
|
+
} : { ok: true };
|
|
290
|
+
} catch {
|
|
291
|
+
return {
|
|
292
|
+
ok: false,
|
|
293
|
+
problem: "invalid"
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* One raw entry checked against the policy: `null` when it names no
|
|
299
|
+
* permission the policy declares (it applies to nothing), `undefined` when
|
|
300
|
+
* it does not load.
|
|
301
|
+
*/
|
|
302
|
+
function loadOne(policy, raw) {
|
|
303
|
+
const checked = checkOne(policy, raw);
|
|
304
|
+
if ("entry" in checked) return checked.entry;
|
|
305
|
+
return checked.problem === "unknown-permission" ? null : void 0;
|
|
306
|
+
}
|
|
307
|
+
function loadAll(policy, raw) {
|
|
308
|
+
if (!Array.isArray(raw)) return "failed";
|
|
309
|
+
const out = [];
|
|
310
|
+
for (const item of raw) {
|
|
311
|
+
const loaded = loadOne(policy, item);
|
|
312
|
+
if (loaded === void 0) return "failed";
|
|
313
|
+
if (loaded !== null) out.push(loaded);
|
|
314
|
+
}
|
|
315
|
+
return Object.freeze({ tighten: (code, input) => tightenApproval(code, out, input) });
|
|
316
|
+
}
|
|
317
|
+
/** Reads the source once for `tenants`; a throw or an invalid entry loads as `"failed"`. */
|
|
318
|
+
function approvalPoliciesFor(policy, source, tenants, auth) {
|
|
319
|
+
const threw = () => {
|
|
320
|
+
auth.push({
|
|
321
|
+
reason: "source-threw",
|
|
322
|
+
source: "approvalPolicies"
|
|
323
|
+
});
|
|
324
|
+
return "failed";
|
|
325
|
+
};
|
|
326
|
+
let raw;
|
|
327
|
+
try {
|
|
328
|
+
raw = source.approvalPoliciesFor({ tenants });
|
|
329
|
+
} catch {
|
|
330
|
+
return threw();
|
|
331
|
+
}
|
|
332
|
+
if (isThenable$1(raw)) return Promise.resolve(raw).then((value) => loadAll(policy, value), threw);
|
|
333
|
+
return loadAll(policy, raw);
|
|
334
|
+
}
|
|
335
|
+
function metaMatches(entry, operation) {
|
|
336
|
+
if (entry.risk === void 0 && entry.effects === void 0) return true;
|
|
337
|
+
if (operation === void 0) return false;
|
|
338
|
+
if (entry.risk !== void 0 && (operation.risk === void 0 || !entry.risk.has(operation.risk))) return false;
|
|
339
|
+
const effects = entry.effects;
|
|
340
|
+
return effects === void 0 || (operation.effects ?? []).some((effect) => effects.has(effect));
|
|
341
|
+
}
|
|
342
|
+
function matches(entry, tenant, subject, current, next, now, scopes) {
|
|
343
|
+
if (entry.tenant !== void 0 && entry.tenant !== tenant) return false;
|
|
344
|
+
if (entry.actors !== void 0 && (subject.actor === void 0 || !entry.actors.has(subject.actor.kind))) return false;
|
|
345
|
+
if (entry.where !== void 0 && !evaluateCondition(entry.where, current, subject, now, scopes)) return false;
|
|
346
|
+
return entry.check === void 0 || evaluateCondition(entry.check, next ?? current, subject, now, scopes);
|
|
347
|
+
}
|
|
348
|
+
function asStages(approval) {
|
|
349
|
+
if (approval === "human") return [{ by: authenticated() }];
|
|
350
|
+
if (approval.stages !== void 0) return approval.stages;
|
|
351
|
+
return [compact({
|
|
352
|
+
by: approval.by ?? authenticated(),
|
|
353
|
+
quorum: approval.quorum
|
|
354
|
+
})];
|
|
355
|
+
}
|
|
356
|
+
/** An entry's stages, each carrying the entry's escalation unless it has its own. */
|
|
357
|
+
function entryStages(approval) {
|
|
358
|
+
const stages = asStages(approval);
|
|
359
|
+
if (approval === "human" || approval.escalation === void 0) return stages;
|
|
360
|
+
const escalation = approval.escalation;
|
|
361
|
+
return stages.map((stage) => stage.escalation === void 0 ? compact({
|
|
362
|
+
by: stage.by,
|
|
363
|
+
quorum: stage.quorum,
|
|
364
|
+
escalation
|
|
365
|
+
}) : stage);
|
|
366
|
+
}
|
|
367
|
+
function shorter(a, b) {
|
|
368
|
+
if (a === void 0) return b;
|
|
369
|
+
if (b === void 0) return a;
|
|
370
|
+
return (durationOf(b) ?? Infinity) < (durationOf(a) ?? Infinity) ? b : a;
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* `code` with every matching entry added as further stages: the result
|
|
374
|
+
* needs all of them (`'sequential'` when any part is ordered), keeps the
|
|
375
|
+
* code's escalation, the shortest `ttl`, and `distinct` unless every part
|
|
376
|
+
* opts out. Without a matching entry, `code` unchanged.
|
|
377
|
+
*/
|
|
378
|
+
function tightenApproval(code, entries, input) {
|
|
379
|
+
const extra = entries.filter((entry) => (entry.permission === void 0 || entry.permission === input.permission) && metaMatches(entry, input.operation) && matches(entry, input.tenant, input.subject, input.current, input.next, input.now, input.scopes));
|
|
380
|
+
if (extra.length === 0) return code;
|
|
381
|
+
const parts = [...code === void 0 ? [] : [code], ...extra.map((entry) => entry.approval)];
|
|
382
|
+
const stages = [];
|
|
383
|
+
const seen = /* @__PURE__ */ new Set();
|
|
384
|
+
let sequential = false;
|
|
385
|
+
let distinct = false;
|
|
386
|
+
let staleOn;
|
|
387
|
+
let ttl;
|
|
388
|
+
for (const [index, part] of parts.entries()) {
|
|
389
|
+
const fromCode = code !== void 0 && index === 0;
|
|
390
|
+
for (const stage of fromCode ? asStages(part) : entryStages(part)) {
|
|
391
|
+
const key = JSON.stringify(stage);
|
|
392
|
+
if (!seen.has(key)) {
|
|
393
|
+
seen.add(key);
|
|
394
|
+
stages.push(stage);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
if (part === "human") {
|
|
398
|
+
distinct = true;
|
|
399
|
+
continue;
|
|
400
|
+
}
|
|
401
|
+
sequential ||= part.mode === "sequential";
|
|
402
|
+
distinct ||= part.distinct !== false;
|
|
403
|
+
staleOn ??= part.staleOn;
|
|
404
|
+
ttl = shorter(ttl, part.ttl);
|
|
405
|
+
}
|
|
406
|
+
return compact({
|
|
407
|
+
mode: sequential ? "sequential" : "all",
|
|
408
|
+
stages,
|
|
409
|
+
distinct: distinct ? void 0 : false,
|
|
410
|
+
staleOn,
|
|
411
|
+
ttl,
|
|
412
|
+
escalation: code === void 0 || code === "human" ? void 0 : code.escalation
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
//#endregion
|
|
416
|
+
//#region src/core/stripe-entitlements.ts
|
|
417
|
+
const PAGE = 100;
|
|
418
|
+
const MAX_PAGES = 10;
|
|
419
|
+
/**
|
|
420
|
+
* Plan names from Stripe Entitlements: the `lookup_key` of every active
|
|
421
|
+
* entitlement of the tenant's Stripe customer. `customer` maps the active
|
|
422
|
+
* tenant to its customer id; no tenant or no customer means no entitlements.
|
|
423
|
+
* Pass it as `entitlements`, and grant with `plan('<lookup_key>')`.
|
|
424
|
+
*/
|
|
425
|
+
function stripeEntitlementSource(options) {
|
|
426
|
+
return { async entitlementsFor(principal, query) {
|
|
427
|
+
if (query.tenant === void 0) return [];
|
|
428
|
+
const customer = await options.customer(query.tenant, principal);
|
|
429
|
+
if (customer === void 0 || customer === "") return [];
|
|
430
|
+
const keys = /* @__PURE__ */ new Set();
|
|
431
|
+
let after;
|
|
432
|
+
for (let page = 0; page < MAX_PAGES; page += 1) {
|
|
433
|
+
const result = await options.stripe.entitlements.activeEntitlements.list(after === void 0 ? {
|
|
434
|
+
customer,
|
|
435
|
+
limit: PAGE
|
|
436
|
+
} : {
|
|
437
|
+
customer,
|
|
438
|
+
limit: PAGE,
|
|
439
|
+
starting_after: after
|
|
440
|
+
});
|
|
441
|
+
for (const item of result.data) if (typeof item.lookup_key === "string" && item.lookup_key !== "") keys.add(item.lookup_key);
|
|
442
|
+
const last = result.data.at(-1);
|
|
443
|
+
if (!result.has_more || last === void 0) break;
|
|
444
|
+
after = last.id;
|
|
445
|
+
}
|
|
446
|
+
return [...keys].toSorted();
|
|
447
|
+
} };
|
|
448
|
+
}
|
|
449
|
+
//#endregion
|
|
450
|
+
//#region src/core/limits.ts
|
|
451
|
+
function isThenable(value) {
|
|
452
|
+
return value !== null && typeof value === "object" && "then" in value && typeof value.then === "function";
|
|
453
|
+
}
|
|
454
|
+
function limitWindowId(per, now) {
|
|
455
|
+
const seconds = durationOf(per);
|
|
456
|
+
return seconds === void 0 ? void 0 : String(Math.floor(now / seconds));
|
|
457
|
+
}
|
|
458
|
+
function limitCacheKey(input, now) {
|
|
459
|
+
const window = limitWindowId(input.per, now);
|
|
460
|
+
return window === void 0 ? void 0 : JSON.stringify([
|
|
461
|
+
input.tenant ?? null,
|
|
462
|
+
input.subjectId,
|
|
463
|
+
input.key,
|
|
464
|
+
input.per,
|
|
465
|
+
window
|
|
466
|
+
]);
|
|
467
|
+
}
|
|
468
|
+
function locate$1(input) {
|
|
469
|
+
const now = nowSeconds(input.now);
|
|
470
|
+
const seconds = durationOf(input.per);
|
|
471
|
+
const id = limitCacheKey(input, now);
|
|
472
|
+
if (seconds === void 0 || id === void 0) return;
|
|
473
|
+
return {
|
|
474
|
+
id,
|
|
475
|
+
until: (Math.floor(now / seconds) + 1) * seconds,
|
|
476
|
+
now
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
function memoryLimitStore() {
|
|
480
|
+
const used = /* @__PURE__ */ new Map();
|
|
481
|
+
let earliest = Number.POSITIVE_INFINITY;
|
|
482
|
+
const sweep = (now) => {
|
|
483
|
+
if (now < earliest) return;
|
|
484
|
+
earliest = Number.POSITIVE_INFINITY;
|
|
485
|
+
for (const [id, entry] of used) if (entry.until <= now) used.delete(id);
|
|
486
|
+
else earliest = Math.min(earliest, entry.until);
|
|
487
|
+
};
|
|
488
|
+
return {
|
|
489
|
+
remaining(input) {
|
|
490
|
+
const cap = input.count;
|
|
491
|
+
const slot = locate$1(input);
|
|
492
|
+
if (!Number.isFinite(cap) || cap <= 0 || slot === void 0) return { remaining: -1 };
|
|
493
|
+
return { remaining: cap - (used.get(slot.id)?.count ?? 0) };
|
|
494
|
+
},
|
|
495
|
+
consume(input) {
|
|
496
|
+
const cap = input.count;
|
|
497
|
+
const slot = locate$1(input);
|
|
498
|
+
if (!Number.isFinite(cap) || cap <= 0 || slot === void 0) return { remaining: -1 };
|
|
499
|
+
sweep(slot.now);
|
|
500
|
+
const seen = used.get(slot.id)?.count ?? 0;
|
|
501
|
+
if (seen >= cap) return { remaining: -1 };
|
|
502
|
+
used.set(slot.id, {
|
|
503
|
+
count: seen + 1,
|
|
504
|
+
until: slot.until
|
|
505
|
+
});
|
|
506
|
+
earliest = Math.min(earliest, slot.until);
|
|
507
|
+
return { remaining: cap - seen - 1 };
|
|
508
|
+
},
|
|
509
|
+
size() {
|
|
510
|
+
return used.size;
|
|
511
|
+
}
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* `left` is what remains once this call counts: the store's answer after
|
|
516
|
+
* `consume`, or one less than `remaining` when only peeking. Below zero the
|
|
517
|
+
* call is past the count.
|
|
518
|
+
*/
|
|
519
|
+
function verdictFor(limit, left, now) {
|
|
520
|
+
const seconds = durationOf(limit.per);
|
|
521
|
+
if (seconds === void 0) return {
|
|
522
|
+
ok: false,
|
|
523
|
+
reason: "limit-unavailable"
|
|
524
|
+
};
|
|
525
|
+
const resetsAt = rfc3339((Math.floor(now / seconds) + 1) * seconds);
|
|
526
|
+
if (left < 0) return limit.mode === "soft" ? {
|
|
527
|
+
ok: true,
|
|
528
|
+
quota: {
|
|
529
|
+
remaining: 0,
|
|
530
|
+
resetsAt
|
|
531
|
+
},
|
|
532
|
+
obligations: [{ kind: "over-limit" }]
|
|
533
|
+
} : {
|
|
534
|
+
ok: false,
|
|
535
|
+
reason: "limit",
|
|
536
|
+
detail: {
|
|
537
|
+
count: limit.count,
|
|
538
|
+
per: seconds,
|
|
539
|
+
resetsAt
|
|
540
|
+
}
|
|
541
|
+
};
|
|
542
|
+
return limit.alertAt !== void 0 && (limit.count - left) / limit.count >= limit.alertAt ? {
|
|
543
|
+
ok: true,
|
|
544
|
+
quota: {
|
|
545
|
+
remaining: left,
|
|
546
|
+
resetsAt
|
|
547
|
+
},
|
|
548
|
+
obligations: [{ kind: "near-limit" }]
|
|
549
|
+
} : {
|
|
550
|
+
ok: true,
|
|
551
|
+
quota: {
|
|
552
|
+
remaining: left,
|
|
553
|
+
resetsAt
|
|
554
|
+
}
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
function limits(store) {
|
|
558
|
+
return Object.freeze({
|
|
559
|
+
feature: "limits",
|
|
560
|
+
quota: (input) => applyQuota(store, input)
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
function applyQuota(store, input) {
|
|
564
|
+
const limit = input.limit;
|
|
565
|
+
const payload = {
|
|
566
|
+
key: input.permissionKey,
|
|
567
|
+
subjectId: input.subjectId,
|
|
568
|
+
count: limit.count,
|
|
569
|
+
per: limit.per,
|
|
570
|
+
now: { epochMilliseconds: input.now * 1e3 },
|
|
571
|
+
...input.tenant === void 0 ? {} : { tenant: input.tenant }
|
|
572
|
+
};
|
|
573
|
+
const cacheKey = limitCacheKey(payload, input.now);
|
|
574
|
+
if (cacheKey === void 0) return {
|
|
575
|
+
ok: false,
|
|
576
|
+
reason: "limit-unavailable"
|
|
577
|
+
};
|
|
578
|
+
if (!input.consume) {
|
|
579
|
+
try {
|
|
580
|
+
const peeked = store.remaining(payload);
|
|
581
|
+
if (isThenable(peeked)) return {
|
|
582
|
+
ok: false,
|
|
583
|
+
reason: "limit-unavailable"
|
|
584
|
+
};
|
|
585
|
+
if (peeked !== void 0) return verdictFor(limit, peeked.remaining - 1, input.now);
|
|
586
|
+
} catch {
|
|
587
|
+
return {
|
|
588
|
+
ok: false,
|
|
589
|
+
reason: "limit-unavailable"
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
const cached = input.cache.get(cacheKey);
|
|
593
|
+
if (cached === void 0) return {
|
|
594
|
+
ok: false,
|
|
595
|
+
reason: "limit-unavailable"
|
|
596
|
+
};
|
|
597
|
+
return verdictFor(limit, cached - 1, input.now);
|
|
598
|
+
}
|
|
599
|
+
try {
|
|
600
|
+
const consumed = store.consume(payload);
|
|
601
|
+
if (isThenable(consumed)) return {
|
|
602
|
+
ok: false,
|
|
603
|
+
reason: "limit-unavailable"
|
|
604
|
+
};
|
|
605
|
+
input.cache.set(cacheKey, consumed.remaining);
|
|
606
|
+
return verdictFor(limit, consumed.remaining, input.now);
|
|
607
|
+
} catch {
|
|
608
|
+
return {
|
|
609
|
+
ok: false,
|
|
610
|
+
reason: "limit-unavailable"
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
//#endregion
|
|
615
|
+
//#region src/core/memory-relations.ts
|
|
616
|
+
/** The holders one edge-table row names for `spec`: none when the row does not belong to it. */
|
|
617
|
+
function tableHolders(resourceName, spec, row, id) {
|
|
618
|
+
if (relationId(ownGet(row, spec.object ?? `${resourceName}_id`)) !== id) return [];
|
|
619
|
+
for (const [column, value] of Object.entries(spec.match ?? {})) if (ownGet(row, column) !== value) return [];
|
|
620
|
+
const expiresAt = spec.expiresAt === void 0 ? void 0 : seconds(ownGet(row, spec.expiresAt));
|
|
621
|
+
const period = expiresAt === void 0 ? {} : { expiresAt };
|
|
622
|
+
const groups = spec.groups;
|
|
623
|
+
const direct = relationId(ownGet(row, spec.subject ?? "user_id"));
|
|
624
|
+
if (groups === void 0) return direct === void 0 ? [] : [{
|
|
625
|
+
principal: { id: direct },
|
|
626
|
+
...period
|
|
627
|
+
}];
|
|
628
|
+
if (groups.column === void 0) {
|
|
629
|
+
const out = direct === void 0 ? [] : [{
|
|
630
|
+
principal: { id: direct },
|
|
631
|
+
...period
|
|
632
|
+
}];
|
|
633
|
+
for (const group of groupEntries(spec)) {
|
|
634
|
+
const groupId = relationId(ownGet(row, group.subject));
|
|
635
|
+
if (groupId !== void 0) out.push({
|
|
636
|
+
group: {
|
|
637
|
+
resource: group.resource,
|
|
638
|
+
id: groupId,
|
|
639
|
+
relation: group.relation
|
|
640
|
+
},
|
|
641
|
+
...period
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
return out;
|
|
645
|
+
}
|
|
646
|
+
const kind = ownGet(row, groups.column);
|
|
647
|
+
if (kind === null || kind === void 0 || kind === groups.direct) return direct === void 0 ? [] : [{
|
|
648
|
+
principal: { id: direct },
|
|
649
|
+
...period
|
|
650
|
+
}];
|
|
651
|
+
const group = groupEntries(spec).find((entry) => entry.resource === kind);
|
|
652
|
+
const groupId = group === void 0 ? void 0 : relationId(ownGet(row, group.subject));
|
|
653
|
+
return group === void 0 || groupId === void 0 ? [] : [{
|
|
654
|
+
group: {
|
|
655
|
+
resource: group.resource,
|
|
656
|
+
id: groupId,
|
|
657
|
+
relation: group.relation
|
|
658
|
+
},
|
|
659
|
+
...period
|
|
660
|
+
}];
|
|
661
|
+
}
|
|
662
|
+
function seconds(value) {
|
|
663
|
+
if (value instanceof Date) {
|
|
664
|
+
const time = value.getTime();
|
|
665
|
+
return Number.isNaN(time) ? void 0 : time / 1e3;
|
|
666
|
+
}
|
|
667
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
668
|
+
if (typeof value === "string") {
|
|
669
|
+
const parsed = Date.parse(value);
|
|
670
|
+
return Number.isNaN(parsed) ? void 0 : parsed / 1e3;
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
function restrictedOf(node, row) {
|
|
674
|
+
return node.restricted !== void 0 && row !== void 0 && ownGet(row, node.restricted) === true;
|
|
675
|
+
}
|
|
676
|
+
function edgeHolder(edge) {
|
|
677
|
+
const holder = { principal: { id: edge.principal } };
|
|
678
|
+
if (edge.startsAt !== void 0) holder.startsAt = edge.startsAt;
|
|
679
|
+
if (edge.expiresAt !== void 0) holder.expiresAt = edge.expiresAt;
|
|
680
|
+
return holder;
|
|
681
|
+
}
|
|
682
|
+
/**
|
|
683
|
+
* An in-process `RelationSource` over plain rows and edges, read against the
|
|
684
|
+
* resource definitions in `permissions`. For tests, fixtures and small apps.
|
|
685
|
+
*/
|
|
686
|
+
function memoryRelationSource(permissions, data = {}) {
|
|
687
|
+
const registry = getRegistry(permissions);
|
|
688
|
+
const byId = /* @__PURE__ */ new Map();
|
|
689
|
+
for (const [name, rows] of Object.entries(data.rows ?? {})) {
|
|
690
|
+
const node = registry.get(name);
|
|
691
|
+
const index = /* @__PURE__ */ new Map();
|
|
692
|
+
for (const row of rows) {
|
|
693
|
+
const id = relationId(ownGet(row, node?.id ?? "id"));
|
|
694
|
+
if (id !== void 0) index.set(id, row);
|
|
695
|
+
}
|
|
696
|
+
byId.set(name, index);
|
|
697
|
+
}
|
|
698
|
+
const rowOf = (resource, id) => byId.get(resource)?.get(id);
|
|
699
|
+
return {
|
|
700
|
+
row({ resource, id }) {
|
|
701
|
+
return rowOf(resource, id) ?? null;
|
|
702
|
+
},
|
|
703
|
+
ancestors({ resource, id, depth, through }) {
|
|
704
|
+
const node = registry.get(resource);
|
|
705
|
+
const start = rowOf(resource, id);
|
|
706
|
+
if (node === void 0 || start === void 0) return { ancestors: [] };
|
|
707
|
+
if (through !== "parent") {
|
|
708
|
+
const link = Object.hasOwn(node.links, through) ? node.links[through] : void 0;
|
|
709
|
+
const next = link === void 0 ? void 0 : relationId(ownGet(start, link.field));
|
|
710
|
+
if (next === void 0 || depth < 1) return { ancestors: [] };
|
|
711
|
+
return { ancestors: [{ id: next }] };
|
|
712
|
+
}
|
|
713
|
+
const restricted = restrictedOf(node, start);
|
|
714
|
+
const stops = restrictedStops(node, "parent");
|
|
715
|
+
if (restricted && stops || node.parent === void 0 || node.parent.resource !== resource) return restricted ? {
|
|
716
|
+
restricted: true,
|
|
717
|
+
ancestors: []
|
|
718
|
+
} : { ancestors: [] };
|
|
719
|
+
const field = node.parent.field;
|
|
720
|
+
const seen = /* @__PURE__ */ new Set([id]);
|
|
721
|
+
const ancestors = [];
|
|
722
|
+
let next = relationId(ownGet(start, field));
|
|
723
|
+
while (next !== void 0 && ancestors.length < depth) {
|
|
724
|
+
const row = rowOf(resource, next);
|
|
725
|
+
if (row === void 0) {
|
|
726
|
+
next = void 0;
|
|
727
|
+
break;
|
|
728
|
+
}
|
|
729
|
+
const hidden = restrictedOf(node, row);
|
|
730
|
+
ancestors.push(hidden ? {
|
|
731
|
+
id: next,
|
|
732
|
+
restricted: true
|
|
733
|
+
} : { id: next });
|
|
734
|
+
if (seen.has(next) || hidden && stops) {
|
|
735
|
+
next = void 0;
|
|
736
|
+
break;
|
|
737
|
+
}
|
|
738
|
+
seen.add(next);
|
|
739
|
+
next = relationId(ownGet(row, field));
|
|
740
|
+
}
|
|
741
|
+
const chain = restricted ? {
|
|
742
|
+
restricted: true,
|
|
743
|
+
ancestors
|
|
744
|
+
} : { ancestors };
|
|
745
|
+
return next === void 0 || rowOf(resource, next) === void 0 ? chain : {
|
|
746
|
+
...chain,
|
|
747
|
+
truncated: true
|
|
748
|
+
};
|
|
749
|
+
},
|
|
750
|
+
related({ resource, id, relation }) {
|
|
751
|
+
const spec = registry.get(resource)?.relations[relation];
|
|
752
|
+
if (spec === void 0 || isComputedRelation(spec)) return [];
|
|
753
|
+
if (isEdgeRelation(spec)) {
|
|
754
|
+
const table = data.tables?.[spec.edge];
|
|
755
|
+
if (table !== void 0) return table.flatMap((row) => tableHolders(resource, spec, row, id));
|
|
756
|
+
return (data.edges?.[resource]?.[relation] ?? []).filter((edge) => edge.id === id).map(edgeHolder);
|
|
757
|
+
}
|
|
758
|
+
const row = rowOf(resource, id);
|
|
759
|
+
if (row === void 0) return [];
|
|
760
|
+
if (isPrincipalRelation(spec)) {
|
|
761
|
+
const holder = relationId(ownGet(row, spec.principal));
|
|
762
|
+
if (holder === void 0) return [];
|
|
763
|
+
const startsAt = spec.period?.startsAt === void 0 ? void 0 : seconds(ownGet(row, spec.period.startsAt));
|
|
764
|
+
const expiresAt = spec.period?.expiresAt === void 0 ? void 0 : seconds(ownGet(row, spec.period.expiresAt));
|
|
765
|
+
return [{
|
|
766
|
+
principal: { id: holder },
|
|
767
|
+
...startsAt === void 0 ? {} : { startsAt },
|
|
768
|
+
...expiresAt === void 0 ? {} : { expiresAt }
|
|
769
|
+
}];
|
|
770
|
+
}
|
|
771
|
+
if (spec.memberOf !== void 0) return [];
|
|
772
|
+
const holder = relationId(ownGet(row, spec.field));
|
|
773
|
+
return holder === void 0 ? [] : [{ principal: { id: holder } }];
|
|
774
|
+
}
|
|
775
|
+
};
|
|
776
|
+
}
|
|
777
|
+
//#endregion
|
|
778
|
+
//#region src/core/policy-sign.ts
|
|
779
|
+
/**
|
|
780
|
+
* Signs `document` as a `vnd.permdock.policy+jwt`: the `policy` claim holds
|
|
781
|
+
* every field but `id` and `issuedAt`, which become `jti` and `iat`, so
|
|
782
|
+
* `policyDocumentFromClaims` reads it back. `iss` is the signer's own.
|
|
783
|
+
*/
|
|
784
|
+
function signPolicyDocument(document, signer, options) {
|
|
785
|
+
const { id, issuedAt, ...policy } = document;
|
|
786
|
+
const iat = secondsOf(issuedAt);
|
|
787
|
+
return signer.sign({ policy }, {
|
|
788
|
+
typ: "vnd.permdock.policy+jwt",
|
|
789
|
+
audience: options.audience,
|
|
790
|
+
jti: id,
|
|
791
|
+
...iat === void 0 ? {} : { issuedAt: iat },
|
|
792
|
+
...options.expiresAt === void 0 ? {} : { expiresAt: options.expiresAt }
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
//#endregion
|
|
796
|
+
//#region src/core/arazzo.ts
|
|
797
|
+
const VERSION = /^1\.[01]\.\d+(?:-.+)?$/u;
|
|
798
|
+
const STEP_TARGETS = [
|
|
799
|
+
"operationId",
|
|
800
|
+
"operationPath",
|
|
801
|
+
"workflowId",
|
|
802
|
+
"channelPath"
|
|
803
|
+
];
|
|
804
|
+
const denied = (reason) => freezeDeep({
|
|
805
|
+
outcome: "denied",
|
|
806
|
+
denials: [{
|
|
807
|
+
role: null,
|
|
808
|
+
reason
|
|
809
|
+
}],
|
|
810
|
+
alternatives: []
|
|
811
|
+
});
|
|
812
|
+
function asString(value) {
|
|
813
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
814
|
+
}
|
|
815
|
+
function parseParameters(raw, components, where) {
|
|
816
|
+
if (raw === void 0) return [];
|
|
817
|
+
if (!Array.isArray(raw)) return `${where}: parameters must be an array`;
|
|
818
|
+
const out = [];
|
|
819
|
+
for (const item of raw) {
|
|
820
|
+
if (!isRecord(item)) return `${where}: a parameter is not an object`;
|
|
821
|
+
const reference = asString(item["reference"]);
|
|
822
|
+
if (reference !== void 0) {
|
|
823
|
+
const match = /^\$components\.parameters\.(.+)$/u.exec(reference);
|
|
824
|
+
const target = match?.[1] === void 0 ? void 0 : components[match[1]];
|
|
825
|
+
const name = isRecord(target) ? asString(target["name"]) : void 0;
|
|
826
|
+
if (!isRecord(target) || name === void 0 || !("value" in target)) return `${where}: unresolved parameter reference ${reference}`;
|
|
827
|
+
out.push({
|
|
828
|
+
name,
|
|
829
|
+
value: "value" in item ? item["value"] : target["value"]
|
|
830
|
+
});
|
|
831
|
+
continue;
|
|
832
|
+
}
|
|
833
|
+
const name = asString(item["name"]);
|
|
834
|
+
if (name === void 0 || !("value" in item)) return `${where}: a parameter needs a name and a value`;
|
|
835
|
+
out.push({
|
|
836
|
+
name,
|
|
837
|
+
value: item["value"]
|
|
838
|
+
});
|
|
839
|
+
}
|
|
840
|
+
return out;
|
|
841
|
+
}
|
|
842
|
+
function parseDocument(arazzo) {
|
|
843
|
+
if (!isRecord(arazzo)) return "Arazzo document is not an object";
|
|
844
|
+
const version = arazzo["arazzo"];
|
|
845
|
+
if (typeof version !== "string" || !VERSION.test(version)) return "arazzo must be a 1.0.x or 1.1.x version";
|
|
846
|
+
if (!isRecord(arazzo["info"])) return "info is required";
|
|
847
|
+
const rawSources = arazzo["sourceDescriptions"];
|
|
848
|
+
if (!Array.isArray(rawSources) || rawSources.length === 0) return "sourceDescriptions needs at least one entry";
|
|
849
|
+
const sources = [];
|
|
850
|
+
for (const source of rawSources) {
|
|
851
|
+
const name = isRecord(source) ? asString(source["name"]) : void 0;
|
|
852
|
+
if (!isRecord(source) || name === void 0 || asString(source["url"]) === void 0) return "a sourceDescription needs a name and a url";
|
|
853
|
+
if (sources.some((item) => item.name === name)) return `duplicate sourceDescription ${name}`;
|
|
854
|
+
sources.push({
|
|
855
|
+
name,
|
|
856
|
+
type: asString(source["type"]) ?? "openapi"
|
|
857
|
+
});
|
|
858
|
+
}
|
|
859
|
+
const components = isRecord(arazzo["components"]) && isRecord(arazzo["components"]["parameters"]) ? arazzo["components"]["parameters"] : {};
|
|
860
|
+
const rawWorkflows = arazzo["workflows"];
|
|
861
|
+
if (!Array.isArray(rawWorkflows) || rawWorkflows.length === 0) return "workflows needs at least one entry";
|
|
862
|
+
const workflows = [];
|
|
863
|
+
for (const item of rawWorkflows) {
|
|
864
|
+
const workflowId = isRecord(item) ? asString(item["workflowId"]) : void 0;
|
|
865
|
+
if (!isRecord(item) || workflowId === void 0) return "a workflow needs a workflowId";
|
|
866
|
+
if (workflows.some((workflow) => workflow.workflowId === workflowId)) return `duplicate workflowId ${workflowId}`;
|
|
867
|
+
const parameters = parseParameters(item["parameters"], components, workflowId);
|
|
868
|
+
if (typeof parameters === "string") return parameters;
|
|
869
|
+
const rawSteps = item["steps"];
|
|
870
|
+
if (!Array.isArray(rawSteps) || rawSteps.length === 0) return `${workflowId}: steps needs at least one entry`;
|
|
871
|
+
const steps = [];
|
|
872
|
+
for (const step of rawSteps) {
|
|
873
|
+
const stepId = isRecord(step) ? asString(step["stepId"]) : void 0;
|
|
874
|
+
if (!isRecord(step) || stepId === void 0) return `${workflowId}: a step needs a stepId`;
|
|
875
|
+
if (steps.some((other) => other.stepId === stepId)) return `${workflowId}: duplicate stepId ${stepId}`;
|
|
876
|
+
const targets = STEP_TARGETS.filter((field) => asString(step[field]) !== void 0);
|
|
877
|
+
if (targets.length !== 1 && !(targets.length === 2 && targets.includes("operationId") && targets.includes("channelPath"))) return `${workflowId}.${stepId}: a step needs exactly one of operationId, operationPath or workflowId`;
|
|
878
|
+
const stepParameters = parseParameters(step["parameters"], components, `${workflowId}.${stepId}`);
|
|
879
|
+
if (typeof stepParameters === "string") return stepParameters;
|
|
880
|
+
steps.push(compact({
|
|
881
|
+
stepId,
|
|
882
|
+
operationId: asString(step["operationId"]),
|
|
883
|
+
operationPath: asString(step["operationPath"]),
|
|
884
|
+
workflowId: asString(step["workflowId"]),
|
|
885
|
+
channelPath: asString(step["channelPath"]),
|
|
886
|
+
parameters: stepParameters
|
|
887
|
+
}));
|
|
888
|
+
}
|
|
889
|
+
workflows.push({
|
|
890
|
+
workflowId,
|
|
891
|
+
parameters,
|
|
892
|
+
steps
|
|
893
|
+
});
|
|
894
|
+
}
|
|
895
|
+
return {
|
|
896
|
+
sources,
|
|
897
|
+
workflows
|
|
898
|
+
};
|
|
899
|
+
}
|
|
900
|
+
function unescapePointer(token) {
|
|
901
|
+
return token.replaceAll("~1", "/").replaceAll("~0", "~");
|
|
902
|
+
}
|
|
903
|
+
function readPointer(doc, pointer) {
|
|
904
|
+
if (!pointer.startsWith("#/")) return;
|
|
905
|
+
let current = doc;
|
|
906
|
+
for (const raw of pointer.slice(2).split("/")) {
|
|
907
|
+
if (!isRecord(current)) return;
|
|
908
|
+
let token;
|
|
909
|
+
try {
|
|
910
|
+
token = unescapePointer(decodeURIComponent(raw));
|
|
911
|
+
} catch {
|
|
912
|
+
return;
|
|
913
|
+
}
|
|
914
|
+
if (!Object.hasOwn(current, token)) return;
|
|
915
|
+
current = current[token];
|
|
916
|
+
}
|
|
917
|
+
return current;
|
|
918
|
+
}
|
|
919
|
+
function findOperation(openapi, operationId) {
|
|
920
|
+
if (!isRecord(openapi) || !isRecord(openapi["paths"])) return;
|
|
921
|
+
for (const pathItem of Object.values(openapi["paths"])) {
|
|
922
|
+
if (!isRecord(pathItem)) continue;
|
|
923
|
+
for (const [method, operation] of Object.entries(pathItem)) if (!method.startsWith("x-") && isRecord(operation) && operation["operationId"] === operationId) return operation;
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
/** One description, or a map of descriptions keyed by source `name`. */
|
|
927
|
+
function descriptionFor(openapi, name) {
|
|
928
|
+
if (isRecord(openapi) && !("openapi" in openapi) && !("paths" in openapi) && isRecord(openapi[name])) return openapi[name];
|
|
929
|
+
return openapi;
|
|
930
|
+
}
|
|
931
|
+
function permissionKeysOf(node) {
|
|
932
|
+
if (!isRecord(node)) return [];
|
|
933
|
+
const raw = node["x-permdock-permissions"];
|
|
934
|
+
if (!Array.isArray(raw)) return [];
|
|
935
|
+
return raw.filter((item) => typeof item === "string");
|
|
936
|
+
}
|
|
937
|
+
/** `$inputs.a.b` against the frame's inputs; anything else that is a runtime expression is unknown. */
|
|
938
|
+
function valueOf(value, inputs) {
|
|
939
|
+
if (typeof value !== "string" || !value.includes("$")) return {
|
|
940
|
+
known: true,
|
|
941
|
+
value
|
|
942
|
+
};
|
|
943
|
+
const match = /^\$inputs\.([^#{}]+)$/u.exec(value);
|
|
944
|
+
if (match?.[1] === void 0) return value.startsWith("$") || value.includes("{$") ? { known: false } : {
|
|
945
|
+
known: true,
|
|
946
|
+
value
|
|
947
|
+
};
|
|
948
|
+
let current = inputs;
|
|
949
|
+
for (const part of match[1].split(".")) {
|
|
950
|
+
if (!isRecord(current) || !Object.hasOwn(current, part)) return { known: false };
|
|
951
|
+
current = current[part];
|
|
952
|
+
}
|
|
953
|
+
return {
|
|
954
|
+
known: true,
|
|
955
|
+
value: current
|
|
956
|
+
};
|
|
957
|
+
}
|
|
958
|
+
function bind(parameters, inputs) {
|
|
959
|
+
const record = {};
|
|
960
|
+
let provisional = false;
|
|
961
|
+
for (const parameter of parameters) {
|
|
962
|
+
const bound = valueOf(parameter.value, inputs);
|
|
963
|
+
if (!bound.known) {
|
|
964
|
+
provisional = true;
|
|
965
|
+
continue;
|
|
966
|
+
}
|
|
967
|
+
Object.defineProperty(record, parameter.name, {
|
|
968
|
+
value: bound.value,
|
|
969
|
+
enumerable: true,
|
|
970
|
+
writable: true,
|
|
971
|
+
configurable: true
|
|
972
|
+
});
|
|
973
|
+
}
|
|
974
|
+
return {
|
|
975
|
+
record,
|
|
976
|
+
provisional
|
|
977
|
+
};
|
|
978
|
+
}
|
|
979
|
+
function flatten(doc, workflowId, inputs, seen, out) {
|
|
980
|
+
if (seen.has(workflowId)) return "workflowId cycle";
|
|
981
|
+
const workflow = doc.workflows.find((item) => item.workflowId === workflowId);
|
|
982
|
+
if (workflow === void 0) return `unknown workflowId ${workflowId}`;
|
|
983
|
+
seen.add(workflowId);
|
|
984
|
+
for (const step of workflow.steps) {
|
|
985
|
+
if (step.workflowId === void 0 || step.workflowId.startsWith("$sourceDescriptions.")) {
|
|
986
|
+
out.push({
|
|
987
|
+
step,
|
|
988
|
+
workflow,
|
|
989
|
+
inputs
|
|
990
|
+
});
|
|
991
|
+
continue;
|
|
992
|
+
}
|
|
993
|
+
const nested = flatten(doc, step.workflowId, bind(step.parameters, inputs).record, seen, out);
|
|
994
|
+
if (nested !== void 0) return nested;
|
|
995
|
+
}
|
|
996
|
+
seen.delete(workflowId);
|
|
997
|
+
}
|
|
998
|
+
function locate(doc, openapi, step) {
|
|
999
|
+
if (step.workflowId !== void 0) return {
|
|
1000
|
+
reason: "unsupported",
|
|
1001
|
+
detail: "workflows in another Arazzo document are not resolved"
|
|
1002
|
+
};
|
|
1003
|
+
let sourceName;
|
|
1004
|
+
let operationId = step.operationId;
|
|
1005
|
+
let pointer;
|
|
1006
|
+
if (step.operationPath !== void 0) {
|
|
1007
|
+
const match = /^\{?\$sourceDescriptions\.([^.}#]+)(?:\.url)?\}?(#.*)?$/u.exec(step.operationPath);
|
|
1008
|
+
if (match?.[1] === void 0) return {
|
|
1009
|
+
reason: "undocumented",
|
|
1010
|
+
detail: "operationPath is not a $sourceDescriptions reference"
|
|
1011
|
+
};
|
|
1012
|
+
sourceName = match[1];
|
|
1013
|
+
pointer = match[2] ?? "";
|
|
1014
|
+
} else if (operationId !== void 0) {
|
|
1015
|
+
const match = /^\$sourceDescriptions\.([^.]+)\.(.+)$/u.exec(operationId);
|
|
1016
|
+
if (match?.[1] !== void 0 && match[2] !== void 0) {
|
|
1017
|
+
sourceName = match[1];
|
|
1018
|
+
operationId = match[2];
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
const candidates = sourceName === void 0 ? doc.sources.filter((source) => source.type !== "arazzo") : doc.sources.filter((source) => source.name === sourceName);
|
|
1022
|
+
if (candidates.length === 0) return {
|
|
1023
|
+
reason: "undocumented",
|
|
1024
|
+
detail: sourceName === void 0 ? "no API sourceDescription" : `unknown sourceDescription ${sourceName}`
|
|
1025
|
+
};
|
|
1026
|
+
if (step.channelPath !== void 0) return {
|
|
1027
|
+
reason: "unsupported",
|
|
1028
|
+
detail: "AsyncAPI steps are not evaluated"
|
|
1029
|
+
};
|
|
1030
|
+
const found = [];
|
|
1031
|
+
for (const source of candidates) {
|
|
1032
|
+
const description = descriptionFor(openapi, source.name);
|
|
1033
|
+
const node = pointer === void 0 ? operationId === void 0 ? void 0 : findOperation(description, operationId) : readPointer(description, pointer);
|
|
1034
|
+
if (node !== void 0 || candidates.length === 1) found.push({
|
|
1035
|
+
source,
|
|
1036
|
+
node
|
|
1037
|
+
});
|
|
1038
|
+
}
|
|
1039
|
+
if (found.length > 1) return {
|
|
1040
|
+
reason: "undocumented",
|
|
1041
|
+
detail: "operationId matches several sources; qualify it with $sourceDescriptions"
|
|
1042
|
+
};
|
|
1043
|
+
const [hit] = found;
|
|
1044
|
+
if (hit?.source.type === "asyncapi") return {
|
|
1045
|
+
reason: "unsupported",
|
|
1046
|
+
detail: "AsyncAPI steps are not evaluated"
|
|
1047
|
+
};
|
|
1048
|
+
if (hit === void 0 || hit.source.type === "arazzo" || !isRecord(hit.node)) return {
|
|
1049
|
+
reason: "undocumented",
|
|
1050
|
+
detail: "operation not found"
|
|
1051
|
+
};
|
|
1052
|
+
return compact({
|
|
1053
|
+
node: hit.node,
|
|
1054
|
+
operationId: asString(hit.node["operationId"]) ?? operationId
|
|
1055
|
+
});
|
|
1056
|
+
}
|
|
1057
|
+
function resolve(doc, openapi, tree, flat) {
|
|
1058
|
+
const { step, workflow, inputs } = flat;
|
|
1059
|
+
const base = {
|
|
1060
|
+
stepId: step.stepId,
|
|
1061
|
+
workflowId: workflow.workflowId
|
|
1062
|
+
};
|
|
1063
|
+
const located = locate(doc, openapi, step);
|
|
1064
|
+
const { record, provisional } = bind([...workflow.parameters.filter((parameter) => !step.parameters.some((own) => own.name === parameter.name)), ...step.parameters], inputs);
|
|
1065
|
+
const data = Object.keys(inputs).length + Object.keys(record).length === 0 ? void 0 : {
|
|
1066
|
+
...inputs,
|
|
1067
|
+
...record
|
|
1068
|
+
};
|
|
1069
|
+
if ("reason" in located) return {
|
|
1070
|
+
...base,
|
|
1071
|
+
...compact({ operationId: step.operationId }),
|
|
1072
|
+
keys: [],
|
|
1073
|
+
finding: located,
|
|
1074
|
+
data,
|
|
1075
|
+
provisional
|
|
1076
|
+
};
|
|
1077
|
+
const keys = permissionKeysOf(located.node);
|
|
1078
|
+
const unknown = tree === void 0 ? void 0 : keys.find((key) => findPermission(tree, key) === void 0);
|
|
1079
|
+
return {
|
|
1080
|
+
...base,
|
|
1081
|
+
...compact({ operationId: located.operationId }),
|
|
1082
|
+
keys,
|
|
1083
|
+
...keys.length === 0 ? { finding: {
|
|
1084
|
+
reason: "undocumented",
|
|
1085
|
+
detail: "operation has no x-permdock-permissions"
|
|
1086
|
+
} } : unknown === void 0 ? {} : { finding: {
|
|
1087
|
+
reason: "unknown-key",
|
|
1088
|
+
detail: unknown
|
|
1089
|
+
} },
|
|
1090
|
+
data,
|
|
1091
|
+
provisional
|
|
1092
|
+
};
|
|
1093
|
+
}
|
|
1094
|
+
function resolveAll(input, tree) {
|
|
1095
|
+
const doc = parseDocument(input.arazzo);
|
|
1096
|
+
if (typeof doc === "string") return {
|
|
1097
|
+
workflowId: input.workflowId ?? "",
|
|
1098
|
+
error: doc
|
|
1099
|
+
};
|
|
1100
|
+
const workflowId = input.workflowId ?? doc.workflows[0]?.workflowId ?? "";
|
|
1101
|
+
const flat = [];
|
|
1102
|
+
const error = flatten(doc, workflowId, input.inputs ?? {}, /* @__PURE__ */ new Set(), flat);
|
|
1103
|
+
if (error !== void 0) return {
|
|
1104
|
+
workflowId,
|
|
1105
|
+
error
|
|
1106
|
+
};
|
|
1107
|
+
return {
|
|
1108
|
+
workflowId,
|
|
1109
|
+
steps: flat.map((item) => resolve(doc, input.openapi, tree, item))
|
|
1110
|
+
};
|
|
1111
|
+
}
|
|
1112
|
+
function arazzoFindings(input, tree) {
|
|
1113
|
+
const resolved = resolveAll(input, tree);
|
|
1114
|
+
if ("error" in resolved) return [{
|
|
1115
|
+
stepId: "document",
|
|
1116
|
+
workflowId: resolved.workflowId,
|
|
1117
|
+
reason: "validation",
|
|
1118
|
+
detail: resolved.error
|
|
1119
|
+
}];
|
|
1120
|
+
return resolved.steps.flatMap((step) => step.finding === void 0 ? [] : [{
|
|
1121
|
+
stepId: step.stepId,
|
|
1122
|
+
workflowId: step.workflowId,
|
|
1123
|
+
reason: step.finding.reason,
|
|
1124
|
+
detail: step.finding.detail
|
|
1125
|
+
}]);
|
|
1126
|
+
}
|
|
1127
|
+
function worst(outcomes) {
|
|
1128
|
+
if (outcomes.includes("denied")) return "denied";
|
|
1129
|
+
if (outcomes.includes("approval-required")) return "approval-required";
|
|
1130
|
+
return "granted";
|
|
1131
|
+
}
|
|
1132
|
+
function combine(decisions) {
|
|
1133
|
+
const outcome = worst(decisions.map((item) => item.outcome));
|
|
1134
|
+
return decisions.find((item) => item.outcome === outcome) ?? denied("undocumented");
|
|
1135
|
+
}
|
|
1136
|
+
/**
|
|
1137
|
+
* Walks an Arazzo workflow against `permdock`: each step's operation is
|
|
1138
|
+
* decided as `permdock.simulate` would, and a server instance emits one
|
|
1139
|
+
* simulate event for the whole plan. Works on an instance built from a
|
|
1140
|
+
* snapshot too.
|
|
1141
|
+
*/
|
|
1142
|
+
function simulateArazzo(permdock, input) {
|
|
1143
|
+
const tree = input.permissions ?? permdock.permissions;
|
|
1144
|
+
const internals = internalsOf(permdock);
|
|
1145
|
+
if (internals !== void 0) return internals.simulateWith((decide) => planArazzo(input, tree, decide));
|
|
1146
|
+
return planArazzo(input, tree, (permission, data) => permdock.simulate([[permission, data]])[0] ?? unknownStep(permission));
|
|
1147
|
+
}
|
|
1148
|
+
function unknownStep(permission) {
|
|
1149
|
+
return {
|
|
1150
|
+
outcome: "denied",
|
|
1151
|
+
denials: [{
|
|
1152
|
+
role: null,
|
|
1153
|
+
reason: "no-grant",
|
|
1154
|
+
detail: permission.key
|
|
1155
|
+
}],
|
|
1156
|
+
alternatives: []
|
|
1157
|
+
};
|
|
1158
|
+
}
|
|
1159
|
+
function planArazzo(input, tree, decide) {
|
|
1160
|
+
const resolved = resolveAll(input, tree);
|
|
1161
|
+
if ("error" in resolved) return freezeDeep({
|
|
1162
|
+
workflowId: resolved.workflowId === "" ? "unknown" : resolved.workflowId,
|
|
1163
|
+
outcome: "denied",
|
|
1164
|
+
steps: [{
|
|
1165
|
+
stepId: "document",
|
|
1166
|
+
permissions: [],
|
|
1167
|
+
decision: denied("validation")
|
|
1168
|
+
}]
|
|
1169
|
+
});
|
|
1170
|
+
const steps = resolved.steps.map((step) => {
|
|
1171
|
+
const owner = step.workflowId === resolved.workflowId ? void 0 : step.workflowId;
|
|
1172
|
+
if (step.finding !== void 0) return compact({
|
|
1173
|
+
stepId: step.stepId,
|
|
1174
|
+
workflowId: owner,
|
|
1175
|
+
operationId: step.operationId,
|
|
1176
|
+
permissions: [],
|
|
1177
|
+
decision: denied(step.finding.reason === "unsupported" ? "unsupported" : "undocumented")
|
|
1178
|
+
});
|
|
1179
|
+
const permissions = step.keys.flatMap((key) => {
|
|
1180
|
+
const leaf = tree === void 0 ? void 0 : findPermission(tree, key);
|
|
1181
|
+
return leaf === void 0 ? [] : [leaf];
|
|
1182
|
+
});
|
|
1183
|
+
return compact({
|
|
1184
|
+
stepId: step.stepId,
|
|
1185
|
+
workflowId: owner,
|
|
1186
|
+
operationId: step.operationId,
|
|
1187
|
+
permissions,
|
|
1188
|
+
decision: combine(permissions.map((permission) => decide(permission, step.data))),
|
|
1189
|
+
provisional: step.provisional ? true : void 0
|
|
1190
|
+
});
|
|
1191
|
+
});
|
|
1192
|
+
return freezeDeep({
|
|
1193
|
+
workflowId: resolved.workflowId,
|
|
1194
|
+
outcome: worst(steps.map((step) => step.decision.outcome)),
|
|
1195
|
+
steps
|
|
1196
|
+
});
|
|
1197
|
+
}
|
|
1198
|
+
//#endregion
|
|
1199
|
+
//#region src/core/local-manifest.ts
|
|
1200
|
+
/** A relation grantee needs the relation graph, which a device does not hold: the grant decides on the server only. */
|
|
1201
|
+
function localGrant(grant) {
|
|
1202
|
+
const relational = flattenGrantee(grant.to).some((item) => item.kind === "relation");
|
|
1203
|
+
return snapshotGrant(relational ? {
|
|
1204
|
+
...grant,
|
|
1205
|
+
portable: false
|
|
1206
|
+
} : grant, void 0);
|
|
1207
|
+
}
|
|
1208
|
+
function customTable(policy, scope, roles) {
|
|
1209
|
+
const keys = [...new Set(ceilingGrants(policy, scope).map((grant) => grant.permission.key))];
|
|
1210
|
+
if (keys.length === 0) return;
|
|
1211
|
+
const resolve = (body) => resolveCustomRole(policy, scope === "global" ? {
|
|
1212
|
+
name: LOCAL_CUSTOM_ROLE,
|
|
1213
|
+
scope: "global",
|
|
1214
|
+
...body
|
|
1215
|
+
} : {
|
|
1216
|
+
name: LOCAL_CUSTOM_ROLE,
|
|
1217
|
+
tenant: LOCAL_CUSTOM_ROLE,
|
|
1218
|
+
scope,
|
|
1219
|
+
...body
|
|
1220
|
+
}).grants.map(localGrant);
|
|
1221
|
+
const permissions = {};
|
|
1222
|
+
for (const key of keys) {
|
|
1223
|
+
const leaf = findPermission(policy.permissions, key);
|
|
1224
|
+
const names = leaf?.kind === "instance" ? levelNames(policy, leaf.resource) : [];
|
|
1225
|
+
permissions[key] = compact({
|
|
1226
|
+
all: resolve({ grants: [{ permission: key }] }),
|
|
1227
|
+
levels: names.length === 0 ? void 0 : Object.fromEntries(names.map((level) => [level, resolve({ grants: [{
|
|
1228
|
+
permission: key,
|
|
1229
|
+
level
|
|
1230
|
+
}] })]))
|
|
1231
|
+
});
|
|
1232
|
+
}
|
|
1233
|
+
const includes = {};
|
|
1234
|
+
for (const role of roles) {
|
|
1235
|
+
const grants = resolve({ includes: [role] });
|
|
1236
|
+
if (grants.length > 0) includes[role] = grants;
|
|
1237
|
+
}
|
|
1238
|
+
return {
|
|
1239
|
+
permissions,
|
|
1240
|
+
includes
|
|
1241
|
+
};
|
|
1242
|
+
}
|
|
1243
|
+
/**
|
|
1244
|
+
* What the policy grants, as JSON a device turns into snapshots from its own
|
|
1245
|
+
* membership rows (`localSnapshot` in `permdock/react-native`). Write it at
|
|
1246
|
+
* build time; the policy itself never ships to the client.
|
|
1247
|
+
*/
|
|
1248
|
+
function localSnapshotManifest(policy) {
|
|
1249
|
+
const roles = [...declaredRoleNames(policy)].toSorted();
|
|
1250
|
+
const custom = {};
|
|
1251
|
+
for (const scope of [...scopeList(policy.scopes).map((item) => item.name), "global"]) {
|
|
1252
|
+
const table = customTable(policy, scope, roles);
|
|
1253
|
+
if (table !== void 0) custom[scope] = table;
|
|
1254
|
+
}
|
|
1255
|
+
const scopes = snapshotScopes(policy);
|
|
1256
|
+
const { vocabulary } = buildSnapshot({
|
|
1257
|
+
subject: {
|
|
1258
|
+
principal: null,
|
|
1259
|
+
context: {}
|
|
1260
|
+
},
|
|
1261
|
+
roles: [],
|
|
1262
|
+
grants: [],
|
|
1263
|
+
vocabulary: policy.vocabulary
|
|
1264
|
+
});
|
|
1265
|
+
return freezeDeep(compact({
|
|
1266
|
+
v: 1,
|
|
1267
|
+
scopes,
|
|
1268
|
+
vocabulary,
|
|
1269
|
+
roles,
|
|
1270
|
+
rank: usesAssigns(policy) ? rankRoles(policy, roles) : void 0,
|
|
1271
|
+
grants: grantList(policy).map(localGrant),
|
|
1272
|
+
custom
|
|
1273
|
+
}));
|
|
1274
|
+
}
|
|
1275
|
+
//#endregion
|
|
1276
|
+
//#region src/app/define-app.ts
|
|
1277
|
+
function grantsOf(policy) {
|
|
1278
|
+
return [...policy.index.grantsByKey.values()].flat();
|
|
1279
|
+
}
|
|
1280
|
+
function assertProduction(policy, options) {
|
|
1281
|
+
const grants = grantsOf(policy);
|
|
1282
|
+
if (options.store === void 0 && grants.some((grant) => grant.approval !== void 0)) throw new Error("PermDock: profile 'prod' needs a store: a grant requires approval and approvals cannot complete without an ApprovalStore");
|
|
1283
|
+
if (options.limits === void 0 && grants.some((grant) => grant.limit !== void 0)) throw new Error("PermDock: profile 'prod' needs limits: a grant has a limit and every call would deny with limit-unavailable; pass limits: limits(store)");
|
|
1284
|
+
}
|
|
1285
|
+
/**
|
|
1286
|
+
* The in-process services `dev` fills in: one memory approval store that
|
|
1287
|
+
* every adapter of the app shares, and memory limits when a grant has a
|
|
1288
|
+
* `limit`. They live here, not in the adapters, so an adapter ships no store
|
|
1289
|
+
* and an app without `defineApp` ships neither.
|
|
1290
|
+
*/
|
|
1291
|
+
function devDefaults(policy, options) {
|
|
1292
|
+
return {
|
|
1293
|
+
...options,
|
|
1294
|
+
profile: "dev",
|
|
1295
|
+
...options.store === void 0 ? { store: memoryApprovalStore() } : {},
|
|
1296
|
+
...options.limits === void 0 && grantsOf(policy).some((grant) => grant.limit !== void 0) ? { limits: limits(memoryLimitStore()) } : {}
|
|
1297
|
+
};
|
|
1298
|
+
}
|
|
1299
|
+
/**
|
|
1300
|
+
* Binds the app's sources, features and services to `policy`. The result
|
|
1301
|
+
* is a policy: pass it wherever a policy goes (`createPermDock`, every
|
|
1302
|
+
* adapter, the CLI) and each picks up the app definition, with its own
|
|
1303
|
+
* options taking precedence. The profile defaults to `prod`.
|
|
1304
|
+
*/
|
|
1305
|
+
function defineApp(policy, options = {}) {
|
|
1306
|
+
const profile = options.profile ?? "prod";
|
|
1307
|
+
const definition = profile === "dev" ? devDefaults(policy, options) : {
|
|
1308
|
+
...options,
|
|
1309
|
+
profile
|
|
1310
|
+
};
|
|
1311
|
+
if (profile === "prod") assertProduction(policy, definition);
|
|
1312
|
+
const prototype = Object.getPrototypeOf(policy);
|
|
1313
|
+
const app = Object.create(prototype, Object.getOwnPropertyDescriptors(policy));
|
|
1314
|
+
Object.defineProperty(app, APP, { value: Object.freeze(definition) });
|
|
1315
|
+
return Object.freeze(app);
|
|
1316
|
+
}
|
|
1317
|
+
//#endregion
|
|
1318
|
+
//#region src/core/shadow.ts
|
|
1319
|
+
function legacyAnswer(legacy, permission, data, subject) {
|
|
1320
|
+
try {
|
|
1321
|
+
return legacy({
|
|
1322
|
+
permission,
|
|
1323
|
+
data,
|
|
1324
|
+
subject
|
|
1325
|
+
}) === true;
|
|
1326
|
+
} catch {
|
|
1327
|
+
return false;
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
function legacyDecision(allowed, permission, subject) {
|
|
1331
|
+
return allowed ? freezeDeep({
|
|
1332
|
+
outcome: "granted",
|
|
1333
|
+
subject,
|
|
1334
|
+
matched: {
|
|
1335
|
+
role: "legacy",
|
|
1336
|
+
permission: permission.key
|
|
1337
|
+
},
|
|
1338
|
+
token: "pd1.legacy"
|
|
1339
|
+
}) : freezeDeep({
|
|
1340
|
+
outcome: "denied",
|
|
1341
|
+
permission: permission.key,
|
|
1342
|
+
denials: [{
|
|
1343
|
+
role: null,
|
|
1344
|
+
reason: "no-grant",
|
|
1345
|
+
detail: "legacy"
|
|
1346
|
+
}],
|
|
1347
|
+
alternatives: []
|
|
1348
|
+
});
|
|
1349
|
+
}
|
|
1350
|
+
/**
|
|
1351
|
+
* Runs PermDock beside the application's existing checks while adopting
|
|
1352
|
+
* it. Each `can`, `decide`, `decideEach`, `filter` and `assert` asks both;
|
|
1353
|
+
* the legacy answer is returned unless the permission is in `enforce`, and
|
|
1354
|
+
* every disagreement reaches `onMismatch`. Promote permissions one by one
|
|
1355
|
+
* as their mismatches are resolved, then remove the wrap. Pass it as an
|
|
1356
|
+
* adapter's `wrap` option, or call it on a core instance.
|
|
1357
|
+
*/
|
|
1358
|
+
function shadowMode(options) {
|
|
1359
|
+
const enforced = (permission) => options.enforce === "all" || (options.enforce ?? []).some((item) => item.key === permission.key);
|
|
1360
|
+
const report = (mismatch) => {
|
|
1361
|
+
try {
|
|
1362
|
+
options.onMismatch?.(mismatch);
|
|
1363
|
+
} catch {}
|
|
1364
|
+
};
|
|
1365
|
+
return (permdock) => wrapPermDock(permdock, (inner) => {
|
|
1366
|
+
const decide = (permission, data, decideOptions) => {
|
|
1367
|
+
const ours = inner.decide(permission, data, decideOptions);
|
|
1368
|
+
const theirs = legacyAnswer(options.legacy, permission, data, inner.subject);
|
|
1369
|
+
const enforce = enforced(permission);
|
|
1370
|
+
if (theirs !== (ours.outcome === "granted")) report({
|
|
1371
|
+
permission: permission.key,
|
|
1372
|
+
principal: inner.subject.principal?.id ?? null,
|
|
1373
|
+
legacy: theirs,
|
|
1374
|
+
permdock: ours.outcome,
|
|
1375
|
+
enforced: enforce
|
|
1376
|
+
});
|
|
1377
|
+
if (enforce || theirs === (ours.outcome === "granted")) return ours;
|
|
1378
|
+
return legacyDecision(theirs, permission, inner.subject);
|
|
1379
|
+
};
|
|
1380
|
+
const can = (permission, data, decideOptions) => {
|
|
1381
|
+
try {
|
|
1382
|
+
return decide(permission, data, decideOptions).outcome === "granted";
|
|
1383
|
+
} catch {
|
|
1384
|
+
return false;
|
|
1385
|
+
}
|
|
1386
|
+
};
|
|
1387
|
+
return {
|
|
1388
|
+
decide,
|
|
1389
|
+
can,
|
|
1390
|
+
decideEach: (permission, rows, decideOptions) => rows.map((row) => decide(permission, row, decideOptions)),
|
|
1391
|
+
filter: (permission, rows, decideOptions) => rows.filter((row) => can(permission, row, decideOptions)),
|
|
1392
|
+
assert: ((permission, data, decideOptions) => {
|
|
1393
|
+
const decision = decide(permission, data, decideOptions);
|
|
1394
|
+
if (decision.outcome === "granted") return decision;
|
|
1395
|
+
if (decision.outcome === "approval-required") return inner.assert(permission, data, decideOptions);
|
|
1396
|
+
throw new PermDockDeniedError({
|
|
1397
|
+
decision,
|
|
1398
|
+
permission: permission.key,
|
|
1399
|
+
scope: permission.scope,
|
|
1400
|
+
resource: { type: permission.resource },
|
|
1401
|
+
subject: inner.subject,
|
|
1402
|
+
message: `PermDock: ${permission.key} denied`
|
|
1403
|
+
});
|
|
1404
|
+
})
|
|
1405
|
+
};
|
|
1406
|
+
});
|
|
1407
|
+
}
|
|
1408
|
+
//#endregion
|
|
1409
|
+
//#region src/core/may-access.ts
|
|
1410
|
+
function policyPlans(policy) {
|
|
1411
|
+
const names = new Set(listPlans(policy.vocabulary?.plans).map((p) => p.key));
|
|
1412
|
+
for (const grant of grantList(policy)) for (const item of flattenGrantee(grant.to)) if (item.kind === "plan") names.add(item.plan);
|
|
1413
|
+
return [...names];
|
|
1414
|
+
}
|
|
1415
|
+
function heldRoleNames(principal) {
|
|
1416
|
+
return [...principal.roles ?? [], ...(principal.memberships ?? []).flatMap((item) => item.roles)];
|
|
1417
|
+
}
|
|
1418
|
+
/**
|
|
1419
|
+
* An optimistic routing hint for a proxy or middleware, never a decision.
|
|
1420
|
+
* `false` only when the subject's declared roles provably lack `permission`;
|
|
1421
|
+
* `true` whenever custom roles, plans, memberships the claims do not carry or
|
|
1422
|
+
* row conditions could still grant it. Synchronous, no network, never throws.
|
|
1423
|
+
* Pages, Server Actions and RLS remain the enforcement points.
|
|
1424
|
+
*/
|
|
1425
|
+
function mayAccess(policy, user, permission, options = {}) {
|
|
1426
|
+
try {
|
|
1427
|
+
const resolved = resolveSubject(policy, user, compact({ tenant: options.tenant }), []);
|
|
1428
|
+
if (isThenable$1(resolved)) return true;
|
|
1429
|
+
const principal = resolved.principal;
|
|
1430
|
+
const relevant = policy.index.grantsByKey.get(permission.key) ?? [];
|
|
1431
|
+
if (principal !== null) {
|
|
1432
|
+
const declared = declaredRoleNames(policy);
|
|
1433
|
+
if (heldRoleNames(principal).some((name) => !declared.has(name))) return true;
|
|
1434
|
+
if ((principal.memberships ?? []).length === 0 && relevant.some((grant) => grant.scope !== "global")) return true;
|
|
1435
|
+
}
|
|
1436
|
+
const tenant = options.tenant;
|
|
1437
|
+
const subject = principal === null ? resolved : {
|
|
1438
|
+
...resolved,
|
|
1439
|
+
principal: {
|
|
1440
|
+
...principal,
|
|
1441
|
+
plans: policyPlans(policy),
|
|
1442
|
+
memberships: (principal.memberships ?? []).filter((item) => {
|
|
1443
|
+
const owner = tenantOf(item, scopeList(policy.scopes));
|
|
1444
|
+
return tenant === void 0 || owner === void 0 || owner === tenant;
|
|
1445
|
+
})
|
|
1446
|
+
}
|
|
1447
|
+
};
|
|
1448
|
+
const matched = collectSnapshotGrants(policy, subject, []).filter((item) => item.grant.permission.key === permission.key);
|
|
1449
|
+
if (matched.some((item) => item.grant.effect === "deny" && item.grant.where === void 0 && item.grant.check === void 0 && item.grant.portable)) return false;
|
|
1450
|
+
return matched.some((item) => item.grant.effect === "allow");
|
|
1451
|
+
} catch {
|
|
1452
|
+
return true;
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
//#endregion
|
|
1456
|
+
//#region src/core/custom-role-source.ts
|
|
1457
|
+
function ofTenant(tenant, roles) {
|
|
1458
|
+
return Array.isArray(roles) ? roles.filter((role) => role !== null && typeof role === "object" && Reflect.get(role, "tenant") === tenant) : [];
|
|
1459
|
+
}
|
|
1460
|
+
/**
|
|
1461
|
+
* A `CustomRoleSource` over a reader that returns every custom role of a tenant.
|
|
1462
|
+
* It keeps only the roles of the requested tenant. With `read: 'held'` it
|
|
1463
|
+
* skips a tenant's read, and `globalRoles`, when the subject holds only
|
|
1464
|
+
* declared roles there.
|
|
1465
|
+
*/
|
|
1466
|
+
function customRoleSource(reader, options = {}) {
|
|
1467
|
+
const declared = options.read === "held" ? declaredRoleNames(options.policy) : void 0;
|
|
1468
|
+
return Object.freeze({
|
|
1469
|
+
rolesFor(tenant, context) {
|
|
1470
|
+
if (declared !== void 0 && context?.held.every((name) => declared.has(name)) === true) return [];
|
|
1471
|
+
const roles = reader.rolesFor(tenant);
|
|
1472
|
+
return isThenable$1(roles) ? roles.then((list) => ofTenant(tenant, list)) : ofTenant(tenant, roles);
|
|
1473
|
+
},
|
|
1474
|
+
...reader.assignable === void 0 ? {} : { assignable: (tenant) => reader.assignable?.(tenant) ?? [] },
|
|
1475
|
+
...reader.globalRoles === void 0 ? {} : { globalRoles: (context) => declared !== void 0 && context?.held.every((name) => declared.has(name)) === true ? [] : reader.globalRoles?.() ?? [] }
|
|
1476
|
+
});
|
|
1477
|
+
}
|
|
1478
|
+
//#endregion
|
|
1479
|
+
//#region src/core/snapshot-for.ts
|
|
1480
|
+
function withPlans(subject, plans) {
|
|
1481
|
+
if (plans === void 0 || subject.principal === null) return subject;
|
|
1482
|
+
return freezeDeep(compact({
|
|
1483
|
+
...subject,
|
|
1484
|
+
principal: compact({
|
|
1485
|
+
...subject.principal,
|
|
1486
|
+
plans: [...plans]
|
|
1487
|
+
})
|
|
1488
|
+
}));
|
|
1489
|
+
}
|
|
1490
|
+
/**
|
|
1491
|
+
* Builds the serializable access snapshot for a subject without a request,
|
|
1492
|
+
* a network call or a cache directive: call it inside the app's own
|
|
1493
|
+
* `'use cache: private'` function. Synchronous; the policy's `subject` and
|
|
1494
|
+
* `context` mappers must be synchronous too.
|
|
1495
|
+
*/
|
|
1496
|
+
function snapshotFor(policy, user, options = {}) {
|
|
1497
|
+
const auth = [];
|
|
1498
|
+
const memberships = options.memberships;
|
|
1499
|
+
const resolved = resolveSubject(policy, user, compact({
|
|
1500
|
+
tenant: options.tenant,
|
|
1501
|
+
memberships: memberships === void 0 ? void 0 : { membershipsFor: () => [...memberships] }
|
|
1502
|
+
}), auth);
|
|
1503
|
+
if (isThenable$1(resolved)) throw new TypeError("PermDock: snapshotFor() needs synchronous subject and context mappers. Resolve async data first and pass it as a Subject, or use (await getPermDock(...)).snapshot().");
|
|
1504
|
+
const subject = withPlans(resolved, options.plans);
|
|
1505
|
+
const tenants = new Set(tenantsOf(subject.principal, scopeList(policy.scopes)));
|
|
1506
|
+
const customRoles = (options.customRoles ?? []).filter((item) => item.scope === "global" || item.tenant !== void 0 && tenants.has(item.tenant));
|
|
1507
|
+
const names = options.assignable;
|
|
1508
|
+
const assignable = names === void 0 ? void 0 : new Map([...tenants].map((tenant) => [tenant, Object.hasOwn(names, tenant) ? names[tenant] ?? [] : []]));
|
|
1509
|
+
return snapshotOf(policy, subject, compact({
|
|
1510
|
+
customRoles,
|
|
1511
|
+
assignable,
|
|
1512
|
+
include: options.include,
|
|
1513
|
+
tenants: options.tenants,
|
|
1514
|
+
now: options.now === void 0 ? void 0 : nowSeconds(options.now)
|
|
1515
|
+
}));
|
|
1516
|
+
}
|
|
1517
|
+
//#endregion
|
|
1518
|
+
//#region src/core/presets.ts
|
|
1519
|
+
const CRUD_ACTIONS = {
|
|
1520
|
+
read: { readOnly: true },
|
|
1521
|
+
update: {},
|
|
1522
|
+
delete: { destructive: true }
|
|
1523
|
+
};
|
|
1524
|
+
const CRUD_COLLECTION = {
|
|
1525
|
+
create: {},
|
|
1526
|
+
list: { readOnly: true }
|
|
1527
|
+
};
|
|
1528
|
+
const READABLE_ACTIONS = { read: { readOnly: true } };
|
|
1529
|
+
const READABLE_COLLECTION = { list: { readOnly: true } };
|
|
1530
|
+
const WRITABLE_ACTIONS = {
|
|
1531
|
+
read: { readOnly: true },
|
|
1532
|
+
update: {}
|
|
1533
|
+
};
|
|
1534
|
+
const WRITABLE_COLLECTION = {};
|
|
1535
|
+
function isNameList(list) {
|
|
1536
|
+
return Array.isArray(list);
|
|
1537
|
+
}
|
|
1538
|
+
function toRecord(list) {
|
|
1539
|
+
if (list === void 0) return {};
|
|
1540
|
+
if (isNameList(list)) {
|
|
1541
|
+
const out = {};
|
|
1542
|
+
for (const name of list) out[name] = {};
|
|
1543
|
+
return out;
|
|
1544
|
+
}
|
|
1545
|
+
return { ...list };
|
|
1546
|
+
}
|
|
1547
|
+
function mergeMeta(base, extra) {
|
|
1548
|
+
return compact({
|
|
1549
|
+
title: extra.title ?? base.title,
|
|
1550
|
+
description: extra.description ?? base.description,
|
|
1551
|
+
tags: extra.tags ?? base.tags,
|
|
1552
|
+
readOnly: extra.readOnly ?? base.readOnly,
|
|
1553
|
+
destructive: extra.destructive ?? base.destructive,
|
|
1554
|
+
idempotent: extra.idempotent ?? base.idempotent
|
|
1555
|
+
});
|
|
1556
|
+
}
|
|
1557
|
+
function mergeActionLists(base, extra) {
|
|
1558
|
+
const extraRecord = toRecord(extra);
|
|
1559
|
+
const out = {};
|
|
1560
|
+
for (const [name, baseMeta] of Object.entries(base)) {
|
|
1561
|
+
const extraMeta = extraRecord[name];
|
|
1562
|
+
out[name] = extraMeta === void 0 ? baseMeta : mergeMeta(baseMeta, extraMeta);
|
|
1563
|
+
}
|
|
1564
|
+
for (const [name, extraMeta] of Object.entries(extraRecord)) {
|
|
1565
|
+
if (Object.hasOwn(out, name)) continue;
|
|
1566
|
+
out[name] = extraMeta;
|
|
1567
|
+
}
|
|
1568
|
+
return freezeDeep(out);
|
|
1569
|
+
}
|
|
1570
|
+
function finishPreset(baseActions, baseCollection, options) {
|
|
1571
|
+
const actions = mergeActionLists(baseActions, options?.actions);
|
|
1572
|
+
const collection = mergeActionLists(baseCollection, options?.collection);
|
|
1573
|
+
return compact({
|
|
1574
|
+
id: options?.id,
|
|
1575
|
+
parent: options?.parent,
|
|
1576
|
+
relations: options?.relations,
|
|
1577
|
+
restricted: options?.restricted,
|
|
1578
|
+
disclosure: options?.disclosure,
|
|
1579
|
+
actions,
|
|
1580
|
+
collection: Object.keys(collection).length === 0 ? void 0 : collection
|
|
1581
|
+
});
|
|
1582
|
+
}
|
|
1583
|
+
function crud(options) {
|
|
1584
|
+
return finishPreset(CRUD_ACTIONS, CRUD_COLLECTION, options);
|
|
1585
|
+
}
|
|
1586
|
+
function readable(options) {
|
|
1587
|
+
return finishPreset(READABLE_ACTIONS, READABLE_COLLECTION, options);
|
|
1588
|
+
}
|
|
1589
|
+
function writable(options) {
|
|
1590
|
+
return finishPreset(WRITABLE_ACTIONS, WRITABLE_COLLECTION, options);
|
|
1591
|
+
}
|
|
1592
|
+
//#endregion
|
|
1593
|
+
//#region src/core/ownership.ts
|
|
1594
|
+
function coversInstance(membership, scopes, scope, id, within) {
|
|
1595
|
+
if (membership.scope === void 0 || membership.id === void 0) return false;
|
|
1596
|
+
if (membership.scope === scope) return sameId(membership.id, id);
|
|
1597
|
+
if (!scopeChain(scopes, scope).slice(1).includes(membership.scope)) return false;
|
|
1598
|
+
const wanted = within === void 0 ? void 0 : Object.entries(within).find(([key]) => resolveScope(scopes, key) === membership.scope)?.[1] ?? void 0;
|
|
1599
|
+
return wanted !== void 0 && scopeIdOf(membership, membership.scope) === wanted;
|
|
1600
|
+
}
|
|
1601
|
+
/** Roles the principal holds at the instance, at an ancestor named in `within`, or globally. */
|
|
1602
|
+
function rolesAtInstance(policy, principal, scopes, scope, id, within, now) {
|
|
1603
|
+
const declared = declaredRoleNames(policy);
|
|
1604
|
+
const names = new Set((principal.roles ?? []).filter((name) => declared.has(name)));
|
|
1605
|
+
for (const membership of principal.memberships ?? []) if (!isMembershipExpired(membership, now) && coversInstance(membership, scopes, scope, id, within)) for (const name of membership.roles) names.add(name);
|
|
1606
|
+
return rankRoles(policy, [...names]);
|
|
1607
|
+
}
|
|
1608
|
+
/** Whether the principal holds `role` on a live membership of exactly this instance. */
|
|
1609
|
+
function holdsAt(principal, role, scope, id, now) {
|
|
1610
|
+
return (principal.memberships ?? []).some((membership) => membership.scope === scope && sameId(membership.id, id) && !isMembershipExpired(membership, now) && membership.roles.includes(role));
|
|
1611
|
+
}
|
|
1612
|
+
function includedRoles(role) {
|
|
1613
|
+
return isReadonlyArray(role.includes) ? role.includes.filter((name) => typeof name === "string") : [];
|
|
1614
|
+
}
|
|
1615
|
+
/**
|
|
1616
|
+
* The membership kinds a custom role may be held through: those every
|
|
1617
|
+
* included declared role with `for` allows. `undefined` when none restricts.
|
|
1618
|
+
*/
|
|
1619
|
+
function customForKinds(policy, role) {
|
|
1620
|
+
let kinds;
|
|
1621
|
+
for (const name of includedRoles(role)) {
|
|
1622
|
+
const listed = bindingOf(policy, name)?.for;
|
|
1623
|
+
if (listed !== void 0) kinds = kinds === void 0 ? listed : kinds.filter((kind) => listed.includes(kind));
|
|
1624
|
+
}
|
|
1625
|
+
return kinds;
|
|
1626
|
+
}
|
|
1627
|
+
function conflictsWith(policy, role) {
|
|
1628
|
+
const out = new Set(bindingOf(policy, role)?.exclusiveWith ?? []);
|
|
1629
|
+
for (const binding of policy.roles) if (binding.exclusiveWith?.includes(role) === true) out.add(binding.name);
|
|
1630
|
+
return out;
|
|
1631
|
+
}
|
|
1632
|
+
/** What the change does to the instance's holder count. */
|
|
1633
|
+
function countRules(binding, role, holders, delta, deny) {
|
|
1634
|
+
if (binding === void 0 || delta === 0) return;
|
|
1635
|
+
const min = binding.min ?? 0;
|
|
1636
|
+
if (!(min > 0 || binding.max !== void 0 || binding.transferOnly === true)) return;
|
|
1637
|
+
if (!(holders !== void 0 && Number.isInteger(holders) && holders >= 0)) {
|
|
1638
|
+
deny(delta < 0 && min > 0 ? "last-holder" : delta > 0 && binding.max !== void 0 ? "max-holders" : "transfer-only", {
|
|
1639
|
+
role,
|
|
1640
|
+
holders: null
|
|
1641
|
+
});
|
|
1642
|
+
return;
|
|
1643
|
+
}
|
|
1644
|
+
const after = holders + delta;
|
|
1645
|
+
if (delta < 0 && min > 0 && after < min) deny("last-holder", {
|
|
1646
|
+
role,
|
|
1647
|
+
min,
|
|
1648
|
+
holders
|
|
1649
|
+
});
|
|
1650
|
+
if (delta > 0 && binding.max !== void 0 && after > binding.max) deny("max-holders", {
|
|
1651
|
+
role,
|
|
1652
|
+
max: binding.max,
|
|
1653
|
+
holders
|
|
1654
|
+
});
|
|
1655
|
+
if (binding.transferOnly === true && holders > 0 && after > 0) deny("transfer-only", {
|
|
1656
|
+
role,
|
|
1657
|
+
holders
|
|
1658
|
+
});
|
|
1659
|
+
}
|
|
1660
|
+
/**
|
|
1661
|
+
* A live membership of the principal on the instance or on an instance under
|
|
1662
|
+
* it: the one in-band fact that ties a nested instance to a tenant.
|
|
1663
|
+
*/
|
|
1664
|
+
function heldAnchor(principal, scopes, scope, id, now) {
|
|
1665
|
+
return (principal.memberships ?? []).find((membership) => !isMembershipExpired(membership, now) && membership.scope !== void 0 && scopeChain(scopes, membership.scope).includes(scope) && scopeIdOf(membership, scope) === id && tenantOf(membership, scopes) !== void 0);
|
|
1666
|
+
}
|
|
1667
|
+
/**
|
|
1668
|
+
* The one rule check behind `permdock.decideRoleChange`. The actor is always
|
|
1669
|
+
* the instance's own principal; nothing in `change` can stand in for it.
|
|
1670
|
+
*/
|
|
1671
|
+
/**
|
|
1672
|
+
* Whether the subject of `permdock` may assign, revoke or transfer a role
|
|
1673
|
+
* in one scope instance: `assigns`, the ceiling, `for`, `exclusiveWith`,
|
|
1674
|
+
* `min`, `max` and `transferOnly`. It never writes; the application does,
|
|
1675
|
+
* and generated RLS triggers re-check the holder counts at commit. A nested
|
|
1676
|
+
* instance's tenant comes from a live membership the subject holds on it,
|
|
1677
|
+
* or from `change.within` only with `{ trusted: true }`; otherwise
|
|
1678
|
+
* `no-membership`. An instance built from a snapshot denies with
|
|
1679
|
+
* `unsupported`.
|
|
1680
|
+
*/
|
|
1681
|
+
function decideRoleChange(permdock, change, options = {}) {
|
|
1682
|
+
const internals = internalsOf(permdock);
|
|
1683
|
+
if (internals === void 0) return freezeDeep({
|
|
1684
|
+
outcome: "denied",
|
|
1685
|
+
change,
|
|
1686
|
+
denials: [{
|
|
1687
|
+
role: null,
|
|
1688
|
+
reason: "unsupported"
|
|
1689
|
+
}]
|
|
1690
|
+
});
|
|
1691
|
+
const { policy, subject } = internals;
|
|
1692
|
+
return decideRoleChangeWith(policy, subject.principal, scopeList(policy.scopes), change, internals.env.customRoles, (tenant) => {
|
|
1693
|
+
const found = internals.assignable(tenant);
|
|
1694
|
+
return {
|
|
1695
|
+
assignable: new Set(found.roles.map((leaf) => leaf.key)),
|
|
1696
|
+
manage: found.manage,
|
|
1697
|
+
inert: new Set(found.inert)
|
|
1698
|
+
};
|
|
1699
|
+
}, internals.now(), options);
|
|
1700
|
+
}
|
|
1701
|
+
function decideRoleChangeWith(policy, principal, scopes, change, customRoles, authority, now, options = {}) {
|
|
1702
|
+
const name = typeof change?.role === "string" ? change.role : change?.role?.key;
|
|
1703
|
+
const denials = [];
|
|
1704
|
+
const deny = (reason, detail) => {
|
|
1705
|
+
denials.push(compact({
|
|
1706
|
+
role: name ?? null,
|
|
1707
|
+
reason,
|
|
1708
|
+
detail
|
|
1709
|
+
}));
|
|
1710
|
+
};
|
|
1711
|
+
const done = (role) => freezeDeep(denials.length === 0 ? {
|
|
1712
|
+
outcome: "granted",
|
|
1713
|
+
change,
|
|
1714
|
+
role
|
|
1715
|
+
} : {
|
|
1716
|
+
outcome: "denied",
|
|
1717
|
+
change,
|
|
1718
|
+
denials
|
|
1719
|
+
});
|
|
1720
|
+
if (principal === null) {
|
|
1721
|
+
deny("anonymous");
|
|
1722
|
+
return done(null);
|
|
1723
|
+
}
|
|
1724
|
+
const kind = change?.kind;
|
|
1725
|
+
const target = change?.target;
|
|
1726
|
+
const global = change?.scope === "global";
|
|
1727
|
+
if (typeof name !== "string" || kind !== "assign" && kind !== "revoke" && kind !== "transfer" || (global ? change.id !== void 0 : typeof change.id !== "string" || change.id === "") || target === null || typeof target !== "object" || typeof target.id !== "string" || target.id === "") {
|
|
1728
|
+
deny("validation");
|
|
1729
|
+
return done(null);
|
|
1730
|
+
}
|
|
1731
|
+
const id = change.id ?? "";
|
|
1732
|
+
const binding = bindingOf(policy, name);
|
|
1733
|
+
const declared = declaredRoleNames(policy).has(name);
|
|
1734
|
+
const named = declared || global ? [] : tenantCustomRoles(policy, customRoles).filter((role) => role.name === name);
|
|
1735
|
+
if (!declared && named.length === 0) {
|
|
1736
|
+
deny("unknown-role");
|
|
1737
|
+
return done(null);
|
|
1738
|
+
}
|
|
1739
|
+
const scope = global ? "global" : resolveScope(scopes, change.scope);
|
|
1740
|
+
const customAt = named.map((role) => customRoleScope(role, scopes));
|
|
1741
|
+
const on = declared ? binding?.on ?? findRole(policy.vocabulary?.roles, name)?.on : customAt.includes(scope) ? scope : customAt[0];
|
|
1742
|
+
const heldAt = typeof on === "string" ? resolveScope(scopes, on) : global ? "global" : void 0;
|
|
1743
|
+
if (scope === void 0 || heldAt === void 0 || heldAt !== scope) {
|
|
1744
|
+
deny("scope", { expected: heldAt ?? null });
|
|
1745
|
+
return done(null);
|
|
1746
|
+
}
|
|
1747
|
+
let tenant;
|
|
1748
|
+
let within = change.within;
|
|
1749
|
+
let custom;
|
|
1750
|
+
if (!global) {
|
|
1751
|
+
const root = scopes[0]?.name;
|
|
1752
|
+
const supplied = change.within === void 0 ? void 0 : Object.entries(change.within).find(([key]) => resolveScope(scopes, key) === root)?.[1];
|
|
1753
|
+
if (scope === root) tenant = id;
|
|
1754
|
+
else {
|
|
1755
|
+
const anchor = heldAnchor(principal, scopes, scope, id, now);
|
|
1756
|
+
const held = anchor === void 0 ? void 0 : tenantOf(anchor, scopes);
|
|
1757
|
+
if (held !== void 0) {
|
|
1758
|
+
tenant = supplied === void 0 || supplied === held ? held : void 0;
|
|
1759
|
+
within = anchor?.within;
|
|
1760
|
+
} else if (options.trusted === true) tenant = supplied;
|
|
1761
|
+
}
|
|
1762
|
+
if (tenant === void 0) {
|
|
1763
|
+
deny("no-membership", {
|
|
1764
|
+
scope,
|
|
1765
|
+
id
|
|
1766
|
+
});
|
|
1767
|
+
return done(null);
|
|
1768
|
+
}
|
|
1769
|
+
const found = tenant;
|
|
1770
|
+
custom = named.find((role) => sameId(role.tenant, found) && customRoleScope(role, scopes) === scope && sameId(customRoleId(role) ?? id, id));
|
|
1771
|
+
}
|
|
1772
|
+
if (!declared && custom === void 0) {
|
|
1773
|
+
deny("unknown-role");
|
|
1774
|
+
return done(null);
|
|
1775
|
+
}
|
|
1776
|
+
if (target.managedBy === "idp") {
|
|
1777
|
+
deny("externally-managed");
|
|
1778
|
+
return done(null);
|
|
1779
|
+
}
|
|
1780
|
+
const self = target.id === principal.id;
|
|
1781
|
+
const targetRoles = isReadonlyArray(target.roles) ? target.roles : [];
|
|
1782
|
+
const already = targetRoles.includes(name);
|
|
1783
|
+
let by = null;
|
|
1784
|
+
if (kind === "transfer") {
|
|
1785
|
+
if (self) deny("self-demotion");
|
|
1786
|
+
if (global ? (principal.roles ?? []).includes(name) : holdsAt(principal, name, scope, id, now)) by = name;
|
|
1787
|
+
else deny("not-assignable-by", { holds: false });
|
|
1788
|
+
} else {
|
|
1789
|
+
if (self) deny(kind === "revoke" ? "self-demotion" : "not-assignable-by", { self: true });
|
|
1790
|
+
const allowed = authority(tenant);
|
|
1791
|
+
const revocable = kind === "revoke" && custom !== void 0 && allowed.inert?.has(name);
|
|
1792
|
+
if (!allowed.assignable.has(name) && revocable !== true) deny("not-assignable-by");
|
|
1793
|
+
else if (custom === void 0 && usesAssigns(policy) && !allowed.manage) {
|
|
1794
|
+
const held = global ? rankRoles(policy, (principal.roles ?? []).filter((role) => declaredRoleNames(policy).has(role))) : rolesAtInstance(policy, principal, scopes, scope, id, within, now);
|
|
1795
|
+
by = held.find((role) => bindingOf(policy, role)?.assigns?.includes(name) === true) ?? null;
|
|
1796
|
+
if (by === null) deny("not-assignable-by", { held });
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1799
|
+
if (kind !== "revoke" && !already) {
|
|
1800
|
+
const kinds = custom === void 0 ? binding?.for : customForKinds(policy, custom);
|
|
1801
|
+
if (kinds !== void 0 && (target.via === void 0 || !kinds.includes(target.via))) deny("not-allowed-for-membership", {
|
|
1802
|
+
via: target.via ?? null,
|
|
1803
|
+
for: kinds
|
|
1804
|
+
});
|
|
1805
|
+
const conflicts = custom === void 0 ? conflictsWith(policy, name) : new Set(includedRoles(custom).flatMap((include) => Array.from(conflictsWith(policy, include))));
|
|
1806
|
+
const clash = targetRoles.filter((role) => conflicts.has(role));
|
|
1807
|
+
if (clash.length > 0) deny("conflicting-role", { with: clash });
|
|
1808
|
+
}
|
|
1809
|
+
const delta = kind === "assign" ? already ? 0 : 1 : kind === "revoke" ? targetRoles.length > 0 && !already ? 0 : -1 : already ? -1 : 0;
|
|
1810
|
+
countRules(binding, name, change.holders, delta, deny);
|
|
1811
|
+
return done(by);
|
|
1812
|
+
}
|
|
1813
|
+
//#endregion
|
|
1814
|
+
//#region src/core/csv.ts
|
|
1815
|
+
/** The CSV export columns, in order; the header row of every CSV file. */
|
|
1816
|
+
const CSV_COLUMNS = Object.freeze([
|
|
1817
|
+
"time",
|
|
1818
|
+
"principal",
|
|
1819
|
+
"actor",
|
|
1820
|
+
"tenant",
|
|
1821
|
+
"permission",
|
|
1822
|
+
"outcome",
|
|
1823
|
+
"matched.role",
|
|
1824
|
+
"via",
|
|
1825
|
+
"denials.reason",
|
|
1826
|
+
"token"
|
|
1827
|
+
]);
|
|
1828
|
+
function field(value) {
|
|
1829
|
+
const text = value ?? "";
|
|
1830
|
+
return /[",\r\n]/u.test(text) ? `"${text.replaceAll("\"", "\"\"")}"` : text;
|
|
1831
|
+
}
|
|
1832
|
+
/**
|
|
1833
|
+
* One RFC 4180 row for a decision or approval event, without a line
|
|
1834
|
+
* terminator; join rows with CRLF under a `CSV_COLUMNS` header.
|
|
1835
|
+
*/
|
|
1836
|
+
function toCsvRow(event) {
|
|
1837
|
+
return [
|
|
1838
|
+
event.at,
|
|
1839
|
+
event.subject.principal?.id,
|
|
1840
|
+
event.subject.actor?.id,
|
|
1841
|
+
event.tenant,
|
|
1842
|
+
event.permission,
|
|
1843
|
+
event.outcome,
|
|
1844
|
+
event.matched?.role,
|
|
1845
|
+
event.via,
|
|
1846
|
+
event.denials?.map((denial) => denial.reason).join(";"),
|
|
1847
|
+
event.token
|
|
1848
|
+
].map(field).join(",");
|
|
1849
|
+
}
|
|
1850
|
+
//#endregion
|
|
1851
|
+
export { memorySnapshotSource as A, validateApprovalPolicy as C, memoryEntitlementSource as D, memoryCustomRoleSource as E, memoryMembershipSource as O, memoryApprovalPolicySource as S, isOperationEffect as T, limits as _, readable as a, approvalPolicies as b, customRoleSource as c, defineApp as d, localSnapshotManifest as f, memoryRelationSource as g, signPolicyDocument as h, crud as i, sqlFunction as j, memorySettingsSource as k, mayAccess as l, simulateArazzo as m, toCsvRow as n, writable as o, arazzoFindings as p, decideRoleChange as r, snapshotFor as s, CSV_COLUMNS as t, shadowMode as u, memoryLimitStore as v, defineOperations as w, approvalPoliciesFor as x, stripeEntitlementSource as y };
|