From 70171eb1c13d35278ffb87e1c4113153b434a163 Mon Sep 17 00:00:00 2001 From: claude-bot Date: Mon, 15 Jun 2026 23:42:44 +0300 Subject: [PATCH] analyst(ET): auto-commit from analyst run_id=726 --- docs/work-items/ORCH-118/01-brd.md | 226 +++++++++++++----- docs/work-items/ORCH-118/02-trz.md | 168 +++++++------ .../ORCH-118/03-acceptance-criteria.md | 118 ++++++--- docs/work-items/ORCH-118/04-test-plan.yaml | 58 +++-- 4 files changed, 393 insertions(+), 177 deletions(-) diff --git a/docs/work-items/ORCH-118/01-brd.md b/docs/work-items/ORCH-118/01-brd.md index 70bda99..1fcabad 100644 --- a/docs/work-items/ORCH-118/01-brd.md +++ b/docs/work-items/ORCH-118/01-brd.md @@ -31,15 +31,26 @@ Work Item: **ORCH-118** · Repo: **orchestrator** · Стадия: analysis > (deployer = кандидат на детерминированную замену, tester = кандидат на hybrid-fallback). > *(R1→R2 ранее «чинил» порядок этих ID; R3 убирает корень проблемы — фиксацию несуществующих ID.)* -> 🔁 **Revision R4 (2026-06-15) — единственный оставшийся блокер R3-ревью.** Рецензент подтвердил: -> follow-up'ы по роли / TBD — ОК, `00-business-request.md=TBD` (конвенция репо) — НЕ блокер, скоуп -> docs+tests — ОК; **менять их не нужно**. Закрывается **один** блокер: инвариант «места вызова LLM» -> **смешивал** факт *«существует процесс Claude CLI / спавнится subprocess»* (транспорт/механизм) с -> фактом *«на control-path происходит **LLM-консультация** — поток управления потребляет суждение -> LLM»* (семантическая единица, ради которой и существует задача «replace avoidable **LLM** control -> paths»). R4 **разводит** эти два понятия во всех артефактах (см. новый блок «Единица анализа» ниже, -> уточнённые BR-1/BR-3/BR-6, FR-1/FR-3/FR-6, AC-1/AC-3/AC-6, TC-01/TC-02 + новый TC-12). Содержательная -> классификация ролей и весь R3-материал (анти-фабрикация ID) — **без изменений**. +> 🔁 **Revision R4 (2026-06-15).** Закрыт блокер R3-ревью: инвариант «места вызова LLM» **смешивал** +> факт *«существует процесс Claude CLI / спавнится subprocess»* (транспорт/механизм) с фактом +> *«потребляется суждение LLM»* (LLM-консультация). R4 развёл **транспорт** (`_spawn`) и **слот/ +> capability** (D1/D2) от факта **консультации** во всех артефактах (см. §0). Содержательная +> классификация ролей и весь R3-материал (анти-фабрикация ID) — без изменений. + +> 🔁 **Revision R5 (2026-06-15) — единственный оставшийся блокер R4-ревью.** Рецензент подтвердил R4 +> (консультация ≠ транспорт/слот; no-alternative-LLM-transport; follow-up'ы по роли/TBD; нет +> runtime-дифа) — **менять их не нужно**. Закрывается **один** блокер: артефакты разводили +> «консультация ≠ транспорт/слот», но **не делали явной третью ось — самую важную для названия +> задачи** *«replace avoidable LLM **control paths**»*: среди фактических консультаций **не +> различались** (i) **control-path-консультации** — где LLM-вердикт **потребляется потоком управления** +> (на нём ветвится `check_*`-гейт), и (ii) **artifact-producer-консультации** — где LLM лишь +> **производит артефакт**, а ветвление делает **детерминированный гейт** (наличие файлов / CI), и +> суждение LLM в control flow **не входит**. И главное — нигде **явно не определена «avoidable LLM +> control path»**. R5 добавляет эту ось и определение во **все** артефакты (новый §0-bis, уточнённые +> BR-1/BR-2 + новые BR-8/BR-9, FR-1/FR-2 + новый FR-8, AC-1/AC-2 + новый AC-10, TC-13/TC-14). +> Содержательная классификация (analyst/architect/developer/reviewer → keep-LLM; deployer → +> replace-deterministic; tester → hybrid) **не меняется** — R5 даёт ей **доказательный control-path +> вывод** из кода (`check_*`/`_parse_*` в `src/qg/checks.py`). --- @@ -61,8 +72,8 @@ Work Item: **ORCH-118** · Repo: **orchestrator** · Стадия: analysis 3. **Capability ≠ consultation (способность ≠ факт консультации).** Спавн процесса делает site *LLM-capable*; происходит ли консультация фактически — **гейтится потоком управления**: - `D1/D2` (`deploy-finalizer`/`post-deploy-monitor`) — job-роли, **занимающие слот агента**, но - перехватываемые в `launch_job` **до** `_spawn` (`src/agents/launcher.py`, код прямо помечает - «Not an LLM spawn») → **консультации LLM нет**, хотя «агент» как job существует; + перехватываемые в `launch_job` **до** `_spawn` (`src/agents/launcher.py:389,394`, код прямо + помечает «Not an LLM spawn») → **консультации LLM нет**, хотя «агент» как job существует; - timeout-kill (`exit_code=-9`) / salvage-guard (`if exit_code==0`) → спавненный процесс может **не произвести** потреблённого суждения. Поэтому «процесс спавнится» **переоценивает** «суждение потреблено». @@ -77,6 +88,75 @@ HTTP-эндпоинт Anthropic/Claude, второй model-invoking subprocess- --- +## 0-bis. Третья ось: control-path-консультация ≠ artifact-producer-консультация; что такое «avoidable» (R5) + +§0 (R4) отделил **факт консультации** от **транспорта** и **слота**. Но название задачи — +«replace **avoidable** LLM **control paths**» — требует ещё одного, **решающего** различия **внутри +множества фактических консультаций** (A1…A6). Без него «control path» и «avoidable» остаются +неопределёнными, и карта не отвечает на главный вопрос задачи: *какие именно консультации — это +avoidable LLM control paths*. Это и есть оставшийся блокер R4-ревью. + +### Ось 3 — как именно LLM-вывод соотносится с потоком управления + +Каждая фактическая консультация (роль-агент) относится **ровно к одному** из двух типов: + +- **(C) Control-path-консультация.** LLM эмитит **machine-verdict**, который **потребляется потоком + управления конвейера**: соответствующий `check_*`-гейт **ветвится на этом вердикте** + (PASS→дальше / FAIL→откат). Суждение LLM **входит в control flow** — это и есть «**LLM control + path**» в точном смысле названия задачи. +- **(P) Artifact-producer-консультация.** LLM **производит артефакт** (документы/код/PR), а решение + о продвижении принимает **детерминированный гейт**, судящий артефакт **независимо** от + самоотчёта LLM (наличие файлов, статус CI). Суждение LLM **в control flow не входит** → это **не** + «LLM control path» (хотя консультация реальна и может требовать настоящего суждения). + +Различие доказывается кодом — **кто потребляет вывод роли** (`src/qg/checks.py`, ground-truth на +момент задачи): + +| Роль | Потребитель вывода (control-flow consumer) | Тип (C/P) | Control path? | +|------|--------------------------------------------|-----------|---------------| +| analyst | `check_analysis_complete` (checks.py:33) — **наличие файлов** 01–04 | **P** | нет | +| architect | `check_architecture_done` (checks.py:62) — **наличие** 06-adr/07 | **P** | нет | +| developer | `check_ci_green` (checks.py:82) + `check_branch_mergeable` (657) — **CI/merge** | **P** | нет | +| reviewer | `check_reviewer_verdict` (checks.py:336) читает **`verdict:`** → REQUEST_CHANGES-откат | **C** | **да** | +| tester | `check_tests_passed` (checks.py:182) → `_parse_tests_verdict` (226) читает **`result:`** | **C** | **да** | +| deployer | `check_staging_status` (599)→`_parse_staging_status` (538) **`staging_status:`**; `check_deploy_status` (473)→`_parse_deploy_status` (413) **`deploy_status:`** | **C** | **да** | + +> Для **P**-ролей гейт читает **не самоотчёт LLM**, а независимый детерминированный сигнал +> (файлы/CI) — поэтому подделать ветвление «самооценкой» нельзя; это структурно НЕ control path. +> Для **C**-ролей гейт читает **именно machine-verdict, который написал LLM** — суждение LLM и есть +> точка ветвления. + +### Определение «avoidable LLM control path» (нормативное, R5) + +Call-site — **avoidable LLM control path** ⟺ выполнены **оба** условия: + +1. **(control path)** это **C**-консультация — её LLM-вердикт потребляется потоком управления + (`check_*` ветвится на нём); **и** +2. **(deterministically derivable)** этот вердикт по сути есть **детерминированная функция от + tool-сигналов** (exit-code `pytest`/smoke, `staging_check.py`, exit-code деплоя), которые + оркестратор **уже вычисляет сам** → суждение LLM **не добавляет информации** → консультацию можно + снять без потери смысла. + +Отсюда — **точный целевой набор задачи** (доказательно, не «на глаз»): + +- **avoidable LLM control paths = {tester, deployer}** — C **и** вердикт деривируем из exit-кодов + (`_parse_tests_verdict` судит то, что есть исход `pytest`; `_parse_staging_status`/ + `_parse_deploy_status` судят то, что есть исход `staging_check.py`/деплоя; прод-деплой + self-hosting **уже** идёт детерминированным путём Phase A/B/C, ORCH-036). +- **control path, но НЕ avoidable = {reviewer}** — C, но вердикт **не** деривируем: «приемлем ли + код?» — настоящее суждение (keep-LLM). Это показывает, что «control path» **сам по себе** не равен + «avoidable» — отсекает условие 2. +- **НЕ control path (avoidable-вопрос неприменим) = {analyst, architect, developer}** — P: + детерминированный гейт судит артефакт, суждение LLM в control flow не входит; авторская работа + требует настоящего суждения (keep-LLM). Это отсекает условие 1. +- **уже детерминированы (вне консультаций) = {deploy-finalizer, post-deploy-monitor}** — §0.3. + +Эта ось **выводит** R3/R4-классификацию из кода, а не постулирует её: класс call-site'а есть +**функция** его (C/P)-типа и деривируемости вердикта (см. BR-2). «Avoidable» больше не «удобство на +глаз», а проверяемый двухбитный предикат над `src/qg/checks.py`. + +--- + ## 1. Бизнес-контекст и проблема Зонтичный follow-up по итогам RCA-цепочки **ORCH-114/117** (и предшествующих ORCH-110/111/112/113): @@ -93,32 +173,37 @@ HTTP-эндпоинт Anthropic/Claude, второй model-invoking subprocess- (подтверждено инвентаризацией; впредь — держится тестом FR-6). ⚠️ Это утверждение про **транспорт**, а не про «единственный subprocess»: в `src/**` десятки прочих `subprocess`-вызовов (`git`/`pytest`/ `docker`/`ssh`/сканеры) — они **не** консультируют LLM (см. §0). +- **Из 6 консультаций только 3 — это LLM control paths** (вердикт потребляется потоком управления, + §0-bis): **reviewer / tester / deployer**. Остальные 3 (**analyst / architect / developer**) — + artifact-producer'ы: их выход судит **детерминированный** гейт (наличие файлов / CI), суждение LLM + в control flow не входит. Среди 3 control path'ов **avoidable** — те, чей вердикт деривируем из + exit-кодов: **tester** и **deployer**; **reviewer** — control path с **настоящим** суждением + (keep). - **Все остальные control-path'ы уже детерминированы (без LLM):** маршрутизация стадий (`STAGE_TRANSITIONS`/`advance_stage`), все Quality Gate'ы и под-гейты (`check_*`, security/merge/ coverage/image-freshness), парсеры вердиктов (`_parse_*` через `frontmatter.py`), классификатор ретраев (`error_classifier.py`), serial-gate/transition-lease/reconciler/reaper, а также **две зарезервированные job-роли** `deploy-finalizer` и `post-deploy-monitor` (перехватываются в `launch_job` **до** `_spawn` — это рабочий прецедент детерминированной замены агента). -- Среди 6 LLM-ролей **tester** и **deployer** по факту почти полностью исполняют детерминированные - команды (`pytest`, `staging_check.py`, exit-code → вердикт; прод-деплой на self-hosting уже идёт - детерминированным путём Phase A/B/C, ORCH-036), завёрнутые в LLM «для удобства». -Боль/риск, который закрывает задача: LLM на механических путях — это (а) лишний источник -недетерминизма и инцидентов (ложный вердикт/действие), (б) задержка (запуск opus-агента вместо -прямого вызова), (в) расход токенов/денег. При этом «вслепую» убирать LLM нельзя — часть путей несёт -**настоящее суждение** (анализ, архитектура, написание кода, ревью), и автономность/гибкость должны -сохраниться. +Боль/риск, который закрывает задача: LLM на **механических control path'ах** — это (а) лишний +источник недетерминизма и инцидентов (ложный вердикт/действие в точке ветвления), (б) задержка +(запуск opus-агента вместо прямого вызова), (в) расход токенов/денег. При этом «вслепую» убирать LLM +нельзя — часть путей несёт **настоящее суждение** (анализ, архитектура, написание кода, **ревью**), и +автономность/гибкость должны сохраниться. Точный дискриминатор «убирать/оставить» — определение +«avoidable LLM control path» (§0-bis). -ORCH-118 даёт **доказательную карту** «где LLM действительно нужен, а где это удобство» и -**упорядоченный план** безопасных замен — фундамент, на котором последующие срезы (по ролям-кандидатам) -выполняются предсказуемо и без регресса. +ORCH-118 даёт **доказательную карту** «где LLM действительно нужен, а где это avoidable control path» +и **упорядоченный план** безопасных замен — фундамент, на котором последующие срезы (по +ролям-кандидатам) выполняются предсказуемо и без регресса. ## 2. Объём (scope) ### В объёме - **BR-1** Полная инвентаризация всех мест вызова LLM и всех ролей-агентов (карта call-site'ов). - **BR-2** Классификация каждого call-site в один из 4 классов (keep / replace-now / replace-later / - hybrid-fallback) с явным обоснованием. + hybrid-fallback) с явным обоснованием, **выведенным** из control-path-оси (§0-bis): класс есть + функция (C/P)-типа и деривируемости вердикта. - **BR-3** Доказательное подтверждение (с привязкой `file:line`), что не-агентские control-path'ы (маршрутизация / ретраи / QG / парсеры / finalizer'ы) уже детерминированы. - **BR-4** Упорядоченный roadmap замен: зависимости, оценка экономии токенов/времени, риски @@ -127,11 +212,15 @@ ORCH-118 даёт **доказательную карту** «где LLM дей - **BR-5** Нормативная **политика использования LLM** («LLM — только там, где нужно настоящее суждение») как durable-документ. - **BR-6** Структурные regression-тесты, **прибивающие инварианты карты к коду** (транспорт-агностичный - двусторонний инвариант: единственный транспорт LLM-консультации `_spawn` **и** отсутствие иного - LLM-транспорта; детерминированные модули не консультируют LLM; карта покрывает все промпты; тотальность - классификации) — анти-дрейф. + двусторонний инвариант + control-path-ось) — анти-дрейф. - **BR-7** Явно позиционировать **роль deployer** и **роль tester** как **кандидаты-follow-up** для детерминированной замены — **по роли, без привязки к конкретным Plane-ID** (см. NFR-6). +- **BR-8 (R5)** Карта **явно** размечает каждую консультацию по оси (C) control-path / + (P) artifact-producer с доказательством — **кто потребляет вывод роли** (`check_*`/`_parse_*` с + `file:line`). +- **BR-9 (R5)** Карта/политика **явно** определяют термин **«avoidable LLM control path»** (двухбитный + предикат §0-bis) и **поимённо** называют целевой набор `{tester, deployer}`, явно отделяя его от + control-path-но-keep (`reviewer`) и от не-control-path (`analyst`/`architect`/`developer`). ### Вне объёма - ❌ **Реализация** детерминированных раннеров deployer / tester и любых других замен — это отдельные @@ -160,14 +249,18 @@ ORCH-118 даёт **доказательную карту** «где LLM дей *LLM-консультация*, §0, а не «спавн процесса»): единственный транспорт `_spawn`, все 6 ролей-агентов и обе зарезервированные job-роли `D1/D2` (включаются как доказательство «слот агента есть, но консультации LLM нет» — перехват до `_spawn`). Для каждого — `file:line`, триггер, стадия/владелец, - выходной артефакт, machine-verdict-ключ (если есть), оценка токенов/времени, **признак - capability-vs-consultation** (LLM-capable vs фактически консультирует, §0.3). Проверяемо: каждый - `file:line` резолвится в реальный код. + выходной артефакт, machine-verdict-ключ (если есть), **потребитель вывода (`check_*`/`_parse_*` с + `file:line`)**, оценка токенов/времени, **признак capability-vs-consultation** (§0.3) и **признак + оси (C) control-path / (P) artifact-producer** (§0-bis, BR-8). Проверяемо: каждый `file:line` + резолвится в реальный код. - **BR-2 — Классификация.** Каждому call-site присвоить **ровно один** класс из таксономии: `keep-LLM` (нужно настоящее суждение), `replace-deterministic-now` (безопасная замена сейчас), `replace-later/risky` (замена позже / рискованно), `needs-hybrid-fallback` (детерминированное ядро + - LLM-фолбэк на суждение). Для `keep-LLM` — **назвать конкретное суждение**, ради которого LLM - сохраняется. + LLM-фолбэк на суждение). Класс **выводится** из control-path-оси (§0-bis): **P** → `keep-LLM` + (артефактная авторская работа); **C + не-деривируемый вердикт** → `keep-LLM` (reviewer); **C + + деривируемый вердикт** → `replace-*` / `needs-hybrid-fallback` (tester/deployer = avoidable). Для + `keep-LLM` — **назвать конкретное суждение**, ради которого LLM сохраняется (для **C**-keep — + почему вердикт **не** деривируем). - **BR-3 — Подтверждение детерминизма не-агентских путей.** Документально, с `file:line`-доказательством, зафиксировать, что маршрутизация стадий, ретраи, QG-проверки, парсеры вердиктов и finalizer'ы **не консультируют LLM** (не зависят от суждения LLM — ни через `_spawn`, ни через иной транспорт; их @@ -177,25 +270,30 @@ ORCH-118 даёт **доказательную карту** «где LLM дей - **BR-4 — Упорядоченный roadmap.** Ранжированный план замен: для каждого кандидата (названного **по роли**) — зависимости, **оценка** экономии токенов/времени (из телеметрии `agent_runs`), риск безопасности, нужен ли hybrid-fallback, ожидание kill-switch/обратимости. Явно указать - **рекомендованный первый срез** и обоснование выбора. Привязка к follow-up задаче — **по роли**; - конкретный Plane-ID НЕ фиксируется (заводится отдельно, NFR-6). + **рекомендованный первый срез** и обоснование выбора (опора на control-path-вывод: первым берётся + самый «чисто деривируемый» control path). Привязка к follow-up задаче — **по роли**; конкретный + Plane-ID НЕ фиксируется (заводится отдельно, NFR-6). - **BR-5 — Политика использования LLM.** Нормативный durable-документ: «LLM — только там, где требуется - настоящее суждение»; критерии решения keep vs replace; требование к новым/изменённым control-path'ам + настоящее суждение»; критерии решения keep vs replace, **сформулированные через ось §0-bis** + (является ли путь control path; деривируем ли вердикт); требование к новым/изменённым control-path'ам обосновывать любое использование LLM против этой политики. -- **BR-6 — Анти-дрейф структурными тестами.** Тесты, привязывающие инварианты карты к коду. Инвариант - единственной точки формулируется **транспорт-агностично и двусторонне** (R4, §0): (i) единственный - транспорт LLM-консультации в `src/**` — `_spawn`; **и** (ii) **отсутствует любой иной LLM-транспорт** - (импорт `anthropic`/`openai`/LLM-SDK, прямой HTTP-эндпоинт Anthropic/Claude, второй model-invoking - subprocess-сборщик `--system-prompt`/`--model`) — это и закрывает дыру «один `_spawn` зелёный, а - рядом проросла новая консультация другим транспортом». Дискриминатор теста — **«консультирует LLM», - а не «спавнит subprocess»** (прочие subprocess git/pytest/docker/ssh/сканеров явно исключены из - матчинга). Плюс: перечисленные детерминированные модули/job-роли не несут LLM-консультации; карта - перечисляет ровно те 6 промптов, что лежат в `.openclaw/agents/`; классификация покрывает все - call-site'ы по одному разу. Тесты — offline (без сети/LLM/subprocess-к-модели). **Тест на привязку - к конкретным follow-up ID не вводится** (анти-паттерн: прибивал бы карту к непроверяемым ID, R3). +- **BR-6 — Анти-дрейф структурными тестами.** Тесты, привязывающие инварианты карты к коду: + транспорт-агностичный двусторонний инвариант единственной точки (§0); перечисленные + детерминированные модули/job-роли не несут LLM-консультации; карта перечисляет ровно те 6 промптов, + что лежат в `.openclaw/agents/`; тотальность классификации; **плюс control-path-ось (R5):** карта + размечает каждую роль (C/P) согласованно с фактическим потребителем в `src/qg/checks.py`, а + avoidable-набор = `{tester, deployer}`. Тесты — offline. **Тест на привязку к конкретным follow-up + ID не вводится** (анти-паттерн R3). - **BR-7 — Позиционирование follow-up'ов по роли.** Карта/roadmap явно отмечают **роль deployer** и **роль tester** как кандидаты-замены, **не** реализуемые в ORCH-118; их старт гейтится утверждением карты. Привязка — **по роли**, без конкретных Plane-ID (NFR-6). +- **BR-8 (R5) — Явная control-path-разметка.** Карта несёт для каждой консультации поле оси + **(C) control-path / (P) artifact-producer** + доказательство (потребитель вывода с `file:line`). + Разметка **проверяема** структурным тестом против `src/qg/checks.py` (BR-6, TC-13). +- **BR-9 (R5) — Явное определение «avoidable LLM control path» и поимённый целевой набор.** Карта/ + политика дают нормативное определение термина (двухбитный предикат §0-bis) и **поимённо** называют + `{tester, deployer}` как avoidable, явно отделяя их от `reviewer` (control path, keep) и от + `analyst/architect/developer` (не control path). Набор **проверяем** тестом (BR-6, TC-14). ## 5. Нефункциональные требования (NFR) @@ -216,6 +314,10 @@ ORCH-118 даёт **доказательную карту** «где LLM дей ссылки, резолвящиеся в код/документы репозитория. Конкретные **follow-up Plane-ID не выдумываются**: кандидаты-замены именуются по роли; ID присваивается при заведении задачи. (Это закрывает корень отклонённой ревизии R2.) +- **NFR-7 (R5) — Контроль-ориентированность определений.** «LLM control path» и «avoidable» + определяются **через потребление вывода потоком управления** (кто из `check_*` ветвится на выводе), + а не через «есть ли вообще LLM-вызов на стадии». Любое утверждение «это avoidable LLM control path» + обязано резолвиться в конкретный `check_*`/`_parse_*` + tool-сигнал, из которого вердикт деривируем. ## 6. Допущения и ограничения - Единственный транспорт LLM сейчас — Claude CLI через `launcher._spawn`; прямых вызовов Anthropic API @@ -226,23 +328,33 @@ ORCH-118 даёт **доказательную карту** «где LLM дей `post-deploy-monitor` в `launch_job` до `_spawn`) — это снижает архитектурный риск follow-up'ов. - На момент анализа конкретные follow-up work item для замены ролей в backlog **не подтверждены** — поэтому ID не фиксируются (NFR-6). +- **(R5)** Control-path-разметка опирается на ground-truth `src/qg/checks.py` на момент задачи; при + эволюции кода её честность держит структурный тест (TC-13/TC-14). ## 7. Критерии успеха -Карта LLM-консультаций полна и привязана к коду, и **разводит транспорт/слот («процесс Claude CLI -существует») от факта консультации («поток управления потребляет суждение LLM»)** (R4, §0); каждый -site классифицирован с обоснованием; детерминизм не-агентских путей доказан (отсутствием LLM-консультации, -не отсутствием subprocess); есть упорядоченный roadmap с зависимостями/экономией/рисками и -рекомендованным первым срезом (кандидаты — по роли); есть нормативная политика; структурные тесты -зелёные и осмысленные — инвариант единственной точки **транспорт-агностичен и двусторонен** (единственный -транспорт `_spawn` **и** отсутствие иного LLM-транспорта); ни один рантайм-инвариант не тронут; раннеры -замен НЕ реализованы; ни один артефакт не фиксирует непроверяемых follow-up ID. Детальные PASS/FAIL — -в `03-acceptance-criteria.md`. +Карта LLM-консультаций полна и привязана к коду, и **разводит три ортогональных факта**: транспорт/слот +(«процесс Claude CLI существует», R4 §0), факт консультации, **и — control-path-ось (R5 §0-bis): +потребляется ли LLM-вывод потоком управления (`check_*` ветвится на нём) или его независимо судит +детерминированный гейт**. Термин **«avoidable LLM control path» явно определён** (двухбитный предикат) +и целевой набор **поимённо** назван `{tester, deployer}` с доказательным отделением от `reviewer` +(control path, keep) и от `analyst/architect/developer` (не control path). Каждый site классифицирован +с обоснованием, **выведенным** из этой оси; детерминизм не-агентских путей доказан (отсутствием +LLM-консультации, не отсутствием subprocess); есть упорядоченный roadmap с зависимостями/экономией/ +рисками и рекомендованным первым срезом (кандидаты — по роли); есть нормативная политика; структурные +тесты зелёные и осмысленные (инвариант единственной точки транспорт-агностичен и двусторонен; control-path +-разметка сверена с `src/qg/checks.py`; avoidable-набор зафиксирован); ни один рантайм-инвариант не +тронут; раннеры замен НЕ реализованы; ни один артефакт не фиксирует непроверяемых follow-up ID. +Детальные PASS/FAIL — в `03-acceptance-criteria.md`. ## 8. Риски - **Недо-/пере-классификация** (LLM убран там, где нужно суждение, или сохранён там, где не нужен) → - митигирует требование «назвать конкретное суждение» для `keep-LLM` и ревью карты. -- **Дрейф карты** относительно кода со временем → митигируют структурные тесты (BR-6). + митигирует **control-path-вывод** (§0-bis: класс выводится из (C/P)-типа и деривируемости вердикта, + а не «на глаз») + требование «назвать конкретное суждение» для `keep-LLM` + ревью карты. +- **Конфляция «есть LLM на стадии» с «это control path»** (рецидив корня R4-блокера) → митигирует + явная разметка оси и тест TC-13 (сверка с фактическим потребителем `check_*`). +- **Дрейф карты** относительно кода со временем → митигируют структурные тесты (BR-6), включая + control-path-инварианты (TC-13/TC-14). - **Преждевременная замена** в погоне за экономией ценой автономности/гибкости → инвентаризация - отделена от реализации; первый срез — самый низкорисковый. + отделена от реализации; первый срез — самый низкорисковый «чисто деривируемый» control path. - **Фабрикация ссылок/ID** (рецидив дефекта R2) → митигирует NFR-6 (только проверяемые ссылки; follow-up'ы — по роли) и ревью. Детали техн.рисков — `10-tech-risks.md` (архитектор). diff --git a/docs/work-items/ORCH-118/02-trz.md b/docs/work-items/ORCH-118/02-trz.md index 9c77f02..085e9bb 100644 --- a/docs/work-items/ORCH-118/02-trz.md +++ b/docs/work-items/ORCH-118/02-trz.md @@ -23,10 +23,19 @@ Work Item: **ORCH-118** · Repo: **orchestrator** · Стадия: analysis > `ORCH-115`/`ORCH-116`) в артефактах **не фиксируются** — этих work item нет в репо/подтверждённом > backlog; ID присваивается при заведении задачи. > -> 🔁 **R4 — единственный блокер R3-ревью.** Единица инвентаря/инварианта — **LLM-консультация** -> (control-path потребляет суждение LLM), **не** «спавн процесса / существование Claude CLI». Claude -> CLI-subprocess через `_spawn` — лишь **текущий транспорт**; «процесс существует» ≠ «LLM -> консультирован» (capability ≠ consultation). Развёрнуто — BRD §0; затрагивает FR-1/FR-3/FR-6. +> 🔁 **R4 — блокер R3-ревью (закрыт).** Единица инвентаря/инварианта — **LLM-консультация** (control-path +> потребляет суждение LLM), **не** «спавн процесса / существование Claude CLI». Claude CLI-subprocess +> через `_spawn` — лишь **текущий транспорт**; «процесс существует» ≠ «LLM консультирован» +> (capability ≠ consultation). Развёрнуто — BRD §0; затрагивает FR-1/FR-3/FR-6. +> +> 🔁 **R5 — единственный оставшийся блокер R4-ревью.** Артефакты разводили «консультация ≠ транспорт/ +> слот», но **не делали явной control-path-ось** — самую важную для названия задачи: среди реальных +> консультаций **не различались** (C) control-path (LLM-вердикт потребляется потоком управления — +> `check_*` ветвится на нём) и (P) artifact-producer (детерминированный гейт судит артефакт; суждение +> LLM в control flow не входит); и нигде **не был определён** термин **«avoidable LLM control path»**. +> R5 добавляет ось + определение (BRD §0-bis) и тянет их в FR-1/FR-2 + новый **FR-8**, в инвентарь +> §1 (новая колонка), в тесты FR-6 (**TC-13/TC-14**). Содержательная классификация не меняется — +> R5 **выводит** её из `src/qg/checks.py`. --- @@ -41,21 +50,29 @@ Work Item: **ORCH-118** · Repo: **orchestrator** · Стадия: analysis Опорный факт инвентаризации (ground-truth кода на момент задачи; line-привязки уточняет карта): > Единица — **LLM-консультация** (потребление суждения LLM), а не «спавн процесса» (R4, BRD §0). -> Колонка ниже помечает, является ли site фактической консультацией или лишь *LLM-capable* транспортом/ -> слотом. +> R5: дополнительно размечается **ось (C) control-path / (P) artifact-producer** и **потребитель +> вывода** (`check_*`/`_parse_*`), доказывающий ось. Колонка `Консультирует LLM?` помечает транспорт/ +> слот vs факт консультации; колонка `Control path?` помечает, входит ли LLM-вывод в поток управления. -| # | Call-site | Где | Что делает | Консультирует LLM? | -|---|-----------|-----|------------|--------------------| -| S0 | **Единственный транспорт LLM-консультации** | `src/agents/launcher.py::_spawn` (сборка `CLAUDE_BIN --print … --system-prompt "$(cat …)"` + `subprocess.Popen`) | реализует консультацию для любой из 6 ролей | транспорт (capability) | -| A1 | analyst | промпт `.openclaw/agents/analyst.md`, стадия `analysis` | анализ бизнес-запроса → 01–04 | да (через S0) | -| A2 | architect | `.openclaw/agents/architect.md`, стадия `architecture` | архитектурные решения → 06-adr | да (через S0) | -| A3 | developer | `.openclaw/agents/developer.md`, стадия `development` | реализация + PR | да (через S0) | -| A4 | reviewer | `.openclaw/agents/reviewer.md`, стадия `review` | ревью → `12-review.md` (`verdict:`) | да (через S0) | -| A5 | tester | `.openclaw/agents/tester.md`, стадия `testing` | `pytest`+smoke → `13-test-report.md` (`result:`) | да (через S0) | -| A6 | deployer | `.openclaw/agents/deployer.md`, стадии `deploy-staging`/`deploy` | `staging_check.py`/exit-code → `15`/`14` логи | да (через S0) | -| D1 | deploy-finalizer | `launch_job` перехват **до** `_spawn` (`launcher.launch_job`) | детерминированный (LLM не консультируется) | **нет** (слот агента, перехват до `_spawn`) | -| D2 | post-deploy-monitor | `launch_job` перехват **до** `_spawn` (`launcher.launch_job`) | детерминированный (LLM не консультируется) | **нет** (слот агента, перехват до `_spawn`) | +| # | Call-site | Где | Что делает | Консультирует LLM? | Потребитель вывода (control-flow consumer) | Control path? (C/P) | Avoidable LLM control path? | +|---|-----------|-----|------------|--------------------|--------------------------------------------|---------------------|-----------------------------| +| S0 | **Единственный транспорт LLM-консультации** | `src/agents/launcher.py::_spawn` (`CLAUDE_BIN --print … --system-prompt "$(cat …)"` + `Popen`) | реализует консультацию для любой из 6 ролей | транспорт (capability) | — | — | — (транспорт, не call-site решения) | +| A1 | analyst | `.openclaw/agents/analyst.md`, стадия `analysis` | анализ → 01–04 | да (через S0) | `check_analysis_complete` (`src/qg/checks.py:33`) — **наличие файлов** | **P** (artifact-producer) | **нет** (не control path) → keep-LLM | +| A2 | architect | `.openclaw/agents/architect.md`, стадия `architecture` | архитектура → 06-adr | да (через S0) | `check_architecture_done` (`checks.py:62`) — **наличие** 06-adr/07 | **P** | **нет** → keep-LLM | +| A3 | developer | `.openclaw/agents/developer.md`, стадия `development` | реализация + PR | да (через S0) | `check_ci_green` (`checks.py:82`) + `check_branch_mergeable` (`checks.py:657`) — **CI/merge** | **P** | **нет** → keep-LLM | +| A4 | reviewer | `.openclaw/agents/reviewer.md`, стадия `review` | ревью → `12-review.md` (`verdict:`) | да (через S0) | `check_reviewer_verdict` (`checks.py:336`) читает **`verdict:`** → REQUEST_CHANGES-откат | **C** (control path) | **нет** (вердикт НЕ деривируем — настоящее суждение) → keep-LLM | +| A5 | tester | `.openclaw/agents/tester.md`, стадия `testing` | `pytest`+smoke → `13-test-report.md` (`result:`) | да (через S0) | `check_tests_passed` (`checks.py:182`) → `_parse_tests_verdict` (`checks.py:226`) читает **`result:`** | **C** | **ДА** (вердикт = exit-code `pytest`/smoke) → needs-hybrid-fallback | +| A6 | deployer | `.openclaw/agents/deployer.md`, стадии `deploy-staging`/`deploy` | `staging_check.py`/exit-code → `15`/`14` логи | да (через S0) | `check_staging_status` (`checks.py:599`)→`_parse_staging_status` (`checks.py:538`) **`staging_status:`**; `check_deploy_status` (`checks.py:473`)→`_parse_deploy_status` (`checks.py:413`) **`deploy_status:`** | **C** | **ДА** (вердикт = `staging_check.py`/exit-code; прод уже детерминирован Phase A/B/C) → replace-deterministic | +| D1 | deploy-finalizer | `launch_job` перехват **до** `_spawn` (`launcher.py:389`) | детерминированный (LLM не консультируется) | **нет** (слот агента, перехват до `_spawn`) | — | — (нет консультации) | — (уже детерминирован) | +| D2 | post-deploy-monitor | `launch_job` перехват **до** `_spawn` (`launcher.py:394`) | детерминированный (LLM не консультируется) | **нет** (слот агента, перехват до `_spawn`) | — | — (нет консультации) | — (уже детерминирован) | +> **Чтение таблицы (R5).** Три ортогональных факта: (1) `Консультирует LLM?` — транспорт/слот vs факт +> консультации (R4 §0); (2) `Control path?` — входит ли вывод роли в **поток управления** (C: `check_*` +> ветвится на LLM-вердикте; P: детерминированный гейт судит артефакт независимо); (3) `Avoidable …?` — +> двухбитный предикат BRD §0-bis (C **И** вердикт деривируем из tool-сигналов). Итог: **avoidable LLM +> control paths = {tester, deployer}**; control-path-но-keep = `{reviewer}`; не-control-path = +> `{analyst, architect, developer}`. +> > Не-агентские control-path'ы (маршрутизация `advance_stage`, `QG_CHECKS`/`check_*`/`_parse_*`, > `error_classifier`, `serial_gate`/`merge_gate`/`coverage_gate`/`security_gate`/`staging_verdict`/ > `review_parse`/`frontmatter`, `self_deploy` Phase A/B/C) — **уже детерминированы** (FR-3). @@ -64,17 +81,17 @@ Work Item: **ORCH-118** · Repo: **orchestrator** · Стадия: analysis | Путь | Действие | |------|----------| -| `src/agents/launcher.py` | **читать** (инвентарь S0/D1/D2; `_spawn`, `launch_job`, `AGENT_CONFIGS`, `resolve_agent_model/effort`) — **не менять** | +| `src/agents/launcher.py` | **читать** (инвентарь S0/D1/D2; `_spawn`, `launch_job:389/394`, `AGENT_CONFIGS`, `resolve_agent_model/effort`) — **не менять** | | `.openclaw/agents/{analyst,architect,developer,reviewer,tester,deployer}.md` | **читать** (инвентарь 6 ролей) — **не менять** | +| `src/qg/checks.py` | **читать** (доказательство control-path-оси: кто потребляет вывод каждой роли — `check_analysis_complete:33`/`check_architecture_done:62`/`check_ci_green:82`/`check_reviewer_verdict:336`/`check_tests_passed:182`+`_parse_tests_verdict:226`/`check_staging_status:599`+`_parse_staging_status:538`/`check_deploy_status:473`+`_parse_deploy_status:413`; `QG_CHECKS`-реестр) — **не менять** | | `src/stages.py`, `src/stage_engine.py` | **читать** (доказать детерминизм маршрутизации) — **не менять** | -| `src/qg/checks.py` | **читать** (`QG_CHECKS`/`check_*`/`_parse_*` — детерминизм) — **не менять** | | `src/{serial_gate,merge_gate,coverage_gate,security_gate,staging_verdict,review_parse,error_classifier,frontmatter,self_deploy,post_deploy,transition_lease,reconciler,job_reaper}.py` | **читать** (детерминированные leaf'ы — доказательная база) — **не менять** | | `src/usage.py`, `src/db.py` (`agent_runs`) | **читать** (источник оценок экономии токенов/времени) — **не менять** | -| `docs/architecture/llm-call-sites.md` *(имя — пример; финально решает архитектор)* | **создать**: карта call-site'ов + классификация (FR-1/FR-2/FR-3) | +| `docs/architecture/llm-call-sites.md` *(имя — пример; финально решает архитектор)* | **создать**: карта call-site'ов + классификация + **control-path-разметка** (FR-1/FR-2/FR-3/FR-8) | | `docs/architecture/llm-determinization-roadmap.md` *(имя — пример)* | **создать**: упорядоченный roadmap (FR-4) | -| `docs/architecture/llm-usage-policy.md` *(имя — пример)* | **создать**: нормативная политика (FR-5) | -| `docs/work-items/ORCH-118/06-adr/ADR-001-*.md` | **создать** (архитектор): фиксация карты/таксономии/первого среза как ADR | -| `tests/test_llm_call_site_inventory.py` *(имя — пример)* | **создать**: структурные анти-дрейф тесты (FR-6) | +| `docs/architecture/llm-usage-policy.md` *(имя — пример)* | **создать**: нормативная политика + определение «avoidable LLM control path» (FR-5/FR-8) | +| `docs/work-items/ORCH-118/06-adr/ADR-001-*.md` | **создать** (архитектор): фиксация карты/таксономии/control-path-оси/первого среза как ADR | +| `tests/test_llm_call_site_inventory.py` *(имя — пример)* | **создать**: структурные анти-дрейф тесты (FR-6), включая control-path-инварианты | | `docs/architecture/README.md`, `docs/overview/*`, `CHANGELOG.md` | **обновить** (ссылка на карту/политику; норматив golden-source) | > Документы карты/политики целесообразно разместить в `docs/architecture/` (durable, сквозное), а не @@ -88,12 +105,12 @@ Work Item: **ORCH-118** · Repo: **orchestrator** · Стадия: analysis консультируют через S0), `D1/D2` (job-роли, **занимают слот агента, но НЕ консультируют LLM** — перехват в `launch_job` до `_spawn`; включены как доказательство паттерна). Поля записи: `id`, `location` (`file:line`), `trigger`, `stage/owner`, `output artifact`, `machine-verdict key` (если -есть), `est. tokens/runtime`, **`consults-LLM` (consultation vs LLM-capable transport/slot, §0.3)**, -`classification`, `rationale`, `dependency`, `risk`. Каждый `file:line` обязан резолвиться в реальный -код. Инвариант (транспорт-агностичный, двусторонний): единственный транспорт LLM-консультации в -`src/**` — `S0`; **иного LLM-транспорта нет** — подтверждается тестами FR-6(a)+(f). «Процесс Claude -CLI существует» ≠ «вторая точка запуска LLM не появилась»: дыру закрывает именно (f), а не подсчёт -`Popen`. +есть), **`output consumer` (`check_*`/`_parse_*` с `file:line` — кто потребляет вывод роли)**, +`est. tokens/runtime`, **`consults-LLM`** (consultation vs LLM-capable transport/slot, §0.3), +**`axis` (C control-path / P artifact-producer, §0-bis)**, `classification`, `rationale`, `dependency`, +`risk`. Каждый `file:line` обязан резолвиться в реальный код. Инвариант (транспорт-агностичный, +двусторонний): единственный транспорт LLM-консультации в `src/**` — `S0`; **иного LLM-транспорта нет** — +тесты FR-6(a)+(f). ### FR-2 — Таксономия классификации (BR-2) Ровно 4 взаимоисключающих класса с определениями: @@ -102,17 +119,20 @@ CLI существует» ≠ «вторая точка запуска LLM не - `replace-later/risky` — замена возможна, но позже / с риском (нужны предпосылки). - `needs-hybrid-fallback` — детерминированное ядро + LLM-фолбэк только на суждение. -Каждому call-site присвоен **ровно один** класс. Ожидаемое (из инвентаризации; финальное решение -фиксирует архитектор в ADR): `analyst/architect/developer/reviewer → keep-LLM`; -`deployer → replace-deterministic-now` или `replace-later/risky` (staging = exit-code-маппинг; прод -self-hosting уже детерминирован Phase A/B/C) — **кандидат-замена по роли deployer**; -`tester → needs-hybrid-fallback` (детерминированный прогон `pytest`+smoke, LLM-суждение только на -маппинг TC↔критерии / триаж падений) — **кандидат-замена по роли tester**; -`deploy-finalizer/post-deploy-monitor → already-deterministic` (вне таксономии замен, как эталон). +Каждому call-site присвоен **ровно один** класс, и класс **выводится из control-path-оси** (§0-bis, +FR-8), а не постулируется: +- **P (artifact-producer)** → `keep-LLM` — детерминированный гейт судит артефакт; авторская работа + требует суждения: `analyst`, `architect`, `developer`. +- **C + НЕ-деривируемый вердикт** → `keep-LLM` — control path, но суждение настоящее: `reviewer` + (назвать суждение: «приемлемость кода/решения», не сводится к exit-коду). +- **C + деривируемый вердикт** → avoidable → `replace-*` / `needs-hybrid-fallback`: + `deployer → replace-deterministic-now`/`replace-later/risky` (staging = exit-code-маппинг; прод + self-hosting уже детерминирован Phase A/B/C), `tester → needs-hybrid-fallback` (детерминированный + прогон `pytest`+smoke даёт PASS/FAIL; LLM-суждение только на триаж падений / маппинг TC↔критерии). +- `deploy-finalizer/post-deploy-monitor → already-deterministic` (вне таксономии замен, эталон). > 📌 **Follow-up'ы — по роли, без Plane-ID (R3, NFR-6).** Кандидаты обозначаются ролью -> (deployer-замена, tester-гибрid), а не конкретными ID. Привязка к будущему work item делается при -> заведении задачи; ORCH-118 ID не выдумывает. +> (deployer-замена, tester-гибрид), а не конкретными ID. ### FR-3 — Подтверждение детерминизма не-агентских путей (BR-3) Карта отдельным разделом фиксирует, с `file:line`-доказательством, что НЕ консультируют LLM (не зависят @@ -130,51 +150,64 @@ image-freshness), `self_deploy` Phase A/B/C, reconciler/reaper/serial-gate/trans **оценка** экономии токенов/времени (из `agent_runs`), риск безопасности, нужен ли hybrid-fallback, ожидание kill-switch/обратимости, и **тип будущей follow-up задачи по роли** (без конкретного Plane-ID — заводится отдельно, NFR-6). Явно: **рекомендованный первый срез** + обоснование (самый -низкорисковый, опирающийся на существующий прецедент D1/D2). +низкорисковый, «чисто деривируемый» control path, опирающийся на существующий прецедент D1/D2). ### FR-5 — Политика использования LLM (BR-5) Нормативный durable-документ: принцип «LLM — только где нужно настоящее суждение»; критерии решения -keep vs replace (детерминируемость выхода, наличие machine-verdict, обратимость, влияние на -автономность); требование к новым/изменённым control-path'ам обосновывать любое использование LLM -против политики. Может включать рекомендацию reviewer-оси (как ORCH-079) — **как требование, не как -реализацию гейта** (новый QG не вводится, FR-6 §QG). +keep vs replace, **сформулированные через ось §0-bis** (является ли путь control path — ветвится ли +`check_*` на LLM-выводе; деривируем ли вердикт из tool-сигналов; обратимость; влияние на автономность); +требование к новым/изменённым control-path'ам обосновывать любое использование LLM против политики. +Может включать рекомендацию reviewer-оси (как ORCH-079) — **как требование, не как реализацию гейта** +(новый QG не вводится, FR-6 §QG). ### FR-6 — Структурные анти-дрейф тесты (BR-6) Новый offline-тест-файл (без сети/LLM/subprocess-к-модели), проверяющий инварианты карты. ⚠️ **R4 — инвариант формулируется вокруг LLM-консультации/транспорта, а не «существования процесса Claude CLI».** Дискриминатор тестов — **«консультирует LLM», а не «спавнит subprocess»**; десятки прочих subprocess -(`git`/`pytest`/`docker`/`ssh`/сканеры/`staging_check.py`) явно исключаются из матчинга, иначе тест -выродился бы в «подсчёт всех `Popen`». +(`git`/`pytest`/`docker`/`ssh`/сканеры/`staging_check.py`) явно исключаются из матчинга. - **(a) Единственный транспорт.** В `src/**` ровно **одна** точка сборки/запуска Claude CLI (матчинг по совокупности признаков LLM-транспорта: `CLAUDE_BIN` + `--system-prompt` + `Popen`/`bash -c`), и - это `launcher._spawn`. *(Необходимое, но не достаточное условие — дополняется (f).)* -- **(f) Отсутствие иного LLM-транспорта (новое, R4).** В `src/**` **нет** альтернативного транспорта + это `launcher._spawn`. *(Необходимое, но не достаточное — дополняется (f).)* +- **(f) Отсутствие иного LLM-транспорта (R4).** В `src/**` **нет** альтернативного транспорта LLM-консультации: ни импорта `anthropic`/`openai`/иного LLM-SDK, ни прямого HTTP-эндпоинта Anthropic/Claude (`api.anthropic.com`, `/v1/messages` и т.п.), ни второго model-invoking - subprocess-сборщика (другой бинарь с `--system-prompt`/`--model`). Это закрывает дыру «один `_spawn` - зелёный, а рядом проросла новая консультация другим транспортом» — то, чего тест (a) в одиночку **не** - ловит. Allowlist единственного разрешённого транспорта = `S0`. + subprocess-сборщика. Allowlist единственного разрешённого транспорта = `S0`. - **(b) Нет консультации в детерминированных путях.** Перечисленные детерминированные модули и - обработчики `D1/D2` **не** консультируют LLM (не содержат ни `_spawn`-транспорта, ни альтернативного - по (f)); их subprocess-вызовы инструментов LLM-консультацией не считаются. -- **(c)** Карта перечисляет ровно те промпт-файлы, что физически лежат в `.openclaw/agents/` - (двусторонняя сверка — нет дрейфа). -- **(d)** Классификация покрывает каждый перечисленный call-site **ровно один раз** (тотальность, - без дублей/пропусков). -- **(e) Capability ≠ consultation.** `D1/D2` действительно перехватываются в `launch_job` **до** - `_spawn` → занимают слот агента, но **консультации LLM не происходит** (эталон «процесс/слот есть — - суждение не потребляется», §0.3). + обработчики `D1/D2` **не** консультируют LLM (ни `_spawn`-транспорта, ни альтернативного по (f)). +- **(c)** Карта перечисляет ровно те промпт-файлы, что физически лежат в `.openclaw/agents/`. +- **(d)** Классификация покрывает каждый перечисленный call-site **ровно один раз** (тотальность). +- **(e) Capability ≠ consultation.** `D1/D2` перехватываются в `launch_job` **до** `_spawn` + (`launcher.py:389/394`) → занимают слот, но консультации нет. +- **(g) Control-path-разметка верна (R5, TC-13).** Для каждой роли поле `axis` (C/P) карты **согласовано + с фактическим потребителем** в `src/qg/checks.py`: P-роли (`analyst`/`architect`/`developer`) + потребляются детерминированными гейтами (`check_analysis_complete`/`check_architecture_done`/ + `check_ci_green`), C-роли (`reviewer`/`tester`/`deployer`) — verdict-парсерами + (`check_reviewer_verdict`/`_parse_tests_verdict`/`_parse_staging_status`/`_parse_deploy_status`). + Дрейф (роль переразмечена или потребитель в коде сменил природу) → красный. +- **(h) Avoidable-набор зафиксирован (R5, TC-14).** Множество «avoidable LLM control path» в карте = + ровно `{tester, deployer}`; `reviewer` помечен control-path-но-keep; `analyst`/`architect`/ + `developer` — не control path. Любое добавление/удаление без обновления карты → красный. > ❌ **Не вводить** тест, прибивающий карту к конкретным follow-up Plane-ID → ✅ тесты проверяют -> только инварианты, резолвящиеся в код/файлы репозитория (R3, NFR-6). Привязка к несуществующим ID -> была корнем отклонённой R2. +> только инварианты, резолвящиеся в код/файлы репозитория (R3, NFR-6). ### FR-7 — Скоуп-гард (BR-7) Раннеры замен **не реализуются** в ORCH-118. Карта/roadmap явно помечают кандидатов **по роли** -(deployer-замена, tester-гибрid) как follow-up, старт которых гейтится утверждением карты. Тест/диф не +(deployer-замена, tester-гибрид) как follow-up, старт которых гейтится утверждением карты. Тест/диф не должны содержать новых детерминированных раннеров tester/deployer. **Конкретные follow-up Plane-ID не фиксируются** ни в одном артефакте (NFR-6). +### FR-8 (R5) — Явная control-path-ось и определение «avoidable» (BR-8/BR-9) +1. **Разметка оси.** Карта несёт для каждой консультации поле `axis` ∈ {C, P} (§0-bis) + доказательство + (поле `output consumer` — `check_*`/`_parse_*` с `file:line`). Разметка проверяема (FR-6g/TC-13). +2. **Определение термина.** Карта/политика дают **нормативное определение** «avoidable LLM control + path» — двухбитный предикат: (i) C-консультация (LLM-вердикт потребляется потоком управления) **И** + (ii) вердикт деривируем из tool-сигналов (exit-code `pytest`/smoke/`staging_check.py`/деплоя). +3. **Поимённый целевой набор.** Карта/roadmap **явно** называют `{tester, deployer}` как avoidable LLM + control paths, явно отделяя их от `reviewer` (C, но keep — суждение не деривируемо) и от + `analyst/architect/developer` (P — не control path). Набор проверяем (FR-6h/TC-14). +4. **Связь с классификацией.** Класс из FR-2 **выводится** из (C/P)-типа и деривируемости (а не наоборот). + ## 4. Изменения API Нет. (Опциональная read-only наблюдаемость в `GET /queue`/`GET /metrics` — **вне скоупа** ORCH-118; если архитектор сочтёт полезным — отдельная аддитивная врезка, но не требуется этой задачей.) @@ -185,16 +218,17 @@ keep vs replace (детерминируемость выхода, наличие ## 6. Требования к новым/изменённым QG checks Нет. `QG_CHECKS` / `check_*` / `_parse_*` / machine-verdict-ключи — **байт-в-байт**. Структурные тесты -FR-6 — обычные `pytest`-тесты, **не** Quality Gate и **не** стадия. Политика LLM (FR-5) — нормативный -документ, а не машинный гейт. +FR-6 (включая control-path TC-13/TC-14) — обычные `pytest`-тесты, **не** Quality Gate и **не** стадия. +Политика LLM (FR-5) — нормативный документ, а не машинный гейт. ⚠️ Control-path-ось — **аналитическая +разметка карты**, читающая `check_*` как ground-truth; она ничего в `src/qg/checks.py` не меняет. ## 7. Совместимость / регресс - **Docs + tests only:** рантайм `src/**` не меняется → нулевая регрессия; enduro-trails не затронут; kill-switch не нужен (нет рантайм-поведения), как в ORCH-077/079/101/102/103/011. - **Обратимость:** артефакты — документы и тесты; откат = удаление/правка docs (рантайм-риска нет). -- **Анти-дрейф:** структурные тесты держат карту синхронной с кодом; норматив сопровождения — «менял - места вызова LLM → обнови карту и политику в том же PR» (фиксируется в политике и golden-source - docs). +- **Анти-дрейф:** структурные тесты держат карту синхронной с кодом, включая control-path-ось + (TC-13/TC-14); норматив сопровождения — «менял места вызова LLM или потребителя вердикта в + `src/qg/checks.py` → обнови карту/разметку и политику в том же PR». - **Анти-фабрикация (R3):** артефакты фиксируют только проверяемые ссылки; follow-up'ы — по роли, без выдуманных Plane-ID (NFR-6). - **Self-hosting:** не деплоит/не рестартит прод/не трогает `main` — безопасно для общего инстанса. diff --git a/docs/work-items/ORCH-118/03-acceptance-criteria.md b/docs/work-items/ORCH-118/03-acceptance-criteria.md index a837dab..26e1a3a 100644 --- a/docs/work-items/ORCH-118/03-acceptance-criteria.md +++ b/docs/work-items/ORCH-118/03-acceptance-criteria.md @@ -16,36 +16,49 @@ Work Item: **ORCH-118** · Repo: **orchestrator** · Стадия: analysis **inventory-first**, docs+tests only; реализация раннеров приёмкой **запрещена** в этой задаче (AC-7); фиксация конкретных follow-up Plane-ID **запрещена** (AC-9, R3). +> 🔁 **R5.** Добавлены/уточнены критерии под **control-path-ось** (BRD §0-bis): среди реальных +> консультаций различаются (C) control-path (LLM-вердикт потребляется потоком управления) и +> (P) artifact-producer (детерминированный гейт судит артефакт); термин **«avoidable LLM control path»** +> явно определён, целевой набор `{tester, deployer}` поимённо назван. Затронуты **AC-1**, **AC-2** и +> новый **AC-10**; добавлены тесты **TC-13/TC-14**. + --- -## AC-1 — Полнота и привязка инвентаря LLM-консультаций +## AC-1 — Полнота и привязка инвентаря LLM-консультаций (+ control-path-разметка) **Условие:** Документ-карта перечисляет каждый call-site, где control-path потребляет (или способен потребить) суждение LLM — **единица = LLM-консультация, не «спавн процесса»** (R4) — с обязательными -полями, привязанными к коду. +полями, привязанными к коду, **включая ось (C/P) и потребителя вывода** (R5). - **PASS:** Карта содержит `S0` (`launcher._spawn` — **единственный транспорт LLM-консультации**), все 6 ролей (analyst/architect/developer/reviewer/tester/deployer, консультируют через S0) и обе job-роли (deploy-finalizer/post-deploy-monitor, помеченные **«занимают слот агента, но LLM не консультируют»** — перехват до `_spawn`); у каждой записи заполнены `location (file:line)` / `trigger` / `stage/owner` / - `output` / `machine-verdict key (если есть)` / `est. tokens-runtime` / **`consults-LLM` - (consultation vs LLM-capable transport/slot)** / `classification` / `rationale`; каждый `file:line` - резолвится в реальный код. Карта явно разводит «транспорт/слот существует» и «LLM фактически - консультируется» (§0 BRD). -- **FAIL:** Пропущен любой call-site; отсутствует любое обязательное поле (включая `consults-LLM`); - `file:line` не резолвится; карта смешивает «процесс Claude CLI существует» с «LLM-консультация - происходит» (напр. помечает `D1/D2` консультирующими LLM, или называет `_spawn` «точкой запуска» без - оговорки транспорт-vs-консультация); заявлен второй транспорт LLM, не подтверждённый кодом. + `output` / `machine-verdict key (если есть)` / **`output consumer` (`check_*`/`_parse_*` с `file:line`)** / + `est. tokens-runtime` / **`consults-LLM`** / **`axis` (C control-path / P artifact-producer)** / + `classification` / `rationale`; каждый `file:line` резолвится в реальный код. Карта явно разводит + «транспорт/слот существует» и «LLM фактически консультируется» (§0) **и** «consultation входит в поток + управления (C)» vs «детерминированный гейт судит артефакт (P)» (§0-bis). +- **FAIL:** Пропущен любой call-site; отсутствует любое обязательное поле (включая `output consumer` + или `axis`); `file:line` не резолвится; карта смешивает «процесс Claude CLI существует» с + «LLM-консультация происходит» (напр. помечает `D1/D2` консультирующими LLM); **или не размечает + ось C/P** (напр. называет analyst «control path», или не доказывает потребителем `check_*`); заявлен + второй транспорт LLM, не подтверждённый кодом. --- -## AC-2 — Классификация по таксономии (4 класса, тотально и однозначно) +## AC-2 — Классификация по таксономии (4 класса, тотально и однозначно, выведена из control-path-оси) -**Условие:** Каждый перечисленный call-site отнесён ровно к одному классу с обоснованием. +**Условие:** Каждый перечисленный call-site отнесён ровно к одному классу с обоснованием, +**выведенным** из оси §0-bis. - **PASS:** Таксономия определена явно (`keep-LLM` / `replace-deterministic-now` / - `replace-later/risky` / `needs-hybrid-fallback`); каждому site присвоен **ровно один** класс; у - `keep-LLM`-записей назван **конкретный** вид суждения, ради которого LLM сохраняется. + `replace-later/risky` / `needs-hybrid-fallback`); каждому site присвоен **ровно один** класс; класс + согласован с осью: **P → keep-LLM** (analyst/architect/developer), **C + не-деривируемый вердикт → + keep-LLM** (reviewer, с названным конкретным суждением, не сводимым к exit-коду), **C + деривируемый + вердикт → replace-\*/hybrid** (tester/deployer); у `keep-LLM`-записей назван **конкретный** вид + суждения, ради которого LLM сохраняется. - **FAIL:** Site не классифицирован / классифицирован дважды; класс вне таксономии; `keep-LLM` без - названного суждения. + названного суждения; **класс противоречит оси** (напр. control-path-deployer помечен `keep-LLM` без + доказательства не-деривируемости вердикта, или artifact-producer-analyst помечен `replace-*`). --- @@ -71,7 +84,7 @@ Work Item: **ORCH-118** · Repo: **orchestrator** · Стадия: analysis **оценка** экономии токенов/времени (со ссылкой на источник — `agent_runs`/`usage`), риск безопасности, потребность в hybrid-fallback, ожидание kill-switch/обратимости и **тип follow-up задачи по роли** (без конкретного Plane-ID); явно назван **рекомендованный первый срез** с - обоснованием. + обоснованием (самый низкорисковый «чисто деривируемый» control path). - **FAIL:** Roadmap не упорядочен; у кандидата отсутствует любой обязательный атрибут; оценка экономии не привязана к источнику; нет рекомендованного первого среза; кандидат привязан к выдуманному Plane-ID (→ см. AC-9). @@ -82,32 +95,36 @@ Work Item: **ORCH-118** · Repo: **orchestrator** · Стадия: analysis **Условие:** Существует durable-документ политики. - **PASS:** Политика формулирует принцип «LLM — только где нужно настоящее суждение», даёт критерии - решения keep vs replace и требование обосновывать любое новое использование LLM против политики; - документ нормативный (durable, в `docs/`), а не разовая заметка. -- **FAIL:** Политика отсутствует; не нормативна; противоречит сохранению автономности (NFR-2). + решения keep vs replace **через ось §0-bis** (control path ли это; деривируем ли вердикт) и требование + обосновывать любое новое использование LLM против политики; документ нормативный (durable, в `docs/`), + а не разовая заметка. +- **FAIL:** Политика отсутствует; не нормативна; не опирается на control-path-критерий; противоречит + сохранению автономности (NFR-2). --- ## AC-6 — Структурные анти-дрейф тесты: зелёные и осмысленные **Условие:** Новый offline-тест-файл прибивает инварианты карты к коду; инвариант сформулирован вокруг -**LLM-консультации/транспорта**, а не «существования процесса Claude CLI» (R4). +**LLM-консультации/транспорта** (R4) **и control-path-оси** (R5), а не «существования процесса Claude CLI». - **PASS:** Тесты проверяют: (a) единственный транспорт LLM-консультации в `src/**` (= `launcher._spawn`); **(f) отсутствие любого иного LLM-транспорта** (нет импорта `anthropic`/`openai`/LLM-SDK, нет прямого - HTTP-эндпоинта Anthropic/Claude, нет второго model-invoking subprocess-сборщика `--system-prompt`/ - `--model`) — именно (f), а не (a), закрывает «вторую консультацию другим транспортом»; - (b) отсутствие LLM-консультации в перечисленных детерминированных модулях и в обработчиках - deploy-finalizer/post-deploy-monitor; (c) двустороннюю сверку списка промптов карты с - `.openclaw/agents/`; (d) тотальность классификации (каждый site ровно один раз); (e) перехват - `D1/D2` в `launch_job` до `_spawn` (слот агента без консультации LLM — capability ≠ consultation). - Дискриминатор тестов — **«консультирует LLM», а не «спавнит subprocess»**: прочие subprocess - (`git`/`pytest`/`docker`/`ssh`/сканеры) явно исключены из матчинга. Тесты не используют сеть/LLM/ - subprocess-к-модели. Полный `pytest tests/ -q` — зелёный. + HTTP-эндпоинта Anthropic/Claude, нет второго model-invoking subprocess-сборщика); (b) отсутствие + LLM-консультации в перечисленных детерминированных модулях и в обработчиках deploy-finalizer/ + post-deploy-monitor; (c) двустороннюю сверку списка промптов карты с `.openclaw/agents/`; + (d) тотальность классификации; (e) перехват `D1/D2` в `launch_job` до `_spawn`; **(g) корректность + control-path-разметки (TC-13)** — `axis` каждой роли согласован с фактическим потребителем в + `src/qg/checks.py` (P-роли → `check_analysis_complete`/`check_architecture_done`/`check_ci_green`; + C-роли → `check_reviewer_verdict`/`_parse_tests_verdict`/`_parse_staging_status`/`_parse_deploy_status`); + **(h) фиксацию avoidable-набора (TC-14)** — множество avoidable LLM control paths = `{tester, deployer}`, + reviewer = control-path-keep, analyst/architect/developer = не control path. Дискриминатор тестов — + **«консультирует LLM», а не «спавнит subprocess»**. Тесты не используют сеть/LLM/subprocess-к-модели. + Полный `pytest tests/ -q` — зелёный. - **FAIL:** Тестов нет; тест тривиально проходит (не привязан к коду); инвариант проверяет лишь «один - `Popen` Claude CLI» **без** проверки (f) отсутствия альтернативного LLM-транспорта (конфлация - «процесс существует» ↔ «единственная консультация» — корень блокера R4); тест выродился в «подсчёт - всех subprocess» (ловит git/pytest/docker как LLM); любой тест красный; полный прогон `tests/` падает; - введён тест, прибивающий карту к конкретным follow-up Plane-ID (анти-паттерн R3). + `Popen` Claude CLI» **без** (f); **отсутствует control-path-инвариант (g/h)** — карта могла бы помечать + analyst «control path» или забыть deployer в avoidable-наборе, и тест бы это пропустил (корень + R4-блокера); тест выродился в «подсчёт всех subprocess»; любой тест красный; полный прогон `tests/` + падает; введён тест, прибивающий карту к конкретным follow-up Plane-ID (анти-паттерн R3). --- @@ -137,7 +154,7 @@ Work Item: **ORCH-118** · Repo: **orchestrator** · Стадия: analysis **Условие:** Ни один артефакт не фиксирует непроверяемых follow-up Plane-ID; кандидаты-замены именуются по роли. - **PASS:** Везде, где карта/BRD/TRZ/roadmap/ADR упоминают кандидата-замену, он назван **по роли** - (deployer-замена, tester-гибрid); конкретные follow-up Plane-ID **не указаны**; все `file:line`/ + (deployer-замена, tester-гибрид); конкретные follow-up Plane-ID **не указаны**; все `file:line`/ ссылки на документы резолвятся в репозиторий. Тип follow-up'а описан по роли, ID — «TBD / при заведении задачи». - **FAIL:** Любой артефакт фиксирует конкретный follow-up Plane-ID (напр. `ORCH-115`/`ORCH-116`) как @@ -151,15 +168,42 @@ Work Item: **ORCH-118** · Repo: **orchestrator** · Стадия: analysis --- +## AC-10 (R5) — Явная control-path-ось и определение «avoidable LLM control path» + +**Условие:** Артефакты **явно** различают control-path-консультации и artifact-producer-консультации и +**явно** определяют целевой термин — это закрывает единственный оставшийся блокер R4-ревью (название +задачи — «replace avoidable LLM **control paths**»). +- **PASS:** + 1. **Ось определена и применена.** Карта/политика явно вводят ось (C) control-path (LLM-вердикт + потребляется потоком управления — `check_*` ветвится на нём) vs (P) artifact-producer + (детерминированный гейт судит артефакт; суждение LLM в control flow не входит), и присваивают + **ровно один** тип каждой из 6 ролей с доказательством-потребителем (`check_*`/`_parse_*`, + `file:line`): P = `analyst`/`architect`/`developer`; C = `reviewer`/`tester`/`deployer`. + 2. **Термин определён.** Дано нормативное определение «avoidable LLM control path» = двухбитный + предикат: (i) C-консультация **И** (ii) вердикт деривируем из tool-сигналов (exit-code `pytest`/ + smoke/`staging_check.py`/деплоя). + 3. **Целевой набор поимённо назван.** Артефакты явно называют **avoidable LLM control paths = + {tester, deployer}**, и явно отделяют: `reviewer` — control path, но **keep** (вердикт не + деривируем, настоящее суждение); `analyst`/`architect`/`developer` — **не** control path + (artifact-producer). Это согласовано с классификацией (AC-2) и закреплено тестами TC-13/TC-14. +- **FAIL:** Артефакты упоминают «LLM control paths» без явного определения; не размечают ось C/P + по ролям или размечают её без доказательства-потребителя; не определяют «avoidable» как + проверяемый предикат; не называют поимённо целевой набор `{tester, deployer}` **или** не отделяют его + от reviewer (control-path-keep) и от analyst/architect/developer (не control path); разметка не + согласована с `src/qg/checks.py` / не закреплена TC-13/TC-14. + +--- + ## Сводная матрица AC ↔ FR/BR | AC | Покрывает | |----|-----------| -| AC-1 | BR-1 / FR-1 | -| AC-2 | BR-2 / FR-2 | +| AC-1 | BR-1 / FR-1 / BR-8 / FR-8 | +| AC-2 | BR-2 / FR-2 (выведена из §0-bis) | | AC-3 | BR-3 / FR-3 | | AC-4 | BR-4 / FR-4 | | AC-5 | BR-5 / FR-5 | -| AC-6 | BR-6 / FR-6 | +| AC-6 | BR-6 / FR-6 (вкл. TC-13/TC-14) | | AC-7 | BR-7 / FR-7 / NFR-1 / NFR-3 | | AC-8 | NFR-4 / правила агентов §2,§6 (golden-source) | | AC-9 | NFR-6 / BR-7 / FR-7 (только проверяемые ссылки; follow-up'ы по роли, без выдуманных ID) | +| AC-10 | **BR-8 / BR-9 / FR-8 / NFR-7 (R5 — control-path-ось + определение «avoidable»)** | diff --git a/docs/work-items/ORCH-118/04-test-plan.yaml b/docs/work-items/ORCH-118/04-test-plan.yaml index d688d2e..8824b73 100644 --- a/docs/work-items/ORCH-118/04-test-plan.yaml +++ b/docs/work-items/ORCH-118/04-test-plan.yaml @@ -4,16 +4,22 @@ author_agent: analyst status: ready-for-review created_at: 2026-06-15 model_used: claude-opus-4-8 -title: "LLM call-site inventory + classification + roadmap + usage policy (inventory-first, docs+tests only)" +title: "LLM call-site inventory + control-path axis + classification + roadmap + usage policy (inventory-first, docs+tests only)" framework: pytest scope: > Покрываются СТРУКТУРНЫЕ инварианты карты LLM-консультаций и анти-дрейф (FR-6), плюс скоуп-гард (рантайм-контракты не тронуты, раннеры не реализованы) и анти-фабрикация ссылок/ID (TC-11). Единица — LLM-КОНСУЛЬТАЦИЯ (control-path потребляет суждение LLM), а не «спавн процесса / Claude CLI существует» (R4, BRD §0). Инвариант единственной точки — транспорт-агностичный и двусторонний: - TC-01 (единственный транспорт = _spawn) + TC-12 (отсутствует иной LLM-транспорт). ВНЕ покрытия: - реализация детерминированных раннеров deployer / tester — отдельные follow-up задачи (именуются по - роли; конкретные Plane-ID в ORCH-118 не фиксируются, R3/NFR-6). + TC-01 (единственный транспорт = _spawn) + TC-12 (отсутствует иной LLM-транспорт). + R5: добавлена CONTROL-PATH-ОСЬ (BRD §0-bis) — среди реальных консультаций различаются + (C) control-path (LLM-вердикт потребляется потоком управления, check_* ветвится на нём) и + (P) artifact-producer (детерминированный гейт судит артефакт); термин «avoidable LLM control path» + определён как двухбитный предикат (C И вердикт деривируем из tool-сигналов), целевой набор поимённо + = {tester, deployer}. Эту ось проверяют TC-13 (разметка C/P согласована с потребителем в + src/qg/checks.py) и TC-14 (avoidable-набор зафиксирован). ВНЕ покрытия: реализация детерминированных + раннеров deployer / tester — отдельные follow-up задачи (именуются по роли; конкретные Plane-ID в + ORCH-118 не фиксируются, R3/NFR-6). notes: > Все тесты детерминированы и offline: без сети, без запуска LLM, без subprocess-к-модели. Имена файла теста и документов карты — примерные (финально решает архитектор); тест-кейсы @@ -24,18 +30,26 @@ notes: > появление второго ТРАНСПОРТА LLM-консультации (новый _spawn ИЛИ импорт anthropic/openai/LLM-SDK ИЛИ прямой HTTP Anthropic/Claude ИЛИ второй model-invoking subprocess), LLM-консультация в детерминированном модуле, дрейф карты относительно .openclaw/agents/, изменение рантайм-контрактов - (STAGE_TRANSITIONS / QG_CHECKS / check_* / machine-verdict / схема БД). - R4 (единственный блокер R3-ревью): инвариант "места вызова LLM" разведён на ТРАНСПОРТ ("процесс - Claude CLI существует") и КОНСУЛЬТАЦИЮ ("поток управления потребляет суждение LLM"); TC-01 уточнён - (необходимое, но не достаточное), добавлен TC-12 (no-alternative-transport), TC-02 уточнён по - дискриминатору, TC-06 закрепляет capability ≠ consultation (D1/D2 — слот без консультации). - R3: тест на привязку follow-up'ов к конкретным Plane-ID УДАЛЁН (бывш. TC-11) как анти-паттерн — - прибивал карту к несуществующим ID; вместо него TC-11 проверяет анти-фабрикацию (ID не выдуманы). + (STAGE_TRANSITIONS / QG_CHECKS / check_* / machine-verdict / схема БД), рассогласование + control-path-разметки с фактическим потребителем в src/qg/checks.py (TC-13), либо изменение + avoidable-набора без обновления карты (TC-14). + R5 (единственный блокер R4-ревью): артефакты разводили "консультация ≠ транспорт/слот", но не делали + явной CONTROL-PATH-ОСЬ — самую важную для названия задачи "replace avoidable LLM CONTROL PATHS". + Добавлены TC-13 (control-path-разметка C/P доказывается фактическим потребителем check_*/_parse_*) и + TC-14 (avoidable LLM control paths = {tester, deployer}; reviewer = control-path-keep; + analyst/architect/developer = не control path). TC-04 (тотальность) теперь сверяет согласованность + класса с осью. + R4 (предыдущий блокер): инвариант "места вызова LLM" разведён на ТРАНСПОРТ и КОНСУЛЬТАЦИЮ; + TC-01 уточнён (необходимое, но не достаточное), добавлен TC-12 (no-alternative-transport), + TC-02 уточнён по дискриминатору, TC-06 закрепляет capability ≠ consultation (D1/D2 — слот без + консультации). + R3: тест на привязку follow-up'ов к конкретным Plane-ID УДАЛЁН (бывш. TC-11) как анти-паттерн; + TC-11 теперь проверяет анти-фабрикацию (ID не выдуманы). tests: - id: TC-01 type: unit - description: "Единственный ТРАНСПОРТ LLM-консультации: ровно одно место в src/** собирает/запускает Claude CLI (матчинг по совокупности признаков LLM-транспорта CLAUDE_BIN + --system-prompt + Popen/bash -c), и это launcher._spawn. Необходимое, но НЕ достаточное условие — дополняется TC-12 (отсутствие иного транспорта); сам по себе подсчёт _spawn не доказывает 'единственная LLM-консультация' (R4 / FR-6a / AC-1)" + description: "Единственный ТРАНСПОРТ LLM-консультации: ровно одно место в src/** собирает/запускает Claude CLI (матчинг по совокупности признаков LLM-транспорта CLAUDE_BIN + --system-prompt + Popen/bash -c), и это launcher._spawn. Необходимое, но НЕ достаточное условие — дополняется TC-12 (отсутствие иного транспорта) (R4 / FR-6a / AC-1)" module: tests/test_llm_call_site_inventory.py expected: PASS @@ -53,19 +67,19 @@ tests: - id: TC-04 type: unit - description: "Тотальность классификации: каждый перечисленный в карте call-site отнесён ровно к одному классу из таксономии {keep-LLM, replace-deterministic-now, replace-later/risky, needs-hybrid-fallback}; без дублей и пропусков (FR-6d / AC-2)" + description: "Тотальность классификации: каждый перечисленный в карте call-site отнесён ровно к одному классу из таксономии {keep-LLM, replace-deterministic-now, replace-later/risky, needs-hybrid-fallback}; без дублей и пропусков; класс СОГЛАСОВАН с осью §0-bis (P → keep-LLM; C+не-деривируем → keep-LLM; C+деривируем → replace-*/hybrid) (FR-6d / FR-2 / AC-2)" module: tests/test_llm_call_site_inventory.py expected: PASS - id: TC-05 type: unit - description: "keep-LLM требует обоснования: каждая запись класса keep-LLM несёт непустое поле названного конкретного суждения (FR-2 / AC-2)" + description: "keep-LLM требует обоснования: каждая запись класса keep-LLM несёт непустое поле названного конкретного суждения; для C-keep (reviewer) обоснование явно фиксирует НЕ-деривируемость вердикта (почему не сводится к exit-коду) (FR-2 / AC-2)" module: tests/test_llm_call_site_inventory.py expected: PASS - id: TC-06 type: unit - description: "Capability ≠ consultation: launch_job перехватывает deploy-finalizer и post-deploy-monitor ДО _spawn — job занимает слот агента, но LLM НЕ консультируется (процесс/слот существует, суждение не потребляется) — эталон паттерна замены и прямая иллюстрация R4-различия (FR-6e / AC-3)" + description: "Capability ≠ consultation: launch_job перехватывает deploy-finalizer и post-deploy-monitor ДО _spawn (launcher.py:389/394) — job занимает слот агента, но LLM НЕ консультируется (процесс/слот существует, суждение не потребляется) — эталон паттерна замены и прямая иллюстрация R4-различия (FR-6e / AC-3)" module: tests/test_llm_call_site_inventory.py expected: PASS @@ -77,7 +91,7 @@ tests: - id: TC-08 type: unit - description: "Политика LLM существует и нормативна: документ политики содержит принцип 'LLM только где нужно суждение' и критерии keep vs replace (FR-5 / AC-5)" + description: "Политика LLM существует и нормативна: документ политики содержит принцип 'LLM только где нужно суждение', критерии keep vs replace СФОРМУЛИРОВАННЫЕ через ось §0-bis (control path ли это; деривируем ли вердикт), и нормативное определение термина 'avoidable LLM control path' (FR-5 / FR-8 / AC-5, AC-10)" module: tests/test_llm_determinization_docs.py expected: PASS @@ -104,3 +118,15 @@ tests: description: "Отсутствие иного LLM-транспорта (R4 / FR-6f / AC-1, AC-6): в src/** НЕТ альтернативного транспорта LLM-консультации помимо _spawn — ни импорта anthropic/openai/иного LLM-SDK, ни прямого HTTP-эндпоинта Anthropic/Claude (api.anthropic.com, /v1/messages), ни второго model-invoking subprocess-сборщика (другой бинарь с --system-prompt/--model). Закрывает дыру 'один _spawn зелёный, а рядом проросла новая консультация другим транспортом', которую TC-01 в одиночку не ловит. Allowlist единственного разрешённого транспорта = S0/launcher._spawn." module: tests/test_llm_call_site_inventory.py expected: PASS + + - id: TC-13 + type: unit + description: "Control-path-ось верна (R5 / FR-6g / FR-8 / AC-10): поле axis (C/P) каждой из 6 ролей в карте СОГЛАСОВАНО с фактическим потребителем вывода в src/qg/checks.py — P-роли (analyst/architect/developer) потребляются детерминированными гейтами (check_analysis_complete:33 / check_architecture_done:62 / check_ci_green:82, судящими наличие файлов / CI, НЕ самоотчёт LLM); C-роли (reviewer/tester/deployer) потребляются verdict-парсерами, читающими machine-verdict, который написал LLM (check_reviewer_verdict:336 'verdict:' / check_tests_passed:182→_parse_tests_verdict:226 'result:' / check_staging_status:599→_parse_staging_status:538 'staging_status:' + check_deploy_status:473→_parse_deploy_status:413 'deploy_status:'). Дискриминатор: 'LLM-вердикт ветвит поток управления', а не 'на стадии есть LLM'. Рассогласование (роль переразмечена ИЛИ потребитель в коде сменил природу) → красный." + module: tests/test_llm_call_site_inventory.py + expected: PASS + + - id: TC-14 + type: unit + description: "Avoidable-набор зафиксирован (R5 / FR-6h / FR-8 / AC-10): множество 'avoidable LLM control path' в карте = РОВНО {tester, deployer} (C И вердикт деривируем из exit-кодов); reviewer помечен control-path-но-keep (C, вердикт НЕ деривируем — настоящее суждение); analyst/architect/developer помечены НЕ control path (P, artifact-producer). Любое добавление/удаление роли в avoidable-набор без обновления карты, либо пометка analyst/architect/developer 'control path', либо пометка reviewer 'avoidable' → красный. Закрывает корень R4-блокера: 'есть LLM на стадии' ≠ 'это avoidable LLM control path'." + module: tests/test_llm_call_site_inventory.py + expected: PASS