# STAGING_CHECK.md — Инструкция по запуску staging check suite (ORCH-33)

## Что это

`scripts/staging_check.py` — самостоятельный скрипт проверки **живого** staging-стенда orchestrator (порт 8501). Не unit-тесты — реальные HTTP-вызовы против работающих сервисов.

Три блока проверок:

| Блок | Название | Что проверяет |
|------|----------|---------------|
| A    | SMOKE    | `/health`, `/queue`, `ORCH_STAGING=true` |
| B    | ACCESS   | Plane sandbox (R), Gitea sandbox (R+push), реестр проектов |
| C    | E2E      | Создать задачу → триггер конвейера → ветка + коммент → cleanup |

Exit code: **0** = advance (все REAL-проверки PASS), **1** = rollback (есть REAL-FAIL).
С ORCH-061 exit 0 может включать *waived* sandbox-infra FAIL (C9a/C9b) — см.
[«Толерантность к sandbox-infra (ORCH-061)»](#толерантность-к-sandbox-infra-orch-061).

---

## Требования к окружению

Скрипт читает токены/URL из env (те же переменные, что использует orchestrator):

| Переменная | Описание |
|-----------|----------|
| `ORCH_STAGING` | Должна быть `true` — защита от случайного запуска на проде |
| `ORCH_PLANE_API_TOKEN` | Plane API token (`X-API-Key`) |
| `ORCH_PLANE_API_URL` | Plane base URL **без** `/api/v1` (скрипт добавляет сам) |
| `ORCH_PLANE_WORKSPACE_SLUG` | Workspace slug (`ag_proj`) |
| `ORCH_GITEA_TOKEN` | Gitea token (`Authorization: token …`) |
| `ORCH_GITEA_URL` | Gitea base URL (`http://localhost:3000`) |
| `ORCH_PLANE_WEBHOOK_SECRET` | HMAC-секрет для подписи `/webhook/plane` (если пустой — без подписи) |

Все эти переменные **уже есть** внутри контейнера `orchestrator-staging`.

---

## Способы запуска

### 1. Внутри контейнера (КАНОНИЧЕСКИЙ — обязателен для деплоера)

```bash
docker exec orchestrator-staging \
  python3 /repos/orchestrator/scripts/staging_check.py \
  --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).**

---

## Толерантность к sandbox-infra (ORCH-061)

**Проблема.** Self-hosting `orchestrator` зацикливался на `deploy-staging → development`:
прежде скрипт давал exit 1 при **любом** FAIL, поэтому две чисто инфраструктурные
проверки — **C9a** (ветка не появилась в `orchestrator-sandbox`) и **C9b** (job
аналитика не встал в очередь staging) — приводили к `staging_status: FAILED` →
откат → цикл. Корень: SANDBOX-бот-аккаунты не состоят в sandbox-проекте Plane,
поэтому шаги 6+ конвейера в песочнице недостижимы. Это **не** регресс конвейера.

**Решение.** Проверки классифицируются на две категории (`src/staging_verdict.py`):

| Категория | Что входит | Поведение |
|-----------|-----------|-----------|
| `REAL` | все проверки конвейера (A*, B*, C7, C8) | **fail-closed** — любой FAIL = rollback |
| `SANDBOX_INFRA` | строго allowlist `{C9a, C9b}` | **waivable** — FAIL терпится, если все REAL зелёные |

Вердикт сворачивается в `compute_staging_verdict(items, infra_tolerant)`:

- любой REAL-FAIL → `FAILED` / exit 1 (страховка сохраняется при ЛЮБОМ значении флага);
- упали **только** C9a/C9b и толерантность включена → `SUCCESS` / exit 0,
  упавшие метки попадают в `waived` (наблюдаемость, печатается строкой `INFRA-WAIVED:`);
- упали только C9a/C9b, толерантность выключена → `FAILED` / exit 1 (legacy-строгий);
- любая внутренняя ошибка вердикта → `FAILED` / exit 1 (никогда не ложный green).

Blast-radius waiver-а ровно две allowlist-метки; всё неизвестное классифицируется
как `REAL` (fail-closed).

### Kill-switch и `--strict`

| Управление | Эффект |
|-----------|--------|
| env `ORCH_STAGING_INFRA_TOLERANCE_ENABLED` (default `true`) | глобальный флаг; `false` → строгий режим (1:1 до ORCH-061) |
| CLI `--strict` | форсит строгий режим для одного запуска, игнорируя env |

Флаг живёт в `.env.staging` (staging-инстанс). `--strict` имеет приоритет над env.

### Что печатает скрипт

В конце прогона `summary()` показывает разбивку REAL/SANDBOX_INFRA, затем:

```
INFRA-WAIVED: C9a Branch appears in orchestrator-sandbox; C9b Analyst job enqueued ...
VERDICT: SUCCESS (infra-waived): ['C9a …', 'C9b …'] are known sandbox-infra checks; all real checks green
```

Контракт `staging_status: SUCCESS|FAILED` во frontmatter **не меняется** —
толерантность применяется в скрипте ДО записи артефакта деплоером.

---

## Режимы (`--mode`)

| Режим | Описание | Скорость |
|-------|----------|----------|
| `stub` (дефолт) | Проверяет **ранние артефакты** конвейера: ветка + QG-0-коммент. Создаются ДО запуска Claude CLI → быстро, детерминированно, без расхода LLM-кредитов. | ~30-90 сек |
| `full-real` | Дополнительно ждёт реального завершения аналитика. Долго, расходует LLM-кредиты. | 5-15+ мин |

**Текущий дефолт: `stub`** — достаточен для проверки работоспособности стенда.

---

## Что проверяет блок C (E2E) и почему это безопасно

Порядок `start_pipeline` в коде orchestrator:
1. Resolve проекта из реестра
2. Получить name/description из Plane API (если в webhook пустые)
3. **QG-0 гейт** (name ≥ 5 симв, description ≥ 20 симв)
4. **Создать work_item_id + ветку в Gitea + начальные доки**
5. **Записать строку задачи в БД**
6. Поставить аналитика в очередь (вот тут Claude CLI)

Блок C проверяет **шаги 4-5**, аналитика (шаг 6) **не ждёт**.  
Тест-задача создаётся ТОЛЬКО в **SANDBOX** (`project_id 8c5a3025-...`),  
ветка создаётся ТОЛЬКО в **orchestrator-sandbox**.

### CLEANUP (обязателен)

`try/finally` гарантирует удаление тестовых артефактов:
- Удаляет ветку из `orchestrator-sandbox`
- Удаляет задачу из Plane SANDBOX

Cleanup отрабатывает даже при падении e2e.

---

## Принцип HMAC-подписи

Скрипт читает `ORCH_PLANE_WEBHOOK_SECRET` из env и формирует подпись:
```python
hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
```
Передаёт как заголовок `X-Plane-Signature`. Алгоритм совпадает с `verify_plane_signature` в `src/webhooks/plane.py`.

---

## Изолированность от прода

| Проверка | Гарантия |
|---------|---------|
| A3 `ORCH_STAGING=true` | При false — abort до деструктивных блоков |
| B6 Реестр без боевых | ET/ORCH project_id absent в `known_plane_project_ids()` |
| C: only SANDBOX project_id | Webhook payload указывает только `8c5a3025-...` |
| C: only orchestrator-sandbox repo | Gitea operations на `admin/orchestrator-sandbox` |
| C: cleanup в finally | Артефакты удаляются даже при ошибке |

---

## Добавление в деплой-хук

```bash
# В deploy.sh, после docker-compose up -d orchestrator-staging
docker exec orchestrator-staging \
  python3 /repos/orchestrator/scripts/staging_check.py --mode stub
if [ $? -ne 0 ]; then
  echo "Staging check FAILED — rolling back"
  exit 1
fi
```
