[2026-09-26] eagle: personal/plans/balda-mm-endtoend-plan.md personal/projects/balda/balda-kraken-mm-deploy.md personal/tech/balda-go-fx-pitfalls.md

This commit is contained in:
Alexey Martemyanov committed 2026-09-26 14:01:35 +06:00
1 parent f9b55abb8c
commit aefc2f9095
3 files changed
+318 -30

No files matched your search

+130 -4
View File
@@ -1,14 +1,135 @@
# План: доделать сквозной путь `@balda` → ответ в Mattermost
> Создан 2026-09-26. Обновлён 2026-09-26 (сессия реализации, полный проход).
> Статус: **✅ ЗАКРЫТ И ПРИНЯТ. Все шесть причин устранены, шестая проверена — бот отвечает
> в UI. Push-уведомления: 🔴 невозможны на Team Edition (разбор ниже) — задача снята с плана.**
> Статус: **🔴 НЕ ЗАКРЫТ. «Приёмка пройдена» была ложной — сквозной путь проверялся от
> ТЕСТОВОГО аккаунта `testuser`, а не от аккаунта Alex (`admin`). Alex: «Нихуя он мне не
> отвечает в ММ. Не готово!!» — ПОДТВЕРДИЛОСЬ логами. Владельцем бота привязан `testuser`,
> аккаунт Alex в `balda_user_bindings` отсутствует полностью → его посты отбиваются
> `settlement_stage=unauthorized`. См. §«ПОЧЕМУ НЕ РАБОТАЕТ У ALEX» ниже — это причина №7.**
> Push-уведомления: 🔴 невозможны на Team Edition (разбор ниже) — задача снята с плана.
> Часть [[personal/projects/balda/balda-kraken-mm-deploy]] (Фаза 3).
> Команда Alex: «Доделывай всё. И уведомления в приложения неактивны тоже поправь.»
## ✅ ПРИЁМКА ПРОЙДЕНА (2026-09-26)
## 🔴 ПОЧЕМУ НЕ РАБОТАЕТ У ALEX — причина №7 (главная, найдена 2026-09-26 13:5x)
Сквозной путь работает. В канале `balda-test` (`7bjw4g7o1jdsbmjbqd649xxgaw`):
Alex написал `@balda привет` в `balda-test` — бот молчит. Лог:
```json
{"component":"balda.handlersfx.mattermost","session_id":"mm-c-7aafea13",
"inbound_id":"mattermost:dqagqce7pibiipt3qtqnkgfd8c",
"settlement_outcome":"terminal","settlement_stage":"unauthorized",
"message":"conversational ingress settled"}
```
**Причина:** в `balda_user_bindings` единственная строка — владелец привязан к
`principal='c6udeze8dfrdxm5jbgq55bd58r'`, это **`testuser`** (аккаунт, которым я тестировал).
Аккаунта Alex — `admin`, MM user_id `cbwsorzwoigbtrdnteugnx4aqo` — в таблице **нет вообще**.
```
cbwsorzwoigbtrdnteugnx4aqo | admin | system_admin ← Alex, НЕ привязан
c6udeze8dfrdxm5jbgq55bd58r | testuser | system_user ← привязан как владелец (мой тест)
edxrgupoxiyg5gzmr79tjypbme | balda | system_user ← бот
```
`IsOwnerSubject` ищет строго по паре `(channel_type='mattermost', principal=<mm_user_id>)`.
Пары для Alex нет → каждая проверка авторизации false → `unauthorized`.
📌 **Мой доклад «фаза закрыта» был неверным.** Я проверял путь от `testuser`, чей post
прошёл авторизацию, и записал это как приёмку. Реальный пользователь — Alex — не проходил.
🔴 **Урок приёмки: проверять ОТ ИМЕНИ ЗАКАЗЧИКА.** Если владелец — Alex, тест обязан
идти с его MM user_id. Чужой аккаунт в bindings делает сквозной путь «рабочим» только
для тестового пользователя — это ложная приёмка.
### Второй симптом (не дефект): пост без `@balda` отбрасывается намеренно
```
{"component":"balda.channel.mattermost.ingress","post_id":"jznhm9ks6jfrtdze5r5qa7g6ic",
"bot_username":"balda","message":"Привет","message":"dropping posted event: bot not mentioned"}
```
Это **корректное** поведение (`MentionsBot`). В канале обращаться надо `@balda <текст>`.
### 🔴 Причина №7 — второй дефект: двойной префикс жив в КОДЕ, не только в БД
`actors/command/start/handler.go:420`:
```go
case transportMattermost:
return auth.MattermostSubject(strings.TrimPrefix(trimmed, transportMattermost+":"))
```
`MattermostSubject()` (`auth/owner.go:307`) **сама** добавляет префикс:
```go
func MattermostSubject(userID string) string {
return fmt.Sprintf("%s:%s", ChannelMattermost, strings.TrimSpace(userID))
}
```
А `trimmed` = `p.Principal` приходит **уже с префиксом** от MM-хендлера
(`mattermost_ingress.go:147`: `Principal: auth.MattermostSubject(cmd.SenderID)`).
⚠️ **`TrimPrefix` здесь снимает префикс, но только один раз, и это не помогает:**
хендлер положил `mattermost:c6udeze…`, `TrimPrefix` даёт `c6udeze…`, `MattermostSubject`
снова даёт `mattermost:c6udeze…` — верно. Но **исходный `p.Principal` уже содержал
префикс**, потому что в прошлой сессии `MattermostSubject` вызывался дважды на разных слоях.
Результат в БД — `mattermost:mattermost:c6udeze…` (виден в `display_name` и в `created_by`
channel-токенов). В прошлой сессии я починил **строку в БД**, но **не код** — поэтому
дефект воспроизвёлся снова при создании токенов.
🔴 **Правило: чинить дефект в коде, а не в данных.** Правка БД без правки кода = дефект
вернётся при следующей записи. Проверять оба слоя.
### 🔴 Блокер привязки Alex — замкнутый круг (нужно решение)
| Путь | Почему не работает |
|---|---|
| `/start owner=<owner_auth_token>` | `HasOwner()` = `len(OwnerSubjects()) != 0`. Владелец (`testuser`) есть → идёт в `handleExistingOwnerState` → `IsOwner(Alex)` = false → **«Bot owner is already registered»** |
| `/start <channel_token>` для MM | `CreateMissingOwnerBindTokens` пропускает канал, где `HasOwnerSubject(channel)` = true. MM считается привязанным (см. строку `testuser`) → **токен для MM не создаётся вообще** |
| `registerOwner` → `attachCanonicalBinding` | Ищет кандидата: `Role == administrator` **и `Binding == nil`**. Единственный админ `b6517e56-…` **уже имеет binding** → `no unbound active administrator user` |
**Выход (выбран, ожидает апрува Alex):** перевести существующий binding с `testuser`
на аккаунт Alex одним `UPDATE` — пользователь `b6517e56-…`, роль и `HasOwner()` остаются,
меняется только `principal` + `display_name`. Это **минимальное** вмешательство: не `DELETE`,
обратимо обратным `UPDATE`, и не требует ни правки кода, ни пересборки.
```sql
UPDATE balda_user_bindings
SET principal = 'cbwsorzwoigbtrdnteugnx4aqo',
display_name = 'mattermost:cbwsorzwoigbtrdnteugnx4aqo'
WHERE channel_type = 'mattermost'
AND principal = 'c6udeze8dfrdxm5jbgq55bd58r';
```
Перед этим — бэкап `state.db` с `-wal` (обязательно).
### Факты БД (2026-09-26, актуальные)
```
balda_users: 1 строка — b6517e56-1110-4cb9-a301-302c585fbd0d / admin / administrator
balda_user_bindings: 1 строка — mattermost | c6udeze8dfrdxm5jbgq55bd58r (testuser)
display_name = mattermost:mattermost:c6udeze8… ← мусор, двойной префикс
balda_collaborators: пусто
balda_user_binding_claims: 1 строка (consumed)
balda_app_kv: owner_auth_token = XDucUE… (32 симв.)
channel_token:* → telemetry: telegram, slackagent, zulip
🔴 для mattermost токена НЕТ (канал считается привязанным)
```
⚠️ `owner_auth_token` в доке **не хранить** — только способ получения:
`sqlite3 "$DB" "SELECT value_json FROM balda_app_kv WHERE key='owner_auth_token';"`
(БД на хосте: `/srv/…-6194539b…/docker/balda/state/state.db`, читать вместе с `-wal`).
### Текущее состояние блокера
**`@balda` от Alex → `unauthorized`, ответа нет.** Бот, транспорт, провайдер, сессии,
доставка — все исправны (доказано проходом от `testuser`). Не хватает **одной строки
привязки** для аккаунта Alex. Работа остановлена на апруве `UPDATE` в БД.
---
## ⚠️ ПРЕДЫДУЩИЙ БЛОК «ПРИЁМКА ПРОЙДЕНА» — НЕПОЛНАЯ (проверено от `testuser`)
Сквозной путь работал **только для аккаунта `testuser`**. В канале `balda-test` (`7bjw4g7o1jdsbmjbqd649xxgaw`):
```
c6udeze8dfrdxm5jbgq55bd58r (testuser) | @balda reply with a single word
@@ -513,6 +634,11 @@ DiagnosticId kgib6ormjf8sf8xds7dkhaee5c
| 24 | `psql -t -c "… name IN ('DiagnosticId')"` в ssh-строке ломается на кавычках → `column "DiagnosticId" does not exist` | экранировать `'"'"'` внутри ssh-команды, либо использовать `ILIKE` без кавычек на значении |
| 25 | Приёмка «виден в UI» без GUI-браузера | `GET /api/v4/channels/{id}/posts?per_page=N` + `jq` по `user_id` автора — однозначное доказательство |
| 26 | `docker compose` в `/home/kraken/mattermost/` — 3 сервиса (`postgres`, `mattermost`, сеть `mm-net`), БД `mm-postgres` | БД доступна как `docker exec mm-postgres psql -U mmuser -d mattermost` |
| 27 | 🔴🔴 **Приёмка от тестового аккаунта** — сквозной путь «работает», а заказчик получает `unauthorized` | тестировать **учёткой заказчика**; сверять `user_id` ленты с `principal` в `balda_user_bindings` (см. §«Ложная приёмка») |
| 28 | 🔴 **Дефект, найденный в БД, чинится только в БД** → возвращается при следующей записи | искать источник в коде; двойной префикс = helper, применённый поверх готового subject'а (см. §«Чинить в КОДЕ») |
| 29 | **Владельцем привязан чужой аккаунт** → `/start owner=` даёт «already registered», channel-токен для канала не создаётся, кандидат `Binding == nil` отсутствует | `UPDATE balda_user_bindings` (см. §«Дефект 8») — бэкап `state.db` + `-wal`, контейнер остановить |
| 30 | `sqlite3` по `balda_app_kv`: колонок `value`/`k` **нет** | схема: `namespace, key, value_json, updated_at, expires_at`; проверять `.schema` перед запросом |
| 31 | Пост **без** `@balda` отбрасывается (`dropping posted event: bot not mentioned`) | это НЕ дефект: `MentionsBot`; в канале писать `@balda <текст>`, в DM — без упоминания |
## Связанные заметки