diff --git a/Cargo.lock b/Cargo.lock index 886b5be..1765b69 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -209,19 +209,6 @@ dependencies = [ "syn", ] -[[package]] -name = "async-compat" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1ba85bc55464dcbf728b56d97e119d673f4cf9062be330a9a26f3acf504a590" -dependencies = [ - "futures-core", - "futures-io", - "once_cell", - "pin-project-lite", - "tokio", -] - [[package]] name = "async-trait" version = "0.1.89" @@ -277,28 +264,6 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" -[[package]] -name = "aws-lc-rs" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ec2f1fc3ec205783a5da9a7e6c1509cc69dedf09a1949e412c1e18469326d00" -dependencies = [ - "aws-lc-sys", - "zeroize", -] - -[[package]] -name = "aws-lc-sys" -version = "0.41.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a2f9779ce85b93ab6170dd940ad0169b5766ff848247aff13bb788b832fe3f4" -dependencies = [ - "cc", - "cmake", - "dunce", - "fs_extra", -] - [[package]] name = "axum" version = "0.8.8" @@ -368,18 +333,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" -[[package]] -name = "base16ct" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd307490d624467aa6f74b0eabb77633d1f758a7b25f12bceb0b22e08d9726f6" - -[[package]] -name = "base32" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "022dfe9eb35f19ebbcb51e0b40a5ab759f46ad60cadf7297e0bd085afb50e076" - [[package]] name = "base64" version = "0.22.1" @@ -461,9 +414,9 @@ dependencies = [ [[package]] name = "block-buffer" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96eb4cdd6cf1b31d671e9efe75c5d1ec614776856cefbe109ca373554a6d514f" +checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be" dependencies = [ "hybrid-array", ] @@ -551,8 +504,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" dependencies = [ "find-msvc-tools", - "jobserver", - "libc", "shlex", ] @@ -585,6 +536,17 @@ dependencies = [ "cpufeatures 0.2.17", ] +[[package]] +name = "chacha20" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "rand_core 0.10.1", +] + [[package]] name = "choice" version = "0.0.2" @@ -689,13 +651,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" [[package]] -name = "cmake" -version = "0.1.58" +name = "cmov" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" -dependencies = [ - "cc", -] +checksum = "3f88a43d011fc4a6876cb7344703e297c71dda42494fee094d5f7c76bf13f746" [[package]] name = "cobs" @@ -1014,6 +973,15 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "ctutils" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" +dependencies = [ + "cmov", +] + [[package]] name = "curve25519-dalek" version = "4.1.3" @@ -1032,16 +1000,16 @@ dependencies = [ [[package]] name = "curve25519-dalek" -version = "5.0.0-pre.1" +version = "5.0.0-pre.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f9200d1d13637f15a6acb71e758f64624048d85b31a5fdbfd8eca1e2687d0b7" +checksum = "335f1947f241137a14106b6f5acc5918a5ede29c9d71d3f2cb1678d5075d9fc3" dependencies = [ "cfg-if", "cpufeatures 0.2.17", "curve25519-dalek-derive", - "digest 0.11.0-rc.10", + "digest 0.11.3", "fiat-crypto 0.3.0", - "rand_core 0.9.5", + "rand_core 0.10.1", "rustc_version", "serde", "subtle", @@ -1163,9 +1131,29 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" +checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" + +[[package]] +name = "data-encoding-macro" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3259c913752a86488b501ed8680446a5ed2d5aeac6e596cb23ba3800768ea32c" +dependencies = [ + "data-encoding", + "data-encoding-macro-internal", +] + +[[package]] +name = "data-encoding-macro-internal" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccc2776f0c61eca1ca32528f85548abd1a4be8fb53d1b21c013e4f18da1e7090" +dependencies = [ + "data-encoding", + "syn", +] [[package]] name = "delegate" @@ -1191,9 +1179,9 @@ dependencies = [ [[package]] name = "der" -version = "0.8.0" +version = "0.8.0-rc.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71fd89660b2dc699704064e59e9dba0147b903e85319429e131620d022be411b" +checksum = "02c1d73e9668ea6b6a28172aa55f3ebec38507131ce179051c8033b5c6037653" dependencies = [ "const-oid 0.10.2", "pem-rfc7468 1.0.0", @@ -1306,11 +1294,11 @@ dependencies = [ [[package]] name = "digest" -version = "0.11.0-rc.10" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afa94b64bfc6549e6e4b5a3216f22593224174083da7a90db47e951c4fb31725" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" dependencies = [ - "block-buffer 0.11.0", + "block-buffer 0.12.0", "const-oid 0.10.2", "crypto-common 0.2.2", ] @@ -1359,9 +1347,9 @@ dependencies = [ [[package]] name = "dlopen2" -version = "0.5.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09b4f5f101177ff01b8ec4ecc81eead416a8aa42819a2869311b3420fa114ffa" +checksum = "5e2c5bd4158e66d1e215c49b837e11d62f3267b30c92f1d171c4d3105e3dc4d4" dependencies = [ "libc", "once_cell", @@ -1378,27 +1366,6 @@ dependencies = [ "serde_json", ] -[[package]] -name = "document-features" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" -dependencies = [ - "litrs", -] - -[[package]] -name = "dunce" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" - -[[package]] -name = "dyn-clone" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" - [[package]] name = "ecdsa" version = "0.16.9" @@ -1426,12 +1393,12 @@ dependencies = [ [[package]] name = "ed25519" -version = "3.0.0" +version = "3.0.0-rc.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29fcf32e6c73d1079f83ab4d782de2d81620346a5f38c6237a86a22f8368980a" +checksum = "c6e914c7c52decb085cea910552e24c63ac019e3ab8bf001ff736da9a9d9d890" dependencies = [ - "pkcs8 0.11.0", - "serdect", + "pkcs8 0.11.0-rc.10", + "serde", "signature 3.0.0", ] @@ -1452,13 +1419,15 @@ dependencies = [ [[package]] name = "ed25519-dalek" -version = "3.0.0-pre.1" +version = "3.0.0-pre.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053618a4c3d3bc24f188aa660ae75a46eeab74ef07fb415c61431e5e7cd4749b" dependencies = [ - "curve25519-dalek 5.0.0-pre.1", - "ed25519 3.0.0", - "rand_core 0.9.5", + "curve25519-dalek 5.0.0-pre.6", + "ed25519 3.0.0-rc.4", + "rand_core 0.10.1", "serde", - "sha2 0.11.0-rc.2", + "sha2 0.11.0-rc.5", "signature 3.0.0", "subtle", "zeroize", @@ -1476,7 +1445,7 @@ version = "0.13.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" dependencies = [ - "base16ct 0.2.0", + "base16ct", "crypto-bigint", "digest 0.10.7", "ff", @@ -1512,18 +1481,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "enum-as-inner" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "enum-assoc" version = "1.3.0" @@ -1551,18 +1508,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "fastbloom" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7f34442dbe69c60fe8eaf58a8cafff81a1f278816d8ab4db255b3bef4ac3c4" -dependencies = [ - "getrandom 0.3.4", - "libm", - "rand 0.9.2", - "siphasher", -] - [[package]] name = "fastrand" version = "2.3.0" @@ -1671,12 +1616,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "fs_extra" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" - [[package]] name = "futures" version = "0.3.32" @@ -1854,6 +1793,7 @@ dependencies = [ "js-sys", "libc", "r-efi", + "rand_core 0.10.1", "wasip2", "wasip3", "wasm-bindgen", @@ -1992,26 +1932,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" [[package]] -name = "hickory-proto" -version = "0.25.2" +name = "hickory-net" +version = "0.26.0-beta.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8a6fe56c0038198998a6f217ca4e7ef3a5e51f46163bd6dd60b5c71ca6c6502" +checksum = "1e232f503c4cfe3f4ea6594971255ecab9f6a0080c4c8e0e17630cc701322aa4" dependencies = [ "async-trait", "bytes", "cfg-if", "data-encoding", - "enum-as-inner", "futures-channel", "futures-io", "futures-util", "h2", + "hickory-proto", "http", "idna", "ipnet", - "once_cell", - "rand 0.9.2", - "ring", + "jni 0.22.4", + "rand 0.10.1", "rustls", "thiserror 2.0.18", "tinyvec", @@ -2022,22 +1961,47 @@ dependencies = [ ] [[package]] -name = "hickory-resolver" -version = "0.25.2" +name = "hickory-proto" +version = "0.26.0-beta.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc62a9a99b0bfb44d2ab95a7208ac952d31060efc16241c87eaf36406fecf87a" +checksum = "fcca12171ce774c549f35510be702f4da00ef12ca486f0f2acb2ee96f2f5ca0f" +dependencies = [ + "data-encoding", + "idna", + "ipnet", + "jni 0.22.4", + "once_cell", + "prefix-trie", + "rand 0.10.1", + "ring", + "thiserror 2.0.18", + "tinyvec", + "tracing", + "url", +] + +[[package]] +name = "hickory-resolver" +version = "0.26.0-beta.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7d2c928fa078e6640f26cf1b537b212e1688829c3944780025c7084e8bbbf6" dependencies = [ "cfg-if", "futures-util", + "hickory-net", "hickory-proto", "ipconfig", + "ipnet", + "jni 0.22.4", "moka", + "ndk-context", "once_cell", "parking_lot", - "rand 0.9.2", + "rand 0.10.1", "resolv-conf", "rustls", "smallvec", + "system-configuration", "thiserror 2.0.18", "tokio", "tokio-rustls", @@ -2358,11 +2322,10 @@ dependencies = [ [[package]] name = "igd-next" -version = "0.16.2" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "516893339c97f6011282d5825ac94fc1c7aad5cad26bdc2d0cee068c0bf97f97" +checksum = "bac9a3c8278f43b4cd8463380f4a25653ac843e5b177e1d3eaf849cc9ba10d4d" dependencies = [ - "async-trait", "attohttpc", "bytes", "futures", @@ -2371,7 +2334,7 @@ dependencies = [ "hyper", "hyper-util", "log", - "rand 0.9.2", + "rand 0.10.1", "tokio", "url", "xmltree", @@ -2452,50 +2415,55 @@ name = "ipnet" version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" +dependencies = [ + "serde", +] [[package]] name = "iroh" -version = "0.96.1" +version = "0.98.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5236da4d5681f317ec393c8fe2b7e3d360d31c6bb40383991d0b7429ca5ad117" +checksum = "9881b221c7c645d90594cbd331012f7cccb914894288a6cf5538a9115f6d0f3e" dependencies = [ "backon", + "blake3", "bytes", "cfg_aliases", + "ctutils", "data-encoding", + "der 0.8.0-rc.10", "derive_more", - "ed25519-dalek 3.0.0-pre.1", + "ed25519-dalek 3.0.0-pre.6", "futures-util", - "getrandom 0.3.4", + "getrandom 0.4.1", "hickory-resolver", "http", - "igd-next", + "ipnet", "iroh-base", + "iroh-dns", "iroh-metrics", - "iroh-quinn", - "iroh-quinn-proto", - "iroh-quinn-udp", "iroh-relay", "n0-error", "n0-future", "n0-watcher", - "netdev", "netwatch", + "noq", + "noq-proto", + "noq-udp", "papaya", "pin-project", - "pkarr", - "pkcs8 0.11.0", + "pkcs8 0.11.0-rc.10", + "portable-atomic", "portmapper", - "rand 0.9.2", - "reqwest 0.12.28", + "rand 0.10.1", + "reqwest 0.13.3", "rustc-hash", "rustls", "rustls-pki-types", "rustls-webpki", "serde", "smallvec", - "strum 0.27.2", - "sync_wrapper", + "strum 0.28.0", "time", "tokio", "tokio-stream", @@ -2508,24 +2476,40 @@ dependencies = [ [[package]] name = "iroh-base" -version = "0.96.1" +version = "0.98.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20c99d836a1c99e037e98d1bf3ef209c3a4df97555a00ce9510eb78eccdf5567" +checksum = "738865784637830fb14204ebd3047922db83bc1816a59027af29579b9c27bd99" dependencies = [ - "curve25519-dalek 5.0.0-pre.1", + "curve25519-dalek 5.0.0-pre.6", "data-encoding", + "data-encoding-macro", "derive_more", - "digest 0.11.0-rc.10", - "ed25519-dalek 3.0.0-pre.1", + "digest 0.11.3", + "ed25519-dalek 3.0.0-pre.6", + "getrandom 0.4.1", "n0-error", - "rand_core 0.9.5", + "rand 0.10.1", "serde", - "sha2 0.11.0-rc.2", + "sha2 0.11.0-rc.5", "url", "zeroize", "zeroize_derive", ] +[[package]] +name = "iroh-dns" +version = "0.98.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca474630d1e62ddef83149db6babe6a1055d901df9054349d31b22df99811b92" +dependencies = [ + "derive_more", + "iroh-base", + "n0-error", + "n0-future", + "simple-dns", + "strum 0.28.0", +] + [[package]] name = "iroh-metrics" version = "0.38.3" @@ -2559,72 +2543,11 @@ dependencies = [ "syn", ] -[[package]] -name = "iroh-quinn" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "034ed21f34c657a123d39525d948c885aacba59508805e4dd67d71f022e7151b" -dependencies = [ - "bytes", - "cfg_aliases", - "iroh-quinn-proto", - "iroh-quinn-udp", - "pin-project-lite", - "rustc-hash", - "rustls", - "socket2", - "thiserror 2.0.18", - "tokio", - "tokio-stream", - "tracing", - "web-time 1.1.0", -] - -[[package]] -name = "iroh-quinn-proto" -version = "0.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de99ad8adc878ee0e68509ad256152ce23b8bbe45f5539d04e179630aca40a9" -dependencies = [ - "bytes", - "derive_more", - "enum-assoc", - "fastbloom", - "getrandom 0.3.4", - "identity-hash", - "lru-slab", - "rand 0.9.2", - "ring", - "rustc-hash", - "rustls", - "rustls-pki-types", - "rustls-platform-verifier 0.6.2", - "slab", - "sorted-index-buffer", - "thiserror 2.0.18", - "tinyvec", - "tracing", - "web-time 1.1.0", -] - -[[package]] -name = "iroh-quinn-udp" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f981dadd5a072a9e0efcd24bdcc388e570073f7e51b33505ceb1ef4668c80c86" -dependencies = [ - "cfg_aliases", - "libc", - "socket2", - "tracing", - "windows-sys 0.61.2", -] - [[package]] name = "iroh-relay" -version = "0.96.1" +version = "0.98.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd2b63e654b9dec799a73372cdc79b529ca6c7248c0c8de7da78a02e3a46f03c" +checksum = "4aa6e9a7277bfbb439739c52b57eb5f9288030983928412022b8e94a43d4d838" dependencies = [ "ahash", "blake3", @@ -2634,27 +2557,27 @@ dependencies = [ "dashmap", "data-encoding", "derive_more", - "getrandom 0.3.4", + "getrandom 0.4.1", "hickory-resolver", "http", "http-body-util", "hyper", "hyper-util", "iroh-base", + "iroh-dns", "iroh-metrics", - "iroh-quinn", - "iroh-quinn-proto", "lru 0.16.4", "n0-error", "n0-future", + "noq", + "noq-proto", "num_enum", "pin-project", - "pkarr", "postcard", - "rand 0.9.2", + "rand 0.10.1", "rcgen", "reloadable-state", - "reqwest 0.12.28", + "reqwest 0.13.3", "rustls", "rustls-cert-file-reader", "rustls-cert-reloadable-resolver", @@ -2662,23 +2585,22 @@ dependencies = [ "serde", "serde_bytes", "serde_json", - "sha1 0.11.0-rc.4", + "sha1 0.11.0-rc.5", "simdutf8", - "strum 0.27.2", + "strum 0.28.0", "time", "tokio", "tokio-rustls", "tokio-rustls-acme", "tokio-util", "tokio-websockets", - "toml 0.9.12+spec-1.1.0", + "toml 1.1.2+spec-1.1.0", "tracing", "tracing-subscriber", "url", "vergen-gitcl", "webpki-roots 1.0.7", "ws_stream_wasm", - "z32", ] [[package]] @@ -2796,16 +2718,6 @@ dependencies = [ "syn", ] -[[package]] -name = "jobserver" -version = "0.1.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" -dependencies = [ - "getrandom 0.3.4", - "libc", -] - [[package]] name = "js-sys" version = "0.3.85" @@ -2861,12 +2773,6 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" -[[package]] -name = "litrs" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" - [[package]] name = "lock_api" version = "0.4.14" @@ -3083,10 +2989,16 @@ dependencies = [ ] [[package]] -name = "netdev" -version = "0.40.1" +name = "ndk-context" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b0a0096d9613ee878dba89bbe595f079d373e3f1960d882e4f2f78ff9c30a0a" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "netdev" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e30af1a5073b82356d9317c18226826370b4288eba2f71c7e84e18bae51b3847" dependencies = [ "block2", "dispatch2", @@ -3101,7 +3013,7 @@ dependencies = [ "objc2-system-configuration", "once_cell", "plist", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -3115,9 +3027,9 @@ dependencies = [ [[package]] name = "netlink-packet-route" -version = "0.28.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ce3636fa715e988114552619582b530481fd5ef176a1e5c1bf024077c2c9445" +checksum = "df9854ea6ad14e3f4698a7f03b65bce0833dd2d81d594a0e4a984170537146b6" dependencies = [ "bitflags", "libc", @@ -3127,9 +3039,9 @@ dependencies = [ [[package]] name = "netlink-packet-route" -version = "0.29.0" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df9854ea6ad14e3f4698a7f03b65bce0833dd2d81d594a0e4a984170537146b6" +checksum = "be8919612f6028ab4eacbbfe1234a9a43e3722c6e0915e7ff519066991905092" dependencies = [ "bitflags", "libc", @@ -3166,15 +3078,14 @@ dependencies = [ [[package]] name = "netwatch" -version = "0.14.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "454b8c0759b2097581f25ed5180b4a1d14c324fde6d0734932a288e044d06232" +checksum = "6fc0d4b4134425d9834e591b1a6f807ea365c6d941d738942215564af5f28a97" dependencies = [ "atomic-waker", "bytes", "cfg_aliases", "derive_more", - "iroh-quinn-udp", "js-sys", "libc", "n0-error", @@ -3182,9 +3093,10 @@ dependencies = [ "n0-watcher", "netdev", "netlink-packet-core", - "netlink-packet-route 0.28.0", + "netlink-packet-route 0.30.0", "netlink-proto", "netlink-sys", + "noq-udp", "objc2-core-foundation", "objc2-system-configuration", "pin-project-lite", @@ -3247,18 +3159,65 @@ dependencies = [ ] [[package]] -name = "ntimestamp" -version = "1.0.0" +name = "noq" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c50f94c405726d3e0095e89e72f75ce7f6587b94a8bd8dc8054b73f65c0fd68c" +checksum = "4b969bd157c3bd3bab239a1a8b14f67f2033fa012770367fcbd5b42d71ae3548" dependencies = [ - "base32", - "document-features", - "getrandom 0.2.17", - "httpdate", - "js-sys", - "once_cell", - "serde", + "bytes", + "cfg_aliases", + "derive_more", + "noq-proto", + "noq-udp", + "pin-project-lite", + "rustc-hash", + "rustls", + "socket2", + "thiserror 2.0.18", + "tokio", + "tokio-stream", + "tracing", + "web-time 1.1.0", +] + +[[package]] +name = "noq-proto" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdec6f5039d98ee5377b2f532d495a555eb664c53161b1b5780dcaeac678b60e" +dependencies = [ + "aes-gcm", + "bytes", + "derive_more", + "enum-assoc", + "getrandom 0.4.1", + "identity-hash", + "lru-slab", + "rand 0.10.1", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "rustls-platform-verifier 0.6.2", + "slab", + "sorted-index-buffer", + "thiserror 2.0.18", + "tinyvec", + "tracing", + "web-time 1.1.0", +] + +[[package]] +name = "noq-udp" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee91b05f4f3353290936ba1f3233518868fb4e2da99cb4c90d1f8cebb064e527" +dependencies = [ + "cfg_aliases", + "libc", + "socket2", + "tracing", + "windows-sys 0.61.2", ] [[package]] @@ -3537,7 +3496,7 @@ version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fc9e2161f1f215afdfce23677034ae137bbd45016a880c2eb3ba8eb95f085b2" dependencies = [ - "base16ct 0.2.0", + "base16ct", "ecdsa", "elliptic-curve", "primeorder", @@ -3691,37 +3650,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "pkarr" -version = "5.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f950360d31be432c0c9467fba5024a94f55128e7f32bc9d32db140369f24c77" -dependencies = [ - "async-compat", - "base32", - "bytes", - "cfg_aliases", - "document-features", - "dyn-clone", - "ed25519-dalek 3.0.0-pre.1", - "futures-buffered", - "futures-lite", - "getrandom 0.4.1", - "log", - "lru 0.16.4", - "ntimestamp", - "reqwest 0.13.3", - "self_cell", - "serde", - "sha1_smol", - "simple-dns", - "thiserror 2.0.18", - "tokio", - "tracing", - "url", - "wasm-bindgen-futures", -] - [[package]] name = "pkcs1" version = "0.7.5" @@ -3762,12 +3690,12 @@ dependencies = [ [[package]] name = "pkcs8" -version = "0.11.0" +version = "0.11.0-rc.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "451913da69c775a56034ea8d9003d27ee8948e12443eae7c038ba100a4f21cb7" +checksum = "b226d2cc389763951db8869584fd800cbbe2962bf454e2edeb5172b31ee99774" dependencies = [ - "der 0.8.0", - "spki 0.8.0", + "der 0.8.0-rc.10", + "spki 0.8.0-rc.4", ] [[package]] @@ -3851,9 +3779,9 @@ dependencies = [ [[package]] name = "portmapper" -version = "0.14.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d2a8825353ace3285138da3378b1e21860d60351942f7aa3b99b13b41f80318" +checksum = "a145e62ddd9aecc9c7b1a3c84cea2a803386c7f4da7795bf9f0d50d90dc52549" dependencies = [ "base64", "bytes", @@ -3867,7 +3795,7 @@ dependencies = [ "n0-error", "netwatch", "num_enum", - "rand 0.9.2", + "rand 0.10.1", "serde", "smallvec", "socket2", @@ -3928,6 +3856,17 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "prefix-trie" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cf6e3177f0684016a5c209b00882e15f8bdd3f3bb48f0491df10cd102d0c6e7" +dependencies = [ + "either", + "ipnet", + "num-traits", +] + [[package]] name = "prettyplease" version = "0.2.37" @@ -4105,7 +4044,6 @@ version = "0.11.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" dependencies = [ - "aws-lc-rs", "bytes", "getrandom 0.3.4", "lru-slab", @@ -4171,6 +4109,17 @@ dependencies = [ "rand_core 0.9.5", ] +[[package]] +name = "rand" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" +dependencies = [ + "chacha20 0.10.0", + "getrandom 0.4.1", + "rand_core 0.10.1", +] + [[package]] name = "rand_chacha" version = "0.3.1" @@ -4209,6 +4158,12 @@ dependencies = [ "getrandom 0.3.4", ] +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + [[package]] name = "rand_xorshift" version = "0.4.0" @@ -4364,14 +4319,12 @@ dependencies = [ "tokio", "tokio-native-tls", "tokio-rustls", - "tokio-util", "tower", "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-streams", "web-sys", "webpki-roots 1.0.7", ] @@ -4385,6 +4338,7 @@ dependencies = [ "base64", "bytes", "futures-core", + "futures-util", "http", "http-body", "http-body-util", @@ -4395,19 +4349,20 @@ dependencies = [ "log", "percent-encoding", "pin-project-lite", - "quinn", "rustls", "rustls-pki-types", "rustls-platform-verifier 0.7.0", "sync_wrapper", "tokio", "tokio-rustls", + "tokio-util", "tower", "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", + "wasm-streams", "web-sys", ] @@ -4474,7 +4429,7 @@ dependencies = [ "bitflags", "byteorder", "cbc", - "chacha20", + "chacha20 0.9.1", "ctr", "curve25519-dalek 4.1.3", "des", @@ -4656,7 +4611,6 @@ version = "0.23.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" dependencies = [ - "aws-lc-rs", "log", "once_cell", "ring", @@ -4776,7 +4730,6 @@ version = "0.103.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" dependencies = [ - "aws-lc-rs", "ring", "rustls-pki-types", "untrusted", @@ -4862,7 +4815,7 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" dependencies = [ - "base16ct 0.2.0", + "base16ct", "der 0.7.10", "generic-array", "pkcs8 0.10.2", @@ -4909,12 +4862,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "self_cell" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b12e76d157a900eb52e81bc6e9f3069344290341720e9178cde2407113ac8d89" - [[package]] name = "semver" version = "1.0.27" @@ -5034,16 +4981,6 @@ dependencies = [ "unsafe-libyaml", ] -[[package]] -name = "serdect" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66cf8fedced2fcf12406bcb34223dffb92eaf34908ede12fed414c82b7f00b3e" -dependencies = [ - "base16ct 1.0.0", - "serde", -] - [[package]] name = "sha1" version = "0.10.6" @@ -5057,13 +4994,13 @@ dependencies = [ [[package]] name = "sha1" -version = "0.11.0-rc.4" +version = "0.11.0-rc.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c777f0a122a53fddb0beb6e706771197000b8eb5c9f42b5b850f450ef48c788" +checksum = "3b167252f3c126be0d8926639c4c4706950f01445900c4b3db0fd7e89fcb750a" dependencies = [ "cfg-if", "cpufeatures 0.2.17", - "digest 0.11.0-rc.10", + "digest 0.11.3", ] [[package]] @@ -5085,13 +5022,13 @@ dependencies = [ [[package]] name = "sha2" -version = "0.11.0-rc.2" +version = "0.11.0-rc.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1e3878ab0f98e35b2df35fe53201d088299b41a6bb63e3e34dada2ac4abd924" +checksum = "7c5f3b1e2dc8aad28310d8410bd4d7e180eca65fca176c52ab00d364475d0024" dependencies = [ "cfg-if", "cpufeatures 0.2.17", - "digest 0.11.0-rc.10", + "digest 0.11.3", ] [[package]] @@ -5202,12 +5139,6 @@ dependencies = [ "toml 0.8.23", ] -[[package]] -name = "siphasher" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" - [[package]] name = "slab" version = "0.4.12" @@ -5274,12 +5205,12 @@ dependencies = [ [[package]] name = "spki" -version = "0.8.0" +version = "0.8.0-rc.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d9efca8738c78ee9484207732f728b1ef517bbb1833d6fc0879ca898a522f6f" +checksum = "8baeff88f34ed0691978ec34440140e1572b68c7dd4a495fd14a3dc1944daa80" dependencies = [ "base64ct", - "der 0.8.0", + "der 0.8.0-rc.10", ] [[package]] @@ -5291,7 +5222,7 @@ dependencies = [ "aes", "aes-gcm", "cbc", - "chacha20", + "chacha20 0.9.1", "cipher", "ctr", "poly1305", @@ -5382,11 +5313,11 @@ dependencies = [ [[package]] name = "strum" -version = "0.27.2" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" +checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" dependencies = [ - "strum_macros 0.27.2", + "strum_macros 0.28.0", ] [[package]] @@ -5404,9 +5335,9 @@ dependencies = [ [[package]] name = "strum_macros" -version = "0.27.2" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" +checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" dependencies = [ "heck", "proc-macro2", @@ -5818,20 +5749,21 @@ dependencies = [ [[package]] name = "tokio-websockets" -version = "0.12.3" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1b6348ebfaaecd771cecb69e832961d277f59845d4220a584701f72728152b7" +checksum = "dad543404f98bfc969aeb71994105c592acfc6c43323fddcd016bb208d1c65cb" dependencies = [ "base64", "bytes", "futures-core", "futures-sink", - "getrandom 0.3.4", + "getrandom 0.4.1", "http", "httparse", - "rand 0.9.2", + "rand 0.10.1", "ring", "rustls-pki-types", + "sha1_smol", "simdutf8", "tokio", "tokio-rustls", @@ -5852,17 +5784,17 @@ dependencies = [ [[package]] name = "toml" -version = "0.9.12+spec-1.1.0" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" +checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee" dependencies = [ "indexmap", "serde_core", "serde_spanned 1.1.1", - "toml_datetime 0.7.5+spec-1.1.0", + "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", "toml_writer", - "winnow 0.7.15", + "winnow 1.0.3", ] [[package]] @@ -5874,15 +5806,6 @@ dependencies = [ "serde", ] -[[package]] -name = "toml_datetime" -version = "0.7.5+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" -dependencies = [ - "serde_core", -] - [[package]] name = "toml_datetime" version = "1.1.1+spec-1.1.0" @@ -6398,9 +6321,9 @@ dependencies = [ [[package]] name = "wasm-streams" -version = "0.4.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" dependencies = [ "futures-util", "js-sys", @@ -7160,12 +7083,6 @@ dependencies = [ "synstructure", ] -[[package]] -name = "z32" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2164e798d9e3d84ee2c91139ace54638059a3b23e361f5c11781c2c6459bde0f" - [[package]] name = "zerocopy" version = "0.8.39" diff --git a/Cargo.toml b/Cargo.toml index 462e0b8..c5566b8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,12 +14,6 @@ members = [ "tests/integration", "xtask", ] -# Stage 6 iteration 8: all production crates and integration -# tests are back in the workspace. The vendored -# `ed25519-dalek 3.0.0-pre.1` patch below unblocks the iroh -# umbrella crate, which datastore/node/tests-integration pull -# in via `iroh = "0.96"` (datastore/node) and -# `distribution/iroh,relay` (tests-integration). exclude = ["crates/bindings/wasm-crypto", "examples"] [package] @@ -72,15 +66,4 @@ harness = false [[bench]] name = "hasher_benchmarks" -harness = false - -# Vendored `ed25519-dalek 3.0.0-pre.1` to fix two upstream -# compile errors (E0277, E0308) at `src/signing.rs:714,717`. -# Upstream encodes `pkcs8::Error::KeyMalformed` as a unit -# variant, but the resolved `pkcs8 0.11.0` re-exports it as -# `KeyMalformed(KeyError)`. The strict `=3.0.0-pre.1` pin -# from `iroh-base 0.96.1` forbids using a newer prerelease, -# so we vendor the published 3.0.0-pre.1 and apply a 2-line -# tuple-variant fix. See `.loop/notes.md` (Stage 6 iteration 8). -[patch.crates-io] -ed25519-dalek = { path = "vendor/ed25519-dalek" } +harness = false \ No newline at end of file diff --git a/crates/datastore/Cargo.toml b/crates/datastore/Cargo.toml index 854a647..9d6a051 100644 --- a/crates/datastore/Cargo.toml +++ b/crates/datastore/Cargo.toml @@ -14,7 +14,7 @@ serde_json = "1" blake3 = "1" crossbeam-queue = "0.3.12" tokio = { version = "1", features = ["rt", "sync", "io-util"] } -iroh = "0.96" +iroh = "0.98" getrandom = "0.2" tiny_http = { version = "0.12", optional = true } clap = { version = "4", features = ["derive"], optional = true } diff --git a/crates/distribution/Cargo.toml b/crates/distribution/Cargo.toml index 39e00b8..4518bae 100644 --- a/crates/distribution/Cargo.toml +++ b/crates/distribution/Cargo.toml @@ -35,8 +35,8 @@ swactor-transport = { path = "../transport" } serde = { version = "1", features = ["derive"] } serde_json = "1" uuid = { version = "1", features = ["v4", "serde"] } -iroh = { version = "0.96", optional = true } -iroh-relay = { version = "0.96", features = ["server"], optional = true } +iroh = { version = "0.98", optional = true } +iroh-relay = { version = "0.98", features = ["server"], optional = true } iroh-metrics = { version = "0.38", optional = true } tokio = { version = "1", features = ["rt-multi-thread"], optional = true } axum = { version = "0.8", optional = true } @@ -48,7 +48,7 @@ libc = "0.2" [dev-dependencies] serde_json = "1" -iroh-relay = { version = "0.96", features = ["server", "test-utils"] } +iroh-relay = { version = "0.98", features = ["server", "test-utils"] } tokio = { version = "1", features = ["rt-multi-thread", "macros", "net", "io-util", "time"] } [[bin]] diff --git a/crates/distribution/src/iroh_driver.rs b/crates/distribution/src/iroh_driver.rs index d749458..4691166 100644 --- a/crates/distribution/src/iroh_driver.rs +++ b/crates/distribution/src/iroh_driver.rs @@ -241,7 +241,8 @@ impl IrohDriver { let endpoint = rt.block_on(async { let mut alpns = vec![ALPN.to_vec()]; alpns.extend(config.additional_alpns.iter().cloned()); - let mut builder = Endpoint::empty_builder(effective_relay_mode) + let mut builder = Endpoint::builder(iroh::endpoint::presets::Minimal) + .relay_mode(effective_relay_mode) .alpns(alpns); if let Some(key) = config.secret_key { diff --git a/crates/node/Cargo.toml b/crates/node/Cargo.toml index 03539d7..4b5af47 100644 --- a/crates/node/Cargo.toml +++ b/crates/node/Cargo.toml @@ -11,7 +11,7 @@ swactor-transport = { path = "../transport" } distribution = { path = "../distribution" } clap = { version = "4", features = ["derive"] } ctrlc = "3" -iroh = { version = "0.96", optional = true } +iroh = { version = "0.98", optional = true } toml = "0.8" serde = { version = "1", features = ["derive"] } serde_json = "1" diff --git a/examples/pipeline-parallel-inference/SIM_SPEC.md b/examples/pipeline-parallel-inference/SIM_SPEC.md new file mode 100644 index 0000000..b06174d --- /dev/null +++ b/examples/pipeline-parallel-inference/SIM_SPEC.md @@ -0,0 +1,1044 @@ +# SIM_SPEC — simulator MVP + +Status: draft, pre-implementation. Lives in `examples/pipeline-parallel-inference/` +so it is not touched by the ongoing simplification of `crates/simulation`. It +moves alongside the implementation once that cleanup lands. + +This spec describes an architecture and an implementation strategy concretely +enough that two independent implementers, working from this document alone, +produce code that meshes. To that end it names components, the data that +crosses every boundary between them, and the behaviour each component owes +the others. Internal data structures, module layout, and helper types are the +implementer's call; the named boundaries are not. + +The MVP's first consumer is SWIM, because SWIM is the algorithm whose +production failures motivated the simulator. The architecture is not SWIM- +specific: the engine knows about *hosts*, not about SWIM. A host is a piece +of code that consumes ticks and inbound messages and emits actions. SWIM is +the first such host kind; the second is whatever we need next. + +--- + +## 0. Motivation + +The pipeline-parallel-inference example failed eight live N≥3 vast.ai deploys. +The session report (`N3_DEPLOYMENT_REPORT.md`, next to this file) traces the +failure to a SWIM gossip-flap bug ("B1"): in a seven-minute run, the +orchestrator refuted Suspect claims against itself 228 times — roughly once +every 1.8 seconds — and one peer ended the run marked Dead despite probes +succeeding in both directions on both sides of the link. The bug is not +visible in any test we have today. It only appears with three or more peers, +multi-region latency, and enough cumulative gossip state for piggybacked +membership updates to grow into the multi-kilobyte range. + +Catching that bug in production costs about two dollars of GPU rental per +attempt, forty-five to ninety minutes of engineer time per iteration, and +produces one non-reproducible bundle of evidence per run. The same source, +run twice, produces different outcomes. + +The simulator exists to make the iteration loop sub-second and the outcomes +byte-identical for a fixed seed. It is not a complete model of production; +it is the smallest model that lets us tune SWIM without deploying. Future +algorithms layer onto the same engine without changing the SWIM behaviour +this MVP guarantees. + +--- + +## 1. What "done" means + +The MVP ships when these are simultaneously true. + +A property test reproduces the gossip-flap bug deterministically against the +current SWIM source. The same scenario with the same seed produces byte- +identical output across runs and across the architectures we claim to +support. + +The fix workflow does not deploy. A developer writes a property, runs the +sim, sees it fail, edits the SWIM source, re-runs, sees it pass — all +locally, in under a second per iteration. + +The simulator is calibration-grounded against the three N3 bundles we have. +Distributions emitted by the sim, when configured to mirror a given N3 run, +are within declared tolerances of the corresponding live bundle. + +A sim bundle diffs cleanly, at the schema level, against any prod bundle. A +sim bundle may be a strict subset of prod's observable surface — events for +subsystems the MVP does not model are listed in a known-gaps document that +shrinks over time — but it may never introduce events prod does not produce, +and may never omit an event from a subsystem it claims to model. + +Each known live failure mode has at least one scenario in the library, with +a prose comment naming what it reproduces. + +--- + +## 2. Deliberate non-goals for the MVP + +These are out of scope; each has a re-entry point in §13. + +- The MVP does not run real iroh or quinn. SWIM's transport is a stream of + self-contained messages; the sim models the message bus, not the QUIC wire + protocol. iroh-induced behaviours SWIM is sensitive to — connection-cache + hit/miss latency, relay routing, cold-dial penalty — are exposed as link- + policy knobs. +- The MVP does not virtualize async runtimes. The engine is single-threaded + and synchronous; the SWIM state machine is synchronous. +- The MVP does not migrate the broader `distribution` crate onto a runtime + facade. SWIM-tuning needs only the SWIM module. +- The MVP does not run the determinism detector as a peer. +- The MVP does not execute recorded production bundles. The scenario format + is forward-compatible with replay; the converter is later work. +- The MVP does not model swactor mailboxes or any peer behaviour beyond + SWIM membership. +- The MVP ships no GUI. The bundle is the artefact. + +--- + +## 3. Architecture + +### 3.1 Components + +The simulator is one process holding six named components. Each component +has one responsibility and one set of inbound and outbound message types. +The boundaries between them are the contract two independent implementers +must agree on; nothing inside a component is. + +``` + scenario.toml + │ + ▼ + ┌──────────────┐ + │ scenario │ parses and validates the input + │ loader │ + └──────┬───────┘ + │ parsed scenario + ▼ + ┌──────────────┐ send queries ┌──────────────┐ + │ ├────────────────────►│ network │ + │ engine │◄────────────────────┤ │ + │ │ arrival times, └──────────────┘ + │ │ drop reasons + │ │ + │ │ ticks, recv ┌──────────────┐ + │ ├────────────────────► │ hosts │ + │ │◄──────────────────── │ (per peer) │ + │ │ actions └──────────────┘ + │ │ + │ │ events, snapshots ┌──────────────┐ + │ ├────────────────────► │ bundle │ + │ │ │ writer │ + └──────────────┘ └──────┬───────┘ + │ + ▼ + bundle on disk + │ + ▼ + ┌──────────────┐ + │ assertion │ + │ evaluator │ + └──────┬───────┘ + │ + ▼ + verdicts on disk +``` + +**Scenario loader.** Reads a TOML scenario, validates it, returns a parsed +scenario value (§8). No state; pure function from path to validated +scenario. + +**Engine.** Owns the virtual clock, the scheduling queue, the table of +hosts, references to the network and the bundle writer. Single entry point: +given a parsed scenario, run to completion. §4 specifies behaviour. + +**Network.** A directed-graph link model. Answers send queries +deterministically and accepts mutations on a timeline. Holds no schedule of +its own; the engine pops events, the network answers questions. §5 +specifies behaviour. + +**Host.** An instance of some host kind, one per peer in the scenario. The +host kind for the MVP is the production SWIM state machine wrapped in a +thin adapter. The host trait — what the engine calls and what the host +returns — is §6. + +**Bundle writer.** The only filesystem-touching component. Receives event +and snapshot records from the engine, serializes them to the production +diagnostics schema, writes them to a bundle directory. §9 specifies output. + +**Assertion evaluator.** Post-run reader of the bundle. Evaluates each +declared assertion against the event stream and snapshot directory. Emits a +verdict file. §10 specifies behaviour. + +### 3.2 Data flow + +Each boundary is named below with the data that crosses it. + +**scenario.toml → scenario loader.** A TOML file. §8 names the schema. + +**scenario loader → engine.** A validated parsed scenario value. The fields +are exactly those §8 names; the engine consumes nothing else from outside. + +**engine ⇄ network.** Two query methods, no others. + +- `send(from, to, byte_len, sent_at_ns) → SendOutcome`. `SendOutcome` is + either `Arrive { at_ns }` or `Drop { reason }`. The network mutates its + per-link state (last-send-time, warm/cold) as it answers. +- `apply_mutation(mutation, at_ns) → Vec`. The network + updates its internal state and returns a list of currently-scheduled + deliveries the mutation invalidates. The engine removes those from its + queue. + +**engine ⇄ host.** Three call sites, no others. The host responds to each +with `Vec`; the engine processes actions in returned order. + +- `tick(now_ns)` — fired at the host's tick instants. +- `recv(message, now_ns)` — fired when a delivery event for this host pops. +- `snapshot() → SnapshotBytes` — fired on snapshot dispatch. + +`Action` is a closed enum the engine handles exhaustively: + +- `Send { to: HostId, message: HostMessage }` — engine asks the codec for + the message's byte length, asks the network for arrival time, schedules a + `Deliver` event or notifies the sender of `SendFailed` (next bullet). +- `RecordEvent { event: EventBytes }` — engine forwards to the bundle + writer with the current virtual time. +- `ScheduleTimer { at_ns, token: TimerToken }` — engine schedules a + `TimerFired` event delivered through `recv` at `at_ns`. +- `Halt` — engine stops issuing further ticks to this host. Inbound recv + still flows (so the host can observe drains) until the host's `recv` + itself returns `Halt`. + +The engine never invents actions; the host produces them. The engine never +silently drops actions; an unknown variant aborts the run. + +**engine → bundle writer.** A single method, `write_record(record)`. The +record is one of: + +- `EventRecord { virtual_time_ns, host_id, kind_tag, event_bytes }` — + produced by host `RecordEvent` actions and by engine-synthesized events + (drops, cold-dial penalty firings, cache-invalidate firings, send-failure + notifications). +- `SnapshotRecord { virtual_time_ns, host_id, snapshot_bytes }` — produced + by snapshot dispatch. +- `MutationRecord { virtual_time_ns, mutation }` — produced when a mutation + pops. + +The bundle writer is append-only and accepts records in arbitrary order. +It is the writer's responsibility to organize records into the bundle +layout in §9. + +**bundle → assertion evaluator.** The evaluator reads the finished bundle +from disk. Its input is the bundle path; its output is a `verdicts.json` +written into the same bundle. §10 names the verdict format. + +### 3.3 Host kinds and the codec contract + +A host kind is a `(host_trait_impl, codec)` pair. The codec exists because +the bandwidth model needs to know how many bytes a host's outgoing message +will occupy on the wire, and that number must equal what the production +transport would put on the wire for the same message, or the simulator's +bandwidth-driven failure modes diverge from the live ones. + +For each host kind, the codec exposes: + +- `encode(message) → bytes`. +- `decode(bytes) → message`. +- `kind_tag() → str`. + +A contract test asserts byte-equality between the sim's encode path and the +production transport's encode path for a representative message set; +divergence breaks the build. + +For the MVP, the SWIM host kind reuses the production transport's encoding +logic directly. If production code is too tangled with iroh to import +cleanly, the encoding is factored into a small shared module that both the +production transport and the sim host call; that refactor is part of the +MVP, not deferred. + +### 3.4 Independent buildability + +Each component in §3.1 can be built by an independent agent against the +contracts in §3.2 and §3.3 alone. Specifically: + +- The scenario loader is built against §8. +- The network is built against §5 and the substream rule in §7. +- The engine is built against §4 plus the network and host call signatures. +- The SWIM host is built against §6 and the production SWIM API. +- The bundle writer is built against §9. +- The assertion evaluator is built against §10. + +A change to a component's *internal* structure is invisible to the others. +A change to a contract in §3.2 / §3.3 is a spec amendment. + +--- + +## 4. The engine + +The engine owns the virtual clock, the scheduling queue, the host table, +the network reference, and the bundle-writer reference. + +### 4.1 The scheduling queue + +The queue is a priority queue over `(virtual_time_ns, sequence_number)` +keys. The sequence number is assigned monotonically on enqueue; it is the +only tie-break mechanism the engine permits. Two events at the same virtual +time pop in enqueue order. + +Each entry carries one of: + +- `Tick { host_id }`. +- `Deliver { host_id, message_bytes }`. +- `TimerFired { host_id, token }`. +- `Mutation { mutation }`. +- `Snapshot`. +- `Terminate`. + +Pre-population: at engine start, one `Tick` is enqueued per host at the +host's first-tick virtual time, one `Mutation` per scenario mutation, one +`Snapshot` per scenario snapshot request, and one `Terminate` at +`duration_ns`. + +### 4.2 The main loop + +Pop the smallest entry. Advance the virtual clock to its time. Dispatch by +kind (§4.3 through §4.7). Repeat until `Terminate` pops or the +early-termination condition fires (§4.8). After termination, finalize the +bundle and run the assertion evaluator. + +The clock advances *only* on pop. Nothing in the engine reads any other +clock, virtual or real. + +### 4.3 Tick dispatch + +For `Tick { host_id }`: + +1. Call `host[host_id].tick(now_ns)`. If the host has `Halt`-ed, skip the + call but still schedule the next tick (the host may un-halt only via a + `PeerResurrect` mutation). +2. Process the returned action list in order (§4.6). +3. Enqueue the next `Tick { host_id }` at + `now_ns + tick_period_ns[host_id]`. + +The first tick for each host is offset by a stable, seed-derived per-host +offset. The offset is drawn from the host's RNG substream (§7) so peers do +not tick on the same virtual instants and silent symmetry artefacts do not +mask real timing bugs. + +### 4.4 Delivery dispatch + +For `Deliver { host_id, message_bytes }`: + +1. If `host[host_id]` is halted or killed, drop the delivery and emit a + `DropOnDelivery` engine-synthesized event. +2. Otherwise, decode the bytes via the host kind's codec. +3. Call `host[host_id].recv(message, now_ns)`. +4. Process the returned action list in order (§4.6). + +### 4.5 Other dispatches + +- `TimerFired { host_id, token }` is delivered through `recv` with a + `TimerFired(token)` envelope and processed identically to a network + delivery. +- `Mutation { mutation }` is forwarded to `network.apply_mutation`. The + returned list of invalidated deliveries is removed from the queue (or + tombstoned — the visible behaviour is identical). The engine emits a + `MutationRecord` to the bundle writer. +- `Snapshot` asks every live host for its `snapshot()` and forwards each + result to the bundle writer as a `SnapshotRecord`. +- `Terminate` ends the main loop. + +### 4.6 Action processing + +For each action returned by a host: + +- `Send { to, message }` — encode via the codec, ask the network with the + resulting byte length. On `Arrive`, enqueue a `Deliver` at the returned + time. On `Drop`, emit a `DropOnSend` engine-synthesized event *and* + deliver a `SendFailed` envelope to the sender via the same recv path the + production transport would. +- `RecordEvent { event }` — forward to the bundle writer with current + virtual time. +- `ScheduleTimer { at_ns, token }` — enqueue `TimerFired` at `at_ns`. +- `Halt` — mark the host halted (§4.3 covers re-entry). + +### 4.7 Engine-synthesized events + +The engine emits records for behaviours hosts do not see directly: + +- `DropOnSend { from, to, reason }` — a send the network refused. +- `DropOnDelivery { to, reason }` — a delivery the engine refused at + arrival time (host killed mid-flight). +- `CacheStateChange { from, to, transition }` — the link warmed, the link + went cold by idle, the link was invalidated by a mutation. The network + surfaces these to the engine through a side-channel on `send` and + `apply_mutation`. +- `DialStart` / `DialOutcome` — emitted whenever the cold-dial penalty + fires on a send. + +These records exist so the bundle's observable surface matches what +production diagnostics emit for the same activity. The engine never +suppresses them and never emits them for activity that did not happen. + +### 4.8 Early termination + +A scenario may set `early_terminate_on_all_assertions_resolved = true`. If +set, after each event dispatch the engine polls the assertion evaluator's +streaming side (§10.4); if every declared assertion has a resolved verdict, +the engine fast-forwards to `Terminate`. The bundle still records every +event that fired up to that point. + +### 4.9 Time unit + +The engine's virtual clock is in integer nanoseconds. The manifest records +the unit so post-processors render times consistently. Sub-nanosecond +ordering is not modelled. + +--- + +## 5. The network + +### 5.1 Topology + +A directed graph. Vertices are the host IDs declared in the scenario. +Edges carry link policies. An ordered pair with no declared edge is +permanently partitioned; the network returns `Drop(NoRoute)` for any send +on it. This is distinct from a temporary partition mutation, which can heal. + +Asymmetry is allowed and intended: `policy(A→B)` and `policy(B→A)` are +independent. + +### 5.2 Link policy + +Each edge carries the following integer-valued fields: + +- `latency_ns` — base one-way delivery time. +- `jitter_stddev_ns` — symmetric jitter; samples are drawn from a + precomputed integer lookup table approximating a standard-normal + distribution scaled by this stddev (§7 forbids float math in decisions). +- `loss_prob_ppm` — independent drop probability per send, parts-per- + million. +- `reorder_prob_ppm` — probability of inserting extra delay sufficient to + swap delivery order with the next message on the same edge. +- `bandwidth_bps` — bytes per second. A message of N bytes occupies the + link for `(N * 1_000_000_000) / bandwidth_bps` ns. +- `cold_dial_penalty_ns` — extra latency added when the link is cold. +- `cache_warm_after_ns` — wall of warm-time after first contact before + subsequent sends are warm. +- `cache_invalidate_after_idle_ns` — idle duration after which the link + returns to cold. + +### 5.3 Per-link state + +Each edge tracks: + +- `last_send_ns` — virtual time of the last `send` that returned `Arrive`. +- `last_arrive_ns` — virtual time of the latest scheduled arrival; used by + the bandwidth model for the next message's serialization start. +- `cache_state` — `Cold`, `Warming(since_ns)`, or `Warm`. + +State transitions happen inside `send` and `apply_mutation`. + +### 5.4 The send algorithm + +`send(from, to, byte_len, sent_at_ns) → SendOutcome`. Steps: + +1. If the edge does not exist, return `Drop(NoRoute)`. +2. If the active partition set cuts `(from, to)`, return `Drop(Partitioned)`. +3. If a `LossBurst` mutation is active for this edge, use its override + probability; otherwise use the edge's `loss_prob_ppm`. Draw a u32 from + the edge's RNG substream; if `draw % 1_000_000 < prob_ppm`, return + `Drop(Lossy)`. +4. Compute `serialization_start = max(sent_at_ns, last_arrive_ns)`. Compute + `serialization_end = serialization_start + (byte_len * 1e9 / bandwidth_bps)`. +5. Compute `arrival = serialization_end + latency_ns + jitter_sample`, + where `jitter_sample` is one draw from the per-link substream into the + integer-Gaussian table, scaled by `jitter_stddev_ns`. +6. If a `LatencySpike` mutation is active, multiply the additive latency + contribution (latency + jitter) by the spike factor before adding. +7. If a `RelayBuffer` mutation is active, take `arrival = + max(arrival, sent_at_ns + floor_ns)`. +8. If `cache_state` is `Cold`, add `cold_dial_penalty_ns` to `arrival` and + transition `cache_state` to `Warming(now)`. Emit a `DialStart` side- + channel notification to the engine and a `DialOutcome` at the arrival + time. +9. If the reorder draw fires, add enough delay so this message arrives + after the next message scheduled on this edge. +10. Update `last_send_ns = sent_at_ns`, `last_arrive_ns = arrival`. If + `cache_state` is `Warming(since)` and `now - since >= cache_warm_after_ns`, + transition to `Warm` and emit a `CacheStateChange`. +11. Return `Arrive(arrival)`. + +If `last_send_ns - now > cache_invalidate_after_idle_ns` at the start of a +send, the link returns to `Cold` and emits a `CacheStateChange` before +proceeding. + +### 5.5 Mutations + +Supported kinds: + +- `Partition { peers_a, peers_b }` — set the active partition to cut every + edge between the two groups in both directions. In-flight messages on + cut edges are returned in the invalidated-deliveries list. +- `Heal` — clear the active partition set. No in-flight invalidations. +- `LatencySpike { links, factor_x100, duration_ns }` — multiply additive + latency on named links by `factor_x100 / 100` for a duration. In-flight + messages are not retroactively delayed. +- `LossBurst { links, prob_ppm, duration_ns }` — override loss probability + on named links for a duration. +- `RelayBuffer { links, floor_ns, duration_ns }` — impose a minimum + delivery delay on named links for a duration. +- `PeerKill { peer }` — drop the peer's inbox. In-flight deliveries to the + peer are invalidated. The peer's ticks are stopped by the engine. +- `PeerResurrect { peer, preserve_state }` — restart the peer. If + `preserve_state`, the engine reuses the host instance; otherwise a fresh + host of the same kind is instantiated from the scenario's peer + declaration. + +### 5.6 Determinism within the network + +Every random draw the network makes comes from a substream keyed by +`("link", from_id, to_id)` (§7). Editing one link's policy must not +perturb the draws on any other link. + +### 5.7 Out of scope for the network model + +The network does not model MTU, fragmentation, congestion control, TCP- +style backpressure, NAT state, or inter-peer clock skew. These limits are +named in the known-gaps document and re-entered when an algorithm under +test is sensitive to them. + +--- + +## 6. Hosting an entity + +### 6.1 The host trait + +A host kind implements: + +- `fn id(&self) -> HostId`. +- `fn kind_tag() -> &'static str`. Used for routing and bundle tagging. +- `fn tick(&mut self, now_ns: u64) -> Vec`. +- `fn recv(&mut self, message: HostMessage, now_ns: u64) -> Vec`. +- `fn snapshot(&self) -> SnapshotBytes`. + +`HostMessage` is either a decoded inbound application message (the host +kind's own type, dispatched via the codec) or a `TimerFired(token)` or a +`SendFailed { to, reason }` envelope. + +A host kind also exposes: + +- `fn new_from_config(id: HostId, config: HostKindConfig, rng: HostRng) -> Self`. +- A codec (§3.3). +- A validation routine for its `HostKindConfig` (used by the scenario + loader; §8). + +### 6.2 The SWIM host kind + +The SWIM host wraps the production SWIM state machine without re- +implementing it. It: + +- Constructs the production state machine with the scenario's per-peer + config. +- Installs the production diagnostics emitter against a shim that pushes + every emission into a per-tick / per-recv `Vec` as + `RecordEvent` actions. +- Installs the production tier-2 introspector against the host's + `snapshot()` method so the snapshot bytes are exactly what production + emits. +- Dispatches `recv` to the production handler matching the message kind + (ping, ack, ping-request, indirect-ack, join-request, join-response). +- Translates production state-machine output (outgoing messages, timer + requests) into `Send` and `ScheduleTimer` actions. + +When the production state machine emits an output the host adapter does +not know how to route — a new message kind in a future SWIM version, for +example — the adapter panics. Silent fallback is exactly the class of bug +the simulator is meant to prevent. + +The adapter does *not* substitute for any production logic. Its job is +purely translation between production data types and the host trait. + +### 6.3 Adding a new host kind + +Adding a new host kind is a strict superset operation: + +1. Implement the host trait against the new algorithm. +2. Provide a codec. +3. Add a `kind` arm to the scenario loader's peer-declaration parser. +4. Register the kind with the engine's host-instantiation factory. + +Existing host kinds continue to work without change. The network, the +bundle writer, the engine main loop, and the determinism contract are +host-kind-agnostic. + +--- + +## 7. Determinism + +This section is normative. A violation is a ship-blocker. + +### 7.1 Forbidden inputs + +No part of the simulator reads any of: host wall clock, host monotonic +clock, host process or thread ID, host hostname, environment variables +outside a documented sim-internal prefix, `/dev/urandom` or any host RNG +source, network interface state, filesystem state outside the bundle +output path. + +The repo's existing lint scanner catches the static cases. Drift is +caught by the cross-architecture parity test (§7.6). + +### 7.2 The randomness tree + +All randomness derives from one root random stream seeded by the +scenario's `seed` field. Substreams are derived by hashing a fixed, +documented tuple with a constant-key siphash: + +- `("link", from_id, to_id)` — per-edge substream used by the network. +- `("host", host_id, label)` — per-host substream used for tick offsets + and any RNG the host kind needs. +- `("mutation", index)` — per-mutation substream if a mutation needs + randomness (none currently do). + +The hash function and the key are fixed in code. Substreams are stable +across runs and across host architectures. + +Substream layout matters: editing one link's policy must not perturb the +draws on any other link, or every test edit becomes a new random universe +and bisection is impossible. + +### 7.3 No hash-randomized iteration + +Wherever any component iterates a collection, the order is determined by +the natural key order (host IDs sort lexicographically; pairs sort +lexicographically on the pair) or by an insertion-order-preserving +structure. Point lookups into hash maps remain allowed; iteration is the +divergence source. + +### 7.4 No floating point in decisions + +Latencies are integer nanoseconds. Probabilities are integer parts-per- +million of a fixed denominator. Jitter samples come from a precomputed +integer lookup table approximating a standard-normal distribution; the +table is checked in. Bandwidth math uses integer arithmetic with explicit +scaling; the precise formula is in §5.4. + +Floating point is permitted in post-hoc calibration tools that read a +bundle. It is forbidden in the engine, the network, the host adapter, the +bundle writer, and the assertion evaluator's verdict computation. + +### 7.5 No work outside the scheduler + +No background thread, no async runtime, no timer that fires without the +engine's knowledge. Every effect is the consequence of a popped event. + +### 7.6 The cross-architecture parity test + +The test suite contains a reference scenario whose bundle output has a +known checksum, checked in. The test runs the simulator on the reference +scenario on every supported architecture and compares the checksum to the +stored value. A mismatch is either a deliberate spec amendment (with +justification) or a bug. + +--- + +## 8. The scenario format + +A scenario is a single TOML file. The file is the simulator's only input +and is byte-for-byte sufficient to reproduce any run. The format is TOML +because the repo already uses it; the choice is not load-bearing. + +### 8.1 Schema + +Top-level fields: + +- `name: String`. +- `seed: u64`. +- `duration_ns: u64`. +- `early_terminate_on_all_assertions_resolved: bool` (default `false`). + +A `[default_tick]` table: + +- `period_ns: u64`. + +A `[default_link]` table containing every field §5.2 names; per-edge +overrides under `[[links]]` may override any subset. + +A `[[peers]]` array, each entry: + +- `id: String`. +- `kind: String` — selects the host kind. +- `kind_config: { ... }` — host-kind-specific opaque table. +- `initial_state: String` — host-kind-specific. +- `tick_period_ns_override: u64` (optional). + +A `[[links]]` array, each entry: + +- `from: String`. +- `to: String`. +- Any subset of the §5.2 fields (overrides on top of `[default_link]`). + +A `[[mutations]]` array, each entry: + +- `at_ns: u64`. +- `kind: String` — one of the §5.5 variants. +- Variant-specific parameters. + +A `[[snapshots]]` array, each entry: + +- `at_ns: u64`. + +A `[[assertions]]` array, each entry: + +- `kind: String` — one of the §10.1 variants. +- Variant-specific parameters. + +A `[base]` table with a single optional `extends: String` pointing to +another scenario file; the merge is deep, with child entries overriding +parent at the leaf. + +### 8.2 Validation + +The loader rejects: + +- Duplicate peer IDs. +- Link, mutation, snapshot, or assertion references to undeclared peers. +- `duration_ns < max(mutation.at_ns)` or similar for snapshots / + assertions. +- Any host-kind config that fails the host kind's own validation routine. + For the SWIM kind, this includes `probe_interval < suspicion_timeout`. +- A `default_link` field that is non-integer, negative, or in a unit other + than the §5.2 names (e.g., `latency_ms` is rejected; only `latency_ns`). + +Validation failures produce a structured error with the file path, the +offending field, and a one-line explanation. + +### 8.3 Library structure + +Scenarios live under a `scenarios/` directory next to the simulator, in +four subdirectories: + +- `smoke/` — happy-path scenarios. Three-node mesh no impairments; + eight-node ring; chain; star. Each asserts continuous Alive. +- `reproduction/` — known live failures. Each is expected to fail until + its cause is fixed. +- `topology/` — partition-and-heal, rolling restart, peer churn. No + specific bug targeted. +- `calibration/` — paired with a captured production bundle. §11. + +Every scenario carries a top-of-file prose comment naming what it +reproduces, the expected verdict (pass-now / fail-until-fix / +sensitivity-study), and any base scenario it extends. + +--- + +## 9. The bundle + +### 9.1 Layout + +A bundle is a directory laid out as follows: + +``` +/ + manifest.json + scenario.toml # echo of the scenario that produced the run + events.ndjson # newline-delimited JSON event stream + snapshots/ + / + .json + verdicts.json # produced by the assertion evaluator (§10) + known_gaps.md # static copy of the known-gaps document +``` + +### 9.2 The event stream + +Each line is one JSON object with the envelope: + +``` +{ + "virtual_time_ns": , + "host_id": , # null for engine-synth events not bound to a host + "kind_tag": , # "swim", "engine", etc. + "event": +} +``` + +The event payload schema is exactly the production diagnostics schema for +that event kind. The simulator must not invent new event kinds; an event +the simulator emits is one production also emits. + +Event kinds the MVP emits: + +- SWIM state transitions, message-send and receive accounting, probe + lifecycle (sent / received / timed out), self-incarnation bumps. +- Engine-synthesized cache state changes, dial start and outcome, drop on + send, drop on delivery, send-failure errors. +- Mutation records. + +Event kinds belonging to subsystems out of scope (iroh internals, node- +map updates, kademlia operations) are not emitted and are listed in +`known_gaps.md`. The schema-diff tool ignores them on the production side +when comparing. + +### 9.3 Snapshots + +A snapshot is exactly the production tier-2 SWIM-state JSON for that host +at that virtual time. The schema is unchanged from production. + +### 9.4 The manifest + +`manifest.json` records: + +- Simulator version (commit hash). +- Path and SHA-256 of the scenario file. +- Seed. +- Duration in ns. +- Host architecture the run executed on. +- SHA-256 of `events.ndjson`. +- SHA-256 of each snapshot file, keyed by relative path. + +Wall-time fields are derived from the virtual clock; the manifest declares +the unit so post-processors do not confuse virtual time with real time. + +### 9.5 Renderability + +The bundle is renderable by the same post-processor production uses. If +the renderer requires inputs the simulator does not have (collector-side +receive timestamps, for instance), the simulator substitutes the virtual- +clock equivalent and records the substitution in the manifest. + +--- + +## 10. Assertions + +### 10.1 The assertion catalog + +Each assertion kind has a name and a parameter shape. The MVP catalog: + +- `all_alive_at { at_ns, peers }` — at the given virtual time, every named + peer's view of every other named peer is Alive. +- `all_alive_throughout { window_start_ns, window_end_ns, peers }` — the + above, continuously, across a window. +- `convergence_after { after_ns, within_ns, peers }` — after the named + time, the cluster reaches a consistent membership view within the + bounded duration. +- `no_flap_while_probes_ok { peer, window_start_ns, window_end_ns }` — no + peer transitions Suspect → Alive → Suspect within a window in which the + peer's bidirectional probes are succeeding. +- `no_dead_when_probes_ok { peer, window_start_ns, window_end_ns }` — the + peer is never marked Dead in any other peer's view while bidirectional + probes are succeeding. +- `self_incarnation_bounded { peer, max_value }` — the peer's self- + incarnation counter never exceeds the bound. +- `message_size_bounded { kind, max_bytes }` — no sent message of the + named kind exceeds the byte threshold. +- `dead_peer_resurrects_within { peer, after_ns, within_ns }` — after the + peer becomes reachable again, the cluster marks it Alive within a + duration. +- `event_count { kind, max }` — bounds the absolute count of an event + kind across the run. +- `event_rate { kind, window_ns, max_per_window }` — bounds the rate of + an event kind. + +Adding a kind is a deliberate amendment to this section. + +### 10.2 The evaluator interface + +The evaluator reads the bundle's `events.ndjson` and `snapshots/` and +evaluates each assertion. Per assertion it emits a verdict: + +``` +{ + "name": "", + "kind": "", + "parameters": { ... }, + "outcome": "Pass" | "Fail" | "Inconclusive", + "evidence": [ + { "virtual_time_ns": , "event_or_snapshot_ref": "" } + ] +} +``` + +`Inconclusive` is reserved for assertions whose preconditions did not +fire during the run (e.g., a peer the assertion names never became +reachable). + +`verdicts.json` is an array of verdict objects, one per declared +assertion, in the order the scenario declared them. + +### 10.3 Library properties + +A library property is a parameterized assertion kind evaluated across a +generated distribution of scenarios. The MVP ships one such property: the +gossip-flap detector. The property generates scenarios from a declared +space (peer count, latency range, jitter range, loss range, duration) and +applies `no_flap_while_probes_ok` to each. + +The framework records the random seed that produced any failing scenario +so the failure is reproducible. The current SWIM source must fail this +property. The proposed fix must pass it. + +Library properties are otherwise identical to per-scenario assertions in +output shape; their verdicts go into the property runner's own output, +not into a single bundle's `verdicts.json`. + +### 10.4 Streaming evaluation (for early termination) + +For early termination (§4.8) the evaluator exposes a streaming side: as +events are emitted, the evaluator may resolve assertions whose verdicts +are determinable from the prefix. The engine polls this side after each +event dispatch. The streaming side is an optimization; the post-run side +remains the authoritative source for `verdicts.json`. + +--- + +## 11. Calibration + +Calibration measures the simulator's fidelity against captured production +bundles. + +### 11.1 The corpus + +The MVP corpus is the three N3 vast.ai bundles described in the +deployment report. Each pairs with a scenario in `scenarios/calibration/` +that approximates the conditions under which the bundle was produced. + +### 11.2 The procedure + +For each pair, the calibration tool: + +1. Runs the simulator with the declared scenario. +2. Loads the corresponding production bundle. +3. Computes the comparison metrics (§11.3) on both bundles. +4. Reports per-metric pass/fail against per-metric tolerances. + +### 11.3 The metrics + +- Per-peer event-timespan summaries (p50, p90, p99) per event kind. +- State-transition reason distribution across all peers. +- Message-size distribution per message kind. +- Message-count per kind, per peer pair. +- Self-incarnation trajectory per peer. +- Connection-cache hit count. +- Dial-started count. +- Per-peer fraction of run time in the Alive state. + +Tolerances ship as placeholders informed by intuition; the first +calibration pass against the N3 corpus sets the real numbers. A widening +of a tolerance is a documented degradation in the known-gaps document. + +### 11.4 CI integration + +Calibration runs on every change that touches the simulator or the SWIM +state machine. A regression — a previously-in-tolerance metric goes out +of tolerance — blocks merge. A widening of a tolerance is a separate, +justified commit. + +--- + +## 12. Implementation phasing + +Each phase ships independently. At every phase boundary the partial +simulator does something useful and is testable. + +| Phase | Adds | Verifiable outcome | +|-------|------|-----| +| 1 | Scenario loader | Every shipped scenario parses; malformed input is rejected with a structured error. | +| 2 | Network (no engine, no host) | A microbenchmark queries `send` at scenario-scale rates and produces deterministic outputs. | +| 3 | Engine skeleton + a trivial echo host kind | The cross-architecture parity test passes on a reference scenario. | +| 4 | SWIM host kind + codec contract test | The smoke scenario runs to completion and asserts continuous Alive. | +| 5 | Assertion evaluator | The gossip-flap reproduction scenario fails on current SWIM, passes after the fix. | +| 6 | Bundle writer + schema-diff tool | A sim bundle renders through the production post-processor and diffs against a prod bundle reporting only known-gap events. | +| 7 | Calibration tool | At least one N3 pair passes calibration. | + +MVP exit is the end of phase 7. Subsequent phases — proptest catalog +expansion, scenario library growth, the forward-compatibility work in +§13 — are post-MVP. + +Phases 1, 2, 6 are independently buildable by separate agents from this +spec alone; phases 3 onwards require the prior phase as input. + +--- + +## 13. Forward compatibility + +Each MVP non-goal has a re-entry point that does not require revisiting +MVP-scope behaviour. + +- **New algorithm.** Implement a new host kind (§6.3). The engine's + dispatch, the network, the bundle, and the determinism contract are + unchanged. +- **Real iroh / quinn.** A future host kind wraps real iroh's `Endpoint` + around a sim-facade UDP that rides the network model; a virtual + `tokio::time` reads from the engine's virtual clock. Existing SWIM + hosts still work because they do not call into iroh. +- **Determinism detector as peer.** Once a host kind exists whose code + traverses a facade, the detector becomes another host kind that probes + the facade and emits verdict events. Until then it has nothing to probe. +- **Replay mode.** A converter reads a captured production bundle and + emits a scenario. The engine does not change; only the converter is + new. +- **Opaque subprocess peer.** A host kind whose tick / recv shim is a + wrapped subprocess with syscall-level I/O virtualization. + +The MVP architecture admits each of these without retracting any of the +contracts in §3 / §4 / §5 / §6 / §7. + +--- + +## 14. Open questions + +These are deliberately unanswered; they are expected to resolve during +phases 1–3. + +- SWIM's reactive probe mode introduces an internal safety-sweep timer. + Whether the engine needs a separate event kind for the safety sweep, or + whether driving the host on its tick interval is enough, depends on + details inside the production probe code that are easier to resolve + once the engine skeleton exists. +- One peer corresponds to one host ID in the MVP. Production permits a + single host to expose multiple endpoints. The MVP punts; if a + calibration scenario needs the multi-endpoint shape, the scenario grows + a per-peer endpoint list and the engine dispatches by endpoint. +- The starting tolerances in §11.3 are placeholders. The first + calibration run against the N3 corpus sets the real numbers; those + numbers replace the placeholders in a follow-up commit. +- The simulator's code lands under `crates/simulation/` after the current + cleanup of that crate. If the cleanup renames or relocates the + simulator, the phase plan in §12 needs a one-pass path refresh; + nothing else in this spec depends on the path. + +--- + +## 15. Spec change protocol + +Changes that relax a contract in §3 / §4 / §5 / §6 / §7 — a widened +tolerance, a removed assertion, a relaxed determinism rule — are +behaviour-changing and require a deliberate commit whose subject names +the relaxation and whose body justifies it in prose. A tightening change +— a new assertion kind, a narrower tolerance, a more restrictive +determinism rule — can land in any commit. Adding new scenarios, new +properties, or new phases does not require special treatment. + +The lint scanner, the determinism digest, the cross-architecture parity +test, the schema-diff tool, the encoding-symmetry contract test, and the +calibration tolerances are the load-bearing artefacts that enforce this +spec. If any of them is short-circuited — disabled in CI, allow-listed at +the call site, silenced with an exemption — the spec is being worked +around, and the workaround must surface in code review. + +--- + +## 16. References + +- `examples/pipeline-parallel-inference/N3_DEPLOYMENT_REPORT.md` — + source of truth for the live failures the simulator must reproduce. +- `crates/simulation/NORTH_STAR.md` — the long-term simulator vision. + This MVP is a strict subset and does not retract any of its claims. +- `crates/simulation/BLOCKED.md` — the staged plan this MVP supersedes + for the immediate iteration. The deeper goals there (real quinn / iroh + on a sim facade, detector as a peer, full distribution-crate facade + migration) remain on the roadmap, just not gating SWIM-tuning. +- `crates/distribution/src/swim/` — the production SWIM state machine + the SWIM host kind wraps. +- `crates/distribution/src/diagnostics/` — the schema the bundle must + match and the renderer it must render through. diff --git a/tests/integration/Cargo.toml b/tests/integration/Cargo.toml index e8804b0..0873697 100644 --- a/tests/integration/Cargo.toml +++ b/tests/integration/Cargo.toml @@ -9,6 +9,6 @@ swactor-datastore = { path = "../../crates/datastore", features = ["node"] } distribution = { path = "../../crates/distribution", features = ["iroh", "relay"] } dashboard = { path = "../../crates/dashboard" } swactor = { path = "../..", features = ["serde", "transport"] } -iroh = "0.96" +iroh = "0.98" ureq = { version = "2", features = ["json"] } serde_json = "1"