[2026-09-26] eagle: family/how-to/jellyfin-config.md personal/index.md personal/plans/balda-mm-endtoend-plan.md personal/projects/balda/balda-kraken-mm-deploy.md personal/projects/balda/balda-mm-delegation-plan.md personal/tech/balda-go-fx-pitfalls.md personal/tech/music-tags-repair-truenas.md personal/tech/yandex-music-api.md personal/yandex-music-library.md

This commit is contained in:
Alexey Martemyanov committed 2026-09-26 13:56:32 +06:00
1 parent 4109797f6b
commit f9b55abb8c
9 files changed
+2190 -94

No files matched your search

+154 -29
View File
@@ -1,15 +1,93 @@
# Фаза 3 — Балда на Kraken + Mattermost (деплой)
> Создан 2026-09-26. Обновлён 2026-09-26 (сессия реализации: `bad_seq` + MM-хендлер).
> Статус: **🟡 ОБЕ ПРИЧИНЫ ТИШИНЫ УСТРАНЕНЫ. Хендлер написан и доказанно вызывается.
> Блокирует одно: привязка владельца (`/start owner=` в DM).**
> Создан 2026-09-26. Обновлён 2026-09-26 (сессия реализации: `bad_seq`, MM-хендлер,
> привязка владельца, правки провайдера, **приёмка**).
> Статус: **✅ ФАЗА 3 ЗАКРЫТА. Сквозной путь `@balda` → ответ в UI работает и принят
> (образ `0.1.8`). Все шесть причин устранены и проверены. Push-уведомления —
> 🔴 невозможны на Team Edition, диагноз снят и задокументирован.**
> Подробный план и фактические изменения — [[personal/plans/balda-mm-endtoend-plan]].
> Питфоллы и метод — [[personal/tech/balda-go-fx-pitfalls]].
> Часть проекта [[personal/projects/balda/balda-mm-delegation-plan]].
> Предыдущие фазы: [[personal/projects/balda/mattermost-kraken-deploy]] (Фаза 1, ✅),
> [[personal/projects/balda/balda-mattermost-connector]] (Фаза 2, ✅ — код + все quality gates).
---
## ✅ ИТОГ (2026-09-26): бот работает, приёмка пройдена
Лента канала `balda-test` (`7bjw4g7o1jdsbmjbqd649xxgaw`) — REST `GET /channels/{id}/posts`:
```
c6udeze8dfrdxm5jbgq55bd58r (testuser) | @balda reply with a single word
edxrgupoxiyg5gzmr79tjypbme (bot) | работает
```
Провайдер ответил: `event_author=deepseek`, `text_char_count=16`, `final_response=true`,
`delivery settled` → `settlement_class=success`. `x509` и `api.openai.com` ушли из логов.
📌 **Проверка «виден в UI» агентом без GUI:** читать ленту REST'ом и грепать `user_id` бота:
```bash
ssh kraken 'curl -s -H "Authorization: Bearer *** /home/kraken/balda/secrets/mm-bot-token)" \
"http://localhost:8065/api/v4/channels/7bjw4g7o1jdsbmjbqd649xxgaw/posts?per_page=4" \
| jq -r ".order[] as \$o | .posts[\$o] | \"\(.user_id) | \(.message)\""'
```
---
## 🔴 История причин тишины (шесть штук, все закрыты)
| № | Причина | Фикс | Проверено |
|---|---|---|---|
| 3 | `bad_seq` — WS-пинг без `seq` убивал подписку молча | дельта 16 | ✅ `bad_seq` 0 вхождений, `pong seq_reply:1,2,3` |
| 4 | `Processor == nil` — MM-хендлер отсутствовал в DI | новый файл + дельта 19 | ✅ лог `post accepted for processing` |
| 5 | `ChannelMattermost` отсутствовал в `auth` → `unsupported transport` | дельта 17, 18 | ✅ |
| 5a | `/start` отбивался проверкой доступа **до** привязки (тупик онбординга) | правка `HandleCommand` | ✅ команда прошла в runtime |
| 5b | `principal` в БД с транспортным префиксом → `unauthorized` | UPDATE с бэкапом | ✅ `assembled provider user content` |
| **6** | **`base_url` в YAML мёртв + `FROM scratch` без CA** → `api.openai.com` + `x509` | дельта 22 + `.env` | ✅ **ответ провайдера получен, 16 символов** |
**Итоговый проход в логах (образ `0.1.8`):**
```
mattermost websocket event decoded event=posted
mattermost post accepted for processing text="reply with a single word"
session created successfully session_id=mm-c-7aafea13
assembled provider user content input_text_char_count=424
received provider event event_author=deepseek text_char_count=16 final_response=true
delivery settled settlement_class=success
```
**Актуальные артефакты:** образ **`balda-mm:0.1.8`** · `.env` — **6 строк** (включая
`OPENAI_BASE_URL=https://api.deepseek.com/v1`) · владелец привязан как
`channel_type='mattermost'`, `principal='c6udeze8dfrdxm5jbgq55bd58r'` (без префикса!) ·
бэкапы состояния в `…/docker/balda/backups/`.
### 🔴 Причина 6 (последняя) — двойной дефект провайдера
`runtime.providers.deepseek.openai.base_url` в YAML **молча игнорируется**: тип
`LocalAPIConfig` (`normahq/runtime/v2@v2.0.11/agentconfig/config.go:82`) имеет **только**
`api_key` и `model`. Базовый URL читается **только** из env — `hostedagent/openai.go:24`:
```go
func openAIBaseURL() string {
if u := os.Getenv("OPENAI_BASE_URL"); u != "" { return strings.TrimRight(u, "/") }
return "https://api.openai.com/v1"
}
```
📌 Для любого OpenAI-совместимого провайдера базовый URL задавать **только** через
`OPENAI_BASE_URL` в `.env`. Ключ `base_url` в YAML выглядит рабочим — это ловушка.
Второй дефект — тот же корень: `FROM scratch` **не содержит `/etc/ssl/certs`**, поэтому
**любой** исходящий HTTPS падал с `x509: certificate signed by unknown authority`.
Фикс (дельта `tools/deltas-mattermost-22-ca.json`):
```dockerfile
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
ENV SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt
```
📌 Правило: любой `scratch`/`distroless`-образ, делающий исходящие HTTPS-запросы, обязан
копировать `ca-certificates.crt` из builder'а **и** выставлять `SSL_CERT_FILE`.
⚠️ Симптом обманчив — выглядит как «сервер с неверным сертификатом», а на деле доверять нечему.
---
## 🔴 ГЛАВНОЕ: третья причина тишины найдена — это был НЕ ингресс, а MM-авторизация сокета
Прошлая сессия заканчивалась версией «событие теряется между `readLoop` и `handleFrame`».
@@ -116,56 +194,103 @@ default-ветку — `/start` работает с MM без правок.
---
## 🔴 Единственный блокер: привязка владельца
## ✅ Привязка владельца — ВЫПОЛНЕНА (2026-09-26)
Авторизация Balda — **subject-based**: владелец в `canonical` store привязан строкой
`<channel>:<principal>`, для MM это `mattermost:<mm_user_id>`.
`<channel>:<principal>`. Для MM `channel_type='mattermost'`, `principal=<mm_user_id>`
**без** транспортного префикса.
**Состояние** (`strings` по `state.db`, т.к. образ `scratch` — ни shell, ни sqlite3):
**Итоговое состояние `balda_user_bindings`:**
```
balda_user_bindings → ПУСТА, ни одной привязки канала
admin user_id → b6517e56-1110-4cb9-a301-302c585fbd0d (administrator, active)
balda.appowner_auth_token → 32-символьный токен в kv-таблице
binding_id d2006cd1-3337-4159-883b-fcb90c6e9be2
user_id b6517e56-1110-4cb9-a301-302c585fbd0d ← администратор
channel_type mattermost
principal c6udeze8dfrdxm5jbgq55bd58r ← testuser, БЕЗ префикса
provenance verified-owner-onboarding
```
**Путь привязки:** `/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>`.
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. Получить токен:
```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
```
⚠️ Значение токена в доке **не хранится** — только способ получения.
3. 🔴 Первая попытка дала **«You are not allowed to use Balda in this channel.»** —
`HandleCommand` проверял доступ **до** обработки, а владельца ещё нет (тупик онбординга).
Фикс: `if cmd.Command != commandStart && !h.canAccess(...)` — по образцу zulip.
4. 🔴 Вторая проблема: `/start` вернул успех, но `principal` записался **с префиксом**
(`mattermost:c6udeze…`), а `GetUserByBinding` сравнивает точно → `unauthorized` навсегда.
Фикс: `tools/mm-fix-owner-principal.sh` (бэкап → стоп → UPDATE → старт).
Получить токен:
```bash
ssh kraken 'sudo -n strings /srv/…-49e8f586…/docker/balda/state/state.db | grep -A2 appowner_auth_token' </dev/null
```
⚠️ Значение токена в доке **не хранится** — только способ получения.
**После привязки:** `@balda` в `balda-test`, приёмка — **ответ виден в UI Mattermost**.
📌 Подробный разбор обеих ловушек — [[personal/tech/balda-go-fx-pitfalls]] §«Дефект 6», §«Дефект 7».
---
## Текущее состояние (2026-09-26, обновлено)
## Текущее состояние (2026-09-26, ФАЗА ЗАКРЫТА)
| Компонент | Статус |
|---|---|
| Бот `balda` в MM | ✅ создан, токен рабочий, в команде и канале |
| Образ | ✅ `balda-mm:0.1.5` (актуальный), задеплоен |
| Образ | ✅ **`balda-mm:0.1.8`** (актуальный), задеплоен |
| Контейнер `balda-mm` | ✅ Up, без ошибок старта |
| Провайдер DeepSeek | ✅ `balda provider runtime ready` agent=deepseek |
| Провайдер DeepSeek | ✅ работает: `event_author=deepseek`, ответ получен |
| **WebSocket к Mattermost** | ✅ 101 Switching Protocols, `pong seq_reply` растёт, `bad_seq` — 0 |
| Причина «челлендж» (дельта 14) | ✅ исправлена |
| Причина «bad_seq» (дельта 16) | ✅ **исправлена, проверена** |
| Причина «Processor==nil» (дельты 17-19) | ✅ **исправлена, доказана логом** |
| Причина «`/start` закрыт доступом» | ✅ **исправлена** |
| Причина «`principal` с префиксом» | ✅ **исправлена, авторизация проходит** |
| Причина «провайдер `base_url` + CA» (дельта 22) | ✅ **исправлена, ПРОВЕРЕНА** |
| **Ответ бота в UI** | ✅ **ПОДТВЕРЖДЁН — бот ответил `работает` в `balda-test`** |
| Push-уведомления | 🔴 **невозможны на Team Edition** — диагноз снят, см. §Push ниже |
| MM-хендлер `handlersfx/mattermost_ingress.go` | ✅ написан, подключён в DI |
| Quality gates (build/vet/arch-lint) | ✅ все зелёные |
| **Привязка владельца** | 🔴 **НЕ ВЫПОЛНЕНА** — блокирует приёмку |
| **Ответ бота на `@balda`** | 🔴 пока нет — упирается в привязку владельца |
| Push-уведомления | ⬜ не начато |
⚠️ **Приёмка = ответ бота виден в UI Mattermost**, не «контейнер Up» и не «лог said accepted».
⚠️ Не докладывать «готово», пока сквозной путь не работает.
✅ **Приёмка пройдена: ответ бота виден в UI Mattermost** (проверено REST-чтением ленты,
`user_id` автора = бот). Фаза 3 закрыта.
---
## 🔴 Push-уведомления — невозможны на Team Edition (диагностика завершена)
Задача Alex «уведомления в приложения неактивны» разобрана. **Это ограничение продукта,
не настройка.** `Build Enterprise Ready: false` — Team Edition.
**Конфиг (факт):**
```
EmailSettings.SendPushNotifications false
EmailSettings.PushNotificationServer "" ← пусто
EmailSettings.SendEmailNotifications true
EmailSettings.SMTPServer localhost / Port 10025 ← SMTP не слушает (exit 7)
```
🔴 Тумблер лежит в **`EmailSettings`**, не в `ServiceSettings` — запрос
`mmctl config get ServiceSettings.EnablePushNotifications` даёт **`Error: invalid key`**.
**Три пути, все закрыты:**
| Путь | Вердикт |
|---|---|
| Официальный push-прокси | Прокси жив (HTTP 200), но **отвергает наш инстанс**: `Did not send message because of missing platform property`. APNs/FCM-сертификаты на прокси вешаются **только для лицензированных EE** |
| Свой MPNS (`mattermost-push-proxy`) | 🔴 Требует **собственные подписанные сборки приложений** + Apple Developer ($99/год) + Firebase. **Официальные приложения к своему MPNS не подключатся** — прошиты на bundle ID Mattermost Inc. |
| SMTP-почта | ⚠️ Работает в TE **без лицензии**, но SMTP не настроен: на хосте нет слушателей на 25/465/587/10025 |
**Варианты для Alex:** лицензия Professional (~$10/юзер/мес) · свои сборки + свой MPNS ·
SMTP-почта вместо push (без затрат, ~15 мин) · оставить как есть.
⚠️ **Живая проба прокси обязательна** — HTTP 200 не означает доставку, читать **тело** ответа
(`"status":"FAIL"`). `DiagnosticId` берётся из `SELECT value FROM systems WHERE name ILIKE '%diagnostic%';`
(таблица `systems`, не `Systems`).
📌 Команда Alex по этому пункту: решить, делаем ли SMTP. Провайдер push на TE не включается.
---
@@ -1,6 +1,8 @@
# Balda 2.0 — Mattermost + распределённое делегирование (план)
> Создан 2026-09-25. Статус: **в работе с 2026-09-26** (Alex дал команду исполнять, без вопросов).
> Создан 2026-09-25. Статус: **Фазы 1-3 закрыты 2026-09-26** (MM на Kraken · MM-адаптер
> в коде · оркестратор развёрнут, **бот отвечает в MM**). Следующая — Фаза 4 (исполнитель).
> Alex дал команду исполнять без вопросов.
> Задача: поднять свежую Балду из `baldaworks/balda` (без наших коммитов), завязать на Mattermost,
> построить распределённое делегирование (дешёвый оркестратор + подписочные CLI в VM).
>
@@ -246,8 +248,9 @@ Mattermost (Alex) — итог + артефакты
и отсутствующие провайдеры видны только при `fx.ValidateApp`/`validateBaldaApplication`.
13 падающих пакетов в апстриме — **предсуществующие**, проверено `git stash`, не наши.
### Фаза 3 — оркестратор на Kraken — 🟡 РАЗВЁРНУТА, бот пока не отвечает
### Фаза 3 — оркестратор на Kraken — ✅ ЗАКРЫТА 2026-09-26 (бот отвечает)
Детальный док: [[personal/projects/balda/balda-kraken-mm-deploy]].
План и разбор причин: [[personal/plans/balda-mm-endtoend-plan]].
**Старт 2026-09-26:** Alex дал команду «разворачивай бота на кракене с привязкой к ММ,
токен deepseek возьми скриптом из hermes whale env, сборка на маке».
@@ -260,18 +263,24 @@ Mattermost (Alex) — итог + артефакты
`balda-mm:0.1.1`, linux/arm64, 20.6 МБ, ~110 с. Перенос `docker save | zstd` → `docker load`.
3. ✅ **Развёрнута на Kraken** — `/home/kraken/balda/`, данные на USB-HDD, сеть
`mattermost_mm-net`, конфиг монтируется в `/workspace/.config/balda/config.yaml`.
4. ✅ **Провайдер DeepSeek** — `type: openai` + `base_url: https://api.deepseek.com/v1`,
ключ из `~/.hermes/hermes-whale/.env` прочитан скриптом, в чат не выводился.
5. ✅ **WebSocket к MM подключён** — `mattermost websocket connected`.
4. ✅ **Провайдер DeepSeek** — `type: openai`, ключ из `~/.hermes/hermes-whale/.env` прочитан
скриптом, в чат не выводился. 🔴 **`base_url` в YAML мёртв** — задавать `OPENAI_BASE_URL`
в `.env` (см. [[personal/tech/balda-go-fx-pitfalls]] §«Провайдер»).
5. ✅ **WebSocket к MM подключён** — `mattermost websocket connected`, `seq` монотонный.
6. ✅ **Backoffice-админ забутстрапен** (не был в плане, но обязателен — иначе отказ старта).
7. ✅ **Обнаружен и исправлен дефект Фазы 2:** `advertised commands have no actor handler: goal`
— опечатка в `Advertisement.Names` роняет **весь fx-граф** и даёт crash-loop. См.
[[personal/tech/balda-go-fx-pitfalls]] §«Дефект 4». Ни один quality gate Фазы 2 его не видел.
8. 🔴 **ОТКРЫТО: бот не отвечает на `@balda`.** Пост доставлен, но в логах ингресса **нет ни
одного кадра от WS** даже на `logger.level: debug`. Клиентская логика разобрана и чистая —
значит подозрение смещается на **серверную сторону: MM не пушит `posted` боту**.
Метод проверки — WS-пробник с токеном бота (см. детальный док, §«Что осталось»).
9. ⏳ **Верификация Alex в UI MM — не пройдена** (приёмка = ответ бота в UI).
7. ✅ **Исправлен дефект Фазы 2:** `advertised commands have no actor handler: goal`
— опечатка в `Advertisement.Names` роняет **весь fx-граф** и даёт crash-loop.
См. [[personal/tech/balda-go-fx-pitfalls]] §«Дефект 4». Ни один quality gate Фазы 2 его не видел.
8. ✅ **Шесть причин тишины найдены и устранены:** `bad_seq` (WS-пинг без `seq` — MM
перестаёт пушить события, не разрывая сокет) · `Processor == nil` (MM-хендлера не было
в DI) · `ChannelMattermost` отсутствовал в `auth` · `/start` отбивался проверкой доступа
до привязки · `principal` в БД с транспортным префиксом · `base_url` + отсутствие CA
в `scratch`-образе. Итоговый образ — **`balda-mm:0.1.8`**.
9. ✅ **Верификация пройдена:** бот ответил в канале `balda-test` — `@balda` → `работает`.
Приёмка выполнена **REST-чтением ленты канала** с проверкой `user_id` автора.
10. 🔴 **Push-уведомления — невозможны на Team Edition.** Диагноз снят и задокументирован
(официальный прокси требует лицензии; свой MPNS требует своих сборок приложений).
Безлицензионная альтернатива — SMTP-почта, решение за Alex.
### Фаза 4 — исполнитель в VM/Docker на Mac (~2-3 дня)
1. Docker-контейнер (или UTM VM) с Node 24 + `codex` + `claude` + `git`.