The broker only delivers pending messages, so anything in flight when
the process died was never delivered again and its Job row read
running forever. At boot, before any consumer polls, interrupted jobs
are failed visibly (a half-run deploy or restore is not safe to repeat
blind) and every other stranded message is redelivered or, when out of
attempts, dead-lettered.
- Add pluggable queue bus with SQLAlchemy backend (Redis-ready interface)
- Models: QueueGroup, Queue, QueueMessage with retries, visibility timeout,
delayed delivery, priority ordering, and dead-letter handling
- REST API at /api/v1/queue for groups, queues, messages, and stats
- Plugin SDK: from app.plugins_sdk import queue
- Operations UI page with group/queue/message management and send-message form
- Migrate webhook deliveries from raw threading.Thread to the queue bus
- Add webhook consumer with retry/dead-letter support
- Migration 022_queue_bus and backend tests