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

15 KiB
Raw Blame History

work_item, stage, author_agent, status, created_at, model_used, escalate
work_item stage author_agent status created_at model_used escalate
ORCH-111 analysis analyst ready-for-review 2026-06-15 claude-opus-4-8 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 (заполняет архитектор).