diff --git a/eoc/eoc-rs/Cargo.lock b/eoc/eoc-rs/Cargo.lock index 928b042e3..083594668 100644 --- a/eoc/eoc-rs/Cargo.lock +++ b/eoc/eoc-rs/Cargo.lock @@ -141,7 +141,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -156,6 +156,29 @@ version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" +[[package]] +name = "aws-lc-rs" +version = "1.17.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00bdb5da18dac48ca2cc7cd4a98e533e8635a58e2361d13a1a4ee3888e0d72f1" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.43.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43103168cc76fe62678a375e722fc9cb3a0146159ac5828bc4f0dfd755c2224c" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", + "pkg-config", +] + [[package]] name = "base64" version = "0.13.1" @@ -185,7 +208,7 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex 1.3.0", - "syn", + "syn 2.0.117", ] [[package]] @@ -205,16 +228,7 @@ dependencies = [ "regex", "rustc-hash 2.1.2", "shlex 1.3.0", - "syn", -] - -[[package]] -name = "bit-set" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" -dependencies = [ - "bit-vec 0.6.3", + "syn 2.0.117", ] [[package]] @@ -223,15 +237,9 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" dependencies = [ - "bit-vec 0.8.0", + "bit-vec", ] -[[package]] -name = "bit-vec" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" - [[package]] name = "bit-vec" version = "0.8.0" @@ -258,6 +266,12 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "borrow-or-share" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc0b364ead1874514c8c2855ab558056ebfeb775653e7ae45ff72f28f8f3166c" + [[package]] name = "bumpalo" version = "3.20.3" @@ -315,7 +329,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" dependencies = [ - "nom 7.1.3", + "nom", ] [[package]] @@ -395,7 +409,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -419,6 +433,16 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "compact_str" version = "0.9.1" @@ -451,6 +475,16 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -506,6 +540,12 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" +[[package]] +name = "daachorse" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f55d7153ba3b507595872a3874803f07a8a81d1e888abed8e5db7da0597d6e2" + [[package]] name = "darling" version = "0.20.11" @@ -527,7 +567,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn", + "syn 2.0.117", ] [[package]] @@ -538,7 +578,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -550,6 +590,12 @@ dependencies = [ "serde", ] +[[package]] +name = "data-encoding" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" + [[package]] name = "deadpool" version = "0.12.3" @@ -568,15 +614,6 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "092966b41edc516079bdf31ec78a2e0588d1d0c08f78b91d8307215928642b2b" -[[package]] -name = "deranged" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" -dependencies = [ - "powerfmt", -] - [[package]] name = "derive_builder" version = "0.20.2" @@ -595,7 +632,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -605,7 +642,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn", + "syn 2.0.117", ] [[package]] @@ -616,9 +653,15 @@ checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + [[package]] name = "dyn-clone" version = "1.0.20" @@ -631,6 +674,15 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" +[[package]] +name = "email_address" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e079f19b08ca6239f47f8ba8509c11cf3ea30095831f7fed61441475edd8c449" +dependencies = [ + "serde", +] + [[package]] name = "encode_unicode" version = "1.0.0" @@ -663,7 +715,7 @@ checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -783,7 +835,7 @@ dependencies = [ "serde", "serde_json", "thiserror 2.0.18", - "tokenizers 0.20.4", + "tokenizers", "tokio", "tracing", "wiremock", @@ -844,7 +896,7 @@ dependencies = [ "serde_json", "tempfile", "thiserror 2.0.18", - "tokenizers 0.21.4", + "tokenizers", "tokio", "tracing", ] @@ -888,7 +940,7 @@ dependencies = [ "serde", "serde_json", "thiserror 2.0.18", - "tokenizers 0.20.4", + "tokenizers", "tokio", "tracing", "wiremock", @@ -947,7 +999,7 @@ dependencies = [ "serde", "serde_json", "thiserror 2.0.18", - "tokenizers 0.20.4", + "tokenizers", "tokio", "tracing", "unicode-normalization", @@ -1096,11 +1148,11 @@ checksum = "d817e038c30374a4bcb22f94d0a8a0e216958d4c3dcde369b1439fec4bdda6e6" [[package]] name = "fancy-regex" -version = "0.13.0" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "531e46835a22af56d1e3b66f04844bed63158bc094a628bec1d321d9b4c44bf2" +checksum = "e1e1dacd0d2082dfcf1351c4bdd566bbe89a2b263235a2b50058f1e130a47277" dependencies = [ - "bit-set 0.5.3", + "bit-set", "regex-automata", "regex-syntax", ] @@ -1122,9 +1174,9 @@ dependencies = [ [[package]] name = "ffmpeg-next" -version = "7.1.0" +version = "8.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da02698288e0275e442a47fc12ca26d50daf0d48b15398ba5906f20ac2e2a9f9" +checksum = "f7c4bd5ab1ac61f29c634df1175d350ded29cf74c3c6d4f7030431a5ae3c7d5d" dependencies = [ "bitflags", "ffmpeg-sys-next", @@ -1133,11 +1185,11 @@ dependencies = [ [[package]] name = "ffmpeg-sys-next" -version = "7.1.3" +version = "8.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9e9c75ebd4463de9d8998fb134ba26347fe5faee62fabf0a4b4d41bd500b4ad" +checksum = "a314bc0e022a33a99567ed4bd2576bd58ffd8fcff7891c29194cfecc26a62547" dependencies = [ - "bindgen 0.70.1", + "bindgen 0.72.1", "cc", "libc", "num_cpus", @@ -1170,6 +1222,17 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "fluent-uri" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc74ac4d8359ae70623506d512209619e5cf8f347124910440dbc221714b328e" +dependencies = [ + "borrow-or-share", + "ref-cast", + "serde", +] + [[package]] name = "fnv" version = "1.0.7" @@ -1182,6 +1245,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + [[package]] name = "form_urlencoded" version = "1.2.2" @@ -1201,6 +1270,12 @@ dependencies = [ "num", ] +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "futures" version = "0.3.32" @@ -1257,7 +1332,7 @@ checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1379,7 +1454,18 @@ checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ "allocator-api2", "equivalent", - "foldhash", + "foldhash 0.1.5", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.2.0", ] [[package]] @@ -1480,7 +1566,6 @@ dependencies = [ "tokio", "tokio-rustls", "tower-service", - "webpki-roots", ] [[package]] @@ -1672,15 +1757,6 @@ dependencies = [ "serde_core", ] -[[package]] -name = "indoc" -version = "2.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" -dependencies = [ - "rustversion", -] - [[package]] name = "insta" version = "1.47.2" @@ -1706,33 +1782,6 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" -[[package]] -name = "iso8601" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1082f0c48f143442a1ac6122f67e360ceee130b967af4d50996e5154a45df46" -dependencies = [ - "nom 8.0.0", -] - -[[package]] -name = "itertools" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" -dependencies = [ - "either", -] - -[[package]] -name = "itertools" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" -dependencies = [ - "either", -] - [[package]] name = "itertools" version = "0.13.0" @@ -1757,6 +1806,55 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" +[[package]] +name = "jni" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys", + "log", + "simd_cesu8", + "thiserror 2.0.18", + "walkdir", + "windows-link", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn 2.0.117", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.117", +] + [[package]] name = "jobserver" version = "0.1.34" @@ -1781,30 +1879,38 @@ dependencies = [ [[package]] name = "jsonschema" -version = "0.18.3" +version = "0.46.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa0f4bea31643be4c6a678e9aa4ae44f0db9e5609d5ca9dc9083d06eb3e9a27a" +checksum = "f0a699d3e77675e6aa4bfffe3b907c8b5f7ed3241f9965bffb25475ad4b08d05" dependencies = [ "ahash", - "anyhow", - "base64 0.22.1", "bytecount", + "data-encoding", + "email_address", "fancy-regex", "fraction", - "getrandom 0.2.17", - "iso8601", + "getrandom 0.3.4", + "idna", "itoa", - "memchr", + "jsonschema-regex", "num-cmp", - "once_cell", - "parking_lot", + "num-traits", "percent-encoding", + "referencing", "regex", "serde", "serde_json", - "time", - "url", - "uuid", + "unicode-general-category", + "uuid-simd", +] + +[[package]] +name = "jsonschema-regex" +version = "0.46.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbd1086b01b9349fd4ef9a07433965af64c8ce8159abe633a189e4ff817bd13" +dependencies = [ + "regex-syntax", ] [[package]] @@ -1953,13 +2059,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" [[package]] -name = "memoffset" -version = "0.9.1" +name = "micromap" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" -dependencies = [ - "autocfg", -] +checksum = "c2a86d3146ed3995b5913c414f6664344b9617457320782e64f0bb44afd49d74" [[package]] name = "mime" @@ -2013,7 +2116,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2025,7 +2128,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2082,7 +2185,7 @@ checksum = "e4db6d5580af57bf992f59068d4ea26fd518574ff48d7639b255a36f9de6e7e9" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2136,15 +2239,6 @@ dependencies = [ "minimal-lexical", ] -[[package]] -name = "nom" -version = "8.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" -dependencies = [ - "memchr", -] - [[package]] name = "num" version = "0.4.3" @@ -2185,12 +2279,6 @@ dependencies = [ "serde", ] -[[package]] -name = "num-conv" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" - [[package]] name = "num-integer" version = "0.1.46" @@ -2261,7 +2349,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2321,6 +2409,12 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + [[package]] name = "ort" version = "2.0.0-rc.12" @@ -2339,6 +2433,12 @@ version = "2.0.0-rc.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7b497d21a8b6fbb4b5a544f8fadb77e801a09ae0add9e411d31c6f89e3c1e90" +[[package]] +name = "outref" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" + [[package]] name = "parking_lot" version = "0.12.5" @@ -2423,12 +2523,6 @@ dependencies = [ "zerovec", ] -[[package]] -name = "powerfmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" - [[package]] name = "ppv-lite86" version = "0.2.21" @@ -2445,7 +2539,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn", + "syn 2.0.117", ] [[package]] @@ -2472,12 +2566,12 @@ version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744" dependencies = [ - "bit-set 0.8.0", - "bit-vec 0.8.0", + "bit-set", + "bit-vec", "bitflags", "num-traits", "rand 0.9.4", - "rand_chacha 0.9.0", + "rand_chacha", "rand_xorshift", "regex-syntax", "rusty-fork", @@ -2493,37 +2587,32 @@ checksum = "e0c5ccf5294c6ccd63a74f1565028353830a9c2f5eb0c682c355c471726a6e3f" [[package]] name = "pyo3" -version = "0.22.6" +version = "0.28.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f402062616ab18202ae8319da13fa4279883a2b8a9d9f83f20dbade813ce1884" +checksum = "91fd8e38a3b50ed1167fb981cd6fd60147e091784c427b8f7183a7ee32c31c12" dependencies = [ - "cfg-if", - "indoc", "libc", - "memoffset", "once_cell", "portable-atomic", "pyo3-build-config", "pyo3-ffi", "pyo3-macros", - "unindent", ] [[package]] name = "pyo3-build-config" -version = "0.22.6" +version = "0.28.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b14b5775b5ff446dd1056212d778012cbe8a0fbffd368029fd9e25b514479c38" +checksum = "e368e7ddfdeb98c9bca7f8383be1648fd84ab466bf2bc015e94008db6d35611e" dependencies = [ - "once_cell", "target-lexicon", ] [[package]] name = "pyo3-ffi" -version = "0.22.6" +version = "0.28.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ab5bcf04a2cdcbb50c7d6105de943f543f9ed92af55818fd17b660390fc8636" +checksum = "7f29e10af80b1f7ccaf7f69eace800a03ecd13e883acfacc1e5d0988605f651e" dependencies = [ "libc", "pyo3-build-config", @@ -2531,27 +2620,27 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.22.6" +version = "0.28.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fd24d897903a9e6d80b968368a34e1525aeb719d568dba8b3d4bfa5dc67d453" +checksum = "df6e520eff47c45997d2fc7dd8214b25dd1310918bbb2642156ef66a67f29813" dependencies = [ "proc-macro2", "pyo3-macros-backend", "quote", - "syn", + "syn 2.0.117", ] [[package]] name = "pyo3-macros-backend" -version = "0.22.6" +version = "0.28.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36c011a03ba1e50152b4b394b479826cad97e7a21eb52df179cd91ac411cbfbe" +checksum = "c4cdc218d835738f81c2338f822078af45b4afdf8b2e33cbb5916f108b813acb" dependencies = [ "heck", "proc-macro2", "pyo3-build-config", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2586,6 +2675,7 @@ version = "0.11.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" dependencies = [ + "aws-lc-rs", "bytes", "getrandom 0.3.4", "lru-slab", @@ -2636,24 +2726,13 @@ version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" -[[package]] -name = "rand" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" -dependencies = [ - "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - [[package]] name = "rand" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" dependencies = [ - "rand_chacha 0.9.0", + "rand_chacha", "rand_core 0.9.5", ] @@ -2668,16 +2747,6 @@ dependencies = [ "rand_core 0.10.1", ] -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core 0.6.4", -] - [[package]] name = "rand_chacha" version = "0.9.0" @@ -2688,15 +2757,6 @@ dependencies = [ "rand_core 0.9.5", ] -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom 0.2.17", -] - [[package]] name = "rand_core" version = "0.9.5" @@ -2714,12 +2774,12 @@ checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" [[package]] name = "rand_distr" -version = "0.4.3" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" +checksum = "4d431c2703ccf129de4d45253c03f49ebb22b97d6ad79ee3ecfc7e3f4862c1d8" dependencies = [ "num-traits", - "rand 0.8.6", + "rand 0.10.1", ] [[package]] @@ -2747,17 +2807,6 @@ dependencies = [ "rayon-core", ] -[[package]] -name = "rayon-cond" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "059f538b55efd2309c9794130bc149c6a553db90e9d99c2030785c82f0bd7df9" -dependencies = [ - "either", - "itertools 0.11.0", - "rayon", -] - [[package]] name = "rayon-cond" version = "0.4.0" @@ -2788,6 +2837,43 @@ dependencies = [ "bitflags", ] +[[package]] +name = "ref-cast" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "216e8f773d7923bcba9ceb86a86c93cabb3903a11872fc3f138c49630e50b96d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9283685feec7d69af75fb0e858d5e7378f33fe4fc699383b2916ab9273e03c" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.2", +] + +[[package]] +name = "referencing" +version = "0.46.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fbf332a2f81899f6836f22c03da73dae8a664c32e3016b84692c23cddadc95d" +dependencies = [ + "ahash", + "fluent-uri", + "getrandom 0.3.4", + "hashbrown 0.16.1", + "itoa", + "micromap", + "parking_lot", + "percent-encoding", + "serde_json", +] + [[package]] name = "regex" version = "1.12.3" @@ -2819,9 +2905,9 @@ checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "reqwest" -version = "0.12.28" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" dependencies = [ "base64 0.22.1", "bytes", @@ -2841,9 +2927,9 @@ dependencies = [ "quinn", "rustls", "rustls-pki-types", + "rustls-platform-verifier", "serde", "serde_json", - "serde_urlencoded", "sync_wrapper", "tokio", "tokio-rustls", @@ -2856,7 +2942,6 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams", "web-sys", - "webpki-roots", ] [[package]] @@ -2885,6 +2970,15 @@ version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "1.1.4" @@ -2904,14 +2998,26 @@ version = "0.23.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" dependencies = [ + "aws-lc-rs", "once_cell", - "ring", "rustls-pki-types", "rustls-webpki", "subtle", "zeroize", ] +[[package]] +name = "rustls-native-certs" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + [[package]] name = "rustls-pki-types" version = "1.14.1" @@ -2922,12 +3028,40 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustls-platform-verifier" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" +dependencies = [ + "core-foundation", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + [[package]] name = "rustls-webpki" version = "0.103.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" dependencies = [ + "aws-lc-rs", "ring", "rustls-pki-types", "untrusted", @@ -2976,12 +3110,22 @@ dependencies = [ ] [[package]] -name = "schemars" -version = "0.8.22" +name = "schannel" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "schemars" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" dependencies = [ "dyn-clone", + "ref-cast", "schemars_derive", "serde", "serde_json", @@ -2989,14 +3133,14 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "0.8.22" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" +checksum = "7d115b50f4aaeea07e79c1912f645c7513d81715d0420f8bc77a18c6260b307f" dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn", + "syn 2.0.117", ] [[package]] @@ -3005,6 +3149,29 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "semver" version = "1.0.28" @@ -3059,7 +3226,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -3070,7 +3237,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -3086,18 +3253,6 @@ dependencies = [ "zmij", ] -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - [[package]] name = "shlex" version = "1.3.0" @@ -3139,6 +3294,22 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" +[[package]] +name = "simd_cesu8" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" +dependencies = [ + "rustc_version", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + [[package]] name = "similar" version = "2.7.0" @@ -3174,7 +3345,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5851699c4033c63636f7ea4cf7b7c1f1bf06d0cc03cfb42e711de5a5c46cf326" dependencies = [ "base64 0.13.1", - "nom 7.1.3", + "nom", "serde", "unicode-segmentation", ] @@ -3216,7 +3387,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn", + "syn 2.0.117", ] [[package]] @@ -3236,6 +3407,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a207d6d6a2b7fc470b80443726053f18a2481b7e1eee970597051596567987a3" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "sync_wrapper" version = "1.0.2" @@ -3253,14 +3435,14 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] name = "target-lexicon" -version = "0.12.16" +version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" +checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca" [[package]] name = "tempfile" @@ -3301,7 +3483,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -3312,37 +3494,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn", -] - -[[package]] -name = "time" -version = "0.3.47" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" -dependencies = [ - "deranged", - "num-conv", - "powerfmt", - "serde_core", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" - -[[package]] -name = "time-macros" -version = "0.2.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" -dependencies = [ - "num-conv", - "time-core", + "syn 2.0.117", ] [[package]] @@ -3372,44 +3524,13 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokenizers" -version = "0.20.4" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b08cc37428a476fc9e20ac850132a513a2e1ce32b6a31addf2b74fa7033b905" -dependencies = [ - "aho-corasick", - "derive_builder", - "esaxx-rs", - "getrandom 0.2.17", - "itertools 0.12.1", - "lazy_static", - "log", - "macro_rules_attribute", - "monostate", - "onig", - "paste", - "rand 0.8.6", - "rayon", - "rayon-cond 0.3.0", - "regex", - "regex-syntax", - "serde", - "serde_json", - "spm_precompiled", - "thiserror 1.0.69", - "unicode-normalization-alignments", - "unicode-segmentation", - "unicode_categories", -] - -[[package]] -name = "tokenizers" -version = "0.21.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a620b996116a59e184c2fa2dfd8251ea34a36d0a514758c6f966386bd2e03476" +checksum = "44e5bea67576e04b6ff8564c5d9e09c2ef0cf476502245f2f120e497769d3112" dependencies = [ "ahash", - "aho-corasick", "compact_str", + "daachorse", "dary_heap", "derive_builder", "esaxx-rs", @@ -3422,7 +3543,7 @@ dependencies = [ "paste", "rand 0.9.4", "rayon", - "rayon-cond 0.4.0", + "rayon-cond", "regex", "regex-syntax", "serde", @@ -3458,7 +3579,7 @@ checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -3578,7 +3699,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -3615,6 +3736,12 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" +[[package]] +name = "unicode-general-category" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b993bddc193ae5bd0d623b49ec06ac3e9312875fdae725a975c51db1cc1677f" + [[package]] name = "unicode-ident" version = "1.0.24" @@ -3657,12 +3784,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" -[[package]] -name = "unindent" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3" - [[package]] name = "untrusted" version = "0.9.0" @@ -3694,13 +3815,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] -name = "uuid" -version = "1.23.2" +name = "uuid-simd" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d258b83ceec21034727ecee8c382cfa6c3e133699b0742c64571814fb420c9f7" +checksum = "23b082222b4f6619906941c17eb2297fff4c2fb96cb60164170522942a200bd8" dependencies = [ - "js-sys", - "wasm-bindgen", + "outref", + "vsimd", ] [[package]] @@ -3721,6 +3842,12 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "vsimd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" + [[package]] name = "wait-timeout" version = "0.2.1" @@ -3815,7 +3942,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn", + "syn 2.0.117", "wasm-bindgen-shared", ] @@ -3852,9 +3979,9 @@ dependencies = [ [[package]] name = "wasm-streams" -version = "0.4.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" dependencies = [ "futures-util", "js-sys", @@ -3896,10 +4023,10 @@ dependencies = [ ] [[package]] -name = "webpki-roots" -version = "1.0.7" +name = "webpki-root-certs" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d" +checksum = "b96554aa2acc8ccdb7e1c9a58a7a68dd5d13bccc69cd124cb09406db612a1c9b" dependencies = [ "rustls-pki-types", ] @@ -3944,7 +4071,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -3955,7 +4082,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -4206,7 +4333,7 @@ dependencies = [ "heck", "indexmap", "prettyplease", - "syn", + "syn 2.0.117", "wasm-metadata", "wit-bindgen-core", "wit-component", @@ -4222,7 +4349,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn", + "syn 2.0.117", "wit-bindgen-core", "wit-bindgen-rust", ] @@ -4273,7 +4400,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -4301,7 +4428,7 @@ checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", "synstructure", ] @@ -4322,7 +4449,7 @@ checksum = "0b631b19d36a892ab55420c92dbc83ccd79274f25be714855d3074aa71cab639" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -4342,7 +4469,7 @@ checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", "synstructure", ] @@ -4382,7 +4509,7 @@ checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] diff --git a/eoc/eoc-rs/crates/eoc-route-learned/Cargo.toml b/eoc/eoc-rs/crates/eoc-route-learned/Cargo.toml index 19f2f65e4..3c81f40f3 100644 --- a/eoc/eoc-rs/crates/eoc-route-learned/Cargo.toml +++ b/eoc/eoc-rs/crates/eoc-route-learned/Cargo.toml @@ -17,7 +17,7 @@ serde_json = "1" async-trait.workspace = true thiserror.workspace = true rand = { version = "0.10", default-features = false, features = ["std", "std_rng"] } -rand_distr = { version = "0.4", default-features = false } +rand_distr = { version = "0.6", default-features = false } nalgebra = { version = "0.33", default-features = false, features = ["std", "serde-serialize"] } tracing = "0.1" diff --git a/jouleclaw/jouleclaw-rs/Cargo.lock b/jouleclaw/jouleclaw-rs/Cargo.lock index 2b8292860..c2428e623 100644 --- a/jouleclaw/jouleclaw-rs/Cargo.lock +++ b/jouleclaw/jouleclaw-rs/Cargo.lock @@ -3389,6 +3389,19 @@ dependencies = [ "thiserror 2.0.18", ] +[[package]] +name = "jouleclaw-corpus" +version = "0.1.0" +dependencies = [ + "jouleclaw-energy", + "jouleclaw-intent", + "jouleclaw-prov", + "serde", + "serde_json", + "tempfile", + "thiserror 2.0.18", +] + [[package]] name = "jouleclaw-critic" version = "0.1.0" diff --git a/jouleclaw/jouleclaw-rs/Cargo.toml b/jouleclaw/jouleclaw-rs/Cargo.toml index 6a37cd19f..c129707d9 100644 --- a/jouleclaw/jouleclaw-rs/Cargo.toml +++ b/jouleclaw/jouleclaw-rs/Cargo.toml @@ -1147,6 +1147,7 @@ members = [ # top of jouleclaw-otel's SpanData. "crates/jouleclaw-otel-authz", "crates/jouleclaw-jcp", + "crates/jouleclaw-corpus", ] [workspace.package] diff --git a/jouleclaw/jouleclaw-rs/crates/jouleclaw-corpus/Cargo.toml b/jouleclaw/jouleclaw-rs/crates/jouleclaw-corpus/Cargo.toml new file mode 100644 index 000000000..d97c70097 --- /dev/null +++ b/jouleclaw/jouleclaw-rs/crates/jouleclaw-corpus/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "jouleclaw-corpus" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +repository.workspace = true +authors.workspace = true +description = "Receipts → training corpus. Projects sealed JouleClaw receipts into the labelled (embedding, stage, success, cost_microjoules) rows an EOC router trainer consumes, turning the audit ledger into a supervision signal. Labels are derived from observed cascade closure — never invented; receipts whose closing tier has no EOC analogue are skipped and counted, not guessed." + +[dependencies] +jouleclaw-prov = { path = "../jouleclaw-prov" } +jouleclaw-energy = { path = "../jouleclaw-energy" } +jouleclaw-intent = { path = "../jouleclaw-intent" } +serde = { workspace = true } +serde_json = { workspace = true } +thiserror = { workspace = true } + +[dev-dependencies] +tempfile = { workspace = true } diff --git a/jouleclaw/jouleclaw-rs/crates/jouleclaw-corpus/examples/emit_corpus.rs b/jouleclaw/jouleclaw-rs/crates/jouleclaw-corpus/examples/emit_corpus.rs new file mode 100644 index 000000000..37fbb0213 --- /dev/null +++ b/jouleclaw/jouleclaw-rs/crates/jouleclaw-corpus/examples/emit_corpus.rs @@ -0,0 +1,70 @@ +//! Emit a training corpus from a batch of sealed receipts. +//! +//! ```text +//! cargo run -p jouleclaw-corpus --example emit_corpus -- /tmp/corpus.jsonl +//! ``` +//! +//! The output file is directly loadable by an EOC router trainer's JSONL +//! loader. This example stands in a synthetic ledger for the receipts a +//! real deployment would read off disk. + +use jouleclaw_corpus::{CorpusBuilder, write_jsonl}; +use jouleclaw_energy::Provenance; +use jouleclaw_intent::{IntentVerdict, QueryIntent}; +use jouleclaw_prov::{CascadeTier, Receipt}; + +fn receipt(tier: CascadeTier, uj: u64, intent: QueryIntent, confidence: f64) -> Receipt { + Receipt { + context: None, + jc_receipt: "1".into(), + id: format!("rcpt-{}-{uj}", tier.wire_tag()), + closed_at: "2026-07-20T00:00:00Z".into(), + input_hash: "a".repeat(64), + tier, + joules_uj: uj, + energy_provenance: Provenance::HwShunt, + tools_touched: Vec::new(), + claims: Vec::new(), + eoc_stage: None, + citations: Vec::new(), + tool_receipts: Vec::new(), + token_attributions: Vec::new(), + model_id: None, + intent: Some(IntentVerdict::new(intent, confidence, "heuristic:v1")), + steward: None, + } +} + +fn main() { + let out = std::env::args() + .nth(1) + .unwrap_or_else(|| "corpus.jsonl".to_string()); + + // A ledger with the shape a real cascade produces: comprehension work + // closing cheap, generation work escalating to a model, and one lawful + // primitive that has no EOC analogue and must be skipped. + let ledger = vec![ + receipt(CascadeTier::L0Cache, 12, QueryIntent::Comprehension, 0.91), + receipt(CascadeTier::L0Cache, 9, QueryIntent::Comprehension, 0.88), + receipt(CascadeTier::L2Embed, 4_100, QueryIntent::Comprehension, 0.72), + receipt(CascadeTier::L2Embed, 3_800, QueryIntent::Review, 0.65), + receipt(CascadeTier::L3Model, 2_400_000, QueryIntent::Generation, 0.94), + receipt(CascadeTier::L3Model, 1_900_000, QueryIntent::Modification, 0.80), + receipt(CascadeTier::L4Wire, 61_000_000, QueryIntent::Generation, 0.97), + receipt(CascadeTier::L1Lawful, 40, QueryIntent::Process, 0.99), + ]; + + let (rows, stats) = CorpusBuilder::default() + .with_monotone_negatives(true) + .build(&ledger); + + write_jsonl(&out, &rows).expect("write corpus"); + + println!("receipts seen {}", stats.receipts_seen); + println!("receipts used {}", stats.receipts_used); + println!("skipped unmapped {}", stats.skipped_unmapped); + println!("rows positive {}", stats.rows_positive); + println!("rows negative {}", stats.rows_negative); + println!("rows total {}", stats.rows()); + println!("wrote {out}"); +} diff --git a/jouleclaw/jouleclaw-rs/crates/jouleclaw-corpus/src/lib.rs b/jouleclaw/jouleclaw-rs/crates/jouleclaw-corpus/src/lib.rs new file mode 100644 index 000000000..c2ecceabf --- /dev/null +++ b/jouleclaw/jouleclaw-rs/crates/jouleclaw-corpus/src/lib.rs @@ -0,0 +1,607 @@ +//! # jouleclaw-corpus +//! +//! **Receipts → training corpus.** The connector that turns the audit +//! ledger into a supervision signal. +//! +//! Every sealed [`Receipt`] already records what a cascade walk actually +//! did: which tier closed the query, how many microjoules it cost, and at +//! what counter honesty. That is an *observed execution outcome* — the same +//! class of signal a code model gets from "the test passed on head and +//! failed on base", except it arrives signed and energy-metered rather than +//! reconstructed after the fact. +//! +//! Until now those receipts were terminal: written, verified, aggregated +//! into cost reports, and never read again. This crate projects them into +//! the labelled rows an EOC router trainer consumes, so the ledger compounds +//! instead of merely accumulating. +//! +//! ## What is derived, and what is not +//! +//! - **`cost_microjoules`** — read directly from [`Receipt::joules_uj`]. Not +//! modelled, not rescaled. +//! - **`stage`** — taken from [`Receipt::eoc_stage`] when the runtime +//! reported one (authoritative), else mapped from [`CascadeTier`] but +//! **only where the mapping is unambiguous**. Receipts that cannot be +//! classified are skipped and counted in [`CorpusStats::skipped_unmapped`], +//! never guessed into a bucket. +//! - **`success`** — a sealed receipt attests the walk *closed* at +//! `tier`; that stage is labelled a success. Note honestly: the receipt +//! carries no failure discriminator, so if a runtime seals receipts for +//! failed walks they are indistinguishable here. Treat `success: true` as +//! "this tier closed the walk", which is what it is. +//! - **`embedding`** — receipts carry no vector. An [`Embedder`] supplies +//! one. The bundled [`StructuralEmbedder`] is deterministic and offline; +//! see its docs for the sharp limitation. +//! +//! ## Wire compatibility +//! +//! Rows serialise to the exact JSON an EOC router trainer's JSONL loader +//! expects. One trap this crate exists to absorb: the receipt's +//! `eoc_stage` label is **lower-case** (`"neural"`), while the trainer's +//! `Stage` enum deserialises **capitalised** (`"Neural"`) and rejects +//! anything else. Feeding the receipt field straight through fails at +//! ingest. [`CorpusStage::from_eoc_label`] absorbs the difference and +//! `stage_wire_form_is_pinned` locks the emitted spelling. +//! +//! The coupling here is the wire format, deliberately — the EOC standard +//! takes no dependency on JouleClaw, and JouleClaw takes none on EOC. + +#![forbid(unsafe_code)] +#![warn(missing_docs)] + +use std::io::Write; +use std::path::Path; + +use jouleclaw_energy::Provenance; +use jouleclaw_intent::QueryIntent; +use jouleclaw_prov::{CascadeTier, Receipt}; +use serde::{Deserialize, Serialize}; +use thiserror::Error; + +/// Errors raised while building or writing a corpus. +#[derive(Debug, Error)] +pub enum Error { + /// Writing the JSONL sink failed. + #[error("corpus write failed: {0}")] + Write(String), + /// A row could not be serialised. + #[error("corpus encode failed: {0}")] + Encode(String), +} + +/// Result alias for this crate. +pub type Result = std::result::Result; + +// ───────────────────────────────────────────────────────────────────── +// Stage +// ───────────────────────────────────────────────────────────────────── + +/// The EOC cascade stage a row is labelled with. +/// +/// Variant spelling is **load-bearing**: these serialise to `"Cache"`, +/// `"Kv"`, `"Graph"`, `"Neural"` to match the consuming trainer's `Stage` +/// enum exactly. Do not add `#[serde(rename_all = ...)]` here — it would +/// silently break every downstream ingest. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum CorpusStage { + /// Content-addressed cache hit. + Cache, + /// Key-value lookup, including embedding-similarity match. + Kv, + /// Graph / triple-store retrieval. + Graph, + /// Neural inference. + Neural, +} + +impl CorpusStage { + /// Relative cost rank, cheapest first. Used to order negatives. + fn rank(self) -> u8 { + match self { + Self::Cache => 0, + Self::Kv => 1, + Self::Graph => 2, + Self::Neural => 3, + } + } + + /// Parse the label a runtime writes into [`Receipt::eoc_stage`]. + /// + /// Receipts carry the lower-case form (`"neural"`); this accepts that, + /// and is case-insensitive so a capitalised label round-trips too. + pub fn from_eoc_label(label: &str) -> Option { + match label.trim().to_ascii_lowercase().as_str() { + "cache" => Some(Self::Cache), + "kv" => Some(Self::Kv), + "graph" => Some(Self::Graph), + "neural" => Some(Self::Neural), + _ => None, + } + } + + /// Map a JouleClaw cascade tier onto an EOC stage. + /// + /// Returns `None` where no honest correspondence exists rather than + /// forcing one: + /// + /// - `L0Cache` → `Cache` + /// - `L2Embed` → `Kv` (the Kv stage is defined to include + /// embedding-similarity match) + /// - `L3Model`, `L4Wire` → `Neural` + /// - `L1Lawful` → **`None`.** A deterministic lawful primitive is not a + /// cache, a lookup, a graph walk, or an inference. There is no EOC + /// stage for it. + /// + /// Note that nothing maps *to* `Graph`: JouleClaw has no graph tier. + /// `Graph` rows only ever arise from an explicit `eoc_stage` label. + pub fn from_tier(tier: CascadeTier) -> Option { + match tier { + CascadeTier::L0Cache => Some(Self::Cache), + CascadeTier::L1Lawful => None, + CascadeTier::L2Embed => Some(Self::Kv), + CascadeTier::L3Model | CascadeTier::L4Wire => Some(Self::Neural), + } + } +} + +// ───────────────────────────────────────────────────────────────────── +// Row +// ───────────────────────────────────────────────────────────────────── + +/// One labelled training row, in the trainer's on-disk shape. +/// +/// Field names and order match the consumer's `Example` struct. See +/// [`CorpusStage`] on why the stage spelling must not change. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct CorpusRow { + /// Feature vector. Dimension must be consistent across the corpus. + pub embedding: Vec, + /// Stage this row labels. + pub stage: CorpusStage, + /// Whether `stage` closed the walk. + pub success: bool, + /// Observed cost in microjoules, verbatim from the receipt. + pub cost_microjoules: u64, +} + +// ───────────────────────────────────────────────────────────────────── +// Embedding +// ───────────────────────────────────────────────────────────────────── + +/// Supplies the feature vector for a receipt. +/// +/// Receipts carry no vector, so this is the one genuinely external input. +/// Implement it over a real text embedder when the query text is available +/// alongside the receipt; use [`StructuralEmbedder`] when it is not. +pub trait Embedder { + /// Feature vector for `receipt`. Must return [`Embedder::dim`] elements. + fn embed(&self, receipt: &Receipt) -> Vec; + /// Dimension of every vector this embedder returns. + fn dim(&self) -> usize; +} + +/// Number of dimensions [`StructuralEmbedder`] emits. +pub const STRUCTURAL_DIM: usize = 15; + +/// A deterministic, offline embedder over receipt *structure*. +/// +/// ## The limitation, stated plainly +/// +/// This does **not** encode query semantics. Receipts hold a BLAKE3 of the +/// input, not the input, and a hash has no semantic locality — using its +/// bits as features would manufacture noise and dress it as signal, so this +/// embedder does not touch `input_hash`. What it captures is the *shape* of +/// a request: its intent class, how much retrieval grounded it, and how +/// trustworthy the energy counter was. That is enough to learn "requests +/// shaped like this tend to be closed by that stage at this cost", and not +/// enough to distinguish two differently-worded questions of the same shape. +/// For semantic routing, supply a real text embedder. +/// +/// ## No label leakage +/// +/// `tier`/`eoc_stage` and `joules_uj` are deliberately **excluded**. They +/// are the label side of every row — a router that could read the closing +/// tier or its cost out of the feature vector would score perfectly while +/// learning nothing. Layout: +/// +/// | dims | feature | +/// |--------|----------------------------------------------------------| +/// | 0–5 | intent one-hot (Comprehension…Other), all-zero if absent | +/// | 6 | intent confidence, `0.0` when no verdict ran | +/// | 7–9 | energy provenance one-hot (HwShunt/ModelBased/Estimator) | +/// | 10–13 | `ln(1+n)` over tools, claims, citations, attributions | +/// | 14 | 1.0 when a model id is present | +#[derive(Debug, Clone, Copy, Default)] +pub struct StructuralEmbedder; + +impl Embedder for StructuralEmbedder { + fn dim(&self) -> usize { + STRUCTURAL_DIM + } + + fn embed(&self, receipt: &Receipt) -> Vec { + let mut v = vec![0.0_f32; STRUCTURAL_DIM]; + + if let Some(verdict) = &receipt.intent { + let idx = match verdict.intent { + QueryIntent::Comprehension => 0, + QueryIntent::Generation => 1, + QueryIntent::Modification => 2, + QueryIntent::Process => 3, + QueryIntent::Review => 4, + QueryIntent::Other => 5, + // `QueryIntent` is `#[non_exhaustive]`. A variant added + // upstream lands in the unclassified slot rather than + // shifting the one-hot layout underneath a trained router. + _ => 5, + }; + v[idx] = 1.0; + v[6] = verdict.confidence as f32; + } + + let prov_idx = match receipt.energy_provenance { + Provenance::HwShunt => 7, + Provenance::ModelBased => 8, + Provenance::Estimator => 9, + }; + v[prov_idx] = 1.0; + + v[10] = ln1p(receipt.tools_touched.len()); + v[11] = ln1p(receipt.claims.len()); + v[12] = ln1p(receipt.citations.len()); + v[13] = ln1p(receipt.token_attributions.len()); + v[14] = if receipt.model_id.is_some() { 1.0 } else { 0.0 }; + + v + } +} + +fn ln1p(n: usize) -> f32 { + (n as f32).ln_1p() +} + +// ───────────────────────────────────────────────────────────────────── +// Builder +// ───────────────────────────────────────────────────────────────────── + +/// What a corpus build actually consumed and produced. +/// +/// Reported rather than swallowed: a silent skip reads as "covered +/// everything" when it did not. +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct CorpusStats { + /// Receipts read. + pub receipts_seen: usize, + /// Receipts that produced at least one row. + pub receipts_used: usize, + /// Receipts skipped because their closing tier has no EOC analogue + /// (in practice: `L1Lawful`) and no `eoc_stage` label was present. + pub skipped_unmapped: usize, + /// Positive rows emitted (one per used receipt). + pub rows_positive: usize, + /// Negative rows emitted under the monotone-escalation assumption. + pub rows_negative: usize, +} + +impl CorpusStats { + /// Total rows emitted. + pub fn rows(&self) -> usize { + self.rows_positive + self.rows_negative + } +} + +/// Projects receipts into corpus rows. +pub struct CorpusBuilder { + embedder: E, + monotone_negatives: bool, +} + +impl Default for CorpusBuilder { + fn default() -> Self { + Self::new(StructuralEmbedder) + } +} + +impl CorpusBuilder { + /// Build with `embedder`, emitting positives only. + pub fn new(embedder: E) -> Self { + Self { + embedder, + monotone_negatives: false, + } + } + + /// Also emit a negative row for each cheaper stage that a monotone + /// cascade must have tried and escalated past. + /// + /// **This rests on an assumption — read before enabling.** A receipt + /// records the tier that *closed* the walk, not the tiers walked. In a + /// strict cheapest-first cascade, closing at `Neural` means `Cache` and + /// `Kv` were attempted and did not satisfy the query, which is a real + /// negative label — and the pairing of "this rung failed, that rung + /// closed it" is the whole supervision signal. A router that skips + /// rungs breaks that inference, and these rows become noise. + /// + /// `Graph` is never emitted as a negative: no JouleClaw tier maps to it, + /// so its absence from a walk is not evidence it was tried and failed. + pub fn with_monotone_negatives(mut self, yes: bool) -> Self { + self.monotone_negatives = yes; + self + } + + /// Resolve the stage a receipt closed at, preferring the runtime's own + /// label over the tier mapping. + fn stage_of(receipt: &Receipt) -> Option { + receipt + .eoc_stage + .as_deref() + .and_then(CorpusStage::from_eoc_label) + .or_else(|| CorpusStage::from_tier(receipt.tier)) + } + + /// Project one receipt. Returns an empty vec when it cannot be mapped. + pub fn rows_from_receipt(&self, receipt: &Receipt) -> Vec { + let Some(stage) = Self::stage_of(receipt) else { + return Vec::new(); + }; + let embedding = self.embedder.embed(receipt); + let mut rows = vec![CorpusRow { + embedding: embedding.clone(), + stage, + success: true, + cost_microjoules: receipt.joules_uj, + }]; + + if self.monotone_negatives { + for cheaper in [CorpusStage::Cache, CorpusStage::Kv] { + if cheaper.rank() < stage.rank() { + rows.push(CorpusRow { + embedding: embedding.clone(), + stage: cheaper, + success: false, + cost_microjoules: receipt.joules_uj, + }); + } + } + } + rows + } + + /// Project a batch of receipts, reporting what was used and skipped. + pub fn build(&self, receipts: &[Receipt]) -> (Vec, CorpusStats) { + let mut out = Vec::new(); + let mut stats = CorpusStats { + receipts_seen: receipts.len(), + ..Default::default() + }; + for r in receipts { + let rows = self.rows_from_receipt(r); + if rows.is_empty() { + stats.skipped_unmapped += 1; + continue; + } + stats.receipts_used += 1; + for row in &rows { + if row.success { + stats.rows_positive += 1; + } else { + stats.rows_negative += 1; + } + } + out.extend(rows); + } + (out, stats) + } +} + +// ───────────────────────────────────────────────────────────────────── +// Sinks +// ───────────────────────────────────────────────────────────────────── + +/// Encode rows as JSONL — one row per line, trainer-ready. +pub fn to_jsonl(rows: &[CorpusRow]) -> Result { + let mut s = String::new(); + for row in rows { + let line = serde_json::to_string(row).map_err(|e| Error::Encode(e.to_string()))?; + s.push_str(&line); + s.push('\n'); + } + Ok(s) +} + +/// Write rows to a JSONL file the trainer's loader can read directly. +pub fn write_jsonl(path: impl AsRef, rows: &[CorpusRow]) -> Result<()> { + let body = to_jsonl(rows)?; + let mut f = std::fs::File::create(path).map_err(|e| Error::Write(e.to_string()))?; + f.write_all(body.as_bytes()) + .map_err(|e| Error::Write(e.to_string())) +} + +#[cfg(test)] +mod tests { + use super::*; + use jouleclaw_intent::IntentVerdict; + + /// Struct-literal construction, as the conformance-vector generators do: + /// the builder *accounts* joules from tool/claim costs rather than taking + /// an asserted total, and these tests need an exact `joules_uj`. + fn receipt(tier: CascadeTier, uj: u64) -> Receipt { + Receipt { + context: None, + jc_receipt: "1".into(), + id: "rcpt-test".into(), + closed_at: "2026-07-20T00:00:00Z".into(), + input_hash: "a".repeat(64), + tier, + joules_uj: uj, + energy_provenance: Provenance::HwShunt, + tools_touched: Vec::new(), + claims: Vec::new(), + eoc_stage: None, + citations: Vec::new(), + tool_receipts: Vec::new(), + token_attributions: Vec::new(), + model_id: None, + intent: None, + steward: None, + } + } + + #[test] + fn stage_wire_form_is_pinned() { + // The consuming trainer rejects any other spelling. If this test + // fails, downstream ingest is already broken. + for (stage, expected) in [ + (CorpusStage::Cache, "\"Cache\""), + (CorpusStage::Kv, "\"Kv\""), + (CorpusStage::Graph, "\"Graph\""), + (CorpusStage::Neural, "\"Neural\""), + ] { + assert_eq!(serde_json::to_string(&stage).unwrap(), expected); + } + } + + #[test] + fn row_matches_trainer_field_shape() { + let row = CorpusRow { + embedding: vec![0.5, 0.25], + stage: CorpusStage::Neural, + success: true, + cost_microjoules: 1234, + }; + assert_eq!( + serde_json::to_string(&row).unwrap(), + r#"{"embedding":[0.5,0.25],"stage":"Neural","success":true,"cost_microjoules":1234}"# + ); + } + + #[test] + fn absorbs_lowercase_receipt_label() { + // The exact trap this crate exists to absorb. + assert_eq!( + CorpusStage::from_eoc_label("neural"), + Some(CorpusStage::Neural) + ); + assert_eq!(CorpusStage::from_eoc_label("kv"), Some(CorpusStage::Kv)); + assert_eq!(CorpusStage::from_eoc_label("Neural"), Some(CorpusStage::Neural)); + assert_eq!(CorpusStage::from_eoc_label("nonsense"), None); + } + + #[test] + fn lawful_tier_is_skipped_not_guessed() { + assert_eq!(CorpusStage::from_tier(CascadeTier::L1Lawful), None); + let (rows, stats) = CorpusBuilder::default().build(&[receipt(CascadeTier::L1Lawful, 10)]); + assert!(rows.is_empty()); + assert_eq!(stats.skipped_unmapped, 1); + assert_eq!(stats.receipts_used, 0); + } + + #[test] + fn eoc_label_wins_over_tier_mapping() { + let mut r = receipt(CascadeTier::L3Model, 900); + r.eoc_stage = Some("graph".into()); + let rows = CorpusBuilder::default().rows_from_receipt(&r); + // Tier alone would have said Neural; the runtime's own label wins. + assert_eq!(rows[0].stage, CorpusStage::Graph); + } + + #[test] + fn cost_is_verbatim() { + let rows = CorpusBuilder::default().rows_from_receipt(&receipt(CascadeTier::L0Cache, 7)); + assert_eq!(rows[0].cost_microjoules, 7); + assert!(rows[0].success); + assert_eq!(rows[0].stage, CorpusStage::Cache); + } + + #[test] + fn monotone_negatives_pair_failure_with_closure() { + let rows = CorpusBuilder::default() + .with_monotone_negatives(true) + .rows_from_receipt(&receipt(CascadeTier::L3Model, 5_000)); + assert_eq!(rows.len(), 3); + assert_eq!(rows[0].stage, CorpusStage::Neural); + assert!(rows[0].success); + // Cheaper rungs a monotone cascade must have escalated past. + assert!(rows[1..].iter().all(|r| !r.success)); + let neg: Vec<_> = rows[1..].iter().map(|r| r.stage).collect(); + assert_eq!(neg, vec![CorpusStage::Cache, CorpusStage::Kv]); + } + + #[test] + fn graph_is_never_a_synthesised_negative() { + // Nothing maps to Graph, so its absence is not evidence of failure. + let rows = CorpusBuilder::default() + .with_monotone_negatives(true) + .rows_from_receipt(&receipt(CascadeTier::L4Wire, 80_000)); + assert!(!rows.iter().any(|r| r.stage == CorpusStage::Graph)); + } + + #[test] + fn cheapest_tier_emits_no_negatives() { + let rows = CorpusBuilder::default() + .with_monotone_negatives(true) + .rows_from_receipt(&receipt(CascadeTier::L0Cache, 1)); + assert_eq!(rows.len(), 1); + } + + #[test] + fn embedding_never_leaks_the_label() { + // Two receipts identical but for tier and cost must embed the same; + // otherwise the router can read its own answer out of the features. + let cheap = receipt(CascadeTier::L0Cache, 1); + let dear = receipt(CascadeTier::L4Wire, 900_000); + let e = StructuralEmbedder; + assert_eq!(e.embed(&cheap), e.embed(&dear)); + } + + #[test] + fn embedding_dim_is_stable_and_declared() { + let e = StructuralEmbedder; + let mut r = receipt(CascadeTier::L3Model, 10); + assert_eq!(e.embed(&r).len(), e.dim()); + r.intent = Some(IntentVerdict::new(QueryIntent::Review, 0.75, "heuristic:v1")); + let v = e.embed(&r); + assert_eq!(v.len(), STRUCTURAL_DIM); + assert_eq!(v[4], 1.0, "Review one-hot"); + assert!((v[6] - 0.75).abs() < 1e-6, "confidence carried"); + } + + #[test] + fn stats_account_for_every_receipt() { + let batch = vec![ + receipt(CascadeTier::L0Cache, 1), + receipt(CascadeTier::L1Lawful, 2), // unmappable + receipt(CascadeTier::L3Model, 3), + ]; + let (rows, stats) = CorpusBuilder::default().build(&batch); + assert_eq!(stats.receipts_seen, 3); + assert_eq!(stats.receipts_used, 2); + assert_eq!(stats.skipped_unmapped, 1); + assert_eq!(stats.receipts_used + stats.skipped_unmapped, stats.receipts_seen); + assert_eq!(rows.len(), stats.rows()); + } + + #[test] + fn jsonl_round_trips() { + let (rows, _) = CorpusBuilder::default().build(&[receipt(CascadeTier::L3Model, 42)]); + let text = to_jsonl(&rows).unwrap(); + assert_eq!(text.lines().count(), 1); + let back: CorpusRow = serde_json::from_str(text.lines().next().unwrap()).unwrap(); + assert_eq!(back, rows[0]); + } + + #[test] + fn writes_a_file_the_loader_can_read() { + let dir = tempfile::tempdir().unwrap(); + let path = dir.path().join("corpus.jsonl"); + let (rows, _) = CorpusBuilder::default() + .with_monotone_negatives(true) + .build(&[receipt(CascadeTier::L3Model, 42)]); + write_jsonl(&path, &rows).unwrap(); + let text = std::fs::read_to_string(&path).unwrap(); + assert_eq!(text.lines().count(), 3); + for line in text.lines() { + serde_json::from_str::(line).unwrap(); + } + } +} diff --git a/sandbox/sandbox-rs/Cargo.lock b/sandbox/sandbox-rs/Cargo.lock index 7256b7d06..027783422 100644 --- a/sandbox/sandbox-rs/Cargo.lock +++ b/sandbox/sandbox-rs/Cargo.lock @@ -1874,6 +1874,7 @@ dependencies = [ name = "sandbox-proof" version = "0.1.0" dependencies = [ + "blake3", "ed25519-dalek", "hex", "sandbox-attest", diff --git a/sandbox/sandbox-rs/crates/sandbox-proof/Cargo.toml b/sandbox/sandbox-rs/crates/sandbox-proof/Cargo.toml index b5bcb3f56..16cfa1053 100644 --- a/sandbox/sandbox-rs/crates/sandbox-proof/Cargo.toml +++ b/sandbox/sandbox-rs/crates/sandbox-proof/Cargo.toml @@ -26,4 +26,5 @@ serde = { workspace = true } serde_json.workspace = true ureq.workspace = true hex.workspace = true +blake3.workspace = true thiserror.workspace = true diff --git a/sandbox/sandbox-rs/crates/sandbox-proof/src/lib.rs b/sandbox/sandbox-rs/crates/sandbox-proof/src/lib.rs index 7e62f644b..f24114cc6 100644 --- a/sandbox/sandbox-rs/crates/sandbox-proof/src/lib.rs +++ b/sandbox/sandbox-rs/crates/sandbox-proof/src/lib.rs @@ -981,6 +981,245 @@ pub fn prove_confidential( pub mod matrix; +// ───────────────────────────────────────────────────────────────────── +// Local (air-gapped) inference +// ───────────────────────────────────────────────────────────────────── + +/// Inputs for a **local** inference proof: an engine to run, and the weights +/// it runs against. Both live on this machine; nothing is fetched. +#[derive(Clone, Debug)] +pub struct LocalInferenceInputs { + /// The local inference engine to execute. + pub engine: String, + /// Arguments passed to the engine. + pub args: Vec, + /// Path to the model weights on local disk. Hashed into the proof, so the + /// cell attests inference against *these bytes* and not merely "a model". + pub weights: String, +} + +/// Stream a BLAKE3 over a file without loading it into memory — weights are +/// routinely gigabytes. +fn hash_file(path: &std::path::Path) -> Result { + use std::io::Read; + let mut f = std::fs::File::open(path) + .map_err(|e| ProofError::Conformance(format!("weights unreadable: {e}")))?; + let mut hasher = blake3::Hasher::new(); + let mut buf = vec![0u8; 1 << 20]; + loop { + let n = f + .read(&mut buf) + .map_err(|e| ProofError::Conformance(format!("weights read failed: {e}")))?; + if n == 0 { + break; + } + hasher.update(&buf[..n]); + } + Ok(hasher.finalize().to_hex().to_string()) +} + +/// Prove **local, offline inference**: run a local engine against local weights +/// with no network grant, meter it on this host's own energy counters, and seal +/// a cell. +/// +/// This is the deliberate inverse of [`prove_inference`]. There, the silicon +/// belongs to somebody else, so the energy is an honest token `Estimator` and +/// the call goes over the wire. Here the silicon is the prover's own, which is +/// the one case where the joules can actually be *measured* — so the device +/// records come straight off [`PowerMeter`] with whatever provenance the +/// hardware honestly supports (`HwShunt` on Apple Silicon IOReport and Linux +/// RAPL/NVML; `Estimator` where no counter exists). The meter can degrade; it +/// does not invent a reading, and this function does not re-tag one. +/// +/// Three properties the cell attests, each checked rather than asserted: +/// +/// 1. **The weights are local.** They are opened from disk and streamed into a +/// BLAKE3 that is bound into the proof. A path that does not resolve is an +/// error, not a warning. +/// 2. **No egress was granted.** The run's [`NetworkGrant`] is the default — +/// empty outbound allowlist, inbound false — and that emptiness is asserted +/// before the engine is spawned. Deny-by-default is the standard's posture; +/// this makes it a checked precondition of the proof. +/// 3. **The energy is this host's.** Measured over the engine's execution span +/// by the best counter the platform offers. +/// +/// The engine is supplied by the operator and driven as a subprocess, exactly +/// as [`prove_accelerator`] drives a GPU workload. That keeps the sandbox +/// standard free of any dependency on a particular inference runtime — this +/// proves *local inference*, not one vendor's loader. +pub fn prove_local( + inputs: &LocalInferenceInputs, + cloud: &str, + at_unix_us: u64, + proof_key: &SigningKey, +) -> Result { + use ed25519_dalek::SigningKey as Sk; + use sandbox_attest::{DeviceIdentity, Outcome}; + use sandbox_capability::{ComputeGrant, Grants}; + use sandbox_meter::PowerMeter; + use sandbox_runtime::{grants_content_id, unit_content_id, Host, RunReport}; + + let report = sandbox_conformance::run(); + let conformance: Vec = report + .results + .iter() + .map(|r| ProofClass { + class: r.class.to_string(), + passed: r.passed, + }) + .collect(); + if !report.ok() { + return Err(ProofError::Conformance("in-code pack did not pass".into())); + } + + // (1) Weights are local, and the proof is bound to their exact bytes. + let weights_path = std::path::Path::new(&inputs.weights); + if !weights_path.is_file() { + return Err(ProofError::Conformance(format!( + "weights not present locally: {}", + inputs.weights + ))); + } + let weights_bytes = std::fs::metadata(weights_path) + .map(|m| m.len()) + .unwrap_or(0); + let weights_hash = hash_file(weights_path)?; + + // (2) Deny-by-default egress, asserted rather than assumed. + let grants = Grants { + compute: ComputeGrant { + devices: vec![DeviceClass::Cpu, DeviceClass::Gpu, DeviceClass::Npu], + energy_budget_uj: u64::MAX, + cpu_time_ms: 600_000, + memory_bytes: 64u64 << 30, + }, + ..Grants::default() + }; + if !grants.network.outbound.is_empty() || grants.network.inbound { + return Err(ProofError::Conformance( + "local proof requires a zero-egress network grant".into(), + )); + } + + // (3) Meter this host's own counters across the engine's execution. + let mut meter = PowerMeter::detect(); + let sources = meter.sources().join(","); + let is_hw = meter.is_hardware(); + let (status, measurement) = meter.measure(|| { + std::process::Command::new(&inputs.engine) + .args(&inputs.args) + .status() + }); + let status = status.map_err(|e| { + ProofError::Run( + sandbox_runtime::ExecError::Unavailable(format!("spawn {}: {e}", inputs.engine)).into(), + ) + })?; + if !status.success() { + return Err(ProofError::Run( + sandbox_runtime::ExecError::Unavailable(format!( + "engine exited with {status}; refusing to seal a proof for a run that failed" + )) + .into(), + )); + } + + // Device records pass through verbatim — the meter's provenance is the + // proof's provenance. Re-tagging here is exactly the lie the ladder exists + // to prevent. + // One identity per metered class — SPEC §7.2 requires every device that + // reports energy to be named. The meter decides which classes it can see + // (on Apple Silicon that is CPU, GPU and ANE together), so the identity + // list is derived from the measurement rather than assumed. + let device_identity: Vec = measurement + .devices + .iter() + .map(|d| DeviceIdentity { + class: d.class, + model: std::env::consts::ARCH.to_string(), + driver: sources.clone(), + }) + .collect(); + let run = RunReport { + device_energy: measurement.devices.clone(), + device_identity, + outcome: Outcome::Completed, + journal: None, + storage: None, + }; + + let host = Host::new( + "sandbox-proof", + "steward.proof", + Sk::from_bytes(&[51u8; 32]), + Sk::from_bytes(&[52u8; 32]), + ); + let (receipt_vk, supervisor_vk) = host.verifying_keys(); + // The image identity *is* the weights: two runs over different weights are + // different units, and the content id says so. + let image_id = ContentId::of_bytes( + format!("sandbox-local-inference:{weights_hash}").as_bytes(), + ); + let grants_id = grants_content_id(&grants); + let unit_id = unit_content_id(&image_id, &grants_id); + let sealed = host.seal_run( + unit_id, + image_id.clone(), + &grants, + Tier::S0, + run, + 1, + [0; 8], + at_unix_us, + at_unix_us, + )?; + sealed + .receipt + .verify_sealed(&sealed.receipt_envelope, &receipt_vk, Some("steward.proof")) + .map_err(|e| ProofError::Crypto(format!("receipt: {e}")))?; + sealed + .attestation + .verify(&sealed.attestation_sig, &supervisor_vk) + .map_err(|e| ProofError::Crypto(format!("attestation: {e}")))?; + + eprintln!( + " local: {} ({:.2} GiB, b3:{}…) — {} over {:.1}s, avg {:.1} W [{}]", + weights_path + .file_name() + .map(|s| s.to_string_lossy().to_string()) + .unwrap_or_default(), + weights_bytes as f64 / (1u64 << 30) as f64, + &weights_hash[..12], + if is_hw { "measured" } else { "ESTIMATED" }, + measurement.wall.as_secs_f64(), + measurement.avg_watts(), + sources, + ); + + let hardware: Vec = sealed.receipt.devices.iter().map(|d| d.class).collect(); + let attestation_id = sealed + .attestation + .content_id() + .map_err(|e| ProofError::Crypto(format!("attestation id: {e}")))?; + let cell = ProofCell { + profile: PROFILE.to_string(), + substrate: Substrate::BareMetal, + os: std::env::consts::OS.to_string(), + arch: std::env::consts::ARCH.to_string(), + cloud: cloud.to_string(), + hardware, + tier: Tier::S0, + conformance, + attestation_id, + receipt_id: sealed.receipt.content_id(), + total_estimated_uj: sealed.receipt.total.estimated_uj, + available_substrates: available_substrates(), + host_id: "sandbox-proof".to_string(), + at_unix_us, + }; + sign(cell, proof_key) +} + #[cfg(test)] mod tests { use super::*; @@ -1094,4 +1333,76 @@ mod tests { 12 * J_IN_UJ + 5 * J_OUT_UJ ); } + + // ── local (air-gapped) inference proofs ────────────────────────── + + fn local_inputs(engine: &str, weights: &str) -> LocalInferenceInputs { + LocalInferenceInputs { + engine: engine.to_string(), + args: vec![], + weights: weights.to_string(), + } + } + + #[test] + fn local_proof_refuses_absent_weights() { + // An air-gap proof whose weights are not actually on this disk is + // exactly the claim that must not be sealable. + let sk = SigningKey::from_bytes(&[9u8; 32]); + let r = prove_local( + &local_inputs("/bin/true", "/nonexistent/model.gguf"), + "local", + 0, + &sk, + ); + match r { + Err(ProofError::Conformance(m)) => assert!(m.contains("not present locally"), "{m}"), + other => panic!("expected a conformance refusal, got {other:?}"), + } + } + + #[test] + fn local_proof_refuses_a_failed_engine() { + // Sealing a proof for a run that exited non-zero would attest to work + // that did not happen. + let dir = std::env::temp_dir().join("sandbox-proof-local-test"); + std::fs::create_dir_all(&dir).unwrap(); + let w = dir.join("weights.bin"); + std::fs::write(&w, b"not a real model, but really on disk").unwrap(); + let sk = SigningKey::from_bytes(&[9u8; 32]); + let r = prove_local( + &local_inputs("/bin/false", w.to_str().unwrap()), + "local", + 0, + &sk, + ); + assert!( + matches!(r, Err(ProofError::Run(_))), + "expected a run refusal, got {r:?}" + ); + } + + #[test] + fn local_proof_binds_the_exact_weight_bytes() { + // Two different models must not produce the same unit identity. + let dir = std::env::temp_dir().join("sandbox-proof-local-hash"); + std::fs::create_dir_all(&dir).unwrap(); + let a = dir.join("a.bin"); + let b = dir.join("b.bin"); + std::fs::write(&a, b"weights-a").unwrap(); + std::fs::write(&b, b"weights-b").unwrap(); + let ha = hash_file(&a).unwrap(); + let hb = hash_file(&b).unwrap(); + assert_ne!(ha, hb); + assert_eq!(ha, hash_file(&a).unwrap(), "hash must be stable"); + assert_eq!(ha.len(), 64, "blake3 hex"); + } + + #[test] + fn local_proof_grant_is_zero_egress() { + // The posture the proof asserts before spawning anything. + let g = sandbox_capability::Grants::default(); + assert!(g.network.outbound.is_empty()); + assert!(!g.network.inbound); + } } diff --git a/sandbox/sandbox-rs/crates/sandbox-proof/src/main.rs b/sandbox/sandbox-rs/crates/sandbox-proof/src/main.rs index 2ae11a4dc..f9e7ef2fe 100644 --- a/sandbox/sandbox-rs/crates/sandbox-proof/src/main.rs +++ b/sandbox/sandbox-rs/crates/sandbox-proof/src/main.rs @@ -21,6 +21,7 @@ fn main() { match cmd { "prove" => prove(&path), "prove-s5" => prove_s5(&path), + "prove-local" => prove_local(&path), "prove-gpu" => prove_gpu(&path), "prove-lpu" => prove_lpu(&path), "prove-s6" => prove_s6(&path), @@ -131,6 +132,48 @@ fn prove_s5(path: &str) { println!("\n appended to {path}"); } +/// Prove local, offline inference: run a local engine against local weights +/// with no network grant, metered on this host's own energy counters. +fn prove_local(path: &str) { + let env = |k: &str, d: &str| std::env::var(k).unwrap_or_else(|_| d.to_string()); + let weights = env("SANDBOX_LOCAL_WEIGHTS", ""); + if weights.is_empty() { + eprintln!("set SANDBOX_LOCAL_WEIGHTS=/path/to/model.gguf"); + std::process::exit(2); + } + let engine = env("SANDBOX_LOCAL_ENGINE", ""); + if engine.is_empty() { + eprintln!("set SANDBOX_LOCAL_ENGINE=/path/to/inference-binary"); + std::process::exit(2); + } + let args: Vec = std::env::var("SANDBOX_LOCAL_ARGS") + .ok() + .map(|s| s.split_whitespace().map(String::from).collect()) + .unwrap_or_else(|| vec![weights.clone()]); + let inputs = sandbox_proof::LocalInferenceInputs { engine, args, weights }; + let cloud = env("SANDBOX_CLOUD", "local"); + let cell = match sandbox_proof::prove_local(&inputs, &cloud, now_us(), &proof_key()) { + Ok(c) => c, + Err(e) => { + eprintln!("local proof failed: {e}"); + std::process::exit(1); + } + }; + if let Err(e) = Matrix::append(path, &cell) { + eprintln!("could not append to {path}: {e}"); + std::process::exit(1); + } + let c = &cell.cell; + println!("Sandbox proof — local inference (offline, zero egress)\n"); + println!(" substrate: {}", c.substrate.label()); + println!(" os / arch: {} / {}", c.os, c.arch); + println!(" tier: {}", c.tier.wire_tag()); + println!(" attestation: {}", c.attestation_id); + println!(" receipt: {} ({} uj)", c.receipt_id, c.total_estimated_uj); + println!(" signature: {} (verified \u{2713})", &cell.signature_hex[..16]); + println!("\n appended to {path}"); +} + /// Prove the accelerator (GPU) fabric: run a GPU workload, meter it via /// nvidia-smi, seal a cell with a separate Gpu energy record + GPU identity. fn prove_gpu(path: &str) {