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