# adr-0010: Post-deploy мониторинг прода + реакция на деградацию

- **Статус:** proposed (design) — реализация в ветке `feature/ORCH-021-post-deploy-rollback`
- **Дата:** 2026-06-07
- **Задача:** ORCH-021
- **Метка:** `arch:major-change` (новая под-компонента + новый reserved-agent job-kind)
- **Детальный ADR:** `docs/work-items/ORCH-021/06-adr/ADR-001-post-deploy-monitor.md`

## Контекст
Конвейер заканчивается на `deploy → done`: `check_deploy_status` видит
`deploy_status: SUCCESS` → terminal-sync (Plane → Done, release merge-lease), и
оркестратор **забывает про прод**. «Успех» сегодня = health-check в момент рестарта
(~60с окно в `orchestrator-deploy-hook.sh`). Класс инцидентов «зелёный деплой, красный
прод» (прецедент **ET-8**): деградация проявляется через минуты под боевым трафиком,
health отвечает `200 ok`, фича сломана. Для self-hosting опасно вдвойне — сломанный
прод-орк (8500) обслуживает ВСЕ проекты из общего инстанса.

## Решение
Продлить ответственность конвейера **ЗА** `done`: после терминального перехода для
применимого репо армится пост-деплой наблюдение окна `post_deploy_window_s` (дефолт
~15 мин) с интервалом `post_deploy_interval_s`; деградация фиксируется по
**детерминированным порогам**, при подтверждении выполняется реакция.

**Механизм — reserved-agent job `post-deploy-monitor`** (калька `deploy-finalizer`,
ORCH-36), НЕ отдельная стадия и НЕ daemon-поток:
- **Арм:** в `stage_engine.advance_stage`, в блоке `next_stage == "done"`, при
  `post_deploy.post_deploy_applies(repo)` → `post_deploy.arm_monitor(...)` (sentinel
  `armed` = идемпотентность, первый job через `enqueue_job(available_at_delay_s=...)`).
- **Тик:** `launcher.launch_job` перехватывает `agent == "post-deploy-monitor"` ДО
  `_spawn` → `stage_engine.run_post_deploy_monitor(job)`: один опрос сигналов, append в
  персистентный `series`, классификация; HEALTHY и окно не истекло → перепостановка с
  задержкой; иначе → реакция + артефакт + `mark_done`.
- **Чистая логика — новый leaf-модуль `src/post_deploy.py`** (never-raise, по образцу
  `self_deploy.py`/`staging_verdict.py`): `post_deploy_applies`, `probe_signals`
  (опрос `/health` + доля 5xx на `/status`,`/queue`), `classify` (HEALTHY|DEGRADED —
  главный предмет юнит-тестов), `decide_action` (NONE|ROLLBACK|ALERT_ONLY с учётом
  self-hosting), sentinel-state хелперы, `write_post_deploy_log`.

**Сигналы и пороги (детерминированно, AC-3…AC-6):** `DEGRADED` ⇔ `≥
post_deploy_fail_threshold` ПОСЛЕДОВАТЕЛЬНЫХ провалов health ИЛИ доля 5xx на окне `>
post_deploy_5xx_threshold`. Одиночный глюк < порога → HEALTHY (нет ложных откатов).

**Реакция (BR-4/BR-5):**
- **Self-hosting (`orchestrator`) — ВСЕГДА `ALERT_ONLY`:** громкий Telegram + Plane,
  запрос ручного approve отката. Тик НИКОГДА не откатывает/рестартит прод-контейнер
  (структурный инвариант). Откат прод-орка, если оператор решит, — только detached
  host-процесс (`self_deploy.initiate_deploy`), вне тика (MVP).
- **Не-self + `post_deploy_auto_rollback=True`:** хук `--rollback` с прод-env; exit
  `0 → ROLLBACK_OK`, `1/2 → ROLLBACK_FAILED` + громкий алерт.
- Дефолт (`auto_rollback=False`) → `ALERT_ONLY`.

**Артефакт `16-post-deploy-log.md`** (новый) с YAML-frontmatter (`post_deploy_status`,
`action_taken`, `window_s`, `checks_total/failed`) — машиночитаемо для петли уроков
ORCH-8; best-effort. **Наблюдаемость** — блок `post_deploy` в `GET /queue` (образец
`reconcile.status()`).

## Альтернативы
- **Daemon-watchdog (как reconciler)** — отклонён: per-task серия опросов в памяти не
  restart-safe (а деплой орка = рестарт); restart-safe-вариант требует тех же sentinel,
  reserved-agent проще и уже имеет проверенную jobs+sentinel машинерию.
- **Отдельная пост-deploy стадия + QG** — отклонён: меняет `STAGE_TRANSITIONS`/
  `QG_CHECKS`, ломает семантику терминального `done`; наблюдение принципиально ПОСЛЕ
  `done`.
- **Авто-rollback прод-орка из тика** — отклонён (self-hosting safety): групповой риск;
  контейнер не откатит себя надёжно. Self → alert + ручной approve (как ORCH-54).
- **Колонка в `tasks`** — отклонён: миграция на проде; sentinel-файлы restart-safe
  (как ORCH-36/53/58).

## Последствия
- Класс «зелёный деплой, красный прод» закрыт измеримыми порогами; деградация =
  сигнал для ORCH-8.
- Реестры (`STAGE_TRANSITIONS`/`QG_CHECKS`), контракт `check_deploy_status`,
  terminal-sync, merge-gate, exit-code-контракт хука, схема БД — **не меняются**.
- Дефолты безопасны: kill-switch on, auto-rollback off, self только alert.
- Ограничение: монитор self бежит внутри наблюдаемого прода — полностью wedged
  контейнер = пропущенный тик/алерт (known MVP gap; внешний watchdog — follow-up).
- Self-hosting: тик не рестартит/не роняет прод-контейнер; kill-switch
  `post_deploy_monitor_enabled` обязателен; поэтапный раскат через `post_deploy_repos`.

## Связи
adr-0007-executable-self-deploy (ORCH-36 — sentinel/detached-host/finalizer образец,
`map_exit_code_to_status`), adr-0007-reconciler (ORCH-53 — daemon/`status()` образец,
отклонён как основной механизм), adr-0006 (merge-gate — условность/флаги раската),
adr-0003 (staging-gate — образец условности), adr-0008 (provenance — `.deploy-prev-image`/
хук-откат). Прецедент ET-8. Будущее: ORCH-8 (петля уроков), ORCH-54 (полный авто).
