15 KiB
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.TimeoutExpiredPython убивает прямого ребёнка, но внуки/репарентированные процессы переживают; а если сам агент-процесс убит по таймауту (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(заполняет архитектор).