lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README

commit 95b147d7db8ed8461f7d405c403c40b3fb3afc9a
parent 6cd934641036e19250352312a252df44d2da99c5
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:
Mcrates/sdk/Cargo.toml | 12+++++++++---
Dcrates/sdk/README | 135-------------------------------------------------------------------------------
Acrates/sdk/README.md | 135+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtools/sdk_xtask_import/src/architecture.rs | 32++++++++++++++++++++++++++++++--
4 files changed, 174 insertions(+), 140 deletions(-)

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/sdk_xtask_import/src/architecture.rs b/tools/sdk_xtask_import/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); + } }