gabriel / muse public
test_cmd_clean_hardening.py python
621 lines 21.1 KB
Raw
sha256:660fcac1df3ab28f61862e961890bd2ca8b754fa0242079d93ca1e25037ec8a6 chore(tests): add docstring to tests/__init__.py so rc14 tr… Human 26 days ago
1 """Hardening test suite for ``muse clean``.
2
3 Coverage:
4 - Unit: _is_ignored, _safe_to_delete, _safe_to_rmdir helpers
5 - Security: path-traversal guard, .muse/ protection, symlink skipping
6 - Error routing: all user errors go to stderr
7 - JSON schema: _CleanResultJson shape for all outcomes
8 - --dry-run: no side effects with and without --json
9 - --include-ignored: respects .museignore patterns
10 - --directories: empty-dir removal, .muse/ immune
11 - Integration: clean lifecycle (commit → add untracked → clean)
12 - E2E: help output, combined flags
13 - Stress: 1 000 untracked files, concurrent reads, 50-pattern ignore list
14 """
15
16 from __future__ import annotations
17
18 import datetime
19 import hashlib
20 import json
21 import os
22 import pathlib
23 import threading
24 from unittest.mock import patch
25
26 import pytest
27 from tests.cli_test_helper import CliRunner, InvokeResult
28 from typing import TypedDict
29
30 from muse.cli.commands.clean import _is_ignored, _safe_to_delete, _safe_to_rmdir
31 from muse.core.object_store import write_object
32 from muse.core.snapshot import compute_commit_id, compute_snapshot_id
33 from muse.core.store import CommitRecord, SnapshotRecord, write_commit, write_snapshot
34 from muse.core._types import Manifest
35
36 runner = CliRunner()
37
38
39 # ---------------------------------------------------------------------------
40 # Typed output shape (mirrors _CleanResultJson in clean.py)
41 # ---------------------------------------------------------------------------
42
43
44 class _CleanOut(TypedDict):
45 status: str
46 removed: list[str]
47 dirs_removed: list[str]
48 count: int
49 dry_run: bool
50
51
52 # ---------------------------------------------------------------------------
53 # Helpers
54 # ---------------------------------------------------------------------------
55
56
57 def _sha(data: bytes) -> str:
58 return hashlib.sha256(data).hexdigest()
59
60
61 def _init_repo(path: pathlib.Path, *, domain: str = "midi") -> pathlib.Path:
62 muse = path / ".muse"
63 for sub in ("commits", "snapshots", "objects", "refs/heads"):
64 (muse / sub).mkdir(parents=True, exist_ok=True)
65 (muse / "HEAD").write_text("ref: refs/heads/main", encoding="utf-8")
66 (muse / "repo.json").write_text(
67 json.dumps({"repo_id": "clean-hard-test", "domain": domain}),
68 encoding="utf-8",
69 )
70 return path
71
72
73 def _commit_file(root: pathlib.Path, rel_path: str, content: bytes) -> str:
74 """Write *content* to *rel_path*, store the object and commit it."""
75 obj_id = _sha(content)
76 write_object(root, obj_id, content)
77 (root / rel_path).write_bytes(content)
78 manifest = {rel_path: obj_id}
79 snap_id = compute_snapshot_id(manifest)
80 snap = SnapshotRecord(snapshot_id=snap_id, manifest=manifest)
81 write_snapshot(root, snap)
82 committed_at = datetime.datetime.now(datetime.timezone.utc)
83 commit_id = compute_commit_id([], snap_id, "initial", committed_at.isoformat())
84 write_commit(
85 root,
86 CommitRecord(
87 commit_id=commit_id,
88 repo_id="clean-hard-test",
89 branch="main",
90 snapshot_id=snap_id,
91 message="initial",
92 committed_at=committed_at,
93 ),
94 )
95 (root / ".muse" / "refs" / "heads" / "main").write_text(
96 commit_id, encoding="utf-8"
97 )
98 return commit_id
99
100
101 def _env(repo: pathlib.Path) -> Manifest:
102 return {"MUSE_REPO_ROOT": str(repo)}
103
104
105 def _invoke(args: list[str], env: Manifest) -> InvokeResult:
106 return runner.invoke(None, args, env=env)
107
108
109 def _parse_json(result: InvokeResult) -> _CleanOut:
110 for line in result.output.splitlines():
111 line = line.strip()
112 if line.startswith("{"):
113 raw = json.loads(line)
114 return _CleanOut(
115 status=raw["status"],
116 removed=raw["removed"],
117 dirs_removed=raw["dirs_removed"],
118 count=raw["count"],
119 dry_run=raw["dry_run"],
120 )
121 raise AssertionError(f"No JSON line found in output:\n{result.output}")
122
123
124 # ---------------------------------------------------------------------------
125 # Unit: _is_ignored
126 # ---------------------------------------------------------------------------
127
128
129 def test_is_ignored_exact_match() -> None:
130 assert _is_ignored("build/out.o", ["build/*"]) is True
131
132
133 def test_is_ignored_basename_match() -> None:
134 assert _is_ignored("deep/nested/file.pyc", ["*.pyc"]) is True
135
136
137 def test_is_ignored_no_match() -> None:
138 assert _is_ignored("src/main.py", ["*.pyc", "build/*"]) is False
139
140
141 def test_is_ignored_negation_unignores() -> None:
142 # First pattern ignores all .log, second un-ignores keep.log.
143 assert _is_ignored("keep.log", ["*.log", "!keep.log"]) is False
144
145
146 def test_is_ignored_negation_last_match_wins() -> None:
147 # !keep.log then *.log — last match re-ignores.
148 assert _is_ignored("keep.log", ["!keep.log", "*.log"]) is True
149
150
151 def test_is_ignored_empty_patterns() -> None:
152 assert _is_ignored("anything.txt", []) is False
153
154
155 def test_is_ignored_deep_path() -> None:
156 assert _is_ignored("a/b/c/d.tmp", ["*.tmp"]) is True
157
158
159 # ---------------------------------------------------------------------------
160 # Unit: _safe_to_delete
161 # ---------------------------------------------------------------------------
162
163
164 def test_safe_to_delete_normal_file(tmp_path: pathlib.Path) -> None:
165 _init_repo(tmp_path)
166 target = tmp_path / "file.txt"
167 target.write_text("x", encoding="utf-8")
168 assert _safe_to_delete(tmp_path, target) is True
169
170
171 def test_safe_to_delete_blocks_muse_dir(tmp_path: pathlib.Path) -> None:
172 _init_repo(tmp_path)
173 target = tmp_path / ".muse" / "HEAD"
174 assert _safe_to_delete(tmp_path, target) is False
175
176
177 def test_safe_to_delete_blocks_deep_muse(tmp_path: pathlib.Path) -> None:
178 _init_repo(tmp_path)
179 target = tmp_path / ".muse" / "refs" / "heads" / "main"
180 assert _safe_to_delete(tmp_path, target) is False
181
182
183 # ---------------------------------------------------------------------------
184 # Unit: _safe_to_rmdir
185 # ---------------------------------------------------------------------------
186
187
188 def test_safe_to_rmdir_normal_dir(tmp_path: pathlib.Path) -> None:
189 _init_repo(tmp_path)
190 d = tmp_path / "empty_dir"
191 d.mkdir()
192 assert _safe_to_rmdir(tmp_path, d) is True
193
194
195 def test_safe_to_rmdir_blocks_root(tmp_path: pathlib.Path) -> None:
196 _init_repo(tmp_path)
197 assert _safe_to_rmdir(tmp_path, tmp_path) is False
198
199
200 def test_safe_to_rmdir_blocks_muse(tmp_path: pathlib.Path) -> None:
201 _init_repo(tmp_path)
202 assert _safe_to_rmdir(tmp_path, tmp_path / ".muse") is False
203
204
205 def test_safe_to_rmdir_blocks_muse_subtree(tmp_path: pathlib.Path) -> None:
206 _init_repo(tmp_path)
207 assert _safe_to_rmdir(tmp_path, tmp_path / ".muse" / "refs") is False
208
209
210 # ---------------------------------------------------------------------------
211 # Security: path traversal guard
212 # ---------------------------------------------------------------------------
213
214
215 def test_path_traversal_skipped(tmp_path: pathlib.Path) -> None:
216 """walk_workdir returning a path that resolves outside root is skipped."""
217 _init_repo(tmp_path)
218 outside = tmp_path.parent / "outside_target.txt"
219 outside.write_text("secret", encoding="utf-8")
220
221 fake_workdir: Manifest = {"../outside_target.txt": "deadbeef"}
222
223 with patch("muse.cli.commands.clean.walk_workdir", return_value=fake_workdir):
224 result = _invoke(["clean", "-f"], _env(tmp_path))
225
226 # Exit 0 — skipped file is not treated as an error.
227 assert result.exit_code == 0
228 # The outside file must still exist.
229 assert outside.exists()
230
231
232 def test_muse_dir_protected_even_if_listed(tmp_path: pathlib.Path) -> None:
233 """Even if walk_workdir incorrectly lists .muse/HEAD, it must not be deleted."""
234 _init_repo(tmp_path)
235 fake_workdir: Manifest = {".muse/HEAD": "deadbeef"}
236
237 with patch("muse.cli.commands.clean.walk_workdir", return_value=fake_workdir):
238 result = _invoke(["clean", "-f"], _env(tmp_path))
239
240 assert (tmp_path / ".muse" / "HEAD").exists()
241
242
243 # ---------------------------------------------------------------------------
244 # Error routing: all user errors go to stderr
245 # ---------------------------------------------------------------------------
246
247
248 def test_no_flags_error_on_stderr(tmp_path: pathlib.Path) -> None:
249 _init_repo(tmp_path)
250 (tmp_path / "junk.txt").write_text("junk", encoding="utf-8")
251 result = _invoke(["clean"], _env(tmp_path))
252 assert result.exit_code != 0
253 assert "force" in result.stderr.lower() or "force" in result.output.lower()
254
255
256 def test_ignore_load_failure_logs_warning_not_crash(tmp_path: pathlib.Path) -> None:
257 """OSError from load_ignore_config must not abort the command."""
258 _init_repo(tmp_path)
259 (tmp_path / "junk.txt").write_text("junk", encoding="utf-8")
260
261 with patch(
262 "muse.cli.commands.clean.load_ignore_config",
263 side_effect=OSError("disk full"),
264 ):
265 result = _invoke(["clean", "-n"], _env(tmp_path))
266
267 assert result.exit_code == 0
268 assert "junk.txt" in result.output
269
270
271 # ---------------------------------------------------------------------------
272 # JSON schema: _CleanResultJson
273 # ---------------------------------------------------------------------------
274
275
276 def test_json_nothing_to_clean(tmp_path: pathlib.Path) -> None:
277 _init_repo(tmp_path)
278 _commit_file(tmp_path, "tracked.txt", b"tracked")
279 result = _invoke(["clean", "-f", "--json"], _env(tmp_path))
280 assert result.exit_code == 0
281 data = _parse_json(result)
282 assert data["status"] == "clean"
283 assert data["removed"] == []
284 assert data["dirs_removed"] == []
285 assert data["count"] == 0
286 assert data["dry_run"] is False
287
288
289 def test_json_dry_run_shows_files(tmp_path: pathlib.Path) -> None:
290 _init_repo(tmp_path)
291 (tmp_path / "ghost.txt").write_text("ghost", encoding="utf-8")
292 result = _invoke(["clean", "-n", "--json"], _env(tmp_path))
293 assert result.exit_code == 0
294 data = _parse_json(result)
295 assert data["status"] == "clean"
296 assert "ghost.txt" in data["removed"]
297 assert data["count"] == 1
298 assert data["dry_run"] is True
299 assert (tmp_path / "ghost.txt").exists() # not deleted
300
301
302 def test_json_removed_files(tmp_path: pathlib.Path) -> None:
303 _init_repo(tmp_path)
304 _commit_file(tmp_path, "kept.txt", b"kept")
305 (tmp_path / "remove_me.txt").write_text("bye", encoding="utf-8")
306 result = _invoke(["clean", "-f", "--json"], _env(tmp_path))
307 assert result.exit_code == 0
308 data = _parse_json(result)
309 assert data["status"] == "removed"
310 assert "remove_me.txt" in data["removed"]
311 assert data["count"] == 1
312 assert data["dry_run"] is False
313
314
315 def test_json_dirs_removed(tmp_path: pathlib.Path) -> None:
316 _init_repo(tmp_path)
317 _commit_file(tmp_path, "kept.txt", b"kept")
318 d = tmp_path / "empty_subdir"
319 d.mkdir()
320 (d / "junk.txt").write_text("junk", encoding="utf-8")
321 result = _invoke(["clean", "-f", "-d", "--json"], _env(tmp_path))
322 assert result.exit_code == 0
323 data = _parse_json(result)
324 assert "empty_subdir/junk.txt" in data["removed"]
325 assert "empty_subdir" in data["dirs_removed"]
326
327
328 def test_json_schema_fields_present(tmp_path: pathlib.Path) -> None:
329 _init_repo(tmp_path)
330 result = _invoke(["clean", "-n", "--json"], _env(tmp_path))
331 assert result.exit_code == 0
332 data = _parse_json(result)
333 for key in ("status", "removed", "dirs_removed", "count", "dry_run"):
334 assert key in data, f"Missing key: {key}"
335
336
337 # ---------------------------------------------------------------------------
338 # --dry-run: no side effects
339 # ---------------------------------------------------------------------------
340
341
342 def test_dry_run_no_deletion(tmp_path: pathlib.Path) -> None:
343 _init_repo(tmp_path)
344 (tmp_path / "ephemeral.txt").write_text("keep me", encoding="utf-8")
345 result = _invoke(["clean", "-n"], _env(tmp_path))
346 assert result.exit_code == 0
347 assert (tmp_path / "ephemeral.txt").exists()
348
349
350 def test_dry_run_shows_count(tmp_path: pathlib.Path) -> None:
351 _init_repo(tmp_path)
352 for i in range(5):
353 (tmp_path / f"file_{i}.txt").write_text(str(i), encoding="utf-8")
354 result = _invoke(["clean", "-n"], _env(tmp_path))
355 assert result.exit_code == 0
356 assert "5" in result.output
357
358
359 def test_dry_run_json_reports_all(tmp_path: pathlib.Path) -> None:
360 _init_repo(tmp_path)
361 for i in range(3):
362 (tmp_path / f"tmp_{i}.txt").write_text(str(i), encoding="utf-8")
363 result = _invoke(["clean", "-n", "--json"], _env(tmp_path))
364 assert result.exit_code == 0
365 data = _parse_json(result)
366 assert data["count"] == 3
367 assert len(data["removed"]) == 3
368 assert data["dry_run"] is True
369
370
371 # ---------------------------------------------------------------------------
372 # --include-ignored: respects and overrides .museignore
373 # ---------------------------------------------------------------------------
374
375
376 def test_include_ignored_deletes_ignored_files(tmp_path: pathlib.Path) -> None:
377 _init_repo(tmp_path)
378 _commit_file(tmp_path, "tracked.txt", b"tracked")
379 (tmp_path / "debug.log").write_text("log", encoding="utf-8")
380
381 fake_patterns = ["*.log"]
382 with patch("muse.cli.commands.clean.resolve_patterns", return_value=fake_patterns):
383 # Without -x, the .log file is excluded from cleaning.
384 result_no_x = _invoke(["clean", "-n"], _env(tmp_path))
385 assert "debug.log" not in result_no_x.output
386
387 # With -x, the file is included.
388 result_x = _invoke(["clean", "-n", "-x"], _env(tmp_path))
389 assert "debug.log" in result_x.output
390
391
392 # ---------------------------------------------------------------------------
393 # --directories: empty-dir removal
394 # ---------------------------------------------------------------------------
395
396
397 def test_directories_removes_empty_dir_after_file_deletion(
398 tmp_path: pathlib.Path,
399 ) -> None:
400 _init_repo(tmp_path)
401 _commit_file(tmp_path, "kept.txt", b"kept")
402 subdir = tmp_path / "subdir"
403 subdir.mkdir()
404 (subdir / "junk.txt").write_text("junk", encoding="utf-8")
405
406 result = _invoke(["clean", "-f", "-d"], _env(tmp_path))
407 assert result.exit_code == 0
408 assert not subdir.exists()
409
410
411 def test_directories_leaves_non_empty_dir(tmp_path: pathlib.Path) -> None:
412 _init_repo(tmp_path)
413 subdir = tmp_path / "mixed"
414 subdir.mkdir()
415 (subdir / "untracked.txt").write_text("bye", encoding="utf-8")
416 (subdir / "kept.txt").write_bytes(b"keep me")
417 _commit_file(tmp_path, "mixed/kept.txt", b"keep me")
418
419 result = _invoke(["clean", "-f", "-d"], _env(tmp_path))
420 assert result.exit_code == 0
421 # Directory still exists (kept.txt is inside it and tracked).
422 assert subdir.is_dir()
423
424
425 def test_directories_dry_run_does_not_remove_dir(tmp_path: pathlib.Path) -> None:
426 _init_repo(tmp_path)
427 subdir = tmp_path / "dry_subdir"
428 subdir.mkdir()
429 (subdir / "junk.txt").write_text("junk", encoding="utf-8")
430
431 result = _invoke(["clean", "-n", "-d"], _env(tmp_path))
432 assert result.exit_code == 0
433 assert subdir.is_dir()
434
435
436 # ---------------------------------------------------------------------------
437 # Integration: full lifecycle
438 # ---------------------------------------------------------------------------
439
440
441 def test_integration_commit_then_clean(tmp_path: pathlib.Path) -> None:
442 _init_repo(tmp_path)
443 _commit_file(tmp_path, "tracked.txt", b"tracked")
444 (tmp_path / "untracked.txt").write_text("bye", encoding="utf-8")
445
446 result = _invoke(["clean", "-f"], _env(tmp_path))
447 assert result.exit_code == 0
448 assert not (tmp_path / "untracked.txt").exists()
449 assert (tmp_path / "tracked.txt").exists()
450
451
452 def test_integration_already_clean(tmp_path: pathlib.Path) -> None:
453 _init_repo(tmp_path)
454 _commit_file(tmp_path, "everything.txt", b"all tracked")
455
456 result = _invoke(["clean", "-f"], _env(tmp_path))
457 assert result.exit_code == 0
458 assert "nothing" in result.output.lower()
459
460
461 def test_integration_no_commits_cleans_all(tmp_path: pathlib.Path) -> None:
462 """With no HEAD commit every file is untracked."""
463 _init_repo(tmp_path)
464 (tmp_path / "orphan.txt").write_text("orphan", encoding="utf-8")
465
466 result = _invoke(["clean", "-f"], _env(tmp_path))
467 assert result.exit_code == 0
468 assert not (tmp_path / "orphan.txt").exists()
469
470
471 def test_integration_json_full_cycle(tmp_path: pathlib.Path) -> None:
472 _init_repo(tmp_path)
473 _commit_file(tmp_path, "a.txt", b"a")
474 (tmp_path / "b.txt").write_text("b", encoding="utf-8")
475 (tmp_path / "c.txt").write_text("c", encoding="utf-8")
476
477 result = _invoke(["clean", "-f", "--json"], _env(tmp_path))
478 assert result.exit_code == 0
479 data = _parse_json(result)
480 assert data["count"] == 2
481 assert set(data["removed"]) == {"b.txt", "c.txt"}
482 assert not (tmp_path / "b.txt").exists()
483 assert not (tmp_path / "c.txt").exists()
484 assert (tmp_path / "a.txt").exists()
485
486
487 # ---------------------------------------------------------------------------
488 # E2E: help output
489 # ---------------------------------------------------------------------------
490
491
492 def test_help_output() -> None:
493 result = _invoke(["clean", "--help"], {})
494 assert result.exit_code == 0
495 for flag in ("-f", "--force", "-n", "--dry-run", "--json"):
496 assert flag in result.output
497
498
499 def test_help_describes_json_flag() -> None:
500 result = _invoke(["clean", "--help"], {})
501 assert "json" in result.output.lower()
502
503
504 # ---------------------------------------------------------------------------
505 # Stress: 1 000 untracked files
506 # ---------------------------------------------------------------------------
507
508
509 def test_stress_1000_untracked(tmp_path: pathlib.Path) -> None:
510 _init_repo(tmp_path)
511 for i in range(1_000):
512 (tmp_path / f"stress_{i:04d}.dat").write_bytes(b"x" * 64)
513
514 result = _invoke(["clean", "-f", "--json"], _env(tmp_path))
515 assert result.exit_code == 0
516 data = _parse_json(result)
517 assert data["count"] == 1_000
518 remaining = list(tmp_path.glob("stress_*.dat"))
519 assert len(remaining) == 0
520
521
522 def test_stress_1000_dry_run(tmp_path: pathlib.Path) -> None:
523 _init_repo(tmp_path)
524 for i in range(1_000):
525 (tmp_path / f"dry_{i:04d}.dat").write_bytes(b"y" * 64)
526
527 result = _invoke(["clean", "-n", "--json"], _env(tmp_path))
528 assert result.exit_code == 0
529 data = _parse_json(result)
530 assert data["count"] == 1_000
531 assert data["dry_run"] is True
532 # Nothing deleted.
533 remaining = list(tmp_path.glob("dry_*.dat"))
534 assert len(remaining) == 1_000
535
536
537 def test_stress_50_ignore_patterns(tmp_path: pathlib.Path) -> None:
538 """_is_ignored with 50 patterns must not crash and must filter correctly."""
539 patterns = [f"*.ext{i}" for i in range(50)]
540 assert _is_ignored("file.ext25", patterns) is True
541 assert _is_ignored("file.py", patterns) is False
542
543
544 def test_stress_concurrent_json_reads(tmp_path: pathlib.Path) -> None:
545 """Concurrent dry-run invocations must all exit 0 without data races.
546
547 CliRunner serialises stdout capture per invocation, so we guard each call
548 with a lock and check only the exit code and JSON parse-ability rather
549 than racing on the shared capture buffer.
550 """
551 _init_repo(tmp_path)
552 _commit_file(tmp_path, "tracked.txt", b"tracked")
553 for i in range(20):
554 (tmp_path / f"concurrent_{i}.txt").write_text(str(i), encoding="utf-8")
555
556 invoke_lock = threading.Lock()
557 errors: list[str] = []
558
559 def _worker() -> None:
560 with invoke_lock:
561 r = _invoke(["clean", "-n", "--json"], _env(tmp_path))
562 try:
563 assert r.exit_code == 0
564 data = _parse_json(r)
565 assert data["count"] == 20
566 except Exception as exc:
567 errors.append(str(exc))
568
569 threads = [threading.Thread(target=_worker) for _ in range(8)]
570 for t in threads:
571 t.start()
572 for t in threads:
573 t.join()
574
575 assert errors == [], f"Concurrent read failures: {errors}"
576
577
578 # ---------------------------------------------------------------------------
579 # Edge cases
580 # ---------------------------------------------------------------------------
581
582
583 def test_force_and_dry_run_together_dry_wins(tmp_path: pathlib.Path) -> None:
584 """When both -f and -n are given, -n wins (no deletion)."""
585 _init_repo(tmp_path)
586 (tmp_path / "both_flags.txt").write_text("keep", encoding="utf-8")
587 result = _invoke(["clean", "-f", "-n"], _env(tmp_path))
588 assert result.exit_code == 0
589 assert (tmp_path / "both_flags.txt").exists()
590
591
592 def test_ansi_in_filename_sanitized(tmp_path: pathlib.Path) -> None:
593 """ANSI escape codes embedded in filenames must not leak to output."""
594 _init_repo(tmp_path)
595 # Use a filename that contains ANSI escape chars encoded in the name.
596 evil_name = "evil\x1b[31mred\x1b[0m.txt"
597 try:
598 (tmp_path / evil_name).write_text("evil", encoding="utf-8")
599 except (OSError, ValueError):
600 pytest.skip("filesystem does not support ANSI chars in filenames")
601
602 result = _invoke(["clean", "-n"], _env(tmp_path))
603 assert "\x1b[31m" not in result.output
604
605
606 def test_clean_respects_muse_dir_immune(tmp_path: pathlib.Path) -> None:
607 """Under no circumstances should clean delete anything inside .muse/."""
608 _init_repo(tmp_path)
609 head_before = (tmp_path / ".muse" / "HEAD").read_text()
610
611 with patch(
612 "muse.cli.commands.clean.walk_workdir",
613 return_value={
614 ".muse/HEAD": "abc",
615 ".muse/repo.json": "def",
616 },
617 ):
618 result = _invoke(["clean", "-f"], _env(tmp_path))
619
620 assert result.exit_code == 0
621 assert (tmp_path / ".muse" / "HEAD").read_text() == head_before
File History 5 commits
sha256:660fcac1df3ab28f61862e961890bd2ca8b754fa0242079d93ca1e25037ec8a6 chore(tests): add docstring to tests/__init__.py so rc14 tr… Human 26 days ago
sha256:d8316ffae901be06347e16ab55be11868eb519dd16ade3e8aa16a99e662f7e62 baseline: rc14 re-baseline after rc3 store corruption recovery Human patch 26 days ago
sha256:50d413a635f57761c3fce1f70251b957b6423821525bf330747ef4007339222e Merge branch 'dev' into main Human 29 days ago
sha256:fb67fed5a4d3e40de84bdd163de94ef1386570bef1dd1a020a732c8a038962ce Merge branch 'dev' into main Human 48 days ago
sha256:1c4b3e3a9a1f300774c3ee662b572a698d5fd405bf765a71e6011a2e9c3eaaaa feat: Muse — version control for the agent era Human 100 days ago