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:
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);
+ }
}