store: the first repeat-til-ack send, and a digest entry that expired unswept (V-617)
LastSent scanned MAX(sent_at) over an empty ack_sends into a bare int64, so the ordinary "nothing sent yet" case came back as a scan error rather than the zero time its doc promises. ack_sends is written only by MarkSent, and MarkSent runs only after a repeat has gone out, so every rule's FIRST repeat read an empty table — and RepeatUnacked aborts its whole sweep on that error. The repeat-til-ack loop could never take its first step. Scans into a NullInt64, the same way OldestPendingTelegram already does two files over. EnqueueDigestEntry deduped against any row still marked pending, including one already past its expires_ts. The tick enqueues before it sweeps, so a suppressed nudge arriving on the tick after an expiry was told deduped=true against an entry PendingDigestEntries will never hand back: the caller drops the phrasing it just paid the LLM for and nothing reaches the bundle. The dedupe now carries the same expiry test the read side does. Its lookup also stops treating a real read failure as "nothing there".
This commit is contained in:
@@ -200,3 +200,46 @@ func TestDigestEntryDrainMarksDrainedNotDeleted(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestDigestEnqueueDoesNotDedupeAgainstAnExpiredEntry — an entry past its
|
||||
// expires_ts is still status='pending' until the sweep runs, and the tick
|
||||
// enqueues before it sweeps. Deduping against one reports deduped=true for a
|
||||
// row PendingDigestEntries will never hand back, so the suppressed nudge is
|
||||
// dropped instead of held: the entry says the thing was recorded when nothing
|
||||
// was.
|
||||
func TestDigestEnqueueDoesNotDedupeAgainstAnExpiredEntry(t *testing.T) {
|
||||
s := newTestStore(t)
|
||||
ctx := context.Background()
|
||||
created := time.Now()
|
||||
expiresAt := created.Add(time.Hour)
|
||||
|
||||
first, deduped, err := s.EnqueueDigestEntry(ctx, "break", 2, "ты долго не отдыхала", created, expiresAt)
|
||||
if err != nil {
|
||||
t.Fatalf("enqueue: %v", err)
|
||||
}
|
||||
if deduped {
|
||||
t.Fatal("first enqueue must not report deduped")
|
||||
}
|
||||
|
||||
// A tick after the expiry, with the sweep not yet run: the same suppressed
|
||||
// nudge comes round again and must be recorded afresh.
|
||||
after := expiresAt.Add(time.Minute)
|
||||
second, deduped, err := s.EnqueueDigestEntry(ctx, "break", 2, "ты долго не отдыхала", after, after.Add(time.Hour))
|
||||
if err != nil {
|
||||
t.Fatalf("re-enqueue: %v", err)
|
||||
}
|
||||
if deduped {
|
||||
t.Fatal("an expired entry must not swallow a fresh one")
|
||||
}
|
||||
if second == first {
|
||||
t.Fatalf("want a new row, got the expired one back: id=%d", second)
|
||||
}
|
||||
|
||||
entries, err := s.PendingDigestEntries(ctx, after)
|
||||
if err != nil {
|
||||
t.Fatalf("pending: %v", err)
|
||||
}
|
||||
if len(entries) != 1 || entries[0].ID != second {
|
||||
t.Fatalf("want the fresh entry %d pending, got %+v", second, entries)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user