Files
orchestrator/docs/work-items/ORCH-115/01-brd.md
claude-bot ac203c0ccf
All checks were successful
CI / test (push) Successful in 1m6s
analyst(ET): auto-commit from analyst run_id=732
2026-06-16 01:11:35 +03:00

176 lines
16 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-115
stage: analysis
author_agent: analyst
status: ready-for-review
created_at: 2026-06-16
model_used: claude-opus-4-8
---
# 01 — BRD (бизнес-требования): ORCH-115 — заменить LLM-деплойера детерминированным staging-раннером
Work Item: **ORCH-115** · Repo: **orchestrator** · Стадия: analysis
## 1. Бизнес-контекст и проблема
Стадия `deploy-staging` сейчас исполняется **LLM-агентом `deployer`** (`src/stages.py:18`,
`get_agent_for_stage("testing") = "deployer"`). Фактическая работа агента на этой стадии —
**чисто детерминированная**: запустить staging-сюиту (`docker exec orchestrator-staging python3
scripts/staging_check.py --base-url http://localhost:8501 --mode stub`), смаппить **exit-код**
в вердикт (`0 → SUCCESS`, ≠0 → `FAILED`), записать `15-staging-log.md` с frontmatter
`staging_status:` и смержить лог в `main` (`.openclaw/agents/deployer.md`, шаги 14).
Это **avoidable LLM control path** по нормативной политике (`docs/architecture/llm-usage-policy.md`
§3): (i) это C-консультация — её вердикт `staging_status:` потребляется гейтом
`check_staging_status` (`src/qg/checks.py:599`), и (ii) вердикт **полностью деривируем** из
exit-кода `staging_check.py`. Карта вызовов (`docs/architecture/llm-call-sites.md`, строка **A6**)
классифицирует deployer как **`replace-deterministic-now`**, а roadmap
(`docs/architecture/llm-determinization-roadmap.md`, машинный блок) ставит его **rank 1** с
`first_slice = yes`, `hybrid_needed = no`. Эта задача — **первый срез** реализации того roadmap.
**Боль / риск, который закрываем:**
- **Недетерминизм в потоке управления.** Решение «advance или rollback» на `deploy-staging` зависит
от LLM-сессии (стоимость, латентность, риск галлюцинации команд), хотя сводится к одному exit-коду.
- **Стоимость и латентность.** Каждый прогон deployer'а на staging тратит токены/время opus-агента
(оценка по `agent_runs`: deployer-строки ~40120k токенов / 315 мин на прогон; точное число —
`GET /metrics`) ради действия, которое выполняется тремя shell-строками.
- **Класс инцидентов «LLM принял решение, которое есть исполнение фиксированных команд + маппинг
результата»** — тот же RCA-трек, что ORCH-110/111/112/113/114/117.
Установленные факты (не изобретать):
- Пьюр-логика вердикта уже существует и юнит-тестируема: `src/staging_verdict.py::compute_staging_verdict`
(ORCH-061) считает infra-tolerant вердикт **внутри** `staging_check.py`; раннеру остаётся доверять
exit-коду (как уже делает LLM-deployer — `deployer.md` step 2).
- Детерминированный прецедент замены агента уже работает: `launch_job` перехватывает зарезервированные
роли `deploy-finalizer` (D1, `src/agents/launcher.py:389`) и `post-deploy-monitor`
(D2, `:394`) **до `_spawn`** и исполняет их как no-LLM-джобы.
- Прод-ребро `deploy` для self-hosting уже детерминировано (`src/self_deploy.py` Phase A/B/C,
ORCH-036) — LLM в критическом self-restart-пути нет. Срез не трогает критический прод-путь.
## 2. Объём (scope)
### В объёме (Phase 1)
- **Детерминированный staging-раннер** для `deploy-staging` репо `orchestrator` (self-hosting):
исполняет staging-сюиту, маппит exit-код в `staging_status:`, пишет `15-staging-log.md`, мержит в
`main`**без** запуска LLM-агента `deployer`.
- Раннер активируется через **перехват в `launch_job` до `_spawn`** (прецедент D1/D2), **без правки
`src/stages.py`/`STAGE_TRANSITIONS`** (роль `deployer` в словаре остаётся; меняется лишь *кто*
обрабатывает джоб на стадии `deploy-staging` для in-scope репо).
- После выпуска вердикта раннер инициирует **существующую** оценку exit-гейта `check_staging_status`
ровно так, как это делал завершившийся LLM-deployer (`_try_advance_stage``advance_stage(
finished_agent="deployer")`) — все нижестоящие под-гейты (security → merge → coverage →
image-freshness, ORCH-022/043/027/058) и Phase A (ORCH-036) ведут себя идентично.
- Kill-switch + скоуп-CSV (паттерн ORCH-022/027/043/089/090): `*_enabled` (откат к LLM-пути) и
`*_repos` (пусто → self-hosting only).
- Наблюдаемость: read-only блок в `GET /queue` + структурный лог вердикта.
### Вне объёма (явно НЕ делаем в ORCH-115)
- **Phase 2 — «project deploy contract» для не-self репо** (например `enduro-trails`): конфигурируемый
контракт deploy/rollback/healthcheck для произвольных репо. Описан как **forward-looking
follow-up** (см. §6 и `02-trz.md` §8); **в приёмку ORCH-115 не входит**. Для не-self репо
`deploy-staging` сейчас — мгновенный pass (`check_staging_status` → N/A, `src/qg/checks.py:620`),
поэтому Phase 1 их не затрагивает.
- **Прод-ребро `deploy`** (Phase A/B/C self-deploy, ORCH-036) — уже детерминировано; не трогаем.
- **LLM debug/triage-аналитик после детерминированного FAILED** — `replace-deterministic-now` без
гибрида (roadmap `hybrid_needed = no`). В этом срезе LLM на `deploy-staging` отсутствует и в
happy-path, и в fail-path; опциональный off-control-path debug-аналитик оставлен как будущее
улучшение и **требованиями не запрещён** (см. NFR-7).
- **Любая правка `STAGE_TRANSITIONS` / реестра и имён `QG_CHECKS` / семантики `check_*` /
machine-verdict-ключей / схемы БД** (см. NFR-1).
- **ORCH-112 (checkout hygiene) и ORCH-114 (transition lease)** — по явной границе задачи не
смешиваем: раннер вызывает `advance_stage`, который уже владеет lease ORCH-114; сам lease/гигиену
не модифицируем.
## 3. Заинтересованные стороны
- **Заказчик / Owner** (`homenet542@gmail.com`) — инициатор детерминизации LLM-control-path'ов.
- **Платформа orchestrator (self-hosting)** — прямой потребитель: дешевле/быстрее/детерминированнее
собственный `deploy-staging`.
- **Другие проекты на общем инстансе** (enduro-trails) — НЕ затронуты в Phase 1 (скоуп self-hosting),
выигрывают позже от Phase 2.
- **Reviewer / Tester / Deployer-роли конвейера** — принимают результат через неизменные гейты.
## 4. Бизнес-требования (BR)
- **BR-1 — Детерминированный staging без LLM.** На `deploy-staging` для in-scope репо вердикт
`staging_status:` производится детерминированным кодом (исполнение `staging_check.py` + маппинг
exit-кода), **без** консультации LLM. Happy-path `deploy-staging` не вызывает `_spawn`.
- **BR-2 — Контракт артефакта неизменен.** Раннер пишет тот же `15-staging-log.md` с тем же
frontmatter-ключом `staging_status: SUCCESS|FAILED`, который читает `check_staging_status`/
`_parse_staging_status`. Гейт байт-в-байт не меняется.
- **BR-3 — Эквивалентность маршрутизации.** SUCCESS → продвижение на `deploy` через те же под-гейты
и Phase A; FAILED → существующий откат `deploy-staging → development` (тот же путь, что у
FAILED-вердикта LLM-deployer'а, `src/stage_engine.py:932`). Никаких новых рёбер/исходов.
- **BR-4 — Переиспользование существующей пьюр-логики.** Раннер использует уже существующий
exit-code→verdict маппинг (тривиальный `0→SUCCESS`/иначе`FAILED`, зеркало
`self_deploy.map_exit_code_to_status`); infra-tolerance (ORCH-061) остаётся **внутри**
`staging_check.py` — раннер ему доверяет, повторно не судит.
- **BR-5 — Обратимость одним флагом.** Глобальный kill-switch возвращает прежний LLM-deployer-путь
на `deploy-staging` байт-в-байт; скоуп-CSV ограничивает раннер in-scope репо (пусто → только
`orchestrator`).
- **BR-6 — Наблюдаемость.** Исход раннера (запущен / SUCCESS / FAILED / ошибка инструмента) виден в
`GET /queue` и в структурном логе; деградации (например staging-инстанс недоступен) различимы от
«код упал».
- **BR-7 — Self-hosting safety.** Раннер на `deploy-staging` **никогда** не рестартит прод-контейнер
8500, не трогает `main` force-push'ем, не правит `.env`/`docker-compose.yml`. Он лишь читает,
исполняет staging-сюиту (порт 8501), пишет лог и мержит лог штатным PR/artifact-merge-путём.
## 5. Нефункциональные требования (NFR)
- **NFR-1 — Скоуп-инвариант (анти-дрейф).** `STAGE_TRANSITIONS` (`src/stages.py`), реестр и имена
`QG_CHECKS`/`check_*`/`_parse_*` (`src/qg/checks.py`), machine-verdict-ключи
(`staging_status:`/`deploy_status:`/`verdict:`/`result:`/`security_status:`/`coverage_status:`),
схема БД — **байт-в-байт не тронуты**. Это замена *продюсера* артефакта, не гейта.
- **NFR-2 — never-raise / fail-safe.** Любая ошибка раннера (docker недоступен, таймаут, I/O) →
безопасный детерминированный исход без падения воркера: либо `FAILED` (fail-closed, никогда ложный
green), либо штатный requeue/defer — не «тихий advance». Сбой раннера не клинит очередь всех
проектов.
- **NFR-3 — Изоляция процесса / таймаут.** Спавненный subprocess (`docker exec …`) имеет
ограниченный таймаут и чистое завершение дерева процессов (согласовано с прецедентом ORCH-110
`proc_group`/tree-kill); сирот pytest/docker не оставляет.
- **NFR-4 — Сквозные бюджеты времени.** Таймаут раннера согласован со сквозным инвариантом
ORCH-065/109/110 (`reaper_max_running_s` > Σ(работ на ребре deploy-staging) + grace) — без правки
`reaper_max_running_s`.
- **NFR-5 — Совместимость с не-self репо.** Для репо вне скоупа `deploy-staging` ведёт себя 1:1 как
до ORCH-115 (LLM-deployer либо мгновенный N/A-pass). enduro-trails не затронут.
- **NFR-6 — Соответствие политике LLM.** Изменение снимает LLM-консультацию A6; карта
`docs/architecture/llm-call-sites.md` и политика/roadmap обновляются **в том же PR** (норматив
сопровождения ORCH-118): строка deployer переходит из «consults_llm: yes» в реализованное
детерминированное состояние.
- **NFR-7 — Не запрещать будущий debug-fallback.** Архитектура раннера не должна архитектурно
исключать опциональный off-control-path LLM debug-аналитик после FAILED (будущее улучшение); но
в ORCH-115 он не реализуется.
## 6. Допущения и ограничения
- **Допущение А1.** staging-инстанс `orchestrator-staging` (8501) поднят и доступен на хосте; его
недоступность раннер трактует детерминированно (fail-closed `FAILED` или defer — решает архитектор,
AC-7).
- **Допущение А2.** `scripts/staging_check.py` остаётся источником истины набора проверок и
exit-кода (включая infra-tolerance ORCH-061). ORCH-115 его логику не меняет.
- **Допущение А3.** Перехват «до `_spawn`» по имени джоб-роли + стадии задачи — достаточный механизм
диспетчеризации (как D1/D2); конкретный механизм финализирует архитектор (06-adr).
- **Ограничение О1.** Граница задачи: не смешивать с ORCH-112/ORCH-114 (их код не модифицируется).
- **Ограничение О2.** Phase 2 (project deploy contract) — отдельный follow-up; ORCH-115 закрывает
только Phase 1.
## 7. Критерии успеха
`deploy-staging` для `orchestrator` проходит без запуска LLM-агента `deployer`: детерминированный
раннер исполняет staging-сюиту, пишет корректный `15-staging-log.md` (`staging_status:`),
мержит его в `main`, и конвейер продвигается/откатывается ровно как раньше — при неизменных
`STAGE_TRANSITIONS`/`QG_CHECKS`/гейтах/схеме БД, под kill-switch с откатом к прежнему поведению.
Детальные PASS/FAIL — `03-acceptance-criteria.md`.
## 8. Риски
Краткий перечень (детали — `10-tech-risks.md`, заполняет архитектор):
- **R-1** — точка диспетчеризации «до `_spawn`» должна корректно отличать staging-deployer от
прод-deployer (по стадии задачи), иначе можно перехватить не тот джоб.
- **R-2** — после выпуска вердикта нужно надёжно инициировать `advance_stage`, иначе задача зависнет
на `deploy-staging` (нет «финиша агента», который раньше триггерил гейт).
- **R-3** — таймаут/изоляция docker-subprocess; утечка процессов (ср. инцидент ORCH-110).
- **R-4** — взаимодействие с transition-lease (ORCH-114) и serial-gate (ORCH-088) на side-effectful
ребре — не сломать владение.
- **R-5** — корректность отката FAILED (developer-retry cap) — должна совпасть с LLM-путём.