Files
orchestrator/docs/work-items/ORCH-116/10-tech-risks.md

8.9 KiB
Raw Blame History

work_item, stage, author_agent, status, created_at, model_used
work_item stage author_agent status created_at model_used
ORCH-116 architecture architect proposed 2026-06-16 claude-opus-4-8

10 — Технические риски: ORCH-116 — детерминированный test-раннер

Work Item: ORCH-116 · Repo: orchestrator · Стадия: architecture

Информационный (гейтом не парсится). Покрывает риски BRD §8 (R-1…R-7) + риски, выявленные архитектором по коду (TR-8…TR-11, специфичные для роли tester / стадии testing).

Реестр рисков

ID Риск Вер. Влия. Митигейшн
TR-1 (R-1) Точка диспетчеризации «до _spawn» перехватывает не тот джоб Низ. Выс. should_intercept = agent=="tester" И applies(repo) И tasks.stage=="testing" (D1). Роль tester исполняет ТОЛЬКО testing (STAGE_TRANSITIONS["review"]["agent"]) → коллизии стадий нет; гард по стадии — defense-in-depth. never-raise → False_spawn. Покрыто тестом перехвата/не-перехвата.
TR-2 (R-2) После вердикта гейт не инициирован → задача зависает на testing Низ. Выс. D7: раннер вызывает advance_stage(current_stage="testing", finished_agent="tester") в run_test_gate (never-raise). finished_agent="tester" обязателен — FAIL-ветвь stage_engine.py:849 матчит по agent=="tester". Покрыто тестом PASS-advance и FAIL-rollback.
TR-3 (R-3) Таймаут/изоляция pytest; утечка процессов (корень ORCH-109/110/111) Сред. Выс. D3: pytest через proc_group.run_in_process_group (tree-kill SIGTERM→grace→SIGKILL); таймаут test_runner_timeout_s=900 (малформ→дефолт+WARNING); прогон в worktree ветки, не в общем клоне. Покрыто тестом изоляции/таймаута.
TR-4 (R-4) Two-level outcome неверен: tool-error жжёт developer-retry (регресс ORCH-110) ИЛИ ложный green Сред. Выс. D5: сюита исполнилась → verdict→advance; сюита НЕ исполнилась (spawn-error/таймаут/None) → bounded DEFER (re-queue tester-джоба, restart-safe маркер) → на исчерпании fail-closed FAIL+advance+alert. Никогда тихий advance/ложный green; не жжёт developer-retry на инфре. Покрыто тестом обоих уровней.
TR-5 (R-5) Откат FAIL не совпадает с LLM-путём (developer-retry cap / extract_test_failures) Низ. Сред. D7 переиспользует существующий откат stage_engine.py:849-892 (тот же MAX_DEVELOPER_RETRIES, extract_test_failures, enqueue_job("developer", …)). Новой ветви маршрутизации нет. Покрыто тестом эквивалентности.
TR-6 (R-6) LLM протаскивается обратно в поток управления вердикта (нарушение BR-8) Низ. Сред. D11: детерминированный раннер — единственный продюсер result:; off-control-path триаж не реализуется и не добавляет ребро в STAGE_TRANSITIONS. Анти-дрейф LLM-карты (D12) + reviewer-ось AC-12.
TR-7 (R-7) Backward-compat: репо без тест-контракта зависает без продюсера отчёта Низ. Выс. D8: _has_test_contract(repo) (Phase 1 = self-hosting) — applies==False для не-self/без-контракта → should_intercept==False → прежний LLM-tester (fail-safe). Покрыто тестом для не-self репо.
TR-8 52c-status: ↔ парсер: ложный FAIL. _parse_tests_verdict читает вердикт из verdict:/status:/result: с negative-token-priority. 52c-status: failed ("FAILED") при result: PASS → негативный токен авторитетен → ложный FAIL здорового прогона. (Отсутствует в ORCH-115 — там гейт читает только staging_status:.) Сред. Выс. D6.1: status: ВСЕГДА выровнен по вердикту (success↔PASS / failed↔FAIL); "SUCCESS" — не негативный/не позитивный токен, позитив берётся из result: PASS. Обязательный unit-тест: _parse_tests_verdict(<тело раннера PASS>)==(True,…) и (FAIL)==(False,…) через неизменённый парсер. Reviewer: status:-литерал с негативным токеном при result: PASS → ≥P1.
TR-9 Smoke против прод-8500 флапает. Разовый блип запущенного оркестратора (connection refused/таймаут) → FAIL → откат здоровой ветки + расход developer-retry. Сред. Сред. D3: bounded smoke-ретрай транзиентной недостижимости (несколько быстрых GET с коротким backoff) перед FAIL; «достижимо, но форма неверна» → немедленный FAIL. test_runner_smoke_enabled позволяет отключить smoke без отката раннера. pytest — первичный сигнал; developer-retry-cap поглощает остаточный шум.
TR-10 DEFER re-queue'ит не тот агент. Копипаст из staging_runner мог бы re-queue'ить deployer-джоб → задача уйдёт в чужой обработчик. Низ. Выс. D5: DEFER re-queue'ит tester-джоб (enqueue_job("tester", …)), повторно входящий в этот раннер на стадии testing. Покрыто тестом DEFER (проверка роли re-queued джоба).
TR-11 Дрейф LLM-карты/политики/витрины при реализации (NFR-6): инвариант «ровно один first_slice=yes» нарушен / avoidable=yes снят с tester / анти-дрейф-тесты красные. Сред. Сред. D12: точная спека правок (A5 → реализовано, но avoidable=yes/axis=C/needs-hybrid-fallback СОХРАНЯЮТСЯ; rank 2 tester → реализован, first_slice НЕ переключать — остаётся у rank 1/deployer). Правки атомарно с кодом в development + зелёные test_llm_call_site_inventory.py/test_llm_determinization_docs.py. Reviewer-ось AC-14 ≥P1.
TR-12 Скоуп-дрейф: правка STAGE_TRANSITIONS/QG_CHECKS/check_tests_passed/_parse_tests_verdict/machine-verdict/схемы БД Низ. Выс. NFR-1: замена только продюсера. Анти-дрейф-тест на неизменность гейта/токенов/схемы (AC-6). Reviewer ловит как ≥P1.

Сводный вывод

Доминирующий класс — корректность интеграции детерминированного раннера в существующий гейт (finished_agent="tester", two-level outcome, эквивалентность отката) и две tester-специфичные мины, которых не было в ORCH-115: (TR-8) коллизия 52c-status: с _parse_tests_verdict и (TR-9) флап smoke против прод-8500. Обе закрыты архитектурно (D6.1 — жёсткое выравнивание status:

  • unit-тест через неизменённый парсер; D3 — bounded smoke-ретрай + config-gate). Остаточный риск для прод-конвейера (self-hosting) — низкий: leaf never-raise + kill-switch (мгновенный откат к LLM-tester байт-в-байт), без правки гейта/стадии/схемы БД, граница с ORCH-112/114/115 соблюдена, сквозной бюджет времени сохранён без правки reaper_max_running_s.

Эскалация arch:major-change не требуется (нет новой стадии/QG/смены БД; новый компонент-leaf — аддитивный, под kill-switch, по доказанному прецеденту ORCH-115). Возврат в анализ не требуется (ТЗ удовлетворяется без нарушения принципов архитектуры).