[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 11:14:40 +06:00
1 parent 06950241dd
commit e7091dd819
4 files changed
+286 -52

No files matched your search

+113 -1
View File
@@ -1,6 +1,7 @@
# Balda — Go/fx pitfalls и метод правок (зеркало скилла)
> Создано 2026-09-26. Зеркало секции скилла `devops/docker-balda-service`, добавленной 2026-09-26.
> Создано 2026-09-26. Обновлено 2026-09-26 (ночь) — добавлены lint/arch-lint и закрытие Фазы 2.
> Зеркало секции скилла `devops/docker-balda-service`, добавленной 2026-09-26.
> Источник: работа над Mattermost-адаптером Балды (Фаза 2).
> Проектные доки: [[personal/projects/balda/balda-mattermost-connector]],
> [[personal/projects/balda/balda-mm-delegation-plan]].
@@ -121,6 +122,117 @@ fx.Provide(
4. `validate<X>Config` + вызов рядом с `validateSlackConfig`
5. включить в `validateBaldaChannelConfiguration` (`cmd/balda/command.go`) —
иначе деплой только на этом транспорте отбивается «at least one channel is required»
6. `.go-arch-lint.yml` — компоненты `<x>_transport`/`<x>_wiring` + права у
`composition_root` и `ingress_wiring` (см. §go-arch-lint выше)
7. тест на `validateBaldaApplication` с `<x>.enabled=true` (см. §«go build ≠ …» выше)
8. тест env-override в `cmd/balda/command_test.go` (по образцу
`TestLoadConfigDocument_AppliesZulipEnvOverrides`)
9. если добавлял маршрут в `BuiltinRoutes()` — обновить `TestBuiltinRoutes`
10. доки: `docs/reference/configuration.md` (ключи), `docs/commands.md` (колонка транспорта)
**Финальные quality gates (все обязательны, из `AGENTS.md`):**
```bash
go build ./... # 0
go vet ./... # 0
go tool golangci-lint run # 0 issues
go tool go-arch-lint check --project-path . # OK
go test -race ./...
```
⚠️ `go-arch-lint` **не входит** в `golangci-lint` — запускать отдельно, иначе ошибка
архитектуры всплывёт только на CI/ревью.
---
## 🔴 `go-arch-lint` — обязательный шаг для нового транспорта
⚠️ **Это не «опциональная придирка»: `AGENTS.md` прямо требует обновлять
`.go-arch-lint.yml` в том же изменении, что и архитектуру.** Без правки линтер падает
с двумя классами ошибок:
```
Component composition_root shouldn't depend on .../channel/mattermost
File /internal/apps/balda/channel/mattermost/adapter.go not attached to any component in archfile
```
**Что добавить в `.go-arch-lint.yml` (4 правки) — по образцу `zulip`:**
1. **Секция `components:`** — пары `in:`-компонентов:
```yaml
# Provider-owned Mattermost transport. It may depend only on
# transport-neutral contracts; application/runtime adapters live in wiring.
mattermost_transport:
in:
- internal/apps/balda/channel/mattermost
- internal/apps/balda/channel/mattermost/presentation
# Composition-root adapters for Mattermost delivery, prompt, and structured renderers.
mattermost_wiring:
in:
- internal/apps/balda/channel/mattermost/mattermostfx
```
⚠️ Root-пакет и `presentation/` идут в **один** компонент `_transport`, а `_fx`-пакет —
отдельно в `_wiring`. Именно так у `zulip`/`telegram`/`slackagent`.
2. **Правила `mayDependOn`:**
- `mattermost_transport` → `foundation, contracts, neutral_registry, mattermost_transport`
- `mattermost_wiring` → `foundation, contracts, neutral_registry, application, delivery,
storage, mattermost_transport, mattermost_wiring`
3. **`composition_root`** → добавить `mattermost_transport`, `mattermost_wiring`
(иначе «composition_root shouldn't depend on …» — это ожидаемый отказ, там
перечислены все транспорты).
4. **`ingress_wiring`** → добавить `mattermost_transport` (соседствует с `telegram_transport`,
`zulip_transport`).
Проверка: `go tool go-arch-lint check --project-path .` → должно быть `OK - No warnings found`.
---
## 🔴 `golangci-lint` на новом транспорте: мёртвый typing-throttle
Первый прогон дал 4 замечания — все в новом коде. Важный урок: **не гейтить линтер
nolint-комментарием, а удалять мёртвый код.**
| Замечание | Что было | Фикс |
|---|---|---|
| `inamedparam` ×2 | `Start(context.Context) error` в interface-assertion | `Start(ctx context.Context) error` |
| `unused` | `shouldSendTyping()` — недостижим, т.к. `SendTyping` всегда no-op | удалён |
| `unused` | `websocketReceiveTimeout` — константа без использования | удалена |
| `unlambda` | `func(client, logger) *Adapter { return NewAdapter(...) }` | заменено на `mattermost.NewAdapter` |
**Корневая причина `unused`:** механизм typing-throttle (`shouldSendTyping`, поля
`typingThrottleInterval`/`typingLastSentAt`, `typingMu sync.Mutex`,
`SetTypingThrottleInterval`) был скопирован из Zulip по аналогии. Но у Mattermost
**ботов нет typing API** (`SendTyping` осознанно возвращает `nil`), поэтому весь механизм
недостижим. Удалён целиком — не заглушён.
**Правило: при копировании транспорта с Zulip проверять каждую фичу на применимость.**
Zulip-специфичное, что НЕ переносится в транспорт без поддержки:
- typing-throttle (нужен интерактивный typing API)
- логика `sendWithPlainFallback` — переносится, но только на content-rejection
⚠️ **Каскад после удаления полей:** удаление `typingThrottleInterval` потянуло `SetTypingThrottleInterval`
→ `sync` (импорт) → `zerolog` (импорт в `module.go`). Удалять надо **всей группой**,
иначе серия `undefined`/`imported and not used`.
---
## ✅ Тест-регрессия: `BuiltinRoutes` и длина списка
Добавление маршрута в `BuiltinRoutes()` (`deliveryfmt/routes.go`) **ломает** `TestBuiltinRoutes`
в `deliveryfmt/registry_test.go`, если не обновить ожидание:
```
len(BuiltinRoutes()) = 5, want 4
```
Фикс — добавить строку в map `want`:
```go
{transport: TransportMattermost, deliveryFormat: DeliveryFormatNone}: NamePlainText,
```
📌 Нашлось только сравнением «с правками / без правок» через `git stash` — в списке
предсуществующих падений апстрима этого теста **не было**, значит это наша регрессия.
---