diet-guard/diet-guard-gate.timer
Krzysztof kuhy Rudnicki c310861855 slots: minute-of-day consumers (phase 2 of 3: Python side)
Every Python consumer now speaks slot minutes:

- satisfaction is nearest-slot snapping (_slot_wire.satisfied_by_entries)
  at every "is this slot logged?" site: gate, CLI status, MCP, catering
  occupancy and dedup. Nothing on disk is rewritten; 07:00/10:00 entries
  keep covering 07:15/10:15 after a schedule move.
- log_meal writes slot_fields(minute) before signing (slot + slot_min).
- schedule store and sync encode via schedule_to_wire/from_wire; a
  whole-hour schedule stays byte-identical (f/l/n only).
- CLI ate --hour takes HH or HH:MM; MCP log_meal takes "HH:MM" or a
  legacy int hour, validated against today's slots; get_slots returns
  {minute, label}.
- gate History tab: first/last are HH:MM ttk.Comboboxes (quarter-hour
  list, typed times, Up/Down step without posting the popdown), locked
  as disabled; validation mirrors normalized().
- gate timer fires every 15 min; docs rewritten for minutes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015npsYVB2PdtWkG7MPAi4tn
2026-10-10 19:53:39 +02:00

24 lines
1.1 KiB
SYSTEMD

[Unit]
Description=Periodically run the Diet Guard gate check
[Timer]
# Check on a WALL-CLOCK schedule (every 15 min: :00, :15, :30, :45).
# The meal slots and the enforcement window are enforced inside gate_is_due(),
# so the timer only needs to fire often enough to notice a slot opening. Slots
# can sit on any quarter hour (interior slots round to the 15-minute grid, and
# a 07:15 first meal is common), so a 30-min tick would let a slot go
# unenforced for up to half an hour; 15 min lands on every grid slot exactly.
# A first/last meal typed off the grid (07:23) is caught by the next tick.
#
# Why OnCalendar and not OnBootSec/OnUnitActiveSec: the gate is a *blocking*
# oneshot -- it stays up until a meal is logged -- and a monotonic schedule
# computed no next elapse once the service had run (and none at all across a
# reboot), leaving the timer dormant (NextElapse=infinity). OnCalendar re-arms
# every period regardless of how long the gate stayed open, and Persistent
# catches a run missed while the machine was suspended.
OnCalendar=*-*-* *:00/15:00
Persistent=true
[Install]
WantedBy=timers.target