Introduce a deterministic (no-LLM) coverage sub-gate that blocks coverage degradation before a task branch merges into `main`. Existing gates judge only by the FACT of passing (check_ci_green / check_tests_passed / merge-gate re-test), not by completeness — so a batch autonomous run (ORCH-088) silently erodes coverage. Pattern mirrors the security-gate (ORCH-022): leaf src/coverage_gate.py (never-raise) + thin check_coverage_gate in QG_CHECKS + _handle_coverage_gate splice in advance_stage, run AFTER merge-gate (measured on the caught-up HEAD that lands in main) and BEFORE image-freshness (fail before the expensive docker rebuild). - measure_coverage: pytest --cov=src --cov-report=json in the per-branch worktree -> line coverage %; None on tool error -> fail-open + WARNING by default (FR-6). - compute_coverage_verdict (pure): absolute | baseline | both + epsilon (NFR-4 anti-flap); baseline None -> bootstrap (absolute-only). - coverage_baseline DB table (additive, CREATE TABLE IF NOT EXISTS) + ratchet-up in _handle_merge_verify (deploy->done): atomic compare-and-set under merge-lease, never decreases; bootstrap on first merge. - Artefact 18-coverage-report.md (coverage_status: frontmatter, single source of truth); GET /queue `coverage` block; FAIL -> Telegram; optional POST /coverage/baseline override. - Flags ORCH_COVERAGE_* (kill-switch + self-hosting-only scope) -> enduro untouched; STAGE_TRANSITIONS / existing check_* / verdict keys byte-for-byte unchanged (NFR-5/AC-8). - pytest-cov==5.0.0 added to requirements.txt. Tests: tests/test_coverage_gate.py (TC-01..TC-15). Frozen QG-registry anti-regress tests + deploy-staging edge tests updated for the new sub-gate. Full suite green. Docs: README / adr-0029 / PIPELINE_DOCS / 18-coverage-report.md template (architecture stage) + CHANGELOG / CLAUDE.md / .env.example (this PR). Refs: ORCH-027 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
38 KiB
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) — frontmattermodel:удалён как мёртвый, 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-путём + smokeserial_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; декларации/детект циклов — leafsrc/task_deps.py(kill-switchORCH_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-deployDEGRADED→ durable per-repo freeze (repo_freeze,cleared_at IS NULL= активен) + Telegram; снятие — вручнуюPOST /serial-gate/unfreeze?repo=…. Leafsrc/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 …/merge→405 "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-switchmerge_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)
Среды
- prod —
orchestrator(8500), внешний URLhttps://openclaw.mva154.duckdns.org/orchestrator/ - staging —
orchestrator-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/Giteatests/— pytestdocs/— документация, ADR, work-items, operationsscripts/— утилиты (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), для cancelled — SUPPRESS. Чтобы легитимный первый 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_mode—bump(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-switchtracker_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-status→analysis → 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. Логический ключ stop — fail-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), durablestage='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, снимаются толькоqueuedjob'ы (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_TRANSITIONSexit-гейты рёбер /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; пусто → все репо). Leafsrc/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-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_mainHEAD — ровно том коде, что landed вmain) и ДО image-freshness (фейл до дорогого docker-rebuild). Порядок под-гейтов: security → merge → coverage → image-freshness. FAIL → штатный откат наdevelopment(+ инкремент developer-retry, capMAX_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 coveragesrc/). Измеритель за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):absolute→measured ≥ floor−ε;baseline→measured ≥ 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-setUPDATE … 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 onlyis_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(frontmattercoverage_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с кликабельным номером, измеренным/порогом/дельтой; опциональный ручной overridePOST /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.
Конвенции
- 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(readerread_frontmatter_value— BC; единый парс-примитивparse_frontmatter; writerrender/write_frontmatter; валидатор схемыvalidate_schema/REQUIRED_FIELDS— warning-only по умолчанию, hard-fail только под kill-switchfrontmatter_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 (регистр чувствителен — иначе гейт упадёт ложно).
Правила для агентов
- Перед любым действием прочесть этот файл и
docs/architecture/README.md. - Документация = golden source наравне с кодом. Изменил функционал → обнови доку В ТОМ ЖЕ PR. Архитектурное решение → заведи ADR (формат —
docs/_standards/PIPELINE_DOCS.md§4). Структура номерных доков и шаблоны —docs/_standards/PIPELINE_DOCS.md+docs/_templates/. ОбновиCHANGELOG.md. - Никогда не править артефакты других этапов.
- Никогда не комментировать ТЗ задним числом — если ТЗ не годится, возвращай в Анализ.
- Никогда не закрывать задачу самостоятельно — это делает CI / финальная стадия.
- Reviewer проверяет: обновлена ли документация. Нет → REQUEST_CHANGES. Это включает обзорные доки (ORCH-079, 52f — финал эпика 52): если PR закрывает пункт
README.md«Известные ограничения», но README не обновлён → finding ≥P1 (витрина проекта не должна выдавать решённое за открытое). - Не использовать
--no-verifyбез явного одобрения Owner. - Секреты — только в
.env/.env.stagingна хосте, в гит НЕ коммитятся (канон —.env.example). - Трассировка маркеров (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).