screen-locker/screen_locker/tests
Krzysztof kuhy Rudnicki 86b63108c7 Tombstone sync records durably: a ledger the PC re-pushes every tick
manual:2026-09-13T14:30 -- the synced copy of the football that this
morning's day-key rekey let back in under a second id -- made every
workout-sync tick log "Manual-workout budget exhausted -- not ingested".
Tombstoning it once did not stick: sync_log pulls only the OTHER devices'
logs and _manual_push re-derives the PC's log from log.json each tick, so
the next push overwrote the tombstone ("tombstoned", then "LIVE" five
seconds later). Deletion is monotonic only for records still in the merge.

- New _sync_tombstones: {id: {deleted_hlc, reason, tombstoned_at}} in
  screen_locker/sync_tombstones.json (gitignored with its siblings). The
  HLC is minted once and stored so re-pushing does not churn the store.
  The ledger path is resolved at call time, not as a default argument, so
  the test suite's redirect actually applies -- verified by hashing the
  real ledger before and after the run.
- _manual_push.records_from_workout_log merges the ledger over the live
  derivation, so every push carries the tombstones.
- scripts/tombstone_sync_record.py <id> [--apply --reason ...]: dry run
  reports live/tombstoned; --apply writes the ledger, runs one tick and
  reports what the store holds afterwards. Rerunnable.
- tests/_isolated_state.py redirects SYNC_TOMBSTONES_FILE (both bindings).

Verified: --apply -> "tombstoned"; a second ordinary tick -> "already
tombstoned" with the same HLC; workout-sync.service now logs "Sync record
manual:2026-09-13T14:30 is tombstoned -- skipping it" and the budget line
for that id is gone. log.json untouched (the local entry stays; it shares
the original's credit per duplicate_credits.json).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C7vgacLhb4WJjxuVj2QLEP
2026-09-13 20:20:06 +02:00
..
fixtures Stop the phone counting deleted and empty manual workouts 2026-08-27 20:13:12 +02:00
shell Cap --restore at 23:00 so it is a reward, not an off switch 2026-08-28 19:59:43 +02:00
__init__.py Add comprehensive tests for stockfish_analysis (100% coverage) 2025-12-02 22:58:06 +01:00
_credential_fixtures.py Detect a Firebase credential the server rejects, not just one that fails to build 2026-08-27 21:32:39 +02:00
_gatelock_fixtures.py Migrate screen_locker to gatelock v0.2.0 per-output locking 2026-07-25 16:26:39 +02:00
_isolated_state.py Tombstone sync records durably: a ledger the PC re-pushes every tick 2026-09-13 20:20:06 +02:00
_locker_factories.py Split screen_lock.py under the 250-line cap 2026-08-15 19:01:18 +02:00
_opt_in_fixtures.py Stop the relaxed-day popup from re-firing every 30 minutes after Skip 2026-08-18 21:55:59 +02:00
_real_state_fixtures.py Replace scheduled skips with the shared free-day pool 2026-09-07 15:42:41 +02:00
_status_view_helpers.py Support multiple workouts per day; fix the dead sync; ban silent failures 2026-07-17 09:22:31 +02:00
_tk_mocks.py Finish the Python side: scripts, conftest, and the last test modules 2026-08-15 20:57:29 +02:00
_unlock_helpers.py Grade against 3.14 too, not just run on it 2026-08-29 11:42:47 +02:00
_workout_sync_fixtures.py Detect a Firebase credential the server rejects, not just one that fails to build 2026-08-27 21:32:39 +02:00
conftest.py Replace scheduled skips with the shared free-day pool 2026-09-07 15:42:41 +02:00
test_adb_and_phone_part2.py Split 13 test modules under the cap; document their test methods 2026-08-15 19:16:18 +02:00
test_adb_and_phone.py Split _shutdown, _phone_verification and _status_data 2026-08-15 20:11:28 +02:00
test_annotate_duplicate_credits.py Record which log entries are two recordings of one workout 2026-08-29 22:31:32 +02:00
test_armed_state.py Close the four gaps that let a decided lock never reach the screen 2026-08-30 15:24:45 +02:00
test_cli.py Cover the headless dispatch and the degraded-source annotation 2026-08-27 20:55:05 +02:00
test_compliance_state_part2.py Replace scheduled skips with the shared free-day pool 2026-09-07 15:42:41 +02:00
test_compliance_state_part3.py Split 13 test modules under the cap; document their test methods 2026-08-15 19:16:18 +02:00
test_compliance_state_part4.py Split 10 more test modules under the 250-line cap 2026-08-15 19:22:00 +02:00
test_compliance_state.py Replace scheduled skips with the shared free-day pool 2026-09-07 15:42:41 +02:00
test_credential_recovery_part2.py Detect a Firebase credential the server rejects, not just one that fails to build 2026-08-27 21:32:39 +02:00
test_credential_recovery.py Detect a Firebase credential the server rejects, not just one that fails to build 2026-08-27 21:32:39 +02:00
test_decision_log_modes.py Close the four gaps that let a decided lock never reach the screen 2026-08-30 15:24:45 +02:00
test_decision_log.py Close the four gaps that let a decided lock never reach the screen 2026-08-30 15:24:45 +02:00
test_decision_reasons.py Say every reason a lock decision had, not just the first one 2026-08-23 07:02:41 +02:00
test_decision_trail.py Close the four gaps that let a decided lock never reach the screen 2026-08-30 15:24:45 +02:00
test_duration_threshold_fixture.py Add sync-status card and duration-threshold fixture 2026-08-15 18:41:31 +02:00
test_early_bird_part2.py Grade against 3.14 too, not just run on it 2026-08-29 11:42:47 +02:00
test_early_bird_part3.py Grade against 3.14 too, not just run on it 2026-08-29 11:42:47 +02:00
test_early_bird.py Split 10 more test modules under the 250-line cap 2026-08-15 19:22:00 +02:00
test_extra_benefits_part2.py Grade against 3.14 too, not just run on it 2026-08-29 11:42:47 +02:00
test_extra_benefits.py Grade against 3.14 too, not just run on it 2026-08-29 11:42:47 +02:00
test_focus_and_scroll.py Take gatelock 0.8.1: nested LockConfig 2026-09-12 19:52:38 +02:00
test_heat_skip.py Add manual (unverified) workout logging with evidence form 2026-07-05 20:08:52 +02:00
test_home_isolation.py Grade against 3.14 too, not just run on it 2026-08-29 11:42:47 +02:00
test_init_and_log_part2.py Grade against 3.14 too, not just run on it 2026-08-29 11:42:47 +02:00
test_init_and_log_part3.py Grade against 3.14 too, not just run on it 2026-08-29 11:42:47 +02:00
test_init_and_log.py Grade against 3.14 too, not just run on it 2026-08-29 11:42:47 +02:00
test_instance.py Finish the RunnerUp sport-allowlist removal; isolate the instance lock in tests 2026-08-21 13:17:26 +02:00
test_local_day_key.py Key every workout day on the LOCAL calendar day, not UTC 2026-09-13 09:50:06 +02:00
test_log_io.py Support multiple workouts per day; fix the dead sync; ban silent failures 2026-07-17 09:22:31 +02:00
test_log_mixin_chokepoint.py Stop the relaxed-day popup from re-firing every 30 minutes after Skip 2026-08-18 21:55:59 +02:00
test_manual_budget_merge_fixture.py Stop the phone counting deleted and empty manual workouts 2026-08-27 20:13:12 +02:00
test_manual_cli.py Stub the HMAC signature so the manual-CLI test stops measuring the host 2026-08-27 21:43:16 +02:00
test_manual_push_part2.py Detect a Firebase credential the server rejects, not just one that fails to build 2026-08-27 21:32:39 +02:00
test_manual_push.py Grade against 3.14 too, not just run on it 2026-08-29 11:42:47 +02:00
test_manual_sync_part2.py Stop the relaxed-day popup from re-firing every 30 minutes after Skip 2026-08-18 21:55:59 +02:00
test_manual_sync_part3.py Stop the relaxed-day popup from re-firing every 30 minutes after Skip 2026-08-18 21:55:59 +02:00
test_manual_sync.py Grade against 3.14 too, not just run on it 2026-08-29 11:42:47 +02:00
test_manual_workout_dialog.py Make the lock overlay's sport selector usable, and focus its form fields 2026-07-26 15:08:08 +02:00
test_manual_workout_features_part2.py Finish the Python side: scripts, conftest, and the last test modules 2026-08-15 20:57:29 +02:00
test_manual_workout_features_part3.py Split the last 8 test modules; scope pylint's test-inherent checks 2026-08-15 19:40:32 +02:00
test_manual_workout_features_part4.py Finish the Python side: scripts, conftest, and the last test modules 2026-08-15 20:57:29 +02:00
test_manual_workout_features.py Split the last 8 test modules; scope pylint's test-inherent checks 2026-08-15 19:40:32 +02:00
test_manual_workout_part2.py Split 12 more test modules under the 250-line cap 2026-08-15 19:34:59 +02:00
test_manual_workout_status_view.py Take gatelock 0.8.1: nested LockConfig 2026-09-12 19:52:38 +02:00
test_manual_workout.py Grade against 3.14 too, not just run on it 2026-08-29 11:42:47 +02:00
test_mcp.py Replace scheduled skips with the shared free-day pool 2026-09-07 15:42:41 +02:00
test_no_popup_widgets.py Make the lock overlay's sport selector usable, and focus its form fields 2026-07-26 15:08:08 +02:00
test_phone_check_unlock_part2.py Stop the relaxed-day popup from re-firing every 30 minutes after Skip 2026-08-18 21:55:59 +02:00
test_phone_check_unlock_part3.py Add auto-fill RunnerUp scan, carrot bonuses, and --status interface 2026-06-28 08:08:35 +02:00
test_phone_verification_part2.py Split _shutdown, _phone_verification and _status_data 2026-08-15 20:11:28 +02:00
test_phone_verification_part3.py Split 10 more test modules under the 250-line cap 2026-08-15 19:22:00 +02:00
test_phone_verification_part4.py Split _shutdown, _phone_verification and _status_data 2026-08-15 20:11:28 +02:00
test_phone_verification_part5.py Grade against 3.14 too, not just run on it 2026-08-29 11:42:47 +02:00
test_phone_verification_part6.py Split _shutdown, _phone_verification and _status_data 2026-08-15 20:11:28 +02:00
test_phone_verification_part7.py Split _shutdown, _phone_verification and _status_data 2026-08-15 20:11:28 +02:00
test_phone_verification_part8.py Split 10 more test modules under the 250-line cap 2026-08-15 19:22:00 +02:00
test_queue_state.py Close the four gaps that let a decided lock never reach the screen 2026-08-30 15:24:45 +02:00
test_runnerup_verification_part1.py Finish the RunnerUp sport-allowlist removal; isolate the instance lock in tests 2026-08-21 13:17:26 +02:00
test_runnerup_verification_part2.py Finish the RunnerUp sport-allowlist removal; isolate the instance lock in tests 2026-08-21 13:17:26 +02:00
test_runnerup_verification_part3.py Finish the RunnerUp sport-allowlist removal; isolate the instance lock in tests 2026-08-21 13:17:26 +02:00
test_runnerup_verification_part4.py Finish the RunnerUp sport-allowlist removal; isolate the instance lock in tests 2026-08-21 13:17:26 +02:00
test_runnerup_verification_part5.py Finish the RunnerUp sport-allowlist removal; isolate the instance lock in tests 2026-08-21 13:17:26 +02:00
test_runnerup_verification_part6.py Finish the RunnerUp sport-allowlist removal; isolate the instance lock in tests 2026-08-21 13:17:26 +02:00
test_runnerup_verification_part7.py Finish the RunnerUp sport-allowlist removal; isolate the instance lock in tests 2026-08-21 13:17:26 +02:00
test_runnerup_verification_part8.py Finish the RunnerUp sport-allowlist removal; isolate the instance lock in tests 2026-08-21 13:17:26 +02:00
test_runnerup_verification_part9.py Finish the RunnerUp sport-allowlist removal; isolate the instance lock in tests 2026-08-21 13:17:26 +02:00
test_runnerup_verification_part10.py Finish the RunnerUp sport-allowlist removal; isolate the instance lock in tests 2026-08-21 13:17:26 +02:00
test_runnerup_verification_part11.py Finish the RunnerUp sport-allowlist removal; isolate the instance lock in tests 2026-08-21 13:17:26 +02:00
test_runnerup_verification_part12.py Finish the RunnerUp sport-allowlist removal; isolate the instance lock in tests 2026-08-21 13:17:26 +02:00
test_runnerup_verification_part13.py Finish the RunnerUp sport-allowlist removal; isolate the instance lock in tests 2026-08-21 13:17:26 +02:00
test_runnerup_verification_part14.py Finish the RunnerUp sport-allowlist removal; isolate the instance lock in tests 2026-08-21 13:17:26 +02:00
test_runnerup_verification_part15.py Finish the RunnerUp sport-allowlist removal; isolate the instance lock in tests 2026-08-21 13:17:26 +02:00
test_scheduled_skip.py Replace scheduled skips with the shared free-day pool 2026-09-07 15:42:41 +02:00
test_screen_lock_coverage_part1.py Split the last 8 test modules; scope pylint's test-inherent checks 2026-08-15 19:40:32 +02:00
test_screen_lock_coverage_part2.py Finish the Python side: scripts, conftest, and the last test modules 2026-08-15 20:57:29 +02:00
test_screen_lock_coverage_part3.py Grade against 3.14 too, not just run on it 2026-08-29 11:42:47 +02:00
test_screen_lock_coverage_part4.py Close the four gaps that let a decided lock never reach the screen 2026-08-30 15:24:45 +02:00
test_screen_lock_coverage_part5.py Credit StrongLifts sessions from the headless sync, as pc_workout_verified 2026-08-22 11:26:20 +02:00
test_session_sync.py Grade against 3.14 too, not just run on it 2026-08-29 11:42:47 +02:00
test_shutdown_base_replay.py Replay today's earned shutdown credit in the daily base reset 2026-09-13 20:00:05 +02:00
test_shutdown_base.py Grade against 3.14 too, not just run on it 2026-08-29 11:42:47 +02:00
test_shutdown_part2.py Split 13 test modules under the cap; document their test methods 2026-08-15 19:16:18 +02:00
test_shutdown_part3.py Grade against 3.14 too, not just run on it 2026-08-29 11:42:47 +02:00
test_shutdown_part4.py Split _shutdown, _phone_verification and _status_data 2026-08-15 20:11:28 +02:00
test_shutdown_part5.py Grade against 3.14 too, not just run on it 2026-08-29 11:42:47 +02:00
test_shutdown_part6.py Split 10 more test modules under the 250-line cap 2026-08-15 19:22:00 +02:00
test_sick_features_part2.py refactor: enforce 400-line file limit with pre-commit hook and split outliers 2026-05-28 21:19:18 +02:00
test_sick_features_part3.py Split 13 test modules under the cap; document their test methods 2026-08-15 19:16:18 +02:00
test_sick_features.py Split 13 test modules under the cap; document their test methods 2026-08-15 19:16:18 +02:00
test_sick_tracker_part2.py Split 12 more test modules under the 250-line cap 2026-08-15 19:34:59 +02:00
test_sick_tracker.py Split 12 more test modules under the 250-line cap 2026-08-15 19:34:59 +02:00
test_source_health.py Grade against 3.14 too, not just run on it 2026-08-29 11:42:47 +02:00
test_status_data_part2.py Close the four gaps that let a decided lock never reach the screen 2026-08-30 15:24:45 +02:00
test_status_data_part3.py Replace scheduled skips with the shared free-day pool 2026-09-07 15:42:41 +02:00
test_status_data_part4.py Grade against 3.14 too, not just run on it 2026-08-29 11:42:47 +02:00
test_status_data.py Replace scheduled skips with the shared free-day pool 2026-09-07 15:42:41 +02:00
test_status_part2.py Grade against 3.14 too, not just run on it 2026-08-29 11:42:47 +02:00
test_status_part3.py Grade against 3.14 too, not just run on it 2026-08-29 11:42:47 +02:00
test_status_view_part2.py Take gatelock 0.8.1: nested LockConfig 2026-09-12 19:52:38 +02:00
test_status_view_part3.py Make Check Phone week-aware; add RunnerUp GPS tolerance; fix CLI weekly count 2026-07-18 06:55:55 +02:00
test_status_view_part4.py Split 13 test modules under the cap; document their test methods 2026-08-15 19:16:18 +02:00
test_status_view_part5.py Split 10 more test modules under the 250-line cap 2026-08-15 19:22:00 +02:00
test_status_view_part6.py Split 12 more test modules under the 250-line cap 2026-08-15 19:34:59 +02:00
test_status_view_part7.py Serve the lock decision, its history and its health at 127.0.0.1:8770 2026-08-29 22:50:19 +02:00
test_status_view.py Take gatelock 0.8.1: nested LockConfig 2026-09-12 19:52:38 +02:00
test_status.py Grade against 3.14 too, not just run on it 2026-08-29 11:42:47 +02:00
test_surface_fanout.py Take gatelock 0.8.1: nested LockConfig 2026-09-12 19:52:38 +02:00
test_sync_degradation_part2.py Close the four gaps that let a decided lock never reach the screen 2026-08-30 15:24:45 +02:00
test_sync_degradation.py Replace scheduled skips with the shared free-day pool 2026-09-07 15:42:41 +02:00
test_sync_fixtures.py Split _workout_sync.py and _sick_tracker.py 2026-08-15 20:30:42 +02:00
test_sync_retry.py Retry sync past the boot/resume network race, and stop blaming the token 2026-07-20 08:46:37 +02:00
test_sync_status.py Show which sync backend the PC uses in the status window 2026-08-09 21:20:02 +02:00
test_sync_tombstones.py Tombstone sync records durably: a ledger the PC re-pushes every tick 2026-09-13 20:20:06 +02:00
test_systemd_units.py Re-arm the locker, and make a disarmed enforcer impossible to miss 2026-08-17 16:47:25 +02:00
test_temperature.py Close the four gaps that let a decided lock never reach the screen 2026-08-30 15:24:45 +02:00
test_time_check.py chore: set up as standalone repo 2026-05-28 07:43:06 +02:00
test_ui_and_timers_part2.py chore: set up as standalone repo 2026-05-28 07:43:06 +02:00
test_ui_and_timers.py Take gatelock 0.8.1: nested LockConfig 2026-09-12 19:52:38 +02:00
test_ui_flows_part2.py Stop the relaxed-day popup from re-firing every 30 minutes after Skip 2026-08-18 21:55:59 +02:00
test_verify_workout_part2.py Stop the relaxed-day popup from re-firing every 30 minutes after Skip 2026-08-18 21:55:59 +02:00
test_verify_workout.py Take gatelock 0.8.1: nested LockConfig 2026-09-12 19:52:38 +02:00
test_vt_switching.py Migrate to the shared gatelock backend 2026-06-21 20:11:16 +02:00
test_wake_shutdown.py Grade against 3.14 too, not just run on it 2026-08-29 11:42:47 +02:00
test_wake_skip.py Add auto-fill RunnerUp scan, carrot bonuses, and --status interface 2026-06-28 08:08:35 +02:00
test_wake_state.py fix: apply pre-commit formatting fixes to unblock CI 2026-05-28 20:59:35 +02:00
test_web_payload_part2.py Serve the lock decision, its history and its health at 127.0.0.1:8770 2026-08-29 22:50:19 +02:00
test_web_payload.py Close the four gaps that let a decided lock never reach the screen 2026-08-30 15:24:45 +02:00
test_web_server_part2.py Serve the lock decision, its history and its health at 127.0.0.1:8770 2026-08-29 22:50:19 +02:00
test_web_server.py Serve the lock decision, its history and its health at 127.0.0.1:8770 2026-08-29 22:50:19 +02:00
test_weekly_check_dedup_part2.py Count one StrongLifts session once, not once per ingestion path 2026-08-29 22:23:17 +02:00
test_weekly_check_dedup.py Count one StrongLifts session once, not once per ingestion path 2026-08-29 22:23:17 +02:00
test_weekly_check_part2.py Stop the relaxed-day popup from re-firing every 30 minutes after Skip 2026-08-18 21:55:59 +02:00
test_weekly_check.py Grade against 3.14 too, not just run on it 2026-08-29 11:42:47 +02:00
test_weekly_logic_part2.py Stop the relaxed-day popup from re-firing every 30 minutes after Skip 2026-08-18 21:55:59 +02:00
test_weekly_logic_part3.py Close the four gaps that let a decided lock never reach the screen 2026-08-30 15:24:45 +02:00
test_weekly_logic_part4.py Stop the relaxed-day popup from re-firing every 30 minutes after Skip 2026-08-18 21:55:59 +02:00
test_weekly_logic_part5.py Grade against 3.14 too, not just run on it 2026-08-29 11:42:47 +02:00
test_weekly_logic.py Split 12 more test modules under the 250-line cap 2026-08-15 19:34:59 +02:00
test_workout_app.py Supervise the workout app as the lock surface, on a deadline that holds 2026-08-22 11:52:02 +02:00
test_workout_handoff.py Lend the lock screen's grab to the workout app, without unlocking it 2026-08-22 12:08:40 +02:00
test_workout_ready.py Supervise the workout app as the lock surface, on a deadline that holds 2026-08-22 11:52:02 +02:00
test_workout_session.py Put the demo's escape hatch inside the app, the only place it can be received 2026-08-22 12:41:07 +02:00
test_workout_sync_part2.py Detect a Firebase credential the server rejects, not just one that fails to build 2026-08-27 21:32:39 +02:00
test_workout_sync_part3.py Detect a Firebase credential the server rejects, not just one that fails to build 2026-08-27 21:32:39 +02:00
test_workout_sync_part4.py Split 13 test modules under the cap; document their test methods 2026-08-15 19:16:18 +02:00
test_workout_sync_part5.py Split _workout_sync.py and _sick_tracker.py 2026-08-15 20:30:42 +02:00
test_workout_sync_part6.py Apply sync tombstones across the device union, not per device log 2026-08-21 13:38:52 +02:00
test_workout_sync.py Cover _tombstoned_ids 2026-08-21 13:43:34 +02:00