[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:
1 parent
47ff9bfacd
commit
06950241dd
4 files changed
+442
-13
No files matched your search
@@ -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-выводе
|
||||
Reference in new issue
Block a user