fix(reminders): deterministic tie-break in resolveSchedule on equal effective_from

ORDER BY effective_from DESC alone picks an engine/plan-dependent winner
when two reminder_schedule rows for the same rule_kind share an
effective_from (a same-day cadence correction under rule 3's
new-row-never-edit makes this likely). Append id DESC — UUIDv7 ids are
time-ordered, so the latest insert wins deterministically on SQLite and
Postgres alike.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
feat/client-detail-redesign
Thomas Joise 5 days ago
parent 5865aafe06
commit 56b00b60c6

@ -157,7 +157,7 @@ export function resolveSchedule(db: DB, ruleKind: ScheduleRuleKind, today: strin
const row = db.prepare(
`SELECT day_offsets, subject, body FROM reminder_schedule
WHERE rule_kind=? AND effective_from <= ? AND (effective_to IS NULL OR effective_to > ?)
ORDER BY effective_from DESC LIMIT 1`,
ORDER BY effective_from DESC, id DESC LIMIT 1`, // id DESC breaks effective_from ties deterministically (UUIDv7 is time-ordered → latest insert wins on any engine)
).get(ruleKind, today, today) as { day_offsets: string; subject: string | null; body: string | null } | undefined
if (row !== undefined) {
const dayOffsets = parseDayOffsets(row.day_offsets)

@ -44,6 +44,19 @@ describe('resolveSchedule', () => {
expect(resolveSchedule(db, 'quote_followup', '2025-12-31').source).toBe('default')
})
it('tied effective_from resolves deterministically: highest id wins on any engine', () => {
const db = openDb(':memory:')
const ins = (id: string, offsets: string) => db.prepare(
`INSERT INTO reminder_schedule (id, rule_kind, effective_from, effective_to, day_offsets, subject, body)
VALUES (?, 'quote_followup', '2026-01-01', NULL, ?, NULL, NULL)`,
).run(id, offsets)
// Insert the higher id FIRST so plain scan order would pick the other row —
// the tiebreaker (id DESC; UUIDv7 ids are time-ordered) must decide, not the plan.
ins('zzzz-high', '2,4')
ins('aaaa-low', '5,10')
expect(resolveSchedule(db, 'quote_followup', '2026-07-17').dayOffsets).toEqual([2, 4])
})
it('effective_to closes the window: active requires effective_to > today', () => {
const db = openDb(':memory:')
insertSchedule(db, { ruleKind: 'quote_followup', from: '2026-01-01', to: '2026-07-01', offsets: '1,2' })

Loading…
Cancel
Save