[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:
1 parent
f9b55abb8c
commit
aefc2f9095
3 files changed
+318
-30
No files matched your search
@@ -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 — без упоминания |
|
||||
|
||||
## Связанные заметки
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
# Фаза 3 — Балда на Kraken + Mattermost (деплой)
|
||||
|
||||
> Создан 2026-09-26. Обновлён 2026-09-26 (сессия реализации: `bad_seq`, MM-хендлер,
|
||||
> привязка владельца, правки провайдера, **приёмка**).
|
||||
> Статус: **✅ ФАЗА 3 ЗАКРЫТА. Сквозной путь `@balda` → ответ в UI работает и принят
|
||||
> (образ `0.1.8`). Все шесть причин устранены и проверены. Push-уведомления —
|
||||
> 🔴 невозможны на Team Edition, диагноз снят и задокументирован.**
|
||||
> привязка владельца, правки провайдера; **🔴 статус приёмки ИСПРАВЛЕН**).
|
||||
> Статус: **🔴 ФАЗА 3 НЕ ЗАКРЫТА. Бот отвечает `testuser`, но НЕ отвечает Alex.**
|
||||
> Причина — аккаунт Alex не привязан как владелец (`unauthorized`); в `balda_user_bindings`
|
||||
> владельцем сидит мой тестовый `testuser`. Alex: «Нихуя он мне не отвечает в ММ.
|
||||
> Не готово!!» Образ `0.1.8`, все шесть технических причин устранены — осталась привязка
|
||||
> аккаунта Alex (причина №7). Push-уведомления — 🔴 невозможны на Team Edition.
|
||||
> Подробный план и фактические изменения — [[personal/plans/balda-mm-endtoend-plan]].
|
||||
> Питфоллы и метод — [[personal/tech/balda-go-fx-pitfalls]].
|
||||
> Часть проекта [[personal/projects/balda/balda-mm-delegation-plan]].
|
||||
@@ -13,9 +15,56 @@
|
||||
|
||||
---
|
||||
|
||||
## ✅ ИТОГ (2026-09-26): бот работает, приёмка пройдена
|
||||
## 🔴 СТАТУС: НЕ ГОТОВО — бот не отвечает аккаунту Alex
|
||||
|
||||
Лента канала `balda-test` (`7bjw4g7o1jdsbmjbqd649xxgaw`) — REST `GET /channels/{id}/posts`:
|
||||
🔴 **Предыдущая запись «ИТОГ: бот работает, приёмка пройдена» была ЛОЖНОЙ.** Сквозной путь
|
||||
проверялся от `testuser` (аккаунт, созданный для тестов), и именно `testuser` оказался
|
||||
привязан владельцем. Alex пишет `@balda привет` — в логе `settlement_stage="unauthorized"`.
|
||||
|
||||
**Актуальная лента канала `balda-test` (`7bjw4g7o1jdsbmjbqd649xxgaw`):**
|
||||
|
||||
```
|
||||
1790409344288 | cbwsorzwoigbtrdnteugnx4aqo | @balda привет ← Alex, БЕЗ ответа
|
||||
1790409329367 | cbwsorzwoigbtrdnteugnx4aqo | Привет ← без @balda, отброшен намеренно
|
||||
1790409148277 | edxrgupoxiyg5gzmr79tjypbme | работает ← ответ бота (testuser)
|
||||
1790409145401 | c6udeze8dfrdxm5jbgq55bd58r | @balda reply with a single word
|
||||
```
|
||||
|
||||
**Лог на пост Alex:**
|
||||
```json
|
||||
{"component":"balda.handlersfx.mattermost",
|
||||
"inbound_id":"mattermost:dqagqce7pibiipt3qtqnkgfd8c",
|
||||
"settlement_outcome":"terminal","settlement_stage":"unauthorized"}
|
||||
```
|
||||
|
||||
**Корень (причина №7):** `balda_user_bindings` содержит одну строку —
|
||||
`mattermost | c6udeze8dfrdxm5jbgq55bd58r` = **`testuser`**. Аккаунт Alex —
|
||||
`cbwsorzwoigbtrdnteugnx4aqo` (`admin`, `system_admin`) — **в таблице отсутствует**.
|
||||
`IsOwnerSubject` ищет строго по `(channel_type='mattermost', principal=<mm_user_id>)` →
|
||||
для Alex пары нет → все проверки авторизации false.
|
||||
|
||||
📌 **Урок приёмки (durable): проверять ОТ ИМЕНИ ЗАКАЗЧИКА.** Сквозной путь,
|
||||
подтверждённый чужим тестовым аккаунтом, — это НЕ приёмка. Если владелец — Alex,
|
||||
тест обязан идти с его MM user_id. Подробный разбор замкнутого круга привязки —
|
||||
[[personal/plans/balda-mm-endtoend-plan]] §«ПОЧЕМУ НЕ РАБОТАЕТ У ALEX».
|
||||
|
||||
**Остаток работы — одна строка в БД** (ожидает апрува Alex):
|
||||
|
||||
```sql
|
||||
UPDATE balda_user_bindings
|
||||
SET principal = 'cbwsorzwoigbtrdnteugnx4aqo',
|
||||
display_name = 'mattermost:cbwsorzwoigbtrdnteugnx4aqo'
|
||||
WHERE channel_type = 'mattermost'
|
||||
AND principal = 'c6udeze8dfrdxm5jbgq55bd58r';
|
||||
```
|
||||
+ бэкап `state.db` с `-wal` до правки; + правка кода `handler.go:420` (двойной префикс,
|
||||
см. §«Причина №7» в плане) и пересборка `0.1.9`, чтобы дефект не вернулся.
|
||||
|
||||
---
|
||||
|
||||
## 📋 ИСТОРИЯ: что реально работало (образ `0.1.8`, от `testuser`)
|
||||
|
||||
Лента канала `balda-test` на момент тестов от `testuser` — REST `GET /channels/{id}/posts`:
|
||||
|
||||
```
|
||||
c6udeze8dfrdxm5jbgq55bd58r (testuser) | @balda reply with a single word
|
||||
@@ -194,34 +243,42 @@ default-ветку — `/start` работает с MM без правок.
|
||||
|
||||
---
|
||||
|
||||
## ✅ Привязка владельца — ВЫПОЛНЕНА (2026-09-26)
|
||||
## ✅ Привязка владельца — ВЫПОЛНЕНА, но ПРИВЯЗАН НЕ ТОТ АККАУНТ (2026-09-26)
|
||||
|
||||
Авторизация Balda — **subject-based**: владелец в `canonical` store привязан строкой
|
||||
`<channel>:<principal>`. Для MM `channel_type='mattermost'`, `principal=<mm_user_id>`
|
||||
**без** транспортного префикса.
|
||||
|
||||
**Итоговое состояние `balda_user_bindings`:**
|
||||
**Итоговое состояние `balda_user_bindings` (актуальное):**
|
||||
|
||||
```
|
||||
binding_id d2006cd1-3337-4159-883b-fcb90c6e9be2
|
||||
user_id b6517e56-1110-4cb9-a301-302c585fbd0d ← администратор
|
||||
channel_type mattermost
|
||||
principal c6udeze8dfrdxm5jbgq55bd58r ← testuser, БЕЗ префикса
|
||||
principal c6udeze8dfrdxm5jbgq55bd58r ← 🔴 testuser, НЕ аккаунт Alex
|
||||
display_name mattermost:mattermost:c6udeze8… ← 🔴 мусор, двойной префикс остался
|
||||
provenance verified-owner-onboarding
|
||||
```
|
||||
|
||||
**Как это делалось (порядок, который сработал):**
|
||||
🔴 **Это и есть причина, почему Alex не получает ответов.** Привязка сделана от
|
||||
`testuser` — аккаунта, созданного мной для тестов. Аккаунт Alex
|
||||
(`cbwsorzwoigbtrdnteugnx4aqo`) не привязан. Требуется `UPDATE` (см. §«СТАТУС» вверху).
|
||||
|
||||
⚠️ `display_name` с двойным префиксом — след того же дефекта кода (`handler.go:420`).
|
||||
В прошлой сессии я поправил `principal` прямой правкой БД, но **не код**, поэтому мусор
|
||||
в `display_name` и `created_by` channel-токенов остался, а дефект воспроизводится.
|
||||
🔴 **Правило: чинить в коде, а не в данных.**
|
||||
|
||||
**Как привязка делалась (порядок, который сработал для первого аккаунта):**
|
||||
|
||||
1. `/start owner=<owner_auth_token>` **в DM** — команда `/start` работает только при
|
||||
`Direct=true`, в канале отбивается. Скрипт `tools/mm-owner-bind.sh <user> <pass> <token>`:
|
||||
логин → `POST /api/v4/channels/direct` с `[<my_id>, <bot_id>]` → `POST /api/v4/posts`
|
||||
с текстом `/start owner=<token>`.
|
||||
2. Получить токен:
|
||||
2. Получить токен из `state.db`:
|
||||
```bash
|
||||
ssh kraken '/tmp/mm-owner-token.sh' # strings + sed по state.db под sudo -n
|
||||
# или вручную:
|
||||
ssh kraken 'sudo -n strings /srv/…-49e8f586…/docker/balda/state/state.db \
|
||||
| grep -o "appowner_auth_token.\{0,45\}"' </dev/null
|
||||
sqlite3 "$DB" "SELECT value_json FROM balda_app_kv WHERE key='owner_auth_token';"
|
||||
# или: sudo -n strings …/state/state.db | grep -o "appowner_auth_token.\{0,45\}"
|
||||
```
|
||||
⚠️ Значение токена в доке **не хранится** — только способ получения.
|
||||
3. 🔴 Первая попытка дала **«You are not allowed to use Balda in this channel.»** —
|
||||
@@ -235,7 +292,7 @@ provenance verified-owner-onboarding
|
||||
|
||||
---
|
||||
|
||||
## Текущее состояние (2026-09-26, ФАЗА ЗАКРЫТА)
|
||||
## Текущее состояние (2026-09-26, 🔴 ФАЗА НЕ ЗАКРЫТА)
|
||||
|
||||
| Компонент | Статус |
|
||||
|---|---|
|
||||
@@ -248,15 +305,17 @@ provenance verified-owner-onboarding
|
||||
| Причина «bad_seq» (дельта 16) | ✅ **исправлена, проверена** |
|
||||
| Причина «Processor==nil» (дельты 17-19) | ✅ **исправлена, доказана логом** |
|
||||
| Причина «`/start` закрыт доступом» | ✅ **исправлена** |
|
||||
| Причина «`principal` с префиксом» | ✅ **исправлена, авторизация проходит** |
|
||||
| Причина «`principal` с префиксом» | ⚠️ **исправлена в БД, НО НЕ В КОДЕ** — дефект воспроизвёлся (см. причину №7) |
|
||||
| Причина «провайдер `base_url` + CA» (дельта 22) | ✅ **исправлена, ПРОВЕРЕНА** |
|
||||
| **Ответ бота в UI** | ✅ **ПОДТВЕРЖДЁН — бот ответил `работает` в `balda-test`** |
|
||||
| **Ответ бота аккаунту Alex** | 🔴 **НЕ РАБОТАЕТ — `unauthorized`. Аккаунт Alex не привязан** |
|
||||
| Ответ бота `testuser` | ✅ работает (это НЕ приёмка — чужой аккаунт) |
|
||||
| Push-уведомления | 🔴 **невозможны на Team Edition** — диагноз снят, см. §Push ниже |
|
||||
| MM-хендлер `handlersfx/mattermost_ingress.go` | ✅ написан, подключён в DI |
|
||||
| Quality gates (build/vet/arch-lint) | ✅ все зелёные |
|
||||
|
||||
✅ **Приёмка пройдена: ответ бота виден в UI Mattermost** (проверено REST-чтением ленты,
|
||||
`user_id` автора = бот). Фаза 3 закрыта.
|
||||
🔴 **НЕ приёмка.** Требуется: привязать аккаунт Alex (`UPDATE balda_user_bindings`),
|
||||
затем проверить `@balda привет` **от его аккаунта** и увидеть ответ в ленте канала.
|
||||
Правку кода (`handler.go:420`) сделать в том же заходе — иначе двойной префикс вернётся.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Balda — Go/fx pitfalls и метод правок (зеркало скилла)
|
||||
|
||||
> Создано 2026-09-26. Обновлено 2026-09-26 (сессия реализации MM-хендлера, полный проход +
|
||||
> **приёмка сквозного пути**, Фаза 3 закрыта).
|
||||
> Создано 2026-09-26. Обновлено 2026-09-26 (сессия реализации MM-хендлера, полный проход;
|
||||
> 🔴 **статус приёмки ИСПРАВЛЕН** — см. §«Ложная приёмка» и §«Чинить в коде, а не в данных»).
|
||||
> Добавлены: **`bad_seq` — настоящая причина тишины WS** (§«WS-фрейм без seq»),
|
||||
> **`optional:"true"` скрывает отсутствующую зависимость** (§«Дефект 5»),
|
||||
> **`canonicalSubject` и неизвестный транспорт** (§«auth: subject-based»),
|
||||
@@ -9,9 +9,11 @@
|
||||
> **`base_url` в YAML мёртв — только `OPENAI_BASE_URL`** (§«Провайдер: …»),
|
||||
> **`FROM scratch` без CA** (§«scratch и TLS»),
|
||||
> **`principal` с префиксом в БД** (§«Дефект 7»),
|
||||
> **`state.db` читать вместе с WAL** (§«Метод: чтение sqlite-состояния»).
|
||||
> 🔴 **Новое в этой сессии (приёмка):** §«Приёмка агент-проверяемый» — как доказать ответ
|
||||
> бота без GUI; §«Mattermost Team Edition: что недоступно» — push невозможен.
|
||||
> **`state.db` читать вместе с WAL** (§«Метод: чтение sqlite-состояния»),
|
||||
> **🔴 ложная приёмка от чужого аккаунта** (§«Ложная приёмка»),
|
||||
> **🔴 двойной префикс жив в коде, а не только в БД** (§«Чинить в коде»),
|
||||
> **🔴 замкнутый круг привязки при занятом binding** (§«Дефект 8»).
|
||||
> 🔴 **Новое в этой сессии:** §«Mattermost Team Edition: что недоступно» — push невозможен.
|
||||
> Уточнён §«авторизация — ОДИН механизм»: челлендж НЕ был причиной №1, им был `bad_seq`.
|
||||
> Зеркало секции скилла `devops/docker-balda-service`, добавленной 2026-09-26.
|
||||
> Источник: работа над Mattermost-адаптером Балды (Фаза 2) + деплой на Kraken (Фаза 3).
|
||||
@@ -22,7 +24,104 @@
|
||||
|
||||
---
|
||||
|
||||
## ✅ Приёмка, проверяемая агентом (без GUI-браузера)
|
||||
## 🔴🔴 Ложная приёмка: сквозной путь от ЧУЖОГО аккаунта — это не приёмка
|
||||
|
||||
**Самый дорогой урок сессии.** Я отчитался «фаза закрыта, приёмка пройдена», проверив
|
||||
сквозной путь от `testuser` — аккаунта, созданного мной же для тестов. Alex написал
|
||||
`@balda привет` — тишина, в логе `settlement_stage="unauthorized"`.
|
||||
|
||||
**Причина:** владельцем бота был привязан `testuser`. Аккаунта Alex в
|
||||
`balda_user_bindings` **не было вообще**. Путь работал — но только для тестового аккаунта.
|
||||
|
||||
📌 **Правило приёмки:** проверять **от имени заказчика**, его учёткой. Если система
|
||||
авторизует по `principal`/`user_id` — тест обязан идти с `user_id` заказчика.
|
||||
Сквозной путь, подтверждённый подставным аккаунтом, доказывает **только** работоспособность
|
||||
конвейера, но **не** работоспособность для пользователя.
|
||||
|
||||
🔴 **Красный флаг:** в тестовой ленте автором успешного запроса стоит НЕ тот `user_id`,
|
||||
который будет в проде. Это уже повод не объявлять приёмку.
|
||||
|
||||
⚠️ Отличать от НЕ-дефекта: пост **без** упоминания бота отбрасывается намеренно —
|
||||
`dropping posted event: bot not mentioned` (`MentionsBot`). В канале обращаться
|
||||
`@balda <текст>`; в DM упоминание не нужно.
|
||||
|
||||
---
|
||||
|
||||
## 🔴 Чинить в КОДЕ, а не в данных (двойной префикс вернулся)
|
||||
|
||||
В прошлой сессии я нашёл, что `principal` в `balda_user_bindings` записан как
|
||||
`mattermost:c6udeze…` (с префиксом), и починил это **прямым `UPDATE` в БД**. Дефект
|
||||
**вернулся**: `display_name` остался `mattermost:mattermost:c6udeze…`, а `created_by`
|
||||
новых channel-токенов снова получил двойной префикс.
|
||||
|
||||
**Настоящий источник — код**, `actors/command/start/handler.go:420`:
|
||||
|
||||
```go
|
||||
case transportMattermost:
|
||||
return auth.MattermostSubject(strings.TrimPrefix(trimmed, transportMattermost+":"))
|
||||
```
|
||||
|
||||
`MattermostSubject()` (`auth/owner.go:307`) **сама** добавляет префикс вида `<channel>:<id>`,
|
||||
а `p.Principal` приходит от MM-хендлера **уже с префиксом**
|
||||
(`mattermost_ingress.go:147`: `Principal: auth.MattermostSubject(cmd.SenderID)`).
|
||||
|
||||
📌 **Правило: дефект, найденный в данных, искать и в коде.** Правка данных без правки
|
||||
кода = дефект вернётся при следующей записи. Проверять **оба слоя** и фиксить источник.
|
||||
|
||||
⚠️ Тот же класс ошибки — двойное применение helper'а, который сам добавляет префикс.
|
||||
Признак: строка вида `<x>:<x>:<id>` в БД, в `display_name`, в `created_by`, в логах.
|
||||
Искать надо `MattermostSubject`/`TelegramSubject`-подобные билдеры, вызванные поверх
|
||||
уже собранного subject'а.
|
||||
|
||||
⚠️ Остальные вызовы `MattermostSubject(cmd.SenderID)` в `mattermost_ingress.go`
|
||||
(строки 189, 277, 292) **корректны** — они получают чистый `user_id`. Дефект ровно один,
|
||||
в `userSubject` (`handler.go:420`).
|
||||
|
||||
---
|
||||
|
||||
## 🔴🔴 Дефект 8: замкнутый круг привязки, когда binding занят чужим аккаунтом
|
||||
|
||||
Ситуация: владельцем привязан **не тот** аккаунт, а нужно привязать правильный.
|
||||
Все штатные пути оказываются закрыты.
|
||||
|
||||
| Штатный путь | Почему не работает |
|
||||
|---|---|
|
||||
| `/start owner=<owner_auth_token>` | `HasOwner()` = `len(OwnerSubjects()) != 0`. Владелец существует → код уходит в `handleExistingOwnerState` → `IsOwner(<нужный аккаунт>)` = false → **«Bot owner is already registered»** |
|
||||
| `/start <channel_token>` для нужного канала | `CreateMissingOwnerBindTokens` (`auth/channel_token.go:215`) **пропускает** канал с `HasOwnerSubject(channel) == true`. Канал считается привязанным → **токен не создаётся вообще** |
|
||||
| `registerOwner` → `attachCanonicalBinding` | Кандидат ищется как `Role == administrator` **И `Binding == nil`** (`canonical_users.go:110`). Единственный админ уже имеет binding → `no unbound active administrator user` |
|
||||
|
||||
📌 **Механика, которую надо помнить:**
|
||||
- `HasOwner()` при `canonical != nil` — это `len(OwnerSubjects()) != 0`, а `OwnerSubjects()`
|
||||
собирается из **binding'ов** (не из роли). Снять binding = обнулить `HasOwner()`.
|
||||
- `AttachBinding` **не даёт перепривязать** (`count != 0` → `ErrBindingAlreadyAssigned`),
|
||||
**detach-API отсутствует**.
|
||||
- `purpose` channel-токена должен быть `owner_bind` — токен из другого назначения
|
||||
`ConsumeOwnerBind` отвергнет.
|
||||
|
||||
🔴 **Единственный выход без правки кода** — `UPDATE` строки binding: меняем `principal`
|
||||
(и `display_name`) на нужный аккаунт. Пользователь, роль и `HasOwner()` сохраняются,
|
||||
связь «владелец ↔ аккаунт» переезжает. Обратимо обратным `UPDATE`.
|
||||
|
||||
```sql
|
||||
UPDATE balda_user_bindings
|
||||
SET principal = '<нужный mm_user_id>',
|
||||
display_name = 'mattermost:<нужный mm_user_id>'
|
||||
WHERE channel_type = 'mattermost'
|
||||
AND principal = '<старый mm_user_id>';
|
||||
```
|
||||
|
||||
⚠️ Перед `UPDATE` — бэкап `state.db` **вместе с `-wal`**; контейнер остановить
|
||||
(sqlite не любит конкурентной записи). Значения проверять через
|
||||
`sqlite3 … ".schema balda_app_kv"` — колонки `namespace, key, value_json, updated_at, expires_at`
|
||||
(`value`/`k` таких колонок НЕТ).
|
||||
|
||||
📌 **Диагностика «почему именно мне бот не отвечает»:** сравнить `user_id` из ленты канала
|
||||
с `principal` в `balda_user_bindings`. Несовпадение = `unauthorized`, и никакие логи
|
||||
транспорта этого не покажут.
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ Приёмка, проверяемая агентом (без GUI-браузера) — но ОТ НУЖНОГО АККАУНТА
|
||||
|
||||
Требование Alex: «приёмка = видно в UI, не "контейнер Up" и не "лог said accepted"».
|
||||
Агент без браузера **не может** посмотреть UI — но может верифицировать ответ бота
|
||||
@@ -44,6 +143,10 @@ edxrgupoxiyg5gzmr79tjypbme | работает ← это и есть док
|
||||
⚠️ `post_user_id` в логах ингресса **не** подходит: там id отправителя запроса, не автора ответа.
|
||||
⚠️ Не отчитываться «готово» по факту `delivery settled` в логах — доставляется ли пост, видно
|
||||
только из ленты.
|
||||
🔴🔴 **Обязательное условие: `user_id` отправителя в тесте должен быть аккаунтом ЗАКАЗЧИКА.**
|
||||
Тест от подставного/тестового аккаунта доказывает работу конвейера, но не работу для
|
||||
пользователя — см. §«Ложная приёмка» выше. Этот самый промах и дал ложный доклад «фаза
|
||||
закрыта», после которого Alex написал «Нихуя он мне не отвечает в ММ. Не готово!!».
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in new issue
Block a user