Files
orchestrator/docs/work-items/ORCH-111/01-brd.md

146 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
work_item: ORCH-111
stage: analysis
author_agent: analyst
status: ready-for-review
created_at: 2026-06-15
model_used: claude-opus-4-8
escalate: full-cycle
---
# 01 — BRD (бизнес-требования): ORCH-111 — watchdog должен алертить на долго живущие pytest/дочерние процессы, блокирующие конвейер
Work Item: **ORCH-111** · Repo: **orchestrator** · Стадия: analysis · Трек: **Bug → эскалация в полный цикл**
> **Эскалация (`escalate: full-cycle`).** Задача пришла как баг (`BUG:` в заголовке), но **не**
> является дешёвым багфиксом: закрытие пробела требует **архитектурного решения** — у sidecar-watchdog
> сейчас **нет видимости процессов хоста вообще** (`network_mode: host`, но без `pid: host` и без
> bind-mount `/proc`). Выбор механизма наблюдения (расширение привилегий sidecar vs обогащение
> контракта `/metrics` орком) — это развилка с последствиями для безопасности и стабильного контракта
> `/metrics` (schema_version, ORCH-099). Поэтому пакет — **полный** (а не lite-bug), и задача
> помечена `escalate: full-cycle`: нужен прогон стадии `architecture` + ADR (механизм видимости,
> эвристика детекции, привилегии/безопасность). Оператор снимает багфикс-трек эндпоинтом
> `POST /bug-fast-track/escalate?work_item=ORCH-111` (ADR-001 D5, ORCH-019).
## 1. Бизнес-контекст и проблема
### 1.1 Симптом (установленный факт)
На хосте прода были обнаружены старые зависшие процессы `python3 -m pytest tests/test_install_lite_script.py`,
которые жили **более 2 суток**, грузили CPU и мешали локальному merge-gate re-test. Из-за конкуренции
за CPU задача **ORCH-109 несколько раз упиралась** в `re-test timeout after 600s`
(`merge_gate.retest_branch`). Сами эти процессы **не были подняты как отдельный alert** watchdog'а
оператор узнал о них случайно.
### 1.2 Локализация
- **Мониторинг-мозг** — sidecar-watchdog (ORCH-100, каталог `watchdog/`, сервис `orchestrator-watchdog`).
Он уже алертит на `stage_stuck` (стадия задачи застряла) и `container_down` (контейнер не в норме),
а также `agent_hung`, `orch_down`, `host_mem`, `queue_depth`, `job_failed`, `dep_down`.
- **Сигнал `agent_hung`** (`watchdog/signals.py::eval_envelope`) покрывает **только running-агент-джобы**:
он читает раздел `agents[]` из `GET /metrics`, а тот строится `src/metrics.py::_build_agents` по
`db.get_running_agents()` — то есть **только по `jobs.pid` активных джобов**.
- **Источник зависших процессов** — субпроцессы pytest, которые орк запускает в worktree:
`merge_gate.retest_branch` (`python -m pytest <target>`) и `coverage_gate.measure_coverage`
(`pytest --cov=src`). При `subprocess.TimeoutExpired` Python убивает прямого ребёнка, но
**внуки/репарентированные процессы переживают**; а если сам агент-процесс убит по таймауту
(`exit_code=-9`, ORCH-109) — его дочерний pytest **репарентируется на PID 1** и продолжает жить.
### 1.3 Причина (root cause)
Между двумя наблюдателями зияет **слепая зона**: `agent_hung` видит лишь *отслеживаемые* агент-джобы
(по `jobs.pid`), а **осиротевшие/внебюджетные тестовые субпроцессы** (внуки pytest, репарентированные
на PID 1) **не присутствуют ни в `/metrics`, ни в поле зрения sidecar**у контейнера watchdog нет
доступа к таблице процессов хоста. Поэтому долго живущий pytest, реально блокирующий конвейер через
CPU-голодание merge-gate, **не порождает ни одного сигнала**, пока формально ни одна стадия задачи не
«застряла».
## 2. Объём (scope)
### В объёме
- Новый **отдельный класс алерта** watchdog'а: «долго живущий тестовый/дочерний процесс блокирует
конвейер» — поднимается, даже если стадия задачи формально не `stuck`.
- Детекция долго живущих процессов тест-класса (pytest и родственные субпроцессы гейтов), переживших
свой бюджет/осиротевших, на хосте прода.
- Актуализация конфиг-канона watchdog (`.env.watchdog.example` / блок `WATCHDOG_*` в `.env.example`)
и наблюдаемости.
### Вне объёма
- **Любая реакция на процесс** (kill/SIGTERM/cleanup/reap/перезапуск). Задача — **только мониторинг +
сигнализация** (явное ограничение заказчика). Автоматический reap осиротевших процессов — **отдельная
задача**.
- Изменение конвейера: `STAGE_TRANSITIONS` / `QG_CHECKS` / `check_*` / machine-verdict / схема БД — **не
трогаются** (watchdog — наблюдатель вне процесса орка и вне Quality Gates).
- Расширение `agent_hung` на нетреканые процессы (это другой класс сигнала; дубль запрещён — см. NFR-4).
- Снятие первопричины осиротения процессов в самом орке (надёжный reap внуков pytest) — ценно, но это
**ремедиация**, она вне объёма этой задачи.
## 3. Заинтересованные стороны
- **Заказчик/оператор прода** (Слава) — получает ранний сигнал о CPU-голодании ещё до того, как оно
завалит merge-gate re-test очередной задачи.
- **Self-hosting конвейер orchestrator** — страдает напрямую (инцидент ORCH-109).
- **Тиражные инсталляции (Lite/Bundled, ORCH-102/103)** — sidecar входит в дефолтный комплект; новый
сигнал должен укладываться в канон конфига и не ломать тираж.
- **Принимает результат** — reviewer/tester + оператор (smoke на staging-эквиваленте sidecar).
## 4. Бизнес-требования (BR)
- **BR-1** — Watchdog поднимает **отдельный, узнаваемый** alert, когда на хосте обнаружен долго живущий
процесс тест-класса (pytest и его субпроцессы), возраст которого превышает настраиваемый порог —
**независимо** от того, застряла ли формально какая-либо стадия задачи.
- **BR-2** — Текст алерта **действенно идентифицирует** виновника: фрагмент командной строки, PID,
возраст процесса и (при наличии) доля CPU — чтобы оператор мог сразу разобраться и вручную вмешаться.
- **BR-3** — **Только мониторинг + сигнализация.** Watchdog **не убивает / не останавливает / не шлёт
сигналы** процессу и не выполняет иную ремедиацию (жёсткое ограничение заказчика, рамка C-1
«наблюдатель строго read-only к наблюдаемому», ORCH-100).
- **BR-4** — **Без ложных срабатываний** на легитимных in-flight прогонах: тестовый процесс,
принадлежащий **активному отслеживаемому** агенту/гейту в пределах его бюджета, alert поднимать
**не должен**.
- **BR-5** — Анти-спам и recovery как у прочих сигналов: один alert на пересечение порога, throttled
re-alert по cooldown, однократный recovery при исчезновении процесса (переиспользовать
`watchdog/decision.py::decide` + `AlertState`).
- **BR-6** — Сигнал под **kill-switch** и управляется конфигом (порог возраста, cooldown, область).
Дефолт выбирается так, чтобы включение было **осознанным** и **self-hosting-безопасным** (см. NFR-3).
## 5. Нефункциональные требования (NFR)
- **NFR-1 (надёжность)** — **never-raise** на всех новых путях (per-source / per-tick / per-send), как
и весь watchdog: сбой коллектора процессов деградирует ОДИН сигнал, а не роняет тик.
- **NFR-2 (read-only)** — строго наблюдение: **ни одного** управляющего действия над процессами/хостом
(нет `kill`/`signal`/`Popen`/записи). Соответствует C-1 (observer separated from observed).
- **NFR-3 (self-hosting безопасность)** — выкат изменения **не перезапускает** прод-контейнер
`orchestrator` (встанет конвейер всех проектов): пересобирается/рестартится **только** контейнер
`orchestrator-watchdog`. Если механизм требует расширения привилегий sidecar (напр. `pid: host`) —
это привилегия **только наблюдателя**, обоснование и риски — задача архитектора (ADR).
- **NFR-4 (без дубля)** — новый сигнал **не пересекается** с `agent_hung` (тот уже покрывает
отслеживаемые агент-джобы): новый сигнал закрывает ровно пробел «нетреканый/осиротевший процесс».
- **NFR-5 (канон тиража)** — при изменении compose / ключей `.env.watchdog` обновить в **том же PR**:
`.env.watchdog.example`, блок `WATCHDOG_*` в `.env.example`, `docs/deployment/LITE_SETUP.md` и
`docs/architecture/README.md` (норматив сопровождения ORCH-102 NFR-5; key-set-sync тест).
- **NFR-6 (стек)** — sidecar остаётся **stdlib-only** (C-3, ORCH-100): без новых сторонних зависимостей.
## 6. Допущения и ограничения
- **Ключевое архитектурное допущение (для архитектора):** у контейнера `orchestrator-watchdog` сейчас
**нет** видимости процессов хоста (`network_mode: host`, но без `pid: host` и без mount `/proc`).
Закрытие пробела требует выбора механизма — **развилка, решаемая ADR**, не аналитиком. Кандидаты
(перечислены как материал для решения, **без навязывания**): (a) расширение привилегий sidecar —
`pid: host` либо read-only mount хостового `/proc`, затем stdlib-скан таблицы процессов; (b)
обогащение `/metrics` орком новым read-only разделом о «бесхозных» тест-субпроцессах (орк видит свой
PID-namespace), который sidecar лишь читает. У каждого — свои trade-off'ы (привилегии vs контракт
`/metrics`).
- `/metrics`**версионированный контракт** (`schema_version`, ORCH-099): если выбран путь (b),
аддитивные изменения **не бампят** версию (sidecar обязан толерировать).
- Порог возраста для детекции **должен превышать** максимальный легитимный бюджет тест-прогона
(`merge_retest_timeout_s` ≈ 600s, `coverage_run_timeout_s`), чтобы нормальный прогон **никогда** не
алертил, а 2-суточный осиротевший pytest — гарантированно (анти-false-positive, материал для ADR).
- enduro-trails не затронут: watchdog наблюдает хост/орк self-hosting; сигнал config-gated.
## 7. Критерии успеха
Watchdog при наличии долго живущего pytest/дочернего процесса, грузящего CPU, **поднимает отдельный
alert** в свой Telegram-канал (с PID/cmd/возрастом), **не трогая** процесс; при отсутствии такого
процесса (или выключенном флаге) — молчит; нормальный тест-прогон под активным джобом **не** триггерит
ложный alert. Детальные PASS/FAIL — `03-acceptance-criteria.md`.
## 8. Риски
- **Ложные срабатывания** на легитимном длинном прогоне → спам в канал (митигируется порогом >
макс. бюджета + корреляцией с активным джобом).
- **Расширение привилегий sidecar** (если выбран `pid: host`/`/proc`-mount) → увеличение поверхности
безопасности наблюдателя (требует явного обоснования в ADR; дефолт-off).
- **Дубль с `agent_hung`** при небрежной реализации (NFR-4).
- Детали и владельцы рисков — `10-tech-risks.md` (заполняет архитектор).