146 lines
15 KiB
Markdown
146 lines
15 KiB
Markdown
---
|
||
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` (заполняет архитектор).
|