[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,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