From 24b2e5c2bccb31bbb7ab9d00daa60a368eebdaf2 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Wed, 3 Jun 2026 01:39:18 +0100 Subject: [PATCH] Add Rust typed Odin core --- .gitignore | 1 + Cargo.lock | 1243 ++++++++++++++++++++++++++++ Cargo.toml | 11 + README.md | 11 + crates/odin-core/Cargo.toml | 21 + crates/odin-core/src/documents.rs | 144 ++++ crates/odin-core/src/lib.rs | 9 + crates/odin-core/src/pipeline.rs | 260 ++++++ crates/odin-core/src/ports.rs | 102 +++ crates/odin-core/src/repository.rs | 185 +++++ docs/architecture.md | 42 + package.json | 1 + 12 files changed, 2030 insertions(+) create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 crates/odin-core/Cargo.toml create mode 100644 crates/odin-core/src/documents.rs create mode 100644 crates/odin-core/src/lib.rs create mode 100644 crates/odin-core/src/pipeline.rs create mode 100644 crates/odin-core/src/ports.rs create mode 100644 crates/odin-core/src/repository.rs diff --git a/.gitignore b/.gitignore index a21fc00..a025e05 100644 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,6 @@ node_modules/ scratch/ bin/ obj/ +target/ *.log *.pid diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..7548953 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1243 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[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", +] + +[[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 = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84d7ced0ae9557296835c32bf1b1e02b44c746701f898460fb000d7eaa84f00a" + +[[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 = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[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 = "chrono" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[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 = "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 = "cultcache-rs" +version = "0.1.0" +dependencies = [ + "anyhow", + "chrono", + "cultcache-rs-derive", + "fs2", + "rmp-serde", + "serde", + "serde_bytes", + "serde_json", + "uuid", +] + +[[package]] +name = "cultcache-rs-derive" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "cultmesh-rs" +version = "0.1.0" +dependencies = [ + "anyhow", + "cultcache-rs", + "cultnet-rs", + "serde", +] + +[[package]] +name = "cultnet-rs" +version = "0.1.0" +dependencies = [ + "aes-gcm", + "anyhow", + "base64", + "chrono", + "cultcache-rs", + "hmac", + "rand", + "rmp-serde", + "rmpv", + "serde", + "serde_bytes", + "serde_json", + "sha2", + "socket2", + "uuid", +] + +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + +[[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 = "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 = "fastrand" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "fs2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-core", + "futures-task", + "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", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi 5.3.0", + "wasip2", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "wasip2", + "wasip3", +] + +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval", +] + +[[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 = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[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 = "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 = "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 = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "log" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "113b30b4cd05f7c06868fdb2854f66a7b9fece9a48425351cd532e810d74024f" + +[[package]] +name = "memchr" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "odin-core" +version = "0.1.0" +dependencies = [ + "anyhow", + "cultcache-rs", + "cultmesh-rs", + "cultnet-rs", + "pretty_assertions", + "serde", + "tempfile", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "pretty_assertions" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" +dependencies = [ + "diff", + "yansi", +] + +[[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 = "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.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +dependencies = [ + "rand_chacha", + "rand_core 0.9.5", +] + +[[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 = "rmp" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ba8be72d372b2c9b35542551678538b562e7cf86c3315773cae48dfbfe7790c" +dependencies = [ + "num-traits", +] + +[[package]] +name = "rmp-serde" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f81bee8c8ef9b577d1681a70ebbc962c232461e397b22c208c43c04b67a155" +dependencies = [ + "rmp", + "serde", +] + +[[package]] +name = "rmpv" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a4e1d4b9b938a26d2996af33229f0ca0956c652c1375067f0b45291c1df8417" +dependencies = [ + "rmp", + "serde", + "serde_bytes", +] + +[[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 = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[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_bytes" +version = "0.11.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" +dependencies = [ + "serde", + "serde_core", +] + +[[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 = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[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 = "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 = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.2", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[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 = "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 = "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", +] + +[[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", +] + +[[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 = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[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-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-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", +] + +[[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", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[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_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[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_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[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_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[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", +] + +[[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", + "wit-parser", +] + +[[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", +] + +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + +[[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 = "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..63c246b --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,11 @@ +[workspace] +members = [ + "crates/odin-core", +] +resolver = "3" + +[workspace.package] +edition = "2024" +license = "MIT" +publish = false + diff --git a/README.md b/README.md index 3e64373..37d1410 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,17 @@ durable service state in CultCache `.cc`, local Verse visibility through CultMesh, meaningful dashboards as Eve GUI/TUI DSL, and renderers as lowerers only. +## Rust Spine + +The target Odin body is Rust-first: ingest through narrow ports, normalize into +typed Odin records, persist through CultCache `.cc`, expose through CultMesh / +CultNet document registries, and lower interface state through Eve/CultUI. + +The first Rust core lives in `crates/odin-core` and already separates typed +documents, ingest ports, normalization, and repository persistence so unit tests +can use mocked inputs and pipeline smokes can prove typed handoff without +booting the whole daemon. + ## Gjallar Gjallar is Odin's herald organ: the daemon/persona package that should carry diff --git a/crates/odin-core/Cargo.toml b/crates/odin-core/Cargo.toml new file mode 100644 index 0000000..4b16b77 --- /dev/null +++ b/crates/odin-core/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "odin-core" +version = "0.1.0" +edition.workspace = true +license.workspace = true +publish.workspace = true + +[lib] +name = "odin_core" +path = "src/lib.rs" + +[dependencies] +anyhow = "1" +cultcache-rs = { path = "E:/Projects/EpiphanyAgent/vendor/cultcache-rs" } +cultmesh-rs = { path = "E:/Projects/EpiphanyAgent/vendor/cultmesh-rs" } +cultnet-rs = { path = "E:/Projects/EpiphanyAgent/vendor/cultnet-rs" } +serde = { version = "1", features = ["derive"] } + +[dev-dependencies] +pretty_assertions = "1.4" +tempfile = "3" diff --git a/crates/odin-core/src/documents.rs b/crates/odin-core/src/documents.rs new file mode 100644 index 0000000..057f47d --- /dev/null +++ b/crates/odin-core/src/documents.rs @@ -0,0 +1,144 @@ +use cultcache_rs::DatabaseEntry; + +pub const ODIN_SNAPSHOT_SCHEMA: &str = "odin.snapshot.v1"; +pub const ODIN_VERSE_SCHEMA: &str = "odin.verse.v1"; +pub const ODIN_SERVICE_SCHEMA: &str = "odin.service.v1"; +pub const ODIN_INTERFACE_SCHEMA: &str = "odin.interface.v1"; +pub const ODIN_OBSERVATION_STREAM_SCHEMA: &str = "odin.observation_stream.v1"; +pub const ODIN_TRANSLATION_ROUTE_SCHEMA: &str = "odin.translation_route.v1"; + +#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)] +#[cultcache(type = "odin.snapshot", schema = "odin.snapshot.v1")] +pub struct OdinSnapshotRecord { + #[cultcache(key = 0)] + pub snapshot_id: String, + #[cultcache(key = 1)] + pub observed_at: String, + #[cultcache(key = 2)] + pub verse_count: u32, + #[cultcache(key = 3)] + pub service_count: u32, + #[cultcache(key = 4)] + pub interface_count: u32, + #[cultcache(key = 5)] + pub observation_stream_count: u32, + #[cultcache(key = 6)] + pub source: String, +} + +#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)] +#[cultcache(type = "odin.verse", schema = "odin.verse.v1")] +pub struct OdinVerseRecord { + #[cultcache(key = 0)] + pub verse_id: String, + #[cultcache(key = 1)] + pub name: String, + #[cultcache(key = 2)] + pub role: String, + #[cultcache(key = 3)] + pub status: String, + #[cultcache(key = 4)] + pub capabilities: Vec, + #[cultcache(key = 5)] + pub observed_at: String, +} + +#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)] +#[cultcache(type = "odin.service", schema = "odin.service.v1")] +pub struct OdinServiceRecord { + #[cultcache(key = 0)] + pub service_id: String, + #[cultcache(key = 1)] + pub verse_id: String, + #[cultcache(key = 2)] + pub name: String, + #[cultcache(key = 3)] + pub state: String, + #[cultcache(key = 4)] + pub detail: String, + #[cultcache(key = 5)] + pub authority: String, + #[cultcache(key = 6)] + pub observed_at: String, +} + +#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)] +#[cultcache(type = "odin.interface", schema = "odin.interface.v1")] +pub struct OdinInterfaceRecord { + #[cultcache(key = 0)] + pub provider_id: String, + #[cultcache(key = 1)] + pub title: String, + #[cultcache(key = 2)] + pub state: String, + #[cultcache(key = 3)] + pub source: String, + #[cultcache(key = 4)] + pub version: Option, + #[cultcache(key = 5)] + pub updated_at: Option, + #[cultcache(key = 6)] + pub observed_at: String, +} + +#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)] +#[cultcache( + type = "odin.observation_stream", + schema = "odin.observation_stream.v1" +)] +pub struct OdinObservationStreamRecord { + #[cultcache(key = 0)] + pub stream_key: String, + #[cultcache(key = 1)] + pub device_id: String, + #[cultcache(key = 2)] + pub stream_id: String, + #[cultcache(key = 3)] + pub kind: String, + #[cultcache(key = 4)] + pub state: String, + #[cultcache(key = 5)] + pub detail: String, + #[cultcache(key = 6)] + pub owner: String, + #[cultcache(key = 7)] + pub observed_at: String, +} + +#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)] +#[cultcache(type = "odin.translation_route", schema = "odin.translation_route.v1")] +pub struct OdinTranslationRouteRecord { + #[cultcache(key = 0)] + pub route_id: String, + #[cultcache(key = 1)] + pub source_schema: String, + #[cultcache(key = 2)] + pub target_schema: String, + #[cultcache(key = 3)] + pub translation_kind: String, + #[cultcache(key = 4)] + pub owner: String, + #[cultcache(key = 5)] + pub version: String, + #[cultcache(key = 6)] + pub notes: String, +} + +cultmesh_rs::cultmesh_documents!(OdinDocuments { + OdinSnapshotRecord => ODIN_SNAPSHOT_SCHEMA, + OdinVerseRecord => ODIN_VERSE_SCHEMA, + OdinServiceRecord => ODIN_SERVICE_SCHEMA, + OdinInterfaceRecord => ODIN_INTERFACE_SCHEMA, + OdinObservationStreamRecord => ODIN_OBSERVATION_STREAM_SCHEMA, + OdinTranslationRouteRecord => ODIN_TRANSLATION_ROUTE_SCHEMA, +}); + +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct OdinRecords { + pub snapshot: Option, + pub verses: Vec, + pub services: Vec, + pub interfaces: Vec, + pub observation_streams: Vec, + pub translation_routes: Vec, +} diff --git a/crates/odin-core/src/lib.rs b/crates/odin-core/src/lib.rs new file mode 100644 index 0000000..a2f8a34 --- /dev/null +++ b/crates/odin-core/src/lib.rs @@ -0,0 +1,9 @@ +pub mod documents; +pub mod pipeline; +pub mod ports; +pub mod repository; + +pub use documents::*; +pub use pipeline::*; +pub use ports::*; +pub use repository::*; diff --git a/crates/odin-core/src/pipeline.rs b/crates/odin-core/src/pipeline.rs new file mode 100644 index 0000000..6635d15 --- /dev/null +++ b/crates/odin-core/src/pipeline.rs @@ -0,0 +1,260 @@ +use crate::documents::{ + OdinInterfaceRecord, OdinObservationStreamRecord, OdinRecords, OdinServiceRecord, + OdinSnapshotRecord, OdinTranslationRouteRecord, OdinVerseRecord, +}; +use crate::ports::{ + Clock, InterfaceIngest, ObservationStreamIngest, OdinInputBatch, ServiceIngest, + TranslationRouteIngest, VerseIngest, +}; +use anyhow::Result; + +pub struct OdinIngestPipeline { + verses: V, + services: S, + interfaces: I, + streams: O, + translations: T, + clock: C, +} + +impl OdinIngestPipeline +where + V: VerseIngest, + S: ServiceIngest, + I: InterfaceIngest, + O: ObservationStreamIngest, + T: TranslationRouteIngest, + C: Clock, +{ + pub fn new( + verses: V, + services: S, + interfaces: I, + streams: O, + translations: T, + clock: C, + ) -> Self { + Self { + verses, + services, + interfaces, + streams, + translations, + clock, + } + } + + pub fn collect(&self) -> Result { + Ok(OdinInputBatch { + observed_at: self.clock.now(), + source: "odin.rust.ingest".to_string(), + verses: self.verses.observe_verses()?, + services: self.services.observe_services()?, + interfaces: self.interfaces.observe_interfaces()?, + observation_streams: self.streams.observe_streams()?, + translation_routes: self.translations.observe_translation_routes()?, + }) + } +} + +pub fn normalize_odin_records(input: OdinInputBatch) -> OdinRecords { + let verses = input + .verses + .into_iter() + .map(|entry| OdinVerseRecord { + verse_id: entry.verse_id, + name: entry.name, + role: entry.role, + status: entry.status, + capabilities: entry.capabilities, + observed_at: input.observed_at.clone(), + }) + .collect::>(); + + let services = input + .services + .into_iter() + .map(|entry| OdinServiceRecord { + service_id: entry.service_id, + verse_id: entry.verse_id, + name: entry.name, + state: entry.state, + detail: entry.detail, + authority: entry.authority, + observed_at: input.observed_at.clone(), + }) + .collect::>(); + + let interfaces = input + .interfaces + .into_iter() + .map(|entry| OdinInterfaceRecord { + provider_id: entry.provider_id, + title: entry.title, + state: entry.state, + source: entry.source, + version: entry.version, + updated_at: entry.updated_at, + observed_at: input.observed_at.clone(), + }) + .collect::>(); + + let observation_streams = input + .observation_streams + .into_iter() + .map(|entry| { + let stream_key = format!("{}:{}:{}", entry.device_id, entry.stream_id, entry.kind); + OdinObservationStreamRecord { + stream_key, + device_id: entry.device_id, + stream_id: entry.stream_id, + kind: entry.kind, + state: entry.state, + detail: entry.detail, + owner: entry.owner, + observed_at: input.observed_at.clone(), + } + }) + .collect::>(); + + let translation_routes = input + .translation_routes + .into_iter() + .map(|entry| { + let route_id = format!("{}=>{}", entry.source_schema, entry.target_schema); + OdinTranslationRouteRecord { + route_id, + source_schema: entry.source_schema, + target_schema: entry.target_schema, + translation_kind: entry.translation_kind, + owner: entry.owner, + version: entry.version, + notes: entry.notes, + } + }) + .collect::>(); + + let snapshot = OdinSnapshotRecord { + snapshot_id: "latest".to_string(), + observed_at: input.observed_at, + verse_count: verses.len() as u32, + service_count: services.len() as u32, + interface_count: interfaces.len() as u32, + observation_stream_count: observation_streams.len() as u32, + source: input.source, + }; + + OdinRecords { + snapshot: Some(snapshot), + verses, + services, + interfaces, + observation_streams, + translation_routes, + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::ports::{ + FixedClock, InterfaceObservation, ObservationStreamObservation, ServiceObservation, + TranslationRouteObservation, VerseObservation, + }; + use anyhow::Result; + use pretty_assertions::assert_eq; + + #[derive(Clone, Debug, Default)] + struct MockIngest; + + impl VerseIngest for MockIngest { + fn observe_verses(&self) -> Result> { + Ok(vec![VerseObservation { + verse_id: "starfire.local".to_string(), + name: "Starfire".to_string(), + role: "coordinator".to_string(), + status: "active".to_string(), + capabilities: vec!["cultmesh".to_string()], + }]) + } + } + + impl ServiceIngest for MockIngest { + fn observe_services(&self) -> Result> { + Ok(vec![ServiceObservation { + service_id: "odin".to_string(), + verse_id: "starfire.local".to_string(), + name: "Odin".to_string(), + state: "active".to_string(), + detail: "typed state".to_string(), + authority: "odin".to_string(), + }]) + } + } + + impl InterfaceIngest for MockIngest { + fn observe_interfaces(&self) -> Result> { + Ok(vec![InterfaceObservation { + provider_id: "odin.allseer".to_string(), + title: "Odin".to_string(), + state: "active".to_string(), + source: "cultmesh".to_string(), + version: Some("1".to_string()), + updated_at: None, + }]) + } + } + + impl ObservationStreamIngest for MockIngest { + fn observe_streams(&self) -> Result> { + Ok(vec![ObservationStreamObservation { + device_id: "periwinkle".to_string(), + stream_id: "motion".to_string(), + kind: "sensor".to_string(), + state: "active".to_string(), + detail: "fresh".to_string(), + owner: "mimir".to_string(), + }]) + } + } + + impl TranslationRouteIngest for MockIngest { + fn observe_translation_routes(&self) -> Result> { + Ok(vec![TranslationRouteObservation { + source_schema: "mimir.eve_sensor_observation.v1".to_string(), + target_schema: "odin.observation_stream.v1".to_string(), + translation_kind: "projection".to_string(), + owner: "odin".to_string(), + version: "v1".to_string(), + notes: "dashboard projection".to_string(), + }]) + } + } + + #[test] + fn pipeline_collects_from_injected_ports() -> Result<()> { + let pipeline = OdinIngestPipeline::new( + MockIngest, + MockIngest, + MockIngest, + MockIngest, + MockIngest, + FixedClock::new("2026-06-03T00:00:00Z"), + ); + + let records = normalize_odin_records(pipeline.collect()?); + + assert_eq!(records.snapshot.unwrap().service_count, 1); + assert_eq!(records.verses[0].verse_id, "starfire.local"); + assert_eq!(records.services[0].authority, "odin"); + assert_eq!( + records.observation_streams[0].stream_key, + "periwinkle:motion:sensor" + ); + assert_eq!( + records.translation_routes[0].route_id, + "mimir.eve_sensor_observation.v1=>odin.observation_stream.v1" + ); + Ok(()) + } +} diff --git a/crates/odin-core/src/ports.rs b/crates/odin-core/src/ports.rs new file mode 100644 index 0000000..33a1feb --- /dev/null +++ b/crates/odin-core/src/ports.rs @@ -0,0 +1,102 @@ +use anyhow::Result; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct VerseObservation { + pub verse_id: String, + pub name: String, + pub role: String, + pub status: String, + pub capabilities: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ServiceObservation { + pub service_id: String, + pub verse_id: String, + pub name: String, + pub state: String, + pub detail: String, + pub authority: String, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct InterfaceObservation { + pub provider_id: String, + pub title: String, + pub state: String, + pub source: String, + pub version: Option, + pub updated_at: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ObservationStreamObservation { + pub device_id: String, + pub stream_id: String, + pub kind: String, + pub state: String, + pub detail: String, + pub owner: String, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct TranslationRouteObservation { + pub source_schema: String, + pub target_schema: String, + pub translation_kind: String, + pub owner: String, + pub version: String, + pub notes: String, +} + +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct OdinInputBatch { + pub observed_at: String, + pub source: String, + pub verses: Vec, + pub services: Vec, + pub interfaces: Vec, + pub observation_streams: Vec, + pub translation_routes: Vec, +} + +pub trait VerseIngest { + fn observe_verses(&self) -> Result>; +} + +pub trait ServiceIngest { + fn observe_services(&self) -> Result>; +} + +pub trait InterfaceIngest { + fn observe_interfaces(&self) -> Result>; +} + +pub trait ObservationStreamIngest { + fn observe_streams(&self) -> Result>; +} + +pub trait TranslationRouteIngest { + fn observe_translation_routes(&self) -> Result>; +} + +pub trait Clock { + fn now(&self) -> String; +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct FixedClock { + now: String, +} + +impl FixedClock { + pub fn new(now: impl Into) -> Self { + Self { now: now.into() } + } +} + +impl Clock for FixedClock { + fn now(&self) -> String { + self.now.clone() + } +} diff --git a/crates/odin-core/src/repository.rs b/crates/odin-core/src/repository.rs new file mode 100644 index 0000000..334e8b0 --- /dev/null +++ b/crates/odin-core/src/repository.rs @@ -0,0 +1,185 @@ +use crate::documents::{ + OdinDocuments, OdinRecords, OdinServiceRecord, OdinSnapshotRecord, OdinVerseRecord, +}; +use anyhow::Result; +use cultmesh_rs::{CultMesh, CultMeshNode, CultMeshNodeOptions}; +use std::path::Path; + +pub trait OdinRepository { + fn persist_records(&mut self, records: &OdinRecords) -> Result<()>; +} + +pub struct CultMeshOdinRepository { + node: CultMeshNode, +} + +impl CultMeshOdinRepository { + pub fn open(store_path: impl AsRef, runtime_id: impl Into) -> Result { + let node = CultMesh::create_node( + store_path, + OdinDocuments, + CultMeshNodeOptions { + runtime_id: runtime_id.into(), + pull_on_start: true, + }, + )?; + Ok(Self { node }) + } + + pub fn get_snapshot(&self) -> Result { + self.node.get_required::("latest") + } + + pub fn get_service(&self, service_id: &str) -> Result> { + self.node.get::(service_id) + } + + pub fn get_verse(&self, verse_id: &str) -> Result> { + self.node.get::(verse_id) + } + + pub fn document_binding_version(&self, document_type: &str) -> Option { + self.node + .documents() + .binding(document_type) + .and_then(|binding| binding.payload_schema_version.clone()) + } +} + +impl OdinRepository for CultMeshOdinRepository { + fn persist_records(&mut self, records: &OdinRecords) -> Result<()> { + if let Some(snapshot) = &records.snapshot { + self.node.put("latest", snapshot)?; + } + for verse in &records.verses { + self.node.put(&verse.verse_id, verse)?; + } + for service in &records.services { + self.node.put(&service.service_id, service)?; + } + for interface in &records.interfaces { + self.node.put(&interface.provider_id, interface)?; + } + for stream in &records.observation_streams { + self.node.put(&stream.stream_key, stream)?; + } + for route in &records.translation_routes { + self.node.put(&route.route_id, route)?; + } + Ok(()) + } +} + +#[derive(Default)] +pub struct InMemoryOdinRepository { + pub records: OdinRecords, +} + +impl OdinRepository for InMemoryOdinRepository { + fn persist_records(&mut self, records: &OdinRecords) -> Result<()> { + self.records = records.clone(); + Ok(()) + } +} + +pub fn persist_pipeline_records( + repository: &mut impl OdinRepository, + records: OdinRecords, +) -> Result { + repository.persist_records(&records)?; + Ok(records) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::documents::ODIN_SERVICE_SCHEMA; + use crate::pipeline::normalize_odin_records; + use crate::ports::{ + InterfaceObservation, ObservationStreamObservation, OdinInputBatch, ServiceObservation, + TranslationRouteObservation, VerseObservation, + }; + use pretty_assertions::assert_eq; + + fn sample_records() -> OdinRecords { + normalize_odin_records(OdinInputBatch { + observed_at: "2026-06-03T00:00:00Z".to_string(), + source: "unit-test".to_string(), + verses: vec![VerseObservation { + verse_id: "starfire.local".to_string(), + name: "Starfire".to_string(), + role: "coordinator".to_string(), + status: "active".to_string(), + capabilities: vec!["cultmesh".to_string()], + }], + services: vec![ServiceObservation { + service_id: "odin".to_string(), + verse_id: "starfire.local".to_string(), + name: "Odin".to_string(), + state: "active".to_string(), + detail: "persisted".to_string(), + authority: "odin".to_string(), + }], + interfaces: vec![InterfaceObservation { + provider_id: "odin.allseer".to_string(), + title: "Odin".to_string(), + state: "active".to_string(), + source: "cultmesh".to_string(), + version: Some("1".to_string()), + updated_at: None, + }], + observation_streams: vec![ObservationStreamObservation { + device_id: "periwinkle".to_string(), + stream_id: "camera".to_string(), + kind: "media".to_string(), + state: "active".to_string(), + detail: "fresh".to_string(), + owner: "mimir".to_string(), + }], + translation_routes: vec![TranslationRouteObservation { + source_schema: "source.v1".to_string(), + target_schema: "target.v1".to_string(), + translation_kind: "projection".to_string(), + owner: "odin".to_string(), + version: "v1".to_string(), + notes: "test".to_string(), + }], + }) + } + + #[test] + fn memory_repository_supports_fast_unit_tests() -> Result<()> { + let records = sample_records(); + let mut repository = InMemoryOdinRepository::default(); + + repository.persist_records(&records)?; + + assert_eq!(repository.records.services[0].service_id, "odin"); + Ok(()) + } + + #[test] + fn cultmesh_repository_round_trips_typed_records() -> Result<()> { + let temp = tempfile::tempdir()?; + let store_path = temp.path().join("odin.cc"); + let records = sample_records(); + + let mut repository = CultMeshOdinRepository::open(&store_path, "odin-test")?; + repository.persist_records(&records)?; + assert_eq!( + repository + .document_binding_version("odin.service") + .as_deref(), + Some(ODIN_SERVICE_SCHEMA) + ); + + let reloaded = CultMeshOdinRepository::open(&store_path, "odin-test-reloaded")?; + assert_eq!(reloaded.get_snapshot()?.service_count, 1); + assert_eq!(reloaded.get_service("odin")?.unwrap().detail, "persisted"); + assert_eq!( + reloaded.get_verse("starfire.local")?.unwrap().capabilities, + vec!["cultmesh".to_string()] + ); + Ok(()) + } +} diff --git a/docs/architecture.md b/docs/architecture.md index 3f0f383..a56339e 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -22,10 +22,39 @@ host / device / service probes This first path proves the operator surface and persistent state. It does not yet pretend to be full peer exchange. +## Rust Target Spine + +The target Odin machine is Rust-first and typed-state-first: + +```text +Verse / host / device / provider inputs + -> ingest ports + -> normalization + -> typed Odin records + -> CultMesh node + -> CultCache .cc persistence + -> CultNet/CultMesh document registry + -> Eve/CultUI projection and agent affordance packets +``` + +The first Rust core lives in `crates/odin-core`: + +- `documents.rs`: typed Odin records and the CultMesh document set. +- `ports.rs`: narrow ingest traits plus clock injection for deterministic tests. +- `pipeline.rs`: collection and normalization from input observations to typed records. +- `repository.rs`: `OdinRepository` abstraction, in-memory mock repository, and CultMesh-backed repository. + +The Rust spine owns the future architecture. The CommonJS daemon remains the +legacy operational body until each organ crosses this typed boundary. + ## Runtime Body Odin's executable body is split by ownership: +- `crates/odin-core`: Rust target core. Owns typed Odin documents, ingest + ports, normalization, and CultMesh/CultCache repository boundaries. This is + the replacement spine; JavaScript remains legacy runtime/probe scaffolding + until each organ has crossed the typed boundary. - `src/odin-coordinator.cjs`: process lifecycle, refresh loop, persistence, health, and transport wiring. - `src/odin/config.cjs`: runtime paths, seed deck URLs, intervals, and CultLib module path setup. - `src/odin/documents.cjs`: CultCache/CultMesh document definitions accepted by Odin. @@ -68,6 +97,19 @@ Verse announcement - Renderers lower surfaces only. If a renderer fixes network truth, the machine is split-brained. - CultCache is the durable state substrate; CultNet is the wire vocabulary; CultMesh is the Verse and peer-consensus layer. - The Eve surface carries explicit `verse`, `service`, and `observation-stream` nodes. Compact renderers may derive visual facets from those nodes, but may not invent observation truth. +- Rust organs must accept mocked inputs through narrow traits. Unit tests prove + local invariants; pipeline smokes prove adjacent typed handoff; full daemon + boots are not the only test path. +- JSON is not state authority. It is allowed only for schema publication, + debugging, compatibility export, or external xenos boundaries. + +## Test Surfaces + +Current Rust verification: + +- `pipeline_collects_from_injected_ports`: proves ingest ports and clock injection. +- `memory_repository_supports_fast_unit_tests`: proves repository consumers can test without CultMesh. +- `cultmesh_repository_round_trips_typed_records`: proves typed records persist through CultMesh/CultCache and reload from `.cc`. ## Service Architecture Contract diff --git a/package.json b/package.json index 7268efd..c5680b2 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "gjallar:persona": "node scripts/write-gjallar-persona-cc.mjs inspect", "gjallar:persona:write": "node scripts/write-gjallar-persona-cc.mjs write", "gjallar:build": "dotnet build src/Gjallar/Gjallar.csproj", + "rust:test": "cargo test --workspace", "check": "node scripts/check-syntax.cjs", "start": "node src/odin-coordinator.cjs" },