[2026-09-26] eagle: personal/projects/balda/balda-kraken-mm-deploy.md personal/projects/balda/balda-mattermost-connector.md personal/projects/balda/balda-mm-delegation-plan.md personal/tech/balda-go-fx-pitfalls.md personal/tech/hermes-secret-redaction-bypass.md

This commit is contained in:
Alexey Martemyanov committed 2026-09-26 12:00:10 +06:00
1 parent eab4ef5dc9
commit a85e45588f
5 files changed
+432 -114

No files matched your search

+74 -2
View File
@@ -1,9 +1,11 @@
# Balda — Go/fx pitfalls и метод правок (зеркало скилла)
> Создано 2026-09-26. Обновлено 2026-09-26 (ночь) — добавлены lint/arch-lint и закрытие Фазы 2.
> Создано 2026-09-26. Обновлено 2026-09-26 (ночь, позже) — добавлен **Дефект 4**
> (advertisement ↔ actor-хендлер), найденный при первом реальном запуске на Kraken.
> Зеркало секции скилла `devops/docker-balda-service`, добавленной 2026-09-26.
> Источник: работа над Mattermost-адаптером Балды (Фаза 2).
> Источник: работа над Mattermost-адаптером Балды (Фаза 2) + деплой на Kraken (Фаза 3).
> Проектные доки: [[personal/projects/balda/balda-mattermost-connector]],
> [[personal/projects/balda/balda-kraken-mm-deploy]],
> [[personal/projects/balda/balda-mm-delegation-plan]].
---
@@ -109,6 +111,72 @@ fx.Provide(
---
## 🔴🔴 Дефект 4: объявленная команда без actor-хендлера (роняет ВЕСЬ fx-граф)
Найден **только реальным запуском на Kraken** (Фаза 3). До этого Фаза 2 считалась закрытой:
`go build` = 0, `go vet` = 0, `golangci-lint` = 0 issues, `go-arch-lint` = OK, тесты зелёные.
**Ни один из этих гейтов его не видит.**
**Симптом** — crash-loop контейнера (`Restarting (1)`), в логах одна строка на тысячи
`"message":"provided"`:
```
Error: starting Balda app: could not build arguments for function
".../execution".init.func1: failed to build *execution.ActorHost: ...
failed to build *command.Router: received non-nil error from function
"reflect".makeFuncStub: advertised commands have no actor handler: goal
```
**Причина:** в `mattermostfx/module.go` в `commandcmd.Advertisement{Names: [...]}` была
объявлена команда `goal`, которой **не существует** как actor-хендлер. `goal` — это
**payload kind** (`goalkeepercmd.PayloadKindGoal`, `actorcmd.KindGoal`,
`actors/job_actor.go:100`), а снаружи цикл GoalKeeper называется `goalkeeper`.
**Механизм:** `actors/command/router.go:49 ValidateAdvertised()` вызывается из
`commandfx/module.go:306`. Он валидирует **весь** список `Names` одного advertisement и при
первом отсутствующем имени возвращает ошибку → fx не может построить `*command.Router` →
падает `*execution.ActorHost` → падает приложение. Одна опечатка в списке = полный отказ старта,
а не «недоступна одна команда».
**Правильно — сверять `Names` с реальными `Name()`:**
```bash
cd ~/Developer/balda
grep -rn 'func (h \*Handler) Name() string' internal/apps/balda/actors/command/
```
Эталонный фактический список (2026-09-26, `balda@de273d8f`):
```
locator · reset · usage · auto · cancel · close · start · user · topic · skill
goalkeeper · help · plugin
```
`mattermostfx/module.go` после фикса:
```go
Names: []string{
"locator", "reset", "usage", "auto", "cancel",
"goalkeeper", "topic", "close", "start",
"user", "skill",
},
```
`channel/mattermost/inbound.go` — тот же список в inbound allow-list, синхронно.
⚠️ `Zulip`-адаптер изначально был **прав** — там `"goalkeeper"` без `"goal"`. Регрессию внёс
MM-адаптер при копировании «на глаз».
⚠️ **`docs/commands.md` — НЕ источник истины** для имён команд. Там таблица доступности по
транспортам; реестр хендлеров — только `actors/command/*/handler.go`.
⚠️ Фикс — дельта-апплаером: `tools/deltas-goal-command-fix.json` (2 дельты, обе APPLIED).
📌 **Общий вывод (расширяет §«go build = exit 0 ≠ рабочий транспорт» выше):** недостаточно
проверять DI-граф тестом `validateBaldaApplication`. Тест должен прогоняться **с реальным
списком `Names` из модуля**. Но и это не поймало бы опечатку, если тест использует тот же
неверный advertisement — поэтому единственная надёжная проверка нового транспорта =
**живой запуск бинаря**. Для нового транспорта добавлять в чек-лист шаг:
`docker run` (или локальный `balda start`) с включённым транспортом **до** объявления готовности.
---
## ⚠️ Env-override работает только по ключам, уже присутствующим в YAML
`appconfig/loader.go:367` — `SetEnvPrefix("BALDA")` + `SetEnvKeyReplacer(".", "_")` +
@@ -129,6 +197,10 @@ fx.Provide(
`TestLoadConfigDocument_AppliesZulipEnvOverrides`)
9. если добавлял маршрут в `BuiltinRoutes()` — обновить `TestBuiltinRoutes`
10. доки: `docs/reference/configuration.md` (ключи), `docs/commands.md` (колонка транспорта)
11. 🔴 **сверить `Advertisement.Names` с реальными `Name()`** в
`actors/command/*/handler.go` — опечатка роняет весь fx-граф (см. §«Дефект 4» выше)
12. 🔴 **живой запуск** `balda start` (или `docker run`) с включённым транспортом —
единственная проверка, которая ловит дефект 4
**Финальные quality gates (все обязательны, из `AGENTS.md`):**
```bash
@@ -223,6 +223,72 @@ ssh host "cat > /tmp/p.sh && bash -n /tmp/p.sh && echo SYNTAX_OK" < payload.sh
Если `bash -n` ругается — смотреть `/tmp/p.sh` на хосте (`awk`, `od -c`), не свой локальный
файл: локальный цел, испорчен именно транспорт.
### 🔴 Питфолл D — `$o` / короткие jq-переменные → «unbound variable»
```
e2e-check.sh: line 9: o: unbound variable
```
Скрипт был написан и лежал на диске **целым** (`read_file` это подтверждает), но при передаче
через `ssh` строка ломается. Тот же класс, что A/B: транспорт, не файл.
**Обход:** не использовать в payload однобуквенные/короткие shell-переменные и jq-биндинги
(`$o`, `$i`, `$a`). Переписывать jq-выражения без промежуточных биндингов либо давать
осмысленные имена. Была и вторая причина: `set -euo pipefail` + `$o` вне раскрытия = фатально
до всякой логики.
### 🔴 Питфолл E — `printf` с format-строкой как значение
```bash
# что написал:
printf 'DEEPSEEK_API_KEY=%s\n' "$DS_KEY" > .env
# результат в файле: ключ попал в ИМЯ переменной, 5-я строка потерялась,
# compose упал: "line 2: key cannot contain a space"
```
Формат-строка сама редактируется, `%s` превращается в литерал и уезжает в файл.
**Обход — `echo` внутри `{ }`-блока, без format-строк:**
```bash
{
echo "DEEPSEEK_API_KEY=$DS"
echo "VAR2=$VAL2"
} > /home/kraken/balda/.env
```
### 🔴 Питфолл F — `ssh host 'bash -s' < script.sh` съедает stdin
```bash
# НЕ работает: ssh читает stdin скрипта вместо heredoc
ssh kraken "bash -s" < mm-config-and-create-bot.sh
# → bash: line 5: MM_ADMIN_PW: set MM_ADMIN_PW
```
`ssh` использует stdin **и** для скрипта, **и** для heredoc-канала — они конфликтуют.
**Обход:** `ssh host "bash -s" < /dev/null <<'REMOTE' … REMOTE`.
### ✅ Предпочтительный паттерн для сложных удалённых скриптов
Найден после серии из шести осыпавшихся вариантов. **Вложенные heredoc не использовать вообще.**
```bash
# 1. написать скрипт ФАЙЛОМ на Mac (write_file)
# 2. залить файлом
ssh kraken "cat > /tmp/x.sh" < x.sh
# 3. проверить синтаксис НА ХОСТЕ
ssh kraken 'bash -n /tmp/x.sh && echo SYNTAX_OK' < /dev/null
# 4. только потом исполнять
ssh kraken "MM_ADMIN_PW='…' bash /tmp/x.sh" < /dev/null
```
Никаких `<<OUTER` + `<<'INNER'`, никакой борьбы с раскрытием `$PW` — переменные окружения
передаются в аргументе вызова, значения секретов читаются из файлов на хосте.
### ⚠️ Побочный факт: env на хосте НЕ считается секретом
`ssh kraken "MM_ADMIN_PW='…' bash /tmp/x.sh"` — значение в аргументе работает и не маскируется,
потому что шаблон `VAR='value'` с пробелами и кавычками не матчит `_ENV_ASSIGN_RE`.
---
## Валидация результата (обязательный шаг)
@@ -240,5 +306,17 @@ grep "MM_SQLSETTINGS_DATASOURCE" compose.yml | grep -q 'postgres://${PGUSR}:\${P
## Связанные заметки
- [[personal/projects/balda/mattermost-kraken-deploy]] — где пригодилось
- [[personal/projects/balda/balda-kraken-mm-deploy]] — Фаза 3: три питфолла из этой заметки найдены там
- [[personal/projects/balda/balda-kraken-mm-deploy]] — Фаза 3: все питфоллы A-F найдены там
- [[personal/projects/balda/balda-mattermost-connector]] — Фаза 2 (провижининг MM-бота)
- [[personal/tech/balda-go-fx-pitfalls]] — fx-питфоллы Балды
- [[personal/tech/hermes-fork-vs-upstream]] — наш форк Hermes
## История
- **2026-09-26**: создано. Механика redaction по исходникам `agent/redact.py`, приёмы обхода.
- **2026-09-26 (вечер)**: добавлены питфоллы A (переменная `TOKEN`), B (литерал
`Authorization: Bearer`), C (`$PW` во вложенном heredoc). Провижининг MM-бота.
- **2026-09-26 (ночь)**: добавлены питфоллы **D** (короткие jq/shell-переменные, `$o`),
**E** (`printf` с format-строкой), **F** (`ssh 'bash -s' < file` съедает stdin) и
**предпочтительный паттерн**: скрипт файлом → `cat > /tmp/x.sh` → `bash -n` на хосте →
исполнение. Найдено на деплое Балды на Kraken.