--- work_item: ORCH-100 stage: analysis author_agent: analyst status: ready-for-review created_at: 2026-06-10 model_used: claude-opus-4-8 --- # 01 — BRD (бизнес-требования): ORCH-100 — FND/F1b: sidecar-watchdog (мозг мониторинга, отдельный контейнер) Work Item: **ORCH-100** · Repo: **orchestrator** · Стадия: analysis ## 1. Бизнес-контекст и проблема Задача — фундаментный кирпич **F1b** домена 0 «Фундамент» эпика автономного саморазвития (`docs/epics/self-evolution.md`, §2, §«Архитектурные рамки наблюдаемости»). **F1a (ORCH-099)** уже реализовал лёгкий read-only `GET /metrics` в самом орке — он отдаёт **только сырьё** (стадии, очередь, agent-liveness, cost), без порогов/алертов/хранения. F1b — **вторая половина пары:** мозг мониторинга, который это сырьё читает, дополняет внешними сигналами (хост, контейнеры, внешние зависимости) и превращает в **алерты**. **Боль, которую закрывает F1b.** Сегодня платформа слепа к собственному здоровью в реальном времени. Инциденты 06–09.06 (диск хоста молча дорос до 100% и встал весь конвейер — ORCH-063; фантом-merge, deploy-петли, флапп-статусы, зомби-jobs) обнаруживались **постфактум, человеком**. Частичные стражи существуют, но они **живут ВНУТРИ процесса орка** (`disk_watchdog` ORCH-063, `reaper` ORCH-065, `reconciler` ORCH-053): если орк завис/съел память/упал — стражи лягут **вместе с ним**, и платформа слепа именно в критический момент. **Архитектурная рамка — установленный факт заказчика (Слава, 09.06), не предмет переизобретения:** - **C-1 / C-1б:** наблюдатель ОТДЕЛЁН от наблюдаемого. Sidecar-контейнер на том же хосте; КОД sidecar — в репо орка (папка `watchdog/`), но рантайм — **ОТДЕЛЬНЫЙ контейнер** (свой Dockerfile + сервис `orchestrator-watchdog` в `docker-compose.yml`). Изоляция — на уровне контейнера, не репо. - **C-2:** без внешнего плеча (одна площадка; принятый риск — падёт весь хост → молчит и наблюдатель). - **C-3:** тонкий стек — **НЕ Grafana/Prometheus**. Хост впритык: RAM 171Mi free / 7.7Gi, диск 92%. - **Разделение ответственности:** орк отдаёт сырьё (`/metrics`), sidecar — мозг (пороги/алерты/свой Telegram-канал, независимый от кода орка). Орк лёг → `/metrics` недоступен = **сам сигнал тревоги**. **Критический инвариант наблюдаемости:** падение/зависание орка должно делать sidecar **громче**, а не тише. Если орк не отвечает на `/metrics` — sidecar жив и обязан зарепортить это как тревогу «орк не отвечает». ## 2. Объём (scope) ### В объёме - Новая папка `watchdog/` в репо орка: тонкий код sidecar + собственный `Dockerfile`. - Сервис `orchestrator-watchdog` в `docker-compose.yml` (отдельный контейнер, свой рестарт/память). - **Сбор сигналов** (периодический тик): (a) `GET /metrics` орка по HTTP; (b) хост — диск %/inode, память, CPU; (c) контейнеры — через `docker.sock` **read-only** (статусы Up/healthy/restarting/ exited/unhealthy); (d) пинг внешних зависимостей — Plane / Gitea / Anthropic. - **Алертинг по порогам:** диск≥порог, память, agent-завис >N мин, job-failed, застрявшая стадия, контейнер-down/unhealthy, внешняя зависимость недоступна, **орк-down (`/metrics` не отвечает)**. - **Доставка:** Telegram через **СОБСТВЕННЫЙ канал sidecar** (свой токен/chat в `.env`), НЕ через код/Telegram-функции орка. - **Гигиена алертов:** дедупликация + throttle (один алерт на пересечение порога, не флапп) + recovery-сообщение при возврате метрики в норму. - **Управляемость:** kill-switch, конфигурируемые пороги, конфигурируемые интервалы. - `.env.example`: токен/chat watchdog + пороги/интервалы (канон, без секретов). - Документация (`07-infra-requirements.md` — разовое инфра-действие) + `CHANGELOG.md`; pytest зелёный. ### Вне объёма - **Любая авто-ремедиация** (рестарт контейнеров, очистка диска, requeue jobs). F1b — **только наблюдение + алерт** (L0 reactive, эпик §9). Авто-фиксы — домен D1 (отдельные задачи). - **Grafana / Prometheus / TSDB / дашборд-UI / исторические графики** (C-3 — тонкий стек). - **Изменение `/metrics` орка** (контракт F1a/ORCH-099 — данность; sidecar — потребитель). Если обнаружится нехватка поля — это отдельная задача-расширение F1a, не часть F1b. - **Изменение `STAGE_TRANSITIONS` / `QG_CHECKS` / `check_*` / схемы БД орка** — sidecar их не касается (он вне процесса орка). - **Журнал уроков (F2)** — отдельная задача; F1b не пишет в БД орка. - **Второе внешнее плечо мониторинга (L2)** — сознательно отложено (C-2). ## 3. Заинтересованные стороны - **Заказчик / приёмка:** Слава (зафиксировал архитектурные рамки 09.06). - **Постановщик / ведение:** Стрим. - **Затрагивает:** операторов платформы (получатели алертов), все проекты в общем прод-инстансе (enduro-trails и пр.) — sidecar повышает наблюдаемость их общей инфраструктуры, **не вмешиваясь**. - **Исполнители конвейера:** architect (стек, формат хранения порогов, владелец диск-алерта), developer, reviewer, tester, deployer. ## 4. Бизнес-требования (BR) - **BR-1 (отдельный контейнер).** Sidecar собирается в отдельный образ (`watchdog/Dockerfile`) и работает как сервис `orchestrator-watchdog` в `docker-compose.yml` — отдельный процесс/память/ рестарт, **НЕ внутри процесса орка**. - **BR-2 (сбор сырья орка).** На каждом тике sidecar делает `GET /metrics` орка по HTTP и разбирает версионированный конверт (`schema_version`/`stages`/`queue`/`agents`/`cost`), **толерантно к неизвестным/отсутствующим полям** (контракт F1a — additive, версия не растёт на добавление поля). - **BR-3 (сбор хоста).** Sidecar измеряет хост: заполнение диска (% и, где доступно, inode), память, CPU — по смонтированным хост-путям/интерфейсам, доступным контейнеру. - **BR-4 (сбор контейнеров).** Sidecar читает состояние контейнеров через `docker.sock` (**read-only mount**): различает Up / healthy / restarting / exited / unhealthy. Минимум — статус ключевых контейнеров платформы (включая сам `orchestrator`). - **BR-5 (пинг зависимостей).** Sidecar периодически проверяет доступность внешних зависимостей — Plane, Gitea, Anthropic (лёгкий health/ping, короткий таймаут) — и алертит при недоступности. - **BR-6 (пороговый алертинг).** При **пересечении порога** сигналом (диск≥порог, память, agent-завис >N мин, job-failed, застрявшая стадия, контейнер-down/unhealthy, зависимость недоступна) sidecar шлёт **ровно один** Telegram-алерт. - **BR-7 (орк-down = тревога).** Если `GET /metrics` орка **не отвечает** (таймаут/connection refused/5xx) — sidecar шлёт алерт «орк не отвечает». Это **главный** сценарий ценности: наблюдатель жив, наблюдаемый лёг. - **BR-8 (свой Telegram-канал).** Алерты идут через **независимый** транспорт sidecar — собственные bot-токен и chat-id из `.env`, БЕЗ обращения к коду/функциям/токену орка (иначе падение орка утянуло бы и алерт-канал — нарушение C-1). - **BR-9 (дедуп / throttle / recovery).** Повторное нахождение метрики за порогом не флаппит: один алерт на пересечение + анти-спам cooldown между повторами + **recovery-сообщение** при возврате метрики в норму. Поведение — по образцу `disk_watchdog` (ORCH-063): чистая решающая функция `(value, threshold, prev_state, now, cooldown) → alert | realert | recovery | none`. - **BR-10 (нет дубля диск-алерта).** Диск уже алертит `disk_watchdog` ORCH-063 (порог 85%, через Telegram орка). F1b **НЕ должен** порождать второй диск-алерт на то же событие. **Владельца диск-алерта (sidecar vs внутренний `disk_watchdog`) выбирает архитектор** — BRD лишь фиксирует требование «один диск-алерт на событие, без дублирования». ## 5. Нефункциональные требования (NFR) - **NFR-1 (изоляция / резилентность).** Падение/зависание/рестарт орка **НЕ роняет** sidecar (доказывается: орк down → sidecar продолжает тикать и шлёт алерт). Обратное тоже: sidecar — чисто наблюдатель, его падение не влияет на конвейер. - **NFR-2 (тонкость).** Контейнер лёгкий: предсказуемо малое потребление памяти (хост впритык — 171Mi free). Конкретный бюджет памяти и `mem_limit` — решение архитектора; BRD требует «в разумных пределах, измеримо». **НЕ Grafana/Prometheus.** - **NFR-3 (never-raise).** Любая ошибка сбора/парсинга/сети/отправки — best-effort: один битый источник деградирует один сигнал, не роняет тик; ошибка тика не роняет демон. По образцу `disk_watchdog` / `metrics` (три уровня never-raise: per-source, per-tick, per-send). - **NFR-4 (безопасность self-hosting).** Sidecar **только читает и шлёт Telegram** — НИКОГДА не трогает диск/контейнеры/прод, не рестартит, не пишет в `docker.sock` (mount **read-only**), не пишет в БД орка, не пушит в `main`. Безопасен для общего инстанса (enduro-trails не затронут). - **NFR-5 (управляемость / обратимость).** Kill-switch (выключить → sidecar инертен/не стартует, нулевой эффект на орк). Пороги и интервалы конфигурируемы через `.env` (не хардкод). - **NFR-6 (изоляция контракта).** Sidecar толерантен к версии `/metrics`: неизвестное поле игнорируется, отсутствие опционального — не падение; рост `schema_version` логируется (предупреждение), не крэшит. - **NFR-7 (наблюдаемость самого sidecar).** Стартап/тик/решения логируются достаточно, чтобы по логам контейнера понять, что sidecar жив и почему (не)сработал алерт. ## 6. Допущения и ограничения - **Зависимость:** F1b **зависит от F1a (ORCH-099)** — читает `GET /metrics`. Контракт `/metrics` (envelope `schema_version`/`generated_at`/`clk_tck`/`stages`/`queue`/`agents`/`cost`/`enabled`) — установленный факт, sidecar его потребитель. - **Сеть:** орк работает `network_mode: host` (порт 8500) → из host-network sidecar `/metrics` достижим как `http://127.0.0.1:8500/metrics`. Точный сетевой режим sidecar — решение архитектора. - **`docker.sock`** доступен на хосте `/var/run/docker.sock`; монтируется в sidecar **read-only**. - **Разовое инфра-действие** (добавить сервис в compose + первый запуск + создать bot/chat watchdog) выполняется человеком (Слава/Стрим) на хосте — фиксируется в `07-infra-requirements.md`. Дальше код watchdog катится через конвейер (self-hosting). - **Стек (Python/Go), формат хранения порогов, владелец диск-алерта** — **зона архитектора** в рамках C-1…C-3; BRD их не предрешает. - **Известный принятый риск (C-2):** падёт весь хост/Docker → молчит и sidecar (нет внешнего плеча). - **Telegram 48ч** и прочие лимиты транспорта — как у орка (best-effort доставка). ## 7. Критерии успеха Sidecar стартует отдельным контейнером, на каждом тике собирает сырьё орка + хост + контейнеры + зависимости, при пересечении порога шлёт ровно один Telegram-алерт со своего канала (throttle + recovery), при недоступности орка шлёт «орк не отвечает», и переживает падение орка не падая сам. Тонкий, с kill-switch и конфигурируемыми порогами. Разовое инфра-действие задокументировано, pytest зелёный, доки + CHANGELOG обновлены. Детальные PASS/FAIL — `03-acceptance-criteria.md`. ## 8. Риски - **Дубль диск-алерта** с `disk_watchdog` ORCH-063 (BR-10) — нужно явное решение владельца (архитектор). - **Шум алертов** (флапп на границе порога) при недостаточном throttle/recovery — закрывается BR-9. - **Зависимость от `/metrics`:** ложный «орк-down» при сетевой икоте — нужен разумный таймаут/ретрай в пороге, чтобы единичный transient не флаппил (детали — архитектор/developer). - **Ресурсы хоста впритык** — sidecar обязан быть лёгким (NFR-2), иначе сам станет частью проблемы. - **`docker.sock` доступ** — строго read-only; риск привилегий минимизируется mount-режимом (NFR-4). - Детальный реестр и митигации — `10-tech-risks.md` (заполняет архитектор).