From b82a0ac0bfb5a55093d8c05b780a2447ed99ab6a Mon Sep 17 00:00:00 2001 From: "Abdulaziz M. Shehri" Date: Wed, 26 Aug 2026 18:22:05 +0300 Subject: [PATCH 1/7] fix(oracle): stage verified archives for adapter --- crates/commandf-pkg/src/oracle_process.rs | 79 ++++++++++++++++++++++- 1 file changed, 77 insertions(+), 2 deletions(-) diff --git a/crates/commandf-pkg/src/oracle_process.rs b/crates/commandf-pkg/src/oracle_process.rs index bd5281bb..c003b7ef 100644 --- a/crates/commandf-pkg/src/oracle_process.rs +++ b/crates/commandf-pkg/src/oracle_process.rs @@ -1,9 +1,12 @@ -use std::io::{self, Read}; -use std::path::Path; +use std::fs::{self, OpenOptions}; +use std::io::{self, Read, Write}; +use std::path::{Path, PathBuf}; use std::process::{Child, Command, Stdio}; use std::thread; use std::time::{Duration, Instant}; +use tempfile::TempDir; + use crate::{parse_hl7_oracle_report, Hl7OracleReport, OracleError}; pub const DEFAULT_ORACLE_TIMEOUT_SECS: u64 = 60; @@ -20,6 +23,78 @@ pub struct Hl7OracleInvocation<'a> { pub right_version: Option<&'a str>, } +pub struct Hl7OracleStagedArchives { + _directory: TempDir, + core_package: PathBuf, + left_package: PathBuf, + right_package: PathBuf, +} + +impl Hl7OracleStagedArchives { + pub fn new(core: &[u8], left: &[u8], right: &[u8]) -> Result { + let directory = tempfile::tempdir().map_err(|source| OracleError::AdapterIo { + operation: "creating staged oracle directory", + source, + })?; + let core_package = stage_archive(directory.path(), "core.tgz", core)?; + let left_package = stage_archive(directory.path(), "left.tgz", left)?; + let right_package = stage_archive(directory.path(), "right.tgz", right)?; + Ok(Self { + _directory: directory, + core_package, + left_package, + right_package, + }) + } + + pub fn core_package(&self) -> &Path { + &self.core_package + } + + pub fn left_package(&self) -> &Path { + &self.left_package + } + + pub fn right_package(&self) -> &Path { + &self.right_package + } +} + +fn stage_archive(root: &Path, name: &str, bytes: &[u8]) -> Result { + let path = root.join(name); + let mut file = OpenOptions::new() + .write(true) + .create_new(true) + .open(&path) + .map_err(|source| OracleError::AdapterIo { + operation: "creating staged oracle archive", + source, + })?; + file.write_all(bytes) + .map_err(|source| OracleError::AdapterIo { + operation: "writing staged oracle archive", + source, + })?; + file.sync_all().map_err(|source| OracleError::AdapterIo { + operation: "syncing staged oracle archive", + source, + })?; + drop(file); + + let mut permissions = fs::metadata(&path) + .map_err(|source| OracleError::AdapterIo { + operation: "reading staged oracle archive metadata", + source, + })? + .permissions(); + permissions.set_readonly(true); + fs::set_permissions(&path, permissions).map_err(|source| OracleError::AdapterIo { + operation: "protecting staged oracle archive", + source, + })?; + Ok(path) +} + pub fn validate_hl7_oracle_adapter(adapter: &Path, java: Option<&Path>) -> Result<(), OracleError> { if !adapter.is_file() { return Err(OracleError::AdapterPath { From 54bb5bd92ef1ca3062510f8a361620f5344b739e Mon Sep 17 00:00:00 2001 From: "Abdulaziz M. Shehri" Date: Wed, 26 Aug 2026 18:22:24 +0300 Subject: [PATCH 2/7] fix(oracle): export staged archive snapshot --- crates/commandf-pkg/src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/commandf-pkg/src/lib.rs b/crates/commandf-pkg/src/lib.rs index 51f206d4..94a65fdd 100644 --- a/crates/commandf-pkg/src/lib.rs +++ b/crates/commandf-pkg/src/lib.rs @@ -109,7 +109,8 @@ pub use oracle_model::{ }; pub use oracle_process::{ run_hl7_oracle_adapter, validate_hl7_oracle_adapter, Hl7OracleInvocation, - DEFAULT_ORACLE_TIMEOUT_SECS, MAX_ORACLE_STDERR_BYTES, MAX_ORACLE_STDOUT_BYTES, + Hl7OracleStagedArchives, DEFAULT_ORACLE_TIMEOUT_SECS, MAX_ORACLE_STDERR_BYTES, + MAX_ORACLE_STDOUT_BYTES, }; pub use oracle_reconcile::{ parse_hl7_oracle_report, reconcile_hl7_oracle, validate_hl7_oracle_report, From 674bf3519a9881f18797e27431eaede17eea7761 Mon Sep 17 00:00:00 2001 From: "Abdulaziz M. Shehri" Date: Wed, 26 Aug 2026 18:22:38 +0300 Subject: [PATCH 3/7] fix(impact): consume verified cache bytes --- crates/commandf-cli/src/impact.rs | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/crates/commandf-cli/src/impact.rs b/crates/commandf-cli/src/impact.rs index c0a6d2b2..f2ad954c 100644 --- a/crates/commandf-cli/src/impact.rs +++ b/crates/commandf-cli/src/impact.rs @@ -24,11 +24,8 @@ pub fn run( let before_cache = PackageCache::new(before_cache); let after_cache = PackageCache::new(after_cache); - before_cache.verify(&before_locked.sha256)?; - after_cache.verify(&after_locked.sha256)?; - - let before_bytes = read_locked_archive(&before_cache, before_locked)?; - let after_bytes = read_locked_archive(&after_cache, after_locked)?; + let before_bytes = before_cache.read_verified(&before_locked.sha256)?; + let after_bytes = after_cache.read_verified(&after_locked.sha256)?; let diff = diff_package_archives( package_name.to_string(), &before_locked.version, @@ -57,15 +54,6 @@ fn require_lock_v2(lockfile: &Lockfile, side: &'static str) -> io::Result<()> { )) } -fn read_locked_archive(cache: &PackageCache, locked: &LockedPackage) -> io::Result> { - fs::read( - cache - .root() - .join("sha256") - .join(format!("{}.tgz", locked.sha256)), - ) -} - fn select_locked_package<'a>( lockfile: &'a Lockfile, package_name: &str, From 5aad1ce9580bd595bda8c9b5ee3f1fe6cef4c157 Mon Sep 17 00:00:00 2001 From: "Abdulaziz M. Shehri" Date: Wed, 26 Aug 2026 18:23:00 +0300 Subject: [PATCH 4/7] fix(oracle): consume staged verified archives --- crates/commandf-cli/src/oracle.rs | 33 +++++++++++-------------------- 1 file changed, 11 insertions(+), 22 deletions(-) diff --git a/crates/commandf-cli/src/oracle.rs b/crates/commandf-cli/src/oracle.rs index 791f88fc..c130eebc 100644 --- a/crates/commandf-cli/src/oracle.rs +++ b/crates/commandf-cli/src/oracle.rs @@ -5,8 +5,9 @@ use std::time::Duration; use commandf_pkg::{ diff_package_archives, matched_structure_definition_pairs, reconcile_hl7_oracle, - run_hl7_oracle_adapter, validate_hl7_oracle_adapter, Hl7OracleInvocation, LockedPackage, - Lockfile, PackageCache, PackageName, ResourceKey, ResourceKeyKind, DEFAULT_ORACLE_TIMEOUT_SECS, + run_hl7_oracle_adapter, validate_hl7_oracle_adapter, Hl7OracleInvocation, + Hl7OracleStagedArchives, LockedPackage, Lockfile, PackageCache, PackageName, ResourceKey, + ResourceKeyKind, DEFAULT_ORACLE_TIMEOUT_SECS, }; const ORACLE_CORE_PACKAGE: &str = "hl7.fhir.r4.core"; @@ -44,17 +45,12 @@ pub fn run( let before_cache = PackageCache::new(before_cache); let after_cache = PackageCache::new(after_cache); - before_cache.verify(&before_locked.sha256)?; - after_cache.verify(&after_locked.sha256)?; - before_cache.verify(&before_core.sha256)?; - after_cache.verify(&after_core.sha256)?; - - let before_archive = archive_path(&before_cache, before_locked); - let after_archive = archive_path(&after_cache, after_locked); - let core_archive = archive_path(&before_cache, before_core); - let before_bytes = fs::read(&before_archive)?; - let after_bytes = fs::read(&after_archive)?; + let before_bytes = before_cache.read_verified(&before_locked.sha256)?; + let after_bytes = after_cache.read_verified(&after_locked.sha256)?; + let core_bytes = before_cache.read_verified(&before_core.sha256)?; + after_cache.read_verified(&after_core.sha256)?; + let staged = Hl7OracleStagedArchives::new(&core_bytes, &before_bytes, &after_bytes)?; let structural_diff = diff_package_archives( package_name.to_string(), &before_locked.version, @@ -83,9 +79,9 @@ pub fn run( } let (url, version) = canonical_parts(&pair.resource)?; let invocation = Hl7OracleInvocation { - core_package: &core_archive, - left_package: &before_archive, - right_package: &after_archive, + core_package: staged.core_package(), + left_package: staged.left_package(), + right_package: staged.right_package(), left_url: url, left_version: version, right_url: url, @@ -142,13 +138,6 @@ fn select_locked_package<'a>( Ok(selected) } -fn archive_path(cache: &PackageCache, package: &LockedPackage) -> PathBuf { - cache - .root() - .join("sha256") - .join(format!("{}.tgz", package.sha256)) -} - fn canonical_parts(resource: &ResourceKey) -> Result<(&str, Option<&str>), io::Error> { if resource.kind != ResourceKeyKind::Canonical { return Err(io::Error::new( From 231639bc3fb5fb31ce0ede1d666797de205f4699 Mon Sep 17 00:00:00 2001 From: "Abdulaziz M. Shehri" Date: Wed, 26 Aug 2026 18:23:36 +0300 Subject: [PATCH 5/7] test(oracle): prove staged snapshot resists source rebinding --- crates/commandf-pkg/tests/oracle_process.rs | 46 ++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/crates/commandf-pkg/tests/oracle_process.rs b/crates/commandf-pkg/tests/oracle_process.rs index ac2611cf..68743194 100644 --- a/crates/commandf-pkg/tests/oracle_process.rs +++ b/crates/commandf-pkg/tests/oracle_process.rs @@ -5,7 +5,9 @@ use std::os::unix::fs::PermissionsExt; use std::path::{Path, PathBuf}; use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; -use commandf_pkg::{run_hl7_oracle_adapter, Hl7OracleInvocation}; +use commandf_pkg::{ + run_hl7_oracle_adapter, Hl7OracleInvocation, Hl7OracleStagedArchives, +}; const GOOD_REPORT: &str = r#"{"schema":1,"oracle":{"project":"hapifhir/org.hl7.fhir.core","release":"6.10.2","source_commit":"d06577dbc5c62c74a2a8823fbc4830a3024d5b0b"},"left":{"url":"http://example.org/StructureDefinition/test","version":null,"id":"test","type":"Patient"},"right":{"url":"http://example.org/StructureDefinition/test","version":null,"id":"test","type":"Patient"},"states":{"metadata":"not_changed","definitions":"not_changed","content":"unknown","content_interpretation":"unknown"},"messages":[]}"#; @@ -72,6 +74,48 @@ fn executable_adapter_accepts_valid_pinned_json() { let _ = fs::remove_dir_all(root); } +#[test] +fn staged_archives_are_independent_of_original_source_after_boundary() { + let root = unique_temp_dir("staged-snapshot"); + fs::create_dir_all(&root).expect("create temp dir"); + let original = root.join("original-left.tgz"); + fs::write(&original, b"verified-generation").expect("write original archive"); + let verified_bytes = fs::read(&original).expect("read verified generation"); + let staged = Hl7OracleStagedArchives::new(b"core", &verified_bytes, b"right") + .expect("stage verified archives"); + + fs::write(&original, b"mutated-after-boundary").expect("mutate original cache generation"); + + let adapter = root.join("adapter.sh"); + write_executable( + &adapter, + &format!( + "left=''\nwhile [ \"$#\" -gt 0 ]; do\n case \"$1\" in\n --left-package) left=\"$2\"; shift 2 ;;\n *) shift ;;\n esac\ndone\ntest \"$(cat \"$left\")\" = 'verified-generation' || exit 9\nprintf '%s\\n' '{}'", + GOOD_REPORT + ), + ); + let invocation = Hl7OracleInvocation { + core_package: staged.core_package(), + left_package: staged.left_package(), + right_package: staged.right_package(), + left_url: "http://example.org/StructureDefinition/test", + left_version: None, + right_url: "http://example.org/StructureDefinition/test", + right_version: None, + }; + let report = run_hl7_oracle_adapter( + &adapter, + None, + &invocation, + Duration::from_secs(1), + ) + .expect("adapter must consume the staged verified snapshot"); + + assert_eq!(report.schema, 1); + assert_eq!(fs::read(&original).unwrap(), b"mutated-after-boundary"); + let _ = fs::remove_dir_all(root); +} + #[test] fn jar_adapter_requires_explicit_java_path() { let root = unique_temp_dir("java-required"); From 01852eb94a33a6f522aae695c9be88c16b08cf17 Mon Sep 17 00:00:00 2001 From: "Abdulaziz M. Shehri" Date: Wed, 26 Aug 2026 18:25:23 +0300 Subject: [PATCH 6/7] fix(oracle): make staged permissions portable --- crates/commandf-pkg/src/oracle_process.rs | 28 ++++++++++++++--------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/crates/commandf-pkg/src/oracle_process.rs b/crates/commandf-pkg/src/oracle_process.rs index c003b7ef..e427c19f 100644 --- a/crates/commandf-pkg/src/oracle_process.rs +++ b/crates/commandf-pkg/src/oracle_process.rs @@ -1,4 +1,4 @@ -use std::fs::{self, OpenOptions}; +use std::fs::OpenOptions; use std::io::{self, Read, Write}; use std::path::{Path, PathBuf}; use std::process::{Child, Command, Stdio}; @@ -80,19 +80,25 @@ fn stage_archive(root: &Path, name: &str, bytes: &[u8]) -> Result Result<(), OracleError> { + use std::fs; + use std::os::unix::fs::PermissionsExt; + + let permissions = fs::Permissions::from_mode(0o400); + fs::set_permissions(path, permissions).map_err(|source| OracleError::AdapterIo { operation: "protecting staged oracle archive", source, - })?; - Ok(path) + }) +} + +#[cfg(not(unix))] +fn protect_staged_archive(_path: &Path) -> Result<(), OracleError> { + Ok(()) } pub fn validate_hl7_oracle_adapter(adapter: &Path, java: Option<&Path>) -> Result<(), OracleError> { From a5a62694ec7dd913ab96bc4110fb07c822c55288 Mon Sep 17 00:00:00 2001 From: "Abdulaziz M. Shehri" Date: Wed, 26 Aug 2026 18:25:43 +0300 Subject: [PATCH 7/7] fix(oracle): stage only when comparison runs --- crates/commandf-cli/src/oracle.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/commandf-cli/src/oracle.rs b/crates/commandf-cli/src/oracle.rs index c130eebc..b5267e0f 100644 --- a/crates/commandf-cli/src/oracle.rs +++ b/crates/commandf-cli/src/oracle.rs @@ -48,9 +48,8 @@ pub fn run( let before_bytes = before_cache.read_verified(&before_locked.sha256)?; let after_bytes = after_cache.read_verified(&after_locked.sha256)?; let core_bytes = before_cache.read_verified(&before_core.sha256)?; - after_cache.read_verified(&after_core.sha256)?; + let _after_core_bytes = after_cache.read_verified(&after_core.sha256)?; - let staged = Hl7OracleStagedArchives::new(&core_bytes, &before_bytes, &after_bytes)?; let structural_diff = diff_package_archives( package_name.to_string(), &before_locked.version, @@ -63,6 +62,7 @@ pub fn run( let mut observations = Vec::new(); if before_locked.sha256 != after_locked.sha256 { + let staged = Hl7OracleStagedArchives::new(&core_bytes, &before_bytes, &after_bytes)?; let pairs = matched_structure_definition_pairs( package_name.as_str(), &before_locked.version,