analyst(ET): auto-commit from analyst run_id=145
All checks were successful
CI / test (push) Successful in 13s
CI / test (pull_request) Successful in 13s

This commit is contained in:
2026-06-06 05:06:33 +00:00
parent 9538103eff
commit 8b5b1f0056
4 changed files with 345 additions and 0 deletions

View File

@@ -0,0 +1,86 @@
# 01 — Business Requirements Document (BRD)
**Work Item:** ORCH-048
**Title:** staging B6 check reads registry from host worktree, not staging container
**Stage:** analysis
**Author:** analyst
**Date:** 2026-06-06
---
## 1. Контекст и проблема
`scripts/staging_check.py` — suite живых проверок staging-стенда orchestrator (порт 8501, ADR-0003). Деплоер запускает его на стадии `deploy-staging` и пишет `staging_status:` в `15-staging-log.md`. FAIL любого чека = откат на `development`.
Блок B содержит проверку **B6 «Registry: sandbox present, prod ET/ORCH absent»** — она должна подтверждать, что в staging-реестре проектов есть только sandbox-проект и НЕТ боевых проектов (enduro-trails / orchestrator). Это страховка изоляции: staging не должен обслуживать прод-проекты.
**B6 даёт ложный FAIL** (`prod-ET=YES(BAD!)`, `prod-ORCH=YES(BAD!)`), хотя сама изоляция реестра в staging РАБОТАЕТ корректно.
### Root cause (подтверждён прямым запуском, Стрим, 06.06)
- Внутри контейнера `orchestrator-staging` `known_plane_project_ids()` корректно отдаёт `count=1, sandbox=True, ET=False, ORCH=False`. `.env.staging` верно задаёт `ORCH_PROJECTS_JSON` = только sandbox. **Изоляция реестра исправна.**
- Все остальные чеки (A1A3, B4, B5, блок C E2E) обращаются к работающему staging-инстансу по HTTP и **зелёные**.
- **B6 — единственный чек, который не ходит по HTTP, а импортирует Python-код локально.** В блоке B6 (строки ~263284) выполняется:
```python
sys.path.insert(0, "/repos/orchestrator") # ХОСТ-worktree
importlib.reload(sys.modules["src.projects"]) # подхватывает env ТЕКУЩЕГО процесса
from src.projects import known_plane_project_ids
```
- Деплоер по факту запускает скрипт **с хоста** (`.openclaw/agents/deployer.md`: `python3 scripts/staging_check.py --base-url http://localhost:8501`). В env хост-процесса `ORCH_PROJECTS_JSON` НЕ задан → `src.projects` грузит встроенный `_DEFAULT_PROJECTS` (ET + ORCH) → `known_plane_project_ids()` возвращает боевые id → **ложный FAIL**.
- Иными словами, B6 проверяет реестр НЕ того окружения, реестр которого реально использует staging-инстанс. Гипотеза деплоера про misconfig staging-контейнера — **опровергнута**.
## 2. Бизнес-цель
B6 должен достоверно отражать реестр проектов **именно работающего staging-инстанса** (изолированное окружение), а не реестр, восстановленный из локального импорта в произвольном process-env. При этом B6 обязан по-прежнему ловить реальное нарушение изоляции.
## 3. Заинтересованные стороны
| Роль | Интерес |
|------|---------|
| Deployer-агент | Достоверный сигнал staging-гейта; нет ложных откатов на development |
| Owner / прод | Изоляция staging от прод-проектов реально проверяется (не ложно-зелёная и не ложно-красная) |
| Self-hosting pipeline | `deploy-staging` — обязательная страховка перед прод-деплоем орка; ложный FAIL блокирует доставку всех ORCH-задач |
## 4. Объём (Scope)
### В объёме
- Исправление блока B6 в `scripts/staging_check.py`, чтобы он читал реестр в окружении staging-инстанса.
- Тест на корректность B6: оба исхода (PASS при чистой изоляции; FAIL при попадании прод-проекта в staging-реестр).
- Обновление документации B6 (`docs/operations/STAGING_CHECK.md`, при необходимости `docs/architecture/README.md`/CHANGELOG) в том же PR.
### Вне объёма (НЕ ТРОГАТЬ)
- `src/projects.py` — реестр работает корректно.
- `.env` / `.env.staging` — конфигурация верна.
- Прод-логика оркестратора.
- Остальные staging-чеки B1B5 и блок C E2E — зелёные.
## 5. Бизнес-требования
| ID | Требование |
|----|------------|
| BR-1 | B6 на staging даёт PASS (`sandbox=YES`, `prod-ET=NO`, `prod-ORCH=NO`), читая реестр из окружения staging-инстанса, а не из локального импорта хост-worktree. |
| BR-2 | B6 по-прежнему детектирует реальное нарушение изоляции: если бы прод-проект реально попал в staging-реестр, B6 обязан выдать FAIL. |
| BR-3 | Остальные staging-чеки не сломаны; `src/projects.py` и `.env*` не изменяются. |
| BR-4 | Существующие unit-тесты остаются зелёными (`pytest tests/ -q`). |
| BR-5 | Документация B6 обновлена в том же PR (golden source). |
## 6. Допущения и ограничения
- Решение должно быть минимально инвазивным и не затрагивать прод-логику.
- Скрипт `scripts/staging_check.py` использует только stdlib (нет `requests`/`httpx`) — это конвенция файла, её нужно сохранить.
- Способ запуска suite может варьироваться (с хоста / `docker exec` внутри контейнера) — выбранное решение должно быть корректным для канонического способа запуска деплоером и задокументировано.
## 7. Критерий успеха (бизнес)
- staging-прогон `scripts/staging_check.py` → **B6 PASS** при работающей изоляции.
- При искусственно нарушенной изоляции → **B6 FAIL** (проверяется тестом, без реального изменения staging).
- `python -m pytest tests/ -q` — зелёный.
## 8. Открытые вопросы (для архитектора)
Бизнес-запрос предлагает три варианта реализации (выбор за архитектором, см. 02-trz §4):
- (а) B6 читает реестр через HTTP-эндпоинт staging-инстанса;
- (б) B6 выполняет проверку через subprocess в окружении staging-контейнера (`docker exec`);
- (в) staging_check запускается ВНУТРИ staging-контейнера и читает собственный process-env (убрать host-path хак).
Предпочтение бизнес-запроса: минимально инвазивный вариант, не трогающий прод-логику.

View File

@@ -0,0 +1,95 @@
# 02 — Техническое задание (ТЗ / TRZ)
**Work Item:** ORCH-048
**Title:** staging B6 check reads registry from host worktree, not staging container
**Stage:** analysis
**Author:** analyst
**Date:** 2026-06-06
> Это ТЗ фиксирует требования и инварианты. Выбор одного из трёх архитектурных вариантов (§4) — за архитектором (ADR). Анализ варианты НЕ выбирает.
---
## 1. Задействованные модули
| Путь | Роль | Характер изменений |
|------|------|--------------------|
| `scripts/staging_check.py` | Suite живых staging-проверок; блок B6 (~строки 263284) | **Изменяется** — переписать механику получения реестра в B6 |
| `tests/` (новый файл, напр. `tests/test_staging_check_b6.py`) | Unit-тест корректности B6 | **Создаётся** |
| `docs/operations/STAGING_CHECK.md` | Док запуска suite | **Обновляется** (описание B6 + способ запуска) |
| `docs/architecture/README.md` / `CHANGELOG.md` | Golden source | **Обновляется** при необходимости |
### НЕ изменять (жёсткий инвариант scope)
- `src/projects.py` — реестр работает корректно.
- `.env`, `.env.staging`, `.env.example` — конфиг верен.
- Прод-логику оркестратора (`src/main.py` прод-роуты, `src/webhooks/*`, `src/stage_engine.py`, `src/qg/*`) — кроме случая варианта (а), если архитектор решит добавить read-only эндпоинт (см. §4а, отдельно обоснованный риск).
- Блоки A1A3, B4, B5 и блок C E2E в `staging_check.py`.
## 2. Текущее поведение (то, что чиним)
Блок B6 (`scripts/staging_check.py`):
```python
sys.path.insert(0, "/repos/orchestrator") # хост-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()
```
Проблема: реестр строится из `ORCH_PROJECTS_JSON` **process-env того процесса, в котором исполняется скрипт**. При запуске деплоером с хоста (`python3 scripts/staging_check.py --base-url http://localhost:8501`) переменная не задана → `_DEFAULT_PROJECTS` (ET+ORCH) → ложный FAIL. B6 не отражает реестр работающего staging-инстанса.
## 3. Требуемое поведение (контракт B6)
| ID | Требование |
|----|------------|
| TR-1 | B6 определяет набор «известных staging-инстансу Plane project id» из источника, который **гарантированно отражает окружение работающего staging-инстанса** (порт 8501 / контейнер `orchestrator-staging`), а не из локального импорта в process-env скрипта. |
| TR-2 | B6 PASS ⟺ `SANDBOX_PROJECT_ID ∈ known` И `PROD_ET_PROJECT_ID ∉ known` И `PROD_ORCH_PROJECT_ID ∉ known`. Идентификаторы — те же константы, что уже в скрипте. |
| TR-3 | B6 сохраняет формат вывода `Results.add(label, passed, detail)` с человекочитаемым detail (`sandbox=…, prod-ET=…, prod-ORCH=…`). |
| TR-4 | При недоступности источника реестра B6 даёт **детерминированный FAIL** с понятным detail (не падает с необработанным исключением, не даёт ложный PASS). |
| TR-5 | Скрипт остаётся на stdlib (без сторонних зависимостей), если выбранный вариант это допускает. |
| TR-6 | Удаляется зависимость B6 от хардкод-пути `/repos/orchestrator` для построения реестра (host-path хак), несовместимого с целью проверки. |
## 4. Варианты реализации (выбор — архитектор, в ADR)
Бизнес-запрос предлагает три варианта. Анализ перечисляет их с известными плюсами/минусами; решение и обоснование — в `06-adr/`.
### (а) HTTP-эндпоинт staging-инстанса
B6 запрашивает реестр у работающего staging-инстанса по HTTP (как делают A/B4/B5/C).
- **Сейчас подходящего эндпоинта НЕТ.** `/health`, `/status`, `/queue` реестр проектов не отдают (`src/main.py`).
- Потребуется добавить read-only эндпоинт (напр. `GET /projects`, отдающий `known_plane_project_ids()` или список репо/prefix). Это касается прод-`main.py` → выходит за «не трогать прод-логику», но изменение read-only и низкорисковое — архитектор взвешивает.
- Плюс: B6 гарантированно читает реестр именно того процесса, что обслуживает webhooks. Единый HTTP-стиль с остальными чеками.
### (б) Subprocess в окружении staging-контейнера
B6 выполняет `docker exec orchestrator-staging python3 -c "from src.projects import known_plane_project_ids; ..."` и парсит stdout.
- Плюс: не трогает прод-`main.py`; читает env контейнера напрямую.
- Минус: требует доступности docker-CLI и имени контейнера из среды запуска suite; усложняет запуск «изнутри контейнера»; есть нюансы экранирования (см. `docs/history/LESSONS_2026-06-05.md`).
### (в) Запуск suite внутри контейнера + чтение собственного process-env
Канонизировать запуск `staging_check.py` ВНУТРИ `orchestrator-staging` (`docker exec orchestrator-staging python3 …`), убрать `sys.path.insert(0, "/repos/orchestrator")`, импортировать `src.projects` из кода контейнера (его cwd/PYTHONPATH), env уже staging.
- Плюс: минимально инвазивно, не трогает прод-логику и `src.projects`; согласуется с «рекомендуемым способом запуска» в `STAGING_CHECK.md §Способы запуска.1`.
- Условие: деплоер должен запускать suite через `docker exec` (а не с хоста). Нужно синхронно обновить `.openclaw/agents/deployer.md` и `STAGING_CHECK.md`, иначе host-запуск воспроизведёт баг.
- Нюанс: внутри контейнера код лежит в `/app` (Dockerfile `COPY`), а `/repos/orchestrator` — отдельный mount; импорт должен резолвиться из кода, чьим env реально живёт инстанс.
## 5. Изменения API
- Варианты (б) и (в): **нет** изменений API.
- Вариант (а): новый read-only эндпоинт (напр. `GET /projects`) — точная схема ответа определяется архитектором. Если выбран — задокументировать в `docs/architecture/README.md` (таблица API) и `CHANGELOG.md`.
## 6. Изменения схемы БД
Нет.
## 7. Требования к новым QG checks
Нет новых QG. Поведение `check_staging_status` (ADR-0003) не меняется — меняется только достоверность одного из чеков suite, чей агрегат пишется в `15-staging-log.md`.
## 8. Артефакты pipeline, создаваемые/обновляемые
- Код: `scripts/staging_check.py` (B6), новый тест в `tests/`.
- Док: `docs/operations/STAGING_CHECK.md`; при выборе варианта (а) — `docs/architecture/README.md` (API) и `CHANGELOG.md`; при выборе (в) — `.openclaw/agents/deployer.md` (способ запуска) и `STAGING_CHECK.md`.
- ADR: `docs/work-items/ORCH-048/06-adr/ADR-001-*.md` — обоснование выбранного варианта.
## 9. Тестируемость
- Логика «PASS/FAIL по набору known id» B6 должна быть выделена в чистую, юнит-тестируемую функцию (напр. `_evaluate_b6(known: set[str]) -> tuple[bool, str]`), чтобы тест проверял оба исхода без поднятия staging-инстанса/docker. План — `04-test-plan.yaml`.
## 10. Definition of Done
- BR-1…BR-5 (01-brd) выполнены.
- staging-прогон → B6 PASS; `pytest tests/ -q` зелёный.
- Док и (при необходимости) ADR обновлены в том же PR.

View File

@@ -0,0 +1,67 @@
# 03 — Критерии приёмки (Acceptance Criteria)
**Work Item:** ORCH-048
**Title:** staging B6 check reads registry from host worktree, not staging container
**Stage:** analysis
**Author:** analyst
**Date:** 2026-06-06
Каждый критерий формулирует чёткое условие PASS/FAIL. Источник — бизнес-запрос ORCH-048 (AC-1…AC-4) + BRD.
---
## AC-1 — B6 PASS на staging, читая реестр из staging-окружения
**Условие PASS:**
- При staging-прогоне `scripts/staging_check.py` (канонический способ запуска, выбранный архитектором) чек **B6** выдаёт `✓ PASS` c detail `sandbox=YES, prod-ET=NO(good), prod-ORCH=NO(good)`.
- Набор known id, по которому судит B6, получен из окружения работающего staging-инстанса (HTTP-эндпоинт / docker-окружение контейнера / собственный process-env при запуске внутри контейнера), **не** из локального импорта `src.projects` в произвольном process-env с host-path хаком `/repos/orchestrator`.
**FAIL, если:** B6 даёт ложный FAIL (`prod-ET=YES(BAD!)` / `prod-ORCH=YES(BAD!)`) при фактически исправной изоляции; либо реестр в B6 по-прежнему строится локальным импортом, зависящим от env процесса-запускателя.
## AC-2 — B6 ловит РЕАЛЬНОЕ нарушение изоляции (оба исхода покрыты тестом)
**Условие PASS:**
- Существует unit-тест, проверяющий логику вердикта B6 на **двух** входах:
1. «чистый» staging-реестр (`known = {SANDBOX}`) → B6 вердикт **PASS**;
2. «загрязнённый» реестр (например `known = {SANDBOX, PROD_ET}` и/или `{SANDBOX, PROD_ORCH}`) → B6 вердикт **FAIL**.
- Тест не требует поднятия живого staging-инстанса/docker (логика вердикта изолирована и тестируема, см. 02-trz §9).
**FAIL, если:** покрыт только один исход; либо B6 даёт PASS при наличии прод-проекта в реестре (потеря защитной функции).
## AC-3 — Остальные staging-чеки не сломаны; src/projects.py и .env не тронуты
**Условие PASS:**
- Блоки A1A3, B4, B5 и блок C (E2E) в `scripts/staging_check.py` функционально не изменены (формат вывода и логика прежние).
- `git diff` work item НЕ содержит изменений в `src/projects.py`, `.env`, `.env.staging`, `.env.example`.
- Прод-логика оркестратора не затронута. Исключение допускается только если архитектор в ADR выбрал вариант (а) и добавил read-only эндпоинт — тогда изменение ограничено добавлением этого эндпоинта, прод-поведение существующих роутов неизменно.
**FAIL, если:** изменён `src/projects.py` или любой `.env*`; либо затронута/сломана логика прочих чеков.
## AC-4 — Существующие unit-тесты зелёные
**Условие PASS:**
- `python -m pytest tests/ -q` завершается с кодом 0; все ранее зелёные тесты остаются зелёными; новый тест B6 (AC-2) проходит.
**FAIL, если:** любой тест падает.
## AC-5 — Документация обновлена в том же PR (golden source)
**Условие PASS:**
- `docs/operations/STAGING_CHECK.md` отражает исправленную механику B6 и канонический способ запуска suite.
- При выборе варианта (а): обновлены таблица API в `docs/architecture/README.md` и `CHANGELOG.md`.
- При выборе варианта (в): обновлены `.openclaw/agents/deployer.md` (запуск через `docker exec`) и `STAGING_CHECK.md`.
- Заведён ADR `docs/work-items/ORCH-048/06-adr/ADR-001-*.md` с обоснованием выбранного варианта.
**FAIL, если:** код изменён, а соответствующая док/ADR не обновлены.
---
## Сводная проверка (как мерить приёмку)
| AC | Команда / действие | Ожидаемый результат |
|----|--------------------|---------------------|
| AC-1 | staging-прогон suite (выбранным способом) | `B6 … ✓ PASS [sandbox=YES, prod-ET=NO(good), prod-ORCH=NO(good)]` |
| AC-2 | `pytest tests/test_staging_check_b6.py -q` | оба кейса (clean→PASS, polluted→FAIL) зелёные |
| AC-3 | `git diff --name-only` по ветке | нет `src/projects.py`, нет `.env*`; чеки A/B4/B5/C не изменены по сути |
| AC-4 | `python -m pytest tests/ -q` | exit 0, все PASS |
| AC-5 | ревью diff документации | STAGING_CHECK.md + ADR-001 присутствуют и согласованы с кодом |

View File

@@ -0,0 +1,97 @@
work_item: ORCH-048
title: staging B6 check reads registry from host worktree, not staging container
stage: analysis
notes: >
B6 в staging_check.py должен оценивать реестр окружения работающего staging-инстанса.
Для тестируемости логика вердикта B6 выделяется в чистую функцию (напр.
_evaluate_b6(known: set[str]) -> tuple[bool, str]); тесты бьют именно её и не
поднимают живой staging-инстанс/docker. Идентификаторы — те же константы из скрипта:
SANDBOX_PROJECT_ID=8c5a3025-4f9d-4190-b79f-fa06276bb27e,
PROD_ET_PROJECT_ID=7a79f0a9-5278-49cd-9007-9a338f238f9c,
PROD_ORCH_PROJECT_ID=8da6aa25-a60e-44d6-a1e2-d8ae59aa7d6a.
tests:
- id: TC-01
type: unit
description: >
B6-вердикт PASS при чистом staging-реестре: known={SANDBOX} ->
passed=True, detail содержит sandbox=YES, prod-ET=NO, prod-ORCH=NO. (AC-1, AC-2)
module: tests/test_staging_check_b6.py
expected: PASS
- id: TC-02
type: unit
description: >
B6-вердикт FAIL при попадании прод-ET в реестр: known={SANDBOX, PROD_ET} ->
passed=False, detail помечает prod-ET как нарушение. (AC-2)
module: tests/test_staging_check_b6.py
expected: PASS
- id: TC-03
type: unit
description: >
B6-вердикт FAIL при попадании прод-ORCH в реестр: known={SANDBOX, PROD_ORCH} ->
passed=False, detail помечает prod-ORCH как нарушение. (AC-2)
module: tests/test_staging_check_b6.py
expected: PASS
- id: TC-04
type: unit
description: >
B6-вердикт FAIL при отсутствии sandbox в реестре: known=set() (пусто) ->
passed=False (sandbox absent), детерминированно, без исключения. (AC-2, TR-4)
module: tests/test_staging_check_b6.py
expected: PASS
- id: TC-05
type: unit
description: >
B6-вердикт FAIL при загрязнении и ET, и ORCH одновременно:
known={SANDBOX, PROD_ET, PROD_ORCH} -> passed=False. (AC-2)
module: tests/test_staging_check_b6.py
expected: PASS
- id: TC-06
type: unit
description: >
Источник реестра в B6 больше не зависит от host-path хака
sys.path.insert(0,"/repos/orchestrator"): проверить (статически/через структуру
кода или мок источника), что построение known не делается локальным импортом
src.projects из произвольного process-env. (AC-1, TR-6)
module: tests/test_staging_check_b6.py
expected: PASS
- id: TC-07
type: unit
description: >
Деградация источника реестра (HTTP-ошибка / недоступный контейнер / битый ответ)
-> B6 даёт детерминированный FAIL с понятным detail, а не ложный PASS и не
необработанное исключение. (TR-4)
module: tests/test_staging_check_b6.py
expected: PASS
- id: TC-08
type: unit
description: >
Регрессия реестра: существующие тесты src/projects.py остаются зелёными,
подтверждая, что src/projects.py не изменён. (AC-3, AC-4)
module: tests/test_projects.py
expected: PASS
- id: TC-09
type: integration
description: >
Полный прогон pytest без падений после правок:
`python -m pytest tests/ -q` -> exit 0. (AC-4)
module: tests/
expected: PASS
- id: TC-10
type: integration
description: >
Живой staging-прогон (ручной, вне CI): запустить scripts/staging_check.py
выбранным архитектором способом против orchestrator-staging (8501) ->
B6 == PASS (sandbox=YES, prod-ET=NO, prod-ORCH=NO); блоки A/B4/B5/C не сломаны.
(AC-1, AC-3) Выполняется деплоером на стадии deploy-staging.
module: scripts/staging_check.py
expected: PASS