"""Unit tests for ``muse.core.patch_record`` — content-addressed Muse patch objects. Test tiers ---------- - Unit: PatchRecord dataclass, compute_patch_id, serialize/deserialize round-trip - Data integrity: patch_id is stable, deterministic, and changes with content - Security: patch_id forgery, tampered fields detected on re-verify - Edge: empty diff, initial commit (no parent), binary objects skipped gracefully """ from __future__ import annotations import hashlib import json import pathlib import pytest from muse.core.patch_record import ( PatchRecord, compute_patch_id, deserialize_patch, serialize_patch, ) from muse.core.ids import hash_snapshot as compute_snapshot_id from muse.core.commits import ( CommitRecord, write_commit, ) from muse.core.snapshots import ( SnapshotRecord, write_snapshot, ) from muse.core.object_store import write_object import datetime from muse.core.types import long_id, blob_id from muse.core.paths import muse_dir # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- def _init_repo(path: pathlib.Path) -> pathlib.Path: dot_muse = muse_dir(path) for sub in ("commits", "snapshots", "objects", "refs/heads"): (dot_muse / sub).mkdir(parents=True, exist_ok=True) (dot_muse / "HEAD").write_text("ref: refs/heads/main\n") (dot_muse / "repo.json").write_text(json.dumps({"repo_id": "test", "domain": "code"})) return path def _make_object(repo: pathlib.Path, content: bytes) -> str: """Write bytes to object store; return sha256: prefixed ID.""" oid = blob_id(content) write_object(repo, oid, content) return oid def _ts() -> datetime.datetime: return datetime.datetime(2026, 1, 1, tzinfo=datetime.timezone.utc) # --------------------------------------------------------------------------- # compute_patch_id # --------------------------------------------------------------------------- class TestComputePatchId: def test_returns_sha256_prefixed_string(self, tmp_path: pathlib.Path) -> None: repo = _init_repo(tmp_path) rec = PatchRecord( patch_id="", from_snapshot_id=long_id("a" * 64), to_snapshot_id=long_id("b" * 64), from_commit_id=long_id("c" * 64), to_commit_id=long_id("d" * 64), domain="code", format_version="1.0", created_at="2026-01-01T00:00:00+00:00", agent_id="", model_id="", signer_public_key="", signature="", intent="", sem_ver_bump="patch", breaking_changes=[], summary="test", ops=[], files_added=[], files_modified=[], files_deleted=[], files_renamed={}, required_objects=[], from_manifest={}, to_manifest={}, applicability={ "requires_snapshot": long_id("a" * 64), "independent_dimensions": [], "conflict_free": True, }, blobs={}, ) pid = compute_patch_id(rec) assert pid.startswith("sha256:") assert len(pid) == 71 # sha256: (7) + 64 hex def test_deterministic_across_calls(self, tmp_path: pathlib.Path) -> None: repo = _init_repo(tmp_path) rec = PatchRecord( patch_id="", from_snapshot_id=long_id("a" * 64), to_snapshot_id=long_id("b" * 64), from_commit_id=long_id("c" * 64), to_commit_id=long_id("d" * 64), domain="code", format_version="1.0", created_at="2026-01-01T00:00:00+00:00", agent_id="test-agent", model_id="claude-sonnet-4-6", signer_public_key="", signature="", intent="test intent", sem_ver_bump="minor", breaking_changes=[], summary="2 modified files", ops=[], files_added=["new.py"], files_modified=[], files_deleted=[], files_renamed={}, required_objects=[], from_manifest={}, to_manifest={}, applicability={ "requires_snapshot": long_id("a" * 64), "independent_dimensions": ["symbols"], "conflict_free": True, }, blobs={}, ) pid1 = compute_patch_id(rec) pid2 = compute_patch_id(rec) assert pid1 == pid2 def test_changes_with_different_content(self, tmp_path: pathlib.Path) -> None: base = dict( patch_id="", from_snapshot_id=long_id("a" * 64), to_snapshot_id=long_id("b" * 64), from_commit_id=long_id("c" * 64), to_commit_id=long_id("d" * 64), domain="code", format_version="1.0", created_at="2026-01-01T00:00:00+00:00", agent_id="", model_id="", signer_public_key="", signature="", intent="", sem_ver_bump="patch", breaking_changes=[], summary="v1", ops=[], files_added=[], files_modified=[], files_deleted=[], files_renamed={}, required_objects=[], from_manifest={}, to_manifest={}, applicability={"requires_snapshot": long_id("a" * 64), "independent_dimensions": [], "conflict_free": True}, ) r1 = PatchRecord(**base) r2 = PatchRecord(**{**base, "summary": "v2"}) assert compute_patch_id(r1) != compute_patch_id(r2) def test_patch_id_excludes_signature_field(self, tmp_path: pathlib.Path) -> None: """Signature must not influence patch_id (it signs the id, not the other way).""" base = dict( patch_id="", from_snapshot_id=long_id("a" * 64), to_snapshot_id=long_id("b" * 64), from_commit_id=long_id("c" * 64), to_commit_id=long_id("d" * 64), domain="code", format_version="1.0", created_at="2026-01-01T00:00:00+00:00", agent_id="", model_id="", signer_public_key="", signature="", intent="", sem_ver_bump="patch", breaking_changes=[], summary="test", ops=[], files_added=[], files_modified=[], files_deleted=[], files_renamed={}, required_objects=[], from_manifest={}, to_manifest={}, applicability={"requires_snapshot": long_id("a" * 64), "independent_dimensions": [], "conflict_free": True}, ) r_no_sig = PatchRecord(**base) r_with_sig = PatchRecord(**{**base, "signature": "abc123", "signer_public_key": "pubkey"}) assert compute_patch_id(r_no_sig) == compute_patch_id(r_with_sig) # --------------------------------------------------------------------------- # Serialization round-trip # --------------------------------------------------------------------------- class TestSerializeDeserialize: def _make_record(self) -> PatchRecord: rec = PatchRecord( patch_id="", from_snapshot_id=long_id("a" * 64), to_snapshot_id=long_id("b" * 64), from_commit_id=long_id("c" * 64), to_commit_id=long_id("d" * 64), domain="code", format_version="1.0", created_at="2026-01-01T00:00:00+00:00", agent_id="claude-code", model_id="claude-sonnet-4-6", signer_public_key="", signature="", intent="improve merge logic", sem_ver_bump="minor", breaking_changes=[], summary="1 modified file", ops=[{"op": "insert", "address": "main.py", "position": 0, "content_id": long_id("e" * 64), "content_summary": "new file", "action_label": "inserted"}], files_added=["main.py"], files_modified=[], files_deleted=[], files_renamed={}, required_objects=[long_id("e" * 64)], from_manifest={}, to_manifest={"main.py": long_id("e" * 64)}, applicability={ "requires_snapshot": long_id("a" * 64), "independent_dimensions": ["symbols", "imports"], "conflict_free": True, }, blobs={}, ) rec.patch_id = compute_patch_id(rec) return rec def test_serialize_returns_bytes(self) -> None: rec = self._make_record() data = serialize_patch(rec) assert isinstance(data, bytes) def test_deserialize_round_trip(self) -> None: rec = self._make_record() data = serialize_patch(rec) rec2 = deserialize_patch(data) assert rec2.patch_id == rec.patch_id assert rec2.domain == rec.domain assert rec2.summary == rec.summary assert rec2.ops == rec.ops assert rec2.files_added == rec.files_added assert rec2.from_manifest == rec.from_manifest assert rec2.to_manifest == rec.to_manifest def test_serialized_is_valid_json(self) -> None: rec = self._make_record() data = serialize_patch(rec) parsed = json.loads(data) assert "patch_id" in parsed assert "domain" in parsed def test_patch_id_preserved_through_round_trip(self) -> None: rec = self._make_record() data = serialize_patch(rec) rec2 = deserialize_patch(data) assert rec2.patch_id == rec.patch_id def test_deserialize_rejects_garbage(self) -> None: with pytest.raises(Exception): deserialize_patch(b"not valid json at all !!!!") def test_deserialize_rejects_missing_patch_id(self) -> None: data = json.dumps({"domain": "code"}).encode() with pytest.raises(Exception): deserialize_patch(data) # --------------------------------------------------------------------------- # PatchRecord dataclass # --------------------------------------------------------------------------- class TestPatchRecord: def test_has_required_fields(self) -> None: rec = PatchRecord( patch_id=long_id("a" * 64), from_snapshot_id=long_id("b" * 64), to_snapshot_id=long_id("c" * 64), from_commit_id=long_id("d" * 64), to_commit_id=long_id("e" * 64), domain="code", format_version="1.0", created_at="2026-01-01T00:00:00+00:00", agent_id="", model_id="", signer_public_key="", signature="", intent="", sem_ver_bump="patch", breaking_changes=[], summary="", ops=[], files_added=[], files_modified=[], files_deleted=[], files_renamed={}, required_objects=[], from_manifest={}, to_manifest={}, applicability={"requires_snapshot": long_id("b" * 64), "independent_dimensions": [], "conflict_free": True}, ) assert rec.domain == "code" assert rec.format_version == "1.0" assert rec.sem_ver_bump == "patch" def test_ops_with_action_label(self) -> None: """Each op can carry an action_label — Cohen-transform extension.""" op = { "op": "insert", "address": "foo.py", "position": 0, "content_id": long_id("a" * 64), "content_summary": "new function", "action_label": "inserted", } rec = PatchRecord( patch_id="", from_snapshot_id=long_id("a" * 64), to_snapshot_id=long_id("b" * 64), from_commit_id=long_id("c" * 64), to_commit_id=long_id("d" * 64), domain="code", format_version="1.0", created_at="2026-01-01T00:00:00+00:00", agent_id="", model_id="", signer_public_key="", signature="", intent="", sem_ver_bump="patch", breaking_changes=[], summary="", ops=[op], files_added=[], files_modified=[], files_deleted=[], files_renamed={}, required_objects=[], from_manifest={}, to_manifest={}, applicability={"requires_snapshot": long_id("a" * 64), "independent_dimensions": [], "conflict_free": True}, ) assert rec.ops[0]["action_label"] == "inserted" def test_applicability_has_requires_snapshot(self) -> None: rec = PatchRecord( patch_id="", from_snapshot_id=long_id("a" * 64), to_snapshot_id=long_id("b" * 64), from_commit_id=long_id("c" * 64), to_commit_id=long_id("d" * 64), domain="code", format_version="1.0", created_at="2026-01-01T00:00:00+00:00", agent_id="", model_id="", signer_public_key="", signature="", intent="", sem_ver_bump="patch", breaking_changes=[], summary="", ops=[], files_added=[], files_modified=[], files_deleted=[], files_renamed={}, required_objects=[], from_manifest={}, to_manifest={}, applicability={ "requires_snapshot": long_id("a" * 64), "independent_dimensions": ["symbols"], "conflict_free": False, }, ) assert rec.applicability["requires_snapshot"] == long_id("a" * 64) assert rec.applicability["conflict_free"] is False