"""TDD — musehub#93: root-of-push snapshots were never hash-verified before storage. Root cause, in ``musehub_wire_push.py``'s snapshot-processing loop:: if _parent_sid and hash_snapshot(_base, _snap_dirs or None) != _sid: raise ValueError(...) This integrity check only ran when ``_parent_sid`` was truthy. Every snapshot that is the *root* of a push batch (``parent_snapshot_id=None`` — the first commit sent, or a genuinely new repo's first commit) has ``_parent_sid`` falsy and was therefore never checked against its own declared ID at all. A wire/ transport bug that dropped or mangled ``directories`` (or, in principle, the manifest itself) for exactly this kind of entry would sail through unverified and be persisted permanently -- undetectable until a client later re-hashed it on clone and rejected the mismatch (the exact `entries=1062 dirs=0` corruption found live on `gabriel/muse`@staging that prompted this issue). RED before the fix: a root snapshot with directories that don't reproduce its own declared snapshot_id is silently stored with dirs=0. GREEN after: the push is rejected outright, matching the existing (and unaffected) behavior for a snapshot with a bad/phantom parent. """ from __future__ import annotations import datetime from unittest.mock import AsyncMock, MagicMock, patch import msgpack import pytest from sqlalchemy.ext.asyncio import AsyncSession from muse.core.ids import hash_snapshot from muse.core.mpack import build_wire_mpack from muse.core.types import blob_id from musehub.core.genesis import compute_identity_id from musehub.db.musehub_repo_models import MusehubSnapshot from musehub.services.musehub_repository import create_repo from musehub.services.musehub_wire_push import wire_push_unpack_mpack _OWNER = "gabriel" _IDENTITY_ID = compute_identity_id(b"gabriel") def _cid(seed: str) -> str: return blob_id(f"root-integrity-commit-{seed}".encode()) def _now() -> datetime.datetime: return datetime.datetime(2026, 1, 1, tzinfo=datetime.timezone.utc) def _raw_commit(cid: str, snap_id: str) -> dict: return { "commit_id": cid, "branch": "feat", "message": f"commit {cid[:12]}", "author": _OWNER, "committed_at": _now().isoformat(), "parent_commit_id": None, "parent2_commit_id": None, "snapshot_id": snap_id, "agent_id": "", "model_id": "", "toolchain_id": "", "sem_ver_bump": "none", "breaking_changes": [], "signature": "", "signer_key_id": "", "signer_public_key": "", "prompt_hash": "", } def _mock_backend(mpack_bytes: bytes) -> MagicMock: backend = MagicMock() backend.get_mpack = AsyncMock(return_value=mpack_bytes) backend.put = AsyncMock(return_value=None) backend.put_mpack = AsyncMock(return_value=None) backend.quarantine_mpack = AsyncMock(return_value=None) backend.presign_get = AsyncMock(return_value="") backend.presign_mpack_get = AsyncMock(return_value="") return backend @pytest.mark.asyncio async def test_root_snapshot_with_wrong_directories_is_rejected_not_silently_stored( db_session: AsyncSession, ) -> None: """RED: a root (parent_snapshot_id=None) snapshot's declared directories don't reproduce its own snapshot_id. Must be rejected outright -- exactly like the existing phantom-parent case -- not persisted with the wrong (or dropped) directories.""" repo = await create_repo( db_session, name="root-integrity-repro", owner=_OWNER, owner_user_id=_IDENTITY_ID, visibility="public", initialize=False, ) await db_session.commit() oid1 = blob_id(b"content-f1") manifest = {"tracks/f1.txt": oid1} real_directories = ["tracks", "docs"] # The snapshot_id was genuinely computed WITH these directories locally -- # this is what a correct client push looks like. snap_id = hash_snapshot(manifest, real_directories) a_commit = _cid("a") mpack_bytes = build_wire_mpack({ "commits": [_raw_commit(a_commit, snap_id)], "snapshots": [{ "snapshot_id": snap_id, "parent_snapshot_id": None, "delta_upsert": manifest, "delta_remove": [], # Wire/transport bug simulation: directories arrived wrong (here: # dropped entirely) relative to what snap_id was actually hashed # with. This is exactly the shape of musehub#93's live corruption. "directories": [], }], "blobs": [{"object_id": oid1, "content": b"content-f1"}], "tags": [], }) mpack_key = blob_id(mpack_bytes) backend = _mock_backend(mpack_bytes) with patch("musehub.services.musehub_wire.get_backend", return_value=backend), \ patch("musehub.services.musehub_wire_push.get_backend", return_value=backend), \ patch("musehub.storage.backends.get_backend", return_value=backend): with pytest.raises(ValueError, match="does not reproduce its own declared ID"): await wire_push_unpack_mpack( db_session, repo.repo_id, mpack_key, pusher_id=_OWNER, branch="feat", head_commit_id=a_commit, commits_count=1, blobs_count=1, force=True, ) stored = await db_session.get(MusehubSnapshot, snap_id) assert stored is None, ( "a root snapshot whose directories don't reproduce its own declared ID " "must never be persisted -- this is exactly the musehub#93 corruption " "(dirs silently dropped, entry_count correct) sailing through unverified" ) @pytest.mark.asyncio async def test_root_snapshot_with_correct_directories_is_stored_intact( db_session: AsyncSession, ) -> None: """GREEN control: a correctly-hashed root snapshot with real directories must still push successfully and be stored with those directories intact -- the new unconditional check must not reject legitimate pushes.""" repo = await create_repo( db_session, name="root-integrity-happy-path", owner=_OWNER, owner_user_id=_IDENTITY_ID, visibility="public", initialize=False, ) await db_session.commit() oid1 = blob_id(b"content-f1-happy") manifest = {"tracks/f1.txt": oid1} real_directories = ["tracks", "docs", "empty_dir"] snap_id = hash_snapshot(manifest, real_directories) a_commit = _cid("happy-a") mpack_bytes = build_wire_mpack({ "commits": [_raw_commit(a_commit, snap_id)], "snapshots": [{ "snapshot_id": snap_id, "parent_snapshot_id": None, "delta_upsert": manifest, "delta_remove": [], "directories": real_directories, }], "blobs": [{"object_id": oid1, "content": b"content-f1-happy"}], "tags": [], }) mpack_key = blob_id(mpack_bytes) backend = _mock_backend(mpack_bytes) with patch("musehub.services.musehub_wire.get_backend", return_value=backend), \ patch("musehub.services.musehub_wire_push.get_backend", return_value=backend), \ patch("musehub.storage.backends.get_backend", return_value=backend): await wire_push_unpack_mpack( db_session, repo.repo_id, mpack_key, pusher_id=_OWNER, branch="feat", head_commit_id=a_commit, commits_count=1, blobs_count=1, force=True, ) await db_session.flush() stored = await db_session.get(MusehubSnapshot, snap_id) assert stored is not None, "a correctly-hashed root snapshot must be stored" assert sorted(stored.directories or []) == sorted(real_directories), ( f"stored directories don't match what was pushed: " f"stored={stored.directories!r} pushed={real_directories!r}" ) assert hash_snapshot(manifest, list(stored.directories or [])) == snap_id