diff --git a/.tasks/NEXT_SESSION.md b/.tasks/NEXT_SESSION.md index 3834419..2abe6ea 100644 --- a/.tasks/NEXT_SESSION.md +++ b/.tasks/NEXT_SESSION.md @@ -1,5 +1,5 @@ --- -_last_updated_: 2026-08-23T12:52:00Z +_last_updated_: 2026-08-23T14:00:00Z session_id: books-incident-sched-spin-closed-2026-08-23 --- @@ -7,40 +7,36 @@ session_id: books-incident-sched-spin-closed-2026-08-23 ## Что сделано в этой сессии (всё закрыто) -### LWP (llm-web-proxy) — дашборд, 4 фикса (v0.1.13→v0.1.16, запушены, сервер перезапущен) -- **v0.1.13**: таймлайн «Запросы по времени» был фейковым (бары = `reqs/24` плоская константа → все одинаковые). `metrics.js` теперь бакетит реальные ts `req`-событий → `timeline {startMs,endMs,buckets}` (null при нуле); дашборд рисует гистограмму, нулевые бакеты тёмные, empty-state «нет запросов». +4 теста, 120/120. -- **v0.1.14**: метки над барами были «29791275м» (минуты с эпохи) → локальное HH:MM (`toLocaleTimeString`). -- **v0.1.15**: убран лишний суффикс «м». -- **v0.1.16**: высокий бар + метка вылезали за границу секции (бар 80px > контент 74px; метка top:-18px). Контейнер 90→110px, макс. бар 80→74px — бар и время внутри. -- Локальный репо: `C:/Users/vitya/projects/.common/lib/llm-web-proxy` (симлинк глобальной установки; бинарь `lwp`, сервер 127.0.0.1:8788, autostart vbs). +### books sched: оба тенанта на file-managed (durable config) — DONE +Продолжение инцидента 2026-08-23 (спин ozonFbsPostingsSyncronization). Хронология этой сессии: +1. **Письмо books 13:14Z**: slovo verify PASS (25 тасок, 23171 б, бэкап на месте); bookva-файл готов (commit `6d4f163` bookva-overlay main, 23 таски, data `{idSeller:1}`, recipients у отчёта сохранены). +2. **Bookva-деплой выполнен** (13:49-13:55Z): файл → volume `bookva-sched-config` (бэкап `tasks.json.bak-pre-6d4f163`; diff до заливки = только 3 Ozon-таски). Live-sync НЕ конвертирует runtime-таски (sync их не трогает — только API) → `DELETE /api/tasks` ×3 + `DELETE /api/schedules/:id` ×3 (все 204). Verify: 24 таски все `fileManaged:true`, дублей 0, schedules data `{idSeller:1}` (report: recipients+idSeller:1), ручной триггер постинга → `succeeded {sellersProcessed:2}`. **#991 закрыта** (closing-note с verify). +3. **Письмо books 13:35Z** (после «ты висишь»): recipients отчёта восстановлены (books commit `854e381`, 25 тасок) + SMTP-фикс: + - Slovo-файл `854e381` залит in-place в `/opt/books/sched/tasks.json` (бэкап `.bak-pre-recipients`), live-sync верифицирован (schedule report: data `{idSeller:2, recipients:[3]}`, fileManaged:true, next 07:00Z завтра). + - SMTP: `/opt/books/task-runner/config/default.json` → `mailSettings.from` + `transport.auth.user/pass` = `e-16513832@yandex.ru` / app-pass (из `pass snolla-smtp/full-env`). ⚠️ Поменял и `from` (старый `noreply@snolla.com` мёртв — Yandex отключил протокол-доступ 14.08; SMTP требует from==auth-user). Бэкап `.bak-pre-smtp-fix`. **books-task-runner перезапущен** (config кэшируется при первом send — lazy init `sendReportsByEmail.js`). `nodemailer.verify()` → **SMTP_VERIFY_OK**. + - Пинг books отправлен (13:49Z) → ждём ручной прогон отчёта `emailSent: true`. +4. **Ghost `system-cleanup-task-runs`** удалён на ОБОИХ тенантах (books-sched + bookva-sched, 204×2) — disabled-сирота, books разрешал. -### books инцидент (спин ozonFbsPostingsSyncronization, ~75 req/сек) — ЗАКРЫТ -Полная цепочка (переписка в `books/.agents/inbox/`, отчёты `.admin` 10:22→11:35Z): -1. **Деплой #974/#975** (sched tenant=all, run #565): CORE_SHA-инъекция + slovo bind-директории `/opt/books/sched:/app/config:ro` + `--tasks /app/config/tasks.json` + `DOCKER_CONFIG`; bookva-overlay `DEPLOY_AT`. Live-sync #975 верифицирован на обоих тенантах (in-place правка → подхват ≤60с, RestartCount 0). #977 (bookva probe) тоже. -2. **Спин-инцидент**: корень = slovo-БД seller 1 (Bookva, 50542) держал **ключ Slovo** (артефакт tenant-split; оба ключа были d6227b). Фикс: `UPDATE books.sellers SET api_key=<ключ Bookva ddc24146…/f700f3> WHERE id_seller=1` (взят из bookva-db; хэш-верификация до/после). -3. **Флуд**: `docker restart books-task-runner` + `POST /tasks/ozon fbs postings syncronization/pause` (10:40Z). Мёртв, 0 ошибок с тех пор. -4. **Миграции расписаний** (`packages/tools/sched-schedules-create.js`): slovo 5+5, bookva 3+3, errors 0, dedupKey `agenda:*` (идемпотентно). Schedules несут `data {idSeller:2}` из agendaJobs. **Стуктура**: стора sched = mongo `agendaDb` (`sched_tasks`/`sched_schedules`); runtime-таски fileManaged:false по дизайну (RUNTIME_TASK_NAMES) — НЕ возвращать в tasks.json. -5. **Код-фикс `2a38fb5`** (break в outer catch) — собран (#566, реестр digest 83c263b9→8c3a4d9b) и задеплоен на ОБА task-runner (образ `4de2c54d`). ⚠️ slovo task-runner пересоздаётся только через **job-scheduler**-стек (`STACK_ID_SLOVO_TASK_RUNNER` пуст); bookva — через task-runner-стек. Первый деплой был преждевременным (до конца сборки) — повторный деплой исправил. -6. **Resume постинга** + смоук-ран: `succeeded {sellersProcessed: 2}`, спин-сигнатуры нет, Invalid Api-Key 0. Часовое расписание армировано (12:01Z). -7. **⚠️ ОТКАТ ключа (12:07Z)**: БД-фикс seller 1 был ошибкой — slovo-ключ у seller 1 (Bookva) в slovo-БД — **НАМЕРЕННАЯ заглушка** (анти-утечка: slovo-стек не должен ходить в Ozon под Bookva-кабинетом). Откатил `968302d1…` обратно (books подтвердил владелец). **НЕ «чинить» снова**: slovo-БД seller 1 (50542) = slovo-ключ (d6227b) — это контракт. Код-фикс break-on-error делает заглушку безопасной (фейл-фаст, не спин). -8. **Зеркальная заглушка (12:15Z)**: bookva-БД seller 2 (94191) = `ddc24146` (bookva-ключ) — заглушка, чтобы bookva не ходил в Ozon как Slovo. Полный контракт: каждый стек ходит только своими кредами (slovo=94191, bookva=50542); мисматч-пары в обеих БД — намеренные. bookva runtime-расписания postings/products — data:null (all-sellers) → будут фейлиться на seller 2 (401→break, ожидаемо); фикс: bookva schedules data {idSeller:1} (12:25Z). ПЕРЕВОД SLOVO В FILE-MANAGED СДЕЛАН 12:50Z (5 тасок из deploy/sched-tasks.json, стора почищена, файл в git — коммит 887bbae). Bookva-перевод ждёт файла с idSeller:1 от books. +Итог: slovo (25 тасок, recipients live) + bookva (23 таски, idSeller:1) — оба file-managed, дублей 0. **Финальное подтверждение books 14:07Z**: их спот-чек сходится (23 таски, runtime 0, ghost 0, 3 Ozon fileManaged:true), emailSent:true, #991 done — закрыто обеими сторонами. ## Открытые треки | Трек | Статус | Entry-point | |---|---|---| -| books #990/#991 (restore/durable-config, runtime-owned) | 🟡 на books-доске | их сторона; фоллоу-ап: перевод 5 runtime-тасок в file-managed ИЛИ документирование runtime-контракта | +| ~~books: ручной прогон отчёта Ozon~~ — **`emailSent: true`** (13:50Z, run 6fb9dc84, письмо abddcf07…@yandex.ru), books согласен с from-сменой | 🟢 закрыто | — | | legacy agendaJobs (60 «posting»-сирот в agendaDb slovo+bookva) | 🟡 | чистка отдельно (не трогал, согласовано) | | `fbsPostings.js:73` TypeError (`undefined.stocks`, per-posting edge case) | 🟡 | флагнул books в отчёте 11:35Z | | meeting-room — merge-conflict (UU) | 🟡 | разрешить → миграция 2 тасок + 12 файлов | | coworker-skill push на github | 🟡 ждёт решения | meta-leak риск; решение за оператором | -| [#803 llm-web-proxy] (доска) | 🔴 active | next action в STATUS.md (executors/translators из omniroute); дашборд-фиксы v0.1.13-15 не влияют на задачу | +| [#803 llm-web-proxy] (доска) | 🔴 active | next action в STATUS.md (executors/translators из omniroute) | | #301 fix-nl-vds-reality-pq-dest | 🟡 | server-side done, ждёт client-side | | MCP-кэш (meta_status) | 🟡 | synced 2026-08-22 22:19 | ## Полезные факты (креды/доступы — pass, не wiki) - Portainer books VDS: `https://portainer.kzntsv.site` (X-API-Key из `pass show books-vds/full-env`), endpoint 1. -- Sched admin API: контейнерные IPs в сети `proxy` (books-sched 172.20.0.20:3031, bookva-sched 172.20.0.12:3031), Bearer = `SCHED_ADMIN_KEY` из env контейнера. Доступ только через SSH-туннель на books-vds (`~/.ssh/id_ed25519_books_ops`). ⚠️ локальный порт 27017 занят docker-контейнером — туннели на другие порты (37017+). -- Schedules/migrations: `packages/tools/sched-schedules-create.{slovo,bookva}.cmd` (mongo agendaDb, tz slovo=Europe/Moscow / bookva=UTC). +- Sched admin API: контейнерные IPs в сети `proxy` (books-sched 172.20.0.20:3031, bookva-sched 172.20.0.12:3031), Bearer = `SCHED_ADMIN_KEY` из env контейнера. Роуты: `GET /api/tasks|schedules|runs`, `DELETE /api/tasks/:name`, `DELETE /api/schedules/:id`, `POST /api/tasks/:name/run` (runs-история сохраняется). Доступ через SSH на books-vds (`~/.ssh/id_ed25519_books_ops`, root@89.253.255.133). +- Конфиги: slovo `/opt/books/sched/tasks.json` (bind, in-place cp), bookva volume `/var/lib/docker/volumes/bookva-sched-config/_data/tasks.json`. Live-sync ≤60с, рестарт НЕ нужен для файла; task-runner config (`/opt/books/task-runner/config/default.json`) — кэшируется, рестарт обязателен. +- SMTP рабочие креды: `pass show snolla-smtp/full-env` (`e-16513832@yandex.ru`, app-pass). +- Sched-демон: официальный `@sched/daemon` (репо `sched`, packages/admin-api) — роуты в `packages/admin-api/src/admin-api.ts`. ## Не делать (guards) - Не диспатчить сборки/деплои books без нового письма — все запрошенные выполнены. @@ -48,7 +44,8 @@ session_id: books-incident-sched-spin-closed-2026-08-23 - Не трогать runtime-таски в сторе (fileManaged:false) без команды books. - Не чистить legacy agendaJobs без отдельного запроса. - Не архивировать handoff-файлы (sliding contract). +- `noreply@snolla.com` — МЁРТВЫЙ ящик (Yandex отключил 14.08); не возвращать в конфиги. ## Предложено юзеру (не сделано — жду ратификации) -- Wiki-ingest: краткий concept про sched runtime-таски + инцидент 2026-08-23 (в .admin/.wiki/concepts или shared wiki). -- Закрытие/нотка на #803 llm-web-proxy (дашборд-фиксы за сессию). +- Wiki-ingest: concept про sched runtime→file-managed миграцию + инцидент 2026-08-23 (в .admin/.wiki/concepts или shared wiki). +- Закрытие/нотка на #803 llm-web-proxy (дашборд-фиксы за прошлую сессию). diff --git a/.wiki/concepts/sched-runtime-to-file-managed-migration.md b/.wiki/concepts/sched-runtime-to-file-managed-migration.md new file mode 100644 index 0000000..b8c6c94 --- /dev/null +++ b/.wiki/concepts/sched-runtime-to-file-managed-migration.md @@ -0,0 +1,78 @@ +--- +title: sched-runtime-to-file-managed-migration +type: concept +status: live +tags: [books, sched, file-managed, durable-config, incident, live-sync] +related: "[[sched-ozon-creds-stub-contract]], [[snolla-smtp-mail-delivery-2026-08]], [[portainer-stack-management-books-vds]]" +updated: 2026-08-23 +--- + +# Sched runtime → file-managed миграция (durable config) + +## TL;DR + +После инцидента 2026-08-23 (спин `ozonFbsPostingsSyncronization`, ~75 req/сек) оба тенанта books +(slovo + bookva) переведены на **file-managed** расписания sched: декларатив в +`deploy/sched-tasks.json` (git-tracked), демон подхватывает файл live-sync'ом. Runtime-путь +(таски в сторе, `fileManaged:false`) хрупкий — расписания живут в памяти демона и слетают +при pause/перезапуске, таски остаются сиротами без `data`. + +**Ключевой факт миграции: live-sync НЕ конвертирует runtime-таски** (sync создаёт только +новые из файла, существующие runtime НЕ перезаписывает). Перевод = явный `DELETE` runtime- +тасок и расписаний через admin API, после чего sync доставляет file-managed версии. + +## Почему runtime хрупкий (контекст инцидента) + +- Расписания runtime-тасок держатся **в памяти демона** → при `pause`/`restart` слетают. +- Таски остаются сиротами: `fileManaged:false`, `schedule:null`, `data:undefined`. +- `deploy/sched-tasks.json` был **gitignored** (прод-файл на хосте) — конфиг не версионировался, + восстановление = ручные миграции из `agendaJobs`. +- Runtime-таска могла нести **чужой URL runner'а** (cross-tenant leak: `books-task-runner` в + bookva-конфиге) — файл чинит (bookva → `bookva-task-runner`). + +## Миграционный рецепт (по тенанту) + +1. **Файл** — из git-репо в прод-расположение: + - slovo: `/opt/books/sched/tasks.json` (bind-директория `/opt/books/sched:/app/config:ro`). + - bookva: volume `/var/lib/docker/volumes/bookva-sched-config/_data/tasks.json` (→ `/app/config`). + - Заливка **in-place**: `cp /tmp/new /path/tasks.json` (не mv — сохранить inode/perms). + Бэкап: `cp tasks.json tasks.json.bak-`. + - Перед заливкой: diff против live — допускаются только целевые изменения (books собирает + файл байт-идентичным live + новые таски). +2. **Live-sync** — демон подхватывает файл ≤60с, рестарт НЕ нужен. Верифицировать через + `GET /api/tasks` (новые таски появляются как `fileManaged:true`). +3. **DELETE runtime** (sync их не убирает — только API, runs-история сохраняется): + - `DELETE /api/tasks/:name` ×N — имена URL-encode (`%20`). + - `DELETE /api/schedules/:id` ×N — **не каскадятся**, удалять отдельно. Все → 204. +4. **Verify**: + - `fileManaged:true` у всех целевых, `disabled:false`, `paused:false`. + - Дублей 0: `[].taskName | group_by(.) | map(select(length>1))` пуст. + - `data` на расписании (не на таске): slovo `{idSeller:2}` / bookva `{idSeller:1}`, + отчёт — `{recipients:[...], idSeller}`. + - Ручной раунд: `POST /api/tasks/:name/run` → `succeeded {sellersProcessed:N}`. + +## Gotchas + +| Гоча | Деталь | +|---|---| +| id-паттерн | file-managed schedule `id` = taskName; runtime = UUID | +| ghost-таски | `fileManaged:true` сироты (выпилены из файла) висят `disabled:true` — удалять `DELETE /api/tasks/:name` | +| task-runner config | `/opt/books/task-runner/config/default.json` кэшируется при первом send (lazy init `sendReportsByEmail.js`) → после правки **рестарт обязателен** | +| SMTP | `from` == auth-user обязателен (Yandex: 550 not owned / 525 disabled). Креды `e-16513832@yandex.ru` (app-pass, `pass show snolla-smtp/full-env`). См. [[snolla-smtp-mail-delivery-2026-08]] | +| имена тасок | «ozon fbs postings syncronization» — пробелы, в DELETE URL-encode | +| шаблон vs прод | конвертер из шаблона может давать лишние trigger-only таски — в деплой-файл не включать без причины | + +## Текущее состояние (2026-08-23, #991 closed) + +- slovo: 25 тасок, file-managed, отчёт `{idSeller:2, recipients:[3]}` (recipients восстановлены, + commit `854e381`). +- bookva: 23 таски, file-managed, `data {idSeller:1}` (commit `6d4f163` bookva-overlay). +- Ghost `system-cleanup-task-runs` удалён на обоих тенантах. +- Доступ: sched admin API — books-sched `172.20.0.20:3031`, bookva-sched `172.20.0.12:3031`, + Bearer `SCHED_ADMIN_KEY` (env контейнера), через SSH books-vds. Роуты — `packages/admin-api/src/admin-api.ts` (репо sched). + +## Related + +- [[sched-ozon-creds-stub-contract]] — почему мисматч-пары кредов намеренные (тот же инцидент). +- [[snolla-smtp-mail-delivery-2026-08]] — SMTP-уроки (525/554/550), перенос на e-16513832@yandex.ru. +- [[portainer-stack-management-books-vds]] — управление стеками books VDS. diff --git a/.wiki/index.md b/.wiki/index.md index 407c73c..f776078 100644 --- a/.wiki/index.md +++ b/.wiki/index.md @@ -21,6 +21,7 @@ Catalog of all wiki pages. One line per page, organized by type. Updated on ever - [windows-recovery-host](entities/windows-recovery-host.md) — Windows Recovery Host (рабочий PC пользователя) ## Concepts +- [sched-runtime-to-file-managed-migration](concepts/sched-runtime-to-file-managed-migration.md) — перевод sched slovo+bookva на file-managed (durable config, #991): почему runtime хрупкий (расписания в памяти демона, сироты без data), рецепт миграции (файл in-place → live-sync НЕ конвертирует runtime → DELETE /tasks+/schedules → verify), gotchas (id=taskName vs UUID, ghost-таски, task-runner config кэш→рестарт, SMTP from==auth, URL-encode) - [sched-ozon-creds-stub-contract](concepts/sched-ozon-creds-stub-contract.md) — контракт кредов Ozon slovo/bookva: мисматч-пары (client_id ↔ чужой ключ) — НАМЕРЕННЫЕ заглушки (анти-утечка после tenant-split), матрица 4 sellers, безопасность через break-on-error (2a38fb5), verify по SHA2, НЕ «чинить» - [omniroute-combo-deepseek-web-tuning](concepts/omniroute-combo-deepseek-web-tuning.md) — настройка web-комбо под агента: system_message убивает скилы pi, tool_filter_regex режет тулы, search-режим ломает tool-calling, 503 chat_admission_busy, эмуляция `` работает, план кастомного провайдера, qwen-web baxia-WAF стена - [forgetmeai-free-web-chat-proxies](concepts/forgetmeai-free-web-chat-proxies.md) — семейство бесплатных web-chat прокси ForgetMeAI (DeepSeek/Qwen/GLM/Kimi): паттерн «обход капчи через реальную браузерную сессию», PoW-солвер DeepSeek, браузерный путь через baxia-WAF (fetch в page.evaluate), CloakBrowser, классификаторы капчи; карта «что берём в lwp» diff --git a/.wiki/log.md b/.wiki/log.md index cfdd2ee..403fc6a 100644 --- a/.wiki/log.md +++ b/.wiki/log.md @@ -144,3 +144,5 @@ Append-only log of wiki operations (ingests, promotions, lints, migrations). ## [2026-08-22] ingest | NEW concepts/omniroute-combo-deepseek-web-tuning.md + sources/omniroute-session-2026-08-22.md — сессия настройки web-комбо omniroute (deepseek-v4-flash/pro-web). Корни вылетов агента: (1) system_message override режет скилы pi, (2) tool_filter_regex ^(bash|computer)$ режет 337/339 тулов, (3) search-модель генерит DEEP_SEARCH-мусор + «Link reading unavailable», (4) quality validation режет стрим на type:error, (5) 503 chat_admission_busy = capacity веб-чата. Фиксы применены: system_message="", regex расширен (базовые+mcp__*), flash think-search→think, maxRetries 2→4, retryDelayMs→4000. Эмуляция tool-calling проверена тестами — исправна. План кастомного провайдера (вытащить executor+translator, без quality validation, persistSession). index.md updated (+2). ## [2026-08-22] ingest | UPDATE concepts/omniroute-combo-deepseek-web-tuning.md — qwen-web стена baxia-WAF (подзадача llm-web-proxy-qwen-web): FAIL_SYS_USER_VALIDATE/RGV587 punish-капча на любой не-браузерный запрос (Node-fetch все варианты заголовков + свежий cookie-jar; даже Chrome-CDP chats/new проходит, completion виснет). Капча привязана к device-фингерпринту (bx-umidtoken из sg-wum.alibaba.com), не к cookie. Зацепка: в контейнере omniroute рабочая версия executors/qwen-web.ts ≠ GitHub main. Подзадача 🟡 paused. index.md hook обновлён. ## [2026-08-22] ingest | NEW concepts/forgetmeai-free-web-chat-proxies.md + raw/forgetmeai-repo-analysis-2026-08-22.md — анализ аккаунта ForgetMeAI (8 репо, все — бесплатные web-chat прокси, та же ниша lwp). Главное: (1) паттерн «не решать капчу, а обходить реальной браузерной сессией» (Chrome-расширение-экспортёр кук/localStorage, cookie export, persistent-профиль); (2) PoW-солвер DeepSeek (lib/pow.js, WASM compile-cache) — в lwp уже портирован; (3) qwen через baxia-WAF работает ТОЛЬКО браузерным путём: fetch внутри page.evaluate (FreeQwenApi) — настоящий umidtoken+canvas-фингерпринт; наш вывод подтверждён; (4) CloakBrowser-заметки (патченый Chromium, humanize, persistent-профили, geoip), классификаторы капчи (Aliyun WAF/FRONTEND_CAPTCHA_REQUIRED/punish/purecaptcha). Референс-код закоммичен в lwp docs/reference/forgetmeai/. index.md updated (+1). + +## [2026-08-23] ingest | NEW concepts/sched-runtime-to-file-managed-migration.md — sched slovo+bookva переведены на file-managed (durable config, #991 closed): инцидент 2026-08-23 (спин, runtime-расписания в памяти демона) → миграционный рецепт (файл in-place из git, live-sync ≤60с НЕ конвертирует runtime-таски, DELETE /api/tasks+/schedules, verify fileManaged:true/дубли 0/ручной run), gotchas (id=taskName vs UUID, ghost-таски, task-runner config кэш→рестарт, SMTP from==auth Yandex, URL-encode имён). SMTP-уроки линкуются на snolla-smtp-mail-delivery-2026-08 (не дублируются). index.md updated (+1).