From 2aa4eb1d06984d9fccc395bc09a43a300160b250 Mon Sep 17 00:00:00 2001 From: Zachery Aaron Shores-Chmielewski Date: Mon, 23 Feb 2026 13:59:14 +0700 Subject: [PATCH 1/3] refactor: logical consolidation of repository features --- Cargo.lock | 2224 ++++++++++------- Cargo.toml | 17 +- README.md | 154 +- benches/runtime_benchmarks.rs | 2 +- crates/bin-runner/Cargo.toml | 14 - crates/bin-runner/build.rs | 28 - crates/bin-runner/src/actor.rs | 63 - crates/bin-runner/src/builder.rs | 109 - crates/bin-runner/src/engine.rs | 35 - crates/bin-runner/src/error.rs | 27 - crates/bin-runner/src/lib.rs | 13 - .../bin-runner/tests/guests/double/Cargo.lock | 7 - .../bin-runner/tests/guests/double/Cargo.toml | 13 - .../bin-runner/tests/guests/double/src/lib.rs | 63 - .../bin-runner/tests/guests/echo/Cargo.lock | 7 - .../bin-runner/tests/guests/echo/Cargo.toml | 13 - .../bin-runner/tests/guests/echo/src/lib.rs | 62 - .../bin-runner/tests/guests/silent/Cargo.lock | 7 - .../bin-runner/tests/guests/silent/Cargo.toml | 13 - .../bin-runner/tests/guests/silent/src/lib.rs | 45 - .../tests/wasm_actor.proptest-regressions | 8 - crates/bin-runner/tests/wasm_actor.rs | 1367 ---------- crates/{ => bindings}/python/Cargo.toml | 2 +- crates/{ => bindings}/python/pyproject.toml | 0 crates/{ => bindings}/python/src/lib.rs | 0 crates/{ => bindings}/python/uv.lock | 0 .../wasm-crypto}/Cargo.toml | 0 .../wasm-crypto}/src/lib.rs | 0 .../wasm-runtime}/Cargo.toml | 3 +- .../wasm-runtime}/demo.html | 0 .../wasm-runtime}/src/lib.rs | 2 +- .../wasm-runtime}/test.mjs | 0 crates/ci/src/yaml.rs | 2 +- crates/dashboard/AGENTS.md | 4 +- crates/dashboard/Cargo.toml | 3 +- crates/dashboard/README.md | 14 +- crates/dashboard/src/bin/swactor-node.rs | 2 +- crates/dashboard/src/collector.rs | 2 +- crates/dashboard/src/command/builtins.rs | 2 +- crates/dashboard/src/datastore_collector.rs | 2 +- crates/datastore/Cargo.toml | 9 +- crates/datastore/src/bridge.rs | 4 +- crates/datastore/src/lib.rs | 3 + crates/datastore/tests/common/mod.rs | 2 +- crates/datastore/tests/stream_integration.rs | 4 +- crates/distribution/src/identity.rs | 2 +- crates/distribution/src/snapshot.rs | 2 +- crates/{swactor-node => node}/Cargo.toml | 3 +- crates/node/README.md | 99 + crates/{swactor-node => node}/build.rs | 0 crates/{swactor-node => node}/src/config.rs | 2 +- crates/{swactor-node => node}/src/install.rs | 0 crates/{swactor-node => node}/src/main.rs | 6 +- crates/{swactor-node => node}/src/names.rs | 0 crates/{swactor-node => node}/src/relay.rs | 0 crates/std/Cargo.toml | 13 - crates/streams/Cargo.toml | 1 - crates/streams/src/ctx_ext.rs | 4 +- docs/connectome/connectome.md | 76 - docs/connectome/connectome_dashboard.html | 743 ------ docs/connectome/connectome_metrics.json | 169 -- docs/connectome/connectome_report.txt | 66 - docs/datastore/DATASTORE_AUTH.md | 503 ---- docs/datastore/actors.md | 278 --- docs/datastore/streaming.md | 103 - .../development_history/PROCESS_PRIMITIVES.md | 542 ---- docs/development_history/PROCESS_RUNNER.md | 434 ---- docs/development_history/STREAMS.md | 475 ---- .../STREAMS_IMPLEMENTATION.md | 338 --- .../bin-runner/WASM_ACTOR.md | 191 -- .../cfuzz/CFUZZ_OVERVIEW.md | 95 - .../cfuzz/CYCLE_01_FAIRNESS.md | 61 - .../cfuzz/CYCLE_02_STRESS_TESTS.md | 74 - .../cfuzz/CYCLE_03_THREAD_PARKING.md | 51 - .../cfuzz/CYCLE_04_SHUTDOWN_FIX.md | 54 - .../cfuzz/CYCLE_05_LOAD_AWARE_PLACEMENT.md | 66 - .../cfuzz/CYCLE_06_BACKPRESSURE.md | 56 - .../cfuzz/CYCLE_07_ACTOR_RECOVERY.md | 59 - .../cfuzz/CYCLE_08_DEAD_ACTOR_CLEANUP.md | 54 - .../cfuzz/CYCLE_09_LIFECYCLE_HOOKS.md | 79 - .../cfuzz/CYCLE_10_TIMERS.md | 76 - .../cfuzz/CYCLE_11_PROPERTY_TESTING.md | 84 - .../cfuzz/CYCLE_12_NAMED_REGISTRY.md | 83 - .../cfuzz/CYCLE_13_MONITORING.md | 74 - .../cfuzz/CYCLE_14_GROUPS.md | 83 - .../cfuzz/CYCLE_15_ASK_PATTERN.md | 67 - .../cfuzz/CYCLE_16_REGISTRY_BENCHMARKS.md | 59 - .../cfuzz/CYCLE_17_SUPERVISION.md | 89 - .../cfuzz/CYCLE_18_SUPERVISOR_STRATEGIES.md | 86 - .../cfuzz/CYCLE_19_ROUTER.md | 78 - docs/development_history/ci/CI_DEPLOYMENT.md | 140 -- .../ci/CI_OUTPUT_IN_FORGEJO.md | 139 -- docs/development_history/ci/CI_RELAY.md | 520 ---- .../dashboard-improvements-research.md | 28 - .../datastore/AUTH_SUMMARY.md | 386 --- docs/development_history/datastore/CHANGES.md | 93 - .../datastore/DATASTORE_AUTH.md | 305 --- .../development_history/datastore/PROTOCOL.md | 471 ---- docs/development_history/datastore/SUMMARY.md | 317 --- .../distribution/DEPLOY_REGRESSION_TESTS.md | 418 ---- .../distribution/DISTRIBUTION.md | 879 ------- .../distribution/DOCKER_REALIZATION.md | 705 ------ .../distribution/IROH_TRANSPORT.md | 738 ------ .../distribution/SIMULATION_TESTING.md | 190 -- .../SWIM_FLAKY_TEST_POSTMORTEM.md | 218 -- .../distribution/WIRE_HINTS_BUGFIX.md | 534 ---- .../distribution/distribution_plan.md | 353 --- .../in-browser/BROWSER_RUNTIME.md | 69 - docs/development_history/in-browser/DEMO.md | 60 - .../in-browser/FEATURE_PARITY.md | 75 - .../in-browser/PLATFORM_ABSTRACTION.md | 82 - .../pooled-datastore/POOLED_DATASTORE.md | 423 ---- docs/diagrams/actor_lifecycle.svg | 145 -- docs/diagrams/actor_resolution.svg | 185 -- docs/diagrams/architecture.svg | 1118 --------- docs/diagrams/dataflow.svg | 806 ------ docs/diagrams/datastore_chunk_lifecycle.svg | 170 -- docs/diagrams/datastore_transfer.svg | 153 -- docs/diagrams/distribution_minor_flows.svg | 199 -- docs/diagrams/message_lifecycle.svg | 155 -- docs/diagrams/runtime_lifecycle.svg | 229 -- docs/diagrams/swim_probe_cycle.svg | 186 -- docs/diagrams/tick_cycle.svg | 258 -- docs/diagrams/transport_encode_decode.svg | 183 -- docs/diagrams/transport_routing.svg | 173 -- docs/diagrams/type_erasure.svg | 222 -- docs/distribution/distribution.md | 140 -- docs/distribution/kademlia.md | 136 - docs/distribution/swim.md | 126 - docs/distribution/transport.md | 127 - docs/render_docs.sh | 85 - docs/runtime/actor-model.md | 138 - docs/runtime/channels.md | 124 - docs/runtime/runtime.md | 354 --- docs/runtime/worker-thread.md | 646 ----- examples/dp-mnist/README.md | 9 - examples/dp-mnist/aggregator.py | 82 - examples/dp-mnist/pyproject.toml | 17 - examples/dp-mnist/run_training.py | 108 - examples/dp-mnist/uv.lock | 930 ------- examples/dp-mnist/worker.py | 108 - examples/hello.rs | 65 - .../getting_started-checkpoint.ipynb | 98 - examples/python/getting_started.ipynb | 98 - examples/python/hello_async.py | 39 - examples/python/hello_single_thread.py | 13 - examples/ring.rs | 71 - examples/tcp_ping_pong.rs | 336 --- fuzz/Cargo.toml | 1 - fuzz/fuzz_targets/fuzz_runtime.rs | 2 +- src/lib.rs | 5 +- .../std/src => src/std}/children_registry.rs | 4 +- {crates/std/src => src/std}/ctx_ext.rs | 26 +- {crates/std/src => src/std}/extension.rs | 22 +- {crates/std/src => src/std}/group_registry.rs | 4 +- crates/std/src/lib.rs => src/std/mod.rs | 0 .../std/src => src/std}/monitor_registry.rs | 4 +- {crates/std/src => src/std}/name_registry.rs | 8 +- .../std/src => src/std}/resource_handle.rs | 2 +- {crates/std/src => src/std}/router.rs | 8 +- {crates/std/src => src/std}/runtime_ext.rs | 8 +- .../std/src => src/std}/service_registry.rs | 8 +- {crates/std/src => src/std}/supervisor.rs | 8 +- .../src => src/std}/supervisor_registry.rs | 4 +- {crates/std/src => src/std}/timer_wheel.rs | 4 +- {crates/std/src => src/std}/watch_registry.rs | 2 +- tests/common/mod.rs | 2 +- tests/proptest_runtime.rs | 2 +- tools/analyze.sh | 15 - xtask/Cargo.toml | 7 - xtask/src/deploy.rs | 1069 -------- xtask/src/main.rs | 448 +--- xtask/src/sim_cluster.rs | 701 ------ 173 files changed, 1594 insertions(+), 25774 deletions(-) delete mode 100644 crates/bin-runner/Cargo.toml delete mode 100644 crates/bin-runner/build.rs delete mode 100644 crates/bin-runner/src/actor.rs delete mode 100644 crates/bin-runner/src/builder.rs delete mode 100644 crates/bin-runner/src/engine.rs delete mode 100644 crates/bin-runner/src/error.rs delete mode 100644 crates/bin-runner/src/lib.rs delete mode 100644 crates/bin-runner/tests/guests/double/Cargo.lock delete mode 100644 crates/bin-runner/tests/guests/double/Cargo.toml delete mode 100644 crates/bin-runner/tests/guests/double/src/lib.rs delete mode 100644 crates/bin-runner/tests/guests/echo/Cargo.lock delete mode 100644 crates/bin-runner/tests/guests/echo/Cargo.toml delete mode 100644 crates/bin-runner/tests/guests/echo/src/lib.rs delete mode 100644 crates/bin-runner/tests/guests/silent/Cargo.lock delete mode 100644 crates/bin-runner/tests/guests/silent/Cargo.toml delete mode 100644 crates/bin-runner/tests/guests/silent/src/lib.rs delete mode 100644 crates/bin-runner/tests/wasm_actor.proptest-regressions delete mode 100644 crates/bin-runner/tests/wasm_actor.rs rename crates/{ => bindings}/python/Cargo.toml (85%) rename crates/{ => bindings}/python/pyproject.toml (100%) rename crates/{ => bindings}/python/src/lib.rs (100%) rename crates/{ => bindings}/python/uv.lock (100%) rename crates/{wasm/crypto => bindings/wasm-crypto}/Cargo.toml (100%) rename crates/{wasm/crypto => bindings/wasm-crypto}/src/lib.rs (100%) rename crates/{wasm/runtime => bindings/wasm-runtime}/Cargo.toml (66%) rename crates/{wasm/runtime => bindings/wasm-runtime}/demo.html (100%) rename crates/{wasm/runtime => bindings/wasm-runtime}/src/lib.rs (99%) rename crates/{wasm/runtime => bindings/wasm-runtime}/test.mjs (100%) rename crates/{swactor-node => node}/Cargo.toml (92%) create mode 100644 crates/node/README.md rename crates/{swactor-node => node}/build.rs (100%) rename crates/{swactor-node => node}/src/config.rs (95%) rename crates/{swactor-node => node}/src/install.rs (100%) rename crates/{swactor-node => node}/src/main.rs (99%) rename crates/{swactor-node => node}/src/names.rs (100%) rename crates/{swactor-node => node}/src/relay.rs (100%) delete mode 100644 crates/std/Cargo.toml delete mode 100644 docs/connectome/connectome.md delete mode 100644 docs/connectome/connectome_dashboard.html delete mode 100644 docs/connectome/connectome_metrics.json delete mode 100644 docs/connectome/connectome_report.txt delete mode 100644 docs/datastore/DATASTORE_AUTH.md delete mode 100644 docs/datastore/actors.md delete mode 100644 docs/datastore/streaming.md delete mode 100644 docs/development_history/PROCESS_PRIMITIVES.md delete mode 100644 docs/development_history/PROCESS_RUNNER.md delete mode 100644 docs/development_history/STREAMS.md delete mode 100644 docs/development_history/STREAMS_IMPLEMENTATION.md delete mode 100644 docs/development_history/bin-runner/WASM_ACTOR.md delete mode 100644 docs/development_history/cfuzz/CFUZZ_OVERVIEW.md delete mode 100644 docs/development_history/cfuzz/CYCLE_01_FAIRNESS.md delete mode 100644 docs/development_history/cfuzz/CYCLE_02_STRESS_TESTS.md delete mode 100644 docs/development_history/cfuzz/CYCLE_03_THREAD_PARKING.md delete mode 100644 docs/development_history/cfuzz/CYCLE_04_SHUTDOWN_FIX.md delete mode 100644 docs/development_history/cfuzz/CYCLE_05_LOAD_AWARE_PLACEMENT.md delete mode 100644 docs/development_history/cfuzz/CYCLE_06_BACKPRESSURE.md delete mode 100644 docs/development_history/cfuzz/CYCLE_07_ACTOR_RECOVERY.md delete mode 100644 docs/development_history/cfuzz/CYCLE_08_DEAD_ACTOR_CLEANUP.md delete mode 100644 docs/development_history/cfuzz/CYCLE_09_LIFECYCLE_HOOKS.md delete mode 100644 docs/development_history/cfuzz/CYCLE_10_TIMERS.md delete mode 100644 docs/development_history/cfuzz/CYCLE_11_PROPERTY_TESTING.md delete mode 100644 docs/development_history/cfuzz/CYCLE_12_NAMED_REGISTRY.md delete mode 100644 docs/development_history/cfuzz/CYCLE_13_MONITORING.md delete mode 100644 docs/development_history/cfuzz/CYCLE_14_GROUPS.md delete mode 100644 docs/development_history/cfuzz/CYCLE_15_ASK_PATTERN.md delete mode 100644 docs/development_history/cfuzz/CYCLE_16_REGISTRY_BENCHMARKS.md delete mode 100644 docs/development_history/cfuzz/CYCLE_17_SUPERVISION.md delete mode 100644 docs/development_history/cfuzz/CYCLE_18_SUPERVISOR_STRATEGIES.md delete mode 100644 docs/development_history/cfuzz/CYCLE_19_ROUTER.md delete mode 100644 docs/development_history/ci/CI_DEPLOYMENT.md delete mode 100644 docs/development_history/ci/CI_OUTPUT_IN_FORGEJO.md delete mode 100644 docs/development_history/ci/CI_RELAY.md delete mode 100644 docs/development_history/dashboard-improvements-research.md delete mode 100644 docs/development_history/datastore/AUTH_SUMMARY.md delete mode 100644 docs/development_history/datastore/CHANGES.md delete mode 100644 docs/development_history/datastore/DATASTORE_AUTH.md delete mode 100644 docs/development_history/datastore/PROTOCOL.md delete mode 100644 docs/development_history/datastore/SUMMARY.md delete mode 100644 docs/development_history/distribution/DEPLOY_REGRESSION_TESTS.md delete mode 100644 docs/development_history/distribution/DISTRIBUTION.md delete mode 100644 docs/development_history/distribution/DOCKER_REALIZATION.md delete mode 100644 docs/development_history/distribution/IROH_TRANSPORT.md delete mode 100644 docs/development_history/distribution/SIMULATION_TESTING.md delete mode 100644 docs/development_history/distribution/SWIM_FLAKY_TEST_POSTMORTEM.md delete mode 100644 docs/development_history/distribution/WIRE_HINTS_BUGFIX.md delete mode 100644 docs/development_history/distribution/distribution_plan.md delete mode 100644 docs/development_history/in-browser/BROWSER_RUNTIME.md delete mode 100644 docs/development_history/in-browser/DEMO.md delete mode 100644 docs/development_history/in-browser/FEATURE_PARITY.md delete mode 100644 docs/development_history/in-browser/PLATFORM_ABSTRACTION.md delete mode 100644 docs/development_history/pooled-datastore/POOLED_DATASTORE.md delete mode 100644 docs/diagrams/actor_lifecycle.svg delete mode 100644 docs/diagrams/actor_resolution.svg delete mode 100644 docs/diagrams/architecture.svg delete mode 100644 docs/diagrams/dataflow.svg delete mode 100644 docs/diagrams/datastore_chunk_lifecycle.svg delete mode 100644 docs/diagrams/datastore_transfer.svg delete mode 100644 docs/diagrams/distribution_minor_flows.svg delete mode 100644 docs/diagrams/message_lifecycle.svg delete mode 100644 docs/diagrams/runtime_lifecycle.svg delete mode 100644 docs/diagrams/swim_probe_cycle.svg delete mode 100644 docs/diagrams/tick_cycle.svg delete mode 100644 docs/diagrams/transport_encode_decode.svg delete mode 100644 docs/diagrams/transport_routing.svg delete mode 100644 docs/diagrams/type_erasure.svg delete mode 100644 docs/distribution/distribution.md delete mode 100644 docs/distribution/kademlia.md delete mode 100644 docs/distribution/swim.md delete mode 100644 docs/distribution/transport.md delete mode 100755 docs/render_docs.sh delete mode 100644 docs/runtime/actor-model.md delete mode 100644 docs/runtime/channels.md delete mode 100644 docs/runtime/runtime.md delete mode 100644 docs/runtime/worker-thread.md delete mode 100644 examples/dp-mnist/README.md delete mode 100644 examples/dp-mnist/aggregator.py delete mode 100644 examples/dp-mnist/pyproject.toml delete mode 100644 examples/dp-mnist/run_training.py delete mode 100644 examples/dp-mnist/uv.lock delete mode 100644 examples/dp-mnist/worker.py delete mode 100644 examples/hello.rs delete mode 100644 examples/python/.ipynb_checkpoints/getting_started-checkpoint.ipynb delete mode 100644 examples/python/getting_started.ipynb delete mode 100644 examples/python/hello_async.py delete mode 100644 examples/python/hello_single_thread.py delete mode 100644 examples/ring.rs delete mode 100644 examples/tcp_ping_pong.rs rename {crates/std/src => src/std}/children_registry.rs (96%) rename {crates/std/src => src/std}/ctx_ext.rs (94%) rename {crates/std/src => src/std}/extension.rs (87%) rename {crates/std/src => src/std}/group_registry.rs (96%) rename crates/std/src/lib.rs => src/std/mod.rs (100%) rename {crates/std/src => src/std}/monitor_registry.rs (96%) rename {crates/std/src => src/std}/name_registry.rs (92%) rename {crates/std/src => src/std}/resource_handle.rs (97%) rename {crates/std/src => src/std}/router.rs (97%) rename {crates/std/src => src/std}/runtime_ext.rs (96%) rename {crates/std/src => src/std}/service_registry.rs (84%) rename {crates/std/src => src/std}/supervisor.rs (98%) rename {crates/std/src => src/std}/supervisor_registry.rs (95%) rename {crates/std/src => src/std}/timer_wheel.rs (98%) rename {crates/std/src => src/std}/watch_registry.rs (97%) delete mode 100755 tools/analyze.sh delete mode 100644 xtask/src/deploy.rs delete mode 100644 xtask/src/sim_cluster.rs diff --git a/Cargo.lock b/Cargo.lock index 9bce3f0..bbce720 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,21 +2,47 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "addr2line" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" -dependencies = [ - "gimli", -] - [[package]] name = "adler2" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common 0.1.7", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + [[package]] name = "ahash" version = "0.8.12" @@ -24,6 +50,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", + "const-random", "getrandom 0.3.4", "once_cell", "version_check", @@ -117,20 +144,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" [[package]] -name = "ar_archive_writer" -version = "0.5.1" +name = "arc-swap" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eb93bbb63b9c227414f6eb3a0adfddca591a8ce1e9b60661bb08969b87e340b" +checksum = "f9f3647c145568cec02c42054e07bdf9a5a698e15b466fb2341bfc393cd24aa5" dependencies = [ - "object 0.37.3", + "rustversion", ] -[[package]] -name = "arbitrary" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" - [[package]] name = "arrayref" version = "0.3.9" @@ -149,6 +170,45 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" +[[package]] +name = "asn1-rs" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56624a96882bb8c26d61312ae18cb45868e5a9992ea73c58e45c3101e56a1e60" +dependencies = [ + "asn1-rs-derive", + "asn1-rs-impl", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror 2.0.18", + "time", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "asn1-rs-impl" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "async-compat" version = "0.2.5" @@ -205,7 +265,7 @@ version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16e2cdb6d5ed835199484bb92bb8b3edd526effe995c61732580439c1a67e2e9" dependencies = [ - "base64 0.22.1", + "base64", "http", "log", "url", @@ -239,6 +299,58 @@ dependencies = [ "fs_extra", ] +[[package]] +name = "axum" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" +dependencies = [ + "axum-core", + "bytes", + "form_urlencoded", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "serde_core", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "sync_wrapper", + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "backon" version = "1.6.0" @@ -250,18 +362,18 @@ dependencies = [ "tokio", ] +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + [[package]] name = "base32" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "022dfe9eb35f19ebbcb51e0b40a5ab759f46ad60cadf7297e0bd085afb50e076" -[[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" @@ -274,6 +386,17 @@ version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" +[[package]] +name = "bcrypt-pbkdf" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aeac2e1fe888769f34f05ac343bbef98b14d1ffb292ab69d4608b3abc86f2a2" +dependencies = [ + "blowfish", + "pbkdf2", + "sha2 0.10.9", +] + [[package]] name = "bit-set" version = "0.8.0" @@ -294,6 +417,9 @@ name = "bitflags" version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +dependencies = [ + "serde_core", +] [[package]] name = "blake3" @@ -327,6 +453,15 @@ dependencies = [ "hybrid-array", ] +[[package]] +name = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] + [[package]] name = "block2" version = "0.6.2" @@ -336,14 +471,30 @@ dependencies = [ "objc2", ] +[[package]] +name = "blowfish" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e412e2cd0f2b2d93e02543ceae7917b3c70331573df19ee046bcbc35e45e87d7" +dependencies = [ + "byteorder", + "cipher", +] + +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + [[package]] name = "bumpalo" version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" -dependencies = [ - "allocator-api2", -] [[package]] name = "byteorder" @@ -378,6 +529,15 @@ dependencies = [ "rustversion", ] +[[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher", +] + [[package]] name = "cc" version = "1.2.56" @@ -408,6 +568,17 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "chacha20" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + [[package]] name = "choice" version = "0.0.2" @@ -421,8 +592,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" dependencies = [ "iana-time-zone", + "js-sys", "num-traits", "serde", + "wasm-bindgen", "windows-link", ] @@ -432,21 +605,6 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e4de3bc4ea267985becf712dc6d9eed8b04c953b3fcfb339ebc87acd9804901" -[[package]] -name = "ci-relay" -version = "0.1.0" -dependencies = [ - "clap", - "hex", - "hmac", - "iroh", - "serde_json", - "sha2 0.10.9", - "swactor-ci", - "tiny_http", - "tokio", -] - [[package]] name = "ciborium" version = "0.2.2" @@ -474,6 +632,16 @@ dependencies = [ "half", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common 0.1.7", + "inout", +] + [[package]] name = "clap" version = "4.5.58" @@ -574,6 +742,26 @@ version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" +[[package]] +name = "const-random" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" +dependencies = [ + "const-random-macro", +] + +[[package]] +name = "const-random-macro" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" +dependencies = [ + "getrandom 0.2.17", + "once_cell", + "tiny-keccak", +] + [[package]] name = "constant_time_eq" version = "0.4.2" @@ -625,15 +813,6 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" -[[package]] -name = "cpp_demangle" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2bb79cb74d735044c972aae58ed0aaa9a837e85b01106a54c39e42e97f62253" -dependencies = [ - "cfg-if", -] - [[package]] name = "cpufeatures" version = "0.2.17" @@ -643,113 +822,6 @@ dependencies = [ "libc", ] -[[package]] -name = "cranelift-bforest" -version = "0.116.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e15d04a0ce86cb36ead88ad68cf693ffd6cda47052b9e0ac114bc47fd9cd23c4" -dependencies = [ - "cranelift-entity", -] - -[[package]] -name = "cranelift-bitset" -version = "0.116.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c6e3969a7ce267259ce244b7867c5d3bc9e65b0a87e81039588dfdeaede9f34" -dependencies = [ - "serde", - "serde_derive", -] - -[[package]] -name = "cranelift-codegen" -version = "0.116.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c22032c4cb42558371cf516bb47f26cdad1819d3475c133e93c49f50ebf304e" -dependencies = [ - "bumpalo", - "cranelift-bforest", - "cranelift-bitset", - "cranelift-codegen-meta", - "cranelift-codegen-shared", - "cranelift-control", - "cranelift-entity", - "cranelift-isle", - "gimli", - "hashbrown 0.14.5", - "log", - "regalloc2", - "rustc-hash", - "serde", - "smallvec", - "target-lexicon 0.13.5", -] - -[[package]] -name = "cranelift-codegen-meta" -version = "0.116.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c904bc71c61b27fc57827f4a1379f29de64fe95653b620a3db77d59655eee0b8" -dependencies = [ - "cranelift-codegen-shared", -] - -[[package]] -name = "cranelift-codegen-shared" -version = "0.116.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40180f5497572f644ce88c255480981ae2ec1d7bb4d8e0c0136a13b87a2f2ceb" - -[[package]] -name = "cranelift-control" -version = "0.116.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d132c6d0bd8a489563472afc171759da0707804a65ece7ceb15a8c6d7dd5ef" -dependencies = [ - "arbitrary", -] - -[[package]] -name = "cranelift-entity" -version = "0.116.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b2d0d9618275474fbf679dd018ac6e009acbd6ae6850f6a67be33fb3b00b323" -dependencies = [ - "cranelift-bitset", - "serde", - "serde_derive", -] - -[[package]] -name = "cranelift-frontend" -version = "0.116.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fac41e16729107393174b0c9e3730fb072866100e1e64e80a1a963b2e484d57" -dependencies = [ - "cranelift-codegen", - "log", - "smallvec", - "target-lexicon 0.13.5", -] - -[[package]] -name = "cranelift-isle" -version = "0.116.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ca20d576e5070044d0a72a9effc2deacf4d6aa650403189d8ea50126483944d" - -[[package]] -name = "cranelift-native" -version = "0.116.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8dee82f3f1f2c4cba9177f1cc5e350fe98764379bcd29340caa7b01f85076c7" -dependencies = [ - "cranelift-codegen", - "libc", - "target-lexicon 0.13.5", -] - [[package]] name = "crc32fast" version = "1.5.0" @@ -875,6 +947,18 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.7" @@ -882,6 +966,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", + "rand_core 0.6.4", "typenum", ] @@ -894,6 +979,15 @@ dependencies = [ "hybrid-array", ] +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + [[package]] name = "ctrlc" version = "3.5.1" @@ -1019,6 +1113,27 @@ dependencies = [ "syn", ] +[[package]] +name = "dashboard" +version = "0.1.0" +dependencies = [ + "axum", + "clap", + "crossbeam-queue", + "crossterm", + "ctrlc", + "distribution", + "iroh", + "ratatui", + "serde", + "serde_json", + "swactor", + "tokio", + "tokio-stream", + "tracing", + "tracing-subscriber", +] + [[package]] name = "dashmap" version = "6.1.0" @@ -1040,12 +1155,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" [[package]] -name = "debugid" -version = "0.8.0" +name = "delegate" +version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" +checksum = "780eb241654bf097afb00fc5f054a09b687dad862e485fdcf8399bb056565370" dependencies = [ - "uuid", + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -1055,6 +1172,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" dependencies = [ "const-oid 0.9.6", + "pem-rfc7468 0.7.0", "zeroize", ] @@ -1065,10 +1183,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71fd89660b2dc699704064e59e9dba0147b903e85319429e131620d022be411b" dependencies = [ "const-oid 0.10.2", - "pem-rfc7468", + "pem-rfc7468 1.0.0", "zeroize", ] +[[package]] +name = "der-parser" +version = "10.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6" +dependencies = [ + "asn1-rs", + "displaydoc", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", +] + [[package]] name = "deranged" version = "0.5.6" @@ -1132,6 +1264,15 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "des" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdd80ce8ce993de27e9f063a444a4d53ce8e8db4c1f00cc03af5ad5a9867a1e" +dependencies = [ + "cipher", +] + [[package]] name = "diatomic-waker" version = "0.2.3" @@ -1145,6 +1286,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.4", + "const-oid 0.9.6", "crypto-common 0.1.7", "subtle", ] @@ -1160,27 +1302,6 @@ dependencies = [ "crypto-common 0.2.0", ] -[[package]] -name = "directories-next" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "339ee130d97a610ea5a5872d2bbb130fdf68884ff09d3028b81bec8a1ac23bbc" -dependencies = [ - "cfg-if", - "dirs-sys-next", -] - -[[package]] -name = "dirs-sys-next" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" -dependencies = [ - "libc", - "redox_users", - "winapi", -] - [[package]] name = "dispatch2" version = "0.3.0" @@ -1208,8 +1329,10 @@ dependencies = [ name = "distribution" version = "0.1.0" dependencies = [ + "bs58", "ed25519-dalek 2.2.0", "iroh", + "iroh-relay", "rand_core 0.6.4", "serde", "serde_json", @@ -1260,6 +1383,20 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der 0.7.10", + "digest 0.10.7", + "elliptic-curve", + "rfc6979", + "signature 2.2.0", + "spki 0.7.3", +] + [[package]] name = "ed25519" version = "2.2.3" @@ -1318,6 +1455,27 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest 0.10.7", + "ff", + "generic-array", + "group", + "hkdf", + "pem-rfc7468 0.7.0", + "pkcs8 0.10.2", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "embedded-io" version = "0.4.0" @@ -1378,12 +1536,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "fallible-iterator" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" - [[package]] name = "fastbloom" version = "0.14.1" @@ -1392,7 +1544,7 @@ checksum = "4e7f34442dbe69c60fe8eaf58a8cafff81a1f278816d8ab4db255b3bef4ac3c4" dependencies = [ "getrandom 0.3.4", "libm", - "rand", + "rand 0.9.2", "siphasher", ] @@ -1402,6 +1554,16 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "fiat-crypto" version = "0.2.9" @@ -1430,6 +1592,18 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "flurry" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf5efcf77a4da27927d3ab0509dec5b0954bb3bc59da5a1de9e52642ebd4cdf9" +dependencies = [ + "ahash", + "num_cpus", + "parking_lot", + "seize 0.3.3", +] + [[package]] name = "fnv" version = "1.0.7" @@ -1592,28 +1766,6 @@ dependencies = [ "slab", ] -[[package]] -name = "fxhash" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" -dependencies = [ - "byteorder", -] - -[[package]] -name = "fxprof-processed-profile" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27d12c0aed7f1e24276a241aadc4cb8ea9f83000f34bc062b7cc2d51e3b0fabd" -dependencies = [ - "bitflags", - "debugid", - "fxhash", - "serde", - "serde_json", -] - [[package]] name = "generator" version = "0.8.8" @@ -1626,7 +1778,7 @@ dependencies = [ "log", "rustversion", "windows-link", - "windows-result", + "windows-result 0.4.1", ] [[package]] @@ -1637,6 +1789,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -1682,14 +1835,13 @@ dependencies = [ ] [[package]] -name = "gimli" -version = "0.31.1" +name = "ghash" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" dependencies = [ - "fallible-iterator", - "indexmap", - "stable_deref_trait", + "opaque-debug", + "polyval", ] [[package]] @@ -1704,6 +1856,17 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "h2" version = "0.4.13" @@ -1748,9 +1911,6 @@ name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" -dependencies = [ - "ahash", -] [[package]] name = "hashbrown" @@ -1761,7 +1921,6 @@ dependencies = [ "allocator-api2", "equivalent", "foldhash 0.1.5", - "serde", ] [[package]] @@ -1807,6 +1966,12 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hex-literal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" + [[package]] name = "hickory-proto" version = "0.25.2" @@ -1826,7 +1991,7 @@ dependencies = [ "idna", "ipnet", "once_cell", - "rand", + "rand 0.9.2", "ring", "rustls", "thiserror 2.0.18", @@ -1850,7 +2015,7 @@ dependencies = [ "moka", "once_cell", "parking_lot", - "rand", + "rand 0.9.2", "resolv-conf", "rustls", "smallvec", @@ -1860,6 +2025,15 @@ dependencies = [ "tracing", ] +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + [[package]] name = "hmac" version = "0.12.1" @@ -1869,6 +2043,15 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "home" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "http" version = "1.4.0" @@ -1985,7 +2168,7 @@ version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ - "base64 0.22.1", + "base64", "bytes", "futures-channel", "futures-util", @@ -2016,7 +2199,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core", + "windows-core 0.62.2", ] [[package]] @@ -2169,7 +2352,7 @@ dependencies = [ "hyper", "hyper-util", "log", - "rand", + "rand 0.9.2", "tokio", "url", "xmltree", @@ -2196,6 +2379,16 @@ dependencies = [ "rustversion", ] +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "block-padding", + "generic-array", +] + [[package]] name = "instability" version = "0.3.11" @@ -2270,7 +2463,7 @@ dependencies = [ "pkarr", "pkcs8 0.11.0-rc.11", "portmapper", - "rand", + "rand 0.9.2", "reqwest 0.12.28", "rustc-hash", "rustls", @@ -2316,12 +2509,17 @@ version = "0.38.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c946095f060e6e59b9ff30cc26c75cdb758e7fb0cde8312c89e2144654989fcb" dependencies = [ + "http-body-util", + "hyper", + "hyper-util", "iroh-metrics-derive", "itoa", "n0-error", "postcard", + "reqwest 0.12.28", "ryu", "serde", + "tokio", "tracing", ] @@ -2371,11 +2569,12 @@ dependencies = [ "getrandom 0.3.4", "identity-hash", "lru-slab", - "rand", + "rand 0.9.2", "ring", "rustc-hash", "rustls", "rustls-pki-types", + "rustls-platform-verifier", "slab", "sorted-index-buffer", "thiserror 2.0.18", @@ -2403,9 +2602,12 @@ version = "0.96.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd2b63e654b9dec799a73372cdc79b529ca6c7248c0c8de7da78a02e3a46f03c" dependencies = [ + "ahash", "blake3", "bytes", "cfg_aliases", + "clap", + "dashmap", "data-encoding", "derive_more", "getrandom 0.3.4", @@ -2425,18 +2627,29 @@ dependencies = [ "pin-project", "pkarr", "postcard", - "rand", + "rand 0.9.2", + "rcgen", + "reloadable-state", "reqwest 0.12.28", "rustls", + "rustls-cert-file-reader", + "rustls-cert-reloadable-resolver", "rustls-pki-types", "serde", "serde_bytes", + "serde_json", + "sha1 0.11.0-rc.4", + "simdutf8", "strum 0.27.2", + "time", "tokio", "tokio-rustls", + "tokio-rustls-acme", "tokio-util", "tokio-websockets", + "toml 0.9.12+spec-1.1.0", "tracing", + "tracing-subscriber", "url", "vergen-gitcl", "webpki-roots 1.0.6", @@ -2470,15 +2683,6 @@ 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 = "itertools" version = "0.13.0" @@ -2494,26 +2698,6 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" -[[package]] -name = "ittapi" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b996fe614c41395cdaedf3cf408a9534851090959d90d54a535f675550b64b1" -dependencies = [ - "anyhow", - "ittapi-sys", - "log", -] - -[[package]] -name = "ittapi-sys" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52f5385394064fa2c886205dba02598013ce83d3e92d33dbdc0c52fe0e7bf4fc" -dependencies = [ - "cc", -] - [[package]] name = "jni" version = "0.21.1" @@ -2561,12 +2745,9 @@ name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - -[[package]] -name = "leb128" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" +dependencies = [ + "spin 0.9.8", +] [[package]] name = "leb128fmt" @@ -2586,16 +2767,6 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" -[[package]] -name = "libredox" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" -dependencies = [ - "bitflags", - "libc", -] - [[package]] name = "linux-raw-sys" version = "0.4.15" @@ -2620,20 +2791,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" -[[package]] -name = "local-runner" -version = "0.1.0" -dependencies = [ - "clap", - "ctrlc", - "iroh", - "runtime-dashboard", - "serde_json", - "swactor", - "swactor-ci", - "tokio", -] - [[package]] name = "lock_api" version = "0.4.14" @@ -2692,15 +2849,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3d25b0e0b648a86960ac23b7ad4abb9717601dec6f66c165f5b037f3f03065f" -[[package]] -name = "mach2" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44" -dependencies = [ - "libc", -] - [[package]] name = "matchers" version = "0.2.0" @@ -2710,21 +2858,24 @@ dependencies = [ "regex-automata", ] +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + +[[package]] +name = "md5" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" + [[package]] name = "memchr" version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" -[[package]] -name = "memfd" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad38eb12aea514a0466ea40a80fd8cc83637065948eb4a426e4aa46261175227" -dependencies = [ - "rustix 1.1.3", -] - [[package]] name = "memoffset" version = "0.9.1" @@ -2740,6 +2891,12 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[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" @@ -2962,8 +3119,8 @@ dependencies = [ "tokio-util", "tracing", "web-sys", - "windows", - "windows-result", + "windows 0.62.2", + "windows-result 0.4.1", "wmi", ] @@ -2979,12 +3136,39 @@ dependencies = [ "libc", ] +[[package]] +name = "node" +version = "0.1.0" +dependencies = [ + "clap", + "ctrlc", + "dashboard", + "distribution", + "iroh", + "libc", + "serde", + "swactor", + "swactor-datastore", + "swactor-streams", + "toml 0.8.23", +] + [[package]] name = "nohash-hasher" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "ntimestamp" version = "1.0.0" @@ -3009,12 +3193,59 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", + "rand 0.8.5", +] + +[[package]] +name = "num-bigint-dig" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" +dependencies = [ + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand 0.8.5", + "smallvec", + "zeroize", +] + [[package]] name = "num-conv" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -3022,6 +3253,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", + "libm", +] + +[[package]] +name = "num_cpus" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +dependencies = [ + "hermit-abi", + "libc", ] [[package]] @@ -3109,24 +3351,12 @@ dependencies = [ ] [[package]] -name = "object" -version = "0.36.7" +name = "oid-registry" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +checksum = "12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7" dependencies = [ - "crc32fast", - "hashbrown 0.15.5", - "indexmap", - "memchr", -] - -[[package]] -name = "object" -version = "0.37.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" -dependencies = [ - "memchr", + "asn1-rs", ] [[package]] @@ -3151,6 +3381,12 @@ version = "11.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + [[package]] name = "openssl" version = "0.10.75" @@ -3195,6 +3431,59 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2 0.10.9", +] + +[[package]] +name = "p384" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2 0.10.9", +] + +[[package]] +name = "p521" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc9e2161f1f215afdfce23677034ae137bbd45016a880c2eb3ba8eb95f085b2" +dependencies = [ + "base16ct", + "ecdsa", + "elliptic-curve", + "primeorder", + "rand_core 0.6.4", + "sha2 0.10.9", +] + +[[package]] +name = "pageant" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "032d6201d2fb765158455ae0d5a510c016bb6da7232e5040e39e9c8db12b0afc" +dependencies = [ + "bytes", + "delegate", + "futures", + "rand 0.8.5", + "thiserror 1.0.69", + "tokio", + "windows 0.58.0", +] + [[package]] name = "papaya" version = "0.2.3" @@ -3202,7 +3491,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f92dd0b07c53a0a0c764db2ace8c541dc47320dad97c2200c2a637ab9dd2328f" dependencies = [ "equivalent", - "seize", + "seize 0.5.1", ] [[package]] @@ -3240,6 +3529,35 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest 0.10.7", + "hmac", +] + +[[package]] +name = "pem" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" +dependencies = [ + "base64", + "serde_core", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + [[package]] name = "pem-rfc7468" version = "1.0.0" @@ -3328,6 +3646,32 @@ dependencies = [ "wasm-bindgen-futures", ] +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der 0.7.10", + "pkcs8 0.10.2", + "spki 0.7.3", +] + +[[package]] +name = "pkcs5" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e847e2c91a18bfa887dd028ec33f2fe6f25db77db3619024764914affe8b69a6" +dependencies = [ + "aes", + "cbc", + "der 0.7.10", + "pbkdf2", + "scrypt", + "sha2 0.10.9", + "spki 0.7.3", +] + [[package]] name = "pkcs8" version = "0.10.2" @@ -3335,6 +3679,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" dependencies = [ "der 0.7.10", + "pkcs5", + "rand_core 0.6.4", "spki 0.7.3", ] @@ -3360,7 +3706,7 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07" dependencies = [ - "base64 0.22.1", + "base64", "indexmap", "quick-xml", "serde", @@ -3395,6 +3741,29 @@ dependencies = [ "plotters-backend", ] +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + [[package]] name = "portable-atomic" version = "1.13.1" @@ -3407,7 +3776,7 @@ version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d2a8825353ace3285138da3378b1e21860d60351942f7aa3b99b13b41f80318" dependencies = [ - "base64 0.22.1", + "base64", "bytes", "derive_more", "futures-lite", @@ -3419,7 +3788,7 @@ dependencies = [ "n0-error", "netwatch", "num_enum", - "rand", + "rand 0.9.2", "serde", "smallvec", "socket2 0.6.2", @@ -3490,6 +3859,15 @@ dependencies = [ "syn", ] +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + [[package]] name = "proc-macro-crate" version = "3.4.0" @@ -3518,8 +3896,8 @@ dependencies = [ "bit-vec", "bitflags", "num-traits", - "rand", - "rand_chacha", + "rand 0.9.2", + "rand_chacha 0.9.0", "rand_xorshift", "regex-syntax", "rusty-fork", @@ -3536,28 +3914,6 @@ dependencies = [ "proptest", ] -[[package]] -name = "psm" -version = "0.1.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3852766467df634d74f0b2d7819bf8dc483a0eb2e3b0f50f756f9cfe8b0d18d8" -dependencies = [ - "ar_archive_writer", - "cc", -] - -[[package]] -name = "pulley-interpreter" -version = "29.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62d95f8575df49a2708398182f49a888cf9dc30210fb1fd2df87c889edcee75d" -dependencies = [ - "cranelift-bitset", - "log", - "sptr", - "wasmtime-math", -] - [[package]] name = "pyo3" version = "0.23.5" @@ -3583,7 +3939,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94f6cbe86ef3bf18998d9df6e0f3fc1050a8c5efa409bf712e661a4366e010fb" dependencies = [ "once_cell", - "target-lexicon 0.12.16", + "target-lexicon", ] [[package]] @@ -3674,7 +4030,7 @@ dependencies = [ "bytes", "getrandom 0.3.4", "lru-slab", - "rand", + "rand 0.9.2", "ring", "rustc-hash", "rustls", @@ -3715,16 +4071,37 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + [[package]] name = "rand" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ - "rand_chacha", + "rand_chacha 0.9.0", "rand_core 0.9.5", ] +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + [[package]] name = "rand_chacha" version = "0.9.0" @@ -3803,6 +4180,20 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "rcgen" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10b99e0098aa4082912d4c649628623db6aba77335e4f4569ff5083a6448b32e" +dependencies = [ + "pem", + "ring", + "rustls-pki-types", + "time", + "x509-parser", + "yasna", +] + [[package]] name = "redox_syscall" version = "0.5.18" @@ -3812,31 +4203,6 @@ dependencies = [ "bitflags", ] -[[package]] -name = "redox_users" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" -dependencies = [ - "getrandom 0.2.17", - "libredox", - "thiserror 1.0.69", -] - -[[package]] -name = "regalloc2" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc06e6b318142614e4a48bc725abbf08ff166694835c43c9dae5a9009704639a" -dependencies = [ - "allocator-api2", - "bumpalo", - "hashbrown 0.15.5", - "log", - "rustc-hash", - "smallvec", -] - [[package]] name = "regex" version = "1.12.3" @@ -3866,13 +4232,30 @@ version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" +[[package]] +name = "reloadable-core" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dc20ac1418988b60072d783c9f68e28a173fb63493c127952f6face3b40c6e0" + +[[package]] +name = "reloadable-state" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3853ef78d45b50f8b989896304a85239539d39b7f866a000e8846b9b72d74ce8" +dependencies = [ + "arc-swap", + "reloadable-core", + "tokio", +] + [[package]] name = "reqwest" version = "0.12.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ - "base64 0.22.1", + "base64", "bytes", "encoding_rs", "futures-channel", @@ -3920,7 +4303,7 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" dependencies = [ - "base64 0.22.1", + "base64", "bytes", "futures-core", "http", @@ -3955,6 +4338,16 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e061d1b48cb8d38042de4ae0a7a6401009d6143dc80d2e2d6f31f0bdd6470c7" +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + [[package]] name = "ring" version = "0.17.14" @@ -3970,30 +4363,163 @@ dependencies = [ ] [[package]] -name = "runtime-dashboard" -version = "0.1.0" +name = "rsa" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" dependencies = [ - "clap", - "crossbeam-queue", - "crossterm", - "ctrlc", - "distribution", - "iroh", - "ratatui", - "serde", - "serde_json", - "swactor", - "swactor-ci", - "tiny_http", - "tracing", - "tracing-subscriber", + "const-oid 0.9.6", + "digest 0.10.7", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8 0.10.2", + "rand_core 0.6.4", + "sha2 0.10.9", + "signature 2.2.0", + "spki 0.7.3", + "subtle", + "zeroize", ] [[package]] -name = "rustc-demangle" -version = "0.1.27" +name = "russh" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" +checksum = "c536b90d8e2468d8dedc8de2369383c101325e23fffa3a30de713032862a11d4" +dependencies = [ + "aes", + "aes-gcm", + "async-trait", + "bitflags", + "byteorder", + "cbc", + "chacha20", + "ctr", + "curve25519-dalek 4.1.3", + "des", + "digest 0.10.7", + "elliptic-curve", + "flate2", + "futures", + "generic-array", + "hex-literal", + "hmac", + "log", + "num-bigint", + "once_cell", + "p256", + "p384", + "p521", + "poly1305", + "rand 0.8.5", + "rand_core 0.6.4", + "russh-cryptovec", + "russh-keys", + "russh-sftp", + "russh-util", + "sha1 0.10.6", + "sha2 0.10.9", + "ssh-encoding", + "ssh-key", + "subtle", + "thiserror 1.0.69", + "tokio", +] + +[[package]] +name = "russh-cryptovec" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fadd2c0ab350e21c66556f94ee06f766d8bdae3213857ba7610bfd8e10e51880" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "russh-keys" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e3db166c8678c824627c2c46f619ed5ce4ae33f38a35403c62f6ab8f3985867" +dependencies = [ + "aes", + "async-trait", + "bcrypt-pbkdf", + "block-padding", + "byteorder", + "cbc", + "ctr", + "data-encoding", + "der 0.7.10", + "digest 0.10.7", + "ecdsa", + "ed25519-dalek 2.2.0", + "elliptic-curve", + "futures", + "getrandom 0.2.17", + "hmac", + "home", + "inout", + "log", + "md5", + "num-integer", + "p256", + "p384", + "p521", + "pageant", + "pbkdf2", + "pkcs1", + "pkcs5", + "pkcs8 0.10.2", + "rand 0.8.5", + "rand_core 0.6.4", + "rsa", + "russh-cryptovec", + "russh-util", + "sec1", + "serde", + "sha1 0.10.6", + "sha2 0.10.9", + "spki 0.7.3", + "ssh-encoding", + "ssh-key", + "thiserror 1.0.69", + "tokio", + "tokio-stream", + "typenum", + "zeroize", +] + +[[package]] +name = "russh-sftp" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bb94393cafad0530145b8f626d8687f1ee1dedb93d7ba7740d6ae81868b13b5" +dependencies = [ + "bitflags", + "bytes", + "chrono", + "flurry", + "log", + "serde", + "thiserror 2.0.18", + "tokio", + "tokio-util", +] + +[[package]] +name = "russh-util" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63aeb9d2b74f8f38befdc0c5172d5ffcf58f3d2ffcb423f3b6cdfe2c2d747b80" +dependencies = [ + "chrono", + "tokio", + "wasm-bindgen", + "wasm-bindgen-futures", +] [[package]] name = "rustc-hash" @@ -4010,6 +4536,15 @@ dependencies = [ "semver", ] +[[package]] +name = "rusticata-macros" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" +dependencies = [ + "nom", +] + [[package]] name = "rustix" version = "0.38.44" @@ -4052,6 +4587,40 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustls-cert-file-reader" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bb47c2a50fdfdaf95b0ac8b12620fc327da1fd4adbb30d0c56d866b005873ff" +dependencies = [ + "rustls-cert-read", + "rustls-pki-types", + "thiserror 2.0.18", + "tokio", +] + +[[package]] +name = "rustls-cert-read" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd46e8c5ae4de3345c4786a83f99ec7aff287209b9e26fa883c473aeb28f19d5" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-cert-reloadable-resolver" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe1baa8a3a1f05eaa9fc55aed4342867f70e5c170ea3bfed1b38c51a4857c0c8" +dependencies = [ + "futures-util", + "reloadable-state", + "rustls", + "rustls-cert-read", + "thiserror 2.0.18", +] + [[package]] name = "rustls-native-certs" version = "0.8.3" @@ -4137,6 +4706,15 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" +[[package]] +name = "salsa20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" +dependencies = [ + "cipher", +] + [[package]] name = "same-file" version = "1.0.6" @@ -4167,6 +4745,31 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "scrypt" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f" +dependencies = [ + "pbkdf2", + "salsa20", + "sha2 0.10.9", +] + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der 0.7.10", + "generic-array", + "pkcs8 0.10.2", + "subtle", + "zeroize", +] + [[package]] name = "security-framework" version = "3.6.0" @@ -4190,6 +4793,12 @@ dependencies = [ "libc", ] +[[package]] +name = "seize" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "689224d06523904ebcc9b482c6a3f4f7fb396096645c4cd10c0d2ff7371a34d3" + [[package]] name = "seize" version = "0.5.1" @@ -4211,10 +4820,6 @@ name = "semver" version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" -dependencies = [ - "serde", - "serde_core", -] [[package]] name = "send_wrapper" @@ -4275,6 +4880,17 @@ dependencies = [ "zmij", ] +[[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_spanned" version = "0.6.9" @@ -4284,6 +4900,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" +dependencies = [ + "serde_core", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -4309,6 +4934,28 @@ dependencies = [ "unsafe-libyaml", ] +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.7", +] + +[[package]] +name = "sha1" +version = "0.11.0-rc.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c777f0a122a53fddb0beb6e706771197000b8eb5c9f42b5b850f450ef48c788" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.11.0-rc.10", +] + [[package]] name = "sha1_smol" version = "1.0.1" @@ -4397,6 +5044,7 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ + "digest 0.10.7", "rand_core 0.6.4", ] @@ -4440,7 +5088,7 @@ dependencies = [ "swactor", "swactor-ci", "tiny_http", - "toml", + "toml 0.8.23", ] [[package]] @@ -4460,9 +5108,6 @@ name = "smallvec" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" -dependencies = [ - "serde", -] [[package]] name = "socket2" @@ -4537,10 +5182,55 @@ dependencies = [ ] [[package]] -name = "sptr" -version = "0.3.2" +name = "ssh-cipher" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b9b39299b249ad65f3b7e96443bad61c02ca5cd3589f46cb6d610a0fd6c0d6a" +checksum = "caac132742f0d33c3af65bfcde7f6aa8f62f0e991d80db99149eb9d44708784f" +dependencies = [ + "aes", + "aes-gcm", + "cbc", + "chacha20", + "cipher", + "ctr", + "poly1305", + "ssh-encoding", + "subtle", +] + +[[package]] +name = "ssh-encoding" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9242b9ef4108a78e8cd1a2c98e193ef372437f8c22be363075233321dd4a15" +dependencies = [ + "base64ct", + "pem-rfc7468 0.7.0", + "sha2 0.10.9", +] + +[[package]] +name = "ssh-key" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b86f5297f0f04d08cabaa0f6bff7cb6aec4d9c3b49d87990d63da9d9156a8c3" +dependencies = [ + "bcrypt-pbkdf", + "ed25519-dalek 2.2.0", + "num-bigint-dig", + "p256", + "p384", + "p521", + "rand_core 0.6.4", + "rsa", + "sec1", + "sha2 0.10.9", + "signature 2.2.0", + "ssh-cipher", + "ssh-encoding", + "subtle", + "zeroize", +] [[package]] name = "stable_deref_trait" @@ -4562,7 +5252,7 @@ dependencies = [ "log", "nohash-hasher", "parking_lot", - "rand", + "rand 0.9.2", "serde", "serde_json", "tiny_http", @@ -4640,44 +5330,30 @@ dependencies = [ "proptest", "proptest-state-machine", "serde", - "swactor-std", "tracing", "web-time 0.2.4", ] -[[package]] -name = "swactor-bin-runner" -version = "0.1.0" -dependencies = [ - "proptest", - "swactor", - "swactor-std", - "wasmtime", - "wat", -] - [[package]] name = "swactor-ci" version = "0.1.0" dependencies = [ + "clap", + "ctrlc", + "dashboard", "hex", "hmac", + "iroh", "serde", "serde_json", "serde_yaml", "sha2 0.10.9", "swactor", "tiny_http", + "tokio", "ureq", ] -[[package]] -name = "swactor-crypto-wasm" -version = "0.1.0" -dependencies = [ - "ed25519-dalek 2.2.0", -] - [[package]] name = "swactor-datastore" version = "0.1.0" @@ -4685,29 +5361,53 @@ dependencies = [ "blake3", "clap", "ctrlc", + "dashboard", "distribution", "getrandom 0.2.17", "proptest", "proptest-state-machine", - "runtime-dashboard", "serde", "serde_json", "shared-types", "stateright", "swactor", - "swactor-std", + "swactor-streams", "tempfile", "tiny_http", - "toml", + "tokio", + "toml 0.8.23", "ureq", ] [[package]] -name = "swactor-std" +name = "swactor-process" version = "0.1.0" dependencies = [ - "getrandom 0.2.17", + "async-trait", + "crossbeam-queue", + "libc", + "proptest", + "proptest-state-machine", + "russh", + "russh-keys", "swactor", + "tokio", +] + +[[package]] +name = "swactor-streams" +version = "0.1.0" +dependencies = [ + "blake3", + "crossbeam-queue", + "distribution", + "getrandom 0.2.17", + "iroh", + "proptest", + "serde", + "shared-types", + "swactor", + "tokio", ] [[package]] @@ -4774,12 +5474,6 @@ version = "0.12.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" -[[package]] -name = "target-lexicon" -version = "0.13.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca" - [[package]] name = "tempfile" version = "3.25.0" @@ -4793,15 +5487,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "termcolor" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" -dependencies = [ - "winapi-util", -] - [[package]] name = "thiserror" version = "1.0.69" @@ -4885,6 +5570,15 @@ dependencies = [ "time-core", ] +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + [[package]] name = "tiny_http" version = "0.12.0" @@ -4979,6 +5673,34 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-rustls-acme" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31fcc374ec87d754358a5d0709ed1ab7671d51e0f70ddc3b17a11ac36604cfa" +dependencies = [ + "async-trait", + "base64", + "chrono", + "futures", + "log", + "num-bigint", + "pem", + "proc-macro2", + "rcgen", + "reqwest 0.12.28", + "ring", + "rustls", + "serde", + "serde_json", + "thiserror 2.0.18", + "time", + "tokio", + "tokio-rustls", + "webpki-roots 1.0.6", + "x509-parser", +] + [[package]] name = "tokio-stream" version = "0.1.18" @@ -5011,14 +5733,14 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1b6348ebfaaecd771cecb69e832961d277f59845d4220a584701f72728152b7" dependencies = [ - "base64 0.22.1", + "base64", "bytes", "futures-core", "futures-sink", "getrandom 0.3.4", "http", "httparse", - "rand", + "rand 0.9.2", "ring", "rustls-pki-types", "simdutf8", @@ -5034,11 +5756,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" dependencies = [ "serde", - "serde_spanned", + "serde_spanned 0.6.9", "toml_datetime 0.6.11", "toml_edit 0.22.27", ] +[[package]] +name = "toml" +version = "0.9.12+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned 1.0.4", + "toml_datetime 0.7.5+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow", +] + [[package]] name = "toml_datetime" version = "0.6.11" @@ -5065,7 +5802,7 @@ checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ "indexmap", "serde", - "serde_spanned", + "serde_spanned 0.6.9", "toml_datetime 0.6.11", "toml_write", "winnow", @@ -5098,6 +5835,12 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" +[[package]] +name = "toml_writer" +version = "1.0.6+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" + [[package]] name = "tower" version = "0.5.3" @@ -5111,6 +5854,7 @@ dependencies = [ "tokio", "tower-layer", "tower-service", + "tracing", ] [[package]] @@ -5205,17 +5949,6 @@ dependencies = [ "tracing-log", ] -[[package]] -name = "trait-variant" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70977707304198400eb4835a78f6a9f928bf41bba420deb8fdb175cd965d77a7" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "try-lock" version = "0.2.5" @@ -5281,6 +6014,16 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3" +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common 0.1.7", + "subtle", +] + [[package]] name = "unsafe-libyaml" version = "0.2.11" @@ -5299,7 +6042,7 @@ version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d" dependencies = [ - "base64 0.22.1", + "base64", "flate2", "log", "once_cell", @@ -5465,15 +6208,6 @@ dependencies = [ "wit-bindgen", ] -[[package]] -name = "wasm" -version = "0.1.0" -dependencies = [ - "swactor", - "swactor-std", - "wasm-bindgen", -] - [[package]] name = "wasm-bindgen" version = "0.2.108" @@ -5533,16 +6267,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "wasm-encoder" -version = "0.221.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc8444fe4920de80a4fe5ab564fff2ae58b6b73166b89751f8c6c93509da32e5" -dependencies = [ - "leb128", - "wasmparser 0.221.3", -] - [[package]] name = "wasm-encoder" version = "0.244.0" @@ -5550,17 +6274,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" dependencies = [ "leb128fmt", - "wasmparser 0.244.0", -] - -[[package]] -name = "wasm-encoder" -version = "0.245.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9dca005e69bf015e45577e415b9af8c67e8ee3c0e38b5b0add5aa92581ed5c" -dependencies = [ - "leb128fmt", - "wasmparser 0.245.1", + "wasmparser", ] [[package]] @@ -5571,8 +6285,16 @@ checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" dependencies = [ "anyhow", "indexmap", - "wasm-encoder 0.244.0", - "wasmparser 0.244.0", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasm-runtime" +version = "0.1.0" +dependencies = [ + "swactor", + "wasm-bindgen", ] [[package]] @@ -5588,19 +6310,6 @@ dependencies = [ "web-sys", ] -[[package]] -name = "wasmparser" -version = "0.221.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d06bfa36ab3ac2be0dee563380147a5b81ba10dd8885d7fbbc9eb574be67d185" -dependencies = [ - "bitflags", - "hashbrown 0.15.5", - "indexmap", - "semver", - "serde", -] - [[package]] name = "wasmparser" version = "0.244.0" @@ -5613,303 +6322,6 @@ dependencies = [ "semver", ] -[[package]] -name = "wasmparser" -version = "0.245.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f08c9adee0428b7bddf3890fc27e015ac4b761cc608c822667102b8bfd6995e" -dependencies = [ - "bitflags", - "indexmap", - "semver", -] - -[[package]] -name = "wasmprinter" -version = "0.221.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7343c42a97f2926c7819ff81b64012092ae954c5d83ddd30c9fcdefd97d0b283" -dependencies = [ - "anyhow", - "termcolor", - "wasmparser 0.221.3", -] - -[[package]] -name = "wasmtime" -version = "29.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11976a250672556d1c4c04c6d5d7656ac9192ac9edc42a4587d6c21460010e69" -dependencies = [ - "addr2line", - "anyhow", - "async-trait", - "bitflags", - "bumpalo", - "cc", - "cfg-if", - "encoding_rs", - "fxprof-processed-profile", - "gimli", - "hashbrown 0.14.5", - "indexmap", - "ittapi", - "libc", - "log", - "mach2", - "memfd", - "object 0.36.7", - "once_cell", - "paste", - "postcard", - "psm", - "pulley-interpreter", - "rayon", - "rustix 0.38.44", - "semver", - "serde", - "serde_derive", - "serde_json", - "smallvec", - "sptr", - "target-lexicon 0.13.5", - "trait-variant", - "wasm-encoder 0.221.3", - "wasmparser 0.221.3", - "wasmtime-asm-macros", - "wasmtime-cache", - "wasmtime-component-macro", - "wasmtime-component-util", - "wasmtime-cranelift", - "wasmtime-environ", - "wasmtime-fiber", - "wasmtime-jit-debug", - "wasmtime-jit-icache-coherence", - "wasmtime-math", - "wasmtime-slab", - "wasmtime-versioned-export-macros", - "wasmtime-winch", - "wat", - "windows-sys 0.59.0", -] - -[[package]] -name = "wasmtime-asm-macros" -version = "29.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f178b0d125201fbe9f75beaf849bd3e511891f9e45ba216a5b620802ccf64f2" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "wasmtime-cache" -version = "29.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b1161c8f62880deea07358bc40cceddc019f1c81d46007bc390710b2fe24ffc" -dependencies = [ - "anyhow", - "base64 0.21.7", - "directories-next", - "log", - "postcard", - "rustix 0.38.44", - "serde", - "serde_derive", - "sha2 0.10.9", - "toml", - "windows-sys 0.59.0", - "zstd", -] - -[[package]] -name = "wasmtime-component-macro" -version = "29.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d74de6592ed945d0a602f71243982a304d5d02f1e501b638addf57f42d57dfaf" -dependencies = [ - "anyhow", - "proc-macro2", - "quote", - "syn", - "wasmtime-component-util", - "wasmtime-wit-bindgen", - "wit-parser 0.221.3", -] - -[[package]] -name = "wasmtime-component-util" -version = "29.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707dc7b3c112ab5a366b30cfe2fb5b2f8e6a0f682f16df96a5ec582bfe6f056e" - -[[package]] -name = "wasmtime-cranelift" -version = "29.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "366be722674d4bf153290fbcbc4d7d16895cc82fb3e869f8d550ff768f9e9e87" -dependencies = [ - "anyhow", - "cfg-if", - "cranelift-codegen", - "cranelift-control", - "cranelift-entity", - "cranelift-frontend", - "cranelift-native", - "gimli", - "itertools 0.12.1", - "log", - "object 0.36.7", - "smallvec", - "target-lexicon 0.13.5", - "thiserror 1.0.69", - "wasmparser 0.221.3", - "wasmtime-environ", - "wasmtime-versioned-export-macros", -] - -[[package]] -name = "wasmtime-environ" -version = "29.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdadc1af7097347aa276a4f008929810f726b5b46946971c660b6d421e9994ad" -dependencies = [ - "anyhow", - "cpp_demangle", - "cranelift-bitset", - "cranelift-entity", - "gimli", - "indexmap", - "log", - "object 0.36.7", - "postcard", - "rustc-demangle", - "semver", - "serde", - "serde_derive", - "smallvec", - "target-lexicon 0.13.5", - "wasm-encoder 0.221.3", - "wasmparser 0.221.3", - "wasmprinter", - "wasmtime-component-util", -] - -[[package]] -name = "wasmtime-fiber" -version = "29.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccba90d4119f081bca91190485650730a617be1fff5228f8c4757ce133d21117" -dependencies = [ - "anyhow", - "cc", - "cfg-if", - "rustix 0.38.44", - "wasmtime-asm-macros", - "wasmtime-versioned-export-macros", - "windows-sys 0.59.0", -] - -[[package]] -name = "wasmtime-jit-debug" -version = "29.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e7b61488a5ee00c35c8c22de707c36c0aecacf419a3be803a6a2ba5e860f56a" -dependencies = [ - "object 0.36.7", - "rustix 0.38.44", - "wasmtime-versioned-export-macros", -] - -[[package]] -name = "wasmtime-jit-icache-coherence" -version = "29.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec5e8552e01692e6c2e5293171704fed8abdec79d1a6995a0870ab190e5747d1" -dependencies = [ - "anyhow", - "cfg-if", - "libc", - "windows-sys 0.59.0", -] - -[[package]] -name = "wasmtime-math" -version = "29.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29210ec2aa25e00f4d54605cedaf080f39ec01a872c5bd520ad04c67af1dde17" -dependencies = [ - "libm", -] - -[[package]] -name = "wasmtime-slab" -version = "29.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb5821a96fa04ac14bc7b158bb3d5cd7729a053db5a74dad396cd513a5e5ccf" - -[[package]] -name = "wasmtime-versioned-export-macros" -version = "29.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86ff86db216dc0240462de40c8290887a613dddf9685508eb39479037ba97b5b" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "wasmtime-winch" -version = "29.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdbabfb8f20502d5e1d81092b9ead3682ae59988487aafcd7567387b7a43cf8f" -dependencies = [ - "anyhow", - "cranelift-codegen", - "gimli", - "object 0.36.7", - "target-lexicon 0.13.5", - "wasmparser 0.221.3", - "wasmtime-cranelift", - "wasmtime-environ", - "winch-codegen", -] - -[[package]] -name = "wasmtime-wit-bindgen" -version = "29.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8358319c2dd1e4db79e3c1c5d3a5af84956615343f9f89f4e4996a36816e06e6" -dependencies = [ - "anyhow", - "heck", - "indexmap", - "wit-parser 0.221.3", -] - -[[package]] -name = "wast" -version = "245.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28cf1149285569120b8ce39db8b465e8a2b55c34cbb586bd977e43e2bc7300bf" -dependencies = [ - "bumpalo", - "leb128fmt", - "memchr", - "unicode-width 0.2.0", - "wasm-encoder 0.245.1", -] - -[[package]] -name = "wat" -version = "1.245.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd48d1679b6858988cb96b154dda0ec5bbb09275b71db46057be37332d5477be" -dependencies = [ - "wast", -] - [[package]] name = "web-sys" version = "0.3.85" @@ -6005,21 +6417,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "winch-codegen" -version = "29.0.1" +name = "windows" +version = "0.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f849ef2c5f46cb0a20af4b4487aaa239846e52e2c03f13fa3c784684552859c" +checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" dependencies = [ - "anyhow", - "cranelift-codegen", - "gimli", - "regalloc2", - "smallvec", - "target-lexicon 0.13.5", - "thiserror 1.0.69", - "wasmparser 0.221.3", - "wasmtime-cranelift", - "wasmtime-environ", + "windows-core 0.58.0", + "windows-targets 0.52.6", ] [[package]] @@ -6029,7 +6433,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" dependencies = [ "windows-collections", - "windows-core", + "windows-core 0.62.2", "windows-future", "windows-numerics", ] @@ -6040,7 +6444,20 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" dependencies = [ - "windows-core", + "windows-core 0.62.2", +] + +[[package]] +name = "windows-core" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" +dependencies = [ + "windows-implement 0.58.0", + "windows-interface 0.58.0", + "windows-result 0.2.0", + "windows-strings 0.1.0", + "windows-targets 0.52.6", ] [[package]] @@ -6049,11 +6466,11 @@ version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ - "windows-implement", - "windows-interface", + "windows-implement 0.60.2", + "windows-interface 0.59.3", "windows-link", - "windows-result", - "windows-strings", + "windows-result 0.4.1", + "windows-strings 0.5.1", ] [[package]] @@ -6062,11 +6479,22 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" dependencies = [ - "windows-core", + "windows-core 0.62.2", "windows-link", "windows-threading", ] +[[package]] +name = "windows-implement" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "windows-implement" version = "0.60.2" @@ -6078,6 +6506,17 @@ dependencies = [ "syn", ] +[[package]] +name = "windows-interface" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "windows-interface" version = "0.59.3" @@ -6101,7 +6540,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" dependencies = [ - "windows-core", + "windows-core 0.62.2", "windows-link", ] @@ -6112,8 +6551,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" dependencies = [ "windows-link", - "windows-result", - "windows-strings", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", ] [[package]] @@ -6125,6 +6573,16 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result 0.2.0", + "windows-targets 0.52.6", +] + [[package]] name = "windows-strings" version = "0.5.1" @@ -6476,7 +6934,7 @@ checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" dependencies = [ "anyhow", "heck", - "wit-parser 0.244.0", + "wit-parser", ] [[package]] @@ -6523,28 +6981,10 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "wasm-encoder 0.244.0", + "wasm-encoder", "wasm-metadata", - "wasmparser 0.244.0", - "wit-parser 0.244.0", -] - -[[package]] -name = "wit-parser" -version = "0.221.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "896112579ed56b4a538b07a3d16e562d101ff6265c46b515ce0c701eef16b2ac" -dependencies = [ - "anyhow", - "id-arena", - "indexmap", - "log", - "semver", - "serde", - "serde_derive", - "serde_json", - "unicode-xid", - "wasmparser 0.221.3", + "wasmparser", + "wit-parser", ] [[package]] @@ -6562,7 +7002,7 @@ dependencies = [ "serde_derive", "serde_json", "unicode-xid", - "wasmparser 0.244.0", + "wasmparser", ] [[package]] @@ -6576,8 +7016,8 @@ dependencies = [ "log", "serde", "thiserror 2.0.18", - "windows", - "windows-core", + "windows 0.62.2", + "windows-core 0.62.2", ] [[package]] @@ -6605,6 +7045,24 @@ dependencies = [ "web-sys", ] +[[package]] +name = "x509-parser" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d43b0f71ce057da06bc0851b23ee24f3f86190b07203dd8f567d0b706a185202" +dependencies = [ + "asn1-rs", + "data-encoding", + "der-parser", + "lazy_static", + "nom", + "oid-registry", + "ring", + "rusticata-macros", + "thiserror 2.0.18", + "time", +] + [[package]] name = "xml-rs" version = "0.8.28" @@ -6625,9 +7083,15 @@ name = "xtask" version = "0.1.0" dependencies = [ "clap", - "libc", - "serde", - "toml", +] + +[[package]] +name = "yasna" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" +dependencies = [ + "time", ] [[package]] @@ -6758,31 +7222,3 @@ name = "zmij" version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" - -[[package]] -name = "zstd" -version = "0.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" -dependencies = [ - "zstd-safe", -] - -[[package]] -name = "zstd-safe" -version = "7.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" -dependencies = [ - "zstd-sys", -] - -[[package]] -name = "zstd-sys" -version = "2.0.16+zstd.1.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" -dependencies = [ - "cc", - "pkg-config", -] diff --git a/Cargo.toml b/Cargo.toml index 1f5565d..3d56f08 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,23 +1,21 @@ [workspace] members = [ ".", - "crates/python", - "crates/wasm/runtime", - "crates/bin-runner", + "crates/bindings/python", + "crates/bindings/wasm-runtime", "crates/simulation", "crates/dashboard", "crates/distribution", - "crates/std", "crates/process", "crates/datastore", "crates/shared-types", - "crates/swactor-node", + "crates/node", "crates/streams", "tests/docker", "crates/ci", "xtask", ] -exclude = ["tools/depgraph", "crates/wasm/crypto"] +exclude = ["tools/depgraph", "crates/bindings/wasm-crypto"] [package] name = "swactor" @@ -33,7 +31,8 @@ strip = false crate-type = ["rlib"] [features] -default = ["getrandom"] +default = ["getrandom", "std"] +std = [] # OTP patterns: supervisors, registries, timers, routers getrandom = ["dep:getrandom"] serde = ["dep:serde"] tracing = ["dep:tracing"] @@ -54,7 +53,6 @@ crossbeam-utils = "0.8.21" criterion = { version = "0.5", features = ["html_reports"] } proptest = "1" proptest-state-machine = "0.3" -swactor-std = { path = "crates/std" } [[bench]] name = "runtime_benchmarks" @@ -68,6 +66,3 @@ harness = false name = "hasher_benchmarks" harness = false -[[example]] -name = "tcp_ping_pong" -required-features = ["transport"] diff --git a/README.md b/README.md index 861a90b..cc69109 100644 --- a/README.md +++ b/README.md @@ -1,152 +1,24 @@ # swactor -Minimal actor runtime for Rust. Single-threaded or multi-threaded, with -Python and WebAssembly bindings. +Minimal actor runtime for Rust. One trait, one message type. +Single-threaded (`tick()`) or multi-threaded (`run()`). -## Quick Start +## Description -```rust -use swactor::actor::{ActorAddress, ActorInterface}; -use swactor::runtime::{Ctx, Runtime, RuntimeConfig}; +Core runtime is `src/`. Actors implement `ActorInterface` (in `actor.rs`), +interact through `Ctx` (in `runtime.rs`), and run on worker threads (`worker.rs`). -#[derive(Clone)] -struct Greet { name: String, reply_to: ActorAddress } +`crates/` builds upward: `std` adds OTP patterns (supervision, monitoring, groups), +`distribution` adds clustering, everything else composes from there. -#[derive(Clone)] -struct Greeting(String); +## Dev commands -struct Greeter; +`cargo xtask --help` for available test groups. -impl ActorInterface for Greeter { - type Incoming = Greet; - type Response = Greeting; +## Testing - fn handle(&mut self, ctx: &Ctx, msg: Greet) { - let _ = ctx.send(msg.reply_to, Greeting(format!("Hello, {}!", msg.name))); - } -} - -fn main() { - let rt = Runtime::new(RuntimeConfig::default()); - let addr = rt.spawn(Greeter).unwrap(); - let inbox = rt.new_inbox::().unwrap(); - - rt.send_to(addr, Greet { name: "world".into(), reply_to: *inbox.addr() }).unwrap(); - rt.tick(); - rt.tick(); - - println!("{}", inbox.try_recv().unwrap().0); // "Hello, world!" -} ``` - -## Features - -### Actor Model - -Actors implement one trait (`ActorInterface`), receive one message type, and -hold mutable state. No lifecycle hooks, no supervision trees, no async. - -Every actor gets a 32-byte globally unique `ActorAddress`. The same -`ctx.send(addr, msg)` call works whether the target is on the same worker, -a different worker thread, an external inbox, or a remote process. - -Single-threaded mode (`rt.tick()`) gives deterministic frame-level control. -Multi-threaded mode (`rt.run()`) spawns OS threads with adaptive backoff. - -See [docs/runtime/actor-model.md](docs/runtime/actor-model.md) and -[docs/runtime/runtime.md](docs/runtime/runtime.md) for the full model. - -### Transport - -Pluggable cross-process messaging. User-provided codecs handle serialization -(gRPC/protobuf, bincode, hand-rolled — no serde bounds imposed) and -user-provided transports handle delivery (TCP, in-memory, gRPC channel). - -```bash -cargo build --features transport -cargo run --example tcp_ping_pong --features transport -- receiver # terminal 1 -cargo run --example tcp_ping_pong --features transport -- sender # terminal 2 +cargo check --workspace +cargo xtask test +cargo xtask test essential ``` - -See [docs/distribution/transport.md](docs/distribution/transport.md) for the routing chain, codec -registry, and address resolution. - -### Runtime Dashboard - -Live web dashboard for monitoring actors, message throughput, and mailbox -depths. Supports trace recording and replay at configurable speed. - -Includes hand-authored SVG diagrams (actor lifecycle, message lifecycle, -tick cycle, transport routing) and generated diagrams from DOT sources -(architecture, dataflow, type erasure). - -See [crates/runtime-dashboard/](crates/runtime-dashboard/README.md). - -### Language Bindings - -**Python** — PyO3 via Maturin. Spawn actors from Python callables, pass -dicts as messages, single-threaded or multi-threaded. - -```bash -cd crates/swactor-python && maturin develop -``` - -Examples in `examples/python/` (single-thread, async, Jupyter notebook). - -**WASM** — wasm-bindgen. Runs single-threaded with deterministic addressing -(`no_random` feature). - -```bash -cd crates/swactor-wasm && wasm-pack build --target nodejs -``` - -### Connectome Analysis - -Structural analysis of the internal dependency graph. - -- **depgraph** (`tools/depgraph/`) — AST-based extraction of module - dependencies, outputs GraphViz DOT -- **spectral** (`tools/spectral/`) — Laplacian eigenvalue analysis, - Connectome Complexity Index (CCI), coupling heatmaps, interactive HTML - dashboard - -```bash -cargo run --manifest-path tools/depgraph/Cargo.toml -- --src-dir src/ --output deps -python tools/spectral/spectral_analysis.py deps.dot -``` - -See [docs/connectome/connectome.md](docs/connectome/connectome.md) for metric interpretation. - -## Building & Testing - -```bash -cargo test # all tests -cargo test --features transport # include transport tests -cargo run --example hello # single actor example -cargo run --example ring # 500-actor ring topology -cargo bench # benchmarks (criterion) -``` - -## Feature Flags - -| Flag | Default | What it does | -|------|---------|--------------| -| `getrandom` | yes | System RNG for actor addresses | -| `no_random` | no | Deterministic counter (WASM / reproducible tests) | -| `transport` | no | Pluggable remote messaging (codec + transport) | -| `tracing` | no | `tracing` instrumentation for runtime internals | -| `serde` | no | Serde derives for stats types | -| `python` | no | PyO3 bindings (cdylib wheel) | - -## Documentation - -| Document | Covers | -|----------|--------| -| [Actor Model](docs/runtime/actor-model.md) | Traits, type erasure, addresses | -| [Runtime](docs/runtime/runtime.md) | Runtime, Ctx, Inbox, RuntimeHandle, stats | -| [Worker Thread](docs/runtime/worker-thread.md) | Tick phases, backoff, routing, full system topology | -| [Channels](docs/runtime/channels.md) | HybridChannel, AddressMap, Placement | -| [Transport](docs/distribution/transport.md) | Codec, Transport, remote messaging, address resolution | -| [Distribution](docs/distribution/distribution.md) | SWIM membership, Kademlia, NodeDriver | -| [Connectome](docs/connectome/connectome.md) | CCI metrics, spectral analysis interpretation | -| [Dashboard](crates/runtime-dashboard/README.md) | Live web UI, trace recording, diagram index | diff --git a/benches/runtime_benchmarks.rs b/benches/runtime_benchmarks.rs index b308c04..0270440 100644 --- a/benches/runtime_benchmarks.rs +++ b/benches/runtime_benchmarks.rs @@ -7,7 +7,7 @@ use swactor::{ config::RuntimeConfig, runtime::{Ctx, Runtime}, }; -use swactor_std::{RuntimeGroups, RuntimeNaming, StdExtension}; +use swactor::std::{RuntimeGroups, RuntimeNaming, StdExtension}; // --------------------------------------------------------------------------- // Helper diff --git a/crates/bin-runner/Cargo.toml b/crates/bin-runner/Cargo.toml deleted file mode 100644 index d7891dd..0000000 --- a/crates/bin-runner/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "swactor-bin-runner" -version = "0.1.0" -edition = "2024" - -[dependencies] -swactor = { path = "../.." } -wasmtime = "29" - -[dev-dependencies] -swactor = { path = "../..", features = ["getrandom"] } -swactor-std = { path = "../std" } -wat = "1" -proptest = "1" diff --git a/crates/bin-runner/build.rs b/crates/bin-runner/build.rs deleted file mode 100644 index c1c291d..0000000 --- a/crates/bin-runner/build.rs +++ /dev/null @@ -1,28 +0,0 @@ -use std::path::Path; -use std::process::Command; - -fn main() { - let manifest_dir = env!("CARGO_MANIFEST_DIR"); - let guests_dir = Path::new(manifest_dir).join("tests/guests"); - - for guest in &["echo", "double", "silent"] { - let guest_dir = guests_dir.join(guest); - - println!( - "cargo:rerun-if-changed={}", - guest_dir.join("src/lib.rs").display() - ); - println!( - "cargo:rerun-if-changed={}", - guest_dir.join("Cargo.toml").display() - ); - - let status = Command::new("cargo") - .args(["build", "--target", "wasm32-unknown-unknown", "--release"]) - .current_dir(&guest_dir) - .status() - .unwrap_or_else(|e| panic!("failed to run cargo build for {guest} guest: {e}")); - - assert!(status.success(), "failed to build {guest} guest"); - } -} diff --git a/crates/bin-runner/src/actor.rs b/crates/bin-runner/src/actor.rs deleted file mode 100644 index 93d1c0a..0000000 --- a/crates/bin-runner/src/actor.rs +++ /dev/null @@ -1,63 +0,0 @@ -use swactor::actor::{ActorInterface, Ctx}; -use wasmtime::{Memory, Store, TypedFunc}; - -use crate::ByteMessage; - -/// State accessible to host functions during guest execution. -#[derive(Default)] -pub(crate) struct HostState { - pub outbox: Vec<(swactor::actor::ActorAddress, Vec)>, -} - -/// An actor whose logic is defined by a WebAssembly guest module. -/// -/// Messages arrive as [`ByteMessage`], are copied into Wasm linear memory, -/// and processed by the guest's `handle` export. The guest can send messages -/// back via the `swactor.send` host import. -pub struct WasmActor { - pub(crate) store: Store, - pub(crate) memory: Memory, - pub(crate) alloc: TypedFunc, - pub(crate) handle: TypedFunc<(i32, i32), ()>, -} - -impl ActorInterface for WasmActor { - type Incoming = ByteMessage; - type Response = (); - - fn handle(&mut self, ctx: &Ctx, msg: ByteMessage) { - let bytes = &msg.0; - let len: i32 = match i32::try_from(bytes.len()) { - Ok(n) => n, - Err(_) => return, // message too large for i32 ABI - }; - - // 1. Allocate space in guest memory - let ptr = match self.alloc.call(&mut self.store, len) { - Ok(ptr) if ptr < 0 => return, // invalid pointer - Ok(0) if len > 0 => return, // OOM — drop message - Ok(ptr) => ptr, - Err(_) => return, // alloc trapped — drop message - }; - - // 2. Write message bytes into guest memory - let mem = self.memory.data_mut(&mut self.store); - let end = (ptr as usize).saturating_add(bytes.len()); - if end > mem.len() { - return; // alloc returned OOB pointer — drop message - } - mem[ptr as usize..end].copy_from_slice(bytes); - - // 3. Call guest handle - if self.handle.call(&mut self.store, (ptr, len)).is_err() { - self.store.data_mut().outbox.clear(); // discard sends from incomplete operation - return; // handle trapped — drop message, keep actor alive - } - - // 4. Drain outbox → send via ctx - let outbox: Vec<_> = self.store.data_mut().outbox.drain(..).collect(); - for (dest, payload) in outbox { - let _ = ctx.send(dest, ByteMessage(payload)); - } - } -} diff --git a/crates/bin-runner/src/builder.rs b/crates/bin-runner/src/builder.rs deleted file mode 100644 index 06a199c..0000000 --- a/crates/bin-runner/src/builder.rs +++ /dev/null @@ -1,109 +0,0 @@ -use swactor::actor::ActorAddress; -use wasmtime::{Linker, Module, Store, TypedFunc}; - -use crate::actor::{HostState, WasmActor}; -use crate::engine::SharedEngine; -use crate::error::WasmActorError; - -/// Compiles a Wasm module and produces a ready-to-use [`WasmActor`]. -pub struct WasmActorBuilder { - engine: SharedEngine, - wasm_bytes: Vec, -} - -impl WasmActorBuilder { - pub fn new(engine: SharedEngine, wasm_bytes: impl Into>) -> Self { - Self { - engine, - wasm_bytes: wasm_bytes.into(), - } - } - - /// Compile the module, link host functions, and instantiate. - pub fn build(self) -> Result { - let engine = self.engine.inner(); - let module = Module::new(engine, &self.wasm_bytes)?; - - let mut linker: Linker = Linker::new(engine); - Self::link_send(&mut linker)?; - - let mut store = Store::new(engine, HostState::default()); - let instance = linker.instantiate(&mut store, &module)?; - - // Extract required exports - let memory = instance - .get_memory(&mut store, "memory") - .ok_or(WasmActorError::MissingExport("memory"))?; - - let alloc: TypedFunc = instance - .get_typed_func(&mut store, "alloc") - .map_err(|_| WasmActorError::MissingExport("alloc"))?; - - let handle: TypedFunc<(i32, i32), ()> = instance - .get_typed_func(&mut store, "handle") - .map_err(|_| WasmActorError::MissingExport("handle"))?; - - Ok(WasmActor { - store, - memory, - alloc, - handle, - }) - } - - /// Link the `swactor.send` host import. - fn link_send(linker: &mut Linker) -> Result<(), WasmActorError> { - linker.func_wrap( - "swactor", - "send", - |mut caller: wasmtime::Caller<'_, HostState>, - dest_ptr: i32, - payload_ptr: i32, - payload_len: i32| - -> Result<(), wasmtime::Error> { - let mem = caller - .get_export("memory") - .and_then(|e| e.into_memory()) - .ok_or_else(|| wasmtime::Error::msg("guest must export memory"))?; - let data = mem.data(&caller); - let mem_len = data.len(); - - // Validate non-negative arguments - if dest_ptr < 0 || payload_ptr < 0 || payload_len < 0 { - return Err(wasmtime::Error::msg( - "negative argument in swactor.send", - )); - } - - let dest_ptr = dest_ptr as usize; - let payload_ptr = payload_ptr as usize; - let payload_len = payload_len as usize; - - // Bounds-check with overflow protection - let dest_end = dest_ptr - .checked_add(32) - .ok_or_else(|| wasmtime::Error::msg("dest_ptr overflow"))?; - let payload_end = payload_ptr - .checked_add(payload_len) - .ok_or_else(|| wasmtime::Error::msg("payload range overflow"))?; - if dest_end > mem_len || payload_end > mem_len { - return Err(wasmtime::Error::msg( - "out-of-bounds memory access in swactor.send", - )); - } - - // Read 32-byte destination address - let mut addr_bytes = [0u8; 32]; - addr_bytes.copy_from_slice(&data[dest_ptr..dest_end]); - let dest = ActorAddress(addr_bytes); - - // Read payload - let payload = data[payload_ptr..payload_end].to_vec(); - - caller.data_mut().outbox.push((dest, payload)); - Ok(()) - }, - )?; - Ok(()) - } -} diff --git a/crates/bin-runner/src/engine.rs b/crates/bin-runner/src/engine.rs deleted file mode 100644 index 8cc2dfc..0000000 --- a/crates/bin-runner/src/engine.rs +++ /dev/null @@ -1,35 +0,0 @@ -use std::sync::Arc; - -use wasmtime::Engine; - -/// A shared, cheaply-cloneable Wasm engine. -/// -/// Created once and reused across multiple [`WasmActor`](crate::WasmActor) instances. -/// Configured with maximum sandboxing — no threads, no SIMD, no reference types. -#[derive(Clone)] -pub struct SharedEngine(Arc); - -impl std::fmt::Debug for SharedEngine { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_tuple("SharedEngine").field(&"").finish() - } -} - -impl SharedEngine { - /// Create a new engine with sandboxed defaults. - pub fn new() -> Result { - let mut config = wasmtime::Config::new(); - config.wasm_threads(false); - config.wasm_simd(false); - config.wasm_relaxed_simd(false); - config.wasm_reference_types(false); - config.wasm_multi_value(false); - config.wasm_bulk_memory(true); - let engine = Engine::new(&config)?; - Ok(Self(Arc::new(engine))) - } - - pub(crate) fn inner(&self) -> &Engine { - &self.0 - } -} diff --git a/crates/bin-runner/src/error.rs b/crates/bin-runner/src/error.rs deleted file mode 100644 index b91c052..0000000 --- a/crates/bin-runner/src/error.rs +++ /dev/null @@ -1,27 +0,0 @@ -use std::fmt; - -/// Errors that can occur when building or running a WasmActor. -#[derive(Debug)] -pub enum WasmActorError { - /// A required export is missing from the Wasm module. - MissingExport(&'static str), - /// The Wasm module failed to compile or instantiate. - Wasmtime(wasmtime::Error), -} - -impl fmt::Display for WasmActorError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::MissingExport(name) => write!(f, "missing required export: `{name}`"), - Self::Wasmtime(e) => write!(f, "wasmtime error: {e}"), - } - } -} - -impl std::error::Error for WasmActorError {} - -impl From for WasmActorError { - fn from(e: wasmtime::Error) -> Self { - Self::Wasmtime(e) - } -} diff --git a/crates/bin-runner/src/lib.rs b/crates/bin-runner/src/lib.rs deleted file mode 100644 index 3f744c2..0000000 --- a/crates/bin-runner/src/lib.rs +++ /dev/null @@ -1,13 +0,0 @@ -mod actor; -mod builder; -mod engine; -mod error; - -pub use actor::WasmActor; -pub use builder::WasmActorBuilder; -pub use engine::SharedEngine; -pub use error::WasmActorError; - -/// A message carrying raw bytes, suitable for passing to/from Wasm guests. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct ByteMessage(pub Vec); diff --git a/crates/bin-runner/tests/guests/double/Cargo.lock b/crates/bin-runner/tests/guests/double/Cargo.lock deleted file mode 100644 index b46d745..0000000 --- a/crates/bin-runner/tests/guests/double/Cargo.lock +++ /dev/null @@ -1,7 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "double-guest" -version = "0.1.0" diff --git a/crates/bin-runner/tests/guests/double/Cargo.toml b/crates/bin-runner/tests/guests/double/Cargo.toml deleted file mode 100644 index 943eabe..0000000 --- a/crates/bin-runner/tests/guests/double/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[workspace] - -[package] -name = "double-guest" -version = "0.1.0" -edition = "2024" - -[lib] -crate-type = ["cdylib"] - -[profile.release] -opt-level = "s" -lto = true diff --git a/crates/bin-runner/tests/guests/double/src/lib.rs b/crates/bin-runner/tests/guests/double/src/lib.rs deleted file mode 100644 index 2ea9158..0000000 --- a/crates/bin-runner/tests/guests/double/src/lib.rs +++ /dev/null @@ -1,63 +0,0 @@ -#![no_std] - -use core::cell::UnsafeCell; -use core::panic::PanicInfo; - -// --- bump allocator --- -const HEAP_SIZE: usize = 65536; - -struct BumpAlloc { - heap: UnsafeCell<[u8; HEAP_SIZE]>, - offset: UnsafeCell, -} - -unsafe impl Sync for BumpAlloc {} - -static ALLOC: BumpAlloc = BumpAlloc { - heap: UnsafeCell::new([0u8; HEAP_SIZE]), - offset: UnsafeCell::new(0), -}; - -#[unsafe(no_mangle)] -pub extern "C" fn alloc(size: i32) -> i32 { - unsafe { - let offset = &mut *ALLOC.offset.get(); - let heap = &mut *ALLOC.heap.get(); - let align = 8; - let start = (*offset + align - 1) & !(align - 1); - let end = start + size as usize; - if end > heap.len() { - return 0; // OOM - } - *offset = end; - heap.as_ptr().add(start) as i32 - } -} - -// --- host import --- -#[link(wasm_import_module = "swactor")] -unsafe extern "C" { - #[link_name = "send"] - fn host_send(dest_ptr: i32, payload_ptr: i32, payload_len: i32); -} - -/// Message format: first 32 bytes = destination address, rest = payload. -/// Sends the payload back twice to demonstrate multi-send. -#[unsafe(no_mangle)] -pub extern "C" fn handle(ptr: i32, len: i32) { - if len < 32 { - return; - } - let dest_ptr = ptr; - let payload_ptr = ptr + 32; - let payload_len = len - 32; - unsafe { - host_send(dest_ptr, payload_ptr, payload_len); - host_send(dest_ptr, payload_ptr, payload_len); - } -} - -#[panic_handler] -fn panic(_info: &PanicInfo) -> ! { - loop {} -} diff --git a/crates/bin-runner/tests/guests/echo/Cargo.lock b/crates/bin-runner/tests/guests/echo/Cargo.lock deleted file mode 100644 index 224c914..0000000 --- a/crates/bin-runner/tests/guests/echo/Cargo.lock +++ /dev/null @@ -1,7 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "echo-guest" -version = "0.1.0" diff --git a/crates/bin-runner/tests/guests/echo/Cargo.toml b/crates/bin-runner/tests/guests/echo/Cargo.toml deleted file mode 100644 index 7c33f5c..0000000 --- a/crates/bin-runner/tests/guests/echo/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[workspace] - -[package] -name = "echo-guest" -version = "0.1.0" -edition = "2024" - -[lib] -crate-type = ["cdylib"] - -[profile.release] -opt-level = "s" -lto = true diff --git a/crates/bin-runner/tests/guests/echo/src/lib.rs b/crates/bin-runner/tests/guests/echo/src/lib.rs deleted file mode 100644 index edc69a5..0000000 --- a/crates/bin-runner/tests/guests/echo/src/lib.rs +++ /dev/null @@ -1,62 +0,0 @@ -#![no_std] - -use core::cell::UnsafeCell; -use core::panic::PanicInfo; - -// --- bump allocator --- -const HEAP_SIZE: usize = 65536; - -struct BumpAlloc { - heap: UnsafeCell<[u8; HEAP_SIZE]>, - offset: UnsafeCell, -} - -unsafe impl Sync for BumpAlloc {} - -static ALLOC: BumpAlloc = BumpAlloc { - heap: UnsafeCell::new([0u8; HEAP_SIZE]), - offset: UnsafeCell::new(0), -}; - -#[unsafe(no_mangle)] -pub extern "C" fn alloc(size: i32) -> i32 { - unsafe { - let offset = &mut *ALLOC.offset.get(); - let heap = &mut *ALLOC.heap.get(); - let align = 8; - let start = (*offset + align - 1) & !(align - 1); - let end = start + size as usize; - if end > heap.len() { - return 0; // OOM - } - *offset = end; - heap.as_ptr().add(start) as i32 - } -} - -// --- host import --- -#[link(wasm_import_module = "swactor")] -unsafe extern "C" { - #[link_name = "send"] - fn host_send(dest_ptr: i32, payload_ptr: i32, payload_len: i32); -} - -/// Message format: first 32 bytes = destination address, rest = payload. -/// Echo sends the payload portion back to the specified destination. -#[unsafe(no_mangle)] -pub extern "C" fn handle(ptr: i32, len: i32) { - if len < 32 { - return; - } - let dest_ptr = ptr; - let payload_ptr = ptr + 32; - let payload_len = len - 32; - unsafe { - host_send(dest_ptr, payload_ptr, payload_len); - } -} - -#[panic_handler] -fn panic(_info: &PanicInfo) -> ! { - loop {} -} diff --git a/crates/bin-runner/tests/guests/silent/Cargo.lock b/crates/bin-runner/tests/guests/silent/Cargo.lock deleted file mode 100644 index aff96a4..0000000 --- a/crates/bin-runner/tests/guests/silent/Cargo.lock +++ /dev/null @@ -1,7 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "silent-guest" -version = "0.1.0" diff --git a/crates/bin-runner/tests/guests/silent/Cargo.toml b/crates/bin-runner/tests/guests/silent/Cargo.toml deleted file mode 100644 index 14e524f..0000000 --- a/crates/bin-runner/tests/guests/silent/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[workspace] - -[package] -name = "silent-guest" -version = "0.1.0" -edition = "2024" - -[lib] -crate-type = ["cdylib"] - -[profile.release] -opt-level = "s" -lto = true diff --git a/crates/bin-runner/tests/guests/silent/src/lib.rs b/crates/bin-runner/tests/guests/silent/src/lib.rs deleted file mode 100644 index c1d2a1c..0000000 --- a/crates/bin-runner/tests/guests/silent/src/lib.rs +++ /dev/null @@ -1,45 +0,0 @@ -#![no_std] - -use core::cell::UnsafeCell; -use core::panic::PanicInfo; - -// --- bump allocator --- -const HEAP_SIZE: usize = 65536; - -struct BumpAlloc { - heap: UnsafeCell<[u8; HEAP_SIZE]>, - offset: UnsafeCell, -} - -unsafe impl Sync for BumpAlloc {} - -static ALLOC: BumpAlloc = BumpAlloc { - heap: UnsafeCell::new([0u8; HEAP_SIZE]), - offset: UnsafeCell::new(0), -}; - -#[unsafe(no_mangle)] -pub extern "C" fn alloc(size: i32) -> i32 { - unsafe { - let offset = &mut *ALLOC.offset.get(); - let heap = &mut *ALLOC.heap.get(); - let align = 8; - let start = (*offset + align - 1) & !(align - 1); - let end = start + size as usize; - if end > heap.len() { - return 0; // OOM - } - *offset = end; - heap.as_ptr().add(start) as i32 - } -} - -#[unsafe(no_mangle)] -pub extern "C" fn handle(_ptr: i32, _len: i32) { - // Silent: receive bytes, do nothing -} - -#[panic_handler] -fn panic(_info: &PanicInfo) -> ! { - loop {} -} diff --git a/crates/bin-runner/tests/wasm_actor.proptest-regressions b/crates/bin-runner/tests/wasm_actor.proptest-regressions deleted file mode 100644 index 8a4ef8c..0000000 --- a/crates/bin-runner/tests/wasm_actor.proptest-regressions +++ /dev/null @@ -1,8 +0,0 @@ -# Seeds for failure cases proptest has generated in the past. It is -# automatically read and these particular cases re-run before any -# novel cases are generated. -# -# It is recommended to check this file in to source control so that -# everyone who runs the test benefits from these saved cases. -cc f8674b306ba8187ad75bcc5a33c4a679e497a0053dc6fdb2234ccaeaae8f85b7 # shrinks to guest_idx = 0, n_msgs = 0, ticks_before = 1, ticks_after = 1 -cc db98a9c0726c0477ba56ec6098a1f21518faaff4551d90e915790ce3c890dd53 # shrinks to payload = [] diff --git a/crates/bin-runner/tests/wasm_actor.rs b/crates/bin-runner/tests/wasm_actor.rs deleted file mode 100644 index 341a2d8..0000000 --- a/crates/bin-runner/tests/wasm_actor.rs +++ /dev/null @@ -1,1367 +0,0 @@ -use swactor::actor::{ActorAddress, ActorInterface}; -use swactor::runtime::{Ctx, Runtime, RuntimeConfig}; -use swactor_bin_runner::{ByteMessage, SharedEngine, WasmActor, WasmActorBuilder, WasmActorError}; -use swactor_std::{CtxWatching, StdExtension}; - -use proptest::prelude::*; - -fn guest_wasm(name: &str) -> Vec { - let path = format!( - "{}/tests/guests/{name}/target/wasm32-unknown-unknown/release/{name}_guest.wasm", - env!("CARGO_MANIFEST_DIR") - ); - std::fs::read(&path).unwrap_or_else(|e| panic!("failed to read {path}: {e}")) -} - -/// Build a message with an inbox address prepended (the guest contract). -fn framed_msg(dest: &ActorAddress, payload: &[u8]) -> ByteMessage { - let mut buf = Vec::with_capacity(32 + payload.len()); - buf.extend_from_slice(&dest.0); - buf.extend_from_slice(payload); - ByteMessage(buf) -} - -/// Build a WAT module where alloc returns a constant value. -fn alloc_returns_wat(alloc_val: i32) -> Vec { - let wat = format!( - r#"(module - (import "swactor" "send" (func $send (param i32 i32 i32))) - (memory (export "memory") 1) - (func (export "alloc") (param i32) (result i32) i32.const {alloc_val}) - (func (export "handle") (param i32 i32)) - )"# - ); - wat::parse_str(&wat).unwrap() -} - -/// Build a WAT module where handle calls send with specific arguments. -fn send_args_wat(dest_ptr: i32, payload_ptr: i32, payload_len: i32) -> Vec { - let wat = format!( - r#"(module - (import "swactor" "send" (func $send (param i32 i32 i32))) - (memory (export "memory") 1) - (func (export "alloc") (param i32) (result i32) i32.const 256) - (func (export "handle") (param i32 i32) - i32.const {dest_ptr} - i32.const {payload_ptr} - i32.const {payload_len} - call $send - ) - )"# - ); - wat::parse_str(&wat).unwrap() -} - -// ═══════════════════════════════════════════════════════════════════════════════ -// Group 1: Builder contract -// ═══════════════════════════════════════════════════════════════════════════════ - -#[test] -fn build_succeeds_for_valid_modules() { - let engine = SharedEngine::new().unwrap(); - - // Pre-compiled guests - WasmActorBuilder::new(engine.clone(), guest_wasm("echo")).build().unwrap(); - WasmActorBuilder::new(engine.clone(), guest_wasm("double")).build().unwrap(); - WasmActorBuilder::new(engine.clone(), guest_wasm("silent")).build().unwrap(); - - // Custom WAT with extra exports, data segments, funcref table - let extras = wat::parse_str(r#"(module - (import "swactor" "send" (func $send (param i32 i32 i32))) - (memory (export "memory") 1) - (data (i32.const 0) "hello") - (table 1 funcref) - (func (export "alloc") (param i32) (result i32) i32.const 256) - (func (export "handle") (param i32 i32)) - (func (export "custom_fn") (result i32) i32.const 42) - )"#).unwrap(); - WasmActorBuilder::new(engine.clone(), extras).build().unwrap(); - - // Module without send import (doesn't import swactor.send) - let no_send = wat::parse_str(r#"(module - (memory (export "memory") 1) - (func (export "alloc") (param i32) (result i32) i32.const 256) - (func (export "handle") (param i32 i32)) - )"#).unwrap(); - WasmActorBuilder::new(engine, no_send).build().unwrap(); -} - -#[test] -fn build_rejects_missing_exports() { - let engine = SharedEngine::new().unwrap(); - - // Empty module — missing everything - let result = WasmActorBuilder::new(engine.clone(), wat::parse_str("(module)").unwrap()).build(); - let err = result.err().expect("should fail"); - let msg = format!("{err}"); - assert!(msg.contains("memory") || msg.contains("alloc"), "got: {msg}"); - - // Memory only — missing alloc - let mem_only = wat::parse_str("(module (memory (export \"memory\") 1))").unwrap(); - let err = WasmActorBuilder::new(engine.clone(), mem_only).build().err().expect("should fail"); - assert!(format!("{err}").contains("alloc")); - - // Memory + alloc — missing handle - let no_handle = wat::parse_str(r#"(module - (memory (export "memory") 1) - (func (export "alloc") (param i32) (result i32) i32.const 0) - )"#).unwrap(); - let err = WasmActorBuilder::new(engine.clone(), no_handle).build().err().expect("should fail"); - assert!(format!("{err}").contains("handle")); - - // Wrong memory export name - let wrong_mem = wat::parse_str(r#"(module - (memory (export "mem") 1) - (func (export "alloc") (param i32) (result i32) i32.const 0) - (func (export "handle") (param i32 i32)) - )"#).unwrap(); - assert!(WasmActorBuilder::new(engine.clone(), wrong_mem).build().is_err()); - - // Wrong alloc signature (two params) - let wrong_alloc = wat::parse_str(r#"(module - (memory (export "memory") 1) - (func (export "alloc") (param i32 i32) (result i32) i32.const 0) - (func (export "handle") (param i32 i32)) - )"#).unwrap(); - assert!(WasmActorBuilder::new(engine.clone(), wrong_alloc).build().is_err()); - - // Wrong handle signature (returns i32) - let wrong_handle = wat::parse_str(r#"(module - (memory (export "memory") 1) - (func (export "alloc") (param i32) (result i32) i32.const 0) - (func (export "handle") (param i32 i32) (result i32) i32.const 0) - )"#).unwrap(); - assert!(WasmActorBuilder::new(engine, wrong_handle).build().is_err()); -} - -#[test] -fn build_rejects_invalid_wasm_and_disabled_features() { - let engine = SharedEngine::new().unwrap(); - - // Invalid bytes - assert!(WasmActorBuilder::new(engine.clone(), vec![0xDE, 0xAD]).build().is_err()); - - // Empty bytes - assert!(WasmActorBuilder::new(engine.clone(), vec![]).build().is_err()); - - // Truncated valid wasm - let valid = guest_wasm("echo"); - let truncated = valid[..valid.len() / 2].to_vec(); - assert!(WasmActorBuilder::new(engine.clone(), truncated).build().is_err()); - - // SIMD (disabled in engine config) - let simd = wat::parse_str(r#"(module - (memory (export "memory") 1) - (func (export "alloc") (param i32) (result i32) i32.const 0) - (func (export "handle") (param i32 i32) - v128.const i32x4 0 0 0 0 - drop - ) - )"#); - assert!(simd.is_err() || WasmActorBuilder::new(engine.clone(), simd.unwrap()).build().is_err()); - - // Multi-value (disabled) - let mv = wat::parse_str(r#"(module - (memory (export "memory") 1) - (func (export "alloc") (param i32) (result i32) i32.const 0) - (func (export "handle") (param i32 i32) - (block (result i32 i32) - i32.const 1 - i32.const 2 - ) - drop drop - ) - )"#); - assert!(mv.is_err() || WasmActorBuilder::new(engine, mv.unwrap()).build().is_err()); -} - -#[test] -fn error_display_and_from_impls() { - // MissingExport - let missing = WasmActorError::MissingExport("memory"); - let s = format!("{missing}"); - assert!(s.contains("memory"), "MissingExport display: {s}"); - - // Wasmtime error via From - let wt_err = wasmtime::Error::msg("test error"); - let converted: WasmActorError = wt_err.into(); - let s = format!("{converted}"); - assert!(s.contains("test error"), "Wasmtime display: {s}"); - - // Distinct display - let missing_s = format!("{}", WasmActorError::MissingExport("alloc")); - let wt_s = format!("{}", WasmActorError::Wasmtime(wasmtime::Error::msg("x"))); - assert_ne!(missing_s, wt_s); - - // Send + Sync - fn assert_send_sync() {} - assert_send_sync::(); -} - -proptest! { - #[test] - fn prop_builder_never_panics( - pages in 0u32..10, - alloc_return in prop_oneof![-100i32..0, 0i32..70000, Just(i32::MIN), Just(i32::MAX)], - ) { - let wat = format!( - r#"(module - (memory (export "memory") {pages}) - (func (export "alloc") (param i32) (result i32) i32.const {alloc_return}) - (func (export "handle") (param i32 i32)) - )"# - ); - if let Ok(wasm) = wat::parse_str(&wat) { - let engine = SharedEngine::new().unwrap(); - // Should succeed or return clean error — never panic - let _ = WasmActorBuilder::new(engine, wasm).build(); - } - } -} - -// ═══════════════════════════════════════════════════════════════════════════════ -// Group 2: Echo round-trip -// ═══════════════════════════════════════════════════════════════════════════════ - -#[test] -fn echo_round_trip_story() { - let engine = SharedEngine::new().unwrap(); - let actor = WasmActorBuilder::new(engine, guest_wasm("echo")).build().unwrap(); - let rt = Runtime::new(RuntimeConfig::default()); - let inbox = rt.new_inbox::().unwrap(); - let addr = rt.spawn(actor).unwrap(); - - // Text payload - rt.send_to(addr, framed_msg(inbox.addr(), b"hello wasm")).unwrap(); - rt.tick(); - assert_eq!(inbox.try_recv().unwrap().0, b"hello wasm"); - - // Binary payload (all 256 byte values) - let binary: Vec = (0..=255).collect(); - rt.send_to(addr, framed_msg(inbox.addr(), &binary)).unwrap(); - rt.tick(); - assert_eq!(inbox.try_recv().unwrap().0, binary); - - // Empty message — echo needs >= 32 bytes, so no reply - rt.send_to(addr, ByteMessage(vec![])).unwrap(); - rt.tick(); - assert!(inbox.try_recv().is_none()); - - // Address-only message (32 bytes, 0 payload) — echo sends empty payload - rt.send_to(addr, framed_msg(inbox.addr(), b"")).unwrap(); - rt.tick(); - let resp = inbox.try_recv().unwrap(); - assert!(resp.0.is_empty(), "address-only should echo empty payload"); - - // Single byte payload - rt.send_to(addr, framed_msg(inbox.addr(), &[0x42])).unwrap(); - rt.tick(); - assert_eq!(inbox.try_recv().unwrap().0, vec![0x42]); - - // Actor still alive after all those messages - rt.send_to(addr, framed_msg(inbox.addr(), b"fin")).unwrap(); - rt.tick(); - assert_eq!(inbox.try_recv().unwrap().0, b"fin"); -} - -proptest! { - #[test] - fn prop_echo_preserves_arbitrary_payload(payload in proptest::collection::vec(any::(), 0..500)) { - let engine = SharedEngine::new().unwrap(); - let actor = WasmActorBuilder::new(engine, guest_wasm("echo")).build().unwrap(); - let rt = Runtime::new(RuntimeConfig::default()); - let inbox = rt.new_inbox::().unwrap(); - let addr = rt.spawn(actor).unwrap(); - - rt.send_to(addr, framed_msg(inbox.addr(), &payload)).unwrap(); - rt.tick(); - - if payload.is_empty() { - // echo guest: if len < 32, returns empty; if len == 32 (addr only), returns empty payload - let resp = inbox.try_recv(); - // With 32-byte address + 0-byte payload, echo sends back empty - if let Some(msg) = resp { - prop_assert!(msg.0.is_empty()); - } - } else { - let received = inbox.try_recv().expect("echo should reply for non-empty payload"); - prop_assert_eq!(received.0, payload); - } - } -} - -#[test] -fn double_and_silent_contracts() { - let engine = SharedEngine::new().unwrap(); - let rt = Runtime::new(RuntimeConfig::default()); - let inbox = rt.new_inbox::().unwrap(); - - // Double: 1 message in → exactly 2 out - let double = WasmActorBuilder::new(engine.clone(), guest_wasm("double")).build().unwrap(); - let daddr = rt.spawn(double).unwrap(); - rt.send_to(daddr, framed_msg(inbox.addr(), b"dup")).unwrap(); - rt.tick(); - assert_eq!(inbox.try_recv().unwrap().0, b"dup"); - assert_eq!(inbox.try_recv().unwrap().0, b"dup"); - assert!(inbox.try_recv().is_none(), "exactly 2"); - - // Silent: messages in → 0 out - let silent = WasmActorBuilder::new(engine, guest_wasm("silent")).build().unwrap(); - let saddr = rt.spawn(silent).unwrap(); - for _ in 0..10 { - rt.send_to(saddr, framed_msg(inbox.addr(), b"ignored")).unwrap(); - } - rt.tick(); - assert!(inbox.try_recv().is_none(), "silent never sends"); -} - -// ═══════════════════════════════════════════════════════════════════════════════ -// Group 3: Alloc failure resilience -// ═══════════════════════════════════════════════════════════════════════════════ - -proptest! { - #[test] - fn prop_any_alloc_return_never_kills_actor( - alloc_val in prop_oneof![ - -100i32..0, - 0i32..70000, - Just(i32::MIN), - Just(i32::MAX), - Just(0i32), - Just(65500i32), - Just(65536i32), - ], - ) { - let wasm = alloc_returns_wat(alloc_val); - let engine = SharedEngine::new().unwrap(); - let actor = WasmActorBuilder::new(engine, wasm).build().unwrap(); - let rt = Runtime::new(RuntimeConfig::default()); - let addr = rt.spawn(actor).unwrap(); - - // Send two messages — actor must survive both regardless of alloc return - rt.send_to(addr, ByteMessage(vec![1, 2, 3])).unwrap(); - rt.tick(); - rt.send_to(addr, ByteMessage(vec![4, 5, 6])).unwrap(); - rt.tick(); - } -} - -#[test] -fn alloc_trap_recovery() { - // Alloc traps on first call (counter == 0), succeeds thereafter - let wat = r#"(module - (import "swactor" "send" (func $send (param i32 i32 i32))) - (memory (export "memory") 1) - (global $calls (mut i32) (i32.const 0)) - (func (export "alloc") (param $len i32) (result i32) - (global.set $calls (i32.add (global.get $calls) (i32.const 1))) - (if (result i32) (i32.eq (global.get $calls) (i32.const 1)) - (then unreachable) - (else i32.const 4096) - ) - ) - (func (export "handle") (param $ptr i32) (param $len i32) - (if (i32.ge_u (local.get $len) (i32.const 33)) - (then - (call $send - (local.get $ptr) - (i32.add (local.get $ptr) (i32.const 32)) - (i32.sub (local.get $len) (i32.const 32)) - ) - ) - ) - ) - )"#; - let engine = SharedEngine::new().unwrap(); - let actor = WasmActorBuilder::new(engine, wat::parse_str(wat).unwrap()).build().unwrap(); - let rt = Runtime::new(RuntimeConfig::default()); - let inbox = rt.new_inbox::().unwrap(); - let addr = rt.spawn(actor).unwrap(); - - // First message — alloc traps, dropped - rt.send_to(addr, framed_msg(inbox.addr(), b"first")).unwrap(); - rt.tick(); - assert!(inbox.try_recv().is_none(), "first msg dropped due to alloc trap"); - - // Second message — alloc succeeds, echoed - rt.send_to(addr, framed_msg(inbox.addr(), b"second")).unwrap(); - rt.tick(); - assert_eq!(inbox.try_recv().unwrap().0, b"second"); -} - -#[test] -fn alloc_alternates_and_exhaustion() { - // Part A: alternating alloc (even calls succeed, odd calls return 0) - let wat = r#"(module - (import "swactor" "send" (func $send (param i32 i32 i32))) - (memory (export "memory") 1) - (global $calls (mut i32) (i32.const 0)) - (func (export "alloc") (param $len i32) (result i32) - (global.set $calls (i32.add (global.get $calls) (i32.const 1))) - (if (result i32) (i32.rem_u (global.get $calls) (i32.const 2)) - (then i32.const 4096) - (else i32.const 0) - ) - ) - (func (export "handle") (param $ptr i32) (param $len i32) - (if (i32.ge_u (local.get $len) (i32.const 33)) - (then - (call $send - (local.get $ptr) - (i32.add (local.get $ptr) (i32.const 32)) - (i32.sub (local.get $len) (i32.const 32)) - ) - ) - ) - ) - )"#; - let engine = SharedEngine::new().unwrap(); - let actor = WasmActorBuilder::new(engine.clone(), wat::parse_str(wat).unwrap()).build().unwrap(); - let rt = Runtime::new(RuntimeConfig::default()); - let inbox = rt.new_inbox::().unwrap(); - let addr = rt.spawn(actor).unwrap(); - - let mut echoed = 0; - for i in 0u8..6 { - rt.send_to(addr, framed_msg(inbox.addr(), &[i])).unwrap(); - rt.tick(); - if inbox.try_recv().is_some() { echoed += 1; } - } - assert_eq!(echoed, 3, "should echo on odd-numbered alloc calls only"); - - // Part B: echo guest under sustained load — bump allocator exhaustion - let echo = WasmActorBuilder::new(engine, guest_wasm("echo")).build().unwrap(); - let rt2 = Runtime::new(RuntimeConfig::default()); - let inbox2 = rt2.new_inbox::().unwrap(); - let addr2 = rt2.spawn(echo).unwrap(); - - let mut total_echoed = 0; - for _ in 0..2000 { - rt2.send_to(addr2, framed_msg(inbox2.addr(), b"ping")).unwrap(); - rt2.tick(); - if inbox2.try_recv().is_some() { total_echoed += 1; } - } - // Some succeed (before OOM), some fail (after OOM). Actor survives throughout. - assert!(total_echoed > 0, "at least some messages should echo"); - assert!(total_echoed < 2000, "bump allocator should eventually exhaust"); -} - -// ═══════════════════════════════════════════════════════════════════════════════ -// Group 4: Handle trap & outbox semantics -// ═══════════════════════════════════════════════════════════════════════════════ - -#[test] -fn handle_trap_actor_survives() { - let engine = SharedEngine::new().unwrap(); - - // unreachable trap - let unreachable_wat = wat::parse_str(r#"(module - (import "swactor" "send" (func $send (param i32 i32 i32))) - (memory (export "memory") 1) - (func (export "alloc") (param i32) (result i32) i32.const 256) - (func (export "handle") (param i32 i32) unreachable) - )"#).unwrap(); - let actor = WasmActorBuilder::new(engine.clone(), unreachable_wat).build().unwrap(); - let rt = Runtime::new(RuntimeConfig::default()); - let addr = rt.spawn(actor).unwrap(); - - // Send 3 messages — all trap, all dropped - for _ in 0..3 { - rt.send_to(addr, ByteMessage(vec![1])).unwrap(); - rt.tick(); - } - - // Division by zero trap - let divzero_wat = wat::parse_str(r#"(module - (import "swactor" "send" (func $send (param i32 i32 i32))) - (memory (export "memory") 1) - (func (export "alloc") (param i32) (result i32) i32.const 256) - (func (export "handle") (param $ptr i32) (param $len i32) - (drop (i32.div_u (local.get $len) (i32.const 0))) - ) - )"#).unwrap(); - let actor2 = WasmActorBuilder::new(engine, divzero_wat).build().unwrap(); - let addr2 = rt.spawn(actor2).unwrap(); - rt.send_to(addr2, ByteMessage(vec![1])).unwrap(); - rt.tick(); // no panic -} - -#[test] -fn outbox_cleared_on_trap() { - // Guest sends once (valid), then traps. Outbox should be cleared. - let wat = r#"(module - (import "swactor" "send" (func $send (param i32 i32 i32))) - (memory (export "memory") 1) - (func (export "alloc") (param i32) (result i32) i32.const 256) - (func (export "handle") (param $ptr i32) (param $len i32) - ;; Send a valid message - (call $send (local.get $ptr) (local.get $ptr) (i32.const 1)) - ;; Then trap - unreachable - ) - )"#; - let engine = SharedEngine::new().unwrap(); - let actor = WasmActorBuilder::new(engine, wat::parse_str(wat).unwrap()).build().unwrap(); - let rt = Runtime::new(RuntimeConfig::default()); - let inbox = rt.new_inbox::().unwrap(); - let addr = rt.spawn(actor).unwrap(); - - rt.send_to(addr, framed_msg(inbox.addr(), b"data")).unwrap(); - rt.tick(); - assert!(inbox.try_recv().is_none(), "outbox cleared on trap — no messages delivered"); - - // Actor survives - rt.send_to(addr, ByteMessage(vec![1])).unwrap(); - rt.tick(); -} - -proptest! { - #[test] - fn prop_any_send_args_never_crash_host( - dest_ptr in any::(), - payload_ptr in any::(), - payload_len in any::(), - ) { - let wasm = send_args_wat(dest_ptr, payload_ptr, payload_len); - let engine = SharedEngine::new().unwrap(); - let actor = WasmActorBuilder::new(engine, wasm).build().unwrap(); - let rt = Runtime::new(RuntimeConfig::default()); - let addr = rt.spawn(actor).unwrap(); - - rt.send_to(addr, ByteMessage(vec![0u8; 64])).unwrap(); - rt.tick(); // never panic - } -} - -#[test] -fn send_boundary_conditions() { - let engine = SharedEngine::new().unwrap(); - let rt = Runtime::new(RuntimeConfig::default()); - let inbox = rt.new_inbox::().unwrap(); - - // Zero-length payload sends empty ByteMessage - let zero_len = wat::parse_str(r#"(module - (import "swactor" "send" (func $send (param i32 i32 i32))) - (memory (export "memory") 1) - (func (export "alloc") (param i32) (result i32) i32.const 256) - (func (export "handle") (param $ptr i32) (param $len i32) - (call $send (local.get $ptr) (i32.const 0) (i32.const 0)) - ) - )"#).unwrap(); - let actor = WasmActorBuilder::new(engine.clone(), zero_len).build().unwrap(); - let addr = rt.spawn(actor).unwrap(); - rt.send_to(addr, framed_msg(inbox.addr(), b"x")).unwrap(); - rt.tick(); - let msg = inbox.try_recv().expect("zero-len payload should deliver"); - assert!(msg.0.is_empty()); - - // Dest at exact memory boundary: ptr=65504, needs 32 bytes → end=65536 = memory size. Works. - let exact_end = send_args_wat(65504, 0, 1); - let actor2 = WasmActorBuilder::new(engine.clone(), exact_end).build().unwrap(); - let addr2 = rt.spawn(actor2).unwrap(); - rt.send_to(addr2, ByteMessage(vec![0u8; 64])).unwrap(); - rt.tick(); // should not trap (exact fit) - - // Dest one past boundary: ptr=65505 → end=65537 > 65536. Traps. - let one_past = send_args_wat(65505, 0, 1); - let actor3 = WasmActorBuilder::new(engine.clone(), one_past).build().unwrap(); - let addr3 = rt.spawn(actor3).unwrap(); - rt.send_to(addr3, ByteMessage(vec![0u8; 64])).unwrap(); - rt.tick(); // traps but actor survives - - // Send to garbage address — silently dropped (no matching inbox) - let garbage = wat::parse_str(r#"(module - (import "swactor" "send" (func $send (param i32 i32 i32))) - (memory (export "memory") 1) - (data (i32.const 500) "\ff\ff\ff\ff\ff\ff\ff\ff\ff\ff\ff\ff\ff\ff\ff\ff\ff\ff\ff\ff\ff\ff\ff\ff\ff\ff\ff\ff\ff\ff\ff\ff") - (func (export "alloc") (param i32) (result i32) i32.const 256) - (func (export "handle") (param $ptr i32) (param $len i32) - (call $send (i32.const 500) (i32.const 0) (i32.const 1)) - ) - )"#).unwrap(); - let actor4 = WasmActorBuilder::new(engine, garbage).build().unwrap(); - let addr4 = rt.spawn(actor4).unwrap(); - rt.send_to(addr4, ByteMessage(vec![1])).unwrap(); - rt.tick(); - assert!(inbox.try_recv().is_none(), "garbage addr → no delivery to our inbox"); -} - -// ═══════════════════════════════════════════════════════════════════════════════ -// Group 5: Lifecycle & integration -// ═══════════════════════════════════════════════════════════════════════════════ - -#[test] -fn full_lifecycle_story() { - let engine = SharedEngine::new().unwrap(); - let rt = Runtime::new(RuntimeConfig::default()); - let inbox = rt.new_inbox::().unwrap(); - - // Spawn echo, use it - let echo = WasmActorBuilder::new(engine.clone(), guest_wasm("echo")).build().unwrap(); - let addr1 = rt.spawn(echo).unwrap(); - rt.send_to(addr1, framed_msg(inbox.addr(), b"alive")).unwrap(); - rt.tick(); - assert_eq!(inbox.try_recv().unwrap().0, b"alive"); - - // Stop it - let _ = rt.stop_actor(addr1); - rt.tick(); - rt.tick(); - - // Send to dead actor — silently dropped - let _ = rt.send_to(addr1, framed_msg(inbox.addr(), b"dead")); - rt.tick(); - assert!(inbox.try_recv().is_none()); - - // Respawn — different address, still works - let echo2 = WasmActorBuilder::new(engine.clone(), guest_wasm("echo")).build().unwrap(); - let addr2 = rt.spawn(echo2).unwrap(); - assert_ne!(addr1, addr2, "respawned actor gets different address"); - rt.send_to(addr2, framed_msg(inbox.addr(), b"new")).unwrap(); - rt.tick(); - assert_eq!(inbox.try_recv().unwrap().0, b"new"); - - // Double-stop is fine - let _ = rt.stop_actor(addr2); - let _ = rt.stop_actor(addr2); - rt.tick(); -} - -struct Forwarder { - engine: SharedEngine, - wasm_bytes: Vec, - inbox_addr: ActorAddress, -} - -#[derive(Clone)] -struct ForwardMsg(Vec); - -impl ActorInterface for Forwarder { - type Incoming = ForwardMsg; - type Response = (); - fn handle(&mut self, ctx: &Ctx, msg: ForwardMsg) { - let wasm = WasmActorBuilder::new(self.engine.clone(), self.wasm_bytes.clone()) - .build() - .unwrap(); - let wasm_addr = ctx.spawn(wasm).unwrap(); - let _ = ctx.send(wasm_addr, framed_msg(&self.inbox_addr, &msg.0)); - } -} - -#[test] -fn wasm_native_interop() { - let engine = SharedEngine::new().unwrap(); - let rt = Runtime::new(RuntimeConfig::default()); - let inbox = rt.new_inbox::().unwrap(); - - // Native forwarder spawns a WASM echo actor and forwards a message to it - let forwarder = Forwarder { - engine: engine.clone(), - wasm_bytes: guest_wasm("echo"), - inbox_addr: inbox.addr().clone(), - }; - let fwd_addr = rt.spawn(forwarder).unwrap(); - rt.send_to(fwd_addr, ForwardMsg(b"via-native".to_vec())).unwrap(); - for _ in 0..5 { rt.tick(); } - assert_eq!(inbox.try_recv().unwrap().0, b"via-native"); -} - -#[test] -fn wasm_relay_chain() { - // A→B→C→inbox: each echo actor strips 32 bytes of address and sends payload to that address - let engine = SharedEngine::new().unwrap(); - let rt = Runtime::new(RuntimeConfig::default()); - let inbox = rt.new_inbox::().unwrap(); - - let a = WasmActorBuilder::new(engine.clone(), guest_wasm("echo")).build().unwrap(); - let b = WasmActorBuilder::new(engine.clone(), guest_wasm("echo")).build().unwrap(); - let c = WasmActorBuilder::new(engine, guest_wasm("echo")).build().unwrap(); - let addr_a = rt.spawn(a).unwrap(); - let addr_b = rt.spawn(b).unwrap(); - let addr_c = rt.spawn(c).unwrap(); - - // Nested framed: [addr_b | addr_c | addr_inbox | "end"] - let mut payload = Vec::new(); - payload.extend_from_slice(&addr_b.0); - payload.extend_from_slice(&addr_c.0); - payload.extend_from_slice(&inbox.addr().0); - payload.extend_from_slice(b"end"); - - rt.send_to(addr_a, ByteMessage(payload)).unwrap(); - for _ in 0..3 { rt.tick(); } - - // After 3 hops, inbox should have the final payload "end" - // A sends [addr_c | addr_inbox | "end"] to B - // B sends [addr_inbox | "end"] to C - // C sends "end" to inbox - let msg = inbox.try_recv().expect("relay chain should deliver"); - assert_eq!(msg.0, b"end"); -} - -struct DeathCounter { - count: std::sync::Arc, -} - -#[derive(Clone)] -struct WatchAddr(ActorAddress); - -impl ActorInterface for DeathCounter { - type Incoming = WatchAddr; - type Response = (); - fn handle(&mut self, ctx: &Ctx, msg: WatchAddr) { - ctx.watch(msg.0); - } - fn on_actor_exit(&mut self, _ctx: &Ctx, _exited: swactor::actor::ActorExited) { - self.count.fetch_add(1, std::sync::atomic::Ordering::SeqCst); - } -} - -#[test] -fn watch_notification() { - let engine = SharedEngine::new().unwrap(); - let rt = Runtime::new(RuntimeConfig::default()) - .with_extension(std::sync::Arc::new(StdExtension::new())); - - let count = std::sync::Arc::new(std::sync::atomic::AtomicUsize::new(0)); - let target = WasmActorBuilder::new(engine, guest_wasm("silent")).build().unwrap(); - let target_addr = rt.spawn(target).unwrap(); - let watcher_addr = rt.spawn(DeathCounter { count: count.clone() }).unwrap(); - - rt.send_to(watcher_addr, WatchAddr(target_addr)).unwrap(); - for _ in 0..3 { rt.tick(); } - - let _ = rt.stop_actor(target_addr); - for _ in 0..5 { rt.tick(); } - - assert_eq!(count.load(std::sync::atomic::Ordering::SeqCst), 1); -} - -proptest! { - #[test] - fn prop_lifecycle_fuzz( - guest_idx in 0usize..3, - n_msgs in 0u8..20, - ticks_before in 1usize..5, - ticks_after in 1usize..5, - ) { - let names = ["echo", "double", "silent"]; - let engine = SharedEngine::new().unwrap(); - let actor = WasmActorBuilder::new(engine, guest_wasm(names[guest_idx])).build().unwrap(); - let rt = Runtime::new(RuntimeConfig::default()); - let inbox = rt.new_inbox::().unwrap(); - let addr = rt.spawn(actor).unwrap(); - - for _ in 0..n_msgs { - let _ = rt.send_to(addr, framed_msg(inbox.addr(), b"x")); - } - for _ in 0..ticks_before { rt.tick(); } - let _ = rt.stop_actor(addr); - for _ in 0..ticks_after { rt.tick(); } - while inbox.try_recv().is_some() {} - } -} - -proptest! { - #[test] - fn prop_mixed_guest_response_counts( - echo_n in 0usize..5, - double_n in 0usize..5, - silent_n in 0usize..5, - ) { - let engine = SharedEngine::new().unwrap(); - let rt = Runtime::new(RuntimeConfig::default()); - let inbox = rt.new_inbox::().unwrap(); - - for _ in 0..echo_n { - let a = WasmActorBuilder::new(engine.clone(), guest_wasm("echo")).build().unwrap(); - let addr = rt.spawn(a).unwrap(); - rt.send_to(addr, framed_msg(inbox.addr(), b"e")).unwrap(); - } - for _ in 0..double_n { - let a = WasmActorBuilder::new(engine.clone(), guest_wasm("double")).build().unwrap(); - let addr = rt.spawn(a).unwrap(); - rt.send_to(addr, framed_msg(inbox.addr(), b"d")).unwrap(); - } - for _ in 0..silent_n { - let a = WasmActorBuilder::new(engine.clone(), guest_wasm("silent")).build().unwrap(); - let addr = rt.spawn(a).unwrap(); - rt.send_to(addr, ByteMessage(b"s".to_vec())).unwrap(); - } - - rt.tick(); - let total: usize = std::iter::from_fn(|| inbox.try_recv()).count(); - prop_assert_eq!(total, echo_n + 2 * double_n); - } -} - -#[test] -fn non_byte_message_silently_ignored() { - // Send a String (wrong type) to a WASM actor — should be silently dropped - let engine = SharedEngine::new().unwrap(); - let actor = WasmActorBuilder::new(engine, guest_wasm("echo")).build().unwrap(); - let rt = Runtime::new(RuntimeConfig::default()); - let inbox = rt.new_inbox::().unwrap(); - let addr = rt.spawn(actor).unwrap(); - - // send_to with wrong type — shouldn't panic - let _ = rt.send_to(addr, "wrong type".to_string()); - rt.tick(); - assert!(inbox.try_recv().is_none()); - - // Actor still alive - rt.send_to(addr, framed_msg(inbox.addr(), b"ok")).unwrap(); - rt.tick(); - assert_eq!(inbox.try_recv().unwrap().0, b"ok"); -} - -// ═══════════════════════════════════════════════════════════════════════════════ -// Group 6: Engine & runtime config -// ═══════════════════════════════════════════════════════════════════════════════ - -#[test] -fn shared_engine_traits_and_clone() { - fn assert_send_sync() {} - fn assert_send() {} - assert_send_sync::(); - assert_send::(); - - let engine = SharedEngine::new().unwrap(); - let _ = format!("{:?}", engine); // Debug doesn't panic - - // Clone produces working independent engine - let clone = engine.clone(); - let a1 = WasmActorBuilder::new(engine, guest_wasm("echo")).build().unwrap(); - let a2 = WasmActorBuilder::new(clone, guest_wasm("echo")).build().unwrap(); - let rt = Runtime::new(RuntimeConfig::default()); - let inbox = rt.new_inbox::().unwrap(); - let addr1 = rt.spawn(a1).unwrap(); - let addr2 = rt.spawn(a2).unwrap(); - rt.send_to(addr1, framed_msg(inbox.addr(), b"c1")).unwrap(); - rt.send_to(addr2, framed_msg(inbox.addr(), b"c2")).unwrap(); - rt.tick(); - let mut msgs: Vec> = std::iter::from_fn(|| inbox.try_recv().map(|m| m.0)).collect(); - msgs.sort(); - assert_eq!(msgs, vec![b"c1".to_vec(), b"c2".to_vec()]); - - // ByteMessage traits - let bm = ByteMessage(vec![1, 2, 3]); - let bm2 = bm.clone(); - assert_eq!(bm, bm2); - let _ = format!("{:?}", bm); -} - -#[test] -fn multi_thread_runtime() { - let engine = SharedEngine::new().unwrap(); - let config = RuntimeConfig { num_threads: 4, ..RuntimeConfig::default() }; - let rt = Runtime::new(config); - let inbox = rt.new_inbox::().unwrap(); - - let mut addrs = Vec::new(); - for _ in 0..10 { - let actor = WasmActorBuilder::new(engine.clone(), guest_wasm("echo")).build().unwrap(); - addrs.push(rt.spawn(actor).unwrap()); - } - for (i, addr) in addrs.iter().enumerate() { - rt.send_to(*addr, framed_msg(inbox.addr(), &[i as u8])).unwrap(); - } - - let handle = rt.run().unwrap(); - let mut received = Vec::new(); - for _ in 0..40 { - std::thread::sleep(std::time::Duration::from_millis(25)); - while let Some(msg) = inbox.try_recv() { - received.push(msg.0[0]); - } - if received.len() == 10 { break; } - } - handle.shutdown(); - received.sort(); - assert_eq!(received, (0..10u8).collect::>()); -} - -#[test] -fn budget_and_mailbox() { - let engine = SharedEngine::new().unwrap(); - - // Budget=1: one message processed per tick - let mut cfg = RuntimeConfig::default(); - cfg.actor_message_budget = 1; - let rt = Runtime::new(cfg); - let inbox = rt.new_inbox::().unwrap(); - let echo = WasmActorBuilder::new(engine.clone(), guest_wasm("echo")).build().unwrap(); - let addr = rt.spawn(echo).unwrap(); - rt.send_to(addr, framed_msg(inbox.addr(), b"a")).unwrap(); - rt.send_to(addr, framed_msg(inbox.addr(), b"b")).unwrap(); - rt.send_to(addr, framed_msg(inbox.addr(), b"c")).unwrap(); - rt.tick(); - let first_tick: usize = std::iter::from_fn(|| inbox.try_recv()).count(); - assert_eq!(first_tick, 1, "budget=1 processes exactly 1 per tick"); - rt.tick(); - rt.tick(); - let rest: usize = std::iter::from_fn(|| inbox.try_recv()).count(); - assert_eq!(rest, 2, "remaining 2 processed over next 2 ticks"); - - // Self-send loop bounded by budget — no explosion - let mut cfg2 = RuntimeConfig::default(); - cfg2.actor_message_budget = 2; - let rt2 = Runtime::new(cfg2); - let echo2 = WasmActorBuilder::new(engine.clone(), guest_wasm("echo")).build().unwrap(); - let self_addr = rt2.spawn(echo2).unwrap(); - rt2.send_to(self_addr, framed_msg(&self_addr, b"loop")).unwrap(); - for _ in 0..5 { rt2.tick(); } // no panic, bounded - - // DropOldest mailbox - use swactor::runtime::MailboxOverflow; - let mut cfg3 = RuntimeConfig::default(); - cfg3.default_mailbox_capacity = 3; - cfg3.mailbox_overflow = MailboxOverflow::DropOldest; - let rt3 = Runtime::new(cfg3); - let inbox3 = rt3.new_inbox::().unwrap(); - let echo3 = WasmActorBuilder::new(engine, guest_wasm("echo")).build().unwrap(); - let addr3 = rt3.spawn(echo3).unwrap(); - for i in 0..10u8 { - rt3.send_to(addr3, framed_msg(inbox3.addr(), &[i])).unwrap(); - } - rt3.tick(); - let responses: Vec<_> = std::iter::from_fn(|| inbox3.try_recv()).collect(); - assert!(responses.len() <= 3, "mailbox capacity limits processing: got {}", responses.len()); -} - -#[test] -fn multiple_runtimes_and_scale() { - let engine = SharedEngine::new().unwrap(); - - // Two runtimes share engine - let rt1 = Runtime::new(RuntimeConfig::default()); - let rt2 = Runtime::new(RuntimeConfig::default()); - let inbox1 = rt1.new_inbox::().unwrap(); - let inbox2 = rt2.new_inbox::().unwrap(); - let a1 = WasmActorBuilder::new(engine.clone(), guest_wasm("echo")).build().unwrap(); - let a2 = WasmActorBuilder::new(engine.clone(), guest_wasm("echo")).build().unwrap(); - let addr1 = rt1.spawn(a1).unwrap(); - let addr2 = rt2.spawn(a2).unwrap(); - rt1.send_to(addr1, framed_msg(inbox1.addr(), b"rt1")).unwrap(); - rt2.send_to(addr2, framed_msg(inbox2.addr(), b"rt2")).unwrap(); - rt1.tick(); - rt2.tick(); - assert_eq!(inbox1.try_recv().unwrap().0, b"rt1"); - assert_eq!(inbox2.try_recv().unwrap().0, b"rt2"); - - // 50 actors from same engine - let rt3 = Runtime::new(RuntimeConfig::default()); - let inbox3 = rt3.new_inbox::().unwrap(); - for i in 0..50u8 { - let a = WasmActorBuilder::new(engine.clone(), guest_wasm("echo")).build().unwrap(); - let addr = rt3.spawn(a).unwrap(); - rt3.send_to(addr, framed_msg(inbox3.addr(), &[i])).unwrap(); - } - rt3.tick(); - let count: usize = std::iter::from_fn(|| inbox3.try_recv()).count(); - assert_eq!(count, 50); - - // Build+drop 100 actors without spawning — no leak - for _ in 0..100 { - let _ = WasmActorBuilder::new(engine.clone(), guest_wasm("echo")).build().unwrap(); - } -} - -#[test] -fn ordering_and_determinism() { - let engine = SharedEngine::new().unwrap(); - - // FIFO ordering - let rt = Runtime::new(RuntimeConfig::default()); - let inbox = rt.new_inbox::().unwrap(); - let echo = WasmActorBuilder::new(engine.clone(), guest_wasm("echo")).build().unwrap(); - let addr = rt.spawn(echo).unwrap(); - for i in 0..20u8 { - rt.send_to(addr, framed_msg(inbox.addr(), &[i])).unwrap(); - } - rt.tick(); - let msgs: Vec = std::iter::from_fn(|| inbox.try_recv().map(|m| m.0[0])).collect(); - assert_eq!(msgs, (0..20u8).collect::>(), "FIFO preserved"); - - // Determinism: same input → same output across runs - let mut results = Vec::new(); - for _ in 0..5 { - let echo = WasmActorBuilder::new(engine.clone(), guest_wasm("echo")).build().unwrap(); - let rt = Runtime::new(RuntimeConfig::default()); - let inbox = rt.new_inbox::().unwrap(); - let addr = rt.spawn(echo).unwrap(); - rt.send_to(addr, framed_msg(inbox.addr(), b"deterministic")).unwrap(); - rt.tick(); - results.push(inbox.try_recv().unwrap().0); - } - assert!(results.windows(2).all(|w| w[0] == w[1]), "deterministic across runs"); -} - -// ═══════════════════════════════════════════════════════════════════════════════ -// Group 7: WASM feature coverage & guest computation -// ═══════════════════════════════════════════════════════════════════════════════ - -#[test] -fn kitchen_sink_wat_module() { - // Single WAT module exercising as many supported WASM features as possible. - // If this builds and doesn't trap, the engine config is correct. - let wat = r#"(module - (import "swactor" "send" (func $send (param i32 i32 i32))) - (memory (export "memory") 2) - - ;; Data segments - (data (i32.const 0) "kitchen-sink") - - ;; Globals: mutable and immutable - (global $counter (mut i32) (i32.const 0)) - (global $MAGIC i32 (i32.const 42)) - - ;; Table for call_indirect - (table 2 funcref) - (elem (i32.const 0) $helper_add $helper_sub) - - ;; Internal helper functions - (func $helper_add (param i32 i32) (result i32) - (i32.add (local.get 0) (local.get 1))) - (func $helper_sub (param i32 i32) (result i32) - (i32.sub (local.get 0) (local.get 1))) - (func $deep_call (param $x i32) (result i32) - (i32.mul (local.get $x) (i32.const 2))) - - (func (export "alloc") (param $len i32) (result i32) i32.const 4096) - (func (export "handle") (param $ptr i32) (param $len i32) - (local $a i32) - (local $b i64) - (local $c f32) - (local $d f64) - (local $i i32) - (local $tmp i32) - - ;; Increment global counter - (global.set $counter (i32.add (global.get $counter) (i32.const 1))) - - ;; Read immutable global - (local.set $a (global.get $MAGIC)) - - ;; i32 arithmetic - (local.set $a (i32.add (local.get $a) (i32.const 10))) - (local.set $a (i32.sub (local.get $a) (i32.const 5))) - (local.set $a (i32.mul (local.get $a) (i32.const 3))) - (local.set $a (i32.div_u (local.get $a) (i32.const 2))) - (local.set $a (i32.rem_u (local.get $a) (i32.const 100))) - - ;; Bitwise operations - (local.set $a (i32.and (local.get $a) (i32.const 0xFF))) - (local.set $a (i32.or (local.get $a) (i32.const 0x10))) - (local.set $a (i32.xor (local.get $a) (i32.const 0x01))) - (local.set $a (i32.shl (local.get $a) (i32.const 2))) - (local.set $a (i32.shr_u (local.get $a) (i32.const 1))) - (local.set $a (i32.shr_s (local.get $a) (i32.const 1))) - (local.set $a (i32.rotl (local.get $a) (i32.const 3))) - (local.set $a (i32.rotr (local.get $a) (i32.const 3))) - - ;; Bit counting - (drop (i32.clz (local.get $a))) - (drop (i32.ctz (local.get $a))) - (drop (i32.popcnt (local.get $a))) - (drop (i32.eqz (local.get $a))) - - ;; Comparisons - (drop (i32.gt_s (local.get $a) (i32.const 0))) - (drop (i32.lt_s (local.get $a) (i32.const 100))) - (drop (i32.le_u (local.get $a) (i32.const 200))) - (drop (i32.ge_s (local.get $a) (i32.const -1))) - - ;; i64 operations - (local.set $b (i64.const 9999999999)) - (local.set $b (i64.add (local.get $b) (i64.const 1))) - (i64.store (i32.const 800) (local.get $b)) - (drop (i64.load (i32.const 800))) - - ;; i64 ↔ i32 conversions - (drop (i32.wrap_i64 (local.get $b))) - (drop (i64.extend_i32_s (local.get $a))) - - ;; f32 operations - (local.set $c (f32.const 3.14)) - (local.set $c (f32.add (local.get $c) (f32.const 1.0))) - (local.set $c (f32.mul (local.get $c) (f32.const 2.0))) - (f32.store (i32.const 900) (local.get $c)) - (drop (f32.load (i32.const 900))) - - ;; f64 operations - (local.set $d (f64.promote_f32 (local.get $c))) - (local.set $d (f64.mul (local.get $d) (f64.const 0.5))) - (f64.store (i32.const 920) (local.get $d)) - - ;; Sign extension - (drop (i32.extend8_s (i32.const 0x80))) - (drop (i32.extend16_s (i32.const 0x8000))) - - ;; Memory store/load variants - (i32.store8 (i32.const 700) (i32.const 0xAB)) - (i32.store16 (i32.const 702) (i32.const 0xCDEF)) - (drop (i32.load8_u (i32.const 700))) - (drop (i32.load16_u (i32.const 702))) - - ;; local.tee - (local.set $tmp (local.tee $a (i32.const 77))) - - ;; Nested function calls - (drop (call $deep_call (i32.const 5))) - (drop (call $helper_add (i32.const 10) (i32.const 20))) - - ;; call_indirect via table - (drop (call_indirect (type 0) (i32.const 7) (i32.const 3) (i32.const 0))) - (drop (call_indirect (type 0) (i32.const 7) (i32.const 3) (i32.const 1))) - - ;; Control flow: if/else - (if (i32.gt_s (local.get $len) (i32.const 0)) - (then nop) - (else nop) - ) - - ;; select - (drop (select (i32.const 10) (i32.const 20) (i32.const 1))) - - ;; block + br_if - (block $skip - (br_if $skip (i32.eqz (local.get $len))) - nop - ) - - ;; loop with counter - (local.set $i (i32.const 0)) - (block $exit - (loop $loop - (br_if $exit (i32.ge_u (local.get $i) (i32.const 5))) - (local.set $i (i32.add (local.get $i) (i32.const 1))) - (br $loop) - ) - ) - - ;; br_table dispatch - (block $b0 (block $b1 (block $b2 - (br_table $b0 $b1 $b2 (i32.const 1)) - ) nop) nop) ;; falls through - - ;; Bulk memory: fill and copy - (memory.fill (i32.const 600) (i32.const 0xAA) (i32.const 32)) - (memory.copy (i32.const 650) (i32.const 600) (i32.const 32)) - - ;; memory.size and memory.grow - (drop (memory.size)) - (drop (memory.grow (i32.const 1))) - - ;; nop - nop - ) - - ;; Type for call_indirect - (type (func (param i32 i32) (result i32))) - )"#; - let engine = SharedEngine::new().unwrap(); - let actor = WasmActorBuilder::new(engine, wat::parse_str(wat).unwrap()).build().unwrap(); - let rt = Runtime::new(RuntimeConfig::default()); - let addr = rt.spawn(actor).unwrap(); - - // Send message, tick — no trap - rt.send_to(addr, ByteMessage(vec![1, 2, 3, 4])).unwrap(); - rt.tick(); - - // Send again — global counter increments, still no trap - rt.send_to(addr, ByteMessage(vec![5, 6, 7, 8])).unwrap(); - rt.tick(); -} - -#[test] -fn guest_mutable_state_persists() { - // Global counter increments on each handle call, sends count back - let wat = r#"(module - (import "swactor" "send" (func $send (param i32 i32 i32))) - (memory (export "memory") 1) - (global $count (mut i32) (i32.const 0)) - (func (export "alloc") (param $len i32) (result i32) i32.const 4096) - (func (export "handle") (param $ptr i32) (param $len i32) - (if (i32.lt_u (local.get $len) (i32.const 32)) (then return)) - (global.set $count (i32.add (global.get $count) (i32.const 1))) - (i32.store (i32.const 200) (global.get $count)) - (call $send (local.get $ptr) (i32.const 200) (i32.const 4)) - ) - )"#; - let engine = SharedEngine::new().unwrap(); - let actor = WasmActorBuilder::new(engine, wat::parse_str(wat).unwrap()).build().unwrap(); - let rt = Runtime::new(RuntimeConfig::default()); - let inbox = rt.new_inbox::().unwrap(); - let addr = rt.spawn(actor).unwrap(); - - for expected in 1..=5u32 { - rt.send_to(addr, framed_msg(inbox.addr(), b"x")).unwrap(); - rt.tick(); - let resp = inbox.try_recv().expect("should get count back"); - let count = u32::from_le_bytes([resp.0[0], resp.0[1], resp.0[2], resp.0[3]]); - assert_eq!(count, expected, "counter should persist across messages"); - } -} - -#[test] -fn guest_transforms_payload() { - let engine = SharedEngine::new().unwrap(); - let rt = Runtime::new(RuntimeConfig::default()); - let inbox = rt.new_inbox::().unwrap(); - - // XOR each byte with 0xFF (bitwise NOT) - let xor_wat = r#"(module - (import "swactor" "send" (func $send (param i32 i32 i32))) - (memory (export "memory") 1) - (func (export "alloc") (param $len i32) (result i32) i32.const 4096) - (func (export "handle") (param $ptr i32) (param $len i32) - (local $i i32) - (if (i32.lt_u (local.get $len) (i32.const 33)) (then return)) - (local.set $i (i32.const 32)) - (block $exit (loop $loop - (br_if $exit (i32.ge_u (local.get $i) (local.get $len))) - (i32.store8 - (i32.add (local.get $ptr) (local.get $i)) - (i32.xor (i32.load8_u (i32.add (local.get $ptr) (local.get $i))) (i32.const 0xFF))) - (local.set $i (i32.add (local.get $i) (i32.const 1))) - (br $loop) - )) - (call $send (local.get $ptr) - (i32.add (local.get $ptr) (i32.const 32)) - (i32.sub (local.get $len) (i32.const 32))) - ) - )"#; - let xor = WasmActorBuilder::new(engine.clone(), wat::parse_str(xor_wat).unwrap()).build().unwrap(); - let xaddr = rt.spawn(xor).unwrap(); - rt.send_to(xaddr, framed_msg(inbox.addr(), &[0x00, 0xFF, 0xAA])).unwrap(); - rt.tick(); - assert_eq!(inbox.try_recv().unwrap().0, vec![0xFF, 0x00, 0x55]); - - // Reverse payload - let rev_wat = r#"(module - (import "swactor" "send" (func $send (param i32 i32 i32))) - (memory (export "memory") 1) - (func (export "alloc") (param $len i32) (result i32) i32.const 4096) - (func (export "handle") (param $ptr i32) (param $len i32) - (local $i i32) (local $plen i32) (local $pstart i32) - (if (i32.lt_u (local.get $len) (i32.const 33)) (then return)) - (local.set $pstart (i32.add (local.get $ptr) (i32.const 32))) - (local.set $plen (i32.sub (local.get $len) (i32.const 32))) - (local.set $i (i32.const 0)) - (block $exit (loop $loop - (br_if $exit (i32.ge_u (local.get $i) (local.get $plen))) - (i32.store8 - (i32.add (i32.const 900) (local.get $i)) - (i32.load8_u (i32.add (local.get $pstart) - (i32.sub (i32.sub (local.get $plen) (i32.const 1)) (local.get $i))))) - (local.set $i (i32.add (local.get $i) (i32.const 1))) - (br $loop) - )) - (call $send (local.get $ptr) (i32.const 900) (local.get $plen)) - ) - )"#; - let rev = WasmActorBuilder::new(engine, wat::parse_str(rev_wat).unwrap()).build().unwrap(); - let raddr = rt.spawn(rev).unwrap(); - rt.send_to(raddr, framed_msg(inbox.addr(), b"abcde")).unwrap(); - rt.tick(); - assert_eq!(inbox.try_recv().unwrap().0, b"edcba"); -} - -#[test] -fn guest_multi_send_patterns() { - let engine = SharedEngine::new().unwrap(); - let rt = Runtime::new(RuntimeConfig::default()); - let inbox = rt.new_inbox::().unwrap(); - - // Guest sends to 2 different destinations in one handle call - let two_dest_wat = r#"(module - (import "swactor" "send" (func $send (param i32 i32 i32))) - (memory (export "memory") 1) - (func (export "alloc") (param $len i32) (result i32) i32.const 1024) - (func (export "handle") (param $ptr i32) (param $len i32) - ;; Layout: [addr1:32][addr2:32][payload:rest] - (if (i32.ge_u (local.get $len) (i32.const 65)) - (then - (call $send (local.get $ptr) - (i32.add (local.get $ptr) (i32.const 64)) - (i32.sub (local.get $len) (i32.const 64))) - (call $send (i32.add (local.get $ptr) (i32.const 32)) - (i32.add (local.get $ptr) (i32.const 64)) - (i32.sub (local.get $len) (i32.const 64))) - ) - ) - ) - )"#; - let actor = WasmActorBuilder::new(engine.clone(), wat::parse_str(two_dest_wat).unwrap()) - .build().unwrap(); - let inbox2 = rt.new_inbox::().unwrap(); - let addr = rt.spawn(actor).unwrap(); - - let mut msg = Vec::new(); - msg.extend_from_slice(&inbox.addr().0); // addr1 - msg.extend_from_slice(&inbox2.addr().0); // addr2 - msg.extend_from_slice(b"shared"); // payload - rt.send_to(addr, ByteMessage(msg)).unwrap(); - rt.tick(); - assert_eq!(inbox.try_recv().unwrap().0, b"shared"); - assert_eq!(inbox2.try_recv().unwrap().0, b"shared"); - - // Guest sends 100 messages in one handle call - let many_sends_wat = r#"(module - (import "swactor" "send" (func $send (param i32 i32 i32))) - (memory (export "memory") 1) - (func (export "alloc") (param $len i32) (result i32) i32.const 1024) - (func (export "handle") (param $ptr i32) (param $len i32) - (local $i i32) - (if (i32.lt_u (local.get $len) (i32.const 32)) (then return)) - (local.set $i (i32.const 0)) - (block $exit (loop $loop - (br_if $exit (i32.ge_u (local.get $i) (i32.const 100))) - (i32.store8 (i32.const 900) (local.get $i)) - (call $send (local.get $ptr) (i32.const 900) (i32.const 1)) - (local.set $i (i32.add (local.get $i) (i32.const 1))) - (br $loop) - )) - ) - )"#; - let actor2 = WasmActorBuilder::new(engine, wat::parse_str(many_sends_wat).unwrap()) - .build().unwrap(); - let addr2 = rt.spawn(actor2).unwrap(); - rt.send_to(addr2, framed_msg(inbox.addr(), b"go")).unwrap(); - rt.tick(); - let count: usize = std::iter::from_fn(|| inbox.try_recv()).count(); - assert_eq!(count, 100, "all 100 sends should be delivered"); -} - -// ═══════════════════════════════════════════════════════════════════════════════ -// Group 8: Stress -// ═══════════════════════════════════════════════════════════════════════════════ - -#[test] -fn echo_sustained_load() { - let engine = SharedEngine::new().unwrap(); - let rt = Runtime::new(RuntimeConfig::default()); - let inbox = rt.new_inbox::().unwrap(); - - // 500 messages to echo actor - let echo = WasmActorBuilder::new(engine.clone(), guest_wasm("echo")).build().unwrap(); - let addr = rt.spawn(echo).unwrap(); - for i in 0..500u16 { - rt.send_to(addr, framed_msg(inbox.addr(), &i.to_le_bytes())).unwrap(); - } - for _ in 0..20 { rt.tick(); } - let count: usize = std::iter::from_fn(|| inbox.try_recv()).count(); - assert_eq!(count, 500, "all 500 messages echoed"); - - // Spawn and stop 100 actors — no panic, no leak - for _ in 0..100 { - let a = WasmActorBuilder::new(engine.clone(), guest_wasm("echo")).build().unwrap(); - let a_addr = rt.spawn(a).unwrap(); - rt.send_to(a_addr, framed_msg(inbox.addr(), b"x")).unwrap(); - rt.tick(); - let _ = rt.stop_actor(a_addr); - rt.tick(); - } - // Drain inbox - while inbox.try_recv().is_some() {} -} diff --git a/crates/python/Cargo.toml b/crates/bindings/python/Cargo.toml similarity index 85% rename from crates/python/Cargo.toml rename to crates/bindings/python/Cargo.toml index 00cd26e..3488195 100644 --- a/crates/python/Cargo.toml +++ b/crates/bindings/python/Cargo.toml @@ -8,5 +8,5 @@ name = "swactor" crate-type = ["cdylib"] [dependencies] -swactor = { path = "../.." } +swactor = { path = "../../.." } pyo3 = { version = "0.23", features = ["extension-module"] } diff --git a/crates/python/pyproject.toml b/crates/bindings/python/pyproject.toml similarity index 100% rename from crates/python/pyproject.toml rename to crates/bindings/python/pyproject.toml diff --git a/crates/python/src/lib.rs b/crates/bindings/python/src/lib.rs similarity index 100% rename from crates/python/src/lib.rs rename to crates/bindings/python/src/lib.rs diff --git a/crates/python/uv.lock b/crates/bindings/python/uv.lock similarity index 100% rename from crates/python/uv.lock rename to crates/bindings/python/uv.lock diff --git a/crates/wasm/crypto/Cargo.toml b/crates/bindings/wasm-crypto/Cargo.toml similarity index 100% rename from crates/wasm/crypto/Cargo.toml rename to crates/bindings/wasm-crypto/Cargo.toml diff --git a/crates/wasm/crypto/src/lib.rs b/crates/bindings/wasm-crypto/src/lib.rs similarity index 100% rename from crates/wasm/crypto/src/lib.rs rename to crates/bindings/wasm-crypto/src/lib.rs diff --git a/crates/wasm/runtime/Cargo.toml b/crates/bindings/wasm-runtime/Cargo.toml similarity index 66% rename from crates/wasm/runtime/Cargo.toml rename to crates/bindings/wasm-runtime/Cargo.toml index 156c268..a227dbc 100644 --- a/crates/wasm/runtime/Cargo.toml +++ b/crates/bindings/wasm-runtime/Cargo.toml @@ -7,6 +7,5 @@ edition = "2024" crate-type = ["cdylib"] [dependencies] -swactor = { path = "../../..", default-features = false, features = ["wasm"] } -swactor-std = { path = "../../std", default-features = false, features = ["wasm"] } +swactor = { path = "../../..", default-features = false, features = ["wasm", "std"] } wasm-bindgen = "0.2" diff --git a/crates/wasm/runtime/demo.html b/crates/bindings/wasm-runtime/demo.html similarity index 100% rename from crates/wasm/runtime/demo.html rename to crates/bindings/wasm-runtime/demo.html diff --git a/crates/wasm/runtime/src/lib.rs b/crates/bindings/wasm-runtime/src/lib.rs similarity index 99% rename from crates/wasm/runtime/src/lib.rs rename to crates/bindings/wasm-runtime/src/lib.rs index 1268b49..fafb01e 100644 --- a/crates/wasm/runtime/src/lib.rs +++ b/crates/bindings/wasm-runtime/src/lib.rs @@ -4,7 +4,7 @@ use wasm_bindgen::prelude::*; use swactor::actor::{ActorAddress, ActorExited, ActorInterface}; use swactor::runtime::{Ctx, Inbox, Runtime, RuntimeConfig}; -use swactor_std::{CtxGroups, CtxWatching, RuntimeNaming, RuntimeGroups, StdExtension}; +use swactor::std::{CtxGroups, CtxWatching, RuntimeNaming, RuntimeGroups, StdExtension}; // ─── Core JS-facing types ─────────────────────────────────────────────────── diff --git a/crates/wasm/runtime/test.mjs b/crates/bindings/wasm-runtime/test.mjs similarity index 100% rename from crates/wasm/runtime/test.mjs rename to crates/bindings/wasm-runtime/test.mjs diff --git a/crates/ci/src/yaml.rs b/crates/ci/src/yaml.rs index 2bf5df4..ca33fe7 100644 --- a/crates/ci/src/yaml.rs +++ b/crates/ci/src/yaml.rs @@ -270,7 +270,7 @@ pipelines: jobs: build: run: cargo build --release - artifacts: ["target/release/swactor-node"] + artifacts: ["target/release/swactor"] "#; let ci = parse_ci_yaml(yaml).unwrap(); assert_eq!(ci.pipelines.len(), 3); diff --git a/crates/dashboard/AGENTS.md b/crates/dashboard/AGENTS.md index 51ffad5..89542fe 100644 --- a/crates/dashboard/AGENTS.md +++ b/crates/dashboard/AGENTS.md @@ -9,13 +9,13 @@ to stdout (one object per line). Human-readable output goes to stderr. ### Launching ```bash -cargo run -p runtime-dashboard --example investigate_demo +cargo run -p dashboard --example investigate_demo ``` Or programmatically against any running runtime: ```rust -use runtime_dashboard::investigate::run_investigate; +use dashboard::investigate::run_investigate; run_investigate(runtime_arc, collector_arc)?; // blocks on stdin ``` diff --git a/crates/dashboard/Cargo.toml b/crates/dashboard/Cargo.toml index 5d7fd03..4b19e2a 100644 --- a/crates/dashboard/Cargo.toml +++ b/crates/dashboard/Cargo.toml @@ -5,7 +5,6 @@ edition = "2024" [dependencies] swactor = { path = "../..", features = ["serde", "tracing"] } -swactor-std = { path = "../std", default-features = false } tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["registry"] } serde = { version = "1", features = ["derive"] } @@ -35,6 +34,6 @@ path = "src/bin/tui.rs" required-features = ["tui"] [[bin]] -name = "swactor-node" +name = "swactor-node-legacy" path = "src/bin/swactor-node.rs" required-features = ["node"] diff --git a/crates/dashboard/README.md b/crates/dashboard/README.md index e9a70ab..3290178 100644 --- a/crates/dashboard/README.md +++ b/crates/dashboard/README.md @@ -1,4 +1,4 @@ -# runtime-dashboard +# dashboard Visual dashboard for the swactor runtime. Provides a live HTTP dashboard, a terminal UI (TUI), trace recording/replay, and an HTTP API for programmatic @@ -16,7 +16,7 @@ runtime investigation. Start the dashboard demo and open it in a browser: ```bash -cargo run -p runtime-dashboard --example dashboard_demo +cargo run -p dashboard --example dashboard_demo ``` Pages: @@ -33,9 +33,9 @@ membership and actor registrations in the directory/cache. A standalone binary that connects to any running dashboard over SSE: ```bash -cargo run -p runtime-dashboard --features tui --bin swactor-tui +cargo run -p dashboard --features tui --bin swactor-tui # or point at a specific endpoint -cargo run -p runtime-dashboard --features tui --bin swactor-tui -- http://localhost:9090 +cargo run -p dashboard --features tui --bin swactor-tui -- http://localhost:9090 ``` Views (cycle with Tab): @@ -70,7 +70,7 @@ All examples are run from the workspace root. **HTTP dashboard** — live workload with distribution cluster, Ctrl+C to stop: ```bash -cargo run -p runtime-dashboard --example dashboard_demo +cargo run -p dashboard --example dashboard_demo # http://localhost:9090 — runtime overview # http://localhost:9090/distribution — cluster view ``` @@ -78,14 +78,14 @@ cargo run -p runtime-dashboard --example dashboard_demo **Benchmarks** — four automated scenarios (~20 s total): ```bash -cargo run -p runtime-dashboard --example bench_dashboard +cargo run -p dashboard --example bench_dashboard # open http://localhost:9090 ``` **Record & replay** — records ~10 s of activity, then serves a replay: ```bash -cargo run -p runtime-dashboard --example record_and_replay_demo +cargo run -p dashboard --example record_and_replay_demo # live dashboard at http://localhost:9090 during recording # replay dashboard at http://localhost:9091 after recording finishes # Ctrl+C to stop diff --git a/crates/dashboard/src/bin/swactor-node.rs b/crates/dashboard/src/bin/swactor-node.rs index e06df89..5ed38ae 100644 --- a/crates/dashboard/src/bin/swactor-node.rs +++ b/crates/dashboard/src/bin/swactor-node.rs @@ -21,7 +21,7 @@ use dashboard::{start_dashboard, DashboardConfig}; // ── CLI ────────────────────────────────────────────────────────────────── #[derive(Parser)] -#[command(name = "swactor-node", about = "Swactor distributed node")] +#[command(name = "swactor-node-legacy", about = "Swactor distributed node (legacy)")] struct Args { /// Transport to use: tcp or iroh #[arg(long, default_value = "tcp")] diff --git a/crates/dashboard/src/collector.rs b/crates/dashboard/src/collector.rs index ecd45b7..d47750c 100644 --- a/crates/dashboard/src/collector.rs +++ b/crates/dashboard/src/collector.rs @@ -55,7 +55,7 @@ pub fn enrich_names(stats: &mut RuntimeStats, runtime: &Runtime) { Some(ext) => ext, None => return, }; - let std_ext = match ext.as_any().downcast_ref::() { + let std_ext = match ext.as_any().downcast_ref::() { Some(ext) => ext, None => return, }; diff --git a/crates/dashboard/src/command/builtins.rs b/crates/dashboard/src/command/builtins.rs index 30f09c5..586ffbf 100644 --- a/crates/dashboard/src/command/builtins.rs +++ b/crates/dashboard/src/command/builtins.rs @@ -1,6 +1,6 @@ //! Built-in command handlers for runtime inspection and management. //! -//! Extracted from `crates/runtime-dashboard/src/investigate.rs`. +//! Extracted from `crates/dashboard/src/investigate.rs`. use std::collections::HashMap; use std::time::{Duration, Instant}; diff --git a/crates/dashboard/src/datastore_collector.rs b/crates/dashboard/src/datastore_collector.rs index b2e38b3..7d55890 100644 --- a/crates/dashboard/src/datastore_collector.rs +++ b/crates/dashboard/src/datastore_collector.rs @@ -1,6 +1,6 @@ //! Datastore stats provider for the runtime dashboard. //! -//! The trait returns a pre-serialized JSON string so that `runtime-dashboard` +//! The trait returns a pre-serialized JSON string so that `dashboard` //! has no compile-time dependency on `swactor-datastore` (which would create a //! circular dependency since `swactor-datastore[node]` depends on us). //! diff --git a/crates/datastore/Cargo.toml b/crates/datastore/Cargo.toml index d4c2d38..31ca0c3 100644 --- a/crates/datastore/Cargo.toml +++ b/crates/datastore/Cargo.toml @@ -13,12 +13,14 @@ shared-types = { path = "../shared-types" } serde = { version = "1", features = ["derive"] } serde_json = "1" blake3 = "1" +tokio = { version = "1", features = ["rt"] } +swactor-streams = { path = "../streams" } tiny_http = { version = "0.12", optional = true } clap = { version = "4", features = ["derive"], optional = true } ureq = { version = "2", features = ["json"], optional = true } getrandom = { version = "0.2", optional = true } ctrlc = { version = "3", optional = true } -runtime-dashboard = { path = "../runtime-dashboard", optional = true } +dashboard = { path = "../dashboard" } toml = { version = "0.8", optional = true } [dev-dependencies] @@ -28,14 +30,13 @@ proptest-state-machine = "0.3" tempfile = "3" distribution = { path = "../distribution" } swactor = { path = "../.." } -swactor-std = { path = "../std" } ureq = { version = "2", features = ["json"] } tiny_http = "0.12" -runtime-dashboard = { path = "../runtime-dashboard" } +dashboard = { path = "../dashboard" } stateright = "0.31" [features] -node = ["dep:tiny_http", "dep:clap", "dep:ctrlc", "dep:runtime-dashboard", "dep:toml"] +node = ["dep:tiny_http", "dep:clap", "dep:ctrlc", "dep:toml"] cli = ["dep:clap", "dep:ureq", "dep:getrandom"] [[bin]] diff --git a/crates/datastore/src/bridge.rs b/crates/datastore/src/bridge.rs index 0b23127..e36e21c 100644 --- a/crates/datastore/src/bridge.rs +++ b/crates/datastore/src/bridge.rs @@ -10,7 +10,7 @@ use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; use swactor::actor::ActorAddress; use swactor::runtime::{Inbox, Runtime}; -use swactor_std::RuntimeNaming; +use swactor::std::RuntimeNaming; use distribution::types::NodeId; use dashboard::datastore_collector::{ @@ -521,7 +521,7 @@ impl DatastoreGroup { // Spawn StreamListener use crate::actors::stream_listener::StreamListener; - use swactor_std::RuntimeNaming; + use swactor::std::RuntimeNaming; if let Ok(addr) = self.runtime.spawn(StreamListener::new(self.datastore_addr, stream_manager)) { let _ = self.runtime.register_name("StreamListener", addr); } diff --git a/crates/datastore/src/lib.rs b/crates/datastore/src/lib.rs index dd140d6..a7879aa 100644 --- a/crates/datastore/src/lib.rs +++ b/crates/datastore/src/lib.rs @@ -4,6 +4,8 @@ pub mod chunking; pub mod storage; pub mod actors; pub mod auth; +pub mod blob_transfer; +pub mod bridge; pub mod cli; pub mod metrics; #[cfg(feature = "node")] @@ -18,3 +20,4 @@ pub use messages::{BlobStoreMsg, DatastoreNodeMsg, DatastoreResponse, MetadataMs pub use chunking::{chunk_blob, reassemble_blob, verify_integrity, ChunkingError}; pub use storage::{StorageBackend, FilesystemBackend, InMemoryBackend}; pub use actors::{BlobStoreActor, DatastoreNode, MetadataActor, TransferActor}; +pub use bridge::{DatastoreAuthConfig, DatastoreGroup, DatastoreGroupConfig, DatastoreNodeFactory}; diff --git a/crates/datastore/tests/common/mod.rs b/crates/datastore/tests/common/mod.rs index 0c7331a..9ea50e1 100644 --- a/crates/datastore/tests/common/mod.rs +++ b/crates/datastore/tests/common/mod.rs @@ -7,7 +7,7 @@ use std::sync::Arc; use swactor::actor::{ActorAddress, Message}; use swactor::runtime::{Inbox, Runtime, RuntimeConfig}; -use swactor_std::StdExtension; +use swactor::std::StdExtension; use swactor_datastore::chunking::chunk_blob; use swactor_datastore::messages::{BlobStoreMsg, DatastoreResponse, MetadataMsg}; diff --git a/crates/datastore/tests/stream_integration.rs b/crates/datastore/tests/stream_integration.rs index 110ff46..2e33977 100644 --- a/crates/datastore/tests/stream_integration.rs +++ b/crates/datastore/tests/stream_integration.rs @@ -11,7 +11,7 @@ use swactor::config::RuntimeConfig; use swactor::runtime::Runtime; use swactor_datastore::bridge::{DatastoreGroup, DatastoreGroupConfig}; use swactor_datastore::messages::{DatastoreNodeMsg, DatastoreResponse}; -use swactor_std::RuntimeNaming; +use swactor::std::RuntimeNaming; fn make_driver_with_streams() -> IrohDriver { IrohDriver::new(IrohDriverConfig { @@ -31,7 +31,7 @@ fn make_runtime() -> Arc { channel_buffer_size: 2000, ..Default::default() }) - .with_extension(Arc::new(swactor_std::StdExtension::new())); + .with_extension(Arc::new(swactor::std::StdExtension::new())); let handle = rt.run().expect("start runtime"); handle.runtime diff --git a/crates/distribution/src/identity.rs b/crates/distribution/src/identity.rs index 5867dc0..13906e5 100644 --- a/crates/distribution/src/identity.rs +++ b/crates/distribution/src/identity.rs @@ -1,6 +1,6 @@ //! Shared keypair persistence — load or generate an ed25519 keypair on disk. //! -//! Reused by `swactor-node`, `store_node`, and xtask tooling. +//! Reused by the `node` crate, `store_node`, and xtask tooling. use std::path::Path; diff --git a/crates/distribution/src/snapshot.rs b/crates/distribution/src/snapshot.rs index 257a90d..0a1092f 100644 --- a/crates/distribution/src/snapshot.rs +++ b/crates/distribution/src/snapshot.rs @@ -1,6 +1,6 @@ //! Serializable snapshot of a `DistributedNode`'s state. //! -//! Used by the runtime-dashboard to display distribution monitoring data +//! Used by the dashboard to display distribution monitoring data //! for a single node without reaching out to other nodes. use serde::{Deserialize, Serialize}; diff --git a/crates/swactor-node/Cargo.toml b/crates/node/Cargo.toml similarity index 92% rename from crates/swactor-node/Cargo.toml rename to crates/node/Cargo.toml index 7499647..dd29565 100644 --- a/crates/swactor-node/Cargo.toml +++ b/crates/node/Cargo.toml @@ -1,11 +1,10 @@ [package] -name = "swactor-node" +name = "node" version = "0.1.0" edition = "2024" [dependencies] swactor = { path = "../..", features = ["serde", "tracing", "transport"] } -swactor-std = { path = "../std" } dashboard = { path = "../dashboard", features = ["distribution"] } swactor-datastore = { path = "../datastore" } distribution = { path = "../distribution" } diff --git a/crates/node/README.md b/crates/node/README.md new file mode 100644 index 0000000..bbeede9 --- /dev/null +++ b/crates/node/README.md @@ -0,0 +1,99 @@ +# node + +The `node` crate produces the `swactor` binary — the batteries-included entry point for running a swactor node. It composes the actor runtime, SWIM-based cluster membership, content-addressed datastore, data streams, an HTTP dashboard, and an optional embedded relay server into a single process. + +Run `swactor --help` for full CLI usage. + +## What a Default Node Does + +### Identity + +Every node has a persistent Ed25519 keypair stored at `~/.swactor/identity/node.key.json`. This is the node's identity across restarts — deleting it makes the node appear as a new peer to the cluster. The keypair's public key doubles as the node ID (used in SWIM, peer auth, and invite codes). A deterministic human-readable name (e.g. `swift-falcon`) is derived from the key so you can tell nodes apart in logs and the dashboard. + +### Networking + +The default transport is **iroh** (QUIC over UDP). Nodes find each other via invite codes — base58-encoded public keys exchanged out-of-band. `swactor join ` adds a peer to the allow-list and sets it as the seed node for the next startup. + +Once connected, **SWIM protocol** handles cluster membership: protocol probes every 500ms, 600ms probe timeout (tuned for relay round-trips), 2 indirect probes, 4s suspicion window. All intervals are in ticks where **1 tick = 100ms** (the main loop period). + +**Peer auth** operates in two modes: open (no `peers_file`) or allow-list (`peers.json`). In allow-list mode, SWIM messages from unknown nodes are dropped at the transport layer. New peers can be added via `swactor join` or the dashboard UI, both of which hot-update the allow-list. + +### Relay + +Nodes with a public IP auto-promote to embedded relay servers (port 3340). Candidacy is evaluated at startup: the node checks its outbound IP is non-RFC1918 and the relay port is bindable. Relay URLs are announced via SWIM gossip so other nodes discover them automatically. Nodes behind NAT use relays for indirect connectivity — this is why the probe timeout is 600ms instead of the typical 300ms. + +### Storage + +The **datastore** is a content-addressed, chunked store. Default config persists to `~/.swactor/datastore/`. Auth is enabled by default (ACL files in `~/.swactor/auth/`). The datastore runs as a group of actors inside the runtime and is driven by the main tick loop — GC runs every 1000 ticks (~100s) and dissemination every 50 ticks (~5s). A `StreamManager` actor bridges iroh QUIC streams into the datastore for bulk data transfer between nodes. + +Disable with `--no-datastore`. Use `--storage-path` to change location, or omit it from config for in-memory only. + +### Observability + +An HTTP dashboard serves on **port 9090**. It exposes runtime stats, cluster membership state, tracing output, and a peer management UI (add/remove peers). The dashboard receives a snapshot of the distribution layer every tick. + +### State & Lifecycle + +All persistent state lives under `~/.swactor/`. Deleting this directory fully resets the node (new identity, empty cluster, empty datastore). The node shuts down cleanly on SIGINT (Ctrl+C). `swactor install` copies the binary to `~/.swactor/bin/swactor` and registers it as a system service (systemd user unit, OpenRC/sysvinit init script, or `@reboot` crontab depending on the host). + +## Exposed Ports + +| Port | Service | Configurable via | +|------|---------|-----------------| +| 9090 | HTTP dashboard | `--dashboard-port` | +| 3340 | Embedded relay (if eligible) | `--relay-port` | + +## Data Directory (`~/.swactor/`) + +``` +~/.swactor/ +├── node.toml # Node configuration +├── peers.json # Peer allow-list +├── identity/ +│ └── node.key.json # Persistent Ed25519 keypair +├── datastore/ # Content-addressed chunk storage +├── auth/ # ACL and owner key files +└── bin/ + └── swactor # Installed binary (after `swactor install`) +``` + +## Architecture + +``` +CLI Args + TOML Config + │ + ▼ + Config Resolution (CLI > config > defaults) + │ + ▼ + Identity (Ed25519 Keypair) ──► Node Name + │ + ▼ + ┌─────────────────────────────────────────────┐ + │ Actor Runtime │ + │ (2 threads, StdExtension, stats hook) │ + │ │ + │ ┌──────────────┐ ┌──────────────────────┐ │ + │ │ StreamManager │ │ Datastore Group │ │ + │ │ (actor) │◄─┤ (store, gateway, │ │ + │ │ │ │ bridge actors) │ │ + │ └──────────────┘ └──────────────────────┘ │ + └──────────────┬──────────────────────────────┘ + │ + ▼ + ┌──────────────────────────────────┐ + │ Distribution Driver (iroh) │ + │ SWIM probes, gossip, relay │ + └──────────────┬───────────────────┘ + │ + ▼ + ┌──────────────────────────────────┐ + │ HTTP Dashboard │ + │ :9090 — stats, tracing, peers │ + └──────────────────────────────────┘ + │ + ▼ + Main Tick Loop (100ms) + recv → tick → streams → joins → + heartbeats → snapshot → datastore +``` diff --git a/crates/swactor-node/build.rs b/crates/node/build.rs similarity index 100% rename from crates/swactor-node/build.rs rename to crates/node/build.rs diff --git a/crates/swactor-node/src/config.rs b/crates/node/src/config.rs similarity index 95% rename from crates/swactor-node/src/config.rs rename to crates/node/src/config.rs index 3fa02e8..74c4c68 100644 --- a/crates/swactor-node/src/config.rs +++ b/crates/node/src/config.rs @@ -1,4 +1,4 @@ -//! TOML configuration file support for swactor-node. +//! TOML configuration file support for the swactor node binary. //! //! CLI flags take precedence over config file values, which take //! precedence over compiled defaults. diff --git a/crates/swactor-node/src/install.rs b/crates/node/src/install.rs similarity index 100% rename from crates/swactor-node/src/install.rs rename to crates/node/src/install.rs diff --git a/crates/swactor-node/src/main.rs b/crates/node/src/main.rs similarity index 99% rename from crates/swactor-node/src/main.rs rename to crates/node/src/main.rs index e43520c..705f711 100644 --- a/crates/swactor-node/src/main.rs +++ b/crates/node/src/main.rs @@ -1,4 +1,4 @@ -//! swactor-node — unified distributed node with dashboard and datastore. +//! Swactor node — unified distributed node with dashboard and datastore. //! //! Combines distribution, runtime dashboard, and content-addressed datastore //! into a single batteries-included binary. Datastore is on by default @@ -495,7 +495,7 @@ fn main() { channel_buffer_size: 2000, ..Default::default() }) - .with_extension(Arc::new(swactor_std::StdExtension::new())); + .with_extension(Arc::new(swactor::std::StdExtension::new())); rt.set_stats_hook(collector.clone()); let handle = rt.run().expect("failed to start runtime"); @@ -713,7 +713,7 @@ fn run_iroh( ) { use distribution::iroh_driver::{IrohDriver, IrohDriverConfig}; use iroh::{RelayMode, SecretKey}; - use swactor_std::RuntimeNaming; + use swactor::std::RuntimeNaming; // Evaluate relay candidacy and determine embedded relay bind address #[cfg(feature = "relay")] diff --git a/crates/swactor-node/src/names.rs b/crates/node/src/names.rs similarity index 100% rename from crates/swactor-node/src/names.rs rename to crates/node/src/names.rs diff --git a/crates/swactor-node/src/relay.rs b/crates/node/src/relay.rs similarity index 100% rename from crates/swactor-node/src/relay.rs rename to crates/node/src/relay.rs diff --git a/crates/std/Cargo.toml b/crates/std/Cargo.toml deleted file mode 100644 index 5b063c8..0000000 --- a/crates/std/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "swactor-std" -version = "0.1.0" -edition = "2024" - -[features] -default = ["getrandom"] -getrandom = ["dep:getrandom", "swactor/getrandom"] -wasm = ["swactor/wasm"] - -[dependencies] -swactor = { path = "../..", default-features = false } -getrandom = { version = "0.2", optional = true } diff --git a/crates/streams/Cargo.toml b/crates/streams/Cargo.toml index 3f81fc0..503490c 100644 --- a/crates/streams/Cargo.toml +++ b/crates/streams/Cargo.toml @@ -5,7 +5,6 @@ edition = "2024" [dependencies] swactor = { path = "../..", features = ["serde"] } -swactor-std = { path = "../std" } shared-types = { path = "../shared-types" } distribution = { path = "../distribution" } crossbeam-queue = "0.3.12" diff --git a/crates/streams/src/ctx_ext.rs b/crates/streams/src/ctx_ext.rs index f68f072..7dc7d51 100644 --- a/crates/streams/src/ctx_ext.rs +++ b/crates/streams/src/ctx_ext.rs @@ -8,8 +8,8 @@ use swactor::actor::{ActorAddress, Ctx}; use swactor::runtime::Runtime; -use swactor_std::CtxNaming; -use swactor_std::RuntimeNaming; +use swactor::std::CtxNaming; +use swactor::std::RuntimeNaming; use crate::messages::StreamManagerMsg; use crate::types::{StreamConfig, StreamError, StreamId, StreamMode}; diff --git a/docs/connectome/connectome.md b/docs/connectome/connectome.md deleted file mode 100644 index db223c0..0000000 --- a/docs/connectome/connectome.md +++ /dev/null @@ -1,76 +0,0 @@ -# Connectome Analysis - -The connectome analysis applies spectral graph theory to the codebase's internal dependency DAG, producing quantitative coupling metrics and visual dashboards. - -## What it measures - -The tool parses `deps.dot` (a GraphViz DOT file describing struct/trait dependencies between modules) and computes: - -- **Laplacian eigenvalue spectrum** -- encodes the graph's overall connectivity structure -- **Fiedler vector** -- the optimal spectral bisection of the dependency graph, revealing natural module clusters -- **Module coupling matrix** -- directed edge counts between every pair of modules -- **Connectome Complexity Index (CCI)** -- a single 0-1 score combining five sub-metrics: - -| Sub-metric | Weight | What it captures | -|---|---|---| -| Algebraic connectivity (lambda_2/n) | 25% | How tightly connected the graph is | -| Spectral entropy (H/log2(k)) | 25% | How uniformly distributed coupling is across eigenvalues | -| Edge density (\|E\|/n(n-1)) | 15% | Raw ratio of edges to possible edges | -| Cross-module coupling ratio | 20% | Fraction of edges that cross module boundaries | -| Spectral radius (rho/(n-1)) | 15% | Maximum hub concentration | - -### Interpreting CCI - -| CCI range | Label | Meaning | -|---|---|---| -| < 0.30 | LOW | Well-decomposed architecture | -| 0.30 - 0.60 | MODERATE | Typical well-structured codebase | -| > 0.60 | HIGH | Consider reviewing module boundaries | - -## Running - -From the project root: - -```sh -# Default: outputs to docs/connectome/ -python tools/spectral/spectral_analysis.py deps.dot - -# Custom output directory -python tools/spectral/spectral_analysis.py deps.dot -o path/to/output - -# Also emit JSON metrics -python tools/spectral/spectral_analysis.py deps.dot --json - -# Text report only (skip matplotlib PNG) -python tools/spectral/spectral_analysis.py deps.dot --no-plots -``` - -### Prerequisites - -The script requires numpy, scipy, and matplotlib (for the PNG dashboard). These are available in the project's `.venv`: - -```sh -source .venv/bin/activate -python tools/spectral/spectral_analysis.py deps.dot -``` - -## Output files - -All output goes to `docs/connectome/` by default: - -| File | Description | -|---|---| -| `connectome_report.txt` | Full text report with eigenvalues, Fiedler bisection, coupling matrix, and CCI breakdown | -| `connectome_dashboard.html` | Interactive HTML dashboard with zoomable DAG, eigenvalue plot, Fiedler bar chart, and coupling heatmap | -| `connectome_dashboard.png` | Static PNG snapshot of the spectral dashboard (dark theme, 16x12 @ 150 DPI) | -| `connectome_metrics.json` | Machine-readable metrics (only with `--json` flag) | - -## Regenerating deps.dot - -The DOT file is the input to the spectral analysis. To regenerate it from source: - -```sh -cargo run --manifest-path tools/depgraph/Cargo.toml -- --src-dir src/ --output deps -``` - -Then re-run the spectral analysis to update the connectome report. diff --git a/docs/connectome/connectome_dashboard.html b/docs/connectome/connectome_dashboard.html deleted file mode 100644 index b8b68ab..0000000 --- a/docs/connectome/connectome_dashboard.html +++ /dev/null @@ -1,743 +0,0 @@ - - - -swactor — dependency analysis - - - - -
-
swactor — dependency analysis
- - -
- -
-
- - - - scroll to zoom · drag to pan · click node to focus -
-
-
Loading Graphviz…
-
- -
-
-
-

◉ Structural Properties

-
-
- -
-

▨ Module Cohesion

- -
- -
-

▦ Module Coupling (directed edge counts)

- -
- -
-

∑ Complexity Metrics

-
-
-
-
- -
- - - - - - - diff --git a/docs/connectome/connectome_metrics.json b/docs/connectome/connectome_metrics.json deleted file mode 100644 index f72835e..0000000 --- a/docs/connectome/connectome_metrics.json +++ /dev/null @@ -1,169 +0,0 @@ -{ - "graph": { - "n_nodes": 39, - "n_edges": 97, - "n_modules": 9, - "connected_components": 2, - "modules": [ - "actor", - "worker", - "channel", - "error", - "config", - "delivery", - "stats", - "runtime", - "transport" - ] - }, - "structural": { - "avg_degree": 2.4871794871794872, - "max_fan_in": { - "count": 16, - "node": "ActorAddress" - }, - "max_fan_out": { - "count": 18, - "node": "Runtime" - }, - "dag_depth": 7, - "clustering_coefficient": 0.26676384839650147, - "avg_module_size": 4.333333333333333 - }, - "module_coupling": { - "module_names": [ - "actor", - "worker", - "channel", - "error", - "config", - "delivery", - "stats", - "runtime", - "transport" - ], - "coupling_matrix": [ - [ - 9.0, - 0.0, - 0.0, - 2.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0 - ], - [ - 9.0, - 2.0, - 1.0, - 1.0, - 0.0, - 5.0, - 3.0, - 0.0, - 0.0 - ], - [ - 0.0, - 0.0, - 3.0, - 0.0, - 0.0, - 1.0, - 0.0, - 0.0, - 0.0 - ], - [ - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0 - ], - [ - 0.0, - 0.0, - 0.0, - 0.0, - 1.0, - 0.0, - 0.0, - 0.0, - 0.0 - ], - [ - 5.0, - 0.0, - 1.0, - 2.0, - 1.0, - 7.0, - 0.0, - 0.0, - 2.0 - ], - [ - 2.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 5.0, - 0.0, - 0.0 - ], - [ - 4.0, - 1.0, - 2.0, - 1.0, - 1.0, - 5.0, - 2.0, - 3.0, - 2.0 - ], - [ - 3.0, - 0.0, - 2.0, - 4.0, - 0.0, - 0.0, - 0.0, - 0.0, - 5.0 - ] - ], - "cross_module_edges": 62, - "total_edges": 97 - }, - "module_cohesion": { - "actor": 0.21428571428571427, - "worker": 0.16666666666666666, - "channel": 0.5, - "error": null, - "config": 0.5, - "delivery": 0.16666666666666666, - "stats": 0.25, - "runtime": 0.5, - "transport": 0.11904761904761904 - }, - "metrics": { - "algebraic_connectivity": 0.0, - "spectral_entropy": 4.728701071955628, - "edge_density": 0.06545209176788123, - "cross_module_ratio": 0.6391752577319587, - "spectral_radius": 7.96362061601628, - "avg_module_cohesion": 0.3020833333333333, - "cci": 0.39601706111504315 - } -} \ No newline at end of file diff --git a/docs/connectome/connectome_report.txt b/docs/connectome/connectome_report.txt deleted file mode 100644 index a685bd1..0000000 --- a/docs/connectome/connectome_report.txt +++ /dev/null @@ -1,66 +0,0 @@ -======================================================================== - SPECTRAL ANALYSIS REPORT — Dependency DAG -======================================================================== - -GRAPH SUMMARY ----------------------------------------- - Nodes: 39 - Directed edges: 97 - Modules: 9 - Connected components: 2 - Modules: actor, worker, channel, error, config, delivery, stats, runtime, transport - -STRUCTURAL PROPERTIES ----------------------------------------- - Edges/node (avg degree): 2.49 - Max fan-in: 16 (ActorAddress) - Max fan-out: 18 (Runtime) - DAG depth: 7 - Clustering coefficient: 0.2668 - -MODULE COHESION ----------------------------------------- - Module Size Cohesion - actor 7 0.214 - worker 4 0.167 - channel 3 0.500 - error 1 — - config 2 0.500 - delivery 7 0.167 - stats 5 0.250 - runtime 3 0.500 - transport 7 0.119 - ──────────────────────────────── - Average cohesion: 0.302 - Avg module size: 4.3 - -MODULE COUPLING MATRIX (directed edge counts) ----------------------------------------- - actor worker channel error config delivery stats runtime transport - actor 9 0 0 2 0 0 0 0 0 - worker 9 2 1 1 0 5 3 0 0 - channel 0 0 3 0 0 1 0 0 0 - error 0 0 0 0 0 0 0 0 0 - config 0 0 0 0 1 0 0 0 0 - delivery 5 0 1 2 1 7 0 0 2 - stats 2 0 0 0 0 0 5 0 0 - runtime 4 1 2 1 1 5 2 3 2 - transport 3 0 2 4 0 0 0 0 5 - - Cross-module edges: 62 / 97 (63.9%) - -CONNECTOME COMPLEXITY INDEX (CCI) ----------------------------------------- - Sub-metric Raw Normalized Weight Contrib - ──────────────────────────────────────── ────────── ────────── ──────── ──────── - Algebraic connectivity (lambda_2/n) 0.0000 0.0000 0.25 0.0000 - Spectral entropy (H/log2(k)) 4.7287 0.9077 0.25 0.2269 - Edge density (|E|/n(n-1)) 0.0655 0.0655 0.15 0.0098 - Cross-module coupling ratio 0.6392 0.6392 0.20 0.1278 - Spectral radius (rho/(n-1)) 7.9636 0.2096 0.15 0.0314 - ──────────────────────────────────────── ────────── ────────── ──────── ──────── - CCI (weighted sum) 1.00 0.3960 - - Interpretation: MODERATE complexity — typical well-structured codebase - -======================================================================== \ No newline at end of file diff --git a/docs/datastore/DATASTORE_AUTH.md b/docs/datastore/DATASTORE_AUTH.md deleted file mode 100644 index e6647d5..0000000 --- a/docs/datastore/DATASTORE_AUTH.md +++ /dev/null @@ -1,503 +0,0 @@ -# Swactor Datastore Auth Specification - -**Version:** 0.2.0 -**Status:** Implemented (MVP) - -## 1. Overview - -This document specifies the authorization layer for the Swactor Datastore as implemented. It defines how access is controlled for external clients connecting to a datastore node. - -### Principles - -- **Cryptographic identity** — keys, not passwords. Every participant is identified by an ed25519 public key (`NodeId`). -- **Binary access** — a client is either authorized or not. No permission tiers for MVP. -- **Owner-only administration** — only the datastore owner can grant or revoke access. -- **Two auth paths** — direct iroh connections (connection-level) and signed HTTP requests (browser/CLI). This spec covers the signed request path (Auth Path 2), which is fully implemented. - -### Non-Goals (MVP) - -- Per-path permission scoping. -- Permission tiers (read-only, read-write, admin). -- Capability tokens or time-limited delegated access. -- Multi-level delegation chains. - -## 2. Trust Boundaries - -``` -┌─────────────────────────────────────────────┐ -│ Cluster (SWIM mesh) │ -│ │ -│ Node A ◄──────────────► Node B │ -│ implicitly trusted │ -│ (no auth checks) │ -└──────────────────┬──────────────────────────┘ - │ - │ auth boundary - │ - ┌──────────▼──────────┐ - │ External Clients │ - │ │ - │ CLI tool │ - │ Browser user │ - └─────────────────────┘ -``` - -- **Cluster-internal** (node-to-node via SWIM): implicitly trusted. Nodes that are members of the SWIM cluster communicate freely — no per-request auth checks. -- **External clients** (CLI, browser): must be authorized. Every external request is checked against the Access Control List before being dispatched to the actor system. - -## 3. Identity Model - -The auth layer reuses the existing ed25519 identity model from the distribution layer: - -- Every client (CLI tool, browser user, node) has an ed25519 keypair. -- Identity is the 32-byte public key, represented as `NodeId`. -- The same `NodeId` type from `distribution::types` is used throughout. - -There is no separate "user" concept — a keypair *is* an identity. - -## 4. Access Control List - -### 4.1 Structure - -```rust -AccessControlList { - owner: NodeId, // The datastore owner's public key - authorized_keys: HashSet, // Explicitly authorized client keys - key_labels: HashMap, // hex(public_key) → human-readable name -} -``` - -- The **owner** always has full access (implicit; never needs to be in `authorized_keys`). -- An empty `authorized_keys` set means only the owner can access the datastore. -- `key_labels` maps the hex-encoded public key to a human-readable name. Labels are set on grant (from the access request's `name` field or an explicit `--name`/`?name=` parameter) and removed on revoke. The `#[serde(default)]` annotation ensures backward compatibility with ACL files written before labels existed. - -### 4.2 Persistence - -The ACL is persisted as JSON in the **auth directory**, separate from the storage path: - -``` -/ -├── owner.key.json # Owner keypair -└── acl.json # AccessControlList -``` - -Default `auth-dir` is `./auth` (configurable via `--auth-dir`). - -### 4.3 Mutations - -| Operation | Signature | Who | -|-----------|-----------|-----| -| Grant access | `grant(requester, key, label)` | Owner only | -| Revoke access | `revoke(requester, key)` | Owner only | - -- `grant` adds a `NodeId` to `authorized_keys` and optionally sets a label in `key_labels`. If the key has a pending access request, the request's `name` is used as the label (unless an explicit label is provided). Idempotent. -- `revoke` removes a `NodeId` from `authorized_keys` and removes its label from `key_labels`. Idempotent. -- Revoking the owner is a no-op (the owner's implicit access cannot be removed). -- Both operations persist the updated ACL to disk immediately via `persist_acl()`. - -## 5. Auth Path 1 — Direct iroh Connection - -For clients that connect directly to the datastore node over iroh (QUIC): - -``` -Client (ed25519 keypair) Datastore Node - │ │ - │──── iroh QUIC handshake ──────────>│ - │ (proves client's NodeId) │ - │ │ - │ check NodeId - │ against ACL - │ │ - │<─── accept / reject ──────────────│ - │ │ - │ (if accepted, all ops on │ - │ this connection are allowed) │ -``` - -1. The iroh QUIC handshake cryptographically proves the peer's `NodeId` (ed25519 public key). -2. On connection establishment, the node checks the peer's `NodeId` against the ACL via `check_node()`. -3. If authorized, connection accepted. All operations on that connection are allowed with no per-message overhead. -4. If not authorized, connection rejected immediately. - -## 6. Auth Path 2 — Signed Requests (HTTP API) - -For browser users and CLI clients communicating over HTTP. - -### 6.1 Threat Model - -The HTTP transport is treated as an **untrusted relay**. Each request is self-authenticating via a signed envelope. The relay cannot forge, modify, or replay requests. - -### 6.2 Signed Envelope - -Each request carries a signed envelope in the `X-Signed-Request` HTTP header: - -```rust -SignedRequest { - payload: SignedRequestPayload, // The request details - public_key: NodeId, // Client's public key (as [u8; 32]) - signature: Signature, // ed25519 signature over serialized payload -} - -SignedRequestPayload { - action: DatastoreAction, // What the client wants to do - timestamp: u64, // Unix timestamp (seconds) - nonce: [u8; 16], // 16 random bytes -} - -DatastoreAction = enum { - Put { name, content_hash, size_bytes, tags }, - Get { content_hash }, - Delete { content_hash }, - List { name_filter }, - Access, // Identity proof (no content binding) -} -``` - -The header value is the JSON serialization of `SignedRequest`. The `public_key` and `signature` fields are serialized as arrays of integers (e.g., `[163, 45, ...]`), matching serde's default serialization for `[u8; 32]` and `[u8; 64]`. - -### 6.3 DatastoreAction::Access - -The `Access` variant is a lightweight identity proof that does not bind to a specific content operation. It is used by: - -- **Browser** — all API calls use `Access` (the browser proves identity, and the HTTP layer gates the actual operation). -- **CLI auth management** — `grant`, `revoke`, `requests`, `keys`, `deny` subcommands use `Access` since these admin operations don't correspond to content actions. - -The CLI's data operations (`put`, `get`, `delete`, `list`) sign the corresponding specific action variants. - -### 6.4 Verification Steps - -The `AuthzEngine` verifies a signed request in strict order: - -1. **Signature validity** — verify the ed25519 signature over the canonical JSON serialization of `SignedRequestPayload` using the provided `public_key`. -2. **Timestamp freshness** — reject if `|now - payload.timestamp| > 300` seconds. -3. **Nonce uniqueness** — reject if `payload.nonce` has been seen before within the time window. -4. **ACL check** — reject if `public_key` is not in the ACL (not owner and not in `authorized_keys`). - -If any step fails, the request is denied with the corresponding `DeniedReason`: -- `InvalidSignature` -- `RequestExpired` -- `ReplayDetected` -- `NotAuthorized` - -### 6.5 Signature-Only Verification - -A separate `check_signature_only()` path performs steps 1-3 (signature, timestamp, nonce) but **skips** step 4 (ACL check). This is used for the access request endpoint (`POST /api/auth/request`), where an unauthorized user needs to prove they own the key they're requesting access for. - -### 6.6 Put Payload Note - -`DatastoreAction::Put` references a `content_hash` rather than embedding raw file data. The bulk data is uploaded separately, and its integrity is guaranteed by blake3 content addressing. The signed envelope authorizes the *operation*, not the data transfer. - -## 7. Replay Protection - -### 7.1 Timestamp Window - -- Requests must have a `timestamp` within ±300 seconds of the node's wall clock. -- Requests outside this window are rejected with `DeniedReason::RequestExpired`. - -### 7.2 Nonce - -- Each request includes a 16-byte random nonce. -- The node maintains a set of recently seen nonces in `seen_nonces: HashMap<[u8; 16], u64>`. -- Duplicate nonces within the time window are rejected with `DeniedReason::ReplayDetected`. - -### 7.3 Nonce Garbage Collection - -- Nonces are stored alongside their timestamps. -- When a nonce's timestamp falls outside the ±300 second window, it is eligible for GC. -- `gc_nonces(now)` is called periodically via `GatewayMsg::NonceGcTick`, which piggybacks on the main loop's GC tick cadence. - -## 8. Enforcement Point - -Auth is enforced at the **edge** of the actor system via the `GatewayActor`: - -``` -External Client - │ - ▼ -┌─────────────┐ -│ GatewayActor│◄── ACL check happens here -└──────┬──────┘ - │ - ▼ -┌──────────────┐ ┌─────────────────┐ ┌────────────────┐ -│ MetadataActor│◄──►│ BlobStoreActor │ │ TransferActor │ -│ │ │ │ │ │ -│ (auth- │ │ (auth- │ │ (auth- │ -│ unaware) │ │ unaware) │ │ unaware) │ -└──────────────┘ └─────────────────┘ └────────────────┘ -``` - -### 8.1 HTTP API Route Table - -| Method | Path | Auth Level | Description | -|--------|------|------------|-------------| -| `GET` | `/` | None | Browser UI page | -| `GET` | `/admin` | None | Admin page | -| `GET` | `/crypto.wasm` | None | WASM Ed25519 module | -| `GET` | `/api/status` | None | Node identity | -| `POST` | `/api/put` | Full (`check_auth`) | Store an object | -| `GET` | `/api/get` | Full (`check_auth`) | Get object metadata | -| `GET` | `/api/data` | Full (`check_auth`) | Download object data | -| `POST` | `/api/delete` | Full (`check_auth`) | Delete an object | -| `GET` | `/api/list` | Full (`check_auth`) | List objects | -| `POST` | `/api/auth/grant` | Full (`check_auth_identity`) | Grant access to a key (owner-only) | -| `POST` | `/api/auth/revoke` | Full (`check_auth_identity`) | Revoke access from a key (owner-only) | -| `GET` | `/api/auth/requests` | Full (`check_auth_identity`) | List pending access requests (owner-only) | -| `GET` | `/api/auth/keys` | Full (`check_auth_identity`) | List authorized keys (owner-only) | -| `POST` | `/api/auth/deny` | Full (`check_auth_identity`) | Deny a pending request (owner-only) | -| `POST` | `/api/auth/request` | Signature-only (`check_auth_signature_only`) | Submit an access request | - -**Auth levels:** -- **None** — no `X-Signed-Request` header required. -- **Full** — `X-Signed-Request` header required; full 4-step verification (signature + timestamp + nonce + ACL). -- **Signature-only** — `X-Signed-Request` header required; 3-step verification (signature + timestamp + nonce, no ACL check). - -`check_auth_identity` is like `check_auth` but also returns the caller's `NodeId`, needed for grant/revoke/deny operations to identify the requester. - -### 8.2 Internal Actors - -`MetadataActor`, `BlobStoreActor`, and `TransferActor` remain **auth-unaware**. They process messages from any source within the actor system. The auth boundary is strictly external. - -## 9. Browser Auth Flow - -### 9.1 WASM Ed25519 Crypto - -Browser clients use a WASM module (`/crypto.wasm`) compiled from `crates/crypto-wasm/` — a `no_std` Rust crate using `ed25519-dalek`. This replaces the earlier Web Crypto API approach, which has inconsistent Ed25519 support across browsers. - -The WASM module exports three functions through a shared 8192-byte buffer: - -| Function | Input | Output | -|----------|-------|--------| -| `buffer_ptr()` | — | Pointer to shared buffer | -| `get_public_key()` | `BUF[0..32]` = seed | `BUF[32..64]` = public key | -| `ed25519_sign(msg_len)` | `BUF[0..32]` = seed, `BUF[128..128+msg_len]` = message | `BUF[64..128]` = signature | - -JavaScript wrapper functions: - -```javascript -async function initCrypto() { - const { instance } = await WebAssembly.instantiate( - await (await fetch('/crypto.wasm')).arrayBuffer() - ); - wasmExports = instance.exports; - bufPtr = wasmExports.buffer_ptr(); -} - -function derivePublicKey(seed) { /* write seed → read pubkey */ } -function signBytes(message, seed) { /* write seed+message → read signature */ } -``` - -### 9.2 Device Key Management - -On first visit (when auth is detected), the browser: - -1. Generates a 32-byte random seed: `crypto.getRandomValues(new Uint8Array(32))` -2. Stores it as hex in `localStorage.deviceKeySeed` -3. Derives the public key via `derivePublicKey(seed)` - -On subsequent visits, the seed is loaded from localStorage. A migration path handles legacy JWK keys (from an earlier Web Crypto implementation) by extracting the `d` parameter as the seed. - -### 9.3 Auth Detection - -On page load, the browser fetches `GET /api/list` without auth: -- If the response is 401, auth is enabled → initialize WASM crypto, generate/load keys, show device key in header -- If the response is 200, auth is disabled → proceed normally - -### 9.4 Request Signing - -All authenticated browser requests go through `authFetch()`: - -```javascript -async function authFetch(url, opts) { - const nonce = Array.from(crypto.getRandomValues(new Uint8Array(16))); - const payload = { - action: "Access", - timestamp: Math.floor(Date.now() / 1000), - nonce: nonce - }; - const payloadBytes = new TextEncoder().encode(JSON.stringify(payload)); - const sigBytes = signBytes(payloadBytes, deviceSeed); - const header = JSON.stringify({ - payload: payload, - public_key: Array.from(pubKeyBytes), - signature: Array.from(sigBytes) - }); - opts.headers['X-Signed-Request'] = header; - return fetch(url, opts); -} -``` - -The browser always uses `DatastoreAction::Access` — it proves identity without binding to a specific content operation. The HTTP API layer handles the actual data operation gating. - -### 9.5 Access Request Flow - -When a browser user is not yet authorized: - -1. **Auth banner appears** — shows a form with name (required, max 64 chars) and message (optional, max 256 chars) fields. -2. **User submits** — `POST /api/auth/request` with JSON body `{ name, message }` and `X-Signed-Request` header (signature-only check). -3. **Pending state** — banner switches to "waiting for operator approval" with localStorage persistence (`accessRequestPending`, `accessRequestName`). -4. **Polling** — every 5 seconds, `authFetch('/api/list')` checks if the user has been granted access. -5. **Granted** — when `/api/list` returns 200, polling stops, banner disappears, object list loads. -6. **Re-submission on reload** — if the page is reloaded while pending, the request is re-submitted to handle node restarts. - -## 10. Admin Page - -The admin page (`/admin`) provides a browser interface for the datastore owner to manage access. - -### 10.1 Authentication - -The owner authenticates by uploading their `key.json` file: -1. File is parsed for `secret_key` (hex) and `public_key` (hex). -2. Public key is derived from the secret key via WASM and compared to the stored `public_key` for integrity. -3. A test call to `GET /api/auth/requests` verifies this is actually the owner key (non-owners get 403). - -### 10.2 Capabilities - -- **Pending access requests** — table showing name, message, key (truncated), with grant/deny buttons per request. -- **Authorized keys** — table showing label, key (truncated), with revoke button per key. -- **Manual grant** — input fields for a 64-char hex public key + optional name, bypassing the access request flow. -- **Name disambiguation** — when multiple entries share the same name, a key prefix `(abcd1234)` is appended for disambiguation. - -### 10.3 Admin Request Signing - -All admin API calls use `ownerAuthFetch()`, which signs with `DatastoreAction::Access` using the owner's seed. - -## 11. CLI - -### 11.1 Auth Signing - -The CLI uses `--key ` to load a key.json file. Each command signs an `X-Signed-Request` header: - -- **Data operations** (`put`, `get`, `delete`, `list`) sign with the corresponding `DatastoreAction` variant (e.g., `DatastoreAction::Put { name, content_hash, size_bytes, tags }`). -- **Auth management** (`grant`, `revoke`, `requests`, `keys`, `deny`) sign with `DatastoreAction::Access`. -- **`status`** — never signed (endpoint is always open). -- Without `--key`, no header is sent (backward compatible with non-auth nodes). - -### 11.2 Subcommands - -``` -swactor-store --key put [--name