> Создан 2026-09-28. Статус: **📋 ПЛАН, ЖДЁТ ОДОБРЕНИЯ ALEX.** > Цель: собрать локально 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]]. --- ## 0. ФАКТЫ, проверенные сейчас (не предположения) | Факт | Значение | |---|---| | `normahq/runtime` PR #4 | **MERGED** 2026-09-28T13:21:59Z, head `22f7f17` | | `normahq/runtime` PR #5 | **MERGED** 2026-09-28T13:22:13Z, head `facb0d0` | | `upstream/main` (runtime) | `c41939f`; содержит `DedupKey` (**6** вхождений) и `openAIToolsFromConfig` (**2**) | | `baldaworks/balda` PR #8 | **OPEN**, head `e0162009`, review **CHANGES_REQUESTED** (2026-09-28T13:54Z) | | Локальный `~/Developer/balda` | `06b2b8d0` — **на 4 коммита позади** `origin/feat/mattermost-channel` | | `~/Developer/balda/runtime-local` | HEAD `06b2b8d0` — **это клон репо balda**, не runtime (!) | ### 🔴 Проблема №1 (блокирующая): `runtime-local` — фикция `~/Developer/balda/runtime-local` — **не отдельный чекаут runtime**, а вложенный git-клон `mallexxx/balda` (HEAD = balda-коммит `06b2b8d0`). В нём: - `openai.go` от PR #4 **вписан руками** (`openAIToolsFromConfig` — 3 вхождения); - `DedupKey` — **0 вхождений**, т.е. PR #5 **отсутствует**. Каталог **git-ignored** в balda (`git ls-files runtime-local` → 0 файлов) и подключён через `replace github.com/normahq/runtime/v2 => ./runtime-local`. В доке [[personal/plans/runtime-pr5-toolset-dedup-review-fixes]] §5 это отмечено как отдельная задача. ⚠️ Мои правки Balda-слоя из PR #5 (`DedupKey` в `internalmcp/manager.go` и `sessionapp/module.go`) в локальном дереве **есть** (M-статус), но **не компилируются** против этой копии. ### 🔴 Проблема №2 (из ревью PR #8): остались 3 блокера Последнее ревью (13:54) подтверждает: canonical chat ingress, command registry, scope classifiers, обязательный `InboundProcessor` — **исправлены**. Осталось **надёжность WS-ingress**: | # | Файл:строка | Суть | |---|---|---| | P1 | `channel/mattermost/ingress.go:389` | При заполнении локального semaphore **событие теряется безвозвратно**: MM не получает ack/nack от клиента и не повторит `posted`. Нужен backpressure на read loop или durable bounded queue с повтором по `post.ID` | | P1 | `ingress.go:483` | `InboundRetry` и `err` только логируются → post считается потерянным. Нужен bounded retry/durable handoff с dedupe по `post.ID`; terminal settlement завершать без повтора. Ошибки `HandleCommand` выше тоже игнорируются | | P2 | `ingress.go:164` | Сначала остановить producer, **потом** `processWG.Wait()`. Сейчас read loop может дойти до `processWG.Add(1)` после начала `waitForProcessing` → `Stop` завершается, не дождавшись воркера. Также нарушение контракта `sync.WaitGroup` | --- ## 1. План — Часть A: runtime-local → чистый upstream **Идея:** `replace` на локальный форк больше не нужен — PR #4 и #5 **влиты в апстрим**. Значит копию можно заменить на настоящий upstream без потери функционала. | Шаг | Действие | Обоснование | |---|---|---| | A1 | Зафиксировать текущее состояние: `git -C ~/Developer/balda/runtime-local rev-parse HEAD`, бэкап каталога в `~/Developer/balda/runtime-local.bak-20260928` | Правило: бэкап ДО изменения | | A2 | Заменить `runtime-local` на чекаут `normahq/runtime` @ `c41939f` (или тега, если есть релиз после merge) | Убирает форк-коппию, снимает вечное расхождение | | A3 | Убрать `replace github.com/normahq/runtime/v2 => ./runtime-local` из `go.mod`, поднять `require` до версии с PR4+PR5 | Штатный путь; форк больше не нужен | | A4 | Если тега после merge нет — оставить `replace` на **чистый upstream**, а не на клон balda | Минимальное изменение, если апстрим ещё не тегирован | | A5 | Обновить комментарий в `go.mod` (сейчас ссылается на PR #4 как на открытый) | Док не должен врать | ⚠️ **Развилка A3/A4 требует решения Alex** — см. §3. **Альтернатива (если править runtime у себя не хочется):** оставить `replace`, но заменить содержимое `runtime-local` на актуальный upstream-чекаут. Тогда Balda-код из PR #5 наконец скомпилируется. --- ## 2. План — Часть B: подтянуть Balda + закрыть 3 блокера PR #8 ### B0. Синхронизация ветки `git fetch` + `git merge --no-rebase origin/feat/mattermost-channel` (правило AGENTS.md: синхронизировать merge'ем, не rebase). Проверить, что мои незакоммиченные правки (`internalmcp/manager.go`, `sessionapp/module.go`, `sessionmemorymcp/broker.go` — это PR #5-слой) не конфликтуют. 🔴 **Решить: коммитить их сейчас или они уже не нужны** (если `DedupKey` приезжает из апстрима — правки Balda-слоя всё равно нужны, чтобы *включить* дедуп). ### B1. P1 — не терять событие при заполнении semaphore (`ingress.go:389`) Сейчас: `acquireSlot()` не смог → лог → post потерян. Направление: **backpressure на read loop** — блокирующее взятие слота до чтения следующего кадра (MM не шлёт ack, повтора не будет). Плюс/или bounded queue с повтором по `post.ID`. 🔴 Выбор между «блокировать read loop» и «durable queue» — **решение Alex** (§3): блокировка проще, но читающий цикл встаёт; очередь честнее, но требует dedupe и хранилища. ### B2. P1 — settlement + retry для retryable failure (`ingress.go:483`) Направление: различать terminal и retryable settlement; retryable — bounded retry с dedupe по `post.ID`; terminal завершать без повтора. Ошибки `HandleCommand` перестать игнорировать. ### B3. P2 — порядок shutdown (`ingress.go:164`) Направление: отменить/закрыть producer → дождаться `done` (новые `Add` невозможны) → и только затем `processWG.Wait()` для drain уже принятых. ### B4. Гейты Balda ```bash go build ./... go vet ./... go test -race ./... go tool golangci-lint run ./... go tool go-arch-lint check --project-path . ``` ### B5. 📌 Проверить гипотезу ревьюера фактом, а не на слово Ревьюер сам пишет: *«The first issue needs a real Mattermost integration check because the current command test injects a synthetic `posted` frame»*. Проверка воспроизводимости — на Kraken живым постом (§C). --- ## 3. ⛔ Требует решения Alex (до начала работ) 1. **A3 vs A4:** убрать `replace` совсем (поднять версию runtime) или заменить `runtime-local` на чистый upstream-чекаут и оставить `replace`? 2. **B1:** backpressure (блокировать read loop) или durable bounded queue с dedupe? 3. **Порядок:** сначала закрыть 3 блокера PR #8, потом собирать/деплоить? Или собрать сейчас (валидация тулинга + MM уже возможна) и блокеры закрыть следом? --- ## 4. План — Часть C: сборка и деплой на Kraken Проверенный порядок (из [[personal/projects/balda/balda-kraken-mm-deploy]]): ```bash cd ~/Developer/balda docker buildx build --builder arm64builder --platform linux/arm64 \ -f Dockerfile.release \ --build-arg VERSION= --build-arg COMMIT= \ --build-arg BUILD_DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ) \ --provenance=false --load -t balda-mm: . zstd -3 -T0 -o /tmp/balda-mm-.tar.zst --force <(docker save balda-mm:) zstd -dc /tmp/balda-mm-.tar.zst | ssh kraken 'docker load' ssh kraken '/home/kraken/balda/tools/mm-switch-image.sh ' # бэкап compose + recreate ``` 🔴 **Обязательные питфоллы (проверенные, не забывать):** 1. `--provenance=false` — иначе `docker load` на Kraken ругается на формат. 2. zstd распаковывать **на Mac** (zstd 1.5.4 на Kraken не читает фрейм macOS 1.5.7). 3. **Все правки → финальный `go build`/`go test` → и только потом docker build.** Сборка, начатая до последней правки, даёт устаревший бинарник. 4. `command: ["start"]` в compose — без подкоманды бинарь печатает help и висит. 5. Образ — **`balda-mm`**, не `claudio-agent`. `-t` определяет, чей контейнер обновится. **Текущий образ на Kraken:** `balda-mm:0.1.13` → следующий **`0.1.14`**. ⚠️ **Проверить состояние хоста ПЕРЕД деплоем** — в доке зафиксировано исчерпание RAM и watchtower-убийца `mm-postgres`, плюс `depends_on` не работает между compose-проектами. `personal/tech/kraken-host-resource-limits.md`, `personal/tech/kraken-docker-startup-order.md`. --- ## 5. План — Часть D: валидация (тулинг + MM) ### D1. Тулинг (MCP-инструменты исполняются) Признак успеха — **вызов инструмента реально исполнен**, не «контейнер Up»: ``` function_response_part_count = 1 duplicate tool: → 0 deadletter → 0 turn_complete / final_response → true ``` ⚠️ **Питфолл своей же проверки:** `grep -c "duplicate tool"` даёт ложную 1, потому что собственная строка лога — «skipping **duplicate tool**set». Искать строго `grep -c "duplicate tool:"` (с двоеточием). ### D2. Mattermost — приёмка ОТ АККАУНТА ALEX (не от `testuser`) 🔴 `testuser` → `settlement_stage=unauthorized`. Это **не** приёмка. ```bash # токен владельца БЕЗ пароля ssh kraken 'docker exec mattermost mmctl --local token generate admin "agent-e2e-$(date +%Y%m%d-%H%M)"' # пост от владельца ssh kraken '/tmp/mm-post-admin.sh 7bjw4g7o1jdsbmjbqd649xxgaw "@balda <текст>"' # чтение ленты ssh kraken '/tmp/mm-feed.sh ' ``` ⚠️ Скрипты `mm-post-admin.sh` / `mm-feed.sh` живут только в `/tmp` на Kraken — **не переживут reboot**. Воспроизводить из дока. Собирать заголовок из кусков (`HN="Auth""orization"`), иначе редакция секретов Hermes калечит файл. **Сценарии проверки (покрывают и блокеры §2):** | # | Сценарий | Что доказывает | |---|---|---| | D2-a | `@balda вызови executor_health` от Alex | тулинг исполняется, scoped-URL выжил (PR #5) | | D2-b | Burst из ~17 сообщений подряд | **не теряется** ни одно (блокер B1) | | D2-c | `docker compose restart balda` во время обработки | drain, нет потерянных постов (B3) | | D2-d | `/topic` / управляющая команда в канале | command ingress работает | | D2-e | Пост из группового DM от двух участников | одна сессия на канал | Метрики: `settlement_stage=accepted`, `bad_seq=0`, `RestartCount=0`. --- ## 6. Риски 1. 🔴 **Смена `runtime-local` — самая опасная правка.** Если в локальной копии есть **незакоммиченные ручные правки**, кроме PR4 (#4 вписан руками), их потеря = регрессия. ⇒ До замены: `diff -r runtime-local ~/Developer/runtime-local` и просмотр отличий от апстрима. 2. 🔴 **RAM на Kraken (4 ГБ, swap 0).** Прошлый раз load 57, MM недоступен. Проверить перед деплоем. 3. ⚠️ `depends_on` между compose-проектами не работает — `balda-mm` может стартовать раньше MM. 4. ⚠️ Правки — **только дельта-апплаером** (`tools/run-project-deltas.sh `, JSON — **плоский массив**, не объект). Не sed/in-place. 5. ⚠️ Мои незакоммиченные правки Balda-слоя (PR #5) — решить судьбу в B0. 6. ⚠️ Двойной префикс `display_name` в БД — прошлый раз починили в БД, а не в коде; проверять. ## Связанные заметки - [[personal/projects/balda/balda-kraken-mm-deploy]] — деплой, питфоллы, скрипты приёмки - [[personal/plans/balda-mm-pr8-review-fixes]] — предыдущий раунд правок PR #8 - [[personal/plans/runtime-pr5-toolset-dedup-review-fixes]] — PR #5, вопрос копии рантайма - [[personal/plans/runtime-pr4-openai-toolcalls-fixes]] — PR #4, tool_calls - [[personal/tech/kraken-host-resource-limits]] · [[personal/tech/kraken-docker-startup-order]] - [[personal/tech/balda-go-fx-pitfalls]] · [[personal/tech/hermes-secret-redaction-bypass]]