16 KiB
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=False→False(откат к 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+ CSVstaging_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) как доказанный паттерн перехвата + маппинга.