140 lines
11 KiB
Markdown
140 lines
11 KiB
Markdown
---
|
||
verdict: APPROVED
|
||
work_item: ORCH-116
|
||
stage: review
|
||
author_agent: reviewer
|
||
status: approved
|
||
created_at: 2026-06-16
|
||
model_used: claude-opus-4-8
|
||
type: review
|
||
work_item_id: ORCH-116
|
||
version: 4
|
||
---
|
||
|
||
# Review ORCH-116 — детерминированный test-раннер вместо LLM-тестера
|
||
|
||
> Машинный вердикт читается ТОЛЬКО из `verdict:` во frontmatter.
|
||
|
||
## Summary
|
||
|
||
Реализация **полностью соответствует ТЗ (02-trz.md), критериям приёмки (03-acceptance-criteria.md)
|
||
и ADR-001 / adr-0050**. ORCH-116 заменяет LLM-агента `tester` на стадии `testing` детерминированным
|
||
leaf `src/test_runner.py`, перехватываемым в `launch_job` до `_spawn` — точное зеркало эталона
|
||
`src/staging_runner.py` (ORCH-115). Критический инвариант NFR-1 соблюдён байт-в-байт: гейт
|
||
`check_tests_passed` / `_parse_tests_verdict`, `STAGE_TRANSITIONS`, `QG_CHECKS`, machine-key `result:`
|
||
и схема БД **не тронуты** (это замена *продюсера* артефакта, не гейта). Тонкая мина D6.1 (52c-`status:`
|
||
↔ парсер с negative-token-priority) обработана корректно и прибита тестом через неизменённый парсер.
|
||
Документация обновлена в полном объёме (CLAUDE.md / CHANGELOG / README / internals / витрина
|
||
`docs/overview/` / LLM-карта/roadmap/policy / tester.md / .env.example). Все 4 оси — зелёные. Findings
|
||
уровня P0/P1, относящихся к ORCH-116, нет.
|
||
|
||
## Оси проверки
|
||
|
||
### 1. Соответствие ТЗ / AC — PASS
|
||
- **FR-1 / AC-1** перехват до `_spawn`: `launcher.launch_job` врезка `if job.get("agent")=="tester"
|
||
and test_runner.should_intercept(job): return self._run_test_runner_job(job)` рядом с
|
||
D1/D2/ORCH-115; `_run_test_runner_job` — зеркало `_run_staging_runner_job`, ведёт `jobs` через
|
||
`mark_job`, возвращает `None` (нет `agent_runs`). ✔ (TC-05).
|
||
- **FR-2/3 / AC-3/AC-11** pytest в worktree через `proc_group` (tree-kill, `_resolve_timeout`
|
||
fail-safe), маппинг `map_exit_code_to_result` поверх единого `self_deploy.map_exit_code_to_status`
|
||
(без второго маппинга). ✔ (TC-02, TC-12).
|
||
- **FR-4 / AC-2** `write_test_report` — `result:` UPPERCASE + 52c-схема, `author_agent: test-runner` /
|
||
`model_used: n/a`, push только в фичеветку (без merge в `main`). ✔ (TC-03, TC-04).
|
||
- **FR-5 / AC-4** `_advance` вызывает `advance_stage(current_stage="testing", finished_agent="tester")`
|
||
— FAIL-ветвь `stage_engine.py:849` матчит по `agent=="tester"`. ✔ (TC-07).
|
||
- **FR-6 / AC-5** two-level outcome: `suite_ran=(returncode is not None) and (not timed_out)`;
|
||
tool-error → bounded DEFER (re-queue именно **`tester`**-джоба + restart-safe маркер), на исчерпании
|
||
→ fail-closed `FAIL` + advance + INFRA-alert. ✔ (TC-10).
|
||
- **FR-7 / AC-7/AC-8** `applies()` = kill-switch + скоуп + `_has_test_contract` (репо без контракта →
|
||
LLM-tester даже при ручном добавлении в CSV). ✔ (TC-01, TC-08).
|
||
- **FR-8 / AC-13** блок `test_runner` в `GET /queue` + счётчики + структурный лог, различающий
|
||
code-fail / tool-error. ✔ (TC-14).
|
||
- **FR-9 / AC-12** гибрид: LLM вне control-path вердикта. ✔.
|
||
|
||
### 2. Соответствие ADR / инварианты — PASS
|
||
- NFR-1 / AC-6: `git diff origin/main...HEAD` НЕ затрагивает `src/stages.py`, `src/qg/`,
|
||
`src/stage_engine.py`, `src/staging_runner.py`, `src/proc_group.py`, `src/transition_lease.py`,
|
||
`src/git_worktree.py` — проверено явно (пустой diff). ✔.
|
||
- **D6.1 (ключевая мина)**: `_parse_tests_verdict` (`src/qg/checks.py:263-277`) читает вердикт из трёх
|
||
полей с приоритетом негативного токена. Раннер выравнивает `status:` по вердикту (`PASS→success`
|
||
«SUCCESS» — не позитивный и не негативный токен → `True` берётся из `result: PASS`; `FAIL→failed`
|
||
«FAILED» — негативный → `False`). Проверено вручную по логике парсера и тестом TC-04
|
||
(`test_tc04_status_field_never_false_negatives_a_pass`) через **неизменённый** парсер. ✔.
|
||
- Граница O1: `staging_runner` (ORCH-115), `transition_lease` (ORCH-114), `checkout_hygiene`
|
||
(ORCH-112) не модифицированы — соответствует требованию. ✔.
|
||
- Трассировка: новые врезки корректно ссылаются на прецеденты D1/D2/ORCH-115; зафиксированные
|
||
инварианты конвейера не сломаны. ✔.
|
||
|
||
### 3. Качество кода — PASS
|
||
- never-raise соблюдён во всех публичных функциях (`applies`/`should_intercept`/`run_test_gate`/
|
||
`snapshot`/`write_test_report`/`run_smoke` — guarded; TC-01/TC-06/TC-11/TC-14 покрывают).
|
||
- Лениво импортируются тяжёлые модули — leaf-чистота сохранена (импорт на уровне модуля только
|
||
`config`/`logging`/`proc_group`).
|
||
- Тесты содержательные (14 TC + анти-дрейф TC-15), без живого Claude CLI/сети; покрывают happy/FAIL/
|
||
tool-error/never-raise/kill-switch/scope/smoke/observability.
|
||
- Это feature-трек (не `Bug`), регресс-тест-фиксатор (ORCH-019 BR-4) не требуется; покрытие тем не
|
||
менее исчерпывающее.
|
||
|
||
### 4. Документация — PASS (обязательная проверка)
|
||
`src/` изменён → документация обновлена в том же PR, проверено пофайлово:
|
||
- `CLAUDE.md` — новый раздел «Детерминированный test-раннер (ORCH-116)». ✔
|
||
- `CHANGELOG.md` — детальная запись `[Unreleased]`. ✔
|
||
- `docs/architecture/README.md` — компонент **Test-runner** + блок roadmap «второй срез реализован». ✔
|
||
- `docs/architecture/internals.md` — примечание о перехвате на `testing`. ✔
|
||
- `docs/architecture/llm-call-sites.md` (A5), `llm-determinization-roadmap.md` (rank 2),
|
||
`llm-usage-policy.md` (§5) — обновлены; **инвариант «ровно один `first_slice=yes`» сохранён**
|
||
(deployer=yes, tester=no), анти-дрейф `tests/test_llm_call_site_inventory.py` /
|
||
`test_llm_determinization_docs.py` — зелёные. ✔
|
||
- Витрина `docs/overview/` (ORCH-011): `tech-pipeline.md` / `tech-agents.md` /
|
||
`tech-quality-security.md` обновлены; `tests/test_system_docs.py` зелёный. ✔
|
||
- `.openclaw/agents/tester.md` — LLM-ветвь помечена fallback'ом (канон 52d цел). ✔
|
||
- `.env.example` — все 7 ключей `ORCH_TEST_RUNNER_*`. ✔
|
||
- ADR + сквозной `adr-0050` присутствуют. ✔
|
||
|
||
## Тесты (AC-15)
|
||
- `tests/test_orch116_test_runner.py` + LLM анти-дрейф — **45 passed**.
|
||
- Полный регресс `pytest tests/ -q` — **2162 passed, 1 failed** (84s). Единственный фейл —
|
||
`tests/test_orch123_staging_runner_exec.py::test_r2_held_deploy_staging_not_rolled_back` (см. P2).
|
||
|
||
## Findings
|
||
|
||
### P0 — Blocker
|
||
- (нет)
|
||
|
||
### P1 — Must fix
|
||
- (нет)
|
||
|
||
### P2 — Should fix
|
||
- **[Вне ORCH-116, для отдельного follow-up] Pre-existing test-isolation flake в ORCH-123.**
|
||
В полном прогоне падает `tests/test_orch123_staging_runner_exec.py::test_r2_held_deploy_staging_
|
||
not_rolled_back`. Доказано, что это **не** дефект ORCH-116: (1) тест зелёный в изоляции и в своём
|
||
файле; (2) фейл воспроизводится при прогоне полного сьюта **без** `tests/test_orch116_test_runner.py`
|
||
(`--ignore`) → 1 failed; (3) ORCH-116 не трогает `src/staging_runner.py` (граница O1, корректно).
|
||
Это загрязнение глобального состояния от соседнего теста в ORCH-123-коде (недавно влит в `main`).
|
||
AC-15 в части «ORCH-116 не краснит ранее зелёные тесты» удовлетворён. Рекомендация: завести
|
||
отдельный bug на изоляцию теста ORCH-123 — он вне области ORCH-116 и не должен блокировать этот PR.
|
||
|
||
### P3 — Nice to have
|
||
- `run_smoke` (`src/test_runner.py:291`) дублирует литерал `"http://localhost:8500"`, который уже
|
||
является дефолтом `settings.post_deploy_base_url`. Это безвредный defensive double-default
|
||
(`localhost`/`8500` НЕ в FORBIDDEN-списке `test_no_host_hardcodes` — нарушения нет); можно опереться
|
||
только на дефолт config. Косметика.
|
||
- Smoke-провал из-за транзиентного блипа прод-8500 после bounded-ретраев даёт `FAIL` → откат +
|
||
расход developer-retry (формально инфра, трактуемая как код-фейл — родственно классу ORCH-110).
|
||
Архитектор **осознанно** взвесил это в ADR (D3 + Consequences «−»): bounded smoke-ретрай +
|
||
config-gate `test_runner_smoke_enabled` + developer-retry-cap; поведение эквивалентно прежнему
|
||
LLM-tester (`curl /health`). Принято как задокументированный компромисс; не блокирует.
|
||
|
||
## Документация
|
||
Обновлена в полном объёме в том же PR (см. ось 4). Изменение `src/` сопровождено обновлением
|
||
паспорта, чейнджлога, архитектурной карты, internals, витрины `docs/overview/`, LLM-карты/roadmap/
|
||
политики, промпта `tester.md`, `.env.example` и ADR (локальный + сквозной). Машинно-проверяемые факты
|
||
витрины и LLM-карты держатся зелёными анти-дрейф-тестами. Требование «документация = golden source»
|
||
выполнено → блокирующих документационных findings нет.
|
||
|
||
## Вердикт
|
||
Нет P0/P1, относящихся к ORCH-116. Все четыре оси (ТЗ, ADR/инварианты, качество кода, документация)
|
||
пройдены; критические инварианты сохранены байт-в-байт; покрытие исчерпывающее. **APPROVED.**
|
||
Единственный красный тест полного сьюта — pre-existing flake ORCH-123 (P2), доказанно независимый от
|
||
ORCH-116 и вне его области правки.
|