Files
obsidian-vault/personal/tech/balda-go-fx-pitfalls.md
T

190 lines
8.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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-выводе