"""Phase 7 — MPack atomicity: objects before refs, topological commit order. Invariants: 1. apply_mpack writes objects → snapshots → commits → (caller advances refs). A crash after objects but before refs leaves reachable but ref-less objects (safe — GC-able). A crash after refs but before objects leaves a ref pointing to a commit whose snapshot has no objects (broken checkout). The safe order is already enforced by apply_mpack: objects first, refs last. 2. Commits in a mpack may arrive newest-first (BFS order). Phase 2's MissingParentError guard rejects a commit whose parent hasn't been written yet. apply_mpack must retry deferred commits until all parents in the mpack are resolved, or give up if a parent is genuinely absent. Testing tiers ------------- Unit apply_mpack handles newest-first commit ordering without error Unit apply_mpack retries and eventually writes all commits when parents arrive after children in the mpack Unit apply_mpack logs and skips commits with truly absent parents (not in mpack, not in store) Integration mpack create → unbundle round-trip writes all commits to store Data after unbundle, every commit in the mpack is readable from store """ from __future__ import annotations import datetime import pathlib import msgpack import pytest from muse.core.mpack import apply_mpack, MPack from muse.core.commits import ( CommitRecord, commit_exists, read_commit, write_commit, ) from muse.core.snapshots import ( SnapshotRecord, read_snapshot, write_snapshot, ) from muse.core.ids import hash_commit as compute_commit_id, hash_snapshot as compute_snapshot_id from muse.core.types import fake_id, long_id # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- _REPO_ID = "repo-phase7-test" _BRANCH = "main" def _make_real_commit( repo: pathlib.Path, tag: str, parent_id: str | None, content: str = "hello", ) -> CommitRecord: """Write a fully content-addressed commit to *repo* and return it.""" manifest = {"file.txt": fake_id(f"obj-{content}")} dirs: dict[str, list[str]] = {} snap_id = compute_snapshot_id(manifest, dirs) write_snapshot(repo, SnapshotRecord(snapshot_id=snap_id, manifest=manifest, directories=dirs)) committed_at = datetime.datetime(2026, 1, 1, tzinfo=datetime.timezone.utc) commit_id = compute_commit_id( parent_ids=[parent_id] if parent_id else [], snapshot_id=snap_id, message=tag, committed_at_iso=committed_at.isoformat(), author="gabriel", ) rec = CommitRecord( commit_id=commit_id, branch=_BRANCH, snapshot_id=snap_id, message=tag, committed_at=committed_at, parent_commit_id=parent_id, author="gabriel", ) write_commit(repo, rec) return rec def _snap_dicts_for(commits: list[CommitRecord], source: pathlib.Path) -> list[dict]: """Return wire-format snapshot dicts for the given commits, deduped by snapshot_id. Reads the real snapshots from *source* so apply_mpack can write them to the destination repo and the missing-snapshot guard passes. """ seen: set[str] = set() result = [] for c in commits: if c.snapshot_id in seen: continue seen.add(c.snapshot_id) snap = read_snapshot(source, c.snapshot_id) if snap is None: continue result.append({ "snapshot_id": snap.snapshot_id, "parent_snapshot_id": None, "delta_upsert": snap.manifest, "delta_remove": [], "directories": list(snap.directories) if snap.directories else [], }) return result # --------------------------------------------------------------------------- # Unit — topological retry: newest-first ordering succeeds # --------------------------------------------------------------------------- class TestTopologicalRetry: def test_newest_first_ordering_writes_all_commits(self, tmp_path: pathlib.Path) -> None: """apply_mpack must succeed even when commits arrive newest-first.""" root_commit = _make_real_commit(tmp_path, "root", None) child_commit = _make_real_commit(tmp_path, "child", root_commit.commit_id) grandchild = _make_real_commit(tmp_path, "grandchild", child_commit.commit_id) # Fresh repo — no commits yet dest = tmp_path / "dest" dest.mkdir() commits = [grandchild, child_commit, root_commit] # MPack commits in newest-first order (BFS from tip) mpack: MPack = { "blobs": [], "snapshots": _snap_dicts_for(commits, tmp_path), "commits": [c.to_dict() for c in commits], } result = apply_mpack(dest, mpack) assert result["commits_written"] == 3, ( f"Expected 3 commits written, got {result['commits_written']}. " "apply_mpack may not be retrying MissingParentError commits." ) assert commit_exists(dest, root_commit.commit_id) assert commit_exists(dest, child_commit.commit_id) assert commit_exists(dest, grandchild.commit_id) def test_correct_order_still_works(self, tmp_path: pathlib.Path) -> None: """Oldest-first ordering (already correct) must still succeed.""" root_commit = _make_real_commit(tmp_path, "root2", None) child_commit = _make_real_commit(tmp_path, "child2", root_commit.commit_id) dest = tmp_path / "dest2" dest.mkdir() commits = [root_commit, child_commit] mpack: MPack = { "blobs": [], "snapshots": _snap_dicts_for(commits, tmp_path), "commits": [c.to_dict() for c in commits], } result = apply_mpack(dest, mpack) assert result["commits_written"] == 2 def test_absent_parent_skipped_gracefully(self, tmp_path: pathlib.Path) -> None: """A commit whose parent is not in the mpack or store must be skipped, not crash apply_mpack.""" root_commit = _make_real_commit(tmp_path, "root3", None) child_commit = _make_real_commit(tmp_path, "child3", root_commit.commit_id) dest = tmp_path / "dest3" dest.mkdir() # MPack only has the child — root is absent mpack: MPack = { "blobs": [], "snapshots": [], "commits": [ child_commit.to_dict(), # parent (root) not in mpack or dest ], } # Should not raise — should log and skip result = apply_mpack(dest, mpack) assert result["commits_written"] == 0, ( "commit with missing parent should have been skipped" ) assert not commit_exists(dest, child_commit.commit_id), ( "commit with missing parent was written despite dangling parent" ) # --------------------------------------------------------------------------- # Data — objects present after unbundle before refs are advanced # --------------------------------------------------------------------------- class TestObjectsBeforeRefs: def test_apply_mpack_writes_commits_before_caller_advances_refs( self, tmp_path: pathlib.Path ) -> None: """apply_mpack (object writes) completes before write_branch_ref is called. This is verified structurally: apply_mpack returns successfully before the caller's write_branch_ref call. If objects were not yet written at the time refs were advanced, a checkout immediately after would fail. """ root_commit = _make_real_commit(tmp_path, "root4", None) dest = tmp_path / "dest4" dest.mkdir() mpack: MPack = { "blobs": [], "snapshots": _snap_dicts_for([root_commit], tmp_path), "commits": [root_commit.to_dict()], } # apply_mpack returns — at this point commits are durable result = apply_mpack(dest, mpack) assert result["commits_written"] == 1 # read_commit must work immediately — no ref advancement needed read_back = read_commit(dest, root_commit.commit_id) assert read_back is not None, ( "commit not readable after apply_mpack — write did not complete" ) assert read_back.commit_id == root_commit.commit_id