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