gabriel / musehub public
fix patch #182 fix/182-overlay-staleness-guard #1 / 1
AI Agent gabriel · 17 hours ago · Sep 11, 2026 · Diff

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]>

sha256:385e16d0166c49fdf5e885bac284e53057a1f11ff8bef0be823212c9d7ca823c sha
+6 ~3 symbols
3 changed · 1072 in snapshot files
sha256:b11bef69b7b6c27dfe91c43c6647a2f0dd0903a31b553099011963610ac16822 snapshot
+6
symbols added
~3
symbols modified
3
files changed
1072
files in snapshot
0
dead code introduced
Semantic Changes 9 symbols
~ tests/test_merge_proposals.py .py 6 symbols added
+ TestMergeDoesNotRevertUnrelatedToBranchChanges class class TestMergeDoesNotRevertUnrelatedToBranchChanges L1758–1884
+ _make_stale_fork_scenario method async_method _make_stale_fork_scenario L1769–1841
+ test_default_overlay_merge_preserves_to_branch_only_changes method async_method test_default_overlay_merge_preserves_to_branch_only_changes L1843–1884
+ TestMergeStatePersistenceAcrossFreshRequests class class TestMergeStatePersistenceAcrossFreshRequests L1664–1749
+ test_fresh_read_after_http_merge_sees_merged_state method async_method test_fresh_read_after_http_merge_sees_merged_state L1665–1706
+ test_second_http_merge_returns_409_never_remerges method async_method test_second_http_merge_returns_409_never_remerges L1708–1749
Files Changed
~3
1072 in snapshot
← Older Oldest on fix/182-overlay-staleness-guard
All commits
Newer → Latest on fix/182-overlay-staleness-guard

0 comments

No comments yet. Be the first to start the discussion.

To add a comment, use the Muse CLI: muse hub commit comment sha256:385e16d0166c49fdf5e885bac284e53057a1f11ff8bef0be823212c9d7ca823c --body "your comment"