From 8b5b1f00569fd71fde623fefaa9e397bfb54a7be Mon Sep 17 00:00:00 2001 From: claude-bot Date: Sat, 6 Jun 2026 05:06:33 +0000 Subject: [PATCH] analyst(ET): auto-commit from analyst run_id=145 --- docs/work-items/ORCH-048/01-brd.md | 86 ++++++++++++++++ docs/work-items/ORCH-048/02-trz.md | 95 ++++++++++++++++++ .../ORCH-048/03-acceptance-criteria.md | 67 +++++++++++++ docs/work-items/ORCH-048/04-test-plan.yaml | 97 +++++++++++++++++++ 4 files changed, 345 insertions(+) create mode 100644 docs/work-items/ORCH-048/01-brd.md create mode 100644 docs/work-items/ORCH-048/02-trz.md create mode 100644 docs/work-items/ORCH-048/03-acceptance-criteria.md create mode 100644 docs/work-items/ORCH-048/04-test-plan.yaml diff --git a/docs/work-items/ORCH-048/01-brd.md b/docs/work-items/ORCH-048/01-brd.md new file mode 100644 index 0000000..fad309d --- /dev/null +++ b/docs/work-items/ORCH-048/01-brd.md @@ -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. **Изоляция реестра исправна.** +- Все остальные чеки (A1–A3, B4, B5, блок C E2E) обращаются к работающему staging-инстансу по HTTP и **зелёные**. +- **B6 — единственный чек, который не ходит по HTTP, а импортирует Python-код локально.** В блоке B6 (строки ~263–284) выполняется: + ```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-чеки B1–B5 и блок 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 хак). + +Предпочтение бизнес-запроса: минимально инвазивный вариант, не трогающий прод-логику. diff --git a/docs/work-items/ORCH-048/02-trz.md b/docs/work-items/ORCH-048/02-trz.md new file mode 100644 index 0000000..5117ceb --- /dev/null +++ b/docs/work-items/ORCH-048/02-trz.md @@ -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 (~строки 263–284) | **Изменяется** — переписать механику получения реестра в 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а, отдельно обоснованный риск). +- Блоки A1–A3, 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. diff --git a/docs/work-items/ORCH-048/03-acceptance-criteria.md b/docs/work-items/ORCH-048/03-acceptance-criteria.md new file mode 100644 index 0000000..1756935 --- /dev/null +++ b/docs/work-items/ORCH-048/03-acceptance-criteria.md @@ -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:** +- Блоки A1–A3, 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 присутствуют и согласованы с кодом | diff --git a/docs/work-items/ORCH-048/04-test-plan.yaml b/docs/work-items/ORCH-048/04-test-plan.yaml new file mode 100644 index 0000000..38345fe --- /dev/null +++ b/docs/work-items/ORCH-048/04-test-plan.yaml @@ -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