Two reviewer-identified defects in the yeeke return sync:
- acquire() wrote LeaseExpiresAt but nothing ever read it back, so a
crash/restart mid-run left a permanent active_slot=1 row blocking every
future sync. acquire() now runs a conditional takeover UPDATE first
(status=running AND lease_expires_at <= now -> failed, active_slot
cleared, error_message recorded), following the lease-with-expiry-
takeover idiom in order_writeback_worker.go. The takeover UPDATE is a
single statement so it is atomic per-row, and the
ux_yeeke_sync_run_active_slot unique index arbitrates a concurrent
takeover race the same way it already arbitrates two brand-new runs.
- itemKey() always appended the positional index, so a package whose items
come back in a different order on a later sync got new keys and
duplicate rows. The index fallback is now used only when i.ID, i.ItemID
and i.VariationID are all empty.
Added tests: TestStaleLeaseIsTakenOverOnNextAcquire,
TestValidLeaseIsNotTakenOver, TestConcurrentTakeoverExactlyOneWins,
TestItemKeyStableAcrossReorder,
TestItemKeyIndexFallbackForItemsLackingAllIDs,
TestItemKeyDistinctVariationsOfSameItemID.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTDbDcwbDw1TSAcE6wfh2F