commit 3f35c869c827b35b27e7a7d789d409e6c3def6a8
parent 2fd262f2c8f4306c622437bc0c4c36dbfcb331e1
Author: triesap <tyson@radroots.org>
Date: Wed, 5 Aug 2026 23:54:57 +0000
consolidation: preserve donor history
- Freeze verified source archives, path mappings, and dual-source controls.
- Validate bundle integrity, commit maps, ancestry, attribution, and patches.
- Rehearse merge-bearing filtering, restoration, and a ref-neutral import.
- Reject context, workflow, secret, bot, license, and object-integrity drift.
Diffstat:
3 files changed, 1448 insertions(+), 7 deletions(-)
diff --git a/contracts/consolidation/history.v1.toml b/contracts/consolidation/history.v1.toml
@@ -0,0 +1,304 @@
+schema_version = 1
+history_id = "radroots.rust.consolidation.history.v1"
+retention_locator = "external://radroots-rust-consolidation-v1-20260805"
+hash_algorithm = "sha256"
+bundle_hash_algorithm = "sha1"
+required_commit_map_fields = [
+ "source_commit",
+ "target_commit",
+ "source_parents",
+ "target_parents",
+ "source_subject",
+ "target_subject",
+ "source_author",
+ "target_author",
+ "source_author_time",
+ "target_author_time",
+ "source_committer_time",
+ "target_committer_time",
+ "source_paths",
+ "normalized_patch_sha256",
+ "empty_commit_disposition",
+]
+required_verifications = [
+ "archive_restore",
+ "git_fsck",
+ "mapped_parent_closure",
+ "commit_count",
+ "path_coverage",
+ "normalized_patch_equivalence",
+ "final_tree_digest",
+ "authorship",
+ "timestamps",
+ "message_scope_only",
+ "git_log_follow",
+ "secret_scan",
+ "license_scan",
+ "bot_identity_scan",
+ "github_workflow_exclusion",
+ "context_firewall",
+]
+
+[dual_source]
+state = "pre_import"
+canonical_owner_before_import = "frozen_donor_commit"
+canonical_owner_after_import = "verified_lib_import_merge"
+emergency_fix_flow = "fix canonical owner, qualify, then replay the normalized patch into the overlap copy"
+divergence_policy = "forbidden"
+exit_condition = "all consumers use one qualified lib revision and rollback evidence is green"
+
+[[archive]]
+source_id = "sdk"
+kind = "git_bundle"
+frozen_commit = "170ecf2b620107fadca85fcb11fbf3798b4ca1ef"
+artifact = "sdk-170ecf2b620107fadca85fcb11fbf3798b4ca1ef.bundle"
+sha256 = "9d2b015bbcd5d516994eeb72064b1e938a04acd0c21eb2e22134b99599fdd2f0"
+bytes = 2020833
+source_commit_count = 427
+source_path_commit_count = 397
+rewritten_commit_count = 395
+topology_support_commit_count = 0
+omitted_empty_commit_count = 2
+source_object_count = 6077
+refname = "refs/heads/master"
+bot_identity_scan = true
+
+[[archive]]
+source_id = "app_rt"
+kind = "git_bundle"
+frozen_commit = "7ab1a8624d50890d6d18545ffb47d8083afa8c67"
+artifact = "app_rt-7ab1a8624d50890d6d18545ffb47d8083afa8c67.bundle"
+sha256 = "9a9ba8e6002ee3d6fcb232e18fb84dbc2885bb37e7bdba736b44a1e5bb6489b0"
+bytes = 167343
+source_commit_count = 60
+source_path_commit_count = 49
+rewritten_commit_count = 56
+topology_support_commit_count = 7
+omitted_empty_commit_count = 0
+source_object_count = 508
+refname = "refs/heads/master"
+bot_identity_scan = true
+
+[[archive]]
+source_id = "studio_app"
+kind = "git_bundle"
+frozen_commit = "2b5fe5d8321fd0e248a9d304651b1ba54ee6a180"
+artifact = "studio_app-2b5fe5d8321fd0e248a9d304651b1ba54ee6a180.bundle"
+sha256 = "27917b71b12392cb005d7125726853c51e56aa615d014a6caa087d4cbd261849"
+bytes = 8772591
+source_commit_count = 1230
+source_path_commit_count = 95
+rewritten_commit_count = 93
+topology_support_commit_count = 0
+omitted_empty_commit_count = 2
+source_object_count = 16143
+refname = "refs/heads/master"
+bot_identity_scan = true
+
+[[archive]]
+source_id = "studio_mpl_legacy_core"
+kind = "path_fast_export"
+frozen_commit = "6074a4745be361f21bb47d4778c74a14b2d57954"
+artifact = "studio_mpl_legacy_core-6074a4745be361f21bb47d4778c74a14b2d57954.fast-export"
+sha256 = "55152f9f2b29fe902c85909f1c9147e5cf0a852f7e23e4ad781638be06389f89"
+bytes = 31305
+source_commit_count = 1
+source_path_commit_count = 1
+rewritten_commit_count = 1
+topology_support_commit_count = 0
+omitted_empty_commit_count = 0
+source_object_count = 10
+refname = "refs/heads/master"
+bot_identity_scan = true
+
+[[path_map]]
+source_id = "sdk"
+source = "crates/sdk"
+target = "crates/sdk"
+package = "radroots_sdk"
+license = "MIT OR Apache-2.0"
+disposition = "retain"
+
+[[path_map]]
+source_id = "sdk"
+source = "crates/radroots"
+target = "crates/radroots"
+package = "radroots"
+license = "MIT OR Apache-2.0"
+disposition = "retain"
+
+[[path_map]]
+source_id = "sdk"
+source = "crates/core_bindings"
+target = "crates/core_bindings"
+package = "radroots_core_bindings"
+license = "MIT OR Apache-2.0"
+disposition = "retain_private"
+
+[[path_map]]
+source_id = "sdk"
+source = "crates/event_bindings"
+target = "crates/event_bindings"
+package = "radroots_event_bindings"
+license = "MIT OR Apache-2.0"
+disposition = "retain_private"
+
+[[path_map]]
+source_id = "sdk"
+source = "crates/identity_bindings"
+target = "crates/identity_bindings"
+package = "radroots_identity_bindings"
+license = "MIT OR Apache-2.0"
+disposition = "retain_private"
+
+[[path_map]]
+source_id = "sdk"
+source = "crates/trade_bindings"
+target = "crates/trade_bindings"
+package = "radroots_trade_bindings"
+license = "MIT OR Apache-2.0"
+disposition = "retain_private"
+
+[[path_map]]
+source_id = "sdk"
+source = "crates/replica_schema_bindings"
+target = "crates/replica_schema_bindings"
+package = "radroots_replica_schema_bindings"
+license = "MIT OR Apache-2.0"
+disposition = "retain_private"
+
+[[path_map]]
+source_id = "sdk"
+source = "crates/event_codec_wasm"
+target = "crates/event_codec_wasm"
+package = "radroots_event_codec_wasm"
+license = "MIT OR Apache-2.0"
+disposition = "retain_private"
+
+[[path_map]]
+source_id = "sdk"
+source = "crates/replica_store_wasm"
+target = "crates/replica_store_wasm"
+package = "radroots_replica_store_wasm"
+license = "MIT OR Apache-2.0"
+disposition = "retain_private"
+
+[[path_map]]
+source_id = "sdk"
+source = "crates/replica_sync_wasm"
+target = "crates/replica_sync_wasm"
+package = "radroots_replica_sync_wasm"
+license = "MIT OR Apache-2.0"
+disposition = "retain_private"
+
+[[path_map]]
+source_id = "sdk"
+source = "crates/ffi"
+target = "crates/sdk_ffi"
+package = "radroots_sdk_ffi"
+license = "MIT OR Apache-2.0"
+disposition = "retain_private"
+
+[[path_map]]
+source_id = "sdk"
+source = "crates/sql_wasm_runtime"
+target = "crates/sdk_sql_wasm_runtime"
+package = "radroots_sdk_sql_wasm_runtime"
+license = "MIT OR Apache-2.0"
+disposition = "retain_private"
+
+[[path_map]]
+source_id = "sdk"
+source = "tools/xtask"
+target = "tools/sdk_xtask_import"
+package = "xtask"
+license = "MIT OR Apache-2.0"
+disposition = "merge_then_retire"
+
+[[path_map]]
+source_id = "app_rt"
+source = "crates/core"
+target = "crates/mobile_core"
+package = "radroots_mobile_core"
+license = "GPL-3.0-or-later"
+disposition = "retain_private"
+
+[[path_map]]
+source_id = "app_rt"
+source = "crates/ffi"
+target = "crates/mobile_ffi"
+package = "radroots_mobile_ffi"
+license = "GPL-3.0-or-later"
+disposition = "retain_private"
+
+[[path_map]]
+source_id = "app_rt"
+source = "crates/wasm"
+target = "crates/mobile_wasm"
+package = "radroots_mobile_wasm"
+license = "GPL-3.0-or-later"
+disposition = "retain_private"
+
+[[path_map]]
+source_id = "app_rt"
+source = "crates/bindgen"
+target = "crates/mobile_bindgen"
+package = "radroots_mobile_bindgen"
+license = "GPL-3.0-or-later"
+disposition = "retain_private"
+
+[[path_map]]
+source_id = "studio_app"
+source = "core/crates/domain"
+target = "crates/studio_domain"
+package = "radroots_studio_domain"
+license = "GPL-3.0-only"
+disposition = "retain_private"
+
+[[path_map]]
+source_id = "studio_app"
+source = "core/crates/application"
+target = "crates/studio_application"
+package = "radroots_studio_application"
+license = "GPL-3.0-only"
+disposition = "retain_private"
+
+[[path_map]]
+source_id = "studio_app"
+source = "core/crates/nostr"
+target = "crates/studio_nostr"
+package = "radroots_studio_nostr"
+license = "GPL-3.0-only"
+disposition = "retain_private"
+
+[[path_map]]
+source_id = "studio_app"
+source = "core/crates/storage"
+target = "crates/studio_storage"
+package = "radroots_studio_storage"
+license = "GPL-3.0-only"
+disposition = "retain_private"
+
+[[path_map]]
+source_id = "studio_app"
+source = "core/crates/ffi"
+target = "crates/studio_ffi"
+package = "radroots_studio_ffi"
+license = "GPL-3.0-only"
+disposition = "retain_private"
+
+[[path_map]]
+source_id = "studio_app"
+source = "core/tools/uniffi-bindgen"
+target = "crates/studio_uniffi_bindgen"
+package = "radroots_studio_uniffi_bindgen"
+license = "GPL-3.0-only"
+disposition = "retain_private"
+
+[[path_map]]
+source_id = "studio_mpl_legacy_core"
+source = "studio_app/studio_app_core"
+target = "imports/studio_mpl_legacy_core"
+package = "studio_app_core"
+license = "MPL-2.0"
+disposition = "import_unique_behavior_then_retire"
diff --git a/tools/xtask/src/consolidation.rs b/tools/xtask/src/consolidation.rs
@@ -1,16 +1,21 @@
use std::{
collections::{BTreeMap, BTreeSet},
fs,
+ io::Read,
path::{Component, Path},
+ process::Command,
};
use serde::Deserialize;
+use sha2::{Digest, Sha256};
const BASELINE_RELATIVE: &str = "contracts/consolidation/baseline.v1.toml";
const STEP_MAP_RELATIVE: &str = "contracts/consolidation/handoff_steps.v1.toml";
+const HISTORY_RELATIVE: &str = "contracts/consolidation/history.v1.toml";
const BASELINE_ID: &str = "radroots.rust.consolidation.baseline.v1";
const STEP_MAP_ID: &str = "radroots.rust.consolidation.handoff-steps.v1";
const ARCHITECTURE_TARGET: &str = "radroots.crates.release.v2";
+const HISTORY_ID: &str = "radroots.rust.consolidation.history.v1";
const PACKAGE_VERSION: &str = "0.1.0-alpha";
const EXPECTED_PUBLIC_PACKAGES: u16 = 19;
const EXPECTED_HANDOFF_STEPS: u16 = 275;
@@ -119,13 +124,102 @@ struct StepRange {
reason: String,
}
-pub fn validate(workspace_root: &Path) -> Result<(), String> {
+#[derive(Debug, Deserialize)]
+#[serde(deny_unknown_fields)]
+struct HistoryContract {
+ schema_version: u16,
+ history_id: String,
+ retention_locator: String,
+ hash_algorithm: String,
+ bundle_hash_algorithm: String,
+ required_commit_map_fields: Vec<String>,
+ required_verifications: Vec<String>,
+ dual_source: DualSource,
+ archive: Vec<Archive>,
+ path_map: Vec<PathMap>,
+}
+
+#[derive(Debug, Deserialize)]
+#[serde(deny_unknown_fields)]
+struct DualSource {
+ state: String,
+ canonical_owner_before_import: String,
+ canonical_owner_after_import: String,
+ emergency_fix_flow: String,
+ divergence_policy: String,
+ exit_condition: String,
+}
+
+#[derive(Debug, Deserialize)]
+#[serde(deny_unknown_fields)]
+struct Archive {
+ source_id: String,
+ kind: String,
+ frozen_commit: String,
+ artifact: String,
+ sha256: String,
+ bytes: u64,
+ source_commit_count: u64,
+ source_path_commit_count: u64,
+ rewritten_commit_count: u64,
+ topology_support_commit_count: u64,
+ omitted_empty_commit_count: u64,
+ source_object_count: u64,
+ refname: String,
+ bot_identity_scan: bool,
+}
+
+#[derive(Debug, Deserialize)]
+#[serde(deny_unknown_fields)]
+struct PathMap {
+ source_id: String,
+ source: String,
+ target: String,
+ package: String,
+ license: String,
+ disposition: String,
+}
+
+#[derive(Clone, Debug, Eq, PartialEq)]
+struct CommitMapEntry {
+ source: String,
+ target: Option<String>,
+}
+
+pub fn run(args: &[String], workspace_root: &Path) -> Result<(), String> {
+ match args {
+ [command] if command == "baseline" => validate_baseline_contracts(workspace_root),
+ [command] if command == "history" => validate_history_contract(workspace_root, None),
+ [command] if command == "history-rehearsal" => run_history_rehearsal(),
+ [command, flag, archive_root] if command == "history" && flag == "--archive-root" => {
+ validate_history_contract(workspace_root, Some(Path::new(archive_root)))
+ }
+ _ => Err(
+ "consolidation accepts baseline, history [--archive-root <absolute-directory>], or history-rehearsal"
+ .to_owned(),
+ ),
+ }
+}
+
+pub fn validate_baseline_contracts(workspace_root: &Path) -> Result<(), String> {
let baseline = read_toml::<Baseline>(workspace_root, BASELINE_RELATIVE)?;
let step_map = read_toml::<StepMap>(workspace_root, STEP_MAP_RELATIVE)?;
validate_baseline(&baseline)?;
validate_step_map(&step_map)
}
+fn validate_history_contract(
+ workspace_root: &Path,
+ archive_root: Option<&Path>,
+) -> Result<(), String> {
+ let history = read_toml::<HistoryContract>(workspace_root, HISTORY_RELATIVE)?;
+ validate_history(&history)?;
+ if let Some(archive_root) = archive_root {
+ validate_archives(&history, archive_root)?;
+ }
+ Ok(())
+}
+
fn read_toml<T: for<'de> Deserialize<'de>>(
workspace_root: &Path,
relative: &str,
@@ -348,6 +442,1034 @@ fn validate_step_map(step_map: &StepMap) -> Result<(), String> {
Ok(())
}
+fn validate_history(history: &HistoryContract) -> Result<(), String> {
+ if history.schema_version != 1
+ || history.history_id != HISTORY_ID
+ || history.retention_locator != "external://radroots-rust-consolidation-v1-20260805"
+ || history.hash_algorithm != "sha256"
+ || history.bundle_hash_algorithm != "sha1"
+ {
+ return Err("history preservation identity drifted".to_owned());
+ }
+
+ let expected_commit_map_fields = BTreeSet::from([
+ "empty_commit_disposition",
+ "normalized_patch_sha256",
+ "source_author",
+ "source_author_time",
+ "source_commit",
+ "source_committer_time",
+ "source_parents",
+ "source_paths",
+ "source_subject",
+ "target_author",
+ "target_author_time",
+ "target_commit",
+ "target_committer_time",
+ "target_parents",
+ "target_subject",
+ ]);
+ if to_unique_set(&history.required_commit_map_fields, "commit map field")?
+ != expected_commit_map_fields
+ {
+ return Err("commit map requirements drifted".to_owned());
+ }
+ let expected_verifications = BTreeSet::from([
+ "archive_restore",
+ "authorship",
+ "bot_identity_scan",
+ "commit_count",
+ "context_firewall",
+ "final_tree_digest",
+ "git_fsck",
+ "git_log_follow",
+ "github_workflow_exclusion",
+ "license_scan",
+ "mapped_parent_closure",
+ "message_scope_only",
+ "normalized_patch_equivalence",
+ "path_coverage",
+ "secret_scan",
+ "timestamps",
+ ]);
+ if to_unique_set(&history.required_verifications, "verification")? != expected_verifications {
+ return Err("history verification requirements drifted".to_owned());
+ }
+ let dual_source = &history.dual_source;
+ if dual_source.state != "pre_import"
+ || dual_source.canonical_owner_before_import != "frozen_donor_commit"
+ || dual_source.canonical_owner_after_import != "verified_lib_import_merge"
+ || dual_source.divergence_policy != "forbidden"
+ || dual_source.emergency_fix_flow.trim().is_empty()
+ || dual_source.exit_condition.trim().is_empty()
+ {
+ return Err("dual-source control drifted".to_owned());
+ }
+
+ let expected_sources =
+ BTreeSet::from(["app_rt", "sdk", "studio_app", "studio_mpl_legacy_core"]);
+ let mut archives = BTreeMap::new();
+ let mut artifact_names = BTreeSet::new();
+ for archive in &history.archive {
+ validate_identifier(&archive.source_id, "archive source id")?;
+ if archives
+ .insert(archive.source_id.as_str(), archive)
+ .is_some()
+ {
+ return Err(format!("duplicate archive source {}", archive.source_id));
+ }
+ validate_oid(&archive.frozen_commit, "archive frozen commit")?;
+ validate_sha256(&archive.sha256, "archive digest")?;
+ validate_artifact_name(&archive.artifact)?;
+ if !artifact_names.insert(archive.artifact.as_str()) {
+ return Err(format!("duplicate archive artifact {}", archive.artifact));
+ }
+ if !matches!(archive.kind.as_str(), "git_bundle" | "path_fast_export")
+ || archive.bytes == 0
+ || archive.source_commit_count == 0
+ || archive.source_path_commit_count == 0
+ || archive.source_path_commit_count > archive.source_commit_count
+ || archive.rewritten_commit_count == 0
+ || archive.rewritten_commit_count + archive.omitted_empty_commit_count
+ != archive.source_path_commit_count + archive.topology_support_commit_count
+ || archive.source_object_count == 0
+ || archive.refname != "refs/heads/master"
+ || !archive.bot_identity_scan
+ {
+ return Err(format!("archive {} is incomplete", archive.source_id));
+ }
+ }
+ if archives.keys().copied().collect::<BTreeSet<_>>() != expected_sources {
+ return Err("history archive inventory is incomplete".to_owned());
+ }
+ if archives["studio_mpl_legacy_core"].kind != "path_fast_export"
+ || archives
+ .iter()
+ .filter(|(id, archive)| {
+ **id != "studio_mpl_legacy_core" && archive.kind == "git_bundle"
+ })
+ .count()
+ != 3
+ {
+ return Err("history archive kinds drifted".to_owned());
+ }
+
+ let allowed_licenses = BTreeSet::from([
+ "GPL-3.0-only",
+ "GPL-3.0-or-later",
+ "MIT OR Apache-2.0",
+ "MPL-2.0",
+ ]);
+ let allowed_dispositions = BTreeSet::from([
+ "import_unique_behavior_then_retire",
+ "merge_then_retire",
+ "retain",
+ "retain_private",
+ ]);
+ let mut source_paths = BTreeSet::new();
+ let mut target_paths = BTreeSet::new();
+ let mut package_sources = BTreeSet::new();
+ for path_map in &history.path_map {
+ if !archives.contains_key(path_map.source_id.as_str()) {
+ return Err(format!("unknown path-map source {}", path_map.source_id));
+ }
+ validate_relative_path(&path_map.source)?;
+ validate_relative_path(&path_map.target)?;
+ validate_identifier(&path_map.package, "path-map package")?;
+ if !allowed_licenses.contains(path_map.license.as_str())
+ || !allowed_dispositions.contains(path_map.disposition.as_str())
+ {
+ return Err(format!(
+ "path map {}/{} has invalid license or disposition",
+ path_map.source_id, path_map.source
+ ));
+ }
+ if !source_paths.insert((path_map.source_id.as_str(), path_map.source.as_str())) {
+ return Err(format!(
+ "duplicate source path {}/{}",
+ path_map.source_id, path_map.source
+ ));
+ }
+ if !target_paths.insert(path_map.target.as_str()) {
+ return Err(format!("duplicate target path {}", path_map.target));
+ }
+ if !package_sources.insert((path_map.source_id.as_str(), path_map.package.as_str())) {
+ return Err(format!(
+ "duplicate package {} in source {}",
+ path_map.package, path_map.source_id
+ ));
+ }
+ }
+ for source in expected_sources {
+ if !source_paths
+ .iter()
+ .any(|(candidate, _)| *candidate == source)
+ {
+ return Err(format!("source {source} has no path map"));
+ }
+ }
+ Ok(())
+}
+
+fn validate_archives(history: &HistoryContract, archive_root: &Path) -> Result<(), String> {
+ if !archive_root.is_absolute() {
+ return Err("archive root must be absolute".to_owned());
+ }
+ let root_metadata = fs::symlink_metadata(archive_root)
+ .map_err(|error| format!("inspect archive root {}: {error}", archive_root.display()))?;
+ if root_metadata.file_type().is_symlink() || !root_metadata.is_dir() {
+ return Err("archive root must be a real directory, not a symlink".to_owned());
+ }
+
+ for archive in &history.archive {
+ let artifact = archive_root.join(&archive.artifact);
+ let metadata = fs::symlink_metadata(&artifact)
+ .map_err(|error| format!("inspect archive {}: {error}", artifact.display()))?;
+ if metadata.file_type().is_symlink() || !metadata.is_file() {
+ return Err(format!(
+ "archive {} must be a regular non-symlink file",
+ artifact.display()
+ ));
+ }
+ if metadata.len() != archive.bytes {
+ return Err(format!(
+ "archive {} size drifted: expected {}, got {}",
+ artifact.display(),
+ archive.bytes,
+ metadata.len()
+ ));
+ }
+ let digest = sha256_file(&artifact)?;
+ if digest != archive.sha256 {
+ return Err(format!("archive {} digest drifted", artifact.display()));
+ }
+ if archive.kind == "git_bundle" {
+ let output = Command::new("git")
+ .args(["bundle", "verify"])
+ .arg(&artifact)
+ .output()
+ .map_err(|error| format!("run git bundle verify: {error}"))?;
+ if !output.status.success() {
+ return Err(format!(
+ "git bundle verify failed for {}: {}",
+ artifact.display(),
+ String::from_utf8_lossy(&output.stderr).trim()
+ ));
+ }
+ } else {
+ validate_fast_export(&artifact, archive)?;
+ }
+ }
+ Ok(())
+}
+
+fn validate_fast_export(path: &Path, archive: &Archive) -> Result<(), String> {
+ let raw = fs::read_to_string(path)
+ .map_err(|error| format!("read fast-export archive {}: {error}", path.display()))?;
+ if !raw.contains(&format!("original-oid {}", archive.frozen_commit))
+ || !raw.contains("reset refs/heads/master")
+ || !raw.contains("author triesap <tyson@radroots.org>")
+ || !raw.contains("committer triesap <tyson@radroots.org>")
+ || raw.to_ascii_lowercase().contains("github-actions")
+ || raw.to_ascii_lowercase().contains("[bot]")
+ {
+ return Err("legacy Studio fast-export identity or ref drifted".to_owned());
+ }
+ for line in raw.lines() {
+ let Some(path) = line
+ .strip_prefix("M ")
+ .and_then(|line| line.splitn(3, ' ').nth(2))
+ else {
+ continue;
+ };
+ if !path.starts_with("studio_app/studio_app_core/") {
+ return Err(format!("fast-export contains out-of-scope path {path}"));
+ }
+ }
+ Ok(())
+}
+
+fn run_history_rehearsal() -> Result<(), String> {
+ let fixture = tempfile::TempDir::new()
+ .map_err(|error| format!("create history rehearsal root: {error}"))?;
+ let source = fixture.path().join("source");
+ let restored = fixture.path().join("restored");
+ let filtered = fixture.path().join("filtered");
+ let import_target = fixture.path().join("import-target");
+ let bundle = fixture.path().join("source.bundle");
+
+ create_history_fixture(&source)?;
+ git(&source, &["bundle", "create", path_arg(&bundle)?, "master"])?;
+ git(
+ fixture.path(),
+ &["clone", path_arg(&bundle)?, path_arg(&restored)?],
+ )?;
+ git(&restored, &["fsck", "--full", "--strict"])?;
+
+ git(
+ fixture.path(),
+ &["clone", path_arg(&bundle)?, path_arg(&filtered)?],
+ )?;
+ git(
+ &filtered,
+ &[
+ "filter-repo",
+ "--force",
+ "--path",
+ "src/",
+ "--path-rename",
+ "src/:crates/imported/",
+ ],
+ )?;
+ let commit_map_path = filtered.join(".git/filter-repo/commit-map");
+ let commit_map = parse_commit_map(&commit_map_path)?;
+ verify_filtered_history(&source, &filtered, &commit_map, "src", "crates/imported")?;
+
+ create_import_target(&import_target)?;
+ git(
+ &import_target,
+ &[
+ "fetch",
+ path_arg(&filtered)?,
+ "master:refs/remotes/rehearsal/imported",
+ ],
+ )?;
+ let merge_tree = git_stdout(
+ &import_target,
+ &[
+ "merge-tree",
+ "--write-tree",
+ "--allow-unrelated-histories",
+ "HEAD",
+ "refs/remotes/rehearsal/imported",
+ ],
+ )?;
+ let merge_tree = merge_tree.trim();
+ validate_oid(merge_tree, "rehearsal merge tree")?;
+ if git_stdout(&import_target, &["cat-file", "-t", merge_tree])?.trim() != "tree" {
+ return Err("no-op import rehearsal did not produce a tree".to_owned());
+ }
+ if git_stdout(&import_target, &["status", "--porcelain"])
+ .map(|output| !output.trim().is_empty())?
+ {
+ return Err("no-op import rehearsal changed the target worktree".to_owned());
+ }
+
+ exercise_history_negative_cases(&source, &filtered, &commit_map)?;
+ Ok(())
+}
+
+fn create_history_fixture(root: &Path) -> Result<(), String> {
+ fs::create_dir(root).map_err(|error| format!("create {}: {error}", root.display()))?;
+ git(root, &["init", "--initial-branch=master"])?;
+ git(root, &["config", "user.name", "Radroots History Fixture"])?;
+ git(
+ root,
+ &["config", "user.email", "history-fixture@radroots.org"],
+ )?;
+ write_fixture(root, "src/LICENSE", "MIT OR Apache-2.0\n")?;
+ write_fixture(root, "src/item.txt", "base\n")?;
+ write_fixture(root, "AGENTS.md", "context only\n")?;
+ fixture_commit(root, "seed reusable source", "2001-01-01T00:00:00+00:00")?;
+
+ git(root, &["branch", "feature"])?;
+ append_fixture(root, "src/item.txt", "main\n")?;
+ fixture_commit(root, "extend main source", "2001-01-02T00:00:00+00:00")?;
+ append_fixture(root, "AGENTS.md", "must not import\n")?;
+ fixture_commit(root, "change donor context", "2001-01-03T00:00:00+00:00")?;
+
+ git(root, &["checkout", "feature"])?;
+ write_fixture(root, "src/feature.txt", "feature\n")?;
+ fixture_commit(root, "add feature source", "2001-01-04T00:00:00+00:00")?;
+ git(root, &["checkout", "master"])?;
+ git_with_identity(
+ root,
+ &["merge", "--no-ff", "feature", "-m", "merge reusable source"],
+ "2001-01-05T00:00:00+00:00",
+ )?;
+ write_fixture(root, ".github/workflows/forbidden.yml", "forbidden: true\n")?;
+ fixture_commit(
+ root,
+ "add forbidden donor automation",
+ "2001-01-06T00:00:00+00:00",
+ )?;
+ append_fixture(root, "src/item.txt", "final\n")?;
+ fixture_commit(root, "finish reusable source", "2001-01-07T00:00:00+00:00")?;
+ Ok(())
+}
+
+fn create_import_target(root: &Path) -> Result<(), String> {
+ fs::create_dir(root).map_err(|error| format!("create {}: {error}", root.display()))?;
+ git(root, &["init", "--initial-branch=master"])?;
+ git(root, &["config", "user.name", "Radroots History Fixture"])?;
+ git(
+ root,
+ &["config", "user.email", "history-fixture@radroots.org"],
+ )?;
+ write_fixture(root, "README", "import target\n")?;
+ fixture_commit(root, "seed import target", "2001-01-08T00:00:00+00:00")
+}
+
+fn write_fixture(root: &Path, relative: &str, contents: &str) -> Result<(), String> {
+ let path = root.join(relative);
+ if let Some(parent) = path.parent() {
+ fs::create_dir_all(parent)
+ .map_err(|error| format!("create {}: {error}", parent.display()))?;
+ }
+ fs::write(&path, contents).map_err(|error| format!("write {}: {error}", path.display()))
+}
+
+fn append_fixture(root: &Path, relative: &str, contents: &str) -> Result<(), String> {
+ use std::io::Write;
+
+ let path = root.join(relative);
+ let mut file = fs::OpenOptions::new()
+ .append(true)
+ .open(&path)
+ .map_err(|error| format!("open {}: {error}", path.display()))?;
+ file.write_all(contents.as_bytes())
+ .map_err(|error| format!("append {}: {error}", path.display()))
+}
+
+fn fixture_commit(root: &Path, subject: &str, timestamp: &str) -> Result<(), String> {
+ git(root, &["add", "--all"])?;
+ git_with_identity(root, &["commit", "-m", subject], timestamp)
+}
+
+fn git_with_identity(root: &Path, args: &[&str], timestamp: &str) -> Result<(), String> {
+ let output = Command::new("git")
+ .args(args)
+ .current_dir(root)
+ .env("GIT_AUTHOR_DATE", timestamp)
+ .env("GIT_COMMITTER_DATE", timestamp)
+ .output()
+ .map_err(|error| format!("run git {}: {error}", args.join(" ")))?;
+ command_success(output, root, args).map(|_| ())
+}
+
+fn parse_commit_map(path: &Path) -> Result<Vec<CommitMapEntry>, String> {
+ let raw = fs::read_to_string(path)
+ .map_err(|error| format!("read commit map {}: {error}", path.display()))?;
+ let mut entries = Vec::new();
+ for (line_index, line) in raw.lines().enumerate() {
+ if line_index == 0 && line == "old new" {
+ continue;
+ }
+ let fields = line.split_ascii_whitespace().collect::<Vec<_>>();
+ if fields.len() != 2 {
+ return Err(format!("commit map line {} is malformed", line_index + 1));
+ }
+ validate_oid(fields[0], "source commit-map object")?;
+ validate_oid(fields[1], "target commit-map object")?;
+ entries.push(CommitMapEntry {
+ source: fields[0].to_owned(),
+ target: (fields[1] != "0000000000000000000000000000000000000000")
+ .then(|| fields[1].to_owned()),
+ });
+ }
+ if entries.is_empty() {
+ return Err("commit map contains no entries".to_owned());
+ }
+ let unique = entries
+ .iter()
+ .map(|entry| entry.source.as_str())
+ .collect::<BTreeSet<_>>();
+ if unique.len() != entries.len() {
+ return Err("commit map contains duplicate source commits".to_owned());
+ }
+ Ok(entries)
+}
+
+fn verify_filtered_history(
+ source: &Path,
+ target: &Path,
+ entries: &[CommitMapEntry],
+ source_prefix: &str,
+ target_prefix: &str,
+) -> Result<(), String> {
+ validate_relative_path(source_prefix)?;
+ validate_relative_path(target_prefix)?;
+ git(source, &["fsck", "--full", "--strict"])?;
+ git(target, &["fsck", "--full", "--strict"])?;
+
+ let by_source = entries
+ .iter()
+ .map(|entry| (entry.source.as_str(), entry.target.as_deref()))
+ .collect::<BTreeMap<_, _>>();
+ let mut saw_merge = false;
+ let mut saw_omitted = false;
+ for entry in entries {
+ git(
+ source,
+ &["cat-file", "-e", &format!("{}^{{commit}}", entry.source)],
+ )?;
+ let Some(target_commit) = entry.target.as_deref() else {
+ saw_omitted = true;
+ continue;
+ };
+ git(
+ target,
+ &["cat-file", "-e", &format!("{target_commit}^{{commit}}")],
+ )?;
+ verify_commit_metadata(source, target, &entry.source, target_commit)?;
+ let source_parents = commit_parents(source, &entry.source)?;
+ saw_merge |= source_parents.len() > 1;
+ let mut expected_target_parents = Vec::new();
+ for parent in source_parents {
+ collect_effective_parents(source, &parent, &by_source, &mut expected_target_parents)?;
+ }
+ deduplicate(&mut expected_target_parents);
+ if commit_parents(target, target_commit)? != expected_target_parents {
+ return Err(format!(
+ "mapped parent closure drifted for {}",
+ entry.source
+ ));
+ }
+ let source_patch = normalized_patch(source, &entry.source, source_prefix, "__IMPORT__")?;
+ let target_patch = normalized_patch(target, target_commit, target_prefix, "__IMPORT__")?;
+ if source_patch != target_patch {
+ return Err(format!("normalized patch drifted for {}", entry.source));
+ }
+ }
+ if !saw_merge || !saw_omitted {
+ return Err("history rehearsal must include a merge and an omitted commit".to_owned());
+ }
+
+ let source_head = git_stdout(source, &["rev-parse", "master"])?;
+ let source_head = source_head.trim();
+ let expected_target_head = by_source
+ .get(source_head)
+ .and_then(|target| *target)
+ .ok_or_else(|| "source master is not retained in commit map".to_owned())?;
+ if git_stdout(target, &["rev-parse", "master"])?.trim() != expected_target_head {
+ return Err("filtered master has unexpected commits".to_owned());
+ }
+ verify_final_tree(
+ source,
+ target,
+ source_head,
+ expected_target_head,
+ source_prefix,
+ target_prefix,
+ )?;
+ verify_context_firewall(target, expected_target_head, target_prefix)?;
+ verify_commit_identities(target, expected_target_head)?;
+ verify_follow_history(source, target, source_prefix, target_prefix)?;
+ Ok(())
+}
+
+fn verify_commit_metadata(
+ source: &Path,
+ target: &Path,
+ source_commit: &str,
+ target_commit: &str,
+) -> Result<(), String> {
+ let format = "%an%x00%ae%x00%aI%x00%cn%x00%ce%x00%cI%x00%s";
+ let source_meta = git_stdout(
+ source,
+ &["show", "-s", &format!("--format={format}"), source_commit],
+ )?;
+ let target_meta = git_stdout(
+ target,
+ &["show", "-s", &format!("--format={format}"), target_commit],
+ )?;
+ let mut source_fields = source_meta.trim_end().split('\0').collect::<Vec<_>>();
+ let mut target_fields = target_meta.trim_end().split('\0').collect::<Vec<_>>();
+ if source_fields.len() != 7 || target_fields.len() != 7 {
+ return Err("commit metadata has unexpected cardinality".to_owned());
+ }
+ let source_subject = source_fields.pop().expect("cardinality checked");
+ let target_subject = target_fields.pop().expect("cardinality checked");
+ if source_fields != target_fields || !message_is_preserved(source_subject, target_subject) {
+ return Err(format!(
+ "attribution or message drifted for {source_commit}"
+ ));
+ }
+ Ok(())
+}
+
+fn message_is_preserved(source: &str, target: &str) -> bool {
+ if source == target {
+ return true;
+ }
+ let Some(scope) = target
+ .strip_prefix(source)
+ .and_then(|suffix| suffix.strip_prefix(" ("))
+ .and_then(|suffix| suffix.strip_suffix(')'))
+ else {
+ return false;
+ };
+ !scope.is_empty()
+ && scope.bytes().all(|byte| {
+ byte.is_ascii_lowercase() || byte.is_ascii_digit() || matches!(byte, b'_' | b'-' | b'/')
+ })
+}
+
+fn commit_parents(repo: &Path, commit: &str) -> Result<Vec<String>, String> {
+ let output = git_stdout(repo, &["show", "-s", "--format=%P", commit])?;
+ Ok(output.split_ascii_whitespace().map(str::to_owned).collect())
+}
+
+fn collect_effective_parents(
+ source: &Path,
+ commit: &str,
+ by_source: &BTreeMap<&str, Option<&str>>,
+ output: &mut Vec<String>,
+) -> Result<(), String> {
+ match by_source.get(commit).copied().flatten() {
+ Some(target) => output.push(target.to_owned()),
+ None => {
+ for parent in commit_parents(source, commit)? {
+ collect_effective_parents(source, &parent, by_source, output)?;
+ }
+ }
+ }
+ Ok(())
+}
+
+fn deduplicate(values: &mut Vec<String>) {
+ let mut seen = BTreeSet::new();
+ values.retain(|value| seen.insert(value.clone()));
+}
+
+fn normalized_patch(
+ repo: &Path,
+ commit: &str,
+ prefix: &str,
+ normalized_prefix: &str,
+) -> Result<String, String> {
+ let patch = git_stdout(
+ repo,
+ &[
+ "-c",
+ "core.quotePath=false",
+ "diff-tree",
+ "--root",
+ "-m",
+ "-r",
+ "--binary",
+ "--full-index",
+ "--no-commit-id",
+ commit,
+ "--",
+ prefix,
+ ],
+ )?;
+ Ok(patch.replace(prefix, normalized_prefix))
+}
+
+fn verify_final_tree(
+ source: &Path,
+ target: &Path,
+ source_commit: &str,
+ target_commit: &str,
+ source_prefix: &str,
+ target_prefix: &str,
+) -> Result<(), String> {
+ let source_tree = tree_manifest(source, source_commit, source_prefix, source_prefix)?;
+ let target_tree = tree_manifest(target, target_commit, target_prefix, source_prefix)?;
+ if source_tree != target_tree {
+ return Err("filtered final tree drifted".to_owned());
+ }
+ Ok(())
+}
+
+fn tree_manifest(
+ repo: &Path,
+ commit: &str,
+ prefix: &str,
+ normalized_prefix: &str,
+) -> Result<String, String> {
+ let output = git_stdout(
+ repo,
+ &["ls-tree", "-r", "--full-tree", commit, "--", prefix],
+ )?;
+ Ok(output.replace(prefix, normalized_prefix))
+}
+
+fn verify_context_firewall(repo: &Path, commit: &str, target_prefix: &str) -> Result<(), String> {
+ let paths = git_stdout(repo, &["ls-tree", "-r", "--name-only", commit])?;
+ let required_prefix = format!("{target_prefix}/");
+ for path in paths.lines() {
+ let lower = path.to_ascii_lowercase();
+ if !path.starts_with(&required_prefix)
+ || path.split('/').any(|segment| segment == ".github")
+ || matches!(path.rsplit('/').next(), Some("AGENTS.md" | "CLAUDE.md"))
+ || lower.ends_with(".pem")
+ || lower.ends_with(".key")
+ || lower.ends_with("/.env")
+ {
+ return Err(format!("context firewall rejected {path}"));
+ }
+ let contents = git_stdout(repo, &["show", &format!("{commit}:{path}")])?;
+ let lower_contents = contents.to_ascii_lowercase();
+ if contents.contains("PRIVATE KEY-----")
+ || contents.contains("ghp_")
+ || lower_contents.contains("github-actions[bot]")
+ {
+ return Err(format!("secret or bot content rejected in {path}"));
+ }
+ }
+ Ok(())
+}
+
+fn verify_commit_identities(repo: &Path, commit: &str) -> Result<(), String> {
+ let identities = git_stdout(repo, &["log", "--format=%an%x00%ae%x00%cn%x00%ce", commit])?;
+ let lower = identities.to_ascii_lowercase();
+ if lower.contains("github-actions") || lower.contains("[bot]") {
+ return Err("bot identity found in filtered history".to_owned());
+ }
+ Ok(())
+}
+
+fn verify_follow_history(
+ source: &Path,
+ target: &Path,
+ source_prefix: &str,
+ target_prefix: &str,
+) -> Result<(), String> {
+ let source_file = format!("{source_prefix}/item.txt");
+ let target_file = format!("{target_prefix}/item.txt");
+ let source_log = git_stdout(
+ source,
+ &["log", "--follow", "--format=%s", "--", &source_file],
+ )?;
+ let target_log = git_stdout(
+ target,
+ &["log", "--follow", "--format=%s", "--", &target_file],
+ )?;
+ if source_log != target_log {
+ return Err("git log --follow attribution drifted".to_owned());
+ }
+ Ok(())
+}
+
+fn exercise_history_negative_cases(
+ source: &Path,
+ filtered: &Path,
+ entries: &[CommitMapEntry],
+) -> Result<(), String> {
+ if message_is_preserved("preserve this", "rewritten message")
+ || message_is_preserved("preserve this", "preserve this (Bad Scope)")
+ {
+ return Err("message negative fixture was accepted".to_owned());
+ }
+ let malformed_map = filtered.join("malformed-commit-map");
+ fs::write(&malformed_map, "old new\nnot-an-oid still-not-an-oid\n")
+ .map_err(|error| format!("write negative commit map: {error}"))?;
+ if parse_commit_map(&malformed_map).is_ok() {
+ return Err("malformed commit map was accepted".to_owned());
+ }
+ let head = entries
+ .iter()
+ .rev()
+ .find_map(|entry| entry.target.as_deref())
+ .ok_or_else(|| "negative fixture has no target head".to_owned())?;
+ if verify_context_firewall(filtered, head, "wrong/prefix").is_ok() {
+ return Err("context-firewall negative fixture was accepted".to_owned());
+ }
+ if normalized_patch(source, &entries[0].source, "src", "__IMPORT__")?
+ == normalized_patch(filtered, head, "crates/imported", "__WRONG__")?
+ {
+ return Err("normalized-patch negative fixture was accepted".to_owned());
+ }
+
+ let source_head = git_stdout(source, &["rev-parse", "master"])?;
+ let source_head = source_head.trim();
+ let negative_root = filtered
+ .parent()
+ .ok_or_else(|| "filtered fixture has no parent".to_owned())?;
+
+ let context = clone_negative(filtered, negative_root, "negative-context")?;
+ write_fixture(
+ &context,
+ "AGENTS.md",
+ "must not cross the source boundary\n",
+ )?;
+ fixture_commit(&context, "inject context", "2001-02-01T00:00:00+00:00")?;
+ let context_head = git_stdout(&context, &["rev-parse", "HEAD"])?;
+ if verify_context_firewall(&context, context_head.trim(), "crates/imported").is_ok() {
+ return Err("context negative fixture was accepted".to_owned());
+ }
+
+ let workflow = clone_negative(filtered, negative_root, "negative-workflow")?;
+ write_fixture(
+ &workflow,
+ ".github/workflows/forbidden.yml",
+ "forbidden: true\n",
+ )?;
+ fixture_commit(&workflow, "inject workflow", "2001-02-02T00:00:00+00:00")?;
+ let workflow_head = git_stdout(&workflow, &["rev-parse", "HEAD"])?;
+ if verify_context_firewall(&workflow, workflow_head.trim(), "crates/imported").is_ok() {
+ return Err("GitHub-workflow negative fixture was accepted".to_owned());
+ }
+
+ let secret = clone_negative(filtered, negative_root, "negative-secret")?;
+ write_fixture(
+ &secret,
+ "crates/imported/secret.pem",
+ "-----BEGIN PRIVATE KEY-----\nfixture\n",
+ )?;
+ fixture_commit(&secret, "inject secret", "2001-02-03T00:00:00+00:00")?;
+ let secret_head = git_stdout(&secret, &["rev-parse", "HEAD"])?;
+ if verify_context_firewall(&secret, secret_head.trim(), "crates/imported").is_ok() {
+ return Err("secret negative fixture was accepted".to_owned());
+ }
+
+ let bot = clone_negative(filtered, negative_root, "negative-bot")?;
+ git_commit_with_actor(
+ &bot,
+ "inject bot identity",
+ "github-actions[bot]",
+ "41898282+github-actions[bot]@users.noreply.github.com",
+ "2001-02-04T00:00:00+00:00",
+ true,
+ )?;
+ let bot_head = git_stdout(&bot, &["rev-parse", "HEAD"])?;
+ if verify_commit_identities(&bot, bot_head.trim()).is_ok() {
+ return Err("bot-identity negative fixture was accepted".to_owned());
+ }
+
+ let license = clone_negative(filtered, negative_root, "negative-license")?;
+ write_fixture(&license, "crates/imported/LICENSE", "GPL-3.0-only\n")?;
+ fixture_commit(&license, "change license", "2001-02-05T00:00:00+00:00")?;
+ let license_head = git_stdout(&license, &["rev-parse", "HEAD"])?;
+ if verify_final_tree(
+ source,
+ &license,
+ source_head,
+ license_head.trim(),
+ "src",
+ "crates/imported",
+ )
+ .is_ok()
+ {
+ return Err("license/tree negative fixture was accepted".to_owned());
+ }
+
+ let attribution = clone_negative(filtered, negative_root, "negative-attribution")?;
+ git_commit_with_actor(
+ &attribution,
+ "finish reusable source",
+ "Wrong Author",
+ "wrong-author@radroots.org",
+ "2001-01-07T00:00:00+00:00",
+ false,
+ )?;
+ let attribution_head = git_stdout(&attribution, &["rev-parse", "HEAD"])?;
+ if verify_commit_metadata(source, &attribution, source_head, attribution_head.trim()).is_ok() {
+ return Err("attribution negative fixture was accepted".to_owned());
+ }
+
+ let timestamp = clone_negative(filtered, negative_root, "negative-timestamp")?;
+ git_commit_with_actor(
+ ×tamp,
+ "finish reusable source",
+ "Radroots History Fixture",
+ "history-fixture@radroots.org",
+ "2002-01-07T00:00:00+00:00",
+ false,
+ )?;
+ let timestamp_head = git_stdout(×tamp, &["rev-parse", "HEAD"])?;
+ if verify_commit_metadata(source, ×tamp, source_head, timestamp_head.trim()).is_ok() {
+ return Err("timestamp negative fixture was accepted".to_owned());
+ }
+
+ let message = clone_negative(filtered, negative_root, "negative-message")?;
+ git_commit_with_actor(
+ &message,
+ "replace the original message",
+ "Radroots History Fixture",
+ "history-fixture@radroots.org",
+ "2001-01-07T00:00:00+00:00",
+ false,
+ )?;
+ let message_head = git_stdout(&message, &["rev-parse", "HEAD"])?;
+ if verify_commit_metadata(source, &message, source_head, message_head.trim()).is_ok() {
+ return Err("message negative fixture was accepted".to_owned());
+ }
+
+ let follow = clone_negative(filtered, negative_root, "negative-follow")?;
+ append_fixture(&follow, "crates/imported/item.txt", "unmapped\n")?;
+ fixture_commit(
+ &follow,
+ "inject unmapped history",
+ "2001-02-06T00:00:00+00:00",
+ )?;
+ if verify_follow_history(source, &follow, "src", "crates/imported").is_ok() {
+ return Err("git-log-follow negative fixture was accepted".to_owned());
+ }
+
+ let mut broken_map = entries.to_vec();
+ let replacement = broken_map
+ .iter()
+ .find_map(|entry| entry.target.clone())
+ .ok_or_else(|| "negative fixture has no replacement target".to_owned())?;
+ broken_map
+ .last_mut()
+ .ok_or_else(|| "negative fixture has no final map entry".to_owned())?
+ .target = Some(replacement);
+ if verify_filtered_history(source, filtered, &broken_map, "src", "crates/imported").is_ok() {
+ return Err("commit-map topology negative fixture was accepted".to_owned());
+ }
+
+ let corrupt = clone_negative(filtered, negative_root, "negative-fsck")?;
+ let payload = corrupt.join("fsck-negative-payload");
+ fs::write(&payload, "unique fsck negative fixture payload\n")
+ .map_err(|error| format!("write fsck negative payload: {error}"))?;
+ let object = git_stdout(&corrupt, &["hash-object", "-w", path_arg(&payload)?])?;
+ let object = object.trim();
+ validate_oid(object, "fsck negative object")?;
+ let object_path = corrupt
+ .join(".git/objects")
+ .join(&object[..2])
+ .join(&object[2..]);
+ fs::remove_file(&object_path).map_err(|error| {
+ format!(
+ "unlink exact temporary negative object {}: {error}",
+ object_path.display()
+ )
+ })?;
+ fs::write(&object_path, "corrupt")
+ .map_err(|error| format!("corrupt negative object {}: {error}", object_path.display()))?;
+ if git(&corrupt, &["fsck", "--full", "--strict"]).is_ok() {
+ return Err("fsck negative fixture was accepted".to_owned());
+ }
+ Ok(())
+}
+
+fn clone_negative(source: &Path, root: &Path, name: &str) -> Result<std::path::PathBuf, String> {
+ let target = root.join(name);
+ git(root, &["clone", path_arg(source)?, path_arg(&target)?])?;
+ git(
+ &target,
+ &["config", "user.name", "Radroots History Fixture"],
+ )?;
+ git(
+ &target,
+ &["config", "user.email", "history-fixture@radroots.org"],
+ )?;
+ Ok(target)
+}
+
+fn git_commit_with_actor(
+ root: &Path,
+ subject: &str,
+ actor: &str,
+ email: &str,
+ timestamp: &str,
+ allow_empty: bool,
+) -> Result<(), String> {
+ let mut command = Command::new("git");
+ command
+ .current_dir(root)
+ .args(["commit", "--amend", "-m", subject])
+ .env("GIT_AUTHOR_NAME", actor)
+ .env("GIT_AUTHOR_EMAIL", email)
+ .env("GIT_COMMITTER_NAME", actor)
+ .env("GIT_COMMITTER_EMAIL", email)
+ .env("GIT_AUTHOR_DATE", timestamp)
+ .env("GIT_COMMITTER_DATE", timestamp);
+ if allow_empty {
+ command.arg("--allow-empty");
+ }
+ let output = command
+ .output()
+ .map_err(|error| format!("run negative commit fixture: {error}"))?;
+ command_success(output, root, &["commit", "--amend"]).map(|_| ())
+}
+
+fn git(root: &Path, args: &[&str]) -> Result<(), String> {
+ let output = Command::new("git")
+ .args(args)
+ .current_dir(root)
+ .output()
+ .map_err(|error| format!("run git {}: {error}", args.join(" ")))?;
+ command_success(output, root, args).map(|_| ())
+}
+
+fn git_stdout(root: &Path, args: &[&str]) -> Result<String, String> {
+ let output = Command::new("git")
+ .args(args)
+ .current_dir(root)
+ .output()
+ .map_err(|error| format!("run git {}: {error}", args.join(" ")))?;
+ String::from_utf8(command_success(output, root, args)?)
+ .map_err(|error| format!("git {} emitted non-UTF-8 output: {error}", args.join(" ")))
+}
+
+fn command_success(
+ output: std::process::Output,
+ root: &Path,
+ args: &[&str],
+) -> Result<Vec<u8>, String> {
+ if output.status.success() {
+ return Ok(output.stdout);
+ }
+ Err(format!(
+ "git {} failed in {}: {}",
+ args.join(" "),
+ root.display(),
+ String::from_utf8_lossy(&output.stderr).trim()
+ ))
+}
+
+fn path_arg(path: &Path) -> Result<&str, String> {
+ path.to_str()
+ .ok_or_else(|| format!("path {} is not valid UTF-8", path.display()))
+}
+
+fn sha256_file(path: &Path) -> Result<String, String> {
+ let mut file = fs::File::open(path)
+ .map_err(|error| format!("open archive {}: {error}", path.display()))?;
+ let mut hasher = Sha256::new();
+ let mut buffer = [0_u8; 64 * 1024];
+ loop {
+ let read = file
+ .read(&mut buffer)
+ .map_err(|error| format!("read archive {}: {error}", path.display()))?;
+ if read == 0 {
+ break;
+ }
+ hasher.update(&buffer[..read]);
+ }
+ Ok(format!("{:x}", hasher.finalize()))
+}
+
+fn to_unique_set<'a>(values: &'a [String], context: &str) -> Result<BTreeSet<&'a str>, String> {
+ let set = values.iter().map(String::as_str).collect::<BTreeSet<_>>();
+ if set.len() != values.len() || set.iter().any(|value| value.trim().is_empty()) {
+ return Err(format!("{context} entries must be nonempty and unique"));
+ }
+ Ok(set)
+}
+
+fn validate_sha256(value: &str, context: &str) -> Result<(), String> {
+ if value.len() != 64
+ || !value
+ .bytes()
+ .all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte))
+ {
+ return Err(format!("{context} must be lowercase 64-hex SHA-256"));
+ }
+ Ok(())
+}
+
+fn validate_artifact_name(value: &str) -> Result<(), String> {
+ if value.is_empty()
+ || value.contains('/')
+ || value.contains('\\')
+ || value == "."
+ || value == ".."
+ || !value
+ .bytes()
+ .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'.' | b'_' | b'-'))
+ {
+ return Err("archive artifact must be a safe portable file name".to_owned());
+ }
+ Ok(())
+}
+
fn validate_oid(value: &str, context: &str) -> Result<(), String> {
if value.len() != 40
|| !value
@@ -412,7 +1534,10 @@ mod tests {
#[test]
fn checked_in_baseline_and_step_map_validate() {
- validate(&crate::workspace_root()).expect("checked-in consolidation baseline");
+ validate_baseline_contracts(&crate::workspace_root())
+ .expect("checked-in consolidation baseline");
+ validate_history_contract(&crate::workspace_root(), None)
+ .expect("checked-in history contract");
}
#[test]
@@ -452,4 +1577,18 @@ mod tests {
gapped.range[0].start = 2;
assert!(validate_step_map(&gapped).is_err());
}
+
+ #[test]
+ fn archive_names_and_digests_are_strict() {
+ assert!(validate_artifact_name("sdk-0123.bundle").is_ok());
+ assert!(validate_artifact_name("../sdk.bundle").is_err());
+ assert!(validate_artifact_name("sdk/bundle").is_err());
+ assert!(validate_sha256(&"a".repeat(64), "digest").is_ok());
+ assert!(validate_sha256(&"A".repeat(64), "digest").is_err());
+ }
+
+ #[test]
+ fn merge_bearing_history_rehearsal_is_green() {
+ run_history_rehearsal().expect("history rewrite rehearsal");
+ }
}
diff --git a/tools/xtask/src/main.rs b/tools/xtask/src/main.rs
@@ -48,6 +48,8 @@ fn usage() {
eprintln!(" cargo xtask contract event-contract-registry-v7 [--write]");
eprintln!(" cargo xtask contract knowledge-manifest [--write]");
eprintln!(" cargo xtask consolidation baseline");
+ eprintln!(" cargo xtask consolidation history [--archive-root <absolute-directory>]");
+ eprintln!(" cargo xtask consolidation history-rehearsal");
eprintln!(" cargo xtask dto-roots --check|--write");
eprintln!(" cargo xtask generate protocol --check|--write");
eprintln!(" cargo xtask release preflight");
@@ -180,11 +182,7 @@ fn run(args: &[String]) -> Result<(), String> {
architecture::validate_dependency_boundaries(&workspace_root())
}
Some("contract") => run_contract(&args[1..]),
- Some("consolidation")
- if args.get(1).map(String::as_str) == Some("baseline") && args.len() == 2 =>
- {
- consolidation::validate(&workspace_root())
- }
+ Some("consolidation") => consolidation::run(&args[1..], &workspace_root()),
Some("coverage") => coverage::run(&args[1..]),
Some("dto-roots") => dto_roots::run(&args[1..], &workspace_root()),
Some("generate") => generate::run(&args[1..], &workspace_root()),