commit 7a258045215bed5a1132e638fce7fb2258abb2ed parent b5b8dee87cbe9f2e6af2ffb63f60cfdd3e9ac054 Author: triesap <tyson@radroots.org> Date: Fri, 10 Apr 2026 00:37:20 +0000 workspace: normalize crate identities Diffstat:
529 files changed, 4148 insertions(+), 3985 deletions(-)
diff --git a/.beads/config.yaml b/.beads/config.yaml @@ -28,26 +28,26 @@ directory: crates/app-wasm: crate:app_wasm crates/core: crate:core crates/events: crate:events - crates/events-codec: crate:events_codec - crates/events-codec-wasm: crate:events_codec_wasm - crates/events-indexed: crate:events_indexed + crates/events_codec: crate:events_codec + crates/events_codec_wasm: crate:events_codec_wasm + crates/events_indexed: crate:events_indexed crates/identity: crate:identity crates/log: crate:log crates/net: crate:net - crates/net-core: crate:net_core + crates/net_core: crate:net_core crates/nostr: crate:nostr - crates/nostr-accounts: crate:nostr_accounts - crates/nostr-ndb: crate:nostr_ndb - crates/nostr-runtime: crate:nostr_runtime + crates/nostr_accounts: crate:nostr_accounts + crates/nostr_ndb: crate:nostr_ndb + crates/nostr_runtime: crate:nostr_runtime crates/runtime: crate:runtime - crates/sql-core: crate:sql_core - crates/sql-wasm-bridge: crate:sql_wasm_bridge - crates/sql-wasm-core: crate:sql_wasm_core - crates/replica-db-schema: crate:replica_db_schema - crates/replica-db: crate:replica_db - crates/replica-db-wasm: crate:replica_db_wasm - crates/replica-sync: crate:replica_sync - crates/replica-sync-wasm: crate:replica_sync_wasm + crates/sql_core: crate:sql_core + crates/sql_wasm_bridge: crate:sql_wasm_bridge + crates/sql_wasm_core: crate:sql_wasm_core + crates/replica_db_schema: crate:replica_db_schema + crates/replica_db: crate:replica_db + crates/replica_db_wasm: crate:replica_db_wasm + crates/replica_sync: crate:replica_sync + crates/replica_sync_wasm: crate:replica_sync_wasm crates/trade: crate:trade crates/types: crate:types crates/xtask: crate:xtask diff --git a/Cargo.lock b/Cargo.lock @@ -2291,7 +2291,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] -name = "radroots-core" +name = "radroots_core" version = "0.1.0-alpha.1" dependencies = [ "rust_decimal", @@ -2303,10 +2303,10 @@ dependencies = [ ] [[package]] -name = "radroots-events" +name = "radroots_events" version = "0.1.0-alpha.1" dependencies = [ - "radroots-core", + "radroots_core", "serde", "serde_json", "ts-rs", @@ -2314,31 +2314,31 @@ dependencies = [ ] [[package]] -name = "radroots-events-codec" +name = "radroots_events_codec" version = "0.1.0-alpha.1" dependencies = [ "nostr", - "radroots-core", - "radroots-events", - "radroots-test-fixtures", + "radroots_core", + "radroots_events", + "radroots_test_fixtures", "serde", "serde_json", ] [[package]] -name = "radroots-events-codec-wasm" +name = "radroots_events_codec_wasm" version = "0.1.0-alpha.1" dependencies = [ - "radroots-core", - "radroots-events", - "radroots-events-codec", + "radroots_core", + "radroots_events", + "radroots_events_codec", "serde", "serde_json", "wasm-bindgen", ] [[package]] -name = "radroots-events-indexed" +name = "radroots_events_indexed" version = "0.1.0-alpha.1" dependencies = [ "serde", @@ -2347,7 +2347,7 @@ dependencies = [ ] [[package]] -name = "radroots-geocoder" +name = "radroots_geocoder" version = "0.1.0-alpha.1" dependencies = [ "rusqlite", @@ -2357,14 +2357,14 @@ dependencies = [ ] [[package]] -name = "radroots-identity" +name = "radroots_identity" version = "0.1.0-alpha.1" dependencies = [ "nostr", - "radroots-events", - "radroots-runtime", - "radroots-runtime-paths", - "radroots-test-fixtures", + "radroots_events", + "radroots_runtime", + "radroots_runtime_paths", + "radroots_test_fixtures", "secrecy", "serde", "serde_json", @@ -2376,7 +2376,7 @@ dependencies = [ ] [[package]] -name = "radroots-log" +name = "radroots_log" version = "0.1.0-alpha.1" dependencies = [ "chrono", @@ -2387,27 +2387,27 @@ dependencies = [ ] [[package]] -name = "radroots-net" +name = "radroots_net" version = "0.1.0-alpha.1" dependencies = [ - "radroots-net-core", + "radroots_net_core", ] [[package]] -name = "radroots-net-core" +name = "radroots_net_core" version = "0.1.0-alpha.1" dependencies = [ "directories", "futures", "hex", - "radroots-events", - "radroots-events-codec", - "radroots-identity", - "radroots-log", - "radroots-nostr", - "radroots-nostr-accounts", - "radroots-nostr-signer", - "radroots-runtime-paths", + "radroots_events", + "radroots_events_codec", + "radroots_identity", + "radroots_log", + "radroots_nostr", + "radroots_nostr_accounts", + "radroots_nostr_signer", + "radroots_runtime_paths", "secrecy", "serde", "serde_json", @@ -2418,15 +2418,15 @@ dependencies = [ ] [[package]] -name = "radroots-nostr" +name = "radroots_nostr" version = "0.1.0-alpha.1" dependencies = [ "nostr", "nostr-sdk", - "radroots-events", - "radroots-events-codec", - "radroots-identity", - "radroots-test-fixtures", + "radroots_events", + "radroots_events_codec", + "radroots_identity", + "radroots_test_fixtures", "reqwest", "serde", "serde_json", @@ -2435,15 +2435,15 @@ dependencies = [ ] [[package]] -name = "radroots-nostr-accounts" +name = "radroots_nostr_accounts" version = "0.1.0-alpha.1" dependencies = [ - "radroots-identity", - "radroots-nostr-ndb", - "radroots-nostr-signer", - "radroots-runtime", - "radroots-secret-vault", - "radroots-test-fixtures", + "radroots_identity", + "radroots_nostr_ndb", + "radroots_nostr_signer", + "radroots_runtime", + "radroots_secret_vault", + "radroots_test_fixtures", "serde", "serde_json", "tempfile", @@ -2452,11 +2452,11 @@ dependencies = [ ] [[package]] -name = "radroots-nostr-connect" +name = "radroots_nostr_connect" version = "0.1.0-alpha.1" dependencies = [ "nostr", - "radroots-test-fixtures", + "radroots_test_fixtures", "serde", "serde_json", "thiserror 1.0.69", @@ -2464,15 +2464,15 @@ dependencies = [ ] [[package]] -name = "radroots-nostr-ndb" +name = "radroots_nostr_ndb" version = "0.1.0-alpha.1" dependencies = [ "futures", "hex", "nostrdb", - "radroots-nostr", - "radroots-nostr-runtime", - "radroots-test-fixtures", + "radroots_nostr", + "radroots_nostr_runtime", + "radroots_test_fixtures", "serde_json", "tempfile", "thiserror 1.0.69", @@ -2480,26 +2480,26 @@ dependencies = [ ] [[package]] -name = "radroots-nostr-runtime" +name = "radroots_nostr_runtime" version = "0.1.0-alpha.1" dependencies = [ "futures", - "radroots-nostr", + "radroots_nostr", "thiserror 1.0.69", "tokio", ] [[package]] -name = "radroots-nostr-signer" +name = "radroots_nostr_signer" version = "0.1.0-alpha.1" dependencies = [ "hex", "nostr", - "radroots-identity", - "radroots-nostr-connect", - "radroots-runtime", - "radroots-sql-core", - "radroots-test-fixtures", + "radroots_identity", + "radroots_nostr_connect", + "radroots_runtime", + "radroots_sql_core", + "radroots_test_fixtures", "serde", "serde_json", "sha2", @@ -2510,50 +2510,50 @@ dependencies = [ ] [[package]] -name = "radroots-protected-store" +name = "radroots_protected_store" version = "0.1.0-alpha.1" dependencies = [ "chacha20poly1305", "getrandom 0.2.17", - "radroots-secret-vault", + "radroots_secret_vault", "serde", "serde_json", "zeroize", ] [[package]] -name = "radroots-replica-db" +name = "radroots_replica_db" version = "0.1.0-alpha.1" dependencies = [ "hex", - "radroots-replica-db-schema", - "radroots-sql-core", - "radroots-types", + "radroots_replica_db_schema", + "radroots_sql_core", + "radroots_types", "serde", "serde_json", "sha2", ] [[package]] -name = "radroots-replica-db-schema" +name = "radroots_replica_db_schema" version = "0.1.0-alpha.1" dependencies = [ - "radroots-types", + "radroots_types", "serde", "serde_json", "ts-rs", ] [[package]] -name = "radroots-replica-db-wasm" +name = "radroots_replica_db_wasm" version = "0.1.0-alpha.1" dependencies = [ "js-sys", - "radroots-replica-db", - "radroots-replica-db-schema", - "radroots-replica-sync", - "radroots-sql-core", - "radroots-sql-wasm-core", + "radroots_replica_db", + "radroots_replica_db_schema", + "radroots_replica_sync", + "radroots_sql_core", + "radroots_sql_wasm_core", "serde", "serde-wasm-bindgen", "serde_json", @@ -2562,17 +2562,17 @@ dependencies = [ ] [[package]] -name = "radroots-replica-sync" +name = "radroots_replica_sync" version = "0.1.0-alpha.1" dependencies = [ "base64 0.22.1", "hex", - "radroots-events", - "radroots-events-codec", - "radroots-replica-db", - "radroots-replica-db-schema", - "radroots-sql-core", - "radroots-types", + "radroots_events", + "radroots_events_codec", + "radroots_replica_db", + "radroots_replica_db_schema", + "radroots_sql_core", + "radroots_types", "serde", "serde_json", "sha2", @@ -2580,14 +2580,14 @@ dependencies = [ ] [[package]] -name = "radroots-replica-sync-wasm" +name = "radroots_replica_sync_wasm" version = "0.1.0-alpha.1" dependencies = [ "base64 0.22.1", - "radroots-events", - "radroots-replica-sync", - "radroots-sql-core", - "radroots-sql-wasm-core", + "radroots_events", + "radroots_replica_sync", + "radroots_sql_core", + "radroots_sql_wasm_core", "serde", "serde-wasm-bindgen", "serde_json", @@ -2596,7 +2596,7 @@ dependencies = [ ] [[package]] -name = "radroots-runtime" +name = "radroots_runtime" version = "0.1.0-alpha.1" dependencies = [ "anyhow", @@ -2604,10 +2604,10 @@ dependencies = [ "clap", "config", "getrandom 0.2.17", - "radroots-log", - "radroots-protected-store", - "radroots-runtime-paths", - "radroots-secret-vault", + "radroots_log", + "radroots_protected_store", + "radroots_runtime_paths", + "radroots_secret_vault", "serde", "serde_json", "tempfile", @@ -2619,7 +2619,7 @@ dependencies = [ ] [[package]] -name = "radroots-runtime-distribution" +name = "radroots_runtime_distribution" version = "0.1.0-alpha.1" dependencies = [ "serde", @@ -2628,10 +2628,10 @@ dependencies = [ ] [[package]] -name = "radroots-runtime-manager" +name = "radroots_runtime_manager" version = "0.1.0-alpha.1" dependencies = [ - "radroots-runtime-paths", + "radroots_runtime_paths", "serde", "tempfile", "thiserror 1.0.69", @@ -2639,48 +2639,48 @@ dependencies = [ ] [[package]] -name = "radroots-runtime-paths" +name = "radroots_runtime_paths" version = "0.1.0-alpha.1" dependencies = [ "thiserror 1.0.69", ] [[package]] -name = "radroots-secret-vault" +name = "radroots_secret_vault" version = "0.1.0-alpha.1" dependencies = [ "keyring", ] [[package]] -name = "radroots-simplex-agent-proto" +name = "radroots_simplex_agent_proto" version = "0.1.0-alpha.1" dependencies = [ - "radroots-simplex-smp-crypto", - "radroots-simplex-smp-proto", + "radroots_simplex_smp_crypto", + "radroots_simplex_smp_proto", ] [[package]] -name = "radroots-simplex-agent-runtime" +name = "radroots_simplex_agent_runtime" version = "0.1.0-alpha.1" dependencies = [ "base64 0.22.1", - "radroots-simplex-agent-proto", - "radroots-simplex-agent-store", - "radroots-simplex-smp-crypto", - "radroots-simplex-smp-proto", - "radroots-simplex-smp-transport", + "radroots_simplex_agent_proto", + "radroots_simplex_agent_store", + "radroots_simplex_smp_crypto", + "radroots_simplex_smp_proto", + "radroots_simplex_smp_transport", "sha2", "tempfile", ] [[package]] -name = "radroots-simplex-agent-store" +name = "radroots_simplex_agent_store" version = "0.1.0-alpha.1" dependencies = [ - "radroots-simplex-agent-proto", - "radroots-simplex-smp-crypto", - "radroots-simplex-smp-proto", + "radroots_simplex_agent_proto", + "radroots_simplex_smp_crypto", + "radroots_simplex_smp_proto", "serde", "serde_json", "sha2", @@ -2688,7 +2688,7 @@ dependencies = [ ] [[package]] -name = "radroots-simplex-chat-proto" +name = "radroots_simplex_chat_proto" version = "0.1.0-alpha.1" dependencies = [ "base64 0.22.1", @@ -2698,45 +2698,45 @@ dependencies = [ ] [[package]] -name = "radroots-simplex-interop-tests" +name = "radroots_simplex_interop_tests" version = "0.1.0-alpha.1" dependencies = [ - "radroots-simplex-agent-proto", - "radroots-simplex-agent-runtime", - "radroots-simplex-chat-proto", - "radroots-simplex-smp-crypto", - "radroots-simplex-smp-proto", - "radroots-simplex-smp-transport", + "radroots_simplex_agent_proto", + "radroots_simplex_agent_runtime", + "radroots_simplex_chat_proto", + "radroots_simplex_smp_crypto", + "radroots_simplex_smp_proto", + "radroots_simplex_smp_transport", "serde_json", ] [[package]] -name = "radroots-simplex-smp-crypto" +name = "radroots_simplex_smp_crypto" version = "0.1.0-alpha.1" dependencies = [ "ed25519-dalek", "getrandom 0.2.17", "hkdf", - "radroots-simplex-smp-proto", + "radroots_simplex_smp_proto", "sha2", "x25519-dalek", "xsalsa20poly1305", ] [[package]] -name = "radroots-simplex-smp-proto" +name = "radroots_simplex_smp_proto" version = "0.1.0-alpha.1" dependencies = [ "base64 0.22.1", ] [[package]] -name = "radroots-simplex-smp-transport" +name = "radroots_simplex_smp_transport" version = "0.1.0-alpha.1" dependencies = [ "base64 0.22.1", - "radroots-simplex-smp-crypto", - "radroots-simplex-smp-proto", + "radroots_simplex_smp_crypto", + "radroots_simplex_smp_proto", "rcgen", "rustls", "sha2", @@ -2744,11 +2744,11 @@ dependencies = [ ] [[package]] -name = "radroots-sql-core" +name = "radroots_sql_core" version = "0.1.0-alpha.1" dependencies = [ "chrono", - "radroots-sql-wasm-bridge", + "radroots_sql_wasm_bridge", "rusqlite", "serde", "serde-wasm-bindgen", @@ -2759,7 +2759,7 @@ dependencies = [ ] [[package]] -name = "radroots-sql-wasm-bridge" +name = "radroots_sql_wasm_bridge" version = "0.1.0-alpha.1" dependencies = [ "js-sys", @@ -2767,13 +2767,13 @@ dependencies = [ ] [[package]] -name = "radroots-sql-wasm-core" +name = "radroots_sql_wasm_core" version = "0.1.0-alpha.1" dependencies = [ "chrono", "js-sys", - "radroots-sql-core", - "radroots-sql-wasm-bridge", + "radroots_sql_core", + "radroots_sql_wasm_bridge", "rusqlite", "serde", "serde-wasm-bindgen", @@ -2785,23 +2785,23 @@ dependencies = [ ] [[package]] -name = "radroots-test-fixtures" +name = "radroots_test_fixtures" version = "0.1.0-alpha.1" [[package]] -name = "radroots-trade" +name = "radroots_trade" version = "0.1.0-alpha.1" dependencies = [ - "radroots-core", - "radroots-events", - "radroots-events-codec", + "radroots_core", + "radroots_events", + "radroots_events_codec", "serde", "serde_json", "ts-rs", ] [[package]] -name = "radroots-types" +name = "radroots_types" version = "0.1.0-alpha.1" dependencies = [ "serde", diff --git a/Cargo.toml b/Cargo.toml @@ -2,45 +2,45 @@ members = [ "crates/core", "crates/events", - "crates/events-codec", - "crates/events-codec-wasm", - "crates/events-indexed", + "crates/events_codec", + "crates/events_codec_wasm", + "crates/events_indexed", "crates/geocoder", "crates/identity", "crates/log", "crates/net", - "crates/net-core", + "crates/net_core", "crates/nostr", - "crates/nostr-accounts", - "crates/nostr-connect", - "crates/nostr-signer", - "crates/nostr-ndb", - "crates/nostr-runtime", + "crates/nostr_accounts", + "crates/nostr_connect", + "crates/nostr_signer", + "crates/nostr_ndb", + "crates/nostr_runtime", "crates/runtime", - "crates/secret-vault", - "crates/simplex-agent-proto", - "crates/simplex-agent-runtime", - "crates/simplex-agent-store", - "crates/simplex-chat-proto", - "crates/simplex-interop-tests", - "crates/simplex-smp-crypto", - "crates/simplex-smp-proto", - "crates/simplex-smp-transport", - "crates/sql-wasm-bridge", - "crates/sql-wasm-core", - "crates/sql-core", - "crates/test-fixtures", - "crates/replica-db-schema", - "crates/replica-sync", - "crates/replica-sync-wasm", - "crates/replica-db", - "crates/replica-db-wasm", - "crates/runtime-paths", - "crates/runtime-distribution", - "crates/runtime-manager", + "crates/secret_vault", + "crates/simplex_agent_proto", + "crates/simplex_agent_runtime", + "crates/simplex_agent_store", + "crates/simplex_chat_proto", + "crates/simplex_interop_tests", + "crates/simplex_smp_crypto", + "crates/simplex_smp_proto", + "crates/simplex_smp_transport", + "crates/sql_wasm_bridge", + "crates/sql_wasm_core", + "crates/sql_core", + "crates/test_fixtures", + "crates/replica_db_schema", + "crates/replica_sync", + "crates/replica_sync_wasm", + "crates/replica_db", + "crates/replica_db_wasm", + "crates/runtime_paths", + "crates/runtime_distribution", + "crates/runtime_manager", "crates/trade", "crates/types", - "crates/protected-store", + "crates/protected_store", "crates/xtask", ] resolver = "2" @@ -55,46 +55,46 @@ homepage = "https://radroots.org" readme = "README" [workspace.dependencies] -radroots-core = { path = "crates/core", version = "0.1.0-alpha.1", default-features = false } -radroots-events = { path = "crates/events", version = "0.1.0-alpha.1", default-features = false } -radroots-events-codec = { path = "crates/events-codec", version = "0.1.0-alpha.1", default-features = false } -radroots-events-indexed = { path = "crates/events-indexed", version = "0.1.0-alpha.1", default-features = false } -radroots-geocoder = { path = "crates/geocoder", version = "0.1.0-alpha.1" } -radroots-identity = { path = "crates/identity", version = "0.1.0-alpha.1", default-features = false } -radroots-nostr = { path = "crates/nostr", version = "0.1.0-alpha.1", default-features = false } -radroots-nostr-accounts = { path = "crates/nostr-accounts", version = "0.1.0-alpha.1", default-features = false } -radroots-nostr-connect = { path = "crates/nostr-connect", version = "0.1.0-alpha.1", default-features = false } -radroots-nostr-signer = { path = "crates/nostr-signer", version = "0.1.0-alpha.1", default-features = false } -radroots-nostr-ndb = { path = "crates/nostr-ndb", version = "0.1.0-alpha.1", default-features = false } -radroots-runtime = { path = "crates/runtime", version = "0.1.0-alpha.1", default-features = false } -radroots-runtime-paths = { path = "crates/runtime-paths", version = "0.1.0-alpha.1", default-features = false } -radroots-runtime-distribution = { path = "crates/runtime-distribution", version = "0.1.0-alpha.1", default-features = false } -radroots-runtime-manager = { path = "crates/runtime-manager", version = "0.1.0-alpha.1", default-features = false } -radroots-log = { path = "crates/log", version = "0.1.0-alpha.1", default-features = false } -radroots-net = { path = "crates/net", version = "0.1.0-alpha.1", default-features = false } -radroots-net-core = { path = "crates/net-core", version = "0.1.0-alpha.1", default-features = false } -radroots-nostr-runtime = { path = "crates/nostr-runtime", version = "0.1.0-alpha.1", default-features = false } -radroots-simplex-agent-proto = { path = "crates/simplex-agent-proto", version = "0.1.0-alpha.1", default-features = false } -radroots-simplex-agent-runtime = { path = "crates/simplex-agent-runtime", version = "0.1.0-alpha.1", default-features = false } -radroots-simplex-agent-store = { path = "crates/simplex-agent-store", version = "0.1.0-alpha.1", default-features = false } -radroots-simplex-chat-proto = { path = "crates/simplex-chat-proto", version = "0.1.0-alpha.1", default-features = false } -radroots-simplex-interop-tests = { path = "crates/simplex-interop-tests", version = "0.1.0-alpha.1", default-features = false } -radroots-simplex-smp-crypto = { path = "crates/simplex-smp-crypto", version = "0.1.0-alpha.1", default-features = false } -radroots-simplex-smp-proto = { path = "crates/simplex-smp-proto", version = "0.1.0-alpha.1", default-features = false } -radroots-simplex-smp-transport = { path = "crates/simplex-smp-transport", version = "0.1.0-alpha.1", default-features = false } -radroots-sql-wasm-bridge = { path = "crates/sql-wasm-bridge", version = "0.1.0-alpha.1" } -radroots-sql-wasm-core = { path = "crates/sql-wasm-core", version = "0.1.0-alpha.1", default-features = false } -radroots-sql-core = { path = "crates/sql-core", version = "0.1.0-alpha.1" } -radroots-test-fixtures = { path = "crates/test-fixtures", version = "0.1.0-alpha.1" } -radroots-replica-db-schema = { path = "crates/replica-db-schema", version = "0.1.0-alpha.1", default-features = false } -radroots-replica-sync = { path = "crates/replica-sync", version = "0.1.0-alpha.1", default-features = false } -radroots-replica-db = { path = "crates/replica-db", version = "0.1.0-alpha.1", default-features = false } -radroots-replica-db-wasm = { path = "crates/replica-db-wasm", version = "0.1.0-alpha.1" } -radroots-replica-sync-wasm = { path = "crates/replica-sync-wasm", version = "0.1.0-alpha.1" } -radroots-trade = { path = "crates/trade", version = "0.1.0-alpha.1", default-features = false } -radroots-types = { path = "crates/types", version = "0.1.0-alpha.1", default-features = false } -radroots-protected-store = { path = "crates/protected-store", version = "0.1.0-alpha.1", default-features = false } -radroots-secret-vault = { path = "crates/secret-vault", version = "0.1.0-alpha.1", default-features = false } +radroots_core = { path = "crates/core", version = "0.1.0-alpha.1", default-features = false } +radroots_events = { path = "crates/events", version = "0.1.0-alpha.1", default-features = false } +radroots_events_codec = { path = "crates/events_codec", version = "0.1.0-alpha.1", default-features = false } +radroots_events_indexed = { path = "crates/events_indexed", version = "0.1.0-alpha.1", default-features = false } +radroots_geocoder = { path = "crates/geocoder", version = "0.1.0-alpha.1" } +radroots_identity = { path = "crates/identity", version = "0.1.0-alpha.1", default-features = false } +radroots_nostr = { path = "crates/nostr", version = "0.1.0-alpha.1", default-features = false } +radroots_nostr_accounts = { path = "crates/nostr_accounts", version = "0.1.0-alpha.1", default-features = false } +radroots_nostr_connect = { path = "crates/nostr_connect", version = "0.1.0-alpha.1", default-features = false } +radroots_nostr_signer = { path = "crates/nostr_signer", version = "0.1.0-alpha.1", default-features = false } +radroots_nostr_ndb = { path = "crates/nostr_ndb", version = "0.1.0-alpha.1", default-features = false } +radroots_runtime = { path = "crates/runtime", version = "0.1.0-alpha.1", default-features = false } +radroots_runtime_paths = { path = "crates/runtime_paths", version = "0.1.0-alpha.1", default-features = false } +radroots_runtime_distribution = { path = "crates/runtime_distribution", version = "0.1.0-alpha.1", default-features = false } +radroots_runtime_manager = { path = "crates/runtime_manager", version = "0.1.0-alpha.1", default-features = false } +radroots_log = { path = "crates/log", version = "0.1.0-alpha.1", default-features = false } +radroots_net = { path = "crates/net", version = "0.1.0-alpha.1", default-features = false } +radroots_net_core = { path = "crates/net_core", version = "0.1.0-alpha.1", default-features = false } +radroots_nostr_runtime = { path = "crates/nostr_runtime", version = "0.1.0-alpha.1", default-features = false } +radroots_simplex_agent_proto = { path = "crates/simplex_agent_proto", version = "0.1.0-alpha.1", default-features = false } +radroots_simplex_agent_runtime = { path = "crates/simplex_agent_runtime", version = "0.1.0-alpha.1", default-features = false } +radroots_simplex_agent_store = { path = "crates/simplex_agent_store", version = "0.1.0-alpha.1", default-features = false } +radroots_simplex_chat_proto = { path = "crates/simplex_chat_proto", version = "0.1.0-alpha.1", default-features = false } +radroots_simplex_interop_tests = { path = "crates/simplex_interop_tests", version = "0.1.0-alpha.1", default-features = false } +radroots_simplex_smp_crypto = { path = "crates/simplex_smp_crypto", version = "0.1.0-alpha.1", default-features = false } +radroots_simplex_smp_proto = { path = "crates/simplex_smp_proto", version = "0.1.0-alpha.1", default-features = false } +radroots_simplex_smp_transport = { path = "crates/simplex_smp_transport", version = "0.1.0-alpha.1", default-features = false } +radroots_sql_wasm_bridge = { path = "crates/sql_wasm_bridge", version = "0.1.0-alpha.1" } +radroots_sql_wasm_core = { path = "crates/sql_wasm_core", version = "0.1.0-alpha.1", default-features = false } +radroots_sql_core = { path = "crates/sql_core", version = "0.1.0-alpha.1" } +radroots_test_fixtures = { path = "crates/test_fixtures", version = "0.1.0-alpha.1" } +radroots_replica_db_schema = { path = "crates/replica_db_schema", version = "0.1.0-alpha.1", default-features = false } +radroots_replica_sync = { path = "crates/replica_sync", version = "0.1.0-alpha.1", default-features = false } +radroots_replica_db = { path = "crates/replica_db", version = "0.1.0-alpha.1", default-features = false } +radroots_replica_db_wasm = { path = "crates/replica_db_wasm", version = "0.1.0-alpha.1" } +radroots_replica_sync_wasm = { path = "crates/replica_sync_wasm", version = "0.1.0-alpha.1" } +radroots_trade = { path = "crates/trade", version = "0.1.0-alpha.1", default-features = false } +radroots_types = { path = "crates/types", version = "0.1.0-alpha.1", default-features = false } +radroots_protected_store = { path = "crates/protected_store", version = "0.1.0-alpha.1", default-features = false } +radroots_secret_vault = { path = "crates/secret_vault", version = "0.1.0-alpha.1", default-features = false } anyhow = { version = "1" } base64 = { version = "0.22" } diff --git a/Makefile b/Makefile @@ -29,13 +29,13 @@ export-ts-sdk-bindings: cargo run -q -p xtask -- sdk export-ts build-replica-db-wasm: - wasm-pack build crates/replica-db-wasm --release --target web \ + wasm-pack build crates/replica_db_wasm --release --target web \ --out-dir ../replica-db-wasm/pkg/dist --scope radroots build-events-codec-wasm: - wasm-pack build crates/events-codec-wasm --release --target web \ + wasm-pack build crates/events_codec_wasm --release --target web \ --out-dir ../events-codec-wasm/pkg/dist --scope radroots build-replica-sync-wasm: - wasm-pack build crates/replica-sync-wasm --release --target web \ + wasm-pack build crates/replica_sync_wasm --release --target web \ --out-dir ../replica-sync-wasm/pkg/dist --scope radroots diff --git a/contract/README.md b/contract/README.md @@ -11,9 +11,9 @@ It defines the public interoperability boundary for external integrators, keeps Contract metadata is defined in `contract/manifest.toml` and currently includes: -- model crates: `radroots-core`, `radroots-types`, `radroots-events`, `radroots-trade`, `radroots-identity` -- algorithm crate: `radroots-events-codec` -- wasm crate: `radroots-events-codec-wasm` +- model crates: `radroots_core`, `radroots_types`, `radroots_events`, `radroots_trade`, `radroots_identity` +- algorithm crate: `radroots_events_codec` +- wasm crate: `radroots_events_codec_wasm` Public SDK exports are intentionally narrower than the full Rust workspace. @@ -35,11 +35,11 @@ Replica contract metadata is defined in `contract/replica.toml`. Internal replica crate family: -- `radroots-replica-db-schema` -- `radroots-replica-db` -- `radroots-replica-db-wasm` -- `radroots-replica-sync` -- `radroots-replica-sync-wasm` +- `radroots_replica_db_schema` +- `radroots_replica_db` +- `radroots_replica_db_wasm` +- `radroots_replica_sync` +- `radroots_replica_sync_wasm` ## Governance diff --git a/contract/coverage/policy.toml b/contract/coverage/policy.toml @@ -7,40 +7,46 @@ require_branches = true [required] crates = [ - "radroots-core", - "radroots-types", - "radroots-events", - "radroots-identity", - "radroots-trade", - "radroots-events-codec", - "radroots-events-codec-wasm", - "radroots-replica-db-schema", + "radroots_core", + "radroots_types", + "radroots_events", + "radroots_identity", + "radroots_trade", + "radroots_events_codec", + "radroots_events_codec_wasm", + "radroots_replica_db_schema", "xtask", - "radroots-events-indexed", - "radroots-geocoder", - "radroots-log", - "radroots-net-core", - "radroots-net", - "radroots-nostr", - "radroots-nostr-accounts", - "radroots-nostr-connect", - "radroots-nostr-signer", - "radroots-nostr-ndb", - "radroots-nostr-runtime", - "radroots-protected-store", - "radroots-runtime", - "radroots-runtime-paths", - "radroots-runtime-distribution", - "radroots-runtime-manager", - "radroots-secret-vault", - "radroots-simplex-chat-proto", - "radroots-simplex-smp-proto", - "radroots-sql-core", - "radroots-sql-wasm-core", - "radroots-sql-wasm-bridge", - "radroots-test-fixtures", - "radroots-replica-sync", - "radroots-replica-db", - "radroots-replica-sync-wasm", - "radroots-replica-db-wasm", + "radroots_events_indexed", + "radroots_geocoder", + "radroots_log", + "radroots_net_core", + "radroots_net", + "radroots_nostr", + "radroots_nostr_accounts", + "radroots_nostr_connect", + "radroots_nostr_signer", + "radroots_nostr_ndb", + "radroots_nostr_runtime", + "radroots_protected_store", + "radroots_runtime", + "radroots_runtime_paths", + "radroots_runtime_distribution", + "radroots_runtime_manager", + "radroots_secret_vault", + "radroots_simplex_agent_proto", + "radroots_simplex_agent_runtime", + "radroots_simplex_agent_store", + "radroots_simplex_chat_proto", + "radroots_simplex_interop_tests", + "radroots_simplex_smp_crypto", + "radroots_simplex_smp_proto", + "radroots_simplex_smp_transport", + "radroots_sql_core", + "radroots_sql_wasm_core", + "radroots_sql_wasm_bridge", + "radroots_test_fixtures", + "radroots_replica_sync", + "radroots_replica_db", + "radroots_replica_sync_wasm", + "radroots_replica_db_wasm", ] diff --git a/contract/coverage/profiles.toml b/contract/coverage/profiles.toml @@ -3,17 +3,17 @@ no_default_features = false features = [] test_threads = 1 -[profiles.crates."radroots-log"] +[profiles.crates."radroots_log"] no_default_features = true features = [] test_threads = 1 -[profiles.crates."radroots-nostr-runtime"] +[profiles.crates."radroots_nostr_runtime"] no_default_features = true features = ["std"] test_threads = 1 -[profiles.crates."radroots-sql-wasm-core"] +[profiles.crates."radroots_sql_wasm_core"] no_default_features = true features = ["embedded"] test_threads = 1 diff --git a/contract/exports/kotlin.toml b/contract/exports/kotlin.toml @@ -3,11 +3,11 @@ id = "kotlin" repository = "sdk-kotlin" [packages] -"radroots-core" = "radroots.core" -"radroots-types" = "radroots.types" -"radroots-events" = "radroots.events" -"radroots-trade" = "radroots.trade" -"radroots-identity" = "radroots.identity" +"radroots_core" = "radroots.core" +"radroots_types" = "radroots.types" +"radroots_events" = "radroots.events" +"radroots_trade" = "radroots.trade" +"radroots_identity" = "radroots.identity" [artifacts] models_dir = "src/generated" diff --git a/contract/exports/py.toml b/contract/exports/py.toml @@ -3,11 +3,11 @@ id = "py" repository = "sdk-python" [packages] -"radroots-core" = "radroots_core" -"radroots-types" = "radroots_types" -"radroots-events" = "radroots_events" -"radroots-trade" = "radroots_trade" -"radroots-identity" = "radroots_identity" +"radroots_core" = "radroots_core" +"radroots_types" = "radroots_types" +"radroots_events" = "radroots_events" +"radroots_trade" = "radroots_trade" +"radroots_identity" = "radroots_identity" [artifacts] models_dir = "src/generated" diff --git a/contract/exports/swift.toml b/contract/exports/swift.toml @@ -3,11 +3,11 @@ id = "swift" repository = "sdk-swift" [packages] -"radroots-core" = "RadrootsCore" -"radroots-types" = "RadrootsTypes" -"radroots-events" = "RadrootsEvents" -"radroots-trade" = "RadrootsTrade" -"radroots-identity" = "RadrootsIdentity" +"radroots_core" = "RadrootsCore" +"radroots_types" = "RadrootsTypes" +"radroots_events" = "RadrootsEvents" +"radroots_trade" = "RadrootsTrade" +"radroots_identity" = "RadrootsIdentity" [artifacts] models_dir = "Sources/Generated" diff --git a/contract/exports/ts.toml b/contract/exports/ts.toml @@ -3,12 +3,12 @@ id = "ts" repository = "sdk-typescript" [packages] -"radroots-core" = "@radroots/core" -"radroots-types" = "@radroots/types" -"radroots-events" = "@radroots/events" -"radroots-trade" = "@radroots/trade" -"radroots-identity" = "@radroots/identity" -"radroots-events-codec-wasm" = "@radroots/events-codec-wasm" +"radroots_core" = "@radroots/core" +"radroots_types" = "@radroots/types" +"radroots_events" = "@radroots/events" +"radroots_trade" = "@radroots/trade" +"radroots_identity" = "@radroots/identity" +"radroots_events_codec_wasm" = "@radroots/events-codec-wasm" [artifacts] models_dir = "src/generated" diff --git a/contract/manifest.toml b/contract/manifest.toml @@ -5,21 +5,21 @@ source = "rust" [surface] model_crates = [ - "radroots-core", - "radroots-types", - "radroots-events", - "radroots-trade", - "radroots-identity", + "radroots_core", + "radroots_types", + "radroots_events", + "radroots_trade", + "radroots_identity", ] -algorithm_crates = ["radroots-events-codec"] -wasm_crates = ["radroots-events-codec-wasm"] +algorithm_crates = ["radroots_events_codec"] +wasm_crates = ["radroots_events_codec_wasm"] [surface.internal_replica_crates] -schema = "radroots-replica-db-schema" -storage = "radroots-replica-db" -storage_wasm = "radroots-replica-db-wasm" -sync = "radroots-replica-sync" -sync_wasm = "radroots-replica-sync-wasm" +schema = "radroots_replica_db_schema" +storage = "radroots_replica_db" +storage_wasm = "radroots_replica_db_wasm" +sync = "radroots_replica_sync" +sync_wasm = "radroots_replica_sync_wasm" [export.ts] packages = [ diff --git a/contract/release/publish-set.toml b/contract/release/publish-set.toml @@ -3,79 +3,88 @@ version = "0.1.0-alpha.1" [publish] crates = [ - "radroots-core", - "radroots-types", - "radroots-events", - "radroots-log", - "radroots-protected-store", - "radroots-runtime", - "radroots-runtime-paths", - "radroots-runtime-distribution", - "radroots-runtime-manager", - "radroots-secret-vault", - "radroots-simplex-chat-proto", - "radroots-simplex-smp-proto", - "radroots-identity", - "radroots-trade", - "radroots-events-codec", - "radroots-events-codec-wasm", - "radroots-events-indexed", - "radroots-geocoder", - "radroots-nostr", - "radroots-nostr-connect", - "radroots-nostr-signer", - "radroots-replica-db-schema", - "radroots-sql-wasm-bridge", - "radroots-nostr-runtime", - "radroots-sql-core", - "radroots-nostr-ndb", - "radroots-replica-db", - "radroots-sql-wasm-core", - "radroots-nostr-accounts", - "radroots-replica-sync", - "radroots-net-core", - "radroots-replica-db-wasm", - "radroots-replica-sync-wasm", - "radroots-net", + "radroots_core", + "radroots_types", + "radroots_events", + "radroots_log", + "radroots_protected_store", + "radroots_runtime", + "radroots_runtime_paths", + "radroots_runtime_distribution", + "radroots_runtime_manager", + "radroots_secret_vault", + "radroots_simplex_chat_proto", + "radroots_simplex_smp_proto", + "radroots_identity", + "radroots_trade", + "radroots_events_codec", + "radroots_events_codec_wasm", + "radroots_events_indexed", + "radroots_geocoder", + "radroots_nostr", + "radroots_nostr_connect", + "radroots_sql_core", + "radroots_nostr_signer", + "radroots_replica_db_schema", + "radroots_sql_wasm_bridge", + "radroots_nostr_runtime", + "radroots_nostr_ndb", + "radroots_replica_db", + "radroots_sql_wasm_core", + "radroots_nostr_accounts", + "radroots_replica_sync", + "radroots_net_core", + "radroots_replica_db_wasm", + "radroots_replica_sync_wasm", + "radroots_net", ] [internal] -crates = ["xtask", "radroots-test-fixtures"] +crates = [ + "xtask", + "radroots_test_fixtures", + "radroots_simplex_agent_proto", + "radroots_simplex_agent_runtime", + "radroots_simplex_agent_store", + "radroots_simplex_interop_tests", + "radroots_simplex_smp_crypto", + "radroots_simplex_smp_transport", +] [publish_order] crates = [ - "radroots-core", - "radroots-types", - "radroots-log", - "radroots-events", - "radroots-protected-store", - "radroots-runtime", - "radroots-runtime-paths", - "radroots-runtime-distribution", - "radroots-runtime-manager", - "radroots-secret-vault", - "radroots-simplex-chat-proto", - "radroots-simplex-smp-proto", - "radroots-events-codec", - "radroots-identity", - "radroots-trade", - "radroots-events-codec-wasm", - "radroots-events-indexed", - "radroots-geocoder", - "radroots-nostr", - "radroots-nostr-connect", - "radroots-nostr-signer", - "radroots-replica-db-schema", - "radroots-sql-wasm-bridge", - "radroots-nostr-runtime", - "radroots-sql-core", - "radroots-nostr-ndb", - "radroots-replica-db", - "radroots-sql-wasm-core", - "radroots-nostr-accounts", - "radroots-replica-sync", - "radroots-net-core", - "radroots-replica-db-wasm", - "radroots-replica-sync-wasm", - "radroots-net", + "radroots_core", + "radroots_events", + "radroots_log", + "radroots_types", + "radroots_runtime_paths", + "radroots_runtime_distribution", + "radroots_runtime_manager", + "radroots_secret_vault", + "radroots_protected_store", + "radroots_runtime", + "radroots_simplex_chat_proto", + "radroots_simplex_smp_proto", + "radroots_identity", + "radroots_events_codec", + "radroots_trade", + "radroots_events_codec_wasm", + "radroots_events_indexed", + "radroots_geocoder", + "radroots_nostr", + "radroots_nostr_connect", + "radroots_replica_db_schema", + "radroots_sql_wasm_bridge", + "radroots_sql_core", + "radroots_nostr_signer", + "radroots_nostr_runtime", + "radroots_nostr_ndb", + "radroots_replica_db", + "radroots_sql_wasm_core", + "radroots_nostr_accounts", + "radroots_replica_sync", + "radroots_net_core", + "radroots_replica_db_wasm", + "radroots_replica_sync_wasm", + "radroots_net", ] diff --git a/contract/replica.toml b/contract/replica.toml @@ -4,11 +4,11 @@ version = "0.1.0" purpose = "offline-first local replica state and deterministic sync" [crate_family] -schema = "radroots-replica-db-schema" -storage = "radroots-replica-db" -storage_wasm = "radroots-replica-db-wasm" -sync = "radroots-replica-sync" -sync_wasm = "radroots-replica-sync-wasm" +schema = "radroots_replica_db_schema" +storage = "radroots_replica_db" +storage_wasm = "radroots_replica_db_wasm" +sync = "radroots_replica_sync" +sync_wasm = "radroots_replica_sync_wasm" [policy] transport_agnostic_sync_core = true diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "radroots-core" +name = "radroots_core" version = "0.1.0-alpha.1" edition.workspace = true authors = [ @@ -10,7 +10,7 @@ license.workspace = true description = "core value types and unit semantics for radroots sdk contracts" repository.workspace = true homepage.workspace = true -documentation = "https://docs.rs/radroots-core" +documentation = "https://docs.rs/radroots_core" readme.workspace = true build = "build.rs" diff --git a/crates/core/README.md b/crates/core/README.md @@ -1,4 +1,4 @@ -# radroots-core +# radroots_core Core value types and unit semantics for the Rad Roots SDK. diff --git a/crates/events-codec-wasm/Cargo.toml b/crates/events-codec-wasm/Cargo.toml @@ -1,35 +0,0 @@ -[package] -name = "radroots-events-codec-wasm" -version = "0.1.0-alpha.1" -edition.workspace = true -authors = [ - "Radroots Authors", -] -rust-version.workspace = true -license.workspace = true -description = "wasm bindings for radroots event encoding and decoding" -repository.workspace = true -homepage.workspace = true -documentation = "https://docs.rs/radroots-events-codec-wasm" -readme.workspace = true - -[lib] -crate-type = ["cdylib", "rlib"] - -[dependencies] -radroots-events = { workspace = true, default-features = false, features = [ - "std", - "serde", -] } -radroots-events-codec = { workspace = true, default-features = false, features = [ - "std", - "serde_json", -] } -serde = { workspace = true } -serde_json = { workspace = true } -wasm-bindgen = { workspace = true } - -[dev-dependencies] -radroots-core = { workspace = true, default-features = false, features = [ - "std", -] } diff --git a/crates/events-codec-wasm/README.md b/crates/events-codec-wasm/README.md @@ -1,14 +0,0 @@ -# radroots-events-codec-wasm - -Wasm bindings for Rad Roots event encoding and decoding. - -## Goals - -- define stable wasm bindings for `radroots-events-codec` -- keep tag generation and JSON conversion behavior deterministic in web runtimes -- support typed event-tag builders across listing, message, profile, and job flows -- provide reusable codec bridge functions for JavaScript integrations - -## License - -Licensed under AGPL-3.0. See LICENSE. diff --git a/crates/events-codec/Cargo.toml b/crates/events-codec/Cargo.toml @@ -1,35 +0,0 @@ -[package] -name = "radroots-events-codec" -version = "0.1.0-alpha.1" -edition.workspace = true -authors = [ - "Radroots Authors", -] -rust-version.workspace = true -license.workspace = true -description = "canonical encoders and decoders for radroots nostr event payloads" -repository.workspace = true -homepage.workspace = true -documentation = "https://docs.rs/radroots-events-codec" -readme.workspace = true - -[features] -default = ["std"] -std = ["radroots-core/std", "radroots-events/std"] -serde = ["dep:serde", "radroots-core/serde", "radroots-events/serde"] -serde_json = ["serde", "dep:serde_json"] -nostr = ["dep:nostr", "std"] - -[dependencies] -radroots-core = { workspace = true, default-features = false } -radroots-events = { workspace = true, default-features = false } -serde = { workspace = true, default-features = false, features = [ - "alloc", -], optional = true } -serde_json = { workspace = true, default-features = false, features = [ - "alloc", -], optional = true } -nostr = { workspace = true, optional = true } - -[dev-dependencies] -radroots-test-fixtures = { workspace = true } diff --git a/crates/events-codec/README.md b/crates/events-codec/README.md @@ -1,14 +0,0 @@ -# radroots-events-codec - -Reference encoders and decoders for Rad Roots Nostr event payloads. - -## Goals - -- define stable encoding and decoding interfaces for Rad Roots event payloads -- keep parsing and validation behavior deterministic across supported event forms -- support std and no_std builds with feature-gated `serde_json` and Nostr integration -- provide reusable codec primitives for higher-level Rad Roots crates - -## License - -Licensed under AGPL-3.0. See LICENSE. diff --git a/crates/events-codec/src/trade/decode.rs b/crates/events-codec/src/trade/decode.rs @@ -1,455 +0,0 @@ -#[cfg(all(not(feature = "std"), feature = "serde_json"))] -use alloc::{borrow::ToOwned, format, string::String, vec::Vec}; - -#[cfg(feature = "serde_json")] -use radroots_events::{ - RadrootsNostrEvent, RadrootsNostrEventPtr, - kinds::{KIND_PROFILE, is_trade_kind}, - tags::{TAG_D, TAG_E_PREV, TAG_E_ROOT}, - trade::{RadrootsTradeEnvelope, RadrootsTradeEnvelopeError, RadrootsTradeMessageType}, -}; -#[cfg(feature = "serde_json")] -use serde::de::DeserializeOwned; - -#[cfg(feature = "serde_json")] -use crate::d_tag::is_d_tag_base64url; -#[cfg(feature = "serde_json")] -use crate::trade::tags::{ - TAG_LISTING_EVENT, parse_trade_counterparty_tag, parse_trade_listing_event_tag, - parse_trade_prev_tag, parse_trade_root_tag, -}; - -#[cfg(feature = "serde_json")] -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum RadrootsTradeEnvelopeParseError { - InvalidKind(u32), - InvalidJson, - InvalidEnvelope(RadrootsTradeEnvelopeError), - MessageTypeKindMismatch { - event_kind: u32, - message_type: RadrootsTradeMessageType, - }, - MissingTag(&'static str), - InvalidTag(&'static str), - ListingAddrTagMismatch, - OrderIdTagMismatch, - InvalidListingAddr(RadrootsTradeListingAddressError), -} - -#[cfg(feature = "serde_json")] -impl core::fmt::Display for RadrootsTradeEnvelopeParseError { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - match self { - Self::InvalidKind(kind) => write!(f, "invalid trade event kind: {kind}"), - Self::InvalidJson => write!(f, "invalid trade envelope json"), - Self::InvalidEnvelope(error) => write!(f, "{error}"), - Self::MessageTypeKindMismatch { - event_kind, - message_type, - } => write!( - f, - "trade envelope type {message_type:?} does not match event kind {event_kind}" - ), - Self::MissingTag(tag) => write!(f, "missing required trade tag: {tag}"), - Self::InvalidTag(tag) => write!(f, "invalid trade tag: {tag}"), - Self::ListingAddrTagMismatch => { - write!(f, "trade listing address tag does not match envelope") - } - Self::OrderIdTagMismatch => write!(f, "trade order id tag does not match envelope"), - Self::InvalidListingAddr(error) => write!(f, "{error}"), - } - } -} - -#[cfg(all(feature = "std", feature = "serde_json"))] -impl std::error::Error for RadrootsTradeEnvelopeParseError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - Self::InvalidEnvelope(error) => Some(error), - Self::InvalidListingAddr(error) => Some(error), - _ => None, - } - } -} - -#[cfg(feature = "serde_json")] -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct RadrootsTradeEventContext { - pub counterparty_pubkey: String, - pub listing_event: Option<RadrootsNostrEventPtr>, - pub root_event_id: Option<String>, - pub prev_event_id: Option<String>, -} - -#[cfg(feature = "serde_json")] -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct RadrootsTradeListingAddress { - pub kind: u32, - pub seller_pubkey: String, - pub listing_id: String, -} - -#[cfg(feature = "serde_json")] -impl RadrootsTradeListingAddress { - pub fn parse(addr: &str) -> Result<Self, RadrootsTradeListingAddressError> { - let (kind_raw, seller_and_listing) = addr - .split_once(':') - .ok_or(RadrootsTradeListingAddressError::InvalidFormat)?; - let (seller_pubkey_raw, listing_id_raw) = seller_and_listing - .split_once(':') - .ok_or(RadrootsTradeListingAddressError::InvalidFormat)?; - if listing_id_raw.contains(':') { - return Err(RadrootsTradeListingAddressError::InvalidFormat); - } - let kind = kind_raw - .parse::<u32>() - .map_err(|_| RadrootsTradeListingAddressError::InvalidFormat)?; - let seller_pubkey = seller_pubkey_raw.to_owned(); - let listing_id = listing_id_raw.to_owned(); - if kind == KIND_PROFILE - || seller_pubkey.trim().is_empty() - || listing_id.trim().is_empty() - || !is_d_tag_base64url(&listing_id) - { - return Err(RadrootsTradeListingAddressError::InvalidFormat); - } - Ok(Self { - kind, - seller_pubkey, - listing_id, - }) - } - - #[inline] - pub fn as_str(&self) -> String { - format!("{}:{}:{}", self.kind, self.seller_pubkey, self.listing_id) - } -} - -#[cfg(feature = "serde_json")] -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum RadrootsTradeListingAddressError { - InvalidFormat, -} - -#[cfg(feature = "serde_json")] -impl core::fmt::Display for RadrootsTradeListingAddressError { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - match self { - Self::InvalidFormat => write!(f, "invalid listing address format"), - } - } -} - -#[cfg(all(feature = "std", feature = "serde_json"))] -impl std::error::Error for RadrootsTradeListingAddressError {} - -#[cfg(feature = "serde_json")] -fn required_tag_value<'a>( - tags: &'a [Vec<String>], - key: &'static str, -) -> Result<&'a str, RadrootsTradeEnvelopeParseError> { - let tag = tags - .iter() - .find(|tag| tag.first().map(|value| value.as_str()) == Some(key)) - .ok_or(RadrootsTradeEnvelopeParseError::MissingTag(key))?; - let value = tag - .get(1) - .map(|value| value.as_str()) - .ok_or(RadrootsTradeEnvelopeParseError::InvalidTag(key))?; - if value.trim().is_empty() { - return Err(RadrootsTradeEnvelopeParseError::InvalidTag(key)); - } - Ok(value) -} - -#[cfg(feature = "serde_json")] -pub fn trade_envelope_from_event<T: DeserializeOwned>( - event: &RadrootsNostrEvent, -) -> Result<RadrootsTradeEnvelope<T>, RadrootsTradeEnvelopeParseError> { - if !is_trade_kind(event.kind) { - return Err(RadrootsTradeEnvelopeParseError::InvalidKind(event.kind)); - } - let envelope = serde_json::from_str::<RadrootsTradeEnvelope<T>>(&event.content) - .map_err(|_| RadrootsTradeEnvelopeParseError::InvalidJson)?; - envelope - .validate() - .map_err(RadrootsTradeEnvelopeParseError::InvalidEnvelope)?; - if envelope.message_type.kind() != event.kind { - return Err(RadrootsTradeEnvelopeParseError::MessageTypeKindMismatch { - event_kind: event.kind, - message_type: envelope.message_type, - }); - } - - let listing_addr = required_tag_value(&event.tags, "a")?; - if envelope.listing_addr != listing_addr { - return Err(RadrootsTradeEnvelopeParseError::ListingAddrTagMismatch); - } - RadrootsTradeListingAddress::parse(&envelope.listing_addr) - .map_err(RadrootsTradeEnvelopeParseError::InvalidListingAddr)?; - - if let Some(order_id) = envelope.order_id.as_deref() { - let tag_order_id = required_tag_value(&event.tags, TAG_D)?; - if tag_order_id != order_id { - return Err(RadrootsTradeEnvelopeParseError::OrderIdTagMismatch); - } - } - - let message_type = envelope.message_type; - trade_event_context_from_tags(message_type, &event.tags)?; - - Ok(envelope) -} - -#[cfg(feature = "serde_json")] -pub fn trade_event_context_from_tags( - message_type: RadrootsTradeMessageType, - tags: &[Vec<String>], -) -> Result<RadrootsTradeEventContext, RadrootsTradeEnvelopeParseError> { - let counterparty_pubkey = - parse_trade_counterparty_tag(tags).map_err(map_tag_parse_error_for_trade_envelope)?; - let listing_event = - parse_trade_listing_event_tag(tags).map_err(map_tag_parse_error_for_trade_envelope)?; - let root_event_id = - parse_trade_root_tag(tags).map_err(map_tag_parse_error_for_trade_envelope)?; - let prev_event_id = - parse_trade_prev_tag(tags).map_err(map_tag_parse_error_for_trade_envelope)?; - - if message_type.requires_listing_snapshot() && listing_event.is_none() { - return Err(RadrootsTradeEnvelopeParseError::MissingTag( - TAG_LISTING_EVENT, - )); - } - if message_type.requires_trade_chain() { - if root_event_id.is_none() { - return Err(RadrootsTradeEnvelopeParseError::MissingTag(TAG_E_ROOT)); - } - if prev_event_id.is_none() { - return Err(RadrootsTradeEnvelopeParseError::MissingTag(TAG_E_PREV)); - } - } - - Ok(RadrootsTradeEventContext { - counterparty_pubkey, - listing_event, - root_event_id, - prev_event_id, - }) -} - -#[cfg(feature = "serde_json")] -fn map_tag_parse_error_for_trade_envelope( - error: crate::error::EventParseError, -) -> RadrootsTradeEnvelopeParseError { - match error { - crate::error::EventParseError::MissingTag(tag) => { - RadrootsTradeEnvelopeParseError::MissingTag(tag) - } - crate::error::EventParseError::InvalidTag(tag) => { - RadrootsTradeEnvelopeParseError::InvalidTag(tag) - } - crate::error::EventParseError::InvalidKind { expected: _, got } => { - RadrootsTradeEnvelopeParseError::InvalidKind(got) - } - crate::error::EventParseError::InvalidNumber(tag, _) - | crate::error::EventParseError::InvalidJson(tag) => { - RadrootsTradeEnvelopeParseError::InvalidTag(tag) - } - } -} - -#[cfg(all(test, feature = "serde_json"))] -mod tests { - use super::{ - RadrootsTradeEnvelopeParseError, RadrootsTradeListingAddress, trade_envelope_from_event, - trade_event_context_from_tags, - }; - use crate::trade::encode::trade_envelope_event_build; - use radroots_events::{ - RadrootsNostrEvent, RadrootsNostrEventPtr, - trade::{ - RadrootsTradeEnvelope, RadrootsTradeMessagePayload, RadrootsTradeMessageType, - RadrootsTradeOrder, RadrootsTradeOrderItem, - }, - }; - - fn base_order() -> RadrootsTradeOrder { - RadrootsTradeOrder { - order_id: "order-1".into(), - listing_addr: "30402:seller:AAAAAAAAAAAAAAAAAAAAAg".into(), - buyer_pubkey: "buyer".into(), - seller_pubkey: "seller".into(), - items: vec![RadrootsTradeOrderItem { - bin_id: "lb".into(), - bin_count: 3, - }], - discounts: None, - } - } - - #[test] - fn listing_address_roundtrips() { - let addr = RadrootsTradeListingAddress::parse("30402:seller:AAAAAAAAAAAAAAAAAAAAAg") - .expect("parse listing address"); - assert_eq!(addr.as_str(), "30402:seller:AAAAAAAAAAAAAAAAAAAAAg"); - } - - #[test] - fn parse_order_request_roundtrip() { - let payload = RadrootsTradeMessagePayload::OrderRequest(base_order()); - let built = trade_envelope_event_build( - "seller", - RadrootsTradeMessageType::OrderRequest, - "30402:seller:AAAAAAAAAAAAAAAAAAAAAg", - Some("order-1".into()), - Some(&RadrootsNostrEventPtr { - id: "listing-snapshot".into(), - relays: None, - }), - None, - None, - &payload, - ) - .expect("build trade envelope"); - let event = RadrootsNostrEvent { - id: "id".into(), - author: "buyer".into(), - created_at: 1, - kind: built.kind, - tags: built.tags, - content: built.content, - sig: "sig".into(), - }; - let envelope: RadrootsTradeEnvelope<RadrootsTradeMessagePayload> = - trade_envelope_from_event(&event).expect("parse trade envelope"); - assert_eq!( - envelope.message_type, - RadrootsTradeMessageType::OrderRequest - ); - assert_eq!(envelope.order_id.as_deref(), Some("order-1")); - } - - #[test] - fn parse_rejects_listing_addr_mismatch() { - let payload = RadrootsTradeMessagePayload::OrderRequest(base_order()); - let built = trade_envelope_event_build( - "seller", - RadrootsTradeMessageType::OrderRequest, - "30402:seller:AAAAAAAAAAAAAAAAAAAAAg", - Some("order-1".into()), - Some(&RadrootsNostrEventPtr { - id: "listing-snapshot".into(), - relays: None, - }), - None, - None, - &payload, - ) - .expect("build trade envelope"); - let mut envelope: RadrootsTradeEnvelope<serde_json::Value> = - serde_json::from_str(&built.content).expect("decode json"); - envelope.listing_addr = "30402:seller:BBBBBBBBBBBBBBBBBBBBBg".into(); - let event = RadrootsNostrEvent { - id: "id".into(), - author: "buyer".into(), - created_at: 1, - kind: built.kind, - tags: built.tags, - content: serde_json::to_string(&envelope).expect("encode json"), - sig: "sig".into(), - }; - let err = trade_envelope_from_event::<serde_json::Value>(&event).unwrap_err(); - assert_eq!(err, RadrootsTradeEnvelopeParseError::ListingAddrTagMismatch); - } - - #[test] - fn parse_rejects_missing_public_snapshot_tag() { - let payload = RadrootsTradeMessagePayload::OrderRequest(base_order()); - let built = trade_envelope_event_build( - "seller", - RadrootsTradeMessageType::OrderRequest, - "30402:seller:AAAAAAAAAAAAAAAAAAAAAg", - Some("order-1".into()), - Some(&RadrootsNostrEventPtr { - id: "listing-snapshot".into(), - relays: None, - }), - None, - None, - &payload, - ) - .expect("build trade envelope"); - let mut event = RadrootsNostrEvent { - id: "id".into(), - author: "buyer".into(), - created_at: 1, - kind: built.kind, - tags: built.tags, - content: built.content, - sig: "sig".into(), - }; - event - .tags - .retain(|tag| tag.first().map(|value| value.as_str()) != Some(TAG_LISTING_EVENT)); - let err = trade_envelope_from_event::<RadrootsTradeMessagePayload>(&event).unwrap_err(); - assert_eq!( - err, - RadrootsTradeEnvelopeParseError::MissingTag(TAG_LISTING_EVENT) - ); - } - - #[test] - fn parse_rejects_missing_public_chain_tags_after_order_request() { - let payload = RadrootsTradeMessagePayload::OrderResponse( - radroots_events::trade::RadrootsTradeOrderResponse { - accepted: true, - reason: None, - }, - ); - let built = trade_envelope_event_build( - "buyer", - RadrootsTradeMessageType::OrderResponse, - "30402:seller:AAAAAAAAAAAAAAAAAAAAAg", - Some("order-1".into()), - None, - Some("root"), - Some("prev"), - &payload, - ) - .expect("build trade envelope"); - let mut event = RadrootsNostrEvent { - id: "id".into(), - author: "seller".into(), - created_at: 1, - kind: built.kind, - tags: built.tags, - content: built.content, - sig: "sig".into(), - }; - event - .tags - .retain(|tag| tag.first().map(|value| value.as_str()) != Some(TAG_E_PREV)); - let err = trade_envelope_from_event::<RadrootsTradeMessagePayload>(&event).unwrap_err(); - assert_eq!(err, RadrootsTradeEnvelopeParseError::MissingTag(TAG_E_PREV)); - } - - #[test] - fn parse_trade_event_context_extracts_public_refs() { - let context = trade_event_context_from_tags( - RadrootsTradeMessageType::OrderResponse, - &[ - vec!["p".into(), "buyer".into()], - vec!["a".into(), "30402:seller:AAAAAAAAAAAAAAAAAAAAAg".into()], - vec![TAG_D.into(), "order-1".into()], - vec![TAG_E_ROOT.into(), "root-id".into()], - vec![TAG_E_PREV.into(), "prev-id".into()], - ], - ) - .expect("event context"); - assert_eq!(context.counterparty_pubkey, "buyer"); - assert_eq!(context.root_event_id.as_deref(), Some("root-id")); - assert_eq!(context.prev_event_id.as_deref(), Some("prev-id")); - assert!(context.listing_event.is_none()); - } -} diff --git a/crates/events-indexed/Cargo.toml b/crates/events-indexed/Cargo.toml @@ -1,30 +0,0 @@ -[package] -name = "radroots-events-indexed" -version = "0.1.0-alpha.1" -edition.workspace = true -authors = [ - "Radroots Authors", -] -rust-version.workspace = true -license.workspace = true -description = "indexed manifest and checkpoint models for radroots event archives" -repository.workspace = true -homepage.workspace = true -documentation = "https://docs.rs/radroots-events-indexed" -readme.workspace = true - -[features] -default = ["serde", "typeshare"] -serde = ["dep:serde"] -typeshare = ["dep:typeshare"] -std = [] - -[dependencies] -serde = { workspace = true, default-features = false, features = [ - "alloc", - "derive", -], optional = true } -typeshare = { workspace = true, optional = true } - -[dev-dependencies] -serde_json = { workspace = true } diff --git a/crates/events-indexed/README.md b/crates/events-indexed/README.md @@ -1,14 +0,0 @@ -# radroots-events-indexed - -Indexed manifest and checkpoint models for Rad Roots event archives. - -## Goals - -- define stable manifest, checkpoint, and shard range models -- keep shard metadata and id-range validation deterministic across targets -- support no_std and std builds with feature-gated serialization and type export -- provide reusable indexed archive primitives for higher-level Rad Roots crates - -## License - -Licensed under AGPL-3.0. See LICENSE. diff --git a/crates/events/Cargo.toml b/crates/events/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "radroots-events" +name = "radroots_events" version = "0.1.0-alpha.1" edition.workspace = true authors = [ @@ -10,19 +10,19 @@ license.workspace = true description = "nostr event models, kinds, and tag helpers for radroots integrations" repository.workspace = true homepage.workspace = true -documentation = "https://docs.rs/radroots-events" +documentation = "https://docs.rs/radroots_events" readme.workspace = true build = "build.rs" [features] default = ["std", "serde", "ts-rs", "typeshare"] -std = ["radroots-core/std"] -serde = ["dep:serde", "radroots-core/serde"] +std = ["radroots_core/std"] +serde = ["dep:serde", "radroots_core/serde"] ts-rs = ["dep:ts-rs"] -typeshare = ["dep:typeshare", "radroots-core/typeshare"] +typeshare = ["dep:typeshare", "radroots_core/typeshare"] [dependencies] -radroots-core = { workspace = true, default-features = false } +radroots_core = { workspace = true, default-features = false } serde = { workspace = true, default-features = false, features = [ "alloc", "derive", diff --git a/crates/events/README.md b/crates/events/README.md @@ -1,4 +1,4 @@ -# radroots-events +# radroots_events Nostr event models, kinds, and tag mappings for the Rad Roots SDK. diff --git a/crates/events/src/listing.rs b/crates/events/src/listing.rs @@ -304,9 +304,9 @@ mod constants_tests { #[test] fn resolves_export_dir_for_override_and_fallback() { - let override_dir = PathBuf::from("/tmp/radroots-events-ts-export"); + let override_dir = PathBuf::from("/tmp/radroots_events-ts-export"); assert_eq!( - ts_export_dir_from(Some("/tmp/radroots-events-ts-export")), + ts_export_dir_from(Some("/tmp/radroots_events-ts-export")), override_dir ); diff --git a/crates/events_codec/Cargo.toml b/crates/events_codec/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "radroots_events_codec" +version = "0.1.0-alpha.1" +edition.workspace = true +authors = [ + "Radroots Authors", +] +rust-version.workspace = true +license.workspace = true +description = "canonical encoders and decoders for radroots nostr event payloads" +repository.workspace = true +homepage.workspace = true +documentation = "https://docs.rs/radroots_events_codec" +readme.workspace = true + +[features] +default = ["std"] +std = ["radroots_core/std", "radroots_events/std"] +serde = ["dep:serde", "radroots_core/serde", "radroots_events/serde"] +serde_json = ["serde", "dep:serde_json"] +nostr = ["dep:nostr", "std"] + +[dependencies] +radroots_core = { workspace = true, default-features = false } +radroots_events = { workspace = true, default-features = false } +serde = { workspace = true, default-features = false, features = [ + "alloc", +], optional = true } +serde_json = { workspace = true, default-features = false, features = [ + "alloc", +], optional = true } +nostr = { workspace = true, optional = true } + +[dev-dependencies] +radroots_test_fixtures = { workspace = true } diff --git a/crates/events_codec/README.md b/crates/events_codec/README.md @@ -0,0 +1,14 @@ +# radroots_events_codec + +Reference encoders and decoders for Rad Roots Nostr event payloads. + +## Goals + +- define stable encoding and decoding interfaces for Rad Roots event payloads +- keep parsing and validation behavior deterministic across supported event forms +- support std and no_std builds with feature-gated `serde_json` and Nostr integration +- provide reusable codec primitives for higher-level Rad Roots crates + +## License + +Licensed under AGPL-3.0. See LICENSE. diff --git a/crates/events-codec/src/app_data/decode.rs b/crates/events_codec/src/app_data/decode.rs diff --git a/crates/events-codec/src/app_data/encode.rs b/crates/events_codec/src/app_data/encode.rs diff --git a/crates/events-codec/src/app_data/mod.rs b/crates/events_codec/src/app_data/mod.rs diff --git a/crates/events-codec/src/comment/decode.rs b/crates/events_codec/src/comment/decode.rs diff --git a/crates/events-codec/src/comment/encode.rs b/crates/events_codec/src/comment/encode.rs diff --git a/crates/events-codec/src/comment/mod.rs b/crates/events_codec/src/comment/mod.rs diff --git a/crates/events-codec/src/coop/decode.rs b/crates/events_codec/src/coop/decode.rs diff --git a/crates/events-codec/src/coop/encode.rs b/crates/events_codec/src/coop/encode.rs diff --git a/crates/events-codec/src/coop/list_sets.rs b/crates/events_codec/src/coop/list_sets.rs diff --git a/crates/events-codec/src/coop/mod.rs b/crates/events_codec/src/coop/mod.rs diff --git a/crates/events-codec/src/d_tag.rs b/crates/events_codec/src/d_tag.rs diff --git a/crates/events-codec/src/document/decode.rs b/crates/events_codec/src/document/decode.rs diff --git a/crates/events-codec/src/document/encode.rs b/crates/events_codec/src/document/encode.rs diff --git a/crates/events-codec/src/document/mod.rs b/crates/events_codec/src/document/mod.rs diff --git a/crates/events-codec/src/error.rs b/crates/events_codec/src/error.rs diff --git a/crates/events-codec/src/event_ref.rs b/crates/events_codec/src/event_ref.rs diff --git a/crates/events-codec/src/farm/decode.rs b/crates/events_codec/src/farm/decode.rs diff --git a/crates/events-codec/src/farm/encode.rs b/crates/events_codec/src/farm/encode.rs diff --git a/crates/events-codec/src/farm/list_sets.rs b/crates/events_codec/src/farm/list_sets.rs diff --git a/crates/events-codec/src/farm/mod.rs b/crates/events_codec/src/farm/mod.rs diff --git a/crates/events-codec/src/follow/decode.rs b/crates/events_codec/src/follow/decode.rs diff --git a/crates/events-codec/src/follow/encode.rs b/crates/events_codec/src/follow/encode.rs diff --git a/crates/events-codec/src/follow/mod.rs b/crates/events_codec/src/follow/mod.rs diff --git a/crates/events-codec/src/geochat/decode.rs b/crates/events_codec/src/geochat/decode.rs diff --git a/crates/events-codec/src/geochat/encode.rs b/crates/events_codec/src/geochat/encode.rs diff --git a/crates/events-codec/src/geochat/mod.rs b/crates/events_codec/src/geochat/mod.rs diff --git a/crates/events-codec/src/gift_wrap/decode.rs b/crates/events_codec/src/gift_wrap/decode.rs diff --git a/crates/events-codec/src/gift_wrap/encode.rs b/crates/events_codec/src/gift_wrap/encode.rs diff --git a/crates/events-codec/src/gift_wrap/mod.rs b/crates/events_codec/src/gift_wrap/mod.rs diff --git a/crates/events-codec/src/job/encode.rs b/crates/events_codec/src/job/encode.rs diff --git a/crates/events-codec/src/job/error.rs b/crates/events_codec/src/job/error.rs diff --git a/crates/events-codec/src/job/feedback/decode.rs b/crates/events_codec/src/job/feedback/decode.rs diff --git a/crates/events-codec/src/job/feedback/encode.rs b/crates/events_codec/src/job/feedback/encode.rs diff --git a/crates/events-codec/src/job/feedback/mod.rs b/crates/events_codec/src/job/feedback/mod.rs diff --git a/crates/events-codec/src/job/mod.rs b/crates/events_codec/src/job/mod.rs diff --git a/crates/events-codec/src/job/request/decode.rs b/crates/events_codec/src/job/request/decode.rs diff --git a/crates/events-codec/src/job/request/encode.rs b/crates/events_codec/src/job/request/encode.rs diff --git a/crates/events-codec/src/job/request/mod.rs b/crates/events_codec/src/job/request/mod.rs diff --git a/crates/events-codec/src/job/result/decode.rs b/crates/events_codec/src/job/result/decode.rs diff --git a/crates/events-codec/src/job/result/encode.rs b/crates/events_codec/src/job/result/encode.rs diff --git a/crates/events-codec/src/job/result/mod.rs b/crates/events_codec/src/job/result/mod.rs diff --git a/crates/events-codec/src/job/traits.rs b/crates/events_codec/src/job/traits.rs diff --git a/crates/events-codec/src/job/util.rs b/crates/events_codec/src/job/util.rs diff --git a/crates/events-codec/src/lib.rs b/crates/events_codec/src/lib.rs diff --git a/crates/events-codec/src/list/decode.rs b/crates/events_codec/src/list/decode.rs diff --git a/crates/events-codec/src/list/encode.rs b/crates/events_codec/src/list/encode.rs diff --git a/crates/events-codec/src/list/mod.rs b/crates/events_codec/src/list/mod.rs diff --git a/crates/events-codec/src/list_set/decode.rs b/crates/events_codec/src/list_set/decode.rs diff --git a/crates/events-codec/src/list_set/encode.rs b/crates/events_codec/src/list_set/encode.rs diff --git a/crates/events-codec/src/list_set/mod.rs b/crates/events_codec/src/list_set/mod.rs diff --git a/crates/events-codec/src/listing/decode.rs b/crates/events_codec/src/listing/decode.rs diff --git a/crates/events-codec/src/listing/encode.rs b/crates/events_codec/src/listing/encode.rs diff --git a/crates/events-codec/src/listing/mod.rs b/crates/events_codec/src/listing/mod.rs diff --git a/crates/events-codec/src/listing/tags.rs b/crates/events_codec/src/listing/tags.rs diff --git a/crates/events-codec/src/message/decode.rs b/crates/events_codec/src/message/decode.rs diff --git a/crates/events-codec/src/message/encode.rs b/crates/events_codec/src/message/encode.rs diff --git a/crates/events-codec/src/message/mod.rs b/crates/events_codec/src/message/mod.rs diff --git a/crates/events-codec/src/message/tags.rs b/crates/events_codec/src/message/tags.rs diff --git a/crates/events-codec/src/message_file/decode.rs b/crates/events_codec/src/message_file/decode.rs diff --git a/crates/events-codec/src/message_file/encode.rs b/crates/events_codec/src/message_file/encode.rs diff --git a/crates/events-codec/src/message_file/mod.rs b/crates/events_codec/src/message_file/mod.rs diff --git a/crates/events-codec/src/parsed.rs b/crates/events_codec/src/parsed.rs diff --git a/crates/events-codec/src/plot/decode.rs b/crates/events_codec/src/plot/decode.rs diff --git a/crates/events-codec/src/plot/encode.rs b/crates/events_codec/src/plot/encode.rs diff --git a/crates/events-codec/src/plot/mod.rs b/crates/events_codec/src/plot/mod.rs diff --git a/crates/events-codec/src/post/decode.rs b/crates/events_codec/src/post/decode.rs diff --git a/crates/events-codec/src/post/encode.rs b/crates/events_codec/src/post/encode.rs diff --git a/crates/events-codec/src/post/mod.rs b/crates/events_codec/src/post/mod.rs diff --git a/crates/events-codec/src/profile/decode.rs b/crates/events_codec/src/profile/decode.rs diff --git a/crates/events-codec/src/profile/encode.rs b/crates/events_codec/src/profile/encode.rs diff --git a/crates/events-codec/src/profile/error.rs b/crates/events_codec/src/profile/error.rs diff --git a/crates/events-codec/src/profile/mod.rs b/crates/events_codec/src/profile/mod.rs diff --git a/crates/events-codec/src/reaction/decode.rs b/crates/events_codec/src/reaction/decode.rs diff --git a/crates/events-codec/src/reaction/encode.rs b/crates/events_codec/src/reaction/encode.rs diff --git a/crates/events-codec/src/reaction/mod.rs b/crates/events_codec/src/reaction/mod.rs diff --git a/crates/events-codec/src/relay_document/decode.rs b/crates/events_codec/src/relay_document/decode.rs diff --git a/crates/events-codec/src/relay_document/encode.rs b/crates/events_codec/src/relay_document/encode.rs diff --git a/crates/events-codec/src/relay_document/mod.rs b/crates/events_codec/src/relay_document/mod.rs diff --git a/crates/events-codec/src/resource_area/decode.rs b/crates/events_codec/src/resource_area/decode.rs diff --git a/crates/events-codec/src/resource_area/encode.rs b/crates/events_codec/src/resource_area/encode.rs diff --git a/crates/events-codec/src/resource_area/list_sets.rs b/crates/events_codec/src/resource_area/list_sets.rs diff --git a/crates/events-codec/src/resource_area/mod.rs b/crates/events_codec/src/resource_area/mod.rs diff --git a/crates/events-codec/src/resource_cap/decode.rs b/crates/events_codec/src/resource_cap/decode.rs diff --git a/crates/events-codec/src/resource_cap/encode.rs b/crates/events_codec/src/resource_cap/encode.rs diff --git a/crates/events-codec/src/resource_cap/mod.rs b/crates/events_codec/src/resource_cap/mod.rs diff --git a/crates/events-codec/src/seal/decode.rs b/crates/events_codec/src/seal/decode.rs diff --git a/crates/events-codec/src/seal/encode.rs b/crates/events_codec/src/seal/encode.rs diff --git a/crates/events-codec/src/seal/mod.rs b/crates/events_codec/src/seal/mod.rs diff --git a/crates/events-codec/src/tag_builders.rs b/crates/events_codec/src/tag_builders.rs diff --git a/crates/events_codec/src/trade/decode.rs b/crates/events_codec/src/trade/decode.rs @@ -0,0 +1,457 @@ +#[cfg(all(not(feature = "std"), feature = "serde_json"))] +use alloc::{borrow::ToOwned, format, string::String, vec::Vec}; + +#[cfg(feature = "serde_json")] +use radroots_events::{ + RadrootsNostrEvent, RadrootsNostrEventPtr, + kinds::{KIND_PROFILE, is_trade_kind}, + tags::{TAG_D, TAG_E_PREV, TAG_E_ROOT}, + trade::{RadrootsTradeEnvelope, RadrootsTradeEnvelopeError, RadrootsTradeMessageType}, +}; +#[cfg(feature = "serde_json")] +use serde::de::DeserializeOwned; + +#[cfg(feature = "serde_json")] +use crate::d_tag::is_d_tag_base64url; +#[cfg(feature = "serde_json")] +use crate::trade::tags::{ + TAG_LISTING_EVENT, parse_trade_counterparty_tag, parse_trade_listing_event_tag, + parse_trade_prev_tag, parse_trade_root_tag, +}; + +#[cfg(feature = "serde_json")] +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum RadrootsTradeEnvelopeParseError { + InvalidKind(u32), + InvalidJson, + InvalidEnvelope(RadrootsTradeEnvelopeError), + MessageTypeKindMismatch { + event_kind: u32, + message_type: RadrootsTradeMessageType, + }, + MissingTag(&'static str), + InvalidTag(&'static str), + ListingAddrTagMismatch, + OrderIdTagMismatch, + InvalidListingAddr(RadrootsTradeListingAddressError), +} + +#[cfg(feature = "serde_json")] +impl core::fmt::Display for RadrootsTradeEnvelopeParseError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::InvalidKind(kind) => write!(f, "invalid trade event kind: {kind}"), + Self::InvalidJson => write!(f, "invalid trade envelope json"), + Self::InvalidEnvelope(error) => write!(f, "{error}"), + Self::MessageTypeKindMismatch { + event_kind, + message_type, + } => write!( + f, + "trade envelope type {message_type:?} does not match event kind {event_kind}" + ), + Self::MissingTag(tag) => write!(f, "missing required trade tag: {tag}"), + Self::InvalidTag(tag) => write!(f, "invalid trade tag: {tag}"), + Self::ListingAddrTagMismatch => { + write!(f, "trade listing address tag does not match envelope") + } + Self::OrderIdTagMismatch => write!(f, "trade order id tag does not match envelope"), + Self::InvalidListingAddr(error) => write!(f, "{error}"), + } + } +} + +#[cfg(all(feature = "std", feature = "serde_json"))] +impl std::error::Error for RadrootsTradeEnvelopeParseError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Self::InvalidEnvelope(error) => Some(error), + Self::InvalidListingAddr(error) => Some(error), + _ => None, + } + } +} + +#[cfg(feature = "serde_json")] +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RadrootsTradeEventContext { + pub counterparty_pubkey: String, + pub listing_event: Option<RadrootsNostrEventPtr>, + pub root_event_id: Option<String>, + pub prev_event_id: Option<String>, +} + +#[cfg(feature = "serde_json")] +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RadrootsTradeListingAddress { + pub kind: u32, + pub seller_pubkey: String, + pub listing_id: String, +} + +#[cfg(feature = "serde_json")] +impl RadrootsTradeListingAddress { + pub fn parse(addr: &str) -> Result<Self, RadrootsTradeListingAddressError> { + let (kind_raw, seller_and_listing) = addr + .split_once(':') + .ok_or(RadrootsTradeListingAddressError::InvalidFormat)?; + let (seller_pubkey_raw, listing_id_raw) = seller_and_listing + .split_once(':') + .ok_or(RadrootsTradeListingAddressError::InvalidFormat)?; + if listing_id_raw.contains(':') { + return Err(RadrootsTradeListingAddressError::InvalidFormat); + } + let kind = kind_raw + .parse::<u32>() + .map_err(|_| RadrootsTradeListingAddressError::InvalidFormat)?; + let seller_pubkey = seller_pubkey_raw.to_owned(); + let listing_id = listing_id_raw.to_owned(); + if kind == KIND_PROFILE + || seller_pubkey.trim().is_empty() + || listing_id.trim().is_empty() + || !is_d_tag_base64url(&listing_id) + { + return Err(RadrootsTradeListingAddressError::InvalidFormat); + } + Ok(Self { + kind, + seller_pubkey, + listing_id, + }) + } + + #[inline] + pub fn as_str(&self) -> String { + format!("{}:{}:{}", self.kind, self.seller_pubkey, self.listing_id) + } +} + +#[cfg(feature = "serde_json")] +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum RadrootsTradeListingAddressError { + InvalidFormat, +} + +#[cfg(feature = "serde_json")] +impl core::fmt::Display for RadrootsTradeListingAddressError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::InvalidFormat => write!(f, "invalid listing address format"), + } + } +} + +#[cfg(all(feature = "std", feature = "serde_json"))] +impl std::error::Error for RadrootsTradeListingAddressError {} + +#[cfg(feature = "serde_json")] +fn required_tag_value<'a>( + tags: &'a [Vec<String>], + key: &'static str, +) -> Result<&'a str, RadrootsTradeEnvelopeParseError> { + let tag = tags + .iter() + .find(|tag| tag.first().map(|value| value.as_str()) == Some(key)) + .ok_or(RadrootsTradeEnvelopeParseError::MissingTag(key))?; + let value = tag + .get(1) + .map(|value| value.as_str()) + .ok_or(RadrootsTradeEnvelopeParseError::InvalidTag(key))?; + if value.trim().is_empty() { + return Err(RadrootsTradeEnvelopeParseError::InvalidTag(key)); + } + Ok(value) +} + +#[cfg(feature = "serde_json")] +pub fn trade_envelope_from_event<T: DeserializeOwned>( + event: &RadrootsNostrEvent, +) -> Result<RadrootsTradeEnvelope<T>, RadrootsTradeEnvelopeParseError> { + if !is_trade_kind(event.kind) { + return Err(RadrootsTradeEnvelopeParseError::InvalidKind(event.kind)); + } + let envelope = serde_json::from_str::<RadrootsTradeEnvelope<T>>(&event.content) + .map_err(|_| RadrootsTradeEnvelopeParseError::InvalidJson)?; + envelope + .validate() + .map_err(RadrootsTradeEnvelopeParseError::InvalidEnvelope)?; + if envelope.message_type.kind() != event.kind { + return Err(RadrootsTradeEnvelopeParseError::MessageTypeKindMismatch { + event_kind: event.kind, + message_type: envelope.message_type, + }); + } + + let listing_addr = required_tag_value(&event.tags, "a")?; + if envelope.listing_addr != listing_addr { + return Err(RadrootsTradeEnvelopeParseError::ListingAddrTagMismatch); + } + RadrootsTradeListingAddress::parse(&envelope.listing_addr) + .map_err(RadrootsTradeEnvelopeParseError::InvalidListingAddr)?; + + if let Some(order_id) = envelope.order_id.as_deref() { + let tag_order_id = required_tag_value(&event.tags, TAG_D)?; + if tag_order_id != order_id { + return Err(RadrootsTradeEnvelopeParseError::OrderIdTagMismatch); + } + } + + let message_type = envelope.message_type; + trade_event_context_from_tags(message_type, &event.tags)?; + + Ok(envelope) +} + +#[cfg(feature = "serde_json")] +pub fn trade_event_context_from_tags( + message_type: RadrootsTradeMessageType, + tags: &[Vec<String>], +) -> Result<RadrootsTradeEventContext, RadrootsTradeEnvelopeParseError> { + let counterparty_pubkey = + parse_trade_counterparty_tag(tags).map_err(map_tag_parse_error_for_trade_envelope)?; + let listing_event = + parse_trade_listing_event_tag(tags).map_err(map_tag_parse_error_for_trade_envelope)?; + let root_event_id = + parse_trade_root_tag(tags).map_err(map_tag_parse_error_for_trade_envelope)?; + let prev_event_id = + parse_trade_prev_tag(tags).map_err(map_tag_parse_error_for_trade_envelope)?; + + if message_type.requires_listing_snapshot() && listing_event.is_none() { + return Err(RadrootsTradeEnvelopeParseError::MissingTag( + TAG_LISTING_EVENT, + )); + } + if message_type.requires_trade_chain() { + if root_event_id.is_none() { + return Err(RadrootsTradeEnvelopeParseError::MissingTag(TAG_E_ROOT)); + } + if prev_event_id.is_none() { + return Err(RadrootsTradeEnvelopeParseError::MissingTag(TAG_E_PREV)); + } + } + + Ok(RadrootsTradeEventContext { + counterparty_pubkey, + listing_event, + root_event_id, + prev_event_id, + }) +} + +#[cfg(feature = "serde_json")] +fn map_tag_parse_error_for_trade_envelope( + error: crate::error::EventParseError, +) -> RadrootsTradeEnvelopeParseError { + match error { + crate::error::EventParseError::MissingTag(tag) => { + RadrootsTradeEnvelopeParseError::MissingTag(tag) + } + crate::error::EventParseError::InvalidTag(tag) => { + RadrootsTradeEnvelopeParseError::InvalidTag(tag) + } + crate::error::EventParseError::InvalidKind { expected: _, got } => { + RadrootsTradeEnvelopeParseError::InvalidKind(got) + } + crate::error::EventParseError::InvalidNumber(tag, _) + | crate::error::EventParseError::InvalidJson(tag) => { + RadrootsTradeEnvelopeParseError::InvalidTag(tag) + } + } +} + +#[cfg(all(test, feature = "serde_json"))] +mod tests { + use super::{ + RadrootsTradeEnvelopeParseError, RadrootsTradeListingAddress, trade_envelope_from_event, + trade_event_context_from_tags, + }; + use crate::trade::encode::trade_envelope_event_build; + use crate::trade::tags::TAG_LISTING_EVENT; + use radroots_events::{ + RadrootsNostrEvent, RadrootsNostrEventPtr, + tags::{TAG_D, TAG_E_PREV, TAG_E_ROOT}, + trade::{ + RadrootsTradeEnvelope, RadrootsTradeMessagePayload, RadrootsTradeMessageType, + RadrootsTradeOrder, RadrootsTradeOrderItem, + }, + }; + + fn base_order() -> RadrootsTradeOrder { + RadrootsTradeOrder { + order_id: "order-1".into(), + listing_addr: "30402:seller:AAAAAAAAAAAAAAAAAAAAAg".into(), + buyer_pubkey: "buyer".into(), + seller_pubkey: "seller".into(), + items: vec![RadrootsTradeOrderItem { + bin_id: "lb".into(), + bin_count: 3, + }], + discounts: None, + } + } + + #[test] + fn listing_address_roundtrips() { + let addr = RadrootsTradeListingAddress::parse("30402:seller:AAAAAAAAAAAAAAAAAAAAAg") + .expect("parse listing address"); + assert_eq!(addr.as_str(), "30402:seller:AAAAAAAAAAAAAAAAAAAAAg"); + } + + #[test] + fn parse_order_request_roundtrip() { + let payload = RadrootsTradeMessagePayload::OrderRequest(base_order()); + let built = trade_envelope_event_build( + "seller", + RadrootsTradeMessageType::OrderRequest, + "30402:seller:AAAAAAAAAAAAAAAAAAAAAg", + Some("order-1".into()), + Some(&RadrootsNostrEventPtr { + id: "listing-snapshot".into(), + relays: None, + }), + None, + None, + &payload, + ) + .expect("build trade envelope"); + let event = RadrootsNostrEvent { + id: "id".into(), + author: "buyer".into(), + created_at: 1, + kind: built.kind, + tags: built.tags, + content: built.content, + sig: "sig".into(), + }; + let envelope: RadrootsTradeEnvelope<RadrootsTradeMessagePayload> = + trade_envelope_from_event(&event).expect("parse trade envelope"); + assert_eq!( + envelope.message_type, + RadrootsTradeMessageType::OrderRequest + ); + assert_eq!(envelope.order_id.as_deref(), Some("order-1")); + } + + #[test] + fn parse_rejects_listing_addr_mismatch() { + let payload = RadrootsTradeMessagePayload::OrderRequest(base_order()); + let built = trade_envelope_event_build( + "seller", + RadrootsTradeMessageType::OrderRequest, + "30402:seller:AAAAAAAAAAAAAAAAAAAAAg", + Some("order-1".into()), + Some(&RadrootsNostrEventPtr { + id: "listing-snapshot".into(), + relays: None, + }), + None, + None, + &payload, + ) + .expect("build trade envelope"); + let mut envelope: RadrootsTradeEnvelope<serde_json::Value> = + serde_json::from_str(&built.content).expect("decode json"); + envelope.listing_addr = "30402:seller:BBBBBBBBBBBBBBBBBBBBBg".into(); + let event = RadrootsNostrEvent { + id: "id".into(), + author: "buyer".into(), + created_at: 1, + kind: built.kind, + tags: built.tags, + content: serde_json::to_string(&envelope).expect("encode json"), + sig: "sig".into(), + }; + let err = trade_envelope_from_event::<serde_json::Value>(&event).unwrap_err(); + assert_eq!(err, RadrootsTradeEnvelopeParseError::ListingAddrTagMismatch); + } + + #[test] + fn parse_rejects_missing_public_snapshot_tag() { + let payload = RadrootsTradeMessagePayload::OrderRequest(base_order()); + let built = trade_envelope_event_build( + "seller", + RadrootsTradeMessageType::OrderRequest, + "30402:seller:AAAAAAAAAAAAAAAAAAAAAg", + Some("order-1".into()), + Some(&RadrootsNostrEventPtr { + id: "listing-snapshot".into(), + relays: None, + }), + None, + None, + &payload, + ) + .expect("build trade envelope"); + let mut event = RadrootsNostrEvent { + id: "id".into(), + author: "buyer".into(), + created_at: 1, + kind: built.kind, + tags: built.tags, + content: built.content, + sig: "sig".into(), + }; + event + .tags + .retain(|tag| tag.first().map(|value| value.as_str()) != Some(TAG_LISTING_EVENT)); + let err = trade_envelope_from_event::<RadrootsTradeMessagePayload>(&event).unwrap_err(); + assert_eq!( + err, + RadrootsTradeEnvelopeParseError::MissingTag(TAG_LISTING_EVENT) + ); + } + + #[test] + fn parse_rejects_missing_public_chain_tags_after_order_request() { + let payload = RadrootsTradeMessagePayload::OrderResponse( + radroots_events::trade::RadrootsTradeOrderResponse { + accepted: true, + reason: None, + }, + ); + let built = trade_envelope_event_build( + "buyer", + RadrootsTradeMessageType::OrderResponse, + "30402:seller:AAAAAAAAAAAAAAAAAAAAAg", + Some("order-1".into()), + None, + Some("root"), + Some("prev"), + &payload, + ) + .expect("build trade envelope"); + let mut event = RadrootsNostrEvent { + id: "id".into(), + author: "seller".into(), + created_at: 1, + kind: built.kind, + tags: built.tags, + content: built.content, + sig: "sig".into(), + }; + event + .tags + .retain(|tag| tag.first().map(|value| value.as_str()) != Some(TAG_E_PREV)); + let err = trade_envelope_from_event::<RadrootsTradeMessagePayload>(&event).unwrap_err(); + assert_eq!(err, RadrootsTradeEnvelopeParseError::MissingTag(TAG_E_PREV)); + } + + #[test] + fn parse_trade_event_context_extracts_public_refs() { + let context = trade_event_context_from_tags( + RadrootsTradeMessageType::OrderResponse, + &[ + vec!["p".into(), "buyer".into()], + vec!["a".into(), "30402:seller:AAAAAAAAAAAAAAAAAAAAAg".into()], + vec![TAG_D.into(), "order-1".into()], + vec![TAG_E_ROOT.into(), "root-id".into()], + vec![TAG_E_PREV.into(), "prev-id".into()], + ], + ) + .expect("event context"); + assert_eq!(context.counterparty_pubkey, "buyer"); + assert_eq!(context.root_event_id.as_deref(), Some("root-id")); + assert_eq!(context.prev_event_id.as_deref(), Some("prev-id")); + assert!(context.listing_event.is_none()); + } +} diff --git a/crates/events-codec/src/trade/encode.rs b/crates/events_codec/src/trade/encode.rs diff --git a/crates/events-codec/src/trade/mod.rs b/crates/events_codec/src/trade/mod.rs diff --git a/crates/events-codec/src/trade/tags.rs b/crates/events_codec/src/trade/tags.rs diff --git a/crates/events-codec/src/wire.rs b/crates/events_codec/src/wire.rs diff --git a/crates/events-codec/tests/app_data.rs b/crates/events_codec/tests/app_data.rs diff --git a/crates/events-codec/tests/codec_error_job.rs b/crates/events_codec/tests/codec_error_job.rs diff --git a/crates/events-codec/tests/comment.rs b/crates/events_codec/tests/comment.rs diff --git a/crates/events-codec/tests/common/mod.rs b/crates/events_codec/tests/common/mod.rs diff --git a/crates/events-codec/tests/domain_encode_non_serde.rs b/crates/events_codec/tests/domain_encode_non_serde.rs diff --git a/crates/events-codec/tests/event_ref.rs b/crates/events_codec/tests/event_ref.rs diff --git a/crates/events-codec/tests/follow.rs b/crates/events_codec/tests/follow.rs diff --git a/crates/events-codec/tests/geochat.rs b/crates/events_codec/tests/geochat.rs diff --git a/crates/events-codec/tests/gift_wrap.rs b/crates/events_codec/tests/gift_wrap.rs diff --git a/crates/events-codec/tests/job_feedback.rs b/crates/events_codec/tests/job_feedback.rs diff --git a/crates/events-codec/tests/job_request.rs b/crates/events_codec/tests/job_request.rs diff --git a/crates/events-codec/tests/job_result.rs b/crates/events_codec/tests/job_result.rs diff --git a/crates/events-codec/tests/job_traits.rs b/crates/events_codec/tests/job_traits.rs diff --git a/crates/events-codec/tests/job_util.rs b/crates/events_codec/tests/job_util.rs diff --git a/crates/events-codec/tests/list.rs b/crates/events_codec/tests/list.rs diff --git a/crates/events-codec/tests/list_private.rs b/crates/events_codec/tests/list_private.rs diff --git a/crates/events-codec/tests/list_set.rs b/crates/events_codec/tests/list_set.rs diff --git a/crates/events-codec/tests/listing.rs b/crates/events_codec/tests/listing.rs diff --git a/crates/events-codec/tests/message.rs b/crates/events_codec/tests/message.rs diff --git a/crates/events-codec/tests/message_file.rs b/crates/events_codec/tests/message_file.rs diff --git a/crates/events-codec/tests/post.rs b/crates/events_codec/tests/post.rs diff --git a/crates/events-codec/tests/profile.rs b/crates/events_codec/tests/profile.rs diff --git a/crates/events-codec/tests/profile_encode.rs b/crates/events_codec/tests/profile_encode.rs diff --git a/crates/events-codec/tests/reaction.rs b/crates/events_codec/tests/reaction.rs diff --git a/crates/events-codec/tests/relay_document.rs b/crates/events_codec/tests/relay_document.rs diff --git a/crates/events-codec/tests/seal.rs b/crates/events_codec/tests/seal.rs diff --git a/crates/events-codec/tests/structured_decode.rs b/crates/events_codec/tests/structured_decode.rs diff --git a/crates/events-codec/tests/structured_encode_default.rs b/crates/events_codec/tests/structured_encode_default.rs diff --git a/crates/events-codec/tests/tag_builders.rs b/crates/events_codec/tests/tag_builders.rs diff --git a/crates/events-codec/tests/wire.rs b/crates/events_codec/tests/wire.rs diff --git a/crates/events_codec_wasm/Cargo.toml b/crates/events_codec_wasm/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "radroots_events_codec_wasm" +version = "0.1.0-alpha.1" +edition.workspace = true +authors = [ + "Radroots Authors", +] +rust-version.workspace = true +license.workspace = true +description = "wasm bindings for radroots event encoding and decoding" +repository.workspace = true +homepage.workspace = true +documentation = "https://docs.rs/radroots_events_codec_wasm" +readme.workspace = true + +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies] +radroots_events = { workspace = true, default-features = false, features = [ + "std", + "serde", +] } +radroots_events_codec = { workspace = true, default-features = false, features = [ + "std", + "serde_json", +] } +serde = { workspace = true } +serde_json = { workspace = true } +wasm-bindgen = { workspace = true } + +[dev-dependencies] +radroots_core = { workspace = true, default-features = false, features = [ + "std", +] } diff --git a/crates/events_codec_wasm/README.md b/crates/events_codec_wasm/README.md @@ -0,0 +1,14 @@ +# radroots_events_codec_wasm + +Wasm bindings for Rad Roots event encoding and decoding. + +## Goals + +- define stable wasm bindings for `radroots_events_codec` +- keep tag generation and JSON conversion behavior deterministic in web runtimes +- support typed event-tag builders across listing, message, profile, and job flows +- provide reusable codec bridge functions for JavaScript integrations + +## License + +Licensed under AGPL-3.0. See LICENSE. diff --git a/crates/events-codec-wasm/pkg/package.json b/crates/events_codec_wasm/pkg/package.json diff --git a/crates/events-codec-wasm/src/lib.rs b/crates/events_codec_wasm/src/lib.rs diff --git a/crates/events_indexed/Cargo.toml b/crates/events_indexed/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "radroots_events_indexed" +version = "0.1.0-alpha.1" +edition.workspace = true +authors = [ + "Radroots Authors", +] +rust-version.workspace = true +license.workspace = true +description = "indexed manifest and checkpoint models for radroots event archives" +repository.workspace = true +homepage.workspace = true +documentation = "https://docs.rs/radroots_events_indexed" +readme.workspace = true + +[features] +default = ["serde", "typeshare"] +serde = ["dep:serde"] +typeshare = ["dep:typeshare"] +std = [] + +[dependencies] +serde = { workspace = true, default-features = false, features = [ + "alloc", + "derive", +], optional = true } +typeshare = { workspace = true, optional = true } + +[dev-dependencies] +serde_json = { workspace = true } diff --git a/crates/events_indexed/README.md b/crates/events_indexed/README.md @@ -0,0 +1,14 @@ +# radroots_events_indexed + +Indexed manifest and checkpoint models for Rad Roots event archives. + +## Goals + +- define stable manifest, checkpoint, and shard range models +- keep shard metadata and id-range validation deterministic across targets +- support no_std and std builds with feature-gated serialization and type export +- provide reusable indexed archive primitives for higher-level Rad Roots crates + +## License + +Licensed under AGPL-3.0. See LICENSE. diff --git a/crates/events-indexed/src/checkpoint.rs b/crates/events_indexed/src/checkpoint.rs diff --git a/crates/events-indexed/src/lib.rs b/crates/events_indexed/src/lib.rs diff --git a/crates/events-indexed/src/manifest.rs b/crates/events_indexed/src/manifest.rs diff --git a/crates/events-indexed/src/serde_ext.rs b/crates/events_indexed/src/serde_ext.rs diff --git a/crates/events-indexed/src/types.rs b/crates/events_indexed/src/types.rs diff --git a/crates/geocoder/Cargo.toml b/crates/geocoder/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "radroots-geocoder" +name = "radroots_geocoder" version = "0.1.0-alpha.1" edition.workspace = true authors = [ @@ -10,7 +10,7 @@ license.workspace = true description = "offline geocoder queries for radroots geonames datasets" repository.workspace = true homepage.workspace = true -documentation = "https://docs.rs/radroots-geocoder" +documentation = "https://docs.rs/radroots_geocoder" readme.workspace = true [dependencies] diff --git a/crates/geocoder/README.md b/crates/geocoder/README.md @@ -1,3 +1,3 @@ -# radroots-geocoder +# radroots_geocoder Offline geocoder queries for the Rad Roots `geonames.db` dataset. diff --git a/crates/identity/Cargo.toml b/crates/identity/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "radroots-identity" +name = "radroots_identity" version = "0.1.0-alpha.1" edition.workspace = true authors = [ @@ -10,24 +10,24 @@ license.workspace = true description = "identity model and profile utilities for radroots nostr applications" repository.workspace = true homepage.workspace = true -documentation = "https://docs.rs/radroots-identity" +documentation = "https://docs.rs/radroots_identity" readme.workspace = true build = "build.rs" [features] default = ["std", "profile", "json-file", "nip49"] -std = ["dep:radroots-runtime-paths"] -profile = ["dep:radroots-events"] -json-file = ["std", "dep:radroots-runtime"] +std = ["dep:radroots_runtime_paths"] +profile = ["dep:radroots_events"] +json-file = ["std", "dep:radroots_runtime"] nip49 = ["std", "nostr/nip49"] secrecy = ["dep:secrecy"] zeroize = ["dep:zeroize"] ts-rs = ["dep:ts-rs"] [dependencies] -radroots-runtime = { workspace = true, optional = true } -radroots-runtime-paths = { workspace = true, optional = true } -radroots-events = { workspace = true, optional = true, default-features = false, features = [ +radroots_runtime = { workspace = true, optional = true } +radroots_runtime_paths = { workspace = true, optional = true } +radroots_events = { workspace = true, optional = true, default-features = false, features = [ "serde", ] } nostr = { workspace = true } @@ -40,5 +40,5 @@ ts-rs = { workspace = true, optional = true } zeroize = { workspace = true, optional = true } [dev-dependencies] -radroots-test-fixtures = { workspace = true } +radroots_test_fixtures = { workspace = true } tempfile = { workspace = true } diff --git a/crates/identity/README.md b/crates/identity/README.md @@ -1,4 +1,4 @@ -# radroots-identity +# radroots_identity Identity models and profile primitives for the Rad Roots SDK. diff --git a/crates/log/Cargo.toml b/crates/log/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "radroots-log" +name = "radroots_log" version = "0.1.0-alpha.1" edition.workspace = true authors = [ @@ -10,7 +10,7 @@ license.workspace = true description = "tracing-based logging primitives for radroots runtime crates" repository.workspace = true homepage.workspace = true -documentation = "https://docs.rs/radroots-log" +documentation = "https://docs.rs/radroots_log" readme.workspace = true [features] diff --git a/crates/log/README.md b/crates/log/README.md @@ -1,4 +1,4 @@ -# radroots-log +# radroots_log Tracing logging primitives for the Rad Roots SDK. diff --git a/crates/log/src/init.rs b/crates/log/src/init.rs @@ -99,7 +99,7 @@ mod tests { .duration_since(UNIX_EPOCH) .expect("system time") .as_nanos(); - let dir = std::env::temp_dir().join(format!("radroots-log-{name}-{nanos}")); + let dir = std::env::temp_dir().join(format!("radroots_log-{name}-{nanos}")); let _ = std::fs::remove_dir_all(&dir); dir } diff --git a/crates/net-core/Cargo.toml b/crates/net-core/Cargo.toml @@ -1,66 +0,0 @@ -[package] -name = "radroots-net-core" -version = "0.1.0-alpha.1" -edition.workspace = true -authors = [ - "Radroots Authors", -] -rust-version.workspace = true -license.workspace = true -description = "network orchestration primitives and runtime interfaces for the radroots sdk" -repository.workspace = true -homepage.workspace = true -documentation = "https://docs.rs/radroots-net-core" -readme.workspace = true - -[features] -default = ["std"] -std = ["serde/std"] -rt = ["std", "dep:tokio"] -nostr-client = [ - "std", - "dep:radroots-events", - "dep:radroots-events-codec", - "radroots-events/serde", - "dep:radroots-nostr-accounts", - "dep:radroots-nostr-signer", - "dep:secrecy", - "dep:hex", - "dep:tempfile", - "dep:serde_json", - "dep:radroots-nostr", -] -directories = ["std", "dep:directories"] -fs-persistence = ["std", "dep:radroots-runtime-paths"] - -[dependencies] -radroots-events = { workspace = true, optional = true, default-features = true, features = [ - "std", - "serde", - "typeshare", -] } -radroots-log = { workspace = true, features = ["std"] } -radroots-nostr-accounts = { workspace = true, optional = true, default-features = true } -radroots-nostr-signer = { workspace = true, optional = true } -radroots-events-codec = { workspace = true, optional = true, default-features = true, features = [ - "std", -] } -radroots-nostr = { workspace = true, optional = true, default-features = true, features = [ - "client", - "events", - "codec", -] } -directories = { workspace = true, optional = true } -hex = { workspace = true, optional = true } -radroots-runtime-paths = { workspace = true, optional = true } -secrecy = { workspace = true, optional = true } -serde = { workspace = true, features = ["derive"] } -serde_json = { workspace = true, optional = true } -tempfile = { workspace = true, optional = true } -thiserror = { workspace = true } -tokio = { workspace = true, optional = true, features = ["rt-multi-thread"] } -tracing = { workspace = true } -futures = { workspace = true } - -[dev-dependencies] -radroots-identity = { workspace = true, default-features = true } diff --git a/crates/net-core/README.md b/crates/net-core/README.md @@ -1,14 +0,0 @@ -# radroots-net-core - -Network orchestration primitives and runtime interfaces for the Rad Roots SDK. - -## Goals - -- define stable interfaces for network configuration, assembly, and control -- keep network orchestration behavior deterministic across runtime environments -- support feature-gated Nostr client, key, and persistence integration surfaces -- provide reusable network primitives for higher-level Rad Roots crates - -## License - -Licensed under AGPL-3.0. See LICENSE. diff --git a/crates/net-core/src/logging.rs b/crates/net-core/src/logging.rs @@ -1,83 +0,0 @@ -use crate::error::{NetError, Result}; -use std::path::PathBuf; -use tracing::info; - -#[derive(Debug, Clone)] -pub struct LoggingOptions { - pub dir: Option<PathBuf>, - pub file_name: String, - pub also_stdout: bool, -} - -impl Default for LoggingOptions { - fn default() -> Self { - Self { - dir: None, - file_name: "radroots_net_core.log".into(), - also_stdout: true, - } - } -} - -pub fn init_logging(opts: LoggingOptions) -> Result<()> { - let log_opts = radroots_log::LoggingOptions { - dir: opts.dir.clone(), - file_name: opts.file_name.clone(), - stdout: opts.also_stdout, - default_level: None, - file_layout: radroots_log::LogFileLayout::PrefixedDate, - }; - match radroots_log::init_logging(log_opts) { - Ok(()) => {} - Err(_) => return Err(NetError::LoggingInit("init")), - } - let file_path = opts - .dir - .as_ref() - .map(|d| d.join(&opts.file_name).display().to_string()) - .unwrap_or_else(|| "<disabled>".into()); - info!( - "logging initialized (file: {}, stdout: {})", - file_path, opts.also_stdout - ); - Ok(()) -} - -#[cfg(test)] -mod tests { - use super::{LoggingOptions, init_logging}; - use crate::error::NetError; - use std::path::PathBuf; - - #[test] - fn logging_options_default_values_are_stable() { - let defaults = LoggingOptions::default(); - assert_eq!(defaults.dir, None); - assert_eq!(defaults.file_name, "radroots_net_core.log"); - assert!(defaults.also_stdout); - } - - #[test] - fn init_logging_covers_error_and_success_paths() { - let invalid = init_logging(LoggingOptions { - dir: Some(PathBuf::from("/dev/null/file")), - file_name: "x.log".to_string(), - also_stdout: false, - }); - assert!(matches!(invalid, Err(NetError::LoggingInit("init")))); - - let valid_with_dir = init_logging(LoggingOptions { - dir: Some(std::env::temp_dir().join("radroots-net-core-log-tests")), - file_name: "ok.log".to_string(), - also_stdout: false, - }); - assert!(valid_with_dir.is_ok()); - - let valid_without_dir = init_logging(LoggingOptions { - dir: None, - file_name: "ok2.log".to_string(), - also_stdout: true, - }); - assert!(valid_without_dir.is_ok()); - } -} diff --git a/crates/net/Cargo.toml b/crates/net/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "radroots-net" +name = "radroots_net" version = "0.1.0-alpha.1" edition.workspace = true authors = [ @@ -10,14 +10,14 @@ license.workspace = true description = "unified networking interface for the radroots sdk" repository.workspace = true homepage.workspace = true -documentation = "https://docs.rs/radroots-net" +documentation = "https://docs.rs/radroots_net" readme.workspace = true [features] default = ["std"] -std = ["radroots-net-core/std"] -rt = ["radroots-net-core/rt"] +std = ["radroots_net_core/std"] +rt = ["radroots_net_core/rt"] all = ["std", "rt"] [dependencies] -radroots-net-core = { workspace = true, optional = false } +radroots_net_core = { workspace = true, optional = false } diff --git a/crates/net/README.md b/crates/net/README.md @@ -1,10 +1,10 @@ -# radroots-net +# radroots_net Unified networking interface for the Rad Roots SDK. ## Goals -- define a stable entry surface over `radroots-net-core` primitives +- define a stable entry surface over `radroots_net_core` primitives - keep network dependency wiring deterministic across build targets - support a minimal integration path for SDK consumers - provide reusable networking entry points for higher-level Rad Roots crates diff --git a/crates/net_core/Cargo.toml b/crates/net_core/Cargo.toml @@ -0,0 +1,66 @@ +[package] +name = "radroots_net_core" +version = "0.1.0-alpha.1" +edition.workspace = true +authors = [ + "Radroots Authors", +] +rust-version.workspace = true +license.workspace = true +description = "network orchestration primitives and runtime interfaces for the radroots sdk" +repository.workspace = true +homepage.workspace = true +documentation = "https://docs.rs/radroots_net_core" +readme.workspace = true + +[features] +default = ["std"] +std = ["serde/std"] +rt = ["std", "dep:tokio"] +nostr-client = [ + "std", + "dep:radroots_events", + "dep:radroots_events_codec", + "radroots_events/serde", + "dep:radroots_nostr_accounts", + "dep:radroots_nostr_signer", + "dep:secrecy", + "dep:hex", + "dep:tempfile", + "dep:serde_json", + "dep:radroots_nostr", +] +directories = ["std", "dep:directories"] +fs-persistence = ["std", "dep:radroots_runtime_paths"] + +[dependencies] +radroots_events = { workspace = true, optional = true, default-features = true, features = [ + "std", + "serde", + "typeshare", +] } +radroots_log = { workspace = true, features = ["std"] } +radroots_nostr_accounts = { workspace = true, optional = true, default-features = true } +radroots_nostr_signer = { workspace = true, optional = true } +radroots_events_codec = { workspace = true, optional = true, default-features = true, features = [ + "std", +] } +radroots_nostr = { workspace = true, optional = true, default-features = true, features = [ + "client", + "events", + "codec", +] } +directories = { workspace = true, optional = true } +hex = { workspace = true, optional = true } +radroots_runtime_paths = { workspace = true, optional = true } +secrecy = { workspace = true, optional = true } +serde = { workspace = true, features = ["derive"] } +serde_json = { workspace = true, optional = true } +tempfile = { workspace = true, optional = true } +thiserror = { workspace = true } +tokio = { workspace = true, optional = true, features = ["rt-multi-thread"] } +tracing = { workspace = true } +futures = { workspace = true } + +[dev-dependencies] +radroots_identity = { workspace = true, default-features = true } diff --git a/crates/net_core/README.md b/crates/net_core/README.md @@ -0,0 +1,14 @@ +# radroots_net_core + +Network orchestration primitives and runtime interfaces for the Rad Roots SDK. + +## Goals + +- define stable interfaces for network configuration, assembly, and control +- keep network orchestration behavior deterministic across runtime environments +- support feature-gated Nostr client, key, and persistence integration surfaces +- provide reusable network primitives for higher-level Rad Roots crates + +## License + +Licensed under AGPL-3.0. See LICENSE. diff --git a/crates/net-core/build.rs b/crates/net_core/build.rs diff --git a/crates/net-core/src/builder.rs b/crates/net_core/src/builder.rs diff --git a/crates/net-core/src/config.rs b/crates/net_core/src/config.rs diff --git a/crates/net-core/src/error.rs b/crates/net_core/src/error.rs diff --git a/crates/net-core/src/keys.rs b/crates/net_core/src/keys.rs diff --git a/crates/net-core/src/lib.rs b/crates/net_core/src/lib.rs diff --git a/crates/net_core/src/logging.rs b/crates/net_core/src/logging.rs @@ -0,0 +1,83 @@ +use crate::error::{NetError, Result}; +use std::path::PathBuf; +use tracing::info; + +#[derive(Debug, Clone)] +pub struct LoggingOptions { + pub dir: Option<PathBuf>, + pub file_name: String, + pub also_stdout: bool, +} + +impl Default for LoggingOptions { + fn default() -> Self { + Self { + dir: None, + file_name: "radroots_net_core.log".into(), + also_stdout: true, + } + } +} + +pub fn init_logging(opts: LoggingOptions) -> Result<()> { + let log_opts = radroots_log::LoggingOptions { + dir: opts.dir.clone(), + file_name: opts.file_name.clone(), + stdout: opts.also_stdout, + default_level: None, + file_layout: radroots_log::LogFileLayout::PrefixedDate, + }; + match radroots_log::init_logging(log_opts) { + Ok(()) => {} + Err(_) => return Err(NetError::LoggingInit("init")), + } + let file_path = opts + .dir + .as_ref() + .map(|d| d.join(&opts.file_name).display().to_string()) + .unwrap_or_else(|| "<disabled>".into()); + info!( + "logging initialized (file: {}, stdout: {})", + file_path, opts.also_stdout + ); + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::{LoggingOptions, init_logging}; + use crate::error::NetError; + use std::path::PathBuf; + + #[test] + fn logging_options_default_values_are_stable() { + let defaults = LoggingOptions::default(); + assert_eq!(defaults.dir, None); + assert_eq!(defaults.file_name, "radroots_net_core.log"); + assert!(defaults.also_stdout); + } + + #[test] + fn init_logging_covers_error_and_success_paths() { + let invalid = init_logging(LoggingOptions { + dir: Some(PathBuf::from("/dev/null/file")), + file_name: "x.log".to_string(), + also_stdout: false, + }); + assert!(matches!(invalid, Err(NetError::LoggingInit("init")))); + + let valid_with_dir = init_logging(LoggingOptions { + dir: Some(std::env::temp_dir().join("radroots_net_core-log-tests")), + file_name: "ok.log".to_string(), + also_stdout: false, + }); + assert!(valid_with_dir.is_ok()); + + let valid_without_dir = init_logging(LoggingOptions { + dir: None, + file_name: "ok2.log".to_string(), + also_stdout: true, + }); + assert!(valid_without_dir.is_ok()); + } +} diff --git a/crates/net-core/src/net.rs b/crates/net_core/src/net.rs diff --git a/crates/net-core/src/nostr_client/connection.rs b/crates/net_core/src/nostr_client/connection.rs diff --git a/crates/net-core/src/nostr_client/events/custom.rs b/crates/net_core/src/nostr_client/events/custom.rs diff --git a/crates/net-core/src/nostr_client/events/post.rs b/crates/net_core/src/nostr_client/events/post.rs diff --git a/crates/net-core/src/nostr_client/events/profile.rs b/crates/net_core/src/nostr_client/events/profile.rs diff --git a/crates/net-core/src/nostr_client/inner.rs b/crates/net_core/src/nostr_client/inner.rs diff --git a/crates/net-core/src/nostr_client/manager.rs b/crates/net_core/src/nostr_client/manager.rs diff --git a/crates/net-core/src/nostr_client/mod.rs b/crates/net_core/src/nostr_client/mod.rs diff --git a/crates/net-core/src/nostr_client/status.rs b/crates/net_core/src/nostr_client/status.rs diff --git a/crates/net-core/src/nostr_client/types.rs b/crates/net_core/src/nostr_client/types.rs diff --git a/crates/net-core/tests/error.rs b/crates/net_core/tests/error.rs diff --git a/crates/nostr-accounts/Cargo.toml b/crates/nostr-accounts/Cargo.toml @@ -1,54 +0,0 @@ -[package] -name = "radroots-nostr-accounts" -version = "0.1.0-alpha.1" -edition.workspace = true -authors = [ - "Radroots Authors", -] -rust-version.workspace = true -license.workspace = true -description = "nostr protocol account primitives and secret-vault integrations for the radroots sdk" -repository.workspace = true -homepage.workspace = true -documentation = "https://docs.rs/radroots-nostr-accounts" -readme.workspace = true - -[features] -default = ["std", "file-store", "memory-vault"] -std = [ - "dep:serde", - "dep:serde_json", - "dep:radroots-identity", - "dep:radroots-nostr-signer", - "dep:radroots-runtime", - "dep:radroots-secret-vault", -] -file-store = ["std"] -memory-vault = ["std", "radroots-secret-vault/memory-vault"] -os-keyring = ["std", "radroots-secret-vault/os-keyring"] -ndb-bridge = ["std", "dep:radroots-nostr-ndb"] - -[dependencies] -radroots-identity = { workspace = true, optional = true, default-features = false, features = [ - "std", - "profile", - "json-file", -] } -radroots-nostr-signer = { workspace = true, optional = true } -radroots-nostr-ndb = { workspace = true, optional = true, default-features = false, features = [ - "ndb", - "giftwrap", - "rt", -] } -radroots-secret-vault = { workspace = true, optional = true, default-features = false, features = [ - "std", -] } -radroots-runtime = { workspace = true, optional = true } -serde = { workspace = true, optional = true, features = ["derive"] } -serde_json = { workspace = true, optional = true } -thiserror = { workspace = true } -zeroize = { workspace = true } - -[dev-dependencies] -radroots-test-fixtures = { workspace = true } -tempfile = { workspace = true } diff --git a/crates/nostr-accounts/README.md b/crates/nostr-accounts/README.md @@ -1,14 +0,0 @@ -# radroots-nostr-accounts - -Nostr protocol account primitives and vault interfaces for the Rad Roots SDK. - -## Goals - -- define stable account, manager, store, and vault interfaces -- keep account selection and persistence behavior deterministic across environments -- support pluggable secret vault backends for local and keyring runtimes -- provide reusable account primitives for higher-level Rad Roots crates - -## License - -Licensed under AGPL-3.0. See LICENSE. diff --git a/crates/nostr-connect/Cargo.toml b/crates/nostr-connect/Cargo.toml @@ -1,24 +0,0 @@ -[package] -name = "radroots-nostr-connect" -version = "0.1.0-alpha.1" -edition.workspace = true -authors = [ - "Radroots Authors", -] -rust-version.workspace = true -license.workspace = true -description = "nip-46 protocol and uri primitives for the radroots sdk" -repository.workspace = true -homepage.workspace = true -documentation = "https://docs.rs/radroots-nostr-connect" -readme.workspace = true - -[dependencies] -nostr = { workspace = true } -serde = { workspace = true, features = ["derive"] } -serde_json = { workspace = true } -thiserror = { workspace = true } -url = { workspace = true } - -[dev-dependencies] -radroots-test-fixtures = { workspace = true } diff --git a/crates/nostr-ndb/Cargo.toml b/crates/nostr-ndb/Cargo.toml @@ -1,43 +0,0 @@ -[package] -name = "radroots-nostr-ndb" -version = "0.1.0-alpha.1" -edition.workspace = true -authors = [ - "Radroots Authors", -] -rust-version.workspace = true -license.workspace = true -description = "nostrdb adapter primitives and runtime interfaces for the radroots sdk" -repository.workspace = true -homepage.workspace = true -documentation = "https://docs.rs/radroots-nostr-ndb" -readme.workspace = true - -[features] -default = ["std", "rt", "ndb", "runtime-adapter"] -std = [] -rt = ["std", "dep:futures", "dep:tokio"] -ndb = ["std", "dep:nostrdb"] -runtime-adapter = ["std", "dep:radroots-nostr-runtime"] -giftwrap = ["std", "ndb"] - -[dependencies] -radroots-nostr = { workspace = true, default-features = false, features = [ - "std", -] } -radroots-nostr-runtime = { workspace = true, optional = true, default-features = false, features = [ - "std", - "rt", - "nostr-client", -] } -futures = { workspace = true, optional = true } -hex = { workspace = true } -nostrdb = { version = "0.9.0", optional = true } -serde_json = { workspace = true } -thiserror = { workspace = true } -tokio = { workspace = true, optional = true, features = ["rt", "sync", "time"] } - -[dev-dependencies] -radroots-test-fixtures = { workspace = true } -tokio = { workspace = true, features = ["macros", "rt", "sync", "time"] } -tempfile = { workspace = true } diff --git a/crates/nostr-ndb/README.md b/crates/nostr-ndb/README.md @@ -1,14 +0,0 @@ -# radroots-nostr-ndb - -Nostrdb adapter primitives and runtime interfaces for the Rad Roots SDK. - -## Goals - -- define stable nostrdb config, ingest, query, and subscription interfaces -- keep nostrdb storage and query behavior deterministic across runtimes -- support runtime adapter surfaces for shared event store integration -- provide reusable nostrdb primitives for higher-level Rad Roots crates - -## License - -Licensed under AGPL-3.0. See LICENSE. diff --git a/crates/nostr-ndb/src/lib.rs b/crates/nostr-ndb/src/lib.rs @@ -1,48 +0,0 @@ -#![cfg_attr(not(feature = "std"), no_std)] -#![forbid(unsafe_code)] - -#[cfg(not(feature = "std"))] -compile_error!("radroots-nostr-ndb requires the std feature"); - -extern crate alloc; - -#[cfg(feature = "ndb")] -pub mod config; -pub mod error; -#[cfg(feature = "ndb")] -pub mod filter; -#[cfg(feature = "ndb")] -pub mod ingest; -#[cfg(feature = "ndb")] -pub mod ndb; -#[cfg(feature = "ndb")] -pub mod query; -#[cfg(all(feature = "ndb", feature = "runtime-adapter"))] -pub mod runtime_adapter; -#[cfg(feature = "ndb")] -pub mod subscription; - -pub mod prelude { - #[cfg(feature = "ndb")] - pub use crate::config::RadrootsNostrNdbConfig; - pub use crate::error::RadrootsNostrNdbError; - #[cfg(feature = "ndb")] - pub use crate::filter::RadrootsNostrNdbFilterSpec; - #[cfg(feature = "ndb")] - pub use crate::ingest::RadrootsNostrNdbIngestSource; - #[cfg(feature = "ndb")] - pub use crate::ndb::RadrootsNostrNdb; - #[cfg(feature = "ndb")] - pub use crate::query::{ - RadrootsNostrNdbNote, RadrootsNostrNdbProfile, RadrootsNostrNdbQuerySpec, - }; - #[cfg(all(feature = "ndb", feature = "runtime-adapter"))] - pub use crate::runtime_adapter::RadrootsNostrNdbEventStoreAdapter; - #[cfg(all(feature = "ndb", feature = "rt"))] - pub use crate::subscription::RadrootsNostrNdbSubscriptionStream; - #[cfg(feature = "ndb")] - pub use crate::subscription::{ - RadrootsNostrNdbNoteKey, RadrootsNostrNdbSubscriptionHandle, - RadrootsNostrNdbSubscriptionSpec, - }; -} diff --git a/crates/nostr-runtime/Cargo.toml b/crates/nostr-runtime/Cargo.toml @@ -1,32 +0,0 @@ -[package] -name = "radroots-nostr-runtime" -version = "0.1.0-alpha.1" -edition.workspace = true -authors = [ - "Radroots Authors", -] -rust-version.workspace = true -license.workspace = true -description = "nostr runtime primitives and orchestration interfaces for the radroots sdk" -repository.workspace = true -homepage.workspace = true -documentation = "https://docs.rs/radroots-nostr-runtime" -readme.workspace = true - -[features] -default = ["std", "nostr-client", "rt"] -std = [] -rt = ["std", "dep:futures", "dep:tokio"] -nostr-client = ["std", "dep:radroots-nostr"] -nostr-ndb = ["nostr-client"] - -[dependencies] -radroots-nostr = { workspace = true, optional = true, default-features = true, features = [ - "client", -] } -futures = { workspace = true, optional = true } -thiserror = { workspace = true } -tokio = { workspace = true, optional = true, features = ["rt", "sync", "time"] } - -[dev-dependencies] -tokio = { workspace = true, features = ["macros", "rt", "sync", "time"] } diff --git a/crates/nostr-runtime/README.md b/crates/nostr-runtime/README.md @@ -1,14 +0,0 @@ -# radroots-nostr-runtime - -Nostr runtime primitives and orchestration interfaces for the Rad Roots SDK. - -## Goals - -- define stable runtime interfaces for events, subscriptions, and connections -- keep runtime orchestration behavior deterministic across supported environments -- support feature-gated client and async runtime integration surfaces -- provide reusable runtime primitives for higher-level Rad Roots crates - -## License - -Licensed under AGPL-3.0. See LICENSE. diff --git a/crates/nostr-signer/Cargo.toml b/crates/nostr-signer/Cargo.toml @@ -1,43 +0,0 @@ -[package] -name = "radroots-nostr-signer" -version = "0.1.0-alpha.1" -edition.workspace = true -authors = [ - "Radroots Authors", -] -rust-version.workspace = true -license.workspace = true -description = "transport-neutral signer domain models and persistence primitives for the radroots sdk" -repository.workspace = true -homepage.workspace = true -documentation = "https://docs.rs/radroots-nostr-signer" -readme.workspace = true - -[features] -default = [] -native = ["dep:radroots-sql-core", "radroots-sql-core/native"] - -[dependencies] -hex = { workspace = true } -nostr = { workspace = true } -radroots-identity = { workspace = true, default-features = false, features = [ - "std", - "profile", -] } -radroots-nostr-connect = { workspace = true } -radroots-runtime = { workspace = true } -radroots-sql-core = { workspace = true, optional = true } -serde = { workspace = true, features = ["derive"] } -serde_json = { workspace = true } -sha2 = { workspace = true } -thiserror = { workspace = true } -url = { workspace = true } -uuid = { workspace = true } - -[dev-dependencies] -radroots-sql-core = { workspace = true, features = ["native"] } -radroots-test-fixtures = { workspace = true } -tempfile = { workspace = true } - -[lints.rust] -unexpected_cfgs = { level = "warn", check-cfg = ['cfg(coverage_nightly)'] } diff --git a/crates/nostr/Cargo.toml b/crates/nostr/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "radroots-nostr" +name = "radroots_nostr" version = "0.1.0-alpha.1" edition.workspace = true authors = [ @@ -10,32 +10,32 @@ license.workspace = true description = "nostr protocol primitives and adapter interfaces for the radroots sdk" repository.workspace = true homepage.workspace = true -documentation = "https://docs.rs/radroots-nostr" +documentation = "https://docs.rs/radroots_nostr" readme.workspace = true [features] default = ["std"] std = [] -client = ["std", "dep:nostr-sdk", "dep:radroots-identity"] +client = ["std", "dep:nostr-sdk", "dep:radroots_identity"] codec = [ - "dep:radroots-events", - "dep:radroots-events-codec", - "radroots-events-codec/nostr", + "dep:radroots_events", + "dep:radroots_events_codec", + "radroots_events_codec/nostr", ] events = [ - "dep:radroots-events", - "dep:radroots-events-codec", - "radroots-events/std", - "radroots-events/serde", - "radroots-events-codec/std", + "dep:radroots_events", + "dep:radroots_events_codec", + "radroots_events/std", + "radroots_events/serde", + "radroots_events_codec/std", ] http = ["dep:reqwest"] nip17 = ["std", "codec", "nostr/nip44", "nostr/nip59"] [dependencies] -radroots-events = { workspace = true, optional = true, default-features = false } -radroots-events-codec = { workspace = true, optional = true, default-features = false } -radroots-identity = { workspace = true, optional = true, default-features = true } +radroots_events = { workspace = true, optional = true, default-features = false } +radroots_events_codec = { workspace = true, optional = true, default-features = false } +radroots_identity = { workspace = true, optional = true, default-features = true } nostr = { workspace = true, features = ["nip04"] } nostr-sdk = { workspace = true, optional = true } reqwest = { workspace = true, optional = true, default-features = false, features = [ @@ -47,5 +47,5 @@ serde_json = { workspace = true } thiserror = { workspace = true } [dev-dependencies] -radroots-test-fixtures = { workspace = true } +radroots_test_fixtures = { workspace = true } tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } diff --git a/crates/nostr/README.md b/crates/nostr/README.md @@ -1,4 +1,4 @@ -# radroots-nostr +# radroots_nostr Nostr protocol primitives and adapter interfaces for the Rad Roots SDK. diff --git a/crates/nostr_accounts/Cargo.toml b/crates/nostr_accounts/Cargo.toml @@ -0,0 +1,54 @@ +[package] +name = "radroots_nostr_accounts" +version = "0.1.0-alpha.1" +edition.workspace = true +authors = [ + "Radroots Authors", +] +rust-version.workspace = true +license.workspace = true +description = "nostr protocol account primitives and secret-vault integrations for the radroots sdk" +repository.workspace = true +homepage.workspace = true +documentation = "https://docs.rs/radroots_nostr_accounts" +readme.workspace = true + +[features] +default = ["std", "file-store", "memory-vault"] +std = [ + "dep:serde", + "dep:serde_json", + "dep:radroots_identity", + "dep:radroots_nostr_signer", + "dep:radroots_runtime", + "dep:radroots_secret_vault", +] +file-store = ["std"] +memory-vault = ["std", "radroots_secret_vault/memory-vault"] +os-keyring = ["std", "radroots_secret_vault/os-keyring"] +ndb-bridge = ["std", "dep:radroots_nostr_ndb"] + +[dependencies] +radroots_identity = { workspace = true, optional = true, default-features = false, features = [ + "std", + "profile", + "json-file", +] } +radroots_nostr_signer = { workspace = true, optional = true } +radroots_nostr_ndb = { workspace = true, optional = true, default-features = false, features = [ + "ndb", + "giftwrap", + "rt", +] } +radroots_secret_vault = { workspace = true, optional = true, default-features = false, features = [ + "std", +] } +radroots_runtime = { workspace = true, optional = true } +serde = { workspace = true, optional = true, features = ["derive"] } +serde_json = { workspace = true, optional = true } +thiserror = { workspace = true } +zeroize = { workspace = true } + +[dev-dependencies] +radroots_test_fixtures = { workspace = true } +tempfile = { workspace = true } diff --git a/crates/nostr_accounts/README.md b/crates/nostr_accounts/README.md @@ -0,0 +1,14 @@ +# radroots_nostr_accounts + +Nostr protocol account primitives and vault interfaces for the Rad Roots SDK. + +## Goals + +- define stable account, manager, store, and vault interfaces +- keep account selection and persistence behavior deterministic across environments +- support pluggable secret vault backends for local and keyring runtimes +- provide reusable account primitives for higher-level Rad Roots crates + +## License + +Licensed under AGPL-3.0. See LICENSE. diff --git a/crates/nostr-accounts/src/error.rs b/crates/nostr_accounts/src/error.rs diff --git a/crates/nostr-accounts/src/lib.rs b/crates/nostr_accounts/src/lib.rs diff --git a/crates/nostr-accounts/src/manager.rs b/crates/nostr_accounts/src/manager.rs diff --git a/crates/nostr-accounts/src/model.rs b/crates/nostr_accounts/src/model.rs diff --git a/crates/nostr-accounts/src/ndb_bridge.rs b/crates/nostr_accounts/src/ndb_bridge.rs diff --git a/crates/nostr-accounts/src/store.rs b/crates/nostr_accounts/src/store.rs diff --git a/crates/nostr-accounts/src/vault.rs b/crates/nostr_accounts/src/vault.rs diff --git a/crates/nostr_connect/Cargo.toml b/crates/nostr_connect/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "radroots_nostr_connect" +version = "0.1.0-alpha.1" +edition.workspace = true +authors = [ + "Radroots Authors", +] +rust-version.workspace = true +license.workspace = true +description = "nip-46 protocol and uri primitives for the radroots sdk" +repository.workspace = true +homepage.workspace = true +documentation = "https://docs.rs/radroots_nostr_connect" +readme.workspace = true + +[dependencies] +nostr = { workspace = true } +serde = { workspace = true, features = ["derive"] } +serde_json = { workspace = true } +thiserror = { workspace = true } +url = { workspace = true } + +[dev-dependencies] +radroots_test_fixtures = { workspace = true } diff --git a/crates/nostr-connect/src/error.rs b/crates/nostr_connect/src/error.rs diff --git a/crates/nostr-connect/src/lib.rs b/crates/nostr_connect/src/lib.rs diff --git a/crates/nostr-connect/src/message.rs b/crates/nostr_connect/src/message.rs diff --git a/crates/nostr-connect/src/method.rs b/crates/nostr_connect/src/method.rs diff --git a/crates/nostr-connect/src/permission.rs b/crates/nostr_connect/src/permission.rs diff --git a/crates/nostr-connect/src/uri.rs b/crates/nostr_connect/src/uri.rs diff --git a/crates/nostr-connect/tests/coverage.rs b/crates/nostr_connect/tests/coverage.rs diff --git a/crates/nostr-connect/tests/protocol.rs b/crates/nostr_connect/tests/protocol.rs diff --git a/crates/nostr_ndb/Cargo.toml b/crates/nostr_ndb/Cargo.toml @@ -0,0 +1,43 @@ +[package] +name = "radroots_nostr_ndb" +version = "0.1.0-alpha.1" +edition.workspace = true +authors = [ + "Radroots Authors", +] +rust-version.workspace = true +license.workspace = true +description = "nostrdb adapter primitives and runtime interfaces for the radroots sdk" +repository.workspace = true +homepage.workspace = true +documentation = "https://docs.rs/radroots_nostr_ndb" +readme.workspace = true + +[features] +default = ["std", "rt", "ndb", "runtime-adapter"] +std = [] +rt = ["std", "dep:futures", "dep:tokio"] +ndb = ["std", "dep:nostrdb"] +runtime-adapter = ["std", "dep:radroots_nostr_runtime"] +giftwrap = ["std", "ndb"] + +[dependencies] +radroots_nostr = { workspace = true, default-features = false, features = [ + "std", +] } +radroots_nostr_runtime = { workspace = true, optional = true, default-features = false, features = [ + "std", + "rt", + "nostr-client", +] } +futures = { workspace = true, optional = true } +hex = { workspace = true } +nostrdb = { version = "0.9.0", optional = true } +serde_json = { workspace = true } +thiserror = { workspace = true } +tokio = { workspace = true, optional = true, features = ["rt", "sync", "time"] } + +[dev-dependencies] +radroots_test_fixtures = { workspace = true } +tokio = { workspace = true, features = ["macros", "rt", "sync", "time"] } +tempfile = { workspace = true } diff --git a/crates/nostr_ndb/README.md b/crates/nostr_ndb/README.md @@ -0,0 +1,14 @@ +# radroots_nostr_ndb + +Nostrdb adapter primitives and runtime interfaces for the Rad Roots SDK. + +## Goals + +- define stable nostrdb config, ingest, query, and subscription interfaces +- keep nostrdb storage and query behavior deterministic across runtimes +- support runtime adapter surfaces for shared event store integration +- provide reusable nostrdb primitives for higher-level Rad Roots crates + +## License + +Licensed under AGPL-3.0. See LICENSE. diff --git a/crates/nostr-ndb/src/config.rs b/crates/nostr_ndb/src/config.rs diff --git a/crates/nostr-ndb/src/error.rs b/crates/nostr_ndb/src/error.rs diff --git a/crates/nostr-ndb/src/filter.rs b/crates/nostr_ndb/src/filter.rs diff --git a/crates/nostr-ndb/src/ingest.rs b/crates/nostr_ndb/src/ingest.rs diff --git a/crates/nostr_ndb/src/lib.rs b/crates/nostr_ndb/src/lib.rs @@ -0,0 +1,48 @@ +#![cfg_attr(not(feature = "std"), no_std)] +#![forbid(unsafe_code)] + +#[cfg(not(feature = "std"))] +compile_error!("radroots_nostr_ndb requires the std feature"); + +extern crate alloc; + +#[cfg(feature = "ndb")] +pub mod config; +pub mod error; +#[cfg(feature = "ndb")] +pub mod filter; +#[cfg(feature = "ndb")] +pub mod ingest; +#[cfg(feature = "ndb")] +pub mod ndb; +#[cfg(feature = "ndb")] +pub mod query; +#[cfg(all(feature = "ndb", feature = "runtime-adapter"))] +pub mod runtime_adapter; +#[cfg(feature = "ndb")] +pub mod subscription; + +pub mod prelude { + #[cfg(feature = "ndb")] + pub use crate::config::RadrootsNostrNdbConfig; + pub use crate::error::RadrootsNostrNdbError; + #[cfg(feature = "ndb")] + pub use crate::filter::RadrootsNostrNdbFilterSpec; + #[cfg(feature = "ndb")] + pub use crate::ingest::RadrootsNostrNdbIngestSource; + #[cfg(feature = "ndb")] + pub use crate::ndb::RadrootsNostrNdb; + #[cfg(feature = "ndb")] + pub use crate::query::{ + RadrootsNostrNdbNote, RadrootsNostrNdbProfile, RadrootsNostrNdbQuerySpec, + }; + #[cfg(all(feature = "ndb", feature = "runtime-adapter"))] + pub use crate::runtime_adapter::RadrootsNostrNdbEventStoreAdapter; + #[cfg(all(feature = "ndb", feature = "rt"))] + pub use crate::subscription::RadrootsNostrNdbSubscriptionStream; + #[cfg(feature = "ndb")] + pub use crate::subscription::{ + RadrootsNostrNdbNoteKey, RadrootsNostrNdbSubscriptionHandle, + RadrootsNostrNdbSubscriptionSpec, + }; +} diff --git a/crates/nostr-ndb/src/ndb.rs b/crates/nostr_ndb/src/ndb.rs diff --git a/crates/nostr-ndb/src/query.rs b/crates/nostr_ndb/src/query.rs diff --git a/crates/nostr-ndb/src/runtime_adapter.rs b/crates/nostr_ndb/src/runtime_adapter.rs diff --git a/crates/nostr-ndb/src/subscription.rs b/crates/nostr_ndb/src/subscription.rs diff --git a/crates/nostr_runtime/Cargo.toml b/crates/nostr_runtime/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "radroots_nostr_runtime" +version = "0.1.0-alpha.1" +edition.workspace = true +authors = [ + "Radroots Authors", +] +rust-version.workspace = true +license.workspace = true +description = "nostr runtime primitives and orchestration interfaces for the radroots sdk" +repository.workspace = true +homepage.workspace = true +documentation = "https://docs.rs/radroots_nostr_runtime" +readme.workspace = true + +[features] +default = ["std", "nostr-client", "rt"] +std = [] +rt = ["std", "dep:futures", "dep:tokio"] +nostr-client = ["std", "dep:radroots_nostr"] +nostr-ndb = ["nostr-client"] + +[dependencies] +radroots_nostr = { workspace = true, optional = true, default-features = true, features = [ + "client", +] } +futures = { workspace = true, optional = true } +thiserror = { workspace = true } +tokio = { workspace = true, optional = true, features = ["rt", "sync", "time"] } + +[dev-dependencies] +tokio = { workspace = true, features = ["macros", "rt", "sync", "time"] } diff --git a/crates/nostr_runtime/README.md b/crates/nostr_runtime/README.md @@ -0,0 +1,14 @@ +# radroots_nostr_runtime + +Nostr runtime primitives and orchestration interfaces for the Rad Roots SDK. + +## Goals + +- define stable runtime interfaces for events, subscriptions, and connections +- keep runtime orchestration behavior deterministic across supported environments +- support feature-gated client and async runtime integration surfaces +- provide reusable runtime primitives for higher-level Rad Roots crates + +## License + +Licensed under AGPL-3.0. See LICENSE. diff --git a/crates/nostr-runtime/src/error.rs b/crates/nostr_runtime/src/error.rs diff --git a/crates/nostr-runtime/src/lib.rs b/crates/nostr_runtime/src/lib.rs diff --git a/crates/nostr-runtime/src/runtime.rs b/crates/nostr_runtime/src/runtime.rs diff --git a/crates/nostr-runtime/src/store.rs b/crates/nostr_runtime/src/store.rs diff --git a/crates/nostr-runtime/src/types.rs b/crates/nostr_runtime/src/types.rs diff --git a/crates/nostr_signer/Cargo.toml b/crates/nostr_signer/Cargo.toml @@ -0,0 +1,43 @@ +[package] +name = "radroots_nostr_signer" +version = "0.1.0-alpha.1" +edition.workspace = true +authors = [ + "Radroots Authors", +] +rust-version.workspace = true +license.workspace = true +description = "transport-neutral signer domain models and persistence primitives for the radroots sdk" +repository.workspace = true +homepage.workspace = true +documentation = "https://docs.rs/radroots_nostr_signer" +readme.workspace = true + +[features] +default = [] +native = ["dep:radroots_sql_core", "radroots_sql_core/native"] + +[dependencies] +hex = { workspace = true } +nostr = { workspace = true } +radroots_identity = { workspace = true, default-features = false, features = [ + "std", + "profile", +] } +radroots_nostr_connect = { workspace = true } +radroots_runtime = { workspace = true } +radroots_sql_core = { workspace = true, optional = true } +serde = { workspace = true, features = ["derive"] } +serde_json = { workspace = true } +sha2 = { workspace = true } +thiserror = { workspace = true } +url = { workspace = true } +uuid = { workspace = true } + +[dev-dependencies] +radroots_sql_core = { workspace = true, features = ["native"] } +radroots_test_fixtures = { workspace = true } +tempfile = { workspace = true } + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(coverage_nightly)'] } diff --git a/crates/nostr-signer/migrations/0000_init.down.sql b/crates/nostr_signer/migrations/0000_init.down.sql diff --git a/crates/nostr-signer/migrations/0000_init.up.sql b/crates/nostr_signer/migrations/0000_init.up.sql diff --git a/crates/nostr-signer/migrations/0001_publish_workflows.down.sql b/crates/nostr_signer/migrations/0001_publish_workflows.down.sql diff --git a/crates/nostr-signer/migrations/0001_publish_workflows.up.sql b/crates/nostr_signer/migrations/0001_publish_workflows.up.sql diff --git a/crates/nostr-signer/src/backend.rs b/crates/nostr_signer/src/backend.rs diff --git a/crates/nostr-signer/src/capability.rs b/crates/nostr_signer/src/capability.rs diff --git a/crates/nostr-signer/src/error.rs b/crates/nostr_signer/src/error.rs diff --git a/crates/nostr-signer/src/evaluation.rs b/crates/nostr_signer/src/evaluation.rs diff --git a/crates/nostr-signer/src/lib.rs b/crates/nostr_signer/src/lib.rs diff --git a/crates/nostr-signer/src/manager.rs b/crates/nostr_signer/src/manager.rs diff --git a/crates/nostr-signer/src/migrations.rs b/crates/nostr_signer/src/migrations.rs diff --git a/crates/nostr-signer/src/model.rs b/crates/nostr_signer/src/model.rs diff --git a/crates/nostr-signer/src/sqlite.rs b/crates/nostr_signer/src/sqlite.rs diff --git a/crates/nostr-signer/src/store.rs b/crates/nostr_signer/src/store.rs diff --git a/crates/nostr-signer/src/test_support.rs b/crates/nostr_signer/src/test_support.rs diff --git a/crates/protected-store/Cargo.toml b/crates/protected-store/Cargo.toml @@ -1,26 +0,0 @@ -[package] -name = "radroots-protected-store" -version = "0.1.0-alpha.1" -edition.workspace = true -authors = [ - "Radroots Authors", -] -rust-version.workspace = true -license.workspace = true -description = "versioned authenticated local protected-store envelopes for radroots runtimes" -repository.workspace = true -homepage.workspace = true -documentation = "https://docs.rs/radroots-protected-store" -readme = "README.md" - -[features] -default = [] -std = ["radroots-secret-vault/std"] - -[dependencies] -chacha20poly1305 = { workspace = true } -getrandom = { workspace = true } -radroots-secret-vault = { workspace = true } -serde = { workspace = true, features = ["derive"] } -serde_json = { workspace = true } -zeroize = { workspace = true } diff --git a/crates/protected-store/README.md b/crates/protected-store/README.md @@ -1,3 +0,0 @@ -# radroots-protected-store - -Versioned authenticated local protected-store envelopes for Rad Roots runtimes. diff --git a/crates/protected_store/Cargo.toml b/crates/protected_store/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "radroots_protected_store" +version = "0.1.0-alpha.1" +edition.workspace = true +authors = [ + "Radroots Authors", +] +rust-version.workspace = true +license.workspace = true +description = "versioned authenticated local protected-store envelopes for radroots runtimes" +repository.workspace = true +homepage.workspace = true +documentation = "https://docs.rs/radroots_protected_store" +readme = "README.md" + +[features] +default = [] +std = ["radroots_secret_vault/std"] + +[dependencies] +chacha20poly1305 = { workspace = true } +getrandom = { workspace = true } +radroots_secret_vault = { workspace = true } +serde = { workspace = true, features = ["derive"] } +serde_json = { workspace = true } +zeroize = { workspace = true } diff --git a/crates/protected_store/README.md b/crates/protected_store/README.md @@ -0,0 +1,3 @@ +# radroots_protected_store + +Versioned authenticated local protected-store envelopes for Rad Roots runtimes. diff --git a/crates/protected-store/src/error.rs b/crates/protected_store/src/error.rs diff --git a/crates/protected-store/src/lib.rs b/crates/protected_store/src/lib.rs diff --git a/crates/replica-db-schema/Cargo.toml b/crates/replica-db-schema/Cargo.toml @@ -1,28 +0,0 @@ -[package] -name = "radroots-replica-db-schema" -version = "0.1.0-alpha.1" -edition.workspace = true -authors = [ - "Radroots Authors", -] -rust-version.workspace = true -license.workspace = true -description = "replica schema models and relational interfaces for radroots data layers" -repository.workspace = true -homepage.workspace = true -documentation = "https://docs.rs/radroots-replica-db-schema" -readme.workspace = true -build = "build.rs" - -[lib] -crate-type = ["rlib"] - -[features] -default = [] -ts-rs = ["dep:ts-rs"] - -[dependencies] -serde = { workspace = true, features = ["derive"] } -serde_json = { workspace = true } -ts-rs = { workspace = true, optional = true } -radroots-types = { workspace = true } diff --git a/crates/replica-db-schema/README.md b/crates/replica-db-schema/README.md @@ -1,14 +0,0 @@ -# radroots-replica-db-schema - -Replica schema models and relational interfaces for Rad Roots data layers. - -## Goals - -- define stable schema models and relation interfaces for replica data -- keep schema serialization and type export deterministic across targets -- support reusable schema contracts for database and wasm crates -- provide reusable schema primitives for higher-level Rad Roots crates - -## License - -Licensed under AGPL-3.0. See LICENSE. diff --git a/crates/replica-db-wasm/Cargo.toml b/crates/replica-db-wasm/Cargo.toml @@ -1,37 +0,0 @@ -[package] -name = "radroots-replica-db-wasm" -version = "0.1.0-alpha.1" -edition.workspace = true -authors = [ - "Radroots Authors", -] -rust-version.workspace = true -license.workspace = true -description = "wasm bindings for replica database runtime interfaces in radroots data layers" -repository.workspace = true -homepage.workspace = true -documentation = "https://docs.rs/radroots-replica-db-wasm" -readme.workspace = true - -[lib] -crate-type = ["cdylib", "rlib"] - -[dependencies] -radroots-sql-core = { workspace = true, features = ["bridge"] } -radroots-sql-wasm-core = { workspace = true, default-features = false, features = [ - "bridge", -] } -radroots-replica-db = { workspace = true } -radroots-replica-db-schema = { workspace = true } -radroots-replica-sync = { workspace = true } -js-sys = { workspace = true } -serde = { workspace = true, features = ["derive"] } -serde_json = { workspace = true } -serde-wasm-bindgen = { workspace = true } -wasm-bindgen = { workspace = true } - -[dev-dependencies] -wasm-bindgen-test = { workspace = true } - -[lints.rust] -unexpected_cfgs = { level = "warn", check-cfg = ['cfg(coverage_nightly)'] } diff --git a/crates/replica-db-wasm/README.md b/crates/replica-db-wasm/README.md @@ -1,14 +0,0 @@ -# radroots-replica-db-wasm - -Wasm bindings for replica database runtime interfaces in Rad Roots data layers. - -## Goals - -- define stable wasm bindings for replica CRUD, migration, backup, and export flows -- keep export/import and manifest marshaling deterministic in web runtimes -- support integration with wasm SQL executor surfaces -- provide reusable data-layer wasm primitives for higher-level Rad Roots crates - -## License - -Licensed under AGPL-3.0. See LICENSE. diff --git a/crates/replica-db/Cargo.toml b/crates/replica-db/Cargo.toml @@ -1,36 +0,0 @@ -[package] -name = "radroots-replica-db" -version = "0.1.0-alpha.1" -edition.workspace = true -authors = [ - "Radroots Authors", -] -rust-version.workspace = true -license.workspace = true -description = "replica sql runtime and migration interfaces for radroots data layers" -repository.workspace = true -homepage.workspace = true -documentation = "https://docs.rs/radroots-replica-db" -readme.workspace = true - -[lib] -crate-type = ["rlib"] - -[features] -default = [] -web = ["radroots-sql-core/web"] -native = ["radroots-sql-core/native"] -embedded = ["radroots-sql-core/embedded"] -coverage-minimal = [] - -[dependencies] -radroots-sql-core = { workspace = true } -radroots-replica-db-schema = { workspace = true } -radroots-types = { workspace = true } -serde = { workspace = true } -serde_json = { workspace = true } -hex = { workspace = true } -sha2 = { workspace = true } - -[dev-dependencies] -radroots-sql-core = { workspace = true, features = ["native"] } diff --git a/crates/replica-db/README.md b/crates/replica-db/README.md @@ -1,14 +0,0 @@ -# radroots-replica-db - -Replica SQL runtime and migration interfaces for Rad Roots data layers. - -## Goals - -- define stable SQL-backed interfaces for replica model operations -- keep migration, backup, and restore behavior deterministic across targets -- support export and manifest generation for synchronized replica snapshots -- provide reusable database primitives for higher-level Rad Roots crates - -## License - -Licensed under AGPL-3.0. See LICENSE. diff --git a/crates/replica-sync-wasm/Cargo.toml b/crates/replica-sync-wasm/Cargo.toml @@ -1,36 +0,0 @@ -[package] -name = "radroots-replica-sync-wasm" -version = "0.1.0-alpha.1" -edition.workspace = true -authors = [ - "Radroots Authors", -] -rust-version.workspace = true -license.workspace = true -description = "wasm bindings for replica synchronization interfaces in radroots data layers" -repository.workspace = true -homepage.workspace = true -documentation = "https://docs.rs/radroots-replica-sync-wasm" -readme.workspace = true - -[lib] -crate-type = ["cdylib", "rlib"] - -[dependencies] -base64 = { workspace = true } -radroots-events = { workspace = true, default-features = false, features = [ - "serde", -] } -radroots-sql-core = { workspace = true, features = ["bridge"] } -radroots-sql-wasm-core = { workspace = true, default-features = false, features = [ - "bridge", -] } -radroots-replica-sync = { workspace = true } -serde = { workspace = true, features = ["derive"] } -serde_json = { workspace = true } -serde-wasm-bindgen = { workspace = true } -uuid = { workspace = true, features = ["js"] } -wasm-bindgen = { workspace = true } - -[lints.rust] -unexpected_cfgs = { level = "warn", check-cfg = ['cfg(coverage_nightly)'] } diff --git a/crates/replica-sync-wasm/README.md b/crates/replica-sync-wasm/README.md @@ -1,14 +0,0 @@ -# radroots-replica-sync-wasm - -Wasm bindings for replica synchronization interfaces in Rad Roots data layers. - -## Goals - -- define stable wasm bindings for replica sync and ingest flows -- keep request parsing and result marshaling deterministic in web runtimes -- support integration with wasm SQL executor surfaces -- provide reusable sync bridge primitives for higher-level Rad Roots crates - -## License - -Licensed under AGPL-3.0. See LICENSE. diff --git a/crates/replica-sync/Cargo.toml b/crates/replica-sync/Cargo.toml @@ -1,54 +0,0 @@ -[package] -name = "radroots-replica-sync" -version = "0.1.0-alpha.1" -edition.workspace = true -authors = [ - "Radroots Authors", -] -rust-version.workspace = true -license.workspace = true -description = "replica event ingest and synchronization interfaces for radroots data layers" -repository.workspace = true -homepage.workspace = true -documentation = "https://docs.rs/radroots-replica-sync" -readme.workspace = true - -[lib] -crate-type = ["rlib"] - -[features] -default = ["std"] -std = [ - "radroots-events/std", - "radroots-events-codec/std", - "dep:base64", - "dep:uuid", -] - -[dependencies] -radroots-events = { workspace = true, default-features = false, features = [ - "serde", -] } -radroots-events-codec = { workspace = true, default-features = false, features = [ - "serde_json", -] } -radroots-sql-core = { workspace = true } -radroots-replica-db-schema = { workspace = true } -radroots-replica-db = { workspace = true } -radroots-types = { workspace = true } -hex = { workspace = true } -serde = { workspace = true, default-features = false, features = [ - "alloc", - "derive", -] } -serde_json = { workspace = true, default-features = false, features = [ - "alloc", -] } -sha2 = { workspace = true, default-features = false } -base64 = { workspace = true, optional = true } -uuid = { workspace = true, optional = true } - -[dev-dependencies] -radroots-sql-core = { workspace = true, features = ["native"] } -radroots-replica-db = { workspace = true } -serde_json = { workspace = true } diff --git a/crates/replica-sync/README.md b/crates/replica-sync/README.md @@ -1,14 +0,0 @@ -# radroots-replica-sync - -Replica event ingest and synchronization interfaces for Rad Roots data layers. - -## Goals - -- define stable interfaces for event ingest, emit, and sync status -- keep ingest and bundle assembly behavior deterministic across targets -- support canonical transfer payloads for replica synchronization flows -- provide reusable synchronization primitives for higher-level Rad Roots crates - -## License - -Licensed under AGPL-3.0. See LICENSE. diff --git a/crates/replica_db/Cargo.toml b/crates/replica_db/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "radroots_replica_db" +version = "0.1.0-alpha.1" +edition.workspace = true +authors = [ + "Radroots Authors", +] +rust-version.workspace = true +license.workspace = true +description = "replica sql runtime and migration interfaces for radroots data layers" +repository.workspace = true +homepage.workspace = true +documentation = "https://docs.rs/radroots_replica_db" +readme.workspace = true + +[lib] +crate-type = ["rlib"] + +[features] +default = [] +web = ["radroots_sql_core/web"] +native = ["radroots_sql_core/native"] +embedded = ["radroots_sql_core/embedded"] +coverage-minimal = [] + +[dependencies] +radroots_sql_core = { workspace = true } +radroots_replica_db_schema = { workspace = true } +radroots_types = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +hex = { workspace = true } +sha2 = { workspace = true } + +[dev-dependencies] +radroots_sql_core = { workspace = true, features = ["native"] } diff --git a/crates/replica_db/README.md b/crates/replica_db/README.md @@ -0,0 +1,14 @@ +# radroots_replica_db + +Replica SQL runtime and migration interfaces for Rad Roots data layers. + +## Goals + +- define stable SQL-backed interfaces for replica model operations +- keep migration, backup, and restore behavior deterministic across targets +- support export and manifest generation for synchronized replica snapshots +- provide reusable database primitives for higher-level Rad Roots crates + +## License + +Licensed under AGPL-3.0. See LICENSE. diff --git a/crates/replica-db/migrations/0000_init.down.sql b/crates/replica_db/migrations/0000_init.down.sql diff --git a/crates/replica-db/migrations/0000_init.up.sql b/crates/replica_db/migrations/0000_init.up.sql diff --git a/crates/replica-db/migrations/0001_log_error.down.sql b/crates/replica_db/migrations/0001_log_error.down.sql diff --git a/crates/replica-db/migrations/0001_log_error.up.sql b/crates/replica_db/migrations/0001_log_error.up.sql diff --git a/crates/replica-db/migrations/0002_farm.down.sql b/crates/replica_db/migrations/0002_farm.down.sql diff --git a/crates/replica-db/migrations/0002_farm.up.sql b/crates/replica_db/migrations/0002_farm.up.sql diff --git a/crates/replica-db/migrations/0003_gcs_location.down.sql b/crates/replica_db/migrations/0003_gcs_location.down.sql diff --git a/crates/replica-db/migrations/0003_gcs_location.up.sql b/crates/replica_db/migrations/0003_gcs_location.up.sql diff --git a/crates/replica-db/migrations/0004_trade_product.down.sql b/crates/replica_db/migrations/0004_trade_product.down.sql diff --git a/crates/replica-db/migrations/0004_trade_product.up.sql b/crates/replica_db/migrations/0004_trade_product.up.sql diff --git a/crates/replica-db/migrations/0005_nostr_profile.down.sql b/crates/replica_db/migrations/0005_nostr_profile.down.sql diff --git a/crates/replica-db/migrations/0005_nostr_profile.up.sql b/crates/replica_db/migrations/0005_nostr_profile.up.sql diff --git a/crates/replica-db/migrations/0006_nostr_relay.down.sql b/crates/replica_db/migrations/0006_nostr_relay.down.sql diff --git a/crates/replica-db/migrations/0006_nostr_relay.up.sql b/crates/replica_db/migrations/0006_nostr_relay.up.sql diff --git a/crates/replica-db/migrations/0007_media_image.down.sql b/crates/replica_db/migrations/0007_media_image.down.sql diff --git a/crates/replica-db/migrations/0007_media_image.up.sql b/crates/replica_db/migrations/0007_media_image.up.sql diff --git a/crates/replica-db/migrations/0008_farm_gcs_location.down.sql b/crates/replica_db/migrations/0008_farm_gcs_location.down.sql diff --git a/crates/replica-db/migrations/0008_farm_gcs_location.up.sql b/crates/replica_db/migrations/0008_farm_gcs_location.up.sql diff --git a/crates/replica-db/migrations/0009_nostr_profile_relay.down.sql b/crates/replica_db/migrations/0009_nostr_profile_relay.down.sql diff --git a/crates/replica-db/migrations/0009_nostr_profile_relay.up.sql b/crates/replica_db/migrations/0009_nostr_profile_relay.up.sql diff --git a/crates/replica-db/migrations/0010_trade_product_location.down.sql b/crates/replica_db/migrations/0010_trade_product_location.down.sql diff --git a/crates/replica-db/migrations/0010_trade_product_location.up.sql b/crates/replica_db/migrations/0010_trade_product_location.up.sql diff --git a/crates/replica-db/migrations/0011_trade_product_media.down.sql b/crates/replica_db/migrations/0011_trade_product_media.down.sql diff --git a/crates/replica-db/migrations/0011_trade_product_media.up.sql b/crates/replica_db/migrations/0011_trade_product_media.up.sql diff --git a/crates/replica-db/migrations/0012_plot.down.sql b/crates/replica_db/migrations/0012_plot.down.sql diff --git a/crates/replica-db/migrations/0012_plot.up.sql b/crates/replica_db/migrations/0012_plot.up.sql diff --git a/crates/replica-db/migrations/0013_plot_gcs_location.down.sql b/crates/replica_db/migrations/0013_plot_gcs_location.down.sql diff --git a/crates/replica-db/migrations/0013_plot_gcs_location.up.sql b/crates/replica_db/migrations/0013_plot_gcs_location.up.sql diff --git a/crates/replica-db/migrations/0014_farm_tag.down.sql b/crates/replica_db/migrations/0014_farm_tag.down.sql diff --git a/crates/replica-db/migrations/0014_farm_tag.up.sql b/crates/replica_db/migrations/0014_farm_tag.up.sql diff --git a/crates/replica-db/migrations/0015_plot_tag.down.sql b/crates/replica_db/migrations/0015_plot_tag.down.sql diff --git a/crates/replica-db/migrations/0015_plot_tag.up.sql b/crates/replica_db/migrations/0015_plot_tag.up.sql diff --git a/crates/replica-db/migrations/0016_farm_member.down.sql b/crates/replica_db/migrations/0016_farm_member.down.sql diff --git a/crates/replica-db/migrations/0016_farm_member.up.sql b/crates/replica_db/migrations/0016_farm_member.up.sql diff --git a/crates/replica-db/migrations/0017_farm_member_claim.down.sql b/crates/replica_db/migrations/0017_farm_member_claim.down.sql diff --git a/crates/replica-db/migrations/0017_farm_member_claim.up.sql b/crates/replica_db/migrations/0017_farm_member_claim.up.sql diff --git a/crates/replica-db/migrations/0018_nostr_event_state.down.sql b/crates/replica_db/migrations/0018_nostr_event_state.down.sql diff --git a/crates/replica-db/migrations/0018_nostr_event_state.up.sql b/crates/replica_db/migrations/0018_nostr_event_state.up.sql diff --git a/crates/replica-db/migrations/0019_repair_missing_indexes.down.sql b/crates/replica_db/migrations/0019_repair_missing_indexes.down.sql diff --git a/crates/replica-db/migrations/0019_repair_missing_indexes.up.sql b/crates/replica_db/migrations/0019_repair_missing_indexes.up.sql diff --git a/crates/replica-db/src/backup.rs b/crates/replica_db/src/backup.rs diff --git a/crates/replica-db/src/export.rs b/crates/replica_db/src/export.rs diff --git a/crates/replica-db/src/lib.rs b/crates/replica_db/src/lib.rs diff --git a/crates/replica-db/src/migrations.rs b/crates/replica_db/src/migrations.rs diff --git a/crates/replica-db/src/models/farm.rs b/crates/replica_db/src/models/farm.rs diff --git a/crates/replica-db/src/models/farm_gcs_location.rs b/crates/replica_db/src/models/farm_gcs_location.rs diff --git a/crates/replica-db/src/models/farm_member.rs b/crates/replica_db/src/models/farm_member.rs diff --git a/crates/replica-db/src/models/farm_member_claim.rs b/crates/replica_db/src/models/farm_member_claim.rs diff --git a/crates/replica-db/src/models/farm_tag.rs b/crates/replica_db/src/models/farm_tag.rs diff --git a/crates/replica-db/src/models/gcs_location.rs b/crates/replica_db/src/models/gcs_location.rs diff --git a/crates/replica-db/src/models/log_error.rs b/crates/replica_db/src/models/log_error.rs diff --git a/crates/replica-db/src/models/media_image.rs b/crates/replica_db/src/models/media_image.rs diff --git a/crates/replica-db-schema/src/models/mod.rs b/crates/replica_db/src/models/mod.rs diff --git a/crates/replica-db/src/models/nostr_event_state.rs b/crates/replica_db/src/models/nostr_event_state.rs diff --git a/crates/replica-db/src/models/nostr_profile.rs b/crates/replica_db/src/models/nostr_profile.rs diff --git a/crates/replica-db/src/models/nostr_profile_relay.rs b/crates/replica_db/src/models/nostr_profile_relay.rs diff --git a/crates/replica-db/src/models/nostr_relay.rs b/crates/replica_db/src/models/nostr_relay.rs diff --git a/crates/replica-db/src/models/plot.rs b/crates/replica_db/src/models/plot.rs diff --git a/crates/replica-db/src/models/plot_gcs_location.rs b/crates/replica_db/src/models/plot_gcs_location.rs diff --git a/crates/replica-db/src/models/plot_tag.rs b/crates/replica_db/src/models/plot_tag.rs diff --git a/crates/replica-db/src/models/trade_product.rs b/crates/replica_db/src/models/trade_product.rs diff --git a/crates/replica-db/src/models/trade_product_location.rs b/crates/replica_db/src/models/trade_product_location.rs diff --git a/crates/replica-db/src/models/trade_product_media.rs b/crates/replica_db/src/models/trade_product_media.rs diff --git a/crates/replica-db/tests/backup_export_paths.rs b/crates/replica_db/tests/backup_export_paths.rs diff --git a/crates/replica-db/tests/error_paths.rs b/crates/replica_db/tests/error_paths.rs diff --git a/crates/replica-db/tests/full_mode.rs b/crates/replica_db/tests/full_mode.rs diff --git a/crates/replica-db/tests/migration_repairs.rs b/crates/replica_db/tests/migration_repairs.rs diff --git a/crates/replica-db/tests/region_scripted_paths.rs b/crates/replica_db/tests/region_scripted_paths.rs diff --git a/crates/replica_db_schema/Cargo.toml b/crates/replica_db_schema/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "radroots_replica_db_schema" +version = "0.1.0-alpha.1" +edition.workspace = true +authors = [ + "Radroots Authors", +] +rust-version.workspace = true +license.workspace = true +description = "replica schema models and relational interfaces for radroots data layers" +repository.workspace = true +homepage.workspace = true +documentation = "https://docs.rs/radroots_replica_db_schema" +readme.workspace = true +build = "build.rs" + +[lib] +crate-type = ["rlib"] + +[features] +default = [] +ts-rs = ["dep:ts-rs"] + +[dependencies] +serde = { workspace = true, features = ["derive"] } +serde_json = { workspace = true } +ts-rs = { workspace = true, optional = true } +radroots_types = { workspace = true } diff --git a/crates/replica_db_schema/README.md b/crates/replica_db_schema/README.md @@ -0,0 +1,14 @@ +# radroots_replica_db_schema + +Replica schema models and relational interfaces for Rad Roots data layers. + +## Goals + +- define stable schema models and relation interfaces for replica data +- keep schema serialization and type export deterministic across targets +- support reusable schema contracts for database and wasm crates +- provide reusable schema primitives for higher-level Rad Roots crates + +## License + +Licensed under AGPL-3.0. See LICENSE. diff --git a/crates/replica-db-schema/build.rs b/crates/replica_db_schema/build.rs diff --git a/crates/replica-db-schema/src/lib.rs b/crates/replica_db_schema/src/lib.rs diff --git a/crates/replica-db-schema/src/models/farm.rs b/crates/replica_db_schema/src/models/farm.rs diff --git a/crates/replica-db-schema/src/models/farm_gcs_location.rs b/crates/replica_db_schema/src/models/farm_gcs_location.rs diff --git a/crates/replica-db-schema/src/models/farm_member.rs b/crates/replica_db_schema/src/models/farm_member.rs diff --git a/crates/replica-db-schema/src/models/farm_member_claim.rs b/crates/replica_db_schema/src/models/farm_member_claim.rs diff --git a/crates/replica-db-schema/src/models/farm_tag.rs b/crates/replica_db_schema/src/models/farm_tag.rs diff --git a/crates/replica-db-schema/src/models/gcs_location.rs b/crates/replica_db_schema/src/models/gcs_location.rs diff --git a/crates/replica-db-schema/src/models/log_error.rs b/crates/replica_db_schema/src/models/log_error.rs diff --git a/crates/replica-db-schema/src/models/media_image.rs b/crates/replica_db_schema/src/models/media_image.rs diff --git a/crates/replica-db/src/models/mod.rs b/crates/replica_db_schema/src/models/mod.rs diff --git a/crates/replica-db-schema/src/models/nostr_event_state.rs b/crates/replica_db_schema/src/models/nostr_event_state.rs diff --git a/crates/replica-db-schema/src/models/nostr_profile.rs b/crates/replica_db_schema/src/models/nostr_profile.rs diff --git a/crates/replica-db-schema/src/models/nostr_profile_relay.rs b/crates/replica_db_schema/src/models/nostr_profile_relay.rs diff --git a/crates/replica-db-schema/src/models/nostr_relay.rs b/crates/replica_db_schema/src/models/nostr_relay.rs diff --git a/crates/replica-db-schema/src/models/plot.rs b/crates/replica_db_schema/src/models/plot.rs diff --git a/crates/replica-db-schema/src/models/plot_gcs_location.rs b/crates/replica_db_schema/src/models/plot_gcs_location.rs diff --git a/crates/replica-db-schema/src/models/plot_tag.rs b/crates/replica_db_schema/src/models/plot_tag.rs diff --git a/crates/replica-db-schema/src/models/trade_product.rs b/crates/replica_db_schema/src/models/trade_product.rs diff --git a/crates/replica-db-schema/src/models/trade_product_location.rs b/crates/replica_db_schema/src/models/trade_product_location.rs diff --git a/crates/replica-db-schema/src/models/trade_product_media.rs b/crates/replica_db_schema/src/models/trade_product_media.rs diff --git a/crates/replica-db-schema/tests/query_bind_values.rs b/crates/replica_db_schema/tests/query_bind_values.rs diff --git a/crates/replica_db_wasm/Cargo.toml b/crates/replica_db_wasm/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "radroots_replica_db_wasm" +version = "0.1.0-alpha.1" +edition.workspace = true +authors = [ + "Radroots Authors", +] +rust-version.workspace = true +license.workspace = true +description = "wasm bindings for replica database runtime interfaces in radroots data layers" +repository.workspace = true +homepage.workspace = true +documentation = "https://docs.rs/radroots_replica_db_wasm" +readme.workspace = true + +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies] +radroots_sql_core = { workspace = true, features = ["bridge"] } +radroots_sql_wasm_core = { workspace = true, default-features = false, features = [ + "bridge", +] } +radroots_replica_db = { workspace = true } +radroots_replica_db_schema = { workspace = true } +radroots_replica_sync = { workspace = true } +js-sys = { workspace = true } +serde = { workspace = true, features = ["derive"] } +serde_json = { workspace = true } +serde-wasm-bindgen = { workspace = true } +wasm-bindgen = { workspace = true } + +[dev-dependencies] +wasm-bindgen-test = { workspace = true } + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(coverage_nightly)'] } diff --git a/crates/replica_db_wasm/README.md b/crates/replica_db_wasm/README.md @@ -0,0 +1,14 @@ +# radroots_replica_db_wasm + +Wasm bindings for replica database runtime interfaces in Rad Roots data layers. + +## Goals + +- define stable wasm bindings for replica CRUD, migration, backup, and export flows +- keep export/import and manifest marshaling deterministic in web runtimes +- support integration with wasm SQL executor surfaces +- provide reusable data-layer wasm primitives for higher-level Rad Roots crates + +## License + +Licensed under AGPL-3.0. See LICENSE. diff --git a/crates/replica-db-wasm/pkg/package.json b/crates/replica_db_wasm/pkg/package.json diff --git a/crates/replica-db-wasm/src/lib.rs b/crates/replica_db_wasm/src/lib.rs diff --git a/crates/replica-db-wasm/src/utils.rs b/crates/replica_db_wasm/src/utils.rs diff --git a/crates/replica-db-wasm/src/wasm_impl.rs b/crates/replica_db_wasm/src/wasm_impl.rs diff --git a/crates/replica_sync/Cargo.toml b/crates/replica_sync/Cargo.toml @@ -0,0 +1,54 @@ +[package] +name = "radroots_replica_sync" +version = "0.1.0-alpha.1" +edition.workspace = true +authors = [ + "Radroots Authors", +] +rust-version.workspace = true +license.workspace = true +description = "replica event ingest and synchronization interfaces for radroots data layers" +repository.workspace = true +homepage.workspace = true +documentation = "https://docs.rs/radroots_replica_sync" +readme.workspace = true + +[lib] +crate-type = ["rlib"] + +[features] +default = ["std"] +std = [ + "radroots_events/std", + "radroots_events_codec/std", + "dep:base64", + "dep:uuid", +] + +[dependencies] +radroots_events = { workspace = true, default-features = false, features = [ + "serde", +] } +radroots_events_codec = { workspace = true, default-features = false, features = [ + "serde_json", +] } +radroots_sql_core = { workspace = true } +radroots_replica_db_schema = { workspace = true } +radroots_replica_db = { workspace = true } +radroots_types = { workspace = true } +hex = { workspace = true } +serde = { workspace = true, default-features = false, features = [ + "alloc", + "derive", +] } +serde_json = { workspace = true, default-features = false, features = [ + "alloc", +] } +sha2 = { workspace = true, default-features = false } +base64 = { workspace = true, optional = true } +uuid = { workspace = true, optional = true } + +[dev-dependencies] +radroots_sql_core = { workspace = true, features = ["native"] } +radroots_replica_db = { workspace = true } +serde_json = { workspace = true } diff --git a/crates/replica_sync/README.md b/crates/replica_sync/README.md @@ -0,0 +1,14 @@ +# radroots_replica_sync + +Replica event ingest and synchronization interfaces for Rad Roots data layers. + +## Goals + +- define stable interfaces for event ingest, emit, and sync status +- keep ingest and bundle assembly behavior deterministic across targets +- support canonical transfer payloads for replica synchronization flows +- provide reusable synchronization primitives for higher-level Rad Roots crates + +## License + +Licensed under AGPL-3.0. See LICENSE. diff --git a/crates/replica-sync/src/canonical.rs b/crates/replica_sync/src/canonical.rs diff --git a/crates/replica-sync/src/emit.rs b/crates/replica_sync/src/emit.rs diff --git a/crates/replica-sync/src/error.rs b/crates/replica_sync/src/error.rs diff --git a/crates/replica-sync/src/event_state.rs b/crates/replica_sync/src/event_state.rs diff --git a/crates/replica-sync/src/geo.rs b/crates/replica_sync/src/geo.rs diff --git a/crates/replica-sync/src/ingest.rs b/crates/replica_sync/src/ingest.rs diff --git a/crates/replica-sync/src/lib.rs b/crates/replica_sync/src/lib.rs diff --git a/crates/replica-sync/src/sync_state.rs b/crates/replica_sync/src/sync_state.rs diff --git a/crates/replica-sync/src/tests.rs b/crates/replica_sync/src/tests.rs diff --git a/crates/replica-sync/src/types.rs b/crates/replica_sync/src/types.rs diff --git a/crates/replica-sync/tests/ingest_roundtrip.rs b/crates/replica_sync/tests/ingest_roundtrip.rs diff --git a/crates/replica_sync_wasm/Cargo.toml b/crates/replica_sync_wasm/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "radroots_replica_sync_wasm" +version = "0.1.0-alpha.1" +edition.workspace = true +authors = [ + "Radroots Authors", +] +rust-version.workspace = true +license.workspace = true +description = "wasm bindings for replica synchronization interfaces in radroots data layers" +repository.workspace = true +homepage.workspace = true +documentation = "https://docs.rs/radroots_replica_sync_wasm" +readme.workspace = true + +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies] +base64 = { workspace = true } +radroots_events = { workspace = true, default-features = false, features = [ + "serde", +] } +radroots_sql_core = { workspace = true, features = ["bridge"] } +radroots_sql_wasm_core = { workspace = true, default-features = false, features = [ + "bridge", +] } +radroots_replica_sync = { workspace = true } +serde = { workspace = true, features = ["derive"] } +serde_json = { workspace = true } +serde-wasm-bindgen = { workspace = true } +uuid = { workspace = true, features = ["js"] } +wasm-bindgen = { workspace = true } + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(coverage_nightly)'] } diff --git a/crates/replica_sync_wasm/README.md b/crates/replica_sync_wasm/README.md @@ -0,0 +1,14 @@ +# radroots_replica_sync_wasm + +Wasm bindings for replica synchronization interfaces in Rad Roots data layers. + +## Goals + +- define stable wasm bindings for replica sync and ingest flows +- keep request parsing and result marshaling deterministic in web runtimes +- support integration with wasm SQL executor surfaces +- provide reusable sync bridge primitives for higher-level Rad Roots crates + +## License + +Licensed under AGPL-3.0. See LICENSE. diff --git a/crates/replica-sync-wasm/pkg/package.json b/crates/replica_sync_wasm/pkg/package.json diff --git a/crates/replica-sync-wasm/src/lib.rs b/crates/replica_sync_wasm/src/lib.rs diff --git a/crates/runtime-distribution/Cargo.toml b/crates/runtime-distribution/Cargo.toml @@ -1,19 +0,0 @@ -[package] -name = "radroots-runtime-distribution" -version = "0.1.0-alpha.1" -edition.workspace = true -authors = [ - "Radroots Authors", -] -rust-version.workspace = true -license.workspace = true -description = "contract parsing and artifact resolution for modular radroots runtime distribution" -repository.workspace = true -homepage.workspace = true -documentation = "https://docs.rs/radroots-runtime-distribution" -readme = "README.md" - -[dependencies] -serde = { workspace = true, features = ["derive"] } -thiserror = { workspace = true } -toml = { workspace = true } diff --git a/crates/runtime-distribution/README.md b/crates/runtime-distribution/README.md @@ -1,15 +0,0 @@ -# radroots-runtime-distribution - -Contract parsing and artifact resolution for modular Rad Roots runtime distribution. - -## Goals - -- parse the machine-readable runtime distribution contract -- resolve installable runtime artifacts by runtime id, channel, version, operating system, and - architecture -- keep installer and runtime-manager selection logic out of ad hoc shell branches -- give CLI and app clients one shared substrate for runtime release-unit resolution - -## License - -Licensed under AGPL-3.0. See LICENSE. diff --git a/crates/runtime-distribution/src/lib.rs b/crates/runtime-distribution/src/lib.rs @@ -1,346 +0,0 @@ -#![forbid(unsafe_code)] - -pub mod error; -pub mod model; -pub mod resolve; - -pub use error::RadrootsRuntimeDistributionError; -pub use model::{ - ArchiveFormat, ArtifactAdapter, ChannelSet, DistributionFamily, - RadrootsRuntimeDistributionContract, RuntimeDistributionEntry, TargetSet, TargetSpec, -}; -pub use resolve::{ - RUNTIME_DISTRIBUTION_SCHEMA, RadrootsRuntimeDistributionResolver, ResolvedRuntimeArtifact, - RuntimeArtifactRequest, -}; - -#[cfg(test)] -mod tests { - use super::{ - RUNTIME_DISTRIBUTION_SCHEMA, RadrootsRuntimeDistributionError, - RadrootsRuntimeDistributionResolver, RuntimeArtifactRequest, - }; - - const CONTRACT: &str = r#" -schema = "radroots-runtime-distribution" -schema_version = 1 -owner_doc = "docs/migration/radroots-modular-runtime-management-bootstrap-rcl.md" -runtime_registry = "registry.toml" - -[family] -id = "radroots-runtime-family" -canonical_installer_engine = "single_runtime_selected" -human_install_facade = "delivery_publication_only" -tooling_consumption = "shared_distribution_library" -independent_runtime_versions = true -version_resolution = "runtime_scoped_channel_latest" -artifact_verification_required = true - -[channels] -active = ["stable"] -defined = ["stable", "candidate", "nightly"] - -[artifact_adapters.rust_binary_archive] -kind = "binary_archive" -supported_archive_formats = ["tar.gz", "zip"] -layout = "single_binary_plus_supporting_files" - -[artifact_adapters.desktop_bundle] -kind = "desktop_bundle" -supported_archive_formats = ["tar.gz", "zip", "dmg"] -layout = "host_native_bundle" - -[artifact_adapters.mobile_store_package] -kind = "mobile_store_package" -supported_archive_formats = [] -layout = "platform_store_managed" - -[artifact_adapters.mojo_workspace_archive] -kind = "workspace_archive" -supported_archive_formats = ["tar.gz"] -layout = "workspace_tree" - -[archive_formats.tar_gz] -extension = ".tar.gz" -platforms = ["linux", "macos"] - -[archive_formats.zip] -extension = ".zip" -platforms = ["windows"] - -[archive_formats.dmg] -extension = ".dmg" -platforms = ["macos"] - -[target_sets.server_default] -targets = [ - "x86_64-unknown-linux-gnu", - "aarch64-unknown-linux-gnu", -] - -[target_sets.cli_default] -targets = [ - "x86_64-unknown-linux-gnu", - "aarch64-unknown-linux-gnu", - "x86_64-apple-darwin", - "aarch64-apple-darwin", -] - -[target_sets.desktop_default] -targets = [ - "x86_64-apple-darwin", - "aarch64-apple-darwin", -] - -[target_sets.mojo_workspace_default] -targets = [ - "osx-arm64", - "linux-64", -] - -[targets.x86_64-unknown-linux-gnu] -os = "linux" -arch = "amd64" -archive_format = "tar.gz" - -[targets.aarch64-unknown-linux-gnu] -os = "linux" -arch = "arm64" -archive_format = "tar.gz" - -[targets.x86_64-apple-darwin] -os = "macos" -arch = "amd64" -archive_format = "tar.gz" - -[targets.aarch64-apple-darwin] -os = "macos" -arch = "arm64" -archive_format = "tar.gz" - -[targets.osx-arm64] -os = "macos" -arch = "arm64" -archive_format = "tar.gz" - -[targets.linux-64] -os = "linux" -arch = "amd64" -archive_format = "tar.gz" - -[[runtime]] -id = "cli" -distribution_state = "active" -release_unit = "cli" -package_name = "radroots-cli" -binary_name = "radroots" -artifact_adapter = "rust_binary_archive" -target_set = "cli_default" -default_channel = "stable" -human_installable = true - -[[runtime]] -id = "radrootsd" -distribution_state = "active" -release_unit = "radrootsd" -package_name = "radrootsd" -binary_name = "radrootsd" -artifact_adapter = "rust_binary_archive" -target_set = "server_default" -default_channel = "stable" -human_installable = true - -[[runtime]] -id = "community-app-desktop" -distribution_state = "defined" -release_unit = "community-app-desktop" -package_name = "radroots-app-desktop" -binary_name = "radroots-app-desktop" -artifact_adapter = "desktop_bundle" -target_set = "desktop_default" -default_channel = "stable" -human_installable = true - -[[runtime]] -id = "community-app-ios" -distribution_state = "external_platform_managed" -release_unit = "community-app-ios" -package_name = "radroots-app-ios" -artifact_adapter = "mobile_store_package" -default_channel = "stable" -human_installable = false - -[[runtime]] -id = "hyf" -distribution_state = "bootstrap_only" -release_unit = "hyf" -package_name = "hyf" -binary_name = "hyf" -artifact_adapter = "mojo_workspace_archive" -target_set = "mojo_workspace_default" -default_channel = "stable" -human_installable = false -"#; - - #[test] - fn parse_str_accepts_the_expected_schema() { - let resolver = - RadrootsRuntimeDistributionResolver::parse_str(CONTRACT).expect("parse contract"); - - assert_eq!(resolver.contract().schema, RUNTIME_DISTRIBUTION_SCHEMA); - assert_eq!(resolver.contract().runtime.len(), 5); - } - - #[test] - fn resolves_cli_linux_artifact_with_explicit_channel() { - let resolver = - RadrootsRuntimeDistributionResolver::parse_str(CONTRACT).expect("parse contract"); - - let artifact = resolver - .resolve_artifact(&RuntimeArtifactRequest { - runtime_id: "cli", - os: "linux", - arch: "amd64", - version: "0.1.0-alpha.1", - channel: Some("stable"), - }) - .expect("resolve cli artifact"); - - assert_eq!(artifact.binary_name.as_deref(), Some("radroots")); - assert_eq!(artifact.target_id, "x86_64-unknown-linux-gnu"); - assert_eq!(artifact.archive_extension, ".tar.gz"); - assert_eq!( - artifact.artifact_file_name, - "cli-0.1.0-alpha.1-x86_64-unknown-linux-gnu.tar.gz" - ); - } - - #[test] - fn resolves_radrootsd_linux_arm64_using_default_channel() { - let resolver = - RadrootsRuntimeDistributionResolver::parse_str(CONTRACT).expect("parse contract"); - - let artifact = resolver - .resolve_artifact(&RuntimeArtifactRequest { - runtime_id: "radrootsd", - os: "linux", - arch: "arm64", - version: "0.1.0-alpha.1", - channel: None, - }) - .expect("resolve radrootsd artifact"); - - assert_eq!(artifact.channel, "stable"); - assert_eq!(artifact.target_id, "aarch64-unknown-linux-gnu"); - assert_eq!(artifact.binary_name.as_deref(), Some("radrootsd")); - } - - #[test] - fn resolves_desktop_bundle_for_macos_arm64() { - let resolver = - RadrootsRuntimeDistributionResolver::parse_str(CONTRACT).expect("parse contract"); - - let artifact = resolver - .resolve_artifact(&RuntimeArtifactRequest { - runtime_id: "community-app-desktop", - os: "macos", - arch: "arm64", - version: "0.1.0-alpha.1", - channel: Some("stable"), - }) - .expect("resolve desktop artifact"); - - assert_eq!(artifact.target_id, "aarch64-apple-darwin"); - assert_eq!(artifact.package_name, "radroots-app-desktop"); - } - - #[test] - fn rejects_non_installable_mobile_runtime() { - let resolver = - RadrootsRuntimeDistributionResolver::parse_str(CONTRACT).expect("parse contract"); - - let err = resolver - .resolve_artifact(&RuntimeArtifactRequest { - runtime_id: "community-app-ios", - os: "macos", - arch: "arm64", - version: "0.1.0-alpha.1", - channel: Some("stable"), - }) - .expect_err("mobile runtime should not be installable"); - - assert_eq!( - err, - RadrootsRuntimeDistributionError::RuntimeNotInstallable( - "community-app-ios".to_string() - ) - ); - } - - #[test] - fn rejects_bootstrap_only_runtime() { - let resolver = - RadrootsRuntimeDistributionResolver::parse_str(CONTRACT).expect("parse contract"); - - let err = resolver - .resolve_artifact(&RuntimeArtifactRequest { - runtime_id: "hyf", - os: "macos", - arch: "arm64", - version: "0.1.0", - channel: Some("stable"), - }) - .expect_err("bootstrap runtime should not be installable"); - - assert_eq!( - err, - RadrootsRuntimeDistributionError::RuntimeNotInstallable("hyf".to_string()) - ); - } - - #[test] - fn rejects_inactive_channel() { - let resolver = - RadrootsRuntimeDistributionResolver::parse_str(CONTRACT).expect("parse contract"); - - let err = resolver - .resolve_artifact(&RuntimeArtifactRequest { - runtime_id: "cli", - os: "linux", - arch: "amd64", - version: "0.1.0-alpha.1", - channel: Some("candidate"), - }) - .expect_err("candidate channel should be inactive"); - - assert_eq!( - err, - RadrootsRuntimeDistributionError::InactiveChannel("candidate".to_string()) - ); - } - - #[test] - fn rejects_unsupported_platform() { - let resolver = - RadrootsRuntimeDistributionResolver::parse_str(CONTRACT).expect("parse contract"); - - let err = resolver - .resolve_artifact(&RuntimeArtifactRequest { - runtime_id: "radrootsd", - os: "windows", - arch: "amd64", - version: "0.1.0-alpha.1", - channel: Some("stable"), - }) - .expect_err("windows target should be unsupported"); - - assert_eq!( - err, - RadrootsRuntimeDistributionError::UnsupportedPlatform { - runtime_id: "radrootsd".to_string(), - os: "windows".to_string(), - arch: "amd64".to_string(), - } - ); - } -} diff --git a/crates/runtime-distribution/src/resolve.rs b/crates/runtime-distribution/src/resolve.rs @@ -1,227 +0,0 @@ -use crate::error::RadrootsRuntimeDistributionError; -use crate::model::{ - ArtifactAdapter, RadrootsRuntimeDistributionContract, RuntimeDistributionEntry, TargetSpec, -}; - -pub const RUNTIME_DISTRIBUTION_SCHEMA: &str = "radroots-runtime-distribution"; - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct RuntimeArtifactRequest<'a> { - pub runtime_id: &'a str, - pub os: &'a str, - pub arch: &'a str, - pub version: &'a str, - pub channel: Option<&'a str>, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct ResolvedRuntimeArtifact { - pub runtime_id: String, - pub release_unit: String, - pub package_name: String, - pub binary_name: Option<String>, - pub artifact_adapter: String, - pub channel: String, - pub version: String, - pub target_id: String, - pub os: String, - pub arch: String, - pub archive_format: String, - pub archive_extension: String, - pub artifact_stem: String, - pub artifact_file_name: String, -} - -#[derive(Debug, Clone)] -pub struct RadrootsRuntimeDistributionResolver { - contract: RadrootsRuntimeDistributionContract, -} - -impl RadrootsRuntimeDistributionResolver { - pub fn parse_str(raw: &str) -> Result<Self, RadrootsRuntimeDistributionError> { - let contract = toml::from_str::<RadrootsRuntimeDistributionContract>(raw) - .map_err(|err| RadrootsRuntimeDistributionError::Parse(err.to_string()))?; - Self::new(contract) - } - - pub fn new( - contract: RadrootsRuntimeDistributionContract, - ) -> Result<Self, RadrootsRuntimeDistributionError> { - if contract.schema != RUNTIME_DISTRIBUTION_SCHEMA { - return Err(RadrootsRuntimeDistributionError::UnexpectedSchema { - expected: RUNTIME_DISTRIBUTION_SCHEMA, - found: contract.schema.clone(), - }); - } - Ok(Self { contract }) - } - - pub fn contract(&self) -> &RadrootsRuntimeDistributionContract { - &self.contract - } - - pub fn resolve_artifact( - &self, - request: &RuntimeArtifactRequest<'_>, - ) -> Result<ResolvedRuntimeArtifact, RadrootsRuntimeDistributionError> { - let runtime = self - .contract - .runtime - .iter() - .find(|runtime| runtime.id == request.runtime_id) - .ok_or_else(|| { - RadrootsRuntimeDistributionError::UnknownRuntime(request.runtime_id.to_string()) - })?; - - if !runtime.human_installable { - return Err(RadrootsRuntimeDistributionError::RuntimeNotInstallable( - runtime.id.clone(), - )); - } - - let channel = request.channel.unwrap_or(runtime.default_channel.as_str()); - self.ensure_channel_is_active(channel)?; - - let target_set_id = runtime.target_set.as_ref().ok_or_else(|| { - RadrootsRuntimeDistributionError::MissingTargetSet(runtime.id.clone()) - })?; - - let adapter = self - .contract - .artifact_adapters - .get(&runtime.artifact_adapter) - .ok_or_else( - || RadrootsRuntimeDistributionError::UnknownArtifactAdapter { - runtime_id: runtime.id.clone(), - adapter_id: runtime.artifact_adapter.clone(), - }, - )?; - - let (target_id, target) = - self.select_target(runtime, target_set_id, request.os, request.arch)?; - let archive_format_id = - self.resolve_archive_format_id(runtime, target_id, target, adapter)?; - let archive_format = self - .contract - .archive_formats - .get(&normalized_contract_key(archive_format_id)) - .ok_or_else(|| RadrootsRuntimeDistributionError::UnknownArchiveFormat { - target_id: target_id.to_string(), - archive_format_id: archive_format_id.to_string(), - })?; - - let artifact_stem = format!("{}-{}-{}", runtime.release_unit, request.version, target_id); - let artifact_file_name = format!("{artifact_stem}{}", archive_format.extension); - - Ok(ResolvedRuntimeArtifact { - runtime_id: runtime.id.clone(), - release_unit: runtime.release_unit.clone(), - package_name: runtime.package_name.clone(), - binary_name: runtime.binary_name.clone(), - artifact_adapter: runtime.artifact_adapter.clone(), - channel: channel.to_string(), - version: request.version.to_string(), - target_id: target_id.to_string(), - os: request.os.to_string(), - arch: request.arch.to_string(), - archive_format: archive_format_id.to_string(), - archive_extension: archive_format.extension.clone(), - artifact_stem, - artifact_file_name, - }) - } - - fn ensure_channel_is_active( - &self, - channel: &str, - ) -> Result<(), RadrootsRuntimeDistributionError> { - if !self - .contract - .channels - .defined - .iter() - .any(|entry| entry == channel) - { - return Err(RadrootsRuntimeDistributionError::UnknownChannel( - channel.to_string(), - )); - } - if !self - .contract - .channels - .active - .iter() - .any(|entry| entry == channel) - { - return Err(RadrootsRuntimeDistributionError::InactiveChannel( - channel.to_string(), - )); - } - Ok(()) - } - - fn select_target<'a>( - &'a self, - runtime: &RuntimeDistributionEntry, - target_set_id: &str, - os: &str, - arch: &str, - ) -> Result<(&'a str, &'a TargetSpec), RadrootsRuntimeDistributionError> { - let target_set = self - .contract - .target_sets - .get(target_set_id) - .ok_or_else(|| RadrootsRuntimeDistributionError::UnsupportedPlatform { - runtime_id: runtime.id.clone(), - os: os.to_string(), - arch: arch.to_string(), - })?; - - let mut found_match = None; - for target_id in &target_set.targets { - let target = self.contract.targets.get(target_id).ok_or_else(|| { - RadrootsRuntimeDistributionError::UnknownTarget { - runtime_id: runtime.id.clone(), - target_set_id: target_set_id.to_string(), - target_id: target_id.clone(), - } - })?; - - if target.os == os && target.arch == arch { - found_match = Some((target_id.as_str(), target)); - break; - } - } - - found_match.ok_or_else(|| RadrootsRuntimeDistributionError::UnsupportedPlatform { - runtime_id: runtime.id.clone(), - os: os.to_string(), - arch: arch.to_string(), - }) - } - - fn resolve_archive_format_id<'a>( - &self, - runtime: &RuntimeDistributionEntry, - target_id: &'a str, - target: &'a TargetSpec, - adapter: &'a ArtifactAdapter, - ) -> Result<&'a str, RadrootsRuntimeDistributionError> { - if let Some(format) = target.archive_format.as_deref() { - return Ok(format); - } - - if adapter.supported_archive_formats.len() == 1 { - return Ok(adapter.supported_archive_formats[0].as_str()); - } - - Err(RadrootsRuntimeDistributionError::MissingArchiveFormat { - runtime_id: runtime.id.clone(), - target_id: target_id.to_string(), - }) - } -} - -fn normalized_contract_key(value: &str) -> String { - value.replace('.', "_") -} diff --git a/crates/runtime-manager/Cargo.toml b/crates/runtime-manager/Cargo.toml @@ -1,23 +0,0 @@ -[package] -name = "radroots-runtime-manager" -version = "0.1.0-alpha.1" -edition.workspace = true -authors = [ - "Radroots Authors", -] -rust-version.workspace = true -license.workspace = true -description = "shared local managed-runtime paths, registry, and lifecycle boundary for radroots" -repository.workspace = true -homepage.workspace = true -documentation = "https://docs.rs/radroots-runtime-manager" -readme = "README.md" - -[dependencies] -radroots-runtime-paths = { workspace = true } -serde = { workspace = true, features = ["derive"] } -thiserror = { workspace = true } -toml = { workspace = true } - -[dev-dependencies] -tempfile = { workspace = true } diff --git a/crates/runtime-manager/README.md b/crates/runtime-manager/README.md @@ -1,14 +0,0 @@ -# radroots-runtime-manager - -Shared local managed-runtime paths, registry, and lifecycle boundary for Rad Roots runtimes. - -## Goals - -- parse the machine-readable runtime management contract -- resolve shared manager roots and per-instance runtime-manager paths -- persist one shared managed-runtime instance registry -- define typed install and health state surfaces for CLI and app adoption later - -## License - -Licensed under AGPL-3.0. See LICENSE. diff --git a/crates/runtime-manager/src/lib.rs b/crates/runtime-manager/src/lib.rs @@ -1,305 +0,0 @@ -#![forbid(unsafe_code)] - -pub mod error; -pub mod model; -pub mod paths; -pub mod registry; - -pub use error::RadrootsRuntimeManagerError; -pub use model::{ - BootstrapRuntimeContract, LifecycleContract, ManagedRuntimeHealthState, - ManagedRuntimeInstallState, ManagedRuntimeInstanceRecord, ManagedRuntimeInstanceRegistry, - ManagementDefaults, ManagementModeContract, ManagementPathContract, - RadrootsRuntimeManagementContract, RuntimeGroups, -}; -pub use paths::{ - ManagedRuntimeInstancePaths, ManagedRuntimeSharedPaths, bootstrap_runtime, - resolve_instance_paths, resolve_shared_paths, -}; -pub use registry::{instance, load_registry, save_registry, upsert_instance}; - -pub const RUNTIME_MANAGEMENT_SCHEMA: &str = "radroots-runtime-management"; - -pub fn parse_contract_str( - raw: &str, -) -> Result<RadrootsRuntimeManagementContract, RadrootsRuntimeManagerError> { - let contract = toml::from_str::<RadrootsRuntimeManagementContract>(raw) - .map_err(|err| RadrootsRuntimeManagerError::Parse(err.to_string()))?; - if contract.schema != RUNTIME_MANAGEMENT_SCHEMA { - return Err(RadrootsRuntimeManagerError::UnexpectedSchema { - expected: RUNTIME_MANAGEMENT_SCHEMA, - found: contract.schema.clone(), - }); - } - Ok(contract) -} - -#[cfg(test)] -mod tests { - use std::path::PathBuf; - - use radroots_runtime_paths::{ - RadrootsHostEnvironment, RadrootsPathOverrides, RadrootsPathProfile, RadrootsPathResolver, - RadrootsPlatform, - }; - use tempfile::tempdir; - - use crate::{ - ManagedRuntimeHealthState, ManagedRuntimeInstallState, ManagedRuntimeInstanceRecord, - bootstrap_runtime, instance, load_registry, parse_contract_str, resolve_instance_paths, - resolve_shared_paths, save_registry, upsert_instance, - }; - - const CONTRACT: &str = r#" -schema = "radroots-runtime-management" -schema_version = 1 -owner_doc = "docs/migration/radroots-modular-runtime-management-bootstrap-rcl.md" -runtime_registry = "registry.toml" -distribution_contract = "distribution.toml" -capabilities_contract = "capabilities.toml" - -[defaults] -instance_cardinality = "single_default_instance" -managed_runtime_lookup = "shared_instance_registry" -explicit_runtime_endpoint_overrides_precede_managed_instance_binding = true -global_path_mutation_forbidden = true - -[management_clients] -active = ["cli"] -defined = ["community-app-desktop"] - -[managed_runtime_targets] -active = ["radrootsd"] -defined = ["myc", "rhi"] -bootstrap_only = ["hyf"] - -[lifecycle] -actions = ["install", "uninstall", "start", "stop", "restart", "status", "logs", "config_show", "config_set"] -destructive_actions = ["uninstall"] -health_states = ["not_installed", "stopped", "starting", "running", "degraded", "failed"] - -[mode.interactive_user_managed] -contract_state = "active" -platforms = ["linux", "macos", "windows"] -supported_profiles = ["interactive_user", "repo_local"] -service_manager_integration = false -uses_absolute_binary_paths = true -requires_explicit_pid_tracking = true -requires_explicit_log_tracking = true -default_instance_cardinality = "single_default_instance" - -[mode.service_host_managed] -contract_state = "defined" -platforms = ["linux", "macos", "windows"] -supported_profiles = ["service_host"] -service_manager_integration = true -uses_absolute_binary_paths = true -default_instance_cardinality = "single_default_instance" - -[paths.interactive_user_managed] -shared_namespace = "shared/runtime-manager" -instance_registry_root_class = "config" -instance_registry_rel = "shared/runtime-manager/instances.toml" -artifact_cache_root_class = "cache" -artifact_cache_rel = "shared/runtime-manager/artifacts" -install_root_class = "data" -install_root_rel = "shared/runtime-manager/installs" -state_root_class = "data" -state_root_rel = "shared/runtime-manager/state" -logs_root_class = "logs" -logs_root_rel = "shared/runtime-manager" -run_root_class = "run" -run_root_rel = "shared/runtime-manager" -secrets_root_class = "secrets" -secrets_namespace_rel = "shared/runtime-manager" - -[instance_metadata] -required_fields = [ - "runtime_id", - "instance_id", - "management_mode", - "install_state", - "binary_path", - "config_path", - "logs_path", - "run_path", - "installed_version", -] -optional_fields = [ - "health_endpoint", - "secret_material_ref", - "last_started_at", - "last_stopped_at", - "notes", -] - -[bootstrap.radrootsd] -runtime_id = "radrootsd" -management_mode = "interactive_user_managed" -default_instance_id = "local" -install_strategy = "archive_unpack" -config_format = "toml" -requires_bootstrap_secret = true -requires_config_bootstrap = true -requires_signer_provider = false -health_surface = "jsonrpc_status" -preferred_cli_binding = true -"#; - - #[test] - fn parse_contract_accepts_expected_schema() { - let contract = parse_contract_str(CONTRACT).expect("parse contract"); - assert_eq!(contract.schema, crate::RUNTIME_MANAGEMENT_SCHEMA); - assert!(contract.mode.contains_key("interactive_user_managed")); - } - - #[test] - fn resolve_shared_paths_uses_interactive_user_roots() { - let contract = parse_contract_str(CONTRACT).expect("parse contract"); - let resolver = RadrootsPathResolver::new( - RadrootsPlatform::Linux, - RadrootsHostEnvironment { - home_dir: Some(PathBuf::from("/home/treesap")), - ..RadrootsHostEnvironment::default() - }, - ); - - let paths = resolve_shared_paths( - &contract, - &resolver, - RadrootsPathProfile::InteractiveUser, - &RadrootsPathOverrides::default(), - "interactive_user_managed", - ) - .expect("resolve shared manager paths"); - - assert_eq!( - paths.instance_registry_path, - PathBuf::from("/home/treesap/.radroots/config/shared/runtime-manager/instances.toml") - ); - assert_eq!( - paths.install_root, - PathBuf::from("/home/treesap/.radroots/data/shared/runtime-manager/installs") - ); - assert_eq!( - paths.logs_root, - PathBuf::from("/home/treesap/.radroots/logs/shared/runtime-manager") - ); - } - - #[test] - fn resolve_repo_local_paths_uses_explicit_base_root() { - let contract = parse_contract_str(CONTRACT).expect("parse contract"); - let resolver = - RadrootsPathResolver::new(RadrootsPlatform::Linux, RadrootsHostEnvironment::default()); - - let paths = resolve_shared_paths( - &contract, - &resolver, - RadrootsPathProfile::RepoLocal, - &RadrootsPathOverrides::repo_local("/repo/.local/radroots"), - "interactive_user_managed", - ) - .expect("resolve repo local manager paths"); - - assert_eq!( - paths.state_root, - PathBuf::from("/repo/.local/radroots/data/shared/runtime-manager/state") - ); - } - - #[test] - fn resolve_instance_paths_builds_per_runtime_layout() { - let contract = parse_contract_str(CONTRACT).expect("parse contract"); - let resolver = RadrootsPathResolver::new( - RadrootsPlatform::Macos, - RadrootsHostEnvironment { - home_dir: Some(PathBuf::from("/Users/treesap")), - ..RadrootsHostEnvironment::default() - }, - ); - let shared = resolve_shared_paths( - &contract, - &resolver, - RadrootsPathProfile::InteractiveUser, - &RadrootsPathOverrides::default(), - "interactive_user_managed", - ) - .expect("resolve shared manager paths"); - - let instance_paths = resolve_instance_paths(&shared, "radrootsd", "local"); - assert_eq!( - instance_paths.install_dir, - PathBuf::from( - "/Users/treesap/.radroots/data/shared/runtime-manager/installs/radrootsd/local" - ) - ); - assert_eq!( - instance_paths.pid_file_path, - PathBuf::from( - "/Users/treesap/.radroots/run/shared/runtime-manager/radrootsd/local/runtime.pid" - ) - ); - assert_eq!( - instance_paths.metadata_path, - PathBuf::from( - "/Users/treesap/.radroots/data/shared/runtime-manager/state/radrootsd/local/instance.toml" - ) - ); - } - - #[test] - fn registry_round_trip_persists_and_reloads_instances() { - let dir = tempdir().expect("tempdir"); - let registry_path = dir.path().join("instances.toml"); - let mut registry = crate::ManagedRuntimeInstanceRegistry::default(); - upsert_instance( - &mut registry, - ManagedRuntimeInstanceRecord { - runtime_id: "radrootsd".to_string(), - instance_id: "local".to_string(), - management_mode: "interactive_user_managed".to_string(), - install_state: ManagedRuntimeInstallState::Configured, - binary_path: PathBuf::from("/tmp/radrootsd"), - config_path: PathBuf::from("/tmp/config.toml"), - logs_path: PathBuf::from("/tmp/logs"), - run_path: PathBuf::from("/tmp/run"), - installed_version: "0.1.0-alpha.1".to_string(), - health_endpoint: Some("jsonrpc_status".to_string()), - secret_material_ref: Some( - "shared/runtime-manager/radrootsd/local/token".to_string(), - ), - last_started_at: Some("2026-04-08T00:00:00Z".to_string()), - last_stopped_at: None, - notes: Some("test".to_string()), - }, - ); - - save_registry(®istry_path, ®istry).expect("save registry"); - let reloaded = load_registry(®istry_path).expect("load registry"); - let record = instance(&reloaded, "radrootsd", "local").expect("instance record"); - assert_eq!(record.install_state, ManagedRuntimeInstallState::Configured); - assert_eq!(record.health_endpoint.as_deref(), Some("jsonrpc_status")); - } - - #[test] - fn bootstrap_lookup_returns_radrootsd_contract() { - let contract = parse_contract_str(CONTRACT).expect("parse contract"); - let bootstrap = bootstrap_runtime(&contract, "radrootsd").expect("bootstrap contract"); - assert_eq!(bootstrap.default_instance_id, "local"); - assert_eq!(bootstrap.health_surface, "jsonrpc_status"); - assert!(bootstrap.preferred_cli_binding); - } - - #[test] - fn install_and_health_state_surface_is_typed() { - assert_eq!( - ManagedRuntimeInstallState::Installed, - ManagedRuntimeInstallState::Installed - ); - assert_eq!( - ManagedRuntimeHealthState::Degraded, - ManagedRuntimeHealthState::Degraded - ); - } -} diff --git a/crates/runtime-manager/src/model.rs b/crates/runtime-manager/src/model.rs @@ -1,162 +0,0 @@ -use std::collections::BTreeMap; -use std::path::PathBuf; - -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Clone, Deserialize, PartialEq, Eq)] -pub struct RadrootsRuntimeManagementContract { - pub schema: String, - pub schema_version: u32, - pub owner_doc: String, - pub runtime_registry: String, - pub distribution_contract: String, - pub capabilities_contract: String, - pub defaults: ManagementDefaults, - pub management_clients: RuntimeGroups, - pub managed_runtime_targets: RuntimeGroups, - pub lifecycle: LifecycleContract, - pub mode: BTreeMap<String, ManagementModeContract>, - pub paths: BTreeMap<String, ManagementPathContract>, - pub instance_metadata: InstanceMetadataContract, - pub bootstrap: BTreeMap<String, BootstrapRuntimeContract>, -} - -#[derive(Debug, Clone, Deserialize, PartialEq, Eq)] -pub struct ManagementDefaults { - pub instance_cardinality: String, - pub managed_runtime_lookup: String, - pub explicit_runtime_endpoint_overrides_precede_managed_instance_binding: bool, - pub global_path_mutation_forbidden: bool, -} - -#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Default)] -pub struct RuntimeGroups { - #[serde(default)] - pub active: Vec<String>, - #[serde(default)] - pub defined: Vec<String>, - #[serde(default)] - pub bootstrap_only: Vec<String>, -} - -#[derive(Debug, Clone, Deserialize, PartialEq, Eq)] -pub struct LifecycleContract { - #[serde(default)] - pub actions: Vec<String>, - #[serde(default)] - pub destructive_actions: Vec<String>, - #[serde(default)] - pub health_states: Vec<String>, -} - -#[derive(Debug, Clone, Deserialize, PartialEq, Eq)] -pub struct ManagementModeContract { - pub contract_state: String, - #[serde(default)] - pub platforms: Vec<String>, - #[serde(default)] - pub supported_profiles: Vec<String>, - pub service_manager_integration: bool, - pub uses_absolute_binary_paths: bool, - pub default_instance_cardinality: String, - pub requires_explicit_pid_tracking: Option<bool>, - pub requires_explicit_log_tracking: Option<bool>, -} - -#[derive(Debug, Clone, Deserialize, PartialEq, Eq)] -pub struct ManagementPathContract { - pub shared_namespace: String, - pub instance_registry_root_class: String, - pub instance_registry_rel: String, - pub artifact_cache_root_class: String, - pub artifact_cache_rel: String, - pub install_root_class: String, - pub install_root_rel: String, - pub state_root_class: String, - pub state_root_rel: String, - pub logs_root_class: String, - pub logs_root_rel: String, - pub run_root_class: String, - pub run_root_rel: String, - pub secrets_root_class: String, - pub secrets_namespace_rel: String, -} - -#[derive(Debug, Clone, Deserialize, PartialEq, Eq)] -pub struct InstanceMetadataContract { - #[serde(default)] - pub required_fields: Vec<String>, - #[serde(default)] - pub optional_fields: Vec<String>, -} - -#[derive(Debug, Clone, Deserialize, PartialEq, Eq)] -pub struct BootstrapRuntimeContract { - pub runtime_id: String, - pub management_mode: String, - pub default_instance_id: String, - pub install_strategy: String, - pub config_format: String, - pub requires_bootstrap_secret: bool, - pub requires_config_bootstrap: bool, - pub requires_signer_provider: bool, - pub health_surface: String, - pub preferred_cli_binding: bool, - pub notes: Option<String>, -} - -#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] -#[serde(rename_all = "snake_case")] -pub enum ManagedRuntimeInstallState { - NotInstalled, - Installed, - Configured, - Failed, -} - -#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] -#[serde(rename_all = "snake_case")] -pub enum ManagedRuntimeHealthState { - NotInstalled, - Stopped, - Starting, - Running, - Degraded, - Failed, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -pub struct ManagedRuntimeInstanceRecord { - pub runtime_id: String, - pub instance_id: String, - pub management_mode: String, - pub install_state: ManagedRuntimeInstallState, - pub binary_path: PathBuf, - pub config_path: PathBuf, - pub logs_path: PathBuf, - pub run_path: PathBuf, - pub installed_version: String, - pub health_endpoint: Option<String>, - pub secret_material_ref: Option<String>, - pub last_started_at: Option<String>, - pub last_stopped_at: Option<String>, - pub notes: Option<String>, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -pub struct ManagedRuntimeInstanceRegistry { - pub schema: String, - pub schema_version: u32, - #[serde(default)] - pub instances: Vec<ManagedRuntimeInstanceRecord>, -} - -impl Default for ManagedRuntimeInstanceRegistry { - fn default() -> Self { - Self { - schema: "radroots-runtime-instance-registry".to_string(), - schema_version: 1, - instances: Vec::new(), - } - } -} diff --git a/crates/runtime-paths/Cargo.toml b/crates/runtime-paths/Cargo.toml @@ -1,17 +0,0 @@ -[package] -name = "radroots-runtime-paths" -version = "0.1.0-alpha.1" -edition.workspace = true -authors = [ - "Radroots Authors", -] -rust-version.workspace = true -license.workspace = true -description = "canonical local filesystem path resolution for radroots runtimes" -repository.workspace = true -homepage.workspace = true -documentation = "https://docs.rs/radroots-runtime-paths" -readme = "README.md" - -[dependencies] -thiserror = { workspace = true } diff --git a/crates/runtime-paths/README.md b/crates/runtime-paths/README.md @@ -1,15 +0,0 @@ -# radroots-runtime-paths - -Canonical local filesystem path resolution for Rad Roots runtimes. - -## Goals - -- define the shared interactive-user, service-host, repo-local, and mobile-native path contract -- keep Linux, macOS, and Windows path derivation deterministic and explicit -- keep runtime namespaces explicit across apps, services, workers, and shared storage areas -- give higher-level runtimes one reusable source of truth for config, data, cache, logs, run, and - secrets roots - -## License - -Licensed under AGPL-3.0. See LICENSE. diff --git a/crates/runtime-paths/src/migration.rs b/crates/runtime-paths/src/migration.rs @@ -1,171 +0,0 @@ -use std::path::PathBuf; - -pub const RADROOTS_MIGRATION_POSTURE: &str = "explicit_operator_import_required"; -pub const RADROOTS_MIGRATION_COMPATIBILITY_WINDOW: &str = "detect_and_report_only"; - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct RadrootsLegacyPathCandidate { - pub id: String, - pub description: String, - pub path: PathBuf, - pub destination: Option<PathBuf>, - pub import_hint: String, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct RadrootsLegacyPathDetection { - pub id: String, - pub description: String, - pub path: PathBuf, - pub destination: Option<PathBuf>, - pub import_hint: String, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct RadrootsMigrationReport { - pub posture: &'static str, - pub state: &'static str, - pub silent_startup_relocation: bool, - pub compatibility_window: &'static str, - pub detected_legacy_paths: Vec<RadrootsLegacyPathDetection>, -} - -impl RadrootsLegacyPathCandidate { - #[must_use] - pub fn new( - id: impl Into<String>, - description: impl Into<String>, - path: impl Into<PathBuf>, - destination: Option<PathBuf>, - import_hint: impl Into<String>, - ) -> Self { - Self { - id: id.into(), - description: description.into(), - path: path.into(), - destination, - import_hint: import_hint.into(), - } - } - - fn into_detection(self) -> RadrootsLegacyPathDetection { - RadrootsLegacyPathDetection { - id: self.id, - description: self.description, - path: self.path, - destination: self.destination, - import_hint: self.import_hint, - } - } -} - -impl RadrootsMigrationReport { - #[must_use] - pub fn empty() -> Self { - Self::from_detected_legacy_paths(Vec::new()) - } - - #[must_use] - pub fn from_detected_legacy_paths( - detected_legacy_paths: Vec<RadrootsLegacyPathDetection>, - ) -> Self { - let state = if detected_legacy_paths.is_empty() { - "ready" - } else { - "legacy_state_detected" - }; - Self { - posture: RADROOTS_MIGRATION_POSTURE, - state, - silent_startup_relocation: false, - compatibility_window: RADROOTS_MIGRATION_COMPATIBILITY_WINDOW, - detected_legacy_paths, - } - } -} - -#[must_use] -pub fn inspect_legacy_paths( - candidates: impl IntoIterator<Item = RadrootsLegacyPathCandidate>, -) -> RadrootsMigrationReport { - let detected = candidates - .into_iter() - .filter(|candidate| candidate.path.exists()) - .map(RadrootsLegacyPathCandidate::into_detection) - .collect(); - RadrootsMigrationReport::from_detected_legacy_paths(detected) -} - -#[cfg(test)] -mod tests { - use std::path::PathBuf; - - use super::{ - RADROOTS_MIGRATION_COMPATIBILITY_WINDOW, RADROOTS_MIGRATION_POSTURE, - RadrootsLegacyPathCandidate, inspect_legacy_paths, - }; - - fn unique_test_dir() -> PathBuf { - let nanos = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .expect("clock") - .as_nanos(); - let path = std::env::temp_dir().join(format!("radroots-runtime-paths-test-{nanos}")); - std::fs::create_dir_all(&path).expect("create temp test dir"); - path - } - - #[test] - fn inspect_legacy_paths_reports_only_paths_that_exist() { - let temp = unique_test_dir(); - let existing = temp.join("old-state"); - let missing = temp.join("missing-state"); - std::fs::write(&existing, "legacy").expect("write legacy marker"); - - let report = inspect_legacy_paths([ - RadrootsLegacyPathCandidate::new( - "old-state", - "old state", - &existing, - Some(temp.join("new-state")), - "run the explicit importer", - ), - RadrootsLegacyPathCandidate::new( - "missing-state", - "missing state", - &missing, - None, - "nothing to do", - ), - ]); - - assert_eq!(report.posture, RADROOTS_MIGRATION_POSTURE); - assert_eq!(report.state, "legacy_state_detected"); - assert!(!report.silent_startup_relocation); - assert_eq!( - report.compatibility_window, - RADROOTS_MIGRATION_COMPATIBILITY_WINDOW - ); - assert_eq!(report.detected_legacy_paths.len(), 1); - assert_eq!(report.detected_legacy_paths[0].id, "old-state"); - assert_eq!(report.detected_legacy_paths[0].path, existing); - std::fs::remove_dir_all(temp).expect("remove temp test dir"); - } - - #[test] - fn inspect_legacy_paths_is_ready_when_no_candidate_exists() { - let temp = unique_test_dir(); - - let report = inspect_legacy_paths([RadrootsLegacyPathCandidate::new( - "missing-state", - "missing state", - temp.join("missing-state"), - None, - "nothing to do", - )]); - - assert_eq!(report.state, "ready"); - assert!(report.detected_legacy_paths.is_empty()); - std::fs::remove_dir_all(temp).expect("remove temp test dir"); - } -} diff --git a/crates/runtime/Cargo.toml b/crates/runtime/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "radroots-runtime" +name = "radroots_runtime" version = "0.1.0-alpha.1" edition.workspace = true authors = [ @@ -10,7 +10,7 @@ license.workspace = true description = "runtime config, io, and process helpers for radroots services and apps" repository.workspace = true homepage.workspace = true -documentation = "https://docs.rs/radroots-runtime" +documentation = "https://docs.rs/radroots_runtime" readme.workspace = true [features] @@ -23,10 +23,10 @@ chacha20poly1305 = { workspace = true } clap = { workspace = true, features = ["derive", "env"], optional = true } config = { workspace = true } getrandom = { workspace = true } -radroots-log = { workspace = true, features = ["std"] } -radroots-protected-store = { workspace = true, features = ["std"] } -radroots-runtime-paths = { workspace = true } -radroots-secret-vault = { workspace = true, features = ["std"] } +radroots_log = { workspace = true, features = ["std"] } +radroots_protected_store = { workspace = true, features = ["std"] } +radroots_runtime_paths = { workspace = true } +radroots_secret_vault = { workspace = true, features = ["std"] } serde = { workspace = true } serde_json = { workspace = true } tempfile = { workspace = true } diff --git a/crates/runtime/README.md b/crates/runtime/README.md @@ -1,4 +1,4 @@ -# radroots-runtime +# radroots_runtime Runtime configuration, I/O, and process primitives for Rad Roots services and apps. diff --git a/crates/runtime/src/config.rs b/crates/runtime/src/config.rs @@ -137,7 +137,7 @@ enabled = false #[test] fn load_required_file_reports_missing_path() { - let path = std::path::PathBuf::from("/tmp/radroots-runtime-config-does-not-exist.toml"); + let path = std::path::PathBuf::from("/tmp/radroots_runtime-config-does-not-exist.toml"); let err = load_required_file::<RuntimeCfg>(&path).expect_err("missing config should fail"); match err { RuntimeConfigError::Load { path: p, .. } => assert_eq!(p, path), @@ -146,7 +146,7 @@ enabled = false #[test] fn load_required_file_reports_missing_path_for_number_cfg_owned_path() { - let path = std::path::PathBuf::from("/tmp/radroots-runtime-config-missing-number.toml"); + let path = std::path::PathBuf::from("/tmp/radroots_runtime-config-missing-number.toml"); let err = load_required_file::<NumberCfg>(path.clone()).expect_err("missing config should fail"); match err { @@ -187,7 +187,7 @@ enabled = true #[test] fn load_required_file_with_env_reports_missing_path() { let path = - std::path::PathBuf::from("/tmp/radroots-runtime-config-does-not-exist-with-env.toml"); + std::path::PathBuf::from("/tmp/radroots_runtime-config-does-not-exist-with-env.toml"); let err = load_required_file_with_env::<RuntimeCfg>(path.clone(), "RADROOTS_RUNTIME_TEST") .expect_err("missing config should fail"); match err { @@ -258,7 +258,7 @@ enabled = false #[test] fn load_required_file_with_env_and_overrides_reports_build_error() { let path = std::path::PathBuf::from( - "/tmp/radroots-runtime-config-does-not-exist-with-overrides.toml", + "/tmp/radroots_runtime-config-does-not-exist-with-overrides.toml", ); let err = load_required_file_with_env_and_overrides::<RuntimeCfg>(path.clone(), None, None) .expect_err("missing config should fail"); diff --git a/crates/runtime_distribution/Cargo.toml b/crates/runtime_distribution/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "radroots_runtime_distribution" +version = "0.1.0-alpha.1" +edition.workspace = true +authors = [ + "Radroots Authors", +] +rust-version.workspace = true +license.workspace = true +description = "contract parsing and artifact resolution for modular radroots runtime distribution" +repository.workspace = true +homepage.workspace = true +documentation = "https://docs.rs/radroots_runtime_distribution" +readme = "README.md" + +[dependencies] +serde = { workspace = true, features = ["derive"] } +thiserror = { workspace = true } +toml = { workspace = true } diff --git a/crates/runtime_distribution/README.md b/crates/runtime_distribution/README.md @@ -0,0 +1,15 @@ +# radroots_runtime_distribution + +Contract parsing and artifact resolution for modular Rad Roots runtime distribution. + +## Goals + +- parse the machine-readable runtime distribution contract +- resolve installable runtime artifacts by runtime id, channel, version, operating system, and + architecture +- keep installer and runtime-manager selection logic out of ad hoc shell branches +- give CLI and app clients one shared substrate for runtime release-unit resolution + +## License + +Licensed under AGPL-3.0. See LICENSE. diff --git a/crates/runtime-distribution/src/error.rs b/crates/runtime_distribution/src/error.rs diff --git a/crates/runtime_distribution/src/lib.rs b/crates/runtime_distribution/src/lib.rs @@ -0,0 +1,346 @@ +#![forbid(unsafe_code)] + +pub mod error; +pub mod model; +pub mod resolve; + +pub use error::RadrootsRuntimeDistributionError; +pub use model::{ + ArchiveFormat, ArtifactAdapter, ChannelSet, DistributionFamily, + RadrootsRuntimeDistributionContract, RuntimeDistributionEntry, TargetSet, TargetSpec, +}; +pub use resolve::{ + RUNTIME_DISTRIBUTION_SCHEMA, RadrootsRuntimeDistributionResolver, ResolvedRuntimeArtifact, + RuntimeArtifactRequest, +}; + +#[cfg(test)] +mod tests { + use super::{ + RUNTIME_DISTRIBUTION_SCHEMA, RadrootsRuntimeDistributionError, + RadrootsRuntimeDistributionResolver, RuntimeArtifactRequest, + }; + + const CONTRACT: &str = r#" +schema = "radroots_runtime_distribution" +schema_version = 1 +owner_doc = "docs/migration/radroots-modular-runtime-management-bootstrap-rcl.md" +runtime_registry = "registry.toml" + +[family] +id = "radroots_runtime-family" +canonical_installer_engine = "single_runtime_selected" +human_install_facade = "delivery_publication_only" +tooling_consumption = "shared_distribution_library" +independent_runtime_versions = true +version_resolution = "runtime_scoped_channel_latest" +artifact_verification_required = true + +[channels] +active = ["stable"] +defined = ["stable", "candidate", "nightly"] + +[artifact_adapters.rust_binary_archive] +kind = "binary_archive" +supported_archive_formats = ["tar.gz", "zip"] +layout = "single_binary_plus_supporting_files" + +[artifact_adapters.desktop_bundle] +kind = "desktop_bundle" +supported_archive_formats = ["tar.gz", "zip", "dmg"] +layout = "host_native_bundle" + +[artifact_adapters.mobile_store_package] +kind = "mobile_store_package" +supported_archive_formats = [] +layout = "platform_store_managed" + +[artifact_adapters.mojo_workspace_archive] +kind = "workspace_archive" +supported_archive_formats = ["tar.gz"] +layout = "workspace_tree" + +[archive_formats.tar_gz] +extension = ".tar.gz" +platforms = ["linux", "macos"] + +[archive_formats.zip] +extension = ".zip" +platforms = ["windows"] + +[archive_formats.dmg] +extension = ".dmg" +platforms = ["macos"] + +[target_sets.server_default] +targets = [ + "x86_64-unknown-linux-gnu", + "aarch64-unknown-linux-gnu", +] + +[target_sets.cli_default] +targets = [ + "x86_64-unknown-linux-gnu", + "aarch64-unknown-linux-gnu", + "x86_64-apple-darwin", + "aarch64-apple-darwin", +] + +[target_sets.desktop_default] +targets = [ + "x86_64-apple-darwin", + "aarch64-apple-darwin", +] + +[target_sets.mojo_workspace_default] +targets = [ + "osx-arm64", + "linux-64", +] + +[targets.x86_64-unknown-linux-gnu] +os = "linux" +arch = "amd64" +archive_format = "tar.gz" + +[targets.aarch64-unknown-linux-gnu] +os = "linux" +arch = "arm64" +archive_format = "tar.gz" + +[targets.x86_64-apple-darwin] +os = "macos" +arch = "amd64" +archive_format = "tar.gz" + +[targets.aarch64-apple-darwin] +os = "macos" +arch = "arm64" +archive_format = "tar.gz" + +[targets.osx-arm64] +os = "macos" +arch = "arm64" +archive_format = "tar.gz" + +[targets.linux-64] +os = "linux" +arch = "amd64" +archive_format = "tar.gz" + +[[runtime]] +id = "cli" +distribution_state = "active" +release_unit = "cli" +package_name = "radroots-cli" +binary_name = "radroots" +artifact_adapter = "rust_binary_archive" +target_set = "cli_default" +default_channel = "stable" +human_installable = true + +[[runtime]] +id = "radrootsd" +distribution_state = "active" +release_unit = "radrootsd" +package_name = "radrootsd" +binary_name = "radrootsd" +artifact_adapter = "rust_binary_archive" +target_set = "server_default" +default_channel = "stable" +human_installable = true + +[[runtime]] +id = "community-app-desktop" +distribution_state = "defined" +release_unit = "community-app-desktop" +package_name = "radroots-app-desktop" +binary_name = "radroots-app-desktop" +artifact_adapter = "desktop_bundle" +target_set = "desktop_default" +default_channel = "stable" +human_installable = true + +[[runtime]] +id = "community-app-ios" +distribution_state = "external_platform_managed" +release_unit = "community-app-ios" +package_name = "radroots-app-ios" +artifact_adapter = "mobile_store_package" +default_channel = "stable" +human_installable = false + +[[runtime]] +id = "hyf" +distribution_state = "bootstrap_only" +release_unit = "hyf" +package_name = "hyf" +binary_name = "hyf" +artifact_adapter = "mojo_workspace_archive" +target_set = "mojo_workspace_default" +default_channel = "stable" +human_installable = false +"#; + + #[test] + fn parse_str_accepts_the_expected_schema() { + let resolver = + RadrootsRuntimeDistributionResolver::parse_str(CONTRACT).expect("parse contract"); + + assert_eq!(resolver.contract().schema, RUNTIME_DISTRIBUTION_SCHEMA); + assert_eq!(resolver.contract().runtime.len(), 5); + } + + #[test] + fn resolves_cli_linux_artifact_with_explicit_channel() { + let resolver = + RadrootsRuntimeDistributionResolver::parse_str(CONTRACT).expect("parse contract"); + + let artifact = resolver + .resolve_artifact(&RuntimeArtifactRequest { + runtime_id: "cli", + os: "linux", + arch: "amd64", + version: "0.1.0-alpha.1", + channel: Some("stable"), + }) + .expect("resolve cli artifact"); + + assert_eq!(artifact.binary_name.as_deref(), Some("radroots")); + assert_eq!(artifact.target_id, "x86_64-unknown-linux-gnu"); + assert_eq!(artifact.archive_extension, ".tar.gz"); + assert_eq!( + artifact.artifact_file_name, + "cli-0.1.0-alpha.1-x86_64-unknown-linux-gnu.tar.gz" + ); + } + + #[test] + fn resolves_radrootsd_linux_arm64_using_default_channel() { + let resolver = + RadrootsRuntimeDistributionResolver::parse_str(CONTRACT).expect("parse contract"); + + let artifact = resolver + .resolve_artifact(&RuntimeArtifactRequest { + runtime_id: "radrootsd", + os: "linux", + arch: "arm64", + version: "0.1.0-alpha.1", + channel: None, + }) + .expect("resolve radrootsd artifact"); + + assert_eq!(artifact.channel, "stable"); + assert_eq!(artifact.target_id, "aarch64-unknown-linux-gnu"); + assert_eq!(artifact.binary_name.as_deref(), Some("radrootsd")); + } + + #[test] + fn resolves_desktop_bundle_for_macos_arm64() { + let resolver = + RadrootsRuntimeDistributionResolver::parse_str(CONTRACT).expect("parse contract"); + + let artifact = resolver + .resolve_artifact(&RuntimeArtifactRequest { + runtime_id: "community-app-desktop", + os: "macos", + arch: "arm64", + version: "0.1.0-alpha.1", + channel: Some("stable"), + }) + .expect("resolve desktop artifact"); + + assert_eq!(artifact.target_id, "aarch64-apple-darwin"); + assert_eq!(artifact.package_name, "radroots-app-desktop"); + } + + #[test] + fn rejects_non_installable_mobile_runtime() { + let resolver = + RadrootsRuntimeDistributionResolver::parse_str(CONTRACT).expect("parse contract"); + + let err = resolver + .resolve_artifact(&RuntimeArtifactRequest { + runtime_id: "community-app-ios", + os: "macos", + arch: "arm64", + version: "0.1.0-alpha.1", + channel: Some("stable"), + }) + .expect_err("mobile runtime should not be installable"); + + assert_eq!( + err, + RadrootsRuntimeDistributionError::RuntimeNotInstallable( + "community-app-ios".to_string() + ) + ); + } + + #[test] + fn rejects_bootstrap_only_runtime() { + let resolver = + RadrootsRuntimeDistributionResolver::parse_str(CONTRACT).expect("parse contract"); + + let err = resolver + .resolve_artifact(&RuntimeArtifactRequest { + runtime_id: "hyf", + os: "macos", + arch: "arm64", + version: "0.1.0", + channel: Some("stable"), + }) + .expect_err("bootstrap runtime should not be installable"); + + assert_eq!( + err, + RadrootsRuntimeDistributionError::RuntimeNotInstallable("hyf".to_string()) + ); + } + + #[test] + fn rejects_inactive_channel() { + let resolver = + RadrootsRuntimeDistributionResolver::parse_str(CONTRACT).expect("parse contract"); + + let err = resolver + .resolve_artifact(&RuntimeArtifactRequest { + runtime_id: "cli", + os: "linux", + arch: "amd64", + version: "0.1.0-alpha.1", + channel: Some("candidate"), + }) + .expect_err("candidate channel should be inactive"); + + assert_eq!( + err, + RadrootsRuntimeDistributionError::InactiveChannel("candidate".to_string()) + ); + } + + #[test] + fn rejects_unsupported_platform() { + let resolver = + RadrootsRuntimeDistributionResolver::parse_str(CONTRACT).expect("parse contract"); + + let err = resolver + .resolve_artifact(&RuntimeArtifactRequest { + runtime_id: "radrootsd", + os: "windows", + arch: "amd64", + version: "0.1.0-alpha.1", + channel: Some("stable"), + }) + .expect_err("windows target should be unsupported"); + + assert_eq!( + err, + RadrootsRuntimeDistributionError::UnsupportedPlatform { + runtime_id: "radrootsd".to_string(), + os: "windows".to_string(), + arch: "amd64".to_string(), + } + ); + } +} diff --git a/crates/runtime-distribution/src/model.rs b/crates/runtime_distribution/src/model.rs diff --git a/crates/runtime_distribution/src/resolve.rs b/crates/runtime_distribution/src/resolve.rs @@ -0,0 +1,227 @@ +use crate::error::RadrootsRuntimeDistributionError; +use crate::model::{ + ArtifactAdapter, RadrootsRuntimeDistributionContract, RuntimeDistributionEntry, TargetSpec, +}; + +pub const RUNTIME_DISTRIBUTION_SCHEMA: &str = "radroots_runtime_distribution"; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct RuntimeArtifactRequest<'a> { + pub runtime_id: &'a str, + pub os: &'a str, + pub arch: &'a str, + pub version: &'a str, + pub channel: Option<&'a str>, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ResolvedRuntimeArtifact { + pub runtime_id: String, + pub release_unit: String, + pub package_name: String, + pub binary_name: Option<String>, + pub artifact_adapter: String, + pub channel: String, + pub version: String, + pub target_id: String, + pub os: String, + pub arch: String, + pub archive_format: String, + pub archive_extension: String, + pub artifact_stem: String, + pub artifact_file_name: String, +} + +#[derive(Debug, Clone)] +pub struct RadrootsRuntimeDistributionResolver { + contract: RadrootsRuntimeDistributionContract, +} + +impl RadrootsRuntimeDistributionResolver { + pub fn parse_str(raw: &str) -> Result<Self, RadrootsRuntimeDistributionError> { + let contract = toml::from_str::<RadrootsRuntimeDistributionContract>(raw) + .map_err(|err| RadrootsRuntimeDistributionError::Parse(err.to_string()))?; + Self::new(contract) + } + + pub fn new( + contract: RadrootsRuntimeDistributionContract, + ) -> Result<Self, RadrootsRuntimeDistributionError> { + if contract.schema != RUNTIME_DISTRIBUTION_SCHEMA { + return Err(RadrootsRuntimeDistributionError::UnexpectedSchema { + expected: RUNTIME_DISTRIBUTION_SCHEMA, + found: contract.schema.clone(), + }); + } + Ok(Self { contract }) + } + + pub fn contract(&self) -> &RadrootsRuntimeDistributionContract { + &self.contract + } + + pub fn resolve_artifact( + &self, + request: &RuntimeArtifactRequest<'_>, + ) -> Result<ResolvedRuntimeArtifact, RadrootsRuntimeDistributionError> { + let runtime = self + .contract + .runtime + .iter() + .find(|runtime| runtime.id == request.runtime_id) + .ok_or_else(|| { + RadrootsRuntimeDistributionError::UnknownRuntime(request.runtime_id.to_string()) + })?; + + if !runtime.human_installable { + return Err(RadrootsRuntimeDistributionError::RuntimeNotInstallable( + runtime.id.clone(), + )); + } + + let channel = request.channel.unwrap_or(runtime.default_channel.as_str()); + self.ensure_channel_is_active(channel)?; + + let target_set_id = runtime.target_set.as_ref().ok_or_else(|| { + RadrootsRuntimeDistributionError::MissingTargetSet(runtime.id.clone()) + })?; + + let adapter = self + .contract + .artifact_adapters + .get(&runtime.artifact_adapter) + .ok_or_else( + || RadrootsRuntimeDistributionError::UnknownArtifactAdapter { + runtime_id: runtime.id.clone(), + adapter_id: runtime.artifact_adapter.clone(), + }, + )?; + + let (target_id, target) = + self.select_target(runtime, target_set_id, request.os, request.arch)?; + let archive_format_id = + self.resolve_archive_format_id(runtime, target_id, target, adapter)?; + let archive_format = self + .contract + .archive_formats + .get(&normalized_contract_key(archive_format_id)) + .ok_or_else(|| RadrootsRuntimeDistributionError::UnknownArchiveFormat { + target_id: target_id.to_string(), + archive_format_id: archive_format_id.to_string(), + })?; + + let artifact_stem = format!("{}-{}-{}", runtime.release_unit, request.version, target_id); + let artifact_file_name = format!("{artifact_stem}{}", archive_format.extension); + + Ok(ResolvedRuntimeArtifact { + runtime_id: runtime.id.clone(), + release_unit: runtime.release_unit.clone(), + package_name: runtime.package_name.clone(), + binary_name: runtime.binary_name.clone(), + artifact_adapter: runtime.artifact_adapter.clone(), + channel: channel.to_string(), + version: request.version.to_string(), + target_id: target_id.to_string(), + os: request.os.to_string(), + arch: request.arch.to_string(), + archive_format: archive_format_id.to_string(), + archive_extension: archive_format.extension.clone(), + artifact_stem, + artifact_file_name, + }) + } + + fn ensure_channel_is_active( + &self, + channel: &str, + ) -> Result<(), RadrootsRuntimeDistributionError> { + if !self + .contract + .channels + .defined + .iter() + .any(|entry| entry == channel) + { + return Err(RadrootsRuntimeDistributionError::UnknownChannel( + channel.to_string(), + )); + } + if !self + .contract + .channels + .active + .iter() + .any(|entry| entry == channel) + { + return Err(RadrootsRuntimeDistributionError::InactiveChannel( + channel.to_string(), + )); + } + Ok(()) + } + + fn select_target<'a>( + &'a self, + runtime: &RuntimeDistributionEntry, + target_set_id: &str, + os: &str, + arch: &str, + ) -> Result<(&'a str, &'a TargetSpec), RadrootsRuntimeDistributionError> { + let target_set = self + .contract + .target_sets + .get(target_set_id) + .ok_or_else(|| RadrootsRuntimeDistributionError::UnsupportedPlatform { + runtime_id: runtime.id.clone(), + os: os.to_string(), + arch: arch.to_string(), + })?; + + let mut found_match = None; + for target_id in &target_set.targets { + let target = self.contract.targets.get(target_id).ok_or_else(|| { + RadrootsRuntimeDistributionError::UnknownTarget { + runtime_id: runtime.id.clone(), + target_set_id: target_set_id.to_string(), + target_id: target_id.clone(), + } + })?; + + if target.os == os && target.arch == arch { + found_match = Some((target_id.as_str(), target)); + break; + } + } + + found_match.ok_or_else(|| RadrootsRuntimeDistributionError::UnsupportedPlatform { + runtime_id: runtime.id.clone(), + os: os.to_string(), + arch: arch.to_string(), + }) + } + + fn resolve_archive_format_id<'a>( + &self, + runtime: &RuntimeDistributionEntry, + target_id: &'a str, + target: &'a TargetSpec, + adapter: &'a ArtifactAdapter, + ) -> Result<&'a str, RadrootsRuntimeDistributionError> { + if let Some(format) = target.archive_format.as_deref() { + return Ok(format); + } + + if adapter.supported_archive_formats.len() == 1 { + return Ok(adapter.supported_archive_formats[0].as_str()); + } + + Err(RadrootsRuntimeDistributionError::MissingArchiveFormat { + runtime_id: runtime.id.clone(), + target_id: target_id.to_string(), + }) + } +} + +fn normalized_contract_key(value: &str) -> String { + value.replace('.', "_") +} diff --git a/crates/runtime_manager/Cargo.toml b/crates/runtime_manager/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "radroots_runtime_manager" +version = "0.1.0-alpha.1" +edition.workspace = true +authors = [ + "Radroots Authors", +] +rust-version.workspace = true +license.workspace = true +description = "shared local managed-runtime paths, registry, and lifecycle boundary for radroots" +repository.workspace = true +homepage.workspace = true +documentation = "https://docs.rs/radroots_runtime_manager" +readme = "README.md" + +[dependencies] +radroots_runtime_paths = { workspace = true } +serde = { workspace = true, features = ["derive"] } +thiserror = { workspace = true } +toml = { workspace = true } + +[dev-dependencies] +tempfile = { workspace = true } diff --git a/crates/runtime_manager/README.md b/crates/runtime_manager/README.md @@ -0,0 +1,14 @@ +# radroots_runtime_manager + +Shared local managed-runtime paths, registry, and lifecycle boundary for Rad Roots runtimes. + +## Goals + +- parse the machine-readable runtime management contract +- resolve shared manager roots and per-instance runtime-manager paths +- persist one shared managed-runtime instance registry +- define typed install and health state surfaces for CLI and app adoption later + +## License + +Licensed under AGPL-3.0. See LICENSE. diff --git a/crates/runtime-manager/src/error.rs b/crates/runtime_manager/src/error.rs diff --git a/crates/runtime_manager/src/lib.rs b/crates/runtime_manager/src/lib.rs @@ -0,0 +1,305 @@ +#![forbid(unsafe_code)] + +pub mod error; +pub mod model; +pub mod paths; +pub mod registry; + +pub use error::RadrootsRuntimeManagerError; +pub use model::{ + BootstrapRuntimeContract, LifecycleContract, ManagedRuntimeHealthState, + ManagedRuntimeInstallState, ManagedRuntimeInstanceRecord, ManagedRuntimeInstanceRegistry, + ManagementDefaults, ManagementModeContract, ManagementPathContract, + RadrootsRuntimeManagementContract, RuntimeGroups, +}; +pub use paths::{ + ManagedRuntimeInstancePaths, ManagedRuntimeSharedPaths, bootstrap_runtime, + resolve_instance_paths, resolve_shared_paths, +}; +pub use registry::{instance, load_registry, save_registry, upsert_instance}; + +pub const RUNTIME_MANAGEMENT_SCHEMA: &str = "radroots_runtime-management"; + +pub fn parse_contract_str( + raw: &str, +) -> Result<RadrootsRuntimeManagementContract, RadrootsRuntimeManagerError> { + let contract = toml::from_str::<RadrootsRuntimeManagementContract>(raw) + .map_err(|err| RadrootsRuntimeManagerError::Parse(err.to_string()))?; + if contract.schema != RUNTIME_MANAGEMENT_SCHEMA { + return Err(RadrootsRuntimeManagerError::UnexpectedSchema { + expected: RUNTIME_MANAGEMENT_SCHEMA, + found: contract.schema.clone(), + }); + } + Ok(contract) +} + +#[cfg(test)] +mod tests { + use std::path::PathBuf; + + use radroots_runtime_paths::{ + RadrootsHostEnvironment, RadrootsPathOverrides, RadrootsPathProfile, RadrootsPathResolver, + RadrootsPlatform, + }; + use tempfile::tempdir; + + use crate::{ + ManagedRuntimeHealthState, ManagedRuntimeInstallState, ManagedRuntimeInstanceRecord, + bootstrap_runtime, instance, load_registry, parse_contract_str, resolve_instance_paths, + resolve_shared_paths, save_registry, upsert_instance, + }; + + const CONTRACT: &str = r#" +schema = "radroots_runtime-management" +schema_version = 1 +owner_doc = "docs/migration/radroots-modular-runtime-management-bootstrap-rcl.md" +runtime_registry = "registry.toml" +distribution_contract = "distribution.toml" +capabilities_contract = "capabilities.toml" + +[defaults] +instance_cardinality = "single_default_instance" +managed_runtime_lookup = "shared_instance_registry" +explicit_runtime_endpoint_overrides_precede_managed_instance_binding = true +global_path_mutation_forbidden = true + +[management_clients] +active = ["cli"] +defined = ["community-app-desktop"] + +[managed_runtime_targets] +active = ["radrootsd"] +defined = ["myc", "rhi"] +bootstrap_only = ["hyf"] + +[lifecycle] +actions = ["install", "uninstall", "start", "stop", "restart", "status", "logs", "config_show", "config_set"] +destructive_actions = ["uninstall"] +health_states = ["not_installed", "stopped", "starting", "running", "degraded", "failed"] + +[mode.interactive_user_managed] +contract_state = "active" +platforms = ["linux", "macos", "windows"] +supported_profiles = ["interactive_user", "repo_local"] +service_manager_integration = false +uses_absolute_binary_paths = true +requires_explicit_pid_tracking = true +requires_explicit_log_tracking = true +default_instance_cardinality = "single_default_instance" + +[mode.service_host_managed] +contract_state = "defined" +platforms = ["linux", "macos", "windows"] +supported_profiles = ["service_host"] +service_manager_integration = true +uses_absolute_binary_paths = true +default_instance_cardinality = "single_default_instance" + +[paths.interactive_user_managed] +shared_namespace = "shared/runtime-manager" +instance_registry_root_class = "config" +instance_registry_rel = "shared/runtime-manager/instances.toml" +artifact_cache_root_class = "cache" +artifact_cache_rel = "shared/runtime-manager/artifacts" +install_root_class = "data" +install_root_rel = "shared/runtime-manager/installs" +state_root_class = "data" +state_root_rel = "shared/runtime-manager/state" +logs_root_class = "logs" +logs_root_rel = "shared/runtime-manager" +run_root_class = "run" +run_root_rel = "shared/runtime-manager" +secrets_root_class = "secrets" +secrets_namespace_rel = "shared/runtime-manager" + +[instance_metadata] +required_fields = [ + "runtime_id", + "instance_id", + "management_mode", + "install_state", + "binary_path", + "config_path", + "logs_path", + "run_path", + "installed_version", +] +optional_fields = [ + "health_endpoint", + "secret_material_ref", + "last_started_at", + "last_stopped_at", + "notes", +] + +[bootstrap.radrootsd] +runtime_id = "radrootsd" +management_mode = "interactive_user_managed" +default_instance_id = "local" +install_strategy = "archive_unpack" +config_format = "toml" +requires_bootstrap_secret = true +requires_config_bootstrap = true +requires_signer_provider = false +health_surface = "jsonrpc_status" +preferred_cli_binding = true +"#; + + #[test] + fn parse_contract_accepts_expected_schema() { + let contract = parse_contract_str(CONTRACT).expect("parse contract"); + assert_eq!(contract.schema, crate::RUNTIME_MANAGEMENT_SCHEMA); + assert!(contract.mode.contains_key("interactive_user_managed")); + } + + #[test] + fn resolve_shared_paths_uses_interactive_user_roots() { + let contract = parse_contract_str(CONTRACT).expect("parse contract"); + let resolver = RadrootsPathResolver::new( + RadrootsPlatform::Linux, + RadrootsHostEnvironment { + home_dir: Some(PathBuf::from("/home/treesap")), + ..RadrootsHostEnvironment::default() + }, + ); + + let paths = resolve_shared_paths( + &contract, + &resolver, + RadrootsPathProfile::InteractiveUser, + &RadrootsPathOverrides::default(), + "interactive_user_managed", + ) + .expect("resolve shared manager paths"); + + assert_eq!( + paths.instance_registry_path, + PathBuf::from("/home/treesap/.radroots/config/shared/runtime-manager/instances.toml") + ); + assert_eq!( + paths.install_root, + PathBuf::from("/home/treesap/.radroots/data/shared/runtime-manager/installs") + ); + assert_eq!( + paths.logs_root, + PathBuf::from("/home/treesap/.radroots/logs/shared/runtime-manager") + ); + } + + #[test] + fn resolve_repo_local_paths_uses_explicit_base_root() { + let contract = parse_contract_str(CONTRACT).expect("parse contract"); + let resolver = + RadrootsPathResolver::new(RadrootsPlatform::Linux, RadrootsHostEnvironment::default()); + + let paths = resolve_shared_paths( + &contract, + &resolver, + RadrootsPathProfile::RepoLocal, + &RadrootsPathOverrides::repo_local("/repo/.local/radroots"), + "interactive_user_managed", + ) + .expect("resolve repo local manager paths"); + + assert_eq!( + paths.state_root, + PathBuf::from("/repo/.local/radroots/data/shared/runtime-manager/state") + ); + } + + #[test] + fn resolve_instance_paths_builds_per_runtime_layout() { + let contract = parse_contract_str(CONTRACT).expect("parse contract"); + let resolver = RadrootsPathResolver::new( + RadrootsPlatform::Macos, + RadrootsHostEnvironment { + home_dir: Some(PathBuf::from("/Users/treesap")), + ..RadrootsHostEnvironment::default() + }, + ); + let shared = resolve_shared_paths( + &contract, + &resolver, + RadrootsPathProfile::InteractiveUser, + &RadrootsPathOverrides::default(), + "interactive_user_managed", + ) + .expect("resolve shared manager paths"); + + let instance_paths = resolve_instance_paths(&shared, "radrootsd", "local"); + assert_eq!( + instance_paths.install_dir, + PathBuf::from( + "/Users/treesap/.radroots/data/shared/runtime-manager/installs/radrootsd/local" + ) + ); + assert_eq!( + instance_paths.pid_file_path, + PathBuf::from( + "/Users/treesap/.radroots/run/shared/runtime-manager/radrootsd/local/runtime.pid" + ) + ); + assert_eq!( + instance_paths.metadata_path, + PathBuf::from( + "/Users/treesap/.radroots/data/shared/runtime-manager/state/radrootsd/local/instance.toml" + ) + ); + } + + #[test] + fn registry_round_trip_persists_and_reloads_instances() { + let dir = tempdir().expect("tempdir"); + let registry_path = dir.path().join("instances.toml"); + let mut registry = crate::ManagedRuntimeInstanceRegistry::default(); + upsert_instance( + &mut registry, + ManagedRuntimeInstanceRecord { + runtime_id: "radrootsd".to_string(), + instance_id: "local".to_string(), + management_mode: "interactive_user_managed".to_string(), + install_state: ManagedRuntimeInstallState::Configured, + binary_path: PathBuf::from("/tmp/radrootsd"), + config_path: PathBuf::from("/tmp/config.toml"), + logs_path: PathBuf::from("/tmp/logs"), + run_path: PathBuf::from("/tmp/run"), + installed_version: "0.1.0-alpha.1".to_string(), + health_endpoint: Some("jsonrpc_status".to_string()), + secret_material_ref: Some( + "shared/runtime-manager/radrootsd/local/token".to_string(), + ), + last_started_at: Some("2026-04-08T00:00:00Z".to_string()), + last_stopped_at: None, + notes: Some("test".to_string()), + }, + ); + + save_registry(®istry_path, ®istry).expect("save registry"); + let reloaded = load_registry(®istry_path).expect("load registry"); + let record = instance(&reloaded, "radrootsd", "local").expect("instance record"); + assert_eq!(record.install_state, ManagedRuntimeInstallState::Configured); + assert_eq!(record.health_endpoint.as_deref(), Some("jsonrpc_status")); + } + + #[test] + fn bootstrap_lookup_returns_radrootsd_contract() { + let contract = parse_contract_str(CONTRACT).expect("parse contract"); + let bootstrap = bootstrap_runtime(&contract, "radrootsd").expect("bootstrap contract"); + assert_eq!(bootstrap.default_instance_id, "local"); + assert_eq!(bootstrap.health_surface, "jsonrpc_status"); + assert!(bootstrap.preferred_cli_binding); + } + + #[test] + fn install_and_health_state_surface_is_typed() { + assert_eq!( + ManagedRuntimeInstallState::Installed, + ManagedRuntimeInstallState::Installed + ); + assert_eq!( + ManagedRuntimeHealthState::Degraded, + ManagedRuntimeHealthState::Degraded + ); + } +} diff --git a/crates/runtime_manager/src/model.rs b/crates/runtime_manager/src/model.rs @@ -0,0 +1,162 @@ +use std::collections::BTreeMap; +use std::path::PathBuf; + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Deserialize, PartialEq, Eq)] +pub struct RadrootsRuntimeManagementContract { + pub schema: String, + pub schema_version: u32, + pub owner_doc: String, + pub runtime_registry: String, + pub distribution_contract: String, + pub capabilities_contract: String, + pub defaults: ManagementDefaults, + pub management_clients: RuntimeGroups, + pub managed_runtime_targets: RuntimeGroups, + pub lifecycle: LifecycleContract, + pub mode: BTreeMap<String, ManagementModeContract>, + pub paths: BTreeMap<String, ManagementPathContract>, + pub instance_metadata: InstanceMetadataContract, + pub bootstrap: BTreeMap<String, BootstrapRuntimeContract>, +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Eq)] +pub struct ManagementDefaults { + pub instance_cardinality: String, + pub managed_runtime_lookup: String, + pub explicit_runtime_endpoint_overrides_precede_managed_instance_binding: bool, + pub global_path_mutation_forbidden: bool, +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Default)] +pub struct RuntimeGroups { + #[serde(default)] + pub active: Vec<String>, + #[serde(default)] + pub defined: Vec<String>, + #[serde(default)] + pub bootstrap_only: Vec<String>, +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Eq)] +pub struct LifecycleContract { + #[serde(default)] + pub actions: Vec<String>, + #[serde(default)] + pub destructive_actions: Vec<String>, + #[serde(default)] + pub health_states: Vec<String>, +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Eq)] +pub struct ManagementModeContract { + pub contract_state: String, + #[serde(default)] + pub platforms: Vec<String>, + #[serde(default)] + pub supported_profiles: Vec<String>, + pub service_manager_integration: bool, + pub uses_absolute_binary_paths: bool, + pub default_instance_cardinality: String, + pub requires_explicit_pid_tracking: Option<bool>, + pub requires_explicit_log_tracking: Option<bool>, +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Eq)] +pub struct ManagementPathContract { + pub shared_namespace: String, + pub instance_registry_root_class: String, + pub instance_registry_rel: String, + pub artifact_cache_root_class: String, + pub artifact_cache_rel: String, + pub install_root_class: String, + pub install_root_rel: String, + pub state_root_class: String, + pub state_root_rel: String, + pub logs_root_class: String, + pub logs_root_rel: String, + pub run_root_class: String, + pub run_root_rel: String, + pub secrets_root_class: String, + pub secrets_namespace_rel: String, +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Eq)] +pub struct InstanceMetadataContract { + #[serde(default)] + pub required_fields: Vec<String>, + #[serde(default)] + pub optional_fields: Vec<String>, +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Eq)] +pub struct BootstrapRuntimeContract { + pub runtime_id: String, + pub management_mode: String, + pub default_instance_id: String, + pub install_strategy: String, + pub config_format: String, + pub requires_bootstrap_secret: bool, + pub requires_config_bootstrap: bool, + pub requires_signer_provider: bool, + pub health_surface: String, + pub preferred_cli_binding: bool, + pub notes: Option<String>, +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum ManagedRuntimeInstallState { + NotInstalled, + Installed, + Configured, + Failed, +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum ManagedRuntimeHealthState { + NotInstalled, + Stopped, + Starting, + Running, + Degraded, + Failed, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct ManagedRuntimeInstanceRecord { + pub runtime_id: String, + pub instance_id: String, + pub management_mode: String, + pub install_state: ManagedRuntimeInstallState, + pub binary_path: PathBuf, + pub config_path: PathBuf, + pub logs_path: PathBuf, + pub run_path: PathBuf, + pub installed_version: String, + pub health_endpoint: Option<String>, + pub secret_material_ref: Option<String>, + pub last_started_at: Option<String>, + pub last_stopped_at: Option<String>, + pub notes: Option<String>, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct ManagedRuntimeInstanceRegistry { + pub schema: String, + pub schema_version: u32, + #[serde(default)] + pub instances: Vec<ManagedRuntimeInstanceRecord>, +} + +impl Default for ManagedRuntimeInstanceRegistry { + fn default() -> Self { + Self { + schema: "radroots_runtime-instance-registry".to_string(), + schema_version: 1, + instances: Vec::new(), + } + } +} diff --git a/crates/runtime-manager/src/paths.rs b/crates/runtime_manager/src/paths.rs diff --git a/crates/runtime-manager/src/registry.rs b/crates/runtime_manager/src/registry.rs diff --git a/crates/runtime_paths/Cargo.toml b/crates/runtime_paths/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "radroots_runtime_paths" +version = "0.1.0-alpha.1" +edition.workspace = true +authors = [ + "Radroots Authors", +] +rust-version.workspace = true +license.workspace = true +description = "canonical local filesystem path resolution for radroots runtimes" +repository.workspace = true +homepage.workspace = true +documentation = "https://docs.rs/radroots_runtime_paths" +readme = "README.md" + +[dependencies] +thiserror = { workspace = true } diff --git a/crates/runtime_paths/README.md b/crates/runtime_paths/README.md @@ -0,0 +1,15 @@ +# radroots_runtime_paths + +Canonical local filesystem path resolution for Rad Roots runtimes. + +## Goals + +- define the shared interactive-user, service-host, repo-local, and mobile-native path contract +- keep Linux, macOS, and Windows path derivation deterministic and explicit +- keep runtime namespaces explicit across apps, services, workers, and shared storage areas +- give higher-level runtimes one reusable source of truth for config, data, cache, logs, run, and + secrets roots + +## License + +Licensed under AGPL-3.0. See LICENSE. diff --git a/crates/runtime-paths/src/conventions.rs b/crates/runtime_paths/src/conventions.rs diff --git a/crates/runtime-paths/src/error.rs b/crates/runtime_paths/src/error.rs diff --git a/crates/runtime-paths/src/lib.rs b/crates/runtime_paths/src/lib.rs diff --git a/crates/runtime_paths/src/migration.rs b/crates/runtime_paths/src/migration.rs @@ -0,0 +1,171 @@ +use std::path::PathBuf; + +pub const RADROOTS_MIGRATION_POSTURE: &str = "explicit_operator_import_required"; +pub const RADROOTS_MIGRATION_COMPATIBILITY_WINDOW: &str = "detect_and_report_only"; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct RadrootsLegacyPathCandidate { + pub id: String, + pub description: String, + pub path: PathBuf, + pub destination: Option<PathBuf>, + pub import_hint: String, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct RadrootsLegacyPathDetection { + pub id: String, + pub description: String, + pub path: PathBuf, + pub destination: Option<PathBuf>, + pub import_hint: String, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct RadrootsMigrationReport { + pub posture: &'static str, + pub state: &'static str, + pub silent_startup_relocation: bool, + pub compatibility_window: &'static str, + pub detected_legacy_paths: Vec<RadrootsLegacyPathDetection>, +} + +impl RadrootsLegacyPathCandidate { + #[must_use] + pub fn new( + id: impl Into<String>, + description: impl Into<String>, + path: impl Into<PathBuf>, + destination: Option<PathBuf>, + import_hint: impl Into<String>, + ) -> Self { + Self { + id: id.into(), + description: description.into(), + path: path.into(), + destination, + import_hint: import_hint.into(), + } + } + + fn into_detection(self) -> RadrootsLegacyPathDetection { + RadrootsLegacyPathDetection { + id: self.id, + description: self.description, + path: self.path, + destination: self.destination, + import_hint: self.import_hint, + } + } +} + +impl RadrootsMigrationReport { + #[must_use] + pub fn empty() -> Self { + Self::from_detected_legacy_paths(Vec::new()) + } + + #[must_use] + pub fn from_detected_legacy_paths( + detected_legacy_paths: Vec<RadrootsLegacyPathDetection>, + ) -> Self { + let state = if detected_legacy_paths.is_empty() { + "ready" + } else { + "legacy_state_detected" + }; + Self { + posture: RADROOTS_MIGRATION_POSTURE, + state, + silent_startup_relocation: false, + compatibility_window: RADROOTS_MIGRATION_COMPATIBILITY_WINDOW, + detected_legacy_paths, + } + } +} + +#[must_use] +pub fn inspect_legacy_paths( + candidates: impl IntoIterator<Item = RadrootsLegacyPathCandidate>, +) -> RadrootsMigrationReport { + let detected = candidates + .into_iter() + .filter(|candidate| candidate.path.exists()) + .map(RadrootsLegacyPathCandidate::into_detection) + .collect(); + RadrootsMigrationReport::from_detected_legacy_paths(detected) +} + +#[cfg(test)] +mod tests { + use std::path::PathBuf; + + use super::{ + RADROOTS_MIGRATION_COMPATIBILITY_WINDOW, RADROOTS_MIGRATION_POSTURE, + RadrootsLegacyPathCandidate, inspect_legacy_paths, + }; + + fn unique_test_dir() -> PathBuf { + let nanos = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .expect("clock") + .as_nanos(); + let path = std::env::temp_dir().join(format!("radroots_runtime_paths-test-{nanos}")); + std::fs::create_dir_all(&path).expect("create temp test dir"); + path + } + + #[test] + fn inspect_legacy_paths_reports_only_paths_that_exist() { + let temp = unique_test_dir(); + let existing = temp.join("old-state"); + let missing = temp.join("missing-state"); + std::fs::write(&existing, "legacy").expect("write legacy marker"); + + let report = inspect_legacy_paths([ + RadrootsLegacyPathCandidate::new( + "old-state", + "old state", + &existing, + Some(temp.join("new-state")), + "run the explicit importer", + ), + RadrootsLegacyPathCandidate::new( + "missing-state", + "missing state", + &missing, + None, + "nothing to do", + ), + ]); + + assert_eq!(report.posture, RADROOTS_MIGRATION_POSTURE); + assert_eq!(report.state, "legacy_state_detected"); + assert!(!report.silent_startup_relocation); + assert_eq!( + report.compatibility_window, + RADROOTS_MIGRATION_COMPATIBILITY_WINDOW + ); + assert_eq!(report.detected_legacy_paths.len(), 1); + assert_eq!(report.detected_legacy_paths[0].id, "old-state"); + assert_eq!(report.detected_legacy_paths[0].path, existing); + std::fs::remove_dir_all(temp).expect("remove temp test dir"); + } + + #[test] + fn inspect_legacy_paths_is_ready_when_no_candidate_exists() { + let temp = unique_test_dir(); + + let report = inspect_legacy_paths([RadrootsLegacyPathCandidate::new( + "missing-state", + "missing state", + temp.join("missing-state"), + None, + "nothing to do", + )]); + + assert_eq!(report.state, "ready"); + assert!(report.detected_legacy_paths.is_empty()); + std::fs::remove_dir_all(temp).expect("remove temp test dir"); + } +} diff --git a/crates/runtime-paths/src/namespace.rs b/crates/runtime_paths/src/namespace.rs diff --git a/crates/runtime-paths/src/platform.rs b/crates/runtime_paths/src/platform.rs diff --git a/crates/runtime-paths/src/roots.rs b/crates/runtime_paths/src/roots.rs diff --git a/crates/secret-vault/Cargo.toml b/crates/secret-vault/Cargo.toml @@ -1,23 +0,0 @@ -[package] -name = "radroots-secret-vault" -version = "0.1.0-alpha.1" -edition.workspace = true -authors = [ - "Radroots Authors", -] -rust-version.workspace = true -license.workspace = true -description = "canonical secret backend taxonomy and fail-closed selection policy for radroots runtimes" -repository.workspace = true -homepage.workspace = true -documentation = "https://docs.rs/radroots-secret-vault" -readme = "README.md" - -[features] -default = [] -std = [] -memory-vault = ["std"] -os-keyring = ["std", "dep:keyring"] - -[dependencies] -keyring = { workspace = true, optional = true } diff --git a/crates/secret-vault/README.md b/crates/secret-vault/README.md @@ -1,3 +0,0 @@ -# radroots-secret-vault - -Canonical secret backend taxonomy and fail-closed backend-selection policy for Rad Roots runtimes. diff --git a/crates/secret_vault/Cargo.toml b/crates/secret_vault/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "radroots_secret_vault" +version = "0.1.0-alpha.1" +edition.workspace = true +authors = [ + "Radroots Authors", +] +rust-version.workspace = true +license.workspace = true +description = "canonical secret backend taxonomy and fail-closed selection policy for radroots runtimes" +repository.workspace = true +homepage.workspace = true +documentation = "https://docs.rs/radroots_secret_vault" +readme = "README.md" + +[features] +default = [] +std = [] +memory-vault = ["std"] +os-keyring = ["std", "dep:keyring"] + +[dependencies] +keyring = { workspace = true, optional = true } diff --git a/crates/secret_vault/README.md b/crates/secret_vault/README.md @@ -0,0 +1,3 @@ +# radroots_secret_vault + +Canonical secret backend taxonomy and fail-closed backend-selection policy for Rad Roots runtimes. diff --git a/crates/secret-vault/src/backend.rs b/crates/secret_vault/src/backend.rs diff --git a/crates/secret-vault/src/error.rs b/crates/secret_vault/src/error.rs diff --git a/crates/secret-vault/src/lib.rs b/crates/secret_vault/src/lib.rs diff --git a/crates/secret-vault/src/policy.rs b/crates/secret_vault/src/policy.rs diff --git a/crates/secret-vault/src/selection.rs b/crates/secret_vault/src/selection.rs diff --git a/crates/secret-vault/src/vault.rs b/crates/secret_vault/src/vault.rs diff --git a/crates/secret-vault/src/wrap.rs b/crates/secret_vault/src/wrap.rs diff --git a/crates/simplex-agent-proto/Cargo.toml b/crates/simplex-agent-proto/Cargo.toml @@ -1,22 +0,0 @@ -[package] -name = "radroots-simplex-agent-proto" -version = "0.1.0-alpha.1" -edition.workspace = true -authors = [ - "Radroots Authors", -] -rust-version.workspace = true -license.workspace = true -description = "simplex agent protocol envelopes and queue lifecycle types for the radroots sdk" -repository.workspace = true -homepage.workspace = true -documentation = "https://docs.rs/radroots-simplex-agent-proto" -readme.workspace = true - -[features] -default = ["std"] -std = ["radroots-simplex-smp-crypto/std", "radroots-simplex-smp-proto/std"] - -[dependencies] -radroots-simplex-smp-crypto = { workspace = true, default-features = false } -radroots-simplex-smp-proto = { workspace = true, default-features = false } diff --git a/crates/simplex-agent-runtime/Cargo.toml b/crates/simplex-agent-runtime/Cargo.toml @@ -1,37 +0,0 @@ -[package] -name = "radroots-simplex-agent-runtime" -version = "0.1.0-alpha.1" -edition.workspace = true -authors = [ - "Radroots Authors", -] -rust-version.workspace = true -license.workspace = true -description = "simplex agent runtime coordination for the radroots sdk" -repository.workspace = true -homepage.workspace = true -documentation = "https://docs.rs/radroots-simplex-agent-runtime" -readme.workspace = true - -[features] -default = ["std"] -std = [ - "radroots-simplex-agent-proto/std", - "radroots-simplex-agent-store/std", - "radroots-simplex-smp-crypto/std", - "radroots-simplex-smp-proto/std", - "radroots-simplex-smp-transport/std", - "sha2/std", -] - -[dependencies] -base64 = { version = "0.22", default-features = false, features = ["alloc"] } -radroots-simplex-agent-proto = { workspace = true, default-features = false } -radroots-simplex-agent-store = { workspace = true, default-features = false } -radroots-simplex-smp-crypto = { workspace = true, default-features = false } -radroots-simplex-smp-proto = { workspace = true, default-features = false } -radroots-simplex-smp-transport = { workspace = true, default-features = false } -sha2 = { workspace = true, default-features = false } - -[dev-dependencies] -tempfile = { workspace = true } diff --git a/crates/simplex-agent-store/Cargo.toml b/crates/simplex-agent-store/Cargo.toml @@ -1,36 +0,0 @@ -[package] -name = "radroots-simplex-agent-store" -version = "0.1.0-alpha.1" -edition.workspace = true -authors = [ - "Radroots Authors", -] -rust-version.workspace = true -license.workspace = true -description = "simplex agent persistence and queue state for the radroots sdk" -repository.workspace = true -homepage.workspace = true -documentation = "https://docs.rs/radroots-simplex-agent-store" -readme.workspace = true - -[features] -default = ["std"] -std = [ - "radroots-simplex-agent-proto/std", - "radroots-simplex-smp-crypto/std", - "radroots-simplex-smp-proto/std", - "serde/std", - "serde_json/std", - "sha2/std", -] - -[dependencies] -radroots-simplex-agent-proto = { workspace = true, default-features = false } -radroots-simplex-smp-crypto = { workspace = true, default-features = false } -radroots-simplex-smp-proto = { workspace = true, default-features = false } -serde = { workspace = true } -serde_json = { workspace = true } -sha2 = { workspace = true, default-features = false } - -[dev-dependencies] -tempfile = { workspace = true } diff --git a/crates/simplex-chat-proto/Cargo.toml b/crates/simplex-chat-proto/Cargo.toml @@ -1,24 +0,0 @@ -[package] -name = "radroots-simplex-chat-proto" -version = "0.1.0-alpha.1" -edition.workspace = true -authors = [ - "Radroots Authors", -] -rust-version.workspace = true -license.workspace = true -description = "simplex chat protocol primitives for the radroots sdk" -repository.workspace = true -homepage.workspace = true -documentation = "https://docs.rs/radroots-simplex-chat-proto" -readme.workspace = true - -[features] -default = ["std"] -std = ["serde/std", "serde_json/std", "dep:zstd"] - -[dependencies] -base64 = { version = "0.22", default-features = false, features = ["alloc"] } -serde = { workspace = true, default-features = false, features = ["alloc", "derive"] } -serde_json = { workspace = true, default-features = false, features = ["alloc"] } -zstd = { workspace = true, optional = true } diff --git a/crates/simplex-interop-tests/Cargo.toml b/crates/simplex-interop-tests/Cargo.toml @@ -1,35 +0,0 @@ -[package] -name = "radroots-simplex-interop-tests" -version = "0.1.0-alpha.1" -edition.workspace = true -authors = [ - "Radroots Authors", -] -rust-version.workspace = true -license.workspace = true -description = "synthetic fixture and opt-in local-service interop coverage for the radroots SimpleX stack" -repository.workspace = true -homepage.workspace = true -documentation = "https://docs.rs/radroots-simplex-interop-tests" -readme.workspace = true - -[features] -default = ["std"] -std = [ - "radroots-simplex-agent-proto/std", - "radroots-simplex-agent-runtime/std", - "radroots-simplex-chat-proto/std", - "radroots-simplex-smp-crypto/std", - "radroots-simplex-smp-proto/std", - "radroots-simplex-smp-transport/std", - "serde_json/std", -] - -[dependencies] -radroots-simplex-agent-proto = { workspace = true, default-features = false } -radroots-simplex-agent-runtime = { workspace = true, default-features = false } -radroots-simplex-chat-proto = { workspace = true, default-features = false } -radroots-simplex-smp-crypto = { workspace = true, default-features = false } -radroots-simplex-smp-proto = { workspace = true, default-features = false } -radroots-simplex-smp-transport = { workspace = true, default-features = false } -serde_json = { workspace = true, default-features = false, features = ["alloc"] } diff --git a/crates/simplex-interop-tests/src/lib.rs b/crates/simplex-interop-tests/src/lib.rs @@ -1,192 +0,0 @@ -#![cfg_attr(not(feature = "std"), no_std)] -#![forbid(unsafe_code)] -#![doc = r#" -`radroots-simplex-interop-tests` owns the synthetic fixture policy for the rr-rs -SimpleX stack. - -Rules: -- committed fixtures must use the `rr-synth/*` namespace. -- committed server hosts must stay in obviously synthetic domains such as - `.invalid`, `.example`, or `.test`. -- committed tests must not copy or derive realistic queue URIs, certificates, - ciphertext, or traffic from `refs/*` or external captures. -- black-box local upstream checks are opt-in through environment variables and - are never required for the default workspace verify lane. -"#] - -extern crate alloc; - -pub mod fixtures; -pub mod policy; - -#[cfg(test)] -mod tests { - use crate::fixtures::{ - synthetic_chat_messages, synthetic_connection_id, synthetic_fixture_id, - synthetic_invitation_queue, synthetic_reply_queue, - }; - use crate::policy::{RadrootsSimplexInteropFixturePolicy, RadrootsSimplexInteropLocalUpstream}; - use radroots_simplex_agent_proto::prelude::{ - RadrootsSimplexAgentDecryptedMessage, RadrootsSimplexAgentEncryptedPayload, - RadrootsSimplexAgentEnvelope, RadrootsSimplexAgentMessage, - RadrootsSimplexAgentMessageFrame, RadrootsSimplexAgentMessageHeader, - decode_agent_message_frame, decode_decrypted_message, decode_envelope, - encode_agent_message_frame, encode_decrypted_message, encode_envelope, - }; - use radroots_simplex_agent_runtime::prelude::{ - RadrootsSimplexAgentRuntime, RadrootsSimplexAgentRuntimeBuilder, - RadrootsSimplexAgentRuntimeEvent, - }; - use radroots_simplex_chat_proto::prelude::{decode_messages, encode_compressed_batch}; - use radroots_simplex_smp_crypto::prelude::{ - RadrootsSimplexSmpQueueAuthorizationMaterial, RadrootsSimplexSmpQueueAuthorizationScope, - }; - use radroots_simplex_smp_proto::prelude::{ - RADROOTS_SIMPLEX_SMP_CURRENT_TRANSPORT_VERSION, RadrootsSimplexSmpCommand, - RadrootsSimplexSmpCommandTransmission, RadrootsSimplexSmpCorrelationId, - RadrootsSimplexSmpMessageFlags, RadrootsSimplexSmpSendCommand, - }; - use radroots_simplex_smp_transport::prelude::RadrootsSimplexSmpTransportBlock; - - #[test] - fn synthetic_policy_accepts_only_rr_owned_fixtures() { - let policy = RadrootsSimplexInteropFixturePolicy::default(); - policy.assert_fixture_id(synthetic_fixture_id()).unwrap(); - policy - .assert_queue_uri(&synthetic_invitation_queue()) - .unwrap(); - policy.assert_queue_uri(&synthetic_reply_queue()).unwrap(); - - let error = policy.assert_fixture_id("copied-from-refs"); - assert!(error.is_err()); - } - - #[test] - fn synthetic_stack_roundtrip_exercises_smp_agent_and_chat_layers() { - let correlation_id = RadrootsSimplexSmpCorrelationId::new([7_u8; 24]); - let send_command = RadrootsSimplexSmpCommand::Send(RadrootsSimplexSmpSendCommand { - flags: RadrootsSimplexSmpMessageFlags::notifications_enabled(), - message_body: b"rr-synth-body".to_vec(), - }); - let transmission = RadrootsSimplexSmpCommandTransmission { - authorization: b"rr-synth-auth".to_vec(), - correlation_id: Some(correlation_id), - entity_id: b"rr-synth-queue".to_vec(), - command: send_command.clone(), - }; - let block = RadrootsSimplexSmpTransportBlock::from_current_command_transmissions(&[ - transmission.clone(), - ]) - .unwrap(); - let decoded = RadrootsSimplexSmpTransportBlock::decode(&block.encode().unwrap()) - .unwrap() - .decode_command_transmissions(RADROOTS_SIMPLEX_SMP_CURRENT_TRANSPORT_VERSION) - .unwrap(); - assert_eq!(decoded, vec![transmission]); - - let scope = RadrootsSimplexSmpQueueAuthorizationScope::new( - b"rr-synth-session".to_vec(), - correlation_id, - b"rr-synth-queue".to_vec(), - ) - .unwrap(); - let auth = RadrootsSimplexSmpQueueAuthorizationMaterial::for_command( - &scope, - &send_command, - RADROOTS_SIMPLEX_SMP_CURRENT_TRANSPORT_VERSION, - b"rr-synth-queue-key".to_vec(), - b"rr-synth-server-key".to_vec(), - ) - .unwrap(); - assert_eq!(auth.nonce, [7_u8; 24]); - - let chat_messages = synthetic_chat_messages(); - let compressed_chat = encode_compressed_batch(&chat_messages).unwrap(); - let decoded_chat = decode_messages(&compressed_chat).unwrap(); - assert_eq!(decoded_chat, chat_messages); - - let frame = RadrootsSimplexAgentMessageFrame { - header: RadrootsSimplexAgentMessageHeader { - message_id: 1, - previous_message_hash: synthetic_connection_id().as_bytes().to_vec(), - }, - message: RadrootsSimplexAgentMessage::UserMessage(compressed_chat.clone()), - padding: Vec::new(), - }; - let encoded_frame = encode_agent_message_frame(&frame).unwrap(); - let decoded_frame = decode_agent_message_frame(&encoded_frame).unwrap(); - assert_eq!(decoded_frame.header, frame.header); - assert_eq!(decoded_frame.message, frame.message); - - let decrypted = RadrootsSimplexAgentDecryptedMessage::Message(frame.clone()); - let encoded_decrypted = encode_decrypted_message(&decrypted).unwrap(); - let envelope = - RadrootsSimplexAgentEnvelope::Message(RadrootsSimplexAgentEncryptedPayload { - ratchet_header: None, - ciphertext: encoded_decrypted.clone(), - }); - let decoded_envelope = decode_envelope(&encode_envelope(&envelope).unwrap()).unwrap(); - let RadrootsSimplexAgentEnvelope::Message(payload) = decoded_envelope else { - panic!("expected message envelope"); - }; - let decoded_decrypted = decode_decrypted_message(&payload.ciphertext).unwrap(); - let RadrootsSimplexAgentDecryptedMessage::Message(decoded_frame_from_envelope) = - decoded_decrypted - else { - panic!("expected message frame"); - }; - let RadrootsSimplexAgentMessage::UserMessage(encoded_chat_again) = - decoded_frame_from_envelope.message - else { - panic!("expected user message"); - }; - assert_eq!(decode_messages(&encoded_chat_again).unwrap(), chat_messages); - } - - #[test] - fn synthetic_runtime_flow_stays_fixture_owned() { - let mut runtime: RadrootsSimplexAgentRuntime = - RadrootsSimplexAgentRuntimeBuilder::new().build().unwrap(); - let created = runtime - .create_connection( - synthetic_invitation_queue(), - b"rr-synth-e2e".to_vec(), - false, - 10, - ) - .unwrap(); - let events = runtime.drain_events(8); - let invitation = events - .into_iter() - .find_map(|event| match event { - RadrootsSimplexAgentRuntimeEvent::InvitationReady { invitation, .. } => { - Some(invitation) - } - _ => None, - }) - .expect("invitation event"); - - let joined = runtime - .join_connection(invitation, synthetic_reply_queue(), 20) - .unwrap(); - runtime - .allow_connection(&joined, b"rr-synth-info".to_vec(), 30) - .unwrap(); - let message_id = runtime - .send_message(&joined, b"rr-synth-chat".to_vec(), 40) - .unwrap(); - assert_eq!(message_id, 1); - runtime.reconnect_connection(&joined, 50).unwrap(); - assert!(!runtime.retry_pending(50 + 5_000, 64).is_empty()); - assert!(created.starts_with("conn-")); - } - - #[cfg(feature = "std")] - #[test] - fn local_upstream_contract_is_opt_in() { - let Some(target) = RadrootsSimplexInteropLocalUpstream::from_env() else { - return; - }; - target.assert_reachable().unwrap(); - } -} diff --git a/crates/simplex-smp-crypto/Cargo.toml b/crates/simplex-smp-crypto/Cargo.toml @@ -1,33 +0,0 @@ -[package] -name = "radroots-simplex-smp-crypto" -version = "0.1.0-alpha.1" -edition.workspace = true -authors = [ - "Radroots Authors", -] -rust-version.workspace = true -license.workspace = true -description = "simplex messaging queue authorization and ratchet state for the radroots sdk" -repository.workspace = true -homepage.workspace = true -documentation = "https://docs.rs/radroots-simplex-smp-crypto" -readme.workspace = true - -[features] -default = ["std"] -std = [ - "ed25519-dalek/std", - "getrandom/std", - "radroots-simplex-smp-proto/std", - "sha2/std", - "xsalsa20poly1305/std", -] - -[dependencies] -ed25519-dalek = { workspace = true, default-features = false, features = ["alloc"] } -getrandom = { workspace = true, default-features = false } -hkdf = { workspace = true, default-features = false } -radroots-simplex-smp-proto = { workspace = true, default-features = false } -sha2 = { workspace = true, default-features = false } -xsalsa20poly1305 = { workspace = true, default-features = false } -x25519-dalek = { workspace = true, default-features = false, features = ["static_secrets"] } diff --git a/crates/simplex-smp-proto/Cargo.toml b/crates/simplex-smp-proto/Cargo.toml @@ -1,21 +0,0 @@ -[package] -name = "radroots-simplex-smp-proto" -version = "0.1.0-alpha.1" -edition.workspace = true -authors = [ - "Radroots Authors", -] -rust-version.workspace = true -license.workspace = true -description = "simplex messaging protocol primitives for the radroots sdk" -repository.workspace = true -homepage.workspace = true -documentation = "https://docs.rs/radroots-simplex-smp-proto" -readme.workspace = true - -[features] -default = ["std"] -std = [] - -[dependencies] -base64 = { version = "0.22", default-features = false, features = ["alloc"] } diff --git a/crates/simplex-smp-transport/Cargo.toml b/crates/simplex-smp-transport/Cargo.toml @@ -1,36 +0,0 @@ -[package] -name = "radroots-simplex-smp-transport" -version = "0.1.0-alpha.1" -edition.workspace = true -authors = [ - "Radroots Authors", -] -rust-version.workspace = true -license.workspace = true -description = "simplex messaging transport framing and handshake policy for the radroots sdk" -repository.workspace = true -homepage.workspace = true -documentation = "https://docs.rs/radroots-simplex-smp-transport" -readme.workspace = true - -[features] -default = ["std"] -std = [ - "base64/std", - "radroots-simplex-smp-crypto/std", - "radroots-simplex-smp-proto/std", - "rustls/std", - "sha2/std", - "x509-parser/verify", -] - -[dependencies] -base64 = { workspace = true } -radroots-simplex-smp-crypto = { workspace = true, default-features = false } -radroots-simplex-smp-proto = { workspace = true, default-features = false } -rustls = { workspace = true, default-features = false } -sha2 = { workspace = true, default-features = false } -x509-parser = { workspace = true, default-features = false } - -[dev-dependencies] -rcgen = { version = "0.14" } diff --git a/crates/simplex_agent_proto/Cargo.toml b/crates/simplex_agent_proto/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "radroots_simplex_agent_proto" +publish = false +version = "0.1.0-alpha.1" +edition.workspace = true +authors = [ + "Radroots Authors", +] +rust-version.workspace = true +license.workspace = true +description = "simplex agent protocol envelopes and queue lifecycle types for the radroots sdk" +repository.workspace = true +homepage.workspace = true +documentation = "https://docs.rs/radroots_simplex_agent_proto" +readme.workspace = true + +[features] +default = ["std"] +std = ["radroots_simplex_smp_crypto/std", "radroots_simplex_smp_proto/std"] + +[dependencies] +radroots_simplex_smp_crypto = { workspace = true, default-features = false } +radroots_simplex_smp_proto = { workspace = true, default-features = false } diff --git a/crates/simplex-agent-proto/src/codec.rs b/crates/simplex_agent_proto/src/codec.rs diff --git a/crates/simplex-agent-proto/src/error.rs b/crates/simplex_agent_proto/src/error.rs diff --git a/crates/simplex-agent-proto/src/lib.rs b/crates/simplex_agent_proto/src/lib.rs diff --git a/crates/simplex-agent-proto/src/model.rs b/crates/simplex_agent_proto/src/model.rs diff --git a/crates/simplex_agent_runtime/Cargo.toml b/crates/simplex_agent_runtime/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "radroots_simplex_agent_runtime" +publish = false +version = "0.1.0-alpha.1" +edition.workspace = true +authors = [ + "Radroots Authors", +] +rust-version.workspace = true +license.workspace = true +description = "simplex agent runtime coordination for the radroots sdk" +repository.workspace = true +homepage.workspace = true +documentation = "https://docs.rs/radroots_simplex_agent_runtime" +readme.workspace = true + +[features] +default = ["std"] +std = [ + "radroots_simplex_agent_proto/std", + "radroots_simplex_agent_store/std", + "radroots_simplex_smp_crypto/std", + "radroots_simplex_smp_proto/std", + "radroots_simplex_smp_transport/std", + "sha2/std", +] + +[dependencies] +base64 = { version = "0.22", default-features = false, features = ["alloc"] } +radroots_simplex_agent_proto = { workspace = true, default-features = false } +radroots_simplex_agent_store = { workspace = true, default-features = false } +radroots_simplex_smp_crypto = { workspace = true, default-features = false } +radroots_simplex_smp_proto = { workspace = true, default-features = false } +radroots_simplex_smp_transport = { workspace = true, default-features = false } +sha2 = { workspace = true, default-features = false } + +[dev-dependencies] +tempfile = { workspace = true } diff --git a/crates/simplex-agent-runtime/src/error.rs b/crates/simplex_agent_runtime/src/error.rs diff --git a/crates/simplex-agent-runtime/src/lib.rs b/crates/simplex_agent_runtime/src/lib.rs diff --git a/crates/simplex-agent-runtime/src/runtime.rs b/crates/simplex_agent_runtime/src/runtime.rs diff --git a/crates/simplex-agent-runtime/src/types.rs b/crates/simplex_agent_runtime/src/types.rs diff --git a/crates/simplex_agent_store/Cargo.toml b/crates/simplex_agent_store/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "radroots_simplex_agent_store" +publish = false +version = "0.1.0-alpha.1" +edition.workspace = true +authors = [ + "Radroots Authors", +] +rust-version.workspace = true +license.workspace = true +description = "simplex agent persistence and queue state for the radroots sdk" +repository.workspace = true +homepage.workspace = true +documentation = "https://docs.rs/radroots_simplex_agent_store" +readme.workspace = true + +[features] +default = ["std"] +std = [ + "radroots_simplex_agent_proto/std", + "radroots_simplex_smp_crypto/std", + "radroots_simplex_smp_proto/std", + "serde/std", + "serde_json/std", + "sha2/std", +] + +[dependencies] +radroots_simplex_agent_proto = { workspace = true, default-features = false } +radroots_simplex_smp_crypto = { workspace = true, default-features = false } +radroots_simplex_smp_proto = { workspace = true, default-features = false } +serde = { workspace = true } +serde_json = { workspace = true } +sha2 = { workspace = true, default-features = false } + +[dev-dependencies] +tempfile = { workspace = true } diff --git a/crates/simplex-agent-store/src/error.rs b/crates/simplex_agent_store/src/error.rs diff --git a/crates/simplex-agent-store/src/lib.rs b/crates/simplex_agent_store/src/lib.rs diff --git a/crates/simplex-agent-store/src/store.rs b/crates/simplex_agent_store/src/store.rs diff --git a/crates/simplex_chat_proto/Cargo.toml b/crates/simplex_chat_proto/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "radroots_simplex_chat_proto" +version = "0.1.0-alpha.1" +edition.workspace = true +authors = [ + "Radroots Authors", +] +rust-version.workspace = true +license.workspace = true +description = "simplex chat protocol primitives for the radroots sdk" +repository.workspace = true +homepage.workspace = true +documentation = "https://docs.rs/radroots_simplex_chat_proto" +readme.workspace = true + +[features] +default = ["std"] +std = ["serde/std", "serde_json/std", "dep:zstd"] + +[dependencies] +base64 = { version = "0.22", default-features = false, features = ["alloc"] } +serde = { workspace = true, default-features = false, features = ["alloc", "derive"] } +serde_json = { workspace = true, default-features = false, features = ["alloc"] } +zstd = { workspace = true, optional = true } diff --git a/crates/simplex-chat-proto/src/codec.rs b/crates/simplex_chat_proto/src/codec.rs diff --git a/crates/simplex-chat-proto/src/error.rs b/crates/simplex_chat_proto/src/error.rs diff --git a/crates/simplex-chat-proto/src/lib.rs b/crates/simplex_chat_proto/src/lib.rs diff --git a/crates/simplex-chat-proto/src/model.rs b/crates/simplex_chat_proto/src/model.rs diff --git a/crates/simplex-chat-proto/src/version.rs b/crates/simplex_chat_proto/src/version.rs diff --git a/crates/simplex-chat-proto/tests/chat_proto.rs b/crates/simplex_chat_proto/tests/chat_proto.rs diff --git a/crates/simplex_interop_tests/Cargo.toml b/crates/simplex_interop_tests/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "radroots_simplex_interop_tests" +publish = false +version = "0.1.0-alpha.1" +edition.workspace = true +authors = [ + "Radroots Authors", +] +rust-version.workspace = true +license.workspace = true +description = "synthetic fixture and opt-in local-service interop coverage for the radroots SimpleX stack" +repository.workspace = true +homepage.workspace = true +documentation = "https://docs.rs/radroots_simplex_interop_tests" +readme.workspace = true + +[features] +default = ["std"] +std = [ + "radroots_simplex_agent_proto/std", + "radroots_simplex_agent_runtime/std", + "radroots_simplex_chat_proto/std", + "radroots_simplex_smp_crypto/std", + "radroots_simplex_smp_proto/std", + "radroots_simplex_smp_transport/std", + "serde_json/std", +] + +[dependencies] +radroots_simplex_agent_proto = { workspace = true, default-features = false } +radroots_simplex_agent_runtime = { workspace = true, default-features = false } +radroots_simplex_chat_proto = { workspace = true, default-features = false } +radroots_simplex_smp_crypto = { workspace = true, default-features = false } +radroots_simplex_smp_proto = { workspace = true, default-features = false } +radroots_simplex_smp_transport = { workspace = true, default-features = false } +serde_json = { workspace = true, default-features = false, features = ["alloc"] } diff --git a/crates/simplex-interop-tests/src/fixtures.rs b/crates/simplex_interop_tests/src/fixtures.rs diff --git a/crates/simplex_interop_tests/src/lib.rs b/crates/simplex_interop_tests/src/lib.rs @@ -0,0 +1,312 @@ +#![cfg_attr(not(feature = "std"), no_std)] +#![forbid(unsafe_code)] +#![doc = r#" +`radroots_simplex_interop_tests` owns the synthetic fixture policy for the rr-rs +SimpleX stack. + +Rules: +- committed fixtures must use the `rr-synth/*` namespace. +- committed server hosts must stay in obviously synthetic domains such as + `.invalid`, `.example`, or `.test`. +- committed tests must not copy or derive realistic queue URIs, certificates, + ciphertext, or traffic from `refs/*` or external captures. +- black-box local upstream checks are opt-in through environment variables and + are never required for the default workspace verify lane. +"#] + +extern crate alloc; + +pub mod fixtures; +pub mod policy; + +#[cfg(test)] +mod tests { + use crate::fixtures::{ + synthetic_chat_messages, synthetic_connection_id, synthetic_fixture_id, + synthetic_invitation_queue, synthetic_reply_queue, + }; + use crate::policy::{RadrootsSimplexInteropFixturePolicy, RadrootsSimplexInteropLocalUpstream}; + use alloc::collections::VecDeque; + use radroots_simplex_agent_proto::prelude::{ + RadrootsSimplexAgentDecryptedMessage, RadrootsSimplexAgentEncryptedPayload, + RadrootsSimplexAgentEnvelope, RadrootsSimplexAgentMessage, + RadrootsSimplexAgentMessageFrame, RadrootsSimplexAgentMessageHeader, + decode_agent_message_frame, decode_decrypted_message, decode_envelope, + encode_agent_message_frame, encode_decrypted_message, encode_envelope, + }; + use radroots_simplex_agent_runtime::prelude::{ + RadrootsSimplexAgentRuntime, RadrootsSimplexAgentRuntimeBuilder, + RadrootsSimplexAgentRuntimeEvent, + }; + use radroots_simplex_chat_proto::prelude::{decode_messages, encode_compressed_batch}; + use radroots_simplex_smp_crypto::prelude::{ + RadrootsSimplexSmpCommandAuthorization, RadrootsSimplexSmpQueueAuthorizationMaterial, + RadrootsSimplexSmpQueueAuthorizationScope, RadrootsSimplexSmpX25519Keypair, + }; + use radroots_simplex_smp_proto::prelude::{ + RADROOTS_SIMPLEX_SMP_CURRENT_TRANSPORT_VERSION, RadrootsSimplexSmpBrokerMessage, + RadrootsSimplexSmpBrokerTransmission, RadrootsSimplexSmpCommand, + RadrootsSimplexSmpCommandTransmission, RadrootsSimplexSmpCorrelationId, + RadrootsSimplexSmpMessageFlags, RadrootsSimplexSmpQueueIdsResponse, + RadrootsSimplexSmpQueueMode, RadrootsSimplexSmpSendCommand, + }; + use radroots_simplex_smp_transport::prelude::{ + RadrootsSimplexSmpCommandTransport, RadrootsSimplexSmpTransportBlock, + RadrootsSimplexSmpTransportRequest, RadrootsSimplexSmpTransportResponse, + }; + + fn ids_response( + recipient_id: &[u8], + sender_id: &[u8], + seed: &[u8], + ) -> RadrootsSimplexSmpBrokerMessage { + RadrootsSimplexSmpBrokerMessage::Ids(RadrootsSimplexSmpQueueIdsResponse { + recipient_id: recipient_id.to_vec(), + sender_id: sender_id.to_vec(), + server_dh_public_key: RadrootsSimplexSmpX25519Keypair::from_seed(seed).public_key, + queue_mode: Some(RadrootsSimplexSmpQueueMode::Messaging), + link_id: None, + service_id: None, + server_notification_credentials: None, + }) + } + + #[derive(Default)] + struct ScriptedTransport { + responses: VecDeque<RadrootsSimplexSmpBrokerMessage>, + } + + impl ScriptedTransport { + fn with_responses(responses: Vec<RadrootsSimplexSmpBrokerMessage>) -> Self { + Self { + responses: responses.into(), + } + } + } + + impl RadrootsSimplexSmpCommandTransport for ScriptedTransport { + type Error = String; + + fn execute( + &mut self, + request: RadrootsSimplexSmpTransportRequest, + ) -> Result<RadrootsSimplexSmpTransportResponse, Self::Error> { + let correlation_id = request + .correlation_id + .ok_or_else(|| "missing scripted transport correlation id".to_owned())?; + let scope = RadrootsSimplexSmpQueueAuthorizationScope::new( + b"scripted-session".to_vec(), + correlation_id, + request.entity_id.clone(), + ) + .map_err(|error| error.to_string())?; + let material = RadrootsSimplexSmpQueueAuthorizationMaterial::for_command( + &scope, + &request.command, + request.transport_version, + &request.authorization, + ) + .map_err(|error| error.to_string())?; + let transmission = RadrootsSimplexSmpCommandTransmission { + authorization: material.authorization, + correlation_id: Some(correlation_id), + entity_id: request.entity_id.clone(), + command: request.command.clone(), + }; + let block = RadrootsSimplexSmpTransportBlock::from_current_command_transmissions(&[ + transmission.clone(), + ]) + .map_err(|error| error.to_string())?; + let encoded = block.encode().map_err(|error| error.to_string())?; + let decoded = RadrootsSimplexSmpTransportBlock::decode(&encoded) + .map_err(|error| error.to_string())?; + let decoded_transmissions = decoded + .decode_command_transmissions(request.transport_version) + .map_err(|error| error.to_string())?; + assert_eq!(decoded_transmissions, vec![transmission.clone()]); + + let response_message = self + .responses + .pop_front() + .ok_or_else(|| "missing scripted transport response".to_owned())?; + let response_transmission = RadrootsSimplexSmpBrokerTransmission { + authorization: Vec::new(), + correlation_id: Some(correlation_id), + entity_id: request.entity_id.clone(), + message: response_message, + }; + let response_block = RadrootsSimplexSmpTransportBlock::from_broker_transmissions( + &[response_transmission.clone()], + request.transport_version, + ) + .map_err(|error| error.to_string())?; + let response_encoded = response_block.encode().map_err(|error| error.to_string())?; + Ok(RadrootsSimplexSmpTransportResponse { + server: request.server, + transport_version: request.transport_version, + transmission: response_transmission, + transport_hash: response_encoded, + }) + } + } + + #[test] + fn synthetic_policy_accepts_only_rr_owned_fixtures() { + let policy = RadrootsSimplexInteropFixturePolicy::default(); + policy.assert_fixture_id(synthetic_fixture_id()).unwrap(); + policy + .assert_queue_uri(&synthetic_invitation_queue()) + .unwrap(); + policy.assert_queue_uri(&synthetic_reply_queue()).unwrap(); + + let error = policy.assert_fixture_id("copied-from-refs"); + assert!(error.is_err()); + } + + #[test] + fn synthetic_stack_roundtrip_exercises_smp_agent_and_chat_layers() { + let correlation_id = RadrootsSimplexSmpCorrelationId::new([7_u8; 24]); + let send_command = RadrootsSimplexSmpCommand::Send(RadrootsSimplexSmpSendCommand { + flags: RadrootsSimplexSmpMessageFlags::notifications_enabled(), + message_body: b"rr-synth-body".to_vec(), + }); + let transmission = RadrootsSimplexSmpCommandTransmission { + authorization: b"rr-synth-auth".to_vec(), + correlation_id: Some(correlation_id), + entity_id: b"rr-synth-queue".to_vec(), + command: send_command.clone(), + }; + let block = RadrootsSimplexSmpTransportBlock::from_current_command_transmissions(&[ + transmission.clone(), + ]) + .unwrap(); + let decoded = RadrootsSimplexSmpTransportBlock::decode(&block.encode().unwrap()) + .unwrap() + .decode_command_transmissions(RADROOTS_SIMPLEX_SMP_CURRENT_TRANSPORT_VERSION) + .unwrap(); + assert_eq!(decoded, vec![transmission]); + + let scope = RadrootsSimplexSmpQueueAuthorizationScope::new( + b"rr-synth-session".to_vec(), + correlation_id, + b"rr-synth-queue".to_vec(), + ) + .unwrap(); + let auth = RadrootsSimplexSmpQueueAuthorizationMaterial::for_command( + &scope, + &send_command, + RADROOTS_SIMPLEX_SMP_CURRENT_TRANSPORT_VERSION, + &RadrootsSimplexSmpCommandAuthorization::None, + ) + .unwrap(); + assert_eq!(auth.authorized_body[0], b"rr-synth-session".len() as u8); + assert!(auth.authorization.is_empty()); + + let chat_messages = synthetic_chat_messages(); + let compressed_chat = encode_compressed_batch(&chat_messages).unwrap(); + let decoded_chat = decode_messages(&compressed_chat).unwrap(); + assert_eq!(decoded_chat, chat_messages); + + let frame = RadrootsSimplexAgentMessageFrame { + header: RadrootsSimplexAgentMessageHeader { + message_id: 1, + previous_message_hash: synthetic_connection_id().as_bytes().to_vec(), + }, + message: RadrootsSimplexAgentMessage::UserMessage(compressed_chat.clone()), + padding: Vec::new(), + }; + let encoded_frame = encode_agent_message_frame(&frame).unwrap(); + let decoded_frame = decode_agent_message_frame(&encoded_frame).unwrap(); + assert_eq!(decoded_frame.header, frame.header); + assert_eq!(decoded_frame.message, frame.message); + + let decrypted = RadrootsSimplexAgentDecryptedMessage::Message(frame.clone()); + let encoded_decrypted = encode_decrypted_message(&decrypted).unwrap(); + let envelope = + RadrootsSimplexAgentEnvelope::Message(RadrootsSimplexAgentEncryptedPayload { + ratchet_header: None, + ciphertext: encoded_decrypted.clone(), + }); + let decoded_envelope = decode_envelope(&encode_envelope(&envelope).unwrap()).unwrap(); + let RadrootsSimplexAgentEnvelope::Message(payload) = decoded_envelope else { + panic!("expected message envelope"); + }; + let decoded_decrypted = decode_decrypted_message(&payload.ciphertext).unwrap(); + let RadrootsSimplexAgentDecryptedMessage::Message(decoded_frame_from_envelope) = + decoded_decrypted + else { + panic!("expected message frame"); + }; + let RadrootsSimplexAgentMessage::UserMessage(encoded_chat_again) = + decoded_frame_from_envelope.message + else { + panic!("expected user message"); + }; + assert_eq!(decode_messages(&encoded_chat_again).unwrap(), chat_messages); + } + + #[test] + fn synthetic_runtime_flow_stays_fixture_owned() { + let mut runtime: RadrootsSimplexAgentRuntime = + RadrootsSimplexAgentRuntimeBuilder::new().build().unwrap(); + let created = runtime + .create_connection( + synthetic_invitation_queue(), + b"rr-synth-e2e".to_vec(), + false, + 10, + ) + .unwrap(); + let mut invitation_transport = ScriptedTransport::with_responses(vec![ids_response( + b"recipient", + b"sender", + b"server-dh", + )]); + runtime + .execute_ready_commands(&mut invitation_transport, 20, 16) + .unwrap(); + let events = runtime.drain_events(8); + let invitation = events + .into_iter() + .find_map(|event| match event { + RadrootsSimplexAgentRuntimeEvent::InvitationReady { invitation, .. } => { + Some(invitation) + } + _ => None, + }) + .expect("invitation event"); + + let joined = runtime + .join_connection(invitation, synthetic_reply_queue(), 30) + .unwrap(); + let mut join_transport = ScriptedTransport::with_responses(vec![ + RadrootsSimplexSmpBrokerMessage::Ok, + ids_response(b"recipient-2", b"sender-2", b"server-dh-2"), + RadrootsSimplexSmpBrokerMessage::Ok, + RadrootsSimplexSmpBrokerMessage::Ok, + RadrootsSimplexSmpBrokerMessage::Ok, + ]); + runtime + .execute_ready_commands(&mut join_transport, 40, 16) + .unwrap(); + runtime + .allow_connection(&joined, b"rr-synth-info".to_vec(), 50) + .unwrap(); + let message_id = runtime + .send_message(&joined, b"rr-synth-chat".to_vec(), 60) + .unwrap(); + assert_eq!(message_id, 1); + runtime.reconnect_connection(&joined, 70).unwrap(); + assert!(!runtime.retry_pending(70 + 5_000, 64).is_empty()); + assert!(created.starts_with("conn-")); + } + + #[cfg(feature = "std")] + #[test] + fn local_upstream_contract_is_opt_in() { + let Some(target) = RadrootsSimplexInteropLocalUpstream::from_env() else { + return; + }; + target.assert_reachable().unwrap(); + } +} diff --git a/crates/simplex-interop-tests/src/policy.rs b/crates/simplex_interop_tests/src/policy.rs diff --git a/crates/simplex_smp_crypto/Cargo.toml b/crates/simplex_smp_crypto/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "radroots_simplex_smp_crypto" +publish = false +version = "0.1.0-alpha.1" +edition.workspace = true +authors = [ + "Radroots Authors", +] +rust-version.workspace = true +license.workspace = true +description = "simplex messaging queue authorization and ratchet state for the radroots sdk" +repository.workspace = true +homepage.workspace = true +documentation = "https://docs.rs/radroots_simplex_smp_crypto" +readme.workspace = true + +[features] +default = ["std"] +std = [ + "ed25519-dalek/std", + "getrandom/std", + "radroots_simplex_smp_proto/std", + "sha2/std", + "xsalsa20poly1305/std", +] + +[dependencies] +ed25519-dalek = { workspace = true, default-features = false, features = ["alloc"] } +getrandom = { workspace = true, default-features = false } +hkdf = { workspace = true, default-features = false } +radroots_simplex_smp_proto = { workspace = true, default-features = false } +sha2 = { workspace = true, default-features = false } +xsalsa20poly1305 = { workspace = true, default-features = false } +x25519-dalek = { workspace = true, default-features = false, features = ["static_secrets"] } diff --git a/crates/simplex-smp-crypto/src/auth.rs b/crates/simplex_smp_crypto/src/auth.rs diff --git a/crates/simplex-smp-crypto/src/error.rs b/crates/simplex_smp_crypto/src/error.rs diff --git a/crates/simplex-smp-crypto/src/lib.rs b/crates/simplex_smp_crypto/src/lib.rs diff --git a/crates/simplex-smp-crypto/src/message.rs b/crates/simplex_smp_crypto/src/message.rs diff --git a/crates/simplex-smp-crypto/src/ratchet.rs b/crates/simplex_smp_crypto/src/ratchet.rs diff --git a/crates/simplex_smp_proto/Cargo.toml b/crates/simplex_smp_proto/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "radroots_simplex_smp_proto" +version = "0.1.0-alpha.1" +edition.workspace = true +authors = [ + "Radroots Authors", +] +rust-version.workspace = true +license.workspace = true +description = "simplex messaging protocol primitives for the radroots sdk" +repository.workspace = true +homepage.workspace = true +documentation = "https://docs.rs/radroots_simplex_smp_proto" +readme.workspace = true + +[features] +default = ["std"] +std = [] + +[dependencies] +base64 = { version = "0.22", default-features = false, features = ["alloc"] } diff --git a/crates/simplex-smp-proto/src/error.rs b/crates/simplex_smp_proto/src/error.rs diff --git a/crates/simplex-smp-proto/src/lib.rs b/crates/simplex_smp_proto/src/lib.rs diff --git a/crates/simplex-smp-proto/src/uri.rs b/crates/simplex_smp_proto/src/uri.rs diff --git a/crates/simplex-smp-proto/src/version.rs b/crates/simplex_smp_proto/src/version.rs diff --git a/crates/simplex-smp-proto/src/wire.rs b/crates/simplex_smp_proto/src/wire.rs diff --git a/crates/simplex_smp_transport/Cargo.toml b/crates/simplex_smp_transport/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "radroots_simplex_smp_transport" +publish = false +version = "0.1.0-alpha.1" +edition.workspace = true +authors = [ + "Radroots Authors", +] +rust-version.workspace = true +license.workspace = true +description = "simplex messaging transport framing and handshake policy for the radroots sdk" +repository.workspace = true +homepage.workspace = true +documentation = "https://docs.rs/radroots_simplex_smp_transport" +readme.workspace = true + +[features] +default = ["std"] +std = [ + "base64/std", + "radroots_simplex_smp_crypto/std", + "radroots_simplex_smp_proto/std", + "rustls/std", + "sha2/std", + "x509-parser/verify", +] + +[dependencies] +base64 = { workspace = true } +radroots_simplex_smp_crypto = { workspace = true, default-features = false } +radroots_simplex_smp_proto = { workspace = true, default-features = false } +rustls = { workspace = true, default-features = false } +sha2 = { workspace = true, default-features = false } +x509-parser = { workspace = true, default-features = false } + +[dev-dependencies] +rcgen = { version = "0.14" } diff --git a/crates/simplex-smp-transport/src/client.rs b/crates/simplex_smp_transport/src/client.rs diff --git a/crates/simplex-smp-transport/src/error.rs b/crates/simplex_smp_transport/src/error.rs diff --git a/crates/simplex-smp-transport/src/executor.rs b/crates/simplex_smp_transport/src/executor.rs diff --git a/crates/simplex-smp-transport/src/frame.rs b/crates/simplex_smp_transport/src/frame.rs diff --git a/crates/simplex-smp-transport/src/handshake.rs b/crates/simplex_smp_transport/src/handshake.rs diff --git a/crates/simplex-smp-transport/src/lib.rs b/crates/simplex_smp_transport/src/lib.rs diff --git a/crates/sql-core/Cargo.toml b/crates/sql-core/Cargo.toml @@ -1,39 +0,0 @@ -[package] -name = "radroots-sql-core" -version = "0.1.0-alpha.1" -edition.workspace = true -authors = [ - "Radroots Authors", -] -rust-version.workspace = true -license.workspace = true -description = "sql execution and migration primitives for radroots data layers" -repository.workspace = true -homepage.workspace = true -documentation = "https://docs.rs/radroots-sql-core" -readme.workspace = true - -[lib] -crate-type = ["rlib"] - -[features] -web = [] -bridge = [ - "web", - "dep:radroots-sql-wasm-bridge", - "dep:serde-wasm-bindgen", - "dep:wasm-bindgen", -] -native = ["dep:rusqlite"] -embedded = [] - -[dependencies] -serde_json = { workspace = true } -thiserror = { workspace = true } -radroots-sql-wasm-bridge = { workspace = true, optional = true } -wasm-bindgen = { workspace = true, optional = true } -serde-wasm-bindgen = { workspace = true, optional = true } -rusqlite = { workspace = true, features = ["bundled"], optional = true } -chrono = { workspace = true } -serde = { workspace = true } -uuid = { workspace = true } diff --git a/crates/sql-core/README.md b/crates/sql-core/README.md @@ -1,14 +0,0 @@ -# radroots-sql-core - -SQL execution and migration primitives for Rad Roots data layers. - -## Goals - -- define stable SQL executor and error interfaces across target environments -- keep migration execution behavior deterministic across runtime targets -- support feature-gated native, web bridge, and embedded execution surfaces -- provide reusable SQL primitives for higher-level Rad Roots crates - -## License - -Licensed under AGPL-3.0. See LICENSE. diff --git a/crates/sql-wasm-bridge/Cargo.toml b/crates/sql-wasm-bridge/Cargo.toml @@ -1,21 +0,0 @@ -[package] -name = "radroots-sql-wasm-bridge" -version = "0.1.0-alpha.1" -edition.workspace = true -authors = [ - "Radroots Authors", -] -rust-version.workspace = true -license.workspace = true -description = "wasm sql bridge primitives for radroots data layers" -repository.workspace = true -homepage.workspace = true -documentation = "https://docs.rs/radroots-sql-wasm-bridge" -readme.workspace = true - -[features] -default = [] - -[dependencies] -js-sys = { workspace = true } -wasm-bindgen = { workspace = true } diff --git a/crates/sql-wasm-bridge/README.md b/crates/sql-wasm-bridge/README.md @@ -1,14 +0,0 @@ -# radroots-sql-wasm-bridge - -Wasm SQL bridge primitives for Rad Roots data layers. - -## Goals - -- define stable wasm bridge interfaces for SQL execute, query, export, and transactions -- keep host interop behavior deterministic across wasm integrations -- support a narrow integration boundary for SQL runtime crates -- provide reusable wasm SQL bridge primitives for higher-level Rad Roots crates - -## License - -Licensed under AGPL-3.0. See LICENSE. diff --git a/crates/sql-wasm-core/Cargo.toml b/crates/sql-wasm-core/Cargo.toml @@ -1,39 +0,0 @@ -[package] -name = "radroots-sql-wasm-core" -version = "0.1.0-alpha.1" -edition.workspace = true -authors = [ - "Radroots Authors", -] -rust-version.workspace = true -license.workspace = true -description = "wasm sql runtime primitives for radroots data layers" -repository.workspace = true -homepage.workspace = true -documentation = "https://docs.rs/radroots-sql-wasm-core" -readme.workspace = true - -[lib] -crate-type = ["cdylib", "rlib"] - -[features] -default = ["bridge"] -bridge = ["dep:radroots-sql-wasm-bridge"] -embedded = ["dep:rusqlite", "radroots-sql-core/native"] - -[dependencies] -radroots-sql-core = { workspace = true } -radroots-sql-wasm-bridge = { workspace = true, optional = true } -rusqlite = { workspace = true, features = [ - "bundled", - "serialize", -], optional = true } -chrono = { workspace = true, features = ["serde"] } -serde = { workspace = true, features = ["derive"] } -serde_json = { workspace = true } -serde-wasm-bindgen = { workspace = true } -thiserror = { workspace = true } -wasm-bindgen = { workspace = true } -js-sys = { workspace = true } -ts-rs = { workspace = true } -uuid = { workspace = true, features = ["v4", "fast-rng", "js"] } diff --git a/crates/sql-wasm-core/README.md b/crates/sql-wasm-core/README.md @@ -1,14 +0,0 @@ -# radroots-sql-wasm-core - -Wasm SQL runtime primitives for Rad Roots data layers. - -## Goals - -- define stable wasm SQL runtime interfaces for execute, query, and export flows -- keep wasm SQL runtime behavior deterministic across supported environments -- support feature-gated bridge and embedded engine integration surfaces -- provide reusable wasm SQL runtime primitives for higher-level Rad Roots crates - -## License - -Licensed under AGPL-3.0. See LICENSE. diff --git a/crates/sql_core/Cargo.toml b/crates/sql_core/Cargo.toml @@ -0,0 +1,39 @@ +[package] +name = "radroots_sql_core" +version = "0.1.0-alpha.1" +edition.workspace = true +authors = [ + "Radroots Authors", +] +rust-version.workspace = true +license.workspace = true +description = "sql execution and migration primitives for radroots data layers" +repository.workspace = true +homepage.workspace = true +documentation = "https://docs.rs/radroots_sql_core" +readme.workspace = true + +[lib] +crate-type = ["rlib"] + +[features] +web = [] +bridge = [ + "web", + "dep:radroots_sql_wasm_bridge", + "dep:serde-wasm-bindgen", + "dep:wasm-bindgen", +] +native = ["dep:rusqlite"] +embedded = [] + +[dependencies] +serde_json = { workspace = true } +thiserror = { workspace = true } +radroots_sql_wasm_bridge = { workspace = true, optional = true } +wasm-bindgen = { workspace = true, optional = true } +serde-wasm-bindgen = { workspace = true, optional = true } +rusqlite = { workspace = true, features = ["bundled"], optional = true } +chrono = { workspace = true } +serde = { workspace = true } +uuid = { workspace = true } diff --git a/crates/sql_core/README.md b/crates/sql_core/README.md @@ -0,0 +1,14 @@ +# radroots_sql_core + +SQL execution and migration primitives for Rad Roots data layers. + +## Goals + +- define stable SQL executor and error interfaces across target environments +- keep migration execution behavior deterministic across runtime targets +- support feature-gated native, web bridge, and embedded execution surfaces +- provide reusable SQL primitives for higher-level Rad Roots crates + +## License + +Licensed under AGPL-3.0. See LICENSE. diff --git a/crates/sql-core/src/error.rs b/crates/sql_core/src/error.rs diff --git a/crates/sql-core/src/executor_embedded.rs b/crates/sql_core/src/executor_embedded.rs diff --git a/crates/sql-core/src/executor_sqlite.rs b/crates/sql_core/src/executor_sqlite.rs diff --git a/crates/sql-core/src/executor_wasm.rs b/crates/sql_core/src/executor_wasm.rs diff --git a/crates/sql-core/src/export_lock.rs b/crates/sql_core/src/export_lock.rs diff --git a/crates/sql-core/src/lib.rs b/crates/sql_core/src/lib.rs diff --git a/crates/sql-core/src/migrations.rs b/crates/sql_core/src/migrations.rs diff --git a/crates/sql-core/src/sqlite_util.rs b/crates/sql_core/src/sqlite_util.rs diff --git a/crates/sql-core/src/utils.rs b/crates/sql_core/src/utils.rs diff --git a/crates/sql-core/tests/coverage.rs b/crates/sql_core/tests/coverage.rs diff --git a/crates/sql_wasm_bridge/Cargo.toml b/crates/sql_wasm_bridge/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "radroots_sql_wasm_bridge" +version = "0.1.0-alpha.1" +edition.workspace = true +authors = [ + "Radroots Authors", +] +rust-version.workspace = true +license.workspace = true +description = "wasm sql bridge primitives for radroots data layers" +repository.workspace = true +homepage.workspace = true +documentation = "https://docs.rs/radroots_sql_wasm_bridge" +readme.workspace = true + +[features] +default = [] + +[dependencies] +js-sys = { workspace = true } +wasm-bindgen = { workspace = true } diff --git a/crates/sql_wasm_bridge/README.md b/crates/sql_wasm_bridge/README.md @@ -0,0 +1,14 @@ +# radroots_sql_wasm_bridge + +Wasm SQL bridge primitives for Rad Roots data layers. + +## Goals + +- define stable wasm bridge interfaces for SQL execute, query, export, and transactions +- keep host interop behavior deterministic across wasm integrations +- support a narrow integration boundary for SQL runtime crates +- provide reusable wasm SQL bridge primitives for higher-level Rad Roots crates + +## License + +Licensed under AGPL-3.0. See LICENSE. diff --git a/crates/sql-wasm-bridge/src/lib.rs b/crates/sql_wasm_bridge/src/lib.rs diff --git a/crates/sql_wasm_core/Cargo.toml b/crates/sql_wasm_core/Cargo.toml @@ -0,0 +1,39 @@ +[package] +name = "radroots_sql_wasm_core" +version = "0.1.0-alpha.1" +edition.workspace = true +authors = [ + "Radroots Authors", +] +rust-version.workspace = true +license.workspace = true +description = "wasm sql runtime primitives for radroots data layers" +repository.workspace = true +homepage.workspace = true +documentation = "https://docs.rs/radroots_sql_wasm_core" +readme.workspace = true + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +default = ["bridge"] +bridge = ["dep:radroots_sql_wasm_bridge"] +embedded = ["dep:rusqlite", "radroots_sql_core/native"] + +[dependencies] +radroots_sql_core = { workspace = true } +radroots_sql_wasm_bridge = { workspace = true, optional = true } +rusqlite = { workspace = true, features = [ + "bundled", + "serialize", +], optional = true } +chrono = { workspace = true, features = ["serde"] } +serde = { workspace = true, features = ["derive"] } +serde_json = { workspace = true } +serde-wasm-bindgen = { workspace = true } +thiserror = { workspace = true } +wasm-bindgen = { workspace = true } +js-sys = { workspace = true } +ts-rs = { workspace = true } +uuid = { workspace = true, features = ["v4", "fast-rng", "js"] } diff --git a/crates/sql_wasm_core/README.md b/crates/sql_wasm_core/README.md @@ -0,0 +1,14 @@ +# radroots_sql_wasm_core + +Wasm SQL runtime primitives for Rad Roots data layers. + +## Goals + +- define stable wasm SQL runtime interfaces for execute, query, and export flows +- keep wasm SQL runtime behavior deterministic across supported environments +- support feature-gated bridge and embedded engine integration surfaces +- provide reusable wasm SQL runtime primitives for higher-level Rad Roots crates + +## License + +Licensed under AGPL-3.0. See LICENSE. diff --git a/crates/sql-wasm-core/src/embedded.rs b/crates/sql_wasm_core/src/embedded.rs diff --git a/crates/sql-wasm-core/src/lib.rs b/crates/sql_wasm_core/src/lib.rs diff --git a/crates/test-fixtures/Cargo.toml b/crates/test-fixtures/Cargo.toml @@ -1,11 +0,0 @@ -[package] -name = "radroots-test-fixtures" -version.workspace = true -edition.workspace = true -rust-version.workspace = true -license.workspace = true -repository.workspace = true -homepage.workspace = true -readme.workspace = true -publish = false -description = "approved deterministic fixtures for radroots test code" diff --git a/crates/test_fixtures/Cargo.toml b/crates/test_fixtures/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "radroots_test_fixtures" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +repository.workspace = true +homepage.workspace = true +readme.workspace = true +publish = false +description = "approved deterministic fixtures for radroots test code" diff --git a/crates/test-fixtures/src/lib.rs b/crates/test_fixtures/src/lib.rs diff --git a/crates/trade/Cargo.toml b/crates/trade/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "radroots-trade" +name = "radroots_trade" version = "0.1.0-alpha.1" edition.workspace = true authors = [ @@ -10,30 +10,30 @@ license.workspace = true description = "workflow validation projections and read models for radroots trade flows" repository.workspace = true homepage.workspace = true -documentation = "https://docs.rs/radroots-trade" +documentation = "https://docs.rs/radroots_trade" readme.workspace = true build = "build.rs" [features] default = ["std", "serde", "serde_json", "ts-rs"] -std = ["radroots-core/std", "radroots-events/std", "radroots-events-codec/std"] +std = ["radroots_core/std", "radroots_events/std", "radroots_events_codec/std"] serde = [ "dep:serde", - "radroots-core/serde", - "radroots-events/serde", - "radroots-events-codec/serde", + "radroots_core/serde", + "radroots_events/serde", + "radroots_events_codec/serde", ] serde_json = [ "serde", "dep:serde_json", - "radroots-events-codec/serde_json", + "radroots_events_codec/serde_json", ] -ts-rs = ["dep:ts-rs", "radroots-events/ts-rs", "radroots-events/std"] +ts-rs = ["dep:ts-rs", "radroots_events/ts-rs", "radroots_events/std"] [dependencies] -radroots-core = { workspace = true, default-features = false } -radroots-events = { workspace = true, default-features = false } -radroots-events-codec = { workspace = true, default-features = false } +radroots_core = { workspace = true, default-features = false } +radroots_events = { workspace = true, default-features = false } +radroots_events_codec = { workspace = true, default-features = false } serde = { workspace = true, default-features = false, features = [ "alloc", "derive", diff --git a/crates/trade/README.md b/crates/trade/README.md @@ -1,4 +1,4 @@ -# radroots-trade +# radroots_trade Trade listing models and Nostr tag mappings for the Rad Roots SDK. diff --git a/crates/types/Cargo.toml b/crates/types/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "radroots-types" +name = "radroots_types" version = "0.1.0-alpha.1" edition.workspace = true authors = [ @@ -10,7 +10,7 @@ license.workspace = true description = "shared api result and error wrapper types for radroots sdk surfaces" repository.workspace = true homepage.workspace = true -documentation = "https://docs.rs/radroots-types" +documentation = "https://docs.rs/radroots_types" readme.workspace = true build = "build.rs" diff --git a/crates/types/README.md b/crates/types/README.md @@ -1,4 +1,4 @@ -# radroots-types +# radroots_types Shared API result and error wrapper types for the Rad Roots SDK. diff --git a/crates/xtask/README.md b/crates/xtask/README.md @@ -14,7 +14,7 @@ Workspace automation for Rad Roots SDK validation and export workflows. - `cargo run -q -p xtask -- sdk validate` - `cargo run -q -p xtask -- sdk release preflight` - `cargo run -q -p xtask -- sdk export-ts` -- `cargo run -q -p xtask -- sdk export-ts-crate --crate radroots-core` +- `cargo run -q -p xtask -- sdk export-ts-crate --crate radroots_core` - `cargo run -q -p xtask -- sdk export-ts-models` - `cargo run -q -p xtask -- sdk export-ts-constants` - `cargo run -q -p xtask -- sdk export-ts-wasm` diff --git a/crates/xtask/src/contract.rs b/crates/xtask/src/contract.rs @@ -915,7 +915,7 @@ resolver = "2" write_file( &root.join("crates").join("a").join("Cargo.toml"), r#"[package] -name = "radroots-a" +name = "radroots_a" version = "0.1.0" edition = "2024" description = "crate a" @@ -928,7 +928,7 @@ readme = "README.md" write_file( &root.join("crates").join("b").join("Cargo.toml"), r#"[package] -name = "radroots-b" +name = "radroots_b" version = "0.1.0" edition = "2024" publish = false @@ -947,14 +947,14 @@ publish = false write_file( &root.join("contract").join("manifest.toml"), r#"[contract] -name = "radroots-contract" +name = "radroots_contract" version = "1.0.0" source = "synthetic" [surface] -model_crates = ["radroots-a"] -algorithm_crates = ["radroots-b"] -wasm_crates = ["radroots-a-wasm"] +model_crates = ["radroots_a"] +algorithm_crates = ["radroots_b"] +wasm_crates = ["radroots_a_wasm"] [policy] exclude_internal_workspace_crates = true @@ -986,7 +986,7 @@ id = "ts" repository = "sdk-typescript" [packages] -"radroots-a" = "@radroots/a" +"radroots_a" = "@radroots/a" [artifacts] models_dir = "src/generated" @@ -1005,7 +1005,7 @@ fail_under_branches = 100.0 require_branches = true [required] -crates = ["radroots-a", "radroots-b"] +crates = ["radroots_a", "radroots_b"] "#, ); write_file( @@ -1017,13 +1017,13 @@ crates = ["radroots-a", "radroots-b"] version = "1.0.0" [publish] -crates = ["radroots-a"] +crates = ["radroots_a"] [internal] -crates = ["radroots-b"] +crates = ["radroots_b"] [publish_order] -crates = ["radroots-a"] +crates = ["radroots_a"] "#, ); write_file( @@ -1031,7 +1031,7 @@ crates = ["radroots-a"] .join("target") .join("coverage") .join("coverage-refresh.tsv"), - "crate\tstatus\texec\tfunc\tbranch\tregion\treport\nradroots-a\tpass\t100.0\t100.0\t100.0\t100.0\tfile\nradroots-b\tpass\t100.0\t100.0\t100.0\t100.0\tfile\n", + "crate\tstatus\texec\tfunc\tbranch\tregion\treport\nradroots_a\tpass\t100.0\t100.0\t100.0\t100.0\tfile\nradroots_b\tpass\t100.0\t100.0\t100.0\t100.0\tfile\n", ); root } @@ -1185,7 +1185,7 @@ pub enum RadrootsCoreUnitDimension { let contract_root = duplicate_root.join("contract"); write_file( &contract_root.join("coverage").join("policy.toml"), - "[gate]\nfail_under_exec_lines = 100.0\nfail_under_functions = 100.0\nfail_under_regions = 100.0\nfail_under_branches = 100.0\nrequire_branches = true\n\n[required]\ncrates = [\"radroots-a\", \"radroots-a\"]\n", + "[gate]\nfail_under_exec_lines = 100.0\nfail_under_functions = 100.0\nfail_under_regions = 100.0\nfail_under_branches = 100.0\nrequire_branches = true\n\n[required]\ncrates = [\"radroots_a\", \"radroots_a\"]\n", ); let duplicate_err = load_coverage_policy(&contract_root).expect_err("duplicate required crates"); @@ -1209,10 +1209,10 @@ pub enum RadrootsCoreUnitDimension { fs::create_dir_all(&coverage_dir).expect("create coverage dir"); fs::write( coverage_dir.join("coverage-refresh.tsv"), - "crate\tstatus\texec\tfunc\tbranch\tregion\treport\nradroots-core\tpass\t100.0\t100.0\t100.0\t100.0\tfile\n", + "crate\tstatus\texec\tfunc\tbranch\tregion\treport\nradroots_core\tpass\t100.0\t100.0\t100.0\t100.0\tfile\n", ) .expect("write coverage file"); - let required = ["radroots-core".to_string()] + let required = ["radroots_core".to_string()] .into_iter() .collect::<BTreeSet<_>>(); validate_required_coverage_summary(&root, &required, strict_thresholds()) @@ -1220,7 +1220,7 @@ pub enum RadrootsCoreUnitDimension { fs::write( coverage_dir.join("coverage-refresh.tsv"), - "crate\tstatus\texec\tfunc\tbranch\tregion\treport\nradroots-core\tpass\t100.0\t99.9\t100.0\t100.0\tfile\n", + "crate\tstatus\texec\tfunc\tbranch\tregion\treport\nradroots_core\tpass\t100.0\t99.9\t100.0\t100.0\tfile\n", ) .expect("write function coverage file"); let func_err = validate_required_coverage_summary(&root, &required, strict_thresholds()) @@ -1229,7 +1229,7 @@ pub enum RadrootsCoreUnitDimension { fs::write( coverage_dir.join("coverage-refresh.tsv"), - "crate\tstatus\texec\tfunc\tbranch\tregion\treport\nradroots-core\tpass\t100.0\t100.0\t99.9\t100.0\tfile\n", + "crate\tstatus\texec\tfunc\tbranch\tregion\treport\nradroots_core\tpass\t100.0\t100.0\t99.9\t100.0\tfile\n", ) .expect("write branch coverage file"); let branch_err = validate_required_coverage_summary(&root, &required, strict_thresholds()) @@ -1238,7 +1238,7 @@ pub enum RadrootsCoreUnitDimension { fs::write( coverage_dir.join("coverage-refresh.tsv"), - "crate\tstatus\texec\tfunc\tbranch\tregion\treport\nradroots-core\tpass\t100.0\t100.0\t100.0\t99.9\tfile\n", + "crate\tstatus\texec\tfunc\tbranch\tregion\treport\nradroots_core\tpass\t100.0\t100.0\t100.0\t99.9\tfile\n", ) .expect("write region coverage file"); let region_err = validate_required_coverage_summary(&root, &required, strict_thresholds()) @@ -1261,17 +1261,17 @@ members = ["crates/a"] fs::write( root.join("crates").join("a").join("Cargo.toml"), r#"[package] -name = "radroots-a" +name = "radroots_a" version = "0.1.0" edition = "2024" repository = { workspace = true } homepage = { workspace = true } -documentation = "https://docs.rs/radroots-a" +documentation = "https://docs.rs/radroots_a" readme = { workspace = true } "#, ) .expect("write package manifest"); - let publish = ["radroots-a".to_string()] + let publish = ["radroots_a".to_string()] .into_iter() .collect::<BTreeSet<_>>(); let err = @@ -1356,14 +1356,14 @@ members = ["crates/a", "crates/b"] write_file( &coverage_dir.join("coverage-refresh.tsv"), - "crate\tstatus\texec\tfunc\tbranch\tregion\treport\nradroots-a\tpass\tnot-a-number\t100\t100\t100\tfile\n", + "crate\tstatus\texec\tfunc\tbranch\tregion\treport\nradroots_a\tpass\tnot-a-number\t100\t100\t100\tfile\n", ); let bad_percent = load_coverage_refresh_rows(&root).expect_err("invalid coverage percent"); assert!(bad_percent.contains("parse exec")); write_file( &coverage_dir.join("coverage-refresh.tsv"), - "crate\tstatus\texec\tfunc\tbranch\tregion\treport\nradroots-a\tpass\t100\t100\t100\tnot-a-number\tfile\n", + "crate\tstatus\texec\tfunc\tbranch\tregion\treport\nradroots_a\tpass\t100\t100\t100\tnot-a-number\tfile\n", ); let bad_region = load_coverage_refresh_rows(&root).expect_err("invalid region coverage percent"); @@ -1371,16 +1371,16 @@ members = ["crates/a", "crates/b"] write_file( &coverage_dir.join("coverage-refresh.tsv"), - "crate\tstatus\texec\tfunc\tbranch\tregion\treport\nradroots-a\tpass\t100\t100\t100\t100\tfile\nradroots-a\tpass\t100\t100\t100\t100\tfile\n", + "crate\tstatus\texec\tfunc\tbranch\tregion\treport\nradroots_a\tpass\t100\t100\t100\t100\tfile\nradroots_a\tpass\t100\t100\t100\t100\tfile\n", ); let duplicate_row = load_coverage_refresh_rows(&root).expect_err("duplicate coverage row"); assert!(duplicate_row.contains("duplicate coverage row")); write_file( &coverage_dir.join("coverage-refresh.tsv"), - "crate\tstatus\texec\tfunc\tbranch\tregion\treport\nradroots-a\tfail\t100\t100\t100\t100\tfile\n", + "crate\tstatus\texec\tfunc\tbranch\tregion\treport\nradroots_a\tfail\t100\t100\t100\t100\tfile\n", ); - let required = ["radroots-a".to_string()] + let required = ["radroots_a".to_string()] .into_iter() .collect::<BTreeSet<_>>(); let non_pass = validate_required_coverage_summary(&root, &required, strict_thresholds()) @@ -1389,7 +1389,7 @@ members = ["crates/a", "crates/b"] write_file( &coverage_dir.join("coverage-refresh.tsv"), - "crate\tstatus\texec\tfunc\tbranch\tregion\treport\nradroots-a\tpass\t99.9\t100\t100\t100\tfile\n", + "crate\tstatus\texec\tfunc\tbranch\tregion\treport\nradroots_a\tpass\t99.9\t100\t100\t100\tfile\n", ); let below_100 = validate_required_coverage_summary(&root, &required, strict_thresholds()) .expect_err("coverage below 100"); @@ -1474,7 +1474,7 @@ fail_under_branches = 100.0 require_branches = true [required] -crates = ["radroots-a", "radroots-b"] +crates = ["radroots_a", "radroots_b"] "#, ); let invalid_gate = validate_coverage_policy_parity(&root, &contract_root) @@ -1491,7 +1491,7 @@ fail_under_branches = 100.0 require_branches = true [required] -crates = ["radroots-a", "radroots-b"] +crates = ["radroots_a", "radroots_b"] "#, ); let invalid_functions = validate_coverage_policy_parity(&root, &contract_root) @@ -1508,7 +1508,7 @@ fail_under_branches = 100.0 require_branches = true [required] -crates = ["radroots-a", "radroots-b"] +crates = ["radroots_a", "radroots_b"] "#, ); let invalid_regions = validate_coverage_policy_parity(&root, &contract_root) @@ -1525,7 +1525,7 @@ fail_under_branches = 99.0 require_branches = true [required] -crates = ["radroots-a", "radroots-b"] +crates = ["radroots_a", "radroots_b"] "#, ); let invalid_branches = validate_coverage_policy_parity(&root, &contract_root) @@ -1542,7 +1542,7 @@ fail_under_branches = 100.0 require_branches = true [required] -crates = ["radroots-a", "radroots-a"] +crates = ["radroots_a", "radroots_a"] "#, ); let duplicate_required = validate_coverage_policy_parity(&root, &contract_root) @@ -1559,7 +1559,7 @@ fail_under_branches = 100.0 require_branches = false [required] -crates = ["radroots-a", "radroots-b"] +crates = ["radroots_a", "radroots_b"] "#, ); let branches_optional = validate_coverage_policy_parity(&root, &contract_root) @@ -1576,7 +1576,7 @@ fail_under_branches = 100.0 require_branches = true [required] -crates = ["radroots-a"] +crates = ["radroots_a"] "#, ); let missing_workspace = validate_coverage_policy_parity(&root, &contract_root) @@ -1614,13 +1614,13 @@ crates = ["unknown"] version = "" [publish] -crates = ["radroots-a"] +crates = ["radroots_a"] [internal] -crates = ["radroots-b"] +crates = ["radroots_b"] [publish_order] -crates = ["radroots-a"] +crates = ["radroots_a"] "#, ); let empty_version = validate_release_publish_policy(&root, &contract_root, "1.0.0") @@ -1633,13 +1633,13 @@ crates = ["radroots-a"] version = "2.0.0" [publish] -crates = ["radroots-a"] +crates = ["radroots_a"] [internal] -crates = ["radroots-b"] +crates = ["radroots_b"] [publish_order] -crates = ["radroots-a"] +crates = ["radroots_a"] "#, ); let version_mismatch = validate_release_publish_policy(&root, &contract_root, "1.0.0") @@ -1652,13 +1652,13 @@ crates = ["radroots-a"] version = "1.0.0" [publish] -crates = ["radroots-a"] +crates = ["radroots_a"] [internal] -crates = ["radroots-a"] +crates = ["radroots_a"] [publish_order] -crates = ["radroots-a"] +crates = ["radroots_a"] "#, ); let overlap = validate_release_publish_policy(&root, &contract_root, "1.0.0") @@ -1671,13 +1671,13 @@ crates = ["radroots-a"] version = "1.0.0" [publish] -crates = ["radroots-a"] +crates = ["radroots_a"] [internal] crates = [] [publish_order] -crates = ["radroots-a"] +crates = ["radroots_a"] "#, ); let missing_workspace = validate_release_publish_policy(&root, &contract_root, "1.0.0") @@ -1690,10 +1690,10 @@ crates = ["radroots-a"] version = "1.0.0" [publish] -crates = ["radroots-a"] +crates = ["radroots_a"] [internal] -crates = ["radroots-b"] +crates = ["radroots_b"] [publish_order] crates = [] @@ -1709,13 +1709,13 @@ crates = [] version = "1.0.0" [publish] -crates = ["radroots-a"] +crates = ["radroots_a"] [internal] -crates = ["radroots-b"] +crates = ["radroots_b"] [publish_order] -crates = ["radroots-a", "radroots-b"] +crates = ["radroots_a", "radroots_b"] "#, ); let extra_publish_order = validate_release_publish_policy(&root, &contract_root, "1.0.0") @@ -1725,7 +1725,7 @@ crates = ["radroots-a", "radroots-b"] write_file( &root.join("crates").join("a").join("Cargo.toml"), r#"[package] -name = "radroots-a" +name = "radroots_a" version = "0.1.0" edition = "2024" description = "crate a" @@ -1735,13 +1735,13 @@ documentation = "https://docs.example.com/a" readme = "README.md" [dependencies] -radroots-b = { path = "../b" } +radroots_b = { path = "../b" } "#, ); write_file( &root.join("crates").join("b").join("Cargo.toml"), r#"[package] -name = "radroots-b" +name = "radroots_b" version = "0.1.0" edition = "2024" description = "crate b" @@ -1757,13 +1757,13 @@ readme = "README.md" version = "1.0.0" [publish] -crates = ["radroots-a", "radroots-b"] +crates = ["radroots_a", "radroots_b"] [internal] crates = [] [publish_order] -crates = ["radroots-a", "radroots-b"] +crates = ["radroots_a", "radroots_b"] "#, ); let dependency_order = validate_release_publish_policy(&root, &contract_root, "1.0.0") @@ -1776,19 +1776,19 @@ crates = ["radroots-a", "radroots-b"] version = "1.0.0" [publish] -crates = ["radroots-a"] +crates = ["radroots_a"] [internal] -crates = ["radroots-b"] +crates = ["radroots_b"] [publish_order] -crates = ["radroots-a"] +crates = ["radroots_a"] "#, ); write_file( &root.join("crates").join("b").join("Cargo.toml"), r#"[package] -name = "radroots-b" +name = "radroots_b" version = "0.1.0" edition = "2024" publish = false @@ -1800,7 +1800,7 @@ publish = false write_file( &root.join("crates").join("a").join("Cargo.toml"), r#"[package] -name = "radroots-a" +name = "radroots_a" version = "0.1.0" edition = "2024" publish = false @@ -1812,13 +1812,13 @@ publish = false version = "1.0.0" [publish] -crates = ["radroots-a"] +crates = ["radroots_a"] [internal] -crates = ["radroots-b"] +crates = ["radroots_b"] [publish_order] -crates = ["radroots-a"] +crates = ["radroots_a"] "#, ); let publish_flag = validate_release_publish_policy(&root, &contract_root, "1.0.0") @@ -1828,7 +1828,7 @@ crates = ["radroots-a"] write_file( &root.join("crates").join("a").join("Cargo.toml"), r#"[package] -name = "radroots-a" +name = "radroots_a" version = "0.1.0" edition = "2024" description = "crate a" @@ -1841,7 +1841,7 @@ readme = "README.md" write_file( &root.join("crates").join("b").join("Cargo.toml"), r#"[package] -name = "radroots-b" +name = "radroots_b" version = "0.1.0" edition = "2024" "#, @@ -2090,7 +2090,7 @@ members = ["crates/a"] let deps_err = read_workspace_package_dependencies(&root).expect_err("deps should fail"); assert!(deps_err.contains("read")); - let publish = ["radroots-a".to_string()] + let publish = ["radroots_a".to_string()] .into_iter() .collect::<BTreeSet<_>>(); let publish_err = @@ -2123,13 +2123,13 @@ resolver = "2" let root = create_synthetic_workspace("workspace_manifest_success"); let manifests = workspace_package_manifests(&root).expect("workspace manifests"); assert_eq!(manifests.len(), 2); - assert!(manifests.contains_key("radroots-a")); - assert!(manifests.contains_key("radroots-b")); + assert!(manifests.contains_key("radroots_a")); + assert!(manifests.contains_key("radroots_b")); write_file( &root.join("crates").join("b").join("Cargo.toml"), r#"[package] -name = "radroots-a" +name = "radroots_a" version = "0.1.0" edition = "2024" description = "crate b duplicate name" @@ -2140,7 +2140,7 @@ readme = "README.md" publish = false "#, ); - let publish = ["radroots-a".to_string()] + let publish = ["radroots_a".to_string()] .into_iter() .collect::<BTreeSet<_>>(); let duplicate_err = @@ -2176,7 +2176,7 @@ fail_under_branches = 100.0 require_branches = true [required] -crates = ["radroots-a", "radroots-b"] +crates = ["radroots_a", "radroots_b"] "#, ); @@ -2198,13 +2198,13 @@ crates = ["radroots-a", "radroots-b"] version = "1.0.0" [publish] -crates = ["radroots-a", "radroots-a"] +crates = ["radroots_a", "radroots_a"] [internal] -crates = ["radroots-b"] +crates = ["radroots_b"] [publish_order] -crates = ["radroots-a"] +crates = ["radroots_a"] "#, ); let duplicate_publish = validate_release_publish_policy(&root, &contract_root, "1.0.0") @@ -2217,13 +2217,13 @@ crates = ["radroots-a"] version = "1.0.0" [publish] -crates = ["radroots-a"] +crates = ["radroots_a"] [internal] -crates = ["radroots-b", "radroots-b"] +crates = ["radroots_b", "radroots_b"] [publish_order] -crates = ["radroots-a"] +crates = ["radroots_a"] "#, ); let duplicate_internal = validate_release_publish_policy(&root, &contract_root, "1.0.0") @@ -2236,13 +2236,13 @@ crates = ["radroots-a"] version = "1.0.0" [publish] -crates = ["radroots-a"] +crates = ["radroots_a"] [internal] -crates = ["radroots-b"] +crates = ["radroots_b"] [publish_order] -crates = ["radroots-a", "radroots-a"] +crates = ["radroots_a", "radroots_a"] "#, ); let duplicate_order = validate_release_publish_policy(&root, &contract_root, "1.0.0") @@ -2255,13 +2255,13 @@ crates = ["radroots-a", "radroots-a"] version = "1.0.0" [publish] -crates = ["radroots-a"] +crates = ["radroots_a"] [internal] -crates = ["radroots-b"] +crates = ["radroots_b"] [publish_order] -crates = ["radroots-a"] +crates = ["radroots_a"] "#, ); write_file( @@ -2287,14 +2287,14 @@ crates = ["radroots-a"] write_file( &invalid_bundle.join("contract").join("manifest.toml"), r#"[contract] -name = "radroots-contract" +name = "radroots_contract" version = "1.0.0" source = "synthetic" [surface] -model_crates = ["radroots-a"] -algorithm_crates = ["radroots-b"] -wasm_crates = ["radroots-a-wasm"] +model_crates = ["radroots_a"] +algorithm_crates = ["radroots_b"] +wasm_crates = ["radroots_a_wasm"] [policy] exclude_internal_workspace_crates = false @@ -2341,13 +2341,13 @@ require_conformance_vectors = true version = "1.0.0" [publish] -crates = ["radroots-a", "radroots-a"] +crates = ["radroots_a", "radroots_a"] [internal] -crates = ["radroots-b"] +crates = ["radroots_b"] [publish_order] -crates = ["radroots-a"] +crates = ["radroots_a"] "#, ); let duplicate_publish_err = @@ -2361,7 +2361,7 @@ crates = ["radroots-a"] .join("contract") .join("coverage") .join("policy.toml"), - "[gate]\nfail_under_exec_lines = 100.0\nfail_under_functions = 100.0\nfail_under_regions = 100.0\nfail_under_branches = 100.0\nrequire_branches = true\n\n[required]\ncrates = [\"radroots-a\", \"radroots-a\"]\n", + "[gate]\nfail_under_exec_lines = 100.0\nfail_under_functions = 100.0\nfail_under_regions = 100.0\nfail_under_branches = 100.0\nrequire_branches = true\n\n[required]\ncrates = [\"radroots_a\", \"radroots_a\"]\n", ); let duplicate_required_err = validate_release_preflight(&duplicate_required).expect_err("duplicate required crates"); @@ -2372,7 +2372,7 @@ crates = ["radroots-a"] write_file( &publish_metadata.join("crates").join("a").join("Cargo.toml"), r#"[package] -name = "radroots-a" +name = "radroots_a" version = "0.1.0" edition = "2024" "#, @@ -2434,7 +2434,7 @@ id = "ts" repository = "sdk-typescript" [packages] -"radroots-a" = "@radroots/a" +"radroots_a" = "@radroots/a" [artifacts] models_dir = "src/generated" @@ -2475,7 +2475,7 @@ fail_under_branches = 100.0 require_branches = false [required] -crates = ["radroots-a", "radroots-b"] +crates = ["radroots_a", "radroots_b"] "#, ); let policy_err = validate_contract_bundle(&bundle).expect_err("coverage policy validation"); @@ -2492,7 +2492,7 @@ crates = ["radroots-a", "radroots-b"] .join("target") .join("coverage") .join("coverage-refresh.tsv"), - "crate\tstatus\texec\tfunc\tbranch\tregion\treport\nradroots-a\tpass\t100\tbad\t100\t100\tfile\n", + "crate\tstatus\texec\tfunc\tbranch\tregion\treport\nradroots_a\tpass\t100\tbad\t100\t100\tfile\n", ); let func_err = load_coverage_refresh_rows(&coverage_root).expect_err("func parse error"); assert!(func_err.contains("parse func")); @@ -2501,7 +2501,7 @@ crates = ["radroots-a", "radroots-b"] .join("target") .join("coverage") .join("coverage-refresh.tsv"), - "crate\tstatus\texec\tfunc\tbranch\tregion\treport\nradroots-a\tpass\t100\t100\tbad\t100\tfile\n", + "crate\tstatus\texec\tfunc\tbranch\tregion\treport\nradroots_a\tpass\t100\t100\tbad\t100\tfile\n", ); let branch_err = load_coverage_refresh_rows(&coverage_root).expect_err("branch parse error"); @@ -2509,7 +2509,7 @@ crates = ["radroots-a", "radroots-b"] let _ = fs::remove_dir_all(&coverage_root); let missing_refresh_root = temp_root("coverage_summary_missing_refresh"); - let required = ["radroots-a".to_string()] + let required = ["radroots_a".to_string()] .into_iter() .collect::<BTreeSet<_>>(); let missing_refresh_err = validate_required_coverage_summary( @@ -2548,7 +2548,7 @@ members = ["crates/a"] write_file( &root.join("crates").join("a").join("Cargo.toml"), r#"[package] -name = "radroots-a" +name = "radroots_a" version = "0.1.0" edition = "2024" description = "crate a" @@ -2557,14 +2557,14 @@ homepage = { workspace = true } readme = { workspace = true } "#, ); - let missing_manifest = ["radroots-b".to_string()] + let missing_manifest = ["radroots_b".to_string()] .into_iter() .collect::<BTreeSet<_>>(); let missing_err = validate_publish_package_metadata(&root, &missing_manifest) .expect_err("missing workspace manifest"); assert!(missing_err.contains("has no workspace manifest")); - let missing_field = ["radroots-a".to_string()] + let missing_field = ["radroots_a".to_string()] .into_iter() .collect::<BTreeSet<_>>(); let field_err = validate_publish_package_metadata(&root, &missing_field) @@ -2585,11 +2585,11 @@ readme = { workspace = true } .join("target") .join("coverage") .join("coverage-refresh.tsv"), - "crate\tstatus\texec\tfunc\tbranch\tregion\treport\n\nradroots-a\tpass\t100\t100\t100\t100\tfile\n", + "crate\tstatus\texec\tfunc\tbranch\tregion\treport\n\nradroots_a\tpass\t100\t100\t100\t100\tfile\n", ); let rows = load_coverage_refresh_rows(&root).expect("rows"); assert_eq!(rows.len(), 1); - assert!(rows.contains_key("radroots-a")); + assert!(rows.contains_key("radroots_a")); let _ = fs::remove_dir_all(&root); } @@ -2636,7 +2636,7 @@ fail_under_branches = 100.0 require_branches = true [required] -crates = ["radroots-a", "radroots-b", "radroots-extra"] +crates = ["radroots_a", "radroots_b", "radroots_extra"] "#, ); let coverage_extra = validate_coverage_policy_parity(&root, &contract_root) @@ -2653,7 +2653,7 @@ fail_under_branches = 100.0 require_branches = true [required] -crates = ["radroots-a"] +crates = ["radroots_a"] "#, ); let required_list_mismatch = validate_coverage_policy_parity(&root, &contract_root) @@ -2666,13 +2666,13 @@ crates = ["radroots-a"] version = "1.0.0" [publish] -crates = ["radroots-a", "radroots-b", "radroots-extra"] +crates = ["radroots_a", "radroots_b", "radroots_extra"] [internal] crates = [] [publish_order] -crates = ["radroots-a", "radroots-b"] +crates = ["radroots_a", "radroots_b"] "#, ); let release_extra = validate_release_publish_policy(&root, &contract_root, "1.0.0") @@ -2685,13 +2685,13 @@ crates = ["radroots-a", "radroots-b"] version = "1.0.0" [publish] -crates = ["radroots-a"] +crates = ["radroots_a"] [internal] -crates = ["radroots-b"] +crates = ["radroots_b"] [publish_order] -crates = ["radroots-a", "radroots-b"] +crates = ["radroots_a", "radroots_b"] "#, ); let publish_order_extra = validate_release_publish_policy(&root, &contract_root, "1.0.0") @@ -2720,8 +2720,8 @@ id = "ts" repository = "sdk-typescript" [packages] -radroots-a = "@radroots/a" -radroots-b = "@radroots/b" +radroots_a = "@radroots/a" +radroots_b = "@radroots/b" [artifacts] models_dir = "src/generated" @@ -2757,10 +2757,10 @@ manifest_file = "export-manifest.json" version = "1.0.0" [publish] -crates = ["radroots-a"] +crates = ["radroots_a"] [internal] -crates = ["radroots-b"] +crates = ["radroots_b"] [publish_order] crates = [] diff --git a/crates/xtask/src/coverage.rs b/crates/xtask/src/coverage.rs @@ -1362,7 +1362,7 @@ mod tests { let non_finite = temp_file_path("coverage_policy_non_finite"); write_file( &non_finite, - "[gate]\nfail_under_exec_lines = inf\nfail_under_functions = 100.0\nfail_under_regions = 100.0\nfail_under_branches = 100.0\nrequire_branches = true\n\n[required]\ncrates = [\"radroots-a\"]\n", + "[gate]\nfail_under_exec_lines = inf\nfail_under_functions = 100.0\nfail_under_regions = 100.0\nfail_under_branches = 100.0\nrequire_branches = true\n\n[required]\ncrates = [\"radroots_a\"]\n", ); let non_finite_err = read_coverage_policy(&non_finite).expect_err("non-finite threshold should fail"); @@ -1372,7 +1372,7 @@ mod tests { let out_of_range = temp_file_path("coverage_policy_out_of_range"); write_file( &out_of_range, - "[gate]\nfail_under_exec_lines = 100.0\nfail_under_functions = 101.0\nfail_under_regions = 100.0\nfail_under_branches = 100.0\nrequire_branches = true\n\n[required]\ncrates = [\"radroots-a\"]\n", + "[gate]\nfail_under_exec_lines = 100.0\nfail_under_functions = 101.0\nfail_under_regions = 100.0\nfail_under_branches = 100.0\nrequire_branches = true\n\n[required]\ncrates = [\"radroots_a\"]\n", ); let out_of_range_err = read_coverage_policy(&out_of_range).expect_err("out-of-range threshold should fail"); @@ -1387,14 +1387,14 @@ mod tests { fs::create_dir_all(&coverage_dir).expect("create coverage dir"); write_file( &coverage_dir.join("policy.toml"), - "[gate]\nfail_under_exec_lines = 100.0\nfail_under_functions = 100.0\nfail_under_regions = 100.0\nfail_under_branches = 100.0\nrequire_branches = true\n\n[required]\ncrates = [\"radroots-a\"]\n", + "[gate]\nfail_under_exec_lines = 100.0\nfail_under_functions = 100.0\nfail_under_regions = 100.0\nfail_under_branches = 100.0\nrequire_branches = true\n\n[required]\ncrates = [\"radroots_a\"]\n", ); let out_path = root.join("gate-report.json"); report_missing_gate_with_root( &[ "--scope".to_string(), - "radroots-a-blocking".to_string(), + "radroots_a_blocking".to_string(), "--out".to_string(), out_path.display().to_string(), "--reason".to_string(), @@ -1434,7 +1434,7 @@ mod tests { let missing_out = report_missing_gate_with_root( &[ "--scope".to_string(), - "radroots-a-blocking".to_string(), + "radroots_a_blocking".to_string(), "--reason".to_string(), "missing-coverage-artifacts".to_string(), ], @@ -1446,7 +1446,7 @@ mod tests { let missing_reason = report_missing_gate_with_root( &[ "--scope".to_string(), - "radroots-a-blocking".to_string(), + "radroots_a_blocking".to_string(), "--out".to_string(), root.join("missing-gate.json").display().to_string(), ], @@ -1458,7 +1458,7 @@ mod tests { let policy_err = report_missing_gate_with_root( &[ "--scope".to_string(), - "radroots-a-blocking".to_string(), + "radroots_a_blocking".to_string(), "--out".to_string(), root.join("missing-gate.json").display().to_string(), "--reason".to_string(), @@ -1473,14 +1473,14 @@ mod tests { fs::create_dir_all(&coverage_dir).expect("create coverage dir"); write_file( &coverage_dir.join("policy.toml"), - "[gate]\nfail_under_exec_lines = 100.0\nfail_under_functions = 100.0\nfail_under_regions = 100.0\nfail_under_branches = 100.0\nrequire_branches = true\n\n[required]\ncrates = [\"radroots-a\"]\n", + "[gate]\nfail_under_exec_lines = 100.0\nfail_under_functions = 100.0\nfail_under_regions = 100.0\nfail_under_branches = 100.0\nrequire_branches = true\n\n[required]\ncrates = [\"radroots_a\"]\n", ); let out_path = root.join("gate-report.json"); fs::create_dir_all(&out_path).expect("create blocking output dir"); let write_err = report_missing_gate_with_root( &[ "--scope".to_string(), - "radroots-a-blocking".to_string(), + "radroots_a_blocking".to_string(), "--out".to_string(), out_path.display().to_string(), "--reason".to_string(), @@ -1501,7 +1501,7 @@ mod tests { fs::create_dir_all(&coverage_dir).expect("create coverage dir"); write_file( &coverage_dir.join("policy.toml"), - "[gate]\nfail_under_exec_lines = 100.0\nfail_under_functions = 100.0\nfail_under_regions = 100.0\nfail_under_branches = 100.0\nrequire_branches = true\n\n[required]\ncrates = [\"radroots-a\"]\n", + "[gate]\nfail_under_exec_lines = 100.0\nfail_under_functions = 100.0\nfail_under_regions = 100.0\nfail_under_branches = 100.0\nrequire_branches = true\n\n[required]\ncrates = [\"radroots_a\"]\n", ); let reports_root = root.join("target").join("coverage"); @@ -1510,7 +1510,7 @@ mod tests { write_file( &crate_dir.join("gate-report.json"), r#"{ - "scope": "radroots-a", + "scope": "radroots_a", "thresholds": { "executable_lines": 100.0, "functions": 100.0, @@ -1563,11 +1563,11 @@ mod tests { let refresh = fs::read_to_string(&refresh_out).expect("read refresh summary"); assert!(refresh.contains("crate\tstatus\texec\tfunc\tbranch\tregion\treport")); assert!( - refresh.contains("radroots-a\tpass\t100.000000\t100.000000\t100.000000\t97.500000\t") + refresh.contains("radroots_a\tpass\t100.000000\t100.000000\t100.000000\t97.500000\t") ); let status = fs::read_to_string(&status_out).expect("read status summary"); - assert_eq!(status, "crate\tstatus\nradroots-a\tpass\n"); + assert_eq!(status, "crate\tstatus\nradroots_a\tpass\n"); fs::remove_dir_all(root).expect("remove root"); @@ -1576,7 +1576,7 @@ mod tests { fs::create_dir_all(&defaults_coverage_dir).expect("create defaults coverage dir"); write_file( &defaults_coverage_dir.join("policy.toml"), - "[gate]\nfail_under_exec_lines = 100.0\nfail_under_functions = 100.0\nfail_under_regions = 100.0\nfail_under_branches = 100.0\nrequire_branches = true\n\n[required]\ncrates = [\"radroots-a\"]\n", + "[gate]\nfail_under_exec_lines = 100.0\nfail_under_functions = 100.0\nfail_under_regions = 100.0\nfail_under_branches = 100.0\nrequire_branches = true\n\n[required]\ncrates = [\"radroots_a\"]\n", ); write_file( &defaults_root @@ -1585,7 +1585,7 @@ mod tests { .join("radroots_a") .join("gate-report.json"), r#"{ - "scope": "radroots-a", + "scope": "radroots_a", "thresholds": { "executable_lines": 100.0, "functions": 100.0, @@ -1633,7 +1633,7 @@ mod tests { .expect("read defaults refresh summary"); assert!( defaults_refresh - .contains("radroots-a\tfail\t100.000000\t100.000000\t100.000000\t100.000000\t") + .contains("radroots_a\tfail\t100.000000\t100.000000\t100.000000\t100.000000\t") ); let dispatch_root = temp_dir_path("refresh_summary_parentless_root"); @@ -1641,7 +1641,7 @@ mod tests { fs::create_dir_all(&dispatch_coverage_dir).expect("create dispatch coverage dir"); write_file( &dispatch_coverage_dir.join("policy.toml"), - "[gate]\nfail_under_exec_lines = 100.0\nfail_under_functions = 100.0\nfail_under_regions = 100.0\nfail_under_branches = 100.0\nrequire_branches = true\n\n[required]\ncrates = [\"radroots-a\"]\n", + "[gate]\nfail_under_exec_lines = 100.0\nfail_under_functions = 100.0\nfail_under_regions = 100.0\nfail_under_branches = 100.0\nrequire_branches = true\n\n[required]\ncrates = [\"radroots_a\"]\n", ); write_file( &dispatch_root.join("Cargo.toml"), @@ -1654,7 +1654,7 @@ mod tests { .join("radroots_a") .join("gate-report.json"), r#"{ - "scope": "radroots-a", + "scope": "radroots_a", "thresholds": { "executable_lines": 100.0, "functions": 100.0, @@ -1692,7 +1692,7 @@ mod tests { &[ "report-missing".to_string(), "--scope".to_string(), - "radroots-a-blocking".to_string(), + "radroots_a_blocking".to_string(), "--out".to_string(), "missing-gate.json".to_string(), "--reason".to_string(), @@ -1729,7 +1729,7 @@ mod tests { fs::create_dir_all(&coverage_dir).expect("create coverage dir"); write_file( &coverage_dir.join("policy.toml"), - "[gate]\nfail_under_exec_lines = 100.0\nfail_under_functions = 100.0\nfail_under_regions = 100.0\nfail_under_branches = 100.0\nrequire_branches = true\n\n[required]\ncrates = [\"radroots-a\"]\n", + "[gate]\nfail_under_exec_lines = 100.0\nfail_under_functions = 100.0\nfail_under_regions = 100.0\nfail_under_branches = 100.0\nrequire_branches = true\n\n[required]\ncrates = [\"radroots_a\"]\n", ); write_file( &root @@ -1738,7 +1738,7 @@ mod tests { .join("radroots_a") .join("gate-report.json"), r#"{ - "scope": "radroots-a", + "scope": "radroots_a", "thresholds": { "executable_lines": 100.0, "functions": 100.0, @@ -1814,7 +1814,7 @@ mod tests { fs::create_dir_all(&coverage_dir).expect("create coverage dir"); write_file( &coverage_dir.join("policy.toml"), - "[gate]\nfail_under_exec_lines = 100.0\nfail_under_functions = 100.0\nfail_under_regions = 100.0\nfail_under_branches = 100.0\nrequire_branches = true\n\n[required]\ncrates = [\"radroots-a\"]\n", + "[gate]\nfail_under_exec_lines = 100.0\nfail_under_functions = 100.0\nfail_under_regions = 100.0\nfail_under_branches = 100.0\nrequire_branches = true\n\n[required]\ncrates = [\"radroots_a\"]\n", ); write_file( &root @@ -1823,7 +1823,7 @@ mod tests { .join("radroots_a") .join("gate-report.json"), r#"{ - "scope": "radroots-a", + "scope": "radroots_a", "thresholds": { "executable_lines": 100.0, "functions": 100.0, @@ -1885,7 +1885,7 @@ mod tests { fs::create_dir_all(&coverage_dir).expect("create coverage dir"); write_file( &coverage_dir.join("policy.toml"), - "[gate]\nfail_under_exec_lines = 100.0\nfail_under_functions = 100.0\nfail_under_regions = 100.0\nfail_under_branches = 100.0\nrequire_branches = true\n\n[required]\ncrates = [\"radroots-a\"]\n", + "[gate]\nfail_under_exec_lines = 100.0\nfail_under_functions = 100.0\nfail_under_regions = 100.0\nfail_under_branches = 100.0\nrequire_branches = true\n\n[required]\ncrates = [\"radroots_a\"]\n", ); write_file( &root @@ -1894,7 +1894,7 @@ mod tests { .join("radroots_a") .join("gate-report.json"), r#"{ - "scope": "radroots-a", + "scope": "radroots_a", "thresholds": { "executable_lines": 100.0, "functions": 100.0, @@ -1973,7 +1973,7 @@ mod tests { fs::create_dir_all(&coverage_dir).expect("create coverage dir"); write_file( &coverage_dir.join("policy.toml"), - "[gate]\nfail_under_exec_lines = 100.0\nfail_under_functions = 100.0\nfail_under_regions = 100.0\nfail_under_branches = 100.0\nrequire_branches = true\n\n[required]\ncrates = [\"radroots-a\"]\n", + "[gate]\nfail_under_exec_lines = 100.0\nfail_under_functions = 100.0\nfail_under_regions = 100.0\nfail_under_branches = 100.0\nrequire_branches = true\n\n[required]\ncrates = [\"radroots_a\"]\n", ); let gate_err = run_with_root( &[ @@ -2129,7 +2129,7 @@ mod tests { fn coverage_profiles_default_when_contract_file_is_missing() { let root = temp_dir_path("profile_missing"); fs::create_dir_all(&root).expect("create root"); - let profile = read_coverage_profile(&root, "radroots-log").expect("read profile"); + let profile = read_coverage_profile(&root, "radroots_log").expect("read profile"); assert!(!profile.no_default_features); assert!(profile.features.is_empty()); assert_eq!(profile.test_threads, None); @@ -2148,19 +2148,19 @@ no_default_features = false features = ["std"] test_threads = 2 -[profiles.crates."radroots-log"] +[profiles.crates."radroots_log"] no_default_features = true features = ["rt"] "#, ) .expect("write profiles"); - let app_profile = read_coverage_profile(&root, "radroots-log").expect("app profile"); + let app_profile = read_coverage_profile(&root, "radroots_log").expect("app profile"); assert!(app_profile.no_default_features); assert_eq!(app_profile.features, vec!["rt".to_string()]); assert_eq!(app_profile.test_threads, Some(2)); - let other_profile = read_coverage_profile(&root, "radroots-types").expect("other profile"); + let other_profile = read_coverage_profile(&root, "radroots_types").expect("other profile"); assert!(!other_profile.no_default_features); assert_eq!(other_profile.features, vec!["std".to_string()]); assert_eq!(other_profile.test_threads, Some(2)); @@ -2175,13 +2175,13 @@ features = ["rt"] fs::create_dir_all(&coverage_dir).expect("create coverage dir"); fs::write( coverage_dir.join("profiles.toml"), - r#"[profiles.crates."radroots-log"] + r#"[profiles.crates."radroots_log"] test_threads = 4 "#, ) .expect("write profiles"); let profile = - read_coverage_profile(&root, "radroots-log").expect("valid positive thread profile"); + read_coverage_profile(&root, "radroots_log").expect("valid positive thread profile"); assert_eq!(profile.test_threads, Some(4)); fs::remove_dir_all(root).expect("remove root"); } @@ -2193,14 +2193,14 @@ test_threads = 4 fs::create_dir_all(&coverage_dir).expect("create coverage dir"); fs::write( coverage_dir.join("profiles.toml"), - r#"[profiles.crates."radroots-log"] + r#"[profiles.crates."radroots_log"] features = [""] test_threads = 0 "#, ) .expect("write profiles"); - let err = read_coverage_profile(&root, "radroots-log").expect_err("invalid profile"); + let err = read_coverage_profile(&root, "radroots_log").expect_err("invalid profile"); assert!( err.contains("empty feature value"), "unexpected error: {err}" @@ -2216,7 +2216,7 @@ test_threads = 0 fs::create_dir_all(&coverage_dir).expect("create coverage dir"); fs::write(coverage_dir.join("profiles.toml"), "[profiles.default\n") .expect("write invalid profiles"); - let err = read_coverage_profile(&root, "radroots-log").expect_err("invalid toml"); + let err = read_coverage_profile(&root, "radroots_log").expect_err("invalid toml"); assert!(err.contains("failed to parse")); fs::remove_dir_all(root).expect("remove root"); } @@ -2228,13 +2228,13 @@ test_threads = 0 fs::create_dir_all(&coverage_dir).expect("create coverage dir"); fs::write( coverage_dir.join("profiles.toml"), - r#"[profiles.crates."radroots-log"] + r#"[profiles.crates."radroots_log"] test_threads = 0 "#, ) .expect("write profiles"); - let err = read_coverage_profile(&root, "radroots-log").expect_err("invalid thread count"); + let err = read_coverage_profile(&root, "radroots_log").expect_err("invalid thread count"); assert!(err.contains("test_threads > 0")); fs::remove_dir_all(root).expect("remove root"); @@ -2433,10 +2433,10 @@ test_threads = 0 let valid = temp_file_path("parse_toml_valid"); write_file( &valid, - "[gate]\nfail_under_exec_lines = 100.0\nfail_under_functions = 100.0\nfail_under_regions = 100.0\nfail_under_branches = 100.0\nrequire_branches = true\n\n[required]\ncrates = [\"radroots-core\"]\n", + "[gate]\nfail_under_exec_lines = 100.0\nfail_under_functions = 100.0\nfail_under_regions = 100.0\nfail_under_branches = 100.0\nrequire_branches = true\n\n[required]\ncrates = [\"radroots_core\"]\n", ); let parsed = parse_toml::<CoveragePolicyFile>(&valid).expect("valid toml"); - assert_eq!(parsed.required.crates, vec!["radroots-core".to_string()]); + assert_eq!(parsed.required.crates, vec!["radroots_core".to_string()]); fs::remove_file(valid).expect("remove valid toml"); } @@ -2600,7 +2600,7 @@ test_threads = 0 let out = temp_dir_path("run_crate_runner"); let args = vec![ "--crate".to_string(), - "radroots-core".to_string(), + "radroots_core".to_string(), "--out".to_string(), out.display().to_string(), "--test-threads".to_string(), @@ -2632,13 +2632,13 @@ test_threads = 0 assert!( rendered_commands .iter() - .filter(|rendered| rendered.contains("report -p radroots-core")) + .filter(|rendered| rendered.contains("report -p radroots_core")) .all(|rendered| rendered.contains("--ignore-filename-regex")) ); assert!( rendered_commands .iter() - .filter(|rendered| rendered.contains("report -p radroots-core")) + .filter(|rendered| rendered.contains("report -p radroots_core")) .all(|rendered| rendered.contains(COVERAGE_EXTERNAL_IGNORE_FILENAME_REGEX)) ); fs::remove_dir_all(out).expect("remove run crate output dir"); @@ -2648,7 +2648,7 @@ test_threads = 0 fn coverage_ignore_filename_regex_excludes_external_and_sibling_workspace_paths() { let root = workspace_root(); let ignore_regex = - coverage_ignore_filename_regex(&root, "radroots-core").expect("build ignore regex"); + coverage_ignore_filename_regex(&root, "radroots_core").expect("build ignore regex"); assert!(ignore_regex.contains(COVERAGE_EXTERNAL_IGNORE_FILENAME_REGEX)); assert!(ignore_regex.contains("crates/identity")); assert!(!ignore_regex.contains("crates/core/")); @@ -2808,7 +2808,7 @@ test_threads = 0 #[test] fn run_crate_with_runner_uses_default_output_dir_when_out_is_missing() { - let args = vec!["--crate".to_string(), "radroots-core".to_string()]; + let args = vec!["--crate".to_string(), "radroots_core".to_string()]; let mut output_path_seen = false; let mut runner = |cmd: Command, _: &str| { let rendered = cmd @@ -2835,7 +2835,7 @@ test_threads = 0 let out = temp_dir_path("run_crate_runner_fail"); let args = vec![ "--crate".to_string(), - "radroots-core".to_string(), + "radroots_core".to_string(), "--out".to_string(), out.display().to_string(), ]; @@ -2848,7 +2848,7 @@ test_threads = 0 write_file(&root.join("blocker"), "x"); let args = vec![ "--crate".to_string(), - "radroots-core".to_string(), + "radroots_core".to_string(), "--out".to_string(), root.join("blocker").join("nested").display().to_string(), ]; @@ -2874,7 +2874,7 @@ test_threads = 0 ); write_file( &root.join("crates").join("core").join("Cargo.toml"), - "[package]\nname = \"radroots-core\"\nversion = \"0.1.0-alpha.1\"\nedition = \"2024\"\n", + "[package]\nname = \"radroots_core\"\nversion = \"0.1.0-alpha.1\"\nedition = \"2024\"\n", ); }; @@ -2889,7 +2889,7 @@ test_threads = 0 ); let profile_args = vec![ "--crate".to_string(), - "radroots-core".to_string(), + "radroots_core".to_string(), "--out".to_string(), profile_root.join("out").display().to_string(), ]; @@ -2904,7 +2904,7 @@ test_threads = 0 write_minimal_workspace(&thread_root); let thread_args = vec![ "--crate".to_string(), - "radroots-core".to_string(), + "radroots_core".to_string(), "--out".to_string(), thread_root.join("out").display().to_string(), "--test-threads".to_string(), @@ -2921,7 +2921,7 @@ test_threads = 0 write_minimal_workspace(&step_root); let step_args = vec![ "--crate".to_string(), - "radroots-core".to_string(), + "radroots_core".to_string(), "--out".to_string(), step_root.join("out").display().to_string(), ]; @@ -3358,10 +3358,10 @@ test_threads = 0 ); write_file( &root.join("crates").join("core").join("Cargo.toml"), - "[package]\nname = \"radroots-core\"\nversion = \"0.1.0\"\nedition = \"2024\"\n", + "[package]\nname = \"radroots_core\"\nversion = \"0.1.0\"\nedition = \"2024\"\n", ); - let err = coverage_ignore_filename_regex(&root, "radroots-missing") + let err = coverage_ignore_filename_regex(&root, "radroots_missing") .expect_err("unknown crate should fail"); assert!(err.contains("could not resolve crate directory")); @@ -3371,12 +3371,12 @@ test_threads = 0 #[test] fn coverage_ignore_filename_regex_reports_workspace_manifest_errors() { let root = temp_dir_path("coverage_regex_workspace_error_root"); - let read_err = coverage_ignore_filename_regex(&root, "radroots-core") + let read_err = coverage_ignore_filename_regex(&root, "radroots_core") .expect_err("missing workspace manifest should fail"); assert!(read_err.contains("failed to read")); write_file(&root.join("Cargo.toml"), "[workspace"); - let parse_err = coverage_ignore_filename_regex(&root, "radroots-core") + let parse_err = coverage_ignore_filename_regex(&root, "radroots_core") .expect_err("invalid workspace manifest should fail"); assert!(parse_err.contains("failed to parse")); @@ -3392,11 +3392,11 @@ test_threads = 0 ); write_file( &root.join("crates").join("other").join("Cargo.toml"), - "[package]\nname = \"radroots-other\"\nversion = \"0.1.0\"\nedition = \"2024\"\n", + "[package]\nname = \"radroots_other\"\nversion = \"0.1.0\"\nedition = \"2024\"\n", ); let args = vec![ "--crate".to_string(), - "radroots-core".to_string(), + "radroots_core".to_string(), "--out".to_string(), root.join("target").join("coverage").display().to_string(), ]; @@ -3442,18 +3442,18 @@ test_threads = 0 let mut output = Vec::new(); write_crate_names_output( &mut output, - vec!["radroots-a".to_string(), "radroots-b".to_string()], + vec!["radroots_a".to_string(), "radroots_b".to_string()], "required crates", ) .expect("write crate names"); let rendered = String::from_utf8(output).expect("utf8"); - assert!(rendered.contains("radroots-a")); - assert!(rendered.contains("radroots-b")); + assert!(rendered.contains("radroots_a")); + assert!(rendered.contains("radroots_b")); let mut failing = FailingWriter; let err = write_crate_names_output( &mut failing, - vec!["radroots-a".to_string()], + vec!["radroots_a".to_string()], "workspace crates", ) .expect_err("writer failure"); diff --git a/crates/xtask/src/export_ts.rs b/crates/xtask/src/export_ts.rs @@ -27,6 +27,20 @@ fn to_package_dir(base: &Path, package_name: &str) -> PathBuf { base.join(stripped) } +fn package_dir_name(package_name: &str) -> &str { + package_name + .strip_prefix("@radroots/") + .unwrap_or(package_name) +} + +fn crate_dir_name(crate_name: &str) -> &str { + crate_name.strip_prefix("radroots_").unwrap_or(crate_name) +} + +fn is_wasm_crate(crate_name: &str) -> bool { + crate_name.ends_with("_wasm") +} + fn ts_export_mapping( bundle: &contract::ContractBundle, ) -> Result<&contract::ExportMapping, String> { @@ -62,8 +76,8 @@ fn selected_package_entries<'a>( { return Ok(vec![entry]); } - if !selector.starts_with("radroots-") { - let crate_candidate = format!("radroots-{selector}"); + if !selector.starts_with("radroots_") { + let crate_candidate = format!("radroots_{selector}"); if let Some(entry) = mapping.packages.get_key_value(&crate_candidate) { return Ok(vec![entry]); } @@ -261,8 +275,8 @@ fn export_ts_models_with_selector( let mut expected = 0usize; let mut copied = 0usize; for (crate_name, package_name) in selected_entries { - let crate_dir = crate_name.strip_prefix("radroots-").unwrap_or(crate_name); - if crate_name.ends_with("-wasm") { + let crate_dir = crate_dir_name(crate_name); + if is_wasm_crate(crate_name) { continue; } if !crate_supports_ts_rs(workspace_root, crate_dir) @@ -271,7 +285,9 @@ fn export_ts_models_with_selector( continue; } expected += 1; - let src = source_root.join(crate_dir).join("types.ts"); + let src = source_root + .join(package_dir_name(package_name)) + .join("types.ts"); let dst = to_package_dir(&ts_out_root, package_name) .join(models_dir) .join("types.ts"); @@ -318,9 +334,13 @@ fn export_ts_constants_with_selector( } let ts_out_root = out_dir.join("ts").join("packages"); for (crate_name, package_name) in selected_entries { - let crate_dir = crate_name.strip_prefix("radroots-").unwrap_or(crate_name); + if is_wasm_crate(crate_name) { + continue; + } for filename in ["constants.ts", "kinds.ts"] { - let src = source_root.join(crate_dir).join(filename); + let src = source_root + .join(package_dir_name(package_name)) + .join(filename); let dst = to_package_dir(&ts_out_root, package_name) .join(constants_dir) .join(filename); @@ -357,10 +377,10 @@ fn export_ts_wasm_artifacts_with_selector( let ts_out_root = out_dir.join("ts").join("packages"); let mut copied = 0usize; for (crate_name, package_name) in selected_entries { - if !crate_name.ends_with("-wasm") { + if !is_wasm_crate(crate_name) { continue; } - let crate_dir = crate_name.strip_prefix("radroots-").unwrap_or(crate_name); + let crate_dir = crate_dir_name(crate_name); let source_root = workspace_root .join("crates") .join(crate_dir) @@ -437,10 +457,10 @@ fn generate_ts_rs_sources_with_selector( let mut expected = 0usize; let mut supports_ts_rs = BTreeMap::new(); for (crate_name, _) in &selected_entries { - if crate_name.ends_with("-wasm") { + if is_wasm_crate(crate_name) { continue; } - let crate_dir = crate_name.strip_prefix("radroots-").unwrap_or(crate_name); + let crate_dir = crate_dir_name(crate_name); let supports = crate_supports_ts_rs(workspace_root, crate_dir) .expect("validated workspace crate manifests are readable"); supports_ts_rs.insert(crate_name.as_str(), supports); @@ -452,7 +472,7 @@ fn generate_ts_rs_sources_with_selector( return Ok(source_root); } for (crate_name, package_name) in selected_entries { - if crate_name.ends_with("-wasm") { + if is_wasm_crate(crate_name) { continue; } if !supports_ts_rs @@ -572,7 +592,7 @@ resolver = "2" &root.join("crates").join("a").join("Cargo.toml"), &format!( r#"[package] -name = "radroots-a" +name = "radroots_a" version = "0.1.0" edition = "2024" description = "crate a" @@ -591,7 +611,7 @@ readme = "README.md" write_file( &root.join("crates").join("b").join("Cargo.toml"), r#"[package] -name = "radroots-b" +name = "radroots_b" version = "0.1.0" edition = "2024" publish = false @@ -613,14 +633,14 @@ publish = false write_file( &root.join("contract").join("manifest.toml"), r#"[contract] -name = "radroots-contract" +name = "radroots_contract" version = "1.0.0" source = "synthetic" [surface] -model_crates = ["radroots-a"] -algorithm_crates = ["radroots-b"] -wasm_crates = ["radroots-a-wasm"] +model_crates = ["radroots_a"] +algorithm_crates = ["radroots_b"] +wasm_crates = ["radroots_a_wasm"] [policy] exclude_internal_workspace_crates = true @@ -652,7 +672,7 @@ id = "ts" repository = "sdk-typescript" [packages] -"radroots-a" = "@radroots/a" +"radroots_a" = "@radroots/a" [artifacts] models_dir = "src/generated" @@ -671,7 +691,7 @@ fail_under_branches = 100.0 require_branches = true [required] -crates = ["radroots-a", "radroots-b"] +crates = ["radroots_a", "radroots_b"] "#, ); write_file( @@ -683,13 +703,13 @@ crates = ["radroots-a", "radroots-b"] version = "1.0.0" [publish] -crates = ["radroots-a"] +crates = ["radroots_a"] [internal] -crates = ["radroots-b"] +crates = ["radroots_b"] [publish_order] -crates = ["radroots-a"] +crates = ["radroots_a"] "#, ); write_file( @@ -697,7 +717,7 @@ crates = ["radroots-a"] .join("target") .join("coverage") .join("coverage-refresh.tsv"), - "crate\tstatus\texec\tfunc\tbranch\tregion\treport\nradroots-a\tpass\t100.0\t100.0\t100.0\t100.0\tfile\nradroots-b\tpass\t100.0\t100.0\t100.0\t100.0\tfile\n", + "crate\tstatus\texec\tfunc\tbranch\tregion\treport\nradroots_a\tpass\t100.0\t100.0\t100.0\t100.0\tfile\nradroots_b\tpass\t100.0\t100.0\t100.0\t100.0\tfile\n", ); root } @@ -735,9 +755,9 @@ mod tests { fn test_ts_mapping() -> contract::ExportMapping { let mut packages = BTreeMap::new(); - packages.insert("radroots-core".to_string(), "@radroots/core".to_string()); + packages.insert("radroots_core".to_string(), "@radroots/core".to_string()); packages.insert( - "radroots-events-codec-wasm".to_string(), + "radroots_events_codec_wasm".to_string(), "@radroots/events-codec-wasm".to_string(), ); contract::ExportMapping { @@ -762,9 +782,9 @@ mod tests { let all = selected_package_entries(&mapping, None).expect("select all"); assert_eq!(all.len(), 2); - let by_crate = selected_package_entries(&mapping, Some("radroots-core")).expect("by crate"); + let by_crate = selected_package_entries(&mapping, Some("radroots_core")).expect("by crate"); assert_eq!(by_crate.len(), 1); - assert_eq!(by_crate[0].0.as_str(), "radroots-core"); + assert_eq!(by_crate[0].0.as_str(), "radroots_core"); let by_short = selected_package_entries(&mapping, Some("core")).expect("by short crate"); assert_eq!(by_short.len(), 1); @@ -773,11 +793,11 @@ mod tests { let by_package = selected_package_entries(&mapping, Some("@radroots/core")).expect("by package"); assert_eq!(by_package.len(), 1); - assert_eq!(by_package[0].0.as_str(), "radroots-core"); + assert_eq!(by_package[0].0.as_str(), "radroots_core"); let wasm = selected_package_entries(&mapping, Some("events-codec-wasm")).expect("wasm"); assert_eq!(wasm.len(), 1); - assert_eq!(wasm[0].0.as_str(), "radroots-events-codec-wasm"); + assert_eq!(wasm[0].0.as_str(), "radroots_events_codec_wasm"); } #[test] @@ -790,7 +810,7 @@ mod tests { #[test] fn selected_package_entries_handle_prefixed_unknown_selectors() { let mapping = test_ts_mapping(); - let by_crate = selected_package_entries(&mapping, Some("radroots-missing")) + let by_crate = selected_package_entries(&mapping, Some("radroots_missing")) .expect_err("unknown prefixed crate selector"); assert!(by_crate.contains("unknown ts export crate selector")); let by_package = selected_package_entries(&mapping, Some("@radroots/missing")) @@ -811,8 +831,8 @@ mod tests { source: "source".to_string(), }, surface: contract::Surface { - model_crates: vec!["radroots-a".to_string()], - algorithm_crates: vec!["radroots-b".to_string()], + model_crates: vec!["radroots_a".to_string()], + algorithm_crates: vec!["radroots_b".to_string()], wasm_crates: vec![], }, policy: contract::Policy { @@ -843,7 +863,7 @@ mod tests { assert!(mapping_err.contains("missing ts export mapping")); let mut packages = BTreeMap::new(); - packages.insert("radroots-a".to_string(), "@radroots/a".to_string()); + packages.insert("radroots_a".to_string(), "@radroots/a".to_string()); let no_artifacts = contract::ExportMapping { language: contract::ExportLanguage { id: "ts".to_string(), @@ -861,7 +881,7 @@ mod tests { fn selected_package_entries_supports_package_candidate_lookup() { let mut packages = BTreeMap::new(); packages.insert( - "radroots-special".to_string(), + "radroots_special".to_string(), "@radroots/special-pkg".to_string(), ); let mapping = contract::ExportMapping { @@ -875,7 +895,7 @@ mod tests { let selected = selected_package_entries(&mapping, Some("special-pkg")).expect("package candidate"); assert_eq!(selected.len(), 1); - assert_eq!(selected[0].0.as_str(), "radroots-special"); + assert_eq!(selected[0].0.as_str(), "radroots_special"); assert_eq!(selected[0].1.as_str(), "@radroots/special-pkg"); } @@ -1155,8 +1175,8 @@ id = "ts" repository = "sdk-typescript" [packages] -"radroots-a" = "@radroots/a" -"radroots-a-wasm" = "@radroots/a-wasm" +"radroots_a" = "@radroots/a" +"radroots_a_wasm" = "@radroots/a-wasm" [artifacts] models_dir = "src/generated" @@ -1168,10 +1188,10 @@ manifest_file = "export-manifest.json" write_file( &root .join("crates") - .join("a-wasm") + .join("a_wasm") .join("pkg") .join("dist") - .join("radroots-a-wasm.js"), + .join("radroots_a_wasm.js"), "export const probe = true;\n", ); let out_dir = root.join("out"); @@ -1184,7 +1204,7 @@ manifest_file = "export-manifest.json" .join("packages") .join("a-wasm") .join("dist") - .join("radroots-a-wasm.js") + .join("radroots_a_wasm.js") .exists() ); @@ -1279,7 +1299,7 @@ id = "ts" repository = "sdk-typescript" [packages] -"radroots-a" = "@radroots/a" +"radroots_a" = "@radroots/a" [artifacts] models_dir = "src/generated" @@ -1305,7 +1325,7 @@ id = "ts" repository = "sdk-typescript" [packages] -"radroots-a" = "@radroots/a" +"radroots_a" = "@radroots/a" [artifacts] models_dir = "src/generated" @@ -1375,7 +1395,7 @@ manifest_file = "nested/export-manifest.json" ); let command_fail_err = generate_ts_rs_sources(&root_command_fail) .expect_err("cargo test failure should surface"); - assert!(command_fail_err.contains("cargo test failed for radroots-a")); + assert!(command_fail_err.contains("cargo test failed for radroots_a")); assert!(command_fail_err.contains("unclosed delimiter")); let _ = fs::remove_dir_all(root_command_fail); } @@ -1391,8 +1411,8 @@ id = "ts" repository = "sdk-typescript" [packages] -"radroots-a" = "@radroots/a" -"radroots-b" = "@radroots/b" +"radroots_a" = "@radroots/a" +"radroots_b" = "@radroots/b" [artifacts] models_dir = "src/generated" @@ -1422,8 +1442,8 @@ id = "ts" repository = "sdk-typescript" [packages] -"radroots-a" = "@radroots/a" -"radroots-b" = "@radroots/b" +"radroots_a" = "@radroots/a" +"radroots_b" = "@radroots/b" [artifacts] models_dir = "src/generated" @@ -1469,7 +1489,7 @@ manifest_file = "export-manifest.json" let single_out = root.join("single-out"); fs::create_dir_all(&single_out).expect("create single out"); let single_manifest = - export_ts_bundle_for_crate(&root, &single_out, "radroots-a").expect("single bundle"); + export_ts_bundle_for_crate(&root, &single_out, "radroots_a").expect("single bundle"); assert!(single_manifest.exists()); assert!( single_out @@ -1511,7 +1531,7 @@ id = "ts" repository = "sdk-typescript" [packages] -"radroots-a" = "@radroots/a" +"radroots_a" = "@radroots/a" [artifacts] models_dir = "src/generated" @@ -1536,8 +1556,8 @@ id = "ts" repository = "sdk-typescript" [packages] -"radroots-a" = "@radroots/a" -"radroots-a-wasm" = "@radroots/a-wasm" +"radroots_a" = "@radroots/a" +"radroots_a_wasm" = "@radroots/a-wasm" [artifacts] models_dir = "src/generated" @@ -1550,7 +1570,7 @@ manifest_file = "export-manifest.json" write_file( &bundle_wasm .join("crates") - .join("a-wasm") + .join("a_wasm") .join("pkg") .join("dist") .join("nested") @@ -1580,7 +1600,7 @@ manifest_file = "export-manifest.json" "blocker", ); let crate_models_err = - export_ts_bundle_for_crate(&crate_models, &crate_models_out, "radroots-a") + export_ts_bundle_for_crate(&crate_models, &crate_models_out, "radroots_a") .expect_err("crate models fail"); assert!(crate_models_err.contains("create")); let _ = fs::remove_dir_all(&crate_models); @@ -1596,7 +1616,7 @@ id = "ts" repository = "sdk-typescript" [packages] -"radroots-a" = "@radroots/a" +"radroots_a" = "@radroots/a" [artifacts] models_dir = "src/generated" @@ -1609,7 +1629,7 @@ manifest_file = "export-manifest.json" let crate_constants_out = crate_constants.join("out"); fs::create_dir_all(&crate_constants_out).expect("create crate constants out"); let crate_constants_err = - export_ts_bundle_for_crate(&crate_constants, &crate_constants_out, "radroots-a") + export_ts_bundle_for_crate(&crate_constants, &crate_constants_out, "radroots_a") .expect_err("crate constants fail"); assert!(crate_constants_err.contains("create")); let _ = fs::remove_dir_all(&crate_constants); @@ -1622,7 +1642,7 @@ id = "ts" repository = "sdk-typescript" [packages] -"radroots-a-wasm" = "@radroots/a-wasm" +"radroots_a_wasm" = "@radroots/a-wasm" [artifacts] models_dir = "src/generated" @@ -1634,7 +1654,7 @@ manifest_file = "export-manifest.json" write_file( &crate_wasm .join("crates") - .join("a-wasm") + .join("a_wasm") .join("pkg") .join("dist") .join("nested") @@ -1652,7 +1672,7 @@ manifest_file = "export-manifest.json" "blocker", ); let crate_wasm_err = - export_ts_bundle_for_crate(&crate_wasm, &crate_wasm_out, "radroots-a-wasm") + export_ts_bundle_for_crate(&crate_wasm, &crate_wasm_out, "radroots_a_wasm") .expect_err("crate wasm fail"); assert!(crate_wasm_err.contains("create")); let _ = fs::remove_dir_all(&crate_wasm); @@ -1670,7 +1690,7 @@ manifest_file = "export-manifest.json" assert!(write_ts_export_manifest(&missing_root, &missing_out).is_err()); assert!(generate_ts_rs_sources(&missing_root).is_err()); assert!(export_ts_bundle(&missing_root, &missing_out).is_err()); - assert!(export_ts_bundle_for_crate(&missing_root, &missing_out, "radroots-a").is_err()); + assert!(export_ts_bundle_for_crate(&missing_root, &missing_out, "radroots_a").is_err()); let _ = fs::remove_dir_all(&missing_root); let invalid_contract = create_synthetic_workspace("wrapper_invalid_contract", true); @@ -1682,9 +1702,9 @@ version = "1.0.0" source = "synthetic" [surface] -model_crates = ["radroots-a"] -algorithm_crates = ["radroots-b"] -wasm_crates = ["radroots-a-wasm"] +model_crates = ["radroots_a"] +algorithm_crates = ["radroots_b"] +wasm_crates = ["radroots_a_wasm"] [policy] exclude_internal_workspace_crates = true @@ -1712,7 +1732,7 @@ id = "py" repository = "sdk-python" [packages] -"radroots-a" = "radroots-a" +"radroots_a" = "radroots_a" "#, ); let missing_ts_out = missing_ts_export.join("out"); @@ -1763,7 +1783,7 @@ id = "ts" repository = "sdk-typescript" [packages] -"radroots-a" = "@radroots/a" +"radroots_a" = "@radroots/a" "#, ); let missing_artifacts_out = missing_artifacts.join("out"); @@ -1785,7 +1805,7 @@ id = "ts" repository = "sdk-typescript" [packages] -"radroots-a" = "@radroots/a" +"radroots_a" = "@radroots/a" [artifacts] models_dir = "" @@ -1813,7 +1833,7 @@ id = "ts" repository = "sdk-typescript" [packages] -"radroots-a" = "@radroots/a" +"radroots_a" = "@radroots/a" [artifacts] models_dir = "src/generated" @@ -1841,7 +1861,7 @@ id = "ts" repository = "sdk-typescript" [packages] -"radroots-a-wasm" = "@radroots/a-wasm" +"radroots_a_wasm" = "@radroots/a-wasm" [artifacts] models_dir = "src/generated" @@ -1869,7 +1889,7 @@ id = "ts" repository = "sdk-typescript" [packages] -"radroots-a" = "@radroots/a" +"radroots_a" = "@radroots/a" [artifacts] models_dir = "src/generated" @@ -1944,7 +1964,7 @@ id = "ts" repository = "sdk-typescript" [packages] -"radroots-a-wasm" = "@radroots/a-wasm" +"radroots_a_wasm" = "@radroots/a-wasm" [artifacts] models_dir = "src/generated" @@ -1956,7 +1976,7 @@ manifest_file = "export-manifest.json" write_file( &wasm_copy_err_root .join("crates") - .join("a-wasm") + .join("a_wasm") .join("pkg") .join("dist") .join("nested") diff --git a/crates/xtask/src/main.rs b/crates/xtask/src/main.rs @@ -258,7 +258,7 @@ resolver = "2" write_file( &root.join("crates").join("a").join("Cargo.toml"), r#"[package] -name = "radroots-a" +name = "radroots_a" version = "0.1.0" edition = "2024" description = "crate a" @@ -298,7 +298,7 @@ mod tests { write_file( &root.join("crates").join("b").join("Cargo.toml"), r#"[package] -name = "radroots-b" +name = "radroots_b" version = "0.1.0" edition = "2024" publish = false @@ -320,14 +320,14 @@ publish = false write_file( &root.join("contract").join("manifest.toml"), r#"[contract] -name = "radroots-contract" +name = "radroots_contract" version = "1.0.0" source = "synthetic" [surface] -model_crates = ["radroots-a"] -algorithm_crates = ["radroots-b"] -wasm_crates = ["radroots-a-wasm"] +model_crates = ["radroots_a"] +algorithm_crates = ["radroots_b"] +wasm_crates = ["radroots_a_wasm"] [policy] exclude_internal_workspace_crates = true @@ -359,7 +359,7 @@ id = "ts" repository = "sdk-typescript" [packages] -"radroots-a" = "@radroots/a" +"radroots_a" = "@radroots/a" [artifacts] models_dir = "src/generated" @@ -378,7 +378,7 @@ fail_under_branches = 100.0 require_branches = true [required] -crates = ["radroots-a", "radroots-b"] +crates = ["radroots_a", "radroots_b"] "#, ); write_file( @@ -390,13 +390,13 @@ crates = ["radroots-a", "radroots-b"] version = "1.0.0" [publish] -crates = ["radroots-a"] +crates = ["radroots_a"] [internal] -crates = ["radroots-b"] +crates = ["radroots_b"] [publish_order] -crates = ["radroots-a"] +crates = ["radroots_a"] "#, ); root @@ -423,14 +423,14 @@ crates = ["radroots-a"] let parsed = parse_crate_out_dir( &[ "--crate".to_string(), - "radroots-core".to_string(), + "radroots_core".to_string(), "--out".to_string(), "my/out".to_string(), ], &root, ) .expect("parsed crate out"); - assert_eq!(parsed.0, "radroots-core".to_string()); + assert_eq!(parsed.0, "radroots_core".to_string()); assert_eq!(parsed.1, PathBuf::from("my/out")); let missing_crate = parse_crate_out_dir(&["--out".to_string(), "x".to_string()], &root) @@ -440,7 +440,7 @@ crates = ["radroots-a"] let invalid_crate_args = parse_crate_out_dir( &[ "--crate".to_string(), - "radroots-core".to_string(), + "radroots_core".to_string(), "--bad".to_string(), ], &root, @@ -455,7 +455,7 @@ crates = ["radroots-a"] let missing_out_value = parse_crate_out_dir( &[ "--crate".to_string(), - "radroots-core".to_string(), + "radroots_core".to_string(), "--out".to_string(), ], &root, diff --git a/nix/common.nix b/nix/common.nix @@ -99,16 +99,16 @@ let ]; sdkContractCrates = [ "xtask" - "radroots-core" - "radroots-types" - "radroots-events" - "radroots-trade" - "radroots-identity" - "radroots-replica-db-schema" - "radroots-events-codec" - "radroots-events-codec-wasm" - "radroots-nostr-connect" - "radroots-nostr-signer" + "radroots_core" + "radroots_types" + "radroots_events" + "radroots_trade" + "radroots_identity" + "radroots_replica_db_schema" + "radroots_events_codec" + "radroots_events_codec_wasm" + "radroots_nostr_connect" + "radroots_nostr_signer" ]; sdkContractCargoArgs = lib.concatStringsSep " " (map (crate: "-p ${crate}") sdkContractCrates); craneLib = (crane.mkLib pkgs).overrideToolchain toolchains.stable;