From 3865b14a1c91baf54166f9fd0b364c45a593fd1b Mon Sep 17 00:00:00 2001 From: claude-bot Date: Tue, 16 Jun 2026 07:55:43 +0300 Subject: [PATCH] analyst(ET): auto-commit from analyst run_id=747 --- docs/work-items/ORCH-123/01-brd.md | 201 ++++++++++++++++++ docs/work-items/ORCH-123/02-trz.md | 136 ++++++++++++ .../ORCH-123/03-acceptance-criteria.md | 169 +++++++++++++++ docs/work-items/ORCH-123/04-test-plan.yaml | 108 ++++++++++ 4 files changed, 614 insertions(+) create mode 100644 docs/work-items/ORCH-123/01-brd.md create mode 100644 docs/work-items/ORCH-123/02-trz.md create mode 100644 docs/work-items/ORCH-123/03-acceptance-criteria.md create mode 100644 docs/work-items/ORCH-123/04-test-plan.yaml diff --git a/docs/work-items/ORCH-123/01-brd.md b/docs/work-items/ORCH-123/01-brd.md new file mode 100644 index 0000000..739c767 --- /dev/null +++ b/docs/work-items/ORCH-123/01-brd.md @@ -0,0 +1,201 @@ +--- +work_item: ORCH-123 +stage: analysis +author_agent: analyst +status: ready-for-review +created_at: 2026-06-16 +model_used: claude-opus-4-8 +escalate: full-cycle +--- + +# 01 — BRD / Bug-report: ORCH-123 — staging-runner assumes Docker CLI inside the orchestrator container and false-fails deploy-staging + +Work Item: **ORCH-123** · Repo: **orchestrator** · Стадия: analysis · Трек: **Bug → эскалация в full-cycle** + +> ⚠️ **`escalate: full-cycle` (ADR-001 D5 ORCH-019).** Метка задачи — `Bug`, но по сути это +> **архитектурный + safety-critical (self-hosting)** дефект: требуется **решение о стратегии +> исполнения** staging-сюиты из прод-окружения (host-side ssh / Docker SDK поверх смонтированного +> сокета / docker CLI в образе / выделенный hook-режим), и любой вариант с доступом к +> `docker.sock`/CLI из контейнера требует **явного security-review** (доступ к сокету = root-эквивалент +> на хосте). Это не «однострочная» правка кода — нужен ADR. Поэтому выпускается **полный** +> analysis-пакет (а не облегчённый bug-пакет). Оператор снимает багфикс-трек: +> `POST /bug-fast-track/escalate?work_item=ORCH-123` → задача пойдёт через стадию `architecture` +> (architect выпустит ADR для стратегии исполнения staging-runner). + +--- + +## 1. Бизнес-контекст и проблема + +### Симптом (наблюдаемое — установленный факт из прод-логов) +Задача **ORCH-116** (поверх раската ORCH-115) дошла до стадии `deploy-staging` с **зелёными +reviewer и tester**, но **детерминированный staging-runner упал ещё до запуска staging-сюиты**. +Запись из прод-логов: +``` +proc_group: spawn error for [docker, exec, orchestrator-staging, python3, +/repos/orchestrator/scripts/staging_check.py, ...]: [Errno 2] No such file or directory: docker +``` +Далее runner отработал свой инфра-DEFER **дважды**, исчерпал `staging_runner_infra_max_retries=2`, +записал `15-staging-log.md` с `staging_status: FAILED` и `stage_engine` **откатил ORCH-116 на +`development`** — как будто это дефект кода/тестов задачи, чем оно не является. + +### Причина симптома (установленный факт — верифицировано по коду и инфраструктуре) +ORCH-115 (`src/staging_runner.py`) исполняет staging-сюиту командой +`docker exec orchestrator-staging python3 .../staging_check.py …` **изнутри прод-контейнера +`orchestrator`** через `proc_group.run_in_process_group`. Прод-контейнер **не содержит docker CLI**: +- **`Dockerfile:11`** ставит `openssh-client git curl ca-certificates` — **бинаря `docker` нет**; + `python:3.12-slim` его тоже не содержит. Поэтому `subprocess.Popen(["docker", …])` падает + `FileNotFoundError: [Errno 2] No such file or directory: docker`. +- **`docker-compose.yml:40`** монтирует `/var/run/docker.sock` (rw) в сервис `orchestrator` и + добавляет `group_add: ${ORCH_DOCKER_GID}`. То есть **сокет в контейнере есть, а CLI-бинаря, который + бы им воспользовался, — нет**. (Это важное уточнение корня: проблема не в доступе к Docker, а в + **отсутствии исполняемого клиента** в образе.) + +Цепочка отказа в коде: +`build_staging_command()` (`staging_runner.py:154`) → `["docker","exec",…]` → `run_staging_suite()` → +`proc_group.run_in_process_group` → `Popen` ⇒ `OSError` ⇒ `ProcResult(returncode=None, timed_out=False)` +⇒ в `run_staging_gate` (`staging_runner.py:528`) `suite_ran = (returncode is not None) and (not +timed_out)` = **False** ⇒ ветка **tool-error** ⇒ `_handle_tool_error` (`staging_runner.py:415`): +инфра-DEFER ×2 → бюджет исчерпан → `write_staging_log(..., "FAILED", tool_error=True)` + +`_advance(...)` ⇒ существующий откат `deploy-staging → development`. + +### Корневая классификационная ошибка +Двухуровневый исход ORCH-115 (D5) различает только **«сюита исполнилась» vs «сюита не исполнилась +(транзиентная инфра)»**. Отсутствие docker CLI — это **детерминированный, постоянный дефект +окружения**, а не транзиентная икота: ретраи бессмысленны (каждая попытка падает идентично), а +**терминальный исход — откат на `development` с расходом developer-retry** — вводит в заблуждение +(винит код/разработчика задачи за environment-проблему раннера). Так до фикса **любая** self-hosting +задача, доходящая до `deploy-staging`, обречена на ложный откат. + +### Локализация (анализ — куда смотреть архитектору/разработчику) + +**Установленный факт о топологии исполнения (CLAUDE.md / `docs/operations/INFRA.md` / код):** +прод-деплой (ORCH-036, `src/self_deploy.py`) **уже решил ровно эту проблему** и НЕ запускает docker +изнутри контейнера. Он исполняет хост-хук **на хосте** через `ssh + setsid` (detached): +`build_deploy_command` (`self_deploy.py:220`) формирует `ssh @ 'setsid bash -c "… bash + --deploy …"'`, где `deploy_ssh_host=127.0.0.1`, ssh-ключ смонтирован, `openssh-client` стоит в +образе (`Dockerfile:11`). Хост-хук `scripts/orchestrator-deploy-hook.sh` уже выполняет `docker +compose …` / `docker tag` / **`docker exec "$STAGING_CONTAINER" python3 staging_check.py`** +(`--build-staging`, строки 197/261) — **на хосте**, где docker CLI есть. + +**Вывод:** ORCH-115 при замене LLM-деплойера детерминированным раннером **отклонился** от уже +установленного паттерна «docker-операции исполняются host-side, не внутри app-контейнера». Дефект — +в **стратегии исполнения** `staging_runner` (где/как запускается staging-сюита), а не в гейте +`check_staging_status` и не в контракте `15-staging-log.md`. Поэтому фикс должен **восстановить +работоспособную стратегию исполнения** staging-сюиты в проде, не завися от недоступного внутри +контейнера docker CLI, и **перестать классифицировать постоянный environment-дефект как код-фейл**. + +> 🔎 **Точка для проверки на стадии architecture (не факт, требует верификации):** как +> staging-сюита исполнялась **до** ORCH-115 LLM-деплойером — действительно через `docker exec` из +> контейнера (тогда путь всегда был сломан и LLM-гейт был «бумажным»), или иным механизмом. Это +> определяет, «сломал ли ORCH-115 рабочий путь» или «сделал давний дефект детерминированным и +> видимым». На фикс выбор стратегии это не меняет, но влияет на формулировку регресса. + +## 2. Объём (scope) + +### В объёме +- Исправить **стратегию исполнения staging-runner** так, чтобы `deploy-staging` для self-hosting + `orchestrator` **проходил в проде**, не завися от недоступного внутри прод-контейнера docker CLI. +- Гарантировать, что **tool-error / environment-дефект** (в частности отсутствие исполняемого + `docker`/невозможность запустить сюиту по причине окружения) **НЕ приводит к вводящему в + заблуждение откату `deploy-staging → development`** как код-фейлу и **не жжёт** developer-retry; + постоянный environment-дефект должен быть отличим от транзиентной инфры и от настоящего код-фейла. +- Добавить **prod-like регресс/preflight**, который ловит «нет исполняемого/стратегия неработоспособна» + **до раската** (а не постфактум, ложным откатом реальной задачи). +- Задокументировать **границу исполнения** (`docs/operations/INFRA.md` + `docs/architecture/README.md`): + где и как staging-сюита исполняется относительно прод-контейнера, какие исполняемые/сокеты доступны. + +### Вне объёма +- ❌ Изменение гейта `check_staging_status` / `_parse_staging_status`, контракта `15-staging-log.md` + (`staging_status:`), `STAGE_TRANSITIONS`, machine-verdict ключей, схемы БД — **байт-в-байт прежние** + (ORCH-115 NFR-1: меняется **продюсер/стратегия исполнения** артефакта, не гейт). +- ❌ Изменение содержимого/логики самой `scripts/staging_check.py` (тулинг сюиты корректен; меняется + лишь **как/откуда** её запускают). +- ❌ Откат ORCH-115 (детерминизация staging корректна по замыслу; чиним способ исполнения). +- ❌ Выбор конкретного механизма (ssh host-side / Docker SDK поверх сокета / docker CLI в образе / + выделенный hook-режим) — это **зона архитектора** (`06-adr/`), здесь — только требования и ограничения. +- ❌ Изменение прод-деплой-пути (ORCH-036 self_deploy) сверх возможного переиспользования его + ssh-механизма; happy-path прод-деплоя не трогается. + +## 3. Заинтересованные стороны +- **Заказчик/оператор (Слава)** — страдает от ложных откатов и ручного разруливания залипших задач; + принимает результат. +- **Self-hosting конвейер `orchestrator`** — прямой потребитель: без фикса **ни одна** self-hosting + задача не проходит `deploy-staging`. +- **Все проекты на общем инстансе (enduro-trails)** — косвенно: застрявшие/откатываемые self-hosting + задачи занимают слоты и внимание оператора на общей очереди. + +## 4. Бизнес-требования (BR) +- **BR-1** — Для self-hosting `orchestrator` стадия `deploy-staging` (детерминированный staging-runner) + **исполняет staging-сюиту и проходит в проде** без зависимости от docker CLI, отсутствующего внутри + прод-контейнера. Задача уровня ORCH-116 (reviewer/tester зелёные, код корректен) **доходит до + `deploy`**, а не откатывается. +- **BR-2** — **Tool-error / environment-дефект ≠ код-фейл.** Невозможность исполнить сюиту по причине + окружения (нет исполняемого, недоступна стратегия) **не должна** завершаться откатом + `deploy-staging → development` как кодовой ошибкой и **не должна** инкрементировать developer-retry; + такой исход должен быть **отдельным, отличимым** (хольд/алерт оператору об инфра/environment-сбое). +- **BR-3** — **Настоящий код-фейл сохраняется (анти-over-tolerance).** Если сюита **реально + исполнилась** и упала (exit≠0), поведение — **прежний** откат `deploy-staging → development` + + developer-retry (BR-2 не должно маскировать настоящие провалы; ср. ORCH-110 BR-6). +- **BR-4** — **Prod-like preflight/регресс.** Должен существовать механизм, ловящий «исполняемое + отсутствует / стратегия неработоспособна» **до раската** (preflight на старте/в smoke-проверке) и + **тест**, воспроизводящий «docker CLI отсутствует в контейнере» (красный до фикса, зелёный после). +- **BR-5** — **Граница исполнения задокументирована.** `docs/operations/INFRA.md` (и + `docs/architecture/README.md`) явно описывают, где/как исполняется staging-сюита относительно + прод-контейнера, какие исполняемые/сокеты доступны, и почему docker-операции идут так, а не «изнутри + app-контейнера». +- **BR-6** — **Наблюдаемость.** Различие «environment/tool-error» vs «код-фейл» видно в логе + (структурная запись), Telegram-алерте (кликабельный номер) и read-only блоке `staging_runner` в + `GET /queue`. + +## 5. Нефункциональные требования (NFR) +- **NFR-1 (нулевая регрессия конвейера)** — `STAGE_TRANSITIONS` / реестр `QG_CHECKS` / имена и + семантика `check_*` (в т.ч. `check_staging_status`/`_parse_staging_status`) / machine-verdict ключи + (`staging_status:`/`deploy_status:`/…) / схема БД — **байт-в-байт не тронуты** (фикс — стратегия + исполнения продюсера, не гейт и не стадия). +- **NFR-2 (self-hosting safety)** — путь исполнения staging-runner **никогда**: не рестартит прод + `orchestrator` (8500), не делает `docker compose up orchestrator`/`--build` прода, не force-push, не + пишет в `main`, не редактирует `.env`. Только запускает staging-сюиту (8501) и пишет лог + (инвариант ORCH-115 BR-7/AC-8 сохраняется). +- **NFR-3 (security — если выбран socket/CLI-в-контейнере)** — любой вариант с прямым использованием + `docker.sock`/CLI из контейнера = root-эквивалент на хосте → **обязателен явный security-review** в + ADR (поверхность атаки, ограничение прав, :ro где возможно). Вариант host-side ssh должен + переиспользовать уже существующий доверенный механизм (ORCH-036) без расширения привилегий. +- **NFR-4 (обратимость / kill-switch)** — поведение под существующим `staging_runner_enabled` + (+ при необходимости — новым флагом выбора стратегии); выключенный kill-switch → прежний LLM-деплойер + через `_spawn` **байт-в-байт** (ORCH-115 fail-safe сохраняется). +- **NFR-5 (надёжность)** — never-raise / fail-safe / restart-safe (по образцу leaf'ов + `staging_runner`/`self_deploy`/`proc_group`); очередь репо **никогда не клинится**; тайм-аут сюиты не + растёт сверх бюджета, держащего сквозной инвариант reaper (ORCH-065/109/110). +- **NFR-6 (область)** — изменение скоупится на self-hosting (`orchestrator`, единственный с staging + 8501); поведение для прочих репо/синхронного LLM-деплоя — не ухудшается (`applies(repo)` первым). + +## 6. Допущения и ограничения +- Прод и staging контейнеры запущены на **одном хосте**; `/var/run/docker.sock` доступен на хосте, + где docker CLI установлен; ssh на `127.0.0.1` под смонтированным ключом — рабочий канал (его уже + использует ORCH-036 self-deploy). +- `staging_check.py` исполняется **внутри контейнера `orchestrator-staging`** (там есть python3 и + приложение 8501) — это контракт сюиты; меняется только то, **кто инициирует** `docker exec` (хост + vs прод-контейнер) или **как** (CLI vs SDK поверх сокета). +- Источник истины «применять ли детерминированный runner» — `staging_runner.applies(repo)` (ORCH-115); + фикс не меняет его семантику. +- Конкретный механизм исполнения (host-side ssh / Docker SDK поверх сокета / docker CLI в образе / + выделенный режим хука) — **открытый вопрос для архитектуры**, решается в `06-adr/` с security-review. + +## 7. Критерии успеха +Self-hosting задача уровня ORCH-116 проходит `deploy-staging` в проде (staging-сюита реально +исполняется, вердикт маппится из exit-кода); environment/tool-error **не** даёт ложного код-фейл-отката +и не жжёт developer-retry; настоящий код-фейл по-прежнему откатывает на `development`; существует +prod-like preflight + обязательный регресс-тест «нет docker CLI в контейнере» (**красный до фикса, +зелёный после**); граница исполнения задокументирована; гейт/контракт/`STAGE_TRANSITIONS`/схема БД — без +регресса; полный `pytest tests/ -q` зелёный. Детальные PASS/FAIL — `03-acceptance-criteria.md`. + +## 8. Риски +- **Security (socket/CLI в контейнере):** прямой доступ к `docker.sock` из app-контейнера = эскалация + до root на хосте → ADR обязан взвесить поверхность атаки против host-side ssh-варианта. +- **Over-tolerance:** слишком широкая трактовка «environment-дефекта» может замаскировать настоящие + код-фейлы/реальные сбои staging-стенда (митигация — BR-3; ср. инцидент ORCH-110). +- **Кросс-каттинг:** ORCH-115 (staging-runner, прямой объект), ORCH-036 (self_deploy ssh-механизм — + потенциально переиспользуемый), ORCH-110 (proc_group tree-kill + infra-tolerance паттерн), ORCH-058 + (`--build-staging` host-side docker exec — прецедент), ORCH-101 (host-параметризация). Правки + маркированных блоков сверять с их `06-adr/` (CLAUDE.md §9). Детали/митигации — `10-tech-risks.md` + (заполняет архитектор). diff --git a/docs/work-items/ORCH-123/02-trz.md b/docs/work-items/ORCH-123/02-trz.md new file mode 100644 index 0000000..a78a7c8 --- /dev/null +++ b/docs/work-items/ORCH-123/02-trz.md @@ -0,0 +1,136 @@ +--- +work_item: ORCH-123 +stage: analysis +author_agent: analyst +status: ready-for-review +created_at: 2026-06-16 +model_used: claude-opus-4-8 +--- + +# 02 — ТЗ (TRZ): ORCH-123 — staging-runner execution strategy must not depend on Docker CLI inside the app container + +Work Item: **ORCH-123** · Repo: **orchestrator** · Стадия: analysis + +> ТЗ описывает **требования и ограничения к реализации**, выведенные из BRD и фактического кода. +> Архитектурное **решение** (какую стратегию исполнения выбрать: host-side ssh / Docker SDK поверх +> сокета / docker CLI в образе / выделенный hook-режим, + security-review) — задача архитектора +> (`06-adr/`), т.к. задача эскалирована в full-cycle (`01-brd.md` → `escalate: full-cycle`). + +## 1. Сводка изменения +Восстановить работоспособную стратегию исполнения staging-сюиты для self-hosting `orchestrator` на +стадии `deploy-staging`, не завися от docker CLI, **отсутствующего внутри прод-контейнера** +(`Dockerfile:11` ставит `openssh-client git curl ca-certificates`, не docker; `docker.sock` +смонтирован — `docker-compose.yml:40` — но клиента нет). Сегодня `staging_runner.build_staging_command` +(`src/staging_runner.py:154`) формирует `["docker","exec","orchestrator-staging",…]` и запускает её +**изнутри** прод-контейнера через `proc_group` → `Popen` падает `FileNotFoundError` → ветка tool-error +→ инфра-DEFER×2 → fail-closed `FAILED` → откат `deploy-staging → development`. Требуется: (а) исполнять +сюиту так, чтобы она реально запускалась в проде (паттерн host-side, уже применённый прод-деплоем +ORCH-036 / `--build-staging`); (б) **различать** постоянный environment/tool-error от настоящего +код-фейла и не делать вводящего в заблуждение код-фейл-отката; (в) prod-like preflight + регресс; +(г) документировать границу исполнения. + +## 2. Задействованные модули / пути +| Путь | Действие | Примечание | +|------|----------|-----------| +| `src/staging_runner.py` | **изменить** | `build_staging_command`/`run_staging_suite` — точка, где сюита запускается «изнутри контейнера» (корень дефекта, FR-1); классификация tool-error vs environment-дефект в `run_staging_gate`/`_handle_tool_error` (FR-2) | +| `src/self_deploy.py` | возможно переиспользовать | `build_deploy_command`/`initiate_deploy` — рабочий host-side ssh+setsid механизм (ORCH-036); кандидат на общий ssh-хелпер исполнения host-side команды (решает архитектор) | +| `scripts/orchestrator-deploy-hook.sh` | возможно изменить | `--build-staging` уже делает host-side `docker exec "$STAGING_CONTAINER" python3 staging_check.py` (строки 197/261) — прецедент/возможная точка выделенного staging-режима | +| `Dockerfile` | возможно изменить | строка 11 — если выбран вариант «docker CLI в образе» (тогда + security-обоснование) | +| `docker-compose.yml` | возможно изменить | строка 40 — `docker.sock` уже смонтирован; если выбран socket/SDK-вариант, зафиксировать права (`:ro` где возможно) | +| `src/proc_group.py` | возможно изменить | `run_in_process_group` уже корректно деградирует spawn-error в `ProcResult(returncode=None)` — кандидат на preflight «исполняемое существует» (FR-4) | +| `src/config.py` | возможно изменить | существующие `staging_runner_*`; при необходимости — флаг выбора/режима стратегии (FR-5), дефолт = боевое | +| `docs/operations/INFRA.md` | **изменить** | граница исполнения staging-сюиты относительно прод-контейнера (FR-6 / BR-5) | +| `docs/architecture/README.md` | **изменить** | описать стратегию исполнения staging-runner (FR-6 / BR-5) | +| `CHANGELOG.md`, `CLAUDE.md` | изменить | docs = golden source (CLAUDE.md §2); раздел ORCH-115 дополнить фиксом исполнения | +| `tests/test_orch115_staging_runner.py` / `tests/test_orch123_staging_runner_exec.py` | **создать/расширить** | регресс «docker CLI отсутствует» + классификация + preflight (`04-test-plan.yaml`) | + +## 3. Функциональные требования + +### FR-1 — Работоспособная стратегия исполнения staging-сюиты в проде (BR-1) +- На стадии `deploy-staging` для self-hosting `orchestrator` staging-сюита (`staging_check.py` внутри + `orchestrator-staging`) **должна реально исполняться** в боевом окружении, **не завися** от наличия + бинаря `docker` внутри прод-контейнера `orchestrator`. +- Стратегия исполнения — **выбор архитектора** (ADR), из перечня BRD §2: host-side через + существующий ssh+setsid механизм (ORCH-036, `deploy_ssh_host=127.0.0.1`, ssh-ключ смонтирован, + `openssh-client` в образе) **либо** Docker SDK/`docker.sock` (уже смонтирован, + security-review) + **либо** docker CLI в образе **либо** выделенный режим хука. ТЗ **не** прескриптивно — фиксирует + лишь требуемый инвариант «сюита исполняется». +- Команда/контракт сюиты (`python3 staging_check.py --base-url http://localhost: + --mode stub`, host-specifics из config — ORCH-101) сохраняются; меняется **инициатор/канал** + запуска, не сама сюита. + +### FR-2 — Environment/tool-error ≠ код-фейл (BR-2, BR-3) +- Невозможность исполнить сюиту по причине **окружения** (нет исполняемого `docker`/SDK недоступен/ + стратегия неработоспособна) **не должна** завершаться откатом `deploy-staging → development` как + кодовой ошибкой и **не должна** инкрементировать developer-retry. Текущий терминальный путь + `_handle_tool_error` → `write_staging_log("FAILED") + _advance` (= откат) для **постоянного** + environment-дефекта вводит в заблуждение (см. BRD §1) и должен быть заменён на **отличимый + инфра/environment-исход** (хольд на `deploy-staging` + алерт оператору, по образцу + `merge_gate` infra-tolerance ORCH-110: задача остаётся на стадии, без developer-retry). +- **Анти-over-tolerance (BR-3):** если сюита **реально исполнилась** (получен exit-код) и упала + (exit≠0), исход — **прежний** откат `deploy-staging → development` + developer-retry (контракт + `staging_runner` D5 для «сюита исполнилась» сохраняется байт-в-байт). +- Различение «сюита исполнилась / постоянный environment-дефект / транзиентная инфра» — + детерминированная классификация (по образцу `merge_gate.classify_retest_failure`, ORCH-110 D2); + никаких догадок — постоянный environment-дефект (spawn-error «исполняемое не найдено») трактуется + как НЕ-транзиентный и НЕ как код-фейл. + +### FR-3 — Анти-бессмысленный-ретрай (BR-2) +- При постоянном environment-дефекте бессмысленно крутить инфра-DEFER ×N (каждая попытка падает + идентично, жжёт время/слот) и затем ложно откатывать. Допустимо: немедленный отличимый + инфра-хольд+алерт (без отката, без developer-retry) **или** preflight, не дающий задаче войти в + ложный путь. Конкретику решает архитектор; инвариант — **не** оканчиваться код-фейл-откатом. + +### FR-4 — Prod-like preflight (BR-4) +- Должен существовать механизм, ловящий «стратегия исполнения неработоспособна (нет исполняемого/ + канал недоступен)» **до раската** — preflight на старте сервиса и/или в `scripts/staging_check.py`/ + smoke (`scripts/staging_check.py` Block …) / в `should_intercept`/`applies`. Условие, проявившееся + в инциденте (нет бинаря `docker` там, где runner его зовёт), должно детектироваться **до** того, + как реальная задача ложно откатится. +- Реализационно preflight никак не должен трогать гейты/стадии; never-raise; область — self-hosting. + +### FR-5 — Условность / kill-switch (BR-1, NFR-4, NFR-6) +- Поведение под существующим `staging_runner_enabled` (выключен → прежний LLM-деплойер через `_spawn` + байт-в-байт) + `staging_runner_repos` (область). При необходимости нового флага выбора/режима + стратегии — env `ORCH_*`, **дефолт = боевое** (паттерн ORCH-101); откат = выставить флаг(и) → + поведение до ORCH-123. `applies(repo)` (локально, без сети) проверяется первым. + +### FR-6 — Документирование границы исполнения (BR-5) +- `docs/operations/INFRA.md` + `docs/architecture/README.md`: явно зафиксировать, что docker-операции + для self-hosting исполняются **host-side** (а не из app-контейнера, где нет docker CLI), какие + исполняемые/сокеты доступны прод-контейнеру (`openssh-client`/`git`/`curl`; `docker.sock` смонтирован, + CLI — нет), и как именно staging-runner запускает сюиту после фикса. + +## 4. Изменения API +**Нет** обязательных. Существующий read-only блок `staging_runner` в `GET /queue` (ORCH-115) +**дополняется** различием environment/tool-error vs код-фейл и (опц.) статусом preflight; новых +управляющих эндпоинтов не требуется (на усмотрение архитектора — опц. read-only preflight-поле). + +## 5. Изменения схемы БД +**Нет.** Restart-safe состояние (счётчик инфра-ретраев) уже ведётся маркером в `task_content` +(`_INFRA_RETRY_MARKER`, ORCH-115) — без миграции. Новой таблицы/колонки не требуется. + +## 6. Требования к новым/изменённым QG checks +**Нет.** Это **не** Quality Gate и **не** стадия — это стратегия исполнения продюсера артефакта +`15-staging-log.md`. `STAGE_TRANSITIONS` / реестр `QG_CHECKS` / имена и семантика `check_*` +(`check_staging_status`/`_parse_staging_status`) / machine-verdict ключи (`staging_status:`/ +`deploy_status:`/…) / схема БД — **байт-в-байт не тронуты** (NFR-1; зеркало инварианта ORCH-115 NFR-1). + +## 7. Совместимость / регресс +- **Обратная совместимость:** `staging_runner_enabled=False` → стадию `deploy-staging` снова ведёт + LLM-`deployer` через `_spawn` **байт-в-байт**; для прочих репо `applies==False` → no-op (нулевая + регрессия enduro). +- **Контракт артефакта:** `15-staging-log.md` (`staging_status:` + 52c-схема, `author_agent: + staging-runner`/`model_used: n/a`) сохраняется; вердикт по-прежнему читается ТОЛЬКО из frontmatter. +- **Self-hosting инварианты (NFR-2):** не рестартить прод 8500, не `docker compose up orchestrator`/ + `--build` прода, не force-push, не писать в `main`, не править `.env` — сохраняются (ORCH-115 BR-7). +- **Security (NFR-3):** любой socket/CLI-в-контейнере вариант проходит security-review в ADR; host-side + ssh-вариант переиспользует доверенный ORCH-036 механизм без расширения привилегий. +- **Бюджеты/инварианты:** тайм-аут сюиты не растёт сверх `staging_runner_timeout_s` (держит сквозной + reaper-инвариант ORCH-065/109/110); proc_group tree-kill (ORCH-110) сохраняется. +- **Артефакты pipeline:** обычные docs work item (`01..04` этой задачи; `06-adr/` на стадии + architecture после эскалации; `15-staging-log.md` при прогоне). Новых pipeline-артефактов задача не + вводит. +- **Трассировка (CLAUDE.md §9 / ORCH-078):** правки маркированных блоков — ORCH-115 (staging-runner, + прямой объект), ORCH-036 (self_deploy ssh), ORCH-110 (proc_group/infra-tolerance), ORCH-058 + (`--build-staging`) — сверять с их `06-adr/` перед изменением; инварианты не ломать. diff --git a/docs/work-items/ORCH-123/03-acceptance-criteria.md b/docs/work-items/ORCH-123/03-acceptance-criteria.md new file mode 100644 index 0000000..e88f649 --- /dev/null +++ b/docs/work-items/ORCH-123/03-acceptance-criteria.md @@ -0,0 +1,169 @@ +--- +work_item: ORCH-123 +stage: analysis +author_agent: analyst +status: ready-for-review +created_at: 2026-06-16 +model_used: claude-opus-4-8 +--- + +# 03 — Критерии приёмки (Acceptance Criteria): ORCH-123 — staging-runner execution strategy fix + +Work Item: **ORCH-123** · Repo: **orchestrator** · Стадия: analysis + +Формат: каждый критерий имеет **PASS** (что должно быть истинно для приёмки) и **FAIL** (что +считается провалом). Любой машинный/ручной reviewer проверяет их буквально по файлам репозитория. +Критерии **механизм-агностичны** (конкретную стратегию выбирает архитектор), проверяют **требуемый +инвариант**, а не способ реализации. + +--- + +## AC-1 — staging-сюита реально исполняется в проде (без docker CLI в контейнере) + +**Условие:** Для self-hosting `orchestrator` на `deploy-staging` staging-runner запускает +`staging_check.py` так, что сюита **исполняется** в боевом окружении, не завися от бинаря `docker` +внутри прод-контейнера. +- **PASS:** Стратегия исполнения staging-сюиты не требует наличия `docker` CLI **внутри** + прод-контейнера `orchestrator` (исполняется host-side через существующий доверенный канал, либо + через смонтированный сокет/SDK с security-обоснованием, либо CLI добавлен в образ — на выбор ADR); + тест воспроизводит окружение «нет `docker` в контейнере» и подтверждает, что сюита **исполняется** + (а не падает spawn-error'ом). +- **FAIL:** staging-runner по-прежнему вызывает `docker exec` напрямую из прод-контейнера без + доступного исполняемого/канала → `proc_group` spawn-error → tool-error. + +--- + +## AC-2 — ORCH-116-подобная задача проходит `deploy-staging` + +**Условие:** Задача с зелёными reviewer/tester и корректным кодом доходит до `deploy`, а не +откатывается на `development` из-за раннера. +- **PASS:** При успешной staging-сюите (exit 0) → `staging_status: SUCCESS` → штатное продвижение по + под-гейтам ребра `deploy-staging → deploy` (контракт не тронут); ORCH-116-подобный сценарий в + тесте/симуляции достигает `deploy`. +- **FAIL:** Корректная задача откатывается на `development` по причине раннера/окружения. + +--- + +## AC-3 — environment/tool-error НЕ даёт вводящего в заблуждение код-фейл-отката + +**Условие:** Невозможность исполнить сюиту по причине окружения (нет исполняемого/канал недоступен) не +трактуется как код-фейл. +- **PASS:** environment/tool-error → **отличимый** инфра/environment-исход (задача **не** откатывается + на `development` как код-фейл; developer-retry **не** инкрементируется; оператору идёт алерт «инфра/ + окружение, НЕ дефект кода»). Подтверждено тестом: при недоступном исполняемом задача **не** уходит в + `development` и счётчик developer-retry не растёт. +- **FAIL:** environment-дефект завершается `write_staging_log("FAILED") + advance` (откат на + `development`) и/или жжёт developer-retry. + +--- + +## AC-4 — настоящий код-фейл по-прежнему откатывает (анти-over-tolerance) + +**Условие:** Если сюита **реально исполнилась** и упала (exit≠0), поведение не ослаблено. +- **PASS:** exit≠0 при исполнившейся сюите → `staging_status: FAILED` → **прежний** откат + `deploy-staging → development` + developer-retry (байт-в-байт контракт ORCH-115 для «сюита + исполнилась»). Тест подтверждает, что реальный фейл сюиты НЕ маскируется как «инфра». +- **FAIL:** Настоящий код-фейл сюиты трактуется как environment/инфра и не откатывает на `development`. + +--- + +## AC-5 — prod-like preflight ловит «исполняемое/стратегия неработоспособна» до раската + +**Условие:** Условие инцидента детектируется заранее, а не ложным откатом реальной задачи. +- **PASS:** Существует preflight (на старте сервиса и/или в smoke/`staging_check.py`/`applies`/ + `should_intercept`), который при неработоспособной стратегии исполнения сигнализирует (лог/алерт/ + отметка) **до** того, как задача дойдёт до ложного отката; есть тест, симулирующий «нет `docker` в + контейнере», и он проверяет именно это. +- **FAIL:** Нет механизма раннего детекта; «исполняемое отсутствует» обнаруживается только постфактум + откатом реальной задачи. + +--- + +## AC-6 — обязательный регресс-тест (red → green) + +**Условие:** Инцидент ORCH-116 воспроизводится тестом. +- **PASS:** Есть тест, моделирующий исполнение staging-сюиты в окружении **без `docker` CLI в + контейнере**; он **красный на коде до фикса** (воспроизводит spawn-error → tool-error → ложный + откат/false-FAILED) и **зелёный после фикса** (сюита исполняется / environment-дефект не даёт + код-фейл-отката). +- **FAIL:** Регресс-теста нет, либо он зелёный и до фикса (значит, не воспроизводит дефект). + +--- + +## AC-7 — гейт/контракт/стадии — без регресса (NFR-1) + +**Условие:** Меняется продюсер/стратегия исполнения, не гейт. +- **PASS:** `STAGE_TRANSITIONS`, реестр `QG_CHECKS`, имена и семантика `check_staging_status`/ + `_parse_staging_status`, machine-verdict ключи (`staging_status:`/`deploy_status:`/…), схема БД — + **байт-в-байт прежние** (структурный анти-регресс-тест зелёный); `15-staging-log.md` сохраняет + `staging_status:`-frontmatter + 52c-схему (`author_agent: staging-runner`/`model_used: n/a`). +- **FAIL:** Любое из перечисленного изменено (имя/регистр/семантика/значение) либо введена миграция БД. + +--- + +## AC-8 — self-hosting safety (NFR-2) + +**Условие:** Путь исполнения staging-runner безопасен для общего прод-инстанса. +- **PASS:** Путь **никогда** не рестартит прод `orchestrator` (8500), не делает `docker compose up + orchestrator`/`--build` прода, не force-push, не пишет в `main`, не правит `.env`; оперирует только + запуском staging-сюиты (8501) и записью лога (статический/поведенческий ассерт зелёный). +- **FAIL:** Путь способен затронуть прод-контейнер/`main`/`.env`/сделать force-push. + +--- + +## AC-9 — security-review для socket/CLI-вариантов (NFR-3) + +**Условие:** Если выбран вариант с прямым `docker.sock`/CLI из контейнера. +- **PASS:** ADR (`06-adr/`) содержит явный security-разбор (поверхность атаки `docker.sock` = + root-эквивалент, ограничение прав, `:ro` где возможно) **либо** выбран host-side ssh-вариант, + переиспользующий доверенный ORCH-036 механизм без расширения привилегий — и это зафиксировано. +- **FAIL:** Введён доступ к `docker.sock`/CLI из контейнера без security-обоснования в ADR. + +--- + +## AC-10 — kill-switch / область (NFR-4, NFR-6) + +**Условие:** Обратимость и скоуп сохранены. +- **PASS:** `staging_runner_enabled=False` → `deploy-staging` снова через LLM-`deployer` (`_spawn`) + байт-в-байт; `applies(repo)`: self-hosting `orchestrator` — real, прочие репо — no-op; любой новый + флаг стратегии имеет дефолт = боевое (откат флагом → поведение до ORCH-123). Подтверждено тестом. +- **FAIL:** Выключение kill-switch не возвращает прежний путь, либо затронуты прочие репо. + +--- + +## AC-11 — never-raise / очередь не клинится (NFR-5) + +**Условие:** Сбой стратегии исполнения не роняет worker и не клинит очередь. +- **PASS:** Любая ошибка исполнения/классификации изолирована (never-raise); задача либо штатно + продвигается, либо остаётся на `deploy-staging` для reconciler/reaper без вечного залипания; полный + `pytest tests/ -q` зелёный. +- **FAIL:** Ошибка исполнения роняет worker / задача залипает на `deploy-staging` навсегда / тесты + красные. + +--- + +## AC-12 — документация границы исполнения (BR-5) + +**Условие:** Граница исполнения staging-сюиты задокументирована. +- **PASS:** `docs/operations/INFRA.md` и `docs/architecture/README.md` содержат явное описание: где/как + исполняется staging-сюита относительно прод-контейнера, какие исполняемые/сокеты доступны, почему + docker-операции идут host-side (структурная сверка зелёная); `CHANGELOG.md`/`CLAUDE.md` обновлены. +- **FAIL:** Граница исполнения нигде не описана, или docs противоречат фактическому коду. + +--- + +## Сводная матрица AC ↔ FR/BR +| AC | Покрывает | +|----|-----------| +| AC-1 | BR-1 / FR-1 | +| AC-2 | BR-1 / FR-1 | +| AC-3 | BR-2 / FR-2, FR-3 | +| AC-4 | BR-3 / FR-2 | +| AC-5 | BR-4 / FR-4 | +| AC-6 | BR-4 / FR-1, FR-2 | +| AC-7 | NFR-1 / FR-(6) | +| AC-8 | NFR-2 | +| AC-9 | NFR-3 | +| AC-10 | NFR-4, NFR-6 / FR-5 | +| AC-11 | NFR-5 | +| AC-12 | BR-5 / FR-6 | diff --git a/docs/work-items/ORCH-123/04-test-plan.yaml b/docs/work-items/ORCH-123/04-test-plan.yaml new file mode 100644 index 0000000..2212820 --- /dev/null +++ b/docs/work-items/ORCH-123/04-test-plan.yaml @@ -0,0 +1,108 @@ +work_item: ORCH-123 +stage: analysis +author_agent: analyst +status: ready-for-review +created_at: 2026-06-16 +model_used: claude-opus-4-8 +title: "Staging-runner execution strategy: docker CLI absent in container must not false-fail deploy-staging" +framework: pytest +scope: > + Покрывается: исполнение staging-сюиты для self-hosting orchestrator на deploy-staging без + зависимости от docker CLI внутри прод-контейнера; различение environment/tool-error vs настоящего + код-фейла; анти-over-tolerance (реальный фейл сюиты по-прежнему откатывает); prod-like preflight; + сохранность контракта гейта/артефакта/STAGE_TRANSITIONS/схемы БД; self-hosting safety; kill-switch/ + область; never-raise. Вне покрытия: логика самой staging_check.py (не трогается), выбор конкретного + механизма исполнения (host-side ssh / docker.sock+SDK / CLI в образе / hook-режим — решает архитектор), + изменения STAGE_TRANSITIONS/QG_CHECKS/схемы БД (их нет). +notes: > + TC-01 — ОБЯЗАТЕЛЬНЫЙ регресс-тест воспроизведения инцидента ORCH-116/ORCH-115: КРАСНЫЙ до фикса, + ЗЕЛЁНЫЙ после. Тесты механизм-агностичны: проверяют ТРЕБУЕМЫЙ ИНВАРИАНТ (сюита исполняется; + environment-дефект не даёт код-фейл-отката; настоящий фейл откатывает), а не конкретную стратегию. + Окружение «нет docker CLI» моделировать без сети/прода/ssh (монки-патч PATH/spawn-функции + proc_group, либо изоляция argv staging_runner). Точные имена тест-функций уточнит разработчик после + выбора механизма архитектором. Полный регресс `pytest tests/ -q` обязан оставаться зелёным (NFR-1). + +tests: + - id: TC-01 + type: integration + description: "РЕГРЕСС (обязательный, red→green): исполнение staging-сюиты в окружении БЕЗ docker CLI в прод-контейнере. До фикса воспроизводит proc_group spawn-error '[Errno 2] No such file or directory: docker' → tool-error → инфра-DEFER×2 → ложный FAILED-откат на development. После фикса сюита ИСПОЛНЯЕТСЯ (или environment-дефект не даёт код-фейл-отката). Красный до фикса." + module: tests/test_orch123_staging_runner_exec.py + expected: PASS + + - id: TC-02 + type: unit + description: "Стратегия исполнения не зависит от docker CLI внутри прод-контейнера: argv/канал, формируемый staging-runner, не требует наличия бинаря `docker` на PATH контейнера (host-side / socket-SDK / CLI-в-образе — в зависимости от выбора ADR; ассерт проверяет инвариант, а не конкретный механизм)." + module: tests/test_orch123_staging_runner_exec.py + expected: PASS + + - id: TC-03 + type: integration + description: "environment/tool-error НЕ даёт код-фейл-отката (AC-3): при неработоспособной стратегии (исполняемое отсутствует) задача НЕ переходит deploy-staging → development, developer-retry НЕ инкрементируется, идёт отличимый инфра/environment-алерт ('НЕ дефект кода')." + module: tests/test_orch123_staging_runner_exec.py + expected: PASS + + - id: TC-04 + type: unit + description: "Анти-over-tolerance (AC-4): сюита РЕАЛЬНО исполнилась (получен exit-код) и упала (exit≠0) → staging_status: FAILED → ПРЕЖНИЙ откат deploy-staging → development + developer-retry. Настоящий фейл сюиты НЕ маскируется как 'инфра'." + module: tests/test_orch123_staging_runner_exec.py + expected: PASS + + - id: TC-05 + type: unit + description: "Happy-path: сюита исполнилась, exit 0 → staging_status: SUCCESS → штатная инициация advance_stage(deploy-staging, deployer) без новых рёбер/исходов (контракт ORCH-115 D7 сохранён)." + module: tests/test_orch123_staging_runner_exec.py + expected: PASS + + - id: TC-06 + type: unit + description: "Классификация (по образцу merge_gate.classify_retest_failure): постоянный environment-дефект (spawn-error 'исполняемое не найдено') отличается от транзиентной инфры и от настоящего код-фейла; постоянный дефект НЕ трактуется как транзиент (бессмысленный ретрай) и НЕ как код-фейл." + module: tests/test_orch123_staging_runner_exec.py + expected: PASS + + - id: TC-07 + type: integration + description: "Prod-like preflight (AC-5): механизм раннего детекта 'стратегия исполнения неработоспособна (нет исполняемого/канал недоступен)' сигнализирует ДО ложного отката реальной задачи (старт сервиса / smoke / applies / should_intercept — в зависимости от выбора ADR)." + module: tests/test_orch123_staging_runner_exec.py + expected: PASS + + - id: TC-08 + type: unit + description: "Self-hosting safety (AC-8): путь исполнения никогда не рестартит прод 8500, не делает docker compose up orchestrator/--build прода, не force-push, не пишет в main, не правит .env (статический/поведенческий ассерт; инвариант ORCH-115 BR-7 сохранён)." + module: tests/test_orch123_staging_runner_exec.py + expected: PASS + + - id: TC-09 + type: unit + description: "Kill-switch/область (AC-10): staging_runner_enabled=False → should_intercept False → прежний LLM-deployer через _spawn байт-в-байт; applies(repo): self-hosting orchestrator real, прочие репо — no-op; новый флаг стратегии (если есть) имеет дефолт = боевое." + module: tests/test_orch123_staging_runner_exec.py + expected: PASS + + - id: TC-10 + type: unit + description: "Контракт артефакта (AC-7): 15-staging-log.md сохраняет staging_status:-frontmatter (UPPERCASE значение) + 52c-схему (author_agent: staging-runner / model_used: n/a); вердикт читается только из frontmatter через src/frontmatter.py." + module: tests/test_orch123_staging_runner_exec.py + expected: PASS + + - id: TC-11 + type: unit + description: "Анти-регресс конвейера (AC-7): STAGE_TRANSITIONS / реестр QG_CHECKS / имена и семантика check_staging_status / _parse_staging_status / machine-verdict ключи (staging_status:/deploy_status:) — не изменены; миграции БД нет (структурная сверка)." + module: tests/test_orch123_staging_runner_exec.py + expected: PASS + + - id: TC-12 + type: unit + description: "never-raise / очередь не клинится (AC-11): любая ошибка исполнения/классификации изолирована, worker не падает, задача не залипает на deploy-staging навсегда; снапшот staging_runner в GET /queue различает environment/tool-error vs код-фейл." + module: tests/test_orch123_staging_runner_exec.py + expected: PASS + + - id: TC-13 + type: unit + description: "Документация-инвариант (AC-12): docs/operations/INFRA.md и docs/architecture/README.md содержат описание границы исполнения staging-сюиты (host-side vs in-container, доступные исполняемые/сокеты) — структурная сверка наличия раздела." + module: tests/test_orch123_staging_runner_exec.py + expected: PASS + + - id: TC-14 + type: unit + description: "Анти-дрейф ORCH-115: существующие структурные тесты staging-runner (tests/test_orch115_staging_runner.py) и LLM-determinization анти-дрейф остаются ЗЕЛЁНЫМИ после фикса (фикс не ломает инварианты ORCH-115)." + module: tests/test_orch115_staging_runner.py + expected: PASS