fix(#182): overlay proposal-merge no longer discards unrelated to-branch changes
Root cause: the default "overlay" merge strategy runs through muse.core.merge_engine.STRATEGY_MAP["overlay"], which is configured with diff_unit="snapshot". run_merge() unconditionally treats diff_unit="snapshot" as effective_base={} — i.e. it always computes the merge as if from_branch and to_branch share zero history, regardless of whether a real common ancestor is actually resolvable. Combined with resolution="prefer_theirs", this means ANY path present on from_branch wins outright, even files from_branch never touched since a stale fork point. This is precisely the failure mode behind the "almost lost 47 files" incident: merging a long-stale proposal silently reverted unrelated files on the target branch back to their state at the fork point.
Fix: - musehub_proposals.py: extend the ancestor-resolution branch (previously only weave/replay/selective/phased) to also resolve an ancestor manifest for "overlay", in both the real merge path and the preview/simulation path. - proposal_merge_strategies.py::execute_merge_strategy: when strategy is "overlay" and a real ancestor manifest was resolved, swap the engine to STRATEGY_MAP["theirs"] before calling run_merge(). "theirs" carries the same resolution="prefer_theirs" contract (from_branch still wins any genuine conflict) but diff_unit="three_way", so run_merge() does a real 3-way diff against the actual ancestor instead of an empty base. Paths from_branch never touched are left untouched; only real conflicts still resolve to from_branch. When no ancestor is resolvable (bounded BFS in _resolve_ancestor_manifest gives up), behavior is unchanged from before.
Tests: - New TestMergeDoesNotRevertUnrelatedToBranchChanges test reproduces the incident shape exactly: an ancestor commit, a from_branch forked there that only ever adds a new file, and a to_branch that independently advances two other files. Confirmed red (fileA reverted to stale ancestor content) on unmodified code, green after the fix. - New TestMergeStatePersistenceAcrossFreshRequests tests investigated #182's other reported symptom (merge state not persisting across requests) via real HTTP merge + fresh HTTP read, and a duplicate-merge attempt expecting 409. Both passed against unmodified code — this symptom did not reproduce via this path. RDS read-replica lag was ruled out as an explanation (no replicas configured on either staging DB instance). Root cause for that symptom remains unknown; documented as such rather than claimed fixed. - Full run of tests/test_merge_proposals.py + tests/test_musehub_proposals.py: 137 passed, no regressions. - Broader sweep across tests/ filtered to merge/overlay/proposal: 772 passed, 2 failed + 6 errored, all in files untouched by this change (test_fetch_mpack_native_phase1.py, test_genesis_ids.py — pre-existing AsyncMock/pydantic strictness issue, test_ui_ssr.py, test_wire_fetch_mpack.py) and unrelated to overlay/merge-engine logic — confirmed pre-existing, not regressions introduced here.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Semantic Changes
9 symbols
Files Changed
~3
1072 in snapshot
0 comments
muse hub commit comment sha256:385e16d0166c49fdf5e885bac284e53057a1f11ff8bef0be823212c9d7ca823c --body "your comment"
No comments yet. Be the first to start the discussion.