commit 2fd262f2c8f4306c622437bc0c4c36dbfcb331e1
parent f7f456d906d91aadbb9fac4850afaa8c5723c3e9
Author: triesap <tyson@radroots.org>
Date: Wed, 5 Aug 2026 23:38:54 +0000
consolidation: freeze the migration baseline
- pin source repositories and compatibility surface trees
- inventory canonical consumers and the additional Studio source
- map every handoff step to its corrected checkpoint owner
- validate identities revisions paths and complete step coverage
Diffstat:
5 files changed, 1038 insertions(+), 1 deletion(-)
diff --git a/contracts/consolidation/baseline.v1.toml b/contracts/consolidation/baseline.v1.toml
@@ -0,0 +1,336 @@
+schema_version = 1
+baseline_id = "radroots.rust.consolidation.baseline.v1"
+architecture_target = "radroots.crates.release.v2"
+captured_date = "2026-08-05"
+public_package_version = "0.1.0-alpha"
+expected_public_packages = 19
+expected_handoff_steps = 275
+
+[[repository]]
+id = "lib"
+canonical_url = "https://github.com/radrootslabs/lib"
+commit = "f7f456d906d91aadbb9fac4850afaa8c5723c3e9"
+tree = "58b76af0611f0851b43d0d42f74194641846d989"
+branch = "master"
+clean = true
+origin_synchronized = true
+worktree_count = 1
+rust_version = "1.97.1"
+resolver = "3"
+package_version = "0.1.0-alpha"
+commands = [
+ "nix flake check",
+ "nix run .#contract",
+ "nix run .#release-preflight",
+ "cargo xtask architecture-ci",
+]
+
+[[repository]]
+id = "sdk"
+canonical_url = "https://github.com/radrootslabs/sdk"
+commit = "170ecf2b620107fadca85fcb11fbf3798b4ca1ef"
+tree = "26239851ebeda25e27a03f93720ca2f7c9170b0b"
+branch = "master"
+clean = true
+origin_synchronized = true
+worktree_count = 1
+rust_version = "1.97.1"
+resolver = "3"
+package_version = "0.1.0-alpha"
+commands = [
+ "cargo xtask check",
+ "cargo test --workspace --locked",
+ "pnpm check",
+]
+
+[[repository]]
+id = "app_rt"
+canonical_url = "https://github.com/radrootslabs/app_rt"
+commit = "7ab1a8624d50890d6d18545ffb47d8083afa8c67"
+tree = "7450d695ad285f4b30f151529236019765dd8f08"
+branch = "master"
+clean = true
+origin_synchronized = true
+worktree_count = 1
+rust_version = "1.97.1"
+resolver = "2"
+package_version = "0.1.0-alpha.1"
+commands = [
+ "cargo fmt --all -- --check",
+ "cargo check --workspace --all-targets --locked",
+ "cargo test --workspace --locked",
+]
+
+[[repository]]
+id = "studio_app"
+canonical_url = "https://github.com/radrootslabs/studio_app"
+commit = "2b5fe5d8321fd0e248a9d304651b1ba54ee6a180"
+tree = "81d222e691c4d3d138edba45550c402e0fba1a84"
+branch = "master"
+clean = true
+origin_synchronized = true
+worktree_count = 1
+rust_version = "1.97.1"
+resolver = "3"
+package_version = "0.1.0-alpha"
+commands = [
+ "make format",
+ "make check",
+ "make bindings",
+ "make audit",
+]
+
+[[surface]]
+repository = "lib"
+path = "docs/api"
+tree = "c8ab68354fd72857372ca9b66386319ac78ab4b4"
+authority = "public_rust_api"
+
+[[surface]]
+repository = "lib"
+path = "contracts"
+tree = "12b282ec60ca33359ed7346f717355c159f3ff13"
+authority = "public_contracts"
+
+[[surface]]
+repository = "sdk"
+path = "crates/sdk"
+tree = "673008e55dff4dc31fdfb040a5ff1ef5cca80f84"
+authority = "sdk_public_api"
+
+[[surface]]
+repository = "sdk"
+path = "crates/radroots"
+tree = "f91e1bcab7b926eb4ae78c19460092c4feee6092"
+authority = "facade_public_api"
+
+[[surface]]
+repository = "sdk"
+path = "crates/core_bindings"
+tree = "2a63164be43ecc7ce8a219c557174920fa3d62b0"
+authority = "core_bindings"
+
+[[surface]]
+repository = "sdk"
+path = "crates/event_bindings"
+tree = "bd4109562147075a276027be0c2c1d228d4507c1"
+authority = "event_bindings"
+
+[[surface]]
+repository = "sdk"
+path = "crates/identity_bindings"
+tree = "1a56a964b0c950d2a7940220548d8390ff7c37b7"
+authority = "identity_bindings"
+
+[[surface]]
+repository = "sdk"
+path = "crates/trade_bindings"
+tree = "7eaed407b9332b14cd47df5c25925428eeff7c68"
+authority = "trade_bindings"
+
+[[surface]]
+repository = "sdk"
+path = "crates/replica_schema_bindings"
+tree = "723ec9da7e442c8d34fc309d28e75a135f897c40"
+authority = "replica_schema_bindings"
+
+[[surface]]
+repository = "sdk"
+path = "crates/event_codec_wasm"
+tree = "5bd41a7a90341217111535082511058e3347f182"
+authority = "event_codec_wasm"
+
+[[surface]]
+repository = "sdk"
+path = "crates/replica_store_wasm"
+tree = "791d1e4ffe3fe453f62aa659490332bb00fc2abc"
+authority = "replica_store_wasm"
+
+[[surface]]
+repository = "sdk"
+path = "crates/replica_sync_wasm"
+tree = "c697807c0178466ecf820b7a397cf294035ba9f5"
+authority = "replica_sync_wasm"
+
+[[surface]]
+repository = "sdk"
+path = "crates/ffi"
+tree = "28d795421fbfd8951118936f8ed376ae9a87a18e"
+authority = "sdk_ffi"
+
+[[surface]]
+repository = "sdk"
+path = "crates/sql_wasm_runtime"
+tree = "b885695fc544de1e4fa8acddcc7d988761389fc7"
+authority = "sdk_sql_wasm_runtime"
+
+[[surface]]
+repository = "sdk"
+path = "tools/xtask"
+tree = "04ed873470d7e727a2cbfe7c07a4cc49406f070a"
+authority = "sdk_generation_commands"
+
+[[surface]]
+repository = "sdk"
+path = "generated"
+tree = "ca2de806ea6276cec9d72c5631fcaa27bf44979c"
+authority = "sdk_generated_outputs"
+
+[[surface]]
+repository = "sdk"
+path = "packages"
+tree = "cd946b9cec8eae673f49d1a61b264b35bae5d828"
+authority = "sdk_product_packages"
+
+[[surface]]
+repository = "app_rt"
+path = "crates/core"
+tree = "4a27d80a13aebf1d6743050e699ea41badd0a415"
+authority = "mobile_core"
+
+[[surface]]
+repository = "app_rt"
+path = "crates/ffi"
+tree = "11ef7426c97ede13bcbaec9c80b4534422f33441"
+authority = "mobile_ffi_abi"
+
+[[surface]]
+repository = "app_rt"
+path = "crates/wasm"
+tree = "41548eae2c71e472596be1dd6450d4e08cac9e91"
+authority = "mobile_wasm"
+
+[[surface]]
+repository = "app_rt"
+path = "crates/bindgen"
+tree = "5b1a4ecd9b6da35d836dc58a1de1853bcf01d09e"
+authority = "mobile_bindgen"
+
+[[surface]]
+repository = "studio_app"
+path = "core/compatibility"
+tree = "0bbbcae4256c895ea085a187427838b7634c7484"
+authority = "studio_product_compatibility"
+
+[[surface]]
+repository = "studio_app"
+path = "core/crates/domain"
+tree = "ec2d055c1f7cc10c83aa8885a5354459412f998f"
+authority = "studio_domain"
+
+[[surface]]
+repository = "studio_app"
+path = "core/crates/application"
+tree = "601d88bfd14f7aeacafc77aede4576d338d47428"
+authority = "studio_application"
+
+[[surface]]
+repository = "studio_app"
+path = "core/crates/nostr"
+tree = "a1bbc8cb99680d2436e2b20deb2f262e88b64036"
+authority = "studio_nostr"
+
+[[surface]]
+repository = "studio_app"
+path = "core/crates/storage"
+tree = "395ff2552630e4ba28203c684e987249295e3ca4"
+authority = "studio_storage_database_keyring"
+
+[[surface]]
+repository = "studio_app"
+path = "core/crates/ffi"
+tree = "5715cae8aba53dfff2cd5191762fde5a06977ddb"
+authority = "studio_ffi_abi"
+
+[[surface]]
+repository = "studio_app"
+path = "core/tools/uniffi-bindgen"
+tree = "39c456eab936a6849c5761abd3be4ddfc46ca0c6"
+authority = "studio_bindgen"
+
+[[consumer]]
+id = "sdk_product"
+repository = "https://github.com/radrootslabs/sdk"
+current_source = "lib"
+current_revision = "f7f456d906d91aadbb9fac4850afaa8c5723c3e9"
+target_source = "lib"
+target_acquisition = "exact_git_rev"
+
+[[consumer]]
+id = "mobile_runtime"
+repository = "https://github.com/radrootslabs/app_rt"
+current_source = "sdk"
+current_revision = "170ecf2b620107fadca85fcb11fbf3798b4ca1ef"
+target_source = "lib"
+target_acquisition = "exact_git_rev"
+
+[[consumer]]
+id = "studio_product"
+repository = "https://github.com/radrootslabs/studio_app"
+current_source = "studio_app"
+current_revision = "2b5fe5d8321fd0e248a9d304651b1ba54ee6a180"
+target_source = "lib"
+target_acquisition = "exact_git_rev"
+
+[[consumer]]
+id = "cli"
+repository = "https://github.com/radrootslabs/cli"
+current_source = "sdk"
+current_revision = "170ecf2b620107fadca85fcb11fbf3798b4ca1ef"
+target_source = "lib"
+target_acquisition = "exact_git_rev"
+
+[[consumer]]
+id = "ios_app"
+repository = "https://github.com/radrootslabs/ios_app"
+current_source = "app_rt"
+current_revision = "b770841d2d2ea1cafc1d4a644d596cb8727624d9"
+target_source = "lib"
+target_acquisition = "exact_git_rev"
+
+[[consumer]]
+id = "myc"
+repository = "https://github.com/radrootslabs/myc"
+current_source = "lib"
+current_revision = "f7f456d906d91aadbb9fac4850afaa8c5723c3e9"
+target_source = "lib"
+target_acquisition = "exact_git_rev"
+
+[[consumer]]
+id = "radrootsd"
+repository = "https://github.com/radrootslabs/radrootsd"
+current_source = "lib"
+current_revision = "f7f456d906d91aadbb9fac4850afaa8c5723c3e9"
+target_source = "lib"
+target_acquisition = "exact_git_rev"
+
+[[consumer]]
+id = "rhi"
+repository = "https://github.com/radrootslabs/rhi"
+current_source = "lib"
+current_revision = "f7f456d906d91aadbb9fac4850afaa8c5723c3e9"
+target_source = "lib"
+target_acquisition = "exact_git_rev"
+
+[[consumer]]
+id = "event_indexer"
+repository = "https://github.com/radrootslabs/tangle_indexer"
+current_source = "lib"
+current_revision = "f7f456d906d91aadbb9fac4850afaa8c5723c3e9"
+target_source = "lib"
+target_acquisition = "exact_git_rev"
+
+[[consumer]]
+id = "integration_parent"
+repository = "integration_parent"
+current_source = "lib_and_sdk_gitlinks"
+current_revision = "f7f456d906d91aadbb9fac4850afaa8c5723c3e9"
+target_source = "lib"
+target_acquisition = "exact_registered_gitlink"
+
+[[additional_source]]
+id = "studio_mpl_legacy_core"
+commit = "6074a4745be361f21bb47d4778c74a14b2d57954"
+tree = "acb0b7d0eeaf798caed5850ae53748bc0b6643f9"
+license = "MPL-2.0"
+disposition = "import_unique_behavior_then_zero_logic_capsule"
diff --git a/contracts/consolidation/handoff_steps.v1.toml b/contracts/consolidation/handoff_steps.v1.toml
@@ -0,0 +1,235 @@
+schema_version = 1
+map_id = "radroots.rust.consolidation.handoff-steps.v1"
+source_step_count = 275
+source_sequence = "radroots_lib_consolidation_handoff/implementation/COMMIT_SEQUENCE.md"
+
+[[range]]
+start = 1
+end = 3
+owners = ["rcld-rlc-010"]
+disposition = "execute"
+reason = "current repository and command baseline"
+
+[[range]]
+start = 4
+end = 4
+owners = ["rcld-rlc-040"]
+disposition = "reassigned"
+reason = "catalog-driven Nix repair belongs to canonical build controls"
+
+[[range]]
+start = 5
+end = 5
+owners = ["rcld-rlc-010"]
+disposition = "already_satisfied"
+reason = "Studio master pins Rust 1.97.1 before source freeze"
+
+[[range]]
+start = 6
+end = 6
+owners = ["rcld-rlc-010"]
+disposition = "already_satisfied"
+reason = "app_rt already consumes the reviewed final SDK revision"
+
+[[range]]
+start = 7
+end = 12
+owners = ["rcld-rlc-010"]
+disposition = "execute"
+reason = "tree-pinned API, generated, ABI, database, and keyring baselines"
+
+[[range]]
+start = 13
+end = 14
+owners = ["rcld-rlc-020"]
+disposition = "reassigned"
+reason = "source ledger and dual-source controls depend on history tooling"
+
+[[range]]
+start = 15
+end = 15
+owners = ["rcld-rlc-010"]
+disposition = "execute"
+reason = "baseline qualification closure"
+
+[[range]]
+start = 16
+end = 27
+owners = ["rcld-rlc-030"]
+disposition = "execute"
+reason = "v2 contracts and canonical catalog"
+
+[[range]]
+start = 28
+end = 28
+owners = ["rcld-rlc-140"]
+disposition = "reassigned"
+reason = "SDK repository instructions are consumer-owned"
+
+[[range]]
+start = 29
+end = 29
+owners = ["rcld-rlc-150", "rcld-rlc-160"]
+disposition = "reassigned"
+reason = "mobile and Studio instructions are updated by their product owners"
+
+[[range]]
+start = 30
+end = 55
+owners = ["rcld-rlc-030"]
+disposition = "execute"
+reason = "v2 contract synchronization and catalog enforcement"
+
+[[range]]
+start = 56
+end = 80
+owners = ["rcld-rlc-040"]
+disposition = "execute"
+reason = "canonical command, path, generation, and artifact controls"
+
+[[range]]
+start = 81
+end = 92
+owners = ["rcld-rlc-050"]
+disposition = "execute"
+reason = "history-preserving SDK move-only import"
+
+[[range]]
+start = 93
+end = 100
+owners = ["rcld-rlc-060"]
+disposition = "execute"
+reason = "canonical SDK generation and qualification"
+
+[[range]]
+start = 101
+end = 112
+owners = ["rcld-rlc-140"]
+disposition = "reassigned"
+reason = "final SDK source lock follows the qualified lib candidate"
+
+[[range]]
+start = 113
+end = 131
+owners = ["rcld-rlc-070"]
+disposition = "execute"
+reason = "history-preserving mobile move-only import"
+
+[[range]]
+start = 132
+end = 133
+owners = ["rcld-rlc-150"]
+disposition = "reassigned"
+reason = "final mobile source locks follow the qualified lib candidate"
+
+[[range]]
+start = 134
+end = 134
+owners = ["rcld-rlc-070"]
+disposition = "execute"
+reason = "mobile move-only closure"
+
+[[range]]
+start = 135
+end = 146
+owners = ["rcld-rlc-080"]
+disposition = "execute"
+reason = "mobile boundary and filesystem hardening"
+
+[[range]]
+start = 147
+end = 160
+owners = ["rcld-rlc-090"]
+disposition = "execute"
+reason = "history-preserving Studio move-only import"
+
+[[range]]
+start = 161
+end = 163
+owners = ["rcld-rlc-160"]
+disposition = "reassigned"
+reason = "final Studio source lock and product commands follow the lib candidate"
+
+[[range]]
+start = 164
+end = 170
+owners = ["rcld-rlc-090"]
+disposition = "execute"
+reason = "Studio compatibility baselines belong to move-only import"
+
+[[range]]
+start = 171
+end = 175
+owners = ["rcld-rlc-160"]
+disposition = "reassigned"
+reason = "desktop platform and duplicate-source cutover are product-owned"
+
+[[range]]
+start = 176
+end = 176
+owners = ["rcld-rlc-090"]
+disposition = "execute"
+reason = "Studio move-only closure"
+
+[[range]]
+start = 177
+end = 210
+owners = ["rcld-rlc-100"]
+disposition = "execute"
+reason = "Studio runtime, networking, and concurrency architecture"
+
+[[range]]
+start = 211
+end = 230
+owners = ["rcld-rlc-110"]
+disposition = "execute"
+reason = "Studio data, secret, filesystem, and FFI security"
+
+[[range]]
+start = 231
+end = 254
+owners = ["rcld-rlc-120"]
+disposition = "execute"
+reason = "supply-chain, quality, and compatibility qualification"
+
+[[range]]
+start = 255
+end = 258
+owners = ["rcld-rlc-130"]
+disposition = "execute"
+reason = "canonical lib desktop and WASM platform matrix"
+
+[[range]]
+start = 259
+end = 260
+owners = ["rcld-rlc-150"]
+disposition = "reassigned"
+reason = "Android and iOS artifacts are qualified with mobile consumers"
+
+[[range]]
+start = 261
+end = 261
+owners = ["rcld-rlc-140"]
+disposition = "reassigned"
+reason = "SDK product qualification is consumer-owned"
+
+[[range]]
+start = 262
+end = 262
+owners = ["rcld-rlc-150"]
+disposition = "reassigned"
+reason = "mobile product qualification is consumer-owned"
+
+[[range]]
+start = 263
+end = 263
+owners = ["rcld-rlc-160"]
+disposition = "reassigned"
+reason = "Studio product qualification is consumer-owned"
+
+[[range]]
+start = 264
+end = 275
+owners = ["rcld-rlc-180"]
+disposition = "execute"
+reason = "rollback, gated retirement, cleanup, and final acceptance"
diff --git a/tools/xtask/src/consolidation.rs b/tools/xtask/src/consolidation.rs
@@ -0,0 +1,455 @@
+use std::{
+ collections::{BTreeMap, BTreeSet},
+ fs,
+ path::{Component, Path},
+};
+
+use serde::Deserialize;
+
+const BASELINE_RELATIVE: &str = "contracts/consolidation/baseline.v1.toml";
+const STEP_MAP_RELATIVE: &str = "contracts/consolidation/handoff_steps.v1.toml";
+const BASELINE_ID: &str = "radroots.rust.consolidation.baseline.v1";
+const STEP_MAP_ID: &str = "radroots.rust.consolidation.handoff-steps.v1";
+const ARCHITECTURE_TARGET: &str = "radroots.crates.release.v2";
+const PACKAGE_VERSION: &str = "0.1.0-alpha";
+const EXPECTED_PUBLIC_PACKAGES: u16 = 19;
+const EXPECTED_HANDOFF_STEPS: u16 = 275;
+
+const RCLD_OWNERS: &[&str] = &[
+ "rcld-rlc-010",
+ "rcld-rlc-020",
+ "rcld-rlc-030",
+ "rcld-rlc-040",
+ "rcld-rlc-050",
+ "rcld-rlc-060",
+ "rcld-rlc-070",
+ "rcld-rlc-080",
+ "rcld-rlc-090",
+ "rcld-rlc-100",
+ "rcld-rlc-110",
+ "rcld-rlc-120",
+ "rcld-rlc-130",
+ "rcld-rlc-140",
+ "rcld-rlc-150",
+ "rcld-rlc-160",
+ "rcld-rlc-170",
+ "rcld-rlc-180",
+];
+
+#[derive(Debug, Deserialize)]
+#[serde(deny_unknown_fields)]
+struct Baseline {
+ schema_version: u16,
+ baseline_id: String,
+ architecture_target: String,
+ captured_date: String,
+ public_package_version: String,
+ expected_public_packages: u16,
+ expected_handoff_steps: u16,
+ repository: Vec<Repository>,
+ surface: Vec<Surface>,
+ consumer: Vec<Consumer>,
+ additional_source: Vec<AdditionalSource>,
+}
+
+#[derive(Debug, Deserialize)]
+#[serde(deny_unknown_fields)]
+struct Repository {
+ id: String,
+ canonical_url: String,
+ commit: String,
+ tree: String,
+ branch: String,
+ clean: bool,
+ origin_synchronized: bool,
+ worktree_count: u16,
+ rust_version: String,
+ resolver: String,
+ package_version: String,
+ commands: Vec<String>,
+}
+
+#[derive(Debug, Deserialize)]
+#[serde(deny_unknown_fields)]
+struct Surface {
+ repository: String,
+ path: String,
+ tree: String,
+ authority: String,
+}
+
+#[derive(Debug, Deserialize)]
+#[serde(deny_unknown_fields)]
+struct Consumer {
+ id: String,
+ repository: String,
+ current_source: String,
+ current_revision: String,
+ target_source: String,
+ target_acquisition: String,
+}
+
+#[derive(Debug, Deserialize)]
+#[serde(deny_unknown_fields)]
+struct AdditionalSource {
+ id: String,
+ commit: String,
+ tree: String,
+ license: String,
+ disposition: String,
+}
+
+#[derive(Debug, Deserialize)]
+#[serde(deny_unknown_fields)]
+struct StepMap {
+ schema_version: u16,
+ map_id: String,
+ source_step_count: u16,
+ source_sequence: String,
+ range: Vec<StepRange>,
+}
+
+#[derive(Debug, Deserialize)]
+#[serde(deny_unknown_fields)]
+struct StepRange {
+ start: u16,
+ end: u16,
+ owners: Vec<String>,
+ disposition: String,
+ reason: String,
+}
+
+pub fn validate(workspace_root: &Path) -> Result<(), String> {
+ let baseline = read_toml::<Baseline>(workspace_root, BASELINE_RELATIVE)?;
+ let step_map = read_toml::<StepMap>(workspace_root, STEP_MAP_RELATIVE)?;
+ validate_baseline(&baseline)?;
+ validate_step_map(&step_map)
+}
+
+fn read_toml<T: for<'de> Deserialize<'de>>(
+ workspace_root: &Path,
+ relative: &str,
+) -> Result<T, String> {
+ let path = workspace_root.join(relative);
+ let raw =
+ fs::read_to_string(&path).map_err(|error| format!("read {}: {error}", path.display()))?;
+ toml::from_str(&raw).map_err(|error| format!("parse {}: {error}", path.display()))
+}
+
+fn validate_baseline(baseline: &Baseline) -> Result<(), String> {
+ if baseline.schema_version != 1
+ || baseline.baseline_id != BASELINE_ID
+ || baseline.architecture_target != ARCHITECTURE_TARGET
+ || baseline.public_package_version != PACKAGE_VERSION
+ || baseline.expected_public_packages != EXPECTED_PUBLIC_PACKAGES
+ || baseline.expected_handoff_steps != EXPECTED_HANDOFF_STEPS
+ {
+ return Err("consolidation baseline identity or cardinality drifted".to_owned());
+ }
+ validate_date(&baseline.captured_date)?;
+
+ let expected_repositories = BTreeSet::from(["app_rt", "lib", "sdk", "studio_app"]);
+ let mut repositories = BTreeMap::new();
+ for repository in &baseline.repository {
+ if repositories
+ .insert(repository.id.as_str(), repository)
+ .is_some()
+ {
+ return Err(format!("duplicate repository id {}", repository.id));
+ }
+ validate_identifier(&repository.id, "repository id")?;
+ if repository.canonical_url != format!("https://github.com/radrootslabs/{}", repository.id)
+ {
+ return Err(format!(
+ "repository {} has a noncanonical URL",
+ repository.id
+ ));
+ }
+ validate_oid(&repository.commit, "repository commit")?;
+ validate_oid(&repository.tree, "repository tree")?;
+ if repository.branch != "master"
+ || !repository.clean
+ || !repository.origin_synchronized
+ || repository.worktree_count != 1
+ || repository.rust_version != "1.97.1"
+ || !matches!(repository.resolver.as_str(), "2" | "3")
+ || repository.commands.is_empty()
+ {
+ return Err(format!(
+ "repository {} baseline is not frozen",
+ repository.id
+ ));
+ }
+ if repository
+ .commands
+ .iter()
+ .any(|command| command.trim().is_empty())
+ {
+ return Err(format!("repository {} has an empty command", repository.id));
+ }
+ }
+ if repositories.keys().copied().collect::<BTreeSet<_>>() != expected_repositories {
+ return Err(
+ "consolidation baseline must contain exactly lib, sdk, app_rt, and studio_app"
+ .to_owned(),
+ );
+ }
+ if repositories["lib"].resolver != "3"
+ || repositories["sdk"].resolver != "3"
+ || repositories["studio_app"].resolver != "3"
+ || repositories["app_rt"].resolver != "2"
+ {
+ return Err("reviewed resolver baseline drifted".to_owned());
+ }
+ if repositories["lib"].package_version != PACKAGE_VERSION
+ || repositories["sdk"].package_version != PACKAGE_VERSION
+ || repositories["studio_app"].package_version != PACKAGE_VERSION
+ || repositories["app_rt"].package_version != "0.1.0-alpha.1"
+ {
+ return Err("reviewed package version baseline drifted".to_owned());
+ }
+
+ let mut surfaces = BTreeSet::new();
+ let mut authorities = BTreeSet::new();
+ for surface in &baseline.surface {
+ if !repositories.contains_key(surface.repository.as_str()) {
+ return Err(format!("unknown surface repository {}", surface.repository));
+ }
+ validate_relative_path(&surface.path)?;
+ validate_oid(&surface.tree, "surface tree")?;
+ validate_identifier(&surface.authority, "surface authority")?;
+ if !surfaces.insert((surface.repository.as_str(), surface.path.as_str())) {
+ return Err(format!(
+ "duplicate surface {}/{}",
+ surface.repository, surface.path
+ ));
+ }
+ if !authorities.insert(surface.authority.as_str()) {
+ return Err(format!("duplicate surface authority {}", surface.authority));
+ }
+ }
+ for repository in expected_repositories {
+ if !surfaces
+ .iter()
+ .any(|(candidate, _)| *candidate == repository)
+ {
+ return Err(format!(
+ "repository {repository} has no compatibility surface"
+ ));
+ }
+ }
+
+ let expected_consumers = BTreeSet::from([
+ "cli",
+ "integration_parent",
+ "ios_app",
+ "mobile_runtime",
+ "myc",
+ "radrootsd",
+ "rhi",
+ "sdk_product",
+ "studio_product",
+ "event_indexer",
+ ]);
+ let mut consumers = BTreeSet::new();
+ for consumer in &baseline.consumer {
+ validate_identifier(&consumer.id, "consumer id")?;
+ if !consumers.insert(consumer.id.as_str()) {
+ return Err(format!("duplicate consumer id {}", consumer.id));
+ }
+ if consumer.repository.trim().is_empty()
+ || consumer.current_source.trim().is_empty()
+ || consumer.target_source != "lib"
+ {
+ return Err(format!("consumer {} has incomplete ownership", consumer.id));
+ }
+ validate_oid(&consumer.current_revision, "consumer revision")?;
+ if !matches!(
+ consumer.target_acquisition.as_str(),
+ "exact_git_rev" | "exact_registered_gitlink"
+ ) {
+ return Err(format!(
+ "consumer {} has invalid target acquisition",
+ consumer.id
+ ));
+ }
+ }
+ if consumers != expected_consumers {
+ return Err("consumer census is incomplete".to_owned());
+ }
+
+ if baseline.additional_source.len() != 1 {
+ return Err("exactly one additional Studio source is required".to_owned());
+ }
+ let additional = &baseline.additional_source[0];
+ validate_identifier(&additional.id, "additional source id")?;
+ validate_oid(&additional.commit, "additional source commit")?;
+ validate_oid(&additional.tree, "additional source tree")?;
+ if additional.id != "studio_mpl_legacy_core"
+ || additional.license != "MPL-2.0"
+ || additional.disposition != "import_unique_behavior_then_zero_logic_capsule"
+ {
+ return Err("additional Studio source disposition drifted".to_owned());
+ }
+ Ok(())
+}
+
+fn validate_step_map(step_map: &StepMap) -> Result<(), String> {
+ if step_map.schema_version != 1
+ || step_map.map_id != STEP_MAP_ID
+ || step_map.source_step_count != EXPECTED_HANDOFF_STEPS
+ || !step_map
+ .source_sequence
+ .ends_with("implementation/COMMIT_SEQUENCE.md")
+ {
+ return Err("handoff step map identity drifted".to_owned());
+ }
+ let allowed_owners = RCLD_OWNERS.iter().copied().collect::<BTreeSet<_>>();
+ let allowed_dispositions = BTreeSet::from(["already_satisfied", "execute", "reassigned"]);
+ let mut next = 1_u16;
+ for range in &step_map.range {
+ if range.start != next || range.end < range.start || range.end > EXPECTED_HANDOFF_STEPS {
+ return Err(format!(
+ "handoff step range {}-{} is overlapping, gapped, or invalid; expected {}",
+ range.start, range.end, next
+ ));
+ }
+ if range.owners.is_empty()
+ || range
+ .owners
+ .iter()
+ .any(|owner| !allowed_owners.contains(owner.as_str()))
+ {
+ return Err(format!(
+ "handoff step range {}-{} has an invalid owner",
+ range.start, range.end
+ ));
+ }
+ if !allowed_dispositions.contains(range.disposition.as_str())
+ || range.reason.trim().is_empty()
+ {
+ return Err(format!(
+ "handoff step range {}-{} has an invalid disposition",
+ range.start, range.end
+ ));
+ }
+ next = range
+ .end
+ .checked_add(1)
+ .ok_or_else(|| "handoff step range overflow".to_owned())?;
+ }
+ if next != EXPECTED_HANDOFF_STEPS + 1 {
+ return Err(format!(
+ "handoff step map ends at {}, expected {}",
+ next.saturating_sub(1),
+ EXPECTED_HANDOFF_STEPS
+ ));
+ }
+ Ok(())
+}
+
+fn validate_oid(value: &str, context: &str) -> Result<(), String> {
+ if value.len() != 40
+ || !value
+ .bytes()
+ .all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte))
+ {
+ return Err(format!(
+ "{context} must be a full lowercase 40-hex Git object id"
+ ));
+ }
+ Ok(())
+}
+
+fn validate_identifier(value: &str, context: &str) -> Result<(), String> {
+ if value.is_empty()
+ || !value
+ .bytes()
+ .all(|byte| byte.is_ascii_lowercase() || byte.is_ascii_digit() || byte == b'_')
+ {
+ return Err(format!("{context} must use lowercase snake case"));
+ }
+ Ok(())
+}
+
+fn validate_relative_path(value: &str) -> Result<(), String> {
+ let path = Path::new(value);
+ if path.as_os_str().is_empty()
+ || path.is_absolute()
+ || value.contains('\\')
+ || value
+ .split('/')
+ .any(|segment| segment.is_empty() || matches!(segment, "." | ".."))
+ || path
+ .components()
+ .any(|component| !matches!(component, Component::Normal(_)))
+ {
+ return Err(format!(
+ "surface path {value:?} must be a safe relative path"
+ ));
+ }
+ Ok(())
+}
+
+fn validate_date(value: &str) -> Result<(), String> {
+ let bytes = value.as_bytes();
+ if bytes.len() != 10
+ || bytes[4] != b'-'
+ || bytes[7] != b'-'
+ || bytes
+ .iter()
+ .enumerate()
+ .any(|(index, byte)| index != 4 && index != 7 && !byte.is_ascii_digit())
+ {
+ return Err("captured_date must use YYYY-MM-DD".to_owned());
+ }
+ Ok(())
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn checked_in_baseline_and_step_map_validate() {
+ validate(&crate::workspace_root()).expect("checked-in consolidation baseline");
+ }
+
+ #[test]
+ fn object_ids_require_full_lowercase_hex() {
+ assert!(validate_oid("0123456789abcdef0123456789abcdef01234567", "commit").is_ok());
+ assert!(validate_oid("0123456", "commit").is_err());
+ assert!(validate_oid("0123456789ABCDEF0123456789abcdef01234567", "commit").is_err());
+ assert!(validate_oid("g123456789abcdef0123456789abcdef01234567", "commit").is_err());
+ }
+
+ #[test]
+ fn paths_reject_escape_and_non_normal_components() {
+ assert!(validate_relative_path("crates/sdk").is_ok());
+ assert!(validate_relative_path("../sdk").is_err());
+ assert!(validate_relative_path("crates/./sdk").is_err());
+ assert!(validate_relative_path("/crates/sdk").is_err());
+ }
+
+ #[test]
+ fn step_ranges_must_cover_every_step_once() {
+ let valid = StepMap {
+ schema_version: 1,
+ map_id: STEP_MAP_ID.to_owned(),
+ source_step_count: EXPECTED_HANDOFF_STEPS,
+ source_sequence: "implementation/COMMIT_SEQUENCE.md".to_owned(),
+ range: vec![StepRange {
+ start: 1,
+ end: EXPECTED_HANDOFF_STEPS,
+ owners: vec!["rcld-rlc-010".to_owned()],
+ disposition: "execute".to_owned(),
+ reason: "fixture".to_owned(),
+ }],
+ };
+ validate_step_map(&valid).expect("complete map");
+
+ let mut gapped = valid;
+ gapped.range[0].start = 2;
+ assert!(validate_step_map(&gapped).is_err());
+ }
+}
diff --git a/tools/xtask/src/hygiene.rs b/tools/xtask/src/hygiene.rs
@@ -239,7 +239,10 @@ pub fn validate_forbidden_identifiers(root: &Path) -> Result<(), String> {
],
&["tangle"],
"removed identifier 'tangle' must not reappear",
- &["tools/xtask/src/hygiene.rs"],
+ &[
+ "contracts/consolidation/baseline.v1.toml",
+ "tools/xtask/src/hygiene.rs",
+ ],
&mut failures,
);
reject_retired_listing_aliases(root, &mut failures);
diff --git a/tools/xtask/src/main.rs b/tools/xtask/src/main.rs
@@ -12,6 +12,8 @@ mod api_qualification;
#[cfg_attr(coverage_nightly, coverage(off))]
mod architecture;
#[cfg_attr(coverage_nightly, coverage(off))]
+mod consolidation;
+#[cfg_attr(coverage_nightly, coverage(off))]
mod contract;
mod coverage;
#[cfg_attr(coverage_nightly, coverage(off))]
@@ -45,6 +47,7 @@ fn usage() {
eprintln!(" cargo xtask contract validate");
eprintln!(" cargo xtask contract event-contract-registry-v7 [--write]");
eprintln!(" cargo xtask contract knowledge-manifest [--write]");
+ eprintln!(" cargo xtask consolidation baseline");
eprintln!(" cargo xtask dto-roots --check|--write");
eprintln!(" cargo xtask generate protocol --check|--write");
eprintln!(" cargo xtask release preflight");
@@ -177,6 +180,11 @@ fn run(args: &[String]) -> Result<(), String> {
architecture::validate_dependency_boundaries(&workspace_root())
}
Some("contract") => run_contract(&args[1..]),
+ Some("consolidation")
+ if args.get(1).map(String::as_str) == Some("baseline") && args.len() == 2 =>
+ {
+ consolidation::validate(&workspace_root())
+ }
Some("coverage") => coverage::run(&args[1..]),
Some("dto-roots") => dto_roots::run(&args[1..], &workspace_root()),
Some("generate") => generate::run(&args[1..], &workspace_root()),