202 lines
21 KiB
Markdown
202 lines
21 KiB
Markdown
---
|
||
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 <user>@<host> 'setsid bash -c "… bash
|
||
<hook> --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`
|
||
(заполняет архитектор).
|