gabriel / muse public

test_snapshot_content_migrate_refs.py file-level

at sha256:3 · View file ↗ · Intel ↗

History
1 files
1 commits
0 hotspots
0 🧊 dead
0 💥 blast risk
sha256:2 chore: bump version to 0.2.0.dev4 for nightly.4 · gabriel · Jul 9, 2026
1 """TDD -- muse#83 Phase 3: ref/reflog integration.
2
3 No new rewrite logic here -- migrate_snapshot_content_and_cascade() composes
4 Phase 1 (migrate_snapshot_content_ids), Phase 2 (migrate_commit_ids_cascade),
5 and muse.core.migrate's existing, already-tested generic ref/reflog passes
6 (_migrate_refs/_migrate_remote_refs/_migrate_reflogs) unchanged. These tests
7 prove the composition is wired correctly end to end, not that the generic
8 passes themselves work (that's muse.core.migrate's own test suite).
9 """
10 from __future__ import annotations
11
12 import datetime
13 import json
14 import pathlib
15
16 import pytest
17 from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
18
19 from muse.core.ids import hash_commit, hash_snapshot
20 from muse.core.object_store import object_path
21 from muse.core.paths import heads_dir, logs_dir, muse_dir, objects_dir, ref_path, remotes_dir
22 from muse.core.provenance import provenance_payload, sign_commit_ed25519
23 from muse.core.snapshot_content_migrate import migrate_snapshot_content_and_cascade
24 from muse.core.transport import SigningIdentity
25
26 _AT_ISO = datetime.datetime(2026, 1, 1, tzinfo=datetime.timezone.utc).isoformat()
27
28
29 @pytest.fixture
30 def repo(tmp_path: pathlib.Path) -> pathlib.Path:
31 objects_dir(tmp_path).mkdir(parents=True, exist_ok=True)
32 heads_dir(tmp_path).mkdir(parents=True, exist_ok=True)
33 return tmp_path
34
35
36 def _make_identity(handle: str = "gabriel") -> SigningIdentity:
37 return SigningIdentity(handle=handle, private_key=Ed25519PrivateKey.generate())
38
39
40 def _sign(commit_id: str, private_key: Ed25519PrivateKey) -> str:
41 payload = provenance_payload(commit_id=commit_id, author="gabriel", committed_at=_AT_ISO)
42 return sign_commit_ed25519(payload, private_key)
43
44
45 def _write_unified_commit(
46 repo: pathlib.Path, commit_id: str, *, parent_commit_id: str | None,
47 snapshot_id: str, message: str, signature: str,
48 ) -> None:
49 raw = {
50 "commit_id": commit_id, "branch": "main", "snapshot_id": snapshot_id,
51 "message": message, "committed_at": _AT_ISO, "parent_commit_id": parent_commit_id,
52 "parent2_commit_id": None, "author": "gabriel", "agent_id": "", "model_id": "",
53 "toolchain_id": "", "prompt_hash": "", "signature": signature,
54 "signer_public_key": "", "signer_key_id": "", "format_version": 8,
55 "sem_ver_bump": "none", "breaking_changes": [],
56 }
57 payload = json.dumps(raw, separators=(",", ":")).encode()
58 header = f"commit {len(payload)}\0".encode()
59 path = object_path(repo, commit_id)
60 path.parent.mkdir(parents=True, exist_ok=True)
61 path.write_bytes(header + payload)
62
63
64 def _write_unified_snapshot(repo: pathlib.Path, declared_id: str, manifest: dict[str, str]) -> None:
65 payload = json.dumps({
66 "snapshot_id": declared_id, "manifest": manifest, "directories": [],
67 "created_at": _AT_ISO,
68 }, separators=(",", ":")).encode()
69 header = f"snapshot {len(payload)}\0".encode()
70 path = object_path(repo, declared_id)
71 path.parent.mkdir(parents=True, exist_ok=True)
72 path.write_bytes(header + payload)
73
74
75 def _build_chain(repo: pathlib.Path, identity: SigningIdentity) -> dict[str, str]:
76 """A -> B(bad snapshot) -> C, matching the plan doc's minimal repro shape."""
77 manifest_a = {"a.txt": "sha256:" + "1" * 64}
78 snap_a = hash_snapshot(manifest_a, None)
79 _write_unified_snapshot(repo, snap_a, manifest_a)
80
81 manifest_b = {**manifest_a, "b.txt": "sha256:" + "2" * 64}
82 wrong_snap_b = "sha256:" + "0" * 64
83 _write_unified_snapshot(repo, wrong_snap_b, manifest_b)
84
85 manifest_c = {**manifest_b, "c.txt": "sha256:" + "3" * 64}
86 snap_c = hash_snapshot(manifest_c, None)
87 _write_unified_snapshot(repo, snap_c, manifest_c)
88
89 id_a = hash_commit(parent_ids=[], snapshot_id=snap_a, message="A",
90 committed_at_iso=_AT_ISO, author="gabriel", signer_public_key="")
91 id_b = hash_commit(parent_ids=[id_a], snapshot_id=wrong_snap_b, message="B",
92 committed_at_iso=_AT_ISO, author="gabriel", signer_public_key="")
93 id_c = hash_commit(parent_ids=[id_b], snapshot_id=snap_c, message="C",
94 committed_at_iso=_AT_ISO, author="gabriel", signer_public_key="")
95
96 _write_unified_commit(repo, id_a, parent_commit_id=None, snapshot_id=snap_a, message="A",
97 signature=_sign(id_a, identity.private_key))
98 _write_unified_commit(repo, id_b, parent_commit_id=id_a, snapshot_id=wrong_snap_b, message="B",
99 signature=_sign(id_b, identity.private_key))
100 _write_unified_commit(repo, id_c, parent_commit_id=id_b, snapshot_id=snap_c, message="C",
101 signature=_sign(id_c, identity.private_key))
102
103 return {"id_a": id_a, "id_b": id_b, "id_c": id_c}
104
105
106 # ---------------------------------------------------------------------------
107 # RF_01 -- a branch head pointing at a rewritten commit resolves to the new ID
108 # ---------------------------------------------------------------------------
109
110
111 def test_rf_01_branch_head_updated_to_new_commit_id(repo: pathlib.Path) -> None:
112 identity = _make_identity()
113 ids = _build_chain(repo, identity)
114 ref_path(repo, "main").write_text(f"{ids['id_c']}\n", encoding="utf-8")
115
116 result = migrate_snapshot_content_and_cascade(repo, identity, dry_run=False)
117
118 new_c = result.commit_id_map[ids["id_c"]]
119 assert ref_path(repo, "main").read_text(encoding="utf-8").strip() == new_c
120 assert result.refs_updated == 1
121
122
123 # ---------------------------------------------------------------------------
124 # RF_02 -- a branch head pointing at an untouched commit is byte-identical
125 # ---------------------------------------------------------------------------
126
127
128 def test_rf_02_untouched_branch_ref_unchanged(repo: pathlib.Path) -> None:
129 identity = _make_identity()
130 ids = _build_chain(repo, identity)
131 ref_path(repo, "main").write_text(f"{ids['id_a']}\n", encoding="utf-8")
132
133 result = migrate_snapshot_content_and_cascade(repo, identity, dry_run=False)
134
135 assert ids["id_a"] not in result.commit_id_map
136 assert ref_path(repo, "main").read_text(encoding="utf-8").strip() == ids["id_a"]
137
138
139 # ---------------------------------------------------------------------------
140 # RF_03 -- remote-tracking refs are updated the same way
141 # ---------------------------------------------------------------------------
142
143
144 def test_rf_03_remote_tracking_ref_updated(repo: pathlib.Path) -> None:
145 identity = _make_identity()
146 ids = _build_chain(repo, identity)
147 remote_ref = remotes_dir(repo) / "staging" / "main"
148 remote_ref.parent.mkdir(parents=True, exist_ok=True)
149 remote_ref.write_text(f"{ids['id_c']}\n", encoding="utf-8")
150
151 result = migrate_snapshot_content_and_cascade(repo, identity, dry_run=False)
152
153 new_c = result.commit_id_map[ids["id_c"]]
154 assert remote_ref.read_text(encoding="utf-8").strip() == new_c
155 assert result.remote_refs_updated == 1
156
157
158 # ---------------------------------------------------------------------------
159 # RF_04 -- reflog entries mentioning a rewritten commit are rewritten;
160 # entries mentioning an untouched commit are not
161 # ---------------------------------------------------------------------------
162
163
164 def test_rf_04_reflog_entries_rewritten_selectively(repo: pathlib.Path) -> None:
165 identity = _make_identity()
166 ids = _build_chain(repo, identity)
167 reflog = logs_dir(repo) / "HEAD"
168 reflog.parent.mkdir(parents=True, exist_ok=True)
169 reflog.write_text(
170 f"{ids['id_a']} {ids['id_b']} gabriel: commit B\n"
171 f"{ids['id_b']} {ids['id_c']} gabriel: commit C\n",
172 encoding="utf-8",
173 )
174
175 result = migrate_snapshot_content_and_cascade(repo, identity, dry_run=False)
176
177 new_b = result.commit_id_map[ids["id_b"]]
178 new_c = result.commit_id_map[ids["id_c"]]
179 content = reflog.read_text(encoding="utf-8")
180 assert ids["id_a"] in content, "A was never rewritten -- must still appear verbatim"
181 assert new_b in content
182 assert new_c in content
183 assert ids["id_b"] not in content, "old (superseded) B id must not remain in the reflog"
184 assert ids["id_c"] not in content, "old (superseded) C id must not remain in the reflog"
185
186
187 # ---------------------------------------------------------------------------
188 # RF_05 -- end-to-end: the full composed result matches Phase 1 + Phase 2
189 # run independently
190 # ---------------------------------------------------------------------------
191
192
193 def test_rf_05_composed_result_matches_independent_phase_runs(repo: pathlib.Path) -> None:
194 from muse.core.snapshot_content_migrate import (
195 migrate_commit_ids_cascade,
196 migrate_snapshot_content_ids,
197 )
198
199 identity = _make_identity()
200 ids = _build_chain(repo, identity)
201 ref_path(repo, "main").write_text(f"{ids['id_c']}\n", encoding="utf-8")
202
203 # Run the phases independently on a throwaway copy's worth of state by
204 # just calling them in sequence manually (same repo -- both approaches
205 # must agree since they're pure recomputation over the same on-disk data).
206 snap_result = migrate_snapshot_content_ids(repo, dry_run=True)
207 commit_result = migrate_commit_ids_cascade(repo, snap_result.id_map, identity, dry_run=True)
208
209 combined = migrate_snapshot_content_and_cascade(repo, identity, dry_run=True)
210
211 assert combined.snapshot_id_map == snap_result.id_map
212 assert combined.commit_id_map == commit_result.id_map