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

This commit is contained in:
Alexey Martemyanov committed 2026-09-26 10:44:19 +06:00
1 parent 47ff9bfacd
commit 06950241dd
4 files changed
+442 -13

No files matched your search

+189
View File
@@ -0,0 +1,189 @@
# Balda — Go/fx pitfalls и метод правок (зеркало скилла)
> Создано 2026-09-26. Зеркало секции скилла `devops/docker-balda-service`, добавленной 2026-09-26.
> Источник: работа над Mattermost-адаптером Балды (Фаза 2).
> Проектные доки: [[personal/projects/balda/balda-mattermost-connector]],
> [[personal/projects/balda/balda-mm-delegation-plan]].
---
## 🔴 `go build` = exit 0 ≠ рабочий транспорт
Компилятор не видит ошибок DI-графа. `go build ./...` проходит, а приложение падает
на старте с `fx.ValidateApp`. Проверять транспорт **только** реальным запуском валидации:
```go
prepared, err := loadBaldaCommandConfig(true) // нужен os.Chdir в tempdir с .config/balda/config.yaml
err = validateBaldaApplication(prepared) // ← вот это и есть проверка графа
```
Эталонный тест: `TestValidateCommandRunsValidationWithMattermostTransport`
в `cmd/balda/command_test.go`. На нём поймано 3 дефекта, которые `go build` пропустил.
---
## 🔴 Дефект 1: `fx.In` + внешний `fx.ParamTags`
**Симптом:**
```
Failed: invalid annotation function func(mattermostfx.IngressParams) (*mattermost.Ingress, error):
fx.In structs cannot be annotated with fx.ParamTags or fx.From
```
**Причина:** `fx.Annotate(NewIngress, fx.ParamTags(...))`, где `IngressParams` **уже** содержит `fx.In`.
Теги обязаны жить внутри структуры, снаружи аннотировать нельзя.
**Неправильно:**
```go
fx.Annotate(NewIngress,
fx.ParamTags(``, ``, ``, `name:"balda_mattermost_enabled"`,
`name:"balda_mattermost_bot_user_id"`,
`name:"balda_mattermost_bot_username"`, ``),
),
```
**Правильно:**
```go
// структура — теги на полях
type IngressParams struct {
fx.In
Processor mattermost.InboundProcessor `optional:"true"`
Client *mattermost.Client
Commands *commandcmd.Registry // обязательный, не optional
Enabled bool `name:"balda_mattermost_enabled"`
BotUserID string `name:"balda_mattermost_bot_user_id"`
BotUsername string `name:"balda_mattermost_bot_username"`
Logger zerolog.Logger
}
// предоставление — голое, без fx.Annotate
fx.Provide(NewIngress),
```
Эталон в апстриме: `zulip.ServerParams` + `zulip.NewServer` в `zulipfx/module.go` (строка 24) —
`zulip.NewServer` объявлен **без** `fx.Annotate`.
⚠️ Поле-порт (`*commandcmd.Registry`) делать **обязательным**. `optional:"true"` на нём — лишнее:
в графе он есть всегда, а optional маскирует ошибки конфигурации.
---
## 🔴 Дефект 2: нет провайдеров зарегистрированных строк
**Симптом:**
```
missing types:
- string[name="balda_mattermost_server_url"] (did you mean to Provide it?)
- string[name="balda_mattermost_token"]
- string[name="balda_mattermost_bot_user_id"]
```
**Причина:** модуль читает значения через `name:`-теги, а провайдеров этих имён в composition root нет.
У Zulip они есть (`balda_zulip_*`), для нового транспорта забыли.
**Правило: каждый `name:`-тег в модуле = обязательный провайдер в `app.go`.**
```go
fx.Provide(
fx.Annotate(
func() string { return strings.TrimSpace(cfg.Balda.Mattermost.ServerURL) },
fx.ResultTags(`name:"balda_mattermost_server_url"`),
),
),
```
`strings.TrimSpace` для отсутствующего ключа YAML даёт `""` — без паники, как у Zulip.
Образец: блок `balda_zulip_*` в `internal/apps/balda/app.go` (~строки 576-606).
---
## 🔴 Дефект 3: забытый импорт корневого пакета
`app.go:965: undefined: mattermost` при вызове `mattermost.ValidateConfig`, если импортирован
только `mattermostfx`. Транспорт требует **оба** импорта:
```go
"github.com/baldaworks/balda/internal/apps/balda/channel/mattermost"
"github.com/baldaworks/balda/internal/apps/balda/channel/mattermost/mattermostfx"
```
---
## ⚠️ Env-override работает только по ключам, уже присутствующим в YAML
`appconfig/loader.go:367` — `SetEnvPrefix("BALDA")` + `SetEnvKeyReplacer(".", "_")` +
`AutomaticEnv()` **по leaf-ключам конфига**. Поэтому блок нового транспорта обязан быть
в `cmd/balda/balda.yaml` — иначе `BALDA_<X>_*` переменные **молча игнорируются**.
Порядок для нового транспорта:
1. блок в `cmd/balda/balda.yaml` (иначе env мёртв)
2. `MattermostConfig`-подобная структура + поле в `BaldaConfig`
3. провайдеры в `app.go`
4. `validate<X>Config` + вызов рядом с `validateSlackConfig`
5. включить в `validateBaldaChannelConfiguration` (`cmd/balda/command.go`) —
иначе деплой только на этом транспорте отбивается «at least one channel is required»
---
## ✅ 13 пакетов падают в апстриме изначально
Не пытаться «чинить» как часть своей задачи — это дефект апстрима. Проверять `git stash`
перед выводами о регрессии.
Падающие в `baldaworks/balda@de273d8f`:
- `TestInitCommand_NonInteractiveAutoSelectsRootAndGeneratesDetectedAgents` (cmd/balda)
- `TestMigrateSessionMemoryLayout_*` ×4 (state)
- `TestWorkspaceMCPImport/Export_UsesPersistedSessionMetadata` (session)
- `TestPluginResolverResolvesContainedArgvFilteredEnvAndLaunchSecrets` (mcpruntime)
- `TestResolveWorkspaceBaseBranch_ConfigPreferredWhenValidVal`, `TestCanonicalSessionMemory*` (apps/balda)
**Метод проверки регрессии:**
```bash
git stash push -q -m baseline --include-untracked
go test ./<пакет>/ -count=1 # смотрим падения БЕЗ правок
git stash pop -q # правки возвращаются
```
⚠️ `git stash` может задеть чужие стэши — в форке лежат `whale-20260623` и
`feat/zulip-events-polling`. Проверять `git stash list` до и после.
---
## Метод правок: дельта-апплаер вместо sed
Правило Alex: **никаких in-place скриптов и sed**. Инструмент в `~/Developer/balda/tools/`:
| Файл | Роль |
|---|---|
| `apply-project-deltas.py` | движок (JSON-дельты + состояние) |
| `run-project-deltas.sh` | обёртка `check` / `apply` / `list` |
| `deltas-<topic>.json` | группы правок |
| `.deltas-state.json` | sha256 применённых якорей |
```bash
cd ~/Developer/balda
bash tools/run-project-deltas.sh check tools/deltas-mattermost.json tools/.deltas-state.json
bash tools/run-project-deltas.sh apply tools/deltas-mattermost.json tools/.deltas-state.json
```
**Формат дельты:**
```json
{ "file": "путь/от/корня", "note": "что делаем",
"search": "уникальный фрагмент текущего текста",
"replace": "чем заменить" }
```
**Три исхода:** `replace` уже есть → `skipped` (идемпотентно); `search` ровно 1 раз → `applied`;
ни того ни другого → `error: ЯКОРЬ НЕ НАЙДЕН`.
📌 `check` **обязателен перед `apply`** — на нём поймано, что дельта валидации целилась
в несуществующий `validate()` в `config.go` (валидация живёт в `app.go`), и переписана до записи.
⚠️ Дельты с длинным `replace` писать в JSON через `write_file`, **не** heredoc — кавычки и `\n`
ломаются молча. Это тот же питфолл, что у SSH-heredoc с вложенными кавычками.
---
## Связанные заметки
- [[personal/projects/balda/balda-mattermost-connector]] — Фаза 2, где всё это найдено
- [[personal/projects/balda/balda-mm-delegation-plan]] — общий план
- [[personal/tech/hermes-secret-redaction-bypass]] — маскировка секретов в tool-выводе