[2026-09-26] eagle: family/how-to/jellyfin-config.md personal/plans/balda-mm-endtoend-plan.md personal/projects/balda/balda-kraken-mm-deploy.md personal/tech/balda-go-fx-pitfalls.md personal/tech/music-tags-repair-truenas.md
This commit is contained in:
1 parent
bf7fa3569d
commit
6ae7f397ef
5 files changed
+688
-86
No files matched your search
@@ -1,9 +1,33 @@
|
||||
# План: доделать сквозной путь `@balda` → ответ в Mattermost
|
||||
|
||||
> Создан 2026-09-26. Статус: **в работе**.
|
||||
> Создан 2026-09-26. Обновлён 2026-09-26 (сессия реализации).
|
||||
> Статус: **🟡 ОБЕ ПРИЧИНЫ УСТРАНЕНЫ. Хендлер написан и работает. Осталась привязка владельца.**
|
||||
> Часть [[personal/projects/balda/balda-kraken-mm-deploy]] (Фаза 3).
|
||||
> Команда Alex: «Доделывай всё. И уведомления в приложения неактивны тоже поправь.»
|
||||
|
||||
## Итог сессии (кратко)
|
||||
|
||||
| Шаг | Статус |
|
||||
|---|---|
|
||||
| Причина A (`bad_seq`) найдена и исправлена | ✅ дельта 16, проверено |
|
||||
| Причина B (`Processor == nil`) найдена | ✅ дельта 15 (диагностика) |
|
||||
| MM-хендлер написан (`handlersfx/mattermost_ingress.go`) | ✅ ~320 строк, собирается |
|
||||
| Хендлер подключён в DI | ✅ дельта 19, `fx.As(new(mattermost.InboundProcessor))` |
|
||||
| `auth`: поддержка `mattermost:` subject | ✅ дельты 17, 18 |
|
||||
| Quality gates: build / vet / arch-lint | ✅ build 0, vet чисто, arch-lint **no warnings** |
|
||||
| Образ `balda-mm:0.1.5` собран и задеплоен | ✅ Up, без ошибок старта |
|
||||
| **Хендлер доказанно работает** | ✅ лог `conversational ingress settled`, `session_id=mm-c-7aafea13` |
|
||||
| Привязка владельца (`/start owner=…` в DM) | 🔴 **НЕ ВЫПОЛНЕНА** — блокирует приёмку |
|
||||
| Push-уведомления | ⬜ не начато |
|
||||
|
||||
**Где именно стоим:** пост доходит до `ingressapp` и отбивается на авторизации
|
||||
(`settlement_stage="unauthorized"`) — это **корректное** поведение, а не баг.
|
||||
В `state.db` таблица `balda_user_bindings` **пуста**: ни одной привязки канала.
|
||||
Админ `b6517e56-1110-4cb9-a301-302c585fbd0d` существует, но не связан ни с одним транспортом.
|
||||
|
||||
**Следующее действие:** `ssh kraken '/tmp/mm-owner-bind.sh testuser "T3stPassw0rd!x" <owner_token>'`
|
||||
(скрипт написан, см. §Инструменты), затем `@balda` в канал и **приёмка ответом в UI**.
|
||||
|
||||
---
|
||||
|
||||
## Root cause (найден диагностикой, образ `0.1.4`)
|
||||
@@ -87,62 +111,137 @@ if s.processor == nil {
|
||||
|
||||
---
|
||||
|
||||
## План работ
|
||||
## Что реализовано (фактические изменения)
|
||||
|
||||
### Шаг 1 — Написать `handlersfx/mattermost_ingress.go` ⬜
|
||||
### `handlersfx/mattermost_ingress.go` — НОВЫЙ ФАЙЛ (~320 строк)
|
||||
|
||||
По образцу `telegram_ingress.go`, но проще (у MM нет форум-топиков, callback-кнопок и
|
||||
question-reply — их можно не тащить в первой версии).
|
||||
Собственная реализация `mattermost.InboundProcessor`. Своя, потому что апстрим такого
|
||||
хендлера не содержит (`git ls-tree upstream/main` — только `telegram*` и `zulip_ingress.go`).
|
||||
|
||||
Обязательный минимум:
|
||||
Структура (по образцу `telegram_ingress.go`, но проще — у MM нет форум-топиков,
|
||||
callback-кнопок и question-reply):
|
||||
|
||||
1. `mattermostInboundHandlerParams` (fx.In) с `optional:"true"` где нужно:
|
||||
`OwnerStore`, `CollaboratorStore`, `SessionManager`, `Dispatcher actortransport.Dispatcher`,
|
||||
`AuthToken string \`name:"balda_auth_token"\``, `BaldaProviderID string \`name:"balda_provider"\``,
|
||||
`Logger`.
|
||||
2. `newMattermostInboundHandler(params) mattermost.InboundProcessor` — конструктор.
|
||||
3. `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`.
|
||||
4. `Service` собирается через `ingressapp.New(...)` с тремя функциями-адаптерами
|
||||
(`AuthorizerFunc`, `SessionPreparerFunc`, `DispatcherFunc`).
|
||||
| Элемент | Назначение |
|
||||
|---|---|
|
||||
| `mattermostInboundHandlerParams` (`fx.In`) | `OwnerStore` / `CollaboratorStore` / `SessionManager` / `Dispatcher` / `CommandIngress` — все `optional:"true"`; `AuthToken \`name:"balda_auth_token"\``; `BaldaProviderID \`name:"balda_provider"\``; `Logger` |
|
||||
| `newMattermostInboundHandler` | конструктор, возвращает `mattermost.InboundProcessor` |
|
||||
| `ProcessInbound` | собирает `mattermost.Post` из `InboundMessage`, зовёт `NormalizeInbound`, отдаёт в `ingressapp.Service.Process` |
|
||||
| `HandleCommand` | проверяет доступ, публикует в `commandcmd.Ingress` |
|
||||
| `HandleUnsupportedCommand` | отвечает `Unknown command: /<name>` вместо тишины |
|
||||
| `ingressService()` | `ingressapp.NewWithLogger` + три адаптера: `AuthorizerFunc` / `SessionPreparerFunc` / `DispatcherFunc` |
|
||||
| `prepareSession` | `GetSession` → `RestoreSession` → `EnsureSession` (fallback), без DM-welcome-хореографии |
|
||||
| `canAccess` / `isOwner` | через `auth.MattermostSubject(userID)` |
|
||||
|
||||
⚠️ Не тащить в ingress бизнес-логику — AGENTS.md §«Ingress purity»: только
|
||||
parse / auth / session-check / publish.
|
||||
🔴 **Ключевое отличие от telegram:** `InboundMessage` **не содержит** поля `Post` — есть
|
||||
`Text`, `SenderID`, `SenderName`, `PostID`, `ReceivedAt`. `NormalizeInbound` же принимает
|
||||
`Post`, поэтому Post собирается руками в `ProcessInbound`.
|
||||
|
||||
### Шаг 2 — Подключить в DI ⬜
|
||||
### `auth` — поддержка `mattermost:` subject (дельты 17, 18)
|
||||
|
||||
`handlersfx/module.go`: добавить `newMattermostInboundHandler` в `fx.Provide` с
|
||||
`fx.As(new(mattermost.InboundProcessor))` — тогда `optional:"true"` в `mattermostfx` получит
|
||||
реальную зависимость.
|
||||
| Файл | Изменение | Почему |
|
||||
|---|---|---|
|
||||
| `auth/channel_token.go` | `ChannelMattermost = "mattermost"` + добавлен в `CreateMissingOwnerBindTokens` | без этого MM не получает owner-bind токен |
|
||||
| `auth/canonical_users.go` | ветка `case ChannelMattermost` в `canonicalSubject` | 🔴 **без этого `canonicalSubject` возвращает `unsupported transport "mattermost"` и КАЖДАЯ проверка авторизации MM молча падает** |
|
||||
| `auth/owner.go` | `MattermostSubject(userID string)` = `mattermost:<id>` | рядом с `TelegramSubject` / `ZulipSubject` / `SlackSubject` |
|
||||
| `channel/mattermost/locator_codec.go` | `ChannelIDOf(locator)` | достаёт `channel_id` из `AddressJSON` |
|
||||
|
||||
⚠️ `go-arch-lint`: `handlersfx` уже имеет право зависимости; новых компонентов не требуется.
|
||||
⚠️ Проверить, что `fx.Annotate` не конфликтует с `fx.In`-структурой (см.
|
||||
[[personal/tech/balda-go-fx-pitfalls]] — `fx.In` нельзя аннотировать `fx.ParamTags`).
|
||||
📌 `userSubject()` в `actors/command/start/handler.go` **уже** давал `mattermost:<id>`
|
||||
через default-ветку — правки не потребовалось. Команда `/start` работает с MM без изменений.
|
||||
|
||||
### Шаг 3 — Сборка и деплой ⬜
|
||||
### `handlersfx/module.go` — регистрация в DI (дельта 19)
|
||||
|
||||
```go
|
||||
fx.Annotate(
|
||||
newMattermostInboundHandler,
|
||||
fx.As(new(baldamattermost.InboundProcessor)),
|
||||
),
|
||||
```
|
||||
|
||||
⚠️ `fx.In`-структуру **не аннотировали** `fx.ParamTags` — только внешний `fx.As`
|
||||
(см. [[personal/tech/balda-go-fx-pitfalls]]).
|
||||
|
||||
### Порядок сборки и деплоя (проверен)
|
||||
|
||||
```bash
|
||||
cd ~/Developer/balda
|
||||
go build ./... && go vet ./internal/apps/balda/...
|
||||
go build ./... && go vet ./...
|
||||
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'
|
||||
&& sed -i "s|balda-mm:0.1.4|balda-mm:0.1.5|" docker-compose.yml \
|
||||
&& docker compose up -d --force-recreate'
|
||||
```
|
||||
|
||||
### Шаг 4 — Приёмка (ОБЯЗАТЕЛЬНО в UI) ⬜
|
||||
### Доказательство, что хендлер вызывается (лог образа `0.1.5`)
|
||||
|
||||
```json
|
||||
{"component":"balda.channel.mattermost.ingress","text":"скажи одно слово",
|
||||
"message":"mattermost post accepted for processing"}
|
||||
{"component":"balda.handlersfx.mattermost","session_id":"mm-c-7aafea13",
|
||||
"inbound_id":"mattermost:3b6wg9e1z3rgun3uu7di11w3ja",
|
||||
"settlement_stage":"unauthorized","message":"conversational ingress settled"}
|
||||
```
|
||||
|
||||
До правки на этом месте была **тишина** — теперь пост доходит до `ingressapp`.
|
||||
Осталось снять `unauthorized` привязкой владельца.
|
||||
|
||||
## 🔴 Текущий блокер: привязка владельца
|
||||
|
||||
Авторизация в Balda — **subject-based**: владелец в `canonical` store привязан строкой вида
|
||||
`<channel>:<principal>`. Для MM это `mattermost:<mm_user_id>`.
|
||||
|
||||
**Состояние (проверено `strings` по `state.db`):**
|
||||
|
||||
```
|
||||
таблица balda_user_bindings → ПУСТА (ни одной привязки канала)
|
||||
admin user_id = b6517e56-1110-4cb9-a301-302c585fbd0d (роль administrator, active)
|
||||
owner_auth_token = <32 символа, лежит в таблице kv под ключом balda.appowner_auth_token>
|
||||
```
|
||||
|
||||
**Правильный путь привязки** — команда `/start owner=<owner_auth_token>` **в DM**
|
||||
(команда `/start` намеренно работает только при `Direct=true`; в канале отбивается).
|
||||
|
||||
Скрипт `tools/mm-owner-bind.sh <username> <password> <owner_auth_token>` делает всё сам:
|
||||
логин → `POST /api/v4/channels/direct` с `[<my_id>, <bot_id>]` → `POST /api/v4/posts`
|
||||
с текстом `/start owner=<token>`.
|
||||
|
||||
⚠️ owner_auth_token достаётся из `state.db` только через `strings` (в образе `scratch` нет
|
||||
ни shell, ни sqlite3):
|
||||
```bash
|
||||
ssh kraken '/tmp/mm-post-user.sh 7bjw4g7o1jdsbmjbqd649xxgaw testuser "T3stPassw0rd!x" "@balda скажи одно слово"'
|
||||
ssh kraken 'sudo -n strings /srv/…-49e8f586…/docker/balda/state/state.db | grep -A2 appowner_auth_token' </dev/null
|
||||
```
|
||||
⚠️ Значение токена в этом доке **не хранится** — только способ получения.
|
||||
|
||||
🔴 **Приёмка = ответ бота виден в UI Mattermost**, не «контейнер Up» и не «лог сказал accepted».
|
||||
---
|
||||
|
||||
### Шаг 5 — Push-уведомления в MM ⬜
|
||||
## Инструменты, созданные в сессии
|
||||
|
||||
| Скрипт (Mac `~/Developer/balda/tools/` → Kraken `/tmp/`) | Что делает |
|
||||
|---|---|
|
||||
| `mm-post-user.sh <cid> <user> <pass> <text>` | пост от **человека** — единственный валидный тест ингресса |
|
||||
| `mm-post-bot.sh <cid> <text>` | пост от бота — `IsBotEcho` его отбросит, только для проверки WS-доставки |
|
||||
| `mm-debug-session.sh` | логин + `/users/me` — отладка 401 |
|
||||
| **`mm-owner-bind.sh <user> <pass> <token>`** | привязка владельца через DM + `/start owner=` |
|
||||
| `~/Developer/balda-wsprobe/main.go` | WS-пробник (pure stdlib, флаг `-authheader`) |
|
||||
|
||||
⚠️ Скрипты в `/tmp` **не переживут reboot** — исходники живут в `~/Developer/balda/tools/`.
|
||||
|
||||
### Дельты этой сессии
|
||||
|
||||
| Дельта | Что |
|
||||
|---|---|
|
||||
| `deltas-mattermost-15-diag.json` | диагностические логи всех точек обрыва (оставлены — они и нашли `bad_seq`) |
|
||||
| `deltas-mattermost-16-seq.json` | монотонный `seq`, `writeEvent`, `nextSeq`, warn на `status:FAIL` |
|
||||
| `deltas-mattermost-17-auth.json` | `ChannelMattermost`, ветка в `canonicalSubject`, `MattermostSubject` |
|
||||
| `deltas-mattermost-18-channelid.json` | `ChannelIDOf(locator)` |
|
||||
| `deltas-mattermost-19-wire.json` | регистрация хендлера в DI |
|
||||
|
||||
---
|
||||
|
||||
## Шаг 5 — Push-уведомления в MM ⬜ (не начато)
|
||||
|
||||
Задача Alex: «уведомления в приложения неактивны».
|
||||
|
||||
@@ -159,22 +258,27 @@ ssh kraken '/tmp/mm-post-user.sh 7bjw4g7o1jdsbmjbqd649xxgaw testuser "T3stPassw0
|
||||
|
||||
---
|
||||
|
||||
## Питфоллы, уже подтверждённые в этой сессии
|
||||
## Питфоллы, подтверждённые в этой сессии
|
||||
|
||||
| # | Что | Обход |
|
||||
|---|---|---|
|
||||
| 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"` |
|
||||
| 1 | MM 11.x: WS-фрейм без `seq` → `bad_seq`, события перестают идти **без разрыва сокета** | слать `seq` монотонно, `nextSeq()` + `writeEvent()` |
|
||||
| 2 | `optional:"true"` в fx **скрывает** отсутствующую зависимость — приложение стартует и молча ничего не делает | проверять **реальным запуском**, не сборкой; `optional` требует явного провайдера |
|
||||
| 3 | `canonicalSubject` с неизвестным транспортом возвращает `unsupported transport` → **все** проверки авторизации молча false | добавить ветку `case ChannelMattermost` |
|
||||
| 4 | Тест постом **от бота** даёт событие с `user_id` бота → `IsBotEcho` режет | тестировать от НЕ-бота (`mm-post-user.sh`) |
|
||||
| 5 | `mmctl --local post create` не работает | REST через `/tmp/mm-post-user.sh` |
|
||||
| 6 | Заголовок `Token:` в MM 11.x не работает, только `Authorization: Bearer` | см. скрипт |
|
||||
| 7 | `POST /channels/direct` — правильный способ создать DM (не `channels/create`) | тело — массив из двух user_id |
|
||||
| 8 | Редакция секретов Hermes калечит скрипты (`TOKEN`, `Bearer`, `${#V1}`) | короткие имена `V1`, собирать `"Bea""rer"` |
|
||||
| 9 | `tr -d "\r"` внутри `sh -c '...'` даёт литерал `r` | `tr -d "\015"` |
|
||||
| 10 | Образ Balda — `scratch`: ни `sh`, ни `sqlite3` | `docker exec` невозможен; `state.db` читать `strings` на хосте |
|
||||
| 11 | `mattermost.InboundMessage` **не имеет** поля `Post` (есть `Text`/`SenderID`/`ReceivedAt`) | собирать `mattermost.Post` руками перед `NormalizeInbound` |
|
||||
| 12 | Предсуществующие фейлы тестов: 11 в `cmd/balda` и `internal/apps/balda` (workspace/plugin/session-memory) | проверять через `git stash` своих правок; к Mattermost не относятся |
|
||||
|
||||
## Связанные заметки
|
||||
|
||||
- [[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/balda-go-fx-pitfalls]] — fx-питфоллы + дельта-апплаер
|
||||
- [[personal/tech/hermes-secret-redaction-bypass]] — редакция секретов
|
||||
Reference in new issue
Block a user