9.8 KiB
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 /projects→ 404 → 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, - смержил в
mainPR с логами (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 cp→docker exec). - Plane state-id зависят от проекта. Approved для проекта orchestrator =
63f2c8fe-dcda-4ace-952f-dd88bd0118ff(НЕ дефолтныйa519a341...из кода — тот для sandbox/ET). Брать реальные state-id черезGET .../states/. - BRD-апрув = перевод Plane-issue в статус Approved → webhook ловит смену статуса → путь
agent=None→approved-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 без ручного пинания стадий.