Make delivery and integration failures explicit
Persist reminder presentations and retry state, atomically complete collapsed deliveries, fall back across away reaches, and block permanent failures visibly (V-715, V-678). Fail closed when enabled integrations lack credentials and keep remote arms explicitly dark (V-691). Give mavweb one sanitized, request-correlated error contract (V-689). Owner explicitly requested direct commits to master.
This commit is contained in:
+18
-15
@@ -28,14 +28,16 @@ const (
|
||||
// send happens, so a crash between "sent externally" and "recorded" leaves a
|
||||
// trace instead of silence. kind is "nudge" or "reminder"; rule is set for
|
||||
// nudges, reminderID for reminders (the other left at its zero value).
|
||||
// deliveryGroup is the exact persisted reminder occurrence or collapsed
|
||||
// bundle; it is empty for nudges and legacy reminder attempts.
|
||||
// bodyHash is an opaque caller-computed key (e.g. sha256 of channel+body) —
|
||||
// stored for post-crash operator triage, not enforced as a uniqueness
|
||||
// constraint (a rule/reminder legitimately re-sends across ticks).
|
||||
func (s *Store) BeginDeliveryAttempt(ctx context.Context, kind, rule string, reminderID int64, channel, bodyHash string, now time.Time) (int64, error) {
|
||||
func (s *Store) BeginDeliveryAttempt(ctx context.Context, kind, rule string, reminderID int64, deliveryGroup, channel, bodyHash string, now time.Time) (int64, error) {
|
||||
res, err := s.db.ExecContext(ctx,
|
||||
`INSERT INTO delivery_attempts (kind, rule, reminder_id, channel, body_hash, status, created_ts)
|
||||
VALUES (?, ?, ?, ?, ?, 'pending', ?)`,
|
||||
kind, rule, reminderID, channel, bodyHash, now.UnixMilli())
|
||||
`INSERT INTO delivery_attempts (kind, rule, reminder_id, delivery_group, channel, body_hash, status, created_ts)
|
||||
VALUES (?, ?, ?, ?, ?, ?, 'pending', ?)`,
|
||||
kind, rule, reminderID, deliveryGroup, channel, bodyHash, now.UnixMilli())
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("begin delivery attempt: %w", err)
|
||||
}
|
||||
@@ -90,15 +92,16 @@ func (s *Store) ReconcileStaleDeliveryAttempts(ctx context.Context, now time.Tim
|
||||
|
||||
// DeliveryAttempt — one row of the outbox, as a reader sees it.
|
||||
type DeliveryAttempt struct {
|
||||
ID int64
|
||||
Kind string // nudge|reminder
|
||||
Rule string // set for nudges
|
||||
ReminderID int64 // set for reminders
|
||||
Channel string
|
||||
Status string // one of the Delivery* constants
|
||||
Created time.Time
|
||||
Completed time.Time // zero while pending
|
||||
HasComplete bool
|
||||
ID int64
|
||||
Kind string // nudge|reminder
|
||||
Rule string // set for nudges
|
||||
ReminderID int64 // set for reminders
|
||||
DeliveryGroup string // exact reminder occurrence/bundle; empty for nudges and legacy rows
|
||||
Channel string
|
||||
Status string // one of the Delivery* constants
|
||||
Created time.Time
|
||||
Completed time.Time // zero while pending
|
||||
HasComplete bool
|
||||
}
|
||||
|
||||
// ListDeliveryAttempts returns recent attempts, newest first. An empty status
|
||||
@@ -117,7 +120,7 @@ func (s *Store) ListDeliveryAttempts(ctx context.Context, status string, limit i
|
||||
if limit <= 0 {
|
||||
limit = 50
|
||||
}
|
||||
q := `SELECT id, kind, rule, reminder_id, channel, status, created_ts, completed_ts
|
||||
q := `SELECT id, kind, rule, reminder_id, delivery_group, channel, status, created_ts, completed_ts
|
||||
FROM delivery_attempts`
|
||||
args := []any{}
|
||||
if status != "" {
|
||||
@@ -138,7 +141,7 @@ func (s *Store) ListDeliveryAttempts(ctx context.Context, status string, limit i
|
||||
var a DeliveryAttempt
|
||||
var created int64
|
||||
var completed *int64
|
||||
if err := rows.Scan(&a.ID, &a.Kind, &a.Rule, &a.ReminderID, &a.Channel, &a.Status, &created, &completed); err != nil {
|
||||
if err := rows.Scan(&a.ID, &a.Kind, &a.Rule, &a.ReminderID, &a.DeliveryGroup, &a.Channel, &a.Status, &created, &completed); err != nil {
|
||||
return nil, fmt.Errorf("list delivery attempts: scan: %w", err)
|
||||
}
|
||||
a.Created = time.UnixMilli(created)
|
||||
|
||||
Reference in New Issue
Block a user