"""TDD — musehub#230: IdentityPushValidator was never invoked by the real wire_push_unpack_mpack pipeline -- I1/I2/I3 were enforced by nothing on an actual push, despite push_validator.py's own docstring claiming otherwise (the false claim #227's audit table also repeated). This test exercises the real, live wire-push endpoint function end-to-end -- not IdentityPushValidator directly (tests/test_identity_push_validator.py already covers that in isolation) -- with an identity-domain commit whose committed state contains a real I1 violation (a 2-node cycle: alice spawns bob, bob spawns alice). It must be rejected by the actual push pipeline, not merely by calling the validator class in a unit test. RED before the fix: the push succeeds; the cyclic relationship is persisted. GREEN after: wire_push_unpack_mpack detects the identity domain, reconstructs the full identities/relationships snapshot, runs IdentityPushValidator, and rejects the push with ValueError (-> HTTP 422 at the route layer) before any objects are committed. """ from __future__ import annotations import datetime from unittest.mock import AsyncMock, MagicMock, patch 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 muse.plugins.identity.records import ( IdentityRecord, RelationshipRecord, identity_path, record_to_bytes, relationship_path, ) from musehub.core.genesis import compute_identity_id from musehub.db.musehub_repo_models import MusehubCommit 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"identity-push-validator-wire-{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": "main", "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 def _identity_record(handle: str, kind: str) -> IdentityRecord: return { "handle": handle, "type": kind, # type: ignore[typeddict-item] "pubkey": None, "quorum": None, "registered_at": _now().isoformat(), "metadata": {}, } def _spawns(frm: str, to: str, signer: str) -> RelationshipRecord: return { "from_handle": frm, "to_handle": to, "edge_type": "spawns", "weight": None, "authorized_by": [{"signer": signer, "signature": "sig", "signed_at": _now().isoformat()}], } async def _push_identity_commit( db_session: AsyncSession, *, repo_name: str, manifest: dict[str, bytes], ) -> tuple[str, bytes]: """Create an identity-domain repo and build the mpack for one commit whose snapshot contains ``manifest`` (path -> file content). Returns (repo_id, mpack_bytes); does not push -- caller drives wire_push_unpack_mpack so it can assert on the raised exception or the resulting state.""" repo = await create_repo( db_session, name=repo_name, owner=_OWNER, owner_user_id=_IDENTITY_ID, visibility="private", domain="identity", initialize=False, ) await db_session.commit() oid_manifest = {path: blob_id(content) for path, content in manifest.items()} snap_id = hash_snapshot(oid_manifest, []) commit_id = _cid(repo_name) mpack_bytes = build_wire_mpack({ "commits": [_raw_commit(commit_id, snap_id)], "snapshots": [{ "snapshot_id": snap_id, "parent_snapshot_id": None, "delta_upsert": oid_manifest, "delta_remove": [], "directories": [], }], "blobs": [ {"object_id": oid, "content": manifest[path]} for path, oid in oid_manifest.items() ], "tags": [], }) return repo.repo_id, mpack_bytes, commit_id @pytest.mark.asyncio async def test_identity_cycle_is_rejected_by_the_real_wire_push_endpoint( db_session: AsyncSession, ) -> None: """RED: a push introducing a 2-node identity cycle (alice spawns bob, bob spawns alice -- an I1 violation) must be rejected by the live wire_push_unpack_mpack pipeline, not silently accepted.""" manifest = { identity_path("alice"): record_to_bytes(_identity_record("alice", "human")), identity_path("bob"): record_to_bytes(_identity_record("bob", "agent")), relationship_path("alice", "spawns", "bob"): record_to_bytes(_spawns("alice", "bob", "alice")), relationship_path("bob", "spawns", "alice"): record_to_bytes(_spawns("bob", "alice", "bob")), } repo_id, mpack_bytes, commit_id = await _push_identity_commit( db_session, repo_name="identity-cycle-repro", manifest=manifest ) 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="I1"): await wire_push_unpack_mpack( db_session, repo_id, mpack_key, pusher_id=_OWNER, branch="main", head_commit_id=commit_id, commits_count=1, blobs_count=4, force=True, ) stored = await db_session.get(MusehubCommit, commit_id) assert stored is None, ( "a push containing an identity-graph cycle must be rejected outright -- " "the commit must never be persisted" ) @pytest.mark.asyncio async def test_valid_identity_push_is_accepted_by_the_real_wire_push_endpoint( db_session: AsyncSession, ) -> None: """GREEN control: a valid, acyclic, fully-authorized identity push must still succeed -- the new validation must not reject legitimate pushes.""" manifest = { identity_path("carol"): record_to_bytes(_identity_record("carol", "human")), identity_path("dave"): record_to_bytes(_identity_record("dave", "agent")), relationship_path("carol", "spawns", "dave"): record_to_bytes(_spawns("carol", "dave", "carol")), } repo_id, mpack_bytes, commit_id = await _push_identity_commit( db_session, repo_name="identity-valid-push", manifest=manifest ) 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_id, mpack_key, pusher_id=_OWNER, branch="main", head_commit_id=commit_id, commits_count=1, blobs_count=3, force=True, ) await db_session.flush() stored = await db_session.get(MusehubCommit, commit_id) assert stored is not None, "a valid identity push must be accepted and persisted"