analyst(ET): auto-commit from analyst run_id=564
This commit is contained in:
167
docs/work-items/ORCH-100/01-brd.md
Normal file
167
docs/work-items/ORCH-100/01-brd.md
Normal file
@@ -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` (заполняет архитектор).
|
||||
155
docs/work-items/ORCH-100/02-trz.md
Normal file
155
docs/work-items/ORCH-100/02-trz.md
Normal file
@@ -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/<entrypoint>` | **создать** — демон/цикл сбора+решения+отправки (имя/структура — архитектор) |
|
||||
| `watchdog/<collectors>` | **создать** — сбор: `/metrics` орка (HTTP), хост (диск/inode/память/CPU), контейнеры (`docker.sock` ro), пинг Plane/Gitea/Anthropic |
|
||||
| `watchdog/<decision>` | **создать** — **чистая** решающая функция порога `(value, threshold, prev_state, now, cooldown) → alert\|realert\|recovery\|none` (образец `src/disk_watchdog.py::decide`) |
|
||||
| `watchdog/<notify>` | **создать** — независимый Telegram-транспорт sidecar (свой токен/chat; НЕ импорт `src/notifications.py`) |
|
||||
| `watchdog/<config>` | **создать** — чтение порогов/интервалов/токенов/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 <orch-metrics-url>` (дефолт-достижимость `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 (свой `<notify>`), читающий **свои** `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, логирует), но не падает.
|
||||
114
docs/work-items/ORCH-100/03-acceptance-criteria.md
Normal file
114
docs/work-items/ORCH-100/03-acceptance-criteria.md
Normal file
@@ -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 · процессные правила агентов |
|
||||
108
docs/work-items/ORCH-100/04-test-plan.yaml
Normal file
108
docs/work-items/ORCH-100/04-test-plan.yaml
Normal file
@@ -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
|
||||
Reference in New Issue
Block a user