Commit Graph

9 Commits

Author SHA1 Message Date
69be24d4c0 Session: tag catch-up + pre-deadline buzz
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-11-01 18:25:15 +01:00
2defe09e6a Launcher: label WakeAlarm, icon fitted to the shared 340px ink circle
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012kQ5PCKqBHAkAVkPSvp6dk
2026-10-23 14:36:09 +02:00
34037e55c1 phone: sick days — picker, silent ring, stick stand-down, pre-alarm sync
- Sick days screen (own name, sick icon, cat-2 fill): tap today or a later
  day to mark it sick, tap again to clear; past days inert. Writes the
  sickdays wire format (state sick/cleared + actor, one HLC per write) to
  sickdays-sync/devices via crdt_sync syncLog.
- Mutual exclusion in both pickers: each syncs the other pool on open and
  refuses a date it holds, naming the pool; an unreadable pool refuses all.
- Native SickDays cache (fail closed via DayCache); AlarmReceiver skips the
  ring with reason "sick_day" (no markFired, no ring_started); StickCheck
  verdict sick_day, streak skips sick days; stakes card says so.
- Pre-alarm pool sync: allow-while-idle alarm at 21:00 and A-30 (request
  code 1006) runs a headless engine (poolSyncMain) that pulls both pools
  from RTDB and rewrites both native caches, so a PC mark arrives with the
  app closed. The ring decision stays a cache read.
- JVM unit tests (JUnit 4.13.2) for DayCache, RingDecision,
  PoolSyncSchedule, StickCheck; Dart tests; Dart->Python interop check.

pylint hook skipped: verify.py imports sickdays, not in the hook env until
the sickdays-v0.1.0 tag exists; run by hand in a sickdays venv (10.00/10).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xfj1sxKH9VBPqpcAni8Jry
2026-10-16 13:22:40 +02:00
728e00cde7 Wake enforcement v2: PC fallback ring + RTC self-wake, stacking Tue-Thu phone lockdown, escape hatch until 2026-10-15, stakes in the app, daily incident audit
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-01 12:17:06 +02:00
bfe8bab58a Add the Tue/Wed/Thu workday stick: signal focus-owner and write tomorrow's penalty
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-23 21:20:58 +02:00
7523cb4225 Ring the away profile when the phone is not at home
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 11:45:11 +02:00
95be57b575 Make the alarm fully automatic and un-weakenable (08:00, earlier-only)
The alarm was opt-in: it only armed when the user tapped "Set Alarm & Sync to
PC", so it could be defeated by simply not tapping it, or by cancelling it the
night before. It is now automatic and there is no lever a half-asleep user can
pull that buys them sleep.

- AlarmPolicy bakes the schedule in at 08:00 daily. The only edit the UI
  offers is moving the NEXT alarm earlier; setEarlier() refuses anything at or
  after the baseline, and refuses to relax an active override. Earlier-only is
  the whole design: every available change costs the user sleep.
- The ratchet resets after each fire, so an early start is one-shot. A one-way
  ratchet with no reset is a trap, not a defence — the first 05:00 would
  otherwise be permanent with no way back short of reinstalling.
- load() re-validates the stored value and ignores anything at or after the
  baseline. SharedPreferences is not a trust boundary.
- No cancel. The Kotlin handler is deleted, not just the Dart wrapper, so the
  capability genuinely does not exist rather than being one invokeMethod away.
- Arming happens in main()'s init seam and via AlarmWatchdog, a daily
  self-rescheduling check that re-arms if nothing is scheduled — closing the
  gap where a force-stop or a failed re-arm goes unnoticed until the morning
  it does not ring. BootReceiver re-establishes it, since setInexactRepeating
  does not survive a reboot.
- Publishing to the PC is automatic too; it used to need the same tap.

PC half, so the two agree with the network down:
- FALLBACK_ALARM_HOUR 13 -> 8, matching the phone's baseline. At 13:00 a
  Firebase outage silently moved the alarm five hours LATER — the one
  direction the design refuses to allow.
- _trigger._clamp_to_baseline() refuses any resolved alarm later than the
  baseline, whatever its source. Without it the PC would faithfully reproduce
  a stale or tampered remote value and the forbidden direction stays reachable
  through sync.

Tests: 465 Python at 100% branch coverage, 79 Dart. Both ratchet directions
are covered, per the standing rule about monotonic counters. test_sync.py and
test_trigger.py were split (payload, token, CLI, clamp) to stay under the
250-line gate; the split lost indirect coverage of extract_hlc/newest_alarm,
which now have direct tests instead.

`python -m wake_alarm._sync --show` and `_trigger --dry-run` both re-run and
still print real output.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EckWmyfetYq3Mi8MAnUudh
2026-08-27 23:03:00 +02:00
e0cc20664c Phase 4: the phone's Kotlin ring path (item 4)
Hand-written Kotlin behind ONE MethodChannel, never a plugin: AGP 9.0.1 /
Gradle 9.1 / Kotlin 2.3.20 is bleeding edge and a plugin that will not compile
breaks the build outright rather than degrading.

- AlarmScheduler: setAlarmClock() (Doze-exempt, no SCHEDULE_EXACT_ALARM
  prompt), persists the time so boot and dismiss can re-arm.
- AlarmReceiver: hands straight to the service — a receiver gets ~10s and no
  wakelock, so nothing that must outlive it belongs there.
- AlarmService: foreground service, PARTIAL_WAKE_LOCK, USAGE_ALARM
  (= STREAM_ALARM, which is what ignores DND and silent mode), looping
  ringtone, vibration, full-screen intent over the lockscreen.
- AlarmActivity: showWhenLocked + turnScreenOn, routes to the Dart ring
  screen, back is a no-op so the alarm cannot be escaped.
- BootReceiver: re-arms on BOOT_COMPLETED; AlarmManager drops alarms across a
  reboot.
- StepCounter: TYPE_STEP_COUNTER baseline for item 3's fallback proof, also
  hand-written to keep plugin risk off this path.

Details that are a hard crash or a silent no-ring rather than a warning:
FLAG_IMMUTABLE on every PendingIntent (mandatory since API 31);
foregroundServiceType declared on the <service> AND passed to
startForeground(); explicit android:exported on every component;
USE_FULL_SCREEN_INTENT declared (missing from the plan's list) and
canUseFullScreenIntent() exposed so the Dart side can gate rather than warn.

Every permission verified present in android-36's api-versions.xml rather
than from recall. Compiles green with `flutter build apk --debug`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EckWmyfetYq3Mi8MAnUudh
2026-08-27 21:12:51 +02:00
0704e44912 Add wake_alarm_sync phone-side Flutter companion app
Some checks are pending
pre-commit / pre-commit (push) Waiting to run
Tests / test (3.10) (push) Waiting to run
Tests / test (3.11) (push) Waiting to run
Tests / test (3.12) (push) Waiting to run
New Flutter app (com.kuhy.wake_alarm_sync) that sets the phone alarm and
writes the wake time to a GitHub Gist for cross-device sync: Gist service,
GitHub device-flow auth, sync settings, home/settings screens. Source only;
build/.dart_tool/.idea artifacts excluded by the app's .gitignore.

Not yet gated by CI (wake-alarm CI is Python-only) and pending on-device
verification.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017RRCdR4D8SaVrN2xraMQxM
2026-07-10 23:16:11 +02:00