Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions cron/evolution_preflight.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,13 @@ def _preflight_enabled(cfg: Optional[Any] = None) -> bool:
def find_latest_digest(
stage: str, hermes_home: Optional[Path] = None
) -> Optional[Path]:
"""Return the most recent digest file for an evolution stage, or None."""
"""Return the most recent digest file for an evolution stage, or None.

Digest filenames follow a sortable date-encoded convention
(``YYYY-MM-DD.json`` / ``.md``, optionally with ``-pass<N>`` / ``-tick<N>``
suffixes). Sorting by filename instead of ``st_mtime`` avoids flaky
results when files are touched or copied after creation (#1767).
"""
if stage not in _EVOLUTION_STAGES:
return None
ext = _EVOLUTION_STAGES[stage]
Expand All @@ -183,7 +189,7 @@ def find_latest_digest(
return None
candidates = sorted(
(p for p in stage_dir.iterdir() if p.is_file() and p.suffix == ext),
key=lambda p: p.stat().st_mtime,
key=lambda p: p.name,
reverse=True,
)
return candidates[0] if candidates else None
Expand Down
20 changes: 20 additions & 0 deletions tests/cron/test_evolution_preflight.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,10 +170,30 @@ def test_find_latest_digest(self, tmp_path):
new = stage_dir / "2026-06-23.json"
old.write_text("old")
new.write_text("new")
# Regression for #1767: touch() on both files resets mtimes to ~now,
# making mtime-based sorting ambiguous. Filename-based sorting is
# stable regardless of mtime.
old.touch()
new.touch()
assert ep.find_latest_digest("introspection", tmp_path) == new

def test_find_latest_digest_ignores_mtime(self, tmp_path):
"""Even when an older digest has a newer mtime, it must not win (#1767)."""
import os
import time

stage_dir = tmp_path / "evolution" / "introspection"
stage_dir.mkdir(parents=True)
old = stage_dir / "2026-06-20.json"
new = stage_dir / "2026-06-23.json"
new.write_text("new")
old.write_text("old")
# Make the older file's mtime much newer — mtime sort would pick it.
ts = time.time()
os.utime(old, (ts + 3600, ts + 3600))
os.utime(new, (ts, ts))
assert ep.find_latest_digest("introspection", tmp_path) == new

def test_load_digest_as_fallback(self, tmp_path):
stage_dir = tmp_path / "evolution" / "analysis"
stage_dir.mkdir(parents=True)
Expand Down
Loading