lib

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

commit 853eb9704cff329effb47549d70654df2e408404
parent 32cc941b99c79002f34e28b8fcc40b8d7cc11f96
Author: triesap <tyson@radroots.org>
Date:   Sat, 13 Jun 2026 16:51:54 -0700

contract: harden sdk ownership metadata

- remove ignored consumer SDK ownership tables
- model rr-rs surface provenance explicitly in xtask
- reject stale consumer SDK tables during contract load
- mark replica WASM bindings as external SDK artifacts

Diffstat:
Mcrates/xtask/src/contract.rs | 142++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Mspec/README.md | 20+++++++++++++-------
Mspec/manifest.toml | 12+++---------
Mspec/operations.toml | 12------------
Mspec/replica.toml | 2+-
5 files changed, 158 insertions(+), 30 deletions(-)

diff --git a/crates/xtask/src/contract.rs b/crates/xtask/src/contract.rs @@ -19,6 +19,7 @@ const EVENT_BOUNDARY_MATRIX_RELATIVES: [&str; 1] = [ ]; #[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] pub struct ContractManifest { pub contract: ManifestContract, pub surface: Surface, @@ -34,10 +35,31 @@ pub struct ManifestContract { } #[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] pub struct Surface { pub model_crates: Vec<String>, pub algorithm_crates: Vec<String>, pub wasm_crates: Vec<String>, + pub rust_crate_tiers: Option<RustCrateTiers>, + pub internal_replica_crates: Option<InternalReplicaCrates>, +} + +#[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct RustCrateTiers { + pub advanced_substrate: Vec<String>, + pub published_support: Vec<String>, + pub deferred_publication: Vec<String>, +} + +#[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct InternalReplicaCrates { + pub schema: String, + pub storage: String, + pub external_storage_wasm_binding_crate: String, + pub sync: String, + pub external_sync_wasm_binding_crate: String, } #[derive(Debug, Deserialize)] @@ -58,6 +80,7 @@ pub struct ManifestLanguagePackages { } #[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] pub struct OperationsContractManifest { pub contract: ManifestContract, pub public: PublicContract, @@ -2535,6 +2558,70 @@ fn validate_sdk_rollout(mapping: &SdkExportMapping) -> Result<(), String> { Ok(()) } +fn validate_crate_identifier(value: &str, field: &str) -> Result<(), String> { + let trimmed = value.trim(); + if trimmed.is_empty() { + return Err(format!("{field} is required")); + } + if trimmed != value + || trimmed.contains('/') + || trimmed.contains('\\') + || trimmed.contains("..") + || trimmed == "radroots_sdk" + { + return Err(format!("{field} must be a crate identifier")); + } + Ok(()) +} + +fn validate_surface_metadata(surface: &Surface) -> Result<(), String> { + if let Some(tiers) = &surface.rust_crate_tiers { + let mut tier_crates = BTreeSet::new(); + for (field, crates) in [ + ( + "surface.rust_crate_tiers.advanced_substrate", + &tiers.advanced_substrate, + ), + ( + "surface.rust_crate_tiers.published_support", + &tiers.published_support, + ), + ( + "surface.rust_crate_tiers.deferred_publication", + &tiers.deferred_publication, + ), + ] { + let entries = collect_unique_set(crates, field)?; + if entries.is_empty() { + return Err(format!("{field} must not be empty")); + } + for crate_name in entries { + if !tier_crates.insert(crate_name.clone()) { + return Err(format!( + "surface.rust_crate_tiers has duplicate crate {crate_name}" + )); + } + } + } + } + + if let Some(replica) = &surface.internal_replica_crates { + validate_crate_identifier(&replica.schema, "surface.internal_replica_crates.schema")?; + validate_crate_identifier(&replica.storage, "surface.internal_replica_crates.storage")?; + validate_crate_identifier(&replica.sync, "surface.internal_replica_crates.sync")?; + validate_crate_identifier( + &replica.external_storage_wasm_binding_crate, + "surface.internal_replica_crates.external_storage_wasm_binding_crate", + )?; + validate_crate_identifier( + &replica.external_sync_wasm_binding_crate, + "surface.internal_replica_crates.external_sync_wasm_binding_crate", + )?; + } + + Ok(()) +} + fn validate_operations_contract( bundle: &ContractBundle, operations_manifest: &OperationsContractManifest, @@ -3612,6 +3699,7 @@ fn validate_contract_bundle_with_release_policy_override( if bundle.manifest.surface.algorithm_crates.is_empty() { return Err("contract surface.algorithm_crates must not be empty".to_string()); } + validate_surface_metadata(&bundle.manifest.surface)?; validate_export_mappings(bundle)?; if bundle.version.contract.version.trim().is_empty() { return Err("version.contract.version is required".to_string()); @@ -3980,6 +4068,7 @@ pub fn validate_contract_bundle(bundle: &ContractBundle) -> Result<(), String> { if bundle.manifest.surface.algorithm_crates.is_empty() { return Err("contract surface.algorithm_crates must not be empty".to_string()); } + validate_surface_metadata(&bundle.manifest.surface)?; validate_export_mappings(bundle)?; if bundle.version.contract.version.trim().is_empty() { return Err("version.contract.version is required".to_string()); @@ -5556,7 +5645,10 @@ edition = "2024" let assert_bundle_error = |expected: &str, mutator: fn(&mut ContractBundle)| { let mut bundle = load_contract_bundle(&root).expect("load bundle"); mutator(&mut bundle); - let err = validate_contract_bundle(&bundle).expect_err("bundle validation error"); + let err = match validate_contract_bundle(&bundle) { + Ok(()) => panic!("expected bundle validation error: {expected}"), + Err(err) => err, + }; assert!(err.contains(expected), "expected `{expected}` in `{err}`"); }; @@ -5576,6 +5668,18 @@ edition = "2024" bundle.manifest.surface.algorithm_crates.clear(); }); assert_bundle_error( + "surface.internal_replica_crates.external_storage_wasm_binding_crate must be a crate identifier", + |bundle| { + bundle.manifest.surface.internal_replica_crates = Some(InternalReplicaCrates { + schema: "radroots_replica_db_schema".to_string(), + storage: "radroots_replica_db".to_string(), + external_storage_wasm_binding_crate: "crates/replica_db_wasm".to_string(), + sync: "radroots_replica_sync".to_string(), + external_sync_wasm_binding_crate: "radroots_replica_sync_wasm".to_string(), + }); + }, + ); + assert_bundle_error( "at least one language export mapping is required", |bundle| { bundle.exports.clear(); @@ -5677,6 +5781,42 @@ edition = "2024" } #[test] + fn load_contract_bundle_rejects_stale_consumer_sdk_tables() { + let stale_manifest_root = create_synthetic_workspace("stale_manifest_consumer_sdk"); + let manifest_path = stale_manifest_root.join("spec").join("manifest.toml"); + let mut manifest = fs::read_to_string(&manifest_path).expect("manifest"); + manifest.push_str( + r#" +[consumer_sdk] +rust_package = "radroots_sdk" +"#, + ); + write_file(&manifest_path, &manifest); + let manifest_err = + load_contract_bundle(&stale_manifest_root).expect_err("stale manifest table"); + assert!(manifest_err.contains("manifest.toml")); + assert!(manifest_err.contains("consumer_sdk")); + let _ = fs::remove_dir_all(stale_manifest_root); + + let stale_operations_root = create_synthetic_workspace("stale_operations_consumer_sdk"); + add_operation_contract_files(&stale_operations_root); + let operations_path = stale_operations_root.join("spec").join("operations.toml"); + let mut operations = fs::read_to_string(&operations_path).expect("operations"); + operations.push_str( + r#" +[consumer_sdk] +rust_package = "radroots_sdk" +"#, + ); + write_file(&operations_path, &operations); + let operations_err = + load_contract_bundle(&stale_operations_root).expect_err("stale operations table"); + assert!(operations_err.contains("operations.toml")); + assert!(operations_err.contains("consumer_sdk")); + let _ = fs::remove_dir_all(stale_operations_root); + } + + #[test] fn validate_contract_bundle_reports_operation_contract_errors() { let root = create_synthetic_workspace("operation_contract_bundle_errors"); add_operation_contract_files(&root); diff --git a/spec/README.md b/spec/README.md @@ -11,15 +11,20 @@ machine-verifiable. ## Contract Surface -SDK contract metadata is defined in `spec/manifest.toml` and currently includes: +Core contract metadata is defined in `spec/manifest.toml` and currently includes: - model crates: `radroots_core`, `radroots_events`, `radroots_trade`, `radroots_identity` - algorithm crate: `radroots_events_codec` -The first-class Rust SDK and WebAssembly package surfaces are owned by the SDK -repository. The crate list above records rr-rs implementation provenance for the -core contract surface; it is not a promise that every listed crate is a -first-class end-user SDK package. +The first-class Rust SDK and WebAssembly package surfaces are owned outside +rr-rs by the SDK repository. The crate list above records rr-rs implementation +provenance for the core contract surface; it is not a promise that every listed +crate is a first-class end-user SDK package. + +`spec/manifest.toml` and `spec/operations.toml` do not carry consumer SDK +ownership tables. Curated language package authority lives under +`spec/sdk-exports/`; SDK-owned Rust and WebAssembly package assembly lives in +the SDK repository. Public SDK exports are intentionally narrower than the full Rust workspace. @@ -155,8 +160,9 @@ Internal replica crate family: - `radroots_replica_db` - `radroots_replica_sync` -SDK-owned wasm bindings for replica storage and sync are recorded in -`spec/replica.toml`. +External SDK-owned wasm binding artifact identifiers for replica storage and +sync are recorded in `spec/replica.toml`. They are provenance identifiers, not +local rr-rs crate paths or runnable package commands. ## Governance diff --git a/spec/manifest.toml b/spec/manifest.toml @@ -13,13 +13,7 @@ model_crates = [ algorithm_crates = ["radroots_events_codec"] wasm_crates = [] -[consumer_sdk] -rust_package = "radroots_sdk" -public_surface = "operation_first" -website_ingest_contract = true - -[consumer_sdk.rust_story] -entrypoints = ["radroots_sdk"] +[surface.rust_crate_tiers] advanced_substrate = [ "radroots_core", "radroots_events", @@ -65,9 +59,9 @@ deferred_publication = [ [surface.internal_replica_crates] schema = "radroots_replica_db_schema" storage = "radroots_replica_db" -storage_wasm_binding_crate = "radroots_replica_db_wasm" +external_storage_wasm_binding_crate = "radroots_replica_db_wasm" sync = "radroots_replica_sync" -sync_wasm_binding_crate = "radroots_replica_sync_wasm" +external_sync_wasm_binding_crate = "radroots_replica_sync_wasm" [export.ts] packages = ["@radroots/sdk"] diff --git a/spec/operations.toml b/spec/operations.toml @@ -55,18 +55,6 @@ model_crates = [ algorithm_crates = ["radroots_events_codec"] wasm_crates = [] -[consumer_sdk] -rust_package = "radroots_sdk" -primary_domains = [ - "profile", - "farm", - "listing", - "order", - "trade_validation", - "social", -] -public_surface = "operation_first" - [operations.profile_build_draft] domain = "profile" id = "profile.build_draft" diff --git a/spec/replica.toml b/spec/replica.toml @@ -8,7 +8,7 @@ schema = "radroots_replica_db_schema" storage = "radroots_replica_db" sync = "radroots_replica_sync" -[sdk_wasm_bindings] +[external_sdk_wasm_bindings] storage = "radroots_replica_db_wasm" sync = "radroots_replica_sync_wasm"