notify: shared reminder scheduler seam #97

Closed
opened 2026-08-16 14:24:15 +00:00 by nalum · 1 comment
Owner

The meal rota reminder (internal/services/mealrota/reminder.go) is a bespoke daily ticker: wake at 09:00 in the rota's zone, claim the firing with a locked compare-and-set on reminder_last_sent, ring, sleep. It works, and it is about to be copied — #91 (JOB_DUE) needs the same loop keyed on item deadlines, #98 (event reminders) needs it keyed on occurrence dates, and #99 (overdue jobs) extends #91.

Three hand-rolled copies of a sleep/claim/fire loop is how the restart-safety and catch-up-after-downtime subtleties drift apart. Extract the shared shape into a seam (internal/notify or a sibling package): the generic part owns the clock, the wall-clock wake hour, the cutoff-hour catch-up bound, the claim protocol and the retry pacing; each reminder supplies "what is due today and who hears about it".

Scope:

  • Extract the generic ticker from the meal reminder without changing its behavior (same 09:00 fire, same 21:00 cutoff, same claim semantics — the existing reminder tests keep passing).
  • Port the meal reminder onto the seam as the first consumer.
  • #91 and #98 build on the seam instead of copying the loop.
  • The claim column pattern (bookkeeping write, no mutation entry) is part of the seam's contract — document it.

Sequencing: either this lands first and #91 consumes it, or #91 copies the loop and this issue refactors both copies onto the seam afterwards. Landing it first is less total work.

Follow-up from the reminders audit.

The meal rota reminder (`internal/services/mealrota/reminder.go`) is a bespoke daily ticker: wake at 09:00 in the rota's zone, claim the firing with a locked compare-and-set on `reminder_last_sent`, ring, sleep. It works, and it is about to be copied — #91 (JOB_DUE) needs the same loop keyed on item deadlines, #98 (event reminders) needs it keyed on occurrence dates, and #99 (overdue jobs) extends #91. Three hand-rolled copies of a sleep/claim/fire loop is how the restart-safety and catch-up-after-downtime subtleties drift apart. Extract the shared shape into a seam (`internal/notify` or a sibling package): the generic part owns the clock, the wall-clock wake hour, the cutoff-hour catch-up bound, the claim protocol and the retry pacing; each reminder supplies "what is due today and who hears about it". Scope: - Extract the generic ticker from the meal reminder without changing its behavior (same 09:00 fire, same 21:00 cutoff, same claim semantics — the existing reminder tests keep passing). - Port the meal reminder onto the seam as the first consumer. - #91 and #98 build on the seam instead of copying the loop. - The claim column pattern (bookkeeping write, no mutation entry) is part of the seam's contract — document it. Sequencing: either this lands first and #91 consumes it, or #91 copies the loop and this issue refactors both copies onto the seam afterwards. Landing it first is less total work. Follow-up from the reminders audit.
Author
Owner

Scope note from the #98 decisions (2026-08-16): the seam must support arbitrary next-fire times (per-event lead times), not only a daily wall-clock hour. The generic part becomes "compute next due instant, sleep until it, claim, fire" with the daily-at-09:00 pattern as one consumer shape (meal rota, #91/#99) and instant-based firing as another (#98). Claim/catch-up semantics unchanged.

Scope note from the #98 decisions (2026-08-16): the seam must support **arbitrary next-fire times** (per-event lead times), not only a daily wall-clock hour. The generic part becomes "compute next due instant, sleep until it, claim, fire" with the daily-at-09:00 pattern as one consumer shape (meal rota, #91/#99) and instant-based firing as another (#98). Claim/catch-up semantics unchanged.
nalum added reference main 2026-08-16 14:43:36 +00:00
nalum changed reference from main to v1.5.0 2026-08-16 14:43:58 +00:00
nalum closed this issue 2026-08-16 19:44:24 +00:00
Sign in to join this conversation.
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
eagraiclainne/app#97
No description provided.