diff --git a/package-lock.json b/package-lock.json index 660a848..b0a9b52 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@predicatesystems/authority", - "version": "0.1.0", + "version": "0.4.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@predicatesystems/authority", - "version": "0.1.0", + "version": "0.4.1", "license": "(MIT OR Apache-2.0)", "devDependencies": { "@biomejs/biome": "^1.9.4", @@ -633,9 +633,9 @@ "license": "MIT" }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz", - "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", + "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", "cpu": [ "arm" ], @@ -647,9 +647,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz", - "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", + "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", "cpu": [ "arm64" ], @@ -661,9 +661,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz", - "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", + "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", "cpu": [ "arm64" ], @@ -675,9 +675,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz", - "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", + "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", "cpu": [ "x64" ], @@ -689,9 +689,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz", - "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", + "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", "cpu": [ "arm64" ], @@ -703,9 +703,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz", - "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", + "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", "cpu": [ "x64" ], @@ -717,9 +717,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz", - "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", + "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", "cpu": [ "arm" ], @@ -731,9 +731,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz", - "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", + "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", "cpu": [ "arm" ], @@ -745,9 +745,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz", - "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", + "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", "cpu": [ "arm64" ], @@ -759,9 +759,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz", - "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", + "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", "cpu": [ "arm64" ], @@ -773,9 +773,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz", - "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", + "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", "cpu": [ "loong64" ], @@ -787,9 +787,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz", - "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", + "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", "cpu": [ "loong64" ], @@ -801,9 +801,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz", - "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", + "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", "cpu": [ "ppc64" ], @@ -815,9 +815,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz", - "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", + "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", "cpu": [ "ppc64" ], @@ -829,9 +829,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz", - "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", + "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", "cpu": [ "riscv64" ], @@ -843,9 +843,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz", - "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", + "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", "cpu": [ "riscv64" ], @@ -857,9 +857,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz", - "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", + "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", "cpu": [ "s390x" ], @@ -871,9 +871,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz", - "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", + "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", "cpu": [ "x64" ], @@ -885,9 +885,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz", - "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", + "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", "cpu": [ "x64" ], @@ -899,9 +899,9 @@ ] }, "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz", - "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", + "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", "cpu": [ "x64" ], @@ -913,9 +913,9 @@ ] }, "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz", - "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", + "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", "cpu": [ "arm64" ], @@ -927,9 +927,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz", - "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", + "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", "cpu": [ "arm64" ], @@ -941,9 +941,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz", - "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", + "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", "cpu": [ "ia32" ], @@ -955,9 +955,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz", - "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", + "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", "cpu": [ "x64" ], @@ -969,9 +969,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz", - "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", + "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", "cpu": [ "x64" ], @@ -1442,9 +1442,9 @@ } }, "node_modules/rollup": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz", - "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", + "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", "dev": true, "license": "MIT", "dependencies": { @@ -1458,31 +1458,31 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.57.1", - "@rollup/rollup-android-arm64": "4.57.1", - "@rollup/rollup-darwin-arm64": "4.57.1", - "@rollup/rollup-darwin-x64": "4.57.1", - "@rollup/rollup-freebsd-arm64": "4.57.1", - "@rollup/rollup-freebsd-x64": "4.57.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", - "@rollup/rollup-linux-arm-musleabihf": "4.57.1", - "@rollup/rollup-linux-arm64-gnu": "4.57.1", - "@rollup/rollup-linux-arm64-musl": "4.57.1", - "@rollup/rollup-linux-loong64-gnu": "4.57.1", - "@rollup/rollup-linux-loong64-musl": "4.57.1", - "@rollup/rollup-linux-ppc64-gnu": "4.57.1", - "@rollup/rollup-linux-ppc64-musl": "4.57.1", - "@rollup/rollup-linux-riscv64-gnu": "4.57.1", - "@rollup/rollup-linux-riscv64-musl": "4.57.1", - "@rollup/rollup-linux-s390x-gnu": "4.57.1", - "@rollup/rollup-linux-x64-gnu": "4.57.1", - "@rollup/rollup-linux-x64-musl": "4.57.1", - "@rollup/rollup-openbsd-x64": "4.57.1", - "@rollup/rollup-openharmony-arm64": "4.57.1", - "@rollup/rollup-win32-arm64-msvc": "4.57.1", - "@rollup/rollup-win32-ia32-msvc": "4.57.1", - "@rollup/rollup-win32-x64-gnu": "4.57.1", - "@rollup/rollup-win32-x64-msvc": "4.57.1", + "@rollup/rollup-android-arm-eabi": "4.59.0", + "@rollup/rollup-android-arm64": "4.59.0", + "@rollup/rollup-darwin-arm64": "4.59.0", + "@rollup/rollup-darwin-x64": "4.59.0", + "@rollup/rollup-freebsd-arm64": "4.59.0", + "@rollup/rollup-freebsd-x64": "4.59.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", + "@rollup/rollup-linux-arm-musleabihf": "4.59.0", + "@rollup/rollup-linux-arm64-gnu": "4.59.0", + "@rollup/rollup-linux-arm64-musl": "4.59.0", + "@rollup/rollup-linux-loong64-gnu": "4.59.0", + "@rollup/rollup-linux-loong64-musl": "4.59.0", + "@rollup/rollup-linux-ppc64-gnu": "4.59.0", + "@rollup/rollup-linux-ppc64-musl": "4.59.0", + "@rollup/rollup-linux-riscv64-gnu": "4.59.0", + "@rollup/rollup-linux-riscv64-musl": "4.59.0", + "@rollup/rollup-linux-s390x-gnu": "4.59.0", + "@rollup/rollup-linux-x64-gnu": "4.59.0", + "@rollup/rollup-linux-x64-musl": "4.59.0", + "@rollup/rollup-openbsd-x64": "4.59.0", + "@rollup/rollup-openharmony-arm64": "4.59.0", + "@rollup/rollup-win32-arm64-msvc": "4.59.0", + "@rollup/rollup-win32-ia32-msvc": "4.59.0", + "@rollup/rollup-win32-x64-gnu": "4.59.0", + "@rollup/rollup-win32-x64-msvc": "4.59.0", "fsevents": "~2.3.2" } }, diff --git a/src/contracts/execute.ts b/src/contracts/execute.ts new file mode 100644 index 0000000..54be057 --- /dev/null +++ b/src/contracts/execute.ts @@ -0,0 +1,328 @@ +/** + * Execute types for Phase 5: Execution Proxying (Zero-Trust). + * + * These types support the `/v1/execute` endpoint which allows the sidecar to + * execute operations on behalf of agents, preventing resource-swapping attacks. + */ + +// --- Request Types --- + +/** + * Payload for fs.write operations + */ +export interface FileWritePayload { + type: "file_write"; + content: string; + create?: boolean; + append?: boolean; +} + +/** + * Payload for cli.exec operations + */ +export interface CliExecPayload { + type: "cli_exec"; + command: string; + args?: string[]; + cwd?: string; + timeout_ms?: number; +} + +/** + * Payload for http.fetch operations + */ +export interface HttpFetchPayload { + type: "http_fetch"; + method: string; + headers?: Record; + body?: string; +} + +/** + * Payload for fs.delete operations + */ +export interface FileDeletePayload { + type: "file_delete"; + recursive?: boolean; +} + +/** + * Payload for env.read operations + */ +export interface EnvReadPayload { + type: "env_read"; + keys: string[]; +} + +/** + * Union type for all execute payloads + */ +export type ExecutePayload = + | FileWritePayload + | CliExecPayload + | HttpFetchPayload + | FileDeletePayload + | EnvReadPayload; + +/** + * POST /v1/execute request body + */ +export interface ExecuteRequest { + /** Mandate ID from prior authorization */ + mandate_id: string; + /** Action to execute (must match mandate) */ + action: string; + /** Resource to operate on (must match mandate's resource scope) */ + resource: string; + /** Action-specific payload */ + payload?: ExecutePayload; +} + +// --- Result Types --- + +/** + * Result of fs.read operation + */ +export interface FileReadResult { + type: "file_read"; + content: string; + size: number; + content_hash: string; +} + +/** + * Result of fs.write operation + */ +export interface FileWriteResult { + type: "file_write"; + bytes_written: number; + content_hash: string; +} + +/** + * Result of cli.exec operation + */ +export interface CliExecResult { + type: "cli_exec"; + exit_code: number; + stdout: string; + stderr: string; + duration_ms: number; +} + +/** + * Result of http.fetch operation + */ +export interface HttpFetchResult { + type: "http_fetch"; + status_code: number; + headers: Record; + body: string; + body_hash: string; +} + +/** + * Directory entry for fs.list result + */ +export interface DirectoryEntry { + name: string; + type: "file" | "dir" | "symlink"; + size: number; + modified?: number; +} + +/** + * Result of fs.list operation + */ +export interface FileListResult { + type: "file_list"; + entries: DirectoryEntry[]; + total_entries: number; +} + +/** + * Result of fs.delete operation + */ +export interface FileDeleteResult { + type: "file_delete"; + paths_removed: number; +} + +/** + * Result of env.read operation + */ +export interface EnvReadResult { + type: "env_read"; + values: Record; +} + +/** + * Union type for all execute results + */ +export type ExecuteResult = + | FileReadResult + | FileWriteResult + | CliExecResult + | HttpFetchResult + | FileListResult + | FileDeleteResult + | EnvReadResult; + +// --- Response Types --- + +/** + * POST /v1/execute response body + */ +export interface ExecuteResponse { + /** Whether execution succeeded */ + success: boolean; + /** Execution result (action-specific) */ + result?: ExecuteResult; + /** Error message if failed */ + error?: string; + /** Audit trail ID */ + audit_id: string; + /** Evidence hash (for verification) */ + evidence_hash?: string; +} + +// --- Error Types --- + +/** + * Execution error codes returned by the sidecar + */ +export type ExecuteErrorCode = + | "mandate_not_found" + | "mandate_expired" + | "action_mismatch" + | "resource_mismatch" + | "execution_failed" + | "unsupported_action" + | "invalid_payload"; + +// --- Type Guards --- + +export function isFileWritePayload(value: unknown): value is FileWritePayload { + if (typeof value !== "object" || value === null) return false; + const obj = value as Record; + return obj.type === "file_write" && typeof obj.content === "string"; +} + +export function isCliExecPayload(value: unknown): value is CliExecPayload { + if (typeof value !== "object" || value === null) return false; + const obj = value as Record; + return obj.type === "cli_exec" && typeof obj.command === "string"; +} + +export function isHttpFetchPayload(value: unknown): value is HttpFetchPayload { + if (typeof value !== "object" || value === null) return false; + const obj = value as Record; + return obj.type === "http_fetch" && typeof obj.method === "string"; +} + +export function isFileDeletePayload(value: unknown): value is FileDeletePayload { + if (typeof value !== "object" || value === null) return false; + const obj = value as Record; + return obj.type === "file_delete"; +} + +export function isEnvReadPayload(value: unknown): value is EnvReadPayload { + if (typeof value !== "object" || value === null) return false; + const obj = value as Record; + return obj.type === "env_read" && Array.isArray(obj.keys); +} + +export function isExecutePayload(value: unknown): value is ExecutePayload { + return ( + isFileWritePayload(value) || + isCliExecPayload(value) || + isHttpFetchPayload(value) || + isFileDeletePayload(value) || + isEnvReadPayload(value) + ); +} + +export function isFileReadResult(value: unknown): value is FileReadResult { + if (typeof value !== "object" || value === null) return false; + const obj = value as Record; + return ( + obj.type === "file_read" && + typeof obj.content === "string" && + typeof obj.size === "number" && + typeof obj.content_hash === "string" + ); +} + +export function isFileWriteResult(value: unknown): value is FileWriteResult { + if (typeof value !== "object" || value === null) return false; + const obj = value as Record; + return ( + obj.type === "file_write" && + typeof obj.bytes_written === "number" && + typeof obj.content_hash === "string" + ); +} + +export function isCliExecResult(value: unknown): value is CliExecResult { + if (typeof value !== "object" || value === null) return false; + const obj = value as Record; + return ( + obj.type === "cli_exec" && + typeof obj.exit_code === "number" && + typeof obj.stdout === "string" && + typeof obj.stderr === "string" && + typeof obj.duration_ms === "number" + ); +} + +export function isHttpFetchResult(value: unknown): value is HttpFetchResult { + if (typeof value !== "object" || value === null) return false; + const obj = value as Record; + return ( + obj.type === "http_fetch" && + typeof obj.status_code === "number" && + typeof obj.headers === "object" && + typeof obj.body === "string" && + typeof obj.body_hash === "string" + ); +} + +export function isFileListResult(value: unknown): value is FileListResult { + if (typeof value !== "object" || value === null) return false; + const obj = value as Record; + return ( + obj.type === "file_list" && + Array.isArray(obj.entries) && + typeof obj.total_entries === "number" + ); +} + +export function isFileDeleteResult(value: unknown): value is FileDeleteResult { + if (typeof value !== "object" || value === null) return false; + const obj = value as Record; + return obj.type === "file_delete" && typeof obj.paths_removed === "number"; +} + +export function isEnvReadResult(value: unknown): value is EnvReadResult { + if (typeof value !== "object" || value === null) return false; + const obj = value as Record; + return obj.type === "env_read" && typeof obj.values === "object"; +} + +export function isExecuteResult(value: unknown): value is ExecuteResult { + return ( + isFileReadResult(value) || + isFileWriteResult(value) || + isCliExecResult(value) || + isHttpFetchResult(value) || + isFileListResult(value) || + isFileDeleteResult(value) || + isEnvReadResult(value) + ); +} + +export function isExecuteResponse(value: unknown): value is ExecuteResponse { + if (typeof value !== "object" || value === null) return false; + const obj = value as Record; + return typeof obj.success === "boolean" && typeof obj.audit_id === "string"; +} diff --git a/src/contracts/index.ts b/src/contracts/index.ts index 705cdd8..695b4dd 100644 --- a/src/contracts/index.ts +++ b/src/contracts/index.ts @@ -22,3 +22,42 @@ export { isPolicyRule } from "./policy-rule.js"; export type { ProofEvent } from "./proof-event.js"; export { isProofEvent } from "./proof-event.js"; export { isLabelPassed, passedLabels } from "./verification.js"; + +// Execute types for Phase 5: Execution Proxying (Zero-Trust) +export type { + ExecuteRequest, + ExecutePayload, + FileWritePayload, + CliExecPayload, + HttpFetchPayload, + FileDeletePayload, + EnvReadPayload, + ExecuteResponse, + ExecuteResult, + FileReadResult, + FileWriteResult, + CliExecResult, + HttpFetchResult, + DirectoryEntry, + FileListResult, + FileDeleteResult, + EnvReadResult, + ExecuteErrorCode, +} from "./execute.js"; +export { + isExecutePayload, + isFileWritePayload, + isCliExecPayload, + isHttpFetchPayload, + isFileDeletePayload, + isEnvReadPayload, + isExecuteResponse, + isExecuteResult, + isFileReadResult, + isFileWriteResult, + isCliExecResult, + isHttpFetchResult, + isFileListResult, + isFileDeleteResult, + isEnvReadResult, +} from "./execute.js"; diff --git a/src/index.ts b/src/index.ts index 1a75e05..c047ee8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,7 +2,11 @@ import { AuthorityClientError } from "./errors.js"; import { type AuthorizationResponse, type AuthorizeRequest, + type ExecutePayload, + type ExecuteRequest, + type ExecuteResponse, isAuthorizationResponse, + isExecuteResponse, toSidecarAuthorizeRequest, } from "./types.js"; @@ -25,6 +29,25 @@ export type { VerificationEvidence, VerificationSignal, VerificationStatus, + // Execute types for Phase 5: Execution Proxying (Zero-Trust) + ExecuteRequest, + ExecutePayload, + FileWritePayload, + CliExecPayload, + HttpFetchPayload, + FileDeletePayload, + EnvReadPayload, + ExecuteResponse, + ExecuteResult, + FileReadResult, + FileWriteResult, + CliExecResult, + HttpFetchResult, + DirectoryEntry, + FileListResult, + FileDeleteResult, + EnvReadResult, + ExecuteErrorCode, } from "./types.js"; export { AuthorityClientError, type AuthorityClientErrorCode } from "./errors.js"; export { @@ -39,6 +62,22 @@ export { passedLabels, isSignedMandate, toSidecarAuthorizeRequest, + // Execute type guards + isExecutePayload, + isFileWritePayload, + isCliExecPayload, + isHttpFetchPayload, + isFileDeletePayload, + isEnvReadPayload, + isExecuteResponse, + isExecuteResult, + isFileReadResult, + isFileWriteResult, + isCliExecResult, + isHttpFetchResult, + isFileListResult, + isFileDeleteResult, + isEnvReadResult, } from "./types.js"; export { effectiveMaxDelegationDepth, globMatch, matchesRule } from "./policy/matching.js"; export { PolicyEngine, type PolicyMatchResult } from "./policy/engine.js"; @@ -158,6 +197,25 @@ export interface AuthorityClientOptions { maxRetries?: number; backoffInitialMs?: number; endpointPath?: "/v1/authorize" | "/authorize"; + executeEndpointPath?: "/v1/execute" | "/execute"; +} + +/** + * Options for authorizeAndExecute convenience method + */ +export interface AuthorizeAndExecuteOptions { + /** Principal making the request */ + principal: string; + /** Action to perform */ + action: string; + /** Resource to operate on */ + resource: string; + /** Intent hash for mandate */ + intentHash?: string; + /** Labels for authorization */ + labels?: string[]; + /** Optional payload for the execution */ + payload?: ExecutePayload; } export class AuthorityClient { @@ -166,6 +224,7 @@ export class AuthorityClient { private readonly maxRetries: number; private readonly backoffInitialMs: number; private readonly endpointPath: "/v1/authorize" | "/authorize"; + private readonly executeEndpointPath: "/v1/execute" | "/execute"; constructor(options: AuthorityClientOptions) { this.baseUrl = options.baseUrl.replace(/\/+$/, ""); @@ -173,6 +232,7 @@ export class AuthorityClient { this.maxRetries = options.maxRetries ?? 0; this.backoffInitialMs = options.backoffInitialMs ?? 200; this.endpointPath = options.endpointPath ?? "/v1/authorize"; + this.executeEndpointPath = options.executeEndpointPath ?? "/v1/execute"; } async authorize(request: AuthorizeRequest): Promise { @@ -243,6 +303,138 @@ export class AuthorityClient { code: "network_error", }); } + + /** + * Execute an operation through the sidecar (Phase 5: Execution Proxying). + * + * The sidecar validates the mandate and executes the operation on behalf of the agent, + * preventing "confused deputy" attacks where an agent could request authorization for + * one resource but access another. + * + * @param request - Execute request with mandate_id from prior authorization + * @returns Execute response with success status and action-specific result + */ + async execute(request: ExecuteRequest): Promise { + const attempts = this.maxRetries + 1; + + for (let attempt = 0; attempt < attempts; attempt += 1) { + const controller = new AbortController(); + const timer = setTimeout(() => controller.abort(), this.timeoutMs); + try { + let response: Response; + try { + response = await fetch(`${this.baseUrl}${this.executeEndpointPath}`, { + method: "POST", + headers: { + "content-type": "application/json", + }, + body: JSON.stringify(request), + signal: controller.signal, + }); + } catch (error) { + if (attempt < this.maxRetries) { + await sleep(this.backoffInitialMs * (attempt + 1)); + continue; + } + if (error instanceof Error && error.name === "AbortError") { + throw new AuthorityClientError("execute request timed out", { + code: "timeout", + cause: error, + }); + } + throw new AuthorityClientError("execute request failed before response", { + code: "network_error", + cause: error, + }); + } + + const payload = await parseJsonSafely(response); + + if (!response.ok) { + if (response.status >= 500 && attempt < this.maxRetries) { + await sleep(this.backoffInitialMs * (attempt + 1)); + continue; + } + // For execute, we may get a 4xx with a valid ExecuteResponse containing error info + if (isExecuteResponse(payload)) { + return payload; + } + throw mapHttpError(response.status, payload); + } + + if (!isExecuteResponse(payload)) { + throw new AuthorityClientError("invalid execute response payload", { + code: "protocol_error", + status: response.status, + details: payload, + }); + } + + return payload; + } finally { + clearTimeout(timer); + } + } + + throw new AuthorityClientError("execute request exhausted retry budget", { + code: "network_error", + }); + } + + /** + * Convenience method that combines authorize + execute in a single call. + * + * This is the recommended pattern for zero-trust execution: + * 1. Authorize the action and obtain a mandate + * 2. Execute the operation through the sidecar using the mandate + * + * @param options - Authorization and execution options + * @returns Execute response with success status and action-specific result + * @throws AuthorityClientError if authorization is denied or execution fails + */ + async authorizeAndExecute(options: AuthorizeAndExecuteOptions): Promise { + const { principal, action, resource, intentHash, labels, payload } = options; + + // Step 1: Authorize and get mandate + const authResponse = await this.authorize({ + principal, + action, + resource, + intent_hash: intentHash ?? `${action}:${resource}`, + labels: labels ?? [], + }); + + if (!authResponse.allowed) { + throw new AuthorityClientError( + `authorization denied: ${authResponse.reason}`, + { + code: "forbidden", + details: { + reason: authResponse.reason, + missing_labels: authResponse.missing_labels, + }, + } + ); + } + + if (!authResponse.mandate_id) { + throw new AuthorityClientError( + "authorization succeeded but no mandate_id returned", + { + code: "protocol_error", + details: authResponse, + } + ); + } + + // Step 2: Execute through sidecar + return this.execute({ + mandate_id: authResponse.mandate_id, + action, + resource, + payload, + }); + } } function sleep(ms: number): Promise { diff --git a/src/types.ts b/src/types.ts index 552dd8b..b28587d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -46,3 +46,42 @@ export { // Backward-compatible alias for the initial scaffold API. export type { SidecarAuthorizeRequest as AuthorizationRequest } from "./contracts/action-request.js"; + +// Execute types for Phase 5: Execution Proxying (Zero-Trust) +export type { + ExecuteRequest, + ExecutePayload, + FileWritePayload, + CliExecPayload, + HttpFetchPayload, + FileDeletePayload, + EnvReadPayload, + ExecuteResponse, + ExecuteResult, + FileReadResult, + FileWriteResult, + CliExecResult, + HttpFetchResult, + DirectoryEntry, + FileListResult, + FileDeleteResult, + EnvReadResult, + ExecuteErrorCode, +} from "./contracts/execute.js"; +export { + isExecutePayload, + isFileWritePayload, + isCliExecPayload, + isHttpFetchPayload, + isFileDeletePayload, + isEnvReadPayload, + isExecuteResponse, + isExecuteResult, + isFileReadResult, + isFileWriteResult, + isCliExecResult, + isHttpFetchResult, + isFileListResult, + isFileDeleteResult, + isEnvReadResult, +} from "./contracts/execute.js"; diff --git a/tests/execute.test.ts b/tests/execute.test.ts new file mode 100644 index 0000000..8d1478d --- /dev/null +++ b/tests/execute.test.ts @@ -0,0 +1,314 @@ +import { describe, expect, it } from "vitest"; +import { + type CliExecPayload, + type CliExecResult, + type ExecutePayload, + type ExecuteRequest, + type ExecuteResponse, + type FileReadResult, + type FileWritePayload, + type FileWriteResult, + type HttpFetchPayload, + type HttpFetchResult, + isCliExecPayload, + isCliExecResult, + isExecutePayload, + isExecuteResponse, + isExecuteResult, + isFileReadResult, + isFileWritePayload, + isFileWriteResult, + isHttpFetchPayload, + isHttpFetchResult, +} from "../src/index.js"; + +describe("Execute types", () => { + describe("ExecuteRequest", () => { + it("accepts valid request without payload", () => { + const request: ExecuteRequest = { + mandate_id: "m_abc123", + action: "fs.read", + resource: "/src/index.ts", + }; + expect(request.mandate_id).toBe("m_abc123"); + expect(request.action).toBe("fs.read"); + expect(request.resource).toBe("/src/index.ts"); + expect(request.payload).toBeUndefined(); + }); + + it("accepts valid request with file_write payload", () => { + const payload: FileWritePayload = { + type: "file_write", + content: "hello world", + create: true, + append: false, + }; + const request: ExecuteRequest = { + mandate_id: "m_xyz789", + action: "fs.write", + resource: "/tmp/test.txt", + payload, + }; + expect(request.payload?.type).toBe("file_write"); + }); + + it("accepts valid request with cli_exec payload", () => { + const payload: CliExecPayload = { + type: "cli_exec", + command: "ls", + args: ["-la"], + cwd: "/tmp", + timeout_ms: 5000, + }; + const request: ExecuteRequest = { + mandate_id: "m_cli456", + action: "cli.exec", + resource: "ls", + payload, + }; + expect(request.payload?.type).toBe("cli_exec"); + }); + + it("accepts valid request with http_fetch payload", () => { + const payload: HttpFetchPayload = { + type: "http_fetch", + method: "POST", + headers: { "Content-Type": "application/json" }, + body: '{"key": "value"}', + }; + const request: ExecuteRequest = { + mandate_id: "m_http789", + action: "http.fetch", + resource: "https://api.example.com/data", + payload, + }; + expect(request.payload?.type).toBe("http_fetch"); + }); + }); + + describe("ExecutePayload type guards", () => { + it("isFileWritePayload returns true for valid file_write payload", () => { + const payload = { type: "file_write", content: "test" }; + expect(isFileWritePayload(payload)).toBe(true); + }); + + it("isFileWritePayload returns false for invalid payload", () => { + expect(isFileWritePayload({ type: "file_write" })).toBe(false); + expect(isFileWritePayload({ type: "cli_exec", content: "test" })).toBe(false); + expect(isFileWritePayload(null)).toBe(false); + expect(isFileWritePayload("string")).toBe(false); + }); + + it("isCliExecPayload returns true for valid cli_exec payload", () => { + const payload = { type: "cli_exec", command: "ls" }; + expect(isCliExecPayload(payload)).toBe(true); + }); + + it("isCliExecPayload returns false for invalid payload", () => { + expect(isCliExecPayload({ type: "cli_exec" })).toBe(false); + expect(isCliExecPayload({ type: "file_write", command: "ls" })).toBe(false); + }); + + it("isHttpFetchPayload returns true for valid http_fetch payload", () => { + const payload = { type: "http_fetch", method: "GET" }; + expect(isHttpFetchPayload(payload)).toBe(true); + }); + + it("isHttpFetchPayload returns false for invalid payload", () => { + expect(isHttpFetchPayload({ type: "http_fetch" })).toBe(false); + expect(isHttpFetchPayload({ type: "file_write", method: "GET" })).toBe(false); + }); + + it("isExecutePayload returns true for any valid payload", () => { + expect(isExecutePayload({ type: "file_write", content: "test" })).toBe(true); + expect(isExecutePayload({ type: "cli_exec", command: "ls" })).toBe(true); + expect(isExecutePayload({ type: "http_fetch", method: "GET" })).toBe(true); + }); + + it("isExecutePayload returns false for invalid payload", () => { + expect(isExecutePayload({ type: "unknown" })).toBe(false); + expect(isExecutePayload({})).toBe(false); + expect(isExecutePayload(null)).toBe(false); + }); + }); + + describe("ExecuteResult type guards", () => { + it("isFileReadResult returns true for valid file_read result", () => { + const result: FileReadResult = { + type: "file_read", + content: "file content", + size: 12, + content_hash: "sha256:abc123", + }; + expect(isFileReadResult(result)).toBe(true); + }); + + it("isFileReadResult returns false for invalid result", () => { + expect(isFileReadResult({ type: "file_read", content: "test" })).toBe(false); + expect(isFileReadResult({ type: "file_write", content: "test", size: 4, content_hash: "x" })).toBe(false); + }); + + it("isFileWriteResult returns true for valid file_write result", () => { + const result: FileWriteResult = { + type: "file_write", + bytes_written: 100, + content_hash: "sha256:def456", + }; + expect(isFileWriteResult(result)).toBe(true); + }); + + it("isCliExecResult returns true for valid cli_exec result", () => { + const result: CliExecResult = { + type: "cli_exec", + exit_code: 0, + stdout: "output", + stderr: "", + duration_ms: 150, + }; + expect(isCliExecResult(result)).toBe(true); + }); + + it("isHttpFetchResult returns true for valid http_fetch result", () => { + const result: HttpFetchResult = { + type: "http_fetch", + status_code: 200, + headers: { "content-type": "application/json" }, + body: '{"ok": true}', + body_hash: "sha256:xyz789", + }; + expect(isHttpFetchResult(result)).toBe(true); + }); + + it("isExecuteResult returns true for any valid result", () => { + expect(isExecuteResult({ type: "file_read", content: "x", size: 1, content_hash: "h" })).toBe(true); + expect(isExecuteResult({ type: "file_write", bytes_written: 1, content_hash: "h" })).toBe(true); + expect(isExecuteResult({ type: "cli_exec", exit_code: 0, stdout: "", stderr: "", duration_ms: 1 })).toBe(true); + expect(isExecuteResult({ type: "http_fetch", status_code: 200, headers: {}, body: "", body_hash: "h" })).toBe(true); + }); + + it("isExecuteResult returns false for invalid result", () => { + expect(isExecuteResult({ type: "unknown" })).toBe(false); + expect(isExecuteResult({})).toBe(false); + }); + }); + + describe("ExecuteResponse type guards", () => { + it("isExecuteResponse returns true for successful response", () => { + const response: ExecuteResponse = { + success: true, + result: { + type: "file_read", + content: "file content", + size: 12, + content_hash: "sha256:abc123", + }, + audit_id: "exec_123", + evidence_hash: "sha256:def456", + }; + expect(isExecuteResponse(response)).toBe(true); + }); + + it("isExecuteResponse returns true for failure response", () => { + const response: ExecuteResponse = { + success: false, + error: "Mandate not found", + audit_id: "exec_456", + }; + expect(isExecuteResponse(response)).toBe(true); + }); + + it("isExecuteResponse returns false for invalid response", () => { + expect(isExecuteResponse({ success: true })).toBe(false); + expect(isExecuteResponse({ audit_id: "x" })).toBe(false); + expect(isExecuteResponse(null)).toBe(false); + expect(isExecuteResponse("string")).toBe(false); + }); + }); + + describe("JSON serialization", () => { + it("ExecuteRequest serializes correctly", () => { + const request: ExecuteRequest = { + mandate_id: "m_abc123", + action: "fs.read", + resource: "/src/index.ts", + }; + const json = JSON.stringify(request); + expect(json).toContain('"mandate_id":"m_abc123"'); + expect(json).toContain('"action":"fs.read"'); + + const parsed = JSON.parse(json) as ExecuteRequest; + expect(parsed.mandate_id).toBe("m_abc123"); + }); + + it("ExecutePayload with file_write serializes correctly", () => { + const payload: ExecutePayload = { + type: "file_write", + content: "hello world", + create: true, + append: false, + }; + const json = JSON.stringify(payload); + expect(json).toContain('"type":"file_write"'); + expect(json).toContain('"content":"hello world"'); + }); + + it("ExecuteResponse serializes correctly", () => { + const response: ExecuteResponse = { + success: true, + result: { + type: "cli_exec", + exit_code: 0, + stdout: "output", + stderr: "", + duration_ms: 150, + }, + audit_id: "exec_123", + }; + const json = JSON.stringify(response); + expect(json).toContain('"success":true'); + expect(json).toContain('"type":"cli_exec"'); + expect(json).toContain('"exit_code":0'); + }); + }); +}); + +// Integration tests that require a running sidecar +describe("AuthorityClient execute integration", () => { + const sidecarBaseUrl = process.env.SIDECAR_BASE_URL; + const shouldRun = process.env.RUN_SIDECAR_INTEGRATION_TESTS === "true" && !!sidecarBaseUrl; + const maybeIt = shouldRun ? it : it.skip; + + maybeIt("execute returns mandate_not_found for invalid mandate", async () => { + const { AuthorityClient } = await import("../src/index.js"); + const client = new AuthorityClient({ baseUrl: sidecarBaseUrl as string, timeoutMs: 4000 }); + + const result = await client.execute({ + mandate_id: "m_nonexistent", + action: "fs.read", + resource: "/tmp/test.txt", + }); + + expect(result.success).toBe(false); + expect(result.error).toContain("not found"); + }); + + maybeIt("authorizeAndExecute works end-to-end", async () => { + const { AuthorityClient } = await import("../src/index.js"); + const client = new AuthorityClient({ baseUrl: sidecarBaseUrl as string, timeoutMs: 4000 }); + + // This test requires a sidecar with appropriate policy and a file that exists + // Skip in most environments + if (!process.env.SIDECAR_E2E_FILE_PATH) { + return; + } + + const result = await client.authorizeAndExecute({ + principal: "agent:test", + action: "fs.read", + resource: process.env.SIDECAR_E2E_FILE_PATH, + }); + + expect(result.success).toBe(true); + expect(result.result?.type).toBe("file_read"); + }); +});