Закрывает Type A эпика ORCH-10 (поверх 10-common ORCH-101). Docs+tests (паттерн ORCH-077/092): src/**, docker-compose.yml, Dockerfile, scripts/** — ноль изменений; конвейер (STAGE_TRANSITIONS/QG_CHECKS/check_*/machine-verdict/ схема БД) — байт-в-байт. - docs/deployment/LITE_SETUP.md (D1/D2): golden source Lite-тиража — 13 нормативных разделов в порядке маршрута оператора, каждый шаг = fenced-команда + явная «Проверка:»/PASS/FAIL, хост-специфика только плейсхолдерами; канон не форкается (статусы/env/вебхуки/smoke — ссылками на ONBOARDING §1 / REPLICATION §2–§4 / SETUP_WEBHOOKS; явно — только fail-closed Confirm Deploy/STOP и обязательные ключи нового хоста). - .env.watchdog.example (D5, исход А-4): третий канонический env-example; key-set = блок WATCHDOG_* .env.example (19 ключей, токены — пустые плейсхолдеры); закрывает ловушку файла-носителя (sidecar читает ТОЛЬКО .env.watchdog); C-1 ORCH-100 + когерентность порта в шапке; .env.watchdog добавлен в .gitignore (секрет-гигиена, зеркало .env.staging). - tests/test_lite_setup_doc.py (D8): 25 структурных тестов без сети/LLM/subprocess — 13 разделов в порядке D2, кирпичи FR-6.1, key-sync watchdog-канона, env-ключи ⊂ .env.example, compose-подмножество (ровно орк+watchdog по дефолту, staging за профилем, анти-появление plane*/gitea*), fenced-скан FORBIDDEN (импорт из test_no_host_hardcodes) + секрет-эвристика с негативным самочеком, «22 статуса» сверкой импорта plane_sync._PLANE_NAME_TO_KEY, перекрёстность. - Перекрёстные доки (FR-7): REPLICATION.md §1 (Type A — Lite → ✅ ORCH-102 + ссылка), README.md (способность Lite + docs/deployment/ в структуре), INFRA.md (.env.watchdog в секрет-нормативе + ссылка на deployment), CLAUDE.md (блок ORCH-102), CHANGELOG.md. Нормативы разделов: Gitea — branch protection на main НЕ включать (D3 / ADR D10 ORCH-009 / INV-4), pre-receive не вводится, ОДИН глобальный webhook-секрет; staging-вилка опциональна (D6); источник кода — параметризованный git clone <ORCHESTRATOR_GIT_URL> (D7); stateless — данные/задачи/секреты боевого хоста НЕ переносятся (AC-3). Тесты: pytest tests/ -q — 1789 passed (полный регресс зелёный). Refs: ORCH-102 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
56 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-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_stage→development,next_agent→developer(минуя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, envORCH_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_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.
Машинный журнал уроков (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-switchlessons_enabled(envORCH_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_verifyHOLD-ветка),transient_retry(launcher._finalize_transientна исчерпании бюджета ретраев, а не на каждом backoff),deploy_degraded(post-deployDEGRADED → 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: STOP→cancelled, терминальные группы только 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» сохранена);Dockerfile—ARG 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-closedstaging_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 до артефактов01–04/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 source —
docs/deployment/LITE_SETUP.md(новый разделdocs/deployment/— витрина тиража, читатель — внешний оператор; vsdocs/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.
Конвенции
- 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).