# 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` — обоснование отказа от варианта (б).
