diff --git a/personal/tech/hermes-whale-mattermost-transport.md b/personal/tech/hermes-whale-mattermost-transport.md index 44a98981..9331e89f 100755 --- a/personal/tech/hermes-whale-mattermost-transport.md +++ b/personal/tech/hermes-whale-mattermost-transport.md @@ -4,7 +4,7 @@ created: '2026-09-29' updated: '2026-09-30' type: tech namespace: personal -status: "транспорт+telegram+pairing+toolsets+рестарт ✅; \U0001F534 фикс root-post thread_id 2026-09-30 (916f2c7e4f) — ЖДЁТ РЕСТАРТА gateway; \U0001F534 открыт инцидент — бот не в канале hermes-maintenance" +status: "транспорт+telegram+pairing+toolsets+фиксы тредов (916f2c7e4f, b738a3dea3) — ЖДУТ РЕСТАРТА gateway; \U0001F534 открыт инцидент — бот не в канале hermes-maintenance" tags: - hermes - mattermost @@ -16,6 +16,7 @@ tags: - hermes-maintenance - incident - thread-session-split + - heartbeat-thread-nesting related: - '[[personal/projects/balda/mattermost-kraken-deploy.md]]' - '[[personal/tech/kraken-host-resource-limits.md]]' @@ -401,6 +402,88 @@ thread_id = post.get("root_id") or post_id or None 456+ ошибок на чистом дереве — конфликт фикстур/состояния при параллельном сборе. Гонять **только целевые файлы**, иначе результат нечитаем. +## 🔴 Дефект #2 2026-09-30 (ЗАКРЫТ): heartbeat/status улетали в корень канала + +**Симптом Alex:** «из треда все равно ушло пара сообщений в общий канал». + +**Факты из MM API** (`GET /api/v4/channels/{id}/posts`) — 4 поста с `root=ROOT`: + +``` +13:26:04 ⏳ Working — 6 min — iteration 45/90, terminal +13:28:05 is fine, docker active, 27 containers. Bridge exited (2) 3 days ago. L +13:30:25 is valid. Let me start the bridge and watch what happens. +13:30:44 request for udp:reverse:0` at 15:30:32. Let me immediately re-test kr +``` + +Первый — heartbeat, остальные — status-callback. Все — **синтетические отправки**. + +### Причина + +`gateway/run.py` (`_run_agent`), было: + +```python +_progress_thread_id = source.thread_id or ( + event_message_id if source.platform == Platform.SLACK else None +) +... +_status_thread_metadata = self._thread_metadata_for_source(source, event_message_id) if _progress_thread_id else None +``` + +**Mattermost не был в fallback** → `_progress_thread_id = None` → гейт +`if _progress_thread_id` **не вызывал** `_thread_metadata_for_source`. + +⚠️ **Ключевое:** ветка Mattermost-fallback **уже была +написана** в `_thread_metadata_for_target`, но была **недостижима**: + +```python +if thread_id is None: + if platform == Platform.MATTERMOST and reply_to_message_id: + return {"thread_id": str(reply_to_message_id)} # <-- не доходило + return None +``` + +Аналогичный питфолл был уже починен для тред-постов (коммит `f7dbceae8e` +— «nest tool progress, status and heartbeat posts in thread»), но остался **корневой случай**. + +### Фикс (коммит `b738a3dea3`) + +Выражение вынесено в именованную функцию уровня модуля +`gateway.run._progress_thread_id_for` + Mattermost добавлен в fallback, зеркально Slack: + +```python +def _progress_thread_id_for(platform, thread_id, event_message_id): + if thread_id: + return thread_id + if platform in (Platform.SLACK, Platform.MATTERMOST): + return event_message_id + return None +``` + +Telegram и Discord остаются `None` — они роутятся по реальным id топиков. + +### 📌 Питфолл: тест-копия выражения — фальшивое покрытие + +Первая версия тестов дублировала выражение своей копией — +**11 passed на НЕФИКШЕНОМ коде**. Не ловили баг вообще. +Правильно: вынести логику в функцию и **импортировать её в тест**. +Тест, который переписывает прод-логику своими словами, **ничего не гарантирует**. + +После рефактора — **проверено откатом**: на нефикшеном helper падает +`assert None == {'thread_id': 'root_post_abc'}` — ровно наш баг. + +### Тесты + +`tests/gateway/test_mattermost_synthetic_thread_metadata.py` — 12 тестов. + +| Прогон | Результат | +|---|---| +| На нефикшеном коде | 2 failed — баг воспроизведён | +| Целевой срез (6 файлов) | 215 passed | + +⚠️ `edit_message` тут **ни при чём** — он патчит уже существующий пост и `root_id` +не задаёт. Обрывки текста в списке — это ОТДЕЛЬНЫЕ посты +`status_callback`, а не части одного сообщения. + ## 🔴 Питфоллы, найденные на месте 1. **`mmctl user convert --bot` требует ШЕСТЬ флагов, не один.**