permdock 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -3
- package/dist/a2a/index.d.ts +7 -7
- package/dist/a2a/index.js +8 -8
- package/dist/action-result-CL-QCITA.d.ts +19 -0
- package/dist/{adapter-context-CRhqyzCk.js → adapter-context-CIQpQxbE.js} +8 -2
- package/dist/{adoption-scan-D-feE7BE.js → adoption-scan-BLs67P1l.js} +5 -5
- package/dist/agent/index.d.ts +2 -2
- package/dist/agent/index.js +1 -1
- package/dist/ai-sdk/index.d.ts +19 -13
- package/dist/ai-sdk/index.js +6 -6
- package/dist/{app-data-MuyzVqX8.js → app-data-UNNm07jV.js} +10 -7
- package/dist/{approval-header-CJACtHkv.js → approval-header-8QV6MU4H.js} +6 -3
- package/dist/{approval-prompt-Dg5LeAcT.js → approval-prompt-B8hQY69x.js} +5 -6
- package/dist/{approval-prompt-K0faOLFp.d.ts → approval-prompt-PYagyQUj.d.ts} +13 -11
- package/dist/approvals/index.d.ts +4 -4
- package/dist/approvals/index.js +6 -6
- package/dist/{approvals-CMRfsbBh.js → approvals-DnIBTuXE.js} +66 -42
- package/dist/{approvers-CqPOLD-o.js → approvers-B4r0N_Uz.js} +8 -2
- package/dist/{arazzo-ClrF3PVb.js → arazzo-3RhGfkAK.js} +2 -2
- package/dist/{arazzo-rdeJ8n1J.js → arazzo-B5-ulWif.js} +6 -6
- package/dist/{args-CC3OwyHW.js → args-0FrVs5EH.js} +14 -7
- package/dist/astro/index.d.ts +30 -12
- package/dist/astro/index.js +2 -2
- package/dist/{audience-BrMs8c4o.js → audience-BAkBDMqn.js} +8 -8
- package/dist/{auth-CscUX8Sy.js → auth--yPyZNgr.js} +19 -7
- package/dist/authzen/index.d.ts +34 -28
- package/dist/authzen/index.js +23 -20
- package/dist/{automation-DOtAFWdy.d.ts → automation-DqEoWHBc.d.ts} +42 -32
- package/dist/{automation-ySsxp_Yp.js → automation-x3KEUusw.js} +22 -21
- package/dist/better-auth/index.d.ts +2 -2
- package/dist/better-auth/index.js +13 -7
- package/dist/better-supabase/index.d.ts +357 -227
- package/dist/better-supabase/index.js +219 -140
- package/dist/{bind-JDuvxjuW.d.ts → bind-Cp2hBHzP.d.ts} +27 -12
- package/dist/{bind-D1nO6fiC.js → bind-D_sZSVA3.js} +22 -10
- package/dist/{body-CH5of8Ne.js → body-CJQwYVa8.js} +3 -3
- package/dist/{body-text-QcXok_SI.js → body-text-DQhcmqdY.js} +8 -4
- package/dist/{boundary-BM8a_P6E.js → boundary-BV3O1eud.js} +8 -2
- package/dist/{boundary-BmHxVH1J.d.ts → boundary-C9ck-3uK.d.ts} +13 -4
- package/dist/{budget-NbEtbJC_.js → budget-BELja5A9.js} +6 -3
- package/dist/{budget-JqcRqyhK.d.ts → budget-hYsPz_yE.d.ts} +6 -3
- package/dist/{build-instance-DxE7czo9.js → build-instance-CNWaYJyr.js} +3 -3
- package/dist/{capability-L7NnfhiE.js → capability-BqX94r07.js} +31 -25
- package/dist/{capability-D4UC9TTW.d.ts → capability-ChuiONQU.d.ts} +41 -29
- package/dist/{casbin-BW9pS47J.js → casbin-H0rKxBx2.js} +16 -8
- package/dist/{casbin-BHijLuHY.js → casbin-cbSbhT_k.js} +2 -2
- package/dist/{casl-CmJlgziS.js → casl-C4OINZHI.js} +2 -2
- package/dist/{casl-UbI8v-Wq.js → casl-CHHoCldP.js} +19 -10
- package/dist/catalog/index.d.ts +51 -30
- package/dist/catalog/index.js +18 -15
- package/dist/{catalog-D69WhFii.js → catalog-BFpz2EmT.js} +20 -19
- package/dist/{catalog-BaMQfQ6Y.js → catalog-DCspHDc8.js} +3 -3
- package/dist/{catalog-doc-CV9s6B1S.js → catalog-doc-DjMKzJp9.js} +28 -14
- package/dist/{catalog-fingerprint-C144oW4M.js → catalog-fingerprint-e6aCWosn.js} +18 -14
- package/dist/catalog-mRQQQhNK.js +1 -0
- package/dist/catalog-operations-Dadrl7Z-.js +60 -0
- package/dist/{challenge-nGWma08S.js → challenge-DImNcR7B.js} +48 -27
- package/dist/chat/index.d.ts +1 -1
- package/dist/chat/index.js +13 -10
- package/dist/chat-sdk/index.d.ts +44 -26
- package/dist/chat-sdk/index.js +17 -17
- package/dist/{checks-BC_0mPIq.js → checks-CGkP3Z7A.js} +4 -1
- package/dist/{claims-map-7oQ3ue4X.js → claims-map-CKkoAAYF.js} +5 -2
- package/dist/{claims-map-Ca-QX8C6.d.ts → claims-map-D1Pb5oRq.d.ts} +8 -5
- package/dist/claude-agent/index.d.ts +6 -6
- package/dist/claude-agent/index.js +6 -3
- package/dist/clerk/index.d.ts +11 -7
- package/dist/clerk/index.js +6 -6
- package/dist/cli/bin.js +4 -4
- package/dist/cli/index.d.ts +82 -7
- package/dist/cli/index.js +6 -6
- package/dist/{clients-C7zLFuWT.d.ts → clients-C_NOET3z.d.ts} +5 -5
- package/dist/{clients-DjU5ABMy.js → clients-DYr3U86j.js} +3 -3
- package/dist/cloud/index.d.ts +84 -52
- package/dist/cloud/index.js +53 -39
- package/dist/{cloud-CO8pYtwr.js → cloud-BFlGMaak.js} +4 -5
- package/dist/{codemod-i4N2BhDc.js → codemod-DLQ9Zpo4.js} +3 -4
- package/dist/{codemod-BP6n8N9L.js → codemod-DWSN2erG.js} +523 -201
- package/dist/collect-B5BL3klN.js +1 -0
- package/dist/{collect-BaWeMjwV.js → collect-B7ZFAJU3.js} +26 -63
- package/dist/{collect-DNm3oKOk.js → collect-Bzv3le0A.js} +4 -5
- package/dist/{compare-pfyVnBjE.js → compare-DTfyUunF.js} +2 -2
- package/dist/compile/index.d.ts +2 -2
- package/dist/compile/index.js +1 -1
- package/dist/{compile-COM2VKuN.d.ts → compile-BFzysW3o.d.ts} +36 -12
- package/dist/{compile-BAEO5bOk.js → compile-EzEMB-7v.js} +77 -33
- package/dist/{completion-BNdVf7X2.js → completion-saoYiRFO.js} +17 -8
- package/dist/config-BwPBfN0t.js +108 -0
- package/dist/{config-DGZzMV1B.js → config-DBeXXmBF.js} +2 -2
- package/dist/{config-report-Cm7UITNA.js → config-report-BipSWA6V.js} +9 -6
- package/dist/config-shape-ykV0Vm2n.js +134 -0
- package/dist/{connection-DjhIJSs2.js → connection-B72dg5Q2.js} +23 -22
- package/dist/{connector-schema-_VCSsdtD.js → connector-schema-CYLQNMYq.js} +3 -2
- package/dist/{context--uM9V3Ui.js → context-BnIzxTNE.js} +4 -3
- package/dist/convex/index.d.ts +13 -7
- package/dist/convex/index.js +9 -6
- package/dist/{core-B5-w6Kw9.js → core-H-mbhpZc.js} +26 -20
- package/dist/{core-CQkE8Esr.d.ts → core-hWIARPDz.d.ts} +49 -22
- package/dist/{create-UTnUVETj.d.ts → create-BSNrVuxh.d.ts} +70 -37
- package/dist/{create-B2-CQqCI.d.ts → create-BVq1lNQE.d.ts} +49 -28
- package/dist/{create-QHuuPw3q.d.ts → create-CM1FciYG.d.ts} +37 -29
- package/dist/{create-Cb86Z8Im.js → create-Cq4Uox0f.js} +10 -7
- package/dist/{create-DJrlhEp9.js → create-_noSrY1T.js} +15 -21
- package/dist/{credential-CIM7wHwA.js → credential-CyyyvCsL.js} +41 -38
- package/dist/{credentials-BpuTwd7a.js → credentials-CdDxp7jN.js} +6 -6
- package/dist/{deciding-columns-DRo2QM6E.js → deciding-columns-NB4wcyBf.js} +2 -2
- package/dist/{delegation-C5rOdTp5.js → delegation-CDB6_keF.js} +31 -22
- package/dist/delivery/index.d.ts +4 -4
- package/dist/delivery/index.js +5 -5
- package/dist/{delivery-rwOKLWRm.js → delivery-BupfvJIV.js} +34 -22
- package/dist/{delivery-event-xX4obB8F.js → delivery-event-JioK5YbS.js} +6 -6
- package/dist/denied-text-CHNlVInN.d.ts +22 -0
- package/dist/{describe-DZlnEtLy.js → describe-Bb4hGK5d.js} +5 -5
- package/dist/{describe-O0SwYIK8.d.ts → describe-ByS9LgHK.d.ts} +5 -5
- package/dist/{device-BARQ8eYG.js → device-BwbJNmb2.js} +11 -8
- package/dist/{diff-Btr1c1Ow.js → diff-CN8IYhC_.js} +2 -2
- package/dist/{diff-DzT-xD13.js → diff-iZWV65nC.js} +22 -13
- package/dist/{digest-B3kc_PgQ.d.ts → digest-8Yx2CchM.d.ts} +3 -3
- package/dist/{doctor-BU1szn63.js → doctor-CHD1M9R5.js} +287 -135
- package/dist/{doctor-BoK_y0KO.js → doctor-D3wo-yuz.js} +4 -4
- package/dist/drizzle/index.d.ts +33 -19
- package/dist/drizzle/index.js +17 -9
- package/dist/elysia/index.d.ts +17 -11
- package/dist/elysia/index.js +5 -5
- package/dist/{errors-BKy0EfhN.js → errors-B6R_lg2a.js} +13 -4
- package/dist/{errors-BWXBMaKK.d.ts → errors-CHwoRRpB.d.ts} +4 -1
- package/dist/{errors-DpP_U6Bf.js → errors-Vpu6rKHl.js} +13 -10
- package/dist/{errors-B_Qg9D3e.d.ts → errors-zi_4T-rx.d.ts} +22 -6
- package/dist/{evaluate-B_oqFbWC.js → evaluate-Dd8Ta0Ie.js} +140 -78
- package/dist/{evaluations-DReoiVBw.js → evaluations-IBceAeKT.js} +40 -19
- package/dist/{evaluations-BCddEOfO.d.ts → evaluations-z8dQFDaN.d.ts} +11 -5
- package/dist/eve/index.d.ts +9 -6
- package/dist/eve/index.js +4 -4
- package/dist/{event-time-CsegxXxB.js → event-time-BGy48400.js} +8 -4
- package/dist/{expiry-D4_YKRKD.js → expiry-6XByK0dm.js} +8 -5
- package/dist/express/index.d.ts +9 -6
- package/dist/express/index.js +9 -6
- package/dist/fastify/index.d.ts +16 -10
- package/dist/fastify/index.js +8 -5
- package/dist/{fetch-adapter-CuhyYwC1.d.ts → fetch-adapter-C1DCvpqV.d.ts} +9 -6
- package/dist/{fetch-adapter-De-u6vhJ.js → fetch-adapter-JL43jD3E.js} +14 -11
- package/dist/fields-D0vUI6dq.js +498 -0
- package/dist/{files-acUbNwWt.js → files-B3AA05f1.js} +8 -5
- package/dist/{files-D_JIzUrP.js → files-BSjrtIHw.js} +11 -4
- package/dist/{fixtures-BTMW6zgU.js → fixtures-A1_sp_tR.js} +15 -6
- package/dist/{flags-aaeRAdiD.js → flags-DXjoTVbS.js} +2 -2
- package/dist/{freeze-CrU8nQeX.js → freeze-jjan0lCv.js} +4 -1
- package/dist/{from-snapshot-Bz469dIh.js → from-snapshot-DCakWRsr.js} +18 -15
- package/dist/gen-B5kvbAoZ.js +58 -0
- package/dist/gen-DWCYcMQM.js +827 -0
- package/dist/{generate-3FNO5P5Q.js → generate-CRVV5V7l.js} +14 -8
- package/dist/generate-CezbCaTI.js +275 -0
- package/dist/{grantee-BrRANrpT.js → grantee-Dke-9rd1.js} +4 -4
- package/dist/{grantee-conditions-C6P1Tvol.js → grantee-conditions-BrYPe-w0.js} +12 -12
- package/dist/{grantee-match-0RtHWQDx.js → grantee-match-7RZZvoT_.js} +12 -6
- package/dist/{graph-sql-B-dsAT6j.d.ts → graph-sql-BwFC4Qjs.d.ts} +3 -3
- package/dist/h3/index.d.ts +10 -7
- package/dist/h3/index.js +3 -3
- package/dist/headers-B9jQxjtW.d.ts +10 -0
- package/dist/{helpers-DY3vXuGs.js → helpers-DJicaG6f.js} +40 -28
- package/dist/hono/index.d.ts +16 -10
- package/dist/hono/index.js +5 -5
- package/dist/{hosted-BmjzxQhp.d.ts → hosted-Cmtxogd0.d.ts} +33 -21
- package/dist/{hosted-XBgU6RGT.js → hosted-D0IzESu_.js} +42 -21
- package/dist/{http-ujKm0JXE.js → http-DH94eh6f.js} +4 -4
- package/dist/{http-BBoKefeS.js → http-DKLXrFGk.js} +13 -7
- package/dist/{http-Ca3Sr5x1.js → http-DOL33cO0.js} +11 -8
- package/dist/{import-Bz0GefWg.js → import-CST57XLp.js} +64 -51
- package/dist/{import-DB2EG_iy.js → import-_YqhhwvX.js} +6 -6
- package/dist/{index-BSfRl0hZ.d.ts → index-B8s204GL.d.ts} +64 -52
- package/dist/{index-DH9DUDEM.d.ts → index-BT5-1VsX.d.ts} +54 -30
- package/dist/{index-BTkwrK7v.d.ts → index-BW_0MZOS.d.ts} +282 -172
- package/dist/{index-CMiPH2Tv.d.ts → index-BhxEUY9J.d.ts} +64 -37
- package/dist/{index-DLwcuCN_.d.ts → index-Bv5iXPqU.d.ts} +51 -30
- package/dist/{index-BE8y3moU.d.ts → index-_9UiQIkt.d.ts} +127 -82
- package/dist/index.d.ts +22 -22
- package/dist/index.js +33 -33
- package/dist/{init-5TXNji88.js → init-Dz_PsHdv.js} +5 -5
- package/dist/{init-B-4t1NzC.js → init-cE3A8aXq.js} +10 -10
- package/dist/{instance-options-CU0YQCLH.d.ts → instance-options-B0KXqTrJ.d.ts} +4 -4
- package/dist/{instance-snapshot-CjcwnEiV.js → instance-snapshot--JWiTrvO.js} +75 -55
- package/dist/{instant-BluqDvyS.js → instant-DK3Iz83e.js} +2 -2
- package/dist/{internals-Dk8A8fX3.js → internals-B3IgHrvo.js} +6 -3
- package/dist/{json-schema-B-vAwlde.js → json-schema-BPGa-RjQ.js} +9 -3
- package/dist/{json-schema-CN_iLFH2.d.ts → json-schema-BfXgnRNT.d.ts} +4 -4
- package/dist/jwt/index.d.ts +57 -27
- package/dist/jwt/index.js +24 -21
- package/dist/{kernel-DVExUwkp.js → kernel-C-fYydV2.js} +32 -23
- package/dist/kysely/index.d.ts +49 -33
- package/dist/kysely/index.js +9 -7
- package/dist/{live-options-BaAFCIiu.js → live-options-BBIT68le.js} +2 -2
- package/dist/{load-DxroEzH1.js → load-DjSU5KXO.js} +6 -6
- package/dist/load-Dm0pSYH3.js +1 -0
- package/dist/{login-ClSJh5t5.js → login-D-8tMqzo.js} +4 -4
- package/dist/{login-WC9HPJpU.js → login-DplQ0s84.js} +16 -9
- package/dist/{manifest-Dv6q4i5s.js → manifest-Cgiq5oqc.js} +49 -38
- package/dist/{manifest-BVhExYhw.d.ts → manifest-CzzWtnAb.d.ts} +126 -31
- package/dist/{manifest-schema-Cv492Qev.js → manifest-schema-2r37XRhD.js} +44 -3
- package/dist/{manifest-B79YuF2T.js → manifest-wg8WkkpD.js} +6 -6
- package/dist/{map-claims-etGIS_Dv.js → map-claims-BNWCJtmP.js} +14 -8
- package/dist/{map-error-BQGyDm_M.js → map-error-pasIGqMT.js} +7 -7
- package/dist/{markers-C6joDI3o.js → markers-BeHZ4x9Q.js} +20 -5
- package/dist/mcp/index.d.ts +114 -61
- package/dist/mcp/index.js +70 -40
- package/dist/{migrate-Dodel0IJ.js → migrate-B7oP_sQU.js} +35 -17
- package/dist/module-values-BJTBdo6y.js +107 -0
- package/dist/nest/index.d.ts +23 -13
- package/dist/nest/index.js +18 -11
- package/dist/next/client.d.ts +5 -2
- package/dist/next/client.js +1 -1
- package/dist/next/index.d.ts +72 -26
- package/dist/next/index.js +110 -6
- package/dist/next/plugin.d.ts +1 -1
- package/dist/next/plugin.js +4 -3
- package/dist/node/index.d.ts +5 -5
- package/dist/node/index.js +3 -3
- package/dist/{ocsf-BFp-PZZA.js → ocsf-TcjRBgIs.js} +15 -5
- package/dist/openai/index.d.ts +13 -7
- package/dist/openai/index.js +3 -3
- package/dist/openapi/index.d.ts +51 -30
- package/dist/openapi/index.js +2 -2
- package/dist/{openapi-Ddb4628T.js → openapi-BOsx0xn9.js} +33 -18
- package/dist/{openapi-DMocl1bh.js → openapi-CBZUhA3l.js} +4 -5
- package/dist/{openapi-ztVaux6j.js → openapi-VeHHU2l9.js} +23 -14
- package/dist/{operation-findings-kRcEp8lE.js → operation-findings-CXtFzlKJ.js} +3 -3
- package/dist/{operations-BNLes61x.d.ts → operations-Ci_rT6Uo.d.ts} +10 -7
- package/dist/orpc/index.d.ts +68 -14
- package/dist/orpc/index.js +62 -13
- package/dist/otel/index.d.ts +7 -4
- package/dist/otel/index.js +8 -5
- package/dist/output-files-BHOIlrfD.js +1 -0
- package/dist/{output-files-CCMdcFWb.js → output-files-BIQ_9pIs.js} +29 -17
- package/dist/{output-DgSDLrta.js → output-tkju6U7R.js} +9 -5
- package/dist/ownership-B4L59T9u.js +1054 -0
- package/dist/{parse-Ddhz2xf4.js → parse-1_ZheTW5.js} +16 -13
- package/dist/parse-local-manifest-BQhTlLQS.d.ts +10 -0
- package/dist/{parse-local-manifest-T1Q8X3A2.js → parse-local-manifest-DEiCF4ah.js} +19 -7
- package/dist/{parse-request-D_tUXG6M.js → parse-request-CwCZpyjV.js} +7 -8
- package/dist/{paths-DXSHAMIa.js → paths-CUvURzHp.js} +4 -1
- package/dist/pdp/index.d.ts +9 -6
- package/dist/pdp/index.js +20 -13
- package/dist/{peer-DOzzMfZL.js → peer-C9LvBCcb.js} +1 -1
- package/dist/{peer-12Q5I_RH.js → peer-Q0fuZ6hx.js} +9 -3
- package/dist/{permdock-Dco6dyCK.d.ts → permdock-B6Sf4T_S.d.ts} +253 -129
- package/dist/{permdock-BfWEeMqk.js → permdock-L-_I_3zP.js} +59 -35
- package/dist/{permission-registry-DpPtSKNZ.js → permission-registry-B8tYxPIt.js} +23 -10
- package/dist/{permission-types-DhNEmoRG.d.ts → permission-types-CAY-GU7e.d.ts} +127 -41
- package/dist/{permissions-CoqCOfJ3.d.ts → permissions-Bx6UI8n1.d.ts} +6 -3
- package/dist/{permissions-CyH2MV_B.js → permissions-CVXmRILq.js} +3 -3
- package/dist/{permissions-DArULThq.js → permissions-mDcTWoBs.js} +8 -5
- package/dist/pg-DQTqRrou.js +60 -0
- package/dist/{pins-8ILTftC9.js → pins-nqarl3oE.js} +4 -1
- package/dist/{pkce-5h8NgMQJ.js → pkce-CnmmYqUZ.js} +1 -1
- package/dist/{policy-B7dg_yzu.d.ts → policy-Ba4bDhaj.d.ts} +5 -5
- package/dist/{policy-DaJy4nK4.js → policy-CLQ8__GF.js} +43 -23
- package/dist/{policy-from-catalog-DFWYkNdu.js → policy-from-catalog-CJgOm6aq.js} +28 -25
- package/dist/{policy-module-CTlwMvLI.js → policy-module-xym9fnSq.js} +12 -6
- package/dist/{policy-types-B-F-qAZk.d.ts → policy-types-CCiDRZBH.d.ts} +738 -349
- package/dist/{postgres-store-BL9M_CA1.js → postgres-store-DcyrPS-r.js} +43 -16
- package/dist/{postgrest-r5faDIzD.js → postgrest-D4nuPXIQ.js} +52 -33
- package/dist/{postgrest-CYacM_u7.d.ts → postgrest-DicQyqm-.d.ts} +54 -35
- package/dist/{powersync-53ryOZip.js → powersync-DHNQX06W.js} +127 -69
- package/dist/{powersync-13odBwiM.js → powersync-DXqGuq9A.js} +3 -3
- package/dist/powersync-Djf-zCpB.js +1 -0
- package/dist/preset/index.d.ts +38 -28
- package/dist/preset/index.js +1 -1
- package/dist/{preset-CHTbd-DQ.js → preset-BjLzeFpp.js} +56 -32
- package/dist/{principal-source-pHneukrx.js → principal-source-C6N60GtM.js} +10 -10
- package/dist/prisma/index.d.ts +69 -41
- package/dist/prisma/index.js +21 -17
- package/dist/{problem-CTviyH5L.js → problem-BVLHFK33.js} +41 -21
- package/dist/{problem-details-cn5X30gZ.js → problem-details-CLDgX1KV.js} +13 -4
- package/dist/{problem-types-CJ2325SZ.js → problem-types-DSsRfONQ.js} +4 -1
- package/dist/{prompt-DwtVemER.js → prompt-BYc6drYW.js} +4 -1
- package/dist/protected-action-BE7N_Luo.d.ts +58 -0
- package/dist/protected-action-DUYqb-Ma.js +80 -0
- package/dist/{protected-DXE3cQYg.js → protected-action-QEA8arRB.js} +48 -14
- package/dist/{provider-BQsTYYy0.js → provider-C55OGXJ7.js} +5 -4
- package/dist/{provider-Csk-THAn.d.ts → provider-DXfq5c2d.d.ts} +1 -1
- package/dist/{provider-store-IgqQipCO.js → provider-store-CaTv00tr.js} +13 -9
- package/dist/pull-B65ndFNZ.js +1 -0
- package/dist/{pull-CN5OLxrF.js → pull-CwpGaQSi.js} +12 -11
- package/dist/{push-tVdG8dUL.js → push-DgssgzkK.js} +19 -13
- package/dist/{pushed-policy-BbcY9r_4.d.ts → pushed-policy-D1Eoi6NM.d.ts} +12 -6
- package/dist/{pushed-policy-CmYeSQqq.js → pushed-policy-ITwYKvvH.js} +10 -7
- package/dist/react/index.d.ts +9 -8
- package/dist/react/index.js +7 -6
- package/dist/react/provider-client.d.ts +1 -1
- package/dist/react/provider-client.js +1 -1
- package/dist/react-native/index.d.ts +94 -55
- package/dist/react-native/index.js +200 -61
- package/dist/{refs-CNJBiTFo.js → refs-C8BKxc_e.js} +2 -2
- package/dist/{relation-walk-3OyDBF0S.js → relation-walk-DK_gkgL7.js} +12 -9
- package/dist/{relations-BnCWnJGI.js → relations-BxvRvDHK.js} +3 -3
- package/dist/{replay-Du1tJjG7.js → replay-BU09ad4L.js} +1 -1
- package/dist/{replay-DCDD_ZfL.d.ts → replay-DNftIb_R.d.ts} +1 -1
- package/dist/{request-context-BykKLmyE.js → request-context-C25ilqiJ.js} +8 -8
- package/dist/{request-schema-CMreVuFM.js → request-schema-DhX7UbfQ.js} +4 -1
- package/dist/resend/index.d.ts +34 -28
- package/dist/resend/index.js +29 -29
- package/dist/resolve-names-CF7xXLSG.js +201 -0
- package/dist/{resource-relations-D_jw0BFx.js → resource-relations-8-pCScBO.js} +18 -9
- package/dist/{retire-Wj6dkyDE.js → retire-B2xffQgY.js} +15 -6
- package/dist/{revocations-R07lFBnX.d.ts → revocations-BaUXHM1N.d.ts} +6 -3
- package/dist/{rls-CxBZb4rZ.js → rls-CNEH2_BG.js} +299 -124
- package/dist/{rls-Ck_PocSp.js → rls-Ca8Jz9b5.js} +13 -6
- package/dist/{role-builder-C1VJzvcr.js → role-builder-BK2-T6oD.js} +41 -20
- package/dist/{row-conditions-h2HythXQ.js → row-conditions-BAGHOmVq.js} +4 -3
- package/dist/{rpc-Bk1bE-ch.js → rpc-fNqAIqQk.js} +8 -5
- package/dist/{run-uLD2aWuO.js → run-Bz26OTfO.js} +26 -23
- package/dist/run-D-JE8QWb.js +1 -0
- package/dist/{run-ticket-BpNi7BCi.d.ts → run-ticket-DbWI9f99.d.ts} +29 -14
- package/dist/{run-ticket-B5d_63Ma.js → run-ticket-Dv7aF7Dk.js} +9 -9
- package/dist/{runtime-Vif3_-zf.js → runtime-BGnrOkVH.js} +5 -2
- package/dist/{saas-Az_Kz-4D.js → saas-iWnUdwCt.js} +42 -29
- package/dist/{scan-DSljDE1Y.js → scan-DCj1KQfn.js} +1 -1
- package/dist/{schema-B4v0Qrac.js → schema-BOJjqlWL.js} +3 -2
- package/dist/{schema-gIcCx7zM.js → schema-Dst9I9Gs.js} +8 -4
- package/dist/scim/index.d.ts +2 -2
- package/dist/scim/index.js +2 -2
- package/dist/{scim-C7JYhySg.js → scim-D8YupzX1.js} +2 -2
- package/dist/{scim-YNieBO1U.js → scim-jJBJ7g1Z.js} +33 -15
- package/dist/{scopes-BHsc-wp-.js → scopes-0Up_UA2t.js} +41 -23
- package/dist/server/index.d.ts +11 -11
- package/dist/server/index.js +12 -12
- package/dist/{server-Bnga6I50.js → server-3cVlTLde.js} +31 -31
- package/dist/{server-types-C4gZUEZy.d.ts → server-types-CIlpSA2G.d.ts} +81 -46
- package/dist/{sha256-D9ihqmvd.js → sha256-C-4WXKIu.js} +4 -3
- package/dist/{shared-fMKOv8hP.js → shared-Afctg7B0.js} +65 -32
- package/dist/{shared-C8LjUdVU.js → shared-l0oGGetg.js} +9 -6
- package/dist/{signals-D3NcTKUS.js → signals-B1vHR3fN.js} +2 -2
- package/dist/{sink-BEZSlMS_.js → sink-D9_vh372.js} +32 -21
- package/dist/{sink-Cp_Eot9W.d.ts → sink-Dvi7YZG_.d.ts} +44 -20
- package/dist/{skills-Dcjw9_1C.js → skills-Bjj7BNEQ.js} +12 -8
- package/dist/{skills-g_3BS9NC.js → skills-OuoMR49L.js} +3 -3
- package/dist/{snapshot-CbGmYiJx.js → snapshot-CXlzi69u.js} +21 -18
- package/dist/{snapshot-cache-Pahd3IYJ.d.ts → snapshot-cache-BAXZ0KOp.d.ts} +21 -12
- package/dist/{snapshot-cache-DT2Kx1F8.js → snapshot-cache-Bx_4xC_6.js} +14 -11
- package/dist/solid/index.d.ts +55 -16
- package/dist/solid/index.js +46 -12
- package/dist/solid-start/index.d.ts +30 -12
- package/dist/solid-start/index.js +3 -3
- package/dist/{sources-DEEuGb8S.d.ts → sources-B1Os_YUu.d.ts} +126 -53
- package/dist/{sources-B-zYB4GS.js → sources-CMAM-2A3.js} +46 -27
- package/dist/{sql-B96movIM.js → sql-0WjxIp9g.js} +28 -7
- package/dist/{sql-CDEPCA0g.d.ts → sql-B80DFdpG.d.ts} +2 -2
- package/dist/{src-DLFrtrrC.js → src-DZ0l9UAg.js} +190 -100
- package/dist/ssf/index.d.ts +3 -3
- package/dist/ssf/index.js +47 -35
- package/dist/{standard-webhooks-B_qRg0ws.d.ts → standard-webhooks-C-qX4mJW.d.ts} +7 -7
- package/dist/{standard-webhooks-DgFFD-Ga.js → standard-webhooks-D67a3nzk.js} +5 -5
- package/dist/{store-DSP9oGvh.js → store-RNFvaJJv.js} +32 -26
- package/dist/{store-options-B_poyYvg.js → store-options-Dil3lraY.js} +105 -15
- package/dist/stores-C0Kta5Ft.d.ts +59 -0
- package/dist/{stores-BqEuHC05.js → stores-DxZWDXCs.js} +42 -5
- package/dist/{stream-CvdUS9V1.js → stream-CDvF0sJm.js} +5 -5
- package/dist/{stream-2Vwhl7Er.d.ts → stream-ltuLdtTG.d.ts} +4 -3
- package/dist/{style-Ds1gNm77.js → style-DblOtSga.js} +1 -1
- package/dist/{style-Cqo7-73x.js → style-Ds11Hmk4.js} +2 -1
- package/dist/{subject-settings-CkEwrw3W.js → subject-settings-8sH5HlII.js} +8 -4
- package/dist/{row-check-Dt35czLy.d.ts → subject-settings-vNW8qmUH.d.ts} +30 -18
- package/dist/supabase/index.d.ts +48 -38
- package/dist/supabase/index.js +5 -5
- package/dist/supabase/middleware.d.ts +19 -13
- package/dist/supabase/middleware.js +7 -7
- package/dist/{supabase--0aJdLEd.js → supabase-BL1qJzzY.js} +79 -35
- package/dist/{supabase-Bqp7iIY2.js → supabase-D39cuGPt.js} +3 -3
- package/dist/{supabase-hook-Dg0SrQWR.js → supabase-hook-B-yXnzUl.js} +175 -196
- package/dist/supabase-hook-H-MtE9Wd.js +1 -0
- package/dist/{supautils-MESd81bF.js → supautils-BU-pAEpf.js} +166 -47
- package/dist/{support-session-Cna49-3f.js → support-session-BsmPCbz8.js} +21 -21
- package/dist/svelte/PermissionBoundary.svelte +2 -6
- package/dist/svelte/Protected.svelte +2 -8
- package/dist/svelte/index.d.ts +9 -8
- package/dist/svelte/index.js +10 -9
- package/dist/svelte/runtime.d.ts +4 -4
- package/dist/svelte/runtime.js +4 -4
- package/dist/svelte/source.d.ts +9 -8
- package/dist/svelte/source.js +6 -5
- package/dist/sveltekit/index.d.ts +30 -12
- package/dist/sveltekit/index.js +2 -2
- package/dist/{ownership-Z2LpxsGa.js → tables-DeMNEy6V.js} +641 -1104
- package/dist/tanstack-start/index.d.ts +22 -10
- package/dist/tanstack-start/index.js +2 -2
- package/dist/{target-Bw37TFNA.js → target-qdEAnnhU.js} +6 -6
- package/dist/{tenancy-CmeBQ6Nv.js → tenancy-5d1zVBvK.js} +25 -19
- package/dist/terminal/index.d.ts +23 -14
- package/dist/terminal/index.js +17 -20
- package/dist/testing/core.d.ts +1 -1
- package/dist/testing/core.js +1 -1
- package/dist/testing/fixtures/index.d.ts +8 -5
- package/dist/testing/fixtures/index.js +78 -17
- package/dist/testing/index.d.ts +224 -141
- package/dist/testing/index.js +299 -234
- package/dist/testing/saas/index.d.ts +43 -27
- package/dist/testing/saas/index.js +2 -2
- package/dist/testing/saas/permissions.d.ts +1 -1
- package/dist/testing/saas/permissions.js +1 -1
- package/dist/{text-diff-DhITNCnD.js → text-diff-BtfSmdUo.js} +2 -2
- package/dist/{thenable-yD7SabqG.js → thenable-BNSM1U2R.js} +12 -3
- package/dist/{time-DkWjdL98.js → time-nR1JesoH.js} +32 -8
- package/dist/{token-2qYDvXz2.js → token-DC9EQiet.js} +14 -12
- package/dist/{tool-refusal-reAhIzdl.js → tool-refusal-Qpjs_W6M.js} +5 -2
- package/dist/trpc/index.d.ts +11 -8
- package/dist/trpc/index.js +15 -9
- package/dist/type-helpers-BIPIDa6a.d.ts +50 -0
- package/dist/{types-DBHftjgS.d.ts → types-AMJ5qoU_.d.ts} +45 -33
- package/dist/{types-CfiKC5aN.d.ts → types-B2mlGuhW.d.ts} +14 -11
- package/dist/{types-D3A19w4A.d.ts → types-B4uqKDnp.d.ts} +22 -13
- package/dist/{types-CQVEQawv.d.ts → types-BJk0BKRX.d.ts} +24 -14
- package/dist/types-B_d_g7T3.d.ts +262 -0
- package/dist/{types-DC44-hqb.d.ts → types-B_tiYvk8.d.ts} +59 -32
- package/dist/{types-DqzsnlSA.js → types-CcUXciSj.js} +17 -5
- package/dist/{types-BWZybqs9.d.ts → types-DC1Loeh3.d.ts} +381 -202
- package/dist/{types-CIeZcG8v.d.ts → types-DHKPcqzr.d.ts} +2 -2
- package/dist/{types-cTos-H7p.d.ts → types-DjuMBNF9.d.ts} +68 -32
- package/dist/{types-CeIscIKp.d.ts → types-HzqOtvsJ.d.ts} +23 -12
- package/dist/{types-BXMYB7BY.d.ts → types-ZzRGoViD.d.ts} +77 -24
- package/dist/{types-BfVmNJO5.d.ts → types-ah9NlU6j.d.ts} +128 -50
- package/dist/{types-DXmwu78Z.d.ts → types-djVzLlIV.d.ts} +31 -14
- package/dist/{types-g7Oy6Zg4.d.ts → types-yUuPR5Hy.d.ts} +47 -24
- package/dist/unplugin/index.d.ts +1 -1
- package/dist/unplugin/index.js +2 -2
- package/dist/usage-CYeXek0p.js +1 -0
- package/dist/{usage-Bybmck7f.js → usage-CxEzS4ey.js} +50 -26
- package/dist/{usage-CDaLXCfL.js → usage-ab0PdusX.js} +2 -2
- package/dist/{validation-error-ZfUHbKvf.js → validation-error-CYg2kJEw.js} +4 -1
- package/dist/{validation-error-CQpoquwj.d.ts → validation-error-YGg2mXyH.d.ts} +5 -2
- package/dist/{validity-CZ7-y0Cj.js → validity-Buc7w8E9.js} +13 -10
- package/dist/{verifier-DHuh-okk.js → verifier-DUV6pLqZ.js} +10 -4
- package/dist/{verify-pe8rL_Qw.js → verify-DZeqoWu5.js} +148 -487
- package/dist/version-DMQ0dnN5.js +48 -0
- package/dist/version-DizhmD72.js +1 -0
- package/dist/{views-BB8E9dTP.js → views-CD8wuL4Z.js} +19 -6
- package/dist/{vocabulary-Bg3eeT2H.d.ts → vocabulary-DEJ5j4mR.d.ts} +7 -4
- package/dist/vue/index.d.ts +71 -22
- package/dist/vue/index.js +50 -13
- package/dist/{waiter-DwEyouan.d.ts → waiter-Bu9zAlE0.d.ts} +13 -10
- package/dist/{waiter-BGUtGYoU.js → waiter-aBLM63_f.js} +1 -1
- package/dist/{watch-C-4VmFlL.js → watch-Cx_WciLE.js} +8 -8
- package/dist/watch-DB42U1ib.js +1 -0
- package/dist/webmcp/index.d.ts +11 -11
- package/dist/webmcp/index.js +8 -5
- package/dist/{where-scope-wq2coopv.js → where-scope-DlCdCM_8.js} +24 -18
- package/dist/{who-can-BwNsI_Js.js → who-can-rI7dCR9Q.js} +23 -20
- package/dist/workflow/index.d.ts +3 -3
- package/dist/workflow/index.js +13 -13
- package/dist/workflow-sdk/index.d.ts +19 -13
- package/dist/workflow-sdk/index.js +6 -6
- package/dist/{wrap-CiSc_KCq.js → wrap-B2wziJI_.js} +4 -4
- package/dist/{wrap-B6R0cMRL.d.ts → wrap-DlJE3gS_.d.ts} +8 -5
- package/package.json +7 -7
- package/schemas/catalog-v1.json +3 -0
- package/schemas/supabase-manifest-v1.json +33 -1
- package/skills/permdock-credentials/references/tokens-keys-links.md +8 -6
- package/skills/permdock-data/references/queries-and-rls.md +2 -1
- package/skills/permdock-tenancy/references/ownership-and-custom-roles.md +2 -2
- package/skills/permdock-wire/references/adapters.md +14 -22
- package/dist/catalog-BCCjSAdH.js +0 -1
- package/dist/collect-k_Dulxr5.js +0 -1
- package/dist/config-CoQHDrDi.js +0 -166
- package/dist/denied-text-D4qkEtFz.d.ts +0 -19
- package/dist/fields-ClcQLrVc.js +0 -238
- package/dist/generate-CNU1pIOo.js +0 -171
- package/dist/headers-BIjfMaoC.js +0 -8
- package/dist/headers-CIbDTUTK.d.ts +0 -7
- package/dist/load-DrMHXzgN.js +0 -1
- package/dist/output-files-DPAY7-GX.js +0 -1
- package/dist/package-root-Dk_ETdkp.js +0 -31
- package/dist/parse-local-manifest-e4mdwAKd.d.ts +0 -10
- package/dist/policies-B2lZIurj.js +0 -295
- package/dist/powersync-CExtTLyA.js +0 -1
- package/dist/problem-details-Tcfmj3bV.d.ts +0 -7
- package/dist/protected-BwZYL1X_.d.ts +0 -39
- package/dist/pull-Cty6Ovyu.js +0 -1
- package/dist/run-Bz7DXGEv.js +0 -1
- package/dist/stores-BAZwSYZA.d.ts +0 -31
- package/dist/supabase-hook-DJxC4PoP.js +0 -1
- package/dist/types-CKV71kKh.d.ts +0 -172
- package/dist/usage-y-kBNdqs.js +0 -1
- package/dist/version-CEbQ5UeM.js +0 -18
- package/dist/version-DXkYUISx.js +0 -1
- package/dist/watch-Bd9mx3rd.js +0 -1
package/dist/testing/index.js
CHANGED
|
@@ -1,46 +1,47 @@
|
|
|
1
1
|
import { r as isRecord } from "../compact-CAf557ZB.js";
|
|
2
|
-
import { n as freezeDeep } from "../freeze-
|
|
3
|
-
import { i as principal } from "../refs-
|
|
4
|
-
import { n as role, r as allow } from "../role-builder-
|
|
5
|
-
import { A as memoryCustomRoleSource, S as memoryLimitStore, T as approvalPoliciesFor, d as snapshotFor, x as limits } from "../src-
|
|
2
|
+
import { n as freezeDeep } from "../freeze-jjan0lCv.js";
|
|
3
|
+
import { i as principal } from "../refs-C8BKxc_e.js";
|
|
4
|
+
import { n as role, r as allow } from "../role-builder-BK2-T6oD.js";
|
|
5
|
+
import { A as memoryCustomRoleSource, S as memoryLimitStore, T as approvalPoliciesFor, d as snapshotFor, x as limits } from "../src-DZ0l9UAg.js";
|
|
6
6
|
import { t as memoryRevocationFeed } from "../revocations-Bhr0aMIC.js";
|
|
7
|
-
import { n as loadPrincipalByIdentity, t as loadPrincipal } from "../principal-source-
|
|
8
|
-
import { v as resource } from "../permission-registry-
|
|
9
|
-
import { d as isoNow, f as nowMillis, p as nowSeconds, u as isoAt } from "../time-
|
|
10
|
-
import { r as parseAutomation } from "../automation-
|
|
11
|
-
import { f as scopeList, o as normalizeMemberships } from "../scopes-
|
|
12
|
-
import { u as evaluateCondition } from "../delegation-
|
|
13
|
-
import { a as authenticated } from "../grantee-
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import { c as mergeHostedGrants, l as parsePolicyDocument } from "../hosted-
|
|
17
|
-
import { t as definePermissions } from "../permissions-
|
|
18
|
-
import { T as validateCustomRole } from "../evaluate-
|
|
19
|
-
import { n as parseSnapshot } from "../snapshot-
|
|
20
|
-
import { n as fromSnapshot } from "../from-snapshot-
|
|
21
|
-
import { t as getPermDock } from "../permdock-
|
|
22
|
-
import { t as APPROVAL_HEADER } from "../types-
|
|
23
|
-
import { i as memoryApprovalStore } from "../store-
|
|
24
|
-
import { t as catalogFingerprint } from "../catalog-fingerprint-
|
|
25
|
-
import { s as parseCredential } from "../credential-
|
|
26
|
-
import {
|
|
27
|
-
import { o as resolveApproval } from "../helpers-
|
|
28
|
-
import { t as APPROVAL_REQUEST_SCHEMA } from "../request-schema-
|
|
29
|
-
import { t as catalogSchema } from "../schema-
|
|
30
|
-
import { c as parseApiKey } from "../server-
|
|
31
|
-
import { n as discoverAuthorizationServer } from "../device-
|
|
32
|
-
import { t as CONNECTOR_SCHEMA } from "../connector-schema-
|
|
33
|
-
import { l as sha256Hex, n as scimHandler, t as directoryMembershipSource } from "../scim-
|
|
34
|
-
import {
|
|
35
|
-
import { t as
|
|
36
|
-
import { t as
|
|
37
|
-
import { t as
|
|
38
|
-
import {
|
|
39
|
-
import { i as
|
|
40
|
-
import {
|
|
41
|
-
import { t as
|
|
7
|
+
import { n as loadPrincipalByIdentity, t as loadPrincipal } from "../principal-source-C6N60GtM.js";
|
|
8
|
+
import { v as resource } from "../permission-registry-B8tYxPIt.js";
|
|
9
|
+
import { d as isoNow, f as nowMillis, p as nowSeconds, u as isoAt } from "../time-nR1JesoH.js";
|
|
10
|
+
import { r as parseAutomation } from "../automation-x3KEUusw.js";
|
|
11
|
+
import { f as scopeList, o as normalizeMemberships } from "../scopes-0Up_UA2t.js";
|
|
12
|
+
import { u as evaluateCondition } from "../delegation-CDB6_keF.js";
|
|
13
|
+
import { a as authenticated } from "../grantee-Dke-9rd1.js";
|
|
14
|
+
import { t as byCodePoint } from "../compare-DTfyUunF.js";
|
|
15
|
+
import { r as definePolicy } from "../policy-CLQ8__GF.js";
|
|
16
|
+
import { c as mergeHostedGrants, l as parsePolicyDocument } from "../hosted-D0IzESu_.js";
|
|
17
|
+
import { t as definePermissions } from "../permissions-CVXmRILq.js";
|
|
18
|
+
import { T as validateCustomRole } from "../evaluate-Dd8Ta0Ie.js";
|
|
19
|
+
import { n as parseSnapshot } from "../snapshot-CXlzi69u.js";
|
|
20
|
+
import { n as fromSnapshot } from "../from-snapshot-DCakWRsr.js";
|
|
21
|
+
import { t as getPermDock } from "../permdock-L-_I_3zP.js";
|
|
22
|
+
import { t as APPROVAL_HEADER } from "../types-CcUXciSj.js";
|
|
23
|
+
import { i as memoryApprovalStore } from "../store-RNFvaJJv.js";
|
|
24
|
+
import { t as catalogFingerprint } from "../catalog-fingerprint-e6aCWosn.js";
|
|
25
|
+
import { s as parseCredential } from "../credential-CyyyvCsL.js";
|
|
26
|
+
import { c as problemFromDecision } from "../problem-BVLHFK33.js";
|
|
27
|
+
import { o as resolveApproval } from "../helpers-DJicaG6f.js";
|
|
28
|
+
import { t as APPROVAL_REQUEST_SCHEMA } from "../request-schema-DhX7UbfQ.js";
|
|
29
|
+
import { t as catalogSchema } from "../schema-BOJjqlWL.js";
|
|
30
|
+
import { c as parseApiKey } from "../server-3cVlTLde.js";
|
|
31
|
+
import { n as discoverAuthorizationServer } from "../device-BwbJNmb2.js";
|
|
32
|
+
import { t as CONNECTOR_SCHEMA } from "../connector-schema-CYLQNMYq.js";
|
|
33
|
+
import { l as sha256Hex, n as scimHandler, t as directoryMembershipSource } from "../scim-jJBJ7g1Z.js";
|
|
34
|
+
import { c as quoteSqlTable, o as quoteSqlIdent } from "../sql-0WjxIp9g.js";
|
|
35
|
+
import { i as cloudEndpoints, n as cloud, s as approverHeaders, t as bearerChallenge } from "../challenge-DImNcR7B.js";
|
|
36
|
+
import { t as supabaseMembershipsBudget } from "../budget-BELja5A9.js";
|
|
37
|
+
import { t as supabaseManifestSchema } from "../manifest-schema-2r37XRhD.js";
|
|
38
|
+
import { t as PUSHED_POLICY_SCHEMA } from "../pushed-policy-ITwYKvvH.js";
|
|
39
|
+
import { i as policyMatrixLayout, n as testRlsParity, r as policyMatrix, t as testOrmParity } from "../core-H-mbhpZc.js";
|
|
40
|
+
import { i as saasPermissions } from "../permissions-mDcTWoBs.js";
|
|
41
|
+
import { C as saasOrg, E as saasSeed, S as saasMemberships, a as saasProject, g as verifySaasSession, h as signSaasToken, k as saasPolicy, t as saasDoc, w as saasPrincipal, x as saasCustomRoles } from "../saas-iWnUdwCt.js";
|
|
42
|
+
import { t as POLICY_NOT_PUBLISHED } from "../problem-types-DSsRfONQ.js";
|
|
42
43
|
import { t as require__2020 } from "../2020-CJbO8Bvz.js";
|
|
43
|
-
import { afterAll, beforeAll, describe, expect, expectTypeOf, it, vi } from "vitest";
|
|
44
|
+
import { afterAll, assert, beforeAll, describe, expect, expectTypeOf, it, vi } from "vitest";
|
|
44
45
|
//#region src/testing/describe-policy.ts
|
|
45
46
|
/** `policyMatrix` under Vitest: one test per cell, plus the exhaustive check. */
|
|
46
47
|
function describePolicy(policy, config) {
|
|
@@ -59,7 +60,7 @@ function describePolicy(policy, config) {
|
|
|
59
60
|
it(title, () => {
|
|
60
61
|
const decided = state.report?.cells.find((item) => item.name === cell.name);
|
|
61
62
|
expect(decided, `cell ${cell.name} was not decided`).toBeDefined();
|
|
62
|
-
expect(decided?.failures).
|
|
63
|
+
expect(decided?.failures).toStrictEqual([]);
|
|
63
64
|
});
|
|
64
65
|
}
|
|
65
66
|
});
|
|
@@ -79,8 +80,8 @@ async function outcomes(policy, entry, options) {
|
|
|
79
80
|
};
|
|
80
81
|
}
|
|
81
82
|
/**
|
|
82
|
-
* Asserts that `fromSnapshot(permdock.snapshot())` never grants what the
|
|
83
|
-
*
|
|
83
|
+
* Asserts that `fromSnapshot(permdock.snapshot())` never grants what the server
|
|
84
|
+
* denies, and matches it exactly unless a case is marked `stricter`.
|
|
84
85
|
*/
|
|
85
86
|
function testClientParity(policy, cases, options = {}) {
|
|
86
87
|
for (const entry of cases) it(`client parity: ${entry.name}`, async () => {
|
|
@@ -95,9 +96,9 @@ function testClientParity(policy, cases, options = {}) {
|
|
|
95
96
|
//#endregion
|
|
96
97
|
//#region src/testing/legacy-parity.ts
|
|
97
98
|
/**
|
|
98
|
-
* Asserts that the policy answers each case as the application's legacy
|
|
99
|
-
*
|
|
100
|
-
*
|
|
99
|
+
* Asserts that the policy answers each case as the application's legacy check
|
|
100
|
+
* does, or as the case's `expect` says where the difference is intended. Run it
|
|
101
|
+
* before enforcing a permission with `shadowMode`.
|
|
101
102
|
*/
|
|
102
103
|
function testLegacyParity(policy, legacy, cases) {
|
|
103
104
|
for (const entry of cases) it(`legacy parity: ${entry.name}`, async () => {
|
|
@@ -111,7 +112,7 @@ function testLegacyParity(policy, legacy, cases) {
|
|
|
111
112
|
expect({
|
|
112
113
|
permission: entry.permission.key,
|
|
113
114
|
granted: ours
|
|
114
|
-
}).
|
|
115
|
+
}).toStrictEqual({
|
|
115
116
|
permission: entry.permission.key,
|
|
116
117
|
granted: entry.expect ?? theirs
|
|
117
118
|
});
|
|
@@ -217,9 +218,9 @@ function unreachable() {
|
|
|
217
218
|
}
|
|
218
219
|
/**
|
|
219
220
|
* Race and cache scenarios for a client snapshot store: tenant switches,
|
|
220
|
-
* `replace` and `refresh`, logout and a second user during a refresh, stale
|
|
221
|
-
*
|
|
222
|
-
*
|
|
221
|
+
* `replace` and `refresh`, logout and a second user during a refresh, stale and
|
|
222
|
+
* signed snapshots, and endpoint answers cached per tenant. Runs against the
|
|
223
|
+
* saas domain; `createStore` wraps the store an adapter ships.
|
|
223
224
|
*/
|
|
224
225
|
function testClientStore(name, createStore) {
|
|
225
226
|
const acmeProject = saasProject("p1");
|
|
@@ -251,7 +252,7 @@ function testClientStore(name, createStore) {
|
|
|
251
252
|
fetch: net.fetch
|
|
252
253
|
});
|
|
253
254
|
await store.get().refresh({ tenant: "globex" });
|
|
254
|
-
expect(net.calls.map((call) => call.url)).
|
|
255
|
+
expect(net.calls.map((call) => call.url)).toStrictEqual(["/api/snapshot?v=1&tenant=globex"]);
|
|
255
256
|
expect(store.get().subject.principal?.tenant).toBe("acme");
|
|
256
257
|
expect(store.get().can(p.project.update, acmeProject)).toBe(true);
|
|
257
258
|
expect(store.get().status()).toBe("stale");
|
|
@@ -499,15 +500,36 @@ function testClientStore(name, createStore) {
|
|
|
499
500
|
async function snapshotFixture(policy, subject, options = {}) {
|
|
500
501
|
const instance = await getPermDock(policy, subject, options.tenant === void 0 ? {} : { tenant: options.tenant });
|
|
501
502
|
const target = options.simulated === true ? instance.simulate({}) : instance;
|
|
502
|
-
if (Array.isArray(target)) throw new
|
|
503
|
+
if (Array.isArray(target)) throw new TypeError("PermDock: snapshotFixture expected a PermDock instance");
|
|
503
504
|
const snapshot = options.include === void 0 && options.tenants === void 0 ? target.snapshot() : target.snapshot({
|
|
504
505
|
...options.include === void 0 ? {} : { include: options.include },
|
|
505
506
|
...options.tenants === void 0 ? {} : { tenants: options.tenants }
|
|
506
507
|
});
|
|
507
|
-
if (typeof snapshot === "string" || snapshot instanceof Promise) throw new
|
|
508
|
+
if (typeof snapshot === "string" || snapshot instanceof Promise) throw new TypeError("PermDock: snapshotFixture expected a JSON snapshot");
|
|
508
509
|
return snapshot;
|
|
509
510
|
}
|
|
510
511
|
//#endregion
|
|
512
|
+
//#region src/testing/seed-rows.ts
|
|
513
|
+
/**
|
|
514
|
+
* Inserts `factories.*` rows through the `query` function `testRlsParity`
|
|
515
|
+
* takes, one parameterised `insert` per row with the row's own keys as quoted
|
|
516
|
+
* column names. `undefined` fields are left to the column default. Run it as
|
|
517
|
+
* the table owner, before RLS applies. Resolves to the number of rows
|
|
518
|
+
* inserted.
|
|
519
|
+
*/
|
|
520
|
+
async function seedRows(query, rows) {
|
|
521
|
+
let inserted = 0;
|
|
522
|
+
for (const [table, list] of Object.entries(rows)) {
|
|
523
|
+
const target = quoteSqlTable(table);
|
|
524
|
+
for (const row of list) {
|
|
525
|
+
const columns = Object.keys(row).filter((key) => row[key] !== void 0);
|
|
526
|
+
await query(columns.length === 0 ? `insert into ${target} default values` : `insert into ${target} (${columns.map((column) => quoteSqlIdent(column)).join(", ")}) values (${columns.map((_, index) => `$${String(index + 1)}`).join(", ")})`, columns.map((column) => row[column]));
|
|
527
|
+
inserted += 1;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
return inserted;
|
|
531
|
+
}
|
|
532
|
+
//#endregion
|
|
511
533
|
//#region src/testing/http-adapter/domain.ts
|
|
512
534
|
function project$1(id) {
|
|
513
535
|
const row = saasSeed.projects.find((candidate) => candidate.id === id);
|
|
@@ -828,11 +850,12 @@ function testHttpAdapter(options) {
|
|
|
828
850
|
return eventStream(await mounted.fetch(new Request(`http://app.test/${org}/projects/${id}/events`, { headers })));
|
|
829
851
|
};
|
|
830
852
|
scenario("tenant", "resolves the tenant from the path on every protect", async () => {
|
|
831
|
-
|
|
853
|
+
const { status } = await send("alice", {
|
|
832
854
|
op: "project.get",
|
|
833
855
|
org: "acme",
|
|
834
856
|
id: "p2"
|
|
835
|
-
})
|
|
857
|
+
});
|
|
858
|
+
expect(status).toBe(200);
|
|
836
859
|
expect((await send("alice", {
|
|
837
860
|
op: "project.get",
|
|
838
861
|
org: "globex",
|
|
@@ -855,11 +878,12 @@ function testHttpAdapter(options) {
|
|
|
855
878
|
})).status).toBe(401);
|
|
856
879
|
});
|
|
857
880
|
scenario("roles", "applies the role held in the requested org", async () => {
|
|
858
|
-
|
|
881
|
+
const { status } = await send("alice", {
|
|
859
882
|
op: "project.update",
|
|
860
883
|
org: "acme",
|
|
861
884
|
id: "p1"
|
|
862
|
-
})
|
|
885
|
+
});
|
|
886
|
+
expect(status).toBe(200);
|
|
863
887
|
expect((await send("alice", {
|
|
864
888
|
op: "project.update",
|
|
865
889
|
org: "globex",
|
|
@@ -884,11 +908,12 @@ function testHttpAdapter(options) {
|
|
|
884
908
|
name: "Contract",
|
|
885
909
|
archived: false
|
|
886
910
|
};
|
|
887
|
-
|
|
911
|
+
const { status } = await send("dave", {
|
|
888
912
|
op: "project.create",
|
|
889
913
|
org: "acme",
|
|
890
914
|
body
|
|
891
|
-
})
|
|
915
|
+
});
|
|
916
|
+
expect(status).toBe(201);
|
|
892
917
|
expect((await send("hank", {
|
|
893
918
|
op: "project.create",
|
|
894
919
|
org: "acme",
|
|
@@ -896,29 +921,34 @@ function testHttpAdapter(options) {
|
|
|
896
921
|
})).status).toBe(403);
|
|
897
922
|
});
|
|
898
923
|
scenario("plans", "gates a feature on the plan of the requested org", async () => {
|
|
899
|
-
|
|
924
|
+
const { status } = await send("erin", {
|
|
900
925
|
op: "analytics.read",
|
|
901
926
|
org: "globex"
|
|
902
|
-
})
|
|
927
|
+
});
|
|
928
|
+
expect(status).toBe(200);
|
|
903
929
|
const upgrade = await send("erin", {
|
|
904
930
|
op: "analytics.read",
|
|
905
931
|
org: "acme"
|
|
906
932
|
});
|
|
907
933
|
expect(upgrade.status).toBe(403);
|
|
908
|
-
expect(new Set(reasonsOf(upgrade.body))).
|
|
934
|
+
expect(new Set(reasonsOf(upgrade.body))).toStrictEqual(/* @__PURE__ */ new Set(["not-entitled"]));
|
|
909
935
|
});
|
|
910
936
|
scenario("expired", "ignores an expired membership", async () => {
|
|
911
|
-
|
|
937
|
+
const { status } = await send("frank", {
|
|
912
938
|
op: "project.get",
|
|
913
939
|
org: "acme",
|
|
914
940
|
id: "p1"
|
|
915
|
-
})
|
|
941
|
+
});
|
|
942
|
+
expect(status).toBe(403);
|
|
916
943
|
});
|
|
917
944
|
scenario("limits", "counts a quota per subject and tenant", async () => {
|
|
918
|
-
for (let index = 0; index < 5; index += 1)
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
945
|
+
for (let index = 0; index < 5; index += 1) {
|
|
946
|
+
const { status } = await send("erin", {
|
|
947
|
+
op: "apiKey.create",
|
|
948
|
+
org: "acme"
|
|
949
|
+
});
|
|
950
|
+
expect(status).toBe(201);
|
|
951
|
+
}
|
|
922
952
|
const exhausted = await send("erin", {
|
|
923
953
|
op: "apiKey.create",
|
|
924
954
|
org: "acme"
|
|
@@ -938,11 +968,12 @@ function testHttpAdapter(options) {
|
|
|
938
968
|
name: "New",
|
|
939
969
|
archived: false
|
|
940
970
|
};
|
|
941
|
-
|
|
971
|
+
const { status } = await send("bob", {
|
|
942
972
|
op: "project.create",
|
|
943
973
|
org: "acme",
|
|
944
974
|
body: valid
|
|
945
|
-
})
|
|
975
|
+
});
|
|
976
|
+
expect(status).toBe(201);
|
|
946
977
|
const invalid = await send("bob", {
|
|
947
978
|
op: "project.create",
|
|
948
979
|
org: "acme",
|
|
@@ -978,12 +1009,13 @@ function testHttpAdapter(options) {
|
|
|
978
1009
|
"title",
|
|
979
1010
|
"permission",
|
|
980
1011
|
"denials"
|
|
981
|
-
]) expect(field(denied.body, key)).
|
|
982
|
-
|
|
1012
|
+
]) expect(field(denied.body, key)).toStrictEqual(field(fromProtect.body, key));
|
|
1013
|
+
const { status } = await send("bob", {
|
|
983
1014
|
op: "project.delete",
|
|
984
1015
|
org: "acme",
|
|
985
1016
|
id: "p3"
|
|
986
|
-
})
|
|
1017
|
+
});
|
|
1018
|
+
expect(status).toBe(204);
|
|
987
1019
|
expect((await send("bob", {
|
|
988
1020
|
op: "project.delete",
|
|
989
1021
|
org: "acme",
|
|
@@ -998,7 +1030,7 @@ function testHttpAdapter(options) {
|
|
|
998
1030
|
expect(asked.status).toBe(403);
|
|
999
1031
|
expect(field(asked.body, "type")).toBe("https://permdock.com/problems/approval-required");
|
|
1000
1032
|
const token = field(asked.body, "token");
|
|
1001
|
-
expect(
|
|
1033
|
+
expect(token).toBeTypeOf("string");
|
|
1002
1034
|
await resolveApproval(domain.store, String(token), {
|
|
1003
1035
|
status: "approved",
|
|
1004
1036
|
by: await approverSubject("carol", "acme")
|
|
@@ -1017,11 +1049,12 @@ function testHttpAdapter(options) {
|
|
|
1017
1049
|
})).status).toBe(204);
|
|
1018
1050
|
});
|
|
1019
1051
|
scenario("stale-approval", "ignores an approval header meant for another request", async () => {
|
|
1020
|
-
|
|
1052
|
+
const { status } = await send("bob", {
|
|
1021
1053
|
op: "project.get",
|
|
1022
1054
|
org: "acme",
|
|
1023
1055
|
id: "p1"
|
|
1024
|
-
}, "pd1.not-a-real-token")
|
|
1056
|
+
}, "pd1.not-a-real-token");
|
|
1057
|
+
expect(status).toBe(200);
|
|
1025
1058
|
});
|
|
1026
1059
|
scenario("evaluations", "answers the decision endpoint in the path tenant", async () => {
|
|
1027
1060
|
const result = await send("bob", {
|
|
@@ -1045,7 +1078,7 @@ function testHttpAdapter(options) {
|
|
|
1045
1078
|
});
|
|
1046
1079
|
expect(result.status).toBe(200);
|
|
1047
1080
|
const rows = field(result.body, "evaluations");
|
|
1048
|
-
expect(Array.isArray(rows) ? rows.map((row) => field(row, "decision")) : rows).
|
|
1081
|
+
expect(Array.isArray(rows) ? rows.map((row) => field(row, "decision")) : rows).toStrictEqual([true, false]);
|
|
1049
1082
|
});
|
|
1050
1083
|
scenario("upload", "leaves a multipart body to the framework parser", async () => {
|
|
1051
1084
|
const file = {
|
|
@@ -1059,22 +1092,24 @@ function testHttpAdapter(options) {
|
|
|
1059
1092
|
file
|
|
1060
1093
|
});
|
|
1061
1094
|
expect(uploaded.status).toBe(201);
|
|
1062
|
-
expect(uploaded.body).
|
|
1095
|
+
expect(uploaded.body).toStrictEqual({
|
|
1063
1096
|
name: "notes.txt",
|
|
1064
1097
|
size: 5
|
|
1065
1098
|
});
|
|
1066
|
-
|
|
1099
|
+
const { status } = await send("bob", {
|
|
1067
1100
|
op: "project.upload",
|
|
1068
1101
|
org: "acme",
|
|
1069
1102
|
id: "p2",
|
|
1070
1103
|
file
|
|
1071
|
-
})
|
|
1104
|
+
});
|
|
1105
|
+
expect(status).toBe(403);
|
|
1072
1106
|
});
|
|
1073
1107
|
scenario("sub-app", "protects a route inside a sub-app", async () => {
|
|
1074
|
-
|
|
1108
|
+
const { status } = await send("alice", {
|
|
1075
1109
|
op: "admin.members",
|
|
1076
1110
|
org: "acme"
|
|
1077
|
-
})
|
|
1111
|
+
});
|
|
1112
|
+
expect(status).toBe(200);
|
|
1078
1113
|
expect((await send("bob", {
|
|
1079
1114
|
op: "admin.members",
|
|
1080
1115
|
org: "acme"
|
|
@@ -1091,7 +1126,7 @@ function testHttpAdapter(options) {
|
|
|
1091
1126
|
org: entry.org,
|
|
1092
1127
|
id: entry.id
|
|
1093
1128
|
})).status));
|
|
1094
|
-
expect(statuses).
|
|
1129
|
+
expect(statuses).toStrictEqual(calls.map((entry) => entry.status));
|
|
1095
1130
|
});
|
|
1096
1131
|
scenario("stream-revoked", "closes an open stream when the session is revoked", async () => {
|
|
1097
1132
|
expect((await openStream(null, "acme", "p1")).status).toBe(401);
|
|
@@ -1146,7 +1181,7 @@ function testHttpAdapter(options) {
|
|
|
1146
1181
|
expect(frame?.event).toBe("message");
|
|
1147
1182
|
ids.push(String(field(JSON.parse(frame?.data ?? "{}"), "id")));
|
|
1148
1183
|
}
|
|
1149
|
-
expect(new Set(ids)).
|
|
1184
|
+
expect(new Set(ids)).toStrictEqual(/* @__PURE__ */ new Set([
|
|
1150
1185
|
"p1",
|
|
1151
1186
|
"p3",
|
|
1152
1187
|
"p4"
|
|
@@ -1473,10 +1508,11 @@ function testAgentRuntime(options) {
|
|
|
1473
1508
|
const listed = await agent().allowedOperations(alice);
|
|
1474
1509
|
expect(listed.has("project_read")).toBe(true);
|
|
1475
1510
|
expect(listed.has("drop_database")).toBe(false);
|
|
1476
|
-
expect([...await agent().allowedOperations(mallory)]).
|
|
1511
|
+
expect([...await agent().allowedOperations(mallory)]).toStrictEqual([]);
|
|
1477
1512
|
});
|
|
1478
1513
|
scenario("model-gate", "grants a model by its permission and denies one the map does not name", async () => {
|
|
1479
|
-
|
|
1514
|
+
const { outcome } = await agent().decideModel("frontier", erin);
|
|
1515
|
+
expect(outcome).toBe("granted");
|
|
1480
1516
|
expect((await agent().decideModel("frontier", alice)).outcome).toBe("denied");
|
|
1481
1517
|
expect((await agent().decideModel("unlisted", erin)).outcome).toBe("denied");
|
|
1482
1518
|
expect((await agent().decideModel("standard", {
|
|
@@ -1485,15 +1521,15 @@ function testAgentRuntime(options) {
|
|
|
1485
1521
|
})).outcome).toBe("denied");
|
|
1486
1522
|
});
|
|
1487
1523
|
scenario("permitted-models", "returns the granted models in declaration order", async () => {
|
|
1488
|
-
expect(await agent().permittedModels(erin)).
|
|
1489
|
-
expect(await agent().permittedModels(alice)).
|
|
1490
|
-
expect(await agent().permittedModels(mallory)).
|
|
1524
|
+
expect(await agent().permittedModels(erin)).toStrictEqual(["standard", "frontier"]);
|
|
1525
|
+
expect(await agent().permittedModels(alice)).toStrictEqual(["standard"]);
|
|
1526
|
+
expect(await agent().permittedModels(mallory)).toStrictEqual([]);
|
|
1491
1527
|
});
|
|
1492
1528
|
scenario("results-filter", "keeps the rows the results permission grants, and none for an unknown operation", async () => {
|
|
1493
1529
|
const rows = projects("p1", "g1", "p2");
|
|
1494
|
-
expect((await agent().filterResults("project_search", rows, alice)).map((row) => row.id)).
|
|
1495
|
-
expect(await agent().filterResults("project_search", rows, mallory)).
|
|
1496
|
-
expect(await agent().filterResults("drop_database", rows, alice)).
|
|
1530
|
+
expect((await agent().filterResults("project_search", rows, alice)).map((row) => row.id)).toStrictEqual(["p1", "p2"]);
|
|
1531
|
+
expect(await agent().filterResults("project_search", rows, mallory)).toStrictEqual([]);
|
|
1532
|
+
expect(await agent().filterResults("drop_database", rows, alice)).toStrictEqual([]);
|
|
1497
1533
|
});
|
|
1498
1534
|
scenario("gateway-options", "attributes gateway usage to the principal, and to nobody for an anonymous call", async () => {
|
|
1499
1535
|
const attributed = await agent().gatewayOptions(alice);
|
|
@@ -1543,7 +1579,7 @@ function testAuthzen(handle, options) {
|
|
|
1543
1579
|
}
|
|
1544
1580
|
for (const [index, vector] of (options.vectors.evaluations ?? []).entries()) it(`evaluations ${index}: ${vector.request.evaluations.length} items`, async () => {
|
|
1545
1581
|
const body = await post("/access/v1/evaluations", vector.request);
|
|
1546
|
-
expect(body.evaluations.map((row) => ({ decision: row.decision }))).
|
|
1582
|
+
expect(body.evaluations.map((row) => ({ decision: row.decision }))).toStrictEqual(vector.expected);
|
|
1547
1583
|
});
|
|
1548
1584
|
const search = options.vectors.search ?? {};
|
|
1549
1585
|
for (const kind of [
|
|
@@ -1552,10 +1588,13 @@ function testAuthzen(handle, options) {
|
|
|
1552
1588
|
"action"
|
|
1553
1589
|
]) for (const [index, vector] of (search[kind] ?? []).entries()) it(`search/${kind} ${index}`, async () => {
|
|
1554
1590
|
const body = await post(`/access/v1/search/${kind}`, vector.request);
|
|
1555
|
-
expect(body.results.map(sortKey).toSorted()).
|
|
1591
|
+
expect(body.results.map(sortKey).toSorted()).toStrictEqual(vector.expected.results.map(sortKey).toSorted());
|
|
1556
1592
|
});
|
|
1557
1593
|
}
|
|
1558
|
-
/**
|
|
1594
|
+
/**
|
|
1595
|
+
* The AuthZEN interop Todo directory: each user is addressable by email or by
|
|
1596
|
+
* identity.
|
|
1597
|
+
*/
|
|
1559
1598
|
const authzenTodoUsers = Object.freeze([
|
|
1560
1599
|
{
|
|
1561
1600
|
id: "rick@the-citadel.com",
|
|
@@ -1600,7 +1639,10 @@ const authzenTodoPermissions = definePermissions({
|
|
|
1600
1639
|
});
|
|
1601
1640
|
const todo = authzenTodoPermissions.todo;
|
|
1602
1641
|
const everyone = [allow(authzenTodoPermissions.user.can_read_user), allow(todo.can_read_todos)];
|
|
1603
|
-
/**
|
|
1642
|
+
/**
|
|
1643
|
+
* The interop Todo rules: viewers read, editors write their own, admins delete
|
|
1644
|
+
* and evil geniuses update anything.
|
|
1645
|
+
*/
|
|
1604
1646
|
const authzenTodoPolicy = definePolicy(authzenTodoPermissions, {
|
|
1605
1647
|
roles: [
|
|
1606
1648
|
role("viewer", everyone),
|
|
@@ -1635,7 +1677,10 @@ const TODOS = Object.freeze(authzenTodoUsers.map((user, index) => ({
|
|
|
1635
1677
|
id: `todo-${String(index + 1)}`,
|
|
1636
1678
|
ownerID: user.id
|
|
1637
1679
|
})));
|
|
1638
|
-
/**
|
|
1680
|
+
/**
|
|
1681
|
+
* Rows for `resources.todo.list` and records for `subjects.list` in the Todo
|
|
1682
|
+
* domain.
|
|
1683
|
+
*/
|
|
1639
1684
|
const authzenTodoData = Object.freeze({
|
|
1640
1685
|
todos: TODOS,
|
|
1641
1686
|
users: Object.freeze(authzenTodoUsers.map((user) => ({ id: user.id })))
|
|
@@ -1653,9 +1698,9 @@ function expectedFor(user, action, ownerID) {
|
|
|
1653
1698
|
}
|
|
1654
1699
|
}
|
|
1655
1700
|
/**
|
|
1656
|
-
* PermDock's vectors over the interop Todo domain, in the interop layout:
|
|
1657
|
-
*
|
|
1658
|
-
*
|
|
1701
|
+
* PermDock's vectors over the interop Todo domain, in the interop layout: every
|
|
1702
|
+
* user against every action on their own and on Rick's todo, one batch per
|
|
1703
|
+
* user, and the three searches.
|
|
1659
1704
|
*/
|
|
1660
1705
|
function authzenTodoVectors() {
|
|
1661
1706
|
const rick = authzenTodoUsers[0];
|
|
@@ -1855,9 +1900,9 @@ function testApprovalStore(store, options = {}) {
|
|
|
1855
1900
|
});
|
|
1856
1901
|
const [first, second] = await Promise.all([store.consume("once-token"), store.consume("once-token")]);
|
|
1857
1902
|
expect([first, second].filter((item) => item !== null)).toHaveLength(1);
|
|
1858
|
-
expect((first ?? second)?.consumedAt).
|
|
1903
|
+
expect((first ?? second)?.consumedAt).toStrictEqual(expect.any(String));
|
|
1859
1904
|
expect(await store.consume("once-token")).toBeNull();
|
|
1860
|
-
expect((await store.get("once-token"))?.consumedAt).
|
|
1905
|
+
expect((await store.get("once-token"))?.consumedAt).toStrictEqual(expect.any(String));
|
|
1861
1906
|
});
|
|
1862
1907
|
it("refuses an approver from another tenant", async () => {
|
|
1863
1908
|
await store.create(tenantApproval("tenant-token", "o_1"));
|
|
@@ -1873,6 +1918,7 @@ function testApprovalStore(store, options = {}) {
|
|
|
1873
1918
|
status: "approved",
|
|
1874
1919
|
by: approver
|
|
1875
1920
|
});
|
|
1921
|
+
assert.isDefined(options.reopen);
|
|
1876
1922
|
const reopened = await options.reopen();
|
|
1877
1923
|
expect((await reopened.get("restart-token"))?.status).toBe("approved");
|
|
1878
1924
|
expect(await reopened.consume("restart-token")).not.toBeNull();
|
|
@@ -1880,6 +1926,7 @@ function testApprovalStore(store, options = {}) {
|
|
|
1880
1926
|
});
|
|
1881
1927
|
it.skipIf(options.capabilities?.expire === void 0)("replaces an expired request on a new ask", async () => {
|
|
1882
1928
|
await store.create(sampleApproval("renew-token"));
|
|
1929
|
+
assert.isDefined(options.capabilities?.expire);
|
|
1883
1930
|
await options.capabilities.expire("renew-token");
|
|
1884
1931
|
const renewed = {
|
|
1885
1932
|
...sampleApproval("renew-token"),
|
|
@@ -1894,8 +1941,8 @@ function testApprovalStore(store, options = {}) {
|
|
|
1894
1941
|
const request = sampleApproval("opaque-token");
|
|
1895
1942
|
await store.create(request);
|
|
1896
1943
|
const loaded = await store.get("opaque-token");
|
|
1897
|
-
expect(loaded).
|
|
1898
|
-
expect(JSON.parse(JSON.stringify(loaded))).
|
|
1944
|
+
expect(loaded).toStrictEqual(request);
|
|
1945
|
+
expect(JSON.parse(JSON.stringify(loaded))).toStrictEqual(request);
|
|
1899
1946
|
const listed = await store.list({ status: "pending" });
|
|
1900
1947
|
expect(listed.items.some((item) => item.token === "opaque-token")).toBe(true);
|
|
1901
1948
|
const resolved = await store.resolve("opaque-token", {
|
|
@@ -1906,7 +1953,7 @@ function testApprovalStore(store, options = {}) {
|
|
|
1906
1953
|
await expect(Promise.resolve().then(() => store.resolve("opaque-token", {
|
|
1907
1954
|
status: "rejected",
|
|
1908
1955
|
by: approver
|
|
1909
|
-
}))).rejects.toThrow(/not pending/);
|
|
1956
|
+
}))).rejects.toThrow(/not pending/u);
|
|
1910
1957
|
await store.create(sampleApproval("stale-token"));
|
|
1911
1958
|
const expired = await store.expire({ epochMilliseconds: nowMillis() + 72e5 });
|
|
1912
1959
|
expect(expired).toBeGreaterThanOrEqual(1);
|
|
@@ -1929,19 +1976,20 @@ function testApprovalStore(store, options = {}) {
|
|
|
1929
1976
|
principalId: "u_pager",
|
|
1930
1977
|
limit: 2
|
|
1931
1978
|
});
|
|
1932
|
-
expect(first.items.map((item) => item.token)).
|
|
1933
|
-
expect(
|
|
1979
|
+
expect(first.items.map((item) => item.token)).toStrictEqual(["page-a", "page-b"]);
|
|
1980
|
+
expect(first.next).toBeTypeOf("string");
|
|
1981
|
+
assert.isDefined(first.next);
|
|
1934
1982
|
const second = await store.list({
|
|
1935
1983
|
principalId: "u_pager",
|
|
1936
1984
|
limit: 2,
|
|
1937
1985
|
cursor: first.next
|
|
1938
1986
|
});
|
|
1939
|
-
expect(second.items.map((item) => item.token)).
|
|
1987
|
+
expect(second.items.map((item) => item.token)).toStrictEqual(["page-c"]);
|
|
1940
1988
|
expect(second.next).toBeUndefined();
|
|
1941
1989
|
expect((await store.list({
|
|
1942
1990
|
principalId: "u_pager",
|
|
1943
1991
|
cursor: "not a cursor"
|
|
1944
|
-
})).items).
|
|
1992
|
+
})).items).toStrictEqual([]);
|
|
1945
1993
|
});
|
|
1946
1994
|
it("refuses an approver who does not match request.approvers", async () => {
|
|
1947
1995
|
const request = {
|
|
@@ -1963,7 +2011,7 @@ function testApprovalStore(store, options = {}) {
|
|
|
1963
2011
|
await expect(Promise.resolve().then(() => store.resolve("gated-token", {
|
|
1964
2012
|
status: "approved",
|
|
1965
2013
|
by: member
|
|
1966
|
-
}))).rejects.toThrow(/eligible|not pending|not found/);
|
|
2014
|
+
}))).rejects.toThrow(/eligible|not pending|not found/u);
|
|
1967
2015
|
});
|
|
1968
2016
|
it("refuses the principal as approver unless the grant sets distinct: false", async () => {
|
|
1969
2017
|
const principal = {
|
|
@@ -2018,7 +2066,7 @@ function testApprovalStore(store, options = {}) {
|
|
|
2018
2066
|
by: approver
|
|
2019
2067
|
});
|
|
2020
2068
|
expect(first.status).toBe("pending");
|
|
2021
|
-
expect(first.approvals).
|
|
2069
|
+
expect(first.approvals).toStrictEqual([{
|
|
2022
2070
|
by: "u_9",
|
|
2023
2071
|
at: expect.any(String)
|
|
2024
2072
|
}]);
|
|
@@ -2032,7 +2080,7 @@ function testApprovalStore(store, options = {}) {
|
|
|
2032
2080
|
by: secondApprover
|
|
2033
2081
|
});
|
|
2034
2082
|
expect(second.status).toBe("approved");
|
|
2035
|
-
expect(second.approvals?.map((item) => item.by)).
|
|
2083
|
+
expect(second.approvals?.map((item) => item.by)).toStrictEqual(["u_9", "u_10"]);
|
|
2036
2084
|
expect(second.resolvedBy).toBe("u_10");
|
|
2037
2085
|
expect(await store.consume("quorum-token")).not.toBeNull();
|
|
2038
2086
|
});
|
|
@@ -2068,7 +2116,7 @@ function testApprovalStore(store, options = {}) {
|
|
|
2068
2116
|
});
|
|
2069
2117
|
expect(resolved.status).toBe("approved");
|
|
2070
2118
|
expect(resolved.vouched).toBe("app-rule");
|
|
2071
|
-
expect(resolved.approvals).
|
|
2119
|
+
expect(resolved.approvals).toStrictEqual([{
|
|
2072
2120
|
by: "u_outsider",
|
|
2073
2121
|
at: expect.any(String),
|
|
2074
2122
|
vouched: "app-rule"
|
|
@@ -2151,7 +2199,7 @@ function testApprovalStore(store, options = {}) {
|
|
|
2151
2199
|
by: secondApprover
|
|
2152
2200
|
});
|
|
2153
2201
|
expect(first.status).toBe("pending");
|
|
2154
|
-
expect(first.approvals).
|
|
2202
|
+
expect(first.approvals).toStrictEqual([{
|
|
2155
2203
|
by: "u_10",
|
|
2156
2204
|
at: expect.any(String),
|
|
2157
2205
|
stage: 0
|
|
@@ -2161,7 +2209,7 @@ function testApprovalStore(store, options = {}) {
|
|
|
2161
2209
|
by: approver
|
|
2162
2210
|
});
|
|
2163
2211
|
expect(second.status).toBe("approved");
|
|
2164
|
-
expect(second.approvals?.map((item) => item.stage)).
|
|
2212
|
+
expect(second.approvals?.map((item) => item.stage)).toStrictEqual([0, 1]);
|
|
2165
2213
|
});
|
|
2166
2214
|
it("matches a relation approver only through the verdict's relations", async () => {
|
|
2167
2215
|
const manager = {
|
|
@@ -2192,21 +2240,21 @@ function testApprovalStore(store, options = {}) {
|
|
|
2192
2240
|
}
|
|
2193
2241
|
/**
|
|
2194
2242
|
* Checks an `ApprovalPolicySource`: entries are plain JSON, belong to the
|
|
2195
|
-
* queried tenant or to none, and every entry loads against `policy` (an
|
|
2196
|
-
*
|
|
2243
|
+
* queried tenant or to none, and every entry loads against `policy` (an entry
|
|
2244
|
+
* that does not would deny every call).
|
|
2197
2245
|
*/
|
|
2198
2246
|
function testApprovalPolicySource(source, options) {
|
|
2199
2247
|
it("answers per tenant with plain entries that load against the policy", async () => {
|
|
2200
2248
|
const entries = await source.approvalPoliciesFor({ tenants: [options.tenant] });
|
|
2201
2249
|
expect(Array.isArray(entries)).toBe(true);
|
|
2202
2250
|
for (const entry of entries) {
|
|
2203
|
-
expect(JSON.parse(JSON.stringify(entry))).
|
|
2251
|
+
expect(JSON.parse(JSON.stringify(entry))).toStrictEqual(entry);
|
|
2204
2252
|
expect([void 0, options.tenant]).toContain(entry.tenant);
|
|
2205
2253
|
}
|
|
2206
2254
|
const loaded = await approvalPoliciesFor(options.policy, source, [options.tenant], []);
|
|
2207
2255
|
expect(loaded).not.toBe("failed");
|
|
2208
2256
|
const other = await source.approvalPoliciesFor({ tenants: [options.unknown ?? "__permdock_unknown__"] });
|
|
2209
|
-
expect(other.filter((entry) => entry.tenant !== void 0)).
|
|
2257
|
+
expect(other.filter((entry) => entry.tenant !== void 0)).toStrictEqual([]);
|
|
2210
2258
|
});
|
|
2211
2259
|
}
|
|
2212
2260
|
const reviewItem = {
|
|
@@ -2251,7 +2299,7 @@ function testAccessReviewStore(store) {
|
|
|
2251
2299
|
limit: 1
|
|
2252
2300
|
});
|
|
2253
2301
|
expect(page.items).toHaveLength(1);
|
|
2254
|
-
expect(page.next).
|
|
2302
|
+
expect(page.next).toStrictEqual(expect.any(String));
|
|
2255
2303
|
const kept = await store.decide("rev", reviewItem.id, {
|
|
2256
2304
|
status: "kept",
|
|
2257
2305
|
decidedBy: "u_9",
|
|
@@ -2292,7 +2340,7 @@ function testApprovalDelegationSource(source, options) {
|
|
|
2292
2340
|
expect(entries.some((entry) => entry.delegate === options.delegate && entry.delegator === options.delegator)).toBe(true);
|
|
2293
2341
|
const instant = Date.parse(at);
|
|
2294
2342
|
for (const entry of entries) {
|
|
2295
|
-
expect(JSON.parse(JSON.stringify(entry))).
|
|
2343
|
+
expect(JSON.parse(JSON.stringify(entry))).toStrictEqual(entry);
|
|
2296
2344
|
expect(entry.delegate).toBe(options.delegate);
|
|
2297
2345
|
expect(Date.parse(entry.from)).toBeLessThanOrEqual(instant);
|
|
2298
2346
|
expect(Date.parse(entry.until)).toBeGreaterThan(instant);
|
|
@@ -2301,13 +2349,13 @@ function testApprovalDelegationSource(source, options) {
|
|
|
2301
2349
|
delegate: "__permdock_unknown__",
|
|
2302
2350
|
...options.tenant === void 0 ? {} : { tenant: options.tenant },
|
|
2303
2351
|
at
|
|
2304
|
-
})).
|
|
2352
|
+
})).toStrictEqual([]);
|
|
2305
2353
|
});
|
|
2306
2354
|
}
|
|
2307
2355
|
/**
|
|
2308
|
-
* Checks an `IdempotencyStore`: a stored response comes back as written
|
|
2309
|
-
*
|
|
2310
|
-
*
|
|
2356
|
+
* Checks an `IdempotencyStore`: a stored response comes back as written until
|
|
2357
|
+
* it expires, keys never share an entry, and a later `set` replaces an earlier
|
|
2358
|
+
* one.
|
|
2311
2359
|
*/
|
|
2312
2360
|
function testIdempotencyStore(store) {
|
|
2313
2361
|
it("replays a stored response per key and keeps keys apart", async () => {
|
|
@@ -2322,14 +2370,14 @@ function testIdempotencyStore(store) {
|
|
|
2322
2370
|
const later = nowMillis() + 36e5;
|
|
2323
2371
|
expect(await store.get(key)).toBeUndefined();
|
|
2324
2372
|
await store.set(key, response, later);
|
|
2325
|
-
expect(await store.get(key)).
|
|
2373
|
+
expect(await store.get(key)).toStrictEqual(response);
|
|
2326
2374
|
expect(await store.get(other)).toBeUndefined();
|
|
2327
2375
|
const replaced = {
|
|
2328
2376
|
...response,
|
|
2329
2377
|
status: 201
|
|
2330
2378
|
};
|
|
2331
2379
|
await store.set(key, replaced, later);
|
|
2332
|
-
expect(await store.get(key)).
|
|
2380
|
+
expect(await store.get(key)).toStrictEqual(replaced);
|
|
2333
2381
|
});
|
|
2334
2382
|
}
|
|
2335
2383
|
//#endregion
|
|
@@ -2347,7 +2395,7 @@ function testLimitStore(store) {
|
|
|
2347
2395
|
expect(first.remaining).toBeGreaterThanOrEqual(0);
|
|
2348
2396
|
const peeked = store.remaining(input);
|
|
2349
2397
|
if (peeked !== void 0) {
|
|
2350
|
-
expect(
|
|
2398
|
+
expect(peeked.remaining).toBeTypeOf("number");
|
|
2351
2399
|
expect(peeked !== null && typeof peeked === "object" && "then" in peeked && typeof peeked.then === "function").toBe(false);
|
|
2352
2400
|
}
|
|
2353
2401
|
await store.consume(input);
|
|
@@ -2391,7 +2439,7 @@ function testPolicySource(source, options = {}) {
|
|
|
2391
2439
|
};
|
|
2392
2440
|
it("returns null or a v1 policy document synchronously", () => {
|
|
2393
2441
|
const document = readCurrent();
|
|
2394
|
-
if (document !== null) expect(parsePolicyDocument(document)).
|
|
2442
|
+
if (document !== null) expect(parsePolicyDocument(document)).toStrictEqual(document);
|
|
2395
2443
|
});
|
|
2396
2444
|
it("refreshes without rejecting and keeps current() synchronous", async () => {
|
|
2397
2445
|
await expect(Promise.resolve(source.refresh())).resolves.toBeUndefined();
|
|
@@ -2454,7 +2502,10 @@ const TOO_MANY = problem$1("Rate limited; retry after Retry-After seconds", {
|
|
|
2454
2502
|
schema: { type: "string" }
|
|
2455
2503
|
}
|
|
2456
2504
|
});
|
|
2457
|
-
/**
|
|
2505
|
+
/**
|
|
2506
|
+
* The RFC 6750 section 3 challenge a refused read carries, so a client tells a
|
|
2507
|
+
* dead token from a missing scope.
|
|
2508
|
+
*/
|
|
2458
2509
|
const challenge = (example) => ({ "WWW-Authenticate": {
|
|
2459
2510
|
description: `RFC 6750 Bearer challenge (\`${example}\`)`,
|
|
2460
2511
|
schema: { type: "string" }
|
|
@@ -2627,7 +2678,10 @@ const PROJECT_ID = {
|
|
|
2627
2678
|
minLength: 1
|
|
2628
2679
|
}
|
|
2629
2680
|
};
|
|
2630
|
-
/**
|
|
2681
|
+
/**
|
|
2682
|
+
* Routes of the platform issuer and its read API; they sit at the API root,
|
|
2683
|
+
* outside /v1/environments/{env}.
|
|
2684
|
+
*/
|
|
2631
2685
|
const PLATFORM_PATHS = {
|
|
2632
2686
|
"/.well-known/oauth-authorization-server": { get: {
|
|
2633
2687
|
security: [],
|
|
@@ -2828,7 +2882,10 @@ const PLATFORM_SECURITY_SCHEMES = {
|
|
|
2828
2882
|
//#endregion
|
|
2829
2883
|
//#region src/testing/cloud-api/platform-checks.ts
|
|
2830
2884
|
const ITEM = /^"[^"]+"(?:;[a-z]+=-?\d+)+$/u;
|
|
2831
|
-
/**
|
|
2885
|
+
/**
|
|
2886
|
+
* Whether a RateLimit or RateLimit-Policy field value lists items with the
|
|
2887
|
+
* parameters `keys` (draft-ietf-httpapi-ratelimit-headers-11).
|
|
2888
|
+
*/
|
|
2832
2889
|
function rateLimitField(value, keys) {
|
|
2833
2890
|
if (value === null || value.trim() === "") return false;
|
|
2834
2891
|
return value.split(",").every((raw) => {
|
|
@@ -2853,14 +2910,14 @@ function testCloudPlatform(url, environment, options, fetchFn) {
|
|
|
2853
2910
|
body: new URLSearchParams(fields).toString()
|
|
2854
2911
|
});
|
|
2855
2912
|
it("publishes RFC 8414 metadata for the platform issuer", async () => {
|
|
2856
|
-
const metadata = await discoverAuthorizationServer(url, { fetch: (input, init) => fetchFn(
|
|
2913
|
+
const metadata = await discoverAuthorizationServer(url, { fetch: (input, init) => fetchFn(input instanceof Request ? input.url : input.toString(), init) });
|
|
2857
2914
|
expect(metadata).not.toBeNull();
|
|
2858
2915
|
expect(metadata).toMatchObject({
|
|
2859
2916
|
issuer: url,
|
|
2860
2917
|
code_challenge_methods_supported: ["S256"]
|
|
2861
2918
|
});
|
|
2862
2919
|
const scopes = metadata?.["scopes_supported"];
|
|
2863
|
-
expect(scopes).
|
|
2920
|
+
expect(scopes).toStrictEqual(expect.arrayContaining(Object.keys(PLATFORM_SCOPES)));
|
|
2864
2921
|
});
|
|
2865
2922
|
it("lists and reads the project and its policy with RateLimit fields", async () => {
|
|
2866
2923
|
const list = await read("/v1/projects", options.token);
|
|
@@ -2979,7 +3036,8 @@ function testCloudPlatform(url, environment, options, fetchFn) {
|
|
|
2979
3036
|
const loopback = await register("http://127.0.0.1:33418/callback");
|
|
2980
3037
|
expect(loopback.status).toBe(201);
|
|
2981
3038
|
expect(await loopback.json()).toMatchObject({ client_id: expect.any(String) });
|
|
2982
|
-
|
|
3039
|
+
const { status } = await register("http://mcp.example.invalid/callback");
|
|
3040
|
+
expect(status).toBe(400);
|
|
2983
3041
|
});
|
|
2984
3042
|
}
|
|
2985
3043
|
//#endregion
|
|
@@ -3998,7 +4056,8 @@ const bearer = (description) => ({
|
|
|
3998
4056
|
});
|
|
3999
4057
|
/**
|
|
4000
4058
|
* `schemas/cloud-api-v1.json`: the PermDock Cloud HTTP API as an OpenAPI 3.1
|
|
4001
|
-
* document. The route tables in `adapters/cloud.mdx` and `docs:drift` keep it
|
|
4059
|
+
* document. The route tables in `adapters/cloud.mdx` and `docs:drift` keep it
|
|
4060
|
+
* whole.
|
|
4002
4061
|
*/
|
|
4003
4062
|
const CLOUD_API_DOCUMENT = freezeDeep({
|
|
4004
4063
|
openapi: "3.1.1",
|
|
@@ -4101,7 +4160,10 @@ function routesOf(paths) {
|
|
|
4101
4160
|
function pointer(parts) {
|
|
4102
4161
|
return parts.map((part) => part.replaceAll("~", "~0").replaceAll("/", "~1")).join("/");
|
|
4103
4162
|
}
|
|
4104
|
-
/**
|
|
4163
|
+
/**
|
|
4164
|
+
* A checker for responses of the PermDock Cloud API, compiled once from
|
|
4165
|
+
* `CLOUD_API_DOCUMENT`.
|
|
4166
|
+
*/
|
|
4105
4167
|
function cloudApiValidator() {
|
|
4106
4168
|
const ajv = new import__2020.Ajv2020({
|
|
4107
4169
|
strict: false,
|
|
@@ -4211,8 +4273,8 @@ async function problemCode(response) {
|
|
|
4211
4273
|
* Runs the conformance runners and the HTTP contract checks against one
|
|
4212
4274
|
* PermDock Cloud environment, and validates every response body against
|
|
4213
4275
|
* `cloud-api-v1.json`. Point it at a test environment: it creates approvals,
|
|
4214
|
-
* sends decision events and deliveries to `.invalid` addresses, and pushes
|
|
4215
|
-
*
|
|
4276
|
+
* sends decision events and deliveries to `.invalid` addresses, and pushes the
|
|
4277
|
+
* catalog the environment already holds.
|
|
4216
4278
|
*/
|
|
4217
4279
|
function testCloudApi(baseUrl, keys, options) {
|
|
4218
4280
|
const fetchFn = options.fetch ?? globalThis.fetch.bind(globalThis);
|
|
@@ -4224,16 +4286,13 @@ function testCloudApi(baseUrl, keys, options) {
|
|
|
4224
4286
|
environment
|
|
4225
4287
|
});
|
|
4226
4288
|
const check = cloudApiValidator();
|
|
4227
|
-
const problems = [];
|
|
4228
4289
|
const checks = [];
|
|
4229
4290
|
const recording = async (input, init) => {
|
|
4230
4291
|
const request = new Request(input, init);
|
|
4231
4292
|
const response = await fetchFn(request);
|
|
4232
4293
|
const { pathname } = new URL(request.url);
|
|
4233
4294
|
const path = pathname === `${METADATA}${prefix}` ? METADATA : pathname.slice(prefix.length);
|
|
4234
|
-
checks.push(check(request.method, path, response.clone())
|
|
4235
|
-
problems.push(...found);
|
|
4236
|
-
}));
|
|
4295
|
+
checks.push(check(request.method, path, response.clone()));
|
|
4237
4296
|
return response;
|
|
4238
4297
|
};
|
|
4239
4298
|
const client = cloud({
|
|
@@ -4301,17 +4360,19 @@ function testCloudApi(baseUrl, keys, options) {
|
|
|
4301
4360
|
issuer,
|
|
4302
4361
|
jwks_uri: `${issuer}/.well-known/jwks.json`
|
|
4303
4362
|
});
|
|
4304
|
-
|
|
4363
|
+
const { status } = await recording(`${url}/v1/environment`);
|
|
4364
|
+
expect(status).toBe(401);
|
|
4305
4365
|
});
|
|
4306
4366
|
it("answers 403 to a key of another kind", async () => {
|
|
4307
|
-
|
|
4367
|
+
const { status } = await call("/hosted-grants", keys.client);
|
|
4368
|
+
expect(status).toBe(403);
|
|
4308
4369
|
if (keys.export !== void 0) expect((await call("/approvals", keys.export)).status).toBe(403);
|
|
4309
4370
|
});
|
|
4310
4371
|
it("sends an ETag on the approval read and refuses an approver token with a stale one", async () => {
|
|
4311
4372
|
const token = tag();
|
|
4312
4373
|
await client.approvals.create(sample(token));
|
|
4313
4374
|
const { request, etag } = await current(token);
|
|
4314
|
-
expect(etag).
|
|
4375
|
+
expect(etag).toStrictEqual(expect.any(String));
|
|
4315
4376
|
const stale = await resolveRaw(token, {
|
|
4316
4377
|
status: "approved",
|
|
4317
4378
|
by: admin
|
|
@@ -4372,7 +4433,7 @@ function testCloudApi(baseUrl, keys, options) {
|
|
|
4372
4433
|
aud: issuer
|
|
4373
4434
|
})
|
|
4374
4435
|
});
|
|
4375
|
-
expect([typ, response.status]).
|
|
4436
|
+
expect([typ, response.status]).toStrictEqual([typ, 422]);
|
|
4376
4437
|
}
|
|
4377
4438
|
});
|
|
4378
4439
|
it("repeats a delivery for a repeated Idempotency-Key and requires one", async () => {
|
|
@@ -4400,7 +4461,8 @@ function testCloudApi(baseUrl, keys, options) {
|
|
|
4400
4461
|
expect(first.ok && second.ok).toBe(true);
|
|
4401
4462
|
const [a, b] = await Promise.all([first.json(), second.json()]);
|
|
4402
4463
|
expect(isRecord(a) && isRecord(b) && a["id"] === b["id"]).toBe(true);
|
|
4403
|
-
|
|
4464
|
+
const { status } = await send({});
|
|
4465
|
+
expect(status).toBe(400);
|
|
4404
4466
|
});
|
|
4405
4467
|
it("answers unchanged for the catalog it already holds", async (context) => {
|
|
4406
4468
|
const response = await call("/catalog", keys.client);
|
|
@@ -4437,12 +4499,12 @@ function testCloudApi(baseUrl, keys, options) {
|
|
|
4437
4499
|
const first = await get({ "permdock-subject": token });
|
|
4438
4500
|
expect(first.status).toBe(200);
|
|
4439
4501
|
expect(first.headers.get("content-type")).toMatch(/^application\/jwt/u);
|
|
4440
|
-
|
|
4502
|
+
const { status } = await get({ "permdock-subject": token });
|
|
4503
|
+
expect(status).toBe(401);
|
|
4441
4504
|
expect((await get({})).status).toBe(401);
|
|
4442
4505
|
});
|
|
4443
4506
|
it("answers every request in the shapes cloud-api-v1.json documents", async () => {
|
|
4444
|
-
await Promise.all(checks);
|
|
4445
|
-
expect(problems).toEqual([]);
|
|
4507
|
+
expect((await Promise.all(checks)).flat()).toStrictEqual([]);
|
|
4446
4508
|
});
|
|
4447
4509
|
}
|
|
4448
4510
|
//#endregion
|
|
@@ -4603,10 +4665,10 @@ const WHERE_SUBJECT = {
|
|
|
4603
4665
|
context: {}
|
|
4604
4666
|
};
|
|
4605
4667
|
/**
|
|
4606
|
-
* `matches` answers whether the compiled output selects `row`, for example
|
|
4607
|
-
*
|
|
4608
|
-
*
|
|
4609
|
-
*
|
|
4668
|
+
* `matches` answers whether the compiled output selects `row`, for example by
|
|
4669
|
+
* running it against a table holding only that row. With it, every case must
|
|
4670
|
+
* select the rows the in-memory evaluator keeps, including rows whose fields
|
|
4671
|
+
* are NULL.
|
|
4610
4672
|
*/
|
|
4611
4673
|
function testWhereCompiler(compiler, options) {
|
|
4612
4674
|
const { matches } = options;
|
|
@@ -4615,7 +4677,7 @@ function testWhereCompiler(compiler, options) {
|
|
|
4615
4677
|
const compiled = compiler(condition, options.target);
|
|
4616
4678
|
const selected = [];
|
|
4617
4679
|
for (const row of WHERE_ROWS) if (await matches(compiled, row)) selected.push(row["id"]);
|
|
4618
|
-
expect(selected).
|
|
4680
|
+
expect(selected).toStrictEqual(WHERE_ROWS.filter((row) => evaluateCondition(condition, row, WHERE_SUBJECT)).map((row) => row["id"]));
|
|
4619
4681
|
});
|
|
4620
4682
|
it("fails closed on an empty allow set", () => {
|
|
4621
4683
|
const compiled = compiler({
|
|
@@ -4683,7 +4745,7 @@ function testReplayStore(store) {
|
|
|
4683
4745
|
}
|
|
4684
4746
|
const expiresAt = nowSeconds() + 60;
|
|
4685
4747
|
const results = await Promise.all([store.claim("claim-key", expiresAt), store.claim("claim-key", expiresAt)]);
|
|
4686
|
-
expect(results.toSorted()).
|
|
4748
|
+
expect(results.toSorted()).toStrictEqual([false, true]);
|
|
4687
4749
|
expect(await store.seen("claim-key")).toBe(true);
|
|
4688
4750
|
await store.release("claim-key");
|
|
4689
4751
|
expect(await store.claim("claim-key", expiresAt)).toBe(true);
|
|
@@ -4694,18 +4756,18 @@ function testSetQueue(queue) {
|
|
|
4694
4756
|
await queue.enqueue("stream-a", "a1", "set-a1");
|
|
4695
4757
|
await queue.enqueue("stream-a", "a2", "set-a2");
|
|
4696
4758
|
await queue.enqueue("stream-b", "b1", "set-b1");
|
|
4697
|
-
expect(await queue.pending("stream-a", 10)).
|
|
4759
|
+
expect(await queue.pending("stream-a", 10)).toStrictEqual([{
|
|
4698
4760
|
jti: "a1",
|
|
4699
4761
|
set: "set-a1"
|
|
4700
4762
|
}, {
|
|
4701
4763
|
jti: "a2",
|
|
4702
4764
|
set: "set-a2"
|
|
4703
4765
|
}]);
|
|
4704
|
-
expect(await queue.pending("stream-a", 1)).
|
|
4766
|
+
expect(await queue.pending("stream-a", 1)).toStrictEqual([{
|
|
4705
4767
|
jti: "a1",
|
|
4706
4768
|
set: "set-a1"
|
|
4707
4769
|
}]);
|
|
4708
|
-
expect(await queue.pending("stream-b", 10)).
|
|
4770
|
+
expect(await queue.pending("stream-b", 10)).toStrictEqual([{
|
|
4709
4771
|
jti: "b1",
|
|
4710
4772
|
set: "set-b1"
|
|
4711
4773
|
}]);
|
|
@@ -4720,7 +4782,7 @@ function testSetQueue(queue) {
|
|
|
4720
4782
|
description: "unknown kid"
|
|
4721
4783
|
});
|
|
4722
4784
|
await queue.ack("stream-other", "c3");
|
|
4723
|
-
expect(await queue.pending("stream-c", 10)).
|
|
4785
|
+
expect(await queue.pending("stream-c", 10)).toStrictEqual([{
|
|
4724
4786
|
jti: "c3",
|
|
4725
4787
|
set: "set-c3"
|
|
4726
4788
|
}]);
|
|
@@ -4745,18 +4807,18 @@ function testSsfStreamStore(store) {
|
|
|
4745
4807
|
await store.create(owner, streamConfiguration(first));
|
|
4746
4808
|
await store.create(owner, streamConfiguration(second));
|
|
4747
4809
|
await store.create("someone-else", streamConfiguration(id("c")));
|
|
4748
|
-
expect(await store.get(first)).
|
|
4810
|
+
expect(await store.get(first)).toStrictEqual({
|
|
4749
4811
|
owner,
|
|
4750
4812
|
configuration: streamConfiguration(first)
|
|
4751
4813
|
});
|
|
4752
|
-
expect(await store.status(first)).
|
|
4814
|
+
expect(await store.status(first)).toStrictEqual({
|
|
4753
4815
|
stream_id: first,
|
|
4754
4816
|
status: "enabled"
|
|
4755
4817
|
});
|
|
4756
4818
|
const listed = await store.list(owner);
|
|
4757
|
-
expect(listed.map((stream) => stream.configuration.stream_id)).
|
|
4819
|
+
expect(listed.map((stream) => stream.configuration.stream_id)).toStrictEqual([first, second]);
|
|
4758
4820
|
const all = await store.list();
|
|
4759
|
-
expect(all.map((stream) => stream.configuration.stream_id)).
|
|
4821
|
+
expect(all.map((stream) => stream.configuration.stream_id)).toStrictEqual(expect.arrayContaining([first, second]));
|
|
4760
4822
|
});
|
|
4761
4823
|
it("answers null for a stream that does not exist", async () => {
|
|
4762
4824
|
expect(await store.get(id("missing"))).toBeNull();
|
|
@@ -4771,11 +4833,11 @@ function testSsfStreamStore(store) {
|
|
|
4771
4833
|
reason: "maintenance"
|
|
4772
4834
|
});
|
|
4773
4835
|
await store.update(streamConfiguration(stream, "renamed"));
|
|
4774
|
-
expect(await store.get(stream)).
|
|
4836
|
+
expect(await store.get(stream)).toStrictEqual({
|
|
4775
4837
|
owner: "owner-update",
|
|
4776
4838
|
configuration: streamConfiguration(stream, "renamed")
|
|
4777
4839
|
});
|
|
4778
|
-
expect(await store.status(stream)).
|
|
4840
|
+
expect(await store.status(stream)).toStrictEqual({
|
|
4779
4841
|
stream_id: stream,
|
|
4780
4842
|
status: "paused",
|
|
4781
4843
|
reason: "maintenance"
|
|
@@ -4807,8 +4869,8 @@ function testRevocationFeed(feed) {
|
|
|
4807
4869
|
};
|
|
4808
4870
|
await feed.revoke(revoked);
|
|
4809
4871
|
await vi.waitFor(() => {
|
|
4810
|
-
expect(first).
|
|
4811
|
-
expect(second).
|
|
4872
|
+
expect(first).toStrictEqual([revoked]);
|
|
4873
|
+
expect(second).toStrictEqual([revoked]);
|
|
4812
4874
|
});
|
|
4813
4875
|
stopFirst();
|
|
4814
4876
|
const changed = {
|
|
@@ -4818,9 +4880,9 @@ function testRevocationFeed(feed) {
|
|
|
4818
4880
|
};
|
|
4819
4881
|
await feed.revoke(changed);
|
|
4820
4882
|
await vi.waitFor(() => {
|
|
4821
|
-
expect(second).
|
|
4883
|
+
expect(second).toStrictEqual([revoked, changed]);
|
|
4822
4884
|
});
|
|
4823
|
-
expect(first).
|
|
4885
|
+
expect(first).toStrictEqual([revoked]);
|
|
4824
4886
|
stopSecond();
|
|
4825
4887
|
});
|
|
4826
4888
|
it("keeps delivering when a listener throws", async () => {
|
|
@@ -4837,7 +4899,7 @@ function testRevocationFeed(feed) {
|
|
|
4837
4899
|
};
|
|
4838
4900
|
await feed.revoke(event);
|
|
4839
4901
|
await vi.waitFor(() => {
|
|
4840
|
-
expect(seen).
|
|
4902
|
+
expect(seen).toStrictEqual([event]);
|
|
4841
4903
|
});
|
|
4842
4904
|
stopThrowing();
|
|
4843
4905
|
stop();
|
|
@@ -4856,7 +4918,7 @@ function testRevocationFeed(feed) {
|
|
|
4856
4918
|
}]) await expect((async () => {
|
|
4857
4919
|
await feed.revoke(event);
|
|
4858
4920
|
})()).rejects.toThrow(TypeError);
|
|
4859
|
-
expect(seen).
|
|
4921
|
+
expect(seen).toStrictEqual([]);
|
|
4860
4922
|
stop();
|
|
4861
4923
|
});
|
|
4862
4924
|
}
|
|
@@ -4931,8 +4993,8 @@ function flipLast(value) {
|
|
|
4931
4993
|
}
|
|
4932
4994
|
/**
|
|
4933
4995
|
* `key` is a live key the verifier knows, or a function issuing one (called
|
|
4934
|
-
* once). `revoke`, when given, revokes it; the runner then expects the key
|
|
4935
|
-
*
|
|
4996
|
+
* once). `revoke`, when given, revokes it; the runner then expects the key to
|
|
4997
|
+
* stop verifying.
|
|
4936
4998
|
*/
|
|
4937
4999
|
function testCredentialVerifier(verifier, options) {
|
|
4938
5000
|
let issued;
|
|
@@ -4948,7 +5010,7 @@ function testCredentialVerifier(verifier, options) {
|
|
|
4948
5010
|
const credential = parseCredential(await verifier.verify(key));
|
|
4949
5011
|
expect(credential).toBeDefined();
|
|
4950
5012
|
expect(credential?.id).toBe(parts?.id);
|
|
4951
|
-
expect(parseCredential(await verifier.verify(key))).
|
|
5013
|
+
expect(parseCredential(await verifier.verify(key))).toStrictEqual(credential);
|
|
4952
5014
|
});
|
|
4953
5015
|
it("never throws and answers null for every other key", async () => {
|
|
4954
5016
|
const key = await live();
|
|
@@ -4978,7 +5040,7 @@ function testCredentialVerifier(verifier, options) {
|
|
|
4978
5040
|
const id = parseApiKey(key)?.id ?? "";
|
|
4979
5041
|
await verifier.touch?.(id, { epochMilliseconds: nowMillis() });
|
|
4980
5042
|
await verifier.touch?.("unknown-id", { epochMilliseconds: nowMillis() });
|
|
4981
|
-
expect(parseCredential(await verifier.verify(key))).
|
|
5043
|
+
expect(parseCredential(await verifier.verify(key))).toStrictEqual(before);
|
|
4982
5044
|
});
|
|
4983
5045
|
if (options.revoke !== void 0) {
|
|
4984
5046
|
const revoke = options.revoke;
|
|
@@ -4999,7 +5061,7 @@ function testTokenSigner(signer, options) {
|
|
|
4999
5061
|
audience: "https://app.example.com"
|
|
5000
5062
|
});
|
|
5001
5063
|
const header = decodeHeader(token);
|
|
5002
|
-
expect(Object.keys(header).toSorted()).
|
|
5064
|
+
expect(Object.keys(header).toSorted()).toStrictEqual([
|
|
5003
5065
|
"alg",
|
|
5004
5066
|
"kid",
|
|
5005
5067
|
"typ"
|
|
@@ -5045,7 +5107,7 @@ function testDecisionProvider(provider, options) {
|
|
|
5045
5107
|
local: LOCAL
|
|
5046
5108
|
});
|
|
5047
5109
|
it("names itself and claims the permission synchronously", () => {
|
|
5048
|
-
expect(
|
|
5110
|
+
expect(provider.name).toBeTypeOf("string");
|
|
5049
5111
|
expect(provider.name.length).toBeGreaterThan(0);
|
|
5050
5112
|
expect(provider.handles(options.permission)).toBe(true);
|
|
5051
5113
|
});
|
|
@@ -5121,12 +5183,12 @@ function testRelationSource(source, options) {
|
|
|
5121
5183
|
});
|
|
5122
5184
|
expect(Array.isArray(full.ancestors)).toBe(true);
|
|
5123
5185
|
for (const ancestor of full.ancestors) {
|
|
5124
|
-
expect(
|
|
5186
|
+
expect(ancestor.id).toBeTypeOf("string");
|
|
5125
5187
|
expect(ancestor.id).not.toBe("");
|
|
5126
5188
|
}
|
|
5127
5189
|
expect(full.ancestors.length).toBeLessThanOrEqual(32);
|
|
5128
5190
|
const expected = options.expect?.ancestors?.[`${object.resource}:${object.id}`];
|
|
5129
|
-
if (expected !== void 0) expect(full.ancestors.map((ancestor) => ancestor.id)).
|
|
5191
|
+
if (expected !== void 0) expect(full.ancestors.map((ancestor) => ancestor.id)).toStrictEqual(expected);
|
|
5130
5192
|
if (full.ancestors.length > 1) {
|
|
5131
5193
|
const short = await source.ancestors({
|
|
5132
5194
|
resource: object.resource,
|
|
@@ -5134,7 +5196,7 @@ function testRelationSource(source, options) {
|
|
|
5134
5196
|
through: "parent",
|
|
5135
5197
|
depth: 1
|
|
5136
5198
|
});
|
|
5137
|
-
expect(short.ancestors.map((ancestor) => ancestor.id)).
|
|
5199
|
+
expect(short.ancestors.map((ancestor) => ancestor.id)).toStrictEqual([full.ancestors[0]?.id]);
|
|
5138
5200
|
expect(short.truncated).toBe(true);
|
|
5139
5201
|
}
|
|
5140
5202
|
const none = await source.ancestors({
|
|
@@ -5143,7 +5205,7 @@ function testRelationSource(source, options) {
|
|
|
5143
5205
|
through: "parent",
|
|
5144
5206
|
depth: 0
|
|
5145
5207
|
});
|
|
5146
|
-
expect(none.ancestors).
|
|
5208
|
+
expect(none.ancestors).toStrictEqual([]);
|
|
5147
5209
|
}
|
|
5148
5210
|
});
|
|
5149
5211
|
it("returns well-formed holders", async () => {
|
|
@@ -5161,14 +5223,14 @@ function testRelationSource(source, options) {
|
|
|
5161
5223
|
holder.group.id,
|
|
5162
5224
|
holder.group.relation
|
|
5163
5225
|
]) {
|
|
5164
|
-
expect(
|
|
5226
|
+
expect(part).toBeTypeOf("string");
|
|
5165
5227
|
expect(part).not.toBe("");
|
|
5166
5228
|
}
|
|
5167
|
-
else expect(
|
|
5229
|
+
else expect(holder.principal.id).toBeTypeOf("string");
|
|
5168
5230
|
for (const instant of [holder.startsAt, holder.expiresAt]) expect(instant === void 0 || typeof instant === "number" && Number.isFinite(instant)).toBe(true);
|
|
5169
5231
|
}
|
|
5170
5232
|
const expected = options.expect?.holders?.[`${object.resource}:${object.id}#${object.relation}`];
|
|
5171
|
-
if (expected !== void 0) expect(holders.map(holderKey).toSorted()).
|
|
5233
|
+
if (expected !== void 0) expect(holders.map(holderKey).toSorted()).toStrictEqual([...expected].toSorted());
|
|
5172
5234
|
}
|
|
5173
5235
|
});
|
|
5174
5236
|
it("follows each link to exactly one instance", async () => {
|
|
@@ -5182,7 +5244,7 @@ function testRelationSource(source, options) {
|
|
|
5182
5244
|
through,
|
|
5183
5245
|
depth: 1
|
|
5184
5246
|
});
|
|
5185
|
-
expect(linked.ancestors.map((ancestor) => ancestor.id)).
|
|
5247
|
+
expect(linked.ancestors.map((ancestor) => ancestor.id)).toStrictEqual([id]);
|
|
5186
5248
|
}
|
|
5187
5249
|
});
|
|
5188
5250
|
it("reads each object's row as an object, or null when there is none", async () => {
|
|
@@ -5212,11 +5274,11 @@ function testRelationSource(source, options) {
|
|
|
5212
5274
|
...missing,
|
|
5213
5275
|
through: "parent",
|
|
5214
5276
|
depth: 16
|
|
5215
|
-
})).ancestors).
|
|
5277
|
+
})).ancestors).toStrictEqual([]);
|
|
5216
5278
|
if (first.relation !== void 0) expect(await source.related({
|
|
5217
5279
|
...missing,
|
|
5218
5280
|
relation: first.relation
|
|
5219
|
-
})).
|
|
5281
|
+
})).toStrictEqual([]);
|
|
5220
5282
|
});
|
|
5221
5283
|
}
|
|
5222
5284
|
//#endregion
|
|
@@ -5228,8 +5290,8 @@ function isFrozenDeep(value) {
|
|
|
5228
5290
|
/**
|
|
5229
5291
|
* Checks a `PrincipalSource`: `principal` answers each listed id with the
|
|
5230
5292
|
* expected principal or `null`, never another id; `principalByIdentity`
|
|
5231
|
-
* resolves each listed identity to the expected principal id or `null`, and
|
|
5232
|
-
*
|
|
5293
|
+
* resolves each listed identity to the expected principal id or `null`, and an
|
|
5294
|
+
* email matches trimmed and in any case.
|
|
5233
5295
|
*/
|
|
5234
5296
|
function testPrincipalSource(source, options) {
|
|
5235
5297
|
it("returns the principal with the asked id, or null", async () => {
|
|
@@ -5241,8 +5303,8 @@ function testPrincipalSource(source, options) {
|
|
|
5241
5303
|
found = null;
|
|
5242
5304
|
}
|
|
5243
5305
|
expect(found === null || typeof found === "object").toBe(true);
|
|
5244
|
-
if (found !== null) expect(await loadPrincipal(source, entry.id, entry.tenant)).
|
|
5245
|
-
if (entry.expect !== void 0) expect(found).
|
|
5306
|
+
if (found !== null) expect(await loadPrincipal(source, entry.id, entry.tenant)).toStrictEqual(found);
|
|
5307
|
+
if (entry.expect !== void 0) expect(found).toStrictEqual(entry.expect);
|
|
5246
5308
|
}
|
|
5247
5309
|
});
|
|
5248
5310
|
it("returns principals a caller cannot change", async () => {
|
|
@@ -5285,10 +5347,10 @@ async function automationOf(source, id, tenant) {
|
|
|
5285
5347
|
}
|
|
5286
5348
|
}
|
|
5287
5349
|
/**
|
|
5288
|
-
* Checks an `AutomationSource`: `automation(id)` answers each listed id
|
|
5289
|
-
*
|
|
5290
|
-
*
|
|
5291
|
-
*
|
|
5350
|
+
* Checks an `AutomationSource`: `automation(id)` answers each listed id with a
|
|
5351
|
+
* valid Automation v1 record of that id or `null`, never another id; a record
|
|
5352
|
+
* held to one tenant is `null` when asked for another; an empty or unknown id
|
|
5353
|
+
* is `null`.
|
|
5292
5354
|
*/
|
|
5293
5355
|
function testAutomationSource(source, options) {
|
|
5294
5356
|
it("returns a valid v1 record with the asked id, or null", async () => {
|
|
@@ -5296,10 +5358,10 @@ function testAutomationSource(source, options) {
|
|
|
5296
5358
|
const found = await automationOf(source, entry.id, entry.tenant);
|
|
5297
5359
|
if (found !== null) {
|
|
5298
5360
|
const parsed = parseAutomation(found);
|
|
5299
|
-
expect(parsed).
|
|
5361
|
+
expect(parsed).toStrictEqual(found);
|
|
5300
5362
|
expect(parsed?.id).toBe(entry.id);
|
|
5301
5363
|
}
|
|
5302
|
-
if (entry.expect !== void 0) expect(found).
|
|
5364
|
+
if (entry.expect !== void 0) expect(found).toStrictEqual(entry.expect);
|
|
5303
5365
|
}
|
|
5304
5366
|
});
|
|
5305
5367
|
it("refuses a record held to another tenant", async () => {
|
|
@@ -5322,9 +5384,9 @@ async function sessionOf(source, id) {
|
|
|
5322
5384
|
}
|
|
5323
5385
|
}
|
|
5324
5386
|
/**
|
|
5325
|
-
* Checks a `SupportSessionSource`: `session(id)` answers each listed id
|
|
5326
|
-
*
|
|
5327
|
-
*
|
|
5387
|
+
* Checks a `SupportSessionSource`: `session(id)` answers each listed id with a
|
|
5388
|
+
* record of that id or `null`, never another id; `ended` is a boolean when
|
|
5389
|
+
* present; an empty or unknown id is `null`.
|
|
5328
5390
|
*/
|
|
5329
5391
|
function testSupportSessionSource(source, options) {
|
|
5330
5392
|
it("returns the session with the asked id, or null", async () => {
|
|
@@ -5349,7 +5411,10 @@ function testSupportSessionSource(source, options) {
|
|
|
5349
5411
|
});
|
|
5350
5412
|
}
|
|
5351
5413
|
const SETTLED = Symbol("settled");
|
|
5352
|
-
/**
|
|
5414
|
+
/**
|
|
5415
|
+
* Whether `promise` settles within `ms` milliseconds; a rejection counts as
|
|
5416
|
+
* settled.
|
|
5417
|
+
*/
|
|
5353
5418
|
async function settlesWithin(promise, ms) {
|
|
5354
5419
|
let timer;
|
|
5355
5420
|
const timeout = new Promise((resolve) => {
|
|
@@ -5364,12 +5429,12 @@ async function settlesWithin(promise, ms) {
|
|
|
5364
5429
|
}
|
|
5365
5430
|
}
|
|
5366
5431
|
/**
|
|
5367
|
-
* Checks an `ApprovalWaiter`: a wait ends when its token is woken and not
|
|
5368
|
-
*
|
|
5369
|
-
*
|
|
5370
|
-
* Pass `expiry: false` or `abort: false` for a waiter whose runner
|
|
5371
|
-
* timer or no cancellation, `pending` when the waiter reads a store,
|
|
5372
|
-
* `parked` when a wait parks asynchronously.
|
|
5432
|
+
* Checks an `ApprovalWaiter`: a wait ends when its token is woken and not when
|
|
5433
|
+
* another token is; waking a token nobody waits on resolves; a wait whose
|
|
5434
|
+
* `expiresAt` has passed ends; an aborted `signal` rejects the wait with its
|
|
5435
|
+
* reason. Pass `expiry: false` or `abort: false` for a waiter whose runner
|
|
5436
|
+
* offers no timer or no cancellation, `pending` when the waiter reads a store,
|
|
5437
|
+
* and `parked` when a wait parks asynchronously.
|
|
5373
5438
|
*/
|
|
5374
5439
|
function testApprovalWaiter(waiter, options = {}) {
|
|
5375
5440
|
const timeout = options.timeout ?? 1e3;
|
|
@@ -5431,9 +5496,9 @@ function key(suffix) {
|
|
|
5431
5496
|
}
|
|
5432
5497
|
/**
|
|
5433
5498
|
* Conformance for a `DeliveryTransport`: it sends a checked target with its
|
|
5434
|
-
* idempotency key, keeps the key on a retry, refuses a target without a
|
|
5435
|
-
*
|
|
5436
|
-
*
|
|
5499
|
+
* idempotency key, keeps the key on a retry, refuses a target without a granted
|
|
5500
|
+
* decision before calling the provider, and maps provider errors to retryable
|
|
5501
|
+
* or final failures without rejecting.
|
|
5437
5502
|
*/
|
|
5438
5503
|
function testDeliveryTransport(name, options) {
|
|
5439
5504
|
const targetWith = async (decision) => ({
|
|
@@ -5447,14 +5512,14 @@ function testDeliveryTransport(name, options) {
|
|
|
5447
5512
|
it("names its channel and provider", () => {
|
|
5448
5513
|
const { transport } = options.create();
|
|
5449
5514
|
expect(transport.channel).toBe(options.channel);
|
|
5450
|
-
expect(
|
|
5515
|
+
expect(transport.name).toBeTypeOf("string");
|
|
5451
5516
|
expect(transport.name).not.toBe("");
|
|
5452
5517
|
});
|
|
5453
5518
|
it("sends a checked target with its idempotency key", async () => {
|
|
5454
5519
|
const harness = options.create();
|
|
5455
5520
|
const result = await send(harness, await targetWith(await grantedDecision()), "a");
|
|
5456
5521
|
expect(result.status).toBe("sent");
|
|
5457
|
-
expect(await harness.received()).
|
|
5522
|
+
expect(await harness.received()).toStrictEqual([{ idempotencyKey: "pdd_runner_a" }]);
|
|
5458
5523
|
});
|
|
5459
5524
|
it("passes the same key on a retry of the same delivery", async () => {
|
|
5460
5525
|
const harness = options.create();
|
|
@@ -5480,7 +5545,7 @@ function testDeliveryTransport(name, options) {
|
|
|
5480
5545
|
cause: "invalid-recipient",
|
|
5481
5546
|
retryable: false
|
|
5482
5547
|
});
|
|
5483
|
-
expect(await harness.received()).
|
|
5548
|
+
expect(await harness.received()).toStrictEqual([]);
|
|
5484
5549
|
});
|
|
5485
5550
|
it("maps a retryable provider error to a retryable failure", async () => {
|
|
5486
5551
|
const harness = options.create();
|
|
@@ -5511,7 +5576,7 @@ function testDeliveryTransport(name, options) {
|
|
|
5511
5576
|
status: "failed",
|
|
5512
5577
|
retryable: true
|
|
5513
5578
|
});
|
|
5514
|
-
expect(await harness.received()).
|
|
5579
|
+
expect(await harness.received()).toStrictEqual([]);
|
|
5515
5580
|
});
|
|
5516
5581
|
});
|
|
5517
5582
|
}
|
|
@@ -5549,7 +5614,7 @@ function testMembershipSource(source, options) {
|
|
|
5549
5614
|
if (options.policy !== void 0) expect(normalizeMemberships([membership], scopeList(options.policy.scopes))).toHaveLength(1);
|
|
5550
5615
|
}
|
|
5551
5616
|
const expected = options.expect?.[principal.id];
|
|
5552
|
-
if (expected !== void 0) expect(memberships).
|
|
5617
|
+
if (expected !== void 0) expect(memberships).toStrictEqual(expected);
|
|
5553
5618
|
}
|
|
5554
5619
|
});
|
|
5555
5620
|
if (source.list !== void 0) it("lists every member of an instance it returns a membership for", async () => {
|
|
@@ -5581,9 +5646,9 @@ function testEntitlementSource(source, options) {
|
|
|
5581
5646
|
it("returns plan names for a tenant and none without one", async () => {
|
|
5582
5647
|
const found = await source.entitlementsFor(options.principal, { tenant: options.tenant });
|
|
5583
5648
|
expect(Array.isArray(found)).toBe(true);
|
|
5584
|
-
for (const name of found) expect(
|
|
5585
|
-
if (options.expect !== void 0) expect([...found].toSorted()).
|
|
5586
|
-
expect(await source.entitlementsFor(options.principal, {})).
|
|
5649
|
+
for (const name of found) expect(name).toBeTypeOf("string");
|
|
5650
|
+
if (options.expect !== void 0) expect([...found].toSorted()).toStrictEqual([...options.expect].toSorted());
|
|
5651
|
+
expect(await source.entitlementsFor(options.principal, {})).toStrictEqual([]);
|
|
5587
5652
|
});
|
|
5588
5653
|
}
|
|
5589
5654
|
function testCustomRoleSource(source, options) {
|
|
@@ -5593,7 +5658,7 @@ function testCustomRoleSource(source, options) {
|
|
|
5593
5658
|
for (const role of roles) {
|
|
5594
5659
|
expect(role.tenant).toBe(options.tenant);
|
|
5595
5660
|
for (const included of role.includes ?? []) expect(declared).toContain(included);
|
|
5596
|
-
if (options.policy !== void 0) expect(validateCustomRole(options.policy, role).dropped).
|
|
5661
|
+
if (options.policy !== void 0) expect(validateCustomRole(options.policy, role).dropped).toStrictEqual([]);
|
|
5597
5662
|
}
|
|
5598
5663
|
if (source.assignable !== void 0) {
|
|
5599
5664
|
const assignable = await source.assignable(options.tenant);
|
|
@@ -5603,7 +5668,7 @@ function testCustomRoleSource(source, options) {
|
|
|
5603
5668
|
const every = options.every;
|
|
5604
5669
|
if (every !== void 0) it("returns every custom role of the tenant, held or not", async () => {
|
|
5605
5670
|
const roles = await source.rolesFor(options.tenant, { held: [] });
|
|
5606
|
-
expect([...new Set(roles.map((role) => role.name))].toSorted()).
|
|
5671
|
+
expect([...new Set(roles.map((role) => role.name))].toSorted()).toStrictEqual([...new Set(every)].toSorted());
|
|
5607
5672
|
});
|
|
5608
5673
|
}
|
|
5609
5674
|
function testDirectoryStore(store, options) {
|
|
@@ -5633,7 +5698,7 @@ function testDirectoryStore(store, options) {
|
|
|
5633
5698
|
created: "",
|
|
5634
5699
|
lastModified: ""
|
|
5635
5700
|
}
|
|
5636
|
-
})).rejects.toThrow(/userName/);
|
|
5701
|
+
})).rejects.toThrow(/userName/u);
|
|
5637
5702
|
const group = await store.putGroup(home, {
|
|
5638
5703
|
id: "g_editors",
|
|
5639
5704
|
displayName: "Editors",
|
|
@@ -5644,11 +5709,11 @@ function testDirectoryStore(store, options) {
|
|
|
5644
5709
|
lastModified: ""
|
|
5645
5710
|
}
|
|
5646
5711
|
});
|
|
5647
|
-
expect(await store.groupsFor(home, created.id)).
|
|
5712
|
+
expect(await store.groupsFor(home, created.id)).toStrictEqual([expect.objectContaining({
|
|
5648
5713
|
id: group.id,
|
|
5649
5714
|
displayName: "Editors"
|
|
5650
5715
|
})]);
|
|
5651
|
-
expect(await store.groupsFor(other, created.id)).
|
|
5716
|
+
expect(await store.groupsFor(other, created.id)).toStrictEqual([]);
|
|
5652
5717
|
const patched = await store.patchUser(home, created.id, [{
|
|
5653
5718
|
op: "replace",
|
|
5654
5719
|
path: "active",
|
|
@@ -5656,13 +5721,13 @@ function testDirectoryStore(store, options) {
|
|
|
5656
5721
|
}]);
|
|
5657
5722
|
expect(patched.active).toBe(false);
|
|
5658
5723
|
const source = directoryMembershipSource(store, { assignable: ["editor"] });
|
|
5659
|
-
expect(await source.membershipsFor({ id: "00u1" }, { tenant: home })).
|
|
5724
|
+
expect(await source.membershipsFor({ id: "00u1" }, { tenant: home })).toStrictEqual([]);
|
|
5660
5725
|
await store.patchUser(home, created.id, [{
|
|
5661
5726
|
op: "replace",
|
|
5662
5727
|
path: "active",
|
|
5663
5728
|
value: true
|
|
5664
5729
|
}]);
|
|
5665
|
-
expect(await source.membershipsFor({ id: "00u1" }, { tenant: home })).
|
|
5730
|
+
expect(await source.membershipsFor({ id: "00u1" }, { tenant: home })).toStrictEqual([{
|
|
5666
5731
|
tenant: home,
|
|
5667
5732
|
roles: ["editor"],
|
|
5668
5733
|
via: `group:${group.id}`,
|
|
@@ -5673,7 +5738,7 @@ function testDirectoryStore(store, options) {
|
|
|
5673
5738
|
path: "members",
|
|
5674
5739
|
value: [{ value: created.id }]
|
|
5675
5740
|
}]);
|
|
5676
|
-
expect(await store.groupsFor(home, created.id)).
|
|
5741
|
+
expect(await store.groupsFor(home, created.id)).toStrictEqual([]);
|
|
5677
5742
|
});
|
|
5678
5743
|
it("ends sessions when scimHandler deactivates a user in this store", async () => {
|
|
5679
5744
|
const feed = memoryRevocationFeed();
|
|
@@ -5726,7 +5791,7 @@ function testDirectoryStore(store, options) {
|
|
|
5726
5791
|
kind: "session-revoked"
|
|
5727
5792
|
});
|
|
5728
5793
|
expect(seen.every((event) => event.kind === "session-revoked")).toBe(true);
|
|
5729
|
-
expect(kinds).
|
|
5794
|
+
expect(kinds).toStrictEqual(["changed", "session-revoked"]);
|
|
5730
5795
|
});
|
|
5731
5796
|
}
|
|
5732
5797
|
/** Settings for `tenant`, or `undefined`; a thrown source fails the runner. */
|
|
@@ -5734,13 +5799,13 @@ function testSettingsSource(source, options) {
|
|
|
5734
5799
|
it("answers per tenant with plain settings and nothing for an unknown tenant", async () => {
|
|
5735
5800
|
const settings = await source.settingsFor(options.tenant);
|
|
5736
5801
|
if (settings !== void 0) {
|
|
5737
|
-
expect(
|
|
5802
|
+
expect(settings).toBeTypeOf("object");
|
|
5738
5803
|
const credentials = settings.credentials;
|
|
5739
5804
|
if (credentials !== void 0) {
|
|
5740
5805
|
expect(credentials.maxTtl === void 0 || Number.isFinite(credentials.maxTtl) && credentials.maxTtl >= 0).toBe(true);
|
|
5741
5806
|
expect(credentials.kinds === void 0 || credentials.kinds.every((kind) => kind === "user" || kind === "service" || kind === "feed")).toBe(true);
|
|
5742
5807
|
}
|
|
5743
|
-
expect(JSON.parse(JSON.stringify(settings))).
|
|
5808
|
+
expect(JSON.parse(JSON.stringify(settings))).toStrictEqual(settings);
|
|
5744
5809
|
}
|
|
5745
5810
|
expect(await source.settingsFor(options.unknown ?? "__permdock_unknown__")).toBeUndefined();
|
|
5746
5811
|
});
|
|
@@ -6175,8 +6240,8 @@ const supabaseClaimVectors = {
|
|
|
6175
6240
|
/**
|
|
6176
6241
|
* The `permdock supabase inspect --json` manifest for a policy with one
|
|
6177
6242
|
* `tenant` scope, the default `supabase.hook` and a `features` claim from
|
|
6178
|
-
* `public.feature_claims`. A package that reads the manifest tests its
|
|
6179
|
-
*
|
|
6243
|
+
* `public.feature_claims`. A package that reads the manifest tests its parser
|
|
6244
|
+
* against this value.
|
|
6180
6245
|
*/
|
|
6181
6246
|
const supabaseHookManifestFixture = {
|
|
6182
6247
|
$schema: "https://permdock.com/schemas/supabase-manifest-v1.json",
|
|
@@ -6467,4 +6532,4 @@ const PROBLEM_EXAMPLES = Object.freeze({
|
|
|
6467
6532
|
}
|
|
6468
6533
|
});
|
|
6469
6534
|
//#endregion
|
|
6470
|
-
export { AGENT_THROWING_ROW, AGENT_THROWING_USER, PROBLEM_EXAMPLES, authzenTodoData, authzenTodoPermissions, authzenTodoPolicy, authzenTodoUsers, authzenTodoVectors, describePolicy, expectTypeOf, jwtFixtureAudience, jwtFixtureIssuer, jwtFixtureJwks, jwtFixtureTokens, policyMatrix, snapshotFixture, supabaseClaimFixtures, supabaseClaimVectors, supabaseHookManifestFixture, supabaseMembershipsBudget, testAccessReviewStore, testAgentAdapter, testAgentRuntime, testApprovalDelegationSource, testApprovalPolicySource, testApprovalStore, testApprovalWaiter, testAuthzen, testAutomationSource, testClientParity, testClientStore, testCloudApi, testCredentialVerifier, testCustomRoleSource, testDecisionProvider, testDecisionSink, testDeliveryTransport, testDirectoryStore, testEntitlementSource, testHttpAdapter, testIdempotencyStore, testLegacyParity, testLimitStore, testMembershipSource, testOrmParity, testPolicySource, testPrincipalSource, testRelationSource, testReplayStore, testRevocationFeed, testRlsParity, testSetQueue, testSettingsSource, testSnapshotSource, testSnapshotStorage, testSsfStreamStore, testSubjectResolver, testSupportSessionSource, testTokenSigner, testTokenVerifier, testWhereCompiler };
|
|
6535
|
+
export { AGENT_THROWING_ROW, AGENT_THROWING_USER, PROBLEM_EXAMPLES, authzenTodoData, authzenTodoPermissions, authzenTodoPolicy, authzenTodoUsers, authzenTodoVectors, describePolicy, expectTypeOf, jwtFixtureAudience, jwtFixtureIssuer, jwtFixtureJwks, jwtFixtureTokens, policyMatrix, seedRows, snapshotFixture, supabaseClaimFixtures, supabaseClaimVectors, supabaseHookManifestFixture, supabaseMembershipsBudget, testAccessReviewStore, testAgentAdapter, testAgentRuntime, testApprovalDelegationSource, testApprovalPolicySource, testApprovalStore, testApprovalWaiter, testAuthzen, testAutomationSource, testClientParity, testClientStore, testCloudApi, testCredentialVerifier, testCustomRoleSource, testDecisionProvider, testDecisionSink, testDeliveryTransport, testDirectoryStore, testEntitlementSource, testHttpAdapter, testIdempotencyStore, testLegacyParity, testLimitStore, testMembershipSource, testOrmParity, testPolicySource, testPrincipalSource, testRelationSource, testReplayStore, testRevocationFeed, testRlsParity, testSetQueue, testSettingsSource, testSnapshotSource, testSnapshotStorage, testSsfStreamStore, testSubjectResolver, testSupportSessionSource, testTokenSigner, testTokenVerifier, testWhereCompiler };
|