diff --git a/docs/work-items/ORCH-021/01-brd.md b/docs/work-items/ORCH-021/01-brd.md new file mode 100644 index 0000000..602578c --- /dev/null +++ b/docs/work-items/ORCH-021/01-brd.md @@ -0,0 +1,88 @@ +# BRD — ORCH-021: Post-deploy мониторинг прода + авто-rollback при деградации + +Work Item: ORCH-021 +Приоритет: высокий (★) +Источник: предложение Стрим, одобрено Славой (2026-06-04) +Стадия: analysis + +## 1. Проблема (Why) + +Сейчас конвейер заканчивается на `deploy → done`: как только `check_deploy_status` +видит `deploy_status: SUCCESS`, задача закрывается и оркестратор **забывает про прод**. +«Успех» деплоя сегодня означает только то, что health-check в момент рестарта +прошёл (10×6с в `scripts/orchestrator-deploy-hook.sh`) — узкое окно ~60 секунд. + +**Прямой урок ET-8:** деплой отрапортовал SUCCESS, а на проде фича не работала. +Класс инцидентов — «зелёный деплой, красный прод»: +- деградация проявляется через минуты, а не в первые 60с (прогрев кэшей, фоновые + миграции, отложенные запросы, утечки, рост 5xx под реальным трафиком); +- health-эндпоинт отвечает `200 ok`, но ключевая функциональность сломана; +- регресс виден только под боевым трафиком, которого нет в момент рестарта. + +После закрытия задачи никакого пригляда за продом нет — деградацию замечает человек +постфактум. Для self-hosting это особенно опасно: сломанный прод-орк (8500) обслуживает +ВСЕ проекты (enduro-trails) из общего инстанса. + +## 2. Цель (What) + +Продлить ответственность конвейера за прод **после** `deploy → done`: в течение +заданного окна наблюдать ключевые сигналы здоровья прода и при доказанной деградации +выполнить реакцию (откат на предыдущий образ или громкий алерт с запросом ручного +отката). Закрыть класс «зелёный деплой, красный прод». + +Механизм частичного отката уже есть: `do_rollback()` и режим `--rollback` в +`scripts/orchestrator-deploy-hook.sh` умеют вернуть предыдущий образ из +`PREV_IMAGE_FILE` (`.deploy-prev-image-prod`), который сохраняется при каждом деплое. +Задача — построить **наблюдение поверх** этого и привязать решение к измеримым порогам. + +## 3. Заинтересованные стороны +- **Owner (Слава)** — принимает риск авто-отката прода; получает алерты. +- **Стрим** — инициатор; потребитель сигнала деградации для петли уроков (ORCH-8). +- **Другие проекты (enduro-trails)** — косвенно: устойчивость общего инстанса. + +## 4. Бизнес-требования + +| # | Требование | Приоритет | +|---|------------|-----------| +| BR-1 | После `deploy → done` прод наблюдается в течение конфигурируемого окна (дефолт ~15 мин), а не забывается. | Must | +| BR-2 | Деградация определяется по **детерминированным измеримым сигналам**: периодический `/health` (HTTP 200 + `{"status":"ok"}`) и доля HTTP 5xx на ключевых эндпоинтах (`/status`, `/queue`). | Must | +| BR-3 | Деградация фиксируется только по **порогам** (N последовательных провалов / окно), а не по разовому сетевому глюку — чтобы не было ложных откатов. | Must | +| BR-4 | При подтверждённой деградации система выполняет реакцию: **авто-rollback** на `.deploy-prev-image-prod` (через существующий хук `--rollback`) **либо** громкий алерт с запросом ручного отката — в зависимости от политики репозитория. | Must | +| BR-5 | **Self-hosting safety:** для самого `orchestrator` авто-откат прода = рестарт инструмента, обслуживающего все проекты. По умолчанию для self-hosting реакция — **алерт + ручной approve отката** (по образцу deploy Phase A/B), НЕ автоматический откат. Для не-self репозиториев допустим авто-откат. | Must | +| BR-6 | Любой исход (наблюдение начато, деградация, откат, откат-провал, окно завершилось чисто) уведомляется в Telegram и комментарием в Plane; результат наблюдения фиксируется артефактом. | Must | +| BR-7 | Мониторинг — **restart-safe**: рестарт оркестратора (в т.ч. сам деплой) не теряет и не задваивает наблюдение. Идемпотентность по образцу reconciler / deploy-finalizer. | Must | +| BR-8 | Глобальный kill-switch (env-флаг) и список репозиториев, на которые распространяется фича (по образцу `merge_gate_enabled` / `image_freshness_enabled` / `self_deploy_repos`). Выключенный флаг = прежнее поведение (наблюдения нет). | Must | +| BR-9 | Наблюдаемость: текущее состояние пост-деплой наблюдения отражается в `GET /queue` (по образцу блока `reconcile`). | Should | +| BR-10 | Сигнал деградации пригоден для будущей петли уроков (ORCH-8): фиксируется в артефакте/логе в машиночитаемом виде. | Should | +| BR-11 | Доменный smoke результата фичи (проверка, что конкретная фича реально работает) — желателен, но выносится в follow-up; MVP ограничивается health + 5xx. | Could | + +## 5. Вне рамок (Out of scope) +- Полноценная система метрик/APM (Prometheus, дашборды) — фича опирается на уже + существующие HTTP-эндпоинты, не вводит сбор метрик. +- Универсальный доменный smoke для произвольной фичи (BR-11 — follow-up). +- Полностью автоматический откат прод-орка без участия человека (противоречит + self-hosting safety; отдельная задача при наборе доверия, аналогично ORCH-54 для deploy). +- Изменение момента вердикта `deploy_status` / контракта `check_deploy_status` + (наблюдение происходит ПОСЛЕ `done`, не заменяет deploy-gate). + +## 6. Связи +- **ET-8** — прецедент «deploy SUCCESS, прод не работает». Обоснование задачи. +- **ORCH-36** (`docs/architecture/adr/adr-0007-executable-self-deploy.md`) — Phase A/B/C + исполняемого самодеплоя; пост-деплой наблюдение продлевает ответственность ЗА `done`, + переиспользует sentinel-паттерн и detached-host-процесс для self-rollback. +- **ORCH-53** (`src/reconciler.py`) — каноничный паттерн фонового daemon-потока + (watchdog), запускаемого в `main.lifespan`; образец для пост-деплой наблюдателя. +- **ORCH-58** — `.deploy-prev-image` и хук-механика отката, на которые опирается реакция. +- **ORCH-8** — деградация прода = сигнал для петли уроков (BR-10). +- **ORCH-12** — фича может оформиться как пост-deploy стадия ИЛИ как watchdog (решение + архитектора, см. §7). + +## 7. Открытые архитектурные вопросы (для архитектора, НЕ решаются в анализе) +1. **Где живёт наблюдение:** отдельная пост-deploy стадия конвейера vs фоновый + watchdog-daemon (по образцу `reconciler`) vs reserved-agent job (по образцу + `deploy-finalizer`). Анализ задаёт требования (BR-1, BR-7), выбор механизма — за архитектором. +2. **Механизм self-rollback для self-hosting:** откат прод-орка требует detached + host-процесса (контейнер не может надёжно откатить себя, умирая) — переиспользовать + ли `self_deploy.initiate_deploy` / хук `--rollback`. +3. Точные пороги и веса сигналов (BR-3) — анализ предлагает дефолты (см. AC), архитектор + фиксирует реализацию. diff --git a/docs/work-items/ORCH-021/02-trz.md b/docs/work-items/ORCH-021/02-trz.md new file mode 100644 index 0000000..83d3035 --- /dev/null +++ b/docs/work-items/ORCH-021/02-trz.md @@ -0,0 +1,165 @@ +# ТЗ — ORCH-021: Post-deploy мониторинг прода + авто-rollback + +Work Item: ORCH-021 +Стадия: analysis → (architecture) + +> Документ описывает ТРЕБОВАНИЯ к изменениям и НАЗЫВАЕТ задействованные модули. +> Выбор механизма (стадия vs watchdog vs reserved-agent) и точная реализация — +> зона архитектора (см. BRD §7). Здесь фиксируется, ЧТО должно измениться и КАКИЕ +> контракты НЕЛЬЗЯ ломать. + +## 1. Контекст в коде (как есть сейчас) + +- Конвейер заканчивается в `src/stages.py`: `deploy → done`, gate `check_deploy_status`. + Терминальный переход `deploy → done` исполняется в `src/stage_engine.py::advance_stage` + (блок «Terminal sync», `set_issue_done`, release merge-lease). После этого ничего + не наблюдает за продом. +- `scripts/orchestrator-deploy-hook.sh` уже умеет: + - `health_check(max_attempts, sleep, label)` — опрос `http://localhost:$TARGET_PORT/health` + с проверкой `"status":"ok"`; + - `do_rollback()` — retag `PREV_IMAGE_FILE` → `TARGET_IMAGE` + рестарт + пост-rollback + health-check; коды возврата 0 (ок) / 1 (нет prev-образа) / 2 (rollback тоже упал); + - режим `--rollback` (ручной откат); + - при обычном деплое сохраняет `PREV_IMG` в `PREV_IMAGE_FILE` + (`.deploy-prev-image-prod` для прода, см. `settings.deploy_prod_prev_image_file`). +- Self-deploy прода идёт через detached host-процесс: `src/self_deploy.py` + (`build_deploy_command`, `initiate_deploy`, sentinel-маркеры под + `.deploy-state-//`, `read_result`, `map_exit_code_to_status`). +- Фоновый daemon-паттерн: `src/reconciler.py` (`threading.Thread(daemon=True)` + + `threading.Event`, старт/стоп в `src/main.py::lifespan` после `worker.start()` / + перед `worker.stop()`, `status()` в `GET /queue`). +- Reserved-agent (детерминированный no-LLM job) паттерн: `deploy-finalizer` — + перехват в `src/agents/launcher.py::launch_job` ДО `_spawn`, исполнение + `stage_engine.run_deploy_finalizer`, отложенная постановка через + `enqueue_job(..., available_at_delay_s=...)`. +- Условность self-hosting: `src/qg/checks.py::is_self_hosting_repo`, + `src/self_deploy.py::self_deploy_applies` (флаг + CSV-репо; пусто → только `orchestrator`). +- Наблюдаемые эндпоинты прода (`src/main.py`): `GET /health`, `GET /status`, `GET /queue`. +- API БД: `src/db.py::enqueue_job` (с `available_at_delay_s`), `get_db`, + `update_task_stage`, `get_active_tasks_for_reconcile`. + +## 2. Требуемые изменения + +### 2.1. Новый leaf-модуль чистой логики наблюдения — `src/post_deploy.py` (новый) +Контракт **never-raise** (по образцу `self_deploy.py` / `staging_verdict.py`). +Чистые, юнит-тестируемые функции: +- **Опрос сигналов:** функция, опрашивающая `/health` и ключевые эндпоинты + (`/status`, `/queue`) прод-инстанса (base-url из config), возвращающая структуру + с результатами (код ответа, ok-флаг, доля 5xx). Сеть/таймаут → консервативный + результат, не исключение. +- **Классификация деградации** (чистая, без сети): на вход — серия результатов + опросов; на выход — вердикт `HEALTHY | DEGRADED` по порогам (BR-3): + `≥ post_deploy_fail_threshold` последовательных провалов health ИЛИ доля 5xx + выше `post_deploy_5xx_threshold` на окне. Эта функция — основной предмет + юнит-тестов (детерминированная, как `compute_staging_verdict` в ORCH-061). +- **Решение о реакции** (чистая): по `(repo, вердикт, политика)` → одно из + `NONE | ROLLBACK | ALERT_ONLY`, с учётом self-hosting (BR-5). +- **Запись артефакта** результата наблюдения (см. §2.5), best-effort. +- Условность: хелпер `post_deploy_applies(repo)` (флаг + CSV-репо, пусто → + только self-hosting), по образцу `self_deploy_applies` / `_merge_gate_applies`. + +### 2.2. Оркестрация наблюдения (механизм — выбор архитектора) +Требования к механизму (независимо от выбора стадия/watchdog/reserved-agent): +- запускается ПОСЛЕ перехода `deploy → done` для применимого репозитория (BR-1); +- наблюдает окно `post_deploy_window_s` с интервалом `post_deploy_interval_s`; +- **restart-safe и идемпотентен** (BR-7): состояние наблюдения — в sentinel-файлах + (по образцу `.deploy-state-//`, напр. маркеры `monitor-started` / + `monitor-done`) ИЛИ через отложенные `enqueue_job(available_at_delay_s=...)`; + повторный старт не задваивает наблюдение и не теряет его при рестарте; +- по итогу вызывает «Решение о реакции» из `src/post_deploy.py` и исполняет реакцию (§2.3). + +Кандидатные точки интеграции (на выбор архитектора, см. BRD §7): +- хук в `stage_engine.advance_stage` в блоке `next_stage == "done"` — арм наблюдения; +- reserved-agent `post-deploy-monitor` (расширение `launcher.launch_job` ДО `_spawn`, + как `deploy-finalizer`), с само-перепостановкой через `available_at_delay_s`; +- отдельный daemon-поток `PostDeployWatcher` (как `Reconciler`), старт/стоп в `main.lifespan`. + +### 2.3. Реакция на деградацию +- **Не-self репозитории / политика auto:** вызвать существующий хук в режиме отката + (`scripts/orchestrator-deploy-hook.sh --rollback` с прод-параметрами окружения, + как в `self_deploy.build_deploy_command`, но action=`--rollback`). Маппинг + exit-code хука (0/1/2) в исход переиспользует логику `self_deploy.map_exit_code_to_status` + по смыслу (0 → откат успешен; 1/2 → откат не выполнен/провалился → громкий алерт). +- **Self-hosting (`orchestrator`) по умолчанию (BR-5):** НЕ откатывать автоматически. + Сформировать громкий алерт (Telegram + Plane-коммент) и запросить ручной approve + отката (по образцу deploy Phase A — статус Plane / Telegram CTA). Откат самого + прод-орка, если выполняется, — только через detached host-процесс (нельзя надёжно + откатить контейнер, который при этом умирает; переиспользовать механику + `self_deploy.initiate_deploy`). +- Команда отката для self НЕ должна ронять прод-контейнер в рамках обычного тика + наблюдения (CLAUDE.md: не ронять/не рестартить прод-контейнер вне явного действия). + +### 2.4. Конфигурация — `src/config.py` (расширение `Settings`) +Добавить (env-префикс `ORCH_`, дефолты безопасные): +- `post_deploy_monitor_enabled: bool = True` — глобальный kill-switch (BR-8). +- `post_deploy_repos: str = ""` — CSV применимых репо; пусто → только self-hosting + (по образцу `self_deploy_repos` / `merge_gate_repos` / `image_freshness_repos`). +- `post_deploy_window_s: int = 900` — длина окна наблюдения (дефолт ~15 мин, BR-1). +- `post_deploy_interval_s: int = 30` — интервал между опросами. +- `post_deploy_fail_threshold: int = 3` — N последовательных провалов health → DEGRADED. +- `post_deploy_5xx_threshold: float = 0.5` — порог доли 5xx на окне → DEGRADED. +- `post_deploy_auto_rollback: bool = False` — глобально разрешён ли авто-откат; + при `True` действует для не-self репо; для self всегда требует approve (BR-5). +- `post_deploy_base_url: str = "http://localhost:8500"` — base-url наблюдаемого прода. +- `post_deploy_target` параметры отката — переиспользовать существующие + `deploy_prod_*` (service/port/image/prev_image_file), новых дублей не вводить. + +### 2.5. Артефакт задачи — `16-post-deploy-log.md` (новый) +В `docs/work-items//`. YAML-frontmatter (машиночитаемо, канон гейтов; +для будущей петли уроков BR-10): +``` +--- +post_deploy_status: HEALTHY | DEGRADED +action_taken: NONE | ROLLBACK_OK | ROLLBACK_FAILED | ALERT_ONLY +work_item: +window_s: +checks_total: +checks_failed: +--- +``` +Тело — человекочитаемая сводка опросов. Записывается best-effort (по образцу +`self_deploy.write_deploy_log`); отсутствие файла не должно ничего ронять. +> Артефакт `16-post-deploy-log.md` добавить в перечень артефактов в `CLAUDE.md` +> и таблицу/описание в `docs/architecture/README.md` (golden-source, в том же PR). + +### 2.6. Наблюдаемость — `GET /queue` (`src/main.py`) (BR-9) +Добавить блок `post_deploy` со снимком состояния (enabled, window, активные +наблюдения, последний исход) — по образцу блока `reconcile` (метод `status()`). + +### 2.7. Изменения схемы БД +**Не требуются.** Состояние наблюдения — sentinel-файлы (restart-safe, без миграции, +по образцу ORCH-36) и/или отложенные jobs. Если архитектор выберет колонку в `tasks` +для отметки наблюдения — потребуется миграция; предпочтительно избежать (как ORCH-36/53/58). + +### 2.8. Новые QG checks +**Не требуются.** Наблюдение происходит ПОСЛЕ `done` и не является gate'ом стадии; +реестр `QG_CHECKS` и `STAGE_TRANSITIONS` не меняются (если архитектор НЕ выберет +вариант «отдельная пост-deploy стадия» — тогда потребуется новая стадия+gate, что +надо явно отразить в ADR; по умолчанию предпочтителен вариант без изменения реестров). + +## 3. Инварианты (НЕ ломать) +- `STAGE_TRANSITIONS`, реестр `QG_CHECKS`, контракт `check_deploy_status` / + `_parse_deploy_status`, момент вердикта `deploy_status`, БАГ-8 откат, terminal-sync + `deploy → done`, merge-gate, exit-code-контракт хука (0/1/2) — без изменений. +- Контракт хука: дефолты STAGING-безопасны; прод-параметры приходят только через env. +- Условность как ORCH-35/36/43/58: реально для `orchestrator`/listed-repos, прочие — no-op. +- Never-raise: ошибка в наблюдении не роняет worker / lifespan / конвейер других проектов. +- Self-hosting: тик наблюдения НИКОГДА не рестартит прод-контейнер сам по себе (BR-5). + +## 4. Задействованные модули (сводка) +| Модуль | Изменение | +|--------|-----------| +| `src/post_deploy.py` | **новый** — чистая логика опроса/классификации/решения/артефакта, never-raise | +| `src/config.py` | +параметры `post_deploy_*` (kill-switch, окно, пороги, политика) | +| `src/stage_engine.py` и/или `src/agents/launcher.py` и/или `src/main.py` | арм/исполнение наблюдения (точка — за архитектором) | +| `scripts/orchestrator-deploy-hook.sh` | переиспользуется (`--rollback`); правки — только если откат self требует отдельной ветки (за архитектором) | +| `src/main.py` | блок `post_deploy` в `GET /queue` (BR-9); возможный старт daemon в `lifespan` | +| `docs/work-items//16-post-deploy-log.md` | **новый** артефакт | +| `CLAUDE.md`, `docs/architecture/README.md`, `CHANGELOG.md` | обновить (golden-source, в том же PR) | +| ADR | `docs/work-items/ORCH-021/06-adr/ADR-001-*.md` (+ возможный сквозной `adr/adr-00NN`) | + +## 5. Артефакты по pipeline, которые должны появиться/обновиться +- `16-post-deploy-log.md` (новый, машиночитаемый frontmatter). +- Обновлённые `CLAUDE.md` (перечень артефактов), `docs/architecture/README.md` + (описание пост-деплой наблюдения), `CHANGELOG.md`. +- ADR work-item (`06-adr/`) с зафиксированным выбором механизма и порогов. diff --git a/docs/work-items/ORCH-021/03-acceptance-criteria.md b/docs/work-items/ORCH-021/03-acceptance-criteria.md new file mode 100644 index 0000000..af7ca32 --- /dev/null +++ b/docs/work-items/ORCH-021/03-acceptance-criteria.md @@ -0,0 +1,106 @@ +# Критерии приёмки — ORCH-021 + +Work Item: ORCH-021 +Формат: каждый критерий имеет чёткое условие PASS/FAIL и проверяется тестом +из `04-test-plan.yaml`. + +## Наблюдение и сигналы + +### AC-1 — наблюдение армится после deploy→done +- **PASS:** для применимого репозитория после терминального перехода `deploy → done` + пост-деплой наблюдение инициируется (создаётся sentinel/отложенный job/запись в watcher). +- **FAIL:** переход `deploy → done` не приводит к старту наблюдения. + +### AC-2 — наблюдение НЕ армится для неприменимых репо +- **PASS:** для репозитория вне области (не self-hosting и не в `post_deploy_repos`) + `post_deploy_applies(repo)` → False; наблюдение не стартует; конвейер не меняется. +- **FAIL:** наблюдение стартует для неприменимого репо. + +### AC-3 — классификация HEALTHY +- **PASS:** серия опросов без провалов (или провалов меньше `post_deploy_fail_threshold` + и доля 5xx ниже `post_deploy_5xx_threshold`) → вердикт `HEALTHY`. +- **FAIL:** при здоровых сигналах возвращается `DEGRADED`. + +### AC-4 — классификация DEGRADED по порогу провалов health +- **PASS:** `≥ post_deploy_fail_threshold` ПОСЛЕДОВАТЕЛЬНЫХ провалов health → `DEGRADED`. +- **FAIL:** порог достигнут, но вердикт не `DEGRADED`. + +### AC-5 — классификация DEGRADED по доле 5xx +- **PASS:** доля 5xx на окне выше `post_deploy_5xx_threshold` → `DEGRADED`, + даже если `/health` отвечает 200. +- **FAIL:** превышение порога 5xx не даёт `DEGRADED`. + +### AC-6 — устойчивость к разовому глюку (нет ложного срабатывания) +- **PASS:** одиночный провал (1 < `post_deploy_fail_threshold`) с последующим + восстановлением → итог `HEALTHY`, реакции нет. +- **FAIL:** одиночный разовый провал приводит к `DEGRADED`/откату. + +## Реакция + +### AC-7 — авто-rollback для не-self репо при политике auto +- **PASS:** при `post_deploy_auto_rollback=True` и НЕ-self репо вердикт `DEGRADED` + приводит к вызову отката (хук `--rollback` с прод-параметрами); `action_taken` + фиксируется как `ROLLBACK_OK`/`ROLLBACK_FAILED` по exit-code. +- **FAIL:** откат не вызывается, либо вызывается с staging-дефолтами, либо роняет прод напрямую. + +### AC-8 — self-hosting НЕ откатывается автоматически (safety) +- **PASS:** для `orchestrator` вердикт `DEGRADED` НЕ приводит к автоматическому + откату/рестарту прод-контейнера в тике наблюдения; вместо этого формируется + громкий алерт + запрос ручного approve (`action_taken: ALERT_ONLY`). +- **FAIL:** тик наблюдения автоматически откатывает/рестартит прод-орк. + +### AC-9 — откат-провал эскалируется +- **PASS:** если откат вызван и вернул код 1/2 (нет prev-образа / откат тоже упал) → + `action_taken: ROLLBACK_FAILED` + громкий Telegram-алерт о необходимости ручного вмешательства. +- **FAIL:** провал отката проглатывается тихо. + +## Конфигурация и совместимость + +### AC-10 — kill-switch выключает фичу +- **PASS:** `post_deploy_monitor_enabled=False` → наблюдение не армится ни для кого; + поведение конвейера 1:1 как до ORCH-021. +- **FAIL:** при выключенном флаге наблюдение всё равно работает. + +### AC-11 — пороги/окно конфигурируемы через env +- **PASS:** `post_deploy_window_s`, `post_deploy_interval_s`, `post_deploy_fail_threshold`, + `post_deploy_5xx_threshold` читаются из `Settings` (env `ORCH_*`) и влияют на поведение. +- **FAIL:** значения захардкожены. + +### AC-12 — реестры и схема БД не изменены +- **PASS:** `STAGE_TRANSITIONS`, `QG_CHECKS`, контракт `check_deploy_status` и схема + таблиц БД не изменены (если архитектор не вводит явно новую стадию — тогда это + отражено в ADR и тестах). Существующие тесты deploy/staging/merge-gate зелёные. +- **FAIL:** молча сломан какой-либо существующий контракт/тест. + +## Наблюдаемость, артефакт, идемпотентность + +### AC-13 — артефакт 16-post-deploy-log.md с машиночитаемым frontmatter +- **PASS:** по итогу наблюдения пишется `16-post-deploy-log.md` с валидным YAML-frontmatter + (`post_deploy_status`, `action_taken`); запись best-effort (её отсутствие ничего не роняет). +- **FAIL:** артефакт не пишется или frontmatter невалиден/непарсится. + +### AC-14 — наблюдаемость в /queue +- **PASS:** `GET /queue` содержит блок `post_deploy` со снимком состояния (enabled, + window, активные/последний исход). +- **FAIL:** состояние наблюдения нигде не видно. + +### AC-15 — идемпотентность / restart-safe +- **PASS:** повторный арм для той же задачи (двойной webhook / рестарт оркестратора) + не создаёт второе параллельное наблюдение и не теряет уже идущее. +- **FAIL:** дублируется наблюдение или теряется при рестарте. + +### AC-16 — never-raise +- **PASS:** любая ошибка опроса/сети/файлов/классификации логируется и НЕ роняет + worker / lifespan / конвейер других проектов. +- **FAIL:** исключение из наблюдения всплывает и ломает обслуживание других проектов. + +### AC-17 — уведомления +- **PASS:** ключевые события (наблюдение начато, DEGRADED, откат/алерт, чистое + завершение окна) уведомляются в Telegram и/или Plane-комментарием. +- **FAIL:** деградация/откат происходят молча. + +### AC-18 — документация обновлена (golden-source) +- **PASS:** в том же PR обновлены `CLAUDE.md` (артефакт `16-post-deploy-log.md`), + `docs/architecture/README.md` (описание пост-деплой наблюдения), `CHANGELOG.md`, + и заведён ADR work-item. +- **FAIL:** функционал есть, документация не обновлена (reviewer → REQUEST_CHANGES). diff --git a/docs/work-items/ORCH-021/04-test-plan.yaml b/docs/work-items/ORCH-021/04-test-plan.yaml new file mode 100644 index 0000000..3a9f8e6 --- /dev/null +++ b/docs/work-items/ORCH-021/04-test-plan.yaml @@ -0,0 +1,163 @@ +work_item: ORCH-021 +description: > + Тест-план пост-деплой мониторинга прода + авто-rollback. Упор на детерминированную + чистую логику классификации/решения (юнит, без сети/LLM) и на интеграцию + армирования наблюдения после deploy->done. Сетевые опросы и хук-вызовы мокируются. + Имена модулей/функций — целевые (src/post_deploy.py); архитектор уточняет точную + сигнатуру, тесты адаптируются под ADR. + +tests: + # --- Классификация деградации (чистая логика, ядро) --- + - id: TC-01 + type: unit + description: "HEALTHY: серия опросов без провалов (< порога) -> вердикт HEALTHY" + module: tests/test_post_deploy.py + covers: [AC-3] + expected: PASS + + - id: TC-02 + type: unit + description: "DEGRADED: N последовательных провалов health (== fail_threshold) -> DEGRADED" + module: tests/test_post_deploy.py + covers: [AC-4] + expected: PASS + + - id: TC-03 + type: unit + description: "DEGRADED по 5xx: доля 5xx выше порога при health=200 -> DEGRADED" + module: tests/test_post_deploy.py + covers: [AC-5] + expected: PASS + + - id: TC-04 + type: unit + description: "Нет ложного срабатывания: одиночный провал (1 < threshold) + восстановление -> HEALTHY" + module: tests/test_post_deploy.py + covers: [AC-6] + expected: PASS + + - id: TC-05 + type: unit + description: "Пороги читаются из Settings (env ORCH_*), изменение порога меняет вердикт на тех же данных" + module: tests/test_post_deploy.py + covers: [AC-11] + expected: PASS + + # --- Решение о реакции (чистая логика + self-hosting safety) --- + - id: TC-06 + type: unit + description: "Решение: не-self репо + auto_rollback=True + DEGRADED -> ROLLBACK" + module: tests/test_post_deploy.py + covers: [AC-7] + expected: PASS + + - id: TC-07 + type: unit + description: "Решение self-hosting: orchestrator + DEGRADED -> ALERT_ONLY (НИКОГДА не авто-rollback)" + module: tests/test_post_deploy.py + covers: [AC-8] + expected: PASS + + - id: TC-08 + type: unit + description: "Решение: HEALTHY -> NONE (реакции нет) для любого репо" + module: tests/test_post_deploy.py + covers: [AC-3] + expected: PASS + + # --- Условность / kill-switch --- + - id: TC-09 + type: unit + description: "post_deploy_applies: пусто в repos -> True только для orchestrator, False для enduro-trails" + module: tests/test_post_deploy.py + covers: [AC-2] + expected: PASS + + - id: TC-10 + type: unit + description: "kill-switch: post_deploy_monitor_enabled=False -> applies()=False для всех; наблюдение не армится" + module: tests/test_post_deploy.py + covers: [AC-10] + expected: PASS + + # --- Маппинг exit-code отката -> исход --- + - id: TC-11 + type: unit + description: "Откат exit 0 -> action_taken=ROLLBACK_OK" + module: tests/test_post_deploy.py + covers: [AC-7] + expected: PASS + + - id: TC-12 + type: unit + description: "Откат exit 1/2 (нет prev-образа / откат упал) -> ROLLBACK_FAILED + эскалация-алерт" + module: tests/test_post_deploy.py + covers: [AC-9] + expected: PASS + + # --- Артефакт --- + - id: TC-13 + type: unit + description: "16-post-deploy-log.md пишется с валидным YAML-frontmatter (post_deploy_status/action_taken), парсится yaml.safe_load" + module: tests/test_post_deploy.py + covers: [AC-13] + expected: PASS + + # --- never-raise --- + - id: TC-14 + type: unit + description: "Опрос при сетевой ошибке/таймауте -> консервативный результат (провал-как-down), исключение НЕ всплывает" + module: tests/test_post_deploy.py + covers: [AC-16] + expected: PASS + + - id: TC-15 + type: unit + description: "Ошибка записи артефакта (нет каталога/IO) -> логируется, функция возвращает False, не raise" + module: tests/test_post_deploy.py + covers: [AC-16, AC-13] + expected: PASS + + # --- Интеграция: армирование после deploy->done --- + - id: TC-16 + type: integration + description: "advance_stage deploy->done для orchestrator армит наблюдение (sentinel/job создан); для enduro-trails — нет" + module: tests/test_post_deploy_integration.py + covers: [AC-1, AC-2] + expected: PASS + + - id: TC-17 + type: integration + description: "Идемпотентность: повторный арм той же задачи (двойной webhook) не создаёт второе наблюдение" + module: tests/test_post_deploy_integration.py + covers: [AC-15] + expected: PASS + + - id: TC-18 + type: integration + description: "Полный цикл DEGRADED -> для не-self вызывается откат (хук замокан), пишется лог, шлётся уведомление" + module: tests/test_post_deploy_integration.py + covers: [AC-7, AC-13, AC-17] + expected: PASS + + - id: TC-19 + type: integration + description: "Self-hosting DEGRADED: тик НЕ вызывает рестарт/откат прод-контейнера, формирует алерт+approve-запрос" + module: tests/test_post_deploy_integration.py + covers: [AC-8, AC-17] + expected: PASS + + # --- Наблюдаемость и обратная совместимость --- + - id: TC-20 + type: integration + description: "GET /queue содержит блок post_deploy со снимком состояния" + module: tests/test_post_deploy_integration.py + covers: [AC-14] + expected: PASS + + - id: TC-21 + type: integration + description: "Регресс: существующие тесты deploy/staging/merge-gate/reconciler зелёные; STAGE_TRANSITIONS и QG_CHECKS не изменены" + module: tests/test_stages.py + covers: [AC-12] + expected: PASS