---
work_item: ORCH-123
stage: architecture
author_agent: architect
status: proposed
created_at: 2026-06-16
model_used: claude-opus-4-8
---

# 07 — Инфра-требования: ORCH-123 — host-side исполнение staging-раннера

Work Item: **ORCH-123** · Repo: **orchestrator** · Стадия: architecture

> When-applicable. **Топология не меняется** (новых контейнеров/портов/томов/compose-правок нет).
> Файл фиксирует **границу исполнения** (BR-5/FR-6/AC-12) и **предусловия канала**, на которые
> раннер теперь опирается программно после фикса (раньше тот же `docker exec` исполнялся изнутри
> контейнера — и поэтому всегда падал в проде).

## I-1. Граница исполнения docker-операций (нормативно — BR-5 / FR-6)

**Прод-контейнер `orchestrator` (8500) НЕ содержит docker CLI.** `Dockerfile:11` ставит
`openssh-client git curl ca-certificates` (+ pinned gitleaks); `python:3.12-slim` docker не несёт.
`/var/run/docker.sock` **смонтирован** (`docker-compose.yml`, rw + `group_add 999`, ORCH-040 «МИНА 1»),
но **изнутри контейнера не используется** (нет клиента — сознательно, см. ADR-001 D2: добавление
клиента/SDK = активация root-эквивалентного пути для всего, что исполняется в контейнере).

**Все docker-операции исполняются host-side через ssh на `127.0.0.1`** (доверенный канал, ключ
смонтирован `${ORCH_HOST_SSH_DIR}:…/.ssh:ro`, `openssh-client` в образе):

| Операция | Компонент | Канал (host-side) |
|----------|-----------|-------------------|
| Прод-деплой (рестарт 8500) | `self_deploy.build_deploy_command` (ORCH-036) | `ssh + setsid bash <hook> --deploy` |
| Ребилд staging-образа из validated commit | `image_freshness.rebuild_staging_image` (ORCH-058) | `ssh … bash <hook> --build-staging` |
| Инспекция revision-label образа | `image_freshness.image_revision(ssh_target=…)` (ORCH-058) | `ssh … docker image inspect …` |
| **Staging-сюита на `deploy-staging`** | **`staging_runner.build_staging_command` (ORCH-123)** | **`ssh … docker exec orchestrator-staging python3 staging_check.py …`** |

`scripts/staging_check.py` по-прежнему исполняется **внутри** `orchestrator-staging` (8501) — это
контракт сюиты (там python3 + приложение 8501, bind-mount `/repos/orchestrator/scripts/…`, ORCH-048).
**Меняется только инициатор `docker exec`** (host-side ssh вместо изнутри прод-контейнера), не сама
сюита и не её exit-код-контракт.

## I-2. Предусловия канала исполнения

Раннер после фикса программно опирается на (preflight их пробит на старте сервиса — ADR-001 D5):
- ssh-доступность `deploy_ssh_host` (`127.0.0.1`) под смонтированным ключом (та же, что использует
  прод-деплой ORCH-036 / image-freshness ORCH-058 — уже в проде);
- наличие `docker` CLI **на хосте** (есть; контейнер его не несёт);
- поднятый и доступный staging-контейнер `orchestrator-staging` (8501) (Допущение А1 ORCH-115).

Недоступность любого предусловия классифицируется **детерминированно** (ADR-001 D3): постоянный
дефект (`docker`/контейнер/ssh недоступны как класс) → `permanent-env` → **infra-HOLD + алерт**
(никогда тихий advance / ложный green / откат как код-фейл); транзиентная икота (timeout/ssh-255) →
bounded DEFER.

## I-3. Переменные окружения / секреты

**Новый env-ключ** (config, дефолт = боевое; добавить в `.env.example`):
- `ORCH_STAGING_RUNNER_EXEC_HOST_SIDE` (`staging_runner_exec_host_side`, дефолт `True`) — выбор
  стратегии исполнения. `True` → host-side ssh (фикс). `False` → прежний in-container `docker exec`
  (валиден только там, где docker CLI запечён в образ). Откат фикса — без LLM-пути.

**Переиспользуются (без изменений):** `ORCH_STAGING_RUNNER_ENABLED`/`_REPOS`/`_TIMEOUT_S`/
`_INFRA_MAX_RETRIES`/`_INFRA_RETRY_DELAY_S` (ORCH-115); `ORCH_DEPLOY_SSH_USER`/`ORCH_DEPLOY_SSH_HOST`/
`ORCH_DEPLOY_HOST_REPO_PATH` (ssh-канал ORCH-036); `ORCH_STAGING_PORT`/`ORCH_REPOS_DIR` (ORCH-101).
**Секретов не вводит** — переиспользует существующий смонтированный ssh-ключ; команда строится из
существующих host-параметров (анти-дрейф `tests/test_no_host_hardcodes.py`).

## I-4. Деплой / рестарт (self-hosting инвариант, NFR-2 / AC-8)

Раннер на `deploy-staging` **никогда** не рестартит прод-8500, не выполняет
`docker compose up orchestrator`/`--build` прода, не пушит force в `main`, не правит `.env`/
`docker-compose.yml`/`Dockerfile` (BR-7 ORCH-115 / AC-8; структурный анти-литерал-тест на
ssh-обёрнутой команде — TC-08). Argv содержит только `ssh … docker exec orchestrator-staging python3
staging_check.py … --mode stub`. ORCH-040 (смонтированный сокет) **не трогается и не расширяется**.
Прод-выкат самого ORCH-123 — штатно через staging-гейт (8501) → `Confirm Deploy` (ORCH-059); это
**docs/код+config**-изменение, активируется на следующем worktree от `main`, включается флагом
(дефолт `True`, обратимо двумя флагами — ADR-001 D6).

## I-5. CI/CD

**Без изменений `.gitea/workflows/`.** Новый/расширенный `tests/test_orch123_staging_runner_exec.py`
исполняется существующим `pytest tests/ -q` (моки subprocess/ssh-spawn и `advance_stage`; живой
Claude CLI / docker / ssh / сеть не требуются — окружение «нет docker CLI» моделируется монки-патчем
argv/spawn, `04-test-plan.yaml` notes). Существующие `tests/test_orch115_staging_runner.py` остаются
зелёными (TC-14).
