sdk

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

commit 208e02d55f433fabe4f46320a577104929c20db2
parent 0b18741e0912f193aabc67357862ea4a18fd725e
Author: triesap <tyson@radroots.org>
Date:   Tue, 30 Jun 2026 11:04:56 +0000

docs: align sdk product readme

Diffstat:
Mcrates/sdk/README | 200+++++++++++++++++++++++++++++++++++--------------------------------------------
Mcrates/sdk/tests/source_boundary.rs | 50++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 139 insertions(+), 111 deletions(-)

diff --git a/crates/sdk/README b/crates/sdk/README @@ -1,89 +1,73 @@ # radroots_sdk -Curated Rad Roots Rust SDK for local-first Rad Roots product workflows. - -The SDK v1 product runtime is centered on `RadrootsSdk::builder()`, -`sdk.farms()`, `sdk.listings()`, `sdk.orders()`, and `sdk.sync()`. - -`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 enqueue defaults used when a publish -request chooses `SdkRelayTargetPolicy::UseConfiguredRelays`. - -When `signer-adapters` is enabled, `RadrootsSdk::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 order write call. -`signer_status()`, `configured_signer()`, and +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, 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 opt-in enqueue defaults used +when a publish request chooses `SdkRelayTargetPolicy::UseConfiguredRelays`. + +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 order write methods follow the same -configured signer pattern. The enqueue path uses typed relay target and -idempotency inputs; omitted idempotency keys are derived deterministically. - -Explicit signer injection remains available under `*_with_explicit_signer` -method names for controlled adapter-level tests and advanced integration -checks. Those methods are not the primary product API. - -Event `created_at` and local observation time are separate contracts. The event -timestamp remains the authored Nostr event 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 `relay-runtime` is enabled. Push time uses the relay -targets already stored on each queued outbox event, so already queued work does -not require configured builder relays. Direct relay publishing and the -`radrootsd-proxy` Publish Proxy transport consume signed outbox events; neither -transport owns signing. `push_outbox_with_adapter(...)` remains available for -tests and controlled adapter-level substrate checks. `radrootsd-proxy` adds -daemon-resolved publishing through `publish.event`. - -The `local-runtime` feature is the curated feature bundle for local product -runtime consumers. It enables `std`, `serde`, `serde_json`, `runtime`, -`signer-adapters`, `relay-runtime`, and `relay-client`. `signer-adapters` -contains the SDK `local_key` and `myc_nip46` signing surface. `relay-client` is -retained in this bundle only for direct relay publish callers. `local-runtime` -does not enable `radrootsd-proxy`; use `local-runtime-radrootsd-proxy` when the -runtime should publish through Radrootsd instead of a direct relay transport. -Both bundles use the same configured signer provider API. +`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 relay target and idempotency inputs; +omitted idempotency keys are derived deterministically. + +Explicit signer injection remains available under `*_with_explicit_signer` method names for +controlled adapter-level tests and advanced integration checks. Those methods are not the primary +product API. + +Event `created_at` and local observation time are separate contracts. The event timestamp remains +the authored Nostr event 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 `relay-runtime` is enabled. Push time uses the relay targets already stored on each queued +outbox event, so already queued work does not require configured builder relays. Direct relay +publishing and the `radrootsd-proxy` Publish Proxy transport consume signed outbox events; neither +transport owns signing. `push_outbox_with_adapter(...)` remains available for tests and controlled +adapter-level substrate checks. `radrootsd-proxy` adds daemon-resolved publishing through +`publish.event`. + +`sdk.trades()` exposes local trade evidence ingestion and status projection APIs. Product workflow +actions are split into role-specific handles: `sdk.trade_buyer()`, `sdk.trade_seller()`, +`sdk.trade_status()`, `sdk.trade_resync()`, and `sdk.trade_validation()`. `sdk.trades().status(...)` +and `sdk.trade_status().status(...)` accept `TradeStatusRequest` and read local projections; they +return typed source, event count, limit state, event IDs, ambiguity candidates, eligibility, +next-action, and reducer issue data. Status is not a network fetch. + +The `local-runtime` feature is the curated feature bundle for local product runtime consumers. It +enables `std`, `serde`, `serde_json`, `runtime`, `signer-adapters`, `relay-runtime`, and +`relay-client`. `signer-adapters` contains the SDK `local_key` and `myc_nip46` signing surface. +`relay-client` is retained in this bundle only for direct relay publish callers. `local-runtime` +does not enable `radrootsd-proxy`; use `local-runtime-radrootsd-proxy` when the runtime should +publish through Radrootsd instead of a direct relay transport. Both bundles use 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 order 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 `SdkRelayUrlPolicy::Localhost` -and only for `localhost`, `127.0.0.1`, or `[::1]`. Non-local insecure `ws://` -targets, including private LAN addresses, are rejected. - -`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 -explicit modules: - -- `protocol::events` -- `protocol::wire` -- `protocol::profile` -- `protocol::farm` -- `protocol::listing` -- `protocol::order` -- `protocol::identity` when `identity-models` is enabled -- `adapters` - -Product runtime callers do not need `WireEventParts`. Wire helpers are exposed -only through `protocol::wire`. +`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 trade 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 `SdkRelayUrlPolicy::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 DVM 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 @@ -95,17 +79,16 @@ cargo check -p radroots_sdk --example sdk_v1_local_enqueue_and_mock_sync --featu cargo check -p radroots_sdk --example sdk_v1_myc_nip46_signer_setup --features runtime,signer-adapters ``` -`sdk_v1_listing_prepare` shows `RadrootsSdk::builder()`, +`sdk_v1_listing_prepare` shows `RadrootsClient::builder()`, `ListingPreparePublishRequest`, and `ListingPublishPlan`. -`sdk_v1_local_enqueue_and_mock_sync` shows localhost relay target selection, -configured local-key signing, prepared listing enqueue, -`push_outbox_with_adapter(...)` with a mock relay adapter, and -`OrderStatusRequest`. `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 `WireEventParts`. +`sdk_v1_local_enqueue_and_mock_sync` shows localhost relay target selection, configured local-key +signing, prepared listing enqueue, `push_outbox_with_adapter(...)` with a mock relay adapter, and +`TradeStatusRequest`. `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 Rad Roots v1 listing and trade event contracts. +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: @@ -115,30 +98,25 @@ Optional advanced substrate is explicitly feature-scoped: - `relay-client`: relay client and publish adapters - `signer-adapters`: SDK local-key and Myc NIP-46 signer providers -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. +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 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`, `OrderStatusRequest`, `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, relay-target, mutation-state, order-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 should preserve -serde compatibility for existing fields. Restore archive and receipt records -follow that same stable serialized-field stance. +Runtime request DTOs are constructor-led and marked non-exhaustive where they carry public fields: +`ListingPreparePublishRequest`, `ListingEnqueuePublishRequest`, `TradeStatusRequest`, +`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, +relay-target, 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 should preserve serde compatibility for existing fields. +Restore archive and receipt records follow that same stable serialized-field stance. diff --git a/crates/sdk/tests/source_boundary.rs b/crates/sdk/tests/source_boundary.rs @@ -39,6 +39,34 @@ const FORBIDDEN_SDK_SOURCE_CONCEPTS: &[ForbiddenSdkConcept] = &[ }, ]; +const FORBIDDEN_SDK_README_CONCEPTS: &[ForbiddenSdkConcept] = &[ + ForbiddenSdkConcept { + pattern: "RadrootsSdk::builder()", + reason: "SDK docs must describe RadrootsClient as the product runtime entrypoint", + }, + ForbiddenSdkConcept { + pattern: "sdk.orders()", + reason: "SDK docs must describe the current trade product clients", + }, + ForbiddenSdkConcept { + pattern: "OrderStatusRequest", + reason: "SDK docs must describe TradeStatusRequest as the status request DTO", + }, + ForbiddenSdkConcept { + pattern: "protocol::", + reason: "SDK docs must not advertise a public protocol workflow bypass", + }, +]; + +const REQUIRED_SDK_README_CONCEPTS: &[&str] = &[ + "RadrootsClient::builder()", + "sdk.trades()", + "TradeStatusRequest", + "sdk.trade_buyer()", + "sdk.trade_seller()", + "sdk.trade_status()", +]; + const REQUIRED_TRADE_RUNTIME_EXPORTS: &[&str] = &[ "TRADE_CANCELLATION_OPERATION_KIND", "TRADE_DECISION_OPERATION_KIND", @@ -325,6 +353,28 @@ fn sdk_manifest_does_not_depend_on_app_or_cli_crates() { } #[test] +fn sdk_readme_documents_current_public_product_surface() { + let readme_path = Path::new(env!("CARGO_MANIFEST_DIR")).join("README"); + let readme = read_source(readme_path.as_path()); + + for concept in FORBIDDEN_SDK_README_CONCEPTS { + assert!( + !readme.contains(concept.pattern), + "README contains forbidden SDK public API concept `{}`: {}", + concept.pattern, + concept.reason + ); + } + + for concept in REQUIRED_SDK_README_CONCEPTS { + assert!( + readme.contains(concept), + "README must document current SDK public API concept `{concept}`" + ); + } +} + +#[test] fn farm_runtime_stays_on_product_runtime_boundary() { product_runtime_file_stays_on_boundary("src/farms_runtime.rs"); }