Files
orchestrator/docs/history/LESSONS_ORCH-048.md
2026-06-06 10:30:50 +03:00

9.8 KiB
Raw Blame History

LESSONS — ORCH-048 (B6 staging registry isolation, вариант «в»)

Дата: 2026-06-06 Work item: ORCH-048 — «staging B6 check reads registry from host worktree, not staging container» Статус: Done. Merge PR #45 (2a36ed80), Plane → Done, task 38 → done. Прод не тронут.


TL;DR

B6-чек staging-suite давал ложный FAIL (prod-ET=YES, prod-ORCH=YES), блокируя deploy-staging у всех ORCH-задач, хотя изоляция реестра в staging работала корректно. Починили, выбрав архитектурный вариант, который не порождает новых ловушек автономности. По дороге словили три урока, которые стоят дороже самой фичи.


1. Root cause (для истории)

scripts/staging_check.py блок B6 был единственным чеком suite, который не ходил по HTTP к живому инстансу, а импортировал Python-код локально:

sys.path.insert(0, "/repos/orchestrator")        # host-worktree
importlib.reload(sys.modules["src.projects"])     # подхватывает env ТЕКУЩЕГО процесса
known = known_plane_project_ids()

Деплоер запускал suite с хоста, где ORCH_PROJECTS_JSON не задан → src.projects грузил встроенный _DEFAULT_PROJECTS (ET+ORCH) → known_plane_project_ids() возвращал боевые id → ложный FAIL. То есть B6 проверял реестр НЕ того окружения, реестр которого реально использует staging-инстанс.

Изоляция при этом была исправна: внутри orchestrator-staging known_plane_project_ids() корректно отдавал только sandbox (.env.staging).


2. ГЛАВНЫЙ УРОК: «курица-яйцо» в staging-гейте

Архитектор на первом прогоне выбрал вариант (а): новый HTTP-эндпоинт GET /projects, и B6 стал ходить на него. Решение красивое (единый HTTP-стиль с остальными чеками), но оно само себя заблокировало:

  • B6 проверяет работающий staging-инстанс (порт 8501).
  • Эндпоинт /projects запечён в Docker-образ (src/main.py).
  • В текущем (ещё не пересобранном) образе эндпоинта НЕТGET /projects404 → B6 FAIL → откат на development.
  • Чтобы чек прошёл, нужен ручной bootstrap-деплой образа. А деплой не происходит, потому что чек красный. Тупик by design.

Подтверждено на проде: GET /projects на 8501 и 8500 → 404 → deploy-staging FAILED.

Вывод-правило:

Staging-чек НЕ должен проверять то, что появляется в работающем инстансе только ПОСЛЕ деплоя проверяемой ветки. Иначе первый прогон всегда падает и требует ручного bootstrap — это прямая поломка автономности.

Решение — вариант (в): запускать suite ВНУТРИ staging-контейнера (docker exec orchestrator-staging), читать реестр из собственного process-env контейнера, убрать host-path хак. Преимущество принципиальное:

  • B6 не зависит от того, что отдаёт инстанс по HTTP.
  • staging_check.py берётся из bind-mount → свежий код подхватывается без ребилда образа.
  • Курицы-яйца нет ни на первом прогоне, ни в будущем.

Вариант (б) (docker exec ... python3 -c "..." + парсинг stdout) отклонён: хрупкое экранирование (см. LESSONS_2026-06-05.md).

Как это попало в реализацию: после FAIL под (а) — откатили ветку к analyst-артефактам (git reset --hard <analyst-commit>), стёрли ADR(а)+код(а), зашили в 02-trz.md §4 блок «РЕШЕНИЕ ПРИНЯТО ВЛАДЕЛЬЦЕМ: вариант (в)» с обоснованием и чек-листом, откатили задачу на architecture + поставили job архитектору заново. Второй прогон: arch→dev→review→tester→deploy-staging — без петель, B6 ✓ PASS, 10/10.


3. УРОК: орк мержит в main ТОЛЬКО логи, а не фикс-код

После прохождения staging орк сам:

  • закрыл задачу в done,
  • смержил в main PR с логами (15-staging-log.md, 14-deploy-log.md),
  • но сам фикс-код остался в feature-веткеmain всё ещё содержал старый сломанный B6.

Это by design: фичу в main вливает владелец. Поймали проверкой:

git fetch origin -q
git log --oneline origin/main..origin/feature/<branch>   # покажет невлитые коммиты фикса
git show origin/main:scripts/staging_check.py | grep -c '_evaluate_b6'   # 0 = фикс НЕ в main

Правило:

Прежде чем считать задачу реально доставленной — проверить git log origin/main..feature и наличие ключевой функции/строки фикса в origin/main. done в Plane + смерженные логи ≠ код в main.

Финальный шаг: смерджить feature-PR в main (Gitea API, Do: merge), затем синхронизировать host-репо.


4. УРОК: rollout bind-mount-фикса = host git pull, без ребилда/рестарта прода

ORCH-048 менял только bind-mounted / non-runtime артефакты:

Файл Как доходит до прода
scripts/staging_check.py bind-mount (/home/slin/repos/repos); не в образе (scripts/ нет в /app) → host git pull → live сразу
.openclaw/agents/deployer.md bind-mounted промпт, читается при запуске агента → live на следующем запуске
tests/, docs/ не деплоятся

src/ и Dockerfile НЕ менялись → рестарт/ребилд прод-контейнера 8500 не нужен и не делался (zero group-risk для ET).

Грабли host-репо: git pull в /home/slin/repos/orchestrator сначала упёрся в sudo: a password is required — ложная тревога. Репо принадлежит slin, sudo не нужен; прямой git pull --ff-only origin main прошёл. Сначала проверь ls -ld / stat -c %U репо — не лезь в sudo вслепую.

Верификация rollout в живом bind-mount (обязательна):

grep -c '_evaluate_b6' scripts/staging_check.py                       # >=1
grep -c 'sys.path.insert(0, "/repos/orchestrator")' scripts/staging_check.py  # 0
grep -c 'docker exec orchestrator-staging' .openclaw/agents/deployer.md       # >=1
curl -s -o /dev/null -w '%{http_code}' http://localhost:8500/health   # 200

5. Технические заметки (gotchas)

  • В контейнере orchestrator НЕТ curl — для Gitea/Plane API использовать urllib через python (script-file → base64 → docker cpdocker exec).
  • Plane state-id зависят от проекта. Approved для проекта orchestrator = 63f2c8fe-dcda-4ace-952f-dd88bd0118ff (НЕ дефолтный a519a341... из кода — тот для sandbox/ET). Брать реальные state-id через GET .../states/.
  • BRD-апрув = перевод Plane-issue в статус Approved → webhook ловит смену статуса → путь agent=Noneapproved-via-status → гейт пропускает, БЕЗ повторного запуска check_analysis_approved.
  • Dockerfile НЕ копирует scripts/ в образ — staging_check.py доступен в контейнере только через mount. Путь запуска внутри контейнера учитывать (не /app/scripts).
  • Перезапуск стадии вручную: update_task_stage(task_id, "<stage>") + enqueue_job(agent, repo, task_content, task_id). Guard перед этим: agent_running IS NULL И нет jobs со status IN ('queued','running') для task_id.

6. Итог по гейтам/ядру после серии ORCH-45/46/47/48

  • check_ci_green — поллинг (ORCH-45)
  • check_tests_passed — читает result: (ORCH-47)
  • stage_engine — передаёт деву текст findings, не только ссылку (ORCH-46)
  • B6 staging — читает реестр ВНУТРИ staging-контейнера, больше не ложный FAIL (ORCH-48) → deploy-staging разблокирован для всех ORCH-задач

Конвейер стал по-настоящему автономным: задача проходит analyst→deploy без ручного пинания стадий.