Second realised slice of the determinization-roadmap (ORCH-118 A5, needs-hybrid-fallback): on the `testing` stage for the self-hosting `orchestrator` repo the LLM `tester` agent is replaced by a deterministic test-runner (src/test_runner.py), intercepted in launch_job BEFORE _spawn (deploy-finalizer / post-deploy-monitor / staging-runner precedent). It runs the regression `python -m pytest <target>` in the task worktree via proc_group (tree-kill) + an optional read-only smoke (/health, /status, /queue + serial_gate), maps the exit-code -> result: PASS|FAIL via the existing self_deploy.map_exit_code_to_status contract, writes 13-test-report.md and initiates the EXISTING check_tests_passed gate exactly as a finished LLM-tester. Invariant (NFR-1): only the *producer* changes — the artifact contract (13-test-report.md / result:), the gate check_tests_passed / _parse_tests_verdict, STAGE_TRANSITIONS and the DB schema are byte-for-byte UNCHANGED. Additive, under a kill-switch (test_runner_enabled), never-raise, fail-closed, self-hosting scope, two-level outcome (tool-error DEFER, anti ORCH-110), hybrid (LLM strictly off-control-path). 52c-`status:` is aligned with the verdict (D6.1) so the three-field _parse_tests_verdict never false-negatives a PASS. Docs (ORCH-118 NFR-6, atomic with code): llm-call-sites.md (A5 implemented), llm-determinization-roadmap.md (rank 2 implemented), llm-usage-policy.md, README/internals/overview, tester.md, CLAUDE.md, CHANGELOG.md. Coverage: tests/test_orch116_test_runner.py (TC-01..TC-14); LLM anti-drift tests green. Full suite: 2137 passed. Refs: ORCH-116 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
93 lines
8.5 KiB
Markdown
93 lines
8.5 KiB
Markdown
# LLM determinization roadmap (ORCH-118)
|
||
|
||
> **Что это.** Упорядоченный план детерминированных замен **avoidable LLM control paths**
|
||
> (`{tester, deployer}` — см. [`llm-call-sites.md`](llm-call-sites.md)). Это **транзиентный план**:
|
||
> он обновляется по мере закрытия follow-up'ов. ORCH-118 раннеры **не реализует** (FR-7); старт каждого
|
||
> кандидата гейтится утверждением карты.
|
||
>
|
||
> **Кандидаты названы ПО РОЛИ.** Конкретные follow-up Plane-ID **не фиксируются** ни в одном артефакте
|
||
> (R3 / NFR-6): этих work item нет в подтверждённом backlog; ID присваивается при заведении задачи.
|
||
> Анти-фабрикация прибита тестом `TC-11` (`tests/test_llm_determinization_docs.py`).
|
||
>
|
||
> **Оценки экономии — «оценка до фактического замера» (NFR-5).** Источник — существующие колонки
|
||
> `agent_runs` (`model`/`effort`/токены/стоимость/время); точные числа снимаются при заведении
|
||
> follow-up'а через `GET /metrics` / запрос к `agent_runs`. Здесь — порядок величины, а не контракт.
|
||
|
||
---
|
||
|
||
## 1. Рекомендованный первый срез — **deployer (staging-status)** — ✅ РЕАЛИЗОВАН (ORCH-115)
|
||
|
||
> **Статус: реализовано.** Срез выполнен в **ORCH-115** — `src/staging_runner.py` (перехват в
|
||
> `launch_job` до `_spawn`, как `D1`/`D2`): на стадии `deploy-staging` для self-hosting `orchestrator`
|
||
> вердикт `staging_status:` производит детерминированный код (маппинг exit-кода `staging_check.py`
|
||
> через `self_deploy.map_exit_code_to_status`), а не LLM. Под kill-switch `staging_runner_enabled` +
|
||
> скоуп `staging_runner_repos` (пусто → self-hosting only); LLM-ветвь остаётся fallback'ом.
|
||
> Контракт артефакта/гейта `check_staging_status`/`STAGE_TRANSITIONS`/схема БД — не тронуты. Детали —
|
||
> `docs/work-items/ORCH-115/06-adr/ADR-001-deterministic-staging-runner.md`, сквозной
|
||
> `docs/architecture/adr/adr-0048-deterministic-staging-runner.md`. Запись `rank 1` в машинном блоке
|
||
> §4 сохраняется (`first_slice = yes`, инвариант карты) как историческая фиксация первого среза.
|
||
|
||
Обоснование (самый низкорисковый «чисто деривируемый» control path):
|
||
|
||
1. **Деривируемость максимальна.** Вердикт `staging_status:` — **чистый маппинг** exit-кода
|
||
`scripts/staging_check.py`; готовый leaf `src/staging_verdict.py::compute_staging_verdict` (ORCH-061)
|
||
уже считает этот вердикт детерминированно.
|
||
2. **Прод уже детерминирован.** Ребро `deploy` (`deploy_status:`) для self-hosting `orchestrator`
|
||
производит детерминированный finalizer (Phase A/B/C, ORCH-036) — LLM в критическом
|
||
self-restart-пути нет. Срез не трогает критический путь → минимальная поверхность риска.
|
||
3. **Опирается на прецедент** `D1`/`D2` (`launch_job`-перехват **до** `_spawn`) — архитектурный риск
|
||
замены агента уже снят рабочим паттерном.
|
||
4. **`replace-deterministic-now`, без hybrid-fallback** (в отличие от tester).
|
||
|
||
## 2. Второй кандидат — **tester (гибрид)** — ✅ РЕАЛИЗОВАН (ORCH-116)
|
||
|
||
> **Статус: реализовано.** Срез выполнен в **ORCH-116** — `src/test_runner.py` (перехват в
|
||
> `launch_job` до `_spawn`, как `D1`/`D2`/ORCH-115): на стадии `testing` для self-hosting
|
||
> `orchestrator` вердикт `result:` производит детерминированный код (exit-код `pytest tests/` в
|
||
> worktree ветки + read-only smoke `/health`/`/status`/`/queue`+`serial_gate`, маппинг через
|
||
> `self_deploy.map_exit_code_to_status` в токенах `PASS`/`FAIL`), а не LLM. Под kill-switch
|
||
> `test_runner_enabled` + скоуп `test_runner_repos` (пусто → self-hosting only) + резолв тест-контракта
|
||
> (репо без контракта → LLM-tester, fail-safe). Контракт артефакта/гейта `check_tests_passed`/
|
||
> `STAGE_TRANSITIONS`/схема БД — не тронуты. Запись `rank 2` в машинном блоке §4 сохраняется
|
||
> (`first_slice = no`, `hybrid_needed = yes` — инвариант карты) как фиксация второго среза.
|
||
|
||
Детерминированное ядро (`pytest` + smoke даёт PASS/FAIL по exit-коду) покрывает основной вердикт;
|
||
LLM-фолбэк сохраняется **только** на суждение, не сводимое к exit-коду: **триаж падений** и **маппинг
|
||
TC ↔ критерии приёмки**. Поэтому `needs-hybrid-fallback`, а не `replace-deterministic-now`: поверхность
|
||
суждения шире и объём работы больше. В Phase 1 (ORCH-116) детерминированное ядро вынесено в раннер;
|
||
off-control-path LLM-триаж (он **не** выносит и **не** переопределяет `result:`, **не** добавляет ребро
|
||
в `STAGE_TRANSITIONS`) зафиксирован как Phase 2 follow-up по роли и в этом срезе не реализуется.
|
||
|
||
## 3. Общие требования к каждому follow-up'у
|
||
|
||
Каждый кандидат при заведении задачи несёт: kill-switch + обратимость (паттерн
|
||
ORCH-022/027/043/089/090 — флаг `*_enabled`, пустой CSV `*_repos` → self-hosting only), скоуп-гард
|
||
(не трогать `STAGE_TRANSITIONS`/`QG_CHECKS`/`check_*`/machine-verdict/схему БД), а замена-агента —
|
||
перехват в `launch_job` **до** `_spawn` (как `D1`/`D2`). Свежесть прецедента — инцидент-трек
|
||
ORCH-110/111/112/113/114/117 (единое детерминированное владение side-effectful путями).
|
||
|
||
---
|
||
|
||
## 4. Машинно-читаемый блок roadmap
|
||
|
||
> Заголовок (`rank | role | dependencies | savings_estimate_source | security_risk | hybrid_needed |
|
||
> followup_type | first_slice`) парсится `tests/test_llm_determinization_docs.py::test_tc07_*`.
|
||
> `followup_type` — **по роли**, без Plane-ID. Ровно один `first_slice = yes`.
|
||
|
||
<!-- ORCH-118-ROADMAP-BLOCK:START -->
|
||
| rank | role | dependencies | savings_estimate_source | security_risk | hybrid_needed | followup_type | first_slice |
|
||
|------|------|--------------|-------------------------|---------------|---------------|---------------|-------------|
|
||
| 1 | deployer | staging_verdict.compute_staging_verdict (ORCH-061) + launch_job pre-spawn precedent (D1/D2) | agent_runs (deployer rows; estimate pending GET /metrics) | low (prod already deterministic via Phase A/B/C ORCH-036) | no | deployer-replacement (staging-status mapping) | yes |
|
||
| 2 | tester | deterministic pytest+smoke core; LLM fallback for failure triage / TC-to-criteria mapping | agent_runs (tester rows; estimate pending GET /metrics) | medium (failure-triage judgment must stay correct) | yes | tester-hybrid (deterministic core + LLM fallback) | no |
|
||
<!-- ORCH-118-ROADMAP-BLOCK:END -->
|
||
|
||
---
|
||
|
||
## 5. Вне scope
|
||
|
||
- `reviewer` — C, но **keep** (вердикт «приемлемость кода/решения» не деривируем): **не** в roadmap'е.
|
||
- `analyst`/`architect`/`developer` — P (artifact-producer, не control path): **не** в roadmap'е.
|
||
- Реализация раннеров — отдельные follow-up задачи (по роли), стартуют после утверждения карты.
|
||
|
||
Связанные документы: [`llm-call-sites.md`](llm-call-sites.md), [`llm-usage-policy.md`](llm-usage-policy.md).
|