Files
obsidian-vault/personal/plans/balda-runtime-rebuild-mm-validation.md
T

15 KiB
Raw Blame History

Создан 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

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):

cd ~/Developer/balda
docker buildx build --builder arm64builder --platform linux/arm64 \
  -f Dockerfile.release \
  --build-arg VERSION=<ver> --build-arg COMMIT=<sha> \
  --build-arg BUILD_DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ) \
  --provenance=false --load -t balda-mm:<NEXT> .
zstd -3 -T0 -o /tmp/balda-mm-<NEXT>.tar.zst --force <(docker save balda-mm:<NEXT>)
zstd -dc /tmp/balda-mm-<NEXT>.tar.zst | ssh kraken 'docker load'
ssh kraken '/home/kraken/balda/tools/mm-switch-image.sh <NEXT>'   # бэкап 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 toolset». Искать строго grep -c "duplicate tool:" (с двоеточием).

D2. Mattermost — приёмка ОТ АККАУНТА ALEX (не от testuser)

🔴 testuser → settlement_stage=unauthorized. Это не приёмка.

# токен владельца БЕЗ пароля
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 <token> 7bjw4g7o1jdsbmjbqd649xxgaw "@balda <текст>"'
# чтение ленты
ssh kraken '/tmp/mm-feed.sh <token>'

⚠️ Скрипты 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 <check|apply|list> <deltas.json> <state.json>, JSON — плоский массив, не объект). Не sed/in-place.
  5. ⚠️ Мои незакоммиченные правки Balda-слоя (PR #5) — решить судьбу в B0.
  6. ⚠️ Двойной префикс display_name в БД — прошлый раз починили в БД, а не в коде; проверять.

Связанные заметки