work_item: ORCH-111 stage: analysis author_agent: analyst status: ready-for-review created_at: 2026-06-15 model_used: claude-opus-4-8 title: "Watchdog alert на долго живущие pytest/дочерние процессы, блокирующие конвейер" framework: pytest scope: > Покрывает новый сигнал watchdog `proc_blocking`: чистый builder, конфиг/kill-switch, never-raise/read-only коллектор, анти-спам/recovery, отсутствие дубля с agent_hung и диспетч в tick(). Вне покрытия: автоматический reap/kill процессов (ремедиация — вне объёма задачи) и любые изменения конвейера/Quality Gates. notes: > Эталон тестируемости — чистые функции `watchdog/signals.py` + `watchdog/decision.py` (тесты без контейнера/сокета/таймера, см. tests/watchdog/test_decision.py, test_host_collector.py). TC-01 и TC-07 — ОБЯЗАТЕЛЬНЫЙ регресс-тест бага: красный до фикса (сигнал/диспетч отсутствуют → нет алерта на долго живущий процесс), зелёный после. Точная форма теста КОЛЛЕКТОРА (host-scan vs orch-/metrics enrichment) зависит от механизма, утверждаемого архитектором (ADR); детерминированные TC ниже якорятся на чистом сигнале + decision-поверхности и не зависят от выбора механизма. Полный регресс `pytest tests/` обязан оставаться зелёным. tests: - id: TC-01 type: unit description: > РЕГРЕСС (красный→зелёный): builder сигнала по записи о процессе с возрастом > порога и cmdline класса pytest, не принадлежащему активному джобу, возвращает активный Signal с ключом `proc_blocking`. До фикса — алерт отсутствует. module: tests/watchdog/test_proc_blocking_signal.py expected: PASS - id: TC-02 type: unit description: > Анти-false-positive: процесс с возрастом НИЖЕ порога ИЛИ атрибутированный активному отслеживаемому джобу → сигнал НЕ активен (нет алерта). Покрывает BR-4. module: tests/watchdog/test_proc_blocking_signal.py expected: PASS - id: TC-03 type: unit description: > Конфиг/kill-switch: `WATCHDOG_PROC_*` парсятся с безопасными дефолтами; дефолтный порог возраста превышает merge_retest_timeout_s; выключенный `WATCHDOG_PROC_ENABLED` делает коллектор/сигнал инертными (нулевая регрессия). Покрывает BR-6/FR-3/AC-7. module: tests/watchdog/test_proc_blocking_signal.py expected: PASS - id: TC-04 type: unit description: > never-raise/read-only коллектора: битый/пустой/недоступный источник → `[]` (один сигнал пропущен), без исключения; на пути нет os.kill/signal/subprocess/мутаций. Покрывает NFR-1/NFR-2/AC-3/AC-8. module: tests/watchdog/test_proc_blocking_signal.py expected: PASS - id: TC-05 type: unit description: > Анти-спам/recovery через decision.decide+AlertState: ALERT при пересечении порога, NONE в пределах cooldown, REALERT по истечении, однократный RECOVERY при исчезновении процесса. Покрывает BR-5/AC-6. module: tests/watchdog/test_proc_blocking_signal.py expected: PASS - id: TC-06 type: unit description: > Без дубля с agent_hung: процесс, присутствующий в /metrics agents[], не порождает `proc_blocking` (исключён атрибуцией к активному джобу). Покрывает NFR-4/FR-6/AC-5. module: tests/watchdog/test_proc_blocking_signal.py expected: PASS - id: TC-07 type: integration description: > РЕГРЕСС tick→dispatch: Watchdog.tick() с инъектированным коллектором, отдающим долго живущий блокирующий процесс, диспетчеризует `proc_blocking`-алерт через fake-Notifier; при выключенном флаге алерт не отправляется. Покрывает BR-1/FR-5/AC-1/AC-7. module: tests/watchdog/test_tick_proc_blocking_integration.py expected: PASS - id: TC-08 type: integration description: > Конфиг-канон/тираж: key-set `.env.watchdog.example` ↔ блок WATCHDOG_* в `.env.example` синхронизирован после добавления `WATCHDOG_PROC_*` (key-sync/LITE_SETUP тест зелёный). Покрывает NFR-5/AC-10. module: tests/watchdog/test_config_killswitch.py expected: PASS - id: TC-09 type: integration description: > Полный регресс: `pytest tests/` зелёный; конвейер/Quality Gates не затронуты (STAGE_TRANSITIONS/QG_CHECKS/схема БД без изменений); при orch-side варианте schema_version /metrics не бампнут. Покрывает AC-9. module: tests/ expected: PASS