Skip to content

fix(merge): a branch whose edits net to zero can never be merged - #517

Merged
aaltshuler merged 6 commits into
ModernRelay:mainfrom
ardacanuckan:fix/473-net-zero-merge
Aug 18, 2026
Merged

fix(merge): a branch whose edits net to zero can never be merged#517
aaltshuler merged 6 commits into
ModernRelay:mainfrom
ardacanuckan:fix/473-net-zero-merge

Conversation

@ardacanuckan

@ardacanuckan ardacanuckan commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

What & why

A branch whose edits return a table to its fork-point content could not be merged. The merge tried to publish a field-identical table registration, failed with a version collision, and failed the same way on every retry.

This fixes that path by planning adoption once and dropping a table candidate only when the planned registration is already the stored manifest row and the source and base rows are proven logically equal. The merge still publishes its graph lineage, while recovery intent, expected versions, and published table updates remain consistent.

The equality proof is deliberately typed rather than display-string based:

  • non-Blob values use Arrow's null-aware logical equality;
  • only the five exact Lance virtual columns are ignored, so legal fields such as _row_id remain visible;
  • external Blob values compare their complete logical descriptor;
  • managed Blob values compare descriptor plus normalized physical data-file identity, resolving inherited branch files through Lance base paths without reading payload bytes;
  • Blob identity lookup is cached from active files, so work does not grow with retained branch ancestry.

The manifest publisher also accepts an idempotent re-registration of the exact stored row while continuing to reject a different row at an occupied identity/version.

Closes #473.

Recovery behavior

The crash test now pins the exact pre-confirmation outcome. Reopen rolls the unpublished sibling-table effect back, leaves the source delta available, and records one RolledBack audit entry. A clean retry must return Merged, publish the source and target rows together, and continue to suppress the net-zero table.

Coverage

  • Net-zero merge shapes: fast-forward, diverged target, mixed tables, and branch-to-branch.
  • Exhaustive merge truth table: 10×10 operations, 49 executable cells.
  • Equality regressions: null vs empty string, delimiter collisions, legal _row_id, same-looking managed Blob descriptors from different files, and inherited managed Blob files across native branches.
  • Existing Blob adopt path: one known-present update and no payload materialization for unchanged cells.
  • Recovery failpoint: exact rollback state before retry and exact merged state afterward.

Local verification

  • cargo test --workspace --locked --features omnigraph-engine/failpoints,omnigraph-cluster/failpoints
  • cargo clippy --workspace --all-targets --locked -- -D warnings -W clippy::dbg_macro
  • cargo clippy --workspace --all-targets --locked --features omnigraph-engine/failpoints,omnigraph-cluster/failpoints -- -D warnings -W clippy::dbg_macro
  • cargo fmt --all --check
  • scripts/check-agents-md.sh

Focused merge, Blob-adopt, truth-table, planner, and recovery tests also pass. The only local diagnostic was the existing macOS linker warning about compact-unwind metadata size.

Greptile Summary

The PR makes net-zero branch edits mergeable by removing manifest-identical adopt candidates before recovery and publication, while permitting idempotent manifest-row registration.

  • Adds typed, physical-file-aware equality for Blob-bearing adopt scans.
  • Introduces a pure three-state adopt publication plan shared by classification and publication.
  • Expands net-zero, truth-table, publisher, and crash-recovery coverage.
  • Documents the platform-specific stack requirement for the full test suite.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
crates/omnigraph/src/exec/merge.rs Replaces adopt signature comparison with typed equality and filters field-identical adopt plans before all candidate-derived recovery and publication state is built.
crates/omnigraph/src/blob.rs Adds managed-Blob equality based on validated descriptors, row addresses, and resolved immutable data-file identity.
crates/omnigraph/src/db/manifest/publisher.rs Allows idempotent re-registration of an identical stored version row while retaining rejection for differing occupied-version state.
crates/omnigraph/src/db/manifest/tests.rs Distinguishes duplicate-request and stored-state collisions and covers both identical and differing re-registration behavior.
crates/omnigraph/tests/merge_net_zero.rs Adds integration coverage for net-zero merges across fast-forward, divergent, mixed-table, and branch-to-branch shapes.
crates/omnigraph/tests/merge_truth_table.rs Adds net-zero operations to the merge behavior matrix rather than treating them as manifest-equal no-ops.
crates/omnigraph/tests/failpoints.rs Covers recovery after durable effects when a merge combines one net-zero table with a real delta.
docs/dev/testing.md Documents the macOS test-stack requirement and its unrelated baseline behavior.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Compare base and source table rows] --> B{Validation delta empty?}
  B -->|No| C[Retain merge candidate]
  B -->|Yes| D[Plan adopted source state]
  D --> E{Planned registration}
  E -->|Nothing| F[Drop candidate before recovery planning]
  E -->|Pointer| C
  E -->|Fork| C
  C --> G[Build expected versions and recovery slots]
  G --> H[Publish table updates and graph head atomically]
  F --> G
Loading

Reviews (3): Last reviewed commit: "fix(merge): prove adopt equality before ..." | Re-trigger Greptile

Context used (3)

Arda Can Uckan and others added 6 commits August 18, 2026 20:56
A branch can carry real commits whose content returns to the merge base:
insert an edge, then delete it. Its manifest state still differs from the
target's, because the Lance version advanced twice, so the table is not caught
by the manifest-equality gate that skips untouched tables.

Merging such a branch fails with "table version N already exists", and a retry
recomputes the same publish and fails identically, so the branch is
permanently unmergeable.

These tests are red until the next commit. They cover fast-forward,
non-fast-forward, mixed and branch-to-branch shapes, and assert more than the
absence of an error: a second merge must report AlreadyUpToDate, the manifest
must advance exactly once, and no table version may move.

Refs ModernRelay#473
The adopt arm built its registration from the target's own version and
metadata, producing a row field-for-field equal to the one __manifest already
holds. The publisher's registry guard exempts only an owner-branch handoff,
which requires a different branch, so it rejected the row.

Plan the adopt once, purely, as AdoptPublish (Nothing / Pointer / Fork), and
drop a candidate whose plan is Nothing at classification. That is the same
disposition the manifest-equal tables get one gate earlier, and it keeps every
set derived from the candidate map consistent: expected versions, recovery
delta slots, effects, and published updates.

Suppressing the update at publish time instead breaks recovery, which requires
the confirmed updates to equal the sidecar's complete intended delta.

Only an empty validation delta qualifies for the drop. A non-empty one is the
evaluator's RI, uniqueness and cardinality input and is still checked. The
condition is exact rather than conservative: compute_adopt_delta returns None
only when every id on both sides carries an identical row signature and
nothing was added or deleted, and classification is reached only when base
equals target.

Closes ModernRelay#473
The registry guard refused any second registration at an occupied
(identity, version) unless it was an owner-branch handoff. That rejected the
strictly benign case, a row field-for-field identical to the one already
stored, while admitting the shape the guard exists to police: two branches
claiming one version. The post-publish fold reaches the same state whether an
identical re-registration is applied or skipped.

The two refusals also shared a byte-identical message. A caller that batched
one version twice is a different bug from a caller racing a stored
registration, and the text now says which happened.

test_batch_create_table_versions_is_atomic_on_conflict used an identical
re-registration as its stand-in for a conflict; it now uses a genuinely
differing row so it still pins batch atomicity under the corrected rule.
The matrix gains a netZeroEdge op: a side that commits real changes whose net
content effect is zero. It is deliberately not folded into noop, because the
two take different engine routes, noop being skipped on manifest equality
while netZeroEdge reaches adopt classification.

Crossing it with every other op turns one reported example into a covered
interaction, and OpVariant's exhaustive match means a future op cannot be
added without dispositioning its net-zero cells. 100 cells, 49 executable.
… sidecar

The branch-merge sidecar requires the confirmed updates to equal its complete
intended delta, and both sets derive from the merge candidates, so dropping a
table at classification keeps them equal.

This crashes between the durable effects and the confirmation on a branch
carrying both shapes at once, one table netting to zero and one with a real
delta, and proves recovery resolves the sidecar and leaves the dropped table
untouched.
@aaltshuler
aaltshuler force-pushed the fix/473-net-zero-merge branch from eb53504 to bced629 Compare August 18, 2026 18:56
@aaltshuler
aaltshuler merged commit 2856568 into ModernRelay:main Aug 18, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: branch merge with a net-zero delta fails permanently with "table version already exists"

2 participants