Add one-shot GPS time-sync engine with guaranteed power-down
- gps::SyncLogic (pure, header-only, host-testable): one-shot state
machine fed raw receiver bytes + a monotonic now; terminal phases are
latched until a fresh start(). Fixes are accepted only when the year
lies in [2024, 2099] (the set-clock widget range — the NMEA parser
alone admits ZDA years to 2255 and the epoch validator only floors at
2001) AND the epoch clears rtc::internal::kMinValidEpoch.
- gps_time_sync (device wiring, #if HAS_GPS with no-op stubs elsewhere):
UART facade begin -> drain/feed/tick -> on the first valid fix one
atomic rtc::setDateTime write + ClockSource::Gps tag -> power-down.
Power-down runs on EVERY exit path (success, timeout, cancel, begin
failure) with best-effort L76K $PCAS12 / u-blox UBX-RXM-PMREQ standby
frames before the UART teardown returns the pads to hi-Z inputs.
- Defense-in-depth: PowerManagerTDeck::enterDeepSleep cancels an
abandoned sync before cutting the BOARD_POWERON rail.
- 14 native tests: fix accept, checksum reject, year clamp (floor, cap,
boundaries), epoch floor, timeout boundary, cancel semantics, terminal
latching, uint32 wrap, nullptr feed. Non-GPS boards verified symbol-free.
Claude-Session: https://claude.ai/code/session_01SV58JXhfxhhc9DC6vdjBo4