Rei bb7958886d
fix(mcp): fence notification claims on SQLite (#7859)
* fix(mcp): fence notification claims on SQLite

claim_notification_work() selects due generations with FOR UPDATE SKIP LOCKED, which SQLite ignores. Two workers could read the same due generation before either writes, both returned it with different lease tokens, and a stale flush could overwrite the winning owner's token or phase.

On SQLite, claim each candidate with a conditional UPDATE fenced on the event version, notification status, dispatch version and the absent-or-expired notification lease the read saw, so a peer claim or a newer recorded event wins and the losing claim has no side effects. Pin the dispatch version on the empty-snapshot consumption fence too. PostgreSQL keeps its existing row-locking path.

Coverage: a paused claim that loses a race to a second worker returns no row and leaves the winner's owner, token and phase stored.

* fix(mcp): honor deferred notification backoff in SQLite claims

claim_notification_work() fenced the observed event version, phase, dispatch version and lease, but not next_notification_at. A peer can claim a due row, then release the claim (release_notification_lease) or defer a dispatched one (defer_dispatched_notification) without changing phase, moving the next attempt into the future and clearing the lease. A paused claim still matched and returned the notification immediately, bypassing retry/defer backoff.

Fence the claim on the observed next_notification_at value too, and cover the interleaving with a regression test.

---------

Co-authored-by: REI-330 <173389910+REI-330@users.noreply.github.com>
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-11-05 14:19:05 +08:00
..