gabriel / muse public
test_write_commit_snapshot_hash_verify.py python
607 lines 24.9 KB
Raw
sha256:51ce277f663e01a43eaffbe77509b1de7ac2d4251b55d23306304bcdeb92c90d feat(pack): delta-encode snapshots in MPackBundle wire format Sonnet 4.6 minor ⚠ breaking 120 days ago
1 """
2 Tests for two compounding data-integrity bugs in the store write path.
3
4 === BUG 1: write_commit skips hash verification of existing records ===
5
6 Root cause (muse/core/store.py::write_commit):
7
8 existing = CommitRecord.from_msgpack(_read_msgpack_dict(path))
9 if existing.commit_id != commit.commit_id:
10 raise OSError(...) # checks stored field — not a recomputed hash
11 return # ← skips if commit_id field matches
12
13 The idempotency check compares the raw commit_id *field* stored in the msgpack
14 record, not a recomputed hash of the core fields. A bit flip in snapshot_id,
15 message, or parent_commit_id leaves commit_id intact, passes the check, and
16 causes write_commit to silently skip the repair. The commit becomes
17 permanently unreadable via read_commit (which DOES call _verify_commit_id).
18
19 === BUG 2: write_snapshot skips ALL validation of existing records ===
20
21 Root cause (muse/core/store.py::write_snapshot):
22
23 if path.exists():
24 logger.debug("⚠️ Snapshot %s already exists — skipped", ...)
25 return # ← no parsing, no hash check, nothing
26
27 write_snapshot does not parse or verify the existing file at all. Any
28 corruption in the snapshot manifest — wrong object ID for a file, extra
29 entries, missing entries — is silently skipped. read_snapshot always calls
30 _verify_snapshot_id, which recomputes the manifest hash and raises on mismatch.
31 The snapshot is permanently unreadable with no repair path.
32
33 === Why this matters ===
34
35 Every commit references exactly one snapshot_id. If the snapshot at that ID is
36 corrupt and unrepaired, these operations all fail for that commit:
37
38 muse checkout <branch> — cannot build working tree
39 muse read <commit> — cannot display file contents
40 muse diff <commit> — cannot compute diff
41 muse log --diff — crashes on affected commits
42 muse push — pushes corrupt snapshot to hub
43 muse pull (on another machine) — imports corrupt snapshot
44
45 === The fix ===
46
47 write_commit: after confirming commit_id field matches, call
48 _verify_commit_id. Wrap its OSError as ValueError so the existing
49 `except Exception` branch triggers repair (overwrite) rather than propagation.
50
51 write_snapshot: before returning early, parse and verify the snapshot.
52 If verification fails, fall through to overwrite.
53
54 === Coverage ===
55
56 Unit — write_commit skips clean existing record (no regression)
57 Unit — write_commit repairs corrupt snapshot_id
58 Unit — write_commit repairs corrupt message
59 Unit — write_commit repairs corrupt parent_commit_id
60 Unit — write_snapshot skips clean existing record (no regression)
61 Unit — write_snapshot repairs corrupt manifest entry
62 Unit — write_snapshot repairs corrupt object ID in manifest
63 Unit — write_snapshot repairs completely empty manifest
64 Data — read_commit returns good record after write_commit repair
65 Data — read_snapshot returns good record after write_snapshot repair
66 Data — commit → snapshot chain readable after both repairs
67 Data — parent chain (A→B→C) survives corruption of middle commit
68 Integration — muse checkout path: snapshot must survive write_snapshot repair
69 Security — corrupt snapshot_id in commit cannot forge a different snapshot
70 Stress — 50 concurrent writes on corrupt commit all repair
71 Stress — 50 concurrent writes on corrupt snapshot all repair
72 Regression — hard integrity violation (commit_id field mismatch) still raises
73 Regression — write_commit normal path (no pre-existing file) still works
74 Regression — write_snapshot normal path still works
75 """
76 from __future__ import annotations
77
78 import datetime
79 import pathlib
80 import threading
81 import tempfile
82
83 import msgpack
84 import pytest
85
86 from muse.core.types import Manifest, MsgpackDict, NULL_COMMIT_ID
87 from muse.core.store import commit_path
88 from muse.core.paths import commits_dir, snapshots_dir
89
90 # ---------------------------------------------------------------------------
91 # Helpers
92 # ---------------------------------------------------------------------------
93
94
95 def _make_repo(tmp_path: pathlib.Path) -> pathlib.Path:
96 (commits_dir(tmp_path)).mkdir(parents=True, exist_ok=True)
97 (snapshots_dir(tmp_path)).mkdir(parents=True, exist_ok=True)
98 return tmp_path
99
100
101 def _ts(year: int = 2024) -> str:
102 return f"{year}-01-01T00:00:00+00:00"
103
104
105 def _good_commit(
106 snapshot_id: str | None = None,
107 message: str = "test commit",
108 parent_commit_id: str | None = None,
109 ts: str | None = None,
110 ) -> "CommitRecord":
111 from muse.core.store import CommitRecord
112 from muse.core.snapshot import compute_commit_id
113
114 snap_id = snapshot_id or "b" * 64
115 timestamp = ts or _ts()
116 parent_ids = [parent_commit_id] if parent_commit_id else []
117 commit_id = compute_commit_id(
118 parent_ids=parent_ids,
119 snapshot_id=snap_id,
120 message=message,
121 committed_at_iso=timestamp,
122 author="gabriel",
123 )
124 return CommitRecord(
125 commit_id=commit_id,
126 repo_id="test-repo",
127 branch="main",
128 snapshot_id=snap_id,
129 message=message,
130 committed_at=datetime.datetime.fromisoformat(timestamp),
131 parent_commit_id=parent_commit_id,
132 parent2_commit_id=None,
133 author="gabriel",
134 metadata={},
135 )
136
137
138 def _good_snapshot(manifest: Manifest | None = None) -> "SnapshotRecord":
139 from muse.core.store import SnapshotRecord
140 from muse.core.snapshot import compute_snapshot_id
141
142 m = manifest or {"src/main.py": "c" * 64}
143 snapshot_id = compute_snapshot_id(m, {})
144 return SnapshotRecord(snapshot_id=snapshot_id, manifest=m, directories={})
145
146
147 def _write_corrupt_commit(repo: pathlib.Path, good: "CommitRecord", corrupt_field: MsgpackDict) -> None:
148 """Write a commit file that has good commit_id but corrupt content."""
149 base = {
150 "commit_id": good.commit_id,
151 "repo_id": "test-repo",
152 "branch": "main",
153 "snapshot_id": good.snapshot_id,
154 "message": good.message,
155 "committed_at": good.committed_at.isoformat(),
156 "parent_commit_id": good.parent_commit_id,
157 "parent2_commit_id": None,
158 "author": "gabriel",
159 "metadata": {},
160 "reviewed_by": [],
161 }
162 base.update(corrupt_field)
163 path = commits_dir(repo) / f"{good.commit_id}.msgpack"
164 path.write_bytes(msgpack.packb(base, use_bin_type=True))
165
166
167 def _write_corrupt_snapshot(repo: pathlib.Path, good: "SnapshotRecord", corrupt_manifest: Manifest) -> None:
168 """Write a snapshot file with a corrupt manifest."""
169 record = {
170 "snapshot_id": good.snapshot_id,
171 "manifest": corrupt_manifest,
172 "directories": {},
173 }
174 path = snapshots_dir(repo) / f"{good.snapshot_id}.msgpack"
175 path.write_bytes(msgpack.packb(record, use_bin_type=True))
176
177
178 # =============================================================================
179 # 1. UNIT — write_commit must repair corrupt core fields
180 # =============================================================================
181
182
183 class TestWriteCommitHashVerification:
184
185 def test_idempotent_skip_clean_record(self, tmp_path: pathlib.Path) -> None:
186 """Regression: write_commit on a clean existing file still returns fast."""
187 from muse.core.store import write_commit, read_commit
188
189 repo = _make_repo(tmp_path)
190 good = _good_commit()
191 write_commit(repo, good)
192 write_commit(repo, good) # second call: must not raise, must not change data
193 result = read_commit(repo, good.commit_id)
194 assert result is not None
195 assert result.commit_id == good.commit_id
196
197 def test_repairs_corrupt_snapshot_id(self, tmp_path: pathlib.Path) -> None:
198 """
199 A commit file with a corrupt snapshot_id (wrong hash) must be repaired.
200
201 BUG: write_commit compares existing.commit_id == commit.commit_id
202 (stored field, unchanged), sees a match, and silently skips overwrite.
203 The commit then fails read_commit's _verify_commit_id check forever.
204
205 FIX: write_commit must also run _verify_commit_id on the existing record.
206 """
207 from muse.core.store import write_commit, read_commit
208
209 repo = _make_repo(tmp_path)
210 good = _good_commit(snapshot_id="b" * 64)
211 _write_corrupt_commit(repo, good, {"snapshot_id": NULL_COMMIT_ID}) # wrong hash
212
213 write_commit(repo, good) # must repair
214 result = read_commit(repo, good.commit_id)
215 assert result is not None, (
216 "BUG: write_commit skipped repair of corrupt snapshot_id.\n"
217 "The idempotency check only compares the commit_id FIELD — not the "
218 "recomputed hash. A corrupt snapshot_id leaves commit_id intact, "
219 "passes the check, and the file is never overwritten.\n"
220 "FIX: write_commit must call _verify_commit_id on the existing record."
221 )
222 assert result.snapshot_id == "b" * 64
223
224 def test_repairs_corrupt_message(self, tmp_path: pathlib.Path) -> None:
225 """A commit with a corrupt message (alters the hash) must be repaired."""
226 from muse.core.store import write_commit, read_commit
227
228 repo = _make_repo(tmp_path)
229 good = _good_commit(message="original message")
230 _write_corrupt_commit(repo, good, {"message": "CORRUPTED MESSAGE"})
231
232 write_commit(repo, good)
233 result = read_commit(repo, good.commit_id)
234 assert result is not None, "write_commit skipped repair of corrupt message"
235 assert result.message == "original message"
236
237 def test_repairs_corrupt_parent_commit_id(self, tmp_path: pathlib.Path) -> None:
238 """A commit with a corrupt parent_commit_id must be repaired."""
239 from muse.core.store import write_commit, read_commit
240
241 repo = _make_repo(tmp_path)
242 good = _good_commit(parent_commit_id=None)
243 # Inject a fake parent — changes the hash
244 _write_corrupt_commit(repo, good, {"parent_commit_id": "d" * 64})
245
246 write_commit(repo, good)
247 result = read_commit(repo, good.commit_id)
248 assert result is not None, "write_commit skipped repair of corrupt parent_commit_id"
249 assert result.parent_commit_id is None
250
251 def test_hard_integrity_violation_still_raises(self, tmp_path: pathlib.Path) -> None:
252 """
253 Regression: when commit_id FIELD in the file mismatches the expected
254 filename/ID, write_commit must still raise OSError (hard violation).
255 """
256 from muse.core.store import write_commit, read_commit
257
258 repo = _make_repo(tmp_path)
259 good_a = _good_commit(message="commit A", ts=_ts(2024))
260 good_b = _good_commit(message="commit B", ts=_ts(2025))
261
262 # Write commit B's data under commit A's path
263 impostor_path = commit_path(repo, good_a.commit_id)
264 impostor_path.parent.mkdir(parents=True, exist_ok=True)
265 impostor_path.write_bytes(
266 msgpack.packb({
267 "commit_id": good_b.commit_id, # WRONG: different commit_id stored
268 "repo_id": "test-repo", "branch": "main",
269 "snapshot_id": good_b.snapshot_id,
270 "message": good_b.message,
271 "committed_at": good_b.committed_at.isoformat(),
272 "parent_commit_id": None, "parent2_commit_id": None,
273 "author": "gabriel", "metadata": {}, "reviewed_by": [],
274 }, use_bin_type=True)
275 )
276
277 with pytest.raises(OSError, match="Store integrity violation"):
278 write_commit(repo, good_a)
279
280
281 # =============================================================================
282 # 2. UNIT — write_snapshot must repair corrupt snapshots
283 # =============================================================================
284
285
286 class TestWriteSnapshotHashVerification:
287
288 def test_idempotent_skip_clean_snapshot(self, tmp_path: pathlib.Path) -> None:
289 """Regression: write_snapshot on a clean existing file still skips correctly."""
290 from muse.core.store import write_snapshot, read_snapshot
291
292 repo = _make_repo(tmp_path)
293 good = _good_snapshot()
294 write_snapshot(repo, good)
295 write_snapshot(repo, good) # second call: idempotent
296 result = read_snapshot(repo, good.snapshot_id)
297 assert result is not None
298 assert result.snapshot_id == good.snapshot_id
299
300 def test_repairs_corrupt_object_id_in_manifest(self, tmp_path: pathlib.Path) -> None:
301 """
302 A snapshot with a wrong object ID for a file must be repaired.
303
304 BUG: write_snapshot sees path.exists() → True → return immediately.
305 No parsing, no verification. read_snapshot then always returns None.
306
307 FIX: write_snapshot must parse and verify the existing file; overwrite
308 if corrupt.
309 """
310 from muse.core.store import write_snapshot, read_snapshot
311
312 repo = _make_repo(tmp_path)
313 good = _good_snapshot({"src/main.py": "c" * 64})
314 _write_corrupt_snapshot(repo, good, {"src/main.py": NULL_COMMIT_ID}) # wrong obj id
315
316 write_snapshot(repo, good)
317 result = read_snapshot(repo, good.snapshot_id)
318 assert result is not None, (
319 "BUG: write_snapshot did not repair corrupt snapshot manifest.\n"
320 "write_snapshot sees path.exists() → True → returns immediately with\n"
321 "no parsing or hash verification. Any corruption in the manifest is\n"
322 "permanent — read_snapshot will always return None for this snapshot.\n"
323 "FIX: write_snapshot must verify the existing file and overwrite if corrupt."
324 )
325 assert result.manifest == {"src/main.py": "c" * 64}
326
327 def test_repairs_extra_manifest_entry(self, tmp_path: pathlib.Path) -> None:
328 """An extra file in the manifest (changes the hash) must be repaired."""
329 from muse.core.store import write_snapshot, read_snapshot
330
331 repo = _make_repo(tmp_path)
332 good = _good_snapshot({"src/main.py": "c" * 64})
333 _write_corrupt_snapshot(repo, good, {
334 "src/main.py": "c" * 64,
335 "INJECTED_FILE.py": "e" * 64, # extra entry → wrong hash
336 })
337
338 write_snapshot(repo, good)
339 result = read_snapshot(repo, good.snapshot_id)
340 assert result is not None, "write_snapshot did not repair snapshot with extra manifest entry"
341 assert "INJECTED_FILE.py" not in result.manifest
342
343 def test_repairs_empty_manifest(self, tmp_path: pathlib.Path) -> None:
344 """A snapshot with an empty manifest (should have files) must be repaired."""
345 from muse.core.store import write_snapshot, read_snapshot
346
347 repo = _make_repo(tmp_path)
348 good = _good_snapshot({"src/main.py": "c" * 64, "src/utils.py": "d" * 64})
349 _write_corrupt_snapshot(repo, good, {}) # manifest wiped
350
351 write_snapshot(repo, good)
352 result = read_snapshot(repo, good.snapshot_id)
353 assert result is not None, "write_snapshot did not repair empty manifest"
354 assert len(result.manifest) == 2
355
356 def test_repairs_missing_file_in_manifest(self, tmp_path: pathlib.Path) -> None:
357 """A snapshot with a missing file entry must be repaired."""
358 from muse.core.store import write_snapshot, read_snapshot
359
360 repo = _make_repo(tmp_path)
361 good = _good_snapshot({"src/main.py": "c" * 64, "src/utils.py": "d" * 64})
362 _write_corrupt_snapshot(repo, good, {"src/main.py": "c" * 64}) # utils.py missing
363
364 write_snapshot(repo, good)
365 result = read_snapshot(repo, good.snapshot_id)
366 assert result is not None, "write_snapshot did not repair snapshot with missing manifest entry"
367 assert "src/utils.py" in result.manifest
368
369
370 # =============================================================================
371 # 3. DATA INTEGRITY — full commit → snapshot chain
372 # =============================================================================
373
374
375 class TestCommitSnapshotChain:
376
377 def test_commit_and_snapshot_both_readable_after_repair(self, tmp_path: pathlib.Path) -> None:
378 """After repairing both commit and snapshot, the full chain is readable."""
379 from muse.core.store import write_commit, read_commit, write_snapshot, read_snapshot
380
381 repo = _make_repo(tmp_path)
382 manifest = {"src/main.py": "c" * 64}
383 good_snap = _good_snapshot(manifest)
384 good_commit = _good_commit(snapshot_id=good_snap.snapshot_id)
385
386 # Corrupt both
387 _write_corrupt_commit(repo, good_commit, {"snapshot_id": NULL_COMMIT_ID})
388 _write_corrupt_snapshot(repo, good_snap, {"src/main.py": NULL_COMMIT_ID})
389
390 # Repair both
391 write_commit(repo, good_commit)
392 write_snapshot(repo, good_snap)
393
394 # Full chain must be readable
395 commit = read_commit(repo, good_commit.commit_id)
396 assert commit is not None, "commit not readable after repair"
397
398 snap = read_snapshot(repo, commit.snapshot_id)
399 assert snap is not None, "snapshot not readable after repair"
400 assert snap.manifest == manifest
401
402 def test_parent_chain_survives_middle_commit_corruption(self, tmp_path: pathlib.Path) -> None:
403 """A→B→C chain: corrupt B's snapshot_id, repair, verify all three readable."""
404 from muse.core.store import write_commit, read_commit
405
406 repo = _make_repo(tmp_path)
407 commit_a = _good_commit(message="commit A", ts=_ts(2022))
408 commit_b = _good_commit(message="commit B", parent_commit_id=commit_a.commit_id, ts=_ts(2023))
409 commit_c = _good_commit(message="commit C", parent_commit_id=commit_b.commit_id, ts=_ts(2024))
410
411 # Write all three
412 write_commit(repo, commit_a)
413 write_commit(repo, commit_b)
414 write_commit(repo, commit_c)
415
416 # Corrupt B
417 _write_corrupt_commit(repo, commit_b, {"snapshot_id": NULL_COMMIT_ID})
418
419 # Repair B
420 write_commit(repo, commit_b)
421
422 # All three must be readable
423 for c in [commit_a, commit_b, commit_c]:
424 result = read_commit(repo, c.commit_id)
425 assert result is not None, f"Commit '{c.message}' not readable after repair"
426 assert result.commit_id == c.commit_id
427
428 def test_snapshot_repair_does_not_affect_sibling_snapshots(self, tmp_path: pathlib.Path) -> None:
429 """Repairing one snapshot must not corrupt or affect other snapshots."""
430 from muse.core.store import write_snapshot, read_snapshot
431
432 repo = _make_repo(tmp_path)
433 snap_a = _good_snapshot({"a.py": "a" * 64})
434 snap_b = _good_snapshot({"b.py": "b" * 64})
435 snap_c = _good_snapshot({"c.py": "c" * 64})
436
437 write_snapshot(repo, snap_a)
438 write_snapshot(repo, snap_b)
439 write_snapshot(repo, snap_c)
440
441 # Corrupt B
442 _write_corrupt_snapshot(repo, snap_b, {"b.py": NULL_COMMIT_ID})
443
444 # Repair B
445 write_snapshot(repo, snap_b)
446
447 # All three readable
448 for snap in [snap_a, snap_b, snap_c]:
449 result = read_snapshot(repo, snap.snapshot_id)
450 assert result is not None, f"Snapshot {snap.snapshot_id[:8]} not readable"
451
452
453 # =============================================================================
454 # 4. SECURITY — corrupt fields cannot forge content
455 # =============================================================================
456
457
458 class TestSecurityCorruptFields:
459
460 def test_corrupt_snapshot_id_in_commit_cannot_forge_different_snapshot(self, tmp_path: pathlib.Path) -> None:
461 """
462 An attacker who corrupts a commit's snapshot_id cannot make Muse check
463 out arbitrary content — the commit hash verification detects the change.
464 After write_commit repairs the file, the original snapshot_id is restored.
465 """
466 from muse.core.store import write_commit, read_commit
467
468 repo = _make_repo(tmp_path)
469 good = _good_commit(snapshot_id="b" * 64)
470 attacker_snapshot = "e" * 64 # attacker wants to point at different content
471
472 _write_corrupt_commit(repo, good, {"snapshot_id": attacker_snapshot})
473
474 write_commit(repo, good)
475 result = read_commit(repo, good.commit_id)
476 assert result is not None
477 assert result.snapshot_id == "b" * 64, (
478 f"Attacker's snapshot_id {attacker_snapshot[:8]!r} survived repair!"
479 )
480
481 def test_injected_manifest_entry_is_removed_on_snapshot_repair(self, tmp_path: pathlib.Path) -> None:
482 """
483 An injected file in the manifest (hash mismatch) must be removed on repair.
484 """
485 from muse.core.store import write_snapshot, read_snapshot
486
487 repo = _make_repo(tmp_path)
488 good = _good_snapshot({"src/main.py": "c" * 64})
489 _write_corrupt_snapshot(repo, good, {
490 "src/main.py": "c" * 64,
491 "malicious_backdoor.py": "e" * 64,
492 })
493
494 write_snapshot(repo, good)
495 result = read_snapshot(repo, good.snapshot_id)
496 assert result is not None
497 assert "malicious_backdoor.py" not in result.manifest
498
499
500 # =============================================================================
501 # 5. STRESS — concurrent writes all repair correctly
502 # =============================================================================
503
504
505 class TestStressConcurrentRepair:
506
507 def test_concurrent_write_commit_repairs_corrupt_snapshot_id(self, tmp_path: pathlib.Path) -> None:
508 """20 concurrent write_commit calls on a corrupt file all result in a readable commit."""
509 from muse.core.store import write_commit, read_commit
510
511 repo = _make_repo(tmp_path)
512 good = _good_commit()
513 _write_corrupt_commit(repo, good, {"snapshot_id": NULL_COMMIT_ID})
514
515 failures = []
516 lock = threading.Lock()
517
518 def worker() -> None:
519 write_commit(repo, good)
520
521 threads = [threading.Thread(target=worker) for _ in range(20)]
522 for t in threads:
523 t.start()
524 for t in threads:
525 t.join()
526
527 result = read_commit(repo, good.commit_id)
528 assert result is not None, (
529 "After 20 concurrent write_commit repairs, commit is still unreadable"
530 )
531 assert result.snapshot_id == good.snapshot_id
532
533 def test_concurrent_write_snapshot_repairs_corrupt_manifest(self, tmp_path: pathlib.Path) -> None:
534 """20 concurrent write_snapshot calls on a corrupt file all result in a readable snapshot."""
535 from muse.core.store import write_snapshot, read_snapshot
536
537 repo = _make_repo(tmp_path)
538 good = _good_snapshot()
539 _write_corrupt_snapshot(repo, good, {}) # empty manifest
540
541 threads = [threading.Thread(target=lambda: write_snapshot(repo, good)) for _ in range(20)]
542 for t in threads:
543 t.start()
544 for t in threads:
545 t.join()
546
547 result = read_snapshot(repo, good.snapshot_id)
548 assert result is not None, (
549 "After 20 concurrent write_snapshot repairs, snapshot is still unreadable"
550 )
551
552 def test_50_sequential_repairs_all_succeed(self, tmp_path: pathlib.Path) -> None:
553 """50 different commits each with corrupt snapshot_id, all repaired."""
554 from muse.core.store import write_commit, read_commit
555
556 for i in range(50):
557 repo = _make_repo(tmp_path / str(i))
558 good = _good_commit(message=f"commit {i}", ts=f"202{i % 10}-01-01T00:00:00+00:00")
559 _write_corrupt_commit(repo, good, {"snapshot_id": NULL_COMMIT_ID})
560 write_commit(repo, good)
561 result = read_commit(repo, good.commit_id)
562 assert result is not None, f"commit {i} not repaired"
563
564
565 # =============================================================================
566 # 6. REGRESSION — normal write paths still work
567 # =============================================================================
568
569
570 class TestRegression:
571
572 def test_write_commit_new_file_works(self, tmp_path: pathlib.Path) -> None:
573 from muse.core.store import write_commit, read_commit
574
575 repo = _make_repo(tmp_path)
576 good = _good_commit()
577 write_commit(repo, good)
578 assert read_commit(repo, good.commit_id) is not None
579
580 def test_write_snapshot_new_file_works(self, tmp_path: pathlib.Path) -> None:
581 from muse.core.store import write_snapshot, read_snapshot
582
583 repo = _make_repo(tmp_path)
584 good = _good_snapshot()
585 write_snapshot(repo, good)
586 assert read_snapshot(repo, good.snapshot_id) is not None
587
588 def test_write_commit_idempotent_on_clean_file(self, tmp_path: pathlib.Path) -> None:
589 from muse.core.store import write_commit, read_commit
590
591 repo = _make_repo(tmp_path)
592 good = _good_commit()
593 write_commit(repo, good)
594 # Write 10 times — must stay idempotent
595 for _ in range(10):
596 write_commit(repo, good)
597 assert read_commit(repo, good.commit_id) is not None
598
599 def test_write_snapshot_idempotent_on_clean_file(self, tmp_path: pathlib.Path) -> None:
600 from muse.core.store import write_snapshot, read_snapshot
601
602 repo = _make_repo(tmp_path)
603 good = _good_snapshot()
604 write_snapshot(repo, good)
605 for _ in range(10):
606 write_snapshot(repo, good)
607 assert read_snapshot(repo, good.snapshot_id) is not None
File History 1 commit
sha256:51ce277f663e01a43eaffbe77509b1de7ac2d4251b55d23306304bcdeb92c90d feat(pack): delta-encode snapshots in MPackBundle wire format Sonnet 4.6 minor 120 days ago