sdk

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

commit 4b653ca15108b173617adc7842684e01190dfdea
parent 2f960e1053ef0e0354d4575b9cd14bfdbe7739d0
Author: triesap <tyson@radroots.org>
Date:   Mon, 27 Jul 2026 07:32:22 +0000

release: freeze package publication during refactor

- Make every SDK workspace package explicitly non-publishable.
- Add the two-package Step 305 enablement contract.
- Enforce the freeze in the repository-wide SDK check.
- Test frozen, implicit, approved, and extra package states.

Diffstat:
MAGENTS.md | 2+-
Acontracts/releases/publication.toml | 5+++++
Mcrates/runtime_contract_v1/Cargo.toml | 1+
Mcrates/sdk/Cargo.toml | 2+-
Adocs/implementation/PUBLICATION_FREEZE.md | 24++++++++++++++++++++++++
Mtools/xtask/src/check.rs | 209+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
6 files changed, 237 insertions(+), 6 deletions(-)

diff --git a/AGENTS.md b/AGENTS.md @@ -68,7 +68,7 @@ overrides this file for its subtree. native Rust module layout. - During migration, every package remains non-publishable until its package-realistic release gates pass and publication is explicitly - authorized. + authorized. Follow `docs/implementation/PUBLICATION_FREEZE.md`. ## Commits, deviations, and irreversible actions diff --git a/contracts/releases/publication.toml b/contracts/releases/publication.toml @@ -0,0 +1,5 @@ +[publication] +frozen = true +registry = "crates-io" +final_enablement_step = 305 +approved_packages = ["radroots-sdk", "radroots"] diff --git a/crates/runtime_contract_v1/Cargo.toml b/crates/runtime_contract_v1/Cargo.toml @@ -1,5 +1,6 @@ [package] name = "radroots_runtime_contract_v1" +publish = false description = "Runtime Contract V1 descriptors for Radroots SDK hosts" version.workspace = true edition.workspace = true diff --git a/crates/sdk/Cargo.toml b/crates/sdk/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "radroots_sdk" -publish = ["crates-io"] +publish = false version.workspace = true edition.workspace = true authors = ["Tyson Lupul <tyson@radroots.org>"] diff --git a/docs/implementation/PUBLICATION_FREEZE.md b/docs/implementation/PUBLICATION_FREEZE.md @@ -0,0 +1,24 @@ +# Crates.io publication freeze + +Crates.io publication is frozen for the complete release-v1 crate refactor. +Every workspace package, including `radroots-sdk` and the future `radroots` +facade, must set: + +```toml +publish = false +``` + +`contracts/releases/publication.toml` is the machine authority for this +freeze. `cargo xtask check` rejects a missing publication policy, an unexpected +registry or enablement checkpoint, and any package that is implicitly or +explicitly publishable. + +The only planned exception is release plan Step 305, after the final package +inventory, resolved public dependency graph, API surface, target matrix, and +security gates are green. That checkpoint may set `publication.frozen = false` +and enable exactly `radroots-sdk` and `radroots` for package-validation +staging. It does not authorize upload or any crates.io mutation. + +Changing the freeze requires an independently reviewed release-control commit. +Actual publication, tag creation, registry ownership changes, and +trusted-publisher changes always require separate explicit authorization. diff --git a/tools/xtask/src/check.rs b/tools/xtask/src/check.rs @@ -64,9 +64,23 @@ struct CargoMetadataPackage { name: String, } +#[derive(Debug, Deserialize)] +struct PublicationPolicyFile { + publication: PublicationPolicy, +} + +#[derive(Debug, Deserialize)] +struct PublicationPolicy { + frozen: bool, + registry: String, + final_enablement_step: u16, + approved_packages: Vec<String>, +} + pub fn check() -> Result<(), String> { validate_package_matrix()?; let root = workspace_root()?; + check_publication_policy(&root)?; validate_sdk_contracts(&root)?; check_sdk_feature_matrix(&root)?; check_forbidden_packages(&root)?; @@ -79,6 +93,87 @@ pub fn check() -> Result<(), String> { Ok(()) } +fn check_publication_policy(root: &Path) -> Result<(), String> { + let policy_path = root.join("contracts/releases/publication.toml"); + let raw = fs::read_to_string(&policy_path) + .map_err(|error| format!("failed to read {}: {error}", policy_path.display()))?; + let policy = toml::from_str::<PublicationPolicyFile>(&raw) + .map_err(|error| format!("failed to parse {}: {error}", policy_path.display()))? + .publication; + if policy.registry != "crates-io" { + return Err("publication.registry must be crates-io".to_owned()); + } + if policy.final_enablement_step != 305 { + return Err("publication.final_enablement_step must be 305".to_owned()); + } + let approved_package_count = policy.approved_packages.len(); + let approved = policy + .approved_packages + .into_iter() + .collect::<BTreeSet<_>>(); + if approved.len() != approved_package_count { + return Err("publication.approved_packages must not contain duplicates".to_owned()); + } + if approved != BTreeSet::from(["radroots".to_owned(), "radroots-sdk".to_owned()]) { + return Err( + "publication.approved_packages must contain exactly radroots-sdk and radroots" + .to_owned(), + ); + } + + let mut workspace_packages = BTreeSet::new(); + for path in sdk_manifest_paths(root)?.into_iter().skip(1) { + let raw = fs::read_to_string(&path) + .map_err(|error| format!("failed to read {}: {error}", path.display()))?; + let manifest = raw + .parse::<toml::Value>() + .map_err(|error| format!("failed to parse {}: {error}", path.display()))?; + let package = manifest + .get("package") + .and_then(toml::Value::as_table) + .ok_or_else(|| format!("{} must define [package]", path.display()))?; + let name = package + .get("name") + .and_then(toml::Value::as_str) + .ok_or_else(|| format!("{} package.name must be a string", path.display()))?; + if !workspace_packages.insert(name.to_owned()) { + return Err(format!("duplicate workspace package name {name}")); + } + let publish = package.get("publish"); + if policy.frozen { + if publish.and_then(toml::Value::as_bool) != Some(false) { + return Err(format!( + "publication freeze requires workspace package {name} to set publish = false" + )); + } + } else if approved.contains(name) { + let registries = publish.and_then(toml::Value::as_array).ok_or_else(|| { + format!("approved package {name} must set publish = [\"crates-io\"]") + })?; + if registries.len() != 1 || registries[0].as_str() != Some("crates-io") { + return Err(format!( + "approved package {name} must set publish = [\"crates-io\"]" + )); + } + } else if publish.and_then(toml::Value::as_bool) != Some(false) { + return Err(format!( + "non-approved workspace package {name} must set publish = false" + )); + } + } + if !policy.frozen && !approved.is_subset(&workspace_packages) { + let missing = approved + .difference(&workspace_packages) + .cloned() + .collect::<Vec<_>>() + .join(", "); + return Err(format!( + "publication enablement is missing approved workspace packages: {missing}" + )); + } + Ok(()) +} + fn check_sdk_feature_matrix(root: &Path) -> Result<(), String> { let path = root.join("crates/sdk/Cargo.toml"); let raw = fs::read_to_string(&path) @@ -1674,10 +1769,11 @@ mod tests { PackedPackage, check_binding_crate_sources, check_generated_package_artifact_inventory, check_manifest_reticulum_dependency_boundaries, check_no_typescript_files, check_package_distribution_metadata, check_package_index, check_package_json, - check_package_surface_artifacts, check_packed_package_json, check_reticulum_feature_entry, - check_wasm_package_surface, consumer_smoke_script, expected_packed_dist_files, - is_forbidden_reticulum_runtime_name, normalized_package_path, parse_pnpm_pack_entry, - read_packed_package_json, validate_npm_pack_payload, validate_packed_dist_inventory, + check_package_surface_artifacts, check_packed_package_json, check_publication_policy, + check_reticulum_feature_entry, check_wasm_package_surface, consumer_smoke_script, + expected_packed_dist_files, is_forbidden_reticulum_runtime_name, normalized_package_path, + parse_pnpm_pack_entry, read_packed_package_json, validate_npm_pack_payload, + validate_packed_dist_inventory, }; #[test] @@ -1686,6 +1782,111 @@ mod tests { } #[test] + fn publication_freeze_rejects_publishable_or_implicit_packages() { + let root = test_root("publication_freeze"); + fs::create_dir_all(root.join("contracts/releases")).expect("create contracts"); + fs::create_dir_all(root.join("crates/a")).expect("create crate a"); + fs::create_dir_all(root.join("crates/b")).expect("create crate b"); + fs::write( + root.join("Cargo.toml"), + "[workspace]\nmembers = [\"crates/a\", \"crates/b\"]\n", + ) + .expect("write workspace"); + fs::write( + root.join("contracts/releases/publication.toml"), + r#"[publication] +frozen = true +registry = "crates-io" +final_enablement_step = 305 +approved_packages = ["radroots-sdk", "radroots"] +"#, + ) + .expect("write policy"); + fs::write( + root.join("crates/a/Cargo.toml"), + "[package]\nname = \"crate-a\"\nversion = \"0.1.0\"\npublish = false\n", + ) + .expect("write crate a"); + fs::write( + root.join("crates/b/Cargo.toml"), + "[package]\nname = \"crate-b\"\nversion = \"0.1.0\"\npublish = false\n", + ) + .expect("write crate b"); + check_publication_policy(&root).expect("private workspace satisfies freeze"); + + fs::write( + root.join("crates/a/Cargo.toml"), + "[package]\nname = \"crate-a\"\nversion = \"0.1.0\"\npublish = [\"crates-io\"]\n", + ) + .expect("make crate a publishable"); + let publishable = + check_publication_policy(&root).expect_err("publishable crate must fail freeze"); + assert!(publishable.contains("publication freeze requires workspace package crate-a")); + + fs::write( + root.join("crates/a/Cargo.toml"), + "[package]\nname = \"crate-a\"\nversion = \"0.1.0\"\n", + ) + .expect("remove publish control"); + let implicit = + check_publication_policy(&root).expect_err("implicit publishability must fail freeze"); + assert!(implicit.contains("publication freeze requires workspace package crate-a")); + + let _ = fs::remove_dir_all(root); + } + + #[test] + fn publication_enablement_allows_only_the_two_approved_packages() { + let root = test_root("publication_enablement"); + fs::create_dir_all(root.join("contracts/releases")).expect("create contracts"); + for member in ["sdk", "facade", "tool"] { + fs::create_dir_all(root.join("crates").join(member)).expect("create member"); + } + fs::write( + root.join("Cargo.toml"), + "[workspace]\nmembers = [\"crates/sdk\", \"crates/facade\", \"crates/tool\"]\n", + ) + .expect("write workspace"); + fs::write( + root.join("contracts/releases/publication.toml"), + r#"[publication] +frozen = false +registry = "crates-io" +final_enablement_step = 305 +approved_packages = ["radroots-sdk", "radroots"] +"#, + ) + .expect("write policy"); + fs::write( + root.join("crates/sdk/Cargo.toml"), + "[package]\nname = \"radroots-sdk\"\nversion = \"0.1.0\"\npublish = [\"crates-io\"]\n", + ) + .expect("write SDK manifest"); + fs::write( + root.join("crates/facade/Cargo.toml"), + "[package]\nname = \"radroots\"\nversion = \"0.1.0\"\npublish = [\"crates-io\"]\n", + ) + .expect("write facade manifest"); + fs::write( + root.join("crates/tool/Cargo.toml"), + "[package]\nname = \"build-tool\"\nversion = \"0.1.0\"\npublish = false\n", + ) + .expect("write tool manifest"); + check_publication_policy(&root).expect("approved package staging should pass"); + + fs::write( + root.join("crates/tool/Cargo.toml"), + "[package]\nname = \"build-tool\"\nversion = \"0.1.0\"\npublish = [\"crates-io\"]\n", + ) + .expect("make tool publishable"); + let extra = check_publication_policy(&root) + .expect_err("non-approved publishable package must fail enablement"); + assert!(extra.contains("non-approved workspace package build-tool")); + + let _ = fs::remove_dir_all(root); + } + + #[test] fn reticulum_gate_allows_first_party_preview_dependency_only() { let manifest = manifest_value( r#"