14 KiB
ТЗ — ORCH-021: Post-deploy мониторинг прода + авто-rollback
Work Item: ORCH-021 Стадия: analysis → (architecture)
Документ описывает ТРЕБОВАНИЯ к изменениям и НАЗЫВАЕТ задействованные модули. Выбор механизма (стадия vs watchdog vs reserved-agent) и точная реализация — зона архитектора (см. BRD §7). Здесь фиксируется, ЧТО должно измениться и КАКИЕ контракты НЕЛЬЗЯ ломать.
1. Контекст в коде (как есть сейчас)
- Конвейер заканчивается в
src/stages.py:deploy → done, gatecheck_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()— retagPREV_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-<repo>/<wi>/,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-<repo>/<wi>/, напр. маркеры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/<plane-id>/. YAML-frontmatter (машиночитаемо, канон гейтов;
для будущей петли уроков BR-10):
---
post_deploy_status: HEALTHY | DEGRADED
action_taken: NONE | ROLLBACK_OK | ROLLBACK_FAILED | ALERT_ONLY
work_item: <plane-id>
window_s: <int>
checks_total: <int>
checks_failed: <int>
---
Тело — человекочитаемая сводка опросов. Записывается 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-syncdeploy → 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/<id>/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/) с зафиксированным выбором механизма и порогов.