Files
orchestrator/CLAUDE.md
claude-bot dc1cb87818 feat(onboarding): turnkey project onboarding — kit + CLI + runbook (ORCH-009)
Operator capability to bring a NEW project online in one pass, fully
outside the runtime and the pipeline (src/** byte-exact, no kill-switch
needed — activation is an explicit human CLI run). Reference = the
orchestrator repo itself (ORCH-52b/c/d/e canons).

* onboarding/repo-skeleton/ — parametrized kit of a new repo: 6 agent
  prompt templates per canon 52d/92 (5 ru + deployer en with the
  shared-host guardrail frame), reviewer doc-gate (REQUEST_CHANGES),
  CLAUDE.md passport, AGENTS.md, CONTRIBUTING.md, docs/ skeleton with
  mandatory operations/INFRA.md, .env.example; {{NAME}} placeholders +
  stdlib render, dictionary onboarding/placeholders.json (bijection
  held by tests). Canon is NOT forked: docs/_templates + docs/_standards
  are live-copied from the checkout at materialization time (BR-2/D3).
* scripts/onboard_project.py — plan (default, GET-only, zero mutations)
  / apply (idempotent ensure, no delete ops at all) / verify (registry
  round-trip via the actual projects._parse_projects_json, all 22 state
  names incl. fail-closed Confirm Deploy/STOP, labels, webhook, kit
  completeness, unresolved-placeholder scan). Closed read-only src
  import list (ADR D4); state groups fixed per ADR D5 (STOP→cancelled,
  terminal groups only Done/Cancelled/STOP); Gitea webhook reuses the
  single global ORCH_GITEA_WEBHOOK_SECRET (TR-6); initial push ONLY
  into a freshly created empty repo (INV-4 untouched); never restarts
  prod / never edits .env / deletes nothing (NFR-2); secrets masked
  (NFR-3); Plane CE API gaps degrade to manual-step (fail-safe).
* docs/operations/ONBOARDING.md runbook + SETUP_WEBHOOKS.md generalized
  per-repo; CLAUDE.md / docs/architecture/README.md / CHANGELOG.md
  updated in the same PR (golden source).
* Anti-drift tests: test_onboarding_kit.py / test_onboarding_script.py
  (mocked, no network) / test_onboarding_invariants.py (snapshots of
  STAGE_TRANSITIONS/QG_CHECKS, closed CLI import list, reference
  .openclaw/agents/ prompts untouched). Full regression: 1713 passed.

Refs: ORCH-009

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 16:08:43 +03:00

49 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.

Конвенции

  • 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).