app

Local-first trade for farms and co-ops
git clone https://radroots.dev/git/app.git
Log | Files | Refs | README | LICENSE

lib.rs (98823B)


      1 use sha2::{Digest, Sha256};
      2 use std::collections::BTreeSet;
      3 use std::fs::{self, OpenOptions};
      4 use std::io::{ErrorKind, Read};
      5 use std::path::{Component, Path, PathBuf};
      6 use std::process::Command as ProcessCommand;
      7 use std::str::FromStr;
      8 
      9 #[cfg(unix)]
     10 use std::os::unix::fs::{MetadataExt, OpenOptionsExt};
     11 
     12 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
     13 pub enum Command {
     14     DesignSourceAudit,
     15     RepoAudit,
     16     NamespaceAudit,
     17     ProvenanceCheck,
     18     QualificationReport,
     19 }
     20 
     21 impl FromStr for Command {
     22     type Err = String;
     23 
     24     fn from_str(value: &str) -> Result<Self, Self::Err> {
     25         match value {
     26             "design-source-audit" => Ok(Self::DesignSourceAudit),
     27             "repo-audit" => Ok(Self::RepoAudit),
     28             "namespace-audit" => Ok(Self::NamespaceAudit),
     29             "provenance-check" => Ok(Self::ProvenanceCheck),
     30             "qualification-report" => Ok(Self::QualificationReport),
     31             _ => Err(format!("unknown xtask command: {value}")),
     32         }
     33     }
     34 }
     35 
     36 pub fn run(root: &Path, command: Command) -> Result<String, Vec<String>> {
     37     let build_mode =
     38         std::env::var("HARVESTCIRCLE_BUILD_MODE").unwrap_or_else(|_| "standalone".to_owned());
     39     if command == Command::QualificationReport
     40         && !matches!(build_mode.as_str(), "standalone" | "governed")
     41     {
     42         return Err(vec![format!(
     43             "unknown qualification build mode: {build_mode}"
     44         )]);
     45     }
     46     let inventory = Inventory::load(root).map_err(|finding| vec![finding])?;
     47     let mut findings = Vec::new();
     48     match command {
     49         Command::DesignSourceAudit => design_source_audit(root, &inventory, &mut findings),
     50         Command::RepoAudit => repo_audit(root, &inventory, &mut findings),
     51         Command::NamespaceAudit => namespace_audit(root, &inventory, &mut findings),
     52         Command::ProvenanceCheck => provenance_check(root, &inventory, &mut findings),
     53         Command::QualificationReport => {
     54             repo_audit(root, &inventory, &mut findings);
     55             namespace_audit(root, &inventory, &mut findings);
     56             provenance_check(root, &inventory, &mut findings);
     57             design_source_audit(root, &inventory, &mut findings);
     58             product_shell_audit(root, &inventory, &mut findings);
     59         }
     60     }
     61     findings.sort();
     62     findings.dedup();
     63     if findings.is_empty() {
     64         let inventory_kind = if inventory.git_aware {
     65             "git"
     66         } else {
     67             "archive"
     68         };
     69         let command_name = match command {
     70             Command::DesignSourceAudit => "design-source-audit",
     71             Command::RepoAudit => "repo-audit",
     72             Command::NamespaceAudit => "namespace-audit",
     73             Command::ProvenanceCheck => "provenance-check",
     74             Command::QualificationReport => "qualification-report",
     75         };
     76         let mode = if command == Command::QualificationReport {
     77             format!("harvestcircle.build.mode={build_mode}\n")
     78         } else {
     79             String::new()
     80         };
     81         Ok(format!(
     82             "harvestcircle.xtask.command={command_name}\nharvestcircle.xtask.inventory={inventory_kind}\n{mode}harvestcircle.xtask.result=pass\n"
     83         ))
     84     } else {
     85         Err(findings)
     86     }
     87 }
     88 
     89 #[derive(Debug)]
     90 struct Inventory {
     91     paths: Vec<String>,
     92     git_aware: bool,
     93 }
     94 
     95 impl Inventory {
     96     fn load(root: &Path) -> Result<Self, String> {
     97         if root.join(".git").exists() {
     98             let output = ProcessCommand::new("git")
     99                 .args([
    100                     "-C",
    101                     &root.to_string_lossy(),
    102                     "ls-files",
    103                     "--cached",
    104                     "--others",
    105                     "--exclude-standard",
    106                     "-z",
    107                 ])
    108                 .output()
    109                 .map_err(|error| {
    110                     format!("unable to enumerate tracked HarvestCircle sources: {error}")
    111                 })?;
    112             if !output.status.success() {
    113                 return Err("unable to enumerate tracked HarvestCircle sources".to_owned());
    114             }
    115             let mut paths = Vec::new();
    116             for raw_path in output
    117                 .stdout
    118                 .split(|byte| *byte == 0)
    119                 .filter(|path| !path.is_empty())
    120             {
    121                 let path = String::from_utf8(raw_path.to_vec())
    122                     .map_err(|_| "Git inventory path is not valid UTF-8".to_owned())?;
    123                 validate_git_inventory_path(root, Path::new(&path))?;
    124                 paths.push(path);
    125             }
    126             paths.sort();
    127             paths.dedup();
    128             Ok(Self {
    129                 paths,
    130                 git_aware: true,
    131             })
    132         } else {
    133             let mut paths = Vec::new();
    134             archive_paths(root, root, &mut paths)?;
    135             paths.sort();
    136             paths.dedup();
    137             Ok(Self {
    138                 paths,
    139                 git_aware: false,
    140             })
    141         }
    142     }
    143 }
    144 
    145 fn validate_git_inventory_path(root: &Path, relative: &Path) -> Result<(), String> {
    146     if relative.is_absolute()
    147         || relative.components().next().is_none()
    148         || relative
    149             .components()
    150             .any(|component| !matches!(component, Component::Normal(_)))
    151     {
    152         return Err(format!(
    153             "{}: Git inventory path must be normalized and relative",
    154             relative.display()
    155         ));
    156     }
    157     let components = relative.components().collect::<Vec<_>>();
    158     let mut current = root.to_path_buf();
    159     for (index, component) in components.iter().enumerate() {
    160         current.push(component.as_os_str());
    161         let metadata = match fs::symlink_metadata(&current) {
    162             Ok(metadata) => metadata,
    163             Err(error) if error.kind() == ErrorKind::NotFound => {
    164                 return Err(format!(
    165                     "{}: Git inventory path is missing",
    166                     relative.display()
    167                 ));
    168             }
    169             Err(error) => {
    170                 return Err(format!(
    171                     "{}: unable to inspect Git inventory path: {error}",
    172                     relative.display()
    173                 ));
    174             }
    175         };
    176         if metadata.file_type().is_symlink() {
    177             return Err(format!(
    178                 "{}: Git inventory path traverses a symbolic link",
    179                 relative.display()
    180             ));
    181         }
    182         if index + 1 < components.len() {
    183             if !metadata.is_dir() {
    184                 return Err(format!(
    185                     "{}: Git inventory path parent is not a directory",
    186                     relative.display()
    187                 ));
    188             }
    189         } else if !metadata.is_file() {
    190             return Err(format!(
    191                 "{}: Git inventory path is not a regular file",
    192                 relative.display()
    193             ));
    194         }
    195     }
    196     Ok(())
    197 }
    198 
    199 fn archive_paths(root: &Path, directory: &Path, paths: &mut Vec<String>) -> Result<(), String> {
    200     let entries = fs::read_dir(directory).map_err(|error| {
    201         format!(
    202             "{}: unable to read archive inventory: {error}",
    203             directory.display()
    204         )
    205     })?;
    206     for entry in entries {
    207         let entry = entry.map_err(|error| format!("archive inventory entry failed: {error}"))?;
    208         let path = entry.path();
    209         let relative = relative(root, &path)?;
    210         let first = relative.split('/').next().unwrap_or_default();
    211         if matches!(
    212             first,
    213             ".git" | ".gradle" | ".kotlin" | ".idea" | "build" | "target" | "out"
    214         ) || relative
    215             .split('/')
    216             .any(|part| matches!(part, "build" | "target" | "out"))
    217         {
    218             continue;
    219         }
    220         paths.push(relative);
    221         if entry
    222             .file_type()
    223             .map_err(|error| format!("unable to classify archive entry: {error}"))?
    224             .is_dir()
    225         {
    226             archive_paths(root, &path, paths)?;
    227         }
    228     }
    229     Ok(())
    230 }
    231 
    232 fn repo_audit(root: &Path, inventory: &Inventory, findings: &mut Vec<String>) {
    233     let required = [
    234         "README.md",
    235         "NOTICE",
    236         "CONTRIBUTING.md",
    237         "SECURITY.md",
    238         "LICENSE",
    239         "LICENSES/GPL-3.0-only.txt",
    240         "LICENSES/OFL-1.1.txt",
    241     ];
    242     for required_path in required {
    243         if !inventory.paths.iter().any(|path| path == required_path) {
    244             findings.push(format!(
    245                 "{required_path}: required public repository file is missing"
    246             ));
    247         }
    248     }
    249     for path in &inventory.paths {
    250         let normalized = path.to_ascii_lowercase();
    251         if is_forbidden_documentation_or_workflow_path(&normalized) {
    252             findings.push(format!("{path}: forbidden repository root"));
    253         }
    254         if fs::symlink_metadata(root.join(path))
    255             .is_ok_and(|metadata| metadata.file_type().is_symlink())
    256         {
    257             findings.push(format!(
    258                 "{path}: symbolic links are not allowed in public sources"
    259             ));
    260         }
    261         if normalized.starts_with("core/target/")
    262             || normalized.contains("/build/")
    263             || normalized.contains("/generated/")
    264             || normalized.contains("generated/uniffi")
    265             || [".dylib", ".so", ".dll", ".class"]
    266                 .iter()
    267                 .any(|suffix| normalized.ends_with(suffix))
    268         {
    269             findings.push(format!(
    270                 "{path}: generated build output must not be source controlled"
    271             ));
    272         }
    273         if [".pem", ".key", ".p12", ".pfx", ".jks", ".keystore", ".env"]
    274             .iter()
    275             .any(|suffix| normalized.ends_with(suffix))
    276             || normalized.contains("/credentials/")
    277         {
    278             findings.push(format!("{path}: credential or secret-shaped source path"));
    279         }
    280         if is_text(path) {
    281             let source = read_text(root, path);
    282             let markers = [
    283                 ["-----BEGIN ", "PRIVATE KEY-----"].concat(),
    284                 ["AWS_", "SECRET_ACCESS_KEY="].concat(),
    285                 ["gh", "p_"].concat(),
    286                 ["sk_", "live_"].concat(),
    287             ];
    288             if markers.iter().any(|marker| source.contains(marker)) {
    289                 findings.push(format!(
    290                     "{path}: credential or private-key material in source text"
    291                 ));
    292             }
    293         }
    294     }
    295     git_source_policy(root, findings);
    296     native_runtime_boundary(root, findings);
    297 }
    298 
    299 fn native_runtime_boundary(root: &Path, findings: &mut Vec<String>) {
    300     let domain_lib = root.join("core/crates/harvestcircle_domain/src/lib.rs");
    301     if !domain_lib.is_file() {
    302         return;
    303     }
    304 
    305     if root
    306         .join("core/crates/harvestcircle_domain/src/relay.rs")
    307         .exists()
    308         || read_text(root, "core/crates/harvestcircle_domain/src/lib.rs").contains("mod relay")
    309     {
    310         findings
    311             .push("harvestcircle_domain: duplicate relay policy surface is forbidden".to_owned());
    312     }
    313 
    314     let nostr_manifest = read_text(root, "core/crates/harvestcircle_nostr/Cargo.toml");
    315     let production_manifest = nostr_manifest
    316         .split_once("[dev-dependencies]")
    317         .map_or(nostr_manifest.as_str(), |(production, _)| production);
    318     if production_manifest.contains("nostr-sdk") {
    319         findings.push(
    320             "harvestcircle_nostr: production nostr-sdk connection authority is forbidden"
    321                 .to_owned(),
    322         );
    323     }
    324     let nostr_client = read_text(root, "core/crates/harvestcircle_nostr/src/client.rs");
    325     for required in [
    326         "radroots_transport_nostr::{Config, NostrTransport, RelayEndpoint, RelayProfile}",
    327         "parse_verified_kind0",
    328         "FetchBounds::new(MAX_PROFILE_EVENTS_PER_FETCH",
    329     ] {
    330         if !nostr_client.contains(required) {
    331             findings.push(format!(
    332                 "harvestcircle_nostr: governed transport boundary is missing {required}"
    333             ));
    334         }
    335     }
    336 
    337     for (path, forbidden) in [
    338         ("core/crates/harvestcircle_ffi/src/commands.rs", "OnceLock"),
    339         (
    340             "core/crates/harvestcircle_ffi/src/commands.rs",
    341             "PoisonError::into_inner",
    342         ),
    343         (
    344             "core/crates/harvestcircle_ffi/src/observer.rs",
    345             "PoisonError::into_inner",
    346         ),
    347         (
    348             "core/crates/harvestcircle_application/src/app_core.rs",
    349             "PoisonError::into_inner",
    350         ),
    351         (
    352             "core/crates/harvestcircle_application/src/custody.rs",
    353             "PoisonError::into_inner",
    354         ),
    355         (
    356             "core/crates/harvestcircle_application/src/secrets.rs",
    357             "PoisonError::into_inner",
    358         ),
    359     ] {
    360         if read_text(root, path).contains(forbidden) {
    361             findings.push(format!("{path}: forbidden runtime boundary {forbidden}"));
    362         }
    363     }
    364 
    365     let runtime = read_text(root, "core/crates/harvestcircle_ffi/src/host_runtime.rs");
    366     let keyring = read_text(root, "core/crates/harvestcircle_ffi/src/keyring_worker.rs");
    367     for (source, required, owner) in [
    368         (&runtime, "pub(crate) struct HostRuntime", "host runtime"),
    369         (&runtime, "pub(crate) async fn shutdown", "host runtime"),
    370         (
    371             &keyring,
    372             "const KEYRING_QUEUE_CAPACITY: usize = 8",
    373             "keyring worker",
    374         ),
    375         (
    376             &keyring,
    377             "pub(crate) struct BoundedKeyringWorker",
    378             "keyring worker",
    379         ),
    380         (
    381             &keyring,
    382             "use tokio::sync::{oneshot, watch}",
    383             "keyring worker",
    384         ),
    385         (&keyring, "response_receiver.await", "keyring worker"),
    386         (
    387             &keyring,
    388             "const KEYRING_SHUTDOWN_DEADLINE: Duration = Duration::from_secs(30)",
    389             "keyring worker",
    390         ),
    391         (&keyring, "OPERATION_QUEUED", "keyring worker"),
    392         (&keyring, "OPERATION_STARTED", "keyring worker"),
    393         (&keyring, "OPERATION_COMPLETED", "keyring worker"),
    394         (&keyring, "OPERATION_CANCELLED", "keyring worker"),
    395     ] {
    396         if !source.contains(required) {
    397             findings.push(format!(
    398                 "harvestcircle_ffi: {owner} contract is missing {required}"
    399             ));
    400         }
    401     }
    402     if keyring.contains("response_receiver.recv") {
    403         findings
    404             .push("harvestcircle_ffi: keyring response blocks a Tokio runtime thread".to_owned());
    405     }
    406     if keyring.contains("std::sync::mpsc::Receiver") {
    407         findings.push("harvestcircle_ffi: keyring response exposes a blocking receiver".to_owned());
    408     }
    409     let native_keyring = read_text(root, "core/crates/harvestcircle_storage/src/os_keyring.rs");
    410     let native_verify = native_keyring
    411         .split_once("    fn verify<'a>(")
    412         .and_then(|(_, source)| source.split_once("\n    fn load("))
    413         .map(|(body, _)| body)
    414         .unwrap_or_default();
    415     for required in [
    416         "request_id: &'a DurableRequestId",
    417         "secret: SecretKeyInput",
    418         "self.operation()?",
    419         "Zeroizing::new(platform_read(&account).map_err(map_read_error)?)",
    420         "verify_replay_binding(request_id, &secret, encoded.as_slice())",
    421     ] {
    422         if !native_verify.contains(required) {
    423             findings.push(format!(
    424                 "harvestcircle_storage: read-only verification is missing {required}"
    425             ));
    426         }
    427     }
    428     for forbidden in ["platform_create(", "platform_delete(", ".put(", ".delete("] {
    429         if native_verify.contains(forbidden) {
    430             findings.push(format!(
    431                 "harvestcircle_storage: verification mutates custody through {forbidden}"
    432             ));
    433         }
    434     }
    435     let worker_verify = keyring
    436         .split_once("Request::Verify(request_id, public_key, secret, phase, response) => {")
    437         .and_then(|(_, source)| source.split_once("Request::Load("))
    438         .map(|(body, _)| body)
    439         .unwrap_or_default();
    440     for required in [
    441         "start_operation(&phase)",
    442         "store.verify(&request_id, public_key, secret).await",
    443         "finish_operation(&phase)",
    444         "response.send(result)",
    445     ] {
    446         if !worker_verify.contains(required) {
    447             findings.push(format!(
    448                 "harvestcircle_ffi: verification lifecycle is missing {required}"
    449             ));
    450         }
    451     }
    452     let worker_submit = keyring
    453         .split_once("    fn verify<'a>(")
    454         .and_then(|(_, source)| source.split_once("\n    fn load("))
    455         .map(|(body, _)| body)
    456         .unwrap_or_default();
    457     if !worker_submit.contains("self.submit(|phase, response|")
    458         || !worker_submit
    459             .contains("Request::Verify(request_id.clone(), public_key, secret, phase, response)")
    460         || worker_submit.contains("Request::Put(")
    461     {
    462         findings.push(
    463             "harvestcircle_ffi: verification bypasses the bounded read-only worker submission"
    464                 .to_owned(),
    465         );
    466     }
    467 }
    468 
    469 fn namespace_audit(root: &Path, inventory: &Inventory, findings: &mut Vec<String>) {
    470     let legacy = ["stu", "dio"].concat();
    471     let temporary_namespace = ["org", "radroots", "harvestcircle"].join(".");
    472     let inherited_preferences = [
    473         ["use", "radroots", "dns"].join("_"),
    474         ["use", "radroots", "subnets"].join("_"),
    475         ["vpn", "on", "demand", "enabled"].join("_"),
    476         ["run", "as", "exit", "node"].join("_"),
    477         ["automatically", "check", "for", "updates"].join("_"),
    478         ["update", "channel"].join("_"),
    479         ["last", "update", "check", "summary"].join("_"),
    480         ["alternate", "server", "url"].join("_"),
    481     ];
    482     for path in &inventory.paths {
    483         let normalized = path.to_ascii_lowercase();
    484         if normalized.contains(&legacy) {
    485             findings.push(format!("{path}: legacy product name in source path"));
    486         }
    487         if normalized.starts_with("app/")
    488             && normalized.contains("/kotlin/")
    489             && normalized.ends_with(".kt")
    490         {
    491             let package_path = normalized
    492                 .split_once("/kotlin/")
    493                 .map(|(_, value)| value)
    494                 .unwrap_or_default();
    495             if !package_path.starts_with("org/harvestcircle/") {
    496                 findings.push(format!(
    497                     "{path}: Kotlin source is outside the final namespace"
    498                 ));
    499             }
    500         }
    501         if !is_text(path) {
    502             continue;
    503         }
    504         let source = read_text(root, path);
    505         if source.to_ascii_lowercase().contains(&legacy) {
    506             findings.push(format!("{path}: legacy product name in source text"));
    507         }
    508         if source.contains(&temporary_namespace)
    509             || source.contains(&temporary_namespace.replace('.', "/"))
    510         {
    511             findings.push(format!("{path}: temporary product namespace"));
    512         }
    513         let production_kotlin = path.ends_with(".kt")
    514             && path.starts_with("app/")
    515             && ["/src/main/", "/src/commonMain/", "/src/desktopMain/"]
    516                 .iter()
    517                 .any(|segment| path.contains(segment));
    518         let bounded_health = path
    519             == "app/desktop/src/main/kotlin/org/harvestcircle/desktop/Main.kt"
    520             && source.contains("HEALTH_CHECK_ARGUMENT")
    521             && source.contains("withTimeout(HEALTH_TIMEOUT_MILLIS)");
    522         if production_kotlin && source.contains(&["run", "Blocking"].concat()) && !bounded_health {
    523             findings.push(format!(
    524                 "{path}: blocking coroutine bridge in application source"
    525             ));
    526         }
    527         if production_kotlin
    528             && (source.contains(&["Atomic", "Long"].concat())
    529                 || source.contains(&["desktop", "-operation:"].concat()))
    530         {
    531             findings.push(format!("{path}: process-local operation counter"));
    532         }
    533         if path.starts_with("app/shared/src/commonMain/")
    534             && [
    535                 ["org.harvestcircle.", "ffi"].concat(),
    536                 ["com.sun.", "jna"].concat(),
    537                 "java.".to_owned(),
    538                 "javax.".to_owned(),
    539             ]
    540             .iter()
    541             .any(|marker| source.contains(marker))
    542         {
    543             findings.push(format!(
    544                 "{path}: platform dependency in shared common source"
    545             ));
    546         }
    547         let lowercase = source.to_ascii_lowercase();
    548         for token in &inherited_preferences {
    549             if lowercase.contains(token) {
    550                 findings.push(format!("{path}: inherited non-product preference {token}"));
    551             }
    552         }
    553         let secret_marker = ["nsec", "1"].concat();
    554         let inspected_secret = lowercase.replace(&format!("{secret_marker}…"), "");
    555         if production_kotlin && inspected_secret.contains(&secret_marker) {
    556             findings.push(format!("{path}: secret key literal in production Kotlin"));
    557         }
    558     }
    559 }
    560 
    561 fn product_shell_audit(root: &Path, inventory: &Inventory, findings: &mut Vec<String>) {
    562     let required = [
    563         "app/shared/src/commonMain/kotlin/org/harvestcircle/product/SurfaceRegistry.kt",
    564         "app/shared/src/commonMain/kotlin/org/harvestcircle/navigation/Navigation.kt",
    565         "app/shared/src/commonMain/kotlin/org/harvestcircle/appearance/AppearanceState.kt",
    566         "app/design_system/src/commonMain/kotlin/org/harvestcircle/designsystem/theme/HarvestCircleTheme.kt",
    567         "app/design_system/src/commonMain/kotlin/org/harvestcircle/designsystem/component/action/HarvestCircleButton.kt",
    568         "app/design_system/src/commonMain/kotlin/org/harvestcircle/designsystem/component/feedback/HarvestCircleBadge.kt",
    569         "app/design_system/src/commonMain/kotlin/org/harvestcircle/designsystem/layout/HarvestCircleAppFrame.kt",
    570         "app/shared/src/commonMain/kotlin/org/harvestcircle/ui/shell/HarvestCircleShell.kt",
    571         "app/shared/src/commonMain/kotlin/org/harvestcircle/ui/shell/FoundationTodayScreen.kt",
    572         "app/shared/src/commonMain/kotlin/org/harvestcircle/ui/shell/FoundationNetworkScreen.kt",
    573         "app/shared/src/commonMain/kotlin/org/harvestcircle/ui/shell/FoundationSettingsScreen.kt",
    574         "app/shared/src/commonMain/kotlin/org/harvestcircle/ui/shell/ShellAccessibility.kt",
    575     ];
    576     for path in required {
    577         if !inventory.paths.iter().any(|candidate| candidate == path) {
    578             findings.push(format!("{path}: required product-shell source is missing"));
    579         }
    580     }
    581     let regression_matrix: &[(&str, &[&str])] = &[
    582         (
    583             "app/shared/src/commonTest/kotlin/org/harvestcircle/application/HarvestCirclePresenterTest.kt",
    584             &["hcSc001", "hcSc002"],
    585         ),
    586         (
    587             "app/shared/src/commonTest/kotlin/org/harvestcircle/application/HarvestCircleShellPresenterTest.kt",
    588             &["hcSc003", "HcSc004"],
    589         ),
    590         (
    591             "app/shared/src/commonTest/kotlin/org/harvestcircle/application/ReferenceInputPolicyTest.kt",
    592             &["hcSc005", "hcSc006", "hcSc007"],
    593         ),
    594         (
    595             "app/design_system/src/commonTest/kotlin/org/harvestcircle/designsystem/theme/HarvestCircleColorContrastTest.kt",
    596             &["hcSc008", "hcSc009"],
    597         ),
    598         (
    599             "app/shared/src/desktopTest/kotlin/org/harvestcircle/ui/shell/OwnedControlsUiTest.kt",
    600             &["hcSc010"],
    601         ),
    602         (
    603             "app/shared/src/desktopTest/kotlin/org/harvestcircle/ui/shell/ShellAccessibilityUiTest.kt",
    604             &["hcSc011"],
    605         ),
    606         (
    607             "app/shared/src/commonTest/kotlin/org/harvestcircle/application/ShellOverlaysTest.kt",
    608             &["hcSc012", "hcSl001", "hcSl006"],
    609         ),
    610         (
    611             "app/shared/src/commonTest/kotlin/org/harvestcircle/application/ReferenceInputPolicyTest.kt",
    612             &["hcSl001"],
    613         ),
    614         (
    615             "app/shared/src/commonTest/kotlin/org/harvestcircle/application/HarvestCirclePresenterTest.kt",
    616             &[
    617                 "hcSl002", "hcSl003", "hcSl004", "hcSl005", "hcEx001", "hcEx002", "hcEx003",
    618             ],
    619         ),
    620         (
    621             "app/shared/src/desktopTest/kotlin/org/harvestcircle/ui/shell/BootstrapIdentityEntryTest.kt",
    622             &["hcEx004"],
    623         ),
    624         (
    625             "app/shared/src/commonTest/kotlin/org/harvestcircle/application/HarvestCircleShellPresenterTest.kt",
    626             &["hcSl001", "hcSl006"],
    627         ),
    628         (
    629             "app/shared/src/commonTest/kotlin/org/harvestcircle/application/ImportSecretDraftTest.kt",
    630             &["hcSl005"],
    631         ),
    632         (
    633             "app/shared/src/desktopTest/kotlin/org/harvestcircle/ui/shell/FoundationOverlayHostTest.kt",
    634             &["hcSl006"],
    635         ),
    636     ];
    637     for (path, markers) in regression_matrix {
    638         let source = read_text(root, path);
    639         for marker in *markers {
    640             if !source.contains(marker) {
    641                 findings.push(format!(
    642                     "{path}: required shell-security regression marker is missing: {marker}"
    643                 ));
    644             }
    645         }
    646     }
    647     let closure_source_contract: &[(&str, &[&str])] = &[
    648         (
    649             "app/shared/src/commonMain/kotlin/org/harvestcircle/application/ReferenceInputPolicy.kt",
    650             &["data object AmbiguousHex", "hasAmbiguousHexShape"],
    651         ),
    652         (
    653             "app/shared/src/commonMain/kotlin/org/harvestcircle/application/ImportSecretDraft.kt",
    654             &[
    655                 "class ImportSecretDraft private constructor",
    656                 "private var characters: CharArray?",
    657             ],
    658         ),
    659         (
    660             "app/shared/src/commonMain/kotlin/org/harvestcircle/application/PresentationModels.kt",
    661             &[
    662                 "val importDraft: ImportSecretDraft",
    663                 "class EditImportDraft private constructor",
    664             ],
    665         ),
    666         (
    667             "app/shared/src/commonMain/kotlin/org/harvestcircle/application/HarvestCirclePresenter.kt",
    668             &[
    669                 "PendingRemovalLease",
    670                 "removalMutex",
    671                 "expireRemovalLease",
    672                 "releaseClaimedRemoval",
    673                 "PresenterClosePhase.TransferredToShutdown",
    674                 "ImportSecretDraft",
    675             ],
    676         ),
    677         (
    678             "app/shared/src/commonMain/kotlin/org/harvestcircle/application/ShellOverlays.kt",
    679             &["ReferenceInputAdmission.AmbiguousHex"],
    680         ),
    681         (
    682             "app/shared/src/commonMain/kotlin/org/harvestcircle/ui/shell/FoundationOverlayHost.kt",
    683             &["val overlayBusy = (overlay as? FoundationOverlay.ConfirmAction)?.busy == true"],
    684         ),
    685         (
    686             "app/shared/src/commonMain/kotlin/org/harvestcircle/ui/shell/BootstrapIdentityEntry.kt",
    687             &[
    688                 "The secret is held only for this import.",
    689                 "It is cleared after it is sent to the local native runtime.",
    690             ],
    691         ),
    692     ];
    693     for (path, markers) in closure_source_contract {
    694         let source = read_text(root, path);
    695         for marker in *markers {
    696             if !source.contains(marker) {
    697                 findings.push(format!(
    698                     "{path}: required secret-lifecycle closure source marker is missing: {marker}"
    699                 ));
    700             }
    701         }
    702     }
    703     let presenter_tests = read_text(
    704         root,
    705         "app/shared/src/commonTest/kotlin/org/harvestcircle/application/HarvestCirclePresenterTest.kt",
    706     );
    707     for forbidden in ["Thread.sleep", "kotlinx.coroutines.delay("] {
    708         if presenter_tests.contains(forbidden) {
    709             findings.push(format!(
    710                 "automatic-expiry tests must use virtual time, not {forbidden}"
    711             ));
    712         }
    713     }
    714     let bootstrap_entry = read_text(
    715         root,
    716         "app/shared/src/commonMain/kotlin/org/harvestcircle/ui/shell/BootstrapIdentityEntry.kt",
    717     );
    718     let retired_copy = [
    719         "The secret is sent directly to the local native runtime ",
    720         "and is not retained in the interface.",
    721     ]
    722     .concat();
    723     if bootstrap_entry.contains(&retired_copy) {
    724         findings.push("Bootstrap identity entry retains retired secret-custody copy".to_owned());
    725     }
    726     let locked_copy = [
    727         (
    728             "app/shared/src/commonMain/kotlin/org/harvestcircle/ui/shell/HarvestCircleShell.kt",
    729             &[
    730                 "Coordinate local food with clear, signed terms.",
    731                 "You do not need a HarvestCircle account.",
    732                 "Open source · Nostr-based · No managed service required",
    733             ][..],
    734         ),
    735         (
    736             "app/shared/src/commonMain/kotlin/org/harvestcircle/ui/shell/FoundationTodayScreen.kt",
    737             &[
    738                 "No active commitments",
    739                 "Explore nearby buying circles or open a shared Nostr reference.",
    740                 "Not available in this build.",
    741             ][..],
    742         ),
    743         (
    744             "app/shared/src/commonMain/kotlin/org/harvestcircle/ui/shell/FoundationNetworkScreen.kt",
    745             &[
    746                 "Overview",
    747                 "Identity",
    748                 "Public relays",
    749                 "Runtime",
    750                 "No managed HarvestCircle service is configured.",
    751             ][..],
    752         ),
    753         (
    754             "app/shared/src/commonMain/kotlin/org/harvestcircle/ui/shell/FoundationSettingsScreen.kt",
    755             &[
    756                 "Appearance",
    757                 "Project",
    758                 "Theme",
    759                 "Text size",
    760                 "Motion",
    761                 "FFI contract",
    762                 "Storage schema",
    763             ][..],
    764         ),
    765     ];
    766     for (path, expected) in locked_copy {
    767         let source = read_text(root, path);
    768         for text in expected {
    769             if !source.contains(text) {
    770                 findings.push(format!(
    771                     "{path}: locked product-shell copy is missing: {text}"
    772                 ));
    773             }
    774         }
    775     }
    776     for path in &inventory.paths {
    777         if !is_production_kotlin(path) {
    778             continue;
    779         }
    780         let source = read_text(root, path);
    781         let normalized_path = path.to_ascii_lowercase();
    782         let compact = source
    783             .chars()
    784             .filter(|character| !character.is_whitespace())
    785             .collect::<String>();
    786         for (shape, diagnostic) in [
    787             ("funShellText(", "superseded shell text adapter"),
    788             ("funShellButton(", "superseded shell button adapter"),
    789             ("funShellTextField(", "superseded shell field adapter"),
    790             (
    791                 "enumclassShellTextRole",
    792                 "superseded shell text-role adapter",
    793             ),
    794             (
    795                 "enumclassShellButtonKind",
    796                 "superseded shell button-kind adapter",
    797             ),
    798         ] {
    799             if compact.contains(shape) {
    800                 findings.push(format!("{path}: {diagnostic}"));
    801             }
    802         }
    803         if source.contains("androidx.compose.material") {
    804             findings.push(format!(
    805                 "{path}: Material component dependency is forbidden"
    806             ));
    807         }
    808         for (shape, diagnostic) in [
    809             (
    810                 "dataobjectConfirmIdentityRemoval",
    811                 "retired parameterless confirmation source shape",
    812             ),
    813             (
    814                 "dataobjectCancelIdentityRemoval",
    815                 "retired parameterless confirmation source shape",
    816             ),
    817             (
    818                 "isOverlayIntent.EditReference->classifyNostrReference(",
    819                 "parser-on-edit source shape",
    820             ),
    821             (
    822                 "OverlayIntent.Open(FoundationOverlay.OpenNostrReference(",
    823                 "prefilled reference ingress source shape",
    824             ),
    825             (
    826                 "selected=true,enabled=false",
    827                 "selected-as-disabled source shape",
    828             ),
    829             (
    830                 "valimportDraft:String",
    831                 "raw String import-draft custody source shape",
    832             ),
    833             (
    834                 "dataclassEditImportDraft",
    835                 "copyable import-draft intent source shape",
    836             ),
    837         ] {
    838             if compact.contains(shape) {
    839                 findings.push(format!("{path}: {diagnostic}"));
    840             }
    841         }
    842         if path
    843             == "app/shared/src/commonMain/kotlin/org/harvestcircle/ui/shell/FoundationOverlayHost.kt"
    844             && compact.contains(
    845                 "funFoundationOverlayHost(state:OverlayState,status:ShellStatusModel,busy:Boolean",
    846             )
    847         {
    848             findings.push(format!(
    849                 "{path}: global busy state must not enter the overlay host"
    850             ));
    851         }
    852         if path == "app/shared/src/commonMain/kotlin/org/harvestcircle/application/ShellOverlays.kt"
    853             && compact.contains("state.identity.busy")
    854         {
    855             findings.push(format!(
    856                 "{path}: unrelated identity busy state must not gate overlay admission"
    857             ));
    858         }
    859         if normalized_path.ends_with("/harvestcirclescreen.kt") {
    860             findings.push(format!("{path}: superseded product-shell screen path"));
    861         }
    862         for marker in [
    863             "home-screen",
    864             "inactive-identities",
    865             "WindowBackgroundColor",
    866             "ButtonBackgroundColor",
    867             "InputBackgroundColor",
    868         ] {
    869             if source.contains(marker) {
    870                 findings.push(format!("{path}: superseded product-shell marker {marker}"));
    871             }
    872         }
    873         if is_production_compose(path, &source)
    874             && source.contains("focusRing: HarvestCircleFocusRing = HarvestCircleFocusRing.None")
    875         {
    876             findings.push(format!(
    877                 "{path}: interactive control defaults to a hidden keyboard focus ring"
    878             ));
    879         }
    880         let approved_color_adapter = path.starts_with(
    881             "app/design_system/src/commonMain/kotlin/org/harvestcircle/designsystem/theme/color/",
    882         ) || path
    883             == "app/design_system/src/commonMain/kotlin/org/harvestcircle/designsystem/shell/HarvestCircleShellVisuals.kt";
    884         if is_production_compose(path, &source)
    885             && !approved_color_adapter
    886             && contains_direct_call(&compact, "Color(")
    887         {
    888             findings.push(format!(
    889                 "{path}: hard-coded Compose color outside the theme adapter"
    890             ));
    891         }
    892         let approved_text_primitive = matches!(
    893             path.as_str(),
    894             "app/design_system/src/commonMain/kotlin/org/harvestcircle/designsystem/primitive/HarvestCircleText.kt"
    895                 | "app/design_system/src/commonMain/kotlin/org/harvestcircle/designsystem/shell/HarvestCircleShellText.kt"
    896         );
    897         let approved_input_primitive = matches!(
    898             path.as_str(),
    899             "app/design_system/src/commonMain/kotlin/org/harvestcircle/designsystem/component/input/HarvestCircleTextField.kt"
    900                 | "app/design_system/src/commonMain/kotlin/org/harvestcircle/designsystem/shell/HarvestCircleShellControls.kt"
    901         );
    902         if is_production_compose(path, &source) {
    903             if !approved_text_primitive && contains_direct_call(&compact, "BasicText(") {
    904                 findings.push(format!(
    905                     "{path}: BasicText bypasses the shell primitive adapter"
    906                 ));
    907             }
    908             if !approved_input_primitive && contains_direct_call(&compact, "BasicTextField(") {
    909                 findings.push(format!(
    910                     "{path}: BasicTextField bypasses the shell primitive adapter"
    911                 ));
    912             }
    913         }
    914         let lowercase = source.to_ascii_lowercase();
    915         for marker in [
    916             "sample farm",
    917             "sample commitment",
    918             "sample price",
    919             "sample event",
    920             "fake farm",
    921             "fake commitment",
    922             "pricecents",
    923             "commitmentid",
    924             "allocationid",
    925             "fulfillmentid",
    926         ] {
    927             if lowercase.contains(marker) {
    928                 findings.push(format!(
    929                     "{path}: fake commercial product data marker {marker}"
    930                 ));
    931             }
    932         }
    933     }
    934     for forbidden in [
    935         "app/shared/src/commonMain/kotlin/org/harvestcircle/design/HarvestCircleDesign.kt",
    936         "app/shared/src/commonMain/kotlin/org/harvestcircle/ui/shell/ShellControls.kt",
    937     ] {
    938         if inventory.paths.iter().any(|path| path == forbidden) {
    939             findings.push(format!(
    940                 "{forbidden}: superseded product-shell authority returned"
    941             ));
    942         }
    943     }
    944 }
    945 
    946 fn is_forbidden_documentation_or_workflow_path(path: &str) -> bool {
    947     path.split('/').any(|part| {
    948         matches!(
    949             part,
    950             "doc" | "docs" | "spec" | "specs" | ".github" | ".act" | "workflow" | "workflows"
    951         )
    952     })
    953 }
    954 
    955 fn is_production_kotlin(path: &str) -> bool {
    956     path.starts_with("app/")
    957         && path.ends_with(".kt")
    958         && ["/src/main/", "/src/commonMain/", "/src/desktopMain/"]
    959             .iter()
    960             .any(|segment| path.contains(segment))
    961 }
    962 
    963 fn is_production_compose(path: &str, source: &str) -> bool {
    964     is_production_kotlin(path)
    965         && (source.contains("@Composable") || source.contains("androidx.compose."))
    966 }
    967 
    968 fn contains_direct_call(source: &str, call: &str) -> bool {
    969     source.match_indices(call).any(|(index, _)| {
    970         source[..index]
    971             .chars()
    972             .next_back()
    973             .is_none_or(|character| !character.is_ascii_alphanumeric() && character != '_')
    974     })
    975 }
    976 
    977 fn manifest_declares_dependency(source: &str, dependency: &str) -> bool {
    978     source.lines().map(str::trim).any(|line| {
    979         if line.is_empty() || line.starts_with('#') {
    980             return false;
    981         }
    982         if line
    983             .split_once('=')
    984             .is_some_and(|(key, _)| key.trim().trim_matches('"') == dependency)
    985         {
    986             return true;
    987         }
    988         line.strip_prefix('[')
    989             .and_then(|value| value.strip_suffix(']'))
    990             .is_some_and(|table| {
    991                 let segments = table.split('.').collect::<Vec<_>>();
    992                 segments.contains(&"dependencies")
    993                     && segments
    994                         .last()
    995                         .is_some_and(|name| name.trim_matches('"') == dependency)
    996             })
    997     })
    998 }
    999 
   1000 fn sqlite_dependency_topology(root: &Path, inventory: &Inventory, findings: &mut Vec<String>) {
   1001     let cargo_lock = read_text(root, "core/Cargo.lock");
   1002     let package_count = |name: &str| {
   1003         let marker = format!("name = \"{name}\"");
   1004         cargo_lock
   1005             .lines()
   1006             .filter(|line| line.trim() == marker)
   1007             .count()
   1008     };
   1009     if package_count("libsqlite3-sys") != 1
   1010         || ["rusqlite", "refinery", "refinery-core", "refinery-macros"]
   1011             .iter()
   1012             .any(|name| package_count(name) != 0)
   1013     {
   1014         findings.push(
   1015             "core/Cargo.lock: exact single SQLx-selected native SQLite topology changed".to_owned(),
   1016         );
   1017     }
   1018 
   1019     for path in inventory
   1020         .paths
   1021         .iter()
   1022         .filter(|path| path.starts_with("core/") && path.ends_with("Cargo.toml"))
   1023     {
   1024         let manifest = read_text(root, path);
   1025         if ["rusqlite", "refinery", "libsqlite3-sys"]
   1026             .iter()
   1027             .any(|dependency| manifest_declares_dependency(&manifest, dependency))
   1028         {
   1029             findings.push(format!(
   1030                 "{path}: direct alternate or native SQLite dependency is forbidden"
   1031             ));
   1032         }
   1033     }
   1034 }
   1035 
   1036 fn development_integration_policy(root: &Path, findings: &mut Vec<String>) {
   1037     let makefile = read_text(root, "Makefile");
   1038     for required in [
   1039         "override CARGO := cargo +1.97.1",
   1040         "api-check: doctor",
   1041         "development-check: development-provenance-check source-check integration-check",
   1042         "governed-development-check:",
   1043         "governed-linux-x86_64-development-check: governed-doctor",
   1044     ] {
   1045         if makefile
   1046             .lines()
   1047             .filter(|line| line.trim() == required)
   1048             .count()
   1049             != 1
   1050         {
   1051             findings.push(format!(
   1052                 "Makefile: development integration boundary is missing {required}"
   1053             ));
   1054         }
   1055     }
   1056 
   1057     let runner = read_text(root, "tools/run-linux-x86_64-development-check.sh");
   1058     for required in [
   1059         "rust:1.97.1-slim-trixie@sha256:fc0648ac2962539be80bd424729a20fd80f7b64bfba7e90bbd642aed6c697c5a",
   1060         "--platform linux/amd64",
   1061         "EXT_BUILD_RUN_ACTIVE",
   1062         "--env JAVA_TOOL_OPTIONS=-Duser.home=/workspace/home",
   1063         "cargo deny --manifest-path core/Cargo.toml check --config core/deny.toml licenses sources",
   1064         "cargo test --manifest-path core/Cargo.toml --workspace --locked",
   1065         "cargo clippy --manifest-path core/Cargo.toml --workspace --all-targets --locked -- -D warnings",
   1066         ":app:desktop:integrationTest",
   1067         ":app:desktop:verifyUniFfiBindings",
   1068         "harvestcircle.linux_x86_64.development=pass",
   1069     ] {
   1070         if !runner.contains(required) {
   1071             findings.push(format!(
   1072                 "tools/run-linux-x86_64-development-check.sh: faithful runner is missing {required}"
   1073             ));
   1074         }
   1075     }
   1076     for forbidden in [
   1077         "cargo audit",
   1078         " advisories",
   1079         "dependencyCheck",
   1080         "releaseReadiness",
   1081         "unsignedReleaseReadiness",
   1082         "verifyReleaseSupplyChainEvidence",
   1083         "packageDmg",
   1084         "packageDeb",
   1085         "CycloneDX",
   1086         "SLSA",
   1087     ] {
   1088         if runner.contains(forbidden) {
   1089             findings.push(format!(
   1090                 "tools/run-linux-x86_64-development-check.sh: deferred release integration is active: {forbidden}"
   1091             ));
   1092         }
   1093     }
   1094 }
   1095 
   1096 fn provenance_check(root: &Path, inventory: &Inventory, findings: &mut Vec<String>) {
   1097     const LIB_REVISION: &str = "189c49b74b4bafc142b00b76b296477931139e72";
   1098     const PROVENANCE_PATH: &str = "core/provenance/harvestcircle-v1.toml";
   1099     const SOURCE_LOCK_PATH: &str = "radroots.lib.source-lock.v1.toml";
   1100     const MAX_SOURCE_LOCK_BYTES: u64 = 1024 * 1024;
   1101     const MAX_CARGO_LOCK_BYTES: u64 = 32 * 1024 * 1024;
   1102     let cargo = read_text(root, "core/Cargo.toml");
   1103     for authority in [
   1104         "repository = \"https://github.com/radrootslabs/harvestcircle\"".to_owned(),
   1105         format!(
   1106             "radroots_event = {{ git = \"https://github.com/radrootslabs/lib\", rev = \"{LIB_REVISION}\", version = \"=0.1.0-alpha\", default-features = false }}"
   1107         ),
   1108         format!(
   1109             "radroots_event_codec = {{ git = \"https://github.com/radrootslabs/lib\", rev = \"{LIB_REVISION}\", version = \"=0.1.0-alpha\", default-features = false }}"
   1110         ),
   1111         format!(
   1112             "radroots_identity = {{ git = \"https://github.com/radrootslabs/lib\", rev = \"{LIB_REVISION}\", version = \"=0.1.0-alpha\", default-features = false }}"
   1113         ),
   1114         format!(
   1115             "radroots_runtime_paths = {{ git = \"https://github.com/radrootslabs/lib\", rev = \"{LIB_REVISION}\", version = \"=0.1.0-alpha\", default-features = false }}"
   1116         ),
   1117         format!(
   1118             "radroots_service_sqlite = {{ git = \"https://github.com/radrootslabs/lib\", rev = \"{LIB_REVISION}\", version = \"=0.1.0-alpha\", default-features = false }}"
   1119         ),
   1120         format!(
   1121             "radroots_storage = {{ git = \"https://github.com/radrootslabs/lib\", rev = \"{LIB_REVISION}\", version = \"=0.1.0-alpha\", default-features = false }}"
   1122         ),
   1123         format!(
   1124             "radroots_transport = {{ git = \"https://github.com/radrootslabs/lib\", rev = \"{LIB_REVISION}\", version = \"=0.1.0-alpha\", default-features = false }}"
   1125         ),
   1126         format!(
   1127             "radroots_transport_nostr = {{ git = \"https://github.com/radrootslabs/lib\", rev = \"{LIB_REVISION}\", version = \"=0.1.0-alpha\", default-features = false }}"
   1128         ),
   1129     ] {
   1130         if cargo
   1131             .lines()
   1132             .filter(|line| line.trim() == authority)
   1133             .count()
   1134             != 1
   1135         {
   1136             findings.push(format!(
   1137                 "core/Cargo.toml: missing exact authority: {authority}"
   1138             ));
   1139         }
   1140     }
   1141     let provenance = read_text(root, PROVENANCE_PATH);
   1142     if !provenance.contains("source_product = \"HarvestCircle\"")
   1143         || !provenance
   1144             .contains("source_repository = \"https://github.com/radrootslabs/harvestcircle\"")
   1145         || !provenance.contains(&format!("canonical_radroots_revision = \"{LIB_REVISION}\""))
   1146         || !provenance
   1147             .contains("foundation_baseline = \"c08d18ea569351dddeef70d4c1410708daf067b6\"")
   1148     {
   1149         findings.push(format!(
   1150             "{PROVENANCE_PATH}: exact source provenance changed"
   1151         ));
   1152     }
   1153     let expected_source_lock = concat!(
   1154         "schema = \"radroots.lib.source-lock.v1\"\n",
   1155         "repository = \"https://github.com/radrootslabs/lib\"\n",
   1156         "revision = \"189c49b74b4bafc142b00b76b296477931139e72\"\n",
   1157         "architecture = \"radroots.crates.release.v2\"\n",
   1158         "workspace_catalog_sha256 = \"ee295f2352e2577a4052d980624415aec9871197d4fc9910a4c21c83a9179200\"\n",
   1159         "version = \"0.1.0-alpha\"\n",
   1160         "source_archive_sha256 = \"c648a3ab993d10253b9073e7e86db7b8970863bdf1d394d9fc30d66825695240\"\n",
   1161         "lockfile = \"core/Cargo.lock\"\n",
   1162         "lockfile_sha256 = \"6a32d1de0105771158647c65116fe797dfa7db7c42db515c63047d6601b846a3\"\n",
   1163     );
   1164     let source_lock_bytes =
   1165         match bounded_no_follow_bytes(root, Path::new(SOURCE_LOCK_PATH), MAX_SOURCE_LOCK_BYTES) {
   1166             Ok(bytes) => bytes,
   1167             Err(error) => {
   1168                 findings.push(format!("{SOURCE_LOCK_PATH}: {error}"));
   1169                 Vec::new()
   1170             }
   1171         };
   1172     let source_lock = String::from_utf8(source_lock_bytes).unwrap_or_default();
   1173     if source_lock != expected_source_lock {
   1174         findings.push(format!("{SOURCE_LOCK_PATH}: exact Lib source lock changed"));
   1175     }
   1176     let lockfile = exact_string_assignment(&source_lock, "lockfile");
   1177     let declared_lockfile_sha256 = exact_string_assignment(&source_lock, "lockfile_sha256");
   1178     let cargo_lock_bytes = lockfile
   1179         .as_deref()
   1180         .ok_or_else(|| "lockfile assignment is missing or duplicated".to_owned())
   1181         .and_then(|path| bounded_no_follow_bytes(root, Path::new(path), MAX_CARGO_LOCK_BYTES));
   1182     if let (Ok(bytes), Some(declared)) = (&cargo_lock_bytes, declared_lockfile_sha256.as_deref()) {
   1183         let actual = format!("{:x}", Sha256::digest(bytes));
   1184         if actual != declared {
   1185             findings.push(format!(
   1186                 "{SOURCE_LOCK_PATH}: lockfile_sha256 does not match actual bounded no-follow bytes"
   1187             ));
   1188         }
   1189     } else {
   1190         let error = cargo_lock_bytes
   1191             .as_ref()
   1192             .err()
   1193             .map(String::as_str)
   1194             .unwrap_or("lockfile_sha256 assignment is missing or duplicated");
   1195         findings.push(format!("{SOURCE_LOCK_PATH}: {error}"));
   1196     }
   1197     let cargo_lock = cargo_lock_bytes
   1198         .ok()
   1199         .and_then(|bytes| String::from_utf8(bytes).ok())
   1200         .unwrap_or_default();
   1201     if !cargo_lock.contains(&format!(
   1202         "source = \"git+https://github.com/radrootslabs/lib?rev={LIB_REVISION}#{LIB_REVISION}\""
   1203     )) {
   1204         findings.push("core/Cargo.lock: selected Lib revision is missing".to_owned());
   1205     }
   1206     sqlite_dependency_topology(root, inventory, findings);
   1207     development_integration_policy(root, findings);
   1208     let coordinates = properties(&read_text(
   1209         root,
   1210         "config/product/harvestcircle-v1.properties",
   1211     ));
   1212     for (key, expected) in [
   1213         ("storage.service_id", "harvestcircle"),
   1214         ("storage.instance_id", "desktop"),
   1215         ("storage.database_filename", "state.sqlite"),
   1216         ("storage.lock_filename", "state.lock"),
   1217         ("storage.application_id", "1212371505"),
   1218         ("storage.application_id_text", "HCR1"),
   1219         ("storage.initial_schema_version", "1"),
   1220         ("legacy.database.filename", "harvestcircle.sqlite3"),
   1221         ("legacy.database.disposition", "untouched_and_unsupported"),
   1222         ("platform.macos.architecture", "aarch64"),
   1223         ("platform.linux.architecture", "x86_64"),
   1224         ("limit.identities", "256"),
   1225         ("limit.unfinished_durable_operations", "1024"),
   1226         ("limit.preference_value_utf8_bytes", "4096"),
   1227         ("limit.relay_endpoints", "16"),
   1228         ("limit.relay_url_bytes", "2048"),
   1229         ("limit.events_per_relay", "64"),
   1230         ("limit.events_total", "1024"),
   1231         ("limit.observers", "32"),
   1232         ("limit.actor_mailbox", "64"),
   1233         ("limit.command_deadline_min_ms", "1"),
   1234         ("limit.command_deadline_max_ms", "30000"),
   1235         ("backup.member_limit", "caller_supplied_positive"),
   1236     ] {
   1237         if coordinates.get(key).map(String::as_str) != Some(expected) {
   1238             findings.push(format!(
   1239                 "config/product/harvestcircle-v1.properties: {key} must remain {expected}"
   1240             ));
   1241         }
   1242     }
   1243     let uniffi = read_text(root, "core/crates/harvestcircle_ffi/uniffi.toml");
   1244     let ffi_package = coordinates
   1245         .get("ffi.kotlin_package")
   1246         .map(String::as_str)
   1247         .unwrap_or_default();
   1248     let cdylib = coordinates
   1249         .get("ffi.cdylib_name")
   1250         .map(String::as_str)
   1251         .unwrap_or_default();
   1252     if !uniffi.contains("[crates.harvestcircle_ffi.bindings.kotlin]")
   1253         || !uniffi.contains(&format!("package_name = \"{ffi_package}\""))
   1254         || !uniffi.contains(&format!("cdylib_name = \"{cdylib}\""))
   1255     {
   1256         findings.push(
   1257             "core/crates/harvestcircle_ffi/uniffi.toml: final FFI identity changed".to_owned(),
   1258         );
   1259     }
   1260     let baseline = read_text(root, "core/compatibility/harvestcircle-ffi-v4.properties");
   1261     if !baseline.contains("contract.id=harvestcircle-desktop-ffi-v4")
   1262         || !baseline.contains("contract.major=4")
   1263     {
   1264         findings.push(
   1265             "core/compatibility/harvestcircle-ffi-v4.properties: FFI v4 identity changed"
   1266                 .to_owned(),
   1267         );
   1268     }
   1269     let shared_build = read_text(root, "app/shared/build.gradle.kts");
   1270     if !shared_build.contains("id(\"org.harvestcircle.build.kmp-shared\")")
   1271         || ["androidTarget", "iosArm", "iosX", "js(", "wasm"]
   1272             .iter()
   1273             .any(|marker| shared_build.contains(marker))
   1274     {
   1275         findings.push("app/shared/build.gradle.kts: shared KMP target boundary changed".to_owned());
   1276     }
   1277     const STORAGE_API_BASELINE: &str = "core/compatibility/harvestcircle-storage-api-v3.txt";
   1278     let storage_api = read_text(root, STORAGE_API_BASELINE);
   1279     for required in [
   1280         "pub struct harvestcircle_storage::HarvestCircleStorageContract",
   1281         "pub const harvestcircle_storage::HARVESTCIRCLE_APPLICATION_ID: u32",
   1282         "pub fn harvestcircle_storage::harvestcircle_schema_catalog()",
   1283         "pub struct harvestcircle_storage::Database",
   1284         "pub async fn harvestcircle_storage::Database::open",
   1285         "pub async fn harvestcircle_storage::Database::close",
   1286         "pub async fn harvestcircle_storage::Database::retain_availability_version",
   1287         "pub async fn harvestcircle_storage::Database::load_availability_version",
   1288         "pub async fn harvestcircle_storage::Database::capture_online_backup",
   1289         "pub async fn harvestcircle_storage::Database::restore_verified_backup",
   1290         "pub struct harvestcircle_storage::VerifiedHarvestCircleBackup",
   1291         "pub fn harvestcircle_storage::verify_harvestcircle_backup",
   1292         "impl harvestcircle_application::ports::DurableOperationRepository for harvestcircle_storage::Database",
   1293         "harvestcircle_application::ports::BoxFuture",
   1294         "pub fn harvestcircle_storage::OsKeyringSecretStore::verify<'a>(&'a self, &'a harvestcircle_application::ports::DurableRequestId, harvestcircle_domain::key::PublicKey, harvestcircle_domain::key::SecretKeyInput) -> harvestcircle_application::ports::BoxFuture<'a",
   1295     ] {
   1296         if !storage_api.contains(required) {
   1297             findings.push(format!("{STORAGE_API_BASELINE}: missing {required}"));
   1298         }
   1299     }
   1300     for forbidden in [
   1301         "rusqlite::",
   1302         "refinery::",
   1303         "sqlx::",
   1304         "OperationJournal",
   1305         "harvestcircle_initial_schema_sql",
   1306         "SELECT_AVAILABILITY_VERSION_SQL",
   1307         "decode_availability_row",
   1308         "retain_availability_version_on",
   1309         "ServiceSqliteTransaction",
   1310         "VerifiedServiceBackup",
   1311         "StagedServiceRestore",
   1312         "verify_backup_bundle",
   1313         "stage_verified_restore",
   1314         "finalize_staged_restore",
   1315         "repair",
   1316         "preflight",
   1317     ] {
   1318         if storage_api.contains(forbidden) {
   1319             findings.push(format!(
   1320                 "{STORAGE_API_BASELINE}: dependency-owned API leaked: {forbidden}"
   1321             ));
   1322         }
   1323     }
   1324     for required in [
   1325         PROVENANCE_PATH,
   1326         SOURCE_LOCK_PATH,
   1327         STORAGE_API_BASELINE,
   1328         "config/verification/lanes-v3.properties",
   1329         "tools/run-linux-x86_64-development-check.sh",
   1330         "tools/verify-storage-api.sh",
   1331     ] {
   1332         if !inventory.paths.iter().any(|path| path == required) {
   1333             findings.push(format!("{required}: governed source evidence is missing"));
   1334         }
   1335     }
   1336 }
   1337 
   1338 fn design_source_audit(root: &Path, inventory: &Inventory, findings: &mut Vec<String>) {
   1339     const PATH: &str = "config/design/harvestcircle-v1.toml";
   1340     if !inventory.paths.iter().any(|path| path == PATH) {
   1341         findings.push(format!("{PATH}: design contract is missing"));
   1342         return;
   1343     }
   1344     let source = read_text(root, PATH);
   1345     let required_scalars = [
   1346         "schema = \"harvestcircle.design.v1\"",
   1347         "repository = \"https://github.com/radrootslabs/harvestcircle\"",
   1348         "baseline_revision = \"c08d18ea569351dddeef70d4c1410708daf067b6\"",
   1349         "license = \"GPL-3.0-only\"",
   1350         "golden_host = \"macos-aarch64\"",
   1351         "golden_status = \"verified\"",
   1352         "design_system_root = \"app/design_system\"",
   1353         "design_catalog_root = \"tools/design_catalog\"",
   1354         "application_shell_root = \"app/shared/src/commonMain/kotlin/org/harvestcircle/ui/shell\"",
   1355         "golden_test_path = \"app/shared/src/desktopTest/kotlin/org/harvestcircle/ui/shell/HarvestCircleMacGoldenTest.kt\"",
   1356     ];
   1357     for scalar in required_scalars {
   1358         if source.lines().filter(|line| line.trim() == scalar).count() != 1 {
   1359             findings.push(format!("{PATH}: missing or duplicate authority: {scalar}"));
   1360         }
   1361     }
   1362     for (path, key, sha256) in [
   1363         (
   1364             "app/shared/src/desktopTest/resources/goldens/macos-aarch64/design-surface-light.png",
   1365             "golden_light_sha256",
   1366             "96e1ef5dd8b5cb14e47471a737a1e57ab0543b7f3aa79b865051e4740a2ee57a",
   1367         ),
   1368         (
   1369             "app/shared/src/desktopTest/resources/goldens/macos-aarch64/design-surface-dark.png",
   1370             "golden_dark_sha256",
   1371             "6a85cd890109b11de6f647dca91cb616651e362aa0802c40aa6aee7f678451c9",
   1372         ),
   1373     ] {
   1374         let authority = format!("{key} = \"{sha256}\"");
   1375         if source
   1376             .lines()
   1377             .filter(|line| line.trim() == authority)
   1378             .count()
   1379             != 1
   1380         {
   1381             findings.push(format!("{PATH}: {key} must match the governed golden"));
   1382         }
   1383         if !inventory.paths.iter().any(|candidate| candidate == path)
   1384             || sha256_file(&root.join(path)).as_deref() != Some(sha256)
   1385         {
   1386             findings.push(format!("{path}: macOS golden is missing or changed"));
   1387         }
   1388     }
   1389     let golden_test = read_text(
   1390         root,
   1391         "app/shared/src/desktopTest/kotlin/org/harvestcircle/ui/shell/HarvestCircleMacGoldenTest.kt",
   1392     );
   1393     if !golden_test.contains("HarvestCircleShell(")
   1394         || !golden_test.contains("liveTodayState(")
   1395         || golden_test.contains("captureReferenceSurface(")
   1396     {
   1397         findings.push(
   1398             "HarvestCircleMacGoldenTest.kt: golden must render a live application shell state"
   1399                 .to_owned(),
   1400         );
   1401     }
   1402     let catalog = read_text(root, "gradle/libs.versions.toml");
   1403     for required in [
   1404         "compose-animation = { module = \"org.jetbrains.compose.animation:animation\", version.ref = \"compose\" }",
   1405         "compose-components-resources = { module = \"org.jetbrains.compose.components:components-resources\", version.ref = \"compose\" }",
   1406     ] {
   1407         if !catalog.contains(required) {
   1408             findings.push(format!(
   1409                 "gradle/libs.versions.toml: missing approved design dependency: {required}"
   1410             ));
   1411         }
   1412     }
   1413     for forbidden in ["compose-material3", "platformtools", "js(", "wasm"] {
   1414         if catalog.to_ascii_lowercase().contains(forbidden) {
   1415             findings.push(format!(
   1416                 "gradle/libs.versions.toml: forbidden design dependency or target: {forbidden}"
   1417             ));
   1418         }
   1419     }
   1420     for path in &inventory.paths {
   1421         if !path.starts_with("app/design_system/") && !path.starts_with("tools/design_catalog/") {
   1422             continue;
   1423         }
   1424         let lowercase = read_text(root, path).to_ascii_lowercase();
   1425         for forbidden in [
   1426             "androidx.compose.material3".to_owned(),
   1427             "io.github.kdroidfilter.platformtools".to_owned(),
   1428             "com.radroots.".to_owned() + &["stu", "dio"].concat(),
   1429         ] {
   1430             if lowercase.contains(&forbidden) {
   1431                 findings.push(format!(
   1432                     "{path}: forbidden dependency or legacy namespace: {forbidden}"
   1433                 ));
   1434             }
   1435         }
   1436     }
   1437     for (path, sha256) in [
   1438         (
   1439             "app/design_system/src/commonMain/composeResources/font/inter_bold.ttf",
   1440             "288316099b1e0a47a4716d159098005eef7c0066921f34e3200393dbdb01947f",
   1441         ),
   1442         (
   1443             "app/design_system/src/commonMain/composeResources/font/inter_medium.ttf",
   1444             "97ad806f526e41546d46365bb3a393145f75b7b1568913db74549ad8b8dba872",
   1445         ),
   1446         (
   1447             "app/design_system/src/commonMain/composeResources/font/inter_regular.ttf",
   1448             "40d692fce188e4471e2b3cba937be967878f631ad3ebbbdcd587687c7ebe0c82",
   1449         ),
   1450         (
   1451             "app/design_system/src/commonMain/composeResources/font/inter_semibold.ttf",
   1452             "78a843fade9d4612a5567302fb595b56976eb5fcebf4fea5a5912d638bafcde3",
   1453         ),
   1454     ] {
   1455         if sha256_file(&root.join(path)).as_deref() != Some(sha256) {
   1456             findings.push(format!(
   1457                 "{path}: Inter font digest differs from the baseline"
   1458             ));
   1459         }
   1460     }
   1461     let font_license = read_text(root, "LICENSES/OFL-1.1.txt");
   1462     let packaged_font_license = read_text(
   1463         root,
   1464         "app/design_system/src/commonMain/composeResources/files/licenses/inter-OFL-1.1.txt",
   1465     );
   1466     if font_license.is_empty() || packaged_font_license != font_license {
   1467         findings.push("Inter font licence is missing or differs in packaged resources".to_owned());
   1468     }
   1469 }
   1470 
   1471 fn git_source_policy(root: &Path, findings: &mut Vec<String>) {
   1472     let deny = read_text(root, "core/deny.toml");
   1473     if !deny
   1474         .lines()
   1475         .any(|line| line.trim() == "required-git-spec = \"rev\"")
   1476     {
   1477         findings
   1478             .push("core/deny.toml: cargo-deny must require revision-pinned Git sources".to_owned());
   1479     }
   1480     let allowed_git = quoted_values(section_value(&deny, "allow-git"));
   1481     if allowed_git.is_empty() {
   1482         findings.push("core/deny.toml: cargo-deny Git allowlist is empty".to_owned());
   1483     }
   1484     let mut inspected = false;
   1485     for manifest in cargo_manifests(root.join("core")) {
   1486         let source = fs::read_to_string(&manifest).unwrap_or_default();
   1487         for (index, line) in source
   1488             .lines()
   1489             .enumerate()
   1490             .filter(|(_, line)| line.contains("git"))
   1491         {
   1492             let Some(git) = attribute(line, "git") else {
   1493                 continue;
   1494             };
   1495             inspected = true;
   1496             let relative_path =
   1497                 relative(root, &manifest).unwrap_or_else(|_| manifest.display().to_string());
   1498             if !allowed_git.contains(&git) {
   1499                 findings.push(format!(
   1500                     "{relative_path}:{}: Git dependency source is not allowlisted",
   1501                     index + 1
   1502                 ));
   1503             }
   1504             if line.contains("branch =") || line.contains("tag =") {
   1505                 findings.push(format!(
   1506                     "{relative_path}:{}: Git dependency uses a branch or tag",
   1507                     index + 1
   1508                 ));
   1509             }
   1510             let revision = attribute(line, "rev").unwrap_or_default();
   1511             if !is_lower_hex(&revision, 40) {
   1512                 findings.push(format!(
   1513                     "{relative_path}:{}: Git dependency must use one full revision pin",
   1514                     index + 1
   1515                 ));
   1516             }
   1517             if git == "https://github.com/rust-nostr/nostr.git"
   1518                 && revision != "5bba5163eb77107f82c4a8262cf29d7f33a73219"
   1519             {
   1520                 findings.push("core/Cargo.toml: direct rust-nostr revision changed".to_owned());
   1521             }
   1522         }
   1523     }
   1524     if !inspected {
   1525         findings.push("core: no revision-pinned Git dependencies were inspected".to_owned());
   1526     }
   1527     for line in read_text(root, "core/Cargo.lock")
   1528         .lines()
   1529         .filter(|line| line.starts_with("source = \"git+"))
   1530     {
   1531         let immutable = line.rsplit_once("?rev=").is_some_and(|(_, suffix)| {
   1532             suffix.len() == 82
   1533                 && suffix.as_bytes().get(40) == Some(&b'#')
   1534                 && suffix.ends_with('"')
   1535                 && is_lower_hex(&suffix[..40], 40)
   1536                 && is_lower_hex(&suffix[41..81], 40)
   1537         });
   1538         if !immutable {
   1539             findings.push(format!(
   1540                 "core/Cargo.lock: Git source is not immutable: {line}"
   1541             ));
   1542         }
   1543     }
   1544 }
   1545 
   1546 fn cargo_manifests(core: PathBuf) -> Vec<PathBuf> {
   1547     let mut manifests = vec![core.join("Cargo.toml")];
   1548     if let Ok(entries) = fs::read_dir(core.join("crates")) {
   1549         for entry in entries.flatten() {
   1550             let manifest = entry.path().join("Cargo.toml");
   1551             if manifest.is_file() {
   1552                 manifests.push(manifest);
   1553             }
   1554         }
   1555     }
   1556     manifests.sort();
   1557     manifests
   1558 }
   1559 
   1560 fn properties(source: &str) -> std::collections::BTreeMap<String, String> {
   1561     source
   1562         .lines()
   1563         .filter_map(|line| {
   1564             let line = line.trim();
   1565             if line.is_empty() || line.starts_with('#') {
   1566                 None
   1567             } else {
   1568                 line.split_once('=')
   1569                     .map(|(key, value)| (key.trim().to_owned(), value.trim().to_owned()))
   1570             }
   1571         })
   1572         .collect()
   1573 }
   1574 
   1575 fn section_value<'a>(source: &'a str, key: &str) -> &'a str {
   1576     source
   1577         .split_once(key)
   1578         .map(|(_, tail)| tail.split_once(']').map_or(tail, |(value, _)| value))
   1579         .unwrap_or_default()
   1580 }
   1581 
   1582 fn quoted_values(source: &str) -> BTreeSet<String> {
   1583     source
   1584         .split('"')
   1585         .enumerate()
   1586         .filter(|(index, _)| index % 2 == 1)
   1587         .map(|(_, value)| value.to_owned())
   1588         .collect()
   1589 }
   1590 
   1591 fn attribute(source: &str, key: &str) -> Option<String> {
   1592     let tail = source.split_once(&format!("{key} = \""))?.1;
   1593     Some(tail.split_once('"')?.0.to_owned())
   1594 }
   1595 
   1596 fn is_lower_hex(value: &str, length: usize) -> bool {
   1597     value.len() == length
   1598         && value
   1599             .bytes()
   1600             .all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte))
   1601 }
   1602 
   1603 fn is_text(relative: &str) -> bool {
   1604     let name = Path::new(relative)
   1605         .file_name()
   1606         .and_then(|value| value.to_str())
   1607         .unwrap_or_default();
   1608     let extension = Path::new(relative)
   1609         .extension()
   1610         .and_then(|value| value.to_str())
   1611         .unwrap_or_default();
   1612     [
   1613         "gradle",
   1614         "json",
   1615         "kt",
   1616         "kts",
   1617         "lock",
   1618         "md",
   1619         "properties",
   1620         "rs",
   1621         "sql",
   1622         "toml",
   1623         "txt",
   1624         "xml",
   1625         "yaml",
   1626         "yml",
   1627     ]
   1628     .contains(&extension)
   1629         || [
   1630             ".gitattributes",
   1631             ".gitignore",
   1632             "AGENTS.md",
   1633             "LICENSE",
   1634             "Makefile",
   1635             "NOTICE",
   1636             "gradlew",
   1637             "gradlew.bat",
   1638         ]
   1639         .contains(&name)
   1640 }
   1641 
   1642 fn read_text(root: &Path, relative: &str) -> String {
   1643     fs::read_to_string(root.join(relative)).unwrap_or_default()
   1644 }
   1645 
   1646 fn sha256_file(path: &Path) -> Option<String> {
   1647     let bytes = fs::read(path).ok()?;
   1648     Some(format!("{:x}", Sha256::digest(bytes)))
   1649 }
   1650 
   1651 fn exact_string_assignment(source: &str, key: &str) -> Option<String> {
   1652     let prefix = format!("{key} = \"");
   1653     let values = source
   1654         .lines()
   1655         .filter_map(|line| {
   1656             let value = line.strip_prefix(&prefix)?.strip_suffix('"')?;
   1657             (!value.is_empty()).then(|| value.to_owned())
   1658         })
   1659         .collect::<Vec<_>>();
   1660     (values.len() == 1).then(|| values[0].clone())
   1661 }
   1662 
   1663 fn bounded_no_follow_bytes(root: &Path, relative: &Path, maximum: u64) -> Result<Vec<u8>, String> {
   1664     if relative.is_absolute()
   1665         || relative.components().next().is_none()
   1666         || relative
   1667             .components()
   1668             .any(|component| !matches!(component, Component::Normal(_)))
   1669     {
   1670         return Err("path must be normalized and relative".to_owned());
   1671     }
   1672     let components = relative.components().collect::<Vec<_>>();
   1673     let mut path = root.to_path_buf();
   1674     let mut admitted = None;
   1675     for (index, component) in components.iter().enumerate() {
   1676         path.push(component.as_os_str());
   1677         let metadata = fs::symlink_metadata(&path)
   1678             .map_err(|error| format!("unable to inspect {}: {error}", relative.display()))?;
   1679         if metadata.file_type().is_symlink() {
   1680             return Err(format!(
   1681                 "path traverses a symbolic link: {}",
   1682                 relative.display()
   1683             ));
   1684         }
   1685         if index + 1 == components.len() {
   1686             if !metadata.is_file() {
   1687                 return Err(format!(
   1688                     "path is not a regular file: {}",
   1689                     relative.display()
   1690                 ));
   1691             }
   1692             if metadata.len() > maximum {
   1693                 return Err(format!("file exceeds byte limit: {}", relative.display()));
   1694             }
   1695             admitted = Some(metadata);
   1696         } else if !metadata.is_dir() {
   1697             return Err(format!(
   1698                 "path parent is not a directory: {}",
   1699                 relative.display()
   1700             ));
   1701         }
   1702     }
   1703 
   1704     let mut options = OpenOptions::new();
   1705     options.read(true);
   1706     #[cfg(unix)]
   1707     options.custom_flags(libc::O_NOFOLLOW | libc::O_CLOEXEC);
   1708     let mut file = options.open(&path).map_err(|error| {
   1709         format!(
   1710             "unable to open {} without following links: {error}",
   1711             relative.display()
   1712         )
   1713     })?;
   1714     let opened = file
   1715         .metadata()
   1716         .map_err(|error| format!("unable to inspect opened {}: {error}", relative.display()))?;
   1717     if !opened.is_file() || opened.len() > maximum {
   1718         return Err(format!(
   1719             "opened path is not a bounded regular file: {}",
   1720             relative.display()
   1721         ));
   1722     }
   1723     #[cfg(unix)]
   1724     if admitted
   1725         .as_ref()
   1726         .is_some_and(|metadata| metadata.dev() != opened.dev() || metadata.ino() != opened.ino())
   1727     {
   1728         return Err(format!(
   1729             "path identity changed before open: {}",
   1730             relative.display()
   1731         ));
   1732     }
   1733 
   1734     let mut bytes = Vec::with_capacity(opened.len() as usize);
   1735     file.by_ref()
   1736         .take(maximum + 1)
   1737         .read_to_end(&mut bytes)
   1738         .map_err(|error| format!("unable to read {}: {error}", relative.display()))?;
   1739     if bytes.len() as u64 > maximum {
   1740         return Err(format!("file exceeds byte limit: {}", relative.display()));
   1741     }
   1742     let completed = file
   1743         .metadata()
   1744         .map_err(|error| format!("unable to revalidate {}: {error}", relative.display()))?;
   1745     if completed.len() != bytes.len() as u64 {
   1746         return Err(format!(
   1747             "file changed while it was read: {}",
   1748             relative.display()
   1749         ));
   1750     }
   1751     #[cfg(unix)]
   1752     if opened.dev() != completed.dev() || opened.ino() != completed.ino() {
   1753         return Err(format!(
   1754             "file identity changed while it was read: {}",
   1755             relative.display()
   1756         ));
   1757     }
   1758     Ok(bytes)
   1759 }
   1760 
   1761 fn relative(root: &Path, path: &Path) -> Result<String, String> {
   1762     path.strip_prefix(root)
   1763         .map(|relative| relative.to_string_lossy().replace('\\', "/"))
   1764         .map_err(|error| format!("{} is outside {}: {error}", path.display(), root.display()))
   1765 }
   1766 
   1767 #[cfg(test)]
   1768 mod tests {
   1769     use super::*;
   1770     use std::time::{SystemTime, UNIX_EPOCH};
   1771 
   1772     #[test]
   1773     fn commands_are_exact_and_unknown_values_fail_closed() {
   1774         assert_eq!(
   1775             "design-source-audit".parse(),
   1776             Ok(Command::DesignSourceAudit)
   1777         );
   1778         assert_eq!("repo-audit".parse(), Ok(Command::RepoAudit));
   1779         assert_eq!("namespace-audit".parse(), Ok(Command::NamespaceAudit));
   1780         assert_eq!("provenance-check".parse(), Ok(Command::ProvenanceCheck));
   1781         assert_eq!(
   1782             "qualification-report".parse(),
   1783             Ok(Command::QualificationReport)
   1784         );
   1785         assert!("all".parse::<Command>().is_err());
   1786     }
   1787 
   1788     #[test]
   1789     fn current_design_contract_is_exact() {
   1790         let root = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
   1791             .parent()
   1792             .and_then(Path::parent)
   1793             .expect("repository root")
   1794             .to_path_buf();
   1795         let inventory = Inventory::load(&root).expect("source inventory");
   1796         let mut findings = Vec::new();
   1797         design_source_audit(&root, &inventory, &mut findings);
   1798         assert!(findings.is_empty(), "{findings:#?}");
   1799     }
   1800 
   1801     #[test]
   1802     fn current_source_provenance_and_lib_lock_are_exact() {
   1803         let root = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
   1804             .parent()
   1805             .and_then(Path::parent)
   1806             .expect("repository root")
   1807             .to_path_buf();
   1808         let inventory = Inventory::load(&root).expect("source inventory");
   1809         let mut findings = Vec::new();
   1810         provenance_check(&root, &inventory, &mut findings);
   1811         assert!(findings.is_empty(), "{findings:#?}");
   1812     }
   1813 
   1814     #[test]
   1815     fn sqlite_topology_rejects_alternate_duplicate_and_direct_authority() {
   1816         let root = fixture("sqlite-topology");
   1817         write(
   1818             &root,
   1819             "core/Cargo.lock",
   1820             "name = \"libsqlite3-sys\"\nname = \"libsqlite3-sys\"\nname = \"rusqlite\"\n",
   1821         );
   1822         write(
   1823             &root,
   1824             "core/crates/unsafe_storage/Cargo.toml",
   1825             "[target.'cfg(unix)'.dependencies.refinery]\nversion = \"0.9\"\n",
   1826         );
   1827         let inventory = Inventory::load(&root).expect("archive inventory");
   1828         let mut findings = Vec::new();
   1829         sqlite_dependency_topology(&root, &inventory, &mut findings);
   1830         assert!(
   1831             findings.iter().any(|finding| finding
   1832                 .contains("exact single SQLx-selected native SQLite topology changed")),
   1833             "{findings:#?}"
   1834         );
   1835         assert!(
   1836             findings.iter().any(|finding| finding
   1837                 .contains("direct alternate or native SQLite dependency is forbidden")),
   1838             "{findings:#?}"
   1839         );
   1840         fs::remove_dir_all(root).expect("remove fixture");
   1841     }
   1842 
   1843     #[test]
   1844     fn development_runner_rejects_release_integration_activation() {
   1845         let root = fixture("development-runner");
   1846         write(
   1847             &root,
   1848             "Makefile",
   1849             "override CARGO := cargo +1.97.1\napi-check: doctor\ndevelopment-check: development-provenance-check source-check integration-check\ngoverned-development-check:\ngoverned-linux-x86_64-development-check: governed-doctor\n",
   1850         );
   1851         write(
   1852             &root,
   1853             "tools/run-linux-x86_64-development-check.sh",
   1854             "dependencyCheckAnalyze\n",
   1855         );
   1856         let mut findings = Vec::new();
   1857         development_integration_policy(&root, &mut findings);
   1858         assert!(
   1859             findings
   1860                 .iter()
   1861                 .any(|finding| { finding.contains("deferred release integration is active") }),
   1862             "{findings:#?}"
   1863         );
   1864         fs::remove_dir_all(root).expect("remove fixture");
   1865     }
   1866 
   1867     #[test]
   1868     fn design_contract_rejects_identity_and_root_drift() {
   1869         let root = fixture("design-contract");
   1870         write(
   1871             &root,
   1872             "config/design/harvestcircle-v1.toml",
   1873             "schema = \"harvestcircle.design.v1\"\nrepository = \"https://example.invalid/other\"\ndesign_system_root = \"../escape\"\n",
   1874         );
   1875         let inventory = Inventory::load(&root).expect("archive inventory");
   1876         let mut findings = Vec::new();
   1877         design_source_audit(&root, &inventory, &mut findings);
   1878         assert!(
   1879             findings
   1880                 .iter()
   1881                 .any(|finding| finding.contains("missing or duplicate authority"))
   1882         );
   1883         fs::remove_dir_all(root).expect("remove fixture");
   1884     }
   1885 
   1886     #[test]
   1887     fn archive_inventory_excludes_outputs_and_includes_source() {
   1888         let root = fixture("archive");
   1889         write(&root, "src/main.rs", "fn main() {}\n");
   1890         write(&root, "target/debug/generated.bin", "output");
   1891         write(&root, "nested/build/generated.txt", "output");
   1892         let inventory = Inventory::load(&root).expect("archive inventory");
   1893         assert!(!inventory.git_aware);
   1894         assert!(inventory.paths.contains(&"src/main.rs".to_owned()));
   1895         assert!(
   1896             !inventory
   1897                 .paths
   1898                 .iter()
   1899                 .any(|path| path.contains("target/") || path.contains("/build/"))
   1900         );
   1901         fs::remove_dir_all(root).expect("remove fixture");
   1902     }
   1903 
   1904     #[test]
   1905     fn repository_policy_rejects_secret_and_generated_shapes() {
   1906         let root = fixture("policy");
   1907         write(&root, "README.md", "safe\n");
   1908         write(&root, "config/credentials/release.key", "fixture\n");
   1909         write(&root, "core/target/generated/native.bin", "fixture\n");
   1910         write(
   1911             &root,
   1912             "safe.txt",
   1913             &["-----BEGIN ", "PRIVATE KEY-----"].concat(),
   1914         );
   1915         write(&root, ".github/workflows/remote.yml", "fixture\n");
   1916         let inventory = Inventory {
   1917             paths: vec![
   1918                 "README.md".to_owned(),
   1919                 ".github/workflows/remote.yml".to_owned(),
   1920                 "config/credentials/release.key".to_owned(),
   1921                 "core/target/generated/native.bin".to_owned(),
   1922                 "safe.txt".to_owned(),
   1923             ],
   1924             git_aware: true,
   1925         };
   1926         let mut findings = Vec::new();
   1927         repo_audit(&root, &inventory, &mut findings);
   1928         assert!(
   1929             findings
   1930                 .iter()
   1931                 .any(|finding| finding.contains("secret-shaped"))
   1932         );
   1933         assert!(
   1934             findings
   1935                 .iter()
   1936                 .any(|finding| finding.contains("generated build output"))
   1937         );
   1938         assert!(
   1939             findings
   1940                 .iter()
   1941                 .any(|finding| finding.contains("forbidden repository root"))
   1942         );
   1943         assert!(
   1944             findings
   1945                 .iter()
   1946                 .any(|finding| finding.contains("private-key material"))
   1947         );
   1948         fs::remove_dir_all(root).expect("remove fixture");
   1949     }
   1950 
   1951     #[test]
   1952     fn namespace_policy_rejects_legacy_temporary_and_platform_sources() {
   1953         let root = fixture("namespace");
   1954         let legacy = ["stu", "dio"].concat();
   1955         write(
   1956             &root,
   1957             &format!("app/{legacy}/Leak.kt"),
   1958             &format!(
   1959                 "package {}\n",
   1960                 ["org", "radroots", "harvestcircle"].join(".")
   1961             ),
   1962         );
   1963         write(
   1964             &root,
   1965             "app/shared/src/commonMain/kotlin/org/harvestcircle/Leak.kt",
   1966             "import com.sun.jna.Native\n",
   1967         );
   1968         let inventory = Inventory::load(&root).expect("archive inventory");
   1969         let mut findings = Vec::new();
   1970         namespace_audit(&root, &inventory, &mut findings);
   1971         assert!(
   1972             findings
   1973                 .iter()
   1974                 .any(|finding| finding.contains("legacy product name"))
   1975         );
   1976         assert!(
   1977             findings
   1978                 .iter()
   1979                 .any(|finding| finding.contains("temporary product namespace"))
   1980         );
   1981         assert!(
   1982             findings
   1983                 .iter()
   1984                 .any(|finding| finding.contains("platform dependency"))
   1985         );
   1986         fs::remove_dir_all(root).expect("remove fixture");
   1987     }
   1988 
   1989     #[test]
   1990     fn namespace_policy_rejects_transition_identity_without_exceptions() {
   1991         let root = fixture("namespace-no-exceptions");
   1992         let legacy = ["stu", "dio"].concat();
   1993         let repository = format!("https://github.com/radrootslabs/{legacy}_app");
   1994         let entry =
   1995             "app/shared/src/commonMain/kotlin/org/harvestcircle/ui/shell/BootstrapIdentityEntry.kt";
   1996         write(
   1997             &root,
   1998             "app/shared/src/commonMain/kotlin/org/harvestcircle/product/SurfaceRegistry.kt",
   1999             &format!("val key = \"round_{legacy}_screen\"\n"),
   2000         );
   2001         write(&root, entry, "val placeholder = \"nsec1…\"\n");
   2002         write(
   2003             &root,
   2004             "app/shared/src/commonMain/kotlin/org/harvestcircle/ui/shell/FoundationSettingsScreen.kt",
   2005             &format!(
   2006                 "val source = \"{repository}\"\nval licence = \"{repository}/blob/dev/LICENSE\"\n"
   2007             ),
   2008         );
   2009         let inventory = Inventory::load(&root).expect("allowlist inventory");
   2010         let mut findings = Vec::new();
   2011         namespace_audit(&root, &inventory, &mut findings);
   2012         assert!(
   2013             findings
   2014                 .iter()
   2015                 .filter(|finding| finding.contains("legacy product name"))
   2016                 .count()
   2017                 >= 2,
   2018             "{findings:?}"
   2019         );
   2020         assert!(
   2021             findings
   2022                 .iter()
   2023                 .all(|finding| !finding.contains("secret key literal")),
   2024             "{findings:?}"
   2025         );
   2026         fs::remove_dir_all(root).expect("remove fixture");
   2027     }
   2028 
   2029     #[test]
   2030     fn product_shell_audit_requires_the_complete_source_contract() {
   2031         let root = fixture("product-shell");
   2032         write(&root, "README.md", "safe\n");
   2033         let inventory = Inventory::load(&root).expect("product shell inventory");
   2034         let mut findings = Vec::new();
   2035         product_shell_audit(&root, &inventory, &mut findings);
   2036         assert!(
   2037             findings
   2038                 .iter()
   2039                 .any(|finding| finding.contains("required product-shell source is missing"))
   2040         );
   2041         fs::remove_dir_all(root).expect("remove fixture");
   2042     }
   2043 
   2044     #[test]
   2045     fn current_product_shell_sources_pass_the_expanded_policy() {
   2046         let root = Path::new(env!("CARGO_MANIFEST_DIR"))
   2047             .join("../..")
   2048             .canonicalize()
   2049             .expect("repository root");
   2050         let inventory = Inventory::load(&root).expect("current source inventory");
   2051         let mut findings = Vec::new();
   2052         product_shell_audit(&root, &inventory, &mut findings);
   2053         assert!(findings.is_empty(), "{findings:?}");
   2054     }
   2055 
   2056     #[test]
   2057     fn product_shell_audit_rejects_legacy_screen_paths_tags_and_colors() {
   2058         let root = fixture("legacy-shell");
   2059         let path = "app/shared/src/commonMain/kotlin/org/harvestcircle/identities/ui/HarvestCircleScreen.kt";
   2060         write(
   2061             &root,
   2062             path,
   2063             "@Composable fun Legacy() { val tag = \"home-screen\"; val color = WindowBackgroundColor }\n",
   2064         );
   2065         let inventory = Inventory::load(&root).expect("legacy inventory");
   2066         let mut findings = Vec::new();
   2067         product_shell_audit(&root, &inventory, &mut findings);
   2068         assert!(
   2069             findings
   2070                 .iter()
   2071                 .any(|finding| finding.contains("superseded product-shell screen path"))
   2072         );
   2073         assert!(
   2074             findings
   2075                 .iter()
   2076                 .any(|finding| finding.contains("superseded product-shell marker home-screen"))
   2077         );
   2078         assert!(findings.iter().any(|finding| {
   2079             finding.contains("superseded product-shell marker WindowBackgroundColor")
   2080         }));
   2081         fs::remove_dir_all(root).expect("remove fixture");
   2082     }
   2083 
   2084     #[test]
   2085     fn product_shell_audit_rejects_color_and_primitive_bypasses_after_a_move() {
   2086         let root = fixture("moved-compose");
   2087         let path = "app/desktop/src/main/kotlin/org/harvestcircle/desktop/MovedScreen.kt";
   2088         write(
   2089             &root,
   2090             path,
   2091             "import androidx.compose.runtime.Composable\nimport androidx.compose.material.Button\n@Composable fun ShellButton() { Color(0xFF000000); BasicText(\"bypass\"); BasicTextField(\"\", {}) }\n",
   2092         );
   2093         let inventory = Inventory::load(&root).expect("moved UI inventory");
   2094         let mut findings = Vec::new();
   2095         product_shell_audit(&root, &inventory, &mut findings);
   2096         assert!(
   2097             findings
   2098                 .iter()
   2099                 .any(|finding| finding
   2100                     .contains("hard-coded Compose color outside the theme adapter"))
   2101         );
   2102         assert!(
   2103             findings
   2104                 .iter()
   2105                 .any(|finding| finding.contains("BasicText bypasses the shell primitive adapter"))
   2106         );
   2107         assert!(findings.iter().any(|finding| {
   2108             finding.contains("BasicTextField bypasses the shell primitive adapter")
   2109         }));
   2110         assert!(
   2111             findings
   2112                 .iter()
   2113                 .any(|finding| finding.contains("Material component dependency is forbidden"))
   2114         );
   2115         assert!(
   2116             findings
   2117                 .iter()
   2118                 .any(|finding| finding.contains("superseded shell button adapter"))
   2119         );
   2120         fs::remove_dir_all(root).expect("remove fixture");
   2121     }
   2122 
   2123     #[test]
   2124     fn product_shell_audit_rejects_fake_commercial_data_outside_the_shell_package() {
   2125         let root = fixture("commercial-data");
   2126         write(
   2127             &root,
   2128             "app/shared/src/commonMain/kotlin/org/harvestcircle/product/FakeData.kt",
   2129             "data class FakeData(val commitmentId: String, val priceCents: Long)\n",
   2130         );
   2131         let inventory = Inventory::load(&root).expect("commercial inventory");
   2132         let mut findings = Vec::new();
   2133         product_shell_audit(&root, &inventory, &mut findings);
   2134         assert!(findings.iter().any(|finding| finding
   2135             .contains("fake commercial product data marker commitmentid")));
   2136         assert!(
   2137             findings
   2138                 .iter()
   2139                 .any(|finding| finding.contains("fake commercial product data marker pricecents"))
   2140         );
   2141         fs::remove_dir_all(root).expect("remove fixture");
   2142     }
   2143 
   2144     #[test]
   2145     fn product_shell_audit_rejects_retired_security_and_selection_shapes() {
   2146         let root = fixture("shell-security-shapes");
   2147         write(
   2148             &root,
   2149             "app/shared/src/commonMain/kotlin/org/harvestcircle/application/LegacyConfirmation.kt",
   2150             "data object ConfirmIdentityRemoval\ndata object CancelIdentityRemoval\n",
   2151         );
   2152         write(
   2153             &root,
   2154             "app/shared/src/commonMain/kotlin/org/harvestcircle/application/UnsafeReference.kt",
   2155             "fun reduce(intent: OverlayIntent) = when (intent) { is OverlayIntent.EditReference -> classifyNostrReference(intent.value) }\n",
   2156         );
   2157         write(
   2158             &root,
   2159             "app/shared/src/commonMain/kotlin/org/harvestcircle/ui/shell/UnsafeIngress.kt",
   2160             "val overlay = OverlayIntent.Open(FoundationOverlay.OpenNostrReference(\"prefilled\"))\n",
   2161         );
   2162         write(
   2163             &root,
   2164             "app/shared/src/commonMain/kotlin/org/harvestcircle/ui/shell/UnsafeSelection.kt",
   2165             "ShellTab(selected = true, enabled = false)\n",
   2166         );
   2167         let inventory = Inventory::load(&root).expect("security shape inventory");
   2168         let mut findings = Vec::new();
   2169         product_shell_audit(&root, &inventory, &mut findings);
   2170         for expected in [
   2171             "retired parameterless confirmation source shape",
   2172             "parser-on-edit source shape",
   2173             "prefilled reference ingress source shape",
   2174             "selected-as-disabled source shape",
   2175         ] {
   2176             assert!(
   2177                 findings.iter().any(|finding| finding.contains(expected)),
   2178                 "missing {expected}: {findings:?}"
   2179             );
   2180         }
   2181         fs::remove_dir_all(root).expect("remove fixture");
   2182     }
   2183 
   2184     #[test]
   2185     fn product_shell_audit_rejects_secret_custody_and_global_overlay_busy_shapes() {
   2186         let root = fixture("secret-lifecycle-shapes");
   2187         write(
   2188             &root,
   2189             "app/shared/src/commonMain/kotlin/org/harvestcircle/application/UnsafeDraft.kt",
   2190             "data class EditImportDraft(val value: String)\ndata class State(val importDraft: String)\n",
   2191         );
   2192         write(
   2193             &root,
   2194             "app/shared/src/commonMain/kotlin/org/harvestcircle/application/ShellOverlays.kt",
   2195             "fun admit(state: State) = state.identity.busy\n",
   2196         );
   2197         write(
   2198             &root,
   2199             "app/shared/src/commonMain/kotlin/org/harvestcircle/ui/shell/FoundationOverlayHost.kt",
   2200             "fun FoundationOverlayHost(state: OverlayState, status: ShellStatusModel, busy: Boolean) = Unit\n",
   2201         );
   2202         let inventory = Inventory::load(&root).expect("closure shape inventory");
   2203         let mut findings = Vec::new();
   2204         product_shell_audit(&root, &inventory, &mut findings);
   2205         for expected in [
   2206             "raw String import-draft custody source shape",
   2207             "copyable import-draft intent source shape",
   2208             "unrelated identity busy state must not gate overlay admission",
   2209             "global busy state must not enter the overlay host",
   2210         ] {
   2211             assert!(
   2212                 findings.iter().any(|finding| finding.contains(expected)),
   2213                 "missing {expected}: {findings:?}"
   2214             );
   2215         }
   2216         fs::remove_dir_all(root).expect("remove fixture");
   2217     }
   2218 
   2219     #[test]
   2220     fn repository_policy_rejects_nested_documentation_and_workflow_roots() {
   2221         let root = fixture("nested-docs");
   2222         write(&root, "app/docs/notes.md", "fixture\n");
   2223         write(&root, "app/.github/workflows/remote.yml", "fixture\n");
   2224         let inventory = Inventory::load(&root).expect("nested docs inventory");
   2225         let mut findings = Vec::new();
   2226         repo_audit(&root, &inventory, &mut findings);
   2227         assert!(
   2228             findings
   2229                 .iter()
   2230                 .any(|finding| finding.starts_with("app/docs/"))
   2231         );
   2232         assert!(
   2233             findings
   2234                 .iter()
   2235                 .any(|finding| finding.starts_with("app/.github/workflows/"))
   2236         );
   2237         fs::remove_dir_all(root).expect("remove fixture");
   2238     }
   2239 
   2240     #[cfg(unix)]
   2241     #[test]
   2242     fn repository_policy_rejects_symbolic_links() {
   2243         use std::os::unix::fs::symlink;
   2244         let root = fixture("symlink");
   2245         write(&root, "outside.txt", "outside\n");
   2246         fs::create_dir_all(root.join("app")).expect("create app");
   2247         symlink(root.join("outside.txt"), root.join("app/escape.txt")).expect("create symlink");
   2248         let inventory = Inventory::load(&root).expect("archive inventory");
   2249         let mut findings = Vec::new();
   2250         repo_audit(&root, &inventory, &mut findings);
   2251         assert!(
   2252             findings
   2253                 .iter()
   2254                 .any(|finding| finding.contains("symbolic links"))
   2255         );
   2256         fs::remove_dir_all(root).expect("remove fixture");
   2257     }
   2258 
   2259     #[cfg(unix)]
   2260     #[test]
   2261     fn git_inventory_rejects_an_intermediate_symbolic_link() {
   2262         use std::os::unix::fs::symlink;
   2263 
   2264         let root = fixture("git-inventory-intermediate-symlink");
   2265         initialize_git_fixture(&root);
   2266         write(&root, "tracked/file.txt", "tracked\n");
   2267         add_git_fixture_path(&root, Path::new("tracked/file.txt"));
   2268         fs::rename(root.join("tracked"), root.join("actual")).expect("move tracked directory");
   2269         symlink(root.join("actual"), root.join("tracked")).expect("create intermediate symlink");
   2270 
   2271         let error = Inventory::load(&root).expect_err("intermediate symlink must fail closed");
   2272         assert!(error.contains("Git inventory path traverses a symbolic link"));
   2273         fs::remove_dir_all(root).expect("remove fixture");
   2274     }
   2275 
   2276     #[cfg(unix)]
   2277     #[test]
   2278     fn git_inventory_rejects_invalid_utf8_before_filesystem_traversal() {
   2279         use std::io::Write as _;
   2280         use std::process::Stdio;
   2281 
   2282         let root = fixture("git-inventory-invalid-utf8-symlink");
   2283         initialize_git_fixture(&root);
   2284         write(&root, "blob.txt", "tracked\n");
   2285         let object = ProcessCommand::new("git")
   2286             .arg("-C")
   2287             .arg(&root)
   2288             .args(["hash-object", "-w", "blob.txt"])
   2289             .output()
   2290             .expect("write fixture blob");
   2291         assert!(object.status.success());
   2292         let object = String::from_utf8(object.stdout).expect("Git object ID is UTF-8");
   2293         let mut index_entry = format!("100644 blob {}\ttracked/", object.trim()).into_bytes();
   2294         index_entry.push(0x80);
   2295         index_entry.extend_from_slice(b"/file.txt\0");
   2296         let mut update = ProcessCommand::new("git")
   2297             .arg("-C")
   2298             .arg(&root)
   2299             .args(["update-index", "-z", "--index-info"])
   2300             .stdin(Stdio::piped())
   2301             .spawn()
   2302             .expect("start hostile index update");
   2303         update
   2304             .stdin
   2305             .take()
   2306             .expect("hostile index stdin")
   2307             .write_all(&index_entry)
   2308             .expect("write hostile index entry");
   2309         assert!(
   2310             update
   2311                 .wait()
   2312                 .expect("finish hostile index update")
   2313                 .success()
   2314         );
   2315         let error =
   2316             Inventory::load(&root).expect_err("invalid UTF-8 inventory path must fail closed");
   2317         assert_eq!(error, "Git inventory path is not valid UTF-8");
   2318         fs::remove_dir_all(root).expect("remove fixture");
   2319     }
   2320 
   2321     #[cfg(unix)]
   2322     #[test]
   2323     fn git_inventory_preserves_a_literal_backslash_without_aliasing_a_separator() {
   2324         let root = fixture("git-inventory-backslash");
   2325         initialize_git_fixture(&root);
   2326         write(&root, r"tracked\file.txt", "literal backslash\n");
   2327         write(&root, "tracked/file.txt", "path separator\n");
   2328         add_git_fixture_path(&root, Path::new(r"tracked\file.txt"));
   2329         add_git_fixture_path(&root, Path::new("tracked/file.txt"));
   2330 
   2331         let inventory = Inventory::load(&root).expect("distinct Git paths must remain distinct");
   2332         assert!(inventory.paths.contains(&r"tracked\file.txt".to_owned()));
   2333         assert!(inventory.paths.contains(&"tracked/file.txt".to_owned()));
   2334         fs::remove_dir_all(root).expect("remove fixture");
   2335     }
   2336 
   2337     #[test]
   2338     fn git_inventory_rejects_a_missing_leaf() {
   2339         let root = fixture("git-inventory-missing-leaf");
   2340         initialize_git_fixture(&root);
   2341         write(&root, "tracked/file.txt", "tracked\n");
   2342         add_git_fixture_path(&root, Path::new("tracked/file.txt"));
   2343         fs::remove_file(root.join("tracked/file.txt")).expect("remove tracked file");
   2344 
   2345         let error = Inventory::load(&root).expect_err("missing inventory leaf must fail closed");
   2346         assert!(error.contains("Git inventory path is missing"));
   2347         fs::remove_dir_all(root).expect("remove fixture");
   2348     }
   2349 
   2350     #[test]
   2351     fn git_inventory_rejects_a_directory_leaf() {
   2352         let root = fixture("git-inventory-directory-leaf");
   2353         initialize_git_fixture(&root);
   2354         write(&root, "tracked/file.txt", "tracked\n");
   2355         add_git_fixture_path(&root, Path::new("tracked/file.txt"));
   2356         fs::remove_file(root.join("tracked/file.txt")).expect("remove tracked file");
   2357         fs::create_dir(root.join("tracked/file.txt")).expect("create directory leaf");
   2358 
   2359         let error = Inventory::load(&root).expect_err("directory inventory leaf must fail closed");
   2360         assert!(error.contains("Git inventory path is not a regular file"));
   2361         fs::remove_dir_all(root).expect("remove fixture");
   2362     }
   2363 
   2364     #[cfg(unix)]
   2365     #[test]
   2366     fn git_inventory_rejects_a_fifo_leaf_without_opening_it() {
   2367         let root = fixture("git-inventory-fifo-leaf");
   2368         initialize_git_fixture(&root);
   2369         write(&root, "tracked/file.txt", "tracked\n");
   2370         add_git_fixture_path(&root, Path::new("tracked/file.txt"));
   2371         fs::remove_file(root.join("tracked/file.txt")).expect("remove tracked file");
   2372         assert!(
   2373             ProcessCommand::new("mkfifo")
   2374                 .arg(root.join("tracked/file.txt"))
   2375                 .status()
   2376                 .expect("create FIFO leaf")
   2377                 .success()
   2378         );
   2379 
   2380         let error = Inventory::load(&root).expect_err("FIFO inventory leaf must fail closed");
   2381         assert!(error.contains("Git inventory path is not a regular file"));
   2382         fs::remove_dir_all(root).expect("remove fixture");
   2383     }
   2384 
   2385     #[test]
   2386     fn mutable_git_dependency_and_provenance_mutation_fail_closed() {
   2387         let root = fixture("provenance");
   2388         write(
   2389             &root,
   2390             "core/deny.toml",
   2391             "required-git-spec = \"rev\"\nallow-git = [\"https://example.invalid/lib\"]\n",
   2392         );
   2393         write(
   2394             &root,
   2395             "core/Cargo.toml",
   2396             "[dependencies]\nlib = { git = \"https://example.invalid/lib\", branch = \"main\" }\n",
   2397         );
   2398         write(&root, "core/Cargo.lock", "");
   2399         let mut findings = Vec::new();
   2400         git_source_policy(&root, &mut findings);
   2401         assert!(
   2402             findings
   2403                 .iter()
   2404                 .any(|finding| finding.contains("branch or tag"))
   2405         );
   2406         assert!(
   2407             findings
   2408                 .iter()
   2409                 .any(|finding| finding.contains("full revision pin"))
   2410         );
   2411 
   2412         findings.clear();
   2413         let inventory = Inventory::load(&root).expect("archive inventory");
   2414         provenance_check(&root, &inventory, &mut findings);
   2415         assert!(
   2416             findings
   2417                 .iter()
   2418                 .any(|finding| finding.contains("exact source provenance changed"))
   2419         );
   2420         assert!(
   2421             findings
   2422                 .iter()
   2423                 .any(|finding| finding.contains("exact Lib source lock changed"))
   2424         );
   2425         fs::remove_dir_all(root).expect("remove fixture");
   2426     }
   2427 
   2428     #[test]
   2429     fn source_lock_digest_rejects_actual_byte_mismatch() {
   2430         let root = fixture("source-lock-digest");
   2431         write(
   2432             &root,
   2433             "radroots.lib.source-lock.v1.toml",
   2434             concat!(
   2435                 "schema = \"radroots.lib.source-lock.v1\"\n",
   2436                 "repository = \"https://github.com/radrootslabs/lib\"\n",
   2437                 "revision = \"189c49b74b4bafc142b00b76b296477931139e72\"\n",
   2438                 "architecture = \"radroots.crates.release.v2\"\n",
   2439                 "workspace_catalog_sha256 = \"ee295f2352e2577a4052d980624415aec9871197d4fc9910a4c21c83a9179200\"\n",
   2440                 "version = \"0.1.0-alpha\"\n",
   2441                 "source_archive_sha256 = \"c648a3ab993d10253b9073e7e86db7b8970863bdf1d394d9fc30d66825695240\"\n",
   2442                 "lockfile = \"core/Cargo.lock\"\n",
   2443                 "lockfile_sha256 = \"6a32d1de0105771158647c65116fe797dfa7db7c42db515c63047d6601b846a3\"\n",
   2444             ),
   2445         );
   2446         write(&root, "core/Cargo.toml", "");
   2447         write(&root, "core/Cargo.lock", "version = 3\n");
   2448         let inventory = Inventory::load(&root).expect("source-lock inventory");
   2449         let mut findings = Vec::new();
   2450         provenance_check(&root, &inventory, &mut findings);
   2451         assert!(findings.iter().any(|finding| {
   2452             finding.contains("lockfile_sha256 does not match actual bounded no-follow bytes")
   2453         }));
   2454         fs::remove_dir_all(root).expect("remove fixture");
   2455     }
   2456 
   2457     #[cfg(unix)]
   2458     #[test]
   2459     fn bounded_source_lock_reads_reject_final_and_intermediate_symlinks() {
   2460         use std::os::unix::fs::symlink;
   2461 
   2462         let root = fixture("source-lock-symlinks");
   2463         write(&root, "actual/Cargo.lock", "version = 4\n");
   2464         symlink(root.join("actual/Cargo.lock"), root.join("final.lock"))
   2465             .expect("create final symlink");
   2466         assert!(
   2467             bounded_no_follow_bytes(&root, Path::new("final.lock"), 1024)
   2468                 .expect_err("final symlink must fail")
   2469                 .contains("symbolic link")
   2470         );
   2471 
   2472         symlink(root.join("actual"), root.join("core")).expect("create intermediate symlink");
   2473         assert!(
   2474             bounded_no_follow_bytes(&root, Path::new("core/Cargo.lock"), 1024)
   2475                 .expect_err("intermediate symlink must fail")
   2476                 .contains("symbolic link")
   2477         );
   2478         assert_eq!(
   2479             bounded_no_follow_bytes(&root, Path::new("actual/Cargo.lock"), 1024)
   2480                 .expect("regular bounded file"),
   2481             b"version = 4\n"
   2482         );
   2483         assert!(
   2484             bounded_no_follow_bytes(&root, Path::new("actual/Cargo.lock"), 1)
   2485                 .expect_err("oversize file must fail")
   2486                 .contains("byte limit")
   2487         );
   2488         fs::remove_dir_all(root).expect("remove fixture");
   2489     }
   2490 
   2491     fn fixture(name: &str) -> PathBuf {
   2492         let nonce = SystemTime::now()
   2493             .duration_since(UNIX_EPOCH)
   2494             .expect("clock")
   2495             .as_nanos();
   2496         let root = std::env::temp_dir().join(format!(
   2497             "harvestcircle-xtask-{name}-{}-{nonce}",
   2498             std::process::id()
   2499         ));
   2500         fs::create_dir_all(&root).expect("create fixture");
   2501         root
   2502     }
   2503 
   2504     fn initialize_git_fixture(root: &Path) {
   2505         assert!(
   2506             ProcessCommand::new("git")
   2507                 .arg("-C")
   2508                 .arg(root)
   2509                 .args(["init", "--quiet"])
   2510                 .status()
   2511                 .expect("initialize Git fixture")
   2512                 .success()
   2513         );
   2514     }
   2515 
   2516     fn add_git_fixture_path(root: &Path, relative: &Path) {
   2517         assert!(
   2518             ProcessCommand::new("git")
   2519                 .arg("-C")
   2520                 .arg(root)
   2521                 .args(["add", "--"])
   2522                 .arg(relative)
   2523                 .status()
   2524                 .expect("index tracked fixture")
   2525                 .success()
   2526         );
   2527     }
   2528 
   2529     fn write(root: &Path, relative: &str, source: &str) {
   2530         let path = root.join(relative);
   2531         fs::create_dir_all(path.parent().expect("fixture parent")).expect("create fixture parent");
   2532         fs::write(path, source).expect("write fixture");
   2533     }
   2534 }