feat: execute v2 Plugin tools in isolated Wasmtime invocations
Reuse canonical artifact and ABI verification; inject live Host admission and operation adapters without product authority or ambient WASI. Bound fresh Stores, resources, CPU, common deadlines and diagnostics, and regress actual PDK/adversarial Components.
This commit is contained in:
Generated
+692
-6
@@ -2,6 +2,15 @@
|
|||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 4
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "addr2line"
|
||||||
|
version = "0.25.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b"
|
||||||
|
dependencies = [
|
||||||
|
"gimli",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "adler2"
|
name = "adler2"
|
||||||
version = "2.0.1"
|
version = "2.0.1"
|
||||||
@@ -213,6 +222,12 @@ dependencies = [
|
|||||||
"syn 2.0.117",
|
"syn 2.0.117",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "arbitrary"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3bc62ac97cc33321f50863d514c3bc38a453947a8f9e781137e47c7401020aed"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arc-swap"
|
name = "arc-swap"
|
||||||
version = "1.9.1"
|
version = "1.9.1"
|
||||||
@@ -556,6 +571,9 @@ name = "bumpalo"
|
|||||||
version = "3.20.2"
|
version = "3.20.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
|
checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
|
||||||
|
dependencies = [
|
||||||
|
"allocator-api2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytecount"
|
name = "bytecount"
|
||||||
@@ -740,6 +758,15 @@ dependencies = [
|
|||||||
"cc",
|
"cc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cobs"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror 2.0.18",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "colorchoice"
|
name = "colorchoice"
|
||||||
version = "1.0.5"
|
version = "1.0.5"
|
||||||
@@ -853,6 +880,144 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cranelift-assembler-x64"
|
||||||
|
version = "0.125.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c088d3406f0c0252efa7445adfd2d05736bfb5218838f64eaf79d567077aed14"
|
||||||
|
dependencies = [
|
||||||
|
"cranelift-assembler-x64-meta",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cranelift-assembler-x64-meta"
|
||||||
|
version = "0.125.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5c03f887a763abb9c1dc08f722aa82b69067fda623b6f0273050f45f8b1a6776"
|
||||||
|
dependencies = [
|
||||||
|
"cranelift-srcgen",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cranelift-bforest"
|
||||||
|
version = "0.125.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0206887a11a43f507fee320a218dc365980bfc42ec2696792079a9f8c9369e90"
|
||||||
|
dependencies = [
|
||||||
|
"cranelift-entity",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cranelift-bitset"
|
||||||
|
version = "0.125.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ac0790c83cfdab95709c5d0105fd888221e3af9049a7d7ec376ec901ab4e4dba"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cranelift-codegen"
|
||||||
|
version = "0.125.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9a98aed2d262eda69310e84bae8e053ee4f17dbdd3347b8d9156aa618ba2de0a"
|
||||||
|
dependencies = [
|
||||||
|
"bumpalo",
|
||||||
|
"cranelift-assembler-x64",
|
||||||
|
"cranelift-bforest",
|
||||||
|
"cranelift-bitset",
|
||||||
|
"cranelift-codegen-meta",
|
||||||
|
"cranelift-codegen-shared",
|
||||||
|
"cranelift-control",
|
||||||
|
"cranelift-entity",
|
||||||
|
"cranelift-isle",
|
||||||
|
"gimli",
|
||||||
|
"hashbrown 0.15.5",
|
||||||
|
"log",
|
||||||
|
"pulley-interpreter",
|
||||||
|
"regalloc2",
|
||||||
|
"rustc-hash",
|
||||||
|
"serde",
|
||||||
|
"smallvec",
|
||||||
|
"target-lexicon",
|
||||||
|
"wasmtime-internal-math",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cranelift-codegen-meta"
|
||||||
|
version = "0.125.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6906852826988563e9b0a9232ad951f53a47aa41ffd02f8ac852d3f41aae836a"
|
||||||
|
dependencies = [
|
||||||
|
"cranelift-assembler-x64-meta",
|
||||||
|
"cranelift-codegen-shared",
|
||||||
|
"cranelift-srcgen",
|
||||||
|
"heck",
|
||||||
|
"pulley-interpreter",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cranelift-codegen-shared"
|
||||||
|
version = "0.125.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3a50105aab667b5cc845f2be37c78475d7cc127cd8ec0a31f7b2b71d526099a7"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cranelift-control"
|
||||||
|
version = "0.125.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6adcc7aa7c0bc1727176a6f2d99c28a9e79a541ccd5ca911a0cb352da8befa36"
|
||||||
|
dependencies = [
|
||||||
|
"arbitrary",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cranelift-entity"
|
||||||
|
version = "0.125.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "981b56af777f9a34ea6dcce93255125776d391410c2a68b75bed5941b714fa15"
|
||||||
|
dependencies = [
|
||||||
|
"cranelift-bitset",
|
||||||
|
"serde",
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cranelift-frontend"
|
||||||
|
version = "0.125.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dea982589684dfb71afecb9fc09555c3a266300a1162a60d7fa39d41a5705b1c"
|
||||||
|
dependencies = [
|
||||||
|
"cranelift-codegen",
|
||||||
|
"log",
|
||||||
|
"smallvec",
|
||||||
|
"target-lexicon",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cranelift-isle"
|
||||||
|
version = "0.125.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a0422686b22ed6a1f33cc40e3c43eb84b67155788568d1a5cac8439d3dca1783"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cranelift-native"
|
||||||
|
version = "0.125.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "56f697bbbe135c655ea1deb7af0bae4a5c4fae2c88fdfc0fa57b34ae58c91040"
|
||||||
|
dependencies = [
|
||||||
|
"cranelift-codegen",
|
||||||
|
"libc",
|
||||||
|
"target-lexicon",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cranelift-srcgen"
|
||||||
|
version = "0.125.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "718efe674f3df645462677e22a3128e890d88ba55821bb091083d257707be76c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crc32fast"
|
name = "crc32fast"
|
||||||
version = "1.5.0"
|
version = "1.5.0"
|
||||||
@@ -1303,6 +1468,18 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "embedded-io"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "embedded-io"
|
||||||
|
version = "0.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "encoding_rs"
|
name = "encoding_rs"
|
||||||
version = "0.8.35"
|
version = "0.8.35"
|
||||||
@@ -1749,6 +1926,17 @@ dependencies = [
|
|||||||
"polyval",
|
"polyval",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gimli"
|
||||||
|
version = "0.32.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7"
|
||||||
|
dependencies = [
|
||||||
|
"fallible-iterator",
|
||||||
|
"indexmap",
|
||||||
|
"stable_deref_trait",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "glob"
|
name = "glob"
|
||||||
version = "0.3.3"
|
version = "0.3.3"
|
||||||
@@ -2608,6 +2796,15 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mach2"
|
||||||
|
version = "0.4.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "manifest"
|
name = "manifest"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -2631,7 +2828,7 @@ dependencies = [
|
|||||||
"tracing",
|
"tracing",
|
||||||
"wasmparser 0.244.0",
|
"wasmparser 0.244.0",
|
||||||
"wit-component",
|
"wit-component",
|
||||||
"wit-parser",
|
"wit-parser 0.244.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2704,6 +2901,15 @@ version = "2.8.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memfd"
|
||||||
|
version = "0.6.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "57804b2c9b69967f1536a56f86297e367a33b19e98852ed624b84551cdbc0d90"
|
||||||
|
dependencies = [
|
||||||
|
"rustix 1.1.4",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memmap2"
|
name = "memmap2"
|
||||||
version = "0.9.10"
|
version = "0.9.10"
|
||||||
@@ -3038,6 +3244,18 @@ dependencies = [
|
|||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "object"
|
||||||
|
version = "0.37.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe"
|
||||||
|
dependencies = [
|
||||||
|
"crc32fast",
|
||||||
|
"hashbrown 0.15.5",
|
||||||
|
"indexmap",
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "object_store"
|
name = "object_store"
|
||||||
version = "0.13.2"
|
version = "0.13.2"
|
||||||
@@ -3465,6 +3683,18 @@ version = "1.13.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
|
checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "postcard"
|
||||||
|
version = "1.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24"
|
||||||
|
dependencies = [
|
||||||
|
"cobs",
|
||||||
|
"embedded-io 0.4.0",
|
||||||
|
"embedded-io 0.6.1",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "postscript"
|
name = "postscript"
|
||||||
version = "0.14.1"
|
version = "0.14.1"
|
||||||
@@ -3577,6 +3807,29 @@ dependencies = [
|
|||||||
"unicase",
|
"unicase",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pulley-interpreter"
|
||||||
|
version = "38.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "beafc309a2d35e16cc390644d88d14dfa45e45e15075ec6a9e37f6dfb43e926f"
|
||||||
|
dependencies = [
|
||||||
|
"cranelift-bitset",
|
||||||
|
"log",
|
||||||
|
"pulley-macros",
|
||||||
|
"wasmtime-internal-math",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pulley-macros"
|
||||||
|
version = "38.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1885fbb6c07454cfc8725a18a1da3cfc328ee8c53fb8d0671ea313edc8567947"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.117",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quinn"
|
name = "quinn"
|
||||||
version = "0.11.9"
|
version = "0.11.9"
|
||||||
@@ -3630,7 +3883,7 @@ dependencies = [
|
|||||||
"once_cell",
|
"once_cell",
|
||||||
"socket2",
|
"socket2",
|
||||||
"tracing",
|
"tracing",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.60.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3868,6 +4121,20 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regalloc2"
|
||||||
|
version = "0.13.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "08effbc1fa53aaebff69521a5c05640523fab037b34a4a2c109506bc938246fa"
|
||||||
|
dependencies = [
|
||||||
|
"allocator-api2",
|
||||||
|
"bumpalo",
|
||||||
|
"hashbrown 0.15.5",
|
||||||
|
"log",
|
||||||
|
"rustc-hash",
|
||||||
|
"smallvec",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex"
|
name = "regex"
|
||||||
version = "1.12.3"
|
version = "1.12.3"
|
||||||
@@ -4255,6 +4522,10 @@ name = "semver"
|
|||||||
version = "1.0.27"
|
version = "1.0.27"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
|
checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
"serde_core",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
@@ -4605,6 +4876,9 @@ name = "smallvec"
|
|||||||
version = "1.15.1"
|
version = "1.15.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "socket2"
|
name = "socket2"
|
||||||
@@ -4864,6 +5138,12 @@ dependencies = [
|
|||||||
"xattr",
|
"xattr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "target-lexicon"
|
||||||
|
version = "0.13.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "target-triple"
|
name = "target-triple"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
@@ -5199,6 +5479,7 @@ dependencies = [
|
|||||||
"bytes",
|
"bytes",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-sink",
|
"futures-sink",
|
||||||
|
"futures-util",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
@@ -5822,6 +6103,16 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-encoder"
|
||||||
|
version = "0.239.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5be00faa2b4950c76fe618c409d2c3ea5a3c9422013e079482d78544bb2d184c"
|
||||||
|
dependencies = [
|
||||||
|
"leb128fmt",
|
||||||
|
"wasmparser 0.239.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-encoder"
|
name = "wasm-encoder"
|
||||||
version = "0.244.0"
|
version = "0.244.0"
|
||||||
@@ -5867,6 +6158,19 @@ dependencies = [
|
|||||||
"web-sys",
|
"web-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasmparser"
|
||||||
|
version = "0.239.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8c9d90bb93e764f6beabf1d02028c70a2156a6583e63ac4218dd07ef733368b0"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.11.0",
|
||||||
|
"hashbrown 0.15.5",
|
||||||
|
"indexmap",
|
||||||
|
"semver",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasmparser"
|
name = "wasmparser"
|
||||||
version = "0.244.0"
|
version = "0.244.0"
|
||||||
@@ -5888,6 +6192,256 @@ checksum = "fe9269dd817e0e7021bf534ccd9761bc2d64778b6708e41228b8439d75eaa0b1"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.11.0",
|
"bitflags 2.11.0",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
|
"semver",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasmprinter"
|
||||||
|
version = "0.239.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b3981f3d51f39f24f5fc90f93049a90f08dbbca8deba602cd46bb8ca67a94718"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"termcolor",
|
||||||
|
"wasmparser 0.239.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasmprinter"
|
||||||
|
version = "0.244.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "09390d7b2bd7b938e563e4bff10aa345ef2e27a3bc99135697514ef54495e68f"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"termcolor",
|
||||||
|
"wasmparser 0.244.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasmtime"
|
||||||
|
version = "38.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f81eafc07c867be94c47e0dc66355d9785e09107a18901f76a20701ba0663ad7"
|
||||||
|
dependencies = [
|
||||||
|
"addr2line",
|
||||||
|
"anyhow",
|
||||||
|
"async-trait",
|
||||||
|
"bitflags 2.11.0",
|
||||||
|
"bumpalo",
|
||||||
|
"cc",
|
||||||
|
"cfg-if",
|
||||||
|
"encoding_rs",
|
||||||
|
"hashbrown 0.15.5",
|
||||||
|
"indexmap",
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"mach2",
|
||||||
|
"memfd",
|
||||||
|
"object",
|
||||||
|
"once_cell",
|
||||||
|
"postcard",
|
||||||
|
"pulley-interpreter",
|
||||||
|
"rustix 1.1.4",
|
||||||
|
"semver",
|
||||||
|
"serde",
|
||||||
|
"serde_derive",
|
||||||
|
"smallvec",
|
||||||
|
"target-lexicon",
|
||||||
|
"wasmparser 0.239.0",
|
||||||
|
"wasmtime-environ",
|
||||||
|
"wasmtime-internal-component-macro",
|
||||||
|
"wasmtime-internal-component-util",
|
||||||
|
"wasmtime-internal-cranelift",
|
||||||
|
"wasmtime-internal-fiber",
|
||||||
|
"wasmtime-internal-jit-debug",
|
||||||
|
"wasmtime-internal-jit-icache-coherence",
|
||||||
|
"wasmtime-internal-math",
|
||||||
|
"wasmtime-internal-slab",
|
||||||
|
"wasmtime-internal-unwinder",
|
||||||
|
"wasmtime-internal-versioned-export-macros",
|
||||||
|
"wasmtime-internal-winch",
|
||||||
|
"windows-sys 0.60.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasmtime-environ"
|
||||||
|
version = "38.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "78587abe085a44a13c90fa16fea6db014e9883e627a7044d7f0cb397ad08d1da"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"cranelift-bitset",
|
||||||
|
"cranelift-entity",
|
||||||
|
"gimli",
|
||||||
|
"indexmap",
|
||||||
|
"log",
|
||||||
|
"object",
|
||||||
|
"postcard",
|
||||||
|
"semver",
|
||||||
|
"serde",
|
||||||
|
"serde_derive",
|
||||||
|
"smallvec",
|
||||||
|
"target-lexicon",
|
||||||
|
"wasm-encoder 0.239.0",
|
||||||
|
"wasmparser 0.239.0",
|
||||||
|
"wasmprinter 0.239.0",
|
||||||
|
"wasmtime-internal-component-util",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasmtime-internal-component-macro"
|
||||||
|
version = "38.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d843bb444f2d1509ea9304ad749242d1fa5de95cde67665bfcdcafa0f360925c"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.117",
|
||||||
|
"wasmtime-internal-component-util",
|
||||||
|
"wasmtime-internal-wit-bindgen",
|
||||||
|
"wit-parser 0.239.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasmtime-internal-component-util"
|
||||||
|
version = "38.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "801ee1a80ab66f065a88c6a62f2d495d5540d027b366757c6a53e9c42f153aef"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasmtime-internal-cranelift"
|
||||||
|
version = "38.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "deb50f1c50365c32e557266ca85acdf77696c44a3f98797ba6af58cebc6d6d1e"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"cfg-if",
|
||||||
|
"cranelift-codegen",
|
||||||
|
"cranelift-control",
|
||||||
|
"cranelift-entity",
|
||||||
|
"cranelift-frontend",
|
||||||
|
"cranelift-native",
|
||||||
|
"gimli",
|
||||||
|
"itertools",
|
||||||
|
"log",
|
||||||
|
"object",
|
||||||
|
"pulley-interpreter",
|
||||||
|
"smallvec",
|
||||||
|
"target-lexicon",
|
||||||
|
"thiserror 2.0.18",
|
||||||
|
"wasmparser 0.239.0",
|
||||||
|
"wasmtime-environ",
|
||||||
|
"wasmtime-internal-math",
|
||||||
|
"wasmtime-internal-unwinder",
|
||||||
|
"wasmtime-internal-versioned-export-macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasmtime-internal-fiber"
|
||||||
|
version = "38.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9308cdb17f8d51e3164185616d809e28c29a6515c03b9dd95c89436b71f6d154"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"cc",
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"rustix 1.1.4",
|
||||||
|
"wasmtime-internal-versioned-export-macros",
|
||||||
|
"windows-sys 0.60.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasmtime-internal-jit-debug"
|
||||||
|
version = "38.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5c9b63a22bf2a8b6a149a41c6768bc17a8b2e3288a249cb8216987fbd7128e81"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"wasmtime-internal-versioned-export-macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasmtime-internal-jit-icache-coherence"
|
||||||
|
version = "38.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "eb8e042b6e3de2f3d708279f89f50b4b9aa1b9bab177300cdffb0ffcd2816df5"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"windows-sys 0.60.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasmtime-internal-math"
|
||||||
|
version = "38.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3c1f0674f38cd7d014eb1a49ea1d1766cca1a64459e8856ee118a10005302e16"
|
||||||
|
dependencies = [
|
||||||
|
"libm",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasmtime-internal-slab"
|
||||||
|
version = "38.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fb24b7535306713e7a250f8b71e35f05b6a5031bf9c3ed7330c308e899cbe7d3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasmtime-internal-unwinder"
|
||||||
|
version = "38.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "21d5a80e2623a49cb8e8c419542337b8fe0260b162c40dcc201080a84cbe9b7c"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"cfg-if",
|
||||||
|
"cranelift-codegen",
|
||||||
|
"log",
|
||||||
|
"object",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasmtime-internal-versioned-export-macros"
|
||||||
|
version = "38.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "23e277f734b9256359b21517c3b0c26a2a9de6c53a51b670ae55cdcde548bf4e"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.117",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasmtime-internal-winch"
|
||||||
|
version = "38.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7b4dc9333737142f6ece4369c8bcdda03a11edbd43d8fbd3e15004c194b9b743"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"cranelift-codegen",
|
||||||
|
"gimli",
|
||||||
|
"log",
|
||||||
|
"object",
|
||||||
|
"target-lexicon",
|
||||||
|
"wasmparser 0.239.0",
|
||||||
|
"wasmtime-environ",
|
||||||
|
"wasmtime-internal-cranelift",
|
||||||
|
"winch-codegen",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasmtime-internal-wit-bindgen"
|
||||||
|
version = "38.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5f758625553fe33fdce0713f63bb7784c4f5fecb7f7cd4813414519ec24b6a4c"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"bitflags 2.11.0",
|
||||||
|
"heck",
|
||||||
|
"indexmap",
|
||||||
|
"wit-parser 0.239.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -6169,6 +6723,26 @@ version = "0.4.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winch-codegen"
|
||||||
|
version = "38.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6c0bb17ae9bf89ebc74512150e6ee0a27b1eac5ff3b54d8cec264f4b4255022d"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"cranelift-assembler-x64",
|
||||||
|
"cranelift-codegen",
|
||||||
|
"gimli",
|
||||||
|
"regalloc2",
|
||||||
|
"smallvec",
|
||||||
|
"target-lexicon",
|
||||||
|
"thiserror 2.0.18",
|
||||||
|
"wasmparser 0.239.0",
|
||||||
|
"wasmtime-environ",
|
||||||
|
"wasmtime-internal-cranelift",
|
||||||
|
"wasmtime-internal-math",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-core"
|
name = "windows-core"
|
||||||
version = "0.62.2"
|
version = "0.62.2"
|
||||||
@@ -6255,6 +6829,15 @@ dependencies = [
|
|||||||
"windows-targets 0.52.6",
|
"windows-targets 0.52.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.60.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
|
||||||
|
dependencies = [
|
||||||
|
"windows-targets 0.53.5",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.61.2"
|
version = "0.61.2"
|
||||||
@@ -6288,13 +6871,30 @@ dependencies = [
|
|||||||
"windows_aarch64_gnullvm 0.52.6",
|
"windows_aarch64_gnullvm 0.52.6",
|
||||||
"windows_aarch64_msvc 0.52.6",
|
"windows_aarch64_msvc 0.52.6",
|
||||||
"windows_i686_gnu 0.52.6",
|
"windows_i686_gnu 0.52.6",
|
||||||
"windows_i686_gnullvm",
|
"windows_i686_gnullvm 0.52.6",
|
||||||
"windows_i686_msvc 0.52.6",
|
"windows_i686_msvc 0.52.6",
|
||||||
"windows_x86_64_gnu 0.52.6",
|
"windows_x86_64_gnu 0.52.6",
|
||||||
"windows_x86_64_gnullvm 0.52.6",
|
"windows_x86_64_gnullvm 0.52.6",
|
||||||
"windows_x86_64_msvc 0.52.6",
|
"windows_x86_64_msvc 0.52.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-targets"
|
||||||
|
version = "0.53.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
|
||||||
|
dependencies = [
|
||||||
|
"windows-link",
|
||||||
|
"windows_aarch64_gnullvm 0.53.1",
|
||||||
|
"windows_aarch64_msvc 0.53.1",
|
||||||
|
"windows_i686_gnu 0.53.1",
|
||||||
|
"windows_i686_gnullvm 0.53.1",
|
||||||
|
"windows_i686_msvc 0.53.1",
|
||||||
|
"windows_x86_64_gnu 0.53.1",
|
||||||
|
"windows_x86_64_gnullvm 0.53.1",
|
||||||
|
"windows_x86_64_msvc 0.53.1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_aarch64_gnullvm"
|
name = "windows_aarch64_gnullvm"
|
||||||
version = "0.42.2"
|
version = "0.42.2"
|
||||||
@@ -6307,6 +6907,12 @@ version = "0.52.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_gnullvm"
|
||||||
|
version = "0.53.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_aarch64_msvc"
|
name = "windows_aarch64_msvc"
|
||||||
version = "0.42.2"
|
version = "0.42.2"
|
||||||
@@ -6319,6 +6925,12 @@ version = "0.52.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_msvc"
|
||||||
|
version = "0.53.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_i686_gnu"
|
name = "windows_i686_gnu"
|
||||||
version = "0.42.2"
|
version = "0.42.2"
|
||||||
@@ -6331,12 +6943,24 @@ version = "0.52.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_gnu"
|
||||||
|
version = "0.53.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_i686_gnullvm"
|
name = "windows_i686_gnullvm"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_gnullvm"
|
||||||
|
version = "0.53.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_i686_msvc"
|
name = "windows_i686_msvc"
|
||||||
version = "0.42.2"
|
version = "0.42.2"
|
||||||
@@ -6349,6 +6973,12 @@ version = "0.52.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_msvc"
|
||||||
|
version = "0.53.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_gnu"
|
name = "windows_x86_64_gnu"
|
||||||
version = "0.42.2"
|
version = "0.42.2"
|
||||||
@@ -6361,6 +6991,12 @@ version = "0.52.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnu"
|
||||||
|
version = "0.53.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_gnullvm"
|
name = "windows_x86_64_gnullvm"
|
||||||
version = "0.42.2"
|
version = "0.42.2"
|
||||||
@@ -6373,6 +7009,12 @@ version = "0.52.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnullvm"
|
||||||
|
version = "0.53.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_msvc"
|
name = "windows_x86_64_msvc"
|
||||||
version = "0.42.2"
|
version = "0.42.2"
|
||||||
@@ -6385,6 +7027,12 @@ version = "0.52.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_msvc"
|
||||||
|
version = "0.53.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winnow"
|
name = "winnow"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
@@ -6474,7 +7122,7 @@ checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"heck",
|
"heck",
|
||||||
"wit-parser",
|
"wit-parser 0.244.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -6525,7 +7173,25 @@ dependencies = [
|
|||||||
"wasm-metadata",
|
"wasm-metadata",
|
||||||
"wasmparser 0.244.0",
|
"wasmparser 0.244.0",
|
||||||
"wat",
|
"wat",
|
||||||
"wit-parser",
|
"wit-parser 0.244.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wit-parser"
|
||||||
|
version = "0.239.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "55c92c939d667b7bf0c6bf2d1f67196529758f99a2a45a3355cc56964fd5315d"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"id-arena",
|
||||||
|
"indexmap",
|
||||||
|
"log",
|
||||||
|
"semver",
|
||||||
|
"serde",
|
||||||
|
"serde_derive",
|
||||||
|
"serde_json",
|
||||||
|
"unicode-xid",
|
||||||
|
"wasmparser 0.239.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -6793,7 +7459,27 @@ dependencies = [
|
|||||||
"wasmparser 0.244.0",
|
"wasmparser 0.244.0",
|
||||||
"wit-bindgen",
|
"wit-bindgen",
|
||||||
"wit-component",
|
"wit-component",
|
||||||
"wit-parser",
|
"wit-parser 0.244.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "yoi-plugin-runtime"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"async-trait",
|
||||||
|
"jsonschema",
|
||||||
|
"manifest",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"tempfile",
|
||||||
|
"thiserror 2.0.18",
|
||||||
|
"tokio",
|
||||||
|
"tokio-util",
|
||||||
|
"wasmprinter 0.244.0",
|
||||||
|
"wasmtime",
|
||||||
|
"wat",
|
||||||
|
"wit-component",
|
||||||
|
"wit-parser 0.244.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ members = [
|
|||||||
"crates/runtime-api",
|
"crates/runtime-api",
|
||||||
"crates/server-api",
|
"crates/server-api",
|
||||||
"crates/plugin-pdk",
|
"crates/plugin-pdk",
|
||||||
|
"crates/plugin-runtime",
|
||||||
"crates/yoi",
|
"crates/yoi",
|
||||||
"crates/protocol",
|
"crates/protocol",
|
||||||
"crates/session-metrics",
|
"crates/session-metrics",
|
||||||
@@ -57,6 +58,7 @@ default-members = [
|
|||||||
"crates/runtime-api",
|
"crates/runtime-api",
|
||||||
"crates/server-api",
|
"crates/server-api",
|
||||||
"crates/plugin-pdk",
|
"crates/plugin-pdk",
|
||||||
|
"crates/plugin-runtime",
|
||||||
"crates/yoi",
|
"crates/yoi",
|
||||||
"crates/protocol",
|
"crates/protocol",
|
||||||
"crates/session-metrics",
|
"crates/session-metrics",
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
[package]
|
||||||
|
name = "yoi-plugin-runtime"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
rust-version.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
async-trait.workspace = true
|
||||||
|
manifest.workspace = true
|
||||||
|
serde = { workspace = true, features = ["derive"] }
|
||||||
|
serde_json.workspace = true
|
||||||
|
thiserror.workspace = true
|
||||||
|
tokio = { workspace = true, features = ["rt", "macros", "sync", "time"] }
|
||||||
|
tokio-util = { version = "0.7", features = ["rt"] }
|
||||||
|
jsonschema = { version = "0.33.0", default-features = false }
|
||||||
|
wasmtime = { version = "=38.0.4", default-features = false, features = ["async", "component-model", "cranelift", "runtime", "std", "threads"] }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
tempfile.workspace = true
|
||||||
|
wit-component = { version = "0.244.0", features = ["dummy-module"] }
|
||||||
|
wit-parser = "0.244.0"
|
||||||
|
wasmprinter = "0.244.0"
|
||||||
|
wat = "1"
|
||||||
|
tokio = { workspace = true, features = ["rt-multi-thread", "test-util"] }
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# yoi-plugin-runtime
|
||||||
|
|
||||||
|
Runtime-owned, standalone execution of exact v2 Tool Components. [`Executor`] compiles only artifacts reverified through `manifest::plugin`, then creates a fresh Store and instance for each invocation. It does not resolve catalogs, config, Worker plans, delivery caches or repository paths, and does not register Tools with a Worker.
|
||||||
|
|
||||||
|
[`host`] is an explicit asynchronous injection boundary, not an implementation of Server authority or HTTPS. Every invocation (including pure computation) requires live admission and result-publication authorization. Providers must bind their authenticated installation/plan/subject and narrowed grants to [`host::InvocationContext`], authorize every protected operation, and make pending work cancellation-safe. There is no default provider or product allow-all fallback. Dropping a provider future must release temporary resources; an already dispatched external effect is not rolled back. Durable effect receipts, secret resolution, URL/DNS/TLS policy and real network transport belong to the Host provider, not this sandbox.
|
||||||
|
|
||||||
|
Component instantiation is not a Tool export: all Host imports, including logging, are rejected until the export context is installed. Service start/stop semantics are outside this crate. Only exact WIT imports are linked, without WASI, filesystem, environment, process, clock or network ambient authority. Call-local connection resources hold opaque provider objects; guest resource representations never identify a Server resource or issue a grant.
|
||||||
|
|
||||||
|
A single deadline covers admission, instantiation, guest execution, asynchronous Host waits, post-return and publication. Fuel bounds computation and cooperatively yields; epoch interruption cooperatively yields as well. Store memory/table/instance limits, call-local resource and operation counts, serialized payload and diagnostic budgets complement those bounds. Text/JSON overflows fail rather than returning truncated success; excess diagnostic entries are dropped and counted. Guest diagnostics are untrusted, must not contain secrets, and are separate from model output. Host adapters must not log raw request/response or credentials.
|
||||||
|
|
||||||
|
Compile results may be shared, but invocation memory, resource tables and imported state are never shared. The caller owns finite aggregate admission/concurrency quotas; per-invocation limits alone do not bound an unbounded number of callers. A validated digest is content identity, never admission authority. This standalone core is not connected to normal Worker launch/restore; product integration must supply the live Server provider.
|
||||||
@@ -0,0 +1,708 @@
|
|||||||
|
use crate::sandbox::SandboxLimits;
|
||||||
|
use crate::{
|
||||||
|
bindings,
|
||||||
|
host::{self, Connection, Host, InvocationContext, Operation, errors, http, logging},
|
||||||
|
};
|
||||||
|
use manifest::plugin::{self, PluginPackageLimits, PluginPackageManifest, PluginToolManifest};
|
||||||
|
use serde::Serialize;
|
||||||
|
use std::{sync::Arc, time::Duration};
|
||||||
|
use tokio::time::Instant;
|
||||||
|
use wasmtime::component::{Component, Linker, Resource, ResourceTable};
|
||||||
|
use wasmtime::{Config, Engine, Store};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Limits {
|
||||||
|
pub payload_bytes: usize,
|
||||||
|
pub config_bytes: usize,
|
||||||
|
pub memory_bytes: usize,
|
||||||
|
pub table_elements: usize,
|
||||||
|
pub instances: usize,
|
||||||
|
pub memories: usize,
|
||||||
|
pub tables: usize,
|
||||||
|
pub resources: usize,
|
||||||
|
pub fuel: u64,
|
||||||
|
pub wall_time: Duration,
|
||||||
|
pub host_operations: usize,
|
||||||
|
pub http_body_bytes: usize,
|
||||||
|
pub http_header_bytes: usize,
|
||||||
|
pub http_header_pairs: usize,
|
||||||
|
pub log_bytes: usize,
|
||||||
|
}
|
||||||
|
impl Default for Limits {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
payload_bytes: 64 * 1024,
|
||||||
|
config_bytes: 64 * 1024,
|
||||||
|
memory_bytes: 256 * 1024 * 1024,
|
||||||
|
table_elements: 10_000,
|
||||||
|
instances: 32,
|
||||||
|
memories: 4,
|
||||||
|
tables: 8,
|
||||||
|
resources: 32,
|
||||||
|
fuel: 10_000_000,
|
||||||
|
wall_time: Duration::from_secs(30),
|
||||||
|
host_operations: 32,
|
||||||
|
http_body_bytes: 1024 * 1024,
|
||||||
|
http_header_bytes: 16 * 1024,
|
||||||
|
http_header_pairs: 64,
|
||||||
|
log_bytes: 16 * 1024,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum ExecutionError {
|
||||||
|
#[error("invalid artifact: {0}")]
|
||||||
|
Artifact(String),
|
||||||
|
#[error("invalid invocation: {0}")]
|
||||||
|
InvalidInput(String),
|
||||||
|
#[error("invalid output: {0}")]
|
||||||
|
InvalidOutput(String),
|
||||||
|
#[error("invocation cancelled")]
|
||||||
|
Cancelled,
|
||||||
|
#[error("invocation deadline exceeded")]
|
||||||
|
Deadline,
|
||||||
|
#[error("Host denied or failed operation: {0:?}")]
|
||||||
|
Host(errors::HostError),
|
||||||
|
#[error("guest error {code}: {message}")]
|
||||||
|
Guest { code: String, message: String },
|
||||||
|
#[error("Component trap or resource limit: {0}")]
|
||||||
|
Trap(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Content identity only; both digests are independently verified by the package owner.
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct ArtifactIdentity {
|
||||||
|
pub package_digest: String,
|
||||||
|
pub artifact_digest: String,
|
||||||
|
pub artifact_size: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Immutable compiled bytes and declarations, not execution authority.
|
||||||
|
/// Private fields prevent mutable package metadata from bypassing verification.
|
||||||
|
pub struct CompiledTool {
|
||||||
|
component: Component,
|
||||||
|
manifest: PluginPackageManifest,
|
||||||
|
identity: ArtifactIdentity,
|
||||||
|
}
|
||||||
|
impl CompiledTool {
|
||||||
|
pub fn identity(&self) -> &ArtifactIdentity {
|
||||||
|
&self.identity
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct InvocationResult {
|
||||||
|
pub output: bindings::exports::yoi::plugin::tool::Output,
|
||||||
|
pub dropped_logs: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Executor {
|
||||||
|
engine: Engine,
|
||||||
|
limits: Limits,
|
||||||
|
}
|
||||||
|
impl Executor {
|
||||||
|
/// Limits are fixed by the Host, never selected by guest input.
|
||||||
|
pub fn new(limits: Limits) -> wasmtime::Result<Self> {
|
||||||
|
let ceiling = Limits::default();
|
||||||
|
macro_rules! check_ceiling {
|
||||||
|
($($field:ident),+ $(,)?) => { $(
|
||||||
|
if limits.$field > ceiling.$field {
|
||||||
|
return Err(wasmtime::Error::msg(concat!("limit exceeds supported ceiling: ", stringify!($field))));
|
||||||
|
}
|
||||||
|
)+ };
|
||||||
|
}
|
||||||
|
check_ceiling!(
|
||||||
|
payload_bytes,
|
||||||
|
config_bytes,
|
||||||
|
memory_bytes,
|
||||||
|
table_elements,
|
||||||
|
instances,
|
||||||
|
memories,
|
||||||
|
tables,
|
||||||
|
resources,
|
||||||
|
fuel,
|
||||||
|
wall_time,
|
||||||
|
host_operations,
|
||||||
|
http_body_bytes,
|
||||||
|
http_header_bytes,
|
||||||
|
http_header_pairs,
|
||||||
|
log_bytes
|
||||||
|
);
|
||||||
|
let mut config = Config::new();
|
||||||
|
config
|
||||||
|
.async_support(true)
|
||||||
|
.wasm_component_model(true)
|
||||||
|
.consume_fuel(true)
|
||||||
|
.epoch_interruption(true)
|
||||||
|
.wasm_threads(false)
|
||||||
|
.wasm_shared_everything_threads(false);
|
||||||
|
Ok(Self {
|
||||||
|
engine: Engine::new(&config)?,
|
||||||
|
limits,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn limits(&self) -> &Limits {
|
||||||
|
&self.limits
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reuses the canonical package/digest/ABI validator; no path discovery or
|
||||||
|
/// duplicate pack/hash implementation is involved. Compilation executes no guest.
|
||||||
|
pub fn compile(
|
||||||
|
&self,
|
||||||
|
artifact: &[u8],
|
||||||
|
package_digest: &str,
|
||||||
|
artifact_digest: &str,
|
||||||
|
artifact_size: u64,
|
||||||
|
) -> Result<CompiledTool, ExecutionError> {
|
||||||
|
let verified = plugin::verify_plugin_artifact(
|
||||||
|
artifact,
|
||||||
|
package_digest,
|
||||||
|
artifact_digest,
|
||||||
|
artifact_size,
|
||||||
|
&PluginPackageLimits::default(),
|
||||||
|
)
|
||||||
|
.map_err(|e| ExecutionError::Artifact(e.message))?;
|
||||||
|
let manifest = verified.package.manifest;
|
||||||
|
let component = Component::new(&self.engine, &verified.files[&manifest.runtime.component])
|
||||||
|
.map_err(|e| ExecutionError::Artifact(e.to_string()))?;
|
||||||
|
Ok(CompiledTool {
|
||||||
|
component,
|
||||||
|
manifest,
|
||||||
|
identity: ArtifactIdentity {
|
||||||
|
package_digest: package_digest.into(),
|
||||||
|
artifact_digest: artifact_digest.into(),
|
||||||
|
artifact_size,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// All computation and Host waits share the earlier of the caller deadline
|
||||||
|
/// and local ceiling. Dropping this future also drops its Store and resources.
|
||||||
|
pub async fn invoke(
|
||||||
|
&self,
|
||||||
|
compiled: &CompiledTool,
|
||||||
|
mut context: InvocationContext,
|
||||||
|
provider: Arc<dyn Host>,
|
||||||
|
tool_name: &str,
|
||||||
|
input_json: &str,
|
||||||
|
config_json: &str,
|
||||||
|
) -> Result<InvocationResult, ExecutionError> {
|
||||||
|
context.deadline = context.deadline.min(Instant::now() + self.limits.wall_time);
|
||||||
|
context.cancellation = context.cancellation.child_token();
|
||||||
|
let _lifetime = InvocationLifetime(context.cancellation.clone());
|
||||||
|
let deadline = context.deadline;
|
||||||
|
let cancellation = context.cancellation.clone();
|
||||||
|
let execution = self.run(
|
||||||
|
compiled,
|
||||||
|
context,
|
||||||
|
provider,
|
||||||
|
tool_name,
|
||||||
|
input_json,
|
||||||
|
config_json,
|
||||||
|
);
|
||||||
|
tokio::pin!(execution);
|
||||||
|
let mut tick = tokio::time::interval(Duration::from_millis(10));
|
||||||
|
loop {
|
||||||
|
tokio::select! {
|
||||||
|
biased;
|
||||||
|
_ = cancellation.cancelled() => return Err(ExecutionError::Cancelled),
|
||||||
|
_ = tokio::time::sleep_until(deadline) => return Err(ExecutionError::Deadline),
|
||||||
|
result = &mut execution => {
|
||||||
|
if cancellation.is_cancelled() { return Err(ExecutionError::Cancelled); }
|
||||||
|
if Instant::now() >= deadline { return Err(ExecutionError::Deadline); }
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
_ = tick.tick() => self.engine.increment_epoch(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run(
|
||||||
|
&self,
|
||||||
|
compiled: &CompiledTool,
|
||||||
|
context: InvocationContext,
|
||||||
|
provider: Arc<dyn Host>,
|
||||||
|
tool_name: &str,
|
||||||
|
input_json: &str,
|
||||||
|
config_json: &str,
|
||||||
|
) -> Result<InvocationResult, ExecutionError> {
|
||||||
|
if !Engine::same(&self.engine, compiled.component.engine()) {
|
||||||
|
return Err(ExecutionError::Artifact(
|
||||||
|
"Component belongs to another executor".into(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if context.artifact != compiled.identity || context.tool_name != tool_name {
|
||||||
|
return Err(ExecutionError::InvalidInput(
|
||||||
|
"artifact/Tool does not match Host-bound context".into(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let tool = compiled
|
||||||
|
.manifest
|
||||||
|
.tools
|
||||||
|
.iter()
|
||||||
|
.find(|t| t.name == tool_name)
|
||||||
|
.ok_or_else(|| ExecutionError::InvalidInput("undeclared Tool".into()))?;
|
||||||
|
if input_json.len() > self.limits.payload_bytes
|
||||||
|
|| config_json.len() > self.limits.config_bytes
|
||||||
|
|| context.invocation_id.len() > 1024
|
||||||
|
{
|
||||||
|
return Err(ExecutionError::InvalidInput(
|
||||||
|
"input/config/identity exceeds limit".into(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
validate_json(&tool.input_schema, input_json).map_err(ExecutionError::InvalidInput)?;
|
||||||
|
serde_json::from_str::<serde_json::Value>(config_json)
|
||||||
|
.map_err(|e| ExecutionError::InvalidInput(e.to_string()))?;
|
||||||
|
provider
|
||||||
|
.authorize(&context, Operation::Admit)
|
||||||
|
.await
|
||||||
|
.map_err(ExecutionError::Host)?;
|
||||||
|
check_current(&context).map_err(ExecutionError::Host)?;
|
||||||
|
let limits = &self.limits;
|
||||||
|
let state = State {
|
||||||
|
context: None,
|
||||||
|
provider: provider.clone(),
|
||||||
|
limits: limits.clone(),
|
||||||
|
manifest: compiled.manifest.clone(),
|
||||||
|
table: ResourceTable::new(),
|
||||||
|
operations: 0,
|
||||||
|
resources: 0,
|
||||||
|
log_bytes: 0,
|
||||||
|
dropped_logs: 0,
|
||||||
|
store_limits: SandboxLimits::new(limits),
|
||||||
|
};
|
||||||
|
let mut store = Store::new(&self.engine, state);
|
||||||
|
store.limiter(|state| &mut state.store_limits);
|
||||||
|
store.set_fuel(limits.fuel).map_err(trap)?;
|
||||||
|
store
|
||||||
|
.fuel_async_yield_interval(Some(10_000))
|
||||||
|
.map_err(trap)?;
|
||||||
|
store.set_epoch_deadline(1);
|
||||||
|
store.epoch_deadline_async_yield_and_update(1);
|
||||||
|
let mut linker = Linker::new(&self.engine);
|
||||||
|
bindings::HttpsLoggedToolPlugin::add_to_linker::<_, wasmtime::component::HasSelf<_>>(
|
||||||
|
&mut linker,
|
||||||
|
|s| s,
|
||||||
|
)
|
||||||
|
.map_err(trap)?;
|
||||||
|
let instance = bindings::HttpsLoggedToolPlugin::instantiate_async(
|
||||||
|
&mut store,
|
||||||
|
&compiled.component,
|
||||||
|
&linker,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(trap)?;
|
||||||
|
// No import during instantiate/start may act, including logging. Service
|
||||||
|
// on-start is a different world and has no adapter here.
|
||||||
|
check_current(&context).map_err(ExecutionError::Host)?;
|
||||||
|
store.data_mut().context = Some(context.clone());
|
||||||
|
let invocation = bindings::exports::yoi::plugin::tool::Invocation {
|
||||||
|
invocation_id: context.invocation_id.clone(),
|
||||||
|
tool_name: tool_name.into(),
|
||||||
|
input_json: input_json.into(),
|
||||||
|
config_json: config_json.into(),
|
||||||
|
remaining_ms: context
|
||||||
|
.deadline
|
||||||
|
.saturating_duration_since(Instant::now())
|
||||||
|
.as_millis()
|
||||||
|
.min(u32::MAX as u128) as u32,
|
||||||
|
};
|
||||||
|
let result = instance
|
||||||
|
.yoi_plugin_tool()
|
||||||
|
.call_invoke(&mut store, &invocation)
|
||||||
|
.await
|
||||||
|
.map_err(trap)?;
|
||||||
|
check_current(&context).map_err(ExecutionError::Host)?;
|
||||||
|
provider
|
||||||
|
.authorize(&context, Operation::Publish)
|
||||||
|
.await
|
||||||
|
.map_err(ExecutionError::Host)?;
|
||||||
|
check_current(&context).map_err(ExecutionError::Host)?;
|
||||||
|
let output = match result {
|
||||||
|
Ok(output) => {
|
||||||
|
validate_output(tool, &output, limits.payload_bytes)?;
|
||||||
|
output
|
||||||
|
}
|
||||||
|
Err(error) => {
|
||||||
|
if error.message.len().saturating_add(error.code.len()) > limits.payload_bytes {
|
||||||
|
return Err(ExecutionError::InvalidOutput(
|
||||||
|
"guest error exceeds limit".into(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let code = if tool.error_codes.contains(&error.code) {
|
||||||
|
error.code
|
||||||
|
} else {
|
||||||
|
"plugin-failed".into()
|
||||||
|
};
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct Failure<'a> {
|
||||||
|
code: &'a str,
|
||||||
|
message: &'a str,
|
||||||
|
}
|
||||||
|
if serde_json::to_vec(&Failure {
|
||||||
|
code: &code,
|
||||||
|
message: &error.message,
|
||||||
|
})
|
||||||
|
.unwrap()
|
||||||
|
.len()
|
||||||
|
> limits.payload_bytes
|
||||||
|
{
|
||||||
|
return Err(ExecutionError::InvalidOutput(
|
||||||
|
"serialized guest error exceeds limit".into(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
return Err(ExecutionError::Guest {
|
||||||
|
code,
|
||||||
|
message: error.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(InvocationResult {
|
||||||
|
output,
|
||||||
|
dropped_logs: store.data().dropped_logs,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn trap(e: wasmtime::Error) -> ExecutionError {
|
||||||
|
ExecutionError::Trap(format!("{e:#}"))
|
||||||
|
}
|
||||||
|
fn validate_json(schema: &serde_json::Value, json: &str) -> Result<(), String> {
|
||||||
|
let value: serde_json::Value = serde_json::from_str(json).map_err(|e| e.to_string())?;
|
||||||
|
// Schema admission already forbids external refs/dialects in manifest::plugin.
|
||||||
|
let validator = jsonschema::options()
|
||||||
|
.with_draft(jsonschema::Draft::Draft202012)
|
||||||
|
.should_validate_formats(true)
|
||||||
|
.build(schema)
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
validator.validate(&value).map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
fn validate_output(
|
||||||
|
tool: &PluginToolManifest,
|
||||||
|
output: &bindings::exports::yoi::plugin::tool::Output,
|
||||||
|
max: usize,
|
||||||
|
) -> Result<(), ExecutionError> {
|
||||||
|
let raw_bytes = output
|
||||||
|
.summary
|
||||||
|
.len()
|
||||||
|
.saturating_add(output.content.as_ref().map_or(0, String::len))
|
||||||
|
.saturating_add(output.structured_json.as_ref().map_or(0, String::len));
|
||||||
|
if raw_bytes > max {
|
||||||
|
return Err(ExecutionError::InvalidOutput("output exceeds limit".into()));
|
||||||
|
}
|
||||||
|
// Use the complete serialized envelope budget, not separate per-field limits.
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct Envelope<'a> {
|
||||||
|
summary: &'a str,
|
||||||
|
content: &'a Option<String>,
|
||||||
|
structured_json: &'a Option<String>,
|
||||||
|
}
|
||||||
|
let bytes = serde_json::to_vec(&Envelope {
|
||||||
|
summary: &output.summary,
|
||||||
|
content: &output.content,
|
||||||
|
structured_json: &output.structured_json,
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
if bytes.len() > max {
|
||||||
|
return Err(ExecutionError::InvalidOutput("output exceeds limit".into()));
|
||||||
|
}
|
||||||
|
let json = output
|
||||||
|
.structured_json
|
||||||
|
.as_deref()
|
||||||
|
.ok_or_else(|| ExecutionError::InvalidOutput("missing structured output".into()))?;
|
||||||
|
validate_json(&tool.output_schema, json).map_err(ExecutionError::InvalidOutput)
|
||||||
|
}
|
||||||
|
|
||||||
|
struct InvocationLifetime(host::CancellationToken);
|
||||||
|
impl Drop for InvocationLifetime {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.0.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct State {
|
||||||
|
context: Option<InvocationContext>,
|
||||||
|
provider: Arc<dyn Host>,
|
||||||
|
limits: Limits,
|
||||||
|
manifest: PluginPackageManifest,
|
||||||
|
table: ResourceTable,
|
||||||
|
operations: usize,
|
||||||
|
resources: usize,
|
||||||
|
log_bytes: usize,
|
||||||
|
dropped_logs: u64,
|
||||||
|
store_limits: SandboxLimits,
|
||||||
|
}
|
||||||
|
fn check_current(context: &InvocationContext) -> Result<(), errors::HostError> {
|
||||||
|
if context.cancellation.is_cancelled() {
|
||||||
|
return Err(host::error(errors::Code::Cancelled, "invocation cancelled"));
|
||||||
|
}
|
||||||
|
if Instant::now() >= context.deadline {
|
||||||
|
return Err(host::error(
|
||||||
|
errors::Code::DeadlineExceeded,
|
||||||
|
"invocation deadline exceeded",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
impl State {
|
||||||
|
fn current(&self) -> wasmtime::Result<InvocationContext> {
|
||||||
|
let context = self
|
||||||
|
.context
|
||||||
|
.as_ref()
|
||||||
|
.ok_or_else(|| wasmtime::Error::msg("Host import outside Tool export context"))?;
|
||||||
|
check_current(context).map_err(|_| wasmtime::Error::msg("invocation no longer active"))?;
|
||||||
|
Ok(context.clone())
|
||||||
|
}
|
||||||
|
fn operation(&mut self) -> Result<(), errors::HostError> {
|
||||||
|
if self.operations >= self.limits.host_operations {
|
||||||
|
return Err(host::error(
|
||||||
|
errors::Code::QuotaExceeded,
|
||||||
|
"Host operation limit",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
self.operations += 1;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl bindings::yoi::host::errors::Host for State {}
|
||||||
|
impl bindings::yoi::host::connections::HostConnection for State {
|
||||||
|
async fn drop(&mut self, resource: Resource<Connection>) -> wasmtime::Result<()> {
|
||||||
|
self.table.delete(resource)?;
|
||||||
|
self.resources -= 1;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl bindings::yoi::host::connections::Host for State {
|
||||||
|
async fn open(
|
||||||
|
&mut self,
|
||||||
|
slot: String,
|
||||||
|
) -> wasmtime::Result<Result<Resource<Connection>, errors::HostError>> {
|
||||||
|
let context = self.current()?;
|
||||||
|
if let Err(e) = self.operation() {
|
||||||
|
return Ok(Err(e));
|
||||||
|
}
|
||||||
|
let declared = self.manifest.connections.iter().any(|s| s.name == slot);
|
||||||
|
if !declared || !context.grants.connection_slots.contains(&slot) {
|
||||||
|
return Ok(Err(host::error(
|
||||||
|
errors::Code::Denied,
|
||||||
|
"connection slot not granted",
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
if self.resources >= self.limits.resources {
|
||||||
|
return Ok(Err(host::error(
|
||||||
|
errors::Code::QuotaExceeded,
|
||||||
|
"resource limit",
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
if let Err(e) = self
|
||||||
|
.provider
|
||||||
|
.authorize(&context, Operation::Open { slot: &slot })
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
return Ok(Err(e));
|
||||||
|
}
|
||||||
|
check_current(&context).map_err(|_| wasmtime::Error::msg("invocation no longer active"))?;
|
||||||
|
let binding = match self.provider.open(&context, &slot).await {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(e) => return Ok(Err(e)),
|
||||||
|
};
|
||||||
|
check_current(&context).map_err(|_| wasmtime::Error::msg("late connection result"))?;
|
||||||
|
let resource = self.table.push(Connection { slot, binding })?;
|
||||||
|
self.resources += 1;
|
||||||
|
Ok(Ok(resource))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl bindings::yoi::host::http::Host for State {
|
||||||
|
async fn send(
|
||||||
|
&mut self,
|
||||||
|
target: Resource<Connection>,
|
||||||
|
mut input: http::Request,
|
||||||
|
) -> wasmtime::Result<Result<http::Response, errors::HostError>> {
|
||||||
|
let context = self.current()?;
|
||||||
|
if let Err(e) = self.operation() {
|
||||||
|
return Ok(Err(e));
|
||||||
|
}
|
||||||
|
let connection = self.table.get(&target)?;
|
||||||
|
let slot = connection.slot.clone();
|
||||||
|
let binding = connection.binding.clone();
|
||||||
|
if !context.grants.connection_slots.contains(&slot) {
|
||||||
|
return Ok(Err(host::error(
|
||||||
|
errors::Code::Denied,
|
||||||
|
"connection slot not granted",
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
let declaration = self
|
||||||
|
.manifest
|
||||||
|
.connections
|
||||||
|
.iter()
|
||||||
|
.find(|s| s.name == slot)
|
||||||
|
.unwrap();
|
||||||
|
let method = match input.method {
|
||||||
|
http::Method::Get => plugin::PluginHttpMethod::Get,
|
||||||
|
http::Method::Head => plugin::PluginHttpMethod::Head,
|
||||||
|
http::Method::Post => plugin::PluginHttpMethod::Post,
|
||||||
|
http::Method::Put => plugin::PluginHttpMethod::Put,
|
||||||
|
http::Method::Patch => plugin::PluginHttpMethod::Patch,
|
||||||
|
http::Method::Delete => plugin::PluginHttpMethod::Delete,
|
||||||
|
http::Method::Options => plugin::PluginHttpMethod::Options,
|
||||||
|
};
|
||||||
|
// Canonical URL/DNS/TLS/secret and narrower binding checks belong to the
|
||||||
|
// provider. These static ceilings are not a network protection claim.
|
||||||
|
if !declaration.methods.contains(&method)
|
||||||
|
|| !declaration.path_prefixes.iter().any(|p| {
|
||||||
|
input.path == *p
|
||||||
|
|| input
|
||||||
|
.path
|
||||||
|
.strip_prefix(p)
|
||||||
|
.is_some_and(|rest| p.ends_with('/') || rest.starts_with('/'))
|
||||||
|
})
|
||||||
|
{
|
||||||
|
return Ok(Err(host::error(
|
||||||
|
errors::Code::Denied,
|
||||||
|
"request exceeds package ceiling",
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
if !request_bounded(&input, &self.limits) {
|
||||||
|
return Ok(Err(host::error(
|
||||||
|
errors::Code::QuotaExceeded,
|
||||||
|
"request exceeds limit",
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
input.timeout_ms = input.timeout_ms.min(15_000).min(
|
||||||
|
context
|
||||||
|
.deadline
|
||||||
|
.saturating_duration_since(Instant::now())
|
||||||
|
.as_millis()
|
||||||
|
.min(u32::MAX as u128) as u32,
|
||||||
|
);
|
||||||
|
let send_deadline = context
|
||||||
|
.deadline
|
||||||
|
.min(Instant::now() + Duration::from_millis(input.timeout_ms as u64));
|
||||||
|
match tokio::time::timeout_at(
|
||||||
|
send_deadline,
|
||||||
|
self.provider.authorize(
|
||||||
|
&context,
|
||||||
|
Operation::Send {
|
||||||
|
slot: &slot,
|
||||||
|
binding: binding.as_ref(),
|
||||||
|
request: &input,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(Ok(())) => (),
|
||||||
|
Ok(Err(error)) => return Ok(Err(error)),
|
||||||
|
Err(_) => {
|
||||||
|
return Ok(Err(host::error(
|
||||||
|
errors::Code::DeadlineExceeded,
|
||||||
|
"HTTP authorization deadline exceeded",
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
check_current(&context).map_err(|_| wasmtime::Error::msg("invocation no longer active"))?;
|
||||||
|
if Instant::now() >= send_deadline {
|
||||||
|
return Ok(Err(host::error(
|
||||||
|
errors::Code::DeadlineExceeded,
|
||||||
|
"HTTP deadline exceeded before dispatch",
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
let response = match tokio::time::timeout_at(
|
||||||
|
send_deadline,
|
||||||
|
self.provider.send(&context, binding.as_ref(), input),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(response) => response,
|
||||||
|
Err(_) => Err(errors::HostError {
|
||||||
|
code: errors::Code::DeadlineExceeded,
|
||||||
|
message: "HTTP wait deadline exceeded".into(),
|
||||||
|
diagnostic_id: String::new(),
|
||||||
|
effect: errors::EffectObservation::Indeterminate,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
check_current(&context).map_err(|_| wasmtime::Error::msg("late HTTP result"))?;
|
||||||
|
Ok(response.and_then(|response| {
|
||||||
|
if response.body.len() > self.limits.http_body_bytes
|
||||||
|
|| !pairs_bounded(&response.headers, &self.limits)
|
||||||
|
{
|
||||||
|
Err(errors::HostError {
|
||||||
|
code: errors::Code::QuotaExceeded,
|
||||||
|
message: "response exceeds limit".into(),
|
||||||
|
diagnostic_id: String::new(),
|
||||||
|
effect: errors::EffectObservation::ResponseObserved,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Ok(response)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn pairs_bounded(pairs: &[http::Pair], limits: &Limits) -> bool {
|
||||||
|
pairs.len() <= limits.http_header_pairs
|
||||||
|
&& pairs
|
||||||
|
.iter()
|
||||||
|
.try_fold(0usize, |n, p| {
|
||||||
|
n.checked_add(p.name.len())?.checked_add(p.value.len())
|
||||||
|
})
|
||||||
|
.is_some_and(|n| n <= limits.http_header_bytes)
|
||||||
|
}
|
||||||
|
fn request_bounded(request: &http::Request, limits: &Limits) -> bool {
|
||||||
|
request.body.len() <= limits.http_body_bytes
|
||||||
|
&& request.path.len() <= limits.payload_bytes
|
||||||
|
&& request.effect_key.len() <= 128
|
||||||
|
&& !request.effect_key.is_empty()
|
||||||
|
&& pairs_bounded(&request.headers, limits)
|
||||||
|
&& pairs_bounded(&request.query, limits)
|
||||||
|
}
|
||||||
|
impl bindings::yoi::host::logging::Host for State {
|
||||||
|
async fn log(
|
||||||
|
&mut self,
|
||||||
|
input: logging::Entry,
|
||||||
|
) -> wasmtime::Result<Result<(), errors::HostError>> {
|
||||||
|
let context = self.current()?;
|
||||||
|
if !context.grants.logging
|
||||||
|
|| !self
|
||||||
|
.manifest
|
||||||
|
.requested_grants
|
||||||
|
.contains(&plugin::PluginRequestedGrant::Logging)
|
||||||
|
{
|
||||||
|
return Ok(Err(host::error(
|
||||||
|
errors::Code::Denied,
|
||||||
|
"logging not granted",
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
let size = input
|
||||||
|
.event_code
|
||||||
|
.len()
|
||||||
|
.saturating_add(input.message.len())
|
||||||
|
.saturating_add(
|
||||||
|
input
|
||||||
|
.fields
|
||||||
|
.iter()
|
||||||
|
.map(|f| {
|
||||||
|
f.name.len().saturating_add(match &f.value {
|
||||||
|
logging::Scalar::Text(s) => s.len(),
|
||||||
|
_ => 8,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.sum::<usize>(),
|
||||||
|
);
|
||||||
|
if size > self.limits.log_bytes.saturating_sub(self.log_bytes) {
|
||||||
|
self.dropped_logs = self.dropped_logs.saturating_add(1);
|
||||||
|
return Ok(Ok(()));
|
||||||
|
}
|
||||||
|
if let Err(e) = self.operation() {
|
||||||
|
return Ok(Err(e));
|
||||||
|
}
|
||||||
|
if let Err(e) = self.provider.authorize(&context, Operation::Log).await {
|
||||||
|
return Ok(Err(e));
|
||||||
|
}
|
||||||
|
check_current(&context).map_err(|_| wasmtime::Error::msg("invocation no longer active"))?;
|
||||||
|
self.log_bytes += size;
|
||||||
|
let result = self.provider.log(&context, input).await;
|
||||||
|
check_current(&context).map_err(|_| wasmtime::Error::msg("late log result"))?;
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
//! Host-bound identity, grants and asynchronous provider contracts.
|
||||||
|
//!
|
||||||
|
//! The provider is the authority adapter: opaque bindings are not credentials
|
||||||
|
//! supplied by guests. Implementations must live-check authorization, remain
|
||||||
|
//! cancellation-safe, bound their own allocations and never auto-retry effects.
|
||||||
|
|
||||||
|
pub use crate::bindings::yoi::host::{errors, http, logging};
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use std::{any::Any, collections::BTreeSet, sync::Arc};
|
||||||
|
use tokio::time::Instant;
|
||||||
|
pub use tokio_util::sync::CancellationToken;
|
||||||
|
|
||||||
|
/// Constructed by the authenticated Runtime integration, never from guest JSON.
|
||||||
|
/// All fields are a snapshot upper bound, not a cached live authorization.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct InvocationContext {
|
||||||
|
pub workspace: String,
|
||||||
|
pub installation: String,
|
||||||
|
pub plan: String,
|
||||||
|
pub subject: String,
|
||||||
|
pub invocation_id: String,
|
||||||
|
pub artifact: crate::ArtifactIdentity,
|
||||||
|
pub tool_name: String,
|
||||||
|
pub grants: Grants,
|
||||||
|
pub deadline: Instant,
|
||||||
|
pub cancellation: CancellationToken,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default)]
|
||||||
|
pub struct Grants {
|
||||||
|
pub logging: bool,
|
||||||
|
pub connection_slots: BTreeSet<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Opaque Host-owned capability, stored only within one invocation's Store.
|
||||||
|
/// The provider must not interpret a guest resource representation as identity.
|
||||||
|
pub struct Connection {
|
||||||
|
pub(crate) slot: String,
|
||||||
|
pub(crate) binding: Arc<dyn Any + Send + Sync>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Each operation requires a fresh owner check; admission covers pure Tools too.
|
||||||
|
pub enum Operation<'a> {
|
||||||
|
Admit,
|
||||||
|
Publish,
|
||||||
|
Open {
|
||||||
|
slot: &'a str,
|
||||||
|
},
|
||||||
|
Send {
|
||||||
|
slot: &'a str,
|
||||||
|
binding: &'a (dyn Any + Send + Sync),
|
||||||
|
request: &'a http::Request,
|
||||||
|
},
|
||||||
|
Log,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// No default implementation is supplied. Test providers belong to test targets.
|
||||||
|
///
|
||||||
|
/// `authorize` must check the current installation/artifact/plan/subject and
|
||||||
|
/// operation against live owner policy. `open`/`send` must also enforce concrete
|
||||||
|
/// binding policy, secret handling and effect receipts at dispatch time. The
|
||||||
|
/// executor only enforces static package and invocation ceilings. All waits are
|
||||||
|
/// dropped on cancellation/deadline; providers must not leave detached work.
|
||||||
|
#[async_trait]
|
||||||
|
pub trait Host: Send + Sync {
|
||||||
|
async fn authorize(
|
||||||
|
&self,
|
||||||
|
context: &InvocationContext,
|
||||||
|
operation: Operation<'_>,
|
||||||
|
) -> Result<(), errors::HostError>;
|
||||||
|
async fn open(
|
||||||
|
&self,
|
||||||
|
context: &InvocationContext,
|
||||||
|
slot: &str,
|
||||||
|
) -> Result<Arc<dyn Any + Send + Sync>, errors::HostError>;
|
||||||
|
async fn send(
|
||||||
|
&self,
|
||||||
|
context: &InvocationContext,
|
||||||
|
binding: &(dyn Any + Send + Sync),
|
||||||
|
request: http::Request,
|
||||||
|
) -> Result<http::Response, errors::HostError>;
|
||||||
|
async fn log(
|
||||||
|
&self,
|
||||||
|
context: &InvocationContext,
|
||||||
|
entry: logging::Entry,
|
||||||
|
) -> Result<(), errors::HostError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn error(code: errors::Code, message: &str) -> errors::HostError {
|
||||||
|
errors::HostError {
|
||||||
|
code,
|
||||||
|
message: message.into(),
|
||||||
|
diagnostic_id: String::new(),
|
||||||
|
effect: errors::EffectObservation::NotDispatched,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
#![doc = include_str!("../README.md")]
|
||||||
|
|
||||||
|
pub mod bindings {
|
||||||
|
wasmtime::component::bindgen!({
|
||||||
|
path: "../../resources/plugin/wit",
|
||||||
|
world: "https-logged-tool-plugin",
|
||||||
|
imports: { default: async | trappable },
|
||||||
|
exports: { default: async },
|
||||||
|
with: { "yoi:host/connections/connection": crate::host::Connection },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
mod executor;
|
||||||
|
pub mod host;
|
||||||
|
mod sandbox;
|
||||||
|
pub use executor::*;
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
//! Aggregate Store budgets: Wasmtime's built-in memory/table sizes are per
|
||||||
|
//! object, so count accepted growth across all objects as well. Nothing in a
|
||||||
|
//! Tool Store outlives that invocation; no cross-Store accounting is retained.
|
||||||
|
use crate::Limits;
|
||||||
|
use wasmtime::{ResourceLimiter, StoreLimits, StoreLimitsBuilder};
|
||||||
|
|
||||||
|
pub(crate) struct SandboxLimits {
|
||||||
|
inner: StoreLimits,
|
||||||
|
memory: Budget,
|
||||||
|
table: Budget,
|
||||||
|
}
|
||||||
|
struct Budget {
|
||||||
|
used: usize,
|
||||||
|
last_growth: usize,
|
||||||
|
ceiling: usize,
|
||||||
|
}
|
||||||
|
impl Budget {
|
||||||
|
fn new(ceiling: usize) -> Self {
|
||||||
|
Self {
|
||||||
|
used: 0,
|
||||||
|
last_growth: 0,
|
||||||
|
ceiling,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn reserve(&mut self, current: usize, desired: usize, kind: &str) -> wasmtime::Result<()> {
|
||||||
|
let growth = desired.saturating_sub(current);
|
||||||
|
if growth > self.ceiling.saturating_sub(self.used) {
|
||||||
|
return Err(wasmtime::Error::msg(format!(
|
||||||
|
"aggregate {kind} budget exceeded"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
self.used += growth;
|
||||||
|
self.last_growth = growth;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
fn failed(&mut self) {
|
||||||
|
self.used -= self.last_growth;
|
||||||
|
self.last_growth = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl SandboxLimits {
|
||||||
|
pub(crate) fn new(limits: &Limits) -> Self {
|
||||||
|
Self {
|
||||||
|
inner: StoreLimitsBuilder::new()
|
||||||
|
.memory_size(limits.memory_bytes)
|
||||||
|
.table_elements(limits.table_elements)
|
||||||
|
.instances(limits.instances)
|
||||||
|
.memories(limits.memories)
|
||||||
|
.tables(limits.tables)
|
||||||
|
.trap_on_grow_failure(true)
|
||||||
|
.build(),
|
||||||
|
memory: Budget::new(limits.memory_bytes),
|
||||||
|
table: Budget::new(limits.table_elements),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl ResourceLimiter for SandboxLimits {
|
||||||
|
fn memory_growing(
|
||||||
|
&mut self,
|
||||||
|
current: usize,
|
||||||
|
desired: usize,
|
||||||
|
maximum: Option<usize>,
|
||||||
|
) -> wasmtime::Result<bool> {
|
||||||
|
if !self.inner.memory_growing(current, desired, maximum)? {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
self.memory.reserve(current, desired, "memory")?;
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
fn table_growing(
|
||||||
|
&mut self,
|
||||||
|
current: usize,
|
||||||
|
desired: usize,
|
||||||
|
maximum: Option<usize>,
|
||||||
|
) -> wasmtime::Result<bool> {
|
||||||
|
if !self.inner.table_growing(current, desired, maximum)? {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
self.table.reserve(current, desired, "table")?;
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
fn memory_grow_failed(&mut self, error: wasmtime::Error) -> wasmtime::Result<()> {
|
||||||
|
self.memory.failed();
|
||||||
|
self.inner.memory_grow_failed(error)
|
||||||
|
}
|
||||||
|
fn table_grow_failed(&mut self, error: wasmtime::Error) -> wasmtime::Result<()> {
|
||||||
|
self.table.failed();
|
||||||
|
self.inner.table_grow_failed(error)
|
||||||
|
}
|
||||||
|
fn instances(&self) -> usize {
|
||||||
|
self.inner.instances()
|
||||||
|
}
|
||||||
|
fn memories(&self) -> usize {
|
||||||
|
self.inner.memories()
|
||||||
|
}
|
||||||
|
fn tables(&self) -> usize {
|
||||||
|
self.inner.tables()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,952 @@
|
|||||||
|
//! Actual Component -> Wasmtime -> async Host adapter integration, no network.
|
||||||
|
//! T-791 binaries exercise the unmodified PDK; the adversarial Rust guest is built
|
||||||
|
//! once with wasm32-unknown-unknown std and offline Cargo, as in the PDK suite.
|
||||||
|
//! Provider authority/HTTP are substituted here, not the executor or WIT boundary.
|
||||||
|
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use manifest::plugin::{self, CanonicalPluginArtifact, PluginPackageLimits, PluginSourceKind};
|
||||||
|
use std::{
|
||||||
|
any::Any,
|
||||||
|
collections::BTreeSet,
|
||||||
|
fs,
|
||||||
|
path::Path,
|
||||||
|
process::Command,
|
||||||
|
sync::{
|
||||||
|
Arc, Mutex, OnceLock,
|
||||||
|
atomic::{AtomicUsize, Ordering},
|
||||||
|
},
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
use tokio::{sync::Semaphore, time::Instant};
|
||||||
|
use yoi_plugin_runtime::{
|
||||||
|
host::{errors, http, logging, *},
|
||||||
|
*,
|
||||||
|
};
|
||||||
|
|
||||||
|
const PURE: &str =
|
||||||
|
include_str!("../../../resources/plugin/templates/rust-component-tool/plugin.toml");
|
||||||
|
const HTTPS: &str =
|
||||||
|
include_str!("../../../resources/plugin/templates/rust-component-https/plugin.toml");
|
||||||
|
const PURE_COMPONENT: &[u8] =
|
||||||
|
include_bytes!("../../plugin-pdk/tests/fixtures/tool-v2.component.wasm");
|
||||||
|
const HTTPS_COMPONENT: &[u8] =
|
||||||
|
include_bytes!("../../plugin-pdk/tests/fixtures/https-tool-v2.component.wasm");
|
||||||
|
|
||||||
|
fn artifact(
|
||||||
|
manifest: &str,
|
||||||
|
bytes: &[u8],
|
||||||
|
) -> Result<CanonicalPluginArtifact, plugin::PluginDiagnostic> {
|
||||||
|
let dir = tempfile::tempdir().unwrap();
|
||||||
|
fs::write(dir.path().join("plugin.toml"), manifest).unwrap();
|
||||||
|
fs::write(dir.path().join("plugin.component.wasm"), bytes).unwrap();
|
||||||
|
let package = plugin::read_plugin_directory(
|
||||||
|
dir.path(),
|
||||||
|
PluginSourceKind::Explicit,
|
||||||
|
&PluginPackageLimits::default(),
|
||||||
|
)?;
|
||||||
|
plugin::pack_plugin_package(&package, &PluginPackageLimits::default())
|
||||||
|
}
|
||||||
|
fn compile(executor: &Executor, artifact: &CanonicalPluginArtifact) -> CompiledTool {
|
||||||
|
executor
|
||||||
|
.compile(
|
||||||
|
&artifact.bytes,
|
||||||
|
&artifact.package_digest,
|
||||||
|
&artifact.artifact_digest,
|
||||||
|
artifact.artifact_size,
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
fn guest() -> &'static [u8] {
|
||||||
|
static COMPONENT: OnceLock<Vec<u8>> = OnceLock::new();
|
||||||
|
COMPONENT.get_or_init(|| {
|
||||||
|
let root = Path::new(env!("CARGO_MANIFEST_DIR")).join("../..");
|
||||||
|
let target = root.join("target/plugin-executor-guest");
|
||||||
|
let output = Command::new(env!("CARGO"))
|
||||||
|
.args([
|
||||||
|
"build",
|
||||||
|
"--locked",
|
||||||
|
"--offline",
|
||||||
|
"--release",
|
||||||
|
"--target",
|
||||||
|
"wasm32-unknown-unknown",
|
||||||
|
"--manifest-path",
|
||||||
|
])
|
||||||
|
.arg(Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/guest/Cargo.toml"))
|
||||||
|
.arg("--target-dir")
|
||||||
|
.arg(&target)
|
||||||
|
.output()
|
||||||
|
.unwrap();
|
||||||
|
assert!(
|
||||||
|
output.status.success(),
|
||||||
|
"{}",
|
||||||
|
String::from_utf8_lossy(&output.stderr)
|
||||||
|
);
|
||||||
|
let core = fs::read(target.join("wasm32-unknown-unknown/release/executor_test_guest.wasm"))
|
||||||
|
.unwrap();
|
||||||
|
wit_component::ComponentEncoder::default()
|
||||||
|
.module(&core)
|
||||||
|
.unwrap()
|
||||||
|
.validate(true)
|
||||||
|
.encode()
|
||||||
|
.unwrap()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
fn guest_manifest() -> String {
|
||||||
|
HTTPS.replace("https-tool-plugin@", "https-logged-tool-plugin@")
|
||||||
|
.replace("\"yoi:host/http@2.0.0\"]", "\"yoi:host/http@2.0.0\", \"yoi:host/logging@2.0.0\"]")
|
||||||
|
.replace("requested_grants = [", "requested_grants = [{ kind = \"logging\" }, ")
|
||||||
|
.replace("input_schema = { type = \"object\", properties = {}, additionalProperties = false }", "input_schema = { type = \"string\" }")
|
||||||
|
.replace("output_schema = { type = \"object\", properties = { status = { type = \"integer\" }, body_bytes = { type = \"integer\" } }, required = [\"status\", \"body_bytes\"], additionalProperties = false }", "output_schema = { type = \"object\", properties = { calls = { type = \"integer\" } }, required = [\"calls\"], additionalProperties = false }")
|
||||||
|
.replace("\"failed\"]", "\"failed\", \"typed\", \"host\"]")
|
||||||
|
}
|
||||||
|
fn context(component: &CompiledTool, id: &str) -> InvocationContext {
|
||||||
|
InvocationContext {
|
||||||
|
workspace: "workspace-a".into(),
|
||||||
|
installation: "installed-a".into(),
|
||||||
|
plan: "exact-plan".into(),
|
||||||
|
subject: "worker-a".into(),
|
||||||
|
invocation_id: id.into(),
|
||||||
|
artifact: component.identity().clone(),
|
||||||
|
tool_name: "example_status".into(),
|
||||||
|
grants: Grants {
|
||||||
|
logging: true,
|
||||||
|
connection_slots: BTreeSet::from(["api".into()]),
|
||||||
|
},
|
||||||
|
deadline: Instant::now() + Duration::from_secs(10),
|
||||||
|
cancellation: CancellationToken::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq)]
|
||||||
|
enum Behavior {
|
||||||
|
Respond,
|
||||||
|
Wait,
|
||||||
|
CancelAtResponse,
|
||||||
|
DenyAdmission,
|
||||||
|
DenySend,
|
||||||
|
DenyPublish,
|
||||||
|
}
|
||||||
|
struct TestHost {
|
||||||
|
behavior: Behavior,
|
||||||
|
entered: Semaphore,
|
||||||
|
release: Semaphore,
|
||||||
|
active: Arc<AtomicUsize>,
|
||||||
|
resources: Arc<AtomicUsize>,
|
||||||
|
sends: AtomicUsize,
|
||||||
|
opens: AtomicUsize,
|
||||||
|
logs: Mutex<Vec<String>>,
|
||||||
|
admissions: AtomicUsize,
|
||||||
|
publications: AtomicUsize,
|
||||||
|
tokens: Mutex<Vec<CancellationToken>>,
|
||||||
|
}
|
||||||
|
impl TestHost {
|
||||||
|
fn new(behavior: Behavior) -> Arc<Self> {
|
||||||
|
Arc::new(Self {
|
||||||
|
behavior,
|
||||||
|
entered: Semaphore::new(0),
|
||||||
|
release: Semaphore::new(0),
|
||||||
|
active: Arc::default(),
|
||||||
|
resources: Arc::default(),
|
||||||
|
sends: AtomicUsize::new(0),
|
||||||
|
opens: AtomicUsize::new(0),
|
||||||
|
logs: Mutex::default(),
|
||||||
|
admissions: AtomicUsize::new(0),
|
||||||
|
publications: AtomicUsize::new(0),
|
||||||
|
tokens: Mutex::default(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
fn released(&self) {
|
||||||
|
assert_eq!(self.active.load(Ordering::SeqCst), 0);
|
||||||
|
assert_eq!(self.resources.load(Ordering::SeqCst), 0);
|
||||||
|
assert!(
|
||||||
|
self.tokens
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.iter()
|
||||||
|
.all(CancellationToken::is_cancelled)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
struct Counter(Arc<AtomicUsize>);
|
||||||
|
impl Counter {
|
||||||
|
fn new(counter: &Arc<AtomicUsize>) -> Self {
|
||||||
|
counter.fetch_add(1, Ordering::SeqCst);
|
||||||
|
Self(counter.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Drop for Counter {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.0.fetch_sub(1, Ordering::SeqCst);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
struct Binding {
|
||||||
|
id: String,
|
||||||
|
_counter: Counter,
|
||||||
|
}
|
||||||
|
fn denied() -> errors::HostError {
|
||||||
|
errors::HostError {
|
||||||
|
code: errors::Code::Denied,
|
||||||
|
message: "denied".into(),
|
||||||
|
diagnostic_id: "test-only".into(),
|
||||||
|
effect: errors::EffectObservation::NotDispatched,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[async_trait]
|
||||||
|
impl Host for TestHost {
|
||||||
|
async fn authorize(
|
||||||
|
&self,
|
||||||
|
context: &InvocationContext,
|
||||||
|
operation: Operation<'_>,
|
||||||
|
) -> Result<(), errors::HostError> {
|
||||||
|
let deny = match operation {
|
||||||
|
Operation::Admit => {
|
||||||
|
self.tokens
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.push(context.cancellation.clone());
|
||||||
|
self.admissions.fetch_add(1, Ordering::SeqCst);
|
||||||
|
self.behavior == Behavior::DenyAdmission
|
||||||
|
}
|
||||||
|
Operation::Publish => {
|
||||||
|
self.publications.fetch_add(1, Ordering::SeqCst);
|
||||||
|
self.behavior == Behavior::DenyPublish
|
||||||
|
}
|
||||||
|
Operation::Send { .. } => self.behavior == Behavior::DenySend,
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
if deny { Err(denied()) } else { Ok(()) }
|
||||||
|
}
|
||||||
|
async fn open(
|
||||||
|
&self,
|
||||||
|
context: &InvocationContext,
|
||||||
|
_: &str,
|
||||||
|
) -> Result<Arc<dyn Any + Send + Sync>, errors::HostError> {
|
||||||
|
self.opens.fetch_add(1, Ordering::SeqCst);
|
||||||
|
Ok(Arc::new(Binding {
|
||||||
|
id: context.invocation_id.clone(),
|
||||||
|
_counter: Counter::new(&self.resources),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
async fn send(
|
||||||
|
&self,
|
||||||
|
context: &InvocationContext,
|
||||||
|
binding: &(dyn Any + Send + Sync),
|
||||||
|
request: http::Request,
|
||||||
|
) -> Result<http::Response, errors::HostError> {
|
||||||
|
assert_eq!(
|
||||||
|
binding.downcast_ref::<Binding>().unwrap().id,
|
||||||
|
context.invocation_id
|
||||||
|
);
|
||||||
|
assert_eq!(request.path, "/status");
|
||||||
|
assert!(request.timeout_ms <= 15_000);
|
||||||
|
let _active = Counter::new(&self.active);
|
||||||
|
self.sends.fetch_add(1, Ordering::SeqCst);
|
||||||
|
self.entered.add_permits(1);
|
||||||
|
if self.behavior == Behavior::Wait {
|
||||||
|
self.release.acquire().await.unwrap().forget();
|
||||||
|
}
|
||||||
|
if self.behavior == Behavior::CancelAtResponse {
|
||||||
|
context.cancellation.cancel();
|
||||||
|
}
|
||||||
|
Ok(http::Response {
|
||||||
|
status: 201,
|
||||||
|
headers: vec![],
|
||||||
|
body: context.invocation_id.as_bytes().to_vec(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
async fn log(
|
||||||
|
&self,
|
||||||
|
context: &InvocationContext,
|
||||||
|
entry: logging::Entry,
|
||||||
|
) -> Result<(), errors::HostError> {
|
||||||
|
assert_eq!(entry.event_code, "count");
|
||||||
|
self.logs
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.push(context.invocation_id.clone());
|
||||||
|
self.entered.add_permits(1);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn executor(limits: Limits) -> Executor {
|
||||||
|
Executor::new(limits).unwrap()
|
||||||
|
}
|
||||||
|
fn adversary(ex: &Executor) -> CompiledTool {
|
||||||
|
compile(ex, &artifact(&guest_manifest(), guest()).unwrap())
|
||||||
|
}
|
||||||
|
async fn call(
|
||||||
|
ex: &Executor,
|
||||||
|
component: &CompiledTool,
|
||||||
|
host: Arc<TestHost>,
|
||||||
|
mode: &str,
|
||||||
|
) -> Result<InvocationResult, ExecutionError> {
|
||||||
|
ex.invoke(
|
||||||
|
component,
|
||||||
|
context(component, mode),
|
||||||
|
host,
|
||||||
|
"example_status",
|
||||||
|
&format!("\"{mode}\""),
|
||||||
|
"{}",
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn pdk_pure_tool_preserves_typed_results_and_requires_live_admission() {
|
||||||
|
let ex = executor(Limits::default());
|
||||||
|
let component = compile(&ex, &artifact(PURE, PURE_COMPONENT).unwrap());
|
||||||
|
let host = TestHost::new(Behavior::Respond);
|
||||||
|
let result = ex
|
||||||
|
.invoke(
|
||||||
|
&component,
|
||||||
|
echo_context(&component, "pure"),
|
||||||
|
host.clone(),
|
||||||
|
"example_echo",
|
||||||
|
r#"{"text":"hello"}"#,
|
||||||
|
"{}",
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let value: serde_json::Value =
|
||||||
|
serde_json::from_str(result.output.structured_json.as_ref().unwrap()).unwrap();
|
||||||
|
assert_eq!(value["text"], "hello");
|
||||||
|
assert_eq!(host.admissions.load(Ordering::SeqCst), 1);
|
||||||
|
assert_eq!(host.publications.load(Ordering::SeqCst), 1);
|
||||||
|
let error = ex
|
||||||
|
.invoke(
|
||||||
|
&component,
|
||||||
|
echo_context(&component, "empty"),
|
||||||
|
host.clone(),
|
||||||
|
"example_echo",
|
||||||
|
r#"{"text":""}"#,
|
||||||
|
"{}",
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap_err();
|
||||||
|
assert!(matches!(error, ExecutionError::Guest { code, .. } if code == "invalid_input"));
|
||||||
|
for behavior in [Behavior::DenyAdmission, Behavior::DenyPublish] {
|
||||||
|
assert!(matches!(
|
||||||
|
ex.invoke(
|
||||||
|
&component,
|
||||||
|
echo_context(&component, "denied"),
|
||||||
|
TestHost::new(behavior),
|
||||||
|
"example_echo",
|
||||||
|
r#"{"text":"x"}"#,
|
||||||
|
"{}"
|
||||||
|
)
|
||||||
|
.await,
|
||||||
|
Err(ExecutionError::Host(_))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn pdk_https_uses_only_bound_host_capability_and_drops_it() {
|
||||||
|
let ex = executor(Limits::default());
|
||||||
|
let component = compile(&ex, &artifact(HTTPS, HTTPS_COMPONENT).unwrap());
|
||||||
|
let host = TestHost::new(Behavior::Respond);
|
||||||
|
let result = ex
|
||||||
|
.invoke(
|
||||||
|
&component,
|
||||||
|
context(&component, "https"),
|
||||||
|
host.clone(),
|
||||||
|
"example_status",
|
||||||
|
"{}",
|
||||||
|
"{}",
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let value: serde_json::Value =
|
||||||
|
serde_json::from_str(result.output.structured_json.as_ref().unwrap()).unwrap();
|
||||||
|
assert_eq!(value["status"], 201);
|
||||||
|
assert_eq!(host.sends.load(Ordering::SeqCst), 1);
|
||||||
|
host.released();
|
||||||
|
let denied = TestHost::new(Behavior::DenySend);
|
||||||
|
assert!(
|
||||||
|
ex.invoke(
|
||||||
|
&component,
|
||||||
|
context(&component, "no-send"),
|
||||||
|
denied.clone(),
|
||||||
|
"example_status",
|
||||||
|
"{}",
|
||||||
|
"{}"
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.is_err()
|
||||||
|
);
|
||||||
|
assert_eq!(denied.sends.load(Ordering::SeqCst), 0);
|
||||||
|
denied.released();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn fresh_store_isolates_memory_and_logs_are_bounded_not_model_output() {
|
||||||
|
let ex = executor(Limits {
|
||||||
|
log_bytes: 40,
|
||||||
|
..Limits::default()
|
||||||
|
});
|
||||||
|
let component = adversary(&ex);
|
||||||
|
let host = TestHost::new(Behavior::Respond);
|
||||||
|
for _ in 0..2 {
|
||||||
|
let result = call(&ex, &component, host.clone(), "logging")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
result.output.structured_json.as_deref(),
|
||||||
|
Some("{\"calls\":1}")
|
||||||
|
);
|
||||||
|
assert_eq!(result.dropped_logs, 2);
|
||||||
|
assert_eq!(result.output.summary, "logging");
|
||||||
|
}
|
||||||
|
assert_eq!(host.logs.lock().unwrap().len(), 2);
|
||||||
|
for (mode, code) in [("typed", "typed"), ("unknown", "plugin-failed")] {
|
||||||
|
assert!(
|
||||||
|
matches!(call(&ex, &component, host.clone(), mode).await, Err(ExecutionError::Guest { code: actual, .. }) if actual == code)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn undeclared_tool_and_invalid_input_are_rejected_before_admission() {
|
||||||
|
let ex = executor(Limits::default());
|
||||||
|
let component = compile(&ex, &artifact(PURE, PURE_COMPONENT).unwrap());
|
||||||
|
let host = TestHost::new(Behavior::Respond);
|
||||||
|
for (tool, input) in [
|
||||||
|
("missing", "{}"),
|
||||||
|
("example_echo", "{}"),
|
||||||
|
("example_echo", "not-json"),
|
||||||
|
("example_echo", r#"{"text":3}"#),
|
||||||
|
] {
|
||||||
|
assert!(matches!(
|
||||||
|
ex.invoke(
|
||||||
|
&component,
|
||||||
|
echo_context(&component, "bad"),
|
||||||
|
host.clone(),
|
||||||
|
tool,
|
||||||
|
input,
|
||||||
|
"{}"
|
||||||
|
)
|
||||||
|
.await,
|
||||||
|
Err(ExecutionError::InvalidInput(_))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
assert_eq!(host.admissions.load(Ordering::SeqCst), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn malformed_schema_output_and_oversized_results_never_publish_success() {
|
||||||
|
let ex = executor(Limits::default());
|
||||||
|
let component = adversary(&ex);
|
||||||
|
for mode in ["bad-json", "bad-schema", "output", "error-output"] {
|
||||||
|
assert!(matches!(
|
||||||
|
call(&ex, &component, TestHost::new(Behavior::Respond), mode).await,
|
||||||
|
Err(ExecutionError::InvalidOutput(_))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn trap_fuel_memory_and_resource_exhaustion_leave_next_call_fresh() {
|
||||||
|
let ex = executor(Limits {
|
||||||
|
memory_bytes: 4 * 1024 * 1024,
|
||||||
|
resources: 2,
|
||||||
|
fuel: 200_000,
|
||||||
|
..Limits::default()
|
||||||
|
});
|
||||||
|
let component = adversary(&ex);
|
||||||
|
let host = TestHost::new(Behavior::Respond);
|
||||||
|
for mode in ["trap", "loop", "memory", "resources"] {
|
||||||
|
assert!(
|
||||||
|
matches!(
|
||||||
|
call(&ex, &component, host.clone(), mode).await,
|
||||||
|
Err(ExecutionError::Trap(_))
|
||||||
|
),
|
||||||
|
"{mode}"
|
||||||
|
);
|
||||||
|
host.released();
|
||||||
|
assert_eq!(
|
||||||
|
call(&ex, &component, host.clone(), "fresh")
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.output
|
||||||
|
.structured_json
|
||||||
|
.as_deref(),
|
||||||
|
Some("{\"calls\":1}")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn stale_and_foreign_resource_representations_cannot_dispatch_http() {
|
||||||
|
let ex = executor(Limits::default());
|
||||||
|
let component = adversary(&ex);
|
||||||
|
for mode in ["stale", "foreign"] {
|
||||||
|
let host = TestHost::new(Behavior::Respond);
|
||||||
|
assert!(
|
||||||
|
matches!(
|
||||||
|
call(&ex, &component, host.clone(), mode).await,
|
||||||
|
Err(ExecutionError::Trap(_))
|
||||||
|
),
|
||||||
|
"{mode}"
|
||||||
|
);
|
||||||
|
assert_eq!(host.sends.load(Ordering::SeqCst), 0);
|
||||||
|
host.released();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn cancellation_during_host_wait_drops_future_and_call_resources() {
|
||||||
|
let ex = executor(Limits::default());
|
||||||
|
let component = adversary(&ex);
|
||||||
|
let host = TestHost::new(Behavior::Wait);
|
||||||
|
let ctx = context(&component, "cancel-wait");
|
||||||
|
let cancellation = ctx.cancellation.clone();
|
||||||
|
let invocation = ex.invoke(
|
||||||
|
&component,
|
||||||
|
ctx,
|
||||||
|
host.clone(),
|
||||||
|
"example_status",
|
||||||
|
"\"http\"",
|
||||||
|
"{}",
|
||||||
|
);
|
||||||
|
let cancellation_task = async {
|
||||||
|
tokio::time::timeout(Duration::from_secs(5), host.entered.acquire())
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.unwrap()
|
||||||
|
.forget();
|
||||||
|
cancellation.cancel();
|
||||||
|
};
|
||||||
|
let (result, ()) = tokio::join!(invocation, cancellation_task);
|
||||||
|
assert!(matches!(result, Err(ExecutionError::Cancelled)));
|
||||||
|
host.released();
|
||||||
|
host.release.add_permits(1);
|
||||||
|
assert_eq!(host.publications.load(Ordering::SeqCst), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(start_paused = true)]
|
||||||
|
async fn host_wait_uses_common_wall_deadline_and_drops_resources() {
|
||||||
|
let ex = executor(Limits {
|
||||||
|
wall_time: Duration::from_secs(1),
|
||||||
|
..Limits::default()
|
||||||
|
});
|
||||||
|
let component = adversary(&ex);
|
||||||
|
let host = TestHost::new(Behavior::Wait);
|
||||||
|
let invocation = call(&ex, &component, host.clone(), "http");
|
||||||
|
let clock = async {
|
||||||
|
tokio::time::timeout(Duration::from_secs(5), host.entered.acquire())
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.unwrap()
|
||||||
|
.forget();
|
||||||
|
tokio::time::advance(Duration::from_secs(2)).await;
|
||||||
|
};
|
||||||
|
let (result, ()) = tokio::join!(invocation, clock);
|
||||||
|
assert!(matches!(result, Err(ExecutionError::Deadline)));
|
||||||
|
host.released();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn cancelled_late_response_is_not_published() {
|
||||||
|
let ex = executor(Limits::default());
|
||||||
|
let component = adversary(&ex);
|
||||||
|
let host = TestHost::new(Behavior::CancelAtResponse);
|
||||||
|
assert!(matches!(
|
||||||
|
call(&ex, &component, host.clone(), "http").await,
|
||||||
|
Err(ExecutionError::Cancelled)
|
||||||
|
));
|
||||||
|
assert_eq!(host.publications.load(Ordering::SeqCst), 0);
|
||||||
|
host.released();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn concurrent_invocations_never_mix_identity_memory_or_capabilities() {
|
||||||
|
let ex = executor(Limits::default());
|
||||||
|
let component = adversary(&ex);
|
||||||
|
let host = TestHost::new(Behavior::Wait);
|
||||||
|
let a = ex.invoke(
|
||||||
|
&component,
|
||||||
|
context(&component, "a"),
|
||||||
|
host.clone(),
|
||||||
|
"example_status",
|
||||||
|
"\"http\"",
|
||||||
|
"{}",
|
||||||
|
);
|
||||||
|
let b = ex.invoke(
|
||||||
|
&component,
|
||||||
|
context(&component, "b"),
|
||||||
|
host.clone(),
|
||||||
|
"example_status",
|
||||||
|
"\"http\"",
|
||||||
|
"{}",
|
||||||
|
);
|
||||||
|
let release = async {
|
||||||
|
tokio::time::timeout(Duration::from_secs(5), host.entered.acquire())
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.unwrap()
|
||||||
|
.forget();
|
||||||
|
tokio::time::timeout(Duration::from_secs(5), host.entered.acquire())
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.unwrap()
|
||||||
|
.forget();
|
||||||
|
host.release.add_permits(2);
|
||||||
|
};
|
||||||
|
let (a, b, ()) = tokio::join!(a, b, release);
|
||||||
|
assert_eq!(a.unwrap().output.summary, "a");
|
||||||
|
assert_eq!(b.unwrap().output.summary, "b");
|
||||||
|
host.released();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn wrong_import_or_interface_version_is_rejected_by_shared_validator() {
|
||||||
|
assert!(artifact(PURE, HTTPS_COMPONENT).is_err());
|
||||||
|
assert!(artifact(&HTTPS.replace("2.0.0", "1.0.0"), HTTPS_COMPONENT).is_err());
|
||||||
|
let ex = executor(Limits::default());
|
||||||
|
let package = artifact(PURE, PURE_COMPONENT).unwrap();
|
||||||
|
assert!(matches!(
|
||||||
|
ex.compile(
|
||||||
|
&package.bytes,
|
||||||
|
&package.artifact_digest,
|
||||||
|
&package.package_digest,
|
||||||
|
package.artifact_size
|
||||||
|
),
|
||||||
|
Err(ExecutionError::Artifact(_))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn echo_context(component: &CompiledTool, id: &str) -> InvocationContext {
|
||||||
|
let mut c = context(component, id);
|
||||||
|
c.tool_name = "example_echo".into();
|
||||||
|
c
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generated_component(transform: impl FnOnce(String) -> String) -> Vec<u8> {
|
||||||
|
let mut resolve = wit_parser::Resolve::default();
|
||||||
|
let package = resolve
|
||||||
|
.push_dir(Path::new(env!("CARGO_MANIFEST_DIR")).join("../../resources/plugin/wit"))
|
||||||
|
.unwrap()
|
||||||
|
.0;
|
||||||
|
let world = resolve
|
||||||
|
.select_world(&[package], Some("https-logged-tool-plugin"))
|
||||||
|
.unwrap();
|
||||||
|
let bytes =
|
||||||
|
wit_component::dummy_module(&resolve, world, wit_parser::ManglingAndAbi::Standard32);
|
||||||
|
let wat = transform(wasmprinter::print_bytes(bytes).unwrap());
|
||||||
|
let mut core = wat::parse_str(wat).unwrap();
|
||||||
|
wit_component::embed_component_metadata(
|
||||||
|
&mut core,
|
||||||
|
&resolve,
|
||||||
|
world,
|
||||||
|
wit_component::StringEncoding::UTF8,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
wit_component::ComponentEncoder::default()
|
||||||
|
.module(&core)
|
||||||
|
.unwrap()
|
||||||
|
.validate(true)
|
||||||
|
.encode()
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
fn startup_component(logging: bool) -> Vec<u8> {
|
||||||
|
generated_component(|wat| {
|
||||||
|
// Mutate only a generated initializer, retaining exact canonical types.
|
||||||
|
// These calls run during instantiation, not the Tool export.
|
||||||
|
let body = if logging {
|
||||||
|
"i32.const 0 ".repeat(7) + "i32.const 128 call 3"
|
||||||
|
} else {
|
||||||
|
"i32.const 0 i32.const 0 i32.const 128 call 0".into()
|
||||||
|
};
|
||||||
|
let needle = "(func (;7;) (type 6))";
|
||||||
|
assert!(wat.contains(needle));
|
||||||
|
wat.replace("(memory (;0;) 0)", "(memory (;0;) 1)")
|
||||||
|
.replace(needle, &format!("(func (;7;) (type 6) {body})"))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn instantiate_without_export_context_rejects_connections_and_logging() {
|
||||||
|
let ex = executor(Limits::default());
|
||||||
|
for logging in [false, true] {
|
||||||
|
let component = compile(
|
||||||
|
&ex,
|
||||||
|
&artifact(&guest_manifest(), &startup_component(logging)).unwrap(),
|
||||||
|
);
|
||||||
|
let host = TestHost::new(Behavior::Respond);
|
||||||
|
let error = call(&ex, &component, host.clone(), "fresh")
|
||||||
|
.await
|
||||||
|
.unwrap_err();
|
||||||
|
assert!(matches!(error, ExecutionError::Trap(_)));
|
||||||
|
assert_eq!(host.opens.load(Ordering::SeqCst), 0);
|
||||||
|
assert_eq!(host.sends.load(Ordering::SeqCst), 0);
|
||||||
|
assert!(host.logs.lock().unwrap().is_empty());
|
||||||
|
host.released();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn host_context_cannot_admit_another_artifact_or_tool() {
|
||||||
|
let ex = executor(Limits::default());
|
||||||
|
let component = compile(&ex, &artifact(PURE, PURE_COMPONENT).unwrap());
|
||||||
|
let host = TestHost::new(Behavior::Respond);
|
||||||
|
for changed_artifact in [false, true] {
|
||||||
|
let mut context = echo_context(&component, "bad-bound-context");
|
||||||
|
if changed_artifact {
|
||||||
|
context.artifact.package_digest.push('x');
|
||||||
|
} else {
|
||||||
|
context.tool_name = "different-tool".into();
|
||||||
|
}
|
||||||
|
assert!(matches!(
|
||||||
|
ex.invoke(
|
||||||
|
&component,
|
||||||
|
context,
|
||||||
|
host.clone(),
|
||||||
|
"example_echo",
|
||||||
|
r#"{"text":"x"}"#,
|
||||||
|
"{}"
|
||||||
|
)
|
||||||
|
.await,
|
||||||
|
Err(ExecutionError::InvalidInput(_))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
assert_eq!(host.admissions.load(Ordering::SeqCst), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn initial_memory_table_and_instance_limits_apply_at_instantiation() {
|
||||||
|
for limits in [
|
||||||
|
Limits {
|
||||||
|
memory_bytes: 0,
|
||||||
|
..Limits::default()
|
||||||
|
},
|
||||||
|
Limits {
|
||||||
|
table_elements: 0,
|
||||||
|
..Limits::default()
|
||||||
|
},
|
||||||
|
Limits {
|
||||||
|
instances: 0,
|
||||||
|
..Limits::default()
|
||||||
|
},
|
||||||
|
Limits {
|
||||||
|
memories: 0,
|
||||||
|
..Limits::default()
|
||||||
|
},
|
||||||
|
Limits {
|
||||||
|
tables: 0,
|
||||||
|
..Limits::default()
|
||||||
|
},
|
||||||
|
] {
|
||||||
|
let ex = executor(limits);
|
||||||
|
let component = adversary(&ex);
|
||||||
|
assert!(matches!(
|
||||||
|
call(&ex, &component, TestHost::new(Behavior::Respond), "fresh").await,
|
||||||
|
Err(ExecutionError::Trap(_))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(start_paused = true)]
|
||||||
|
async fn running_cpu_loop_obeys_wall_deadline_without_consuming_all_fuel() {
|
||||||
|
let ex = executor(Limits {
|
||||||
|
wall_time: Duration::from_secs(1),
|
||||||
|
..Limits::default()
|
||||||
|
});
|
||||||
|
let component = adversary(&ex);
|
||||||
|
let host = TestHost::new(Behavior::Respond);
|
||||||
|
let invocation = call(&ex, &component, host.clone(), "wall-loop");
|
||||||
|
let clock = async {
|
||||||
|
tokio::time::timeout(Duration::from_secs(5), host.entered.acquire())
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.unwrap()
|
||||||
|
.forget();
|
||||||
|
tokio::time::advance(Duration::from_secs(2)).await;
|
||||||
|
};
|
||||||
|
let (result, ()) = tokio::join!(invocation, clock);
|
||||||
|
assert!(matches!(result, Err(ExecutionError::Deadline)));
|
||||||
|
assert_eq!(host.logs.lock().unwrap().len(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn missing_grants_and_host_operation_ceiling_never_dispatch() {
|
||||||
|
let ex = executor(Limits::default());
|
||||||
|
let component = adversary(&ex);
|
||||||
|
for mode in ["logging", "http"] {
|
||||||
|
let host = TestHost::new(Behavior::Respond);
|
||||||
|
let mut context = context(&component, mode);
|
||||||
|
context.grants = Grants::default();
|
||||||
|
assert!(matches!(
|
||||||
|
ex.invoke(
|
||||||
|
&component,
|
||||||
|
context,
|
||||||
|
host.clone(),
|
||||||
|
"example_status",
|
||||||
|
&format!("\"{mode}\""),
|
||||||
|
"{}"
|
||||||
|
)
|
||||||
|
.await,
|
||||||
|
Err(ExecutionError::Guest { .. })
|
||||||
|
));
|
||||||
|
assert_eq!(host.opens.load(Ordering::SeqCst), 0);
|
||||||
|
assert!(host.logs.lock().unwrap().is_empty());
|
||||||
|
}
|
||||||
|
let ex = executor(Limits {
|
||||||
|
host_operations: 1,
|
||||||
|
..Limits::default()
|
||||||
|
});
|
||||||
|
let component = adversary(&ex);
|
||||||
|
let host = TestHost::new(Behavior::Respond);
|
||||||
|
assert!(call(&ex, &component, host.clone(), "http").await.is_err());
|
||||||
|
assert_eq!(host.opens.load(Ordering::SeqCst), 1);
|
||||||
|
assert_eq!(host.sends.load(Ordering::SeqCst), 0);
|
||||||
|
host.released();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn oversized_input_and_config_are_rejected_before_admission() {
|
||||||
|
let ex = executor(Limits {
|
||||||
|
payload_bytes: 32,
|
||||||
|
config_bytes: 16,
|
||||||
|
..Limits::default()
|
||||||
|
});
|
||||||
|
let component = compile(&ex, &artifact(PURE, PURE_COMPONENT).unwrap());
|
||||||
|
let host = TestHost::new(Behavior::Respond);
|
||||||
|
for (input, config) in [
|
||||||
|
(format!(r#"{{"text":"{}"}}"#, "x".repeat(32)), "{}".into()),
|
||||||
|
(
|
||||||
|
r#"{"text":"ok"}"#.into(),
|
||||||
|
format!(r#"{{"x":"{}"}}"#, "x".repeat(16)),
|
||||||
|
),
|
||||||
|
] {
|
||||||
|
assert!(matches!(
|
||||||
|
ex.invoke(
|
||||||
|
&component,
|
||||||
|
echo_context(&component, "size"),
|
||||||
|
host.clone(),
|
||||||
|
"example_echo",
|
||||||
|
&input,
|
||||||
|
&config
|
||||||
|
)
|
||||||
|
.await,
|
||||||
|
Err(ExecutionError::InvalidInput(_))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
assert_eq!(host.admissions.load(Ordering::SeqCst), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(start_paused = true)]
|
||||||
|
async fn http_wait_has_its_own_shorter_deadline_without_extending_the_call() {
|
||||||
|
let ex = executor(Limits::default());
|
||||||
|
let component = adversary(&ex);
|
||||||
|
let host = TestHost::new(Behavior::Wait);
|
||||||
|
let mut context = context(&component, "request-timeout");
|
||||||
|
context.deadline = Instant::now() + Duration::from_secs(30);
|
||||||
|
let invocation = ex.invoke(
|
||||||
|
&component,
|
||||||
|
context,
|
||||||
|
host.clone(),
|
||||||
|
"example_status",
|
||||||
|
"\"http\"",
|
||||||
|
"{}",
|
||||||
|
);
|
||||||
|
let clock = async {
|
||||||
|
tokio::time::timeout(Duration::from_secs(5), host.entered.acquire())
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.unwrap()
|
||||||
|
.forget();
|
||||||
|
tokio::time::advance(Duration::from_secs(16)).await;
|
||||||
|
};
|
||||||
|
let (result, ()) = tokio::join!(invocation, clock);
|
||||||
|
assert!(
|
||||||
|
matches!(&result, Err(ExecutionError::Guest { code, message }) if code == "host" && message == "deadline-exceeded:indeterminate"),
|
||||||
|
"{result:?}"
|
||||||
|
);
|
||||||
|
host.released();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn bounded_http_response_is_not_truncated_into_success() {
|
||||||
|
let ex = executor(Limits {
|
||||||
|
http_body_bytes: 1,
|
||||||
|
..Limits::default()
|
||||||
|
});
|
||||||
|
let component = adversary(&ex);
|
||||||
|
let host = TestHost::new(Behavior::Respond);
|
||||||
|
let result = call(&ex, &component, host.clone(), "http").await;
|
||||||
|
assert!(
|
||||||
|
matches!(&result, Err(ExecutionError::Guest { code, message }) if code == "host" && message == "quota-exceeded:response-observed"),
|
||||||
|
"{result:?}"
|
||||||
|
);
|
||||||
|
assert_eq!(host.sends.load(Ordering::SeqCst), 1);
|
||||||
|
host.released();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn dropping_invoke_cancels_only_its_child_lifetime_and_reclaims_store() {
|
||||||
|
let ex = executor(Limits::default());
|
||||||
|
let component = adversary(&ex);
|
||||||
|
let host = TestHost::new(Behavior::Wait);
|
||||||
|
let context = context(&component, "dropped");
|
||||||
|
let parent_token = context.cancellation.clone();
|
||||||
|
let mut invocation = Box::pin(ex.invoke(
|
||||||
|
&component,
|
||||||
|
context,
|
||||||
|
host.clone(),
|
||||||
|
"example_status",
|
||||||
|
"\"http\"",
|
||||||
|
"{}",
|
||||||
|
));
|
||||||
|
tokio::select! {
|
||||||
|
result = &mut invocation => panic!("unexpected completion: {result:?}"),
|
||||||
|
entered = tokio::time::timeout(Duration::from_secs(5), host.entered.acquire()) => entered.unwrap().unwrap().forget(),
|
||||||
|
}
|
||||||
|
drop(invocation);
|
||||||
|
assert!(!parent_token.is_cancelled());
|
||||||
|
host.released();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn multiple_memories_and_tables_cannot_multiply_store_budgets() {
|
||||||
|
for memory in [false, true] {
|
||||||
|
let ex = executor(Limits {
|
||||||
|
memory_bytes: 4 * 1024 * 1024,
|
||||||
|
table_elements: 100,
|
||||||
|
..Limits::default()
|
||||||
|
});
|
||||||
|
let bytes = generated_component(|wat| {
|
||||||
|
wat.replace(
|
||||||
|
"(memory (;0;) 0)",
|
||||||
|
if memory {
|
||||||
|
"(memory (;0;) 40) (memory 40)"
|
||||||
|
} else {
|
||||||
|
"(memory (;0;) 0) (table 80 funcref) (table 80 funcref)"
|
||||||
|
},
|
||||||
|
)
|
||||||
|
});
|
||||||
|
let component = compile(&ex, &artifact(&guest_manifest(), &bytes).unwrap());
|
||||||
|
let error = call(&ex, &component, TestHost::new(Behavior::Respond), "fresh")
|
||||||
|
.await
|
||||||
|
.unwrap_err();
|
||||||
|
let expected = if memory {
|
||||||
|
"aggregate memory budget exceeded"
|
||||||
|
} else {
|
||||||
|
"aggregate table budget exceeded"
|
||||||
|
};
|
||||||
|
assert!(
|
||||||
|
matches!(&error, ExecutionError::Trap(message) if message.contains(expected)),
|
||||||
|
"{error:?}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn operational_limits_cannot_raise_the_supported_ceiling() {
|
||||||
|
assert!(
|
||||||
|
Executor::new(Limits {
|
||||||
|
wall_time: Duration::MAX,
|
||||||
|
..Limits::default()
|
||||||
|
})
|
||||||
|
.is_err()
|
||||||
|
);
|
||||||
|
}
|
||||||
+367
@@ -0,0 +1,367 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anyhow"
|
||||||
|
version = "1.0.104"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "2.13.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "equivalent"
|
||||||
|
version = "1.0.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "00d174d5400e5e8fd687ad1049e2f578285fa914201b1af7e8b112a4546bd826"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "executor-test-guest"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"yoi-plugin-pdk",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "foldhash"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashbrown"
|
||||||
|
version = "0.15.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
|
||||||
|
dependencies = [
|
||||||
|
"foldhash",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashbrown"
|
||||||
|
version = "0.17.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "heck"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "id-arena"
|
||||||
|
version = "2.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "indexmap"
|
||||||
|
version = "2.14.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855"
|
||||||
|
dependencies = [
|
||||||
|
"equivalent",
|
||||||
|
"hashbrown 0.17.1",
|
||||||
|
"serde",
|
||||||
|
"serde_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itoa"
|
||||||
|
version = "1.0.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "leb128fmt"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "log"
|
||||||
|
version = "0.4.34"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memchr"
|
||||||
|
version = "2.8.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "prettyplease"
|
||||||
|
version = "0.2.37"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"syn 2.0.119",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.107"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.47"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "semver"
|
||||||
|
version = "1.0.28"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde"
|
||||||
|
version = "1.0.229"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
|
||||||
|
dependencies = [
|
||||||
|
"serde_core",
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_core"
|
||||||
|
version = "1.0.229"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
|
||||||
|
dependencies = [
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_derive"
|
||||||
|
version = "1.0.229"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 3.0.9",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_json"
|
||||||
|
version = "1.0.154"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6"
|
||||||
|
dependencies = [
|
||||||
|
"itoa",
|
||||||
|
"memchr",
|
||||||
|
"serde",
|
||||||
|
"serde_core",
|
||||||
|
"zmij",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "2.0.119"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "3.0.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d78c8dee4c7bf0e14673097256fed6142ce9d3b85a408189d07482442145823b"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror"
|
||||||
|
version = "2.0.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror-impl",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror-impl"
|
||||||
|
version = "2.0.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 3.0.9",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "1.0.27"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-xid"
|
||||||
|
version = "0.2.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-encoder"
|
||||||
|
version = "0.244.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319"
|
||||||
|
dependencies = [
|
||||||
|
"leb128fmt",
|
||||||
|
"wasmparser",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-metadata"
|
||||||
|
version = "0.244.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"indexmap",
|
||||||
|
"wasm-encoder",
|
||||||
|
"wasmparser",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasmparser"
|
||||||
|
version = "0.244.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"hashbrown 0.15.5",
|
||||||
|
"indexmap",
|
||||||
|
"semver",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wit-bindgen"
|
||||||
|
version = "0.51.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"wit-bindgen-rust-macro",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wit-bindgen-core"
|
||||||
|
version = "0.51.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"heck",
|
||||||
|
"wit-parser",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wit-bindgen-rust"
|
||||||
|
version = "0.51.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"heck",
|
||||||
|
"indexmap",
|
||||||
|
"prettyplease",
|
||||||
|
"syn 2.0.119",
|
||||||
|
"wasm-metadata",
|
||||||
|
"wit-bindgen-core",
|
||||||
|
"wit-component",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wit-bindgen-rust-macro"
|
||||||
|
version = "0.51.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"prettyplease",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.119",
|
||||||
|
"wit-bindgen-core",
|
||||||
|
"wit-bindgen-rust",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wit-component"
|
||||||
|
version = "0.244.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"bitflags",
|
||||||
|
"indexmap",
|
||||||
|
"log",
|
||||||
|
"serde",
|
||||||
|
"serde_derive",
|
||||||
|
"serde_json",
|
||||||
|
"wasm-encoder",
|
||||||
|
"wasm-metadata",
|
||||||
|
"wasmparser",
|
||||||
|
"wit-parser",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wit-parser"
|
||||||
|
version = "0.244.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"id-arena",
|
||||||
|
"indexmap",
|
||||||
|
"log",
|
||||||
|
"semver",
|
||||||
|
"serde",
|
||||||
|
"serde_derive",
|
||||||
|
"serde_json",
|
||||||
|
"unicode-xid",
|
||||||
|
"wasmparser",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "yoi-plugin-pdk"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"thiserror",
|
||||||
|
"wit-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zmij"
|
||||||
|
version = "1.0.23"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
[package]
|
||||||
|
name = "executor-test-guest"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[workspace]
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
crate-type = ["cdylib"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
yoi-plugin-pdk = { path = "../../../plugin-pdk" }
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
use yoi_plugin_pdk::wit_bindgen;
|
||||||
|
wit_bindgen::generate!({
|
||||||
|
path: "../../../../resources/plugin/wit",
|
||||||
|
world: "https-logged-tool-plugin",
|
||||||
|
generate_all,
|
||||||
|
runtime_path: "yoi_plugin_pdk::wit_bindgen::rt",
|
||||||
|
});
|
||||||
|
use exports::yoi::plugin::tool::{Guest, Invocation, Output, ToolError};
|
||||||
|
use std::sync::atomic::{AtomicU32, Ordering};
|
||||||
|
use yoi::host::{connections, http, logging};
|
||||||
|
|
||||||
|
static CALLS: AtomicU32 = AtomicU32::new(0);
|
||||||
|
struct Plugin;
|
||||||
|
impl Guest for Plugin {
|
||||||
|
fn invoke(input: Invocation) -> Result<Output, ToolError> {
|
||||||
|
let count = CALLS.fetch_add(1, Ordering::SeqCst) + 1;
|
||||||
|
let mode = input.input_json.trim_matches('"');
|
||||||
|
match mode {
|
||||||
|
"trap" => panic!("guest trap"),
|
||||||
|
"loop" => loop {
|
||||||
|
std::hint::black_box(CALLS.load(Ordering::SeqCst));
|
||||||
|
},
|
||||||
|
"wall-loop" => {
|
||||||
|
logging::log(&logging::Entry {
|
||||||
|
level: logging::Level::Info,
|
||||||
|
event_code: "count".into(),
|
||||||
|
message: "loop entered".into(),
|
||||||
|
fields: vec![],
|
||||||
|
})
|
||||||
|
.map_err(failure)?;
|
||||||
|
loop {
|
||||||
|
std::hint::black_box(CALLS.load(Ordering::SeqCst));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"memory" => {
|
||||||
|
core::arch::wasm32::memory_grow::<0>(8192);
|
||||||
|
}
|
||||||
|
"error-output" => {
|
||||||
|
return Err(ToolError {
|
||||||
|
code: "typed".into(),
|
||||||
|
message: "\"".repeat(40_000),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
"typed" | "unknown" => {
|
||||||
|
return Err(ToolError {
|
||||||
|
code: mode.into(),
|
||||||
|
message: "bounded business failure".into(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
"logging" => {
|
||||||
|
for _ in 0..3 {
|
||||||
|
logging::log(&logging::Entry {
|
||||||
|
level: logging::Level::Info,
|
||||||
|
event_code: "count".into(),
|
||||||
|
message: "safe diagnostic".into(),
|
||||||
|
fields: vec![logging::Field {
|
||||||
|
name: "calls".into(),
|
||||||
|
value: logging::Scalar::Unsigned(count as u64),
|
||||||
|
}],
|
||||||
|
})
|
||||||
|
.map_err(failure)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"http" | "stale" | "foreign" | "resources" => {
|
||||||
|
if mode == "resources" {
|
||||||
|
let _resources: Vec<_> =
|
||||||
|
(0..64).map(|_| connections::open("api").unwrap()).collect();
|
||||||
|
}
|
||||||
|
let target = if mode == "foreign" {
|
||||||
|
// An attacker can fabricate a representation, not a capability.
|
||||||
|
unsafe { connections::Connection::from_handle(0) }
|
||||||
|
} else {
|
||||||
|
connections::open("api").map_err(failure)?
|
||||||
|
};
|
||||||
|
let target = if mode == "stale" {
|
||||||
|
let handle = target.handle();
|
||||||
|
drop(target);
|
||||||
|
unsafe { connections::Connection::from_handle(handle) }
|
||||||
|
} else {
|
||||||
|
target
|
||||||
|
};
|
||||||
|
let response = http::send(
|
||||||
|
&target,
|
||||||
|
&http::Request {
|
||||||
|
method: http::Method::Get,
|
||||||
|
path: "/status".into(),
|
||||||
|
query: vec![],
|
||||||
|
headers: vec![],
|
||||||
|
body: vec![],
|
||||||
|
timeout_ms: 15_000,
|
||||||
|
effect_key: "one".into(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.map_err(failure)?;
|
||||||
|
return Ok(Output {
|
||||||
|
summary: String::from_utf8(response.body).unwrap(),
|
||||||
|
content: None,
|
||||||
|
structured_json: Some(format!("{{\"calls\":{count}}}")),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
Ok(Output {
|
||||||
|
summary: if mode == "output" {
|
||||||
|
"x".repeat(70_000)
|
||||||
|
} else {
|
||||||
|
input.invocation_id
|
||||||
|
},
|
||||||
|
content: None,
|
||||||
|
structured_json: Some(if mode == "bad-json" {
|
||||||
|
"not-json".into()
|
||||||
|
} else if mode == "bad-schema" {
|
||||||
|
"{}".into()
|
||||||
|
} else {
|
||||||
|
format!("{{\"calls\":{count}}}")
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn failure(error: yoi::host::errors::HostError) -> ToolError {
|
||||||
|
ToolError {
|
||||||
|
code: "host".into(),
|
||||||
|
message: format!(
|
||||||
|
"{}:{}",
|
||||||
|
match error.code {
|
||||||
|
yoi::host::errors::Code::DeadlineExceeded => "deadline-exceeded",
|
||||||
|
yoi::host::errors::Code::QuotaExceeded => "quota-exceeded",
|
||||||
|
_ => "host-rejected",
|
||||||
|
},
|
||||||
|
match error.effect {
|
||||||
|
yoi::host::errors::EffectObservation::NotDispatched => "not-dispatched",
|
||||||
|
yoi::host::errors::EffectObservation::ResponseObserved => "response-observed",
|
||||||
|
yoi::host::errors::EffectObservation::Indeterminate => "indeterminate",
|
||||||
|
}
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export!(Plugin);
|
||||||
+1
-1
@@ -44,7 +44,7 @@ rustPlatform.buildRustPackage rec {
|
|||||||
};
|
};
|
||||||
|
|
||||||
# All four WIP SDK crates resolve from crates.io 0.2.0 via Cargo.lock checksums.
|
# All four WIP SDK crates resolve from crates.io 0.2.0 via Cargo.lock checksums.
|
||||||
cargoHash = "sha256-cLDfqFu6H9YjaL5EpTs5NE2GzkeS0qYcj5Ys/6U5u40=";
|
cargoHash = "sha256-bT0HZacyAThuZPFw7T7etyy80K0C8Y1tlJkhkjAjA34=";
|
||||||
|
|
||||||
depsExtraArgs = {
|
depsExtraArgs = {
|
||||||
# Older fetchCargoVendor utilities used crates.io's API download endpoint,
|
# Older fetchCargoVendor utilities used crates.io's API download endpoint,
|
||||||
|
|||||||
Reference in New Issue
Block a user