fix(alarms): give up after three runs without delivery (FLAW-12)
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m34s
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m34s
New column alarm_failures counts ticker runs in which no channel delivered; at three the alarm is marked as done and a warning is logged instead of retrying every minute forever. Success, a new due date or reopening the task starts over with three attempts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
b5f926bcf7
commit
d1982816c1
8 files changed
+80
-13
No files matched your search
@@ -251,8 +251,9 @@ func (s *Store) ReorderTasks(taskListID string, items []domain.ThoughtReorderIte
|
||||
}
|
||||
_, err = tx.Exec(
|
||||
`UPDATE thoughts SET sort_order = ?, kanban_status = ?, is_completed = ?,
|
||||
alarm_sent = CASE WHEN ? = 0 THEN 0 ELSE alarm_sent END WHERE id = ?`,
|
||||
it.SortOrder, *it.KanbanStatus, isCompleted, isCompleted, it.ID,
|
||||
alarm_sent = CASE WHEN ? = 0 THEN 0 ELSE alarm_sent END,
|
||||
alarm_failures = CASE WHEN ? = 0 THEN 0 ELSE alarm_failures END WHERE id = ?`,
|
||||
it.SortOrder, *it.KanbanStatus, isCompleted, isCompleted, isCompleted, it.ID,
|
||||
)
|
||||
} else {
|
||||
_, err = tx.Exec(
|
||||
@@ -388,9 +389,10 @@ func (s *Store) UpdateThoughtTriage(userID, thoughtID, action, convertTo, target
|
||||
_, err = s.db.Exec(
|
||||
`UPDATE thoughts
|
||||
SET status = ?, converted_type = ?, converted_ref_id = ?, due_date = ?, task_list_id = ?, triaged_at = ?,
|
||||
alarm_sent = CASE WHEN due_date != ? THEN 0 ELSE alarm_sent END
|
||||
alarm_sent = CASE WHEN due_date != ? THEN 0 ELSE alarm_sent END,
|
||||
alarm_failures = CASE WHEN due_date != ? THEN 0 ELSE alarm_failures END
|
||||
WHERE id = ? AND user_id = ?`,
|
||||
string(newStatus), convertedType, convertedRefID, dueDate, taskListID, now, dueDate, thoughtID, userID,
|
||||
string(newStatus), convertedType, convertedRefID, dueDate, taskListID, now, dueDate, dueDate, thoughtID, userID,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -479,7 +481,7 @@ func (s *Store) UpdateThoughtDetails(userID, thoughtID string, content *string,
|
||||
}
|
||||
// A new due date or a reopened task gets its alarm again
|
||||
if thought.DueDate != oldDue || (wasCompleted && !thought.IsCompleted) {
|
||||
_, _ = s.db.Exec(`UPDATE thoughts SET alarm_sent = 0 WHERE id = ?`, thoughtID)
|
||||
_, _ = s.db.Exec(`UPDATE thoughts SET alarm_sent = 0, alarm_failures = 0 WHERE id = ?`, thoughtID)
|
||||
thought.AlarmSent = false
|
||||
}
|
||||
if taskListID != nil {
|
||||
@@ -572,8 +574,9 @@ func (s *Store) SyncTaskFromBlockCheck(userID, taskID string, checked bool) {
|
||||
doneInt = 1
|
||||
kanban = "done"
|
||||
}
|
||||
res, _ := s.db.Exec(`UPDATE thoughts SET is_completed = ?, kanban_status = ?, alarm_sent = CASE WHEN ? = 0 THEN 0 ELSE alarm_sent END
|
||||
WHERE id = ? AND user_id = ? AND is_completed != ?`, doneInt, kanban, doneInt, taskID, userID, doneInt)
|
||||
res, _ := s.db.Exec(`UPDATE thoughts SET is_completed = ?, kanban_status = ?, alarm_sent = CASE WHEN ? = 0 THEN 0 ELSE alarm_sent END,
|
||||
alarm_failures = CASE WHEN ? = 0 THEN 0 ELSE alarm_failures END
|
||||
WHERE id = ? AND user_id = ? AND is_completed != ?`, doneInt, kanban, doneInt, doneInt, taskID, userID, doneInt)
|
||||
if checked && res != nil {
|
||||
if n, _ := res.RowsAffected(); n > 0 {
|
||||
s.spawnNextOccurrence(taskID)
|
||||
@@ -634,6 +637,20 @@ func (s *Store) GetDueTasksForAlarm() ([]DueTaskAlarm, error) {
|
||||
}
|
||||
|
||||
func (s *Store) MarkTaskAlarmSent(taskID string) error {
|
||||
_, err := s.db.Exec(`UPDATE thoughts SET alarm_sent = 1 WHERE id = ?`, taskID)
|
||||
_, err := s.db.Exec(`UPDATE thoughts SET alarm_sent = 1, alarm_failures = 0 WHERE id = ?`, taskID)
|
||||
return err
|
||||
}
|
||||
|
||||
// MaxAlarmFailures: after this many runs in which no channel delivered, the alarm is given up instead of
|
||||
// being retried every minute. A new due date or reopening the task starts over.
|
||||
const MaxAlarmFailures = 3
|
||||
|
||||
// RecordTaskAlarmFailure counts a run without any successful channel and reports whether the alarm was
|
||||
// given up now.
|
||||
func (s *Store) RecordTaskAlarmFailure(taskID string) (gaveUp bool, err error) {
|
||||
var failures int
|
||||
err = s.db.QueryRow(`UPDATE thoughts SET alarm_failures = alarm_failures + 1,
|
||||
alarm_sent = CASE WHEN alarm_failures + 1 >= ? THEN 1 ELSE alarm_sent END
|
||||
WHERE id = ? RETURNING alarm_failures`, MaxAlarmFailures, taskID).Scan(&failures)
|
||||
return err == nil && failures >= MaxAlarmFailures, err
|
||||
}
|
||||
Reference in new issue
Block a user