--- 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/` перед изменением; инварианты не ломать.