commit 9656841cfa620eca297778e5414475ab50643edc
parent 924d91fdf4a7062c8e071610e56dcfa24003652c
Author: triesap <tyson@radroots.org>
Date: Tue, 30 Jun 2026 11:04:56 +0000
docs: align sdk product readme
Diffstat:
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");
}