gabriel / muse public
test_integrity_I5_commit_integrity.py python
726 lines 30.0 KB
Raw
sha256:51ce277f663e01a43eaffbe77509b1de7ac2d4251b55d23306304bcdeb92c90d feat(pack): delta-encode snapshots in MPackBundle wire format Sonnet 4.6 minor ⚠ breaking 121 days ago
1 """Phase 1.5 — Commit record integrity on re-read.
2
3 Tests cover:
4 - write_commit idempotency: silent drop of duplicate ID
5 - write_commit collision detection: existing file is corrupt → CRITICAL + overwrite
6 - write_commit integrity violation: existing record has mismatched commit_id
7 - read_commit: WARNING→CRITICAL upgrade for corrupt files
8 - read_commit_result: discriminated union (ok / not_found / corrupt)
9 - read_snapshot / read_snapshot_result: same guarantees
10 - get_all_commits / get_all_tags: CRITICAL on corrupt (previously silent)
11 - list_releases: CRITICAL on corrupt (previously silent)
12 - verify-pack integration after write_commit
13 - Concurrent write with same ID: first writer always wins (idempotency at scale)
14 - Regression: corrupt file must log CRITICAL (level 50), never WARNING (level 30)
15 """
16
17 from __future__ import annotations
18
19 import datetime
20 import json
21 import logging
22 import pathlib
23 import threading
24
25 import msgpack
26 import pytest
27
28 from muse.core.snapshot import compute_commit_id, compute_snapshot_id
29
30 from muse.core.types import Manifest, fake_id, long_id
31 from muse.core.paths import muse_dir
32
33 _REPO_ID = fake_id("repo")
34 from muse.core.store import (
35 CommitReadCorrupt,
36 CommitReadNotFound,
37 CommitReadOk,
38 CommitRecord,
39 ReleaseRecord,
40 SemVerTag,
41 SnapshotReadCorrupt,
42 SnapshotReadNotFound,
43 SnapshotReadOk,
44 SnapshotRecord,
45 TagRecord,
46 commit_read_is_corrupt,
47 commit_read_is_not_found,
48 commit_read_is_ok,
49 get_all_commits,
50 get_all_tags,
51 list_releases,
52 read_commit,
53 read_commit_result,
54 read_snapshot,
55 read_snapshot_result,
56 snapshot_read_is_corrupt,
57 snapshot_read_is_ok,
58 write_commit,
59 write_release,
60 write_snapshot,
61 write_tag,
62 commit_path,
63 snapshot_path,
64 tag_path,
65 release_path as _release_path,
66 )
67
68 # ---------------------------------------------------------------------------
69 # Helpers
70 # ---------------------------------------------------------------------------
71
72 def _make_commit(
73 root: pathlib.Path,
74 message: str = "msg",
75 branch: str = "main",
76 parent: str | None = None,
77 write: bool = True,
78 ) -> CommitRecord:
79 """Create a CommitRecord with a content-addressed commit_id.
80
81 Uses ``compute_commit_id`` so every record passes ``_verify_commit_id``
82 on read-back. ``write=False`` builds the record without persisting it —
83 useful for testing concurrent or idempotent write scenarios.
84 """
85 committed_at = datetime.datetime(2026, 3, 1, tzinfo=datetime.timezone.utc)
86 snap_id = compute_snapshot_id({})
87 parent_ids = [parent] if parent else []
88 cid = compute_commit_id(
89 parent_ids=parent_ids,
90 snapshot_id=snap_id,
91 message=message,
92 committed_at_iso=committed_at.isoformat(),
93 author="tester",
94 )
95 c = CommitRecord(
96 repo_id=_REPO_ID,
97 commit_id=cid,
98 branch=branch,
99 snapshot_id=snap_id,
100 message=message,
101 committed_at=committed_at,
102 author="tester",
103 parent_commit_id=parent,
104 parent2_commit_id=None,
105 )
106 if write:
107 write_commit(root, c)
108 return c
109
110
111 def _make_snapshot(
112 root: pathlib.Path, manifest: Manifest | None = None
113 ) -> SnapshotRecord:
114 """Create a SnapshotRecord with a content-addressed snapshot_id.
115
116 Pass distinct ``manifest`` dicts to get distinct snapshot_ids — e.g.
117 ``{"file-A.py": "a" * 64}`` vs ``{"file-B.py": "b" * 64}``.
118 """
119 m = manifest or {}
120 sid = compute_snapshot_id(m)
121 s = SnapshotRecord(
122 snapshot_id=sid,
123 manifest=m,
124 created_at=datetime.datetime(2026, 3, 1, tzinfo=datetime.timezone.utc),
125 )
126 write_snapshot(root, s)
127 return s
128
129
130 def _make_tag(root: pathlib.Path, tag_name: str) -> TagRecord:
131 t = TagRecord(
132 repo_id=_REPO_ID,
133 tag_id=fake_id(tag_name),
134 commit_id=fake_id("tag-commit"),
135 tag=tag_name,
136 created_at=datetime.datetime(2026, 1, 1, tzinfo=datetime.timezone.utc),
137 )
138 write_tag(root, t)
139 return t
140
141
142 def _make_release(root: pathlib.Path, tag: str, semver: SemVerTag) -> ReleaseRecord:
143 r = ReleaseRecord(
144 repo_id=_REPO_ID,
145 release_id=fake_id(tag + "-release"),
146 tag=tag,
147 semver=semver,
148 channel="stable",
149 commit_id=fake_id("release-commit"),
150 snapshot_id=fake_id(tag),
151 title=tag,
152 body="",
153 changelog=[],
154 )
155 write_release(root, r)
156 return r
157
158
159
160 def _tag_path(root: pathlib.Path, tag_id: str) -> pathlib.Path:
161 return tag_path(root, _REPO_ID, tag_id)
162
163
164 # ---------------------------------------------------------------------------
165 # Fixtures
166 # ---------------------------------------------------------------------------
167
168 @pytest.fixture()
169 def repo(tmp_path: pathlib.Path) -> pathlib.Path:
170 dot_muse = muse_dir(tmp_path)
171 (dot_muse / "commits").mkdir(parents=True)
172 (dot_muse / "snapshots").mkdir(parents=True)
173 (dot_muse / "refs" / "heads").mkdir(parents=True)
174 (dot_muse / "tags").mkdir(parents=True)
175 (dot_muse / "releases").mkdir(parents=True)
176 (dot_muse / "repo.json").write_text(json.dumps({"repo_id": _REPO_ID}))
177 (dot_muse / "HEAD").write_text("ref: refs/heads/main\n")
178 (dot_muse / "refs" / "heads" / "main").write_text("")
179 return tmp_path
180
181
182 # ===========================================================================
183 # 1. write_commit — idempotency
184 # ===========================================================================
185
186 class TestWriteCommitIdempotency:
187 def test_first_writer_wins(self, repo: pathlib.Path) -> None:
188 """A record with wrong incoming hash is rejected before it can overwrite anything.
189
190 The old "first writer wins via silent drop" path is superseded by incoming
191 hash verification: a record whose commit_id doesn't match its content hash
192 raises ValueError immediately — the good file on disk is never touched.
193 """
194 c1 = _make_commit(repo, message="first-wins")
195 # Construct a record with the same commit_id but different content —
196 # the hash won't match, so write_commit must raise before touching disk.
197 c2 = CommitRecord(
198 repo_id=_REPO_ID,
199 commit_id=c1.commit_id,
200 branch="main",
201 snapshot_id=c1.snapshot_id,
202 message="second-attempt",
203 committed_at=c1.committed_at,
204 author="tester",
205 parent_commit_id=None,
206 parent2_commit_id=None,
207 )
208 with pytest.raises(ValueError):
209 write_commit(repo, c2)
210 loaded = read_commit(repo, c1.commit_id)
211 assert loaded is not None
212 assert loaded.message == "first-wins", "bad incoming record must not overwrite good file"
213
214 def test_exact_duplicate_emits_no_critical(
215 self, repo: pathlib.Path, caplog: pytest.LogCaptureFixture
216 ) -> None:
217 """Writing the exact same record twice must not log CRITICAL."""
218 c = _make_commit(repo, message="exact-dup-no-critical")
219 with caplog.at_level(logging.DEBUG, logger="muse.core.store"):
220 write_commit(repo, c)
221 assert not any(r.levelno >= logging.CRITICAL for r in caplog.records)
222
223 def test_idempotent_round_trip_preserves_all_fields(self, repo: pathlib.Path) -> None:
224 c = _make_commit(repo, message="preserve-me", branch="feat/x")
225 write_commit(repo, c) # second write — must be completely harmless
226 loaded = read_commit(repo, c.commit_id)
227 assert loaded is not None
228 assert loaded.message == "preserve-me"
229 assert loaded.branch == "feat/x"
230
231
232 # ===========================================================================
233 # 2. write_commit — corrupt existing file → CRITICAL + overwrite
234 # ===========================================================================
235
236 class TestWriteCommitCorruptExistingFile:
237 def test_corrupt_existing_is_overwritten(
238 self, repo: pathlib.Path, caplog: pytest.LogCaptureFixture
239 ) -> None:
240 """Corrupt existing commit file is replaced with the incoming good record."""
241 c = _make_commit(repo, message="original-overwrite")
242 commit_path(repo, c.commit_id).write_bytes(b"\xff\xfe\x00bad-data\x99")
243 with caplog.at_level(logging.CRITICAL, logger="muse.core.store"):
244 write_commit(repo, c)
245 crits = [r for r in caplog.records if r.levelno >= logging.CRITICAL]
246 assert crits, "Must log CRITICAL when overwriting corrupt file"
247 loaded = read_commit(repo, c.commit_id)
248 assert loaded is not None
249 assert loaded.message == "original-overwrite"
250
251 def test_empty_existing_is_overwritten(
252 self, repo: pathlib.Path, caplog: pytest.LogCaptureFixture
253 ) -> None:
254 """Zero-byte commit file (crash during write) is replaced with good record."""
255 c = _make_commit(repo, message="after-crash-overwrite")
256 commit_path(repo, c.commit_id).write_bytes(b"")
257 with caplog.at_level(logging.CRITICAL, logger="muse.core.store"):
258 write_commit(repo, c)
259 loaded = read_commit(repo, c.commit_id)
260 assert loaded is not None
261 assert loaded.message == "after-crash-overwrite"
262
263 def test_truncated_msgpack_is_overwritten(
264 self, repo: pathlib.Path, caplog: pytest.LogCaptureFixture
265 ) -> None:
266 """Partially-written (truncated) msgpack file is replaced."""
267 c = _make_commit(repo, message="after-truncation-overwrite")
268 path = commit_path(repo, c.commit_id)
269 good_bytes = path.read_bytes()
270 path.write_bytes(good_bytes[: len(good_bytes) // 2])
271 with caplog.at_level(logging.CRITICAL, logger="muse.core.store"):
272 write_commit(repo, c)
273 loaded = read_commit(repo, c.commit_id)
274 assert loaded is not None
275 assert loaded.message == "after-truncation-overwrite"
276
277
278 # ===========================================================================
279 # 3. write_commit — store integrity violation
280 # ===========================================================================
281
282 class TestWriteCommitIntegrityViolation:
283 def test_commit_id_mismatch_raises_os_error(self, repo: pathlib.Path) -> None:
284 """Stored record's commit_id does not match filename → OSError."""
285 # Write a legitimate commit so the file exists on disk.
286 c_legit = _make_commit(repo, message="legitimate-mismatch")
287 # Build a different commit (different content → different commit_id).
288 c_impostor = _make_commit(repo, message="impostor-mismatch")
289 # Overwrite the legitimate file with the impostor's msgpack bytes —
290 # the file path says c_legit.commit_id but the bytes claim c_impostor.commit_id.
291 impostor_bytes = msgpack.packb(c_impostor.to_dict(), use_bin_type=True)
292 commit_path(repo, c_legit.commit_id).write_bytes(impostor_bytes)
293 # Now re-write the original c_legit — it passes incoming hash verification
294 # (its commit_id matches its content), but the file on disk now contains
295 # the impostor's bytes. write_commit must detect the mismatch and raise OSError.
296 with pytest.raises(OSError, match="Store integrity violation"):
297 write_commit(repo, c_legit)
298
299
300 # ===========================================================================
301 # 4. read_commit — CRITICAL log for corrupt
302 # ===========================================================================
303
304 class TestReadCommitCriticalLogging:
305 def test_corrupt_file_logs_critical(
306 self, repo: pathlib.Path, caplog: pytest.LogCaptureFixture
307 ) -> None:
308 c = _make_commit(repo, message="garbage-payload")
309 commit_path(repo, c.commit_id).write_bytes(b"\x00\x01garbage\xff")
310 with caplog.at_level(logging.CRITICAL, logger="muse.core.store"):
311 result = read_commit(repo, c.commit_id)
312 assert result is None
313 crits = [r for r in caplog.records if r.levelno >= logging.CRITICAL]
314 assert crits, "Must log CRITICAL for corrupt commit file"
315 assert any("Corrupt" in r.message for r in crits)
316
317 def test_missing_file_returns_none_no_log(
318 self, repo: pathlib.Path, caplog: pytest.LogCaptureFixture
319 ) -> None:
320 with caplog.at_level(logging.DEBUG, logger="muse.core.store"):
321 result = read_commit(repo, fake_id("missing-commit"))
322 assert result is None
323 assert not any(r.levelno >= logging.WARNING for r in caplog.records)
324
325 def test_valid_file_returns_record_no_critical(
326 self, repo: pathlib.Path, caplog: pytest.LogCaptureFixture
327 ) -> None:
328 c = _make_commit(repo, message="clean-read")
329 with caplog.at_level(logging.CRITICAL, logger="muse.core.store"):
330 result = read_commit(repo, c.commit_id)
331 assert result is not None
332 assert result.message == "clean-read"
333 assert not any(r.levelno >= logging.CRITICAL for r in caplog.records)
334
335 def test_corrupt_log_references_filename(
336 self, repo: pathlib.Path, caplog: pytest.LogCaptureFixture
337 ) -> None:
338 c = _make_commit(repo, message="not-msgpack-content")
339 commit_path(repo, c.commit_id).write_bytes(b"not-msgpack")
340 with caplog.at_level(logging.CRITICAL, logger="muse.core.store"):
341 read_commit(repo, c.commit_id)
342 messages = " ".join(r.message + str(r.args) for r in caplog.records)
343 bare = long_id(c.commit_id, strip=True)
344 assert bare[:8] in messages or bare in messages
345
346
347 # ===========================================================================
348 # 5. read_commit_result — discriminated union
349 # ===========================================================================
350
351 class TestReadCommitResult:
352 def test_ok_status_on_valid_record(self, repo: pathlib.Path) -> None:
353 c = _make_commit(repo, message="typed-ok")
354 r = read_commit_result(repo, c.commit_id)
355 assert commit_read_is_ok(r)
356 assert isinstance(r["commit"], CommitRecord)
357 assert r["commit"].message == "typed-ok"
358
359 def test_not_found_status_when_missing(self, repo: pathlib.Path) -> None:
360 r = read_commit_result(repo, "ff" * 32)
361 assert commit_read_is_not_found(r)
362
363 def test_corrupt_status_on_bad_bytes(
364 self, repo: pathlib.Path, caplog: pytest.LogCaptureFixture
365 ) -> None:
366 c = _make_commit(repo, message="corrupt-bytes")
367 commit_path(repo, c.commit_id).write_bytes(b"\xff\x00garbage")
368 with caplog.at_level(logging.CRITICAL, logger="muse.core.store"):
369 r = read_commit_result(repo, c.commit_id)
370 assert commit_read_is_corrupt(r)
371 assert r["path"] != ""
372 assert r["error"] != ""
373 crits = [rec for rec in caplog.records if rec.levelno >= logging.CRITICAL]
374 assert crits
375
376 def test_corrupt_result_path_contains_commit_id(self, repo: pathlib.Path) -> None:
377 c = _make_commit(repo, message="path-in-corrupt")
378 commit_path(repo, c.commit_id).write_bytes(b"")
379 r = read_commit_result(repo, c.commit_id)
380 assert commit_read_is_corrupt(r)
381 assert long_id(c.commit_id, strip=True) in r["path"]
382
383 def test_ok_result_roundtrips_all_metadata(self, repo: pathlib.Path) -> None:
384 # Build with a real content-addressed ID so _verify_commit_id passes.
385 snap_id = fake_id("snap-meta-roundtrip")
386 committed_at = datetime.datetime(2026, 3, 15, tzinfo=datetime.timezone.utc)
387 cid = compute_commit_id(
388 parent_ids=[],
389 snapshot_id=snap_id,
390 message="full metadata",
391 committed_at_iso=committed_at.isoformat(),
392 author="alice",
393 )
394 c = CommitRecord(
395 repo_id=_REPO_ID,
396 commit_id=cid,
397 branch="dev",
398 snapshot_id=snap_id,
399 message="full metadata",
400 committed_at=committed_at,
401 author="alice",
402 parent_commit_id=None,
403 parent2_commit_id=None,
404 metadata={"key": "val"},
405 )
406 write_commit(repo, c)
407 r = read_commit_result(repo, cid)
408 assert commit_read_is_ok(r)
409 assert r["commit"].branch == "dev"
410 assert r["commit"].author == "alice"
411 assert r["commit"].metadata == {"key": "val"}
412
413 def test_status_field_is_string(self, repo: pathlib.Path) -> None:
414 """Status values are plain strings — easy for agents to pattern-match."""
415 c = _make_commit(repo, message="status-str-check")
416 r = read_commit_result(repo, c.commit_id)
417 assert isinstance(r["status"], str)
418
419 def test_not_found_has_only_status_key(self, repo: pathlib.Path) -> None:
420 r = read_commit_result(repo, "90" * 32)
421 assert set(r.keys()) == {"status"}
422
423 def test_three_outcomes_are_mutually_exclusive(self, repo: pathlib.Path) -> None:
424 """Confirm all three outcome strings are distinct and unambiguous."""
425 c_ok = _make_commit(repo, message="outcome-ok")
426 c_corrupt = _make_commit(repo, message="outcome-corrupt")
427 commit_path(repo, c_corrupt.commit_id).write_bytes(b"bad")
428 statuses = {
429 read_commit_result(repo, c_ok.commit_id)["status"],
430 read_commit_result(repo, "cc" * 32)["status"],
431 read_commit_result(repo, c_corrupt.commit_id)["status"],
432 }
433 assert statuses == {"ok", "not_found", "corrupt"}
434
435
436 # ===========================================================================
437 # 6. read_snapshot / read_snapshot_result
438 # ===========================================================================
439
440 class TestReadSnapshotIntegrity:
441 def test_corrupt_snapshot_logs_critical(
442 self, repo: pathlib.Path, caplog: pytest.LogCaptureFixture
443 ) -> None:
444 s = _make_snapshot(repo, manifest={"snap-critical.py": fake_id("oid-a")})
445 snapshot_path(repo, s.snapshot_id).write_bytes(b"\xde\xad\xbe\xef")
446 with caplog.at_level(logging.CRITICAL, logger="muse.core.store"):
447 result = read_snapshot(repo, s.snapshot_id)
448 assert result is None
449 assert any(r.levelno >= logging.CRITICAL for r in caplog.records)
450
451 def test_snapshot_result_ok(self, repo: pathlib.Path) -> None:
452 s = _make_snapshot(repo, manifest={"snap-ok.py": fake_id("oid-b")})
453 r = read_snapshot_result(repo, s.snapshot_id)
454 assert snapshot_read_is_ok(r)
455 assert isinstance(r["snapshot"], SnapshotRecord)
456
457 def test_snapshot_result_not_found(self, repo: pathlib.Path) -> None:
458 r = read_snapshot_result(repo, fake_id("no-snap"))
459 assert r["status"] == "not_found"
460 assert set(r.keys()) == {"status"}
461
462 def test_snapshot_result_corrupt(
463 self, repo: pathlib.Path, caplog: pytest.LogCaptureFixture
464 ) -> None:
465 s = _make_snapshot(repo, manifest={"snap-corrupt.py": fake_id("oid-c")})
466 snapshot_path(repo, s.snapshot_id).write_bytes(b"garbage-bytes\x00")
467 with caplog.at_level(logging.CRITICAL, logger="muse.core.store"):
468 r = read_snapshot_result(repo, s.snapshot_id)
469 assert snapshot_read_is_corrupt(r)
470 assert r["path"] != ""
471 assert r["error"] != ""
472
473 def test_missing_snapshot_no_log(
474 self, repo: pathlib.Path, caplog: pytest.LogCaptureFixture
475 ) -> None:
476 with caplog.at_level(logging.DEBUG, logger="muse.core.store"):
477 result = read_snapshot(repo, fake_id("missing"))
478 assert result is None
479 assert not any(r.levelno >= logging.WARNING for r in caplog.records)
480
481
482 # ===========================================================================
483 # 7. get_all_commits — CRITICAL on corrupt (previously silent)
484 # ===========================================================================
485
486 class TestGetAllCommitsCorruptLogging:
487 def test_one_corrupt_skipped_with_critical(
488 self, repo: pathlib.Path, caplog: pytest.LogCaptureFixture
489 ) -> None:
490 """Corrupt commit is skipped; good commits returned; CRITICAL emitted."""
491 c_good = _make_commit(repo, message="good-survives")
492 c_bad = _make_commit(repo, message="will-corrupt")
493 commit_path(repo, c_bad.commit_id).write_bytes(b"\xff\x00")
494 with caplog.at_level(logging.CRITICAL, logger="muse.core.store"):
495 commits = get_all_commits(repo)
496 ids = {c.commit_id for c in commits}
497 assert c_good.commit_id in ids, "good commit must still appear"
498 assert c_bad.commit_id not in ids, "corrupt commit must be excluded"
499 assert any(r.levelno >= logging.CRITICAL for r in caplog.records)
500
501 def test_all_corrupt_returns_empty_with_critical(
502 self, repo: pathlib.Path, caplog: pytest.LogCaptureFixture
503 ) -> None:
504 written = [_make_commit(repo, message=f"c{i}") for i in range(3)]
505 for c in written:
506 commit_path(repo, c.commit_id).write_bytes(b"bad")
507 with caplog.at_level(logging.CRITICAL, logger="muse.core.store"):
508 commits = get_all_commits(repo)
509 assert commits == []
510 crits = [r for r in caplog.records if r.levelno >= logging.CRITICAL]
511 assert len(crits) == 3
512
513 def test_empty_store_returns_empty_no_log(
514 self, repo: pathlib.Path, caplog: pytest.LogCaptureFixture
515 ) -> None:
516 with caplog.at_level(logging.DEBUG, logger="muse.core.store"):
517 commits = get_all_commits(repo)
518 assert commits == []
519 assert not any(r.levelno >= logging.WARNING for r in caplog.records)
520
521 def test_mixed_good_and_corrupt_correct_count(self, repo: pathlib.Path) -> None:
522 good = [_make_commit(repo, message=f"g{i}") for i in range(5)]
523 bad = [_make_commit(repo, message=f"b{i}") for i in range(3)]
524 for c in bad:
525 commit_path(repo, c.commit_id).write_bytes(b"corrupt")
526 commits = get_all_commits(repo)
527 assert len(commits) == len(good)
528
529
530 # ===========================================================================
531 # 8. get_all_tags — CRITICAL on corrupt (previously silent)
532 # ===========================================================================
533
534 class TestGetAllTagsCorruptLogging:
535 def test_corrupt_tag_skipped_with_critical(
536 self, repo: pathlib.Path, caplog: pytest.LogCaptureFixture
537 ) -> None:
538 t1 = _make_tag(repo, "v1.0.0")
539 t2 = _make_tag(repo, "v2.0.0")
540 _tag_path(repo, t2.tag_id).write_bytes(b"\x00bad")
541 with caplog.at_level(logging.CRITICAL, logger="muse.core.store"):
542 tags = get_all_tags(repo, _REPO_ID)
543 tag_values = {t.tag for t in tags}
544 assert "v1.0.0" in tag_values
545 assert "v2.0.0" not in tag_values
546 assert any(r.levelno >= logging.CRITICAL for r in caplog.records)
547
548 def test_good_tags_all_returned(self, repo: pathlib.Path) -> None:
549 _make_tag(repo, "v0.1")
550 _make_tag(repo, "v0.2")
551 tags = get_all_tags(repo, _REPO_ID)
552 assert len(tags) == 2
553
554 def test_all_corrupt_tags_returns_empty_with_critical(
555 self, repo: pathlib.Path, caplog: pytest.LogCaptureFixture
556 ) -> None:
557 for name in ("v1", "v2", "v3"):
558 t = _make_tag(repo, name)
559 _tag_path(repo, t.tag_id).write_bytes(b"bad")
560 with caplog.at_level(logging.CRITICAL, logger="muse.core.store"):
561 tags = get_all_tags(repo, _REPO_ID)
562 assert tags == []
563 crits = [r for r in caplog.records if r.levelno >= logging.CRITICAL]
564 assert len(crits) == 3
565
566
567 # ===========================================================================
568 # 9. list_releases — CRITICAL on corrupt (previously silent)
569 # ===========================================================================
570
571 class TestListReleasesCorruptLogging:
572 def test_corrupt_release_skipped_with_critical(
573 self, repo: pathlib.Path, caplog: pytest.LogCaptureFixture
574 ) -> None:
575 good = _make_release(
576 repo, "v1.0.0", SemVerTag(major=1, minor=0, patch=0, pre="", build="")
577 )
578 bad = _make_release(
579 repo, "v2.0.0", SemVerTag(major=2, minor=0, patch=0, pre="", build="")
580 )
581 _release_path(repo, _REPO_ID, bad.release_id).write_bytes(b"\xff\x00garbage")
582 with caplog.at_level(logging.CRITICAL, logger="muse.core.store"):
583 releases = list_releases(repo, _REPO_ID)
584 ids = {r.release_id for r in releases}
585 assert good.release_id in ids
586 assert bad.release_id not in ids
587 assert any(r.levelno >= logging.CRITICAL for r in caplog.records)
588
589 def test_all_releases_good_returns_all(self, repo: pathlib.Path) -> None:
590 _make_release(repo, "v1.0.0", SemVerTag(major=1, minor=0, patch=0, pre="", build=""))
591 _make_release(repo, "v1.1.0", SemVerTag(major=1, minor=1, patch=0, pre="", build=""))
592 releases = list_releases(repo, _REPO_ID)
593 assert len(releases) == 2
594
595
596 # ===========================================================================
597 # 10. verify-pack integration after write_commit
598 # ===========================================================================
599
600 class TestVerifyPackAfterWriteCommit:
601 def test_cmd_read_commit_roundtrip(self, repo: pathlib.Path) -> None:
602 """``muse read-commit`` must succeed for every written commit."""
603 from tests.cli_test_helper import CliRunner
604
605 c = _make_commit(repo, message="plumbing-check")
606
607 runner = CliRunner()
608 result = runner.invoke(
609 None,
610 ["read-commit", c.commit_id, "--json"],
611 env={"MUSE_REPO_ROOT": str(repo)},
612 )
613 assert result.exit_code == 0
614 import json as _json
615 data = _json.loads(result.output)
616 assert data["commit_id"] == c.commit_id
617 assert data["message"] == "plumbing-check"
618
619
620 # ===========================================================================
621 # 11. Concurrent idempotency — 50 threads race to write the same commit
622 # ===========================================================================
623
624 class TestConcurrentIdempotentWrite:
625 def test_50_threads_same_commit_id_first_wins(self, repo: pathlib.Path) -> None:
626 """50 threads writing the EXACT same commit — idempotent, exactly one file written."""
627 # In a content-addressed system, identical content → identical commit_id.
628 c = _make_commit(repo, message="concurrent-idempotent", write=False)
629 errors: list[Exception] = []
630
631 def write_one() -> None:
632 try:
633 write_commit(repo, c)
634 except Exception as exc:
635 errors.append(exc)
636
637 threads = [threading.Thread(target=write_one) for _ in range(50)]
638 for t in threads:
639 t.start()
640 for t in threads:
641 t.join()
642
643 assert not errors, f"Unexpected errors in same-ID concurrent writes: {errors[:3]}"
644
645 loaded = read_commit(repo, c.commit_id)
646 assert loaded is not None
647 assert loaded.commit_id == c.commit_id
648 assert loaded.message == "concurrent-idempotent"
649
650 def test_50_threads_distinct_ids_all_survive(self, repo: pathlib.Path) -> None:
651 """50 threads writing distinct commit IDs must all persist without errors."""
652 errors: list[Exception] = []
653
654 def write_unique(i: int) -> None:
655 # _make_commit uses compute_commit_id so the hash always matches content.
656 c = _make_commit(repo, message=f"unique {i}", write=False)
657 try:
658 write_commit(repo, c)
659 except Exception as exc:
660 errors.append(exc)
661
662 threads = [threading.Thread(target=write_unique, args=(i,)) for i in range(50)]
663 for t in threads:
664 t.start()
665 for t in threads:
666 t.join()
667
668 assert not errors, f"Unexpected errors in distinct-ID concurrent writes: {errors[:3]}"
669 commits = get_all_commits(repo)
670 assert len(commits) == 50
671
672
673 # ===========================================================================
674 # 12. Regression: WARNING→CRITICAL upgrade is permanent
675 # ===========================================================================
676
677 class TestRegressionCorruptLevelUpgrade:
678 """Confirm the upgrade from WARNING to CRITICAL is permanent and precise."""
679
680 def test_corrupt_commit_logs_at_critical_not_warning(
681 self, repo: pathlib.Path, caplog: pytest.LogCaptureFixture
682 ) -> None:
683 c = _make_commit(repo, message="level-upgrade-commit")
684 commit_path(repo, c.commit_id).write_bytes(b"trash")
685 with caplog.at_level(logging.DEBUG, logger="muse.core.store"):
686 read_commit(repo, c.commit_id)
687 levels = [r.levelno for r in caplog.records]
688 assert any(lvl == logging.CRITICAL for lvl in levels), (
689 f"Expected CRITICAL (50) but got levels: {levels}"
690 )
691 assert not any(lvl == logging.WARNING for lvl in levels), (
692 "Must not downgrade corruption to WARNING — only CRITICAL is acceptable"
693 )
694
695 def test_corrupt_snapshot_logs_at_critical_not_warning(
696 self, repo: pathlib.Path, caplog: pytest.LogCaptureFixture
697 ) -> None:
698 s = _make_snapshot(repo, manifest={"snap-level.py": fake_id("oid-d")})
699 snapshot_path(repo, s.snapshot_id).write_bytes(b"bad")
700 with caplog.at_level(logging.DEBUG, logger="muse.core.store"):
701 read_snapshot(repo, s.snapshot_id)
702 levels = [r.levelno for r in caplog.records]
703 assert any(lvl == logging.CRITICAL for lvl in levels)
704 assert not any(lvl == logging.WARNING for lvl in levels)
705
706 def test_get_all_commits_logs_corrupt_at_critical(
707 self, repo: pathlib.Path, caplog: pytest.LogCaptureFixture
708 ) -> None:
709 c = _make_commit(repo, message="level-upgrade-get-all")
710 commit_path(repo, c.commit_id).write_bytes(b"trash")
711 with caplog.at_level(logging.DEBUG, logger="muse.core.store"):
712 get_all_commits(repo)
713 levels = [r.levelno for r in caplog.records]
714 assert any(lvl == logging.CRITICAL for lvl in levels)
715 assert not any(lvl == logging.WARNING for lvl in levels)
716
717 def test_get_all_tags_logs_corrupt_at_critical(
718 self, repo: pathlib.Path, caplog: pytest.LogCaptureFixture
719 ) -> None:
720 t = _make_tag(repo, "v-crit")
721 _tag_path(repo, t.tag_id).write_bytes(b"trash")
722 with caplog.at_level(logging.DEBUG, logger="muse.core.store"):
723 get_all_tags(repo, _REPO_ID)
724 levels = [r.levelno for r in caplog.records]
725 assert any(lvl == logging.CRITICAL for lvl in levels)
726 assert not any(lvl == logging.WARNING for lvl in levels)
File History 1 commit
sha256:51ce277f663e01a43eaffbe77509b1de7ac2d4251b55d23306304bcdeb92c90d feat(pack): delta-encode snapshots in MPackBundle wire format Sonnet 4.6 minor 121 days ago