From 1bf83204462d81def581840e7dee831b499400c2 Mon Sep 17 00:00:00 2001 From: Alexey Martemyanov Date: Sat, 26 Sep 2026 14:57:15 +0600 Subject: [PATCH] [2026-09-26] eagle: family/how-to/truenas-infrastructure.md personal/plans/balda-mm-pr-plan.md personal/projects/balda/balda-mattermost-connector.md personal/projects/balda/balda-mm-delegation-plan.md personal/tech/balda-go-fx-pitfalls.md --- family/how-to/truenas-infrastructure.md | 91 ++++++++++- personal/plans/balda-mm-pr-plan.md | 142 +++++++++++++++--- .../balda/balda-mattermost-connector.md | 62 ++++---- .../balda/balda-mm-delegation-plan.md | 35 ++++- personal/tech/balda-go-fx-pitfalls.md | 45 ++++++ 5 files changed, 318 insertions(+), 57 deletions(-) diff --git a/family/how-to/truenas-infrastructure.md b/family/how-to/truenas-infrastructure.md index 493fbd60..27556536 100644 --- a/family/how-to/truenas-infrastructure.md +++ b/family/how-to/truenas-infrastructure.md @@ -115,6 +115,19 @@ > - **✅ НЕАКТУАЛЬНО с 2026-09-14:** USB-адаптеры (CH340 ZONT/вентиляция) **физически перенесены на t610** → на TrueNAS узлов `/dev/ttyZONT` / `/dev/ttyVent` **нет вообще**, а сами контейнеры `mbusd`, `modbus-bridge`, `ser2net` **погашены** (`stop` + `--restart=no`). Проверка: `ls /dev/ttyUSB*` → пусто; `/sys/bus/usb/devices` содержит только принтер Samsung `04e8:3425` (**`lsusb` на TrueNAS не установлен** — использовать `/sys/bus/usb/devices`). > - **Актуальная гонка udev живёт на t610** — см. [[family/how-to/zont-modbus-bridge-udev-race-protection]]. > - ⚠️ **Историческая справка (если когда-нибудь откатывать):** `restart: unless-stopped` НЕ перезапускает контейнер, упавший на `start` из-за отсутствующего device-узла (отказ до старта процесса, ExitCode 128/255, RestartCount=0); лечилось ручным `docker start modbus-bridge mbusd` после готовности симлинков. +> **⚠️ ПОСЛЕДСТВИЕ, ПОДТВЕРЖДЁННОЕ 2026-09-26 (File Browser):** потеря `.ix-apps` = потеря **всех docker NAMED VOLUMES**, а не только образов. Образы = кеш, перекачаются; **тома = данные, не восстанавливаются**. Первый пострадавший — `filebrowser`: его БД пользователей жила в томах `filebrowser_filebrowser_db` / `filebrowser_filebrowser_config` → при первом старте тома создались с нуля (`2026-08-24T02:04:12`), File Browser сгенерировал нового `admin`, прежние пользователи (`file_admin` в т.ч.) утрачены. +> +> 🔴 **Правило для будущих переносов:** перед `zpool destroy`/пересозданием всегда выгружать **`/mnt/.ix-apps/docker/volumes/`** отдельным шагом — `rsync -aHAX /mnt/.ix-apps/docker/volumes/ /mnt//ix-apps-volumes/`. Одного `/mnt/RED_2TB/*` НЕДОСТАТОЧНО: `.ix-apps` — **отдельный ZFS-датасет**, смонтированный поверх одноимённой папки, в глоб пула не попадает. +> +> 🔑 **Как быстро найти сервисы с томами** (они теряются при переносе, в отличие от bind-mount'ов): +> ```bash +> docker volume ls +> for c in $(docker ps -a --format '{{.Names}}'); do +> echo "== $c =="; docker inspect $c --format '{{json .Mounts}}' | jq -c '.[] | select(.Type=="volume") | {Name, Destination, Source}' +> done +> ``` +> **Долгосрочное лечение:** переводить критичные сервисы с named volumes на **bind-mount в `/mnt/RED_2TB/docker//`** — тогда данные попадают в обычный бэкап `mailru-crypt:` и переживают пересоздание пула. Кандидат №1 — File Browser (см. раздел «File Browser — детали», вариант B). + > **⚠️ ДРУГОЕ (открытое):** пул `DEST` (IronWolf) на 2026-08-24 `DEGRADED` — разобраться, почему (отвалился mirror-член). TimeMachine (~277G) не возвращён (бэкапы macOS, вероятно не нужен — решение Alex не подтверждено). SSH с Mac по `192.168.2.197` — подключение давало `Connection reset` / `kex_exchange_identification` (вероятен конфликт сессий при активном входе на консоль) — рабочий путь: `mallexxx.duckdns.org`. > ## 🔒 ИСТОРИЯ: статус до восстановления (2026-08-24) @@ -207,7 +220,7 @@ TrueNAS использует **NFSv4 ACL**, а не стандартный POSIX | transmission | linuxserver/transmission:latest | 9091, 51413 | transmission.mallexxx.duckdns.org | | hermes-taiga | hermes-taiga:latest | — | — | | vless-proxy | teddysun/xray:latest | — | — | -| filebrowser | filebrowser/filebrowser:latest | — | — | +| filebrowser | filebrowser/filebrowser:latest | 8000 | (через Caddy `192.168.2.197:8000`) — см. раздел «File Browser» | | webdav | hacdias/webdav:latest | — | webdav.mallexxx.duckdns.org | | ~~homeassistant~~ ⛔ | home-assistant:stable | ~~8123~~ | **ПОГАШЕН 2026-09-14** — переехал на t610 | | immich-server | immich-app/immich-server:release | 2283 | immich.mallexxx.duckdns.org | @@ -384,6 +397,82 @@ nas_users:x:3000:root,truenas_admin,mallexxx,nas,transmission - **Web:** https://transmission.mallexxx.duckdns.org - **Порты:** 9091 (RPC/web), 51413 (TCP/UDP peers) +### File Browser — детали (разобрано 2026-09-26) + +**Папка:** `/mnt/RED_2TB/docker/filebrowser/` (только `docker-compose.yml`). +**Порт:** `8000:80` (хост `0.0.0.0:8000`). Проверка: `nc -z 127.0.0.1 8000`. +**Образ:** `filebrowser/filebrowser:latest` → **v2.63.23** (проект заархивирован 2026-09-01, обновлений не будет). + +```yaml +services: + filebrowser: + image: filebrowser/filebrowser:latest + container_name: filebrowser + restart: unless-stopped + user: "0:0" # ← root: обходит NFSv4 ACL, как и webdav + ports: + - "8000:80" + volumes: + - /mnt/RED_2TB/storage:/srv + - /mnt/RED_2TB/Edu:/srv/Edu + - filebrowser_db:/database # ← NAMED VOLUME, не bind-mount! + - filebrowser_config:/config # ← NAMED VOLUME, не bind-mount! +volumes: + filebrowser_db: + filebrowser_config: +``` + +> 🔴 **ДАННЫЕ ЖИВУТ В DOCKER NAMED VOLUMES, А НЕ В ПАПКЕ `/mnt/RED_2TB/docker/filebrowser/`.** +> В папке контейнера лежат только `docker-compose.yml` + `settings.json` (130 б, дата `Jul 27 20:00`). +> Реальные данные: +> ``` +> filebrowser_filebrowser_db → /mnt/.ix-apps/docker/volumes/filebrowser_filebrowser_db/_data/filebrowser.db +> filebrowser_filebrowser_config → /mnt/.ix-apps/docker/volumes/filebrowser_filebrowser_config/_data/settings.json +> ``` +> Физически это **внутри `.ix-apps`** (`DockerRootDir=/mnt/.ix-apps/docker`). Оба тома созданы +> `2026-08-24T02:04:12` — то есть **с нуля**, в день пересоздания пула. + +#### 🔴 Пифолл 1: логин не работает, потому что пользователя нет / `hideLoginButton: true` + +**Симптом (2026-09-26):** «не могу залогиниться как `file_admin`». +**Факт:** в БД **ровно один** пользователь — `"username":"admin"` (id=1, `perm.admin=true`). `file_admin` **никогда не создавался**. +**Почему 403:** в настройках `"hideLoginButton": true` → эндпоинт `GET /api/login` отдаёт **403 Forbidden** (а не 401). Лог контейнера: +``` +2026/09/26 08:44:02 /api/renew: 401 92.62.77.233 ← Кра́кен +2026/09/26 08:44:17 /api/login: 403 92.62.77.233 ← попытка логина, отбита +``` +Прочие настройки из БД: `authMethod: "json"`, `signup: false`, `minimumPasswordLength: 12`, `tokenExpirationTime: "2h"`. + +> ⚠️ **«403 на логине» ≠ «контейнер сломан».** Контейнер `Up (healthy)`, HTTP 200 на `/`, порт слушает. + +#### 🔴 Пифолл 2: БД залочена — `filebrowser users ls` даёт `Error: timeout` + +Bolt-БД держит живой процесс. Любая CLI-команда по живой БД падает: +```bash +docker exec filebrowser filebrowser users ls -d /database/filebrowser.db +# → Error: timeout +``` +**Правка пользователей/паролей только при ОСТАНОВЛЕННОМ контейнере.** Порядок: +`docker stop filebrowser` → правка/бэкап → `docker start filebrowser`. +Читать содержимое живой БД можно без остановки через `strings`: +```bash +docker exec filebrowser sh -c "strings /database/filebrowser.db | grep -oE '\"username\":\"[^\"]*\"'" +docker exec filebrowser sh -c "strings /database/filebrowser.db | grep -A2 'authMethod'" +``` + +#### 🔴 Пифолл 3: `docker exec ... wget --header=...` → `bad address 'application'` + +Busybox-`wget` внутри контейнера не переваривает `--header='Content-Type: application/json'` через вложенный ssh-квотинг (аргумент рвётся). Для API-проб использовать `printf` в файл + `wget --post-file` или просто проверять код через `wget -qS`. +Настройки также читаются прямо из тома (root-only для хоста `truenas_admin`): +```bash +docker exec filebrowser cat /config/settings.json +docker exec filebrowser cat /database/filebrowser.db # бинарь, читать strings +``` + +**Состояние на 2026-09-26:** решено НЕ менять (ждёт решения Alex по вариантам A/B/C из отчёта). Данные `admin`-пользователя утрачены при переносе пула; `file_admin` отсутствует. + +> ⚠️ **`/mnt/RED_2TB/docker.bak/filebrowser/` — это НЕ бэкап данных.** Там те же два файла (`docker-compose.yml` + `settings.json`, дата `Apr 29`), которые пережили перенос, потому что лежали на пуле. Томов там нет. + ### Caddy — домены | Домен | → | |-------|---| diff --git a/personal/plans/balda-mm-pr-plan.md b/personal/plans/balda-mm-pr-plan.md index 71b9db0c..c38af3f7 100644 --- a/personal/plans/balda-mm-pr-plan.md +++ b/personal/plans/balda-mm-pr-plan.md @@ -1,13 +1,93 @@ # Balda — Mattermost connector: PR-подготовка (закрыто) -> Создан 2026-09-26. Обновлён 2026-09-26 (PR-проход, финал). -> Статус: **🟡 код/тесты/доки/коммиты готовы — но осталось ОДНО решение по `tools/` в ветке** -> (см. §«Открытое решение» ниже). После него — `git push` и PR. +> Создан 2026-09-26. Обновлён 2026-09-26 (PR открыт). +> Статус: **✅ PR ОТКРЫТ — baldaworks/balda#8, 8/8 CI PASS, MERGEABLE.** +> https://github.com/baldaworks/balda/pull/8 > Команда Alex: «Коммит текущего стейта и продолжай пока не стыдно будет открывать PR mattermost connector. > Со всеми поддерживаемыми фичами и тестами. Вперёд! Без вопросов и требующих апрува команд.» > Часть [[personal/projects/balda/balda-mm-delegation-plan]] (Фаза 2 → PR в апстрим). -## ✅ Итог: ветка PR-clean (2026-09-26, финал) +## ✅ PR ОТКРЫТ — https://github.com/baldaworks/balda/pull/8 (2026-09-26) + +**Статус: OPEN · MERGEABLE · все 8 CI-джобов PASS.** + +| | | +|---|---| +| PR | **baldaworks/balda#8** | +| База | `baldaworks/balda:main` | +| Голова | `mallexxx:feat/mattermost-channel` | +| Объём | **41 файл, +5622 / −18** | +| Коммитов | 7 | +| Mergeable | ✅ MERGEABLE | + +### 🔴 Целевой репозиторий — `baldaworks/balda`, не `normahq/balda` + +Проверено через API, а не по названию remote'а: +``` +gh repo view mallexxx/balda --json parent +→ parent: baldaworks/balda ← форк от него +gh repo view normahq/balda --json owner +→ owner.id: O_kgDOEgAHrw (= baldaworks) ← редирект, не отдельный репо +``` +`origin` = `mallexxx/balda` (форк), `upstream` = `normahq/balda` (редирект на baldaworks). +**PR создавать в `baldaworks/balda`**, head указывать как `mallexxx:` +(не полный URL форка — иначе `gh` путается). + +### База ветки — идеальная + +`git rev-list --left-right --count upstream/main...HEAD` → `0 7` (0 позади, 7 впереди). +`merge-base HEAD upstream/main` == `upstream/main` == `de273d8f`. Rebase не потребовался. + +### CI — 8/8 PASS (прогон на чистом раннере) + +``` +architecture-lint pass 2m2s +ingress-e2e pass 30s +jetstream-integration pass 1m0s +lint pass 2m46s +postgres-integration pass 57s +projection-replay pass 26s +sqlite-integration pass 1m15s +test pass 6m0s ← главный: полный go test на чистом раннере +``` + +📌 **Это и есть настоящая приёмка сборки.** Локально `cmd/balda` падал на +`TestInitCommand_NonInteractiveAutoSelectsRootAndGeneratesDetectedAgents` — в CI он +**прошёл**, то есть падение было особенностью моей локальной среды, а не дефектом. +CI на чистом раннере закрывает ровно тот класс проблем, который локальные гейты не видят. + +⚠️ **CI-пайплайн в репо ЕСТЬ** (`architecture-lint`, `lint`, `test`, `ingress-e2e`, +`jetstream-integration`, `postgres-integration`, `sqlite-integration`, `projection-replay`). +Значит локальные гейты — необходимое, но не достаточное условие; ждать CI обязательно. + +### Команды (воспроизводимо) + +```bash +cd ~/Developer/balda +git fetch upstream --quiet +git rev-list --left-right --count upstream/main...HEAD # 0 7 +git push -u origin feat/mattermost-channel + +gh pr create --repo baldaworks/balda --base main \ + --head mallexxx:feat/mattermost-channel \ + --title "feat(mattermost): add Mattermost channel transport" \ + --body-file /tmp/mm-pr-body.md + +gh pr view 8 --repo baldaworks/balda --json state,mergeable,changedFiles,additions +gh pr checks 8 --repo baldaworks/balda +``` + +### Что осталось + +1. **Ревью апстрима** — ждать. Правки по замечаниям делать в этой же ветке (push обновит PR). +2. **Боевая проверка на Kraken** — PR-merge ≠ работает на стенде. Пересобрать образ из + `9f1e6c9c`, задеплоить, `@balda` → ответ в ленте канала (см. + [[personal/projects/balda/balda-kraken-mm-deploy]]). +3. Фаза 4 (исполнитель) — [[personal/projects/balda/balda-mm-delegation-plan]]. + +--- + +## ✅ Итог: ветка PR-clean (2026-09-26, до пуша) **7 коммитов**, рабочее дерево **чистое**, `tools/` **исключён** из ветки. @@ -194,18 +274,24 @@ tools/deltas-mattermost.json tools/deltas-mattermost-2.json … -13.json (12 файлов) ``` -**Что нужно сделать — по выбору Alex:** +**✅ РЕШЕНО (2026-09-26):** выбран вариант (a), выполнен по команде Alex «Добавь в ignored». +Коммит `9f1e6c9c chore: ignore local project-editing tooling`. -| Вариант | Команда | Последствие | -|---|---|---| -| **(a) рекомендую** | `git rm -r --cached tools/` + коммит | файлы остаются на диске **untracked**, локальный инструментарий работает, ветка чистая | -| (b) оставить | — | PR содержит 16 лишних файлов, ревьюер спросит | -| (c) отдельная ветка | — | tooling живёт отдельно от PR | +```bash +echo '/tools/' >> .gitignore # + комментарий, что это локальный инструментарий +git rm -r --cached tools/ # 16 файлов ушли из индекса, ОСТАЛИСЬ на диске +git add .gitignore && git commit # → 9f1e6c9c +``` -⚠️ **Попытка (a) в этой сессии НЕ выполнена.** `git rm -r --cached tools/` ушла на -апрув и **истекла по таймауту** — команда не применилась, состояние дерева целое -(подтверждено `git status --short`: все файлы на месте). Повторять без команды Alex -не стал: удаление из индекса — операция, требующая подтверждения. +Результат: `git diff --stat upstream/main...HEAD -- tools/` → **пусто**; +в PR-diff **41 файл**, все — файлы проекта. `tools/` на диске цел (**39 файлов**), +`git status --short` чист. + +⚠️ **`git rm -r --cached tools/` уходит на апрув** (рекурсивное удаление). Первая попытка +истекла по таймауту и **не применилась** — состояние было целое. Проверять +`git diff --cached --name-only` перед повтором. +🔴 **`gitignore` НЕ отвязывает уже отслеживаемые файлы** — без `git rm --cached` они +останутся в PR навсегда. Нужны оба шага, в этом порядке. 📌 **Правило на будущее:** проверять состав PR через `git diff --stat upstream/main...HEAD` **перед** объявлением готовности, а не только @@ -214,15 +300,29 @@ tools/deltas-mattermost-2.json … -13.json (12 файлов) ## Что осталось -**Ничего по коду.** Дальше по желанию Alex: +**По PR — ничего, кроме ревью.** Всё выполнено: -0. 🔴 **Убрать `tools/` из ветки** — вариант (a) выше. Блокирует «стыдный» PR. -1. `git push -u origin feat/mattermost-channel` и открыть PR в `baldaworks/balda`. -2. Фаза 3/4 (эксплуатация на Kraken) — [[personal/projects/balda/balda-kraken-mm-deploy]]. +| Шаг | Статус | +|---|---| +| `tools/` убран из ветки, `/tools/` в `.gitignore` | ✅ коммит `9f1e6c9c` | +| `git push -u origin feat/mattermost-channel` | ✅ запушено | +| PR в `baldaworks/balda` открыт | ✅ **#8**, 41 файл, +5622/−18 | +| CI | ✅ **8/8 PASS** | +| Локальные гейты (build/vet/lint/arch-lint/no-race) | ✅ все зелёные | -⚠️ Скрипты `tools/mm-*.sh` (9 шт.) и дельты `tools/deltas-mattermost-14…26` + -`deltas-goal-command-fix.json` + `deltas-mm-prefixfix.json` — **untracked, локальный -инструментарий**. Для PR в апстрим не отправлять. Существенное продублировано в +**Осталось (по желанию Alex):** + +1. **Ревью апстрима** — ждать. Правки по замечаниям делать **в эту же ветку**: + push обновит PR автоматически. +2. 🔴 **Боевая проверка на Kraken — НЕ сделана.** PR-merge ≠ «бот работает на стенде». + Нужно: пересобрать образ из `9f1e6c9c`, задеплоить, `@balda` → ответ в ленте канала. + См. [[personal/projects/balda/balda-kraken-mm-deploy]]. +3. **Замержить PR** (после ревью) — тогда Фаза 2 закрывается официально. + +⚠️ Скрипты `tools/mm-*.sh` (9 шт.), дельты `tools/deltas-mattermost-14…26` + +`deltas-goal-command-fix.json` + `deltas-mm-prefixfix.json`, `apply-project-deltas.py`, +`run-project-deltas.sh`, `.deltas-state.json` — **untracked, локальный инструментарий**, +git-ignored. Существенное продублировано в [[personal/projects/balda/balda-kraken-mm-deploy]] и [[personal/tech/balda-go-fx-pitfalls]]. ## Связанные заметки diff --git a/personal/projects/balda/balda-mattermost-connector.md b/personal/projects/balda/balda-mattermost-connector.md index 04d5affa..3a8f566f 100644 --- a/personal/projects/balda/balda-mattermost-connector.md +++ b/personal/projects/balda/balda-mattermost-connector.md @@ -1,30 +1,25 @@ # Balda — Mattermost коннектор (архитектура) -> Создано 2026-09-26. Обновлено 2026-09-26 (сессия PR-подготовки, завершена). -> Статус: **🟡 КОД ГОТОВ К PR — 6 коммитов, все гейты зелёные. Осталось одно решение: `tools/` в ветке.** -> 🟢 **Сессия PR-подготовки завершена:** починены 2 дефекта **в коде** (идемпотентный -> `MattermostSubject`; `ProgressPolicy.Typing`), написаны 6 файлов тестов (~90 тестов) -> на транспорт + хендлер, обновлены README / `docs/balda.md` и создан `docs/mattermost.md`. -> `golangci-lint` = 0 issues, `go-arch-lint` = OK. -> 🔴 **Блокер PR:** в ветке 16 файлов `tools/` из базового коммита `004fda2f` — в апстриме -> такого каталога нет. Нужно `git rm -r --cached tools/` по команде Alex (заблокировано -> истёкшим апрувом). Детали, коммиты и варианты — [[personal/plans/balda-mm-pr-plan]]. +> Создано 2026-09-26. Обновлено 2026-09-26 (PR открыт). +> Статус: **✅ PR ОТКРЫТ — baldaworks/balda#8, 8/8 CI PASS, MERGEABLE.** +> 🟢 **PR:** https://github.com/baldaworks/balda/pull/8 — 41 файл, +5622/−18, 7 коммитов. +> Фаза 2 закрыта полностью: код, 103 новых теста, доки, зелёный CI. +> Блокер `tools/` снят: `/tools/` в `.gitignore` + `git rm -r --cached tools/`. +> Детали — [[personal/plans/balda-mm-pr-plan]]. > Часть проекта [[personal/projects/balda/balda-mm-delegation-plan]] (Фаза 2). > Требование Alex: поддержка **всех** функций Балды, по новым API, production-качество. > -> **Проверено (2026-09-26, финал):** -> - `go build ./...` → exit 0, `go vet ./...` → exit 0 +> **Проверено (2026-09-26, PR):** +> - `go build ./...` → exit 0 · `go vet ./...` → exit 0 > - `go tool golangci-lint run` → **0 issues** > - `go tool go-arch-lint check --project-path .` → **OK - No warnings found** -> - **DI-граф собирается целиком** с `balda.mattermost.enabled=true` — проверено реальным +> - **DI-граф собирается целиком** с `balda.mattermost.enabled=true` — проверено > `validateBaldaApplication(prepared)` через fx, а не только компиляцией -> - 3 новых теста зелёные (см. §«Тесты») -> - Закоммичено: `004fda2f feat(mattermost): add Mattermost transport adapter` (40 файлов, +3072) +> - **103 новых теста** зелёные, включая `-race`; в CI — 126 PASS, 0 FAIL, 0 SKIP +> - **CI: 8/8 джобов PASS** на чистом раннере (`.github/workflows`) > -> **Осталось:** ничего по коду. Дальше — Фаза 3 (Балда с MM на Kraken) и Фаза 4 (исполнитель). -> -> 🔴 **ОБНОВЛЕНО 2026-09-26 (ночь, позже): «все quality gates пройдены» оказалось неполным.** -> При первом **живом запуске** на Kraken (Фаза 3) Балда ушла в crash-loop: +> 🔴 **Исторический урок (Фаза 3):** «все quality gates пройдены» оказалось неполным — +> при первом **живом запуске** на Kraken Балда ушла в crash-loop: > `advertised commands have no actor handler: goal`. В `Advertisement.Names` была объявлена > несуществующая команда `goal` (это payload kind, а не имя хендлера; цикл называется > `goalkeeper`). **Ни `go build`, ни `go vet`, ни `golangci-lint`, ни `go-arch-lint`, ни @@ -32,13 +27,23 @@ > `inbound.go` дельтой `tools/deltas-goal-command-fix.json`. Разбор — > [[personal/tech/balda-go-fx-pitfalls]] §«Дефект 4». > 📌 Вывод: «Фаза 2 закрыта» означала «код компилируется и граф собирается», но **не** -> «транспорт работает». Живой запуск обязателен. Текущий статус Фазы 3 — +> «транспорт работает». Живой запуск обязателен. Статус Фазы 3 — > [[personal/projects/balda/balda-kraken-mm-deploy]]. > ## ✅ PR-ПРОХОД 2026-09-26 — транспорт приведён к PR-качеству -Ветка `feat/mattermost-channel`, **6 коммитов**, готово к `git push` и PR. -Подробности — [[personal/plans/balda-mm-pr-plan]]. +Ветка `feat/mattermost-channel`, **7 коммитов**, PR **открыт** — baldaworks/balda#8. +Подробности и полный список — [[personal/plans/balda-mm-pr-plan]]. + +``` +9f1e6c9c chore: ignore local project-editing tooling ← уборка tools/ из PR +58f27120 docs(mattermost): document the transport setup and behavior +80d4f41d test(mattermost): cover the transport, the ingress and the handler +c7f18654 fix(mattermost): send a monotonic seq on every client frame +6dc3077a feat(mattermost): add the inbound handler and wire it into DI +fa1a2855 feat(auth): support the Mattermost channel subject +004fda2f feat(mattermost): add Mattermost transport adapter ← база прошлой сессии +``` **Исправлено в коде (не в данных):** @@ -47,12 +52,13 @@ | 🔴 Двойной префикс `principal` — прошлый фикс был в БД и вернулся | `auth.MattermostSubject()` теперь **идемпотентен** (срезает `mattermost:` перед повторной квалификацией); из `handler.go` убран `TrimPrefix`-костыль | | `ProgressPolicy.Typing: true` в command-пути | Приведено к `false` — у ботов MM нет typing API, `SendTyping` осознанный no-op | -**Тесты — 6 новых файлов, ~90 тестов:** `locator_codec_test.go`, `inbound_test.go`, -`adapter_test.go`, `client_test.go`, `ingress_test.go` (в пакете транспорта) + -`handlersfx/mattermost_ingress_test.go`. Ключевые регрессионные guard'ы: +**Тесты — 6 новых файлов, 103 теста:** `locator_codec_test.go` (15), `inbound_test.go` (18), +`adapter_test.go` (17), `client_test.go` (13), `ingress_test.go` (23) + +`handlersfx/mattermost_ingress_test.go` (17). Ключевые регрессионные guard'ы: `writeEvent` всегда с `seq` и уникальный `seq` под 16 горутинами (`bad_seq`), plain-fallback только на content-rejection (не на 5xx), идемпотентность subject'а, тред = одна сессия ≠ канальной, `/start` доступен до авторизации. +Пакеты `mattermost` + `handlersfx` в verbose-прогоне: **126 PASS, 0 FAIL, 0 SKIP**. **Доки:** README + `docs/balda.md` + новый **`docs/mattermost.md`** (создание бота, минтинг токена, membership, 5 ключей, поведение, troubleshooting). @@ -65,11 +71,13 @@ go tool golangci-lint run → 0 issues go tool go-arch-lint check --project-path . → OK - No warnings found go test -race (затронутые пакеты) → все ok ``` -⚠️ 5 пакетов падают (`cmd/balda`, `apps/balda`, `mcpruntime`, `session`, `state`) — +⚠️ 5 пакетов падают локально (`cmd/balda`, `apps/balda`, `mcpruntime`, `session`, `state`) — **это апстримная база**, проверено `git stash` (те же 4 теста падают без правок). +🔴 **В CI эти же тесты ПРОШЛИ** — локальное падение было особенностью машины, не дефектом. -⚠️ `tools/mm-*.sh` + `tools/deltas-*.json` оставлены **незакоммиченными** — локальный -инструментарий, в PR не идёт. +**`tools/` вычищен из ветки:** `/tools/` в `.gitignore` + `git rm -r --cached tools/` +(коммит `9f1e6c9c`). Файлы на диске целы (39 шт.), в PR-diff их нет. +⚠️ `gitignore` **не отвязывает** уже отслеживаемые файлы — нужны оба шага. --- diff --git a/personal/projects/balda/balda-mm-delegation-plan.md b/personal/projects/balda/balda-mm-delegation-plan.md index 3c4a021d..a05bf7db 100644 --- a/personal/projects/balda/balda-mm-delegation-plan.md +++ b/personal/projects/balda/balda-mm-delegation-plan.md @@ -1,7 +1,8 @@ # Balda 2.0 — Mattermost + распределённое делегирование (план) > Создан 2026-09-25. Статус: **Фазы 1-3 закрыты 2026-09-26** (MM на Kraken · MM-адаптер -> в коде · оркестратор развёрнут, **бот отвечает в MM**). Следующая — Фаза 4 (исполнитель). +> в коде · оркестратор развёрнут, **бот отвечает в MM**). Фаза 2 выведена на **PR +> `baldaworks/balda#8`** — 8/8 CI PASS, ждёт ревью. Следующая — Фаза 4 (исполнитель). > Alex дал команду исполнять без вопросов. > Задача: поднять свежую Балду из `baldaworks/balda` (без наших коммитов), завязать на Mattermost, > построить распределённое делегирование (дешёвый оркестратор + подписочные CLI в VM). @@ -203,8 +204,9 @@ Mattermost (Alex) — итог + артефакты туннель `{"status":"OK"}`. ⏳ Осталась приёмка входом в браузере (делает Alex). → [[personal/projects/balda/mattermost-kraken-deploy]] -### Фаза 2 — адаптер Mattermost в Балде — ✅ ЗАКРЫТА (все quality gates пройдены) +### Фаза 2 — адаптер Mattermost в Балде — ✅ PR ОТКРЫТ: baldaworks/balda#8 Детальный док: [[personal/projects/balda/balda-mattermost-connector]]. +PR-план и команды: [[personal/plans/balda-mm-pr-plan]]. 1. ✅ Ветка **`feat/mattermost-channel`** от чистого `upstream/main` (`de273d8f`). 2. ✅ Изучен `channel/zulip/*` целиком как образец. @@ -221,12 +223,26 @@ Mattermost (Alex) — итог + артефакты 8. ✅ **DI-граф проверен реальным `validateBaldaApplication`** с `mattermost.enabled=true` — найдены и починены 3 дефекта (`fx.In` + `fx.ParamTags`, отсутствующие провайдеры строк, отсутствующий импорт). -9. ✅ 3 новых теста зелёные (env-override + fx-граф + маршрут `TestBuiltinRoutes`). -10. ✅ **`golangci-lint` = 0 issues, `go-arch-lint` = OK**, `go build`/`go vet` = exit 0. -11. ✅ **Закоммичено:** `004fda2f feat(mattermost): add Mattermost transport adapter` (40 файлов, +3072). +9. ✅ **`golangci-lint` = 0 issues, `go-arch-lint` = OK**, `go build`/`go vet` = exit 0. +10. ✅ **Закоммичено:** `004fda2f feat(mattermost): add Mattermost transport adapter` (40 файлов, +3072). +11. ✅ **PR-проход (2026-09-26):** починены 2 дефекта в коде (идемпотентный + `MattermostSubject` — из-за него бот не отвечал владельцу; `ProgressPolicy.Typing`), + написаны **103 новых теста** в 6 файлах, доки README/`docs/balda.md`/`docs/mattermost.md`. +12. ✅ **`tools/` вычищен из ветки** — `/tools/` в `.gitignore` + `git rm -r --cached tools/` + (коммит `9f1e6c9c`); файлы на диске целы, в PR-diff их нет. +13. ✅ **PR ОТКРЫТ: `baldaworks/balda#8`**, 41 файл, +5622/−18, 7 коммитов. +14. ✅ **CI: 8/8 PASS** на чистом раннере (architecture-lint, lint, test, ingress-e2e, + jetstream-integration, postgres-integration, sqlite-integration, projection-replay). -**Фаза 2 закрыта на уровне кода.** Все quality gates из `AGENTS.md` пройдены. -Дальше — Фаза 3 (развернуть Балду с MM-адаптером на Kraken) и Фаза 4 (исполнитель в Docker на Mac). +**Фаза 2 закрыта на уровне кода и PR.** Осталось: **ревью апстрима + merge**. + +📌 **Целевой репозиторий — `baldaworks/balda`.** `origin` = `mallexxx/balda` (форк, родитель +baldaworks), `upstream` = `normahq/balda` = **редирект** на baldaworks (тот же org id). +PR: `gh pr create --repo baldaworks/balda --base main --head mallexxx:feat/mattermost-channel`. + +🔴 **Урок PR-прохода:** локальные гейты — необходимое, но **не достаточное** условие. Локально +`cmd/balda` падал на `TestInitCommand_...GeneratesDetectedAgents`, **в CI тот же тест прошёл** — +падение было особенностью машины. В репо есть CI (8 джобов) — **дожидаться его** перед «готово». #### Дефекты, найденные на этапе закрытия (важно для будущих транспортов) @@ -238,11 +254,14 @@ Mattermost (Alex) — итог + артефакты | 4 | `TestBuiltinRoutes` ждал 4 маршрута | `len(BuiltinRoutes()) = 5, want 4` | добавить маршрут Mattermost в `want` | | 5 | Мёртвый typing-throttle (`shouldSendTyping`, поля, mutex, `SetTypingThrottleInterval`) | `unused` ×2, затем каскад `undefined` | удалён целиком — `SendTyping` всегда no-op | | 6 | Обёртка `func(client, logger) *Adapter` без тела | `unlambda` (gocritic) | заменена на `mattermost.NewAdapter` | +| 7 | 🔴 **Двойной префикс `principal`** (`mattermost:mattermost:`) | каждый пост владельца → `settlement_stage=unauthorized`, бот молчит | `MattermostSubject()` сделана **идемпотентной** (срезает префикс) — дефект был «починен» в БД и вернулся | +| 8 | `ProgressPolicy.Typing: true` в command-пути, `false` в inbound | мёртвая ветка + warn-шум | приведено к `false` — у ботов MM нет typing API | ⚠️ **Метод проверки, который окупился:** `go build` ловит только компиляцию. **Реальный DI-граф валидируется только `validateBaldaApplication` через fx** — именно этот тест нашёл дефекты 1-2, которых `build`/`vet` не видят. Для любого нового транспорта писать такой тест сразу. - +🔴 **Дефект из данных — чинить в коде, а не в БД:** дефект 7 «починили» UPDATE'ом и он вернулся +при следующей записи. 🔴 **Урок Фазы 2:** `go build` = exit 0 **не значит** «транспорт рабочий». fx-несовместимости и отсутствующие провайдеры видны только при `fx.ValidateApp`/`validateBaldaApplication`. diff --git a/personal/tech/balda-go-fx-pitfalls.md b/personal/tech/balda-go-fx-pitfalls.md index ccdfe0a9..8d335373 100644 --- a/personal/tech/balda-go-fx-pitfalls.md +++ b/personal/tech/balda-go-fx-pitfalls.md @@ -1223,6 +1223,51 @@ git rm -r --cached tools/ # 🔴 файлы ОСТАЮТСЯ на диске ⚠️ Артефакты деплоя (`mm-*.sh` ops-скрипты) в PR не шлём — только код транспорта, wiring, тесты, доки. +## 🔴 Целевой репозиторий определять через API, не по имени remote'а + +Два remote'а выглядели так: `origin` = `mallexxx/balda` (форк), `upstream` = `normahq/balda`. +Но **реальный родитель форка — `baldaworks/balda`**, а `normahq/balda` — просто редирект: + +```bash +gh repo view mallexxx/balda --json parent +# → parent: baldaworks/balda + +gh repo view normahq/balda --json owner +# → owner.id: O_kgDOEgAHrw ← тот же org, что у baldaworks/balda = редирект, не отдельный репо +``` + +📌 Создавать PR в **`baldaworks/balda`**, а `--head` указывать как `mallexxx:` +(«владелец:ветка»). Полный URL форка в `--head` ломает `gh`. + +```bash +gh pr create --repo baldaworks/balda --base main \ + --head mallexxx:feat/mattermost-channel \ + --title "…" --body-file /tmp/pr-body.md +``` + +⚠️ **Проверять базу перед пушем:** `git rev-list --left-right --count upstream/main...HEAD` +→ `0 7` значит «0 позади, 7 впереди» — идеальная база, rebase не нужен. + +## 🔴 CI на чистом раннере — это НАСТОЯЩАЯ приёмка сборки (шире локальных гейтов) + +Локально `cmd/balda` падал на +`TestInitCommand_NonInteractiveAutoSelectsRootAndGeneratesDetectedAgents`. +В CI (чистый раннер, свой образ) **тот же тест прошёл**. + +Локальные гейты (`build`/`vet`/`golangci-lint`/`go-arch-lint`/`go test`) — **необходимое, +но не достаточное** условие. Они видят состояние **моей** машины: остатки кэшей, локальные +конфиги, переменные окружения, версии инструментов. CI видит чистое дерево. + +**Пайплайн в `baldaworks/balda` (8 джобов):** +`architecture-lint`, `lint`, `test`, `ingress-e2e`, `jetstream-integration`, +`postgres-integration`, `sqlite-integration`, `projection-replay`. + +📌 **Порядок:** локальные гейты → push → **дождаться CI** → только потом говорить +«готово». Не объявлять готовность по локальным гейтам, если в репо есть CI. + +⚠️ Локальный фейл, которого нет в CI, — **не повод чинить код**: сначала проверить, не +особенность ли это локальной среды (`git stash` + повтор, или прогон в контейнере). + ## Связанные заметки - [[personal/projects/balda/balda-mattermost-connector]] — Фаза 2, где всё это найдено