sdk

Radroots SDK and bindings
git clone https://radroots.dev/git/sdk.git
Log | Files | Refs | README

commit 59a6d373ca009c152a8afad482cd7de0e4d5cf1e
parent 3e67c7d9d5f0b12cbc5b8ab0c2ad36c25ed7caa7
Author: triesap <tyson@radroots.org>
Date:   Mon, 27 Jul 2026 10:20:13 +0000

workspace: align public package layout and names

- rename the advanced SDK Cargo package while preserving its Rust crate path
- retain the new radroots facade as the second governed SDK package
- enforce the exact local public package inventory in architecture validation
- refresh package documentation and the lockfile without dependency upgrades

Diffstat:
MCargo.lock | 362++++++++++++++++++++++++++++++++++++++++----------------------------------------
MCargo.toml | 21+++++++++++----------
MREADME | 2+-
Mcontracts/releases/publication.toml | 2+-
Mcrates/sdk/Cargo.toml | 12+++++++++---
Dcrates/sdk/README | 135-------------------------------------------------------------------------------
Acrates/sdk/README.md | 135+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtools/xtask/src/architecture.rs | 32++++++++++++++++++++++++++++++--
8 files changed, 368 insertions(+), 333 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -1874,16 +1874,8 @@ name = "radroots" version = "0.1.0" [[package]] -name = "radroots_authority" -version = "1.0.0-alpha.1" -dependencies = [ - "radroots_event", - "radroots_nostr", -] - -[[package]] -name = "radroots_blossom" -version = "1.0.0-alpha.1" +name = "radroots-blossom" +version = "0.1.0" dependencies = [ "mediatype", "serde", @@ -1893,8 +1885,8 @@ dependencies = [ ] [[package]] -name = "radroots_core" -version = "1.0.0-alpha.1" +name = "radroots-core" +version = "0.1.0" dependencies = [ "dto_bindgen", "rust_decimal", @@ -1903,21 +1895,14 @@ dependencies = [ ] [[package]] -name = "radroots_core_bindings" +name = "radroots-event" version = "0.1.0" dependencies = [ - "radroots_core", -] - -[[package]] -name = "radroots_event" -version = "1.0.0-alpha.1" -dependencies = [ "dto_bindgen", "hex", "jiff-tzdb", - "radroots_blossom", - "radroots_core", + "radroots-blossom", + "radroots-core", "secp256k1", "serde", "serde_json", @@ -1927,25 +1912,168 @@ dependencies = [ ] [[package]] -name = "radroots_event_bindings" +name = "radroots-event-codec" version = "0.1.0" dependencies = [ - "dto_bindgen_backend_ts", - "radroots_event", + "hex", + "nostr", + "radroots-blossom", + "radroots-core", + "radroots-event", + "serde", + "serde_json", + "sha2", ] [[package]] -name = "radroots_event_codec" -version = "1.0.0-alpha.1" +name = "radroots-identity" +version = "0.1.0" +dependencies = [ + "nostr", + "radroots_protected_store", + "radroots_runtime_paths", + "radroots_secret_vault", + "serde", + "serde_json", + "thiserror 1.0.69", + "tracing", +] + +[[package]] +name = "radroots-nostr" +version = "0.1.0" +dependencies = [ + "nostr", + "nostr-sdk", + "radroots-event", + "radroots-event-codec", + "radroots-identity", + "serde", + "serde_json", + "thiserror 1.0.69", +] + +[[package]] +name = "radroots-nostr-connect" +version = "0.1.0" dependencies = [ + "nostr", + "serde", + "serde_json", + "thiserror 1.0.69", + "url", +] + +[[package]] +name = "radroots-sdk" +version = "0.1.0" +dependencies = [ + "base64 0.22.1", + "futures", "hex", "nostr", - "radroots_blossom", - "radroots_core", - "radroots_event", + "radroots-core", + "radroots-event", + "radroots-event-codec", + "radroots-identity", + "radroots-nostr", + "radroots-nostr-connect", + "radroots-trade", + "radroots-transport", + "radroots-transport-nostr", + "radroots_authority", + "radroots_event_store", + "radroots_geocoder", + "radroots_nostr_signer", + "radroots_outbox", + "radroots_protected_store", + "radroots_replica_schema", + "radroots_replica_store", + "radroots_replica_sync", + "radroots_runtime_contract_v1", + "radroots_runtime_paths", + "radroots_secret_vault", + "radroots_sql_core", + "radroots_transport_publish_protocol", + "radroots_transport_reticulum", + "reqwest", + "serde", + "serde_json", + "sha2", + "sqlx", + "tempfile", + "tokio", + "tokio-tungstenite", + "uuid", +] + +[[package]] +name = "radroots-trade" +version = "0.1.0" +dependencies = [ + "base64 0.22.1", + "dto_bindgen", + "dto_bindgen_core", + "hex", + "radroots-core", + "radroots-event", + "radroots-event-codec", + "radroots_authority", + "radroots_event_store", "serde", "serde_json", "sha2", + "sqlx", + "thiserror 1.0.69", +] + +[[package]] +name = "radroots-transport" +version = "0.1.0" +dependencies = [ + "serde", + "sha2", +] + +[[package]] +name = "radroots-transport-nostr" +version = "0.1.0" +dependencies = [ + "futures", + "nostr", + "radroots-event", + "radroots-nostr", + "radroots-transport", + "radroots_event_store", + "radroots_outbox", + "serde", + "serde_json", + "thiserror 1.0.69", + "tokio", + "url", +] + +[[package]] +name = "radroots_authority" +version = "1.0.0-alpha.1" +dependencies = [ + "radroots-event", + "radroots-nostr", +] + +[[package]] +name = "radroots_core_bindings" +version = "0.1.0" +dependencies = [ + "radroots-core", +] + +[[package]] +name = "radroots_event_bindings" +version = "0.1.0" +dependencies = [ + "dto_bindgen_backend_ts", + "radroots-event", ] [[package]] @@ -1953,10 +2081,10 @@ name = "radroots_event_codec_wasm" version = "0.1.0-alpha.2" dependencies = [ "nostr", - "radroots_blossom", - "radroots_core", - "radroots_event", - "radroots_event_codec", + "radroots-blossom", + "radroots-core", + "radroots-event", + "radroots-event-codec", "serde", "serde_json", "wasm-bindgen", @@ -1984,10 +2112,10 @@ version = "1.0.0-alpha.1" dependencies = [ "getrandom 0.2.17", "hex", - "radroots_blossom", - "radroots_event", - "radroots_event_codec", - "radroots_transport", + "radroots-blossom", + "radroots-event", + "radroots-event-codec", + "radroots-transport", "serde", "serde_json", "sha2", @@ -2013,25 +2141,11 @@ dependencies = [ ] [[package]] -name = "radroots_identity" -version = "1.0.0-alpha.1" -dependencies = [ - "nostr", - "radroots_protected_store", - "radroots_runtime_paths", - "radroots_secret_vault", - "serde", - "serde_json", - "thiserror 1.0.69", - "tracing", -] - -[[package]] name = "radroots_identity_bindings" version = "0.1.0" dependencies = [ "dto_bindgen_backend_ts", - "radroots_identity", + "radroots-identity", ] [[package]] @@ -2047,39 +2161,14 @@ dependencies = [ ] [[package]] -name = "radroots_nostr" -version = "1.0.0-alpha.1" -dependencies = [ - "nostr", - "nostr-sdk", - "radroots_event", - "radroots_event_codec", - "radroots_identity", - "serde", - "serde_json", - "thiserror 1.0.69", -] - -[[package]] -name = "radroots_nostr_connect" -version = "1.0.0-alpha.1" -dependencies = [ - "nostr", - "serde", - "serde_json", - "thiserror 1.0.69", - "url", -] - -[[package]] name = "radroots_nostr_signer" version = "1.0.0-alpha.1" dependencies = [ "hex", "nostr", - "radroots_identity", - "radroots_nostr", - "radroots_nostr_connect", + "radroots-identity", + "radroots-nostr", + "radroots-nostr-connect", "radroots_runtime", "serde", "serde_json", @@ -2094,9 +2183,9 @@ name = "radroots_outbox" version = "1.0.0-alpha.1" dependencies = [ "hex", - "radroots_event", + "radroots-event", + "radroots-transport", "radroots_event_store", - "radroots_transport", "serde", "serde_json", "sha2", @@ -2175,9 +2264,9 @@ version = "1.0.0-alpha.1" dependencies = [ "base64 0.22.1", "hex", - "radroots_core", - "radroots_event", - "radroots_event_codec", + "radroots-core", + "radroots-event", + "radroots-event-codec", "radroots_replica_schema", "radroots_replica_store", "radroots_sql_core", @@ -2192,7 +2281,7 @@ name = "radroots_replica_sync_wasm" version = "0.1.0-alpha.2" dependencies = [ "base64 0.22.1", - "radroots_event", + "radroots-event", "radroots_replica_sync", "radroots_sdk_sql_wasm_runtime", "serde", @@ -2241,49 +2330,6 @@ dependencies = [ ] [[package]] -name = "radroots_sdk" -version = "0.1.0" -dependencies = [ - "base64 0.22.1", - "futures", - "hex", - "nostr", - "radroots_authority", - "radroots_core", - "radroots_event", - "radroots_event_codec", - "radroots_event_store", - "radroots_geocoder", - "radroots_identity", - "radroots_nostr", - "radroots_nostr_connect", - "radroots_nostr_signer", - "radroots_outbox", - "radroots_protected_store", - "radroots_replica_schema", - "radroots_replica_store", - "radroots_replica_sync", - "radroots_runtime_contract_v1", - "radroots_runtime_paths", - "radroots_secret_vault", - "radroots_sql_core", - "radroots_trade", - "radroots_transport", - "radroots_transport_nostr", - "radroots_transport_publish_protocol", - "radroots_transport_reticulum", - "reqwest", - "serde", - "serde_json", - "sha2", - "sqlx", - "tempfile", - "tokio", - "tokio-tungstenite", - "uuid", -] - -[[package]] name = "radroots_sdk_sql_wasm_runtime" version = "0.1.0-alpha.2" dependencies = [ @@ -2301,8 +2347,8 @@ dependencies = [ "dto_bindgen_backend_ts", "dto_bindgen_core", "flate2", - "radroots_core", - "radroots_event", + "radroots-core", + "radroots-event", "radroots_event_bindings", "radroots_event_index", "radroots_identity_bindings", @@ -2333,65 +2379,19 @@ dependencies = [ ] [[package]] -name = "radroots_trade" -version = "1.0.0-alpha.1" -dependencies = [ - "base64 0.22.1", - "dto_bindgen", - "dto_bindgen_core", - "hex", - "radroots_authority", - "radroots_core", - "radroots_event", - "radroots_event_codec", - "radroots_event_store", - "serde", - "serde_json", - "sha2", - "sqlx", - "thiserror 1.0.69", -] - -[[package]] name = "radroots_trade_bindings" version = "0.1.0" dependencies = [ "dto_bindgen", "dto_bindgen_core", - "radroots_trade", -] - -[[package]] -name = "radroots_transport" -version = "1.0.0-alpha.1" -dependencies = [ - "serde", - "sha2", -] - -[[package]] -name = "radroots_transport_nostr" -version = "1.0.0-alpha.1" -dependencies = [ - "futures", - "nostr", - "radroots_event", - "radroots_event_store", - "radroots_nostr", - "radroots_outbox", - "radroots_transport", - "serde", - "serde_json", - "thiserror 1.0.69", - "tokio", - "url", + "radroots-trade", ] [[package]] name = "radroots_transport_publish_protocol" version = "1.0.0-alpha.1" dependencies = [ - "radroots_transport", + "radroots-transport", "serde", ] @@ -2399,7 +2399,7 @@ dependencies = [ name = "radroots_transport_reticulum" version = "1.0.0-alpha.1" dependencies = [ - "radroots_transport", + "radroots-transport", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml @@ -34,6 +34,7 @@ readme = "README.md" [workspace.lints.rust] unsafe_code = "forbid" +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(coverage_nightly)'] } [workspace.lints.rustdoc] broken_intra_doc_links = "deny" @@ -48,27 +49,27 @@ dto_bindgen = { version = "0.1.0" } dto_bindgen_backend_ts = { version = "0.1.0" } dto_bindgen_core = { version = "0.1.0" } radroots_authority = { path = "../lib/crates/authority", version = "=1.0.0-alpha.1", default-features = false } -radroots_blossom = { path = "../lib/crates/blossom", version = "=1.0.0-alpha.1", default-features = false } -radroots_core = { path = "../lib/crates/core", version = "=1.0.0-alpha.1", default-features = false } +radroots_blossom = { package = "radroots-blossom", path = "../lib/crates/blossom", version = "=0.1.0", default-features = false } +radroots_core = { package = "radroots-core", path = "../lib/crates/core", version = "=0.1.0", default-features = false } radroots_event_store = { path = "../lib/crates/event_store", version = "=1.0.0-alpha.1", default-features = false } -radroots_event = { path = "../lib/crates/event", version = "=1.0.0-alpha.1", default-features = false } -radroots_event_codec = { path = "../lib/crates/event_codec", version = "=1.0.0-alpha.1", default-features = false } +radroots_event = { package = "radroots-event", path = "../lib/crates/event", version = "=0.1.0", default-features = false } +radroots_event_codec = { package = "radroots-event-codec", path = "../lib/crates/event_codec", version = "=0.1.0", default-features = false } radroots_event_index = { path = "../lib/crates/event_index", version = "=1.0.0-alpha.1", default-features = false } radroots_geocoder = { path = "../lib/crates/geocoder", version = "=1.0.0-alpha.1" } -radroots_identity = { path = "../lib/crates/identity", version = "=1.0.0-alpha.1", default-features = false, features = [ +radroots_identity = { package = "radroots-identity", path = "../lib/crates/identity", version = "=0.1.0", default-features = false, features = [ "std", ] } -radroots_nostr = { path = "../lib/crates/nostr", version = "=1.0.0-alpha.1", default-features = false } -radroots_nostr_connect = { path = "../lib/crates/nostr_connect", version = "=1.0.0-alpha.1", default-features = false } +radroots_nostr = { package = "radroots-nostr", path = "../lib/crates/nostr", version = "=0.1.0", default-features = false } +radroots_nostr_connect = { package = "radroots-nostr-connect", path = "../lib/crates/nostr_connect", version = "=0.1.0", default-features = false } radroots_nostr_signer = { path = "../lib/crates/nostr_signer", version = "=1.0.0-alpha.1", default-features = false } radroots_outbox = { path = "../lib/crates/outbox", version = "=1.0.0-alpha.1", default-features = false } radroots_protocol_contract_v1 = { path = "../lib/crates/protocol_contract_v1", version = "=1.0.0-alpha.1", default-features = false } radroots_protected_store = { path = "../lib/crates/protected_store", version = "=1.0.0-alpha.1", default-features = false } radroots_runtime_contract_v1 = { path = "crates/runtime_contract_v1", version = "0.1.0", default-features = false } radroots_secret_vault = { path = "../lib/crates/secret_vault", version = "=1.0.0-alpha.1", default-features = false } -radroots_transport = { path = "../lib/crates/transport", version = "=1.0.0-alpha.1", default-features = false } +radroots_transport = { package = "radroots-transport", path = "../lib/crates/transport", version = "=0.1.0", default-features = false } radroots_transport_publish_protocol = { path = "../lib/crates/transport_publish_protocol", version = "=1.0.0-alpha.1", default-features = false } -radroots_transport_nostr = { path = "../lib/crates/transport_nostr", version = "=1.0.0-alpha.1", default-features = false } +radroots_transport_nostr = { package = "radroots-transport-nostr", path = "../lib/crates/transport_nostr", version = "=0.1.0", default-features = false } radroots_transport_reticulum = { path = "../lib/crates/transport_reticulum", version = "=1.0.0-alpha.1", default-features = false } radroots_replica_store = { path = "../lib/crates/replica_store", version = "=1.0.0-alpha.1", default-features = false } radroots_replica_schema = { path = "../lib/crates/replica_schema", version = "=1.0.0-alpha.1", default-features = false } @@ -76,7 +77,7 @@ radroots_replica_sync = { path = "../lib/crates/replica_sync", version = "=1.0.0 radroots_runtime_paths = { path = "../lib/crates/runtime_paths", version = "=1.0.0-alpha.1", default-features = false } radroots_sdk_sql_wasm_runtime = { path = "crates/sql_wasm_runtime", version = "0.1.0-alpha.2" } radroots_sql_core = { path = "../lib/crates/sql_core", version = "=1.0.0-alpha.1", default-features = false } -radroots_trade = { path = "../lib/crates/trade", version = "=1.0.0-alpha.1", default-features = false, features = [ +radroots_trade = { package = "radroots-trade", path = "../lib/crates/trade", version = "=0.1.0", default-features = false, features = [ "serde_json", "std", ] } diff --git a/README b/README @@ -1,4 +1,4 @@ -# radroots_sdk +# radroots-sdk This is the README for `sdk` which contains the Rad Roots SDK, including Rust runtime APIs, generated bindings, FFI layers, and package surfaces. diff --git a/contracts/releases/publication.toml b/contracts/releases/publication.toml @@ -46,7 +46,7 @@ external_packages = [ ] [workspace_classification] -private = ["radroots_runtime_contract_v1", "radroots_sdk"] +private = ["radroots_runtime_contract_v1"] build_codegen = [ "radroots_core_bindings", "radroots_event_bindings", diff --git a/crates/sdk/Cargo.toml b/crates/sdk/Cargo.toml @@ -1,15 +1,18 @@ [package] -name = "radroots_sdk" +name = "radroots-sdk" publish = false version.workspace = true edition.workspace = true -authors = ["Tyson Lupul <tyson@radroots.org>"] +authors.workspace = true rust-version.workspace = true license.workspace = true description = "Public Rust SDK for Radroots" repository.workspace = true homepage.workspace = true -readme = "README" +readme = "README.md" + +[lib] +name = "radroots_sdk" [features] default = ["std", "serde", "serde_json", "identity-models"] @@ -223,3 +226,6 @@ radroots_nostr = { workspace = true, default-features = false, features = [ tempfile = { workspace = true } tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } tokio-tungstenite = "0.26.2" + +[lints] +workspace = true diff --git a/crates/sdk/README b/crates/sdk/README @@ -1,135 +0,0 @@ -# radroots_sdk - -Curated Radroots Rust SDK for local-first Radroots product workflows. - -The SDK v1 product runtime is centered on `RadrootsClient::builder()`, -`sdk.farms()`, `sdk.listings()`, `sdk.trades()`, `sdk.market()`, and `sdk.sync()`. - -`RadrootsClient::builder()` defaults to memory storage, the system clock, the `LocalOnly` transport -profile, and no production network publishing. Directory storage is opt-in and creates -`runtime.sqlite`, `private.sqlite`, and `studio.sqlite` in the selected directory. Configured Nostr -relay URLs live inside `TransportProfile::Nostr` or the Nostr side of `TransportProfile::MultiTarget`, -and product enqueue requests choose the active profile through `TargetPolicy::default_profile()`. - -When `signer-adapters` is enabled, `RadrootsClient::builder()` accepts a configured -`RadrootsSdkSignerProvider`. The production signing modes are `local_key` and `myc_nip46`. Product -enqueue methods use that configured provider, so callers do not pass signing internals through every -farm, listing, or trade write call. `signer_status()`, `configured_signer()`, and -`sign_with_configured_signer(...)` expose the typed SDK signer boundary. - -`sdk.listings().prepare_publish(...)` is side-effect-free and returns a typed -`ListingPublishPlan`. With a configured signer, `sdk.listings().enqueue_prepared_publish(...)` signs -that prepared plan, ingests it into the local event store, queues signed outbox work, and returns a -typed `ListingEnqueueReceipt`. `sdk.listings().enqueue_publish(...)` is the convenience path that -prepares once and delegates to `enqueue_prepared_publish(...)`. Farm and trade write methods follow -the same configured signer pattern. The enqueue path uses typed transport target and idempotency -inputs; write requests require explicit UUIDv7 idempotency keys. `SatisfactionPolicy::NoWait` -records first-class no-wait delivery plans, so enqueue-only product workflows can complete without -accepted or delivered transport targets and without fabricated target delivery receipts. - -Event `created_at` and local observation time are separate contracts. The event timestamp remains -the authored event-envelope timestamp, while event-store and outbox mutation timestamps use the SDK -clock at enqueue time. Listing enqueue receipts report mutation state with the product names -`StoredAndQueued` and `AlreadyQueued`. - -`sdk.sync().push_outbox(...)` is the product sync entrypoint. It publishes queued signed outbox work -when `transport-nostr-runtime` is enabled. Push time uses the delivery targets already stored on each -queued outbox event, so already queued work does not require a configured builder profile. Reticulum -work reports explicit deferred-until-implemented receipt state with zero network -attempts; it does not deliver and does not fall back to another transport. Nostr transport publishing -and radrootsd execution consume signed outbox events; neither path owns signing. -`push_outbox_with_transport(...)` remains available for tests and controlled transport-level -substrate checks. `radrootsd-execution` adds daemon-resolved publishing through `publish.event`. - -`sdk.trades()` exposes the local trade runtime surface. With `runtime`, callers can read -`sdk.trades().capabilities()`, seal/open/delete protected private artifacts, issue semantic trade -commands through `sdk.trades().commands()`, and read semantic projections through -`sdk.trades().queries()`. Query methods read local projections and evidence; they are not network -fetches. - -Configured-signer trade writes compile when both `runtime` and `signer-adapters` are enabled. -Trade write workflows use `SubmitProposalRequest`, `ProposeRevisionRequest`, -`DecideCandidateRequest`, `CancelTradeRequest`, and `ResumeOperationRequest` through the command -service. Runtime-only builds keep explicit-signer methods available and do not expose a parallel -buyer/seller workflow namespace. - -The `local-runtime` feature is the curated feature bundle for local product runtime consumers. It -enables `std`, `serde`, `serde_json`, `runtime`, `signer-adapters`, `transport-nostr-runtime`, and -`transport-nostr-client`. `signer-adapters` contains the SDK `local_key` and `myc_nip46` signing surface. -`transport-nostr-client` supplies the Nostr relay WebSocket publish adapter used when signed outbox work -targets Nostr relay URLs. `local-runtime-radrootsd-execution` uses daemon-resolved publishing through -`publish.event` and the same configured signer provider API. - -`radroots_sdk_myc_nip46_product_permissions()` and -`radroots_sdk_myc_nip46_product_permission_strings()` expose the SDK product NIP-46 -`sign_event:<kind>` permission set for Myc connection setup. The set is derived from the farm, -listing, and canonical trade mutation event kinds the SDK can write. - -Relay URL policy is explicit. Public relay URLs must use `wss://`. Local development `ws://` relay -URLs are accepted only under `NostrRelayUrlPolicy::Localhost` and only for `localhost`, `127.0.0.1`, -or `[::1]`. Non-local insecure `ws://` targets, including private LAN addresses, are rejected. - -Low-level event-contract, wire-codec, reducer, and agreement-attestation contract ownership lives in -the shared Radroots libraries. The SDK exposes product APIs and selected adapter boundaries; it does -not expose a public workflow-bypass namespace for product callers. - -## Examples - -Compile the SDK v1 product examples with: - -```bash -cargo check -p radroots_sdk --example sdk_v1_listing_prepare --features runtime -cargo check -p radroots_sdk --example sdk_v1_knowledge_prepare --features knowledge -cargo check -p radroots_sdk --example sdk_v1_local_enqueue_and_mock_sync --features runtime,signer-adapters -cargo check -p radroots_sdk --example sdk_v1_myc_nip46_signer_setup --features runtime,signer-adapters -``` - -`sdk_v1_listing_prepare` shows `RadrootsClient::builder()`, -`ListingPreparePublishRequest`, and `ListingPublishPlan`. -`sdk_v1_knowledge_prepare` shows `radroots_sdk::knowledge`, typed event builders, frozen draft -preparation, signed-event verification, decoded event matching, and manifest hash access without -local runtime storage. -`sdk_v1_local_enqueue_and_mock_sync` shows localhost Nostr target selection, configured local-key -signing, prepared listing enqueue, `push_outbox_with_transport(...)` with a Nostr transport facade, and -queued outbox sync. `sdk_v1_myc_nip46_signer_setup` shows Myc NIP-46 signer provider setup and -product permission derivation at the SDK boundary. The examples stay on product APIs and do not use -wire-event internals. - -SDK v1 does not claim strict NIP-99 Markdown-content interoperability. It emits and consumes -Radroots v1 listing and trade event contracts. - -Optional advanced substrate is explicitly feature-scoped: - -- `identity-models`: identity data types without local storage coupling -- `identity-storage`: encrypted identity-file helpers -- `signing`: dependency substrate for curated Nostr adapters; it exposes no - generic builder or caller-constructed wire-part signing module -- `transport-nostr-client`: Nostr relay WebSocket client and publish adapters -- `signer-adapters`: SDK local-key and Myc NIP-46 signer providers plus configured-signer product - write APIs - -The crate is licensed as `MIT OR Apache-2.0`. Its manifest is configured for a future crates.io -release, but a public release still requires the full SDK check lane, generated artifact -reproducibility checks, metadata review, and a publish dry run. - -Runtime errors use a non-exhaustive `RadrootsSdkError` enum. Public callers should branch on the -stable method surface: `code`, `class`, `retryable`, `detail_json`, and `recovery_actions`. - -## Runtime DTO Stability - -Runtime request DTOs are constructor-led and marked non-exhaustive where they carry public fields: -`ListingPreparePublishRequest`, `ListingEnqueuePublishRequest`, `SubmitProposalRequest`, -`ProposeRevisionRequest`, `DecideCandidateRequest`, `CancelTradeRequest`, `ResumeOperationRequest`, -`GetTradeRequest`, `ListTradesRequest`, `RefreshTradeEvidenceRequest`, `InspectEvidenceRequest`, -`StorageStatusRequest`, `BackupRequest`, `IntegrityRequest`, `SyncStatusRequest`, and -`PushOutboxRequest`. Restore uses the same request posture through `RestoreRequest`. Use `new`, -`parse`, `default`, and `with_*` methods rather than struct literals. - -Runtime enums that may gain variants are non-exhaustive. This includes storage, clock, -target-policy, transport-profile, mutation-state, trade-status, sync-status, relay-auth, and -push-outbox state or outcome enums. - -Runtime receipts and status records expose stable serialized public fields for CLI and local-runtime -reporting. Future additive reporting must add fields without changing existing serialized field -names, meanings, or types. Restore archive and receipt records follow that same serialized-field -stance. diff --git a/crates/sdk/README.md b/crates/sdk/README.md @@ -0,0 +1,135 @@ +# radroots-sdk + +Curated Radroots Rust SDK for local-first Radroots product workflows. + +The SDK v1 product runtime is centered on `RadrootsClient::builder()`, +`sdk.farms()`, `sdk.listings()`, `sdk.trades()`, `sdk.market()`, and `sdk.sync()`. + +`RadrootsClient::builder()` defaults to memory storage, the system clock, the `LocalOnly` transport +profile, and no production network publishing. Directory storage is opt-in and creates +`runtime.sqlite`, `private.sqlite`, and `studio.sqlite` in the selected directory. Configured Nostr +relay URLs live inside `TransportProfile::Nostr` or the Nostr side of `TransportProfile::MultiTarget`, +and product enqueue requests choose the active profile through `TargetPolicy::default_profile()`. + +When `signer-adapters` is enabled, `RadrootsClient::builder()` accepts a configured +`RadrootsSdkSignerProvider`. The production signing modes are `local_key` and `myc_nip46`. Product +enqueue methods use that configured provider, so callers do not pass signing internals through every +farm, listing, or trade write call. `signer_status()`, `configured_signer()`, and +`sign_with_configured_signer(...)` expose the typed SDK signer boundary. + +`sdk.listings().prepare_publish(...)` is side-effect-free and returns a typed +`ListingPublishPlan`. With a configured signer, `sdk.listings().enqueue_prepared_publish(...)` signs +that prepared plan, ingests it into the local event store, queues signed outbox work, and returns a +typed `ListingEnqueueReceipt`. `sdk.listings().enqueue_publish(...)` is the convenience path that +prepares once and delegates to `enqueue_prepared_publish(...)`. Farm and trade write methods follow +the same configured signer pattern. The enqueue path uses typed transport target and idempotency +inputs; write requests require explicit UUIDv7 idempotency keys. `SatisfactionPolicy::NoWait` +records first-class no-wait delivery plans, so enqueue-only product workflows can complete without +accepted or delivered transport targets and without fabricated target delivery receipts. + +Event `created_at` and local observation time are separate contracts. The event timestamp remains +the authored event-envelope timestamp, while event-store and outbox mutation timestamps use the SDK +clock at enqueue time. Listing enqueue receipts report mutation state with the product names +`StoredAndQueued` and `AlreadyQueued`. + +`sdk.sync().push_outbox(...)` is the product sync entrypoint. It publishes queued signed outbox work +when `transport-nostr-runtime` is enabled. Push time uses the delivery targets already stored on each +queued outbox event, so already queued work does not require a configured builder profile. Reticulum +work reports explicit deferred-until-implemented receipt state with zero network +attempts; it does not deliver and does not fall back to another transport. Nostr transport publishing +and radrootsd execution consume signed outbox events; neither path owns signing. +`push_outbox_with_transport(...)` remains available for tests and controlled transport-level +substrate checks. `radrootsd-execution` adds daemon-resolved publishing through `publish.event`. + +`sdk.trades()` exposes the local trade runtime surface. With `runtime`, callers can read +`sdk.trades().capabilities()`, seal/open/delete protected private artifacts, issue semantic trade +commands through `sdk.trades().commands()`, and read semantic projections through +`sdk.trades().queries()`. Query methods read local projections and evidence; they are not network +fetches. + +Configured-signer trade writes compile when both `runtime` and `signer-adapters` are enabled. +Trade write workflows use `SubmitProposalRequest`, `ProposeRevisionRequest`, +`DecideCandidateRequest`, `CancelTradeRequest`, and `ResumeOperationRequest` through the command +service. Runtime-only builds keep explicit-signer methods available and do not expose a parallel +buyer/seller workflow namespace. + +The `local-runtime` feature is the curated feature bundle for local product runtime consumers. It +enables `std`, `serde`, `serde_json`, `runtime`, `signer-adapters`, `transport-nostr-runtime`, and +`transport-nostr-client`. `signer-adapters` contains the SDK `local_key` and `myc_nip46` signing surface. +`transport-nostr-client` supplies the Nostr relay WebSocket publish adapter used when signed outbox work +targets Nostr relay URLs. `local-runtime-radrootsd-execution` uses daemon-resolved publishing through +`publish.event` and the same configured signer provider API. + +`radroots_sdk_myc_nip46_product_permissions()` and +`radroots_sdk_myc_nip46_product_permission_strings()` expose the SDK product NIP-46 +`sign_event:<kind>` permission set for Myc connection setup. The set is derived from the farm, +listing, and canonical trade mutation event kinds the SDK can write. + +Relay URL policy is explicit. Public relay URLs must use `wss://`. Local development `ws://` relay +URLs are accepted only under `NostrRelayUrlPolicy::Localhost` and only for `localhost`, `127.0.0.1`, +or `[::1]`. Non-local insecure `ws://` targets, including private LAN addresses, are rejected. + +Low-level event-contract, wire-codec, reducer, and agreement-attestation contract ownership lives in +the shared Radroots libraries. The SDK exposes product APIs and selected adapter boundaries; it does +not expose a public workflow-bypass namespace for product callers. + +## Examples + +Compile the SDK v1 product examples with: + +```bash +cargo check -p radroots-sdk --example sdk_v1_listing_prepare --features runtime +cargo check -p radroots-sdk --example sdk_v1_knowledge_prepare --features knowledge +cargo check -p radroots-sdk --example sdk_v1_local_enqueue_and_mock_sync --features runtime,signer-adapters +cargo check -p radroots-sdk --example sdk_v1_myc_nip46_signer_setup --features runtime,signer-adapters +``` + +`sdk_v1_listing_prepare` shows `RadrootsClient::builder()`, +`ListingPreparePublishRequest`, and `ListingPublishPlan`. +`sdk_v1_knowledge_prepare` shows `radroots_sdk::knowledge`, typed event builders, frozen draft +preparation, signed-event verification, decoded event matching, and manifest hash access without +local runtime storage. +`sdk_v1_local_enqueue_and_mock_sync` shows localhost Nostr target selection, configured local-key +signing, prepared listing enqueue, `push_outbox_with_transport(...)` with a Nostr transport facade, and +queued outbox sync. `sdk_v1_myc_nip46_signer_setup` shows Myc NIP-46 signer provider setup and +product permission derivation at the SDK boundary. The examples stay on product APIs and do not use +wire-event internals. + +SDK v1 does not claim strict NIP-99 Markdown-content interoperability. It emits and consumes +Radroots v1 listing and trade event contracts. + +Optional advanced substrate is explicitly feature-scoped: + +- `identity-models`: identity data types without local storage coupling +- `identity-storage`: encrypted identity-file helpers +- `signing`: dependency substrate for curated Nostr adapters; it exposes no + generic builder or caller-constructed wire-part signing module +- `transport-nostr-client`: Nostr relay WebSocket client and publish adapters +- `signer-adapters`: SDK local-key and Myc NIP-46 signer providers plus configured-signer product + write APIs + +The crate is licensed as `MIT OR Apache-2.0`. Its manifest is configured for a future crates.io +release, but a public release still requires the full SDK check lane, generated artifact +reproducibility checks, metadata review, and a publish dry run. + +Runtime errors use a non-exhaustive `RadrootsSdkError` enum. Public callers should branch on the +stable method surface: `code`, `class`, `retryable`, `detail_json`, and `recovery_actions`. + +## Runtime DTO Stability + +Runtime request DTOs are constructor-led and marked non-exhaustive where they carry public fields: +`ListingPreparePublishRequest`, `ListingEnqueuePublishRequest`, `SubmitProposalRequest`, +`ProposeRevisionRequest`, `DecideCandidateRequest`, `CancelTradeRequest`, `ResumeOperationRequest`, +`GetTradeRequest`, `ListTradesRequest`, `RefreshTradeEvidenceRequest`, `InspectEvidenceRequest`, +`StorageStatusRequest`, `BackupRequest`, `IntegrityRequest`, `SyncStatusRequest`, and +`PushOutboxRequest`. Restore uses the same request posture through `RestoreRequest`. Use `new`, +`parse`, `default`, and `with_*` methods rather than struct literals. + +Runtime enums that may gain variants are non-exhaustive. This includes storage, clock, +target-policy, transport-profile, mutation-state, trade-status, sync-status, relay-auth, and +push-outbox state or outcome enums. + +Runtime receipts and status records expose stable serialized public fields for CLI and local-runtime +reporting. Future additive reporting must add fields without changing existing serialized field +names, meanings, or types. Restore archive and receipt records follow that same serialized-field +stance. diff --git a/tools/xtask/src/architecture.rs b/tools/xtask/src/architecture.rs @@ -286,12 +286,13 @@ fn validate_public_package_metadata( .values() .find(|repository| repository.url == workspace.workspace.package.repository) .ok_or_else(|| "workspace repository has no architecture allocation".to_owned())?; - let local_packages = repository.packages.iter().collect::<BTreeSet<_>>(); + let local_packages = repository.packages.iter().cloned().collect::<BTreeSet<_>>(); let all_packages = architecture .package .iter() .map(|package| package.name.as_str()) .collect::<BTreeSet<_>>(); + let mut found_local_packages = BTreeSet::new(); for member in &workspace.workspace.members { let manifest_path = workspace_root.join(member).join("Cargo.toml"); @@ -311,11 +312,12 @@ fn validate_public_package_metadata( if !all_packages.contains(name) { continue; } - if !local_packages.contains(&name.to_owned()) { + if !local_packages.contains(name) { return Err(format!( "public package {name} belongs to a different canonical repository" )); } + found_local_packages.insert(name.to_owned()); validate_public_manifest_field( package, "version", @@ -391,6 +393,21 @@ fn validate_public_package_metadata( )); } } + if found_local_packages != local_packages { + let missing = local_packages + .difference(&found_local_packages) + .cloned() + .collect::<Vec<_>>() + .join(", "); + let extra = found_local_packages + .difference(&local_packages) + .cloned() + .collect::<Vec<_>>() + .join(", "); + return Err(format!( + "workspace public package inventory is missing: {missing}; workspace public package inventory has unallocated packages: {extra}" + )); + } Ok(()) } @@ -814,4 +831,15 @@ adr_required = false assert!(error.contains("must inherit the workspace lint policy")); let _ = fs::remove_dir_all(root); } + + #[test] + fn public_package_inventory_requires_every_local_package() { + let root = test_root("public_package_inventory"); + fs::write(root.join("Cargo.toml"), complete_workspace_manifest("")) + .expect("write workspace manifest"); + let error = validate_public_package_metadata(&root, &architecture()) + .expect_err("missing local public package must fail"); + assert!(error.contains("workspace public package inventory is missing: radroots")); + let _ = fs::remove_dir_all(root); + } }