diff --git a/.cargo/config.toml b/.cargo/config.toml index ca9b54364ae..bdb3215f86a 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,6 +1,12 @@ [registries.crates-io] protocol = "sparse" +# x86-64 builds assume the x86-64-v3 baseline (AVX2, FMA, BMI2): the math kernels pin fused +# multiply-add semantics and take the hardware lowering from it. `hash-graph-atlas` verifies the +# baseline at process entry, turning a mismatched machine into a diagnosable failure. +[target.'cfg(target_arch = "x86_64")'] +rustflags = ["-Ctarget-cpu=x86-64-v3"] + [unstable] cargo-lints = true codegen-backend = true diff --git a/.env b/.env index c8b38eadca9..71478b5428f 100644 --- a/.env +++ b/.env @@ -42,6 +42,8 @@ HASH_GRAPH_HTTP_HOST=127.0.0.1 HASH_GRAPH_HTTP_PORT=4000 HASH_GRAPH_RPC_HOST=127.0.0.1 HASH_GRAPH_RPC_PORT=4002 +HASH_GRAPH_ATLAS_HOST=127.0.0.1 +HASH_GRAPH_ATLAS_PORT=4003 HASH_GRAPH_EMBED_TYPE_FETCHER=true HASH_GRAPH_TYPE_FETCHER_HOST=localhost HASH_GRAPH_TYPE_FETCHER_PORT=4455 diff --git a/.env.development b/.env.development index 1fe9b287fe5..0ed96536a31 100644 --- a/.env.development +++ b/.env.development @@ -14,6 +14,9 @@ AWS_S3_UPLOADS_SECRET_ACCESS_KEY="dev-s3-secret-access-key" AWS_S3_UPLOADS_FORCE_PATH_STYLE=true FILE_UPLOAD_PROVIDER="AWS_S3" +# Development only dev secret for atlas +HASH_GRAPH_ATLAS_SECRET=de70de70de70de70de70de70de70de70de70de70de70de70de70de70de70de70 + # Feature flags SHOW_WORKER_COST=true diff --git a/Cargo.lock b/Cargo.lock index 44052f0507a..e11633f6f39 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -67,6 +67,7 @@ dependencies = [ "cfg-if", "getrandom 0.3.4", "once_cell", + "serde", "version_check", "zerocopy", ] @@ -80,6 +81,75 @@ dependencies = [ "memchr", ] +[[package]] +name = "aide" +version = "0.16.0-alpha.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "390515b47251185fa076ac92a7a582d9d383b03e13cef0c801e7670cf928229b" +dependencies = [ + "aide-macros", + "axum", + "bytes", + "cfg-if", + "http 1.4.2", + "indexmap 2.14.0", + "schemars 1.2.1", + "serde", + "serde_json", + "serde_qs", + "thiserror 2.0.18", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "aide-macros" +version = "0.16.0-alpha.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44db8ab6bd2a6748ce7c948a4813456bd95ec3bfdbe1635d52f4a0276d0fa4f0" +dependencies = [ + "darling 0.23.0", + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "alga" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f823d037a7ec6ea2197046bafd4ae150e6bc36f9ca347404f46a46823fa84f2" +dependencies = [ + "approx 0.3.2", + "num-complex 0.2.4", + "num-traits", +] + +[[package]] +name = "aligned" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee4508988c62edf04abd8d92897fca0c2995d907ce1dfeaf369dac3716a40685" +dependencies = [ + "as-slice", +] + +[[package]] +name = "aligned-vec" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4aa90d7ce82d4be67b64039a3d588d38dbcc6736577de4a847025ce5b0c468d1" + +[[package]] +name = "aligned-vec" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc890384c8602f339876ded803c97ad529f3842aba97f6392b3dba0dd171769b" +dependencies = [ + "equator", +] + [[package]] name = "alloca" version = "0.4.0" @@ -241,6 +311,15 @@ version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +[[package]] +name = "approx" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0e60b75072ecd4168020818c0107f2857bb6c4e64252d8d3983f6263b40a5c3" +dependencies = [ + "num-traits", +] + [[package]] name = "approx" version = "0.5.1" @@ -259,6 +338,12 @@ dependencies = [ "object", ] +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" + [[package]] name = "arc-swap" version = "1.9.1" @@ -277,6 +362,23 @@ dependencies = [ "triomphe", ] +[[package]] +name = "arg_enum_proc_macro" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "array-init" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d62b7694a562cdf5a74227903507c56ab2cc8bdd1f781ed5cb4cf9c9f810bfc" + [[package]] name = "arrayref" version = "0.3.9" @@ -295,6 +397,15 @@ version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f02882884d3e1bc524fb12c79f107f6ad0e1cfd498c536ffb494301740995dfe" +[[package]] +name = "as-slice" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "516b6b4f0e40d50dcda9365d53964ec74560ad4284da2e7fc97122cd83174516" +dependencies = [ + "stable_deref_trait", +] + [[package]] name = "ascii-canvas" version = "4.0.0" @@ -304,6 +415,15 @@ dependencies = [ "term", ] +[[package]] +name = "ash" +version = "0.38.0+1.3.281" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f" +dependencies = [ + "libloading 0.8.8", +] + [[package]] name = "asn1-rs" version = "0.7.2" @@ -353,7 +473,7 @@ dependencies = [ "futures-core", "libc", "portable-atomic", - "rustc-hash", + "rustc-hash 2.1.2", "tokio", "tokio-stream", "xattr", @@ -399,6 +519,17 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "async-lock" +version = "3.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + [[package]] name = "async-scoped" version = "0.9.0" @@ -471,13 +602,19 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +[[package]] +name = "atomic_float" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "628d228f918ac3b82fe590352cc719d30664a0c13ca3a60266fe02c7132d480a" + [[package]] name = "attohttpc" version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16e2cdb6d5ed835199484bb92bb8b3edd526effe995c61732580439c1a67e2e9" dependencies = [ - "base64", + "base64 0.22.1", "http 1.4.2", "log", "url", @@ -489,6 +626,49 @@ version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" +[[package]] +name = "av-scenechange" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f321d77c20e19b92c39e7471cf986812cbb46659d2af674adc4331ef3f18394" +dependencies = [ + "aligned", + "anyhow", + "arg_enum_proc_macro", + "arrayvec 0.7.7", + "log", + "num-rational", + "num-traits", + "pastey", + "rayon", + "thiserror 2.0.18", + "v_frame", + "y4m", +] + +[[package]] +name = "av1-grain" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f3efb2ca85bc610acfa917b5aaa36f3fcbebed5b3182d7f877b02531c4b80c8" +dependencies = [ + "anyhow", + "arrayvec 0.7.7", + "log", + "nom 7.1.3", + "num-rational", + "v_frame", +] + +[[package]] +name = "avif-serialize" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47c8fbc0f831f4519fe8b810b6a7a91410ec83031b8233f730a0480029f6a23f" +dependencies = [ + "arrayvec 0.7.7", +] + [[package]] name = "aws-config" version = "1.8.18" @@ -957,6 +1137,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90" dependencies = [ "axum-core", + "base64 0.22.1", "bytes", "form_urlencoded", "futures-util", @@ -975,8 +1156,10 @@ dependencies = [ "serde_json", "serde_path_to_error", "serde_urlencoded", + "sha1 0.10.6", "sync_wrapper", "tokio", + "tokio-tungstenite", "tower", "tower-layer", "tower-service", @@ -1002,6 +1185,12 @@ dependencies = [ "tracing", ] +[[package]] +name = "az" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973" + [[package]] name = "backoff" version = "0.4.0" @@ -1050,6 +1239,12 @@ dependencies = [ "match-lookup", ] +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + [[package]] name = "base64" version = "0.22.1" @@ -1072,6 +1267,45 @@ version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bincode" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740" +dependencies = [ + "serde", + "unty", +] + +[[package]] +name = "bindgen" +version = "0.71.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3" +dependencies = [ + "bitflags 2.13.0", + "cexpr", + "clang-sys", + "itertools 0.13.0", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash 2.1.2", + "shlex 1.3.0", + "syn 2.0.118", +] + [[package]] name = "bit-set" version = "0.5.3" @@ -1090,6 +1324,15 @@ dependencies = [ "bit-vec 0.8.0", ] +[[package]] +name = "bit-set" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34ddef2995421ab6a5c779542c81ee77c115206f4ad9d5a8e05f4ff49716a3dd" +dependencies = [ + "bit-vec 0.9.1", +] + [[package]] name = "bit-vec" version = "0.6.3" @@ -1102,6 +1345,18 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" +[[package]] +name = "bit-vec" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71798fca2c1fe1086445a7258a4bc81e6e49dcd24c8d0dd9a1e57395b603f51" + +[[package]] +name = "bit_field" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6" + [[package]] name = "bitflags" version = "1.3.2" @@ -1117,6 +1372,15 @@ dependencies = [ "serde_core", ] +[[package]] +name = "bitstream-io" +version = "4.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eff00be299a18769011411c9def0d827e8f2d7bf0c3dbf53633147a8867fd1f" +dependencies = [ + "no_std_io2", +] + [[package]] name = "bitvec" version = "1.1.1" @@ -1156,13 +1420,22 @@ dependencies = [ "hybrid-array", ] +[[package]] +name = "block2" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" +dependencies = [ + "objc2 0.5.2", +] + [[package]] name = "block2" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" dependencies = [ - "objc2", + "objc2 0.6.4", ] [[package]] @@ -1172,7 +1445,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee04c4c84f1f811b017f2fbb7dd8815c976e7ca98593de9c1e2afad0f636bff4" dependencies = [ "async-stream", - "base64", + "base64 0.22.1", "bitflags 2.13.0", "bollard-buildkit-proto", "bollard-stubs", @@ -1220,7 +1493,7 @@ dependencies = [ "prost-types", "tonic", "tonic-prost", - "ureq", + "ureq 3.3.0", ] [[package]] @@ -1229,7 +1502,7 @@ version = "1.52.1-rc.29.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f0a8ca8799131c1837d1282c3f81f31e76ceb0ce426e04a7fe1ccee3287c066" dependencies = [ - "base64", + "base64 0.22.1", "bollard-buildkit-proto", "bytes", "prost", @@ -1255,7 +1528,7 @@ version = "3.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dee98b0db6a962de883bf5d20362dee4d7ca0d12fe39a7c6c73c844e1cd7c1f" dependencies = [ - "darling", + "darling 0.21.3", "ident_case", "prettyplease", "proc-macro2", @@ -1294,6 +1567,12 @@ dependencies = [ "serde", ] +[[package]] +name = "built" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4ad8f11f288f48ca24471bbd51ac257aaeaaa07adae295591266b792902ae64" + [[package]] name = "bump-scope" version = "2.3.1" @@ -1315,748 +1594,2003 @@ dependencies = [ ] [[package]] -name = "by_address" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06" - -[[package]] -name = "bytemuck" -version = "1.25.0" +name = "burn" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" +checksum = "39474be398d30e08681f1f6a8ff446b1e4f1403b5cf7749649a8871fd5945f3a" +dependencies = [ + "burn-autodiff", + "burn-candle", + "burn-collective", + "burn-core", + "burn-cpu", + "burn-cuda", + "burn-dispatch", + "burn-flex", + "burn-ndarray", + "burn-nn", + "burn-optim", + "burn-remote", + "burn-rocm", + "burn-router", + "burn-std", + "burn-store", + "burn-tch", + "burn-train", + "burn-vision", + "burn-wgpu", +] [[package]] -name = "byteorder" -version = "1.5.0" +name = "burn-autodiff" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +checksum = "3b93a80e43bfab909399444b29ce3894ff51f7e879720bfc75b6007ae6a01c3d" +dependencies = [ + "burn-backend", + "burn-std", + "derive-new", + "hashbrown 0.16.1", + "log", + "num-traits", + "parking_lot", + "portable-atomic", + "spin", + "tracing", +] [[package]] -name = "bytes" -version = "1.12.0" +name = "burn-backend" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593" +checksum = "64491519ac0867f550fa9c726cd443e5db94608c629050986cf2614c8c5ad39f" dependencies = [ + "burn-std", + "bytemuck", + "cubecl", + "derive-new", + "enumset", + "hashbrown 0.16.1", + "num-traits", + "portable-atomic-util", + "rand 0.10.1", + "rand_distr 0.6.0", "serde", + "spin", + "thiserror 2.0.18", ] [[package]] -name = "bytes-utils" -version = "0.1.4" +name = "burn-candle" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dafe3a8757b027e2be6e4e5601ed563c55989fcf1546e933c66c8eb3a058d35" +checksum = "917fbbe1238d80ec1483c2c360ed9fa0146fdf57b2e66015c0824ccf5155276c" dependencies = [ - "bytes", - "either", + "burn-backend", + "burn-std", + "candle-core", + "derive-new", ] [[package]] -name = "camino" -version = "1.2.3" +name = "burn-collective" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ce8d3bd5823c7504d3f579f13e7b2f3da252fcb938c594d5680ee508bf846f" +checksum = "427bdb9240bd1a41199066d44bc042341aa0cb0f46590d9af15eb88dd81066e7" dependencies = [ - "serde_core", + "burn-backend", + "burn-communication", + "burn-std", + "bytes", + "futures", + "log", + "rmp-serde", + "serde", + "tokio", + "tokio-util", ] [[package]] -name = "cargo-platform" -version = "0.3.3" +name = "burn-communication" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd0061da739915fae12ea00e16397555ed4371a6bb285431aab930f61b0aa4ba" +checksum = "0da93381634fb6ef2fbf9a19415439b1658bcdf2e044a8909caa54b8960e63bb" dependencies = [ + "axum", + "burn-std", + "burn-tensor", + "bytes", + "derive-new", + "futures", + "futures-util", + "log", + "rmp-serde", "serde", - "serde_core", + "serde_bytes", + "tokio", + "tokio-tungstenite", + "tokio-util", + "tracing", + "tracing-core", + "tracing-subscriber", ] [[package]] -name = "cargo_metadata" -version = "0.23.1" +name = "burn-core" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef987d17b0a113becdd19d3d0022d04d7ef41f9efe4f3fb63ac44ba61df3ade9" +checksum = "5bde1e3b8e7e39b0aa71cd5905207ceea5c3478bec47bdb64acf72dcd0d6f978" dependencies = [ - "camino", - "cargo-platform", - "semver", + "ahash", + "bincode 2.0.1", + "burn-dataset", + "burn-derive", + "burn-std", + "burn-tensor", + "data-encoding", + "derive-new", + "flate2", + "half", + "hashbrown 0.16.1", + "log", + "num-traits", + "portable-atomic", + "portable-atomic-util", + "rand 0.10.1", + "regex", + "rmp-serde", "serde", "serde_json", - "thiserror 2.0.18", + "spin", + "uuid", ] [[package]] -name = "cast" -version = "0.3.0" +name = "burn-cpu" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" +checksum = "681478c5438ab6c753a55b85f5b42e5bd4c1f69a8564f2afa2f698ac396ba556" +dependencies = [ + "burn-backend", + "burn-cubecl", + "burn-fusion", + "cubecl", +] [[package]] -name = "castaway" -version = "0.2.4" +name = "burn-cubecl" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" +checksum = "26efbec96ca3f691593c966fa90f98ef8a72867f62627904920b619ba3a9e6b6" dependencies = [ - "rustversion", + "burn-backend", + "burn-cubecl-fusion", + "burn-fusion", + "burn-ir", + "burn-std", + "cubecl", + "cubek", + "derive-new", + "futures-lite", + "log", + "serde", + "text_placeholder", ] [[package]] -name = "cc" -version = "1.2.65" +name = "burn-cubecl-fusion" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96" +checksum = "6993d15b8ed588f55626e835ca768c1c3c7aa7a79d59beff4c987c40fc82d1f4" dependencies = [ - "find-msvc-tools", - "jobserver", - "libc", - "shlex", + "burn-backend", + "burn-fusion", + "burn-ir", + "burn-std", + "cubecl", + "cubek", + "derive-new", + "hashbrown 0.16.1", + "serde", + "spin", ] [[package]] -name = "cedar-policy-core" -version = "4.5.1" +name = "burn-cuda" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b45be5c965ce63bec3d284fefe0e042342f4e77f149a1e7aafc045119df738" +checksum = "a6c66b49136fc11f59773054358f401fbec7fa0d69615f55ef22c29cf241c9b7" dependencies = [ - "educe", - "either", - "itertools 0.14.0", - "lalrpop", - "lalrpop-util", - "lazy_static", - "miette", - "nonempty", - "ref-cast", - "regex", - "rustc_lexer", + "burn-backend", + "burn-cubecl", + "burn-fusion", + "cubecl", +] + +[[package]] +name = "burn-dataset" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba469fba38357c8bb65458873ddfd82aa97534aec0b3bae93d4c44604fc63839" +dependencies = [ + "csv", + "derive-new", + "dirs", + "gix-tempfile", + "image", + "r2d2", + "r2d2_sqlite", + "rand 0.10.1", + "rmp-serde", + "rusqlite", + "sanitize-filename", "serde", "serde_json", - "serde_with", - "smol_str", - "stacker", + "serde_rusqlite", + "strum", + "tempfile", "thiserror 2.0.18", - "unicode-security", ] [[package]] -name = "cfg-expr" -version = "0.20.8" +name = "burn-derive" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb693542bcafa528e198be0ebd9d3632ca5b7c93dbe7237460e199910835997c" +checksum = "dce7be43cdfc9903c43dbaf3f4821543e0497d79047d8fc9ee9084e448844446" dependencies = [ - "smallvec 1.15.2", - "target-lexicon", + "derive-new", + "proc-macro2", + "quote", + "syn 2.0.118", ] [[package]] -name = "cfg-if" -version = "1.0.4" +name = "burn-dispatch" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +checksum = "c2087b9e12323e0d25cc0ebdccfee40427dfdec4a23fa6d3f49ba2aa1f94ac17" +dependencies = [ + "burn-autodiff", + "burn-backend", + "burn-cpu", + "burn-cuda", + "burn-flex", + "burn-ndarray", + "burn-rocm", + "burn-tch", + "burn-wgpu", + "paste", +] [[package]] -name = "cfg_aliases" -version = "0.2.1" +name = "burn-flex" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +checksum = "0098bf6555c151517b4c98ca0e8ed1ab6a976e40f5f4e967c117b00eee21e192" +dependencies = [ + "aligned-vec 0.6.4", + "burn-backend", + "burn-ir", + "burn-std", + "bytemuck", + "gemm", + "half", + "libm", + "macerator", + "num-traits", + "rayon", +] [[package]] -name = "chacha20" -version = "0.9.1" +name = "burn-fusion" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +checksum = "95e58fcfd52a60cdf2f7da34fbebf0ed38f960fbd8675d384bacd5795cf2bdf2" dependencies = [ - "cfg-if", - "cipher", - "cpufeatures 0.2.17", + "burn-backend", + "burn-ir", + "burn-std", + "derive-new", + "hashbrown 0.16.1", + "log", + "serde", + "smallvec 1.15.2", + "spin", + "tracing", ] [[package]] -name = "chacha20" -version = "0.10.0" +name = "burn-ir" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +checksum = "a81a55c32f35c2265d9e15ba2e796013d9780d5a49f9e21ea0ac4d29609dbf13" dependencies = [ - "cfg-if", - "cpufeatures 0.3.0", - "rand_core 0.10.1", + "burn-backend", + "hashbrown 0.16.1", + "serde", ] [[package]] -name = "chacha20poly1305" -version = "0.10.1" +name = "burn-ndarray" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +checksum = "dceb9692e292782bab7ad0259e8e8f5ee80ba2b0329a78f0ba589a26a9633dd6" dependencies = [ - "aead", - "chacha20 0.9.1", - "cipher", - "poly1305", - "zeroize", + "atomic_float", + "burn-autodiff", + "burn-backend", + "burn-ir", + "burn-std", + "bytemuck", + "const-random", + "itertools 0.14.0", + "libm", + "macerator", + "matrixmultiply", + "ndarray 0.17.2", + "num-traits", + "paste", + "portable-atomic", + "portable-atomic-util", + "rand 0.10.1", + "rayon", + "seq-macro", ] [[package]] -name = "chrono" -version = "0.4.45" +name = "burn-nn" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +checksum = "93e4acd90806fa8663610f1071f3a8992eb98637d2d0816ee3062b334f61af00" dependencies = [ - "iana-time-zone", + "burn-core", + "num-traits", +] + +[[package]] +name = "burn-optim" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c5ea466ae0b85bf18f7656cdc152050dc8c581057fbbe33bbed267e22600228" +dependencies = [ + "burn-core", + "derive-new", + "hashbrown 0.16.1", + "log", "num-traits", "serde", - "windows-link", ] [[package]] -name = "ciborium" -version = "0.2.2" +name = "burn-remote" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +checksum = "754cace9288e3927857ace69387eb5e4a8bc6eab3cb3ac00d4327cb30e4d2383" dependencies = [ - "ciborium-io", - "ciborium-ll", + "async-channel", + "axum", + "burn-backend", + "burn-communication", + "burn-ir", + "burn-router", + "burn-std", + "bytes", + "derive-new", + "futures-util", + "log", + "rmp-serde", "serde", + "serde_bytes", + "tokio", + "tokio-tungstenite", + "tokio-util", + "tracing-core", + "tracing-subscriber", ] [[package]] -name = "ciborium-io" -version = "0.2.2" +name = "burn-rl" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" +checksum = "5d5bb875c36ddbb11f71a467bd6052d329d0ab78fbe79ab3777f4085e72290c4" +dependencies = [ + "burn-core", + "burn-optim", + "derive-new", + "log", + "rand 0.10.1", +] [[package]] -name = "ciborium-ll" -version = "0.2.2" +name = "burn-rocm" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +checksum = "66af82fdaaf2ad0fa4b0dae90119aa94f333105d76d520e0eb48917717c06fde" dependencies = [ - "ciborium-io", - "half", + "burn-backend", + "burn-cubecl", + "burn-fusion", + "cubecl", ] [[package]] -name = "cipher" -version = "0.4.4" +name = "burn-router" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +checksum = "7960a5d43918c3158629da2bd7956e38dda16ef54de95cca83b4b7b1477e6185" dependencies = [ - "crypto-common 0.1.7", - "inout", - "zeroize", + "burn-backend", + "burn-ir", + "burn-std", + "hashbrown 0.16.1", + "log", + "spin", ] [[package]] -name = "circular-buffer" -version = "1.2.0" +name = "burn-std" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c638459986b83c2b885179bd4ea6a2cbb05697b001501a56adb3a3d230803b" +checksum = "81c7eb60eb2c316854af5b214fd55f59fc7b0e25dfde7db671f09cc49f269048" +dependencies = [ + "bytemuck", + "bytes", + "cubecl", + "cubecl-common", + "cubecl-zspace", + "half", + "num-traits", + "serde", + "smallvec 1.15.2", + "spin", +] [[package]] -name = "clap" -version = "4.6.1" +name = "burn-store" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" +checksum = "2f64a4fe2a0616d18f07d1d0d19c79e655e70e1828cbcb5e9df7b52211091023" dependencies = [ - "clap_builder", - "clap_derive", + "burn-core", + "burn-tensor", + "byteorder", + "bytes", + "half", + "hashbrown 0.16.1", + "memmap2", + "regex", + "safetensors 0.7.0", + "textdistance", ] [[package]] -name = "clap_builder" -version = "4.6.0" +name = "burn-tch" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" +checksum = "dfa36b630bd7a790395b4561f7d0e315c0d5e1c557db7dd4cf1b48d5c0d4aff2" dependencies = [ - "anstream", - "anstyle", - "clap_lex", - "strsim", - "terminal_size", + "burn-backend", + "cc", + "libc", + "log", + "tch", + "torch-sys", ] [[package]] -name = "clap_complete" -version = "4.6.5" +name = "burn-tensor" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a7a9bfdb35811f9e59832f0f05975114d2251b415fb534108e6f34060fd772" +checksum = "223ed3804bb9436e401fc57b87e44c86267070b870813520ed9f706c80c81442" dependencies = [ - "clap", + "burn-backend", + "burn-std", + "colored 3.1.1", + "derive-new", + "num-traits", + "serde", ] [[package]] -name = "clap_derive" -version = "4.6.1" +name = "burn-train" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" +checksum = "fa65e2e569e571bb8dc9d37aa8fcf715f522f8159c6ee87f665c1961fee5259f" dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn 2.0.118", + "async-channel", + "burn-core", + "burn-flex", + "burn-optim", + "burn-rl", + "derive-new", + "log", + "nvml-wrapper", + "rand 0.10.1", + "ratatui", + "rstest", + "serde", + "sysinfo", + "systemstat", + "thiserror 2.0.18", + "tracing-appender", + "tracing-core", + "tracing-subscriber", ] [[package]] -name = "clap_lex" -version = "1.1.0" +name = "burn-vision" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" +checksum = "38b5a3e9096977462cd22bf713087f9777e427f7693508871f86f5b1e8347a70" +dependencies = [ + "aligned-vec 0.6.4", + "bon", + "burn-cubecl", + "burn-ir", + "burn-tensor", + "bytemuck", + "cubecl", + "derive-new", + "half", + "image", + "macerator", + "ndarray 0.17.2", + "num-traits", + "paste", + "serde", +] [[package]] -name = "cmake" -version = "0.1.58" +name = "burn-wgpu" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" +checksum = "6eb009254af15922cb37814f3b3b61043046193ba2fde97c3879a25fbd51a92e" dependencies = [ - "cc", + "burn-backend", + "burn-cubecl", + "burn-fusion", + "cubecl", ] [[package]] -name = "cmov" -version = "0.5.4" +name = "by_address" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" +checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06" [[package]] -name = "codspeed" -version = "4.7.0" +name = "bytecheck" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57af92d1db7f6871b7e82c79cd87f2501db66f36b0eab924be6ea83dd6b2f3f3" +checksum = "0caa33a2c0edca0419d15ac723dff03f1956f7978329b1e3b5fdaaaed9d3ca8b" dependencies = [ - "anyhow", - "cc", - "colored", - "getrandom 0.2.17", - "glob", - "libc", - "nix 0.31.3", - "serde", - "serde_json", - "statrs", + "bytecheck_derive", + "ptr_meta", + "rancor", + "simdutf8", ] [[package]] -name = "codspeed-criterion-compat" -version = "4.7.0" +name = "bytecheck_derive" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d31ae2e9ab23c29fa13bdfa06d012524176f5c0f4e25ec262cd829d947ebc5e" +checksum = "89385e82b5d1821d2219e0b095efa2cc1f246cbf99080f3be46a1a85c0d392d9" dependencies = [ - "clap", - "codspeed", - "codspeed-criterion-compat-walltime", - "colored", - "regex", + "proc-macro2", + "quote", + "syn 2.0.118", ] [[package]] -name = "codspeed-criterion-compat-walltime" -version = "4.7.0" +name = "bytemuck" +version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc8605e40bab5114dcb0f76268e18880082b5798dec10757b5b58d2c3bbc7a1c" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" dependencies = [ - "anes", - "cast", - "ciborium", - "clap", - "codspeed", - "criterion-plot 0.5.0", - "is-terminal", - "itertools 0.10.5", - "num-traits", - "once_cell", - "oorandom", - "plotters", - "rayon", - "regex", - "serde", - "serde_derive", - "serde_json", - "tinytemplate", - "walkdir", + "bytemuck_derive", ] [[package]] -name = "colorchoice" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" - -[[package]] -name = "colored" -version = "2.2.0" +name = "bytemuck_derive" +version = "1.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" +checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" dependencies = [ - "lazy_static", - "windows-sys 0.59.0", + "proc-macro2", + "quote", + "syn 2.0.118", ] [[package]] -name = "combine" -version = "4.6.7" +name = "byteorder" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" -dependencies = [ - "bytes", - "memchr", -] +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] -name = "common-path" -version = "1.0.0" +name = "byteorder-lite" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2382f75942f4b3be3690fe4f86365e9c853c1587d6ee58212cebf6e2a9ccd101" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" [[package]] -name = "compact_str" -version = "0.9.1" +name = "bytes" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dfdd1c2274d9aa354115b09dc9a901d6c5576818cdf70d14cae2bdb47df00ab" +checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593" dependencies = [ - "castaway", - "cfg-if", - "itoa", - "rustversion", - "ryu", - "static_assertions", + "portable-atomic", + "serde", ] [[package]] -name = "concurrent-queue" -version = "2.5.0" +name = "bytes-utils" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +checksum = "7dafe3a8757b027e2be6e4e5601ed563c55989fcf1546e933c66c8eb3a058d35" dependencies = [ - "crossbeam-utils", + "bytes", + "either", ] [[package]] -name = "configparser" -version = "3.2.0" +name = "bytesize" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b46dec724fd22199ebde05033a0cbae453bc3b1ecff11eb6a6bb3eec4b90c6a4" +checksum = "2e93abca9e28e0a1b9877922aacb20576e05d4679ffa78c3d6dc22a26a216659" [[package]] -name = "console" -version = "0.16.3" +name = "bzip2" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d64e8af5551369d19cf50138de61f1c42074ab970f74e99be916646777f8fc87" +checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8" dependencies = [ - "encode_unicode", + "bzip2-sys", "libc", - "windows-sys 0.61.2", ] [[package]] -name = "console_error_panic_hook" -version = "0.1.7" +name = "bzip2-sys" +version = "0.1.13+1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14" dependencies = [ - "cfg-if", - "wasm-bindgen", + "cc", + "pkg-config", ] [[package]] -name = "const-oid" -version = "0.9.6" +name = "camino" +version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +checksum = "b4ce8d3bd5823c7504d3f579f13e7b2f3da252fcb938c594d5680ee508bf846f" +dependencies = [ + "serde_core", +] [[package]] -name = "const-oid" +name = "candle-core" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" +checksum = "6bd9895436c1ba5dc1037a19935d084b838db066ff4e15ef7dded020b7c12a4a" +dependencies = [ + "byteorder", + "float8", + "gemm", + "half", + "libm", + "memmap2", + "num-traits", + "num_cpus", + "rand 0.9.4", + "rand_distr 0.5.1", + "rayon", + "safetensors 0.7.0", + "thiserror 2.0.18", + "tokenizers", + "yoke", + "zip 7.2.0", +] [[package]] -name = "const-str" -version = "0.4.3" +name = "cargo-platform" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f421161cb492475f1661ddc9815a745a1c894592070661180fdec3d4872e9c3" +checksum = "dd0061da739915fae12ea00e16397555ed4371a6bb285431aab930f61b0aa4ba" +dependencies = [ + "serde", + "serde_core", +] [[package]] -name = "convert_case" -version = "0.10.0" +name = "cargo_metadata" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +checksum = "ef987d17b0a113becdd19d3d0022d04d7ef41f9efe4f3fb63ac44ba61df3ade9" dependencies = [ - "unicode-segmentation", + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror 2.0.18", ] [[package]] -name = "convert_case" -version = "0.11.0" +name = "caseless" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "affbf0190ed2caf063e3def54ff444b449371d55c58e513a95ab98eca50adb49" +checksum = "8b6fd507454086c8edfd769ca6ada439193cdb209c7681712ef6275cccbfe5d8" dependencies = [ - "unicode-segmentation", + "unicode-normalization", ] [[package]] -name = "core-foundation" -version = "0.9.4" +name = "cast" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + +[[package]] +name = "castaway" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" dependencies = [ - "core-foundation-sys", - "libc", + "rustversion", ] [[package]] -name = "core-foundation" -version = "0.10.1" +name = "cc" +version = "1.2.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96" dependencies = [ - "core-foundation-sys", + "find-msvc-tools", + "jobserver", "libc", + "shlex 2.0.1", ] [[package]] -name = "core-foundation-sys" -version = "0.8.7" +name = "cedar-policy-core" +version = "4.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +checksum = "02b45be5c965ce63bec3d284fefe0e042342f4e77f149a1e7aafc045119df738" +dependencies = [ + "educe", + "either", + "itertools 0.14.0", + "lalrpop", + "lalrpop-util", + "lazy_static", + "miette", + "nonempty", + "ref-cast", + "regex", + "rustc_lexer", + "serde", + "serde_json", + "serde_with", + "smol_str", + "stacker", + "thiserror 2.0.18", + "unicode-security", +] [[package]] -name = "cow-utils" -version = "0.1.3" +name = "cexpr" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "417bef24afe1460300965a25ff4a24b8b45ad011948302ec221e8a0a81eb2c79" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom 7.1.3", +] [[package]] -name = "cpufeatures" -version = "0.2.17" +name = "cfg-expr" +version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +checksum = "fb693542bcafa528e198be0ebd9d3632ca5b7c93dbe7237460e199910835997c" dependencies = [ - "libc", + "smallvec 1.15.2", + "target-lexicon", ] [[package]] -name = "cpufeatures" -version = "0.3.0" +name = "cfg-if" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" -dependencies = [ - "libc", -] +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] -name = "crc-fast" -version = "1.10.0" +name = "cfg_aliases" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e75b2483e97a5a7da73ac68a05b629f9c53cff58d8ed1c77866079e18b00dba5" -dependencies = [ - "digest 0.10.7", - "spin", -] +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] -name = "crc32fast" -version = "1.5.0" +name = "chacha20" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" dependencies = [ "cfg-if", + "cipher", + "cpufeatures 0.2.17", ] [[package]] -name = "criterion" -version = "0.8.2" +name = "chacha20" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "950046b2aa2492f9a536f5f4f9a3de7b9e2476e575e05bd6c333371add4d98f3" +checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "rand_core 0.10.1", +] + +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead", + "chacha20 0.9.1", + "cipher", + "poly1305", + "zeroize", +] + +[[package]] +name = "chrono" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +dependencies = [ + "iana-time-zone", + "num-traits", + "serde", + "windows-link", +] + +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common 0.1.7", + "inout", + "zeroize", +] + +[[package]] +name = "circular-buffer" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c638459986b83c2b885179bd4ea6a2cbb05697b001501a56adb3a3d230803b" + +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading 0.8.8", +] + +[[package]] +name = "clap" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", + "terminal_size", +] + +[[package]] +name = "clap_complete" +version = "4.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7a9bfdb35811f9e59832f0f05975114d2251b415fb534108e6f34060fd772" +dependencies = [ + "clap", +] + +[[package]] +name = "clap_derive" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "cmake" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" +dependencies = [ + "cc", +] + +[[package]] +name = "cmov" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" + +[[package]] +name = "codespan-reporting" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af491d569909a7e4dee0ad7db7f5341fef5c614d5b8ec8cf765732aba3cff681" +dependencies = [ + "serde", + "termcolor", + "unicode-width 0.1.14", +] + +[[package]] +name = "codspeed" +version = "4.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57af92d1db7f6871b7e82c79cd87f2501db66f36b0eab924be6ea83dd6b2f3f3" +dependencies = [ + "anyhow", + "cc", + "colored 2.2.0", + "getrandom 0.2.17", + "glob", + "libc", + "nix 0.31.3", + "serde", + "serde_json", + "statrs", +] + +[[package]] +name = "codspeed-criterion-compat" +version = "4.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d31ae2e9ab23c29fa13bdfa06d012524176f5c0f4e25ec262cd829d947ebc5e" +dependencies = [ + "clap", + "codspeed", + "codspeed-criterion-compat-walltime", + "colored 2.2.0", + "regex", +] + +[[package]] +name = "codspeed-criterion-compat-walltime" +version = "4.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc8605e40bab5114dcb0f76268e18880082b5798dec10757b5b58d2c3bbc7a1c" dependencies = [ - "alloca", "anes", "cast", "ciborium", "clap", - "criterion-plot 0.8.2", - "itertools 0.13.0", + "codspeed", + "criterion-plot 0.5.0", + "is-terminal", + "itertools 0.10.5", "num-traits", + "once_cell", "oorandom", - "page_size", "plotters", "rayon", "regex", "serde", + "serde_derive", "serde_json", "tinytemplate", - "tokio", "walkdir", ] [[package]] -name = "criterion-macro" -version = "0.4.0" +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "colored" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" +dependencies = [ + "lazy_static", + "windows-sys 0.59.0", +] + +[[package]] +name = "colored" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" +dependencies = [ + "windows-sys 0.60.2", +] + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "common-path" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2382f75942f4b3be3690fe4f86365e9c853c1587d6ee58212cebf6e2a9ccd101" + +[[package]] +name = "compact_str" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dfdd1c2274d9aa354115b09dc9a901d6c5576818cdf70d14cae2bdb47df00ab" +dependencies = [ + "castaway", + "cfg-if", + "itoa", + "rustversion", + "ryu", + "serde", + "static_assertions", +] + +[[package]] +name = "comrak" +version = "0.39.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fefab951771fc3beeed0773ce66a4f7b706273fc6c4c95b08dd1615744abcf5" +dependencies = [ + "caseless", + "entities", + "memchr", + "slug", + "typed-arena", + "unicode_categories", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "configparser" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b46dec724fd22199ebde05033a0cbae453bc3b1ecff11eb6a6bb3eec4b90c6a4" + +[[package]] +name = "console" +version = "0.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d64e8af5551369d19cf50138de61f1c42074ab970f74e99be916646777f8fc87" +dependencies = [ + "encode_unicode", + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + +[[package]] +name = "const-random" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" +dependencies = [ + "const-random-macro", +] + +[[package]] +name = "const-random-macro" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" +dependencies = [ + "getrandom 0.2.17", + "once_cell", + "tiny-keccak", +] + +[[package]] +name = "const-str" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f421161cb492475f1661ddc9815a745a1c894592070661180fdec3d4872e9c3" + +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + +[[package]] +name = "constcat" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136d3e02915a2cea4d74caa8681e2d44b1c3254bdbf17d11d41d587ff858832c" + +[[package]] +name = "convert_case" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baaaa0ecca5b51987b9423ccdc971514dd8b0bb7b4060b983d3664dad3f1f89f" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "convert_case" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "affbf0190ed2caf063e3def54ff444b449371d55c58e513a95ab98eca50adb49" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cow-utils" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "417bef24afe1460300965a25ff4a24b8b45ad011948302ec221e8a0a81eb2c79" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + +[[package]] +name = "crc-fast" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e75b2483e97a5a7da73ac68a05b629f9c53cff58d8ed1c77866079e18b00dba5" +dependencies = [ + "digest 0.10.7", + "spin", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "criterion" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "950046b2aa2492f9a536f5f4f9a3de7b9e2476e575e05bd6c333371add4d98f3" +dependencies = [ + "alloca", + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot 0.8.2", + "itertools 0.13.0", + "num-traits", + "oorandom", + "page_size", + "plotters", + "rayon", + "regex", + "serde", + "serde_json", + "tinytemplate", + "tokio", + "walkdir", +] + +[[package]] +name = "criterion-macro" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "288a8f36b28a19d7dbd572c76006c0a0eba1f3bf912a254dda211c6f665e7ffc" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "criterion-plot" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +dependencies = [ + "cast", + "itertools 0.10.5", +] + +[[package]] +name = "criterion-plot" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8d80a2f4f5b554395e47b5d8305bc3d27813bacb73493eb1001e8f76dae29ea" +dependencies = [ + "cast", + "itertools 0.13.0", +] + +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "803d13fb3b09d88be9f4dbc29062c66b19bf7170867ceb746d2a8689bf6c7a26" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crossterm" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" +dependencies = [ + "bitflags 2.13.0", + "crossterm_winapi", + "derive_more", + "document-features", + "mio", + "parking_lot", + "rustix", + "signal-hook 0.3.18", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "typenum", +] + +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "csscolorparser" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb2a7d3066da2de787b7f032c736763eb7ae5d355f81a68bab2675a96008b0bf" +dependencies = [ + "lab", + "phf 0.11.3", +] + +[[package]] +name = "csv" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52cd9d68cf7efc6ddfaaee42e7288d3a99d613d4b50f76ce9827ae0c6e14f938" +dependencies = [ + "csv-core", + "itoa", + "ryu", + "serde_core", +] + +[[package]] +name = "csv-core" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "704a3c26996a80471189265814dbc2c257598b96b8a7feae2d31ace646bb9782" +dependencies = [ + "memchr", +] + +[[package]] +name = "ctor" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec09e802f5081de6157da9a75701d6c713d8dc3ba52571fd4bd25f412644e8a6" +dependencies = [ + "ctor-proc-macro", + "dtor", +] + +[[package]] +name = "ctor" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01334b89b69ff726750c5ce5073fc8bd860e99aa9a8fc5ca11b04730e3aee97a" + +[[package]] +name = "ctor-proc-macro" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2931af7e13dc045d8e9d26afccc6fa115d64e115c9c84b1166288b46f6782c2" + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + +[[package]] +name = "ctutils" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" +dependencies = [ + "cmov", +] + +[[package]] +name = "cubecl" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd203fef6e359472e4cb8f6c0ef3eca062815a1edd560eb6d6c1d5c1397838d9" +dependencies = [ + "cubecl-core", + "cubecl-cpu", + "cubecl-cuda", + "cubecl-hip", + "cubecl-ir", + "cubecl-runtime", + "cubecl-std", + "cubecl-wgpu", + "half", +] + +[[package]] +name = "cubecl-common" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc956c2dcc993f16f748d03bfdbd900f75cab4d469f4e5d8924f8ee128e861ad" +dependencies = [ + "backtrace", + "bytemuck", + "bytes", + "cfg-if", + "cfg_aliases", + "ciborium", + "derive-new", + "derive_more", + "dirs", + "embassy-futures", + "embassy-time", + "float4", + "float8", + "futures-lite", + "half", + "hashbrown 0.16.1", + "log", + "num-traits", + "oneshot", + "parking_lot", + "portable-atomic", + "portable-atomic-util", + "rand 0.10.1", + "sanitize-filename", + "serde", + "serde_bytes", + "serde_json", + "spin", + "toml", + "tracing", + "tynm", + "wasm-bindgen-futures", + "web-time", + "xxhash-rust", +] + +[[package]] +name = "cubecl-core" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7522e7acf25d7848032c7b5270780f9f2abe84e13c7ed6345aa27ba70c312ca" +dependencies = [ + "bitflags 2.13.0", + "bytemuck", + "cubecl-common", + "cubecl-ir", + "cubecl-macros", + "cubecl-runtime", + "cubecl-zspace", + "derive-new", + "derive_more", + "enumset", + "float-ord", + "half", + "hashbrown 0.16.1", + "log", + "num-traits", + "paste", + "serde", + "serde_json", + "tracing", + "variadics_please", +] + +[[package]] +name = "cubecl-cpp" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "411af04828cbf4583ecd388579fb30cd7a644eec19a334bb8f0d9d08522e1458" +dependencies = [ + "bytemuck", + "cubecl-common", + "cubecl-core", + "cubecl-opt", + "cubecl-runtime", + "derive-new", + "half", + "itertools 0.14.0", + "log", +] + +[[package]] +name = "cubecl-cpu" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f572143f54e42a49ee0635a4ec36005f639e62be029e4f49890c808985981b35" +dependencies = [ + "bytemuck", + "cubecl-common", + "cubecl-core", + "cubecl-opt", + "cubecl-runtime", + "cubecl-std", + "derive-new", + "half", + "log", + "paste", + "serde", + "sysinfo", + "tracel-llvm", + "tracel-llvm-bundler", +] + +[[package]] +name = "cubecl-cuda" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6b0a69ff45688d322ad8e92c8bf645167b9ca490fa8fa087fc6adac8c5e46be" +dependencies = [ + "bytemuck", + "cubecl-common", + "cubecl-core", + "cubecl-cpp", + "cubecl-runtime", + "cudarc", + "derive-new", + "half", + "log", + "serde", + "tracing", +] + +[[package]] +name = "cubecl-hip" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "288a8f36b28a19d7dbd572c76006c0a0eba1f3bf912a254dda211c6f665e7ffc" +checksum = "3c6b510a9348f06ecd56b32cf10eb6241639e927a87f5c09ec61cc7a7610d5a3" dependencies = [ - "proc-macro2", - "quote", + "bytemuck", + "cubecl-common", + "cubecl-core", + "cubecl-cpp", + "cubecl-hip-sys", + "cubecl-runtime", + "derive-new", + "half", + "log", + "paste", + "serde", + "tracing", ] [[package]] -name = "criterion-plot" -version = "0.5.0" +name = "cubecl-hip-sys" +version = "7.2.5321100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +checksum = "58887c6d217859c2261a868a3a6b66aa8f44ea2f4bfa51d0dbe4dcb0d1f5768d" dependencies = [ - "cast", - "itertools 0.10.5", + "libc", + "regex", ] [[package]] -name = "criterion-plot" -version = "0.8.2" +name = "cubecl-ir" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8d80a2f4f5b554395e47b5d8305bc3d27813bacb73493eb1001e8f76dae29ea" +checksum = "d28a897a40c8ee6c57a8b8d76d8823ba2e97f4c2b83db9338f17a0752132d486" dependencies = [ - "cast", - "itertools 0.13.0", + "cubecl-common", + "cubecl-macros-internal", + "derive-new", + "derive_more", + "enumset", + "float-ord", + "fnv", + "foldhash 0.2.0", + "half", + "hashbrown 0.16.1", + "num-traits", + "portable-atomic", + "serde", + "variadics_please", ] [[package]] -name = "critical-section" -version = "1.2.0" +name = "cubecl-macros" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" +checksum = "77aa6df563e8e6a0926d2e9eeacb968737940a0e1ae9be819f6a65a341006e12" +dependencies = [ + "cubecl-common", + "darling 0.23.0", + "derive-new", + "ident_case", + "inflections", + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.118", +] [[package]] -name = "crossbeam-channel" -version = "0.5.15" +name = "cubecl-macros-internal" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +checksum = "a515651a5a91e25d87f71f311f80a088e6cf815798b9922560a97636da6b8740" dependencies = [ - "crossbeam-utils", + "darling 0.23.0", + "proc-macro2", + "quote", + "syn 2.0.118", ] [[package]] -name = "crossbeam-deque" -version = "0.8.6" +name = "cubecl-opt" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +checksum = "0a599c6c3efefdcee8636994c90e58ef696c7efbed2d18b5b5ad8d5f29923abb" dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", + "cubecl-common", + "cubecl-core", + "cubecl-ir", + "float-ord", + "log", + "num", + "petgraph 0.8.3", + "smallvec 1.15.2", + "stable-vec", + "type-map", ] [[package]] -name = "crossbeam-epoch" -version = "0.9.18" +name = "cubecl-runtime" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +checksum = "b68491bf5b3e997ae36bdc4e63b4ccd6d2f0e86b3b596a5d7a48d2b9e92622a0" dependencies = [ - "crossbeam-utils", + "ahash", + "async-channel", + "bytemuck", + "cfg-if", + "cfg_aliases", + "cubecl-common", + "cubecl-ir", + "cubecl-zspace", + "derive-new", + "derive_more", + "dirs", + "enumset", + "hashbrown 0.16.1", + "log", + "md5", + "serde", + "serde_json", + "spin", + "thiserror 2.0.18", + "toml", + "tracing", + "wasm-bindgen-futures", + "web-time", ] [[package]] -name = "crossbeam-utils" -version = "0.8.21" +name = "cubecl-std" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +checksum = "b391b584a4897683dd9fc0767f96337ac712b733126ce0f68a9ee8a2df073d2d" +dependencies = [ + "cubecl-common", + "cubecl-core", + "cubecl-runtime", + "half", + "num-traits", + "paste", + "serde", + "spin", + "variadics_please", +] [[package]] -name = "crossterm" -version = "0.29.0" +name = "cubecl-wgpu" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" +checksum = "4d3663c6e1a86187172b2cee495b6b533e7d91a2cb37e26f421649e315fe4c3a" dependencies = [ - "bitflags 2.13.0", - "crossterm_winapi", + "async-channel", + "bytemuck", + "cfg-if", + "cfg_aliases", + "cubecl-common", + "cubecl-core", + "cubecl-cpp", + "cubecl-ir", + "cubecl-runtime", + "derive-new", "derive_more", - "document-features", - "mio", - "parking_lot", - "rustix", - "signal-hook", - "signal-hook-mio", - "winapi", + "half", + "hashbrown 0.16.1", + "log", + "sanitize-filename", + "tracing", + "wasm-bindgen-futures", + "wgpu", ] [[package]] -name = "crossterm_winapi" -version = "0.9.1" +name = "cubecl-zspace" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +checksum = "04054d95698afab785a4dda9f949e297831dd9e4cc6d036a93e6603f88e88b07" dependencies = [ - "winapi", + "derive-new", + "serde", + "smallvec 1.15.2", ] [[package]] -name = "crunchy" -version = "0.2.4" +name = "cubek" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" +checksum = "ba62fb5043d5eb723017415eec57c541f41901beea984500e2e16b57d75fa717" +dependencies = [ + "cubecl", + "cubek-attention", + "cubek-convolution", + "cubek-fft", + "cubek-matmul", + "cubek-quant", + "cubek-random", + "cubek-reduce", + "cubek-std", +] [[package]] -name = "crypto-common" -version = "0.1.7" +name = "cubek-attention" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +checksum = "75ca5c363f05a9297b9d1c0e90959cc5f9fd90bc596e4e537f26f20602ec63a5" dependencies = [ - "generic-array", - "rand_core 0.6.4", - "typenum", + "bytemuck", + "cubecl", + "cubecl-common", + "cubek-matmul", + "cubek-std", + "half", + "serde", ] [[package]] -name = "crypto-common" -version = "0.2.2" +name = "cubek-convolution" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +checksum = "70f2755a8eb9ea5509c17edac78e4a9dc4be89c433d8e2ee709703a93b96f749" dependencies = [ - "hybrid-array", + "bytemuck", + "cubecl", + "cubecl-common", + "cubek-matmul", + "cubek-std", + "derive-new", + "enumset", + "half", + "serde", ] [[package]] -name = "csscolorparser" -version = "0.6.2" +name = "cubek-fft" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb2a7d3066da2de787b7f032c736763eb7ae5d355f81a68bab2675a96008b0bf" +checksum = "8dd7c2972363332dc4609067a2ccc21856308d98089e676d3f0c52747ae61a5b" dependencies = [ - "lab", - "phf 0.11.3", + "cubecl", ] [[package]] -name = "ctor" -version = "0.4.3" +name = "cubek-matmul" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec09e802f5081de6157da9a75701d6c713d8dc3ba52571fd4bd25f412644e8a6" +checksum = "83a4cea5f0f439907dc953c7638a6204b3f055f1bcbd10db91dfc5faa030ac1c" dependencies = [ - "ctor-proc-macro", - "dtor", + "bytemuck", + "cubecl", + "cubecl-common", + "cubek-std", + "half", + "serde", ] [[package]] -name = "ctor" -version = "1.0.7" +name = "cubek-quant" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01334b89b69ff726750c5ce5073fc8bd860e99aa9a8fc5ca11b04730e3aee97a" +checksum = "802ff1b5f19597a7b7cb308c7c69e9eef28b78d57dab7794c816bb8f7d3d1b85" +dependencies = [ + "cubecl", + "cubecl-common", + "half", + "serde", +] [[package]] -name = "ctor-proc-macro" -version = "0.0.6" +name = "cubek-random" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2931af7e13dc045d8e9d26afccc6fa115d64e115c9c84b1166288b46f6782c2" +checksum = "8dfecce959bdbeb3b355120e586d1ea9e45a0fc7f2ca354f4260a571952551be" +dependencies = [ + "cubecl", + "cubecl-common", + "half", + "num-traits", + "rand 0.10.1", + "serde", +] [[package]] -name = "ctr" -version = "0.9.2" +name = "cubek-reduce" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +checksum = "86758b84452b06e9121f25200da13b4909ecd6235cefb5f33f459bea24dac41a" dependencies = [ - "cipher", + "cubecl", + "cubek-std", + "half", + "num-traits", + "serde", + "thiserror 2.0.18", ] [[package]] -name = "ctutils" -version = "0.4.2" +name = "cubek-std" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" +checksum = "a587d0a44b6f14f4c3711ac10717179b48adb59203b2b2bffe334876bf713187" dependencies = [ - "cmov", + "cubecl", + "cubecl-common", + "half", +] + +[[package]] +name = "cudarc" +version = "0.19.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42310153e06cf4cd532901f7096beb27504d681736a29ee90728ae4e2d93b2a8" +dependencies = [ + "libloading 0.9.0", ] [[package]] @@ -2088,24 +3622,94 @@ dependencies = [ [[package]] name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core 0.20.11", + "darling_macro 0.20.11", +] + +[[package]] +name = "darling" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +dependencies = [ + "darling_core 0.21.3", + "darling_macro 0.21.3", +] + +[[package]] +name = "darling" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +dependencies = [ + "darling_core 0.23.0", + "darling_macro 0.23.0", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.118", +] + +[[package]] +name = "darling_core" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.118", +] + +[[package]] +name = "darling_core" version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.118", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ - "darling_core", - "darling_macro", + "darling_core 0.20.11", + "quote", + "syn 2.0.118", ] [[package]] -name = "darling_core" -version = "0.23.0" +name = "darling_macro" +version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" dependencies = [ - "ident_case", - "proc-macro2", + "darling_core 0.21.3", "quote", - "strsim", "syn 2.0.118", ] @@ -2115,7 +3719,7 @@ version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" dependencies = [ - "darling_core", + "darling_core 0.23.0", "quote", "syn 2.0.118", ] @@ -2159,6 +3763,29 @@ version = "0.1.1" name = "darwin-kperf-sys" version = "0.1.1" +[[package]] +name = "dary_heap" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b1e3a325bc115f096c8b77bbf027a7c2592230e70be2d985be950d3d5e60ebe" +dependencies = [ + "serde", +] + +[[package]] +name = "dashmap" +version = "6.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6361d5c062261c78a176addb82d4c821ae42bed6089de0e12603cd25de2059c" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + [[package]] name = "dashu-base" version = "0.4.3" @@ -2315,6 +3942,17 @@ dependencies = [ "syn 2.0.118", ] +[[package]] +name = "derive-new" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cdc8d50f426189eef89dac62fabfa0abb27d5cc008f25bf4156a0203325becc" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + [[package]] name = "derive-where" version = "1.6.1" @@ -2326,6 +3964,37 @@ dependencies = [ "syn 2.0.118", ] +[[package]] +name = "derive_builder" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" +dependencies = [ + "darling 0.20.11", + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "derive_builder_macro" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" +dependencies = [ + "derive_builder_core", + "syn 2.0.118", +] + [[package]] name = "derive_more" version = "2.1.1" @@ -2349,6 +4018,12 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "deunicode" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "890d779e1bc371e4fa7727ef6d29a9346be20ddfe40cd8c744cd083ce0640b15" + [[package]] name = "diatomic-waker" version = "0.1.0" @@ -2388,6 +4063,27 @@ dependencies = [ "ctutils", ] +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.60.2", +] + [[package]] name = "dispatch2" version = "0.3.1" @@ -2395,7 +4091,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" dependencies = [ "bitflags 2.13.0", - "objc2", + "objc2 0.6.4", ] [[package]] @@ -2415,13 +4111,22 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aeda16ab4059c5fd2a83f2b9c9e9c981327b18aa8e3b313f7e6563799d4f093e" +[[package]] +name = "dlib" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab8ecd87370524b461f8557c119c405552c396ed91fc0a8eec68679eab26f94a" +dependencies = [ + "libloading 0.8.8", +] + [[package]] name = "docker_credential" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29547a1dc60885a552306986316bc9701ba120c1a8db6769fa68691529ad373d" dependencies = [ - "base64", + "base64 0.22.1", "serde", "serde_json", ] @@ -2441,6 +4146,15 @@ version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccff2060df2e1a42732ab1e7af8c5f930ed3d0255aaadfae66e032781e8d271a" +[[package]] +name = "doxygen-rs" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "415b6ec780d34dcf624666747194393603d0373b7141eef01d12ee58881507d9" +dependencies = [ + "phf 0.11.3", +] + [[package]] name = "dragonbox_ecma" version = "0.0.5" @@ -2480,6 +4194,22 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" +[[package]] +name = "dyn-stack" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c4713e43e2886ba72b8271aa66c93d722116acf7a75555cce11dcde84388fe8" +dependencies = [ + "bytemuck", + "dyn-stack-macros", +] + +[[package]] +name = "dyn-stack-macros" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d926b4d407d372f141f93bb444696142c29d32962ccbd3531117cf3aa0bfa9" + [[package]] name = "ed25519" version = "2.2.3" @@ -2538,6 +4268,62 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e079f19b08ca6239f47f8ba8509c11cf3ea30095831f7fed61441475edd8c449" +[[package]] +name = "embassy-futures" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc2d050bdc5c21e0862a89256ed8029ae6c290a93aecefc73084b3002cdebb01" + +[[package]] +name = "embassy-time" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "592b0c143ec626e821d4d90da51a2bd91d559d6c442b7c74a47d368c9e23d97a" +dependencies = [ + "cfg-if", + "critical-section", + "document-features", + "embassy-time-driver", + "embedded-hal 0.2.7", + "embedded-hal 1.0.0", + "embedded-hal-async", + "futures-core", +] + +[[package]] +name = "embassy-time-driver" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ee71af1b3a0deaa53eaf2d39252f83504c853646e472400b763060389b9fcc9" +dependencies = [ + "document-features", +] + +[[package]] +name = "embedded-hal" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff" +dependencies = [ + "nb 0.1.3", + "void", +] + +[[package]] +name = "embedded-hal" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89" + +[[package]] +name = "embedded-hal-async" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4c685bbef7fe13c3c6dd4da26841ed3980ef33e841cddfa15ce8a8fb3f1884" +dependencies = [ + "embedded-hal 1.0.0", +] + [[package]] name = "ena" version = "0.14.4" @@ -2553,6 +4339,24 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" +[[package]] +name = "entities" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5320ae4c3782150d900b79807611a59a99fc9a1d61d686faafc24b93fc8d7ca" + +[[package]] +name = "enum-as-inner" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.118", +] + [[package]] name = "enum-iterator" version = "2.3.0" @@ -2626,6 +4430,70 @@ dependencies = [ "syn 2.0.118", ] +[[package]] +name = "enumset" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "839c4174b41e75c8f7306110b2c51996a293b8d1d850edd529011841d9fede7d" +dependencies = [ + "enumset_derive", + "serde", +] + +[[package]] +name = "enumset_derive" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bd536557b58c682b217b8fb199afdff47cd3eff260623f19e77074eb073d63a" +dependencies = [ + "darling 0.21.3", + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "env_filter" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900d271a03799a1ee8d1ca9b19893b48ca674a9284fefcfb85f05e74ed314217" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de671bd27a75a797dc9ae289ba1e77276e75e2026408aab65185384e2d5cd3f6" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "log", +] + +[[package]] +name = "equator" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4711b213838dfee0117e3be6ac926007d7f433d7bbe33595975d4190cb07e6fc" +dependencies = [ + "equator-macro", +] + +[[package]] +name = "equator-macro" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -2686,6 +4554,12 @@ dependencies = [ name = "error-stack-macros" version = "0.0.0-reserved" +[[package]] +name = "esaxx-rs" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d817e038c30374a4bcb22f94d0a8a0e216958d4c3dcde369b1439fec4bdda6e6" + [[package]] name = "escape8259" version = "0.5.3" @@ -2742,6 +4616,21 @@ dependencies = [ "once_cell", ] +[[package]] +name = "exr" +version = "1.74.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4300e043a56aa2cb633c01af81ca8f699a321879a7854d3896a0ba89056363be" +dependencies = [ + "bit_field", + "half", + "lebe", + "miniz_oxide", + "rayon-core", + "smallvec 1.15.2", + "zune-inflate", +] + [[package]] name = "eyre" version = "0.6.12" @@ -2758,6 +4647,18 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + [[package]] name = "fancy-regex" version = "0.11.0" @@ -2791,6 +4692,35 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" +[[package]] +name = "fax" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05de7d48f37cd6730705cbca900770cab77a89f413d23e100ad7fad7795a0ab" +dependencies = [ + "fax_derive", +] + +[[package]] +name = "fax_derive" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0aca10fb742cb43f9e7bb8467c91aa9bcb8e3ffbc6a6f7389bb93ffc920577d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "fdeflate" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8090f921a24b04994d9929e204f50b498a33ea6ba559ffaa05e04f7ee7fb5ab" +dependencies = [ + "simd-adler32", +] + [[package]] name = "ferroid" version = "2.0.0" @@ -2847,12 +4777,25 @@ dependencies = [ "winapi", ] -[[package]] -name = "finl_unicode" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9844ddc3a6e533d62bba727eb6c28b5d360921d5175e9ff0f1e621a5c590a4d5" - +[[package]] +name = "finl_unicode" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9844ddc3a6e533d62bba727eb6c28b5d360921d5175e9ff0f1e621a5c590a4d5" + +[[package]] +name = "fixed" +version = "1.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707070ccf8c4173548210893a0186e29c266901b71ed20cd9e2ca0193dfe95c3" +dependencies = [ + "az", + "bytemuck", + "half", + "num-traits", + "typenum", +] + [[package]] name = "fixedbitset" version = "0.4.2" @@ -2875,6 +4818,30 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "float-ord" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce81f49ae8a0482e4c55ea62ebbd7e5a686af544c00b9d090bba3ff9be97b3d" + +[[package]] +name = "float4" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a5404bf31d22893d61cf24d4dda149d8e6b2ff07601c3cb3be651031f61a4ed" + +[[package]] +name = "float8" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2d1f04709a8ac06e8e8042875a3c466cc4832d3c1a18dbcb9dba3c6e83046bc" +dependencies = [ + "half", + "num-traits", + "rand 0.9.4", + "rand_distr 0.5.1", +] + [[package]] name = "fnv" version = "1.0.7" @@ -2947,6 +4914,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" +[[package]] +name = "fst" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ab85b9b05e3978cc9a9cf8fea7f01b494e1a09ed3037e16ba39edc7a29eb61a" + [[package]] name = "funty" version = "2.0.0" @@ -3096,6 +5069,125 @@ dependencies = [ "slab", ] +[[package]] +name = "gemm" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa0673db364b12263d103b68337a68fbecc541d6f6b61ba72fe438654709eacb" +dependencies = [ + "dyn-stack", + "gemm-c32", + "gemm-c64", + "gemm-common", + "gemm-f16", + "gemm-f32", + "gemm-f64", + "num-complex 0.4.6", + "num-traits", + "paste", + "raw-cpuid", + "seq-macro", +] + +[[package]] +name = "gemm-c32" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "086936dbdcb99e37aad81d320f98f670e53c1e55a98bee70573e83f95beb128c" +dependencies = [ + "dyn-stack", + "gemm-common", + "num-complex 0.4.6", + "num-traits", + "paste", + "raw-cpuid", + "seq-macro", +] + +[[package]] +name = "gemm-c64" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20c8aeeeec425959bda4d9827664029ba1501a90a0d1e6228e48bef741db3a3f" +dependencies = [ + "dyn-stack", + "gemm-common", + "num-complex 0.4.6", + "num-traits", + "paste", + "raw-cpuid", + "seq-macro", +] + +[[package]] +name = "gemm-common" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88027625910cc9b1085aaaa1c4bc46bb3a36aad323452b33c25b5e4e7c8e2a3e" +dependencies = [ + "bytemuck", + "dyn-stack", + "half", + "libm", + "num-complex 0.4.6", + "num-traits", + "once_cell", + "paste", + "pulp", + "raw-cpuid", + "rayon", + "seq-macro", + "sysctl", +] + +[[package]] +name = "gemm-f16" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3df7a55202e6cd6739d82ae3399c8e0c7e1402859b30e4cb780e61525d9486e" +dependencies = [ + "dyn-stack", + "gemm-common", + "gemm-f32", + "half", + "num-complex 0.4.6", + "num-traits", + "paste", + "raw-cpuid", + "rayon", + "seq-macro", +] + +[[package]] +name = "gemm-f32" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02e0b8c9da1fbec6e3e3ab2ce6bc259ef18eb5f6f0d3e4edf54b75f9fd41a81c" +dependencies = [ + "dyn-stack", + "gemm-common", + "num-complex 0.4.6", + "num-traits", + "paste", + "raw-cpuid", + "seq-macro", +] + +[[package]] +name = "gemm-f64" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "056131e8f2a521bfab322f804ccd652520c79700d81209e9d9275bbdecaadc6a" +dependencies = [ + "dyn-stack", + "gemm-common", + "num-complex 0.4.6", + "num-traits", + "paste", + "raw-cpuid", + "seq-macro", +] + [[package]] name = "generator" version = "0.7.5" @@ -3109,6 +5201,21 @@ dependencies = [ "windows 0.48.0", ] +[[package]] +name = "generator" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3b854b0e584ead1a33f18b2fcad7cf7be18b3875c78816b753639aa501513ae" +dependencies = [ + "cc", + "cfg-if", + "libc", + "log", + "rustversion", + "windows-link", + "windows-result", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -3168,12 +5275,110 @@ dependencies = [ "polyval", ] +[[package]] +name = "gif" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee8cfcc411d9adbbaba82fb72661cc1bcca13e8bba98b364e62b2dba8f960159" +dependencies = [ + "color_quant", + "weezl", +] + [[package]] name = "gimli" version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" +[[package]] +name = "gix-features" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af375693ad5333d0a2c66b4c5b2cbe9ccc38e34f8e8bf24e4ae42c12307fdc4f" +dependencies = [ + "gix-trace", + "gix-utils", + "libc", +] + +[[package]] +name = "gix-fs" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e1967daac9848757c47c2aef0c57bcadc1a897347f559778249bf286a536c86" +dependencies = [ + "bstr", + "fastrand", + "gix-features", + "gix-path", + "gix-utils", + "thiserror 2.0.18", +] + +[[package]] +name = "gix-path" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "671a6059e8a4c1b7f406e24716499cefa3926e060876fb1959ef225efeee346e" +dependencies = [ + "bstr", + "gix-trace", + "gix-validate", + "thiserror 2.0.18", +] + +[[package]] +name = "gix-tempfile" +version = "23.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "691ea1e31435c7e7d4d04705ec9d1c0d9482c46b2acf512bc723939d8f0af7fb" +dependencies = [ + "dashmap", + "gix-fs", + "libc", + "parking_lot", + "signal-hook 0.4.4", + "signal-hook-registry", + "tempfile", +] + +[[package]] +name = "gix-trace" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f23569e55f2ffaf958617353b9734a7d52a7c19c439eeaa5e3efc217fd2270e" + +[[package]] +name = "gix-utils" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e477b4f07a6e8da4ba791c53c858102959703c60d70f199932010d5b94adb2c" +dependencies = [ + "fastrand", + "unicode-normalization", +] + +[[package]] +name = "gix-validate" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e26ac2602b43eadfdca0560b81d3341944162a3c9f64ccdeef8fc501ad80dad5" +dependencies = [ + "bstr", +] + +[[package]] +name = "gl_generator" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" +dependencies = [ + "khronos_api", + "log", + "xml-rs", +] + [[package]] name = "glob" version = "0.3.3" @@ -3218,6 +5423,61 @@ dependencies = [ "web-sys", ] +[[package]] +name = "glow" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29038e1c483364cc6bb3cf78feee1816002e127c331a1eec55a4d202b9e1adb5" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "glutin_wgl_sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4ee00b289aba7a9e5306d57c2d05499b2e5dc427f84ac708bd2c090212cf3e" +dependencies = [ + "gl_generator", +] + +[[package]] +name = "gpu-allocator" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51255ea7cfaadb6c5f1528d43e92a82acb2b96c43365989a28b2d44ee38f8795" +dependencies = [ + "ash", + "hashbrown 0.16.1", + "log", + "presser", + "thiserror 2.0.18", + "windows 0.62.2", +] + +[[package]] +name = "gpu-descriptor" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b89c83349105e3732062a895becfc71a8f921bb71ecbbdd8ff99263e3b53a0ca" +dependencies = [ + "bitflags 2.13.0", + "gpu-descriptor-types", + "hashbrown 0.15.5", +] + +[[package]] +name = "gpu-descriptor-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" +dependencies = [ + "bitflags 2.13.0", +] + [[package]] name = "guppy" version = "0.17.26" @@ -3276,11 +5536,42 @@ version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" dependencies = [ + "bytemuck", "cfg-if", "crunchy", + "num-traits", + "rand 0.9.4", + "rand_distr 0.5.1", + "serde", "zerocopy", ] +[[package]] +name = "hannoy" +version = "0.1.7-nested-rtxns" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "887aa70bc3aecc9b5c4652752820e3b37a1fb95d62520efbd18acd377cc26e95" +dependencies = [ + "bytemuck", + "byteorder", + "crossbeam-channel", + "hashbrown 0.15.5", + "heed", + "madvise", + "min-max-heap", + "page_size", + "papaya", + "rand 0.8.6", + "rayon", + "roaring 0.10.12", + "rustc-hash 2.1.2", + "steppe", + "thiserror 2.0.18", + "thread_local", + "tinyvec", + "tracing", +] + [[package]] name = "harpc-client" version = "0.0.0" @@ -3447,7 +5738,7 @@ dependencies = [ name = "harpc-wire-protocol" version = "0.0.0" dependencies = [ - "base64", + "base64 0.22.1", "bytes", "enumflags2", "error-stack", @@ -3514,6 +5805,7 @@ dependencies = [ "harpc-server", "hash-codec", "hash-graph-api", + "hash-graph-atlas", "hash-graph-authorization", "hash-graph-embeddings", "hash-graph-postgres-store", @@ -3526,7 +5818,7 @@ dependencies = [ "multiaddr", "rayon", "regex", - "reqwest", + "reqwest 0.13.4", "simple-mermaid", "time", "tokio", @@ -3584,21 +5876,86 @@ dependencies = [ "mime", "opentelemetry", "opentelemetry-semantic-conventions", - "reqwest", + "reqwest 0.13.4", "sentry", "serde", "serde_json", "simple-mermaid", - "time", + "time", + "tokio", + "tokio-util", + "tower", + "tower-http", + "tracing", + "tracing-opentelemetry", + "type-system", + "utoipa", + "uuid", +] + +[[package]] +name = "hash-graph-atlas" +version = "0.0.0" +dependencies = [ + "aide", + "axum", + "burn", + "camino", + "chacha20poly1305", + "clap", + "codspeed-criterion-compat", + "darwin-kperf-criterion", + "darwin-kperf-events", + "error-stack", + "fst", + "futures", + "futures-sink", + "hannoy", + "hash-graph-authorization", + "hash-graph-embeddings", + "hash-graph-postgres-store", + "hash-graph-store", + "hash-graph-temporal-versioning", + "hash-graph-types", + "hashql-core", + "heed", + "hkdf 0.13.0", + "kiddo", + "memchr", + "memmap2", + "moka", + "pin-project-lite", + "proptest", + "rand 0.10.1", + "rand_core 0.10.1", + "rand_core 0.6.4", + "rand_xoshiro", + "ratatui", + "rayon", + "regex", + "rkyv", + "roaring 0.11.4", + "schemars 1.2.1", + "serde", + "serde_json", + "sha2 0.11.0", + "simple-mermaid", + "siphasher", + "smallvec 2.0.0-alpha.12", + "sprs", + "steppe", + "tiktoken-rs", "tokio", - "tokio-util", + "tokio-postgres", "tower", - "tower-http", "tracing", - "tracing-opentelemetry", + "tracing-subscriber", "type-system", - "utoipa", + "unicode-segmentation", + "unidecode", "uuid", + "zerocopy", + "zeroize", ] [[package]] @@ -3678,7 +6035,7 @@ dependencies = [ "rand 0.10.1", "rand_xoshiro", "rayon", - "reqwest", + "reqwest 0.13.4", "reqwest-middleware", "reqwest-retry", "reqwest-tracing", @@ -3858,7 +6215,7 @@ dependencies = [ "pretty_assertions", "rand 0.10.1", "rand_core 0.10.1", - "rand_distr", + "rand_distr 0.6.0", "rayon", "regex", "serde", @@ -3895,7 +6252,7 @@ dependencies = [ "hash-temporal-client", "include_dir", "opentelemetry", - "reqwest", + "reqwest 0.13.4", "reqwest-middleware", "reqwest-tracing", "serde", @@ -4049,6 +6406,21 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + [[package]] name = "hashbrown" version = "0.15.5" @@ -4056,6 +6428,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ "allocator-api2 0.2.21", + "equivalent", "foldhash 0.1.5", ] @@ -4083,6 +6456,15 @@ dependencies = [ "foldhash 0.2.0", ] +[[package]] +name = "hashlink" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +dependencies = [ + "hashbrown 0.15.5", +] + [[package]] name = "hashlink" version = "0.11.1" @@ -4157,7 +6539,8 @@ dependencies = [ "pretty", "proptest", "rapidfuzz", - "roaring", + "rayon", + "roaring 0.11.4", "rstest", "serde", "simple-mermaid", @@ -4167,6 +6550,7 @@ dependencies = [ "tracing", "unicase", "unicode-segmentation", + "zerocopy", ] [[package]] @@ -4291,6 +6675,44 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "heed" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad82d6598ccf1dac15c8b758a1bd282b755b6776be600429176757190a1b0202" +dependencies = [ + "bitflags 2.13.0", + "byteorder", + "heed-traits", + "heed-types", + "libc", + "lmdb-master-sys", + "once_cell", + "page_size", + "serde", + "synchronoise", + "url", +] + +[[package]] +name = "heed-traits" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb3130048d404c57ce5a1ac61a903696e8fcde7e8c2991e9fcfc1f27c3ef74ff" + +[[package]] +name = "heed-types" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c255bdf46e07fb840d120a36dcc81f385140d7191c76a7391672675c01a55d" +dependencies = [ + "bincode 1.3.3", + "byteorder", + "heed-traits", + "serde", + "serde_json", +] + [[package]] name = "hermit-abi" version = "0.5.2" @@ -4309,6 +6731,12 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b07f60793ff0a4d9cef0f18e63b5357e06209987153a64648c972c1e5aff336f" +[[package]] +name = "hexf-parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" + [[package]] name = "hickory-net" version = "0.26.1" @@ -4394,6 +6822,15 @@ dependencies = [ "hmac 0.12.1", ] +[[package]] +name = "hkdf" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4aaa26c720c68b866f2c96ef5c1264b3e6f473fe5d4ce61cd44bbe913e553018" +dependencies = [ + "hmac 0.13.0", +] + [[package]] name = "hmac" version = "0.12.1" @@ -4577,6 +7014,7 @@ dependencies = [ "tokio", "tokio-rustls", "tower-service", + "webpki-roots 1.0.8", ] [[package]] @@ -4598,7 +7036,7 @@ version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ - "base64", + "base64 0.22.1", "bytes", "futures-channel", "futures-util", @@ -4816,6 +7254,46 @@ dependencies = [ "xmltree", ] +[[package]] +name = "image" +version = "0.25.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104" +dependencies = [ + "bytemuck", + "byteorder-lite", + "color_quant", + "exr", + "gif", + "image-webp", + "moxcms", + "num-traits", + "png", + "qoi", + "ravif", + "rayon", + "rgb", + "tiff", + "zune-core", + "zune-jpeg", +] + +[[package]] +name = "image-webp" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3" +dependencies = [ + "byteorder-lite", + "quick-error", +] + +[[package]] +name = "imgref" +version = "1.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89194689a993ab15268672e99e7b0e19da2da3268ac682e8f02d29d4d1434cd7" + [[package]] name = "include_dir" version = "0.7.4" @@ -4890,6 +7368,12 @@ dependencies = [ "str_stack", ] +[[package]] +name = "inflections" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a257582fdcde896fd96463bf2d40eefea0580021c0712a0e2b028b60b47a837a" + [[package]] name = "inout" version = "0.1.4" @@ -4920,7 +7404,7 @@ version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5eb2d60ef19920a3a9193c3e371f726ec1dafc045dac788d0fb3704272458971" dependencies = [ - "darling", + "darling 0.23.0", "indoc", "proc-macro2", "quote", @@ -4936,6 +7420,17 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "interpolate_name" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + [[package]] name = "inventory" version = "0.3.24" @@ -5041,7 +7536,7 @@ dependencies = [ "cfg-if", "combine", "jni-macros", - "jni-sys", + "jni-sys 0.4.1", "log", "simd_cesu8", "thiserror 2.0.18", @@ -5062,6 +7557,12 @@ dependencies = [ "syn 2.0.118", ] +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + [[package]] name = "jni-sys" version = "0.4.1" @@ -5120,7 +7621,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eba32bfb4ffdeaca3e34431072faf01745c9b26d25504aa7a6cf5684334fc4fc" dependencies = [ "aws-lc-rs", - "base64", + "base64 0.22.1", "getrandom 0.2.17", "js-sys", "serde", @@ -5149,6 +7650,57 @@ dependencies = [ "cpufeatures 0.2.17", ] +[[package]] +name = "khronos-egl" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" +dependencies = [ + "libc", + "libloading 0.8.8", + "pkg-config", +] + +[[package]] +name = "khronos_api" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" + +[[package]] +name = "kiddo" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20131a7981708df6a051a83cefb0d7675f587241a46857ebf196e1304c8f90f7" +dependencies = [ + "aligned-vec 0.6.4", + "array-init", + "az", + "bytecheck", + "cmov", + "fixed", + "generator 0.8.9", + "nonmax", + "num-traits", + "ptr_meta", + "rayon", + "rkyv", + "sorted-vec", + "test-log", + "tracing", + "yep-cache-line-size", +] + +[[package]] +name = "kstat-rs" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27964e4632377753acb0898ce6f28770d50cbca1339200ae63d700cff97b5c2b" +dependencies = [ + "libc", + "thiserror 1.0.69", +] + [[package]] name = "lab" version = "0.11.0" @@ -5225,6 +7777,12 @@ dependencies = [ "thiserror 2.0.18", ] +[[package]] +name = "lebe" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" + [[package]] name = "lexical" version = "7.0.5" @@ -5297,6 +7855,26 @@ version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" +[[package]] +name = "libfuzzer-sys" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9fd2f41a1cba099f79a0b6b6c35656cf7c03351a7bae8ff0f28f25270f929d2" +dependencies = [ + "arbitrary", + "cc", +] + +[[package]] +name = "libloading" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" +dependencies = [ + "cfg-if", + "windows-targets 0.53.5", +] + [[package]] name = "libloading" version = "0.9.0" @@ -5307,6 +7885,27 @@ dependencies = [ "windows-link", ] +[[package]] +name = "liblzma" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45aec2360b3933207e27908049d8e4df4e476b58180afb1e56b2a4fb72efe4ba" +dependencies = [ + "liblzma-sys", + "num_cpus", +] + +[[package]] +name = "liblzma-sys" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a046c7f353ba30f810545151e04f63545833803f5b86ee3ddf1517247fe560a5" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + [[package]] name = "libm" version = "0.2.16" @@ -5419,7 +8018,7 @@ source = "git+https://github.com/libp2p/rust-libp2p?rev=86b2122c8ffdd636d0c939b0 dependencies = [ "async-channel", "asynchronous-codec", - "base64", + "base64 0.22.1", "byteorder", "bytes", "either", @@ -5427,7 +8026,7 @@ dependencies = [ "futures", "futures-timer", "getrandom 0.2.17", - "hashlink", + "hashlink 0.11.1", "hex_fmt", "libp2p-core", "libp2p-identity", @@ -5469,7 +8068,7 @@ source = "git+https://github.com/libp2p/rust-libp2p?rev=86b2122c8ffdd636d0c939b0 dependencies = [ "bs58", "ed25519-dalek", - "hkdf", + "hkdf 0.12.4", "multihash", "quick-protobuf", "rand 0.8.6", @@ -5594,7 +8193,7 @@ dependencies = [ "futures", "futures-timer", "getrandom 0.2.17", - "hashlink", + "hashlink 0.11.1", "libp2p-core", "libp2p-identity", "libp2p-swarm-derive", @@ -5687,6 +8286,17 @@ dependencies = [ "libc", ] +[[package]] +name = "libsqlite3-sys" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "133c182a6a2c87864fe97778797e46c7e999672690dc9fa3ee8e241aa4a9c13f" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + [[package]] name = "libtest-mimic" version = "0.8.2" @@ -5736,6 +8346,17 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" +[[package]] +name = "lmdb-master-sys" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aaeb9bd22e73bd1babffff614994b341e9b2008de7bb73bf1f7e9154f1978f8b" +dependencies = [ + "cc", + "doxygen-rs", + "libc", +] + [[package]] name = "lock_api" version = "0.4.14" @@ -5790,44 +8411,112 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff50ecb28bb86013e935fb6683ab1f6d3a20016f123c76fd4c27470076ac30f5" dependencies = [ "cfg-if", - "generator", + "generator 0.7.5", "scoped-tls", "tracing", "tracing-subscriber", ] [[package]] -name = "lru" -version = "0.16.4" +name = "loop9" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" +dependencies = [ + "imgref", +] + +[[package]] +name = "lru" +version = "0.16.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f66e8d5d03f609abc3a39e6f08e4164ebf1447a732906d39eb9b99b7919ef39" +dependencies = [ + "hashbrown 0.16.1", +] + +[[package]] +name = "lru" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a860605968fce16869fd239cf4237a82f3ac470723415db603b0e8b6c8d4fb9" +dependencies = [ + "hashbrown 0.17.1", +] + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "mac_address" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0aeb26bf5e836cc1c341c8106051b573f1766dfa05aa87f0b98be5e51b02303" +dependencies = [ + "nix 0.29.0", + "winapi", +] + +[[package]] +name = "macerator" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "508a2f720538bb7e3ea3cb6d098615b107cb82ae387d77d906276905e9ec894b" +dependencies = [ + "bytemuck", + "cfg_aliases", + "half", + "macerator-macros", + "moddef", + "num-traits", + "paste", + "rustc_version", +] + +[[package]] +name = "macerator-macros" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f66e8d5d03f609abc3a39e6f08e4164ebf1447a732906d39eb9b99b7919ef39" +checksum = "ed5ce85961d618ce9794bdf822bfe96fe9dd341aa5b033b454f7a8d96e79b9b1" dependencies = [ - "hashbrown 0.16.1", + "darling 0.20.11", + "proc-macro2", + "quote", + "syn 2.0.118", ] [[package]] -name = "lru" -version = "0.18.0" +name = "mach2" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a860605968fce16869fd239cf4237a82f3ac470723415db603b0e8b6c8d4fb9" +checksum = "dae608c151f68243f2b000364e1f7b186d9c29845f7d2d85bd31b9ad77ad552b" + +[[package]] +name = "macro_rules_attribute" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65049d7923698040cd0b1ddcced9b0eb14dd22c5f86ae59c3740eab64a676520" dependencies = [ - "hashbrown 0.17.1", + "macro_rules_attribute-proc_macro", + "paste", ] [[package]] -name = "lru-slab" -version = "0.1.2" +name = "macro_rules_attribute-proc_macro" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" +checksum = "670fdfda89751bc4a84ac13eaa63e205cf0fd22b4c9a5fbfa085b63c1f1d3a30" [[package]] -name = "mac_address" -version = "1.1.8" +name = "madvise" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0aeb26bf5e836cc1c341c8106051b573f1766dfa05aa87f0b98be5e51b02303" +checksum = "4e1e75c3c34c2b34cec9f127418cb35240c7ebee5de36a51437e6b382c161b86" dependencies = [ - "nix 0.29.0", - "winapi", + "libc", ] [[package]] @@ -5856,6 +8545,29 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" +[[package]] +name = "matrixmultiply" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06de3016e9fae57a36fd14dba131fccf49f74b40b7fbdb472f96e361ec71a08" +dependencies = [ + "autocfg", + "num_cpus", + "once_cell", + "rawpointer", + "thread-tree", +] + +[[package]] +name = "maybe-rayon" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" +dependencies = [ + "cfg-if", + "rayon", +] + [[package]] name = "md-5" version = "0.11.0" @@ -5866,12 +8578,28 @@ dependencies = [ "digest 0.11.3", ] +[[package]] +name = "md5" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ebb8d8732c6a6df3d8f032a82911cfc747e00efb95cc46e8d0acd5b5b88570c" + [[package]] name = "memchr" version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" +[[package]] +name = "memmap2" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3" +dependencies = [ + "libc", + "stable_deref_trait", +] + [[package]] name = "memmem" version = "0.1.1" @@ -5925,6 +8653,12 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "min-max-heap" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2687e6cf9c00f48e9284cf9fd15f2ef341d03cc7743abf9df4c5f07fdee50b18" + [[package]] name = "minicov" version = "0.3.8" @@ -5963,16 +8697,25 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "moddef" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a0b3262dc837d2513fe2ef31ff8461352ef932dcca31ba0c0abe33547cf6b9b" + [[package]] name = "moka" version = "0.12.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "957228ad12042ee839f93c8f257b62b4c0ab5eaae1d4fa60de53b27c9d7c5046" dependencies = [ + "async-lock", "crossbeam-channel", "crossbeam-epoch", "crossbeam-utils", "equivalent", + "event-listener", + "futures-util", "parking_lot", "portable-atomic", "smallvec 1.15.2", @@ -5980,6 +8723,38 @@ dependencies = [ "uuid", ] +[[package]] +name = "monostate" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f766eeb5719df144c29802e56f3f7e0b3f29bd3ec8ab6c819aa1eaddec3f80c" +dependencies = [ + "monostate-impl", + "serde", + "serde_core", +] + +[[package]] +name = "monostate-impl" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d4b3dcd6ce9277a52fb05f00bf3e45d475af8cce7376de2f8d6bd065fa4adb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "moxcms" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b" +dependencies = [ + "num-traits", + "pxfm", +] + [[package]] name = "multiaddr" version = "0.18.2" @@ -6039,12 +8814,58 @@ dependencies = [ "unsigned-varint", ] +[[package]] +name = "munge" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e22e7961c873e8b305b176d2a4e1d41ce7ba31bc1c52d2a107a89568ec74c55" +dependencies = [ + "munge_macro", +] + +[[package]] +name = "munge_macro" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ac7d860b767c6398e88fe93db73ce53eb496057aa6895ffa4d60cb02e1d1c6b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + [[package]] name = "mutants" version = "0.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc0287524726960e07b119cebd01678f852f147742ae0d925e6a520dca956126" +[[package]] +name = "naga" +version = "29.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2bf919621e7975acb27d881bae2fb993e0d45c8e0446e85e6272971e00dc8df" +dependencies = [ + "arrayvec 0.7.7", + "bit-set 0.9.1", + "bitflags 2.13.0", + "cfg-if", + "cfg_aliases", + "codespan-reporting", + "half", + "hashbrown 0.16.1", + "hexf-parse", + "indexmap 2.14.0", + "libm", + "log", + "num-traits", + "once_cell", + "rustc-hash 1.1.0", + "spirv", + "thiserror 2.0.18", + "unicode-ident", +] + [[package]] name = "napi" version = "3.9.4" @@ -6057,7 +8878,7 @@ dependencies = [ "napi-build", "napi-sys", "nohash-hasher", - "rustc-hash", + "rustc-hash 2.1.2", "tokio", ] @@ -6100,7 +8921,66 @@ version = "3.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f5bcdf71abd3a50d00b49c1c2c75251cb3c913777d6139cd37dabc093a5e400" dependencies = [ - "libloading", + "libloading 0.9.0", +] + +[[package]] +name = "nb" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f" +dependencies = [ + "nb 1.1.0", +] + +[[package]] +name = "nb" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" + +[[package]] +name = "ndarray" +version = "0.15.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb12d4e967ec485a5f71c6311fe28158e9d6f4bc4a447b474184d0f91a8fa32" +dependencies = [ + "matrixmultiply", + "num-complex 0.4.6", + "num-integer", + "num-traits", + "rawpointer", +] + +[[package]] +name = "ndarray" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "882ed72dce9365842bf196bdeedf5055305f11fc8c03dee7bb0194a6cad34841" +dependencies = [ + "matrixmultiply", + "num-complex 0.4.6", + "num-integer", + "num-traits", + "portable-atomic", + "portable-atomic-util", + "rawpointer", +] + +[[package]] +name = "ndarray" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520080814a7a6b4a6e9070823bb24b4531daac8c4627e08ba5de8c5ef2f2752d" +dependencies = [ + "matrixmultiply", + "num-complex 0.4.6", + "num-integer", + "num-traits", + "portable-atomic", + "portable-atomic-util", + "rawpointer", + "rayon", ] [[package]] @@ -6109,6 +8989,15 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys 0.3.0", +] + [[package]] name = "nested" version = "0.1.1" @@ -6245,6 +9134,15 @@ dependencies = [ "libc", ] +[[package]] +name = "no_std_io2" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418abd1b6d34fbf6cae440dc874771b0525a604428704c76e48b29a5e67b8003" +dependencies = [ + "memchr", +] + [[package]] name = "nodejs_package_json" version = "0.4.9" @@ -6253,7 +9151,7 @@ checksum = "9c87ccf239f2f9d82f1efe31340ae1229c50a0e46ddd4d57a2518a25476061cd" dependencies = [ "indexmap 2.14.0", "regex", - "rustc-hash", + "rustc-hash 2.1.2", "semver", "serde", "serde_json", @@ -6300,6 +9198,21 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "610a5acd306ec67f907abe5567859a3c693fb9886eb1f012ab8f2a47bef3db51" +[[package]] +name = "noop_proc_macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" + +[[package]] +name = "ntapi" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3b335231dfd352ffb0f8017f3b6027a4917f7df785ea2143d8af2adc66980ae" +dependencies = [ + "winapi", +] + [[package]] name = "nu-ansi-term" version = "0.50.3" @@ -6316,7 +9229,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" dependencies = [ "num-bigint", - "num-complex", + "num-complex 0.4.6", "num-integer", "num-iter", "num-rational", @@ -6333,12 +9246,23 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-complex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6b19411a9719e753aff12e5187b74d60d3dc449ec3f4dc21e3989c3f554bc95" +dependencies = [ + "autocfg", + "num-traits", +] + [[package]] name = "num-complex" version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" dependencies = [ + "bytemuck", "num-traits", ] @@ -6435,6 +9359,45 @@ dependencies = [ "libc", ] +[[package]] +name = "nvml-wrapper" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f049ae562349fefb8e837eb15443da1e7c6dcbd8a11f52a228f92220c2e5c85e" +dependencies = [ + "bitflags 2.13.0", + "libloading 0.8.8", + "nvml-wrapper-sys", + "static_assertions", + "thiserror 1.0.69", + "wrapcenum-derive", +] + +[[package]] +name = "nvml-wrapper-sys" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b4d594420fcda43b1c2c4bd44d48974aa3c7a9ab2cbf10dc18e35265767bf0b" +dependencies = [ + "libloading 0.8.8", +] + +[[package]] +name = "objc-sys" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" + +[[package]] +name = "objc2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" +dependencies = [ + "objc-sys", + "objc2-encode", +] + [[package]] name = "objc2" version = "0.6.4" @@ -6451,8 +9414,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" dependencies = [ "bitflags 2.13.0", - "objc2", - "objc2-foundation", + "objc2 0.6.4", + "objc2-foundation 0.3.2", ] [[package]] @@ -6461,8 +9424,8 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa" dependencies = [ - "objc2", - "objc2-foundation", + "objc2 0.6.4", + "objc2-foundation 0.3.2", ] [[package]] @@ -6473,7 +9436,7 @@ checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ "bitflags 2.13.0", "dispatch2", - "objc2", + "objc2 0.6.4", ] [[package]] @@ -6484,7 +9447,7 @@ checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" dependencies = [ "bitflags 2.13.0", "dispatch2", - "objc2", + "objc2 0.6.4", "objc2-core-foundation", "objc2-io-surface", ] @@ -6495,60 +9458,119 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006" dependencies = [ - "objc2", - "objc2-foundation", + "objc2 0.6.4", + "objc2-foundation 0.3.2", +] + +[[package]] +name = "objc2-core-location" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009" +dependencies = [ + "objc2 0.6.4", + "objc2-foundation 0.3.2", +] + +[[package]] +name = "objc2-core-text" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" +dependencies = [ + "bitflags 2.13.0", + "objc2 0.6.4", + "objc2-core-foundation", + "objc2-core-graphics", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-foundation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" +dependencies = [ + "bitflags 2.13.0", + "block2 0.5.1", + "libc", + "objc2 0.5.2", +] + +[[package]] +name = "objc2-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +dependencies = [ + "bitflags 2.13.0", + "block2 0.6.2", + "libc", + "objc2 0.6.4", + "objc2-core-foundation", ] [[package]] -name = "objc2-core-location" +name = "objc2-io-kit" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009" +checksum = "33fafba39597d6dc1fb709123dfa8289d39406734be322956a69f0931c73bb15" dependencies = [ - "objc2", - "objc2-foundation", + "libc", + "objc2-core-foundation", ] [[package]] -name = "objc2-core-text" +name = "objc2-io-surface" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" +checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" dependencies = [ "bitflags 2.13.0", - "objc2", + "objc2 0.6.4", "objc2-core-foundation", - "objc2-core-graphics", ] [[package]] -name = "objc2-encode" -version = "4.1.0" +name = "objc2-metal" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" +checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" +dependencies = [ + "bitflags 2.13.0", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] [[package]] -name = "objc2-foundation" +name = "objc2-metal" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +checksum = "a0125f776a10d00af4152d74616409f0d4a2053a6f57fa5b7d6aa2854ac04794" dependencies = [ "bitflags 2.13.0", - "block2", - "libc", - "objc2", - "objc2-core-foundation", + "block2 0.6.2", + "objc2 0.6.4", + "objc2-foundation 0.3.2", ] [[package]] -name = "objc2-io-surface" -version = "0.3.2" +name = "objc2-quartz-core" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" +checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" dependencies = [ "bitflags 2.13.0", - "objc2", - "objc2-core-foundation", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", + "objc2-metal 0.2.2", ] [[package]] @@ -6558,9 +9580,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" dependencies = [ "bitflags 2.13.0", - "objc2", + "objc2 0.6.4", "objc2-core-foundation", - "objc2-foundation", + "objc2-foundation 0.3.2", + "objc2-metal 0.3.2", ] [[package]] @@ -6579,8 +9602,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" dependencies = [ "bitflags 2.13.0", - "block2", - "objc2", + "block2 0.6.2", + "objc2 0.6.4", "objc2-cloud-kit", "objc2-core-data", "objc2-core-foundation", @@ -6588,8 +9611,8 @@ dependencies = [ "objc2-core-image", "objc2-core-location", "objc2-core-text", - "objc2-foundation", - "objc2-quartz-core", + "objc2-foundation 0.3.2", + "objc2-quartz-core 0.3.2", "objc2-user-notifications", ] @@ -6599,8 +9622,8 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9df9128cbbfef73cda168416ccf7f837b62737d748333bfe9ab71c245d76613e" dependencies = [ - "objc2", - "objc2-foundation", + "objc2 0.6.4", + "objc2-foundation 0.3.2", ] [[package]] @@ -6637,6 +9660,34 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +[[package]] +name = "oneshot" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe21416a02c693fb9f980befcb230ecc70b0b3d1cc4abf88b9675c4c1457f0c" + +[[package]] +name = "onig" +version = "6.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc3cbf698f9438986c11a880c90a6d04b9de27575afd28bbf45b154b6c709e2" +dependencies = [ + "bitflags 2.13.0", + "libc", + "once_cell", + "onig_sys", +] + +[[package]] +name = "onig_sys" +version = "69.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e68317604e77e53b85896388e1a803c1d21b74c899ec9e5e1112db90735edd7" +dependencies = [ + "cc", + "pkg-config", +] + [[package]] name = "oorandom" version = "11.1.5" @@ -6734,6 +9785,12 @@ dependencies = [ "tokio-stream", ] +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + [[package]] name = "ordered-float" version = "2.10.1" @@ -6752,6 +9809,15 @@ dependencies = [ "num-traits", ] +[[package]] +name = "ordered-float" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7d950ca161dc355eaf28f82b11345ed76c6e1f6eb1f4f4479e0323b9e2fbd0e" +dependencies = [ + "num-traits", +] + [[package]] name = "os_info" version = "3.15.0" @@ -6761,8 +9827,8 @@ dependencies = [ "android_system_properties", "log", "nix 0.31.3", - "objc2", - "objc2-foundation", + "objc2 0.6.4", + "objc2-foundation 0.3.2", "objc2-ui-kit", "serde", "windows-sys 0.61.2", @@ -6829,7 +9895,7 @@ dependencies = [ "bumpalo", "hashbrown 0.15.5", "oxc_data_structures", - "rustc-hash", + "rustc-hash 2.1.2", ] [[package]] @@ -6887,7 +9953,7 @@ dependencies = [ "oxc_sourcemap", "oxc_span", "oxc_syntax", - "rustc-hash", + "rustc-hash 2.1.2", ] [[package]] @@ -6952,7 +10018,7 @@ dependencies = [ "oxc_regular_expression", "oxc_span", "oxc_syntax", - "rustc-hash", + "rustc-hash 2.1.2", "seq-macro", ] @@ -6967,7 +10033,7 @@ dependencies = [ "oxc_diagnostics", "oxc_span", "phf 0.13.1", - "rustc-hash", + "rustc-hash 2.1.2", "unicode-id-start", ] @@ -6987,7 +10053,7 @@ dependencies = [ "oxc_span", "oxc_syntax", "phf 0.13.1", - "rustc-hash", + "rustc-hash 2.1.2", "self_cell", ] @@ -6999,7 +10065,7 @@ checksum = "6d378eb8bad20e89d66276aebab51f6a5408571092cac94abdd3eabb773713d6" dependencies = [ "base64-simd", "json-escape-simd", - "rustc-hash", + "rustc-hash 2.1.2", "serde", "serde_json", ] @@ -7051,7 +10117,7 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cbf71184cc5ecc2e4e1baccdb21026c20e5fc3dcf63028a086131b3ab00b6e6" dependencies = [ - "approx", + "approx 0.5.1", "fast-srgb8", "libm", "palette_derive", @@ -7069,6 +10135,16 @@ dependencies = [ "syn 2.0.118", ] +[[package]] +name = "papaya" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "997ee03cd38c01469a7046643714f0ad28880bcb9e6679ff0666e24817ca19b7" +dependencies = [ + "equivalent", + "seize", +] + [[package]] name = "parking" version = "2.2.1" @@ -7123,12 +10199,29 @@ dependencies = [ "syn 2.0.118", ] +[[package]] +name = "password-hash" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" +dependencies = [ + "base64ct", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "paste" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "pastey" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec" + [[package]] name = "pathdiff" version = "0.2.3" @@ -7144,7 +10237,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8edd1efdd8ab23ba9cb9ace3d9987a72663d5d7c9f74fa00b51d6213645cf6c" dependencies = [ - "base64", + "base64 0.22.1", "serde", ] @@ -7160,13 +10253,25 @@ dependencies = [ "prost-types", ] +[[package]] +name = "pbkdf2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" +dependencies = [ + "digest 0.10.7", + "hmac 0.12.1", + "password-hash", + "sha2 0.10.9", +] + [[package]] name = "pem" version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" dependencies = [ - "base64", + "base64 0.22.1", "serde_core", ] @@ -7238,6 +10343,7 @@ dependencies = [ "fixedbitset 0.5.7", "hashbrown 0.15.5", "indexmap 2.14.0", + "serde", ] [[package]] @@ -7383,13 +10489,19 @@ dependencies = [ "spki", ] +[[package]] +name = "pkg-config" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + [[package]] name = "plist" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "092791278e026273c1b65bbdcfbba3a300f2994c896bd01ab01da613c29c46f1" dependencies = [ - "base64", + "base64 0.22.1", "indexmap 2.14.0", "quick-xml", "serde", @@ -7424,6 +10536,19 @@ dependencies = [ "plotters-backend", ] +[[package]] +name = "png" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" +dependencies = [ + "bitflags 2.13.0", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + [[package]] name = "polling" version = "3.11.0" @@ -7466,6 +10591,18 @@ name = "portable-atomic" version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" +dependencies = [ + "serde", +] + +[[package]] +name = "portable-atomic-util" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" +dependencies = [ + "portable-atomic", +] [[package]] name = "postgres-derive" @@ -7485,10 +10622,10 @@ version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08808e3c483c46e999108051c78334f473d5adb59d78bb80a1268c7e6aa6c514" dependencies = [ - "base64", + "base64 0.22.1", "byteorder", "bytes", - "fallible-iterator", + "fallible-iterator 0.2.0", "hmac 0.13.0", "md-5", "memchr", @@ -7504,7 +10641,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "851ca9db4932932d69f3ea811b1abe63087a0f740a47692619dd40d4899b68be" dependencies = [ "bytes", - "fallible-iterator", + "fallible-iterator 0.2.0", "postgres-derive", "postgres-protocol", "serde_core", @@ -7554,6 +10691,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "presser" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" + [[package]] name = "pretty" version = "0.12.5" @@ -7585,6 +10728,15 @@ dependencies = [ "syn 2.0.118", ] +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -7618,6 +10770,25 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "profiling" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d595e54a326bc53c1c197b32d295e14b169e3cfeaa8dc82b529f947fba6bcf5" +dependencies = [ + "profiling-procmacros", +] + +[[package]] +name = "profiling-procmacros" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4488a4a36b9a4ba6b9334a32a39971f77c1436ec82c38707bce707699cc3bbcb" +dependencies = [ + "quote", + "syn 2.0.118", +] + [[package]] name = "prometheus-client" version = "0.24.1" @@ -7649,6 +10820,7 @@ checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744" dependencies = [ "bitflags 2.13.0", "num-traits", + "proptest-macro", "rand 0.9.4", "rand_chacha 0.9.0", "rand_xorshift", @@ -7656,6 +10828,18 @@ dependencies = [ "unarray", ] +[[package]] +name = "proptest-macro" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efaa288b896cb2b345da7b7f2110ab19e51565b83495b56fcec98a62f8b1f33e" +dependencies = [ + "convert_case 0.11.0", + "proc-macro2", + "quote", + "syn 2.0.118", +] + [[package]] name = "prost" version = "0.14.4" @@ -7765,6 +10949,26 @@ dependencies = [ "cc", ] +[[package]] +name = "ptr_meta" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b9a0cf95a1196af61d4f1cbdab967179516d9a4a4312af1f31948f8f6224a79" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7347867d0a7e1208d93b46767be83e2b8f978c3dad35f775ac8d8847551d6fe1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + [[package]] name = "pulldown-cmark" version = "0.13.4" @@ -7785,6 +10989,50 @@ dependencies = [ "pulldown-cmark", ] +[[package]] +name = "pulp" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "046aa45b989642ec2e4717c8e72d677b13edd831a4d3b6cf37d9a3e54912496a" +dependencies = [ + "bytemuck", + "cfg-if", + "libm", + "num-complex 0.4.6", + "paste", + "pulp-wasm-simd-flag", + "raw-cpuid", + "reborrow", + "version_check", +] + +[[package]] +name = "pulp-wasm-simd-flag" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d8f70e07b9c3962945a74e59ca1c511bba65b6419468acc217c457d93f3c740" + +[[package]] +name = "pxfm" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d55d956fa96f5ec02be2e13af0e20391a5aa83d6a074e3ad368959d0fab299ea" + +[[package]] +name = "qoi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + [[package]] name = "quick-protobuf" version = "0.8.1" @@ -7827,7 +11075,7 @@ dependencies = [ "pin-project-lite", "quinn-proto", "quinn-udp", - "rustc-hash", + "rustc-hash 2.1.2", "rustls", "socket2", "thiserror 2.0.18", @@ -7848,7 +11096,7 @@ dependencies = [ "lru-slab", "rand 0.9.4", "ring", - "rustc-hash", + "rustc-hash 2.1.2", "rustls", "rustls-pki-types", "slab", @@ -7893,12 +11141,43 @@ version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" +[[package]] +name = "r2d2" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51de85fb3fb6524929c8a2eb85e6b6d363de4e8c48f9e2c2eac4944abc181c93" +dependencies = [ + "log", + "parking_lot", + "scheduled-thread-pool", +] + +[[package]] +name = "r2d2_sqlite" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63417e83dc891797eea3ad379f52a5986da4bca0d6ef28baf4d14034dd111b0c" +dependencies = [ + "r2d2", + "rusqlite", + "uuid", +] + [[package]] name = "radium" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" +[[package]] +name = "rancor" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daff8b7b3ccf5f7ba270b3e7a0a4d4c701c5797e38dec27c7e2c3dbb830fed1c" +dependencies = [ + "ptr_meta", +] + [[package]] name = "rand" version = "0.8.6" @@ -7975,6 +11254,16 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" +[[package]] +name = "rand_distr" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8615d50dcf34fa31f7ab52692afec947c4dd0ab803cc87cb3b0b4570ff7463" +dependencies = [ + "num-traits", + "rand 0.9.4", +] + [[package]] name = "rand_distr" version = "0.6.0" @@ -8003,6 +11292,12 @@ dependencies = [ "rand_core 0.10.1", ] +[[package]] +name = "range-alloc" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca45419789ae5a7899559e9512e58ca889e41f04f1f2445e9f4b290ceccd1d08" + [[package]] name = "rapidfuzz" version = "0.5.0" @@ -8110,6 +11405,88 @@ dependencies = [ "unicode-width 0.2.2", ] +[[package]] +name = "rav1e" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43b6dd56e85d9483277cde964fd1bdb0428de4fec5ebba7540995639a21cb32b" +dependencies = [ + "aligned-vec 0.6.4", + "arbitrary", + "arg_enum_proc_macro", + "arrayvec 0.7.7", + "av-scenechange", + "av1-grain", + "bitstream-io", + "built", + "cfg-if", + "interpolate_name", + "itertools 0.14.0", + "libc", + "libfuzzer-sys", + "log", + "maybe-rayon", + "new_debug_unreachable", + "noop_proc_macro", + "num-derive", + "num-traits", + "paste", + "profiling", + "rand 0.9.4", + "rand_chacha 0.9.0", + "simd_helpers", + "thiserror 2.0.18", + "v_frame", + "wasm-bindgen", +] + +[[package]] +name = "ravif" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e52310197d971b0f5be7fe6b57530dcd27beb35c1b013f29d66c1ad73fbbcc45" +dependencies = [ + "avif-serialize", + "imgref", + "loop9", + "quick-error", + "rav1e", + "rayon", + "rgb", +] + +[[package]] +name = "raw-cpuid" +version = "11.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" +dependencies = [ + "bitflags 2.13.0", +] + +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + +[[package]] +name = "raw-window-metal" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2000e45d7daa9b6d946e88dfa1d7ae330424a81918a6545741821c989eb80a9" +dependencies = [ + "objc2 0.5.2", + "objc2-foundation 0.2.2", + "objc2-quartz-core 0.2.2", +] + +[[package]] +name = "rawpointer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" + [[package]] name = "rayon" version = "1.12.0" @@ -8120,6 +11497,17 @@ dependencies = [ "rayon-core", ] +[[package]] +name = "rayon-cond" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2964d0cf57a3e7a06e8183d14a8b527195c706b7983549cd5462d5aa3747438f" +dependencies = [ + "either", + "itertools 0.14.0", + "rayon", +] + [[package]] name = "rayon-core" version = "1.13.0" @@ -8143,6 +11531,12 @@ dependencies = [ "yasna 0.5.2", ] +[[package]] +name = "reborrow" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03251193000f4bd3b042892be858ee50e8b3719f2b08e5833ac4353724632430" + [[package]] name = "recursion" version = "0.5.4" @@ -8158,6 +11552,17 @@ dependencies = [ "bitflags 2.13.0", ] +[[package]] +name = "redox_users" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +dependencies = [ + "getrandom 0.2.17", + "libredox", + "thiserror 2.0.18", +] + [[package]] name = "ref-cast" version = "1.0.25" @@ -8262,13 +11667,69 @@ version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" +[[package]] +name = "rend" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "663ba70707f96e871406fe10d68128412e619b06d1d47cb91c3a4c6501176240" +dependencies = [ + "bytecheck", +] + +[[package]] +name = "renderdoc-sys" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" + +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http 1.4.2", + "http-body 1.0.1", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots 1.0.8", +] + [[package]] name = "reqwest" version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" dependencies = [ - "base64", + "base64 0.22.1", "bytes", "futures-channel", "futures-core", @@ -8313,7 +11774,7 @@ dependencies = [ "anyhow", "async-trait", "http 1.4.2", - "reqwest", + "reqwest 0.13.4", "serde", "thiserror 2.0.18", "tower-service", @@ -8331,7 +11792,7 @@ dependencies = [ "getrandom 0.2.17", "http 1.4.2", "hyper", - "reqwest", + "reqwest 0.13.4", "reqwest-middleware", "retry-policies", "thiserror 2.0.18", @@ -8350,7 +11811,7 @@ dependencies = [ "getrandom 0.2.17", "http 1.4.2", "matchit", - "reqwest", + "reqwest 0.13.4", "reqwest-middleware", "tracing", ] @@ -8393,6 +11854,67 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rkyv" +version = "0.8.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "815cc8a37159a463064825246cadb07961e25cd9885908606f6d08a98d8f8874" +dependencies = [ + "bytecheck", + "bytes", + "hashbrown 0.17.1", + "indexmap 2.14.0", + "munge", + "ptr_meta", + "rancor", + "rend", + "rkyv_derive", + "tinyvec", + "uuid", +] + +[[package]] +name = "rkyv_derive" +version = "0.8.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ed1a78a1b19d184b0daa629dd9a024573173ec7d485b287cb369fb3607cc1c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "rmp" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "228ed7c16fa39782c3b3468e974aec2795e9089153cd08ee2e9aefb3613334c4" +dependencies = [ + "byteorder", + "num-traits", + "paste", +] + +[[package]] +name = "rmp-serde" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f81bee8c8ef9b577d1681a70ebbc962c232461e397b22c208c43c04b67a155" +dependencies = [ + "rmp", + "serde", +] + +[[package]] +name = "roaring" +version = "0.10.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19e8d2cfa184d94d0726d650a9f4a1be7f9b76ac9fdb954219878dc00c1c1e7b" +dependencies = [ + "bytemuck", + "byteorder", +] + [[package]] name = "roaring" version = "0.11.4" @@ -8433,6 +11955,8 @@ version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f5a3193c063baaa2a95a33f03035c8a72b83d97a54916055ba22d35ed3839d49" dependencies = [ + "futures-timer", + "futures-util", "rstest_macros", ] @@ -8444,6 +11968,7 @@ checksum = "9c845311f0ff7951c5506121a9ad75aec44d083c31583b2ea5a30bcb0b0abba0" dependencies = [ "cfg-if", "glob", + "proc-macro-crate", "proc-macro2", "quote", "regex", @@ -8471,12 +11996,32 @@ dependencies = [ "tokio", ] +[[package]] +name = "rusqlite" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "165ca6e57b20e1351573e3729b958bc62f0e48025386970b6e4d29e7a7e71f3f" +dependencies = [ + "bitflags 2.13.0", + "fallible-iterator 0.3.0", + "fallible-streaming-iterator", + "hashlink 0.10.0", + "libsqlite3-sys", + "smallvec 1.15.2", +] + [[package]] name = "rustc-demangle" version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustc-hash" version = "2.1.2" @@ -8628,6 +12173,27 @@ version = "5.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68f5acb362a0e75c2a963532fa7fabf13dff81626dc494df16488d30befcbea0" +[[package]] +name = "safetensors" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d93279b86b3de76f820a8854dd06cbc33cfa57a417b19c47f6a25280112fb1df" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "safetensors" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "675656c1eabb620b921efea4f9199f97fc86e36dd6ffd1fbbe48d0f59a4987f5" +dependencies = [ + "hashbrown 0.16.1", + "serde", + "serde_json", +] + [[package]] name = "same-file" version = "1.0.6" @@ -8637,6 +12203,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "sanitize-filename" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc984f4f9ceb736a7bb755c3e3bd17dc56370af2600c9780dcc48c66453da34d" +dependencies = [ + "regex", +] + [[package]] name = "scc" version = "3.8.4" @@ -8656,6 +12231,15 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "scheduled-thread-pool" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cbc66816425a074528352f5789333ecff06ca41b36b0b0efdfbb29edc391a19" +dependencies = [ + "parking_lot", +] + [[package]] name = "schemars" version = "0.9.0" @@ -8675,11 +12259,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" dependencies = [ "dyn-clone", + "indexmap 2.14.0", "ref-cast", + "schemars_derive", "serde", "serde_json", ] +[[package]] +name = "schemars_derive" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d115b50f4aaeea07e79c1912f645c7513d81715d0420f8bc77a18c6260b307f" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 2.0.118", +] + [[package]] name = "scoped-tls" version = "1.0.1" @@ -8724,6 +12322,16 @@ dependencies = [ "libc", ] +[[package]] +name = "seize" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b55fb86dfd3a2f5f76ea78310a88f96c4ea21a3031f8d212443d56123fd0521" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + [[package]] name = "self_cell" version = "1.2.2" @@ -8754,7 +12362,7 @@ checksum = "82719d9cd9d2686541dc49875f7944ab71348edfcc6a5686414b22ea0b6d9b29" dependencies = [ "cfg_aliases", "httpdate", - "reqwest", + "reqwest 0.13.4", "rustls", "sentry-backtrace", "sentry-contexts", @@ -8764,7 +12372,7 @@ dependencies = [ "sentry-tower", "sentry-tracing", "tokio", - "ureq", + "ureq 3.3.0", ] [[package]] @@ -8895,6 +12503,16 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_bytes" +version = "0.11.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" +dependencies = [ + "serde", + "serde_core", +] + [[package]] name = "serde_core" version = "1.0.228" @@ -8960,6 +12578,19 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_qs" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2316d01592c3382277c5062105510e35e0a6bfb2851e30028485f7af8cf1240" +dependencies = [ + "axum", + "itoa", + "percent-encoding", + "ryu", + "serde", +] + [[package]] name = "serde_repr" version = "0.1.20" @@ -8971,6 +12602,16 @@ dependencies = [ "syn 2.0.118", ] +[[package]] +name = "serde_rusqlite" +version = "0.40.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec8bd74f47e124e760475a7e863b5820dcef09cae50782d03d65961f5ca1e6d9" +dependencies = [ + "rusqlite", + "serde_core", +] + [[package]] name = "serde_spanned" version = "1.1.1" @@ -8998,7 +12639,7 @@ version = "3.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76a5c54c7310e7b8b9577c286d7e399ddd876c3e12b3ed917a8aabc4b96e9e8c" dependencies = [ - "base64", + "base64 0.22.1", "bs58", "chrono", "hex", @@ -9018,7 +12659,7 @@ version = "3.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84d57bc0c8b9a17920c178daa6bb924850d54a9c97ab45194bb8c17ad66bb660" dependencies = [ - "darling", + "darling 0.23.0", "proc-macro2", "quote", "syn 2.0.118", @@ -9106,6 +12747,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "shlex" version = "2.0.1" @@ -9122,6 +12769,16 @@ dependencies = [ "signal-hook-registry", ] +[[package]] +name = "signal-hook" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a0c28ca5908dbdbcd52e6fdaa00358ab88637f8ab33e1f188dd510eb44b53d" +dependencies = [ + "libc", + "signal-hook-registry", +] + [[package]] name = "signal-hook-mio" version = "0.2.5" @@ -9130,7 +12787,7 @@ checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc" dependencies = [ "libc", "mio", - "signal-hook", + "signal-hook 0.3.18", ] [[package]] @@ -9168,6 +12825,15 @@ dependencies = [ "simdutf8", ] +[[package]] +name = "simd_helpers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" +dependencies = [ + "quote", +] + [[package]] name = "simdutf8" version = "0.1.5" @@ -9217,11 +12883,33 @@ version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" +[[package]] +name = "slotmap" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038" +dependencies = [ + "version_check", +] + +[[package]] +name = "slug" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "882a80f72ee45de3cc9a5afeb2da0331d58df69e4e7d8eeb5d3c7784ae67e724" +dependencies = [ + "deunicode", + "wasm-bindgen", +] + [[package]] name = "smallvec" version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" +dependencies = [ + "serde", +] [[package]] name = "smallvec" @@ -9281,6 +12969,12 @@ dependencies = [ "serde_json", ] +[[package]] +name = "sorted-vec" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f58d7b0190c7f12df7e8be6b79767a0836059159811b869d5ab55721fe14d0" + [[package]] name = "specta" version = "2.0.0-rc.22" @@ -9307,6 +13001,19 @@ name = "spin" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591" +dependencies = [ + "lock_api", + "portable-atomic", +] + +[[package]] +name = "spirv" +version = "0.4.0+sdk-1.4.341.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9571ea910ebd84c86af4b3ed27f9dbdc6ad06f17c5f96146b2b671e2976744f" +dependencies = [ + "bitflags 2.13.0", +] [[package]] name = "spki" @@ -9318,6 +13025,33 @@ dependencies = [ "der", ] +[[package]] +name = "spm_precompiled" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5851699c4033c63636f7ea4cf7b7c1f1bf06d0cc03cfb42e711de5a5c46cf326" +dependencies = [ + "base64 0.13.1", + "nom 7.1.3", + "serde", + "unicode-segmentation", +] + +[[package]] +name = "sprs" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dca58a33be2188d4edc71534f8bafa826e787cc28ca1c47f31be3423f0d6e55" +dependencies = [ + "alga", + "ndarray 0.15.6", + "num-complex 0.4.6", + "num-traits", + "num_cpus", + "rayon", + "smallvec 1.15.2", +] + [[package]] name = "sqruff-lib" version = "0.37.3" @@ -9382,6 +13116,12 @@ dependencies = [ "strum", ] +[[package]] +name = "stable-vec" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dac7bc0f7d0d44329b200020effbc25a534d89fa142af95e3ddf76113412a5e" + [[package]] name = "stable_deref_trait" version = "1.2.1" @@ -9413,10 +13153,20 @@ version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a3fe7c28c6512e766b0874335db33c94ad7b8f9054228ae1c2abd47ce7d335e" dependencies = [ - "approx", + "approx 0.5.1", "num-traits", ] +[[package]] +name = "steppe" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bae27ce2bb518cda778e53d49e6981c71337cb1f0ed11eec5e1cb1ce7d67989" +dependencies = [ + "convert_case 0.8.0", + "indexmap 2.14.0", +] + [[package]] name = "str_stack" version = "0.1.1" @@ -9563,6 +13313,15 @@ dependencies = [ "futures-core", ] +[[package]] +name = "synchronoise" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dbc01390fc626ce8d1cffe3376ded2b72a11bb70e1c75f404a210e4daa4def2" +dependencies = [ + "crossbeam-queue", +] + [[package]] name = "synstructure" version = "0.13.2" @@ -9574,6 +13333,34 @@ dependencies = [ "syn 2.0.118", ] +[[package]] +name = "sysctl" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01198a2debb237c62b6826ec7081082d951f46dbb64b0e8c7649a452230d1dfc" +dependencies = [ + "bitflags 2.13.0", + "byteorder", + "enum-as-inner", + "libc", + "thiserror 1.0.69", + "walkdir", +] + +[[package]] +name = "sysinfo" +version = "0.38.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ab6a2f8bfe508deb3c6406578252e491d299cbbf3bc0529ecc3313aee4a52f" +dependencies = [ + "libc", + "memchr", + "ntapi", + "objc2-core-foundation", + "objc2-io-kit", + "windows 0.62.2", +] + [[package]] name = "system-configuration" version = "0.7.0" @@ -9595,6 +13382,22 @@ dependencies = [ "libc", ] +[[package]] +name = "systemstat" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a583abe520746270ffdbdaf0e3039a806f29be9d7034d66466a4839a01de0610" +dependencies = [ + "bytesize", + "kstat-rs", + "lazy_static", + "libc", + "mach2", + "nom 7.1.3", + "time", + "winapi", +] + [[package]] name = "tachyonix" version = "0.3.1" @@ -9620,6 +13423,17 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" +[[package]] +name = "tar" +version = "0.4.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6221d9a6003c78398e3b239969f352578258df48c8eb051caadae0015bc840" +dependencies = [ + "filetime", + "libc", + "xattr", +] + [[package]] name = "target-lexicon" version = "0.13.5" @@ -9645,6 +13459,23 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "591ef38edfb78ca4771ee32cf494cb8771944bee237a9b91fc9c1424ac4b777b" +[[package]] +name = "tch" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e09b91610202dc4820c21eb474a42b386ef69f323b1c0902b5472ba7456ebb5" +dependencies = [ + "half", + "lazy_static", + "libc", + "ndarray 0.16.1", + "rand 0.8.6", + "safetensors 0.3.3", + "thiserror 1.0.69", + "torch-sys", + "zip 0.6.6", +] + [[package]] name = "tempfile" version = "3.27.0" @@ -9667,7 +13498,7 @@ dependencies = [ "anyhow", "async-trait", "backoff", - "base64", + "base64 0.22.1", "bon", "bytes", "derive_more", @@ -9752,7 +13583,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bce29ee903d46edec16df756e78e5ab125e4d342957174d4ac8ee30290ef249a" dependencies = [ "anyhow", - "base64", + "base64 0.22.1", "derive_more", "http 1.4.2", "pbjson", @@ -9795,7 +13626,7 @@ dependencies = [ "bitflags 2.13.0", "parking_lot", "rustix", - "signal-hook", + "signal-hook 0.3.18", "windows-sys 0.60.2", ] @@ -9837,7 +13668,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4676b37242ccbd1aabf56edb093a4827dc49086c0ffd764a5705899e0f35f8f7" dependencies = [ "anyhow", - "base64", + "base64 0.22.1", "bitflags 2.13.0", "fancy-regex 0.11.0", "filedescriptor", @@ -9856,7 +13687,7 @@ dependencies = [ "pest_derive", "phf 0.11.3", "sha2 0.10.9", - "signal-hook", + "signal-hook 0.3.18", "siphasher", "terminfo", "termios", @@ -9878,6 +13709,7 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b9c218384242b5c89b68303ab6f6fc53a312d923f0c14dc6bb860c6aeee40f1" dependencies = [ + "env_logger", "test-log-macros", "tracing-subscriber", ] @@ -9965,6 +13797,23 @@ dependencies = [ "serde", ] +[[package]] +name = "text_placeholder" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd5008f74a09742486ef0047596cf35df2b914e2a8dca5727fcb6ba6842a766b" +dependencies = [ + "hashbrown 0.13.2", + "serde", + "serde_json", +] + +[[package]] +name = "textdistance" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa672c55ab69f787dbc9126cc387dbe57fdd595f585e4524cf89018fa44ab819" + [[package]] name = "textwrap" version = "0.16.2" @@ -10016,6 +13865,15 @@ dependencies = [ "syn 2.0.118", ] +[[package]] +name = "thread-tree" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffbd370cb847953a25954d9f63e14824a36113f8c72eecf6eccef5dc4b45d630" +dependencies = [ + "crossbeam-channel", +] + [[package]] name = "thread_local" version = "1.1.9" @@ -10025,6 +13883,35 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "tiff" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63feaf3343d35b6ca4d50483f94843803b0f51634937cc2ec519fc32232bc52" +dependencies = [ + "fax", + "flate2", + "half", + "quick-error", + "weezl", + "zune-jpeg", +] + +[[package]] +name = "tiktoken-rs" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "027853bbf8c7763b77c5c595f1c271c7d536ced7d6f83452911b944621e57fc2" +dependencies = [ + "anyhow", + "base64 0.22.1", + "bstr", + "fancy-regex 0.17.0", + "lazy_static", + "regex", + "rustc-hash 2.1.2", +] + [[package]] name = "time" version = "0.3.51" @@ -10057,6 +13944,15 @@ dependencies = [ "time-core", ] +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + [[package]] name = "tinystr" version = "0.8.3" @@ -10092,6 +13988,39 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +[[package]] +name = "tokenizers" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b238e22d44a15349529690fb07bd645cf58149a1b1e44d6cb5bd1641ff1a6223" +dependencies = [ + "ahash", + "aho-corasick", + "compact_str", + "dary_heap", + "derive_builder", + "esaxx-rs", + "getrandom 0.3.4", + "itertools 0.14.0", + "log", + "macro_rules_attribute", + "monostate", + "onig", + "paste", + "rand 0.9.4", + "rayon", + "rayon-cond", + "regex", + "regex-syntax", + "serde", + "serde_json", + "spm_precompiled", + "thiserror 2.0.18", + "unicode-normalization-alignments", + "unicode-segmentation", + "unicode_categories", +] + [[package]] name = "tokio" version = "1.52.3" @@ -10105,6 +14034,7 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", + "tracing", "windows-sys 0.61.2", ] @@ -10128,7 +14058,7 @@ dependencies = [ "async-trait", "byteorder", "bytes", - "fallible-iterator", + "fallible-iterator 0.2.0", "futures-channel", "futures-util", "log", @@ -10178,6 +14108,18 @@ dependencies = [ "tokio-stream", ] +[[package]] +name = "tokio-tungstenite" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f72a05e828585856dacd553fba484c242c46e391fb0e58917c942ee9202915c" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite", +] + [[package]] name = "tokio-util" version = "0.7.18" @@ -10219,6 +14161,18 @@ dependencies = [ "serde_core", ] +[[package]] +name = "toml_edit" +version = "0.25.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ca1a40644a28bce036923f6a431df0b34236949d111cc07cb6dca830c9ef2e1" +dependencies = [ + "indexmap 2.14.0", + "toml_datetime", + "toml_parser", + "winnow", +] + [[package]] name = "toml_parser" version = "1.1.2+spec-1.1.0" @@ -10242,7 +14196,7 @@ checksum = "ac2a5518c70fa84342385732db33fb3f44bc4cc748936eb5833d2df34d6445ef" dependencies = [ "async-trait", "axum", - "base64", + "base64 0.22.1", "bytes", "flate2", "h2", @@ -10316,6 +14270,21 @@ dependencies = [ "tonic", ] +[[package]] +name = "torch-sys" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aef40c585e342df95b66a1fa7c923188623999c2b657227befb481dfb03a6a42" +dependencies = [ + "anyhow", + "cc", + "libc", + "serde", + "serde_json", + "ureq 2.10.1", + "zip 0.6.6", +] + [[package]] name = "tower" version = "0.5.3" @@ -10380,6 +14349,85 @@ dependencies = [ "tower-service", ] +[[package]] +name = "tracel-llvm" +version = "20.1.4-7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "982535db9eb1a30ac0f2c50239a0eec3e5cf50993a88e92b04747bd2f4d365b2" +dependencies = [ + "tracel-mlir-rs", + "tracel-mlir-sys", +] + +[[package]] +name = "tracel-llvm-bundler" +version = "20.1.4-7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c75b8e477cb8d49d907afab029ca74d48459f5b88c27bdb4c6cd6acb5e61977" +dependencies = [ + "anyhow", + "bytes", + "constcat", + "dirs", + "liblzma", + "regex", + "reqwest 0.12.28", + "serde", + "serde_json", + "sha2 0.10.9", + "tar", + "walkdir", +] + +[[package]] +name = "tracel-mlir-rs" +version = "20.1.4-7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a478a35efd68d0ba73f747adfb7923b121c64e7f5be9cd8364ca1dcb772d5c" +dependencies = [ + "tracel-mlir-rs-macros", + "tracel-mlir-sys", +] + +[[package]] +name = "tracel-mlir-rs-macros" +version = "20.1.4-7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a94f36868c3b10b1825945223d99d106c73f4d249f063caa4651deeb9379344" +dependencies = [ + "comrak", + "convert_case 0.8.0", + "proc-macro2", + "quote", + "regex", + "syn 2.0.118", + "tracel-llvm-bundler", + "tracel-tblgen-rs", + "unindent", +] + +[[package]] +name = "tracel-mlir-sys" +version = "20.1.4-7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02f26d31af0c225a6d2e3d65d012fd6de848c9fc776897b152ee83b7d1bd15c4" +dependencies = [ + "tracel-llvm-bundler", +] + +[[package]] +name = "tracel-tblgen-rs" +version = "20.1.4-7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00d2581070380418ccc33b500f3739e4d4869421fdb477fcea51ff97c6253a52" +dependencies = [ + "bindgen", + "cc", + "paste", + "thiserror 2.0.18", + "tracel-llvm-bundler", +] + [[package]] name = "tracing" version = "0.1.44" @@ -10559,12 +14607,46 @@ dependencies = [ name = "tsify-macros" version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fc2c44dc9fe4baf55b88e032621b7a11b215a1f0a7de8d0aa04367207d915bc" +checksum = "9fc2c44dc9fe4baf55b88e032621b7a11b215a1f0a7de8d0aa04367207d915bc" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 2.0.118", +] + +[[package]] +name = "tungstenite" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c01152af293afb9c7c2a57e4b559c5620b421f6d133261c60dd2d0cdb38e6b8" +dependencies = [ + "bytes", + "data-encoding", + "http 1.4.2", + "httparse", + "log", + "rand 0.9.4", + "sha1 0.10.6", + "thiserror 2.0.18", +] + +[[package]] +name = "tynm" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21cdb0fc8f85c98b1ec812bc4cd69faf6c0fa2fc17d44ea3c2cdd38dc08e999" +dependencies = [ + "nom 8.0.0", +] + +[[package]] +name = "type-map" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb30dbbd9036155e74adad6812e9898d03ec374946234fbcebd5dfc7b9187b90" dependencies = [ - "proc-macro2", - "quote", - "serde_derive_internals", - "syn 2.0.118", + "rustc-hash 2.1.2", ] [[package]] @@ -10608,6 +14690,12 @@ version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" +[[package]] +name = "typed-path" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e28f89b80c87b8fb0cf04ab448d5dd0dd0ade2f8891bae878de66a75a28600e" + [[package]] name = "typeid" version = "1.0.3" @@ -10704,6 +14792,15 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-normalization-alignments" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43f613e4fa046e69818dd287fdc4bc78175ff20331479dab6e1b0f98d57062de" +dependencies = [ + "smallvec 1.15.2", +] + [[package]] name = "unicode-properties" version = "0.1.4" @@ -10761,6 +14858,24 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "unicode_categories" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" + +[[package]] +name = "unidecode" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "402bb19d8e03f1d1a7450e2bd613980869438e0666331be3e073089124aa1adc" + +[[package]] +name = "unindent" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3" + [[package]] name = "universal-hash" version = "0.5.1" @@ -10805,20 +14920,44 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "unty" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae" + +[[package]] +name = "ureq" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b74fc6b57825be3373f7054754755f03ac3a8f5d70015ccad699ba2029956f4a" +dependencies = [ + "base64 0.22.1", + "flate2", + "log", + "once_cell", + "rustls", + "rustls-pki-types", + "serde", + "serde_json", + "url", + "webpki-roots 0.26.11", +] + [[package]] name = "ureq" version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dea7109cdcd5864d4eeb1b58a1648dc9bf520360d7af16ec26d0a9354bafcfc0" dependencies = [ - "base64", + "base64 0.22.1", "log", "percent-encoding", "rustls", "rustls-pki-types", "ureq-proto", "utf8-zero", - "webpki-roots", + "webpki-roots 1.0.8", ] [[package]] @@ -10827,7 +14966,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e994ba84b0bd1b1b0cf92878b7ef898a5c1760108fe7b6010327e274917a808c" dependencies = [ - "base64", + "base64 0.22.1", "http 1.4.2", "httparse", "log", @@ -10911,23 +15050,58 @@ dependencies = [ "atomic", "getrandom 0.4.3", "js-sys", + "rand 0.10.1", "serde_core", "sha1_smol", "wasm-bindgen", ] +[[package]] +name = "v_frame" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f32aaa24bacd11e488aa9ba66369c7cd514885742c9fe08cfe85884db3e92b" +dependencies = [ + "aligned-vec 0.5.0", + "num-traits", + "wasm-bindgen", +] + [[package]] name = "valuable" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" +[[package]] +name = "variadics_please" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41b6d82be61465f97d42bd1d15bf20f3b0a3a0905018f38f9d6f6962055b0b5c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + [[package]] name = "vsimd" version = "0.8.0" @@ -11137,6 +15311,18 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "wayland-sys" +version = "0.31.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8eab23fefc9e41f8e841df4a9c707e8a8c4ed26e944ef69297184de2785e3be" +dependencies = [ + "dlib", + "log", + "once_cell", + "pkg-config", +] + [[package]] name = "web-sys" version = "0.3.85" @@ -11166,6 +15352,15 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "webpki-roots" +version = "0.26.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +dependencies = [ + "webpki-roots 1.0.8", +] + [[package]] name = "webpki-roots" version = "1.0.8" @@ -11175,6 +15370,12 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "weezl" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" + [[package]] name = "wezterm-bidi" version = "0.2.3" @@ -11247,6 +15448,174 @@ dependencies = [ "wezterm-dynamic", ] +[[package]] +name = "wgpu" +version = "29.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76e8840e1ba2881d4cbb18d2147627a56af426ff064c0401eb0c8410c6325d07" +dependencies = [ + "arrayvec 0.7.7", + "bitflags 2.13.0", + "bytemuck", + "cfg-if", + "cfg_aliases", + "document-features", + "hashbrown 0.16.1", + "js-sys", + "log", + "naga", + "parking_lot", + "portable-atomic", + "profiling", + "raw-window-handle", + "smallvec 1.15.2", + "static_assertions", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "wgpu-core", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-core" +version = "29.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f519832254e56965a9940c4af57dcb75f702b6f6fa4a0b172f685395843a4d7" +dependencies = [ + "arrayvec 0.7.7", + "bit-set 0.9.1", + "bit-vec 0.9.1", + "bitflags 2.13.0", + "bytemuck", + "cfg_aliases", + "document-features", + "hashbrown 0.16.1", + "indexmap 2.14.0", + "log", + "naga", + "once_cell", + "parking_lot", + "portable-atomic", + "profiling", + "raw-window-handle", + "rustc-hash 1.1.0", + "smallvec 1.15.2", + "thiserror 2.0.18", + "wgpu-core-deps-apple", + "wgpu-core-deps-emscripten", + "wgpu-core-deps-windows-linux-android", + "wgpu-hal", + "wgpu-naga-bridge", + "wgpu-types", +] + +[[package]] +name = "wgpu-core-deps-apple" +version = "29.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5e39e26c4c0e07589e67d18546cf79ff45383659fc72fca4dd293358a0347f3" +dependencies = [ + "wgpu-hal", +] + +[[package]] +name = "wgpu-core-deps-emscripten" +version = "29.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01e09be551dc939498bdd5f6b2c66e55ab275dad25825267a08605a80fc9f0af" +dependencies = [ + "wgpu-hal", +] + +[[package]] +name = "wgpu-core-deps-windows-linux-android" +version = "29.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e592c1bbef6ad047647ae6e666ebd8cee7a32bb4544d9700ec96cbf73230257" +dependencies = [ + "wgpu-hal", +] + +[[package]] +name = "wgpu-hal" +version = "29.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97ace1c17727311c22a46e4e3faf56ea6de81af99dcc839bdfb54857b94d448d" +dependencies = [ + "android_system_properties", + "arrayvec 0.7.7", + "ash", + "bit-set 0.9.1", + "bitflags 2.13.0", + "block2 0.6.2", + "bytemuck", + "cfg-if", + "cfg_aliases", + "glow", + "glutin_wgl_sys", + "gpu-allocator", + "gpu-descriptor", + "hashbrown 0.16.1", + "js-sys", + "khronos-egl", + "libc", + "libloading 0.8.8", + "log", + "naga", + "ndk-sys", + "objc2 0.6.4", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "objc2-metal 0.3.2", + "objc2-quartz-core 0.3.2", + "once_cell", + "ordered-float 5.3.0", + "parking_lot", + "portable-atomic", + "portable-atomic-util", + "profiling", + "range-alloc", + "raw-window-handle", + "raw-window-metal", + "renderdoc-sys", + "smallvec 1.15.2", + "thiserror 2.0.18", + "wasm-bindgen", + "wayland-sys", + "web-sys", + "wgpu-naga-bridge", + "wgpu-types", + "windows 0.62.2", + "windows-core", + "windows-result", +] + +[[package]] +name = "wgpu-naga-bridge" +version = "29.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95226013f547544b223281cd16a4fb549aa9dcb562adbda0faae4c73ffbbc161" +dependencies = [ + "naga", + "wgpu-types", +] + +[[package]] +name = "wgpu-types" +version = "29.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84bf84cd9ca8ca45e2b223a3868f1adf9bfc0c66aeac212e76ee7e40fdadf8f5" +dependencies = [ + "bitflags 2.13.0", + "bytemuck", + "js-sys", + "log", + "raw-window-handle", + "web-sys", +] + [[package]] name = "whoami" version = "2.1.2" @@ -11664,6 +16033,18 @@ version = "0.57.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" +[[package]] +name = "wrapcenum-derive" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a76ff259533532054cfbaefb115c613203c73707017459206380f03b3b3f266e" +dependencies = [ + "darling 0.20.11", + "proc-macro2", + "quote", + "syn 2.0.118", +] + [[package]] name = "writeable" version = "0.6.3" @@ -11745,6 +16126,12 @@ version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3" +[[package]] +name = "y4m" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5a4b21e1a62b67a2970e6831bc091d7b87e119e7f9791aef9702e3bef04448" + [[package]] name = "yamux" version = "0.12.1" @@ -11797,6 +16184,16 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5f6765e852b9b4dc8e2a76843e4d64d1cea8e79bcde0b6901aea8e7c7f08282" +[[package]] +name = "yep-cache-line-size" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59dea97e5518f64cbb9468f2cd1ac59c9d0186eeb5ee6198d4bb8adfa1465499" +dependencies = [ + "libc", + "raw-cpuid", +] + [[package]] name = "yoke" version = "0.8.3" @@ -11822,18 +16219,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.52" +version = "0.8.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f" +checksum = "b5a105cd7b140f6eeec8acff2ea38135d3cab283ada58540f629fe51e46696eb" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.52" +version = "0.8.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930" +checksum = "0fe976fb70c78cd64cccfe3a6fc142244e8a77b70959b30faf9d0ac37ee228eb" dependencies = [ "proc-macro2", "quote", @@ -11914,8 +16311,94 @@ dependencies = [ "syn 2.0.118", ] +[[package]] +name = "zip" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261" +dependencies = [ + "aes", + "byteorder", + "bzip2", + "constant_time_eq", + "crc32fast", + "crossbeam-utils", + "flate2", + "hmac 0.12.1", + "pbkdf2", + "sha1 0.10.6", + "time", + "zstd", +] + +[[package]] +name = "zip" +version = "7.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c42e33efc22a0650c311c2ef19115ce232583abbe80850bc8b66509ebef02de0" +dependencies = [ + "crc32fast", + "indexmap 2.14.0", + "memchr", + "typed-path", +] + [[package]] name = "zmij" version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" + +[[package]] +name = "zstd" +version = "0.11.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "5.0.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" +dependencies = [ + "libc", + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.8+zstd.1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5556e6ee25d32df2586c098bbfa278803692a20d0ab9565e049480d52707ec8c" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + +[[package]] +name = "zune-core" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "111f7d9820f05fd715df3144e254d6fc02ee4088b0644c0ffd0efc9e6d9d2773" + +[[package]] +name = "zune-inflate" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "zune-jpeg" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d915729b0e7d5fe35c2f294c5dc10b30207cc637920e5b59077bfa3da63f28" +dependencies = [ + "zune-core", +] diff --git a/Cargo.toml b/Cargo.toml index 5b3342dbe59..e7ec4c2530d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ members = [ "libs/@local/codegen", "libs/@local/effect-dns/hickory", "libs/@local/graph/api", + "libs/@local/graph/atlas", "libs/@local/graph/authorization", "libs/@local/graph/embeddings", "libs/@local/graph/migrations", @@ -75,6 +76,7 @@ harpc-wire-protocol.path = "libs/@local/harpc/wire-protocol" hash-codec.path = "libs/@local/codec" hash-codegen.path = "libs/@local/codegen" hash-graph-api.path = "libs/@local/graph/api" +hash-graph-atlas.path = "libs/@local/graph/atlas" hash-graph-authorization.path = "libs/@local/graph/authorization" hash-graph-embeddings.path = "libs/@local/graph/embeddings" hash-graph-migrations.path = "libs/@local/graph/migrations" @@ -103,6 +105,7 @@ hashql-syntax-jexpr.path = "libs/@local/hashql/syntax-jexpr" type-system.path = "libs/@blockprotocol/type-system/rust" # External dependencies +aide = { version = "0.16.0-alpha.4" } annotate-snippets = { version = "0.12.8", default-features = false } ansi-to-html = { version = "0.2.2", default-features = false } ansi-to-tui = { version = "8.0.1" } @@ -121,11 +124,13 @@ bitvec = { version = "1.0.1", default-features = fal bon = { version = "3.8.1", default-features = false, features = ["implied-bounds"] } bstr = { version = "1.12.1" } bump-scope = { version = "2.0.0", default-features = false, features = ["alloc", "nightly-clone-to-uninit", "panic-on-alloc", "std"] } +burn = { version = "0.21.0" } bytes = { version = "1.10.1" } bytes-utils = { version = "0.1.4", default-features = false } camino = { version = "1.1.12", default-features = false } cargo_metadata = { version = "0.23.0", default-features = false } cedar-policy-core = { version = "4.5.1", default-features = false, features = ["decimal"] } # FIXME: Remove `decimal`: https://github.com/cedar-policy/cedar/issues/1700 +chacha20poly1305 = { version = "0.10.1", default-features = false, features = ["alloc"] } circular-buffer = { version = "1.1.0", default-features = false } clap = { version = "4.5.51", features = ["color", "error-context", "help", "std", "suggestions", "usage"] } clap_builder = { version = "4.5.51", default-features = false, features = ["std"] } @@ -152,6 +157,7 @@ expect-test = { version = "1.5.1", default-features = fal foldhash = { version = "0.2.0", default-features = false } frunk = { version = "0.4.4", default-features = false } frunk_core = { version = "0.4.4", default-features = false } +fst = { version = "0.4.7" } futures = { version = "0.3.31", default-features = false } futures-channel = { version = "0.3.31", default-features = false } futures-core = { version = "0.3.31", default-features = false } @@ -162,8 +168,11 @@ futures-util = { version = "0.3.31", default-features = fa glob = { version = "0.3.3" } globset = { version = "0.4.18", default-features = false } guppy = { version = "0.17.20", default-features = false } +hannoy = { version = "0.1.7-nested-rtxns" } hashbrown = { version = "0.17.0", default-features = false, features = ["inline-more", "nightly", "raw-entry"] } +heed = { version = "0.22.1" } hifijson = { version = "0.4.0", default-features = false } +hkdf = { version = "0.13.0", default-features = false } hostname = { version = "0.4.2", default-features = false } http = { version = "1.3.1", default-features = false } humansize = { version = "2.1.3", default-features = false } @@ -178,6 +187,7 @@ iso8601-duration = { version = "0.2.0", default-features = fal itertools = { version = "0.14.0", default-features = false } jsonptr = { version = "0.7.1", default-features = false } jsonwebtoken = { version = "10.3.0", default-features = false } +kiddo = { version = "6.0.0" } lcov = { version = "0.8.1" } lexical = { version = "7.0.5", default-features = false } libp2p = { version = "0.57.0", default-features = false } @@ -192,8 +202,10 @@ line-index = { version = "0.1.2", default-features = fal logos = { version = "0.16.0", default-features = false } md-5 = { version = "0.11.0", default-features = false } memchr = { version = "2.7.6", default-features = false } +memmap2 = { version = "0.9" } mimalloc = { version = "0.1.48", default-features = false } mime = { version = "0.3.17", default-features = false } +moka = { version = "0.12.15", default-features = false } multiaddr = { version = "0.18.2", default-features = false } multistream-select = { version = "0.14.0", default-features = false } napi = { version = "3.9.0", default-features = false } @@ -223,6 +235,7 @@ proptest = { version = "1.7.0", default-features = fal quote = { version = "1.0.41", default-features = false } rand = { version = "0.10.0", default-features = false } rand_core = { version = "0.10.0", default-features = false } +rand_core_06 = { version = "0.6", package = "rand_core" } rand_distr = { version = "0.6.0", default-features = false } rand_xoshiro = { version = "0.8.1" } rapidfuzz = { version = "0.5.0", default-features = false } @@ -234,11 +247,13 @@ reqwest = { version = "0.13.0", default-features = fa reqwest-middleware = { version = "0.5.0", default-features = false } reqwest-retry = { version = "0.9.1", default-features = false } reqwest-tracing = { version = "0.7.0", default-features = false } +rkyv = { version = "0.8.9" } roaring = { version = "0.11.2", default-features = false } rpds = { version = "1.1.2", default-features = false } rstest = { version = "0.26.1", default-features = false } rustc_version = { version = "0.4.1", default-features = false } scc = { version = "3.8.4", default-features = false } +schemars = { version = "1.2.1" } semver = { version = "1.0.27", default-features = false } sentry = { version = "0.48.0", default-features = false, features = ["backtrace", "contexts", "debug-images", "panic", "reqwest", "rustls", "tower-http", "tracing"] } sentry-core = { version = "0.48.0", default-features = false } @@ -250,13 +265,16 @@ serde_plain = { version = "1.0.2", default-features = fal sha2 = { version = "0.11.0", default-features = false } similar-asserts = { version = "2.0.0", default-features = false } simple-mermaid = { version = "0.2.0", default-features = false } +siphasher = { version = "1.0.3", default-features = false } smallvec = { version = "2.0.0-alpha.11", default-features = false } smol_str = { version = "0.3.4" } sort-package-json = { version = "0.0.12" } specta = { version = "2.0.0-rc.22", default-features = false } +sprs = { version = "0.11.4" } sqruff-lib = { version = "0.37.3" } sqruff-lib-core = { version = "0.37.3" } stacker = { version = "0.1.22", default-features = false } +steppe = { version = "0.4.1", default-features = false } supports-color = { version = "3.0.2", default-features = false } supports-unicode = { version = "3.0.0", default-features = false } syn = { version = "2.0.108", default-features = false } @@ -269,6 +287,7 @@ testcontainers = { version = "0.27.1", default-features = fa testcontainers-modules = { version = "0.15.0", default-features = false } text-size = { version = "1.1.1", default-features = false } thiserror = { version = "2.0.17", default-features = false } +tiktoken-rs = { version = "0.12.0" } time = { version = "0.3.44", default-features = false } tokio = { version = "1.47.1", default-features = false } tokio-postgres = { version = "0.7.15", default-features = false } @@ -296,6 +315,7 @@ unicode-ident = { version = "1.0.22", default-features = fa unicode-normalization = { version = "0.1.25", default-features = false } unicode-properties = { version = "0.1.4", default-features = false } unicode-segmentation = { version = "1.12.0", default-features = false } +unidecode = { version = "0.3.0" } unsynn = { version = "0.3.0", default-features = false, features = ["proc_macro2"] } url = { version = "2.5.7", default-features = false } utoipa = { version = "4.2.3", default-features = false } @@ -305,6 +325,8 @@ wasm-bindgen = { version = "0.2.100", default-features = f wasm-bindgen-test = { version = "0.3.50", default-features = false } winnow = { version = "1.0.0", default-features = false } xxhash-rust = { version = "0.8.15", default-features = false } +zerocopy = { version = "0.8.55" } +zeroize = { version = "1.9.0", default-features = false } [profile.dev] # TODO: Use `codegen-backend = "cranelift"` diff --git a/apps/hash-api/src/atlas-proxy.test.ts b/apps/hash-api/src/atlas-proxy.test.ts new file mode 100644 index 00000000000..5f921ac5101 --- /dev/null +++ b/apps/hash-api/src/atlas-proxy.test.ts @@ -0,0 +1,393 @@ +import { createServer } from "node:http"; + +import bodyParser from "body-parser"; +import express from "express"; +import { afterAll, beforeAll, describe, expect, it } from "vitest"; + +import { publicUserAccountId } from "@local/hash-backend-utils/public-user-account-id"; + +import { + ATLAS_ACTOR_HEADER, + ATLAS_AUTHORITY_HEADER, + isAtlasPath, + setupAtlasProxy, +} from "./atlas-proxy"; + +import type { User } from "./graph/knowledge/system-types/user"; +import type { Logger } from "@local/hash-backend-utils/logger"; +import type { RequestHandler } from "express"; +import type { Server } from "node:http"; +import type { AddressInfo } from "node:net"; + +/** + * The actor a session resolves to in the tests that have one. + * + * Any uuid distinct from the public user works; the value only has to be recognisable in the header + * the upstream receives. + */ +const SESSION_ACTOR = "11111111-1111-4111-8111-111111111111"; + +/** An actor a caller would like the atlas to answer under. */ +const SPOOFED_ACTOR = "22222222-2222-4222-8222-222222222222"; + +/** + * A stand-in authority token. + * + * An obviously arbitrary opaque string, deliberately not the atlas's width. + * + * Opaque on both sides of the hop - this suite asserts the bytes survive, never that they parse, and + * the proxy under test copies a header it cannot read. A fixture imitating the real width would only + * teach a number that goes stale; the contract is retain and present, unchanged. + */ +const MINTED_TOKEN = "minted-opaque-authority-token"; + +/** + * A filter document written in four ways a re-serialisation does not preserve. + * + * Shaped like the document the generation manifest takes as its request body, and deliberately + * noncanonical: the indentation and the space before `:` are dropped by `JSON.stringify`, `1.0` + * comes back as `1`, `\u0041` comes back as `A`, and the integer-like keys `"10"` and `"2"` come + * back in ascending numeric order rather than the order they were written in. The document's meaning + * is beside the point - the proxy cannot read it either way - so what the assertion needs is text + * whose parse is lossy in every direction the hop could round-trip it. + * + * The manifest seals this document's digest into the authority token, and the client's own filter + * state is the bytes it sent. So a hop that delivers a different spelling of the same value answers + * a document the caller never sent, and the caller re-presenting its own bytes then digests + * differently from the token it holds. + */ +const NONCANONICAL_FILTER = `{ + "all" : [ + { "equal": [ { "path": ["type", "versionedUrl"] }, { "parameter": "\\u0041pple" } ] }, + { "greater": [ { "path": ["depth"] }, { "parameter": 1.0 } ] } + ], + "weights": { "10": 1.0, "2": 3 } +}`; + +const silentLogger = { + info: () => {}, + warn: () => {}, + error: () => {}, +} as unknown as Logger; + +/** + * A session carrying only what this path reads. + * + * `getActorIdFromRequest` reads `accountId` and nothing else, so the stub states that field alone + * rather than assembling a whole graph user. + */ +const sessionUser = { accountId: SESSION_ACTOR } as unknown as User; + +/** What the upstream received per request, in request order. */ +const received: { + actor: string | string[] | undefined; + authority: string | string[] | undefined; + body: string; + path: string | undefined; +}[] = []; + +const listen = (server: Server) => + new Promise((resolve) => { + server.listen(0, () => { + resolve(server); + }); + }); + +const close = (server: Server) => + new Promise((resolve) => { + server.close(() => { + resolve(); + }); + }); + +const portOf = (server: Server) => (server.address() as AddressInfo).port; + +/** + * A session resolver standing in for `createAuthMiddleware`. + * + * It populates `req.user` on a session and leaves it unset otherwise, which is the state the real + * middleware leaves behind in each case. + */ +const resolving = + (session: User | undefined): RequestHandler => + (req, _res, next) => { + if (session) { + req.user = session; + } + next(); + }; + +/** + * Mounts the proxy in `index.ts`'s composition: session resolution first, then the JSON parser that + * skips the atlas prefix, then the route. + * + * The composition is the point of the fixture. The proxy sits past the parser, so it can only be a + * body's second reader, and the skip is what leaves the stream unread for it - which is why the + * skip decision is imported from the module under test rather than restated here. A copy of the + * predicate would keep passing while production drifted. + * + * `parseAtlasBodies` composes the app the way it was before the skip existed, so a test can hold + * the parser responsible instead of describing it. + */ +const startApi = async ( + session: User | undefined, + { parseAtlasBodies = false }: { parseAtlasBodies?: boolean } = {}, +) => { + const app = express(); + const jsonParser = bodyParser.json(); + app.use(resolving(session)); + app.use((req, res, next) => + !parseAtlasBodies && isAtlasPath(req.path) + ? next() + : jsonParser(req, res, next), + ); + setupAtlasProxy(app, silentLogger); + + const server = await listen(createServer(app)); + + return { server, url: `http://127.0.0.1:${portOf(server)}` }; +}; + +/** A stub upstream: this suite is about what crosses the hop. */ +let upstream: Server; + +beforeAll(async () => { + upstream = await listen( + createServer((req, res) => { + let body = ""; + req.on("data", (chunk: Buffer) => { + body += chunk.toString(); + }); + req.on("end", () => { + received.push({ + actor: req.headers[ATLAS_ACTOR_HEADER.toLowerCase()], + authority: req.headers[ATLAS_AUTHORITY_HEADER.toLowerCase()], + body, + path: req.url, + }); + // The manifest route is the only one that mints, so the stub answers it the way the atlas + // does - token header plus the no-store posture the per-caller token forces. Answering it + // here rather than from a second server on a swapped port keeps this suite free of shared + // mutable state: an assertion that throws mid-test cannot then strand the port for the + // tests after it. + if (req.url?.includes("/manifest")) { + res + .writeHead(200, { + [ATLAS_AUTHORITY_HEADER]: MINTED_TOKEN, + "cache-control": "private, no-store", + }) + .end(); + return; + } + res.writeHead(204).end(); + }); + }), + ); + + process.env.HASH_GRAPH_ATLAS_HOST = "127.0.0.1"; + process.env.HASH_GRAPH_ATLAS_PORT = String(portOf(upstream)); +}); + +afterAll(async () => { + await close(upstream); +}); + +describe("the atlas proxy's actor header", () => { + it("states the session's actor", async () => { + const api = await startApi(sessionUser); + received.length = 0; + + const response = await fetch(`${api.url}/atlas/current`); + + expect(response.status).toBe(204); + expect(received.map(({ actor }) => actor)).toEqual([SESSION_ACTOR]); + + await close(api.server); + }); + + it("overwrites an actor the caller supplied", async () => { + const api = await startApi(sessionUser); + received.length = 0; + + const response = await fetch(`${api.url}/atlas/current`, { + headers: { [ATLAS_ACTOR_HEADER]: SPOOFED_ACTOR }, + }); + + expect(response.status).toBe(204); + expect(received.map(({ actor }) => actor)).toEqual([SESSION_ACTOR]); + + await close(api.server); + }); + + it("states the public user with no session, caller header included", async () => { + const api = await startApi(undefined); + received.length = 0; + + const response = await fetch(`${api.url}/atlas/current`, { + headers: { [ATLAS_ACTOR_HEADER]: SPOOFED_ACTOR }, + }); + + expect(response.status).toBe(204); + expect(received.map(({ actor }) => actor)).toEqual([publicUserAccountId]); + + await close(api.server); + }); +}); + +describe("a request body", () => { + it("reaches the atlas", async () => { + const api = await startApi(sessionUser); + received.length = 0; + + const tiles = { tiles: [{ z: 3, x: 1, y: 2 }] }; + const response = await fetch(`${api.url}/atlas/edges/g/plain`, { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify(tiles), + }); + + expect(response.status).toBe(204); + expect(received).toHaveLength(1); + expect(JSON.parse(received[0]!.body)).toEqual(tiles); + expect(received[0]!.actor).toBe(SESSION_ACTOR); + + await close(api.server); + }); + + it("arrives byte for byte, however it was spelled", async () => { + const api = await startApi(sessionUser); + received.length = 0; + + const response = await fetch(`${api.url}/atlas/generation/g/manifest`, { + method: "POST", + headers: { "content-type": "application/json" }, + body: NONCANONICAL_FILTER, + }); + + expect(response.status).toBe(200); + expect(received).toHaveLength(1); + expect(received[0]!.body).toBe(NONCANONICAL_FILTER); + + await close(api.server); + }); + + it("loses its spelling once the parser has read it, which is what the skip is for", async () => { + // The transparency in the test above belongs to the parser skip, not to the proxy: a body the + // parser consumed can only be re-serialised from `req.body`, and `JSON.stringify` renders one + // canonical spelling of a value with no memory of the text it came from. Composing the app the + // pre-skip way states that here, so removing the skip from `index.ts` cannot leave a suite that + // still claims the bytes cross unmodified. + const api = await startApi(sessionUser, { parseAtlasBodies: true }); + received.length = 0; + + const response = await fetch(`${api.url}/atlas/generation/g/manifest`, { + method: "POST", + headers: { "content-type": "application/json" }, + body: NONCANONICAL_FILTER, + }); + + expect(response.status).toBe(200); + expect(received).toHaveLength(1); + expect(received[0]!.body).not.toBe(NONCANONICAL_FILTER); + expect(received[0]!.body).toBe( + JSON.stringify(JSON.parse(NONCANONICAL_FILTER)), + ); + + await close(api.server); + }); +}); + +describe("the authority token's path back to the browser", () => { + // Without the expose header the whole token round trip no-ops silently: a cross-origin response + // hands script only the CORS-safelisted headers, so a contract-perfect client reads `null` for + // the minted token, sends nothing back, and takes a uniform 401 on every data route - a refusal + // that reads as authority working. `CORS_CONFIG` states no `exposedHeaders`, and the `cors` + // package emits the header not at all when the option is unset. + it("exposes the authority header so the caller's own script can read it", async () => { + const api = await startApi(sessionUser); + received.length = 0; + + const response = await fetch(`${api.url}/atlas/current`); + + expect(response.headers.get("access-control-expose-headers")).toBe( + ATLAS_AUTHORITY_HEADER, + ); + + await close(api.server); + }); + + it("carries a minted token back across the hop verbatim, on a bodyless manifest POST", async () => { + // The expose header is only half of it: the value itself has to survive the hop. + // + // The manifest is a `POST` route whose body is optional, and the client's mint and renewal both + // send none - so this is the exact request shape that crosses the hop. It states no content type + // either, because a request with no document to describe has no type to state, and stating one + // would cost the caller a preflight for nothing. + const api = await startApi(sessionUser); + received.length = 0; + + const response = await fetch(`${api.url}/atlas/generation/g/manifest`, { + method: "POST", + }); + + expect(received).toHaveLength(1); + expect(received[0]!.body).toBe(""); + expect(response.headers.get(ATLAS_AUTHORITY_HEADER)).toBe(MINTED_TOKEN); + expect(response.headers.get("cache-control")).toBe("private, no-store"); + expect(response.headers.get("access-control-expose-headers")).toBe( + ATLAS_AUTHORITY_HEADER, + ); + + await close(api.server); + }); + + it("passes a presented token through to the atlas unchanged", async () => { + // The reverse direction needs no proxy change - `allowedHeaders` unset makes `cors` reflect the + // requested headers - but the value must still cross the hop, and `proxyReq` replaces only the + // actor header. + const api = await startApi(sessionUser); + received.length = 0; + + const response = await fetch(`${api.url}/atlas/tile/g/plain/3/5/1`, { + method: "POST", + headers: { + "content-type": "application/json", + [ATLAS_AUTHORITY_HEADER]: MINTED_TOKEN, + }, + body: JSON.stringify({ coloredTypeIds: [] }), + }); + + expect(response.status).toBe(204); + expect(received).toHaveLength(1); + expect(received[0]!.authority).toBe(MINTED_TOKEN); + + await close(api.server); + }); +}); + +describe("the mount path", () => { + it("claims the mount and its descendants, and stops at the boundary", () => { + // The parser skip is decided by this predicate for paths the mount never sees, so the boundary is + // its own claim: a neighbouring route starting with the same letters keeps its parsed body. + expect(isAtlasPath("/atlas")).toBe(true); + expect(isAtlasPath("/atlas/generation/g/manifest")).toBe(true); + expect(isAtlasPath("/atlas-two/tile/g/plain/3/5/1")).toBe(false); + expect(isAtlasPath("/graphql")).toBe(false); + }); + + it("names the atlas's own version prefix", async () => { + const api = await startApi(sessionUser); + received.length = 0; + + const response = await fetch( + `${api.url}/atlas/tile/g/plain/3/5/1?coloredTypeIds=x`, + ); + + expect(response.status).toBe(204); + expect(received.map(({ path }) => path)).toEqual([ + "/v1/atlas/tile/g/plain/3/5/1?coloredTypeIds=x", + ]); + + await close(api.server); + }); +}); diff --git a/apps/hash-api/src/atlas-proxy.ts b/apps/hash-api/src/atlas-proxy.ts new file mode 100644 index 00000000000..312094f0f3f --- /dev/null +++ b/apps/hash-api/src/atlas-proxy.ts @@ -0,0 +1,169 @@ +import { createProxyMiddleware, fixRequestBody } from "http-proxy-middleware"; + +import { getRequiredEnv } from "@local/hash-backend-utils/environment"; + +import { getActorIdFromRequest } from "./auth/get-actor-id"; + +import type { Logger } from "@local/hash-backend-utils/logger"; +import type { Express, Request, Response } from "express"; + +/** + * The header naming the actor the atlas answers under. + * + * The atlas reads it as an authenticated statement of identity, so this API states it from its own + * session resolution on every proxied request. + */ +export const ATLAS_ACTOR_HEADER = "X-Authenticated-User-Actor-Id"; + +/** + * The header carrying the atlas's per-caller authority token. + * + * The generation manifest mints one and returns it here; the four data routes require it back in + * the same header. The frontend is a different origin from this API, so a cross-origin response + * exposes only the CORS-safelisted headers to script unless this one is named in + * `Access-Control-Expose-Headers` - see {@link setupAtlasProxy}. + */ +export const ATLAS_AUTHORITY_HEADER = "Atlas-Authority"; + +/** + * The path this API mounts the atlas surface at. + */ +export const ATLAS_MOUNT_PATH = "/atlas"; + +/** + * Whether `path` addresses the atlas mount, and so must reach it with its body unread. + * + * Two places decide by this: the body parsing middleware skips these paths, and + * {@link setupAtlasProxy} mounts on them - see there for what a parsed body costs this surface. One + * exported predicate rather than a prefix written out twice, because the two only work as a pair: + * a mount whose parser skip has drifted still answers, and answers a re-serialised document. + */ +export const isAtlasPath = (path: string) => + path === ATLAS_MOUNT_PATH || path.startsWith(`${ATLAS_MOUNT_PATH}/`); + +/** + * Resolves the atlas listener's address from the environment. + * + * `hash-graph atlas` owns the defaults for these two variables as its own clap defaults, so this + * side reads them as required rather than restating them: one owner, and a misconfiguration is loud + * here instead of silently proxying to an address nobody is listening on. The read happens per + * call, which places the address in the environment the server starts in. + * + * @throws if either variable is unset. + */ +const atlasTarget = () => { + const host = getRequiredEnv("HASH_GRAPH_ATLAS_HOST"); + const port = Number.parseInt(getRequiredEnv("HASH_GRAPH_ATLAS_PORT"), 10); + + return `http://${host}:${port}`; +}; + +/** + * Mounts the atlas REST surface at `/atlas` on `app`, under the actor each session resolves to. + * + * Every request reaches the atlas carrying {@link ATLAS_ACTOR_HEADER}, set from + * `getActorIdFromRequest` and replacing any value the caller sent: the atlas answers under the + * actor this API resolved, and a session-less request answers under the public user. This is the + * identity handling every other route here uses, and the header is what the graph client states + * when this API calls the graph. + * + * Mount it past `authMiddleware`, because the header derivation reads `req.user`. + * + * Mount it past a body parser that skips {@link ATLAS_MOUNT_PATH}. A parsed body reaches the + * upstream only by being re-serialised from `req.body`, and re-serialising changes JSON text: + * whitespace goes, `1.0` becomes `1`, `\u0041` becomes `A`, a duplicate key is dropped, and + * integer-like keys come back in ascending order. The atlas digests the body it is handed - the + * generation manifest seals the digest of the filter document into the authority token, and the + * client retains and re-presents the exact bytes it sent - so a hop that re-serialises answers a + * different document than the caller stated, and the caller's own re-presentation of the same + * bytes then digests differently. Skipping the prefix leaves `req.body` unset, so + * `fixRequestBody` writes nothing and the unread stream is piped through as it arrived. + * + * Payloads therefore cross the hop byte for byte; the mount's one addition is to the CORS envelope - + * `Atlas-Authority` is exposed so the caller's own script can read the token it was just handed. + * The saltile media types, the `Cache-Control: private, no-store` posture, and RFC 9457 + * `application/problem+json` bodies all arrive verbatim. Request caps are the atlas's own and its + * generation manifest publishes them. Rate limiting belongs to whatever fronts this API; the route + * applies none. + * + * Without the expose line the token never reaches the caller's script, and nothing reports it: the frontend reaches this + * mount cross-origin (`apiOrigin` is a different origin from `frontendUrl` by default and by + * deployment shape), and `CORS_CONFIG` states no `exposedHeaders`, so the `cors` package emits no + * `Access-Control-Expose-Headers` at all. A client implementing the token round trip exactly to + * contract would then read `null` for the minted token, send nothing back, and take a uniform `401` + * on every data route - a refusal that looks like authority working correctly. Stating it here + * rather than on `CORS_CONFIG` keeps the list where the header is known, and leaves the envelope of + * every other route on this API alone. + */ +export const setupAtlasProxy = (app: Express, logger: Logger) => { + const target = atlasTarget(); + + app.use( + ATLAS_MOUNT_PATH, + // Stated before the hop rather than in `proxyRes`: the value does not depend on the upstream + // response, and a header set here survives the proxy's own header copying. + (_req, res, next) => { + res.setHeader("Access-Control-Expose-Headers", ATLAS_AUTHORITY_HEADER); + next(); + }, + createProxyMiddleware({ + target, + /** + * `/atlas/` names the atlas's `/v1/atlas/`: express strips the mount, and the + * atlas's own version prefix is restored in its place. + */ + pathRewrite: (path) => `/v1/atlas${path}`, + logger: { + /** This API's request logging already covers these requests. */ + info: () => {}, + warn: (...args: unknown[]) => { + logger.warn(`[atlas-proxy] ${args.map(String).join(" ")}`); + }, + error: (...args: unknown[]) => { + logger.error(`[atlas-proxy] ${args.map(String).join(" ")}`); + }, + }, + on: { + /** + * States the request's actor. + * + * `setHeader` replaces any value the caller sent, so the actor the atlas reads is the one + * this API resolved. + * + * `fixRequestBody` stays as the fallback for a body that was parsed anyway: it returns + * without writing when `req.body` is unset, which is the state this mount is composed to be + * in, and re-streams a parsed body rather than hanging the request if some other middleware + * ever consumes the stream. It cannot restore the original bytes, so its running is a + * degradation to notice, not the design. + */ + proxyReq: (proxyReq, req) => { + proxyReq.setHeader(ATLAS_ACTOR_HEADER, getActorIdFromRequest(req)); + fixRequestBody(proxyReq, req); + }, + /** + * Answers an unreachable atlas in RFC 9457 shape. + * + * The atlas renders its own failures the same way, so one error vocabulary covers the + * whole surface. + */ + error: (error, _req, res) => { + logger.error(`[atlas-proxy] upstream error: ${error.message}`); + if ("headersSent" in res && !res.headersSent && "status" in res) { + res + .status(502) + .type("application/problem+json") + .send( + JSON.stringify({ + type: "https://hash.ai/problems/atlas-unreachable", + title: "Atlas server unreachable", + status: 502, + }), + ); + } + }, + }, + }), + ); + + logger.info(`Atlas proxy: ${ATLAS_MOUNT_PATH} -> ${target}/v1/atlas`); +}; diff --git a/apps/hash-api/src/graph/knowledge/primitive/cluster-entities.ts b/apps/hash-api/src/graph/knowledge/primitive/cluster-entities.ts new file mode 100644 index 00000000000..c7587e22bf1 --- /dev/null +++ b/apps/hash-api/src/graph/knowledge/primitive/cluster-entities.ts @@ -0,0 +1,33 @@ +import { getActorIdFromRequest } from "../../../auth/get-actor-id"; + +import type { ClusterEntitiesParams } from "@local/hash-graph-client"; +import type { RequestHandler } from "express"; + +export const clusterEntitiesHandler: RequestHandler< + Record, + | { + clusters: { + clusterId: number; + entityIds: string[]; + centroid: number[]; + }[]; + missingEmbeddings: string[]; + } + | { error: string }, + ClusterEntitiesParams +> = async (req, res) => { + const actorId = getActorIdFromRequest(req); + + try { + const { data } = await req.context.graphApi.clusterEntities( + actorId, + req.body, + ); + + res.status(200).json(data); + } catch (error) { + res.status(500).json({ + error: error instanceof Error ? error.message : "Unknown error", + }); + } +}; diff --git a/apps/hash-api/src/index.ts b/apps/hash-api/src/index.ts index e57de784e26..90789a5f51a 100644 --- a/apps/hash-api/src/index.ts +++ b/apps/hash-api/src/index.ts @@ -48,6 +48,7 @@ import { gptQueryTypes } from "./ai/gpt/gpt-query-types"; import { upsertGptOauthClient } from "./ai/gpt/upsert-gpt-oauth-client"; import { openInferEntitiesWebSocket } from "./ai/infer-entities-websocket"; import { setupAnalysisHandler } from "./analysis/setup-analysis-handler"; +import { isAtlasPath, setupAtlasProxy } from "./atlas-proxy"; import { addKratosAfterRegistrationHandler, createAuthMiddleware, @@ -63,6 +64,7 @@ import { kratosPublicUrl } from "./auth/ory-kratos"; import { setupBlockProtocolExternalServiceMethodProxy } from "./block-protocol-external-service-method-proxy"; import { createEmailTransporter } from "./email/create-email-transporter"; import { ensureSystemGraphIsInitialized } from "./graph/ensure-system-graph-is-initialized"; +import { clusterEntitiesHandler } from "./graph/knowledge/primitive/cluster-entities"; import { ensureHashSystemAccountExists } from "./graph/system-account"; import { createApolloServer } from "./graphql/create-apollo-server"; import { otelSetup } from "./instrument.mjs"; @@ -606,6 +608,15 @@ const main = async () => { // webhooks typically need the raw body for signature verification return rawParser(req, res, next); } + if (isAtlasPath(req.path)) { + // The atlas proxy has to come after `authMiddleware`, which is where the actor it states as a + // header comes from, so it cannot be moved above this parser the way the proxies above it + // are. Skipping the prefix is the same exemption by another means: the atlas digests the + // request body it is handed, a parse-then-re-serialise round trip does not preserve JSON + // text, and the digest of the filter document is sealed into an authority token the client + // re-presents. See `setupAtlasProxy`. + return next(); + } return jsonParser(req, res, next); }); @@ -851,6 +862,13 @@ const main = async () => { next(); }); + // Entity clustering + app.post("/entities/embeddings/clusters", clusterEntitiesHandler); + + // The atlas REST surface, proxied to the `hash-graph atlas` server under this API's own session + // resolution: the actor reaches the atlas as a header this API states, never as caller input. + setupAtlasProxy(app, logger); + // Integrations app.get("/oauth/linear", authRouteRateLimiter, oAuthLinear); app.get("/oauth/linear/callback", authRouteRateLimiter, oAuthLinearCallback); diff --git a/apps/hash-frontend/panda.config.ts b/apps/hash-frontend/panda.config.ts index 549b9a7c5df..c55deb8f466 100644 --- a/apps/hash-frontend/panda.config.ts +++ b/apps/hash-frontend/panda.config.ts @@ -22,12 +22,14 @@ export default defineConfig({ ...scopedThemeConfig(".hash-ds-root"), /** - * Styles used inside ds-components itself, plus the supply-chain tool - * and its route pages, which author Panda `css()` calls against the ds-components preset tokens. + * Styles used inside ds-components itself, plus this app's own sources that + * author Panda `css()` calls against the ds-components preset tokens: the + * supply-chain tool and its route pages, and the tiled network graph. */ include: [ require.resolve("@hashintel/ds-components/panda.buildinfo.json"), "./src/pages/supply-chain/**/*.{ts,tsx}", + "./src/components/tiled-network-graph/**/*.{ts,tsx}", ], exclude: [], diff --git a/apps/hash-frontend/src/components/tiled-network-graph/atlas-decode/cbor.test.ts b/apps/hash-frontend/src/components/tiled-network-graph/atlas-decode/cbor.test.ts new file mode 100644 index 00000000000..3cb93d751e5 --- /dev/null +++ b/apps/hash-frontend/src/components/tiled-network-graph/atlas-decode/cbor.test.ts @@ -0,0 +1,138 @@ +import { describe, expect, it } from "vitest"; + +import { decodeCbor, SaltileCborError } from "./cbor"; + +const bytes = (...values: number[]): Uint8Array => new Uint8Array(values); + +const failure = (input: Uint8Array): string => { + try { + decodeCbor(input); + } catch (error) { + expect(error).toBeInstanceOf(SaltileCborError); + return (error as SaltileCborError).message; + } + throw new Error("expected the payload to be rejected"); +}; + +describe("decodeCbor", () => { + it("decodes unsigned integers at every width", () => { + expect(decodeCbor(bytes(0x00))).toBe(0); + expect(decodeCbor(bytes(0x17))).toBe(23); + expect(decodeCbor(bytes(0x18, 0x18))).toBe(24); + expect(decodeCbor(bytes(0x18, 0xff))).toBe(255); + expect(decodeCbor(bytes(0x19, 0x01, 0x00))).toBe(256); + expect(decodeCbor(bytes(0x1a, 0x00, 0x01, 0x00, 0x00))).toBe(65536); + expect(decodeCbor(bytes(0x1b, 0, 0, 0, 1, 0, 0, 0, 0))).toBe(4_294_967_296); + }); + + it("decodes negative integers", () => { + expect(decodeCbor(bytes(0x20))).toBe(-1); + expect(decodeCbor(bytes(0x38, 0x63))).toBe(-100); + }); + + it("rejects integers that are not shortest form", () => { + expect(failure(bytes(0x18, 0x17))).toMatch(/shortest-form/u); + expect(failure(bytes(0x19, 0x00, 0xff))).toMatch(/shortest-form/u); + expect(failure(bytes(0x1a, 0, 0, 0xff, 0xff))).toMatch(/shortest-form/u); + expect(failure(bytes(0x1b, 0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff))).toMatch( + /shortest-form/u, + ); + }); + + it("rejects integers beyond the safe JavaScript range", () => { + expect( + failure(bytes(0x1b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff)), + ).toMatch(/safe JavaScript range/u); + }); + + it("returns byte strings as views over the input buffer", () => { + const input = bytes(0x44, 0xde, 0xad, 0xbe, 0xef); + const value = decodeCbor(input) as Uint8Array; + expect([...value]).toEqual([0xde, 0xad, 0xbe, 0xef]); + expect(value.buffer).toBe(input.buffer); + }); + + it("decodes text strings and rejects invalid UTF-8", () => { + expect(decodeCbor(bytes(0x63, 0xe2, 0x82, 0xac))).toBe("\u20ac"); + expect(failure(bytes(0x62, 0xc3, 0x28))).toMatch(/not valid UTF-8/u); + }); + + it("decodes arrays, maps, booleans, null, and f32", () => { + // {0: [1, null], 1: true, 2: 1.5f32} + const value = decodeCbor( + bytes( + 0xa3, + 0x00, + 0x82, + 0x01, + 0xf6, + 0x01, + 0xf5, + 0x02, + 0xfa, + 0x3f, + 0xc0, + 0x00, + 0x00, + ), + ) as ReadonlyMap; + expect(value.get(0)).toEqual([1, null]); + expect(value.get(1)).toBe(true); + expect(value.get(2)).toBe(1.5); + }); + + it("decodes f64 doubles (locate property values)", () => { + // 0.1 has no exact f32 form, so a single-precision read would drift. + expect( + decodeCbor(bytes(0xfb, 0x3f, 0xb9, 0x99, 0x99, 0x99, 0x99, 0x99, 0x9a)), + ).toBe(0.1); + expect(decodeCbor(bytes(0xfb, 0xc0, 0x04, 0, 0, 0, 0, 0, 0))).toBe(-2.5); + }); + + it("rejects map keys that are unsorted, duplicated, or not uints", () => { + expect(failure(bytes(0xa2, 0x01, 0x00, 0x00, 0x00))).toMatch( + /ascending order/u, + ); + expect(failure(bytes(0xa2, 0x01, 0x00, 0x01, 0x00))).toMatch( + /occurs twice/u, + ); + expect(failure(bytes(0xa1, 0x61, 0x61, 0x00))).toMatch( + /unsigned integers/u, + ); + expect(failure(bytes(0xa1, 0x20, 0x00))).toMatch(/unsigned integers/u); + }); + + it("rejects everything outside the profile by name", () => { + expect(failure(bytes(0xc0, 0x00))).toMatch(/tags/u); + expect(failure(bytes(0x5f))).toMatch(/indefinite/u); + expect(failure(bytes(0x9f))).toMatch(/indefinite/u); + expect(failure(bytes(0xbf))).toMatch(/indefinite/u); + expect(failure(bytes(0xf9, 0x3c, 0x00))).toMatch(/half-precision/u); + expect(failure(bytes(0xf7))).toMatch(/simple value 23/u); + expect(failure(bytes(0x1c))).toMatch(/reserved additional info/u); + }); + + it("rejects truncation and trailing bytes", () => { + expect(failure(bytes())).toMatch(/where a value is required/u); + expect(failure(bytes(0x19, 0x01))).toMatch(/inside an integer argument/u); + expect(failure(bytes(0x44, 0x01))).toMatch(/inside a byte string/u); + expect(failure(bytes(0x82, 0x00))).toMatch(/where a value is required/u); + expect(failure(bytes(0xfa, 0x3f))).toMatch(/inside a float/u); + expect(failure(bytes(0x00, 0x00))).toMatch(/1 trailing bytes/u); + }); + + it("rejects nesting beyond the depth cap", () => { + const deep = bytes(...Array.from({ length: 18 }, () => 0x81), 0x00); + expect(failure(deep)).toMatch(/nesting exceeds depth 16/u); + }); + + it("carries the violating byte offset on every error", () => { + try { + decodeCbor(bytes(0x82, 0x00, 0x18, 0x00)); + } catch (error) { + expect((error as SaltileCborError).offset).toBe(2); + return; + } + throw new Error("unreachable"); + }); +}); diff --git a/apps/hash-frontend/src/components/tiled-network-graph/atlas-decode/cbor.ts b/apps/hash-frontend/src/components/tiled-network-graph/atlas-decode/cbor.ts new file mode 100644 index 00000000000..25fdbe88660 --- /dev/null +++ b/apps/hash-frontend/src/components/tiled-network-graph/atlas-decode/cbor.ts @@ -0,0 +1,259 @@ +/** + * Reader for the SALTILE deterministic CBOR subset (normative profile: + * `SPEC-ADDENDUM-WIRE.md` section 4). The profile restricts RFC 8949 + * deterministic encoding further: definite lengths only, unsigned + * integer map keys in strictly ascending order, no tags, and floats + * IEEE 754 single precision (geometry and HEAD scalars) or double + * precision (locate trailer property values, WIRE 6b) — half precision + * is rejected. + * + * Byte strings decode to views over the input buffer, never copies. + * Text strings decode through one fatal TextDecoder pass, so invalid + * UTF-8 is a named error rather than replacement characters. + */ + +/** A decoded value of the subset. */ +export type CborValue = + | number + | boolean + | null + | string + | Uint8Array + | readonly CborValue[] + | ReadonlyMap; + +/** A payload violated the SALTILE CBOR profile. */ +export class SaltileCborError extends Error { + override readonly name = "SaltileCborError"; + /** Byte offset within the decoded payload at which the check applies. */ + readonly offset: number; + + constructor(detail: string, offset: number) { + super(`${detail} (at byte ${offset})`); + this.offset = offset; + } +} + +/** + * Nesting bound; the deepest wire schema is three levels, so the cap + * exists to make hostile input a named error instead of stack + * exhaustion. + */ +const MAXIMUM_DEPTH = 16; + +const utf8 = new TextDecoder("utf-8", { fatal: true }); + +/** Shortest-form minima per additional-info width. */ +const shortestMinimum: Readonly> = { + 24: 24, + 25: 0x100, + 26: 0x1_0000, +}; + +class Reader { + readonly #bytes: Uint8Array; + readonly #view: DataView; + #offset = 0; + + constructor(bytes: Uint8Array) { + this.#bytes = bytes; + this.#view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength); + } + + #fail(detail: string, offset: number): never { + throw new SaltileCborError(detail, offset); + } + + /** Reads a definite-length argument, enforcing shortest form. */ + #readArgument(info: number): number { + const headOffset = this.#offset - 1; + if (info < 24) { + return info; + } + if (info > 27) { + return this.#fail( + info === 31 + ? "indefinite lengths are outside the profile" + : `reserved additional info ${info}`, + headOffset, + ); + } + + const width = 2 ** (info - 24); + if (this.#offset + width > this.#bytes.length) { + return this.#fail( + "payload ends inside an integer argument", + this.#offset, + ); + } + + let value: number; + if (info === 27) { + const wide = this.#view.getBigUint64(this.#offset, false); + if (wide > BigInt(Number.MAX_SAFE_INTEGER)) { + return this.#fail( + "integer exceeds the safe JavaScript range", + this.#offset, + ); + } + value = Number(wide); + if (value <= 0xffff_ffff) { + return this.#fail("integer is not shortest-form encoded", headOffset); + } + } else { + value = + info === 24 + ? this.#view.getUint8(this.#offset) + : info === 25 + ? this.#view.getUint16(this.#offset, false) + : this.#view.getUint32(this.#offset, false); + if (value < shortestMinimum[info]!) { + return this.#fail("integer is not shortest-form encoded", headOffset); + } + } + this.#offset += width; + return value; + } + + readValue(depth: number): CborValue { + if (depth > MAXIMUM_DEPTH) { + return this.#fail(`nesting exceeds depth ${MAXIMUM_DEPTH}`, this.#offset); + } + if (this.#offset >= this.#bytes.length) { + return this.#fail("payload ends where a value is required", this.#offset); + } + + const headOffset = this.#offset; + const head = this.#bytes[this.#offset]!; + this.#offset += 1; + const major = Math.floor(head / 32); + const info = head % 32; + + switch (major) { + case 0: { + return this.#readArgument(info); + } + case 1: { + return -1 - this.#readArgument(info); + } + case 2: { + const length = this.#readArgument(info); + if (this.#offset + length > this.#bytes.length) { + return this.#fail("payload ends inside a byte string", this.#offset); + } + const start = this.#offset; + this.#offset += length; + return this.#bytes.subarray(start, start + length); + } + case 3: { + const length = this.#readArgument(info); + if (this.#offset + length > this.#bytes.length) { + return this.#fail("payload ends inside a text string", this.#offset); + } + const start = this.#offset; + this.#offset += length; + try { + return utf8.decode(this.#bytes.subarray(start, start + length)); + } catch { + return this.#fail("text string is not valid UTF-8", start); + } + } + case 4: { + const length = this.#readArgument(info); + const entries: CborValue[] = []; + for (let index = 0; index < length; index += 1) { + entries.push(this.readValue(depth + 1)); + } + return entries; + } + case 5: { + const length = this.#readArgument(info); + const entries = new Map(); + let previousKey = -1; + for (let index = 0; index < length; index += 1) { + const keyOffset = this.#offset; + const key = this.readValue(depth + 1); + if (typeof key !== "number" || !Number.isInteger(key) || key < 0) { + return this.#fail("map keys must be unsigned integers", keyOffset); + } + if (key <= previousKey) { + return this.#fail( + key === previousKey + ? `map key ${key} occurs twice` + : "map keys are not in ascending order", + keyOffset, + ); + } + previousKey = key; + entries.set(key, this.readValue(depth + 1)); + } + return entries; + } + case 6: { + return this.#fail("tags are outside the profile", headOffset); + } + default: { + switch (info) { + case 20: { + return false; + } + case 21: { + return true; + } + case 22: { + return null; + } + case 26: { + if (this.#offset + 4 > this.#bytes.length) { + return this.#fail("payload ends inside a float", this.#offset); + } + const value = this.#view.getFloat32(this.#offset, false); + this.#offset += 4; + return value; + } + case 27: { + if (this.#offset + 8 > this.#bytes.length) { + return this.#fail("payload ends inside a float", this.#offset); + } + const value = this.#view.getFloat64(this.#offset, false); + this.#offset += 8; + return value; + } + case 25: { + return this.#fail( + "half-precision floats are outside the profile", + headOffset, + ); + } + default: { + return this.#fail( + `simple value ${info} is outside the profile`, + headOffset, + ); + } + } + } + } + } + + finish(value: CborValue): CborValue { + if (this.#offset !== this.#bytes.length) { + return this.#fail( + `payload carries ${this.#bytes.length - this.#offset} trailing bytes`, + this.#offset, + ); + } + return value; + } +} + +/** + * Decodes exactly one value spanning the whole payload. + * + * @throws {@link SaltileCborError} on any profile violation, including + * trailing bytes after the value. + */ +export const decodeCbor = (bytes: Uint8Array): CborValue => { + const reader = new Reader(bytes); + return reader.finish(reader.readValue(0)); +}; diff --git a/apps/hash-frontend/src/components/tiled-network-graph/atlas-decode/edges.test.ts b/apps/hash-frontend/src/components/tiled-network-graph/atlas-decode/edges.test.ts new file mode 100644 index 00000000000..311743e4f7a --- /dev/null +++ b/apps/hash-frontend/src/components/tiled-network-graph/atlas-decode/edges.test.ts @@ -0,0 +1,373 @@ +import { describe, expect, it } from "vitest"; + +import { decodeSaltileEdges, type SaltileEdgesRequest } from "./edges"; +import { + DIRECTORY_ENTRY_BYTES, + PAYLOAD_ALIGNMENT, + PREFIX_BYTES, + SaltileWireError, +} from "./wire"; + +/* Reference encoder helpers; CBOR is big-endian, envelope and columns + * little-endian, as in the other suites. */ + +const cborUint = (value: number, major = 0): number[] => { + const base = major * 32; + if (value < 24) { + return [base + value]; + } + if (value <= 0xff) { + return [base + 24, value]; + } + const wide = new DataView(new ArrayBuffer(2)); + wide.setUint16(0, value, false); + return [base + 25, ...new Uint8Array(wide.buffer)]; +}; + +const cborBstr = (bytes: number[]): number[] => [ + ...cborUint(bytes.length, 2), + ...bytes, +]; + +const cborTstr = (text: string): number[] => { + const encoded = [...new TextEncoder().encode(text)]; + return [...cborUint(encoded.length, 3), ...encoded]; +}; + +const cborArray = (entries: number[][]): number[] => [ + ...cborUint(entries.length, 4), + ...entries.flat(), +]; + +const cborMap = (entries: [number, number[]][]): number[] => [ + ...cborUint(entries.length, 5), + ...entries.flatMap(([key, value]) => [...cborUint(key), ...value]), +]; + +const cborNull = (): number[] => [0xf6]; +const cborBool = (value: boolean): number[] => [value ? 0xf5 : 0xf4]; + +const u32le = (values: number[]): number[] => { + const view = new DataView(new ArrayBuffer(values.length * 4)); + for (const [index, value] of values.entries()) { + view.setUint32(index * 4, value, true); + } + return [...new Uint8Array(view.buffer)]; +}; + +const response = ( + payloads: (number[] | null)[], + tail: number[] = [], +): ArrayBuffer => { + const base = PREFIX_BYTES + payloads.length * DIRECTORY_ENTRY_BYTES; + const directory = new DataView( + new ArrayBuffer(payloads.length * DIRECTORY_ENTRY_BYTES), + ); + const body: number[] = []; + let cursor = base; + for (const [slot, payload] of payloads.entries()) { + if (payload === null) { + continue; + } + directory.setUint32(slot * DIRECTORY_ENTRY_BYTES, cursor, true); + directory.setUint32( + slot * DIRECTORY_ENTRY_BYTES + 4, + cursor + payload.length, + true, + ); + body.push(...payload); + cursor += payload.length; + while (cursor % PAYLOAD_ALIGNMENT !== 0) { + body.push(0); + cursor += 1; + } + } + const prefix = new DataView(new ArrayBuffer(PREFIX_BYTES)); + for (const [index, byte] of [ + 0x53, + 0x41, + 0x4c, + 0x54, + 0x49, + 0x4c, + 0x45, + 0x45, // SALTILEE + ].entries()) { + prefix.setUint8(index, byte); + } + prefix.setUint16(8, 1, true); + prefix.setUint16(12, payloads.length, true); + return new Uint8Array([ + ...new Uint8Array(prefix.buffer), + ...new Uint8Array(directory.buffer), + ...body, + ...tail, + ]).buffer; +}; + +const generation = Array.from({ length: 32 }, (_, index) => index); +const sources = [7, 11, 13]; +const targets = [11, 42, 7]; + +/** Three 32-byte identity records in ascending byte order. */ +const edgeIdRecords = [ + [ + ...Array.from({ length: 16 }, () => 0xa0), + ...Array.from({ length: 16 }, () => 0xa1), + ], + [ + ...Array.from({ length: 16 }, () => 0xb0), + ...Array.from({ length: 16 }, () => 0xb1), + ], + [ + ...Array.from({ length: 16 }, () => 0xc0), + ...Array.from({ length: 16 }, () => 0xc1), + ], +]; +const edgeIdColumn = edgeIdRecords.flat(); +const edgeIdStrings = [ + "a0a0a0a0-a0a0-a0a0-a0a0-a0a0a0a0a0a0~a1a1a1a1-a1a1-a1a1-a1a1-a1a1a1a1a1a1", + "b0b0b0b0-b0b0-b0b0-b0b0-b0b0b0b0b0b0~b1b1b1b1-b1b1-b1b1-b1b1-b1b1b1b1b1b1", + "c0c0c0c0-c0c0-c0c0-c0c0-c0c0c0c0c0c0~c1c1c1c1-c1c1-c1c1-c1c1-c1c1c1c1c1c1", +]; + +const defaultHead = ( + overrides: Partial> = {}, +): [number, number[]][] => { + const entries = new Map([ + [0, cborBstr(generation)], + [1, cborUint(2)], + [2, cborUint(3)], + [3, cborBool(true)], + [4, cborBool(false)], + ]); + for (const [key, value] of Object.entries(overrides)) { + entries.set(Number(key), value!); + } + return [...entries.entries()].sort(([left], [right]) => left - right); +}; + +interface EdgesFixture { + request?: Partial; + head?: [number, number[]][]; + payloads?: (number[] | null)[]; + tail?: number[]; +} + +const fixture = ({ + request = {}, + head, + payloads, + tail = [], +}: EdgesFixture = {}) => { + const fullRequest: SaltileEdgesRequest = { + generation: new Uint8Array(generation), + variant: 2, + detail: "minimal", + ...request, + }; + const slots = payloads ?? [ + cborMap(head ?? defaultHead()), + u32le(sources), + u32le(targets), + edgeIdColumn, + ]; + return { buffer: response(slots, tail), request: fullRequest }; +}; + +const failure = (input: { + buffer: ArrayBuffer; + request: SaltileEdgesRequest; +}): string => { + try { + decodeSaltileEdges(input.buffer, input.request); + } catch (error) { + expect(error).toBeInstanceOf(SaltileWireError); + return (error as SaltileWireError).message; + } + throw new Error("expected the edges response to be rejected"); +}; + +describe("decodeSaltileEdges", () => { + it("decodes an edges response into zero-copy views and identity strings", () => { + const { buffer, request } = fixture(); + const edges = decodeSaltileEdges(buffer, request); + + expect(edges.count).toBe(3); + expect(edges.complete).toBe(true); + expect([...edges.sources]).toEqual(sources); + expect([...edges.targets]).toEqual(targets); + expect(edges.edgeIds).toEqual(edgeIdStrings); + expect(edges.sources.buffer).toBe(buffer); + expect(edges.targets.buffer).toBe(buffer); + expect(edges.detail).toBeNull(); + }); + + it("reports cap truncation through complete", () => { + const { buffer, request } = fixture({ + head: defaultHead({ 3: cborBool(false) }), + }); + expect(decodeSaltileEdges(buffer, request).complete).toBe(false); + }); + + it("rejects an EDGE_IDS column out of ascending identity order", () => { + const descending = [...edgeIdRecords].reverse().flat(); + const { buffer, request } = fixture({ + payloads: [ + cborMap(defaultHead()), + u32le(sources), + u32le(targets), + descending, + ], + }); + expect(failure({ buffer, request })).toMatch( + /EDGE_IDS record 1 is not in ascending identity order/u, + ); + }); + + it("decodes the trailer detail columns and resolves the intern table", () => { + const { buffer, request } = fixture({ + request: { detail: "auxiliary" }, + head: defaultHead({ 4: cborBool(true) }), + tail: cborMap([ + [ + 0, + cborArray([ + cborTstr("https://t.test/authored/v/1"), + cborTstr("https://t.test/cites/v/2"), + ]), + ], + [1, cborArray([cborTstr("employs"), cborNull(), cborTstr("owns")])], + [2, cborArray([cborUint(1), cborUint(0), cborNull()])], + ]), + }); + const edges = decodeSaltileEdges(buffer, request); + expect(edges.detail?.typeTable).toEqual([ + "https://t.test/authored/v/1", + "https://t.test/cites/v/2", + ]); + expect(edges.detail?.linkLabels).toEqual(["employs", null, "owns"]); + expect(edges.detail?.linkTypeIds).toEqual([ + "https://t.test/cites/v/2", + "https://t.test/authored/v/1", + null, + ]); + }); + + it("rejects an unsorted intern table", () => { + const { buffer, request } = fixture({ + request: { detail: "auxiliary" }, + head: defaultHead({ 4: cborBool(true) }), + tail: cborMap([ + [ + 0, + cborArray([cborTstr("https://z.test/"), cborTstr("https://a.test/")]), + ], + [1, cborArray([cborNull(), cborNull(), cborNull()])], + [2, cborArray([cborNull(), cborNull(), cborNull()])], + ]), + }); + expect(failure({ buffer, request })).toMatch( + /typeTable must be bytewise-sorted/u, + ); + }); + + it("rejects a type index outside the intern table", () => { + const { buffer, request } = fixture({ + request: { detail: "auxiliary" }, + head: defaultHead({ 4: cborBool(true) }), + tail: cborMap([ + [0, cborArray([cborTstr("https://t.test/only/v/1")])], + [1, cborArray([cborNull(), cborNull(), cborNull()])], + [2, cborArray([cborUint(1), cborNull(), cborNull()])], + ]), + }); + expect(failure({ buffer, request })).toMatch( + /lies outside the intern table/u, + ); + }); + + it("decodes the empty edges response", () => { + const { buffer, request } = fixture({ + head: defaultHead({ 2: cborUint(0) }), + payloads: [cborMap(defaultHead({ 2: cborUint(0) })), [], [], []], + }); + const edges = decodeSaltileEdges(buffer, request); + expect(edges.count).toBe(0); + expect(edges.sources).toHaveLength(0); + expect(edges.edgeIds).toHaveLength(0); + }); + + it("names every schema and echo rejection", () => { + expect( + failure(fixture({ request: { generation: new Uint8Array(32).fill(9) } })), + ).toMatch(/generation does not match/u); + + expect(failure(fixture({ request: { variant: 5 } }))).toMatch( + /HEAD variant is 2; the request expects 5/u, + ); + + expect(failure(fixture({ head: defaultHead({ 5: cborUint(1) }) }))).toMatch( + /unknown key 5/u, + ); + + expect(failure(fixture({ head: defaultHead({ 3: cborUint(1) }) }))).toMatch( + /complete \(key 3\) must be a boolean/u, + ); + + const shortColumn = fixture({ + payloads: [ + cborMap(defaultHead()), + u32le(sources.slice(0, 2)), + u32le(targets), + edgeIdColumn, + ], + }); + expect(failure(shortColumn)).toMatch( + /EDGE_SOURCES\) is 8 bytes; 12 required/u, + ); + + const missingColumn = fixture({ + payloads: [cborMap(defaultHead()), u32le(sources), null, edgeIdColumn], + }); + expect(failure(missingColumn)).toMatch( + /slot 2 \(EDGE_TARGETS\) is absent/u, + ); + + const shortIds = fixture({ + payloads: [ + cborMap(defaultHead()), + u32le(sources), + u32le(targets), + edgeIdColumn.slice(0, 64), + ], + }); + expect(failure(shortIds)).toMatch(/EDGE_IDS\) is 64 bytes; 96 required/u); + + expect( + failure( + fixture({ + request: { detail: "auxiliary" }, + head: defaultHead({ 4: cborBool(true) }), + }), + ), + ).toMatch(/carries no tail/u); + + expect(failure(fixture({ tail: [0xa0] }))).toMatch( + /HEAD declares no trailer/u, + ); + + const shortTrailer = fixture({ + request: { detail: "auxiliary" }, + head: defaultHead({ 4: cborBool(true) }), + tail: cborMap([ + [0, cborArray([cborTstr("https://t.test/only/v/1")])], + [1, cborArray([cborNull()])], + [2, cborArray([cborNull()])], + ]), + }); + expect(failure(shortTrailer)).toMatch( + /linkLabels must carry exactly 3 strings or nulls/u, + ); + }); +}); diff --git a/apps/hash-frontend/src/components/tiled-network-graph/atlas-decode/edges.ts b/apps/hash-frontend/src/components/tiled-network-graph/atlas-decode/edges.ts new file mode 100644 index 00000000000..0e001985922 --- /dev/null +++ b/apps/hash-frontend/src/components/tiled-network-graph/atlas-decode/edges.ts @@ -0,0 +1,271 @@ +/** + * Edges-kind decoder for the SALTILE wire (normative contract: + * `libs/@local/graph/atlas/docs/wire.md` sections 3, 5, and 7). Layers + * over the envelope reader: validates the HEAD schema and its echo of + * the request, exposes the endpoint columns as typed-array views over + * the response buffer, and reads `EDGE_IDS` - 32-byte link-entity + * identity records in ascending byte order, the edge's identity on + * every binary surface (edges carry no wire id of their own). + * + * Delivery-set membership (sources/targets referencing node rows the + * client holds) is a server-side contract, not a decoder walk. + */ + +import { decodeCbor } from "./cbor"; +import { + ENTITY_ID_BYTES, + expectEqual, + fail, + formatEntityId, + GENERATION_BYTES, + isCborArray, + isCborMap, + isNullableStringArray, + isUint, + readEntityIdColumn, + readInternTable, + requireBool, + requireGenerationEcho, + requireSlot, + requireUint, +} from "./schema"; +import { EdgesSlot, readEnvelope, SaltileDetail } from "./wire"; + +import type { EntityId, VersionedUrl } from "@blockprotocol/type-system"; + +/** The request context an edges response must echo. */ +export interface SaltileEdgesRequest { + /** Generation identity from the manifest, 32 raw bytes. */ + readonly generation: Uint8Array; + readonly variant: number; + /** The request body's `detail` mode; the HEAD `trailer` bool must echo it. */ + readonly detail: SaltileDetail; +} + +/** + * Per-edge detail from the trailer tail, edge order. + * + * The trailer ships type references: `linkTypeIds` entries are + * versioned type URLs resolved through the trailer's intern table, and + * label and icon rendering for a type is the client's own metadata. + */ +export interface SaltileEdgesDetail { + /** The type intern table: every referenced versioned type URL once. */ + readonly typeTable: readonly VersionedUrl[]; + /** Link-entity labels, edge order. */ + readonly linkLabels: readonly (string | null)[]; + /** + * Each link's first direct type as a versioned URL, edge order. + * + * `null` marks a link the store no longer serves or records no types + * for. + */ + readonly linkTypeIds: readonly (VersionedUrl | null)[]; +} + +/** One decoded edges response; typed arrays are views, not copies. */ +export interface DecodedSaltileEdges { + readonly count: number; + /** False when the rank-ordered cap truncated the set. */ + readonly complete: boolean; + /** Source node row ids, edge order. */ + readonly sources: Uint32Array; + /** Target node row ids, edge order. */ + readonly targets: Uint32Array; + /** Link-entity identities, edge order (ascending identity bytes). */ + readonly edgeIds: readonly EntityId[]; + readonly detail: SaltileEdgesDetail | null; +} + +/** HEAD keys of the edges schema (wire.md section 7). */ +const edgesHeadKeys = { + generation: 0, + variant: 1, + count: 2, + complete: 3, + trailer: 4, +} as const; + +const knownHeadKeys = new Set(Object.values(edgesHeadKeys)); + +/** TRAILER keys of the edges schema (wire.md section 7). */ +const trailerKeys = { + typeTable: 0, + linkLabels: 1, + linkTypeIds: 2, +} as const; + +const knownTrailerKeys = new Set(Object.values(trailerKeys)); + +const readDetail = ( + payload: Uint8Array, + count: number, + offset: number, +): SaltileEdgesDetail => { + const value = decodeCbor(payload); + if (!isCborMap(value)) { + return fail("TRAILER must be a map", offset); + } + for (const key of value.keys()) { + if (!knownTrailerKeys.has(key)) { + return fail(`TRAILER carries unknown key ${key}`, offset); + } + } + + // The wire contract pins the table entries as versioned type URLs. + const typeTable = readInternTable( + value.get(trailerKeys.typeTable), + "typeTable", + offset, + ) as readonly VersionedUrl[]; + + const linkLabels = value.get(trailerKeys.linkLabels); + if (!isNullableStringArray(linkLabels) || linkLabels.length !== count) { + return fail( + `TRAILER linkLabels must carry exactly ${count} strings or nulls`, + offset, + ); + } + + const rawTypeIds = value.get(trailerKeys.linkTypeIds); + if (!isCborArray(rawTypeIds) || rawTypeIds.length !== count) { + return fail( + `TRAILER linkTypeIds must carry exactly ${count} entries`, + offset, + ); + } + const linkTypeIds = rawTypeIds.map((entry) => { + if (entry === null) { + return null; + } + if (!isUint(entry) || entry >= typeTable.length) { + return fail( + // eslint-disable-next-line @typescript-eslint/no-base-to-string -- diagnostic for a malformed, possibly non-numeric CBOR entry + `linkTypeIds entry ${String(entry)} lies outside the intern table`, + offset, + ); + } + return typeTable[entry]!; + }); + + return { typeTable, linkLabels, linkTypeIds }; +}; + +/** + * Decodes one edges response against the request that produced it. + * + * @throws {@link SaltileWireError} on envelope, schema, echo, or count + * violations; `SaltileCborError` on CBOR profile violations. + */ +export const decodeSaltileEdges = ( + buffer: ArrayBuffer, + request: SaltileEdgesRequest, +): DecodedSaltileEdges => { + if (request.generation.length !== GENERATION_BYTES) { + fail( + `request generation is ${request.generation.length} bytes; expected ${GENERATION_BYTES}`, + 0, + ); + } + + const envelope = readEnvelope(buffer, "edges"); + const bytes = new Uint8Array(buffer); + + const headSlot = envelope.slots[EdgesSlot.Head]!; + const headOffset = headSlot.start; + const head = decodeCbor(bytes.subarray(headSlot.start, headSlot.end)); + if (!isCborMap(head)) { + return fail("HEAD must be a map", headOffset); + } + for (const key of head.keys()) { + if (!knownHeadKeys.has(key)) { + return fail(`HEAD carries unknown key ${key}`, headOffset); + } + } + + requireGenerationEcho(head, request.generation, headOffset); + expectEqual( + requireUint(head, edgesHeadKeys.variant, "variant", headOffset), + request.variant, + "variant", + headOffset, + ); + + const count = requireUint(head, edgesHeadKeys.count, "count", headOffset); + const complete = requireBool( + head, + edgesHeadKeys.complete, + "complete", + headOffset, + ); + + const trailerDeclared = requireBool( + head, + edgesHeadKeys.trailer, + "trailer", + headOffset, + ); + if (trailerDeclared !== (request.detail === SaltileDetail.Auxiliary)) { + return fail( + `HEAD trailer is ${trailerDeclared}; the request's detail is "${request.detail}"`, + headOffset, + ); + } + + const sourcesSlot = requireSlot( + envelope.slots, + EdgesSlot.Sources, + "EDGE_SOURCES", + count * 4, + buffer.byteLength, + ); + const targetsSlot = requireSlot( + envelope.slots, + EdgesSlot.Targets, + "EDGE_TARGETS", + count * 4, + buffer.byteLength, + ); + const edgeIdsSlot = requireSlot( + envelope.slots, + EdgesSlot.EdgeIds, + "EDGE_IDS", + count * ENTITY_ID_BYTES, + buffer.byteLength, + ); + const edgeIdRecords = readEntityIdColumn( + bytes.subarray(edgeIdsSlot.start, edgeIdsSlot.end), + count, + "EDGE_IDS", + edgeIdsSlot.start, + ); + + let detail: SaltileEdgesDetail | null = null; + if (trailerDeclared) { + if (envelope.tailOffset >= buffer.byteLength) { + return fail( + "TRAILER is declared but the response carries no tail", + envelope.tailOffset, + ); + } + detail = readDetail( + bytes.subarray(envelope.tailOffset), + count, + envelope.tailOffset, + ); + } else if (envelope.tailOffset !== buffer.byteLength) { + return fail( + "response carries a tail but HEAD declares no trailer", + envelope.tailOffset, + ); + } + + return { + count, + complete, + sources: new Uint32Array(buffer, sourcesSlot.start, count), + targets: new Uint32Array(buffer, targetsSlot.start, count), + edgeIds: edgeIdRecords.map((record) => formatEntityId(record)), + detail, + }; +}; diff --git a/apps/hash-frontend/src/components/tiled-network-graph/atlas-decode/fixtures-conformance.test.ts b/apps/hash-frontend/src/components/tiled-network-graph/atlas-decode/fixtures-conformance.test.ts new file mode 100644 index 00000000000..72f7ba953fc --- /dev/null +++ b/apps/hash-frontend/src/components/tiled-network-graph/atlas-decode/fixtures-conformance.test.ts @@ -0,0 +1,406 @@ +/** + * Conformance against the checked-in wire fixtures: the Rust encoder + * writes `libs/@local/graph/atlas/fixtures/wire/*.saltile` with JSON + * sidecars, and every decoder implementation asserts field-for-field + * equality against them (wire.md section 10). "Matches the server" is + * proven by shared bytes, never by eye. + */ + +import { readdirSync, readFileSync } from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +import { describe, expect, it } from "vitest"; + +import { decodeSaltileEdges } from "./edges"; +import { decodeSaltileLocate } from "./locate"; +import { decodeSaltileTile } from "./tile"; + +import type { SaltileMode } from "./wire"; + +const fixturesDir = path.join( + path.dirname(fileURLToPath(import.meta.url)), + "../../../../../../libs/@local/graph/atlas/fixtures/wire", +); + +const readFixture = (name: string): { buffer: ArrayBuffer; sidecar: never } => { + const bytes = readFileSync(path.join(fixturesDir, `${name}.saltile`)); + const buffer = bytes.buffer.slice( + bytes.byteOffset, + bytes.byteOffset + bytes.byteLength, + ); + const sidecar = JSON.parse( + readFileSync(path.join(fixturesDir, `${name}.json`), "utf8"), + ) as never; + return { buffer, sidecar }; +}; + +/** + * The delivery cut every checked-in tile golden was encoded at, as a + * literal rather than as a function of the head it is compared against: + * the generator cuts each tile at `m + k = 2`, which is what makes `g1`'s + * `firstBucket` its `z + 2 = 4` and the root goldens' `runs` length + * `2 + 1 = 3`. Deriving it from `firstBucket` instead would make the + * decoder's echo check agree with itself by construction; written this + * way, a regenerated fixture that moves its cut fails here loudly. + */ +const TILE_CUT_ADDEND = 2; + +/** f32 bit patterns -> the numbers a decoded column holds. */ +const f32FromBits = (bits: readonly number[]): number[] => [ + ...new Float32Array(new Uint32Array(bits).buffer), +]; + +/** A decoded f32 column -> the bit patterns a sidecar prints. */ +const bitsOfF32 = (values: Float32Array): number[] => [ + ...new Uint32Array(values.buffer, values.byteOffset, values.length), +]; + +const bytesFromHex = (hex: string): Uint8Array => + new Uint8Array( + Array.from({ length: hex.length / 2 }, (_, index) => + Number.parseInt(hex.slice(index * 2, index * 2 + 2), 16), + ), + ); + +/** Sidecar 32-byte identity hex -> the decoder's `webUuid~entityUuid` form. */ +const entityIdOfHex = (hex: string): string => { + const uuid = (part: string): string => + `${part.slice(0, 8)}-${part.slice(8, 12)}-${part.slice(12, 16)}-${part.slice(16, 20)}-${part.slice(20)}`; + return `${uuid(hex.slice(0, 32))}~${uuid(hex.slice(32))}`; +}; + +/** The tile sidecar shape, shared by all eight tile goldens. */ +interface TileSidecar { + readonly head: { + readonly generation: string; + readonly variant: number; + readonly coordinate: [number, number, number]; + readonly mode: number; + readonly delivered: number; + readonly visible: number; + readonly firstBucket: number; + readonly runs: number[]; + readonly children: number; + readonly trailer: boolean; + readonly global: { + readonly boundsBits: number[] | null; + readonly minResolution: number; + readonly visibleAtZoom: number; + } | null; + }; + readonly positions: number[]; + readonly rowIds: number[]; + readonly typeMask: number[] | null; + readonly trailer: { + readonly labels: (string | null)[]; + readonly icons: (string | null)[]; + } | null; + readonly mass: number[] | null; + readonly appended: Record | null; +} + +/** + * Decodes one tile golden with the request its sidecar describes and + * asserts every field the tile decoder exposes for every golden: the head + * echoes, the run schedule, and the three columns. Per-fixture specifics + * (global metadata, the trailer tail, slot tolerance) are asserted by the + * caller against the same sidecar. + * + * `coloredTypeIdCount` is reconstructed from the mask's bytes per point, + * which pins it to a band rather than a value - the wire carries + * `ceil(count / 8)` bytes per row, so the top of the band decodes every + * mask the fixtures hold. Same reasoning as the locate golden above. + */ +const decodeTileFixture = ( + name: string, +): { decoded: ReturnType; sidecar: TileSidecar } => { + const { buffer, sidecar } = readFixture(name) as unknown as { + buffer: ArrayBuffer; + sidecar: TileSidecar; + }; + const { head } = sidecar; + const maskBytesPerPoint = + sidecar.typeMask && head.delivered > 0 + ? sidecar.typeMask.length / head.delivered + : 0; + + const decoded = decodeSaltileTile(buffer, { + generation: bytesFromHex(head.generation), + variant: head.variant, + coordinate: { + z: head.coordinate[0], + x: head.coordinate[1], + y: head.coordinate[2], + }, + mode: head.mode as SaltileMode, + deliverySpanLog2: TILE_CUT_ADDEND, + coloredTypeIdCount: maskBytesPerPoint * 8, + detail: head.trailer ? "auxiliary" : "minimal", + }); + + expect(decoded.delivered).toBe(head.delivered); + expect(decoded.visible).toBe(head.visible); + expect(decoded.firstBucket).toBe(head.firstBucket); + expect(decoded.runs).toEqual(head.runs); + expect(decoded.children).toBe(head.children); + + // sum(runs) = delivered is law in every response (wire.md, the runs + // contract), so the goldens are the cross-implementation witness for it. + expect(decoded.runs.reduce((sum, run) => sum + run, 0)).toBe(head.delivered); + + // Sidecar positions are f32 bit patterns (never printed decimals). + expect(bitsOfF32(decoded.positions)).toEqual(sidecar.positions); + expect(decoded.positions.length).toBe(head.delivered * 2); + expect([...decoded.rowIds]).toEqual(sidecar.rowIds); + expect(decoded.typeMask === null ? null : [...decoded.typeMask]).toEqual( + sidecar.typeMask, + ); + + return { decoded, sidecar }; +}; + +describe("wire fixtures", () => { + it("names every golden on disk, so a new fixture cannot land undecoded", () => { + // The gap this closes is the one that left the eight tile goldens unread for as long as they + // existed: the suite was normative and nothing said which fixtures it covered. Checked against + // this file's own source, so adding a golden fails here until someone writes its case. + // + // Its limit, stated rather than papered over: a name mentioned in a comment satisfies this, so it + // catches an unnoticed fixture and never proves how deeply one is read. + const source = readFileSync(fileURLToPath(import.meta.url), "utf8"); + const goldens = readdirSync(fixturesDir) + .filter((entry) => entry.endsWith(".saltile")) + .map((entry) => entry.replace(/\.saltile$/, "")); + + expect(goldens.length).toBeGreaterThan(0); + expect(goldens.filter((golden) => !source.includes(golden))).toEqual([]); + }); + + it("decodes g1-minimal-tile field-for-field against its sidecar", () => { + const { decoded } = decodeTileFixture("g1-minimal-tile"); + + // A delta tile below the root: one run, starting at the cut. + expect(decoded.firstBucket).toBe(2 /* z */ + TILE_CUT_ADDEND); + expect(decoded.runs).toHaveLength(1); + expect(decoded.global).toBeNull(); + expect(decoded.detail).toBeNull(); + }); + + it("decodes g2-root-tile, whose global metadata frames the camera", () => { + const { decoded, sidecar } = decodeTileFixture("g2-root-tile"); + const global = sidecar.head.global!; + + expect(decoded.global).toEqual({ + visibleAtZoom: global.visibleAtZoom, + bounds: f32FromBits(global.boundsBits!), + minResolution: global.minResolution, + }); + // The root of a delta cascade starts at bucket 0 and carries the whole + // schedule, gaps included. + expect(decoded.firstBucket).toBe(0); + expect(decoded.runs).toHaveLength(TILE_CUT_ADDEND + 1); + expect(decoded.detail).toBeNull(); + }); + + it("decodes g3-total-tile, a total-mode cascade with a two-byte type mask", () => { + const { decoded, sidecar } = decodeTileFixture("g3-total-tile"); + + expect(sidecar.head.mode).toBe(1 satisfies SaltileMode); + // Total mode delivers every bucket up to the cut, from zero. + expect(decoded.firstBucket).toBe(0); + expect(decoded.runs).toHaveLength(1 /* z */ + TILE_CUT_ADDEND + 1); + expect(decoded.typeMask).not.toBeNull(); + expect(decoded.typeMask!.length).toBe(sidecar.head.delivered * 2); + }); + + it("decodes g4-empty-root, where the visible set is empty and bounds are absent", () => { + const { decoded } = decodeTileFixture("g4-empty-root"); + + expect(decoded.delivered).toBe(0); + expect(decoded.visible).toBe(0); + expect(decoded.positions).toHaveLength(0); + expect(decoded.rowIds).toHaveLength(0); + expect(decoded.typeMask).toBeNull(); + expect(decoded.global).toEqual({ + visibleAtZoom: 0, + bounds: null, + minResolution: 0, + }); + }); + + it("decodes g5-trailer-tile's per-point labels and icons, multibyte included", () => { + const { decoded, sidecar } = decodeTileFixture("g5-trailer-tile"); + const trailer = sidecar.trailer!; + + expect(decoded.detail).not.toBeNull(); + expect(decoded.detail!.labels).toEqual(trailer.labels); + expect(decoded.detail!.icons).toEqual(trailer.icons); + expect(decoded.detail!.labels).toHaveLength(sidecar.head.delivered); + }); + + it("decodes g8-appended-slot, tolerating a present mass slot and an appended one", () => { + const { decoded, sidecar } = decodeTileFixture("g8-appended-slot"); + + // The golden's own reason for existing: a directory longer than the + // kind's v1 table, plus a payload in the reserved mass slot. Both are + // forward compatibility, so decoding must succeed and expose neither - + // the mass column has no field on the decoded tile, and the sidecar + // prints it for the implementations that do read it. + expect(sidecar.mass).not.toBeNull(); + expect(sidecar.appended).not.toBeNull(); + expect(decoded.detail).toBeNull(); + expect(decoded.global).toBeNull(); + }); + + it("decodes g9-padding-low and g10-padding-high across their alignment padding", () => { + const low = decodeTileFixture("g9-padding-low"); + const high = decodeTileFixture("g10-padding-high"); + + // Deep coordinates with wide visible counts: the columns land behind + // different amounts of 8-alignment padding in the two goldens, and + // g10 also appends two unknown slots. + expect(low.decoded.rowIds).toHaveLength(low.sidecar.head.delivered); + expect(high.decoded.rowIds).toHaveLength(high.sidecar.head.delivered); + expect(high.sidecar.appended).not.toBeNull(); + }); + + it("decodes g6-edges field-for-field against its sidecar", () => { + const { buffer, sidecar } = readFixture("g6-edges") as { + buffer: ArrayBuffer; + sidecar: { + head: { + generation: string; + variant: number; + count: number; + complete: boolean; + trailer: boolean; + }; + sources: number[]; + targets: number[]; + edgeIds: string[]; + trailer: { + typeTable: string[]; + linkLabels: (string | null)[]; + linkTypeIds: (number | null)[]; + }; + }; + }; + + const decoded = decodeSaltileEdges(buffer, { + generation: bytesFromHex(sidecar.head.generation), + variant: sidecar.head.variant, + detail: sidecar.head.trailer ? "auxiliary" : "minimal", + }); + + expect(decoded.count).toBe(sidecar.head.count); + expect(decoded.complete).toBe(sidecar.head.complete); + expect([...decoded.sources]).toEqual(sidecar.sources); + expect([...decoded.targets]).toEqual(sidecar.targets); + expect(decoded.edgeIds).toEqual(sidecar.edgeIds.map(entityIdOfHex)); + expect(decoded.detail?.typeTable).toEqual(sidecar.trailer.typeTable); + expect(decoded.detail?.linkLabels).toEqual(sidecar.trailer.linkLabels); + expect(decoded.detail?.linkTypeIds).toEqual( + sidecar.trailer.linkTypeIds.map((index) => + index === null ? null : sidecar.trailer.typeTable[index]!, + ), + ); + }); + + it("decodes g7-locate field-for-field against its sidecar", () => { + const { buffer, sidecar } = readFixture("g7-locate") as { + buffer: ArrayBuffer; + sidecar: { + head: { + generation: string; + variant: number; + count: number; + zoom: number; + cell: [number, number, number]; + edges: number; + complete: boolean; + entityId: string; + typeIdsComplete: boolean; + propertiesComplete: boolean; + }; + positions: number[]; + rowIds: number[]; + sources: number[]; + targets: number[]; + edgeIds: string[]; + typeMask: number[]; + trailer: { + typeTable: string[]; + propertyTable: string[]; + labels: (string | null)[]; + typeIds: (number | null)[]; + properties: Record; + linkLabels: (string | null)[]; + linkTypeIds: number[][]; + linkTypeIdsComplete: boolean[]; + linkProperties: (Record | null)[]; + linkPropertiesComplete: boolean[]; + }; + }; + }; + + // The fixture's TYPE_MASK column is one byte per point: the fixture + // request carried between one and eight colored type ids. + const decoded = decodeSaltileLocate(buffer, { + generation: bytesFromHex(sidecar.head.generation), + variant: sidecar.head.variant, + coloredTypeIdCount: 8, + }); + + expect(decoded.count).toBe(sidecar.head.count); + expect(decoded.zoom).toBe(sidecar.head.zoom); + expect(decoded.cell).toEqual({ + z: sidecar.head.cell[0], + x: sidecar.head.cell[1], + y: sidecar.head.cell[2], + }); + expect(decoded.edgesCount).toBe(sidecar.head.edges); + expect(decoded.complete).toBe(sidecar.head.complete); + expect(decoded.entityId).toBe(entityIdOfHex(sidecar.head.entityId)); + expect(decoded.typeIdsComplete).toBe(sidecar.head.typeIdsComplete); + expect(decoded.propertiesComplete).toBe(sidecar.head.propertiesComplete); + + // Sidecar positions are f32 bit patterns (never printed decimals). + const positionBits = new Uint32Array( + decoded.positions.buffer, + decoded.positions.byteOffset, + decoded.positions.length, + ); + expect([...positionBits]).toEqual(sidecar.positions); + + expect([...decoded.rowIds]).toEqual(sidecar.rowIds); + expect([...decoded.sources]).toEqual(sidecar.sources); + expect([...decoded.targets]).toEqual(sidecar.targets); + expect(decoded.edgeIds).toEqual(sidecar.edgeIds.map(entityIdOfHex)); + expect([...(decoded.typeMask ?? [])]).toEqual(sidecar.typeMask); + + const { detail } = decoded; + expect(detail.typeTable).toEqual(sidecar.trailer.typeTable); + expect(detail.propertyTable).toEqual(sidecar.trailer.propertyTable); + expect(detail.labels).toEqual(sidecar.trailer.labels); + expect(detail.typeIds).toEqual( + sidecar.trailer.typeIds.map((index) => + index === null ? null : sidecar.trailer.typeTable[index]!, + ), + ); + expect(detail.properties).toEqual(sidecar.trailer.properties); + expect(detail.linkLabels).toEqual(sidecar.trailer.linkLabels); + expect(detail.linkTypeIds).toEqual( + sidecar.trailer.linkTypeIds.map((indexes) => + indexes.map((index) => sidecar.trailer.typeTable[index]!), + ), + ); + expect(detail.linkTypeIdsComplete).toEqual( + sidecar.trailer.linkTypeIdsComplete, + ); + expect(detail.linkProperties).toEqual(sidecar.trailer.linkProperties); + expect(detail.linkPropertiesComplete).toEqual( + sidecar.trailer.linkPropertiesComplete, + ); + }); +}); diff --git a/apps/hash-frontend/src/components/tiled-network-graph/atlas-decode/fixtures.ts b/apps/hash-frontend/src/components/tiled-network-graph/atlas-decode/fixtures.ts new file mode 100644 index 00000000000..5f770acf5a7 --- /dev/null +++ b/apps/hash-frontend/src/components/tiled-network-graph/atlas-decode/fixtures.ts @@ -0,0 +1,135 @@ +/** + * Reference byte builders for SALTILE fixtures: hand-built responses + * for decoder tests, and the shape the checked-in goldens grow from. + * Test support - not exported from the package surface. + * + * CBOR integers and floats are big-endian per RFC 8949; envelope + * integers and column payloads are little-endian per the wire. + */ + +import { + DIRECTORY_ENTRY_BYTES, + PAYLOAD_ALIGNMENT, + PREFIX_BYTES, + type SaltileKind, +} from "./wire"; + +export const cborUint = (value: number, major = 0): number[] => { + const base = major * 32; + if (value < 24) { + return [base + value]; + } + if (value <= 0xff) { + return [base + 24, value]; + } + const wide = new DataView(new ArrayBuffer(2)); + wide.setUint16(0, value, false); + return [base + 25, ...new Uint8Array(wide.buffer)]; +}; + +export const cborBstr = (bytes: number[]): number[] => [ + ...cborUint(bytes.length, 2), + ...bytes, +]; + +export const cborTstr = (text: string): number[] => { + const encoded = [...new TextEncoder().encode(text)]; + return [...cborUint(encoded.length, 3), ...encoded]; +}; + +export const cborArray = (entries: number[][]): number[] => [ + ...cborUint(entries.length, 4), + ...entries.flat(), +]; + +export const cborMap = (entries: [number, number[]][]): number[] => [ + ...cborUint(entries.length, 5), + ...entries.flatMap(([key, value]) => [...cborUint(key), ...value]), +]; + +export const cborNull = (): number[] => [0xf6]; + +export const cborBool = (value: boolean): number[] => [value ? 0xf5 : 0xf4]; + +export const cborF32 = (value: number): number[] => { + const view = new DataView(new ArrayBuffer(4)); + view.setFloat32(0, value, false); + return [0xfa, ...new Uint8Array(view.buffer)]; +}; + +export const f32le = (values: number[]): number[] => { + const view = new DataView(new ArrayBuffer(values.length * 4)); + for (const [index, value] of values.entries()) { + view.setFloat32(index * 4, value, true); + } + return [...new Uint8Array(view.buffer)]; +}; + +export const u32le = (values: number[]): number[] => { + const view = new DataView(new ArrayBuffer(values.length * 4)); + for (const [index, value] of values.entries()) { + view.setUint32(index * 4, value, true); + } + return [...new Uint8Array(view.buffer)]; +}; + +const kindBytes: Record = { + tile: 0x54, + edges: 0x45, + locate: 0x4c, +}; + +/** + * Builds one response: prefix, offset directory, payloads sequential + * in slot order (null = absent slot), optional self-delimiting tail. + */ +export const buildResponse = ( + kind: SaltileKind, + payloads: (number[] | null)[], + tail: number[] = [], +): ArrayBuffer => { + const base = PREFIX_BYTES + payloads.length * DIRECTORY_ENTRY_BYTES; + const directory = new DataView( + new ArrayBuffer(payloads.length * DIRECTORY_ENTRY_BYTES), + ); + const body: number[] = []; + let cursor = base; + for (const [slot, payload] of payloads.entries()) { + if (payload === null) { + continue; + } + directory.setUint32(slot * DIRECTORY_ENTRY_BYTES, cursor, true); + directory.setUint32( + slot * DIRECTORY_ENTRY_BYTES + 4, + cursor + payload.length, + true, + ); + body.push(...payload); + cursor += payload.length; + while (cursor % PAYLOAD_ALIGNMENT !== 0) { + body.push(0); + cursor += 1; + } + } + const prefix = new DataView(new ArrayBuffer(PREFIX_BYTES)); + for (const [index, byte] of [ + 0x53, + 0x41, + 0x4c, + 0x54, + 0x49, + 0x4c, + 0x45, + kindBytes[kind], + ].entries()) { + prefix.setUint8(index, byte); + } + prefix.setUint16(8, 1, true); + prefix.setUint16(12, payloads.length, true); + return new Uint8Array([ + ...new Uint8Array(prefix.buffer), + ...new Uint8Array(directory.buffer), + ...body, + ...tail, + ]).buffer; +}; diff --git a/apps/hash-frontend/src/components/tiled-network-graph/atlas-decode/locate.test.ts b/apps/hash-frontend/src/components/tiled-network-graph/atlas-decode/locate.test.ts new file mode 100644 index 00000000000..bb956521a16 --- /dev/null +++ b/apps/hash-frontend/src/components/tiled-network-graph/atlas-decode/locate.test.ts @@ -0,0 +1,490 @@ +import { describe, expect, it } from "vitest"; + +import { decodeSaltileLocate, type SaltileLocateRequest } from "./locate"; +import { + DIRECTORY_ENTRY_BYTES, + PAYLOAD_ALIGNMENT, + PREFIX_BYTES, + SaltileWireError, +} from "./wire"; + +/* Reference encoder helpers; CBOR is big-endian, envelope and columns + * little-endian, as in the tile/edges suites. */ + +const cborUint = (value: number, major = 0): number[] => { + const base = major * 32; + if (value < 24) { + return [base + value]; + } + if (value <= 0xff) { + return [base + 24, value]; + } + const wide = new DataView(new ArrayBuffer(2)); + wide.setUint16(0, value, false); + return [base + 25, ...new Uint8Array(wide.buffer)]; +}; + +/** A negative integer `value` (< 0) as CBOR major type 1. */ +const cborNegInt = (value: number): number[] => cborUint(-1 - value, 1); + +const cborBstr = (bytes: number[]): number[] => [ + ...cborUint(bytes.length, 2), + ...bytes, +]; + +/** One completeness bitmask word: the low byte's bits, then seven zero padding bytes. */ +const maskWord = (bits: number): number[] => + cborBstr([bits, 0, 0, 0, 0, 0, 0, 0]); + +const cborTstr = (text: string): number[] => { + const encoded = [...new TextEncoder().encode(text)]; + return [...cborUint(encoded.length, 3), ...encoded]; +}; + +const cborArray = (entries: number[][]): number[] => [ + ...cborUint(entries.length, 4), + ...entries.flat(), +]; + +const cborMap = (entries: [number, number[]][]): number[] => [ + ...cborUint(entries.length, 5), + ...entries.flatMap(([key, value]) => [...cborUint(key), ...value]), +]; + +const cborNull = (): number[] => [0xf6]; +const cborBool = (value: boolean): number[] => [value ? 0xf5 : 0xf4]; + +/** A double-precision float (0xFB), as locate property values ship. */ +const cborF64 = (value: number): number[] => { + const view = new DataView(new ArrayBuffer(8)); + view.setFloat64(0, value, false); + return [0xfb, ...new Uint8Array(view.buffer)]; +}; + +const f32le = (values: number[]): number[] => { + const view = new DataView(new ArrayBuffer(values.length * 4)); + for (const [index, value] of values.entries()) { + view.setFloat32(index * 4, value, true); + } + return [...new Uint8Array(view.buffer)]; +}; + +const u32le = (values: number[]): number[] => { + const view = new DataView(new ArrayBuffer(values.length * 4)); + for (const [index, value] of values.entries()) { + view.setUint32(index * 4, value, true); + } + return [...new Uint8Array(view.buffer)]; +}; + +const response = ( + payloads: (number[] | null)[], + tail: number[] = [], +): ArrayBuffer => { + const base = PREFIX_BYTES + payloads.length * DIRECTORY_ENTRY_BYTES; + const directory = new DataView( + new ArrayBuffer(payloads.length * DIRECTORY_ENTRY_BYTES), + ); + const body: number[] = []; + let cursor = base; + for (const [slot, payload] of payloads.entries()) { + if (payload === null) { + continue; + } + directory.setUint32(slot * DIRECTORY_ENTRY_BYTES, cursor, true); + directory.setUint32( + slot * DIRECTORY_ENTRY_BYTES + 4, + cursor + payload.length, + true, + ); + body.push(...payload); + cursor += payload.length; + while (cursor % PAYLOAD_ALIGNMENT !== 0) { + body.push(0); + cursor += 1; + } + } + const prefix = new DataView(new ArrayBuffer(PREFIX_BYTES)); + for (const [index, byte] of [ + 0x53, + 0x41, + 0x4c, + 0x54, + 0x49, + 0x4c, + 0x45, + 0x4c, // SALTILEL + ].entries()) { + prefix.setUint8(index, byte); + } + prefix.setUint16(8, 1, true); + prefix.setUint16(12, payloads.length, true); + return new Uint8Array([ + ...new Uint8Array(prefix.buffer), + ...new Uint8Array(directory.buffer), + ...body, + ...tail, + ]).buffer; +}; + +const generation = Array.from({ length: 32 }, (_, index) => index); +const positions = [0, 0, 0.5, -0.5]; +const rowIds = [61, 11]; +const sources = [61]; +const targets = [11]; + +/** The source's 32-byte identity record: web uuid then entity uuid. */ +const sourceEntityId = [ + ...Array.from({ length: 16 }, () => 0x42), + ...Array.from({ length: 16 }, () => 0x24), +]; +const sourceEntityIdString = + "42424242-4242-4242-4242-424242424242~24242424-2424-2424-2424-242424242424"; + +/** One 32-byte link-entity identity record for the single edge. */ +const edgeIdRecord = [ + ...Array.from({ length: 16 }, () => 0xd0), + ...Array.from({ length: 16 }, () => 0xd1), +]; +const edgeIdString = + "d0d0d0d0-d0d0-d0d0-d0d0-d0d0d0d0d0d0~d1d1d1d1-d1d1-d1d1-d1d1-d1d1d1d1d1d1"; + +/** HEAD with two nodes and one edge (wire.md section 8, keys 0-9). */ +const defaultHead = ( + overrides: Partial> = {}, +): [number, number[]][] => { + const entries = new Map([ + [0, cborBstr(generation)], + [1, cborUint(2)], + [2, cborUint(2)], + [3, cborUint(3)], + [4, cborArray([cborUint(3), cborUint(5), cborUint(2)])], + [5, cborUint(1)], + [6, cborBool(true)], + [7, cborBstr(sourceEntityId)], + [8, cborBool(true)], + [9, cborBool(false)], + ]); + for (const [key, value] of Object.entries(overrides)) { + entries.set(Number(key), value!); + } + return [...entries.entries()].sort(([left], [right]) => left - right); +}; + +/** + * Trailer per wire.md section 8: intern tables first, then node arrays + * (delivered order) and link arrays (edge order). The SOURCE alone + * carries a property map; bitmasks are LSB-first whole 8-byte words. + */ +const defaultTrailer = (): number[] => + cborMap([ + [ + 0, + cborArray([ + cborTstr("https://t.test/authored/v/1"), + cborTstr("https://t.test/person/v/3"), + ]), + ], + [ + 1, + cborArray([ + cborTstr("https://x.test/age/"), + cborTstr("https://x.test/name/"), + cborTstr("https://x.test/score/"), + ]), + ], + [2, cborArray([cborTstr("Café"), cborNull()])], + [3, cborArray([cborUint(1), cborNull()])], + [ + 4, + cborMap([ + [0, cborNegInt(-3)], + [1, cborTstr("Ada")], + [2, cborF64(0.5)], + ]), + ], + [5, cborArray([cborTstr("cites")])], + [6, cborArray([cborArray([cborUint(1), cborUint(0)])])], + [7, maskWord(0b0000_0001)], + [8, cborArray([cborMap([[0, cborUint(7)]])])], + [9, maskWord(0b0000_0000)], + ]); + +interface LocateFixture { + request?: Partial; + head?: [number, number[]][]; + payloads?: (number[] | null)[]; + tail?: number[]; +} + +const fixture = ({ + request = {}, + head, + payloads, + tail, +}: LocateFixture = {}) => { + const fullRequest: SaltileLocateRequest = { + generation: new Uint8Array(generation), + variant: 2, + coloredTypeIdCount: 0, + ...request, + }; + const slots = payloads ?? [ + cborMap(head ?? defaultHead()), + f32le(positions), + u32le(rowIds), + null, + u32le(sources), + u32le(targets), + edgeIdRecord, + ]; + return { + buffer: response(slots, tail ?? defaultTrailer()), + request: fullRequest, + }; +}; + +const failure = (input: { + buffer: ArrayBuffer; + request: SaltileLocateRequest; +}): string => { + try { + decodeSaltileLocate(input.buffer, input.request); + } catch (error) { + expect(error).toBeInstanceOf(SaltileWireError); + return (error as SaltileWireError).message; + } + throw new Error("expected the locate response to be rejected"); +}; + +describe("decodeSaltileLocate", () => { + it("decodes a locate response into zero-copy views", () => { + const { buffer, request } = fixture(); + const located = decodeSaltileLocate(buffer, request); + + expect(located.count).toBe(2); + expect(located.edgesCount).toBe(1); + expect(located.zoom).toBe(3); + expect(located.cell).toEqual({ z: 3, x: 5, y: 2 }); + expect(located.complete).toBe(true); + expect(located.entityId).toBe(sourceEntityIdString); + expect(located.typeIdsComplete).toBe(true); + expect(located.propertiesComplete).toBe(false); + expect([...located.positions]).toEqual(positions); + expect([...located.rowIds]).toEqual(rowIds); + expect([...located.sources]).toEqual(sources); + expect([...located.targets]).toEqual(targets); + expect(located.edgeIds).toEqual([edgeIdString]); + expect(located.typeMask).toBeNull(); + // Views over the response buffer, not copies. + expect(located.positions.buffer).toBe(buffer); + expect(located.rowIds.buffer).toBe(buffer); + expect(located.sources.buffer).toBe(buffer); + }); + + it("resolves the interned detail trailer", () => { + const { buffer, request } = fixture(); + const { detail } = decodeSaltileLocate(buffer, request); + + expect(detail.typeTable).toEqual([ + "https://t.test/authored/v/1", + "https://t.test/person/v/3", + ]); + expect(detail.propertyTable).toEqual([ + "https://x.test/age/", + "https://x.test/name/", + "https://x.test/score/", + ]); + expect(detail.labels).toEqual(["Café", null]); + expect(detail.typeIds).toEqual(["https://t.test/person/v/3", null]); + // The SOURCE's map alone; neighbours ship no properties. + expect(detail.properties).toEqual({ + "https://x.test/age/": -3, + "https://x.test/name/": "Ada", + "https://x.test/score/": 0.5, + }); + expect(detail.linkLabels).toEqual(["cites"]); + expect(detail.linkTypeIds).toEqual([ + ["https://t.test/person/v/3", "https://t.test/authored/v/1"], + ]); + expect(detail.linkTypeIdsComplete).toEqual([true]); + expect(detail.linkProperties).toEqual([{ "https://x.test/age/": 7 }]); + expect(detail.linkPropertiesComplete).toEqual([false]); + }); + + it("exposes the TYPE_MASK when colored types were requested", () => { + const { buffer, request } = fixture({ + request: { coloredTypeIdCount: 3 }, + payloads: [ + cborMap(defaultHead()), + f32le(positions), + u32le(rowIds), + [0b0000_0001, 0b0000_0010], + u32le(sources), + u32le(targets), + edgeIdRecord, + ], + }); + const located = decodeSaltileLocate(buffer, request); + expect([...(located.typeMask ?? [])]).toEqual([1, 2]); + }); + + it("reports cap truncation through complete", () => { + const { buffer, request } = fixture({ + head: defaultHead({ 6: cborBool(false) }), + }); + expect(decodeSaltileLocate(buffer, request).complete).toBe(false); + }); + + it("names every schema and echo rejection", () => { + expect( + failure(fixture({ request: { generation: new Uint8Array(32).fill(9) } })), + ).toMatch(/generation does not match/u); + + expect(failure(fixture({ request: { variant: 5 } }))).toMatch( + /HEAD variant is 2; the request expects 5/u, + ); + + expect( + failure(fixture({ head: defaultHead({ 10: cborUint(1) }) })), + ).toMatch(/unknown key 10/u); + + expect( + failure(fixture({ head: defaultHead({ 7: cborBstr([1, 2, 3]) }) })), + ).toMatch(/entityId \(key 7\) must be a 32-byte identity record/u); + + expect(failure(fixture({ head: defaultHead({ 8: cborUint(1) }) }))).toMatch( + /typeIdsComplete \(key 8\) must be a boolean/u, + ); + + expect( + failure( + fixture({ + head: defaultHead({ + 4: cborArray([cborUint(4), cborUint(5), cborUint(2)]), + }), + }), + ), + ).toMatch(/z equal to zoom/u); + + const shortColumn = fixture({ + payloads: [ + cborMap(defaultHead()), + f32le(positions.slice(0, 2)), + u32le(rowIds), + null, + u32le(sources), + u32le(targets), + edgeIdRecord, + ], + }); + expect(failure(shortColumn)).toMatch( + /POSITIONS\) is 8 bytes; 16 required/u, + ); + + const missingColumn = fixture({ + payloads: [ + cborMap(defaultHead()), + f32le(positions), + u32le(rowIds), + null, + u32le(sources), + null, + edgeIdRecord, + ], + }); + expect(failure(missingColumn)).toMatch(/EDGE_TARGETS\) is absent/u); + + const shortIds = fixture({ + payloads: [ + cborMap(defaultHead()), + f32le(positions), + u32le(rowIds), + null, + u32le(sources), + u32le(targets), + edgeIdRecord.slice(0, 16), + ], + }); + expect(failure(shortIds)).toMatch(/EDGE_IDS\) is 16 bytes; 32 required/u); + + // TYPE_MASK present without coloredTypeIds. + const strayMask = fixture({ + payloads: [ + cborMap(defaultHead()), + f32le(positions), + u32le(rowIds), + [0, 0], + u32le(sources), + u32le(targets), + edgeIdRecord, + ], + }); + expect(failure(strayMask)).toMatch(/TYPE_MASK must be absent/u); + + // Locate is the detail view: a missing trailer is a violation. + expect(failure(fixture({ tail: [] }))).toMatch( + /locate requires a trailer tail/u, + ); + }); + + it("rejects an unsorted intern table", () => { + const badTrailer = cborMap([ + [0, cborArray([cborTstr("https://t.test/authored/v/1")])], + [ + 1, + cborArray([ + cborTstr("https://x.test/z/"), + cborTstr("https://x.test/a/"), + ]), + ], + [2, cborArray([cborTstr("Café"), cborNull()])], + [3, cborArray([cborNull(), cborNull()])], + [4, cborNull()], + [5, cborArray([cborNull()])], + [6, cborArray([cborArray([])])], + [7, maskWord(0)], + [8, cborArray([cborNull()])], + [9, maskWord(0)], + ]); + expect(failure(fixture({ tail: badTrailer }))).toMatch( + /propertyTable must be bytewise-sorted/u, + ); + }); + + it("rejects a property index outside the intern table", () => { + const badTrailer = cborMap([ + [0, cborArray([cborTstr("https://t.test/authored/v/1")])], + [1, cborArray([cborTstr("https://x.test/only/")])], + [2, cborArray([cborTstr("Café"), cborNull()])], + [3, cborArray([cborNull(), cborNull()])], + [4, cborMap([[5, cborTstr("oops")]])], + [5, cborArray([cborNull()])], + [6, cborArray([cborArray([])])], + [7, maskWord(0)], + [8, cborArray([cborNull()])], + [9, maskWord(0)], + ]); + expect(failure(fixture({ tail: badTrailer }))).toMatch( + /outside the intern table/u, + ); + }); + + it("rejects a bitmask of the wrong extent", () => { + const badTrailer = cborMap([ + [0, cborArray([cborTstr("https://t.test/authored/v/1")])], + [1, cborArray([cborTstr("https://x.test/only/")])], + [2, cborArray([cborTstr("Café"), cborNull()])], + [3, cborArray([cborNull(), cborNull()])], + [4, cborNull()], + [5, cborArray([cborNull()])], + [6, cborArray([cborArray([])])], + [7, cborBstr([0, 0])], + [8, cborArray([cborNull()])], + [9, maskWord(0)], + ]); + expect(failure(fixture({ tail: badTrailer }))).toMatch( + /linkTypeIdsComplete is 2 bytes; 8 required/u, + ); + }); +}); diff --git a/apps/hash-frontend/src/components/tiled-network-graph/atlas-decode/locate.ts b/apps/hash-frontend/src/components/tiled-network-graph/atlas-decode/locate.ts new file mode 100644 index 00000000000..30aa9cf8d41 --- /dev/null +++ b/apps/hash-frontend/src/components/tiled-network-graph/atlas-decode/locate.ts @@ -0,0 +1,540 @@ +/** + * Locate-kind decoder for the SALTILE wire (normative contract: + * `libs/@local/graph/atlas/docs/wire.md` sections 3, 4, 5, and 8). + * Layers over the envelope reader like {@link decodeSaltileTile}/ + * {@link decodeSaltileEdges}: validates the HEAD schema and its echo + * of the request, exposes the node/edge columns as typed-array views + * over the response buffer (never copied), and parses the trailer. + * + * A locate response answers one entity's ego-graph: the source is + * delivered first, then the delivered edges' partners (both + * directions, ascending wire row id), with every edge incident to the + * source riding the edge columns in ascending `EDGE_IDS` bytes - the + * link entity's 32-byte identity IS the edge's identity; edges carry + * no wire id. The HEAD carries the source's upstream entity id (the + * by-row flow's identity answer), its first visible zoom and tile + * (the fly-to target), and the two completeness verdicts over the + * source's types and properties. + * + * Locate is the detail view: the trailer is ALWAYS present. Its two + * intern tables (`typeTable`, `propertyTable`) come first, and every + * type or property reference in the node and link arrays is a uint + * index into its table; this decoder validates the intern laws + * (bytewise-sorted, deduplicated, indexes in range, property keys + * ascending) and returns resolved URL-keyed objects. + */ + +import { decodeCbor, type CborValue } from "./cbor"; +import { + ENTITY_ID_BYTES, + expectEqual, + fail, + formatEntityId, + GENERATION_BYTES, + isCborArray, + isCborMap, + isNullableStringArray, + isUint, + readBitmask, + readEntityIdColumn, + readInternTable, + requireBool, + requireGenerationEcho, + requireSlot, + requireUint, +} from "./schema"; +import { LocateSlot, readEnvelope } from "./wire"; + +import type { + BaseUrl, + EntityId, + VersionedUrl, +} from "@blockprotocol/type-system"; + +/** The request context a locate response must echo. */ +export interface SaltileLocateRequest { + /** Generation identity from the manifest, 32 raw bytes. */ + readonly generation: Uint8Array; + readonly variant: number; + /** Count of colored type ids sent; 0 means TYPE_MASK is absent. */ + readonly coloredTypeIdCount: number; +} + +/** One simple property value; nested shapes never ship. */ +export type SaltilePropertyValue = string | number | boolean | null; + +/** A property map resolved through the intern table, keyed by base URL. */ +export type SaltileProperties = Readonly>; + +/** Node and link detail from the always-present trailer tail. */ +export interface SaltileLocateDetail { + /** The type intern table: every referenced versioned type URL once. */ + readonly typeTable: readonly VersionedUrl[]; + /** The property intern table: every surviving property base URL once. */ + readonly propertyTable: readonly BaseUrl[]; + /** Node labels, delivered order. */ + readonly labels: readonly (string | null)[]; + /** + * Each node's first direct type as a versioned URL, delivered order. + * + * `null` marks a node the store no longer serves or records no types + * for. + */ + readonly typeIds: readonly (VersionedUrl | null)[]; + /** + * The source's capped simple properties. + * + * `null` marks a store-absent source. Neighbour nodes ship no + * properties; their detail is one locate away. + */ + readonly properties: SaltileProperties | null; + /** Link-entity labels, edge order. */ + readonly linkLabels: readonly (string | null)[]; + /** + * Each link's direct types as versioned URLs, edge order. + * + * Canonical order is preserved and the list is capped; an empty list + * marks a store-absent link. + */ + readonly linkTypeIds: readonly (readonly VersionedUrl[])[]; + /** Bit e = edge e's type list is the link's whole direct set. */ + readonly linkTypeIdsComplete: readonly boolean[]; + /** Per-link capped simple properties; `null` = store-absent link. */ + readonly linkProperties: readonly (SaltileProperties | null)[]; + /** Bit e = edge e's property map is the link entity's whole set. */ + readonly linkPropertiesComplete: readonly boolean[]; +} + +/** The source's first visible zoom and its tile there - the fly-to target. */ +export interface SaltileLocateCell { + readonly z: number; + readonly x: number; + readonly y: number; +} + +/** One decoded locate response; typed arrays are views, not copies. */ +export interface DecodedSaltileLocate { + /** Delivered node count; index 0 is the SOURCE. */ + readonly count: number; + /** The source's first visible zoom. */ + readonly zoom: number; + /** The source's tile at that zoom - the fly-to target. */ + readonly cell: SaltileLocateCell; + /** Delivered edge count. */ + readonly edgesCount: number; + /** False when the locate edge cap truncated the subgraph. */ + readonly complete: boolean; + /** The source's upstream entity identity. */ + readonly entityId: EntityId; + /** The request's coloredTypeIds cover every direct type of the source. */ + readonly typeIdsComplete: boolean; + /** The trailer's source property map is the entity's whole set. */ + readonly propertiesComplete: boolean; + /** Wire-frame xy pairs, delivered order (source first). */ + readonly positions: Float32Array; + /** Node row ids, delivered order (source first). */ + readonly rowIds: Uint32Array; + /** + * Per-node type masks, `ceil(coloredTypeIds / 8)` bytes each, + * LSB-first; null when the request carried no coloredTypeIds. + */ + readonly typeMask: Uint8Array | null; + /** Source node row ids, edge order. */ + readonly sources: Uint32Array; + /** Target node row ids, edge order. */ + readonly targets: Uint32Array; + /** Link-entity identities, edge order (ascending identity bytes). */ + readonly edgeIds: readonly EntityId[]; + /** Trailer detail; locate is the detail view, always present. */ + readonly detail: SaltileLocateDetail; +} + +/** HEAD keys of the locate schema (wire.md section 8). */ +const locateHeadKeys = { + generation: 0, + variant: 1, + count: 2, + zoom: 3, + cell: 4, + edges: 5, + complete: 6, + entityId: 7, + typeIdsComplete: 8, + propertiesComplete: 9, +} as const; + +const knownHeadKeys = new Set(Object.values(locateHeadKeys)); + +/** TRAILER keys of the locate schema (wire.md section 8). */ +const trailerKeys = { + typeTable: 0, + propertyTable: 1, + labels: 2, + typeIds: 3, + properties: 4, + linkLabels: 5, + linkTypeIds: 6, + linkTypeIdsComplete: 7, + linkProperties: 8, + linkPropertiesComplete: 9, +} as const; + +const knownTrailerKeys = new Set(Object.values(trailerKeys)); + +const isPropertyValue = ( + value: CborValue | undefined, +): value is SaltilePropertyValue => + value === null || + typeof value === "string" || + typeof value === "number" || + typeof value === "boolean"; + +const nullableStrings = ( + map: ReadonlyMap, + key: number, + name: string, + count: number, + offset: number, +): readonly (string | null)[] => { + const value = map.get(key); + if (!isNullableStringArray(value)) { + return fail(`TRAILER ${name} entries must be strings or null`, offset); + } + if (value.length !== count) { + return fail(`TRAILER ${name} must carry exactly ${count} entries`, offset); + } + return value; +}; + +/** + * Resolves one wire property map (propertyTable index -> simple value, + * keys ascending) into a base-URL-keyed object; `null` passes through. + */ +const resolveProperties = ( + entry: CborValue | undefined, + table: readonly BaseUrl[], + name: string, + offset: number, +): SaltileProperties | null => { + if (entry === null || entry === undefined) { + return null; + } + if (!isCborMap(entry)) { + return fail(`${name} must be a map or null`, offset); + } + const resolved: Record = {}; + let previous = -1; + for (const [index, value] of entry) { + if (!isUint(index) || index >= table.length) { + return fail( + `${name} index ${index} lies outside the intern table`, + offset, + ); + } + if (index <= previous) { + return fail(`${name} keys must ascend`, offset); + } + previous = index; + if (!isPropertyValue(value)) { + return fail(`${name} values are simple only`, offset); + } + resolved[table[index]!] = value; + } + return resolved; +}; + +const readDetail = ( + payload: Uint8Array, + nodes: number, + edges: number, + offset: number, +): SaltileLocateDetail => { + const value = decodeCbor(payload); + if (!isCborMap(value)) { + return fail("TRAILER must be a map", offset); + } + for (const key of value.keys()) { + if (!knownTrailerKeys.has(key)) { + return fail(`TRAILER carries unknown key ${key}`, offset); + } + } + + // The wire contract pins typeTable entries as versioned type URLs and + // propertyTable entries as property base URLs. + const typeTable = readInternTable( + value.get(trailerKeys.typeTable), + "typeTable", + offset, + ) as readonly VersionedUrl[]; + const propertyTable = readInternTable( + value.get(trailerKeys.propertyTable), + "propertyTable", + offset, + ) as readonly BaseUrl[]; + + const rawTypeIds = value.get(trailerKeys.typeIds); + if (!isCborArray(rawTypeIds) || rawTypeIds.length !== nodes) { + return fail(`TRAILER typeIds must carry exactly ${nodes} entries`, offset); + } + const typeIds = rawTypeIds.map((entry) => { + if (entry === null) { + return null; + } + if (!isUint(entry) || entry >= typeTable.length) { + return fail( + // eslint-disable-next-line @typescript-eslint/no-base-to-string -- diagnostic for a malformed, possibly non-numeric CBOR entry + `typeIds entry ${String(entry)} lies outside the intern table`, + offset, + ); + } + return typeTable[entry]!; + }); + + const rawLinkTypeIds = value.get(trailerKeys.linkTypeIds); + if (!isCborArray(rawLinkTypeIds) || rawLinkTypeIds.length !== edges) { + return fail( + `TRAILER linkTypeIds must carry exactly ${edges} entries`, + offset, + ); + } + const linkTypeIds = rawLinkTypeIds.map((entry) => { + if (!isCborArray(entry)) { + return fail("a linkTypeIds entry must be an index array", offset); + } + return entry.map((index) => { + if (!isUint(index) || index >= typeTable.length) { + return fail( + // eslint-disable-next-line @typescript-eslint/no-base-to-string -- diagnostic for a malformed, possibly non-numeric CBOR index + `linkTypeIds index ${String(index)} lies outside the intern table`, + offset, + ); + } + return typeTable[index]!; + }); + }); + + const rawLinkProperties = value.get(trailerKeys.linkProperties); + if (!isCborArray(rawLinkProperties) || rawLinkProperties.length !== edges) { + return fail( + `TRAILER linkProperties must carry exactly ${edges} entries`, + offset, + ); + } + const linkProperties = rawLinkProperties.map((entry) => + resolveProperties(entry, propertyTable, "a linkProperties entry", offset), + ); + + return { + typeTable, + propertyTable, + labels: nullableStrings(value, trailerKeys.labels, "labels", nodes, offset), + typeIds, + properties: resolveProperties( + value.has(trailerKeys.properties) + ? value.get(trailerKeys.properties) + : fail("TRAILER properties (key 4) is required", offset), + propertyTable, + "properties", + offset, + ), + linkLabels: nullableStrings( + value, + trailerKeys.linkLabels, + "linkLabels", + edges, + offset, + ), + linkTypeIds, + linkTypeIdsComplete: readBitmask( + value.get(trailerKeys.linkTypeIdsComplete), + edges, + "linkTypeIdsComplete", + offset, + ), + linkProperties, + linkPropertiesComplete: readBitmask( + value.get(trailerKeys.linkPropertiesComplete), + edges, + "linkPropertiesComplete", + offset, + ), + }; +}; + +/** + * Decodes one locate response against the request that produced it. + * + * @throws {@link SaltileWireError} on envelope, schema, echo, or count + * violations; `SaltileCborError` on CBOR profile violations. + */ +export const decodeSaltileLocate = ( + buffer: ArrayBuffer, + request: SaltileLocateRequest, +): DecodedSaltileLocate => { + if (request.generation.length !== GENERATION_BYTES) { + fail( + `request generation is ${request.generation.length} bytes; expected ${GENERATION_BYTES}`, + 0, + ); + } + + const envelope = readEnvelope(buffer, "locate"); + const bytes = new Uint8Array(buffer); + + const headSlot = envelope.slots[LocateSlot.Head]!; + const headOffset = headSlot.start; + const head = decodeCbor(bytes.subarray(headSlot.start, headSlot.end)); + if (!isCborMap(head)) { + return fail("HEAD must be a map", headOffset); + } + for (const key of head.keys()) { + if (!knownHeadKeys.has(key)) { + return fail(`HEAD carries unknown key ${key}`, headOffset); + } + } + + requireGenerationEcho(head, request.generation, headOffset); + expectEqual( + requireUint(head, locateHeadKeys.variant, "variant", headOffset), + request.variant, + "variant", + headOffset, + ); + + const count = requireUint(head, locateHeadKeys.count, "count", headOffset); + const zoom = requireUint(head, locateHeadKeys.zoom, "zoom", headOffset); + const rawCell = head.get(locateHeadKeys.cell); + if (!isCborArray(rawCell) || rawCell.length !== 3) { + return fail("HEAD cell must be [z, x, y]", headOffset); + } + const [z, x, y] = rawCell; + if (!isUint(z) || !isUint(x) || !isUint(y) || z !== zoom) { + return fail("HEAD cell must carry uints with z equal to zoom", headOffset); + } + const edgesCount = requireUint( + head, + locateHeadKeys.edges, + "edges", + headOffset, + ); + const complete = requireBool( + head, + locateHeadKeys.complete, + "complete", + headOffset, + ); + + const rawEntityId = head.get(locateHeadKeys.entityId); + if ( + !(rawEntityId instanceof Uint8Array) || + rawEntityId.length !== ENTITY_ID_BYTES + ) { + return fail( + "HEAD entityId (key 7) must be a 32-byte identity record", + headOffset, + ); + } + const typeIdsComplete = requireBool( + head, + locateHeadKeys.typeIdsComplete, + "typeIdsComplete", + headOffset, + ); + const propertiesComplete = requireBool( + head, + locateHeadKeys.propertiesComplete, + "propertiesComplete", + headOffset, + ); + + const positionsSlot = requireSlot( + envelope.slots, + LocateSlot.Positions, + "POSITIONS", + count * 8, + buffer.byteLength, + ); + const rowIdsSlot = requireSlot( + envelope.slots, + LocateSlot.RowIds, + "ROW_IDS", + count * 4, + buffer.byteLength, + ); + + const stride = Math.ceil(request.coloredTypeIdCount / 8); + let typeMask: Uint8Array | null = null; + if (request.coloredTypeIdCount > 0) { + const maskSlot = requireSlot( + envelope.slots, + LocateSlot.TypeMask, + "TYPE_MASK", + count * stride, + buffer.byteLength, + ); + typeMask = bytes.subarray(maskSlot.start, maskSlot.end); + } else if (envelope.slots[LocateSlot.TypeMask] !== null) { + return fail("TYPE_MASK must be absent without coloredTypeIds", headOffset); + } + + const sourcesSlot = requireSlot( + envelope.slots, + LocateSlot.EdgeSources, + "EDGE_SOURCES", + edgesCount * 4, + buffer.byteLength, + ); + const targetsSlot = requireSlot( + envelope.slots, + LocateSlot.EdgeTargets, + "EDGE_TARGETS", + edgesCount * 4, + buffer.byteLength, + ); + const edgeIdsSlot = requireSlot( + envelope.slots, + LocateSlot.EdgeIds, + "EDGE_IDS", + edgesCount * ENTITY_ID_BYTES, + buffer.byteLength, + ); + const edgeIdRecords = readEntityIdColumn( + bytes.subarray(edgeIdsSlot.start, edgeIdsSlot.end), + edgesCount, + "EDGE_IDS", + edgeIdsSlot.start, + ); + + // Locate is the detail view: the trailer is always present (wire.md + // section 8) - no HEAD key declares it. + if (envelope.tailOffset >= buffer.byteLength) { + return fail( + "locate requires a trailer tail; the response carries none", + envelope.tailOffset, + ); + } + const detail = readDetail( + bytes.subarray(envelope.tailOffset), + count, + edgesCount, + envelope.tailOffset, + ); + + return { + count, + zoom, + cell: { z, x, y }, + edgesCount, + complete, + entityId: formatEntityId(rawEntityId), + typeIdsComplete, + propertiesComplete, + positions: new Float32Array(buffer, positionsSlot.start, count * 2), + rowIds: new Uint32Array(buffer, rowIdsSlot.start, count), + typeMask, + sources: new Uint32Array(buffer, sourcesSlot.start, edgesCount), + targets: new Uint32Array(buffer, targetsSlot.start, edgesCount), + edgeIds: edgeIdRecords.map((record) => formatEntityId(record)), + detail, + }; +}; diff --git a/apps/hash-frontend/src/components/tiled-network-graph/atlas-decode/manifest.test.ts b/apps/hash-frontend/src/components/tiled-network-graph/atlas-decode/manifest.test.ts new file mode 100644 index 00000000000..4b1f466a304 --- /dev/null +++ b/apps/hash-frontend/src/components/tiled-network-graph/atlas-decode/manifest.test.ts @@ -0,0 +1,136 @@ +import { describe, expect, it } from "vitest"; + +import { + AtlasContractError, + generationBytes, + parseCurrent, + parseManifest, +} from "./manifest"; + +const generationHex = Array.from({ length: 32 }, (_, index) => + index.toString(16).padStart(2, "0"), +).join(""); + +const manifestBody = { + generation: generationHex, + wireVersion: 1, + variants: ["plain"], + bucketSchedule: { span: 64, cut: "z+6", maxZoom: 16 }, + scopeSchedule: { k: 0, cut: "z+6" }, + limits: { + coloredTypeIds: 8, + edgesTiles: 32, + locateEdges: 512, + locateProperties: 20, + locateLinkTypeIds: 5, + locateLinkProperties: 10, + }, + createdAt: "2026-07-19T16:00:00Z", +}; + +describe("generationBytes", () => { + it("decodes 64 hex characters into 32 raw bytes", () => { + const bytes = generationBytes(generationHex); + expect(bytes).toHaveLength(32); + expect([...bytes.slice(0, 4)]).toEqual([0, 1, 2, 3]); + }); + + it("rejects a non-hex identity", () => { + expect(() => generationBytes("not-hex")).toThrow(AtlasContractError); + }); +}); + +describe("parseCurrent", () => { + it("returns the active generation", () => { + expect(parseCurrent({ generation: generationHex })).toEqual({ + generation: generationHex, + }); + }); + + it("rejects a missing, non-string, or malformed generation", () => { + expect(() => parseCurrent(null)).toThrow(AtlasContractError); + expect(() => parseCurrent({})).toThrow(AtlasContractError); + expect(() => parseCurrent({ generation: 5 })).toThrow(AtlasContractError); + expect(() => parseCurrent({ generation: "abc" })).toThrow( + AtlasContractError, + ); + }); +}); + +describe("parseManifest", () => { + it("parses a well-formed manifest", () => { + const manifest = parseManifest(manifestBody, generationHex); + expect(manifest.variants).toEqual(["plain"]); + expect(manifest.bucketSchedule.span).toBe(64); + expect(manifest.bucketSchedule.maxZoom).toBe(16); + expect(manifest.limits.coloredTypeIds).toBe(8); + expect(manifest.limits.locateEdges).toBe(512); + expect(manifest.limits.locateProperties).toBe(20); + expect(manifest.limits.locateLinkTypeIds).toBe(5); + expect(manifest.limits.locateLinkProperties).toBe(10); + expect(manifest.scopeSchedule.k).toBe(0); + expect(manifest.scopeSchedule.cut).toBe("z+6"); + expect(manifest.createdAt).toBe("2026-07-19T16:00:00Z"); + }); + + it("carries a restricted caller's own delivery offset", () => { + const manifest = parseManifest( + { ...manifestBody, scopeSchedule: { k: 2, cut: "z+8" } }, + generationHex, + ); + expect(manifest.scopeSchedule.k).toBe(2); + }); + + it("requires the scopeSchedule block, because a missing k reads as zero", () => { + const { scopeSchedule: _omitted, ...withoutScope } = manifestBody; + expect(() => parseManifest(withoutScope, generationHex)).toThrow( + /scopeSchedule/u, + ); + expect(() => + parseManifest( + { ...manifestBody, scopeSchedule: { k: -1, cut: "z+6" } }, + generationHex, + ), + ).toThrow(/scopeSchedule k\/cut/u); + }); + + it("accepts an absent createdAt, which a fixture-fitted generation omits", () => { + const { createdAt: _omitted, ...withoutCreatedAt } = manifestBody; + expect(parseManifest(withoutCreatedAt, generationHex).createdAt).toBe( + undefined, + ); + expect(() => + parseManifest({ ...manifestBody, createdAt: 7 }, generationHex), + ).toThrow(/createdAt/u); + }); + + it("requires the generation to echo the route", () => { + expect(() => parseManifest(manifestBody, "f".repeat(64))).toThrow( + /does not echo the route/u, + ); + }); + + it("requires the bucket-schedule span to be a power of two", () => { + expect(() => + parseManifest( + { + ...manifestBody, + bucketSchedule: { span: 63, cut: "z+6", maxZoom: 16 }, + }, + generationHex, + ), + ).toThrow(/power of two/u); + }); + + it("requires a non-empty variant set", () => { + expect(() => + parseManifest({ ...manifestBody, variants: [] }, generationHex), + ).toThrow(/variants/u); + }); + + it("rejects a non-object body", () => { + expect(() => parseManifest(null, generationHex)).toThrow( + AtlasContractError, + ); + }); +}); diff --git a/apps/hash-frontend/src/components/tiled-network-graph/atlas-decode/manifest.ts b/apps/hash-frontend/src/components/tiled-network-graph/atlas-decode/manifest.ts new file mode 100644 index 00000000000..0e5350d2f6c --- /dev/null +++ b/apps/hash-frontend/src/components/tiled-network-graph/atlas-decode/manifest.ts @@ -0,0 +1,206 @@ +/** + * Parsers for the two JSON reads that bootstrap a SALTILE session + * (normative contract: `SPEC-ADDENDUM-API.md` "Surface v1"): + * `GET /v1/atlas/current`, the one mutable read naming the active + * generation, and the immutable per-generation manifest carrying the + * canonical variant set, bucket schedule, and request caps. + * + * Both validate the schema and throw {@link AtlasContractError} on any + * violation. The transport itself lives in the fetcher; these are pure + * functions over already-parsed JSON. + */ + +import { GENERATION_BYTES } from "./schema"; + +/** Response of `GET /v1/atlas/current` — the one mutable read. */ +export interface AtlasCurrent { + /** Active generation identity, 64 hex characters. */ + readonly generation: string; +} + +/** Request caps served as data (never synchronized constants). */ +export interface AtlasLimits { + readonly coloredTypeIds: number; + readonly edgesTiles: number; + /** Most ego-graph edges one locate response delivers. */ + readonly locateEdges: number; + /** Most properties one located entity ships in its trailer map. */ + readonly locateProperties: number; + /** Most type ids one located link lists. */ + readonly locateLinkTypeIds: number; + /** Most properties one located link ships. */ + readonly locateLinkProperties: number; +} + +/** Immutable per-generation manifest. */ +export interface AtlasManifest { + readonly generation: string; + readonly wireVersion: number; + readonly variants: readonly string[]; + readonly bucketSchedule: { + readonly span: number; + readonly cut: string; + readonly maxZoom: number; + }; + /** + * This caller's resolved delivery schedule; see `wire.md`'s runs contract. + * + * `k` is the delivery-cut offset the authority token seals, so the cut + * this caller is served at is `z + log2(bucketSchedule.span) + k`. A + * full-visibility caller reads `k = 0`; a restricted one reads its own + * offset, and the server publishes the resulting rule in `cut`. + */ + readonly scopeSchedule: { + readonly k: number; + readonly cut: string; + }; + readonly limits: AtlasLimits; + /** + * Absent for a generation fitted from sources without temporal axes, + * such as a synthetic fixture: the server omits the key entirely + * (`skip_serializing_if` on `Option`), so requiring it refused + * those generations at parse. + */ + readonly createdAt?: string; +} + +/** A JSON response violated the Surface v1 schema. */ +export class AtlasContractError extends Error { + override readonly name = "AtlasContractError"; +} + +const contractFail = (detail: string): never => { + throw new AtlasContractError(detail); +}; + +const hexPattern = /^[0-9a-f]{64}$/u; + +/** Decodes a 64-hex generation identity into its 32 raw bytes. */ +export const generationBytes = (hex: string): Uint8Array => { + if (!hexPattern.test(hex)) { + contractFail(`generation is not 64 lowercase hex characters: ${hex}`); + } + const bytes = new Uint8Array(GENERATION_BYTES); + for (let index = 0; index < GENERATION_BYTES; index += 1) { + bytes[index] = Number.parseInt(hex.slice(index * 2, index * 2 + 2), 16); + } + return bytes; +}; + +const isUintValue = (value: unknown): value is number => + typeof value === "number" && Number.isInteger(value) && value >= 0; + +// Index-signature access with a variable key (host idiom: satisfies +// `dot-notation` and `noPropertyAccessFromIndexSignature` together). +const field = (record: Record, key: string): unknown => + record[key]; + +/** Parses `GET /v1/atlas/current`, validating the generation identity. */ +export const parseCurrent = (value: unknown): AtlasCurrent => { + if (typeof value !== "object" || value === null) { + return contractFail("current is not an object"); + } + const generation = field(value as Record, "generation"); + if (typeof generation !== "string") { + return contractFail("current.generation must be a string"); + } + // Validates the 64-hex shape; the bytes it returns are discarded here. + generationBytes(generation); + return { generation }; +}; + +/** Parses a manifest body, checking it echoes the route's `generation`. */ +export const parseManifest = ( + value: unknown, + generation: string, +): AtlasManifest => { + if (typeof value !== "object" || value === null) { + return contractFail("manifest is not an object"); + } + const manifest = value as Record; + if (field(manifest, "generation") !== generation) { + return contractFail("manifest generation does not echo the route"); + } + const wireVersion = field(manifest, "wireVersion"); + if (!isUintValue(wireVersion)) { + return contractFail("manifest wireVersion must be an unsigned integer"); + } + const variants = field(manifest, "variants"); + if ( + !Array.isArray(variants) || + variants.length === 0 || + !variants.every((entry) => typeof entry === "string") + ) { + return contractFail("manifest variants must be a non-empty string array"); + } + const schedule = field(manifest, "bucketSchedule") as Record< + string, + unknown + > | null; + if (typeof schedule !== "object" || schedule === null) { + return contractFail("manifest bucketSchedule is not an object"); + } + const span = field(schedule, "span"); + const cut = field(schedule, "cut"); + const maxZoom = field(schedule, "maxZoom"); + if (!isUintValue(span) || !Number.isInteger(Math.log2(span))) { + return contractFail( + `bucketSchedule span must be a power of two (the per-tile grid width); got ${String(span)}`, + ); + } + if (typeof cut !== "string" || !isUintValue(maxZoom)) { + return contractFail("bucketSchedule cut/maxZoom are malformed"); + } + const scopeSchedule = field(manifest, "scopeSchedule") as Record< + string, + unknown + > | null; + if (typeof scopeSchedule !== "object" || scopeSchedule === null) { + return contractFail("manifest scopeSchedule is not an object"); + } + const scopeOffset = field(scopeSchedule, "k"); + const scopeCut = field(scopeSchedule, "cut"); + if (!isUintValue(scopeOffset) || typeof scopeCut !== "string") { + return contractFail("scopeSchedule k/cut are malformed"); + } + const limits = field(manifest, "limits") as Record | null; + if (typeof limits !== "object" || limits === null) { + return contractFail("manifest limits are malformed"); + } + const coloredTypeIds = field(limits, "coloredTypeIds"); + const edgesTiles = field(limits, "edgesTiles"); + const locateEdges = field(limits, "locateEdges"); + const locateProperties = field(limits, "locateProperties"); + const locateLinkTypeIds = field(limits, "locateLinkTypeIds"); + const locateLinkProperties = field(limits, "locateLinkProperties"); + if ( + !isUintValue(coloredTypeIds) || + !isUintValue(edgesTiles) || + !isUintValue(locateEdges) || + !isUintValue(locateProperties) || + !isUintValue(locateLinkTypeIds) || + !isUintValue(locateLinkProperties) + ) { + return contractFail("manifest limits are malformed"); + } + const createdAt = field(manifest, "createdAt"); + if (createdAt !== undefined && typeof createdAt !== "string") { + return contractFail("manifest createdAt must be an ISO-8601 string"); + } + return { + generation, + wireVersion, + variants, + bucketSchedule: { span, cut, maxZoom }, + scopeSchedule: { k: scopeOffset, cut: scopeCut }, + limits: { + coloredTypeIds, + edgesTiles, + locateEdges, + locateProperties, + locateLinkTypeIds, + locateLinkProperties, + }, + createdAt, + }; +}; diff --git a/apps/hash-frontend/src/components/tiled-network-graph/atlas-decode/schema.ts b/apps/hash-frontend/src/components/tiled-network-graph/atlas-decode/schema.ts new file mode 100644 index 00000000000..9bee569c7e7 --- /dev/null +++ b/apps/hash-frontend/src/components/tiled-network-graph/atlas-decode/schema.ts @@ -0,0 +1,252 @@ +/** + * Shared schema-validation helpers for the per-kind SALTILE decoders: + * typed guards over decoded CBOR values (the type-aware lint requires + * guards - `instanceof Map` and `Array.isArray` both narrow to `any` + * containers), request-echo assertions, and slot-extent checks. + */ + +import { SaltileWireError, type SaltileSlot } from "./wire"; + +import type { CborValue } from "./cbor"; +import type { EntityId } from "@blockprotocol/type-system"; + +/** Length of a generation identity in bytes (sha256). */ +export const GENERATION_BYTES = 32; + +/** Length of an entity identity in bytes: web uuid then entity uuid. */ +export const ENTITY_ID_BYTES = 32; + +export const fail = (detail: string, offset: number): never => { + throw new SaltileWireError(detail, offset); +}; + +export const isUint = (value: CborValue | undefined): value is number => + typeof value === "number" && Number.isInteger(value) && value >= 0; + +export const isCborArray = ( + value: CborValue | undefined, +): value is readonly CborValue[] => Array.isArray(value); + +export const isCborMap = ( + value: CborValue | undefined, +): value is ReadonlyMap => value instanceof Map; + +export const isUintArray = ( + value: CborValue | undefined, +): value is readonly number[] => + isCborArray(value) && value.every((entry) => isUint(entry)); + +export const isNumberArray = ( + value: CborValue | undefined, +): value is readonly number[] => + isCborArray(value) && value.every((entry) => typeof entry === "number"); + +export const isNullableStringArray = ( + value: CborValue | undefined, +): value is readonly (string | null)[] => + isCborArray(value) && + value.every((entry) => entry === null || typeof entry === "string"); + +export const requireUint = ( + map: ReadonlyMap, + key: number, + name: string, + offset: number, +): number => { + const value = map.get(key); + if (!isUint(value)) { + return fail( + `HEAD ${name} (key ${key}) must be an unsigned integer`, + offset, + ); + } + return value; +}; + +export const requireBool = ( + map: ReadonlyMap, + key: number, + name: string, + offset: number, +): boolean => { + const value = map.get(key); + if (typeof value !== "boolean") { + return fail(`HEAD ${name} (key ${key}) must be a boolean`, offset); + } + return value; +}; + +export const expectEqual = ( + actual: number, + expected: number, + name: string, + offset: number, +): void => { + if (actual !== expected) { + fail(`HEAD ${name} is ${actual}; the request expects ${expected}`, offset); + } +}; + +export const bytesEqual = (left: Uint8Array, right: Uint8Array): boolean => + left.length === right.length && + left.every((byte, index) => byte === right[index]); + +/** Validates the 32-byte generation echo against the request's. */ +export const requireGenerationEcho = ( + map: ReadonlyMap, + expected: Uint8Array, + offset: number, +): void => { + const generation = map.get(0); + if ( + !(generation instanceof Uint8Array) || + generation.length !== GENERATION_BYTES + ) { + fail("HEAD generation must be a 32-byte identity", offset); + } + if (!bytesEqual(generation as Uint8Array, expected)) { + fail("HEAD generation does not match the request", offset); + } +}; + +/** Strict bytewise less-than over two byte strings. */ +export const bytewiseLess = (left: Uint8Array, right: Uint8Array): boolean => { + const shared = Math.min(left.length, right.length); + for (let index = 0; index < shared; index += 1) { + if (left[index] !== right[index]) { + return left[index]! < right[index]!; + } + } + return left.length < right.length; +}; + +const hexOf = (bytes: Uint8Array): string => + [...bytes].map((byte) => byte.toString(16).padStart(2, "0")).join(""); + +const uuidOf = (bytes: Uint8Array): string => { + const hex = hexOf(bytes); + return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`; +}; + +/** + * The [`EntityId`] of a 32-byte identity record. + * + * The record carries the web uuid then the entity uuid, sixteen bytes + * each; the result is the canonical `webUuid~entityUuid` form. + */ +export const formatEntityId = (record: Uint8Array): EntityId => + // The two uuids are hex-formatted straight from the record, so the + // result is canonical by construction. + `${uuidOf(record.subarray(0, 16))}~${uuidOf(record.subarray(16, 32))}` as EntityId; + +/** + * The per-edge 32-byte identity records of an `EDGE_IDS` column. + * + * Delivery order is ascending identity bytes; a column out of order is + * rejected. + */ +export const readEntityIdColumn = ( + column: Uint8Array, + count: number, + name: string, + offset: number, +): readonly Uint8Array[] => { + const records: Uint8Array[] = new Array(count); + for (let index = 0; index < count; index += 1) { + const record = column.subarray( + index * ENTITY_ID_BYTES, + (index + 1) * ENTITY_ID_BYTES, + ); + if (index > 0 && !bytewiseLess(records[index - 1]!, record)) { + return fail( + `${name} record ${index} is not in ascending identity order`, + offset + index * ENTITY_ID_BYTES, + ); + } + records[index] = record; + } + return records; +}; + +/** + * The per-edge booleans of an LSB-first completeness bitmask. + * + * The byte string carries one bit per edge in whole 8-byte words: + * exactly `ceil(count / 64) * 8` bytes, padding bits zero. + */ +export const readBitmask = ( + value: CborValue | undefined, + count: number, + name: string, + offset: number, +): readonly boolean[] => { + if (!(value instanceof Uint8Array)) { + return fail(`TRAILER ${name} must be a byte string`, offset); + } + const required = Math.ceil(count / 64) * 8; + if (value.length !== required) { + return fail( + `TRAILER ${name} is ${value.length} bytes; ${required} required for ${count} edges`, + offset, + ); + } + const bits: boolean[] = new Array(count); + for (let index = 0; index < count; index += 1) { + // eslint-disable-next-line no-bitwise -- reads bit `index` from the packed bitmap + bits[index] = (value[index >> 3]! & (1 << (index & 7))) !== 0; + } + return bits; +}; + +/** + * The entries of an intern table (`typeTable` / `propertyTable`). + * + * The table is a text array, bytewise-sorted and deduplicated over the + * UTF-8 encodings. + */ +export const readInternTable = ( + value: CborValue | undefined, + name: string, + offset: number, +): readonly string[] => { + if ( + !isCborArray(value) || + !value.every((entry) => typeof entry === "string") + ) { + return fail(`TRAILER ${name} must be a text array`, offset); + } + // The law is bytewise UTF-8 order; a plain `<` on JS strings compares + // UTF-16 code units, which diverges above U+FFFF. + const encoder = new TextEncoder(); + const encoded = value.map((entry) => encoder.encode(entry)); + for (let index = 1; index < encoded.length; index += 1) { + if (!bytewiseLess(encoded[index - 1]!, encoded[index]!)) { + return fail( + `TRAILER ${name} must be bytewise-sorted and deduplicated`, + offset, + ); + } + } + return value; +}; + +export const requireSlot = ( + slots: readonly (SaltileSlot | null)[], + slot: number, + name: string, + expectedLength: number, + responseLength: number, +): SaltileSlot => { + const extent = slots[slot] ?? null; + if (extent === null) { + return fail(`required slot ${slot} (${name}) is absent`, responseLength); + } + const length = extent.end - extent.start; + if (length !== expectedLength) { + return fail( + `slot ${slot} (${name}) is ${length} bytes; ${expectedLength} required`, + extent.start, + ); + } + return extent; +}; diff --git a/apps/hash-frontend/src/components/tiled-network-graph/atlas-decode/tile.test.ts b/apps/hash-frontend/src/components/tiled-network-graph/atlas-decode/tile.test.ts new file mode 100644 index 00000000000..20355302791 --- /dev/null +++ b/apps/hash-frontend/src/components/tiled-network-graph/atlas-decode/tile.test.ts @@ -0,0 +1,484 @@ +import { describe, expect, it } from "vitest"; + +import { decodeSaltileTile, type SaltileTileRequest } from "./tile"; +import { + DIRECTORY_ENTRY_BYTES, + PAYLOAD_ALIGNMENT, + PREFIX_BYTES, + SaltileMode, + SaltileWireError, +} from "./wire"; + +/* Minimal reference encoder for hand-built fixtures. CBOR integers and + * floats are big-endian per RFC 8949; envelope integers and column + * payloads are little-endian per the wire. */ + +const cborUint = (value: number, major = 0): number[] => { + const base = major * 32; + if (value < 24) { + return [base + value]; + } + if (value <= 0xff) { + return [base + 24, value]; + } + const wide = new DataView(new ArrayBuffer(2)); + wide.setUint16(0, value, false); + return [base + 25, ...new Uint8Array(wide.buffer)]; +}; + +const cborBstr = (bytes: number[]): number[] => [ + ...cborUint(bytes.length, 2), + ...bytes, +]; + +const cborTstr = (text: string): number[] => { + const encoded = [...new TextEncoder().encode(text)]; + return [...cborUint(encoded.length, 3), ...encoded]; +}; + +const cborArray = (entries: number[][]): number[] => [ + ...cborUint(entries.length, 4), + ...entries.flat(), +]; + +const cborMap = (entries: [number, number[]][]): number[] => [ + ...cborUint(entries.length, 5), + ...entries.flatMap(([key, value]) => [...cborUint(key), ...value]), +]; + +const cborNull = (): number[] => [0xf6]; +const cborBool = (value: boolean): number[] => [value ? 0xf5 : 0xf4]; + +const cborF32 = (value: number): number[] => { + const view = new DataView(new ArrayBuffer(4)); + view.setFloat32(0, value, false); + return [0xfa, ...new Uint8Array(view.buffer)]; +}; + +const f32le = (values: number[]): number[] => { + const view = new DataView(new ArrayBuffer(values.length * 4)); + for (const [index, value] of values.entries()) { + view.setFloat32(index * 4, value, true); + } + return [...new Uint8Array(view.buffer)]; +}; + +const u32le = (values: number[]): number[] => { + const view = new DataView(new ArrayBuffer(values.length * 4)); + for (const [index, value] of values.entries()) { + view.setUint32(index * 4, value, true); + } + return [...new Uint8Array(view.buffer)]; +}; + +const response = ( + kind: number, + payloads: (number[] | null)[], + tail: number[] = [], +): ArrayBuffer => { + const base = PREFIX_BYTES + payloads.length * DIRECTORY_ENTRY_BYTES; + const directory = new DataView( + new ArrayBuffer(payloads.length * DIRECTORY_ENTRY_BYTES), + ); + const body: number[] = []; + let cursor = base; + for (const [slot, payload] of payloads.entries()) { + if (payload === null) { + continue; + } + directory.setUint32(slot * DIRECTORY_ENTRY_BYTES, cursor, true); + directory.setUint32( + slot * DIRECTORY_ENTRY_BYTES + 4, + cursor + payload.length, + true, + ); + body.push(...payload); + cursor += payload.length; + while (cursor % PAYLOAD_ALIGNMENT !== 0) { + body.push(0); + cursor += 1; + } + } + const prefix = new DataView(new ArrayBuffer(PREFIX_BYTES)); + for (const [index, byte] of [ + 0x53, + 0x41, + 0x4c, + 0x54, + 0x49, + 0x4c, + 0x45, + kind, + ].entries()) { + prefix.setUint8(index, byte); + } + prefix.setUint16(8, 1, true); + prefix.setUint16(12, payloads.length, true); + return new Uint8Array([ + ...new Uint8Array(prefix.buffer), + ...new Uint8Array(directory.buffer), + ...body, + ...tail, + ]).buffer; +}; + +const generation = Array.from({ length: 32 }, (_, index) => index); +const positions = [0.25, -0.5, 0.125, 0.75, -1, 1]; +const rowIds = [7, 11, 13]; + +type HeadEntries = [number, number[]][]; + +const defaultHead = ( + overrides: Partial> = {}, +): HeadEntries => { + const entries = new Map([ + [0, cborBstr(generation)], + [1, cborUint(2)], + [2, cborArray([cborUint(3), cborUint(5), cborUint(1)])], + [3, cborUint(SaltileMode.Delta)], + [4, cborUint(3)], + [5, cborUint(40)], + [6, cborUint(9)], + [7, cborArray([cborUint(3)])], + [9, cborUint(5)], + [10, cborBool(false)], + ]); + for (const [key, value] of Object.entries(overrides)) { + entries.set(Number(key), value!); + } + return [...entries.entries()].sort(([left], [right]) => left - right); +}; + +interface TileFixture { + request?: Partial; + head?: HeadEntries; + payloads?: (number[] | null)[]; + tail?: number[]; +} + +/** A well-formed 3-point delta tile at z 3, overridable per case. */ +const fixture = ({ + request = {}, + head, + payloads, + tail = [], +}: TileFixture = {}) => { + const fullRequest: SaltileTileRequest = { + generation: new Uint8Array(generation), + variant: 2, + coordinate: { z: 3, x: 5, y: 1 }, + mode: SaltileMode.Delta, + deliverySpanLog2: 6, + coloredTypeIdCount: 0, + detail: "minimal", + ...request, + }; + const slots = payloads ?? [ + cborMap(head ?? defaultHead()), + f32le(positions), + u32le(rowIds), + null, + null, + ]; + if (payloads === undefined && head !== undefined) { + slots[0] = cborMap(head); + } + return { buffer: response(0x54, slots, tail), request: fullRequest }; +}; + +const failure = (input: { + buffer: ArrayBuffer; + request: SaltileTileRequest; +}): string => { + try { + decodeSaltileTile(input.buffer, input.request); + } catch (error) { + expect(error).toBeInstanceOf(SaltileWireError); + return (error as SaltileWireError).message; + } + throw new Error("expected the tile to be rejected"); +}; + +describe("decodeSaltileTile", () => { + it("decodes a delta tile into zero-copy views", () => { + const { buffer, request } = fixture(); + const tile = decodeSaltileTile(buffer, request); + + expect(tile.delivered).toBe(3); + expect(tile.visible).toBe(40); + expect(tile.firstBucket).toBe(9); + expect(tile.runs).toEqual([3]); + expect(tile.children).toBe(5); + expect([...tile.positions]).toEqual(positions); + expect([...tile.rowIds]).toEqual(rowIds); + expect(tile.positions.buffer).toBe(buffer); + expect(tile.rowIds.buffer).toBe(buffer); + expect(tile.typeMask).toBeNull(); + expect(tile.detail).toBeNull(); + expect(tile.global).toBeNull(); + }); + + it("decodes TYPE_MASK and the trailer tail when the request asks", () => { + const masks = [0b101, 0b000, 0b110]; + const { buffer, request } = fixture({ + request: { coloredTypeIdCount: 3, detail: "auxiliary" }, + payloads: [ + cborMap(defaultHead({ 10: cborBool(true) })), + f32le(positions), + u32le(rowIds), + masks, + null, + ], + tail: cborMap([ + [ + 0, + cborArray([ + cborTstr("Alpha \u{1f30d}"), + cborNull(), + cborTstr("Gamma"), + ]), + ], + [1, cborArray([cborNull(), cborNull(), cborTstr("rocket")])], + ]), + }); + const tile = decodeSaltileTile(buffer, request); + + expect([...tile.typeMask!]).toEqual(masks); + expect(tile.detail?.labels).toEqual(["Alpha \u{1f30d}", null, "Gamma"]); + expect(tile.detail?.icons).toEqual([null, null, "rocket"]); + }); + + it("refuses runs that do not sum to delivered, in either scope", () => { + // The identity is law in every response (wire.md), so a mismatch is a + // server defect and not the accepted hole an expired FIXME claimed. + // Both directions, because a sum that overshoots is as wrong as one + // that falls short and only one of them was ever exercised. + const short = fixture({ + head: defaultHead({ 7: cborArray([cborUint(2)]) }), + }); + expect(failure(short)).toMatch(/runs sum to 2; delivered is 3/u); + + const over = fixture({ + head: defaultHead({ 7: cborArray([cborUint(4)]) }), + }); + expect(failure(over)).toMatch(/runs sum to 4; delivered is 3/u); + }); + + it("counts the head from the delivery cut a restricted caller is served at", () => { + // k = 1, so the cut is z + m + k = 3 + 6 + 1 and the delta head's + // firstBucket is 10 rather than 9. Taking m alone - the corpus span + // exponent, which is what the session used before scopeSchedule was + // read - refuses this tile at the head, which is how every restricted + // caller was refused on its first tile. + const restricted = defaultHead({ 6: cborUint(10) }); + const accepted = fixture({ + request: { deliverySpanLog2: 7 }, + head: restricted, + }); + expect( + decodeSaltileTile(accepted.buffer, accepted.request).firstBucket, + ).toBe(10); + + const corpusSpanOnly = fixture({ + request: { deliverySpanLog2: 6 }, + head: restricted, + }); + expect(failure(corpusSpanOnly)).toMatch(/firstBucket/u); + }); + + it("expects one run per bucket to the restricted cut in total mode", () => { + // A total response carries z+m+k+1 entries with b0 = 0 (wire.md). + const totalHead = (entries: number) => + defaultHead({ + 3: cborUint(SaltileMode.Total), + 6: cborUint(0), + 7: cborArray( + Array.from({ length: entries }, (_, index) => + cborUint(index === 0 ? 3 : 0), + ), + ), + }); + const request = { mode: SaltileMode.Total, deliverySpanLog2: 7 } as const; + const accepted = fixture({ request, head: totalHead(3 + 7 + 1) }); + expect( + decodeSaltileTile(accepted.buffer, accepted.request).runs.length, + ).toBe(11); + + const corpusSpanOnly = fixture({ request, head: totalHead(3 + 6 + 1) }); + expect(failure(corpusSpanOnly)).toMatch(/runs length/u); + }); + + it("decodes the root tile with its required global bounds", () => { + const rootHead = defaultHead({ + 2: cborArray([cborUint(0), cborUint(0), cborUint(0)]), + 6: cborUint(0), + 7: cborArray([cborUint(1), cborUint(0), cborUint(2)]), + 8: cborMap([ + [0, cborUint(40)], + [ + 1, + cborArray([cborF32(-0.5), cborF32(-0.25), cborF32(0.75), cborF32(1)]), + ], + [2, cborUint(7)], + ]), + 9: cborUint(15), + }); + const { buffer, request } = fixture({ + request: { coordinate: { z: 0, x: 0, y: 0 }, deliverySpanLog2: 2 }, + head: rootHead, + }); + const tile = decodeSaltileTile(buffer, request); + expect(tile.runs).toEqual([1, 0, 2]); + expect(tile.global?.bounds).toEqual([-0.5, -0.25, 0.75, 1]); + expect(tile.global?.visibleAtZoom).toBe(40); + + const missingGlobal = fixture({ + request: { coordinate: { z: 0, x: 0, y: 0 }, deliverySpanLog2: 2 }, + head: defaultHead({ + 2: cborArray([cborUint(0), cborUint(0), cborUint(0)]), + 6: cborUint(0), + 7: cborArray([cborUint(1), cborUint(0), cborUint(2)]), + }), + }); + expect(failure(missingGlobal)).toMatch(/global is required on the root/u); + }); + + it("accepts absent bounds only for the empty visible set", () => { + const emptyGlobal = cborMap([ + [0, cborUint(0)], + [2, cborUint(0)], + ]); + const { buffer, request } = fixture({ + head: defaultHead({ 8: emptyGlobal }), + }); + expect(decodeSaltileTile(buffer, request).global?.bounds).toBeNull(); + + const nonEmptyNoBounds = fixture({ + head: defaultHead({ + 8: cborMap([ + [0, cborUint(12)], + [2, cborUint(0)], + ]), + }), + }); + expect(failure(nonEmptyNoBounds)).toMatch( + /bounds are absent but the visible set is not empty/u, + ); + }); + + it("ignores populated slots it does not consume", () => { + const { buffer, request } = fixture({ + payloads: [ + cborMap(defaultHead()), + f32le(positions), + u32le(rowIds), + null, + u32le([50, 12, 8]), // populated MASS, ignored by choice + [1, 2, 3, 4], // appended slot beyond the v1 table + ], + }); + const tile = decodeSaltileTile(buffer, request); + expect(tile.delivered).toBe(3); + expect([...tile.rowIds]).toEqual(rowIds); + }); + + it("decodes the empty tile with present-empty columns", () => { + const { buffer, request } = fixture({ + head: defaultHead({ + 4: cborUint(0), + 5: cborUint(0), + 7: cborArray([cborUint(0)]), + 9: cborUint(0), + }), + payloads: [ + cborMap( + defaultHead({ + 4: cborUint(0), + 5: cborUint(0), + 7: cborArray([cborUint(0)]), + 9: cborUint(0), + }), + ), + [], + [], + null, + null, + ], + }); + const tile = decodeSaltileTile(buffer, request); + expect(tile.delivered).toBe(0); + expect(tile.positions).toHaveLength(0); + expect(tile.children).toBe(0); + }); + + it("names every schema and echo rejection", () => { + const wrongGeneration = fixture({ + request: { generation: new Uint8Array(32).fill(9) }, + }); + expect(failure(wrongGeneration)).toMatch(/generation does not match/u); + + const wrongCoordinate = fixture({ + request: { coordinate: { z: 3, x: 5, y: 2 } }, + }); + expect(failure(wrongCoordinate)).toMatch(/HEAD y is 1/u); + + const wrongMode = fixture({ request: { mode: SaltileMode.Total } }); + expect(failure(wrongMode)).toMatch(/HEAD mode is 0/u); + + const badChildren = fixture({ + head: defaultHead({ 9: cborUint(16) }), + }); + expect(failure(badChildren)).toMatch(/children carries bits/u); + + const unknownKey = fixture({ + head: defaultHead({ 11: cborUint(1) }), + }); + expect(failure(unknownKey)).toMatch(/unknown key 11/u); + + const shortColumn = fixture({ + payloads: [ + cborMap(defaultHead()), + f32le(positions.slice(0, 4)), + u32le(rowIds), + null, + null, + ], + }); + expect(failure(shortColumn)).toMatch(/16 bytes; 24 required/u); + + const missingColumn = fixture({ + payloads: [cborMap(defaultHead()), f32le(positions), null, null, null], + }); + expect(failure(missingColumn)).toMatch(/slot 2 \(ROW_IDS\) is absent/u); + + const maskUnrequested = fixture({ + payloads: [ + cborMap(defaultHead()), + f32le(positions), + u32le(rowIds), + [1, 2, 3], + null, + ], + }); + expect(failure(maskUnrequested)).toMatch(/request sent no coloredTypeIds/u); + + const maskMissing = fixture({ request: { coloredTypeIdCount: 3 } }); + expect(failure(maskMissing)).toMatch(/slot 3 \(TYPE_MASK\) is absent/u); + + const trailerMissing = fixture({ + request: { detail: "auxiliary" }, + head: defaultHead({ 10: cborBool(true) }), + }); + expect(failure(trailerMissing)).toMatch(/carries no tail/u); + + const trailerDisagrees = fixture({ + request: { detail: "auxiliary" }, + }); + expect(failure(trailerDisagrees)).toMatch( + /HEAD trailer is false; the request's detail is "auxiliary"/u, + ); + + const undeclaredTail = fixture({ tail: [0xa0] }); + expect(failure(undeclaredTail)).toMatch(/HEAD declares no trailer/u); + }); +}); diff --git a/apps/hash-frontend/src/components/tiled-network-graph/atlas-decode/tile.ts b/apps/hash-frontend/src/components/tiled-network-graph/atlas-decode/tile.ts new file mode 100644 index 00000000000..a0e44cc178e --- /dev/null +++ b/apps/hash-frontend/src/components/tiled-network-graph/atlas-decode/tile.ts @@ -0,0 +1,397 @@ +/** + * Tile-kind decoder for the SALTILE wire (normative schema: + * `SPEC-ADDENDUM-WIRE.md` sections 3 and 5). Layers over the envelope + * reader: validates the HEAD schema and its echo of the request, then + * exposes the columns as typed-array views over the response buffer - + * point data is never copied and never walked. + */ + +import { decodeCbor, type CborValue } from "./cbor"; +import { + expectEqual, + fail, + GENERATION_BYTES, + isCborMap, + isNullableStringArray, + isNumberArray, + isUint, + isUintArray, + requireBool, + requireGenerationEcho, + requireSlot, + requireUint, +} from "./schema"; +import { readEnvelope, SaltileDetail, SaltileMode, TileSlot } from "./wire"; + +/** The request context a tile response must echo. */ +export interface SaltileTileRequest { + /** Generation identity from the manifest, 32 raw bytes. */ + readonly generation: Uint8Array; + readonly variant: number; + readonly coordinate: { + readonly z: number; + readonly x: number; + readonly y: number; + }; + readonly mode: SaltileMode; + /** + * The delivery cut's addend for this caller: the manifest's `m + k`. + * + * Not `log2` of the bucket-schedule span alone. A restricted caller is + * served at `z + m + k`, where `k` comes from the manifest's + * `scopeSchedule` (`wire.md`, the runs contract), and the head's + * `firstBucket` and `runs` length are both counted from that cut. + */ + readonly deliverySpanLog2: number; + /** Count of colored type ids sent; 0 means TYPE_MASK is absent. */ + readonly coloredTypeIdCount: number; + /** The request body's `detail` mode; the HEAD `trailer` bool must echo it. */ + readonly detail: SaltileDetail; +} + +/** + * Post-intersection set metadata carried by HEAD key 8. Required on + * the root tile - `bounds` is the initial camera framing datum. + */ +export interface SaltileTileGlobal { + readonly visibleAtZoom: number; + /** + * Tight wire-frame extent of the entire visible set as + * [minX, minY, maxX, maxY]; null iff that set is empty. + */ + readonly bounds: readonly number[] | null; + readonly minResolution: number; +} + +/** Per-point detail from the trailer tail, delivered order. */ +export interface SaltileTileDetail { + readonly labels: readonly (string | null)[]; + readonly icons: readonly (string | null)[]; +} + +/** One decoded tile response; typed arrays are views, not copies. */ +export interface DecodedSaltileTile { + readonly delivered: number; + readonly visible: number; + readonly firstBucket: number; + /** Per-bucket delivered counts for buckets firstBucket.. */ + readonly runs: readonly number[]; + /** Occupancy bitmask of the four Morton children below this cut; 0 = complete. */ + readonly children: number; + /** f32 xy pairs in the wire frame, length `delivered * 2`. */ + readonly positions: Float32Array; + /** Row ids are opaque map keys; they may be any u32 value. */ + readonly rowIds: Uint32Array; + /** + * Per-point type bitmask rows, `ceil(coloredTypeIdCount / 8)` bytes + * per point, LSB-first; null when the request sent no colored types. + */ + readonly typeMask: Uint8Array | null; + readonly detail: SaltileTileDetail | null; + readonly global: SaltileTileGlobal | null; +} + +/** HEAD keys of the tile schema. */ +const tileHeadKeys = { + generation: 0, + variant: 1, + coordinate: 2, + mode: 3, + delivered: 4, + visible: 5, + firstBucket: 6, + runs: 7, + global: 8, + children: 9, + trailer: 10, +} as const; + +const knownHeadKeys = new Set(Object.values(tileHeadKeys)); + +const readGlobal = (value: CborValue, offset: number): SaltileTileGlobal => { + if (!isCborMap(value)) { + return fail("HEAD global (key 8) must be a map", offset); + } + for (const key of value.keys()) { + if (key !== 0 && key !== 1 && key !== 2) { + return fail(`HEAD global carries unknown key ${key}`, offset); + } + } + const visibleAtZoom = value.get(0); + const bounds = value.get(1); + const minResolution = value.get(2); + if (!isUint(visibleAtZoom) || !isUint(minResolution)) { + return fail("HEAD global counts must be unsigned integers", offset); + } + if (bounds === undefined) { + if (visibleAtZoom !== 0) { + return fail( + "HEAD global bounds are absent but the visible set is not empty", + offset, + ); + } + return { visibleAtZoom, bounds: null, minResolution }; + } + if (!isNumberArray(bounds) || bounds.length !== 4) { + return fail("HEAD global bounds must be four floats", offset); + } + return { visibleAtZoom, bounds, minResolution }; +}; + +const readDetail = ( + payload: Uint8Array, + delivered: number, + offset: number, +): SaltileTileDetail => { + const value = decodeCbor(payload); + if (!isCborMap(value)) { + return fail("TRAILER must be a map", offset); + } + for (const key of value.keys()) { + if (key !== 0 && key !== 1) { + return fail(`TRAILER carries unknown key ${key}`, offset); + } + } + const columns: (readonly (string | null)[])[] = []; + for (const [key, name] of [ + [0, "labels"], + [1, "icons"], + ] as const) { + const entries = value.get(key); + if (!isNullableStringArray(entries)) { + return fail(`TRAILER ${name} entries must be strings or null`, offset); + } + if (entries.length !== delivered) { + return fail( + `TRAILER ${name} must carry exactly ${delivered} entries`, + offset, + ); + } + columns.push(entries); + } + return { labels: columns[0]!, icons: columns[1]! }; +}; + +/** + * Decodes one tile response against the request that produced it. + * + * Slots beyond the ones this decoder consumes - the reserved MASS + * slot and any appended by a newer table - are ignored by design + * (structural optionality; the envelope validated their extents). + * + * @throws {@link SaltileWireError} on envelope, schema, echo, or count + * violations; `SaltileCborError` on CBOR profile violations. + */ +export const decodeSaltileTile = ( + buffer: ArrayBuffer, + request: SaltileTileRequest, +): DecodedSaltileTile => { + if (request.generation.length !== GENERATION_BYTES) { + fail( + `request generation is ${request.generation.length} bytes; expected ${GENERATION_BYTES}`, + 0, + ); + } + + const envelope = readEnvelope(buffer, "tile"); + const bytes = new Uint8Array(buffer); + + const headSlot = envelope.slots[TileSlot.Head]!; + const head = decodeCbor(bytes.subarray(headSlot.start, headSlot.end)); + const headOffset = headSlot.start; + if (!isCborMap(head)) { + return fail("HEAD must be a map", headOffset); + } + for (const key of head.keys()) { + if (!knownHeadKeys.has(key)) { + return fail(`HEAD carries unknown key ${key}`, headOffset); + } + } + + requireGenerationEcho(head, request.generation, headOffset); + + expectEqual( + requireUint(head, tileHeadKeys.variant, "variant", headOffset), + request.variant, + "variant", + headOffset, + ); + + const coordinate = head.get(tileHeadKeys.coordinate); + if (!isUintArray(coordinate) || coordinate.length !== 3) { + return fail("HEAD coordinate must be [z, x, y]", headOffset); + } + expectEqual(coordinate[0]!, request.coordinate.z, "z", headOffset); + expectEqual(coordinate[1]!, request.coordinate.x, "x", headOffset); + expectEqual(coordinate[2]!, request.coordinate.y, "y", headOffset); + + expectEqual( + requireUint(head, tileHeadKeys.mode, "mode", headOffset), + request.mode, + "mode", + headOffset, + ); + + const delivered = requireUint( + head, + tileHeadKeys.delivered, + "delivered", + headOffset, + ); + const visible = requireUint( + head, + tileHeadKeys.visible, + "visible", + headOffset, + ); + if (visible < delivered) { + return fail( + `HEAD visible ${visible} is below delivered ${delivered}`, + headOffset, + ); + } + + const firstBucket = requireUint( + head, + tileHeadKeys.firstBucket, + "firstBucket", + headOffset, + ); + const runs = head.get(tileHeadKeys.runs); + if (!isUintArray(runs)) { + return fail("HEAD runs must be an array of unsigned integers", headOffset); + } + + const { z } = request.coordinate; + const cut = z + request.deliverySpanLog2; + const delta = request.mode === SaltileMode.Delta; + const expectedFirst = delta && z !== 0 ? cut : 0; + const expectedRuns = delta + ? z === 0 + ? request.deliverySpanLog2 + 1 + : 1 + : cut + 1; + expectEqual(firstBucket, expectedFirst, "firstBucket", headOffset); + expectEqual(runs.length, expectedRuns, "runs length", headOffset); + + // `sum(runs) = delivered` is law in every response, scoped or operator + // (wire.md: key 11 is retired to a reserved slot, and a restricted + // cascade delivers without fill, so every run counts exactly its + // bucket's delivered points). Dark from d26c824ab4 until 2026-08-03 on + // a premise that had expired: the fill this waited for is gone, both + // server paths derive runs and delivered from the same values, and + // TileHead has no key 11 to emit. So a violation here is a server + // defect rather than an accepted hole, and refusing is right. + const runSum = runs.reduce((sum, entry) => sum + entry, 0); + if (runSum !== delivered) { + return fail( + `HEAD runs sum to ${runSum}; delivered is ${delivered}`, + headOffset, + ); + } + + const children = requireUint( + head, + tileHeadKeys.children, + "children", + headOffset, + ); + if (children > 15) { + return fail( + "HEAD children carries bits above the four Morton children", + headOffset, + ); + } + + const trailerDeclared = requireBool( + head, + tileHeadKeys.trailer, + "trailer", + headOffset, + ); + if (trailerDeclared !== (request.detail === SaltileDetail.Auxiliary)) { + return fail( + `HEAD trailer is ${trailerDeclared}; the request's detail is "${request.detail}"`, + headOffset, + ); + } + + const globalValue = head.get(tileHeadKeys.global); + if (globalValue === undefined && z === 0) { + return fail( + "HEAD global is required on the root tile (bootstrap framing)", + headOffset, + ); + } + const global = + globalValue === undefined ? null : readGlobal(globalValue, headOffset); + + const positionsSlot = requireSlot( + envelope.slots, + TileSlot.Positions, + "POSITIONS", + delivered * 8, + buffer.byteLength, + ); + const rowIdsSlot = requireSlot( + envelope.slots, + TileSlot.RowIds, + "ROW_IDS", + delivered * 4, + buffer.byteLength, + ); + + const maskStride = Math.ceil(request.coloredTypeIdCount / 8); + const maskExtent = envelope.slots[TileSlot.TypeMask] ?? null; + let typeMask: Uint8Array | null = null; + if (request.coloredTypeIdCount === 0) { + if (maskExtent !== null) { + return fail( + "TYPE_MASK is present; the request sent no coloredTypeIds", + maskExtent.start, + ); + } + } else { + const slot = requireSlot( + envelope.slots, + TileSlot.TypeMask, + "TYPE_MASK", + delivered * maskStride, + buffer.byteLength, + ); + typeMask = bytes.subarray(slot.start, slot.end); + } + + let detail: SaltileTileDetail | null = null; + if (trailerDeclared) { + if (envelope.tailOffset >= buffer.byteLength) { + return fail( + "TRAILER is declared but the response carries no tail", + envelope.tailOffset, + ); + } + detail = readDetail( + bytes.subarray(envelope.tailOffset), + delivered, + envelope.tailOffset, + ); + } else if (envelope.tailOffset !== buffer.byteLength) { + return fail( + "response carries a tail but HEAD declares no trailer", + envelope.tailOffset, + ); + } + + return { + delivered, + visible, + firstBucket, + runs, + children, + positions: new Float32Array(buffer, positionsSlot.start, delivered * 2), + rowIds: new Uint32Array(buffer, rowIdsSlot.start, delivered), + typeMask, + detail, + global, + }; +}; diff --git a/apps/hash-frontend/src/components/tiled-network-graph/atlas-decode/wire.test.ts b/apps/hash-frontend/src/components/tiled-network-graph/atlas-decode/wire.test.ts new file mode 100644 index 00000000000..e83198a8376 --- /dev/null +++ b/apps/hash-frontend/src/components/tiled-network-graph/atlas-decode/wire.test.ts @@ -0,0 +1,215 @@ +import { describe, expect, it } from "vitest"; + +import { + DIRECTORY_ENTRY_BYTES, + PAYLOAD_ALIGNMENT, + PREFIX_BYTES, + readEnvelope, + SaltileWireError, + type SaltileKind, +} from "./wire"; + +const kindByte: Record = { + tile: 0x54, + edges: 0x45, + locate: 0x4c, +}; + +/** + * Builds a response: prefix, directory, payloads sequential in slot + * order (null = absent slot), optional self-delimiting tail. + */ +const response = ( + kind: SaltileKind, + payloads: (number[] | null)[], + tail: number[] = [], +): ArrayBuffer => { + const base = PREFIX_BYTES + payloads.length * DIRECTORY_ENTRY_BYTES; + const directory = new DataView( + new ArrayBuffer(payloads.length * DIRECTORY_ENTRY_BYTES), + ); + const body: number[] = []; + let cursor = base; + for (const [slot, payload] of payloads.entries()) { + if (payload === null) { + continue; + } + directory.setUint32(slot * DIRECTORY_ENTRY_BYTES, cursor, true); + directory.setUint32( + slot * DIRECTORY_ENTRY_BYTES + 4, + cursor + payload.length, + true, + ); + body.push(...payload); + cursor += payload.length; + while (cursor % PAYLOAD_ALIGNMENT !== 0) { + body.push(0); + cursor += 1; + } + } + const prefix = new DataView(new ArrayBuffer(PREFIX_BYTES)); + for (const [index, byte] of [ + 0x53, + 0x41, + 0x4c, + 0x54, + 0x49, + 0x4c, + 0x45, + kindByte[kind], + ].entries()) { + prefix.setUint8(index, byte); + } + prefix.setUint16(8, 1, true); + prefix.setUint16(12, payloads.length, true); + return new Uint8Array([ + ...new Uint8Array(prefix.buffer), + ...new Uint8Array(directory.buffer), + ...body, + ...tail, + ]).buffer; +}; + +/** A well-formed 5-slot tile skeleton: HEAD + two columns, MASS and TYPE_MASK absent. */ +const tileSkeleton = (): (number[] | null)[] => [ + [0xa0], + Array.from({ length: 24 }, () => 1), + Array.from({ length: 12 }, () => 2), + null, + null, +]; + +const failure = (buffer: ArrayBuffer, kind: SaltileKind = "tile"): string => { + try { + readEnvelope(buffer, kind); + } catch (error) { + expect(error).toBeInstanceOf(SaltileWireError); + return (error as SaltileWireError).message; + } + throw new Error("expected the envelope to be rejected"); +}; + +describe("readEnvelope", () => { + it("locates present slots and marks absent ones null", () => { + const envelope = readEnvelope(response("tile", tileSkeleton()), "tile"); + + expect(envelope.kind).toBe("tile"); + expect(envelope.wireVersion).toBe(1); + expect(envelope.slots).toHaveLength(5); + expect(envelope.slots[0]).toEqual({ start: 56, end: 57 }); + expect(envelope.slots[1]).toEqual({ start: 64, end: 88 }); + expect(envelope.slots[2]).toEqual({ start: 88, end: 100 }); + expect(envelope.slots[3]).toBeNull(); + expect(envelope.slots[4]).toBeNull(); + expect(envelope.tailOffset).toBe(104); + }); + + it("keeps every payload 8-aligned across all padding widths", () => { + for (let length = 1; length <= PAYLOAD_ALIGNMENT; length += 1) { + const payloads = tileSkeleton(); + payloads[0] = Array.from({ length }, () => 0x01); + const envelope = readEnvelope(response("tile", payloads), "tile"); + for (const slot of envelope.slots) { + if (slot !== null) { + expect(slot.start % PAYLOAD_ALIGNMENT).toBe(0); + } + } + } + }); + + it("distinguishes present-empty from absent", () => { + const payloads = tileSkeleton(); + payloads[1] = []; + payloads[2] = []; + const envelope = readEnvelope(response("tile", payloads), "tile"); + expect(envelope.slots[1]).toEqual({ start: 64, end: 64 }); + expect(envelope.slots[2]).toEqual({ start: 64, end: 64 }); + expect(envelope.slots[3]).toBeNull(); + }); + + it("accepts appended slots beyond the v1 table", () => { + const payloads = [...tileSkeleton(), [9, 9, 9, 9]]; + const envelope = readEnvelope(response("tile", payloads), "tile"); + expect(envelope.slots).toHaveLength(6); + expect(envelope.slots[5]).not.toBeNull(); + }); + + it("reports the tail offset after the last present payload", () => { + const buffer = response("tile", tileSkeleton(), [0xa0]); + expect(readEnvelope(buffer, "tile").tailOffset).toBe(104); + expect(buffer.byteLength).toBe(105); + }); + + it("names the violated check for every envelope rejection", () => { + const wrongFamily = response("tile", tileSkeleton()); + new Uint8Array(wrongFamily)[0] = 0x58; + expect(failure(wrongFamily)).toMatch(/SALTILE family/u); + + const unknownKind = response("tile", tileSkeleton()); + new Uint8Array(unknownKind)[7] = 0x51; + expect(failure(unknownKind)).toMatch(/unknown kind/u); + + expect(failure(response("edges", [[0xa0], null, null, null]))).toMatch( + /kind is edges; the request expects tile/u, + ); + + const badVersion = response("tile", tileSkeleton()); + new Uint8Array(badVersion)[8] = 9; + expect(failure(badVersion)).toMatch(/wire version is 9/u); + + const prefixFlags = response("tile", tileSkeleton()); + new Uint8Array(prefixFlags)[10] = 1; + expect(failure(prefixFlags)).toMatch(/prefix flags/u); + + const reserved = response("tile", tileSkeleton()); + new Uint8Array(reserved)[14] = 1; + expect(failure(reserved)).toMatch(/reserved bytes/u); + + expect(failure(response("tile", [[0xa0], null, null, null]))).toMatch( + /slot count 4 is below the tile table size 5/u, + ); + + expect( + failure(response("tile", [null, [1, 2, 3, 4], null, null, null])), + ).toMatch(/slot 0 \(HEAD\) must be present/u); + + const gapStart = response("tile", tileSkeleton()); + new DataView(gapStart).setUint32( + PREFIX_BYTES + DIRECTORY_ENTRY_BYTES, + 72, + true, + ); + expect(failure(gapStart)).toMatch(/sequential layout requires 64/u); + + const inverted = response("tile", tileSkeleton()); + new DataView(inverted).setUint32( + PREFIX_BYTES + DIRECTORY_ENTRY_BYTES + 4, + 32, + true, + ); + expect(failure(inverted)).toMatch(/slot 1 ends before it starts/u); + + const overrun = response("tile", tileSkeleton()); + new DataView(overrun).setUint32( + PREFIX_BYTES + 2 * DIRECTORY_ENTRY_BYTES + 4, + 4096, + true, + ); + expect(failure(overrun)).toMatch(/slot 2 extends beyond the response/u); + + const midDirectory = response("tile", tileSkeleton()).slice( + 0, + PREFIX_BYTES + 6, + ); + expect(failure(midDirectory)).toMatch(/ends inside the directory/u); + + const dirtyPadding = response("tile", tileSkeleton()); + new Uint8Array(dirtyPadding)[58] = 7; + expect(failure(dirtyPadding)).toMatch(/padding bytes must be zero/u); + + const midPadding = response("tile", tileSkeleton()).slice(0, 90); + expect(failure(midPadding)).toMatch( + /slot 2 extends beyond|ends inside payload padding/u, + ); + }); +}); diff --git a/apps/hash-frontend/src/components/tiled-network-graph/atlas-decode/wire.ts b/apps/hash-frontend/src/components/tiled-network-graph/atlas-decode/wire.ts new file mode 100644 index 00000000000..f421571535c --- /dev/null +++ b/apps/hash-frontend/src/components/tiled-network-graph/atlas-decode/wire.ts @@ -0,0 +1,255 @@ +/** + * Envelope layer of the SALTILE binary wire, the atlas serving API's + * response encoding. The normative contract is + * `libs/@local/graph/atlas/docs/wire.md` (also exported verbatim into + * the server's OpenAPI description); agreement with the Rust encoder + * is proven by shared fixture bytes, never asserted. + * + * A response is a 16-byte prefix, a fixed offset directory of + * `slotCount` (start, end) pairs, payloads sequential in slot order, + * and an optional self-delimiting CBOR trailer tail after the last + * payload. The directory gives random access to any payload with zero + * parsing; every payload starts 8-byte aligned, so columns are viewed + * as typed arrays in place - the decoder never copies point data. + * + * Slot meanings are frozen per (kind, wireVersion) and evolution + * appends: a decoder reads the slots it supports and ignores the rest, + * so a populated slot it does not consume costs nothing. + */ + +/** Response kind, discriminated by the magic's eighth byte. */ +export type SaltileKind = "tile" | "edges" | "locate"; + +/** Seven-byte magic family prefix, "SALTILE" in ASCII. */ +const magicFamily = [0x53, 0x41, 0x4c, 0x54, 0x49, 0x4c, 0x45] as const; + +/** Kind discriminators: ASCII initials in the magic's eighth byte. */ +const kindBytes: Readonly> = { + tile: 0x54, // "T" + edges: 0x45, // "E" + locate: 0x4c, // "L" +}; + +export const SALTILE_MEDIA_TYPE = "application/vnd.hash.saltile-v1"; +export const SALTILE_WIRE_VERSION = 1; + +/** Byte length of the prefix (magic u64, version u16, flags u16, slotCount u16, reserved u16). */ +export const PREFIX_BYTES = 16; +/** Byte length of one directory entry (start u32, end u32). */ +export const DIRECTORY_ENTRY_BYTES = 8; +/** Alignment of every payload start. */ +export const PAYLOAD_ALIGNMENT = 8; + +/** Tile slot table (v1). */ +export const TileSlot = { + Head: 0, + Positions: 1, + RowIds: 2, + TypeMask: 3, + /** Reserved for the enshrined mass channel; (0, 0) in v1. */ + Mass: 4, +} as const; + +/** Edges slot table (v1). */ +export const EdgesSlot = { + Head: 0, + Sources: 1, + Targets: 2, + /** 32-byte link-entity identity records, delivery order. */ + EdgeIds: 3, +} as const; + +/** Locate slot table (v1). */ +export const LocateSlot = { + Head: 0, + Positions: 1, + RowIds: 2, + TypeMask: 3, + EdgeSources: 4, + EdgeTargets: 5, + /** 32-byte link-entity identity records, edge order. */ + EdgeIds: 6, +} as const; + +/** Minimum slotCount per kind: the v1 table sizes. */ +const minimumSlots: Readonly> = { + tile: 5, + edges: 4, + locate: 7, +}; + +/** Tile delivery mode carried by the HEAD. */ +export const SaltileMode = { + Delta: 0, + Total: 1, +} as const; + +export type SaltileMode = (typeof SaltileMode)[keyof typeof SaltileMode]; + +/** + * The request body's `detail` mode, shared by the tile and edges routes: + * `"auxiliary"` asks for the detail trailer tail, `"minimal"` (the server + * default) sends geometry alone. The HEAD `trailer` bool echoes the choice. + */ +export const SaltileDetail = { + Minimal: "minimal", + Auxiliary: "auxiliary", +} as const; + +export type SaltileDetail = (typeof SaltileDetail)[keyof typeof SaltileDetail]; + +/** One present payload extent; `end` is exclusive and unpadded. */ +export interface SaltileSlot { + readonly start: number; + readonly end: number; +} + +/** Envelope structure of one response. */ +export interface SaltileEnvelope { + readonly kind: SaltileKind; + readonly wireVersion: number; + /** Directory in slot order; null marks an absent (0, 0) slot. */ + readonly slots: readonly (SaltileSlot | null)[]; + /** 8-aligned offset after the last present payload, where a trailer tail may begin. */ + readonly tailOffset: number; +} + +/** A response body violated the SALTILE envelope contract. */ +export class SaltileWireError extends Error { + override readonly name = "SaltileWireError"; + /** Byte offset at which the violated check applies. */ + readonly offset: number; + + constructor(detail: string, offset: number) { + super(`${detail} (at byte ${offset})`); + this.offset = offset; + } +} + +const fail = (detail: string, offset: number): never => { + throw new SaltileWireError(detail, offset); +}; + +const kindOfByte = (byte: number): SaltileKind | undefined => + byte === kindBytes.tile + ? "tile" + : byte === kindBytes.edges + ? "edges" + : byte === kindBytes.locate + ? "locate" + : undefined; + +const align8 = (offset: number): number => + Math.ceil(offset / PAYLOAD_ALIGNMENT) * PAYLOAD_ALIGNMENT; + +/** + * Validates the prefix and offset directory of a SALTILE response: + * magic and kind, wire version, zero reserved bits, slot count at + * least the kind's table size, HEAD present, present slots strictly + * sequential and 8-aligned with zero padding bytes between payloads. + * + * Payloads are located, not interpreted; slot meaning, presence + * requirements, and the trailer tail belong to the per-kind decoders + * layered above. + * + * @throws {@link SaltileWireError} on the first violated check. + */ +export const readEnvelope = ( + buffer: ArrayBuffer, + expectedKind: SaltileKind, +): SaltileEnvelope => { + if (buffer.byteLength < PREFIX_BYTES) { + return fail( + `response is ${buffer.byteLength} bytes; the prefix requires ${PREFIX_BYTES}`, + 0, + ); + } + + const bytes = new Uint8Array(buffer); + for (const [index, expected] of magicFamily.entries()) { + if (bytes[index] !== expected) { + return fail("magic does not begin with the SALTILE family", index); + } + } + + const kind = kindOfByte(bytes[7]!); + if (kind === undefined) { + return fail(`unknown kind byte 0x${bytes[7]!.toString(16)}`, 7); + } + if (kind !== expectedKind) { + return fail( + `response kind is ${kind}; the request expects ${expectedKind}`, + 7, + ); + } + + const view = new DataView(buffer); + const wireVersion = view.getUint16(8, true); + if (wireVersion !== SALTILE_WIRE_VERSION) { + return fail( + `wire version is ${wireVersion}; this decoder speaks ${SALTILE_WIRE_VERSION}`, + 8, + ); + } + if (view.getUint16(10, true) !== 0) { + return fail("prefix flags must be zero", 10); + } + const slotCount = view.getUint16(12, true); + if (view.getUint16(14, true) !== 0) { + return fail("prefix reserved bytes must be zero", 14); + } + if (slotCount < minimumSlots[kind]) { + return fail( + `slot count ${slotCount} is below the ${kind} table size ${minimumSlots[kind]}`, + 12, + ); + } + + const payloadBase = PREFIX_BYTES + slotCount * DIRECTORY_ENTRY_BYTES; + if (buffer.byteLength < payloadBase) { + return fail("response ends inside the directory", buffer.byteLength); + } + + const slots: (SaltileSlot | null)[] = []; + let expectedStart = payloadBase; + + for (let slot = 0; slot < slotCount; slot += 1) { + const entryOffset = PREFIX_BYTES + slot * DIRECTORY_ENTRY_BYTES; + const start = view.getUint32(entryOffset, true); + const end = view.getUint32(entryOffset + 4, true); + + if (start === 0 && end === 0) { + slots.push(null); + continue; + } + if (end < start) { + return fail(`slot ${slot} ends before it starts`, entryOffset); + } + if (start !== expectedStart) { + return fail( + `slot ${slot} starts at ${start}; the sequential layout requires ${expectedStart}`, + entryOffset, + ); + } + if (end > buffer.byteLength) { + return fail(`slot ${slot} extends beyond the response`, entryOffset); + } + for (let cursor = end; cursor < align8(end); cursor += 1) { + if (cursor >= buffer.byteLength) { + return fail("response ends inside payload padding", cursor); + } + if (bytes[cursor] !== 0) { + return fail("padding bytes must be zero", cursor); + } + } + + slots.push({ start, end }); + expectedStart = align8(end); + } + + if (slots[0] === null) { + return fail("slot 0 (HEAD) must be present", PREFIX_BYTES); + } + + return { kind, wireVersion, slots, tailOffset: expectedStart }; +}; diff --git a/apps/hash-frontend/src/components/tiled-network-graph/located-entity-popover.tsx b/apps/hash-frontend/src/components/tiled-network-graph/located-entity-popover.tsx new file mode 100644 index 00000000000..bde6e6bf9cc --- /dev/null +++ b/apps/hash-frontend/src/components/tiled-network-graph/located-entity-popover.tsx @@ -0,0 +1,715 @@ +/** + * The selection popover for the Atlas network graph: a compact white entity card + * anchored to the selected node/edge, showing the located item's title, its + * type chip, its simple-value properties (nodes), and a "Go to entity" action. + * When the located detail is truncated (its type or property set capped) the + * card flags the missing parts with a "+ more" cue. Used by the + * entities-visualizer network graph view. + * + * Purely presentational — the consumer decodes a {@link fetchLocate} response + * into {@link LocatedEntityDetail} and owns the + * {@link LocatedEntityPopoverProps.onGoTo} behaviour; this file only lays the + * card out (reusing the {@link Button} primitive) and positions it via + * {@link Popover}. Every text run is clamped so a long label or value can't + * stretch or overflow the card. + * + * The {@link Popover} portals its content, so a consumer that scopes the ds Panda + * tokens to a subtree (rather than globally) must supply a `PortalContainerContext` + * inside that scope, or the card's token-based colours won't resolve. + */ + +import { Button, Icon, Popover } from "@hashintel/ds-components"; +import { css, cva } from "@hashintel/ds-helpers/css"; + +/** + * Sits this popover — and the graph's search widget, which layers around it — + * below app overlays that open over the graph (notably the entity drawer). + * Passed as the `Popover`'s `positionerClassName` so it lands on the positioner, + * whose z-index reads the `--z-index-popover` token; overriding that variable + * here lowers only this popover's layer, not the ds default. Kept in step with + * the search widget's base (`network-graph-search.tsx`). + */ +const lowLayerStyles = css({ "--z-index-popover": "50" }); + +/** The selection's live on-screen anchor, as `NetworkGraph` reports it. */ +export type LocatedEntityPopoverAnchor = + | { + type: "node"; + x: number; + y: number; + nodeRadius: number; + variant: "detailed" | "compact"; + } + | { type: "edge"; x: number; y: number }; + +/** + * One coloured run of a styled property value — the shape the entity drawer's + * property table emits for a value (a plain `{ text, color }`, matching its + * formatted-value parts), so the card can render the same coloured runs. + */ +export interface LocatedEntityValuePart { + readonly text: string; + readonly color: string; +} + +/** + * One property row of the card: a shortened key and its value — either a plain + * string, or the styled coloured runs the property table renders. + */ +export interface LocatedEntityProperty { + readonly key: string; + readonly value: string | readonly LocatedEntityValuePart[]; +} + +/** + * A type chip: a label, the type's icon (shown at the chip's left), and the + * colour of its dot (shown at the chip's right). + */ +export interface LocatedEntityTypeChip { + readonly label: string; + readonly color: string; + /** The type's icon — emoji or ds glyph — drawn at the chip's left. */ + readonly icon?: LocatedEntityIcon; +} + +/** + * A type icon the card renders: an emoji glyph drawn as text, or an SVG type + * icon (given by URL) drawn as a tintable mask — the served `/icons/types/*.svg` + * icons the rest of the app shows, which text surfaces can't draw directly. At + * most one is set; `emoji` wins when both are. + */ +export interface LocatedEntityIcon { + readonly emoji?: string; + readonly svgUrl?: string; +} + +/** + * One endpoint entity of a link, shown in the edge card's from→to row: the + * entity's label and its type icon. + */ +export interface LocatedEntityEndpoint { + readonly label: string; + readonly icon?: LocatedEntityIcon; + /** + * Selects this endpoint's node when its label is clicked — makes the label a + * link-style button. Omitted when the endpoint can't be resolved to a + * selectable node (the label then renders as plain text). + */ + readonly onClick?: () => void; +} + +interface LocatedEntityDetailShared { + /** Bold card title — the entity/edge label. */ + readonly title: string; + /** Property rows shown in the card's property table. */ + readonly properties: readonly LocatedEntityProperty[]; + /** + * Whether the `types` list is the entity's whole set. When `false` a cue is + * shown beside the chips that some types aren't displayed. + */ + readonly typesComplete: boolean; + /** + * Whether {@link properties} is the entity's whole set. When `false` a note is + * shown at the foot of the property table that some properties aren't displayed. + */ + readonly propertiesComplete: boolean; +} + +/** A located node: its types float beside the title as chips. */ +export interface LocatedNodeDetail extends LocatedEntityDetailShared { + readonly kind: "node"; + /** + * The node's types, shown as chips floating beside the title — every type we + * have for the entity, whether or not it carries a distinct colour (uncoloured + * types render with a grey dot). + */ + readonly types: readonly LocatedEntityTypeChip[]; + /** The node's edge/neighbour count, shown as a subtle caption under the header. */ + readonly connectionCount: number; + /** Whether the ego-graph is whole; `false` suffixes the count with a "+". */ + readonly connectionsComplete: boolean; +} + +/** + * A located edge (a link entity): its link types float beside the title as + * chips, and its two endpoints are shown as a from→to row above the properties. + */ +export interface LocatedEdgeDetail extends LocatedEntityDetailShared { + readonly kind: "edge"; + /** The link's types, shown as chips floating beside the title. */ + readonly types: readonly LocatedEntityTypeChip[]; + /** The entities the link connects, in link direction. */ + readonly endpoints: { + readonly from: LocatedEntityEndpoint; + readonly to: LocatedEntityEndpoint; + }; +} + +/** The presentational detail the card renders for a located node or edge. */ +export type LocatedEntityDetail = LocatedNodeDetail | LocatedEdgeDetail; + +export interface LocatedEntityPopoverProps { + /** The element the popover is positioned within (the chart frame). */ + readonly triggerRef: React.Ref; + /** The selected item's on-screen anchor; the popover tracks it on zoom/pan. */ + readonly anchor: LocatedEntityPopoverAnchor; + readonly detail: LocatedEntityDetail; + /** Dismiss (Escape or a click outside). */ + readonly onClose: () => void; + /** "Go to entity" handler; the button is omitted when absent. */ + readonly onGoTo?: () => void; + /** + * Called when the card is focused or clicked, so a consumer can bring it to + * the front over other overlays (e.g. the network graph's search widget). + */ + readonly onActivate?: () => void; +} + +const cardStyles = css({ + display: "flex", + flexDirection: "column", + gap: "2", + // Size to the content, floored at the former fixed width and capped at twice + // it, so a sparse card stays compact while a dense one can grow. + width: "[fit-content]", + minWidth: "[200px]", + maxWidth: "[400px]", + padding: "3", + backgroundColor: "white", + borderRadius: "md", + borderWidth: "1px", + borderStyle: "solid", + borderColor: "neutral.s40", + boxShadow: "md", +}); + +// The title and type chip share one wrapping flex row so the chip "floats" +// beside a short label and drops onto its own line under a long one — flex-wrap +// packs by content width, so a short title keeps the chip alongside it while a +// long (clamped) title claims the full row and pushes the chip below. +const headerStyles = css({ + display: "flex", + flexWrap: "wrap", + alignItems: "center", + columnGap: "2", + rowGap: "1", +}); + +const titleStyles = css({ + fontSize: "base", + fontWeight: "[700]", + color: "neutral.s120", + lineHeight: "tight", + lineClamp: "2", +}); + +// A compact type lozenge: a subtle grey pill holding the type's icon, its label, +// and a dot in the type's colour. The tight, even spacing keeps the icon, text, +// and dot reading as one balanced unit. The left padding flexes on whether an +// icon leads: a glyph fills the left edge, so it can sit tighter, whereas a bare +// label needs a touch more so it isn't cramped against the pill. +const typeChipRecipe = cva({ + base: { + display: "inline-flex", + alignItems: "center", + gap: "1.5", + minWidth: "0", + height: "[18px]", + paddingRight: "1.5", + borderRadius: "sm", + backgroundColor: "neutral.s30", + }, + variants: { + hasIcon: { + true: { paddingLeft: "1" }, + false: { paddingLeft: "2" }, + }, + }, +}); + +// The type's icon at the chip's left — an emoji glyph or a ds Icon. Matches the +// label's tone so a monochrome glyph reads as cohesive with the text (emojis +// keep their own colour). +const chipIconStyles = css({ + display: "inline-flex", + alignItems: "center", + flexShrink: "0", + fontSize: "xs", + lineHeight: "none", + color: "neutral.s110", + transform: "[translateY(-0.25px)]", +}); + +/** Truncates a long type label in the chip, at readable contrast. */ +const chipLabelStyles = css({ + truncate: true, + maxWidth: "[160px]", + fontSize: "xs", + fontWeight: "medium", + lineHeight: "none", + color: "neutral.s110", +}); + +// A little extra space before the dot (beyond the chip's base gap) sets the +// colour indicator apart from the label. Nudged up a touch too: geometric +// centring reads slightly low against the label's optical centre (the caps sit +// above the box middle). +const typeDotStyles = css({ + width: "[6px]", + height: "[6px]", + borderRadius: "full", + flexShrink: "0", + marginLeft: "0.5", +}); + +// A muted cue sitting after the type chips when the type list is truncated — +// signals the entity holds types beyond those shown, without dressing as a chip. +const moreTypesStyles = css({ + display: "inline-flex", + alignItems: "center", + fontSize: "xs", + fontWeight: "medium", + fontStyle: "italic", + lineHeight: "none", + color: "neutral.s90", +}); + +// The node's connection count, on its own line under the header as a tiny grey +// caption. The negative top margin claws back most of the card's 8px inter-child +// gap, leaving ~2px so the caption tucks under the header as part of that block +// rather than reading as a separate section. Anchoring it to the header's bottom +// (not the title) keeps that ~2px constant however tall the header grows — so +// when the title wraps and the type chips drop onto their own taller row, the +// caption still sits snug beneath the chips rather than drifting away. +const connectionCountStyles = css({ + marginTop: "[-2px]", + fontSize: "[11px]", + lineHeight: "none", + color: "neutral.s90", +}); + +/** "3 connections" / "1 connection", with a "+" when the ego-graph is capped. */ +const formatConnectionCount = (count: number, complete: boolean): string => + `${count}${complete ? "" : "+"} ${ + count === 1 && complete ? "connection" : "connections" + }`; + +// The "connects" section beneath the header: the two entities the link joins, +// drawn as a directed source→target rail. Each endpoint sits on its own +// full-width row so a long label truncates identically on both (no lopsided +// wrap), and a vertical rail down the left gutter — a dot at the source, a caret +// at the target — reads top-to-bottom as the link's direction. +const connectionSectionStyles = css({ + display: "flex", + flexDirection: "column", + gap: "1.5", + paddingTop: "2", + borderTopWidth: "1px", + borderTopStyle: "solid", + borderTopColor: "neutral.s40", +}); + +// The two endpoint rows, stacked with no gap so the gutters' rail segments meet +// edge-to-edge and form one continuous line between the dot and the caret. +const connectionRowsStyles = css({ + display: "flex", + flexDirection: "column", +}); + +// One endpoint row: a fixed rail gutter, then the entity (icon + label) in a +// track that shrinks to zero so the label truncates. +const endpointRowStyles = css({ + display: "grid", + gridTemplateColumns: "[16px minmax(0, 1fr)]", + columnGap: "2", + alignItems: "stretch", +}); + +// The rail gutter: a fixed-width column stacking (spacer · marker · line) so the +// marker centres on the row while its line segment fills to the row edge, where +// it meets the neighbouring row's segment. Its min height sets the row height — +// so the rail reads tall enough between its markers regardless of label length. +const gutterStyles = css({ + display: "flex", + flexDirection: "column", + alignItems: "center", + minHeight: "[26px]", +}); + +// A rail line segment — a thin vertical line, centred in the gutter, growing to +// fill the space between a marker and the row edge. +const railLineStyles = css({ + flexGrow: "[1]", + width: "[1.5px]", + backgroundColor: "neutral.s60", +}); + +// Balances a marker against its line segment so the marker centres in the row. +const gutterSpacerStyles = css({ + flexGrow: "[1]", +}); + +// The source marker: a small filled dot the rail leaves from. +const sourceDotStyles = css({ + flexShrink: "0", + width: "[7px]", + height: "[7px]", + borderRadius: "full", + backgroundColor: "neutral.s90", +}); + +// The target marker: a small downward triangle the rail points into (the link's +// direction). A CSS border-triangle — not a ds icon — so the glyph has no +// internal padding: it sits flush with the line above and centres on the same +// axis as the line and the source dot. Its borders are set inline (below) since +// the triangle's arbitrary border widths don't map cleanly to Panda utilities. +const targetCaretStyles = css({ + flexShrink: "0", +}); + +const endpointStyles = css({ + display: "flex", + minWidth: "0", + alignItems: "center", + gap: "1", +}); + +const endpointIconStyles = css({ + flexShrink: "0", + fontSize: "sm", + color: "neutral.s100", +}); + +const endpointLabelStyles = css({ + minWidth: "0", + color: "neutral.s110", + fontSize: "sm", + truncate: true, +}); + +// The endpoint label as a link-style button when its node is selectable: a bare +// text button (no chrome) that shifts to the brand tone on hover/focus. On +// hover/focus its label underlines and a trailing arrow affordance fades in, so +// it reads as selecting/focusing that node in the graph. +const endpointButtonStyles = css({ + display: "inline-flex", + alignItems: "center", + gap: "1", + minWidth: "0", + maxWidth: "full", + appearance: "none", + background: "[transparent]", + border: "none", + padding: "0", + cursor: "pointer", + color: "neutral.s110", + fontFamily: "[inherit]", + fontWeight: "[inherit]", + fontSize: "sm", + textAlign: "left", + _hover: { color: "blue.s90" }, + _focusVisible: { color: "blue.s90", outline: "none" }, + "&:hover [data-endpoint-affordance], &:focus-visible [data-endpoint-affordance]": + { opacity: "1" }, +}); + +// The truncating label text inside the endpoint button. +const endpointButtonLabelStyles = css({ + minWidth: "0", + truncate: true, +}); + +// The trailing "focus this node" affordance: hidden until the button is +// hovered/focused (revealed by `endpointButtonStyles`), tinted the brand tone. +const endpointAffordanceStyles = css({ + display: "inline-flex", + flexShrink: "0", + alignItems: "center", + color: "blue.s90", + opacity: "0", +}); + +const propertyListStyles = css({ + display: "flex", + flexDirection: "column", + // Extra breathing room from the header/endpoints above and the button below. + marginY: "1.5", +}); + +const propertyRowStyles = css({ + display: "flex", + justifyContent: "space-between", + alignItems: "baseline", + gap: "3", + paddingY: "1.5", + borderTopWidth: "1px", + borderTopStyle: "solid", + borderTopColor: "neutral.s40", + // Drop the first row's divider so the table has no outer top (or bottom) + // border, keeping only the dividers between rows. + "&:first-child": { + borderTopWidth: "0", + }, +}); + +const propertyKeyStyles = css({ + minWidth: "0", + flex: "[1 1 auto]", + color: "neutral.s100", + fontSize: "sm", + overflowWrap: "anywhere", + lineClamp: "2", +}); + +const propertyValueStyles = css({ + minWidth: "0", + flex: "[1 1 50%]", + color: "neutral.s120", + fontSize: "sm", + fontWeight: "semibold", + textAlign: "right", + overflowWrap: "anywhere", + lineClamp: "2", +}); + +// A muted note drawn as the property table's final row when the property set is +// truncated — reuses the row divider so it reads as the table's foot. Loses its +// divider when it's the only row (no properties resolved). +const incompletePropertiesStyles = css({ + paddingTop: "1.5", + borderTopWidth: "1px", + borderTopStyle: "solid", + borderTopColor: "neutral.s40", + color: "neutral.s90", + fontSize: "xs", + fontStyle: "italic", + "&:first-child": { + borderTopWidth: "0", + paddingTop: "0", + }, +}); + +const goToButtonStyles = css({ + marginTop: "1", + width: "full", +}); + +/** + * Gaps that keep the card clear of its anchor: a node offsets by its drawn + * radius (larger in the detailed view); an edge anchor uses fixed gaps. + */ +const gapsFor = ( + anchor: LocatedEntityPopoverAnchor, +): { x: number; y: number } => + anchor.type === "node" + ? { + x: anchor.nodeRadius + (anchor.variant === "compact" ? 5 : 3), + y: anchor.nodeRadius + (anchor.variant === "compact" ? 0 : 3), + } + : { x: 10, y: 12 }; + +/** + * Renders an SVG entity/type icon as a tintable mask: a `size`×`size` px box + * filled with `currentColor` and clipped to the SVG shape (`center / contain`), + * matching how `EntityOrTypeIcon` draws the served type icons elsewhere. The + * colour is inherited from the surrounding icon style so a monochrome glyph reads + * as cohesive with the label. + */ +const svgMaskStyle = (svgUrl: string, size: number): React.CSSProperties => ({ + width: size, + height: size, + backgroundColor: "currentColor", + WebkitMaskImage: `url("${svgUrl}")`, + maskImage: `url("${svgUrl}")`, + WebkitMaskRepeat: "no-repeat", + maskRepeat: "no-repeat", + WebkitMaskPosition: "center", + maskPosition: "center", + WebkitMaskSize: "contain", + maskSize: "contain", +}); + +// The type lozenge: the type's icon at the left, its label, and a dot in the +// type's colour at the right. Shared by node and link types. +const TypeChip = ({ chip }: { chip: LocatedEntityTypeChip }) => { + const hasIcon = + chip.icon?.emoji !== undefined || chip.icon?.svgUrl !== undefined; + return ( + + {chip.icon?.emoji !== undefined ? ( + {chip.icon.emoji} + ) : chip.icon?.svgUrl !== undefined ? ( +