[2026-09-28] eagle: personal/plans/balda-mm-phase4-agents.md personal/plans/balda-mm-pr-plan.md
This commit is contained in:
1 parent
b49ef14a34
commit
a03203fc96
2 files changed
+336
-363
No files matched your search
@@ -25,6 +25,7 @@
|
||||
| 6g | A/B-эксперимент откатом на `0.1.11` | ✅ **доведён 2026-09-27** — `0.1.11` падает С ТЕМ ЖЕ `duplicate tool`. Гипотеза §16.2 («форк обнажил») **ОПРОВЕРГНУТА**: баг был всегда, независимо от раунда. См. §16.4 |
|
||||
| 6h | Фикс дублирующегося тулсета | ✅ **ИСПРАВЛЕНО 2026-09-27, `balda-mm:0.1.13` задеплоен** — дедуп по эндпоинту в `hostedToolsets` (`runtime-local/agentfactory/agentfactory.go`) + 6 тестов. `go build`/`go vet`/`go test ./...` ✅. Приёмка от владельца: `@balda вызови executor_health` → ответ `**executor_health:** ok`, `turn_complete: true`, `duplicate tool:` = **0**, deadletter = **0**. См. §19.3-19.5 |
|
||||
| 7 | Приёмка от аккаунта Alex | ✅ **ПРОЙДЕНА 2026-09-27** — пост владельца `cbwsorzwoigbtrdnteugnx4aqo` → ответ бота `edxrgupoxiyig5gzmr79tjypbme`: `**executor_health:** ok`. `settlement_stage: accepted`, `turn_complete: true`, инструмент исполнился. См. §17, §19.5 |
|
||||
| 8 | 🔴 **CLI исполнителя НЕ залогинен → реальная задача не проходит** | 🔴 **Открыто 2026-09-28** — весь конвейер живой, упирается ТОЛЬКО в логин CLI. `POST /task` через мост → `exit_code: 1`, `duration 0.8 c`, `stdout: "Not logged in · Please run /login"`. Диагноз, доказательства и два пути фикса — **§20**. Ждёт решения Alex |
|
||||
|
||||
---
|
||||
|
||||
@@ -315,8 +316,12 @@ lsof -nP -iTCP:8790 -sTCP:LISTEN
|
||||
🔴 **Продолжение 2 (§§15-16, 2026-09-27):** инструменты всё равно не исполнялись — в
|
||||
`runtime v2` вырезан tool_calls. Форк собран и задеплоен (`0.1.12`), и **обнажил** двойную
|
||||
паковку инструментов (`duplicate tool` вернулся). Разбор — §16.
|
||||
4. 🟡 Логин CLI (Alex вручную) — метод приёмки в обход (admin PAT, §17) есть, но сквозная
|
||||
приёмка упирается в `duplicate tool` (§16).
|
||||
4. 🟡 Логин CLI (Alex вручную) — **ЭТО ЕДИНСТВЕННЫЙ ОСТАВШИЙСЯ БЛОКЕР Фазы 4**
|
||||
(перепроверено 2026-09-28). Прежняя формулировка «сквозная приёмка упирается в
|
||||
`duplicate tool` (§16)» **устарела** — тот дефект закрыт 27.09 (`0.1.13`, §19.5).
|
||||
Сейчас путь такой: конвейер целиком исправен, `executor_health` вызывается и отвечает,
|
||||
но **любая настоящая задача падает на `Not logged in · Please run /login`** — потому что
|
||||
CLI в контейнере-исполнителе не авторизован. Полный диагноз и два пути фикса — **§20**.
|
||||
5. 🔴 **Завести Access Application** на `agents-bridge.qentra.top` — сейчас туннель
|
||||
отвечает 200 без авторизации (проверено). Защита — только `X-Bridge-Secret`.
|
||||
6. ⚠️ **`~/Developer/balda-executor-mcp/docker-compose.yml` в репо → `0.2.0`**;
|
||||
@@ -1298,6 +1303,135 @@ I/O-wait на HDD). Симптомы, которые **не** являются
|
||||
|
||||
---
|
||||
|
||||
## 20. 🔴 Проверка 2026-09-28: единственный блокер — CLI не залогинен
|
||||
|
||||
Сессия началась с вопроса Alex: «Что далее чтобы заставить все это работать?». Проведён
|
||||
**аудит всей цепочки живыми вызовами** (не по докам). Итог: Фаза 4 завершена везде, кроме
|
||||
одного звена — **авторизации CLI внутри контейнера-исполнителя**.
|
||||
|
||||
### 20.1 Что проверено живыми вызовами и РАБОТАЕТ
|
||||
|
||||
| Узел | Проверка | Результат |
|
||||
|---|---|---|
|
||||
| Балда + бот MM на Kraken | `docker ps`, `docker-compose.yml` | `balda-mm:0.1.13` Up 19h ✅ |
|
||||
| Логи Балды | `docker logs balda-mm --since 6h \| grep -c error` | **0** ошибок ✅ |
|
||||
| `duplicate tool` не вернулся | grep по логам | чисто ✅ |
|
||||
| MCP-сервер исполнителя | `initialize` из контейнера `mattermost` | HTTP 200, `serverInfo.name=balda-executor-mcp` ✅ |
|
||||
| Мост на Mac | `curl 127.0.0.1:8790/healthz` | `status: ok`, `container_running: true` ✅ |
|
||||
| Cloudflare Tunnel | `curl https://agents-bridge.qentra.top/healthz` | 200 с обеих сторон ✅ |
|
||||
| Контейнер-исполнитель | `docker ps` | `balda-executor:0.1.0` Up 40h (healthy) ✅ |
|
||||
| Тулчейн в контейнере | `docker exec … which` | `claude` + `codex` + `node` + `git` на месте ✅ |
|
||||
| PR #8 | `gh pr view 8` | `OPEN`, `MERGEABLE`, `reviewDecision: ""` — внешний блокер, не наш ✅ |
|
||||
|
||||
### 20.2 Что СЛОМАНО — ровно одна вещь
|
||||
|
||||
Задача через мост (`POST /task`, `provider: claude`):
|
||||
|
||||
```bash
|
||||
cd ~/Developer/agents-bridge
|
||||
SECRET=*** # из .env, поле BRIDGE_SECRET
|
||||
curl -s -X POST http://127.0.0.1:8790/task \
|
||||
-H "Content-Type: application/json" -H "X-Bridge-Secret: $SECRET" \
|
||||
-H "Idempotency-Key: probe-auth-$(date +%s)" \
|
||||
-d '{"prompt":"Reply with exactly: BRIDGE_OK"}'
|
||||
# → {"task_id":"48fcd4a7d0a44a0c","status":"queued",…}
|
||||
```
|
||||
|
||||
Результат задачи (`GET /task/<id>` с заголовком `X-Bridge-Secret`):
|
||||
|
||||
```json
|
||||
{ "status": "failed", "exit_code": 1, "duration_s": 0.8,
|
||||
"stdout": "Not logged in · Please run /login\n", "stderr": "" }
|
||||
```
|
||||
|
||||
📌 **Конвейер рабочий**: задача дошла до CLI, CLI ответил, ответ вернулся через мост.
|
||||
Падение — внутри CLI, на проверке авторизации.
|
||||
|
||||
### 20.3 Доказательство, что логина нет НА ХОСТЕ (а не просто в контейнере)
|
||||
|
||||
```bash
|
||||
docker exec balda-executor sh -c 'jq -c "keys" /root/.claude.json'
|
||||
# → ["firstStartTime","firstStartVersion","hasResetAutoModeOptInForDefaultOffer",
|
||||
# "machineID","migrationVersion","opusProMigrationComplete","pluginUsage",
|
||||
# "seenNotifications","sonnet1m45MigrationComplete","userID"]
|
||||
```
|
||||
|
||||
Это **свежий стартовый файл**: 9 служебных ключей, **ключа `oauthAccount` нет**.
|
||||
Никаких следов авторизации.
|
||||
|
||||
```bash
|
||||
ls -la ~/cli-auth/claude/
|
||||
# → backups/ projects/ sessions/ ← монтируется в /root/.claude
|
||||
ls -la ~/cli-auth/claude/backups/
|
||||
# → .claude.json.backup.1790423979201 (84 байта)
|
||||
# .claude.json.backup.1790435884422 (84 байта)
|
||||
```
|
||||
|
||||
🔴 **В хост-монтировании `~/cli-auth/claude/` НЕТ файла `.claude.json`.**
|
||||
Лежат только `backups/`, `projects/`, `sessions/`. Тот файл в контейнере (`692 байта`,
|
||||
`Sep 26 15:18`) — это **контейнерная копия, созданная самим CLI**, а не проброшенный
|
||||
логин. Бэкапы по 84 байта — тоже не креды.
|
||||
|
||||
**Корень:** логин никогда не был посажен туда, откуда он переживает пересоздание контейнера.
|
||||
`docker compose up -d` пересоздаёт контейнер, монтирование отдаёт пустой набор файлов
|
||||
(только каталоги), CLI генерирует себе пустой `.claude.json` с нуля → каждый раз «Not logged in».
|
||||
|
||||
### 20.4 Контекст: как CLI вызывается
|
||||
|
||||
`~/Developer/agents-bridge/executor.py:21-25`:
|
||||
|
||||
```python
|
||||
PROVIDERS: dict[str, list[str]] = {
|
||||
"claude": ["claude", "-p", "--output-format", "text"],
|
||||
"codex": ["codex", "exec", "--skip-git-repo-check"],
|
||||
}
|
||||
# :41 — запуск происходит НЕ на хосте, а через docker exec в контейнер
|
||||
return ["docker", "exec", "-w", WORKDIR, "@container", *base, prompt]
|
||||
```
|
||||
|
||||
Значит авторизация нужна **внутри контейнера**, а не на Mac. `WORKDIR = "/agents"`.
|
||||
|
||||
### 20.5 Два пути фикса (решение за Alex — ждёт ответа)
|
||||
|
||||
**Путь A (рекомендован) — `claude setup-token`, токен в переменную окружения.**
|
||||
|
||||
Почему лучше: долгоживущий токен **не файл** → пересоздание контейнера его не затирает.
|
||||
Документированный Anthropic путь для headless (см. §0.6 «Юридическая граница» — разрешён,
|
||||
не дыра).
|
||||
|
||||
1. **Alex на Mac:** `claude setup-token` (открывает браузер, аккаунт подписки) → получает токен.
|
||||
2. Токен кладём в `~/Docker/balda-executor/.env` как `CLAUDE_CODE_OAUTH_TOKEN=…`
|
||||
(`.env` — `0600`, уже в `env_file` compose → в контейнер попадает автоматически).
|
||||
3. Проверка: задача через мост → ждём ответ модели вместо `Not logged in`.
|
||||
|
||||
⚠️ **Питфолл записи в `.env`:** `write_file` редактирует/калечит секреты
|
||||
(`personal/tech/hermes-secret-redaction-bypass`) — значение собирать **на хосте**
|
||||
скриптом, не литералом.
|
||||
|
||||
**Путь B — залогиниться в контейнере, затем СКОПИРОВАТЬ креды на хост.**
|
||||
|
||||
1. **Alex:** `docker exec -it balda-executor claude` → `/login`.
|
||||
2. Я **копирую** (не перемещаю) `~/.claude.json` и `.credentials.json` из контейнера в
|
||||
`~/cli-auth/claude/` **с бэкапом** существующего каталога.
|
||||
3. Проверка: `docker compose up -d` (пересоздание) → повторить задачу → логин должен выжить.
|
||||
|
||||
Почему B хуже: зависит от того, что CLI держит всё в файлах и что структура не поменяется;
|
||||
`setup-token` — официально поддерживаемый для headless путь.
|
||||
|
||||
**Не делать:** логиниться на голом Mac в `~/.claude` и ждать, что это проедет в контейнер —
|
||||
монтируется `~/cli-auth/claude`, **не** `~/.claude`. На хосте Mac логин вообще не участвует.
|
||||
|
||||
### 20.6 Что проверить ПОСЛЕ фикса (сквозная приёмка Фазы 4)
|
||||
|
||||
1. `POST /task` через мост → реальный ответ модели, `exit_code: 0`.
|
||||
2. Пост от **владельца** в MM (`@balda …`) → Балда → `executor_run` → мост → CLI → ответ в чат.
|
||||
Метод постинга от владельца — §17.1 (admin PAT через `mmctl --local token generate`).
|
||||
3. `codex` тоже проверить (второй провайдер в том же образе).
|
||||
4. Обновить §0: строка 8 → ✅, Фаза 4 → закрыта; Фаза 5 (связка и приёмка) в
|
||||
[[personal/projects/balda/balda-mm-delegation-plan]].
|
||||
|
||||
---
|
||||
|
||||
## Связанные заметки
|
||||
- [[personal/projects/balda/balda-mm-delegation-plan]] — общий план (Фазы 4-5)
|
||||
- [[personal/projects/balda/balda-kraken-mm-deploy]] — Фаза 3 (✅)
|
||||
|
||||
+200
-361
@@ -1,44 +1,83 @@
|
||||
# Balda — Mattermost connector: PR-подготовка (закрыто)
|
||||
# Balda — Mattermost connector: PR-подготовка
|
||||
|
||||
> Создан 2026-09-26. Обновлён **2026-09-28** (сверка PR + разбор незакоммиченных файлов).
|
||||
> Статус: **✅ PR ОТКРЫТ — baldaworks/balda#8, 8/8 CI PASS, MERGEABLE. Ревью апстрима НЕТ (`reviewDecision` пустой).**
|
||||
> Обновлён 2026-09-28. Обновлён 2026-09-28 (ревизия: убраны дубли, оставлены факты).
|
||||
> Статус: **PR ОТКРЫТ — baldaworks/balda#8 · OPEN · MERGEABLE · 8/8 CI PASS · ревью апстрима НЕТ.**
|
||||
> https://github.com/baldaworks/balda/pull/8
|
||||
> Команда Alex: «Коммит текущего стейта и продолжай пока не стыдно будет открывать PR mattermost connector.
|
||||
> Со всеми поддерживаемыми фичами и тестами. Вперёд! Без вопросов и требующих апрува команд.»
|
||||
> Часть [[personal/projects/balda/balda-mm-delegation-plan]] (Фаза 2 → PR в апстрим).
|
||||
>
|
||||
> 📌 **Читать два раздела 2026-09-28 ниже:** «5 незакоммиченных файлов» (НЕ фиксы, не трогать)
|
||||
> и «Проверка состояния PR» (следующие шаги по приоритету).
|
||||
|
||||
## ✅ БОЕВАЯ ПРИЁМКА ПРОЙДЕНА — 2026-09-26 (образ `0.1.10`, коммит `9f1e6c9c`)
|
||||
---
|
||||
|
||||
**Сквозной путь работает ОТ АККАУНТА ALEX.** Это первая настоящая приёмка (прошлая была
|
||||
ложной — от `testuser`).
|
||||
## 1. Состояние сейчас (сверено 2026-09-28 через API, не по доке)
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| PR | **baldaworks/balda#8** — `feat(mattermost): add Mattermost channel transport` |
|
||||
| Состояние | `OPEN` · `MERGEABLE` · `reviewDecision` **пустой** |
|
||||
| База / голова | `baldaworks/balda:main` ← `mallexxx:feat/mattermost-channel` |
|
||||
| Объём | **41 файл, +5622 / −18**, 7 коммитов |
|
||||
| CI | **8/8 PASS** |
|
||||
|
||||
```
|
||||
architecture-lint pass 2m2s
|
||||
ingress-e2e pass 30s
|
||||
jetstream-integration pass 1m0s
|
||||
lint pass 2m46s
|
||||
postgres-integration pass 57s
|
||||
projection-replay pass 26s
|
||||
sqlite-integration pass 1m15s
|
||||
test pass 6m0s ← главный: полный go test на чистом раннере
|
||||
```
|
||||
|
||||
**Ничего не изменилось с 26.09** — новых проверок нет, ревью апстрима не состоялось.
|
||||
|
||||
🔴 **Блокер: `reviewDecision` пустой = PR никто не смотрел.** Мержа не будет, пока внешний
|
||||
ревьюер не заглянет. Наш ход тут — только периодически перепроверять
|
||||
`gh pr view 8 --repo baldaworks/balda --json reviewDecision`.
|
||||
|
||||
🔴 **Целевой репозиторий — `baldaworks/balda`, НЕ `normahq/balda`.** Проверено через API,
|
||||
не по названию remote'а:
|
||||
|
||||
```
|
||||
gh repo view mallexxx/balda --json parent
|
||||
→ parent: baldaworks/balda ← форк от него
|
||||
gh repo view normahq/balda --json owner
|
||||
→ owner.id: O_kgDOEgAHrw (= baldaworks) ← редирект, не отдельный репо
|
||||
```
|
||||
|
||||
`origin` = `mallexxx/balda` (форк), `upstream` = `normahq/balda` (редирект на baldaworks).
|
||||
PR создавать **в `baldaworks/balda`**, head указывать как `mallexxx:<branch>` — не полный
|
||||
URL форка, иначе `gh` путается.
|
||||
|
||||
---
|
||||
|
||||
## 2. Боевая приёмка — ПРОЙДЕНА 2026-09-26 (образ `0.1.10`, коммит `9f1e6c9c`)
|
||||
|
||||
Сквозной путь работает **от аккаунта Alex**. Это первая настоящая приёмка — прошлая была
|
||||
ложной (от `testuser`).
|
||||
|
||||
### Доказательство — лента канала `balda-test`
|
||||
|
||||
```
|
||||
1790413414689 | edxrgupoxiyg5gzmr79tjypbme | слово ← ОТВЕТ БОТА
|
||||
1790413414689 | edxrgupoxiyig5gzmr79tjypbme | слово ← ОТВЕТ БОТА
|
||||
1790413409055 | cbwsorzwoigbtrdnteugnx4aqo | @balda скажи одно слово ← пост ALEX
|
||||
```
|
||||
|
||||
Запрос — `cbwsorzwoigbtrdnteugnx4aqo` (**аккаунт Alex**, `admin`), ответ — бот.
|
||||
В логах: **`settlement_stage=accepted`** (НЕ `unauthorized`!), `assembled provider user content`,
|
||||
В логах: **`settlement_stage=accepted`** (НЕ `unauthorized`), `assembled provider user content`,
|
||||
`delivery settled`.
|
||||
|
||||
### Состояние привязки владельца — починено
|
||||
### Привязка владельца — починена
|
||||
|
||||
```
|
||||
balda_user_bindings:
|
||||
channel_type = mattermost
|
||||
principal = cbwsorzwoigbtrdnteugnx4aqo ← БЕЗ префикса ✅ (иначе unauthorized)
|
||||
principal = cbwsorzwoigbtrdnteugnx4aqo ← БЕЗ префикса ✅ (иначе unauthorized)
|
||||
display_name = mattermost:cbwsorzwoigbtrdnteugnx4aqo ← ОДИНАРНЫЙ префикс ✅ (нет мусора)
|
||||
```
|
||||
|
||||
Прошлый дефект двойного префикса вылечен **в коде** (идемпотентный `MattermostSubject`),
|
||||
в БД мусора нет.
|
||||
Дефект двойного префикса вылечен **в коде** (идемпотентный `MattermostSubject`), в БД мусора нет.
|
||||
|
||||
### WS-здоровье
|
||||
### WS-здоровье и старт
|
||||
|
||||
| Метрика | Значение |
|
||||
|---|---|
|
||||
@@ -48,29 +87,29 @@ balda_user_bindings:
|
||||
| `RestartCount` | **0** ✅ |
|
||||
| Контейнер | `balda-mm:0.1.10` Up стабильно |
|
||||
|
||||
### Старт — чистый
|
||||
|
||||
```
|
||||
mattermost transport configured server_url=http://mattermost:8065 bot_user_id_set=true
|
||||
session manager ready balda_provider=deepseek
|
||||
mattermost websocket connected ws://mattermost:8065/api/v4/websocket
|
||||
Balda bot started.
|
||||
```
|
||||
Никакого crash-loop, `advertised commands have no actor handler` **отсутствует**.
|
||||
|
||||
### ⚠️ Два не-блокера, которые стоит знать
|
||||
Никакого crash-loop, `advertised commands have no actor handler` отсутствует.
|
||||
|
||||
1. **`warn: getMe response missing result`** при старте. Косметика: startup-лог не смог
|
||||
прочитать identity бота. На работу НЕ влияет — `bot_user_id_set=true` из конфига, ответы
|
||||
идут. Стоит починить отдельно (low priority, в апстриме).
|
||||
2. **`transport_user_id = c6udeze8dfrdxm5jbgq55bd58r` в логах сессии** — это **`testuser`**,
|
||||
id прошлого владельца, зашитый в **persisted-метаданные сессии** `mm-c-7aafea13` (создана
|
||||
в прошлой сессии от testuser). Авторизация при этом прошла верно (по `principal` из
|
||||
bindings), ответ ушёл Alex'у. **Но это грязь:** сессия хранит чужой `user_id`.
|
||||
🔴 Если Alex хочет чистую сессию — `/reset` в канале создаст новую с его `user_id`.
|
||||
### Два не-блокера
|
||||
|
||||
1. **`warn: getMe response missing result`** при старте — косметика: startup-лог не смог
|
||||
прочитать identity бота. На работу НЕ влияет (`bot_user_id_set=true` из конфига).
|
||||
Low priority, чинить в апстриме.
|
||||
2. **`transport_user_id = c6udeze8dfrdxm5jbgq55bd58r`** в логах сессии — это `testuser`,
|
||||
id прошлого владельца, зашитый в persisted-метаданные сессии `mm-c-7aafea13` (создана
|
||||
в прошлой сессии от testuser). Авторизация прошла верно (по `principal` из bindings),
|
||||
ответ ушёл Alex'у, **но сессия хранит чужой `user_id`**. Чистится `/reset` в канале.
|
||||
На работу не влияет (проверено: ответ пришёл).
|
||||
|
||||
### Деплой — как сделано (воспроизводимо)
|
||||
---
|
||||
|
||||
## 3. Сборка и деплой (воспроизводимо)
|
||||
|
||||
```bash
|
||||
# 1. образ из PR-коммита (на Mac)
|
||||
@@ -86,83 +125,99 @@ docker save balda-mm:0.1.10 -o /tmp/balda-0110.tar
|
||||
zstd -3 -T0 -o /tmp/balda-0110.tar.zst --force /tmp/balda-0110.tar
|
||||
zstd -dc /tmp/balda-0110.tar.zst | ssh kraken 'docker load'
|
||||
|
||||
# 3. переключение с бэкапом compose (скрипт tools/mm-switch-image.sh)
|
||||
# 3. переключение с бэкапом compose
|
||||
ssh kraken "cat > /tmp/mm-switch-image.sh" < tools/mm-switch-image.sh
|
||||
ssh kraken 'bash -n /tmp/mm-switch-image.sh' < /dev/null # SYNTAX_OK
|
||||
ssh kraken 'bash /tmp/mm-switch-image.sh' < /dev/null
|
||||
```
|
||||
|
||||
**Скрипты, созданные для этого деплоя:**
|
||||
| Скрипт | Что делает |
|
||||
|---|---|
|
||||
| `tools/mm-switch-image.sh` | смена тега образа в compose + бэкап + `--force-recreate` |
|
||||
| `tools/mm-whoami.sh` | какой MM-аккаунт у PAT (`/users/me`) — нужен для приёмки от заказчика |
|
||||
|
||||
⚠️ **Проверка CA в образе:** `docker save` даёт **OCI**-формат, где файлы в
|
||||
`blobs/sha256/*`; `tar -tf image.tar | grep ca-certificates` даст **0** (ложный минус).
|
||||
Проверять по слоям: распаковать и искать в `blobs/`, или верить `docker inspect` →
|
||||
`SSL_CERT_FILE` в `Config.Env`.
|
||||
⚠️ **Проверка CA в образе:** `docker save` даёт **OCI**-формат, files в `blobs/sha256/*`;
|
||||
`tar -tf image.tar | grep ca-certificates` даст **0** (ложный минус). Проверять по слоям или
|
||||
верить `docker inspect` → `SSL_CERT_FILE` в `Config.Env`.
|
||||
|
||||
⚠️ **Приёмка обязывает иметь PAT заказчика.** `/tmp/admin-tok` = PAT Alex
|
||||
(`cbwsorzwoigbtrdnteugnx4aqo`, `admin`) — сверено через `mm-whoami.sh` **до** постинга,
|
||||
а не на глаз. Без этого тест снова был бы от `testuser` = ложная приёмка.
|
||||
(`cbwsorzwoigbtrdnteugnx4aqo`, `admin`) — сверено через `mm-whoami.sh` **до** постинга.
|
||||
Без этого тест снова был бы от `testuser` = ложная приёмка.
|
||||
|
||||
---
|
||||
|
||||
## ✅ PR ОТКРЫТ — https://github.com/baldaworks/balda/pull/8 (2026-09-26)
|
||||
## 4. Что в PR
|
||||
|
||||
**Статус: OPEN · MERGEABLE · все 8 CI-джобов PASS.**
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| PR | **baldaworks/balda#8** |
|
||||
| База | `baldaworks/balda:main` |
|
||||
| Голова | `mallexxx:feat/mattermost-channel` |
|
||||
| Объём | **41 файл, +5622 / −18** |
|
||||
| Коммитов | 7 |
|
||||
| Mergeable | ✅ MERGEABLE |
|
||||
|
||||
### 🔴 Целевой репозиторий — `baldaworks/balda`, не `normahq/balda`
|
||||
|
||||
Проверено через API, а не по названию remote'а:
|
||||
```
|
||||
gh repo view mallexxx/balda --json parent
|
||||
→ parent: baldaworks/balda ← форк от него
|
||||
gh repo view normahq/balda --json owner
|
||||
→ owner.id: O_kgDOEgAHrw (= baldaworks) ← редирект, не отдельный репо
|
||||
```
|
||||
`origin` = `mallexxx/balda` (форк), `upstream` = `normahq/balda` (редирект на baldaworks).
|
||||
**PR создавать в `baldaworks/balda`**, head указывать как `mallexxx:<branch>`
|
||||
(не полный URL форка — иначе `gh` путается).
|
||||
|
||||
### База ветки — идеальная
|
||||
|
||||
`git rev-list --left-right --count upstream/main...HEAD` → `0 7` (0 позади, 7 впереди).
|
||||
`merge-base HEAD upstream/main` == `upstream/main` == `de273d8f`. Rebase не потребовался.
|
||||
|
||||
### CI — 8/8 PASS (прогон на чистом раннере)
|
||||
### Коммиты (7)
|
||||
|
||||
```
|
||||
architecture-lint pass 2m2s
|
||||
ingress-e2e pass 30s
|
||||
jetstream-integration pass 1m0s
|
||||
lint pass 2m46s
|
||||
postgres-integration pass 57s
|
||||
projection-replay pass 26s
|
||||
sqlite-integration pass 1m15s
|
||||
test pass 6m0s ← главный: полный go test на чистом раннере
|
||||
9f1e6c9c chore: ignore local project-editing tooling
|
||||
58f27120 docs(mattermost): document the transport setup and behavior
|
||||
80d4f41d test(mattermost): cover the transport, the ingress and the handler
|
||||
c7f18654 fix(mattermost): send a monotonic seq on every client frame
|
||||
6dc3077a feat(mattermost): add the inbound handler and wire it into DI
|
||||
fa1a2855 feat(auth): support the Mattermost channel subject
|
||||
004fda2f feat(mattermost): add Mattermost transport adapter ← база прошлой сессии
|
||||
```
|
||||
|
||||
📌 **Это и есть настоящая приёмка сборки.** Локально `cmd/balda` падал на
|
||||
`TestInitCommand_NonInteractiveAutoSelectsRootAndGeneratesDetectedAgents` — в CI он
|
||||
**прошёл**, то есть падение было особенностью моей локальной среды, а не дефектом.
|
||||
CI на чистом раннере закрывает ровно тот класс проблем, который локальные гейты не видят.
|
||||
База ветки идеальная: `git rev-list --left-right --count upstream/main...HEAD` → `0 7`
|
||||
(0 позади, 7 впереди), `merge-base` == `upstream/main` == `de273d8f`. Rebase не потребовался.
|
||||
|
||||
⚠️ **CI-пайплайн в репо ЕСТЬ** (`architecture-lint`, `lint`, `test`, `ingress-e2e`,
|
||||
`jetstream-integration`, `postgres-integration`, `sqlite-integration`, `projection-replay`).
|
||||
Значит локальные гейты — необходимое, но не достаточное условие; ждать CI обязательно.
|
||||
### Гейты — все зелёные на закоммиченном дереве
|
||||
|
||||
### Команды (воспроизводимо)
|
||||
```
|
||||
go build ./... → exit 0
|
||||
go vet ./... → exit 0
|
||||
go tool golangci-lint run → 0 issues
|
||||
go tool go-arch-lint check --project-path . → OK - No warnings found
|
||||
go test -race (затронутые пакеты) → все ok
|
||||
тесты mattermost + handlersfx (verbose) → 126 PASS, 0 FAIL, 0 SKIP
|
||||
```
|
||||
|
||||
📌 **Настоящая приёмка сборки — CI на чистом раннере.** Локально `cmd/balda` падал на
|
||||
`TestInitCommand_NonInteractiveAutoSelectsRootAndGeneratesDetectedAgents` — в CI он **прошёл**,
|
||||
то есть падение было особенностью локальной среды, а не дефектом. Локальные гейты —
|
||||
необходимое, но не достаточное условие; ждать CI обязательно.
|
||||
|
||||
### Тесты — 103 добавлено (`grep -c '^func Test'`)
|
||||
|
||||
| Файл | Тестов | Покрытие |
|
||||
|---|---|---|
|
||||
| `channel/mattermost/ingress_test.go` | 23 | 🔴 `bad_seq`-регрессия, монотонность и уникальность `seq` под 16 горутинами, фильтры, panic-recovery, семафор |
|
||||
| `channel/mattermost/inbound_test.go` | 18 | `NormalizeInbound`, mention-фильтр, `ParseCommand`, whitelist команд, `PostIDFromWebSocket` |
|
||||
| `channel/mattermost/adapter_test.go` | 17 | все `deliverycmd.Operation`, plain-fallback только на content-rejection, draft-as-post, typing no-op без сети, `root_id` в треде |
|
||||
| `handlersfx/mattermost_ingress_test.go` | 17 | публикация всех команд, **одиночный** префикс principal, `/start` до авторизации, отказ чужим, nil-safety |
|
||||
| `channel/mattermost/locator_codec_test.go` | 15 | локаторы, thread↔channel изоляция, `LocatorFromAddressKey`, scope, UserID |
|
||||
| `channel/mattermost/client_test.go` | 13 | ws/wss derivation, `Bearer`, `APIError`, `isContentRejectedError`, контекст |
|
||||
|
||||
### Что починено по существу
|
||||
|
||||
1. **Дефект двойного префикса — в коде, а не в БД.** Прошлый фикс был прямым `UPDATE` в
|
||||
`state.db`, поэтому дефект вернулся. Теперь `auth.MattermostSubject()` идемпотентен (срезает
|
||||
уже имеющийся префикс `mattermost:` перед повторной квалификацией), а из
|
||||
`actors/command/start/handler.go` убран локальный `TrimPrefix`-костыль.
|
||||
Почему ломало прод: `canonicalSubject` режет subject по **первому** `:`, а `GetUserByBinding`
|
||||
сравнивает **точно** → `mattermost:mattermost:<id>` не совпадает ни с чем → каждый пост Alex
|
||||
отбивался `settlement_stage=unauthorized`.
|
||||
Регрессия: `TestMattermostSubjectIsIdempotent`,
|
||||
`TestMattermostHandlerSendsChannelQualifiedPrincipalExactlyOnce`.
|
||||
2. **Противоречие `ProgressPolicy.Typing`.** Command-путь ставил `Typing: true`, inbound-путь —
|
||||
`false`, а `SendTyping` у MM-адаптера осознанный no-op (у ботов MM нет typing API). Мёртвая
|
||||
ветка + warn-шум на каждом ходу → приведено к `false`.
|
||||
3. **Доки** — `README.md`, `docs/balda.md`, новый `docs/mattermost.md` (создание бота, минтинг
|
||||
токена — отдаётся ОДИН раз, membership в команде/канале, 5 ключей конфига, поведение в
|
||||
DM/канале/треде, troubleshooting `bad_seq`/`unauthorized`/`x509`).
|
||||
|
||||
### Предсуществующие падения — НЕ наши (проверено `git stash`)
|
||||
|
||||
5 пакетов: `cmd/balda`, `apps/balda`, `mcpruntime`, `session`, `state`. Те же 4 теста падают
|
||||
**без** наших правок:
|
||||
`TestInitCommand_NonInteractiveAutoSelectsRootAndGeneratesDetectedAgents`,
|
||||
`TestCanonicalSessionMemoryRuntimeComposesPortableCapabilities`,
|
||||
`TestCanonicalSessionMemoryRuntimeMigratesAndReopensGroupedStore`,
|
||||
`TestResolveWorkspaceBaseBranch_ConfigPreferredWhenValid`.
|
||||
|
||||
### Команды PR (воспроизводимо)
|
||||
|
||||
```bash
|
||||
cd ~/Developer/balda
|
||||
@@ -179,306 +234,90 @@ gh pr view 8 --repo baldaworks/balda --json state,mergeable,changedFiles,additio
|
||||
gh pr checks 8 --repo baldaworks/balda
|
||||
```
|
||||
|
||||
### Что осталось
|
||||
|
||||
1. **Ревью апстрима** — ждать. Правки по замечаниям делать в этой же ветке (push обновит PR).
|
||||
2. **Боевая проверка на Kraken** — PR-merge ≠ работает на стенде. Пересобрать образ из
|
||||
`9f1e6c9c`, задеплоить, `@balda` → ответ в ленте канала (см.
|
||||
[[personal/projects/balda/balda-kraken-mm-deploy]]).
|
||||
3. Фаза 4 (исполнитель) — [[personal/projects/balda/balda-mm-delegation-plan]].
|
||||
|
||||
---
|
||||
|
||||
## ✅ Итог: ветка PR-clean (2026-09-26, до пуша)
|
||||
## 5. Пять файлов на диске — зачем они, НЕ трогать
|
||||
|
||||
**7 коммитов**, рабочее дерево **чистое**, `tools/` **исключён** из ветки.
|
||||
В `~/Developer/balda` пять файлов изменены относительно коммита `9f1e6c9c` и **в PR не входят**.
|
||||
Это **не фиксы** и **не потерянный код** — так и задумано.
|
||||
|
||||
```
|
||||
9f1e6c9c chore: ignore local project-editing tooling
|
||||
58f27120 docs(mattermost): document the transport setup and behavior
|
||||
80d4f41d test(mattermost): cover the transport, the ingress and the handler
|
||||
c7f18654 fix(mattermost): send a monotonic seq on every client frame
|
||||
6dc3077a feat(mattermost): add the inbound handler and wire it into DI
|
||||
fa1a2855 feat(auth): support the Mattermost channel subject
|
||||
004fda2f feat(mattermost): add Mattermost transport adapter ← база прошлой сессии
|
||||
```
|
||||
|
||||
### 🔴 Уборка `tools/` — важный урок про PR
|
||||
|
||||
База `004fda2f` (прошлая сессия, **не мои 5 коммитов**) затащила в ветку **16 файлов
|
||||
`tools/`**: `apply-project-deltas.py`, `run-project-deltas.sh`, `deltas-mattermost*.json`,
|
||||
`.deltas-state.json`. **В апстриме каталога `tools/` нет вообще** — проверено
|
||||
`git ls-tree upstream/main tools/` → пусто.
|
||||
|
||||
**Как найдено:** `git diff --stat upstream/main...HEAD` показал `tools/` в списке файлов PR.
|
||||
|
||||
**Фикс (коммит `9f1e6c9c`):**
|
||||
1. `/tools/` добавлен в `.gitignore`;
|
||||
2. `git rm -r --cached tools/` — файлы **остались на диске** (39 файлов), ушли только из git.
|
||||
|
||||
⚠️ **`gitignore` НЕ отвязывает уже отслеживаемые файлы** — нужен именно `git rm --cached`.
|
||||
⚠️ **Проверять состав PR через `git diff upstream/main...HEAD`, а не по своим коммитам.**
|
||||
Свои 5 коммитов были чистыми (`git log upstream..HEAD -- tools/` → пусто), но ветка в
|
||||
целом — нет, из-за базового коммита.
|
||||
|
||||
**Результат:** в PR-diff **41 файл**, все — файлы проекта, ни одного `tools/`.
|
||||
|
||||
### Финальные гейты — все зелёные на закоммиченном дереве
|
||||
|
||||
```
|
||||
go build ./... → exit 0
|
||||
go vet ./... → exit 0
|
||||
go tool golangci-lint run → 0 issues
|
||||
go tool go-arch-lint check --project-path . → OK - No warnings found
|
||||
go test -race (затронутые пакеты) → все ok
|
||||
тесты mattermost + handlersfx (verbose) → 126 PASS, 0 FAIL, 0 SKIP
|
||||
```
|
||||
|
||||
**Тестов добавлено:** **103** (`grep -c '^func Test'`), по файлам:
|
||||
|
||||
| Файл | Тестов |
|
||||
|---|---|
|
||||
| `channel/mattermost/ingress_test.go` | 23 |
|
||||
| `channel/mattermost/inbound_test.go` | 18 |
|
||||
| `channel/mattermost/adapter_test.go` | 17 |
|
||||
| `handlersfx/mattermost_ingress_test.go` | 17 |
|
||||
| `channel/mattermost/locator_codec_test.go` | 15 |
|
||||
| `channel/mattermost/client_test.go` | 13 |
|
||||
|
||||
### Предсуществующие падения — НЕ наши (проверено `git stash`)
|
||||
|
||||
5 пакетов: `cmd/balda`, `apps/balda`, `mcpruntime`, `session`, `state`. Те же 4 теста
|
||||
падают **без** моих правок:
|
||||
`TestInitCommand_NonInteractiveAutoSelectsRootAndGeneratesDetectedAgents`,
|
||||
`TestCanonicalSessionMemoryRuntimeComposesPortableCapabilities`,
|
||||
`TestCanonicalSessionMemoryRuntimeMigratesAndReopensGroupedStore`,
|
||||
`TestResolveWorkspaceBaseBranch_ConfigPreferredWhenValid`.
|
||||
|
||||
### Лишнее, что я НЕ отправляю в PR
|
||||
|
||||
`tools/mm-*.sh` (9 ops-скриптов), `tools/deltas-*.json` (16 файлов дельта-групп),
|
||||
`tools/apply-project-deltas.py`, `tools/run-project-deltas.sh`, `tools/.deltas-state.json`
|
||||
— локальный инструментарий, остаётся на диске, в git не идёт.
|
||||
|
||||
### Осталось (по желанию Alex)
|
||||
|
||||
1. `git push -u origin feat/mattermost-channel` → открыть PR в `baldaworks/balda`.
|
||||
2. Фаза 3/4 — [[personal/projects/balda/balda-kraken-mm-deploy]].
|
||||
|
||||
---
|
||||
|
||||
## Итог (промежуточный, до уборки tools/)
|
||||
|
||||
Ветка `feat/mattermost-channel`, **6 коммитов** (базовый `004fda2f` + 5 новых). Готово к `git push` и PR.
|
||||
|
||||
```
|
||||
58f27120 docs(mattermost): document the transport setup and behavior
|
||||
80d4f41d test(mattermost): cover the transport, the ingress and the handler
|
||||
c7f18654 fix(mattermost): send a monotonic seq on every client frame
|
||||
6dc3077a feat(mattermost): add the inbound handler and wire it into DI
|
||||
fa1a2855 feat(auth): support the Mattermost channel subject
|
||||
004fda2f feat(mattermost): add Mattermost transport adapter ← база
|
||||
```
|
||||
|
||||
## Quality gates — все зелёные (проверено на закоммиченном дереве)
|
||||
|
||||
```
|
||||
go build ./... → exit 0
|
||||
go vet ./... → exit 0
|
||||
go tool golangci-lint run → 0 issues
|
||||
go tool go-arch-lint check --project-path . → OK - No warnings found
|
||||
go test -race ./channel/mattermost/ ./handlersfx/ ./auth/ ./deliveryfmt/
|
||||
./actors/command/... → все ok, включая start и auth
|
||||
```
|
||||
|
||||
## Что сделано в этом проходе
|
||||
|
||||
### 1. Дефект двойного префикса — починен В КОДЕ, а не в БД (главное)
|
||||
|
||||
Прошлый фикс был прямым `UPDATE` в `state.db`, поэтому дефект вернулся.
|
||||
Теперь источник закрыт:
|
||||
|
||||
- `auth.MattermostSubject()` **идемпотентен** — срезает уже имеющийся префикс
|
||||
`mattermost:` перед повторной квалификацией;
|
||||
- из `actors/command/start/handler.go` убран локальный `TrimPrefix`-костыль,
|
||||
ветка стала как у остальных транспортов: `auth.MattermostSubject(trimmed)`.
|
||||
|
||||
Почему это ломало прод: `canonicalSubject` режет subject по **первому** `:`, а
|
||||
`GetUserByBinding` сравнивает **точно**. Значение `mattermost:mattermost:<id>`
|
||||
в `principal` не совпадает ни с чем → каждый пост Alex отбивался
|
||||
`settlement_stage=unauthorized`.
|
||||
**Тест-регрессия:** `TestMattermostSubjectIsIdempotent`
|
||||
(`handlersfx/mattermost_ingress_test.go`) + `TestMattermostHandlerSendsChannelQualifiedPrincipalExactlyOnce`.
|
||||
|
||||
### 2. Исправлено противоречие `ProgressPolicy.Typing`
|
||||
|
||||
Command-путь ставил `Typing: true`, тогда как inbound-путь — `false`, а
|
||||
`SendTyping` у MM-адаптера **осознанный no-op** (у ботов MM нет typing API).
|
||||
Мёртвая ветка + warn-шум на каждом ходу. Приведено к `false`.
|
||||
|
||||
### 3. Тесты транспорта — 6 новых файлов (~90 тестов)
|
||||
|
||||
| Файл | Покрытие |
|
||||
|---|---|
|
||||
| `channel/mattermost/locator_codec_test.go` | локаторы, **thread↔channel изоляция**, `LocatorFromAddressKey`, scope, UserID |
|
||||
| `channel/mattermost/inbound_test.go` | `NormalizeInbound`, mention-фильтр, `ParseCommand`, whitelist команд, `PostIDFromWebSocket` |
|
||||
| `channel/mattermost/adapter_test.go` | все `deliverycmd.Operation`, **plain-fallback только на content-rejection**, draft-as-post, typing no-op без сети, `root_id` в треде |
|
||||
| `channel/mattermost/client_test.go` | ws/wss derivation, `Bearer`, `APIError`, `isContentRejectedError`, контекст |
|
||||
| `channel/mattermost/ingress_test.go` | 🔴 **`bad_seq`-регрессия**, монотонность и уникальность `seq` под 16 горутинами, фильтры, panic-recovery, семафор |
|
||||
| `handlersfx/mattermost_ingress_test.go` | публикация всех команд, **одиночный** префикс principal, `/start` до авторизации, отказ чужим, nil-safety |
|
||||
|
||||
### 4. Доки — `README.md`, `docs/balda.md`, новый `docs/mattermost.md`
|
||||
|
||||
`docs/mattermost.md` — полный гайд: создание бота, минтинг токена (отдаётся
|
||||
ОДИН раз), membership в команде/канале, 5 ключей конфига, поведение в
|
||||
DM/канале/треде, troubleshooting (`bad_seq`, `unauthorized`, `x509`).
|
||||
|
||||
## Уроки метода (дополнить `balda-go-fx-pitfalls`)
|
||||
|
||||
1. **Тест-ассерт — это гипотеза, а не истина.** Дважды падал тест, и оба раза
|
||||
неверным был ассерт, а не код: (а) «тред и корневой пост = одна сессия» —
|
||||
на самом деле тред **отдельная** сессия от канала (проверено пробником:
|
||||
`mm-c-a4aa02ef` vs `mm-t-89bbf64f`, два разных ответа в треде дают ОДНУ
|
||||
сессию); (b) «`NewIngress` фолбэчит `botUserID` из клиента» — на самом деле
|
||||
`onStart` **отказывается стартовать** без `bot_user_id`. Прежде чем править
|
||||
код под падающий тест — проверить, чему код **обязан** соответствовать.
|
||||
2. **`goconst` в тестах** — повторяющиеся строковые литералы (`"channel-1"` ×15)
|
||||
валят линтер. Заводить константы (`testChannelID` и т.п.) сразу.
|
||||
3. **`dupword`** — сообщение `"thread channel = %q, channel channel = %q"`
|
||||
поймано линтером как повтор слова. Формулировать иначе.
|
||||
4. **`write_file` + `***` в доке** — плейсхолдеры вида `BALDA_MATTERMOST_TOKEN=***`
|
||||
в примерах конфига остаются на диске **целыми**; искажается только вывод
|
||||
чтения. Проверять `awk 'NR==N'`, не глазами (см. `hermes-secret-redaction-bypass`).
|
||||
5. **Артефакты деплоя — вон из PR.** `tools/mm-*.sh` (9 скриптов) и
|
||||
`tools/deltas-*.json` (15 файлов) оставлены **незакоммиченными**: это
|
||||
ops-инструменты и метод правок, в апстрим-репозиторий им не место.
|
||||
|
||||
## 🔴 Открытое решение: `tools/` закоммичены в ветку (выявлено в конце сессии)
|
||||
|
||||
**Найдено проверкой `git diff --stat upstream/main...HEAD`:** в диапазоне PR — 56 файлов,
|
||||
и среди них **16 файлов `tools/`**, которых в апстриме **нет вообще**
|
||||
(`git ls-tree upstream/main tools/` → пусто).
|
||||
|
||||
Кто занёс: **базовый коммит `004fda2f`** (из прошлой сессии, не из этого прохода).
|
||||
Мои 5 новых коммитов чисты — проверено:
|
||||
|
||||
```bash
|
||||
git log --oneline --name-only fa1a2855^..HEAD -- 'tools/' # → пусто
|
||||
```
|
||||
|
||||
Что именно закоммичено в `004fda2f` (лишнее для PR):
|
||||
|
||||
```
|
||||
tools/.deltas-state.json
|
||||
tools/apply-project-deltas.py
|
||||
tools/run-project-deltas.sh
|
||||
tools/deltas-mattermost.json
|
||||
tools/deltas-mattermost-2.json … -13.json (12 файлов)
|
||||
```
|
||||
|
||||
**✅ РЕШЕНО (2026-09-26):** выбран вариант (a), выполнен по команде Alex «Добавь в ignored».
|
||||
Коммит `9f1e6c9c chore: ignore local project-editing tooling`.
|
||||
|
||||
```bash
|
||||
echo '/tools/' >> .gitignore # + комментарий, что это локальный инструментарий
|
||||
git rm -r --cached tools/ # 16 файлов ушли из индекса, ОСТАЛИСЬ на диске
|
||||
git add .gitignore && git commit # → 9f1e6c9c
|
||||
```
|
||||
|
||||
Результат: `git diff --stat upstream/main...HEAD -- tools/` → **пусто**;
|
||||
в PR-diff **41 файл**, все — файлы проекта. `tools/` на диске цел (**39 файлов**),
|
||||
`git status --short` чист.
|
||||
|
||||
⚠️ **`git rm -r --cached tools/` уходит на апрув** (рекурсивное удаление). Первая попытка
|
||||
истекла по таймауту и **не применилась** — состояние было целое. Проверять
|
||||
`git diff --cached --name-only` перед повтором.
|
||||
🔴 **`gitignore` НЕ отвязывает уже отслеживаемые файлы** — без `git rm --cached` они
|
||||
останутся в PR навсегда. Нужны оба шага, в этом порядке.
|
||||
|
||||
📌 **Правило на будущее:** проверять состав PR через
|
||||
`git diff --stat upstream/main...HEAD` **перед** объявлением готовности, а не только
|
||||
`git status` (последний показывает рабочее дерево, не содержимое коммитов). Иначе
|
||||
мусор из прошлой сессии всплывает последним.
|
||||
|
||||
## Что осталось
|
||||
|
||||
**По PR — ничего, кроме ревью.** Всё выполнено:
|
||||
|
||||
| Шаг | Статус |
|
||||
|---|---|
|
||||
| `tools/` убран из ветки, `/tools/` в `.gitignore` | ✅ коммит `9f1e6c9c` |
|
||||
| `git push -u origin feat/mattermost-channel` | ✅ запушено |
|
||||
| PR в `baldaworks/balda` открыт | ✅ **#8**, 41 файл, +5622/−18 |
|
||||
| CI | ✅ **8/8 PASS** |
|
||||
| Локальные гейты (build/vet/lint/arch-lint/no-race) | ✅ все зелёные |
|
||||
|
||||
**Осталось (по желанию Alex):**
|
||||
|
||||
1. **Ревью апстрима** — ждать. Правки по замечаниям делать **в эту же ветку**:
|
||||
push обновит PR автоматически.
|
||||
2. 🔴 **Боевая проверка на Kraken — НЕ сделана.** PR-merge ≠ «бот работает на стенде».
|
||||
Нужно: пересобрать образ из `9f1e6c9c`, задеплоить, `@balda` → ответ в ленте канала.
|
||||
См. [[personal/projects/balda/balda-kraken-mm-deploy]].
|
||||
3. **Замержить PR** (после ревью) — тогда Фаза 2 закрывается официально.
|
||||
|
||||
⚠️ Скрипты `tools/mm-*.sh` (9 шт.), дельты `tools/deltas-mattermost-14…26` +
|
||||
`deltas-goal-command-fix.json` + `deltas-mm-prefixfix.json`, `apply-project-deltas.py`,
|
||||
`run-project-deltas.sh`, `.deltas-state.json` — **untracked, локальный инструментарий**,
|
||||
git-ignored. Существенное продублировано в
|
||||
[[personal/projects/balda/balda-kraken-mm-deploy]] и [[personal/tech/balda-go-fx-pitfalls]].
|
||||
|
||||
## ✅ Разъяснено 2026-09-28 — 5 незакоммиченных файлов: НЕ трогать, НЕ фиксы
|
||||
|
||||
Запрос «что там за фиксы, suitable для PR?» — разобран. Ответ: **фиксов там нет.** Два разных набора, оба остаются на диске как есть (решение Alex: «оставляем»).
|
||||
|
||||
**Откуда взялось:** 2026-09-25 18:58 (Zulip `personal/Балда`, msg **116952**) ветка пересоздана от чистого апстрима:
|
||||
`git branch -f feat/mattermost-channel upstream/main` — «без мержа наших коммитов». WIP прошлых сессий остался висеть на диске. Работа продолжилась в этой же ветке и завершилась PR #8 — **ничего не потеряно**, правки вошли в коммит `9f1e6c9c`.
|
||||
**Откуда взялось:** 2026-09-25 18:58 (Zulip `personal/Балда`, msg `116952`) ветка пересоздана
|
||||
от чистого апстрима: `git branch -f feat/mattermost-channel upstream/main` — «без мержа наших
|
||||
коммитов». WIP прошлых сессий остался висеть на диске. Работа продолжилась в этой же ветке и
|
||||
завершилась PR #8; существенное вошло в коммит `9f1e6c9c`.
|
||||
|
||||
| Файл | Что это | В PR? |
|
||||
|---|---|---|
|
||||
| `internal/apps/balda/app.go` | WIP прошлых сессий (MM-импорты без псевдонима) — артефакт переключения ветки, НЕ откат логики | ❌ |
|
||||
| `internal/apps/balda/preflight.go` | то же + `ClassifyLocatorScope` во вторую карту | ❌ |
|
||||
| `go.mod` | `replace github.com/normahq/runtime/v2 => ./runtime-local` — потомок `5ad65955` | ❌ |
|
||||
| `Dockerfile.release` | `COPY runtime-local/` перед `go mod download` — иначе сборка падает | ❌ |
|
||||
| `go.sum` | −2 хэша runtime v2.0.11 под тот же replace | ❌ |
|
||||
| `Dockerfile.release` | `COPY runtime-local/` перед `go mod download` | ❌ |
|
||||
| `internal/apps/balda/app.go` | WIP прошлых сессий (MM-импорты без псевдонима) — артефакт переключения ветки | ❌ |
|
||||
| `internal/apps/balda/preflight.go` | то же + `ClassifyLocatorScope` во вторую карту | ❌ |
|
||||
|
||||
**`runtime-local/`** — локальный форк runtime (592 KB) с патчем `tool_calls` для hostedagent, авторский PR `normahq/runtime#4`. Стенд Kraken живёт на этом форке, но в апстрим `baldaworks/balda` такое не принимается: `replace` на локальную папку ломает сборку у всех, кто клонирует репо. Место ему — в локальной ветке (`local/*`), не в PR. **Решение: оставлено как есть, отдельная ветка не делалась.**
|
||||
**Зачем они нужны:** без `runtime-local` стенд Balda на Kraken **не собирается**. `go.mod`
|
||||
берёт не апстримовый runtime, а локальный форк (592 KB) с патчем `tool_calls` для hostedagent
|
||||
(авторский PR `normahq/runtime#4`) — без него делегирование в CLI-агентов не работает.
|
||||
Остальные 4 файла обслуживают этот же replace: `Dockerfile.release` копирует папку в образ,
|
||||
`go.sum` больше не содержит хэшей апстримового runtime.
|
||||
|
||||
⚠️ **Риск:** грязное дерево → `git checkout -- .` / `git stash` / `git reset --hard` снесёт эти 5 файлов. Перед любой такой командой в `~/Developer/balda` — сверить `git status --short`.
|
||||
**Почему не в PR:** `replace` на локальную папку ломает сборку у любого, кто склонирует
|
||||
`baldaworks/balda`. Такое апстрим не принимает. Место этому — в локальной ветке (`local/*`).
|
||||
|
||||
## ✅ Проверка состояния PR — 2026-09-28
|
||||
**Решение (2026-09-28, Alex):** оставлено как есть, отдельная ветка не делалась.
|
||||
|
||||
Свежая сверка через API (не по доке): **PR #8 `OPEN` · `MERGEABLE` · `reviewDecision` пустой · 8/8 CI PASS.**
|
||||
⚠️ **Риск:** `git checkout -- .` / `git stash` / `git reset --hard` в `~/Developer/balda`
|
||||
снесёт эти 5 файлов. Перед любой такой командой — сверить `git status --short`.
|
||||
|
||||
```
|
||||
gh pr list --repo baldaworks/balda --state open
|
||||
→ #8 feat(mattermost): add Mattermost channel transport, MERGEABLE, OPEN
|
||||
---
|
||||
|
||||
gh pr checks 8 --repo baldaworks/balda
|
||||
→ architecture-lint pass 2m2s | ingress-e2e pass 30s | jetstream-integration pass 1m0s
|
||||
lint pass 2m46s | postgres-integration pass 57s | projection-replay pass 26s
|
||||
sqlite-integration pass 1m15s | test pass 6m0s
|
||||
```
|
||||
|
||||
**Ничего не изменилось с 26.09** — новых проверок нет, ревью апстрима не состоялось.
|
||||
|
||||
🔴 **Главное: `reviewDecision` пустой = PR никто не смотрел.** Мержа не будет, пока внешний ревьюер не заглянет. Это блокер не нашего производства — перепроверять `gh pr view 8 --json reviewDecision` имеет смысл периодически, но ускорить нельзя.
|
||||
|
||||
### Следующие шаги, чтобы «всё работало» — по приоритету
|
||||
## 6. Следующие шаги, чтобы «всё работало» — по приоритету
|
||||
|
||||
| # | Шаг | Тип |
|
||||
|---|---|---|
|
||||
| 1 | Дождаться ревью #8 → правки в ту же ветку | ⛔ внешний блокер |
|
||||
| 2 | **Фаза 4 — исполнитель (делегирование)** — вот что превращает «бот отвечает» в «бот работает» | 🎯 основной фронт |
|
||||
| 3 | Боевая перепроверка приёмки на Kraken (образ `0.1.10` уже примлен 26.09 — повторять только если что-то менялось) | ↩ по необходимости |
|
||||
| 1 | Дождаться ревью #8 → правки в ту же ветку (push обновит PR) | ⛔ внешний блокер |
|
||||
| 2 | **Фаза 4 — исполнитель (делегирование)** — превращает «бот отвечает» в «бот работает» | 🎯 основной фронт |
|
||||
| 3 | Боевая перепроверка приёмки на Kraken (образ `0.1.10` примлен 26.09 — повторять только если что-то менялось) | ↩ по необходимости |
|
||||
| 4 | Замержить PR после ревью → Фаза 2 закрывается официально | ⏳ после 1 |
|
||||
|
||||
📌 **Пока PR висит на ревью — работать в ветке `local/*` над Фазой 4, не в `feat/mattermost-channel`.** Иначе новые коммиты уедут в открытый PR и обесценят уже пройденный CI.
|
||||
📌 **Пока PR висит на ревью — работать над Фазой 4 в ветке `local/*`, НЕ в
|
||||
`feat/mattermost-channel`.** Иначе новые коммиты уедут в открытый PR и обесценят пройденный CI.
|
||||
|
||||
---
|
||||
|
||||
## 7. Питфоллы, которые стоили времени
|
||||
|
||||
1. **`gitignore` НЕ отвязывает уже отслеживаемые файлы.** База `004fda2f` затащила в ветку
|
||||
**16 файлов `tools/`**, которых в апстриме нет вообще (`git ls-tree upstream/main tools/` →
|
||||
пусто). Найдено проверкой `git diff --stat upstream/main...HEAD`. Фикс (коммит `9f1e6c9c`):
|
||||
`/tools/` в `.gitignore` **плюс** `git rm -r --cached tools/` — файлы остались на диске
|
||||
(39 файлов), ушли только из git. Нужны оба шага, в этом порядке.
|
||||
⚠️ `git rm -r --cached` уходит на апрув (рекурсивное удаление) — первая попытка истекла по
|
||||
таймауту и не применилась, состояние было целое. Проверять `git diff --cached --name-only`.
|
||||
2. **Проверять состав PR через `git diff --stat upstream/main...HEAD`, а не по своим коммитам.**
|
||||
Свои 5 коммитов были чистыми (`git log upstream..HEAD -- tools/` → пусто), но ветка в
|
||||
целом — нет, из-за базового коммита. `git status` показывает рабочее дерево, не содержимое
|
||||
коммитов.
|
||||
3. **Тест-ассерт — это гипотеза, а не истина.** Дважды падал тест, и оба раза неверным был
|
||||
ассерт, а не код: (а) «тред и корневой пост = одна сессия» — на самом деле тред **отдельная**
|
||||
сессия от канала (пробник: `mm-c-a4aa02ef` vs `mm-t-89bbf64f`); (b) «`NewIngress` фолбэчит
|
||||
`botUserID` из клиента» — на самом деле `onStart` отказывается стартовать без `bot_user_id`.
|
||||
Прежде чем править код под падающий тест — проверить, чему код **обязан** соответствовать.
|
||||
4. **`goconst` в тестах** — повторяющиеся строковые литералы (`"channel-1"` ×15) валят линтер.
|
||||
Заводить константы (`testChannelID`) сразу. **`dupword`** — `"thread channel = %q, channel
|
||||
channel = %q"` пойман как повтор слова.
|
||||
5. **`write_file` + `***` в доке** — плейсхолдеры вида `BALDA_MATTERMOST_TOKEN=***` в примерах
|
||||
конфига остаются на диске **целыми**; искажается только вывод чтения. Проверять
|
||||
`awk 'NR==N'`, не глазами (см. [[personal/tech/hermes-secret-redaction-bypass]]).
|
||||
6. **⛔ zstd-фрейм macOS.** `zstd -d` на Kraken не читает фрейм, записанный на macOS (хостовый
|
||||
zstd 1.5.4). Распаковывать **на Mac**: `zstd -dc file.tar.zst | ssh kraken 'docker load'`.
|
||||
|
||||
---
|
||||
|
||||
## Связанные заметки
|
||||
|
||||
- [[personal/projects/balda/balda-mattermost-connector]] — Фаза 2 (код коннектора)
|
||||
- [[personal/projects/balda/balda-kraken-mm-deploy]] — Фаза 3 (деплой, боевые причины тишины)
|
||||
- [[personal/projects/balda/balda-mm-delegation-plan]] — Фаза 4 (делегирование)
|
||||
- [[personal/tech/balda-go-fx-pitfalls]] — fx-питфоллы + дельта-апплаер
|
||||
- [[personal/plans/balda-mm-endtoend-plan]] — сквозной путь
|
||||
- [[personal/tech/hermes-secret-redaction-bypass]] — редакция секретов
|
||||
Reference in new issue
Block a user