diff --git a/apps/api/src/alicebot_api/sqlite_store.py b/apps/api/src/alicebot_api/sqlite_store.py index 41556494..10327480 100644 --- a/apps/api/src/alicebot_api/sqlite_store.py +++ b/apps/api/src/alicebot_api/sqlite_store.py @@ -311,7 +311,11 @@ def _utc_now_iso() -> str: - return datetime.now(UTC).isoformat().replace("+00:00", "Z") + # timespec pins the fractional part: isoformat() omits it entirely when + # microsecond == 0, and a '...59Z' string sorts lexicographically AFTER + # every '...59.000123Z' sibling, corrupting timestamp ordering for the + # ~1e-6 of writes that land on a whole second. + return datetime.now(UTC).isoformat(timespec="microseconds").replace("+00:00", "Z") def _iso_or_none(value: object | None) -> str | None: diff --git a/tests/unit/test_sqlite_store.py b/tests/unit/test_sqlite_store.py index 16112f6b..4f5f5838 100644 --- a/tests/unit/test_sqlite_store.py +++ b/tests/unit/test_sqlite_store.py @@ -2399,3 +2399,22 @@ def test_latest_agentic_commit_memory_finds_newest_by_agent() -> None: assert store.latest_agentic_commit_memory(agent_id="unknown") is None # Cross-user isolation: another user's store sees nothing. assert mallory.latest_agentic_commit_memory() is None + + +def test_utc_now_iso_always_carries_fractional_seconds(monkeypatch) -> None: + # A whole-second clock reading must not produce '...59Z': that string + # sorts lexicographically after every '...59.000123Z' sibling and + # corrupts timestamp ordering roughly once per million writes. + from datetime import datetime as real_datetime + + from alicebot_api import sqlite_store + + class WholeSecondDatetime: + @staticmethod + def now(tz): + return real_datetime(2026, 7, 7, 23, 59, 59, 0, tzinfo=tz) + + monkeypatch.setattr(sqlite_store, "datetime", WholeSecondDatetime) + stamped = sqlite_store._utc_now_iso() + assert stamped == "2026-07-07T23:59:59.000000Z" + assert stamped < "2026-07-07T23:59:59.000001Z"