gioser

tawasuyu/card

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

Markdown 9Rust 7TOML 4HTML 1165 Definiciones

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 · por Sergio · (hace 4 meses) · ver el árbol en este commit

Delta semántico · 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

15 archivos · +6739 −0

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());
+    }
+}