lib

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

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:
Acontracts/consolidation/baseline.v1.toml | 336+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acontracts/consolidation/handoff_steps.v1.toml | 235+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atools/xtask/src/consolidation.rs | 455+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtools/xtask/src/hygiene.rs | 5++++-
Mtools/xtask/src/main.rs | 8++++++++
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()),