sdk

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

commit 3b6767ef28eae443cb29a62a88ccd649b2977274
parent c57ea6bcf846ecccb8e4b8a8696a86e1a5c312ce
Author: triesap <tyson@radroots.org>
Date:   Mon, 27 Jul 2026 10:50:23 +0000

workspace: remove production sibling paths

- make SDK production manifests resolve lib crates by exact registry identity
- isolate coordinated-checkout path patches in developer-only Cargo config
- reject repository-escaping production dependency paths with fixture coverage
- document package-realistic qualification and override retirement requirements

Diffstat:
M.cargo/config.toml | 31+++++++++++++++++++++++++++++++
MCargo.toml | 52++++++++++++++++++++++++++--------------------------
Mcrates/runtime_contract_v1/Cargo.toml | 2+-
Adocs/engineering/local-overrides.md | 16++++++++++++++++
Mtools/xtask/src/architecture.rs | 253+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------
5 files changed, 284 insertions(+), 70 deletions(-)

diff --git a/.cargo/config.toml b/.cargo/config.toml @@ -1,2 +1,33 @@ [alias] xtask = "run -p radroots_sdk_xtask --" + +# Migration-only local development overrides. Release manifests contain only +# registry identities; package-realistic checks run extracted crates without +# this repository configuration. +[patch.crates-io] +radroots_authority = { path = "../lib/crates/authority" } +radroots-blossom = { path = "../lib/crates/blossom" } +radroots-core = { path = "../lib/crates/core" } +radroots_event_store = { path = "../lib/crates/event_store" } +radroots-event = { path = "../lib/crates/event" } +radroots-event-codec = { path = "../lib/crates/event_codec" } +radroots_event_index = { path = "../lib/crates/event_index" } +radroots_geocoder = { path = "../lib/crates/geocoder" } +radroots-identity = { path = "../lib/crates/identity" } +radroots-nostr = { path = "../lib/crates/nostr" } +radroots-nostr-connect = { path = "../lib/crates/nostr_connect" } +radroots_nostr_signer = { path = "../lib/crates/nostr_signer" } +radroots_outbox = { path = "../lib/crates/outbox" } +radroots_protocol_contract_v1 = { path = "../lib/crates/protocol_contract_v1" } +radroots_protected_store = { path = "../lib/crates/protected_store" } +radroots_secret_vault = { path = "../lib/crates/secret_vault" } +radroots-transport = { path = "../lib/crates/transport" } +radroots_transport_publish_protocol = { path = "../lib/crates/transport_publish_protocol" } +radroots-transport-nostr = { path = "../lib/crates/transport_nostr" } +radroots_transport_reticulum = { path = "../lib/crates/transport_reticulum" } +radroots_replica_store = { path = "../lib/crates/replica_store" } +radroots_replica_schema = { path = "../lib/crates/replica_schema" } +radroots_replica_sync = { path = "../lib/crates/replica_sync" } +radroots_runtime_paths = { path = "../lib/crates/runtime_paths" } +radroots_sql_core = { path = "../lib/crates/sql_core" } +radroots-trade = { path = "../lib/crates/trade" } diff --git a/Cargo.toml b/Cargo.toml @@ -48,36 +48,36 @@ unimplemented = "deny" dto_bindgen = { version = "0.1.0" } dto_bindgen_backend_ts = { version = "0.1.0" } dto_bindgen_core = { version = "0.1.0" } -radroots_authority = { path = "../lib/crates/authority", version = "=1.0.0-alpha.1", default-features = false } -radroots_blossom = { package = "radroots-blossom", path = "../lib/crates/blossom", version = "=0.1.0", default-features = false } -radroots_core = { package = "radroots-core", path = "../lib/crates/core", version = "=0.1.0", default-features = false } -radroots_event_store = { path = "../lib/crates/event_store", version = "=1.0.0-alpha.1", default-features = false } -radroots_event = { package = "radroots-event", path = "../lib/crates/event", version = "=0.1.0", default-features = false } -radroots_event_codec = { package = "radroots-event-codec", path = "../lib/crates/event_codec", version = "=0.1.0", default-features = false } -radroots_event_index = { path = "../lib/crates/event_index", version = "=1.0.0-alpha.1", default-features = false } -radroots_geocoder = { path = "../lib/crates/geocoder", version = "=1.0.0-alpha.1" } -radroots_identity = { package = "radroots-identity", path = "../lib/crates/identity", version = "=0.1.0", default-features = false, features = [ +radroots_authority = { version = "=1.0.0-alpha.1", default-features = false } +radroots_blossom = { package = "radroots-blossom", version = "=0.1.0", default-features = false } +radroots_core = { package = "radroots-core", version = "=0.1.0", default-features = false } +radroots_event_store = { version = "=1.0.0-alpha.1", default-features = false } +radroots_event = { package = "radroots-event", version = "=0.1.0", default-features = false } +radroots_event_codec = { package = "radroots-event-codec", version = "=0.1.0", default-features = false } +radroots_event_index = { version = "=1.0.0-alpha.1", default-features = false } +radroots_geocoder = { version = "=1.0.0-alpha.1" } +radroots_identity = { package = "radroots-identity", version = "=0.1.0", default-features = false, features = [ "std", ] } -radroots_nostr = { package = "radroots-nostr", path = "../lib/crates/nostr", version = "=0.1.0", default-features = false } -radroots_nostr_connect = { package = "radroots-nostr-connect", path = "../lib/crates/nostr_connect", version = "=0.1.0", default-features = false } -radroots_nostr_signer = { path = "../lib/crates/nostr_signer", version = "=1.0.0-alpha.1", default-features = false } -radroots_outbox = { path = "../lib/crates/outbox", version = "=1.0.0-alpha.1", default-features = false } -radroots_protocol_contract_v1 = { path = "../lib/crates/protocol_contract_v1", version = "=1.0.0-alpha.1", default-features = false } -radroots_protected_store = { path = "../lib/crates/protected_store", version = "=1.0.0-alpha.1", default-features = false } +radroots_nostr = { package = "radroots-nostr", version = "=0.1.0", default-features = false } +radroots_nostr_connect = { package = "radroots-nostr-connect", version = "=0.1.0", default-features = false } +radroots_nostr_signer = { version = "=1.0.0-alpha.1", default-features = false } +radroots_outbox = { version = "=1.0.0-alpha.1", default-features = false } +radroots_protocol_contract_v1 = { version = "=1.0.0-alpha.1", default-features = false } +radroots_protected_store = { version = "=1.0.0-alpha.1", default-features = false } radroots_runtime_contract_v1 = { path = "crates/runtime_contract_v1", version = "0.1.0", default-features = false } -radroots_secret_vault = { path = "../lib/crates/secret_vault", version = "=1.0.0-alpha.1", default-features = false } -radroots_transport = { package = "radroots-transport", path = "../lib/crates/transport", version = "=0.1.0", default-features = false } -radroots_transport_publish_protocol = { path = "../lib/crates/transport_publish_protocol", version = "=1.0.0-alpha.1", default-features = false } -radroots_transport_nostr = { package = "radroots-transport-nostr", path = "../lib/crates/transport_nostr", version = "=0.1.0", default-features = false } -radroots_transport_reticulum = { path = "../lib/crates/transport_reticulum", version = "=1.0.0-alpha.1", default-features = false } -radroots_replica_store = { path = "../lib/crates/replica_store", version = "=1.0.0-alpha.1", default-features = false } -radroots_replica_schema = { path = "../lib/crates/replica_schema", version = "=1.0.0-alpha.1", default-features = false } -radroots_replica_sync = { path = "../lib/crates/replica_sync", version = "=1.0.0-alpha.1", default-features = false } -radroots_runtime_paths = { path = "../lib/crates/runtime_paths", version = "=1.0.0-alpha.1", default-features = false } +radroots_secret_vault = { version = "=1.0.0-alpha.1", default-features = false } +radroots_transport = { package = "radroots-transport", version = "=0.1.0", default-features = false } +radroots_transport_publish_protocol = { version = "=1.0.0-alpha.1", default-features = false } +radroots_transport_nostr = { package = "radroots-transport-nostr", version = "=0.1.0", default-features = false } +radroots_transport_reticulum = { version = "=1.0.0-alpha.1", default-features = false } +radroots_replica_store = { version = "=1.0.0-alpha.1", default-features = false } +radroots_replica_schema = { version = "=1.0.0-alpha.1", default-features = false } +radroots_replica_sync = { version = "=1.0.0-alpha.1", default-features = false } +radroots_runtime_paths = { version = "=1.0.0-alpha.1", default-features = false } radroots_sdk_sql_wasm_runtime = { path = "crates/sql_wasm_runtime", version = "0.1.0-alpha.2" } -radroots_sql_core = { path = "../lib/crates/sql_core", version = "=1.0.0-alpha.1", default-features = false } -radroots_trade = { package = "radroots-trade", path = "../lib/crates/trade", version = "=0.1.0", default-features = false, features = [ +radroots_sql_core = { version = "=1.0.0-alpha.1", default-features = false } +radroots_trade = { package = "radroots-trade", version = "=0.1.0", default-features = false, features = [ "serde_json", "std", ] } diff --git a/crates/runtime_contract_v1/Cargo.toml b/crates/runtime_contract_v1/Cargo.toml @@ -16,7 +16,7 @@ serde = ["dep:serde", "radroots_protocol_contract_v1/serde"] std = ["radroots_protocol_contract_v1/std"] [dependencies] -radroots_protocol_contract_v1 = { path = "../../../lib/crates/protocol_contract_v1", version = "=1.0.0-alpha.1", default-features = false } +radroots_protocol_contract_v1 = { version = "=1.0.0-alpha.1", default-features = false } serde = { workspace = true, default-features = false, features = [ "alloc", "derive", diff --git a/docs/engineering/local-overrides.md b/docs/engineering/local-overrides.md @@ -0,0 +1,16 @@ +# Local dependency overrides + +The SDK release manifests resolve packages owned by `radrootslabs/lib` through +their registry identities and exact migration-train versions. Production +`Cargo.toml` files must not contain sibling-repository paths or Git overrides. + +The checked-in `.cargo/config.toml` supplies path patches only for development +inside a coordinated checkout before the initial packages exist in a registry. +Cargo patches do not alter packaged manifests. Package-realistic validation +must run the extracted `.crate` archives outside this repository so the local +configuration cannot satisfy or conceal a registry dependency. + +These patches are temporary migration infrastructure. Remove them once all +lower packages are available to clean consumers from the qualification +registry. Do not add an application, build script, generated package, or public +crate dependency to this override surface. diff --git a/tools/xtask/src/architecture.rs b/tools/xtask/src/architecture.rs @@ -153,6 +153,7 @@ pub fn validate(workspace_root: &Path) -> Result<(), String> { validate_workspace_toolchain(workspace_root, &architecture)?; validate_public_package_metadata(workspace_root, &architecture)?; + validate_no_production_sibling_paths(workspace_root)?; validate_public_dependency_versions(workspace_root, &architecture)?; let ledger_path = workspace_root.join(DEVIATIONS_RELATIVE); @@ -434,7 +435,11 @@ fn validate_public_dependency_versions( .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() + .map(String::as_str) + .collect::<BTreeSet<_>>(); let public_packages = architecture .package .iter() @@ -454,46 +459,41 @@ fn validate_public_dependency_versions( .and_then(|package| package.get("name")) .and_then(toml::Value::as_str) .ok_or_else(|| format!("{} is missing package.name", manifest_path.display()))?; - if !local_packages.contains(&package_name.to_owned()) { + if !local_packages.contains(package_name) { continue; } - validate_public_dependency_sections( + let policy = PublicDependencyPolicy { workspace_root, - member, - package_name, - &manifest, workspace_dependencies, - &public_packages, - &architecture.initial_version, - )?; + public_packages: &public_packages, + local_packages: &local_packages, + initial_version: &architecture.initial_version, + }; + validate_public_dependency_sections(member, package_name, &manifest, &policy)?; } Ok(()) } +struct PublicDependencyPolicy<'a> { + workspace_root: &'a Path, + workspace_dependencies: Option<&'a toml::value::Table>, + public_packages: &'a BTreeSet<&'a str>, + local_packages: &'a BTreeSet<&'a str>, + initial_version: &'a str, +} + fn validate_public_dependency_sections( - workspace_root: &Path, member: &str, owner: &str, manifest: &toml::Value, - workspace_dependencies: Option<&toml::value::Table>, - public_packages: &BTreeSet<&str>, - initial_version: &str, + policy: &PublicDependencyPolicy<'_>, ) -> Result<(), String> { let manifest_table = manifest .as_table() .ok_or_else(|| format!("{member}/Cargo.toml must be a TOML table"))?; for section in ["dependencies", "dev-dependencies", "build-dependencies"] { if let Some(dependencies) = manifest_table.get(section).and_then(toml::Value::as_table) { - validate_public_dependency_table( - workspace_root, - member, - owner, - section, - dependencies, - workspace_dependencies, - public_packages, - initial_version, - )?; + validate_public_dependency_table(member, owner, section, dependencies, policy)?; } } if let Some(targets) = manifest_table.get("target").and_then(toml::Value::as_table) { @@ -506,14 +506,11 @@ fn validate_public_dependency_sections( target_table.get(section).and_then(toml::Value::as_table) { validate_public_dependency_table( - workspace_root, member, owner, &format!("target.{target}.{section}"), dependencies, - workspace_dependencies, - public_packages, - initial_version, + policy, )?; } } @@ -522,16 +519,12 @@ fn validate_public_dependency_sections( Ok(()) } -#[allow(clippy::too_many_arguments)] fn validate_public_dependency_table( - workspace_root: &Path, member: &str, owner: &str, section: &str, dependencies: &toml::value::Table, - workspace_dependencies: Option<&toml::value::Table>, - public_packages: &BTreeSet<&str>, - initial_version: &str, + policy: &PublicDependencyPolicy<'_>, ) -> Result<(), String> { for (dependency_key, declaration) in dependencies { let inherits_workspace = declaration @@ -540,7 +533,8 @@ fn validate_public_dependency_table( .and_then(toml::Value::as_bool) == Some(true); let resolved = if inherits_workspace { - workspace_dependencies + policy + .workspace_dependencies .and_then(|dependencies| dependencies.get(dependency_key)) .ok_or_else(|| { format!( @@ -558,18 +552,22 @@ fn validate_public_dependency_table( .and_then(|table| table.get("package")) .and_then(toml::Value::as_str); let path_base = if inherits_workspace { - workspace_root.to_path_buf() + policy.workspace_root.to_path_buf() + } else { + policy.workspace_root.join(member) + }; + let path_package = if declared_package.is_none() { + dependency_path + .map(|path| dependency_package_name(&path_base.join(path))) + .transpose()? } else { - workspace_root.join(member) + None }; - let path_package = dependency_path - .map(|path| dependency_package_name(&path_base.join(path))) - .transpose()?; let normalized_key = dependency_key.replace('_', "-"); let dependency_name = declared_package .or(path_package.as_deref()) .unwrap_or(normalized_key.as_str()); - if !public_packages.contains(dependency_name) { + if !policy.public_packages.contains(dependency_name) { continue; } let version = match resolved { @@ -577,12 +575,135 @@ fn validate_public_dependency_table( toml::Value::Table(table) => table.get("version").and_then(toml::Value::as_str), _ => None, }; - let exact_version = format!("={initial_version}"); - if dependency_path.is_none() || version != Some(exact_version.as_str()) { + let exact_version = format!("={}", policy.initial_version); + if policy.local_packages.contains(dependency_name) + && (dependency_path.is_none() || version != Some(exact_version.as_str())) + { return Err(format!( "public package {owner} {section}.{dependency_key} dependency on {dependency_name} must declare path and exact version {exact_version}" )); } + if !policy.local_packages.contains(dependency_name) + && (dependency_path.is_some() || version != Some(exact_version.as_str())) + { + return Err(format!( + "public package {owner} {section}.{dependency_key} cross-repository dependency on {dependency_name} must declare exact registry version {exact_version} without a path" + )); + } + } + Ok(()) +} + +fn validate_no_production_sibling_paths(workspace_root: &Path) -> Result<(), String> { + let canonical_root = fs::canonicalize(workspace_root) + .map_err(|error| format!("canonicalize {}: {error}", workspace_root.display()))?; + let workspace_path = workspace_root.join("Cargo.toml"); + let workspace_raw = fs::read_to_string(&workspace_path) + .map_err(|error| format!("read {}: {error}", workspace_path.display()))?; + let workspace = toml::from_str::<WorkspaceManifest>(&workspace_raw) + .map_err(|error| format!("parse {}: {error}", workspace_path.display()))?; + let workspace_value = workspace_raw + .parse::<toml::Value>() + .map_err(|error| format!("parse {}: {error}", workspace_path.display()))?; + if let Some(dependencies) = workspace_value + .get("workspace") + .and_then(toml::Value::as_table) + .and_then(|workspace| workspace.get("dependencies")) + .and_then(toml::Value::as_table) + { + validate_dependency_path_table( + &canonical_root, + &canonical_root, + "workspace", + "workspace.dependencies", + dependencies, + )?; + } + for member in &workspace.workspace.members { + let manifest_path = workspace_root.join(member).join("Cargo.toml"); + let raw = fs::read_to_string(&manifest_path) + .map_err(|error| format!("read {}: {error}", manifest_path.display()))?; + let manifest = raw + .parse::<toml::Value>() + .map_err(|error| format!("parse {}: {error}", manifest_path.display()))?; + validate_manifest_dependency_paths( + &canonical_root, + &canonical_root.join(member), + member, + &manifest, + )?; + } + Ok(()) +} + +fn validate_manifest_dependency_paths( + workspace_root: &Path, + package_root: &Path, + owner: &str, + manifest: &toml::Value, +) -> Result<(), String> { + let Some(manifest_table) = manifest.as_table() else { + return Err(format!("{owner}/Cargo.toml must be a TOML table")); + }; + for section in ["dependencies", "dev-dependencies", "build-dependencies"] { + if let Some(dependencies) = manifest_table.get(section).and_then(toml::Value::as_table) { + validate_dependency_path_table( + workspace_root, + package_root, + owner, + section, + dependencies, + )?; + } + } + if let Some(targets) = manifest_table.get("target").and_then(toml::Value::as_table) { + for (target, target_value) in targets { + let Some(target_table) = target_value.as_table() else { + continue; + }; + for section in ["dependencies", "dev-dependencies", "build-dependencies"] { + if let Some(dependencies) = + target_table.get(section).and_then(toml::Value::as_table) + { + validate_dependency_path_table( + workspace_root, + package_root, + owner, + &format!("target.{target}.{section}"), + dependencies, + )?; + } + } + } + } + Ok(()) +} + +fn validate_dependency_path_table( + workspace_root: &Path, + path_base: &Path, + owner: &str, + section: &str, + dependencies: &toml::value::Table, +) -> Result<(), String> { + for (dependency, declaration) in dependencies { + let Some(path) = declaration + .as_table() + .and_then(|table| table.get("path")) + .and_then(toml::Value::as_str) + else { + continue; + }; + let resolved = fs::canonicalize(path_base.join(path)).map_err(|error| { + format!( + "resolve production dependency {owner} {section}.{dependency} path {path}: {error}" + ) + })?; + if !resolved.starts_with(workspace_root) { + return Err(format!( + "production dependency {owner} {section}.{dependency} path {path} escapes the repository; use a registry version or an external local-development override" + )); + } } Ok(()) } @@ -862,8 +983,8 @@ mod tests { use super::{ ArchitectureIdentity, ArchitecturePackage, ArchitectureRepository, validate_ledger, - validate_public_dependency_versions, validate_public_package_metadata, - validate_workspace_members, validate_workspace_toolchain, + validate_no_production_sibling_paths, validate_public_dependency_versions, + validate_public_package_metadata, validate_workspace_members, validate_workspace_toolchain, }; fn test_root(label: &str) -> PathBuf { @@ -1070,6 +1191,12 @@ adr_required = false architecture.package.push(ArchitecturePackage { name: "radroots-core".to_owned(), }); + architecture + .repositories + .get_mut("sdk") + .expect("sdk repository") + .packages + .push("radroots-core".to_owned()); validate_public_dependency_versions(&root, &architecture) .expect("path plus exact version public dependency"); @@ -1085,4 +1212,44 @@ adr_required = false assert!(error.contains("must declare path and exact version =0.1.0")); let _ = fs::remove_dir_all(root); } + + #[test] + fn production_dependencies_reject_sibling_paths() { + let root = test_root("production_sibling_path"); + let sibling = root.with_extension("sibling"); + fs::create_dir_all(root.join("crates/radroots")).expect("create public package"); + fs::create_dir_all(root.join("crates/dependency")).expect("create local dependency"); + fs::create_dir_all(&sibling).expect("create sibling dependency"); + fs::write( + root.join("Cargo.toml"), + complete_workspace_manifest("\"crates/radroots\""), + ) + .expect("write workspace manifest"); + let sibling_path = sibling.to_string_lossy(); + fs::write( + root.join("crates/radroots/Cargo.toml"), + format!( + "[package]\nname = \"radroots\"\nversion = \"0.1.0\"\n\n[dependencies]\nprobe = {{ path = \"{sibling_path}\" }}\n" + ), + ) + .expect("write sibling dependency"); + let error = validate_no_production_sibling_paths(&root) + .expect_err("sibling production path must fail"); + assert!(error.contains("escapes the repository")); + + fs::write( + root.join("crates/radroots/Cargo.toml"), + "[package]\nname = \"radroots\"\nversion = \"0.1.0\"\n\n[dependencies]\nprobe = { path = \"../dependency\" }\n", + ) + .expect("write local dependency"); + fs::create_dir_all(root.join(".cargo")).expect("create cargo config directory"); + fs::write( + root.join(".cargo/config.toml"), + format!("[patch.crates-io]\nprobe = {{ path = \"{sibling_path}\" }}\n"), + ) + .expect("write development override"); + validate_no_production_sibling_paths(&root).expect("in-repository path"); + let _ = fs::remove_dir_all(root); + let _ = fs::remove_dir_all(sibling); + } }