sdk

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

commit 6d68bdd73aca544d0d4cc88f785b38a22d4b85d6
parent a3781823586e4a61c49afa4fb0a8396dd309afe7
Author: triesap <tyson@radroots.org>
Date:   Mon,  3 Aug 2026 23:30:33 +0000

release: qualify front-door public APIs

- pin the facade and SDK API baseline to the portable checkpoint
- require cargo-semver-checks at the governed minimum version
- execute all-feature minor-release comparisons for both front doors
- preserve locked automation dependencies and standalone architecture checks

Diffstat:
MCargo.lock | 1+
Acontracts/releases/api_semver.toml | 7+++++++
Mtools/xtask/Cargo.toml | 1+
Atools/xtask/src/api_qualification.rs | 120+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtools/xtask/src/main.rs | 8+++++++-
5 files changed, 136 insertions(+), 1 deletion(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -2113,6 +2113,7 @@ dependencies = [ "radroots_identity_bindings", "radroots_protocol", "radroots_replica_schema_bindings", + "semver", "serde", "serde_json", "sha2", diff --git a/contracts/releases/api_semver.toml b/contracts/releases/api_semver.toml @@ -0,0 +1,7 @@ +schema_version = 1 +baseline_revision = "a378182" +tool = "cargo-semver-checks" +minimum_tool_version = "0.50.0" +release_type = "minor" +feature_policy = "all" +packages = ["radroots", "radroots_sdk"] diff --git a/tools/xtask/Cargo.toml b/tools/xtask/Cargo.toml @@ -26,6 +26,7 @@ radroots_replica_schema_bindings = { path = "../../crates/replica_schema_binding flate2 = "1" serde_json = "1" serde = { workspace = true, features = ["derive"] } +semver = "1" sha2 = { workspace = true } syn = { version = "2", features = ["full", "visit"] } tar = "0.4" diff --git a/tools/xtask/src/api_qualification.rs b/tools/xtask/src/api_qualification.rs @@ -0,0 +1,120 @@ +use std::{collections::BTreeSet, fs, path::Path, process::Command}; + +use serde::Deserialize; + +#[derive(Debug, Deserialize)] +struct Contract { + schema_version: u16, + baseline_revision: String, + tool: String, + minimum_tool_version: String, + release_type: String, + feature_policy: String, + packages: Vec<String>, +} + +pub fn run(root: &Path) -> Result<(), String> { + let contract = load(root)?; + validate(&contract)?; + verify_tool(&contract)?; + verify_revision(root, &contract.baseline_revision)?; + for package in &contract.packages { + let args = invocation(&contract, package); + eprintln!("cargo {}", args.join(" ")); + let status = Command::new("cargo") + .args(&args) + .current_dir(root) + .status() + .map_err(|error| format!("failed to start cargo-semver-checks: {error}"))?; + if !status.success() { + return Err(format!("public API qualification failed for {package}")); + } + } + Ok(()) +} + +fn load(root: &Path) -> Result<Contract, String> { + let path = root.join("contracts/releases/api_semver.toml"); + let raw = fs::read_to_string(&path) + .map_err(|error| format!("failed to read {}: {error}", path.display()))?; + toml::from_str(&raw).map_err(|error| format!("failed to parse {}: {error}", path.display())) +} + +fn validate(contract: &Contract) -> Result<(), String> { + let unique = contract.packages.iter().collect::<BTreeSet<_>>(); + if contract.schema_version != 1 + || contract.tool != "cargo-semver-checks" + || contract.release_type != "minor" + || contract.feature_policy != "all" + || contract.baseline_revision.len() < 7 + || unique.len() != 2 + || unique.len() != contract.packages.len() + { + return Err("invalid SDK public API qualification contract".to_owned()); + } + Ok(()) +} + +fn verify_tool(contract: &Contract) -> Result<(), String> { + let output = Command::new("cargo") + .args(["semver-checks", "--version"]) + .output() + .map_err(|error| format!("failed to start cargo-semver-checks: {error}"))?; + if !output.status.success() { + return Err("cargo-semver-checks is required".to_owned()); + } + let stdout = String::from_utf8_lossy(&output.stdout); + let installed = stdout + .split_whitespace() + .find_map(|value| semver::Version::parse(value).ok()) + .ok_or_else(|| format!("could not parse cargo-semver-checks version: {stdout}"))?; + let minimum = semver::Version::parse(&contract.minimum_tool_version) + .map_err(|error| format!("invalid minimum tool version: {error}"))?; + if installed < minimum { + return Err(format!( + "cargo-semver-checks {} or newer is required, found {installed}", + contract.minimum_tool_version + )); + } + Ok(()) +} + +fn verify_revision(root: &Path, revision: &str) -> Result<(), String> { + let status = Command::new("git") + .args(["cat-file", "-e", &format!("{revision}^{{commit}}")]) + .current_dir(root) + .status() + .map_err(|error| format!("failed to inspect API baseline revision: {error}"))?; + if status.success() { + Ok(()) + } else { + Err(format!("API baseline revision {revision} is unavailable")) + } +} + +fn invocation(contract: &Contract, package: &str) -> Vec<String> { + vec![ + "semver-checks".to_owned(), + "check-release".to_owned(), + "--package".to_owned(), + package.to_owned(), + "--baseline-rev".to_owned(), + contract.baseline_revision.clone(), + "--all-features".to_owned(), + "--release-type".to_owned(), + contract.release_type.clone(), + ] +} + +#[cfg(test)] +mod tests { + use super::{invocation, load, validate}; + + #[test] + fn current_contract_covers_both_front_doors() { + let root = crate::fs::workspace_root().expect("workspace root"); + let contract = load(&root).expect("contract"); + validate(&contract).expect("valid contract"); + assert!(invocation(&contract, "radroots").contains(&"--all-features".to_owned())); + } +} diff --git a/tools/xtask/src/main.rs b/tools/xtask/src/main.rs @@ -1,3 +1,4 @@ +mod api_qualification; mod architecture; mod bindings; mod check; @@ -33,6 +34,7 @@ enum CommandAction<'a> { GeneratePackageMetadata, Coverage(&'a [String]), QualifyFeatures, + QualifyApi, QualifyPortable, QualifyTargets, Check, @@ -70,6 +72,7 @@ fn run(args: impl IntoIterator<Item = String>) -> Result<(), String> { CommandAction::QualifyFeatures => { release_qualification::run_feature_matrix(&fs::workspace_root()?) } + CommandAction::QualifyApi => api_qualification::run(&fs::workspace_root()?), CommandAction::QualifyPortable => portable_qualification::run(&fs::workspace_root()?), CommandAction::QualifyTargets => target_qualification::run(&fs::workspace_root()?), CommandAction::Check => check::check(), @@ -102,6 +105,9 @@ fn command_action(args: &[String]) -> Result<CommandAction<'_>, String> { [command, target] if command == "release" && target == "qualify-features" => { Ok(CommandAction::QualifyFeatures) } + [command, target] if command == "release" && target == "qualify-api" => { + Ok(CommandAction::QualifyApi) + } [command, target] if command == "release" && target == "qualify-portable" => { Ok(CommandAction::QualifyPortable) } @@ -116,7 +122,7 @@ fn command_action(args: &[String]) -> Result<CommandAction<'_>, String> { } fn usage() -> String { - "usage: cargo xtask architecture | cargo xtask architecture-ci | cargo xtask check-api-boundaries | cargo xtask check-dependency-boundaries | cargo xtask generate | cargo xtask generate ts | cargo xtask generate wasm [--package <key>] | cargo xtask generate bindings <swift|kotlin> | cargo xtask generate package-metadata | cargo xtask check | cargo xtask smoke facade-rust-local | sdk-rust-local | front-doors-rust-local | cargo xtask coverage run | cargo xtask release qualify-features | cargo xtask release qualify-portable | cargo xtask release qualify-targets" + "usage: cargo xtask architecture | cargo xtask architecture-ci | cargo xtask check-api-boundaries | cargo xtask check-dependency-boundaries | cargo xtask generate | cargo xtask generate ts | cargo xtask generate wasm [--package <key>] | cargo xtask generate bindings <swift|kotlin> | cargo xtask generate package-metadata | cargo xtask check | cargo xtask smoke facade-rust-local | sdk-rust-local | front-doors-rust-local | cargo xtask coverage run | cargo xtask release qualify-features | cargo xtask release qualify-api | cargo xtask release qualify-portable | cargo xtask release qualify-targets" .to_owned() }