From 46a18222a1cd7e7b0947f1c53e717dac8583dc9e Mon Sep 17 00:00:00 2001 From: KooshaPari Date: Thu, 25 Jun 2026 08:50:11 -0700 Subject: [PATCH] feat(F3): enriched DAG schema with prereqs, acceptance, audit hooks --- Cargo.lock | 1532 +++++++------------------- Cargo.toml | 1 + crates/byteport-dag/Cargo.toml | 11 + crates/byteport-dag/src/dag.rs | 191 ++++ crates/byteport-dag/src/lib.rs | 38 + crates/byteport-dag/src/scheduler.rs | 231 ++++ crates/byteport-dag/src/schema.rs | 499 +++++++++ crates/byteport-dag/src/serialize.rs | 374 +++++++ crates/byteport-dag/src/topo.rs | 246 +++++ 9 files changed, 2010 insertions(+), 1113 deletions(-) create mode 100644 crates/byteport-dag/Cargo.toml create mode 100644 crates/byteport-dag/src/dag.rs create mode 100644 crates/byteport-dag/src/lib.rs create mode 100644 crates/byteport-dag/src/scheduler.rs create mode 100644 crates/byteport-dag/src/schema.rs create mode 100644 crates/byteport-dag/src/serialize.rs create mode 100644 crates/byteport-dag/src/topo.rs diff --git a/Cargo.lock b/Cargo.lock index 2d9a7d54..6fb83f64 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -36,9 +36,9 @@ checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" [[package]] name = "alloc-stdlib" -version = "0.2.2" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +checksum = "0e76a019e91224d279006ff972f1e984179a6e9feb050adba6ce8274aef23195" dependencies = [ "alloc-no-stdlib", ] @@ -129,41 +129,24 @@ checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" name = "app" version = "0.1.0" dependencies = [ - "async-trait", "byteport-transport", "clap", "clap-ext", "log", - "mockall", "serde", "serde_json", "tauri", "tauri-build", "tauri-plugin-log", - "tauri-plugin-os", - "thiserror 2.0.18", - "tokio", - "tracing", + "thiserror 1.0.69", "tracing-subscriber", - "url", ] [[package]] name = "arrayvec" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" - -[[package]] -name = "async-trait" -version = "0.1.89" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] +checksum = "f02882884d3e1bc524fb12c79f107f6ad0e1cfd498c536ffb494301740995dfe" [[package]] name = "atk" @@ -196,9 +179,9 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "base64" @@ -235,18 +218,18 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.11.1" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" dependencies = [ "serde_core", ] [[package]] name = "bitvec" -version = "1.0.1" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +checksum = "ddcec3d12c579d40898fe0a9a358a803c23e9c52ca3c425707f81c9436211837" dependencies = [ "funty", "radium", @@ -274,9 +257,9 @@ dependencies = [ [[package]] name = "borsh" -version = "1.6.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfd1e3f8955a5d7de9fab72fc8373fade9fb8a703968cb200ae3dc6cf08e185a" +checksum = "2f3f6da4992df95bbcd9af42a6c7dcb994498fc9048230405f3b36ff7cd3f145" dependencies = [ "borsh-derive", "bytes", @@ -285,22 +268,22 @@ dependencies = [ [[package]] name = "borsh-derive" -version = "1.6.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfcfdc083699101d5a7965e49925975f2f55060f94f9a05e7187be95d530ca59" +checksum = "3ae8fb4fb5740e4b2c4884ff95f5f32f5e8479db1e8fd8eb49ddbe09eb09bb7c" dependencies = [ "once_cell", - "proc-macro-crate 3.5.0", + "proc-macro-crate 3.4.0", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] name = "brotli" -version = "8.0.2" +version = "8.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" +checksum = "5cc91aac060a7a1e25823bdccbfb6af1875b88f17c6daac97894eed8207166b3" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -309,9 +292,9 @@ dependencies = [ [[package]] name = "brotli-decompressor" -version = "5.0.0" +version = "5.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" +checksum = "3a32acac15fe1967bc3986b2a6347dffc965602354ea6f450ad07e8bfd253583" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -319,18 +302,17 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.20.2" +version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" [[package]] name = "byte-unit" -version = "5.2.0" +version = "5.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c6d47a4e2961fb8721bcfc54feae6455f2f64e7054f9bc67e875f0e77f4c58d" +checksum = "e1cd29c3c585209b0cbc7309bfe3ed7efd8c84c21b7af29c8bfae908f8777174" dependencies = [ "rust_decimal", - "schemars 1.2.1", "serde", "utf8-width", ] @@ -369,6 +351,16 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "byteport-dag" +version = "0.1.0" +dependencies = [ + "serde", + "serde_json", + "serde_yaml", + "thiserror 2.0.18", +] + [[package]] name = "byteport-transport" version = "0.1.0" @@ -379,9 +371,9 @@ dependencies = [ [[package]] name = "bytes" -version = "1.11.1" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593" dependencies = [ "serde", ] @@ -392,7 +384,7 @@ version = "0.18.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "cairo-sys-rs", "glib", "libc", @@ -413,34 +405,34 @@ dependencies = [ [[package]] name = "camino" -version = "1.2.2" +version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48" +checksum = "b4ce8d3bd5823c7504d3f579f13e7b2f3da252fcb938c594d5680ee508bf846f" dependencies = [ "serde_core", ] [[package]] name = "cargo-platform" -version = "0.1.9" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" +checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc" dependencies = [ "serde", ] [[package]] name = "cargo_metadata" -version = "0.19.2" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba" +checksum = "afc309ed89476c8957c50fb818f56fe894db857866c3e163335faa91dc34eb85" dependencies = [ "camino", "cargo-platform", "semver", "serde", "serde_json", - "thiserror 2.0.18", + "thiserror 1.0.69", ] [[package]] @@ -455,9 +447,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.61" +version = "1.2.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d" +checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96" dependencies = [ "find-msvc-tools", "shlex", @@ -504,9 +496,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" -version = "0.4.44" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" dependencies = [ "iana-time-zone", "num-traits", @@ -516,9 +508,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.6.1" +version = "4.5.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" +checksum = "52fa72306bb30daf11bc97773431628e5b4916e97aaa74b7d3f625d4d495da02" dependencies = [ "clap_builder", "clap_derive", @@ -538,9 +530,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.6.0" +version = "4.5.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" +checksum = "2071365c5c56eae7d77414029dde2f4f4ba151cf68d5a3261c9a40de428ace93" dependencies = [ "anstream", "anstyle", @@ -551,21 +543,21 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.6.1" +version = "4.5.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" +checksum = "dec5be1eea072311774b7b84ded287adbd9f293f9d23456817605c6042f4f5e0" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] name = "clap_lex" -version = "1.1.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" +checksum = "0e78417baa3b3114dc0e95e7357389a249c4da97c3c2b540700079db6171bfd7" [[package]] name = "colorchoice" @@ -583,12 +575,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "convert_case" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" - [[package]] name = "cookie" version = "0.18.1" @@ -621,7 +607,7 @@ version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "core-foundation", "core-graphics-types", "foreign-types", @@ -634,7 +620,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "core-foundation", "libc", ] @@ -682,23 +668,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "cssparser" -version = "0.29.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93d03419cb5950ccfd3daf3ff1c7a36ace64609a1a8746d493df1ca0afde0fa" -dependencies = [ - "cssparser-macros", - "dtoa-short", - "itoa", - "matches", - "phf 0.10.1", - "proc-macro2", - "quote", - "smallvec", - "syn 1.0.109", -] - [[package]] name = "cssparser" version = "0.36.0" @@ -708,7 +677,7 @@ dependencies = [ "cssparser-macros", "dtoa-short", "itoa", - "phf 0.13.1", + "phf", "smallvec", ] @@ -719,7 +688,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" dependencies = [ "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -740,9 +709,9 @@ checksum = "52560adf09603e58c9a7ee1fe1dcb95a16927b17c127f0ac02d6e768a0e25bc1" [[package]] name = "darling" -version = "0.23.0" +version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" dependencies = [ "darling_core", "darling_macro", @@ -750,26 +719,27 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.23.0" +version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" dependencies = [ + "fnv", "ident_case", "proc-macro2", "quote", "strsim", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] name = "darling_macro" -version = "0.23.0" +version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" dependencies = [ "darling_core", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -785,46 +755,32 @@ dependencies = [ [[package]] name = "deranged" -version = "0.5.8" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" dependencies = [ "powerfmt", - "serde_core", -] - -[[package]] -name = "derive_more" -version = "0.99.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" -dependencies = [ - "convert_case", - "proc-macro2", - "quote", - "rustc_version", - "syn 2.0.117", + "serde", ] [[package]] name = "derive_more" -version = "2.1.1" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" dependencies = [ "derive_more-impl", ] [[package]] name = "derive_more-impl" -version = "2.1.1" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" dependencies = [ "proc-macro2", "quote", - "rustc_version", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -864,7 +820,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "block2", "libc", "objc2", @@ -872,20 +828,20 @@ dependencies = [ [[package]] name = "displaydoc" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] name = "dlopen2" -version = "0.8.2" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e2c5bd4158e66d1e215c49b837e11d62f3267b30c92f1d171c4d3105e3dc4d4" +checksum = "8d65cde5fb0c42a3d5882d99807698b459f5928de035fa7f547c784fb7b34219" dependencies = [ "dlopen2_derive", "libc", @@ -895,13 +851,13 @@ dependencies = [ [[package]] name = "dlopen2_derive" -version = "0.4.3" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fbbb781877580993a8707ec48672673ec7b81eeba04cfd2310bd28c08e47c8f" +checksum = "95f4a04e1bfbfa4835a6073177aafb95ead4de0722dbb339195fdc7e0a09599b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -911,20 +867,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "521e380c0c8afb8d9a1e83a1822ee03556fc3e3e7dbc1fd30be14e37f9cb3f89" dependencies = [ "bit-set", - "cssparser 0.36.0", - "foldhash 0.2.0", - "html5ever 0.38.0", + "cssparser", + "foldhash", + "html5ever", "precomputed-hash", - "selectors 0.36.1", - "tendril 0.5.0", + "selectors", + "tendril", ] -[[package]] -name = "downcast" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" - [[package]] name = "dpi" version = "0.1.2" @@ -985,7 +935,7 @@ dependencies = [ "cc", "memchr", "rustc_version", - "toml 1.1.2+spec-1.1.0", + "toml 1.0.7+spec-1.1.0", "vswhom", "winreg", ] @@ -1089,12 +1039,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foldhash" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" - [[package]] name = "foldhash" version = "0.2.0" @@ -1119,7 +1063,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -1137,31 +1081,12 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "fragile" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8878864ba14bb86e818a412bfd6f18f9eabd4ec0f008a28e8f7eb61db532fcf9" -dependencies = [ - "futures-core", -] - [[package]] name = "funty" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" -[[package]] -name = "futf" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" -dependencies = [ - "mac", - "new_debug_unreachable", -] - [[package]] name = "futures-channel" version = "0.3.32" @@ -1202,7 +1127,7 @@ checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -1233,15 +1158,6 @@ dependencies = [ "slab", ] -[[package]] -name = "fxhash" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" -dependencies = [ - "byteorder", -] - [[package]] name = "gdk" version = "0.18.2" @@ -1351,27 +1267,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "gethostname" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8" -dependencies = [ - "rustix", - "windows-link 0.2.1", -] - -[[package]] -name = "getrandom" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" -dependencies = [ - "cfg-if", - "libc", - "wasi 0.9.0+wasi-snapshot-preview1", -] - [[package]] name = "getrandom" version = "0.2.17" @@ -1380,7 +1275,7 @@ checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", "libc", - "wasi 0.11.1+wasi-snapshot-preview1", + "wasi", ] [[package]] @@ -1391,23 +1286,10 @@ checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", - "r-efi 5.3.0", + "r-efi", "wasip2", ] -[[package]] -name = "getrandom" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" -dependencies = [ - "cfg-if", - "libc", - "r-efi 6.0.0", - "wasip2", - "wasip3", -] - [[package]] name = "gio" version = "0.18.4" @@ -1446,7 +1328,7 @@ version = "0.18.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "futures-channel", "futures-core", "futures-executor", @@ -1474,7 +1356,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -1553,7 +1435,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -1567,18 +1449,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" -dependencies = [ - "foldhash 0.1.5", -] - -[[package]] -name = "hashbrown" -version = "0.17.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" [[package]] name = "heck" @@ -1598,18 +1471,6 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -[[package]] -name = "html5ever" -version = "0.29.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b7410cae13cbc75623c98ac4cbfd1f0bedddf3227afc24f370cf0f50a44a11c" -dependencies = [ - "log", - "mac", - "markup5ever 0.14.1", - "match_token", -] - [[package]] name = "html5ever" version = "0.38.0" @@ -1617,14 +1478,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1054432bae2f14e0061e33d23402fbaa67a921d319d56adc6bcf887ddad1cbc2" dependencies = [ "log", - "markup5ever 0.38.0", + "markup5ever", ] [[package]] name = "http" -version = "1.4.0" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" dependencies = [ "bytes", "itoa", @@ -1661,9 +1522,9 @@ checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "hyper" -version = "1.9.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" +checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498" dependencies = [ "atomic-waker", "bytes", @@ -1738,23 +1599,21 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.2.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" dependencies = [ "displaydoc", - "potential_utf", - "utf8_iter", "yoke", "zerofrom", "zerovec", ] [[package]] -name = "icu_locale_core" -version = "2.2.0" +name = "icu_locid" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" dependencies = [ "displaydoc", "litemap", @@ -1763,66 +1622,98 @@ dependencies = [ "zerovec", ] +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7515e6d781098bf9f7205ab3fc7e9709d34554ae0b21ddbcb5febfa4bc7df11d" + [[package]] name = "icu_normalizer" -version = "2.2.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" dependencies = [ + "displaydoc", "icu_collections", "icu_normalizer_data", "icu_properties", "icu_provider", "smallvec", + "utf16_iter", + "utf8_iter", + "write16", "zerovec", ] [[package]] name = "icu_normalizer_data" -version = "2.2.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" +checksum = "c5e8338228bdc8ab83303f16b797e177953730f601a96c25d10cb3ab0daa0cb7" [[package]] name = "icu_properties" -version = "2.2.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" dependencies = [ + "displaydoc", "icu_collections", - "icu_locale_core", + "icu_locid_transform", "icu_properties_data", "icu_provider", - "zerotrie", + "tinystr", "zerovec", ] [[package]] name = "icu_properties_data" -version = "2.2.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" +checksum = "85fb8799753b75aee8d2a21d7c14d9f38921b54b3dbda10f5a3c7a7b82dba5e2" [[package]] name = "icu_provider" -version = "2.2.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" dependencies = [ "displaydoc", - "icu_locale_core", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", "writeable", "yoke", "zerofrom", - "zerotrie", "zerovec", ] [[package]] -name = "id-arena" -version = "2.3.0" +name = "icu_provider_macros" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] [[package]] name = "ident_case" @@ -1843,9 +1734,9 @@ dependencies = [ [[package]] name = "idna_adapter" -version = "1.2.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" dependencies = [ "icu_normalizer", "icu_properties", @@ -1864,12 +1755,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.14.0" +version = "2.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" dependencies = [ "equivalent", - "hashbrown 0.17.0", + "hashbrown 0.16.1", "serde", "serde_core", ] @@ -1889,16 +1780,6 @@ version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" -[[package]] -name = "iri-string" -version = "0.7.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20" -dependencies = [ - "memchr", - "serde", -] - [[package]] name = "is_terminal_polyfill" version = "1.70.2" @@ -1975,18 +1856,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" dependencies = [ "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] name = "js-sys" -version = "0.3.95" +version = "0.3.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca" +checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" dependencies = [ "cfg-if", "futures-util", - "once_cell", "wasm-bindgen", ] @@ -2018,35 +1898,17 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "serde", "unicode-segmentation", ] -[[package]] -name = "kuchikiki" -version = "0.8.8-speedreader" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02cb977175687f33fa4afa0c95c112b987ea1443e5a51c8f8ff27dc618270cc2" -dependencies = [ - "cssparser 0.29.6", - "html5ever 0.29.1", - "indexmap 2.14.0", - "selectors 0.24.0", -] - [[package]] name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" -[[package]] -name = "leb128fmt" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" - [[package]] name = "libappindicator" version = "0.9.0" @@ -2098,9 +1960,9 @@ dependencies = [ [[package]] name = "libredox" -version = "0.1.16" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" +checksum = "f02ab6bace2054fb888a3c16f990117b579d14a3088e472d63c6011fa185c9d3" dependencies = [ "libc", ] @@ -2113,9 +1975,9 @@ checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litemap" -version = "0.8.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" +checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" [[package]] name = "lock_api" @@ -2128,33 +1990,13 @@ dependencies = [ [[package]] name = "log" -version = "0.4.32" +version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" dependencies = [ "value-bag", ] -[[package]] -name = "mac" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" - -[[package]] -name = "markup5ever" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7a7213d12e1864c0f002f52c2923d4556935a43dec5e71355c2760e0f6e7a18" -dependencies = [ - "log", - "phf 0.11.3", - "phf_codegen 0.11.3", - "string_cache 0.8.9", - "string_cache_codegen 0.5.4", - "tendril 0.4.3", -] - [[package]] name = "markup5ever" version = "0.38.0" @@ -2162,21 +2004,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8983d30f2915feeaaab2d6babdd6bc7e9ed1a00b66b5e6d74df19aa9c0e91862" dependencies = [ "log", - "tendril 0.5.0", + "tendril", "web_atoms", ] -[[package]] -name = "match_token" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88a9689d8d44bf9964484516275f5cd4c9b59457a6940c1d5d0ecbb94510a36b" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - [[package]] name = "matchers" version = "0.2.0" @@ -2186,17 +2017,11 @@ dependencies = [ "regex-automata", ] -[[package]] -name = "matches" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" - [[package]] name = "memchr" -version = "2.8.0" +version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" [[package]] name = "memoffset" @@ -2225,46 +2050,20 @@ dependencies = [ [[package]] name = "mio" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" +checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" dependencies = [ "libc", - "wasi 0.11.1+wasi-snapshot-preview1", + "wasi", "windows-sys 0.61.2", ] -[[package]] -name = "mockall" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39a6bfcc6c8c7eed5ee98b9c3e33adc726054389233e201c95dab2d41a3839d2" -dependencies = [ - "cfg-if", - "downcast", - "fragile", - "mockall_derive", - "predicates", - "predicates-tree", -] - -[[package]] -name = "mockall_derive" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25ca3004c2efe9011bd4e461bd8256445052b9615405b4f7ea43fc8ca5c20898" -dependencies = [ - "cfg-if", - "proc-macro2", - "quote", - "syn 2.0.117", -] - [[package]] name = "muda" -version = "0.19.1" +version = "0.19.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ae8844f63b5b118e334e205585b8c5c17b984121dbdb179d44aeb087ffad3cb" +checksum = "1dd04e60bc0b07438a6771710ee1698f98f6ebbc7f89b61264af1563b8aeb878" dependencies = [ "crossbeam-channel", "dpi", @@ -2287,7 +2086,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "jni-sys 0.3.1", "log", "ndk-sys", @@ -2311,24 +2110,6 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" -[[package]] -name = "nix" -version = "0.30.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" -dependencies = [ - "bitflags 2.11.1", - "cfg-if", - "cfg_aliases", - "libc", -] - -[[package]] -name = "nodrop" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" - [[package]] name = "nu-ansi-term" version = "0.50.3" @@ -2340,9 +2121,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.2.1" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" [[package]] name = "num-traits" @@ -2369,10 +2150,10 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" dependencies = [ - "proc-macro-crate 2.0.2", + "proc-macro-crate 3.4.0", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -2400,7 +2181,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "block2", "objc2", "objc2-core-foundation", @@ -2413,7 +2194,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "objc2", "objc2-foundation", ] @@ -2434,7 +2215,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "dispatch2", "objc2", ] @@ -2445,7 +2226,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "dispatch2", "objc2", "objc2-core-foundation", @@ -2478,7 +2259,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "objc2", "objc2-core-foundation", "objc2-core-graphics", @@ -2505,9 +2286,8 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "block2", - "libc", "objc2", "objc2-core-foundation", ] @@ -2518,7 +2298,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "objc2", "objc2-core-foundation", ] @@ -2529,7 +2309,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "objc2", "objc2-core-foundation", "objc2-foundation", @@ -2541,7 +2321,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "block2", "objc2", "objc2-cloud-kit", @@ -2572,7 +2352,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2e5aaab980c433cf470df9d7af96a7b46a9d892d521a2cbbb2f8a4c16751e7f" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "block2", "objc2", "objc2-app-kit", @@ -2598,22 +2378,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" -[[package]] -name = "os_info" -version = "3.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4022a17595a00d6a369236fdae483f0de7f0a339960a53118b818238e132224" -dependencies = [ - "android_system_properties", - "log", - "nix", - "objc2", - "objc2-foundation", - "objc2-ui-kit", - "serde", - "windows-sys 0.61.2", -] - [[package]] name = "pango" version = "0.18.3" @@ -2668,104 +2432,25 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" -[[package]] -name = "phf" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" -dependencies = [ - "phf_shared 0.8.0", -] - -[[package]] -name = "phf" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" -dependencies = [ - "phf_macros 0.10.0", - "phf_shared 0.10.0", - "proc-macro-hack", -] - -[[package]] -name = "phf" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" -dependencies = [ - "phf_shared 0.11.3", -] - [[package]] name = "phf" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" dependencies = [ - "phf_macros 0.13.1", - "phf_shared 0.13.1", + "phf_macros", + "phf_shared", "serde", ] -[[package]] -name = "phf_codegen" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" -dependencies = [ - "phf_generator 0.8.0", - "phf_shared 0.8.0", -] - -[[package]] -name = "phf_codegen" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" -dependencies = [ - "phf_generator 0.11.3", - "phf_shared 0.11.3", -] - [[package]] name = "phf_codegen" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49aa7f9d80421bca176ca8dbfebe668cc7a2684708594ec9f3c0db0805d5d6e1" dependencies = [ - "phf_generator 0.13.1", - "phf_shared 0.13.1", -] - -[[package]] -name = "phf_generator" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" -dependencies = [ - "phf_shared 0.8.0", - "rand 0.7.3", -] - -[[package]] -name = "phf_generator" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" -dependencies = [ - "phf_shared 0.10.0", - "rand 0.8.6", -] - -[[package]] -name = "phf_generator" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" -dependencies = [ - "phf_shared 0.11.3", - "rand 0.8.6", + "phf_generator", + "phf_shared", ] [[package]] @@ -2775,21 +2460,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" dependencies = [ "fastrand", - "phf_shared 0.13.1", -] - -[[package]] -name = "phf_macros" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58fdf3184dd560f160dd73922bea2d5cd6e8f064bf4b13110abd81b03697b4e0" -dependencies = [ - "phf_generator 0.10.0", - "phf_shared 0.10.0", - "proc-macro-hack", - "proc-macro2", - "quote", - "syn 1.0.109", + "phf_shared", ] [[package]] @@ -2798,38 +2469,11 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" dependencies = [ - "phf_generator 0.13.1", - "phf_shared 0.13.1", + "phf_generator", + "phf_shared", "proc-macro2", "quote", - "syn 2.0.117", -] - -[[package]] -name = "phf_shared" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" -dependencies = [ - "siphasher 0.3.11", -] - -[[package]] -name = "phf_shared" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" -dependencies = [ - "siphasher 0.3.11", -] - -[[package]] -name = "phf_shared" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" -dependencies = [ - "siphasher 1.0.2", + "syn 2.0.118", ] [[package]] @@ -2838,7 +2482,7 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" dependencies = [ - "siphasher 1.0.2", + "siphasher", ] [[package]] @@ -2855,12 +2499,12 @@ checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" [[package]] name = "plist" -version = "1.9.0" +version = "1.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "092791278e026273c1b65bbdcfbba3a300f2994c896bd01ab01da613c29c46f1" +checksum = "3af6b589e163c5a788fab00ce0c0366f6efbb9959c2f9874b224936af7fce7e1" dependencies = [ "base64 0.22.1", - "indexmap 2.14.0", + "indexmap 2.11.4", "quick-xml", "serde", "time", @@ -2885,22 +2529,13 @@ version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "crc32fast", "fdeflate", "flate2", "miniz_oxide", ] -[[package]] -name = "potential_utf" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" -dependencies = [ - "zerovec", -] - [[package]] name = "powerfmt" version = "0.2.0" @@ -2922,42 +2557,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" -[[package]] -name = "predicates" -version = "3.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ada8f2932f28a27ee7b70dd6c1c39ea0675c55a36879ab92f3a715eaa1e63cfe" -dependencies = [ - "anstyle", - "predicates-core", -] - -[[package]] -name = "predicates-core" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cad38746f3166b4031b1a0d39ad9f954dd291e7854fcc0eed52ee41a0b50d144" - -[[package]] -name = "predicates-tree" -version = "1.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0de1b847b39c8131db0467e9df1ff60e6d0562ab8e9a16e568ad0fdb372e2f2" -dependencies = [ - "predicates-core", - "termtree", -] - -[[package]] -name = "prettyplease" -version = "0.2.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" -dependencies = [ - "proc-macro2", - "syn 2.0.117", -] - [[package]] name = "proc-macro-crate" version = "1.3.1" @@ -2980,11 +2579,11 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "3.5.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" dependencies = [ - "toml_edit 0.25.11+spec-1.1.0", + "toml_edit 0.23.10+spec-1.0.0", ] [[package]] @@ -3011,12 +2610,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "proc-macro-hack" -version = "0.5.20+deprecated" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" - [[package]] name = "proc-macro2" version = "1.0.106" @@ -3048,18 +2641,18 @@ dependencies = [ [[package]] name = "quick-xml" -version = "0.39.2" +version = "0.38.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "958f21e8e7ceb5a1aa7fa87fab28e7c75976e0bfe7e23ff069e0a260f894067d" +checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c" dependencies = [ "memchr", ] [[package]] name = "quote" -version = "1.0.45" +version = "1.0.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" dependencies = [ "proc-macro2", ] @@ -3070,51 +2663,21 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" -[[package]] -name = "r-efi" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" - [[package]] name = "radium" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" -[[package]] -name = "rand" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" -dependencies = [ - "getrandom 0.1.16", - "libc", - "rand_chacha 0.2.2", - "rand_core 0.5.1", - "rand_hc", - "rand_pcg", -] - [[package]] name = "rand" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" dependencies = [ - "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_chacha" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" -dependencies = [ - "ppv-lite86", - "rand_core 0.5.1", + "libc", + "rand_chacha", + "rand_core", ] [[package]] @@ -3124,16 +2687,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_core" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" -dependencies = [ - "getrandom 0.1.16", + "rand_core", ] [[package]] @@ -3145,24 +2699,6 @@ dependencies = [ "getrandom 0.2.17", ] -[[package]] -name = "rand_hc" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" -dependencies = [ - "rand_core 0.5.1", -] - -[[package]] -name = "rand_pcg" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" -dependencies = [ - "rand_core 0.5.1", -] - [[package]] name = "raw-window-handle" version = "0.6.2" @@ -3175,7 +2711,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", ] [[package]] @@ -3206,14 +2742,14 @@ checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] name = "regex" -version = "1.12.3" +version = "1.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" dependencies = [ "aho-corasick", "memchr", @@ -3234,9 +2770,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" [[package]] name = "rend" @@ -3249,9 +2785,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.13.2" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" +checksum = "62e0021ea2c22aed41653bc7e1419abb2c97e038ff2c33d0e1309e49a97deec0" dependencies = [ "base64 0.22.1", "bytes", @@ -3312,15 +2848,15 @@ dependencies = [ [[package]] name = "rust_decimal" -version = "1.41.0" +version = "1.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ce901f9a19d251159075a4c37af514c3b8ef99c22e02dd8c19161cf397ee94a" +checksum = "be2a24f50780bc85f09cc6ac299bdf1424302742d77221106859c9d8b102126a" dependencies = [ "arrayvec", "borsh", "bytes", "num-traits", - "rand 0.8.6", + "rand", "rkyv", "serde", "serde_json", @@ -3348,7 +2884,7 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "errno", "libc", "linux-raw-sys", @@ -3361,6 +2897,12 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + [[package]] name = "same-file" version = "1.0.6" @@ -3418,7 +2960,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -3433,40 +2975,22 @@ version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" -[[package]] -name = "selectors" -version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c37578180969d00692904465fb7f6b3d50b9a2b952b87c23d0e2e5cb5013416" -dependencies = [ - "bitflags 1.3.2", - "cssparser 0.29.6", - "derive_more 0.99.20", - "fxhash", - "log", - "phf 0.8.0", - "phf_codegen 0.8.0", - "precomputed-hash", - "servo_arc 0.2.0", - "smallvec", -] - [[package]] name = "selectors" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5d9c0c92a92d33f08817311cf3f2c29a3538a8240e94a6a3c622ce652d7e00c" dependencies = [ - "bitflags 2.11.1", - "cssparser 0.36.0", - "derive_more 2.1.1", + "bitflags 2.13.0", + "cssparser", + "derive_more", "log", "new_debug_unreachable", - "phf 0.13.1", - "phf_codegen 0.13.1", + "phf", + "phf_codegen", "precomputed-hash", "rustc-hash", - "servo_arc 0.4.3", + "servo_arc", "smallvec", ] @@ -3519,7 +3043,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -3530,7 +3054,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -3554,7 +3078,7 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -3568,24 +3092,24 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "1.1.1" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" +checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" dependencies = [ "serde_core", ] [[package]] name = "serde_with" -version = "3.18.0" +version = "3.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd5414fad8e6907dbdd5bc441a50ae8d6e26151a03b1de04d89a5576de61d01f" +checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7" dependencies = [ "base64 0.22.1", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.14.0", + "indexmap 2.11.4", "schemars 0.9.0", "schemars 1.2.1", "serde_core", @@ -3596,14 +3120,27 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.18.0" +version = "3.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3db8978e608f1fe7357e211969fd9abdcae80bac1ba7a3369bb7eb6b404eb65" +checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c" dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", +] + +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap 2.11.4", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", ] [[package]] @@ -3625,17 +3162,7 @@ checksum = "772ee033c0916d670af7860b6e1ef7d658a4629a6d0b4c8c3e67f09b3765b75d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", -] - -[[package]] -name = "servo_arc" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52aa42f8fdf0fed91e5ce7f23d8138441002fa31dca008acf47e6fd4721f741" -dependencies = [ - "nodrop", - "stable_deref_trait", + "syn 2.0.118", ] [[package]] @@ -3667,20 +3194,11 @@ dependencies = [ "lazy_static", ] -[[package]] -name = "sharded-slab" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" -dependencies = [ - "lazy_static", -] - [[package]] name = "shlex" -version = "1.3.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" [[package]] name = "simd-adler32" @@ -3696,15 +3214,9 @@ checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" [[package]] name = "siphasher" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" - -[[package]] -name = "siphasher" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" +checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" [[package]] name = "slab" @@ -3714,9 +3226,9 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" -version = "1.15.1" +version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" [[package]] name = "socket2" @@ -3782,19 +3294,6 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" -[[package]] -name = "string_cache" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" -dependencies = [ - "new_debug_unreachable", - "parking_lot", - "phf_shared 0.11.3", - "precomputed-hash", - "serde", -] - [[package]] name = "string_cache" version = "0.9.0" @@ -3803,30 +3302,18 @@ checksum = "a18596f8c785a729f2819c0f6a7eae6ebeebdfffbfe4214ae6b087f690e31901" dependencies = [ "new_debug_unreachable", "parking_lot", - "phf_shared 0.13.1", + "phf_shared", "precomputed-hash", ] -[[package]] -name = "string_cache_codegen" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" -dependencies = [ - "phf_generator 0.11.3", - "phf_shared 0.11.3", - "proc-macro2", - "quote", -] - [[package]] name = "string_cache_codegen" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "585635e46db231059f76c5849798146164652513eb9e8ab2685939dd90f29b69" dependencies = [ - "phf_generator 0.13.1", - "phf_shared 0.13.1", + "phf_generator", + "phf_shared", "proc-macro2", "quote", ] @@ -3861,9 +3348,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.117" +version = "2.0.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" dependencies = [ "proc-macro2", "quote", @@ -3887,16 +3374,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", -] - -[[package]] -name = "sys-locale" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eab9a99a024a169fe8a903cf9d4a3b3601109bcc13bd9e3c6fff259138626c4" -dependencies = [ - "libc", + "syn 2.0.118", ] [[package]] @@ -3914,11 +3392,11 @@ dependencies = [ [[package]] name = "tao" -version = "0.35.2" +version = "0.35.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a33f7f9e486ade65fcf1e45c440f9236c904f5c1002cdc7fc6ae582777345ce4" +checksum = "d1c93047acf68669466a34690ac58cca7010bd1b201e1ec86f1fd0a75d3dd4a9" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "block2", "core-foundation", "core-graphics", @@ -3960,7 +3438,7 @@ checksum = "f4e16beb8b2ac17db28eab8bca40e62dbfbb34c0fcdc6d9826b11b7b5d047dfd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -4065,7 +3543,7 @@ dependencies = [ "serde", "serde_json", "sha2", - "syn 2.0.117", + "syn 2.0.118", "tauri-utils", "thiserror 2.0.18", "time", @@ -4083,16 +3561,16 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", "tauri-codegen", "tauri-utils", ] [[package]] name = "tauri-plugin" -version = "2.5.4" +version = "2.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddde7d51c907b940fb573006cdda9a642d6a7c8153657e88f8a5c3c9290cd4aa" +checksum = "74be5dd4bed9afbd145e5716b5fa2ec28cbc29c34ffa61c258c9273d896c8020" dependencies = [ "anyhow", "glob", @@ -4101,7 +3579,6 @@ dependencies = [ "serde", "serde_json", "tauri-utils", - "toml 0.9.12+spec-1.1.0", "walkdir", ] @@ -4127,24 +3604,6 @@ dependencies = [ "time", ] -[[package]] -name = "tauri-plugin-os" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8f08346c8deb39e96f86973da0e2d76cbb933d7ac9b750f6dc4daf955a6f997" -dependencies = [ - "gethostname", - "log", - "os_info", - "serde", - "serde_json", - "serialize-to-javascript", - "sys-locale", - "tauri", - "tauri-plugin", - "thiserror 2.0.18", -] - [[package]] name = "tauri-runtime" version = "2.11.3" @@ -4209,14 +3668,12 @@ dependencies = [ "dom_query", "dunce", "glob", - "html5ever 0.29.1", "http", "infer", "json-patch", - "kuchikiki", "log", "memchr", - "phf 0.13.1", + "phf", "plist", "proc-macro2", "quote", @@ -4229,7 +3686,7 @@ dependencies = [ "serde_with", "swift-rs", "thiserror 2.0.18", - "toml 0.9.12+spec-1.1.0", + "toml 1.0.7+spec-1.1.0", "url", "urlpattern", "uuid", @@ -4244,18 +3701,7 @@ checksum = "cc65d45c68858bfe420dd29e834b5d15dbecf8a07a8a16cf4d532c7b1f69d4b6" dependencies = [ "dunce", "embed-resource", - "toml 1.1.2+spec-1.1.0", -] - -[[package]] -name = "tendril" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" -dependencies = [ - "futf", - "mac", - "utf-8", + "toml 1.0.7+spec-1.1.0", ] [[package]] @@ -4275,15 +3721,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874" dependencies = [ "rustix", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] -[[package]] -name = "termtree" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" - [[package]] name = "thiserror" version = "1.0.69" @@ -4310,7 +3750,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -4321,7 +3761,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -4335,9 +3775,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.47" +version = "0.3.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" dependencies = [ "deranged", "itoa", @@ -4345,22 +3785,22 @@ dependencies = [ "num-conv", "num_threads", "powerfmt", - "serde_core", + "serde", "time-core", "time-macros", ] [[package]] name = "time-core" -version = "0.1.8" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" +checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" [[package]] name = "time-macros" -version = "0.2.27" +version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" dependencies = [ "num-conv", "time-core", @@ -4368,9 +3808,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.3" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" dependencies = [ "displaydoc", "zerovec", @@ -4402,21 +3842,9 @@ dependencies = [ "mio", "pin-project-lite", "socket2", - "tokio-macros", "windows-sys 0.61.2", ] -[[package]] -name = "tokio-macros" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - [[package]] name = "tokio-util" version = "0.7.18" @@ -4448,9 +3876,9 @@ version = "0.9.12+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" dependencies = [ - "indexmap 2.14.0", + "indexmap 2.11.4", "serde_core", - "serde_spanned 1.1.1", + "serde_spanned 1.0.4", "toml_datetime 0.7.5+spec-1.1.0", "toml_parser", "toml_writer", @@ -4459,17 +3887,17 @@ dependencies = [ [[package]] name = "toml" -version = "1.1.2+spec-1.1.0" +version = "1.0.7+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee" +checksum = "dd28d57d8a6f6e458bc0b8784f8fdcc4b99a437936056fa122cb234f18656a96" dependencies = [ - "indexmap 2.14.0", + "indexmap 2.11.4", "serde_core", - "serde_spanned 1.1.1", - "toml_datetime 1.1.1+spec-1.1.0", + "serde_spanned 1.0.4", + "toml_datetime 1.0.1+spec-1.1.0", "toml_parser", "toml_writer", - "winnow 1.0.2", + "winnow 1.0.3", ] [[package]] @@ -4492,9 +3920,9 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "1.1.1+spec-1.1.0" +version = "1.0.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +checksum = "9b320e741db58cac564e26c607d3cc1fdc4a88fd36c879568c07856ed83ff3e9" dependencies = [ "serde_core", ] @@ -4505,7 +3933,7 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.14.0", + "indexmap 2.11.4", "toml_datetime 0.6.3", "winnow 0.5.40", ] @@ -4516,7 +3944,7 @@ version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" dependencies = [ - "indexmap 2.14.0", + "indexmap 2.11.4", "serde", "serde_spanned 0.6.9", "toml_datetime 0.6.3", @@ -4525,30 +3953,30 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.25.11+spec-1.1.0" +version = "0.23.10+spec-1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" +checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" dependencies = [ - "indexmap 2.14.0", - "toml_datetime 1.1.1+spec-1.1.0", + "indexmap 2.11.4", + "toml_datetime 0.7.5+spec-1.1.0", "toml_parser", - "winnow 1.0.2", + "winnow 0.7.15", ] [[package]] name = "toml_parser" -version = "1.1.2+spec-1.1.0" +version = "1.0.10+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +checksum = "7df25b4befd31c4816df190124375d5a20c6b6921e2cad937316de3fccd63420" dependencies = [ - "winnow 1.0.2", + "winnow 1.0.3", ] [[package]] name = "toml_writer" -version = "1.1.1+spec-1.1.0" +version = "1.0.7+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" +checksum = "f17aaa1c6e3dc22b1da4b6bba97d066e354c7945cac2f7852d4e4e7ca7a6b56d" [[package]] name = "tower" @@ -4567,20 +3995,20 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.8" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "bytes", "futures-util", "http", "http-body", - "iri-string", "pin-project-lite", "tower", "tower-layer", "tower-service", + "url", ] [[package]] @@ -4614,7 +4042,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -4638,6 +4066,16 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-serde" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" +dependencies = [ + "serde", + "tracing-core", +] + [[package]] name = "tracing-subscriber" version = "0.3.23" @@ -4648,12 +4086,15 @@ dependencies = [ "nu-ansi-term", "once_cell", "regex-automata", + "serde", + "serde_json", "sharded-slab", "smallvec", "thread_local", "tracing", "tracing-core", "tracing-log", + "tracing-serde", ] [[package]] @@ -4692,9 +4133,9 @@ checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" [[package]] name = "typenum" -version = "1.20.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" [[package]] name = "unic-char-property" @@ -4745,15 +4186,15 @@ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-segmentation" -version = "1.13.2" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] -name = "unicode-xid" -version = "0.2.6" +name = "unsafe-libyaml" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" [[package]] name = "url" @@ -4786,6 +4227,12 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + [[package]] name = "utf8-width" version = "0.1.8" @@ -4806,11 +4253,11 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.23.1" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" +checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f" dependencies = [ - "getrandom 0.4.2", + "getrandom 0.3.4", "js-sys", "serde_core", "wasm-bindgen", @@ -4879,12 +4326,6 @@ dependencies = [ "try-lock", ] -[[package]] -name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" - [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" @@ -4893,27 +4334,18 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.3+wasi-0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" -dependencies = [ - "wit-bindgen 0.57.1", -] - -[[package]] -name = "wasip3" -version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +version = "1.0.4+wasi-0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" dependencies = [ - "wit-bindgen 0.51.0", + "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.118" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89" +checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" dependencies = [ "cfg-if", "once_cell", @@ -4924,9 +4356,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.68" +version = "0.4.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f371d383f2fb139252e0bfac3b81b265689bf45b6874af544ffa4c975ac1ebf8" +checksum = "c62df1340f32221cb9c54d6a27b030e3dba64361d4a95bed55f9aacb44da291d" dependencies = [ "js-sys", "wasm-bindgen", @@ -4934,9 +4366,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.118" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed" +checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4944,48 +4376,26 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.118" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904" +checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.118" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129" +checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" dependencies = [ "unicode-ident", ] -[[package]] -name = "wasm-encoder" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" -dependencies = [ - "leb128fmt", - "wasmparser", -] - -[[package]] -name = "wasm-metadata" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" -dependencies = [ - "anyhow", - "indexmap 2.14.0", - "wasm-encoder", - "wasmparser", -] - [[package]] name = "wasm-streams" version = "0.5.0" @@ -4999,23 +4409,11 @@ dependencies = [ "web-sys", ] -[[package]] -name = "wasmparser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" -dependencies = [ - "bitflags 2.11.1", - "hashbrown 0.15.5", - "indexmap 2.14.0", - "semver", -] - [[package]] name = "web-sys" -version = "0.3.95" +version = "0.3.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f2dfbb17949fa2088e5d39408c48368947b86f7834484e87b73de55bc14d97d" +checksum = "8622dcb61c0bcc9fffa6938bed81210af2da9a7e4a1a834b2e37a59b6dfb6141" dependencies = [ "js-sys", "wasm-bindgen", @@ -5023,14 +4421,14 @@ dependencies = [ [[package]] name = "web_atoms" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7cff6eef815df1834fd250e3a2ff436044d82a9f1bc1980ca1dbdf07effc538" +checksum = "075474b12bcb3d2e3d4546580e9de478eeeead668a1761e2a8860c836b7ef297" dependencies = [ - "phf 0.13.1", - "phf_codegen 0.13.1", - "string_cache 0.9.0", - "string_cache_codegen 0.6.1", + "phf", + "phf_codegen", + "string_cache", + "string_cache_codegen", ] [[package]] @@ -5099,7 +4497,7 @@ checksum = "67a921c1b6914c367b2b823cd4cde6f96beec77d30a939c8199bb377cf9b9b54" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -5213,7 +4611,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -5224,7 +4622,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -5447,15 +4845,15 @@ name = "winnow" version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" +dependencies = [ + "memchr", +] [[package]] name = "winnow" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0" -dependencies = [ - "memchr", -] +checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" [[package]] name = "winreg" @@ -5467,15 +4865,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "wit-bindgen" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" -dependencies = [ - "wit-bindgen-rust-macro", -] - [[package]] name = "wit-bindgen" version = "0.57.1" @@ -5483,89 +4872,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" [[package]] -name = "wit-bindgen-core" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" -dependencies = [ - "anyhow", - "heck 0.5.0", - "wit-parser", -] - -[[package]] -name = "wit-bindgen-rust" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" -dependencies = [ - "anyhow", - "heck 0.5.0", - "indexmap 2.14.0", - "prettyplease", - "syn 2.0.117", - "wasm-metadata", - "wit-bindgen-core", - "wit-component", -] - -[[package]] -name = "wit-bindgen-rust-macro" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" -dependencies = [ - "anyhow", - "prettyplease", - "proc-macro2", - "quote", - "syn 2.0.117", - "wit-bindgen-core", - "wit-bindgen-rust", -] - -[[package]] -name = "wit-component" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" -dependencies = [ - "anyhow", - "bitflags 2.11.1", - "indexmap 2.14.0", - "log", - "serde", - "serde_derive", - "serde_json", - "wasm-encoder", - "wasm-metadata", - "wasmparser", - "wit-parser", -] - -[[package]] -name = "wit-parser" -version = "0.244.0" +name = "write16" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" -dependencies = [ - "anyhow", - "id-arena", - "indexmap 2.14.0", - "log", - "semver", - "serde", - "serde_derive", - "serde_json", - "unicode-xid", - "wasmparser", -] +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" [[package]] name = "writeable" -version = "0.6.3" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" [[package]] name = "wry" @@ -5643,10 +4959,11 @@ dependencies = [ [[package]] name = "yoke" -version = "0.8.2" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" dependencies = [ + "serde", "stable_deref_trait", "yoke-derive", "zerofrom", @@ -5654,41 +4971,41 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.2" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", "synstructure", ] [[package]] name = "zerocopy" -version = "0.8.48" +version = "0.8.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.48" +version = "0.8.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] name = "zerofrom" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" dependencies = [ "zerofrom-derive", ] @@ -5701,26 +5018,15 @@ checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", "synstructure", ] -[[package]] -name = "zerotrie" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", -] - [[package]] name = "zerovec" -version = "0.11.6" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" dependencies = [ "yoke", "zerofrom", @@ -5729,13 +5035,13 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.3" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 37a68b5d..148014d9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,5 +2,6 @@ resolver = "3" members = [ "crates/byteport-transport", + "crates/byteport-dag", "frontend/web/src-tauri", ] diff --git a/crates/byteport-dag/Cargo.toml b/crates/byteport-dag/Cargo.toml new file mode 100644 index 00000000..2018ca22 --- /dev/null +++ b/crates/byteport-dag/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "byteport-dag" +version = "0.1.0" +edition = "2021" +description = "DAG foundation for Phenotype compute/infra automation (epic F)" + +[dependencies] +serde = { version = "1", features = ["derive"] } +serde_json = "1" +serde_yaml = "0.9" +thiserror = "2" diff --git a/crates/byteport-dag/src/dag.rs b/crates/byteport-dag/src/dag.rs new file mode 100644 index 00000000..b343cafa --- /dev/null +++ b/crates/byteport-dag/src/dag.rs @@ -0,0 +1,191 @@ +//! Core DAG data structure. +//! +//! A generic directed acyclic graph backed by an adjacency list. + +use std::collections::{HashMap, HashSet}; +use std::hash::Hash; + +/// Error types for DAG operations. +#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)] +pub enum DagError { + #[error("node `{0:?}` already exists")] + NodeAlreadyExists(String), + + #[error("node `{0:?}` not found")] + NodeNotFound(String), + + #[error("edge from `{0:?}` to `{1:?}` would create a cycle")] + CycleDetected(String, String), +} + +/// A generic directed acyclic graph over node identifiers of type `K`. +#[derive(Debug, Clone)] +pub struct Dag { + /// All known nodes. + nodes: HashSet, + /// Adjacency list: parent -> children (forward edges). + children: HashMap>, + /// Reverse adjacency list: child -> parents (back edges). + parents: HashMap>, +} + +// --------------------------------------------------------------------------- +// Construction +// --------------------------------------------------------------------------- + +impl Default for Dag { + fn default() -> Self { + Self { + nodes: HashSet::new(), + children: HashMap::new(), + parents: HashMap::new(), + } + } +} + +impl Dag +where + K: Eq + Hash + Clone + std::fmt::Debug, +{ + /// Create an empty DAG. + pub fn new() -> Self { + Self::default() + } + + /// Insert a new node. Returns an error if the node already exists. + pub fn add_node(&mut self, node: K) -> Result<(), DagError> { + if self.nodes.contains(&node) { + return Err(DagError::NodeAlreadyExists(format!("{:?}", node))); + } + self.nodes.insert(node.clone()); + self.children.entry(node.clone()).or_default(); + self.parents.entry(node).or_default(); + Ok(()) + } + + /// Insert a directed edge `from -> to`. + /// + /// Both endpoints must already exist. + pub fn add_edge(&mut self, from: K, to: K) -> Result<(), DagError> { + if !self.nodes.contains(&from) { + return Err(DagError::NodeNotFound(format!("{:?}", from))); + } + if !self.nodes.contains(&to) { + return Err(DagError::NodeNotFound(format!("{:?}", to))); + } + self.children.entry(from.clone()).or_default().push(to.clone()); + self.parents.entry(to).or_default().push(from); + Ok(()) + } + + // ----------------------------------------------------------------------- + // Accessors + // ----------------------------------------------------------------------- + + /// Return the set of all node identifiers. + pub fn nodes(&self) -> &HashSet { + &self.nodes + } + + /// Return the number of nodes. + pub fn node_count(&self) -> usize { + self.nodes.len() + } + + /// Return the number of edges. + pub fn edge_count(&self) -> usize { + self.children.values().map(|v| v.len()).sum() + } + + /// Return the children (direct successors) of `node`. + pub fn children_of(&self, node: &K) -> Option<&[K]> { + self.children.get(node).map(|v| v.as_slice()) + } + + /// Return the parents (direct predecessors) of `node`. + pub fn parents_of(&self, node: &K) -> Option<&[K]> { + self.parents.get(node).map(|v| v.as_slice()) + } + + /// Return `true` if the graph contains `node`. + pub fn contains(&self, node: &K) -> bool { + self.nodes.contains(node) + } + + /// Iterate over all nodes. + pub fn iter_nodes(&self) -> impl Iterator { + self.nodes.iter() + } +} + +// --------------------------------------------------------------------------- +// Tests +// --------------------------------------------------------------------------- + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn empty_dag() { + let dag: Dag = Dag::new(); + assert_eq!(dag.node_count(), 0); + assert_eq!(dag.edge_count(), 0); + } + + #[test] + fn add_and_query_nodes() { + let mut dag = Dag::new(); + dag.add_node(1).unwrap(); + dag.add_node(2).unwrap(); + assert_eq!(dag.node_count(), 2); + assert!(dag.contains(&1)); + assert!(dag.contains(&2)); + } + + #[test] + fn duplicate_node_error() { + let mut dag = Dag::new(); + dag.add_node("a").unwrap(); + assert!(dag.add_node("a").is_err()); + } + + #[test] + fn add_edge_missing_source() { + let mut dag = Dag::<&str>::new(); + dag.add_node("b").unwrap(); + assert!(dag.add_edge("a", "b").is_err()); + } + + #[test] + fn add_edge_missing_target() { + let mut dag = Dag::<&str>::new(); + dag.add_node("a").unwrap(); + assert!(dag.add_edge("a", "b").is_err()); + } + + #[test] + fn simple_edge() { + let mut dag = Dag::new(); + dag.add_node("a").unwrap(); + dag.add_node("b").unwrap(); + dag.add_edge("a", "b").unwrap(); + assert_eq!(dag.edge_count(), 1); + assert_eq!(dag.children_of(&"a"), Some(&["b"][..])); + assert_eq!(dag.parents_of(&"b"), Some(&["a"][..])); + } + + #[test] + fn diamond_graph() { + let mut dag = Dag::new(); + for n in ["a", "b", "c", "d"] { + dag.add_node(n).unwrap(); + } + dag.add_edge("a", "b").unwrap(); + dag.add_edge("a", "c").unwrap(); + dag.add_edge("b", "d").unwrap(); + dag.add_edge("c", "d").unwrap(); + assert_eq!(dag.node_count(), 4); + assert_eq!(dag.edge_count(), 4); + } +} diff --git a/crates/byteport-dag/src/lib.rs b/crates/byteport-dag/src/lib.rs new file mode 100644 index 00000000..db067934 --- /dev/null +++ b/crates/byteport-dag/src/lib.rs @@ -0,0 +1,38 @@ +//! # byteport-dag +//! +//! DAG foundation for Phenotype compute/infra automation (epic F). +//! +//! ## Modules +//! +//! | Module | Description | +//! |---------------|------------------------------------------------------------| +//! | [`dag`] | Generic directed-acyclic-graph data structure | +//! | [`topo`] | Topological sort (Kahn's algorithm + DFS variant) | +//! | [`scheduler`] | Parallel-bucket scheduler built on topological order | +//! | [`schema`] | Enriched node/edge schema: prereqs, acceptance, audit hooks | +//! | [`serialize`] | YAML/JSON round-trip serialization for the enriched schema | +//! +//! ## Example +//! +//! ```rust +//! use byteport_dag::dag::Dag; +//! use byteport_dag::serialize::DagSchema; +//! +//! let mut dag: Dag = Dag::new(); +//! dag.add_node("build".into()).unwrap(); +//! dag.add_node("test".into()).unwrap(); +//! dag.add_node("deploy".into()).unwrap(); +//! dag.add_edge("build".into(), "test".into()).unwrap(); +//! dag.add_edge("test".into(), "deploy".into()).unwrap(); +//! +//! let schema = DagSchema::from_dag(&dag, "2.0.0"); +//! let yaml = schema.to_yaml().unwrap(); +//! let round: DagSchema = DagSchema::from_yaml(&yaml).unwrap(); +//! assert_eq!(schema, round); +//! ``` + +pub mod dag; +pub mod topo; +pub mod scheduler; +pub mod schema; +pub mod serialize; diff --git a/crates/byteport-dag/src/scheduler.rs b/crates/byteport-dag/src/scheduler.rs new file mode 100644 index 00000000..f90edc84 --- /dev/null +++ b/crates/byteport-dag/src/scheduler.rs @@ -0,0 +1,231 @@ +//! Parallel-bucket scheduler for DAG execution. +//! +//! Given a DAG, the scheduler groups nodes into "buckets" such that all +//! nodes within a bucket can execute in parallel, and buckets are ordered +//! so that bucket *i* must complete before bucket *i+1* begins. +//! +//! # Algorithm +//! +//! 1. Compute a topological order via Kahn's algorithm. +//! 2. Assign each node a **rank** = max(parent ranks) + 1 (source nodes get +//! rank 0). +//! 3. Group nodes by rank → each rank is a parallel bucket. +//! 4. Return the list of buckets. + +use std::collections::HashMap; +use std::hash::Hash; + +use crate::dag::{Dag, DagError}; +use crate::topo; + +/// A wall of parallel-execution buckets produced by the scheduler. +/// +/// Buckets are ordered: bucket[0] must finish before bucket[1] starts, etc. +/// Within a bucket all nodes are independent and can be scheduled in parallel. +pub struct Schedule { + /// Ordered list of buckets. Each bucket is a set of node references. + pub buckets: Vec>, + /// Maximum concurrency (size of the largest bucket). + pub max_concurrency: usize, +} + +/// Compute a parallel-bucket schedule from the given DAG. +/// +/// Returns an error if the DAG contains a cycle. +pub fn schedule(dag: &Dag) -> Result, DagError> +where + K: Eq + Hash + Clone + std::fmt::Debug, +{ + // 1. Topological order (validates acyclicity). + let topo_order = topo::kahn_sort(dag)?; + + // 2. Assign ranks. + let mut rank: HashMap<&K, usize> = HashMap::with_capacity(dag.node_count()); + + for node in &topo_order { + let parent_rank = dag + .parents_of(node) + .unwrap_or(&[]) + .iter() + .filter_map(|p| rank.get(p)) + .max() + .copied() + .unwrap_or(0); + + rank.insert(node, parent_rank + 1); + } + + // 3. Group by rank into buckets. + let max_rank = rank.values().copied().max().unwrap_or(0); + let mut buckets: Vec> = vec![Vec::new(); max_rank]; + + for (node, r) in &rank { + // r is 1-based; bucket index = r - 1 + buckets[*r - 1].push((*node).clone()); + } + + let max_concurrency = buckets.iter().map(|b| b.len()).max().unwrap_or(0); + + Ok(Schedule { + buckets, + max_concurrency, + }) +} + +/// Print a human-readable schedule summary. +/// +/// Only available when `std`/default formatting is sufficient. +pub fn format_schedule(schedule: &Schedule) -> String +where + K: std::fmt::Debug, +{ + let mut out = String::new(); + out.push_str(&format!( + "Schedule ({} buckets, max concurrency = {})\n", + schedule.buckets.len(), + schedule.max_concurrency + )); + for (i, bucket) in schedule.buckets.iter().enumerate() { + out.push_str(&format!(" Bucket {}: {:?}\n", i, bucket)); + } + out +} + +// --------------------------------------------------------------------------- +// Tests +// --------------------------------------------------------------------------- + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn linear_chain_produces_one_per_bucket() { + let mut dag = Dag::new(); + for n in &["a", "b", "c"] { + dag.add_node(*n).unwrap(); + } + dag.add_edge("a", "b").unwrap(); + dag.add_edge("b", "c").unwrap(); + + let sched = schedule(&dag).unwrap(); + assert_eq!(sched.buckets.len(), 3); + assert_eq!(sched.max_concurrency, 1); + + // a alone in bucket 0, b in bucket 1, c in bucket 2 + assert_eq!(sched.buckets[0], vec!["a"]); + assert_eq!(sched.buckets[1], vec!["b"]); + assert_eq!(sched.buckets[2], vec!["c"]); + } + + #[test] + fn diamond_parallelism() { + let mut dag = Dag::new(); + for n in &["a", "b", "c", "d"] { + dag.add_node(*n).unwrap(); + } + dag.add_edge("a", "b").unwrap(); + dag.add_edge("a", "c").unwrap(); + dag.add_edge("b", "d").unwrap(); + dag.add_edge("c", "d").unwrap(); + + let sched = schedule(&dag).unwrap(); + // Buckets: [a], [b, c], [d] + assert_eq!(sched.buckets.len(), 3); + assert_eq!(sched.max_concurrency, 2); + + let b0: std::collections::BTreeSet<_> = sched.buckets[0].iter().collect(); + let b1: std::collections::BTreeSet<_> = sched.buckets[1].iter().collect(); + let b2: std::collections::BTreeSet<_> = sched.buckets[2].iter().collect(); + + assert_eq!(b0, std::collections::BTreeSet::from([&"a"])); + assert_eq!(b1, std::collections::BTreeSet::from([&"b", &"c"])); + assert_eq!(b2, std::collections::BTreeSet::from([&"d"])); + } + + #[test] + fn fan_out_fan_in() { + let mut dag = Dag::new(); + for n in &["root", "a1", "a2", "a3", "leaf"] { + dag.add_node(*n).unwrap(); + } + dag.add_edge("root", "a1").unwrap(); + dag.add_edge("root", "a2").unwrap(); + dag.add_edge("root", "a3").unwrap(); + dag.add_edge("a1", "leaf").unwrap(); + dag.add_edge("a2", "leaf").unwrap(); + dag.add_edge("a3", "leaf").unwrap(); + + let sched = schedule(&dag).unwrap(); + assert_eq!(sched.buckets.len(), 3); + assert_eq!(sched.max_concurrency, 3); // a1, a2, a3 in parallel + + let b1: std::collections::BTreeSet<_> = sched.buckets[1].iter().collect(); + assert_eq!( + b1, + std::collections::BTreeSet::from([&"a1", &"a2", &"a3"]) + ); + } + + #[test] + fn empty_schedule() { + let dag: Dag = Dag::new(); + let sched = schedule(&dag).unwrap(); + assert!(sched.buckets.is_empty()); + assert_eq!(sched.max_concurrency, 0); + } + + #[test] + fn single_node() { + let mut dag = Dag::new(); + dag.add_node("only").unwrap(); + let sched = schedule(&dag).unwrap(); + assert_eq!(sched.buckets.len(), 1); + assert_eq!(sched.buckets[0], vec!["only"]); + assert_eq!(sched.max_concurrency, 1); + } + + #[test] + fn cycle_returns_error() { + let mut dag = Dag::new(); + for n in &["x", "y", "z"] { + dag.add_node(*n).unwrap(); + } + dag.add_edge("x", "y").unwrap(); + dag.add_edge("y", "z").unwrap(); + dag.add_edge("z", "x").unwrap(); + assert!(schedule(&dag).is_err()); + } + + #[test] + fn multi_source_dag() { + let mut dag = Dag::new(); + for n in &["s1", "s2", "mid", "t"] { + dag.add_node(*n).unwrap(); + } + dag.add_edge("s1", "mid").unwrap(); + dag.add_edge("s2", "mid").unwrap(); + dag.add_edge("mid", "t").unwrap(); + + let sched = schedule(&dag).unwrap(); + assert_eq!(sched.buckets.len(), 3); + // Bucket 0: all sources + let b0: std::collections::BTreeSet<_> = sched.buckets[0].iter().collect(); + assert_eq!(b0, std::collections::BTreeSet::from([&"s1", &"s2"])); + assert_eq!(sched.max_concurrency, 2); + } + + #[test] + fn format_output() { + let mut dag = Dag::new(); + for n in &["a", "b", "c"] { + dag.add_node(*n).unwrap(); + } + dag.add_edge("a", "b").unwrap(); + dag.add_edge("a", "c").unwrap(); + let sched = schedule(&dag).unwrap(); + let formatted = format_schedule(&sched); + assert!(formatted.contains("2 buckets")); + assert!(formatted.contains("max concurrency = 2")); + } +} diff --git a/crates/byteport-dag/src/schema.rs b/crates/byteport-dag/src/schema.rs new file mode 100644 index 00000000..8eb0f00d --- /dev/null +++ b/crates/byteport-dag/src/schema.rs @@ -0,0 +1,499 @@ +//! Enriched DAG schema types: node, edge, prerequisite, acceptance criteria, +//! and audit hooks. +//! +//! These types extend the core [`Dag`] with domain-specific metadata needed +//! for Phenotype compute/infra automation. +//! +//! # Schema concepts +//! +//! | Concept | Description | +//! |----------------------|-------------------------------------------------------| +//! | Prerequisite | Condition that must be satisfied **before** a node runs | +//! | AcceptanceCriterion | Condition that must pass **after** a node completes | +//! | AuditHook | Pre/post hook that emits audit/log/metric events | + +use std::collections::HashMap; + +use serde::{Deserialize, Serialize}; + +// --------------------------------------------------------------------------- +// Prerequisite — gating conditions +// --------------------------------------------------------------------------- + +/// A condition that must be satisfied before a DAG node may execute. +/// +/// # Variants +/// +/// | Variant | Description | +/// |----------------------|------------------------------------------------------| +/// | `ImageReady` | A container image must be built and available | +/// | `SecretAvailable` | A named secret must exist in the secrets store | +/// | `ResourceExists` | An infrastructure resource must exist (e.g., S3 bucket) | +/// | `ApiHealthy` | An HTTP/HTTPS endpoint must return 2xx | +/// | `FileExists` | A file or directory path must exist on disk | +/// | `EnvironmentVariable`| An environment variable must be set | +/// | `CustomScript` | An arbitrary script/command must exit with code 0 | +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(tag = "type", rename_all = "snake_case")] +pub enum Prerequisite { + /// A container image must be available at the given reference. + ImageReady { + /// Container image reference (e.g. `"nginx:1.25"`). + image: String, + }, + /// A secret must exist in the secrets store. + SecretAvailable { + /// Name of the secret. + name: String, + }, + /// An infrastructure resource must exist. + ResourceExists { + /// Resource provider (e.g. `"aws"`, `"gcp"`). + provider: String, + /// Resource type (e.g. `"s3_bucket"`, `"ec2_instance"`). + resource_type: String, + /// Resource identifier. + id: String, + }, + /// An HTTP endpoint must be healthy (2xx status). + ApiHealthy { + /// URL to probe. + url: String, + /// Optional expected status code range (default 200-299). + #[serde(default, skip_serializing_if = "Option::is_none")] + expected_status: Option, + }, + /// A file or directory must exist. + FileExists { + /// Absolute or relative path. + path: String, + }, + /// An environment variable must be set. + EnvironmentVariable { + /// Variable name. + variable: String, + }, + /// A custom check (arbitrary script). + CustomScript { + /// Shell command to run. + command: String, + /// Human-readable description of the check. + #[serde(default, skip_serializing_if = "Option::is_none")] + description: Option, + }, +} + +// --------------------------------------------------------------------------- +// AcceptanceCriterion — post-execution validation +// --------------------------------------------------------------------------- + +/// A condition that must be satisfied after a DAG node executes for the node +/// to be considered successful. +/// +/// # Variants +/// +/// | Variant | Description | +/// |--------------------|----------------------------------------------------| +/// | `ExitCode` | Process exited with a specific code | +/// | `OutputContains` | Stdout/stderr contains a substring or regex | +/// | `HttpOk` | An HTTP endpoint returns a 2xx status | +/// | `MetricThreshold` | A named metric is within a numeric range | +/// | `LogCheck` | Log output matches or does not match a pattern | +/// | `CustomCheck` | Arbitrary programmatic check as a shell command | +/// +/// Note: `PartialEq` is derived for testing but `Eq` is omitted because +/// `f64` fields (in `MetricThreshold`) do not implement `Eq`. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(tag = "type", rename_all = "snake_case")] +pub enum AcceptanceCriterion { + /// Expect the process to exit with this code. + ExitCode { + /// Expected exit code (default 0). + #[serde(default = "default_exit_code")] + code: i32, + }, + /// Output (stdout + stderr) must contain this pattern. + OutputContains { + /// Substring or regex pattern. + pattern: String, + /// If true, treat `pattern` as a regex. + #[serde(default)] + regex: bool, + }, + /// An HTTP endpoint must respond with a 2xx status. + HttpOk { + /// URL to check. + url: String, + /// Optional expected status code. + #[serde(default, skip_serializing_if = "Option::is_none")] + expected_status: Option, + }, + /// A named metric must be within the expected range. + MetricThreshold { + /// Metric name (e.g. `"memory_usage_mb"`). + metric: String, + /// Lower bound (inclusive). + #[serde(default, skip_serializing_if = "Option::is_none")] + min: Option, + /// Upper bound (inclusive). + #[serde(default, skip_serializing_if = "Option::is_none")] + max: Option, + }, + /// Check log output against a pattern. + LogCheck { + /// Pattern to search for in logs. + pattern: String, + /// If true, fail when the pattern is found. + #[serde(default)] + negate: bool, + /// Log source (e.g. `"stdout"`, `"stderr"`, `"file:/var/log/app.log"`). + #[serde(default, skip_serializing_if = "Option::is_none")] + source: Option, + }, + /// An arbitrary programmatic check. + CustomCheck { + /// Shell command to run. + command: String, + /// Human-readable description. + #[serde(default, skip_serializing_if = "Option::is_none")] + description: Option, + }, +} + +fn default_exit_code() -> i32 { + 0 +} + +// --------------------------------------------------------------------------- +// AuditHook — pre/post execution hooks +// --------------------------------------------------------------------------- + +/// When the hook fires relative to node execution. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum HookTiming { + /// Fires before the node starts. + Pre, + /// Fires after the node completes (success or failure). + Post, + /// Fires on successful completion only. + OnSuccess, + /// Fires on failure only. + OnFailure, +} + +impl Default for HookTiming { + fn default() -> Self { + Self::Post + } +} + +/// An audit / observability hook attached to a DAG node. +/// +/// Hooks fire at specified points in the node lifecycle to emit audit +/// records, metrics, or notifications. +/// +/// # Variants +/// +/// | Variant | Description | +/// |-------------|----------------------------------------------| +/// | `Webhook` | POST a JSON payload to a URL | +/// | `LogEntry` | Write a structured log record | +/// | `MetricEmit`| Emit a numeric metric to a telemetry backend | +/// | `Notify` | Send a notification (e.g. Slack, email) | +/// +/// Note: `PartialEq` is derived for testing but `Eq` is omitted because +/// `f64` fields (in `MetricEmit`) do not implement `Eq`. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(tag = "type", rename_all = "snake_case")] +pub enum AuditHook { + /// HTTP webhook callback. + Webhook { + /// Target URL. + url: String, + /// Optional custom headers. + #[serde(default, skip_serializing_if = "Option::is_none")] + headers: Option>, + /// When to fire. + #[serde(default)] + timing: HookTiming, + }, + /// Structured log entry. + LogEntry { + /// Log message template. + message: String, + /// Log severity level. + #[serde(default = "default_log_level")] + level: String, + /// When to fire. + #[serde(default)] + timing: HookTiming, + }, + /// Emit a metric to the telemetry pipeline. + MetricEmit { + /// Metric name. + name: String, + /// Metric value. + value: f64, + /// Metric unit (e.g. `"ms"`, `"count"`, `"bytes"`). + #[serde(default, skip_serializing_if = "Option::is_none")] + unit: Option, + /// When to fire. + #[serde(default)] + timing: HookTiming, + }, + /// Send a notification. + Notify { + /// Notification channel (e.g. `"slack"`, `"email"`, `"pagerduty"`). + channel: String, + /// Message payload. + message: String, + /// When to fire. + #[serde(default)] + timing: HookTiming, + }, +} + +fn default_log_level() -> String { + "info".to_string() +} + +// --------------------------------------------------------------------------- +// Enriched node and edge types +// --------------------------------------------------------------------------- + +/// An enriched node in the DAG schema. +/// +/// Carries optional prerequisites, acceptance criteria, audit hooks, and +/// arbitrary metadata alongside the basic identifier. +/// +/// Note: `PartialEq` is derived for testing but `Eq` is omitted because +/// this type transitively contains `f64` fields via `AuditHook::MetricEmit`. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct SchemaNode { + /// Unique node identifier. + pub id: String, + /// Human-readable label (optional). + #[serde(default, skip_serializing_if = "Option::is_none")] + pub label: Option, + /// Longer description of what this node does. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub description: Option, + /// Conditions that must be satisfied before this node runs. + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub prerequisites: Vec, + /// Conditions that must pass after this node completes. + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub acceptance: Vec, + /// Audit hooks attached to this node's lifecycle. + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub audit_hooks: Vec, + /// Arbitrary key-value metadata. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub metadata: Option>, +} + +/// An edge in the DAG schema. +/// +/// Carries an optional label and condition for conditional branching. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct SchemaEdge { + /// Source node id. + pub from: String, + /// Target node id. + pub to: String, + /// Optional edge label / annotation. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub label: Option, + /// Optional condition expression (e.g. `"build.exit_code == 0"`). + /// When set, the edge is only traversed if the condition evaluates true. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub condition: Option, +} + +// --------------------------------------------------------------------------- +// Tests +// --------------------------------------------------------------------------- + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn prerequisite_image_ready_round_trip() { + let p = Prerequisite::ImageReady { + image: "nginx:1.25".into(), + }; + let json = serde_json::to_string_pretty(&p).unwrap(); + let back: Prerequisite = serde_json::from_str(&json).unwrap(); + assert_eq!(p, back); + } + + #[test] + fn prerequisite_secret_available() { + let p = Prerequisite::SecretAvailable { + name: "DB_PASSWORD".into(), + }; + let json = serde_json::to_string(&p).unwrap(); + assert!(json.contains("\"type\":\"secret_available\"")); + assert!(json.contains("DB_PASSWORD")); + } + + #[test] + fn prerequisite_api_healthy_default_status() { + let p = Prerequisite::ApiHealthy { + url: "https://example.com/health".into(), + expected_status: None, + }; + let json = serde_json::to_string(&p).unwrap(); + let back: Prerequisite = serde_json::from_str(&json).unwrap(); + assert_eq!(p, back); + } + + #[test] + fn acceptance_exit_code_default() { + let ac = AcceptanceCriterion::ExitCode { code: 0 }; + let json = serde_json::to_string(&ac).unwrap(); + assert!(json.contains("\"code\":0")); + } + + #[test] + fn acceptance_output_contains_with_regex() { + let ac = AcceptanceCriterion::OutputContains { + pattern: r"ERROR|FATAL".into(), + regex: true, + }; + let json = serde_json::to_string(&ac).unwrap(); + let back: AcceptanceCriterion = serde_json::from_str(&json).unwrap(); + assert_eq!(ac, back); + } + + #[test] + fn acceptance_metric_threshold() { + let ac = AcceptanceCriterion::MetricThreshold { + metric: "memory_usage_mb".into(), + min: Some(0.0), + max: Some(512.0), + }; + let json = serde_json::to_string(&ac).unwrap(); + let back: AcceptanceCriterion = serde_json::from_str(&json).unwrap(); + assert_eq!(ac, back); + } + + #[test] + fn audit_hook_webhook_round_trip() { + let hook = AuditHook::Webhook { + url: "https://hooks.example.com/audit".into(), + headers: None, + timing: HookTiming::Pre, + }; + let json = serde_json::to_string_pretty(&hook).unwrap(); + let back: AuditHook = serde_json::from_str(&json).unwrap(); + assert_eq!(hook, back); + } + + #[test] + fn audit_hook_log_entry_default_timing() { + let hook = AuditHook::LogEntry { + message: "Node {node_id} completed".into(), + level: "warn".into(), + timing: HookTiming::Post, + }; + let json = serde_json::to_string(&hook).unwrap(); + let back: AuditHook = serde_json::from_str(&json).unwrap(); + assert_eq!(hook, back); + } + + #[test] + fn audit_hook_metric_emit() { + let hook = AuditHook::MetricEmit { + name: "dag_node_duration_ms".into(), + value: 1234.5, + unit: Some("ms".into()), + timing: HookTiming::Post, + }; + let json = serde_json::to_string_pretty(&hook).unwrap(); + let back: AuditHook = serde_json::from_str(&json).unwrap(); + assert_eq!(hook, back); + } + + #[test] + fn audit_hook_notify() { + let hook = AuditHook::Notify { + channel: "slack".into(), + message: "DAG node `deploy` failed".into(), + timing: HookTiming::OnFailure, + }; + let json = serde_json::to_string(&hook).unwrap(); + let back: AuditHook = serde_json::from_str(&json).unwrap(); + assert_eq!(hook, back); + } + + #[test] + fn schema_node_with_all_fields() { + let node = SchemaNode { + id: "deploy-staging".into(), + label: Some("Deploy to Staging".into()), + description: Some("Deploy the built artifact to the staging environment".into()), + prerequisites: vec![ + Prerequisite::SecretAvailable { + name: "STAGING_SSH_KEY".into(), + }, + Prerequisite::ImageReady { + image: "myapp:latest".into(), + }, + ], + acceptance: vec![ + AcceptanceCriterion::HttpOk { + url: "https://staging.example.com/health".into(), + expected_status: Some(200), + }, + ], + audit_hooks: vec![ + AuditHook::Webhook { + url: "https://hooks.example.com/deploy".into(), + headers: None, + timing: HookTiming::Post, + }, + ], + metadata: Some(HashMap::from_iter(vec![ + ("team".into(), "platform".into()), + ("tier".into(), "2".into()), + ])), + }; + let json = serde_json::to_string_pretty(&node).unwrap(); + let back: SchemaNode = serde_json::from_str(&json).unwrap(); + assert_eq!(node, back); + } + + #[test] + fn schema_edge_with_condition() { + let edge = SchemaEdge { + from: "build".into(), + to: "deploy".into(), + label: Some("deploy-on-success".into()), + condition: Some("build.exit_code == 0".into()), + }; + let json = serde_json::to_string_pretty(&edge).unwrap(); + let back: SchemaEdge = serde_json::from_str(&json).unwrap(); + assert_eq!(edge, back); + } + + #[test] + fn enum_variants_are_exhaustive() { + // Compile-time check: ensure all variants are constructable. + let _: Prerequisite = Prerequisite::CustomScript { + command: "echo ok".into(), + description: None, + }; + let _: AcceptanceCriterion = AcceptanceCriterion::CustomCheck { + command: "echo ok".into(), + description: None, + }; + let _: AuditHook = AuditHook::LogEntry { + message: "test".into(), + level: "info".into(), + timing: HookTiming::Pre, + }; + } +} diff --git a/crates/byteport-dag/src/serialize.rs b/crates/byteport-dag/src/serialize.rs new file mode 100644 index 00000000..6961d00d --- /dev/null +++ b/crates/byteport-dag/src/serialize.rs @@ -0,0 +1,374 @@ +//! YAML/JSON serialization for the enriched DAG schema. +//! +//! Provides a portable [`DagSchema`] that uses the enriched [`SchemaNode`] +//! and [`SchemaEdge`] types with prerequisites, acceptance criteria, and +//! audit hooks, defined in [`crate::schema`]. +//! +//! # Example +//! +//! ```rust +//! use byteport_dag::dag::Dag; +//! use byteport_dag::serialize::DagSchema; +//! +//! let mut dag: Dag = Dag::new(); +//! dag.add_node("build".into()).unwrap(); +//! dag.add_node("test".into()).unwrap(); +//! dag.add_node("deploy".into()).unwrap(); +//! dag.add_edge("build".into(), "test".into()).unwrap(); +//! dag.add_edge("test".into(), "deploy".into()).unwrap(); +//! +//! let schema = DagSchema::from_dag(&dag, "2.0.0"); +//! let yaml = schema.to_yaml().unwrap(); +//! let round: DagSchema = DagSchema::from_yaml(&yaml).unwrap(); +//! assert_eq!(schema, round); +//! ``` + +use std::collections::BTreeSet; + +use serde::{Deserialize, Serialize}; +use thiserror::Error; + +use crate::dag::Dag; +use crate::schema::{SchemaEdge, SchemaNode}; + +// --------------------------------------------------------------------------- +// Errors +// --------------------------------------------------------------------------- + +/// Errors that can occur during DAG serialization / deserialization. +#[derive(Debug, Error)] +pub enum DagSerError { + #[error("YAML error: {0}")] + Yaml(#[from] serde_yaml::Error), + #[error("JSON error: {0}")] + Json(#[from] serde_json::Error), +} + +// --------------------------------------------------------------------------- +// DagSchema — portable, serde-friendly, enriched DAG representation +// --------------------------------------------------------------------------- + +/// Portable, serializable, enriched DAG representation. +/// +/// Extends the basic node/edge structure with prerequisites, acceptance +/// criteria, audit hooks, and metadata. +/// +/// This type is the **only** entry point for YAML / JSON serialization +/// of the enriched schema. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct DagSchema { + /// Schema version (e.g. "2.0.0"). + pub version: String, + /// DAG name (optional). + #[serde(default, skip_serializing_if = "Option::is_none")] + pub name: Option, + /// Nodes in the graph. + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub nodes: Vec, + /// Directed edges. + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub edges: Vec, +} + +impl DagSchema { + /// Build a `DagSchema` from an internal `Dag` keyed by `String`. + /// + /// `version` is a free-form version string (e.g. `"2.0.0"`). + /// + /// The enriched fields (prerequisites, acceptance, audit hooks) are + /// left empty in the auto-generated nodes; consumers should populate + /// them on the returned schema before serializing. + pub fn from_dag(dag: &Dag, version: impl Into) -> Self { + let nodes: Vec = dag + .iter_nodes() + .map(|id| SchemaNode { + id: id.clone(), + label: None, + description: None, + prerequisites: Vec::new(), + acceptance: Vec::new(), + audit_hooks: Vec::new(), + metadata: None, + }) + .collect(); + + // Collect deduplicated edges. + let mut edges_set: BTreeSet<(String, String)> = BTreeSet::new(); + for node in dag.iter_nodes() { + if let Some(children) = dag.children_of(node) { + for child in children { + edges_set.insert((node.clone(), child.clone())); + } + } + } + let edges: Vec = edges_set + .into_iter() + .map(|(from, to)| SchemaEdge { + from, + to, + label: None, + condition: None, + }) + .collect(); + + Self { + version: version.into(), + name: None, + nodes, + edges, + } + } + + /// Set an optional name on the schema. + pub fn with_name(mut self, name: impl Into) -> Self { + self.name = Some(name.into()); + self + } + + /// Reconstruct an internal `Dag` from this schema. + /// + /// Returns an error if a duplicate node is encountered. + pub fn into_dag(&self) -> Result, crate::dag::DagError> { + let mut dag = Dag::new(); + for node in &self.nodes { + dag.add_node(node.id.clone())?; + } + for edge in &self.edges { + dag.add_edge(edge.from.clone(), edge.to.clone())?; + } + Ok(dag) + } + + // ----------------------------------------------------------------------- + // YAML + // ----------------------------------------------------------------------- + + /// Serialize to a YAML string. + pub fn to_yaml(&self) -> Result { + Ok(serde_yaml::to_string(self)?) + } + + /// Deserialize from a YAML string. + pub fn from_yaml(yaml: &str) -> Result { + Ok(serde_yaml::from_str(yaml)?) + } + + // ----------------------------------------------------------------------- + // JSON + // ----------------------------------------------------------------------- + + /// Serialize to a compact JSON string. + pub fn to_json(&self) -> Result { + Ok(serde_json::to_string(self)?) + } + + /// Serialize to a pretty-printed JSON string. + pub fn to_json_pretty(&self) -> Result { + Ok(serde_json::to_string_pretty(self)?) + } + + /// Deserialize from a JSON string. + pub fn from_json(json: &str) -> Result { + Ok(serde_json::from_str(json)?) + } +} + +// --------------------------------------------------------------------------- +// Tests +// --------------------------------------------------------------------------- + +#[cfg(test)] +mod tests { + use super::*; + use crate::schema::*; + + fn sample_dag() -> Dag { + let mut dag = Dag::new(); + for n in ["checkout", "build", "test", "deploy"] { + dag.add_node(n.to_string()).unwrap(); + } + dag.add_edge("checkout".into(), "build".into()).unwrap(); + dag.add_edge("build".into(), "test".into()).unwrap(); + dag.add_edge("test".into(), "deploy".into()).unwrap(); + dag + } + + fn enriched_schema() -> DagSchema { + let dag = sample_dag(); + let mut schema = DagSchema::from_dag(&dag, "2.0.0").with_name("ci-pipeline"); + + for node in &mut schema.nodes { + if node.id == "build" { + node.description = Some("Compile the application".into()); + node.prerequisites = vec![ + Prerequisite::FileExists { + path: "./Cargo.toml".into(), + }, + Prerequisite::EnvironmentVariable { + variable: "RUSTFLAGS".into(), + }, + ]; + node.acceptance = vec![ + AcceptanceCriterion::ExitCode { code: 0 }, + AcceptanceCriterion::OutputContains { + pattern: "Compiling".into(), + regex: false, + }, + ]; + node.audit_hooks = vec![ + AuditHook::MetricEmit { + name: "build_duration_ms".into(), + value: 0.0, + unit: Some("ms".into()), + timing: HookTiming::Post, + }, + ]; + } + if node.id == "deploy" { + node.prerequisites = vec![ + Prerequisite::ImageReady { + image: "myapp:latest".into(), + }, + ]; + node.acceptance = vec![ + AcceptanceCriterion::HttpOk { + url: "https://staging.example.com/health".into(), + expected_status: Some(200), + }, + ]; + node.audit_hooks = vec![ + AuditHook::Notify { + channel: "slack".into(), + message: "Deploy completed".into(), + timing: HookTiming::OnSuccess, + }, + ]; + } + } + + schema + } + + #[test] + fn yaml_round_trip_basic() { + let dag = sample_dag(); + let schema = DagSchema::from_dag(&dag, "2.0.0").with_name("pipeline"); + let yaml = schema.to_yaml().expect("YAML serialization"); + let restored = DagSchema::from_yaml(&yaml).expect("YAML deserialization"); + assert_eq!(schema, restored, "YAML round-trip must be lossless"); + } + + #[test] + fn json_round_trip_basic() { + let dag = sample_dag(); + let schema = DagSchema::from_dag(&dag, "2.0.0"); + let json = schema.to_json().expect("JSON serialization"); + let restored = DagSchema::from_json(&json).expect("JSON deserialization"); + assert_eq!(schema, restored, "JSON round-trip must be lossless"); + } + + #[test] + fn yaml_round_trip_enriched() { + let schema = enriched_schema(); + let yaml = schema.to_yaml().expect("YAML serialization of enriched schema"); + let restored = DagSchema::from_yaml(&yaml).expect("YAML deserialization"); + assert_eq!(schema, restored, "Enriched YAML round-trip must be lossless"); + } + + #[test] + fn json_round_trip_enriched() { + let schema = enriched_schema(); + let json = schema.to_json_pretty().expect("JSON serialization of enriched schema"); + let restored = DagSchema::from_json(&json).expect("JSON deserialization"); + assert_eq!(schema, restored, "Enriched JSON round-trip must be lossless"); + } + + #[test] + fn cross_format_consistency() { + let schema = enriched_schema(); + let yaml = schema.to_yaml().unwrap(); + let from_yaml = DagSchema::from_yaml(&yaml).unwrap(); + let json = from_yaml.to_json().unwrap(); + let from_json = DagSchema::from_json(&json).unwrap(); + assert_eq!( + schema, from_json, + "YAML → JSON cross-format must be consistent" + ); + } + + #[test] + fn schema_to_dag_and_back() { + let dag = sample_dag(); + let schema = DagSchema::from_dag(&dag, "2.0.0"); + let reconstructed = schema.into_dag().expect("into_dag should succeed"); + assert_eq!(dag.node_count(), reconstructed.node_count()); + assert_eq!(dag.edge_count(), reconstructed.edge_count()); + for n in dag.iter_nodes() { + assert!(reconstructed.contains(n), "node {n} should exist"); + } + } + + #[test] + fn empty_dag_round_trips() { + let dag: Dag = Dag::new(); + let schema = DagSchema::from_dag(&dag, "0.1.0"); + let yaml = schema.to_yaml().unwrap(); + let back = DagSchema::from_yaml(&yaml).unwrap(); + assert_eq!(schema, back); + } + + #[test] + fn yaml_content_is_readable() { + let schema = enriched_schema(); + let yaml = schema.to_yaml().unwrap(); + assert!(yaml.contains("version:")); + assert!(yaml.contains("ci-pipeline")); + assert!(yaml.contains("checkout")); + assert!(yaml.contains("deploy")); + assert!(yaml.contains("prerequisites")); + assert!(yaml.contains("acceptance")); + assert!(yaml.contains("audit_hooks")); + } + + #[test] + fn json_content_is_readable() { + let schema = enriched_schema(); + let json = schema.to_json_pretty().unwrap(); + assert!(json.contains("\"version\"")); + assert!(json.contains("\"checkout\"")); + assert!(json.contains("\"deploy\"")); + assert!(json.contains("\"prerequisites\"")); + } + + #[test] + fn diamond_graph_round_trip() { + let mut dag = Dag::new(); + for n in ["a", "b", "c", "d"] { + dag.add_node(n.to_string()).unwrap(); + } + dag.add_edge("a".into(), "b".into()).unwrap(); + dag.add_edge("a".into(), "c".into()).unwrap(); + dag.add_edge("b".into(), "d".into()).unwrap(); + dag.add_edge("c".into(), "d".into()).unwrap(); + + let schema = DagSchema::from_dag(&dag, "1.0.0"); + let json = schema.to_json().unwrap(); + let back = DagSchema::from_json(&json).unwrap(); + assert_eq!(schema, back); + assert_eq!(back.edges.len(), 4); + } + + #[test] + fn enriched_schema_has_correct_prereqs() { + let schema = enriched_schema(); + let build_node = schema.nodes.iter().find(|n| n.id == "build").unwrap(); + assert_eq!(build_node.prerequisites.len(), 2); + assert_eq!(build_node.acceptance.len(), 2); + assert_eq!(build_node.audit_hooks.len(), 1); + + let deploy_node = schema.nodes.iter().find(|n| n.id == "deploy").unwrap(); + assert_eq!(deploy_node.prerequisites.len(), 1); + assert_eq!(deploy_node.acceptance.len(), 1); + assert_eq!(deploy_node.audit_hooks.len(), 1); + } +} diff --git a/crates/byteport-dag/src/topo.rs b/crates/byteport-dag/src/topo.rs new file mode 100644 index 00000000..f785c5de --- /dev/null +++ b/crates/byteport-dag/src/topo.rs @@ -0,0 +1,246 @@ +//! Topological sort algorithms for the DAG. +//! +//! Provides Kahn's algorithm (iterative, queue-based) and a DFS-based +//! variant, both producing a linear ordering. + +use std::collections::{HashMap, VecDeque}; +use std::hash::Hash; + +use crate::dag::{Dag, DagError}; + +// --------------------------------------------------------------------------- +// Kahn's algorithm (BFS-based topological sort) +// --------------------------------------------------------------------------- + +/// Compute a topological ordering of `dag` using Kahn's algorithm. +/// +/// Returns `Ok(Vec)` where the nodes appear in topologically-sorted order +/// (parents before children). Returns `Err` if the graph contains a cycle. +pub fn kahn_sort(dag: &Dag) -> Result, DagError> +where + K: Eq + Hash + Clone + std::fmt::Debug, +{ + // in-degree for every node + let mut in_degree: HashMap<&K, usize> = dag.iter_nodes().map(|n| (n, 0)).collect(); + + for node in dag.iter_nodes() { + if let Some(children) = dag.children_of(node) { + for child in children { + *in_degree.entry(child).or_insert(0) += 1; + } + } + } + + // seed queue with zero-in-degree nodes + let mut queue: VecDeque<&K> = in_degree + .iter() + .filter(|(_, deg)| **deg == 0) + .map(|(n, _)| *n) + .collect(); + + let mut order = Vec::with_capacity(dag.node_count()); + + while let Some(node) = queue.pop_front() { + order.push(node); + + if let Some(children) = dag.children_of(node) { + for child in children { + let deg = in_degree.get_mut(child).expect("child must be in map"); + *deg -= 1; + if *deg == 0 { + queue.push_back(child); + } + } + } + } + + if order.len() != dag.node_count() { + return Err(DagError::CycleDetected( + "graph".into(), + "contains a cycle".into(), + )); + } + + Ok(order) +} + +// --------------------------------------------------------------------------- +// DFS-based topological sort +// --------------------------------------------------------------------------- + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum DfsColor { + White, // unvisited + Gray, // in the current DFS path (cycle detection) + Black, // fully processed +} + +/// Compute a topological ordering using DFS with cycle detection. +/// +/// Returns `Ok(Vec)` on success, `Err` if a cycle is detected. +pub fn dfs_sort(dag: &Dag) -> Result, DagError> +where + K: Eq + Hash + Clone + std::fmt::Debug, +{ + let mut color: HashMap<&K, DfsColor> = dag.iter_nodes().map(|n| (n, DfsColor::White)).collect(); + let mut order = Vec::with_capacity(dag.node_count()); + + for start in dag.iter_nodes() { + if color[start] == DfsColor::White { + visit(dag, start, &mut color, &mut order)?; + } + } + + // DFS pushes at the end; reverse to get parent-before-child ordering. + order.reverse(); + Ok(order) +} + +fn visit<'a, K>( + dag: &'a Dag, + node: &'a K, + color: &mut HashMap<&'a K, DfsColor>, + order: &mut Vec<&'a K>, +) -> Result<(), DagError> +where + K: Eq + Hash + Clone + std::fmt::Debug, +{ + color.insert(node, DfsColor::Gray); + + if let Some(children) = dag.children_of(node) { + for child in children { + match color.get(child).copied().unwrap_or(DfsColor::White) { + DfsColor::Gray => { + return Err(DagError::CycleDetected( + format!("{:?}", node), + format!("{:?}", child), + )); + } + DfsColor::White => visit(dag, child, color, order)?, + DfsColor::Black => continue, + } + } + } + + color.insert(node, DfsColor::Black); + order.push(node); + Ok(()) +} + +// --------------------------------------------------------------------------- +// Tests +// --------------------------------------------------------------------------- + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn kahn_linear_chain() { + let mut dag = Dag::new(); + for n in &["a", "b", "c", "d"] { + dag.add_node(*n).unwrap(); + } + dag.add_edge("a", "b").unwrap(); + dag.add_edge("b", "c").unwrap(); + dag.add_edge("c", "d").unwrap(); + + let order = kahn_sort(&dag).unwrap(); + let ids: Vec<&str> = order.into_iter().copied().collect(); + // a must be before b before c before d + let pos = |name: &str| ids.iter().position(|x| *x == name).unwrap(); + assert!(pos("a") < pos("b")); + assert!(pos("b") < pos("c")); + assert!(pos("c") < pos("d")); + } + + #[test] + fn kahn_diamond() { + let mut dag = Dag::new(); + for n in &["a", "b", "c", "d"] { + dag.add_node(*n).unwrap(); + } + dag.add_edge("a", "b").unwrap(); + dag.add_edge("a", "c").unwrap(); + dag.add_edge("b", "d").unwrap(); + dag.add_edge("c", "d").unwrap(); + + let order = kahn_sort(&dag).unwrap(); + let ids: Vec<&str> = order.into_iter().copied().collect(); + let pos = |name: &str| ids.iter().position(|x| *x == name).unwrap(); + assert!(pos("a") < pos("b")); + assert!(pos("a") < pos("c")); + assert!(pos("b") < pos("d")); + assert!(pos("c") < pos("d")); + } + + #[test] + fn kahn_cycle_detection() { + let mut dag = Dag::new(); + for n in &["a", "b", "c"] { + dag.add_node(*n).unwrap(); + } + dag.add_edge("a", "b").unwrap(); + dag.add_edge("b", "c").unwrap(); + dag.add_edge("c", "a").unwrap(); // cycle + + assert!(kahn_sort(&dag).is_err()); + } + + #[test] + fn dfs_sort_matches_kahn() { + let mut dag = Dag::new(); + for n in &["a", "b", "c", "d", "e", "f"] { + dag.add_node(*n).unwrap(); + } + dag.add_edge("a", "b").unwrap(); + dag.add_edge("a", "c").unwrap(); + dag.add_edge("b", "d").unwrap(); + dag.add_edge("c", "d").unwrap(); + dag.add_edge("d", "e").unwrap(); + dag.add_edge("c", "f").unwrap(); + + let kahn_order = kahn_sort(&dag).unwrap(); + let dfs_order = dfs_sort(&dag).unwrap(); + + // Both should produce valid topological orders of the same length. + assert_eq!(kahn_order.len(), dfs_order.len()); + + // Verify DFS order respects all edges. + for node in dag.iter_nodes() { + if let Some(children) = dag.children_of(node) { + let n_pos = dfs_order.iter().position(|x| *x == node).unwrap(); + for child in children { + let c_pos = dfs_order.iter().position(|x| *x == child).unwrap(); + assert!(n_pos < c_pos, "DFS: {} should come before {}", node, child); + } + } + } + } + + #[test] + fn dfs_cycle_detection() { + let mut dag = Dag::new(); + for n in &["a", "b", "c"] { + dag.add_node(*n).unwrap(); + } + dag.add_edge("a", "b").unwrap(); + dag.add_edge("b", "c").unwrap(); + dag.add_edge("c", "a").unwrap(); + assert!(dfs_sort(&dag).is_err()); + } + + #[test] + fn kahn_empty_dag() { + let dag: Dag = Dag::new(); + let order = kahn_sort(&dag).unwrap(); + assert!(order.is_empty()); + } + + #[test] + fn dfs_empty_dag() { + let dag: Dag = Dag::new(); + let order = dfs_sort(&dag).unwrap(); + assert!(order.is_empty()); + } +}