lib

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

architecture.rs (60199B)


      1 use std::{
      2     collections::{BTreeMap, BTreeSet},
      3     fs,
      4     path::{Component, Path},
      5 };
      6 
      7 use serde::Deserialize;
      8 
      9 mod api_leakage;
     10 mod core_contract;
     11 mod dependency_boundary;
     12 mod retired_compatibility;
     13 
     14 const DEVIATIONS_RELATIVE: &str = "contracts/architecture/deviations.toml";
     15 const ARCHITECTURE_RELATIVE: &str = "contracts/crates/release_v1/radroots_crates_release_v1.toml";
     16 const ARCHITECTURE_ID: &str = "radroots.crates.release.v1";
     17 const PUBLIC_HOMEPAGE: &str = "https://radroots.org";
     18 const PUBLIC_README: &str = "README.md";
     19 const PUBLIC_AUTHORS: &[&str] = &["Tyson Lupul <tyson@radroots.org>"];
     20 
     21 #[derive(Debug, Deserialize)]
     22 #[serde(deny_unknown_fields)]
     23 struct DeviationLedger {
     24     schema_version: u16,
     25     architecture_id: String,
     26     deviation: Vec<DeviationRecord>,
     27 }
     28 
     29 #[derive(Debug, Deserialize)]
     30 #[serde(deny_unknown_fields)]
     31 struct DeviationRecord {
     32     id: String,
     33     date: String,
     34     status: String,
     35     approval: String,
     36     affected_steps: Vec<String>,
     37     spec_anchors: Vec<String>,
     38     source_evidence: Vec<String>,
     39     replacement_action: String,
     40     verification: Vec<String>,
     41     unresolved_risk: String,
     42     normative_architecture_change: bool,
     43     adr_required: bool,
     44     #[serde(default)]
     45     closure_evidence: Vec<String>,
     46 }
     47 
     48 #[derive(Debug, Deserialize)]
     49 struct ArchitectureIdentity {
     50     spec_id: String,
     51     edition: String,
     52     resolver: String,
     53     rust_version: String,
     54     license: String,
     55     canonical_repositories: Vec<String>,
     56     repositories: BTreeMap<String, ArchitectureRepository>,
     57     package: Vec<ArchitecturePackage>,
     58 }
     59 
     60 #[derive(Debug, Deserialize)]
     61 struct ArchitectureRepository {
     62     url: String,
     63     version: String,
     64     packages: Vec<String>,
     65 }
     66 
     67 #[derive(Debug, Deserialize)]
     68 struct ArchitecturePackage {
     69     name: String,
     70 }
     71 
     72 #[derive(Debug, Deserialize)]
     73 struct ConsolidationOwnership {
     74     canonical_rust_repository: String,
     75 }
     76 
     77 #[derive(Debug, Deserialize)]
     78 struct WorkspaceManifest {
     79     workspace: WorkspaceMembers,
     80 }
     81 
     82 #[derive(Debug, Deserialize)]
     83 struct WorkspaceMembershipManifest {
     84     workspace: WorkspaceMembership,
     85 }
     86 
     87 #[derive(Debug, Deserialize)]
     88 struct WorkspaceMembership {
     89     members: Vec<String>,
     90 }
     91 
     92 #[derive(Debug, Deserialize)]
     93 struct WorkspaceMembers {
     94     members: Vec<String>,
     95     resolver: String,
     96     package: WorkspacePackage,
     97     metadata: WorkspaceMetadata,
     98     lints: WorkspaceLints,
     99 }
    100 
    101 #[derive(Debug, Deserialize)]
    102 struct WorkspaceLints {
    103     rust: WorkspaceRustLints,
    104     rustdoc: WorkspaceRustdocLints,
    105     clippy: WorkspaceClippyLints,
    106 }
    107 
    108 #[derive(Debug, Deserialize)]
    109 struct WorkspaceRustLints {
    110     unsafe_code: String,
    111 }
    112 
    113 #[derive(Debug, Deserialize)]
    114 struct WorkspaceRustdocLints {
    115     broken_intra_doc_links: String,
    116 }
    117 
    118 #[derive(Debug, Deserialize)]
    119 struct WorkspaceClippyLints {
    120     dbg_macro: String,
    121     todo: String,
    122     unimplemented: String,
    123 }
    124 
    125 #[derive(Debug, Deserialize)]
    126 struct WorkspaceMetadata {
    127     radroots: RadrootsWorkspaceMetadata,
    128 }
    129 
    130 #[derive(Debug, Deserialize)]
    131 struct RadrootsWorkspaceMetadata {
    132     #[serde(rename = "public-package")]
    133     public_package: PublicPackageMetadata,
    134 }
    135 
    136 #[derive(Debug, Deserialize)]
    137 struct PublicPackageMetadata {
    138     version: String,
    139     authors: Vec<String>,
    140     readme: String,
    141 }
    142 
    143 #[derive(Debug, Deserialize)]
    144 struct WorkspacePackage {
    145     version: String,
    146     edition: String,
    147     #[serde(rename = "rust-version")]
    148     rust_version: String,
    149     license: String,
    150     repository: String,
    151     homepage: String,
    152     readme: String,
    153     authors: Vec<String>,
    154 }
    155 
    156 pub fn validate(workspace_root: &Path) -> Result<(), String> {
    157     validate_workspace_members(workspace_root)?;
    158     let architecture_path = workspace_root.join(ARCHITECTURE_RELATIVE);
    159     let architecture_raw = fs::read_to_string(&architecture_path)
    160         .map_err(|error| format!("read {}: {error}", architecture_path.display()))?;
    161     let architecture = toml::from_str::<ArchitectureIdentity>(&architecture_raw)
    162         .map_err(|error| format!("parse {}: {error}", architecture_path.display()))?;
    163 
    164     let architecture_packages = architecture
    165         .package
    166         .iter()
    167         .map(|package| package.name.clone())
    168         .collect::<BTreeSet<_>>();
    169     core_contract::validate(workspace_root)?;
    170     api_leakage::validate_policy_catalog(workspace_root, &architecture_packages)?;
    171     dependency_boundary::validate_policy_catalog(workspace_root, &architecture_packages)?;
    172     retired_compatibility::validate(workspace_root)?;
    173     validate_workspace_toolchain(workspace_root, &architecture)?;
    174     validate_public_package_metadata(workspace_root, &architecture)?;
    175     validate_no_production_sibling_paths(workspace_root)?;
    176     validate_public_dependency_versions(workspace_root, &architecture)?;
    177 
    178     let ledger_path = workspace_root.join(DEVIATIONS_RELATIVE);
    179     let ledger_raw = fs::read_to_string(&ledger_path)
    180         .map_err(|error| format!("read {}: {error}", ledger_path.display()))?;
    181     validate_ledger(workspace_root, &architecture.spec_id, &ledger_raw)
    182 }
    183 
    184 pub fn validate_dependency_boundaries(workspace_root: &Path) -> Result<(), String> {
    185     validate(workspace_root)?;
    186     dependency_boundary::validate_resolved_boundaries(workspace_root)
    187 }
    188 
    189 pub fn validate_api_boundaries(workspace_root: &Path) -> Result<(), String> {
    190     validate(workspace_root)?;
    191     api_leakage::validate_public_api(workspace_root)
    192 }
    193 
    194 pub fn validate_ci(workspace_root: &Path) -> Result<(), String> {
    195     validate(workspace_root)?;
    196     dependency_boundary::validate_resolved_boundaries(workspace_root)?;
    197     api_leakage::validate_public_api(workspace_root)
    198 }
    199 
    200 fn validate_workspace_toolchain(
    201     workspace_root: &Path,
    202     architecture: &ArchitectureIdentity,
    203 ) -> Result<(), String> {
    204     let manifest_path = workspace_root.join("Cargo.toml");
    205     let manifest_raw = fs::read_to_string(&manifest_path)
    206         .map_err(|error| format!("read {}: {error}", manifest_path.display()))?;
    207     let manifest = toml::from_str::<WorkspaceManifest>(&manifest_raw)
    208         .map_err(|error| format!("parse {}: {error}", manifest_path.display()))?;
    209     if manifest.workspace.resolver != architecture.resolver || architecture.resolver != "3" {
    210         return Err(format!(
    211             "workspace resolver {} must match architecture resolver {}",
    212             manifest.workspace.resolver, architecture.resolver
    213         ));
    214     }
    215     if manifest.workspace.package.rust_version != architecture.rust_version
    216         || architecture.rust_version != "1.97.1"
    217     {
    218         return Err(format!(
    219             "workspace rust-version {} must match architecture rust_version {}",
    220             manifest.workspace.package.rust_version, architecture.rust_version
    221         ));
    222     }
    223     let workspace_package = &manifest.workspace.package;
    224     let public_metadata = &manifest.workspace.metadata.radroots.public_package;
    225     let repository = architecture
    226         .repositories
    227         .values()
    228         .find(|repository| repository.url == workspace_package.repository)
    229         .ok_or_else(|| "workspace repository has no architecture allocation".to_owned())?;
    230     if public_metadata.version != repository.version {
    231         return Err(format!(
    232             "public package version source {} must match repository version {}",
    233             public_metadata.version, repository.version
    234         ));
    235     }
    236     if public_metadata.authors != PUBLIC_AUTHORS {
    237         return Err("public package authors source must match the workspace convention".to_owned());
    238     }
    239     if public_metadata.readme != PUBLIC_README {
    240         return Err(format!(
    241             "public package readme source must be {PUBLIC_README}"
    242         ));
    243     }
    244     let lints = &manifest.workspace.lints;
    245     if lints.rust.unsafe_code != "deny"
    246         || lints.rustdoc.broken_intra_doc_links != "deny"
    247         || lints.clippy.dbg_macro != "deny"
    248         || lints.clippy.todo != "deny"
    249         || lints.clippy.unimplemented != "deny"
    250     {
    251         return Err(
    252             "workspace lints must deny unsafe code and deny the approved rustdoc/Clippy baseline"
    253                 .to_owned(),
    254         );
    255     }
    256     if workspace_package.edition != architecture.edition || architecture.edition != "2024" {
    257         return Err(format!(
    258             "workspace edition {} must match architecture edition {}",
    259             workspace_package.edition, architecture.edition
    260         ));
    261     }
    262     if workspace_package.license != architecture.license {
    263         return Err(format!(
    264             "workspace license {} must match architecture license {}",
    265             workspace_package.license, architecture.license
    266         ));
    267     }
    268     if !architecture
    269         .canonical_repositories
    270         .contains(&workspace_package.repository)
    271     {
    272         return Err(format!(
    273             "workspace repository {} is not canonical",
    274             workspace_package.repository
    275         ));
    276     }
    277     if workspace_package.homepage != PUBLIC_HOMEPAGE {
    278         return Err(format!(
    279             "workspace homepage {} must be {PUBLIC_HOMEPAGE}",
    280             workspace_package.homepage
    281         ));
    282     }
    283     if workspace_package.authors != PUBLIC_AUTHORS {
    284         return Err("workspace authors must match the public package convention".to_owned());
    285     }
    286     if !workspace_root.join(&workspace_package.readme).is_file() {
    287         return Err(format!(
    288             "workspace readme {} does not exist",
    289             workspace_package.readme
    290         ));
    291     }
    292     for license in ["LICENSE-MIT", "LICENSE-APACHE"] {
    293         if !workspace_root.join(license).is_file() {
    294             return Err(format!("workspace is missing {license}"));
    295         }
    296     }
    297     let toolchain_path = workspace_root.join("rust-toolchain.toml");
    298     let toolchain_raw = fs::read_to_string(&toolchain_path)
    299         .map_err(|error| format!("read {}: {error}", toolchain_path.display()))?;
    300     let toolchain = toolchain_raw
    301         .parse::<toml::Value>()
    302         .map_err(|error| format!("parse {}: {error}", toolchain_path.display()))?;
    303     let channel = toolchain
    304         .get("toolchain")
    305         .and_then(|value| value.get("channel"))
    306         .and_then(toml::Value::as_str);
    307     if channel != Some(architecture.rust_version.as_str()) {
    308         return Err(format!(
    309             "rust-toolchain.toml channel must match architecture rust_version {}",
    310             architecture.rust_version
    311         ));
    312     }
    313     Ok(())
    314 }
    315 
    316 fn validate_public_package_metadata(
    317     workspace_root: &Path,
    318     architecture: &ArchitectureIdentity,
    319 ) -> Result<(), String> {
    320     let workspace_raw = fs::read_to_string(workspace_root.join("Cargo.toml"))
    321         .map_err(|error| format!("read workspace Cargo.toml: {error}"))?;
    322     let workspace = toml::from_str::<WorkspaceManifest>(&workspace_raw)
    323         .map_err(|error| format!("parse workspace Cargo.toml: {error}"))?;
    324     let repository = architecture
    325         .repositories
    326         .values()
    327         .find(|repository| repository.url == workspace.workspace.package.repository)
    328         .ok_or_else(|| "workspace repository has no architecture allocation".to_owned())?;
    329     let all_packages = architecture
    330         .package
    331         .iter()
    332         .map(|package| package.name.as_str())
    333         .collect::<BTreeSet<_>>();
    334     let consolidation_path = workspace_root.join("contracts/consolidation/architecture.v1.toml");
    335     let consolidated_owner = if consolidation_path.is_file() {
    336         let consolidation = fs::read_to_string(&consolidation_path)
    337             .map_err(|error| format!("read {}: {error}", consolidation_path.display()))?;
    338         Some(
    339             toml::from_str::<ConsolidationOwnership>(&consolidation)
    340                 .map_err(|error| format!("parse {}: {error}", consolidation_path.display()))?
    341                 .canonical_rust_repository,
    342         )
    343     } else {
    344         None
    345     };
    346     let local_packages =
    347         if consolidated_owner.as_deref() == Some(workspace.workspace.package.repository.as_str()) {
    348             all_packages.iter().map(|name| (*name).to_owned()).collect()
    349         } else {
    350             repository.packages.iter().cloned().collect::<BTreeSet<_>>()
    351         };
    352     let mut found_local_packages = BTreeSet::new();
    353 
    354     for member in &workspace.workspace.members {
    355         let manifest_path = workspace_root.join(member).join("Cargo.toml");
    356         let raw = fs::read_to_string(&manifest_path)
    357             .map_err(|error| format!("read {}: {error}", manifest_path.display()))?;
    358         let manifest = raw
    359             .parse::<toml::Value>()
    360             .map_err(|error| format!("parse {}: {error}", manifest_path.display()))?;
    361         let package = manifest
    362             .get("package")
    363             .and_then(toml::Value::as_table)
    364             .ok_or_else(|| format!("{} is missing [package]", manifest_path.display()))?;
    365         let name = package
    366             .get("name")
    367             .and_then(toml::Value::as_str)
    368             .ok_or_else(|| format!("{} is missing package.name", manifest_path.display()))?;
    369         if !all_packages.contains(name) {
    370             continue;
    371         }
    372         if !local_packages.contains(name) {
    373             return Err(format!(
    374                 "public package {name} belongs to a different canonical repository"
    375             ));
    376         }
    377         found_local_packages.insert(name.to_owned());
    378         validate_public_manifest_field(
    379             package,
    380             "version",
    381             &workspace.workspace.package.version,
    382             &repository.version,
    383             name,
    384         )?;
    385         validate_public_manifest_field(
    386             package,
    387             "edition",
    388             &workspace.workspace.package.edition,
    389             &architecture.edition,
    390             name,
    391         )?;
    392         validate_public_manifest_field(
    393             package,
    394             "rust-version",
    395             &workspace.workspace.package.rust_version,
    396             &architecture.rust_version,
    397             name,
    398         )?;
    399         validate_public_manifest_field(
    400             package,
    401             "license",
    402             &workspace.workspace.package.license,
    403             &architecture.license,
    404             name,
    405         )?;
    406         validate_public_manifest_field(
    407             package,
    408             "repository",
    409             &workspace.workspace.package.repository,
    410             &repository.url,
    411             name,
    412         )?;
    413         validate_public_manifest_field(
    414             package,
    415             "homepage",
    416             &workspace.workspace.package.homepage,
    417             PUBLIC_HOMEPAGE,
    418             name,
    419         )?;
    420         let authors = resolve_public_authors(package, &workspace.workspace.package.authors)
    421             .ok_or_else(|| format!("public package {name} must declare authors"))?;
    422         if authors != PUBLIC_AUTHORS {
    423             return Err(format!(
    424                 "public package {name} authors must match the workspace convention"
    425             ));
    426         }
    427         let readme = package.get("readme").and_then(toml::Value::as_str);
    428         if readme != Some(PUBLIC_README)
    429             || !workspace_root.join(member).join(PUBLIC_README).is_file()
    430         {
    431             return Err(format!(
    432                 "public package {name} must use an existing crate-local {PUBLIC_README}"
    433             ));
    434         }
    435         let publish_registries = package
    436             .get("publish")
    437             .and_then(toml::Value::as_array)
    438             .ok_or_else(|| format!("public package {name} must set publish = [\"crates-io\"]"))?;
    439         if publish_registries.len() != 1 || publish_registries[0].as_str() != Some("crates-io") {
    440             return Err(format!(
    441                 "public package {name} must set publish = [\"crates-io\"]"
    442             ));
    443         }
    444         let inherits_lints = manifest
    445             .get("lints")
    446             .and_then(toml::Value::as_table)
    447             .is_some_and(|lints| {
    448                 lints.len() == 1
    449                     && lints.get("workspace").and_then(toml::Value::as_bool) == Some(true)
    450             });
    451         if !inherits_lints {
    452             return Err(format!(
    453                 "public package {name} must inherit the workspace lint policy"
    454             ));
    455         }
    456     }
    457     if found_local_packages != local_packages {
    458         let missing = local_packages
    459             .difference(&found_local_packages)
    460             .cloned()
    461             .collect::<Vec<_>>()
    462             .join(", ");
    463         let extra = found_local_packages
    464             .difference(&local_packages)
    465             .cloned()
    466             .collect::<Vec<_>>()
    467             .join(", ");
    468         return Err(format!(
    469             "workspace public package inventory is missing: {missing}; workspace public package inventory has unallocated packages: {extra}"
    470         ));
    471     }
    472     Ok(())
    473 }
    474 
    475 fn validate_public_dependency_versions(
    476     workspace_root: &Path,
    477     architecture: &ArchitectureIdentity,
    478 ) -> Result<(), String> {
    479     let workspace_path = workspace_root.join("Cargo.toml");
    480     let workspace_raw = fs::read_to_string(&workspace_path)
    481         .map_err(|error| format!("read {}: {error}", workspace_path.display()))?;
    482     let workspace = toml::from_str::<WorkspaceManifest>(&workspace_raw)
    483         .map_err(|error| format!("parse {}: {error}", workspace_path.display()))?;
    484     let workspace_value = workspace_raw
    485         .parse::<toml::Value>()
    486         .map_err(|error| format!("parse {}: {error}", workspace_path.display()))?;
    487     let workspace_dependencies = workspace_value
    488         .get("workspace")
    489         .and_then(toml::Value::as_table)
    490         .and_then(|workspace| workspace.get("dependencies"))
    491         .and_then(toml::Value::as_table);
    492     let repository = architecture
    493         .repositories
    494         .values()
    495         .find(|repository| repository.url == workspace.workspace.package.repository)
    496         .ok_or_else(|| "workspace repository has no architecture allocation".to_owned())?;
    497     let local_packages = repository
    498         .packages
    499         .iter()
    500         .map(String::as_str)
    501         .collect::<BTreeSet<_>>();
    502     let public_packages = architecture
    503         .package
    504         .iter()
    505         .map(|package| package.name.as_str())
    506         .collect::<BTreeSet<_>>();
    507     let package_versions = architecture
    508         .repositories
    509         .values()
    510         .flat_map(|repository| {
    511             repository
    512                 .packages
    513                 .iter()
    514                 .map(|package| (package.as_str(), repository.version.as_str()))
    515         })
    516         .collect::<BTreeMap<_, _>>();
    517 
    518     for member in &workspace.workspace.members {
    519         let manifest_path = workspace_root.join(member).join("Cargo.toml");
    520         let raw = fs::read_to_string(&manifest_path)
    521             .map_err(|error| format!("read {}: {error}", manifest_path.display()))?;
    522         let manifest = raw
    523             .parse::<toml::Value>()
    524             .map_err(|error| format!("parse {}: {error}", manifest_path.display()))?;
    525         let package_name = manifest
    526             .get("package")
    527             .and_then(toml::Value::as_table)
    528             .and_then(|package| package.get("name"))
    529             .and_then(toml::Value::as_str)
    530             .ok_or_else(|| format!("{} is missing package.name", manifest_path.display()))?;
    531         if !local_packages.contains(package_name) {
    532             continue;
    533         }
    534         let policy = PublicDependencyPolicy {
    535             workspace_root,
    536             workspace_dependencies,
    537             public_packages: &public_packages,
    538             local_packages: &local_packages,
    539             package_versions: &package_versions,
    540         };
    541         validate_public_dependency_sections(member, package_name, &manifest, &policy)?;
    542     }
    543     Ok(())
    544 }
    545 
    546 struct PublicDependencyPolicy<'a> {
    547     workspace_root: &'a Path,
    548     workspace_dependencies: Option<&'a toml::value::Table>,
    549     public_packages: &'a BTreeSet<&'a str>,
    550     local_packages: &'a BTreeSet<&'a str>,
    551     package_versions: &'a BTreeMap<&'a str, &'a str>,
    552 }
    553 
    554 fn validate_public_dependency_sections(
    555     member: &str,
    556     owner: &str,
    557     manifest: &toml::Value,
    558     policy: &PublicDependencyPolicy<'_>,
    559 ) -> Result<(), String> {
    560     let manifest_table = manifest
    561         .as_table()
    562         .ok_or_else(|| format!("{member}/Cargo.toml must be a TOML table"))?;
    563     for section in ["dependencies", "dev-dependencies", "build-dependencies"] {
    564         if let Some(dependencies) = manifest_table.get(section).and_then(toml::Value::as_table) {
    565             validate_public_dependency_table(member, owner, section, dependencies, policy)?;
    566         }
    567     }
    568     if let Some(targets) = manifest_table.get("target").and_then(toml::Value::as_table) {
    569         for (target, target_value) in targets {
    570             let Some(target_table) = target_value.as_table() else {
    571                 continue;
    572             };
    573             for section in ["dependencies", "dev-dependencies", "build-dependencies"] {
    574                 if let Some(dependencies) =
    575                     target_table.get(section).and_then(toml::Value::as_table)
    576                 {
    577                     validate_public_dependency_table(
    578                         member,
    579                         owner,
    580                         &format!("target.{target}.{section}"),
    581                         dependencies,
    582                         policy,
    583                     )?;
    584                 }
    585             }
    586         }
    587     }
    588     Ok(())
    589 }
    590 
    591 fn validate_public_dependency_table(
    592     member: &str,
    593     owner: &str,
    594     section: &str,
    595     dependencies: &toml::value::Table,
    596     policy: &PublicDependencyPolicy<'_>,
    597 ) -> Result<(), String> {
    598     for (dependency_key, declaration) in dependencies {
    599         let inherits_workspace = declaration
    600             .as_table()
    601             .and_then(|table| table.get("workspace"))
    602             .and_then(toml::Value::as_bool)
    603             == Some(true);
    604         let resolved = if inherits_workspace {
    605             policy
    606                 .workspace_dependencies
    607                 .and_then(|dependencies| dependencies.get(dependency_key))
    608                 .ok_or_else(|| {
    609                     format!(
    610                         "public package {owner} {section}.{dependency_key} inherits a missing workspace dependency"
    611                     )
    612                 })?
    613         } else {
    614             declaration
    615         };
    616         let resolved_table = resolved.as_table();
    617         let dependency_path = resolved_table
    618             .and_then(|table| table.get("path"))
    619             .and_then(toml::Value::as_str);
    620         let declared_package = resolved_table
    621             .and_then(|table| table.get("package"))
    622             .and_then(toml::Value::as_str);
    623         let path_base = if inherits_workspace {
    624             policy.workspace_root.to_path_buf()
    625         } else {
    626             policy.workspace_root.join(member)
    627         };
    628         let path_package = if declared_package.is_none() {
    629             dependency_path
    630                 .map(|path| dependency_package_name(&path_base.join(path)))
    631                 .transpose()?
    632         } else {
    633             None
    634         };
    635         let dependency_name = declared_package
    636             .or(path_package.as_deref())
    637             .unwrap_or(dependency_key.as_str());
    638         let first_party =
    639             dependency_key.starts_with("radroots_") || dependency_name.starts_with("radroots_");
    640         if first_party && !policy.public_packages.contains(dependency_name) {
    641             return Err(format!(
    642                 "public package {owner} {section}.{dependency_key} must not depend on private first-party package {dependency_name}"
    643             ));
    644         }
    645         if !policy.public_packages.contains(dependency_name) {
    646             continue;
    647         }
    648         let version = match resolved {
    649             toml::Value::String(version) => Some(version.as_str()),
    650             toml::Value::Table(table) => table.get("version").and_then(toml::Value::as_str),
    651             _ => None,
    652         };
    653         let governed_version = policy
    654             .package_versions
    655             .get(dependency_name)
    656             .ok_or_else(|| {
    657                 format!("public dependency {dependency_name} has no repository version authority")
    658             })?;
    659         let exact_version = format!("={governed_version}");
    660         if policy.local_packages.contains(dependency_name)
    661             && (dependency_path.is_none() || version != Some(exact_version.as_str()))
    662         {
    663             return Err(format!(
    664                 "public package {owner} {section}.{dependency_key} dependency on {dependency_name} must declare path and exact version {exact_version}"
    665             ));
    666         }
    667         if !policy.local_packages.contains(dependency_name)
    668             && (dependency_path.is_some() || version != Some(exact_version.as_str()))
    669         {
    670             return Err(format!(
    671                 "public package {owner} {section}.{dependency_key} cross-repository dependency on {dependency_name} must declare exact registry version {exact_version} without a path"
    672             ));
    673         }
    674     }
    675     Ok(())
    676 }
    677 
    678 fn validate_no_production_sibling_paths(workspace_root: &Path) -> Result<(), String> {
    679     let canonical_root = fs::canonicalize(workspace_root)
    680         .map_err(|error| format!("canonicalize {}: {error}", workspace_root.display()))?;
    681     let workspace_path = workspace_root.join("Cargo.toml");
    682     let workspace_raw = fs::read_to_string(&workspace_path)
    683         .map_err(|error| format!("read {}: {error}", workspace_path.display()))?;
    684     let workspace = toml::from_str::<WorkspaceManifest>(&workspace_raw)
    685         .map_err(|error| format!("parse {}: {error}", workspace_path.display()))?;
    686     let workspace_value = workspace_raw
    687         .parse::<toml::Value>()
    688         .map_err(|error| format!("parse {}: {error}", workspace_path.display()))?;
    689     if let Some(dependencies) = workspace_value
    690         .get("workspace")
    691         .and_then(toml::Value::as_table)
    692         .and_then(|workspace| workspace.get("dependencies"))
    693         .and_then(toml::Value::as_table)
    694     {
    695         validate_dependency_path_table(
    696             &canonical_root,
    697             &canonical_root,
    698             "workspace",
    699             "workspace.dependencies",
    700             dependencies,
    701         )?;
    702     }
    703     for member in &workspace.workspace.members {
    704         let manifest_path = workspace_root.join(member).join("Cargo.toml");
    705         let raw = fs::read_to_string(&manifest_path)
    706             .map_err(|error| format!("read {}: {error}", manifest_path.display()))?;
    707         let manifest = raw
    708             .parse::<toml::Value>()
    709             .map_err(|error| format!("parse {}: {error}", manifest_path.display()))?;
    710         validate_manifest_dependency_paths(
    711             &canonical_root,
    712             &canonical_root.join(member),
    713             member,
    714             &manifest,
    715         )?;
    716     }
    717     Ok(())
    718 }
    719 
    720 fn validate_manifest_dependency_paths(
    721     workspace_root: &Path,
    722     package_root: &Path,
    723     owner: &str,
    724     manifest: &toml::Value,
    725 ) -> Result<(), String> {
    726     let Some(manifest_table) = manifest.as_table() else {
    727         return Err(format!("{owner}/Cargo.toml must be a TOML table"));
    728     };
    729     for section in ["dependencies", "dev-dependencies", "build-dependencies"] {
    730         if let Some(dependencies) = manifest_table.get(section).and_then(toml::Value::as_table) {
    731             validate_dependency_path_table(
    732                 workspace_root,
    733                 package_root,
    734                 owner,
    735                 section,
    736                 dependencies,
    737             )?;
    738         }
    739     }
    740     if let Some(targets) = manifest_table.get("target").and_then(toml::Value::as_table) {
    741         for (target, target_value) in targets {
    742             let Some(target_table) = target_value.as_table() else {
    743                 continue;
    744             };
    745             for section in ["dependencies", "dev-dependencies", "build-dependencies"] {
    746                 if let Some(dependencies) =
    747                     target_table.get(section).and_then(toml::Value::as_table)
    748                 {
    749                     validate_dependency_path_table(
    750                         workspace_root,
    751                         package_root,
    752                         owner,
    753                         &format!("target.{target}.{section}"),
    754                         dependencies,
    755                     )?;
    756                 }
    757             }
    758         }
    759     }
    760     Ok(())
    761 }
    762 
    763 fn validate_dependency_path_table(
    764     workspace_root: &Path,
    765     path_base: &Path,
    766     owner: &str,
    767     section: &str,
    768     dependencies: &toml::value::Table,
    769 ) -> Result<(), String> {
    770     for (dependency, declaration) in dependencies {
    771         let Some(path) = declaration
    772             .as_table()
    773             .and_then(|table| table.get("path"))
    774             .and_then(toml::Value::as_str)
    775         else {
    776             continue;
    777         };
    778         let resolved = fs::canonicalize(path_base.join(path)).map_err(|error| {
    779             format!(
    780                 "resolve production dependency {owner} {section}.{dependency} path {path}: {error}"
    781             )
    782         })?;
    783         if !resolved.starts_with(workspace_root) {
    784             return Err(format!(
    785                 "production dependency {owner} {section}.{dependency} path {path} escapes the repository; use a registry version or an external local-development override"
    786             ));
    787         }
    788     }
    789     Ok(())
    790 }
    791 
    792 fn dependency_package_name(package_root: &Path) -> Result<String, String> {
    793     let manifest_path = package_root.join("Cargo.toml");
    794     let raw = fs::read_to_string(&manifest_path).map_err(|error| {
    795         format!(
    796             "read dependency manifest {}: {error}",
    797             manifest_path.display()
    798         )
    799     })?;
    800     let manifest = raw.parse::<toml::Value>().map_err(|error| {
    801         format!(
    802             "parse dependency manifest {}: {error}",
    803             manifest_path.display()
    804         )
    805     })?;
    806     manifest
    807         .get("package")
    808         .and_then(toml::Value::as_table)
    809         .and_then(|package| package.get("name"))
    810         .and_then(toml::Value::as_str)
    811         .map(str::to_owned)
    812         .ok_or_else(|| format!("{} is missing package.name", manifest_path.display()))
    813 }
    814 
    815 fn validate_public_manifest_field(
    816     package: &toml::value::Table,
    817     key: &str,
    818     workspace_value: &str,
    819     expected: &str,
    820     package_name: &str,
    821 ) -> Result<(), String> {
    822     let value = resolve_public_string(package, key, workspace_value)
    823         .ok_or_else(|| format!("public package {package_name} must declare {key}"))?;
    824     if value != expected {
    825         return Err(format!(
    826             "public package {package_name} {key} {value} must be {expected}"
    827         ));
    828     }
    829     Ok(())
    830 }
    831 
    832 fn resolve_public_string<'a>(
    833     package: &'a toml::value::Table,
    834     key: &str,
    835     workspace_value: &'a str,
    836 ) -> Option<&'a str> {
    837     match package.get(key)? {
    838         toml::Value::String(value) => Some(value),
    839         toml::Value::Table(value)
    840             if value.get("workspace").and_then(toml::Value::as_bool) == Some(true) =>
    841         {
    842             Some(workspace_value)
    843         }
    844         _ => None,
    845     }
    846 }
    847 
    848 fn resolve_public_authors<'a>(
    849     package: &'a toml::value::Table,
    850     workspace_authors: &'a [String],
    851 ) -> Option<Vec<&'a str>> {
    852     match package.get("authors")? {
    853         toml::Value::Array(values) => values.iter().map(toml::Value::as_str).collect(),
    854         toml::Value::Table(value)
    855             if value.get("workspace").and_then(toml::Value::as_bool) == Some(true) =>
    856         {
    857             Some(workspace_authors.iter().map(String::as_str).collect())
    858         }
    859         _ => None,
    860     }
    861 }
    862 
    863 fn validate_workspace_members(workspace_root: &Path) -> Result<(), String> {
    864     let manifest_path = workspace_root.join("Cargo.toml");
    865     let manifest_raw = fs::read_to_string(&manifest_path)
    866         .map_err(|error| format!("read {}: {error}", manifest_path.display()))?;
    867     let manifest = toml::from_str::<WorkspaceMembershipManifest>(&manifest_raw)
    868         .map_err(|error| format!("parse {}: {error}", manifest_path.display()))?;
    869     let manifest_value = manifest_raw
    870         .parse::<toml::Value>()
    871         .map_err(|error| format!("parse {}: {error}", manifest_path.display()))?;
    872     validate_manifest_dependency_keys(&manifest_value, "Cargo.toml")?;
    873     let declared = manifest
    874         .workspace
    875         .members
    876         .into_iter()
    877         .collect::<BTreeSet<_>>();
    878     let mut discovered = BTreeSet::new();
    879     for root in ["crates", "tools"] {
    880         let directory = workspace_root.join(root);
    881         for entry in fs::read_dir(&directory)
    882             .map_err(|error| format!("read {}: {error}", directory.display()))?
    883         {
    884             let entry =
    885                 entry.map_err(|error| format!("read {} entry: {error}", directory.display()))?;
    886             if entry
    887                 .file_type()
    888                 .map_err(|error| format!("read {} type: {error}", entry.path().display()))?
    889                 .is_dir()
    890                 && entry.path().join("Cargo.toml").is_file()
    891             {
    892                 let directory_name = entry.file_name().to_string_lossy().into_owned();
    893                 require_lower_snake_case(
    894                     &directory_name,
    895                     &format!("{root}/{directory_name} crate directory"),
    896                 )?;
    897                 let package_manifest_path = entry.path().join("Cargo.toml");
    898                 let package_manifest_raw =
    899                     fs::read_to_string(&package_manifest_path).map_err(|error| {
    900                         format!("read {}: {error}", package_manifest_path.display())
    901                     })?;
    902                 let package_manifest =
    903                     package_manifest_raw
    904                         .parse::<toml::Value>()
    905                         .map_err(|error| {
    906                             format!("parse {}: {error}", package_manifest_path.display())
    907                         })?;
    908                 let package_name = package_manifest
    909                     .get("package")
    910                     .and_then(toml::Value::as_table)
    911                     .and_then(|package| package.get("name"))
    912                     .and_then(toml::Value::as_str)
    913                     .ok_or_else(|| {
    914                         format!(
    915                             "{} is missing package.name",
    916                             package_manifest_path.display()
    917                         )
    918                     })?;
    919                 require_lower_snake_case(
    920                     package_name,
    921                     &format!("{} package.name", package_manifest_path.display()),
    922                 )?;
    923                 validate_manifest_dependency_keys(
    924                     &package_manifest,
    925                     &package_manifest_path.display().to_string(),
    926                 )?;
    927                 discovered.insert(format!("{root}/{directory_name}"));
    928             }
    929         }
    930     }
    931     if declared != discovered {
    932         let missing = discovered
    933             .difference(&declared)
    934             .cloned()
    935             .collect::<Vec<_>>()
    936             .join(", ");
    937         let unknown = declared
    938             .difference(&discovered)
    939             .cloned()
    940             .collect::<Vec<_>>()
    941             .join(", ");
    942         return Err(format!(
    943             "workspace membership is missing local package roots: {missing}; workspace membership has unknown package roots: {unknown}"
    944         ));
    945     }
    946     Ok(())
    947 }
    948 
    949 fn validate_manifest_dependency_keys(manifest: &toml::Value, label: &str) -> Result<(), String> {
    950     let Some(manifest) = manifest.as_table() else {
    951         return Err(format!("{label} must be a TOML table"));
    952     };
    953     for section in ["dependencies", "dev-dependencies", "build-dependencies"] {
    954         if let Some(dependencies) = manifest.get(section).and_then(toml::Value::as_table) {
    955             validate_dependency_keys(dependencies, label, section)?;
    956         }
    957     }
    958     if let Some(workspace_dependencies) = manifest
    959         .get("workspace")
    960         .and_then(toml::Value::as_table)
    961         .and_then(|workspace| workspace.get("dependencies"))
    962         .and_then(toml::Value::as_table)
    963     {
    964         validate_dependency_keys(workspace_dependencies, label, "workspace.dependencies")?;
    965     }
    966     if let Some(targets) = manifest.get("target").and_then(toml::Value::as_table) {
    967         for (target, target_value) in targets {
    968             let Some(target_table) = target_value.as_table() else {
    969                 continue;
    970             };
    971             for section in ["dependencies", "dev-dependencies", "build-dependencies"] {
    972                 if let Some(dependencies) =
    973                     target_table.get(section).and_then(toml::Value::as_table)
    974                 {
    975                     validate_dependency_keys(
    976                         dependencies,
    977                         label,
    978                         &format!("target.{target}.{section}"),
    979                     )?;
    980                 }
    981             }
    982         }
    983     }
    984     Ok(())
    985 }
    986 
    987 fn validate_dependency_keys(
    988     dependencies: &toml::value::Table,
    989     label: &str,
    990     section: &str,
    991 ) -> Result<(), String> {
    992     for key in dependencies
    993         .keys()
    994         .filter(|key| key.starts_with("radroots"))
    995     {
    996         require_lower_snake_case(key, &format!("{label} {section} dependency key"))?;
    997     }
    998     Ok(())
    999 }
   1000 
   1001 fn require_lower_snake_case(value: &str, label: &str) -> Result<(), String> {
   1002     if is_lower_snake_case(value) {
   1003         Ok(())
   1004     } else {
   1005         Err(format!("{label} `{value}` must use lowercase snake case"))
   1006     }
   1007 }
   1008 
   1009 fn is_lower_snake_case(value: &str) -> bool {
   1010     !value.is_empty()
   1011         && value.split('_').all(|segment| {
   1012             segment
   1013                 .as_bytes()
   1014                 .first()
   1015                 .is_some_and(u8::is_ascii_lowercase)
   1016                 && segment
   1017                     .bytes()
   1018                     .all(|byte| byte.is_ascii_lowercase() || byte.is_ascii_digit())
   1019         })
   1020 }
   1021 
   1022 fn validate_ledger(
   1023     workspace_root: &Path,
   1024     expected_architecture_id: &str,
   1025     raw: &str,
   1026 ) -> Result<(), String> {
   1027     let ledger = toml::from_str::<DeviationLedger>(raw)
   1028         .map_err(|error| format!("parse {DEVIATIONS_RELATIVE}: {error}"))?;
   1029     if ledger.schema_version != 1 {
   1030         return Err("deviation ledger schema_version must be 1".to_owned());
   1031     }
   1032     if ledger.architecture_id != expected_architecture_id
   1033         || ledger.architecture_id != ARCHITECTURE_ID
   1034     {
   1035         return Err(format!(
   1036             "deviation ledger architecture_id {} must match {}",
   1037             ledger.architecture_id, expected_architecture_id
   1038         ));
   1039     }
   1040 
   1041     let mut ids = BTreeSet::new();
   1042     for record in &ledger.deviation {
   1043         validate_record(workspace_root, record)?;
   1044         if !ids.insert(record.id.as_str()) {
   1045             return Err(format!("duplicate deviation id {}", record.id));
   1046         }
   1047     }
   1048     Ok(())
   1049 }
   1050 
   1051 fn validate_record(workspace_root: &Path, record: &DeviationRecord) -> Result<(), String> {
   1052     if !is_deviation_id(&record.id) {
   1053         return Err(format!("deviation id {} must use RCRV1-DEV-NNN", record.id));
   1054     }
   1055     if !is_iso_date(&record.date) {
   1056         return Err(format!("deviation {} date must use YYYY-MM-DD", record.id));
   1057     }
   1058     if !matches!(record.status.as_str(), "active" | "closed" | "superseded") {
   1059         return Err(format!(
   1060             "deviation {} status must be active, closed, or superseded",
   1061             record.id
   1062         ));
   1063     }
   1064     require_text(&record.id, "approval", &record.approval)?;
   1065     require_text(&record.id, "replacement_action", &record.replacement_action)?;
   1066     require_text(&record.id, "unresolved_risk", &record.unresolved_risk)?;
   1067     require_nonempty_list(&record.id, "affected_steps", &record.affected_steps)?;
   1068     require_nonempty_list(&record.id, "spec_anchors", &record.spec_anchors)?;
   1069     require_nonempty_list(&record.id, "source_evidence", &record.source_evidence)?;
   1070     require_nonempty_list(&record.id, "verification", &record.verification)?;
   1071 
   1072     for step in &record.affected_steps {
   1073         let valid = step.len() == 3
   1074             && step.bytes().all(|byte| byte.is_ascii_digit())
   1075             && step
   1076                 .parse::<u16>()
   1077                 .is_ok_and(|value| (1..=315).contains(&value));
   1078         if !valid {
   1079             return Err(format!(
   1080                 "deviation {} affected step {} must be in 001..315",
   1081                 record.id, step
   1082             ));
   1083         }
   1084     }
   1085     for anchor in &record.spec_anchors {
   1086         validate_spec_anchor(workspace_root, &record.id, anchor)?;
   1087     }
   1088     if record.status == "active" && !record.closure_evidence.is_empty() {
   1089         return Err(format!(
   1090             "active deviation {} must not carry closure_evidence",
   1091             record.id
   1092         ));
   1093     }
   1094     if record.status != "active" {
   1095         require_nonempty_list(&record.id, "closure_evidence", &record.closure_evidence)?;
   1096     }
   1097 
   1098     let _ = (record.normative_architecture_change, record.adr_required);
   1099     Ok(())
   1100 }
   1101 
   1102 fn validate_spec_anchor(
   1103     workspace_root: &Path,
   1104     deviation_id: &str,
   1105     anchor: &str,
   1106 ) -> Result<(), String> {
   1107     let (relative, fragment) = anchor
   1108         .split_once('#')
   1109         .map_or((anchor, None), |(path, fragment)| (path, Some(fragment)));
   1110     if relative.trim().is_empty() || fragment.is_none_or(|value| value.trim().is_empty()) {
   1111         return Err(format!(
   1112             "deviation {deviation_id} has invalid spec anchor {anchor}"
   1113         ));
   1114     }
   1115     let path = Path::new(relative);
   1116     if path.components().any(|component| {
   1117         matches!(
   1118             component,
   1119             Component::ParentDir | Component::RootDir | Component::Prefix(_)
   1120         )
   1121     }) {
   1122         return Err(format!(
   1123             "deviation {deviation_id} spec anchor must be repository-relative: {anchor}"
   1124         ));
   1125     }
   1126     if relative != ARCHITECTURE_RELATIVE {
   1127         return Err(format!(
   1128             "deviation {deviation_id} spec anchor must target {ARCHITECTURE_RELATIVE}: {anchor}"
   1129         ));
   1130     }
   1131     let machine_path = workspace_root.join(path);
   1132     let raw = fs::read_to_string(&machine_path)
   1133         .map_err(|error| format!("read {}: {error}", machine_path.display()))?;
   1134     let machine = toml::from_str::<toml::Value>(&raw)
   1135         .map_err(|error| format!("parse {}: {error}", machine_path.display()))?;
   1136     let selector = fragment.expect("fragment is required above");
   1137     if !machine_selector_exists(&machine, selector) {
   1138         return Err(format!(
   1139             "deviation {deviation_id} spec anchor has unknown machine selector {selector}"
   1140         ));
   1141     }
   1142     Ok(())
   1143 }
   1144 
   1145 fn machine_selector_exists(machine: &toml::Value, selector: &str) -> bool {
   1146     let segments = selector.split('.').collect::<Vec<_>>();
   1147     if segments.iter().any(|segment| segment.is_empty()) {
   1148         return false;
   1149     }
   1150     let root = match machine.as_table() {
   1151         Some(root) => root,
   1152         None => return false,
   1153     };
   1154     match segments.as_slice() {
   1155         ["repositories", repository] => root
   1156             .get("repositories")
   1157             .and_then(toml::Value::as_table)
   1158             .and_then(|repositories| repositories.get(*repository))
   1159             .is_some_and(toml::Value::is_table),
   1160         ["repository_policy"] | ["release_policy"] => {
   1161             root.get(segments[0]).is_some_and(toml::Value::is_table)
   1162         }
   1163         ["quality_policy", "coverage"] => root
   1164             .get("quality_policy")
   1165             .and_then(toml::Value::as_table)
   1166             .and_then(|quality| quality.get("coverage"))
   1167             .is_some_and(toml::Value::is_table),
   1168         ["package", package] => root
   1169             .get("package")
   1170             .and_then(toml::Value::as_array)
   1171             .is_some_and(|packages| {
   1172                 packages.iter().any(|candidate| {
   1173                     candidate
   1174                         .as_table()
   1175                         .and_then(|table| table.get("name"))
   1176                         .and_then(toml::Value::as_str)
   1177                         == Some(*package)
   1178                 })
   1179             }),
   1180         _ => false,
   1181     }
   1182 }
   1183 
   1184 fn require_text(deviation_id: &str, field: &str, value: &str) -> Result<(), String> {
   1185     if value.trim().is_empty() {
   1186         return Err(format!(
   1187             "deviation {deviation_id} field {field} must not be empty"
   1188         ));
   1189     }
   1190     Ok(())
   1191 }
   1192 
   1193 fn require_nonempty_list(deviation_id: &str, field: &str, values: &[String]) -> Result<(), String> {
   1194     if values.is_empty() || values.iter().any(|value| value.trim().is_empty()) {
   1195         return Err(format!(
   1196             "deviation {deviation_id} field {field} must contain non-empty values"
   1197         ));
   1198     }
   1199     Ok(())
   1200 }
   1201 
   1202 fn is_deviation_id(value: &str) -> bool {
   1203     value
   1204         .strip_prefix("RCRV1-DEV-")
   1205         .is_some_and(|suffix| suffix.len() == 3 && suffix.bytes().all(|byte| byte.is_ascii_digit()))
   1206 }
   1207 
   1208 fn is_iso_date(value: &str) -> bool {
   1209     value.len() == 10
   1210         && value.as_bytes()[4] == b'-'
   1211         && value.as_bytes()[7] == b'-'
   1212         && value
   1213             .bytes()
   1214             .enumerate()
   1215             .all(|(index, byte)| matches!(index, 4 | 7) || byte.is_ascii_digit())
   1216 }
   1217 
   1218 #[cfg(test)]
   1219 mod tests {
   1220     use std::{
   1221         collections::BTreeMap,
   1222         fs,
   1223         path::PathBuf,
   1224         time::{SystemTime, UNIX_EPOCH},
   1225     };
   1226 
   1227     use super::{
   1228         ArchitectureIdentity, ArchitecturePackage, ArchitectureRepository, validate_ledger,
   1229         validate_no_production_sibling_paths, validate_public_dependency_versions,
   1230         validate_public_package_metadata, validate_workspace_members, validate_workspace_toolchain,
   1231     };
   1232 
   1233     fn test_root(label: &str) -> PathBuf {
   1234         let nonce = SystemTime::now()
   1235             .duration_since(UNIX_EPOCH)
   1236             .expect("clock")
   1237             .as_nanos();
   1238         let root = std::env::temp_dir().join(format!("radroots_architecture_{label}_{nonce}"));
   1239         fs::create_dir_all(root.join("contracts/crates/release_v1")).expect("create spec root");
   1240         fs::write(
   1241             root.join("contracts/crates/release_v1/radroots_crates_release_v1.toml"),
   1242             "spec_id = \"radroots.crates.release.v1\"\n\n[repositories.sdk]\nurl = \"https://github.com/radrootslabs/sdk\"\n",
   1243         )
   1244         .expect("write spec");
   1245         root
   1246     }
   1247 
   1248     fn complete_ledger() -> &'static str {
   1249         r#"schema_version = 1
   1250 architecture_id = "radroots.crates.release.v1"
   1251 
   1252 [[deviation]]
   1253 id = "RCRV1-DEV-001"
   1254 date = "2026-07-27"
   1255 status = "active"
   1256 approval = "Explicit user correction dated 2026-07-27."
   1257 affected_steps = ["015", "016"]
   1258 spec_anchors = ["contracts/crates/release_v1/radroots_crates_release_v1.toml#repositories.sdk"]
   1259 source_evidence = ["The approved architecture assigns packages to the existing lib and sdk repositories."]
   1260 replacement_action = "Keep both standalone repositories and verify them independently."
   1261 verification = ["Repository-local architecture validation passes."]
   1262 unresolved_risk = "Remote publication remains separately authorized."
   1263 normative_architecture_change = false
   1264 adr_required = false
   1265 "#
   1266     }
   1267 
   1268     fn architecture() -> ArchitectureIdentity {
   1269         ArchitectureIdentity {
   1270             spec_id: "radroots.crates.release.v1".to_string(),
   1271             edition: "2024".to_string(),
   1272             resolver: "3".to_string(),
   1273             rust_version: "1.97.1".to_string(),
   1274             license: "MIT OR Apache-2.0".to_string(),
   1275             canonical_repositories: vec!["https://github.com/radrootslabs/sdk".to_string()],
   1276             repositories: BTreeMap::from([(
   1277                 "sdk".to_string(),
   1278                 ArchitectureRepository {
   1279                     url: "https://github.com/radrootslabs/sdk".to_string(),
   1280                     version: "0.1.0".to_string(),
   1281                     packages: vec!["radroots".to_string()],
   1282                 },
   1283             )]),
   1284             package: vec![ArchitecturePackage {
   1285                 name: "radroots".to_string(),
   1286             }],
   1287         }
   1288     }
   1289 
   1290     fn complete_workspace_manifest(members: &str) -> String {
   1291         format!(
   1292             "[workspace]\nmembers = [{members}]\nresolver = \"3\"\n\n[workspace.package]\nversion = \"0.1.0\"\nedition = \"2024\"\nrust-version = \"1.97.1\"\nlicense = \"MIT OR Apache-2.0\"\nrepository = \"https://github.com/radrootslabs/sdk\"\nhomepage = \"https://radroots.org\"\nreadme = \"README\"\nauthors = [\"Tyson Lupul <tyson@radroots.org>\"]\n\n[workspace.metadata.radroots.public-package]\nversion = \"0.1.0\"\nauthors = [\"Tyson Lupul <tyson@radroots.org>\"]\nreadme = \"README.md\"\n\n[workspace.lints.rust]\nunsafe_code = \"deny\"\n\n[workspace.lints.rustdoc]\nbroken_intra_doc_links = \"deny\"\n\n[workspace.lints.clippy]\ndbg_macro = \"deny\"\ntodo = \"deny\"\nunimplemented = \"deny\"\n"
   1293         )
   1294     }
   1295 
   1296     #[test]
   1297     fn accepts_complete_active_deviation() {
   1298         let root = test_root("complete");
   1299         validate_ledger(&root, "radroots.crates.release.v1", complete_ledger())
   1300             .expect("complete deviation");
   1301         let _ = fs::remove_dir_all(root);
   1302     }
   1303 
   1304     #[test]
   1305     fn rejects_incomplete_active_deviation() {
   1306         let root = test_root("incomplete");
   1307         let incomplete = complete_ledger().replace(
   1308             "spec_anchors = [\"contracts/crates/release_v1/radroots_crates_release_v1.toml#repositories.sdk\"]",
   1309             "spec_anchors = []",
   1310         );
   1311         let error = validate_ledger(&root, "radroots.crates.release.v1", &incomplete)
   1312             .expect_err("missing anchor must fail");
   1313         assert!(error.contains("field spec_anchors must contain non-empty values"));
   1314         let _ = fs::remove_dir_all(root);
   1315     }
   1316 
   1317     #[test]
   1318     fn deviation_anchors_require_resolving_machine_selectors() {
   1319         let root = test_root("machine_selector");
   1320         let invalid = complete_ledger().replace("#repositories.sdk", "#garbage");
   1321         let error = validate_ledger(&root, "radroots.crates.release.v1", &invalid)
   1322             .expect_err("arbitrary fragment must fail");
   1323         assert!(error.contains("unknown machine selector garbage"));
   1324 
   1325         let markdown_slug =
   1326             complete_ledger().replace("#repositories.sdk", "#20-current-to-target-migration-map");
   1327         let error = validate_ledger(&root, "radroots.crates.release.v1", &markdown_slug)
   1328             .expect_err("Markdown heading slug must fail");
   1329         assert!(error.contains("unknown machine selector"));
   1330         let _ = fs::remove_dir_all(root);
   1331     }
   1332 
   1333     #[test]
   1334     fn workspace_membership_requires_every_local_package_root() {
   1335         let root = test_root("workspace_members");
   1336         for path in ["crates/a", "tools/xtask"] {
   1337             fs::create_dir_all(root.join(path)).expect("create package root");
   1338             fs::write(
   1339                 root.join(path).join("Cargo.toml"),
   1340                 "[package]\nname = \"fixture\"\n",
   1341             )
   1342             .expect("write package manifest");
   1343         }
   1344         fs::write(
   1345             root.join("Cargo.toml"),
   1346             "[workspace]\nmembers = [\"crates/a\", \"tools/xtask\"]\nresolver = \"3\"\n\n[workspace.package]\nrust-version = \"1.97.1\"\n",
   1347         )
   1348         .expect("write complete workspace");
   1349         validate_workspace_members(&root).expect("complete membership");
   1350 
   1351         fs::write(
   1352             root.join("Cargo.toml"),
   1353             "[workspace]\nmembers = [\"tools/xtask\"]\nresolver = \"3\"\n\n[workspace.package]\nrust-version = \"1.97.1\"\n",
   1354         )
   1355         .expect("write incomplete workspace");
   1356         let error = validate_workspace_members(&root).expect_err("missing member must fail");
   1357         assert!(error.contains("missing local package roots: crates/a"));
   1358         let _ = fs::remove_dir_all(root);
   1359     }
   1360 
   1361     #[test]
   1362     fn workspace_identity_rejects_kebab_case_crate_names_and_dependency_keys() {
   1363         let root = test_root("workspace_snake_case_identity");
   1364         fs::create_dir_all(root.join("crates/radroots_probe")).expect("create package root");
   1365         fs::create_dir_all(root.join("tools/xtask")).expect("create xtask root");
   1366         fs::write(
   1367             root.join("Cargo.toml"),
   1368             "[workspace]\nmembers = [\"crates/radroots_probe\", \"tools/xtask\"]\nresolver = \"3\"\n",
   1369         )
   1370         .expect("write workspace manifest");
   1371         fs::write(
   1372             root.join("crates/radroots_probe/Cargo.toml"),
   1373             "[package]\nname = \"radroots-probe\"\n",
   1374         )
   1375         .expect("write kebab package manifest");
   1376         fs::write(
   1377             root.join("tools/xtask/Cargo.toml"),
   1378             "[package]\nname = \"xtask\"\n",
   1379         )
   1380         .expect("write xtask manifest");
   1381 
   1382         let package_error = validate_workspace_members(&root)
   1383             .expect_err("kebab-case Cargo package identity must fail");
   1384         assert!(package_error.contains("package.name"));
   1385         assert!(package_error.contains("lowercase snake case"));
   1386 
   1387         fs::write(
   1388             root.join("crates/radroots_probe/Cargo.toml"),
   1389             "[package]\nname = \"radroots_probe\"\n",
   1390         )
   1391         .expect("write snake package manifest");
   1392         fs::write(
   1393             root.join("Cargo.toml"),
   1394             "[workspace]\nmembers = [\"crates/radroots_probe\", \"tools/xtask\"]\nresolver = \"3\"\n\n[workspace.dependencies]\nradroots-probe = \"0.1.0\"\n",
   1395         )
   1396         .expect("write kebab dependency key");
   1397 
   1398         let dependency_error = validate_workspace_members(&root)
   1399             .expect_err("kebab-case Radroots dependency key must fail");
   1400         assert!(dependency_error.contains("workspace.dependencies dependency key"));
   1401         assert!(dependency_error.contains("lowercase snake case"));
   1402         let _ = fs::remove_dir_all(root);
   1403     }
   1404 
   1405     #[test]
   1406     fn workspace_toolchain_requires_exact_resolver_and_rust_version() {
   1407         let root = test_root("workspace_toolchain");
   1408         fs::write(root.join("Cargo.toml"), complete_workspace_manifest(""))
   1409             .expect("write workspace manifest");
   1410         for path in ["README", "LICENSE-MIT", "LICENSE-APACHE"] {
   1411             fs::write(root.join(path), "fixture\n").expect("write workspace metadata file");
   1412         }
   1413         fs::write(
   1414             root.join("rust-toolchain.toml"),
   1415             "[toolchain]\nchannel = \"1.97.1\"\n",
   1416         )
   1417         .expect("write toolchain");
   1418         let architecture = architecture();
   1419         validate_workspace_toolchain(&root, &architecture).expect("exact toolchain policy");
   1420 
   1421         fs::write(
   1422             root.join("rust-toolchain.toml"),
   1423             "[toolchain]\nchannel = \"1.97.0\"\n",
   1424         )
   1425         .expect("write mismatched toolchain");
   1426         let error = validate_workspace_toolchain(&root, &architecture)
   1427             .expect_err("mismatched toolchain must fail");
   1428         assert!(error.contains("channel must match"));
   1429         let _ = fs::remove_dir_all(root);
   1430     }
   1431 
   1432     #[test]
   1433     fn public_package_metadata_requires_canonical_inheritance() {
   1434         let root = test_root("public_package_metadata");
   1435         fs::create_dir_all(root.join("crates/radroots")).expect("create public package");
   1436         fs::write(
   1437             root.join("Cargo.toml"),
   1438             complete_workspace_manifest("\"crates/radroots\""),
   1439         )
   1440         .expect("write workspace manifest");
   1441         let manifest = "[package]\nname = \"radroots\"\nversion.workspace = true\nedition.workspace = true\nrust-version.workspace = true\nlicense.workspace = true\nrepository.workspace = true\nhomepage.workspace = true\nauthors.workspace = true\nreadme = \"README.md\"\npublish = [\"crates-io\"]\n\n[lints]\nworkspace = true\n";
   1442         fs::write(root.join("crates/radroots/Cargo.toml"), manifest)
   1443             .expect("write public manifest");
   1444         fs::write(root.join("crates/radroots/README.md"), "fixture\n")
   1445             .expect("write public readme");
   1446         validate_public_package_metadata(&root, &architecture()).expect("canonical metadata");
   1447 
   1448         fs::write(
   1449             root.join("crates/radroots/Cargo.toml"),
   1450             manifest.replace("[lints]\nworkspace = true\n", ""),
   1451         )
   1452         .expect("write incomplete public manifest");
   1453         let error = validate_public_package_metadata(&root, &architecture())
   1454             .expect_err("missing lint inheritance must fail");
   1455         assert!(error.contains("must inherit the workspace lint policy"));
   1456         let _ = fs::remove_dir_all(root);
   1457     }
   1458 
   1459     #[test]
   1460     fn public_package_inventory_requires_every_local_package() {
   1461         let root = test_root("public_package_inventory");
   1462         fs::write(root.join("Cargo.toml"), complete_workspace_manifest(""))
   1463             .expect("write workspace manifest");
   1464         let error = validate_public_package_metadata(&root, &architecture())
   1465             .expect_err("missing local public package must fail");
   1466         assert!(error.contains("workspace public package inventory is missing: radroots"));
   1467         let _ = fs::remove_dir_all(root);
   1468     }
   1469 
   1470     #[test]
   1471     fn public_dependencies_require_path_and_exact_version() {
   1472         let root = test_root("public_dependency_version");
   1473         fs::create_dir_all(root.join("crates/radroots")).expect("create public package");
   1474         fs::create_dir_all(root.join("crates/dependency")).expect("create dependency package");
   1475         fs::write(
   1476             root.join("Cargo.toml"),
   1477             format!(
   1478                 "{}\n[workspace.dependencies]\nradroots_core = {{ package = \"radroots_core\", path = \"crates/dependency\", version = \"=0.1.0\" }}\n",
   1479                 complete_workspace_manifest("\"crates/radroots\"")
   1480             ),
   1481         )
   1482         .expect("write workspace manifest");
   1483         fs::write(
   1484             root.join("crates/radroots/Cargo.toml"),
   1485             "[package]\nname = \"radroots\"\nversion = \"0.1.0\"\n\n[dependencies]\nradroots_core = { workspace = true }\n",
   1486         )
   1487         .expect("write public manifest");
   1488         fs::write(
   1489             root.join("crates/dependency/Cargo.toml"),
   1490             "[package]\nname = \"radroots_core\"\nversion = \"0.1.0\"\n",
   1491         )
   1492         .expect("write dependency manifest");
   1493         let mut architecture = architecture();
   1494         architecture.package.push(ArchitecturePackage {
   1495             name: "radroots_core".to_owned(),
   1496         });
   1497         architecture
   1498             .repositories
   1499             .get_mut("sdk")
   1500             .expect("sdk repository")
   1501             .packages
   1502             .push("radroots_core".to_owned());
   1503         validate_public_dependency_versions(&root, &architecture)
   1504             .expect("path plus exact version public dependency");
   1505 
   1506         let workspace_path = root.join("Cargo.toml");
   1507         let workspace = fs::read_to_string(&workspace_path).expect("read workspace manifest");
   1508         fs::write(
   1509             &workspace_path,
   1510             workspace.replace(", version = \"=0.1.0\"", ""),
   1511         )
   1512         .expect("write path-only dependency");
   1513         let error = validate_public_dependency_versions(&root, &architecture)
   1514             .expect_err("path-only public dependency must fail");
   1515         assert!(error.contains("must declare path and exact version =0.1.0"));
   1516         let _ = fs::remove_dir_all(root);
   1517     }
   1518 
   1519     #[test]
   1520     fn production_dependencies_reject_sibling_paths() {
   1521         let root = test_root("production_sibling_path");
   1522         let sibling = root.with_extension("sibling");
   1523         fs::create_dir_all(root.join("crates/radroots")).expect("create public package");
   1524         fs::create_dir_all(root.join("crates/dependency")).expect("create local dependency");
   1525         fs::create_dir_all(&sibling).expect("create sibling dependency");
   1526         fs::write(
   1527             root.join("Cargo.toml"),
   1528             complete_workspace_manifest("\"crates/radroots\""),
   1529         )
   1530         .expect("write workspace manifest");
   1531         let sibling_path = sibling.to_string_lossy();
   1532         fs::write(
   1533             root.join("crates/radroots/Cargo.toml"),
   1534             format!(
   1535                 "[package]\nname = \"radroots\"\nversion = \"0.1.0\"\n\n[dependencies]\nprobe = {{ path = \"{sibling_path}\" }}\n"
   1536             ),
   1537         )
   1538         .expect("write sibling dependency");
   1539         let error = validate_no_production_sibling_paths(&root)
   1540             .expect_err("sibling production path must fail");
   1541         assert!(error.contains("escapes the repository"));
   1542 
   1543         fs::write(
   1544             root.join("crates/radroots/Cargo.toml"),
   1545             "[package]\nname = \"radroots\"\nversion = \"0.1.0\"\n\n[dependencies]\nprobe = { path = \"../dependency\" }\n",
   1546         )
   1547         .expect("write local dependency");
   1548         fs::create_dir_all(root.join(".cargo")).expect("create cargo config directory");
   1549         fs::write(
   1550             root.join(".cargo/config.toml"),
   1551             format!("[patch.crates-io]\nprobe = {{ path = \"{sibling_path}\" }}\n"),
   1552         )
   1553         .expect("write development override");
   1554         validate_no_production_sibling_paths(&root).expect("in-repository path");
   1555         let _ = fs::remove_dir_all(root);
   1556         let _ = fs::remove_dir_all(sibling);
   1557     }
   1558 }