mirror of
https://github.com/kuhyx/wake-alarm.git
synced 2026-11-11 13:47:31 +01:00
Adds the diagnostic substrate the rest of the hardening work builds on. A 6am failure should be reconstructable afterwards instead of re-debugged. Two sinks, deliberately. stderr -> journal is unchanged. Alongside it, one JSON object per line in ~/.local/state/wake_alarm/events.jsonl, so a morning can be replayed with jq. The hard constraint is that _trigger is a fresh process every 60 seconds, so an in-memory rate limiter would reset on every tick and suppress nothing. _log_dedupe therefore persists the last emitted fingerprint to disk and flushes a suppressed_repeats count onto the next distinct record. Verified across separate processes: 4 invocations logging the same routine event produce 1 record plus an accurate count of 3. That is what satisfies the standing "do not raise the level of _sync's degraded-read paths" rule by construction, rather than by keeping levels artificially low and losing the signal. The correlation id is derived, never generated: both halves compute wake-YYYYMMDD-HHMM from the resolved alarm instant, so the PC and the phone agree with no handshake and no uuid exchange -- which matters precisely when sync is down. Logging never raises. An unwritable sink degrades to journal-only. _alarm.py and _sync.py keep the default component= for now; tagging them means splitting them for the 250-line gate, which belongs in the phases that actually restructure those files. 389 tests pass, 100% branch coverage held. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EckWmyfetYq3Mi8MAnUudh
52 lines
1.7 KiB
Python
52 lines
1.7 KiB
Python
"""Derive the identifier that joins one morning's PC and phone log lines.
|
|
|
|
The id is **derived, never generated**: both halves compute it independently
|
|
from the resolved alarm instant, so no handshake, no uuid exchange and no
|
|
network round-trip is involved. That matters because the two devices must
|
|
still agree when sync is down -- which is exactly when the logs are worth
|
|
reading.
|
|
|
|
Local time, matching ``_trigger``'s local-time basis (``_state.py`` keys by
|
|
UTC, which is a separate known mismatch tracked in the hardening plan).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING
|
|
|
|
if TYPE_CHECKING:
|
|
from datetime import datetime
|
|
|
|
# ``wake-YYYYMMDD-HHMM``. Stable across processes and across devices.
|
|
_CORRELATION_FORMAT = "wake-%Y%m%d-%H%M"
|
|
|
|
|
|
def correlation_id(fire_time: datetime) -> str:
|
|
"""Build the correlation id for the alarm firing at *fire_time*.
|
|
|
|
Args:
|
|
fire_time: The resolved local fire instant for today's alarm.
|
|
|
|
Returns:
|
|
A string of the form ``wake-20260826-0700``.
|
|
"""
|
|
return fire_time.strftime(_CORRELATION_FORMAT)
|
|
|
|
|
|
def correlation_id_for(hour: int, minute: int, *, now: datetime) -> str:
|
|
"""Build the correlation id from an ``(hour, minute)`` alarm on *now*'s date.
|
|
|
|
Convenience for callers that hold the synced alarm rather than a resolved
|
|
datetime. The date comes from *now*, so a caller must pass the date the
|
|
alarm belongs to, not merely the current instant.
|
|
|
|
Args:
|
|
hour: Alarm hour, 0-23.
|
|
minute: Alarm minute, 0-59.
|
|
now: Any datetime on the alarm's local date.
|
|
|
|
Returns:
|
|
The correlation id string.
|
|
"""
|
|
return correlation_id(now.replace(hour=hour, minute=minute))
|