# ТЗ — 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/`) с зафиксированным выбором механизма и порогов.