diff --git a/personal/plans/balda-runtime-rebuild-mm-validation.md b/personal/plans/balda-runtime-rebuild-mm-validation.md index d75cfc09..df356e08 100755 --- a/personal/plans/balda-runtime-rebuild-mm-validation.md +++ b/personal/plans/balda-runtime-rebuild-mm-validation.md @@ -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. ## Связанные заметки diff --git a/personal/plans/runtime-pr5-toolset-dedup-review-fixes.md b/personal/plans/runtime-pr5-toolset-dedup-review-fixes.md index 949d2937..268faad4 100755 --- a/personal/plans/runtime-pr5-toolset-dedup-review-fixes.md +++ b/personal/plans/runtime-pr5-toolset-dedup-review-fixes.md @@ -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()`. --- diff --git a/personal/projects/balda/balda-kraken-mm-deploy.md b/personal/projects/balda/balda-kraken-mm-deploy.md index f5efcf91..061fa703 100755 --- a/personal/projects/balda/balda-kraken-mm-deploy.md +++ b/personal/projects/balda/balda-kraken-mm-deploy.md @@ -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 ' хеш сессии, не её `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 :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 — не от тестового.** Прошлая «приёмка» от