Files
orchestrator/docs/work-items/ORCH-123/01-brd.md
claude-bot 3865b14a1c
All checks were successful
CI / test (push) Successful in 1m12s
analyst(ET): auto-commit from analyst run_id=747
2026-06-16 07:55:43 +03:00

202 lines
21 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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`
(заполняет архитектор).