[2026-09-28] eagle: personal/plans/balda-runtime-rebuild-mm-validation.md personal/plans/runtime-pr5-toolset-dedup-review-fixes.md personal/projects/balda/balda-kraken-mm-deploy.md

This commit is contained in:
Alexey Martemyanov committed 2026-09-28 20:39:07 +06:00
1 parent 1480fa8d95
commit bf7787bcbd
3 files changed
+187 -28

No files matched your search

@@ -1,10 +1,25 @@
> Создан 2026-09-28. Статус: **✅ ЧАСТЬ A (runtime) И ЧАСТЬ C/D (сборка+деплой+валидация) ВЫПОЛНЕНЫ.**
> Создан 2026-09-28. Статус: **✅ ЧАСТЬ A (runtime) · C/D (сборка+деплой+валидация) · E (sync с апстримом) ВЫПОЛНЕНЫ.**
> Часть B (3 блокера PR #8) — **НЕ НАЧАТА**.
> Цель: собрать локально runtime (PR4+PR5 влиты) + balda (ревью PR #8), задеплоить на Kraken,
> прогнать проверку тулинга и Mattermost.
> Связано: [[personal/projects/balda/balda-kraken-mm-deploy]], [[personal/plans/balda-mm-pr8-review-fixes]],
> [[personal/plans/runtime-pr5-toolset-dedup-review-fixes]].
### 🔴 Состояние git на конец сессии (читать первым)
| | |
|---|---|
| HEAD | **`6f5695c7`** — `Merge remote-tracking branch 'upstream/main' into feat/mattermost-channel` |
| Ветка | `feat/mattermost-channel` |
| behind `upstream/main` | **0** (впереди на 18) |
| `upstream` | `https://github.com/baldaworks/balda.git` (заменён с `normahq/balda`) |
| runtime pin | **`v2.1.0`**, директивы `replace` нет |
| Незакоммичено | **5 файлов** (см. §7.1) + `?? personal/` (мусор) |
⚠️ **ВАЖНО:** задеплоенный на Kraken образ **`0.1.14` собран из дерева ДО merge `6f5695c7`**.
Merge добавил апстримные коммиты (backoffice-фичи), т.е. **текущее дерево ≠ собранному образу**.
Перед следующим релизом — пересобрать.
---
## 0а. ✅ РЕЗУЛЬТАТ — что сделано в этой сессии
@@ -103,7 +118,7 @@ rejected by transport: sending "tools/call": Internal Server Error
## 0б. ✅ Замена `upstream` на baldaworks
`git remote set-url upstream https://github.com/baldaworks/balda.git`.
Было `https://github.com/normahq/balda.git`.
Было `https://github.com/normahq/balda.git`. Fetch прошёл: `upstream/main` = `c1fde3c4`.
📌 **Разъяснение путаницы (проверено фактами):**
- `normahq/balda` и `baldaworks/balda` отдают **идентичный** `main` = `c1fde3c4` → это один
@@ -113,13 +128,16 @@ rejected by transport: sending "tools/call": Internal Server Error
PR идёт из форка.
- PR #8: head `mallexxx/balda:feat/mattermost-channel` @ `e0162009` → base `baldaworks/balda:main`.
- 🔴 **PR создаётся через gh API, git-ремоут на базовый репо для этого НЕ нужен.**
Отсюда и путаница «как открыт PR, если ремоута нет».
Отсюда и путаница «как открыт PR, если ремоута нет». Аккаунт `mallexxx` (тот же, под которым
работает `gh auth`) имеет доступ к `baldaworks/balda`; `gh pr create --repo baldaworks/balda
--head mallexxx:feat/mattermost-channel` сравнивает ветку форка с базой апстрима **по API**.
Ремоут `upstream` нужен только для `fetch`/синхронизации, а не для открытия PR.
⚠️ После fetch: `upstream/main` продвинулся `de273d8f..c1fde3c4` (плюс forced update
служебной ветки `__dolt_remote_info__`). База PR #8 = `de273d8f` **отстаёт** от текущего main —
**PR #8 нужно синхронизировать перед мержем.**
служебной ветки `__dolt_remote_info__`). База PR #8 = `de273d8f` **отставала** от текущего main —
✅ **устранено** merge'ом `6f5695c7` (§1б), осталось запушить.
Бэкап конфига: `/tmp/balda-git-config.bak-20260928` (md5 `69fdc0de503640002dba06ac49741c24`).
Конфиг-бэкап: `/tmp/balda-git-config.bak-20260928` (md5 `69fdc0de503640002dba06ac49741c24`).
---
@@ -156,7 +174,9 @@ jq -r '.posts as $p | .order[] | $p[.] | "\(.create_at) | \(.user_id[0:10]) | \(
| runtime tag **`v2.1.0`** | = `c41939f` = `upstream/main`; содержит `DedupKey` (**6**) и `openAIToolsFromConfig` (**2**) |
| maint-фиксы в `v2.1.0` | `hydrateMCPServerConfig` несёт `DedupKey`/`DedupPreferred` (без этого дедуп был бы мёртв в проде) |
| `baldaworks/balda` PR #8 | **OPEN**, head `e0162009`, review **CHANGES_REQUESTED** (2026-09-28T13:54Z) |
| Локальный `~/Developer/balda` | **`e0162009`** — совпадает с головой PR #8 (был `06b2b8d0`, отстал на 4) |
| Локальный `~/Developer/balda` | **`6f5695c7`** (merge upstream/main); before merge был `e0162009` |
| `upstream/main` (balda) | **`c1fde3c4`** — содержит backoffice-фичи (`docs(backoffice): document UI review workflow`) |
| `upstream/main` (balda) runtime pin | ⚠️ всё ещё **`v2.0.11`**, тег `2.1.0` НЕ заменён (см. §1б) |
---
@@ -174,6 +194,46 @@ session-memory **fail closed**. Лексикографически меньши
---
## 1б. Часть E: подтягивание локала к апстриму ✅ ВЫПОЛНЕНО
Задача Alex: *«подтяни локал к апстриму. вероятно там уже заменен тег 2.1.0»*.
**Гипотеза про тег НЕ подтвердилась — проверено фактом:**
`git show upstream/main:go.mod` → `github.com/normahq/runtime/v2 v2.0.11`.
Тег `2.1.0` в апстриме **не заменён**, директивы `replace` там нет.
📌 Вывод: наш bump до `v2.1.0` — **наше локальное опережение**, а не подтягивание за апстримом.
Его надо будет протолкнуть в апстрим отдельно (иначе при следующем sync go.mod снова откатится
на `v2.0.11` — merge его сохранит только потому, что правка неконфликтная).
**Merge:** `git merge upstream/main` → `de273d8f..c1fde3c4`.
**Один конфликт:** `cmd/balda/init_test.go`.
- **наша ветка (HEAD)** добавила `filepath.EvalSymlinks` для пути к `.env`
(устойчивость к symlink-нутой working dir);
- **upstream/main** добавил ассерты bootstrap администратора
(`Backoffice password: …`, `NewSQLiteProvider`, `ListUsers`, `GetCredentialSecret` + `userpassword.Verify`)
и вернул путь **без** symlink-разрешения.
**Разрешено объединением:** все upstream-ассерты + наш `tokenPath` через `EvalSymlinks`,
финальная проверка `strings.Contains(out, "telegram token stored in: "+tokenPath)`.
Коммит **`6f5695c7`** (merge). Целевой тест проходит:
`TestInitCommand_NonInteractiveAutoSelectsRootAndGeneratesDetectedAgents` → **PASS**.
📌 **Урок merge-конфликта с апстримом:** апстрим переписал тот же участок, что и мы, но по другой
причине. Нельзя брать «их вариант целиком» — потеряем свою правку; нельзя «наш целиком» —
потеряем новую функциональность апстрима. Читать оба блока и **объединять по смыслу**.
**Гейты на смерженном дереве (HEAD `6f5695c7`):**
```
go build ./... → OK
go vet ./... → OK
go test -race ./... → 107 пакетов ok, 0 фейлов
go tool golangci-lint run → 0 issues
go tool go-arch-lint check --project-path . → OK - No warnings found
```
`go mod tidy` сохранил `v2.1.0` (апстрим не переопределил).
---
## 2. План — Часть B: 3 блокера PR #8 ⏳ НЕ НАЧАТО
Последнее ревью (13:54) подтверждает: canonical chat ingress, command registry, scope
@@ -325,14 +385,29 @@ reboot**. Источники в `~/Developer/balda/tools/` (созданы за
## 7. ⏭️ Что делать дальше (приоритет)
1. 🔴 **Закоммитить незакоммиченные правки** в `~/Developer/balda` — образ `0.1.14` собран из них.
Файлы: `go.mod`, `go.sum`, `Dockerfile.release`, `internalmcp/manager.go`,
`sessionapp/module.go`, `sessionmemorymcp/broker.go`.
1. 🔴 **Закоммитить 5 незакоммиченных файлов** в `~/Developer/balda` — образ `0.1.14` собран из них:
```
M go.mod ← runtime v2.0.11 → v2.1.0, убран replace на ./runtime-local
M go.sum ← следствие go mod tidy; gorilla/websocket стал прямым
M internal/apps/balda/internalmcp/manager.go ← DedupKey у bundled-записи
M internal/apps/balda/sessionapp/module.go ← DedupKey + DedupPreferred
M internal/apps/balda/sessionmemorymcp/broker.go ← константа BundledDedupKey
```
⚠️ `Dockerfile.release` **уже НЕ в списке** — правка (удаление `COPY runtime-local/`) совпала
с HEAD после merge; ссылок на `runtime-local` нет и в HEAD.
2. **Часть B** — 3 блокера PR #8 (`ingress.go:389`, `:483`, `:164`).
3. **Синхронизировать PR #8** с `upstream/main` (`c1fde3c4`): база PR = `de273d8f`, отстала.
4. **Починить bridge `balda-executor-mcp`** — HTTP 000 + JSONDecodeError (отдельная инфра).
5. Прогнать сценарии D2-b / D2-c (после части B) — они и есть приёмка надёжности WS.
6. ⚠️ Убрать мусор `~/Developer/balda/personal/` (создан ошибочным `write_file`).
3. **Протолкнуть bump `v2.1.0` в апстрим** — сейчас в `upstream/main` всё ещё `v2.0.11`
(см. §1б). Иначе при следующем sync go.mod откатится.
4. **Синхронизировать PR #8** с `upstream/main` (`c1fde3c4`) — сделано merge `6f5695c7`
локально; **запушить в `origin`** (`mallexxx/balda`), чтобы PR подхватил.
⚠️ База PR = `de273d8f`, отставала от main.
5. **Пересобрать образ** после merge — `0.1.14` собран из pre-merge дерева; следующий `0.1.15`.
6. **Починить bridge `balda-executor-mcp`** — HTTP 000 + JSONDecodeError (отдельная инфра).
7. Прогнать сценарии D2-b / D2-c (после части B) — они и есть приёмка надёжности WS.
8. ⚠️ Убрать мусор `~/Developer/balda/personal/` (создан ошибочным `write_file`).
9. ⚠️ Временное в `/tmp` (не переживёт reboot): `runtime-local-removed-20260928`,
`runtime-local-bak-20260928`, `balda-git-config.bak-20260928`, `mergemsg.txt`,
`balda-test.log`, `/tmp/mm-*.sh` на Kraken.
## Связанные заметки
@@ -1,8 +1,40 @@
> Создан 2026-09-28. Статус: **✅ ВЛИТО В АПСТРИМ (2026-09-28T13:22:13Z).**
> Мой коммит: **`d4ab923`** (4 файла, +411/−1), ветка `fix/hosted-toolsets-duplicate-endpoint`.
> **PR normahq/runtime#5** — **MERGED** (merge-коммит `6775d71`).
> Ответ ревьюеру: https://github.com/normahq/runtime/pull/5#issuecomment-5867224210
> Репо: `~/Developer/runtime-local`. Связано: [[personal/plans/runtime-pr4-openai-toolcalls-fixes]].
> Создан 2026-09-28. Статус: **✅ MERGED В АПСТРИМ 2026-09-28T13:22:13Z (head `facb0d0`).**
> Финальная голова PR: **`facb0d0`**. Мейнтейнер добавил **3 фикса поверх** моего `d4ab923` —
> см. §0г. Образ Балды переведён на тег **`v2.1.0`** (= `c41939f`, содержит PR #4 + #5).
> **PR normahq/runtime#5** — MERGED. Репо: `~/Developer/runtime-local`.
> Связано: [[personal/plans/runtime-pr4-openai-toolcalls-fixes]],
> [[personal/plans/balda-runtime-rebuild-mm-validation]].
---
## 0г. 🔴 Мейнтейнер нашёл 2 реальных дефекта в моём фиксе (`d4ab923` → `facb0d0`)
Перед мержем в ветку PR #5 добавлены 3 коммита:
`f8011c1` fix(agentfactory): preserve MCP dedup metadata ·
`e888d5e` fix(agentconfig): validate preferred MCP dedup ·
`facb0d0` fix(agentfactory): keep MCP dedup silent.
Diff: 5 файлов, +160/−10.
| # | Дефект в моём фиксе | Фикс мейнтейнера |
|---|---|---|
| **1** | 🔴 **`hydrateMCPServerConfig` не переносил `DedupKey`/`DedupPreferred`** — собирал новый `MCPServerConfig` поле за полем и **забыл новые поля**. Фикс был бы **мёртв в проде** на пути `Build()` | добавлены `DedupKey: cfg.DedupKey`, `DedupPreferred: cfg.DedupPreferred`. Покрыто тестом `TestFactoryBuildKeepsPreferredDedupEndpoint` (сквозной путь registry → hydration → `hostedToolsets`) |
| **2** | `strings.TrimSpace(key)` **нормализовал** ключ → независимо настроенные namespace'ы с пробелами схлопывались (ровно тот cross-tenant риск, который API и предотвращает) | сравнение по **сырому** ключу; проверка пустоты отдельно: `key := resolvedMCP[id].DedupKey; if strings.TrimSpace(key) == ""`. Тест `TestDedupKeyIsOpaque` |
| **3** | Две записи с `DedupPreferred: true` в одной группе — **молча** выбиралась одна (зависит от порядка id) | явная ошибка: `mcp dedup key %q has multiple preferred configs`. Тест `TestHostedToolsetsRejectsMultipleDedupPreferredConfigs` |
| **4** | `fmt.Printf("[mcp] skipping …")` в **библиотеке** — печать в stdout потребителя | вывод убран (коммит «keep MCP dedup silent») |
| **5** | `DedupPreferred: true` без `DedupKey` не валидировался | `ValidateMCPServerConfig`: `dedup_preferred requires dedup_key`. Тест `TestValidateMCPServerConfig_DedupPreferredRequiresDedupKey` |
📌 **Уроки (durable):**
1. 🔴 **Добавляя поле в конфиг — грепнуть ВСЕ места ручного копирования структуры.**
`hydrateMCPServerConfig` собирает `MCPServerConfig` поле за полем; компилятор такое **не ловит**
(пропущенное поле = zero value, валидный код). Искать `grep -n "MCPServerConfig{"` во всём репо.
Это ровно тот класс дефекта, что и «терпимый ассерт» / «копия логики в тесте»: **выглядит
рабочим, молчит в проде**.
2. ⚠️ **Opaque = opaque.** Назвал ключ «непрозрачным» у вызывающего — не нормализуй его сам.
`TrimSpace` выглядел гигиеной, а был дырой безопасности.
3. ⚠️ **Библиотека не печатает в stdout.** Логирование — дело потребителя.
4. 📌 **Тест сквозного пути ловит то, что юнит-тест уровня функции пропускает.**
Мой тест дёргал `hostedToolsets` напрямую и не проходил через hydration — дефект №1 был невидим.
Правильный тест — через `f.Build()`.
---
@@ -22,13 +22,12 @@
> Полный разбор: **`personal/tech/kraken-docker-startup-order.md`**.
> Создан 2026-09-26. Обновлён 2026-09-27 (**✅ ФАЗА ЗАКРЫТА — боевая приёмка пройдена, включая
> ВЫЗОВ ИНСТРУМЕНТОВ**).
> 🔄 **ОБНОВЛЕНО 2026-09-28: задеплоен `balda-mm:0.1.14`** — runtime переведён с локального
> форка на **апстрим `v2.1.0`** (PR #4 + #5 влиты), приёмка тулинга и MM пройдена повторно.
> Разбор: [[personal/plans/balda-runtime-rebuild-mm-validation]].
> ВЫЗОВ ИНСТРУМЕНТОВ**). Обновлён **2026-09-28**: текущий образ **`balda-mm:0.1.14`** —
> рантайм переведён на апстрим `v2.1.0` (PR #4 + #5 влиты, форк `replace` убран), тулинг и MM
> **перепроверены в бою от аккаунта Alex**. Детали — [[personal/plans/balda-runtime-rebuild-mm-validation]].
> Статус: **✅ РАБОТАЕТ. Бот отвечает аккаунту Alex и исполняет MCP-инструменты.**
> Текущий образ на Kraken — **`balda-mm:0.1.14`** (см. блок ниже). Первая приёмка боевого
> пути была на `0.1.10` (коммит `9f1e6c9c`, он же PR baldaworks/balda#8) — но тогда
> Текущий образ на Kraken — **`balda-mm:0.1.14`**. Ранее: `0.1.13` (дедуп по эндпоинту),
> первая боевая приёмка на `0.1.10` (коммит `9f1e6c9c`, PR baldaworks/balda#8) — но тогда
> инструменты не исполнялись.
> 🔴 **КОРЕНЬ (исправлено 2026-09-27, проверено кодом):** Balda регистрирует bundled MCP-сервер
> **ДВАЖДЫ на одном URL** — `registry.Set("balda", …)` (`internalmcp/manager.go:213`) и
@@ -112,9 +111,26 @@ ssh kraken '/tmp/mm-feed.sh <token>'
хеш сессии, не её `id`).
⚠️ **Скрипты `mm-post-admin.sh` и `mm-feed.sh` живут только в `/tmp` на Kraken — не переживут
reboot.** Содержимое воспроизвести: заголовок собирать из кусков
(`HN="Auth""orization"`, `HDR="$HN: Bea""rer $TOK"`) — иначе редакция секретов Hermes
калечит файл на диске. Проверять `bash -n` на хосте, не глазами.
reboot.** ✅ **2026-09-28: исходники восстановлены в репозитории** — `~/Developer/balda/tools/`.
Заголовок собирать из кусков (`HN="Auth""orization"`, `HD="${HN}: Bea""rer $V1"`) — иначе редакция
секретов Hermes калечит файл на диске. Проверять `bash -n` на хосте, не глазами.
🔴 **Питфоллы, найденные 2026-09-28 при восстановлении скриптов:**
1. **`docker exec mattermost curl -s … | jq` (пайп на ХОСТЕ) ломается:**
`jq: error (at <stdin>:0): Cannot iterate over null (null)`. Поток exec гонки с `jq`.
**Обход — писать ответ в файл, парсить из файла** (в `mm-feed.sh` это уже сделано):
```bash
OUT="$(mktemp)"; trap 'rm -f "$OUT"' EXIT
docker exec mattermost curl -s -H "$HD" "…/posts?per_page=$N" > "$OUT"
jq -r '…' "$OUT"
```
2. **Правильная jq-форма для posts-endpoint MM:** `.posts as $p | .order[] | $p[.] | …`
(вариант `.order[] as $o | .posts[$o]` падает на вложенном контексте).
3. **После `ssh kraken 'cat > /tmp/x.sh' < tools/x.sh` обязательно `chmod +x`** —
иначе `bash: /tmp/x.sh: Permission denied`.
4. `mm-switch-image.sh` **параметризован**: было хардкод `NEW_TAG="balda-mm:0.1.10"` в теле
(править файл под каждый тег), стало `$1` + usage-проверка `[ $# -ne 1 ]`.
Вызов: `ssh kraken 'bash /tmp/mm-switch-image.sh balda-mm:0.1.15'`.
| Параметр | Значение | ❌ ЧТО НЕ ТАК |
|---|---|---|
@@ -161,6 +177,42 @@ ssh kraken 'docker exec mattermost mmctl --local post list qentra:balda-test 2>&
---
## ✅ ПРИЁМКА ПРОЙДЕНА ПОВТОРНО (2026-09-28, образ `balda-mm:0.1.14`)
Причина пересборки: PR #4 и #5 рантайма **влиты в апстрим** → локальный форк не нужен.
Балда переведена на upstream-runtime **`v2.1.0`**, директива `replace … => ./runtime-local`
удалена. Полный разбор — [[personal/plans/balda-runtime-rebuild-mm-validation]].
**Доказательство тулинга (от аккаунта Alex):**
```
@balda вызови get_vault_stats и покажи результат
→ **Vault stats** - Notes: `504` - Folders: `46` - Size: `~11.6 MB`
```
Лог: `function_response_part_count=1` · `turn_complete=true` · `final_response=true` ·
`duplicate tool:` = **0** · deadletter = **0**. `settlement_stage=accepted`.
⚠️ **`executor_health` падает — НЕ дефект этого деплоя:**
```
rejected by transport: sending "tools/call": Internal Server Error
```
Причина: мост `balda-executor-mcp` на Kraken отдаёт **HTTP 000** + `JSONDecodeError` в логах
(`Up 23 hours`, но неработоспособен). Локальный Mac-бридж `balda-executor` — `Up 2 days (healthy)`.
📌 Доказательство, что конвейер тулинга цел: `get_vault_stats` из **того же** тулсета исполнился.
**Технические параметры деплоя:**
| | |
|---|---|
| Образ | `balda-mm:0.1.14` (`arm64 linux`, 20.8 МБ) |
| Runtime | upstream **`v2.1.0`** (= `c41939f`), без `replace` |
| Хост перед деплоем | load 3.40, available 1950 МБ (RAM-инцидент не повторялся) |
| compose | `0.1.13` → `0.1.14`, бэкап `docker-compose.yml.bak-20260928-202251` |
| Старт | `websocket connected` → `Balda bot started.`, `RestartCount=0` |
⚠️ `0.1.14` собран из дерева **до** merge `6f5695c7` (подтягивание `upstream/main`).
Перед следующим релизом — пересобрать (`0.1.15`).
---
## ✅ ПРИЁМКА ПРОЙДЕНА (2026-09-26, образ `balda-mm:0.1.10`)
**Сквозной путь проверен ОТ АККАУНТА ALEX — не от тестового.** Прошлая «приёмка» от