From 9cdba75df68c56a8df567bd881786f355d9f9e28 Mon Sep 17 00:00:00 2001 From: claude-bot Date: Wed, 10 Jun 2026 04:08:53 +0300 Subject: [PATCH] analyst(ET): auto-commit from analyst run_id=564 --- docs/work-items/ORCH-100/01-brd.md | 167 ++++++++++++++++++ docs/work-items/ORCH-100/02-trz.md | 155 ++++++++++++++++ .../ORCH-100/03-acceptance-criteria.md | 114 ++++++++++++ docs/work-items/ORCH-100/04-test-plan.yaml | 108 +++++++++++ 4 files changed, 544 insertions(+) create mode 100644 docs/work-items/ORCH-100/01-brd.md create mode 100644 docs/work-items/ORCH-100/02-trz.md create mode 100644 docs/work-items/ORCH-100/03-acceptance-criteria.md create mode 100644 docs/work-items/ORCH-100/04-test-plan.yaml diff --git a/docs/work-items/ORCH-100/01-brd.md b/docs/work-items/ORCH-100/01-brd.md new file mode 100644 index 0000000..2bad4b8 --- /dev/null +++ b/docs/work-items/ORCH-100/01-brd.md @@ -0,0 +1,167 @@ +--- +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` (заполняет архитектор). diff --git a/docs/work-items/ORCH-100/02-trz.md b/docs/work-items/ORCH-100/02-trz.md new file mode 100644 index 0000000..f348bfb --- /dev/null +++ b/docs/work-items/ORCH-100/02-trz.md @@ -0,0 +1,155 @@ +--- +work_item: ORCH-100 +stage: analysis +author_agent: analyst +status: ready-for-review +created_at: 2026-06-10 +model_used: claude-opus-4-8 +--- + +# 02 — ТЗ (TRZ): ORCH-100 — FND/F1b: sidecar-watchdog (мозг мониторинга, отдельный контейнер) + +Work Item: **ORCH-100** · Repo: **orchestrator** · Стадия: analysis + +> ТЗ описывает **конкретные изменения к реализации**, выведенные из BRD (`01-brd.md`) и фактического +> кода. Архитектурное обоснование/решения (выбор стека Python/Go, формат хранения порогов, владелец +> диск-алерта, точная топология сети sidecar, бюджет памяти/`mem_limit`) — **зона архитектора** +> (`06-adr/`). ТЗ фиксирует ТРЕБОВАНИЯ и ограничения, не способ реализации. + +## 1. Сводка изменения + +Добавить **отдельный sidecar-контейнер** `orchestrator-watchdog`, код которого лежит в новой папке +`watchdog/` репозитория орка, а рантайм — изолированный контейнер (свой `watchdog/Dockerfile` + сервис +в `docker-compose.yml`). Sidecar периодически (тик): (1) тянет `GET /metrics` орка; (2) меряет хост +(диск/inode/память/CPU); (3) читает статусы контейнеров через read-only `docker.sock`; (4) пингует +Plane/Gitea/Anthropic. По набору **конфигурируемых порогов** через **чистую решающую функцию** +(образец `disk_watchdog.decide`) принимает решение `alert | realert | recovery | none` с дедупом/ +throttle, и шлёт алерт в **собственный** Telegram-канал (свой токен/chat, независимо от кода орка). +Особый сигнал: `/metrics` не отвечает → алерт «орк не отвечает». Всё — never-raise, под kill-switch, +строго read-only к наблюдаемому (self-hosting-безопасно). + +**Орк-сторона (`src/**`) не меняется**: F1b — потребитель уже существующего `GET /metrics` (F1a, +ORCH-099). `STAGE_TRANSITIONS` / `QG_CHECKS` / `check_*` / схема БД орка — **не тронуты**. + +## 2. Задействованные модули / пути + +| Путь | Действие | +|------|----------| +| `watchdog/` | **создать** — корень кода sidecar (новая папка в репо орка) | +| `watchdog/Dockerfile` | **создать** — отдельный тонкий образ sidecar (стек — выбор архитектора) | +| `watchdog/` | **создать** — демон/цикл сбора+решения+отправки (имя/структура — архитектор) | +| `watchdog/` | **создать** — сбор: `/metrics` орка (HTTP), хост (диск/inode/память/CPU), контейнеры (`docker.sock` ro), пинг Plane/Gitea/Anthropic | +| `watchdog/` | **создать** — **чистая** решающая функция порога `(value, threshold, prev_state, now, cooldown) → alert\|realert\|recovery\|none` (образец `src/disk_watchdog.py::decide`) | +| `watchdog/` | **создать** — независимый Telegram-транспорт sidecar (свой токен/chat; НЕ импорт `src/notifications.py`) | +| `watchdog/` | **создать** — чтение порогов/интервалов/токенов/kill-switch из env | +| `watchdog/tests/` (или `tests/watchdog/`) | **создать** — pytest на чистые функции (решение/парсинг/детект орк-down); размещение — архитектор | +| `docker-compose.yml` | **изменить** — добавить сервис `orchestrator-watchdog` (build `watchdog/`, restart-policy, read-only `docker.sock`, `mem_limit`, env, kill-switch) | +| `.env.example` | **изменить** — канон: токен/chat watchdog + пороги + интервалы + kill-switch (без секретов) | +| `CHANGELOG.md` | **изменить** — запись о F1b | +| `docs/work-items/ORCH-100/07-infra-requirements.md` | **создать (architect)** — разовое инфра-действие: добавить сервис в compose, создать bot/chat watchdog, первый запуск на хосте | + +> **`src/**` НЕ редактируется.** Если в ходе разработки выяснится нехватка поля в `/metrics` — это +> отдельная задача-расширение F1a (ORCH-099), а не правка в рамках F1b (см. BRD §«Вне объёма»). + +## 3. Функциональные требования + +### FR-1 — Отдельный контейнер sidecar (BR-1, NFR-1) +Sidecar собирается из `watchdog/Dockerfile` в отдельный образ и поднимается сервисом +`orchestrator-watchdog` в `docker-compose.yml`: отдельный процесс/память/рестарт-политика, **НЕ** +внутри процесса орка. `restart: unless-stopped` (или эквивалент) — sidecar самовосстанавливается. + +### FR-2 — Сбор сырья орка (BR-2, NFR-6) +На каждом тике `GET ` (дефолт-достижимость `http://127.0.0.1:8500/metrics` при +host-network; URL конфигурируем). Тело — версионированный конверт F1a: +`{schema_version, generated_at, clk_tck, stages[], queue, agents[], cost, enabled}`. Парсинг +**толерантен**: неизвестные поля игнорируются, отсутствие опционального — не ошибка, рост +`schema_version` логируется (warning), не крэшит. Из конверта извлекаются сигналы для порогов: +agent-liveness (cpu_ticks/runtime → «завис»), застрявшая стадия, job-failed, длина очереди. + +### FR-3 — Детект «орк не отвечает» (BR-7) — главный сигнал +Если `GET /metrics` завершается таймаутом / connection refused / 5xx / нечитаемым телом — это +**отдельный сигнал тревоги** `orchestrator_down`. Проходит через ту же машину порога/дедупа/recovery +(BR-9): один алерт «орк не отвечает», recovery при восстановлении. Единичный transient не должен +немедленно флаппить — порог/таймаут/ретрай подбираются так, чтобы алерт был осмысленным (детали — +архитектор/developer; требование: «не флаппить на одиночной сетевой икоте»). + +### FR-4 — Сбор хоста (BR-3) +Измерять заполнение диска (% и, где доступно, inode), память, CPU по доступным контейнеру +хост-путям/интерфейсам (стдлиб-средствами выбранного стека; **без** тяжёлых агентов). Пути/пороги — +конфигурируемы. **Диск:** см. FR-9 (анти-дубль с ORCH-063). + +### FR-5 — Сбор контейнеров (BR-4, NFR-4) +Через `docker.sock`, смонтированный **read-only**, читать состояния контейнеров платформы: +различать Up / healthy / restarting / exited / unhealthy. Минимум — статус `orchestrator` (и других +ключевых сервисов). **Только чтение** Docker API (list/inspect) — никаких start/stop/restart/exec. + +### FR-6 — Пинг внешних зависимостей (BR-5) +Периодически проверять доступность Plane / Gitea / Anthropic лёгким запросом (health/ping, короткий +таймаут, never-raise). Недоступность → сигнал для порога. Эндпоинты/таймауты — конфигурируемы. + +### FR-7 — Пороговый алертинг (BR-6, BR-9) +Каждый сигнал проходит через **чистую решающую функцию** (образец `disk_watchdog.decide`): +вход `(value/state, threshold, prev_state, now, cooldown)`, выход `alert | realert | recovery | none`. +Семантика: +- не-alerting & за порогом → **ALERT** (один на пересечение); +- alerting & за порогом & cooldown истёк → **REALERT**; +- alerting & за порогом & в cooldown → **NONE** (анти-спам); +- alerting & вернулось в норму → **RECOVERY**; +- не-alerting & в норме → **NONE**. +Состояние порога (alerting/last_alert_at) — per-signal, in-memory (best-effort; рестарт sidecar +сбрасывает → корректно повторно алертит ещё стоящую проблему, как `disk_watchdog`). Хранилище +состояния/порогов (in-memory vs файл/иное) — **решение архитектора**. + +### FR-8 — Независимый Telegram-транспорт (BR-8, NFR-4) +Отправка через собственный код sidecar (свой ``), читающий **свои** `bot_token`/`chat_id` +из env. **Запрещено** импортировать/вызывать `src/notifications.py` или использовать токен/функции +орка (иначе падение орка утянет алерт-канал). `disable_web_page_preview`/`parse_mode` — по +усмотрению; сообщение содержит суть алерта (сигнал, значение, порог, хост/контейнер). + +### FR-9 — Анти-дубль диск-алерта (BR-10) +Диск уже алертит `disk_watchdog` (ORCH-063, порог 85%, Telegram орка). F1b **не должен** слать +второй диск-алерт на то же событие. **Владельца диск-алерта выбирает архитектор** (варианты: +sidecar становится единственным владельцем и внутренний `disk_watchdog` остаётся как fallback на +случай down-канала орка; ИЛИ sidecar не дублирует диск, оставляя его за ORCH-063). ТЗ фиксирует +инвариант: **на одно событие переполнения диска — не более одного алерта**, решение и его обоснование — +в `06-adr/`. + +### FR-10 — Управляемость (NFR-5) +Kill-switch (env): выключен → sidecar не стартует / инертен, нулевой эффект на орк и конвейер. +Пороги (диск, память, agent-завис N мин, длина очереди, и т.п.), интервал тика, таймауты, cooldown — +из env (`.env.example` — канон). + +### FR-11 — never-raise (NFR-3) +Три уровня: per-source (битый источник деградирует один сигнал, прочие собираются), per-tick (внешний +try/except цикла), per-send (обёрнутая отправка). Демон не падает от ошибки сбора/сети/парсинга. + +## 4. Изменения API + +**Нет** изменений API орка. Sidecar — **клиент** существующего `GET /metrics` (F1a, ORCH-099). Орк +новых эндпоинтов не получает. Sidecar собственного входящего HTTP-API не обязан иметь (опциональный +liveness-эндпоинт самого sidecar — на усмотрение архитектора, вне обязательного объёма). + +## 5. Изменения схемы БД + +**Нет.** Sidecar **не пишет** в БД орка (NFR-4) и не имеет своей БД (тонкий стек, C-3). Состояние +порогов — in-memory best-effort (FR-7). Журнал уроков (F2, БД орка) — отдельная задача, не F1b. + +## 6. Требования к новым/изменённым QG checks + +**Нет.** F1b живёт **вне** процесса орка и **вне** конвейера Quality Gate. `QG_CHECKS` / `check_*` / +`STAGE_TRANSITIONS` — **не тронуты** (по образцу operational-демонов `disk_watchdog`/`reaper`/ +`reconciler`, которые тоже не являются Quality Gate). Sidecar — операционный наблюдатель, не гейт. + +## 7. Совместимость / регресс + +- **Обратная совместимость:** изменения **аддитивны** — новая папка `watchdog/`, новый сервис в + compose, новые ключи в `.env.example`. Существующий орк-контейнер и его поведение — без изменений. +- **Kill-switch:** выключенный sidecar = нулевой эффект (не стартует), полная обратимость (NFR-5). +- **Область раската:** только инфраструктура наблюдения; конвейер всех проектов не затронут + (self-hosting-безопасно, NFR-4). +- **Регресс:** существующий `pytest tests/` остаётся зелёным; новые тесты sidecar добавляются + изолированно (FR — чистые функции тестируемы без контейнера/таймера, образец + `tests/` для `disk_watchdog.decide`). +- **Разовое инфра-предусловие** (не код): добавить сервис в compose + создать bot/chat watchdog + + первый запуск на хосте (Слава/Стрим). Зафиксировать в `07-infra-requirements.md`. Отсутствие + bot/chat watchdog = sidecar не шлёт (fail-safe, логирует), но не падает. diff --git a/docs/work-items/ORCH-100/03-acceptance-criteria.md b/docs/work-items/ORCH-100/03-acceptance-criteria.md new file mode 100644 index 0000000..05c024f --- /dev/null +++ b/docs/work-items/ORCH-100/03-acceptance-criteria.md @@ -0,0 +1,114 @@ +--- +work_item: ORCH-100 +stage: analysis +author_agent: analyst +status: ready-for-review +created_at: 2026-06-10 +model_used: claude-opus-4-8 +--- + +# 03 — Критерии приёмки (Acceptance Criteria): ORCH-100 — FND/F1b: sidecar-watchdog + +Work Item: **ORCH-100** · Repo: **orchestrator** · Стадия: analysis + +Формат: каждый критерий имеет **PASS** (что должно быть истинно для приёмки) и **FAIL** (что +считается провалом). Reviewer/tester проверяет их буквально по файлам репозитория и поведению. + +--- + +## AC-1 — Sidecar стартует отдельным контейнером и собирает все источники + +**Условие:** есть папка `watchdog/` с кодом + `watchdog/Dockerfile`; в `docker-compose.yml` есть +сервис `orchestrator-watchdog`, собираемый из `watchdog/`; запущенный sidecar на тике собирает +сырьё орка (`GET /metrics`) + хост (диск/память/CPU) + контейнеры (`docker.sock`) + пинг зависимостей. +- **PASS:** `watchdog/Dockerfile` существует; сервис `orchestrator-watchdog` объявлен отдельным + сервисом в `docker-compose.yml` (свой build/restart/`mem_limit`, read-only `docker.sock`); код + sidecar реализует все 4 коллектора (метрики орка, хост, контейнеры, зависимости); тик опрашивает + все 4 (подтверждается тестами/логами). +- **FAIL:** мониторинг встроен в процесс орка (`src/**`) / нет отдельного сервиса в compose / отсутствует + любой из 4 коллекторов / `docker.sock` смонтирован НЕ read-only. + +--- + +## AC-2 — Пороговый алерт: один на пересечение + throttle + recovery + орк-down + +**Условие:** при пересечении порога — ровно один Telegram-алерт со **своего** канала sidecar; повтор +в cooldown молчит; возврат в норму шлёт recovery; недоступность `/metrics` орка → алерт «орк не +отвечает». +- **PASS:** чистая решающая функция возвращает `alert | realert | recovery | none` по семантике FR-7 + (тесты TC-01…TC-04 зелёные); алерт идёт через независимый транспорт sidecar (свой токен/chat, БЕЗ + импорта `src/notifications.py`); сценарий `orchestrator_down` (таймаут/refused/5xx) даёт алерт + «орк не отвечает» (TC-05) и recovery при восстановлении. +- **FAIL:** флапп (>1 алерта на одно пересечение без cooldown) / нет recovery / алерт шлётся через + код/токен орка / `orchestrator_down` не детектируется или не алертит. + +--- + +## AC-3 — Изоляция: падение орка не роняет sidecar + +**Условие:** орк недоступен/упал → sidecar продолжает работать и репортит проблему. +- **PASS:** при недоступном `/metrics` (мок таймаута/refused) тик sidecar не падает, проходит до конца, + формирует алерт `orchestrator_down` (TC-05, TC-08); демон never-raise на трёх уровнях (per-source/ + per-tick/per-send) — ошибка одного источника не валит тик, ошибка тика не валит демон (TC-06). +- **FAIL:** исключение в коллекторе/отправке роняет тик или демон / недоступность орка приводит к + падению/остановке sidecar. + +--- + +## AC-4 — Тонкость, kill-switch, конфигурируемые пороги + +**Условие:** контейнер лёгкий; есть kill-switch; пороги/интервалы конфигурируемы через env. +- **PASS:** `docker-compose.yml` задаёт ограничение памяти sidecar (`mem_limit`/эквивалент) в разумных + пределах (НЕ Grafana/Prometheus-стек); kill-switch (env) при выключении → sidecar не стартует/инертен, + нулевой эффект на орк (TC-07); пороги (диск/память/agent-завис N мин/очередь и т.п.), интервал, + таймауты, cooldown читаются из env; `.env.example` содержит токен/chat watchdog + все пороги/интервалы + (канон, без реальных секретов). +- **FAIL:** нет `mem_limit` / тянется Grafana/Prometheus / нет kill-switch или он не отключает sidecar / + пороги захардкожены / `.env.example` не обновлён или содержит реальный секрет. + +--- + +## AC-5 — Анти-дубль диск-алерта (согласовано с ORCH-063) + +**Условие:** на одно событие переполнения диска — не более одного алерта; владелец зафиксирован в ADR. +- **PASS:** в `06-adr/` зафиксировано решение о владельце диск-алерта (sidecar vs внутренний + `disk_watchdog` ORCH-063); реализация не порождает два алерта на то же событие переполнения; выбор + обоснован. +- **FAIL:** диск алертится дважды (и sidecar, и `disk_watchdog`) на одно событие / решение о владельце + не задокументировано. + +--- + +## AC-6 — Безопасность self-hosting (только чтение/алерт) + +**Условие:** sidecar ничего не мутирует в наблюдаемой системе. +- **PASS:** код sidecar не содержит вызовов записи/управления — нет start/stop/restart/exec контейнеров, + нет записи в `docker.sock` (mount read-only), нет записи в БД орка, нет операций с диском хоста + (кроме чтения заполнения), нет push в `main`. Подтверждается ревью кода + статической проверкой + (TC-09: docker-клиент используется только для list/inspect). +- **FAIL:** sidecar выполняет любое мутирующее действие над контейнерами/диском/БД/прод-инстансом. + +--- + +## AC-7 — Разовое инфра-действие задокументировано; pytest зелёный; доки+CHANGELOG + +**Условие:** инфра-предусловие описано; тесты проходят; документация обновлена. +- **PASS:** `07-infra-requirements.md` описывает разовое действие (добавить сервис в compose, создать + bot/chat watchdog, первый запуск на хосте); `pytest` (полный `tests/` + тесты sidecar) зелёный; + `CHANGELOG.md` содержит запись F1b; релевантные доки (CLAUDE.md/README при необходимости) обновлены. +- **FAIL:** нет `07-infra-requirements.md` / падают тесты / нет записи в CHANGELOG / функционал добавлен + без обновления документации. + +--- + +## Сводная матрица AC ↔ FR/BR + +| AC | Покрывает | +|----|-----------| +| AC-1 | BR-1/2/3/4/5 · FR-1/2/4/5/6 · NFR-4 | +| AC-2 | BR-6/7/8/9 · FR-3/7/8 | +| AC-3 | NFR-1/3 · FR-3/11 | +| AC-4 | NFR-2/5 · FR-10 | +| AC-5 | BR-10 · FR-9 | +| AC-6 | NFR-4 · FR-5/8 | +| AC-7 | BR (доки) · NFR-7 · процессные правила агентов | diff --git a/docs/work-items/ORCH-100/04-test-plan.yaml b/docs/work-items/ORCH-100/04-test-plan.yaml new file mode 100644 index 0000000..2eb4eb7 --- /dev/null +++ b/docs/work-items/ORCH-100/04-test-plan.yaml @@ -0,0 +1,108 @@ +work_item: ORCH-100 +stage: analysis +author_agent: analyst +status: ready-for-review +created_at: 2026-06-10 +model_used: claude-opus-4-8 +title: "FND/F1b sidecar-watchdog — пороговый алертинг, орк-down, изоляция, self-hosting safety" +framework: pytest +scope: > + Покрывает чистую логику sidecar (решающая функция порога, парсинг конверта /metrics, + детект orchestrator-down, never-raise) и структурно-инфраструктурные инварианты (отдельный + сервис в compose, read-only docker.sock, независимый Telegram-транспорт, kill-switch, + анти-дубль диск-алерта). ВНЕ покрытия: реальный Telegram-API, живой docker.sock, живой + хост-хост-стек (мокаются); сетевые коллекторы тестируются на моках, не на боевых Plane/Gitea/ + Anthropic. Стек sidecar (Python/Go) и точное размещение тестов выбирает архитектор — при Python + тесты идут в общий pytest; если архитектор выберет Go, набор тест-кейсов переносится на go test + 1:1 по смыслу (решение/парсинг/детект/never-raise остаются обязательными). +notes: > + Образец чистой решающей функции и её тестов — src/disk_watchdog.py::decide и его тесты в tests/. + Все коллекторы/транспорт мокаются (никаких боевых сетевых/docker-вызовов в CI). Полный регресс + tests/ орка должен оставаться зелёным (src/** не меняется). Тесты sidecar изолированы и не требуют + поднятого контейнера/таймера. Пути модулей watchdog/* — ориентировочные; финальные имена задаёт + архитектор/developer, id и смысл тест-кейсов сохраняются. + +tests: + - id: TC-01 + type: unit + description: "Решающая функция: not-alerting & value>=threshold -> ALERT (один на пересечение порога)" + module: watchdog/tests/test_decision.py + expected: PASS + + - id: TC-02 + type: unit + description: "Решающая функция: alerting & still>=threshold & cooldown НЕ истёк -> NONE (анти-спам throttle)" + module: watchdog/tests/test_decision.py + expected: PASS + + - id: TC-03 + type: unit + description: "Решающая функция: alerting & still>=threshold & cooldown истёк -> REALERT (повторный алерт)" + module: watchdog/tests/test_decision.py + expected: PASS + + - id: TC-04 + type: unit + description: "Решающая функция: alerting & value вернулось ниже порога -> RECOVERY (recovery-сообщение)" + module: watchdog/tests/test_decision.py + expected: PASS + + - id: TC-05 + type: unit + description: "Детект orchestrator-down: /metrics таймаут/connection-refused/5xx -> сигнал orchestrator_down -> ALERT «орк не отвечает»" + module: watchdog/tests/test_orch_down.py + expected: PASS + + - id: TC-06 + type: unit + description: "never-raise: исключение в одном коллекторе (хост/контейнеры/деп) деградирует один сигнал, тик доходит до конца и собирает остальные" + module: watchdog/tests/test_never_raise.py + expected: PASS + + - id: TC-07 + type: unit + description: "Kill-switch: при выключенном флаге sidecar инертен/не стартует тик; пороги/интервалы/таймауты читаются из env (не хардкод)" + module: watchdog/tests/test_config_killswitch.py + expected: PASS + + - id: TC-08 + type: integration + description: "Полный тик при недоступном орке (мок /metrics down): тик не падает, собирает хост/контейнеры/деп, формирует ровно один алерт orchestrator_down, recovery при восстановлении" + module: watchdog/tests/test_tick_orch_down_integration.py + expected: PASS + + - id: TC-09 + type: unit + description: "Self-hosting safety: docker-клиент используется только для чтения (list/inspect); нет вызовов start/stop/restart/exec/записи (статическая/мок-проверка)" + module: watchdog/tests/test_docker_readonly.py + expected: PASS + + - id: TC-10 + type: unit + description: "Независимый транспорт: алерт-отправка использует СВОИ токен/chat sidecar из env и НЕ импортирует src/notifications.py / код орка" + module: watchdog/tests/test_notify_isolation.py + expected: PASS + + - id: TC-11 + type: unit + description: "Толерантность к контракту /metrics: неизвестное поле игнорируется, отсутствие опционального не падает, рост schema_version логируется (warning) без крэша" + module: watchdog/tests/test_metrics_parse.py + expected: PASS + + - id: TC-12 + type: integration + description: "Compose-инвариант: orchestrator-watchdog объявлен отдельным сервисом (свой build watchdog/, restart, mem_limit) с docker.sock в режиме :ro" + module: watchdog/tests/test_compose_service.py + expected: PASS + + - id: TC-13 + type: unit + description: "Анти-дубль диск-алерта: согласно решению ADR владельца — sidecar не порождает второй диск-алерт на то же событие переполнения (по образцу взаимодействия с ORCH-063)" + module: watchdog/tests/test_disk_alert_dedup.py + expected: PASS + + - id: TC-14 + type: unit + description: "Регресс орка: полный pytest tests/ зелёный — src/** не изменён, /metrics-контракт (ORCH-099) не сломан" + module: tests/ + expected: PASS