tawasuyu

tawasuyu/card

Sovereign content-addressed identity primitive (card-core + WIT-typed flows) in Rust.

Markdown 9Rust 7TOML 4HTML 1165 Definitions

chore: refresco desde el monorepo (pin tawasuyu.git v0.3.0)

Trae los README y LEEME de todos los crates del dominio, que ahora existen
para los 1.096 crates del workspace.

7417b3a5a68890126061562873ae0a3804549835 · by Sergio · (1 month ago) · parent 58c483eb2 · browse the tree at this commit

Semantic delta · 24

shared/card/card-net/src/lib.rs

shared/card/card-wit/src/lib.rs

8 files · +1238 −431

diff --git a/Cargo.lock b/Cargo.lock
index 3f59096..7cc2b34 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -67,7 +67,7 @@ dependencies = [
  "nom",
  "num-traits",
  "rusticata-macros",
- "thiserror 2.0.19",
+ "thiserror 2.0.20",
  "time",
 ]
 
@@ -114,13 +114,13 @@ dependencies = [
 
 [[package]]
 name = "async-trait"
-version = "0.1.91"
+version = "0.1.92"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ae36dc4177970ef04fde5178d3e2429882def40e57a451f919c098f72baa6cec"
+checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 3.0.3",
+ "syn 3.0.5",
 ]
 
 [[package]]
@@ -220,16 +220,15 @@ dependencies = [
 
 [[package]]
 name = "blake3"
-version = "1.8.5"
+version = "1.8.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0aa83c34e62843d924f905e0f5c866eb1dd6545fc4d719e803d9ba6030371fce"
+checksum = "6d9e454fc11f76977dc803893aff6304ed33d6a26efae8696573bea74baa27ae"
 dependencies = [
- "arrayref",
  "arrayvec",
  "cc",
  "cfg-if",
  "constant_time_eq",
- "cpufeatures 0.3.0",
+ "cpufeatures 0.3.1",
 ]
 
 [[package]]
@@ -276,7 +275,7 @@ dependencies = [
  "postcard",
  "serde",
  "serde_json",
- "thiserror 2.0.19",
+ "thiserror 2.0.20",
  "toml",
  "ulid",
 ]
@@ -291,7 +290,7 @@ dependencies = [
  "libp2p-allow-block-list",
  "libp2p-stream",
  "serde",
- "thiserror 2.0.19",
+ "thiserror 2.0.20",
  "tokio",
  "tokio-util",
 ]
@@ -302,15 +301,15 @@ version = "0.1.0"
 dependencies = [
  "anyhow",
  "card-core",
- "thiserror 2.0.19",
+ "thiserror 2.0.20",
  "wit-parser",
 ]
 
 [[package]]
 name = "cc"
-version = "1.4.0"
+version = "1.4.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5add81bb678e6cb321aff7fa0dc7689ad82b112dbc032cea19f91d6b8e3582b9"
+checksum = "005ec2760ca554fae18df7a11195552ec576cd665632a881bc011d5bb2fd4d80"
 dependencies = [
  "find-msvc-tools",
  "shlex",
@@ -341,12 +340,12 @@ dependencies = [
 
 [[package]]
 name = "chacha20"
-version = "0.10.1"
+version = "0.10.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81"
+checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06"
 dependencies = [
  "cfg-if",
- "cpufeatures 0.3.0",
+ "cpufeatures 0.3.1",
  "rand_core 0.10.1",
 ]
 
@@ -380,7 +379,7 @@ version = "0.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1"
 dependencies = [
- "thiserror 2.0.19",
+ "thiserror 2.0.20",
 ]
 
 [[package]]
@@ -437,9 +436,9 @@ dependencies = [
 
 [[package]]
 name = "cpufeatures"
-version = "0.3.0"
+version = "0.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201"
+checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566"
 dependencies = [
  "libc",
 ]
@@ -452,27 +451,27 @@ checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b"
 
 [[package]]
 name = "crossbeam-channel"
-version = "0.5.16"
+version = "0.5.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d85363c37faeca707aef026efa9f3b34d077bce547e48f770770625c6013679e"
+checksum = "98b0cc327b5bc766e7fda9c9260cc0fa81b43a8e240440422dff70788e3f9ef1"
 dependencies = [
  "crossbeam-utils",
 ]
 
 [[package]]
 name = "crossbeam-epoch"
-version = "0.9.20"
+version = "0.9.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f"
+checksum = "dc74980687109a3b14c72fd458107bf0baa1da1a1a805e178d15501ba9b86d9d"
 dependencies = [
  "crossbeam-utils",
 ]
 
 [[package]]
 name = "crossbeam-utils"
-version = "0.8.22"
+version = "0.8.23"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17"
+checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6"
 
 [[package]]
 name = "crunchy"
@@ -529,15 +528,15 @@ dependencies = [
 
 [[package]]
 name = "data-encoding"
-version = "2.11.0"
+version = "2.11.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8"
+checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06"
 
 [[package]]
 name = "data-encoding-macro"
-version = "0.1.20"
+version = "0.1.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3259c913752a86488b501ed8680446a5ed2d5aeac6e596cb23ba3800768ea32c"
+checksum = "c6a127ecbb3c4632e1525380e04c0c3fcf8dcb44d32a79ea290d8a36906edcd8"
 dependencies = [
  "data-encoding",
  "data-encoding-macro-internal",
@@ -545,12 +544,12 @@ dependencies = [
 
 [[package]]
 name = "data-encoding-macro-internal"
-version = "0.1.18"
+version = "0.1.19"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ccc2776f0c61eca1ca32528f85548abd1a4be8fb53d1b21c013e4f18da1e7090"
+checksum = "c54e03a951783e8b327515db3f2a2fd0e3bed362a96b066f341ce66ed49b4ead"
 dependencies = [
  "data-encoding",
- "syn 2.0.119",
+ "syn 3.0.5",
 ]
 
 [[package]]
@@ -596,13 +595,13 @@ dependencies = [
 
 [[package]]
 name = "displaydoc"
-version = "0.2.6"
+version = "0.2.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f"
+checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.119",
+ "syn 3.0.5",
 ]
 
 [[package]]
@@ -637,9 +636,9 @@ dependencies = [
 
 [[package]]
 name = "either"
-version = "1.17.0"
+version = "1.18.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d"
+checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34"
 
 [[package]]
 name = "embedded-io"
@@ -701,9 +700,9 @@ checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
 
 [[package]]
 name = "find-msvc-tools"
-version = "0.1.9"
+version = "0.1.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
+checksum = "3e0f1c7c3a72c66fd80abe965175f7523475c0489a87d3ff9d6e8c87d87a9d2d"
 
 [[package]]
 name = "fnv"
@@ -735,7 +734,7 @@ dependencies = [
 [[package]]
 name = "format"
 version = "0.1.0"
-source = "git+https://git.tawasuyu.net/tawasuyu/tawasuyu.git?tag=v0.2.0#a446bb3a73a0e022158cc0ec7deb65173859644b"
+source = "git+https://git.tawasuyu.net/tawasuyu/tawasuyu.git?tag=v0.3.0#36a1eabf32eb77f901869f516a9be352f3d17d30"
 dependencies = [
  "blake3",
  "postcard",
@@ -745,9 +744,9 @@ dependencies = [
 
 [[package]]
 name = "futures"
-version = "0.3.33"
+version = "0.3.34"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a88cf1f829d945f548cf8fec32c61b1f202b6d93b45848602fc02af4b12ad218"
+checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3"
 dependencies = [
  "futures-channel",
  "futures-core",
@@ -770,9 +769,9 @@ dependencies = [
 
 [[package]]
 name = "futures-channel"
-version = "0.3.33"
+version = "0.3.34"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae"
+checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4"
 dependencies = [
  "futures-core",
  "futures-sink",
@@ -780,15 +779,15 @@ dependencies = [
 
 [[package]]
 name = "futures-core"
-version = "0.3.33"
+version = "0.3.34"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7"
+checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e"
 
 [[package]]
 name = "futures-executor"
-version = "0.3.33"
+version = "0.3.34"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6754879cc9f2c66f88c6e5c35344bb0bdb0708b0352b1201815667c7eabc7458"
+checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432"
 dependencies = [
  "futures-core",
  "futures-task",
@@ -797,9 +796,9 @@ dependencies = [
 
 [[package]]
 name = "futures-io"
-version = "0.3.33"
+version = "0.3.34"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a"
+checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed"
 
 [[package]]
 name = "futures-lite"
@@ -813,13 +812,13 @@ dependencies = [
 
 [[package]]
 name = "futures-macro"
-version = "0.3.33"
+version = "0.3.34"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b"
+checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.119",
+ "syn 3.0.5",
 ]
 
 [[package]]
@@ -835,15 +834,15 @@ dependencies = [
 
 [[package]]
 name = "futures-sink"
-version = "0.3.33"
+version = "0.3.34"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e34418ac499d6305c2fb5ad0ed2f6ac998c5f8ca209b4510f7f94242c647e307"
+checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d"
 
 [[package]]
 name = "futures-task"
-version = "0.3.33"
+version = "0.3.34"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109"
+checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd"
 
 [[package]]
 name = "futures-timer"
@@ -853,9 +852,9 @@ checksum = "af43fadb8a98512d547e37b4e92e0ced13e205c061b87b4623eff01d918d6968"
 
 [[package]]
 name = "futures-util"
-version = "0.3.33"
+version = "0.3.34"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa"
+checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc"
 dependencies = [
  "futures-channel",
  "futures-core",
@@ -929,9 +928,9 @@ dependencies = [
 
 [[package]]
 name = "h2"
-version = "0.4.15"
+version = "0.4.19"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6cb093c84e8bd9b188d4c4a8cb6579fc016968d14c99882163cd3ff402a4f155"
+checksum = "ef8e5e5a340588f4452631496976cf8636d4a7ecf600239fdc27615d2530bc16"
 dependencies = [
  "atomic-waker",
  "bytes",
@@ -1001,9 +1000,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
 
 [[package]]
 name = "hermit-abi"
-version = "0.5.2"
+version = "0.5.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
+checksum = "e17592d60ebacc7d5e169f4663c5f84f9161cc90328abcfe8456f41e4dfcb284"
 
 [[package]]
 name = "hex"
@@ -1030,7 +1029,7 @@ dependencies = [
  "rand 0.9.5",
  "ring",
  "socket2 0.5.10",
- "thiserror 2.0.19",
+ "thiserror 2.0.20",
  "tinyvec",
  "tokio",
  "tracing",
@@ -1053,7 +1052,7 @@ dependencies = [
  "rand 0.9.5",
  "resolv-conf",
  "smallvec",
- "thiserror 2.0.19",
+ "thiserror 2.0.20",
  "tokio",
  "tracing",
 ]
@@ -1078,9 +1077,9 @@ dependencies = [
 
 [[package]]
 name = "http"
-version = "1.4.2"
+version = "1.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425"
+checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0"
 dependencies = [
  "bytes",
  "itoa",
@@ -1098,9 +1097,9 @@ dependencies = [
 
 [[package]]
 name = "http-body-util"
-version = "0.1.4"
+version = "0.1.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e9f41fd6a08e4d4ec69df65976da761afd5ad5e58a9d4acb46bd1c953a9e3ff2"
+checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c"
 dependencies = [
  "bytes",
  "futures-core",
@@ -1117,9 +1116,9 @@ checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
 
 [[package]]
 name = "hyper"
-version = "1.11.0"
+version = "1.11.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d22053281f852e11534f5198498373cbb59295120a20771d90f7ed1897490a72"
+checksum = "27b501faa50e7a26c3d3560ca625132f4078a17771f4810baf70475ae48cbe43"
 dependencies = [
  "atomic-waker",
  "bytes",
@@ -1158,9 +1157,9 @@ dependencies = [
 
 [[package]]
 name = "icu_collections"
-version = "2.2.0"
+version = "2.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c"
+checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513"
 dependencies = [
  "displaydoc",
  "potential_utf",
@@ -1172,9 +1171,9 @@ dependencies = [
 
 [[package]]
 name = "icu_locale_core"
-version = "2.2.0"
+version = "2.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29"
+checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb"
 dependencies = [
  "displaydoc",
  "litemap",
@@ -1185,9 +1184,9 @@ dependencies = [
 
 [[package]]
 name = "icu_normalizer"
-version = "2.2.0"
+version = "2.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4"
+checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f"
 dependencies = [
  "icu_collections",
  "icu_normalizer_data",
@@ -1199,16 +1198,17 @@ dependencies = [
 
 [[package]]
 name = "icu_normalizer_data"
-version = "2.2.0"
+version = "2.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38"
+checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0"
 
 [[package]]
 name = "icu_properties"
-version = "2.2.0"
+version = "2.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de"
+checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148"
 dependencies = [
+ "displaydoc",
  "icu_collections",
  "icu_locale_core",
  "icu_properties_data",
@@ -1219,15 +1219,15 @@ dependencies = [
 
 [[package]]
 name = "icu_properties_data"
-version = "2.2.0"
+version = "2.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14"
+checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa"
 
 [[package]]
 name = "icu_provider"
-version = "2.2.0"
+version = "2.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421"
+checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73"
 dependencies = [
  "displaydoc",
  "icu_locale_core",
@@ -1321,9 +1321,9 @@ dependencies = [
 
 [[package]]
 name = "indexmap"
-version = "2.14.0"
+version = "2.14.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
+checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855"
 dependencies = [
  "equivalent",
  "hashbrown 0.17.1",
@@ -1355,9 +1355,9 @@ dependencies = [
 
 [[package]]
 name = "ipnet"
-version = "2.12.0"
+version = "2.12.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2"
+checksum = "791930b43c0d5973160d90a8f3894509f2b273430f5c5c73b668636d0287c5c0"
 
 [[package]]
 name = "itertools"
@@ -1376,9 +1376,9 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
 
 [[package]]
 name = "js-sys"
-version = "0.3.103"
+version = "0.3.105"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102"
+checksum = "ce57d20d1ea864ce2ac172ab472d409214f4fd359f0b2a2775abdf522e2af99e"
 dependencies = [
  "cfg-if",
  "futures-util",
@@ -1435,7 +1435,7 @@ dependencies = [
  "multiaddr",
  "pin-project",
  "rw-stream-sink",
- "thiserror 2.0.19",
+ "thiserror 2.0.20",
 ]
 
 [[package]]
@@ -1467,9 +1467,9 @@ dependencies = [
  "libp2p-swarm",
  "quick-protobuf",
  "quick-protobuf-codec",
- "rand 0.8.7",
+ "rand 0.8.8",
  "rand_core 0.6.4",
- "thiserror 2.0.19",
+ "thiserror 2.0.20",
  "tracing",
  "web-time",
 ]
@@ -1502,9 +1502,9 @@ dependencies = [
  "parking_lot",
  "pin-project",
  "quick-protobuf",
- "rand 0.8.7",
+ "rand 0.8.8",
  "rw-stream-sink",
- "thiserror 2.0.19",
+ "thiserror 2.0.20",
  "tracing",
  "unsigned-varint 0.8.0",
  "web-time",
@@ -1527,7 +1527,7 @@ dependencies = [
  "libp2p-swarm",
  "quick-protobuf",
  "quick-protobuf-codec",
- "thiserror 2.0.19",
+ "thiserror 2.0.20",
  "tracing",
  "web-time",
 ]
@@ -1565,7 +1565,7 @@ dependencies = [
  "quick-protobuf",
  "quick-protobuf-codec",
  "smallvec",
- "thiserror 2.0.19",
+ "thiserror 2.0.20",
  "tracing",
 ]
 
@@ -1580,9 +1580,9 @@ dependencies = [
  "hkdf",
  "multihash",
  "prost",
- "rand 0.8.7",
+ "rand 0.8.8",
  "sha2",
- "thiserror 2.0.19",
+ "thiserror 2.0.20",
  "tracing",
  "zeroize",
 ]
@@ -1605,10 +1605,10 @@ dependencies = [
  "libp2p-swarm",
  "quick-protobuf",
  "quick-protobuf-codec",
- "rand 0.8.7",
+ "rand 0.8.8",
  "sha2",
  "smallvec",
- "thiserror 2.0.19",
+ "thiserror 2.0.20",
  "tracing",
  "uint",
  "web-time",
@@ -1626,7 +1626,7 @@ dependencies = [
  "libp2p-core",
  "libp2p-identity",
  "libp2p-swarm",
- "rand 0.8.7",
+ "rand 0.8.8",
  "smallvec",
  "socket2 0.5.10",
  "tokio",
@@ -1666,10 +1666,10 @@ dependencies = [
  "multiaddr",
  "multihash",
  "quick-protobuf",
- "rand 0.8.7",
+ "rand 0.8.8",
  "snow",
  "static_assertions",
- "thiserror 2.0.19",
+ "thiserror 2.0.20",
  "tracing",
  "x25519-dalek",
  "zeroize",
@@ -1689,11 +1689,11 @@ dependencies = [
  "libp2p-tls",
  "quinn",
  "quinn-proto",
- "rand 0.8.7",
+ "rand 0.8.8",
  "ring",
  "rustls",
  "socket2 0.5.10",
- "thiserror 2.0.19",
+ "thiserror 2.0.20",
  "tokio",
  "tracing",
 ]
@@ -1715,9 +1715,9 @@ dependencies = [
  "libp2p-swarm",
  "quick-protobuf",
  "quick-protobuf-codec",
- "rand 0.8.7",
+ "rand 0.8.8",
  "static_assertions",
- "thiserror 2.0.19",
+ "thiserror 2.0.20",
  "tracing",
  "web-time",
 ]
@@ -1734,7 +1734,7 @@ dependencies = [
  "libp2p-core",
  "libp2p-identity",
  "libp2p-swarm",
- "rand 0.8.7",
+ "rand 0.8.8",
  "smallvec",
  "tracing",
 ]
@@ -1749,7 +1749,7 @@ dependencies = [
  "libp2p-core",
  "libp2p-identity",
  "libp2p-swarm",
- "rand 0.8.7",
+ "rand 0.8.8",
  "tracing",
 ]
 
@@ -1768,7 +1768,7 @@ dependencies = [
  "libp2p-identity",
  "libp2p-swarm-derive",
  "multistream-select",
- "rand 0.8.7",
+ "rand 0.8.8",
  "smallvec",
  "tokio",
  "tracing",
@@ -1816,7 +1816,7 @@ dependencies = [
  "ring",
  "rustls",
  "rustls-webpki",
- "thiserror 2.0.19",
+ "thiserror 2.0.20",
  "x509-parser",
  "yasna",
 ]
@@ -1845,7 +1845,7 @@ dependencies = [
  "either",
  "futures",
  "libp2p-core",
- "thiserror 2.0.19",
+ "thiserror 2.0.20",
  "tracing",
  "yamux 0.12.1",
  "yamux 0.13.10",
@@ -1859,9 +1859,9 @@ checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
 
 [[package]]
 name = "litemap"
-version = "0.8.2"
+version = "0.8.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0"
+checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae"
 
 [[package]]
 name = "lock_api"
@@ -1874,9 +1874,9 @@ dependencies = [
 
 [[package]]
 name = "log"
-version = "0.4.33"
+version = "0.4.34"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
+checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
 
 [[package]]
 name = "lru-slab"
@@ -1886,9 +1886,9 @@ checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
 
 [[package]]
 name = "match-lookup"
-version = "0.1.2"
+version = "0.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "757aee279b8bdbb9f9e676796fd459e4207a1f986e87886700abf589f5abf771"
+checksum = "549e39695cc0b640f3cb378053832db3d2133422d49e8dcae5c866a2aaf1f730"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1909,9 +1909,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
 
 [[package]]
 name = "mio"
-version = "1.2.2"
+version = "1.2.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427"
+checksum = "4b18443e9c262bfe8fa82f51666e2642c53393f7e5c27b3e1aeab922cff5b9d8"
 dependencies = [
  "libc",
  "wasi",
@@ -1920,9 +1920,9 @@ dependencies = [
 
 [[package]]
 name = "moka"
-version = "0.12.15"
+version = "0.12.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "957228ad12042ee839f93c8f257b62b4c0ab5eaae1d4fa60de53b27c9d7c5046"
+checksum = "4293f18e7567a1caf3c584855554377025c65e0aa445344d04171f5ad63d19b9"
 dependencies = [
  "crossbeam-channel",
  "crossbeam-epoch",
@@ -1992,9 +1992,9 @@ dependencies = [
 
 [[package]]
 name = "netlink-packet-core"
-version = "0.8.1"
+version = "0.8.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3463cbb78394cb0141e2c926b93fc2197e473394b761986eca3b9da2c63ae0f4"
+checksum = "b897d7bd4f0af82e68d40d0344cf37e97f9c97ddf74a098de3e4da05e96ca395"
 dependencies = [
  "paste",
 ]
@@ -2013,9 +2013,9 @@ dependencies = [
 
 [[package]]
 name = "netlink-proto"
-version = "0.12.1"
+version = "0.12.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e6f7398dddf5f152d2a91a2921a134c6097056e292c0d4b9906007855e7cece6"
+checksum = "93af8261786086024cd5e96e0a991dd65ced07bbf7c233a487bbc96b971d5539"
 dependencies = [
  "bytes",
  "futures-channel",
@@ -2023,7 +2023,7 @@ dependencies = [
  "log",
  "netlink-packet-core",
  "netlink-sys",
- "thiserror 2.0.19",
+ "thiserror 2.0.20",
 ]
 
 [[package]]
@@ -2085,9 +2085,9 @@ checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441"
 
 [[package]]
 name = "num-integer"
-version = "0.1.46"
+version = "0.1.47"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
+checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b"
 dependencies = [
  "num-traits",
 ]
@@ -2252,9 +2252,9 @@ dependencies = [
 
 [[package]]
 name = "portable-atomic"
-version = "1.14.0"
+version = "1.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3d20d5497ef88037a52ff98267d066e7f11fcc5e99bbfbd58a42336193aacec3"
+checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85"
 
 [[package]]
 name = "postcard"
@@ -2271,9 +2271,9 @@ dependencies = [
 
 [[package]]
 name = "potential_utf"
-version = "0.1.5"
+version = "0.1.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564"
+checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661"
 dependencies = [
  "zerovec",
 ]
@@ -2385,7 +2385,7 @@ dependencies = [
  "rustc-hash",
  "rustls",
  "socket2 0.6.5",
- "thiserror 2.0.19",
+ "thiserror 2.0.20",
  "tokio",
  "tracing",
  "web-time",
@@ -2393,9 +2393,9 @@ dependencies = [
 
 [[package]]
 name = "quinn-proto"
-version = "0.11.16"
+version = "0.11.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2f4bfc015262b9df63c8845072ce59068853ff5872180c2ce2f13038b970e560"
+checksum = "04759210543be93709136e28212294a659ef5001836ff4eab4d663e4529bba83"
 dependencies = [
  "bytes",
  "fastbloom",
@@ -2408,7 +2408,7 @@ dependencies = [
  "rustls",
  "rustls-pki-types",
  "slab",
- "thiserror 2.0.19",
+ "thiserror 2.0.20",
  "tinyvec",
  "tracing",
  "web-time",
@@ -2451,9 +2451,9 @@ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
 
 [[package]]
 name = "rand"
-version = "0.8.7"
+version = "0.8.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a"
+checksum = "e058c7de0b26af77780c769414d6257830bb240f3c38477dbc2c16e5f54d6d4c"
 dependencies = [
  "libc",
  "rand_chacha 0.3.1",
@@ -2476,7 +2476,7 @@ version = "0.10.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80"
 dependencies = [
- "chacha20 0.10.1",
+ "chacha20 0.10.2",
  "getrandom 0.4.3",
  "rand_core 0.10.1",
 ]
@@ -2633,9 +2633,9 @@ dependencies = [
 
 [[package]]
 name = "rustls"
-version = "0.23.42"
+version = "0.23.43"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3c54fcab019b409d04215d3a17cb438fd7fbf192ee61461f20f4fe18704bc138"
+checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06"
 dependencies = [
  "once_cell",
  "ring",
@@ -2657,9 +2657,9 @@ dependencies = [
 
 [[package]]
 name = "rustls-webpki"
-version = "0.103.13"
+version = "0.103.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e"
+checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2"
 dependencies = [
  "ring",
  "rustls-pki-types",
@@ -2731,7 +2731,7 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 3.0.3",
+ "syn 3.0.5",
 ]
 
 [[package]]
@@ -2806,9 +2806,9 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
 
 [[package]]
 name = "smallvec"
-version = "1.15.2"
+version = "1.16.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
+checksum = "b9be42f50aa861c555654aa3a37f52f4b1074bacf4e48fe0ef7fa584e80f1f0f"
 
 [[package]]
 name = "snow"
@@ -2897,9 +2897,9 @@ dependencies = [
 
 [[package]]
 name = "syn"
-version = "3.0.3"
+version = "3.0.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3"
+checksum = "12df2e0110f65b775f769bb17ef989067a1d931b2eb822bd4346631eeada89f9"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -2955,11 +2955,11 @@ dependencies = [
 
 [[package]]
 name = "thiserror"
-version = "2.0.19"
+version = "2.0.20"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9"
+checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f"
 dependencies = [
- "thiserror-impl 2.0.19",
+ "thiserror-impl 2.0.20",
 ]
 
 [[package]]
@@ -2975,20 +2975,20 @@ dependencies = [
 
 [[package]]
 name = "thiserror-impl"
-version = "2.0.19"
+version = "2.0.20"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd"
+checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 3.0.3",
+ "syn 3.0.5",
 ]
 
 [[package]]
 name = "time"
-version = "0.3.54"
+version = "0.3.55"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3e1d5e639ff6bab73cb6885cc7e7b1de96c3f32c68ec55f3952614bec1092244"
+checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134"
 dependencies = [
  "deranged",
  "num-conv",
@@ -3016,9 +3016,9 @@ dependencies = [
 
 [[package]]
 name = "tinystr"
-version = "0.8.3"
+version = "0.8.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d"
+checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643"
 dependencies = [
  "displaydoc",
  "zerovec",
@@ -3026,9 +3026,9 @@ dependencies = [
 
 [[package]]
 name = "tinyvec"
-version = "1.12.0"
+version = "1.13.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f"
+checksum = "4cf0ded5c4e56918d8f8a339e1bb67d038d3bc6d144ac407904015ba2e4cde9b"
 dependencies = [
  "tinyvec_macros",
 ]
@@ -3058,13 +3058,13 @@ dependencies = [
 
 [[package]]
 name = "tokio-macros"
-version = "2.7.1"
+version = "2.7.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6328af13490e73a9b4694030fafd93f8c8c6a9dede33e821c3fc63eddf8042ba"
+checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.119",
+ "syn 3.0.5",
 ]
 
 [[package]]
@@ -3175,9 +3175,9 @@ checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
 
 [[package]]
 name = "uint"
-version = "0.10.0"
+version = "0.10.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "909988d098b2f738727b161a106cfc7cab00c539c2687a8836f8e565976fb53e"
+checksum = "6f9227a75a5a540a464c832ad4a4195dbdbecd8787610a56262721fde6f04f90"
 dependencies = [
  "byteorder",
  "crunchy",
@@ -3256,9 +3256,9 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
 
 [[package]]
 name = "uuid"
-version = "1.24.0"
+version = "1.26.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bf3923a6f5c4c6382e0b653c4117f48d631ea17f38ed86e2a828e6f7412f5239"
+checksum = "b5772d71c9be8a8a6ac2117d949c5b224c1b72241bb611d9a3012edcf8af7812"
 dependencies = [
  "getrandom 0.4.3",
  "js-sys",
@@ -3297,9 +3297,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen"
-version = "0.2.126"
+version = "0.2.128"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4"
+checksum = "aecb87a33d3b0c5e3b7aa46336eaf486cffafbd281b195e4c8b80d50df2351bf"
 dependencies = [
  "cfg-if",
  "once_cell",
@@ -3310,9 +3310,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-macro"
-version = "0.2.126"
+version = "0.2.128"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1"
+checksum = "a690d511e3c1a8b3a55e33511e3c2c00c78415cd23650f32b808627f5696b9ed"
 dependencies = [
  "quote",
  "wasm-bindgen-macro-support",
@@ -3320,22 +3320,22 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-macro-support"
-version = "0.2.126"
+version = "0.2.128"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e"
+checksum = "411e4887f0071ef2d2164a9d5fdf2d20efbef78fccd3a78b0c10a1dc5295e48a"
 dependencies = [
  "bumpalo",
  "proc-macro2",
  "quote",
- "syn 2.0.119",
+ "syn 3.0.5",
  "wasm-bindgen-shared",
 ]
 
 [[package]]
 name = "wasm-bindgen-shared"
-version = "0.2.126"
+version = "0.2.128"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24"
+checksum = "81941cd78d0c92026c33e5e01312845a4cb1e9af3407f9134b100dd03144103e"
 dependencies = [
  "unicode-ident",
 ]
@@ -3605,9 +3605,9 @@ dependencies = [
 
 [[package]]
 name = "writeable"
-version = "0.6.3"
+version = "0.6.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4"
+checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc"
 
 [[package]]
 name = "x25519-dalek"
@@ -3634,15 +3634,15 @@ dependencies = [
  "nom",
  "oid-registry",
  "rusticata-macros",
- "thiserror 2.0.19",
+ "thiserror 2.0.20",
  "time",
 ]
 
 [[package]]
 name = "xml-rs"
-version = "0.8.28"
+version = "0.8.29"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f"
+checksum = "e450f9b2ed1dff33c94c12589a87338689467b9c4f5d8a5710bd09a847d2c8a7"
 
 [[package]]
 name = "xmltree"
@@ -3664,7 +3664,7 @@ dependencies = [
  "nohash-hasher",
  "parking_lot",
  "pin-project",
- "rand 0.8.7",
+ "rand 0.8.8",
  "static_assertions",
 ]
 
@@ -3718,18 +3718,18 @@ dependencies = [
 
 [[package]]
 name = "zerocopy"
-version = "0.8.55"
+version = "0.8.56"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b5a105cd7b140f6eeec8acff2ea38135d3cab283ada58540f629fe51e46696eb"
+checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb"
 dependencies = [
  "zerocopy-derive",
 ]
 
 [[package]]
 name = "zerocopy-derive"
-version = "0.8.55"
+version = "0.8.56"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0fe976fb70c78cd64cccfe3a6fc142244e8a77b70959b30faf9d0ac37ee228eb"
+checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -3779,9 +3779,9 @@ dependencies = [
 
 [[package]]
 name = "zerotrie"
-version = "0.2.4"
+version = "0.2.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf"
+checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f"
 dependencies = [
  "displaydoc",
  "yoke",
@@ -3790,9 +3790,9 @@ dependencies = [
 
 [[package]]
 name = "zerovec"
-version = "0.11.6"
+version = "0.11.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239"
+checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8"
 dependencies = [
  "yoke",
  "zerofrom",
@@ -3801,13 +3801,13 @@ dependencies = [
 
 [[package]]
 name = "zerovec-derive"
-version = "0.11.3"
+version = "0.11.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555"
+checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.119",
+ "syn 3.0.5",
 ]
 
 [[package]]
diff --git a/Cargo.toml b/Cargo.toml
index e09890f..5923963 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 # Cargo.toml raíz STANDALONE de card — front-door público.
 # GENERADO por scripts/actualizar-standalone.py desde el monorepo tawasuyu.git
-# (v0.2.0). No editar a mano: se regenera. Lo fundacional se consume por
+# (v0.3.0). No editar a mano: se regenera. Lo fundacional se consume por
 # git-dep al tag; sólo el código del dominio vive en este repo.
 [workspace]
 resolver = "2"
@@ -18,43 +18,88 @@ members = [
 # `x86_64-unknown-none` y `panic = "abort"`, incompatibles con los
 # perfiles globales. Los crates compartidos se referencian por `path`
 # cruzando la frontera.
+# `llimphi-wawa` está EXCLUIDA, no ausente por olvido. Enciende
+# `llimphi-text/cpu`, que es el backend EXCLUYENTE del `vello` que todo el resto
+# del workspace usa por default — y la unificación de features de cargo los
+# junta, disparando el `compile_error!` que llimphi-text tiene justo para
+# delatarlo. Vive en el mismo mundo que el kernel: se compila aparte, con su
+# propio lock. Sus tests se corren así:
+#   cargo test --manifest-path 02_ruway/llimphi/llimphi-wawa/Cargo.toml \
+#     --no-default-features --features std
+#
+# `llimphi-jaula` está excluida por lo MISMO, del otro lado: enciende
+# `llimphi-text/wire`, el tercer backend excluyente (W4). Es la cáscara de una
+# app dentro de la jaula WASM — el gemelo de `llimphi-wawa`—, y que las dos estén
+# afuera del workspace no es una molestia sino el diseño: son dos procesos en dos
+# lados de una frontera, y forzarlos a compartir un lock sería fingir lo
+# contrario. Sus tests:
+#   cargo test --manifest-path 02_ruway/llimphi/llimphi-jaula/Cargo.toml \
+#     --no-default-features --features std
+#
+# `llimphi-hybrid-android` está excluida **por contagio**: consume `llimphi-wawa`
+# y por lo tanto arrastra el mismo `llimphi-text/cpu`. Es la tercera cáscara de
+# la familia —kernel, jaula, teléfono— y cae del mismo lado de la frontera por
+# el mismo motivo. Se compila así:
+#   cargo check --manifest-path \
+#     02_ruway/llimphi/android/llimphi-hybrid-android/Cargo.toml
+# y se empaca con `02_ruway/llimphi/android/scripts/build-android.sh`.
 
 [workspace.package]
 version = "0.1.0"
 edition = "2021"
 rust-version = "1.80"
 license = "MIT"
-authors = ["Sergio <gerencia@jlsoltech.com>"]
+authors = ["Sergio <sergio@gioser.net>"]
 publish = false
 repository = "https://git.tawasuyu.net/tawasuyu/card"
 
 [workspace.dependencies]
 # === Configuración declarativa (vocabulario de esquemas) ===
-allichay = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-marca = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
+allichay = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+allichay-plano = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+marca = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+# Escala única de `nice` de la sesión (compositor · marco · app · segundo plano
+# · lote) + las tres formas de aplicarla (propia / pid vivo / pre_exec).
+prioridad = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+ron-ralo = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
 # Telemetría local de desarrollo (stderr+panics+tracing → $XDG_STATE_HOME/tawasuyu/<dominio>/<bin>.log)
-bitacora = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
+bitacora = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
 # default = [] (núcleo liviano sin vello); `bake` es opt-in. Declaramos
 # default-features = false aquí para que el `default-features = false` de los
 # consumidores (splash/greeter/compositor) se honre y no quede el warning.
-mirada-fondo = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-mirada-teclado-core = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-mirada-teclado-widget = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
+mirada-fondo = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+mirada-teclado-core = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+mirada-teclado-widget = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
 # === Registro de apps / menú global ===
-app-bus = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-app-iconset = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
+app-bus = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+app-iconset = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
 # === Centro de eventos (notificaciones + capturas + clipboard + …) ===
-willay-core = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-willay-checkpoint = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-willay-hilo = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-willay-store = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-willay-emit = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-rag-motor = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-churay-core = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-churay-welcome = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-churay-welcome-runner = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
+willay-core = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+willay-checkpoint = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+willay-hilo = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+willay-store = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+socket-local = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+willay-emit = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+sandokan-seguridad-core = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+sandokan-salud-core = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+sandokan-monitor-core = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+# El censo de red de la máquina (netstat completo: socket → proceso → unidad) +
+# huella de red + drift de puertos + contabilidad por unidad. Ver `shared/PLAN-RED.md`.
+sandokan-red-core = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+# La baliza de LAN: el descubridor por multicast, con el payload opaco. Promovido
+# de `uya-app::lan`, que es el de los tres del arbol que resuelve los tres modos
+# de fallo reales. Ver `shared/PLAN-RED.md` §F1.
+baliza = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+matilda-apply = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+matilda-plan = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+matilda-core = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+matilda-discover = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+rag-motor = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+churay-core = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+churay-welcome = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+churay-welcome-runner = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
 # === Puente de drag-and-drop compositor → app (suple winit en Wayland) ===
-drop-bridge = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
+drop-bridge = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
 # === Serialización ===
 serde = { version = "1", features = ["derive"] }
 serde_json = "1"
@@ -80,6 +125,10 @@ futures = "0.3"
 tracing = "0.1"
 tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] }
 
+# Sonda de librerías del sistema en build.rs. La usa `auth-huellita` para
+# detectar `libfprint` sin volverla una dependencia dura del workspace.
+pkg-config = "0.3"
+
 # === Linux primitives (arje) ===
 nix = { version = "0.29", features = ["signal", "process", "sched", "mount", "fs", "socket", "net", "user", "reboot"] }
 libc = "0.2"
@@ -95,9 +144,16 @@ sha2 = "0.10"
 blake3 = "1.5"
 ed25519-dalek = "2"
 aes-gcm = "0.10"
+chacha20 = "0.9"
 chacha20poly1305 = "0.10"
 argon2 = "0.5"
 rand = "0.8"
+# TOTP (RFC 6238) del segundo factor de nakui: HMAC-SHA1 es lo que hablan
+# Google Authenticator, Aegis y los tokens de hardware — no es elección
+# nuestra, es el formato del ecosistema. base32 para el secreto del QR.
+hmac = "0.12"
+sha1 = "0.10"
+data-encoding = "2"
 
 # === WASM (arje) ===
 # wasmi 1.0: unifica la versión con wawa (su kernel ya corre 1.0), para
@@ -130,6 +186,10 @@ unrar = "0.5"
 
 # === HTTP server (iniy-server) ===
 axum = "0.7"
+# WebSocket pelado (sin framework HTTP): lo usa `shuma-ide-host`, que sirve el
+# protocolo de integración de IDE de Claude Code en loopback. Ya estaba en el
+# lock por transitividad; acá se declara para poder usarlo directo.
+tokio-tungstenite = "0.24"
 tower = "0.5"
 
 # === ANN sobre embeddings (iniy nli --ann) ===
@@ -157,12 +217,30 @@ midly = "0.5"
 # zwlr_data_control_manager_v1 (que mirada expone) y cae a X11 si no hay Wayland.
 arboard = { version = "3", features = ["wayland-data-control"] }
 ropey = "1.6"
-tree-sitter = "0.24"
-tree-sitter-rust = "0.23"
+# El runtime lee gramáticas por ABI, y ahí está el acople: 0.24 lee hasta la 14,
+# 0.25 lee 13–15. Se subió el 2026-09-06 porque `tree-sitter-rust` 0.23.3 (ABI 14)
+# lee `&raw` como el arranque de una referencia cruda, así que **una variable
+# llamada `raw` prestada con `&`** rompe el archivo entero para la gramática:
+# 69 de 6.428 archivos de este repo (1,1 %). La 0.24.2 lo arregla, pero es ABI 15
+# y por eso arrastra al runtime. Las otras gramáticas siguen en ABI 14 y cargan
+# igual — 0.25 es compatible hacia atrás.
+tree-sitter = "0.25"
+tree-sitter-rust = "0.24"
 tree-sitter-python = "0.23"
 tree-sitter-typescript = "0.23"
 tree-sitter-javascript = "0.23"
 tree-sitter-go = "0.23"
+# C: el quinto dialecto que minga versiona por AST (el kernel y todo lo ajeno
+# que la suite absorbe está en C).
+tree-sitter-c = "0.23"
+# Dart entra por zorge (SDD-SERVIDOR-AJENO §A.1, RA-2): el otro proyecto es
+# Flutter, y Dart 3 es lo más parecido a Rust que hay del otro lado —tipado
+# estático, `sealed` con `switch` exhaustivo, y `_nombre` privado de verdad—.
+# El fork `orchard` y no `tree-sitter-dart` a secas. El motivo original era el
+# ABI: `tree-sitter-dart` se generó con la 15 y el runtime del workspace leía
+# hasta la 14. Con el runtime en 0.25 ese motivo **ya no aplica**; el fork se
+# conserva porque es lo que está probado, no porque haga falta.
+tree-sitter-dart-orchard = "0.3"
 
 # === FS notify ===
 notify = "6.1"
@@ -208,164 +286,226 @@ tempfile = "3"
 
 # === Llimphi (motor gráfico soberano) ===
 # wgpu sobre Vulkan/Metal/DX12, winit para ventana en dev Linux.
-# raw-window-handle 0.6 alinea winit 0.30 con wgpu 27.
-# vello 0.7 = rasterizador vectorial sobre wgpu 27 (renderer GPU "wgpu" + opt-in "hybrid" CPU+GPU).
-# taffy 0.9 = motor Flexbox/Grid puro Rust (ya pulled por transitivos, lo alineamos).
+# raw-window-handle 0.6 alinea winit 0.30 con wgpu 29.
+# vello 0.9 = rasterizador vectorial sobre wgpu 29 (renderer GPU "wgpu" + opt-in "hybrid" CPU+GPU).
+#
+# **El 27→29 no fue por gusto** (2026-07-28): `vello_hybrid 0.0.9` —el ÚNICO
+# rasterizador que el driver Mali del teléfono digiere, ver
+# `02_ruway/llimphi/android/DIAGNOSTICO.md`— vive en wgpu 29, y dos versiones de
+# wgpu no comparten un `Device`. La migración salió barata porque `vello 0.9`
+# usa el mismo peniko 0.6.1 y no movió su API de `Scene`: el bump de vello dio
+# CERO errores; todos los que hubo fueron deriva de wgpu, y concentrada en los
+# cuatro crates que tienen código wgpu propio.
+# taffy 0.12 = motor Flexbox/Grid puro Rust (0.9→0.12 en 2026-07-27: arregla el percent de ítem de grid en track sizing).
 # parley 0.6 = shaping/layout de texto compatible con peniko 0.6 (que vello 0.7 expone).
-wgpu = "27"
+wgpu = "29"
+# El compilador de shaders que wgpu lleva adentro. Se declara acá para que los
+# tests que validan WGSL headless (llimphi-3d, supay-render-llimphi) usen la
+# MISMA versión que después compila esos shaders en la GPU. Con dos versiones,
+# un test puede dar verde sobre un shader que el runtime rechaza — o al revés.
+naga = "29"
 winit = "0.30"
 raw-window-handle = "0.6"
 pollster = "0.4"
-vello = "0.7"
+# `default-features = false` acá arriba y no en cada consumidor: el default de
+# vello es ["wgpu","wgpu_default"], y `llimphi-text`/`llimphi-compositor` sólo
+# quieren sus TIPOS (Scene, peniko), no el motor de GPU. Declararlo abajo no
+# alcanza — cargo IGNORA `default-features` en un dep heredado si la raíz no lo
+# fijó, y lo avisa con un warning fácil de pasar por alto. Quien necesita el
+# motor lo pide por feature (ver llimphi-raster).
+vello = { version = "0.9", default-features = false }
 # Renderer hybrid CPU+GPU sin compute shaders (mejor compat WebGL2/Adreno viejas).
 # Opt-in; el renderer "wgpu" sigue siendo el default.
 vello_hybrid = "0.0.9"
 # foreign-lottie = fork vendorizado de velato 0.9 (Lottie → vello::Scene),
 # completado para no paniquear ante features no soportadas. Lo consume
 # llimphi-lottie. Ver shared/foreign-lottie.
-foreign-lottie = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
+foreign-lottie = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
 # Árbol de accesibilidad para lectores de pantalla (NVDA/VoiceOver/Orca/TalkBack).
 # Lo consume el runtime de Llimphi (iter 2/3 del plan AccessKit); el modelo
 # `SemanticsSpec` del compositor es independiente de estas crates.
 accesskit = "0.24"
 accesskit_winit = "0.33"
-taffy = "0.9"
+taffy = "0.12"
 # parley = shaping completo (bidi, ligatures, fallback CJK/emoji vía fontique, line break).
 parley = "0.6"
 # Bucle Elm (input→update→view→layout→raster→present). Lo consumen las apps.
-llimphi-ui = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
+# `config-wawa`: el sentido de la rueda (ratón / touchpad, por separado) sale
+# del bus `wawa-config` y se fija al arrancar cada app, con watcher para el
+# cambio en caliente. Va acá y no app por app a propósito: es una preferencia
+# del HUMANO, no de cada app, y ninguna app del monorepo debería poder
+# desobedecerla por olvido. Ver `llimphi-ui/src/rueda.rs`.
+llimphi-ui = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+# El compositor DIRECTO, para que el catálogo de widgets cuelgue de él y no del
+# shell de escritorio (W4.6). Un widget que dependa de `llimphi-ui` arrastra
+# winit + accesskit + arboard + vello, y con eso queda fuera del alcance de una
+# app enjaulada; colgando de acá sirve igual en Linux, en el kernel y en la jaula.
+#
+# `default-features = false` VA ACÁ Y NO EN CADA WIDGET. Es la trampa recurrente
+# de cargo: apagar los defaults desde un miembro NO HACE NADA si el workspace
+# declaró la dep sin ellos (cargo lo avisa y lo ignora). Ya pasó con taffy, vello
+# y peniko. Sin esto, cada widget encendería el backend `vello` del compositor y
+# volvería a quedar fuera del alcance de una app enjaulada — que es justamente lo
+# que W4.6 vino a arreglar. Quien ensambla la app elige el backend: `llimphi-ui`
+# en Linux, `llimphi-jaula` en la jaula, `llimphi-wawa` en el kernel.
+llimphi-compositor = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
 # Tier 3 — apps WASM con UI Llimphi real. El guest pinta un WireNode (IR
 # serializable), el host (runner) lo materializa en View<Msg> y rebota los
 # eventos al update del guest. SDK = lado guest, runner = lado host.
-llimphi-wire-view = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-wasm-app-sdk = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-wasm-runner = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
+llimphi-wire-view = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-wire-escena = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-wasm-app-sdk = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-wasm-runner = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
 # Núcleo puro de distribución (CAS + verificación + resolve), SIN runner/GPU.
-llimphi-wasm-core = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
+llimphi-wasm-core = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
 # Cara con baterías: re-exporta core + puente al runner para correr la app.
-llimphi-wasm-dist = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-wasm-open = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-wasm-registry = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-wasm-wasi = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
+llimphi-wasm-dist = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-wasm-open = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-wasm-registry = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-wasm-wasi = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
 # Transporte P2P del bytecode por hash sobre BrahmanNet (card-net).
-llimphi-wasm-net = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-3d = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
+llimphi-wasm-net = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-3d = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
 # Runner wlr-layer-shell: corre un `App` de Llimphi como barra anclada a un
 # borde (no como ventana), reusando la plumbing sctk+wgpu que pata probó.
-llimphi-layer = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
+llimphi-layer = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
 # Paleta semántica compartida por las apps y los widgets.
-llimphi-theme = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
+llimphi-theme = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
 # Tweens y helpers de animación sobre el bucle Elm.
-llimphi-motion = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
+llimphi-motion = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
 # Máquina de estados de animación (estilo Rive) clip-agnóstica. Núcleo puro;
 # el render lo cablea el consumidor (llimphi-lottie). Escalón sobre el playback.
-llimphi-anim = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
+llimphi-anim = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
 # Studio de animación «rive»: además del editor (bin), expone como lib los
 # documentos serializables (Doc + RigDoc + Project) que carga mirada-fondo.
-llimphi-anim-studio = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
+llimphi-anim-studio = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
 # Render de mallas deformables (skel::Mesh) a vello Scene: vectorial y texturizada.
-llimphi-mesh = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
+llimphi-mesh = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
 # Iconos vectoriales (BezPath en grid 24×24) compartidos por todas las apps.
-llimphi-icons = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
+llimphi-icons = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
 # Puente fino vello_svg → Llimphi para SVG arbitrario (íconos .desktop, logos).
-llimphi-svg = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
+llimphi-svg = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
 # Puente fino velato (Lottie) → Llimphi para animación vectorial autorada (.json).
-llimphi-lottie = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-image = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
+llimphi-lottie = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-image = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
 # Decodificador de protocolos de gráficos de terminal (kitty/sixel) → RGBA.
-llimphi-term-graphics = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
+llimphi-term-graphics = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
 # Widgets reusables sobre llimphi-ui — uno por crate.
-llimphi-widget-app-header = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-banner = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-button = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-rive-button = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-card = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-clipboard = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-context-menu = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-edit-menu = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-menubar = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-list = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-grid = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-table = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-color-picker = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-slider = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-scroll = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-lazy-list = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-splitter = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-stat-card = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-tabs = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-module-allichay = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-module-command-palette = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-module-diff-viewer = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-module-fif = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-module-file-picker = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-module-bookmarks = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-module-mini-map = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-module-shuma-term = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-module-symbol-outline = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-plugin-host = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-theme-switcher = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-text-area = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-text-editor-core = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-text-editor = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-text-editor-lsp = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-text-input = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-tiled = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-nodegraph = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-tree = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-grafo-nav = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-llimphi-widget-navigator = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-detail-table = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-select = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-terminal = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
+llimphi-widget-app-header = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-banner = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-button = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-rive-button = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-card = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-clipboard = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-context-menu = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-edit-menu = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-menubar = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-list = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-grid = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-table = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-color-picker = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-slider = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-app-icon = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-scroll = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-lazy-list = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-splitter = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-stat-card = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-tabs = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-module-allichay = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-module-command-palette = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-module-diff-viewer = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-module-fif = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-module-file-picker = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-module-bookmarks = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-module-mini-map = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-module-shuma-term = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-module-symbol-outline = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-plugin-host = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-theme-switcher = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-text-area = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-text-editor-core = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-text-editor = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-text-editor-lsp = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-text-input = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-tiled = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-nodegraph = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-tree = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+grafo-nav = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+# La lengua de expresiones compartida (PLAN-CRUCES §A5). `default-features =
+# false` acá NO: los consumidores Linux (maskay, pizarra, yupay, krama) la
+# quieren con `std`; el kernel y las jaulas la piden sin default desde su propio
+# manifiesto, como hacen con `format` y `dataflow`.
+krama-expr = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+krama-core = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+krama-store = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+krama-app-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+llimphi-widget-navigator = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-detail-table = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-select = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+# `gpu` explícito: el camino GPU-directo del modo grilla pasó a ser una feature
+# (para que el terminal pueda salir del proceso), y este `default-features =
+# false` la apagaba para TODOS los que lo toman por el workspace — dejando a
+# `shuma-module-shell` sin `CellPipeline`. Pedirla acá devuelve exactamente el
+# comportamiento de antes; quien la quiera apagar lo hace en su manifiesto.
+llimphi-widget-terminal = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
 # Sello vectorial wawa (rombo + W implícita + Merkle Core).
-llimphi-widget-wawa-mark = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
+llimphi-widget-wawa-mark = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
 # Widgets de elegancia transversal (tooltip, spinner, progress, toast,
 # modal, empty, status-bar, shortcuts-help, splash).
-llimphi-widget-tooltip = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-spinner = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-progress = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-toast = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-modal = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-empty = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-status-bar = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-shortcuts-help = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-timeline = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-transport = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-waveform = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-splash = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
+llimphi-widget-tooltip = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-spinner = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-progress = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-toast = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-modal = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-empty = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-status-bar = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-shortcuts-help = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-timeline = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-transport = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-waveform = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-splash = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
 # Controles de formulario y signaling (switch, segmented, breadcrumb,
 # badge, avatar, skeleton, field).
-llimphi-widget-switch = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-segmented = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-rag-sidebar = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-dock-rail = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-toolbar = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-breadcrumb = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-badge = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-avatar = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-skeleton = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-field = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
+llimphi-widget-switch = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-segmented = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-rag-sidebar = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-dock-rail = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-toolbar = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-breadcrumb = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-badge = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-avatar = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-skeleton = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-field = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
 # Firma visual transversal (gradient sutil + hairline accent).
-llimphi-widget-panel = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-panes = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
+# `default-features = false` acá y no en `card`: los defaults de un widget sólo
+# se pueden apagar desde la declaración del WORKSPACE (la trampa recurrente de
+# cargo). Sin esto, `card` arrastraría a `panel` con backend `vello` y volvería a
+# quedar fuera de una jaula, aunque `card` pidiera `wire`.
+llimphi-widget-panel = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-panes = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
 # Widgets Flutter-like — composición sobre primitivas Tier 1/3 ya
 # expuestas (sombra, gradient, animated, ripple).
-llimphi-widget-chip = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-fab = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-wrap = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-range-slider = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-calendar = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-fitted-box = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-carousel = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-rating = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-gauge = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-scaffold = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-hero = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-router = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-workspace = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
+llimphi-widget-chip = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-fab = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-fugaces = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-wrap = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-range-slider = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-calendar = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-datetime-picker = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-form = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-accordion = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-stepper = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-fitted-box = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-carousel = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-rating = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-gauge = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-seven-segment = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-scaffold = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-hero = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-router = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-workspace = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
 # Abstracción Selector — host (paths) + wawa (khipus).
-llimphi-module-selector = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
+llimphi-module-selector = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
 
 # === Filesystem helpers ===
 directories = "5"
@@ -424,92 +564,96 @@ ttf-parser = "0.25"
 # ============================================================
 # Intra-workspace deps de nahual (referenciadas por workspace = true)
 # ============================================================
-nahual-text-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-nahual-image-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-nahual-pdf-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-nahual-cbz-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-nahual-thumb-core = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-nahual-gallery-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-nahual-video-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-nahual-card-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-nahual-audio-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-media-module = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-tullpu-module = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-tullpu-core = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-tullpu-icon-core = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-tullpu-icon-cli = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-tullpu-icon-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-tullpu-icon-llm = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-foreign-svg = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-foreign-pdf = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-tullpu-render = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-tullpu-render-gpu = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-tullpu-ops = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-tullpu-paint = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-nahual-tree-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-nahual-hex-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-nahual-svg-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-nahual-sheet-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-nahual-dbf-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-nahual-table-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-nahual-markdown-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-nahual-pluma-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-nahual-cotejo-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-nahual-deck-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-nahual-archive-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-nahual-font-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-nahual-map-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-nahual-geo-core = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-nahual-geo-voxel = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-nahual-iconos = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-nahual-viewer-core = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-nahual-file-explorer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
+nahual-text-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+nahual-image-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+nahual-pdf-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+nahual-cbz-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+nahual-thumb-core = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+nahual-vistazo-core = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+nahual-gallery-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+nahual-video-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+nahual-card-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+nahual-audio-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+media-module = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+tullpu-module = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+tullpu-core = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+tullpu-icon-core = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+tullpu-icon-cli = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+tullpu-icon-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+tullpu-icon-llm = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+foreign-svg = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+foreign-pdf = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+tullpu-render = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+tullpu-tiles = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+tullpu-render-gpu = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+tullpu-ops = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+tullpu-paint = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+nahual-tree-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+nahual-hex-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+nahual-svg-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+nahual-sheet-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+nahual-dbf-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+nahual-table-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+nahual-markdown-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+nahual-pluma-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+nahual-cotejo-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+nahual-deck-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+nahual-archive-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+nahual-font-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+nahual-map-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+nahual-geo-core = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+nahual-geo-voxel = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+nahual-iconos = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+nahual-viewer-core = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+nahual-file-explorer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
 
 # ============================================================
 # Intra-workspace deps de pineal (módulo de gráficos)
 # ============================================================
-pineal-core = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-pineal-render = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-pineal-cartesian = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-pineal-stream = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-pineal-mesh = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-pineal-financial = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-pineal-polar = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-pineal-heatmap = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-pineal-treemap = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-pineal-flow = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-pineal-phosphor = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-pineal-export = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-pineal-hexbin = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-pineal-contour = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-pineal-bars = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-pineal = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
+pineal-core = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+pineal-render = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+pineal-cartesian = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+pineal-stream = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+pineal-mesh = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+pineal-financial = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+pineal-polar = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+pineal-heatmap = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+pineal-treemap = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+pineal-flow = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+pineal-phosphor = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+pineal-export = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+pineal-hexbin = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+pineal-contour = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+pineal-bars = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+pineal-quiver = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+pineal-yupana = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+pineal = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
 
 # ============================================================
 # Intra-workspace deps de iniy (laboratorio semántico de creencias)
 # ============================================================
-iniy-core = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-iniy-evidencia = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-iniy-derive = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-iniy-emisores = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-iniy-cache = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-iniy-daemon = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-iniy-ingest = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-iniy-extract = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-iniy-nli = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-iniy-nli-llm = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-iniy-graph = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-iniy-store = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
+iniy-core = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+iniy-evidencia = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+iniy-derive = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+iniy-emisores = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+iniy-cache = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+iniy-daemon = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+iniy-ingest = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+iniy-extract = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+iniy-nli = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+iniy-nli-llm = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+iniy-graph = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+iniy-store = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
 # la física de decaimiento de khipu la importa iniy-derive (SDD iniy §3)
-khipu-gravity = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
+khipu-gravity = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
 
 # === auto: declarados por crates internos faltantes ===
-cosmos-coords = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-cosmos-cities = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-cosmos-core = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-cosmos-ephemeris = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-cosmos-time = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-cosmos-wcs = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
+cosmos-coords = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+cosmos-cities = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+cosmos-core = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+cosmos-ephemeris = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+cosmos-time = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+cosmos-wcs = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
 
 # === auto: externas de eternal ===
 celestial-eop-data = { version = "0.1"}
@@ -531,6 +675,18 @@ memmap2 = "0.9"
 wayland-client = "0.31"
 wayland-protocols-wlr = { version = "0.3", features = ["client"] }
 wayland-protocols-misc = { version = "0.3", features = ["client"] }
+# PipeWire (bindings a libpipewire-0.3, MIT) — el transporte de video que
+# `xdg-desktop-portal` exige para compartir pantalla. Lo usa
+# `mirada-screencast`; no entra al compositor (ver su LEEME: el productor es
+# un cliente wlr-screencopy, no el compositor mismo).
+#
+# `v0_3_34` es el piso que habilita `pw_stream_trigger_process` — sin él un
+# nodo DRIVER no se puede disparar y el consumidor recibe CERO cuadros, sin
+# error. Se pide el mínimo que hace falta, no la última: la feature declara
+# contra qué libpipewire se compila, y pedir de más rompería en una distro con
+# la biblioteca más vieja.
+pipewire = { version = "0.10", features = ["v0_3_34"] }
+libspa = "0.10"
 mockito = "1.0"
 ndarray = "0.15"
 num-traits = "0.2"
@@ -541,7 +697,27 @@ proptest = "1.4"
 quick-xml = "0.31"
 rayon = "1.8"
 regex = "1.11"
-reqwest = "0.12"
+# TLS por **rustls**, no por el `default-tls` de reqwest (que es `native-tls`
+# y arrastra `openssl-sys`). Dos razones, y la segunda es la que muerde:
+#  1. `openssl-sys` NO cross-compila a `aarch64-linux-android` — su build
+#     script no encuentra la libreria del NDK. Con el default puesto, toda
+#     app que toque HTTP queda fuera del porte a Android (khipu fue la
+#     primera en chocar).
+#  2. Seis de los doce consumidores YA pedian `rustls-tls` a mano. Como las
+#     features de cargo son aditivas, eso no apagaba `default-tls`: estaban
+#     linkeando **las dos** pilas de TLS creyendo que usaban una. Esto hace
+#     verdadero lo que esos crates ya decian.
+# El resto de los defaults de reqwest se conservan uno por uno.
+# ⚠️ Cambio de comportamiento a nombrar: rustls valida contra las raices
+# empaquetadas (webpki-roots), no contra el almacen de certificados del SO.
+# Para las APIs publicas que consume la suite da igual; un servidor con CA
+# propia habria que agregarlo explicitamente.
+reqwest = { version = "0.12", default-features = false, features = [
+    "rustls-tls",
+    "charset",
+    "http2",
+    "macos-system-configuration",
+] }
 tiff = "0.11"
 wide = "0.7"
 wiremock = "0.6"
@@ -563,7 +739,12 @@ markup5ever = "0.39"
 markup5ever_rcdom = "0.39"
 cssparser = "0.35"
 url = "2"
-ureq = { version = "2", default-features = false, features = ["tls"] }
+# `gzip`: sin ella ureq ni anuncia `Accept-Encoding` ni decodifica la respuesta.
+# python.org (detrás de Fastly) devuelve `content-encoding: gzip` AUNQUE no se lo
+# pidan, y el cuerpo comprimido se decodificaba como UTF-8: 3.911 caracteres
+# U+FFFD y una página de 16 cajas en vez de 1.489. Además baja lo que se
+# descarga en todo el resto.
+ureq = { version = "2", default-features = false, features = ["tls", "gzip"] }
 # Charset de la web real: no todo es UTF-8 (google.com sirve ISO-8859-1).
 # Ya venía transitivo por el stack de Servo — aquí se hace explícito.
 encoding_rs = "0.8"
@@ -617,7 +798,7 @@ matroska-demuxer = "0.7"
 # `panic = "unwind"` se mantiene a propósito: mirada-plugin-host aísla plugins
 # con `catch_unwind`, así que `abort` rompería el sandbox.
 # === Crates del monorepo consumidos por git-dep (pin del tag) ===
-format = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
+format = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
 
 [profile.dist]
 inherits = "release"
diff --git a/shared/card/card-net/src/lib.rs b/shared/card/card-net/src/lib.rs
index 6918977..04b741f 100644
--- a/shared/card/card-net/src/lib.rs
+++ b/shared/card/card-net/src/lib.rs
@@ -48,7 +48,9 @@ use std::time::Duration;
 use futures::StreamExt;
 use libp2p::{
     autonat, dcutr, identify, identity, kad, mdns, noise, relay,
-    swarm::{behaviour::toggle::Toggle, NetworkBehaviour, SwarmEvent},
+    core::transport::ListenerId,
+    TransportError,
+    swarm::{behaviour::toggle::Toggle, ConnectionId, NetworkBehaviour, SwarmEvent},
     tcp, yamux, Swarm, SwarmBuilder,
 };
 use libp2p_allow_block_list::{self as allow_block_list, BlockedPeers};
@@ -108,17 +110,32 @@ struct BrahmanBehaviour {
 pub enum NodeError {
     #[error("transport build failed: {0}")]
     Build(String),
+    /// Escuchar no salió. Cubre los dos modos: el rechazo inmediato (una
+    /// dirección que el transporte no sabe atender, un puerto privilegiado, una
+    /// interfaz que no existe) y el que llega después, si un listener ya abierto
+    /// se muere.
+    #[error("no se pudo escuchar en {addr}: {causa}")]
+    ListenRechazado { addr: String, causa: String },
 }
 
 #[derive(Debug)]
 enum Command {
     Dial(Multiaddr),
-    Listen(Multiaddr),
+    /// El `oneshot` devuelve el id del listener recién creado. Sin él,
+    /// [`BrahmanNet::listen`] no puede distinguir SU dirección de la de un
+    /// listener anterior. `Err` = `listen_on` rechazó la dirección, con su razón.
+    Listen(Multiaddr, oneshot::Sender<Result<ListenerId, String>>),
     AddDhtPeer(PeerId, Multiaddr),
     FindClosestPeers(PeerId, oneshot::Sender<Vec<DiscoveredPeer>>),
     StartProviding(Vec<u8>),
     StopProviding(Vec<u8>),
     GetProviders(Vec<u8>, oneshot::Sender<Vec<PeerId>>),
+    KnownAddrs(PeerId, oneshot::Sender<Vec<Multiaddr>>),
+    /// Instrumento: las conexiones vivas y por dónde viajan. Sin esto no hay
+    /// manera de saber si DCUtR promovió un circuito a conexión directa, y
+    /// una medición de latencia que no distingue las dos cosas no dice nada:
+    /// un mal número puede ser el código o puede ser el camino.
+    Conexiones(oneshot::Sender<Vec<Conexion>>),
     BlockPeer(PeerId),
     UnblockPeer(PeerId),
 }
@@ -130,6 +147,39 @@ pub struct DiscoveredPeer {
     pub addrs: Vec<Multiaddr>,
 }
 
+/// Por dónde viaja una conexión viva.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Camino {
+    /// Los dos equipos hablan de punta a punta.
+    Directo,
+    /// El tráfico pasa por un tercero (Circuit Relay v2). Suma el viaje hasta
+    /// el relay y la vuelta: para KVM —uywas pide p99 ≤ 3 ms— no alcanza.
+    Circuito,
+}
+
+impl Camino {
+    /// Un `/p2p-circuit` adentro de la dirección es la marca del relay, y es la
+    /// única diferencia OBSERVABLE entre un circuito y una conexión que DCUtR
+    /// ya promovió: cuando el agujereado sale bien, libp2p abre una conexión
+    /// nueva con la dirección directa y esa no lo lleva.
+    pub fn de_addr(addr: &Multiaddr) -> Self {
+        if addr.iter().any(|p| matches!(p, Protocol::P2pCircuit)) {
+            Camino::Circuito
+        } else {
+            Camino::Directo
+        }
+    }
+}
+
+/// Una conexión viva del nodo: con quién, por qué dirección y por qué camino.
+#[derive(Debug, Clone)]
+pub struct Conexion {
+    pub peer: PeerId,
+    /// Dirección remota tal como la ve el transporte.
+    pub addr: Multiaddr,
+    pub camino: Camino,
+}
+
 /// Nodo Brahman en la malla P2P. Maneja el swarm libp2p y expone
 /// API uniforme para listen/dial/DHT/streams.
 pub struct BrahmanNet {
@@ -143,7 +193,7 @@ pub struct BrahmanNet {
     /// Hello). NO se expone públicamente; usar [`Self::keypair`].
     keypair: Arc<Keypair>,
     cmd_tx: mpsc::UnboundedSender<Command>,
-    listen_rx: Mutex<mpsc::UnboundedReceiver<Multiaddr>>,
+    listen_rx: Mutex<mpsc::UnboundedReceiver<(ListenerId, Result<Multiaddr, String>)>>,
     /// Control para abrir y aceptar streams. Cada protocolo
     /// (handshake brahman, sync minga, etc.) llama
     /// `control.accept(StreamProtocol::new("/foo/1.0.0"))` para
@@ -152,6 +202,30 @@ pub struct BrahmanNet {
     pub control: stream::Control,
 }
 
+/// Apaga el descubrimiento LAN (mDNS) para **todo el proceso**: los nodos que
+/// se creen después de esta llamada sólo conocen a quien se les diga.
+///
+/// **Llamala como primera línea de cualquier test que levante más de un nodo.**
+/// No es higiene opcional. mDNS inyecta en la routing table de Kademlia a
+/// cualquier nodo brahman vivo en la red física, así que un test de tres nodos
+/// sobre `127.0.0.1` corriendo en una casa donde hay otra máquina con
+/// khipu/minga encendido no está probando tres nodos: está probando tres más
+/// los que haya, y el resultado depende de qué esté prendido.
+///
+/// Costó una tarde el 9-ago. Cuatro tests —`minga-p2p::kad_discovery` ×2 y
+/// `khipu-brahman::p2p_roundtrip` ×2— fallaban de forma reproducible y se
+/// archivaron dos veces con diagnósticos equivocados («necesita red real entre
+/// nodos», después «el DHT no propaga transitivamente»). Con `BRAHMAN_DEBUG=1`
+/// se vio la verdad: mDNS descubría un nodo ajeno estable en `192.168.1.44`
+/// que se colaba en la tabla y se comía los lugares de la consulta. Con el
+/// descubrimiento LAN apagado, los cuatro pasan sin tocar una línea del DHT.
+///
+/// Equivale a `BRAHMAN_NO_MDNS=1` en el entorno; existe como función para que
+/// nadie tenga que acordarse de la cadena mágica.
+pub fn aislar_de_la_lan() {
+    std::env::set_var("BRAHMAN_NO_MDNS", "1");
+}
+
 impl BrahmanNet {
     /// Crea un nodo con keypair Ed25519 generada al vuelo (peer_id
     /// efímero — cambia en cada arranque).
@@ -213,7 +287,23 @@ impl BrahmanNet {
                 // mDNS resiliente: si el socket multicast no abre, seguimos sin
                 // descubrimiento LAN en vez de fallar el nodo entero.
                 let depurar = std::env::var("BRAHMAN_DEBUG").is_ok();
+                // `BRAHMAN_NO_MDNS=1` apaga el descubrimiento LAN. Existe por los
+                // tests, y no es una comodidad: mDNS mete en la routing table a
+                // **cualquier nodo brahman vivo en la red física**, así que un
+                // test de tres nodos en `127.0.0.1` corriendo en una casa donde
+                // hay otra máquina con khipu/minga encendido no está probando
+                // tres nodos — está probando tres más los que haya. El resultado
+                // depende de qué máquinas estén prendidas, y eso no es un test.
+                let sin_mdns = std::env::var("BRAHMAN_NO_MDNS")
+                    .map(|v| v != "0" && v != "false")
+                    .unwrap_or(false);
                 let mdns = match mdns::tokio::Behaviour::new(mdns::Config::default(), local) {
+                    Ok(_) if sin_mdns => {
+                        if depurar {
+                            eprintln!("brahman: mDNS APAGADO por BRAHMAN_NO_MDNS");
+                        }
+                        Toggle::from(None)
+                    }
                     Ok(b) => {
                         if depurar {
                             eprintln!("brahman: mDNS ACTIVO (descubrimiento LAN)");
@@ -260,7 +350,7 @@ impl BrahmanNet {
         let control = swarm.behaviour().stream.new_control();
 
         let (cmd_tx, mut cmd_rx) = mpsc::unbounded_channel::<Command>();
-        let (listen_tx, listen_rx) = mpsc::unbounded_channel::<Multiaddr>();
+        let (listen_tx, listen_rx) = mpsc::unbounded_channel::<(ListenerId, Result<Multiaddr, String>)>();
 
         tokio::spawn(async move {
             let mut pending_finds: HashMap<
@@ -271,16 +361,45 @@ impl BrahmanNet {
                 kad::QueryId,
                 (Vec<PeerId>, oneshot::Sender<Vec<PeerId>>),
             > = HashMap::new();
+            // Las conexiones vivas, indexadas por su id. El swarm no expone
+            // los extremos de sus conexiones, así que la única forma de saber
+            // por dónde viaja cada una es anotarla al abrirse y borrarla al
+            // cerrarse. Es también lo que hace visible la promoción de DCUtR:
+            // aparece una conexión NUEVA, directa, junto a la del circuito.
+            let mut vivas: HashMap<ConnectionId, Conexion> = HashMap::new();
 
             loop {
                 tokio::select! {
                     Some(cmd) = cmd_rx.recv() => {
                         match cmd {
                             Command::Dial(addr) => {
-                                let _ = swarm.dial(addr);
+                                // El error del dial NO se tira: un dial que
+                                // falla en silencio se ve, del otro lado, como
+                                // un par que no contesta — y se busca el
+                                // problema en el protocolo, no en la dirección.
+                                if let Err(e) = swarm.dial(addr.clone()) {
+                                    if std::env::var("BRAHMAN_DEBUG").is_ok() {
+                                        eprintln!("brahman: no pude dializar {addr}: {e}");
+                                    }
+                                }
                             }
-                            Command::Listen(addr) => {
-                                let _ = swarm.listen_on(addr);
+                            Command::Listen(addr, tx) => {
+                                // La razón viaja al que pidió escuchar en vez de
+                                // irse por stderr: el que llama es el único que
+                                // puede decidir qué hacer con ella.
+                                //
+                                // Se desarma a mano y no con `to_string()`: el
+                                // Display de `TransportError::Other` es la cadena
+                                // VACÍA, así que el motivo de verdad —«permiso
+                                // denegado», «dirección inasignable»— se perdía
+                                // entero y el error nombraba sólo la dirección.
+                                // Un error a medias casi no ahorra tiempo.
+                                let _ = tx.send(swarm.listen_on(addr).map_err(|e| match e {
+                                    TransportError::MultiaddrNotSupported(a) => {
+                                        format!("el transporte no atiende {a}")
+                                    }
+                                    TransportError::Other(io) => io.to_string(),
+                                }));
                             }
                             Command::AddDhtPeer(peer, addr) => {
                                 swarm.behaviour_mut().kad.add_address(&peer, addr);
@@ -312,6 +431,27 @@ impl BrahmanNet {
                                 let qid = swarm.behaviour_mut().kad.get_providers(key.into());
                                 pending_providers.insert(qid, (Vec::new(), tx));
                             }
+                            Command::KnownAddrs(peer, tx) => {
+                                // Lectura LOCAL: lo que mDNS/identify/add_dht_peer
+                                // ya metieron en la tabla de ruteo. Un peer del
+                                // que el DHT sabe el id pero no la dirección
+                                // (p.ej. un provider que nadie más conoce)
+                                // se resuelve por acá, no por FIND_NODE — un
+                                // nodo kad no se incluye a sí mismo en sus
+                                // respuestas de closest-peers.
+                                let mut out: Vec<Multiaddr> = Vec::new();
+                                if let Some(bucket) = swarm.behaviour_mut().kad.kbucket(peer) {
+                                    for entry in bucket.iter() {
+                                        if entry.node.key.preimage() == &peer {
+                                            out.extend(entry.node.value.iter().cloned());
+                                        }
+                                    }
+                                }
+                                let _ = tx.send(out);
+                            }
+                            Command::Conexiones(tx) => {
+                                let _ = tx.send(vivas.values().cloned().collect());
+                            }
                             Command::BlockPeer(peer) => {
                                 swarm.behaviour_mut().block_list.block_peer(peer);
                             }
@@ -322,8 +462,47 @@ impl BrahmanNet {
                     }
                     event = swarm.select_next_some() => {
                         match event {
-                            SwarmEvent::NewListenAddr { address, .. } => {
-                                let _ = listen_tx.send(address);
+                            SwarmEvent::OutgoingConnectionError { peer_id, error, .. } => {
+                                if std::env::var("BRAHMAN_DEBUG").is_ok() {
+                                    eprintln!("brahman: conexión saliente falló ({peer_id:?}): {error}");
+                                }
+                            }
+                            SwarmEvent::NewListenAddr { listener_id, address } => {
+                                let _ = listen_tx.send((listener_id, Ok(address)));
+                            }
+                            // El otro modo de fallar: un listener ya abierto que
+                            // se muere (la interfaz se cayó). Sin esto el que
+                            // pidió escuchar espera una dirección que no viene.
+                            // ⚠️ NO está ejercitado por ningún test: las tres
+                            // direcciones que se probaron las rechaza `listen_on`
+                            // de forma inmediata, por el camino de arriba. Es un
+                            // seguro, no un camino certificado.
+                            SwarmEvent::ListenerClosed { listener_id, reason: Err(e), .. } => {
+                                let _ = listen_tx.send((listener_id, Err(e.to_string())));
+                            }
+                            SwarmEvent::ConnectionEstablished {
+                                peer_id, connection_id, ref endpoint, ..
+                            } => {
+                                let addr = endpoint.get_remote_address().clone();
+                                let camino = Camino::de_addr(&addr);
+                                vivas.insert(connection_id, Conexion { peer: peer_id, addr, camino });
+                            }
+                            SwarmEvent::ConnectionClosed { connection_id, .. } => {
+                                vivas.remove(&connection_id);
+                            }
+                            // El intento de agujereado, dicho en voz alta. Que
+                            // FALLE no rompe nada —se sigue por el circuito—,
+                            // pero es exactamente el dato que después explica
+                            // una latencia de cientos de milisegundos.
+                            SwarmEvent::Behaviour(BrahmanBehaviourEvent::Dcutr(ev)) => {
+                                match &ev.result {
+                                    Ok(_) => eprintln!(
+                                        "brahman: dcutr promovió a directa con {}", ev.remote_peer_id
+                                    ),
+                                    Err(e) => eprintln!(
+                                        "brahman: dcutr NO pudo con {}: {e}", ev.remote_peer_id
+                                    ),
+                                }
                             }
                             // Identify nos dice las listen-addrs reales del
                             // peer. Las inyectamos a Kad para poblar el
@@ -434,6 +613,20 @@ impl BrahmanNet {
         })
     }
 
+    /// Las conexiones vivas de este nodo y por dónde viaja cada una.
+    ///
+    /// Es el instrumento que contesta «¿quedé en el circuito del relay o DCUtR
+    /// me promovió a directa?». Un mismo par puede aparecer DOS veces mientras
+    /// convive la conexión vieja con la nueva; con que haya una [`Camino::Directo`]
+    /// el tráfico ya no está pagando el viaje hasta el relay.
+    pub async fn conexiones(&self) -> Vec<Conexion> {
+        let (tx, rx) = oneshot::channel();
+        if self.cmd_tx.send(Command::Conexiones(tx)).is_err() {
+            return Vec::new();
+        }
+        rx.await.unwrap_or_default()
+    }
+
     /// Acceso a la keypair de identidad del nodo. Usar para firmar
     /// payloads que viajan asociados al `peer_id` (handshake brahman
     /// firmado, futuros sub-protocolos con autenticación). El `Arc`
@@ -457,15 +650,69 @@ impl BrahmanNet {
         let _ = self.cmd_tx.send(Command::UnblockPeer(peer));
     }
 
-    /// Empieza a escuchar en `addr`. Bloquea hasta que el listener
-    /// publique su dirección real (Multiaddr resuelta — útil cuando
-    /// pediste `/ip4/0.0.0.0/tcp/0` y quieres saber qué puerto te tocó).
+    /// Empieza a escuchar en `addr`. Bloquea hasta que **ese** listener
+    /// publique su dirección real (Multiaddr resuelta — útil cuando pediste
+    /// `/ip4/0.0.0.0/tcp/0` y querés saber qué puerto te tocó).
+    ///
+    /// La dirección se correlaciona por [`ListenerId`], y no es un detalle: un
+    /// listener en `0.0.0.0` emite una dirección **por interfaz** y el llamador
+    /// se queda con la primera, así que las demás quedan en la cola. Devolver
+    /// «la próxima que llegue» le daba al segundo `listen` la dirección del
+    /// PRIMER listener — por eso el circuito de relay de `tejido` publicaba la
+    /// IP privada de la LAN en vez de su dirección de circuito.
+    ///
+    /// Si `listen_on` rechaza la dirección, no va a llegar ninguna que sea de
+    /// este listener y la espera no termina: el motivo se imprime desde la task
+    /// del swarm, y ponerle techo es del llamador (`tejido` lo envuelve en un
+    /// `timeout`).
     pub async fn listen(&self, addr: Multiaddr) -> Multiaddr {
-        self.cmd_tx
-            .send(Command::Listen(addr))
-            .expect("swarm task alive");
+        // El lock se toma ANTES de pedir el listener, no después: serializa las
+        // llamadas y hace que la única en vuelo sea la nuestra.
+        // Morir ruidosamente es lo correcto: quien pide escuchar y no puede no
+        // tiene camino alternativo, y esta función no puede devolver un error sin
+        // cambiarle la firma a los treinta y un crates que la llaman. Lo que sí
+        // cambió es que el panic lleva la CAUSA adentro y no sólo la dirección.
+        //
+        // ⚠️ El caso que uno esperaría acá y NO llega: **el puerto ya tomado no
+        // rechaza.** El transporte TCP de libp2p pide `SO_REUSEPORT`, así que dos
+        // nodos escuchan el mismo puerto y el kernel les reparte las conexiones
+        // (medido en `tests/listen.rs`). Lo que sí llega es lo otro: un puerto
+        // privilegiado sin permiso, una interfaz que no existe, una multiaddr que
+        // el transporte no sabe escuchar.
+        match self.try_listen(addr).await {
+            Ok(a) => a,
+            Err(e) => panic!("card-net: {e}"),
+        }
+    }
+
+    /// Igual que [`Self::listen`], pero devuelve el fallo en vez de entrar en
+    /// pánico. Es el que hay que usar cuando escuchar **puede** no salir y eso
+    /// no es motivo para tumbar el programa — el caso de manual es reservar un
+    /// circuito en un relay: que no haya nadie del otro lado es una respuesta
+    /// legítima, no una catástrofe.
+    pub async fn try_listen(&self, addr: Multiaddr) -> Result<Multiaddr, NodeError> {
+        let fallo = |causa: String| NodeError::ListenRechazado { addr: addr.to_string(), causa };
+
+        // El lock se toma ANTES de pedir el listener, no después: serializa las
+        // llamadas y hace que la única en vuelo sea la nuestra.
         let mut rx = self.listen_rx.lock().await;
-        rx.recv().await.expect("listen address arrives")
+        let (tx, id_rx) = oneshot::channel();
+        if self.cmd_tx.send(Command::Listen(addr.clone(), tx)).is_err() {
+            return Err(fallo("la tarea del swarm ya no está viva".into()));
+        }
+        let id = match id_rx.await {
+            Ok(Ok(id)) => id,
+            Ok(Err(causa)) => return Err(fallo(causa)),
+            Err(_) => return Err(fallo("la tarea del swarm ya no está viva".into())),
+        };
+        loop {
+            match rx.recv().await {
+                Some((llegado, r)) if llegado == id => return r.map_err(fallo),
+                // De otro listener: se descarta y se sigue esperando el nuestro.
+                Some(_) => {}
+                None => return Err(fallo("la tarea del swarm ya no está viva".into())),
+            }
+        }
     }
 
     /// Inicia conexión con un peer en `addr`. No-op si ya hay
@@ -512,6 +759,18 @@ impl BrahmanNet {
         let _ = self.cmd_tx.send(Command::StopProviding(key.to_vec()));
     }
 
+    /// Direcciones conocidas de `peer` en la tabla de ruteo LOCAL (las
+    /// pobló mDNS, identify o [`add_dht_peer`](Self::add_dht_peer)).
+    /// Vacío si el peer no está en la tabla. A diferencia de
+    /// [`find_closest_peers`](Self::find_closest_peers) no consulta la red:
+    /// responde lo que este nodo ya sabe, que es la única fuente de la
+    /// dirección de un provider que nadie más conoce.
+    pub async fn known_addrs(&self, peer: PeerId) -> Vec<Multiaddr> {
+        let (tx, rx) = oneshot::channel();
+        let _ = self.cmd_tx.send(Command::KnownAddrs(peer, tx));
+        rx.await.unwrap_or_default()
+    }
+
     /// Consulta el DHT por peers que han anunciado proveer `key`.
     /// Devuelve la lista de `PeerId`s que se reportan como providers.
     /// Lista vacía si nadie anuncia.
diff --git a/shared/card/card-net/tests/camino.rs b/shared/card/card-net/tests/camino.rs
new file mode 100644
index 0000000..dcbc52d
--- /dev/null
+++ b/shared/card/card-net/tests/camino.rs
@@ -0,0 +1,91 @@
+// =============================================================================
+//  Por dónde viaja una conexión: el instrumento que faltaba
+// -----------------------------------------------------------------------------
+//  `card-net` trae Circuit Relay v2 y DCUtR desde siempre, pero no había forma
+//  de preguntarle a un nodo vivo si una conexión seguía pasando por el relay o
+//  si el agujereado ya la había promovido a directa. Sin esa respuesta, una
+//  medición de latencia no distingue «el código está mal» de «el camino está
+//  mal» — y para uywas (KVM, p99 ≤ 3 ms) las dos cosas dan el mismo síntoma:
+//  un puntero que se arrastra.
+//
+//  Acá se certifican las tres cosas que el instrumento tiene que hacer bien:
+//  no inventar conexiones, ver la que existe, y no confundir los dos caminos.
+// =============================================================================
+
+use std::time::Duration;
+
+use card_net::{aislar_de_la_lan, BrahmanNet, Camino, Multiaddr};
+
+/// Un nodo recién nacido no habló con nadie. Mata al instrumento que fabrica
+/// entradas o que devuelve basura de otro nodo.
+#[tokio::test]
+async fn un_nodo_solo_no_tiene_conexiones() {
+    aislar_de_la_lan();
+    let n = BrahmanNet::new().expect("nodo");
+    assert!(
+        n.conexiones().await.is_empty(),
+        "un nodo que no marcó a nadie no puede reportar conexiones"
+    );
+}
+
+/// El caso feliz, y el que mata al instrumento que devuelve siempre vacío: dos
+/// nodos que se marcan de verdad aparecen, con el par correcto y por directa.
+#[tokio::test]
+async fn dos_nodos_que_se_marcan_aparecen_como_directa() {
+    aislar_de_la_lan();
+    let a = BrahmanNet::new().expect("nodo a");
+    let b = BrahmanNet::new().expect("nodo b");
+
+    let bound = a.listen("/ip4/127.0.0.1/tcp/0".parse().unwrap()).await;
+    let addr: Multiaddr = format!("{bound}/p2p/{}", a.peer_id).parse().unwrap();
+    b.dial(addr);
+
+    // La conexión no es instantánea: TCP + Noise + Yamux. Se espera por el
+    // hecho, no por un sleep fijo.
+    let mut vistas = Vec::new();
+    for _ in 0..100 {
+        vistas = b.conexiones().await;
+        if !vistas.is_empty() {
+            break;
+        }
+        tokio::time::sleep(Duration::from_millis(50)).await;
+    }
+
+    assert_eq!(vistas.len(), 1, "esperaba una sola conexión, vinieron {vistas:?}");
+    let c = &vistas[0];
+    assert_eq!(c.peer, a.peer_id, "el par reportado no es con quien marcamos");
+    assert_eq!(
+        c.camino,
+        Camino::Directo,
+        "un loopback sin relay de por medio es directo, vino {:?} por {}",
+        c.camino, c.addr
+    );
+
+    // Y del otro lado se ve la misma conexión, también directa: el instrumento
+    // no depende de quién marcó.
+    let del_lado_de_a = a.conexiones().await;
+    assert_eq!(del_lado_de_a.len(), 1, "el que recibe también la ve: {del_lado_de_a:?}");
+    assert_eq!(del_lado_de_a[0].camino, Camino::Directo);
+}
+
+/// La distinción, aislada del resto: sin esto un instrumento que contesta
+/// siempre `Directo` pasaría el test de arriba y mentiría justo en el caso que
+/// importa.
+#[test]
+fn una_direccion_de_circuito_no_se_confunde_con_una_directa() {
+    let directa: Multiaddr = "/ip4/204.168.193.248/tcp/4102".parse().unwrap();
+    assert_eq!(Camino::de_addr(&directa), Camino::Directo);
+
+    // La forma real que devuelve una reserva de relay.
+    let por_circuito: Multiaddr =
+        "/ip4/204.168.193.248/tcp/4102/p2p/12D3KooWBw2u8mt2ciUjX69qpVAZiJZ8sZMGxz8eHJEnz4rzg37L/p2p-circuit"
+            .parse()
+            .unwrap();
+    assert_eq!(Camino::de_addr(&por_circuito), Camino::Circuito);
+
+    // Y con el destino colgado atrás, que es como llega al que se une.
+    let completa: Multiaddr = format!("{por_circuito}/p2p/12D3KooWBw2u8mt2ciUjX69qpVAZiJZ8sZMGxz8eHJEnz4rzg37L")
+        .parse()
+        .unwrap();
+    assert_eq!(Camino::de_addr(&completa), Camino::Circuito);
+}
diff --git a/shared/card/card-net/tests/listen.rs b/shared/card/card-net/tests/listen.rs
new file mode 100644
index 0000000..8c06769
--- /dev/null
+++ b/shared/card/card-net/tests/listen.rs
@@ -0,0 +1,191 @@
+//! `listen` devuelve la dirección DE SU listener, no la próxima que llegue.
+//!
+//! El defecto que estos tests fijan salió en metal el 2026-09-01: `tejido
+//! servir --relay` publicaba, como dirección de circuito, la IP **privada** de
+//! la LAN de la laptop. La causa no estaba en tejido: `BrahmanNet::listen`
+//! hacía `recv()` sobre un canal compartido y devolvía lo primero que hubiera.
+//! Un listener en `0.0.0.0` emite una dirección **por interfaz** y el llamador
+//! se queda con la primera; las demás quedan encoladas, y el `listen`
+//! siguiente se llevaba una de ésas.
+//!
+//! Por eso el primer listener de los dos tests es siempre en `0.0.0.0`: es lo
+//! que deja el sobrante. En una máquina con **una sola** interfaz no hay
+//! sobrante que dejar y los tests pasan por vacuidad — no dan falso negativo,
+//! pero tampoco matan al mutante. Acá y en cualquier máquina con red (lo +
+//! eth0/wlan0, que es también el caso de un contenedor de CI) sí lo matan:
+//! comprobado revirtiendo el arreglo, los dos fallan.
+
+use card_net::{aislar_de_la_lan, BrahmanNet};
+use libp2p::Multiaddr;
+
+fn puerto(a: &Multiaddr) -> Option<u16> {
+    a.iter().find_map(|p| match p {
+        libp2p::multiaddr::Protocol::Tcp(n) => Some(n),
+        _ => None,
+    })
+}
+
+/// El caso exacto del relay: dos `listen` seguidos, el segundo con un puerto
+/// FIJO. Si vuelve el puerto del primero, el llamador publica la dirección de
+/// otro listener creyendo que es la suya.
+#[tokio::test]
+async fn el_segundo_listen_no_se_lleva_la_direccion_del_primero() {
+    let net = BrahmanNet::new().expect("nodo");
+
+    let comodin = net.listen("/ip4/0.0.0.0/tcp/0".parse().unwrap()).await;
+    let p1 = puerto(&comodin).expect("el comodín resuelve a un puerto");
+
+    // Puerto fijo y distinto: hace que el fallo sea una igualdad, no una
+    // coincidencia estadística de puertos efímeros.
+    let fijo = net.listen("/ip4/127.0.0.1/tcp/47811".parse().unwrap()).await;
+    let p2 = puerto(&fijo).expect("el fijo resuelve a un puerto");
+
+    assert_eq!(p2, 47811, "listen devolvió la dirección de OTRO listener: {fijo}");
+    assert_ne!(p1, p2, "los dos listen devolvieron el mismo puerto: {comodin} / {fijo}");
+}
+
+/// La forma pedida sobrevive, no sólo el puerto: quien pide loopback no puede
+/// recibir la dirección de una interfaz de la LAN. Es la mitad del defecto que
+/// se veía a simple vista — la dirección publicada era `192.168.x.x`.
+#[tokio::test]
+async fn listen_en_loopback_no_devuelve_una_direccion_de_la_lan() {
+    let net = BrahmanNet::new().expect("nodo");
+
+    let _comodin = net.listen("/ip4/0.0.0.0/tcp/0".parse().unwrap()).await;
+    let loopback = net.listen("/ip4/127.0.0.1/tcp/0".parse().unwrap()).await;
+
+    let ip = loopback
+        .iter()
+        .find_map(|p| match p {
+            libp2p::multiaddr::Protocol::Ip4(ip) => Some(ip),
+            _ => None,
+        })
+        .expect("la dirección lleva una IPv4");
+    assert!(ip.is_loopback(), "pedí loopback y volvió {loopback}");
+}
+
+/// **Dos nodos pueden escuchar el mismo puerto, y eso no falla.**
+///
+/// El test nació buscando otra cosa —que un `listen` imposible muriera en vez de
+/// colgarse— y encontró esto, que importa más: el transporte TCP de libp2p pide
+/// `SO_REUSEPORT`, así que el segundo `listen` sobre un puerto tomado **devuelve
+/// ese mismo puerto**, sin error. El kernel reparte las conexiones entrantes
+/// entre los dos sockets.
+///
+/// Por qué se deja escrito en un test y no en un comentario: es una propiedad
+/// del transporte que no está en nuestra API ni en nuestra documentación, y de
+/// la que dependen decisiones nuestras. La consecuencia concreta está en
+/// `COLA-VERIFICACION.md`: dos `tejido relay` con la misma semilla tienen el
+/// mismo `PeerId` y se parten las reservas de circuito, así que arrancarlo dos
+/// veces lo rompe a la mitad **sin que nada lo diga**. Si algún día libp2p deja
+/// de pedir `SO_REUSEPORT`, este test cambia de rama y avisa.
+#[tokio::test]
+async fn dos_nodos_escuchan_el_mismo_puerto_sin_error() {
+    let primero = BrahmanNet::new().expect("nodo");
+    let tomado = primero.listen("/ip4/127.0.0.1/tcp/0".parse().unwrap()).await;
+    let p = puerto(&tomado).expect("el primero resuelve a un puerto");
+
+    // El segundo nodo pide EXACTAMENTE el mismo puerto: `listen_on` lo rechaza.
+    let segundo = BrahmanNet::new().expect("nodo");
+    let choca: Multiaddr = format!("/ip4/127.0.0.1/tcp/{p}").parse().unwrap();
+
+    // El timeout es el test: sin el arreglo esto no falla, se **cuelga**, y un
+    // test colgado no dice nada. Con el arreglo la task paniquea, y un panic
+    // adentro de un `spawn` vuelve como `Err` del join en vez de tumbar la suite.
+    let intento = tokio::time::timeout(
+        std::time::Duration::from_secs(10),
+        tokio::spawn(async move { segundo.listen(choca).await }),
+    )
+    .await
+    .expect("listen sobre un puerto tomado no puede colgarse");
+
+    // El invariante no es «tiene que fallar»: es que **no puede devolver otra
+    // cosa que lo pedido sin decirlo**. Fallar es una respuesta aceptable;
+    // devolver un puerto distinto callado, no — es el mismo defecto que el
+    // circuito publicando la IP de la LAN, con otro disfraz.
+    match intento {
+        // Paniqueó: dijo que no podía. Se imprime porque las dos ramas son
+        // aceptables y muy distintas, y un test que pasa sin decir por cuál
+        // pasó no certifica nada (CLAUDE.md, regla 8.bis).
+        Err(_) => eprintln!("rama: listen PANIQUEÓ sobre el puerto {p} tomado"),
+        Ok(addr) => {
+            eprintln!("rama: listen DEVOLVIÓ {addr} pidiendo el puerto {p}");
+            let dado = puerto(&addr).expect("la dirección lleva un puerto");
+            assert_eq!(
+                dado, p,
+                "pedí el puerto {p} (tomado por {tomado}) y devolvió {addr} sin avisar"
+            );
+        }
+    }
+}
+
+// -----------------------------------------------------------------------------
+//  Y cuando escuchar no sale: que se sepa POR QUÉ
+// -----------------------------------------------------------------------------
+//  `listen` ya no se cuelga — paniquea. Pero paniquear nombrando sólo la
+//  dirección deja afuera la mitad útil: el motivo del sistema. Estos dos casos
+//  van contra `try_listen`, que devuelve el fallo en vez de morirse, y exigen
+//  las dos partes: qué dirección y por qué.
+// -----------------------------------------------------------------------------
+
+/// Rechazo por la forma del dato: este nodo habla TCP, y `/ip4/…` a secas no
+/// dice por dónde.
+#[tokio::test]
+async fn una_direccion_que_el_transporte_no_atiende_falla_con_nombre() {
+    aislar_de_la_lan();
+    let n = BrahmanNet::new().expect("nodo");
+
+    // Con techo, y no por prolijidad: si esto vuelve a colgarse —que es
+    // exactamente la regresión que el test vigila— sin el `timeout` no falla, se
+    // cuelga la suite entera y nadie sabe cuál fue. Se comprobó rompiéndolo.
+    let inicio = std::time::Instant::now();
+    let e = tokio::time::timeout(
+        std::time::Duration::from_secs(10),
+        n.try_listen("/ip4/127.0.0.1".parse().unwrap()),
+    )
+    .await
+    .expect("no puede colgarse: ése era el bug")
+    .expect_err("una multiaddr sin transporte no se puede escuchar");
+
+    assert!(
+        e.to_string().contains("/ip4/127.0.0.1"),
+        "el error tiene que nombrar la dirección, vino: {e}"
+    );
+    assert!(inicio.elapsed() < std::time::Duration::from_secs(2), "tardó {:?}", inicio.elapsed());
+}
+
+/// Rechazo del sistema operativo: una dirección que no es de ninguna interfaz
+/// de esta máquina. Es el caso que trae un motivo de verdad, y el que casi se
+/// pierde.
+#[tokio::test]
+async fn una_direccion_que_no_es_de_esta_maquina_falla_con_motivo() {
+    aislar_de_la_lan();
+    let n = BrahmanNet::new().expect("nodo");
+
+    // TEST-NET-1 (RFC 5737): reservada para documentación, jamás asignada a una
+    // interfaz. Si algún día lo estuviera, este test empezaría a fallar y diría
+    // por qué — mejor que elegir una IP privada que quizá sí exista.
+    let e = tokio::time::timeout(
+        std::time::Duration::from_secs(10),
+        n.try_listen("/ip4/192.0.2.7/tcp/0".parse().unwrap()),
+    )
+    .await
+    .expect("no puede colgarse: ése era el bug")
+    .expect_err("esa dirección no es de ninguna interfaz local");
+
+    assert!(
+        e.to_string().contains("192.0.2.7"),
+        "el error tiene que nombrar la dirección, vino: {e}"
+    );
+    // Y la CAUSA, que es lo que casi se pierde: el `Display` de
+    // `TransportError::Other` es la cadena vacía, así que la primera versión de
+    // esto decía «no se pudo escuchar en /ip4/192.0.2.7/tcp/0: » y nada más. Un
+    // error que nombra el qué pero no el porqué no ahorra ni una hora.
+    let texto = e.to_string();
+    let causa = texto.rsplit(": ").next().unwrap_or("");
+    assert!(
+        !causa.trim().is_empty(),
+        "el error tiene que traer el motivo del sistema, vino pelado: {e}"
+    );
+    eprintln!("motivo del sistema: {causa}");
+}
diff --git a/shared/card/card-wit/examples/brahman-wit-info.rs b/shared/card/card-wit/examples/brahman-wit-info.rs
index ef58524..a3bca24 100644
--- a/shared/card/card-wit/examples/brahman-wit-info.rs
+++ b/shared/card/card-wit/examples/brahman-wit-info.rs
@@ -2,7 +2,7 @@
 //!
 //! Uso:
 //! ```sh
-//! cargo run -p brahman-card-wit --example brahman-wit-info -- shared_wit/protocol.wit
+//! cargo run -p card-wit --example brahman-wit-info -- shared/card/card-wit/fixtures/modulo-demo.wit
 //! ```
 
 use std::process::ExitCode;
diff --git a/shared/card/card-wit/fixtures/modulo-demo.wit b/shared/card/card-wit/fixtures/modulo-demo.wit
new file mode 100644
index 0000000..d29a5f4
--- /dev/null
+++ b/shared/card/card-wit/fixtures/modulo-demo.wit
@@ -0,0 +1,63 @@
+// Fixture del parser de card-wit. NO es un contrato vigente.
+//
+// La Capa 3 de Brahman (módulos agnósticos descritos por WIT) está MUERTA
+// desde el 2026-05-30 — ver BRAHMAN.md. El contrato real y vigente es el
+// formato `Card` + el handshake nativo Rust de `card-handshake`.
+//
+// Este archivo existe por dos razones concretas y ninguna es arquitectónica:
+//
+//   1. `parse_wit_file` es API pública y no tenía ni un solo test que la
+//      tocara: el único que la ejercitaba apuntaba a `shared_wit/protocol.wit`,
+//      un archivo que nunca existió, y por eso vivía en cuarentena.
+//   2. Los dos examples del parser (`brahman-wit-info` y el
+//      `presence-conscious` de card-sidecar) tenían ese mismo path muerto de
+//      default, así que correrlos sin argumentos no demostraba nada.
+//
+// La forma imita al handshake real (Hello → HelloAck, Ping, Farewell) para que
+// leerlo enseñe algo, pero nadie genera bindings de acá.
+
+package brahman:demo@0.1.0;
+
+/// Lo que abre toda conexión: el módulo se presenta y el servidor le contesta
+/// con una sesión.
+interface handshake {
+    /// El saludo. Lleva la Card del módulo, que acá se resume a su etiqueta.
+    record hello {
+        schema-version: u16,
+        protocol-version: string,
+        label: string,
+    }
+
+    /// La respuesta del servidor, con el ULID de sesión asignado.
+    record hello-ack {
+        server-version: string,
+        session: string,
+        init-attached: bool,
+    }
+
+    greet: func(h: hello) -> result<hello-ack, string>;
+}
+
+/// Lo que se puede hacer una vez saludado.
+interface lifecycle {
+    ping: func() -> result<_, string>;
+    farewell: func();
+}
+
+/// La cara del proxy Admin: mirar quién está conectado.
+interface admin {
+    list-modules: func() -> list<string>;
+}
+
+/// Un módulo Brahman: saluda y responde al ciclo de vida.
+world module {
+    import lifecycle;
+    export handshake;
+}
+
+/// El otro lado: el Init, o un proxy Admin que lo representa.
+world admin-host {
+    import handshake;
+    import lifecycle;
+    export admin;
+}
diff --git a/shared/card/card-wit/src/lib.rs b/shared/card/card-wit/src/lib.rs
index 6d37e29..bdb4f75 100644
--- a/shared/card/card-wit/src/lib.rs
+++ b/shared/card/card-wit/src/lib.rs
@@ -143,19 +143,41 @@ world module {
         );
     }
 
+    /// El camino de archivo, que hasta el 2026-08-10 **no tenía un solo test**:
+    /// el único que lo tocaba apuntaba a `shared_wit/protocol.wit`, un archivo
+    /// que nunca existió, y por eso estaba en cuarentena. La cuarentena tapaba
+    /// que `parse_wit_file` —API pública— no estaba cubierta por nada.
+    ///
+    /// De paso cubre lo que el test inline no puede: **más de un world en el
+    /// mismo paquete**.
     #[test]
-    fn parses_shared_protocol() {
-        let path = concat!(env!("CARGO_MANIFEST_DIR"), "/../../../shared_wit/protocol.wit");
+    fn parses_fixture_file() {
+        let path = concat!(env!("CARGO_MANIFEST_DIR"), "/fixtures/modulo-demo.wit");
         let worlds = parse_wit_file(path).unwrap();
+        let nombres: Vec<_> = worlds.iter().map(|w| w.world.as_str()).collect();
         assert!(
-            worlds.iter().any(|w| w.world == "module"),
-            "no encontró world 'module' en {:?}",
-            worlds.iter().map(|w| &w.world).collect::<Vec<_>>()
+            nombres.contains(&"module"),
+            "no encontró world 'module' en {nombres:?}"
         );
         assert!(
-            worlds.iter().any(|w| w.world == "admin-host"),
-            "no encontró world 'admin-host'"
+            nombres.contains(&"admin-host"),
+            "no encontró world 'admin-host' en {nombres:?}"
         );
+
+        let m = worlds.iter().find(|w| w.world == "module").unwrap();
+        assert!(m.package.starts_with("brahman:demo"), "{}", m.package);
+        assert!(m.exports.iter().any(|e| e == "handshake"), "{:?}", m.exports);
+        assert!(m.imports.iter().any(|i| i == "lifecycle"), "{:?}", m.imports);
+    }
+
+    /// Un path que no existe tiene que llegar como [`WitError::Io`] y no como
+    /// un parse vacío: los dos examples del parser reciben el path por
+    /// argumento, y confundir «no está» con «no declara worlds» manda a buscar
+    /// el problema al lado equivocado.
+    #[test]
+    fn archivo_ausente_es_error_de_io() {
+        let e = parse_wit_file("/no/existe/jamas.wit").unwrap_err();
+        assert!(matches!(e, WitError::Io(_)), "{e:?}");
     }
 
     #[test]