commit c46b39be5ee7517f238ad8de29853fd3296eae3b
parent 63f396706f8e32f8ae15e2f342d3fca3054e40c4
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:
3 files changed, 128 insertions(+), 1 deletion(-)
diff --git a/tools/sdk_xtask_import/Cargo.toml b/tools/sdk_xtask_import/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/sdk_xtask_import/src/api_qualification.rs b/tools/sdk_xtask_import/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/sdk_xtask_import/src/main.rs b/tools/sdk_xtask_import/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()
}