# 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__*` переменные **молча игнорируются**. Порядок для нового транспорта: 1. блок в `cmd/balda/balda.yaml` (иначе env мёртв) 2. `MattermostConfig`-подобная структура + поле в `BaldaConfig` 3. провайдеры в `app.go` 4. `validateConfig` + вызов рядом с `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-.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-выводе