Files
orchestrator/docs/work-items/ORCH-115/02-trz.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

02 — ТЗ (TRZ): ORCH-115 — детерминированный staging-раннер вместо LLM-деплойера

Work Item: ORCH-115 · Repo: orchestrator · Стадия: analysis

ТЗ описывает конкретные изменения к реализации, выведенные из BRD и фактического кода. Архитектурное обоснование (точный механизм перехвата, размещение раннера, способ инициации advance_stage, лестница таймаутов) — задача архитектора (06-adr/). Здесь — требования и привязка к реальным модулям src/.

1. Сводка изменения

Заменить LLM-агента deployer на стадии deploy-staging (для self-hosting orchestrator) детерминированным staging-раннером, перехватываемым в launch_job до _spawn (прецедент deploy-finalizer/post-deploy-monitor, src/agents/launcher.py:389/394). Раннер исполняет ту же staging-сюиту, что исполнял LLM (docker exec orchestrator-staging python3 scripts/staging_check.py …), маппит exit-код в staging_status: (0→SUCCESS, иначе FAILED), пишет 15-staging-log.md, best-effort мержит лог в main, затем инициирует существующую оценку exit-гейта check_staging_status ровно как завершившийся LLM-deployer. Контракт артефакта, гейт, STAGE_TRANSITIONS, схема БД — неизменны. Под kill-switch + скоуп-CSV; never-raise; fail-closed.

2. Задействованные модули / пути

Путь Действие Назначение
src/staging_runner.py (новый leaf) создать Детерминированный раннер: applies(repo) (kill-switch + скоуп), исполнение staging-сюиты, маппинг exit-кода, запись 15-staging-log.md, best-effort merge, снапшот для /queue. Leaf-чистота по образцу self_deploy.py/staging_verdict.py: импортирует только config/git_worktree (+ лениво qg.checks.is_self_hosting_repo), never-raise.
src/agents/launcher.py изменить В launch_job добавить перехват до _spawn (рядом с D1/D2): если джоб — deployer на стадии задачи deploy-staging и staging_runner.applies(repo) → исполнить раннер синхронно в воркер-треде, инициировать advance_stage и пометить джоб (как _run_deploy_finalizer_job); вернуть None (нет agent_runs-строки).
src/config.py изменить Добавить ключи staging_runner_enabled: bool = True (env ORCH_STAGING_RUNNER_ENABLED) и staging_runner_repos: str = "" (env ORCH_STAGING_RUNNER_REPOS; пусто → self-hosting only) + опц. staging_runner_timeout_s (см. FR-5). Дефолты = боевое; паттерн coverage_gate_enabled/coverage_gate_repos/self_deploy_*.
src/stage_engine.py (потенциально) точечно Если архитектор решит инициировать гейт из stage_engine, а не из launcher — добавить тонкий хелпер (вызов существующего advance_stage(finished_agent="deployer")). Без правки STAGE_TRANSITIONS/exit-гейтов.
src/main.py (GET /queue) изменить Read-only блок staging_runner (флаг/скоуп/счётчики исходов) — наблюдаемость BR-6.
.openclaw/agents/deployer.md изменить (docs) Отметить, что на deploy-staging для in-scope репо стадию ведёт детерминированный код (зеркало формулировки prod-Phase A/B/C); LLM-ветвь deploy-staging остаётся как fallback под выключенным флагом / для не-self репо.
docs/architecture/llm-call-sites.md, llm-determinization-roadmap.md, llm-usage-policy.md изменить (docs) Норматив сопровождения ORCH-118 (NFR-6): отразить реализацию A6 (deployer staging-status) — обновить инвентарь/политику/roadmap в том же PR; синхронно поправить tests/test_llm_call_site_inventory.py / tests/test_llm_determinization_docs.py.
CLAUDE.md, CHANGELOG.md, docs/overview/ изменить (docs) Паспорт/чейнджлог/витрина — правило для агентов №2.
tests/test_orch115_staging_runner.py (новый) создать Покрытие (см. 04-test-plan.yaml).

Не трогать (NFR-1): src/stages.py::STAGE_TRANSITIONS; имена/семантику QG_CHECKS/check_*/ _parse_* в src/qg/checks.py; src/staging_verdict.py (переиспользуем как есть); src/self_deploy.py прод-путь; src/transition_lease.py (ORCH-114); src/checkout_hygiene.py (ORCH-112); схему БД.

3. Функциональные требования

FR-1 — Детерминированный перехват на deploy-staging (без _spawn)

В launch_job (src/agents/launcher.py) до вызова _spawn, по образцу D1/D2: если job.agent == "deployer" и стадия задачи (tasks.stage по job.task_id) == deploy-staging и staging_runner.applies(job.repo) истинно → не вызывать _spawn, а исполнить раннер синхронно. Контракт: возвращает None (нет agent_runs), сам ведёт jobs-строку (mark_job(done|failed|queued)) как _run_deploy_finalizer_job.

  • Дискриминатор «staging vs prod» — стадия задачи, не имя роли (роль deployer общая для deploy-staging и deploy). Для self-hosting прод-ребро не запускает deployer (Phase A), поэтому коллизии нет; гард по стадии — защита от перехвата не того джоба (R-1).
  • applies(repo): staging_runner_enabled=FalseFalse (откат к LLM-пути); непустой staging_runner_repos → membership; пустой CSV → is_self_hosting_repo(repo). Никакой сети, проверяется первым (нулевой оверхед при выключенном флаге). Never-raise → False при ошибке (fail-safe к прежнему LLM-пути).

FR-2 — Исполнение staging-сюиты

Раннер исполняет ту же канонную команду, что исполнял LLM-deployer (.openclaw/agents/deployer.md step 1): docker exec orchestrator-staging python3 /repos/orchestrator/scripts/staging_check.py --base-url http://localhost:8501 --mode stub (точные аргументы/таргет — из config, не хардкодить host-специфику; ORCH-101). Захватывает exit-код (и stdout для observability/тела лога). infra-tolerance (ORCH-061) уже внутри staging_check.py → раннер вердикт повторно не судит (BR-4).

FR-3 — Маппинг exit-кода → staging_status:

0 → "SUCCESS", любой ненулевой / отсутствие кода / ошибка запуска → "FAILED" (fail-closed, никогда ложный green). Зеркало уже существующего self_deploy.map_exit_code_to_status (pure, unit-tested) — переиспользовать общий контракт, не плодить второй маппинг.

FR-4 — Запись и merge 15-staging-log.md

Раннер пишет docs/work-items/<work_item_id>/15-staging-log.md в worktree фичеветки с frontmatter: staging_status: SUCCESS|FAILED + обязательная 52c-схема (work_item/stage=deploy-staging/ author_agent/status/created_at/model_used) — зеркало self_deploy.build_deploy_log для 14-deploy-log.md. author_agent/model_used отражают детерминированный продюсер (например author_agent: staging-runner, model_used: n/a или платформенный литерал — финализирует архитектор; ключи и имя staging_status: не меняются). При INFRA-WAIVED-строке от staging_check.py — скопировать её в тело (observability, как требовал prompt). Best-effort git add/commit/push лога в main (зеркало self_deploy.write_deploy_log, тот же git-identity-паттерн ORCH-101); гейт всё равно читает worktree → origin/main fallback (check_staging_status lookup order, src/qg/checks.py:627-638).

FR-5 — Инициация существующего гейта после вердикта

После записи (и best-effort merge) раннер инициирует ту же оценку exit-гейта, что триггерил завершившийся LLM-deployer: advance_stage(task_id, current_stage="deploy-staging", repo, work_item_id, branch, finished_agent="deployer") (через _try_advance_stage-эквивалент). Это запускает check_staging_status и — на SUCCESS — под-гейты security→merge→coverage→image-freshness (ORCH-022/043/027/058) и Phase A (ORCH-036); на FAILED — существующий rollback (src/stage_engine.py:932). Никакой новой ветви маршрутизации. Lease ORCH-114 берётся внутри advance_stage как сейчас — раннер его не трогает (граница задачи).

  • Таймаут раннер-subprocess — выделенный ключ staging_runner_timeout_s с дефолтом, согласованным со сквозным бюджетом ORCH-065/109/110 (NFR-4); малформ/непозитив → дефолт + WARNING (never-break).

FR-6 — Kill-switch и скоуп (обратимость)

staging_runner_enabled=False → перехват не срабатывает → на deploy-staging запускается прежний LLM-deployer (_spawn) байт-в-байт как до ORCH-115. staging_runner_repos ограничивает скоуп (пусто → только orchestrator); не-self репо никогда не перехватываются (для них staging-гейт и так N/A, src/qg/checks.py:620).

FR-7 — Наблюдаемость

  • Read-only блок staging_runner в GET /queue: enabled, repos, счётчики success/failed/ tool_error/runs.
  • Один структурный лог-вердикт на прогон (work_item/repo/exit_code/status/duration_s), различающий «код упал» (FAILED от staging-сюиты) и «инструмент недоступен» (tool-error).

4. Изменения API

  • GET /queue — добавить read-only ключ staging_runner (наблюдаемость). Существующие поля ответа не меняются.
  • Опционально (на усмотрение архитектора, по образцу POST /coverage/baseline): нет обязательного нового мутирующего эндпоинта. Откат — через env-флаг.
  • Новых вебхуков нет.

5. Изменения схемы БД

Нет. Раннер использует существующие таблицы (tasks для стадии, jobs для статуса джоба) и sentinel/worktree-механику. Никаких новых таблиц/колонок/миграций (NFR-1). Счётчики /queue — in-process (паттерн _MERGE_GATE_COUNTERS, ORCH-110), не БД.

6. Требования к новым/изменённым QG checks

Нет новых QG и нет изменений существующих. check_staging_status / _parse_staging_status / ключ staging_status: (src/qg/checks.py:538/599) и состав QG_CHECKSбайт-в-байт неизменны. ORCH-115 меняет только продюсера 15-staging-log.md (детерминированный код вместо LLM); гейт, читающий артефакт, остаётся прежним. Это критический инвариант (NFR-1) — reviewer ловит любое изменение имени/семантики гейта как finding ≥P1.

7. Совместимость / регресс

  • Обратная совместимость: staging_runner_enabled=False → прежний LLM-deployer-путь байт-в-байт; не-self репо → 1:1 (N/A-pass либо LLM, в зависимости от скоупа). enduro-trails не затронут (NFR-5).
  • Kill-switch / область раската: один флаг staging_runner_enabled + CSV staging_runner_repos (пусто → self-hosting only). Откат = ORCH_STAGING_RUNNER_ENABLED=false.
  • Обратимость: полностью обратимо флагом; артефакт и гейт неизменны, так что переключение туда-сюда не оставляет несовместимого состояния.
  • never-raise / fail-safe (NFR-2): ошибка раннера → FAILED (fail-closed) или штатный requeue, не «тихий advance»; сбой не клинит очередь. Self-hosting safety (BR-7): никаких рестартов 8500 / force-push в main / правок инфры.
  • Граница (О1): код ORCH-112 (checkout hygiene) и ORCH-114 (transition lease) не модифицируется.
  • Норматив сопровождения (NFR-6): в том же PR обновить docs/architecture/llm-call-sites.md / llm-determinization-roadmap.md / llm-usage-policy.md + соответствующие анти-дрейф тесты; CLAUDE.md / CHANGELOG.md / docs/overview/.

8. Phase 2 (forward-looking, вне приёмки ORCH-115)

Зафиксировано для преемственности — не реализуется в этой задаче, заводится отдельным follow-up:

  • Project deploy contract для не-self репо (enduro-trails): декларативный per-repo контракт deploy / rollback / healthcheck (команды + ожидаемые коды/эндпоинты), исполняемый тем же детерминированным раннер-паттерном (run → map exit code → verdict → artifact → healthcheck).
  • LLM остаётся допустим только как off-control-path debug/triage-аналитик после детерминированного провала (NFR-7) — не как продюсер вердикта.
  • Зависимость: устойчивый Phase 1 (этот work item) как доказанный паттерн перехвата + маппинга.