# 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-код локально**: ```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 `), стёрли 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 вливает **владелец**. Поймали проверкой: ```bash git fetch origin -q git log --oneline origin/main..origin/feature/ # покажет невлитые коммиты фикса 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 (обязательна):** ```bash 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, "")` + `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 без ручного пинания стадий.