From 45fdf3be071550ab09724bbd1d990d5f8f6d50eb Mon Sep 17 00:00:00 2001 From: Robert M1 <50460704+githubrobbi@users.noreply.github.com> Date: Mon, 16 Mar 2026 09:32:24 -0700 Subject: [PATCH] fix(mft): restore OFFLINE sort + custom format for parity (5/6 drives pass) Commit 90ee522ed removed all sort_directory_children() calls to match C++ LIVE behavior, but this broke OFFLINE mode (0/6 drives passing). OFFLINE paths need sorting for deterministic output since MFT dump parsing order differs from live IOCP traversal. Changes: - Restore sort_directory_children() before compute_tree_metrics() in all 3 OFFLINE code paths: builder.rs, persistence.rs, merge.rs - Add --format custom to verify_parity.rs to match C++ baseline footer Verified: D, E, G, M, S all SORTED MATCH. F is known deferred (Bug 3). Co-Authored-By: Claude Opus 4.6 --- crates/uffs-mft/src/index/builder.rs | 10 +++++++++- crates/uffs-mft/src/index/merge.rs | 3 +++ crates/uffs-mft/src/reader/persistence.rs | 4 ++++ scripts/verify_parity.rs | 2 ++ 4 files changed, 18 insertions(+), 1 deletion(-) diff --git a/crates/uffs-mft/src/index/builder.rs b/crates/uffs-mft/src/index/builder.rs index 0fef4e1d2..5576074f5 100644 --- a/crates/uffs-mft/src/index/builder.rs +++ b/crates/uffs-mft/src/index/builder.rs @@ -465,7 +465,15 @@ impl MftIndex { tracing::debug!("[TRIP] MftIndex::from_parsed_records -> Phase 2: ExtensionIndex::build"); index.extension_index = Some(ExtensionIndex::build(&index)); - // 2. Compute tree metrics for directory statistics (Phase 5) + // 2. Sort directory children for deterministic OFFLINE output (Phase 4) + // CRITICAL: OFFLINE path (from_parsed_records) requires sorted children + // for deterministic CSV output order. LIVE paths (to_index.rs) do NOT + // sort to match C++ behavior (reverse MFT parse order). + tracing::debug!("[TRIP] MftIndex::from_parsed_records -> Phase 4: sort_directory_children"); + index.sort_directory_children(); + + // 3. Compute tree metrics for directory statistics (Phase 5) + // Must run AFTER sorting in OFFLINE path for deterministic results. tracing::debug!("[TRIP] MftIndex::from_parsed_records -> Phase 5: compute_tree_metrics"); index.compute_tree_metrics(); diff --git a/crates/uffs-mft/src/index/merge.rs b/crates/uffs-mft/src/index/merge.rs index 065a8de2a..07f8abed5 100644 --- a/crates/uffs-mft/src/index/merge.rs +++ b/crates/uffs-mft/src/index/merge.rs @@ -54,6 +54,9 @@ impl MftIndex { debug!("🔨 Building extension index..."); self.extension_index = Some(ExtensionIndex::build(self)); + debug!("🔨 Sorting directory children..."); + self.sort_directory_children(); + debug!("🔨 Computing tree metrics..."); self.compute_tree_metrics(); diff --git a/crates/uffs-mft/src/reader/persistence.rs b/crates/uffs-mft/src/reader/persistence.rs index fcd5e97a9..1152f2d0e 100644 --- a/crates/uffs-mft/src/reader/persistence.rs +++ b/crates/uffs-mft/src/reader/persistence.rs @@ -609,6 +609,10 @@ impl MftReader { } } + // Sort directory children for deterministic output + // CRITICAL for OFFLINE path: ensures consistent ordering across runs + index.sort_directory_children(); + // Compute tree metrics index.compute_tree_metrics(); diff --git a/scripts/verify_parity.rs b/scripts/verify_parity.rs index 6b00d81bb..ad12abef4 100644 --- a/scripts/verify_parity.rs +++ b/scripts/verify_parity.rs @@ -994,6 +994,8 @@ fn regenerate_rust_output( drive_letter, "--tz-offset", &tz_str, + "--format", + "custom", // Match C++ baseline footer format "--out", &rust_output.to_string_lossy(), ])