fix: code-review findings on overnight-jul5

- digest queue no longer dropped on failed dispatch (retry next tick)
- collapsed reminders marked fired/rescheduled only after digest delivers
- /tools step-up gate skipped when WebAuthn is not configured (was 403 forever)
- passkey credential store rolls back memory on persist failure
- auth_test fake updated for TickTrace (branch build break)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ASstMtsZWLSRcD1Tq8T68Q
This commit is contained in:
kami
2026-07-05 17:50:21 +04:00
parent 5c34fb14f9
commit 8d823000d1
10 changed files with 96 additions and 40 deletions
+10 -20
View File
@@ -12,7 +12,6 @@ import (
"context"
"encoding/json"
"fmt"
"log"
"time"
"github.com/kami/maven/internal/store"
@@ -143,10 +142,7 @@ func (g *Gatherer) GatherState(ctx context.Context, now time.Time) (State, []sto
if err != nil {
return State{}, nil, err
}
due, err = collapseReminders(ctx, g.store, due)
if err != nil {
return State{}, nil, err
}
due = collapseReminders(due)
s := State{
Now: now,
@@ -204,12 +200,14 @@ func readFact(ctx context.Context, s *store.Store, key string) (store.Fact, bool
// collapseReminders — when multiple reminders are due at once (e.g. after
// the daemon was offline), collapse them into a single digest reminder to
// avoid a burst of individual notifications. All original reminders are
// marked as fired; the synthetic digest (ID=0) is what gets dispatched.
// avoid a burst of individual notifications. The synthetic digest (ID=0)
// carries the originals in Collapsed; the dispatcher completes them (mark
// fired / reschedule) only after the digest actually delivers, preserving
// the "failed send leaves the reminder pending" invariant.
// When 0 or 1 reminders are due, returns them unchanged.
func collapseReminders(ctx context.Context, s *store.Store, due []store.Reminder) ([]store.Reminder, error) {
func collapseReminders(due []store.Reminder) []store.Reminder {
if len(due) <= 1 {
return due, nil
return due
}
// Build a summary payload.
var items []string
@@ -235,21 +233,13 @@ func collapseReminders(ctx context.Context, s *store.Store, due []store.Reminder
"items": items,
})
// Mark originals as fired so they won't re-fire on next tick.
for _, r := range due {
if err := s.MarkReminder(ctx, r.ID, "fired"); err != nil {
// Log and continue — one failure shouldn't block the digest.
// The next tick will re-gather and re-attempt.
log.Printf("gather: mark reminder %d fired: %v", r.ID, err)
}
}
// Return a single synthetic digest reminder. ID=0 signals "digest" to
// the dispatcher (which skips MarkReminder for ID=0).
// the dispatcher, which completes the Collapsed originals on success.
return []store.Reminder{{
ID: 0,
FireTs: earliest,
Payload: string(digestPayload),
Status: "pending",
}}, nil
Collapsed: due,
}}
}