docs(mm-transport): дефект #2 — heartbeat/status улетали в корень канала (b738a3dea3)

This commit is contained in:
Alexey Martemyanov committed 2026-09-30 13:34:18 +06:00
1 parent 913282b667
commit 9d54f0dcba
1 file changed
+84 -1
@@ -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` требует ШЕСТЬ флагов, не один.**