11 KiB
План: доделать сквозной путь @balda → ответ в Mattermost
Создан 2026-09-26. Статус: в работе. Часть personal/projects/balda/balda-kraken-mm-deploy (Фаза 3). Команда Alex: «Доделывай всё. И уведомления в приложения неактивны тоже поправь.»
Root cause (найден диагностикой, образ 0.1.4)
Две независимые причины, обе подтверждены логами:
Причина A — bad_seq: MM отбрасывал пинг и переставал пушить события
Диагностический лог каждого кадра показал:
{"status":"FAIL","error":{"id":"api.web_socket_router.bad_seq.app_error",
"message":"Invalid sequence for WebSocket message."}}
раз в 30 секунд, ровно на каждый пинг.
Механизм: Mattermost требует монотонный seq в каждом исходящем фрейме от клиента.
Балда слала пинг как {"action":"ping"} — без seq. MM помечал соединение невалидным
и прекращал доставлять события posted, при этом не разрывая сокет. Отсюда
«connected, но тишина»: лог показывал здоровое соединение, а события не шли.
⚙️ Усугублялось тем, что дельта 14 убрала authentication_challenge вместе с его seq:1 —
после этого ни один исходящий фрейм не нёс seq.
Фикс (дельта tools/deltas-mattermost-16-seq.json):
- поле
seq int64вIngress; nextSeq()— монотонный счётчик под мьютексом, начинается с 1;writeEvent(conn, action, data)— единственный способ слать клиентский фрейм, всегда соseq;- сброс
seq = 0при новом соединении; - пинг идёт через
writeEvent(conn, websocketActionPing, nil); - фрейм со
"status":"FAIL"логируется как warn, не игнорируется молча.
✅ Проверено: после деплоя bad_seq — 0 вхождений, в логах pong seq_reply:1, 2, 3.
Причина B — Processor == nil: посты принимаются и никуда не идут
После фикса A событие дошло до инбаунда:
"mattermost websocket event decoded" event=posted
"mattermost posted event accepted" post_id=7qnmcp9sm3biuen7kfy5jhxwwh
"mattermost post accepted for processing" text="скажи одно слово"
…и дальше тишина. В processPosted (ingress.go:476):
if s.processor == nil {
return // ← сюда и попадаем: InboundProcessor не предоставлен
}
Механизм: в mattermostfx/ingress.go поле объявлено как
Processor mattermost.InboundProcessor \optional:"true"`, но **никто в DI-графе его не предоставляет**. В handlersfx/есть толькоzulip_ingress.goиtelegram_ingress.go` —
MM-хендлера нет. Ингресс работает как глухой приёмник: событие принимается, логируется и
отбрасывается.
⚠️ optional:"true" сделал этот разрыв невидимым: fx не падает при отсутствии зависимости,
go build/go vet молчат, тестов на MM-транспорт нет (no test files).
Что уже готово в проекте (проверено чтением кода)
| Компонент | Файл | Состояние |
|---|---|---|
NormalizeInbound для MM |
channel/mattermost/inbound.go:114 |
✅ есть, 26 строк, полный |
LocatorForPost |
channel/mattermost/inbound.go:146 |
✅ есть (DM / channel / thread-root) |
IsDirectChannelType |
channel/mattermost/inbound.go |
✅ есть |
MentionsBot / StripMention / IsBotEcho |
channel/mattermost/inbound.go |
✅ есть |
Транспорт-нейтральный ingressapp.Service |
ingressapp/service.go:160 |
✅ Process(ctx, NormalizedInbound) — authorize → prepare session → dispatch |
| Образец хендлера | handlersfx/telegram_ingress.go (695 стр.) |
✅ использует тот же actortransport.Dispatcher |
Интерфейс InboundProcessor |
channel/mattermost/inbound.go:44 |
✅ объявлен |
Реализация InboundProcessor для MM |
— | 🔴 ОТСУТСТВУЕТ |
📌 Ключевой вывод: MM-хендлер — это тонкая обёртка, не 1200 строк. Вся логика
авторизации/сессий/dispatch уже вынесена в ingressapp.Service. Апстрим такого хендлера не
содержит (git ls-tree upstream/main — только telegram* и zulip_ingress.go), писать надо самим.
План работ
Шаг 1 — Написать handlersfx/mattermost_ingress.go ⬜
По образцу telegram_ingress.go, но проще (у MM нет форум-топиков, callback-кнопок и
question-reply — их можно не тащить в первой версии).
Обязательный минимум:
mattermostInboundHandlerParams(fx.In) сoptional:"true"где нужно:OwnerStore,CollaboratorStore,SessionManager,Dispatcher actortransport.Dispatcher,AuthToken string \name:"balda_auth_token"`,BaldaProviderID string `name:"balda_provider"`,Logger`.newMattermostInboundHandler(params) mattermost.InboundProcessor— конструктор.ProcessInbound(ctx, msg mattermost.InboundMessage) (turncmd.InboundSettlement, error):- собрать
ingressapp.InboundContextизmsg.Locator/msg.SenderID/msg.Direct; Authorize→Prepare→ingressapp.Service.Process(ctx, normalized);- вернуть
result.Settlement.
- собрать
Serviceсобирается черезingressapp.New(...)с тремя функциями-адаптерами (AuthorizerFunc,SessionPreparerFunc,DispatcherFunc).
⚠️ Не тащить в ingress бизнес-логику — AGENTS.md §«Ingress purity»: только parse / auth / session-check / publish.
Шаг 2 — Подключить в DI ⬜
handlersfx/module.go: добавить newMattermostInboundHandler в fx.Provide с
fx.As(new(mattermost.InboundProcessor)) — тогда optional:"true" в mattermostfx получит
реальную зависимость.
⚠️ go-arch-lint: handlersfx уже имеет право зависимости; новых компонентов не требуется.
⚠️ Проверить, что fx.Annotate не конфликтует с fx.In-структурой (см.
personal/tech/balda-go-fx-pitfalls — fx.In нельзя аннотировать fx.ParamTags).
Шаг 3 — Сборка и деплой ⬜
cd ~/Developer/balda
go build ./... && go vet ./internal/apps/balda/...
go tool go-arch-lint check --project-path .
docker buildx build --builder arm64builder --platform linux/arm64 \
-f Dockerfile.release -t balda-mm:0.1.5 --load .
docker save balda-mm:0.1.5 | zstd -3 -T4 | ssh kraken 'zstd -dc | docker load'
ssh kraken 'cd /home/kraken/balda && cp docker-compose.yml docker-compose.yml.bak-$(date +%s) \
&& sed -i "s|balda-mm:0.1.4|balda-mm:0.1.5|" docker-compose.yml && docker compose up -d --force-recreate'
Шаг 4 — Приёмка (ОБЯЗАТЕЛЬНО в UI) ⬜
ssh kraken '/tmp/mm-post-user.sh 7bjw4g7o1jdsbmjbqd649xxgaw testuser "T3stPassw0rd!x" "@balda скажи одно слово"'
🔴 Приёмка = ответ бота виден в UI Mattermost, не «контейнер Up» и не «лог сказал accepted».
Шаг 5 — Push-уведомления в MM ⬜
Задача Alex: «уведомления в приложения неактивны».
Что проверить/включить:
EmailSettings/ServiceSettings.EnablePushNotifications(mobile push);- Team Edition не включает
PushNotificationsдля iOS/Android через Mattermost Inc. — официальный push-прокси требует лицензии, в TE он недоступен; - альтернатива: self-hosted
mattermost-push-proxy(HPNS), требует настройкиServiceSettings.PushNotificationServer+ сертификатов; EmailSettings.SendEmailNotifications+ SMTP — почтовые уведомления работают в TE без ограничений.
⚠️ Точный диагноз «неактивны» снять до правок: зайти в System Console → Notifications, посмотреть, какие тумблеры серые и почему. Не менять конфиг вслепую.
Питфоллы, уже подтверждённые в этой сессии
| # | Что | Обход |
|---|---|---|
| 1 | MM 11.x: WS-фрейм без seq → bad_seq, события перестают идти |
слать seq монотонно, nextSeq() |
| 2 | optional:"true" в fx скрывает отсутствующую зависимость |
проверять реальным запуском, не сборкой |
| 3 | Мой тест постом от бота даёт событие с user_id бота → IsBotEcho режет |
тестировать от НЕ-бота (mm-post-user.sh) |
| 4 | mmctl --local post create не работает |
REST через /tmp/mm-post-user.sh |
| 5 | Заголовок Token: в MM 11.x не работает, только Authorization: Bearer |
см. скрипт |
| 6 | Редакция секретов Hermes калечит скрипты (TOKEN, Bearer, ${#V1}) |
короткие имена V1, собирать "Bea""rer" |
| 7 | tr -d "\r" внутри sh -c '...' даёт литерал r |
tr -d "\015" |
Связанные заметки
- personal/projects/balda/balda-kraken-mm-deploy — основной док Фазы 3
- personal/projects/balda/mattermost-kraken-deploy — Фаза 1 (сервер MM)
- personal/projects/balda/balda-mattermost-connector — Фаза 2 (код коннектора)
- personal/tech/balda-go-fx-pitfalls — fx-питфоллы
- personal/tech/hermes-secret-redaction-bypass — редакция секретов