feat: card standalone — primitiva de identidad soberana (hoja, autocontenida)
Tarjeta de Presentación canónica: identidad arje + flujos tipados brahman,
content-addressed. Hoja pura sin deps internas — la base sobre la que se
montan red (chasqui/minga) y escritorio. cargo check pasa (3 crates).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
e6d21c60273e028ce20801c3287895cc7777c43a · ruwaq Sergio · 2026-06-04T12:05:09Z (4 killa ñawpaq) · kay commitpi sach'ata qhaway
Semántico sayk'uy · 119
shared/card/card-core/src/lib.rs
shared/card/card-net/src/key.rs
shared/card/card-net/src/lib.rs
shared/card/card-wit/examples/brahman-wit-info.rs
shared/card/card-wit/src/lib.rs
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..b7141ea
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+/target
+**/*.rs.bk
+*.pdb
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 0000000..f99711c
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,3933 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "aead"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
+dependencies = [
+ "crypto-common",
+ "generic-array",
+]
+
+[[package]]
+name = "aes"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
+dependencies = [
+ "cfg-if",
+ "cipher",
+ "cpufeatures 0.2.17",
+]
+
+[[package]]
+name = "aes-gcm"
+version = "0.10.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1"
+dependencies = [
+ "aead",
+ "aes",
+ "cipher",
+ "ctr",
+ "ghash",
+ "subtle",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.102"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
+
+[[package]]
+name = "arrayref"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb"
+
+[[package]]
+name = "arrayvec"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
+
+[[package]]
+name = "asn1-rs"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b7f43a50ac4fdca5df8e885c21b835997f0a1cdee65494a6847694a98652d9d8"
+dependencies = [
+ "asn1-rs-derive",
+ "asn1-rs-impl",
+ "displaydoc",
+ "nom",
+ "num-traits",
+ "rusticata-macros",
+ "thiserror 2.0.18",
+ "time",
+]
+
+[[package]]
+name = "asn1-rs-derive"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "synstructure",
+]
+
+[[package]]
+name = "asn1-rs-impl"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "async-io"
+version = "2.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc"
+dependencies = [
+ "autocfg",
+ "cfg-if",
+ "concurrent-queue",
+ "futures-io",
+ "futures-lite",
+ "parking",
+ "polling",
+ "rustix",
+ "slab",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "async-trait"
+version = "0.1.89"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "asynchronous-codec"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a860072022177f903e59730004fb5dc13db9275b79bb2aef7ba8ce831956c233"
+dependencies = [
+ "bytes",
+ "futures-sink",
+ "futures-util",
+ "memchr",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "atomic-polyfill"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4"
+dependencies = [
+ "critical-section",
+]
+
+[[package]]
+name = "atomic-waker"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
+
+[[package]]
+name = "attohttpc"
+version = "0.30.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16e2cdb6d5ed835199484bb92bb8b3edd526effe995c61732580439c1a67e2e9"
+dependencies = [
+ "base64",
+ "http",
+ "log",
+ "url",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
+
+[[package]]
+name = "base-x"
+version = "0.2.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270"
+
+[[package]]
+name = "base256emoji"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5e9430d9a245a77c92176e649af6e275f20839a48389859d1661e9a128d077c"
+dependencies = [
+ "const-str",
+ "match-lookup",
+]
+
+[[package]]
+name = "base64"
+version = "0.22.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
+
+[[package]]
+name = "base64ct"
+version = "1.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06"
+
+[[package]]
+name = "bitflags"
+version = "2.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "84d7ced0ae9557296835c32bf1b1e02b44c746701f898460fb000d7eaa84f00a"
+
+[[package]]
+name = "blake2"
+version = "0.10.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe"
+dependencies = [
+ "digest",
+]
+
+[[package]]
+name = "blake3"
+version = "1.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0aa83c34e62843d924f905e0f5c866eb1dd6545fc4d719e803d9ba6030371fce"
+dependencies = [
+ "arrayref",
+ "arrayvec",
+ "cc",
+ "cfg-if",
+ "constant_time_eq",
+ "cpufeatures 0.3.0",
+]
+
+[[package]]
+name = "block-buffer"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "bs58"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4"
+dependencies = [
+ "tinyvec",
+]
+
+[[package]]
+name = "bumpalo"
+version = "3.20.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
+
+[[package]]
+name = "byteorder"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
+
+[[package]]
+name = "bytes"
+version = "1.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
+
+[[package]]
+name = "card-core"
+version = "0.1.0"
+dependencies = [
+ "postcard",
+ "serde",
+ "serde_json",
+ "thiserror 2.0.18",
+ "toml",
+ "ulid",
+]
+
+[[package]]
+name = "card-net"
+version = "0.1.0"
+dependencies = [
+ "blake3",
+ "futures",
+ "libp2p",
+ "libp2p-allow-block-list",
+ "libp2p-stream",
+ "serde",
+ "thiserror 2.0.18",
+ "tokio",
+ "tokio-util",
+ "tracing",
+]
+
+[[package]]
+name = "card-wit"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "card-core",
+ "thiserror 2.0.18",
+ "wit-parser 0.230.0",
+]
+
+[[package]]
+name = "cc"
+version = "1.2.63"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f"
+dependencies = [
+ "find-msvc-tools",
+ "shlex",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
+
+[[package]]
+name = "cfg_aliases"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
+
+[[package]]
+name = "chacha20"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818"
+dependencies = [
+ "cfg-if",
+ "cipher",
+ "cpufeatures 0.2.17",
+]
+
+[[package]]
+name = "chacha20poly1305"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35"
+dependencies = [
+ "aead",
+ "chacha20",
+ "cipher",
+ "poly1305",
+ "zeroize",
+]
+
+[[package]]
+name = "cipher"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
+dependencies = [
+ "crypto-common",
+ "inout",
+ "zeroize",
+]
+
+[[package]]
+name = "cobs"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1"
+dependencies = [
+ "thiserror 2.0.18",
+]
+
+[[package]]
+name = "concurrent-queue"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "const-oid"
+version = "0.9.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
+
+[[package]]
+name = "const-str"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2f421161cb492475f1661ddc9815a745a1c894592070661180fdec3d4872e9c3"
+
+[[package]]
+name = "constant_time_eq"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b"
+
+[[package]]
+name = "core-foundation"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
+
+[[package]]
+name = "cpufeatures"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "cpufeatures"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "critical-section"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b"
+
+[[package]]
+name = "crossbeam-channel"
+version = "0.5.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-epoch"
+version = "0.9.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
+
+[[package]]
+name = "crunchy"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
+
+[[package]]
+name = "crypto-common"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
+dependencies = [
+ "generic-array",
+ "rand_core 0.6.4",
+ "typenum",
+]
+
+[[package]]
+name = "ctr"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835"
+dependencies = [
+ "cipher",
+]
+
+[[package]]
+name = "curve25519-dalek"
+version = "4.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be"
+dependencies = [
+ "cfg-if",
+ "cpufeatures 0.2.17",
+ "curve25519-dalek-derive",
+ "digest",
+ "fiat-crypto",
+ "rustc_version",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "curve25519-dalek-derive"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "data-encoding"
+version = "2.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8"
+
+[[package]]
+name = "data-encoding-macro"
+version = "0.1.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3259c913752a86488b501ed8680446a5ed2d5aeac6e596cb23ba3800768ea32c"
+dependencies = [
+ "data-encoding",
+ "data-encoding-macro-internal",
+]
+
+[[package]]
+name = "data-encoding-macro-internal"
+version = "0.1.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ccc2776f0c61eca1ca32528f85548abd1a4be8fb53d1b21c013e4f18da1e7090"
+dependencies = [
+ "data-encoding",
+ "syn",
+]
+
+[[package]]
+name = "der"
+version = "0.7.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
+dependencies = [
+ "const-oid",
+ "zeroize",
+]
+
+[[package]]
+name = "der-parser"
+version = "10.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6"
+dependencies = [
+ "asn1-rs",
+ "displaydoc",
+ "nom",
+ "num-bigint",
+ "num-traits",
+ "rusticata-macros",
+]
+
+[[package]]
+name = "deranged"
+version = "0.5.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
+dependencies = [
+ "powerfmt",
+]
+
+[[package]]
+name = "digest"
+version = "0.10.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
+dependencies = [
+ "block-buffer",
+ "crypto-common",
+ "subtle",
+]
+
+[[package]]
+name = "displaydoc"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "dtoa"
+version = "1.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c3cf4824e2d5f025c7b531afcb2325364084a16806f6d47fbc1f5fbd9960590"
+
+[[package]]
+name = "ed25519"
+version = "2.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53"
+dependencies = [
+ "pkcs8",
+ "signature",
+]
+
+[[package]]
+name = "ed25519-dalek"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9"
+dependencies = [
+ "curve25519-dalek",
+ "ed25519",
+ "serde",
+ "sha2",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "either"
+version = "1.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e"
+
+[[package]]
+name = "embedded-io"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced"
+
+[[package]]
+name = "embedded-io"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d"
+
+[[package]]
+name = "enum-as-inner"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "equivalent"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
+
+[[package]]
+name = "errno"
+version = "0.3.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
+dependencies = [
+ "libc",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "fiat-crypto"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
+
+[[package]]
+name = "find-msvc-tools"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "foldhash"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
+
+[[package]]
+name = "form_urlencoded"
+version = "1.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf"
+dependencies = [
+ "percent-encoding",
+]
+
+[[package]]
+name = "futures"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-executor",
+ "futures-io",
+ "futures-sink",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-bounded"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91f328e7fb845fc832912fb6a34f40cf6d1888c92f974d1893a54e97b5ff542e"
+dependencies = [
+ "futures-timer",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-channel"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
+
+[[package]]
+name = "futures-executor"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-io"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718"
+
+[[package]]
+name = "futures-lite"
+version = "2.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad"
+dependencies = [
+ "futures-core",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "futures-macro"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "futures-rustls"
+version = "0.26.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8f2f12607f92c69b12ed746fabf9ca4f5c482cba46679c1a75b874ed7c26adb"
+dependencies = [
+ "futures-io",
+ "rustls",
+ "rustls-pki-types",
+]
+
+[[package]]
+name = "futures-sink"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893"
+
+[[package]]
+name = "futures-task"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393"
+
+[[package]]
+name = "futures-timer"
+version = "3.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af43fadb8a98512d547e37b4e92e0ced13e205c061b87b4623eff01d918d6968"
+
+[[package]]
+name = "futures-util"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "futures-macro",
+ "futures-sink",
+ "futures-task",
+ "memchr",
+ "pin-project-lite",
+ "slab",
+]
+
+[[package]]
+name = "generic-array"
+version = "0.14.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
+dependencies = [
+ "typenum",
+ "version_check",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "libc",
+ "wasi",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "libc",
+ "r-efi 5.3.0",
+ "wasip2",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "r-efi 6.0.0",
+ "wasip2",
+ "wasip3",
+]
+
+[[package]]
+name = "ghash"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1"
+dependencies = [
+ "opaque-debug",
+ "polyval",
+]
+
+[[package]]
+name = "h2"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "171fefbc92fe4a4de27e0698d6a5b392d6a0e333506bc49133760b3bcf948733"
+dependencies = [
+ "atomic-waker",
+ "bytes",
+ "fnv",
+ "futures-core",
+ "futures-sink",
+ "http",
+ "indexmap",
+ "slab",
+ "tokio",
+ "tokio-util",
+ "tracing",
+]
+
+[[package]]
+name = "hash32"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67"
+dependencies = [
+ "byteorder",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.15.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
+dependencies = [
+ "foldhash",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.17.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
+
+[[package]]
+name = "hashlink"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1"
+dependencies = [
+ "hashbrown 0.15.5",
+]
+
+[[package]]
+name = "heapless"
+version = "0.7.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f"
+dependencies = [
+ "atomic-polyfill",
+ "hash32",
+ "rustc_version",
+ "serde",
+ "spin",
+ "stable_deref_trait",
+]
+
+[[package]]
+name = "heck"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
+
+[[package]]
+name = "hermit-abi"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
+
+[[package]]
+name = "hex"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
+
+[[package]]
+name = "hickory-proto"
+version = "0.25.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8a6fe56c0038198998a6f217ca4e7ef3a5e51f46163bd6dd60b5c71ca6c6502"
+dependencies = [
+ "async-trait",
+ "cfg-if",
+ "data-encoding",
+ "enum-as-inner",
+ "futures-channel",
+ "futures-io",
+ "futures-util",
+ "idna",
+ "ipnet",
+ "once_cell",
+ "rand 0.9.4",
+ "ring",
+ "socket2 0.5.10",
+ "thiserror 2.0.18",
+ "tinyvec",
+ "tokio",
+ "tracing",
+ "url",
+]
+
+[[package]]
+name = "hickory-resolver"
+version = "0.25.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc62a9a99b0bfb44d2ab95a7208ac952d31060efc16241c87eaf36406fecf87a"
+dependencies = [
+ "cfg-if",
+ "futures-util",
+ "hickory-proto",
+ "ipconfig",
+ "moka",
+ "once_cell",
+ "parking_lot",
+ "rand 0.9.4",
+ "resolv-conf",
+ "smallvec",
+ "thiserror 2.0.18",
+ "tokio",
+ "tracing",
+]
+
+[[package]]
+name = "hkdf"
+version = "0.12.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7"
+dependencies = [
+ "hmac",
+]
+
+[[package]]
+name = "hmac"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
+dependencies = [
+ "digest",
+]
+
+[[package]]
+name = "http"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8be7462df143984c4598a256ef469b251d7d7f9e271135073e78fc535414f3d0"
+dependencies = [
+ "bytes",
+ "itoa",
+]
+
+[[package]]
+name = "http-body"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
+dependencies = [
+ "bytes",
+ "http",
+]
+
+[[package]]
+name = "http-body-util"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "http",
+ "http-body",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "httparse"
+version = "1.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
+
+[[package]]
+name = "hyper"
+version = "1.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498"
+dependencies = [
+ "atomic-waker",
+ "bytes",
+ "futures-channel",
+ "futures-core",
+ "h2",
+ "http",
+ "http-body",
+ "httparse",
+ "itoa",
+ "pin-project-lite",
+ "smallvec",
+ "tokio",
+ "want",
+]
+
+[[package]]
+name = "hyper-util"
+version = "0.1.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0"
+dependencies = [
+ "bytes",
+ "futures-channel",
+ "futures-util",
+ "http",
+ "http-body",
+ "hyper",
+ "libc",
+ "pin-project-lite",
+ "socket2 0.6.4",
+ "tokio",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "icu_collections"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c"
+dependencies = [
+ "displaydoc",
+ "potential_utf",
+ "utf8_iter",
+ "yoke",
+ "zerofrom",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_locale_core"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29"
+dependencies = [
+ "displaydoc",
+ "litemap",
+ "tinystr",
+ "writeable",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_normalizer"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4"
+dependencies = [
+ "icu_collections",
+ "icu_normalizer_data",
+ "icu_properties",
+ "icu_provider",
+ "smallvec",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_normalizer_data"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38"
+
+[[package]]
+name = "icu_properties"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de"
+dependencies = [
+ "icu_collections",
+ "icu_locale_core",
+ "icu_properties_data",
+ "icu_provider",
+ "zerotrie",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_properties_data"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14"
+
+[[package]]
+name = "icu_provider"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421"
+dependencies = [
+ "displaydoc",
+ "icu_locale_core",
+ "writeable",
+ "yoke",
+ "zerofrom",
+ "zerotrie",
+ "zerovec",
+]
+
+[[package]]
+name = "id-arena"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
+
+[[package]]
+name = "idna"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de"
+dependencies = [
+ "idna_adapter",
+ "smallvec",
+ "utf8_iter",
+]
+
+[[package]]
+name = "idna_adapter"
+version = "1.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714"
+dependencies = [
+ "icu_normalizer",
+ "icu_properties",
+]
+
+[[package]]
+name = "if-addrs"
+version = "0.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0a05c691e1fae256cf7013d99dad472dc52d5543322761f83ec8d47eab40d2b"
+dependencies = [
+ "libc",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "if-watch"
+version = "3.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "71c02a5161c313f0cbdbadc511611893584a10a7b6153cb554bdf83ddce99ec2"
+dependencies = [
+ "async-io",
+ "core-foundation",
+ "fnv",
+ "futures",
+ "if-addrs",
+ "ipnet",
+ "log",
+ "netlink-packet-core",
+ "netlink-packet-route",
+ "netlink-proto",
+ "netlink-sys",
+ "rtnetlink",
+ "system-configuration",
+ "tokio",
+ "windows",
+]
+
+[[package]]
+name = "igd-next"
+version = "0.16.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "516893339c97f6011282d5825ac94fc1c7aad5cad26bdc2d0cee068c0bf97f97"
+dependencies = [
+ "async-trait",
+ "attohttpc",
+ "bytes",
+ "futures",
+ "http",
+ "http-body-util",
+ "hyper",
+ "hyper-util",
+ "log",
+ "rand 0.9.4",
+ "tokio",
+ "url",
+ "xmltree",
+]
+
+[[package]]
+name = "indexmap"
+version = "2.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
+dependencies = [
+ "equivalent",
+ "hashbrown 0.17.1",
+ "serde",
+ "serde_core",
+]
+
+[[package]]
+name = "inout"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "ipconfig"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4d40460c0ce33d6ce4b0630ad68ff63d6661961c48b6dba35e5a4d81cfb48222"
+dependencies = [
+ "socket2 0.6.4",
+ "widestring",
+ "windows-registry",
+ "windows-result",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "ipnet"
+version = "2.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2"
+
+[[package]]
+name = "itertools"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
+
+[[package]]
+name = "js-sys"
+version = "0.3.99"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11"
+dependencies = [
+ "cfg-if",
+ "futures-util",
+ "once_cell",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
+
+[[package]]
+name = "leb128fmt"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
+
+[[package]]
+name = "libc"
+version = "0.2.186"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
+
+[[package]]
+name = "libp2p"
+version = "0.56.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce71348bf5838e46449ae240631117b487073d5f347c06d434caddcb91dceb5a"
+dependencies = [
+ "bytes",
+ "either",
+ "futures",
+ "futures-timer",
+ "getrandom 0.2.17",
+ "libp2p-allow-block-list",
+ "libp2p-autonat",
+ "libp2p-connection-limits",
+ "libp2p-core",
+ "libp2p-dcutr",
+ "libp2p-dns",
+ "libp2p-identify",
+ "libp2p-identity",
+ "libp2p-kad",
+ "libp2p-mdns",
+ "libp2p-metrics",
+ "libp2p-noise",
+ "libp2p-quic",
+ "libp2p-relay",
+ "libp2p-swarm",
+ "libp2p-tcp",
+ "libp2p-upnp",
+ "libp2p-yamux",
+ "multiaddr",
+ "pin-project",
+ "rw-stream-sink",
+ "thiserror 2.0.18",
+]
+
+[[package]]
+name = "libp2p-allow-block-list"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d16ccf824ee859ca83df301e1c0205270206223fd4b1f2e512a693e1912a8f4a"
+dependencies = [
+ "libp2p-core",
+ "libp2p-identity",
+ "libp2p-swarm",
+]
+
+[[package]]
+name = "libp2p-autonat"
+version = "0.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fab5e25c49a7d48dac83d95d8f3bac0a290d8a5df717012f6e34ce9886396c0b"
+dependencies = [
+ "async-trait",
+ "asynchronous-codec",
+ "either",
+ "futures",
+ "futures-bounded",
+ "futures-timer",
+ "libp2p-core",
+ "libp2p-identity",
+ "libp2p-request-response",
+ "libp2p-swarm",
+ "quick-protobuf",
+ "quick-protobuf-codec",
+ "rand 0.8.6",
+ "rand_core 0.6.4",
+ "thiserror 2.0.18",
+ "tracing",
+ "web-time",
+]
+
+[[package]]
+name = "libp2p-connection-limits"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a18b8b607cf3bfa2f8c57db9c7d8569a315d5cc0a282e6bfd5ebfc0a9840b2a0"
+dependencies = [
+ "libp2p-core",
+ "libp2p-identity",
+ "libp2p-swarm",
+]
+
+[[package]]
+name = "libp2p-core"
+version = "0.43.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "249128cd37a2199aff30a7675dffa51caf073b51aa612d2f544b19932b9aebca"
+dependencies = [
+ "either",
+ "fnv",
+ "futures",
+ "futures-timer",
+ "libp2p-identity",
+ "multiaddr",
+ "multihash",
+ "multistream-select",
+ "parking_lot",
+ "pin-project",
+ "quick-protobuf",
+ "rand 0.8.6",
+ "rw-stream-sink",
+ "thiserror 2.0.18",
+ "tracing",
+ "unsigned-varint 0.8.0",
+ "web-time",
+]
+
+[[package]]
+name = "libp2p-dcutr"
+version = "0.14.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b4107305e12158af3e66960b6181789c547394c9c9a8696f721521602bfc73a"
+dependencies = [
+ "asynchronous-codec",
+ "either",
+ "futures",
+ "futures-bounded",
+ "futures-timer",
+ "hashlink",
+ "libp2p-core",
+ "libp2p-identity",
+ "libp2p-swarm",
+ "quick-protobuf",
+ "quick-protobuf-codec",
+ "thiserror 2.0.18",
+ "tracing",
+ "web-time",
+]
+
+[[package]]
+name = "libp2p-dns"
+version = "0.44.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b770c1c8476736ca98c578cba4b505104ff8e842c2876b528925f9766379f9a"
+dependencies = [
+ "async-trait",
+ "futures",
+ "hickory-resolver",
+ "libp2p-core",
+ "libp2p-identity",
+ "parking_lot",
+ "smallvec",
+ "tracing",
+]
+
+[[package]]
+name = "libp2p-identify"
+version = "0.47.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ab792a8b68fdef443a62155b01970c81c3aadab5e659621b063ef252a8e65e8"
+dependencies = [
+ "asynchronous-codec",
+ "either",
+ "futures",
+ "futures-bounded",
+ "futures-timer",
+ "libp2p-core",
+ "libp2p-identity",
+ "libp2p-swarm",
+ "quick-protobuf",
+ "quick-protobuf-codec",
+ "smallvec",
+ "thiserror 2.0.18",
+ "tracing",
+]
+
+[[package]]
+name = "libp2p-identity"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9525f3831544f7ae497bde79adf114ef127b0fbbb97edbbf692a80408636421c"
+dependencies = [
+ "bs58",
+ "ed25519-dalek",
+ "hkdf",
+ "multihash",
+ "prost",
+ "rand 0.8.6",
+ "sha2",
+ "thiserror 2.0.18",
+ "tracing",
+ "zeroize",
+]
+
+[[package]]
+name = "libp2p-kad"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13d3fd632a5872ec804d37e7413ceea20588f69d027a0fa3c46f82574f4dee60"
+dependencies = [
+ "asynchronous-codec",
+ "bytes",
+ "either",
+ "fnv",
+ "futures",
+ "futures-bounded",
+ "futures-timer",
+ "libp2p-core",
+ "libp2p-identity",
+ "libp2p-swarm",
+ "quick-protobuf",
+ "quick-protobuf-codec",
+ "rand 0.8.6",
+ "sha2",
+ "smallvec",
+ "thiserror 2.0.18",
+ "tracing",
+ "uint",
+ "web-time",
+]
+
+[[package]]
+name = "libp2p-mdns"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c66872d0f1ffcded2788683f76931be1c52e27f343edb93bc6d0bcd8887be443"
+dependencies = [
+ "futures",
+ "hickory-proto",
+ "if-watch",
+ "libp2p-core",
+ "libp2p-identity",
+ "libp2p-swarm",
+ "rand 0.8.6",
+ "smallvec",
+ "socket2 0.5.10",
+ "tokio",
+ "tracing",
+]
+
+[[package]]
+name = "libp2p-metrics"
+version = "0.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "805a555148522cb3414493a5153451910cb1a146c53ffbf4385708349baf62b7"
+dependencies = [
+ "futures",
+ "libp2p-core",
+ "libp2p-dcutr",
+ "libp2p-identify",
+ "libp2p-identity",
+ "libp2p-kad",
+ "libp2p-relay",
+ "libp2p-swarm",
+ "pin-project",
+ "prometheus-client",
+ "web-time",
+]
+
+[[package]]
+name = "libp2p-noise"
+version = "0.46.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc73eacbe6462a0eb92a6527cac6e63f02026e5407f8831bde8293f19217bfbf"
+dependencies = [
+ "asynchronous-codec",
+ "bytes",
+ "futures",
+ "libp2p-core",
+ "libp2p-identity",
+ "multiaddr",
+ "multihash",
+ "quick-protobuf",
+ "rand 0.8.6",
+ "snow",
+ "static_assertions",
+ "thiserror 2.0.18",
+ "tracing",
+ "x25519-dalek",
+ "zeroize",
+]
+
+[[package]]
+name = "libp2p-quic"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8dc448b2de9f4745784e3751fe8bc6c473d01b8317edd5ababcb0dec803d843f"
+dependencies = [
+ "futures",
+ "futures-timer",
+ "if-watch",
+ "libp2p-core",
+ "libp2p-identity",
+ "libp2p-tls",
+ "quinn",
+ "rand 0.8.6",
+ "ring",
+ "rustls",
+ "socket2 0.5.10",
+ "thiserror 2.0.18",
+ "tokio",
+ "tracing",
+]
+
+[[package]]
+name = "libp2p-relay"
+version = "0.21.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8b9b0392ed623243ad298326b9f806d51191829ac7585cc825c54c6c67b04d9"
+dependencies = [
+ "asynchronous-codec",
+ "bytes",
+ "either",
+ "futures",
+ "futures-bounded",
+ "futures-timer",
+ "libp2p-core",
+ "libp2p-identity",
+ "libp2p-swarm",
+ "quick-protobuf",
+ "quick-protobuf-codec",
+ "rand 0.8.6",
+ "static_assertions",
+ "thiserror 2.0.18",
+ "tracing",
+ "web-time",
+]
+
+[[package]]
+name = "libp2p-request-response"
+version = "0.29.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a9f1cca83488b90102abac7b67d5c36fc65bc02ed47620228af7ed002e6a1478"
+dependencies = [
+ "async-trait",
+ "futures",
+ "futures-bounded",
+ "libp2p-core",
+ "libp2p-identity",
+ "libp2p-swarm",
+ "rand 0.8.6",
+ "smallvec",
+ "tracing",
+]
+
+[[package]]
+name = "libp2p-stream"
+version = "0.4.0-alpha"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d6bd8025c80205ec2810cfb28b02f362ab48a01bee32c50ab5f12761e033464"
+dependencies = [
+ "futures",
+ "libp2p-core",
+ "libp2p-identity",
+ "libp2p-swarm",
+ "rand 0.8.6",
+ "tracing",
+]
+
+[[package]]
+name = "libp2p-swarm"
+version = "0.47.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce88c6c4bf746c8482480345ea3edfd08301f49e026889d1cbccfa1808a9ed9e"
+dependencies = [
+ "either",
+ "fnv",
+ "futures",
+ "futures-timer",
+ "hashlink",
+ "libp2p-core",
+ "libp2p-identity",
+ "libp2p-swarm-derive",
+ "multistream-select",
+ "rand 0.8.6",
+ "smallvec",
+ "tokio",
+ "tracing",
+ "web-time",
+]
+
+[[package]]
+name = "libp2p-swarm-derive"
+version = "0.35.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd297cf53f0cb3dee4d2620bb319ae47ef27c702684309f682bdb7e55a18ae9c"
+dependencies = [
+ "heck",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "libp2p-tcp"
+version = "0.44.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fb6585b9309699f58704ec9ab0bb102eca7a3777170fa91a8678d73ca9cafa93"
+dependencies = [
+ "futures",
+ "futures-timer",
+ "if-watch",
+ "libc",
+ "libp2p-core",
+ "socket2 0.6.4",
+ "tokio",
+ "tracing",
+]
+
+[[package]]
+name = "libp2p-tls"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96ff65a82e35375cbc31ebb99cacbbf28cb6c4fefe26bf13756ddcf708d40080"
+dependencies = [
+ "futures",
+ "futures-rustls",
+ "libp2p-core",
+ "libp2p-identity",
+ "rcgen",
+ "ring",
+ "rustls",
+ "rustls-webpki",
+ "thiserror 2.0.18",
+ "x509-parser",
+ "yasna",
+]
+
+[[package]]
+name = "libp2p-upnp"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4757e65fe69399c1a243bbb90ec1ae5a2114b907467bf09f3575e899815bb8d3"
+dependencies = [
+ "futures",
+ "futures-timer",
+ "igd-next",
+ "libp2p-core",
+ "libp2p-swarm",
+ "tokio",
+ "tracing",
+]
+
+[[package]]
+name = "libp2p-yamux"
+version = "0.47.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f15df094914eb4af272acf9adaa9e287baa269943f32ea348ba29cfb9bfc60d8"
+dependencies = [
+ "either",
+ "futures",
+ "libp2p-core",
+ "thiserror 2.0.18",
+ "tracing",
+ "yamux 0.12.1",
+ "yamux 0.13.10",
+]
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
+
+[[package]]
+name = "litemap"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0"
+
+[[package]]
+name = "lock_api"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
+dependencies = [
+ "scopeguard",
+]
+
+[[package]]
+name = "log"
+version = "0.4.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a"
+
+[[package]]
+name = "lru-slab"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
+
+[[package]]
+name = "match-lookup"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "757aee279b8bdbb9f9e676796fd459e4207a1f986e87886700abf589f5abf771"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "memchr"
+version = "2.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8"
+
+[[package]]
+name = "minimal-lexical"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
+
+[[package]]
+name = "mio"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda"
+dependencies = [
+ "libc",
+ "wasi",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "moka"
+version = "0.12.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "957228ad12042ee839f93c8f257b62b4c0ab5eaae1d4fa60de53b27c9d7c5046"
+dependencies = [
+ "crossbeam-channel",
+ "crossbeam-epoch",
+ "crossbeam-utils",
+ "equivalent",
+ "parking_lot",
+ "portable-atomic",
+ "smallvec",
+ "tagptr",
+ "uuid",
+]
+
+[[package]]
+name = "multiaddr"
+version = "0.18.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fe6351f60b488e04c1d21bc69e56b89cb3f5e8f5d22557d6e8031bdfd79b6961"
+dependencies = [
+ "arrayref",
+ "byteorder",
+ "data-encoding",
+ "libp2p-identity",
+ "multibase",
+ "multihash",
+ "percent-encoding",
+ "serde",
+ "static_assertions",
+ "unsigned-varint 0.8.0",
+ "url",
+]
+
+[[package]]
+name = "multibase"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8694bb4835f452b0e3bb06dbebb1d6fc5385b6ca1caf2e55fd165c042390ec77"
+dependencies = [
+ "base-x",
+ "base256emoji",
+ "data-encoding",
+ "data-encoding-macro",
+]
+
+[[package]]
+name = "multihash"
+version = "0.19.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "577c63b00ad74d57e8c9aa870b5fccebf2fd64a308a5aee9f1bb88e4aea19447"
+dependencies = [
+ "unsigned-varint 0.8.0",
+]
+
+[[package]]
+name = "multistream-select"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea0df8e5eec2298a62b326ee4f0d7fe1a6b90a09dfcf9df37b38f947a8c42f19"
+dependencies = [
+ "bytes",
+ "futures",
+ "log",
+ "pin-project",
+ "smallvec",
+ "unsigned-varint 0.7.2",
+]
+
+[[package]]
+name = "netlink-packet-core"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3463cbb78394cb0141e2c926b93fc2197e473394b761986eca3b9da2c63ae0f4"
+dependencies = [
+ "paste",
+]
+
+[[package]]
+name = "netlink-packet-route"
+version = "0.28.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ce3636fa715e988114552619582b530481fd5ef176a1e5c1bf024077c2c9445"
+dependencies = [
+ "bitflags",
+ "libc",
+ "log",
+ "netlink-packet-core",
+]
+
+[[package]]
+name = "netlink-proto"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b65d130ee111430e47eed7896ea43ca693c387f097dd97376bffafbf25812128"
+dependencies = [
+ "bytes",
+ "futures",
+ "log",
+ "netlink-packet-core",
+ "netlink-sys",
+ "thiserror 2.0.18",
+]
+
+[[package]]
+name = "netlink-sys"
+version = "0.8.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd6c30ed10fa69cc491d491b85cc971f6bdeb8e7367b7cde2ee6cc878d583fae"
+dependencies = [
+ "bytes",
+ "futures-util",
+ "libc",
+ "log",
+ "tokio",
+]
+
+[[package]]
+name = "nix"
+version = "0.30.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
+dependencies = [
+ "bitflags",
+ "cfg-if",
+ "cfg_aliases",
+ "libc",
+]
+
+[[package]]
+name = "nohash-hasher"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451"
+
+[[package]]
+name = "nom"
+version = "7.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
+dependencies = [
+ "memchr",
+ "minimal-lexical",
+]
+
+[[package]]
+name = "num-bigint"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
+dependencies = [
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-conv"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441"
+
+[[package]]
+name = "num-integer"
+version = "0.1.46"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "oid-registry"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7"
+dependencies = [
+ "asn1-rs",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.21.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
+dependencies = [
+ "critical-section",
+ "portable-atomic",
+]
+
+[[package]]
+name = "opaque-debug"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
+
+[[package]]
+name = "parking"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
+
+[[package]]
+name = "parking_lot"
+version = "0.12.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
+dependencies = [
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.9.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall",
+ "smallvec",
+ "windows-link",
+]
+
+[[package]]
+name = "paste"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
+
+[[package]]
+name = "pem"
+version = "3.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be"
+dependencies = [
+ "base64",
+ "serde_core",
+]
+
+[[package]]
+name = "percent-encoding"
+version = "2.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
+
+[[package]]
+name = "pin-project"
+version = "1.1.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924"
+dependencies = [
+ "pin-project-internal",
+]
+
+[[package]]
+name = "pin-project-internal"
+version = "1.1.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
+
+[[package]]
+name = "pkcs8"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
+dependencies = [
+ "der",
+ "spki",
+]
+
+[[package]]
+name = "polling"
+version = "3.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218"
+dependencies = [
+ "cfg-if",
+ "concurrent-queue",
+ "hermit-abi",
+ "pin-project-lite",
+ "rustix",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "poly1305"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf"
+dependencies = [
+ "cpufeatures 0.2.17",
+ "opaque-debug",
+ "universal-hash",
+]
+
+[[package]]
+name = "polyval"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25"
+dependencies = [
+ "cfg-if",
+ "cpufeatures 0.2.17",
+ "opaque-debug",
+ "universal-hash",
+]
+
+[[package]]
+name = "portable-atomic"
+version = "1.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
+
+[[package]]
+name = "postcard"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24"
+dependencies = [
+ "cobs",
+ "embedded-io 0.4.0",
+ "embedded-io 0.6.1",
+ "heapless",
+ "serde",
+]
+
+[[package]]
+name = "potential_utf"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564"
+dependencies = [
+ "zerovec",
+]
+
+[[package]]
+name = "powerfmt"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
+dependencies = [
+ "zerocopy",
+]
+
+[[package]]
+name = "prettyplease"
+version = "0.2.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
+dependencies = [
+ "proc-macro2",
+ "syn",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.106"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "prometheus-client"
+version = "0.23.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf41c1a7c32ed72abe5082fb19505b969095c12da9f5732a4bc9878757fd087c"
+dependencies = [
+ "dtoa",
+ "itoa",
+ "parking_lot",
+ "prometheus-client-derive-encode",
+]
+
+[[package]]
+name = "prometheus-client-derive-encode"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "440f724eba9f6996b75d63681b0a92b06947f1457076d503a4d2e2c8f56442b8"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "prost"
+version = "0.14.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2ea70524a2f82d518bce41317d0fae74151505651af45faf1ffbd6fd33f0568"
+dependencies = [
+ "bytes",
+ "prost-derive",
+]
+
+[[package]]
+name = "prost-derive"
+version = "0.14.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b"
+dependencies = [
+ "anyhow",
+ "itertools",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "quick-protobuf"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d6da84cc204722a989e01ba2f6e1e276e190f22263d0cb6ce8526fcdb0d2e1f"
+dependencies = [
+ "byteorder",
+]
+
+[[package]]
+name = "quick-protobuf-codec"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "15a0580ab32b169745d7a39db2ba969226ca16738931be152a3209b409de2474"
+dependencies = [
+ "asynchronous-codec",
+ "bytes",
+ "quick-protobuf",
+ "thiserror 1.0.69",
+ "unsigned-varint 0.8.0",
+]
+
+[[package]]
+name = "quinn"
+version = "0.11.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20"
+dependencies = [
+ "bytes",
+ "cfg_aliases",
+ "futures-io",
+ "pin-project-lite",
+ "quinn-proto",
+ "quinn-udp",
+ "rustc-hash",
+ "rustls",
+ "socket2 0.6.4",
+ "thiserror 2.0.18",
+ "tokio",
+ "tracing",
+ "web-time",
+]
+
+[[package]]
+name = "quinn-proto"
+version = "0.11.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098"
+dependencies = [
+ "bytes",
+ "getrandom 0.3.4",
+ "lru-slab",
+ "rand 0.9.4",
+ "ring",
+ "rustc-hash",
+ "rustls",
+ "rustls-pki-types",
+ "slab",
+ "thiserror 2.0.18",
+ "tinyvec",
+ "tracing",
+ "web-time",
+]
+
+[[package]]
+name = "quinn-udp"
+version = "0.5.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd"
+dependencies = [
+ "cfg_aliases",
+ "libc",
+ "once_cell",
+ "socket2 0.6.4",
+ "tracing",
+ "windows-sys 0.60.2",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "r-efi"
+version = "5.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
+
+[[package]]
+name = "r-efi"
+version = "6.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
+
+[[package]]
+name = "rand"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a"
+dependencies = [
+ "libc",
+ "rand_chacha 0.3.1",
+ "rand_core 0.6.4",
+]
+
+[[package]]
+name = "rand"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea"
+dependencies = [
+ "rand_chacha 0.9.0",
+ "rand_core 0.9.5",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core 0.6.4",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
+dependencies = [
+ "ppv-lite86",
+ "rand_core 0.9.5",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+dependencies = [
+ "getrandom 0.2.17",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
+dependencies = [
+ "getrandom 0.3.4",
+]
+
+[[package]]
+name = "rcgen"
+version = "0.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75e669e5202259b5314d1ea5397316ad400819437857b90861765f24c4cf80a2"
+dependencies = [
+ "pem",
+ "ring",
+ "rustls-pki-types",
+ "time",
+ "yasna",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.5.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "resolv-conf"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e061d1b48cb8d38042de4ae0a7a6401009d6143dc80d2e2d6f31f0bdd6470c7"
+
+[[package]]
+name = "ring"
+version = "0.17.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
+dependencies = [
+ "cc",
+ "cfg-if",
+ "getrandom 0.2.17",
+ "libc",
+ "untrusted",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "rtnetlink"
+version = "0.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b960d5d873a75b5be9761b1e73b146f52dddcd27bac75263f40fba686d4d7b5"
+dependencies = [
+ "futures-channel",
+ "futures-util",
+ "log",
+ "netlink-packet-core",
+ "netlink-packet-route",
+ "netlink-proto",
+ "netlink-sys",
+ "nix",
+ "thiserror 1.0.69",
+ "tokio",
+]
+
+[[package]]
+name = "rustc-hash"
+version = "2.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe"
+
+[[package]]
+name = "rustc_version"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
+dependencies = [
+ "semver",
+]
+
+[[package]]
+name = "rusticata-macros"
+version = "4.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632"
+dependencies = [
+ "nom",
+]
+
+[[package]]
+name = "rustix"
+version = "1.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
+dependencies = [
+ "bitflags",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "rustls"
+version = "0.23.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b"
+dependencies = [
+ "once_cell",
+ "ring",
+ "rustls-pki-types",
+ "rustls-webpki",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "rustls-pki-types"
+version = "1.14.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9"
+dependencies = [
+ "web-time",
+ "zeroize",
+]
+
+[[package]]
+name = "rustls-webpki"
+version = "0.103.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e"
+dependencies = [
+ "ring",
+ "rustls-pki-types",
+ "untrusted",
+]
+
+[[package]]
+name = "rustversion"
+version = "1.0.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
+
+[[package]]
+name = "rw-stream-sink"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8c9026ff5d2f23da5e45bbc283f156383001bfb09c4e44256d02c1a685fe9a1"
+dependencies = [
+ "futures",
+ "pin-project",
+ "static_assertions",
+]
+
+[[package]]
+name = "scopeguard"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+
+[[package]]
+name = "semver"
+version = "1.0.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
+
+[[package]]
+name = "serde"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
+dependencies = [
+ "serde_core",
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_core"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.150"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
+dependencies = [
+ "itoa",
+ "memchr",
+ "serde",
+ "serde_core",
+ "zmij",
+]
+
+[[package]]
+name = "serde_spanned"
+version = "0.6.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "sha2"
+version = "0.10.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
+dependencies = [
+ "cfg-if",
+ "cpufeatures 0.2.17",
+ "digest",
+]
+
+[[package]]
+name = "shlex"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
+
+[[package]]
+name = "signal-hook-registry"
+version = "1.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
+dependencies = [
+ "errno",
+ "libc",
+]
+
+[[package]]
+name = "signature"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
+dependencies = [
+ "rand_core 0.6.4",
+]
+
+[[package]]
+name = "slab"
+version = "0.4.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
+
+[[package]]
+name = "smallvec"
+version = "1.15.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
+
+[[package]]
+name = "snow"
+version = "0.9.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "850948bee068e713b8ab860fe1adc4d109676ab4c3b621fd8147f06b261f2f85"
+dependencies = [
+ "aes-gcm",
+ "blake2",
+ "chacha20poly1305",
+ "curve25519-dalek",
+ "rand_core 0.6.4",
+ "ring",
+ "rustc_version",
+ "sha2",
+ "subtle",
+]
+
+[[package]]
+name = "socket2"
+version = "0.5.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678"
+dependencies = [
+ "libc",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "socket2"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51"
+dependencies = [
+ "libc",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "spin"
+version = "0.9.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
+dependencies = [
+ "lock_api",
+]
+
+[[package]]
+name = "spki"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
+dependencies = [
+ "base64ct",
+ "der",
+]
+
+[[package]]
+name = "stable_deref_trait"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
+
+[[package]]
+name = "static_assertions"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+
+[[package]]
+name = "subtle"
+version = "2.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
+
+[[package]]
+name = "syn"
+version = "2.0.117"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "synstructure"
+version = "0.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "system-configuration"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b"
+dependencies = [
+ "bitflags",
+ "core-foundation",
+ "system-configuration-sys",
+]
+
+[[package]]
+name = "system-configuration-sys"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "tagptr"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417"
+
+[[package]]
+name = "thiserror"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
+dependencies = [
+ "thiserror-impl 1.0.69",
+]
+
+[[package]]
+name = "thiserror"
+version = "2.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
+dependencies = [
+ "thiserror-impl 2.0.18",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "2.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "time"
+version = "0.3.47"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c"
+dependencies = [
+ "deranged",
+ "itoa",
+ "num-conv",
+ "powerfmt",
+ "serde_core",
+ "time-core",
+ "time-macros",
+]
+
+[[package]]
+name = "time-core"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca"
+
+[[package]]
+name = "time-macros"
+version = "0.2.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215"
+dependencies = [
+ "num-conv",
+ "time-core",
+]
+
+[[package]]
+name = "tinystr"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d"
+dependencies = [
+ "displaydoc",
+ "zerovec",
+]
+
+[[package]]
+name = "tinyvec"
+version = "1.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3"
+dependencies = [
+ "tinyvec_macros",
+]
+
+[[package]]
+name = "tinyvec_macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
+
+[[package]]
+name = "tokio"
+version = "1.52.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe"
+dependencies = [
+ "bytes",
+ "libc",
+ "mio",
+ "parking_lot",
+ "pin-project-lite",
+ "signal-hook-registry",
+ "socket2 0.6.4",
+ "tokio-macros",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "tokio-macros"
+version = "2.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "tokio-util"
+version = "0.7.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "futures-io",
+ "futures-sink",
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
+name = "toml"
+version = "0.8.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
+dependencies = [
+ "serde",
+ "serde_spanned",
+ "toml_datetime",
+ "toml_edit",
+]
+
+[[package]]
+name = "toml_datetime"
+version = "0.6.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "toml_edit"
+version = "0.22.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
+dependencies = [
+ "indexmap",
+ "serde",
+ "serde_spanned",
+ "toml_datetime",
+ "toml_write",
+ "winnow",
+]
+
+[[package]]
+name = "toml_write"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
+
+[[package]]
+name = "tower-service"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
+
+[[package]]
+name = "tracing"
+version = "0.1.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
+dependencies = [
+ "pin-project-lite",
+ "tracing-attributes",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-attributes"
+version = "0.1.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
+dependencies = [
+ "once_cell",
+]
+
+[[package]]
+name = "try-lock"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
+
+[[package]]
+name = "typenum"
+version = "1.20.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
+
+[[package]]
+name = "uint"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "909988d098b2f738727b161a106cfc7cab00c539c2687a8836f8e565976fb53e"
+dependencies = [
+ "byteorder",
+ "crunchy",
+ "hex",
+ "static_assertions",
+]
+
+[[package]]
+name = "ulid"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "470dbf6591da1b39d43c14523b2b469c86879a53e8b758c8e090a470fe7b1fbe"
+dependencies = [
+ "rand 0.9.4",
+ "serde",
+ "web-time",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
+
+[[package]]
+name = "unicode-xid"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
+
+[[package]]
+name = "universal-hash"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
+dependencies = [
+ "crypto-common",
+ "subtle",
+]
+
+[[package]]
+name = "unsigned-varint"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6889a77d49f1f013504cec6bf97a2c730394adedaeb1deb5ea08949a50541105"
+
+[[package]]
+name = "unsigned-varint"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eb066959b24b5196ae73cb057f45598450d2c5f71460e98c49b738086eff9c06"
+
+[[package]]
+name = "untrusted"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
+
+[[package]]
+name = "url"
+version = "2.5.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed"
+dependencies = [
+ "form_urlencoded",
+ "idna",
+ "percent-encoding",
+ "serde",
+]
+
+[[package]]
+name = "utf8_iter"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
+
+[[package]]
+name = "uuid"
+version = "1.23.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d258b83ceec21034727ecee8c382cfa6c3e133699b0742c64571814fb420c9f7"
+dependencies = [
+ "getrandom 0.4.2",
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "version_check"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
+
+[[package]]
+name = "want"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
+dependencies = [
+ "try-lock",
+]
+
+[[package]]
+name = "wasi"
+version = "0.11.1+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
+
+[[package]]
+name = "wasip2"
+version = "1.0.3+wasi-0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6"
+dependencies = [
+ "wit-bindgen 0.57.1",
+]
+
+[[package]]
+name = "wasip3"
+version = "0.4.0+wasi-0.3.0-rc-2026-01-06"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5"
+dependencies = [
+ "wit-bindgen 0.51.0",
+]
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.122"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+ "rustversion",
+ "wasm-bindgen-macro",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.122"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.122"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e"
+dependencies = [
+ "bumpalo",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.122"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "wasm-encoder"
+version = "0.244.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319"
+dependencies = [
+ "leb128fmt",
+ "wasmparser 0.244.0",
+]
+
+[[package]]
+name = "wasm-metadata"
+version = "0.244.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909"
+dependencies = [
+ "anyhow",
+ "indexmap",
+ "wasm-encoder",
+ "wasmparser 0.244.0",
+]
+
+[[package]]
+name = "wasmparser"
+version = "0.230.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "808198a69b5a0535583370a51d459baa14261dfab04800c4864ee9e1a14346ed"
+dependencies = [
+ "bitflags",
+ "indexmap",
+ "semver",
+]
+
+[[package]]
+name = "wasmparser"
+version = "0.244.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
+dependencies = [
+ "bitflags",
+ "hashbrown 0.15.5",
+ "indexmap",
+ "semver",
+]
+
+[[package]]
+name = "web-time"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "widestring"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471"
+
+[[package]]
+name = "windows"
+version = "0.62.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580"
+dependencies = [
+ "windows-collections",
+ "windows-core",
+ "windows-future",
+ "windows-numerics",
+]
+
+[[package]]
+name = "windows-collections"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610"
+dependencies = [
+ "windows-core",
+]
+
+[[package]]
+name = "windows-core"
+version = "0.62.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
+dependencies = [
+ "windows-implement",
+ "windows-interface",
+ "windows-link",
+ "windows-result",
+ "windows-strings",
+]
+
+[[package]]
+name = "windows-future"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb"
+dependencies = [
+ "windows-core",
+ "windows-link",
+ "windows-threading",
+]
+
+[[package]]
+name = "windows-implement"
+version = "0.60.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "windows-interface"
+version = "0.59.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "windows-link"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
+
+[[package]]
+name = "windows-numerics"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26"
+dependencies = [
+ "windows-core",
+ "windows-link",
+]
+
+[[package]]
+name = "windows-registry"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720"
+dependencies = [
+ "windows-link",
+ "windows-result",
+ "windows-strings",
+]
+
+[[package]]
+name = "windows-result"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "windows-strings"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+dependencies = [
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.60.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
+dependencies = [
+ "windows-targets 0.53.5",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.61.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
+dependencies = [
+ "windows_aarch64_gnullvm 0.52.6",
+ "windows_aarch64_msvc 0.52.6",
+ "windows_i686_gnu 0.52.6",
+ "windows_i686_gnullvm 0.52.6",
+ "windows_i686_msvc 0.52.6",
+ "windows_x86_64_gnu 0.52.6",
+ "windows_x86_64_gnullvm 0.52.6",
+ "windows_x86_64_msvc 0.52.6",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.53.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
+dependencies = [
+ "windows-link",
+ "windows_aarch64_gnullvm 0.53.1",
+ "windows_aarch64_msvc 0.53.1",
+ "windows_i686_gnu 0.53.1",
+ "windows_i686_gnullvm 0.53.1",
+ "windows_i686_msvc 0.53.1",
+ "windows_x86_64_gnu 0.53.1",
+ "windows_x86_64_gnullvm 0.53.1",
+ "windows_x86_64_msvc 0.53.1",
+]
+
+[[package]]
+name = "windows-threading"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
+
+[[package]]
+name = "winnow"
+version = "0.7.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "wit-bindgen"
+version = "0.51.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
+dependencies = [
+ "wit-bindgen-rust-macro",
+]
+
+[[package]]
+name = "wit-bindgen"
+version = "0.57.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
+
+[[package]]
+name = "wit-bindgen-core"
+version = "0.51.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc"
+dependencies = [
+ "anyhow",
+ "heck",
+ "wit-parser 0.244.0",
+]
+
+[[package]]
+name = "wit-bindgen-rust"
+version = "0.51.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21"
+dependencies = [
+ "anyhow",
+ "heck",
+ "indexmap",
+ "prettyplease",
+ "syn",
+ "wasm-metadata",
+ "wit-bindgen-core",
+ "wit-component",
+]
+
+[[package]]
+name = "wit-bindgen-rust-macro"
+version = "0.51.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a"
+dependencies = [
+ "anyhow",
+ "prettyplease",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wit-bindgen-core",
+ "wit-bindgen-rust",
+]
+
+[[package]]
+name = "wit-component"
+version = "0.244.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
+dependencies = [
+ "anyhow",
+ "bitflags",
+ "indexmap",
+ "log",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "wasm-encoder",
+ "wasm-metadata",
+ "wasmparser 0.244.0",
+ "wit-parser 0.244.0",
+]
+
+[[package]]
+name = "wit-parser"
+version = "0.230.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "679fde5556495f98079a8e6b9ef8c887f731addaffa3d48194075c1dd5cd611b"
+dependencies = [
+ "anyhow",
+ "id-arena",
+ "indexmap",
+ "log",
+ "semver",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "unicode-xid",
+ "wasmparser 0.230.0",
+]
+
+[[package]]
+name = "wit-parser"
+version = "0.244.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736"
+dependencies = [
+ "anyhow",
+ "id-arena",
+ "indexmap",
+ "log",
+ "semver",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "unicode-xid",
+ "wasmparser 0.244.0",
+]
+
+[[package]]
+name = "writeable"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4"
+
+[[package]]
+name = "x25519-dalek"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277"
+dependencies = [
+ "curve25519-dalek",
+ "rand_core 0.6.4",
+ "serde",
+ "zeroize",
+]
+
+[[package]]
+name = "x509-parser"
+version = "0.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4569f339c0c402346d4a75a9e39cf8dad310e287eef1ff56d4c68e5067f53460"
+dependencies = [
+ "asn1-rs",
+ "data-encoding",
+ "der-parser",
+ "lazy_static",
+ "nom",
+ "oid-registry",
+ "rusticata-macros",
+ "thiserror 2.0.18",
+ "time",
+]
+
+[[package]]
+name = "xml-rs"
+version = "0.8.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f"
+
+[[package]]
+name = "xmltree"
+version = "0.10.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7d8a75eaf6557bb84a65ace8609883db44a29951042ada9b393151532e41fcb"
+dependencies = [
+ "xml-rs",
+]
+
+[[package]]
+name = "yamux"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ed0164ae619f2dc144909a9f082187ebb5893693d8c0196e8085283ccd4b776"
+dependencies = [
+ "futures",
+ "log",
+ "nohash-hasher",
+ "parking_lot",
+ "pin-project",
+ "rand 0.8.6",
+ "static_assertions",
+]
+
+[[package]]
+name = "yamux"
+version = "0.13.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1991f6690292030e31b0144d73f5e8368936c58e45e7068254f7138b23b00672"
+dependencies = [
+ "futures",
+ "log",
+ "nohash-hasher",
+ "parking_lot",
+ "pin-project",
+ "rand 0.9.4",
+ "static_assertions",
+ "web-time",
+]
+
+[[package]]
+name = "yasna"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd"
+dependencies = [
+ "time",
+]
+
+[[package]]
+name = "yoke"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5"
+dependencies = [
+ "stable_deref_trait",
+ "yoke-derive",
+ "zerofrom",
+]
+
+[[package]]
+name = "yoke-derive"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "synstructure",
+]
+
+[[package]]
+name = "zerocopy"
+version = "0.8.50"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b065d4f0e55f82fae73202e189638116a87c55ab6b8e6c2721e13dd9d854ad1"
+dependencies = [
+ "zerocopy-derive",
+]
+
+[[package]]
+name = "zerocopy-derive"
+version = "0.8.50"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b631b19d36a892ab55420c92dbc83ccd79274f25be714855d3074aa71cab639"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "zerofrom"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272"
+dependencies = [
+ "zerofrom-derive",
+]
+
+[[package]]
+name = "zerofrom-derive"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "synstructure",
+]
+
+[[package]]
+name = "zeroize"
+version = "1.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
+dependencies = [
+ "zeroize_derive",
+]
+
+[[package]]
+name = "zeroize_derive"
+version = "1.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "zerotrie"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf"
+dependencies = [
+ "displaydoc",
+ "yoke",
+ "zerofrom",
+]
+
+[[package]]
+name = "zerovec"
+version = "0.11.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239"
+dependencies = [
+ "yoke",
+ "zerofrom",
+ "zerovec-derive",
+]
+
+[[package]]
+name = "zerovec-derive"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "zmij"
+version = "1.0.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..9462437
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,432 @@
+# Cargo.toml raíz STANDALONE de card — front-door sobre Llimphi.
+# Solo el código de card; Llimphi y lo fundacional por git-dep del monorepo gioser.git.
+[workspace]
+resolver = "2"
+members = [
+ "shared/card/card-core",
+ "shared/card/card-net",
+ "shared/card/card-wit",
+]
+
+[workspace.package]
+version = "0.1.0"
+edition = "2021"
+rust-version = "1.80"
+license = "MIT"
+authors = ["Sergio <gerencia@jlsoltech.com>"]
+publish = false
+repository = "https://gitea.gioser.net/sergio/card"
+
+[workspace.dependencies]
+
+# === Registro de apps / menú global ===
+app-bus = { git = "https://gitea.gioser.net/sergio/gioser.git" }
+# === Serialización ===
+serde = { version = "1", features = ["derive"] }
+serde_json = "1"
+lsp-types = "0.97"
+serde-big-array = "0.5"
+postcard = { version = "1", features = ["use-std"] }
+toml = "0.8"
+ron = "0.8"
+bincode = "1"
+base64 = "0.22"
+
+# === Errores ===
+thiserror = "2" # bump uniforme; arje (era 1) puede requerir ajustes menores
+anyhow = "1"
+
+# === Async ===
+tokio = { version = "1", features = ["full"] }
+tokio-util = { version = "0.7", features = ["compat"] }
+async-trait = "0.1"
+futures = "0.3"
+
+# === Observabilidad ===
+tracing = "0.1"
+tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] }
+
+# === Linux primitives (arje) ===
+nix = { version = "0.29", features = ["signal", "process", "sched", "mount", "fs", "socket", "net", "user"] }
+libc = "0.2"
+
+# === IDs / Hash / Crypto ===
+ulid = { version = "1", features = ["serde"] }
+uuid = { version = "1", features = ["v4", "rng-getrandom"] }
+sha2 = "0.10"
+blake3 = "1.5"
+ed25519-dalek = "2"
+aes-gcm = "0.10"
+chacha20poly1305 = "0.10"
+argon2 = "0.5"
+rand = "0.8"
+
+# === WASM (arje) ===
+# wasmi 1.0: unifica la versión con renaser (su kernel ya corre 1.0), para
+# que el ABI WASM del host sea idéntico en Linux y en bare-metal.
+wasmi = "1.0"
+wat = "1"
+
+# === Storage / DB ===
+sled = "0.34"
+rusqlite = { version = "0.31", features = ["bundled", "blob"] }
+
+# === Ingesta de documentos (iniy-ingest: PDF / EPUB) ===
+pdf-extract = "0.7"
+epub = "2.1"
+
+# === Bulk import Wikipedia (iniy-wiki dump) ===
+bzip2 = "0.4"
+
+# === Compresión (minga multi-bundle) ===
+zstd = "0.13"
+
+# === HTTP server (iniy-server) ===
+axum = "0.7"
+tower = "0.5"
+
+# === ANN sobre embeddings (iniy nli --ann) ===
+instant-distance = "0.6"
+
+# === P2P (minga) ===
+libp2p = { version = "0.56", features = ["tokio", "tcp", "noise", "yamux", "macros", "kad", "identify", "relay", "dcutr", "autonat", "mdns"] }
+libp2p-stream = "=0.4.0-alpha"
+libp2p-allow-block-list = "0.6"
+
+# === SSH (ssh, sandokan RemoteEngine, matilda) ===
+russh = "0.54"
+
+# === Math determinista cross-platform (dominium) ===
+libm = "0.2"
+
+# === SMF (takiy-midi) ===
+# midly: parser/emitter SMF tipo 0/1, no_std-friendly, sin allocs en hot path.
+midly = "0.5"
+
+# === Code parsing (minga) ===
+arboard = "3"
+ropey = "1.6"
+tree-sitter = "0.24"
+tree-sitter-rust = "0.23"
+tree-sitter-python = "0.23"
+tree-sitter-typescript = "0.23"
+tree-sitter-javascript = "0.23"
+tree-sitter-go = "0.23"
+
+# === FS notify ===
+notify = "6.1"
+
+# === Grafos (iniy, nakui-core ya lo usa directo en 0.6) ===
+petgraph = "0.6"
+
+# === Image decoding (nahual-image-viewer-llimphi) ===
+# default-features = false: nos quedamos con PNG + JPEG + WebP (lossless).
+# tullpu-render exporta a las tres; AVIF/TIFF/… los habilitamos si una app
+# los pide específicamente.
+image = { version = "0.25", default-features = false, features = ["png", "jpeg", "webp"] }
+
+# === FUSE (minga-vfs) ===
+# default-features = false: prescinde de pkg-config/libfuse-dev en build.
+# El montaje pasa a ser Rust puro (vía el helper `fusermount3` en runtime).
+fuser = { version = "0.15", default-features = false }
+
+# === CLI / auth (minga) ===
+clap = { version = "4", features = ["derive"] }
+rpassword = "7"
+
+# === PAM (auth-core) ===
+pam = "0.8"
+
+# === D-Bus (arje compat) ===
+zbus = { version = "4", default-features = false, features = ["tokio"] }
+
+# === Tests ===
+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 24.
+# vello 0.5 = rasterizador vectorial sobre wgpu 24.
+# taffy 0.9 = motor Flexbox/Grid puro Rust (ya pulled por transitivos, lo alineamos).
+# parley 0.2 = shaping/layout de texto compatible con peniko 0.4 (que vello 0.5 expone).
+wgpu = "24"
+winit = "0.30"
+raw-window-handle = "0.6"
+pollster = "0.4"
+vello = "0.5"
+taffy = "0.9"
+# parley = shaping completo (bidi, ligatures, fallback CJK/emoji vía fontique, line break).
+parley = "0.4"
+# Bucle Elm (input→update→view→layout→raster→present). Lo consumen las apps.
+llimphi-ui = { git = "https://gitea.gioser.net/sergio/llimphi.git" }
+# Paleta semántica compartida por las apps y los widgets.
+llimphi-theme = { git = "https://gitea.gioser.net/sergio/llimphi.git" }
+# Tweens y helpers de animación sobre el bucle Elm.
+llimphi-motion = { git = "https://gitea.gioser.net/sergio/llimphi.git" }
+# Iconos vectoriales (BezPath en grid 24×24) compartidos por todas las apps.
+llimphi-icons = { git = "https://gitea.gioser.net/sergio/llimphi.git" }
+# Widgets reusables sobre llimphi-ui — uno por crate.
+llimphi-widget-app-header = { git = "https://gitea.gioser.net/sergio/llimphi.git" }
+llimphi-widget-banner = { git = "https://gitea.gioser.net/sergio/llimphi.git" }
+llimphi-widget-button = { git = "https://gitea.gioser.net/sergio/llimphi.git" }
+llimphi-widget-card = { git = "https://gitea.gioser.net/sergio/llimphi.git" }
+llimphi-clipboard = { git = "https://gitea.gioser.net/sergio/llimphi.git" }
+llimphi-widget-context-menu = { git = "https://gitea.gioser.net/sergio/llimphi.git" }
+llimphi-widget-edit-menu = { git = "https://gitea.gioser.net/sergio/llimphi.git" }
+llimphi-widget-menubar = { git = "https://gitea.gioser.net/sergio/llimphi.git" }
+llimphi-widget-list = { git = "https://gitea.gioser.net/sergio/llimphi.git" }
+llimphi-widget-grid = { git = "https://gitea.gioser.net/sergio/llimphi.git" }
+llimphi-widget-slider = { git = "https://gitea.gioser.net/sergio/llimphi.git" }
+llimphi-widget-scroll = { git = "https://gitea.gioser.net/sergio/llimphi.git" }
+llimphi-widget-splitter = { git = "https://gitea.gioser.net/sergio/llimphi.git" }
+llimphi-widget-stat-card = { git = "https://gitea.gioser.net/sergio/llimphi.git" }
+llimphi-widget-tabs = { git = "https://gitea.gioser.net/sergio/llimphi.git" }
+llimphi-module-command-palette = { git = "https://gitea.gioser.net/sergio/llimphi.git" }
+llimphi-module-diff-viewer = { git = "https://gitea.gioser.net/sergio/llimphi.git" }
+llimphi-module-fif = { git = "https://gitea.gioser.net/sergio/llimphi.git" }
+llimphi-module-file-picker = { git = "https://gitea.gioser.net/sergio/llimphi.git" }
+llimphi-module-bookmarks = { git = "https://gitea.gioser.net/sergio/llimphi.git" }
+llimphi-module-mini-map = { git = "https://gitea.gioser.net/sergio/llimphi.git" }
+llimphi-module-shuma-term = { git = "https://gitea.gioser.net/sergio/llimphi.git" }
+llimphi-module-symbol-outline = { git = "https://gitea.gioser.net/sergio/llimphi.git" }
+llimphi-plugin-host = { git = "https://gitea.gioser.net/sergio/llimphi.git" }
+llimphi-widget-theme-switcher = { git = "https://gitea.gioser.net/sergio/llimphi.git" }
+llimphi-widget-text-area = { git = "https://gitea.gioser.net/sergio/llimphi.git" }
+llimphi-widget-text-editor-core = { git = "https://gitea.gioser.net/sergio/llimphi.git" }
+llimphi-widget-text-editor = { git = "https://gitea.gioser.net/sergio/llimphi.git" }
+llimphi-widget-text-editor-lsp = { git = "https://gitea.gioser.net/sergio/llimphi.git" }
+llimphi-widget-text-input = { git = "https://gitea.gioser.net/sergio/llimphi.git" }
+llimphi-widget-tiled = { git = "https://gitea.gioser.net/sergio/llimphi.git" }
+llimphi-widget-nodegraph = { git = "https://gitea.gioser.net/sergio/llimphi.git" }
+llimphi-widget-tree = { git = "https://gitea.gioser.net/sergio/llimphi.git" }
+llimphi-widget-navigator = { git = "https://gitea.gioser.net/sergio/llimphi.git" }
+# Sello vectorial wawa (rombo + W implícita + Merkle Core).
+llimphi-widget-wawa-mark = { git = "https://gitea.gioser.net/sergio/llimphi.git" }
+# Widgets de elegancia transversal (tooltip, spinner, progress, toast,
+# modal, empty, status-bar, shortcuts-help, splash).
+llimphi-widget-tooltip = { git = "https://gitea.gioser.net/sergio/llimphi.git" }
+llimphi-widget-spinner = { git = "https://gitea.gioser.net/sergio/llimphi.git" }
+llimphi-widget-progress = { git = "https://gitea.gioser.net/sergio/llimphi.git" }
+llimphi-widget-toast = { git = "https://gitea.gioser.net/sergio/llimphi.git" }
+llimphi-widget-modal = { git = "https://gitea.gioser.net/sergio/llimphi.git" }
+llimphi-widget-empty = { git = "https://gitea.gioser.net/sergio/llimphi.git" }
+llimphi-widget-status-bar = { git = "https://gitea.gioser.net/sergio/llimphi.git" }
+llimphi-widget-shortcuts-help = { git = "https://gitea.gioser.net/sergio/llimphi.git" }
+llimphi-widget-timeline = { git = "https://gitea.gioser.net/sergio/llimphi.git" }
+llimphi-widget-splash = { git = "https://gitea.gioser.net/sergio/llimphi.git" }
+# Controles de formulario y signaling (switch, segmented, breadcrumb,
+# badge, avatar, skeleton, field).
+llimphi-widget-switch = { git = "https://gitea.gioser.net/sergio/llimphi.git" }
+llimphi-widget-segmented = { git = "https://gitea.gioser.net/sergio/llimphi.git" }
+llimphi-widget-dock-rail = { git = "https://gitea.gioser.net/sergio/llimphi.git" }
+llimphi-widget-breadcrumb = { git = "https://gitea.gioser.net/sergio/llimphi.git" }
+llimphi-widget-badge = { git = "https://gitea.gioser.net/sergio/llimphi.git" }
+llimphi-widget-avatar = { git = "https://gitea.gioser.net/sergio/llimphi.git" }
+llimphi-widget-skeleton = { git = "https://gitea.gioser.net/sergio/llimphi.git" }
+llimphi-widget-field = { git = "https://gitea.gioser.net/sergio/llimphi.git" }
+# Firma visual transversal (gradient sutil + hairline accent).
+llimphi-widget-panel = { git = "https://gitea.gioser.net/sergio/llimphi.git" }
+llimphi-widget-panes = { git = "https://gitea.gioser.net/sergio/llimphi.git" }
+llimphi-workspace = { git = "https://gitea.gioser.net/sergio/llimphi.git" }
+# Abstracción Selector — host (paths) + wawa (khipus).
+llimphi-module-selector = { git = "https://gitea.gioser.net/sergio/llimphi.git" }
+
+# === Filesystem helpers ===
+directories = "5"
+
+# === Diff line-based (llimphi-module-diff-viewer) ===
+# `similar` es la crate de facto: implementa Myers + Patience + LCS,
+# expone `TextDiff` con ChangeTag por línea (Equal/Insert/Delete),
+# zero deps fuera de std. La 2.x es estable hace años.
+similar = "2"
+
+# === Fuzzy matching (shuma-history) ===
+# nucleo-matcher = mismo matcher que helix-editor: rápido, Unicode-correct,
+# bonus por prefijos, ranking estable. La versión 0.3 expone el API simple
+# que necesitamos (Matcher + Pattern + score).
+nucleo-matcher = "0.3"
+
+# === Transporte autenticado (shuma-link) ===
+# snow = framework Noise pure-rust. Lo usamos en modo Noise_XK (cliente
+# conoce la pubkey del servidor, server descubre la del cliente y la
+# valida contra una allowlist). ChaCha20-Poly1305 + X25519 + BLAKE2s.
+# La versión 0.9 viene pinneada por libp2p, así nos alineamos.
+snow = "0.9"
+hex = "0.4"
+
+# === PTY + emulador de terminal (shuma-exec, módulos REPL) ===
+# portable-pty aloja un PTY cross-platform; lo usamos para los
+# comandos TUI tipo vim/htop/less que necesitan un terminal de verdad.
+# vt100 parsea la secuencia de bytes que el PTY emite (ANSI + cursor
+# movement + erase + screen state) y mantiene un buffer de pantalla
+# renderizable como grid.
+portable-pty = "0.9"
+vt100 = "0.16"
+
+# === WASM web (gioser) ===
+wasm-bindgen = "0.2"
+wasm-bindgen-futures = "0.4"
+js-sys = "0.3"
+web-sys = "0.3"
+glam = "0.30"
+
+# === Markdown (pluma) ===
+pulldown-cmark = { version = "0.12", default-features = false, features = ["html"] }
+
+# === Archivos comprimidos (nahual archive viewer) ===
+# Sólo listamos el directorio central (nombres/tamaños); no descomprimimos,
+# por eso default-features=false alcanza para ZIP. Para tar.gz sí
+# descomprimimos en streaming con flate2 (ya declarado arriba), saltando
+# los datos de cada entrada — sólo leemos headers.
+zip = { version = "2.4", default-features = false }
+tar = { version = "0.4", default-features = false }
+
+# === Fuentes (nahual font viewer) ===
+# Parseo de TTF/OTF/TTC y extracción de contornos de glifo a paths.
+ttf-parser = "0.25"
+
+# ============================================================
+# Intra-workspace deps de nahual (referenciadas por workspace = true)
+# ============================================================
+nahual-text-viewer-llimphi = { git = "https://gitea.gioser.net/sergio/gioser.git" }
+nahual-image-viewer-llimphi = { git = "https://gitea.gioser.net/sergio/gioser.git" }
+nahual-thumb-core = { git = "https://gitea.gioser.net/sergio/gioser.git" }
+nahual-gallery-llimphi = { git = "https://gitea.gioser.net/sergio/gioser.git" }
+nahual-video-viewer-llimphi = { git = "https://gitea.gioser.net/sergio/gioser.git" }
+nahual-card-viewer-llimphi = { git = "https://gitea.gioser.net/sergio/gioser.git" }
+nahual-audio-viewer-llimphi = { git = "https://gitea.gioser.net/sergio/gioser.git" }
+nahual-tree-viewer-llimphi = { git = "https://gitea.gioser.net/sergio/gioser.git" }
+nahual-hex-viewer-llimphi = { git = "https://gitea.gioser.net/sergio/gioser.git" }
+nahual-table-viewer-llimphi = { git = "https://gitea.gioser.net/sergio/gioser.git" }
+nahual-markdown-viewer-llimphi = { git = "https://gitea.gioser.net/sergio/gioser.git" }
+nahual-archive-viewer-llimphi = { git = "https://gitea.gioser.net/sergio/gioser.git" }
+nahual-font-viewer-llimphi = { git = "https://gitea.gioser.net/sergio/gioser.git" }
+nahual-map-viewer-llimphi = { git = "https://gitea.gioser.net/sergio/gioser.git" }
+nahual-geo-core = { git = "https://gitea.gioser.net/sergio/gioser.git" }
+nahual-viewer-core = { git = "https://gitea.gioser.net/sergio/gioser.git" }
+nahual-file-explorer-llimphi = { git = "https://gitea.gioser.net/sergio/gioser.git" }
+
+# ============================================================
+# Intra-workspace deps de pineal (módulo de gráficos)
+# ============================================================
+pineal-core = { git = "https://gitea.gioser.net/sergio/gioser.git" }
+pineal-render = { git = "https://gitea.gioser.net/sergio/gioser.git" }
+pineal-cartesian = { git = "https://gitea.gioser.net/sergio/gioser.git" }
+pineal-stream = { git = "https://gitea.gioser.net/sergio/gioser.git" }
+pineal-mesh = { git = "https://gitea.gioser.net/sergio/gioser.git" }
+pineal-financial = { git = "https://gitea.gioser.net/sergio/gioser.git" }
+pineal-polar = { git = "https://gitea.gioser.net/sergio/gioser.git" }
+pineal-heatmap = { git = "https://gitea.gioser.net/sergio/gioser.git" }
+pineal-treemap = { git = "https://gitea.gioser.net/sergio/gioser.git" }
+pineal-flow = { git = "https://gitea.gioser.net/sergio/gioser.git" }
+pineal-phosphor = { git = "https://gitea.gioser.net/sergio/gioser.git" }
+pineal-export = { git = "https://gitea.gioser.net/sergio/gioser.git" }
+pineal-hexbin = { git = "https://gitea.gioser.net/sergio/gioser.git" }
+pineal-contour = { git = "https://gitea.gioser.net/sergio/gioser.git" }
+pineal-bars = { git = "https://gitea.gioser.net/sergio/gioser.git" }
+pineal = { git = "https://gitea.gioser.net/sergio/gioser.git" }
+
+# ============================================================
+# Intra-workspace deps de iniy (laboratorio semántico de creencias)
+# ============================================================
+iniy-core = { git = "https://gitea.gioser.net/sergio/gioser.git" }
+iniy-ingest = { git = "https://gitea.gioser.net/sergio/gioser.git" }
+iniy-extract = { git = "https://gitea.gioser.net/sergio/gioser.git" }
+iniy-nli = { git = "https://gitea.gioser.net/sergio/gioser.git" }
+iniy-nli-llm = { git = "https://gitea.gioser.net/sergio/gioser.git" }
+iniy-graph = { git = "https://gitea.gioser.net/sergio/gioser.git" }
+iniy-store = { git = "https://gitea.gioser.net/sergio/gioser.git" }
+
+# === auto: declarados por crates internos faltantes ===
+cosmos-coords = { git = "https://gitea.gioser.net/sergio/gioser.git" }
+cosmos-core = { git = "https://gitea.gioser.net/sergio/gioser.git" }
+cosmos-ephemeris = { git = "https://gitea.gioser.net/sergio/gioser.git" }
+cosmos-time = { git = "https://gitea.gioser.net/sergio/gioser.git" }
+cosmos-wcs = { git = "https://gitea.gioser.net/sergio/gioser.git" }
+
+# === auto: externas de eternal ===
+celestial-eop-data = { version = "0.1"}
+approx = "0.5"
+byteorder = "1.5"
+cc = "1.0"
+chrono = "0.4"
+crc32fast = "1.4"
+criterion = "0.5"
+csv = "1.4"
+flate2 = "1.0"
+glob = "0.3"
+indicatif = "0.18"
+lz4_flex = "0.11"
+memmap2 = "0.9"
+mockito = "1.0"
+ndarray = "0.15"
+num-traits = "0.2"
+once_cell = "1.19"
+parking_lot = "0.12"
+png = "0.18"
+proptest = "1.4"
+quick-xml = "0.31"
+rayon = "1.8"
+regex = "1.11"
+reqwest = "0.12"
+tiff = "0.11"
+wide = "0.7"
+wiremock = "0.6"
+
+# === i18n (rimay-localize) ===
+fluent-bundle = "0.15"
+unic-langid = { version = "0.9", features = ["macros"] }
+sys-locale = "0.3"
+
+# === Servo (puriy-engine) ===
+# Crates publicados de Servo embebibles individualmente. html5ever/markup5ever
+# ya entran via ammonia→surrealdb→nakui, así que alineamos versión para no
+# duplicar el árbol. markup5ever_rcdom es el DOM Rc-based simple (suficiente
+# para Fase 2: parsear y renderizar, sin scripting). cssparser es el tokenizer
+# CSS de Stylo, sirve para inline styles. ureq = HTTP síncrono minimalista,
+# evita pull de tokio en el engine.
+html5ever = "0.39"
+markup5ever = "0.39"
+markup5ever_rcdom = "0.39"
+cssparser = "0.35"
+url = "2"
+ureq = { version = "2", default-features = false, features = ["tls"] }
+
+# === takiy-synth (SoundFont MIDI) ===
+# rustysynth = sintetizador SF2 puro Rust, MIT. Reemplaza el oscilador
+# feo de takiy-synth por muestras reales (FluidR3, GeneralUser GS, etc).
+rustysynth = "1.3"
+
+# === takiy-playback (audio device output) ===
+# cpal = backend de audio cross-platform (ALSA/PulseAudio/Pipewire en
+# Linux, WASAPI en Windows, CoreAudio en macOS). Lo usamos sólo para
+# abrir el device default y empujar muestras f32 — nada de mezclado
+# ni efectos en el callback.
+cpal = "0.15"
+
+# === media-source-wav (decoder PCM en disco) ===
+# hound = lector/escritor WAV puro-Rust, sin deps nativas. Soporta PCM
+# entero (8/16/24/32) y float (32). Suficiente para abrir samples y
+# stems de prueba sin meter ffmpeg/symphonia.
+hound = "3.5"
+
+# === media-source-{mp3,flac,vorbis} (decoders vía symphonia) ===
+# symphonia es una colección de decoders puro-Rust mantenida. `mp3` cubre
+# media-source-mp3; `flac` (decoder + demuxer FLAC nativo) cubre
+# media-source-flac (lossless); `vorbis` + `ogg` (codec + demuxer Ogg)
+# cubren media-source-vorbis (lossy clásico, libre de patentes). Sin aac:
+# ese tier patentado entra por shared/foreign-av.
+symphonia = { version = "0.5", default-features = false, features = ["mp3", "flac", "vorbis", "ogg"] }
+
+# === media-source-opus (decoder Opus NATIVO puro-Rust) ===
+# Opus es el formato de audio nativo de gioser (par del video AV1). ogg
+# demuxea las páginas Ogg; opus-wave es un port puro-Rust de libopus
+# (SILK+CELT, sin C ni FFI) — par del rav1d del lado video.
+ogg = "0.9"
+opus-wave = "3"
+
+# === media-source-webm (demux nativo Matroska/WebM) ===
+# matroska-demuxer es un demuxer puro-Rust de MKV/WebM (EBML). Saca los
+# paquetes de los tracks V_AV1 y A_OPUS para alimentar a media-source-av1
+# y media-source-opus — un .webm AV1+Opus se reproduce 100% nativo.
+matroska-demuxer = "0.7"
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..ede9631
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2026 Sergio
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..4149fb7
--- /dev/null
+++ b/README.md
@@ -0,0 +1,18 @@
+# card
+
+> The canonical **presentation card** — sovereign identity + typed flows, in Rust.
+
+`card` is the suite's identity primitive: a content-addressed *Tarjeta de Presentación* that binds an `arje` identity to typed `brahman` flows. It is the leaf that the networking and desktop layers build on — peers, channels and capabilities are all expressed in terms of cards.
+
+## Crates
+
+- **`card-core`** — the canonical card type (identity + typed flows), content-addressed.
+- **`card-wit`** — WIT-typed flow descriptions (component-model interfaces).
+
+## How dependencies work
+
+A true leaf: `card` has **no internal dependencies** — only crates.io. It compiles standalone and is git-depended by the rest of the suite (networking via `chasqui`/`minga`, the desktop, etc.).
+
+## License
+
+MIT.
diff --git a/shared/card/README.md b/shared/card/README.md
new file mode 100644
index 0000000..d06e224
--- /dev/null
+++ b/shared/card/README.md
@@ -0,0 +1,36 @@
+# card — identidad agnóstica de transporte
+
+Contrato de identidad y membresía **independiente del transporte**: claves
+Ed25519, `EspinaId` (hash de la clave pública), firmas y handshake de membresía
+de una "espina" (red privada). El mismo contrato lo implementan dos transportes
+distintos: `card-net` (libp2p) y `wawa-akasha` (protocolo propio de wawa).
+
+## Subcrates
+
+- **`card-core`** — el contrato agnóstico real: par Ed25519 → `EspinaId`,
+ `Card` firmada, `handshake` de membresía (un miembro presenta su tarjeta
+ firmada; el anfitrión la verifica contra la raíz de confianza de la espina).
+ Sólo bytes firmados — no sabe de libp2p ni Akasha.
+- **`card-net`** — espina dorsal P2P sobre **libp2p**: discovery (mDNS +
+ Kademlia DHT), gossipsub, NAT traversal (Circuit Relay v2 + DCUtR + AutoNAT).
+ Implementa el contrato de `card-core` sobre libp2p. Lo consume `khipu`.
+- **`card-wit`** — **[DORMIDO]** binding WIT/wasm del contrato. Se reactiva
+ cuando `card` cruce a apps WASM; hoy el contrato real es `card-core`.
+
+## Estado (2026-05-31)
+
+### Hecho
+- `card-core`: identidad Ed25519 + `EspinaId` + handshake de membresía (contrato
+ agnóstico declarado como la fuente de verdad).
+- `card-net`: discovery (mDNS+DHT), gossipsub y NAT traversal completo
+ (Relay v2 + DCUtR + AutoNAT); discovery de personas por `DhtKey::Persona`.
+
+### Pendiente
+- `card-wit`: dormido — binding WASM pendiente de reactivación.
+- Espejo del contrato sobre `wawa-akasha` (transporte wawa) aún por cablear.
+- Endurecer revocación / rotación de membresía de espina.
+
+## Lugar en el repo
+
+`shared/card` — contrato de identidad. `card-net` lo lleva a libp2p (khipu);
+`agora` cubre firma/confianza de más alto nivel.
diff --git a/shared/card/card-core/Cargo.toml b/shared/card/card-core/Cargo.toml
new file mode 100644
index 0000000..b012cde
--- /dev/null
+++ b/shared/card/card-core/Cargo.toml
@@ -0,0 +1,19 @@
+[package]
+name = "card-core"
+version.workspace = true
+edition.workspace = true
+rust-version.workspace = true
+license.workspace = true
+authors.workspace = true
+publish.workspace = true
+description = "Brahman — Tarjeta de Presentación canónica (identidad arje + flujos tipados brahman)."
+
+[dependencies]
+serde = { workspace = true }
+serde_json = { workspace = true }
+toml = { workspace = true }
+thiserror = { workspace = true }
+ulid = { workspace = true }
+
+[dev-dependencies]
+postcard = { workspace = true }
diff --git a/shared/card/card-core/src/lib.rs b/shared/card/card-core/src/lib.rs
new file mode 100644
index 0000000..1861d8d
--- /dev/null
+++ b/shared/card/card-core/src/lib.rs
@@ -0,0 +1,1290 @@
+//! `brahman-card` — Tarjeta de Presentación canónica de Brahman.
+//!
+//! Híbrida del `EntityCard` de arje (identidad ULID, capacidades tipadas,
+//! `Payload`/`SomaSpec`/`Supervision`/`genesis` recursivo) con flujos tipados,
+//! permisos enumerados explícitos y nivel de confianza derivado del modelo
+//! que veníamos diseñando en `core_protocol`. Una sola tarjeta sirve a:
+//!
+//! - **El Init** (encarnación): `payload` + `soma` + `supervision` + `genesis`.
+//! - **El Admin** (matching): `provides`/`requires` + `flow` + `permissions`.
+//! - **El runtime** (sandbox): `permissions` enumerados → seccomp / namespaces.
+//!
+//! Forward-compat: cualquier campo desconocido se preserva en `extensions`
+//! (raíz) o en `extra` (sub-secciones).
+//!
+//! Formatos soportados: JSON (canónico, compatible con arje) y TOML
+//! (humano-legible). Auto-detección por extensión.
+
+#![forbid(unsafe_code)]
+#![warn(rust_2018_idioms)]
+
+use std::collections::{BTreeMap, BTreeSet, HashSet};
+use std::path::{Path, PathBuf};
+use std::time::Duration;
+
+use serde::{Deserialize, Serialize};
+use serde_json::Value as JsonValue;
+use thiserror::Error;
+use ulid::Ulid;
+
+// Re-export para que los consumidores no necesiten depender de `ulid`
+// directamente.
+pub use ::ulid;
+
+/// Versión del esquema de la Card.
+pub const CARD_SCHEMA_VERSION: u16 = 1;
+
+/// Versión del protocolo Brahman.
+pub const PROTOCOL_VERSION: &str = "0.1.0";
+
+/// Errores de parseo o validación de la Card.
+#[derive(Debug, Error)]
+pub enum CardError {
+ #[error("schema version mismatch: got {got}, expected {expected}")]
+ SchemaMismatch { got: u16, expected: u16 },
+ #[error("label vacío")]
+ EmptyLabel,
+ #[error("label demasiado largo: {0} bytes (máx 256)")]
+ LabelTooLong(usize),
+ #[error("capacidad presente en provides Y requires: {0:?}")]
+ SelfDependency(Capability),
+ #[error("payload Native/Legacy con exec vacío")]
+ EmptyExec,
+ #[error("payload Wasm con sha256 sentinela (todo ceros)")]
+ SentinelWasmHash,
+ #[error("rlimit inválido: {0}")]
+ InvalidRlimit(&'static str),
+ #[error("cgroup weight fuera de [1,10000]: {0}")]
+ InvalidCgroupWeight(&'static str),
+ #[error("flujo {section}: nombre duplicado '{name}'")]
+ DuplicateFlowName {
+ section: &'static str,
+ name: String,
+ },
+ #[error("JSON inválido: {0}")]
+ Json(#[from] serde_json::Error),
+ #[error("TOML inválido: {0}")]
+ Toml(#[from] toml::de::Error),
+ #[error("E/S leyendo card: {0}")]
+ Io(#[from] std::io::Error),
+ #[error("format desconocido (extensiones esperadas: .json, .toml)")]
+ UnknownFormat,
+}
+
+// =====================================================================
+// Card raíz
+// =====================================================================
+
+/// Tarjeta de Presentación de un módulo Brahman.
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct Card {
+ /// Versión del esquema. Cambiar = romper compatibilidad del fractal.
+ pub schema_version: u16,
+
+ /// Identidad opaca, única en el grafo del fractal.
+ pub id: Ulid,
+
+ /// Ancestro del que esta Card desciende (genealogía).
+ #[serde(default)]
+ pub lineage: Option<Ulid>,
+
+ /// Nombre humano-legible. Único por convención, no por validación.
+ pub label: String,
+
+ /// Capacidades del sistema que esta Card ofrece a otros.
+ #[serde(default)]
+ pub provides: BTreeSet<Capability>,
+
+ /// Capacidades que necesita resolver el Init antes de encarnarla.
+ #[serde(default)]
+ pub requires: BTreeSet<Capability>,
+
+ /// Permisos sandbox declarativos (más alto nivel que `Capability`).
+ /// El Admin los compila a seccomp/namespaces/cgroups concretos.
+ #[serde(default)]
+ pub permissions: Permissions,
+
+ /// Spec runtime Linux (namespaces, cgroups, rlimits, cpu_affinity).
+ #[serde(default)]
+ pub soma: SomaSpec,
+
+ /// Qué encarnar: WASM, ELF nativo, virtual, o legacy con shims.
+ pub payload: Payload,
+
+ /// Política de supervisión (restart con backoff, oneshot, delegada).
+ pub supervision: Supervision,
+
+ /// Modelo de ejecución (eje ortogonal a `supervision`).
+ #[serde(default)]
+ pub lifecycle: Lifecycle,
+
+ /// Prioridad de scheduling.
+ #[serde(default)]
+ pub priority: Priority,
+
+ /// Contratos de flujo de datos: qué consume, qué produce.
+ #[serde(default)]
+ pub flow: Flows,
+
+ /// Si la entidad expone un socket Unix de servicio (data plane,
+ /// distinto al socket del Init), declara aquí su path. Los
+ /// consumidores que reciban un `MatchEvent` con este Card como
+ /// productor pueden conectar directo al socket sin discovery
+ /// adicional.
+ #[serde(default)]
+ pub service_socket: Option<PathBuf>,
+
+ /// Referencias a otras Cards: "soy procesado por X", "poseo Y",
+ /// etc. Forma el grafo de relaciones del fractal. Cada Card las
+ /// declara unilateralmente; los consumidores pueden cruzarlas para
+ /// reconstruir vínculos bidireccionales.
+ #[serde(default)]
+ pub references: Vec<CardReference>,
+
+ /// Naturaleza de la entidad detrás de la Card. Por defecto `Ente`
+ /// para mantener compatibilidad con Cards existentes.
+ #[serde(default)]
+ pub kind: CardKind,
+
+ /// Faceta de datos cuando `kind != Ente`. `None` para entes
+ /// runtime; `Some(...)` para Mónadas, índices, etc.
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ pub data: Option<DataFacet>,
+
+ /// Hijas a instanciar inmediatamente al encarnar esta Card.
+ #[serde(default)]
+ pub genesis: Vec<Card>,
+
+ /// Biases per-contexto. La key es el nombre del contexto (p. ej.
+ /// `"test"`, `"prod"`, `"foreground"`). Cuando el broker está
+ /// configurado bajo ese contexto, el bias se aplica. Sin contexto
+ /// activo o sin entrada matching, este campo no afecta el ranking.
+ #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
+ pub priority_contexts: BTreeMap<String, ContextBias>,
+
+ /// Campos JSON/TOML desconocidos preservados durante I/O de archivos
+ /// (forward-compat). **No se transmiten por wire (postcard)** — la
+ /// proyección a [`WireCard`] los descarta porque `serde_json::Value`
+ /// no es postcard-friendly. Sirven para anotaciones locales que
+ /// sobreviven leer/escribir Cards en disco.
+ #[serde(flatten, default, skip_serializing_if = "BTreeMap::is_empty")]
+ pub extensions: BTreeMap<String, JsonValue>,
+}
+
+impl Default for Card {
+ /// Default determinista pensado para el patrón `..Default::default()`
+ /// en struct-literals donde el caller sobreescribe `id` y `label`.
+ ///
+ /// **Trap conocida**: `id` queda en `Ulid::nil()`. Si construís una
+ /// Card "viva" para registrar en el broker, NUNCA dejes el `id`
+ /// derivado de `Default` — todas las Cards default-construidas
+ /// colisionarían bajo el mismo `00000000000000000000000000`. Para
+ /// Cards frescas usá [`Card::new`], que asigna `Ulid::new()`.
+ /// `Ulid::nil()` queda reservado para patterns de búsqueda y
+ /// sentinel values en serialización.
+ fn default() -> Self {
+ Self {
+ schema_version: CARD_SCHEMA_VERSION,
+ id: Ulid::nil(),
+ lineage: None,
+ label: String::new(),
+ provides: BTreeSet::new(),
+ requires: BTreeSet::new(),
+ permissions: Permissions::default(),
+ soma: SomaSpec::default(),
+ payload: Payload::Virtual,
+ supervision: Supervision::OneShot,
+ lifecycle: Lifecycle::default(),
+ priority: Priority::default(),
+ flow: Flows::default(),
+ genesis: Vec::new(),
+ service_socket: None,
+ references: Vec::new(),
+ kind: CardKind::default(),
+ data: None,
+ priority_contexts: BTreeMap::new(),
+ extensions: BTreeMap::new(),
+ }
+ }
+}
+
+// =====================================================================
+// Capacidades — heredadas de arje, tipadas, no strings
+// =====================================================================
+
+/// Capacidad del sistema. Identificadores tipados, no strings libres.
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
+pub enum Capability {
+ /// Provee un punto de montaje root para Cards hijas.
+ FilesystemRoot,
+ /// Acceso a una familia netlink del kernel.
+ KernelNetlink(NetlinkFamily),
+ /// Endpoint del bus interno — equivalente tipado de un nombre D-Bus.
+ Endpoint {
+ interface: InterfaceId,
+ version: u16,
+ },
+ /// Reemplazo del shim de systemd-logind. Solo el ente compat lo provee.
+ LegacyLogind,
+ /// Acceso crudo a una clase de dispositivo. Capacidad escalada.
+ Device { class: DeviceClass },
+ /// Permiso de instanciar Cards hijas. Por defecto solo PID 1 lo tiene.
+ Spawn,
+ /// Acceso a logging estructurado del fractal.
+ Journal,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
+pub enum NetlinkFamily {
+ Uevent,
+ Route,
+ Generic,
+ Audit,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
+pub enum DeviceClass {
+ Block,
+ Tty,
+ Input,
+ Drm,
+ Net,
+ Hidraw,
+}
+
+/// Identificador de interfaz del bus interno (UUID, no string libre).
+/// Para extender el protocolo, se genera un UUID nuevo y se versiona.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
+pub struct InterfaceId(pub [u8; 16]);
+
+// =====================================================================
+// Permisos sandbox — más alto nivel que Capability
+// =====================================================================
+
+/// Permisos declarativos. El Admin los traduce a seccomp/namespaces.
+#[derive(Debug, Clone, Default, Serialize, Deserialize)]
+pub struct Permissions {
+ #[serde(default)]
+ pub networking: NetworkingPolicy,
+ #[serde(default)]
+ pub filesystem: FsPolicy,
+ #[serde(default)]
+ pub ipc: IpcPolicy,
+ /// Capacidad de spawnear sub-procesos. Implica `TrustLevel::System`.
+ #[serde(default)]
+ pub processes: bool,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
+#[serde(rename_all = "kebab-case")]
+pub enum NetworkingPolicy {
+ #[default]
+ None,
+ Loopback,
+ Outbound,
+ Full,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
+#[serde(rename_all = "kebab-case")]
+pub enum FsPolicy {
+ #[default]
+ None,
+ ReadOnly,
+ ReadWrite,
+}
+
+#[derive(Debug, Clone, Default, Serialize, Deserialize)]
+pub struct IpcPolicy {
+ /// Protocolos IPC permitidos (p. ej. `"wit-v1"`, `"shm-v1"`).
+ #[serde(default)]
+ pub allow: Vec<String>,
+}
+
+// =====================================================================
+// SomaSpec — runtime Linux (heredado de arje sin cambios)
+// =====================================================================
+
+#[derive(Debug, Clone, Default, Serialize, Deserialize)]
+pub struct SomaSpec {
+ pub namespaces: NamespaceSet,
+ pub rlimits: ResourceLimits,
+ pub cgroup: CgroupSpec,
+ pub cpu_affinity: Option<Vec<u32>>,
+}
+
+#[derive(Debug, Clone, Default, Serialize, Deserialize)]
+pub struct NamespaceSet {
+ pub mount: bool,
+ pub pid: bool,
+ pub net: bool,
+ pub uts: bool,
+ pub ipc: bool,
+ pub user: bool,
+ pub cgroup: bool,
+}
+
+#[derive(Debug, Clone, Default, Serialize, Deserialize)]
+pub struct ResourceLimits {
+ pub mem_bytes: Option<u64>,
+ pub nproc: Option<u32>,
+ pub nofile: Option<u32>,
+}
+
+#[derive(Debug, Clone, Default, Serialize, Deserialize)]
+pub struct CgroupSpec {
+ #[serde(default)]
+ pub path: String,
+ pub cpu_weight: Option<u32>,
+ pub io_weight: Option<u32>,
+}
+
+// =====================================================================
+// Payload — qué encarnar (heredado de arje)
+// =====================================================================
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub enum Payload {
+ Wasm {
+ module_sha256: [u8; 32],
+ entry: String,
+ },
+ Native {
+ exec: String,
+ argv: Vec<String>,
+ envp: Vec<(String, String)>,
+ },
+ /// Sin proceso. Nodo lógico del grafo (agregadores, mediators).
+ Virtual,
+ /// Wrapper de daemon legacy. `fakes` activa shims D-Bus / sd_notify.
+ Legacy {
+ exec: String,
+ argv: Vec<String>,
+ fakes: BTreeSet<LegacyFacade>,
+ },
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
+pub enum LegacyFacade {
+ SystemdLogind,
+ SystemdHostnamed,
+ SystemdNotify,
+}
+
+// =====================================================================
+// Supervisión (heredada de arje)
+// =====================================================================
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub enum Supervision {
+ Restart {
+ #[serde(with = "duration_millis")]
+ initial: Duration,
+ #[serde(with = "duration_millis")]
+ max: Duration,
+ },
+ OneShot,
+ Delegate,
+}
+
+mod duration_millis {
+ use serde::{Deserialize, Deserializer, Serializer};
+ use std::time::Duration;
+
+ pub fn serialize<S: Serializer>(d: &Duration, s: S) -> Result<S::Ok, S::Error> {
+ s.serialize_u64(d.as_millis() as u64)
+ }
+
+ pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<Duration, D::Error> {
+ let ms = u64::deserialize(d)?;
+ Ok(Duration::from_millis(ms))
+ }
+}
+
+// =====================================================================
+// Lifecycle / Priority (del modelo brahman)
+// =====================================================================
+
+/// Modelo de ejecución (rol). Ortogonal a `Supervision` (política de restart).
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
+#[serde(rename_all = "lowercase")]
+pub enum Lifecycle {
+ /// Servicio de larga duración.
+ #[default]
+ Daemon,
+ /// Una sola ejecución; sale al terminar su tarea.
+ Oneshot,
+ /// Componente UI gestionado por el motor de widgets.
+ Widget,
+}
+
+/// Tipo de relación entre dos Cards.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
+#[serde(rename_all = "kebab-case")]
+pub enum RelationshipKind {
+ /// Esta Card administra/posee al target (Ente sobre Mónada).
+ Owns,
+ /// Esta Card es administrada/poseída por el target (Mónada bajo Ente).
+ OwnedBy,
+ /// Esta Card procesa al target (Ente que consume Mónada).
+ Processes,
+ /// Esta Card es procesada por el target (Mónada siendo consumida).
+ ProcessedBy,
+ /// Relación lateral genérica.
+ Sibling,
+}
+
+/// Referencia desde una Card a otra. Forma el grafo de relaciones del
+/// fractal: "el Engine X posee la Mónada Y", "el Worker A procesa la
+/// Tarea B", etc.
+///
+/// Es responsabilidad del que declara mantener `target_id` apuntando a
+/// una Card que existe (o existió) en el ecosistema. El `target_label`
+/// es redundante con el lookup en runtime, pero se incluye para que la
+/// UI pueda renderear sin resolver.
+#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
+pub struct CardReference {
+ pub kind: RelationshipKind,
+ pub target_id: Ulid,
+ /// Label humano del target en el momento de declararse la
+ /// referencia (cache; el target real puede haber cambiado de label).
+ #[serde(default)]
+ pub target_label: String,
+}
+
+/// Naturaleza de la entidad detrás de la Card.
+///
+/// La función de presentarse es la misma para todos: tener identidad,
+/// resumen, capacidades, y poder ser encontrada por otros. Pero NO todas
+/// las entidades son procesos — algunas son agrupaciones de datos
+/// (Mónadas de Nouser, índices, streams).
+///
+/// El kind permite a consumidores (UI, broker, observadores) discriminar
+/// sólo cuando importa, pero todos hablan el mismo protocolo de Card.
+#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
+#[serde(rename_all = "lowercase")]
+pub enum CardKind {
+ /// Entidad runtime con `payload`/`soma`/`supervision` activos
+ /// (proceso, módulo, daemon).
+ #[default]
+ Ente,
+ /// Agrupación de datos sin proceso detrás (Mónadas Nouser, índices,
+ /// resultados cacheados). `payload` típicamente `Virtual`.
+ Data,
+}
+
+/// Faceta de datos: campos relevantes cuando `Card.kind != Ente`.
+///
+/// Optimizada para el wire — incluye sólo metadatos de presentación, NO
+/// listas pesadas (los miembros, embeddings completos, etc. se consultan
+/// al daemon dueño bajo demanda). El "presentation_hint" es un string
+/// libre que la UI mapea a su lente (p. ej. `"code"` → editor de código).
+#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
+pub struct DataFacet {
+ /// Resumen humano (1-2 oraciones). Generado por el daemon dueño.
+ #[serde(default)]
+ pub summary: String,
+ /// Tokens dominantes / palabras clave (5-10 típicamente).
+ #[serde(default)]
+ pub keywords: Vec<String>,
+ /// Centroide vectorial. Vacío si no hay embeddings calculados.
+ #[serde(default)]
+ pub centroid: Vec<f32>,
+ /// Cantidad de elementos contenidos (archivos, registros, ...).
+ #[serde(default)]
+ pub member_count: u32,
+ /// Métrica de dispersión interna [0, 1] (típicamente entropía).
+ #[serde(default)]
+ pub dispersion: f32,
+ /// Hint de presentación. Strings libres como `"code"`, `"gallery"`,
+ /// `"markdown"`, `"database"`, `"grid"`, `"tree"`. La UI los mapea.
+ #[serde(default)]
+ pub presentation_hint: String,
+}
+
+/// Prioridad de scheduling. Orden: `Low < Normal < High < Critical` —
+/// usable como tiebreaker en el broker (mayor priority gana).
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize)]
+#[serde(rename_all = "lowercase")]
+pub enum Priority {
+ Low,
+ #[default]
+ Normal,
+ High,
+ Critical,
+}
+
+/// Override per-contexto sobre los matches del broker.
+///
+/// La Card declara biases bajo `priority_contexts.<nombre>` que se
+/// activan cuando el broker corre bajo ese contexto. Aplicación según rol:
+///
+/// - **Como consumidor**: `pin_to` sobreescribe el `pin_to` estático del
+/// `Flow.pin_to` durante la búsqueda de productores.
+/// - **Como productor**: `priority_offset` se suma a la priority base
+/// (saturando en `[Low, Critical]`) para el ranking de candidatos.
+///
+/// Casos de uso típicos: test↔prod (mock vs real), foreground↔background
+/// (latencia vs costo), trust gates (sólo productores con cierto nivel).
+#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
+pub struct ContextBias {
+ /// Override del `pin_to` estático cuando el broker está en este
+ /// contexto y la Card actúa como consumidor.
+ ///
+ /// **No se usa `skip_serializing_if` aquí**: postcard requiere
+ /// layout fijo. La verbosidad extra en JSON (campos null/cero
+ /// emitidos) es el costo aceptado para compatibilidad de wire.
+ #[serde(default)]
+ pub pin_to: Option<String>,
+
+ /// Modifica la priority efectiva del Card como productor.
+ /// `+1` lo eleva, `-1` lo baja. El resultado se clampa al rango de
+ /// `Priority` ([Low, Critical]).
+ #[serde(default)]
+ pub priority_offset: i8,
+}
+
+// =====================================================================
+// Flujos tipados (del modelo brahman)
+// =====================================================================
+
+#[derive(Debug, Clone, Default, Serialize, Deserialize)]
+pub struct Flows {
+ #[serde(default)]
+ pub input: Vec<Flow>,
+ #[serde(default)]
+ pub output: Vec<Flow>,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct Flow {
+ /// Nombre único dentro de su dirección.
+ pub name: String,
+ /// Tipo de los datos que viajan por el flujo.
+ #[serde(rename = "type")]
+ pub ty: TypeRef,
+ /// Sugerencia de productor/consumidor concreto. El broker la respeta
+ /// como pista; cae en matching por tipo si no es resoluble.
+ #[serde(default)]
+ pub pin_to: Option<String>,
+}
+
+/// Referencia a un tipo, discriminada para distinguir primitivas de tipos WIT.
+///
+/// **Wire format (JSON / TOML / postcard):** externally-tagged. Ejemplo JSON:
+/// ```json
+/// { "primitive": { "name": "string" } }
+/// { "wit": { "package": "brahman:dht", "name": "entity-result" } }
+/// ```
+/// Se eligió externally-tagged por compatibilidad con postcard, que no
+/// soporta `#[serde(tag = "...")]` (internally-tagged) en formatos no
+/// self-describing.
+#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
+#[serde(rename_all = "lowercase")]
+pub enum TypeRef {
+ /// Tipo primitivo del runtime.
+ Primitive { name: String },
+ /// Tipo declarado en un paquete WIT.
+ Wit {
+ package: String,
+ #[serde(default)]
+ interface: Option<String>,
+ name: String,
+ },
+}
+
+// =====================================================================
+// API: parseo y validación
+// =====================================================================
+
+impl Card {
+ /// Construye una Card "viva" lista para registrarse en el broker:
+ /// `id = Ulid::new()` (único), `label` provisto, todo lo demás en
+ /// los defaults seguros (Payload::Virtual, Supervision::OneShot,
+ /// CardKind::Ente, etc.).
+ ///
+ /// Diseñada para usarse en struct-literals con override parcial,
+ /// igual que `Default` pero sin la trap de `Ulid::nil()`:
+ ///
+ /// ```ignore
+ /// let card = Card {
+ /// kind: CardKind::Data,
+ /// payload: Payload::Embedded(serde_json::json!({"foo": 1})),
+ /// ..Card::new("mi-modulo.algo")
+ /// };
+ /// ```
+ ///
+ /// Para Cards de búsqueda/sentinel donde `nil` es semánticamente
+ /// significativo, usá `Card::default()` directamente.
+ pub fn new(label: impl Into<String>) -> Self {
+ Self {
+ id: Ulid::new(),
+ label: label.into(),
+ ..Self::default()
+ }
+ }
+
+ /// Deserializa una Card desde JSON y valida.
+ pub fn from_json(src: &str) -> Result<Self, CardError> {
+ let c: Self = serde_json::from_str(src)?;
+ c.validate()?;
+ Ok(c)
+ }
+
+ /// Deserializa una Card desde TOML y valida.
+ pub fn from_toml(src: &str) -> Result<Self, CardError> {
+ let c: Self = toml::from_str(src)?;
+ c.validate()?;
+ Ok(c)
+ }
+
+ /// Carga una Card desde disco. Auto-detecta format por extensión
+ /// (`.json` o `.toml`).
+ pub fn from_path(path: impl AsRef<Path>) -> Result<Self, CardError> {
+ let p = path.as_ref();
+ let src = std::fs::read_to_string(p)?;
+ match p.extension().and_then(|e| e.to_str()) {
+ Some("json") => Self::from_json(&src),
+ Some("toml") => Self::from_toml(&src),
+ _ => Err(CardError::UnknownFormat),
+ }
+ }
+
+ /// Re-serializa la Card a JSON con indentación.
+ pub fn to_json_pretty(&self) -> Result<String, CardError> {
+ Ok(serde_json::to_string_pretty(self)?)
+ }
+
+ /// Validación semántica exhaustiva, recursiva sobre `genesis`.
+ pub fn validate(&self) -> Result<(), CardError> {
+ if self.schema_version != CARD_SCHEMA_VERSION {
+ return Err(CardError::SchemaMismatch {
+ got: self.schema_version,
+ expected: CARD_SCHEMA_VERSION,
+ });
+ }
+ if self.label.is_empty() {
+ return Err(CardError::EmptyLabel);
+ }
+ if self.label.len() > 256 {
+ return Err(CardError::LabelTooLong(self.label.len()));
+ }
+ for cap in &self.requires {
+ if self.provides.contains(cap) {
+ return Err(CardError::SelfDependency(cap.clone()));
+ }
+ }
+ validate_payload(&self.payload)?;
+ validate_rlimits(&self.soma.rlimits)?;
+ validate_cgroup(&self.soma.cgroup)?;
+ check_unique_flow_names(&self.flow.input, "flow.input")?;
+ check_unique_flow_names(&self.flow.output, "flow.output")?;
+ for child in &self.genesis {
+ child.validate()?;
+ }
+ Ok(())
+ }
+}
+
+fn validate_payload(p: &Payload) -> Result<(), CardError> {
+ match p {
+ Payload::Native { exec, .. } | Payload::Legacy { exec, .. } => {
+ if exec.trim().is_empty() {
+ return Err(CardError::EmptyExec);
+ }
+ }
+ Payload::Wasm { module_sha256, .. } => {
+ if module_sha256.iter().all(|&b| b == 0) {
+ return Err(CardError::SentinelWasmHash);
+ }
+ }
+ Payload::Virtual => {}
+ }
+ Ok(())
+}
+
+fn validate_rlimits(rl: &ResourceLimits) -> Result<(), CardError> {
+ if let Some(m) = rl.mem_bytes {
+ if m == 0 {
+ return Err(CardError::InvalidRlimit("mem_bytes=0"));
+ }
+ if m > 1u64 << 40 {
+ return Err(CardError::InvalidRlimit("mem_bytes>1TiB"));
+ }
+ }
+ if let Some(n) = rl.nproc {
+ if n == 0 || n > 65535 {
+ return Err(CardError::InvalidRlimit("nproc fuera de [1,65535]"));
+ }
+ }
+ if let Some(n) = rl.nofile {
+ if n == 0 || n > 1_048_576 {
+ return Err(CardError::InvalidRlimit("nofile fuera de [1,1M]"));
+ }
+ }
+ Ok(())
+}
+
+fn validate_cgroup(cg: &CgroupSpec) -> Result<(), CardError> {
+ if let Some(w) = cg.cpu_weight {
+ if !(1..=10000).contains(&w) {
+ return Err(CardError::InvalidCgroupWeight("cpu_weight"));
+ }
+ }
+ if let Some(w) = cg.io_weight {
+ if !(1..=10000).contains(&w) {
+ return Err(CardError::InvalidCgroupWeight("io_weight"));
+ }
+ }
+ Ok(())
+}
+
+fn check_unique_flow_names(flows: &[Flow], section: &'static str) -> Result<(), CardError> {
+ let mut seen = HashSet::new();
+ for f in flows {
+ if !seen.insert(f.name.as_str()) {
+ return Err(CardError::DuplicateFlowName {
+ section,
+ name: f.name.clone(),
+ });
+ }
+ }
+ Ok(())
+}
+
+// =====================================================================
+// Trust derivado
+// =====================================================================
+
+/// Nivel de confianza derivado de los permisos. **No es un campo declarado** —
+/// se calcula. Una sola fuente de verdad: lo que el Admin concede.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
+pub enum TrustLevel {
+ /// Sin permisos — sandbox total.
+ Untrusted,
+ /// Permisos menores (loopback, FS read-only, IPC).
+ Sandboxed,
+ /// Permisos amplios (red saliente o FS read-write).
+ Privileged,
+ /// Capacidad de spawnear procesos.
+ System,
+}
+
+impl TrustLevel {
+ /// Política de derivación:
+ /// - `processes = true` ⇒ `System`.
+ /// - FS `read-write` o networking `outbound`/`full` ⇒ `Privileged`.
+ /// - FS `read-only`, networking `loopback`, o cualquier IPC ⇒ `Sandboxed`.
+ /// - Sin permisos ⇒ `Untrusted`.
+ pub fn derive(p: &Permissions) -> Self {
+ if p.processes {
+ return Self::System;
+ }
+ if matches!(p.filesystem, FsPolicy::ReadWrite)
+ || matches!(
+ p.networking,
+ NetworkingPolicy::Outbound | NetworkingPolicy::Full
+ )
+ {
+ return Self::Privileged;
+ }
+ if matches!(p.filesystem, FsPolicy::ReadOnly)
+ || matches!(p.networking, NetworkingPolicy::Loopback)
+ || !p.ipc.allow.is_empty()
+ {
+ return Self::Sandboxed;
+ }
+ Self::Untrusted
+ }
+}
+
+// =====================================================================
+// Identidad runtime (Card + WIT extraído + trust)
+// =====================================================================
+
+/// Resumen de la interfaz WIT extraída del componente WASM/WIT.
+/// Vacío para módulos agnósticos (sin contrato WIT).
+#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
+pub struct WitInterface {
+ pub package: String,
+ pub world: String,
+ pub exports: Vec<String>,
+ pub imports: Vec<String>,
+}
+
+/// Card resuelta a runtime: schema + WIT opcional + trust derivado.
+/// Es lo que el Admin indexa.
+#[derive(Debug, Clone)]
+pub struct ResolvedCard {
+ pub card: Card,
+ /// `Some` si el módulo es consciente (expone WIT), `None` si es agnóstico.
+ pub wit: Option<WitInterface>,
+ pub trust: TrustLevel,
+}
+
+impl ResolvedCard {
+ /// Construye una Card resuelta sin información WIT.
+ pub fn from_agnostic(card: Card) -> Self {
+ let trust = TrustLevel::derive(&card.permissions);
+ Self {
+ card,
+ wit: None,
+ trust,
+ }
+ }
+
+ /// Construye una Card resuelta con interfaz WIT extraída.
+ pub fn from_conscious(card: Card, wit: WitInterface) -> Self {
+ let trust = TrustLevel::derive(&card.permissions);
+ Self {
+ card,
+ wit: Some(wit),
+ trust,
+ }
+ }
+}
+
+// =====================================================================
+// WireCard — proyección postcard-friendly de Card
+// =====================================================================
+
+/// Forma de wire de [`Card`]: idéntica al schema rico **sin** el campo
+/// `extensions` (incompatible con postcard porque `serde_json::Value`
+/// usa secuencias/maps de longitud dinámica).
+///
+/// Conversión:
+/// - `WireCard::from(card)` descarta `extensions` y proyecta `genesis`
+/// recursivamente.
+/// - `Card::from(wire)` recupera todos los campos; `extensions` queda
+/// vacío (la información de extensions no cruza el wire).
+///
+/// Esta separación implementa el contrato:
+/// - **JSON/TOML**: `Card` directa, con extensiones preservadas.
+/// - **Wire (postcard)**: `WireCard`, sin extensiones.
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct WireCard {
+ pub schema_version: u16,
+ pub id: Ulid,
+ #[serde(default)]
+ pub lineage: Option<Ulid>,
+ pub label: String,
+ #[serde(default)]
+ pub provides: BTreeSet<Capability>,
+ #[serde(default)]
+ pub requires: BTreeSet<Capability>,
+ #[serde(default)]
+ pub permissions: Permissions,
+ #[serde(default)]
+ pub soma: SomaSpec,
+ pub payload: Payload,
+ pub supervision: Supervision,
+ #[serde(default)]
+ pub lifecycle: Lifecycle,
+ #[serde(default)]
+ pub priority: Priority,
+ #[serde(default)]
+ pub flow: Flows,
+ #[serde(default)]
+ pub genesis: Vec<WireCard>,
+ #[serde(default)]
+ pub service_socket: Option<PathBuf>,
+ #[serde(default)]
+ pub references: Vec<CardReference>,
+ #[serde(default)]
+ pub kind: CardKind,
+ #[serde(default)]
+ pub data: Option<DataFacet>,
+ #[serde(default)]
+ pub priority_contexts: BTreeMap<String, ContextBias>,
+}
+
+impl From<Card> for WireCard {
+ fn from(c: Card) -> Self {
+ Self {
+ schema_version: c.schema_version,
+ id: c.id,
+ lineage: c.lineage,
+ label: c.label,
+ provides: c.provides,
+ requires: c.requires,
+ permissions: c.permissions,
+ soma: c.soma,
+ payload: c.payload,
+ supervision: c.supervision,
+ lifecycle: c.lifecycle,
+ priority: c.priority,
+ flow: c.flow,
+ genesis: c.genesis.into_iter().map(WireCard::from).collect(),
+ service_socket: c.service_socket,
+ references: c.references,
+ kind: c.kind,
+ data: c.data,
+ priority_contexts: c.priority_contexts,
+ }
+ }
+}
+
+impl From<WireCard> for Card {
+ fn from(w: WireCard) -> Self {
+ Self {
+ schema_version: w.schema_version,
+ id: w.id,
+ lineage: w.lineage,
+ label: w.label,
+ provides: w.provides,
+ requires: w.requires,
+ permissions: w.permissions,
+ soma: w.soma,
+ payload: w.payload,
+ supervision: w.supervision,
+ lifecycle: w.lifecycle,
+ priority: w.priority,
+ flow: w.flow,
+ genesis: w.genesis.into_iter().map(Card::from).collect(),
+ service_socket: w.service_socket,
+ references: w.references,
+ kind: w.kind,
+ data: w.data,
+ priority_contexts: w.priority_contexts,
+ extensions: BTreeMap::new(),
+ }
+ }
+}
+
+// =====================================================================
+// Tests
+// =====================================================================
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ fn sample_card_json() -> &'static str {
+ r#"{
+ "schema_version": 1,
+ "id": "01HQAR53D4M2NBV8KZTYXFGS01",
+ "lineage": null,
+ "label": "brahman.semantic_dht",
+ "provides": ["Spawn", "Journal"],
+ "requires": [],
+ "permissions": {
+ "networking": "loopback",
+ "filesystem": "read-only",
+ "ipc": { "allow": ["wit-v1"] },
+ "processes": false
+ },
+ "soma": {
+ "namespaces": {
+ "mount": false, "pid": false, "net": false,
+ "uts": false, "ipc": false, "user": false, "cgroup": false
+ },
+ "rlimits": { "mem_bytes": null, "nproc": null, "nofile": null },
+ "cgroup": { "path": "ente.slice/dht", "cpu_weight": null, "io_weight": null },
+ "cpu_affinity": null
+ },
+ "payload": "Virtual",
+ "supervision": { "Restart": { "initial": 100, "max": 30000 } },
+ "lifecycle": "daemon",
+ "priority": "high",
+ "flow": {
+ "input": [
+ { "name": "search-query", "type": { "primitive": { "name": "string" } } }
+ ],
+ "output": [
+ { "name": "dht-results",
+ "type": { "wit": { "package": "brahman:dht", "name": "entity-result" } } }
+ ]
+ },
+ "genesis": []
+ }"#
+ }
+
+ #[test]
+ fn parses_full_json() {
+ let c = Card::from_json(sample_card_json()).unwrap();
+ assert_eq!(c.label, "brahman.semantic_dht");
+ assert_eq!(c.lifecycle, Lifecycle::Daemon);
+ assert_eq!(c.priority, Priority::High);
+ assert_eq!(c.permissions.filesystem, FsPolicy::ReadOnly);
+ assert_eq!(c.permissions.networking, NetworkingPolicy::Loopback);
+ assert_eq!(c.permissions.ipc.allow, vec!["wit-v1".to_string()]);
+ assert_eq!(c.flow.input.len(), 1);
+ assert_eq!(c.flow.output.len(), 1);
+ match &c.flow.output[0].ty {
+ TypeRef::Wit { package, name, .. } => {
+ assert_eq!(package, "brahman:dht");
+ assert_eq!(name, "entity-result");
+ }
+ _ => panic!("expected Wit"),
+ }
+ }
+
+ #[test]
+ fn json_roundtrip_preserves_shape() {
+ let c1 = Card::from_json(sample_card_json()).unwrap();
+ let s = c1.to_json_pretty().unwrap();
+ let c2 = Card::from_json(&s).unwrap();
+ assert_eq!(c1.label, c2.label);
+ assert_eq!(c1.flow.input.len(), c2.flow.input.len());
+ }
+
+ #[test]
+ fn trust_derivation() {
+ let mut p = Permissions::default();
+ assert_eq!(TrustLevel::derive(&p), TrustLevel::Untrusted);
+ p.filesystem = FsPolicy::ReadOnly;
+ assert_eq!(TrustLevel::derive(&p), TrustLevel::Sandboxed);
+ p.networking = NetworkingPolicy::Outbound;
+ assert_eq!(TrustLevel::derive(&p), TrustLevel::Privileged);
+ p.processes = true;
+ assert_eq!(TrustLevel::derive(&p), TrustLevel::System);
+ }
+
+ #[test]
+ fn duplicate_flow_names_rejected() {
+ let mut c: Card = serde_json::from_str(sample_card_json()).unwrap();
+ c.flow.input.push(c.flow.input[0].clone());
+ assert!(matches!(
+ c.validate(),
+ Err(CardError::DuplicateFlowName { .. })
+ ));
+ }
+
+ #[test]
+ fn self_dependency_rejected() {
+ let mut c: Card = serde_json::from_str(sample_card_json()).unwrap();
+ c.requires.insert(Capability::Spawn);
+ assert!(matches!(c.validate(), Err(CardError::SelfDependency(_))));
+ }
+
+ #[test]
+ fn invalid_genesis_propagates() {
+ let parent_src = r#"{
+ "schema_version": 1,
+ "id": "01HQAR53D4M2NBV8KZTYXFGS01",
+ "label": "parent",
+ "provides": [], "requires": [],
+ "soma": {
+ "namespaces": {"mount":false,"pid":false,"net":false,"uts":false,"ipc":false,"user":false,"cgroup":false},
+ "rlimits": {"mem_bytes":null,"nproc":null,"nofile":null},
+ "cgroup": {"path":"x","cpu_weight":null,"io_weight":null},
+ "cpu_affinity": null
+ },
+ "payload": "Virtual",
+ "supervision": "OneShot",
+ "genesis": [{
+ "schema_version": 1,
+ "id": "01HQAR53D4M2NBV8KZTYXFGS02",
+ "label": "",
+ "provides": [], "requires": [],
+ "soma": {
+ "namespaces": {"mount":false,"pid":false,"net":false,"uts":false,"ipc":false,"user":false,"cgroup":false},
+ "rlimits": {"mem_bytes":null,"nproc":null,"nofile":null},
+ "cgroup": {"path":"x","cpu_weight":null,"io_weight":null},
+ "cpu_affinity": null
+ },
+ "payload": "Virtual",
+ "supervision": "OneShot",
+ "genesis": []
+ }]
+ }"#;
+ assert!(matches!(
+ Card::from_json(parent_src),
+ Err(CardError::EmptyLabel)
+ ));
+ }
+
+ #[test]
+ fn presentation_card_carries_derived_trust() {
+ let c = Card::from_json(sample_card_json()).unwrap();
+ let resolved = ResolvedCard::from_agnostic(c);
+ assert_eq!(resolved.trust, TrustLevel::Sandboxed);
+ assert!(resolved.wit.is_none());
+ }
+
+ #[test]
+ fn arje_seed_format_compatible() {
+ // Reproduce el format canónico de arje (sin lifecycle/priority/flow,
+ // que son aditivos brahman). Debe parsear con defaults.
+ let src = r#"{
+ "schema_version": 1,
+ "id": "01HQAR53D4M2NBV8KZTYXFGS01",
+ "lineage": null,
+ "label": "vps-min",
+ "provides": ["Spawn", "Journal"],
+ "requires": [],
+ "soma": {
+ "namespaces": {"mount":false,"pid":false,"net":false,"uts":false,"ipc":false,"user":false,"cgroup":false},
+ "rlimits": {"mem_bytes":null,"nproc":null,"nofile":null},
+ "cgroup": {"path":"ente.slice/zero","cpu_weight":null,"io_weight":null},
+ "cpu_affinity": null
+ },
+ "payload": "Virtual",
+ "supervision": "OneShot",
+ "genesis": []
+ }"#;
+ let c = Card::from_json(src).unwrap();
+ assert_eq!(c.lifecycle, Lifecycle::Daemon); // default
+ assert_eq!(c.priority, Priority::Normal); // default
+ assert_eq!(c.flow.input.len(), 0);
+ }
+
+ #[test]
+ fn extensions_preserved_in_json_roundtrip() {
+ let src = r#"{
+ "schema_version": 1,
+ "id": "01HQAR53D4M2NBV8KZTYXFGS01",
+ "label": "x",
+ "soma": {
+ "namespaces": {"mount":false,"pid":false,"net":false,"uts":false,"ipc":false,"user":false,"cgroup":false},
+ "rlimits": {"mem_bytes":null,"nproc":null,"nofile":null},
+ "cgroup": {"path":"x","cpu_weight":null,"io_weight":null},
+ "cpu_affinity": null
+ },
+ "payload": "Virtual",
+ "supervision": "OneShot",
+ "author": "sergio",
+ "tags": ["draft", "experimental"]
+ }"#;
+ let c = Card::from_json(src).unwrap();
+ assert_eq!(c.extensions.get("author").and_then(|v| v.as_str()), Some("sergio"));
+ assert!(c.extensions.contains_key("tags"));
+
+ // Roundtrip JSON: extensions deben re-emitirse.
+ let s = c.to_json_pretty().unwrap();
+ let c2 = Card::from_json(&s).unwrap();
+ assert_eq!(c2.extensions.get("author"), c.extensions.get("author"));
+ }
+
+ #[test]
+ fn wire_card_roundtrip_strips_extensions() {
+ let src = r#"{
+ "schema_version": 1,
+ "id": "01HQAR53D4M2NBV8KZTYXFGS01",
+ "label": "x",
+ "soma": {
+ "namespaces": {"mount":false,"pid":false,"net":false,"uts":false,"ipc":false,"user":false,"cgroup":false},
+ "rlimits": {"mem_bytes":null,"nproc":null,"nofile":null},
+ "cgroup": {"path":"x","cpu_weight":null,"io_weight":null},
+ "cpu_affinity": null
+ },
+ "payload": "Virtual",
+ "supervision": "OneShot",
+ "author": "sergio"
+ }"#;
+ let c = Card::from_json(src).unwrap();
+ assert!(c.extensions.contains_key("author"));
+
+ // Card → WireCard descarta extensions.
+ let wire: WireCard = c.into();
+ assert_eq!(wire.label, "x");
+
+ // WireCard → Card → extensiones quedan vacías (se perdieron).
+ let c_back: Card = wire.into();
+ assert_eq!(c_back.label, "x");
+ assert!(c_back.extensions.is_empty(), "extensions sobreviven al wire");
+ }
+
+ #[test]
+ fn wirecard_postcard_with_priority_contexts() {
+ // Repro del bug que rompía chasqui-nous-mock: ContextBias con
+ // skip_serializing_if causaba que postcard leyera bytes
+ // equivocados. Sin esos atributos, el roundtrip es estable.
+ let src = r#"{
+ "schema_version": 1,
+ "id": "01HQAR53D4M2NBV8KZTYXFGS01",
+ "label": "x",
+ "soma": {
+ "namespaces": {"mount":false,"pid":false,"net":false,"uts":false,"ipc":false,"user":false,"cgroup":false},
+ "rlimits": {"mem_bytes":null,"nproc":null,"nofile":null},
+ "cgroup": {"path":"x","cpu_weight":null,"io_weight":null},
+ "cpu_affinity": null
+ },
+ "payload": "Virtual",
+ "supervision": "OneShot"
+ }"#;
+ let mut c = Card::from_json(src).unwrap();
+ c.priority_contexts.insert(
+ "test".into(),
+ ContextBias {
+ pin_to: None,
+ priority_offset: 1,
+ },
+ );
+ c.priority_contexts.insert(
+ "prod".into(),
+ ContextBias {
+ pin_to: Some("real-nous".into()),
+ priority_offset: 2,
+ },
+ );
+
+ let wire: WireCard = c.into();
+ let bytes = postcard::to_allocvec(&wire).expect("postcard encode");
+ let decoded: WireCard = postcard::from_bytes(&bytes).expect("postcard decode");
+
+ assert_eq!(decoded.priority_contexts.len(), 2);
+ let test_bias = decoded
+ .priority_contexts
+ .get("test")
+ .expect("test context");
+ assert_eq!(test_bias.priority_offset, 1);
+ assert!(test_bias.pin_to.is_none());
+ let prod_bias = decoded
+ .priority_contexts
+ .get("prod")
+ .expect("prod context");
+ assert_eq!(prod_bias.pin_to.as_deref(), Some("real-nous"));
+ assert_eq!(prod_bias.priority_offset, 2);
+ }
+
+ #[test]
+ fn wire_card_postcard_friendly() {
+ // Validación: WireCard puede ser postcard-encoded sin error.
+ // Si Card tuviera extensions populadas, el encode rompería con
+ // "length of a sequence must be known". WireCard las descarta.
+ let src = r#"{
+ "schema_version": 1,
+ "id": "01HQAR53D4M2NBV8KZTYXFGS01",
+ "label": "x",
+ "soma": {
+ "namespaces": {"mount":false,"pid":false,"net":false,"uts":false,"ipc":false,"user":false,"cgroup":false},
+ "rlimits": {"mem_bytes":null,"nproc":null,"nofile":null},
+ "cgroup": {"path":"x","cpu_weight":null,"io_weight":null},
+ "cpu_affinity": null
+ },
+ "payload": "Virtual",
+ "supervision": "OneShot",
+ "author": "sergio"
+ }"#;
+ let c = Card::from_json(src).unwrap();
+ let wire: WireCard = c.into();
+ let bytes = postcard::to_allocvec(&wire).expect("WireCard debe encodear");
+ let decoded: WireCard = postcard::from_bytes(&bytes).expect("WireCard debe decodear");
+ assert_eq!(decoded.label, "x");
+ }
+
+ #[test]
+ fn new_assigns_real_ulid_and_label() {
+ let c = Card::new("chasqui.engine");
+ assert_eq!(c.label, "chasqui.engine");
+ assert_ne!(c.id, Ulid::nil(), "Card::new no debe dejar id en nil");
+ }
+
+ #[test]
+ fn new_yields_distinct_ids_per_call() {
+ let a = Card::new("x");
+ let b = Card::new("x");
+ assert_ne!(a.id, b.id);
+ }
+
+ #[test]
+ fn default_keeps_nil_id_for_struct_update_pattern() {
+ // Mantener este invariante explícito: Default::default() es
+ // determinista y devuelve nil. Cualquier cambio aquí rompería
+ // el patrón `..Default::default()` en patterns de búsqueda.
+ let d = Card::default();
+ assert_eq!(d.id, Ulid::nil());
+ assert!(d.label.is_empty());
+ }
+}
diff --git a/shared/card/card-net/Cargo.toml b/shared/card/card-net/Cargo.toml
new file mode 100644
index 0000000..36b63af
--- /dev/null
+++ b/shared/card/card-net/Cargo.toml
@@ -0,0 +1,24 @@
+[package]
+name = "card-net"
+version.workspace = true
+edition.workspace = true
+rust-version.workspace = true
+license.workspace = true
+authors.workspace = true
+publish.workspace = true
+description = "Brahman — capa de transporte P2P compartida (libp2p TCP+Noise+Yamux+Kad+Identify+Stream). Cualquier protocolo (handshake brahman, sync minga, futuros) puede registrar su StreamProtocol y abrir/aceptar streams sobre la malla común."
+
+[dependencies]
+futures = { workspace = true }
+libp2p = { workspace = true }
+libp2p-stream = { workspace = true }
+libp2p-allow-block-list = { workspace = true }
+thiserror = { workspace = true }
+tokio = { workspace = true }
+tracing = { workspace = true }
+blake3 = { workspace = true }
+serde = { workspace = true }
+
+[dev-dependencies]
+tokio = { workspace = true }
+tokio-util = { workspace = true }
diff --git a/shared/card/card-net/src/key.rs b/shared/card/card-net/src/key.rs
new file mode 100644
index 0000000..ba3d402
--- /dev/null
+++ b/shared/card/card-net/src/key.rs
@@ -0,0 +1,167 @@
+//! Claves namespaced del DHT compartido — la **primitiva unificadora** de
+//! Brahman (Capa 1).
+//!
+//! El ecosistema brahman corre UN solo Kademlia (en este crate, `card-net`).
+//! Para que distintos dominios — código indexado (minga), Cards
+//! (card-discovery), Personas (ágora), servicios — coexistan sin colisión,
+//! cada clave lleva un byte de `kind` como prefijo. La representación en wire
+//! es de longitud fija: `[kind_tag] ++ blake3(id)` = 33 bytes.
+//!
+//! Vive en `card-net` (y no en un dominio concreto) precisamente porque es el
+//! namespace COMÚN: minga, agora y card-discovery la comparten. `minga-dht`
+//! la re-exporta por compatibilidad histórica.
+
+use serde::{Deserialize, Serialize};
+
+/// Tipo de registro — el namespace de una clave en el DHT.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
+pub enum RecordKind {
+ /// Bloque de código indexado (minga).
+ Code,
+ /// `Card` brahman (módulo, ente, etc.).
+ Card,
+ /// `Persona` de ágora (identidad humana federada).
+ Persona,
+ /// Endpoint de servicio.
+ Service,
+ /// Dominio definido por el consumidor.
+ Custom(u8),
+}
+
+impl RecordKind {
+ /// Byte de etiqueta. `Custom(n)` ocupa `0x80 | n` (top bit) para no
+ /// chocar nunca con los kinds estándar (`0x00..`).
+ pub fn tag(&self) -> u8 {
+ match self {
+ RecordKind::Code => 0x01,
+ RecordKind::Card => 0x02,
+ RecordKind::Persona => 0x03,
+ RecordKind::Service => 0x04,
+ RecordKind::Custom(n) => 0x80 | (n & 0x7f),
+ }
+ }
+}
+
+/// Longitud fija de la clave en wire: 1 byte de kind + 32 de hash.
+pub const DHT_KEY_LEN: usize = 33;
+
+/// Clave de DHT namespaced. Dos formas de construcción:
+///
+/// - Con un `id` legible (`new`, `code`, `card`, `persona`) — el wire
+/// hashea el id con blake3. Útil cuando el consumidor publica
+/// identidades simbólicas (nombres de módulos, slugs de personas).
+/// - Con `for_hash` — el wire usa los 32 bytes del hash directamente.
+/// Útil cuando el id YA es un blake3 (como en minga, que indexa
+/// contenido por su α-hash, o ágora, cuyo `IdentityId` ya es
+/// `blake3(pubkey)`) — evita una segunda pasada de blake3.
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct DhtKey {
+ kind: RecordKind,
+ /// Representación canónica de los 32 bytes que forman la "id" en wire.
+ /// Si se construyó por nombre simbólico, son `blake3(id_string)`.
+ /// Si se construyó por hash directo, son el hash tal cual.
+ body: [u8; 32],
+ /// `id` legible — para `Display`. `None` si se construyó por hash.
+ label: Option<String>,
+}
+
+impl DhtKey {
+ pub fn new(kind: RecordKind, id: impl Into<String>) -> Self {
+ let id = id.into();
+ let body = *blake3::hash(id.as_bytes()).as_bytes();
+ Self {
+ kind,
+ body,
+ label: Some(id),
+ }
+ }
+
+ /// Clave para un bloque de código (id legible).
+ pub fn code(id: impl Into<String>) -> Self {
+ Self::new(RecordKind::Code, id)
+ }
+
+ /// Clave para una Card.
+ pub fn card(id: impl Into<String>) -> Self {
+ Self::new(RecordKind::Card, id)
+ }
+
+ /// Clave para una Persona.
+ pub fn persona(id: impl Into<String>) -> Self {
+ Self::new(RecordKind::Persona, id)
+ }
+
+ /// Clave a partir de un hash ya computado (32 bytes). El wire usa
+ /// esos bytes directamente, **sin re-hashear**.
+ pub fn for_hash(kind: RecordKind, hash: [u8; 32]) -> Self {
+ Self {
+ kind,
+ body: hash,
+ label: None,
+ }
+ }
+
+ pub fn kind(&self) -> RecordKind {
+ self.kind
+ }
+
+ pub fn id(&self) -> Option<&str> {
+ self.label.as_deref()
+ }
+
+ /// Representación en wire: `[kind_tag] ++ body`, 33 bytes.
+ pub fn to_bytes(&self) -> Vec<u8> {
+ let mut out = Vec::with_capacity(DHT_KEY_LEN);
+ out.push(self.kind.tag());
+ out.extend_from_slice(&self.body);
+ out
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn wire_key_has_fixed_length() {
+ assert_eq!(DhtKey::card("modulo-x").to_bytes().len(), DHT_KEY_LEN);
+ assert_eq!(DhtKey::code("fn-hash").to_bytes().len(), DHT_KEY_LEN);
+ }
+
+ #[test]
+ fn same_id_different_kind_does_not_collide() {
+ let a = DhtKey::card("foo").to_bytes();
+ let b = DhtKey::code("foo").to_bytes();
+ let c = DhtKey::persona("foo").to_bytes();
+ assert_ne!(a, b);
+ assert_ne!(b, c);
+ assert_ne!(a, c);
+ // El hash del id es el mismo; sólo difiere el byte de kind.
+ assert_eq!(a[1..], b[1..]);
+ assert_ne!(a[0], b[0]);
+ }
+
+ #[test]
+ fn same_kind_and_id_is_stable() {
+ assert_eq!(DhtKey::card("x").to_bytes(), DhtKey::card("x").to_bytes());
+ }
+
+ #[test]
+ fn for_hash_no_rehashea() {
+ // for_hash debe usar los bytes tal cual: body = hash, no blake3(hash).
+ let h = [7u8; 32];
+ let k = DhtKey::for_hash(RecordKind::Persona, h);
+ let wire = k.to_bytes();
+ assert_eq!(wire[0], RecordKind::Persona.tag());
+ assert_eq!(&wire[1..], &h);
+ }
+
+ #[test]
+ fn custom_kind_never_collides_with_standard() {
+ for std in [RecordKind::Code, RecordKind::Card, RecordKind::Persona, RecordKind::Service] {
+ for n in 0..=127u8 {
+ assert_ne!(std.tag(), RecordKind::Custom(n).tag());
+ }
+ }
+ }
+}
diff --git a/shared/card/card-net/src/lib.rs b/shared/card/card-net/src/lib.rs
new file mode 100644
index 0000000..c10f29c
--- /dev/null
+++ b/shared/card/card-net/src/lib.rs
@@ -0,0 +1,518 @@
+//! `brahman-net` — capa P2P compartida de la red Brahman.
+//!
+//! Provee un nodo libp2p genérico que cualquier protocolo de la
+//! familia (handshake brahman remoto, sync minga, futuros) puede
+//! reusar. La idea: una sola malla, múltiples sub-protocolos
+//! multiplexados por `StreamProtocol`.
+//!
+//! ## Stack
+//!
+//! - **TCP + Noise + Yamux**: transporte autenticado y multiplexado.
+//! - **`stream::Behaviour`**: streams bidireccionales por
+//! `StreamProtocol`. Cada protocolo (`/brahman/handshake/1.0.0`,
+//! `/minga/sync/1.0.0`, …) se registra independientemente vía el
+//! `stream::Control` que `BrahmanNet` expone.
+//! - **`kad::Behaviour<MemoryStore>`**: Kademlia DHT en modo Server
+//! para discovery (peers cercanos + content providers).
+//! - **`identify::Behaviour`**: cada peer anuncia sus listen-addrs
+//! reales; las inyectamos automáticamente al routing table de Kad.
+//!
+//! ## Modelo
+//!
+//! El swarm corre en una task tokio dedicada. La interfaz pública son:
+//! 1. **Comandos** (canal mpsc): `dial`, `listen`, `add_dht_peer`,
+//! `find_closest_peers`, `start_providing`, `find_providers`.
+//! 2. **`stream::Control`** (acceso directo): para abrir/aceptar
+//! streams de un protocolo concreto. Cada protocolo se ocupa de
+//! su propia lógica sobre el stream resultante.
+//!
+//! La separación entre comandos y control permite que la lógica de
+//! red (DHT, dial, listen) y la lógica de protocolos (handshake/sync)
+//! evolucionen independientes — el protocolo no necesita conocer al
+//! swarm, sólo pide streams.
+//!
+//! ## Identidad
+//!
+//! Por defecto se genera una keypair Ed25519 efímera. Para identidad
+//! persistente (la misma `peer_id` across reboots), pasar la keypair
+//! con [`BrahmanNet::with_keypair`]. Esa misma keypair puede ser la
+//! base para firmas de Cards (cuando se implemente trust remoto).
+
+#![forbid(unsafe_code)]
+#![warn(rust_2018_idioms)]
+
+use std::collections::HashMap;
+use std::sync::Arc;
+use std::time::Duration;
+
+use futures::StreamExt;
+use libp2p::{
+ autonat, dcutr, identify, identity, kad, mdns, noise, relay,
+ swarm::{behaviour::toggle::Toggle, NetworkBehaviour, SwarmEvent},
+ tcp, yamux, Swarm, SwarmBuilder,
+};
+use libp2p_allow_block_list::{self as allow_block_list, BlockedPeers};
+use libp2p_stream as stream;
+use tokio::sync::{mpsc, oneshot, Mutex};
+
+pub mod key;
+pub use key::{DhtKey, RecordKind, DHT_KEY_LEN};
+
+pub use libp2p::{
+ identity::{Keypair, PublicKey},
+ multiaddr::Protocol,
+ Multiaddr, PeerId, Stream, StreamProtocol,
+};
+pub use libp2p_stream::OpenStreamError;
+
+const IDENTIFY_PROTOCOL: &str = "/brahman-net/0.1.0";
+const IDLE_CONNECTION_TIMEOUT: Duration = Duration::from_secs(60);
+
+#[derive(NetworkBehaviour)]
+struct BrahmanBehaviour {
+ /// Block-list a nivel de swarm: peers en este behaviour son
+ /// rechazados ANTES del handshake Noise. Más eficiente que
+ /// rechazar al nivel del handshake brahman (ahorra round-trip
+ /// TCP+Noise por intento denegado). Sincronizado con la
+ /// `PeerPolicy.deny` vía `block_peer`/`unblock_peer` exposed
+ /// en `BrahmanNet`.
+ block_list: allow_block_list::Behaviour<BlockedPeers>,
+ stream: stream::Behaviour,
+ kad: kad::Behaviour<kad::store::MemoryStore>,
+ identify: identify::Behaviour,
+ /// Relay server (Circuit Relay v2): un nodo alcanzable presta su
+ /// conexión para que dos pares detrás de NAT se contacten. Pasivo —
+ /// sólo actúa ante reservas/solicitudes de circuito.
+ relay: relay::Behaviour,
+ /// Relay client: permite reservar un circuito en un relay y ser
+ /// alcanzable vía `…/p2p/<relay>/p2p-circuit/p2p/<self>`.
+ relay_client: relay::client::Behaviour,
+ /// DCUtR: tras conectar por relay, intenta promover a conexión
+ /// directa (hole-punching) coordinando por el circuito.
+ dcutr: dcutr::Behaviour,
+ /// AutoNAT: confirma qué direcciones externas son realmente alcanzables
+ /// pidiéndole a otros peers que nos disquen de vuelta. Sustituye el
+ /// "confiar a ciegas en el observed_addr de identify" por direcciones
+ /// verificadas — sólo esas se anuncian (y entran en reservas de relay).
+ autonat: autonat::Behaviour,
+ /// mDNS: descubrimiento de pares en la MISMA LAN sin bootstrap ni IP
+ /// conocida (multicast 224.0.0.251). Los pares descubiertos se inyectan
+ /// a la routing table de Kad (igual que las listen-addrs de identify), así
+ /// el DHT y los protocolos de stream funcionan zero-config en LAN.
+ /// `Toggle`: si el socket multicast no se puede abrir (sandbox, red sin
+ /// multicast), queda deshabilitado y el nodo sigue andando igual.
+ mdns: Toggle<mdns::tokio::Behaviour>,
+}
+
+#[derive(Debug, thiserror::Error)]
+pub enum NodeError {
+ #[error("transport build failed: {0}")]
+ Build(String),
+}
+
+#[derive(Debug)]
+enum Command {
+ Dial(Multiaddr),
+ Listen(Multiaddr),
+ AddDhtPeer(PeerId, Multiaddr),
+ FindClosestPeers(PeerId, oneshot::Sender<Vec<DiscoveredPeer>>),
+ StartProviding(Vec<u8>),
+ StopProviding(Vec<u8>),
+ GetProviders(Vec<u8>, oneshot::Sender<Vec<PeerId>>),
+ BlockPeer(PeerId),
+ UnblockPeer(PeerId),
+}
+
+/// Peer descubierto vía DHT: identidad + direcciones conocidas.
+#[derive(Debug, Clone)]
+pub struct DiscoveredPeer {
+ pub peer_id: PeerId,
+ pub addrs: Vec<Multiaddr>,
+}
+
+/// Nodo Brahman en la malla P2P. Maneja el swarm libp2p y expone
+/// API uniforme para listen/dial/DHT/streams.
+pub struct BrahmanNet {
+ /// Identidad libp2p de este nodo. Estable mientras viva la
+ /// keypair (efímera por default; persistente si pasaste una
+ /// vía [`with_keypair`]).
+ pub peer_id: PeerId,
+ /// Keypair compartida (Arc para compartir con consumers que
+ /// necesitan firmar mensajes con la misma identidad — p. ej.
+ /// `card_handshake::network::connect_libp2p` que firma el
+ /// Hello). NO se expone públicamente; usar [`Self::keypair`].
+ keypair: Arc<Keypair>,
+ cmd_tx: mpsc::UnboundedSender<Command>,
+ listen_rx: Mutex<mpsc::UnboundedReceiver<Multiaddr>>,
+ /// Control para abrir y aceptar streams. Cada protocolo
+ /// (handshake brahman, sync minga, etc.) llama
+ /// `control.accept(StreamProtocol::new("/foo/1.0.0"))` para
+ /// recibir streams entrantes, o `control.open_stream(peer, proto)`
+ /// para abrirlos. Multiplexado y demultiplexado lo hace libp2p.
+ pub control: stream::Control,
+}
+
+impl BrahmanNet {
+ /// Crea un nodo con keypair Ed25519 generada al vuelo (peer_id
+ /// efímero — cambia en cada arranque).
+ pub fn new() -> Result<Self, NodeError> {
+ Self::with_keypair(identity::Keypair::generate_ed25519())
+ }
+
+ /// Crea un nodo con una keypair libp2p específica. Usá esto para
+ /// `peer_id` estable (por ejemplo si tu identidad se persiste a
+ /// disco, o si la derivás de la identidad criptográfica del
+ /// módulo).
+ ///
+ /// Sólo Ed25519 se soporta — la `keypair` se duplica internamente
+ /// vía clone del `ed25519::Keypair` para que tanto el swarm
+ /// (Noise auth) como el caller (firma de Cards) compartan la
+ /// misma identidad sin la fricción de que `identity::Keypair` no
+ /// implemente `Clone`.
+ pub fn with_keypair(keypair: identity::Keypair) -> Result<Self, NodeError> {
+ let ed_kp = keypair
+ .try_into_ed25519()
+ .map_err(|_| NodeError::Build("brahman-net sólo soporta keypairs Ed25519".into()))?;
+ let kp_for_swarm = identity::Keypair::from(ed_kp.clone());
+ let kp_for_storage = Arc::new(identity::Keypair::from(ed_kp));
+ let peer_id = kp_for_swarm.public().to_peer_id();
+
+ let mut swarm: Swarm<BrahmanBehaviour> = SwarmBuilder::with_existing_identity(kp_for_swarm)
+ .with_tokio()
+ .with_tcp(
+ tcp::Config::default(),
+ noise::Config::new,
+ yamux::Config::default,
+ )
+ .map_err(|e| NodeError::Build(format!("{e}")))?
+ // Inyecta el transporte de relay-client: habilita marcar y
+ // escuchar direcciones `…/p2p-circuit`. Provee el
+ // `relay_client` behaviour al closure de abajo.
+ .with_relay_client(noise::Config::new, yamux::Config::default)
+ .map_err(|e| NodeError::Build(format!("{e}")))?
+ .with_behaviour(|key, relay_client| {
+ let local = key.public().to_peer_id();
+ let mut kad =
+ kad::Behaviour::new(local, kad::store::MemoryStore::new(local));
+ // Modo Server: respondemos a queries del DHT. Auto
+ // requiere detectar reachability; para entornos
+ // controlados (localhost, redes privadas) Server es
+ // lo correcto.
+ kad.set_mode(Some(kad::Mode::Server));
+ let identify = identify::Behaviour::new(
+ identify::Config::new(IDENTIFY_PROTOCOL.to_string(), key.public())
+ .with_agent_version(format!("brahman-net/{}", env!("CARGO_PKG_VERSION"))),
+ );
+ // 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();
+ let mdns = match mdns::tokio::Behaviour::new(mdns::Config::default(), local) {
+ Ok(b) => {
+ if depurar {
+ eprintln!("brahman: mDNS ACTIVO (descubrimiento LAN)");
+ }
+ Toggle::from(Some(b))
+ }
+ Err(e) => {
+ if depurar {
+ eprintln!("brahman: mDNS DESACTIVADO ({e})");
+ }
+ Toggle::from(None)
+ }
+ };
+ BrahmanBehaviour {
+ block_list: allow_block_list::Behaviour::default(),
+ stream: stream::Behaviour::new(),
+ kad,
+ identify,
+ relay: relay::Behaviour::new(local, Default::default()),
+ relay_client,
+ dcutr: dcutr::Behaviour::new(local),
+ mdns,
+ autonat: autonat::Behaviour::new(
+ local,
+ autonat::Config {
+ // Confirmamos también IPs privadas/loopback: la
+ // malla Brahman vive tanto en LAN como en WAN, no
+ // sólo en IPs globales (default `true` las
+ // ignoraría y nada se confirmaría en LAN).
+ only_global_ips: false,
+ // Sondeo pronto tras arrancar (default ~15 s es
+ // demasiado para el flujo de reservas de relay).
+ boot_delay: Duration::from_secs(2),
+ retry_interval: Duration::from_secs(5),
+ ..Default::default()
+ },
+ ),
+ }
+ })
+ .map_err(|e| NodeError::Build(format!("{e}")))?
+ .with_swarm_config(|c| c.with_idle_connection_timeout(IDLE_CONNECTION_TIMEOUT))
+ .build();
+
+ 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>();
+
+ tokio::spawn(async move {
+ let mut pending_finds: HashMap<
+ kad::QueryId,
+ oneshot::Sender<Vec<DiscoveredPeer>>,
+ > = HashMap::new();
+ let mut pending_providers: HashMap<
+ kad::QueryId,
+ (Vec<PeerId>, oneshot::Sender<Vec<PeerId>>),
+ > = HashMap::new();
+
+ loop {
+ tokio::select! {
+ Some(cmd) = cmd_rx.recv() => {
+ match cmd {
+ Command::Dial(addr) => {
+ let _ = swarm.dial(addr);
+ }
+ Command::Listen(addr) => {
+ let _ = swarm.listen_on(addr);
+ }
+ Command::AddDhtPeer(peer, addr) => {
+ swarm.behaviour_mut().kad.add_address(&peer, addr);
+ }
+ Command::FindClosestPeers(target, tx) => {
+ let qid = swarm.behaviour_mut().kad.get_closest_peers(target);
+ pending_finds.insert(qid, tx);
+ }
+ Command::StartProviding(key) => {
+ // Best-effort: si falla (sin peers cercanos para
+ // replicar), seguirá viviendo en el local store
+ // y se servirá vía get_providers de quien tenga
+ // conexión con nosotros.
+ let _ = swarm.behaviour_mut().kad.start_providing(key.into());
+ }
+ Command::StopProviding(key) => {
+ // Quitamos el record local del provider store.
+ // Los peers cercanos eventualmente expiran su
+ // copia replicada por TTL natural (~24h en
+ // libp2p kad default); para retiro inmediato
+ // habría que enviar un republish con sentinel,
+ // pero kad no expone esa primitiva. Aceptable
+ // para el caso "el provider local desapareció":
+ // queries que pasen por nosotros dejan de
+ // listarnos al instante.
+ swarm.behaviour_mut().kad.stop_providing(&key.into());
+ }
+ Command::GetProviders(key, tx) => {
+ let qid = swarm.behaviour_mut().kad.get_providers(key.into());
+ pending_providers.insert(qid, (Vec::new(), tx));
+ }
+ Command::BlockPeer(peer) => {
+ swarm.behaviour_mut().block_list.block_peer(peer);
+ }
+ Command::UnblockPeer(peer) => {
+ swarm.behaviour_mut().block_list.unblock_peer(peer);
+ }
+ }
+ }
+ event = swarm.select_next_some() => {
+ match event {
+ SwarmEvent::NewListenAddr { address, .. } => {
+ let _ = listen_tx.send(address);
+ }
+ // Identify nos dice las listen-addrs reales del
+ // peer. Las inyectamos a Kad para poblar el
+ // routing table sin necesidad de add_dht_peer
+ // manual — la propagación pasa a ser automática.
+ SwarmEvent::Behaviour(BrahmanBehaviourEvent::Identify(
+ identify::Event::Received { peer_id, info, .. }
+ )) => {
+ // El observed_addr que nos reporta identify se
+ // emite como CANDIDATO a externa; AutoNAT lo
+ // prueba pidiendo dial-backs y, si es
+ // alcanzable, dispara StatusChanged(Public) —
+ // ahí recién lo confirmamos (abajo). Las
+ // listen-addrs del peer pueblan la routing
+ // table de Kad.
+ for addr in info.listen_addrs {
+ swarm.behaviour_mut().kad.add_address(&peer_id, addr);
+ }
+ }
+ // mDNS descubrió pares en la LAN: inyectamos sus
+ // direcciones a Kad (igual que identify). Con eso el
+ // DHT y los streams funcionan sin bootstrap manual.
+ SwarmEvent::Behaviour(BrahmanBehaviourEvent::Mdns(
+ mdns::Event::Discovered(list)
+ )) => {
+ for (peer_id, addr) in list {
+ if std::env::var("BRAHMAN_DEBUG").is_ok() {
+ eprintln!("brahman: mDNS descubrió {peer_id} en {addr}");
+ }
+ swarm.behaviour_mut().kad.add_address(&peer_id, addr);
+ }
+ }
+ // AutoNAT confirmó (vía dial-back de otros peers)
+ // que somos alcanzables en `addr`: recién entonces
+ // la anunciamos como externa — la usa el relay
+ // server en las reservas y la ven los pares.
+ SwarmEvent::Behaviour(BrahmanBehaviourEvent::Autonat(
+ autonat::Event::StatusChanged { new: autonat::NatStatus::Public(addr), .. }
+ )) => {
+ swarm.add_external_address(addr);
+ }
+ SwarmEvent::Behaviour(BrahmanBehaviourEvent::Kad(
+ kad::Event::OutboundQueryProgressed { id, result, step, .. }
+ )) => {
+ match result {
+ kad::QueryResult::GetClosestPeers(Ok(ok)) if step.last => {
+ if let Some(tx) = pending_finds.remove(&id) {
+ let infos = ok.peers.into_iter()
+ .map(|p| DiscoveredPeer {
+ peer_id: p.peer_id,
+ addrs: p.addrs,
+ })
+ .collect();
+ let _ = tx.send(infos);
+ }
+ }
+ kad::QueryResult::GetClosestPeers(Err(_)) if step.last => {
+ if let Some(tx) = pending_finds.remove(&id) {
+ let _ = tx.send(Vec::new());
+ }
+ }
+ kad::QueryResult::GetProviders(Ok(ok)) => {
+ if let Some((collected, _)) =
+ pending_providers.get_mut(&id)
+ {
+ if let kad::GetProvidersOk::FoundProviders {
+ providers, ..
+ } = ok
+ {
+ for p in providers {
+ if !collected.contains(&p) {
+ collected.push(p);
+ }
+ }
+ }
+ }
+ if step.last {
+ if let Some((providers, tx)) =
+ pending_providers.remove(&id)
+ {
+ let _ = tx.send(providers);
+ }
+ }
+ }
+ kad::QueryResult::GetProviders(Err(_)) if step.last => {
+ if let Some((providers, tx)) =
+ pending_providers.remove(&id)
+ {
+ let _ = tx.send(providers);
+ }
+ }
+ _ => {}
+ }
+ }
+ _ => {}
+ }
+ }
+ }
+ }
+ });
+
+ Ok(Self {
+ peer_id,
+ keypair: kp_for_storage,
+ cmd_tx,
+ listen_rx: Mutex::new(listen_rx),
+ control,
+ })
+ }
+
+ /// 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`
+ /// permite compartir sin copia — la keypair libp2p no es `Clone`.
+ pub fn keypair(&self) -> Arc<Keypair> {
+ self.keypair.clone()
+ }
+
+ /// Bloquea conexiones desde/hacia `peer` a nivel del swarm.
+ /// Conexiones existentes se cierran y nuevos intentos son
+ /// rechazados ANTES del Noise handshake — más eficiente que
+ /// rechazar al nivel del handshake brahman (ahorra round-trip
+ /// TCP+Noise por intento). Idempotente.
+ pub fn block_peer(&self, peer: PeerId) {
+ let _ = self.cmd_tx.send(Command::BlockPeer(peer));
+ }
+
+ /// Quita a `peer` de la block-list del swarm. Conexiones futuras
+ /// son aceptadas con normalidad. Idempotente.
+ pub fn unblock_peer(&self, peer: PeerId) {
+ 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 querés saber qué puerto te tocó).
+ pub async fn listen(&self, addr: Multiaddr) -> Multiaddr {
+ self.cmd_tx
+ .send(Command::Listen(addr))
+ .expect("swarm task alive");
+ let mut rx = self.listen_rx.lock().await;
+ rx.recv().await.expect("listen address arrives")
+ }
+
+ /// Inicia conexión con un peer en `addr`. No-op si ya hay
+ /// conexión. Best-effort — fallos se loggean al swarm pero no se
+ /// propagan al caller (consistente con libp2p).
+ pub fn dial(&self, addr: Multiaddr) {
+ let _ = self.cmd_tx.send(Command::Dial(addr));
+ }
+
+ /// Añade un peer al routing table de Kademlia. Punto de entrada
+ /// para bootstrap: tras esto, el nodo puede dirigir queries DHT
+ /// a través de este peer.
+ pub fn add_dht_peer(&self, peer: PeerId, addr: Multiaddr) {
+ let _ = self.cmd_tx.send(Command::AddDhtPeer(peer, addr));
+ }
+
+ /// Consulta el DHT por los peers más cercanos al `target` PeerId.
+ /// Devuelve la lista resuelta (vacía si la query falla o si no
+ /// hay peers conocidos). Bloquea hasta que la query completa.
+ pub async fn find_closest_peers(&self, target: PeerId) -> Vec<DiscoveredPeer> {
+ let (tx, rx) = oneshot::channel();
+ let _ = self
+ .cmd_tx
+ .send(Command::FindClosestPeers(target, tx));
+ rx.await.unwrap_or_default()
+ }
+
+ /// Anuncia en el DHT que este peer tiene el contenido identificado
+ /// por `key`. Otros peers pueden luego descubrirlo vía
+ /// [`find_providers`](Self::find_providers). Best-effort: si la
+ /// replicación falla inicialmente, el record vive en el store
+ /// local hasta que llegue conexión.
+ pub fn start_providing(&self, key: &[u8]) {
+ let _ = self.cmd_tx.send(Command::StartProviding(key.to_vec()));
+ }
+
+ /// Retira el anuncio previo de [`start_providing`] para `key`.
+ /// El record local se borra al instante (queries que lleguen a
+ /// nosotros dejan de listarnos). Los records replicados en peers
+ /// remotos viven hasta su TTL — kad no expone primitiva para
+ /// retracción inmediata cross-peer. Aceptable: simétrico al
+ /// caso "el provider apareció" (también propagación eventual).
+ pub fn stop_providing(&self, key: &[u8]) {
+ let _ = self.cmd_tx.send(Command::StopProviding(key.to_vec()));
+ }
+
+ /// 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.
+ pub async fn find_providers(&self, key: &[u8]) -> Vec<PeerId> {
+ let (tx, rx) = oneshot::channel();
+ let _ = self
+ .cmd_tx
+ .send(Command::GetProviders(key.to_vec(), tx));
+ rx.await.unwrap_or_default()
+ }
+}
diff --git a/shared/card/card-wit/Cargo.toml b/shared/card/card-wit/Cargo.toml
new file mode 100644
index 0000000..a92e65f
--- /dev/null
+++ b/shared/card/card-wit/Cargo.toml
@@ -0,0 +1,21 @@
+[package]
+name = "card-wit"
+version.workspace = true
+edition.workspace = true
+rust-version.workspace = true
+license.workspace = true
+authors.workspace = true
+publish.workspace = true
+description = "Brahman — extractor opcional: parsea contratos WIT y devuelve `WitInterface` listo para acoplar a una `Card`."
+
+[dependencies]
+card-core = { path = "../card-core" }
+wit-parser = "0.230"
+thiserror = { workspace = true }
+
+[dev-dependencies]
+anyhow = { workspace = true }
+
+[[example]]
+name = "brahman-wit-info"
+path = "examples/brahman-wit-info.rs"
diff --git a/shared/card/card-wit/README.md b/shared/card/card-wit/README.md
new file mode 100644
index 0000000..0568111
--- /dev/null
+++ b/shared/card/card-wit/README.md
@@ -0,0 +1,34 @@
+# brahman-card-wit
+
+> **DORMIDO (2026-05-30).** Capa 3 de Brahman. Ver `/BRAHMAN.md`.
+
+Parser opcional de contratos WIT (`.wit` texto → `Vec<card_core::WitInterface>`, uno por `world`),
+sobre `wit-parser` (sin `wasm-tools`/`wit-component`).
+
+## Estado: relegado, no borrado
+
+La visión original de Brahman incluía **módulos agnósticos descritos por interfaz WIT** (eventualmente WASM).
+Esa capa **nunca se ejecutó**:
+
+- No existe ningún archivo `.wit` en el workspace.
+- Ningún crate de producción depende de este crate — sólo `examples/brahman-wit-info.rs` y la
+ **dev-dependency** de `card-sidecar`.
+
+Se conserva (funciona, 210 LOC, reversible) por si algún día aparecen `.wit` reales. **No asumir que está
+en ninguna ruta de build.**
+
+## El contrato agnóstico real y vigente
+
+```
+shared/card (formato Card) + card-handshake (handshake nativo Rust) + DhtKey (namespacing en la DHT)
+```
+
+El tipo de metadata `WitInterface` vive en **`card-core`** (no aquí) y **sí** lo usa el broker
+(`chasqui-broker`) para matching estructural — es metadata opcional viva. Lo único dormido es *este parser*
+de archivos `.wit` inexistentes.
+
+## Si se decide revivir WIT
+
+Requeriría: (1) que el Init lea `<modulo>/wit/protocol.wit` en el descubrimiento y construya
+`ResolvedCard::from_conscious(card, wit)`; (2) que algún módulo de producción publique un `.wit`.
+Hasta entonces, este crate es tooling latente (`cargo run -p card-wit --example brahman-wit-info -- <archivo.wit>`).
diff --git a/shared/card/card-wit/examples/brahman-wit-info.rs b/shared/card/card-wit/examples/brahman-wit-info.rs
new file mode 100644
index 0000000..ef58524
--- /dev/null
+++ b/shared/card/card-wit/examples/brahman-wit-info.rs
@@ -0,0 +1,45 @@
+//! `brahman-wit-info` — inspecciona un archivo WIT y lista sus worlds.
+//!
+//! Uso:
+//! ```sh
+//! cargo run -p brahman-card-wit --example brahman-wit-info -- shared_wit/protocol.wit
+//! ```
+
+use std::process::ExitCode;
+
+fn main() -> ExitCode {
+ let path = match std::env::args().nth(1) {
+ Some(p) => p,
+ None => {
+ eprintln!("uso: brahman-wit-info <ruta.wit>");
+ return ExitCode::from(2);
+ }
+ };
+
+ let worlds = match card_wit::parse_wit_file(&path) {
+ Ok(w) => w,
+ Err(e) => {
+ eprintln!("error parseando {path}: {e}");
+ return ExitCode::from(1);
+ }
+ };
+
+ if worlds.is_empty() {
+ println!("(ningún world declarado)");
+ return ExitCode::SUCCESS;
+ }
+
+ println!("{} world(s):", worlds.len());
+ for w in &worlds {
+ println!();
+ println!(" package: {}", w.package);
+ println!(" world: {}", w.world);
+ if !w.imports.is_empty() {
+ println!(" imports: {}", w.imports.join(", "));
+ }
+ if !w.exports.is_empty() {
+ println!(" exports: {}", w.exports.join(", "));
+ }
+ }
+ ExitCode::SUCCESS
+}
diff --git a/shared/card/card-wit/src/lib.rs b/shared/card/card-wit/src/lib.rs
new file mode 100644
index 0000000..6d37e29
--- /dev/null
+++ b/shared/card/card-wit/src/lib.rs
@@ -0,0 +1,178 @@
+//! `brahman-card-wit` — extractor de contratos WIT.
+//!
+//! **DORMIDO (2026-05-30).** No existe ningún archivo `.wit` en el workspace
+//! y ningún crate de producción depende de éste (sólo `examples/` y la
+//! dev-dependency de `card-sidecar`). Es la "Capa 3" de Brahman (ver `/BRAHMAN.md`):
+//! la idea de módulos agnósticos descritos por interfaz WIT nunca se ejecutó.
+//! El **contrato agnóstico real y vigente** es `shared/card` (formato `Card`) +
+//! el handshake nativo Rust de `card-handshake` + el namespacing de `DhtKey`.
+//! Se conserva como herramienta opcional por si algún día aparecen `.wit`
+//! reales; no asumir que está en ninguna ruta de build.
+//!
+//! Nota: el tipo de metadata [`WitInterface`] vive en `card-core` y SÍ lo usa
+//! el broker para matching estructural — eso es metadata opcional viva. Lo
+//! dormido es este *parser* de archivos `.wit` inexistentes.
+//!
+//! Crate **opcional** (no es dep de `brahman-card`). Parsea texto WIT
+//! mediante [`wit-parser`] y devuelve una lista de [`WitInterface`]
+//! (uno por `world`) lista para acoplarse a una [`card_core::Card`]
+//! cuando se construye una [`card_core::ResolvedCard`].
+//!
+//! Casos de uso (hipotéticos hasta que existan `.wit`):
+//!
+//! - El Init lee `<modulo>/wit/protocol.wit` durante el descubrimiento
+//! y lo combina con la Card del módulo para obtener una
+//! `ResolvedCard::from_conscious(card, wit)`.
+//! - Tooling (`brahman-wit-info`) inspecciona un `.wit` y muestra
+//! sus mundos, exports e imports.
+//!
+//! No depende de `wasm-tools`/`wit-component` — sólo del parser texto.
+
+#![forbid(unsafe_code)]
+#![warn(rust_2018_idioms)]
+
+use std::path::{Path, PathBuf};
+
+use card_core::WitInterface;
+use thiserror::Error;
+use wit_parser::{Resolve, WorldKey};
+
+#[derive(Debug, Error)]
+pub enum WitError {
+ #[error("parse: {0}")]
+ Parse(String),
+ #[error("E/S: {0}")]
+ Io(#[from] std::io::Error),
+}
+
+/// Parsea WIT desde una string. Devuelve un `WitInterface` por cada
+/// `world` declarado.
+pub fn parse_wit(source: &str) -> Result<Vec<WitInterface>, WitError> {
+ parse_with_path(source, Path::new("inline.wit"))
+}
+
+/// Parsea WIT desde un archivo. Útil para `module/wit/protocol.wit`.
+pub fn parse_wit_file(path: impl AsRef<Path>) -> Result<Vec<WitInterface>, WitError> {
+ let p = path.as_ref();
+ let source = std::fs::read_to_string(p)?;
+ parse_with_path(&source, p)
+}
+
+fn parse_with_path(source: &str, path: &Path) -> Result<Vec<WitInterface>, WitError> {
+ let mut resolve = Resolve::new();
+ let path_buf: PathBuf = path.to_path_buf();
+ resolve
+ .push_str(&path_buf, source)
+ .map_err(|e| WitError::Parse(e.to_string()))?;
+
+ let mut out = Vec::new();
+ for (_pkg_id, pkg) in resolve.packages.iter() {
+ let pkg_name = pkg.name.to_string();
+ for (_name, &world_id) in &pkg.worlds {
+ let world = &resolve.worlds[world_id];
+ let exports = collect_keys(world.exports.iter().map(|(k, _)| k), &resolve);
+ let imports = collect_keys(world.imports.iter().map(|(k, _)| k), &resolve);
+ out.push(WitInterface {
+ package: pkg_name.clone(),
+ world: world.name.clone(),
+ exports,
+ imports,
+ });
+ }
+ }
+ Ok(out)
+}
+
+fn collect_keys<'a, I>(keys: I, resolve: &Resolve) -> Vec<String>
+where
+ I: Iterator<Item = &'a WorldKey>,
+{
+ keys.map(|k| match k {
+ WorldKey::Name(n) => n.clone(),
+ WorldKey::Interface(id) => resolve.interfaces[*id]
+ .name
+ .clone()
+ .unwrap_or_else(|| format!("<interface#{}>", id.index())),
+ })
+ .collect()
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ const SAMPLE: &str = r#"
+package brahman:test@0.1.0;
+
+interface handshake {
+ hello: func() -> result<_, string>;
+}
+
+interface lifecycle {
+ report: func();
+}
+
+world module {
+ import handshake;
+ import lifecycle;
+ export run: func() -> result<_, string>;
+}
+"#;
+
+ #[test]
+ fn parses_inline_wit() {
+ let worlds = parse_wit(SAMPLE).unwrap();
+ assert_eq!(worlds.len(), 1, "esperaba un único world");
+ let w = &worlds[0];
+ assert!(w.package.starts_with("brahman:test"));
+ assert_eq!(w.world, "module");
+ assert!(
+ w.imports.iter().any(|i| i == "handshake"),
+ "imports={:?}",
+ w.imports
+ );
+ assert!(
+ w.imports.iter().any(|i| i == "lifecycle"),
+ "imports={:?}",
+ w.imports
+ );
+ assert!(
+ w.exports.iter().any(|e| e == "run"),
+ "exports={:?}",
+ w.exports
+ );
+ }
+
+ #[test]
+ fn parses_shared_protocol() {
+ let path = concat!(env!("CARGO_MANIFEST_DIR"), "/../../../shared_wit/protocol.wit");
+ let worlds = parse_wit_file(path).unwrap();
+ assert!(
+ worlds.iter().any(|w| w.world == "module"),
+ "no encontró world 'module' en {:?}",
+ worlds.iter().map(|w| &w.world).collect::<Vec<_>>()
+ );
+ assert!(
+ worlds.iter().any(|w| w.world == "admin-host"),
+ "no encontró world 'admin-host'"
+ );
+ }
+
+ #[test]
+ fn parse_error_on_garbage() {
+ let bad = "this is not wit at all { } } ;;;;";
+ assert!(matches!(parse_wit(bad), Err(WitError::Parse(_))));
+ }
+
+ #[test]
+ fn empty_world_handled() {
+ let src = r#"
+package brahman:empty@0.1.0;
+world hollow {}
+"#;
+ let worlds = parse_wit(src).unwrap();
+ assert_eq!(worlds.len(), 1);
+ assert!(worlds[0].exports.is_empty());
+ assert!(worlds[0].imports.is_empty());
+ }
+}