Files
orchestrator/docs/architecture/README.md
claude-bot 5e60543232 docs(standards): pipeline docs standard — manifest + templates + ADR-naming
Создан golden source структуры номерных документов work item (ORCH-52b, слой 1
эпика ORCH-52). Docs-only: STAGE_TRANSITIONS / QG_CHECKS / check_* / схема БД не
трогаются (AC-6).

- docs/_standards/PIPELINE_DOCS.md — манифест «стадия→агент→документ→категория→
  гейт→frontmatter machine-key» (сверен с src/stages.py и src/qg/checks.py) +
  раздел ADR-naming. Манифест документирует поведение гейтов, источник истины
  остаётся код (ADR-001 §D2); честно различает machine-verdict (12/13/14/15/17)
  и информационные (00/08/10/16) доки; под-гейты ребра deploy-staging→deploy
  отмечены как врезки в advance_stage.
- docs/_templates/* — 15 копируемых скелетов; машинные доки несут точный
  frontmatter-ключ из _parse_* (verdict/result/deploy_status/staging_status/
  security_status/post_deploy_status).
- Точки-ссылки: CLAUDE.md, docs/architecture/README.md; запись CHANGELOG.
- tests/test_orch_52b_docs_standard.py — TC-01..TC-20 структурные проверки;
  полный pytest tests/ зелёный (1177 passed).

Refs: ORCH-075

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 13:25:39 +03:00

100 KiB
Raw Blame History

Архитектура Orchestrator

Обзор

Мульти-агентный оркестратор разработки. Принимает webhooks от Plane (управление задачами) и Gitea (git-события), ведёт задачи по конвейеру стадий через Quality Gates, на каждой стадии запускает Claude CLI агента. Поддерживает несколько проектов (multi-repo) и self-hosting (дорабатывает сам себя).

Компоненты

  • Webhook Receivers (src/webhooks/plane.py, gitea.py) — приём событий, HMAC-проверка, дедупликация (_dedup.py). Роуты: POST /webhook/plane, POST /webhook/gitea.
  • State Machine (src/stages.py) — STAGE_TRANSITIONS: переходы, агент и QG каждой стадии. Хелперы: get_next_stage, get_agent_for_stage, get_qg_for_stage, get_previous_stage.
  • Stage Engine (src/stage_engine.py) — исполнение переходов, диспетчеризация QG (_run_qg), откаты, синхронизация с Plane.
  • Review/Test Parsers (src/review_parse.py, ORCH-046) — defensive-извлечение дословного must-fix текста из артефактов для встраивания в task_desc заворота: extract_review_findings (P0/P1 из 12-review.md), extract_test_failures (фрагмент тела 13-test-report.md). Контракт «never raise»: любая ошибка → "".
  • Quality Gates (src/qg/checks.py) — проверки выхода со стадии, реестр QG_CHECKS.
  • Agent Launcher (src/agents/launcher.py) — запуск Claude CLI агентов в изолированном git worktree, мониторинг, auto-advance. Модель/эффорт каждого агента резолвятся из config (resolve_agent_model/resolve_agent_effort, ORCH-41), а не из frontmatter промпта. ORCH-74: имя модели валидируется форматом ^claude-…$ (is_valid_model) перед --model; невалидное → лог + откат на следующий уровень/CLI-дефолт (never-break, как VALID_EFFORTS для эффорта). Тот же предикат гардит inline-чтение --fallback-model.
  • Queue (src/queue_worker.py, ORCH-1) — персистентная очередь задач (SQLite jobs), atomic claim, max_concurrency, ретраи, restart-safe. ORCH-026: claim_next_job гейтит задачи с незавершёнными зависимостями (job_deps, NOT EXISTS) без занятия слота; декларации/циклы — leaf src/task_deps.py.
  • Job-reaper (src/job_reaper.py, ORCH-065 — adr-0011) — фоновый daemon-поток (каркас reconciler), стартует/останавливается в main.lifespan (после reconciler.start() / перед worker.stop()). Детектирует «мёртвый» running-job без рестарта процесса (Tier-1 мёртвый jobs.pid после reaper_dead_ticks тиков; Tier-2 agent_runs.exit_code записан, а job ещё running; Tier-3 backstop reaper_max_running_s) и приводит строку к корректному статусу через те же контракты (_try_advance_stage/_finalize_job, gate-driven; exit≠0/неизвестно → attempts<maxqueued, иначе failed+Telegram). Атомарный reap-claim (guard status='running') совместим со стартовым requeue_running_jobs. Тот же поток периодически делает проактивный реклейм stale/dead merge-lease (см. ниже). never-raise; kill-switch ORCH_REAPER_ENABLED; снимок в GET /queue (блок reaper).
  • Reconciler (src/reconciler.py, ORCH-053 — реализовано, adr-0007) — фоновый daemon-поток (паттерн queue_worker), стартует/останавливается в main.lifespan (после worker.start() / перед worker.stop()). Реконсилирует рассинхрон «источник истины ≠ стадия задачи» при потерянном webhook. F-1 gate-side (продвигает застрявшую стадию по локальной БД через штатный advance_stage(..., finished_agent=None)), F-2 plane-side (опрос Plane API → handle_* из plane.py), F-3 (БД-fallback sha→branch в handle_ci_status). Источник истины — гейт/Plane, не событие; идемпотентность (active-job guard + atomic-claim + grace); kill-switch ORCH_RECONCILE_ENABLED. analysis F-1 не трогает (человеческий гейт). F-1 также пропускает escalated (retry≥лимита) и Blocked/Needs-Input задачи (ORCH-060). Наблюдаемость — блок reconcile в GET /queue.
  • Notifications / Live-tracker (src/notifications.py, ORCH-042/ORCH-067) — ОДНА live-карточка на задачу (update_task_tracker), обновляется на каждом переходе. Режим ORCH_TRACKER_MODE (дефолт bump с ORCH-067: delete+silent send+repoint внизу чата; edit — правка на месте). Карточка несёт строку Plane-статуса 📍 … (оффлайн-ядро plane_status_label + best-effort live-overlay _live_plane_branch_override, kill-switch ORCH_TRACKER_LIVE_STATUS) и кликабельный номер задачи (plane_issue_link/link_for → ссылка в Plane, fail-safe на сырой номер). ORCH-080: оба низкоуровневых примитива (send_telegram/edit_telegram) шлют payload с disable_web_page_preview: True — Telegram больше не разворачивает баннер link-preview Plane под карточкой/уведомлениями; parse_mode: HTML сохранён (ссылка остаётся кликабельной), безусловно без kill-switch. Все алерты, упоминающие work_item_id, делают номер кликабельным. ORCH-087: bump ведёт авторитетный леджер всех созданных карточек (tracker_messages, deleted_at IS NULL = жива) и на каждом обновлении зачищает ВСЕ незакрытые mid (а не только скаляр tracker_message_id) → класс «замёрзшая сирота» устранён; строка стадии несёт фактический эффорт рядом с моделью (· {model} · {effort}, колонка agent_runs.effort, стамп в launcher._spawn); done-строка времени переписана на три подписанных метрики ⏱️ Агенты · твоё{~cap} · общее с ожиданием (кап ORCH_TRACKER_BRD_REVIEW_CAP_S); deploy-цикл дополнен overlay-ключом confirm_deploy. Контракт всего компонента — never raises; карточка всегда silent. Детали — internals.md §7 и ADR-001.
  • Project Registry (src/projects.py, ORCH-6) — Plane project id → repo + prefix; фильтрация вебхуков по проекту.
  • Plane Sync (src/plane_sync.py) — синхронизация статусов/комментариев в Plane. Резолв статусов проекта get_project_states (ORCH-10) кэширует {logical_key→uuid} per-project; ORCH-068 добавляет в кэш-запись {uuid→group} (для терминал-исключения F-2) и TTL ORCH_PLANE_STATES_TTL_S (дефолт 300с; 0 → прежний lifetime-кэш) — устаревший набор статусов самозалечивается без рестарта процесса через существующий reload_project_states() (баг кэша после появления нового Plane-статуса). Форма возврата get_project_states неизменна (обратная совместимость).

Конвейер и Quality Gates

created → analysis → architecture → development → review → testing → deploy-staging → deploy → done
                          ↑                          │
                          └──── REQUEST_CHANGES ──────┘  (откат на development, max 3 retries)
Стадия Агент (выход) Quality Gate Артефакт
created analyst
analysis architect check_analysis_approved 01-brd / 02-trz / 03-acceptance-criteria / 04-test-plan.yaml
architecture developer check_architecture_done 06-adr/
development reviewer check_ci_green код + PR
review tester check_reviewer_verdict 12-review.md (verdict:)
testing deployer check_tests_passed 13-test-report.md
deploy-staging deployer check_staging_status 15-staging-log.md (staging_status:)
deploy check_deploy_status 14-deploy-log.md (deploy_status:)
done

Реестр QG (QG_CHECKS): check_analysis_approved, check_analysis_complete, check_architecture_done, check_ci_green, check_review_approved, check_tests_passed, check_reviewer_verdict, check_tests_local, check_deploy_status, check_staging_status, check_branch_mergeable (ORCH-043), check_staging_image_fresh (ORCH-058), check_security_gate (ORCH-022).

Канон гейтов: машинные вердикты читаются ТОЛЬКО из YAML-frontmatter, никогда из прозы. Лог-файлы мержатся в origin/main отдельным PR; гейт читает из origin/main.

Стандарт документов конвейера (ORCH-075, ORCH-52b)

Структура номерных документов work item (00-business-request.md17-security-report.md), карта «стадия → агент → документ → категория → гейт/механизм → frontmatter machine-key» и конвенция ADR-naming зафиксированы как golden source в docs/_standards/PIPELINE_DOCS.md; копируемые скелеты — в docs/_templates/. Манифест документирует поведение гейтов (источник истины остаётся код: src/stages.py, src/qg/checks.py), честно различая machine-verdict доки (12/13/14/15/17 — несут читаемый гейтом ключ) и информационные (00/08/10/16 — гейтом не парсятся). Это слой 1 (описательный); машинная проверка соответствия — задача ORCH-52c. ADR: adr-0019, детально — docs/work-items/ORCH-075/06-adr/ADR-001-pipeline-docs-standard.md.

Модель и эффорт по ролям (ORCH-41, валидация ORCH-74)

Модель и --effort каждого агента берутся из config (src/config.py), резолвятся launcher.resolve_agent_model / resolve_agent_effort по приоритету project-override (projects_json agent_models/agent_efforts) > ORCH_AGENT_MODEL_<AGENT>/ORCH_AGENT_EFFORT_<AGENT> > *_default > CLI-дефолт (без флага). Эффорт (ORCH-081): ниже *_default добавлен непустой per-role floor — class-default поля agent_effort_<role> из config.py (его пустой env перебить не может). Floor — строго последний уровень (ниже default) и срабатывает ТОЛЬКО когда все уровни пусты, поэтому пустые прод-ORCH_AGENT_EFFORT_*= (которые pydantic трактует как явное '' и обнуляют дефолт) больше не приводят к запуску без --effort: каждая роль получает свой канонический пол (developer=xhigh, tester/deployer=medium, прочие=high). Непустой явный конфиг по-прежнему побеждает floor; опечатка вне VALID_EFFORTS дропается валидацией ДО floor (never-break, не маскируется). См. docs/work-items/ORCH-081/06-adr/ADR-001-effort-resolution-floor.md. frontmatter model: в .openclaw/agents/*.md удалён (ORCH-74 G1) — он был мёртвой/лживой декларацией (launcher его не читает); config — единственный источник правды о модели. Model-routing (G3) НЕ включён — все 6 агентов на claude-opus-4-8.

Агент Модель Эффорт
analyst claude-opus-4-8 high
architect claude-opus-4-8 high
developer claude-opus-4-8 xhigh
reviewer claude-opus-4-8 high
tester claude-opus-4-8 medium
deployer claude-opus-4-8 medium

Валидация (ORCH-74 G2, never-break): резолвенное имя модели проходит формат-чек is_valid_model (^claude-[a-z0-9.-]+$) перед попаданием в --model. Невалидное (опечатка, gpt-4, пустое) → logger.warning + откат на следующий валидный уровень (в пределе — без --model, CLI-дефолт); мусор никогда не уезжает в CLI и запуск не падает. Форма — формат-чек, а не статичный allowlist: forward-compatible (будущие claude-* проходят без правки кода). Тот же предикат гардит inline-чтение --fallback-model (agent_fallback_model читается мимо резолва — TRZ §4). Эффорт валидируется множеством VALID_EFFORTS (low|medium|high|xhigh|max). Fallback (G4) НЕ включён (agent_fallback_model=""). Детали — docs/work-items/ORCH-074/06-adr/ADR-001-model-name-validation.md.

Условный staging-гейт (ORCH-35)

check_staging_status реален только для self-hosting (is_self_hosting_repo(repo)orchestrator); для остальных проектов → no-op (True, "Staging gate N/A"). Для orchestrator парсит staging_status: из 15-staging-log.md; FAILED → откат на development. Подробнее: ADR-0003.

Толерантность staging-вердикта к инфра-FAIL (ORCH-061 — design)

Self-hosting зацикливался на deploy-staging: scripts/staging_check.py давал ложный FAILED на C9a/C9b (ветка в sandbox / analyst-job в очереди), вызванный отсутствием sandbox-настроек (bot-аккаунты не члены SANDBOX-проекта), а не регрессом кода → откат deploy-staging → development → петля. ORCH-061 классифицирует проверки suite на REAL (pipeline) и SANDBOX_INFRA (узкий allowlist {C9a, C9b}) и делает вердикт толерантным к инфра-FAIL, сохраняя fail-closed для реальных проверок:

  • Чистая логика — leaf-модуль src/staging_verdict.py (classify_check, compute_staging_verdict, never-raise). Упала хоть одна REAL → FAILED/exit1; упали ТОЛЬКО SANDBOX_INFRA и толерантность вкл → SUCCESS/exit0 (waived); waiver применяется только когда все REAL (вкл. C7/C8) зелёные.
  • scripts/staging_check.py помечает проверки категориями, считает вердикт через staging_verdict, печатает INFRA-WAIVED (наблюдаемость).
  • Kill-switch staging_infra_tolerance_enabled (env ORCH_STAGING_INFRA_TOLERANCE_ENABLED, дефолт true, в .env.staging); false → 1:1 прежнее строгое поведение.
  • check_staging_status / _parse_staging_status / STAGE_TRANSITIONS / реестр QG_CHECKSбез изменений (новый QG-чек не вводится); условность ORCH-35 и схема БД сохранены.
  • Инвариант: «no changes to commit» на action-стадиях (deploy-staging/deploy) не есть недовыполнение — продвижение определяется exit0 + гейт-вердиктом (launcher не откатывает; добавлена observability-строка).

Подробнее: adr-0009, детально — docs/work-items/ORCH-061/06-adr/ADR-001-staging-infra-tolerance.md.

Merge-gate: догон main + re-test + сериализация слияний (ORCH-043)

Детерминированный под-гейт (check_branch_mergeable, без LLM) на ребре deploy-staging → deploy: исполняется ПОСЛЕ check_staging_status и ДО запуска deployer'а, который вливает PR в main (deployer мержит в начале стадии deploy). Стадии (STAGE_TRANSITIONS) НЕ меняются — это «под-гейт» ребра, а не отдельная стадия (триггер — то же событие «staging-deployer завершился»).

Назначение: ветка валидируется относительно того main, из которого создана; параллельная задача могла уйти вперёд → семантический конфликт слияния (зелёная ветка ломает обновлённый main). Merge-gate гарантирует проверку против актуального origin/main перед слиянием:

  • Догон: ветка отстаёт (⇔ origin/main не предок HEAD) → rebase origin/main в worktree + push --force-with-lease (ТОЛЬКО ветка задачи; main — никогда). Текстовый конфликт → rebase --abort → откат на development.
  • Безусловный pre-merge rebase (ORCH-026, A-2): при premerge_rebase_always (дефолт True, скоуп merge_gate_repos) short-circuit branch_is_behind_main пропускается — auto_rebase_onto_main вызывается всегда под лизом. На актуальной ветке это no-op (rebase не меняет HEAD, push --force-with-lease → «Everything up-to-date», CI не триггерится); на отстающей — реальный догон. Детерминированный структурный анти-фантом на уровне планировщика (дополняет рубежи ORCH-073, не заменяет). Kill-switch premerge_rebase_always=False → прежнее поведение (ребейз только при behind).
  • Re-test: python -m pytest (merge_retest_target, дефолт tests/) в worktree догнанной ветки, тайм-аут merge_retest_timeout_s. Красный/тайм-аут → откат на development.
  • Сериализация (merge-lock): файловый merge-lease на репо (<repos_dir>/.merge-lease-<repo>.json), живёт от гейта до фактического merge. Acquire неблокирующий (anti-deadlock при max_concurrency=1): busy → defer (повторная постановка deployer'а на deploy-staging с задержкой через available_at), а не откат. Release — на PR-merged вебхуке / deploy→done / откате / по возрасту (crash-реклейм). Restart-safe; без изменения схемы БД. ORCH-026 (A-1): это окно = «merge → main-updated» (для self done ⇔ SHA-in-main, ORCH-073) — пока A не в main, B того же репо получает merge-lock busy → defer. Окно сериализации per-repo НЕ переписывается; кросс-репо параллелизм сохранён (лиз — per-repo файл).
  • Условность (как ORCH-35): реален для orchestrator; прочие репо — no-op. Флаги merge_gate_enabled / merge_gate_repos — поэтапный раскат. Контракт never-raise.

Подробнее: adr-0006, детально — docs/work-items/ORCH-043/06-adr/ADR-001-merge-gate.md. Безусловный pre-merge rebase + связь с зависимостями задач — adr-0015 (ORCH-026).

Зависимости задач: B ждёт A (ORCH-026, Уровень B)

Плоская очередь ORCH-1 (FIFO по id + available_at + max_concurrency) не выражала логических зависимостей. ORCH-026 вводит декларативные связи «задача B не стартует, пока не готовы её depends-on» — без новой стадии и без изменения STAGE_TRANSITIONS/QG_CHECKS.

  • Источник истины планировщика — БД (аддитивная таблица job_deps(task_id, depends_on_task_id)): claim в горячем цикле обслуживает очередь ВСЕХ проектов и обязан быть offline-устойчив (сетевой Plane на каждый claim = встанет очередь всех проектов). Источник декларации настраивается task_deps_source = db|plane|hybrid (дефолт db; plane/hybrid читают Plane relations в handle_work_item_created и кэшируют в job_deps).
  • Гейт планировщика (claim_next_job) — условие NOT EXISTS (job_deps d JOIN tasks t … WHERE d.task_id=j.task_id AND t.stage!='done') при task_deps_enabled: задача с незавершённой зависимостью не выбирается (агент не запускается, слот max_concurrency не занимается). Инертно при пустой job_deps → нулевая регрессия; kill-switch task_deps_enabled=False → запрос 1:1 как ORCH-1.
  • Детект дедлоков — DFS-цикл-детектор (leaf src/task_deps.py::detect_cycle) при вставке связи + backstop в reconciler; цикл → set_issue_blocked + alert (Telegram/Plane) с перечислением цикла. Поток остальных задач не блокируется.
  • Видимость — строка « ждёт ORCH-NNN» в Telegram-карточке (update_task_tracker, never-raise); Plane Blocked — на дедлоке (не на нормальном коротком ожидании, чтобы не флаппить). Инвариант «одна карточка на задачу» сохранён.
  • Совместимость: reconciler F-1 пропускает dep-заблокированные задачи (is_task_ready, паттерн ORCH-060); reaper сканирует только running → dep-блок остаётся queued, не трогается. Зависимости — только intra-repo (v1).
  • Наблюдаемость: блок task_deps в GET /queue (заблокированные задачи, держатель merge-lease, defer-счётчики, обнаруженные циклы) — read-only.

Подробнее: adr-0015, детально — docs/work-items/ORCH-026/06-adr/ADR-001-merge-serialization-and-task-deps.md.

Per-repo serial gate: пакетный автономный режим (ORCH-088 — реализовано)

Эпик «1020 задач за ночь», Этап 1 (serial e2e). Закрывает stale-анализ: ветка задачи N+1 срезалась на входе в анализ (start_pipeline._create_gitea_branch) от main, ещё не содержащего код предшественника N (физическое код-затирание уже закрыто ORCH-026; ORCH-088 — логический разрыв). Новая задача репо не входит в analysis (не режет ветку, не запускает analyst), пока в том же репо есть незавершённая задача (stage != 'done') или репо заморожен. Аддитивно, под kill-switch, область репо, never-raise, restart-safe; STAGE_TRANSITIONS / QG_CHECKS / check_*без изменений.

  • Gate-в-claim (db.claim_next_job) — analyst-job (jobs.agent='analyst') применимого репо не выбирается, если EXISTS более ранняя незавершённая задача репо (t2.id < jobs.task_id) ИЛИ активна строка repo_freeze. По образцу task_deps NOT EXISTS (ORCH-026); только локальная БД (offline hot-path, NFR-2). Job'ы уже активной задачи проходят свободно. FIFO-уточнение реализации (FR-2): ADR-001 D1 фиксировал псевдо-SQL t2.id != jobs.task_id; при != пакет одновременно созданных свежих задач (все в analysis) взаимно блокировался бы (каждая — «другая незавершённая» для остальных) ⇒ дедлок всей serial-очереди. < допускает ровно самую раннюю задачу и сериализует остальные за ней (строго по одной, FIFO по jobs.id), при этом по-прежнему не блокирует rework-analyst собственной задачи (R-7) и сохраняет AC-1.
  • Отложенный срез ветки (анти-stale-base, AC-6): для применимого репо start_pipeline создаёт task-row + enqueue analyst, но не создаёт Gitea-ветку/docs; срез релоцируется на момент claim analyst-job (launcher), когда origin/main уже содержит предшественника (done ⇔ SHA-в-main, ORCH-071/073). ensure_worktree режет от свежего origin/main ⇒ AC-6 структурно. Идемпотентно (_create_gitea_branch 409 = no-op).
  • Durable per-repo freeze (новая аддитивная таблица repo_freeze, cleared_at IS NULL = активен) — post-deploy DEGRADED/rollback (ORCH-021) → set_repo_freeze + Telegram-алерт; gate закрыт безусловно до ручного снятия (POST /serial-gate/unfreeze). Деградировавшая задача уже done (BR-7) ⇒ отдельный сигнал, независимый от stage.
  • Согласование NFR-1: hot-claim тотальный сбой построения gate-фрагмента → fail-open (не заклинить очередь всех проектов, AC-8); freeze в Python-слое (is_repo_frozen) → fail-closed (безопасность прода, AC-9).
  • Чистая логика — leaf src/serial_gate.py (never-raise). Флаги serial_gate_enabled (kill-switch), serial_gate_repos (CSV; пусто ⇒ все репо, в отличие от self-hosting-only ORCH-35/43/58), serial_gate_freeze_enabled. Наблюдаемость — аддитивный блок serial_gate в GET /queue (per-repo active_task / waiting / frozen). Cross-repo параллелизм сохранён (FR-3); при выключенном флаге — нулевая регрессия (enduro не затронут).

Подробнее: adr-0017, детально — docs/work-items/ORCH-088/06-adr/ADR-001-serial-gate.md, docs/work-items/ORCH-088/08-data-requirements.md.

Авто-режим по лейблам: autoApprove + autoDeploy (ORCH-089 — реализовано)

Конвейер имеет два человеческих гейта, тормозящих пакетный автономный прогон (эпик ORCH-088): гейт BRD (analysis: ждёт ручного Approved) и гейт прод-деплоя (deploy: Phase A ждёт ручного Confirm Deploy, ORCH-059). ORCH-089 снимает только эти два человеческих решения — выборочно (лейбл Plane на задаче), декларативно, обратимо, не трогая ни одной технической проверки. Аддитивно, по образцу условных под-гейтов (ORCH-035/043/058/059/088): leaf src/labels.py (never-raise) + точечные врезки + флаги; STAGE_TRANSITIONS / QG_CHECKS / check_* / схема БД — не трогаются.

  • autoApprove → врезка в stage_engine._handle_analysis_approved_flow (ветка files_ok) после In Review+коммента: set_issue_approved (индикация) + лог/Telegram/Plane-коммент + advance_stage(..., finished_agent=None)тот же путь, что человеческий Approved (approved-via-statusanalysis → architecture + mark_brd_review_ended). Без дублирования переходной логики.
  • autoDeploy → врезка в stage_engine._handle_self_deploy_phase_a сразу после advance на deploy + clear_state: лог/Telegram/Plane-коммент + _handle_self_deploy_phase_b(...) (idempotency-маркер INITIATED, статус Deploying, finalizer). Пропускаются лишь индикативно-человеческие шаги (Awaiting Deploy + «ask-human»). BR-5 структурно: Phase A достигается только после зелёных под-гейтов ребра deploy-staging → deploy (security → merge-gate → image-freshness → staging) → autoDeploy физически не деплоит сломанное.
  • Чтение лейбловplane_sync.fetch_issue_labels (поле labels issue, None при ошибке ≠ []) + get_project_labels ({normalized_name→uuid}, TTL-кэш по образцу get_project_states); сопоставление по нормализованному имени (strip().casefold()), неоднозначность → «нет лейбла». Источник истины — Plane API, не payload вебхука. Новый сеттер set_issue_approved (ключ approved уже в _DEFAULT_STATES).
  • Флаги (config.py): auto_label_enabled (kill-switch), auto_approve_label/ auto_deploy_label, auto_label_repos (CSV; пусто → self-hosting only), auto_label_states_ttl_s. applies(repo) (локальный) проверяется ПЕРВЫМ; has_label (сеть) — только при applies==True → при выключенном флаге нулевой сетевой оверхед, нулевая регрессия для enduro.
  • Fail-safe (never auto): любая ошибка/недоступность Plane/неоднозначность → «нет авто» → ручной гейт (never-raise). Идемпотентность: autoApprove — advance один раз (поздний Approved/F-2 видят architecture); autoDeploy — маркер INITIATED. Прозрачность (AC-7): лог + Telegram + Plane-коммент + live-карточка; блок auto_labels в GET /queue.
  • Инфра-предусловие: создать лейблы autoApprove/autoDeploy в Plane-проекте ORCH (labels API); их отсутствие = has_label False = ручной режим (fail-safe).

Подробнее: adr-0018, детально — docs/work-items/ORCH-089/06-adr/ADR-001-auto-label-gates.md, docs/work-items/ORCH-089/07-infra-requirements.md.

Исполняемый самодеплой стадии deploy (ORCH-36)

deploy перестаёт быть «бумажной»: для self-hosting (is_self_hosting_repo) стадия РЕАЛЬНО деплоит прод (8500) через хост-хук scripts/orchestrator-deploy-hook.sh, а deploy_status: SUCCESS означает доказанный health-ok, не декларацию LLM. Три фазы (детерминированно, без LLM в критическом пути self-restart):

  • Фаза A (вход в deploy) — при deploy_require_manual_approve=true вместо запуска прод-deployer выставляется approval-pending статус Plane + запрос перевести задачу в статус «Confirm Deploy» (ORCH-059; Plane-коммент + Telegram). Перехват в advance_stage ПОСЛЕ check_staging_status и merge-gate.
  • Фаза B (Plane → Confirm Deploy, ORCH-059)advance_stage(deploy, finished_agent=None, confirm_deploy=True) запускает detached host-процесс (ssh + setsid → хук с прод-параметрами + build-once retag SOURCE_IMAGE) и ставит детерминированный finalizer-job; маркер initiated — идемпотентность. Возврат БЕЗ advance (вердикта ещё нет). Обычный Approved на deploy (confirm_deploy=False) — детерминированный no-op (не деплоит и не откатывает).
  • Фаза C (finalizer) — новый контейнер после рестарта читает sentinel result (exit-code хука), маппит 0→SUCCESS / иначе→FAILED, пишет 14-deploy-log.md, вызывает advance_stage(deploy, finished_agent="deployer") → существующие контракты: SUCCESS → done, FAILED → откат БАГ-8 на development.

Триггер прод-деплоя = смена статуса Plane на Confirm Deploy (ORCH-059; status-only verdict model; комментарии не управляют конвейером). Approved остаётся исключительно человеческим гейтом конвейера и прод-деплой не запускает. На старте — обязательный ручной approve (флаг true); полный авто — отдельная задача (ORCH-54). Условность как ORCH-35: реально для orchestrator, прочие репо — прежний синхронный ssh-деплой агентом. Контракты не меняются: STAGE_TRANSITIONS, реестр QG, check_deploy_status/_parse_deploy_status, БАГ-8, terminal-sync, merge-gate, exit-code-контракт хука. Restart-safe состояние — sentinel-файлы (<repos_dir>/.deploy-state-<repo>/<wi>/), без миграции БД. Подробнее: adr-0007, детально — docs/work-items/ORCH-036/06-adr/ADR-001-executable-self-deploy.md.

Выделенный статус-триггер прод-деплоя «Confirm Deploy» (ORCH-059 — реализовано)

Перегрузка: один Plane-статус Approved служил И человеческим гейтом BRD на analysis (check_analysis_approved), И триггером Фазы B прод-деплоя на deploy — привычный жест approve молча запускал прод-рестарт (групповой self-hosting риск). ORCH-059 разделяет жесты: вводится отдельный логический статус confirm_deploy («Confirm Deploy»), который триггерит ТОЛЬКО Фазу B на deploy; Approved остаётся исключительно гейтом конвейера.

  • _PLANE_NAME_TO_KEY += "Confirm Deploy" → "confirm_deploy"; в _DEFAULT_STATES ключ НЕ добавляется (нет UUID для enduro/fallback) → fail-closed: нет статуса → нет деплоя, без KeyError (доступ через .get).
  • handle_issue_updated маршрутизирует Confirm Deployhandle_confirm_deploy (гард stage=="deploy") → _try_advance_stage(..., confirm_deploy=True).
  • advance_stage получает kwarg confirm_deploy: bool=False; блок Фазы B (deploy+finished_agent is None+self-hosting) деплоит ТОЛЬКО при confirm_deploy=True, иначе (обычный Approved) — no-op (check_deploy_status не запускается → нет ложного отката БАГ-8).
  • CTA Фазы A (_handle_self_deploy_phase_a) просит «Confirm Deploy», не «Approved».
  • Условность как ORCH-35/36 (только orchestrator); Фазы A/C, STAGE_TRANSITIONS, QG_CHECKS, check_deploy_status, merge-gate, схема БД — без изменений.
  • Эксплуатация: в Plane-проекте ORCH создать статус «Confirm Deploy» + сброс кэша состояний (docs/work-items/ORCH-059/07-infra-requirements.md).

Детально — docs/work-items/ORCH-059/06-adr/ADR-001-confirm-deploy-status.md (уточняет/триггер Фазы B относительно adr-0007).

Merge-в-main + пост-деплой верификация как условие done (ORCH-071 — фикс фантомного merge)

Фантомный merge (CRITICAL, постмортем docs/history/LESSONS_2026-06-08_phantom-merge.md): на self-hosting пути deploy агент deployer НЕ запускается, а фактический merge PR в main исторически делал ТОЛЬКО он → детерминированный путь (_handle_self_deploy_phase_b → initiate_deploy → run_deploy_finalizer) не содержал шага merge-в-main вообще. Detached host-деплой лишь retag'ал образ + рестартил 8500; done достигался по deploy_status: SUCCESS без верификации main. Зелёный деплой (образ из рабочей ветки) маскировал отсутствие merge → следующая задача срезала ветку от устаревшего main и теряла код предшественника (накопительно потеряны ORCH-022/059/066/068). ORCH-071 вводит детерминированный merge-актор + пост-merge верификацию как под-гейт ребра deploy → done (симметрично edge-под-гейтам deploy-staging → deploy), только для self-hosting:

  • Врезка _handle_merge_verify в advance_stage (current_stage=="deploy" и next_stage=="done", ПОСЛЕ зелёного check_deploy_status, ДО update_task_stage). Гейтит ВСЕ пути к done единообразно (run_deploy_finalizer Phase C, reconciler F-1, job-reaper — все идут через advance_stage), закрывая дыру обхода merge.
  • Merge в Phase C (после рестарта), НЕ в Phase B — finalizer restart-surviving (claim воркером нового контейнера, re-drive reaper'ом), merge физически строго ПОСЛЕ рестарта прода → рестарт его не убивает (G3 «шаг, переживающий рестарт»; постмортем-урок №3).
  • Merge-актор merge_gate.merge_prpr_already_merged (idempotency no-op повтор) → иначе Gitea POST /repos/{owner}/{repo}/pulls/{index}/merge. Выбор PR строго по head.ref==branch И base.ref=="main". Никогда push/force-push в main.
  • Верификатор merge_gate.verify_merged_to_main (семантика ORCH-073, FR-1): подтверждение — ТОЛЬКО git merge-base --is-ancestor <validated_sha> origin/main (validated_revision — якорь ORCH-058). PR-флаг pr_already_merged больше НЕ подтверждает merge (удалён из verify): он понижен до idempotency-guard merge_pr и засчитывает merged PR лишь при head.ref==branch И base.ref=="main" (исключает авто docs-PR). Пустой SHA / git-ошибка → False (fail-closed), never-raise.
  • Регресс-гард целостности main (ORCH-073, FR-5): merge_gate.check_main_regression в _handle_merge_verify ПОСЛЕ подтверждённого SHA-в-main и ДО done проверяет, что origin/main содержит декларативный набор маркеров ранее-merged задач (MAIN_REGRESSION_MARKERS, git grep -c <marker> origin/main -- <path> > 0). Маркер отсутствует → alert «main regressed» + HOLD (НЕ done, ALERT-only). Fail-open на git-ошибке грепа (регресс — только при count==0). Kill-switch regression_guard_enabled; non-self → no-op. Набор — append-only константа, значимая задача дописывает свой маркер.
  • Не подтверждено → alert «deploy succeeded but not merged» (Telegram+Plane) + HOLD (set_issue_blocked, задача НЕ done, БЕЗ авто-отката на development — not-merged есть инфра-дефект, реакция ALERT-only как ORCH-021 self-hosting). Подтверждено → штатный deploy → done + merged_to_main: true во frontmatter 14-deploy-log.md (deploy_status: нетронут).
  • Защита от CHANGELOG-затирания (ORCH-073, FR-4): корневой .gitattributes с CHANGELOG.md merge=union → правки ## [Unreleased] авто-сливаются при auto_rebase_onto_main без конфликта, ветка не откатывается в development и не тащит устаревший код-сосед. docs/** под union НЕ ставится (union только для append-only).
  • Условность как ORCH-35/43/58: merge_verify_enabled (kill-switch, дефолт true) + merge_verify_repos (пусто → только self-hosting); non-self — no-op, merge остаётся за deployer. never-raise; идемпотентность по SHA-в-main (INV-4, не «любой merged PR»); ручной approve сохранён (Confirm Deploy).
  • Инварианты: STAGE_TRANSITIONS, check_deploy_status/_parse_deploy_status, реестр QG_CHECKS (под-гейт — врезка в advance_stage, НЕ новый зарегистрированный QG), схема БД, БАГ-8, terminal-sync, merge-gate, image-freshness, exit-коды хука — без изменений. Диагностика фантома — runbook docs/operations/PHANTOM_MERGE_RUNBOOK.md (4 проверки постмортема).

Подробнее: adr-0013 + adr-0014 (amends 0013 — SHA-в-main как единственный критерий + регресс-гард, ORCH-073); детально — docs/work-items/ORCH-071/06-adr/ADR-001-merge-verify-gate.md, docs/work-items/ORCH-073/06-adr/ADR-001-merge-verify-sha-truth-and-regression-guard.md.

Гарантированный код-PR перед merge-verify (ORCH-082 — фикс ложного HOLD «no open PR»)

Под-гейт merge-verify (ORCH-071/073) детерминированно мержит открытый код-PR ветки в main (merge_pr, фильтр head.ref==branch И base.ref=="main"). Но конвейер не гарантировал, что к моменту merge у ветки этот PR есть: PR создаётся единственной launcher._ensure_pr только на developer-пути и только при свежем worktree-коммите. На деплое ORCH-074 (08.06, первая задача после ручных восстановлений main) у ветки не оказалось открытого код-PR → merge_pr вернул ("False", "no open PR") → защита ORCH-073 верно удержала задачу (HOLD, не ложный done), но это лечило следствие. ORCH-082 закрывает отсутствующий инвариант «к merge-verify у ветки есть открытый код-PR» аддитивно, внутри того же под-гейта, не трогая машину стадий:

  • Новый leaf-актор merge_gate.ensure_open_pr(repo, branch) -> (status, detail) (never-raise): GET …/pulls?state=open с фильтром head.ref==branch И base.ref=="main" (идентичен merge_pr/ORCH-073 FR-3 — авто-docs-PR base != main НЕ код-PR) → ("existed", N); иначе POST …/pulls("created", N); гонка «PR exists»/409/422 → повторный GET → existed (без дублей); любая иная ошибка → ("failed", reason).
  • Врезка в _handle_merge_verify ПОСЛЕ резолва validated_revision и ПЕРЕД merge_pr: created|existed → штатно к merge_prverify_merged_to_main; failed → честный HOLD+alert через новый helper _hold_pr_create_failed (текст «PR создать не удалось» — отличим от not-merged HOLD; result.note="pr-create-failed-hold"), задача остаётся на deploy, БЕЗ отката на development.
  • Защита ORCH-073 неприкосновенна и приоритетна: подтверждение merge остаётся ТОЛЬКО verify_merged_to_main (SHA-в-main) + check_main_regression; ensure_open_pr устраняет лишь ложный HOLD «no open PR», но не маскирует реально невлитый код (тот → HOLD как прежде).
  • launcher._ensure_pr рекомендуется делегировать в ensure_open_pr (единый код создания PR), сохранив прежний триггер «только developer-путь».
  • Условность как ORCH-35/43/58/71: kill-switch merge_verify_autocreate_pr_enabled (дефолт true); область — merge_verify_applies(repo) (self-hosting / merge_verify_repos); non-self — no-op. False → поведение ORCH-074 1:1. Идемпотентность из Gitea (наличие открытого PR), без миграции БД (restart-safe). STAGE_TRANSITIONS, QG_CHECKS, схема БД, check_deploy_status, exit-коды хука, merge-gate, image-freshness — без изменений; main не push/force-push.

Подробнее: adr-0016 (amends 0013/0014); детально — docs/work-items/ORCH-082/06-adr/ADR-001-ensure-open-pr-before-merge-verify.md.

Post-deploy наблюдение прода + реакция на деградацию (ORCH-021 — реализовано)

Конвейер заканчивался на deploy → done и забывал про прод: «успех» = health-check в момент рестарта (~60с). Класс «зелёный деплой, красный прод» (прецедент ET-8 — деградация через минуты под трафиком, health 200 ok, фича сломана). ORCH-021 продлевает ответственность ЗА done: для применимого репо после терминального перехода армится наблюдение окна post_deploy_window_s (~15 мин) с интервалом post_deploy_interval_s; деградация фиксируется по детерминированным порогам, при подтверждении — реакция.

Механизм — reserved-agent job post-deploy-monitor (калька deploy-finalizer, НЕ стадия и НЕ daemon): арм в advance_stage в блоке next_stage == "done" (post_deploy.arm_monitor, sentinel armed = идемпотентность); тик перехватывается в launcher.launch_job ДО _spawnstage_engine.run_post_deploy_monitor (один опрос → append в series → классификация → перепостановка с задержкой ИЛИ реакция+артефакт+done). Чистая логика — новый leaf-модуль src/post_deploy.py (never-raise): post_deploy_applies, probe_signals (/health 200+{"status":"ok"} + доля 5xx на /status,/queue), classify (HEALTHY|DEGRADED — главный предмет юнит-тестов), decide_action, sentinel-state, write_post_deploy_log.

  • Пороги (BR-3): DEGRADED≥ post_deploy_fail_threshold ПОСЛЕДОВАТЕЛЬНЫХ провалов health ИЛИ доля 5xx > post_deploy_5xx_threshold; одиночный глюк → HEALTHY (нет ложных откатов).
  • Реакция: self-hosting (orchestrator) — ВСЕГДА ALERT_ONLY (Telegram+Plane, ручной approve; тик НИКОГДА не откатывает/рестартит прод-контейнер); не-self + post_deploy_auto_rollback=true → хук --rollback (0→ROLLBACK_OK, 1/2→ROLLBACK_FAILED+алерт); дефолт → ALERT_ONLY.
  • Артефакт 16-post-deploy-log.md (YAML-frontmatter post_deploy_status/ action_taken/…) — машиночитаемо для петли уроков ORCH-8; best-effort.
  • Наблюдаемость — блок post_deploy в GET /queue (образец reconcile).
  • Инварианты: STAGE_TRANSITIONS, QG_CHECKS, check_deploy_status, terminal-sync, merge-gate, exit-коды хука (0/1/2), схема БД — НЕ меняются. Restart-safe (sentinel .post-deploy-state-<repo>/<wi>/ + jobs-очередь). Kill-switch post_deploy_monitor_enabled, область post_deploy_repos (пусто → self-hosting). Условность как ORCH-35/36/43/58.

Подробнее: adr-0010, детально — docs/work-items/ORCH-021/06-adr/ADR-001-post-deploy-monitor.md.

Свежесть артефакта BUILD-ONCE: провенанс staging-образа (ORCH-058 — реализовано)

BUILD-ONCE retag (ORCH-36) промоутит SOURCE_IMAGE=orchestrator-orchestrator-staging в прод без rebuild, полагаясь на «staging-образ свеж и провалидирован». Этой гарантии нет: конвейер нигде не пересобирает staging-образ из провалидированного коммита → retag мог тихо промоутнуть УСТАРЕВШИЙ образ (инцидент LESSONS_ORCH-036 п.4 — зелёный деплой молча откатывал прод). ORCH-058 обеспечивает инвариант INV-FRESH двумя слоями (defense in depth), только для self-hosting:

  • A — пересборка (liveness): детерминированный QG-под-чек check_staging_image_fresh на ребре deploy-staging → deploy ПОСЛЕ merge-gate и ДО Phase A пересобирает orchestrator-orchestrator-staging из worktree валидированного коммита (--build-arg GIT_SHA=<sha>, OCI-лейбл org.opencontainers.image.revision), пересоздаёт 8501 и прогоняет staging_check против свежего образа → валидируем и промоутим один артефакт. FAIL → откат на development (как merge-gate). Сборки/recreate — ТОЛЬКО staging.
  • B — fail-closed guard (safety): хук шагом 2b ПЕРЕД docker tag сверяет лейбл revision у SOURCE_IMAGE с EXPECTED_REVISION (пробрасывает build_deploy_command). Несовпадение / пустой лейбл / пустой ожидаемый SHA / ошибка inspect → exit 1 → FAILED (БАГ-8 откат), прод не трогается. Делает тихий промоут устаревшего образа структурно невозможным даже при отключённой/проигравшей гонку A.

Якорь «провалидированного коммита» — git rev-parse HEAD worktree ПОСЛЕ merge-gate (один helper validated_revision питает и штамп A, и EXPECTED_REVISION B). Единый kill-switch image_freshness_enabled включает A+B как целое (нет «B без A» = вечного fail-fast); image_freshness_repos (пусто → self-hosting). STAGE_TRANSITIONS, exit-code хука (0/1/2), check_deploy_status, БАГ-8, merge-gate, схема БД — НЕ меняются (под-гейт ребра + лейбл образа, без миграций). Подробнее: adr-0008, детально — docs/work-items/ORCH-058/06-adr/ADR-001-staging-image-provenance.md.

Security-гейт: secret-scanning + dependency audit перед мержем (ORCH-022 — реализовано)

Автономный конвейер вливал ветку в main без проверки на утёкший секрет (ключ/токен/пароль/ приватный ключ) и уязвимую зависимость (CVE); для self-hosting один секрет/CVE через одну задачу уезжал в общий прод всех проектов (CLAUDE.md §8). ORCH-022 вводит детерминированный (без LLM) security-гейт как под-гейт ребра deploy-staging → deploy, рядом с merge-gate (ORCH-043) и image-freshness (ORCH-058), исполняемый ПЕРВЫМ среди edge-под-гейтов (ДО merge-gate). Паттерн соседей: leaf src/security_gate.py (never-raise) + тонкая обёртка check_security_gate в QG_CHECKS + врезка _handle_security_gate в advance_stage. STAGE_TRANSITIONS и схема БД — без изменений.

  • Secret-scanning (gitleaks, offline): скан origin/main..HEAD; любой секрет вне аллоулиста .gitleaks.toml → вклад в FAIL. Offline → гарантия «секрет всегда блокирует» не зависит от сети (безусловна).
  • Dependency audit (pip-audit, OSV/PyPI): severity ≥ security_dep_block_severity (дефолт HIGH) → FAIL; ниже / UNKNOWN → warning. Недоступность фида → fail-open + громкий warning (анти-петля ORCH-061; флаг security_dep_audit_fail_closed для строгого режима). best-effort при доступности фида.
  • ПЕРВЫМ, ДО merge-gate: дёшево фейлить до дорогих rebase/rebuild; скан ветки ДО rebase не «обвиняет» задачу в CVE из обновившегося main; до захвата merge-lease → при FAIL lease освобождать не нужно.
  • Артефакт 17-security-report.md (YAML-frontmatter security_status/secrets_found/ deps_blocking/deps_warning/deps_audit_degraded); вердикт читается ТОЛЬКО из frontmatter (гейт пишет → читает обратно через parse_security_status → возвращает: единый источник истины), negative-токен авторитетен, битый/нет → fail-closed.
  • FAIL → откат на development + developer-retry (общий _developer_retry_count, cap 3, затем set_issue_blocked + Telegram); task_desc несёт дословные находки (ORCH-046).
  • Условность как ORCH-35/43/58: security_gate_enabled + security_gate_repos (пусто → только self-hosting); never-raise; таймаут security_scan_timeout_s; гейт не деплоит/не рестартит прод. v1 — Python-only; SAST/мульти-стек — follow-up (BR-14).

Подробнее: adr-0012, детально — docs/work-items/ORCH-022/06-adr/ADR-001-security-gate.md.

Live-трекер: зачистка сирот + эффорт в карточке + честное время (ORCH-087 — реализовано)

Скалярный tasks.tracker_message_id (только последний message_id) при рассинхроне bump-режима (доминанты: гонка двух update_task_tracker и delete-fail+send-ok) терял ссылку на прежние карточки → осиротевшие «замёрзшие» карточки (скриншот ORCH-082: 📍 To Analyse на задаче, реально дошедшей до deploy). G0-расследование (ADR-001): рендер исправен, корень — потеря учёта старых mid. Решение (bump сохраняется как дефолт — фича «карточка внизу» ORCH-042/067):

  • G1 — полный учёт mid: аддитивная таблица-леджер tracker_messages(task_id, message_id, created_at, deleted_at) (вариант A1; JSON-массив A2 отклонён — lost-update при гонке). На каждом bump зачищаются ВСЕ незакрытые mid (deleted_at IS NULL): успех/«already gone» → deleted_at, transient → остаётся для ретрая; новый mid в леджер + set_tracker_message_id ТОЛЬКО при send is not None (BR-6). Скаляр tracker_message_id сохранён (BC). Остаточная гонка самозалечивается за один переход (лок не вводится). Known-limitation: Telegram 48ч (сироты старше неудаляемы).
  • G2/G3 — заголовок/deploy-цикл: после G1 единственная живая карточка несёт заголовок текущей стадии; _LIVE_BRANCH_LABELS дополняется ключом confirm_deploy (полнота цикла Awaiting Deploy → Deploying → Confirm Deploy → Monitoring → Done).
  • BR-EFF — эффорт в строке стадии: новая колонка agent_runs.effort TEXT, стамп фактического resolve_agent_effort в launcher._spawn (CLI эффорт не возвращает); рендер · {model} · {effort} (developer=xhigh, tester/deployer= medium, прочие=high); пустой → суффикс опускается.
  • BR-G5 — честное время: done-строка ⏱️ Агенты {agent} · твоё {review~cap} · общее с ожиданием {wall} — три независимых подписанных метрики; agentagent_runs (главная, точная); «твоё» ограничено порогом tracker_brd_review_cap_s (дефолт 2ч, маркер ~ при отсечке аномального застоя); wall подписан «с ожиданием», не выдаётся за сумму.
  • Инварианты: STAGE_TRANSITIONS/QG_CHECKS/стадии — без изменений; миграции аддитивны/идемпотентны (общая прод-БД, enduro не трогается); never-raise, disable_notification, plane_issue_link (ORCH-067), disable_web_page_preview (ORCH-080) — сохранены; разработка поверх свежего origin/main (ORCH-86), reconciler.py не эродируется.

Детально — ADR-001, docs/work-items/ORCH-087/08-data-requirements.md.

Reconciler: реконсиляция потерянных webhook (ORCH-053 — реализовано)

Конвейер продвигается только входящими webhook; потерянное событие (502 на ребилде, нет ретраев у Plane/Gitea, неразрезолвленный sha→branch) → задача застревает молча (инцидент ORCH-044). Фоновый поток reconciler периодически (reconcile_interval_s) находит застрявшие задачи и доигрывает пропущенный переход через те же штатные гейты/обработчики, что и webhook:

  • F-1 gate-side: для задач со stage∉{done}, без активного job и age(updated_at) ≥ grace_for_stage(stage) — read-only пред-оценка канонического QG; зелёный → stage_engine.advance_stage(..., finished_agent=None); красный → тишина (спам нотификаций структурно невозможен). analysis не реконсилируется. Skip escalated / Blocked / Needs-Input (ORCH-060): ДО оценки гейта F-1 пропускает (молча, без advance/нотификаций) задачи, которые ждут человека — (1) исчерпавшие лимит developer-ретраев (developer_retry_count(task_id) >= MAX_DEVELOPER_RETRIES, детерминированно, без сети — закрывает bounce-петлю ET-013) и (2) в явном Plane-статусе Blocked / Needs Input (Вариант A — запрос Plane API, без миграции БД; never-raise → консервативный skip). Гард retry-count проверяется первым (дёшево, локальный SQL). ORCH-086 (закрытие F-1-пробела ORCH-068): терминал-исключение и state_uuid-dedup (изначально только F-2) распространены на F-1. После дешёвых локальных гардов F-1 делает один резолв Plane-статуса задачи на тик (общий fetch для Guard 2 + терминал-скипа + _note_unblock); терминальная задача (группа Plane completed/cancelled, fallback — логические ключи done/cancelled, ЛИБО стадия в БД орка ∈ {done, cancelled}) → безусловный ранний скип (skipped_terminal_total++, без advance/уведомления; не подчинён reconcile_skip_blocked_enabled). Вызов _note_unblock на F-1 теперь передаёт state_uuid → in-memory dedup работает на обоих путях (страховка от повтора после рестарта). Лечит периодическое ложное «ET-002 done разблокирована (потерян webhook)» для терминальных в Plane задач (enduro/orchestrator), сохраняя легитимный unblock реально застрявшей не-терминальной задачи. STAGE_TRANSITIONS/QG_CHECKS/схема БД/сигнатуры/новые флаги — без изменений. Детали — docs/work-items/ORCH-086/06-adr/ADR-001-reconciler-f1-terminal-skip-and-dedup.md.
  • F-2 plane-side: опрос Plane API per-project → handle_status_start / handle_verdict из webhooks/plane.py (логика не дублируется). ORCH-068 (livelock-fix): (1) задачи в терминальной группе Plane (state.group ∈ {completed, cancelled}, fallback — логические ключи done/cancelled) исключаются из actionable-выборки per-issue — проектно-независимо, устойчиво к UUID-алиасингу после переименований статусов (ORCH-066); (2) _note_unblock (лог + Telegram + unblocked_total) вызывается ТОЛЬКО при подтверждённом state change (сравнение стадии задачи до/после _dispatch; no-op dispatch → тишина), плюс in-memory дедуп по issue_id→state. Восстанавливает инвариант silence-when-in-sync (AC-9/AC-10). Детали — docs/work-items/ORCH-068/06-adr/ADR-001-reconciler-terminal-exclusion-and-cache-ttl.md.
  • F-3: усиление sha→branch в handle_ci_status (БД-fallback по единственной development-задаче repo; неоднозначность → не резолвим).
  • F-4 observability: при разблокировке — лог-строка reconciler: <wi> <stage> разблокирована (потерян webhook) + Telegram (reconcile_notify_unblock); снимок состояния в GET /queue (блок reconcile). ORCH-068 добавляет в снимок счётчики skipped_terminal_total (исключённые терминалы) и deduped_total (подавленные повторные нотификации).

Реализация: src/reconciler.py (daemon-поток по образцу queue_worker), стартует в main.lifespan после worker.start(), останавливается в finally перед worker.stop().

Инварианты: источник истины — гейт/Plane, не событие; идемпотентность (active-job guard + atomic-claim на создании под process-wide Lock + grace + max_concurrency=1); never-raise на единицу работы; тишина при синхронности; restart-safe; kill-switch ORCH_RECONCILE_ENABLED (+ ORCH_RECONCILE_PLANE_ENABLED гасит только F-2). Схема БД и реестры (STAGE_TRANSITIONS/QG_CHECKS) не меняются. Подробнее: adr-0007, детально — docs/work-items/ORCH-053/06-adr/ADR-001-stuck-task-reconciler.md.

Job-reaper + проактивный реклейм merge-lease (ORCH-065 — design)

Финализация статуса job (done/queued/failed) выполняется ТОЛЬКО в launcher._monitor_agent → _finalize_job внутри живого процесса. Смерть monitor-потока/процесса между proc.wait() и _finalize_job (краш, OOM, self-restart во время deploy) оставляла строку jobs навсегда running; при max_concurrency=1 одна зомби-строка блокирует claim всех job → встаёт конвейер ВСЕХ проектов (инциденты 07.06: jobs 236/239/242/254). requeue_running_jobs() спасал ТОЛЬКО на старте процесса. Симметрично залипал merge-lease (ORCH-043): реклейм был лениво-по-TTL и только при чужом acquire, liveness держателя по pid не проверялся. Это последняя ручная точка автономного self-deploy (блокер ORCH-54). ORCH-065 вводит фоновый watchdog, чтобы смерть процесса/потока на любой стадии НЕ оставляла навсегда захваченных ресурсов:

  • Job-reaper (src/job_reaper.py) — daemon-поток по образцу reconciler, работает без рестарта. Трёхуровневая liveness: Tier-1 мёртвый jobs.pid (новая колонка) после reaper_dead_ticks подряд тиков (анти-ложноположительность — живой долгий агент не реапится); Tier-2 agent_runs.exit_code записан, а job ещё running — но это окно неоднозначно (живой monitor пишет exit_code ПЕРВЫМ, затем git push/PR/Plane-комментарии), поэтому Tier-2 реапит только после finalization-grace reaper_finalize_grace_s (живой финализирующий monitor НЕ реапится); Tier-3 backstop по потолку reaper_max_running_s (> max agent_timeout+grace). Действие переиспользует контракты по принципу claim-before-act: для exit0 канонический QG оценивается read-only ПЕРЕД атомарным claim, затем claim done ПЕРВЫМ и только победитель claim делает _try_advance_stage (advance+enqueue) — проигравший claim (поздний monitor / стартовый requeue) не выполняет побочных эффектов (нет дубль-advance/-enqueue); источник истины — канонический QG, не факт «exit0»; гейт красный или exit≠0/ неизвестно → attempts<maxqueued, иначе failed+Telegram. Атомарный reap-claim (UPDATE ... WHERE id=? AND status='running') совместим со стартовым requeue_running_jobs (restart-safe, без двойной обработки).
  • Проактивный реклейм stale/dead lease (функции в merge_gate.py: pid_alive, reclaim_stale_lease) — на старте (рядом с requeue_running_jobs) и периодически из тика reaper: освобождает lease, чей держатель мёртв (pid не жив) ИЛИ просрочен (TTL merge_lock_timeout_s); живой держатель в пределах TTL — НЕ трогать (защита легитимного merge). holder-aware, never-raise, условность как ORCH-43 (merge_gate_repos/self-hosting).
  • Идемпотентная финализация merge — без новой merge-логики: re-drive через reaper→queued→переисполнение стадии / reconciler; дорогие шаги не повторяются (branch_is_behind_main==False); добавлен never-raise guard pr_already_merged (читает состояние PR) — уже слит = no-op. Консультируется самим merge-актором: фактический merge PR в main делает агент deployer (в начале стадии deploy), поэтому wiring — в его промпте .openclaw/agents/deployer.md, который вызывает pr_already_merged ПЕРЕД любым (повторным) merge (AC-11). Чек check_branch_mergeable НЕ меняется (AC-13): он на ПЕРВОМ ребре deploy-staging → deploy, а риск второго merge — на re-drive самой стадии deploy.
  • Схема БД: единственное изменение — jobs.pid INTEGER через идемпотентный _ensure_column (live-safe). STAGE_TRANSITIONS, QG_CHECKS, check_*, БАГ-8, exit-коды хука, файл-схема lease — без изменений.
  • Наблюдаемость: блок reaper в GET /queue (enabled, interval, last_run_ts, reaped_total, last_reaped, lease_reclaimed_total); каждый reap/lease-reclaim → logger.warning; reap→failed и lease-reclaim → Telegram.
  • Kill-switch'и: ORCH_REAPER_ENABLED, ORCH_REAPER_INTERVAL_S, ORCH_REAPER_DEAD_TICKS, ORCH_REAPER_MAX_RUNNING_S, ORCH_REAPER_FINALIZE_GRACE_S, ORCH_LEASE_RECLAIM_ENABLED; false → строго прежнее поведение.

Подробнее: adr-0011, детально — docs/work-items/ORCH-065/06-adr/ADR-001-job-reaper-and-lease-reclaim.md.

Осмысленная статусная модель Plane (ORCH-066 — реализовано)

Plane-доска была семантически перегружена: In Progress означал «человек запускает конвейер», «идёт анализ», «идёт прод-деплой» и «возврат из Needs Input» одновременно. ORCH-066 наводит порядок по утверждённой Owner модели, меняя только слой B (Plane-индикация: src/plane_sync.py + точки простановки в src/stage_engine.py/ src/webhooks/plane.py/src/reconciler.py) и не трогая слой A (STAGE_TRANSITIONS, инвариант). Статус — индикация, не управление (вердикты по-прежнему из YAML-frontmatter):

Backlog → Todo → [To Analyse] → Analysis → [In Review → Approved] → Architecture →
Development → Code-Review → Testing → Awaiting Deploy → [Confirm Deploy] → Deploying →
Monitoring after Deploy → Done

[...] = человеческий вход-триггер; остальное ставит орк.

  • 6 новых логических ключей (to_analyse/analysis/code_review/awaiting_deploy/ deploying/monitoring) в _PLANE_NAME_TO_KEY (резолв по имени) + _DEFAULT_STATES. To Analyse заменяет In Progress как вход-триггер (старт + resume аналитика из Needs Input; fork «старт vs resume» по get_task_by_plane_id+has_active_job_for_task — сохранён). Стадии: analysis→Analysis, review→Code-Review (_STAGE_TO_STATE_KEY).
  • Self-deploy фазы: Phase A → Awaiting Deploy (разгружает In Review), Phase B → Deploying, Phase C/terminal-sync (self) → Monitoring after Deploy (НЕ Done сразу); post-deploy monitor (ORCH-021): HEALTHY-окно → Done, DEGRADED → Blocked (тик по-прежнему НИКОГДА не рестартит прод — ALERT_ONLY). Не-self репо: deploy → Done как сейчас (terminal-sync разводится по post_deploy.post_deploy_applies).
  • Fail-closed (project-relative alias-fallback): отсутствующий новый статус в проекте деградирует на собственный базовый UUID того же проекта (to_analyse/analysis→in_progress, code_review→review, awaiting_deploy→in_review, deploying→in_progress, monitoring→done) — индикация откатывается к текущей, конвейер не ломается, PATCH валиден даже при частичной конфигурации. Enduro (статусы не создаются) → строго прежнее поведение. Усиленный паттерн ORCH-059 AC-7.
  • Reconciler: F-2 триггер in_progressto_analyse; Guard 2 skip-set расширен активными ожиданиями (awaiting_deploy/deploying/monitoring) с вычитанием базовых рабочих статусов — на enduro (алиасы схлопнуты) нулевой регресс, на orchestrator skip реальных ожиданий (BR-13).
  • Инварианты: STAGE_TRANSITIONS, QG_CHECKS, check_deploy_status, exit-коды хука, merge-gate, Confirm Deploy, механизм Needs Input (analyst-only), схема БД — без изменений. Без нового kill-switch (раскат гейтится созданием Plane-статусов оператором). Инфра-предусловие — docs/work-items/ORCH-066/07-infra-requirements.md.

Подробнее: docs/work-items/ORCH-066/06-adr/ADR-001-plane-status-model.md.

Откаты

  • Reviewer REQUEST_CHANGES → откат на development + retry (MAX_DEVELOPER_RETRIES = 3).
  • Tester check_tests_passed FAIL → откат на development + retry.
  • Deploy / deploy-staging FAILED → откат на development.
  • Merge-gate FAIL (конфликт rebase / красный re-test, ORCH-043) → откат на development + retry; merge-lock busydefer (не откат, dev-retry не тратится).
  • get_previous_stage использует порядок ключей STAGE_TRANSITIONS.

Обогащение task_desc при заворотах (ORCH-046)

При откате на development task_desc (попадает в .task-dev.md developer-агента) несёт дословный must-fix текст, а не только ссылку — чтобы агент видел суть претензий сразу и не повторял ту же ошибку:

  • reviewer REQUEST_CHANGES → дословные пункты P0/P1 из секции ## Findings файла 12-review.md (extract_review_findings);
  • tester check_tests_passed FAILreason гейта + фрагмент тела 13-test-report.md (приоритет: ## Вывод pytest → FAIL-строки ## Результаты## Итог; extract_test_failures).

Ссылка на полный файл-артефакт сохраняется всегда («Полный контекст»). Парсеры src/review_parse.py — defensive (never-raise); при отсутствующем/битом артефакте task_desc graceful-фоллбэк на прежнюю ссылку-строку, последовательность отката и retry-счётчик не меняются (ADR docs/work-items/ORCH-046/06-adr/ADR-001-embed-findings-in-task-desc.md).

Plane Sync: единый status-коммент агентов (ORCH-016)

Все агенты (analyst / architect / developer / reviewer / tester / deployer) пишут финальный коммент через один хелпер usage.build_status_comment(...) (ADR docs/work-items/ORCH-016/06-adr/ADR-001-unified-status-comment.md). Формат HTML, разделители <br>:

{ICON} {RoleName} — {описание стадии}
[Verdict|Status: VALUE]                  # reviewer/tester/deployer, из YAML-frontmatter артефакта
[Длительность: 4m 12s]                   # явный duration_s от launcher, либо fallback из agent_runs
<b>Документы:</b><ul><li><a href="…">label</a></li>…</ul>
[<sub>8.5M in / 45.8k out · $7.29</sub>] # тех-хвост usage; опускается при нулях
  • Длительность считается launcher'ом (_monitor_agent) и пробрасывается в _post_usage_comments; для analyst (коммент строится в stage_engine) используется DB-фоллбэк usage.get_agent_duration(task_id, agent).
  • Vердикт-парсерsrc/frontmatter.read_frontmatter_value(...) (defensive, никогда не raise). Машинные ключи: reviewer → verdict: (12-review.md); testing-гейт check_tests_passed (13-test-report.md) → любое из трёх равноправных: result: (канон промпта тестера), verdict:, status: (ORCH-047, ADR-001); deployer → deploy_status: (14-deploy-log.md), staging_status: (15-staging-log.md). Negative-токен в любом поле авторитетен (перебивает positive).
  • Формат коммента не меняет реестр гейтов и стадий; коммент — отображение, не управление.

База данных (SQLite)

  • events — входящие вебхуки (дедуп)
  • tasks — задачи и их стадии
  • agent_runs — запуски агентов (run_id, usage, cost)
  • jobs — очередь задач (ORCH-1); колонка pid (ORCH-065) — pid агентского процесса для liveness-детекции зомби job-reaper'ом
  • job_deps — декларативные зависимости задач (ORCH-026, Уровень B): (task_id, depends_on_task_id), аддитивная; источник истины планировщика для гейта «B ждёт A»
  • repo_freeze — durable per-repo rollback-freeze (ORCH-088, FR-5): (id, repo, frozen_at, reason, work_item_id, cleared_at), аддитивная append-only; активный freeze ⇔ строка репо с cleared_at IS NULL. Выставляется post-deploy DEGRADED (set_repo_freeze), снимается вручную (POST /serial-gate/unfreezecleared_at=now). Гейтит serial-claim безусловно (деградировавшая задача уже done)

Изоляция (git worktree, ORCH-2)

Каждая задача исполняется в отдельном git worktree, ветки не пересекаются. Репозитории проектов разделены под /repos/<project>.

API

Method Path Описание
GET /health health check
GET /status активные задачи (stage != done)
GET /queue очередь: counts + max_concurrency + resilience + reconcile (ORCH-053) + reaper (ORCH-065) + post_deploy (ORCH-021) + task_deps (ORCH-026) + serial_gate (ORCH-088) + последние jobs
POST /serial-gate/unfreeze ORCH-088 (FR-5): ручное снятие per-repo rollback-freeze (query/body repo=<repo>) → {ok, repo, cleared, frozen}; идемпотентно. Альтернатива — UPDATE repo_freeze SET cleared_at=datetime('now') WHERE repo=? AND cleared_at IS NULL
POST /webhook/plane Plane webhook
POST /webhook/gitea Gitea webhook (push, PR, CI status)

Деплой и эксплуатация

Топология, контейнеры, порты, env-карта, self-hosting риски — docs/operations/INFRA.md. Деплой-хук — DEPLOY_HOOK.md. Staging — STAGING.md.

ADR

Сквозные архитектурные решения — adr/. Per-work-item решения — docs/work-items/<id>/06-adr/.

Детали реализации

Схема БД, потоки данных, resilience-слой, детали Dockerfile — internals.md.


Актуально на 2026-06-07. Обновлять при изменении src/stages.py, src/qg/checks.py, src/main.py. Статусы доработок: ORCH-036 (исполняемый самодеплой deploy, adr-0007) — реализовано; ORCH-043 (merge-gate, adr-0006) — design, ветка feature/ORCH-043; ORCH-053 (reconciler, adr-0007, src/reconciler.py) — реализовано; ORCH-060 (F-1 skip escalated/Blocked/Needs-Input, docs/work-items/ORCH-060/06-adr/ADR-001) — реализовано в ветке feature/ORCH-060 (Guard 1 developer_retry_count>=MAX_DEVELOPER_RETRIES + Guard 2 plane_sync.fetch_issue_state Blocked/Needs-Input, флаг ORCH_RECONCILE_SKIP_BLOCKED_ENABLED); ORCH-058 (провенанс staging-образа: check_staging_image_fresh + staging_check свежего образа + хук-guard, adr-0008) — реализовано в ветке feature/ORCH-058 (обновлять также при изменении src/image_freshness.py, scripts/orchestrator-deploy-hook.sh, Dockerfile); ORCH-061 (толерантность staging-вердикта к инфра-FAIL C9a/C9b, adr-0009, docs/work-items/ORCH-061/06-adr/ADR-001) — реализовано в ветке feature/ORCH-061 (обновлять также при изменении src/staging_verdict.py, scripts/staging_check.py, флаг staging_infra_tolerance_enabled); ORCH-021 (post-deploy наблюдение прода + реакция на деградацию, adr-0010, docs/work-items/ORCH-021/06-adr/ADR-001) — реализовано в ветке feature/ORCH-021-post-deploy-rollback (reserved-agent job post-deploy-monitor: арм в src/stage_engine.py блок next_stage == "done", тик run_post_deploy_monitor + перехват в src/agents/launcher.py ДО _spawn; чистая логика src/post_deploy.py never-raise; флаги post_deploy_* в src/config.py; блок post_deploy в /queue; артефакт 16-post-deploy-log.md; self-hosting всегда ALERT_ONLY — тик не рестартит прод; обновлять также при изменении src/post_deploy.py / арм-блока / launcher-перехвата); ORCH-065 (job-reaper + проактивный реклейм merge-lease + идемпотентная финализация merge, adr-0011, docs/work-items/ORCH-065/06-adr/ADR-001) — реализовано в ветке feature/ORCH-065 (новый daemon-поток src/job_reaper.py + старт/стоп в src/main.py lifespan; колонка jobs.pid через _ensure_column + проставление в src/agents/launcher.py _spawn; функции реклейма lease pid_alive/reclaim_stale_lease + guard pr_already_merged в src/merge_gate.py (консультируется merge-актором — промпт .openclaw/agents/deployer.md); флаги reaper_*/lease_reclaim_* в src/config.py; блок reaper в /queue; обновлять также при изменении этих мест); ORCH-022 (security-гейт: secret-scanning gitleaks + dependency audit pip-audit как под-гейт ребра deploy-staging → deploy ПЕРВЫМ, adr-0012, docs/work-items/ORCH-022/06-adr/ADR-001) — реализовано в ветке feature/ORCH-022-security-secret-scanning (leaf src/security_gate.py never-raise + check_security_gate в src/qg/checks.py QG_CHECKS + врезка _handle_security_gate в src/stage_engine.py блок current_stage == "deploy-staging" ПЕРВОЙ; флаги security_* в src/config.py; gitleaks (pinned) в Dockerfile, pip-audit в requirements.txt, .gitleaks.toml в корне; артефакт 17-security-report.md; обновлять также при изменении этих мест). Актуально на 2026-06-07. Обновлять при изменении src/stages.py, src/qg/checks.py, src/main.py. Статусы доработок: ORCH-036 (исполняемый самодеплой deploy, adr-0007) — реализовано; ORCH-043 (merge-gate, adr-0006) — design, ветка feature/ORCH-043; ORCH-053 (reconciler, adr-0007, src/reconciler.py) — реализовано; ORCH-060 (F-1 skip escalated/Blocked/Needs-Input, docs/work-items/ORCH-060/06-adr/ADR-001) — реализовано в ветке feature/ORCH-060 (Guard 1 developer_retry_count>=MAX_DEVELOPER_RETRIES + Guard 2 plane_sync.fetch_issue_state Blocked/Needs-Input, флаг ORCH_RECONCILE_SKIP_BLOCKED_ENABLED); ORCH-058 (провенанс staging-образа: check_staging_image_fresh + staging_check свежего образа + хук-guard, adr-0008) — реализовано в ветке feature/ORCH-058 (обновлять также при изменении src/image_freshness.py, scripts/orchestrator-deploy-hook.sh, Dockerfile); ORCH-061 (толерантность staging-вердикта к инфра-FAIL C9a/C9b, adr-0009, docs/work-items/ORCH-061/06-adr/ADR-001) — реализовано в ветке feature/ORCH-061 (обновлять также при изменении src/staging_verdict.py, scripts/staging_check.py, флаг staging_infra_tolerance_enabled); ORCH-021 (post-deploy наблюдение прода + реакция на деградацию, adr-0010, docs/work-items/ORCH-021/06-adr/ADR-001) — реализовано в ветке feature/ORCH-021-post-deploy-rollback (reserved-agent job post-deploy-monitor: арм в src/stage_engine.py блок next_stage == "done", тик run_post_deploy_monitor + перехват в src/agents/launcher.py ДО _spawn; чистая логика src/post_deploy.py never-raise; флаги post_deploy_* в src/config.py; блок post_deploy в /queue; артефакт 16-post-deploy-log.md; self-hosting всегда ALERT_ONLY — тик не рестартит прод; обновлять также при изменении src/post_deploy.py / арм-блока / launcher-перехвата); ORCH-065 (job-reaper + проактивный реклейм merge-lease + идемпотентная финализация merge, adr-0011, docs/work-items/ORCH-065/06-adr/ADR-001) — реализовано в ветке feature/ORCH-065 (новый daemon-поток src/job_reaper.py + старт/стоп в src/main.py lifespan; колонка jobs.pid через _ensure_column + проставление в src/agents/launcher.py _spawn; функции реклейма lease pid_alive/reclaim_stale_lease + guard pr_already_merged в src/merge_gate.py (консультируется merge-актором — промпт .openclaw/agents/deployer.md); флаги reaper_*/lease_reclaim_* в src/config.py; блок reaper в /queue; обновлять также при изменении этих мест); ORCH-059 (выделенный статус-триггер прод-деплоя «Confirm Deploy», ADR docs/work-items/ORCH-059/06-adr/ADR-001) — реализовано в ветке feature/ORCH-059 (маппинг "Confirm Deploy"→"confirm_deploy" в src/plane_sync.py _PLANE_NAME_TO_KEY, НЕ в _DEFAULT_STATES = fail-closed; ветка handle_confirm_deploy + fail-closed .get("confirm_deploy") в src/webhooks/plane.py handle_issue_updated; keyword-only confirm_deploy в src/stage_engine.py advance_stage — Фаза B деплоит ТОЛЬКО при confirm_deploy=True, иначе Approved-на-deploy = no-op; CTA Фазы A просит «Confirm Deploy»; эксплуатация — статус доски «Confirm Deploy» в Plane-проекте ORCH, docs/work-items/ORCH-059/07-infra-requirements.md). Актуально на 2026-06-07. Обновлять при изменении src/stages.py, src/qg/checks.py, src/main.py. Статусы доработок: ORCH-036 (исполняемый самодеплой deploy, adr-0007) — реализовано; ORCH-043 (merge-gate, adr-0006) — design, ветка feature/ORCH-043; ORCH-053 (reconciler, adr-0007, src/reconciler.py) — реализовано; ORCH-060 (F-1 skip escalated/Blocked/Needs-Input, docs/work-items/ORCH-060/06-adr/ADR-001) — реализовано в ветке feature/ORCH-060 (Guard 1 developer_retry_count>=MAX_DEVELOPER_RETRIES + Guard 2 plane_sync.fetch_issue_state Blocked/Needs-Input, флаг ORCH_RECONCILE_SKIP_BLOCKED_ENABLED); ORCH-058 (провенанс staging-образа: check_staging_image_fresh + staging_check свежего образа + хук-guard, adr-0008) — реализовано в ветке feature/ORCH-058 (обновлять также при изменении src/image_freshness.py, scripts/orchestrator-deploy-hook.sh, Dockerfile); ORCH-061 (толерантность staging-вердикта к инфра-FAIL C9a/C9b, adr-0009, docs/work-items/ORCH-061/06-adr/ADR-001) — реализовано в ветке feature/ORCH-061 (обновлять также при изменении src/staging_verdict.py, scripts/staging_check.py, флаг staging_infra_tolerance_enabled); ORCH-021 (post-deploy наблюдение прода + реакция на деградацию, adr-0010, docs/work-items/ORCH-021/06-adr/ADR-001) — реализовано в ветке feature/ORCH-021-post-deploy-rollback (reserved-agent job post-deploy-monitor: арм в src/stage_engine.py блок next_stage == "done", тик run_post_deploy_monitor + перехват в src/agents/launcher.py ДО _spawn; чистая логика src/post_deploy.py never-raise; флаги post_deploy_* в src/config.py; блок post_deploy в /queue; артефакт 16-post-deploy-log.md; self-hosting всегда ALERT_ONLY — тик не рестартит прод; обновлять также при изменении src/post_deploy.py / арм-блока / launcher-перехвата); ORCH-065 (job-reaper + проактивный реклейм merge-lease + идемпотентная финализация merge, adr-0011, docs/work-items/ORCH-065/06-adr/ADR-001) — реализовано в ветке feature/ORCH-065 (новый daemon-поток src/job_reaper.py + старт/стоп в src/main.py lifespan; колонка jobs.pid через _ensure_column + проставление в src/agents/launcher.py _spawn; функции реклейма lease pid_alive/reclaim_stale_lease + guard pr_already_merged в src/merge_gate.py (консультируется merge-актором — промпт .openclaw/agents/deployer.md); флаги reaper_*/lease_reclaim_* в src/config.py; блок reaper в /queue; обновлять также при изменении этих мест); ORCH-066 (осмысленная статусная модель Plane — слой B, docs/work-items/ORCH-066/06-adr/ADR-001-plane-status-model.md) — реализовано в ветке feature/ORCH-066-plane (только Plane-индикация: новые ключи to_analyse/analysis/code_review/awaiting_deploy/deploying/monitoring в _PLANE_NAME_TO_KEY/_DEFAULT_STATES + project-relative _STATE_ALIAS_FALLBACK в get_project_states + _STAGE_TO_STATE_KEY analysis/review + 5 новых set_issue_* в src/plane_sync.py; триггер in_progressto_analyse и set_issue_analysis в src/webhooks/plane.py; Phase A→Awaiting Deploy / Phase B→Deploying / terminal-sync split monitoring↔done / post-deploy monitor HEALTHY→Done DEGRADED→Blocked в src/stage_engine.py; F-2 триггер to_analyse + Guard 2 skip-set с вычитанием base_working в src/reconciler.py; STAGE_TRANSITIONS/QG/схема БД НЕ трогаются; без kill-switch — раскат гейтится созданием 6 Plane-статусов оператором, docs/work-items/ORCH-066/07-infra-requirements.md; обновлять при изменении этих мест). Актуально на 2026-06-07. Обновлять при изменении src/stages.py, src/qg/checks.py, src/main.py. Статусы доработок: ORCH-036 (исполняемый самодеплой deploy, adr-0007) — реализовано; ORCH-043 (merge-gate, adr-0006) — design, ветка feature/ORCH-043; ORCH-053 (reconciler, adr-0007, src/reconciler.py) — реализовано; ORCH-060 (F-1 skip escalated/Blocked/Needs-Input, docs/work-items/ORCH-060/06-adr/ADR-001) — реализовано в ветке feature/ORCH-060 (Guard 1 developer_retry_count>=MAX_DEVELOPER_RETRIES + Guard 2 plane_sync.fetch_issue_state Blocked/Needs-Input, флаг ORCH_RECONCILE_SKIP_BLOCKED_ENABLED); ORCH-058 (провенанс staging-образа: check_staging_image_fresh + staging_check свежего образа + хук-guard, adr-0008) — реализовано в ветке feature/ORCH-058 (обновлять также при изменении src/image_freshness.py, scripts/orchestrator-deploy-hook.sh, Dockerfile); ORCH-061 (толерантность staging-вердикта к инфра-FAIL C9a/C9b, adr-0009, docs/work-items/ORCH-061/06-adr/ADR-001) — реализовано в ветке feature/ORCH-061 (обновлять также при изменении src/staging_verdict.py, scripts/staging_check.py, флаг staging_infra_tolerance_enabled); ORCH-021 (post-deploy наблюдение прода + реакция на деградацию, adr-0010, docs/work-items/ORCH-021/06-adr/ADR-001) — реализовано в ветке feature/ORCH-021-post-deploy-rollback (reserved-agent job post-deploy-monitor: арм в src/stage_engine.py блок next_stage == "done", тик run_post_deploy_monitor + перехват в src/agents/launcher.py ДО _spawn; чистая логика src/post_deploy.py never-raise; флаги post_deploy_* в src/config.py; блок post_deploy в /queue; артефакт 16-post-deploy-log.md; self-hosting всегда ALERT_ONLY — тик не рестартит прод; обновлять также при изменении src/post_deploy.py / арм-блока / launcher-перехвата); ORCH-065 (job-reaper + проактивный реклейм merge-lease + идемпотентная финализация merge, adr-0011, docs/work-items/ORCH-065/06-adr/ADR-001) — реализовано в ветке feature/ORCH-065 (новый daemon-поток src/job_reaper.py + старт/стоп в src/main.py lifespan; колонка jobs.pid через _ensure_column + проставление в src/agents/launcher.py _spawn; функции реклейма lease pid_alive/reclaim_stale_lease + guard pr_already_merged в src/merge_gate.py (консультируется merge-актором — промпт .openclaw/agents/deployer.md); флаги reaper_*/lease_reclaim_* в src/config.py; блок reaper в /queue; обновлять также при изменении этих мест); ORCH-068 (livelock-fix reconciler F-2: терминал-исключение по группе состояния + _note_unblock только при подтверждённом state change + дедуп; TTL _STATES_CACHE, docs/work-items/ORCH-068/06-adr/ADR-001) — реализовано в ветке feature/ORCH-068 (D1 терминал-гард по группе _is_terminal_state + get_project_state_groups в src/plane_sync.py; D2 сравнение стадии до/после _dispatch + дедуп-словарь в src/reconciler.py; TTL-запись _STATES_CACHE + флаг plane_states_ttl_s в src/config.py; счётчики skipped_terminal_total/deduped_total в /queue; обновлять также при изменении src/reconciler.py F-2, src/plane_sync.py get_project_states/get_project_state_groups/_STATES_CACHE). Актуально на 2026-06-09. Статус доработки: ORCH-088 (per-repo serial gate, Этап 1 serial e2e, adr-0017, docs/work-items/ORCH-088/06-adr/ADR-001-serial-gate.md) — реализовано в ветке feature/ORCH-088 (leaf src/serial_gate.py never-raise: gate-фрагмент в src/db.py claim_next_job fail-OPEN c FIFO-условием t2.id < jobs.task_id + freeze repo_freeze.cleared_at IS NULL, freeze-решения fail-CLOSED; отложенный срез ветки src/webhooks/plane.py start_pipeline → src/agents/launcher.py _materialize_deferred_branch (sync asyncio.run в worker-потоке) при claim analyst-job; durable freeze таблица repo_freeze (idempotent миграция в init_db) + set_repo_freeze в src/stage_engine.py DEGRADED-ветке run_post_deploy_monitor + ручное снятие POST /serial-gate/unfreeze в src/main.py; флаги serial_gate_enabled/serial_gate_repos/serial_gate_freeze_enabled в src/config.py; блок serial_gate в GET /queue; STAGE_TRANSITIONS/QG_CHECKS НЕ трогаются; обновлять также при изменении этих мест).