8.8 KiB
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. Проверять транспорт только реальным запуском валидации:
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.
Теги обязаны жить внутри структуры, снаружи аннотировать нельзя.
Неправильно:
fx.Annotate(NewIngress,
fx.ParamTags(``, ``, ``, `name:"balda_mattermost_enabled"`,
`name:"balda_mattermost_bot_user_id"`,
`name:"balda_mattermost_bot_username"`, ``),
),
Правильно:
// структура — теги на полях
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.
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. Транспорт требует оба импорта:
"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>_* переменные молча игнорируются.
Порядок для нового транспорта:
- блок в
cmd/balda/balda.yaml(иначе env мёртв) MattermostConfig-подобная структура + поле вBaldaConfig- провайдеры в
app.go validate<X>Config+ вызов рядом сvalidateSlackConfig- включить в
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)
Метод проверки регрессии:
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 применённых якорей |
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
Формат дельты:
{ "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-выводе