The previous commit dropped tombstoned records while reading each device
log, which was not enough: _merge_device_records unions across devices
keeping the highest HLC, so a record tombstoned on one device and still
live on another was simply absent from one dict and won the merge from
the other -- ingested exactly as before.
Collect tombstoned ids across every device log, then suppress by id
after the union. crdt-sync makes deletion monotonic inside its own merge
for this reason; this reader rolls its own merge, so it has to honour
the same rule itself.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018M5gKyYzxWG6QkCaj1wWfk
_records_matching read every record's payload and never checked
record.deleted, so a tombstoned workout was ingested like a live one.
Deleting a workout in the phone app therefore looked like it worked and
came back on the next 15-minute sync, silently re-crediting it.
crdt-sync deletion is monotonic (a tombstone cannot be resurrected by
merging an older, non-deleted copy), so this is the only durable way to
retract a synced workout across devices. Skip tombstoned records and say
so at info, since "this stopped counting" is exactly the kind of thing
that must not happen silently.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018M5gKyYzxWG6QkCaj1wWfk
_workout_sync.py (332) -> _sync_client.py token, Firebase/GitHub client
_sync_records.py payload predicates + log decoding
_sick_tracker.py (317) -> _sick_budget.py SickHistory + rolling budget
This one was not a pure move until the fixtures followed it. Three autouse
test guards patch names by module, and all three pointed at _workout_sync:
isolate_sync_token SYNC_TOKEN_FILE -> now also on _sync_client
_no_real_firebase_config CONFIG_FILE -> now on _sync_client
_firebase_config helper (7 call sites) -> now passed _sync_client
With them stale, sync_client built a REAL client: the suite went from 14s to
over 120s and test_workout_sync_part5 asserted against live workout data
pulled from the actual backend. Fixed, the same tests run in 0.7s. Both
modules keep SYNC_TOKEN_FILE so either patch point works, and every relocated
guard target is asserted to exist.
977 tests pass at 100% branch coverage; pylint back to 8.70 after fixing the
findings the split introduced (a dropped `return None` in _parse_iso and
leftover dead imports). `screen_lock --status` unchanged.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EveB813BQ8abAP68VSqZ5Y