commit b5c7b0d62c8477fc2621a549bd21e762dc25af3f
parent b743b87c31b4b51d82e7148ba4fedbf8d5b35250
Author: triesap <tyson@radroots.org>
Date: Mon, 15 Jun 2026 17:34:59 -0700
sdk: document runtime publish posture
Diffstat:
2 files changed, 56 insertions(+), 16 deletions(-)
diff --git a/crates/sdk/README b/crates/sdk/README
@@ -3,20 +3,49 @@
Curated Rad Roots Rust SDK for local-first Rad Roots product workflows.
The SDK v1 product runtime is centered on `RadrootsSdk::builder()`,
-`sdk.listings()`, `sdk.sync()`, and `sdk.orders()`. Product callers use the
-runtime clients to prepare listing publishes, enqueue signed local mutations,
-push the signed outbox through an explicit relay transport adapter, and read
-local order status projections.
+`sdk.listings()`, `sdk.sync()`, and `sdk.orders()`.
+
+`RadrootsSdk::builder()` defaults to memory storage, the system clock, no relay
+URLs, and no production network publishing. Directory storage is opt-in and
+creates `event_store.sqlite` and `outbox.sqlite` in the selected directory.
+Configured relay URLs are also opt-in. Product sync publish requires configured
+relay URLs and the `relay-runtime` feature.
+
+`sdk.listings().prepare_publish(...)` is side-effect-free and returns a typed
+`ListingPublishPlan`. `sdk.listings().enqueue_publish(...)` signs the authorized
+listing event, ingests it into the local event store, queues signed outbox work,
+and returns a typed `ListingEnqueueReceipt`. The enqueue request uses typed relay
+target and idempotency inputs; omitted idempotency keys are derived
+deterministically.
+
+`sdk.sync().push_outbox(...)` is the product sync entrypoint. It publishes queued
+signed outbox work only when relay runtime support and configured relay URLs are
+present. `push_outbox_with_adapter(...)` remains available for tests and
+controlled adapter-level substrate checks.
+
+`sdk.orders().status(...)` reads local order projections and returns typed
+source, event count, limit state, event IDs, and reducer issues. It is not a
+network fetch.
Low-level event-contract and transport helpers are intentionally scoped under
-`protocol`:
+explicit modules:
+- `protocol::events`
+- `protocol::wire`
- `protocol::profile`
- `protocol::farm`
- `protocol::listing`
- `protocol::order`
-- `protocol::client`
-- `protocol::config`
+- `protocol::identity` when `identity-models` is enabled
+- `client`
+- `config`
+- `adapters`
+
+Product runtime callers do not need `WireEventParts`. Wire helpers are exposed
+only through `protocol::wire`.
+
+SDK v1 does not claim strict NIP-99 Markdown-content interoperability. It emits
+and consumes Rad Roots v1 listing and trade event contracts.
Optional advanced substrate is explicitly feature-scoped:
@@ -25,3 +54,8 @@ Optional advanced substrate is explicitly feature-scoped:
- `signing`: Nostr builder and local signing adapters
- `relay-client`: relay client and publish adapters
- `signer-adapters`: NIP-46 and signer-session primitives
+
+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.
diff --git a/crates/sdk/examples/runtime_local.rs b/crates/sdk/examples/runtime_local.rs
@@ -10,14 +10,14 @@ use radroots_events::draft::{
RadrootsFrozenEventDraft, RadrootsSignedNostrEvent, RadrootsSignedNostrEventParts,
};
use radroots_events::ids::{RadrootsDTag, RadrootsInventoryBinId};
-use radroots_relay_transport::RadrootsMockRelayPublishAdapter;
use radroots_sdk::protocol::farm::RadrootsFarmRef;
use radroots_sdk::protocol::listing::{
RadrootsListing, RadrootsListingBin, RadrootsListingProduct,
};
use radroots_sdk::{
ListingEnqueuePublishRequest, ListingPreparePublishRequest, OrderStatusRequest,
- PushOutboxRequest, RadrootsSdk, RadrootsSdkTimestamp, SdkRelayTargetPolicy,
+ PushOutboxRequest, RadrootsSdk, RadrootsSdkError, RadrootsSdkTimestamp, SdkRelayTargetPolicy,
+ SdkRelayUrlPolicy,
};
const SELLER: &str = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
@@ -76,7 +76,6 @@ impl RadrootsEventSigner for FixtureSigner {
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let sdk = RadrootsSdk::builder()
.fixed_clock(RadrootsSdkTimestamp::from_unix_seconds(1_700_000_000))
- .relay_url(RELAY)
.build()
.await?;
let actor = RadrootsActorContext::test(SELLER, [RadrootsActorRole::Seller])?;
@@ -87,6 +86,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
listing,
SdkRelayTargetPolicy::UseConfiguredRelays,
)
+ .try_with_target_relays([RELAY], SdkRelayUrlPolicy::Public)?
.try_with_idempotency_key("example-1")?;
let prepared = sdk.listings().prepare_publish(prepare_request)?;
@@ -96,11 +96,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
.await?;
let push = sdk
.sync()
- .push_outbox_with_adapter(
- &RadrootsMockRelayPublishAdapter::new(),
- PushOutboxRequest::new().with_limit(1),
- )
- .await?;
+ .push_outbox(PushOutboxRequest::new().with_limit(1))
+ .await;
let order_status = sdk
.orders()
.status(OrderStatusRequest::parse("example-order-1")?)
@@ -110,7 +107,16 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
prepared.public_listing_addr.as_str(),
enqueue.public_listing_addr.as_str()
);
- assert_eq!(push.attempted_events, 1);
+ #[cfg(feature = "relay-runtime")]
+ assert!(matches!(
+ push,
+ Err(RadrootsSdkError::ProductSyncRelaySetupFailure { .. })
+ ));
+ #[cfg(not(feature = "relay-runtime"))]
+ assert!(matches!(
+ push,
+ Err(RadrootsSdkError::ProductSyncUnsupported { .. })
+ ));
assert!(!order_status.found);
Ok(())
}