ТЕХНИЧЕСКИЙ ПРИМЕР / WEBHOOK

Повторное событие.
Одно действие.

Сервис уже переместил папку, а подтверждение не сохранилось. Посмотрите, как проверка фактического состояния помогает завершить обработку без второго перемещения.

Учебная симуляция. Здесь показан сохранённый прогон Python-механизма с моделью внешнего диска. Кнопки переключают его состояния. Реальные CRM и API не подключены; клиентские данные не читаются и ничего не отправляется. Это технический образец, не выполненный заказ.

От события до восстановления

Сохранённый прогон · 09.10.2026
01 / ИСХОДНЫЙ WEBHOOK

Создаём папку сделки

Сначала журнал сохраняет намерение. Модель диска создаёт папку, затем журнал подтверждает результат.

event_id: delivery-1
deal_id: deal-101
revision: 1 · phase: active

Одно создание. Папка находится в «Дела в работе».

02 / ПОВТОРНАЯ ДОСТАВКА

То же событие пришло ещё раз

Идентификатор доставки уже есть в журнале. Повтор возвращает статус duplicate; нового создания нет.

event_id: delivery-1
deal_id: deal-101
revision: 1 · phase: active

Папка и число действий не меняются.

03 / ЭФФЕКТ ЕСТЬ, ПОДТВЕРЖДЕНИЯ НЕТ

Сбой после перемещения

Папка уже перемещена в «Завершенные дела». Симулированный сбой происходит до записи подтверждения в SQLite.

event_id: delivery-2
deal_id: deal-101
revision: 2 · phase: completed

Намерение — pending. Сохранённый подтверждённый путь ещё старый; он не доказывает текущее состояние диска.

04 / НОВЫЙ ОБРАБОТЧИК

Проверяем, затем подтверждаем

recover() находит папку по устойчивому deal_id. Она уже в нужном месте: обработчик подтверждает намерение без повторного перемещения.

recover()
pending → applied
effect: none

Итог прогона: одно создание и одно перемещение.

05 / СОБЫТИЕ НЕ ПО ПОРЯДКУ

Старая версия не откатывает результат

Новая доставка содержит версию 1, а сохранённая версия сделки — 2. Обработчик возвращает stale.

event_id: late-delivery
deal_id: deal-101
revision: 1 · phase: active

Завершённая папка остаётся на месте. Версии должны приходить из доверенного источника; порядок доставки их не заменяет.

06 / ДРУГАЯ СДЕЛКА, ТО ЖЕ ИМЯ

Конфликт требует решения

Другая сделка пытается занять уже закреплённое название. Статус conflict останавливает операцию: слияния и перезаписи нет.

event_id: delivery-other
deal_id: deal-202
revision: 1 · phase: active

Папка первой сделки сохранена. Причину конфликта нужно устранить отдельно.

ГРАНИЦЫ ПРИМЕРА

Что проверить в настоящей интеграции

Найти результат после сбоя

В модели папка ищется по ID сделки. Настоящий API должен дать устойчивый ID, метаданные владения или подходящий ключ идемпотентности. Одного названия недостаточно.

Получить актуальную версию

Нужны доверенное состояние сделки и правило порядка версий. Переименования и повторное открытие сделки в этот образец не входят.

Проверить восстановление

Отдельно проверяются хранение журнала, реальный рестарт, таймауты, повторы и ограничения API. Общей транзакции между БД и диском нет.

Основание и ограничения проверки

Оригинальный локальный Python-образец проверен 17 тестами: повторы, одновременные доставки, ошибки до и после эффекта, старые версии и конфликты. Страница показывает шесть состояний из его demo.py. Python-код в браузере не исполняется.

SQLite хранит намерения; внешний диск существует только как объект в памяти. При восстановлении в прогоне объект диска сохраняется, а обработчик создаётся заново. Завершение Python-процесса теряет модель диска. Симулированный сбой не проверяет аварийное выключение ОС и не доказывает работу реального коннектора.

Статус duplicate не выполняет постоянный аудит диска. Доставка «ровно один раз» через границу БД/API не гарантируется; результат после потерянного ответа требует проверки.