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