diff --git a/README.md b/README.md index 61eb1d17..5b19a98c 100644 --- a/README.md +++ b/README.md @@ -42,8 +42,9 @@ This is illustrative compact block output; exact matches depend on the graph. ## Quick start -Supported platforms are macOS on Apple Silicon (ARM64) and Windows x86_64. -Linux and Intel macOS are no longer supported. +The supported platform scope is the current stable macOS release on Apple +Silicon (ARM64) and Windows x86_64. Older macOS releases are not part of the +qualification matrix. Linux and Intel macOS are no longer supported. Install from crates.io with Cargo (Rust 1.82 or newer) from the repository you want to index: @@ -54,9 +55,14 @@ codebase-graph install codebase-graph check-health --repo-root . ``` -Prefer a prebuilt binary? Download a platform archive from [GitHub -Releases](https://github.com/rabii-chaarani/codebaseGraph/releases), put -`codebase-graph` on your `PATH`, and run the same `install` and health commands. +Prefer a prebuilt archive? Download it from [GitHub +Releases](https://github.com/rabii-chaarani/codebaseGraph/releases) and extract it. +On Apple Silicon macOS, run the included `install.sh` to install both binaries +and their bundled OpenSSL libraries together; copying only `codebase-graph` +is not sufficient. The libraries resolve beside the installed binaries, so +Homebrew or a separate OpenSSL installation is not required. Then run the +same `install` and health commands. See the [release guide](docs/release.md) +for archive verification and qualification limits. For development from this checkout: ```bash diff --git a/crates/xtask/src/macos.rs b/crates/xtask/src/macos.rs new file mode 100644 index 00000000..39b700b6 --- /dev/null +++ b/crates/xtask/src/macos.rs @@ -0,0 +1,958 @@ +use flate2::read::GzDecoder; +use std::collections::BTreeSet; +use std::env; +use std::fs::{self, File}; +use std::io::Read; +use std::path::{Component, Path, PathBuf}; +use std::process::Command; + +const OPENSSL_VERSION: &str = "3.6.2"; +const OPENSSL_SHA256: &str = "aaf51a1fe064384f811daeaeb4ec4dce7340ec8bd893027eee676af31e83a04f"; +const OPENSSL_SOURCE_URL: &str = + "https://github.com/openssl/openssl/releases/download/openssl-3.6.2/openssl-3.6.2.tar.gz"; +const DEPLOYMENT_TARGET: &str = "13.3"; +const SOURCE_TOP: &str = "openssl-3.6.2"; +const SSL_SOURCE_NAME: &str = "libssl.3.dylib"; +const CRYPTO_SOURCE_NAME: &str = "libcrypto.3.dylib"; + +pub const PAYLOAD_FILES: [&str; 3] = [ + "libcodebasegraph-ssl-3.6.2.dylib", + "libcodebasegraph-crypto-3.6.2.dylib", + "openssl-3.6.2-LICENSE.txt", +]; + +/// Fetch, verify, and build the approved OpenSSL release into a fresh private root. +pub fn prepare_openssl(root: &Path) -> Result { + ensure_fresh_root(root)?; + let archive = root.join("openssl-3.6.2.tar.gz"); + let mut download = Command::new("curl"); + download.args([ + "--fail", + "--location", + "--silent", + "--show-error", + "--output", + ]); + download.arg(&archive).arg(OPENSSL_SOURCE_URL); + run(&mut download, "OpenSSL source download")?; + + let actual_sha = super::sha256_file(&archive) + .map_err(|error| format!("failed to hash downloaded OpenSSL source: {error}"))?; + if actual_sha != OPENSSL_SHA256 { + return Err(format!( + "OpenSSL {OPENSSL_VERSION} source SHA-256 mismatch: expected {OPENSSL_SHA256}, got {actual_sha}" + )); + } + + let source = root.join("source"); + extract_verified_source(&archive, root, &source)?; + let license = source.join("LICENSE.txt"); + let license_metadata = fs::symlink_metadata(&license) + .map_err(|error| format!("verified OpenSSL source has no LICENSE.txt: {error}"))?; + if !license_metadata.file_type().is_file() || license_metadata.len() == 0 { + return Err("verified OpenSSL LICENSE.txt is missing or is not a regular file".into()); + } + super::copy_file(&license, root.join("LICENSE.txt")) + .map_err(|error| format!("failed to copy verified OpenSSL license: {error}"))?; + + let library_dir = root.join("build"); + let openssl_dir = root.join("openssl-config"); + let mut configure = Command::new("perl"); + configure.current_dir(&source).arg("Configure").args([ + "darwin64-arm64-cc", + "shared", + "no-tests", + "no-apps", + "no-module", + ]); + configure + .arg(format!("--prefix={}", library_dir.display())) + .arg(format!("--libdir={}", library_dir.display())) + .arg(format!("--openssldir={}", openssl_dir.display())); + clean_openssl_build_environment(&mut configure); + configure + .env("CC", "clang") + .env("MACOSX_DEPLOYMENT_TARGET", DEPLOYMENT_TARGET); + run(&mut configure, "OpenSSL arm64 configuration")?; + + let jobs = std::thread::available_parallelism() + .map(usize::from) + .unwrap_or(1); + let mut build = Command::new("make"); + build + .current_dir(&source) + .arg(format!("-j{jobs}")) + .arg("install_sw"); + clean_openssl_build_environment(&mut build); + build.env("MACOSX_DEPLOYMENT_TARGET", DEPLOYMENT_TARGET); + run(&mut build, "OpenSSL shared-library build and install")?; + + for name in [SSL_SOURCE_NAME, CRYPTO_SOURCE_NAME] { + let path = library_dir.join(name); + let metadata = fs::symlink_metadata(&path).map_err(|error| { + format!("OpenSSL build did not produce {}: {error}", path.display()) + })?; + if !metadata.file_type().is_file() || metadata.len() == 0 { + return Err(format!( + "OpenSSL build output is not a regular library: {}", + path.display() + )); + } + } + for (link, target) in [ + ("libssl.dylib", SSL_SOURCE_NAME), + ("libcrypto.dylib", CRYPTO_SOURCE_NAME), + ] { + let path = library_dir.join(link); + let metadata = fs::symlink_metadata(&path).map_err(|error| { + format!( + "OpenSSL build did not produce linker symlink {}: {error}", + path.display() + ) + })?; + if !metadata.file_type().is_symlink() + || fs::read_link(&path).map_err(|error| error.to_string())? != Path::new(target) + { + return Err(format!( + "OpenSSL linker symlink is invalid: {}", + path.display() + )); + } + } + Ok(library_dir) +} + +/// Copy and relocate the two OpenSSL dylibs beside the already-staged binaries. +pub fn stage_runtime(library_dir: &Path, package: &Path) -> Result<(), String> { + if !package.is_dir() { + return Err(format!( + "macOS package directory is missing: {}", + package.display() + )); + } + let (graph, wiki) = executable_paths(package)?; + let source_license = library_dir + .parent() + .ok_or_else(|| "OpenSSL library directory has no parent for LICENSE.txt".to_string())? + .join("LICENSE.txt"); + copy_regular_file( + &library_dir.join(SSL_SOURCE_NAME), + &package.join(PAYLOAD_FILES[0]), + )?; + copy_regular_file( + &library_dir.join(CRYPTO_SOURCE_NAME), + &package.join(PAYLOAD_FILES[1]), + )?; + copy_regular_file(&source_license, &package.join(PAYLOAD_FILES[2]))?; + + let ssl = package.join(PAYLOAD_FILES[0]); + let crypto = package.join(PAYLOAD_FILES[1]); + let graph_dependencies = original_openssl_dependencies(&graph, true)?; + let wiki_dependencies = original_openssl_dependencies(&wiki, true)?; + let ssl_dependencies = original_openssl_dependencies(&ssl, false)?; + let crypto_dependencies = original_openssl_dependencies(&crypto, false)?; + if !crypto_dependencies.is_empty() { + return Err(format!( + "OpenSSL crypto library has unexpected non-system dependencies: {crypto_dependencies:?}" + )); + } + + for (binary, dependencies) in [(&graph, graph_dependencies), (&wiki, wiki_dependencies)] { + for (name, old_path) in dependencies { + let staged_name = if name == SSL_SOURCE_NAME { + PAYLOAD_FILES[0] + } else { + PAYLOAD_FILES[1] + }; + change_dependency( + binary, + &old_path, + &format!("@executable_path/{staged_name}"), + )?; + } + } + change_dependency( + &ssl, + &ssl_dependencies + .get(CRYPTO_SOURCE_NAME) + .ok_or_else(|| "OpenSSL ssl library does not load libcrypto.3.dylib".to_string())? + .clone(), + &format!("@loader_path/{}", PAYLOAD_FILES[1]), + )?; + set_dylib_id(&ssl, &format!("@loader_path/{}", PAYLOAD_FILES[0]))?; + set_dylib_id(&crypto, &format!("@loader_path/{}", PAYLOAD_FILES[1]))?; + + for path in [&graph, &wiki, &ssl, &crypto] { + for rpath in read_rpaths(path)? { + let mut command = Command::new("install_name_tool"); + command.args(["-delete_rpath", &rpath]).arg(path); + run( + &mut command, + &format!("remove LC_RPATH from {}", path.display()), + )?; + } + } + for path in [&crypto, &ssl, &graph, &wiki] { + let mut command = Command::new("codesign"); + command.args(["--force", "--sign", "-"]).arg(path); + run(&mut command, &format!("ad-hoc sign {}", path.display()))?; + } + validate_package(package) +} + +/// Audit a final package on the matching native macOS ARM64 build host. +pub fn validate_package(package: &Path) -> Result<(), String> { + if env::consts::OS != "macos" || env::consts::ARCH != "aarch64" { + return Err(format!( + "macOS package audit requires a native macOS/aarch64 host, current host is {}/{}", + env::consts::OS, + env::consts::ARCH + )); + } + if !package.is_dir() { + return Err(format!( + "macOS package directory is missing: {}", + package.display() + )); + } + let (graph, wiki) = executable_paths(package)?; + let ssl = package.join(PAYLOAD_FILES[0]); + let crypto = package.join(PAYLOAD_FILES[1]); + let license = package.join(PAYLOAD_FILES[2]); + for path in [&ssl, &crypto, &license] { + let metadata = fs::symlink_metadata(path).map_err(|error| { + format!("missing macOS package payload {}: {error}", path.display()) + })?; + if !metadata.file_type().is_file() || metadata.len() == 0 { + return Err(format!("invalid macOS package payload: {}", path.display())); + } + } + + for path in [&crypto, &ssl, &graph, &wiki] { + verify_signature(path)?; + } + audit_macho(&crypto, MachOKind::Crypto)?; + audit_macho(&ssl, MachOKind::Ssl)?; + audit_macho(&graph, MachOKind::Executable)?; + audit_macho(&wiki, MachOKind::Executable)?; + Ok(()) +} + +#[derive(Clone, Copy)] +enum MachOKind { + Executable, + Ssl, + Crypto, +} + +fn executable_paths(package: &Path) -> Result<(PathBuf, PathBuf), String> { + let graph = package.join("codebase-graph"); + let wiki = package.join("k-wiki"); + for path in [&graph, &wiki] { + let metadata = fs::symlink_metadata(path).map_err(|error| { + format!( + "missing staged macOS executable {}: {error}", + path.display() + ) + })?; + if !metadata.file_type().is_file() || metadata.len() == 0 { + return Err(format!( + "staged macOS executable is invalid: {}", + path.display() + )); + } + } + Ok((graph, wiki)) +} + +fn audit_macho(path: &Path, kind: MachOKind) -> Result<(), String> { + let archs = output( + Command::new("lipo").arg("-archs").arg(path), + "inspect Mach-O architecture", + )?; + let archs: Vec<&str> = archs.split_whitespace().collect(); + if archs != ["arm64"] { + return Err(format!( + "{} must contain only arm64, found {archs:?}", + path.display() + )); + } + let load_commands = output( + Command::new("otool").arg("-l").arg(path), + "inspect Mach-O load commands", + )?; + let minimum = parse_macos_minimum(&load_commands)?; + if minimum > (13, 3, 0) { + return Err(format!( + "{} has macOS minimum {}.{}.{} above 13.3", + path.display(), + minimum.0, + minimum.1, + minimum.2 + )); + } + let rpaths = parse_rpaths(&load_commands)?; + if !rpaths.is_empty() { + return Err(format!( + "{} retains LC_RPATH entries: {rpaths:?}", + path.display() + )); + } + + let dependencies = read_dependencies(path, matches!(kind, MachOKind::Ssl | MachOKind::Crypto))?; + let expected = match kind { + MachOKind::Executable => vec![ + format!("@executable_path/{}", PAYLOAD_FILES[0]), + format!("@executable_path/{}", PAYLOAD_FILES[1]), + ], + MachOKind::Ssl => vec![format!("@loader_path/{}", PAYLOAD_FILES[1])], + MachOKind::Crypto => Vec::new(), + }; + let actual: Vec = dependencies + .into_iter() + .filter(|dependency| !is_apple_system_library(dependency)) + .collect(); + let actual_set: BTreeSet<&str> = actual.iter().map(String::as_str).collect(); + let expected_set: BTreeSet<&str> = expected.iter().map(String::as_str).collect(); + if actual.len() != actual_set.len() || actual_set != expected_set { + return Err(format!( + "{} has unexpected non-system dependencies: expected {expected:?}, got {actual:?}", + path.display() + )); + } + if matches!(kind, MachOKind::Ssl | MachOKind::Crypto) { + let id = read_dylib_id(path)?; + let expected_id = if matches!(kind, MachOKind::Ssl) { + format!("@loader_path/{}", PAYLOAD_FILES[0]) + } else { + format!("@loader_path/{}", PAYLOAD_FILES[1]) + }; + if id != expected_id { + return Err(format!( + "{} has unexpected dylib ID: expected {expected_id}, got {id}", + path.display() + )); + } + } + Ok(()) +} + +fn original_openssl_dependencies( + path: &Path, + require_both: bool, +) -> Result, String> { + let mut openssl = std::collections::BTreeMap::new(); + for dependency in read_dependencies(path, !require_both)? { + if is_apple_system_library(&dependency) { + continue; + } + let basename = Path::new(&dependency) + .file_name() + .and_then(|name| name.to_str()) + .ok_or_else(|| { + format!( + "invalid non-system dependency {dependency:?} in {}", + path.display() + ) + })?; + let expected_name = match basename { + SSL_SOURCE_NAME => SSL_SOURCE_NAME, + CRYPTO_SOURCE_NAME => CRYPTO_SOURCE_NAME, + _ => { + return Err(format!( + "{} has unexpected non-system dependency {dependency:?}", + path.display() + )); + } + }; + if openssl.insert(expected_name, dependency.clone()).is_some() { + return Err(format!( + "{} loads {expected_name} more than once", + path.display() + )); + } + } + if require_both + && (openssl.len() != 2 + || !openssl.contains_key(SSL_SOURCE_NAME) + || !openssl.contains_key(CRYPTO_SOURCE_NAME)) + { + return Err(format!( + "{} must load exactly one libssl.3.dylib and libcrypto.3.dylib, found {openssl:?}", + path.display() + )); + } + if !require_both + && path + .file_name() + .is_some_and(|name| name == PAYLOAD_FILES[0]) + && (openssl.len() != 1 || !openssl.contains_key(CRYPTO_SOURCE_NAME)) + { + return Err(format!( + "{} must load exactly libcrypto.3.dylib and no other non-system dependency, found {openssl:?}", + path.display() + )); + } + if !require_both + && path + .file_name() + .is_some_and(|name| name == PAYLOAD_FILES[1]) + && !openssl.is_empty() + { + return Err(format!( + "{} has unexpected OpenSSL dependencies: {openssl:?}", + path.display() + )); + } + Ok(openssl) +} + +fn change_dependency(path: &Path, old: &str, new: &str) -> Result<(), String> { + let mut command = Command::new("install_name_tool"); + command.args(["-change", old, new]).arg(path); + run( + &mut command, + &format!("rewrite dependency in {}", path.display()), + ) +} + +fn set_dylib_id(path: &Path, id: &str) -> Result<(), String> { + let mut command = Command::new("install_name_tool"); + command.args(["-id", id]).arg(path); + run( + &mut command, + &format!("set dylib ID for {}", path.display()), + ) +} + +fn read_dependencies(path: &Path, has_dylib_id: bool) -> Result, String> { + let dylib_id = if has_dylib_id { + Some(read_dylib_id(path)?) + } else { + None + }; + let text = output( + Command::new("otool").arg("-L").arg(path), + "inspect Mach-O dependencies", + )?; + parse_dependencies(&text, dylib_id.as_deref()) +} + +fn parse_dependencies(text: &str, dylib_id: Option<&str>) -> Result, String> { + let mut lines = text.lines(); + let header = lines + .next() + .ok_or_else(|| "otool -L returned no header".to_string())?; + if header.trim().is_empty() { + return Err("otool -L returned an empty header".into()); + } + let mut dependencies = Vec::new(); + for line in lines { + let line = line.trim(); + if line.is_empty() { + continue; + } + let dependency = line + .split_once(" (compatibility version ") + .map_or(line, |(path, _)| path) + .trim(); + if dependency.is_empty() { + return Err("otool -L returned an empty dependency".into()); + } + if dylib_id.is_some_and(|id| dependency == id) { + continue; + } + dependencies.push(dependency.to_string()); + } + if let Some(dylib_id) = dylib_id { + let self_entries = text + .lines() + .skip(1) + .filter(|line| { + line.trim() + .split_once(" (compatibility version ") + .map_or(line.trim(), |(path, _)| path.trim()) + == dylib_id + }) + .count(); + if self_entries != 1 { + return Err(format!( + "otool -L must report exactly one matching LC_ID_DYLIB {dylib_id:?}, found {self_entries}" + )); + } + } + Ok(dependencies) +} + +fn read_dylib_id(path: &Path) -> Result { + let text = output( + Command::new("otool").arg("-D").arg(path), + "inspect dylib ID", + )?; + let ids: Vec<&str> = text + .lines() + .skip(1) + .map(str::trim) + .filter(|line| !line.is_empty()) + .collect(); + if ids.len() != 1 { + return Err(format!( + "{} must have exactly one dylib ID, got {ids:?}", + path.display() + )); + } + Ok(ids[0].to_string()) +} + +fn read_rpaths(path: &Path) -> Result, String> { + let text = output( + Command::new("otool").arg("-l").arg(path), + "inspect Mach-O rpaths", + )?; + parse_rpaths(&text) +} + +fn parse_rpaths(text: &str) -> Result, String> { + let lines: Vec<&str> = text.lines().collect(); + let mut rpaths = Vec::new(); + let mut index = 0; + while index < lines.len() { + if lines[index].trim() != "cmd LC_RPATH" { + index += 1; + continue; + } + index += 1; + let mut found = None; + while index < lines.len() && !lines[index].trim_start().starts_with("cmd ") { + if let Some(rest) = lines[index].trim().strip_prefix("path ") { + found = Some( + rest.split_once(" (offset ") + .map_or(rest, |(path, _)| path) + .to_string(), + ); + break; + } + index += 1; + } + let path = found.ok_or_else(|| "LC_RPATH has no path entry".to_string())?; + rpaths.push(path); + } + Ok(rpaths) +} + +fn parse_macos_minimum(text: &str) -> Result<(u32, u32, u32), String> { + let lines: Vec<&str> = text.lines().collect(); + let mut versions = Vec::new(); + let mut index = 0; + while index < lines.len() { + let command = lines[index].trim(); + if command == "cmd LC_VERSION_MIN_MACOSX" { + index += 1; + while index < lines.len() && !lines[index].trim_start().starts_with("cmd ") { + if let Some(value) = lines[index].trim().strip_prefix("version ") { + versions.push(parse_version(value)?); + break; + } + index += 1; + } + } else if command == "cmd LC_BUILD_VERSION" { + index += 1; + let mut platform = None; + let mut minimum = None; + while index < lines.len() && !lines[index].trim_start().starts_with("cmd ") { + let line = lines[index].trim(); + if let Some(value) = line.strip_prefix("platform ") { + platform = Some(value); + } else if let Some(value) = line.strip_prefix("minos ") { + minimum = Some(value); + } + index += 1; + } + if matches!(platform, Some("MACOS" | "1")) { + versions.push(parse_version(minimum.ok_or_else(|| { + "LC_BUILD_VERSION for macOS has no minos value".to_string() + })?)?); + } + continue; + } + index += 1; + } + if versions.len() != 1 { + return Err(format!( + "expected one macOS minimum-version load command, found {versions:?}" + )); + } + Ok(versions[0]) +} + +fn parse_version(value: &str) -> Result<(u32, u32, u32), String> { + let parts: Vec<&str> = value.split('.').collect(); + if !(2..=3).contains(&parts.len()) + || parts + .iter() + .any(|part| part.is_empty() || !part.bytes().all(|byte| byte.is_ascii_digit())) + { + return Err(format!("invalid macOS minimum version {value:?}")); + } + let major = parts[0] + .parse() + .map_err(|_| format!("invalid macOS minimum version {value:?}"))?; + let minor = parts[1] + .parse() + .map_err(|_| format!("invalid macOS minimum version {value:?}"))?; + let patch = if parts.len() == 3 { + parts[2] + .parse() + .map_err(|_| format!("invalid macOS minimum version {value:?}"))? + } else { + 0 + }; + Ok((major, minor, patch)) +} + +fn is_apple_system_library(path: &str) -> bool { + path.starts_with("/System/Library/") || path.starts_with("/usr/lib/") +} + +fn verify_signature(path: &Path) -> Result<(), String> { + let mut command = Command::new("codesign"); + command.args(["--verify", "--strict"]).arg(path); + run( + &mut command, + &format!("strict code-signature verification for {}", path.display()), + ) +} + +fn output(command: &mut Command, description: &str) -> Result { + let result = command + .output() + .map_err(|error| format!("failed to run {description}: {error}"))?; + if !result.status.success() { + return Err(format!( + "{description} failed with status {}: {}", + result.status, + String::from_utf8_lossy(&result.stderr).trim() + )); + } + String::from_utf8(result.stdout) + .map_err(|error| format!("{description} returned non-UTF-8 output: {error}")) +} + +fn run(command: &mut Command, description: &str) -> Result<(), String> { + let status = command + .status() + .map_err(|error| format!("failed to run {description}: {error}"))?; + if status.success() { + Ok(()) + } else { + Err(format!("{description} failed with status {status}")) + } +} + +fn ensure_fresh_root(root: &Path) -> Result<(), String> { + match fs::symlink_metadata(root) { + Ok(metadata) if metadata.file_type().is_symlink() || !metadata.is_dir() => { + return Err(format!( + "OpenSSL staging root is not a real directory: {}", + root.display() + )); + } + Ok(_) => { + if fs::read_dir(root) + .map_err(|error| format!("failed to inspect OpenSSL staging root: {error}"))? + .next() + .is_some() + { + return Err(format!( + "OpenSSL staging root is not fresh and empty: {}", + root.display() + )); + } + } + Err(error) if error.kind() == std::io::ErrorKind::NotFound => { + fs::create_dir_all(root).map_err(|error| { + format!( + "failed to create OpenSSL staging root {}: {error}", + root.display() + ) + })?; + } + Err(error) => { + return Err(format!( + "failed to inspect OpenSSL staging root {}: {error}", + root.display() + )); + } + } + Ok(()) +} + +fn extract_verified_source(archive_path: &Path, root: &Path, source: &Path) -> Result<(), String> { + let file = File::open(archive_path) + .map_err(|error| format!("failed to open verified OpenSSL archive: {error}"))?; + let mut archive = tar::Archive::new(GzDecoder::new(file)); + let mut seen = BTreeSet::new(); + let mut root_directory = false; + let mut license_file = false; + for entry in archive + .entries() + .map_err(|error| format!("failed to read OpenSSL source archive: {error}"))? + { + let mut entry = + entry.map_err(|error| format!("invalid OpenSSL source archive entry: {error}"))?; + if entry.header().entry_type().is_pax_global_extensions() { + validate_pax_global_metadata(&mut entry)?; + continue; + } + let path = entry + .path() + .map_err(|error| format!("invalid OpenSSL archive path: {error}"))? + .into_owned(); + let components: Vec<_> = path.components().collect(); + if path.is_absolute() + || components.is_empty() + || components[0] != Component::Normal(Path::new(SOURCE_TOP).as_os_str()) + || components + .iter() + .any(|component| !matches!(component, Component::Normal(_))) + { + return Err(format!( + "unsafe path in verified OpenSSL archive: {}", + path.display() + )); + } + if !seen.insert(path.clone()) { + return Err(format!( + "duplicate path in verified OpenSSL archive: {}", + path.display() + )); + } + let entry_type = entry.header().entry_type(); + if !entry_type.is_file() && !entry_type.is_dir() { + return Err(format!( + "unsupported entry type in verified OpenSSL archive: {}", + path.display() + )); + } + if path == Path::new(SOURCE_TOP) && entry_type.is_dir() { + root_directory = true; + } + if path == Path::new(SOURCE_TOP).join("LICENSE.txt") && entry_type.is_file() { + license_file = true; + } + } + if !root_directory || !license_file { + return Err( + "verified OpenSSL archive is missing its source directory or LICENSE.txt".into(), + ); + } + let file = File::open(archive_path) + .map_err(|error| format!("failed to reopen OpenSSL archive: {error}"))?; + let mut archive = tar::Archive::new(GzDecoder::new(file)); + for entry in archive + .entries() + .map_err(|error| format!("failed to read verified OpenSSL source entries: {error}"))? + { + let mut entry = + entry.map_err(|error| format!("invalid verified OpenSSL source entry: {error}"))?; + if entry.header().entry_type().is_pax_global_extensions() { + validate_pax_global_metadata(&mut entry)?; + continue; + } + if !entry + .unpack_in(root) + .map_err(|error| format!("failed to extract verified OpenSSL source entry: {error}"))? + { + return Err("verified OpenSSL source entry was rejected during extraction".into()); + } + } + fs::rename(root.join(SOURCE_TOP), source).map_err(|error| { + format!( + "failed to place OpenSSL source at {}: {error}", + source.display() + ) + }) +} + +fn validate_pax_global_metadata(entry: &mut tar::Entry) -> Result<(), String> { + let path = entry + .path() + .map_err(|error| format!("invalid PAX global header path: {error}"))?; + if path != Path::new("pax_global_header") { + return Err(format!( + "unexpected PAX global header path: {}", + path.display() + )); + } + let mut extensions = entry + .pax_extensions() + .map_err(|error| format!("invalid PAX global metadata: {error}"))? + .ok_or_else(|| "PAX global header has no metadata".to_string())?; + let mut count = 0; + for extension in &mut extensions { + let extension = + extension.map_err(|error| format!("invalid PAX global metadata: {error}"))?; + let key = extension + .key() + .map_err(|error| format!("PAX global metadata key is not UTF-8: {error}"))?; + let value = extension + .value() + .map_err(|error| format!("PAX global metadata value is not UTF-8: {error}"))?; + if key != "comment" + || value.len() != 40 + || !value.bytes().all(|byte| byte.is_ascii_hexdigit()) + { + return Err(format!( + "unexpected OpenSSL source PAX metadata: {key}={value:?}" + )); + } + count += 1; + } + if count != 1 { + return Err(format!( + "expected one OpenSSL source PAX comment, found {count}" + )); + } + Ok(()) +} + +fn copy_regular_file(source: &Path, destination: &Path) -> Result<(), String> { + let metadata = fs::symlink_metadata(source).map_err(|error| { + format!( + "required OpenSSL payload is missing at {}: {error}", + source.display() + ) + })?; + if !metadata.file_type().is_file() || metadata.len() == 0 { + return Err(format!( + "OpenSSL payload is not a nonempty regular file: {}", + source.display() + )); + } + super::copy_file(source, destination) + .map_err(|error| format!("failed to stage OpenSSL payload: {error}")) +} + +fn clean_openssl_build_environment(command: &mut Command) { + for key in [ + "OPENSSL_CONF", + "OPENSSL_CONF_INCLUDE", + "OPENSSL_MODULES", + "OPENSSL_ENGINES", + "OPENSSL_INIT_SETTINGS", + "CFLAGS", + "CPPFLAGS", + "LDFLAGS", + "CC", + "CXX", + "AR", + "RANLIB", + "CROSS_COMPILE", + "MAKEFLAGS", + "MFLAGS", + ] { + command.env_remove(key); + } +} + +#[cfg(test)] +mod tests { + use super::{parse_dependencies, parse_macos_minimum, parse_rpaths}; + + #[test] + fn source_archive_accepts_comment_metadata_without_extracting_it() { + let root = super::super::unique_temp_dir("openssl_pax_source").unwrap(); + let archive_path = root.join("source.tar.gz"); + let file = std::fs::File::create(&archive_path).unwrap(); + let encoder = flate2::write::GzEncoder::new(file, flate2::Compression::default()); + let mut archive = tar::Builder::new(encoder); + let metadata = b"52 comment=fe686e15d84334b284f883118ed92f64b409b3aa\n"; + let mut header = tar::Header::new_gnu(); + header.set_entry_type(tar::EntryType::new(b'g')); + header.set_size(metadata.len() as u64); + header.set_mode(0o644); + header.set_cksum(); + archive + .append_data(&mut header, "pax_global_header", &metadata[..]) + .unwrap(); + let mut directory = tar::Header::new_gnu(); + directory.set_entry_type(tar::EntryType::Directory); + directory.set_size(0); + directory.set_mode(0o755); + directory.set_cksum(); + archive + .append_data(&mut directory, super::SOURCE_TOP, std::io::empty()) + .unwrap(); + let mut license = tar::Header::new_gnu(); + license.set_size(7); + license.set_mode(0o644); + license.set_cksum(); + archive + .append_data( + &mut license, + format!("{}/LICENSE.txt", super::SOURCE_TOP), + &b"license"[..], + ) + .unwrap(); + archive.into_inner().unwrap().finish().unwrap(); + let destination = root.join("extracted"); + std::fs::create_dir(&destination).unwrap(); + super::extract_verified_source(&archive_path, &destination, &destination.join("source")) + .unwrap(); + assert_eq!( + std::fs::read(destination.join("source/LICENSE.txt")).unwrap(), + b"license" + ); + assert!(!destination.join("pax_global_header").exists()); + std::fs::remove_dir_all(root).unwrap(); + } + + #[test] + fn dylib_dependency_parser_filters_only_matching_install_id() { + let text = "/package/libcodebasegraph-ssl-3.6.2.dylib:\n\t/producer/libssl.3.dylib (compatibility version 3.0.0, current version 3.0.0)\n\t@loader_path/libcodebasegraph-crypto-3.6.2.dylib (compatibility version 3.0.0, current version 3.0.0)\n"; + assert_eq!( + parse_dependencies(text, Some("/producer/libssl.3.dylib")).unwrap(), + ["@loader_path/libcodebasegraph-crypto-3.6.2.dylib"] + ); + assert!(parse_dependencies(text, Some("/wrong/libssl.dylib")).is_err()); + assert_eq!( + parse_dependencies(text, None).unwrap(), + [ + "/producer/libssl.3.dylib", + "@loader_path/libcodebasegraph-crypto-3.6.2.dylib" + ] + ); + } + + #[test] + fn dependency_parser_preserves_paths_with_spaces() { + let text = "/tmp/package with spaces/lib.dylib:\n\t@loader_path/libcodebasegraph-crypto-3.6.2.dylib (compatibility version 3.0.0, current version 3.0.0)\n\t/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1356.0.0)\n"; + assert_eq!( + parse_dependencies(text, None).unwrap(), + [ + "@loader_path/libcodebasegraph-crypto-3.6.2.dylib", + "/usr/lib/libSystem.B.dylib" + ] + ); + } + + #[test] + fn minimum_version_parser_reads_macos_build_and_legacy_commands() { + let build = "Load command 1\n cmd LC_BUILD_VERSION\n cmdsize 32\n platform MACOS\n minos 13.3\n sdk 26.5\n"; + let legacy = "Load command 1\n cmd LC_VERSION_MIN_MACOSX\n cmdsize 16\n version 10.13\n sdk 14.0\n"; + assert_eq!(parse_macos_minimum(build).unwrap(), (13, 3, 0)); + assert_eq!(parse_macos_minimum(legacy).unwrap(), (10, 13, 0)); + assert!(parse_macos_minimum("cmd LC_BUILD_VERSION\n platform IOS\n minos 13.0\n").is_err()); + assert!( + parse_macos_minimum("cmd LC_BUILD_VERSION\n platform MACOS\n minos invalid\n").is_err() + ); + } + + #[test] + fn rpath_parser_reads_and_rejects_malformed_entries() { + let text = "Load command 1\n cmd LC_RPATH\n cmdsize 32\n path @loader_path/../lib (offset 12)\n"; + assert_eq!(parse_rpaths(text).unwrap(), ["@loader_path/../lib"]); + assert!(parse_rpaths("cmd LC_RPATH\n cmdsize 8\n").is_err()); + assert!(parse_rpaths("cmd LC_LOAD_DYLIB\n").unwrap().is_empty()); + } +} diff --git a/crates/xtask/src/macos_installer_tests.rs b/crates/xtask/src/macos_installer_tests.rs new file mode 100644 index 00000000..98828f79 --- /dev/null +++ b/crates/xtask/src/macos_installer_tests.rs @@ -0,0 +1,260 @@ +#![cfg(unix)] + +use super::{ + expected_package_files, set_executable, sha256_file, unique_temp_dir, write_internal_checksums, + NativeTarget, +}; +use std::collections::BTreeSet; +use std::env; +use std::fs; +use std::path::{Path, PathBuf}; +use std::process::{Command, Output}; + +fn installer_path() -> PathBuf { + Path::new(env!("CARGO_MANIFEST_DIR")).join("../../release/install/install.sh") +} + +fn fixture() -> (PathBuf, PathBuf, PathBuf) { + let root = unique_temp_dir("macos_installer") + .unwrap() + .join("fixture with spaces"); + let source = root.join("release payload"); + let destination = root.join("installed files"); + fs::create_dir_all(&source).unwrap(); + + let (graph, wiki) = NativeTarget::MacosArm64.binary_names(); + for name in [graph, wiki] { + let binary = source.join(name); + fs::write(&binary, b"#!/bin/sh\nexit 0\n").unwrap(); + set_executable(&binary).unwrap(); + } + for name in super::macos::PAYLOAD_FILES { + fs::write(source.join(name), format!("fixture for {name}\n")).unwrap(); + } + write_internal_checksums(&source, NativeTarget::MacosArm64).unwrap(); + (root, source, destination) +} + +fn test_path(root: &Path) -> std::ffi::OsString { + let bin = root.join("fake host tools"); + fs::create_dir_all(&bin).unwrap(); + let uname = bin.join("uname"); + fs::write( + &uname, + "#!/bin/sh\ncase \"$1\" in -s) echo Darwin ;; -m) echo arm64 ;; esac\n", + ) + .unwrap(); + set_executable(&uname).unwrap(); + env::join_paths(std::iter::once(bin).chain(env::split_paths(&env::var_os("PATH").unwrap()))) + .unwrap() +} + +fn invoke(root: &Path, source: &Path, destination: &Path, dry_run: bool) -> Output { + let mut command = Command::new("bash"); + command + .arg(installer_path()) + .arg("--source-dir") + .arg(source) + .arg("--bin-dir") + .arg(destination) + .env("PATH", test_path(root)); + if dry_run { + command.arg("--dry-run"); + } + command.output().unwrap() +} + +fn installed_snapshot(destination: &Path) -> BTreeSet<(String, String)> { + fs::read_dir(destination) + .unwrap() + .map(|entry| { + let path = entry.unwrap().path(); + ( + path.file_name().unwrap().to_string_lossy().into_owned(), + sha256_file(&path).unwrap(), + ) + }) + .collect() +} + +fn preserve_existing_files(destination: &Path) -> BTreeSet<(String, String)> { + fs::create_dir_all(destination).unwrap(); + let (graph, wiki) = NativeTarget::MacosArm64.binary_names(); + fs::write(destination.join(graph), b"previous graph binary").unwrap(); + fs::write(destination.join(wiki), b"previous wiki binary").unwrap(); + for name in NativeTarget::MacosArm64.payload_files() { + if name != graph && name != wiki { + fs::write(destination.join(name), format!("previous {name}")).unwrap(); + } + } + fs::write( + destination.join("existing sidecar"), + b"previous unrelated sidecar", + ) + .unwrap(); + installed_snapshot(destination) +} + +fn run_fails_before_destination_changes(root: &Path, source: &Path, destination: &Path) { + let before = preserve_existing_files(destination); + let output = invoke(root, source, destination, false); + assert!( + !output.status.success(), + "stdout={} stderr={}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); + assert_eq!(installed_snapshot(destination), before); +} + +fn versioned_name(name: &str, replacement: &str) -> String { + let version = name + .strip_prefix("libcodebasegraph-ssl-") + .and_then(|name| name.strip_suffix(".dylib")) + .expect("SSL fixture filename has the canonical shape"); + name.replacen(version, replacement, 1) +} + +#[test] +fn dry_run_validates_all_payloads_without_creating_destination() { + let (root, source, destination) = fixture(); + let output = invoke(&root, &source, &destination, true); + assert!( + output.status.success(), + "{}", + String::from_utf8_lossy(&output.stderr) + ); + assert!(!destination.exists()); + fs::remove_dir_all(root.parent().unwrap()).unwrap(); +} + +#[cfg(unix)] +#[test] +fn real_install_places_verified_runtime_license_and_binaries_in_spaced_directory() { + use std::os::unix::fs::PermissionsExt; + + let (root, source, destination) = fixture(); + let output = invoke(&root, &source, &destination, false); + assert!( + output.status.success(), + "{}", + String::from_utf8_lossy(&output.stderr) + ); + let expected: BTreeSet = NativeTarget::MacosArm64 + .payload_files() + .into_iter() + .map(str::to_string) + .collect(); + let actual: BTreeSet = fs::read_dir(&destination) + .unwrap() + .map(|entry| entry.unwrap().file_name().to_string_lossy().into_owned()) + .collect(); + assert_eq!(actual, expected); + for name in NativeTarget::MacosArm64.payload_files() { + assert_eq!( + sha256_file(&source.join(name)).unwrap(), + sha256_file(&destination.join(name)).unwrap() + ); + let mode = fs::metadata(destination.join(name)) + .unwrap() + .permissions() + .mode() + & 0o777; + let expected_mode = if NativeTarget::MacosArm64.binary_names().0 == name + || NativeTarget::MacosArm64.binary_names().1 == name + { + 0o755 + } else { + 0o644 + }; + assert_eq!(mode, expected_mode, "mode for {name}"); + } + assert_eq!(expected_package_files(NativeTarget::MacosArm64).len(), 8); + fs::remove_dir_all(root.parent().unwrap()).unwrap(); +} + +#[cfg(unix)] +#[test] +fn missing_corrupt_and_symlink_payloads_preserve_existing_destination_files() { + use std::os::unix::fs::symlink; + + let (root, source, destination) = fixture(); + for name in NativeTarget::MacosArm64.payload_files() { + let selected_path = source.join(name); + let saved = fs::read(&selected_path).unwrap(); + + fs::remove_file(&selected_path).unwrap(); + run_fails_before_destination_changes(&root, &source, &destination); + fs::write(&selected_path, &saved).unwrap(); + + fs::write(&selected_path, b"corrupted payload bytes").unwrap(); + run_fails_before_destination_changes(&root, &source, &destination); + fs::write(&selected_path, &saved).unwrap(); + } + + let selected_path = source.join(super::macos::PAYLOAD_FILES[0]); + let saved = fs::read(&selected_path).unwrap(); + fs::remove_file(&selected_path).unwrap(); + let external = root.join("outside runtime"); + fs::write(&external, &saved).unwrap(); + symlink(&external, &selected_path).unwrap(); + run_fails_before_destination_changes(&root, &source, &destination); + + fs::remove_dir_all(root.parent().unwrap()).unwrap(); +} + +#[cfg(unix)] +#[test] +fn invalid_inventory_rows_and_mixed_versions_preserve_destination() { + let (root, source, destination) = fixture(); + let checksum_path = source.join("checksums.txt"); + let valid = fs::read_to_string(&checksum_path).unwrap(); + let rows: Vec<&str> = valid.lines().collect(); + let duplicate = format!("{}\n{}\n", rows[0], rows[0]); + let malformed = format!( + "not-a-digest {}\n", + NativeTarget::MacosArm64.binary_names().0 + ); + let traversal = format!("{} ../payload\n", "0".repeat(64)); + let unknown = format!("{}\n{}", valid.trim_end(), traversal); + let missing = rows[1..].join("\n"); + + for manifest in [duplicate, malformed, unknown, missing] { + fs::write(&checksum_path, manifest).unwrap(); + run_fails_before_destination_changes(&root, &source, &destination); + } + + let original_name = super::macos::PAYLOAD_FILES[0]; + let alternate_name = versioned_name(original_name, "9.8.7"); + fs::rename(source.join(original_name), source.join(&alternate_name)).unwrap(); + let mut mixed_rows = vec![format!( + "{} {alternate_name}", + sha256_file(&source.join(&alternate_name)).unwrap() + )]; + for name in NativeTarget::MacosArm64 + .payload_files() + .into_iter() + .filter(|name| *name != original_name) + { + mixed_rows.push(format!( + "{} {name}", + sha256_file(&source.join(name)).unwrap() + )); + } + fs::write(&checksum_path, format!("{}\n", mixed_rows.join("\n"))).unwrap(); + run_fails_before_destination_changes(&root, &source, &destination); + + fs::remove_dir_all(root.parent().unwrap()).unwrap(); +} + +#[cfg(unix)] +#[test] +fn nonzero_binary_probe_preserves_existing_destination_files() { + let (root, source, destination) = fixture(); + let graph = source.join(NativeTarget::MacosArm64.binary_names().0); + fs::write(&graph, b"#!/bin/sh\nexit 17\n").unwrap(); + set_executable(&graph).unwrap(); + write_internal_checksums(&source, NativeTarget::MacosArm64).unwrap(); + run_fails_before_destination_changes(&root, &source, &destination); + fs::remove_dir_all(root.parent().unwrap()).unwrap(); +} diff --git a/crates/xtask/src/main.rs b/crates/xtask/src/main.rs index a633567e..59bbb61b 100644 --- a/crates/xtask/src/main.rs +++ b/crates/xtask/src/main.rs @@ -13,6 +13,9 @@ use std::process::{Command, Stdio}; use std::time::{Duration, SystemTime, UNIX_EPOCH}; use yaml_serde::Value as YamlValue; +mod macos; +#[cfg(test)] +mod macos_installer_tests; mod native_tests; mod release; @@ -63,6 +66,29 @@ impl NativeTarget { fn uses_windows_extensions(self) -> bool { self == Self::WindowsX86_64 } + + fn payload_files(self) -> Vec<&'static str> { + let (graph, wiki) = self.binary_names(); + let mut files = vec![graph, wiki]; + if self == Self::MacosArm64 { + files.extend(macos::PAYLOAD_FILES); + } + files + } + + fn package_files(self) -> Vec<(&'static str, u32)> { + let (graph, wiki) = self.binary_names(); + let mut files = vec![(graph, 0o755), (wiki, 0o755)]; + if self == Self::MacosArm64 { + files.extend(macos::PAYLOAD_FILES.map(|name| (name, 0o644))); + } + files.extend([ + ("checksums.txt", 0o644), + ("install.sh", 0o755), + ("install.ps1", 0o644), + ]); + files + } } #[derive(Debug, Deserialize, Serialize)] @@ -276,7 +302,6 @@ fn native_artifact(args: Vec) -> Result<(), String> { ensure_native_host(target)?; let version = release_version()?; - build_native_binaries(target)?; fs::create_dir_all(output).map_err(|error| { format!( "failed to create artifact output {}: {error}", @@ -285,7 +310,22 @@ fn native_artifact(args: Vec) -> Result<(), String> { })?; let staging = unique_temp_dir(&format!("native_artifact_{}", target.as_str()))?; - let result = build_and_smoke_native_archive(target, &version, commit_sha, output, &staging); + let result = (|| { + let runtime = if target == NativeTarget::MacosArm64 { + Some(macos::prepare_openssl(&staging.join("openssl"))?) + } else { + None + }; + build_native_binaries(target, runtime.as_deref())?; + build_and_smoke_native_archive( + target, + &version, + commit_sha, + output, + &staging, + runtime.as_deref(), + ) + })(); let _ = fs::remove_dir_all(&staging); result } @@ -382,8 +422,22 @@ fn release_version_from_tag(tag: &str) -> Result { Ok(version.to_string()) } -fn build_native_binaries(target: NativeTarget) -> Result<(), String> { +fn configure_native_build(command: &mut Command, runtime: Option<&Path>) { + if let Some(library_dir) = runtime { + command + .env("OPENSSL_LIB_DIR", library_dir) + .env("MACOSX_DEPLOYMENT_TARGET", "13.3"); + let flags = env::var("RUSTFLAGS").unwrap_or_default(); + command.env_remove("CARGO_ENCODED_RUSTFLAGS").env( + "RUSTFLAGS", + format!("{flags} -C link-arg=-Wl,-headerpad_max_install_names"), + ); + } +} + +fn build_native_binaries(target: NativeTarget, runtime: Option<&Path>) -> Result<(), String> { let mut graph = Command::new("cargo"); + configure_native_build(&mut graph, runtime); graph.args(["build", "--locked", "--release", "--bin", "codebase-graph"]); if target.uses_windows_extensions() { graph.args(["--features", "bundled-windows-extensions"]); @@ -391,6 +445,7 @@ fn build_native_binaries(target: NativeTarget) -> Result<(), String> { run_command(&mut graph, "codebase-graph release build")?; let mut wiki = Command::new("cargo"); + configure_native_build(&mut wiki, runtime); wiki.args([ "build", "--locked", @@ -412,6 +467,7 @@ fn build_and_smoke_native_archive( commit_sha: &str, output: &Path, staging: &Path, + runtime: Option<&Path>, ) -> Result<(), String> { let package = staging.join("package"); let extracted = staging.join("extracted"); @@ -433,12 +489,10 @@ fn build_and_smoke_native_archive( set_executable(&package.join(wiki_name))?; set_executable(&package.join("install.sh"))?; - let checksums = format!( - "{} {graph_name}\n{} {wiki_name}\n", - sha256_file(&package.join(graph_name))?, - sha256_file(&package.join(wiki_name))? - ); - fs::write(package.join("checksums.txt"), checksums).map_err(|error| error.to_string())?; + if let Some(library_dir) = runtime { + macos::stage_runtime(library_dir, &package)?; + } + write_internal_checksums(&package, target)?; let archive_name = target.archive_name(version); let archive_path = output.join(&archive_name); @@ -465,8 +519,18 @@ fn build_and_smoke_native_archive( extract_and_validate_native_archive(&archive_path, target, &extracted)?; dry_run_installer(target, &extracted, &install_bin)?; - smoke_artifact(&extracted.join(graph_name))?; - smoke_wiki_artifact(&extracted.join(wiki_name))?; + if target == NativeTarget::MacosArm64 { + macos::validate_package(&extracted)?; + let installed = staging.join("installed bin with spaces"); + install_macos_package(&extracted, &installed, false)?; + macos::validate_package(&installed)?; + smoke_artifact(&installed.join(graph_name))?; + smoke_wiki_artifact(&installed.join(wiki_name))?; + verify_macos_install_rejects_bad_runtime(&extracted, &installed, staging)?; + } else { + smoke_artifact(&extracted.join(graph_name))?; + smoke_wiki_artifact(&extracted.join(wiki_name))?; + } println!("{}", archive_path.display()); Ok(()) } @@ -510,14 +574,7 @@ fn create_native_archive( .mtime(0) .write(file, Compression::default()); let mut archive = tar::Builder::new(encoder); - let (graph_name, wiki_name) = target.binary_names(); - for (name, mode) in [ - (graph_name, 0o755), - (wiki_name, 0o755), - ("checksums.txt", 0o644), - ("install.sh", 0o755), - ("install.ps1", 0o644), - ] { + for (name, mode) in target.package_files() { append_deterministic_file(&mut archive, &package.join(name), name, mode)?; } let encoder = archive @@ -594,17 +651,22 @@ fn extract_and_validate_native_archive( } fn expected_package_files(target: NativeTarget) -> BTreeSet { - let (graph_name, wiki_name) = target.binary_names(); - [ - graph_name, - wiki_name, - "checksums.txt", - "install.sh", - "install.ps1", - ] - .into_iter() - .map(str::to_string) - .collect() + target + .package_files() + .into_iter() + .map(|(name, _)| name.to_string()) + .collect() +} + +fn write_internal_checksums(directory: &Path, target: NativeTarget) -> Result<(), String> { + let mut text = String::new(); + for name in target.payload_files() { + text.push_str(&format!( + "{} {name}\n", + sha256_file(&directory.join(name))? + )); + } + fs::write(directory.join("checksums.txt"), text).map_err(|error| error.to_string()) } fn validate_internal_checksums(directory: &Path, target: NativeTarget) -> Result<(), String> { @@ -612,12 +674,12 @@ fn validate_internal_checksums(directory: &Path, target: NativeTarget) -> Result .map_err(|error| format!("failed to read checksums.txt: {error}"))?; let mut checksums = BTreeMap::new(); for line in text.lines() { - let mut parts = line.split_whitespace(); - let digest = parts.next().unwrap_or_default(); - let name = parts.next().unwrap_or_default(); - if parts.next().is_some() - || digest.len() != 64 + let Some((digest, name)) = line.split_once(" ") else { + return Err(format!("invalid checksums.txt line: {line:?}")); + }; + if digest.len() != 64 || !digest.bytes().all(|byte| byte.is_ascii_hexdigit()) + || name.bytes().any(|byte| byte.is_ascii_whitespace()) || checksums .insert(name.to_string(), digest.to_ascii_lowercase()) .is_some() @@ -625,13 +687,13 @@ fn validate_internal_checksums(directory: &Path, target: NativeTarget) -> Result return Err(format!("invalid checksums.txt line: {line:?}")); } } - let (graph_name, wiki_name) = target.binary_names(); - let expected_names: BTreeSet = [graph_name, wiki_name] + let expected_names: BTreeSet = target + .payload_files() .into_iter() .map(str::to_string) .collect(); if checksums.keys().cloned().collect::>() != expected_names { - return Err("checksums.txt does not contain exactly both packaged binaries".to_string()); + return Err("checksums.txt does not contain the exact packaged payload".to_string()); } for (name, expected) in checksums { let actual = sha256_file(&directory.join(&name))?; @@ -672,6 +734,61 @@ fn dry_run_installer( run_command(&mut command, "packaged installer dry-run") } +fn install_macos_package(source: &Path, destination: &Path, dry_run: bool) -> Result<(), String> { + let mut command = Command::new("/bin/bash"); + command + .arg(source.join("install.sh")) + .arg("--source-dir") + .arg(source) + .arg("--bin-dir") + .arg(destination); + if dry_run { + command.arg("--dry-run"); + } + run_command(&mut command, "packaged macOS installer") +} + +fn verify_macos_install_rejects_bad_runtime( + source: &Path, + destination: &Path, + staging: &Path, +) -> Result<(), String> { + let target = NativeTarget::MacosArm64; + let before = target + .payload_files() + .into_iter() + .map(|name| sha256_file(&destination.join(name)).map(|digest| (name, digest))) + .collect::, _>>()?; + for failure in ["missing", "corrupt", "incompatible"] { + let broken = staging.join(format!("{failure} runtime")); + fs::create_dir(&broken).map_err(|error| error.to_string())?; + for (name, _) in target.package_files() { + copy_file(source.join(name), broken.join(name))?; + } + let library = broken.join(macos::PAYLOAD_FILES[0]); + if failure == "missing" { + fs::remove_file(&library).map_err(|error| error.to_string())?; + } else { + fs::write(&library, b"not a compatible Mach-O library\n") + .map_err(|error| error.to_string())?; + if failure == "incompatible" { + write_internal_checksums(&broken, target)?; + } + } + for dry_run in [true, false] { + if install_macos_package(&broken, destination, dry_run).is_ok() { + return Err(format!("installer accepted {failure} bundled runtime")); + } + for (name, digest) in &before { + if sha256_file(&destination.join(name))? != *digest { + return Err(format!("{failure} runtime changed installed {name}")); + } + } + } + } + Ok(()) +} + fn sha256_file(path: &Path) -> Result { let mut file = File::open(path).map_err(|error| format!("failed to open {}: {error}", path.display()))?; @@ -1663,6 +1780,9 @@ fn smoke_artifact(executable: &Path) -> Result<(), String> { if !executable.exists() { return Err(format!("binary does not exist: {}", executable.display())); } + let executable = executable + .canonicalize() + .map_err(|error| error.to_string())?; let temp = unique_temp_dir("codebase_graph_smoke")?; fs::create_dir_all(temp.join("sample")).map_err(|error| error.to_string())?; fs::write( @@ -1671,12 +1791,12 @@ fn smoke_artifact(executable: &Path) -> Result<(), String> { ) .map_err(|error| error.to_string())?; - run_checked(executable, ["--help"])?; - let schema = run_capture(executable, ["schema", "--json"])?; - serde_json::from_str::(&schema) + run_checked_in(&executable, ["--help"], &temp)?; + let schema = run_capture_in(&executable, ["schema", "--json"], &temp)?; + serde_json::from_slice::(&schema.stdout) .map_err(|error| format!("schema did not emit JSON: {error}"))?; - run_checked( - executable, + run_checked_in( + &executable, [ "install", "--repo-root", @@ -1688,9 +1808,10 @@ fn smoke_artifact(executable: &Path) -> Result<(), String> { "--dry-run", "--json", ], + &temp, )?; - run_checked( - executable, + run_checked_in( + &executable, [ "install", "--repo-root", @@ -1701,18 +1822,20 @@ fn smoke_artifact(executable: &Path) -> Result<(), String> { "skip", "--json", ], + &temp, )?; - run_checked( - executable, + run_checked_in( + &executable, [ "check-health", "--repo-root", temp.join("sample").to_str().ok_or("invalid temp path")?, "--json", ], + &temp, )?; - run_checked( - executable, + run_checked_in( + &executable, [ "codebase-search", "helper", @@ -1720,8 +1843,105 @@ fn smoke_artifact(executable: &Path) -> Result<(), String> { temp.join("sample").to_str().ok_or("invalid temp path")?, "--no-refresh", ], + &temp, )?; - smoke_mcp_stdio(executable, &temp.join("sample"))?; + if cfg!(target_os = "macos") { + verify_bundled_extension_smoke(&executable, &temp)?; + } + smoke_mcp_stdio(&executable, &temp.join("sample"), &temp)?; + Ok(()) +} + +fn verify_bundled_extension_smoke(executable: &Path, fixture: &Path) -> Result<(), String> { + let sample = fixture.join("sample"); + let database = fixture.join("extension-smoke.ldb"); + let manifest = fixture.join("extension-manifest.json"); + run_checked_in( + executable, + [ + "build", + "--repo-root", + sample.to_str().ok_or("invalid sample path")?, + "--db", + database.to_str().ok_or("invalid database path")?, + "--manifest", + manifest.to_str().ok_or("invalid manifest path")?, + "--no-git", + "--json", + ], + fixture, + )?; + let search = run_capture_in( + executable, + [ + "codebase-search", + "helper", + "--repo-root", + sample.to_str().ok_or("invalid sample path")?, + "--db", + database.to_str().ok_or("invalid database path")?, + "--manifest", + manifest.to_str().ok_or("invalid manifest path")?, + "--no-refresh", + "--json", + ], + fixture, + )?; + let search: Value = + serde_json::from_slice(&search.stdout).map_err(|error| error.to_string())?; + if search["results"].as_array().is_none_or(Vec::is_empty) { + return Err("installed graph search did not find the smoke fixture".into()); + } + + // A legacy manifest selects Ladybug FTS rather than the current sidecar backend. + let mut legacy: Value = + serde_json::from_slice(&fs::read(&manifest).map_err(|error| error.to_string())?) + .map_err(|error| error.to_string())?; + legacy["search_backend"] = Value::Null; + let legacy_manifest = fixture.join("legacy-extension-manifest.json"); + fs::write( + &legacy_manifest, + serde_json::to_vec(&legacy).map_err(|error| error.to_string())?, + ) + .map_err(|error| error.to_string())?; + run_checked_in( + executable, + [ + "codebase-search", + "helper", + "--repo-root", + sample.to_str().ok_or("invalid sample path")?, + "--db", + database.to_str().ok_or("invalid database path")?, + "--manifest", + legacy_manifest + .to_str() + .ok_or("invalid legacy manifest path")?, + "--no-refresh", + "--json", + ], + fixture, + )?; + let cache = fixture.join("smoke-home/.lbdb/extension/0.19.0/osx_arm64"); + for (extension, digest) in [ + ( + "json", + "e6482a025de31d662ee4b32a39c991706b8cc3c2ec49673ef5d8ce45cc23ce7a", + ), + ( + "fts", + "83ae4be9fa718577ca971f27ff890b3aea5789c9dd908784e28f52917febbfdb", + ), + ] { + let path = cache + .join(extension) + .join(format!("lib{extension}.lbug_extension")); + if sha256_file(&path)? != digest { + return Err(format!( + "installed smoke did not seed the official {extension} bytes" + )); + } + } Ok(()) } @@ -1761,7 +1981,7 @@ fn smoke_wiki_artifact(executable: &Path) -> Result<(), String> { let bundle_text = bundle.to_str().ok_or("invalid bundle path")?; let site_text = site.to_str().ok_or("invalid site path")?; - assert_version_surface(&executable, Path::new("crates/k-wiki/Cargo.toml"))?; + assert_version_surface(&executable, Path::new("crates/k-wiki/Cargo.toml"), &temp)?; run_checked_in(&executable, ["--help"], &temp)?; run_checked_in(&executable, ["validate", bundle_text, "--json"], &temp)?; run_checked_in( @@ -1779,7 +1999,7 @@ fn smoke_wiki_artifact(executable: &Path) -> Result<(), String> { } fn smoke_wiki_http(executable: &Path, bundle: &Path, current_dir: &Path) -> Result<(), String> { - let mut child = Command::new(executable) + let mut child = smoke_command(executable, current_dir)? .args(["serve"]) .arg(bundle) .args(["--host", "127.0.0.1", "--port", "0"]) @@ -1845,7 +2065,7 @@ fn smoke_wiki_mcp_stdio( bundle: &Path, current_dir: &Path, ) -> Result<(), String> { - let mut child = Command::new(executable) + let mut child = smoke_command(executable, current_dir)? .arg("mcp") .arg(bundle) .current_dir(current_dir) @@ -1898,9 +2118,13 @@ fn smoke_wiki_mcp_stdio( Ok(()) } -fn assert_version_surface(executable: &Path, manifest: &Path) -> Result<(), String> { +fn assert_version_surface( + executable: &Path, + manifest: &Path, + fixture: &Path, +) -> Result<(), String> { let expected = cargo_version(manifest)?; - let output = run_capture_in(executable, ["--version"], Path::new("."))?; + let output = run_capture_in(executable, ["--version"], fixture)?; let stdout = String::from_utf8(output.stdout).map_err(|error| error.to_string())?; let actual = stdout.trim(); let expected_line = format!("k-wiki {expected}"); @@ -2094,8 +2318,8 @@ fn assert_wiki_codex_project_install( Ok(()) } -fn smoke_mcp_stdio(executable: &Path, repo_root: &Path) -> Result<(), String> { - let mut child = Command::new(executable) +fn smoke_mcp_stdio(executable: &Path, repo_root: &Path, fixture: &Path) -> Result<(), String> { + let mut child = smoke_command(executable, fixture)? .args(["mcp", "start", "--repo-root"]) .arg(repo_root) .stdin(Stdio::piped()) @@ -2203,25 +2427,22 @@ fn dependency_version(manifest: &Path, dependency: &str) -> Result(executable: &Path, args: I) -> Result<(), String> -where - I: IntoIterator, -{ - let output = Command::new(executable) - .args(args) - .output() - .map_err(|error| error.to_string())?; - if output.status.success() { - Ok(()) - } else { - Err(format!( - "command failed with status {}: {}\nstdout:\n{}\nstderr:\n{}", - output.status, - executable.display(), - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - )) +fn smoke_command(executable: &Path, fixture: &Path) -> Result { + let mut command = Command::new(executable); + if cfg!(target_os = "macos") { + let home = fixture.join("smoke-home"); + fs::create_dir_all(&home).map_err(|error| error.to_string())?; + for (key, _) in env::vars_os() { + let name = key.to_string_lossy(); + if name.starts_with("DYLD_") || name.starts_with("OPENSSL_") { + command.env_remove(key); + } + } + command + .env("HOME", home) + .env("PATH", "/usr/bin:/bin:/usr/sbin:/sbin"); } + Ok(command) } fn run_capture_in<'a, I>( @@ -2232,7 +2453,7 @@ fn run_capture_in<'a, I>( where I: IntoIterator, { - Command::new(executable) + smoke_command(executable, current_dir)? .args(args) .current_dir(current_dir) .output() @@ -2261,7 +2482,7 @@ fn run_capture_in_with_env<'a, I>( where I: IntoIterator, { - let mut command = Command::new(executable); + let mut command = smoke_command(executable, current_dir)?; command.args(args).current_dir(current_dir); for (key, value) in env_pairs { command.env(key, value); @@ -2288,7 +2509,7 @@ fn run_checked_in<'a, I>(executable: &Path, args: I, current_dir: &Path) -> Resu where I: IntoIterator, { - let output = Command::new(executable) + let output = smoke_command(executable, current_dir)? .args(args) .current_dir(current_dir) .output() @@ -2306,26 +2527,6 @@ where } } -fn run_capture<'a, I>(executable: &Path, args: I) -> Result -where - I: IntoIterator, -{ - let output = Command::new(executable) - .args(args) - .output() - .map_err(|error| error.to_string())?; - if !output.status.success() { - return Err(format!( - "command failed with status {}: {}\nstdout:\n{}\nstderr:\n{}", - output.status, - executable.display(), - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - )); - } - String::from_utf8(output.stdout).map_err(|error| error.to_string()) -} - fn unique_temp_dir(prefix: &str) -> Result { let nanos = SystemTime::now() .duration_since(UNIX_EPOCH) @@ -2484,13 +2685,7 @@ mod tests { let temp = super::unique_temp_dir("xtask_archive_contract").unwrap(); let package = temp.join("package"); fs::create_dir_all(&package).unwrap(); - for name in [ - "codebase-graph", - "k-wiki", - "checksums.txt", - "install.sh", - "install.ps1", - ] { + for (name, _) in NativeTarget::MacosArm64.package_files() { fs::write(package.join(name), name).unwrap(); } let archive = temp.join("artifact.tar.gz"); @@ -2503,6 +2698,46 @@ mod tests { fs::remove_dir_all(temp).unwrap(); } + #[cfg(target_os = "macos")] + #[test] + fn macos_smoke_commands_use_a_fixture_home_and_system_path() { + let fixture = super::unique_temp_dir("xtask_smoke_env").unwrap(); + let command = super::smoke_command(Path::new("/usr/bin/true"), &fixture).unwrap(); + let environment = command + .get_envs() + .collect::>(); + assert_eq!( + environment.get(std::ffi::OsStr::new("HOME")), + Some(&Some(fixture.join("smoke-home").as_os_str())) + ); + assert_eq!( + environment.get(std::ffi::OsStr::new("PATH")), + Some(&Some(std::ffi::OsStr::new("/usr/bin:/bin:/usr/sbin:/sbin"))) + ); + fs::remove_dir_all(fixture).unwrap(); + } + + #[test] + fn native_payload_integrity_covers_runtime_files_and_preserves_windows_inventory() { + let temp = super::unique_temp_dir("xtask_payload_integrity").unwrap(); + let target = NativeTarget::MacosArm64; + for name in target.payload_files() { + fs::write(temp.join(name), name).unwrap(); + } + super::write_internal_checksums(&temp, target).unwrap(); + super::validate_internal_checksums(&temp, target).unwrap(); + for name in super::macos::PAYLOAD_FILES { + fs::write(temp.join(name), b"corrupt").unwrap(); + assert!(super::validate_internal_checksums(&temp, target).is_err()); + fs::write(temp.join(name), name).unwrap(); + } + assert_eq!( + NativeTarget::WindowsX86_64.payload_files(), + ["codebase-graph.exe", "k-wiki.exe"] + ); + fs::remove_dir_all(temp).unwrap(); + } + #[test] fn complete_artifact_set_rejects_mixed_provenance() { let temp = super::unique_temp_dir("xtask_artifact_set").unwrap(); @@ -2536,15 +2771,12 @@ mod tests { fs::write(package.join(wiki_name), b"wiki").unwrap(); fs::write(package.join("install.sh"), b"installer").unwrap(); fs::write(package.join("install.ps1"), b"installer").unwrap(); - fs::write( - package.join("checksums.txt"), - format!( - "{} {graph_name}\n{} {wiki_name}\n", - sha256_file(&package.join(graph_name)).unwrap(), - sha256_file(&package.join(wiki_name)).unwrap() - ), - ) - .unwrap(); + if target == NativeTarget::MacosArm64 { + for name in super::macos::PAYLOAD_FILES { + fs::write(package.join(name), name).unwrap(); + } + } + super::write_internal_checksums(&package, target).unwrap(); let archive_name = target.archive_name(version); let archive_path = artifact_dir.join(&archive_name); create_native_archive(&package, &archive_path, target).unwrap(); diff --git a/docs/release.md b/docs/release.md index 6e7d1008..e6b30429 100644 --- a/docs/release.md +++ b/docs/release.md @@ -121,10 +121,34 @@ Each native archive must contain: - `codebase-graph` / `codebase-graph.exe` - `k-wiki` / `k-wiki.exe` -- `checksums.txt` for the packaged binaries +- `checksums.txt` for the complete target payload - `install.sh` - `install.ps1` +The macOS ARM64 archive additionally includes the version-qualified +`libcodebasegraph-ssl-3.6.2.dylib`, `libcodebasegraph-crypto-3.6.2.dylib`, and +`openssl-3.6.2-LICENSE.txt`. These three files are covered by `checksums.txt` +and installed beside both binaries. Do not copy a binary without its matching +runtime payload. Windows archive contents are unchanged by this macOS repair. + +The macOS artifact builder verifies the official OpenSSL 3.6.2 source archive +SHA-256, source-builds its dynamic libraries for ARM64 with deployment target +13.3, and uses that library directory for both release builds. Packaging rewrites +the binary imports to executable-relative names and the SSL-to-crypto import +to a loader-relative name. It rejects unexpected non-system imports, runpaths, +architectures or minimum-OS metadata above 13.3. After load-command edits it +renews and verifies ad-hoc code signatures, then hashes the final files. This +is not Developer ID signing or notarization. + +Qualification covers only the current stable macOS release on Apple Silicon. +The exact installed archive must run on that release without relying on +separately installed Homebrew/OpenSSL. Current-release hosted CI can supply this +evidence when it records the actual OS/build and demonstrates those conditions; +a `macos-latest` label or deployment metadata alone is not proof. The earlier +macOS13.3 execution requirement is dropped. The existing13.3 deployment target +remains build metadata, not an older-version support guarantee. See the +[current-only policy amendment](https://github.com/rabii-chaarani/codebaseGraph/blob/8a410c7a22f5291f06a1cece5cd9e7c0be255e72/docs/specs/CBG-PLAT-01/decisions/d05a-current-macos-only.md). + The internal Actions artifact also carries `provenance.json`, which binds the public archive checksum to the exact commit SHA, version, and target. `provenance.json` is validation metadata and is not uploaded as a public release asset. @@ -149,9 +173,22 @@ To recover a blocked release, locate its merge SHA and successful `CI` run, disp publish that exact target. Do not clear the pending label to bypass the block. Once finalization succeeds, later merged changes can enter the next release PR. Repeating the same target is safe after a partial upload or lost response. -The packaged installer validates both binaries against `checksums.txt`, runs -`codebase-graph --help` plus `k-wiki --version`, and only then atomically -replaces the selected target binaries. +The macOS installer validates the exact five-file payload and every checksum, +then probes `codebase-graph --help` and `k-wiki --version` without inherited +runtime-library overrides. All validation precedes destination writes. Missing, +corrupt or incompatible runtime files fail without replacing the existing +installation. Dry run writes no destination. Real installation copies the two +dylibs and license before the binaries, using a temporary file and rename for +each file; this is not an all-file transaction or rollback guarantee. Older +version-qualified runtime files are retained. Windows installer behavior is +unchanged by this slice. + +The macOS artifact smoke performs a real install into a relocated directory +with spaces. Every runtime child receives an isolated home, no inherited +`DYLD_*` or OpenSSL configuration overrides, and a system-only PATH. It checks +graph build/reopen/search, bundled JSON/FTS bytes, wiki/MCP and invalid runtime +preflight while preserving an installed payload. These are local/native checks, +not, by themselves, proof of the required current-release runtime isolation. After upgrading from a release archive: diff --git a/docs/specs/CBG-PLAT-01/approvals/cod54-macos-openssl.md b/docs/specs/CBG-PLAT-01/approvals/cod54-macos-openssl.md new file mode 100644 index 00000000..620d9b5d --- /dev/null +++ b/docs/specs/CBG-PLAT-01/approvals/cod54-macos-openssl.md @@ -0,0 +1,18 @@ +# COD-54 — macOS bundled OpenSSL implementation approval + +- Work: `CBG-PLAT-01/S03`, [COD-54](https://linear.app/codebasegraph/issue/COD-54), Maintenance `P-COD-2`, CodebaseGraph workspace/team COD. +- Canonical specification: `docs/specs/CBG-PLAT-01/spec.md`, revision **0.10**, full approved Git SHA **`1a9db7b558b60248888408daa6268a3b45f2f0df`**. +- Immutable specification: https://github.com/rabii-chaarani/codebaseGraph/blob/1a9db7b558b60248888408daa6268a3b45f2f0df/docs/specs/CBG-PLAT-01/spec.md +- Verified Git blob: `3e3bc03546607c4d6cc558fa34699f4349688640`. +- Exact approved scope: **COD-54/S03 only; R03 / A05–A06**. +- Decision reference: 2026-10-27, this Prime Agent conversation. The assistant asked “Approve implementation of COD-54 only?” and named revision0.10, R03/A05–A06, bundled compatible OpenSSL libraries with relative paths/licenses/checksums and final-archive verification on clean Apple Silicon macOS13.3/current release. The user answered **“approved”**. + +## Included + +Reuse settled D02: compatible redistributable OpenSSL dylibs in macOS ARM64 prebuilt releases, relative install names/runtime paths, licenses and checksums, with no separate Homebrew/OpenSSL prerequisite. Reuse settled D05a: target macOS13.3+ Apple Silicon and require actual final installed archive execution on the minimum and a current macOS release before claiming support. Inspect final Mach-O dependencies and cover missing/corrupt/incompatible-library failures. Unavailable qualification environments remain pending, not waived. + +## Excluded and remaining gates + +No other slice, static-linking switch, ABI/dependency-version upgrade, new macOS Cargo-install contract, platform expansion, process-containment change, package/release publication, merge, deployment, service restart or aggregate closure. Existing approvals and maintainer closure ownership remain unchanged. Implementation/review, local/native qualification and aggregate closure remain separate states. + +Initial source baseline: main `34eac4f8644adc046093babd84ee1fdbffdae853` (isolated branch `fix/cod-54-macos-distribution`). Original dirty user checkout is preserved. Scryer change `chg-h7rgh8` records the approved existing-claim restoration; Release Gate `resp-16/17/255–258`, Graph Store `resp-113`. No new claims or topology are invented. Detailed execution ownership and prerequisites will be recorded in Scryer before dependent implementation; applicable fresh proposal/final review gates remain mandatory. diff --git a/docs/specs/CBG-PLAT-01/execution/cod54-macos-openssl.md b/docs/specs/CBG-PLAT-01/execution/cod54-macos-openssl.md new file mode 100644 index 00000000..c0d70f71 --- /dev/null +++ b/docs/specs/CBG-PLAT-01/execution/cod54-macos-openssl.md @@ -0,0 +1,66 @@ +# COD-54 — macOS bundled OpenSSL implementation and verification + +## Current qualification amendment — 2026-10-27 + +The user explicitly said **“well then just drop it and keep only current”** after the minimum-runner/hosted-CI discussion. [Canonical spec revision0.11](https://github.com/rabii-chaarani/codebaseGraph/blob/8a410c7a22f5291f06a1cece5cd9e7c0be255e72/docs/specs/CBG-PLAT-01/spec.md) at full SHA`8a410c7a22f5291f06a1cece5cd9e7c0be255e72` and [exact amendment approval](https://github.com/rabii-chaarani/codebaseGraph/blob/5c2e9a56d0fa87779fb31312562f4e53f902f0ac/docs/specs/CBG-PLAT-01/approvals/d05a-current-macos-only.md) replace D05a with **current stable macOS on Apple Silicon only**. There is no13.3 execution/support-qualification requirement. Actual current-release final-archive runtime/failure evidence remains required; hosted CI may supply it when the actual OS/build and no-external-Homebrew/OpenSSL runtime conditions are recorded. A moving runner label is not execution proof. + +The existing13.3 deployment target and the reviewed/tested product source`855c7e282a27de4afbad2d4252d7692de4b42ecb` remain unchanged. This is a qualification-policy/documentation change, not a new binary or a passing CI claim. Earlier approval/review/evidence files retain their original scope and are historical where they name the replaced minimum gate. D02, Windows, full native/hosted checks and maintainer aggregate closure are unchanged. Scryer amendment`chg-p0s0ar` records the impact; no architecture claim change. + +## Identity and authority + +- Work: **CBG-PLAT-01/S03 / COD-54**, R03 / A05–A06 only; [Linear](https://linear.app/codebasegraph/issue/COD-54), [draft PR163](https://github.com/rabii-chaarani/codebaseGraph/pull/163). +- Approved canonical spec: [`docs/specs/CBG-PLAT-01/spec.md`, rev0.10 at `1a9db7b558b60248888408daa6268a3b45f2f0df`](https://github.com/rabii-chaarani/codebaseGraph/blob/1a9db7b558b60248888408daa6268a3b45f2f0df/docs/specs/CBG-PLAT-01/spec.md). [Exact user approval](../approvals/cod54-macos-openssl.md), committed at `71dc27e92857c4215bbbe583f104de7f341dc816`. +- Baseline main `34eac4f8644adc046093babd84ee1fdbffdae853`; implementation `a2005dbb37a9ab914ea97b2735825b5286af5b6e`; strict-lint-only follow-up **`855c7e282a27de4afbad2d4252d7692de4b42ecb`** is the final product-source candidate. +- Scryer `chg-h7rgh8` / `chg-metn5v`; existing Release Gate resp-16/17/255–258 and Graph Store resp-113. No new claims/topology. Detailed execution brief/file ownership stays in Scryer. Ancillary cfg-only baseline warning hygiene: `chg-8zcgg3`. +- Status: implementation delivered for review; final source-bound archive verification, hosted CI, clean-consumer qualification and maintainer closure are distinct. No package publication, merge, deployment, service restart or aggregate closure is authorized. + +## Implementation and constants + +The artifact-only macOS path downloads official OpenSSL3.6.2 source, verifies SHA-256 `aaf51a1fe064384f811daeaeb4ec4dce7340ec8bd893027eee676af31e83a04f` before extraction, and builds ARM64 shared libraries with target13.3. This matches the observed producer version; it does not change Cargo dependencies or the Ladybug/extension ABI. The producer Homebrew libraries required macOS26 and were not shipped. Source/license provenance and same-version source-build behavior were independently reviewed. + +Both binaries link from the controlled library directory. Final binary dependencies use executable-relative version-qualified dylibs; SSL resolves crypto loader-relative. Packaging audits imports, IDs, no rpaths, ARM64 and minimum-OS metadata, then renews and strictly verifies ad-hoc signatures before hashing. This is not Developer ID signing/notarization. + +The typed target inventory grows from two to **five checksummed macOS payload files** and from five to **eight regular archive entries**: both binaries, SSL, crypto, license, checksums and both installers. Windows remains two/five with provenance schema1. Installer preflight verifies all hashes and both probes before any destination write; real install places runtime/license first and uses per-file temp+rename. No all-file transaction, rollback or old-runtime cleanup is promised. + +Smoke installs into a relocated path with spaces. Every macOS child receives a fresh fixture HOME, system-only PATH, and no inherited DYLD/OpenSSL overrides. Graph build/reopen/populated disk-BM25 search uses JSON. A separate legacy-manifest smoke executes actual LOAD fts with official hash-checked cache bytes; its absent native FTS indices intentionally return no results. It does **not** claim a populated native-FTS-index query. Missing/corrupt/incompatible runtime preflight runs in dry and real modes and compares all installed payload hashes. + +No changes to bundled extension bytes, Cargo/lock/dependency versions, build.rs, workflows, Windows packaging or process-containment behavior. One `cfg(any(debug_assertions,test))` import attribute removes a pre-existing release-only warning; runtime bodies/constants are unchanged. + +## Executed evidence to date + +Environment: Apple Silicon ARM64, macOS26.6.2 build25G83; rustc1.96.0/cargo1.96.0. Ladybug0.19 official static macOS asset source SHA `6bee40babdf837bb6aa4d271047dfe5ed3dacf022f16af84952ca4c22d034d62`, static library SHA `d2e9a5c37617b0affb01f18f6ef48bb1531f5744f5ba06c849226842617ad142`. + +| Check | Executed outcome | Evidence / limit | +|---|---|---| +| Baseline `cargo test -p xtask --locked` | 43 passed, zero skipped | [baseline](cod54/baseline-xtask.json) | +| Final-source nextest `-p xtask --locked`, serial/no retries | **55 passed, zero skipped** | [JUnit](cod54/final-xtask.junit.xml), [log](cod54/final-xtask.log), source855c7e2; earlier failed attempts retained | +| `cargo fmt --check` | passed | Final source candidate855c7e2 | +| Strict `cargo clippy --workspace --all-targets --all-features --locked -- -D warnings` | **passed** | [log](cod54/clippy-final.log), [identity](cod54/clippy-final.json) | +| Actual native artifact provisional run | pipeline passed: source build, package/audit/sign/install/runtime positives and preflight negatives | [record](cod54/artifact-attempt-2.json), [inventory](cod54/artifact-attempt-2-inventory.json). Provisional metadata named approval SHA, so **excluded from final source-bound qualification** | +| Exact-source archive at855c7e2 | **full pipeline passed, exit0** | [record](cod54/source-bound-artifact.json), [inventory/provenance](cod54/source-bound-inventory.json), [compressed log](cod54/source-bound-artifact.log.gz). Archive SHA-256 `e5f32abbf8124584d5f6f2df403091c3323d16a1359a70f387fc99eeeb6e4282`; four Mach-O signatures/imports/ARM64/minOS13.3 independently checked by reviewer | +| Full local native suite, all cohorts | **passed, exit0; 1,356 passed / 0 failed / 4 declared ignored** | [summary](cod54/native-full-summary.json), [native execution/inventory report](cod54/native-full-report.json), [compressed full log](cod54/native-full.log.gz); source855c7e2, captured CLI SHA`de3930c3c3daa63b54b6fae59a8e287ce1f929b0e877a6c2bc24814a3157345a`. This is a native report/log, not JUnit; four ignored fixtures are not passing tests | +| Hosted macOS/Windows native CI | **failed [run40474966274](https://github.com/rabii-chaarani/codebaseGraph/actions/runs/40474966274)** at docs/policy head1f68596 | [failed-job details](cod54/ci-40474966274-summary.json), [compressed failed log](cod54/ci-40474966274-failed.log.gz). Mac unit quiescence-confirmation assertion and Windows integration numeric-PID assertion failed; both archive jobs skipped. No hosted qualification pass | + +Earlier compiler/fixture/PAX failures are retained in linked evidence and are not passing results. The official source PAX global comment is validated and never extracted as a file; actual source entries remain confined. All product-source changes are isolated from the original dirty user checkout. + +## Review + +- Fresh independent proposal review: **GPT-6.1 Sol / High**, [GREEN revision2 and starting conditions](cod54/proposal-review.md), before implementation. +- Parallel bounded backend lanes: GPT-6 Luna / Extra High, one writer per file; coordinator serialized integration/Git/builds and later took over installer fixture cleanup after stopping the unresponsive worker. +- Fresh final integrated review: **GPT-6.1 Sol / Max**, [final report](cod54/final-review.md), **GREEN for bounded implementation/local native artifact handoff** at855c7e2. No unresolved introduced P0–P3 defect. The reviewer verified the final55/55 suite, strict lint, source-bound artifact pipeline, immutable archive/installed bytes, imports/IDs/minOS/signatures and clean-env probes. Full native/hosted CI and clean minimum/current consumer qualification remain separate pending gates. The transient older-read mutability observation was withdrawn, not left unresolved. + +## Completed local native suite and hosted blockers — 2026-10-27 + +The source855c7e2 local all-cohort native run completed:1,356 passed,0 failed,4 declared ignored, exit0. These are executed source-bound native results, not clean consumer qualification. Four ignored cases remain explicitly listed in the summary; no generated JUnit is invented for the native runner. + +Hosted CI40474966274 at docs/policy head1f68596 failed in two existing paths, so both native artifact jobs and required qualification remain blocked. Mac failure: `dc_s5_existing_old_current_managed_store_requires_one_confirmation_then_retires_old_generation` did not observe durable acceptance of the single quiescence confirmation; local same-source run passes, and no root cause is asserted. Windows failure: `bounded_production_chunks_reopen_with_primary_keys_connectors_and_search` asserts unique numeric PID count53against56; this is the separately tracked COD-98 process-identity issue, fixed and qualified in PR157 but not included in this branch's baseline. Both paths are unchanged by COD-54. This is not proof that the entire hosted failure is unrelated; no failure is suppressed or retried into success. + +No storage/COPY fixes, cherry-picks, test weakening or new qualification job have been implemented from this read-only triage. Any independent consumer-qualification workflow discussed in chat remains a proposal, not approved/implemented scope. COD-54 stays In Review; actual current-release hosted/runtime qualification and full head-bound CI remain open. The13.3 execution requirement remains removed by the approved current-only amendment. + +## Remaining qualification and handoff + +**COD-54 is not Done.** Under the approved current-only amendment above, actual final installed archive execution on **current stable macOS on Apple Silicon without reliance on separately installed Homebrew/OpenSSL** remains required. Hosted CI may supply that execution when its actual OS/build and runtime conditions are evidenced. There is no13.3 execution gate. Deployment metadata, a moving runner label and developer-host success alone are not current-release qualification. Full native/hosted checks still need passing execution; the final bounded source/artifact review is GREEN. Aggregate closure stays with the maintainer; do not infer S10/COD-61 closure. + +Configured wiki/Scryer/MCP graph are bound to the older original checkout, not the implementation SHA. Target-local graph discovery was completed before source inspection. Existing claims were checked proportionately; no source-map/current verdict is fabricated for the older root and no unrelated global drift reconciliation is performed. + +Reusable knowledge assessment: no new memory candidate is warranted for transient pickup/progress. The existing single-artifact-recipe/provenance memory applies; canonical execution evidence is retained here instead of duplicating status into memory. diff --git a/docs/specs/CBG-PLAT-01/execution/cod54/artifact-attempt-1.log b/docs/specs/CBG-PLAT-01/execution/cod54/artifact-attempt-1.log new file mode 100644 index 00000000..2a5e7f01 --- /dev/null +++ b/docs/specs/CBG-PLAT-01/execution/cod54/artifact-attempt-1.log @@ -0,0 +1,4 @@ + Compiling xtask v4.0.0 (/private/tmp/codebasegraph-cod54-macos-distribution/crates/xtask) + Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.70s + Running `target/debug/xtask native-artifact --target macos-arm64 --commit-sha 71dc27e92857c4215bbbe583f104de7f341dc816 --output /private/tmp/codebasegraph-cod54-macos-distribution/dist/cod54-local` +unsafe path in verified OpenSSL archive: pax_global_header diff --git a/docs/specs/CBG-PLAT-01/execution/cod54/artifact-attempt-2-inventory.json b/docs/specs/CBG-PLAT-01/execution/cod54/artifact-attempt-2-inventory.json new file mode 100644 index 00000000..2060caca --- /dev/null +++ b/docs/specs/CBG-PLAT-01/execution/cod54/artifact-attempt-2-inventory.json @@ -0,0 +1,46 @@ +{ + "files": [ + [ + "codebase-graph", + 48024240, + "0o755" + ], + [ + "k-wiki", + 6776576, + "0o755" + ], + [ + "libcodebasegraph-ssl-3.6.2.dylib", + 996384, + "0o644" + ], + [ + "libcodebasegraph-crypto-3.6.2.dylib", + 5172016, + "0o644" + ], + [ + "openssl-3.6.2-LICENSE.txt", + 10175, + "0o644" + ], + [ + "checksums.txt", + 447, + "0o644" + ], + [ + "install.sh", + 6442, + "0o755" + ], + [ + "install.ps1", + 3086, + "0o644" + ] + ], + "checksum_text": "5b4c952d07b8f5a280a8c307e9e396b0e3a750c67558020edfd163f05bd6ebed codebase-graph\nb5598844dbe362438d3a7ce06cfc0cc99fc08622c539a4b000589a6ef4e4b7cd k-wiki\nbf8b550949bf3695242555c5f2282332435f1ba00202cfcc5ad2411a5268c714 libcodebasegraph-ssl-3.6.2.dylib\n88c5646e625be69572b2810f3a1e0a55f7b3a9f9c29d6900d36f1bf17a12da34 libcodebasegraph-crypto-3.6.2.dylib\n7d5450cb2d142651b8afa315b5f238efc805dad827d91ba367d8516bc9d49e7a openssl-3.6.2-LICENSE.txt\n", + "archive_sha256": "aaf4b6ca5eb8e7c5b2d510b83102c28a2125bd4e601105eaaf6c451238227e77" +} \ No newline at end of file diff --git a/docs/specs/CBG-PLAT-01/execution/cod54/artifact-attempt-2.json b/docs/specs/CBG-PLAT-01/execution/cod54/artifact-attempt-2.json new file mode 100644 index 00000000..6113a070 --- /dev/null +++ b/docs/specs/CBG-PLAT-01/execution/cod54/artifact-attempt-2.json @@ -0,0 +1,15 @@ +{ + "command": "cargo run -p xtask --locked -- native-artifact --target macos-arm64 --commit-sha 71dc27e92857c4215bbbe583f104de7f341dc816 --output /private/tmp/codebasegraph-cod54-macos-distribution/dist/cod54-local", + "built_source_sha": "a2005dbb37a9ab914ea97b2735825b5286af5b6e", + "provenance_commit_arg": "71dc27e92857c4215bbbe583f104de7f341dc816", + "status": "passed", + "classification": "historical provisional test; source commit argument not final; excluded from final-archive qualification", + "native_env": { + "os": "macOS26.6.2", + "build": "25G83", + "arch": "arm64" + }, + "exit": 0, + "duration_seconds": 348.5107852909714, + "archive_sha256": "aaf4b6ca5eb8e7c5b2d510b83102c28a2125bd4e601105eaaf6c451238227e77" +} \ No newline at end of file diff --git a/docs/specs/CBG-PLAT-01/execution/cod54/baseline-binary-manifest.json b/docs/specs/CBG-PLAT-01/execution/cod54/baseline-binary-manifest.json new file mode 100644 index 00000000..61cbb5c1 --- /dev/null +++ b/docs/specs/CBG-PLAT-01/execution/cod54/baseline-binary-manifest.json @@ -0,0 +1,14 @@ +{ + "codebase-graph": { + "sha256": "05736d64ae262deb3a38b3ed464534c2e191e483a3aa1d4476bdf98e464cec2b", + "path": "/private/tmp/codebasegraph-cod54-review/baseline-bins/codebase-graph", + "imports": "/private/tmp/codebasegraph-cod54-review/baseline-bins/codebase-graph:\n\t/private/tmp/codebasegraph-cod54-review/openssl-probe-build/install/lib/libssl.3.dylib (compatibility version 3.0.0, current version 3.0.0)\n\t/private/tmp/codebasegraph-cod54-review/openssl-probe-build/install/lib/libcrypto.3.dylib (compatibility version 3.0.0, current version 3.0.0)\n\t/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation (compatibility version 150.0.0, current version 5026.5.4)\n\t/System/Library/Frameworks/CoreServices.framework/Versions/A/CoreServices (compatibility version 1.0.0, current version 1226.0.0)\n\t/usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 2100.43.0)\n\t/usr/lib/libiconv.2.dylib (compatibility version 7.0.0, current version 7.0.0)\n\t/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1356.0.0)\n", + "minos": "/private/tmp/codebasegraph-cod54-review/baseline-bins/codebase-graph:\nLoad command 11\n cmd LC_BUILD_VERSION\n cmdsize 32\n platform MACOS\n minos 13.3\n sdk 26.5\n ntools 1\n tool LD\n version 1267.0\n" + }, + "k-wiki": { + "sha256": "7b19765973e76685689655456003b6c1c68bd5f4f87e995c3af7364d95b082f7", + "path": "/private/tmp/codebasegraph-cod54-review/baseline-bins/k-wiki", + "imports": "/private/tmp/codebasegraph-cod54-review/baseline-bins/k-wiki:\n\t/private/tmp/codebasegraph-cod54-review/openssl-probe-build/install/lib/libssl.3.dylib (compatibility version 3.0.0, current version 3.0.0)\n\t/private/tmp/codebasegraph-cod54-review/openssl-probe-build/install/lib/libcrypto.3.dylib (compatibility version 3.0.0, current version 3.0.0)\n\t/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation (compatibility version 150.0.0, current version 5026.5.4)\n\t/System/Library/Frameworks/CoreServices.framework/Versions/A/CoreServices (compatibility version 1.0.0, current version 1226.0.0)\n\t/usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 2100.43.0)\n\t/usr/lib/libiconv.2.dylib (compatibility version 7.0.0, current version 7.0.0)\n\t/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1356.0.0)\n", + "minos": "/private/tmp/codebasegraph-cod54-review/baseline-bins/k-wiki:\nLoad command 11\n cmd LC_BUILD_VERSION\n cmdsize 32\n platform MACOS\n minos 13.3\n sdk 26.5\n ntools 1\n tool LD\n version 1267.0\n" + } +} \ No newline at end of file diff --git a/docs/specs/CBG-PLAT-01/execution/cod54/baseline-xtask.json b/docs/specs/CBG-PLAT-01/execution/cod54/baseline-xtask.json new file mode 100644 index 00000000..bd61f50b --- /dev/null +++ b/docs/specs/CBG-PLAT-01/execution/cod54/baseline-xtask.json @@ -0,0 +1,9 @@ +{ + "command": "cargo test -p xtask --locked", + "revision": "71dc27e92857c4215bbbe583f104de7f341dc816", + "product_source": "34eac4f8644adc046093babd84ee1fdbffdae853", + "exit": 0, + "passed": 43, + "failed": 0, + "ignored": 0 +} \ No newline at end of file diff --git a/docs/specs/CBG-PLAT-01/execution/cod54/checks-in-progress.json b/docs/specs/CBG-PLAT-01/execution/cod54/checks-in-progress.json new file mode 100644 index 00000000..72c57d51 --- /dev/null +++ b/docs/specs/CBG-PLAT-01/execution/cod54/checks-in-progress.json @@ -0,0 +1,85 @@ +{ + "baseline": { + "command": "cargo test -p xtask --locked", + "pass": 43, + "skip": 0 + }, + "integrated": { + "command": "cargo nextest run --config-file /private/tmp/codebasegraph-cod54-review/nextest.toml --profile cod54 -p xtask --locked --test-threads 1 --no-fail-fast", + "pass": 55, + "skip": 0, + "revision": "a2005dbb37a9ab914ea97b2735825b5286af5b6e", + "source_hashes": { + "crates/xtask/src/main.rs": "6610d3c571b2a39af7f50cc2703cb9a5cb5431573c0dd46af43dcb9669bd35dd", + "crates/xtask/src/macos.rs": "1f4f650bd0ae0227de998c13f1659ade6485c8cad5e56d2c6602a0820c09299f", + "crates/xtask/src/macos_installer_tests.rs": "10646723ce3db83a5a70e51769957214801d4ef7533362eab1035c31ce7fab17", + "release/install/install.sh": "7ae5c7c5e08fa4c025b0fe5f5b2da605caccdde1a0aefcec148a272530c260e9", + "src/materialization_worker.rs": "5124af6f9df654761bfd09cd46fbf68f9dacb4efc7d0dfc11b9b9ddcb9bc386e", + "README.md": "0f784e2a63656208ae19137e3d745dcc7c3f3132a404f417a3697d836c620323", + "docs/release.md": "6c673b23376ad03a2c1c26c2c7ab01351a56c54ed71cbbfe084b1c073d1d05db" + } + }, + "failed_attempts": [ + "first-compile.log", + "second-compile.log", + "xtask-attempt-3.log", + "pax-compile-failure.log", + "artifact-attempt-1.log" + ], + "limits": [ + "DeveloperMac26.6.2notcleanconsumer13.3", + "NativeWindowsCInotyet", + "NoDeveloperIDnotarization", + "No merge/release/deploy/restart" + ], + "artifact_attempt_2": { + "result": "pass", + "exit": 0, + "duration_seconds": 348.5107852909714, + "archive_sha256": "aaf4b6ca5eb8e7c5b2d510b83102c28a2125bd4e601105eaaf6c451238227e77", + "source_product_sha": "a2005dbb37a9ab914ea97b2735825b5286af5b6e", + "metadata_sha_argument": "71dc27e92857c4215bbbe583f104de7f341dc816", + "qualification": "excluded from finalsourceboundqualification: approvalSHA argument provisional", + "verified": [ + "sourceOpenSSLhash", + "ARM64 minos<=13.3 fourMachO", + "strict signatures", + "8files/5payloadhashes", + "relocatedrealinstall", + "graphbuild/reopen/diskBM25search", + "realLOADfts/officialbytes", + "wikiCLIHTTPMCP", + "missing/corrupt/incompatiblebothdryrealrejectpreserveinstalledbytes" + ] + }, + "final_source_sha": "855c7e282a27de4afbad2d4252d7692de4b42ecb", + "final_local_checks": { + "xtask": { + "passed": 55, + "skipped": 0 + }, + "fmt": 0, + "strict_workspace_clippy": 0, + "source_bound_archive_pipeline": 0 + }, + "final_review": "final-review.md GREEN boundedhandoff", + "full_native_suite": { + "status": "passed", + "source_sha": "855c7e282a27de4afbad2d4252d7692de4b42ecb", + "exit": 0, + "passed": 1356, + "failed": 0, + "ignored": 4, + "measured": 0, + "filtered_out": 0, + "report": "native-full-report.json", + "summary": "native-full-summary.json" + }, + "hosted_ci": { + "status": "failed", + "run_id": 40474966274, + "head_sha": "1f6859618140b3f434475b56ab2f2fa470976519", + "both_native_artifact_jobs": "skipped", + "summary": "ci-40474966274-summary.json" + } +} \ No newline at end of file diff --git a/docs/specs/CBG-PLAT-01/execution/cod54/ci-40474966274-failed.log.gz b/docs/specs/CBG-PLAT-01/execution/cod54/ci-40474966274-failed.log.gz new file mode 100644 index 00000000..4dfb4eed Binary files /dev/null and b/docs/specs/CBG-PLAT-01/execution/cod54/ci-40474966274-failed.log.gz differ diff --git a/docs/specs/CBG-PLAT-01/execution/cod54/ci-40474966274-summary.json b/docs/specs/CBG-PLAT-01/execution/cod54/ci-40474966274-summary.json new file mode 100644 index 00000000..4af19e81 --- /dev/null +++ b/docs/specs/CBG-PLAT-01/execution/cod54/ci-40474966274-summary.json @@ -0,0 +1,42 @@ +{ + "run_id": 40474966274, + "url": "https://github.com/rabii-chaarani/codebaseGraph/actions/runs/40474966274", + "head_sha": "1f6859618140b3f434475b56ab2f2fa470976519", + "conclusion": "failure", + "failed_jobs": [ + { + "name": "native (macos-arm64) / native tests (macos-arm64, unit)", + "databaseId": 122091021485, + "conclusion": "failure", + "url": "https://github.com/rabii-chaarani/codebaseGraph/actions/runs/40474966274/job/122091021485" + }, + { + "name": "native (windows-x86_64) / native tests (windows-x86_64, integration, shard 3)", + "databaseId": 122091021897, + "conclusion": "failure", + "url": "https://github.com/rabii-chaarani/codebaseGraph/actions/runs/40474966274/job/122091021897" + }, + { + "name": "required", + "databaseId": 122110349093, + "conclusion": "failure", + "url": "https://github.com/rabii-chaarani/codebaseGraph/actions/runs/40474966274/job/122110349093" + } + ], + "artifact_jobs": "bothskippedbecausenativecohortfailures; nohostedartifact/qualificationpass", + "failures": [ + { + "test": "dc_s5_existing_old_current_managed_store_requires_one_confirmation_then_retires_old_generation", + "context": "native (macos-arm64) / native tests (macos-arm64, unit)\tUNKNOWN STEP\t2026-10-27T08:52:03.6426260Z ok\nnative (macos-arm64) / native tests (macos-arm64, unit)\tUNKNOWN STEP\t2026-10-27T08:53:15.8204940Z test storage::maintenance_collector_tests::dc_s5_existing_old_current_managed_store_requires_one_confirmation_then_retires_old_generation ... \nnative (macos-arm64) / native tests (macos-arm64, unit)\tUNKNOWN STEP\t2026-10-27T08:53:15.8210340Z thread 'storage::maintenance_collector_tests::dc_s5_existing_old_current_managed_store_requires_one_confirmation_then_retires_old_generation' (70800) panicked at src/storage/../../tests/support/storage_maintenance/collector.rs:3032:5:\nnative (macos-arm64) / native tests (macos-arm64, unit)\tUNKNOWN STEP\t2026-10-27T08:53:15.8259340Z single quiescence confirmation was not durably accepted for the actual old current destination: {\"active_generation\":\"12657-1793091126635946000-220\",\"active_generation_published_at_unix_ms\":1793091129456,\"cleanup_pending\":true,\"database_exists\":true,\"database_path\":\"/private/var/folders/n6/hn37q6pj7sj_9dmqqc2113800000gn/T/cgraph-s5-collector-12657-0000000000000005/repository/.codebaseGraph/storage/generations/gen-12657-1793091126635946000-220/graph.ldb\",\"error\":null,\"freshness\":\"unknown\",\"graph_readable\":true,\"logical_database_bytes\":6714158,\"maintenance\":{\"bytes\":{\"by_category\":{\"active_cache\":{\"allocated_bytes\":102400,\"logical_bytes\":98949},\"active_graph\":{\"allocated_bytes\":6742016,\"logical_bytes\":6714996},\"artifact_temporary\":{\"allocated_bytes\":null,\"logical_bytes\":null},\"coordination\":{\"allocated_bytes\":8192,\"logical_bytes\":2482},\"leased_generation\":{\"allocated_bytes\":6787072,\"logical_bytes\":6759929},\"legacy_staging\":{\"allocated_bytes\":null,\"logical_bytes\":null},\"live_run\":{\"allocated_bytes\":0,\"logical_bytes\":64},\"recovery_evidence\":{\"allocated_bytes\":0,\"logical_bytes\":64},\"retired_direct\":{\"allocated_bytes\":null,\"logical_bytes\":null},\"unknown\":{\"allocated_bytes\":73728,\"logical_bytes\":71292}},\"deferred\":{\"allocated_bytes\":6787072,\"logical_bytes\":6759993},\"potential_reclamation\":{\"allocated_bytes\":null,\"logical_bytes\":null},\"retained\":{\"allocated_bytes\":6852608,\"logical_bytes\":6816491},\"total\":{\"allocated_bytes\":13713408,\"logical_bytes\":13647776},\"unknown\":{\"allocated_bytes\":73728,\"logical_bytes\":71292}},\"errors\":[],\"inventory_status\":null,\"last_attempt\":{\"attempted_entries\":9,\"continuation_phase\":null,\"deleted_entries\":0,\"finished_at_unix_ms\":1793091195813,\"reclaimed_allocated_bytes\":null,\"started_at_unix_ms\":1793091195695,\"status\":\"deferred\"},\"measurement_complete\":false,\"observation_age_ms\":111,\"observation_complete\":false,\"observed_at_unix_ms\":1793091195703,\"pending\":true,\"reasons\":[\"legacy_quiescence_required\",\"maintenance_measurement_partial_lower_bounds\",\"unknown_entries_retained\",\"maintenance_admission_busy\",\"maintenance_candidate_deferred\",\"maintenance_observation_incomplete\",\"maintenance_stopped\"],\"requested\":true,\"running\":false},\"manifest_exists\":true,\"manifest_path\":\"/private/var/folders/n6/hn37q6pj7sj_9dmqqc2113800000gn/T/cgraph-s5-collector-12657-0000000000000005/repository/.codebaseGraph/storage/generations/gen-12657-1793091126635946000-220/manifest.json\",\"ok\":true,\"pending_runs\":0,\"physical_database_bytes\":7536640,\"refresh\":null,\"refresh_health\":{\"dirty_epoch\":null,\"effective_root\":null,\"freshness\":\"unknown\",\"last_error\":null,\"last_successful_reconciliation_unix_ms\":null,\"next_retry_unix_ms\":null,\"oldest_pending_unix_ms\":null,\"pending\":null,\"readiness\":\"unknown\",\"reconciled_epoch\":null,\"state\":\"unknown\",\"task_alive\":null},\"refresh_readiness\":\"unknown\",\"remediation\":null,\"repo_root\":\"/private/var/folders/n6/hn37q6pj7sj_9dmqqc2113800000gn/T/cgraph-s5-collector-12657-0000000000000005/repository\",\"storage_format\":\"managed_v2\",\"storage_root\":\"/private/var/folders/n6/hn37q6pj7sj_9dmqqc2113800000gn/T/cgraph-s5-collector-12657-0000000000000005/repository/.codebaseGraph/storage\",\"total_nodes\":138,\"writable\":true}\nnative (macos-arm64) / native tests (macos-arm64, unit)\tUNKNOWN STEP\t2026-10-27T08:53:15.8303830Z retained failed storage qualification fixture: /private/var/folders/n6/hn37q6pj7sj_9dmqqc2113800000gn/T/cgraph-s5-collector-12657-0000000000000006/repository\nnative (macos-arm64) / native tests (macos-arm64, unit)\tUNKNOWN STEP\t2026-10-27T08:53:15.8307720Z retained failed storage qualification fixture: /private/var/folders/n6/hn37q6pj7sj_9dmqqc2113800000gn/T/cgraph-s5-collector-12657-0000000000000005/repository\nnative (macos-arm64) / native tests (macos-arm64, unit)\tUNKNOWN STEP\t2026-10-27T08:53:15.8308700Z FAILED\nnative (macos-arm64) / native tests (macos-arm64, unit)\tUNKNOWN STEP\t2026-10-27T08:53:24.1805480Z test storage::maintenance_collector_tests::dc_s5_foreign_leaf_stays_at_its_original_path_when_owned_neighbor_is_unreferenced ... ok\nnative (macos-arm64) / native tests (macos-arm64, unit)\tUNKNOWN STEP\t2026-10-27T08:53:37.0256930Z test storage::maintenance_collector_tests::dc_s5_former_default_direct_is_retired_and_explicit_rebuild_creates_fresh_incarnation ... ok\nnative (macos-arm64) / native tests (macos-arm64, unit)\tUNKNOWN STEP\t2026-10-27T08:53:40.1883810Z test storage::maintenance_collector_tests::dc_s5_genuine_multileaf_staging_without_terminal_authority_is_preserved ... ok\nnative (macos-arm64) / native tests (macos-arm64, unit)\tUNKNOWN STEP\t2026-10-27T08:54:46.3066850Z test storage::maintenance_collector_tests::dc_s5_incomplete_copied_orphan_staging_is_preserved_even_with_confirmation ... ok\nnative (macos-arm64) / native tests (macos-arm64, unit)\tUNKNOWN STEP\t2026-10-27T08:54:49.1827950Z test storage::maintenance_collector_tests::dc_s5_permission_failure_is_reported_and_retry_without_edit_reclaims_owned_key ... canonical-only fixture preparation: 1 known completed run(s) removed by established compatibility cleanup; this does not qualify terminal GC\nnative (macos-arm64) / native tests (macos-arm64, unit)\tUNKNOWN STEP\t2026-10-27T08:54:51.6082130Z canonical-only fixture preparation: 1 known completed run(s) removed by established compatibility cleanup; this does not qualify terminal GC" + }, + { + "test": "bounded_production_chunks_reopen_with_primary_keys_connectors_and_search", + "context": "native (windows-x86_64) / native tests (windows-x86_64, integration, shard 3)\tUNKNOWN STEP\t2026-10-27T08:56:45.0417441Z test bounded_production_chunks_reopen_with_primary_keys_connectors_and_search ... chunked native build resources: {\"database_write\":197021696,\"parse\":23124171,\"search_index\":1048568,\"staging\":2053466}\nnative (windows-x86_64) / native tests (windows-x86_64, integration, shard 3)\tUNKNOWN STEP\t2026-10-27T08:56:45.0418684Z \nnative (windows-x86_64) / native tests (windows-x86_64, integration, shard 3)\tUNKNOWN STEP\t2026-10-27T08:56:45.0419526Z thread 'bounded_production_chunks_reopen_with_primary_keys_connectors_and_search' (4812) panicked at tests\\copy_phase_process.rs:417:5:\nnative (windows-x86_64) / native tests (windows-x86_64, integration, shard 3)\tUNKNOWN STEP\t2026-10-27T08:56:45.0420395Z assertion `left == right` failed\nnative (windows-x86_64) / native tests (windows-x86_64, integration, shard 3)\tUNKNOWN STEP\t2026-10-27T08:56:45.0420788Z left: 53\nnative (windows-x86_64) / native tests (windows-x86_64, integration, shard 3)\tUNKNOWN STEP\t2026-10-27T08:56:45.0421114Z right: 56\nnative (windows-x86_64) / native tests (windows-x86_64, integration, shard 3)\tUNKNOWN STEP\t2026-10-27T08:56:45.0421607Z note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace\nnative (windows-x86_64) / native tests (windows-x86_64, integration, shard 3)\tUNKNOWN STEP\t2026-10-27T08:56:45.0422254Z FAILED\nnative (windows-x86_64) / native tests (windows-x86_64, integration, shard 3)\tUNKNOWN STEP\t2026-10-27T08:56:45.0422419Z \nnative (windows-x86_64) / native tests (windows-x86_64, integration, shard 3)\tUNKNOWN STEP\t2026-10-27T08:56:45.0422588Z failures:\nnative (windows-x86_64) / native tests (windows-x86_64, integration, shard 3)\tUNKNOWN STEP\t2026-10-27T08:56:45.0422755Z \nnative (windows-x86_64) / native tests (windows-x86_64, integration, shard 3)\tUNKNOWN STEP\t2026-10-27T08:56:45.0422914Z failures:\nnative (windows-x86_64) / native tests (windows-x86_64, integration, shard 3)\tUNKNOWN STEP\t2026-10-27T08:56:45.0423436Z bounded_production_chunks_reopen_with_primary_keys_connectors_and_search" + } + ], + "existing_work": { + "windows": "COD-98completedseparatePR157verifiedidentityfixnotinCOD54baseline. Donotreopenclosureorinfermerge; sourceunchangedinCOD54.", + "macos": "storagecollectorquiescenceconfirmationassertion; localfullsuitepasses. Rootcauseandintroducednessnotestablished." + }, + "limits": "Read-onlyCItriageonly; no sourcefix,retry,assertionweaken,scopeexpansionorqualificationwaiver." +} \ No newline at end of file diff --git a/docs/specs/CBG-PLAT-01/execution/cod54/claim-reconciliation.json b/docs/specs/CBG-PLAT-01/execution/cod54/claim-reconciliation.json new file mode 100644 index 00000000..55bd1bf9 --- /dev/null +++ b/docs/specs/CBG-PLAT-01/execution/cod54/claim-reconciliation.json @@ -0,0 +1,23 @@ +{ + "source_sha": "855c7e282a27de4afbad2d4252d7692de4b42ecb", + "model_scope": "older original checkout; not target-SHA-bound", + "claims_checked": [ + "resp-16", + "resp-17", + "resp-255", + "resp-256", + "resp-257", + "resp-258", + "resp-113" + ], + "result": "intendedresponsibilitiesunchanged existing-claimrepair; noaddedclaims/anchors/topology", + "executed_reports": [ + "final-xtask.junit.xml", + "source-bound-artifact.json" + ], + "not_performed": [ + "no targetimplementationfingerprint ingestion intoolder-rootmodel", + "no globaldriftreconcile coveringunrelatedscopes" + ], + "pending": "reconcilearchitecturalevidenceagainstintendedsourcewhenmodelrootavailable" +} \ No newline at end of file diff --git a/docs/specs/CBG-PLAT-01/execution/cod54/clippy-final.json b/docs/specs/CBG-PLAT-01/execution/cod54/clippy-final.json new file mode 100644 index 00000000..f149da0e --- /dev/null +++ b/docs/specs/CBG-PLAT-01/execution/cod54/clippy-final.json @@ -0,0 +1,13 @@ +{ + "source_sha": "855c7e282a27de4afbad2d4252d7692de4b42ecb", + "command": "cargo clippy --workspace --all-targets --all-features --locked -- -D warnings", + "exit": 0, + "duration_seconds": 6.04125641589053, + "environment": { + "os": "macOS26.6.2", + "build": "25G83", + "arch": "arm64" + }, + "toolchain": "rustc1.96.0", + "limits": "nativeMacdeveloperhost; notcleanconsumerqualification" +} \ No newline at end of file diff --git a/docs/specs/CBG-PLAT-01/execution/cod54/clippy-final.log b/docs/specs/CBG-PLAT-01/execution/cod54/clippy-final.log new file mode 100644 index 00000000..b877f82e --- /dev/null +++ b/docs/specs/CBG-PLAT-01/execution/cod54/clippy-final.log @@ -0,0 +1,4 @@ + Checking k-wiki v4.0.0 (/private/tmp/codebasegraph-cod54-macos-distribution/crates/k-wiki) + Checking xtask v4.0.0 (/private/tmp/codebasegraph-cod54-macos-distribution/crates/xtask) + Checking codebase-graph v4.0.0 (/private/tmp/codebasegraph-cod54-macos-distribution) + Finished `dev` profile [unoptimized + debuginfo] target(s) in 5.94s diff --git a/docs/specs/CBG-PLAT-01/execution/cod54/constants-and-assumptions.json b/docs/specs/CBG-PLAT-01/execution/cod54/constants-and-assumptions.json new file mode 100644 index 00000000..b2c7eb2d --- /dev/null +++ b/docs/specs/CBG-PLAT-01/execution/cod54/constants-and-assumptions.json @@ -0,0 +1,22 @@ +{ + "new_distribution_source": "OpenSSL3.6.2 official source pin, noexistingdependencyversionchanged", + "new_source_sha256": "aaf51a1fe064384f811daeaeb4ec4dce7340ec8bd893027eee676af31e83a04f", + "deployment_target": "13.3 already approved/existingworkflow, nowartifactdependenciesandbothbinarycommandsenforced", + "native_payload_count": { + "macOS": 5, + "previousmacOS": 2, + "Windows": 2 + }, + "archive_entries": { + "macOS": 8, + "previousmacOS": 5, + "Windows": 5 + }, + "unmodified": [ + "Ladybug0.19ABI", + "extensionbytes", + "Cargo/lock/dependencyversions", + "Windowsarchive", + "processcontainment" + ] +} \ No newline at end of file diff --git a/docs/specs/CBG-PLAT-01/execution/cod54/current-macos-policy.json b/docs/specs/CBG-PLAT-01/execution/cod54/current-macos-policy.json new file mode 100644 index 00000000..965c44d5 --- /dev/null +++ b/docs/specs/CBG-PLAT-01/execution/cod54/current-macos-policy.json @@ -0,0 +1,20 @@ +{ + "decision": "2026-10-27 user: \"well then just drop it and keep only current\", answering recommendation to add macOS13.3qualificationrunner vs existingcurrentmacOShostedCI.", + "spec_revision": "0.11", + "spec_sha": "8a410c7a22f5291f06a1cece5cd9e7c0be255e72", + "spec_url": "https://github.com/rabii-chaarani/codebaseGraph/blob/8a410c7a22f5291f06a1cece5cd9e7c0be255e72/docs/specs/CBG-PLAT-01/spec.md", + "approval_sha": "5c2e9a56d0fa87779fb31312562f4e53f902f0ac", + "approval_url": "https://github.com/rabii-chaarani/codebaseGraph/blob/5c2e9a56d0fa87779fb31312562f4e53f902f0ac/docs/specs/CBG-PLAT-01/approvals/d05a-current-macos-only.md", + "qualification": "currentstablemacOSAppleSilicononly", + "dropped": "macOS13.3execution/oldersupportqualification", + "unchanged": [ + "13.3deploymenttarget/buildmetadata", + "productsource855c7e2", + "OpenSSL/Ladybug/extensionversions/bytes", + "Windowsrequirements", + "allactualruntime/failureandCIgates", + "maintainerclosureowner" + ], + "passing_evidence": "no new test/CIpass assertedbypolicyamendment", + "scryer": "chg-p0s0ar" +} \ No newline at end of file diff --git a/docs/specs/CBG-PLAT-01/execution/cod54/execution-state.md b/docs/specs/CBG-PLAT-01/execution/cod54/execution-state.md new file mode 100644 index 00000000..f804d6c4 --- /dev/null +++ b/docs/specs/CBG-PLAT-01/execution/cod54/execution-state.md @@ -0,0 +1,15 @@ +# COD-54 local execution state + +Approved S03 only: spec rev0.10 `1a9db7b558b60248888408daa6268a3b45f2f0df`, R03/A05–A06; approval `71dc27e92857c4215bbbe583f104de7f341dc816`. + +Initial source main `34eac4f8644adc046093babd84ee1fdbffdae853`. Isolated branch `fix/cod-54-macos-distribution`; original dirty user checkout remains untouched. + +Initial proposal review: fresh GPT-6.1 Sol / High, `proposal-review.md`, GREEN revision2 after focused conditions verification. Runtime/installer lanes used GPT-6 Luna / Extra High with disjoint files. Coordinator now owns all files after handoff and stopping the installer worker before repairing its failing fixture helper. Final integrated review is fresh GPT-6.1 Sol / Max and remains pending until final source/evidence. + +Proportional existing-claim restoration: Scryer `chg-h7rgh8` approval/pickup, `chg-metn5v` execution/ownership, `chg-8zcgg3` cfg-only baseline warning hygiene. No new architectural claims/topology. Model/connected graph still refer to older user root; target-local graph discovery precedes source reads. No broad drift reconciliation or SHA-bound model evidence inferred. + +Failed attempts retained: initial partial-edit compile, fixture version parsing panic, and official OpenSSL source PAX global-comment handling. These are not passing evidence. The final source-only PAX comment metadata is validated and never extracted as a file; regular source paths/types remain confined. + +Implementation builds pinned OpenSSL3.6.2 (same observed producer version, not a product dependency/ABI upgrade), official source SHA `aaf51a1fe064384f811daeaeb4ec4dce7340ec8bd893027eee676af31e83a04f`, ARM64 target13.3; five checksummed macOS payload files/eight archive entries. Windows remains two checksummed binaries/five entries. Extension bytes/ABI, Cargo/dependency versions, build.rs, workflows and containment behavior are unchanged. One import cfg fixes a pre-existing release warning without changing materialization behavior. + +Actual clean Apple Silicon macOS13.3 and current-release final-installed-archive execution is still required and unexecuted. Deployment metadata/local developer-host tests do not qualify support. Ad-hoc signatures are not Developer ID/notarization. No other issue, package publication, merge, deployment, restart or aggregate closure is authorized. diff --git a/docs/specs/CBG-PLAT-01/execution/cod54/final-review.md b/docs/specs/CBG-PLAT-01/execution/cod54/final-review.md new file mode 100644 index 00000000..31f68fb9 --- /dev/null +++ b/docs/specs/CBG-PLAT-01/execution/cod54/final-review.md @@ -0,0 +1,75 @@ +# COD-54 / CBG-PLAT-01 S03 — final integrated review + +## Outcome + +**GREEN for the bounded implementation and local/native artifact handoff. No unresolved introduced P0–P3 defect was established.** Reviewed source: `855c7e282a27de4afbad2d4252d7692de4b42ecb`; baseline: `34eac4f8644adc046093babd84ee1fdbffdae853`. + +This is not support qualification, release/merge approval, COD-54 closure or aggregate closure. Clean minimum/current macOS qualification and the remaining full native/hosted CI gates are still pending. + +## Reviewer and authority + +Fresh independent reviewer: `openai-codex/gpt-6.1-sol`, thinking **max**, child `sub-b0e5e01b`, confirmed against the coordinator’s actual spawn call; no implementation context fork. Used `/Users/rabii/.codex/skills/adversarial-review-engineer/SKILL.md` and its engineering lenses. + +Approved scope: `docs/specs/CBG-PLAT-01/spec.md` revision **0.10** at `1a9db7b558b60248888408daa6268a3b45f2f0df`, **R03 / A05–A06**, approval record commit `71dc27e92857c4215bbbe583f104de7f341dc816`. Examined initial proposal review conditions **SC-01–SC-04**. PR: [#163](https://github.com/rabii-chaarani/codebaseGraph/pull/163). Neither PR creation nor this review changes closure ownership. + +Product source, tests, Git and Scryer were not changed by this reviewer; this report is the only reviewer file write. No reviewer Cargo/build/full-suite execution. No constants or behavior assumptions were changed by the reviewer. + +## Findings and focused fix verification + +**Confirmed unresolved findings: none.** No conditional code defect or unresolved design decision was established within the authorized scope. Platform/verification limits below remain material gates, not invented product defects. + +- Verified the repaired official-source extraction path accepts only the expected PAX comment metadata and does not extract it, while ordinary entries retain strict source-root/type/path validation. The current first-loop binding is mutable (`macos.rs:701`); the transient FR-01 observation from an older in-flight read is withdrawn. Inspected preserved E0596/PAX failure history and the passing PAX regression; they are not presented as final passing runs. +- Verified the installer mixed-version fixture helper now derives the version from the canonical SSL prefix/suffix; its final regression passes without weakened assertions. +- Focused review of `a2005dbb..855c7e28`: the sole source delta removes `&` from an already-borrowed `binary` argument to `change_dependency` (`macos.rs:168`). It changes no behavior and adds no lint suppression. Strict Clippy was rerun successfully. + +## Contracts traced in the final source + +1. **Supply/build:** `macos.rs:24–122`, `main.rs:425–461`. Exact official OpenSSL 3.6.2 source digest is checked before fresh-root extraction; same-source license is copied. Source build retains upstream defaults except approved shared/no-tests/no-apps/no-module configuration. Both Cargo binary commands receive the prepared `OPENSSL_LIB_DIR`, deployment target 13.3 and header padding; no new consumer Homebrew prerequisite or static-link/dependency/ABI change. +2. **Relocation/audit:** `macos.rs:125–238,267–337`. Binary imports are executable-relative; SSL-to-crypto and dylib identities are loader-relative. Rpaths are removed; unknown non-system imports, wrong architecture, excessive minimum OS or bad signatures fail. Mutation precedes ad-hoc re-signing/strict verification, then checksums. Native audit is confined to native macOS build/execution, not cross-target artifact-set validation. +3. **Inventory/provenance:** `main.rs:70–91,464–535,610–704,800–880`. One NativeTarget inventory produces five macOS payload checksum rows and eight regular flat archive entries. Windows retains two payloads/five entries; provenance remains schema 1. Archive and internal hashes cover final bytes. +4. **Installer:** `install.sh:82–194`. Exact categories/common constrained numeric version, no duplicate/unknown/path/missing rows, regular non-symlink payloads and all five hashes are validated before either executable probe or any destination write. Compiled probes run without inherited runtime overrides. Real install puts version-qualified runtime/license files before binaries using per-file temporary-copy/chmod/rename; dry run creates no destination. No all-file rollback or old-sidecar garbage collection is promised. +5. **Runtime evidence:** `main.rs:751–789,1779–1998,2001–2118,2321–2357,2430–2527`. Actual relocated installed pair is exercised, not just extracted binaries. All direct graph/wiki/MCP/HTTP runtime-child sites use the cleaned smoke environment and fixture home. Negative checks use the actual incompatible Mach-O load failure and compare every installed payload hash after both dry-run and real rejection. +6. **Scope:** `materialization_worker.rs` changes only the import cfg; all uses were already debug/test-only. Cargo/lock/dependency versions, extension assets/ABI, Windows behavior/workflows and process containment remain unchanged. + +## Executed evidence + +All evidence filenames below are under `/private/tmp/codebasegraph-cod54-review/` unless otherwise stated. + +### Coordinator-executed, reviewer-inspected + +- Baseline: 43 passing xtask tests. Final `final-xtask.log` / `final-xtask.junit.xml`: **55 tests, 0 failures, 0 errors, 0 skipped** after the final lint-only commit. Includes archive/checksum/cross-target contract, PAX extraction and installer dry-run/install/preflight preservation cases. Unit installer fixture binaries are shell scripts; native compatibility is established separately below. +- `clippy-final.json` / `.log`: `cargo clippy --workspace --all-targets --all-features --locked -- -D warnings`, **exit 0**, Rust 1.96.0, source `855c7e28`. Coordinator reports final formatting check **exit 0**; reviewer independently ran shell syntax and diff-whitespace checks. +- `source-bound-artifact.json` / `.log`: `cargo run -p xtask --locked -- native-artifact --target macos-arm64 --commit-sha 855c7e282a27de4afbad2d4252d7692de4b42ecb --output .../dist/cod54-source-bound`, **exit 0**, 309.5 seconds, macOS **26.6.2 / 25G83, ARM64**. It builds the official runtime and both optimized binaries, audits/signs/packages/extracts, dry-runs and really installs to a relocated spaced path, exercises graph/wiki/MCP/HTTP and rejects missing, corrupt and checksum-valid non-MachO runtime files in both installer modes with installed hashes unchanged. The negative dyld aborts in this successful log are expected preflight failures, not passing probes. +- Earlier provisional artifact runs were inspected as historical evidence only; their approval-SHA provenance labels are excluded from final artifact proof. The final run uses the actual implementation SHA. + +### Reviewer-executed / independently checked + +- Target-local graph semantic search/context preceded source inspection. Adjacent runtime paths were discovered with MCP graph; wiki memory/search and Scryer rules/model were consulted read-only. The configured wiki/Scryer/MCP graph refer to an older root checkout, so they are architectural/discovery context, not final-SHA behavior proof. Linear was not mutated. +- `bash -n release/install/install.sh`, `git diff --check`, and final product-source comparison against `855c7e28`: **exit 0**. Actual host read with `sw_vers`/`uname`: macOS 26.6.2 / 25G83, ARM64. +- Independently hashed the official source tarball: `aaf51a1fe064384f811daeaeb4ec4dce7340ec8bd893027eee676af31e83a04f`, matching the saved official release asset digest. Compared upstream archive `LICENSE.txt` to the supplied/shipped license: byte-identical, SHA-256 `7d5450cb2d142651b8afa315b5f238efc805dad827d91ba367d8516bc9d49e7a`; no top-level NOTICE file. +- Final archive `/private/tmp/codebasegraph-cod54-review/final-archive/codebase-graph-4.0.0-macos-arm64.tar.gz`: independently SHA-256 hashed as **`e5f32abbf8124584d5f6f2df403091c3323d16a1359a70f387fc99eeeb6e4282`**. Schema-1 provenance names exact source `855c7e282a27de4afbad2d4252d7692de4b42ecb`, version 4.0.0 and target macos-arm64 with matching filename/hash. +- Read tar directly without writing/extracting: exactly eight regular flat entries with prescribed modes; all five internal payload hashes match actual bytes and upstream license identity. +- Independently decoded all four native files’ Mach-O commands from the exact tar: thin ARM64, macOS minOS **13.3**, no LC_RPATH, both executables’ OpenSSL imports through `@executable_path`, SSL’s crypto import and dylib IDs through `@loader_path`, all other imports Apple system libraries. +- Preserved `final-archive/installed bin`: all five payloads byte-identical to the exact archive. Actual `otool -L/-D` confirms import/ID closure; `codesign --verify --strict` **exit 0** on all four native files. Independent `env -i` installed `codebase-graph --help` / `k-wiki --version` probes both **exit 0**, with controlled HOME/system PATH; wiki reports 4.0.0. These are real compiled binary probes. + +## Qualification, limits and remaining gates + +- **Clean-host A05 qualification remains open:** actual final installed archive execution on clean Apple Silicon **macOS 13.3 and a current macOS release**, without Homebrew/OpenSSL, has not been supplied. Deployment metadata and a developer-host pass cannot substitute. Do not close COD-54 or claim supported-platform qualification from this review. +- **Full native and hosted CI remain pending** at this report’s handoff. Passing targeted/local artifact checks do not imply those broader suites passed. Record any subsequent failures and evaluate introduced effects before merge/completion. +- Normal populated build/reopen/search exercises JSON and **disk-BM25**. The legacy-manifest branch executes real **LOAD fts** with fresh, hash-checked official bytes; native FTS indices are intentionally absent, so it does **not** prove a populated native-FTS-index query. No product fallback/assertion was weakened to hide that distinction. +- Ad-hoc signature verification is not Developer ID signing, notarization, legal certification or artifact authentication. Source/package checksums prove integrity, not a new signature policy. +- No full-file transaction, concurrent cohort upgrade guarantee, old-sidecar cleanup, static linking, source-install redesign or dependency/ABI upgrade was required or inferred. No new owner decision or waiver is implied. + +## Reviewed final source hashes (SHA-256) + +- `crates/xtask/src/main.rs`: `6610d3c571b2a39af7f50cc2703cb9a5cb5431573c0dd46af43dcb9669bd35dd` +- `crates/xtask/src/macos.rs`: `810255a2714e8aed31b77b26ac5260747fb68d5c6c5f85bc3e17757a97c1f0cf` +- `crates/xtask/src/macos_installer_tests.rs`: `10646723ce3db83a5a70e51769957214801d4ef7533362eab1035c31ce7fab17` +- `release/install/install.sh`: `7ae5c7c5e08fa4c025b0fe5f5b2da605caccdde1a0aefcec148a272530c260e9` +- `README.md`: `0f784e2a63656208ae19137e3d745dcc7c3f3132a404f417a3697d836c620323` +- `docs/release.md`: `6c673b23376ad03a2c1c26c2c7ab01351a56c54ed71cbbfe084b1c073d1d05db` +- `src/materialization_worker.rs`: `5124af6f9df654761bfd09cd46fbf68f9dacb4efc7d0dfc11b9b9ddcb9bc386e` + +## Handoff + +Proceed with the bounded implementation/local-artifact handoff, preserving the pending qualification and broader verification gates above. No unresolved reviewer defect requires a source fix. Re-review any later material source change against this snapshot. Reusable knowledge assessment: no new memory candidate is warranted; this task-specific evidence does not replace the existing repository single-artifact-recipe invariant. diff --git a/docs/specs/CBG-PLAT-01/execution/cod54/final-snapshot.json b/docs/specs/CBG-PLAT-01/execution/cod54/final-snapshot.json new file mode 100644 index 00000000..aee5b186 --- /dev/null +++ b/docs/specs/CBG-PLAT-01/execution/cod54/final-snapshot.json @@ -0,0 +1,28 @@ +{ + "source_sha": "855c7e282a27de4afbad2d4252d7692de4b42ecb", + "source_files": { + "crates/xtask/src/main.rs": "6610d3c571b2a39af7f50cc2703cb9a5cb5431573c0dd46af43dcb9669bd35dd", + "crates/xtask/src/macos.rs": "810255a2714e8aed31b77b26ac5260747fb68d5c6c5f85bc3e17757a97c1f0cf", + "crates/xtask/src/macos_installer_tests.rs": "10646723ce3db83a5a70e51769957214801d4ef7533362eab1035c31ce7fab17", + "release/install/install.sh": "7ae5c7c5e08fa4c025b0fe5f5b2da605caccdde1a0aefcec148a272530c260e9", + "src/materialization_worker.rs": "5124af6f9df654761bfd09cd46fbf68f9dacb4efc7d0dfc11b9b9ddcb9bc386e" + }, + "archive_sha256": "e5f32abbf8124584d5f6f2df403091c3323d16a1359a70f387fc99eeeb6e4282", + "approved_spec_sha": "1a9db7b558b60248888408daa6268a3b45f2f0df", + "native_host": { + "os": "macOS26.6.2", + "build": "25G83", + "arch": "arm64" + }, + "xtask": { + "pass": 55, + "skip": 0, + "exit": 0 + }, + "fmt": 0, + "clippy": 0, + "native_archive": 0, + "native_full_suite": "running", + "hosted_ci": "queued", + "clean_consumer_minimum_current": "notexecuted" +} \ No newline at end of file diff --git a/docs/specs/CBG-PLAT-01/execution/cod54/final-xtask.junit.xml b/docs/specs/CBG-PLAT-01/execution/cod54/final-xtask.junit.xml new file mode 100644 index 00000000..e2a1e17a --- /dev/null +++ b/docs/specs/CBG-PLAT-01/execution/cod54/final-xtask.junit.xml @@ -0,0 +1,115 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/specs/CBG-PLAT-01/execution/cod54/final-xtask.log b/docs/specs/CBG-PLAT-01/execution/cod54/final-xtask.log new file mode 100644 index 00000000..638a8a7e --- /dev/null +++ b/docs/specs/CBG-PLAT-01/execution/cod54/final-xtask.log @@ -0,0 +1,62 @@ + Compiling xtask v4.0.0 (/private/tmp/codebasegraph-cod54-macos-distribution/crates/xtask) + Finished `test` profile [unoptimized + debuginfo] target(s) in 0.59s +──────────── + Nextest run ID 5ea23964-6958-4af7-bb37-cc476d71ff74 with nextest profile: cod54 + Starting 55 tests across 1 binary + PASS [ 0.013s] ( 1/55) xtask::bin/xtask macos::tests::dependency_parser_preserves_paths_with_spaces + PASS [ 0.010s] ( 2/55) xtask::bin/xtask macos::tests::dylib_dependency_parser_filters_only_matching_install_id + PASS [ 0.012s] ( 3/55) xtask::bin/xtask macos::tests::minimum_version_parser_reads_macos_build_and_legacy_commands + PASS [ 0.010s] ( 4/55) xtask::bin/xtask macos::tests::rpath_parser_reads_and_rejects_malformed_entries + PASS [ 0.011s] ( 5/55) xtask::bin/xtask macos::tests::source_archive_accepts_comment_metadata_without_extracting_it + PASS [ 0.952s] ( 6/55) xtask::bin/xtask macos_installer_tests::dry_run_validates_all_payloads_without_creating_destination + PASS [ 0.463s] ( 7/55) xtask::bin/xtask macos_installer_tests::invalid_inventory_rows_and_mixed_versions_preserve_destination + PASS [ 0.721s] ( 8/55) xtask::bin/xtask macos_installer_tests::missing_corrupt_and_symlink_payloads_preserve_existing_destination_files + PASS [ 0.526s] ( 9/55) xtask::bin/xtask macos_installer_tests::nonzero_binary_probe_preserves_existing_destination_files + PASS [ 0.790s] (10/55) xtask::bin/xtask macos_installer_tests::real_install_places_verified_runtime_license_and_binaries_in_spaced_directory + PASS [ 0.010s] (11/55) xtask::bin/xtask native_tests::shards::tests::empty_shard_never_runs_an_unfiltered_suite + PASS [ 0.012s] (12/55) xtask::bin/xtask native_tests::shards::tests::selected_exact_filters_execute_the_expected_real_harness_cases + PASS [ 0.018s] (13/55) xtask::bin/xtask native_tests::shards::tests::shards_cover_every_listed_case_once_and_bound_windows_arguments + PASS [ 0.011s] (14/55) xtask::bin/xtask native_tests::tests::captured_cli_survives_cargo_replacing_its_output + PASS [ 0.010s] (15/55) xtask::bin/xtask native_tests::tests::partitions_every_test_target_and_keeps_new_integration_targets + PASS [ 0.009s] (16/55) xtask::bin/xtask native_tests::tests::windows_regressions_precede_the_complete_unfiltered_suite + PASS [ 0.010s] (17/55) xtask::bin/xtask release::tests::metadata_rejects_inconsistent_versions + PASS [ 0.008s] (18/55) xtask::bin/xtask release::tests::notes_only_include_the_exact_release + PASS [ 0.008s] (19/55) xtask::bin/xtask release::tests::notes_reject_missing_duplicate_and_empty_sections + PASS [ 0.459s] (20/55) xtask::bin/xtask release::workflow_tests::ci_run_recovery_dry_run_validates_without_authorizing_publication + PASS [ 0.558s] (21/55) xtask::bin/xtask release::workflow_tests::ordinary_commit_cannot_authorize_release_and_tagged_retry_still_can + PASS [ 0.291s] (22/55) xtask::bin/xtask release::workflow_tests::proposal_reports_pending_release_then_proceeds_after_finalization + PASS [ 0.322s] (23/55) xtask::bin/xtask release::workflow_tests::publisher_rejects_conflicting_tags_and_does_not_regress_latest + PASS [ 0.333s] (24/55) xtask::bin/xtask release::workflow_tests::publisher_targets_original_sha_and_is_idempotent + PASS [ 0.035s] (25/55) xtask::bin/xtask release::workflow_tests::recovery_requires_one_target_and_forbids_ci_run_rebuilds + PASS [ 1.681s] (26/55) xtask::bin/xtask release::workflow_tests::verified_release_rejects_failed_ci_and_untrusted_run_identity + PASS [ 2.475s] (27/55) xtask::bin/xtask release::workflow_tests::verified_release_rejects_untrusted_prs_and_removed_history + PASS [ 0.267s] (28/55) xtask::bin/xtask release::workflow_tests::verified_release_survives_main_advancing_and_ignores_other_pending_releases + PASS [ 0.015s] (29/55) xtask::bin/xtask tests::archive_contract_rejects_unexpected_entries + PASS [ 0.019s] (30/55) xtask::bin/xtask tests::complete_artifact_set_rejects_mixed_provenance + PASS [ 0.009s] (31/55) xtask::bin/xtask tests::crate_size_validation_enforces_the_registry_limit + PASS [ 0.459s] (32/55) xtask::bin/xtask tests::installer_rejects_removed_hosts_before_reading_archives_or_writing_destinations + PASS [ 0.011s] (33/55) xtask::bin/xtask tests::macos_smoke_commands_use_a_fixture_home_and_system_path + PASS [ 0.010s] (34/55) xtask::bin/xtask tests::native_capture_requires_unique_source_bound_normal_cli_artifact + PASS [ 0.009s] (35/55) xtask::bin/xtask tests::native_capture_reuses_the_exact_test_profile_and_features + PASS [ 0.013s] (36/55) xtask::bin/xtask tests::native_payload_integrity_covers_runtime_files_and_preserves_windows_inventory + PASS [ 0.009s] (37/55) xtask::bin/xtask tests::native_target_contract_maps_names_and_binaries + PASS [ 0.023s] (38/55) xtask::bin/xtask tests::native_test_failure_cannot_authorize_artifact_production + PASS [ 0.009s] (39/55) xtask::bin/xtask tests::release_identity_validation_is_strict + PASS [ 0.009s] (40/55) xtask::bin/xtask tests::windows_native_tests_forward_bundled_extensions + PASS [ 0.016s] (41/55) xtask::bin/xtask tests::workflow_policy_rejects_automatic_rebuild_recovery + PASS [ 0.016s] (42/55) xtask::bin/xtask tests::workflow_policy_rejects_direct_release_push_trigger + PASS [ 0.016s] (43/55) xtask::bin/xtask tests::workflow_policy_rejects_github_sha_for_release_identity + PASS [ 0.016s] (44/55) xtask::bin/xtask tests::workflow_policy_rejects_missing_ci_success_guard + PASS [ 0.029s] (45/55) xtask::bin/xtask tests::workflow_policy_rejects_missing_crate_publish_retry + PASS [ 0.022s] (46/55) xtask::bin/xtask tests::workflow_policy_rejects_missing_crate_size_gates + PASS [ 0.016s] (47/55) xtask::bin/xtask tests::workflow_policy_rejects_missing_repository_release_gate + PASS [ 0.016s] (48/55) xtask::bin/xtask tests::workflow_policy_rejects_missing_triggering_run_binding + PASS [ 0.016s] (49/55) xtask::bin/xtask tests::workflow_policy_rejects_release_queue_replacement + PASS [ 0.016s] (50/55) xtask::bin/xtask tests::workflow_policy_rejects_unconditional_release_publication + PASS [ 0.029s] (51/55) xtask::bin/xtask tests::workflow_policy_rejects_unvalidated_publishers + PASS [ 0.016s] (52/55) xtask::bin/xtask tests::workflow_policy_requires_ci_completion_release_trigger + PASS [ 0.029s] (53/55) xtask::bin/xtask tests::workflow_policy_requires_publication_revalidation + PASS [ 0.008s] (54/55) xtask::bin/xtask tests::workflow_yaml_validation_rejects_unindented_heredoc_body + PASS [ 0.023s] (55/55) xtask::bin/xtask tests::workflows_reject_removed_platform_targets +──────────── + Summary [ 10.906s] 55 tests run: 55 passed, 0 skipped diff --git a/docs/specs/CBG-PLAT-01/execution/cod54/handoff-state.json b/docs/specs/CBG-PLAT-01/execution/cod54/handoff-state.json new file mode 100644 index 00000000..4483d4a0 --- /dev/null +++ b/docs/specs/CBG-PLAT-01/execution/cod54/handoff-state.json @@ -0,0 +1,46 @@ +{ + "scope": "COD-54 only R03 A05-A06", + "approved_spec_sha": "1a9db7b558b60248888408daa6268a3b45f2f0df", + "approval_commit": "71dc27e92857c4215bbbe583f104de7f341dc816", + "product_source_sha": "855c7e282a27de4afbad2d4252d7692de4b42ecb", + "local_review": "GREEN independentGPT6.1SolMaxnofindings", + "local_checks": { + "xtask": { + "passed": 55, + "skipped": 0 + }, + "fmt": 0, + "strict_workspace_clippy": 0, + "source_bound_archive_pipeline": 0 + }, + "archive_sha256": "e5f32abbf8124584d5f6f2df403091c3323d16a1359a70f387fc99eeeb6e4282", + "full_native_suite": { + "status": "passed", + "source_sha": "855c7e282a27de4afbad2d4252d7692de4b42ecb", + "exit": 0, + "passed": 1356, + "failed": 0, + "ignored": 4, + "measured": 0, + "filtered_out": 0, + "report": "native-full-report.json", + "summary": "native-full-summary.json" + }, + "hosted_ci": { + "status": "failed", + "run_id": 40474966274, + "head_sha": "1f6859618140b3f434475b56ab2f2fa470976519", + "both_native_artifact_jobs": "skipped", + "summary": "ci-40474966274-summary.json" + }, + "consumer_qualification": "currentstablemacOSAppleSiliconactualOS/build+noexternalOpenSSLruntime evidence required; no13.3gate", + "closure": "COD54notDone,noaggregateclosure", + "fts_limit": "realLOADfts+officialbytes; populateddiskBM25search, notpopulatednativeFTSindex", + "policy_amendment": { + "spec_revision": "0.11", + "spec_sha": "8a410c7a22f5291f06a1cece5cd9e7c0be255e72", + "approval_sha": "5c2e9a56d0fa87779fb31312562f4e53f902f0ac", + "scope": "currentstablemacOSonly;13.3executiondropped", + "original_implementation_approval_preserved": true + } +} \ No newline at end of file diff --git a/docs/specs/CBG-PLAT-01/execution/cod54/initial-evidence.json b/docs/specs/CBG-PLAT-01/execution/cod54/initial-evidence.json new file mode 100644 index 00000000..bb062ee2 --- /dev/null +++ b/docs/specs/CBG-PLAT-01/execution/cod54/initial-evidence.json @@ -0,0 +1,24 @@ +{ + "approved_spec_sha": "1a9db7b558b60248888408daa6268a3b45f2f0df", + "approval_commit": "71dc27e92857c4215bbbe583f104de7f341dc816", + "baseline": "34eac4f8644adc046093babd84ee1fdbffdae853", + "source_status": "no product diffs", + "baseline_warning": "release-only unused crate::storage::atomic::write_json_atomically in src/materialization_worker.rs:13; pre-existing; source unchanged", + "openssl_source_sha256": "aaf51a1fe064384f811daeaeb4ec4dce7340ec8bd893027eee676af31e83a04f", + "openssl_probe_minos": "13.3", + "baseline_import_failure_exit": 134, + "baseline_binaries": { + "codebase-graph": { + "sha256": "05736d64ae262deb3a38b3ed464534c2e191e483a3aa1d4476bdf98e464cec2b", + "path": "/private/tmp/codebasegraph-cod54-review/baseline-bins/codebase-graph", + "imports": "/private/tmp/codebasegraph-cod54-review/baseline-bins/codebase-graph:\n\t/private/tmp/codebasegraph-cod54-review/openssl-probe-build/install/lib/libssl.3.dylib (compatibility version 3.0.0, current version 3.0.0)\n\t/private/tmp/codebasegraph-cod54-review/openssl-probe-build/install/lib/libcrypto.3.dylib (compatibility version 3.0.0, current version 3.0.0)\n\t/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation (compatibility version 150.0.0, current version 5026.5.4)\n\t/System/Library/Frameworks/CoreServices.framework/Versions/A/CoreServices (compatibility version 1.0.0, current version 1226.0.0)\n\t/usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 2100.43.0)\n\t/usr/lib/libiconv.2.dylib (compatibility version 7.0.0, current version 7.0.0)\n\t/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1356.0.0)\n", + "minos": "/private/tmp/codebasegraph-cod54-review/baseline-bins/codebase-graph:\nLoad command 11\n cmd LC_BUILD_VERSION\n cmdsize 32\n platform MACOS\n minos 13.3\n sdk 26.5\n ntools 1\n tool LD\n version 1267.0\n" + }, + "k-wiki": { + "sha256": "7b19765973e76685689655456003b6c1c68bd5f4f87e995c3af7364d95b082f7", + "path": "/private/tmp/codebasegraph-cod54-review/baseline-bins/k-wiki", + "imports": "/private/tmp/codebasegraph-cod54-review/baseline-bins/k-wiki:\n\t/private/tmp/codebasegraph-cod54-review/openssl-probe-build/install/lib/libssl.3.dylib (compatibility version 3.0.0, current version 3.0.0)\n\t/private/tmp/codebasegraph-cod54-review/openssl-probe-build/install/lib/libcrypto.3.dylib (compatibility version 3.0.0, current version 3.0.0)\n\t/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation (compatibility version 150.0.0, current version 5026.5.4)\n\t/System/Library/Frameworks/CoreServices.framework/Versions/A/CoreServices (compatibility version 1.0.0, current version 1226.0.0)\n\t/usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 2100.43.0)\n\t/usr/lib/libiconv.2.dylib (compatibility version 7.0.0, current version 7.0.0)\n\t/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1356.0.0)\n", + "minos": "/private/tmp/codebasegraph-cod54-review/baseline-bins/k-wiki:\nLoad command 11\n cmd LC_BUILD_VERSION\n cmdsize 32\n platform MACOS\n minos 13.3\n sdk 26.5\n ntools 1\n tool LD\n version 1267.0\n" + } + } +} \ No newline at end of file diff --git a/docs/specs/CBG-PLAT-01/execution/cod54/installer-ownership-takeover.json b/docs/specs/CBG-PLAT-01/execution/cod54/installer-ownership-takeover.json new file mode 100644 index 00000000..aa5b844c --- /dev/null +++ b/docs/specs/CBG-PLAT-01/execution/cod54/installer-ownership-takeover.json @@ -0,0 +1,9 @@ +{ + "reason": "worker stopped responding to queued diagnostics; coordinator explicitly stopped child before edits", + "writable_files": [ + "release/install/install.sh", + "crates/xtask/src/macos_installer_tests.rs" + ], + "fix": "fixture version helper extracts exact SSL prefix/suffix, no production behavior change", + "time": "2026-10-27T08:04Z" +} \ No newline at end of file diff --git a/docs/specs/CBG-PLAT-01/execution/cod54/installer-research.md b/docs/specs/CBG-PLAT-01/execution/cod54/installer-research.md new file mode 100644 index 00000000..834a217f --- /dev/null +++ b/docs/specs/CBG-PLAT-01/execution/cod54/installer-research.md @@ -0,0 +1,23 @@ +# COD-54 / CBG-PLAT-01 S03 R03 A05–A06 — installer research + +Read-only investigation of `/private/tmp/codebasegraph-cod54-macos-distribution` at baseline `34eac4f8644adc046093babd84ee1fdbffdae853`, approval `71dc27e92857c4215bbbe583f104de7f341dc816`; canonical scope is `canonical-spec.md`. No product changes, Git writes, Cargo builds, or Scryer changes. + +## Recommendation + +Preserve the current flat archive. Add version-qualified flat payload sidecars for OpenSSL 3.6.2—`libcodebasegraph-ssl-3.6.2.dylib` and `libcodebasegraph-crypto-3.6.2.dylib`—and the source `LICENSE` file. The inspected source tree has a top-level LICENSE; do not assume an Apache NOTICE file exists. Put them beside the two binaries and current installer/checksum files. + +Make the Rust `NativeTarget`/package manifest the inventory authority. Generate `checksums.txt` from it and cover both binaries, both dylibs, and LICENSE. Extend archive construction, exact-entry validation, checksum validation, and provenance/artifact tests from the same manifest. The public installer must not rely on internal `provenance.json`; strictly parse checksums against the exact binary names and required dylib/license categories, reject duplicates/unknown/path entries and missing files, and verify every hash before probing or writing. Avoid a second hard-coded dylib version in shell; have tests ensure its accepted payload equals the Rust archive inventory. + +Install the two executables and dylibs together in `bin-dir`, colocated for relative loading. Keep prevalidation of all payload/checksums and both executable probes before destination mutation. Keep the existing per-file temp-and-rename behavior only: do not promise multi-file rollback, which the spec excludes. Dry run must create/change nothing. Test successful dry-run and real install; missing/corrupt dylibs or license; invalid/duplicate/unknown checksum rows; incompatible dylib/probe failure; unsupported host; and preservation of preexisting destination files for all validation failures. Avoid asserting rollback after a later individual rename failure. + +## Evidence and boundaries + +- Existing archive is exactly five flat files: `codebase-graph`, `k-wiki`, `checksums.txt`, `install.sh`, `install.ps1`. Rust builds the archive, writes whole-archive `.sha256` and internal commit/version/target provenance, then extracts, validates, runs installer dry-run and smoke tests. +- Existing `install.sh` rejects unsupported OS/architecture before archive reads/writes; validates checksums for both binaries and probes `--help` / `--version` before destination creation; dry-run returns without writes. Real install separately stages and renames each binary. There is a direct shell-process test for unsupported hosts, but no matching current source tests for ordinary dry-run/real install or dylib corrupt/missing/incompatible cases. +- `build.rs` links dynamic `ssl` and `crypto`, checking `OPENSSL_LIB_DIR` then Homebrew paths. This does not package/rewrite dylibs. Native workflow sets deployment target 13.3 and uses `macos-latest`; metadata alone does not qualify macOS 13.3 execution. +- Review staging contains official OpenSSL 3.6.2 source metadata/digest `aaf51a1fe064384f811daeaeb4ec4dce7340ec8bd893027eee676af31e83a04f` for `openssl-3.6.2.tar.gz`. The staged source-build probe dylibs are arm64 and report `minos 13.3`; their IDs/dependencies still contain the staging absolute install path and need rewriting before packaging. This supports same-version source-build feasibility, not final relink/runtime qualification. Parent additionally reports an optimized baseline build proved relink/sign operations; that was not independently run in this investigation. Source version remains 3.6.2; no upgrade is implied. +- Required final checks: inspect final release executables and both dylibs with `otool -L` and load commands; reject absolute producer/staging paths; verify relative SSL/crypto resolution and signatures after mutation. Apple `dyld(1)` documents `@executable_path`, `@loader_path`, `@rpath`; `install_name_tool(1)` documents `-change`, `-id`, `-add_rpath`. I consulted these local Apple man pages and [Apple Run-Path Dependent Libraries](https://developer.apple.com/library/archive/documentation/DeveloperTools/Conceptual/DynamicLibraries/100-Articles/RunpathDependentLibraries.html). +- Current native artifact smoke inherits environment variables. It does not clear `DYLD_LIBRARY_PATH`, `DYLD_FRAMEWORK_PATH`, `DYLD_FALLBACK_LIBRARY_PATH`, or `DYLD_INSERT_LIBRARIES`; sanitize these for release smoke and test a poisoned library path cannot override the bundled relative dependencies. Keep `otool` validation independent of smoke. +- Local graph semantic discovery found relevant Rust files and references/paths for shell/YAML files; this graph setup does not parse `.sh`/`.yml`. No parsing claim is made. Primary files reviewed: `crates/xtask/src/main.rs`, `build.rs`, `release/install/install.sh`, `.github/workflows/native.yml`, `docs/release.md`. + +Clean Apple Silicon macOS 13.3 and current-version execution, final artifact import/path proof, licensing review, and missing/corrupt/incompatible runtime negative execution remain pending. Do not claim A05/A06 or weaken D05a. diff --git a/docs/specs/CBG-PLAT-01/execution/cod54/native-full-report.json b/docs/specs/CBG-PLAT-01/execution/cod54/native-full-report.json new file mode 100644 index 00000000..76df670e --- /dev/null +++ b/docs/specs/CBG-PLAT-01/execution/cod54/native-full-report.json @@ -0,0 +1,227 @@ +{ + "capture_seconds": 198.092365125, + "cli": "/var/folders/3y/n_wglyqn3bv8xy8s0b9p32d00000gn/T/native-test-cli-16608-1793089629458405000/codebase-graph", + "cli_sha256": "de3930c3c3daa63b54b6fae59a8e287ce1f929b0e877a6c2bc24814a3157345a", + "cli_unchanged": true, + "cohort": "all", + "commands": [ + [ + "test", + "--workspace", + "--locked", + "--profile", + "native-tests", + "--no-fail-fast" + ] + ], + "compiler_artifact": "/private/tmp/codebasegraph-cod54-macos-distribution/target/native-tests/codebase-graph", + "executions": [ + { + "command": 0, + "error": null, + "seconds": 6260.82830775 + } + ], + "inventory": [ + { + "cohort": "unit", + "kind": "lib", + "package": "codebase-graph", + "target": "codebase_graph" + }, + { + "cohort": "integration", + "kind": "doctest", + "package": "codebase-graph", + "target": "codebase_graph" + }, + { + "cohort": "unit", + "kind": "bin", + "package": "codebase-graph", + "target": "codebase-graph" + }, + { + "cohort": "integration", + "kind": "test", + "package": "codebase-graph", + "target": "agent_hooks_process" + }, + { + "cohort": "integration", + "kind": "test", + "package": "codebase-graph", + "target": "auto_repo_root" + }, + { + "cohort": "integration", + "kind": "test", + "package": "codebase-graph", + "target": "coordinator_process" + }, + { + "cohort": "integration", + "kind": "test", + "package": "codebase-graph", + "target": "copy_phase_process" + }, + { + "cohort": "generation", + "kind": "test", + "package": "codebase-graph", + "target": "generation_storage" + }, + { + "cohort": "integration", + "kind": "test", + "package": "codebase-graph", + "target": "http_admission_process" + }, + { + "cohort": "integration", + "kind": "test", + "package": "codebase-graph", + "target": "http_daemon_process" + }, + { + "cohort": "integration", + "kind": "test", + "package": "codebase-graph", + "target": "native_request_identity" + }, + { + "cohort": "integration", + "kind": "test", + "package": "codebase-graph", + "target": "refresh_recovery_process" + }, + { + "cohort": "integration", + "kind": "test", + "package": "codebase-graph", + "target": "storage_foundation" + }, + { + "cohort": "integration", + "kind": "test", + "package": "codebase-graph", + "target": "storage_maintenance" + }, + { + "cohort": "unit", + "kind": "lib", + "package": "k-wiki", + "target": "k_wiki" + }, + { + "cohort": "integration", + "kind": "doctest", + "package": "k-wiki", + "target": "k_wiki" + }, + { + "cohort": "unit", + "kind": "bin", + "package": "k-wiki", + "target": "k-wiki" + }, + { + "cohort": "integration", + "kind": "test", + "package": "k-wiki", + "target": "api_integration" + }, + { + "cohort": "integration", + "kind": "test", + "package": "k-wiki", + "target": "authoring" + }, + { + "cohort": "integration", + "kind": "test", + "package": "k-wiki", + "target": "bundle_reader" + }, + { + "cohort": "integration", + "kind": "test", + "package": "k-wiki", + "target": "compilation" + }, + { + "cohort": "integration", + "kind": "test", + "package": "k-wiki", + "target": "conformance" + }, + { + "cohort": "integration", + "kind": "test", + "package": "k-wiki", + "target": "diagnostic_transports" + }, + { + "cohort": "integration", + "kind": "test", + "package": "k-wiki", + "target": "diagnostics" + }, + { + "cohort": "integration", + "kind": "test", + "package": "k-wiki", + "target": "memory" + }, + { + "cohort": "integration", + "kind": "test", + "package": "k-wiki", + "target": "projection" + }, + { + "cohort": "integration", + "kind": "test", + "package": "k-wiki", + "target": "refresh" + }, + { + "cohort": "integration", + "kind": "test", + "package": "k-wiki", + "target": "rendering" + }, + { + "cohort": "integration", + "kind": "test", + "package": "k-wiki", + "target": "search" + }, + { + "cohort": "integration", + "kind": "test", + "package": "k-wiki", + "target": "transports" + }, + { + "cohort": "unit", + "kind": "bin", + "package": "xtask", + "target": "xtask" + } + ], + "shard": null, + "source_sha": "855c7e282a27de4afbad2d4252d7692de4b42ecb", + "target": "macos-arm64", + "total_seconds": 6484.231147167, + "unsharded_commands": [ + [ + "test", + "--workspace", + "--locked", + "--profile", + "native-tests", + "--no-fail-fast" + ] + ] +} \ No newline at end of file diff --git a/docs/specs/CBG-PLAT-01/execution/cod54/native-full-summary.json b/docs/specs/CBG-PLAT-01/execution/cod54/native-full-summary.json new file mode 100644 index 00000000..999ed3f5 --- /dev/null +++ b/docs/specs/CBG-PLAT-01/execution/cod54/native-full-summary.json @@ -0,0 +1,31 @@ +{ + "source_sha": "855c7e282a27de4afbad2d4252d7692de4b42ecb", + "command": "env SOURCE_SHA=855c7e282a27de4afbad2d4252d7692de4b42ecb cargo run -p xtask --locked -- native-test --target macos-arm64 --cohort all", + "exit": 0, + "duration_seconds": 6488.1888902080245, + "runner": "localmacOS26.6.2/25G83ARM64", + "profile": "native-tests optimized debugassertionsenabled", + "cohort": "all", + "captured_cli_sha256": "de3930c3c3daa63b54b6fae59a8e287ce1f929b0e877a6c2bc24814a3157345a", + "captured_cli_unchanged": true, + "harness_summaries": 31, + "totals": { + "passed": 1356, + "failed": 0, + "ignored": 4, + "measured": 0, + "filtered_out": 0 + }, + "ignored_cases": [ + "storage::maintenance_native_projection::selected_oracle_tests::dc_native_actual_producer187_selected_strings_and_support_rows ... ignored, requires separately generated actual public-producer187 synthetic fixtures", + "storage::maintenance_native_projection::tests::dc_native_actual_producer187_catalog_metadata_checkpoint ... ignored, requires separately generated synthetic public-producer fixture directory", + "unrelated_worker_pid_fixture ... ignored, subprocess fixture for legacy worker recovery", + "relative_barrier_subprocess_fixture ... ignored, subprocess fixture for cwd-independent run cleanup" + ], + "evidence_format": "native inventory/report +fullcapturedlog; notJUnit. Focusedxtask55JUnitseparate.", + "limits": [ + "Local developerhostnotindependentcleanconsumerqualification", + "31harnessresultlines includingdoctests; fourdeclaredignoredcasesnotcountedaspassed", + "Latercommitsonlydocs/policy; nativeRustsourceunchanged" + ] +} \ No newline at end of file diff --git a/docs/specs/CBG-PLAT-01/execution/cod54/native-full.log.gz b/docs/specs/CBG-PLAT-01/execution/cod54/native-full.log.gz new file mode 100644 index 00000000..e6ad0909 Binary files /dev/null and b/docs/specs/CBG-PLAT-01/execution/cod54/native-full.log.gz differ diff --git a/docs/specs/CBG-PLAT-01/execution/cod54/negative-evidence.json b/docs/specs/CBG-PLAT-01/execution/cod54/negative-evidence.json new file mode 100644 index 00000000..6e31b0bc --- /dev/null +++ b/docs/specs/CBG-PLAT-01/execution/cod54/negative-evidence.json @@ -0,0 +1,15 @@ +{ + "source_sha": "855c7e282a27de4afbad2d4252d7692de4b42ecb", + "success_semantics": "artifactpipelineexit0afterexpectingnegativeinstallerfailures", + "negative_cases": [ + "missingruntimefile", + "hashcorruptedruntime", + "checksumvalidincompatibleMachO" + ], + "modes": [ + "dryrun", + "real" + ], + "invariant": "allfiveinstalledpayloadSHAcomparedaftereachfailure", + "scope": "sourceboundartifact currentnativehostonly" +} \ No newline at end of file diff --git a/docs/specs/CBG-PLAT-01/execution/cod54/pax-compile-failure.log b/docs/specs/CBG-PLAT-01/execution/cod54/pax-compile-failure.log new file mode 100644 index 00000000..4015d1b3 --- /dev/null +++ b/docs/specs/CBG-PLAT-01/execution/cod54/pax-compile-failure.log @@ -0,0 +1,15 @@ + Compiling xtask v4.0.0 (/private/tmp/codebasegraph-cod54-macos-distribution/crates/xtask) +error[E0596]: cannot borrow `entry` as mutable, as it is not declared as mutable + --> crates/xtask/src/macos.rs:704:42 + | +704 | validate_pax_global_metadata(&mut entry)?; + | ^^^^^^^^^^ cannot borrow as mutable + | +help: consider changing this to be mutable + | +701 | let mut entry = + | +++ + +For more information about this error, try `rustc --explain E0596`. +error: could not compile `xtask` (bin "xtask" test) due to 1 previous error +error: command `/Users/rabii/.rustup/toolchains/stable-aarch64-apple-darwin/bin/cargo test --no-run --message-format json-render-diagnostics --package xtask --locked` exited with code 101 diff --git a/docs/specs/CBG-PLAT-01/execution/cod54/proposal-review.md b/docs/specs/CBG-PLAT-01/execution/cod54/proposal-review.md new file mode 100644 index 00000000..d3cb3c94 --- /dev/null +++ b/docs/specs/CBG-PLAT-01/execution/cod54/proposal-review.md @@ -0,0 +1,65 @@ +# COD-54 S03 proposal review + +## Recommendation + +**GREEN: proceed with revision2 and its locked shared contract.** Focused verification of revision2 confirms SC-01–SC-04 are incorporated; no proposal revision or further design decision is required before the first implementation edit. The conditions below remain implementation/verification obligations, not unresolved start gates. The requested commitment is bounded implementation and local/native verification of S03 / R03 / A05–A06, not support qualification, release, merge or aggregate closure. No decision-blocking or material design defect was established in draft1 plus the revised `shared-contract.json` inspected on 2026-10-27. The design is proportionate: one macOS packaging module, the existing typed target/inventory authority, and a flat installer payload. Do not add a general dependency resolver, platform framework, nested runtime tree or cross-file rollback. + +Initial reviewer: fresh independent `gpt-6.1-sol`, High effort; adversarial-proposal-review-engineer skill and its proposal-lenses/research-protocol references. No product edits, Git writes, Cargo builds or Scryer changes were performed by this reviewer. This report is the reviewer's only file write. + +## Decision and authority + +Reviewed `proposal.md` draft1, then revision2 in a fresh focused pass, and `shared-contract.json` (including its locked revision read during that pass), `canonical-spec.md` revision0.10, and `docs/specs/CBG-PLAT-01/approvals/cod54-macos-openssl.md`. Baseline: `34eac4f8644adc046093babd84ee1fdbffdae853`; approval record revision: `71dc27e92857c4215bbbe583f104de7f341dc816`; approved canonical spec SHA: `1a9db7b558b60248888408daa6268a3b45f2f0df`. + +D02 requires bundled compatible dylibs, relative paths, licenses/checksums and no consumer Homebrew prerequisite. D05a requires actual final installed archive execution on clean Apple Silicon macOS13.3 and a current release before support qualification. Those settled policies remain unchanged. Other slices, dependency/ABI upgrades, source-install redesign, static linking and qualification waivers are excluded. + +The causal case is sound: current absolute producer imports prevent independent launch; supplying same-version dylibs, rewriting direct/transitive imports, re-signing and installing the complete verified payload removes that dependency; installed runtime smoke and later clean-host execution then establish the bounded behavior and qualified support respectively. + +## Findings and starting conditions + +**Decision-blocking findings: none. Material findings: none established.** The following stable condition IDs distinguish implementation obligations from demonstrated defects. They must remain in the worker briefs and integrated verification; they are not extra architecture or acceptance scope. + +### SC-01 — Freeze the shared contracts before parallel writes + +**Status: satisfied by the revised shared contract, subject to faithful implementation.** Draft1 lines13–16 left module details and strict shell inventory open. The final contract now fixes `prepare_openssl`, `stage_runtime`, `validate_package`, payload names, license source location, signing order, source extraction and shell grammar. Ownership remains parent-only for `main.rs`, shared target/archive/checksum/provenance/smoke integration and Git. This is sufficient to start the two bounded workers without inventing shared types or a second inventory authority. + +Keep the exact macOS checksum payload at five entries: two binaries, SSL, crypto, license. The archive contains eight regular flat files: those five plus `checksums.txt` and both installers. Shell may validate categories and one common constrained numeric version from the generated rows; it must not accept arbitrary checksum names as copy instructions. Reject malformed/duplicate/unknown/missing rows and mismatched sidecar versions before *either* executable probe or any destination write. Preserve Windows' existing inventory and provenance schema1. Existing central anchors: `main.rs:502–643`; final contract `payload` and `installer` keys. + +### SC-02 — Keep native auditing out of cross-target validation + +**Status: resolved in the revised contract.** `validate_native_artifact_set` currently extracts/checksums every target on its caller's host (`main.rs:683–764`). A macOS-only `otool`/`codesign` audit cannot be unconditionally inserted there without breaking cross-target validation. The final `validate_package` contract explicitly confines it to matching native macOS build/execution paths; inventory/checksums remain host-independent. No replacement framework is needed. + +### SC-03 — Prove installed behavior without inherited runtime assistance + +**Status: specified; execution pending as part of implementation verification.** Draft1 line17 correctly requires installed smoke with an empty extension cache. Existing smoke helpers directly inherit environment and include separately created MCP/HTTP processes (`main.rs:1662–1777,1782,1848,2098,2206–2313`). Source cache seeding uses macOS `HOME` and skips existing extension files (`src/db_writer/extensions.rs:10–37,63–71`). Therefore setting one parent variable or cleaning only `run_checked` would not prove isolation. + +The final contract addresses this with per-child command configuration: remove every `DYLD_*`, remove caller OpenSSL configuration overrides, use controlled system PATH for smoke only, and reuse the same fresh HOME across graph/reopen/search and MCP children. Keep build/tool environment separate. Install into a spaced path, then execute the installed pair. Verify JSON/FTS use the bundled bytes in the fresh cache rather than treating generic CLI help as extension evidence. Actual final installed checks, including missing/corrupt/incompatible sidecars and destination preservation, remain required—not already passing because scratch launch passed. + +### SC-04 — Preserve failure semantics and honest qualification + +**Status: specified; implementation and qualification evidence pending.** Verify all five source payload hashes, then both executable probes, before `mkdir`, destination temp files or replacement. Install license/dylibs before binaries, with per-file temp-and-rename and declared modes; retain old version-qualified sidecars. Negative tests must compare existing destination bytes, not merely check a nonzero exit. Do not promise all-file rollback, concurrency-safe cohort replacement or old-sidecar garbage collection; these are not approved here. Source/package hashes are integrity checks, not a new signature/authentication claim. + +Clean minimum/current macOS execution is a later qualification gate, **not a prerequisite to this bounded implementation**. The developer host and scratch artifacts do not satisfy A05 or close COD-54. If licenses/compatibility fail, report the blocker rather than switching to external prerequisites. + +## Independent falsification and evidence + +All artifact locators below are under `/private/tmp/codebasegraph-cod54-review/` unless a repository path or URL is given. + +1. **Repository/authority discovery.** Ran target-local semantic graph search for `native_artifact` and explicit dependency/runtime contexts before source inspection. Results locate `crates/xtask/src/main.rs`; supplied `discovery.json` locates `build.rs`, and explicit runtime context locates `src/db_writer/extensions.rs`. Installer is referenced by discovered `main.rs:430,645–669`; shell is not claimed parsed by the graph. Read Scryer governing change/evidence rules and Release Gate model. It supports existing-claim restoration, not new topology. The configured Scryer/wiki serve the older root and are not target-SHA implementation proof. Wiki release-verification still lists retired targets; target source and approved scope prevail. Linear was not exposed to this reviewer; approval and supplied work IDs were used, with no claimed Linear update. + +2. **Official source and hash.** Independently fetched the OpenSSL GitHub release API `https://api.github.com/repos/openssl/openssl/releases/tags/openssl-3.6.2` on 2026-10-27. Its source asset digest matches the pinned value. Independently SHA-256 hashed the supplied `openssl-3.6.2.tar.gz`: `aaf51a1fe064384f811daeaeb4ec4dce7340ec8bd893027eee676af31e83a04f`. Pinning and checking it *before extraction* is feasible; a mutable release tag alone is not the trust check. The revised fresh-root/no-untrusted-cache-reuse extraction contract is sufficient and narrow. + +3. **License and provider configuration.** Read exact source `openssl-src/openssl-3.6.2/LICENSE.txt`: Apache2 section4(a) requires supplying the license; the source root has no NOTICE file. Ship that same-version license beside the runtime and preserve upstream notices; do not claim legal certification. Source `INSTALL.md:897–927` documents no-legacy and no-module; `doc/man7/OSSL_PROVIDER-default.pod`, DESCRIPTION, documents automatic default-provider loading when no other provider is explicitly loaded. The final no-tests/no-apps/shared/no-module plan does not require a separate default-provider sidecar. The existing probe additionally used no-legacy; the final simpler configuration deliberately avoids that optional exclusion. Its exact final output must still be audited/tested; probe metadata is not the final implementation build. + +4. **Source-built dylib feasibility and OS floor metadata.** Independently inspected `openssl-probe-build/configdata.pm:241–255,294–302`, `file`, and `otool -l/-L` outputs on both probe libraries. They are ARM64, carry `minos13.3`, and import only the peer crypto library and `/usr/lib/libSystem.B.dylib` (plus their own LC_ID_DYLIB identities). This supports feasibility of source-building the pinned version rather than copying producer Homebrew binaries. It does not prove 13.3 execution or all final component compatibility. + +5. **Relative paths and re-signing on real optimized binaries.** Read `baseline-binary-manifest.json`, `baseline-missing-producer-path.txt` and `relocation-probe-result.json`. The captured unchanged-product optimized pair has absolute imports to a nonexistent probe install directory; the supplied baseline launch failed134. Independently inspected `relocation-probe/` with `otool -L`: both binaries load version-qualified SSL/crypto through `@executable_path`; SSL uses `@loader_path` for crypto; all other imports are Apple system paths. Independently ran `codesign --verify --strict` on the four relocated Mach-O files: exit0. Independently launched graph `--help` and wiki `--version` under `env -i`, HOME pointing within the review directory and PATH `/usr/bin:/bin:/usr/sbin:/sbin`: both exit0. No files were mutated by these reviewer checks. This directly defeats the concern that relative rewrites necessarily exceed header capacity or invalid signatures prevent launch. It does not establish final installer/extension/MCP behavior. + +6. **Platform documentation and captured extensions.** Read local primary `dyld(1)` and `codesign(1)` manuals. Their `@executable_path`, `@loader_path`, runpath, environment-override and verification semantics support the chosen layout and cleaning/re-signing requirements. Independently inspected captured JSON/FTS files with `file` and `otool -L`: ARM64; only Apple system imports apart from each file's own ID. An LC_ID_DYLIB is not an unresolved dependency requiring another bundled copy. Preserve extension bytes and ABI. Their origin/minOS reports remain supporting research; final extension-cache byte/hash and actual load proof belong to execution verification. + +## Supported strengths, limits and next action + +The proposal preserves one packaging authority, exact-archive provenance, fail-before-write preflight, narrow relative dependencies, same-version source provenance, post-mutation signing and separate qualification states. These safeguards survive review. + +Focused revision2 verification: its items1–5 now explicitly retain upstream defaults without no-legacy, restrict native auditing to macOS, fix five checksum/eight archive entries, require sidecars-first/modes/preflight before writes, isolate every smoke child and retain minimum/current qualification gates. This matches the locked contract. SC-01–SC-04 are resolved as proposal starting conditions; actual implementation evidence remains pending. Start the bounded implementation under revision2 and the locked shared contract. Then run the targeted regressions, actual optimized archive/install/runtime negatives, affected native checks and fresh final integrated review required by the proposal. No additional owner policy question was discovered. No reusable memory write is warranted: this review establishes task-specific readiness and pending evidence, not a new durable project rule. + +`installer-research.md` and the subsequently delivered `runtime-research.md` were read as supporting research. The runtime report corroborates the observed closure and same-version floor feasibility. Its alternate example filenames/runpath scheme are recommendations, not authority over revision2’s locked version-qualified direct-relative layout. It supplies no new blocking premise. Existing baseline43-pass and reported full relocated graph smoke evidence are supplied parent evidence, not reviewer-executed Cargo/full-runtime runs. Revision2 records a pre-existing unused-import warning outside this slice: this does not block starting S03, but its disposition must be explicit before a completion claim under the repository warning gate. No minimum/current clean-host qualification, final archive execution, network-disabled extension proof or release readiness is claimed by this report. diff --git a/docs/specs/CBG-PLAT-01/execution/cod54/relocation-probe-result.json b/docs/specs/CBG-PLAT-01/execution/cod54/relocation-probe-result.json new file mode 100644 index 00000000..a998e999 --- /dev/null +++ b/docs/specs/CBG-PLAT-01/execution/cod54/relocation-probe-result.json @@ -0,0 +1,8 @@ +{ + "baseline_error_exit": 134, + "mutated_help_exit": 0, + "mutated_wiki_version_exit": 0, + "mutated_wiki_version": "k-wiki 4.0.0\n", + "original_install_path_exists": false, + "note": "scratch captured copies only; actual product source unchanged" +} \ No newline at end of file diff --git a/docs/specs/CBG-PLAT-01/execution/cod54/reviewed-initial-snapshot.json b/docs/specs/CBG-PLAT-01/execution/cod54/reviewed-initial-snapshot.json new file mode 100644 index 00000000..25102850 --- /dev/null +++ b/docs/specs/CBG-PLAT-01/execution/cod54/reviewed-initial-snapshot.json @@ -0,0 +1,25 @@ +{ + "implementation_sha": "a2005dbb37a9ab914ea97b2735825b5286af5b6e", + "baseline": "34eac4f8644adc046093babd84ee1fdbffdae853", + "approval_sha": "71dc27e92857c4215bbbe583f104de7f341dc816", + "spec_sha": "1a9db7b558b60248888408daa6268a3b45f2f0df", + "files": { + "crates/xtask/src/main.rs": "6610d3c571b2a39af7f50cc2703cb9a5cb5431573c0dd46af43dcb9669bd35dd", + "crates/xtask/src/macos.rs": "1f4f650bd0ae0227de998c13f1659ade6485c8cad5e56d2c6602a0820c09299f", + "crates/xtask/src/macos_installer_tests.rs": "10646723ce3db83a5a70e51769957214801d4ef7533362eab1035c31ce7fab17", + "release/install/install.sh": "7ae5c7c5e08fa4c025b0fe5f5b2da605caccdde1a0aefcec148a272530c260e9", + "src/materialization_worker.rs": "5124af6f9df654761bfd09cd46fbf68f9dacb4efc7d0dfc11b9b9ddcb9bc386e", + "README.md": "0f784e2a63656208ae19137e3d745dcc7c3f3132a404f417a3697d836c620323", + "docs/release.md": "6c673b23376ad03a2c1c26c2c7ab01351a56c54ed71cbbfe084b1c073d1d05db" + }, + "tests": { + "xtask55": { + "exit": 0, + "skipped": 0, + "log": "xtask-55-pass.log", + "junit": "xtask-55-pass.junit.xml" + } + }, + "artifact": "retry2 provisional metadata - not final source-bound until rerun", + "qualification": "actual clean13.3/currentunexecuted" +} \ No newline at end of file diff --git a/docs/specs/CBG-PLAT-01/execution/cod54/runtime-implementation.md b/docs/specs/CBG-PLAT-01/execution/cod54/runtime-implementation.md new file mode 100644 index 00000000..32be7b59 --- /dev/null +++ b/docs/specs/CBG-PLAT-01/execution/cod54/runtime-implementation.md @@ -0,0 +1 @@ +macos.rs handoff: implements the fixed `macos` API (`PAYLOAD_FILES`, `prepare_openssl`, `stage_runtime`, native-only `validate_package`) and portable parser tests. OpenSSL producer uses only per-command env, downloads pinned upstream 3.6.2, verifies SHA-256 before strict source extraction, copies verified LICENSE, and builds shared darwin64-arm64 with no-tests/no-apps/no-module at deployment target 13.3. Staging rewrites imports and IDs relative, strips all LC_RPATH, ad-hoc signs in crypto→ssl→graph→wiki order, strict verifies then audits arm64/minos/imports/IDs/no-RPATH. No Homebrew fallback, arbitrary resolver, archive, manifest, shell, or product edits. Changes limited to `crates/xtask/src/macos.rs`. `rustfmt --edition 2024 crates/xtask/src/macos.rs` passed. I did not run Cargo. Parent reports first compile failed before completed edits; second compile passed, four parser tests passed; parent should rerun after final unused-import removal. No native `stage_runtime` fixture has run, no artifact build/smoke; clean 13.3 qualification/signing remain outside this slice. No known unresolved external API changes. diff --git a/docs/specs/CBG-PLAT-01/execution/cod54/runtime-research.md b/docs/specs/CBG-PLAT-01/execution/cod54/runtime-research.md new file mode 100644 index 00000000..ddf9e071 --- /dev/null +++ b/docs/specs/CBG-PLAT-01/execution/cod54/runtime-research.md @@ -0,0 +1,33 @@ +# COD-54 / S03 R03 A05–A06 — macOS runtime investigation + +**Scope and state.** Read-only investigation for the exact approved scope `CBG-PLAT-01/S03`, R03/A05–A06, spec revision 0.10. Repository baseline/approval: `71dc27e92857c4215bbbe583f104de7f341dc816`, based on `34eac4f8644adc046093babd84ee1fdbffdae853`. The approval explicitly permits bundling compatible OpenSSL dylibs with relative paths, license/checksum coverage, and final-archive verification; it excludes static linking, ABI/dependency-version upgrades, new Cargo distribution policy, and release publication/merge/deployment. I made no product or Git changes and ran no Cargo builds. This report is the only durable output. + +## Findings + +- Both actual optimized baseline binaries import **both** OpenSSL libraries by the same absolute producer-prefix path: + - `codebase-graph`, SHA-256 `05736d64ae262deb3a38b3ed464534c2e191e483a3aa1d4476bdf98e464cec2b` + - `k-wiki`, SHA-256 `7b19765973e76685689655456003b6c1c68bd5f4f87e995c3af7364d95b082f7` + - Each imports `/private/tmp/codebasegraph-cod54-review/openssl-probe-build/install/lib/libssl.3.dylib` and `/private/tmp/codebasegraph-cod54-review/openssl-probe-build/install/lib/libcrypto.3.dylib`, plus only Apple CoreFoundation, CoreServices, libc++, libiconv and libSystem at the executable level. Neither reported an `LC_RPATH`. Both are arm64, `LC_BUILD_VERSION platform MACOS minos 13.3`, SDK 26.5, and have a valid ad-hoc linker signature. Source tree remained at requested baseline. Complete tool output is preserved in `/private/tmp/codebasegraph-cod54-review/baseline-binary-manifest.json`. +- The currently installed producer Homebrew OpenSSL is **3.6.2**; its dylibs have minos 26.0 and thus are *not* bundleable as-is for the approved 13.3 floor. The independently source-built OpenSSL 3.6.2 probe has minos 13.3. The probe used official 3.6.2 source, `darwin64-arm64-cc`, `shared`, `no-tests`, `no-apps`, `no-module`, `no-legacy`, a private prefix, and `MACOSX_DEPLOYMENT_TARGET=13.3`; its `libssl.3.dylib` depends on `libcrypto.3.dylib` and libSystem, and `libcrypto.3.dylib` depends only on libSystem. This establishes a feasible same-version/minimum target—not an approval to pin a version, change ABI, or promote this probe unchanged. `no-legacy` is a feature reduction: it should be omitted unless exported-symbol/use review proves applications do not need legacy provider functionality. `no-apps` implies tests are disabled per upstream OpenSSL INSTALL; run upstream library tests separately in the artifact recipe if practical, without bundling apps/tests. +- The checked-in Ladybug 0.19.0 macOS ARM64 JSON and FTS extensions are **not** OpenSSL dependencies. `otool -L` on both exact uncompressed cache-seeded files shows only `@rpath/lib{json,fts}.lbug_extension`, `/usr/lib/libc++.1.dylib`, and `/usr/lib/libSystem.B.dylib`; both report minos 13.3, arm64. SHA-256/size: JSON `e6482a025de31d662ee4b32a39c991706b8cc3c2ec49673ef5d8ce45cc23ce7a` / 630440 bytes; FTS `83ae4be9fa718577ca971f27ff890b3aea5789c9dd908784e28f52917febbfdb` / 1562680 bytes. Their XZ source assets are already embedded and `preseed_ladybug_extensions` writes them under `~/.lbdb/extension/0.19.0/osx_arm64/{json,fts}`. Keep these official extension bytes untouched: no footer rewrite, `install_name_tool`, or ABI/cache change is indicated. +- Dependency-resolution nuance: extensions are loaded later by Ladybug from an absolute cache pathname. Apple's dyld resolves an extension's dependency by its load-command install name, and current dyld source first checks loaded images for an exact path match and for `@rpath`-relative matches (Apple dyld `Loader.cpp`, `getLoader`, around lines 797–805). A generic C fixture on this Darwin host confirmed that after the client has loaded a dylib from an `@rpath` name, `dlopen` of an extension whose `LC_LOAD_DYLIB` names the exact same dylib by its prior absolute producer path succeeds: `preloaded=41`, `extension=42`. A rewritten client dependency is not, by itself, evidence that extension bytes must be rewritten. Here the actual JSON/FTS imports don't name OpenSSL at all, so no OpenSSL alias-resolution workaround is needed. Native fixture limitation: it validates dyld semantics on this host, not Ladybug's full extension load path or macOS 13.3 behavior. + +## Minimal compatible packaging/build contract + +1. Keep changes within the `xtask` macOS artifact build/package path. Build the **same approved OpenSSL 3.x ABI and exact source release selected by implementation**, arm64 with deployment target 13.3; use official source/checksum and include the corresponding license. Do not use current Homebrew Cellar dylibs (minos 26.0), statically link, upgrade OpenSSL/ABI, or add new product build-script/source-install policy. +2. Make OpenSSL available during both existing `cargo build` invocations from one controlled, isolated artifact build directory. The least change is create the directory and source-build OpenSSL before invoking Cargo, then set explicit `OPENSSL_LIB_DIR` on both `codebase-graph` and `k-wiki` Cargo commands; `build.rs` already recognizes this setting, and the root `k-wiki` dependency means both binaries carry those same dynamic links. Set/verify `MACOSX_DEPLOYMENT_TARGET=13.3` for OpenSSL and both Rust builds. A per-invocation environment prevents producer settings from becoming consumer prerequisites. Do not mutate macOS test workflow/setup or openSSL build.rs fallback paths for this prebuilt-release scope. +3. Stage a minimal flat archive layout, e.g. existing five root files plus `libssl.3.dylib`, `libcrypto.3.dylib`, `LICENSE-OpenSSL.txt` (and provenance/checksum entries). Flat root layout allows a simple executable-relative `@rpath`; any nested framework layout is fine only if all link edits agree. Preserve both binaries and installer scripts; include each intentional library/license in exact archive allowlist, deterministic archive creation, SHA manifest, public archive digest, provenance and extraction/validation contract. Do not leave redundant `libssl.dylib` symlink unless a tool/build requires it. +4. Before final hash/sign, set dylib IDs to `@rpath/libssl.3.dylib` and `@rpath/libcrypto.3.dylib`; change each binary's exact old `libssl` and `libcrypto` dependencies to corresponding `@rpath/...`; change `libssl`'s `libcrypto` dependency to `@rpath/libcrypto.3.dylib`. Put `LC_RPATH @executable_path` in both executables so root sidecars resolve independent of current working directory; `@loader_path` on libssl can make its own libcrypto edge independently relocatable. These names are relative to bundle/executable locations, not Homebrew or CWD. Strip/reject extra build rpaths and inspect complete `otool -L` and `otool -l` closure for both exes and dylibs. Allow only the expected two OpenSSL dylibs plus Apple system libraries; fail closed on any other non-system dependency, missing sidecar, bad architecture/minos, bad IDs, or unexpected rpath. Check exact architecture and minimum-OS metadata for every packaged Mach-O, not only Cargo targets. No dependency-complete generic resolver is needed for the observed closure. +5. Install-name/rpath mutation invalidates existing code signatures. Perform all `install_name_tool` edits first, then re-sign each changed Mach-O with a deliberate final distribution identity. Current Cargo/OpenSSL files are only ad-hoc signed; `codesign -s -` can create ad-hoc signatures for local CI/package validation but is not a Developer ID/notarization answer. Keep signing/notarization policy unchanged absent explicit approval; record it as a release qualification limitation rather than claiming distribution trust from ad-hoc signatures. Verify final code signature state after edits; then compute checksums/provenance over final bytes. If future real signing occurs, preserve order: mutate → designated code sign all changed Mach-O → final archive/package signature if any → archive checksums/provenance. Never mutate after signing/checksum. +6. Extend the exact artifact validation/smoke path with final extracted `otool`/ID/rpath/minos/architecture checks and run both shipped binaries and actual Ladybug JSON/FTS load/reopen with the bundle relocated into a clean temporary root and Homebrew library search paths absent. Add missing/corrupt sidecar negatives: failure must be clear and no readiness/success claim. Exact built artifact (not an unrelated debug binary) supplies A05/A06 evidence. Actual clean macOS 13.3 and current Apple Silicon execution remains required to claim A05; this investigation host is macOS 26.6.2 build 25G83, ARM64. + +## Why packaging only at CLI build/link time is insufficient + +`build.rs` presently dynamically links `ssl` and `crypto` and searches producer Homebrew paths (`/opt/homebrew/opt/openssl@3/lib`, `/usr/local/opt/openssl@3/lib`) when `OPENSSL_LIB_DIR` isn't set. `crates/xtask` currently stages only the two executables and two installer scripts plus binary checksums; it archives an exact five-file allowlist. Thus adding a linker search dir or editing just the CLI import names cannot provide the runtime libraries: the executables will still fail on a clean consumer if the sidecars are absent. The two dylibs plus license/checksum/archive allowlist and relative resolution must be part of each prebuilt macOS artifact. No extension sidecars need separate bundling: the actual macOS extensions are already compressed into product binaries, seeded byte-for-byte into Ladybug cache and import only Apple system dylibs. + +## Evidence and limits + +- Graph-first discovery from local graph targeted `build.rs`, `native_artifact`, `preseed_ladybug_extensions`; explicit Scryer read-only `orient` returned existing Release Gate `resp-16/17/255–258`, Graph Store `resp-113`; no model write. Wiki memory recall returned release verification and superseded cache memory; current code is independently checked. Scryer is bound to the main checkout and only supplies architectural context. +- Source observed at approved repository baseline: `build.rs` exact OpenSSL linker/configuration; `crates/xtask/src/main.rs` `build_native_binaries` (both commands), `build_and_smoke_native_archive`, archive creation/validation and internal checksums; `src/db_writer/extensions.rs` cache seed/XZ inclusion/version 0.19.0; current installers validate binaries only. No Cargo/build/test suite was run by me. +- Primary sources: Apple [Run-Path Dependent Libraries](https://developer.apple.com/library/archive/documentation/DeveloperTools/Conceptual/DynamicLibraries/100-Articles/RunpathDependentLibraries.html) documents `@rpath/` dylib install names and executable runpath search paths used at load; Apple [Code Signing Tasks](https://developer.apple.com/library/archive/documentation/Security/Conceptual/CodeSigningGuide/Procedures/Procedures.html) describes signatures and warns modification invalidates signed app code. OpenSSL [license page](https://openssl-library.org/source/license/index.html) says OpenSSL 3.0+ is Apache-2.0 and source release carries top-level LICENSE. OpenSSL 3.6.2 release API manifest in `/private/tmp/codebasegraph-cod54-review/openssl-release.json` identifies source tarball SHA-256 `aaf51a1fe064384f811daeaeb4ec4dce7340ec8bd893027eee676af31e83a04f`; official tag points to commit `fe686e15d84334b284f883118ed92f64b409b3aa`. This source release is observed evidence, not fixed approval/version choice. +- No actual Scryer update, approved Scryer execution brief, package/signature/notarization approval, actual final archive, minimum OS consumer, current consumer, negative artifact, or extension integration test is produced here. `A05/A06`, platform qualification, review/implementation and release gates remain pending. No unresolved user-scope question found; exact compatible dylib release/configuration and `xtask` layout/tooling remain implementation details inside approved D02. diff --git a/docs/specs/CBG-PLAT-01/execution/cod54/source-bound-artifact.json b/docs/specs/CBG-PLAT-01/execution/cod54/source-bound-artifact.json new file mode 100644 index 00000000..8c07c073 --- /dev/null +++ b/docs/specs/CBG-PLAT-01/execution/cod54/source-bound-artifact.json @@ -0,0 +1,19 @@ +{ + "source_sha": "855c7e282a27de4afbad2d4252d7692de4b42ecb", + "command": "cargo run -p xtask --locked -- native-artifact --target macos-arm64 --commit-sha 855c7e282a27de4afbad2d4252d7692de4b42ecb --output /private/tmp/codebasegraph-cod54-macos-distribution/dist/cod54-source-bound", + "exit": 0, + "duration_seconds": 309.5025947499089, + "archive_sha256": "e5f32abbf8124584d5f6f2df403091c3323d16a1359a70f387fc99eeeb6e4282", + "environment": { + "os": "macOS26.6.2", + "build": "25G83", + "arch": "arm64" + }, + "preserved_archive": "/private/tmp/codebasegraph-cod54-review/final-archive/codebase-graph-4.0.0-macos-arm64.tar.gz", + "preserved_installed": "/private/tmp/codebasegraph-cod54-review/final-archive/installed bin", + "limits": [ + "ActualcleanMac13.3/currentunexecuted", + "FTSLOADnotpopulatednativeFTSindex", + "AdhocnotDeveloperIDnotarization" + ] +} \ No newline at end of file diff --git a/docs/specs/CBG-PLAT-01/execution/cod54/source-bound-artifact.log.gz b/docs/specs/CBG-PLAT-01/execution/cod54/source-bound-artifact.log.gz new file mode 100644 index 00000000..133d8c59 Binary files /dev/null and b/docs/specs/CBG-PLAT-01/execution/cod54/source-bound-artifact.log.gz differ diff --git a/docs/specs/CBG-PLAT-01/execution/cod54/source-bound-inventory.json b/docs/specs/CBG-PLAT-01/execution/cod54/source-bound-inventory.json new file mode 100644 index 00000000..84d71a6d --- /dev/null +++ b/docs/specs/CBG-PLAT-01/execution/cod54/source-bound-inventory.json @@ -0,0 +1,62 @@ +{ + "entries": [ + { + "name": "codebase-graph", + "size": 48024240, + "mode": "0o755" + }, + { + "name": "k-wiki", + "size": 6776576, + "mode": "0o755" + }, + { + "name": "libcodebasegraph-ssl-3.6.2.dylib", + "size": 996384, + "mode": "0o644" + }, + { + "name": "libcodebasegraph-crypto-3.6.2.dylib", + "size": 5172016, + "mode": "0o644" + }, + { + "name": "openssl-3.6.2-LICENSE.txt", + "size": 10175, + "mode": "0o644" + }, + { + "name": "checksums.txt", + "size": 447, + "mode": "0o644" + }, + { + "name": "install.sh", + "size": 6442, + "mode": "0o755" + }, + { + "name": "install.ps1", + "size": 3086, + "mode": "0o644" + } + ], + "payload_sha256": { + "codebase-graph": "9b8694eac05840968c514a554171f6e705e0a6420bb4e31cafacfa5d242916f0", + "k-wiki": "fb51f6eb6fff723971c0f803659bfb8e829e6670cbef597441a5fff6a065106b", + "libcodebasegraph-ssl-3.6.2.dylib": "2cd63a2b676c6203cda0a8f87500ece49f798a844d33932090327de438abfa5d", + "libcodebasegraph-crypto-3.6.2.dylib": "d2a54e077eee955b4903a22c2c78e5d3de1a78a1b672adebed12c8f25c02a8b9", + "openssl-3.6.2-LICENSE.txt": "7d5450cb2d142651b8afa315b5f238efc805dad827d91ba367d8516bc9d49e7a" + }, + "archive_sha256": "e5f32abbf8124584d5f6f2df403091c3323d16a1359a70f387fc99eeeb6e4282", + "provenance": { + "schema_version": 1, + "commit_sha": "855c7e282a27de4afbad2d4252d7692de4b42ecb", + "version": "4.0.0", + "target": "macos-arm64", + "archive": "codebase-graph-4.0.0-macos-arm64.tar.gz", + "archive_sha256": "e5f32abbf8124584d5f6f2df403091c3323d16a1359a70f387fc99eeeb6e4282" + }, + "implementation_sha": "855c7e282a27de4afbad2d4252d7692de4b42ecb", + "installer_result": 0 +} \ No newline at end of file diff --git a/docs/specs/CBG-PLAT-01/execution/cod54/xtask-55-pass.junit.xml b/docs/specs/CBG-PLAT-01/execution/cod54/xtask-55-pass.junit.xml new file mode 100644 index 00000000..697d7680 --- /dev/null +++ b/docs/specs/CBG-PLAT-01/execution/cod54/xtask-55-pass.junit.xml @@ -0,0 +1,115 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/specs/CBG-PLAT-01/execution/cod54/xtask-55-pass.log b/docs/specs/CBG-PLAT-01/execution/cod54/xtask-55-pass.log new file mode 100644 index 00000000..6fda7b0b --- /dev/null +++ b/docs/specs/CBG-PLAT-01/execution/cod54/xtask-55-pass.log @@ -0,0 +1,62 @@ + Compiling xtask v4.0.0 (/private/tmp/codebasegraph-cod54-macos-distribution/crates/xtask) + Finished `test` profile [unoptimized + debuginfo] target(s) in 0.82s +──────────── + Nextest run ID 2af01b9e-e0fc-4987-a624-fcbcd0b89e5f with nextest profile: cod54 + Starting 55 tests across 1 binary + PASS [ 0.011s] ( 1/55) xtask::bin/xtask macos::tests::dependency_parser_preserves_paths_with_spaces + PASS [ 0.012s] ( 2/55) xtask::bin/xtask macos::tests::dylib_dependency_parser_filters_only_matching_install_id + PASS [ 0.011s] ( 3/55) xtask::bin/xtask macos::tests::minimum_version_parser_reads_macos_build_and_legacy_commands + PASS [ 0.009s] ( 4/55) xtask::bin/xtask macos::tests::rpath_parser_reads_and_rejects_malformed_entries + PASS [ 0.011s] ( 5/55) xtask::bin/xtask macos::tests::source_archive_accepts_comment_metadata_without_extracting_it + PASS [ 0.757s] ( 6/55) xtask::bin/xtask macos_installer_tests::dry_run_validates_all_payloads_without_creating_destination + PASS [ 0.648s] ( 7/55) xtask::bin/xtask macos_installer_tests::invalid_inventory_rows_and_mixed_versions_preserve_destination + PASS [ 0.749s] ( 8/55) xtask::bin/xtask macos_installer_tests::missing_corrupt_and_symlink_payloads_preserve_existing_destination_files + PASS [ 0.555s] ( 9/55) xtask::bin/xtask macos_installer_tests::nonzero_binary_probe_preserves_existing_destination_files + PASS [ 0.828s] (10/55) xtask::bin/xtask macos_installer_tests::real_install_places_verified_runtime_license_and_binaries_in_spaced_directory + PASS [ 0.011s] (11/55) xtask::bin/xtask native_tests::shards::tests::empty_shard_never_runs_an_unfiltered_suite + PASS [ 0.012s] (12/55) xtask::bin/xtask native_tests::shards::tests::selected_exact_filters_execute_the_expected_real_harness_cases + PASS [ 0.018s] (13/55) xtask::bin/xtask native_tests::shards::tests::shards_cover_every_listed_case_once_and_bound_windows_arguments + PASS [ 0.012s] (14/55) xtask::bin/xtask native_tests::tests::captured_cli_survives_cargo_replacing_its_output + PASS [ 0.009s] (15/55) xtask::bin/xtask native_tests::tests::partitions_every_test_target_and_keeps_new_integration_targets + PASS [ 0.009s] (16/55) xtask::bin/xtask native_tests::tests::windows_regressions_precede_the_complete_unfiltered_suite + PASS [ 0.015s] (17/55) xtask::bin/xtask release::tests::metadata_rejects_inconsistent_versions + PASS [ 0.010s] (18/55) xtask::bin/xtask release::tests::notes_only_include_the_exact_release + PASS [ 0.009s] (19/55) xtask::bin/xtask release::tests::notes_reject_missing_duplicate_and_empty_sections + PASS [ 0.264s] (20/55) xtask::bin/xtask release::workflow_tests::ci_run_recovery_dry_run_validates_without_authorizing_publication + PASS [ 0.648s] (21/55) xtask::bin/xtask release::workflow_tests::ordinary_commit_cannot_authorize_release_and_tagged_retry_still_can + PASS [ 0.905s] (22/55) xtask::bin/xtask release::workflow_tests::proposal_reports_pending_release_then_proceeds_after_finalization + PASS [ 0.334s] (23/55) xtask::bin/xtask release::workflow_tests::publisher_rejects_conflicting_tags_and_does_not_regress_latest + PASS [ 0.342s] (24/55) xtask::bin/xtask release::workflow_tests::publisher_targets_original_sha_and_is_idempotent + PASS [ 0.034s] (25/55) xtask::bin/xtask release::workflow_tests::recovery_requires_one_target_and_forbids_ci_run_rebuilds + PASS [ 1.823s] (26/55) xtask::bin/xtask release::workflow_tests::verified_release_rejects_failed_ci_and_untrusted_run_identity + PASS [ 2.139s] (27/55) xtask::bin/xtask release::workflow_tests::verified_release_rejects_untrusted_prs_and_removed_history + PASS [ 0.270s] (28/55) xtask::bin/xtask release::workflow_tests::verified_release_survives_main_advancing_and_ignores_other_pending_releases + PASS [ 0.015s] (29/55) xtask::bin/xtask tests::archive_contract_rejects_unexpected_entries + PASS [ 0.020s] (30/55) xtask::bin/xtask tests::complete_artifact_set_rejects_mixed_provenance + PASS [ 0.009s] (31/55) xtask::bin/xtask tests::crate_size_validation_enforces_the_registry_limit + PASS [ 0.262s] (32/55) xtask::bin/xtask tests::installer_rejects_removed_hosts_before_reading_archives_or_writing_destinations + PASS [ 0.015s] (33/55) xtask::bin/xtask tests::macos_smoke_commands_use_a_fixture_home_and_system_path + PASS [ 0.011s] (34/55) xtask::bin/xtask tests::native_capture_requires_unique_source_bound_normal_cli_artifact + PASS [ 0.009s] (35/55) xtask::bin/xtask tests::native_capture_reuses_the_exact_test_profile_and_features + PASS [ 0.012s] (36/55) xtask::bin/xtask tests::native_payload_integrity_covers_runtime_files_and_preserves_windows_inventory + PASS [ 0.009s] (37/55) xtask::bin/xtask tests::native_target_contract_maps_names_and_binaries + PASS [ 0.023s] (38/55) xtask::bin/xtask tests::native_test_failure_cannot_authorize_artifact_production + PASS [ 0.009s] (39/55) xtask::bin/xtask tests::release_identity_validation_is_strict + PASS [ 0.009s] (40/55) xtask::bin/xtask tests::windows_native_tests_forward_bundled_extensions + PASS [ 0.016s] (41/55) xtask::bin/xtask tests::workflow_policy_rejects_automatic_rebuild_recovery + PASS [ 0.016s] (42/55) xtask::bin/xtask tests::workflow_policy_rejects_direct_release_push_trigger + PASS [ 0.018s] (43/55) xtask::bin/xtask tests::workflow_policy_rejects_github_sha_for_release_identity + PASS [ 0.016s] (44/55) xtask::bin/xtask tests::workflow_policy_rejects_missing_ci_success_guard + PASS [ 0.029s] (45/55) xtask::bin/xtask tests::workflow_policy_rejects_missing_crate_publish_retry + PASS [ 0.021s] (46/55) xtask::bin/xtask tests::workflow_policy_rejects_missing_crate_size_gates + PASS [ 0.016s] (47/55) xtask::bin/xtask tests::workflow_policy_rejects_missing_repository_release_gate + PASS [ 0.015s] (48/55) xtask::bin/xtask tests::workflow_policy_rejects_missing_triggering_run_binding + PASS [ 0.015s] (49/55) xtask::bin/xtask tests::workflow_policy_rejects_release_queue_replacement + PASS [ 0.016s] (50/55) xtask::bin/xtask tests::workflow_policy_rejects_unconditional_release_publication + PASS [ 0.029s] (51/55) xtask::bin/xtask tests::workflow_policy_rejects_unvalidated_publishers + PASS [ 0.016s] (52/55) xtask::bin/xtask tests::workflow_policy_requires_ci_completion_release_trigger + PASS [ 0.029s] (53/55) xtask::bin/xtask tests::workflow_policy_requires_publication_revalidation + PASS [ 0.009s] (54/55) xtask::bin/xtask tests::workflow_yaml_validation_rejects_unindented_heredoc_body + PASS [ 0.022s] (55/55) xtask::bin/xtask tests::workflows_reject_removed_platform_targets +──────────── + Summary [ 11.143s] 55 tests run: 55 passed, 0 skipped diff --git a/docs/specs/CBG-PLAT-01/execution/cod54/xtask-attempt-4.junit.xml b/docs/specs/CBG-PLAT-01/execution/cod54/xtask-attempt-4.junit.xml new file mode 100644 index 00000000..3da452e5 --- /dev/null +++ b/docs/specs/CBG-PLAT-01/execution/cod54/xtask-attempt-4.junit.xml @@ -0,0 +1,113 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/specs/CBG-PLAT-01/execution/cod54/xtask-attempt-4.log b/docs/specs/CBG-PLAT-01/execution/cod54/xtask-attempt-4.log new file mode 100644 index 00000000..3e05a4d2 --- /dev/null +++ b/docs/specs/CBG-PLAT-01/execution/cod54/xtask-attempt-4.log @@ -0,0 +1,61 @@ + Compiling xtask v4.0.0 (/private/tmp/codebasegraph-cod54-macos-distribution/crates/xtask) + Finished `test` profile [unoptimized + debuginfo] target(s) in 0.89s +──────────── + Nextest run ID e053d7d9-8176-47fa-80ae-a9d2f248dbfa with nextest profile: cod54 + Starting 54 tests across 1 binary + PASS [ 0.010s] ( 1/54) xtask::bin/xtask macos::tests::dependency_parser_preserves_paths_with_spaces + PASS [ 0.009s] ( 2/54) xtask::bin/xtask macos::tests::dylib_dependency_parser_filters_only_matching_install_id + PASS [ 0.009s] ( 3/54) xtask::bin/xtask macos::tests::minimum_version_parser_reads_macos_build_and_legacy_commands + PASS [ 0.008s] ( 4/54) xtask::bin/xtask macos::tests::rpath_parser_reads_and_rejects_malformed_entries + PASS [ 0.894s] ( 5/54) xtask::bin/xtask macos_installer_tests::dry_run_validates_all_payloads_without_creating_destination + PASS [ 0.483s] ( 6/54) xtask::bin/xtask macos_installer_tests::invalid_inventory_rows_and_mixed_versions_preserve_destination + PASS [ 0.753s] ( 7/54) xtask::bin/xtask macos_installer_tests::missing_corrupt_and_symlink_payloads_preserve_existing_destination_files + PASS [ 0.549s] ( 8/54) xtask::bin/xtask macos_installer_tests::nonzero_binary_probe_preserves_existing_destination_files + PASS [ 1.021s] ( 9/54) xtask::bin/xtask macos_installer_tests::real_install_places_verified_runtime_license_and_binaries_in_spaced_directory + PASS [ 0.010s] (10/54) xtask::bin/xtask native_tests::shards::tests::empty_shard_never_runs_an_unfiltered_suite + PASS [ 0.012s] (11/54) xtask::bin/xtask native_tests::shards::tests::selected_exact_filters_execute_the_expected_real_harness_cases + PASS [ 0.017s] (12/54) xtask::bin/xtask native_tests::shards::tests::shards_cover_every_listed_case_once_and_bound_windows_arguments + PASS [ 0.013s] (13/54) xtask::bin/xtask native_tests::tests::captured_cli_survives_cargo_replacing_its_output + PASS [ 0.009s] (14/54) xtask::bin/xtask native_tests::tests::partitions_every_test_target_and_keeps_new_integration_targets + PASS [ 0.008s] (15/54) xtask::bin/xtask native_tests::tests::windows_regressions_precede_the_complete_unfiltered_suite + PASS [ 0.010s] (16/54) xtask::bin/xtask release::tests::metadata_rejects_inconsistent_versions + PASS [ 0.008s] (17/54) xtask::bin/xtask release::tests::notes_only_include_the_exact_release + PASS [ 0.008s] (18/54) xtask::bin/xtask release::tests::notes_reject_missing_duplicate_and_empty_sections + PASS [ 0.257s] (19/54) xtask::bin/xtask release::workflow_tests::ci_run_recovery_dry_run_validates_without_authorizing_publication + PASS [ 0.639s] (20/54) xtask::bin/xtask release::workflow_tests::ordinary_commit_cannot_authorize_release_and_tagged_retry_still_can + PASS [ 0.301s] (21/54) xtask::bin/xtask release::workflow_tests::proposal_reports_pending_release_then_proceeds_after_finalization + PASS [ 0.330s] (22/54) xtask::bin/xtask release::workflow_tests::publisher_rejects_conflicting_tags_and_does_not_regress_latest + PASS [ 0.337s] (23/54) xtask::bin/xtask release::workflow_tests::publisher_targets_original_sha_and_is_idempotent + PASS [ 0.034s] (24/54) xtask::bin/xtask release::workflow_tests::recovery_requires_one_target_and_forbids_ci_run_rebuilds + PASS [ 1.738s] (25/54) xtask::bin/xtask release::workflow_tests::verified_release_rejects_failed_ci_and_untrusted_run_identity + PASS [ 2.113s] (26/54) xtask::bin/xtask release::workflow_tests::verified_release_rejects_untrusted_prs_and_removed_history + PASS [ 0.265s] (27/54) xtask::bin/xtask release::workflow_tests::verified_release_survives_main_advancing_and_ignores_other_pending_releases + PASS [ 0.015s] (28/54) xtask::bin/xtask tests::archive_contract_rejects_unexpected_entries + PASS [ 0.020s] (29/54) xtask::bin/xtask tests::complete_artifact_set_rejects_mixed_provenance + PASS [ 0.009s] (30/54) xtask::bin/xtask tests::crate_size_validation_enforces_the_registry_limit + PASS [ 0.464s] (31/54) xtask::bin/xtask tests::installer_rejects_removed_hosts_before_reading_archives_or_writing_destinations + PASS [ 0.012s] (32/54) xtask::bin/xtask tests::macos_smoke_commands_use_a_fixture_home_and_system_path + PASS [ 0.011s] (33/54) xtask::bin/xtask tests::native_capture_requires_unique_source_bound_normal_cli_artifact + PASS [ 0.009s] (34/54) xtask::bin/xtask tests::native_capture_reuses_the_exact_test_profile_and_features + PASS [ 0.012s] (35/54) xtask::bin/xtask tests::native_payload_integrity_covers_runtime_files_and_preserves_windows_inventory + PASS [ 0.009s] (36/54) xtask::bin/xtask tests::native_target_contract_maps_names_and_binaries + PASS [ 0.022s] (37/54) xtask::bin/xtask tests::native_test_failure_cannot_authorize_artifact_production + PASS [ 0.009s] (38/54) xtask::bin/xtask tests::release_identity_validation_is_strict + PASS [ 0.008s] (39/54) xtask::bin/xtask tests::windows_native_tests_forward_bundled_extensions + PASS [ 0.015s] (40/54) xtask::bin/xtask tests::workflow_policy_rejects_automatic_rebuild_recovery + PASS [ 0.015s] (41/54) xtask::bin/xtask tests::workflow_policy_rejects_direct_release_push_trigger + PASS [ 0.016s] (42/54) xtask::bin/xtask tests::workflow_policy_rejects_github_sha_for_release_identity + PASS [ 0.016s] (43/54) xtask::bin/xtask tests::workflow_policy_rejects_missing_ci_success_guard + PASS [ 0.030s] (44/54) xtask::bin/xtask tests::workflow_policy_rejects_missing_crate_publish_retry + PASS [ 0.022s] (45/54) xtask::bin/xtask tests::workflow_policy_rejects_missing_crate_size_gates + PASS [ 0.016s] (46/54) xtask::bin/xtask tests::workflow_policy_rejects_missing_repository_release_gate + PASS [ 0.016s] (47/54) xtask::bin/xtask tests::workflow_policy_rejects_missing_triggering_run_binding + PASS [ 0.016s] (48/54) xtask::bin/xtask tests::workflow_policy_rejects_release_queue_replacement + PASS [ 0.016s] (49/54) xtask::bin/xtask tests::workflow_policy_rejects_unconditional_release_publication + PASS [ 0.029s] (50/54) xtask::bin/xtask tests::workflow_policy_rejects_unvalidated_publishers + PASS [ 0.015s] (51/54) xtask::bin/xtask tests::workflow_policy_requires_ci_completion_release_trigger + PASS [ 0.029s] (52/54) xtask::bin/xtask tests::workflow_policy_requires_publication_revalidation + PASS [ 0.009s] (53/54) xtask::bin/xtask tests::workflow_yaml_validation_rejects_unindented_heredoc_body + PASS [ 0.022s] (54/54) xtask::bin/xtask tests::workflows_reject_removed_platform_targets +──────────── + Summary [ 10.733s] 54 tests run: 54 passed, 0 skipped diff --git a/release/install/install.sh b/release/install/install.sh index c8d5c388..5964c616 100644 --- a/release/install/install.sh +++ b/release/install/install.sh @@ -11,7 +11,7 @@ Usage: Options: --source-dir Extracted archive directory containing binaries and checksums.txt. Defaults to the directory containing this script. - --bin-dir Destination directory for installed binaries. + --bin-dir Destination directory for installed binaries and runtime libraries. Defaults to $HOME/.local/bin. --dry-run Validate checksums and executable surfaces without replacing files. EOF @@ -23,7 +23,7 @@ resolve_sha_cmd() { return 0 fi if command -v shasum >/dev/null 2>&1; then - printf '%s\n' "shasum -a 256" + printf '%s\n' "shasum" return 0 fi return 1 @@ -69,55 +69,128 @@ esac source_dir="$(CDPATH='' cd -- "$source_dir" && pwd)" checksums_path="${source_dir}/checksums.txt" -[[ -f "$checksums_path" ]] || { echo "missing checksums.txt in ${source_dir}" >&2; exit 1; } +[[ -f "$checksums_path" && ! -L "$checksums_path" ]] || { + echo "missing or unsafe checksums.txt in ${source_dir}" >&2 + exit 1 +} sha_cmd="$(resolve_sha_cmd)" || { echo "sha256 tool is required (sha256sum or shasum)." >&2 exit 1 } -verify_binary() { - local name="$1" - local probe_arg="$2" - local path="${source_dir}/${name}" - [[ -f "$path" ]] || { echo "missing binary ${name} in ${source_dir}" >&2; exit 1; } - ( - cd "$source_dir" - grep -E " ${name}\$" checksums.txt >/dev/null 2>&1 || { - echo "checksums.txt does not contain ${name}" >&2 - exit 1 +verify_payload() { + local line digest name path hash_result actual_hash expected_version row_version + local rows=0 graph_count=0 wiki_count=0 ssl_count=0 crypto_count=0 license_count=0 + local expected_version="" + local graph_name="codebase-graph" wiki_name="k-wiki" + local ssl_name="" crypto_name="" license_name="" + + while IFS= read -r line || [[ -n "$line" ]]; do + [[ "$line" =~ ^([[:xdigit:]]{64})\ \ ([[:alnum:]_.-]+)$ ]] || { + echo "malformed checksums.txt row" >&2 + return 1 + } + digest="${BASH_REMATCH[1]}" + name="${BASH_REMATCH[2]}" + rows=$((rows + 1)) + + if [[ "$name" == "$graph_name" ]]; then + graph_count=$((graph_count + 1)) + elif [[ "$name" == "$wiki_name" ]]; then + wiki_count=$((wiki_count + 1)) + elif [[ "$name" =~ ^libcodebasegraph-ssl-(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.dylib$ ]]; then + ssl_count=$((ssl_count + 1)) + row_version="${BASH_REMATCH[1]}.${BASH_REMATCH[2]}.${BASH_REMATCH[3]}" + ssl_name="$name" + elif [[ "$name" =~ ^libcodebasegraph-crypto-(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.dylib$ ]]; then + crypto_count=$((crypto_count + 1)) + row_version="${BASH_REMATCH[1]}.${BASH_REMATCH[2]}.${BASH_REMATCH[3]}" + crypto_name="$name" + elif [[ "$name" =~ ^openssl-(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)-LICENSE\.txt$ ]]; then + license_count=$((license_count + 1)) + row_version="${BASH_REMATCH[1]}.${BASH_REMATCH[2]}.${BASH_REMATCH[3]}" + license_name="$name" + else + echo "unexpected checksums.txt payload name: ${name}" >&2 + return 1 + fi + + if [[ "$name" != "$graph_name" && "$name" != "$wiki_name" ]]; then + if [[ -z "$expected_version" ]]; then + expected_version="$row_version" + elif [[ "$row_version" != "$expected_version" ]]; then + echo "OpenSSL payload filenames have different versions" >&2 + return 1 + fi + fi + + path="${source_dir}/${name}" + [[ -f "$path" && ! -L "$path" ]] || { + echo "missing or unsafe payload file ${name} in ${source_dir}" >&2 + return 1 } if [[ "$sha_cmd" == "sha256sum" ]]; then - sha256sum --check --status --strict <(grep -E " ${name}\$" checksums.txt) + hash_result="$(sha256sum "$path")" || { echo "failed to hash ${name}" >&2; return 1; } else - shasum -a 256 --check --status <(grep -E " ${name}\$" checksums.txt) + hash_result="$(shasum -a 256 "$path")" || { echo "failed to hash ${name}" >&2; return 1; } fi - ) - "$path" "$probe_arg" >/dev/null + actual_hash="${hash_result%% *}" + actual_hash="$(printf '%s' "$actual_hash" | tr '[:upper:]' '[:lower:]')" + digest="$(printf '%s' "$digest" | tr '[:upper:]' '[:lower:]')" + [[ "$actual_hash" == "$digest" ]] || { + echo "SHA256 mismatch for ${name}" >&2 + return 1 + } + done < "$checksums_path" + + [[ "$rows" -eq 5 && "$graph_count" -eq 1 && "$wiki_count" -eq 1 && \ + "$ssl_count" -eq 1 && "$crypto_count" -eq 1 && "$license_count" -eq 1 ]] || { + echo "checksums.txt must contain exactly two binaries and one each SSL, crypto, and license payload" >&2 + return 1 + } + printf '%s\n%s\n%s\n' "$ssl_name" "$crypto_name" "$license_name" } -install_binary() { - local name="$1" - local source_path="${source_dir}/${name}" - local target_path="${target_bin_dir}/${name}" - local temp_path - temp_path="$(mktemp "${target_bin_dir}/.${name}.tmp.XXXXXX")" - if ! cp "$source_path" "$temp_path" || ! chmod +x "$temp_path" || ! mv "$temp_path" "$target_path"; then - rm -f -- "$temp_path" +payload_names="$(verify_payload)" || exit 1 +ssl_name="$(printf '%s\n' "$payload_names" | sed -n '1p')" +crypto_name="$(printf '%s\n' "$payload_names" | sed -n '2p')" +license_name="$(printf '%s\n' "$payload_names" | sed -n '3p')" + +probe_binary() { + local name="$1" probe_arg="$2" path="${source_dir}/$1" + if ! env -i "PATH=/usr/bin:/bin:/usr/sbin:/sbin" "HOME=${HOME:-/tmp}" "$path" "$probe_arg" >/dev/null; then + echo "binary probe failed for ${name} (${probe_arg})" >&2 return 1 fi } -verify_binary "codebase-graph" "--help" -verify_binary "k-wiki" "--version" +probe_binary "codebase-graph" "--help" +probe_binary "k-wiki" "--version" if [[ "$dry_run" == "true" ]]; then - printf 'validated %s and %s from %s\n' "codebase-graph" "k-wiki" "$source_dir" + printf 'validated %s, %s, and OpenSSL payloads from %s\n' "codebase-graph" "k-wiki" "$source_dir" exit 0 fi +install_file() { + local name="$1" mode="$2" + local source_path="${source_dir}/${name}" + local target_path="${target_bin_dir}/${name}" + local temp_path + temp_path="$(mktemp "${target_bin_dir}/.${name}.tmp.XXXXXX")" + if ! cp "$source_path" "$temp_path" || ! chmod "$mode" "$temp_path" || ! mv "$temp_path" "$target_path"; then + rm -f -- "$temp_path" + echo "failed to install ${name}" >&2 + return 1 + fi +} + mkdir -p "$target_bin_dir" -install_binary "codebase-graph" -install_binary "k-wiki" +install_file "$ssl_name" 0644 +install_file "$crypto_name" 0644 +install_file "$license_name" 0644 +install_file "codebase-graph" 0755 +install_file "k-wiki" 0755 -printf 'installed %s and %s to %s\n' "codebase-graph" "k-wiki" "$target_bin_dir" +printf 'installed %s, %s, and OpenSSL payloads to %s\n' "codebase-graph" "k-wiki" "$target_bin_dir" diff --git a/src/materialization_worker.rs b/src/materialization_worker.rs index 1fd60afa..15393cf4 100644 --- a/src/materialization_worker.rs +++ b/src/materialization_worker.rs @@ -10,6 +10,7 @@ use crate::process_lifecycle::{ use crate::protocol::{ GraphSummary, ManifestDiff, NativeSyntaxMaterializationResponse, ProgressEvent, }; +#[cfg(any(debug_assertions, test))] use crate::storage::atomic::write_json_atomically; use crate::storage::layout::{managed_generation_id, DirectLayout, ManagedLayout}; use crate::storage::locks::{try_open_locked, LockMode, WorkerLease};