03 — Implement leased delivery workers and recovery sweeps
## Objective Recover committed effects despite absent/failed enqueue, duplicate jobs, worker death or partial success, without resubmitting a voting command. ## Code locations Existing: app/models/voting_engine/outbox_event.rb; config/sidekiq.yml; app/jobs/application_job.rb. Reuse recovery principles from app/jobs/kaukus_teams/reconcile_sessions_job.rb and generation-bound session work. Proposed: app/services/voting_engine/outbox/deliver.rb; app/jobs/voting_engine/{deliver_outbox_job,reconcile_outbox_job}.rb. ## Changes - Job arguments are persistent delivery IDs. PostgreSQL rows and next_attempt_at are retry authority; Redis is transport only. - Atomically claim due work in a short transaction; increment attempts and persist token/lease. Commit before external I/O; never hold canonical MeetingLock across network calls. Complete/fail only with matching ownership token. Recover expired claims. - Serialize each order-sensitive meeting lane using delivery-specific exclusion, e.g. PostgreSQL advisory lock held on an explicitly owned connection and released in ensure. Order events by event ID, not timestamp/run sequence. UI progresses independently of failed integrations; separate meetings do not block one another. Lease renewal/timeout handling must prevent concurrent stale work, and sink idempotency still covers ambiguous network outcomes. - Add after-create-commit wakeup as an optimization, rescuing/reporting raised or unsuccessful enqueue results without turning an accepted command into HTTP failure. - Add bounded reconciliation batches, proposed every 15 seconds in config/sidekiq.yml. Sweep due/expired work even when no originating callback ran. Concurrent sweepers are safe. - Proposed retry defaults: 5 seconds exponential backoff capped at 5 minutes, bounded network timeouts/leases. Transient failures remain retryable with alerts; unknown payloads and ambiguous non-idempotent outcomes are blocked, never silently delivered or blindly replayed. - Use already consumed default and streaming queues; no unconfigured new queue. Do not treat child-job enqueue as delivery completion: carry the same delivery identity through the worker that performs the effect, or acknowledge only a verified durable downstream recovery handoff. - Missing/deleted row is a harmless stale job. Completed effects never rerun on ordinary duplicate scheduling. ## Acceptance and tests Proposed spec/jobs/voting_engine/{deliver_outbox_job,reconcile_outbox_job}_spec.rb and spec/services/voting_engine/outbox/concurrency_spec.rb. Cover duplicate jobs/sweepers, concurrent connections, expired lease, stale completion token, partial success, backoff/time travel, no callback, Redis enqueue error/false result, independent lanes and meeting progress. Do not globally run every enqueued job because existing jobs self-reschedule. Run bundle exec rspec spec/jobs/voting_engine spec/services/voting_engine/outbox/concurrency_spec.rb; RuboCop changed Ruby. ## Depends on https://task.matda.me/tasks/tsk_2qravp6n9b — contract. https://task.matda.me/tasks/tsk_5t6us3yxjk — durable ledger. ## Questions None blocking local implementation. Proposed retry/sweep defaults must be measured in the rollout rehearsal; external unsafe retry rules come from provider-specific contracts, not an arbitrary retry count.
Checklist
0 of 0 completeNo checklist items.
- Area
- Kaukus
- Kind
- Feature
- Priority
- Medium
- Task ID
tsk_beu5nmfqfj- Updated