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

207 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
> Создан 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=<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 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 <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` в БД — прошлый раз починили в БД, а не в коде; проверять.
## Связанные заметки
- [[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]]