371 -> 168. Token discovery and both backends move to _sync_transport.py;
_sync.py keeps resolution (cache, staleness, fallback), the Alarm dataclass
and the CLI.
The seam is chosen for item 8, not just for line count: that phase adds a
dismissal-record reader, which belongs beside the alarm reader in the
transport rather than inside the resolution chain.
ALARM_CACHE_FILE deliberately stays in _sync. The suite patches
`wake_alarm._sync.ALARM_CACHE_FILE` to keep tests off
~/.config/wake_alarm/alarm_cache.json — the file shutdown-wrapper.sh reads at
poweroff — and moving that binding would silently reconnect the tests to the
real one. That is the failure mode that once wrote 27 fake entries into a
live log while the suite still looked green.
The split broke 31 tests loudly, all of them patch targets that had to follow
the code: the three SYNC_* constants, the two fetchers, the patch.multiple on
the firebase names, and the reload test for the crdt_sync import guard. Every
one is repointed at _sync_transport; none was left silently patching nothing.
Verified after: 465 passed at 100% coverage, `python -m wake_alarm._sync
--show` prints, `--no-cache` still resolves the phone's real alarm through
Firebase, `_trigger --dry-run` prints, and wake_state.json is byte-identical.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EckWmyfetYq3Mi8MAnUudh