mirror of
https://github.com/kuhyx/screen-locker.git
synced 2026-11-10 21:27:13 +01:00
A manual workout logged at 00:02 CEST on 2026-09-13 was filed under 2026-09-12: every day-key derivation used datetime.now(tz=UTC), and at that instant UTC was still yesterday. The 09:18 lock check looked for 2026-09-13, found nothing, and locked the PC — for a workout that was on disk. The budget counter (a trailing window) counted it fine, which is why the status showed 1/2 used while the locker still locked. Any log between local midnight and 02:00 CEST hit this; it had happened before. - New screen_locker/_day.py: today_str() is the one definition of "what day is it" — the local calendar day the user sees on the clock, which is also what the phone app, RunnerUp verification, the status window and the freedays pool already used. All 14 UTC sites (log writer, lock predicates, explain chain, early-bird, wake state, shutdown/sick state, sick budget, manual CLI, phone verification) now go through it, writers and readers together so no new midnight window opens between them. - write_signed_entry dedups by workout_id across ALL days: a workout id is globally unique, and an entry moved to its correct day must not let its synced twin (still stamped with the old day) re-ingest as a duplicate. - scripts/rekey_log_entry.py: move a wrongly-filed entry to its real day (the HMAC never covered the day key, so it still verifies). - test_local_day_key.py freezes the clock at the exact failing instant. Replayed the real timeline (log at 00:02:35, evaluate at 09:18:53, weekly 3/5) against HEAD in a throwaway worktree vs this tree: HEAD -> logged under 2026-09-12, LOCK fixed -> logged under 2026-09-13, no lock (already_logged) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FcU5dCcUNHMAMzUNChdrrJ
33 lines
1.5 KiB
Python
33 lines
1.5 KiB
Python
"""The one definition of "what day is it" for every day-keyed file.
|
|
|
|
``log.json``, ``sick_day_state.json``, ``early_bird_pending.json``,
|
|
``wake_state.json`` and the sick history are all keyed by ``YYYY-MM-DD``.
|
|
Until 2026-09-13 each writer and reader derived that key on its own, and
|
|
most of them used ``datetime.now(tz=UTC)`` while the lock window, the phone
|
|
app, RunnerUp verification and the status window used LOCAL time. In CEST
|
|
that is a two-hour window after local midnight in which a workout logged
|
|
"today" was filed under yesterday — the lock check then looked for today,
|
|
found nothing, and locked the PC for a workout that was on disk.
|
|
|
|
The day is the **local calendar day**: it is what the user sees on the
|
|
clock when they log, what the lock screen names when it says "no workout
|
|
for today", and what the phone already writes. Every module goes through
|
|
:func:`today_str` so the two can never disagree again; tests freeze the
|
|
clock by patching ``screen_locker._day.datetime``.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import UTC, datetime
|
|
|
|
|
|
def today_str(now: datetime | None = None) -> str:
|
|
"""Return the local calendar day of *now* (default: this instant) as ``YYYY-MM-DD``.
|
|
|
|
*now* may be in any timezone; it is converted to the machine's local zone
|
|
first, which is the only conversion that turns ``22:02Z`` on the 12th
|
|
into the 13th the user actually logged on.
|
|
"""
|
|
instant = now if now is not None else datetime.now(tz=UTC)
|
|
return instant.astimezone().strftime("%Y-%m-%d")
|