48 lines
8.9 KiB
Markdown
48 lines
8.9 KiB
Markdown
---
|
||
work_item: ORCH-116
|
||
stage: architecture
|
||
author_agent: architect
|
||
status: proposed
|
||
created_at: 2026-06-16
|
||
model_used: 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). Возврат в анализ **не требуется**
|
||
(ТЗ удовлетворяется без нарушения принципов архитектуры).
|