From 71dc27e92857c4215bbbe583f104de7f341dc816 Mon Sep 17 00:00:00 2001 From: rabii-chaarani Date: Tue, 27 Oct 2026 16:53:55 +0930 Subject: [PATCH 1/6] docs(spec): record COD-54 macOS OpenSSL approval --- .../approvals/cod54-macos-openssl.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 docs/specs/CBG-PLAT-01/approvals/cod54-macos-openssl.md 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. From a2005dbb37a9ab914ea97b2735825b5286af5b6e Mon Sep 17 00:00:00 2001 From: rabii-chaarani Date: Tue, 27 Oct 2026 17:40:54 +0930 Subject: [PATCH 2/6] fix(release): bundle relocatable macOS OpenSSL runtimes --- README.md | 11 +- crates/xtask/src/macos.rs | 958 ++++++++++++++++++++++ crates/xtask/src/macos_installer_tests.rs | 260 ++++++ crates/xtask/src/main.rs | 460 ++++++++--- docs/release.md | 41 +- release/install/install.sh | 135 ++- src/materialization_worker.rs | 1 + 7 files changed, 1714 insertions(+), 152 deletions(-) create mode 100644 crates/xtask/src/macos.rs create mode 100644 crates/xtask/src/macos_installer_tests.rs diff --git a/README.md b/README.md index 61eb1d17..33f65259 100644 --- a/README.md +++ b/README.md @@ -54,9 +54,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..843ee117 --- /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..39c2dccc 100644 --- a/docs/release.md +++ b/docs/release.md @@ -121,10 +121,30 @@ 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. + +Deployment metadata and current-host CI are not support qualification. The exact +installed archive must still run on clean Apple Silicon macOS 13.3 and a current +macOS release without Homebrew/OpenSSL. Record those OS builds and archive +hashes separately; unavailable minimum-host execution leaves qualification open. + 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 +169,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 proof of a clean minimum-OS environment. After upgrading from a release archive: 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}; From 855c7e282a27de4afbad2d4252d7692de4b42ecb Mon Sep 17 00:00:00 2001 From: rabii-chaarani Date: Tue, 27 Oct 2026 17:46:55 +0930 Subject: [PATCH 3/6] fix(release): clear strict macOS packager lint --- crates/xtask/src/macos.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/xtask/src/macos.rs b/crates/xtask/src/macos.rs index 843ee117..39b700b6 100644 --- a/crates/xtask/src/macos.rs +++ b/crates/xtask/src/macos.rs @@ -165,7 +165,7 @@ pub fn stage_runtime(library_dir: &Path, package: &Path) -> Result<(), String> { PAYLOAD_FILES[1] }; change_dependency( - &binary, + binary, &old_path, &format!("@executable_path/{staged_name}"), )?; From 55a54ec5a120849a8fd6fcf433ade6b4d5eb0567 Mon Sep 17 00:00:00 2001 From: rabii-chaarani Date: Tue, 27 Oct 2026 17:56:53 +0930 Subject: [PATCH 4/6] docs(evidence): record COD-54 source-bound archive verification --- .../execution/cod54-macos-openssl.md | 52 ++++++++ .../execution/cod54/artifact-attempt-1.log | 4 + .../cod54/artifact-attempt-2-inventory.json | 46 +++++++ .../execution/cod54/artifact-attempt-2.json | 15 +++ .../cod54/baseline-binary-manifest.json | 14 +++ .../execution/cod54/baseline-xtask.json | 9 ++ .../execution/cod54/checks-in-progress.json | 68 +++++++++++ .../execution/cod54/claim-reconciliation.json | 23 ++++ .../execution/cod54/clippy-final.json | 13 ++ .../execution/cod54/clippy-final.log | 4 + .../cod54/constants-and-assumptions.json | 22 ++++ .../execution/cod54/execution-state.md | 15 +++ .../execution/cod54/final-review.md | 75 ++++++++++++ .../execution/cod54/final-snapshot.json | 28 +++++ .../execution/cod54/final-xtask.junit.xml | 115 ++++++++++++++++++ .../execution/cod54/final-xtask.log | 62 ++++++++++ .../execution/cod54/handoff-state.json | 26 ++++ .../execution/cod54/initial-evidence.json | 24 ++++ .../cod54/installer-ownership-takeover.json | 9 ++ .../execution/cod54/installer-research.md | 23 ++++ .../execution/cod54/negative-evidence.json | 15 +++ .../execution/cod54/pax-compile-failure.log | 15 +++ .../execution/cod54/proposal-review.md | 65 ++++++++++ .../cod54/relocation-probe-result.json | 8 ++ .../cod54/reviewed-initial-snapshot.json | 25 ++++ .../execution/cod54/runtime-implementation.md | 1 + .../execution/cod54/runtime-research.md | 33 +++++ .../cod54/source-bound-artifact.json | 19 +++ .../cod54/source-bound-artifact.log.gz | Bin 0 -> 56500 bytes .../cod54/source-bound-inventory.json | 62 ++++++++++ .../execution/cod54/xtask-55-pass.junit.xml | 115 ++++++++++++++++++ .../execution/cod54/xtask-55-pass.log | 62 ++++++++++ .../execution/cod54/xtask-attempt-4.junit.xml | 113 +++++++++++++++++ .../execution/cod54/xtask-attempt-4.log | 61 ++++++++++ 34 files changed, 1241 insertions(+) create mode 100644 docs/specs/CBG-PLAT-01/execution/cod54-macos-openssl.md create mode 100644 docs/specs/CBG-PLAT-01/execution/cod54/artifact-attempt-1.log create mode 100644 docs/specs/CBG-PLAT-01/execution/cod54/artifact-attempt-2-inventory.json create mode 100644 docs/specs/CBG-PLAT-01/execution/cod54/artifact-attempt-2.json create mode 100644 docs/specs/CBG-PLAT-01/execution/cod54/baseline-binary-manifest.json create mode 100644 docs/specs/CBG-PLAT-01/execution/cod54/baseline-xtask.json create mode 100644 docs/specs/CBG-PLAT-01/execution/cod54/checks-in-progress.json create mode 100644 docs/specs/CBG-PLAT-01/execution/cod54/claim-reconciliation.json create mode 100644 docs/specs/CBG-PLAT-01/execution/cod54/clippy-final.json create mode 100644 docs/specs/CBG-PLAT-01/execution/cod54/clippy-final.log create mode 100644 docs/specs/CBG-PLAT-01/execution/cod54/constants-and-assumptions.json create mode 100644 docs/specs/CBG-PLAT-01/execution/cod54/execution-state.md create mode 100644 docs/specs/CBG-PLAT-01/execution/cod54/final-review.md create mode 100644 docs/specs/CBG-PLAT-01/execution/cod54/final-snapshot.json create mode 100644 docs/specs/CBG-PLAT-01/execution/cod54/final-xtask.junit.xml create mode 100644 docs/specs/CBG-PLAT-01/execution/cod54/final-xtask.log create mode 100644 docs/specs/CBG-PLAT-01/execution/cod54/handoff-state.json create mode 100644 docs/specs/CBG-PLAT-01/execution/cod54/initial-evidence.json create mode 100644 docs/specs/CBG-PLAT-01/execution/cod54/installer-ownership-takeover.json create mode 100644 docs/specs/CBG-PLAT-01/execution/cod54/installer-research.md create mode 100644 docs/specs/CBG-PLAT-01/execution/cod54/negative-evidence.json create mode 100644 docs/specs/CBG-PLAT-01/execution/cod54/pax-compile-failure.log create mode 100644 docs/specs/CBG-PLAT-01/execution/cod54/proposal-review.md create mode 100644 docs/specs/CBG-PLAT-01/execution/cod54/relocation-probe-result.json create mode 100644 docs/specs/CBG-PLAT-01/execution/cod54/reviewed-initial-snapshot.json create mode 100644 docs/specs/CBG-PLAT-01/execution/cod54/runtime-implementation.md create mode 100644 docs/specs/CBG-PLAT-01/execution/cod54/runtime-research.md create mode 100644 docs/specs/CBG-PLAT-01/execution/cod54/source-bound-artifact.json create mode 100644 docs/specs/CBG-PLAT-01/execution/cod54/source-bound-artifact.log.gz create mode 100644 docs/specs/CBG-PLAT-01/execution/cod54/source-bound-inventory.json create mode 100644 docs/specs/CBG-PLAT-01/execution/cod54/xtask-55-pass.junit.xml create mode 100644 docs/specs/CBG-PLAT-01/execution/cod54/xtask-55-pass.log create mode 100644 docs/specs/CBG-PLAT-01/execution/cod54/xtask-attempt-4.junit.xml create mode 100644 docs/specs/CBG-PLAT-01/execution/cod54/xtask-attempt-4.log 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..e6bf8f6b --- /dev/null +++ b/docs/specs/CBG-PLAT-01/execution/cod54-macos-openssl.md @@ -0,0 +1,52 @@ +# COD-54 — macOS bundled OpenSSL implementation and verification + +## 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 | running | Native test profile/captured CLI execution is separate from shipped release archive | +| Hosted macOS/Windows native CI | running on draft PR163 | Do not count queued/skipped jobs as passing | + +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. + +## Remaining qualification and handoff + +**COD-54 is not Done.** Actual final installed archive execution on clean Apple Silicon **macOS13.3 and a current release without Homebrew/OpenSSL** remains unexecuted and mandatory. Deployment metadata and developer-host success are not consumer qualification. Hosted native CI/full-suite evidence and final reviewer evidence reconciliation must also complete. 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..db513f1f --- /dev/null +++ b/docs/specs/CBG-PLAT-01/execution/cod54/checks-in-progress.json @@ -0,0 +1,68 @@ +{ + "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": "running", + "hosted_ci": "40472491235 queued" +} \ 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/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..cf5eef90 --- /dev/null +++ b/docs/specs/CBG-PLAT-01/execution/cod54/handoff-state.json @@ -0,0 +1,26 @@ +{ + "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": "running", + "hosted_ci": { + "run": 40472491235, + "status": "queued", + "source": "855c7e282a27de4afbad2d4252d7692de4b42ecb" + }, + "consumer_qualification": "notexecutedcleanMac13.3/current", + "closure": "COD54notDone,noaggregateclosure", + "fts_limit": "realLOADfts+officialbytes; populateddiskBM25search, notpopulatednativeFTSindex" +} \ 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/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 0000000000000000000000000000000000000000..133d8c5955b2a7fe13c6b3ee559474a69a98eb9d GIT binary patch literal 56500 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

9x>{y!cf$s3EoQR6<6n)|E4Oh$-j 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 From 1f6859618140b3f434475b56ab2f2fa470976519 Mon Sep 17 00:00:00 2001 From: rabii-chaarani Date: Tue, 27 Oct 2026 18:08:48 +0930 Subject: [PATCH 5/6] docs(release): qualify current macOS only --- README.md | 5 +++-- docs/release.md | 14 ++++++++----- .../execution/cod54-macos-openssl.md | 8 +++++++- .../execution/cod54/current-macos-policy.json | 20 +++++++++++++++++++ .../execution/cod54/handoff-state.json | 11 ++++++++-- 5 files changed, 48 insertions(+), 10 deletions(-) create mode 100644 docs/specs/CBG-PLAT-01/execution/cod54/current-macos-policy.json diff --git a/README.md b/README.md index 33f65259..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: diff --git a/docs/release.md b/docs/release.md index 39c2dccc..e6b30429 100644 --- a/docs/release.md +++ b/docs/release.md @@ -140,10 +140,14 @@ 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. -Deployment metadata and current-host CI are not support qualification. The exact -installed archive must still run on clean Apple Silicon macOS 13.3 and a current -macOS release without Homebrew/OpenSSL. Record those OS builds and archive -hashes separately; unavailable minimum-host execution leaves qualification open. +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. @@ -184,7 +188,7 @@ 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 proof of a clean minimum-OS environment. +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/execution/cod54-macos-openssl.md b/docs/specs/CBG-PLAT-01/execution/cod54-macos-openssl.md index e6bf8f6b..e2c0d582 100644 --- a/docs/specs/CBG-PLAT-01/execution/cod54-macos-openssl.md +++ b/docs/specs/CBG-PLAT-01/execution/cod54-macos-openssl.md @@ -1,5 +1,11 @@ # 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). @@ -45,7 +51,7 @@ Earlier compiler/fixture/PAX failures are retained in linked evidence and are no ## Remaining qualification and handoff -**COD-54 is not Done.** Actual final installed archive execution on clean Apple Silicon **macOS13.3 and a current release without Homebrew/OpenSSL** remains unexecuted and mandatory. Deployment metadata and developer-host success are not consumer qualification. Hosted native CI/full-suite evidence and final reviewer evidence reconciliation must also complete. Aggregate closure stays with the maintainer; do not infer S10/COD-61 closure. +**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. 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/handoff-state.json b/docs/specs/CBG-PLAT-01/execution/cod54/handoff-state.json index cf5eef90..b8a45f8c 100644 --- a/docs/specs/CBG-PLAT-01/execution/cod54/handoff-state.json +++ b/docs/specs/CBG-PLAT-01/execution/cod54/handoff-state.json @@ -20,7 +20,14 @@ "status": "queued", "source": "855c7e282a27de4afbad2d4252d7692de4b42ecb" }, - "consumer_qualification": "notexecutedcleanMac13.3/current", + "consumer_qualification": "currentstablemacOSAppleSiliconactualOS/build+noexternalOpenSSLruntime evidence required; no13.3gate", "closure": "COD54notDone,noaggregateclosure", - "fts_limit": "realLOADfts+officialbytes; populateddiskBM25search, notpopulatednativeFTSindex" + "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 From 1c8d6a3c445620897ed153ebea9842244f94ea2e Mon Sep 17 00:00:00 2001 From: rabii-chaarani Date: Tue, 27 Oct 2026 19:54:11 +0930 Subject: [PATCH 6/6] docs(evidence): record full native pass and hosted CI blockers --- .../execution/cod54-macos-openssl.md | 12 +- .../execution/cod54/checks-in-progress.json | 21 +- .../cod54/ci-40474966274-failed.log.gz | Bin 0 -> 69823 bytes .../cod54/ci-40474966274-summary.json | 42 ++++ .../execution/cod54/handoff-state.json | 21 +- .../execution/cod54/native-full-report.json | 227 ++++++++++++++++++ .../execution/cod54/native-full-summary.json | 31 +++ .../execution/cod54/native-full.log.gz | Bin 0 -> 32740 bytes 8 files changed, 346 insertions(+), 8 deletions(-) create mode 100644 docs/specs/CBG-PLAT-01/execution/cod54/ci-40474966274-failed.log.gz create mode 100644 docs/specs/CBG-PLAT-01/execution/cod54/ci-40474966274-summary.json create mode 100644 docs/specs/CBG-PLAT-01/execution/cod54/native-full-report.json create mode 100644 docs/specs/CBG-PLAT-01/execution/cod54/native-full-summary.json create mode 100644 docs/specs/CBG-PLAT-01/execution/cod54/native-full.log.gz diff --git a/docs/specs/CBG-PLAT-01/execution/cod54-macos-openssl.md b/docs/specs/CBG-PLAT-01/execution/cod54-macos-openssl.md index e2c0d582..c0d70f71 100644 --- a/docs/specs/CBG-PLAT-01/execution/cod54-macos-openssl.md +++ b/docs/specs/CBG-PLAT-01/execution/cod54-macos-openssl.md @@ -38,8 +38,8 @@ Environment: Apple Silicon ARM64, macOS26.6.2 build25G83; rustc1.96.0/cargo1.96. | 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 | running | Native test profile/captured CLI execution is separate from shipped release archive | -| Hosted macOS/Windows native CI | running on draft PR163 | Do not count queued/skipped jobs as passing | +| 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. @@ -49,6 +49,14 @@ Earlier compiler/fixture/PAX failures are retained in linked evidence and are no - 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. 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 index db513f1f..72c57d51 100644 --- a/docs/specs/CBG-PLAT-01/execution/cod54/checks-in-progress.json +++ b/docs/specs/CBG-PLAT-01/execution/cod54/checks-in-progress.json @@ -63,6 +63,23 @@ "source_bound_archive_pipeline": 0 }, "final_review": "final-review.md GREEN boundedhandoff", - "full_native_suite": "running", - "hosted_ci": "40472491235 queued" + "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 0000000000000000000000000000000000000000..4dfb4eed910fb28bc2c7a25c449613374a63e715 GIT binary patch literal 69823 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

^|E9EM#+1O+4VkPYBb7`9aiOmi6X6StdH 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 z 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