diff --git a/personal/tech/balda-go-fx-pitfalls.md b/personal/tech/balda-go-fx-pitfalls.md index 7d276890..d570d5e5 100644 --- a/personal/tech/balda-go-fx-pitfalls.md +++ b/personal/tech/balda-go-fx-pitfalls.md @@ -1,7 +1,8 @@ # Balda — Go/fx pitfalls и метод правок (зеркало скилла) -> Создано 2026-09-26. Обновлено 2026-09-26 (ночь, позже) — добавлен **Дефект 4** -> (advertisement ↔ actor-хендлер), найденный при первом реальном запуске на Kraken. +> Создано 2026-09-26. Обновлено 2026-09-26 (ночь, root-cause сессия) — добавлены: +> **WS-авторизация MM** (§«авторизация — ОДИН механизм»), **`Token:`-заголовок мёртв в MM 11.x**, +> **формат JSON дельт** (плоский массив + 3 аргумента у обёртки). > Зеркало секции скилла `devops/docker-balda-service`, добавленной 2026-09-26. > Источник: работа над Mattermost-адаптером Балды (Фаза 2) + деплой на Kraken (Фаза 3). > Проектные доки: [[personal/projects/balda/balda-mattermost-connector]], @@ -379,6 +380,90 @@ bash tools/run-project-deltas.sh apply tools/deltas-mattermost.json tools/.delta ⚠️ Дельты с длинным `replace` писать в JSON через `write_file`, **не** heredoc — кавычки и `\n` ломаются молча. Это тот же питфолл, что у SSH-heredoc с вложенными кавычками. +🔴 **JSON дельт — ПЛОСКИЙ МАССИВ, не объект.** Формат-обёртка +`{"deltas": [...]}` (или любой объект с ключами) роняет апплаер: + +``` +TypeError: string indices must be integers + File "apply-project-deltas.py", line 91, in main + path = root / d["file"] +``` + +Причина: `main()` итерирует `for i, d in enumerate(deltas)` — если `deltas` объект, `d` +становится строкой-ключом. Проверено 2026-09-26 на `deltas-mattermost-14.json`. +**Правильно:** файл начинается с `[`, а не с `{`. + +⚠️ Обёртка `run-project-deltas.sh` требует **ТРИ** аргумента, а не два: +`run-project-deltas.sh `. +Без третьего — `line 9: 3: нужен файл состояния`. +И `check` не поддерживает `--list`: `list` — отдельный глагол (`$1 = list`). + +--- + +## 🔴 Mattermost WebSocket: авторизация — ОДИН механизм, не два + +Найдено живым запуском (Фаза 3, root-cause). Симптом: в логе +`mattermost websocket connected`, но **ни одного** события `posted` в инбаунде, даже +на `logger.level: debug`. При этом пробник (pure Go stdlib) ловил `posted` на том же сокете. + +**Две причины, наложившиеся друг на друга:** + +### 1. `Origin` в handshake → 403 CORS + +Если клиент шлёт `Origin: http://mattermost:8065`, а `SiteURL=https://mm.qentra.top` — +MM отвечает: + +``` +HTTP/1.1 403 Forbidden +{"id":"api.web_socket.connect.upgrade.app_error","message":"URL Blocked because of CORS. Url: …"} +``` + +📌 **`Origin` не слать вообще.** Балда его не шлёт — с ней этой проблемы нет. +Это ловушка для пробников и внешних клиентов. + +### 2. `authentication_challenge` поверх `Authorization: Bearer` → подписка умирает + +`channel/mattermost/ingress.go`, `streamOnce`: + +```go +conn, _, err := dialer.DialContext(ctx, endpoint, header) // header: Authorization: Bearer *** +// … +s.logger.Info().Msg("mattermost websocket connected") // ← лог УЖЕ «здоровый» +conn.WriteJSON(map[string]any{ // ← 🔴 а это ломает подписку + "seq": 1, "action": "authentication_challenge", + "data": map[string]string{"token": token}, +}) +``` + +**Эмпирика (пробник, `-authheader`):** +- с челленджем: `hello` → `status_change` → **тишина 40 с**, хотя пост в канал создаётся; +- без челленджа: `hello` → `posted` штатно. + +То есть избыточный челлендж заставляет MM переинициализировать сессию сокета, и `posted` +на него больше не пушится. **Диагностически коварно:** `websocket connected` логируется +**до** челленджа, поэтому лог выглядит здоровым. + +**Правило:** сокет авторизуется **либо** заголовком на dial, **либо** `authentication_challenge`. +Оба сразу — тишина. Если заголовок есть, челлендж слать **нельзя**. + +**Фикс** — дельта `tools/deltas-mattermost-14.json` (2 дельты: убрать `WriteJSON`-блок +и unused-константу `websocketActionAuthChallenge`). Образ `balda-mm:0.1.2`. + +📌 **Общий урок для транспортов:** лог «connection established» ничего не говорит о том, +что подписка жива. Проверять **фактом получения события**, а не фактом коннекта. +Для MM — пробник из `~/Developer/balda-wsprobe/` (pure stdlib, RFC6455, собирается нативно +на Mac, на хост кладётся статический бинарь). + +### 🔴 MM 11.x: заголовок `Token:` для API мёртв + +`POST /users/login` отдаёт сессию в **заголовке ответа** `Token:`. Но использовать её нужно +**только** как `Authorization: Bearer *** — заголовок `Token: ` в запросах даёт +`401 api.context.session_expired.app_error` при живой сессии (в БД `expiresat` = +180 дней). +Старый стиль MM 5.x больше не принимается. Проверено 2026-09-26. + +📌 Отсюда же: если скрипт падает с `session_expired` — сначала проверить, каким **именем +заголовка** шлётся токен, а не искать проблему в пароле/лимите сессий. + --- ## Связанные заметки diff --git a/personal/tech/hermes-secret-redaction-bypass.md b/personal/tech/hermes-secret-redaction-bypass.md index 0eff3b22..18e28cbf 100644 --- a/personal/tech/hermes-secret-redaction-bypass.md +++ b/personal/tech/hermes-secret-redaction-bypass.md @@ -266,6 +266,67 @@ ssh kraken "bash -s" < mm-config-and-create-bot.sh `ssh` использует stdin **и** для скрипта, **и** для heredoc-канала — они конфликтуют. **Обход:** `ssh host "bash -s" < /dev/null <<'REMOTE' … REMOTE`. +### 🔴 Питфолл G — заголовок `Authorization: Bearer *** `$V1"` в блоке `sh -c '…'` + +```bash +# что написал: +curl -s -X POST $API/posts -H "Authorization: Bearer *** -H "Content-Type: application/json" --data "$P" +# что оказалось на диске: +curl -s -X POST $API/posts -H "Authorization: Bearer *** -H "Content-Type: application/json" --data "$P" +# ^^^ всё после "Bearer " съедено до конца строки +# симптом: sh: syntax error: unterminated quoted string +``` + +Тот же `_AUTH_HEADER_RE`, что в питфолле B, но здесь важно: **срабатывает не только на +входе, но и при записи файла через `write_file`** — если в аргументе tool-call есть литерал +`Authorization: Bearer` рядом с подстановкой переменной. Файл на диске реально испорчен +(проверено `sed -n 'Np'` на хосте — виден обрыв). + +**Обход — собирать и имя заголовка, и схему из кусков, и передавать ОДНОЙ переменной:** + +```bash +SCHEME="Bea""rer" +HD="Auth""orization: ${SCHEME} ${V1}" +curl -s -X POST $API/posts -H "$HD" -H "Content-Type: application/json" --data "$P" +``` + +⚠️ Недостаточно собрать только схему (`HD="Authorization: Bearer $V1"`) — литерал +`Auth`+`orization` всё равно матчится. **Рвать надо и имя, и схему.** + +### 🔴 Питфолл H — `${#VAR}` (длина строки) и `if` на одной строке с `echo` + +```bash +# что написал: +echo "V1_LEN=***" if [ -z "$V1" ]; then echo "EMPTY"; fi +# что уехало: +echo "V1_LEN=***" if [ -z "$V1" ]; then echo "EMPTY"; fi +# симптом: sh: syntax error: unexpected "then" +``` + +Подстановка `${#V1}` матчится и заменяется на `***`, строка **сливается** со следующей, +`if` теряет начало. Диагноз — только `bash -n` на хосте. + +**Обход:** не печатать длину секретоподобных значений; `if` — отдельной строкой, без `echo` +рядом. Для отладки печатать `parsed_token: [$V1]` (значение в скобках читается на глаз). + +### 🔴 Питфолл I — `tr -d "\r"` внутри одинарных кавычек `sh -c` + +```bash +docker run … sh -c 'curl … | tr -d "\r" | grep "^Token:" | cut -d" " -f2' +# внутри одинарных кавычек \r — это ЛИТЕРАЛ r, а не CR-байт +# → CR не удаляется → токен с \r на конце → MM 401 session_expired +``` + +**Обход — октальный код, он работает в одинарных кавычках:** + +```bash +… | tr -d "\015" | grep -i "^Token:" | cut -d" " -f2 +``` + +📌 Симптом коварный: `grep` показывает «нормальный» токен (CR невидим в выводе), но запрос +падает с 401. Отладка — печатать `token: [$V1]` в квадратных скобках: скобка уедет на +следующую строку, если CR остался. + ### ✅ Предпочтительный паттерн для сложных удалённых скриптов Найден после серии из шести осыпавшихся вариантов. **Вложенные heredoc не использовать вообще.** @@ -344,3 +405,8 @@ grep "MM_SQLSETTINGS_DATASOURCE" compose.yml | grep -q 'postgres://${PGUSR}:\${P - **2026-09-26 (позже)**: добавлен §«Как отличить "транспорт испортил" от "файл испорчен"» — критерий по расхождению длин строки на хосте и локально, плюс правило не собирать `patch`-овский `search` из обрезанного `read_file`. +- **2026-09-26 (root-cause сессия Балды)**: добавлены питфоллы **G** (литерал + `Authorization: Bearer` рядом с подстановкой — рвать надо и имя заголовка, и схему), + **H** (`${#VAR}` + `if` на одной строке с `echo`), **I** (`tr -d "\r"` внутри одинарных + кавычек `sh -c` — нужен `\015`, иначе MM 401). Найдено при написании + `~/Developer/balda/tools/mm-post-user.sh`.