9.9 KiB
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 |
adr-0049: Детерминированный test-раннер — второй реализованный срез determinization-roadmap (tester-гибрид)
Сквозной (cross-cutting) ADR. Агрегирует решение ORCH-116, влияющее на весь оркестратор: вводит новый компонент-leaf
src/test_runner.py, снимает вторую avoidable LLM-консультацию из потока управления (tester/result:, A5) и переводит rank-2 determinization-roadmap из «план» в «реализовано». Локальная детализация (все решения D1–D12, включая tester-специфичную анти-коллизиюstatus:D6.1) —docs/work-items/ORCH-116/06-adr/ADR-001-deterministic-test-runner.md.
Статус
Proposed
Контекст
ORCH-118 (adr-0047) зафиксировал нормативную
политику и карту LLM-консультаций и назвал avoidable LLM control paths = {tester, deployer}.
Первый срез — deployer (staging-status, rank 1) — реализован ORCH-115
(adr-0048). Второй кандидат — tester (rank 2,
needs-hybrid-fallback, hybrid_needed = yes, first_slice = no). ORCH-116 — его фактическая
реализация.
Вердикт result: на стадии testing сейчас эмитит LLM-агент tester, но PASS/FAIL-ядро есть
чистый маппинг exit-кода pytest + read-only smoke, а гейт check_tests_passed
(_parse_tests_verdict) детерминирован и читает только frontmatter result: (+ legacy
verdict:/status:). Это удовлетворяет обоим условиям «avoidable»: C-консультация и
деривируемый вердикт. Гибрид-нюанс: прежний промпт нёс ещё и настоящее суждение (триаж падений,
маппинг TC↔критерии) — поэтому ORCH-116 выносит из потока управления только PASS/FAIL-исполнителя,
оставляя LLM допустимым лишь как будущий off-control-path триаж (Phase 2, не control-path).
Прецедент детерминированной замены агента (launch_job-перехват до _spawn, D1/D2 +
рабочий эталон src/staging_runner.py ORCH-115) и эталон «детерминированный джоб → advance_stage»
уже в проде — архитектурный риск замены снят.
Решение
Новый leaf src/test_runner.py + перехват в launch_job до _spawn (рядом с D1/D2/ORCH-115).
На testing для in-scope репо с резолвимым тест-контрактом джоб tester обрабатывает раннер:
исполняет регресс pytest <target> в worktree ветки через proc_group (tree-kill, ORCH-110) +
опциональный read-only smoke, маппит exit-код единым контрактом self_deploy.map_exit_code_to_status
(транслируя токены в PASS/FAIL), пишет 13-test-report.md (тот же machine-key result:),
best-effort пушит лог в фичеветку, вызывает существующий advance_stage(current_stage="testing", finished_agent="tester").
Кросс-каттинговые инварианты (сохранены байт-в-байт):
STAGE_TRANSITIONS(src/stages.py), реестр и именаQG_CHECKS/check_tests_passed/_parse_tests_verdict/прочихcheck_*/_parse_*, machine-verdict-ключи (result:/verdict:/status:/staging_status:/deploy_status:/security_status:/coverage_status:), схема БД — не тронуты. Это замена продюсера артефакта, не гейта/стадии.- Единственный транспорт LLM-консультации (
launcher._spawn/S0, llm-usage-policy.md §5) — соблюдён: раннер не зовёт LLM; второй транспорт не вводится; будущий off-control-path триаж — вне control-path (не контр-пример политике). - Сквозной бюджет времени ORCH-065/109/110 (
reaper_max_running_s(5400) > Σ(работ на ребре)) — соблюдён без правкиreaper_max_running_s: реброtestingотдельно отdeploy-staging, окно раннера ≤900s ≤ прежнего LLM-окнаagent_timeout_seconds(1800s). - Граница ORCH-112/ORCH-114/ORCH-115: transition-lease берётся внутри
advance_stage; раннер lease/гигиену/staging_runnerне модифицирует.
Скоуп — self-hosting only (test_runner_repos="" → is_self_hosting_repo + резолв
тест-контракта _has_test_contract, в Phase 1 = self-hosting), под kill-switch
test_runner_enabled (off → _spawn LLM-tester'а байт-в-байт). never-raise во всех публичных
функциях; двухуровневый исход (verdict при исполнившейся сюите; bounded defer → fail-closed на
tool-error/таймауте) убирает с testing-ребра RCA-класс ORCH-110 (инфра ≠ код-фейл).
Backward-compat (BR-9): репо без резолвимого тест-контракта → applies==False → прежний
LLM-tester (enduro-trails не затронут).
Tester-специфичная анти-коллизия (D6.1 локального ADR, отсутствует в ORCH-115):
_parse_tests_verdict читает вердикт из трёх полей (verdict:/status:/result:) с
negative-token-priority — поэтому обязательное 52c-поле status: раннера жёстко выровнено по
вердикту (success для PASS / failed для FAIL), иначе негативный токен в status: при result: PASS дал бы ложный FAIL. Зафиксировано unit-тестом через неизменённый парсер.
Эволюция карты LLM (норматив сопровождения, в том же PR — D12 локального ADR):
llm-call-sites.md (A5 → реализовано детерминированно, но avoidable=yes/axis=C/
needs-hybrid-fallback сохранены — LLM-ветвь как fallback / будущий off-control-path триаж),
llm-determinization-roadmap.md (rank 2 tester → реализован; инвариант «ровно один
first_slice = yes» цел — first_slice остаётся у rank 1/deployer, у tester — no),
llm-usage-policy.md (§5 — транспорт не нарушен), плюс анти-дрейф-тесты
(test_llm_call_site_inventory.py/test_llm_determinization_docs.py). Эти правки коуплены к коду →
применяются в development атомарно с реализацией, не в architecture-стадии (как ORCH-115).
Последствия
- + Минус ещё один avoidable LLM control path; второй доказанный раннер-паттерн (теперь и для
needs-hybrid-fallback-кандидата, не толькоreplace-deterministic-now). - + Дешевле/быстрее/детерминированнее собственный
testing; нет токенов/латентности LLM в точке ветвленияtesting → deploy-staging/testing → development. - + Паттерн остаётся переиспользуемым: leaf + перехват до
_spawn+advance_stage— шаблон для Phase 2 (project test contract не-self репо + опциональный off-control-path LLM-триаж). - + Гибрид-граница (D11 локального ADR): архитектура не закрывает будущий off-control-path триаж, не пуская LLM обратно в поток управления вердикта.
- − Новый компонент + врезка + defer-механика + tester-специфичная анти-коллизия
status:. Митигейшн: never-raise leaf, kill-switch (fail-safe к LLM), без схемы БД, инвариант выравниванияstatus:+ структурное покрытиеtests/test_orch116_test_runner.py. - Откат:
ORCH_TEST_RUNNER_ENABLED=false→ прежний LLM-путь наtestingбайт-в-байт.
Ссылки
- Локальный ADR:
docs/work-items/ORCH-116/06-adr/ADR-001-deterministic-test-runner.md - Первый срез: adr-0048 (ORCH-115,
src/staging_runner.py) - Политика/карта/roadmap: llm-usage-policy.md, llm-call-sites.md (A5), llm-determinization-roadmap.md (rank 2), adr-0047
- Прецеденты: D1/D2 (
launcher.py:397/402),_run_staging_runner_job(launcher.py:438),run_staging_gate(staging_runner.py),proc_group(ORCH-110, adr-0042), transition-lease (ORCH-114, adr-0045)