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

16 KiB
Raw Blame History

work_item, stage, author_agent, status, created_at, model_used
work_item stage author_agent status created_at model_used
ORCH-115 analysis analyst ready-for-review 2026-06-16 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_stageadvance_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-аналитик после детерминированного FAILEDreplace-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-путём.