diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3a018a9..ce04650 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -41,7 +41,12 @@ Echo is a deterministic, renderer-agnostic engine. We prioritize: ## Testing Expectations - Write tests before or alongside code changes. - `cargo test` must pass locally before PR submission. -- Add unit/integration coverage for new logic; Lua/TypeScript tooling will regain coverage when reintroduced. +- Add unit/integration coverage for new logic; Rhai/TypeScript tooling will regain coverage when reintroduced. +- For WASM / living specs: + - Install toolchain target: `rustup target add wasm32-unknown-unknown`. + - Install Trunk once: `cargo install --locked trunk`. + - Dev loop for Spec-000: from repo root run `make spec-000-dev` (hot reload at http://127.0.0.1:8080). + - Release build: `make spec-000-build` (outputs to `specs/spec-000-rewrite/dist/`). ## Documentation & Telemetry - Update relevant docs in `docs/` whenever behavior or architecture changes. @@ -59,7 +64,7 @@ Echo is a deterministic, renderer-agnostic engine. We prioritize: ## Code Style - Rust code must pass `cargo fmt` and `cargo clippy` without warnings. -- Lua scripts should remain deterministic (no uncontrolled globals, RNG via engine services). +- Rhai scripts should remain deterministic (no uncontrolled globals, RNG via engine services). - TypeScript tooling (when active) lives in `reference/typescript/`; follow local lint configs when reactivated. - Avoid non-deterministic APIs (no wall-clock, no uncontrolled randomness). Use Echo’s deterministic services. diff --git a/Cargo.lock b/Cargo.lock index c2e218e..5c3c42a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -88,12 +88,56 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" +[[package]] +name = "anstream" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + [[package]] name = "anstyle" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + [[package]] name = "anyhow" version = "1.0.100" @@ -129,6 +173,12 @@ dependencies = [ "x11rb", ] +[[package]] +name = "arc-swap" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" + [[package]] name = "arrayref" version = "0.3.9" @@ -156,6 +206,17 @@ dependencies = [ "libloading", ] +[[package]] +name = "async-recursion" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "async-trait" version = "0.1.89" @@ -173,12 +234,120 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +[[package]] +name = "attribute-derive" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f1ee502851995027b06f99f5ffbeffa1406b38d0b318a1ebfa469332c6cbafd" +dependencies = [ + "attribute-derive-macro", + "derive-where", + "manyhow", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "attribute-derive-macro" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3601467f634cfe36c4780ca9c75dea9a5b34529c1f2810676a337e7e0997f954" +dependencies = [ + "collection_literals", + "interpolator", + "manyhow", + "proc-macro-utils 0.8.0", + "proc-macro2", + "quote", + "quote-use", + "syn", +] + [[package]] name = "autocfg" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "axum" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" +dependencies = [ + "async-trait", + "axum-core", + "base64 0.21.7", + "bitflags 1.3.2", + "bytes", + "futures-util", + "http 0.2.12", + "http-body", + "hyper", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sha1", + "sync_wrapper", + "tokio", + "tokio-tungstenite", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http 0.2.12", + "http-body", + "mime", + "rustversion", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-server" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "447f28c85900215cc1bea282f32d4a2f22d55c5a300afdfbc661c8d6a632e063" +dependencies = [ + "arc-swap", + "bytes", + "futures-util", + "http 0.2.12", + "http-body", + "hyper", + "pin-project-lite", + "rustls", + "rustls-pemfile", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + [[package]] name = "base64" version = "0.22.1" @@ -231,6 +400,15 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" +[[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 = "block2" version = "0.5.1" @@ -266,6 +444,12 @@ dependencies = [ "syn", ] +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "byteorder-lite" version = "0.1.0" @@ -329,6 +513,12 @@ dependencies = [ "wayland-client", ] +[[package]] +name = "camino" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "276a59bf2b2c967788139340c9f0c5b12d7fd6630315c15c217e559de85d2609" + [[package]] name = "cast" version = "0.3.0" @@ -399,6 +589,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c26d721170e0295f191a69bd9a1f93efcdb0aff38684b61ab5750468972e5f5" dependencies = [ "clap_builder", + "clap_derive", ] [[package]] @@ -407,8 +598,22 @@ version = "4.5.51" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75835f0c7bf681bfd05abe44e965760fea999a5286c6eb2d59883634fd02011a" dependencies = [ + "anstream", "anstyle", "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -437,6 +642,18 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "collection_literals" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2550f75b8cfac212855f6b1885455df8eaee8fe8e246b647d69146142e016084" + +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + [[package]] name = "combine" version = "4.6.7" @@ -456,6 +673,19 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "config" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68578f196d2a33ff61b27fae256c3164f65e36382648e30666dde05b8cc9dfdf" +dependencies = [ + "convert_case", + "nom", + "pathdiff", + "serde", + "toml", +] + [[package]] name = "console_error_panic_hook" version = "0.1.7" @@ -466,12 +696,41 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "const_format" +version = "0.2.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7faa7469a93a566e9ccc1c73fe783b4a65c274c5ace346038dca9c39fe0030ad" +dependencies = [ + "const_format_proc_macros", +] + +[[package]] +name = "const_format_proc_macros" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + [[package]] name = "constant_time_eq" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -533,6 +792,15 @@ dependencies = [ "libc", ] +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + [[package]] name = "crc32fast" version = "1.5.0" @@ -554,7 +822,7 @@ dependencies = [ "clap", "criterion-plot", "is-terminal", - "itertools", + "itertools 0.10.5", "num-traits", "once_cell", "oorandom", @@ -573,7 +841,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" dependencies = [ "cast", - "itertools", + "itertools 0.10.5", ] [[package]] @@ -588,18 +856,68 @@ 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", + "typenum", +] + [[package]] name = "cursor-icon" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" +[[package]] +name = "dashmap" +version = "5.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +dependencies = [ + "cfg-if", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "data-encoding" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" + [[package]] name = "data-url" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be1e0bca6c3637f992fc1cc7cbc52a78c1ef6db076dbf1059c4323d6a2048376" +[[package]] +name = "derive-where" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef941ded77d15ca19b40374869ac6000af1c9f2a4c0f3d4c70926287e6364a8f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + [[package]] name = "directories" version = "5.0.1" @@ -678,6 +996,12 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" +[[package]] +name = "drain_filter_polyfill" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "669a445ee724c5c69b1b06fe0b63e70a1c84bc9bb7d9696cd4f4e3ec45050408" + [[package]] name = "echo-app-core" version = "0.1.0" @@ -743,6 +1067,20 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "echo-session-ws-gateway" +version = "0.1.0" +dependencies = [ + "anyhow", + "axum", + "axum-server", + "clap", + "futures-util", + "tokio", + "tracing", + "tracing-subscriber", +] + [[package]] name = "ecolor" version = "0.32.3" @@ -1084,6 +1422,105 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "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 = "gethostname" version = "1.1.0" @@ -1101,8 +1538,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -1134,6 +1573,40 @@ version = "0.29.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8babf46d4c1c9d92deac9f7be466f76dfc4482b6452fc5024b5e8daf6ffeb3ee" +[[package]] +name = "gloo-net" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06f627b1a58ca3d42b45d6104bf1e1a03799df472df00988b6ba21accc10580" +dependencies = [ + "futures-channel", + "futures-core", + "futures-sink", + "gloo-utils", + "http 1.4.0", + "js-sys", + "pin-project", + "serde", + "serde_json", + "thiserror 1.0.69", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "gloo-utils" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5555354113b18c547c1d3a98fbf7fb32a9ff4f6fa112ce823a21641a0ba3aa" +dependencies = [ + "js-sys", + "serde", + "serde_json", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "glow" version = "0.16.0" @@ -1206,6 +1679,25 @@ dependencies = [ "bitflags 2.10.0", ] +[[package]] +name = "h2" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.12", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "half" version = "2.7.1" @@ -1218,6 +1710,12 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + [[package]] name = "hashbrown" version = "0.15.5" @@ -1236,6 +1734,12 @@ dependencies = [ "foldhash 0.2.0", ] +[[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" @@ -1254,6 +1758,83 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" +[[package]] +name = "html-escape" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d1ad449764d627e22bfd7cd5e8868264fc9236e07c752972b4080cd351cb476" +dependencies = [ + "utf8-width", +] + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.12", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "0.14.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http 0.2.12", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2 0.5.10", + "tokio", + "tower-service", + "tracing", + "want", +] + [[package]] name = "icu_collections" version = "2.1.1" @@ -1386,6 +1967,21 @@ dependencies = [ "hashbrown 0.16.1", ] +[[package]] +name = "interpolator" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71dd52191aae121e8611f1e8dc3e324dd0dd1dee1e6dd91d10ee07a3cfb4d9d8" + +[[package]] +name = "inventory" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc61209c082fbeb19919bee74b176221b27223e27b65d781eb91af24eb1fb46e" +dependencies = [ + "rustversion", +] + [[package]] name = "is-terminal" version = "0.4.17" @@ -1397,6 +1993,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + [[package]] name = "itertools" version = "0.10.5" @@ -1406,6 +2008,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.15" @@ -1455,38 +2066,185 @@ dependencies = [ ] [[package]] -name = "khronos-egl" -version = "6.0.0" +name = "khronos-egl" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" +dependencies = [ + "libc", + "libloading", + "pkg-config", +] + +[[package]] +name = "khronos_api" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" + +[[package]] +name = "kurbo" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c62026ae44756f8a599ba21140f350303d4f08dcdcc71b5ad9c9bb8128c13c62" +dependencies = [ + "arrayvec", + "euclid", + "smallvec", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "leptos" +version = "0.6.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cbb3237c274dadf00dcc27db96c52601b40375117178fb24a991cda073624f0" +dependencies = [ + "cfg-if", + "leptos_config", + "leptos_dom", + "leptos_macro", + "leptos_reactive", + "leptos_server", + "server_fn", + "tracing", + "typed-builder", + "typed-builder-macro", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "leptos_config" +version = "0.6.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62ed778611380ddea47568ac6ad6ec5158d39b5bd59e6c4dcd24efc15dc3dc0d" +dependencies = [ + "config", + "regex", + "serde", + "thiserror 1.0.69", + "typed-builder", +] + +[[package]] +name = "leptos_dom" +version = "0.6.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8401c46c86c1f4c16dcb7881ed319fcdca9cda9b9e78a6088955cb423afcf119" +dependencies = [ + "async-recursion", + "cfg-if", + "drain_filter_polyfill", + "futures", + "getrandom 0.2.16", + "html-escape", + "indexmap", + "itertools 0.12.1", + "js-sys", + "leptos_reactive", + "once_cell", + "pad-adapter", + "paste", + "rustc-hash 1.1.0", + "serde", + "serde_json", + "server_fn", + "smallvec", + "tracing", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "leptos_hot_reload" +version = "0.6.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" +checksum = "6cb53d4794240b684a2f4be224b84bee9e62d2abc498cf2bcd643cd565e01d96" dependencies = [ - "libc", - "libloading", - "pkg-config", + "anyhow", + "camino", + "indexmap", + "parking_lot", + "proc-macro2", + "quote", + "rstml", + "serde", + "syn", + "walkdir", ] [[package]] -name = "khronos_api" -version = "3.1.0" +name = "leptos_macro" +version = "0.6.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" +checksum = "4b13bc3db70715cd8218c4535a5af3ae3c0e5fea6f018531fc339377b36bc0e0" +dependencies = [ + "attribute-derive", + "cfg-if", + "convert_case", + "html-escape", + "itertools 0.12.1", + "leptos_hot_reload", + "prettyplease", + "proc-macro-error2", + "proc-macro2", + "quote", + "rstml", + "server_fn_macro", + "syn", + "tracing", + "uuid", +] [[package]] -name = "kurbo" -version = "0.11.3" +name = "leptos_reactive" +version = "0.6.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62026ae44756f8a599ba21140f350303d4f08dcdcc71b5ad9c9bb8128c13c62" +checksum = "e4161acbf80f59219d8d14182371f57302bc7ff81ee41aba8ba1ff7295727f23" dependencies = [ - "arrayvec", - "euclid", - "smallvec", + "base64 0.22.1", + "cfg-if", + "futures", + "indexmap", + "js-sys", + "oco_ref", + "paste", + "pin-project", + "rustc-hash 1.1.0", + "self_cell", + "serde", + "serde-wasm-bindgen", + "serde_json", + "slotmap", + "thiserror 1.0.69", + "tracing", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", ] [[package]] -name = "lazy_static" -version = "1.5.0" +name = "leptos_server" +version = "0.6.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +checksum = "4a97eb90a13f71500b831c7119ddd3bdd0d7ae0a6b0487cade4fddeed3b8c03f" +dependencies = [ + "inventory", + "lazy_static", + "leptos_macro", + "leptos_reactive", + "serde", + "server_fn", + "thiserror 1.0.69", + "tracing", +] [[package]] name = "libc" @@ -1569,6 +2327,44 @@ dependencies = [ "libc", ] +[[package]] +name = "manyhow" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f91ea592d76c0b6471965708ccff7e6a5d277f676b90ab31f4d3f3fc77fade64" +dependencies = [ + "manyhow-macros", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "manyhow-macros" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c64621e2c08f2576e4194ea8be11daf24ac01249a4f53cd8befcbb7077120ead" +dependencies = [ + "proc-macro-utils 0.8.0", + "proc-macro2", + "quote", +] + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + [[package]] name = "memchr" version = "2.7.6" @@ -1627,6 +2423,12 @@ dependencies = [ "walkdir", ] +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.8.9" @@ -1720,6 +2522,16 @@ 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 = "nu-ansi-term" version = "0.50.3" @@ -2040,12 +2852,28 @@ dependencies = [ "objc2-foundation 0.2.2", ] +[[package]] +name = "oco_ref" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c51ebcefb2f0b9a5e0bea115532c8ae4215d1b01eff176d0f4ba4192895c2708" +dependencies = [ + "serde", + "thiserror 1.0.69", +] + [[package]] name = "once_cell" version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + [[package]] name = "oorandom" version = "11.1.5" @@ -2094,6 +2922,12 @@ dependencies = [ "ttf-parser", ] +[[package]] +name = "pad-adapter" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56d80efc4b6721e8be2a10a5df21a30fa0b470f1539e53d8b4e6e75faf938b63" + [[package]] name = "parking_lot" version = "0.12.5" @@ -2123,6 +2957,12 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "pathdiff" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" + [[package]] name = "percent-encoding" version = "2.3.2" @@ -2205,6 +3045,12 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "pkg-config" version = "0.3.32" @@ -2296,13 +3142,89 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" +[[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-macro-crate" version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" dependencies = [ - "toml_edit", + "toml_edit 0.23.7", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-utils" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f59e109e2f795a5070e69578c4dc101068139f74616778025ae1011d4cd41a8" +dependencies = [ + "proc-macro2", + "quote", + "smallvec", +] + +[[package]] +name = "proc-macro-utils" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eeaf08a13de400bc215877b5bdc088f241b12eb42f0a548d3390dc1c56bb7071" +dependencies = [ + "proc-macro2", + "quote", + "smallvec", ] [[package]] @@ -2314,6 +3236,19 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proc-macro2-diagnostics" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "version_check", + "yansi", +] + [[package]] name = "profiling" version = "1.0.17" @@ -2378,6 +3313,28 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "quote-use" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9619db1197b497a36178cfc736dc96b271fe918875fbf1344c436a7e93d0321e" +dependencies = [ + "quote", + "quote-use-macros", +] + +[[package]] +name = "quote-use-macros" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82ebfb7faafadc06a7ab141a6f67bcfb24cb8beb158c6fe933f2f035afa99f35" +dependencies = [ + "proc-macro-utils 0.10.0", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "r-efi" version = "5.3.0" @@ -2551,6 +3508,20 @@ dependencies = [ "bytemuck", ] +[[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.16", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + [[package]] name = "rmg-benches" version = "0.1.0" @@ -2642,6 +3613,20 @@ version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" +[[package]] +name = "rstml" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe542870b8f59dd45ad11d382e5339c9a1047cde059be136a7016095bbdefa77" +dependencies = [ + "proc-macro2", + "proc-macro2-diagnostics", + "quote", + "syn", + "syn_derive", + "thiserror 1.0.69", +] + [[package]] name = "rustc-hash" version = "1.1.0" @@ -2680,6 +3665,37 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "rustls" +version = "0.21.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" +dependencies = [ + "log", + "ring", + "rustls-webpki", + "sct", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.7", +] + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "rustversion" version = "1.0.22" @@ -2725,6 +3741,16 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "sctk-adwaita" version = "0.10.1" @@ -2738,6 +3764,21 @@ dependencies = [ "tiny-skia", ] +[[package]] +name = "self_cell" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16c2f82143577edb4921b71ede051dac62ca3c16084e918bf7b40c96ae10eb33" + +[[package]] +name = "send_wrapper" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" +dependencies = [ + "futures-core", +] + [[package]] name = "serde" version = "1.0.228" @@ -2749,13 +3790,24 @@ dependencies = [ ] [[package]] -name = "serde-value" -version = "0.7.0" +name = "serde-value" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" +dependencies = [ + "ordered-float 2.10.1", + "serde", +] + +[[package]] +name = "serde-wasm-bindgen" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" +checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b" dependencies = [ - "ordered-float 2.10.1", + "js-sys", "serde", + "wasm-bindgen", ] [[package]] @@ -2791,6 +3843,113 @@ dependencies = [ "serde_core", ] +[[package]] +name = "serde_path_to_error" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" +dependencies = [ + "itoa", + "serde", + "serde_core", +] + +[[package]] +name = "serde_qs" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0431a35568651e363364210c91983c1da5eb29404d9f0928b67d4ebcfa7d330c" +dependencies = [ + "percent-encoding", + "serde", + "thiserror 1.0.69", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "server_fn" +version = "0.6.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fae7a3038a32e5a34ba32c6c45eb4852f8affaf8b794ebfcd4b1099e2d62ebe" +dependencies = [ + "bytes", + "ciborium", + "const_format", + "dashmap", + "futures", + "gloo-net", + "http 1.4.0", + "js-sys", + "once_cell", + "send_wrapper", + "serde", + "serde_json", + "serde_qs", + "server_fn_macro_default", + "thiserror 1.0.69", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "xxhash-rust", +] + +[[package]] +name = "server_fn_macro" +version = "0.6.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faaaf648c6967aef78177c0610478abb5a3455811f401f3c62d10ae9bd3901a1" +dependencies = [ + "const_format", + "convert_case", + "proc-macro2", + "quote", + "syn", + "xxhash-rust", +] + +[[package]] +name = "server_fn_macro_default" +version = "0.6.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f2aa8119b558a17992e0ac1fd07f080099564f24532858811ce04f742542440" +dependencies = [ + "server_fn_macro", + "syn", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -2806,6 +3965,15 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signal-hook-registry" +version = "1.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7664a098b8e616bdfcc2dc0e9ac44eb231eedf41db4e9fe95d8d32ec728dedad" +dependencies = [ + "libc", +] + [[package]] name = "simd-adler32" version = "0.3.7" @@ -2839,6 +4007,7 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" dependencies = [ + "serde", "version_check", ] @@ -2920,6 +4089,16 @@ dependencies = [ "serde", ] +[[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.1" @@ -2930,6 +4109,14 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "spec-000-rewrite" +version = "0.1.0" +dependencies = [ + "console_error_panic_hook", + "leptos", +] + [[package]] name = "spirv" version = "0.3.0+sdk-1.3.268.0" @@ -2960,6 +4147,12 @@ dependencies = [ "float-cmp", ] +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "svgtypes" version = "0.15.3" @@ -2981,6 +4174,24 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn_derive" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1329189c02ff984e9736652b1631330da25eaa6bc639089ed4915d25446cbe7b" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + [[package]] name = "synstructure" version = "0.13.2" @@ -3133,7 +4344,8 @@ dependencies = [ "libc", "mio", "pin-project-lite", - "socket2", + "signal-hook-registry", + "socket2 0.6.1", "tokio-macros", "windows-sys 0.61.2", ] @@ -3149,6 +4361,62 @@ dependencies = [ "syn", ] +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite", +] + +[[package]] +name = "tokio-util" +version = "0.7.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" +dependencies = [ + "bytes", + "futures-core", + "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 0.6.11", + "toml_edit 0.22.27", +] + +[[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_datetime" version = "0.7.3" @@ -3158,6 +4426,20 @@ dependencies = [ "serde_core", ] +[[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 0.6.11", + "toml_write", + "winnow", +] + [[package]] name = "toml_edit" version = "0.23.7" @@ -3165,7 +4447,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d" dependencies = [ "indexmap", - "toml_datetime", + "toml_datetime 0.7.3", "toml_parser", "winnow", ] @@ -3179,6 +4461,40 @@ dependencies = [ "winnow", ] +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[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.43" @@ -3229,20 +4545,49 @@ version = "0.3.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" dependencies = [ + "matchers", "nu-ansi-term", + "once_cell", + "regex-automata", "sharded-slab", "smallvec", "thread_local", + "tracing", "tracing-core", "tracing-log", ] +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + [[package]] name = "ttf-parser" version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" +[[package]] +name = "tungstenite" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e3dac10fd62eaf6617d3a904ae222845979aec67c615d1c842b4002c7666fb9" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http 0.2.12", + "httparse", + "log", + "rand 0.8.5", + "sha1", + "thiserror 1.0.69", + "url", + "utf-8", +] + [[package]] name = "type-map" version = "0.5.1" @@ -3252,6 +4597,32 @@ dependencies = [ "rustc-hash 2.1.1", ] +[[package]] +name = "typed-builder" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77739c880e00693faef3d65ea3aad725f196da38b22fdc7ea6ded6e1ce4d3add" +dependencies = [ + "typed-builder-macro", +] + +[[package]] +name = "typed-builder-macro" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f718dfaf347dcb5b983bfc87608144b0bad87970aebcbea5ce44d2a30c08e63" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + [[package]] name = "unarray" version = "0.1.4" @@ -3282,6 +4653,18 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[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.7" @@ -3300,7 +4683,7 @@ version = "0.45.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80be9b06fbae3b8b303400ab20778c80bbaf338f563afe567cf3c9eea17b47ef" dependencies = [ - "base64", + "base64 0.22.1", "data-url", "flate2", "imagesize", @@ -3316,12 +4699,41 @@ dependencies = [ "xmlwriter", ] +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf8-width" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1292c0d970b54115d14f2492fe0170adf21d68a1de108eebc51c1df4f346a091" + [[package]] name = "utf8_iter" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uuid" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" +dependencies = [ + "getrandom 0.3.4", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "valuable" version = "0.1.1" @@ -3353,6 +4765,15 @@ dependencies = [ "winapi-util", ] +[[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" @@ -3458,6 +4879,19 @@ dependencies = [ "syn", ] +[[package]] +name = "wasm-streams" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "wayland-backend" version = "0.3.11" @@ -4304,6 +5738,18 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" +[[package]] +name = "xxhash-rust" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3" + +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + [[package]] name = "yoke" version = "0.8.1" diff --git a/Cargo.toml b/Cargo.toml index 13892e2..4d87aab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,9 @@ members = [ "crates/echo-session-proto", "crates/echo-session-service", "crates/echo-session-client", - "crates/echo-graph" + "crates/echo-graph", + "crates/echo-session-ws-gateway", + "specs/spec-000-rewrite" ] resolver = "2" diff --git a/Makefile b/Makefile index 0154108..4ef83ca 100644 --- a/Makefile +++ b/Makefile @@ -126,3 +126,12 @@ bench-bake: vendor-d3 bench-open-inline: @open docs/benchmarks/report-inline.html + +# Spec-000 (WASM) helpers +.PHONY: spec-000-dev spec-000-build + +spec-000-dev: + @cd specs/spec-000-rewrite && trunk serve + +spec-000-build: + @cd specs/spec-000-rewrite && trunk build --release diff --git a/README.md b/README.md index fff437e..7dc0067 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,18 @@ -# Echo +# ![[echo-white.svg]] +--- + +> [!note] +> # ⚠️ NOTICE: Echo is Becoming the JITOS Kernel +> +> Echo is now the kernel for **JITOS**—the world's first causal operating system, where history is immutable, execution is deterministic, and debugging means time-traveling to exact states, instead of hopes and prayers. +> +> **THE REVΩLUTION WILL BE DETERMINISTIC.** +> **THE PROOF IS MATHEMATICAL.** +> **TIME WILL TELL.** +> +> 🔗 [AIΩN](https://github.com/flyingrobots/aion) | [JITOS RFCs](https://github.com/flyingrobots/jitos) | [CΩMPUTER Paper](https://github.com/flyingrobots/aion-computer-book) ```rust //! ░▒▓████████▓▒░▒▓██████▓▒░░▒▓█▓▒░░▒▓█▓▒░░▒▓██████▓▒░ @@ -34,14 +46,6 @@ RMG provides atomic, in-place edits of recursive meta-graphs with deterministic Echo is a mathematically rigorous game engine that replaces traditional OOP with deterministic graph rewriting, enabling time-travel debugging, perfect replay, and Git-like branching for game states. -## Developer: Running Benchmarks - -- Command (live dashboard): `make bench-report` - - Runs `cargo bench -p rmg-benches`, starts a local server, and opens the dashboard at `http://localhost:8000/docs/benchmarks/`. -- Command (offline static file): `make bench-bake` - - Runs benches and bakes `docs/benchmarks/report-inline.html` with results injected so it works over `file://` (no server required). -- Docs: see `crates/rmg-benches/benches/README.md` for details, tips, and report paths. - ### Core Principles | Principle | Description | @@ -64,6 +68,15 @@ Echo runs on something called an **RMG (Recursive Meta-Graph)**. Think of it as Echo doesn’t “update objects.” It _rewrites_ parts of the graph using a set of deterministic rules. That’s what “graph rewriting” means. +## JITOS Engineering Standard (Living Specs) + +Echo follows the JITOS Engineering Standard: every SPEC is simultaneously documentation, implementation, interactive demo (WASM), living test, and contributor certification. See `docs/METHODOLOGY.md` for the full 5x Duty model and workflow. + +### WASM Dev Quickstart (Spec-000) +- Prereq: `rustup target add wasm32-unknown-unknown` and `cargo install --locked trunk` +- Dev: `make spec-000-dev` (hot reload at http://127.0.0.1:8080) +- Build: `make spec-000-build` (outputs to `specs/spec-000-rewrite/dist/`) + ### Why Echo's Cool - **Deterministic:** same inputs = same world every time. @@ -146,7 +159,7 @@ echo/ │ ├── rmg-geom/ (Geometry primitives: AABB, transforms, broad-phase) │ ├── rmg-benches/ (Criterion microbenchmarks: snapshot_hash, scheduler_drain) │ ├── rmg-wasm/ (WebAssembly bindings for tools and web) -│ ├── rmg-ffi/ (C ABI for Lua/host integration) +│ ├── rmg-ffi/ (C ABI for host integrations; Rhai is embedded directly) │ └── rmg-cli/ (Command-line interface, demos launcher) ├── docs/ (Comprehensive specifications and diagrams) └── scripts/ (Build automation, benchmarking) @@ -294,6 +307,14 @@ Phase 1 MVP (active development on echo/pr-12-snapshot-bench): - `crates/rmg-core/tests/permutation_commute_tests.rs` — Determinism proofs - `crates/rmg-benches/benches/snapshot_hash.rs` — Hashing throughput +## Developer: Running Benchmarks + +- Command (live dashboard): `make bench-report` + - Runs `cargo bench -p rmg-benches`, starts a local server, and opens the dashboard at `http://localhost:8000/docs/benchmarks/`. +- Command (offline static file): `make bench-bake` + - Runs benches and bakes `docs/benchmarks/report-inline.html` with results injected so it works over `file://` (no server required). +- Docs: see `crates/rmg-benches/benches/README.md` for details, tips, and report paths. + --- ## Contributing diff --git a/WASM-TASKS.md b/WASM-TASKS.md new file mode 100644 index 0000000..584318d --- /dev/null +++ b/WASM-TASKS.md @@ -0,0 +1,41 @@ + + + +# WASM Task Checklist + +Policy: write failing tests first, then implement; check off tasks only when tests and docs are updated. + +## P0 — Bootstrap & Scaffold +- [ ] Scaffold `specs/spec-000-rewrite` Leptos+Trunk app (CSR) with `index.html`, `src/lib.rs`, panic hook, hot-reload. +- [ ] Add workspace membership and `make spec-000-{dev,build}` helpers. +- [ ] Failing check: `cargo check -p spec-000-rewrite --target wasm32-unknown-unknown` in CI job (Trunk build). + +## P1 — Kernel Bindings & Types +- [ ] Add `wasm-bindgen` feature to kernel crate (or shim crate) and expose minimal RMG/rewrite API (add node, set field, connect, tombstone, materialize). +- [ ] Create shared DTO crate (`echo-wasm-abi`) with serde + wasm-bindgen-friendly types for graph and rewrite log; reuse in UI. +- [ ] Failing tests: wasm-bindgen unit test exercising add/set/connect/tombstone round-trip serialization. + +## P1 — UI MVP (Living Spec) +- [ ] Render graph (SVG/canvas) from serialized RMG; simple layout. +- [ ] Render rewrite log; click-to-time-travel replays history via kernel API. +- [ ] “Apply Rewrite” panel hooks to kernel methods; updates view reactively. +- [ ] Failing tests: screenshot/DOM snapshot via Playwright (Trunk serve) or headless wasm-bindgen tests for state transitions. + +## P2 — Certification & Win Condition +- [ ] Implement completion detector that issues a completion hash/badge when the user reaches target state (Spec-000). +- [ ] Persist/emit completion hash for PR inclusion; document the flow. +- [ ] Failing test: deterministic hash for canonical walkthrough sequence. + +## P2 — Tooling & CI +- [ ] GitHub Action: build spec-000 with Trunk (wasm32-unknown-unknown), cache target/Trunk, artifact the dist. +- [ ] Size guard: assert wasm bundle < configured budget; fail if exceeded. +- [ ] Lint: add `cargo fmt`/`clippy` (wasm target) gate for spec crates. + +## P3 — UX & Resilience +- [ ] Error surface: UI shows kernel errors (invalid rewrite, payload too large). +- [ ] Offline-first: bundle assets, graceful fallback when no network. +- [ ] Performance pass: incremental graph diffing instead of full redraw; fast layout for ≤200 nodes. +- [ ] Accessibility: keyboard navigation for rewrites; ARIA on controls. + +## P3 — Future Spec Template +- [ ] Turn spec-000 into a `spec-template/` scaffold script for future specs (copy, rename, wire to new kernel module, add win condition). diff --git a/assets/echo-white-2.svg b/assets/echo-white-2.svg new file mode 100644 index 0000000..87cdfcd --- /dev/null +++ b/assets/echo-white-2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/echo-white.svg b/assets/echo-white.svg new file mode 100644 index 0000000..4b958ef --- /dev/null +++ b/assets/echo-white.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/crates/echo-session-client/src/lib.rs b/crates/echo-session-client/src/lib.rs index 948441c..217fd81 100644 --- a/crates/echo-session-client/src/lib.rs +++ b/crates/echo-session-client/src/lib.rs @@ -6,7 +6,7 @@ use anyhow::{anyhow, Result}; use echo_session_proto::{ wire::{decode_message, encode_message}, - HandshakePayload, Message, Notification, RmgFrame, RmgId, + HandshakePayload, Message, Notification, NotifyKind, NotifyScope, RmgFrame, RmgId, }; use std::io::{self, ErrorKind, Read, Write}; use std::os::unix::net::UnixStream; @@ -209,6 +209,14 @@ fn run_message_loop( Ok((Message::Notification(n), _, _)) => { let _ = notif_tx.send(n); } + Ok((Message::Error(err), _, _)) => { + let _ = notif_tx.send(Notification { + kind: NotifyKind::Error, + scope: NotifyScope::Local, + title: format!("{} ({})", err.name, err.code), + body: Some(err.message), + }); + } _ => continue, } } diff --git a/crates/echo-session-service/src/main.rs b/crates/echo-session-service/src/main.rs index 5401be7..26b53d5 100644 --- a/crates/echo-session-service/src/main.rs +++ b/crates/echo-session-service/src/main.rs @@ -9,7 +9,7 @@ use echo_graph::{RmgFrame, RmgSnapshot}; use echo_session_proto::{ default_socket_path, wire::{decode_message, encode_message}, - AckStatus, HandshakeAckPayload, Message, RmgId, + AckStatus, ErrorPayload, HandshakeAckPayload, Message, RmgId, }; use serde::{Deserialize, Serialize}; use std::collections::{HashMap, HashSet}; @@ -178,6 +178,73 @@ mod tests { "no packet should be sent on gap" ); } + + #[tokio::test] + async fn non_owner_publish_is_rejected_with_error() { + let hub = Arc::new(Mutex::new(HubState::default())); + let (owner, _rx_owner) = add_conn(&hub).await; + let (attacker, mut rx_attacker) = add_conn(&hub).await; + let (subscriber, mut rx_sub) = add_conn(&hub).await; + + handle_message(Message::SubscribeRmg { rmg_id: 7 }, subscriber, &hub) + .await + .unwrap(); + + handle_message( + Message::RmgStream { + rmg_id: 7, + frame: RmgFrame::Snapshot(RmgSnapshot { + epoch: 0, + graph: RenderGraph::default(), + state_hash: None, + }), + }, + owner, + &hub, + ) + .await + .unwrap(); + timeout(Duration::from_secs(1), rx_sub.recv()) + .await + .ok() + .flatten() + .expect("subscriber received snapshot"); + + let err = handle_message( + Message::RmgStream { + rmg_id: 7, + frame: RmgFrame::Diff(RmgDiff { + from_epoch: 0, + to_epoch: 1, + ops: vec![], + state_hash: None, + }), + }, + attacker, + &hub, + ) + .await; + assert!(err.is_err()); + + let pkt = timeout(Duration::from_secs(1), rx_attacker.recv()) + .await + .ok() + .flatten() + .expect("attacker receives error"); + let (msg, _ts, _) = decode_message(&pkt).expect("decode error payload"); + match msg { + Message::Error(payload) => { + assert_eq!(payload.name, "E_FORBIDDEN_PUBLISH"); + assert_eq!(payload.code, 403); + } + other => panic!("expected error, got {:?}", other), + } + + assert!( + rx_sub.try_recv().is_err(), + "subscriber should not receive attacker diff" + ); + } } #[derive(Default)] @@ -385,44 +452,104 @@ async fn handle_message(msg: Message, conn_id: u64, hub: &Arc>) } } Message::RmgStream { rmg_id, frame } => { - let mut h = hub.lock().await; - let ts = h.alloc_ts(); - let stream = h.streams.entry(rmg_id).or_default(); - // enforce single producer - if let Some(p) = stream.producer { - if p != conn_id { - anyhow::bail!("producer mismatch for rmg_id {}", rmg_id); - } - } else { - stream.producer = Some(conn_id); - } - match &frame { - RmgFrame::Snapshot(s) => { - stream.last_epoch = Some(s.epoch); - stream.last_hash = s.state_hash; - stream.latest_snapshot = Some(s.clone()); - } - RmgFrame::Diff(d) => { - let last = stream - .last_epoch - .ok_or_else(|| anyhow::anyhow!("diff before snapshot"))?; - if d.from_epoch != last || d.to_epoch != d.from_epoch + 1 { - anyhow::bail!( - "gap for rmg_id {}: got {}->{} expected {}->{}", - rmg_id, - d.from_epoch, - d.to_epoch, - last, - last + 1 - ); + let (subs, pkt) = { + let mut h = hub.lock().await; + let mut error: Option = None; + let mut err_reason: Option = None; + let mut subs: Option> = None; + { + let stream = h.streams.entry(rmg_id).or_default(); + // enforce single producer + if let Some(p) = stream.producer { + if p != conn_id { + error = Some(ErrorPayload { + code: 403, + name: "E_FORBIDDEN_PUBLISH".into(), + details: None, + message: format!("rmg_id {} is owned by {}", rmg_id, p), + }); + err_reason = Some(format!("producer mismatch for rmg_id {}", rmg_id)); + } + } else { + stream.producer = Some(conn_id); + } + + if error.is_none() { + match &frame { + RmgFrame::Snapshot(s) => { + stream.last_epoch = Some(s.epoch); + stream.last_hash = s.state_hash; + stream.latest_snapshot = Some(s.clone()); + } + RmgFrame::Diff(d) => { + let last = match stream.last_epoch { + Some(v) => v, + None => { + error = Some(ErrorPayload { + code: 409, + name: "E_RMG_SNAPSHOT_REQUIRED".into(), + details: None, + message: "send a snapshot before the first diff".into(), + }); + err_reason = Some("diff before snapshot".into()); + 0 // placeholder, unused when error is set + } + }; + if error.is_none() + && (d.from_epoch != last || d.to_epoch != d.from_epoch + 1) + { + error = Some(ErrorPayload { + code: 409, + name: "E_RMG_EPOCH_GAP".into(), + details: None, + message: format!( + "expected {}->{} but got {}->{}", + last, + last + 1, + d.from_epoch, + d.to_epoch + ), + }); + err_reason = Some(format!( + "gap for rmg_id {}: got {}->{} expected {}->{}", + rmg_id, + d.from_epoch, + d.to_epoch, + last, + last + 1 + )); + } + if error.is_none() { + stream.last_epoch = Some(d.to_epoch); + stream.last_hash = d.state_hash; + } + } + } + } + + if error.is_none() { + subs = Some(stream.subscribers.clone()); } - stream.last_epoch = Some(d.to_epoch); - stream.last_hash = d.state_hash; + } // drop stream borrow + + if let Some(payload) = error { + let tx = h.conns.get(&conn_id).map(|c| c.tx.clone()); + let ts = h.alloc_ts(); + if let Some(tx) = tx { + let pkt = encode_message(Message::Error(payload), ts)?; + let _ = tx.send(pkt).await; + } + let reason = err_reason.unwrap_or_else(|| "rmg stream error".into()); + anyhow::bail!(reason); } - } - // fan out to subscribers - let pkt = encode_message(Message::RmgStream { rmg_id, frame }, ts)?; - let subs = stream.subscribers.clone(); + + let subs = subs.unwrap_or_default(); + let ts = h.alloc_ts(); + let pkt = encode_message(Message::RmgStream { rmg_id, frame }, ts)?; + (subs, pkt) + }; + + let h = hub.lock().await; for sub in subs { if let Some(conn) = h.conns.get(&sub) { let _ = conn.tx.send(pkt.clone()).await; diff --git a/crates/echo-session-ws-gateway/Cargo.toml b/crates/echo-session-ws-gateway/Cargo.toml new file mode 100644 index 0000000..d40632b --- /dev/null +++ b/crates/echo-session-ws-gateway/Cargo.toml @@ -0,0 +1,22 @@ +# SPDX-License-Identifier: Apache-2.0 OR MIND-UCAL-1.0 +# © James Ross Ω FLYING•ROBOTS +[package] +name = "echo-session-ws-gateway" +version = "0.1.0" +edition = "2021" +description = "WebSocket to Unix-socket bridge for the Echo session hub (browser clients -> JS-ABI frames)" +license = "Apache-2.0" +repository = "https://github.com/flyingrobots/echo" +readme = "README.md" +keywords = ["echo", "rmg", "websocket", "gateway", "wasm"] +categories = ["network-programming", "web-programming", "asynchronous"] + +[dependencies] +anyhow = "1" +axum = { version = "0.6", features = ["ws"] } +axum-server = { version = "0.5", features = ["tls-rustls"] } +clap = { version = "4", features = ["derive"] } +futures-util = "0.3" +tokio = { version = "1", features = ["rt-multi-thread", "macros", "net", "io-util", "signal", "time"] } +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["fmt", "env-filter"] } diff --git a/crates/echo-session-ws-gateway/README.md b/crates/echo-session-ws-gateway/README.md new file mode 100644 index 0000000..6b52147 --- /dev/null +++ b/crates/echo-session-ws-gateway/README.md @@ -0,0 +1,22 @@ + + +# echo-session-ws-gateway + +WebSocket ↔ Unix-socket bridge for the Echo session hub. It terminates browser WebSocket connections, enforces JS-ABI frame sizing, and forwards binary frames to the Unix-domain socket exposed by `echo-session-service`. + +## Usage + +``` +cargo run -p echo-session-ws-gateway -- \ + --unix-socket /tmp/echo-session.sock \ + --listen 0.0.0.0:8787 \ + --allow-origin https://your.host \ + --tls-cert cert.pem --tls-key key.pem +``` + +## Features +- Binary WS frames → JS-ABI packets over UDS +- Payload guard (8 MiB default) +- Optional origin allowlist +- Optional TLS (rustls) +- Ping/pong keepalive diff --git a/crates/echo-session-ws-gateway/src/main.rs b/crates/echo-session-ws-gateway/src/main.rs new file mode 100644 index 0000000..4651241 --- /dev/null +++ b/crates/echo-session-ws-gateway/src/main.rs @@ -0,0 +1,281 @@ +// SPDX-License-Identifier: Apache-2.0 +// © James Ross Ω FLYING•ROBOTS +//! WebSocket ↔ Unix socket bridge for the Echo session service. +//! Browsers speak WebSocket; the bridge forwards binary JS-ABI frames to the Unix bus. + +use std::{collections::HashSet, net::SocketAddr, path::PathBuf, sync::Arc}; + +use anyhow::{anyhow, Context, Result}; +use axum::{ + extract::ws::{Message, WebSocket}, + extract::{ConnectInfo, State, WebSocketUpgrade}, + http::{HeaderMap, StatusCode}, + response::IntoResponse, + routing::get, + Router, +}; +use axum_server::{tls_rustls::RustlsConfig, Handle}; +use clap::Parser; +use futures_util::{SinkExt, StreamExt}; +use tokio::{ + io::{AsyncReadExt, AsyncWriteExt}, + net::UnixStream, + sync::mpsc, + time::{self, Duration}, +}; +use tracing::{error, info, warn}; +use tracing_subscriber::EnvFilter; + +#[derive(Parser, Debug)] +#[command(author, version, about = "Echo session WebSocket gateway")] +struct Args { + /// TCP listener for browser clients (e.g. 0.0.0.0:8787) + #[arg(long, default_value = "0.0.0.0:8787")] + listen: SocketAddr, + /// Path to the Unix socket exposed by echo-session-service + #[arg(long, default_value = "/tmp/echo-session.sock")] + unix_socket: PathBuf, + /// Maximum frame payload in bytes (binary WS message must match exact frame length) + #[arg(long, default_value_t = 8 * 1024 * 1024)] + max_frame_bytes: usize, + /// Optional allowed Origin values (repeatable). If none provided, all origins are accepted. + #[arg(long)] + allow_origin: Vec, + /// TLS certificate (PEM). If provided, key must also be provided. + #[arg(long)] + tls_cert: Option, + /// TLS private key (PEM). If provided, cert must also be provided. + #[arg(long)] + tls_key: Option, +} + +#[derive(Clone)] +struct AppState { + unix_socket: PathBuf, + max_frame_bytes: usize, + allow_origins: Option>, +} + +#[tokio::main] +async fn main() -> Result<()> { + let args = Args::parse(); + + tracing_subscriber::fmt() + .with_env_filter(EnvFilter::from_default_env().add_directive("info".parse()?)) + .init(); + + let allow_origins = if args.allow_origin.is_empty() { + None + } else { + Some(args.allow_origin.iter().cloned().collect()) + }; + + let state = Arc::new(AppState { + unix_socket: args.unix_socket.clone(), + max_frame_bytes: args.max_frame_bytes, + allow_origins, + }); + + let app = Router::new() + .route("/ws", get(ws_handler)) + .with_state(state); + + let handle = Handle::new(); + // graceful shutdown on Ctrl+C + let shutdown = handle.clone(); + tokio::spawn(async move { + tokio::signal::ctrl_c() + .await + .expect("failed to install ctrl-c handler"); + shutdown.shutdown(); + }); + + match (args.tls_cert, args.tls_key) { + (Some(cert), Some(key)) => { + let tls_config = load_tls(cert, key).await.context("load tls config")?; + info!("ws gateway listening (TLS) on {}", args.listen); + axum_server::bind_rustls(args.listen, tls_config) + .handle(handle) + .serve(app.into_make_service_with_connect_info::()) + .await?; + } + (None, None) => { + info!("ws gateway listening on {}", args.listen); + axum_server::bind(args.listen) + .handle(handle) + .serve(app.into_make_service_with_connect_info::()) + .await?; + } + _ => { + return Err(anyhow!( + "must provide both --tls-cert and --tls-key or neither" + )) + } + } + + Ok(()) +} + +async fn ws_handler( + State(state): State>, + ConnectInfo(addr): ConnectInfo, + headers: HeaderMap, + ws: WebSocketUpgrade, +) -> impl IntoResponse { + if !origin_allowed(&state, &headers) { + warn!(?addr, "origin rejected"); + return StatusCode::FORBIDDEN.into_response(); + } + ws.on_upgrade(move |socket| handle_socket(socket, state, addr)) +} + +async fn handle_socket(mut socket: WebSocket, state: Arc, peer: SocketAddr) { + let unix = match UnixStream::connect(&state.unix_socket).await { + Ok(stream) => stream, + Err(err) => { + error!(?err, "failed to connect to unix socket"); + let _ = socket + .send(Message::Close(Some(axum::extract::ws::CloseFrame { + code: axum::extract::ws::close_code::ERROR, + reason: "upstream unavailable".into(), + }))) + .await; + return; + } + }; + + let (mut ws_tx, mut ws_rx) = socket.split(); + let (mut uds_reader, mut uds_writer) = tokio::io::split(unix); + let (out_tx, mut out_rx) = mpsc::channel::(256); + + // Writer task: WS outbound messages (binary frames, pongs) + let writer = tokio::spawn(async move { + while let Some(msg) = out_rx.recv().await { + if ws_tx.send(msg).await.is_err() { + break; + } + } + }); + + // UDS -> WS task: frame and forward packets + let max_len = state.max_frame_bytes; + let out_tx_clone = out_tx.clone(); + let uds_to_ws = tokio::spawn(async move { + let mut buf = vec![0u8; 16 * 1024]; + let mut acc: Vec = Vec::with_capacity(32 * 1024); + loop { + let n = uds_reader.read(&mut buf).await?; + if n == 0 { + break; + } + acc.extend_from_slice(&buf[..n]); + while let Some(pkt) = try_extract_frame(&mut acc, max_len)? { + if out_tx_clone.send(Message::Binary(pkt)).await.is_err() { + return Ok::<(), anyhow::Error>(()); + } + } + } + Ok::<(), anyhow::Error>(()) + }); + + // WS -> UDS task: validate and forward binary frames + let max_len_ws = state.max_frame_bytes; + let pong_tx = out_tx.clone(); + let ws_to_uds = tokio::spawn(async move { + while let Some(msg) = ws_rx.next().await { + match msg { + Ok(Message::Binary(data)) => { + if let Err(err) = validate_frame(&data, max_len_ws) { + warn!(?err, ?peer, "invalid frame from client"); + break; + } + if let Err(err) = uds_writer.write_all(&data).await { + warn!(?err, "failed to write to uds"); + break; + } + } + Ok(Message::Ping(payload)) => { + let _ = pong_tx.send(Message::Pong(payload)).await; + } + Ok(Message::Close(_)) => break, + Ok(Message::Text(_)) => { + warn!(?peer, "ignoring text frame"); + break; + } + Err(err) => { + warn!(?err, ?peer, "ws recv error"); + break; + } + _ => {} + } + } + }); + + // Optional ping loop to keep connections alive + let ping_tx = out_tx.clone(); + let ping = tokio::spawn(async move { + let mut interval = time::interval(Duration::from_secs(30)); + loop { + interval.tick().await; + if ping_tx.send(Message::Ping(Vec::new())).await.is_err() { + break; + } + } + }); + + let _ = tokio::join!(ws_to_uds, uds_to_ws, writer, ping); +} + +fn origin_allowed(state: &AppState, headers: &HeaderMap) -> bool { + let Some(allow) = &state.allow_origins else { + return true; + }; + if let Some(origin) = headers.get("origin") { + if let Ok(origin_str) = origin.to_str() { + return allow.contains(origin_str); + } + } + false +} + +fn validate_frame(buf: &[u8], max: usize) -> Result<()> { + if buf.len() < 12 { + return Err(anyhow!("frame too small")); + } + let len = u32::from_be_bytes([buf[8], buf[9], buf[10], buf[11]]) as usize; + if len > max { + return Err(anyhow!("payload too large")); + } + let frame_len = 12usize + .checked_add(len) + .and_then(|v| v.checked_add(32)) + .ok_or_else(|| anyhow!("frame length overflow"))?; + if buf.len() != frame_len { + return Err(anyhow!("frame length mismatch")); + } + Ok(()) +} + +fn try_extract_frame(acc: &mut Vec, max: usize) -> Result>> { + if acc.len() < 12 { + return Ok(None); + } + let len = u32::from_be_bytes([acc[8], acc[9], acc[10], acc[11]]) as usize; + if len > max { + return Err(anyhow!("payload too large")); + } + let frame_len = 12usize + .checked_add(len) + .and_then(|v| v.checked_add(32)) + .ok_or_else(|| anyhow!("frame length overflow"))?; + if acc.len() < frame_len { + return Ok(None); + } + let pkt: Vec = acc.drain(..frame_len).collect(); + Ok(Some(pkt)) +} + +async fn load_tls(cert_path: PathBuf, key_path: PathBuf) -> Result { + let cfg = RustlsConfig::from_pem_file(cert_path, key_path).await?; + Ok(cfg) +} diff --git a/crates/rmg-ffi/Cargo.toml b/crates/rmg-ffi/Cargo.toml index af8fcaf..c0827c1 100644 --- a/crates/rmg-ffi/Cargo.toml +++ b/crates/rmg-ffi/Cargo.toml @@ -5,7 +5,7 @@ name = "rmg-ffi" version = "0.1.0" edition = "2021" rust-version = "1.71.1" -description = "Echo FFI: C ABI for host integrations (Lua/C/etc.)" +description = "Echo FFI: C ABI for host integrations (Rhai in-process; C/others via ABI)" license = "Apache-2.0" repository = "https://github.com/flyingrobots/echo" readme = "README.md" diff --git a/crates/rmg-ffi/README.md b/crates/rmg-ffi/README.md index 48d0bb9..61665f4 100644 --- a/crates/rmg-ffi/README.md +++ b/crates/rmg-ffi/README.md @@ -4,7 +4,7 @@ Thin C ABI bindings for the `rmg-core` deterministic graph rewriting engine. -This crate produces a C-callable library for embedding Echo’s core in other runtimes (C/C++, Lua, etc.). It exposes a minimal, stable surface: engine creation, rule registration by name, apply/commit, and snapshot hash retrieval. +This crate produces a C-callable library for embedding Echo’s core in other runtimes (C/C++, host modules alongside Rhai, etc.). It exposes a minimal, stable surface: engine creation, rule registration by name, apply/commit, and snapshot hash retrieval. ## Platforms and Toolchain diff --git a/crates/rmg-ffi/src/lib.rs b/crates/rmg-ffi/src/lib.rs index 1f4557d..4c0578a 100644 --- a/crates/rmg-ffi/src/lib.rs +++ b/crates/rmg-ffi/src/lib.rs @@ -3,7 +3,7 @@ //! C-compatible bindings for the motion rewrite spike. //! -//! This module exposes a minimal ABI that higher-level languages (Lua, Python, +//! This module exposes a minimal ABI that higher-level languages (Rhai host modules, Python, //! etc.) can use to interact with the deterministic engine without knowing the //! internal Rust types. #![deny(missing_docs)] diff --git a/docs/METHODOLOGY.md b/docs/METHODOLOGY.md new file mode 100644 index 0000000..0a2fc9a --- /dev/null +++ b/docs/METHODOLOGY.md @@ -0,0 +1,99 @@ + + + +# JITOS Engineering Standard: The Living Specification + +**Status:** Active +**Version:** 1.0.0 +**Context:** Development Methodology & Contributor Workflow + +## 1. Abstract + +The JITOS operating system rejects the traditional dichotomy between "code" and "documentation." Given the paradigm-shifting nature of the Causal Operating System (Recursive Metagraphs, Event Sourcing, Schrödinger Workspaces), static text is insufficient to convey system behavior. + +Instead, JITOS adopts the **"5x Duty" Methodology**. Every feature added to the kernel must simultaneously serve five distinct purposes through a single, unified codebase. We do not write documentation *about* the OS; we compile the OS *into* the documentation. + +## 2. The 5x Duty Model + +Every Major Feature Specification (SPEC) acts as a unified artifact fulfilling these five roles: + +1. **Documentation:** A narrative explanation of the feature (the "Why" and "What"). +2. **Implementation:** The actual, production-grade Rust code (the "How"). +3. **Interactive Demo:** A WebAssembly-compiled instance of the kernel running in the browser, allowing real-time state manipulation. +4. **Living Test:** A visual verification suite where the "Demo" acts as a graphical test runner. +5. **Certification:** A gamified proof-of-competence that issues a cryptographic hash to users who successfully drive the kernel to a target state, proving they understand the concept. + +## 3. Workflow Architecture + +The following diagram illustrates how a single Rust source feed generates the Kernel, the Spec, and the Verification assets simultaneously. + +```mermaid +graph TD + subgraph "The Source of Truth" + Source[crates/echo-kernel
(Pure Rust / No_Std)] + end + + subgraph "Build Targets" + Native[Native Target
x86_64 / Aarch64] + Wasm[WASM Target
wasm32-unknown] + end + + subgraph "The Living Spec (Web)" + Page[Spec Page
(Leptos/HTML)] + UI[Interactive UI] + Narrative[Docs & Theory] + end + + subgraph "Outputs" + Binary[Production OS Binary] + Cert[Contributor Certificate] + end + + Source -->|Compiles| Native + Source -->|Compiles| Wasm + + Native --> Binary + + Wasm --> UI + UI -->|Embedded In| Page + Narrative -->|Embedded In| Page + + User((User / Dev)) -->|Reads| Narrative + User -->|Manipulates| UI + UI -->|Calls| Source + + UI -->|Verifies Success| Cert + + style Source fill:#f96,stroke:#333,stroke-width:2px + style Page fill:#bbf,stroke:#333,stroke-width:2px + style Cert fill:#9f9,stroke:#333,stroke-width:2px +``` + +## 4. The Contributor Lifecycle + +Under this methodology, the "Onboarding" process is identical to the "Testing" process. + +1. **The Challenge:** A new contributor navigates to `spec-001.jitos.dev` (The Rewrite). +2. **The Context:** They read the narrative explaining *why* JITOS uses append-only storage. +3. **The Interaction:** They use the embedded WASM demo to attempt a rewrite. This executes the **actual** `echo-kernel` crate logic in their browser. +4. **The Validation:** If they correctly perform the operation (e.g., creating a transaction rather than mutating a value), the Kernel state updates successfully. +5. **The Certification:** The UI detects the valid state transition and generates a `Completion Hash`. +6. **The Contribution:** The contributor includes this hash in their Pull Request, proving they have interacted with and understood the subsystem they are modifying. + +## 5. Technical Stack + +To enable this workflow, we strictly separate **Logic** from **IO**. + +- **Logic (The Kernel):** Written in `no_std` Rust. It manages the Graph, Time, and Inversion Engine. It knows nothing about files, sockets, or screens. +- **The Spec Runner (WASM):** Uses **Leptos** and **Trunk** to bind the Kernel Logic to DOM elements. +- **The OS Runner (Native):** Binds the Kernel Logic to physical hardware drivers and NVMe storage. + +## 6. Definition of Done + +A feature is not "Done" until: + +- [ ] The Core Logic is written in `crates/echo-kernel`. +- [ ] A `specs/spec-XXX` directory is created. +- [ ] The Spec page explains the concept. +- [ ] The Spec page imports the Kernel and creates an interactive visualization. +- [ ] A "Win Condition" is defined in the UI that issues a completion badge. diff --git a/docs/decision-log.md b/docs/decision-log.md index 24c059d..9da7f7c 100644 --- a/docs/decision-log.md +++ b/docs/decision-log.md @@ -6,6 +6,10 @@ | Date | Context | Decision | Rationale | Consequence | | ---- | ------- | -------- | --------- | ----------- | +| 2025-12-11 | Browser WS gateway | Added `echo-session-ws-gateway`: axum-based WebSocket→Unix bridge with optional rustls TLS, origin allowlist, 8 MiB frame guard, and JS-ABI frame parsing to keep gapless RMG framing intact for browser clients. | Browsers need WS; TCP proxies can’t handle WS framing. A thin Rust gateway keeps protocol identical while enforcing size/origin limits and retaining Unix-socket hub. | Browser clients can subscribe/publish RMG streams over WS; no protocol change to hub; TLS/origin filters available for internet exposure. | +| 2025-12-11 | Scripting runtime pivot | Replace the prior scripting embedding with Rhai as the deterministic scripting layer; update docs/roadmaps/backlogs accordingly. rmg-ffi stays for host/native integrations; scripting embeds Rhai directly. | Rhai runs in-process, is Rust-native, and gives tighter control over deterministic sandboxes without C FFI complexity; aligns with deterministic host modules and reduces attack surface. | All design documents, plans, and demo targets now refer to Rhai; future scripting work focuses on Rhai modules and hot-reload, not legacy bindings. | +| 2025-12-11 | RMG authority enforcement | Session-service now pins a single producer per RmgId, rejects other publishers with structured errors (`E_FORBIDDEN_PUBLISH`, `E_RMG_SNAPSHOT_REQUIRED`, `E_RMG_EPOCH_GAP`), and preserves snapshot/diff gap checks; session-client surfaces Error frames as local error notifications. | Prevents competing writers from corrupting RMG streams and makes protocol violations visible to tools instead of silent drops or disconnects. | Unauthorized publishes fail fast without fanout; clients see explicit error notifications; sets the stage for dirty-loop/resync work. | +| 2025-12-10 | CI cargo-audit warnings | Keep `cargo audit --deny warnings` green by ignoring RUSTSEC-2024-0436 (paste unmaintained via wgpu/metal) and RUSTSEC-2021-0127 (serde_cbor legacy advisory) in `security-audit.yml`. | Paste is an unmaintained transitive dep of wgpu with no downstream replacement yet; serde_cbor was removed from the codebase but may reappear through cached lockfiles—ignoring prevents CI flakiness while we track upstream updates. | Security audit job passes; advisories remain documented and will surface again once upstreams ship replacements or if serde_cbor re-enters the tree. | | 2025-12-10 | CI cargo-audit warnings | Keep `cargo audit --deny warnings` green by ignoring RUSTSEC-2024-0436 (paste unmaintained via wgpu/metal) and RUSTSEC-2021-0127 (serde_cbor legacy advisory) in both `security-audit.yml` and `ci.yml`. | Paste is an unmaintained transitive dep of wgpu with no downstream replacement yet; serde_cbor was removed from the codebase but may reappear through cached lockfiles—ignoring prevents CI flakiness while we track upstream updates. | Security audit jobs now ignore these advisories in both workflows; advisories remain documented and will surface again once upstreams ship replacements or if serde_cbor re-enters the tree. | | 2025-12-10 | CI cargo-deny index warnings | Prime crates.io index with `cargo fetch --locked` in the `deny` job before running `cargo-deny` (ci.yml). | `cargo deny` was emitting `warning[index-failure]` when it tried to query yanked status without a local index; warming the index prevents network flakiness from spamming logs and fails early if fetch cannot reach crates.io. | CI is quieter and deterministically fails on real connectivity issues; yank checks now use the warmed cache instead of best-effort network lookups during the deny step. | | 2025-12-10 | RMG View Protocol plan | Added `docs/tasks.md` with a checklist to deliver the RMG View Protocol (pub/sub authority, dirty-loop publishing, demo path, tests) and logged intent in execution-plan. | Centralizes the work needed to demo multi-viewer shared RMGs and sets commit-by-slice expectations. | Provides a single progress list; future slices will check off items and keep docs aligned. | diff --git a/docs/execution-plan.md b/docs/execution-plan.md index 657f591..28b5016 100644 --- a/docs/execution-plan.md +++ b/docs/execution-plan.md @@ -35,6 +35,24 @@ This is Codex’s working map for building Echo. Update it relentlessly—each s ## Today’s Intent +> 2025-12-11 — WebSocket gateway for session hub (COMPLETED) + +- Goal: allow browser clients to connect to the Unix-socket session bus via a secure WS bridge. +- Scope: new `echo-session-ws-gateway` crate with WS→UDS forwarding, frame guards, origin allowlist, optional TLS. +- Status: completed; gateway parses JS-ABI frame lengths, enforces 8 MiB cap, and proxies binary frames over WS. + +> 2025-12-11 — Scripting pivot to Rhai (COMPLETED) + +- Goal: cement Rhai as the scripting layer across design/docs, update scripting backlog items, and log the pivot. +- Scope: execution plan, decision log, scripting/spec docs, FFI descriptions. +- Status: completed; scripting plans now target Rhai with deterministic sandboxing, prior scripting references removed. + +> 2025-12-11 — RMG authority enforcement (COMPLETED) + +- Goal: Reject non-owner publishes on RMG channels and surface explicit errors to clients. +- Scope: `echo-session-service` (producer lock + error frames), `echo-session-client` (map error frames to notifications), protocol tasks checklist. +- Status: completed; unauthorized publishers now receive `E_FORBIDDEN_PUBLISH` errors, gap/diff ordering errors emit structured responses, and clients surface them as error notifications. + > 2025-12-10 — CI cargo-deny index failures (COMPLETED) - Goal: stop noisy `warning[index-failure]: unable to check for yanked crates` in GitHub Actions by ensuring `cargo-deny` has a warm crates.io index. @@ -448,8 +466,8 @@ This is Codex’s working map for building Echo. Update it relentlessly—each s - [ ] Scaffold Rust workspace (`crates/rmg-core`, `crates/rmg-ffi`, `crates/rmg-wasm`, `crates/rmg-cli`). - [ ] Port ECS archetype storage + branch diff engine to Rust. - [ ] Implement deterministic PRNG + math module in Rust. -- [ ] Expose C ABI for Lua and C integrations. -- [ ] Integrate Lua 5.4 runtime via bindings (mlua or custom FFI). +- [ ] Expose C ABI for host integrations and embed Rhai for scripting. +- [ ] Integrate Rhai runtime with deterministic sandboxing and host modules. - [ ] Adapt TypeScript CLI/inspector to Rust backend (WASM/FFI). - [ ] Archive TypeScript prototype under `/reference/` as spec baseline. - [ ] Add Rust CI jobs (cargo test, replay verification). @@ -488,7 +506,7 @@ This is Codex’s working map for building Echo. Update it relentlessly—each s | ---- | -------- | ------- | --------- | | 2025-10-23 | Monorepo seeded with pnpm & TypeScript skeleton | Baseline repo reset from Caverns to Echo | Implement Phase 0 specs | | 2025-10-24 | Branch tree spec v0.1: roaring bitmaps, chunk epochs, content-addressed IDs | Feedback loop to handle deterministic merges | Implement roaring bitmap integration | -| 2025-10-25 | Language direction pivot: Echo core to Rust | TypeScript validated specs; long-term determinism enforced via Rust + C ABI + Lua scripting | Update Phase 1 backlog: scaffold Rust workspace, port ECS/diff engine, FFI bindings | +| 2025-10-25 | Language direction pivot: Echo core to Rust | TypeScript validated specs; long-term determinism enforced via Rust + C ABI + Rhai scripting | Update Phase 1 backlog: scaffold Rust workspace, port ECS/diff engine, FFI bindings | | 2025-10-25 | Math validation fixtures & Rust test harness | Established deterministic scalar/vector/matrix/quaternion/PRNG coverage in rmg-core | Extend coverage to browser environments and fixed-point mode | | 2025-10-26 | Adopt RMG + Confluence as core architecture | RMG v2 (typed DPOi engine) + Confluence replication baseline | Scaffold rmg-core/ffi/wasm/cli crates; implement rewrite executor spike; integrate Rust CI; migrate TS prototype to `/reference` | diff --git a/docs/phase1-plan.md b/docs/phase1-plan.md index 4e64972..dc28daf 100644 --- a/docs/phase1-plan.md +++ b/docs/phase1-plan.md @@ -11,7 +11,7 @@ Goal: deliver a deterministic Rust implementation of RMG powering the Echo runti graph TD A[1A · RMG Core Bootstrap] B[1B · Rewrite Executor Spike] - C[1C · Lua/TS Bindings] + C[1C · Rhai/TS Bindings] D[1D · Echo ECS on RMG] E[1E · Networking & Confluence MVP] F[1F · Tooling Integration] @@ -26,7 +26,7 @@ graph TD DemoToy[Demo 2 · Toy Rewrite Benchmark] DemoNetcode[Demo 1 · Deterministic Netcode] DemoTimeTravel[Demo 5 · Time Travel Merge] - DemoLiveCoding[Demo 6 · Lua Live Coding] + DemoLiveCoding[Demo 6 · Rhai Live Coding] end ``` @@ -49,12 +49,12 @@ graph TD - Demonstration: **Demo 2 · Toy Benchmark** - 100 nodes, 10 rules, property tests showing stable hashes. -### 1C · Lua/TS Bindings +### 1C · Rhai/TS Bindings - Tasks - - Expose C ABI, embed Lua 5.4 with deterministic async helpers. + - Expose C ABI for host integrations, embed Rhai with deterministic sandbox + host modules. - Build WASM bindings for tooling. - Port inspector CLI to use snapshots. -- Demonstration: Lua script triggers rewrite; inspector shows matching snapshot hash. +- Demonstration: Rhai script triggers rewrite; inspector shows matching snapshot hash. ### 1D · Echo ECS on RMG - Tasks @@ -76,10 +76,10 @@ graph TD - Tasks - Echo Studio (TS + WASM) graph viewer with live updates. - Entropy lens, paradox heatmap overlays. - - Lua live coding pipeline (hot reload). + - Rhai live coding pipeline (hot reload). - Demonstrations: - **Demo 3 · Real Benchmark** (1k nodes, 100 rules). - - **Demo 6 · Live Coding** (Lua edit updates live graph). + - **Demo 6 · Live Coding** (Rhai edit updates live graph). --- @@ -113,4 +113,4 @@ Optimization roadmap once baseline is working: - Append decision log entries per phase. - Record demo outcomes in `docs/decision-log.md`, prefixing the Decision column with `Demo —` (e.g., `Demo 2 — Timeline hash verified`). -Phase 1 completes when Demo 6 (Live Coding) runs atop the Rust RMG runtime with inspector tooling in place. +Phase 1 completes when Demo 6 (Live Coding) runs atop the Rust RMG runtime with inspector tooling in place, using Rhai as the scripting layer. diff --git a/docs/rmg-demo-roadmap.md b/docs/rmg-demo-roadmap.md index d8c6eac..f7654e2 100644 --- a/docs/rmg-demo-roadmap.md +++ b/docs/rmg-demo-roadmap.md @@ -34,12 +34,12 @@ This document captures the interactive demos and performance milestones we want - Success criteria: merge replay produces the documented canonical hash, paradox branch quarantined with deterministic error log, entropy metrics trend as expected. - Deliverable: recorded replay plus JSON report showing branch IDs, merge decisions, and resulting hashes. -## Demo 4: Lua Live Coding Loop +## Demo 4: Rhai Live Coding Loop -**Goal:** Prove Lua bindings support hot reload without breaking determinism. +**Goal:** Prove Rhai bindings support hot reload without breaking determinism. -- Script registers a system that increments a component each tick; developer edits Lua code mid-run via CLI hot-reload. -- Engine stages rewrite intents from Lua through the FFI; after reload, replay the prior ticks to confirm deterministic equivalence. +- Script registers a system that increments a component each tick; developer edits Rhai code mid-run via CLI hot-reload. +- Engine stages rewrite intents from Rhai through the FFI; after reload, replay the prior ticks to confirm deterministic equivalence. - Success: frame hashes before/after reload identical when replayed from the same snapshot; inspector shows live diff of system graphs. - Includes integration test capturing reload latency budget (< 50 ms) and ensuring queued rewrites survive reload boundary. @@ -68,7 +68,7 @@ This document captures the interactive demos and performance milestones we want | ----- | ------------- | ------------- | | 1A | Demo 2 harness scaffolding | Criterion setup, synthetic rewrite fixtures | | 1B | Demo 1 prototype (local hash) | Motion rewrite spike, snapshot hashing | -| 1C | Demo 4 Lua API | `rmg-ffi` bindings, hot-reload CLI | +| 1C | Demo 4 Rhai API | `rmg-ffi` bindings, hot-reload CLI | | 1D | Demo 3 timeline tooling | Branch tree diff viewer, entropy metrics | | 1E | Demo 5 networking | Confluence transaction protocol, replay verification | | 1F | Demo dashboards | Inspector frame overlays, JSON ingestion | diff --git a/docs/rmg-runtime-architecture.md b/docs/rmg-runtime-architecture.md index e4e11c5..c088cf0 100644 --- a/docs/rmg-runtime-architecture.md +++ b/docs/rmg-runtime-architecture.md @@ -74,7 +74,7 @@ loop { ## Implementation Notes - RMG engine runs in Rust (`rmg-core`). -- Lua scripts issue rewrite intents via bindings; remain deterministic. +- Rhai scripts issue rewrite intents via bindings; remain deterministic. - TypeScript tools (via WASM) visualize the same graphs. --- diff --git a/docs/roadmap-mwmr-mini-epic.md b/docs/roadmap-mwmr-mini-epic.md index 3b8e9f8..06ca749 100644 --- a/docs/roadmap-mwmr-mini-epic.md +++ b/docs/roadmap-mwmr-mini-epic.md @@ -51,7 +51,7 @@ Status: Active • Owner: rmg-core • Created: 2025-10-27 - [x] build.rs generates const family id for `rule:motion/update` (domain‑separated) - [ ] Generalize generator (src/gen/rule_ids.rs) and runtime assert test to catch drift -- [ ] Lua FFI registration: `register_rule{name, match, exec, ?id, ?revision}`; engine computes if omitted +- [ ] Rhai rule registration: `register_rule{name, match, exec, ?id, ?revision}`; engine computes if omitted - [ ] Revision ID = blake3("rule-rev::canon-ast-v1" || canonical AST bytes) --- diff --git a/docs/rust-lua-ts-division.md b/docs/rust-rhai-ts-division.md similarity index 80% rename from docs/rust-lua-ts-division.md rename to docs/rust-rhai-ts-division.md index eb94bf4..2c0a13b 100644 --- a/docs/rust-lua-ts-division.md +++ b/docs/rust-rhai-ts-division.md @@ -2,7 +2,7 @@ # Language & Responsibility Map (Phase 1) -Echo’s runtime stack is intentionally stratified. Rust owns the deterministic graph engine; Lua sits on top for gameplay scripting; TypeScript powers the tooling layer via WebAssembly bindings. This document captures what lives where as we enter Phase 1 (Core Ignition). +Echo’s runtime stack is intentionally stratified. Rust owns the deterministic graph engine; Rhai sits on top for gameplay scripting; TypeScript powers the tooling layer via WebAssembly bindings. This document captures what lives where as we enter Phase 1 (Core Ignition). --- @@ -16,32 +16,32 @@ Echo’s runtime stack is intentionally stratified. Rust owns the deterministic - Netcode: lockstep / rollback / authority modes using rewrite transactions. - Asset pipeline: import/export graphs, payload storage, zero-copy access. - Confluence: distributed synchronization of rewrite transactions. -- Lua VM hosting: embed Lua 5.4, expose RMG bindings via FFI. +- Rhai engine hosting: embed Rhai with deterministic module set; expose RMG bindings. - CLI tools: `rmg` command for apply/snapshot/diff/verify. ### Key Crates - `rmg-core` – core engine -- `rmg-ffi` – C ABI for Lua and other native consumers +- `rmg-ffi` – C ABI for host/native consumers; Rhai binds directly in-process - `rmg-wasm` – WASM build for tooling/editor - `rmg-cli` – CLI utilities --- -## Lua (gameplay authoring layer) +## Rhai (gameplay authoring layer) ### Responsibilities - Gameplay systems & components (e.g., AI state machines, quests, input handling). - Component registration, entity creation/destruction via exposed APIs. - Scripting for deterministic “async” (scheduled events through Codex’s Baby). -- Editor lenses and inspector overlays written in Lua for rapid iteration. +- Editor lenses and inspector overlays written in Rhai for rapid iteration. ### Constraints - Single-threaded per branch; no OS threads. -- GC runs in deterministic stepped mode, bounded per tick. +- Engine budgeted deterministically per tick. - Mutations occur through rewrite intents (`rmg.apply(...)`), not raw memory access. ### Bindings -- `rmg` Lua module providing: +- `rmg` Rhai module providing: - `apply(rule_name, scope, params)` - `delay(seconds, fn)` (schedules replay-safe events) - Query helpers (read components, iterate entities) @@ -72,7 +72,7 @@ Echo’s runtime stack is intentionally stratified. Rust owns the deterministic ## Summary - Rust: core deterministic runtime + binding layers. -- Lua: gameplay logic, editor lenses, deterministic script-level behavior. +- Rhai: gameplay logic, editor lenses, deterministic script-level behavior. - TypeScript: visualization and tooling on top of WASM/IPC. This division keeps determinism and performance anchored in Rust while giving designers and tooling engineers approachable layers tailored for their workflows. diff --git a/docs/spec-concurrency-and-authoring.md b/docs/spec-concurrency-and-authoring.md index 4fa7267..9cd00c9 100644 --- a/docs/spec-concurrency-and-authoring.md +++ b/docs/spec-concurrency-and-authoring.md @@ -2,13 +2,13 @@ # Concurrency & Authoring Specification (Phase 0.75) -Clarifies Echo’s deterministic concurrency model and how Lua/Rust developers author gameplay systems at Unity-scale without sacrificing replay guarantees. +Clarifies Echo’s deterministic concurrency model and how Rhai/Rust developers author gameplay systems at Unity-scale without sacrificing replay guarantees. --- ## Core Principles - **Parallelism lives in the Rust core** (scheduler, ECS, branch tree). -- **Scripting remains single-threaded** (Lua sandbox per branch/world). +- **Scripting remains single-threaded** (Rhai sandbox per branch/world). - **All side effects traverse Codex’s Baby**; no direct threaded mutations from scripts. - **Adapters may use threads internally** but must commit results deterministically at tick boundaries. @@ -24,20 +24,20 @@ Clarifies Echo’s deterministic concurrency model and how Lua/Rust developers a --- -## Lua Execution Model -- Each branch/world owns one Lua VM. -- Scheduler phases (`pre_update`, `update`, `post_update`) call into Lua sequentially. -- Coroutine usage allowed intra-VM but cannot mutate world state across threads; resumed within the tick. -- GC runs in stepped deterministic mode with fixed budget per tick. -- Lua “async” tasks emit events; e.g., `echo.delay(seconds, fn)` enqueues an event to Codex’s Baby targeting future Chronos. +## Rhai Execution Model +- Each branch/world owns one Rhai engine + AST set. +- Scheduler phases (`pre_update`, `update`, `post_update`) call into Rhai sequentially. +- Rhai tasks stay single-threaded; no host threads spawned from scripts. +- GC/engine budgeting runs in deterministic steps per tick. +- Rhai “async” helpers emit events; e.g., `echo::delay(seconds, callback)` enqueues an event to Codex’s Baby targeting future Chronos. ### Deterministic Async Example -```lua -function on_start() - echo.delay(3.0, function() - echo.emit("spawn_particle", {pos = self.pos}) - end) -end +```rhai +fn on_start() { + echo::delay(3.0, || { + echo::emit("spawn_particle", #{ pos: this.pos }); + }); +} ``` - `echo.delay` schedules a timed event with `chronos + seconds * tickRate`. - Replay reproduces identical scheduling. @@ -57,11 +57,11 @@ end | Layer | Language | Purpose | | ----- | -------- | ------- | -| Lua scripts | Lua 5.4 | Gameplay logic, event handlers, component queries | +| Rhai scripts | Rhai | Gameplay logic, event handlers, component queries | | Rust plugins | Rust (plugin system) | New systems/components, AI planners, deterministic subsystems | | Native adapters | C (via C ABI) | Custom renderers, physics backends | -- Lua authors interact via `EchoWorldAPI` in scripting mode. +- Rhai authors interact via `EchoWorldAPI` in scripting mode. - Rust plugin authors register systems/components with deterministic access declarations. - C adapters communicate through FFI, respecting capability tokens. @@ -69,7 +69,7 @@ end ## Determinism Rules Summary - Only core scheduler launches parallel jobs; scripts remain single-threaded. -- Lua async → scheduled events; no OS threads. +- Rhai async → scheduled events; no OS threads. - All mutations route through Codex’s Baby and ECS APIs. - Adapter threads must synchronize and canonicalize outputs before commit. - Replay (`echo replay --verify`) detects divergences caused by nondeterministic plugins/adapters. diff --git a/docs/spec-mwmr-concurrency.md b/docs/spec-mwmr-concurrency.md index 94abc28..5515b13 100644 --- a/docs/spec-mwmr-concurrency.md +++ b/docs/spec-mwmr-concurrency.md @@ -70,7 +70,7 @@ Ordering & determinism ## Rule Identity & Hot-Load -- Family ID (stable): `blake3("rule-family:v1" || fully_qualified_name)` — compile-time const in Rust; computed once on load in Lua. +- Family ID (stable): `blake3("rule-family:v1" || fully_qualified_name)` — compile-time const in Rust; computed once on load in Rhai. - Revision ID (dynamic): `blake3("rule-rev::canon-ast-v1" || canonical AST graph bytes)` — flips on semantic changes; used for hot‑reload/peer compatibility; not in scheduling keys. ## Performance Targets @@ -115,4 +115,3 @@ Phase 3 (Real demo) - Multiplayer confluence demo (zero desync), time‑travel fork/merge, inspector visualization of footprints/conflicts References: confluence skeleton v5, RMG math confluence, offset-graph arena notes - diff --git a/docs/spec-networking.md b/docs/spec-networking.md index e9f8cd0..bf3eef6 100644 --- a/docs/spec-networking.md +++ b/docs/spec-networking.md @@ -16,8 +16,8 @@ Networking transports `EventEnvelope`s; no raw state replication. Every node run | Layer | Responsibility | Language | | ----- | -------------- | -------- | | Networking Core | Event replication, lockstep/rollback, authority decisions | Rust | -| Codex’s Baby Bridge | Converts network packets into cross-branch events | Rust / Lua | -| Lua Gameplay | Declares networked components/events via API | Lua | +| Codex’s Baby Bridge | Converts network packets into cross-branch events | Rust / Rhai | +| Rhai Gameplay | Declares networked components/events via API | Rhai | --- @@ -45,22 +45,19 @@ interface NetworkingPort { --- -## Lua API Surface +## Rhai API Surface -```lua -function on_start() - echo.network.emit("player_input", { - axis = self.move, - tick = echo.chronos() - }) -end +```rhai +fn on_start() { + echo::network::emit("player_input", #{ axis: this.move, tick: echo::chronos() }); +} -function on_player_input(evt) - self:applyInput(evt.payload) -end +fn on_player_input(evt) { + this.apply_input(evt.payload); +} ``` -- Lua never opens sockets; it emits/handles events. +- Rhai never opens sockets; it emits/handles events. - Engine assigns Chronos/Kairos IDs and handles delivery/rollback. --- diff --git a/docs/spec-rmg-core.md b/docs/spec-rmg-core.md index 1104863..37cbf04 100644 --- a/docs/spec-rmg-core.md +++ b/docs/spec-rmg-core.md @@ -94,9 +94,9 @@ engine.rewrite("physics:update"); let snap = engine.snapshot(); ``` -### Lua -```lua -rmg.apply("update/transform", {entity = id}) +### Rhai +```rhai +rmg.apply("update/transform", #{ entity: id }) ``` ### TypeScript (WASM) diff --git a/docs/spec-rmg-view-protocol.md b/docs/spec-rmg-view-protocol.md new file mode 100644 index 0000000..dbb5225 --- /dev/null +++ b/docs/spec-rmg-view-protocol.md @@ -0,0 +1,73 @@ + + +# RMG View Protocol (RVP) + +A narrow, deterministic pub/sub protocol for sharing Render Mesh Graphs (RMGs) between clients via the session service. It is an instantiation of the Echo Interaction Pattern (EIP) template; see the template section below for reuse in future services. + +## Goals +- Deterministic state sharing for RMG timelines across multiple viewers. +- Publisher authority: only the owner of an RmgId may write; others are read‑only. +- Gapless, hash‑checked stream of snapshots + diffs. +- Bounded, non‑blocking I/O suitable for desktop + wasm targets. + +## Transport +- Encoding: canonical CBOR via `ciborium` (same rules as `spec-serialization-protocol`). +- Framing: header + payload + checksum (current session wire framing). +- MAX_PAYLOAD: 8 MiB (hard cap; errors if exceeded). +- Non‑blocking read loop; partial frames preserved until complete. + +## Identity & Authority +- `ClientId`: assigned by session service at connect (opaque). +- `RmgId`: publisher-chosen identifier for an RMG timeline. +- `Owner`: the `ClientId` that registered/published the RmgId first; only the owner may send writes on that channel. +- Any client may subscribe to any RmgId; writes from non‑owners are rejected with `Forbidden`. + +## Message Pattern +- Channels: one logical channel per `RmgId`. +- Types (direction server↔client): + - `RegisterRmg { rmg_id, initial_epoch, hash }` (client→server) + - `Snapshot { rmg_id, epoch, hash, bytes }` (owner→server→subscribers) + - `Diff { rmg_id, from_epoch, to_epoch, hash, bytes }` (owner→server→subscribers) + - `Ack { rmg_id, epoch, hash }` (server→owner; optional telemetry) + - `Error { rmg_id, code, detail }` (server→owner) + - `ResyncRequest { rmg_id, expected_epoch, expected_hash }` (server→owner, subscriber→server optional) +- Gaplessness: server enforces `to_epoch` == `last_epoch + 1` per RmgId per subscriber. +- Hashing: canonical hash of post-apply graph; server validates owner frames before fan‑out. +- Backpressure: if subscriber lag or hash mismatch, server sends `ResyncRequest`; owner must send fresh `Snapshot`. + +## Client Behavior (viewer) +- Maintains dirty flag per RmgId. On local mutation: `dirty = true` with next epoch candidate. +- Net tick: if `dirty` and publish enabled, send `Diff` (or `Snapshot` if no base) and clear `dirty` on success; retry/backoff on error. +- Subscriptions: apply incoming Snapshot/Diff only if gapless and hash matches; otherwise request resync. +- UX toggles: per‑RmgId `publish_enabled`, `receive_enabled`; when receive is re‑enabled, request resync to current epoch. + +## Session Service Behavior +- Track owner per RmgId; reject non‑owner publishes with `Error{Forbidden}`. +- Validate gapless epochs and hashes from owner; on mismatch reply `Error{HashMismatch}` and drop frame. +- Fan‑out valid Snapshot/Diff to all subscribers except sender. +- On subscriber hash/epoch mismatch, send `ResyncRequest` to owner and optionally drop or buffer until fixed. + +## Error Codes (draft) +- `Forbidden`: non‑owner attempted publish. +- `HashMismatch`: provided hash does not match recomputed state. +- `EpochGap`: `from/to` not contiguous with server state. +- `Oversize`: payload exceeded MAX_PAYLOAD. +- `DecodeError`: CBOR/frame parse failure. + +## Echo Interaction Pattern (EIP) Template +Use this skeleton for future services: +- **Roles:** publisher/authority, subscribers/consumers (and optional mediating hub). +- **Identity:** opaque `ClientId`, resource identifier (`RmgId` here). +- **Authority rule:** who may write, who may read; enforcement location. +- **Message set:** register, snapshot, diff/update, ack, error, resync. +- **Validation:** gapless sequencing, content hash, size caps, decode checks. +- **Transport:** canonical encoding, framing, MAX payload, non‑blocking I/O. +- **Backpressure/recovery:** resync requests, retries, drop/flush policy. +- **Toggles:** per‑resource publish/receive enable. +- **Observability:** emit errors + resync events for metrics/logging. + +## Open Questions +- Do we need per-subscriber flow control (drop vs queue) or rely on resend-on-resync only? +- Should owner registration time out/expire to allow ownership handoff? +- Encrypt/authenticate transport (TLS/Noise) in this phase or later? + diff --git a/docs/tasks.md b/docs/tasks.md index f2f94e2..41777d1 100644 --- a/docs/tasks.md +++ b/docs/tasks.md @@ -2,6 +2,9 @@ # RMG View Protocol Tasks +- [x] Define the “RMG View Protocol” package: channel naming, RmgId + owner identity, publisher-only writes, message pattern (snapshot + diff, gapless epochs, hashes/acks), transport (canonical CBOR, MAX_PAYLOAD, non-blocking). +- [x] Generalize as an Echo Interaction Pattern (EIP) template capturing roles, authority, message types, flow styles (req/resp, pub/sub, bidir), reliability/validation hooks for future services. +- [x] Enforce authority: session-service rejects non-owner writes on the RMG channel; client surfaces errors. - [ ] Define the “RMG View Protocol” package: channel naming, RmgId + owner identity, publisher-only writes, message pattern (snapshot + diff, gapless epochs, hashes/acks), transport (canonical CBOR, MAX_PAYLOAD, non-blocking). - [ ] Generalize as an Echo Interaction Pattern (EIP) template capturing roles, authority, message types, flow styles (req/resp, pub/sub, bidir), reliability/validation hooks for future services. - [ ] Enforce authority: session-service rejects non-owner writes on the RMG channel; client surfaces errors. diff --git a/specs/spec-000-rewrite/Cargo.toml b/specs/spec-000-rewrite/Cargo.toml new file mode 100644 index 0000000..0c2a26d --- /dev/null +++ b/specs/spec-000-rewrite/Cargo.toml @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: Apache-2.0 OR MIND-UCAL-1.0 +# © James Ross Ω FLYING•ROBOTS +[package] +name = "spec-000-rewrite" +version = "0.1.0" +edition = "2024" +description = "Living spec (Spec-000) for Echo/JITOS: Leptos + Trunk WASM demo scaffold" +license = "Apache-2.0" +repository = "https://github.com/flyingrobots/echo" +readme = "README.md" +keywords = ["echo", "rmg", "wasm", "leptos", "spec"] +categories = ["wasm", "gui", "education"] + +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies] +leptos = { version = "0.6", features = ["csr"] } +console_error_panic_hook = "0.1" diff --git a/specs/spec-000-rewrite/README.md b/specs/spec-000-rewrite/README.md new file mode 100644 index 0000000..408a8f3 --- /dev/null +++ b/specs/spec-000-rewrite/README.md @@ -0,0 +1,26 @@ + + +# Spec-000 Rewrite (Living Spec) + +Leptos + Trunk WASM scaffold for Spec-000: “Everything Is a Rewrite.” This page will embed the actual Echo/JITOS kernel in the browser to demonstrate rewrite-driven state. + +## Dev + +``` +rustup target add wasm32-unknown-unknown +cargo install --locked trunk +make spec-000-dev # from repo root +``` + +Serves at http://127.0.0.1:8080 with hot reload. + +## Build + +``` +make spec-000-build # outputs dist/ +``` + +## Next steps +- Wire kernel bindings (wasm-bindgen feature) +- Render RMG graph + rewrite log +- Add completion badge win condition diff --git a/specs/spec-000-rewrite/index.html b/specs/spec-000-rewrite/index.html new file mode 100644 index 0000000..41c2ab5 --- /dev/null +++ b/specs/spec-000-rewrite/index.html @@ -0,0 +1,89 @@ + + + + + + + Spec-000: Everything Is a Rewrite + + + + +
+ + + diff --git a/specs/spec-000-rewrite/src/lib.rs b/specs/spec-000-rewrite/src/lib.rs new file mode 100644 index 0000000..280310b --- /dev/null +++ b/specs/spec-000-rewrite/src/lib.rs @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: Apache-2.0 +// © James Ross Ω FLYING•ROBOTS +//! Spec-000 scaffold: Leptos CSR app wired for trunk/wasm32. + +use leptos::*; +use wasm_bindgen::prelude::*; + +/// Top-level Spec-000 Leptos component (WASM). +#[allow(missing_docs)] +#[component] +pub fn App() -> impl IntoView { + let (epoch, set_epoch) = create_signal(0usize); + + view! { +
+

"Spec-000: Everything Is a Rewrite"

+

+ "Living spec harness — the same Rust compiled to native and WASM." +

+ +
+
+ "Current Epoch:" + {move || epoch.get()} +
+ +
+ +
+ "Hook this component to the real kernel bindings to drive rewrites, " + "render the RMG graph, and record completion hashes." +
+
+ } +} + +/// WASM entry point required by `trunk serve`. +#[allow(missing_docs)] +#[wasm_bindgen(start)] +pub fn main() { + console_error_panic_hook::set_once(); + leptos::mount_to_body(|| view! { }) +}