From 55c13abb9ae0ff29a70ac2085491e8310add53d3 Mon Sep 17 00:00:00 2001 From: claude-bot Date: Mon, 15 Jun 2026 23:29:04 +0300 Subject: [PATCH] analyst(ET): auto-commit from analyst run_id=725 --- docs/work-items/ORCH-118/01-brd.md | 100 ++++++++++++++---- docs/work-items/ORCH-118/02-trz.md | 90 +++++++++++----- .../ORCH-118/03-acceptance-criteria.md | 57 ++++++---- docs/work-items/ORCH-118/04-test-plan.yaml | 35 ++++-- 4 files changed, 207 insertions(+), 75 deletions(-) diff --git a/docs/work-items/ORCH-118/01-brd.md b/docs/work-items/ORCH-118/01-brd.md index 5d5206d..70bda99 100644 --- a/docs/work-items/ORCH-118/01-brd.md +++ b/docs/work-items/ORCH-118/01-brd.md @@ -31,6 +31,50 @@ 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) — **без изменений**. + +--- + +## 0. Единица анализа: «LLM-консультация» ≠ «процесс Claude CLI» (R4) + +Задача — про **LLM control paths**, поэтому единица инвентаря/классификации/инвариантов — это +**LLM-консультация (call-site)**, а НЕ «спавн процесса / существование Claude CLI». Три ортогональных +факта фиксируются **раздельно** (их смешение и было блокером R3-ревью): + +1. **LLM-консультация (семантическая единица).** Точка потока управления, где решение/артефакт + конвейера **потребляет суждение LLM** (инференс модели). Именно её перечисляет и классифицирует + карта. «Заменить avoidable LLM control path» = убрать *консультацию* там, где суждение не нужно, — + независимо от того, каким транспортом она реализована. +2. **Транспорт (механизм) ≠ консультация.** Claude CLI-subprocess через `launcher._spawn` + (`src/agents/launcher.py`, сборка `CLAUDE_BIN --print … --system-prompt "$(cat …)"` + `Popen`) — + **текущий единственный транспорт**, которым реализуется LLM-консультация. Транспорт — это одна + *реализация* понятия, а НЕ его *определение*. «Существует процесс Claude CLI» само по себе **не** + равно «принято решение на основе суждения LLM». +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 существует; + - timeout-kill (`exit_code=-9`) / salvage-guard (`if exit_code==0`) → спавненный процесс может **не + произвести** потреблённого суждения. + Поэтому «процесс спавнится» **переоценивает** «суждение потреблено». + +Следствие для инварианта единственной точки (детализируется в BR-1/BR-6): он должен быть +**транспорт-агностичным и двусторонним** — (i) единственный транспорт LLM-консультации в `src/**` — +`_spawn`, И (ii) **отсутствует любой иной транспорт** (импорт `anthropic`/`openai`/LLM-SDK, прямой +HTTP-эндпоинт Anthropic/Claude, второй model-invoking subprocess-сборщик `--system-prompt`/`--model`). +Дискриминатор — **«консультирует LLM», а не «спавнит subprocess»**: десятки subprocess-вызовов в +`src/**` (`git`/`pytest`/`docker`/`ssh`/сканеры/`staging_check.py`) **не** являются LLM-консультациями +и не должны попадать под инвариант (см. FR-6 матчинг). + --- ## 1. Бизнес-контекст и проблема @@ -42,9 +86,13 @@ Work Item: **ORCH-118** · Repo: **orchestrator** · Стадия: analysis Установленный факт по текущему коду (инвентаризация — §1 TRZ, артефакт-карта): -- В оркестраторе **ровно одна точка запуска LLM** — `src/agents/launcher.py::_spawn` (одна - `subprocess.Popen` сборка Claude CLI: `CLAUDE_BIN --print … --system-prompt "$(cat …)"`), которой +- В оркестраторе **единственный транспорт LLM-консультации** — `src/agents/launcher.py::_spawn` (одна + `subprocess.Popen` сборка Claude CLI: `CLAUDE_BIN --print … --system-prompt "$(cat …)"`), которым пользуются **6 ролей-агентов** (analyst, architect, developer, reviewer, tester, deployer). + Прямых вызовов Anthropic API / LLM-SDK / иных model-invoking транспортов в `src/**` **нет** + (подтверждено инвентаризацией; впредь — держится тестом FR-6). ⚠️ Это утверждение про **транспорт**, + а не про «единственный subprocess»: в `src/**` десятки прочих `subprocess`-вызовов (`git`/`pytest`/ + `docker`/`ssh`/сканеры) — они **не** консультируют LLM (см. §0). - **Все остальные control-path'ы уже детерминированы (без LLM):** маршрутизация стадий (`STAGE_TRANSITIONS`/`advance_stage`), все Quality Gate'ы и под-гейты (`check_*`, security/merge/ coverage/image-freshness), парсеры вердиктов (`_parse_*` через `frontmatter.py`), классификатор @@ -78,8 +126,9 @@ ORCH-118 даёт **доказательную карту** «где LLM дей **по роли** (follow-up ID — TBD). - **BR-5** Нормативная **политика использования LLM** («LLM — только там, где нужно настоящее суждение») как durable-документ. -- **BR-6** Структурные regression-тесты, **прибивающие инварианты карты к коду** (единственная точка - запуска; детерминированные модули не несут запуска LLM; карта покрывает все промпты; тотальность +- **BR-6** Структурные regression-тесты, **прибивающие инварианты карты к коду** (транспорт-агностичный + двусторонний инвариант: единственный транспорт LLM-консультации `_spawn` **и** отсутствие иного + LLM-транспорта; детерминированные модули не консультируют LLM; карта покрывает все промпты; тотальность классификации) — анти-дрейф. - **BR-7** Явно позиционировать **роль deployer** и **роль tester** как **кандидаты-follow-up** для детерминированной замены — **по роли, без привязки к конкретным Plane-ID** (см. NFR-6). @@ -106,11 +155,14 @@ ORCH-118 даёт **доказательную карту** «где LLM дей ## 4. Бизнес-требования (BR) -- **BR-1 — Инвентарь call-site'ов LLM.** Выпустить durable-документ, перечисляющий **каждое** место, - где LLM вызывается или может быть вызван: единственную точку `_spawn`, все 6 ролей-агентов и обе - зарезервированные детерминированные job-роли (как доказательство «уже без LLM»). Для каждого — - `file:line`, триггер, стадия/владелец, выходной артефакт, machine-verdict-ключ (если есть), оценка - токенов/времени. Проверяемо: каждый `file:line` резолвится в реальный код. +- **BR-1 — Инвентарь LLM-консультаций (call-site'ов).** Выпустить durable-документ, перечисляющий + **каждое** место, где control-path **потребляет суждение LLM** или может его потребить (единица — + *LLM-консультация*, §0, а не «спавн процесса»): единственный транспорт `_spawn`, все 6 ролей-агентов + и обе зарезервированные job-роли `D1/D2` (включаются как доказательство «слот агента есть, но + консультации LLM нет» — перехват до `_spawn`). Для каждого — `file:line`, триггер, стадия/владелец, + выходной артефакт, machine-verdict-ключ (если есть), оценка токенов/времени, **признак + capability-vs-consultation** (LLM-capable vs фактически консультирует, §0.3). Проверяемо: каждый + `file:line` резолвится в реальный код. - **BR-2 — Классификация.** Каждому call-site присвоить **ровно один** класс из таксономии: `keep-LLM` (нужно настоящее суждение), `replace-deterministic-now` (безопасная замена сейчас), `replace-later/risky` (замена позже / рискованно), `needs-hybrid-fallback` (детерминированное ядро + @@ -118,8 +170,10 @@ ORCH-118 даёт **доказательную карту** «где LLM дей сохраняется. - **BR-3 — Подтверждение детерминизма не-агентских путей.** Документально, с `file:line`-доказательством, зафиксировать, что маршрутизация стадий, ретраи, QG-проверки, парсеры вердиктов и finalizer'ы **не - вызывают LLM**. Если инвентаризация найдёт неожиданный LLM-путь — он попадает в карту и - классификацию. + консультируют LLM** (не зависят от суждения LLM — ни через `_spawn`, ни через иной транспорт; их + subprocess-вызовы git/pytest/docker/ssh/сканеров — детерминированные инструменты, не LLM, §0). Если + инвентаризация найдёт неожиданную LLM-консультацию — она попадает в карту (BR-1) и классификацию + (BR-2). - **BR-4 — Упорядоченный roadmap.** Ранжированный план замен: для каждого кандидата (названного **по роли**) — зависимости, **оценка** экономии токенов/времени (из телеметрии `agent_runs`), риск безопасности, нужен ли hybrid-fallback, ожидание kill-switch/обратимости. Явно указать @@ -128,9 +182,15 @@ ORCH-118 даёт **доказательную карту** «где LLM дей - **BR-5 — Политика использования LLM.** Нормативный durable-документ: «LLM — только там, где требуется настоящее суждение»; критерии решения keep vs replace; требование к новым/изменённым control-path'ам обосновывать любое использование LLM против этой политики. -- **BR-6 — Анти-дрейф структурными тестами.** Тесты, привязывающие инварианты карты к коду: - единственная точка запуска LLM; перечисленные детерминированные модули/job-роли не несут запуска; - карта перечисляет ровно те 6 промптов, что лежат в `.openclaw/agents/`; классификация покрывает все +- **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-7 — Позиционирование follow-up'ов по роли.** Карта/roadmap явно отмечают **роль deployer** и @@ -168,11 +228,15 @@ ORCH-118 даёт **доказательную карту** «где LLM дей поэтому ID не фиксируются (NFR-6). ## 7. Критерии успеха -Карта call-site'ов полна и привязана к коду; каждый site классифицирован с обоснованием; детерминизм -не-агентских путей доказан; есть упорядоченный roadmap с зависимостями/экономией/рисками и +Карта LLM-консультаций полна и привязана к коду, и **разводит транспорт/слот («процесс Claude CLI +существует») от факта консультации («поток управления потребляет суждение LLM»)** (R4, §0); каждый +site классифицирован с обоснованием; детерминизм не-агентских путей доказан (отсутствием LLM-консультации, +не отсутствием subprocess); есть упорядоченный roadmap с зависимостями/экономией/рисками и рекомендованным первым срезом (кандидаты — по роли); есть нормативная политика; структурные тесты -зелёные и осмысленные; ни один рантайм-инвариант не тронут; раннеры замен НЕ реализованы; ни один -артефакт не фиксирует непроверяемых follow-up ID. Детальные PASS/FAIL — в `03-acceptance-criteria.md`. +зелёные и осмысленные — инвариант единственной точки **транспорт-агностичен и двусторонен** (единственный +транспорт `_spawn` **и** отсутствие иного LLM-транспорта); ни один рантайм-инвариант не тронут; раннеры +замен НЕ реализованы; ни один артефакт не фиксирует непроверяемых follow-up ID. Детальные PASS/FAIL — +в `03-acceptance-criteria.md`. ## 8. Риски - **Недо-/пере-классификация** (LLM убран там, где нужно суждение, или сохранён там, где не нужен) → diff --git a/docs/work-items/ORCH-118/02-trz.md b/docs/work-items/ORCH-118/02-trz.md index f0d00d5..9c77f02 100644 --- a/docs/work-items/ORCH-118/02-trz.md +++ b/docs/work-items/ORCH-118/02-trz.md @@ -22,6 +22,11 @@ Work Item: **ORCH-118** · Repo: **orchestrator** · Стадия: analysis > 📌 **Follow-up'ы — по роли, без выдуманных Plane-ID** (R3, NFR-6). Конкретные ID (напр. > `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. --- @@ -35,17 +40,21 @@ Work Item: **ORCH-118** · Repo: **orchestrator** · Стадия: analysis Опорный факт инвентаризации (ground-truth кода на момент задачи; line-привязки уточняет карта): -| # | Call-site (LLM-capable) | Где | Что делает | -|---|--------------------------|-----|------------| -| S0 | **Единственная точка запуска** | `src/agents/launcher.py::_spawn` (сборка `CLAUDE_BIN --print … --system-prompt "$(cat …)"` + `subprocess.Popen`) | запускает любую из 6 ролей | -| A1 | analyst | промпт `.openclaw/agents/analyst.md`, стадия `analysis` | анализ бизнес-запроса → 01–04 | -| A2 | architect | `.openclaw/agents/architect.md`, стадия `architecture` | архитектурные решения → 06-adr | -| A3 | developer | `.openclaw/agents/developer.md`, стадия `development` | реализация + PR | -| A4 | reviewer | `.openclaw/agents/reviewer.md`, стадия `review` | ревью → `12-review.md` (`verdict:`) | -| A5 | tester | `.openclaw/agents/tester.md`, стадия `testing` | `pytest`+smoke → `13-test-report.md` (`result:`) | -| A6 | deployer | `.openclaw/agents/deployer.md`, стадии `deploy-staging`/`deploy` | `staging_check.py`/exit-code → `15`/`14` логи | -| D1 | deploy-finalizer | `launch_job` перехват **до** `_spawn` (`launcher.launch_job`) | детерминированный (LLM не запускается) | -| D2 | post-deploy-monitor | `launch_job` перехват **до** `_spawn` (`launcher.launch_job`) | детерминированный (LLM не запускается) | +> Единица — **LLM-консультация** (потребление суждения LLM), а не «спавн процесса» (R4, BRD §0). +> Колонка ниже помечает, является ли site фактической консультацией или лишь *LLM-capable* транспортом/ +> слотом. + +| # | 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`) | > Не-агентские control-path'ы (маршрутизация `advance_stage`, `QG_CHECKS`/`check_*`/`_parse_*`, > `error_classifier`, `serial_gate`/`merge_gate`/`coverage_gate`/`security_gate`/`staging_verdict`/ @@ -73,13 +82,18 @@ Work Item: **ORCH-118** · Repo: **orchestrator** · Стадия: analysis ## 3. Функциональные требования -### FR-1 — Полнота и привязка инвентаря (BR-1) -Карта перечисляет **каждый** LLM-capable call-site: `S0` (единственный `_spawn`), `A1…A6` (6 ролей), -`D1/D2` (детерминированные job-роли — как доказательство паттерна). Поля записи: `id`, `location` -(`file:line`), `trigger`, `stage/owner`, `output artifact`, `machine-verdict key` (если есть), -`est. tokens/runtime`, `classification`, `rationale`, `dependency`, `risk`. Каждый `file:line` -обязан резолвиться в реальный код. Инвариант: иных мест запуска LLM в `src/**`, кроме `S0`, нет — и -это подтверждается тестом FR-6(a). +### FR-1 — Полнота и привязка инвентаря LLM-консультаций (BR-1) +Единица — **LLM-консультация** (control-path потребляет суждение LLM), а не «спавн процесса» (R4, BRD +§0). Карта перечисляет **каждый** call-site: `S0` (единственный транспорт `_spawn`), `A1…A6` (6 ролей, +консультируют через 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`. ### FR-2 — Таксономия классификации (BR-2) Ровно 4 взаимоисключающих класса с определениями: @@ -101,12 +115,15 @@ self-hosting уже детерминирован Phase A/B/C) — **кандид > заведении задачи; ORCH-118 ID не выдумывает. ### FR-3 — Подтверждение детерминизма не-агентских путей (BR-3) -Карта отдельным разделом фиксирует, с `file:line`-доказательством, что НЕ вызывают LLM: -маршрутизация (`advance_stage`/`STAGE_TRANSITIONS`), все `QG_CHECKS`/`check_*`, парсеры вердиктов -(`_parse_*` через `frontmatter.parse_frontmatter`), `error_classifier` (regex), под-гейты -(security/merge/coverage/image-freshness), `self_deploy` Phase A/B/C, reconciler/reaper/serial-gate/ -transition-lease. Любой найденный неожиданный LLM-путь добавляется в инвентарь (FR-1) и -классифицируется (FR-2). +Карта отдельным разделом фиксирует, с `file:line`-доказательством, что НЕ консультируют LLM (не зависят +от суждения LLM ни через `_spawn`, ни иным транспортом): маршрутизация (`advance_stage`/ +`STAGE_TRANSITIONS`), все `QG_CHECKS`/`check_*`, парсеры вердиктов (`_parse_*` через +`frontmatter.parse_frontmatter`), `error_classifier` (regex), под-гейты (security/merge/coverage/ +image-freshness), `self_deploy` Phase A/B/C, reconciler/reaper/serial-gate/transition-lease. ⚠️ Эти +модули **спавнят subprocess'ы** (`git`/`pytest`/`docker`/`ssh`/сканеры) — это детерминированные +инструменты, **не** LLM-консультации (§0): доказательство детерминизма — отсутствие *LLM*-транспорта, +а не отсутствие *subprocess*. Любая найденная неожиданная LLM-консультация добавляется в инвентарь +(FR-1) и классифицируется (FR-2). ### FR-4 — Упорядоченный roadmap замен (BR-4) Ранжированный список кандидатов (названных **по роли**); для каждого: зависимости (предпосылки/блокеры), @@ -123,15 +140,30 @@ keep vs replace (детерминируемость выхода, наличие реализацию гейта** (новый QG не вводится, FR-6 §QG). ### FR-6 — Структурные анти-дрейф тесты (BR-6) -Новый offline-тест-файл (без сети/LLM/subprocess-к-модели), проверяющий инварианты карты: -- **(a)** В `src/**` ровно **одна** точка сборки/запуска Claude CLI (поиск по `CLAUDE_BIN` + - `--system-prompt` + `Popen`/`bash -c`), и это `launcher._spawn`. -- **(b)** Перечисленные детерминированные модули и обработчики `D1/D2` **не** содержат запуска LLM. +Новый offline-тест-файл (без сети/LLM/subprocess-к-модели), проверяющий инварианты карты. ⚠️ **R4 — +инвариант формулируется вокруг LLM-консультации/транспорта, а не «существования процесса Claude CLI».** +Дискриминатор тестов — **«консультирует LLM», а не «спавнит subprocess»**; десятки прочих subprocess +(`git`/`pytest`/`docker`/`ssh`/сканеры/`staging_check.py`) явно исключаются из матчинга, иначе тест +выродился бы в «подсчёт всех `Popen`». +- **(a) Единственный транспорт.** В `src/**` ровно **одна** точка сборки/запуска Claude CLI (матчинг + по совокупности признаков LLM-транспорта: `CLAUDE_BIN` + `--system-prompt` + `Popen`/`bash -c`), и + это `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`. +- **(b) Нет консультации в детерминированных путях.** Перечисленные детерминированные модули и + обработчики `D1/D2` **не** консультируют LLM (не содержат ни `_spawn`-транспорта, ни альтернативного + по (f)); их subprocess-вызовы инструментов LLM-консультацией не считаются. - **(c)** Карта перечисляет ровно те промпт-файлы, что физически лежат в `.openclaw/agents/` (двусторонняя сверка — нет дрейфа). - **(d)** Классификация покрывает каждый перечисленный call-site **ровно один раз** (тотальность, без дублей/пропусков). -- **(e)** `D1/D2` действительно перехватываются в `launch_job` **до** `_spawn` (детерминированы). +- **(e) Capability ≠ consultation.** `D1/D2` действительно перехватываются в `launch_job` **до** + `_spawn` → занимают слот агента, но **консультации LLM не происходит** (эталон «процесс/слот есть — + суждение не потребляется», §0.3). > ❌ **Не вводить** тест, прибивающий карту к конкретным follow-up Plane-ID → ✅ тесты проверяют > только инварианты, резолвящиеся в код/файлы репозитория (R3, NFR-6). Привязка к несуществующим ID diff --git a/docs/work-items/ORCH-118/03-acceptance-criteria.md b/docs/work-items/ORCH-118/03-acceptance-criteria.md index be53611..a837dab 100644 --- a/docs/work-items/ORCH-118/03-acceptance-criteria.md +++ b/docs/work-items/ORCH-118/03-acceptance-criteria.md @@ -18,16 +18,23 @@ Work Item: **ORCH-118** · Repo: **orchestrator** · Стадия: analysis --- -## AC-1 — Полнота и привязка инвентаря call-site'ов +## AC-1 — Полнота и привязка инвентаря LLM-консультаций -**Условие:** Документ-карта перечисляет каждый LLM-capable call-site с обязательными полями, привязанными к коду. -- **PASS:** Карта содержит `S0` (`launcher._spawn` — единственная точка запуска), все 6 ролей - (analyst/architect/developer/reviewer/tester/deployer) и обе детерминированные job-роли - (deploy-finalizer/post-deploy-monitor); у каждой записи заполнены `location (file:line)` / `trigger` / - `stage/owner` / `output` / `machine-verdict key (если есть)` / `est. tokens-runtime` / - `classification` / `rationale`; каждый `file:line` резолвится в реальный код. -- **FAIL:** Пропущен любой LLM-capable site; отсутствует любое обязательное поле; `file:line` не - резолвится; заявлена «вторая точка запуска LLM», не подтверждённая кодом. +**Условие:** Документ-карта перечисляет каждый call-site, где control-path потребляет (или способен +потребить) суждение LLM — **единица = LLM-консультация, не «спавн процесса»** (R4) — с обязательными +полями, привязанными к коду. +- **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, не подтверждённый кодом. --- @@ -44,13 +51,16 @@ Work Item: **ORCH-118** · Repo: **orchestrator** · Стадия: analysis ## AC-3 — Доказанный детерминизм не-агентских путей -**Условие:** Карта отдельно фиксирует, что control-path'ы вне 6 агентов не вызывают LLM, с доказательством. +**Условие:** Карта отдельно фиксирует, что control-path'ы вне 6 агентов не консультируют LLM, с доказательством. - **PASS:** Перечислены маршрутизация (`advance_stage`/`STAGE_TRANSITIONS`), все `QG_CHECKS`/`check_*`, парсеры `_parse_*`, `error_classifier`, под-гейты (security/merge/coverage/image-freshness), `self_deploy` Phase A/B/C, reconciler/reaper/serial-gate/transition-lease — каждый с `file:line`, - подтверждающим отсутствие вызова LLM. + подтверждающим отсутствие **LLM-консультации** (ни `_spawn`-транспорта, ни альтернативного). Их + subprocess-вызовы инструментов (`git`/`pytest`/`docker`/`ssh`/сканеры) явно квалифицированы как + **не-LLM** (детерминизм доказывается отсутствием LLM-транспорта, а не отсутствием subprocess). - **FAIL:** Утверждение о детерминизме без `file:line`-доказательства; путь, заявленный - детерминированным, фактически запускает LLM (и это не отражено в инвентаре/классификации). + детерминированным, фактически консультирует LLM (и это не отражено в инвентаре/классификации); либо + детерминизм «доказан» простым отсутствием subprocess (подмена дискриминатора, §0). --- @@ -80,15 +90,24 @@ Work Item: **ORCH-118** · Repo: **orchestrator** · Стадия: analysis ## AC-6 — Структурные анти-дрейф тесты: зелёные и осмысленные -**Условие:** Новый offline-тест-файл прибивает инварианты карты к коду. -- **PASS:** Тесты проверяют: (a) единственную точку запуска LLM в `src/**` (= `launcher._spawn`); - (b) отсутствие запуска LLM в перечисленных детерминированных модулях и в обработчиках +**Условие:** Новый offline-тест-файл прибивает инварианты карты к коду; инвариант сформулирован вокруг +**LLM-консультации/транспорта**, а не «существования процесса Claude CLI» (R4). +- **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/subprocess-к-модели. Полный - `pytest tests/ -q` — зелёный. -- **FAIL:** Тестов нет; тест тривиально проходит (не привязан к коду); любой тест красный; полный - прогон `tests/` падает; введён тест, прибивающий карту к конкретным follow-up Plane-ID (анти-паттерн R3). + `D1/D2` в `launch_job` до `_spawn` (слот агента без консультации LLM — capability ≠ consultation). + Дискриминатор тестов — **«консультирует LLM», а не «спавнит subprocess»**: прочие subprocess + (`git`/`pytest`/`docker`/`ssh`/сканеры) явно исключены из матчинга. Тесты не используют сеть/LLM/ + subprocess-к-модели. Полный `pytest tests/ -q` — зелёный. +- **FAIL:** Тестов нет; тест тривиально проходит (не привязан к коду); инвариант проверяет лишь «один + `Popen` Claude CLI» **без** проверки (f) отсутствия альтернативного LLM-транспорта (конфлация + «процесс существует» ↔ «единственная консультация» — корень блокера R4); тест выродился в «подсчёт + всех subprocess» (ловит git/pytest/docker как LLM); любой тест красный; полный прогон `tests/` падает; + введён тест, прибивающий карту к конкретным follow-up Plane-ID (анти-паттерн R3). --- diff --git a/docs/work-items/ORCH-118/04-test-plan.yaml b/docs/work-items/ORCH-118/04-test-plan.yaml index 8eb2489..d688d2e 100644 --- a/docs/work-items/ORCH-118/04-test-plan.yaml +++ b/docs/work-items/ORCH-118/04-test-plan.yaml @@ -7,30 +7,41 @@ model_used: claude-opus-4-8 title: "LLM call-site inventory + classification + roadmap + usage policy (inventory-first, docs+tests only)" framework: pytest scope: > - Покрываются СТРУКТУРНЫЕ инварианты карты вызовов LLM и анти-дрейф (FR-6), плюс скоуп-гард + Покрываются СТРУКТУРНЫЕ инварианты карты LLM-консультаций и анти-дрейф (FR-6), плюс скоуп-гард (рантайм-контракты не тронуты, раннеры не реализованы) и анти-фабрикация ссылок/ID (TC-11). - ВНЕ покрытия: реализация детерминированных раннеров deployer / tester — отдельные follow-up - задачи (именуются по роли; конкретные Plane-ID в ORCH-118 не фиксируются, R3/NFR-6). + Единица — 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). notes: > Все тесты детерминированы и offline: без сети, без запуска LLM, без subprocess-к-модели. Имена файла теста и документов карты — примерные (финально решает архитектор); тест-кейсы привязываются к фактическим путям артефактов, выбранным в 06-adr. Полный регресс tests/ - должен оставаться зелёным (TC-10). Регрессом считается: появление второй точки запуска LLM, - запуск LLM в детерминированном модуле, дрейф карты относительно .openclaw/agents/, изменение - рантайм-контрактов (STAGE_TRANSITIONS / QG_CHECKS / check_* / machine-verdict / схема БД). + должен оставаться зелёным (TC-10). Дискриминатор всех structural-тестов — "консультирует LLM", + а НЕ "спавнит subprocess": прочие subprocess (git/pytest/docker/ssh/сканеры/staging_check.py) явно + исключаются из матчинга, иначе тест выродился бы в подсчёт всех Popen. Регрессом считается: + появление второго ТРАНСПОРТА 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 не выдуманы). tests: - id: TC-01 type: unit - description: "Единственная точка запуска LLM: ровно одно место в src/** собирает/запускает Claude CLI (CLAUDE_BIN + --system-prompt + Popen/bash -c), и это launcher._spawn (FR-6a / AC-1)" + description: "Единственный ТРАНСПОРТ LLM-консультации: ровно одно место в src/** собирает/запускает Claude CLI (матчинг по совокупности признаков LLM-транспорта CLAUDE_BIN + --system-prompt + Popen/bash -c), и это launcher._spawn. Необходимое, но НЕ достаточное условие — дополняется TC-12 (отсутствие иного транспорта); сам по себе подсчёт _spawn не доказывает 'единственная LLM-консультация' (R4 / FR-6a / AC-1)" module: tests/test_llm_call_site_inventory.py expected: PASS - id: TC-02 type: unit - description: "Детерминированные модули без LLM: перечисленные leaf'ы (serial_gate, merge_gate, coverage_gate, security_gate, staging_verdict, review_parse, error_classifier, frontmatter, self_deploy, post_deploy, transition_lease, reconciler, job_reaper) не содержат запуска Claude CLI (FR-6b / AC-3)" + description: "Детерминированные модули без LLM-консультации: перечисленные leaf'ы (serial_gate, merge_gate, coverage_gate, security_gate, staging_verdict, review_parse, error_classifier, frontmatter, self_deploy, post_deploy, transition_lease, reconciler, job_reaper) не консультируют LLM (нет ни _spawn-транспорта, ни альтернативного по TC-12); их subprocess-вызовы git/pytest/docker/ssh/сканеров LLM-консультацией НЕ считаются — дискриминатор 'консультирует LLM', а не 'спавнит subprocess' (R4 / FR-6b / AC-3)" module: tests/test_llm_call_site_inventory.py expected: PASS @@ -54,7 +65,7 @@ tests: - id: TC-06 type: unit - description: "Детерминированные job-роли: launch_job перехватывает deploy-finalizer и post-deploy-monitor ДО _spawn (LLM не запускается) — эталон паттерна замены (FR-6e / AC-3)" + description: "Capability ≠ consultation: launch_job перехватывает deploy-finalizer и post-deploy-monitor ДО _spawn — job занимает слот агента, но LLM НЕ консультируется (процесс/слот существует, суждение не потребляется) — эталон паттерна замены и прямая иллюстрация R4-различия (FR-6e / AC-3)" module: tests/test_llm_call_site_inventory.py expected: PASS @@ -87,3 +98,9 @@ tests: description: "Анти-фабрикация follow-up ID (R3 / NFR-6 / AC-9): документы карты/roadmap НЕ содержат привязки кандидатов-замен к конкретным follow-up Plane-ID несуществующих work item (паттерн ORCH-1\\d\\d, не равный самому ORCH-118 и не присутствующий в docs/work-items/); кандидаты именуются по роли. Заменяет ошибочный mapping-тест R2, прибивавший карту к выдуманным ID." module: tests/test_llm_determinization_docs.py expected: PASS + + - id: TC-12 + type: unit + 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