Files
orchestrator/CLAUDE.md
claude-bot f0cd19d748 feat(replication): ORCH-10b Bundled-тираж — bundle-compose всего стека + bootstrap-скрипт
Закрывает Type B эпика ORCH-10 (по ADR-001 ORCH-103, D1–D11):

- deploy/bundled/docker-compose.yml — самодостаточный compose всего стека
  (орк + watchdog + Gitea 1.22.6 + зеркало upstream Plane CE v0.23.1,
  ~14 контейнеров); project name orchestrator-bundle (узнаваемый префикс),
  container_name не пиннится, staging-контура нет; одна bridge-сеть,
  машинный трафик — сервис-DNS, наружу только человеческие порты;
  GITEA__webhook__ALLOWED_HOST_LIST=orchestrator; все образы пиннованы
  неподвижными тегами. Корневой compose/Dockerfile/src/** — байт-в-байт.
- deploy/bundled/.env.example — конфиг-канон bundle (плейсхолдеры, ни одного
  дефолтного пароля; key-set-sync интерполяций держит тест).
- scripts/bootstrap_bundle.py — python stdlib-only, режимы plan/apply/verify,
  step-движок check→ensure, exit 0/2/1: preflight (fail-fast до мутаций) →
  секреты (gen_secrets.py + stdlib secrets, без перетирания) → up+готовность →
  init Gitea автоматом → init Plane (manual-step с API-верификацией) →
  онбординг строго onboard_project.py apply+verify → token-remote клон →
  сборка .env/.env.watchdog (единственный писатель, права 600) → health.
  Delete-операций нет вообще (D9), секреты не печатаются (NFR-3).
- CHANGELOG.md, CLAUDE.md (абзац Type B), .gitignore (deploy/bundled/repos/).

Док BUNDLED_SETUP.md, REPLICATION §1, arch README, adr-0038 и три структурных
тест-модуля (TC-01…TC-11) — в предыдущих коммитах ветки; полный регресс
1844 passed, ruff по файлам задачи чистый.

Refs: ORCH-103

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 02:16:32 +03:00

59 KiB
Raw Blame History

CLAUDE.md — паспорт проекта orchestrator

TL;DR

Мульти-агентный оркестратор разработки. FastAPI-сервис: принимает webhooks от Plane и Gitea, ведёт задачи по конвейеру стадий через Quality Gates, запускает Claude CLI агентов (analyst → architect → developer → reviewer → tester → deployer) на каждой стадии. Оркестратор дорабатывает в том числе сам себя (self-hosting).

Стек

  • Backend: FastAPI + uvicorn (Python 3.12)
  • БД: SQLite (src/db.py)
  • Агенты: Claude CLI (ORCH_CLAUDE_BIN), по одному промпту на роль в .openclaw/agents/. ORCH-74: модель/эффорт агента берутся ТОЛЬКО из config (resolve_agent_model/resolve_agent_effort, ORCH-41) — frontmatter model: удалён как мёртвый, frontmatter описательный; имя модели валидируется форматом ^claude-…$ перед --model (never-break). ORCH-077 (52d, замыкает эпик 52): тело всех 6 промптов переписано в едином каноне Anthropic (5 обязательных XML-секций в нормативном порядке <context><task><deliverables><constraints><output_format>, запреты в формате « X → Y», <thinking> у решающих ролей), и каждый промпт добровольно эмитит 6-польную frontmatter-схему 52c (work_item/stage/author_agent/status/created_at/model_used) аддитивно — рядом с machine-verdict ключом, НЕ меняя его имя/регистр/значения (verdict:/result:/staging_status:/deploy_status:/security_status: — байт-в-байт). Это docs/prompts-only изменение: src/**/STAGE_TRANSITIONS/QG_CHECKS/схема БД не тронуты; frontmatter_validation_strict остаётся False (enforcement НЕ включён). Промпт cat-ается из worktree в момент запуска → новые промпты вступают в силу на следующем worktree от main без прод-рестарта. Анти-регресс — структурные тесты tests/test_agent_prompts_canon.py + зелёный test_agent_frontmatter_no_model.py. Норматив на будущее: новые/изменённые агент-промпты следуют этому канону. Детали — docs/architecture/adr/adr-0021-prompt-canon-anthropic.md. ORCH-092 (эпилог эпика 52, docs/prompts-only): аудит 6 промптов поверх канона — копируемые frontmatter-примеры расхардкожены (created_at: <YYYY-MM-DD>/model_used: <resolve ORCH-41> + врезка «подставь date +%F/модель из конфига, не копируй буквально»; литерал claude-opus-4-8 — только справка в таблице полей); добавлена секция <escalation> developer/reviewer/tester (после </success_criteria>, порядок 5 секций цел); developer лишён ручного git rebase origin/main (свежесть базы — инвариант движка serial-gate ORCH-088 + auto_rebase_onto_main под merge-lease; ручной rebase конфликтовал с запретом force-push — ADR-001 D1); tester обогащён worktree-путём + smoke serial_gate + покрытием каждого TC; из reviewer удалена мёртвая строка «тот же экземпляр Developer». Языковое исключение (нормативно, ADR-001 D2): deployer.md сознательно остаётся на английском (5 ru + 1 en) как самый safety-critical промпт — НЕ «чинить» язык вслепую; критичные self-hosting-запреты подняты в видную рамку. Verdict-ключи и канон 52d — байт-в-байт; анти-регрессtests/test_agent_prompts_canon.py (ORCH-092 TC-01…TC-08). Детали — docs/work-items/ORCH-092/06-adr/ADR-001-developer-rebase-and-deployer-language.md.
  • Очередь задач: собственная (SQLite jobs, src/queue_worker.py, ORCH-1). ORCH-026: claim_next_job гейтит задачи с незавершёнными зависимостями (job_deps, NOT EXISTS) без занятия слота max_concurrency; декларации/детект циклов — leaf src/task_deps.py (kill-switch ORCH_TASK_DEPS_ENABLED). Сериализация мержа одного репо — безусловный pre-merge rebase под merge-lease (ORCH_PREMERGE_REBASE_ALWAYS). ORCH-088 (serial gate, Этап 1): новая задача репо не входит в analysis (analyst-job не выбирается, ветка не режется), пока в репо есть более ранняя незавершённая задача (t2.id < jobs.task_id, FIFO) ИЛИ репо заморожен (repo_freeze). Срез ветки отложен со start_pipeline на момент claim analyst-job (launcher._materialize_deferred_branch) — база = свежий origin/main с кодом предшественника (анти-stale-base). Post-deploy DEGRADED → durable per-repo freeze (repo_freeze, cleared_at IS NULL = активен) + Telegram; снятие — вручную POST /serial-gate/unfreeze?repo=…. Leaf src/serial_gate.py (claim — fail-OPEN, freeze — fail-CLOSED); флаги ORCH_SERIAL_GATE_ENABLED (kill-switch), ORCH_SERIAL_GATE_REPOS (CSV; пусто = все репо), ORCH_SERIAL_GATE_FREEZE_ENABLED. Блок serial_gate в GET /queue. STAGE_TRANSITIONS/QG_CHECKS не тронуты. ORCH-093 (merge-актор устойчив к икоте Gitea): детерминированный merge-актор под-гейта deploy → done (src/merge_gate.py) ретраит транзиентные ошибки Gitea вместо ложного HOLD (инцидент ORCH-063: POST …/merge405 "try again later" сразу после пуша). merge_pr оборачивает только мутирующий POST …/merge в ограниченный retry-loop с экспоненциальным backoff (min(base*2^(i-1), max), потолок суммарного сна (N-1)*max ≤ 10 с); классификатор _classify_merge_response: транзиент (ретрай) — 405/408/5xx/таймаут/сетевая + 409/422 при mergeable==True (доп. GET /pulls/{index}; mergeable==None → дефолт-транзиент, fail-OPEN-в-ретрай), терминал (быстрый честный False, защита ORCH-071/073 как прежде) — 403/404/реальный конфликт (mergeable==False). Kill-switch merge_retry_enabled=false → ровно один POST (байт-в-байт прежнее one-shot); флаги ORCH_MERGE_RETRY_* (max_attempts=3, backoff_base_s=2, backoff_max_s=5). Гард already-in-main в ensure_open_pr (leaf _branch_fully_in_main, git merge-base --is-ancestor HEAD origin/main): ветка целиком в main → исход "already-in-main" без создания мусорного пустого PR; _handle_merge_verify пропускает merge_pr и отдаёт авторитетному SHA-в-main довести до done (НЕ HOLD); git-ошибка → fail-OPEN на create-путь. Без отдельного флага (накрыт merge_verify_autocreate_pr_enabled). INV-4 (мерж только через Gitea PR-merge API, никогда push/force-push в main), never-raise, STAGE_TRANSITIONS/QG_CHECKS/схема БД — сохранены. Детали — docs/work-items/ORCH-093/06-adr/ADR-001-merge-transient-retry-and-already-in-main-guard.md, сквозной docs/architecture/adr/adr-0027-merge-actor-transient-retry-and-already-in-main.md.
  • Контейнеризация: Docker + Compose
  • CI/CD: Gitea Actions (.gitea/workflows/)
  • Деплой: docker compose на mva154

Команды

  • uvicorn src.main:app --reload --port 8500 — поднять локально (dev)
  • pytest tests/ -q — все тесты
  • docker compose up -d --build — прод
  • docker compose --profile staging up -d orchestrator-staging — staging-песочница (8501)

Среды

  • prodorchestrator (8500), внешний URL https://openclaw.mva154.duckdns.org/orchestrator/
  • stagingorchestrator-staging (8501), изолированная БД (./data/staging), только sandbox-проект

Структура

  • src/ — приложение (main, config, db, stages, stage_engine, queue_worker, projects, usage)
  • src/agents/launcher.py — запуск Claude CLI агентов
  • src/qg/checks.py — Quality Gate проверки
  • src/webhooks/ — приём вебхуков Plane/Gitea
  • tests/ — pytest
  • docs/ — документация, ADR, work-items, operations
  • scripts/ — утилиты (staging_check.py, orchestrator-deploy-hook.sh)

Конвейер (кратко; детали — docs/architecture/README.md)

created → analysis → architecture → development → review → testing → deploy-staging → deploy → done
                          ↑                          │
                          └──── REQUEST_CHANGES ──────┘  (откат на development, max 3)

Статусная модель Plane (ORCH-066) — индикация ≠ управление

Статусы Plane — это слой B (индикация), отдельный от слоя A (машина стадий) src/stages.py::STAGE_TRANSITIONS. Plane показывает наблюдателю осмысленную картину (Backlog → Todo → Analysis → Architecture → Development → Code-Review → Testing → Awaiting Deploy → Deploying → Monitoring after Deploy → Done + человеческие гейты In Review/Approved, Confirm Deploy), но НИКОГДА не управляет конвейером. Маппинг и сеттеры — src/plane_sync.py (6 новых ключей: to_analyse/analysis/code_review/awaiting_deploy/deploying/monitoring), с project-relative alias-fallback: на частично сконфигурированном проекте новый ключ деградирует на базовый UUID ТОГО ЖЕ проекта (нулевая регрессия для enduro-trails). Детали — docs/architecture/README.md.

Terminal-window-aware гард deploy-статусов (ORCH-094). Задача с БД stage=done и 0 активных job'ов стабильно держит Plane=Done: три deploy-фазовых сеттера (set_issue_awaiting_deploy/set_issue_deploying/set_issue_monitoring) были терминал-слепы и флаппили Awaiting ⟷ Monitoring (верифицировано на ORCH-061, task 47), т.к. любой стейл/двойной/неизвестный вызов под бот-токеном перезаписывал терминал промежуточным статусом. Новый leaf src/deploy_status_guard.py (чистая, never-raise, config-gated; по образцу serial_gate/labels/cancel) — decide(work_item_id, target, reason) -> ALLOW | CONVERGE_DONE | SUPPRESS на входе трёх сеттеров plane_sync (низкий чокпоинт ловит любой путь, включая неизвестный актор). Инвариант: deploy-статус легитимен ⇔ задача нетерминальна ИЛИ (done И активно пост-деплой-окно post_deploy.window_active = ARMED & не DONE); иначе для done — идемпотентное CONVERGE_DONE (сеттер зовёт set_issue_done), для cancelledSUPPRESS. Чтобы легитимный первый Monitoring (БД уже done к моменту стр. 404) прошёл, арм-блок post_deploy.arm_monitor перенесён выше terminal-sync-блока в advance_stage (ADR-001 D3) → window_active==True до выставления Monitoring. Монитор-тик при БД cancelled мид-окно → закрыть окно без статус-PATCH (zombie-tick guard, FR-3). Наблюдаемость: BC-kwarg reason у трёх сеттеров + одна структурная лог-запись на вердикт (work_item/caller/target/db_stage/window_active/verdict; converge/suppress → WARNING). Read-only аксессор db.get_task_by_work_item_id. Флаги deploy_status_guard_enabled (kill-switch; False → 1:1 прежнее) / deploy_status_guard_repos (CSV; пусто → self-hosting only, enduro не затронут). STAGE_TRANSITIONS/QG_CHECKS/check_*/machine-verdict/схема БД — не тронуты. Детали — docs/work-items/ORCH-094/06-adr/ADR-001-terminal-window-aware-deploy-status-guard.md, сквозной docs/architecture/adr/adr-0028-terminal-window-aware-deploy-status-guard.md.

Нотификации / Telegram live-tracker (ORCH-042/066/067/087)

Каждая задача = одна карточка в Telegram (src/notifications.py). Поведение карточки:

  • Дефолт tracker_modebump (ORCH-067; edit доступен через ORCH_TRACKER_MODE=edit). bump на каждом обновлении удаляет старую карточку и шлёт свежую вниз чата (тихо), edit редактирует на месте. Инвариант «одна карточка на задачу» — в обоих режимах.
  • Зачистка сирот (ORCH-087): bump ведёт авторитетный леджер ВСЕХ созданных карточек (таблица tracker_messages, deleted_at IS NULL = жива) и на каждом обновлении удаляет ВСЕ незакрытые mid, а не только скаляр tracker_message_id (он сохранён как указатель на текущую карточку, BC). Это устраняет класс «замёрзшая сирота» (старая карточка с заголовком ранней стадии, потерявшая ссылку при гонке/delete-fail+send-ok). Новый mid пишется в леджер ТОЛЬКО при успешном send (BR-6); transient-delete остаётся незакрытым для ретрая; «already gone»/>48ч (_DELETE_GONE_MARKERS) → закрывается. Остаточная гонка самозалечивается за один bump. Known-limitation: Telegram 48ч (сироты старше неудаляемы).
  • Эффорт в строке стадии (ORCH-087): колонка agent_runs.effort стампится фактическим resolve_agent_effort в launcher._spawn (CLI его в result-JSON не возвращает); строка рендерится · {model} · {effort} (developer=xhigh, tester/deployer=medium, прочие=high); пустой/исторический effort → суффикс опускается.
  • Честное итоговое время (ORCH-087): done-строка = три независимых подписанных метрики ⏱️ Агенты {Σ agent_runs} · твоё {review~cap} · общее с ожиданием {wall} (раньше Всего {wall} читалось как сумма, которой не является). «Твоё» ограничено tracker_brd_review_cap_s (ORCH_TRACKER_BRD_REVIEW_CAP_S, дефолт 2ч; маркер ~ при отсечке аномального застоя).
  • Статус-строка карточки (📍 <status_label>) показывает текущий Plane-статус по модели ORCH-066 (plane_status_label). Оффлайн-ядро (stage → статус, In Review из brd-clock) работает всегда без сети; best-effort live-overlay (kill-switch tracker_live_status, TTL-кэш, короткий таймаут) лишь дорисовывает ветки, неотличимые offline (Needs Input / Blocked / Rejected / Cancelled / Confirm Deploy / Deploying / Monitoring) и никогда не блокирует конвейер.
  • Кликабельный номер задачи (plane_issue_link) — ORCH-NNN в карточке И во всех уведомлениях (notify_*, alert'ы стадий) рендерится как <a href=…> на issue в Plane; fail-safe → просто html.escape(номер), если ссылку построить нельзя. Никогда не падает.
  • Без link-preview (ORCH-080): оба примитива (send_telegram/edit_telegram) шлют payload с disable_web_page_preview: True — баннер Plane («Modern project management») под кликабельной ссылкой ORCH-NNN больше не разворачивается ни в карточке (bump/edit), ни в notify/alert-сообщениях. parse_mode: HTML сохранён → ссылка остаётся кликабельной.
  • Транспорт (send_telegram/edit_telegram/delete_telegram), disable_notification (карточка тихая, пингуют только alert-хелперы), схема БД — не трогаются.

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

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

  • autoApprove → врезка в stage_engine._handle_analysis_approved_flow (ветка files_ok): 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 (маркер INITIATED, статус Deploying, finalizer). Пропускаются лишь индикативно-человеческие шаги. BR-5 структурно: Phase A достигается только после зелёных под-гейтов ребра deploy-staging → deploy (security → merge-gate → image-freshness → staging) → autoDeploy физически не деплоит сломанное.
  • Чтение лейбловplane_sync.fetch_issue_labels (None при ошибке ≠ []) + get_project_labels ({normalized_name→uuid}, TTL-кэш); сопоставление по нормализованному имени (strip().casefold()), неоднозначность → «нет лейбла». Источник истины — Plane API, не payload вебхука. Новый сеттер set_issue_approved.
  • Флаги (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 → при выключенном флаге нулевой сетевой оверхед.
  • Fail-safe (never auto): любая ошибка/недоступность Plane/неоднозначность → «нет авто» → ручной гейт (never-raise). Прозрачность: лог + Telegram + Plane-коммент + live-карточка; блок auto_labels в GET /queue. Инфра-предусловие: создать лейблы autoApprove/autoDeploy в Plane-проекте ORCH (их отсутствие = ручной режим, fail-safe). Детали — docs/work-items/ORCH-089/06-adr/ADR-001-auto-label-gates.md, docs/architecture/adr/adr-0018-auto-label-gates.md.

Отмена задачи: статус STOP (ORCH-090)

Выделенный Plane-статус STOP — операторская кнопка «отменить + сбросить» задачу. Вводит новое системное терминальное состояние cancelled (стадия tasks.stage='cancelled' + job-исход jobs.status='cancelled'), равноправное done. Логический ключ stopfail-closed (нет в _DEFAULT_STATES, по образцу confirm_deploy/ORCH-059): доска без статуса STOP → ветка не активируется. Маршрут handle_issue_updated → handle_stop → stage_engine.cancel_task:

  • Полный сброс (вне критичного окна): graceful SIGTERM активного агента (launcher.stop_process, переиспользует каскад _watchdog), все job'ы → терминальный cancelled (не реквью'ятся: claim_next_job берёт только queued, reaper/worker сверяют терминал задачи — TR-2), удаление worktree + рабочей Gitea-ветки (gitea.delete_remote_branch, никогда main, без force-push), durable stage='cancelled' + тумбстон натуральных ключей (plane_id/ work_item_id/plane_issue_id → суффикс #cancelled-<id>; ADR-001 D4 уточнён: тумбстонится и plane_issue_id, т.к. get_task_by_plane_id/create_task_atomic матчат по нему — иначе re-create коллизирует; исходный UUID парсится из суффикса для аудита). Docs-артефакты (01..17) сохраняются.
  • STOP в критичном окне merge/deploy (ADR-001 D7): cancel.in_critical_windowотложенная отмена: tasks.cancel_requested_at, снимаются только queued job'ы (running-актор деплоя/мержа не трогается), алерт; детерминированный finalizer (run_deploy_finalizer) доводит необратимый шаг до честного исхода и применяет отмену (force=True). «Критичное окно» = реально начатый необратимый шаг: INITIATED-sentinel self-deploy (ORCH-036; детач-деплой + поздний merge_pr в _handle_merge_verify идут под тем же маркером) либо держание merge-lease (ORCH-043) И активно бегущий актор (running-job). Уточнение P1 (ORCH-090 review): держание merge-lease в Phase A на стадии deploy в ожидании ручного Confirm Deploy БЕЗ бегущего актора полностью обратимо (ничего не смержено/задеплоено) → НЕ критично → немедленный полный сброс (сам отпускает lease). Иначе отмена откладывалась бы к finalizer'у, который оператор (нажавший STOP именно чтобы НЕ подтверждать деплой) не запускает — задача застревала бы с удержанным lease, клиня serial-gate репо. STOP никогда не трогает main/force-push/прод-контейнер/detached-процесс (NFR-3).
  • Кросс-каттинг (adr-0026): предикат «задача терминальна» расширен {done}{done, cancelled} в serial_gate/task_deps/stages.py-сток (иначе отменённая задача заклинит очередь репо); reconciler-терминал-скип уже знал cancelled (ORCH-086). STAGE_TRANSITIONS exit-гейты рёбер / QG_CHECKS / check_*не тронуты (cancelled — сток, не ребро).
  • Дыра релонча закрыта (D6): relaunch агента в handle_status_start ограничен стадией analysis (единственный владелец Needs Input, ORCH-066); ручной перевод существующей задачи в иной промежуточный статус больше не релончит середину пайплайна. Запуск пайплайна — только «To Analyse» → start_pipeline.
  • Флаги stop_status_enabled (kill-switch; False → всё инертно, нулевая регрессия) / stop_status_repos (CSV; пусто → все репо). Leaf src/cancel.py (never-raise). Read-only блок stop в GET /queue. Аддитивные колонки tasks.cancelled_at/cancel_requested_at (_ensure_column). Инфра-предусловие: создать статус STOP с группой cancelled на доске ORCH (его отсутствие = fail-safe no-op). Детали — docs/work-items/ORCH-090/06-adr/ADR-001-stop-cancel-task.md, docs/architecture/adr/adr-0026-stop-cancel-task.md.

Багфикс-трек: дешёвый маршрут для багов (ORCH-019)

Задача с меткой Plane Bug идёт укороченным маршрутом — пропускается стадия architecture (отдельный прогон opus-агента architect + ADR + exit-гейт check_architecture_done); тяжёлая аналитика заменяется облегчённым пакетом (короткий bug-report + обязательный план регресс-теста, но всё равно все 4 файла analysis — гейт check_analysis_complete не меняется). Корневой инвариант (NFR-1): срезается ТОЛЬКО аналитика/архитектура — все Quality Gate'ы и под-гейты исполняются без изменений (STAGE_TRANSITIONS / QG_CHECKS / check_* / machine-verdict ключи — байт-в-байт прежние); маршрутизация багфикса — свойство планировщика, не гейт. Аддитивно, под kill-switch, never-raise, fail-safe → полный цикл.

  • Классификация (D1): leaf src/bug_fast_track.py (never-raise, образец labels/serial_gate). bug_fast_track_applies(repo) (локально, без сети) ПЕРВЫМ → выключенный флаг = нулевой сетевой оверхед; is_bug_task делегирует в labels.has_label (ORCH-089-аппарат, источник истины — Plane API, не payload). Чтение метки — только в start_pipeline, никогда в горячем claim_next_job (NFR-4).
  • Хранение типа (D2): аддитивная идемпотентная колонка tasks.track TEXT DEFAULT 'full' (_ensure_column, паттерн tasks.cancelled_at); значения 'full' (дефолт, ВСЕ существующие и не-баг задачи) | 'bug'. Хелперы db.set_task_track/get_task_track (отсутствие/NULL → 'full', fail-safe). Читается в advance_stage из БД, не из сети.
  • Routing-override (D3): врезка в advance_stage на ребре выхода из analysis: при track='bug' (чистый предикат bug_fast_track.skips_architecture) next_stagedevelopment, next_agentdeveloper (минуя architect). STAGE_TRANSITIONS/get_next_stage/get_agent_for_stage — чистые, 1:1. Стамп mark_brd_review_ended расширен на analysis → development (честная метрика ORCH-087).
  • Эскалация (D5): POST /bug-fast-track/escalate?work_item=<id> сбрасывает track 'bug'→'full' → следующий переход уходит в architecture (полный цикл). Плюс self-escalate мини-аналитика («баг сложный → полный пакет + escalate: full-cycle»).
  • Флаги (config.py): bug_fast_track_enabled (kill-switch, env ORCH_BUG_FAST_TRACK_ENABLED), bug_fast_track_label (дефолт Bug), bug_fast_track_repos (CSV; пусто → self-hosting only). False/неприменимый репо → старт и маршрут байт-в-байт прежние (нулевая регрессия для enduro и orchestrator). Наблюдаемость — read-only блок bug_fast_track в GET /queue (флаг/метка/область + счётчик багфикс-задач + метрика пропущенных стадий architecture) + отметка 🐞 в Telegram-карточке (never-raise). Композиция: багфикс-задача — обычная задача репо для serial-gate (ORCH-088, не обходит его); autoApprove/autoDeploy (ORCH-089), coverage-gate (ORCH-027, союзник BR-4), merge-gate (ORCH-043) — штатно. Инфра-предусловие: создать метку Bug в Plane-проекте ORCH (её отсутствие = fail-safe полный цикл). Детали — docs/work-items/ORCH-019/06-adr/ADR-001-bug-fast-track.md, docs/architecture/adr/adr-0032-bug-fast-track.md.

Гейт покрытия тестами (ORCH-027)

Существующие тестовые гейты (check_ci_green, check_tests_passed, merge-gate re-test) судят только по факту прохождения, не по полноте — ни один не замечает «300 строк кода, 0 тестов», и при пакетном автономном прогоне (ORCH-088) покрытие монотонно деградирует. Введён детерминированный (без LLM) под-гейт ребра deploy-staging → deploy по образцу security-гейта (ORCH-022): leaf src/coverage_gate.py (never-raise) + тонкая обёртка check_coverage_gate в QG_CHECKS + врезка _handle_coverage_gate в advance_stage. Инвариант: STAGE_TRANSITIONS / семантика существующих check_* / machine-verdict ключи (verdict:/result:/deploy_status:/ staging_status:/security_status:) — байт-в-байт прежние; новая БД-таблица аддитивна (NFR-5).

  • Точка/порядок: ПОСЛЕ merge-gate (покрытие меряется на догнанном auto_rebase_onto_main HEAD — ровно том коде, что landed в main) и ДО image-freshness (фейл до дорогого docker-rebuild). Порядок под-гейтов: security → merge → coverage → image-freshness. FAIL → штатный откат на development (+ инкремент developer-retry, cap MAX_DEVELOPER_RETRIES) и освобождение merge-lease (merge-gate держал его на своём PASS — зеркало image-freshness rollback).
  • Измерение: python -m pytest tests/ --cov=src --cov-report=json в изолированном per-branch worktree (ensure_worktree); метрика — totals.percent_covered (line coverage src/). Измеритель за measure_coverage(repo, branch) -> float | None (стек-расширяемость BR-6). Тайм-аут coverage_run_timeout_s. Новая pip-зависимость pytest-cov.
  • Решение — чистая функция compute_coverage_verdict(measured, baseline, floor, policy, epsilon) -> (ok, reason): absolutemeasured ≥ floorε; baselinemeasured ≥ baselineε; both (дефолт) → оба; baseline is None (bootstrap) → baseline-условие не применяется. epsilon — допуск на шум измерения (анти-флап у границы).
  • Базовая линия — аддитивная БД-таблица coverage_baseline(repo PK, coverage, source_sha, updated_at) (CREATE TABLE IF NOT EXISTS; хелперы db.get_coverage_baseline/ ratchet_coverage_baseline/set_coverage_baseline). Наращивание только вверх в choke-point подтверждённого merge _handle_merge_verify (ребро deploy → done): ratchet_baseline_on_merge читает измеренное из 18-coverage-report.md (single source of truth), атомарный compare-and-set UPDATE … WHERE coverage <= measured под держимым merge-lease (ORCH-043) → базовая линия не падает даже при гонке; bootstrap засевается первым применимым merge.
  • Условность (как ORCH-22/43/58): coverage_gate_enabled (kill-switch; False → 1:1 как до ORCH-027) + coverage_gate_repos (CSV; пусто → self-hosting only is_self_hosting_repo → enduro не затронут, no-op (True, "N/A")); applies(repo) (локально) ПЕРВЫМ — дорогой прогон только при applies==True. Ошибка инструмента/непарсимая метрика → fail-open + WARNING по умолчанию (coverage_tool_fail_closed=False, анти-петля); флаг → fail-closed.
  • Артефакт 18-coverage-report.md (frontmatter coverage_status: PASS|FAIL + measured_coverage/baseline/floor/policy/epsilon/delta), вердикт читается ТОЛЬКО из frontmatter через src/frontmatter.py (single source of truth, как security_status:). Наблюдаемость — read-only блок coverage в GET /queue; при FAIL — send_telegram с кликабельным номером, измеренным/порогом/дельтой; опциональный ручной override POST /coverage/baseline. Флаги ORCH_COVERAGE_* (MIN_PERCENT/POLICY/EPSILON/TOOL_FAIL_CLOSED/RUN_TIMEOUT_S). Self-hosting-безопасно: гейт только мерит/читает/пишет/решает — не деплоит/не рестартит прод/не пушит main. Инфра-предусловие: pytest-cov в прод/staging-образе. Детали — docs/work-items/ORCH-027/06-adr/ADR-001-coverage-gate.md, docs/architecture/adr/adr-0029-coverage-gate.md.

Машинный журнал уроков (ORCH-098)

Шаг 1 («Фундамент», F2) эпика саморазвития: формализует свободнотекстовые «уроки» из memory/ в машинную структурированную таблицу отклонений конвейера lessons, фундамент для будущих ретроспективщика (E2), приоритизатора RICE (E3) и Стрим. Чистый observer-leaf src/lessons.py (never-raise, kill-switch, паттерн serial_gate/coverage_gate/metrics): record()/get()/ update()/snapshot(). Инвариант: журнал — наблюдатель, не Quality Gate; запись урока никогда не влияет на продвижение по стадиям — STAGE_TRANSITIONS/QG_CHECKS/check_*/ machine-verdict/схемы существующих таблиц байт-в-байт не тронуты.

  • Таблица (D1): аддитивная идемпотентная lessons (CREATE TABLE IF NOT EXISTS в init_db(), три индекса) — контекст (work_item_id/task_id/stage/agent/repo), анализ (root_cause/ suggestion), статус (status/related_task), атрибуция сразу и нуллабельно (attribution/ target_repo/target_domain, требование Славы 10.06 / NFR-6, заполняется позже через update; _ensure_column форвард-safe на старой таблице) + source/detail. Без enum-констрейнтов — значения суть forward-compatible слаги. Хелперы db.record_lesson/get_lessons/update_lesson/ lessons_snapshot/lessons_recent_dup_exists.
  • НЕ скоупится по репо (D2): в отличие от гейт-leaf'ов (serial_gate/coverage_gate имеют *_repos, т.к. действуют на репо), журнал observer-only → единственный регулятор — глобальный kill-switch lessons_enabled (env ORCH_LESSONS_ENABLED, дефолт True); lessons_repos НЕ вводится. Recorder пишет уроки про любой репо (включая enduro-trails — урок ценен для петли); репо-разрез — на выборке (get(repo=…)). enduro не затронут (общая БД, аддитивная таблица).
  • Автозапись 4 типов (D3): тонкие best-effort врезки (source="auto", never-raise, дедуп) — gate_failure (stage_engine._handle_qg_failure_rollbacks, откат на development), merge_hold (stage_engine._handle_merge_verify HOLD-ветка), transient_retry (launcher._finalize_transient на исчерпании бюджета ретраев, а не на каждом backoff), deploy_degraded (post-deploy DEGRADED → set_repo_freeze, урок слоя-3 «деплой OK / прод сломан» ET-8 — attribution="unknown", классифицируется позже).
  • Дедуп (D4): для source="auto" — один indexed-SELECT по idx_lessons_wi_type: дубль с тем же (work_item_id, lesson_type, stage) в окне lessons_dedup_window_s (env, дефолт 3600с) → no-op. source="manual" дедуп НЕ проходит (оператор/Стрим всегда пишут).
  • Эндпоинты (D5): GET /lessons (read-only, фильтры type/status/repo/work_item/limit), POST /lessons (ручная запись, source="manual"), POST /lessons/{id} (доклассификация/update); read-only ключ lessons в GET /queue. Выключенный флаг → {"enabled": false}.
  • never-raise (NFR-1): все публичные функции и врезки изолированы (try/except → warning + безопасный дефолт) — сбой журнала не роняет конвейер. Self-hosting-безопасно: только читает/пишет свою таблицу, не деплоит/не рестартит прод/не трогает main/без процессов/сети. Детали — docs/work-items/ORCH-098/06-adr/ADR-001-lessons-journal.md, docs/architecture/adr/adr-0034-lessons-journal.md.

Turnkey-онбординг проектов (ORCH-009)

Операторская способность развернуть новый проект одним проходом — вне рантайма и вне конвейера (src/** байт-в-байт, kill-switch не нужен: активация — только явный запуск CLI человеком). Три артефакта: kit onboarding/repo-skeleton/ (параметризуемый каркас нового репо: 6 промптов канона 52d/92 — 5 ru + deployer en, паспорт CLAUDE.md, AGENTS.md, CONTRIBUTING.md, скелет docs/ с обязательным operations/INFRA.md; плейсхолдеры {{NAME}}, словарь — onboarding/placeholders.json; канон не форкается: docs/_templates/+docs/_standards/ копируются live из чекаута в момент материализации); CLI scripts/onboard_project.py (plan — дефолт, GET-only / apply — идемпотентный ensure без delete / verify): Plane-проект + 22 статуса с точными именами (read-only импорт plane_sync._PLANE_NAME_TO_KEY; группы фиксированы ADR: STOPcancelled, терминальные группы только Done/Cancelled/STOP) + лейблы autoApprove/autoDeploy/Bug → Gitea-репо + per-repo webhook (переиспользует глобальный ORCH_GITEA_WEBHOOK_SECRET) → материализация kit + initial push только в свежесозданный пустой репо → merged-вывод ORCH_PROJECTS_JSON (round-trip через фактический _parse_projects_json); скрипт никогда не рестартит прод / не правит .env / ничего не удаляет; недоступное в Plane CE API → manual-step (fail-safe); runbook docs/operations/ONBOARDING.md (ручные шаги: env + управляемый рестарт; smoke — на staging 8501). Анти-дрейф — структурные тесты tests/test_onboarding_{kit,script,invariants}.py. Детали — docs/work-items/ORCH-009/06-adr/ADR-001-turnkey-onboarding-kit-and-cli.md, сквозной docs/architecture/adr/adr-0035-turnkey-project-onboarding.md.

Тираж платформы: фундамент 10-common (ORCH-101)

Платформа разворачивается на новой инфре без правки кода — только env/конфиг (эпик ORCH-10, оба типа A Lite / B Bundled, stateless). Принцип: дефолт каждого параметра = боевому значению (пустой .env ⇒ поведение байт-в-байт; kill-switch-природа, отдельный флаг не вводится). STAGE_TRANSITIONS/QG_CHECKS/check_*/machine-verdict/схема БД — не тронуты.

  • Расхардкод: ключи agent_home_dir/agent_git_name/git_email_domain (HOME + git-идентичность акторов: агенты — единый launcher.agent_git_env(); системные имена deploy-finalizer/ post-deploy-monitor — платформенные литералы под тем же доменом), staging_port; ссылки Plane-комментариев — из gitea_public_url/gitea_owner. docker-compose.yml — интерполяция ${VAR:-default} (карта ORCH_HOST_*/ORCH_DOCKER_GID/ORCH_RUN_UID/GID; группа ORCH-040 uid/gid/HOME/маунты — одни env насквозь, «МИНА 1» сохранена); DockerfileARG APP_* (CMD exec-form 8500 не тронут); deploy-hook — "${REPO:-…}" + явная передача REPO= обоими инвокерами. Платформенные константы (НЕ конфиг): SELF_HOSTING_REPO="orchestrator" (узел «empty CSV → self-hosting only» всех *_repos-leaf'ов), имена сервисов/профиля, контейнерный layout. Инвариант ORCH-058 усилен: guard fail-closed staging_port == прод-порт → отказ freshness-пути ДО любого ssh/build, без тихого fallback.
  • Секреты нового хоста: stdlib scripts/gen_secrets.py (secrets.token_hex(32); печать по умолчанию; --write отказывает при существующем .env, перезапись только --force); норматив — боевые секреты не копируются. .env.example — канон 100% ключей старта.
  • Smoke тиража: runbook docs/operations/REPLICATION.md (карта env, чек-лист секретов, пошаговый smoke с PASS/FAIL до артефактов 0104/done, границы 10-common vs Lite vs Bundled). Анти-регрессtests/test_no_host_hardcodes.py (запрещённые литералы в исполняемом коде src/**+watchdog/**; tokenize-исключение комментариев/докстрингов; config-модули — канон дефолтов, вне скана; allowlist пуст). Детали — docs/work-items/ORCH-101/06-adr/ADR-001-host-parametrization-secrets-smoke.md, сквозной docs/architecture/adr/adr-0036-replication-foundation-host-parametrization.md.

Lite-тираж: орк+watchdog на инфре заказчика (ORCH-102)

Закрыт Type A эпика ORCH-10 (поверх фундамента 10-common ORCH-101): заказчик разворачивает у себя ТОЛЬКО orchestrator+orchestrator-watchdog и донастраивает окружение (Plane/Gitea/Telegram/LLM — его инсталляции) по одной сквозной инструкции. Docs+tests (паттерн ORCH-077/092): src/**/compose/Dockerfile/scripts/** не тронуты; конвейер байт-в-байт.

  • Golden sourcedocs/deployment/LITE_SETUP.md (новый раздел docs/deployment/ — витрина тиража, читатель — внешний оператор; vs docs/operations/ — эксплуатация НАШЕГО прода): 13 нормативных разделов в порядке маршрута оператора, каждый шаг = fenced-команда + явная «Проверка:»/PASS/FAIL, хост-специфика только плейсхолдерами; канон не форкается — статусы/env/ вебхуки/smoke ссылками на ONBOARDING §1 / REPLICATION §2§4 / SETUP_WEBHOOKS (явно в доке — только fail-closed имена Confirm Deploy/STOP и обязательные ключи нового хоста).
  • Канон watchdog-конфига — новый .env.watchdog.example (key-set = блок WATCHDOG_* .env.example, держится key-sync тестом): sidecar читает ТОЛЬКО .env.watchdog, ключ WATCHDOG_* в .env для него инертен (ловушка файла-носителя закрыта); C-1 ORCH-100 — свой бот, токен орка не переиспользовать; .env.watchdog в .gitignore.
  • Нормативы: Gitea — branch protection на main НЕ включать (ADR D10 ORCH-009 / INV-4), pre-receive не вводится, ОДИН глобальный webhook-секрет; compose НЕ форкается (дефолтный up -d = ровно орк+watchdog, staging строго за profiles: [staging] — вилка только под self-hosting развитие платформы); stateless — данные/задачи/секреты боевого хоста НЕ переносятся, проверка чистоты через GET /queue.
  • Анти-дрейфtests/test_lite_setup_doc.py (структурный, без сети/LLM/subprocess): 13 разделов в порядке, кирпичи, env-ключи ⊂ .env.example, compose-подмножество (анти-появление plane*/gitea*), fenced-скан FORBIDDEN (импорт из test_no_host_hardcodes.py) + секрет-эвристика, «22 статуса» сверкой импорта plane_sync._PLANE_NAME_TO_KEY, перекрёстность REPLICATION→LITE_SETUP. Норматив сопровождения (NFR-5): меняешь шаги тиража → обнови LITE_SETUP.md в том же PR. Детали — docs/work-items/ORCH-102/06-adr/ADR-001-lite-setup-doc-canon.md, сквозной docs/architecture/adr/adr-0037-lite-replication-canon.md.

Bundled-тираж: весь стек одним комплектом (ORCH-103)

Закрыт Type B эпика ORCH-10 (поверх 10-common ORCH-101 и канона Lite ORCH-102): заказчик без собственной инфраструктуры получает весь стек одним комплектом — новый top-level каталог deploy/bundled/ (самодостаточный compose: орк + watchdog + Gitea + зеркало upstream Plane CE ≈14 контейнеров; project name orchestrator-bundle = узнаваемый префикс томов/контейнеров; container_name не пиннится; staging-контура нет вовсе — самразвитие платформы у заказчика = маршрут Lite) + scripts/bootstrap_bundle.py (python stdlib-only, режимы plan (дефолт) / apply/verify, step-движок check→ensure, exit 0/2/1), доводящий стек одним прогоном: preflight (fail-fast до мутаций) → секреты (webhook — строго gen_secrets.py; bundle-креды — stdlib secrets, без перетирания без --force-secrets) → up+готовность → init Gitea (полностью автоматом, gitea admin …; branch protection НЕ включается — D10 ORCH-009/INV-4) → init Plane (честные manual-step c API-верификацией; молчаливый пропуск запрещён) → онбординг sandbox-проекта строго onboard_project.py apply+verify (22 статуса — plane_sync._PLANE_NAME_TO_KEY, нулевой дрейф канона) → git-доступ агентов HTTP token-remote (ssh-контур не вводится) → сборка корневых .env/.env.watchdog (bootstrap — единственный писатель live-конфигов) → health/итог. Сеть — одна bridge, машинный трафик строго сервис-DNS (http://orchestrator:8500/webhook/*), наружу — только человеческие порты (BUNDLE_ORCH_PORT/BUNDLE_PLANE_PORT/BUNDLE_GITEA_HTTP_PORT); мина Gitea закрыта GITEA__webhook__ALLOWED_HOST_LIST=orchestrator. Все сторонние образы пиннованы неподвижными тегами; teardown — только документированная процедура BUNDLED_SETUP §13 (delete-операций в скрипте НЕТ вообще). Рантайм байт-в-байт: src/**, корневой compose, Dockerfile, STAGE_TRANSITIONS/QG_CHECKS/схема БД — не тронуты; kill-switch не нужен (активация — только явный запуск оператором, паттерн ORCH-009/102). Golden source — docs/deployment/BUNDLED_SETUP.md (14 разделов канона LITE_SETUP; общие шаги — ссылками на LITE_SETUP/ONBOARDING/REPLICATION). Анти-дрейф — tests/test_bundle_compose.py (состав/пины/key-set-sync/заморозка корневого compose), tests/test_bundled_setup_doc.py (разделы/FORBIDDEN-импорт/секрет-эвристика/env-ключи/кросс-рефы), tests/test_bootstrap_script.py (кирпичи/stdlib-only ast-сканом/нет delete-операций/unit чистых функций). Норматив сопровождения (NFR-5): меняешь шаги Bundled-тиража → обнови BUNDLED_SETUP.md в том же PR. Детали — docs/work-items/ORCH-103/06-adr/ADR-001-bundled-stack-compose-and-bootstrap.md, сквозной docs/architecture/adr/adr-0038-bundled-replication-canon.md.

Конвенции

  • Conventional Commits (feat:, fix:, docs:, refactor:, test:)
  • Ветки: feature/ORCH-NNN-slug, fix/ORCH-NNN-slug
  • ADR per work-item: docs/work-items/<plane-id>/06-adr/ADR-NNN-slug.md
  • Global ADR (сквозные решения): docs/architecture/adr/adr-NNNN-slug.md
  • Work items: docs/work-items/<plane-id>/
  • Машинные вердикты Quality Gate — строго YAML-frontmatter (verdict:, deploy_status:, staging_status:, security_status:), никогда проза. ORCH-52c (ORCH-076): парсинг frontmatter сведён к единому контракту src/frontmatter.py (reader read_frontmatter_value — BC; единый парс-примитив parse_frontmatter; writer render/write_frontmatter; валидатор схемы validate_schema/REQUIRED_FIELDS — warning-only по умолчанию, hard-fail только под kill-switch frontmatter_validation_strict, дефолт False). Пять вердикт-парсеров (check_reviewer_verdict, _parse_tests_verdict, _parse_deploy_status, _parse_staging_status, parse_security_status) читают через ОДНУ точку парсинга; семантика вердиктов и STAGE_TRANSITIONS/состав QG_CHECKS — 1:1. Формальная спека «стадия → обязательный выход» + обязательная frontmatter-схема — docs/_standards/HANDOFF_PROTOCOL.md

Артефакты задачи (docs/work-items/<plane-id>/)

00-business-request.md, 01-brd.md, 02-trz.md, 03-acceptance-criteria.md, 04-test-plan.yaml, 06-adr/ADR-NNN-slug.md, 07-infra-requirements.md, 08-data-requirements.md, 10-tech-risks.md, 12-review.md, 13-test-report.md, 14-deploy-log.md, 15-staging-log.md, 16-post-deploy-log.md (post-deploy наблюдение, ORCH-021), 17-security-report.md (security-гейт: security_status:/secrets/deps, ORCH-022), 18-coverage-report.md (coverage-гейт: coverage_status:/measured/baseline, ORCH-027).

Стандарт документов (ORCH-075, ORCH-52b): структура каждого дока, карта «стадия→агент→документ→гейт→machine-key» и конвенция ADR-naming зафиксированы в docs/_standards/PIPELINE_DOCS.md (golden source); копируемые скелеты — в docs/_templates/. Перед написанием номерного дока бери скелет из docs/_templates/ и не меняй имя machine-key frontmatter (регистр чувствителен — иначе гейт упадёт ложно).

Правила для агентов

  1. Перед любым действием прочесть этот файл и docs/architecture/README.md.
  2. Документация = golden source наравне с кодом. Изменил функционал → обнови доку В ТОМ ЖЕ PR. Архитектурное решение → заведи ADR (формат — docs/_standards/PIPELINE_DOCS.md §4). Структура номерных доков и шаблоны — docs/_standards/PIPELINE_DOCS.md + docs/_templates/. Обнови CHANGELOG.md.
  3. Никогда не править артефакты других этапов.
  4. Никогда не комментировать ТЗ задним числом — если ТЗ не годится, возвращай в Анализ.
  5. Никогда не закрывать задачу самостоятельно — это делает CI / финальная стадия.
  6. Reviewer проверяет: обновлена ли документация. Нет → REQUEST_CHANGES. Это включает обзорные доки (ORCH-079, 52f — финал эпика 52): если PR закрывает пункт README.md «Известные ограничения», но README не обновлён → finding ≥P1 (витрина проекта не должна выдавать решённое за открытое).
  7. Не использовать --no-verify без явного одобрения Owner.
  8. Секреты — только в .env/.env.staging на хосте, в гит НЕ коммитятся (канон — .env.example).
  9. Трассировка маркеров (ORCH-078, ORCH-52e): правишь строку/блок с маркером ORCH-NNN → ПЕРЕД изменением прочитай его docs/work-items/ORCH-NNN/06-adr/ и не сломай зафиксированный инвариант; блок с 3+ маркерами → опирайся на сводный сквозной ADR. Стандарт маркеров (формат, размещение, fallback-доступ, анти-археология, каноничное правило чтения) — docs/_standards/TRACEABILITY.md.

⚠️ Self-hosting — оркестратор правит САМ СЕБЯ

Задачи проекта ORCH меняют инструмент, который СЕЙЧАС работает в продакшене и обслуживает ДРУГИЕ проекты (enduro-trails) из ОДНОГО инстанса с ОБЩЕЙ БД и общей очередью.

  • НЕ перезапускать / не ронять прод-контейнер orchestrator в рамках задачи — встанет конвейер всех проектов.
  • Любой деплой/рестарт self = групповой риск. Детали и топология — docs/operations/INFRA.md.
  • Стадия deploy-staging (порт 8501) — обязательная страховка перед прод-деплоем орка.
  • Прод-деплой орка запускается ТОЛЬКО переводом задачи на стадии deploy в выделенный Plane-статус «Confirm Deploy» (ORCH-059). Статус Approved — человеческий гейт конвейера и прод-деплой НЕ запускает (на deploy — no-op). Это разделяет «одобрить артефакт» и «выкатить в прод», чтобы привычный approve не ронял прод случайным кликом.

Паспорт проекта orchestrator. Поддерживается агентами при каждой доработке. Изолирован: описывает только этот проект (канон per-repo, см. ORCH-9).