Files
iistwin/migrations/0086_task_message_idempotency.sql
Ильяс Султанов af4850bd6d fix(chat): идемпотентность сообщений по clientMessageId — защита от дублей при повторной отправке
Первопричина дублей (3 одинаковых сообщения в задаче 1988): живой POST сорвался
→ сообщение попало в общую IndexedDB-очередь → background-sync разослал
TRIGGER_SYNC всем вкладкам → каждая вкладка реплейнула одну и ту же запись
(мьютекс _syncLock модульный, кросс-вкладочной координации нет). Гонка
воспроизведена тестом tests/offline-queue-multitab-race.test.tsx (1 запись →
2 POST для двух вкладок).

Вариант C — серверная идемпотентность:
- task_messages.client_message_id + partial unique index (миграция 0086);
- sendTaskMessage: повтор с тем же clientMessageId возвращает существующее
  сообщение БЕЗ insert и БЕЗ side-эффектов (уведомления/SSE/вебхуки не дублируются);
  гонка insert'ов ловится по 23505 → fallback на select;
- клиент TaskChat шлёт crypto.randomUUID() в каждом сообщении; при офлайн-постановке
  тело с ключом сохраняется, все реплеи идут с одним ключом;
- attachment-реплей: clientMessageId = id записи очереди (стабилен между реплеями);
- юнит-тесты tests/task-message-idempotency.test.ts (3: быстрый путь, гонка 23505,
  проброс прочих ошибок).
2026-09-29 17:40:47 +03:00

12 lines
801 B
SQL

-- Идемпотентность сообщений чата задач: клиентский ключ (uuid).
-- Защита от дублей при повторной отправке одного сообщения:
-- multi-tab replay офлайн-очереди (каждая вкладка реплеит независимо),
-- ретраи после сетевых сбоев, двойные клики.
-- Partial unique index: NULL-значения (старые сообщения, боты, автоматизации) не конфликтуют.
ALTER TABLE task_messages ADD COLUMN IF NOT EXISTS client_message_id varchar(64);
CREATE UNIQUE INDEX IF NOT EXISTS task_messages_client_message_id_unique
ON task_messages (client_message_id)
WHERE client_message_id IS NOT NULL;