From d6744c3c05f09c02b0a8e0bda9c5a935319d1f9a Mon Sep 17 00:00:00 2001 From: claude-bot Date: Sat, 6 Jun 2026 06:59:56 +0000 Subject: [PATCH] architect(ET): auto-commit from architect run_id=151 --- docs/operations/STAGING_CHECK.md | 63 +++++--- ...DR-001-b6-registry-via-in-container-run.md | 139 ++++++++++++++++++ 2 files changed, 180 insertions(+), 22 deletions(-) create mode 100644 docs/work-items/ORCH-048/06-adr/ADR-001-b6-registry-via-in-container-run.md diff --git a/docs/operations/STAGING_CHECK.md b/docs/operations/STAGING_CHECK.md index 4d1b912..f3e275e 100644 --- a/docs/operations/STAGING_CHECK.md +++ b/docs/operations/STAGING_CHECK.md @@ -36,34 +36,53 @@ Exit code: **0** = все PASS, **non-zero** = есть FAIL. ## Способы запуска -### 1. Внутри контейнера (рекомендуемый) - -```bash -docker exec orchestrator-staging \ - python3 /repos/orchestrator/scripts/staging_check.py --mode stub -``` - -### 2. С хоста (если есть токены в env) - -```bash -export ORCH_STAGING=true -export ORCH_PLANE_API_TOKEN=... -# ... остальные переменные ... - -python3 scripts/staging_check.py \ - --base-url http://localhost:8501 \ - --mode stub -``` - -### 3. Из docker exec с передачей URL +### 1. Внутри контейнера (КАНОНИЧЕСКИЙ — обязателен для деплоера) ```bash docker exec orchestrator-staging \ python3 /repos/orchestrator/scripts/staging_check.py \ - --base-url http://localhost:8501 \ - --mode stub + --base-url http://localhost:8501 --mode stub ``` +Это единственный канонический способ для стадии `deploy-staging` (ORCH-048, ADR-001). +Внутри контейнера env уже staging (`.env.staging`), а чек **B6** строит реестр проектов из +собственного process-env инстанса (см. ниже). Путь к скрипту — `/repos/orchestrator/scripts/…` +(bind-mount); `scripts/` **не** копируется в образ, поэтому `/app/scripts` не существует. + +### 2. С хоста — НЕ рекомендуется + +```bash +# ⚠️ Воспроизводит баг ORCH-048: на хосте ORCH_PROJECTS_JSON не задан → +# B6 строит реестр из дефолта (ET+ORCH) → ложный FAIL. +# Допустимо ТОЛЬКО если env хоста полностью повторяет staging (включая ORCH_PROJECTS_JSON). +export ORCH_STAGING=true +export ORCH_PROJECTS_JSON=... # обязателен, иначе B6 даст ложный FAIL +export ORCH_PLANE_API_TOKEN=... +# ... остальные переменные ... + +python3 scripts/staging_check.py --base-url http://localhost:8501 --mode stub +``` + +--- + +## Механика чека B6 (ORCH-048, ADR-001) + +B6 «Registry: sandbox present, prod ET/ORCH absent» подтверждает изоляцию: в реестре +работающего staging-инстанса есть только sandbox-проект и НЕТ боевых (ET/ORCH). + +- B6 импортирует `known_plane_project_ids()` из `src.projects` **кода контейнера** + (`/app/src` через `PYTHONPATH=/app`), env которого — `.env.staging`. Реестр отражает + именно работающий staging-инстанс. +- Прежний host-path хак (`sys.path.insert(0, "/repos/orchestrator")` + `importlib.reload`) + удалён: он подхватывал env процесса-запускателя и при запуске с хоста давал ложный FAIL. +- Логика вердикта вынесена в чистую функцию `_evaluate_b6(known) -> (passed, detail)`: + `passed ⟺ SANDBOX ∈ known ∧ PROD_ET ∉ known ∧ PROD_ORCH ∉ known`. Покрыта юнит-тестами + (`tests/test_staging_check_b6.py`) на оба исхода без поднятия инстанса/docker. +- При недоступности источника реестра B6 даёт детерминированный FAIL (не ложный PASS, + не необработанное исключение). + +**Поэтому B6 достоверен только при каноническом запуске (способ 1).** + --- ## Режимы (`--mode`) diff --git a/docs/work-items/ORCH-048/06-adr/ADR-001-b6-registry-via-in-container-run.md b/docs/work-items/ORCH-048/06-adr/ADR-001-b6-registry-via-in-container-run.md new file mode 100644 index 0000000..875876d --- /dev/null +++ b/docs/work-items/ORCH-048/06-adr/ADR-001-b6-registry-via-in-container-run.md @@ -0,0 +1,139 @@ +# ADR-001: B6 читает реестр через запуск suite ВНУТРИ staging-контейнера + +## Статус +Accepted + +- **Задача:** ORCH-048 +- **Дата:** 2026-06-06 +- **Автор:** architect +- **Решение варианта:** принято Владельцем проекта (Слава, 06.06) — вариант **(в)**. Архитектор фиксирует и обосновывает. + +## Контекст + +Чек **B6 «Registry: sandbox present, prod ET/ORCH absent»** в `scripts/staging_check.py` +(блок B, ~строки 263–284) — страховка изоляции staging: подтверждает, что в реестре +проектов работающего staging-инстанса есть только sandbox-проект и НЕТ боевых +(enduro-trails / orchestrator). + +B6 даёт **ложный FAIL** (`prod-ET=YES(BAD!)`, `prod-ORCH=YES(BAD!)`), хотя изоляция +реестра в staging исправна. Root cause (подтверждён прямым запуском, 06.06): + +```python +sys.path.insert(0, "/repos/orchestrator") # host-worktree path +import importlib +if "src.projects" in sys.modules: + importlib.reload(sys.modules["src.projects"]) # перечитывает env ТЕКУЩЕГО процесса +from src.projects import known_plane_project_ids +known = known_plane_project_ids() +``` + +B6 — единственный чек, который не ходит к инстансу по HTTP, а импортирует Python-код +локально и строит реестр из `ORCH_PROJECTS_JSON` **process-env того процесса, в котором +исполняется скрипт**. Деплоер фактически запускает suite **с хоста** +(`python3 scripts/staging_check.py --base-url http://localhost:8501`), где +`ORCH_PROJECTS_JSON` не задан → `src.projects` грузит встроенный `_DEFAULT_PROJECTS` +(ET + ORCH) → ложный FAIL. B6 проверяет реестр НЕ того окружения, реестр которого +реально использует staging-инстанс. + +### Топология (ключевой факт для решения) + +- Контейнер `orchestrator-staging`: `WORKDIR /app`, `ENV PYTHONPATH=/app`; код приложения + **скопирован** в образ (`Dockerfile: COPY src/ ./src/`) → живёт в `/app/src/`. +- `.env.staging` (env_file контейнера) задаёт `ORCH_PROJECTS_JSON` = только sandbox. +- `Dockerfile` **НЕ копирует** `scripts/` в образ. Скрипт доступен в контейнере только + через bind-mount `/home/slin/repos:/repos` → `/repos/orchestrator/scripts/staging_check.py`. + +Из этого следует: при запуске `docker exec orchestrator-staging python3 +/repos/orchestrator/scripts/staging_check.py` интерпретатор добавляет в `sys.path[0]` +каталог скрипта (`/repos/orchestrator/scripts`), а `import src.projects` резолвится через +`PYTHONPATH=/app` → `/app/src/projects.py` (собственный код контейнера) с env из +`.env.staging`. Это ровно реестр работающего staging-инстанса — без HTTP, без host-path хака. + +## Решение + +Принят **вариант (в): канонизировать запуск suite ВНУТРИ `orchestrator-staging` и читать +собственный process-env контейнера.** + +Архитектурно фиксируется (детальная реализация — стадия development): + +1. **Убрать из B6 host-path хак:** удалить `sys.path.insert(0, "/repos/orchestrator")` и + `importlib.reload(sys.modules["src.projects"])`. Импорт `from src.projects import + known_plane_project_ids` остаётся, но резолвится из кода контейнера (`/app/src` через + `PYTHONPATH=/app`), env которого — staging (`.env.staging`). + +2. **Канонизировать запуск suite внутри контейнера** (а не с хоста): + ```bash + docker exec orchestrator-staging \ + python3 /repos/orchestrator/scripts/staging_check.py \ + --base-url http://localhost:8501 --mode stub + ``` + `--base-url http://localhost:8501` корректен изнутри контейнера: сеть `network_mode: host`. + Путь к скрипту — `/repos/orchestrator/scripts/...` (mount), а НЕ `/app/scripts` (в образе + scripts отсутствует). + +3. **Синхронно обновить документацию запуска** (этот же PR), иначе host-запуск воспроизведёт + баг: + - `.openclaw/agents/deployer.md` — команда стадии `deploy-staging` через `docker exec`. + - `docs/operations/STAGING_CHECK.md` — канонический способ запуска и описание механики B6. + +4. **Логику вердикта B6 вынести в чистую функцию** `_evaluate_b6(known: set[str]) -> + tuple[bool, str]`, инвариант (TR-2): `passed ⟺ SANDBOX ∈ known ∧ PROD_ET ∉ known ∧ + PROD_ORCH ∉ known`; `detail` сохраняет формат `sandbox=…, prod-ET=…, prod-ORCH=…` (TR-3). + Функция юнит-тестируема без поднятия инстанса/docker (TC-01…TC-07). + +5. **Детерминированная деградация (TR-4):** при недоступности источника реестра (ошибка + импорта/построения `known`) B6 даёт FAIL с понятным detail, без необработанного исключения + и без ложного PASS. + +### Границы (scope guards — обязательны) + +- **НЕ** добавлять HTTP-эндпоинт `GET /projects`; **НЕ** трогать прод-`src/main.py`, + `src/webhooks/*`, `src/stage_engine.py`, `src/qg/*`. +- **НЕ** изменять `src/projects.py`, `.env`, `.env.staging`, `.env.example`. +- **НЕ** менять блоки A1–A3, B4, B5 и блок C (E2E): формат вывода и логика прежние. +- Реестр QG и стадий не меняется; ADR-0003 (`check_staging_status`) в силе — меняется только + достоверность одного чека внутри suite, чей агрегат пишется в `15-staging-log.md`. + +## Альтернативы (отклонены) + +### (а) HTTP-эндпоинт `GET /projects` работающего staging-инстанса — ОТКЛОНЁН +Порождает «курицу-яйцо»: B6 ходит на эндпоинт **работающего** инстанса, а эндпоинт запечён +в Docker-образ → на первом прогоне его в живом инстансе ещё нет (404) → ложный FAIL → откат. +Требует ручного bootstrap-деплоя. Это ровно тот класс поломки автономности, который мы +устраняем. Подтверждено на проде 06.06: `GET /projects` на 8501 → 404 → deploy-staging FAILED. +(Предыдущая итерация архитектора выбрала (а); решение отклонено Владельцем, код и ADR(а) +удалены, ветка откатана к analyst-артефактам.) + +### (б) `docker exec` subprocess + парсинг stdout — ОТКЛОНЁН +`docker exec orchestrator-staging python3 -c "..."` из процесса suite. Хрупкое экранирование +(`docs/history/LESSONS_2026-06-05.md`), зависимость от наличия docker-CLI и имени контейнера +в среде запуска, усложняет запуск «изнутри контейнера». + +### (в) Запуск suite внутри контейнера + собственный process-env — ВЫБРАН +B6 не зависит от того, что отдаёт инстанс по HTTP; `staging_check.py` берётся из mount (свежий +код сразу, без ребилда образа); реестр читается из env самого `orchestrator-staging`. Курицы-яйца +нет ни на первом прогоне, ни в будущем. Минимально инвазивно, прод-логика и `src/projects.py` не +тронуты. Согласуется с «рекомендуемым способом запуска» (`STAGING_CHECK.md §Способы запуска.1`). + +## Последствия + +**Плюсы** +- B6 достоверно отражает реестр работающего staging-инстанса; ложные FAIL/откаты устранены. +- Автономность self-hosting не ломается: нет bootstrap-зависимости от запечённого в образ кода. +- Свежий `staging_check.py` подхватывается из mount без ребилда образа. +- Защитная функция B6 сохранена и покрыта юнит-тестами на оба исхода (PASS/FAIL). + +**Минусы / ограничения** +- Запуск suite **обязан** идти через `docker exec` внутри `orchestrator-staging`. Запуск с + хоста воспроизведёт исходный баг (host-env без `ORCH_PROJECTS_JSON`). Это закреплено в + `deployer.md` и `STAGING_CHECK.md`; способ «с хоста» остаётся возможен, только если env + хоста корректно повторяет staging (не рекомендуется, помечено). +- Деплоер должен иметь доступ к docker-CLI/сокету (есть: `/var/run/docker.sock` смонтирован в + контейнер оркестратора, у которого deployer-агент исполняется; `deployer.md` tools: Bash docker). + +## Связи +- ADR-0003 (`docs/architecture/adr/adr-0003-staging-gate.md`) — staging-гейт, который этот чек + обслуживает. +- ORCH-6 / `src/projects.py` — реестр проектов (источник `known_plane_project_ids()`), + НЕ изменяется. +- `docs/history/LESSONS_2026-06-05.md` — обоснование отказа от варианта (б).