Repository navigation
Content roundtrip fidelity tests failing: duplication and truncation in record pipeline #41
Description
Summary
6 of 9 content roundtrip fidelity tests fail on the feat/roundtrip-fidelity branch (PR #40). These tests were ported from chore/roundtirp-fidelity-test where they passed against the old codebase structure.
Failing Tests
| Test | Symptom |
|---|---|
test_four_step_evolution_fidelity |
Got 1297 bytes, expected 665 — content duplication (2×) |
test_hyperfine_content_duplication_bug |
Got 6706 bytes, expected 3465 — content duplication (2×) |
test_hyperfine_extended_commit_sequence |
Got 6706 bytes, expected 3465 — content duplication (2×) |
test_multi_hunk_refactor_fidelity |
Content duplication after multi-hunk refactor |
test_three_sequential_modifications |
Got 21 bytes, expected 27 — content truncation |
test_single_line_middle_insertion_after_multiple_edits |
Content mismatch after middle insertion |
Passing Tests
| Test | Description |
|---|---|
test_simple_add_then_modify |
Basic add → modify cycle |
test_single_line_middle_insertion |
Single middle insertion (2-version file) |
test_hyperfine_pairwise_transitions |
Pairwise commit transitions |
Root Cause Analysis
The hunk consolidation fix (from commit 2467443 on the old branch) was ported to dev's restructured record pipeline. The consolidation logic in record/mod.rs uses has_nuclear_hunk to detect when hunks need collapsing into a single Replace. However, on dev's architecture:
-
Content duplication (4 tests): Multiple Replace/Delete/middle-Insert hunks each independently do delete-all + insert-full-content in the globalize pipeline, creating N copies. The consolidation fires (
has_nuclear_hunkis true) but the collapsed Replace hunk's content boundaries may not align correctly with dev'scontent_start/content_endbyte position calculation inrecord/mod.rs. -
Content truncation (1 test): The three-sequential-modifications test gets FEWER bytes than expected (21 vs 27), suggesting the Replace hunk's content range is too narrow — it's not covering the full new file.
-
Interaction with
ViewGraph: The mainrecord()path wraps the transaction inViewGraphwithcollect_visible_change_ids. The old branch usedOverlayTxn. The globalize pipeline'sfind_content_verticesnow goes through INODE_GRAPH (the O(m) optimization), which may return different results through theViewGraphwrapper vs a bare transaction.
Key Files
atomic-core/src/record/workflow/record/mod.rs— Consolidation logic (lines ~484-520)atomic-core/src/record/workflow/globalize/hunk.rs—find_content_verticesandglobalize_replaceatomic-core/src/record/workflow/globalize/pipeline.rs— Content routing (Replace → full content, Insert → slice)atomic-repository/src/repository/record.rs—record()entry point withViewGraphwrappingatomic-repository/tests/content_roundtrip_fidelity_test.rs— The failing tests
Reproduction
cargo test -p atomic-repository --test content_roundtrip_fidelity_testContext
- These tests pass on
origin/chore/roundtirp-fidelity-test(old codebase structure) - The git import pipeline (which uses
assemble_and_hashwith a bare&txn) works correctly — 1017/1017 hyperfine commits import with 0 drops - The issue is specific to the
record()path which usesViewGraphwrapping - PR Improve roundtrip fidelity and optimize Git import process #40: Improve roundtrip fidelity and optimize Git import process #40
Activity
leefaus commentedon Apr 9, 2026
Update: 5 of 6 failures fixed
Root cause for 5 tests: The INODE_GRAPH fast path in
find_content_verticesbypassedViewGraph's change filter. Whenrecord()usesViewGraphwrapping, the INODE_GRAPH returned vertices from ALL changes regardless of view visibility, causing content duplication.Fix: Always use
retrieve_graph(which respects whateverGraphTxnTimpl is provided) for content vertex discovery. Commit: 0a2d376.Remaining failure:
test_three_sequential_modificationsThis test records 3 versions of a file:
line1\nline2\nline3\nline1\nmodified\nline3\n(middle line changed)line1\nmodified\nline3\nextra\n(line appended)Then calls
assert_content_after_change(&repo, "data.txt", &h3, v3)which fails:line1\nmodified\nline3\n(21 bytes — v2's content, missing the appended line)line1\nmodified\nline3\nextra\n(27 bytes — v3's content)This test passes on the old branch (
origin/chore/roundtirp-fidelity-test), so it's a regression specific to dev's architecture.The issue is likely in
get_file_content_after_change()and how it builds the change filter for state-based content retrieval. The change filter may not include h3's change, so the graph traversal returns v2's state instead of v3's.leefaus commentedon Apr 9, 2026
Update: narrowed to 1 remaining failure
Commit 0a2d376 fixed 5 of 6 tests by disabling the INODE_GRAPH fast path in
find_content_vertices(it bypassed ViewGraph's change filter).Remaining:
test_three_sequential_modificationsDiagnosis complete — the issue is NOT in recording. Debug confirms:
The issue is in content retrieval:
get_file_content("data.txt")returns 21 bytes (v2 content) even though h3 (with the append) is in the viewget_file_content_after_change(h3)also returns 21 bytesRoot cause: h3's Insert hunk creates a new vertex for "extra\n" that should be connected after the last content vertex. But
retrieve_graphdoesn't find it — the vertex either wasn't written to GRAPH correctly byinsert_change, or the edge connecting it to the existing graph is missing/wrong.This is a graph-write or edge-connectivity bug in the apply pipeline, not a recording or content retrieval bug. The old branch's apply pipeline (
apply_change) produced correct edges; dev'sinsert_change→write_change_to_graphdoes not for this specific case (append after a Replace).leefaus commentedon Apr 9, 2026
Deep dive on last failure — hit dead end
Exhaustive comparison of the code paths between old branch and dev shows identical logic for:
record_modified_file(consolidation, content position calc)globalize_hunk/classify_insert/find_content_verticespipeline.rscontent routingis_vertex_alive/create_alive_vertexiter_parentsflag rangesretrieve_graphDFSKey finding: After h2's Replace,
classify_insertfinds 2 content vertices (h1's old vertex AND h2's new vertex) on dev. On the old branch it should find only 1 (h2's). This means h1's old content vertex is consideredaliveon dev butdeadon the old branch.Both branches have the same
is_vertex_alivelogic (check for non-deleted PARENT edges). Both useiter_adjacentwith the same flag range to exclude DELETED. The old branch'sOverlayTxnpasses through to bareReadTxnfor shared stacks.Hypothesis: The difference is in how h2's Replace hunk is globalized — the deletion edges it produces might differ between the two branches, causing h1's vertex to retain an alive parent edge on dev but not on the old branch. The globalize pipeline code LOOKS identical, but the restructuring into subdirectories may have introduced a subtle difference in how content bytes are passed through the pipeline.
Recommended next step: Add a graph edge dump test (iterate all GRAPH entries) on both branches after recording h1+h2, and diff the actual B-tree contents. This will show exactly which edges are present and whether h1's vertex has the expected DELETED parent on dev.
leefaus commentedon Apr 9, 2026
Root cause found
The apply pipeline's vertex resolution is different between the old branch and dev.
Old branch (
atomic-core/src/apply/insertion.rs):resolve_context_vertex_for_target(txn, pos, is_up, target)resolve_vertex_for_target(txn, pos, target, FindBlockMode)overlay::find_block_in_stack_graphfor target-aware resolutionDev (
atomic-core/src/apply/insertion.rs):resolve_context_vertex(txn, pos, is_up)resolve_vertex(txn, pos, is_up)When a second Replace targets a file that was already Replaced by a prior change, the deletion edges need to find the CORRECT content vertex (the one created by the first Replace). The old branch's target-aware resolution finds it correctly. Dev's simpler resolution can resolve to the wrong vertex (the original vertex from the first change), causing the deletion to mark the wrong edges — leaving the first Replace's content vertex appearing alive alongside the second Replace's new content.
This affects both:
test_three_sequential_modifications(text: append after replace)Fix: Port the target-aware vertex resolution from the old branch, or adjust
resolve_vertex/resolve_context_vertexin dev'sposition.rsto handle the multi-vertex ambiguity correctly. The key functions to compare:atomic-core/src/apply/insertion.rslines 117, 136, 155-165, 178-188atomic-core/src/apply/insertion.rslines 109, 128, 147-148, 161-162atomic-core/src/apply/edge.rs—resolve_vertex_for_targetatomic-core/src/pristine/overlay.rs—find_block_in_stack_graphfix: restore del_edge_with_reverse in apply pipeline
Improve roundtrip fidelity and optimize Git import process (#40)
Enhance Git import performance and roundtrip fidelity (#44)