Compare commits

..

651 Commits

Author SHA1 Message Date
1e50d94d56 deployer(ET): auto-commit from deployer run_id=651
Some checks failed
CI / test (push) Has been cancelled
CI / test (pull_request) Successful in 1m3s
2026-06-12 08:19:26 +03:00
cfbe4c2eae tester(ET): auto-commit from tester run_id=650
All checks were successful
CI / test (push) Successful in 55s
CI / test (pull_request) Successful in 56s
2026-06-12 08:15:57 +03:00
f583bcf2eb reviewer(ET): auto-commit from reviewer run_id=649
All checks were successful
CI / test (push) Successful in 1m0s
CI / test (pull_request) Successful in 1m3s
2026-06-12 08:12:38 +03:00
adf7cc08b2 docs(overview): ORCH-105 — слайды Lite-установки и использования через Plane
All checks were successful
CI / test (push) Successful in 57s
CI / test (pull_request) Successful in 58s
Расширяю слайдо-источник презентации docs/overview/presentation.md тремя
слайдами в каноне ORCH-011 (16 → 19, сквозная нумерация сохранена):

- Слайд «Запуск и ведение задачи через Plane» (вход «To Analyse»,
  статусы = индикация, наблюдение: доска + Telegram-карточка + комментарии).
- Слайд «Что решает человек: гейты, авто-режим, отмена» (Approved /
  Confirm Deploy; autoApprove/autoDeploy/Bug — без пропуска тех. проверок; STOP).
- Слайд «Lite-установка скриптами» (два контейнера платформы; только конфиг;
  gen_secrets.py/onboard_project.py + docker compose up -d; runbook LITE_SETUP.md;
  одношаговый bootstrap — это смежный Bundled, не Lite).

Факты сверены с golden sources (LITE_SETUP.md, tech-pipeline.md,
tech-integrations.md, CLAUDE.md). Анти-дрейф — новая функция
test_presentation_covers_lite_and_plane_usage_bits в tests/test_system_docs.py
(существующие проверки без послаблений). CHANGELOG обновлён.

Docs+tests only: src/**/STAGE_TRANSITIONS/QG_CHECKS/check_*/схема БД —
байт-в-байт; python-pptx не в прод-образе; .pptx в git не коммитится.
Ручная сборка .pptx (TC-07) проверена в dev-venv: «Собрано слайдов: 19», exit 0.

Refs: ORCH-105

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 08:07:38 +03:00
2795fc2cf7 architect(ET): auto-commit from architect run_id=647
All checks were successful
CI / test (push) Successful in 1m1s
2026-06-12 07:59:45 +03:00
87e75ab2c3 architect(ET): auto-commit from architect run_id=646
All checks were successful
CI / test (push) Successful in 59s
2026-06-12 07:55:33 +03:00
5c72503f4c analyst(ET): auto-commit from analyst run_id=645
All checks were successful
CI / test (push) Successful in 1m0s
2026-06-12 07:49:14 +03:00
225ab4aa65 docs: init ORCH-105 business request
All checks were successful
CI / test (push) Successful in 58s
2026-06-12 07:39:35 +03:00
4d5e4613e5 Merge pull request 'docs(overview): ORCH-011 — витрина системы docs/overview/ (бизнес+тех, 3 аудитории, презентация)' (#125) from feature/ORCH-011- into main
Some checks failed
CI / test (push) Has been cancelled
2026-06-11 09:42:53 +03:00
deploy-finalizer
a6bf5d1b25 deploy(ORCH-036): finalize SUCCESS for ORCH-011
All checks were successful
CI / test (push) Successful in 55s
2026-06-11 09:42:52 +03:00
7191b8dca2 tester(ET): auto-commit from tester run_id=636
All checks were successful
CI / test (push) Successful in 1m3s
CI / test (pull_request) Successful in 1m1s
2026-06-11 09:36:40 +03:00
eb92cc6c2c reviewer(ET): auto-commit from reviewer run_id=635 2026-06-11 09:36:40 +03:00
6d798c01ef docs(overview): витрина системы docs/overview/ — бизнес+тех, 3 аудитории, презентация (ORCH-011)
Единая точка входа в документацию платформы (ADR-001 D1–D9):
- docs/overview/ — 10 файлов: индекс (маршруты «Я заказчик / Я менеджер /
  Я разработчик» + норматив «изменил функциональность → обнови витрину в том же
  PR»), business.md (без жаргона, 6 сценариев), 7 тех-блоков (link-first),
  presentation.md (16 слайдов + процедура сборки «команда + Проверка:»).
- scripts/build_presentation.py — генератор .pptx в тёмном дизайне (python-pptx;
  чистый stdlib-парсер parse_slides + ленивый import pptx; бинарь не коммитится,
  build/ в .gitignore; зависимость НЕ в прод-образе — машинный гард TC-09).
- tests/test_system_docs.py — структурный анти-дрейф: derive-сверки стадий/
  гейтов/агентов импортом STAGE_TRANSITIONS/QG_CHECKS/glob промптов/config,
  валидность ссылок, FORBIDDEN-скан + секрет-эвристика, слайды каноническим
  парсером, NFR-2, указатели.
- reviewer.md — ось обзорных доков ORCH-079 расширена на витрину (D7; канон 52d
  байт-в-байт, только текст внутри секций) + анти-регресс ассерт в
  test_agent_prompts_canon.py.
- Указатели: README.md, CLAUDE.md (правила №2/№6, «Структура»),
  PRODUCT_VISION.md (врезка-ссылка), CHANGELOG.md.

Рантайм байт-в-байт: src/**, docker-compose.yml, Dockerfile, requirements* —
ноль изменений (docs+tests+dev-скрипт, паттерн ORCH-102/103). pytest: 1873 passed.

Refs: ORCH-011

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 09:36:40 +03:00
c455931ae7 architect(ET): auto-commit from architect run_id=633 2026-06-11 09:36:40 +03:00
47479a9b75 analyst(ET): auto-commit from analyst run_id=632 2026-06-11 09:36:40 +03:00
6d1230bcc5 docs: init ORCH-011 business request 2026-06-11 09:36:40 +03:00
9b7bdc0c6c docs(ORCH-011): staging gate log — SUCCESS (8/10, C9a/C9b infra-waived)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 09:36:21 +03:00
2c72a889b6 Merge pull request 'feat(replication): ORCH-10b Bundled-тираж — весь стек одним комплектом + bootstrap (ORCH-103)' (#124) from feature/ORCH-103-orch-10b-bundled-bootstrap into main
Some checks failed
CI / test (push) Has been cancelled
2026-06-11 02:22:42 +03:00
deploy-finalizer
cf94fb813f deploy(ORCH-036): finalize SUCCESS for ORCH-103
All checks were successful
CI / test (push) Successful in 54s
2026-06-11 02:22:41 +03:00
6e17f33be4 tester(ET): auto-commit from tester run_id=630
All checks were successful
CI / test (push) Successful in 57s
CI / test (pull_request) Successful in 1m1s
2026-06-11 02:16:32 +03:00
8512dad29e reviewer(ET): auto-commit from reviewer run_id=629 2026-06-11 02:16:32 +03:00
f0cd19d748 feat(replication): ORCH-10b Bundled-тираж — bundle-compose всего стека + bootstrap-скрипт
Закрывает Type B эпика ORCH-10 (по ADR-001 ORCH-103, D1–D11):

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

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

Refs: ORCH-103

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 02:16:32 +03:00
215930fb90 developer(ET): auto-commit from developer run_id=627 2026-06-11 02:16:32 +03:00
054b78c8ca architect(ET): auto-commit from architect run_id=626 2026-06-11 02:16:32 +03:00
4050ccbfde analyst(ET): auto-commit from analyst run_id=625 2026-06-11 02:16:32 +03:00
d282d25659 docs: init ORCH-103 business request 2026-06-11 02:16:32 +03:00
c74a68a251 docs(ORCH-103): staging gate log — SUCCESS (8/10, C9a/C9b infra-waived)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 02:16:04 +03:00
0d15719676 Merge pull request 'docs(deployment): ORCH-102 — ORCH-10a Lite-тираж (LITE_SETUP + watchdog-канон + анти-дрейф)' (#123) from feature/ORCH-102-orch-10a-lite-watchdog into main
Some checks failed
CI / test (push) Has been cancelled
2026-06-11 00:48:29 +03:00
deploy-finalizer
f09aff6b43 deploy(ORCH-036): finalize SUCCESS for ORCH-102
All checks were successful
CI / test (push) Successful in 54s
2026-06-11 00:48:28 +03:00
a5f904b56a tester(ET): auto-commit from tester run_id=623
All checks were successful
CI / test (push) Successful in 59s
CI / test (pull_request) Successful in 1m1s
2026-06-11 00:42:15 +03:00
56cbf9bd0e reviewer(ET): auto-commit from reviewer run_id=611 2026-06-11 00:42:15 +03:00
8351e91382 docs(deployment): ORCH-10a Lite-тираж — LITE_SETUP.md + канон watchdog-конфига + анти-дрейф контур
Закрывает 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>
2026-06-11 00:42:15 +03:00
443ddc6b6f architect(ET): auto-commit from architect run_id=609 2026-06-11 00:42:15 +03:00
30f1f33af1 analyst(ET): auto-commit from analyst run_id=608 2026-06-11 00:42:15 +03:00
3a103a6e92 docs: init ORCH-102 business request 2026-06-11 00:42:15 +03:00
7d6251d4b6 docs(ORCH-102): staging gate log — SUCCESS (8/10, C9a/C9b infra-waived) 2026-06-11 00:41:48 +03:00
ede5ec9473 Merge pull request 'feat(replication): ORCH-101 — расхардкод хоста + секреты нового хоста + smoke (фундамент тиража 10-common)' (#122) from feature/ORCH-101-orch-10-common-smoke into main
Some checks failed
CI / test (push) Has been cancelled
2026-06-10 21:09:38 +03:00
deploy-finalizer
c1e3c46f98 deploy(ORCH-036): finalize SUCCESS for ORCH-101
All checks were successful
CI / test (push) Successful in 56s
CI / test (pull_request) Successful in 58s
2026-06-10 21:09:37 +03:00
cb1f27e9c0 docs(ORCH-101): staging gate log — staging_status SUCCESS (8/10, C9a/C9b infra-waived)
All checks were successful
CI / test (push) Successful in 1m2s
CI / test (pull_request) Successful in 1m3s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 21:02:45 +03:00
c55f956d78 tester(ET): auto-commit from tester run_id=606
All checks were successful
CI / test (push) Successful in 56s
CI / test (pull_request) Successful in 55s
2026-06-10 21:00:13 +03:00
26fe4cdd5e reviewer(ET): auto-commit from reviewer run_id=605
All checks were successful
CI / test (push) Successful in 1m1s
CI / test (pull_request) Successful in 59s
2026-06-10 20:57:20 +03:00
f1635ddb39 feat(replication): расхардкод хоста + секреты нового хоста + smoke-runbook
All checks were successful
CI / test (push) Successful in 57s
CI / test (pull_request) Successful in 55s
Фундамент тиража 10-common (эпик ORCH-10): платформа разворачивается на
новой инфре без правки кода — только env/конфиг. Каждый дефолт = боевому
значению (пустой .env => поведение 1:1, kill-switch-природа, NFR-2);
STAGE_TRANSITIONS/QG_CHECKS/check_*/machine-verdict/схема БД не тронуты.

- config: agent_home_dir / agent_git_name / git_email_domain / staging_port
  (ADR-001 D2/D4); код-блокеры A1-A4 закрыты: plane_sync ссылки из
  gitea_public_url+gitea_owner, launcher - единый agent_git_env() (x2 места),
  self_deploy/post_deploy - HOME+домен из Settings (имена системных акторов -
  платформенные литералы)
- image_freshness: staging_port из конфига + fail-closed guard
  staging_port == прод-порт -> отказ ДО ssh/build (инвариант ORCH-058 AC-9
  стал исполняемым); REPO= передаётся хуку явно обоими инвокерами (D7)
- SELF_HOSTING_REPO - нормативная платформенная константа (D3, пин-тест)
- compose: полная ${VAR:-default}-интерполяция (реестр B, карта D6); группа
  ORCH-040 uid/gid/HOME/маунты двигается согласованно (build.args APP_*);
  group_add "МИНА 1" сохранён x3; оба app-сервиса с явным command:
- Dockerfile: ARG APP_UID/APP_GID/APP_USER/APP_HOME (CMD exec-form 8500
  сознательно не тронут - D5); deploy-hook: REPO="${REPO:-...}" (D1 реестра)
- секреты: stdlib scripts/gen_secrets.py (token_hex(32); печать по умолчанию;
  --write никогда не перезаписывает существующий .env молча, exit=2;
  перезапись только --force); .env.example дополнен до полноты ключей старта
- доки: новый docs/operations/REPLICATION.md (карта env, чек-лист секретов,
  smoke-процедура с PASS/FAIL, границы 10-common/Lite/Bundled), INFRA.md,
  README, CLAUDE.md, CHANGELOG
- анти-регресс: tests/test_no_host_hardcodes.py (tokenize-сканер запрещённых
  литералов, config-модули - структурное исключение, allowlist пуст,
  негативная самопроверка) + test_host_config_keys / test_infra_parametrization
  / test_secrets_gen / test_replication_smoke; согласованные структурные
  правки test_orch040_compose (судит резолв дефолтов) и
  test_deploy_hook_rollback_sim (REPO через env-override = контракт D7)

Полный регресс: 1764 passed.

Refs: ORCH-101

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 20:50:43 +03:00
26bdd783d6 architect(ET): auto-commit from architect run_id=603
All checks were successful
CI / test (push) Successful in 59s
2026-06-10 20:23:50 +03:00
69aa6eacde analyst(ET): auto-commit from analyst run_id=602
All checks were successful
CI / test (push) Successful in 1m9s
2026-06-10 20:02:14 +03:00
9d0f2e40b7 docs: init ORCH-101 business request
All checks were successful
CI / test (push) Successful in 59s
2026-06-10 19:50:40 +03:00
4c232112d4 Merge pull request 'feat(onboarding): turnkey project onboarding — kit + CLI + runbook (ORCH-009)' (#120) from feature/ORCH-009-turnkey-plane into main
Some checks failed
CI / test (push) Has been cancelled
2026-06-10 19:50:38 +03:00
deploy-finalizer
2fb6dc32f6 deploy(ORCH-036): finalize SUCCESS for ORCH-009
All checks were successful
CI / test (push) Successful in 58s
2026-06-10 19:50:37 +03:00
e5c3774bc5 tester(ET): auto-commit from tester run_id=600
All checks were successful
CI / test (push) Successful in 58s
CI / test (pull_request) Successful in 57s
2026-06-10 19:40:51 +03:00
b97ffae7a1 reviewer(ET): auto-commit from reviewer run_id=593
All checks were successful
CI / test (push) Successful in 56s
CI / test (pull_request) Successful in 1m3s
2026-06-10 17:26:44 +03:00
b26a391fa3 developer(ET): auto-commit from developer run_id=592
All checks were successful
CI / test (push) Successful in 55s
CI / test (pull_request) Successful in 55s
2026-06-10 16:18:27 +03:00
e9038182a1 fix(tests): hermetic ORCH-41 model/effort tests vs host env (unblock merge-gate)
Some checks failed
CI / test (push) Has been cancelled
CI / test (pull_request) Successful in 55s
Merge-gate re-test runs under the orchestrator's prod env, where the
operator legitimately set ORCH_AGENT_FALLBACK_MODEL and changed
ORCH_AGENT_MODEL_DEFAULT / ORCH_AGENT_EFFORT_*. Two ORCH-41-era tests
asserted SHIPPED defaults through the env-backed settings singleton and
failed 3/3 there, while Gitea CI (clean env) stayed green. Branch
ORCH-009 touches neither src/ nor these tests - latent non-hermetic
landmine on main, detonated by the prod env change.

- test_resolve_agent_effort.py: autouse fixture now mirrors the sibling
  model-file baseline (pins shipped model/fallback fields) so the
  flag-assembly tests are env-independent.
- test_resolve_agent_model.py: fixture also resets agent_fallback_model;
  test_fallback_model_disabled_by_default now asserts the CLASS field
  default (the actual ORCH-074 ADR-001 G4 invariant: shipped default
  is ""), never-break is_valid_model asserts unchanged byte-for-byte.

Clean-env behaviour is byte-equivalent (fixtures pin exactly what an
empty env yields). Full suite: 1713 passed (was 2 failed / 1711).

Refs: ORCH-009

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 16:17:54 +03:00
dd09e3da89 tester(ET): auto-commit from tester run_id=590
All checks were successful
CI / test (push) Successful in 54s
CI / test (pull_request) Successful in 53s
2026-06-10 16:08:43 +03:00
cc3ed42041 reviewer(ET): auto-commit from reviewer run_id=589 2026-06-10 16:08:43 +03:00
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
13e9618bd2 developer(ET): auto-commit from developer run_id=587 2026-06-10 16:08:43 +03:00
d141280390 architect(ET): auto-commit from architect run_id=586 2026-06-10 16:08:43 +03:00
ed04f71fd1 architect(ET): auto-commit from architect run_id=585 2026-06-10 16:08:43 +03:00
11551572e9 analyst(ET): auto-commit from analyst run_id=584 2026-06-10 16:08:43 +03:00
1289d728a8 docs: init ORCH-009 business request 2026-06-10 16:08:43 +03:00
2c801d8759 docs(ORCH-009): staging gate log — SUCCESS (8/10, C9a/C9b infra-waived) 2026-06-10 16:08:02 +03:00
af949afc58 Merge pull request 'feat(lessons): машинный журнал уроков — аддитивная таблица + observer-leaf (ORCH-098)' (#118) from feature/ORCH-098-fnd into main
Some checks failed
CI / test (push) Has been cancelled
2026-06-10 11:02:23 +03:00
deploy-finalizer
4203d93978 deploy(ORCH-036): finalize SUCCESS for ORCH-098
All checks were successful
CI / test (push) Successful in 55s
2026-06-10 11:02:22 +03:00
66700123ac docs(ORCH-098): staging gate SUCCESS — 15-staging-log.md
All checks were successful
CI / test (push) Successful in 57s
CI / test (pull_request) Successful in 1m2s
Staging suite (docker exec orchestrator-staging, port 8501) exit 0.
All REAL checks green; C9a/C9b INFRA-WAIVED (ORCH-061).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 10:55:51 +03:00
917acf3e1e tester(ET): auto-commit from tester run_id=582
All checks were successful
CI / test (push) Successful in 57s
CI / test (pull_request) Successful in 56s
2026-06-10 10:52:53 +03:00
de009822c0 reviewer(ET): auto-commit from reviewer run_id=581
All checks were successful
CI / test (push) Successful in 59s
CI / test (pull_request) Successful in 59s
2026-06-10 10:49:49 +03:00
21a47e85d3 fix(lessons): resolve land-race with ORCH-100 — renumber ADR 0033→0034
All checks were successful
CI / test (push) Successful in 56s
CI / test (pull_request) Successful in 55s
Merge-gate auto_rebase_onto_main bounced this branch back: ORCH-100 landed
in main first and claimed global ADR number adr-0033 (adr-0033-sidecar-watchdog),
while this branch had created adr-0033-lessons-journal. Resolved the genuine
land race:

- rebased feature/ORCH-098-fnd onto current origin/main (linear history)
- resolved docs/architecture/README.md component-list conflict — both the
  Lessons-journal and Sidecar-watchdog bullets now coexist
- renamed docs/architecture/adr/adr-0033-lessons-journal.md →
  adr-0034-lessons-journal.md (next free global ADR number) + fixed the
  in-file header
- updated all cross-references (CLAUDE.md, README.md, work-item ADR-001,
  12-review.md) 0033→0034 for the lessons journal; ORCH-100's adr-0033
  (sidecar) left intact
- recovered the ORCH-098 CHANGELOG entry silently dropped by the rebase
  auto-merge (now above ORCH-100, ADR ref corrected to 0034)

No code semantics changed; src/** auto-merged cleanly (ORCH-100 did not
touch src/**). ruff: n/a locally (CI). pytest tests/ -q: 1630 passed.

Refs: ORCH-098
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 10:44:34 +03:00
c01c42e532 tester(ET): auto-commit from tester run_id=578 2026-06-10 10:40:17 +03:00
eea70551e6 reviewer(ET): auto-commit from reviewer run_id=577 2026-06-10 10:40:17 +03:00
7d21625d84 feat(lessons): machine lessons-journal — additive table + observer leaf (ORCH-098)
Step 1 ("Foundation", F2) of the self-improvement epic: formalise free-text
"lessons" from memory/ into a machine-readable `lessons` table — the foundation
for the future retrospective agent (E2), the RICE prioritiser (E3) and Стрим.

- src/lessons.py: pure never-raise observer leaf (record/get/update/snapshot),
  kill-switch only, NO repo scope (observer-only; records about any repo incl.
  enduro; repo cut on the read side). Slug-convention constants.
- src/db.py: additive idempotent `lessons` table in init_db() (+3 indexes);
  nullable attribution columns from the start (NFR-6, _ensure_column forward-safe);
  helpers record_lesson/get_lessons/update_lesson/lessons_snapshot/
  lessons_recent_dup_exists (auto-dedup window).
- 4 auto-detectors (best-effort, source="auto", deduped): gate_failure
  (_handle_qg_failure_rollbacks), merge_hold (_handle_merge_verify HOLD),
  transient_retry (launcher._finalize_transient budget-exhaustion), deploy_degraded
  (post-deploy DEGRADED -> set_repo_freeze).
- src/main.py: GET /lessons, POST /lessons, POST /lessons/{id} + read-only
  `lessons` block in GET /queue; off-switch -> {"enabled": false}.
- src/config.py: lessons_enabled / lessons_query_limit_default / lessons_dedup_window_s.
- tests/test_lessons.py: TC-01..TC-12 (unit + integration), all green.
- Docs: CLAUDE.md, docs/architecture/README.md (component + schema + API), CHANGELOG.

Invariant: the journal is an OBSERVER, not a Quality Gate — STAGE_TRANSITIONS /
QG_CHECKS / check_* / machine-verdict / existing table schemas are byte-for-byte
untouched; enduro not affected. never-raise on every public fn + injection.

Refs: ORCH-098
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 10:40:17 +03:00
9f62df02eb architect(ET): auto-commit from architect run_id=574 2026-06-10 10:39:17 +03:00
1dc067a00c analyst(ET): auto-commit from analyst run_id=573 2026-06-10 10:37:51 +03:00
0677ea3a7e docs: init ORCH-098 business request 2026-06-10 10:37:51 +03:00
b915503b37 Merge pull request 'docs(ORCH-098): staging gate log — SUCCESS (8/10, C9a/C9b infra-waived)' (#119) from docs/ORCH-098-staging-log into main 2026-06-10 10:33:44 +03:00
b1a7239e20 docs(ORCH-098): staging gate log — SUCCESS (8/10, C9a/C9b infra-waived)
All checks were successful
CI / test (pull_request) Successful in 53s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 10:33:25 +03:00
db78c9eb7a Merge pull request 'feat(watchdog): sidecar-watchdog F1b — monitoring brain in a separate container (ORCH-100)' (#116) from feature/ORCH-100-fnd-f1b-sidecar-watchdog into main 2026-06-10 09:57:12 +03:00
deploy-finalizer
e7dad0f644 deploy(ORCH-036): finalize SUCCESS for ORCH-100
All checks were successful
CI / test (push) Successful in 52s
2026-06-10 09:57:11 +03:00
0ef1cf6698 tester(ET): auto-commit from tester run_id=571
All checks were successful
CI / test (push) Successful in 1m1s
CI / test (pull_request) Successful in 58s
2026-06-10 09:36:02 +03:00
9f62e05d01 reviewer(ET): auto-commit from reviewer run_id=570 2026-06-10 09:36:02 +03:00
318bae7472 fix(test): isolate settings.runs_dir in conftest to stop ambient prod-log pollution (ORCH-100)
test_queue.py::TestRetry::test_finalize_job_requeue_then_fail failed in the
self-hosting environment because launcher._finalize_job classifies a non-zero
exit by reading the tail of <settings.runs_dir>/<run_id>.log. settings.runs_dir
defaults to the live prod dir /app/data/runs, which on the host holds REAL
accumulated agent logs; a real 2.log containing "429" flips the expected
'permanent' classification to 'transient', requeueing the job instead of
marking it 'failed'. This is ambient prod pollution, not a code fault.

Add an autouse _isolate_runs_dir fixture (mirroring _no_telegram /
_disable_merge_verify) that redirects settings.runs_dir to a per-test tmp dir
so _run_log_path() resolves to a non-existent file and classify_log_file()
returns the documented 'permanent' default. Full suite: 1617 passed. src/**
untouched.

Refs: ORCH-100

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 09:36:02 +03:00
d61b583dad tester(ET): auto-commit from tester run_id=568 2026-06-10 09:36:02 +03:00
93cf2732a2 reviewer(ET): auto-commit from reviewer run_id=567 2026-06-10 09:36:02 +03:00
259b507906 feat(watchdog): sidecar-watchdog F1b — monitoring brain in a separate container (ORCH-100)
Add the `watchdog/` package (thin Python-3.12 stdlib-only daemon) and the
`orchestrator-watchdog` compose service — the brain half of the domain-0
observability pair. F1a (ORCH-099) exposes GET /metrics raw signal; F1b reads it,
augments with host / container / dependency probes, runs each signal through a
generalised pure decision function (decide(signal_active, prev, now, cooldown),
a strict superset of disk_watchdog.decide_action) with per-signal in-memory
dedup/throttle/recovery, and alerts over its OWN independent Telegram channel.

Key properties (ADR-001):
- Observer separated from observed: separate container; /metrics not answering is
  itself the master `orch_down` alarm (debounced K ticks — no flap on a hiccup).
- Strictly read-only: docker.sock GET-only + mounted :ro (double guard), host
  paths :ro, no DB/disk writes, no process control — self-hosting-safe.
- never-raise on three levels (per-source/per-tick/per-send) + WATCHDOG_ENABLED
  kill-switch (disabled -> inert idle-loop, not exit).
- Disk anti-duplicate (D6): disk_watchdog (ORCH-063) stays sole owner of the 85%
  alert; sidecar carries orch_down + an opt-in 97% ceiling (default off).
- NO import from src/** (C-1); src/**, STAGE_TRANSITIONS, QG_CHECKS, check_*, DB
  schema — untouched. env_file optional so a missing .env.watchdog never breaks
  `docker compose up` for the prod orchestrator.

Tests: tests/watchdog/ (TC-01…TC-13) + full tests/ regression green (TC-14).
Docs: CHANGELOG, .env.example canon (WATCHDOG_*); architecture README + adr-0033
authored at the architecture stage.

Refs: ORCH-100

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 09:36:02 +03:00
1c08b3f62a architect(ET): auto-commit from architect run_id=565 2026-06-10 09:36:02 +03:00
36102f253f analyst(ET): auto-commit from analyst run_id=564 2026-06-10 09:36:02 +03:00
874cc29ff7 docs: init ORCH-100 business request 2026-06-10 09:36:02 +03:00
26d6936eed Merge pull request 'docs(ORCH-100): staging gate log — SUCCESS (8/10, C9a/C9b infra-waived)' (#117) from docs/ORCH-100-staging-log into main
Some checks failed
CI / test (push) Has been cancelled
2026-06-10 09:35:30 +03:00
b63fca4396 docs(ORCH-100): staging gate log — SUCCESS (8/10, C9a/C9b infra-waived)
All checks were successful
CI / test (pull_request) Successful in 54s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 09:35:18 +03:00
64bb895402 docs(epic): скоуп наблюдения (3 слоя) + атрибуция уроков platform-vs-project (Слава 10.06) 2026-06-10 09:05:26 +03:00
ff20c3827a Merge pull request 'feat(bug-fast-track): cheaper/shorter pipeline route for bug-fix tasks (ORCH-019)' (#115) from feature/ORCH-019- into main
Some checks failed
CI / test (push) Has been cancelled
2026-06-10 04:03:53 +03:00
deploy-finalizer
758a732422 deploy(ORCH-036): finalize SUCCESS for ORCH-019
All checks were successful
CI / test (push) Successful in 47s
CI / test (pull_request) Successful in 47s
2026-06-10 04:03:52 +03:00
5ecc870897 tester(ET): auto-commit from tester run_id=562
All checks were successful
CI / test (push) Successful in 50s
CI / test (pull_request) Successful in 52s
2026-06-10 03:58:15 +03:00
69970ecebb reviewer(ET): auto-commit from reviewer run_id=561 2026-06-10 03:58:15 +03:00
50bcae765a feat(bug-fast-track): cheaper/shorter pipeline route for bug-fix tasks (ORCH-019)
A task carrying the Plane `Bug` label takes a shortened route that skips the
`architecture` stage (one opus architect run + ADR + check_architecture_done),
replacing heavy analysis with a lite package (bug-report + mandatory regression
test plan). EVERY Quality Gate / sub-gate runs UNCHANGED — the route is a
scheduler property, not a gate (root invariant NFR-1): STAGE_TRANSITIONS /
QG_CHECKS / check_* / machine-verdict keys are byte-for-byte preserved.

- src/bug_fast_track.py: new leaf (never-raise) — bug_fast_track_applies (local,
  network-free, checked first), is_bug_task (labels.has_label, Plane API source),
  skips_architecture (pure DB-backed routing predicate), snapshot.
- src/db.py: additive idempotent tasks.track column (TEXT DEFAULT 'full') +
  set_task_track / get_task_track helpers (missing/NULL -> 'full', fail-safe).
- src/stage_engine.py: routing-override on the analysis-exit edge (track='bug' ->
  development/developer, skipping architect); brd-review-clock stamp extended to
  analysis->development. get_next_stage/get_agent_for_stage stay pure.
- src/webhooks/plane.py: classify task as bug in start_pipeline (applies-first
  short-circuit; never-raise -> full cycle on any error).
- src/main.py: additive bug_fast_track block in GET /queue + POST
  /bug-fast-track/escalate (reset 'bug'->'full' to return to the full cycle).
- src/config.py: bug_fast_track_enabled / _label / _repos flags (empty CSV ->
  self-hosting only).
- src/notifications.py: optional 🐞 marker on the bug-track card (never-raise).
- Prompts: analyst.md (lite bug package + escalation), reviewer.md (regression-
  test axis) — 52d canon preserved.
- Docs: CLAUDE.md, README.md (env + API + section), docs/architecture/README.md,
  CHANGELOG.md, .env.example.
- Tests: tests/test_bug_fast_track*.py + test_db_migrations.py + queue block
  (TC-01..TC-15). Full regression green (1551 passed).

Kill-switch ORCH_BUG_FAST_TRACK_ENABLED=false -> 1:1 pre-ORCH-019 (zero
regression; residual track column harmless).

Refs: ORCH-019

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 03:58:15 +03:00
bc04186b93 architect(ET): auto-commit from architect run_id=558 2026-06-10 03:58:15 +03:00
2dfbdd61aa analyst(ET): auto-commit from analyst run_id=557 2026-06-10 03:58:15 +03:00
5fd9b1a094 docs: init ORCH-019 business request 2026-06-10 03:58:15 +03:00
a14d2cc5c8 docs(ORCH-019): staging gate log — SUCCESS (8/10, C9a/C9b infra-waived) 2026-06-10 03:57:54 +03:00
e2c0b2ba9b Merge pull request 'feat: ORCH-057-bug-follow-up-orch-040-normali' (#113) from feature/ORCH-057-bug-follow-up-orch-040-normali into main
Some checks failed
CI / test (push) Has been cancelled
2026-06-10 03:09:07 +03:00
deploy-finalizer
c30dc71b88 deploy(ORCH-036): finalize SUCCESS for ORCH-057
All checks were successful
CI / test (push) Successful in 44s
2026-06-10 03:09:06 +03:00
6d8b7fb934 tester(ET): auto-commit from tester run_id=555
All checks were successful
CI / test (push) Successful in 51s
CI / test (pull_request) Successful in 48s
2026-06-10 03:03:34 +03:00
5d4ef9369e reviewer(ET): auto-commit from reviewer run_id=554 2026-06-10 03:03:34 +03:00
a98d605477 feat(fs): legacy root-owned ownership detect + actionable worktree error (ORCH-057)
Follow-up ORCH-040: legacy root:root files in /repos broke worktree creation
under uid 1000 with a raw "Permission denied" (agent never started, no diagnosis).
Three additive, kill-switch-reversible layers; STAGE_TRANSITIONS / QG_CHECKS /
check_* / machine-verdict keys / DB schema are byte-for-byte unchanged.

- D1: ensure_worktree classifies the permission class and raises an actionable
  RuntimeError (cause + chown command + INFRA.md ref); non-permission errors keep
  the prior raw-stderr contract; kill-switch off -> contract 1:1 as before ORCH-057.
- D2: new never-raise leaf src/fs_normalize.py — scan_ownership (TTL-cached,
  early-exit per root), applies()-first scope (empty CSV -> self-hosting only),
  opt-in normalize() that chowns ONLY when privileged (no-op under uid 1000).
- D3: best-effort startup detect in main.lifespan (WARNING + Telegram on mismatch,
  never-fatal); read-only fs_ownership block in GET /queue; POST /fs-normalize/check.
  Claim is NOT blocked — the clear early outcome is delivered by D1 at launch.
- Docs/config: .env.example flags + CHANGELOG (architecture README / adr-0031 /
  INFRA.md procedure already landed on the branch).
- Tests: test_fs_normalize.py, test_git_worktree_perm.py,
  test_fs_normalize_startup.py, test_api_queue.py (TC-01..TC-12). Full suite green.

Refs: ORCH-057
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 03:03:34 +03:00
34778217fe developer(ET): auto-commit from developer run_id=552 2026-06-10 03:03:34 +03:00
6a923f53cb architect(ET): auto-commit from architect run_id=551 2026-06-10 03:03:34 +03:00
e7868e3fc9 architect(ET): auto-commit from architect run_id=550 2026-06-10 03:03:34 +03:00
a0659de4d2 analyst(ET): auto-commit from analyst run_id=548 2026-06-10 03:03:34 +03:00
3364436a2e docs: init ORCH-057 business request 2026-06-10 03:03:34 +03:00
7125c03d16 Merge pull request 'docs(ORCH-057): staging gate log — SUCCESS (8/10, C9a/C9b infra-waived)' (#114) from docs/ORCH-057-staging-log into main 2026-06-10 03:03:08 +03:00
78c3fe100f docs(ORCH-057): staging gate log — SUCCESS (8/10, C9a/C9b infra-waived)
All checks were successful
CI / test (pull_request) Successful in 48s
Staging suite exit 0; all REAL checks green, C9a/C9b INFRA-WAIVED (ORCH-061).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 03:02:56 +03:00
cd664b0382 Merge pull request 'feat(metrics): lightweight read-only GET /metrics raw-signal endpoint (ORCH-099)' (#111) from feature/ORCH-099-fnd-f1a-metrics-agent-liveness into main
Some checks failed
CI / test (push) Has been cancelled
2026-06-10 02:14:40 +03:00
deploy-finalizer
999615f8cd deploy(ORCH-036): finalize SUCCESS for ORCH-099
All checks were successful
CI / test (push) Successful in 45s
2026-06-10 02:14:39 +03:00
fda1bea9b8 tester(ET): auto-commit from tester run_id=546
All checks were successful
CI / test (push) Successful in 46s
CI / test (pull_request) Successful in 51s
2026-06-10 02:09:19 +03:00
4840f3f411 reviewer(ET): auto-commit from reviewer run_id=545 2026-06-10 02:09:19 +03:00
d8793c9698 feat(metrics): lightweight read-only GET /metrics raw-signal endpoint (ORCH-099)
FND/F1a: add a versioned read-only JSON endpoint GET /metrics that exposes the
orchestrator's own raw state for the future observability sidecar F1b — active
task stages, job queue, agent-liveness (pid/runtime/cpu_ticks), and cost/tokens.
The orchestrator emits ONLY raw signal it alone knows; thresholds/alerts/history
live in the separate sidecar (observer separated from observed, BRD §1).

- src/metrics.py: new leaf collector build_metrics() (never-raise per section,
  serial_gate.snapshot() pattern); envelope schema_version/generated_at/clk_tck +
  stages/queue/agents/cost. _read_cpu_ticks(pid) reads utime+stime from
  /proc/<pid>/stat (null on None/dead/non-Linux pid — never raises).
- src/main.py: thin @app.get("/metrics") wrapper (style of GET /queue).
- src/db.py: read-only helpers get_running_agents() (dedicated SELECT, not an
  extension of the hot-path get_running_jobs()), agent_cost_totals(),
  queue_retry_stats(); job_status_counts() default dict gains the cancelled key.
- src/config.py: metrics_endpoint_enabled kill-switch (default True), env
  ORCH_METRICS_ENABLED via explicit validation_alias so the documented switch
  actually controls the flag.
- docs: README API table row + CHANGELOG entry (contract section already added
  by architect); .env.example ORCH_METRICS_ENABLED.

Strictly read-only / never-raise: STAGE_TRANSITIONS / QG_CHECKS / check_* /
machine-verdict keys / DB schema untouched; /health//status//queue byte-for-byte.
Tests: tests/test_metrics.py (TC-01..TC-11) + env-alias tests in test_config.py.
Full suite green (1482).

Refs: ORCH-099
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 02:09:19 +03:00
8988dca14d architect(ET): auto-commit from architect run_id=542 2026-06-10 02:09:19 +03:00
aa724885d1 analyst(ET): auto-commit from analyst run_id=541 2026-06-10 02:09:19 +03:00
da6e1bb9f1 docs: init ORCH-099 business request 2026-06-10 02:09:19 +03:00
6ea732bbb4 Merge pull request 'docs(ORCH-099): staging gate log — SUCCESS' (#112) from docs/ORCH-099-staging-log into main 2026-06-10 02:08:53 +03:00
5632a047d5 docs(ORCH-099): staging gate log — SUCCESS (8/10, C9a/C9b infra-waived)
All checks were successful
CI / test (pull_request) Successful in 45s
2026-06-10 02:08:40 +03:00
567c27e1d9 Merge pull request 'feat(coverage): deterministic test-coverage gate (ORCH-027)' (#109) from feature/ORCH-027-code-coverage into main
Some checks failed
CI / test (push) Has been cancelled
2026-06-10 01:30:54 +03:00
deploy-finalizer
dffd151434 deploy(ORCH-036): finalize SUCCESS for ORCH-027
All checks were successful
CI / test (push) Successful in 43s
CI / test (pull_request) Successful in 43s
2026-06-10 01:30:51 +03:00
c2369db808 tester(ET): auto-commit from tester run_id=539
All checks were successful
CI / test (push) Successful in 45s
CI / test (pull_request) Successful in 43s
2026-06-10 01:26:24 +03:00
4fbc8d99e3 reviewer(ET): auto-commit from reviewer run_id=538 2026-06-10 01:26:24 +03:00
78b6cdb3f1 docs(changelog): repair duplicated ORCH-095 entry body
Reviewer P1 (ORCH-027 attempt 2): inserting the ORCH-027 changelog
block duplicated the adjacent ORCH-095 entry — its paragraph body was
repeated verbatim, corrupting a golden-source doc and another work
item's artifact (CLAUDE.md §3). Remove the duplicate half, leaving a
single ORCH-095 body. ORCH-027 entry untouched (already correct).

Refs: ORCH-027

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 01:26:24 +03:00
feb8bc188b reviewer(ET): auto-commit from reviewer run_id=536 2026-06-10 01:26:24 +03:00
9647fe1ffb fix(coverage): use sys.executable for the pytest --cov subprocess (ORCH-027)
measure_coverage hardcoded "python" for the coverage subprocess; the prod container
and the CI runner expose "python3" (a bare "python" may be absent), and pytest-cov
lives in exactly the running interpreter's environment. Use sys.executable so the
measurement always runs under the same interpreter as the orchestrator.

Refs: ORCH-027
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 01:26:24 +03:00
eadfd8419b feat(coverage): deterministic test-coverage gate on deploy-staging->deploy edge (ORCH-027)
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>
2026-06-10 01:26:24 +03:00
1f9c128a48 architect(ET): auto-commit from architect run_id=534 2026-06-10 01:26:24 +03:00
e9e8b1e246 architect(ET): auto-commit from architect run_id=533 2026-06-10 01:26:24 +03:00
9953275eed analyst(ET): auto-commit from analyst run_id=532 2026-06-10 01:26:24 +03:00
a37de1d890 docs: init ORCH-027 business request 2026-06-10 01:26:24 +03:00
9e10bea500 Merge pull request 'docs(ORCH-027): staging gate log — SUCCESS' (#110) from docs/ORCH-027-staging-log into main 2026-06-10 01:25:57 +03:00
2f72390dba docs(ORCH-027): staging gate log — SUCCESS (8/10, C9a/C9b infra-waived)
All checks were successful
CI / test (pull_request) Successful in 43s
2026-06-10 01:25:47 +03:00
9c522e9f76 docs(epic): концепция автономного саморазвития платформы (домены/вертикали/архрамки наблюдаемости) 2026-06-10 00:54:54 +03:00
8c2fa5de6d Merge pull request 'fix(notifications): HTML-safe card data render — fix <1м injection freezing the tracker (ORCH-095)' (#107) from feature/ORCH-095-bug-html-1-render-task-tracker into main
Some checks failed
CI / test (push) Has been cancelled
2026-06-10 00:21:49 +03:00
deploy-finalizer
2686e3e99f deploy(ORCH-036): finalize SUCCESS for ORCH-095
All checks were successful
CI / test (push) Successful in 40s
2026-06-10 00:21:48 +03:00
cdc5e5c548 tester(ET): auto-commit from tester run_id=530
All checks were successful
CI / test (push) Successful in 41s
CI / test (pull_request) Successful in 41s
2026-06-10 00:17:26 +03:00
b77d412c36 reviewer(ET): auto-commit from reviewer run_id=529 2026-06-10 00:17:26 +03:00
b38cc16041 fix(notifications): escape all card data fields at the render boundary (ORCH-095)
render_task_tracker sends/edits the live card with parse_mode=HTML. _fmt_minutes
returns the literal "<1м" for a sub-minute stage; interpolated raw into HTML text
Telegram parsed "<1м" as an opening tag -> editMessageText 400 can't parse
entities -> edit_telegram EDIT_FAILED -> update_task_tracker early return
(anti-duplicate ORCH-087) -> the card froze (incident ORCH-093, message_id 18854).

Close the whole "unescaped data in HTML text" class per ADR-001: a module-local
_esc(x)=html.escape(str(x)) (never-raise) wraps every DATA slot (durations, status
label, model, effort, token/cost metrics) exactly once at the render boundary in
render_task_tracker/_stage_line. Source functions stay HTML-agnostic (_fmt_minutes
still returns "<1м"; escape on the boundary renders it visually identical as
&lt;1м, so the visible format is unchanged). Intentional MARKUP slots (num_html /
link_for / _done_link / already-escaped esc_title) are NOT escaped, so the issue
number stays a clickable <a> tag and nothing is double-escaped.

A previously-frozen card auto-recovers on the next stage transition (a new safe
render edits in place, 200) — no new code, no touch to edit_telegram /
update_task_tracker / the orphan ledger, so the ORCH-087 anti-duplicate invariant
is preserved (a transient edit failure still does not spawn a new card).

STAGE_TRANSITIONS / QG_CHECKS / check_* / notification transport / DB schema are
untouched. New tests/test_tracker_html_escape.py (TC-01..TC-11); full suite green.

Refs: ORCH-095

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 00:17:26 +03:00
6b14b07f40 architect(ET): auto-commit from architect run_id=526 2026-06-10 00:17:26 +03:00
d528f77b03 analyst(ET): auto-commit from analyst run_id=525 2026-06-10 00:17:26 +03:00
c8aab19958 docs: init ORCH-095 business request 2026-06-10 00:17:26 +03:00
af86c7fabb Merge pull request 'docs(ORCH-095): staging gate log — SUCCESS (8/10, C9a/C9b infra-waived)' (#108) from docs/ORCH-095-staging-log into main 2026-06-10 00:17:00 +03:00
7fa381d814 docs(ORCH-095): staging gate log — SUCCESS (8/10, C9a/C9b infra-waived)
All checks were successful
CI / test (pull_request) Successful in 42s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 00:16:47 +03:00
e0f44cc4ef Merge pull request 'fix(deploy): terminal-window-aware guard so done tasks hold Done in Plane (ORCH-094)' (#105) from feature/ORCH-094-bug-done-deploy-plane-awaiting into main
Some checks failed
CI / test (push) Has been cancelled
2026-06-09 23:45:49 +03:00
deploy-finalizer
b243343cd5 deploy(ORCH-036): finalize SUCCESS for ORCH-094
All checks were successful
CI / test (push) Successful in 43s
CI / test (pull_request) Successful in 41s
2026-06-09 23:45:46 +03:00
fe35b2224a tester(ET): auto-commit from tester run_id=523
All checks were successful
CI / test (push) Successful in 42s
CI / test (pull_request) Successful in 40s
2026-06-09 23:41:24 +03:00
08ca4ab258 reviewer(ET): auto-commit from reviewer run_id=522 2026-06-09 23:41:24 +03:00
a46dcbcab3 fix(deploy): terminal-window-aware guard so done tasks hold Done in Plane (ORCH-094)
A DB stage=done task with 0 active jobs flapped in Plane between `Awaiting
Deploy` and `Monitoring after Deploy` instead of holding `Done` (verified live
on ORCH-061, task 47): the three deploy-phase setters were terminal-blind, so
any stale/duplicate/unknown caller under the bot token re-stamped an
intermediate status over the terminal Done, forever.

- New leaf src/deploy_status_guard.py (pure, never-raise, config-gated): decide()
  -> ALLOW | CONVERGE_DONE | SUPPRESS on the entry of set_issue_awaiting_deploy /
  set_issue_deploying / set_issue_monitoring. A deploy-phase status is legitimate
  iff the task is non-terminal OR (done AND post-deploy window active); otherwise
  done converges to Done idempotently, cancelled is suppressed (FR-2, D1/D2).
- D3: move post_deploy.arm_monitor ABOVE the terminal-sync block in advance_stage
  so window_active is True when the legitimate first Monitoring is set (the task
  is already DB-done by then); a re-drive after the window closes converges to Done.
- D4: run_post_deploy_monitor no-ops without a status PATCH / re-queue when the
  task became cancelled mid-window (zombie-tick guard, FR-3).
- D5: additive `reason` kwarg on the three setters + one structured log line per
  verdict (work_item/caller/target/db_stage/window_active/verdict); new read-only
  db.get_task_by_work_item_id; post_deploy.window_active helper.
- Flags deploy_status_guard_enabled (kill-switch -> 1:1) / deploy_status_guard_repos
  (CSV; empty = self-hosting only). STAGE_TRANSITIONS / QG_CHECKS / check_* /
  machine-verdict keys / DB schema untouched (reads existing tasks.stage).

Tests: TC-01..TC-12 across 5 new test modules + config flags; updated the
reason-kwarg assertions in test_deploy_terminal_sync / test_deploy_approve.
Full regress green (1413). Docs: CHANGELOG, CLAUDE.md, docs/architecture/README.md
(status -> реализовано), .env.example.

Refs: ORCH-094

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 23:41:24 +03:00
db4dd275e4 architect(ET): auto-commit from architect run_id=520 2026-06-09 23:41:24 +03:00
8959e0e3f4 architect(ET): auto-commit from architect run_id=519 2026-06-09 23:41:24 +03:00
f36528705e analyst(ET): auto-commit from analyst run_id=518 2026-06-09 23:41:24 +03:00
5e01df00eb docs: init ORCH-094 business request 2026-06-09 23:41:24 +03:00
fcb40eb4bb Merge pull request 'docs(ORCH-094): staging gate log — SUCCESS' (#106) from docs/ORCH-094-staging-log into main 2026-06-09 23:40:59 +03:00
b86fc9043f docs(ORCH-094): staging gate log — SUCCESS (8/10, C9a/C9b infra-waived)
All checks were successful
CI / test (pull_request) Successful in 41s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 23:40:46 +03:00
fbedd0485b Merge pull request 'fix(merge_gate): retry transient Gitea merge errors (405/5xx) + already-in-main guard (ORCH-093)' (#104) from feature/ORCH-093-bug-merge-gitea-405-5xx-hold-p into main
Some checks failed
CI / test (push) Has been cancelled
2026-06-09 22:51:43 +03:00
deploy-finalizer
f9ce5ca1b8 deploy(ORCH-036): finalize SUCCESS for ORCH-093
All checks were successful
CI / test (push) Successful in 38s
CI / test (pull_request) Successful in 40s
2026-06-09 22:51:42 +03:00
7863932012 tester(ET): auto-commit from tester run_id=516
All checks were successful
CI / test (push) Successful in 42s
CI / test (pull_request) Successful in 39s
2026-06-09 22:47:20 +03:00
74418893d7 reviewer(ET): auto-commit from reviewer run_id=515 2026-06-09 22:47:20 +03:00
0b25fc1527 fix(merge_gate): retry transient Gitea merge errors + already-in-main guard
merge_pr now wraps ONLY the mutating POST /pulls/{n}/merge in a bounded
exponential-backoff retry-loop on TRANSIENT outcomes (405 "try again later",
408, any 5xx, network/timeout, and 409|422 while the PR is still mergeable);
TERMINAL outcomes (403/404/real conflict via mergeable==False) -> fast honest
False, so the ORCH-071/081 not-merged HOLD backstop is unchanged. Fixes the
ORCH-063 false HOLD + manual re-merge on Gitea's post-push mergeability hiccup.

ensure_open_pr gains an "already fully in main" guard (_branch_fully_in_main,
git merge-base --is-ancestor HEAD origin/main) BEFORE creating a PR -> new
"already-in-main" outcome avoids the garbage empty PR on a re-driven finalizer;
_handle_merge_verify skips merge_pr on that outcome and lets the authoritative
SHA-in-main check confirm -> done (not a HOLD). git error of the guard fails
OPEN to the create path.

New ORCH_MERGE_RETRY_* settings (kill-switch merge_retry_enabled -> one-shot,
max_attempts=3, backoff base=2/max=5). INV-4 (merge only via Gitea PR-merge API,
never push/force-push main), never-raise, STAGE_TRANSITIONS/QG_CHECKS/DB schema
unchanged. Docs (README merge-verify section, CLAUDE.md, CHANGELOG, .env.example)
updated in the same PR. Tests: test_merge_gate.py TC-01..12, test_config.py
TC-13, test_merge_verify.py TC-14..16; full suite green (1389).

Refs: ORCH-093

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 22:47:20 +03:00
3d0f51512b architect(ET): auto-commit from architect run_id=512 2026-06-09 22:47:20 +03:00
520373a694 analyst(ET): auto-commit from analyst run_id=511 2026-06-09 22:47:20 +03:00
cf0a72a46b docs: init ORCH-093 business request 2026-06-09 22:47:20 +03:00
1a52fcba9e docs(ORCH-093): staging gate log — SUCCESS (8/10, C9a/C9b infra-waived)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 22:46:56 +03:00
33b7fd57ff Merge pull request 'fix(notifications): tracker card — status completeness, rollback reflection, stage-metric summation (ORCH-091)' (#102) from feature/ORCH-091-bug-to-analyse-stage-deploy-st into main
Some checks failed
CI / test (push) Has been cancelled
2026-06-09 22:13:09 +03:00
deploy-finalizer
6feae55a4b deploy(ORCH-036): finalize SUCCESS for ORCH-091
All checks were successful
CI / test (push) Successful in 32s
2026-06-09 22:13:08 +03:00
86b013c872 tester(ET): auto-commit from tester run_id=509
All checks were successful
CI / test (push) Successful in 36s
CI / test (pull_request) Successful in 33s
2026-06-09 22:08:52 +03:00
3d6e957cae reviewer(ET): auto-commit from reviewer run_id=508 2026-06-09 22:08:52 +03:00
328ae78da3 fix(notifications): tracker card — status-map completeness, rollback reflection, stage-metric summation (ORCH-091)
Three verified live-card defects in src/notifications.py (ORCH-067/087),
all additive and indication-only (STAGE_TRANSITIONS / QG_CHECKS / check_* /
transport / DB schema untouched; never-raise; revert = git revert):

- Деф.1 (D1): _STAGE_STATUS_LABEL covered 8 of 10 STAGE_TRANSITIONS keys —
  deploy-staging and cancelled (ORCH-090) fell back to the misleading
  "To Analyse". Added deploy-staging→"Deploying (staging)",
  cancelled→"Cancelled"; replaced the runtime fallback for an UNMAPPED stage
  with a neutral capitalized label (_neutral_stage_label). created stays an
  explicit "To Analyse"; broken/None input degrades safely. Map completeness
  is asserted programmatically from STAGE_TRANSITIONS.keys() (single source of
  truth), not a static list.
- Деф.2 (D2): the stage-row loop drew  for any stage with a finished agent
  run regardless of position — after a rollback the card showed the absurd
  " Внедрение + 🔄 Разработка". Added read-only _pipeline_pos from the
  STAGE_TRANSITIONS order and a suppression gate ( only when
  current_pos >= _pipeline_pos(stage_key)); deploy-staging→deploy normalization
  applied ONLY to the current position; is_active_stage untouched.
- Деф.3 (D3): _stage_line took only the LAST run (ORCH-069: developer 3 runs
  Σ $3.98 rendered ~$0.00). It now aggregates ALL of the agent's runs with the
  same per-run formulas as the task totals → strict convergence with
  SUM(agent_runs) by task_id; model/effort/attempt come from the last run.

Tests: test_tracker_status_line.py (ORCH-091 TC-01..TC-03 + updated tc06);
new test_tracker_rollback_metrics.py (TC-05..TC-08). Full suite green (1370).
Docs: CHANGELOG + internals.md (architecture README already updated by architect).

Refs: ORCH-091
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 22:08:52 +03:00
c0f2d917bf architect(ET): auto-commit from architect run_id=506 2026-06-09 22:08:52 +03:00
53022d20f4 architect(ET): auto-commit from architect run_id=505 2026-06-09 22:08:52 +03:00
852da919b9 analyst(ET): auto-commit from analyst run_id=504 2026-06-09 22:08:52 +03:00
67f7a3abfa docs: init ORCH-091 business request 2026-06-09 22:08:52 +03:00
a994b25146 Merge pull request 'docs(ORCH-091): staging gate log — SUCCESS' (#103) from docs/ORCH-091-staging-log into main 2026-06-09 22:08:27 +03:00
36cd6e887b docs(ORCH-091): staging gate log — SUCCESS (8/10, C9a/C9b infra-waived)
All checks were successful
CI / test (pull_request) Successful in 34s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 22:08:14 +03:00
3b64cddd32 Merge pull request 'feat(cancel): STOP-status task cancellation + relaunch-hole close (ORCH-090)' (#101) from feature/ORCH-090-stop-plane into main
Some checks failed
CI / test (push) Has been cancelled
2026-06-09 21:36:12 +03:00
deploy-finalizer
08e6bfc3d5 deploy(ORCH-036): finalize SUCCESS for ORCH-090
All checks were successful
CI / test (push) Successful in 34s
2026-06-09 21:36:11 +03:00
5ca9b8fd62 tester(ET): auto-commit from tester run_id=502
All checks were successful
CI / test (push) Successful in 36s
CI / test (pull_request) Successful in 31s
2026-06-09 21:31:56 +03:00
07190f69f5 reviewer(ET): auto-commit from reviewer run_id=501 2026-06-09 21:31:56 +03:00
aae65969d5 fix(cancel): narrow STOP critical-window so deploy-park cancel applies (ORCH-090)
Review P1: a STOP while a self-hosting task is PARKED on `deploy` awaiting the
manual `Confirm Deploy` was classified as a critical merge/deploy window solely
because the task still held the per-repo merge-lease (held from merge-gate through
deploy->done). That window is fully reversible — nothing is merged or deployed yet
(the irreversible merge_pr runs later in _handle_merge_verify, always under an
INITIATED marker). So the cancel was DEFERRED to run_deploy_finalizer, which only
runs after Phase B (Confirm Deploy) — the very step the operator pressed STOP to
avoid. Result: the deferred cancel was never applied, the task wedged non-terminal
holding the lease, blocking the repo's serial-gate (ORCH-088) and merges.

Fix: gate the merge-lease branch of cancel.in_critical_window on an actively
RUNNING actor (_task_has_running_actor). Lease held + running deploy/merge job ->
still deferred (genuine in-flight step). Lease held + no running actor (idle
deploy parking) -> NOT critical -> immediate full reset, which itself releases the
lease (step 3c) and drives the task terminal. INITIATED-marker deferral unchanged.

Also fixes review P2 (AC-6): set_task_cancel_requested now returns the first-stamp
fact (rowcount), and the deferred branch only notifies on the first transition —
a repeated STOP while still deferred no longer spams duplicate notifications.

Tests: test_d7_lease_held_idle_parking_is_not_critical,
test_d7_lease_held_with_running_actor_still_critical,
test_d7_stop_on_deploy_awaiting_confirm_full_resets,
test_d7_repeated_stop_in_critical_window_no_duplicate_notify. Full suite green (1349).

Refs: ORCH-090

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 21:31:56 +03:00
46c59bad99 reviewer(ET): auto-commit from reviewer run_id=499 2026-06-09 21:31:56 +03:00
ebbf2e7a2d feat(cancel): STOP-status task cancellation + relaunch-hole close (ORCH-090)
Introduce the dedicated Plane STOP status as a single declarative task-cancel
mechanism: stop the active agent (graceful SIGTERM cascade), cancel all jobs
(terminal `cancelled`, never requeued), remove the worktree + delete the remote
feature branch (never main, never force-push), drive the task to the new
system-terminal state `cancelled` and tombstone the natural keys so a later
"To Analyse" re-creates it from scratch (docs artefacts preserved). STOP during a
critical merge/deploy window is deferred until the irreversible step finishes
honestly. Also closes the relaunch hole: handle_status_start relaunch is gated to
the `analysis` stage; the only pipeline-start entry point remains "To Analyse".

Cross-cutting (adr-0026): the "task terminal" predicate is widened {done} ->
{done, cancelled} in serial_gate / task_deps / stages sink + reaper/worker
requeue guards. STAGE_TRANSITIONS exit-gates / QG_CHECKS / check_* are unchanged
(`cancelled` is a sink, not a new edge). Additive, never-raise, restart-safe,
under kill-switch ORCH_STOP_STATUS_ENABLED (off -> zero regression).

New: src/cancel.py (leaf), src/gitea.py (delete_remote_branch), tasks columns
cancelled_at/cancel_requested_at, jobs status `cancelled`, GET /queue `stop` block.
Tests: tests/test_stop_status.py (TC-01..TC-14 + D7); full suite green (1345).
Docs updated in-PR (architecture README, CLAUDE.md, README.md, .env.example,
CHANGELOG). ADR-001 D4 refinement: plane_issue_id is tombstoned too (the lookup
ORs on it) — original UUID recoverable from the parseable suffix.

Refs: ORCH-090

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 21:31:56 +03:00
ab083ba826 architect(ET): auto-commit from architect run_id=497 2026-06-09 21:31:56 +03:00
96a99a09b7 analyst(ET): auto-commit from analyst run_id=496 2026-06-09 21:31:56 +03:00
105d6e9cba docs: init ORCH-090 business request 2026-06-09 21:31:56 +03:00
7b760e54da docs(ORCH-090): staging gate log — SUCCESS (8/10, C9a/C9b infra-waived)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 21:31:30 +03:00
6ae611a376 Merge pull request 'ORCH-062 — INFRA: авто-prune docker build cache на mva154' (#100) from feature/ORCH-062-infra-prune-docker-build-cache into main
Some checks failed
CI / test (push) Has been cancelled
2026-06-09 19:59:13 +03:00
deploy-finalizer
c816b33c19 deploy(ORCH-036): finalize SUCCESS for ORCH-062
All checks were successful
CI / test (push) Successful in 29s
2026-06-09 19:59:13 +03:00
5ead4543ee tester(ET): auto-commit from tester run_id=494
All checks were successful
CI / test (push) Successful in 33s
CI / test (pull_request) Successful in 29s
2026-06-09 19:55:00 +03:00
247915e3d1 reviewer(ET): auto-commit from reviewer run_id=493 2026-06-09 19:55:00 +03:00
664c2e945a feat(infra): auto-prune docker build cache on mva154 (ORCH-062)
Add src/build_cache_pruner.py — a background daemon thread modelled 1:1 on
src/disk_watchdog.py that periodically runs STRICTLY `docker builder prune -f
--filter until=<until>` (BuildKit GC) on the HOST over ssh. It is the "second
half" of the disk-watchdog (ORCH-063): the watchdog signals, the pruner cleans.
Removes the root cause of the 07.06.2026 incident (build cache ~11GB -> disk
100% -> whole self-hosting pipeline down) automatically, без оператора.

ADR-001 (Variant A): host-over-ssh, same channel as image_freshness/self_deploy
(no docker CLI in the image). Touches ONLY the build cache — no image/system
prune, no image/container removal, never restarts the docker daemon or the prod
container (self-hosting safety). No ssh target -> tick is a no-op.

- src/config.py: ORCH_BUILD_CACHE_PRUNE_* flags + defensive validators
  (interval/timeout >0, until ~ ^\d+[smhdw]?$, notify_min_gb >=0 -> safe default).
- src/main.py: start last (after disk_watchdog) / stop first in lifespan;
  additive read-only build_cache_prune block in GET /queue.
- never-raise on two levels (per-command + per-tick); kill-switch
  ORCH_BUILD_CACHE_PRUNE_ENABLED (false -> daemon does not start, 1:1 as before).
- STAGE_TRANSITIONS / QG_CHECKS / check_* / _parse_* / DB schema UNCHANGED;
  last-run/last-result is in-memory (no migration).
- tests/test_build_cache_pruner.py: TC-01..TC-12 (23 cases, docker fully mocked).
- .env.example + CHANGELOG.md updated; INFRA.md / architecture docs already
  carry the component (architecture stage).

Refs: ORCH-062

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 19:55:00 +03:00
d2604e42cd architect(ET): auto-commit from architect run_id=491 2026-06-09 19:55:00 +03:00
621c1352e1 analyst(ET): auto-commit from analyst run_id=490 2026-06-09 19:55:00 +03:00
e86ea82501 docs: init ORCH-062 business request 2026-06-09 19:55:00 +03:00
1b03f6b3a7 docs(ORCH-062): staging gate log — SUCCESS (8/10, C9a/C9b infra-waived) 2026-06-09 19:54:36 +03:00
4d74d981da Merge pull request 'ORCH-063 — Disk-watchdog: мониторинг диска mva154 + Telegram-алерт при ≥85%' (#98) from feature/ORCH-063-infra-mva154-85 into main
Some checks failed
CI / test (push) Has been cancelled
2026-06-09 19:13:33 +03:00
deploy-finalizer
2bd3bb75d4 deploy(ORCH-036): finalize SUCCESS for ORCH-063
All checks were successful
CI / test (push) Successful in 30s
CI / test (pull_request) Successful in 30s
2026-06-09 19:08:50 +03:00
efd744f766 tester(ET): auto-commit from tester run_id=488
All checks were successful
CI / test (push) Successful in 35s
CI / test (pull_request) Successful in 32s
2026-06-09 19:04:36 +03:00
fb4203b8f9 reviewer(ET): auto-commit from reviewer run_id=486 2026-06-09 19:04:36 +03:00
8759cb7df8 feat(disk-watchdog): host-FS fill heartbeat + Telegram alert at >=85% (ORCH-063)
Adds src/disk_watchdog.py — a background daemon thread modelled on
reconciler/job_reaper that measures host-FS fill via the mounted bind-paths
(/repos, /app/data) with shutil.disk_usage and Telegram-alerts the operator at
>= threshold (default 85%). The missing proactive signal: on 07.06.2026 the
mva154 host disk silently hit 100% and stalled the whole self-hosting pipeline.

- Pure decide_action(used_pct, threshold, prev, now, realert_s): alert on
  crossing up, cooldown re-alert, single recovery below threshold (unit-tested
  without a thread/timer; clock injected).
- measure_paths: shutil.disk_usage per path, dedup by st_dev, per-path
  never-raise (a broken path never fails the tick).
- Config flags ORCH_DISK_MONITOR_* with defensive validation (threshold 1..100,
  positive intervals -> default + warning). Kill-switch -> daemon does not start.
- Additive disk_monitor block in GET /queue; start/stop in main.lifespan.
- never-raise (per-path/per-tick/per-send); STAGE_TRANSITIONS/QG_CHECKS/check_*/
  DB schema untouched, no migration (anti-spam state in-memory).

Tests: tests/test_disk_watchdog.py (TC-01..TC-12, 18 cases); full suite green
(1296). Docs: INFRA.md, .env.example, CHANGELOG.md (architecture/README.md +
ADRs authored at architecture stage).

Refs: ORCH-063
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 19:04:36 +03:00
4d9251c698 architect(ET): auto-commit from architect run_id=484 2026-06-09 19:04:36 +03:00
8ace9f880d analyst(ET): auto-commit from analyst run_id=483 2026-06-09 19:04:36 +03:00
8c97a6ab1c docs: init ORCH-063 business request 2026-06-09 19:04:36 +03:00
a499ee8e42 docs(ORCH-063): staging gate log — SUCCESS (8/10, C9a/C9b infra-waived)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 19:04:16 +03:00
fb96556a79 Merge pull request 'ORCH-092 — промпт-аудит 6 агентов: расхардкод даты/модели, escalation, чистка' (#97) from feature/ORCH-092-6-escalation into main
Some checks failed
CI / test (push) Has been cancelled
2026-06-09 17:50:42 +03:00
deploy-finalizer
2265cb4a93 deploy(ORCH-036): finalize SUCCESS for ORCH-092
All checks were successful
CI / test (push) Successful in 30s
CI / test (pull_request) Successful in 29s
2026-06-09 17:50:41 +03:00
caea18577a tester(ET): auto-commit from tester run_id=481
All checks were successful
CI / test (push) Successful in 37s
CI / test (pull_request) Successful in 33s
2026-06-09 17:46:27 +03:00
ef43e4a48c reviewer(ET): auto-commit from reviewer run_id=480 2026-06-09 17:46:27 +03:00
6cae171745 docs(prompts): ORCH-092 — аудит 6 агент-промптов (расхардкод, escalation, чистка)
Эпилог эпика ORCH-52. Docs/prompts-only: src/**, STAGE_TRANSITIONS, QG_CHECKS,
machine-verdict ключи и схема БД не тронуты; frontmatter_validation_strict=False.

- FR-1/FR-2: копируемые frontmatter-примеры всех 6 промптов расхардкожены
  (created_at: <YYYY-MM-DD> / model_used: <resolve ORCH-41> + врезка «не копируй
  буквально, подставь date +%F и модель из конфига»); литерал claude-opus-4-8 —
  только справка в таблице полей.
- FR-3: имена check_* в промптах сверены с QG_CHECKS — несовпадений нет
  (закреплено интеграционным тестом TC-03).
- FR-4: developer «PR>1500 → разбивай» переформулирован в эскалацию на уровне задач.
- FR-5: секция <escalation> у developer/reviewer/tester (после </success_criteria>):
  back-to:analysis / back-to:dev / REQUEST_CHANGES.
- FR-6: deployer — критичные self-hosting-запреты в видной рамке в начале <context>.
- FR-7: tester обогащён worktree-путём, smoke serial_gate (ORCH-088), покрытием TC.
- FR-8: из reviewer удалена мёртвая строка «тот же экземпляр Developer».
- FR-9 (ADR-001 D1): убран ручной git rebase origin/main — свежесть базы держит
  движок (serial-gate ORCH-088 + auto_rebase_onto_main под merge-lease).
- FR-10 (ADR-001 D2): deployer.md оставлен на английском как нормативное исключение.
- FR-11: расширен tests/test_agent_prompts_canon.py (ORCH-092 TC-01…TC-08);
  канон 52d и test_agent_frontmatter_no_model.py зелёные; полный регресс 1278 зелёный.

Документация: 6 промптов, CLAUDE.md, docs/architecture/README.md, CHANGELOG.md.

Refs: ORCH-092

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 17:46:27 +03:00
f61d963f9b architect(ET): auto-commit from architect run_id=478 2026-06-09 17:46:27 +03:00
484069851e analyst(ET): auto-commit from analyst run_id=477 2026-06-09 17:46:27 +03:00
334b8dd8fd docs: init ORCH-092 business request 2026-06-09 17:46:27 +03:00
d0b2208087 docs(ORCH-092): staging gate log — SUCCESS (8/10, C9a/C9b infra-waived)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 17:46:08 +03:00
61d5d8ffc5 Merge pull request 'ORCH-079 — ORCH-52f: синхронизация README/доков с кодом + reviewer-ось обзорных доков' (#96) from feature/ORCH-079-orch-52f-readme-reviewer into main
Some checks failed
CI / test (push) Has been cancelled
2026-06-09 16:37:44 +03:00
deploy-finalizer
99db59a277 deploy(ORCH-036): finalize SUCCESS for ORCH-079
All checks were successful
CI / test (push) Successful in 35s
2026-06-09 16:37:43 +03:00
991443b215 tester(ET): auto-commit from tester run_id=475
All checks were successful
CI / test (push) Successful in 34s
CI / test (pull_request) Successful in 34s
2026-06-09 16:33:33 +03:00
499a040ee6 reviewer(ET): auto-commit from reviewer run_id=474 2026-06-09 16:33:33 +03:00
d97b26a59f docs(ORCH-079): ORCH-52f — sync README with code + reviewer overview-docs axis
Layer 5 (final) of epic ORCH-52. Docs + prompt-only; src/ untouched.

- README.md «Известные ограничения»: fix numbering (was 1,2,3,4,3,4),
  move 6 resolved/obsolete items to «Закрыто (история)» trail with ORCH
  refs, keep only really-open limitations (Telegram-48h ORCH-087,
  task-deps intra-repo ORCH-026, serial-gate ORCH-088). Point-sync stage
  table (development → check_ci_green) and event-routing (ORCH-045).
- reviewer.md: overview-docs axis (axis 4 + constraints) — closing a
  README limitation without updating README → finding ≥P1 (canon 52d
  «»; verdict key + 5 XML sections + 6 schema fields byte-intact).
- tests: new tests/test_readme_limitations.py (numbering + no resolved
  items as open); test_agent_prompts_canon.py asserts the new axis.
- CLAUDE.md / CHANGELOG.md updated; epic ORCH-52 closed (52b→…→52f).

Refs: ORCH-079

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 16:33:33 +03:00
07a2d6ad1e architect(ET): auto-commit from architect run_id=472 2026-06-09 16:33:33 +03:00
7d1346d90f analyst(ET): auto-commit from analyst run_id=471 2026-06-09 16:33:33 +03:00
4a2a50c12b docs: init ORCH-079 business request 2026-06-09 16:33:33 +03:00
0d4f579c3f docs(ORCH-079): staging gate log — SUCCESS (8/10, C9a/C9b infra-waived)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 16:32:52 +03:00
55ead46f13 Merge pull request 'ORCH-078 — ORCH-52e: трассировка ORCH-NNN (стандарт маркеров + правило чтения ADR)' (#95) from feature/ORCH-078-orch-52e-orch-nnn into main
Some checks failed
CI / test (push) Has been cancelled
2026-06-09 15:52:57 +03:00
deploy-finalizer
8e2179a890 deploy(ORCH-036): finalize SUCCESS for ORCH-078
All checks were successful
CI / test (push) Successful in 30s
2026-06-09 15:52:56 +03:00
da709895f9 tester(ET): auto-commit from tester run_id=469
All checks were successful
CI / test (push) Successful in 35s
CI / test (pull_request) Successful in 33s
2026-06-09 15:48:43 +03:00
fb9c133ef9 reviewer(ET): auto-commit from reviewer run_id=468 2026-06-09 15:48:43 +03:00
572b3172cd docs(ORCH-078): ORCH-52e — стандарт трассировки ORCH-NNN + правило чтения ADR
Слой 4 (трассировка) эпика ORCH-52, замыкающий цепочку 52b/52c/52d.
Docs + prompts-only: src/**, STAGE_TRANSITIONS, QG_CHECKS, src/frontmatter.py,
схема БД — не тронуты; новый QG не вводится; ретро-фит 51 маркера вне объёма.

- Новый нормативный стандарт docs/_standards/TRACEABILITY.md: формат маркера,
  правило размещения, чтение истории с реальным проверяемым примером
  (src/serial_gate.py → ORCH-088 → ADR-001-serial-gate.md), fallback-доступ
  (git show origin/main:...), анти-археология (3+ → сводный сквозной ADR),
  каноничный текст правила чтения (единый источник).
- Точечные аддитивные врезки в промпты (52d-канон не переписан): developer.md
  (правило чтения чужого маркера + fallback, « X →  Y»), architect.md
  (правило чтения + анти-археология), reviewer.md (усиление оси «Соответствие
  ADR» под-пунктом: слом маркированного инварианта → finding ≥P1). Все три
  ссылаются на единый текст в TRACEABILITY.md, не копируют (анти-дубль BR-6).
- Сопутствующе: CLAUDE.md, docs/architecture/README.md (слой 4 эпика 52),
  CHANGELOG.md.
- Анти-регресс: расширен tests/test_agent_prompts_canon.py (9 новых проверок);
  проверки 52d и test_agent_frontmatter_no_model.py зелёные;
  полный pytest tests/ -q зелёный (1253 passed), src/ не изменён.

Refs: ORCH-078

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 15:48:43 +03:00
14f037a8a9 architect(ET): auto-commit from architect run_id=466 2026-06-09 15:48:43 +03:00
8064ae2c5d analyst(ET): auto-commit from analyst run_id=465 2026-06-09 15:48:43 +03:00
5349a41182 docs: init ORCH-078 business request 2026-06-09 15:48:43 +03:00
e8523ac116 docs(ORCH-078): staging gate log — deploy-staging SUCCESS
Staging suite passed inside orchestrator-staging (exit 0); C9a/C9b waived (ORCH-061).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 15:48:16 +03:00
76a778696c Merge pull request 'ORCH-077 — ORCH-52d: канон Anthropic для 6 промптов + эмиссия frontmatter-схемы 52c' (#94) from feature/ORCH-077-orch-52d-6-anthropic into main
Some checks failed
CI / test (push) Has been cancelled
2026-06-09 15:12:40 +03:00
deploy-finalizer
9fd99cb67a deploy(ORCH-036): finalize SUCCESS for ORCH-077
All checks were successful
CI / test (push) Successful in 31s
2026-06-09 15:12:39 +03:00
7619f12169 tester(ET): auto-commit from tester run_id=463
All checks were successful
CI / test (push) Successful in 35s
CI / test (pull_request) Successful in 30s
2026-06-09 15:08:27 +03:00
93d5c9c296 reviewer(ET): auto-commit from reviewer run_id=462 2026-06-09 15:08:27 +03:00
8beed58d98 docs(prompts): rewrite 6 agent prompts in Anthropic canon + emit 52c schema (ORCH-52d)
Замыкающий слой эпика ORCH-52. Тело всех 6 промптов .openclaw/agents/*.md
переписано в едином каноне 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 не включён).
Функциональное содержание старых промптов перенесено 1:1 (инвентарь TRZ §FR-6).

- tests/test_agent_prompts_canon.py: структурный анти-регресс (TC-01…TC-07)
- tests/manual/ab_prompt_compare.md: метод A/B (TC-09 / AC-6)
- CLAUDE.md, CHANGELOG.md обновлены; README/ADR — архитектором

Полный регресс pytest tests/ -q зелёный (1244); test_agent_frontmatter_no_model
остаётся зелёным.

Refs: ORCH-077
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 15:08:27 +03:00
b21e9d8898 architect(ET): auto-commit from architect run_id=460 2026-06-09 15:08:27 +03:00
bd5d681083 analyst(ET): auto-commit from analyst run_id=459 2026-06-09 15:08:27 +03:00
9deff540f5 docs: init ORCH-077 business request 2026-06-09 15:08:27 +03:00
8455e31dae deploy-staging(ORCH-077): staging gate SUCCESS
Staging check suite exit 0 (8/10 REAL green; C9a/C9b infra-waived per ORCH-061).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 15:08:02 +03:00
3602eee69f Merge pull request 'ORCH-076 — ORCH-52c: единый frontmatter-контракт (reader/writer/валидатор) + спека handoff' (#92) from feature/ORCH-076-orch-52c-handoff-frontmatter-w into main
Some checks failed
CI / test (push) Has been cancelled
2026-06-09 14:18:43 +03:00
deploy-finalizer
2e27f68958 deploy(ORCH-036): finalize SUCCESS for ORCH-076
All checks were successful
CI / test (push) Successful in 31s
2026-06-09 14:18:42 +03:00
cb9bfcff12 tester(ET): auto-commit from tester run_id=457
All checks were successful
CI / test (push) Successful in 36s
CI / test (pull_request) Successful in 31s
2026-06-09 14:14:30 +03:00
d846910ca6 reviewer(ET): auto-commit from reviewer run_id=456 2026-06-09 14:14:30 +03:00
92961d1d32 refactor(frontmatter): unified frontmatter contract + handoff spec (ORCH-52c)
src/frontmatter.py grows from a single-key reader into the full machine
contract: reader (read_frontmatter_value, unchanged), one parse primitive
(parse_frontmatter), writer (render/write_frontmatter), schema validator
(validate_schema/REQUIRED_FIELDS, warning-only by default) and a shared
strip_frontmatter helper. The five verdict gates (check_reviewer_verdict,
_parse_tests_verdict, _parse_deploy_status, _parse_staging_status,
parse_security_status) now read through the single parse_frontmatter point
instead of duplicated ad-hoc YAML logic; review_parse._strip_frontmatter and
security_gate.extract_security_findings reuse the shared helper.

Strictly backward compatible + never-raise: STAGE_TRANSITIONS, the QG_CHECKS
composition, verdict semantics (incl. ORCH-047 three-field tester + negative
token priority), reason-strings and worktree->origin/main fallback are 1:1.
The schema validator never influences a gate verdict by default; hard-fail is
reserved behind the frontmatter_validation_strict kill-switch (default False).

New formal handoff spec docs/_standards/HANDOFF_PROTOCOL.md ("stage -> required
output" + required frontmatter schema), aligned 1:1 with PIPELINE_DOCS.md.

Tests: test_frontmatter.py (TC-01..07), test_qg_verdicts.py (TC-08..15),
test_security_gate.py (TC-12), test_stages_invariants.py (TC-16). Full
tests/ green (1212).

Refs: ORCH-076

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 14:14:30 +03:00
2030d1627a architect(ET): auto-commit from architect run_id=454 2026-06-09 14:14:30 +03:00
98c50a094b analyst(ET): auto-commit from analyst run_id=453 2026-06-09 14:14:30 +03:00
561f58abe0 docs: init ORCH-076 business request 2026-06-09 14:14:30 +03:00
95d2c2093a Merge pull request 'docs(ORCH-076): staging gate log — SUCCESS' (#93) from docs/ORCH-076-staging-log into main 2026-06-09 14:13:59 +03:00
6868e34d1f docs(ORCH-076): staging gate log — staging_status SUCCESS
All checks were successful
CI / test (pull_request) Successful in 32s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 14:13:42 +03:00
03c6f2a145 Merge pull request 'ORCH-075 — ORCH-52b: стандарт документов (docs/_standards + docs/_templates + ADR-naming)' (#91) from feature/ORCH-075-orch-52b-docs-templates-adr-na into main
Some checks failed
CI / test (push) Has been cancelled
2026-06-09 13:31:18 +03:00
deploy-finalizer
da3e6e8acd deploy(ORCH-036): finalize SUCCESS for ORCH-075
All checks were successful
CI / test (push) Successful in 31s
2026-06-09 13:31:17 +03:00
119b8f2bec tester(ET): auto-commit from tester run_id=451
All checks were successful
CI / test (push) Successful in 36s
CI / test (pull_request) Successful in 32s
2026-06-09 13:25:39 +03:00
138092e040 reviewer(ET): auto-commit from reviewer run_id=450 2026-06-09 13:25:39 +03:00
5e60543232 docs(standards): pipeline docs standard — manifest + templates + ADR-naming
Создан golden source структуры номерных документов work item (ORCH-52b, слой 1
эпика ORCH-52). Docs-only: STAGE_TRANSITIONS / QG_CHECKS / check_* / схема БД не
трогаются (AC-6).

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

Refs: ORCH-075

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 13:25:39 +03:00
3251c8c4ed architect(ET): auto-commit from architect run_id=448 2026-06-09 13:25:39 +03:00
6511ddadbb analyst(ET): auto-commit from analyst run_id=447 2026-06-09 13:25:39 +03:00
18e98945dd docs: init ORCH-075 business request 2026-06-09 13:25:39 +03:00
0f7db904f1 deploy-staging(ORCH-075): staging gate SUCCESS
Staging suite 8/10 PASS, exit 0. All REAL checks green; C9a/C9b
sandbox-infra waived (ORCH-061). staging_status: SUCCESS.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 13:25:04 +03:00
73d936a4c4 Merge pull request 'feat(labels): auto-mode by Plane labels — autoApprove + autoDeploy (ORCH-089)' (#89) from feature/ORCH-089-autoapprove-brd-autodeploy into main
Some checks failed
CI / test (push) Has been cancelled
2026-06-09 12:38:52 +03:00
deploy-finalizer
a16196d68c deploy(ORCH-036): finalize SUCCESS for ORCH-089
All checks were successful
CI / test (push) Successful in 30s
2026-06-09 12:38:51 +03:00
9b3490ceaa tester(ET): auto-commit from tester run_id=445
All checks were successful
CI / test (push) Successful in 35s
CI / test (pull_request) Successful in 29s
2026-06-09 12:31:24 +03:00
3c407397da reviewer(ET): auto-commit from reviewer run_id=444 2026-06-09 12:31:24 +03:00
a6d0ba51c0 feat(labels): auto-mode by Plane labels — autoApprove + autoDeploy (ORCH-089)
Lift the two HUMAN gates that block an autonomous batch run (epic ORCH-088):
the BRD gate (analysis: manual Approved) and the prod-deploy gate (deploy
Phase A: manual Confirm Deploy, ORCH-059). Selective (a Plane label on the
issue), declarative, reversible, and WITHOUT touching a single technical check.

Additive, mirroring the conditional sub-gates (ORCH-035/043/058/088): leaf
src/labels.py (never-raise) + two point insertions + config flags.
STAGE_TRANSITIONS / QG_CHECKS / check_* / DB schema are NOT touched.

- autoApprove: врезка in _handle_analysis_approved_flow (files_ok branch) ->
  set_issue_approved + log/Telegram/Plane-comment + advance_stage(
  finished_agent=None) — the SAME path a human Approved takes (approved-via-
  status -> analysis->architecture + mark_brd_review_ended). No duplicated
  transition logic; re-entrancy safe.
- autoDeploy: врезка in _handle_self_deploy_phase_a after advance to deploy +
  clear_state -> log/Telegram/Plane-comment + _handle_self_deploy_phase_b
  (INITIATED marker, Deploying, finalizer). Only the indicative human steps are
  skipped. BR-5 holds structurally: Phase A is reached only after the green edge
  sub-gates, so autoDeploy can never deploy a broken build.
- plane_sync: fetch_issue_labels (None on error != []), get_project_labels
  ({normalized_name->uuid}, TTL cache, ambiguity sentinel), set_issue_approved.
- config flags: auto_label_enabled (kill-switch), auto_approve_label/
  auto_deploy_label, auto_label_repos (empty -> self-hosting only),
  auto_label_states_ttl_s. applies() (local) checked FIRST; has_label (network)
  only when applies==True -> zero network / zero regression when disabled (AC-8).
- Fail-safe (never auto on doubt), transparency via log+Telegram+Plane+card,
  read-only auto_labels block in GET /queue.
- Tests TC-01..TC-26 across 7 modules; docs (CLAUDE.md, architecture README,
  CHANGELOG) updated in the same PR.

Refs: ORCH-089

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 12:31:24 +03:00
f7488e9536 architect(ET): auto-commit from architect run_id=442 2026-06-09 12:31:24 +03:00
0b5fede802 analyst(ET): auto-commit from analyst run_id=441 2026-06-09 12:31:24 +03:00
cc2f1885e8 docs: init ORCH-089 business request 2026-06-09 12:31:24 +03:00
c9be0eb4c9 Merge pull request 'docs(ORCH-089): staging gate SUCCESS — 15-staging-log.md' (#90) from chore/ORCH-089-staging-log into main 2026-06-09 12:30:52 +03:00
21bde85708 docs(ORCH-089): staging gate SUCCESS — 15-staging-log.md
All checks were successful
CI / test (pull_request) Successful in 29s
Staging check 8/10 PASS, exit 0. All REAL checks green; C9a/C9b
sandbox-infra waived (ORCH-061).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 12:30:34 +03:00
7d61c820a7 Merge pull request 'feat(serial-gate): per-repo serial gate + deferred branch cut + rollback-freeze (ORCH-088)' (#88) from feature/ORCH-088-orch-88-10-20 into main
Some checks failed
CI / test (push) Has been cancelled
2026-06-09 11:31:58 +03:00
deploy-finalizer
69f493fec5 deploy(ORCH-036): finalize SUCCESS for ORCH-088
All checks were successful
CI / test (push) Successful in 29s
2026-06-09 11:31:57 +03:00
dd4aaebe84 tester(ET): auto-commit from tester run_id=439
All checks were successful
CI / test (push) Successful in 34s
CI / test (pull_request) Successful in 32s
2026-06-09 11:24:48 +03:00
f645090e4d reviewer(ET): auto-commit from reviewer run_id=438 2026-06-09 11:24:48 +03:00
ee4773f5b0 feat(serial-gate): per-repo serial gate + deferred branch cut + rollback-freeze (ORCH-088)
Этап 1 (serial e2e) пакетного автономного режима. Новая задача репо не входит
в analysis (analyst-job не выбирается, ветка не режется), пока в репо есть более
ранняя незавершённая задача (FIFO, t2.id < jobs.task_id) ИЛИ репо заморожен.

- src/serial_gate.py — новый leaf (never-raise): build_claim_clause (fail-OPEN),
  is_repo_frozen (fail-CLOSED), set/clear_repo_freeze, serial_gate_applies, snapshot.
- src/db.py — идемпотентная миграция repo_freeze + serial_gate-фрагмент в claim_next_job.
- src/webhooks/plane.py + src/agents/launcher.py — отложенный срез ветки: start_pipeline
  не создаёт Gitea-ветку/docs для применимого репо; релокация в _materialize_deferred_branch
  на момент claim analyst-job (база = свежий origin/main с кодом предшественника, AC-6).
- src/stage_engine.py — post-deploy DEGRADED → durable per-repo freeze + Telegram-алерт.
- src/main.py — блок serial_gate в GET /queue + POST /serial-gate/unfreeze.
- src/config.py — serial_gate_enabled / serial_gate_repos / serial_gate_freeze_enabled.

FIFO-уточнение реализации (FR-2): ADR-001 D1 фиксировал t2.id != jobs.task_id; при !=
пакет одновременно созданных свежих задач взаимно блокировался бы (дедлок). t2.id <
jobs.task_id допускает самую раннюю задачу и сериализует остальные, сохраняя AC-1/R-7.

STAGE_TRANSITIONS / QG_CHECKS / check_* — без изменений. Аддитивно, под kill-switch,
never-raise, restart-safe; при выключенном флаге — нулевая регрессия (enduro не затронут).

Тесты: TC-01..TC-22 (test_serial_gate*.py + test_queue_endpoint.py); полный прогон 1114 зелёных.
Docs: README (serial gate / /queue / API / БД), CLAUDE.md, CHANGELOG.md, .env.example.

Refs: ORCH-088
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 11:24:48 +03:00
4597a8471d architect(ET): auto-commit from architect run_id=436 2026-06-09 11:24:48 +03:00
b478b38df5 analyst(ET): auto-commit from analyst run_id=435 2026-06-09 11:24:48 +03:00
99cafefba6 docs: init ORCH-088 business request 2026-06-09 11:24:48 +03:00
85cfce451f docs(ORCH-088): staging gate log — SUCCESS (8/10, C9a/C9b infra-waived)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 11:24:23 +03:00
a23d4c0971 Merge pull request 'fix(notifications): tracker orphan cleanup + effort + honest done-time (ORCH-087)' (#87) from feature/ORCH-087-orch-87-to-analyse-bump into main
Some checks failed
CI / test (push) Has been cancelled
2026-06-09 10:12:06 +03:00
deploy-finalizer
49fad5e458 deploy(ORCH-036): finalize SUCCESS for ORCH-087
All checks were successful
CI / test (push) Successful in 28s
CI / test (pull_request) Successful in 28s
2026-06-09 10:12:05 +03:00
d9bb8d5fe3 tester(ET): auto-commit from tester run_id=433
All checks were successful
CI / test (push) Successful in 32s
CI / test (pull_request) Successful in 30s
2026-06-09 10:06:17 +03:00
32cc965f84 reviewer(ET): auto-commit from reviewer run_id=432 2026-06-09 10:06:17 +03:00
81fc2df8a8 fix(launcher): runs log dir from settings, not hardcoded /app (CI fix)
test_spawn_stamps_resolved_effort упал в CI с PermissionError на '/app':
launcher._spawn хардкодил output_path='/app/data/runs/{run_id}.log' и
os.makedirs('/app/data/runs'). В контейнере /app есть, на CI-хосте
(act_runner hostexecutor) — нет, makedirs бросает -> красный CI.

Фикс корня (не только теста): базовый каталог per-run логов вынесен в
Settings.runs_dir (env ORCH_RUNS_DIR, дефолт '/app/data/runs' = прод 1:1).
Новый хелпер _run_log_path(run_id) — единый источник пути, использован в
_spawn + три прежних inline-строки логов/алертов. Тест monkeypatch-ит
settings.runs_dir на tmp_path -> окружение-независим (проверено прогоном
с принудительно недоступным /app). pytest tests/ -q: 1090 passed.

STAGE_TRANSITIONS/QG_CHECKS/схема БД не тронуты. Docs: README env-таблица,
CHANGELOG.

Refs: ORCH-087
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 10:06:17 +03:00
a7b27f2235 fix(notifications): tracker orphan cleanup + effort-in-line + honest done-time (ORCH-087)
Устраняет «замёрзшие» осиротевшие карточки live-трекера и доделывает строку
стадии/итоговое время.

G1 — зачистка сирот: аддитивный леджер tracker_messages(task_id, message_id,
created_at, deleted_at) + хелперы add/get_open/mark_deleted в src/db.py. bump
теперь удаляет ВСЕ незакрытые mid задачи (а не только скаляр
tasks.tracker_message_id, сохранён как BC-указатель). Новый mid в леджер только
при успешном send (BR-6); transient-delete остаётся для ретрая; «already
gone»/>48ч закрывается. Корень бага — скалярный учёт, терявший ссылку при
гонке/delete-fail+send-ok (ADR-001 G0).

G3 — deploy-цикл: ключ confirm_deploy в _LIVE_BRANCH_LABELS (без base-alias).

BR-EFF — эффорт в строке: колонка agent_runs.effort (_ensure_column,
идемпотентно), стамп фактического resolve_agent_effort в launcher._spawn в
момент запуска; рендер `· {model} · {effort}`, пустой → суффикс опускается.

BR-G5 — честное время: done-строка `⏱️ Агенты Σ · твоё {review~cap} · общее с
ожиданием {wall}` — три независимых подписанных метрики; кап
tracker_brd_review_cap_s (ORCH_TRACKER_BRD_REVIEW_CAP_S, дефолт 2ч, маркер ~).

Инварианты: STAGE_TRANSITIONS/QG_CHECKS/стадии без изменений; миграции
аддитивны/идемпотентны (enduro не трогается); never-raise,
disable_notification, plane_issue_link (ORCH-067), disable_web_page_preview
(ORCH-080) сохранены; src/reconciler.py не эродирован (ORCH-086 на месте).

Тесты: tests/test_notifications_orphans.py (TC-01..05 + never-raise),
tests/test_tracker_effort_time.py (TC-06/11..15 + confirm_deploy),
tests/test_launcher.py::TestEffortStamp (TC-09/10). Доки: CLAUDE.md
(§Нотификации), docs/architecture/README.md (Notifications), CHANGELOG.md.

Refs: ORCH-087

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 10:06:17 +03:00
36c7a68722 architect(ET): auto-commit from architect run_id=429 2026-06-09 10:06:17 +03:00
18fb2eb17d analyst(ET): auto-commit from analyst run_id=428 2026-06-09 10:06:17 +03:00
c86dc3ca95 docs: init ORCH-087 business request 2026-06-09 10:06:17 +03:00
77714aa318 docs(ORCH-087): staging gate SUCCESS — 15-staging-log.md
Staging check suite passed (8/10 PASS, exit 0). C9a/C9b waived as
sandbox-infra (ORCH-061); all REAL checks green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 10:05:45 +03:00
493b9be9c4 Merge pull request 'fix(reconciler): terminal-skip + state_uuid dedup on F-1 path (ORCH-086)' (#86) from feature/ORCH-086-orch-86-reconciler-telegram-et into main
Some checks failed
CI / test (push) Has been cancelled
2026-06-09 05:00:12 +03:00
deploy-finalizer
1b095282bf deploy(ORCH-036): finalize SUCCESS for ORCH-086
All checks were successful
CI / test (push) Successful in 26s
CI / test (pull_request) Successful in 26s
2026-06-09 05:00:11 +03:00
9c19588bcd tester(ET): auto-commit from tester run_id=424
All checks were successful
CI / test (push) Successful in 28s
CI / test (pull_request) Successful in 30s
2026-06-09 02:26:49 +03:00
fe3f1658ba reviewer(ET): auto-commit from reviewer run_id=423 2026-06-09 02:26:49 +03:00
595c382ac7 fix(reconciler): terminal-skip + state_uuid dedup on F-1 path
Закрывает F-1-пробел ORCH-068: терминал-исключение и in-memory dedup
(изначально только F-2) распространены на gate-side путь реконсилятора,
устраняя ложное «🔧 reconciler: ET-002 done разблокирована (потерян
webhook)» (особенно после рестарта).

- D1: новый _resolve_issue_status — один сетевой резолв Plane-статуса
  задачи за тик (states, groups, state_uuid) после дешёвых локальных
  гардов; never-raise -> ({}, {}, None) при сбое.
- D2: безусловный терминал-скип ДО Guard 2 (группа Plane completed/
  cancelled, fallback на логические ключи done/cancelled, либо стадия в
  БД орка ∈ {done, cancelled}); skipped_terminal_total++, не подчинён
  reconcile_skip_blocked_enabled.
- D3: _is_blocked_or_needs_input переиспользует резолв D1 (опц. аргументы,
  _UNSET -> самостоятельный резолв для прямых/легаси-вызовов; 1:1).
- D4: вызов _note_unblock на F-1 теперь передаёт state_uuid -> dedup
  работает на обоих путях (deduped_total++ на повторе).

Анти-регресс: легитимный unblock не-терминальной застрявшей задачи
по-прежнему advance + один Telegram. STAGE_TRANSITIONS / QG_CHECKS /
схема БД / сигнатуры advance_*/_note_unblock / форма status() / новые
флаги — без изменений; never-raise сохранён.

Тесты: tests/test_reconciler.py TC-86-01..09/11,
tests/test_reconciler_plane.py TC-86-10. Полный прогон зелёный (1069).

Refs: ORCH-086
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 02:26:49 +03:00
aa488edddf architect(ET): auto-commit from architect run_id=421 2026-06-09 02:26:49 +03:00
f2161451a0 analyst(ET): auto-commit from analyst run_id=419 2026-06-09 02:26:49 +03:00
0e7d608fc0 docs: init ORCH-086 business request 2026-06-09 02:26:49 +03:00
fb9390e216 docs(ORCH-086): staging gate log — SUCCESS (infra-waived C9a/C9b)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 02:26:25 +03:00
92817889c4 Merge pull request 'fix: disable Telegram link-preview in tracker notifications (ORCH-080)' (#85) from feature/ORCH-080-orch-52g-telegram-link-preview into main
Some checks failed
CI / test (push) Has been cancelled
2026-06-09 01:38:16 +03:00
deploy-finalizer
baf7860822 deploy(ORCH-036): finalize SUCCESS for ORCH-080
All checks were successful
CI / test (push) Successful in 24s
CI / test (pull_request) Successful in 24s
2026-06-09 01:38:15 +03:00
2cf40c1af9 tester(ET): auto-commit from tester run_id=417
All checks were successful
CI / test (push) Successful in 28s
CI / test (pull_request) Successful in 25s
2026-06-09 01:32:53 +03:00
44ef0bb570 reviewer(ET): auto-commit from reviewer run_id=416 2026-06-09 01:32:53 +03:00
d826eacfcf fix: disable Telegram link-preview in tracker notifications (ORCH-080)
Add "disable_web_page_preview": True to the JSON payload of both
low-level Telegram primitives — send_telegram (POST /sendMessage) and
edit_telegram (POST /editMessageText). Telegram no longer expands the
Plane "Modern project management" link-preview banner under every
tracker card (bump/edit) and notify/alert message, which the default
bump mode (ORCH-067) was duplicating on each transition.

Single-point fix at the primitive level — all consumers
(update_task_tracker, notify_approve_requested, notify_error, stage
alerts from launcher/stage_engine) inherit it without code changes.
parse_mode: HTML is preserved so the ORCH-NNN issue link stays
clickable; disable_notification, bump/edit logic, the one-card-per-task
invariant, return contracts and never-raise are untouched. Unconditional,
no kill-switch (ADR-001).

Tests: tests/test_link_preview_disabled.py (TC-01..06). Docs: CHANGELOG,
CLAUDE.md, docs/architecture/README.md (Notifications component).

Refs: ORCH-080
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 01:32:53 +03:00
a482b36dae architect(ET): auto-commit from architect run_id=414 2026-06-09 01:32:53 +03:00
f452626bb8 analyst(ET): auto-commit from analyst run_id=413 2026-06-09 01:32:53 +03:00
b46fc6e51b docs: init ORCH-080 business request 2026-06-09 01:32:53 +03:00
140827f4da docs(ORCH-080): merge staging gate log (staging_status: SUCCESS)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 01:32:32 +03:00
fc29ba76ec Merge pull request 'feat(merge-verify): guarantee idempotent open code-PR before merge_pr (ORCH-082)' (#82) from feature/ORCH-082-orch-81-pr-merge-verify-hold into main
Some checks failed
CI / test (push) Has been cancelled
2026-06-09 01:06:25 +03:00
deploy-finalizer
9834dae108 deploy(ORCH-036): finalize SUCCESS for ORCH-082
All checks were successful
CI / test (push) Successful in 24s
CI / test (pull_request) Successful in 24s
2026-06-09 01:01:56 +03:00
039322001a tester(ET): auto-commit from tester run_id=411
All checks were successful
CI / test (push) Successful in 27s
CI / test (pull_request) Successful in 25s
2026-06-09 00:57:08 +03:00
1997376eb5 reviewer(ET): auto-commit from reviewer run_id=410 2026-06-09 00:57:08 +03:00
0ab6a33ef5 feat(merge-verify): guarantee idempotent open code-PR before merge_pr (ORCH-082)
Close the missing invariant "by merge-verify time the branch has an open
code-PR". The pipeline created a PR only on the developer path with a fresh
worktree commit (launcher._ensure_pr), so a branch (e.g. after a manual main
restore) could reach the deploy->done merge-verify under-gate PR-less ->
merge_pr returned "no open PR" -> a FALSE HOLD (ORCH-074 incident).

- merge_gate.ensure_open_pr(repo, branch) -> (status, detail): idempotent
  leaf-actor (never-raise). GET open PRs filtered head==branch AND base==main
  (identical to merge_pr/ORCH-073 FR-3 — auto docs-PR is not a code-PR) ->
  existed; else POST -> created; 409/422 race -> re-GET -> existed (no dup);
  any other error -> failed.
- stage_engine._handle_merge_verify: врезка after validated_revision and
  BEFORE merge_pr. created|existed -> proceed; failed -> honest HOLD via new
  _hold_pr_create_failed (note "pr-create-failed-hold", text distinguishable
  from the not-merged HOLD; task stays on deploy, NO rollback).
- launcher._ensure_pr delegated to ensure_open_pr (single PR-creation path,
  shared head==branch & base==main filter); the developer-only trigger is
  unchanged.
- ORCH-073 protection untouched & authoritative: merge is confirmed ONLY by
  verify_merged_to_main (SHA-in-main) + check_main_regression. Real un-merged
  code still HOLDs.
- Kill-switch ORCH_MERGE_VERIFY_AUTOCREATE_PR_ENABLED (default true); scope =
  merge_verify_applies (self-hosting / merge_verify_repos); non-self -> no-op;
  false -> ORCH-074 behaviour 1:1. No DB migration; main never push/force-push.
- Append ORCH-082 marker to MAIN_REGRESSION_MARKERS (append-only convention).
- conftest defaults the autocreate flag OFF (mirrors merge_verify_enabled) so
  unrelated deploy->done tests stay 1:1 (no network).

Tests: tests/test_orch082_ensure_pr.py (TC-01..05),
tests/test_orch082_merge_verify_autocreate.py (TC-06..12). Docs: README
merge-verify block (ORCH-082), CHANGELOG, .env.example.

Refs: ORCH-082

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 00:57:08 +03:00
74269b467c architect(ET): auto-commit from architect run_id=408 2026-06-09 00:57:08 +03:00
781f9df26c analyst(ET): auto-commit from analyst run_id=407 2026-06-09 00:57:08 +03:00
c0715ad55b docs: init ORCH-082 business request 2026-06-09 00:57:08 +03:00
7ee528ad7b Merge pull request 'docs(ORCH-082): staging gate log — SUCCESS' (#83) from docs/ORCH-082-staging-log into main 2026-06-09 00:56:37 +03:00
2861dea613 docs(ORCH-082): staging gate log — SUCCESS (8/10, C9a/C9b infra-waived)
All checks were successful
CI / test (pull_request) Successful in 24s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 00:56:25 +03:00
50434fc2b1 Merge pull request 'fix(effort): per-role floor for --effort + developer→xhigh (ORCH-081)' (#80) from feature/ORCH-081-orch-52h-env-config into main
Some checks failed
CI / test (push) Has been cancelled
2026-06-08 22:57:11 +03:00
deploy-finalizer
6eb9992585 deploy(ORCH-036): finalize SUCCESS for ORCH-081
All checks were successful
CI / test (push) Successful in 24s
CI / test (pull_request) Successful in 24s
2026-06-08 22:55:37 +03:00
e9b23d3c04 tester(ET): auto-commit from tester run_id=403
All checks were successful
CI / test (push) Successful in 26s
CI / test (pull_request) Successful in 27s
2026-06-08 22:50:47 +03:00
e3c3292ec7 reviewer(ET): auto-commit from reviewer run_id=402 2026-06-08 22:50:47 +03:00
1ada41f272 fix(effort): per-role floor for --effort resolution + developer→xhigh
resolve_agent_effort returned '' for all agents in prod because empty
ORCH_AGENT_EFFORT_*= env vars clobber pydantic class-defaults, leaving no
non-empty floor to fall back to -> --effort never reached the Claude CLI.

Add a level-4 per-role floor in resolve_agent_effort (src/agents/launcher.py):
_agent_effort_floor reads the declared class-default of agent_effort_<agent>
(model_fields[...].default), which a present-but-empty env cannot override.
Floor applies only when levels 1-3 are empty and BEFORE validation, so a typo
(non-empty) still drops to '' (never-break ORCH-41) and explicit env/override
still wins (priority preserved). config.py: agent_effort_developer high->xhigh
(single source of truth; floor follows automatically).

Refs: ORCH-081

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-08 22:50:47 +03:00
62b4d1f7d1 architect(ET): auto-commit from architect run_id=400 2026-06-08 22:50:47 +03:00
c5007e6c90 analyst(ET): auto-commit from analyst run_id=399 2026-06-08 22:50:47 +03:00
10510ac48c docs: init ORCH-081 business request 2026-06-08 22:50:47 +03:00
8ccd17e199 Merge pull request 'docs(ORCH-081): staging gate log — SUCCESS (8/10, C9a/C9b infra-waived)' (#81) from docs/ORCH-081-staging-log into main 2026-06-08 22:50:26 +03:00
30d9effea1 docs(ORCH-081): staging gate log — SUCCESS (8/10, C9a/C9b infra-waived)
All checks were successful
CI / test (pull_request) Successful in 28s
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-08 22:50:14 +03:00
a091a2d999 Merge pull request 'feat(launcher): ORCH-074 drop dead frontmatter model + validate model name (never-break)' (#79) from feature/ORCH-074-orch-52a-frontmatter-routing-e into main
Some checks failed
CI / test (push) Has been cancelled
2026-06-08 22:11:20 +03:00
deploy-finalizer
b371b6d940 deploy(ORCH-036): finalize SUCCESS for ORCH-074
All checks were successful
CI / test (push) Successful in 27s
CI / test (pull_request) Successful in 24s
2026-06-08 22:07:38 +03:00
ea094f5922 tester(ET): auto-commit from tester run_id=397
All checks were successful
CI / test (push) Successful in 29s
2026-06-08 22:00:54 +03:00
17258fb69e reviewer(ET): auto-commit from reviewer run_id=396 2026-06-08 22:00:54 +03:00
0873803faa feat(launcher): drop dead frontmatter model + validate model name (never-break)
G1: remove the dead `model:` line from all 6 .openclaw/agents/*.md prompts —
launcher never read it; config (agent_model_*) is the single source of truth.

G2: add is_valid_model helper (format check ^claude-…$) applied inside
resolve_agent_model's resolution cascade and at the inline --fallback-model
read in _spawn. An invalid name is logged and skipped to the next valid level
(in the limit: no --model flag), never passed to the CLI, never raises. Format
check chosen over an allowlist for forward-compatibility (ADR-001).

G3 (routing) and G4 (fallback) intentionally NOT enabled — all agents stay on
claude-opus-4-8; agent_fallback_model stays "".

Docs (golden source) updated in the same change: README model/effort table +
validation, CLAUDE.md, .env.example (ORCH_AGENT_MODEL_*/EFFORT_*/FALLBACK_MODEL),
CHANGELOG. Tests: test_agent_frontmatter_no_model.py (G1), extended
test_resolve_agent_model.py (G2 never-break).

Refs: ORCH-074
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-08 22:00:54 +03:00
0c240198e4 architect(ET): auto-commit from architect run_id=394 2026-06-08 22:00:54 +03:00
1e1811a4bc analyst(ET): auto-commit from analyst run_id=393 2026-06-08 22:00:54 +03:00
e89f7c7a11 analyst(ET): auto-commit from analyst run_id=392 2026-06-08 22:00:54 +03:00
0f82ebc1a7 docs: init ORCH-074 business request 2026-06-08 22:00:54 +03:00
d04be97c0e docs(ORCH-074): staging gate log — SUCCESS (8/10, C9a/C9b infra-waived)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-08 22:00:34 +03:00
b0e517c76a Merge pull request 'ORCH-026: task dependencies (B waits for A) + single-repo merge serialization' (#78) from feature/ORCH-026-b-a into main
Some checks failed
CI / test (push) Has been cancelled
2026-06-08 19:47:58 +03:00
deploy-finalizer
662d2d6434 deploy(ORCH-036): finalize SUCCESS for ORCH-026
All checks were successful
CI / test (push) Successful in 26s
2026-06-08 19:47:57 +03:00
deploy-finalizer
90a5cae8e6 deploy(ORCH-036): finalize FAILED for ORCH-026
All checks were successful
CI / test (push) Successful in 23s
CI / test (pull_request) Successful in 23s
2026-06-08 19:33:57 +03:00
deploy-finalizer
1d928dab57 deploy(ORCH-036): finalize FAILED for ORCH-026
All checks were successful
CI / test (push) Successful in 23s
CI / test (pull_request) Successful in 22s
2026-06-08 19:28:41 +03:00
9800dc89e3 tester(ET): auto-commit from tester run_id=390
All checks were successful
CI / test (push) Successful in 27s
CI / test (pull_request) Successful in 27s
2026-06-08 19:17:44 +03:00
5b80f8facb reviewer(ET): auto-commit from reviewer run_id=389 2026-06-08 19:17:44 +03:00
a74379f657 feat(ORCH-026): task dependencies (B waits for A) + single-repo merge serialization
Level A — merge/deploy serialization within one repo: reuse the existing
ORCH-043/065 merge-lease (no new mechanism); the only new logic is an
unconditional pre-merge rebase in check_branch_mergeable — under the held
lease, auto_rebase_onto_main is ALWAYS called when premerge_rebase_always
(default True), not just when the branch is behind. No-op on an up-to-date
branch (rebase keeps HEAD, force-with-lease -> "Everything up-to-date", CI
not triggered). Kill-switch off -> ORCH-043 behaviour 1:1.

Level B — declarative task dependencies: additive job_deps table
(CREATE ... IF NOT EXISTS, no live-DB migration); claim_next_job gate
(NOT EXISTS) defers a job whose depends-on tasks are not yet 'done' without
occupying a max_concurrency slot; inert on empty job_deps -> zero regression.
New leaf src/task_deps.py (never-raise): is_task_ready (fail-open), DFS cycle
detection + Blocked/alert, declare/ingest_plane_relations (db source never
hits the network on the hot path), snapshot. Telegram waiting-line, /queue
observability, reconciler skip + cycle backstop, reaper untouched.

Invariants unchanged: STAGE_TRANSITIONS, QG_CHECKS registry (dep gate is a
claim_next_job врезка, not a registered QG), DB schema of existing tables,
HTTP endpoints; non-self repos remain a no-op on empty deps/scope.

Flags: ORCH_PREMERGE_REBASE_ALWAYS, ORCH_TASK_DEPS_ENABLED, ORCH_TASK_DEPS_SOURCE.
Docs: docs/architecture/README.md, CLAUDE.md, .env.example, CHANGELOG.md,
adr-0015. Tests: tests/test_orch026_*.py (64 tests); full suite 991 green.

Refs: ORCH-026

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-08 19:17:44 +03:00
9019e12d98 architect(ET): auto-commit from architect run_id=387 2026-06-08 19:17:44 +03:00
518d7d18c8 analyst(ET): auto-commit from analyst run_id=386 2026-06-08 19:17:44 +03:00
520bcafa73 docs: init ORCH-026 business request 2026-06-08 19:17:44 +03:00
9f7b6edb6d docs(ORCH-026): staging gate log — SUCCESS (8/10, C9a/C9b infra-waived)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-08 19:17:18 +03:00
1c3ecb973e Merge pull request 'ORCH-073: SHA-in-main merge-verify + main regression guard' (#77) from feature/ORCH-073-crit-main-orch-067-069 into main
Some checks failed
CI / test (push) Has been cancelled
2026-06-08 16:54:01 +03:00
deploy-finalizer
1b45fa0008 deploy(ORCH-036): finalize SUCCESS for ORCH-073
All checks were successful
CI / test (push) Successful in 21s
2026-06-08 16:54:00 +03:00
1f0929838a tester(ET): auto-commit from tester run_id=384
All checks were successful
CI / test (push) Successful in 26s
CI / test (pull_request) Successful in 21s
2026-06-08 16:30:46 +03:00
7deb151ce5 reviewer(ET): auto-commit from reviewer run_id=383 2026-06-08 16:30:46 +03:00
aff334e82b fix(merge-gate): SHA-in-main as sole merge-verify criterion + main regression guard
Root-cause fix for main erosion (phantom merge): code of ORCH-067/069 reached
`done` while absent from origin/main (only their auto docs-PRs landed).

- FR-1: verify_merged_to_main confirms merge ONLY by `git merge-base
  --is-ancestor <validated_sha> origin/main`; the OR-branch pr_already_merged is
  removed (a merged PR no longer confirms). Empty SHA / git error -> False.
- FR-2: pr_already_merged demoted to merge_pr idempotency-guard; counts a PR only
  when merged & head.ref==<branch> & base.ref=="main" (explicit in-loop filter).
- FR-3: merge_pr selects the open code-PR by head==<branch> AND base==main.
- FR-5: new deterministic check_main_regression in _handle_merge_verify (after
  confirmed SHA-in-main, before done) verifies MAIN_REGRESSION_MARKERS still in
  origin/main; deterministic count==0 -> alert "main regressed" + HOLD (NOT done,
  no rollback); git error of the grep -> fail-open. Kill-switch
  ORCH_REGRESSION_GUARD_ENABLED; non-self -> no-op.
- FR-4: root .gitattributes `CHANGELOG.md merge=union` so Unreleased edits
  auto-merge on rebase without conflict (branch not rolled back).

Invariants unchanged (STAGE_TRANSITIONS, QG_CHECKS, deploy-status, merge-gate,
image-freshness, DB schema, external HTTP API); non-self repos no-op (INV-5);
never-raise (INV-1); merge only via Gitea PR-API (INV-2).

Docs: CHANGELOG, .env.example (README/ADR updated by architect). Tests:
tests/test_orch073_*.py (TC-01..18); existing merge-gate tests updated for the
new code-PR filter.

Refs: ORCH-073

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-08 16:30:46 +03:00
fa9b96545c architect(ET): auto-commit from architect run_id=381 2026-06-08 16:30:46 +03:00
319b23b4fc analyst(ET): auto-commit from analyst run_id=380 2026-06-08 16:30:46 +03:00
e54d1fc4ac docs: init ORCH-073 business request 2026-06-08 16:30:46 +03:00
77abfb399c docs(ORCH-073): staging gate log — SUCCESS (8/10, C9a/C9b infra-waived)
Canonical run inside orchestrator-staging (ORCH-048): exit 0, all REAL
checks green; C9a/C9b waived as known sandbox-infra (ORCH-061).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-08 16:30:19 +03:00
05bd169b14 Merge pull request 'restore(main): re-merge ORCH-067 + ORCH-069 (ORCH-073)' (#76) from restore/orch-6769-2026-06-08 into main
Some checks failed
CI / test (push) Has been cancelled
2026-06-08 15:04:47 +03:00
stream
183e6d68bc restore: re-merge ORCH-069 qg0_title_max
All checks were successful
CI / test (pull_request) Successful in 21s
# Conflicts:
#	CHANGELOG.md
2026-06-08 14:58:30 +03:00
stream
befa2979ec restore: re-merge ORCH-067 tracker bump+статусы+ссылки 2026-06-08 14:58:03 +03:00
deploy-finalizer
d33e0ded2e deploy(ORCH-036): finalize SUCCESS for ORCH-069
All checks were successful
CI / test (push) Successful in 23s
CI / test (pull_request) Successful in 20s
2026-06-08 11:44:38 +00:00
de70ee811d tester(ET): auto-commit from tester run_id=378
All checks were successful
CI / test (push) Successful in 20s
CI / test (pull_request) Successful in 20s
2026-06-08 11:30:17 +00:00
post-deploy-monitor
41da03470a docs(ORCH-021): post-deploy HEALTHY/NONE for ORCH-067
All checks were successful
CI / test (push) Successful in 22s
CI / test (pull_request) Successful in 21s
2026-06-08 11:28:18 +00:00
e1055861b5 reviewer(ET): auto-commit from reviewer run_id=377
All checks were successful
CI / test (push) Successful in 20s
CI / test (pull_request) Successful in 22s
2026-06-08 11:28:16 +00:00
2e84813c13 developer(ET): auto-commit from developer run_id=376
All checks were successful
CI / test (push) Successful in 20s
CI / test (pull_request) Successful in 20s
2026-06-08 11:25:09 +00:00
18f887c886 tester(ET): auto-commit from tester run_id=374
Some checks failed
CI / test (push) Has been cancelled
CI / test (pull_request) Successful in 21s
2026-06-08 11:24:01 +00:00
37ef58f21f reviewer(ET): auto-commit from reviewer run_id=373 2026-06-08 11:24:01 +00:00
0b9ae514c9 docs(qg0): add ORCH_QG0_TITLE_MAX to README config table
Reviewer P1 fix (attempt 2/3): новый параметр отсутствовал в таблице
«Все переменные с префиксом ORCH_», делая её неконсистентной заголовку.
Закрывает AC-6 / ТЗ §9.

Refs: ORCH-069
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-08 11:24:01 +00:00
c56672aabf reviewer(ET): auto-commit from reviewer run_id=371 2026-06-08 11:24:01 +00:00
0ed05417e6 feat(qg0): configurable QG-0 title limit via ORCH_QG0_TITLE_MAX (default 200)
Replace the hardcoded `len(name) > 80` cap in the QG-0 entry validation
(_qg0_errors) with a configurable Settings.qg0_title_max (env
ORCH_QG0_TITLE_MAX, default 200). The 80-char cap was a hygiene limit, not
structural, so valid 81-200 char titles were rejected without a business
reason. The limit is read dynamically per call and the error text interpolates
the active value.

Graceful degradation (AC-3, self-hosting safety): an empty/non-numeric env
value no longer crashes the process on startup. A field_validator(mode="before")
intercepts the raw env before int-parsing and falls back to 200 (never raises),
suppressing pydantic ValidationError.

Additive and backward-compatible (default 200 > old 80). Invariants unchanged:
STAGE_TRANSITIONS, QG_CHECKS registry, DB schema, slug [:30], lower limits,
soft-QG-0 warning path, API.

Refs: ORCH-069

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-08 11:24:01 +00:00
7d99782673 architect(ET): auto-commit from architect run_id=369 2026-06-08 11:23:26 +00:00
59603f6e92 analyst(ET): auto-commit from analyst run_id=350 2026-06-08 11:23:26 +00:00
d5f11e5caa docs: init ORCH-069 business request 2026-06-08 11:23:26 +00:00
affbb259a1 Merge pull request 'docs(ORCH-069): staging gate log — SUCCESS (8/10, C9a/C9b infra-waived)' (#75) from docs/ORCH-069-staging-log into main 2026-06-08 14:22:30 +03:00
8149eb7769 docs(ORCH-069): staging gate log — SUCCESS (8/10, C9a/C9b infra-waived)
All checks were successful
CI / test (pull_request) Successful in 21s
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-08 11:22:22 +00:00
deploy-finalizer
9979eec168 deploy(ORCH-036): finalize SUCCESS for ORCH-067
All checks were successful
CI / test (push) Successful in 22s
CI / test (pull_request) Successful in 22s
2026-06-08 10:52:45 +00:00
c991b9de1a tester(ET): auto-commit from tester run_id=367
All checks were successful
CI / test (push) Successful in 27s
CI / test (pull_request) Successful in 24s
2026-06-08 10:34:33 +00:00
3d7d751b7a reviewer(ET): auto-commit from reviewer run_id=366 2026-06-08 10:34:33 +00:00
f330a580c4 docs(tracker): update CHANGELOG, CLAUDE.md, .env.example for ORCH-067
Закрывает P0/P1 ревью (attempt 2/3): документация = golden source.
- CHANGELOG.md: запись ORCH-067 в [Unreleased] (bump-дефолт, статус-строка
  карточки по модели ORCH-066, кликабельный номер задачи, новые флаги).
- CLAUDE.md: раздел «Нотификации / Telegram live-tracker» (ТЗ §5).
- .env.example: ORCH_TRACKER_MODE=bump (синхрон с новым дефолтом) +
  ORCH_TRACKER_LIVE_STATUS / _TTL_S / _TIMEOUT_S.

Refs: ORCH-067

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-08 10:34:33 +00:00
896ecf6acb reviewer(ET): auto-commit from reviewer run_id=364 2026-06-08 10:34:33 +00:00
096c452230 developer(ET): auto-commit from developer run_id=363 2026-06-08 10:34:33 +00:00
9f176036f1 architect(ET): auto-commit from architect run_id=362 2026-06-08 10:34:33 +00:00
3e4191050f analyst(ET): auto-commit from analyst run_id=361 2026-06-08 10:34:33 +00:00
38e329f6f7 docs: init ORCH-067 business request 2026-06-08 10:34:33 +00:00
58d6c433d1 docs(ORCH-067): staging gate verdict SUCCESS
Merge 15-staging-log.md artifact into main (staging gate passed, exit 0).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-08 10:34:16 +00:00
52ca882e5b Merge pull request 'feat: ORCH-071-crit-bug-merge-main' (#72) from feature/ORCH-071-crit-bug-merge-main into main
Some checks failed
CI / test (push) Has been cancelled
2026-06-08 12:02:47 +03:00
d49e88cf3f tester(ET): auto-commit from tester run_id=359
All checks were successful
CI / test (push) Successful in 23s
CI / test (pull_request) Successful in 24s
2026-06-08 08:45:31 +00:00
e7a5b50f97 reviewer(ET): auto-commit from reviewer run_id=358 2026-06-08 08:45:31 +00:00
034343ec5d docs(changelog): add ORCH-071 merge-verify gate entry
Add CHANGELOG entry for the phantom-merge fix (merge-verify sub-gate,
deterministic merge actor, post-deploy verification, kill-switch).
Addresses P0 blocker from reviewer (attempt 2/3): docs = golden source
per CLAUDE.md §2/§6 and AC-5.

Refs: ORCH-071

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-08 08:45:31 +00:00
cc87beb2b4 reviewer(ET): auto-commit from reviewer run_id=356 2026-06-08 08:45:31 +00:00
fb25e9a0cf developer(ET): auto-commit from developer run_id=355 2026-06-08 08:45:31 +00:00
2824fd8543 architect(ET): auto-commit from architect run_id=354 2026-06-08 08:45:31 +00:00
c26a6b637c analyst(ET): auto-commit from analyst run_id=353 2026-06-08 08:45:31 +00:00
dd5fe619d5 docs: init ORCH-071 business request 2026-06-08 08:45:31 +00:00
f6b5671267 docs(ORCH-071): staging gate log — SUCCESS (8/10, C9a/C9b infra-waived)
Staging check suite passed against orchestrator-staging (8501), exit 0.
All REAL pipeline checks green; sandbox-infra C9a/C9b waived per ORCH-061.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-08 08:45:01 +00:00
49461238f1 Merge pull request 'restore(main): долить фантомные ORCH-022/059/066/068 (4 потерянных PR)' (#71) from integ/restore-main-2026-06-08 into main
Some checks failed
CI / test (push) Has been cancelled
2026-06-08 09:57:18 +03:00
stream
c90c01b919 fix(tests): integ fixtures — to_analyse always present (066), full status keys, security_gate registered (022)
All checks were successful
CI / test (pull_request) Successful in 20s
2026-06-08 06:41:52 +00:00
stream
2ec6873e33 integ: merge ORCH-068 reconciler livelock fix
# Conflicts:
#	docs/architecture/README.md
#	src/reconciler.py
2026-06-08 06:36:29 +00:00
stream
cac6539698 integ: merge ORCH-066 plane status model
# Conflicts:
#	CHANGELOG.md
#	docs/architecture/README.md
#	src/plane_sync.py
#	src/webhooks/plane.py
2026-06-08 06:34:37 +00:00
stream
af7472df05 integ: merge ORCH-059 confirm-deploy
# Conflicts:
#	CHANGELOG.md
#	docs/architecture/README.md
2026-06-08 06:32:53 +00:00
stream
995ba0af71 integ: merge ORCH-022 security/secret-scanning 2026-06-08 06:32:13 +00:00
772ccab013 docs(history): CRITICAL postmortem — phantom merge (deploy without main-merge), see ORCH-071 2026-06-08 09:20:22 +03:00
post-deploy-monitor
06271b0bfb docs(ORCH-021): post-deploy HEALTHY/NONE for ORCH-068
All checks were successful
CI / test (push) Successful in 17s
CI / test (pull_request) Successful in 18s
2026-06-08 05:49:27 +00:00
101bd1c512 docs(history): lesson — Confirm Deploy dead trigger (ORCH-066 regression, see ORCH-070) 2026-06-08 08:38:30 +03:00
deploy-finalizer
aa4161fc78 deploy(ORCH-036): finalize SUCCESS for ORCH-068
All checks were successful
CI / test (push) Successful in 19s
CI / test (pull_request) Successful in 20s
2026-06-08 05:34:23 +00:00
6bbd530caa tester(ET): auto-commit from tester run_id=351
All checks were successful
CI / test (push) Successful in 19s
CI / test (pull_request) Successful in 21s
2026-06-08 05:18:46 +00:00
4b03f213f7 reviewer(ET): auto-commit from reviewer run_id=349 2026-06-08 05:18:46 +00:00
1d72c44587 fix(reconciler): stop F-2 livelock spam on synced terminal tasks + cache TTL
Reconciler F-2 spammed Telegram "<wi> разблокирована" every ~120s for a
fully-synchronized Done task (incident ET-002, 191+ msgs/night) after the
ORCH-066 Plane status model merge. Two stacked defects (defense in depth):

- D1 (selection): actionable states were told apart by bare UUID, so a Done
  issue aliased onto the approved UUID entered the approved branch. Now
  terminal states are excluded by Plane state GROUP (completed/cancelled),
  a project-independent discriminator robust to UUID aliasing; per-issue
  check with a logical-key fallback when the group is unavailable.
  get_project_states caches {uuid -> group} from the same /states/ fetch;
  new sibling accessor get_project_state_groups.
- D2 (notification): _note_unblock fired unconditionally after _dispatch.
  Now it only fires on a confirmed state change (stage before/after _dispatch;
  task-appears for the start case) — handlers' contracts untouched.
- TR-3: in-memory dedup guard {issue_id -> last unblocked state} as a backstop.
- TR-4: _STATES_CACHE lived for the whole process lifetime, so a new Plane
  status was invisible without a restart. Added TTL ORCH_PLANE_STATES_TTL_S
  (default 300s; 0 = previous lifetime cache) reusing reload_project_states();
  a failed refresh serves the stale-but-correct set, not enduro defaults.

STAGE_TRANSITIONS / QG_CHECKS / DB schema / handle_* contracts / F-1 / F-3
unchanged; never-raise preserved; self-hosting tick never restarts prod.
Observability: skipped_terminal_total / deduped_total in /queue reconcile block.

Tests: tests/test_reconciler_plane.py (TC-01..TC-10),
tests/test_plane_states_cache.py (TC-11/TC-12).

Refs: ORCH-068

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-08 05:18:46 +00:00
0605309602 architect(ET): auto-commit from architect run_id=347 2026-06-08 05:18:46 +00:00
044894cbe9 analyst(ET): auto-commit from analyst run_id=346 2026-06-08 05:18:46 +00:00
cb11137a77 docs: init ORCH-068 business request 2026-06-08 05:18:46 +00:00
48b54051e5 docs(ORCH-068): add staging gate log (staging_status: SUCCESS) 2026-06-08 05:18:24 +00:00
post-deploy-monitor
72d662ae88 docs(ORCH-021): post-deploy HEALTHY/NONE for ORCH-066
All checks were successful
CI / test (push) Successful in 19s
CI / test (pull_request) Successful in 19s
2026-06-07 22:33:36 +00:00
deploy-finalizer
348cf8c164 deploy(ORCH-036): finalize SUCCESS for ORCH-066
All checks were successful
CI / test (push) Successful in 19s
CI / test (pull_request) Successful in 19s
2026-06-07 22:18:32 +00:00
bc2347abd3 tester(ET): auto-commit from tester run_id=343
All checks were successful
CI / test (push) Successful in 21s
CI / test (pull_request) Successful in 18s
2026-06-07 22:02:45 +00:00
62c1fe3461 reviewer(ET): auto-commit from reviewer run_id=342 2026-06-07 22:02:45 +00:00
0dfddf93f0 feat(plane): осмысленная статусная модель Plane (слой B — индикация)
Приводит статусы доски Plane к смыслу стадий конвейера, сохраняя
инвариант «статус — индикация, а не управление». Меняется только слой B
(отображение: src/plane_sync.py + точки выставления статуса в
stage_engine.py/webhooks/plane.py/reconciler.py); слой A — машина стадий
src/stages.py::STAGE_TRANSITIONS — остаётся байт-в-байт неизменным (AC-21).

- 6 новых логических ключей статуса (to_analyse, analysis, code_review,
  awaiting_deploy, deploying, monitoring) + сеттеры и диспетчер
  set_issue_stage_state.
- Project-relative alias-fallback (BR-12): новый ключ деградирует на
  базовый UUID того же проекта → нулевая регрессия для enduro-trails.
- Самодеплой (ORCH-036) индицирует фазы: Awaiting Deploy / Deploying;
  terminal-sync для self-hosting → Monitoring after Deploy, для прочих →
  терминальный Done.
- Post-deploy монитор (ORCH-021): HEALTHY → Done, DEGRADED → Blocked
  (только индикация; self-hosting ALERT_ONLY, прод не трогается, BR-5).
- Reconciler: триггер старта/резюма на To Analyse; Guard 2 учитывает
  новые активные ожидания без расширения skip-set на алиасах.
- never-raise контракт сеттеров и резолвера состояний сохранён.
- Раскатка — созданием статусов в Plane оператором, без kill-switch.

Инварианты не менялись: STAGE_TRANSITIONS, QG_CHECKS (12 чеков),
check_deploy_status, exit-код-контракт хука, merge-gate, схема БД.

ADR: docs/work-items/ORCH-066/06-adr/ADR-001-plane-status-model.md
Тесты: test_plane_status_model, test_plane_to_analyse_resume,
test_plane_status_failclosed + TC в существующих наборах. 774 passed.

Refs: ORCH-066

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-07 22:02:45 +00:00
22d3b77426 architect(ET): auto-commit from architect run_id=340 2026-06-07 22:02:45 +00:00
4a06537afd analyst(ET): auto-commit from analyst run_id=339 2026-06-07 22:02:45 +00:00
b6c0e11e4d docs: init ORCH-066 business request 2026-06-07 22:02:45 +00:00
3fb3d15cb4 docs(ORCH-066): add staging gate log (staging_status: SUCCESS)
Some checks failed
CI / test (push) Has been cancelled
Staging check suite passed (8/10, exit 0): all REAL checks green;
C9a/C9b waived as known sandbox-infra (ORCH-061).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-07 22:02:33 +00:00
post-deploy-monitor
9f4d79baee docs(ORCH-021): post-deploy HEALTHY/NONE for ORCH-059
All checks were successful
CI / test (push) Successful in 18s
CI / test (pull_request) Successful in 19s
2026-06-07 19:44:39 +00:00
deploy-finalizer
7cdef6d377 deploy(ORCH-036): finalize SUCCESS for ORCH-059
All checks were successful
CI / test (push) Successful in 18s
CI / test (pull_request) Successful in 18s
2026-06-07 19:29:34 +00:00
post-deploy-monitor
0cbb7ef0bb docs(ORCH-021): post-deploy HEALTHY/NONE for ORCH-022
All checks were successful
CI / test (push) Successful in 18s
CI / test (pull_request) Successful in 18s
2026-06-07 19:24:29 +00:00
ca41d9210b tester(ET): auto-commit from tester run_id=337
All checks were successful
CI / test (push) Successful in 20s
CI / test (pull_request) Successful in 17s
2026-06-07 19:20:41 +00:00
48943fe10a reviewer(ET): auto-commit from reviewer run_id=336 2026-06-07 19:20:41 +00:00
86fe8dd509 feat(deploy): dedicated "Confirm Deploy" status triggers prod deploy
Split the overloaded `Approved` Plane status: it served BOTH as the human BRD
gate on `analysis` AND as the silent Phase B prod-deploy trigger on `deploy`
(ORCH-036), so a routine approve could launch a self-hosting prod restart.

ORCH-059 introduces a dedicated logical status `confirm_deploy` ("Confirm
Deploy") that triggers ONLY Phase B on `deploy`; `Approved` stays purely a
pipeline gate.

- plane_sync: map "Confirm Deploy" -> "confirm_deploy" in _PLANE_NAME_TO_KEY;
  intentionally absent from _DEFAULT_STATES => fail-closed (no UUID -> .get
  yields None, no KeyError, no blind deploy).
- webhooks/plane: handle_issue_updated routes "Confirm Deploy" (fail-closed
  .get) to new handle_confirm_deploy (guarded to stage=="deploy") ->
  _try_advance_stage(confirm_deploy=True).
- stage_engine: advance_stage gains keyword-only confirm_deploy=False; Phase B
  block returns early for deploy+finished_agent is None but only initiates the
  deploy when confirm_deploy=True; a plain Approved is a deterministic no-op
  (returns before check_deploy_status -> no false БАГ-8 rollback).
- Phase A CTA now asks the operator for "Confirm Deploy", not "Approved".

Contracts unchanged: STAGE_TRANSITIONS, QG_CHECKS, check_deploy_status, hook
exit codes, Phases A/C, merge-gate, DB schema. Conditional like ORCH-35/36
(self-hosting only). Docs updated (CLAUDE.md, architecture/README.md, CHANGELOG).

Refs: ORCH-059

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-07 19:20:41 +00:00
dd07b58165 architect(ET): auto-commit from architect run_id=334 2026-06-07 19:20:41 +00:00
b67a61ecef analyst(ET): auto-commit from analyst run_id=333 2026-06-07 19:20:41 +00:00
8fcb867dcf docs: init ORCH-059 business request 2026-06-07 19:20:41 +00:00
4815e378d9 docs(ORCH-059): staging gate log — staging_status SUCCESS
Some checks failed
CI / test (push) Has been cancelled
Staging check suite passed (exit 0); C9a/C9b sandbox-infra waived (ORCH-061).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-07 19:20:18 +00:00
deploy-finalizer
e07ee9e574 deploy(ORCH-036): finalize SUCCESS for ORCH-022
All checks were successful
CI / test (push) Successful in 17s
CI / test (pull_request) Successful in 17s
2026-06-07 18:42:29 +00:00
8cdb9f194a tester(ET): auto-commit from tester run_id=331
All checks were successful
CI / test (push) Successful in 19s
CI / test (pull_request) Successful in 19s
2026-06-07 18:04:50 +00:00
cb3bdd9c7a reviewer(ET): auto-commit from reviewer run_id=330 2026-06-07 18:04:50 +00:00
Dev
04233cb3c8 test(ORCH-022): isolate TC-17 worktree under tmp_path (fix CI PermissionError on /repos/_wt)
TC-17 seeded 17-security-report.md via get_worktree_path() which resolves to
settings.worktrees_dir (default /repos/_wt) -> the test wrote into the real shared
host worktree path. In CI that dir is owned by another user -> PermissionError.

Monkeypatch git_worktree.settings.worktrees_dir to tmp_path/_wt (same pattern as
test_git_worktree.py / test_merge_gate.py). Prod logic untouched.
2026-06-07 18:04:50 +00:00
stream
85ecf50926 ci: re-run after gitea restart (ORCH-022 flaky CI) 2026-06-07 18:04:50 +00:00
30b6187c73 feat(security): security-gate (gitleaks secret-scan + pip-audit) before merge
Add a deterministic (no-LLM) security sub-gate on the deploy-staging -> deploy
edge, run FIRST (before merge-gate ORCH-043 and image-freshness ORCH-058) so it
fails cheaply before any expensive rebase/rebuild, and scans origin/main..HEAD
before rebase so a task is never blamed for a CVE introduced by an updated main.

Why: the autonomous pipeline merged branches into main with no check for a leaked
secret or a vulnerable dependency. For the self-hosting orchestrator (one shared
prod instance serving every project from a shared DB) a single leak/CVE landed in
the prod of all projects (CLAUDE.md self-hosting, section 8).

- New leaf src/security_gate.py (never-raise): gitleaks (offline, fail-closed on
  tool error => secrets guarantee is unconditional) + pip-audit (best-effort;
  unreachable CVE feed degrades fail-open + loud warning by default, strict via
  security_dep_audit_fail_closed). Verdict lives ONLY in 17-security-report.md
  YAML frontmatter (write -> read-back single source of truth); FAIL is
  authoritative; missing/broken frontmatter => fail-closed.
- check_security_gate thin wrapper registered in QG_CHECKS (lazy import, no cycle).
- _handle_security_gate wired FIRST in advance_stage deploy-staging block: FAIL ->
  rollback to development + developer-retry (cap MAX_DEVELOPER_RETRIES); task_desc
  carries verbatim findings (ORCH-046 pattern). No merge-lease release (runs before
  lease acquire). Self-hosting safe: only reads/scans/writes, never deploys.
- Conditional rollout (security_gate_enabled + security_gate_repos; empty scope ->
  self-hosting only). 6 new ORCH_SECURITY_* settings.
- Infra: pinned gitleaks Go binary in Dockerfile (+curl/ca-certificates), pip-audit
  in requirements.txt, versioned .gitleaks.toml at repo root.
- STAGE_TRANSITIONS and DB schema unchanged.

Docs: docs/architecture/README.md (marked realized), CLAUDE.md (artifact 17),
CHANGELOG.md. Tests: test_security_gate.py, test_qg_security.py,
test_stage_engine_security_gate.py + updated registry/edge snapshots.

Refs: ORCH-022

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-07 18:04:50 +00:00
44db94e462 architect(ET): auto-commit from architect run_id=327 2026-06-07 18:04:50 +00:00
4f24f96169 analyst(ET): auto-commit from analyst run_id=326 2026-06-07 18:04:50 +00:00
2d20da295e docs: init ORCH-022 business request 2026-06-07 18:04:50 +00:00
67e98b8296 docs(ORCH-022): staging gate log — staging_status SUCCESS
Some checks failed
CI / test (push) Has been cancelled
Canonical staging_check.py run inside orchestrator-staging:
8/10 PASS, all REAL checks green, C9a/C9b infra-waived (ORCH-061),
exit 0 → advance.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-07 18:04:35 +00:00
stream
cad5e98892 docs(history): lessons 2026-06-07 — autonomy closure (5 задач: ORCH-58/60/61/21/65 в прод)
Some checks failed
CI / test (push) Has been cancelled
2026-06-07 19:24:49 +03:00
bb03350ec9 Merge pull request 'feat(reaper): job-reaper + stale merge-lease reclaim + idempotent merge finalization (ORCH-065)' (#66) from feature/ORCH-065-bug-zombie-jobs-merge-lease-ru into main 2026-06-07 19:16:23 +03:00
930e65298c tester(ET): auto-commit from tester run_id=324
All checks were successful
CI / test (push) Successful in 20s
CI / test (pull_request) Successful in 18s
2026-06-07 16:14:45 +00:00
cba67a4270 reviewer(ET): auto-commit from reviewer run_id=323 2026-06-07 16:14:45 +00:00
720c31393a fix(reaper): Tier-2 finalization grace + claim-before-act (no dup advance)
Tier-2 reaped a LIVE, still-finalizing monitor: _monitor_agent writes
agent_runs.exit_code FIRST, then does git push / PR / Plane comments before
_finalize_job, and the agent pid is already dead in that window — so the old
"exit_code recorded -> reap now" had no grace and could race a healthy job.
Worse, _reap_known_outcome ran the advance (advance_stage -> enqueue_job)
BEFORE the atomic claim, so a reaper that lost the race had already enqueued
the next stage (dup advance / dup enqueue), violating ADR-001 Р-1.

Fix:
- Tier-2 grace: reap only once agent_runs.exit_code has been recorded for
  >= reaper_finalize_grace_s (new setting, default 300s; > max finalization
  window). A live finalizing monitor is never reaped (FR-1.3/AC-3). New
  finished_age_s column computed in get_running_jobs.
- claim-before-act for exit0: evaluate the canonical QG READ-ONLY (the
  reconciler pattern) to choose the terminal status, then atomically claim
  'done' FIRST; only the claim winner runs the advance. A loser performs no
  side effects -> no dup advance / dup enqueue.

Docs (golden source) updated in the same change: ADR-001, global adr-0011,
README, internals, .env.example, CHANGELOG (also fixes the P3 broken adr-0011
link). New tests cover the grace window, lost-claim no-side-effects, and the
already-advanced idempotent path.

Refs: ORCH-065

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-07 16:14:45 +00:00
9b7c855df3 reviewer(ET): auto-commit from reviewer run_id=321 2026-06-07 16:14:45 +00:00
a6b444c356 fix(merge): wire pr_already_merged guard into deployer merge path (idempotent re-merge)
The pr_already_merged guard was defined + unit-tested but consulted by zero
production code, while ADR-001 Р-3 / README / CHANGELOG claimed the merge path
consults it before a repeat merge (reviewer P1, ORCH-065 attempt 2/3). The
actual merge actor is the LLM deployer agent (it merges the feature PR at the
start of the `deploy` stage), so on a reaper re-drive of an already-merged PR
the deployer would blindly re-merge → Gitea error → false БАГ-8 rollback; AC-11
("no second merge") was not met deterministically.

Wire the guard at the real consultation point — the deployer prompt — so it
runs merge_gate.pr_already_merged before any (re-)merge and no-ops when the PR
is already merged. check_branch_mergeable is left untouched (AC-13: check_*
behaviour unchanged; it runs on the first deploy-staging→deploy edge, not on a
deploy-stage re-drive where the second-merge risk lives).

- .openclaw/agents/deployer.md: idempotent pre-merge guard step + general rule.
- src/merge_gate.py: docstring names the deployer-prompt consultation point.
- docs/architecture/README.md, CHANGELOG.md: state the consultation point so
  golden-source matches implementation.
- tests/test_merge_gate.py: regression test asserting the deployer prompt wires
  the guard (so it can't silently become dead code again).

pytest tests/ -q: 743 passed.

Refs: ORCH-065
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-07 16:14:45 +00:00
dbf14e3d5a reviewer(ET): auto-commit from reviewer run_id=319 2026-06-07 16:14:45 +00:00
4bebb921ff feat(reaper): job-reaper + stale merge-lease reclaim + idempotent merge finalization
Closes the "zombie jobs" incident class: job status was set only inside
the live launcher process, so a process death left jobs.status='running'
forever; at max_concurrency=1 one zombie blocked ALL projects' queue
(self-hosting risk). Adds a background daemon (src/job_reaper.py) with
three-tier liveness (dead-pid streak / known exit_code / max-running
backstop) whose only mutating write is an atomic terminal flip guarded by
WHERE status='running' (no double-process). For exit0 the canonical QG is
the source of truth via gate-driven advance, not "exit0".

Also proactively reclaims stale merge-lease (dead pid OR TTL) via file
delete only (no git ops), and makes merge finalization idempotent
(pr_already_merged guard + up-to-date short-circuit on re-drive).

New jobs.pid column via idempotent _ensure_column (no migration); pid
stamped in launcher._spawn after Popen. Reaper start/stop in lifespan;
"reaper" snapshot in GET /queue. Kill-switches: ORCH_REAPER_ENABLED,
ORCH_REAPER_INTERVAL_S, ORCH_REAPER_DEAD_TICKS, ORCH_REAPER_MAX_RUNNING_S,
ORCH_LEASE_RECLAIM_ENABLED.

Invariants unchanged (AC-13): STAGE_TRANSITIONS, QG_CHECKS registry,
check_branch_mergeable signature/behaviour, BUG-8 rollback, hook exit
codes. restart-safe, never-raise per unit of background work.

Docs: docs/architecture/README.md, CHANGELOG.md, .env.example.
Tests: tests/test_job_reaper.py, tests/test_merge_lease_reclaim.py,
tests/test_merge_gate.py (TC-16), tests/test_merge_gate_race.py (TC-17),
tests/test_queue.py, tests/test_config.py (TC-19/TC-20). 742 passed.

Refs: ORCH-065

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-07 16:14:45 +00:00
9f846b5a50 architect(ET): auto-commit from architect run_id=317 2026-06-07 16:14:45 +00:00
b760b24a48 analyst(ET): auto-commit from analyst run_id=316 2026-06-07 16:14:45 +00:00
f0ac9d5562 docs: init ORCH-065 business request 2026-06-07 16:14:45 +00:00
987ea810bf docs(ORCH-065): staging gate SUCCESS — REAL green, C9a/C9b infra-waived
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-07 16:14:22 +00:00
f85e449d80 Merge pull request 'feat(post-deploy): post-deploy prod monitoring + auto-rollback (ORCH-021)' (#65) from feature/ORCH-021-post-deploy-rollback into main
Some checks failed
CI / test (push) Has been cancelled
2026-06-07 17:42:27 +03:00
1c89ac9df9 tester(ET): auto-commit from tester run_id=313
All checks were successful
CI / test (push) Successful in 19s
CI / test (pull_request) Successful in 17s
2026-06-07 14:40:06 +00:00
03d899812c reviewer(ET): auto-commit from reviewer run_id=312 2026-06-07 14:40:06 +00:00
b9bcdc1545 fix(deploy): drop COPY data/ from Dockerfile so worktree-context staging build succeeds
The ORCH-058 staging rebuild (check_staging_image_fresh) builds the image with
the task git-worktree as the docker build context. A fresh worktree holds only
tracked files, but the Dockerfile did `COPY data/ ./data/` — and `data/` (the
SQLite dir) is gitignored, so it is absent from that context: `docker build`
failed with exit 1 ("BUILD-STAGING: docker build failed - aborting"), bouncing
the task off deploy-staging back to development in a loop.

The COPY was dead weight regardless: `data/` is always supplied at runtime as a
bind-mount volume (./data:/app/data, see docker-compose.yml) which shadows
anything baked into the image. Replace it with `RUN mkdir -p /app/data` so the
mountpoint exists without depending on the build context.

Regression guard: test_tc08b_dockerfile_does_not_copy_gitignored_data_dir
forbids COPY of any gitignored path (the worktree-context invariant).

Refs: ORCH-021

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-07 14:40:06 +00:00
b04fae748e tester(ET): auto-commit from tester run_id=309 2026-06-07 14:40:06 +00:00
fbfcd84b16 reviewer(ET): auto-commit from reviewer run_id=308 2026-06-07 14:40:06 +00:00
2f4c553fd8 feat(post-deploy): post-deploy prod monitoring + degradation reaction (ORCH-021)
Extend pipeline responsibility past deploy->done: after the terminal
transition for an applicable repo, arm a ~15min observation window that
probes prod and reacts to a degradation the restart-time health-check
missed ("green deploy, red prod").

- src/post_deploy.py: new leaf module (config + lazy qg/db only).
  Sentinel-file restart-safe state (.post-deploy-state-<repo>/<wi>/),
  no DB migration. probe_signals/classify/decide_action/run_rollback,
  all never-raise.
- Reserved-agent job `post-deploy-monitor` (no-LLM, Variant B, calque of
  deploy-finalizer): self-requeues each tick via enqueue_job.
- Deterministic classify: DEGRADED iff >= fail_threshold consecutive
  health failures OR window 5xx ratio > 5xx_threshold; fail-safe HEALTHY.
- Self-hosting invariant (BR-5/AC-8): a tick NEVER restarts the prod
  orchestrator container -> orchestrator is ALWAYS ALERT_ONLY.
- Conditionality (ORCH-35/36/43/58): kill-switch + CSV repos, empty ->
  self-hosting only.
- QG_CHECKS / STAGE_TRANSITIONS / schema unchanged (AC-12).
- Docs: CHANGELOG, CLAUDE artefact list (16-post-deploy-log.md),
  architecture README, .env.example (ORCH_POST_DEPLOY_*).

Refs: ORCH-021

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-07 14:40:06 +00:00
2bdba532d5 architect(ET): auto-commit from architect run_id=306 2026-06-07 14:40:06 +00:00
db83b89467 analyst(ET): auto-commit from analyst run_id=305 2026-06-07 14:40:06 +00:00
961c5e9eee docs: init ORCH-021 business request 2026-06-07 14:40:06 +00:00
84a6f61ba8 docs(ORCH-021): staging gate SUCCESS — refresh 15-staging-log timestamp
Re-ran staging_check inside orchestrator-staging (exit 0); all REAL checks
green, C9a/C9b waived per ORCH-061.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-07 14:39:48 +00:00
1af356a343 docs(ORCH-021): staging gate SUCCESS — REAL green, C9a/C9b infra-waived
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-07 14:25:00 +00:00
e18947d2d9 Merge pull request 'fix(staging): tolerate sandbox-infra-only FAILs (C9a/C9b) in deploy-staging verdict (ORCH-061)' (#62) from feature/ORCH-061-bug-deploy-staging-development into main
Some checks failed
CI / test (push) Has been cancelled
2026-06-07 16:30:07 +03:00
0ec34d10fc Merge pull request 'docs(ORCH-061): staging gate SUCCESS — C9a/C9b infra-waived' (#63) from docs/ORCH-061-staging-log into main 2026-06-07 16:29:55 +03:00
bf6a0c095a docs(ORCH-061): staging gate SUCCESS — REAL green, C9a/C9b infra-waived
All checks were successful
CI / test (pull_request) Successful in 16s
Validated ORCH-061 infra-tolerance against live staging (8501): all REAL
checks pass, only sandbox-infra C9a/C9b fail and are waived → exit 0.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-07 13:29:33 +00:00
39769bdf23 tester(ET): auto-commit from tester run_id=300
All checks were successful
CI / test (push) Successful in 17s
CI / test (pull_request) Successful in 17s
2026-06-07 13:21:17 +00:00
de47737f4f reviewer(ET): auto-commit from reviewer run_id=299
All checks were successful
CI / test (push) Successful in 16s
CI / test (pull_request) Successful in 15s
2026-06-07 13:18:47 +00:00
stream
e3f7c1c272 ci: re-trigger after gitea restart (ORCH-061)
All checks were successful
CI / test (push) Successful in 16s
CI / test (pull_request) Successful in 17s
2026-06-07 13:14:14 +00:00
stream
32a7aa8c6b ci: trigger re-run after host disk cleanup (ORCH-061) 2026-06-07 13:08:38 +00:00
stream
fe8586ed78 ci: re-run after host disk cleanup (ORCH-061) 2026-06-07 13:04:38 +00:00
9070489968 fix(staging): tolerate sandbox-infra-only FAILs (C9a/C9b) in deploy-staging verdict
Some checks failed
CI / test (push) Failing after 39s
CI / test (pull_request) Failing after 35s
The self-hosting orchestrator looped on deploy-staging -> development because
scripts/staging_check.py exited 1 on ANY failed check, so two infra-only checks
(C9a sandbox branch / C9b analyst-job — caused by SANDBOX bot accounts not being
members of the sandbox Plane project, NOT a pipeline regress) forced
staging_status: FAILED -> rollback -> loop, burning developer retries and tokens.

Direction (б) per ADR-001: classify staging checks as REAL (all pipeline checks,
fail-closed) vs SANDBOX_INFRA (narrow allowlist {C9a, C9b}, waivable). New leaf
module src/staging_verdict.py (stdlib-only, never-raise): classify_check +
compute_staging_verdict fold per-check results into a tolerant-but-fail-closed
verdict — any REAL failure -> FAILED/exit1 (safety net holds under any flag);
only C9a/C9b failed & tolerant -> SUCCESS/exit0 with waived list; only infra &
strict -> FAILED/exit1; any internal error -> FAILED/exit1 (never a false green).

staging_check.py now auto-classifies each check (public 3-tuple _items shape kept
as an ORCH-048 b6 regression guard), exposes categorized_items(), prints
INFRA-WAIVED/VERDICT lines, and exits via the verdict; new --strict flag forces
legacy strictness per-run. Kill-switch ORCH_STAGING_INFRA_TOLERANCE_ENABLED
(default true) restores legacy strict mode globally. launcher gains
action_stage_no_changes_note so "no changes to commit" on action stages is logged
as expected, not treated as under-delivery.

Contracts unchanged: STAGE_TRANSITIONS, QG_CHECKS registry, staging_status:/
deploy_status: frontmatter, hook exit-code (0/1/2), check_staging_status; no DB
migration. Docs: README, STAGING_CHECK.md, deployer.md, .env.example, CHANGELOG.

Refs: ORCH-061

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-07 12:39:00 +00:00
1d1208c136 architect(ET): auto-commit from architect run_id=297
All checks were successful
CI / test (push) Successful in 18s
2026-06-07 12:22:46 +00:00
3ab2690a68 analyst(ET): auto-commit from analyst run_id=296
All checks were successful
CI / test (push) Successful in 16s
2026-06-07 12:10:46 +00:00
3806522041 docs: init ORCH-061 business request
All checks were successful
CI / test (push) Successful in 17s
2026-06-07 15:05:55 +03:00
d4c6cc0f61 Merge pull request 'fix(reconciler): skip escalated / Blocked / Needs-Input tasks in F-1 (ORCH-060)' (#60) from feature/ORCH-060-reconciler-escalated-max-retri into main
Some checks failed
CI / test (push) Has been cancelled
2026-06-07 15:01:11 +03:00
210aef6954 deployer(ET): auto-commit from deployer run_id=293
All checks were successful
CI / test (push) Successful in 17s
CI / test (pull_request) Successful in 16s
2026-06-07 11:59:00 +00:00
1820b0244e Merge pull request 'docs(ORCH-060): staging gate FAILED (8/10) — C9a/C9b E2E' (#61) from docs/ORCH-060-staging-log into main 2026-06-07 14:58:44 +03:00
2f898ede7b docs(ORCH-060): staging gate FAILED (8/10) — C9a/C9b E2E
All checks were successful
CI / test (pull_request) Successful in 17s
Canonical staging_check run inside orchestrator-staging container
(ORCH-048). Exit code 1: branch never appeared in sandbox (C9a) and
analyst job never enqueued (C9b). staging_status: FAILED → rollback
to development per ORCH-35.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-07 11:58:29 +00:00
829b914ff7 tester(ET): auto-commit from tester run_id=292
All checks were successful
CI / test (push) Successful in 17s
CI / test (pull_request) Successful in 16s
2026-06-07 11:54:59 +00:00
55e5e968ae reviewer(ET): auto-commit from reviewer run_id=291
All checks were successful
CI / test (push) Successful in 16s
CI / test (pull_request) Successful in 22s
2026-06-07 11:53:34 +00:00
4db8276f98 fix(reconciler): skip escalated / Blocked / Needs-Input tasks in F-1
All checks were successful
CI / test (push) Successful in 16s
CI / test (pull_request) Successful in 16s
Reconciler F-1 could not tell "stuck by a lost webhook" from "escalated:
max developer retries reached, waiting for a human". With CI green and a
reviewer that kept sending REQUEST_CHANGES up to the cap, every tick
re-unblocked development -> review -> rollback -> re-unblock (incident
ET-013, infinite bounce: wasted agent runs, Telegram spam, parasitic load
on the shared self-hosting instance).

Add two pre-gate guards in Reconciler._reconcile_gate_task (after the
existing analysis/no-gate/active-job/grace guards, before the gate
pre-evaluation), each an early silent return (no advance, no unblocked_total
increment, no notifications):
- Guard 1 (escalated, deterministic, no network, checked first):
  developer_retry_count(task_id) >= MAX_DEVELOPER_RETRIES. Promote
  stage_engine._developer_retry_count to public developer_retry_count
  (single source of truth; private alias kept). Limit from the constant,
  not a literal 3.
- Guard 2 (explicit human Plane gate, Variant A, no DB migration): new
  never-raise plane_sync.fetch_issue_state + Reconciler._is_blocked_or_needs_input;
  any error/None/unresolved project -> conservative skip. New sub-flag
  ORCH_RECONCILE_SKIP_BLOCKED_ENABLED mutes only the networked Guard 2.

F-2 unchanged: Blocked/Needs Input are outside {in_progress, approved,
rejected} so they are never replayed (regression test added). DB schema,
STAGE_TRANSITIONS, QG_CHECKS, never-raise, analysis carve-out and
kill-switches untouched.

Refs: ORCH-060

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-07 11:50:02 +00:00
efe437a4aa architect(ET): auto-commit from architect run_id=289
All checks were successful
CI / test (push) Successful in 16s
2026-06-07 11:41:02 +00:00
365c67f45d analyst(ET): auto-commit from analyst run_id=288
All checks were successful
CI / test (push) Successful in 17s
2026-06-07 11:28:57 +00:00
d6e0df3550 docs: init ORCH-060 business request
All checks were successful
CI / test (push) Successful in 17s
2026-06-07 14:24:00 +03:00
4d4f542b71 Merge pull request '#59 staging gate FAILED — corrected root cause' into main
Some checks failed
CI / test (push) Has been cancelled
2026-06-07 14:05:59 +03:00
9e810c89f0 docs(ORCH-058): staging gate FAILED (8/10) — CORRECTED root cause (harness bug, not handler)
All checks were successful
CI / test (pull_request) Successful in 16s
Staging check exit code 1 (C9a/C9b). Live inspection inside orchestrator-staging
proves the production webhook handler is correct: get_project_states(SANDBOX).in_progress
= 84a76f65..., but scripts/staging_check.py hardcodes the enduro fallback b873d9eb...
=> handler correctly classifies the webhook as "no pipeline action". Fix belongs in
scripts/staging_check.py (resolve SANDBOX in_progress dynamically), NOT in handle_status_start
or any ORCH-058 image-freshness code. Image under test = ORCH-058 merge commit 094b5e2f.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-07 11:05:37 +00:00
60e5596e94 docs(ORCH-058): staging gate re-run — staging_status FAILED (8/10, C9a/C9b)
E2E pipeline not triggered on staging webhook ("no pipeline action" on
state b873d9eb...); reproduces prior FAILED. Rolls task back to development.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-07 10:42:21 +00:00
bf60f7a48a Merge pull request 'docs(ORCH-058): staging gate re-run on fresh image — staging_status FAILED' (#58) from deployer/ORCH-058-staging-verdict into main 2026-06-07 13:22:14 +03:00
637c4e9e2e docs(ORCH-058): staging gate re-run on fresh image — staging_status FAILED (8/10)
All checks were successful
CI / test (pull_request) Successful in 16s
Strategy-A freshness re-validation rebuilt 8501 from merged commit 094b5e2 and
re-ran staging_check; E2E C9a/C9b fail (Plane "In Progress"/started webhook ->
"no pipeline action", no task/branch/analyst-job). Machine verdict FAILED ->
rollback to development. Prod (8500) untouched.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-07 10:21:37 +00:00
094b5e2f96 Merge pull request 'feat(ORCH-058): staging-image provenance before BUILD-ONCE prod retag (INV-FRESH)' (#57) from feature/ORCH-058-self-deploy-retag-staging into main 2026-06-07 13:04:07 +03:00
90b6c8d5a8 docs(ORCH-058): staging gate re-run — staging_status SUCCESS (10/10 PASS)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-07 09:52:41 +00:00
2221d402b1 docs(ORCH-058): staging gate log — staging_status SUCCESS (10/10 PASS)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-07 09:33:05 +00:00
6ddff5583d fix(ORCH-058): parametrize staging_check in --build-staging + explicit staging target
All checks were successful
CI / test (push) Successful in 19s
CI / test (pull_request) Successful in 18s
Round-3 review follow-up on c53d625 (P1/P2):

- P1: --build-staging now runs staging_check via parametrized
  STAGING_CONTAINER / STAGING_CHECK_PATH / STAGING_CHECK_MODE (default
  orchestrator-staging / bind-mount path / stub) instead of hardcoding
  $TARGET_SERVICE + the script path. docker exec runs INSIDE the staging
  container (ORCH-048 canonical: B6 registry isolation), after health,
  before exit 0. Fail-closed: any non-zero -> exit 1. STAGING only (8501).
- P2a: rebuild_staging_image now passes the STAGING target EXPLICITLY
  (TARGET_SERVICE/TARGET_PORT/COMPOSE_PROFILE/STAGING_CONTAINER) so the
  self-rebuild can never drift onto prod 8500 if hook defaults change (AC-9).
- P2b: TC-09 caller<->hook contract tests assert the ssh command carries
  GIT_SHA + BUILD_CONTEXT + the staging target and never the prod 8500 one;
  no-ssh-host fails closed.
- P3: consolidated the three duplicate README footers into one.
- Docs (golden source): DEPLOY_HOOK.md step 4 + env rows, README footer,
  CHANGELOG, Dockerfile ARG GIT_SHA="" comment, .env.example freshness block.

Validates exactly the artefact later BUILD-ONCE retagged to prod (AC-4,
ADR-001 step 3). 632 tests pass, ruff clean, bash -n OK.

Refs: ORCH-058

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-07 09:24:38 +00:00
Dev Agent
c53d625744 ORCH-058: --build-staging runs staging_check.py --mode stub vs fresh 8501 (AC-4)
All checks were successful
CI / test (push) Successful in 16s
Per ADR-001 step 3 / AC-4: after the freshly rebuilt staging container is
healthy, run staging_check.py --mode stub against the fresh 8501 stand BEFORE
reporting success, so the EXACT artefact BUILD-ONCE retagged to prod is the one
validated on staging. Fail-closed: staging_check rc!=0 -> exit 1 (not promoted).

- Invoked inside the container (docker exec $TARGET_SERVICE) per the canonical
  signature in scripts/staging_check.py header, --base-url http://localhost:$TARGET_PORT.
- Targets ONLY 8501 (staging), never 8500 (prod) - AC-9.
- --mode stub: fast, deterministic, no LLM spend (ADR).
- Static regression test test_tc07_build_staging_runs_staging_check_stub_after_health:
  asserts staging_check.py + --mode stub present, runs after health, before exit 0,
  fail-closed, and never hard-codes prod 8500.
2026-06-07 12:11:07 +03:00
2ee06ae676 fix(deploy-hook): --build-staging must build from validated worktree, recreate+health 8501
All checks were successful
CI / test (push) Successful in 17s
Closes reviewer P0/P1 (ORCH-058 attempt 3): the committed --build-staging hook
recomputed GIT_SHA=$(git rev-parse HEAD) in $REPO (prod clone on `main`) and built
`docker build ... "$REPO"`, ignoring the caller-supplied BUILD_CONTEXT/GIT_SHA. On
the deploy-staging -> deploy edge the PR is not yet merged, so `main` HEAD != the
validated SHA -> the staging image got the wrong revision label and Strategy-B's
guard fail-closed on EVERY valid self-deploy (AC-6 deadlock). It also only did
`docker build` + exit 0 — never recreating 8501 nor health-checking — so
rebuild_staging_image's rc=0 ("rebuilt and healthy") was a lie (AC-4 unmet).

- Hook --build-staging now honours caller BUILD_CONTEXT (validated worktree) and
  GIT_SHA, recreates orchestrator-staging on the fresh image and runs the 10x6s
  health-check; build/health failure -> exit 1 (FAILED contract preserved).
- image_freshness.rebuild_staging_image: document why COMPOSE_PROFILE/TARGET_SERVICE/
  TARGET_PORT are intentionally omitted (hook STAGING defaults -> 8501 only, P2).
- tests: assert the caller<->hook contract (builds from $BUILD_CONTEXT, no
  `git rev-parse HEAD` recompute, recreates + health-checks 8501) so the P0
  regression can't pass green again (P1).

Refs: ORCH-058

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-07 08:37:51 +00:00
3b3d587300 docs(ORCH-058): add CHANGELOG entry, .env.example flags, fix README status
All checks were successful
CI / test (push) Successful in 17s
Close AC-11 documentation gap left by the prior developer run: the
ORCH-058 feature (staging-image provenance before BUILD-ONCE retag) was
implemented and green but never recorded in the golden-source docs.

- CHANGELOG.md: add the ORCH-058 [Unreleased]/Added entry (layers A+B,
  validated_revision anchor, check_staging_image_fresh, EXPECTED_REVISION
  hook guard, new ORCH_IMAGE_FRESHNESS_* flags, ADR/test refs).
- .env.example (canon): document ORCH_IMAGE_FRESHNESS_ENABLED /
  ORCH_IMAGE_FRESHNESS_REPOS, mirroring the ORCH-036/043/053 precedent.
- docs/architecture/README.md: footer note design -> реализовано, aligning
  it with the already-updated section.

Refs: ORCH-058

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-07 08:27:57 +00:00
Dev Agent
f0c2986477 ORCH-058: implement fail-closed provenance guard in deploy-hook + GIT_SHA OCI label in Dockerfile
All checks were successful
CI / test (push) Successful in 16s
- deploy-hook: REVISION_LABEL/EXPECTED_REVISION (default unset -> backward-compat)
- deploy-hook: fail-closed guard inspects SOURCE_IMAGE revision label before docker tag, normalises <no value>, exit 1 on empty/mismatch
- deploy-hook: new --build-staging mode rebuilds staging image stamping GIT_SHA
- Dockerfile: ARG GIT_SHA + LABEL org.opencontainers.image.revision=$GIT_SHA

Closes TC07/TC08 (tests/test_deploy_hook_provenance.py).
2026-06-07 11:20:38 +03:00
83397570fe developer(ET): auto-commit from developer run_id=264
Some checks failed
CI / test (push) Failing after 17s
2026-06-07 07:46:19 +00:00
dbc32fc106 architect(ET): auto-commit from architect run_id=263
All checks were successful
CI / test (push) Successful in 16s
2026-06-07 07:27:38 +00:00
282636fedb analyst(ET): auto-commit from analyst run_id=262
All checks were successful
CI / test (push) Successful in 16s
2026-06-07 07:06:10 +00:00
e5f9c38e65 docs: init ORCH-058 business request
All checks were successful
CI / test (push) Successful in 17s
2026-06-07 10:01:11 +03:00
stream
e4c6401633 docs(history): LESSONS self-deploy bootstrap — каскад 4 инфра-багов (passwd/env/log-perms/stale-staging-image)
Some checks failed
CI / test (push) Has been cancelled
2026-06-07 09:52:39 +03:00
stream
115519ebb4 fix(compose): ORCH_DEPLOY_* env for self-deploy (prefix ORCH_, orchestrator hook, host-repo path) — ORCH-36 Phase B 2026-06-07 09:39:51 +03:00
stream
64e031a37f fix(docker): passwd entry for uid 1000 (slin) — fixes ssh/whoami, unblocks ORCH-36 self-deploy Phase B 2026-06-07 09:27:04 +03:00
01ff71978f docs(ORCH-036): staging gate SUCCESS — 10/10 checks pass (re-run inside orchestrator-staging)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-06 21:48:50 +00:00
stream
d5915a89b9 docs(history): LESSONS ORCH-036+053 — bootstrap-деплой, merge-конфликт, reconciler в проде 2026-06-07 00:34:36 +03:00
1ff8d85bb9 Merge pull request 'feat: executable self-deploy — stage deploy triggers host hook (ORCH-036)' (#55) from feature/ORCH-036-orch-36-deploy-b into main 2026-06-07 00:23:40 +03:00
stream
36c1898fac Merge remote-tracking branch 'origin/main' into feature/ORCH-036-orch-36-deploy-b
All checks were successful
CI / test (push) Successful in 16s
CI / test (pull_request) Successful in 14s
# Conflicts:
#	.env.example
#	CHANGELOG.md
#	docs/architecture/README.md
#	docs/operations/INFRA.md
#	src/config.py
2026-06-07 00:22:19 +03:00
e2dc9d6df6 Merge pull request 'ORCH-053: sweeper потерянных webhook (реконсиляция застрявших стадий)' (#56) from feature/ORCH-053-sweeper-webhook-stuck-task into main 2026-06-07 00:20:53 +03:00
c0bcb544cf tester(ET): auto-commit from tester run_id=201
All checks were successful
CI / test (push) Successful in 17s
CI / test (pull_request) Successful in 15s
2026-06-06 21:07:35 +00:00
2be39b398b reviewer(ET): auto-commit from reviewer run_id=199 2026-06-06 21:07:35 +00:00
d79defeadd fix(deploy): clear stale self-deploy markers on rollback; document env
Re-deploy after a FAILED prod deploy wedged the task on `deploy`: the
sentinel markers (approve-requested/initiated/result) are keyed by the
stable work_item_id, so after the БАГ-8 rollback (deploy -> development)
and a developer fix, Phase B's idempotency-guard saw a STALE `initiated`
and became a no-op — the detached hook never re-launched and the
finalizer was never enqueued. Add self_deploy.clear_state (never-raise,
idempotent) and call it on the check_deploy_status FAILED rollback and at
the start of Phase A, so every fresh prod-deploy pass starts clean.

Also document the new ORCH_SELF_DEPLOY_* / ORCH_DEPLOY_* descriptors in
the canonical .env.example (CLAUDE.md rule #8, ТЗ §2.6), modelled on the
ORCH-043 merge-gate block (placeholders only, secrets not committed).

Contracts untouched: STAGE_TRANSITIONS, QG_CHECKS, _parse_deploy_status,
БАГ-8, merge-gate.

Refs: ORCH-036
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-06 21:07:35 +00:00
9f43e6a0ae reviewer(ET): auto-commit from reviewer run_id=195 2026-06-06 21:07:35 +00:00
10f2a39a58 feat(deploy): build-once SOURCE_IMAGE retag in hook + deploy-stage docs
Add the optional, backward-compatible SOURCE_IMAGE branch to
orchestrator-deploy-hook.sh: when set, retag the staging-validated image
onto TARGET_IMAGE (docker tag) before `up -d --no-build` instead of
rebuilding — guarantees prod runs the exact artefact that passed staging
(AC-7 / TC-14). Unset -> prior behaviour; exit-code contract (0/1/2) and
health-loop untouched.

Update golden-source docs (AC-13): rewrite deployer.md `deploy` stage from
"paper SUCCESS" to the executable self-deploy (Phase A/B/C, no self-restart
from inside the container) and add the ORCH-036 CHANGELOG entry.

Refs: ORCH-036

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-06 21:07:35 +00:00
63187ff102 developer(ET): auto-commit from developer run_id=192 2026-06-06 21:07:35 +00:00
5c5525548d architect(ET): auto-commit from architect run_id=190 2026-06-06 21:07:35 +00:00
0d0cd6e281 analyst(ET): auto-commit from analyst run_id=189 2026-06-06 21:07:35 +00:00
480b203a9d docs: init ORCH-036 business request 2026-06-06 21:07:35 +00:00
7705552f08 docs(ORCH-036): staging gate log — staging_status SUCCESS (10/10 PASS)
Re-run of deploy-staging gate (merge-gate defer cycle). Canonical
staging_check.py (mode=stub) ran inside orchestrator-staging (8501);
all 10 checks passed (exit 0). No prod (8500) container touched.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-06 21:07:20 +00:00
c1196e34e8 deployer(ET): auto-commit from deployer run_id=204
All checks were successful
CI / test (push) Successful in 16s
CI / test (pull_request) Successful in 15s
2026-06-06 21:04:39 +00:00
d43603b224 docs(ORCH-053): deploy gate log — deploy_status SUCCESS
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-06 21:04:04 +00:00
682ae09316 docs(ORCH-036): staging gate SUCCESS log (10/10 checks PASS)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-06 20:58:12 +00:00
5089f99bb1 tester(ET): auto-commit from tester run_id=200
All checks were successful
CI / test (push) Successful in 15s
CI / test (pull_request) Successful in 16s
2026-06-06 20:55:25 +00:00
32161a180a reviewer(ET): auto-commit from reviewer run_id=198 2026-06-06 20:55:25 +00:00
7d2d77217a feat(reconciler): sweeper потерянных webhook (реконсиляция застрявших стадий)
Конвейер продвигается только входящими webhook; потерянное событие (502 на
ребилде, отсутствие ретраев у Plane/Gitea, неразрезолвленный sha→branch)
оставляет задачу молча застрявшей (класс инцидента ORCH-044). Новый фоновый
daemon-поток src/reconciler.py (паттерн queue_worker) доигрывает пропущенный
переход через те же штатные гейты/обработчики, что и webhook:

- F-1 gate-side: для задач stage≠done, без активного job и age(updated_at) ≥
  grace_for_stage(stage) — read-only пред-оценка канонического QG; зелёный →
  stage_engine.advance_stage(..., finished_agent=None); красный → тишина (спам
  нотификаций структурно невозможен). analysis F-1 не трогает (человеческий гейт).
- F-2 plane-side: опрос Plane API per-project (plane_sync.list_issues_by_state,
  курсорная пагинация, never-raise) → реплей In Progress/Approved/Rejected через
  существующие handle_status_start/handle_verdict (async из sync-потока, asyncio.run).
- F-3: усиление sha→branch в handle_ci_status — БД-fallback по единственной
  development-задаче repo (неоднозначность → не резолвим), debug→info.
- Анти-дубль на создании (db.create_task_atomic под process-wide Lock): гонка
  reconcile↔webhook не плодит второй task/branch/worktree/analyst-job (AC-4).
- F-4 observability: лог-строка разблокировки + Telegram + блок reconcile в /queue.

Старт/стоп в main.lifespan (после worker.start() / перед worker.stop()),
restart-safe, never-raise на единицу работы. Kill-switches ORCH_RECONCILE_ENABLED
/ ORCH_RECONCILE_PLANE_ENABLED + grace-настройки. Схема БД и реестры
STAGE_TRANSITIONS/QG_CHECKS не менялись.

Тесты: test_reconciler.py, test_reconciler_plane.py, test_gitea_sha_resolve.py,
test_config.py (33 новых, 563 всего зелёные). Документация обновлена (golden source):
architecture/README.md, INFRA.md, README.md, CHANGELOG.md, adr-0007 → accepted.

Refs: ORCH-053

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-06 20:55:25 +00:00
f5aae50514 architect(ET): auto-commit from architect run_id=194 2026-06-06 20:55:25 +00:00
a083ed8495 analyst(ET): auto-commit from analyst run_id=191 2026-06-06 20:55:25 +00:00
eac0eb4b3a docs: init ORCH-053 business request 2026-06-06 20:55:25 +00:00
434bd6243d docs(ORCH-053): staging gate SUCCESS log
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-06 20:55:10 +00:00
c21a279565 Merge pull request 'feat(merge-gate): auto-rebase onto current main + re-test + serialise merges (ORCH-043)' (#54) from feature/ORCH-043-merge-gate-auto-rebase-re-test into main
Some checks failed
CI / test (push) Has been cancelled
2026-06-06 21:24:33 +03:00
d9afb3a10d docs(ORCH-043): deploy gate log — deploy_status SUCCESS
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-06 17:45:13 +00:00
8447853db8 deployer(ET): auto-commit from deployer run_id=187
All checks were successful
CI / test (push) Successful in 16s
CI / test (pull_request) Successful in 16s
2026-06-06 17:41:23 +00:00
5dc5893a49 docs(ORCH-043): staging gate log — staging_status SUCCESS
Live staging-stand suite (scripts/staging_check.py, stub mode) ran inside
orchestrator-staging: 10/10 checks PASS, exit code 0. Merge-gate edge
(deploy-staging → deploy) cleared for ORCH-043.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-06 17:41:06 +00:00
581a8b595a tester(ET): auto-commit from tester run_id=186
All checks were successful
CI / test (push) Successful in 17s
CI / test (pull_request) Successful in 15s
2026-06-06 17:38:38 +00:00
ba51aa17bc reviewer(ET): auto-commit from reviewer run_id=185
All checks were successful
CI / test (push) Successful in 19s
CI / test (pull_request) Successful in 18s
2026-06-06 17:37:05 +00:00
00d69d9e27 feat(merge-gate): auto-rebase onto current main + re-test + serialise merges
All checks were successful
CI / test (push) Successful in 15s
CI / test (pull_request) Successful in 17s
Deterministic (no-LLM) sub-gate on the deploy-staging -> deploy edge that
catches a feature branch up to the CURRENT origin/main, re-tests the combined
tree, and serialises merges with a per-repo file lease — so two green parallel
branches can no longer break main (self-hosting safety for the orchestrator repo).

- src/merge_gate.py: branch_is_behind_main, auto_rebase_onto_main (push
  --force-with-lease ONLY the task branch, NEVER main), retest_branch, and a
  file merge-lease (atomic O_CREAT|O_EXCL, holder-aware release, stale reclaim).
  Strict never-raise contract; all git ops in the per-branch worktree.
- src/qg/checks.py: check_branch_mergeable composes the primitives under the
  lease; registered in QG_CHECKS. Conditional rollout (merge_gate_enabled /
  merge_gate_repos, default self-hosting only).
- src/stage_engine.py: sub-gate hook on deploy-staging (not a new stage). PASS ->
  advance; "merge-lock busy" -> DEFER (re-queue with available_at, anti-deadlock
  at max_concurrency=1, capped); conflict/red re-test -> rollback to development
  + developer retry (capped by MAX_DEVELOPER_RETRIES). Lease released on
  deploy->done / rollback / PR-merged webhook.
- src/db.py: enqueue_job(available_at_delay_s=...) for the defer (no schema change).
- src/webhooks/gitea.py: holder-aware lease release on PR-merged.
- src/config.py + .env.example: ORCH_MERGE_* settings.

Docs: README + adr-0006 (architect) already cover the design; CHANGELOG updated.
Tests: test_merge_gate.py, test_qg_merge_gate.py, test_merge_gate_race.py,
test_stage_engine.py::TestMergeGate, test_config.py, QG-registry snapshot.
Full suite: 535 passed.

Refs: ORCH-043

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-06 17:32:50 +00:00
ad1589084b architect(ET): auto-commit from architect run_id=183
All checks were successful
CI / test (push) Successful in 14s
2026-06-06 17:16:00 +00:00
77e7205ce8 analyst(ET): auto-commit from analyst run_id=182
All checks were successful
CI / test (push) Successful in 14s
2026-06-06 16:39:20 +00:00
445807dd90 docs: init ORCH-043 business request
All checks were successful
CI / test (push) Successful in 14s
2026-06-06 19:31:37 +03:00
39cb5dde70 Merge pull request 'fix(infra): ORCH-040 run containers as host uid 1000:1000 (not root)' (#53) from feature/ORCH-040-root-git into main
Some checks failed
CI / test (push) Has been cancelled
2026-06-06 19:26:35 +03:00
7b748b7ac5 docs(ORCH-040): deploy gate log — deploy_status SUCCESS
Self-hosting deploy verdict: artifact validated (staging gate green, compose
user=1000:1000 with МИНА 1 group_add intact). Prod cut-over handed to Owner
(P-1…P-4 + deploy hook) — in-task prod restart not performed by design.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-06 15:11:08 +00:00
bcf5256731 deployer(ET): auto-commit from deployer run_id=180
All checks were successful
CI / test (push) Successful in 15s
CI / test (pull_request) Successful in 14s
2026-06-06 15:09:01 +00:00
80275a3336 docs(ORCH-040): staging gate log — staging_status SUCCESS (10/10)
Staging check suite passed 10/10 (exit 0), run canonically inside
orchestrator-staging via the Docker Engine API (docker exec equivalent).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-06 15:08:50 +00:00
59e47ba067 tester(ET): auto-commit from tester run_id=179
All checks were successful
CI / test (push) Successful in 14s
CI / test (pull_request) Successful in 14s
2026-06-06 15:07:07 +00:00
be64761654 reviewer(ET): auto-commit from reviewer run_id=178
All checks were successful
CI / test (push) Successful in 13s
CI / test (pull_request) Successful in 13s
2026-06-06 15:05:26 +00:00
f81715bd39 fix(infra): run orchestrator containers as host uid 1000:1000 (not root)
All checks were successful
CI / test (push) Successful in 12s
CI / test (pull_request) Successful in 12s
Both compose services (orchestrator, orchestrator-staging) now declare
user: "1000:1000" so pipeline artifacts (git worktree, docs/work-items
commits) are created as slin:slin on the host — git pull/reset under slin
no longer fail with permission errors. docker.sock access preserved via
group_add: ["999"]. SSH mount target aligned with the launcher-forced
HOME=/home/slin (/root/.ssh -> /home/slin/.ssh). launcher.py and Dockerfile
unchanged. INFRA.md and CHANGELOG.md updated; host-prerequisites (P-1..P-4)
documented.

Refs: ORCH-040

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-06 15:02:33 +00:00
fe5eb38af2 architect(ET): auto-commit from architect run_id=176
All checks were successful
CI / test (push) Successful in 14s
2026-06-06 14:59:07 +00:00
5436c4110e analyst(ET): auto-commit from analyst run_id=175
All checks were successful
CI / test (push) Successful in 13s
2026-06-06 14:55:35 +00:00
8e91c8c23c analyst(ET): auto-commit from analyst run_id=174
All checks were successful
CI / test (push) Successful in 14s
2026-06-06 14:49:21 +00:00
83e26279bf docs: init ORCH-040 business request
All checks were successful
CI / test (push) Successful in 14s
2026-06-06 17:46:34 +03:00
3441f01650 Merge pull request 'feat(notifications): ORCH-042 Telegram tracker bump mode + russification' (#52) from feature/ORCH-042-telegram-live-tracker-bump into main
Some checks failed
CI / test (push) Has been cancelled
2026-06-06 13:48:55 +03:00
18378c2713 docs(ORCH-042): add deploy log (deploy_status: SUCCESS)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-06 10:22:01 +00:00
753eea37fc deployer(ET): auto-commit from deployer run_id=172
All checks were successful
CI / test (push) Successful in 13s
CI / test (pull_request) Successful in 12s
2026-06-06 10:20:02 +00:00
efbd8b7b8f docs(ORCH-042): add staging gate log (staging_status: SUCCESS)
Staging check suite ran inside orchestrator-staging (port 8501): 10/10 PASS.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-06 10:19:50 +00:00
6ef28efccd tester(ET): auto-commit from tester run_id=171
All checks were successful
CI / test (push) Successful in 14s
CI / test (pull_request) Successful in 12s
2026-06-06 10:17:45 +00:00
52cfe51bd8 reviewer(ET): auto-commit from reviewer run_id=170
All checks were successful
CI / test (push) Successful in 12s
CI / test (pull_request) Successful in 12s
2026-06-06 10:16:31 +00:00
05c17135c1 feat(notifications): add bump mode + russify Telegram live-tracker
All checks were successful
CI / test (push) Successful in 13s
CI / test (pull_request) Successful in 13s
ORCH-042: new ORCH_TRACKER_MODE (Settings.tracker_mode, default edit) selects
the live-tracker card behaviour. bump mode re-creates the card at the bottom of
the chat on every update (delete_telegram + send silently + repoint message_id),
keeping the "one card per task" invariant: <=1 new message per call, repoint
only on successful send, delete result never gates the send. New never-raising
delete_telegram helper. Anything != "bump" resolves to edit (zero regression).

Also russify/cosmetic-fix the card text (both modes): "Подтверждение BRD" label,
 after approve-gate, Russian stage labels, "📦 Внедрено". Docs updated in the
same PR (CHANGELOG, internals.md, .env.example).

Refs: ORCH-042

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-06 10:13:49 +00:00
0ac50b8c73 architect(ET): auto-commit from architect run_id=168
All checks were successful
CI / test (push) Successful in 12s
2026-06-06 10:05:26 +00:00
66100855f6 analyst(ET): auto-commit from analyst run_id=167
All checks were successful
CI / test (push) Successful in 15s
2026-06-06 09:49:58 +00:00
3f23897327 docs: init ORCH-042 business request
All checks were successful
CI / test (push) Successful in 13s
2026-06-06 12:27:13 +03:00
ed10f28879 docs(ORCH-044): add deploy log (deploy_status: SUCCESS)
Some checks failed
CI / test (push) Has been cancelled
Artifact-only production deploy verdict for ORCH-044. All gates green
(review APPROVED, tests PASS, staging SUCCESS 10/10). src/ runtime
changed → real rebuild+restart of prod orchestrator (8500) delegated to
Owner-run deploy hook (ORCH-36); prod container not touched by agent.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-06 08:45:08 +00:00
45480966c1 Merge pull request '#51' staging-log/ORCH-044 into main 2026-06-06 11:42:58 +03:00
a662eeb2a1 docs(ORCH-044): staging gate log — SUCCESS (10/10, B6 registry isolation PASS)
All checks were successful
CI / test (pull_request) Successful in 15s
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-06 08:42:47 +00:00
507c225175 Merge pull request 'docs(history): LESSONS_ORCH-048' (#49) from docs/lessons-orch-048 into main
Some checks failed
CI / test (push) Has been cancelled
2026-06-06 10:30:51 +03:00
a8221f01c8 docs(history): LESSONS_ORCH-048 — staging B6 isolation, variant (v), chicken-egg lesson
All checks were successful
CI / test (pull_request) Successful in 12s
2026-06-06 10:30:50 +03:00
2a36ed80b9 fix(staging_check): ORCH-048 B6 reads registry inside staging container (variant v)
ADR-001 in-container run; removes host-path hack; _evaluate_b6 pure fn; deployer.md+STAGING_CHECK.md updated. Staging 10/10 PASS incl B6.
2026-06-06 10:24:10 +03:00
3f1f3fc73b Merge pull request 'docs(ORCH-048): prod deploy log — SUCCESS (bind-mount-only, prod untouched)' (#48) from deploy-log/ORCH-048-20260606T071157 into main 2026-06-06 10:12:28 +03:00
8a70398496 docs(ORCH-048): prod deploy log — SUCCESS (bind-mount-only, prod untouched, staging gate green)
All checks were successful
CI / test (pull_request) Successful in 14s
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-06 07:11:57 +00:00
9c1c028dc1 Merge pull request 'docs(ORCH-048): staging gate log — SUCCESS (10/10, B6 registry isolation PASS)' (#47) from staging-log/ORCH-048-20260606T071003 into main 2026-06-06 10:10:18 +03:00
81e6ec5a20 docs(ORCH-048): staging gate log — SUCCESS (10/10, B6 registry isolation PASS)
All checks were successful
CI / test (pull_request) Successful in 13s
Staging suite run inside orchestrator-staging via docker exec (canonical,
ADR-001). All 10/10 checks pass, exit 0. B6 now reads registry from the
running staging instance's own process-env -> sandbox present, prod ET/ORCH
absent, no false FAIL / spurious rollback.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-06 07:10:03 +00:00
913c185232 tester(ET): auto-commit from tester run_id=154
All checks were successful
CI / test (push) Successful in 13s
CI / test (pull_request) Successful in 13s
2026-06-06 07:07:53 +00:00
2424f9aaad reviewer(ET): auto-commit from reviewer run_id=153
All checks were successful
CI / test (push) Successful in 13s
CI / test (pull_request) Successful in 14s
2026-06-06 07:05:47 +00:00
28d019a1e2 fix(staging_check): B6 reads registry from running staging instance env
All checks were successful
CI / test (push) Successful in 12s
CI / test (pull_request) Successful in 16s
B6 false-FAILed because it built the project registry from the
launcher process-env via a host-path hack (sys.path.insert +
importlib.reload), not from the running staging instance. Run from the
host, ORCH_PROJECTS_JSON is unset -> default ET+ORCH registry -> false
FAIL -> spurious deploy-staging -> development rollback.

Variant (v) per ADR-001: remove the host-path hack; canonically run the
suite INSIDE orchestrator-staging via docker exec so src.projects
resolves from /app (PYTHONPATH) with .env.staging. Verdict logic
extracted into pure _evaluate_b6(known) -> (passed, detail) +
_known_project_ids_from_registry() / _run_b6() with deterministic FAIL on
source unavailability. deployer.md and STAGING_CHECK.md updated to the
docker exec command. src/projects.py, .env* and checks A/B4/B5/C
untouched.

Refs: ORCH-048

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-06 07:03:31 +00:00
d6744c3c05 architect(ET): auto-commit from architect run_id=151
All checks were successful
CI / test (push) Successful in 15s
CI / test (pull_request) Successful in 14s
2026-06-06 06:59:56 +00:00
stream
7a6c7a0151 docs(ORCH-048): owner decision — pin variant (v), reject (a) HTTP-endpoint (chicken-egg)
All checks were successful
CI / test (push) Successful in 13s
CI / test (pull_request) Successful in 13s
2026-06-06 06:56:09 +00:00
04e88b833f Merge pull request 'docs(ORCH-048): staging gate log — FAILED (9/10, B6 /projects 404 on stale staging)' (#46) from staging-log/ORCH-048-20260606T053413 into main 2026-06-06 08:34:44 +03:00
7203812b17 docs(ORCH-048): staging gate log — FAILED (9/10, B6 /projects 404 on stale staging)
All checks were successful
CI / test (pull_request) Successful in 12s
Staging instance (8501) still runs a pre-ORCH-048 image without GET /projects,
so B6 deterministically FAILs (endpoint unavailable → no false PASS). Branch
code is correct; remediation is a host-side `--profile staging up -d --build`
of orchestrator-staging before re-running the gate.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-06 05:34:14 +00:00
8b5b1f0056 analyst(ET): auto-commit from analyst run_id=145
All checks were successful
CI / test (push) Successful in 13s
CI / test (pull_request) Successful in 13s
2026-06-06 05:06:33 +00:00
9538103eff docs: init ORCH-048 business request
All checks were successful
CI / test (push) Successful in 13s
2026-06-06 08:03:16 +03:00
0bc2398462 feat(stage_engine): ORCH-046 embed reviewer/tester findings in task_desc (#43)
Some checks failed
CI / test (push) Has been cancelled
Manual merge (Slava trust, variant A). Findings text embedded into developer task_desc (not just link). New src/review_parse.py, graceful fallback. 50 tests pass. Reviewer APPROVED, CI green. Staging FAIL = B6/ORCH-48 (infra, unrelated).
2026-06-06 07:54:03 +03:00
13b7df06b1 deployer(ET): auto-commit from deployer run_id=144
All checks were successful
CI / test (push) Successful in 12s
CI / test (pull_request) Successful in 12s
2026-06-06 04:49:37 +00:00
b5f4eb6f2f Merge pull request 'docs(ORCH-046): staging gate log — FAILED (9/10, B6 registry isolation)' (#44) from staging-log/ORCH-046-20260606T044841 into main 2026-06-06 07:49:22 +03:00
75c2b814d8 docs(ORCH-046): staging gate log — FAILED (9/10, B6 registry isolation)
All checks were successful
CI / test (pull_request) Successful in 12s
Staging suite ran end-to-end against staging (8501, stub mode): 9/10 PASS,
exit 1. Failure is B6 — staging project registry not isolated (sees prod
ET/ORCH, sandbox absent), violating the INFRA isolation invariant. Gate is
authoritative and red → staging_status: FAILED (rollback to development).
Note: this is a staging .env/ORCH_PROJECTS_JSON misconfig, not an ORCH-046
code regression (same B6 as ORCH-047).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-06 04:48:47 +00:00
be10becae2 tester(ET): auto-commit from tester run_id=143
All checks were successful
CI / test (push) Successful in 16s
CI / test (pull_request) Successful in 12s
2026-06-06 04:46:28 +00:00
4cd55063b4 reviewer(ET): auto-commit from reviewer run_id=142
All checks were successful
CI / test (push) Successful in 12s
CI / test (pull_request) Successful in 11s
2026-06-06 04:44:57 +00:00
03c3d77cac feat(stage-engine): embed verbatim reviewer/tester findings in rollback task_desc
All checks were successful
CI / test (push) Successful in 12s
CI / test (pull_request) Successful in 11s
При заворотах на development task_desc теперь несёт дословный must-fix текст
(P0/P1 ревьюера, причина FAIL тестера) вместо одной ссылки на файл — developer-
агент видит суть претензий сразу и не повторяет ту же ошибку, экономя retry-
бюджет и токены общего инстанса.

- Новый defensive-модуль src/review_parse.py (never-raise): extract_review_findings
  (P0/P1 из 12-review.md ## Findings), extract_test_failures (фрагмент тела
  13-test-report.md: pytest output / FAIL-строки / Итог), усечение по лимиту.
- Две rollback-ветки stage_engine: встраивают текст + сохраняют ссылку на полный
  файл; graceful-фоллбэк на ссылку-строку при битом/пустом артефакте.
- Последовательность отката, retry-счётчик, поля AdvanceResult, реестр QG_CHECKS
  не менялись.
- Доки: README (Stage Engine / Откаты), CHANGELOG.
- Тесты: tests/test_review_parse.py, test_stage_engine.py::TestRollbackTaskDescEmbedding.

Refs: ORCH-046

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-06 04:42:11 +00:00
29e83341b5 architect(ET): auto-commit from architect run_id=140
All checks were successful
CI / test (push) Successful in 11s
2026-06-06 04:36:40 +00:00
c7bca51d4b analyst(ET): auto-commit from analyst run_id=139
All checks were successful
CI / test (push) Successful in 12s
2026-06-06 04:09:41 +00:00
50a3c60b0e docs: init ORCH-046 business request
All checks were successful
CI / test (push) Successful in 13s
2026-06-06 07:06:44 +03:00
615a778d20 docs: lessons 2026-06-05 (#42)
Some checks failed
CI / test (push) Has been cancelled
2026-06-06 00:42:14 +03:00
stream
58116f93bd docs(history): сводные уроки вечера 05.06 — ORCH-17/45/47, деплой прода, грабли auth/git/Gitea
All checks were successful
CI / test (pull_request) Successful in 11s
2026-06-05 21:41:52 +00:00
941eec248e Merge pull request 'docs(ORCH-047): staging gate log — FAILED' (#41) from staging-log/ORCH-047-20260605213215 into main 2026-06-06 00:32:38 +03:00
b061354a8f docs(ORCH-047): staging gate log — FAILED (9/10, B6 registry isolation)
All checks were successful
CI / test (pull_request) Successful in 11s
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-05 21:32:15 +00:00
5d04de9eb6 fix(qg): ORCH-047 read result in tests gate (#40)
Manual merge by Owner. check_tests_passed reads result as equal-rank field. APPROVED reviewer v3, 68 tests pass.
2026-06-06 00:25:40 +03:00
edff0484c9 reviewer(ET): auto-commit from reviewer run_id=134
All checks were successful
CI / test (push) Successful in 12s
CI / test (pull_request) Successful in 11s
2026-06-05 21:18:33 +00:00
2f396452e8 tester(ET): auto-commit from tester run_id=132
All checks were successful
CI / test (push) Successful in 15s
CI / test (pull_request) Successful in 12s
2026-06-05 21:14:05 +00:00
185eb3f6cf reviewer(ET): auto-commit from reviewer run_id=131
All checks were successful
CI / test (push) Successful in 12s
CI / test (pull_request) Successful in 12s
2026-06-05 21:12:23 +00:00
58fc0a8b94 tester(ET): auto-commit from tester run_id=129
All checks were successful
CI / test (push) Successful in 12s
CI / test (pull_request) Successful in 12s
2026-06-05 21:07:30 +00:00
c1abfb7436 reviewer(ET): auto-commit from reviewer run_id=128
All checks were successful
CI / test (push) Successful in 12s
CI / test (pull_request) Successful in 12s
2026-06-05 21:06:01 +00:00
51a76e8169 fix(qg): read result: alongside verdict:/status: in tests gate
All checks were successful
CI / test (push) Successful in 12s
CI / test (pull_request) Successful in 11s
_parse_tests_verdict now accepts three equal-rank machine-readable
frontmatter fields in 13-test-report.md — result: (canonical tester
output), verdict: and status: (legacy/enduro-trails). Any one non-empty
field suffices; a negative token in any field stays authoritative.

Fixes the producer/consumer contract mismatch where the tester emits
`result: PASS` (per .openclaw/agents/tester.md) but the gate only read
verdict:/status:, causing a testing->development rollback loop until
MAX_DEVELOPER_RETRIES (observed on ORCH-17). Token sets frozen and gate
signature/QG_CHECKS unchanged for full backward compatibility.

Refs: ORCH-047
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-05 21:03:32 +00:00
75fb4069a4 architect(ET): auto-commit from architect run_id=126
All checks were successful
CI / test (push) Successful in 11s
2026-06-05 21:00:57 +00:00
c3879f2b80 analyst(ET): auto-commit from analyst run_id=125
All checks were successful
CI / test (push) Successful in 12s
2026-06-05 20:44:58 +00:00
974d4f94db docs: init ORCH-047 business request
All checks were successful
CI / test (push) Successful in 13s
2026-06-05 23:42:29 +03:00
982698c4e3 feat(qg): ORCH-045 CI poll-retry (#39)
Some checks failed
CI / test (push) Has been cancelled
Manual merge by Owner (Slava). check_ci_green polling with retry to fix CI race.
2026-06-05 23:08:15 +03:00
stream
0eff781d13 feat(qg): ORCH-045 — poll check_ci_green with retry to fix CI race (pending->success)
All checks were successful
CI / test (push) Successful in 12s
CI / test (pull_request) Successful in 12s
2026-06-05 19:59:06 +00:00
b9c61fc1f1 Merge pull request 'docs: uroki ORCH-017 (deadlock shared-gate, isporchennyy telefon, otkat)' (#38) from docs/lessons-orch-017 into main 2026-06-05 22:50:17 +03:00
stream
53c76cb539 docs(history): уроки ORCH-017 — дедлок shared-гейта, испорченный телефон, неполный откат
All checks were successful
CI / test (pull_request) Successful in 13s
2026-06-05 19:49:50 +00:00
26c6f2676f ORCH-017: Telegram approve-ping links to BRD & Plane (#37)
Manual merge by Owner (Слава). Только ссылки в уведомлениях; правка shared gate вынесена в ORCH-47.
2026-06-05 22:45:11 +03:00
stream
43ef160f40 docs(ORCH-017): drop check_tests_passed/result gate notes (belong to ORCH-47); keep only approve-ping link docs
All checks were successful
CI / test (push) Successful in 12s
CI / test (pull_request) Successful in 12s
2026-06-05 19:35:28 +00:00
9c28431167 reviewer(ET): auto-commit from reviewer run_id=124
All checks were successful
CI / test (push) Successful in 15s
CI / test (pull_request) Successful in 13s
2026-06-05 19:32:33 +00:00
stream
d615747d53 revert(ORCH-017): drop shared check_tests_passed gate change — moved to ORCH-47 (own ADR); keep only approve-ping links
All checks were successful
CI / test (push) Successful in 12s
CI / test (pull_request) Successful in 11s
2026-06-05 19:28:27 +00:00
c91eb7f82b reviewer(ET): auto-commit from reviewer run_id=123
All checks were successful
CI / test (push) Successful in 12s
CI / test (pull_request) Successful in 12s
2026-06-05 18:40:52 +00:00
e62d51aa77 fix(qg): testing gate reads documented tester result: frontmatter key (ORCH-017)
All checks were successful
CI / test (push) Successful in 12s
CI / test (pull_request) Successful in 11s
check_tests_passed/_parse_tests_verdict gated the testing -> deploy-staging
transition on `verdict:`/`status:` in 13-test-report.md, but the tester agent
prompt (.openclaw/agents/tester*) documents `result: PASS | FAIL` as THE
machine-readable field. A report that followed the contract literally
(ORCH-017: only `result: PASS`, no verdict:/status:) was bounced back to
development with a misleading "Tests FAILED". ORCH-016 only passed because its
report redundantly carried both `verdict:` and `result:`.

Treat `result:` as a first-class machine field alongside verdict/status; a
negative token in any field stays authoritative (ET-013 contract preserved).
Self-hosting QG fix: unblocks every project whose tester emits only `result:`.

Docs updated in-PR: CHANGELOG, architecture README machine-keys note.
Tests: test_qg.py::TestCheckTestsPassed::test_result_pass_only_passes / _fail_only_fails.

Refs: ORCH-017
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-05 18:34:25 +00:00
0e999d289d reviewer(ET): auto-commit from reviewer run_id=120
All checks were successful
CI / test (push) Successful in 12s
CI / test (pull_request) Successful in 11s
2026-06-05 18:25:02 +00:00
950a86e4d8 tester(ET): auto-commit from tester run_id=118
All checks were successful
CI / test (push) Successful in 12s
CI / test (pull_request) Successful in 17s
2026-06-05 18:20:16 +00:00
6509891f74 reviewer(ET): auto-commit from reviewer run_id=117
All checks were successful
CI / test (push) Successful in 13s
CI / test (pull_request) Successful in 11s
2026-06-05 18:18:42 +00:00
69a4aaab99 feat(notifications): direct BRD + Plane links in approve ping (ORCH-017)
All checks were successful
CI / test (push) Successful in 12s
CI / test (pull_request) Successful in 12s
notify_approve_requested now embeds two HTML <a> links into the single
notifying approve-gate message: a Gitea branch-view link to 01-brd.md and a
Plane issue browser link. Adds ORCH_PLANE_WEB_URL (external Plane web URL,
fallback to plane_api_url) with a loopback-guard that omits the Plane link
when the resolved base is localhost/empty (no broken localhost URLs in prod).

Each link is built independently and omitted on missing data; the message and
the "flip to Approved" call to action are always sent as exactly one ping. The
shared send_telegram helper is left untouched (min blast radius for the
self-hosting prod container). Dynamic labels are html.escaped; parse_mode=HTML
preserved. QG registry / stages / approve handler unchanged.

Docs updated in-PR: CHANGELOG, .env.example, INFRA env map.
Tests: test_notify_approve_links.py, test_analysis_approve_flow_links.py.

Refs: ORCH-017
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-05 17:58:00 +00:00
c9b1195c0b architect(ET): auto-commit from architect run_id=115
All checks were successful
CI / test (push) Successful in 12s
2026-06-05 17:50:28 +00:00
08528b655e analyst(ET): auto-commit from analyst run_id=112
All checks were successful
CI / test (push) Successful in 12s
2026-06-05 17:39:34 +00:00
7f31d62a4d docs: init ORCH-017 business request
All checks were successful
CI / test (push) Successful in 13s
2026-06-05 19:59:55 +03:00
401bf66fe0 feat(agents): configurable LLM model + effort per-agent and per-project (ORCH-41) (#36)
Some checks failed
CI / test (push) Has been cancelled
2026-06-05 19:45:19 +03:00
8da571de86 feat(plane): unified status-comment format with duration line (ORCH-016) (#34) 2026-06-05 17:50:47 +03:00
f375be249f fix(tests): per-project Plane states in webhook tests + close CI hole (ORCH-39) (#35) 2026-06-05 17:36:40 +03:00
053ea3b1c5 docs(ORCH-016): merge staging-log into main (staging_status: SUCCESS)
Mirrors the deploy-log pattern: deployer writes 15-staging-log.md on the
feature branch, then merges the artifact into origin/main so the
check_staging_status quality gate can read it via _staging_log_from_main()
(see src/qg/checks.py:489).

Verdict from the staging run on http://localhost:8501 (mode=stub):
  staging_status: SUCCESS  (10/10 checks PASS)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-05 12:49:59 +00:00
a2cf1454fd Merge pull request 'fix(plane): resolve issue states per-project instead of hardcoded enduro UUIDs (ORCH-10)' (#33) from feature/ORCH-10-per-project-states into main
Some checks failed
CI / test (push) Has been cancelled
2026-06-05 14:42:56 +03:00
Dev Agent
00325bcab0 fix(plane): resolve issue states per-project instead of hardcoded enduro UUIDs (ORCH-10)
All checks were successful
CI / test (push) Successful in 12s
CI / test (pull_request) Successful in 10s
ORCH-10 root cause: PLANE_STATES was a global dict hardcoding enduro-trails
UUIDs. The webhook comparison  only
matched ET UUID (b873d9eb) and silently ignored the ORCH in_progress UUID
(e331bfb3), blocking pipeline start for all orchestrator-project tasks.

Changes:
- src/plane_sync.py:
  * Rename PLANE_STATES -> _DEFAULT_STATES (enduro UUIDs kept as safe fallback).
  * PLANE_STATES preserved as alias to _DEFAULT_STATES (backward compat).
  * Add get_project_states(project_id) -> {logical_key: state_uuid}:
    fetches Plane API GET /projects/<id>/states/, maps by state name,
    caches per project_id, falls back to _DEFAULT_STATES on API failure.
  * Add _STATES_CACHE: dict, reload_project_states(project_id=None).
  * Add _PLANE_NAME_TO_KEY mapping and _STAGE_TO_STATE_KEY for clean lookup.
  * Add stage_to_state(stage, project_id) using get_project_states().
  * update_issue_state() uses stage_to_state() instead of STAGE_TO_STATE dict.
  * set_issue_{needs_input,in_review,blocked,done,in_progress,stage_state}()
    all resolve state UUID via get_project_states(project_id) instead of
    the global PLANE_STATES dict.

- src/webhooks/plane.py:
  * handle_issue_updated: import get_project_states, resolve proj_states per
    incoming project_id, compare new_state against proj_states["in_progress"],
    proj_states["approved"], proj_states["rejected"].
  * start_pipeline QG-0 blocked path: use get_project_states(plane_project_id)
    instead of PLANE_STATES["blocked"].

- tests/test_orch10_states.py: 23 new tests covering:
  * get_project_states returns correct UUIDs for both ET and ORCH projects.
  * API failure / empty response / None project_id -> _DEFAULT_STATES fallback.
  * Caching and reload_project_states (per-project and full flush).
  * stage_to_state() per-project resolution.
  * Webhook in_progress triggers pipeline for BOTH b873d9eb (ET) and e331bfb3 (ORCH).
  * Webhook approved/rejected routes correctly per project.
  * PLANE_STATES alias and _DEFAULT_STATES backward compat.
2026-06-05 14:23:31 +03:00
5ecd1c4692 Merge pull request 'docs(orchestrator): doc canon + CLAUDE.md + agent prompts + reviewer-gate (self-hosting)' (#32) from docs/ORCH-9-canon into main 2026-06-05 13:28:50 +03:00
Dev Agent
7c68d1d812 docs(orchestrator): adopt enduro doc canon + CLAUDE.md + ADR (ORCH-9)
All checks were successful
CI / test (pull_request) Successful in 9s
2026-06-05 12:33:55 +03:00
f1b31463ad Merge pull request 'feat(pipeline): add deploy-staging gate before prod deploy (ORCH-35)' (#31) from feature/ORCH-35-staging-gate into main 2026-06-05 10:43:38 +03:00
Dev Agent
e0c14fae5f fix(pipeline): make deploy-staging gate conditional on self-hosting repo (ORCH-35)
All checks were successful
CI / test (push) Successful in 10s
CI / test (pull_request) Successful in 10s
2026-06-05 10:36:46 +03:00
Dev Agent
e0b6e92b09 feat(pipeline): add deploy-staging gate before prod deploy (ORCH-35)
All checks were successful
CI / test (push) Successful in 9s
CI / test (pull_request) Successful in 9s
2026-06-05 10:06:06 +03:00
e405a55f9d Merge pull request 'feat(staging): add orchestrator deploy hook with health-check and auto-rollback (ORCH-34)' (#30) from feature/ORCH-34-deploy-hook into main 2026-06-05 09:46:18 +03:00
Dev Agent
a6cbacb62c feat(staging): add orchestrator deploy hook with health-check and auto-rollback (ORCH-34)
All checks were successful
CI / test (push) Successful in 13s
CI / test (pull_request) Successful in 9s
2026-06-05 09:26:12 +03:00
93169f16e0 Merge pull request 'feat(staging): add live staging check suite (smoke + access + e2e) [ORCH-33]' (#29) from feature/ORCH-33-staging-testsuite into main 2026-06-05 09:12:51 +03:00
Dev Agent
94334bdd42 feat(staging): add live staging check suite (smoke + access + e2e)
All checks were successful
CI / test (push) Successful in 10s
CI / test (pull_request) Successful in 10s
2026-06-05 08:54:56 +03:00
3b68a29ae1 Merge PR #28: add isolated orchestrator-staging service (ORCH-31)
Stage 1/5 of staging environment for self-hosting (ORCH-7).
Adds orchestrator-staging compose service under staging profile, isolated DB, .env.staging.example, docs. Prod untouched; service inert until explicitly started.
2026-06-05 08:01:10 +03:00
Dev Agent
6c1e5fff52 feat(staging): add isolated orchestrator-staging service (port 8501, separate DB)
All checks were successful
CI / test (push) Successful in 10s
CI / test (pull_request) Successful in 9s
- Add orchestrator-staging compose service under profile 'staging'
  so normal 'docker compose up -d' does NOT start it.
- Port 8501 via command override; network_mode: host (no ports mapping needed).
- DB isolation via separate volume ./data/staging:/app/data — physically
  separate from prod ./data/orchestrator.db on the host.
- ORCH_DB_PATH=/app/data/orchestrator.db explicit in env (same container
  path, isolated by volume mount).
- Add .env.staging.example with all required keys and placeholders.
- Update .gitignore: add .env.staging and data/staging/ exclusions.
- Add docs/STAGING.md: how to start staging, architecture table, roadmap.

Refs: ORCH-31 (Stage 1 of 5)
2026-06-05 07:34:48 +03:00
d0a34249cc Merge PR #27: isolate webhook tests + add CI workflow (self-hosting gate)
Closes the CI quality gate for orchestrator self-hosting (ORCH-7).
Full pytest tests/ green (294 passed). Supersedes #26.
2026-06-05 07:29:04 +03:00
Dev Agent
1baae81165 test: reset webhook secret per-test to fix cross-file isolation (CI green)
All checks were successful
CI / test (push) Successful in 10s
CI / test (pull_request) Successful in 10s
Adds autouse fixture _reset_webhook_secrets to tests/conftest.py that
resets the process-wide Pydantic settings singleton before every test:

1. gitea_webhook_secret / plane_webhook_secret → "" (HMAC disabled by
   default). Tests that deliberately test the 401 path
   (test_webhook_dedup.py:268,278) override this with their own monkeypatch
   which runs after autouse fixtures and wins for that test only.

2. db_path → os.environ["ORCH_DB_PATH"] (last written value after all test
   modules are imported). Without this, test_webhook_dedup.py (imported
   first alphabetically) seeds settings.db_path = dedup.db, while
   test_webhooks.py setup_db tries to remove test_orchestrator.db — leaving
   the DB dirty between tests that share a branch name and causing
   get_task_by_repo_branch() to return a stale row with the wrong stage.
   Per-test monkeypatches in test_webhook_dedup.setup_db still override it.

Root cause: both leaks come from the same singleton settings being read once
at import, before any per-test isolation runs. The autouse fixture is the
correct per-test reset point for process-wide singletons.

Result: pytest tests/ → 294 passed, 0 failed (was 10 failed/284 passed).
2026-06-05 00:00:01 +03:00
Dev Agent
e856e0940b test: migrate sequential_ids test to In Progress contract
Some checks failed
CI / test (push) Failing after 9s
CI / test (pull_request) Failing after 9s
2026-06-04 22:38:09 +03:00
Dev Agent
7bbab9c38b test: isolate webhook tests from live Plane API (fix CI)
Some checks failed
CI / test (push) Failing after 9s
CI / test (pull_request) Failing after 9s
2026-06-04 22:15:40 +03:00
a33a971c9c Merge pull request 'docs: Product Vision платформы (MD + PPTX)' (#25) from docs/product-vision into main 2026-06-04 17:37:36 +03:00
Стрим
d0c604bc66 docs: Product Vision платформы (MD + PPTX, 8 слайдов) 2026-06-04 17:37:16 +03:00
83f5020f94 Merge pull request 'fix(qg): gate testing->deploy on machine-readable test verdict, not substring (ET-013)' (#24) from fix/tests-machine-verdict into main 2026-06-04 16:08:10 +03:00
dev-agent
757745a221 fix(qg): gate testing->deploy on machine-readable test verdict, not substring (ET-013)
check_tests_passed did "if PASS in content" over the whole 13-test-report.md
body, so a report explicitly marked verdict: BLOCKED / status: blocked whose
prose mentioned "23 passed" / "PASS" / "All checks passed" passed the gate.
On ET-013 an unfinished feature (P1 AC-19 failed) reached Done.

Now mirrors check_reviewer_verdict (S-5) and check_deploy_status: read ONLY the
YAML frontmatter verdict:/status: fields. Positive tokens (PASS/PASSED/
READY-TO-DEPLOY/GREEN/APPROVED) -> True; negative tokens (BLOCKED/FAILED/...) are
authoritative -> False; missing/empty/no-frontmatter/bad-YAML -> False with reason;
file missing -> not found. Never raises.

Positive token set derived from REAL enduro-trails reports ET-001..ET-014
(inconsistent: PASS, ready-to-deploy+status:PASSED, stage:ready-to-deploy+status:pass,
PASS — ready-to-deploy). Validated: all 9 prior passing WIs stay True, ET-013 -> False.
2026-06-04 16:05:52 +03:00
34894f4684 Merge pull request 'fix(qg): find 14-deploy-log.md in origin/main when absent in feature worktree (false-FAILED deploy)' (#23) from fix/deploy-gate-log-path into main 2026-06-04 13:38:30 +03:00
dev-agent
4e4cc6c724 fix(qg): find 14-deploy-log.md in origin/main when absent in feature worktree
ET-013: deployer writes 14-deploy-log.md and merges deploy artifacts into
main via a separate PR, so the log lands in origin/main, not the feature
branch worktree that check_deploy_status reads via _repo_path(repo, branch).
Result: every successful deploy was falsely failed (Deploy log not found)
and rolled back deploy->development.

Fix: when the log is absent in the worktree, fall back to reading it from
origin/main on the shared clone (git fetch origin main + git show
origin/main:docs/work-items/<WI>/14-deploy-log.md). Lookup order:
worktree -> origin/main -> not found. Fetch/show failures degrade to
not found (never raise). Does not touch the merge-gate in gitea.py.

Tests: origin/main SUCCESS->PASS (ET-013 case), origin/main FAILED->FAILED,
absent everywhere->not found, fetch failure->degrades no exception,
worktree log short-circuits main lookup.
2026-06-04 13:35:35 +03:00
b222d7af27 Merge pull request 'fix(tracker): no duplicate Telegram messages on not-modified/transient edits' (#22) from fix/tracker-edit-not-modified into main 2026-06-04 13:22:46 +03:00
1042 changed files with 124004 additions and 525 deletions

View File

@@ -1,10 +1,566 @@
ORCH_PLANE_API_URL=http://plane-app-api-1:8000
# External (browser) web URL of Plane for clickable issue links in notifications
# (ORCH-017). Falls back to ORCH_PLANE_API_URL; a loopback fallback is treated as
# "no web URL" and the Plane link is omitted. Example: https://plane.example.org
ORCH_PLANE_WEB_URL=
ORCH_PLANE_API_TOKEN=
ORCH_PLANE_WORKSPACE_SLUG=
# Webhook secrets are GENERATED PER HOST: python3 scripts/gen_secrets.py
# (ORCH-101 / AC-5: production secrets are NEVER copied to a new host).
ORCH_PLANE_WEBHOOK_SECRET=
ORCH_GITEA_URL=http://localhost:3000
# External (browser) URL of Gitea for clickable Branch/PR links in comments;
# empty -> falls back to ORCH_GITEA_URL.
ORCH_GITEA_PUBLIC_URL=
ORCH_GITEA_TOKEN=
ORCH_GITEA_WEBHOOK_SECRET=
ORCH_GITEA_OWNER=admin
# Per-agent Plane bot tokens (optional): when set, comments are posted under
# the matching bot so Plane shows the real author; empty -> ORCH_PLANE_API_TOKEN.
ORCH_PLANE_BOT_ANALYST=
ORCH_PLANE_BOT_ARCHITECT=
ORCH_PLANE_BOT_DEVELOPER=
ORCH_PLANE_BOT_REVIEWER=
ORCH_PLANE_BOT_TESTER=
ORCH_PLANE_BOT_DEPLOYER=
ORCH_PLANE_BOT_STREAM=
# Telegram live-tracker / alerts (empty -> notifications are logged, not sent).
ORCH_TELEGRAM_BOT_TOKEN=
ORCH_TELEGRAM_CHAT_ID=
# ORCH-6: project registry — JSON array of {plane_project_id, repo,
# work_item_prefix, name}. Empty -> built-in default registry (src/projects.py)
# whose Plane UUIDs belong to the ORIGINAL host. On a NEW host this key is
# MANDATORY (ORCH-101 replication checklist, docs/operations/REPLICATION.md).
ORCH_PROJECTS_JSON=
ORCH_CLAUDE_BIN=/usr/bin/claude
ORCH_REPOS_DIR=/home/slin/repos
ORCH_DB_PATH=/app/data/orchestrator.db
# ── ORCH-101: host parametrization (replication foundation, ADR-001 D1D7) ───
# Every host-specific value lives HERE (defaults = the current production host;
# an empty/absent value keeps behaviour 1:1). The same names are read by BOTH
# pydantic Settings (env_file) and docker-compose ${VAR:-default} interpolation
# (compose reads .env/shell, NOT a service's env_file). Full variable map and
# the new-host procedure: docs/operations/REPLICATION.md.
# AGENT_HOME_DIR -> HOME of all actor subprocesses (agents/finalizer/monitor)
# AND the target of the .claude/.claude.json/.ssh mounts AND
# Dockerfile ARG APP_HOME (ORCH-040 group moves together).
# AGENT_GIT_NAME / GIT_EMAIL_DOMAIN -> git identity of agent commits; system
# actors keep platform names deploy-finalizer/post-deploy-
# monitor under the same domain.
# STAGING_PORT -> staging instance port; image_freshness fail-closes when it
# equals the prod port (ORCH-058 AC-9 guard).
# HOST_* -> host-side sources of the bind mounts (repos, ~/.claude,
# ~/.claude.json, ssh keydir, claude-code dist, node binary).
# RUN_UID/RUN_GID/DOCKER_GID -> container uid:gid + host docker group for
# docker.sock access (group_add «МИНА 1», ORCH-040).
ORCH_AGENT_HOME_DIR=/home/slin
ORCH_AGENT_GIT_NAME=claude-bot
ORCH_GIT_EMAIL_DOMAIN=mva154.local
ORCH_STAGING_PORT=8501
ORCH_HOST_REPOS_DIR=/home/slin/repos
ORCH_HOST_CLAUDE_DIR=/home/slin/.claude
ORCH_HOST_CLAUDE_JSON=/home/slin/.claude.json
ORCH_HOST_SSH_DIR=/home/slin/.orchestrator-ssh
ORCH_HOST_CLAUDE_CODE_DIR=/usr/lib/node_modules/@anthropic-ai/claude-code
ORCH_HOST_NODE_BIN=/usr/bin/node
ORCH_RUN_UID=1000
ORCH_RUN_GID=1000
ORCH_DOCKER_GID=999
# ── Agent model / effort / fallback (ORCH-41, validation ORCH-74) ─────────────
# Per-agent LLM model + reasoning effort, resolved by launcher.resolve_agent_*.
# Resolution priority (per agent): project-override (projects_json agent_models/
# agent_efforts) > ORCH_AGENT_MODEL_<AGENT> / ORCH_AGENT_EFFORT_<AGENT> >
# ORCH_AGENT_MODEL_DEFAULT / ORCH_AGENT_EFFORT_DEFAULT > CLI default (no flag).
# The frontmatter `model:` in .openclaw/agents/*.md is DESCRIPTIVE only and is NOT
# read — config below is the single source of truth for the model (ORCH-74 G1).
#
# ORCH-74 (G2): a resolved MODEL name is validated (^claude-…$ format check) before
# it reaches --model. A structurally invalid name (typo, gpt-4, empty) is logged and
# the next valid level is used (in the limit: no --model flag). Forward-compatible:
# a future claude-* version passes without editing any allowlist. EFFORT is validated
# against low|medium|high|xhigh|max (ORCH-41); an invalid effort is dropped.
#
# All 6 agents resolve to claude-opus-4-8 (model-routing G3 NOT enabled). Leave the
# per-agent overrides empty to use the default. Do NOT hardcode the model version
# anywhere except ORCH_AGENT_MODEL_DEFAULT.
ORCH_AGENT_MODEL_DEFAULT=claude-opus-4-8
ORCH_AGENT_MODEL_ANALYST=
ORCH_AGENT_MODEL_ARCHITECT=
ORCH_AGENT_MODEL_DEVELOPER=
ORCH_AGENT_MODEL_REVIEWER=
ORCH_AGENT_MODEL_TESTER=
ORCH_AGENT_MODEL_DEPLOYER=
# Effort split (ORCH-081/ORCH-52h): thinking agents (analyst/architect/reviewer)
# -> high; developer -> xhigh (coding/agentic role, Opus 4.8 canon); mechanical
# agents (tester/deployer) -> medium. NB: an empty ORCH_AGENT_EFFORT_*= no longer
# zeroes the effort — the launcher falls back to a per-role floor (= the config.py
# class-default) so each role still runs at its canonical level (ORCH-081).
ORCH_AGENT_EFFORT_DEFAULT=high
ORCH_AGENT_EFFORT_ANALYST=high
ORCH_AGENT_EFFORT_ARCHITECT=high
ORCH_AGENT_EFFORT_DEVELOPER=xhigh
ORCH_AGENT_EFFORT_REVIEWER=high
ORCH_AGENT_EFFORT_TESTER=medium
ORCH_AGENT_EFFORT_DEPLOYER=medium
# Optional --fallback-model used when the primary is overloaded. Empty -> no flag
# (G4 NOT enabled, ADR-001 ORCH-74: determinism — all agents stay on opus-4-8). A
# non-empty value is validated by the SAME predicate as the model; a typo is dropped.
ORCH_AGENT_FALLBACK_MODEL=
# ORCH-042/ORCH-067: live-tracker mode. bump (DEFAULT since ORCH-067) -> on every
# update the old card is deleted and a fresh one is sent silently to the BOTTOM of
# the chat (deleteMessage + sendMessage + repoint), so the current status is always
# the last message in an active chat. edit -> the task card is edited in place
# (editMessageText). One card per task in both modes. Any value other than "bump"
# (incl. empty/garbage) -> edit.
ORCH_TRACKER_MODE=bump
# ORCH-067: best-effort live-overlay for the card status line. The offline core
# (stage -> Plane status, In Review from the brd-clock) always works without network;
# the overlay only fills in branches indistinguishable offline (Needs Input / Blocked /
# Rejected / Cancelled / Deploying / Monitoring after Deploy) by reading the LIVE Plane
# status with a short timeout + per-issue TTL cache. It NEVER blocks the pipeline and
# NEVER raises.
# LIVE_STATUS -> kill-switch (false -> offline core only).
# LIVE_STATUS_TTL_S -> TTL (seconds) of the per-issue live-uuid cache (hot-path guard).
# LIVE_STATUS_TIMEOUT_S -> timeout (seconds) of a single live-GET on the render path.
ORCH_TRACKER_LIVE_STATUS=true
ORCH_TRACKER_LIVE_STATUS_TTL_S=60
ORCH_TRACKER_LIVE_STATUS_TIMEOUT_S=3
# ORCH-043: merge-gate (auto-rebase onto current origin/main + re-test + merge-lock)
# on the deploy-staging -> deploy edge. Deterministic sub-gate (no LLM) that catches
# the branch up to the CURRENT origin/main, re-tests it, and serialises merges so two
# green parallel branches can't break main.
# ENABLED -> global kill-switch (false -> whole gate is a no-op pass).
# REPOS -> CSV of repos where the gate is REAL; empty -> only the self-hosting
# repo (orchestrator); other repos -> conditional no-op (mirrors ORCH-35).
# RETEST_TIMEOUT_S -> wall-clock budget for the post-rebase re-test.
# RETEST_TARGET -> pytest target for the re-test.
# LOCK_TIMEOUT_S -> max merge-lease age before a stale lease is reclaimed.
# DEFER_DELAY_S -> delay before re-running the gate when the lock is busy.
# DEFER_MAX_ATTEMPTS -> defer retries before escalation (avoids livelock).
ORCH_MERGE_GATE_ENABLED=true
ORCH_MERGE_GATE_REPOS=
ORCH_MERGE_RETEST_TIMEOUT_S=600
ORCH_MERGE_RETEST_TARGET=tests/
ORCH_MERGE_LOCK_TIMEOUT_S=300
ORCH_MERGE_DEFER_DELAY_S=60
ORCH_MERGE_DEFER_MAX_ATTEMPTS=5
# ORCH-026 Level A: unconditional pre-merge rebase. With the flag ON (default),
# check_branch_mergeable ALWAYS rebases the branch onto origin/main under the held
# merge-lease (not only when behind) — a deterministic structural anti-phantom on
# the scheduler edge. No-op on an up-to-date branch (rebase keeps HEAD, force-with-
# lease -> "Everything up-to-date", CI not triggered). Scope = ORCH_MERGE_GATE_REPOS.
# PREMERGE_REBASE_ALWAYS=false -> strictly pre-ORCH-026 (rebase only when behind).
ORCH_PREMERGE_REBASE_ALWAYS=true
# ORCH-026 Level B: declarative task dependencies ("B waits for A"). claim_next_job
# gates jobs whose depends-on tasks are not yet 'done' (additive job_deps table,
# NOT EXISTS) WITHOUT occupying a max_concurrency slot. Inert on an empty job_deps.
# TASK_DEPS_ENABLED=false -> claim query is 1:1 the ORCH-1 query (no gate).
# TASK_DEPS_SOURCE=db|plane|hybrid -> declaration source; db (default) never calls
# Plane on the hot path; plane/hybrid ingest Plane `blocked-by` relations and
# cache them into job_deps (the scheduler then reads only the DB).
ORCH_TASK_DEPS_ENABLED=true
ORCH_TASK_DEPS_SOURCE=db
# ORCH-088 (Stage 1, serial e2e): per-repo serial gate. A NEW task's analyst-job does
# NOT enter analysis (no branch cut, no analyst) while the same repo has an EARLIER
# unfinished task (FIFO, tasks.id < the job's task) OR the repo is frozen. The branch
# cut is DEFERRED from start_pipeline to the analyst-job claim so its base is a fresh
# origin/main already containing the predecessor (anti-stale-base). Gate lives in
# claim_next_job (offline hot-path, fail-OPEN on error); freeze (FR-5) is a durable
# repo_freeze row set on post-deploy DEGRADED, cleared manually via
# POST /serial-gate/unfreeze?repo=<repo>. Leaf src/serial_gate.py (never-raise).
# SERIAL_GATE_ENABLED=false -> claim AND start_pipeline are 1:1 as before ORCH-088.
# SERIAL_GATE_REPOS (CSV) -> scope; EMPTY = ALL repos (not self-hosting-only).
# SERIAL_GATE_FREEZE_ENABLED=false -> the rollback-freeze layer is off (not set/read).
ORCH_SERIAL_GATE_ENABLED=true
ORCH_SERIAL_GATE_REPOS=
ORCH_SERIAL_GATE_FREEZE_ENABLED=true
# ORCH-090: STOP-status task cancellation (stop active agent + full progress reset)
# and the relaunch-hole close. A dedicated Plane "STOP" status (logical key `stop`,
# fail-closed: absent from _DEFAULT_STATES, so a board without the status -> no-op)
# routes to a cancel handler that drives the task to the system-terminal state
# `cancelled` (stop agent via the graceful SIGTERM cascade, cancel all jobs, remove
# worktree + delete the remote feature branch [never main / never force-push],
# tombstone the natural keys for a clean re-create via "To Analyse"; docs preserved).
# STOP during a critical merge/deploy window is DEFERRED until the irreversible step
# finishes honestly. The relaunch-hole gate restricts the "To Analyse" agent relaunch
# to the `analysis` stage (the sole Needs-Input owner). Additive, never-raise.
# Infra precondition: create a "STOP" status with the `cancelled` group on the ORCH
# board (07-infra-requirements.md). Leaf src/cancel.py.
# STOP_STATUS_ENABLED=false -> STOP handling AND the relaunch-hole gate are inert
# (behaviour strictly as before ORCH-090).
# STOP_STATUS_REPOS (CSV) -> scope; EMPTY = ALL repos (cancellation is meaningful
# for enduro too).
ORCH_STOP_STATUS_ENABLED=true
ORCH_STOP_STATUS_REPOS=
# ORCH-019: bug-fast-track — a cheaper/shorter pipeline route for bug-fix tasks.
# A task carrying the Plane `Bug` label skips the whole `architecture` stage; EVERY
# Quality Gate / sub-gate runs UNCHANGED (route is a scheduler property, not a gate).
# Additive, never-raise, fail-safe -> full cycle. Infra precondition: create a `Bug`
# label on the ORCH board (its absence = full cycle, fail-safe). Leaf src/bug_fast_track.py.
# BUG_FAST_TRACK_ENABLED=false -> start_pipeline AND advance_stage are 1:1 as before
# ORCH-019 (zero regression).
# BUG_FAST_TRACK_LABEL -> Plane label that activates the track (default `Bug`).
# BUG_FAST_TRACK_REPOS (CSV) -> scope; EMPTY = self-hosting only (orchestrator).
ORCH_BUG_FAST_TRACK_ENABLED=true
ORCH_BUG_FAST_TRACK_LABEL=Bug
ORCH_BUG_FAST_TRACK_REPOS=
# ORCH-094: terminal-window-aware guard for the three deploy-phase Plane status
# setters (set_issue_awaiting_deploy / set_issue_deploying / set_issue_monitoring).
# A DB stage=done task converges to Done idempotently instead of flapping
# Awaiting <-> Monitoring, EXCEPT the legitimate post-deploy Monitoring while the
# window is active (ARMED & not DONE). Leaf src/deploy_status_guard.py, never-raise;
# STAGE_TRANSITIONS / QG_CHECKS / machine-verdict keys untouched (no DB migration).
# DEPLOY_STATUS_GUARD_ENABLED=false -> setters are terminal-blind (1:1 pre-ORCH-094).
# DEPLOY_STATUS_GUARD_REPOS (CSV) -> scope; EMPTY = self-hosting only (orchestrator),
# the only repo where deploy-phase statuses are set.
ORCH_DEPLOY_STATUS_GUARD_ENABLED=true
ORCH_DEPLOY_STATUS_GUARD_REPOS=
# ORCH-071/073: merge-verify under-gate on the `deploy -> done` edge (врезка in
# advance_stage, NOT a new STAGE_TRANSITIONS edge / registered QG). A deterministic
# merge-actor merges the feature code-PR via the Gitea PR-merge API (never push/
# force-push to main), then `done` is allowed ONLY when the deployed SHA is proven an
# ancestor of origin/main (ORCH-073 FR-1: SHA-in-main is the single criterion; a
# merged PR alone no longer confirms). A secondary regression guard then checks a
# declarative marker set (MAIN_REGRESSION_MARKERS) is still in origin/main; a missing
# marker -> alert + HOLD (NOT done), a git error of the grep itself -> fail-open.
# MERGE_VERIFY_ENABLED -> global kill-switch (false -> strictly pre-ORCH-071).
# MERGE_VERIFY_REPOS -> CSV of repos where the under-gate is REAL; empty ->
# only the self-hosting repo (orchestrator); non-self -> no-op.
# MERGE_PR_TIMEOUT_S -> per Gitea list/merge HTTP call timeout.
# MERGE_VERIFY_TIMEOUT_S -> git fetch/merge-base timeout for the ancestor + marker checks.
# REGRESSION_GUARD_ENABLED -> kill-switch for the ORCH-073 main-integrity regression
# guard (false -> SHA-in-main alone gates done); reuses the
# merge-verify scope, so non-self repos are a no-op.
# MERGE_VERIFY_AUTOCREATE_PR_ENABLED -> ORCH-082: guarantee an open code-PR
# (head==branch, base==main) via merge_gate.ensure_open_pr
# BEFORE the deterministic merge_pr (fixes the false HOLD
# "no open PR"). false -> exactly pre-ORCH-082 behaviour.
# Reuses the merge-verify scope; non-self repos -> no-op.
ORCH_MERGE_VERIFY_ENABLED=true
ORCH_MERGE_VERIFY_REPOS=
ORCH_MERGE_PR_TIMEOUT_S=60
ORCH_MERGE_VERIFY_TIMEOUT_S=60
ORCH_REGRESSION_GUARD_ENABLED=true
ORCH_MERGE_VERIFY_AUTOCREATE_PR_ENABLED=true
# ORCH-093: deterministic merge-actor retry of TRANSIENT Gitea merge errors. merge_pr
# wraps ONLY the mutating POST /pulls/{n}/merge in a bounded exponential-backoff
# retry-loop on transient outcomes (405 "try again later" / 408 / 5xx / network /
# timeout, and 409|422 while the PR is still mergeable); terminal outcomes
# (403/404/real conflict) -> fast honest False (the ORCH-071/081 HOLD backstop is
# unchanged). Fixes the ORCH-063 false HOLD + manual re-merge. The already-in-main
# guard (no commits beyond origin/main -> no garbage PR) is always-on under
# MERGE_VERIFY_AUTOCREATE_PR_ENABLED (no separate flag).
# MERGE_RETRY_ENABLED -> kill-switch; false -> exactly one POST (one-shot, prior behaviour).
# MERGE_RETRY_MAX_ATTEMPTS -> max POST attempts on a transient outcome.
# MERGE_RETRY_BACKOFF_BASE_S -> exponential backoff base seconds (sleep = base*2^(i-1)).
# MERGE_RETRY_BACKOFF_MAX_S -> per-sleep backoff ceiling seconds (bounds total wait).
ORCH_MERGE_RETRY_ENABLED=true
ORCH_MERGE_RETRY_MAX_ATTEMPTS=3
ORCH_MERGE_RETRY_BACKOFF_BASE_S=2
ORCH_MERGE_RETRY_BACKOFF_MAX_S=5
# ORCH-036: executable self-deploy of the `deploy` stage. For the self-hosting repo
# (orchestrator) the stage REALLY restarts prod (8500) via a detached host hook;
# deploy_status: SUCCESS means proven health-ok, not an LLM declaration. Three
# deterministic phases (A: request approve, B: human Approved -> detached deploy,
# C: finalizer maps hook exit-code -> deploy_status). Non-self repos: unchanged
# synchronous ssh deploy. SECRETS / host paths live ONLY on the host — do NOT commit.
# SELF_DEPLOY_ENABLED -> global kill-switch (false -> legacy synchronous deploy for all).
# SELF_DEPLOY_REPOS -> CSV of repos where Phase A/B/C is REAL; empty -> only the
# self-hosting repo (orchestrator); others -> no-op (mirrors ORCH-35).
# DEPLOY_REQUIRE_MANUAL_APPROVE -> require a human Plane "Approved" before the prod
# deploy (true on rollout; full auto is ORCH-54).
# DEPLOY_FINALIZE_DELAY_S -> delay before the first/each finalize poll (>= hook+health).
# DEPLOY_FINALIZE_MAX_ATTEMPTS -> bounded finalize-defer budget (anti-livelock).
# DEPLOY_SSH_USER / DEPLOY_SSH_HOST -> ssh target for the host hook (DEPLOY_SSH_HOST
# empty -> detached deploy will NOT launch; set on the host).
# DEPLOY_HOOK_SCRIPT -> path to the hook ON THE HOST (relative to the repo).
# DEPLOY_HOST_REPO_PATH -> orchestrator clone path on the host.
# DEPLOY_PROD_SOURCE_IMAGE -> staging-validated image, retagged build-once (no rebuild).
# DEPLOY_PROD_TARGET_SERVICE / _PORT / _IMAGE / _COMPOSE_PROFILE -> prod compose profile.
# DEPLOY_PROD_PREV_IMAGE_FILE -> prod prev-image snapshot (separate from staging's).
ORCH_SELF_DEPLOY_ENABLED=true
ORCH_SELF_DEPLOY_REPOS=
ORCH_DEPLOY_REQUIRE_MANUAL_APPROVE=true
ORCH_DEPLOY_FINALIZE_DELAY_S=90
ORCH_DEPLOY_FINALIZE_MAX_ATTEMPTS=10
ORCH_DEPLOY_SSH_USER=slin
ORCH_DEPLOY_SSH_HOST=
ORCH_DEPLOY_HOOK_SCRIPT=scripts/orchestrator-deploy-hook.sh
ORCH_DEPLOY_HOST_REPO_PATH=/home/slin/repos/orchestrator
ORCH_DEPLOY_PROD_SOURCE_IMAGE=orchestrator-orchestrator-staging
ORCH_DEPLOY_PROD_TARGET_SERVICE=orchestrator
ORCH_DEPLOY_PROD_TARGET_PORT=8500
ORCH_DEPLOY_PROD_TARGET_IMAGE=orchestrator-orchestrator
ORCH_DEPLOY_PROD_COMPOSE_PROFILE=
ORCH_DEPLOY_PROD_PREV_IMAGE_FILE=.deploy-prev-image-prod
# ORCH-058: staging-image provenance before the BUILD-ONCE prod retag (INV-FRESH).
# Guarantees the staging image promoted to prod is the EXACT artefact rebuilt from the
# validated commit — two layers, self-hosting only:
# A (liveness): QG sub-check `check_staging_image_fresh` on the deploy-staging->deploy
# edge rebuilds orchestrator-orchestrator-staging from the validated commit + recreates
# 8501; FAIL -> rollback to development. (builds/recreate STAGING only, never prod.)
# B (safety): the Dockerfile stamps `org.opencontainers.image.revision`; the prod hook
# fail-closes (exit 1) before `docker tag` if SOURCE_IMAGE's label != EXPECTED_REVISION.
# ENABLED -> single kill-switch for A+B as a WHOLE (never "B without A"); false -> legacy.
# REPOS -> CSV of repos where the gate is REAL; empty -> only self-hosting (orchestrator).
ORCH_IMAGE_FRESHNESS_ENABLED=true
ORCH_IMAGE_FRESHNESS_REPOS=
# ORCH-061: staging-verdict tolerance to sandbox-infra-only FAILs. The self-hosting
# orchestrator looped on deploy-staging because staging_check.py exited 1 on ANY FAIL,
# so two infra-only checks (C9a sandbox branch / C9b analyst-job — caused by SANDBOX
# bot accounts not being members of the sandbox Plane project, NOT a pipeline regress)
# forced staging_status: FAILED -> rollback -> loop. With this ON, C9a/C9b are WAIVED
# to SUCCESS when every REAL check is green; any REAL failure still fails closed.
# true (default) -> tolerant; false -> legacy strict (1:1 pre-ORCH-061, any FAIL rolls back).
# Lives in .env.staging (the staging instance). CLI --strict overrides this per-run.
ORCH_STAGING_INFRA_TOLERANCE_ENABLED=true
# ORCH-053: stuck-task reconciler (sweeper for lost webhooks). A background daemon
# replays a missed stage transition through the SAME gates/handlers a webhook would,
# fixing tasks that got stuck on a dropped event (502 on rebuild, no Plane/Gitea
# retries, unresolved sha->branch).
# ENABLED -> global kill-switch (self-hosting safety / staged rollout).
# PLANE_ENABLED -> separate flag for the F-2 Plane-API poll (mute only F-2).
# INTERVAL_S -> background sweep period (seconds).
# GRACE_DEFAULT_S -> default "stuck" threshold on tasks.updated_at (seconds).
# GRACE_OVERRIDES_JSON -> per-stage thresholds, e.g. {"development":300}; bad JSON -> default.
# NOTIFY_UNBLOCK -> send a Telegram message when a stuck task is unblocked.
# SKIP_BLOCKED_ENABLED -> ORCH-060 F-1 Guard 2: skip reconciling issues a human moved
# to Blocked / Needs Input (per-candidate Plane state lookup).
# false mutes ONLY the networked Guard 2; Guard 1 (escalated by
# developer retries, local+deterministic) is always active.
ORCH_RECONCILE_ENABLED=true
ORCH_RECONCILE_PLANE_ENABLED=true
ORCH_RECONCILE_INTERVAL_S=120
ORCH_RECONCILE_GRACE_DEFAULT_S=600
ORCH_RECONCILE_GRACE_OVERRIDES_JSON=
ORCH_RECONCILE_NOTIFY_UNBLOCK=true
ORCH_RECONCILE_SKIP_BLOCKED_ENABLED=true
# ORCH-068: TTL (seconds) for the per-project Plane states cache (plane_sync
# _STATES_CACHE). Historically the cache lived for the whole process lifetime,
# so a status added to Plane after start was invisible until a restart
# ("stale set -> no pipeline action"). With a TTL the entry self-heals by
# re-fetching /states/ once it expires (reuses reload_project_states()).
# >0 -> re-fetch after this many seconds (default 300 = 5 min);
# 0 -> disable TTL -> strictly the previous lifetime cache (back-compat).
ORCH_PLANE_STATES_TTL_S=300
# ORCH-065: job-reaper + proactive merge-lease reclaim. A background daemon thread
# (src/job_reaper.py, started LAST in main.lifespan after requeue_running_jobs) reaps
# zombie 'running' jobs whose monitor/process died before writing the terminal status
# (one zombie at max_concurrency=1 blocks the whole shared queue) and periodically
# reclaims dead/stale merge-leases. Liveness is three-tier: Tier-1 dead jobs.pid
# (os.kill(pid,0)) after REAPER_DEAD_TICKS consecutive dead ticks (anti-false-positive
# for a live agent); Tier-2 agent_runs.exit_code recorded but job still 'running'
# (only after a REAPER_FINALIZE_GRACE_S finalization grace, so a live monitor still
# doing git push / PR / Plane comments is never reaped); Tier-3 backstop after
# REAPER_MAX_RUNNING_S. The terminal flip carries an atomic status='running' guard and
# precedes any advance/enqueue (claim-before-act) so it never double-processes/-advances
# a row racing a late monitor or requeue_running_jobs.
# REAPER_ENABLED -> global kill-switch (false -> strictly prior behaviour).
# REAPER_INTERVAL_S -> background scan period (seconds).
# REAPER_DEAD_TICKS -> consecutive dead-pid ticks before reaping (Tier-1, >=2).
# REAPER_MAX_RUNNING_S -> Tier-3 backstop ceiling; must exceed max agent_timeout+grace.
# REAPER_FINALIZE_GRACE_S -> Tier-2 grace: how long agent_runs.exit_code must have been
# recorded before a still-'running' job is reaped; MUST exceed
# the max finalization window (git push + PR + Plane comments).
# LEASE_RECLAIM_ENABLED -> kill-switch for the proactive stale/dead lease reclaim
# (false -> only the legacy lazy TTL reclaim in acquire_merge_lease).
# (reuse) ORCH_MERGE_LOCK_TIMEOUT_S -> lease TTL; ORCH_MERGE_GATE_REPOS -> reclaim scope.
ORCH_REAPER_ENABLED=true
ORCH_REAPER_INTERVAL_S=60
ORCH_REAPER_DEAD_TICKS=2
ORCH_REAPER_MAX_RUNNING_S=3600
ORCH_REAPER_FINALIZE_GRACE_S=300
ORCH_LEASE_RECLAIM_ENABLED=true
# ORCH-063: disk-watchdog — background heartbeat that measures HOST-FS fill via the
# mounted bind-paths (/repos, /app/data) with shutil.disk_usage (NOT the container
# overlay /) and Telegram-alerts the operator at >= threshold. On 07.06.2026 the
# mva154 host disk silently hit 100% and stalled the WHOLE self-hosting pipeline;
# this is the missing proactive signal. Daemon thread modelled on reconciler/reaper
# (start/stop in main.lifespan, /queue snapshot, never-raise). Anti-spam state is
# in-memory (no DB migration); the watchdog only READS fill and SENDS Telegram — it
# never touches the disk/container or restarts prod (self-hosting safety).
# DISK_MONITOR_ENABLED -> kill-switch; false -> the daemon does not start (1:1 as before).
# DISK_MONITOR_INTERVAL_S -> heartbeat measurement period, seconds (order of minutes).
# DISK_MONITOR_THRESHOLD_PCT -> fill % that triggers the alert (Owner-fixed 85; valid 1..100).
# DISK_MONITOR_REALERT_S -> cooldown between repeat alerts while above threshold (~6h).
# DISK_MONITOR_PATHS -> CSV of monitored HOST bind-paths; empty -> /repos,/app/data.
ORCH_DISK_MONITOR_ENABLED=true
ORCH_DISK_MONITOR_INTERVAL_S=300
ORCH_DISK_MONITOR_THRESHOLD_PCT=85
ORCH_DISK_MONITOR_REALERT_S=21600
ORCH_DISK_MONITOR_PATHS=/repos,/app/data
# ORCH-062: build-cache-pruner — the "second half" of the disk-watchdog
# (watchdog SIGNALS, pruner CLEANS). A daemon thread modelled on disk_watchdog
# that periodically runs STRICTLY `docker builder prune -f --filter until=<until>`
# on the HOST over ssh (BuildKit GC). Touches ONLY the build cache: never
# images/containers of running services, never restarts the docker daemon or the
# prod container (self-hosting safety). State is in-memory (no DB migration). No
# ssh host configured -> the tick is a no-op. See docs/operations/INFRA.md.
# BUILD_CACHE_PRUNE_ENABLED -> kill-switch; false -> the daemon does not start (1:1 as before).
# BUILD_CACHE_PRUNE_INTERVAL_S -> tick period, seconds (order of hours; default ~6h). >0, else default.
# BUILD_CACHE_PRUNE_UNTIL -> retention age for the warm cache (`--filter until=`); ^\d+[smhdw]?$, else 24h.
# BUILD_CACHE_PRUNE_ALL -> add `-a` (ALWAYS paired with until); default false.
# BUILD_CACHE_PRUNE_TIMEOUT_S -> bound on the ssh command, seconds. >0, else default.
# BUILD_CACHE_PRUNE_NOTIFY_MIN_GB -> Telegram when reclaimed >= N GB; 0 -> silent.
ORCH_BUILD_CACHE_PRUNE_ENABLED=true
ORCH_BUILD_CACHE_PRUNE_INTERVAL_S=21600
ORCH_BUILD_CACHE_PRUNE_UNTIL=24h
ORCH_BUILD_CACHE_PRUNE_ALL=false
ORCH_BUILD_CACHE_PRUNE_TIMEOUT_S=120
ORCH_BUILD_CACHE_PRUNE_NOTIFY_MIN_GB=0
# ORCH-022: security-gate (secret-scanning + dependency audit) on the
# deploy-staging -> deploy edge, run FIRST among the edge sub-gates. Deterministic
# (no LLM): gitleaks (offline secret-scan, pinned Go binary in the image) + pip-audit
# (OSV/PyPI CVE audit). Verdict in the versioned 17-security-report.md frontmatter;
# FAIL -> rollback to development + developer-retry (cap 3). See ADR-001.
# GATE_ENABLED -> global kill-switch; false -> pipeline 1:1 as before ORCH-022.
# GATE_REPOS -> CSV of repos where the gate is REAL; empty -> only self-hosting.
# DEP_BLOCK_SEVERITY -> CVE severity that BLOCKS (CRITICAL>HIGH>MEDIUM>LOW); below /
# UNKNOWN -> warning only (anti-loop).
# SCAN_TIMEOUT_S -> per external scanner call timeout.
# DEP_AUDIT_FAIL_CLOSED -> strict mode: unreachable CVE feed -> FAIL instead of the
# default fail-open + warning (anti-loop). Default false.
# SECRETS_BLOCK -> a found secret blocks (always true by default; the offline
# secrets guarantee is unconditional).
ORCH_SECURITY_GATE_ENABLED=true
ORCH_SECURITY_GATE_REPOS=
ORCH_SECURITY_DEP_BLOCK_SEVERITY=HIGH
ORCH_SECURITY_SCAN_TIMEOUT_S=300
ORCH_SECURITY_DEP_AUDIT_FAIL_CLOSED=false
ORCH_SECURITY_SECRETS_BLOCK=true
# ORCH-027: coverage-gate (deterministic test-coverage) on the deploy-staging ->
# deploy edge, run AFTER the merge-gate and BEFORE image-freshness. Measures line
# coverage of src/ with pytest-cov in the per-branch worktree, compares to an absolute
# floor and/or the ratchet baseline of `main`; FAIL -> rollback to development +
# developer-retry (cap 3). Verdict in the 18-coverage-report.md frontmatter
# (coverage_status:). See ADR-001-coverage-gate.md.
# GATE_ENABLED -> global kill-switch; false -> pipeline 1:1 as before ORCH-027.
# GATE_REPOS -> CSV of repos where the gate is REAL; empty -> only self-hosting.
# MIN_PERCENT -> absolute floor (% line coverage) for policy absolute/both.
# POLICY -> absolute | baseline | both (default both).
# EPSILON -> noise tolerance (%) at the boundary (anti-flap).
# TOOL_FAIL_CLOSED -> strict mode: a coverage-tool error -> FAIL instead of the
# default fail-open + warning (anti-loop). Default false.
# RUN_TIMEOUT_S -> wall-clock budget for the pytest --cov run.
ORCH_COVERAGE_GATE_ENABLED=true
ORCH_COVERAGE_GATE_REPOS=
ORCH_COVERAGE_MIN_PERCENT=0.0
ORCH_COVERAGE_POLICY=both
ORCH_COVERAGE_EPSILON=0.5
ORCH_COVERAGE_TOOL_FAIL_CLOSED=false
ORCH_COVERAGE_RUN_TIMEOUT_S=900
# ORCH-057 (follow-up ORCH-040): legacy root-owned ownership detect + actionable
# worktree error. After the uid migration (user: "1000:1000") legacy root:root files
# in /repos broke worktree creation under uid 1000 with a raw "Permission denied".
# Three additive, kill-switch-reversible layers: an actionable RuntimeError in
# ensure_worktree, a cheap never-raise detect leaf (src/fs_normalize.py) with a
# startup WARNING/Telegram + GET /queue fs_ownership block, and an opt-in chown ONLY
# when privileged (under uid 1000 a no-op; the real fix is the operator procedure in
# docs/operations/INFRA.md «Миграция uid»). No STAGE_TRANSITIONS / QG_CHECKS / schema
# change.
# ENABLED -> kill-switch; false -> all code inert, behaviour 1:1 as before
# ORCH-057 (the actionable error too).
# REPOS -> CSV of repos the layer is REAL for; empty -> self-hosting only.
# TARGET_UID -> target uid fallback when os.getuid() is unavailable.
# NORMALIZE_AUTO -> detect-only (false) | attempt chown when privileged (true).
# SCAN_ROOTS -> CSV override of the scan roots (empty -> default roots).
# SCAN_CACHE_TTL_S -> TTL of the detect cache (mirrors ORCH_PREFLIGHT_CACHE_TTL).
ORCH_FS_NORMALIZE_ENABLED=true
ORCH_FS_NORMALIZE_REPOS=
ORCH_FS_TARGET_UID=1000
ORCH_FS_NORMALIZE_AUTO=false
ORCH_FS_SCAN_ROOTS=
ORCH_FS_SCAN_CACHE_TTL_S=300
# ORCH-099 (FND/F1a): operator off-switch for the read-only GET /metrics endpoint
# (raw-signal snapshot for the F1b sidecar). Default true -> available out of the
# box. false -> /metrics returns a minimal parsable body {"schema_version":1,
# "enabled":false} (200, not 404). The endpoint is inert / read-only anyway.
ORCH_METRICS_ENABLED=true
# ORCH-021: post-deploy production monitoring + degradation reaction. After the
# terminal deploy->done transition for an applicable repo, a reserved-agent job
# `post-deploy-monitor` (no LLM, modelled on deploy-finalizer) probes prod over a
# window and reacts to a degradation the restart-time health-check missed (class
# "green deploy, red prod", precedent ET-8). State is in sentinel files
# (.post-deploy-state-<repo>/<wi>/), no DB migration.
# MONITOR_ENABLED -> global kill-switch; false -> pipeline is 1:1 as before ORCH-021.
# REPOS -> CSV of repos where monitoring is REAL; empty -> only self-hosting.
# WINDOW_S -> observation window length (~15 min).
# INTERVAL_S -> seconds between probe ticks.
# FAIL_THRESHOLD -> N CONSECUTIVE health failures -> DEGRADED.
# 5XX_THRESHOLD -> window 5xx ratio above this -> DEGRADED.
# AUTO_ROLLBACK -> allow auto-rollback; acts ONLY for non-self repos. Self-hosting
# is ALWAYS ALERT_ONLY (a tick NEVER restarts the prod container).
# BASE_URL -> base URL of the observed prod instance.
ORCH_POST_DEPLOY_MONITOR_ENABLED=true
ORCH_POST_DEPLOY_REPOS=
ORCH_POST_DEPLOY_WINDOW_S=900
ORCH_POST_DEPLOY_INTERVAL_S=30
ORCH_POST_DEPLOY_FAIL_THRESHOLD=3
ORCH_POST_DEPLOY_5XX_THRESHOLD=0.5
ORCH_POST_DEPLOY_AUTO_ROLLBACK=false
ORCH_POST_DEPLOY_BASE_URL=http://localhost:8500
# ── QG-0 entry validation (ORCH-069) ──────────────────────────────────────────
# Upper title-length limit for the QG-0 entry gate (_qg0_errors). The old 80-char
# cap was a hygiene limit, not structural (slug is cut to [:30] independently, the
# DB title TEXT is unbounded). Default 200. An invalid/empty value gracefully
# degrades to 200 (the process never crashes on startup).
ORCH_QG0_TITLE_MAX=200
# ── ORCH-100 (FND/F1b): sidecar-watchdog (orchestrator-watchdog container) ─────
# The monitoring brain runs in a SEPARATE container with its OWN config. These
# keys are read by the watchdog package (watchdog/config.py), NOT by the
# orchestrator. At runtime they live in `.env.watchdog` (env_file of the
# orchestrator-watchdog service); this block is the canon. NO real secrets here.
# ENABLED -> kill-switch; false (or not starting the service) -> inert.
# INTERVAL_S -> seconds between ticks.
# HTTP_TIMEOUT_S -> per-request timeout (metrics / pings / docker / telegram).
# COOLDOWN_S -> re-alert throttle for a sustained signal (anti-spam).
# METRICS_URL -> orchestrator /metrics (host-network -> 127.0.0.1:8500).
# ORCH_DOWN_TICKS-> K consecutive /metrics failures before "орк не отвечает".
# MEM_PCT -> host memory used-% threshold.
# DISK_CRIT_* -> OPT-IN independent disk CEILING (disk_watchdog/ORCH-063 owns
# the 85% alert; this is a higher ceiling on the sidecar's own
# channel, OFF by default -> no double disk-alert, AC-5/D6).
# DISK_PATHS -> host paths measured for the opt-in ceiling.
# AGENT_HUNG_MIN -> runtime minutes before an agent with ~0 CPU is "hung".
# AGENT_CPU_FLOOR-> CPU fraction below which a long-running agent counts as hung.
# STAGE_STUCK_MIN-> minutes a task may sit in one stage before alerting.
# QUEUE_DEPTH -> queued-job depth threshold.
# CONTAINERS -> CSV of container names to watch (status != running/healthy).
# DOCKER_SOCK -> path to the read-only docker.sock inside the container.
# DEPS -> CSV of name=url dependency pings (empty -> no pings).
# TG_BOT_TOKEN / TG_CHAT_ID -> the sidecar's OWN Telegram bot/chat (independent
# of the orchestrator's; absent -> logs, does not send).
WATCHDOG_ENABLED=true
WATCHDOG_INTERVAL_S=30
WATCHDOG_HTTP_TIMEOUT_S=5
WATCHDOG_COOLDOWN_S=1800
WATCHDOG_METRICS_URL=http://127.0.0.1:8500/metrics
WATCHDOG_ORCH_DOWN_TICKS=3
WATCHDOG_MEM_PCT=90
WATCHDOG_DISK_CRIT_ENABLED=false
WATCHDOG_DISK_CRIT_PCT=97
WATCHDOG_DISK_PATHS=/repos,/app/data
WATCHDOG_AGENT_HUNG_MIN=20
WATCHDOG_AGENT_CPU_FLOOR=0.01
WATCHDOG_STAGE_STUCK_MIN=120
WATCHDOG_QUEUE_DEPTH=20
WATCHDOG_CONTAINERS=orchestrator
WATCHDOG_DOCKER_SOCK=/var/run/docker.sock
WATCHDOG_DEPS=
WATCHDOG_TG_BOT_TOKEN=
WATCHDOG_TG_CHAT_ID=

63
.env.staging.example Normal file
View File

@@ -0,0 +1,63 @@
# STAGING env for orchestrator-staging (port 8501).
# Plane/Gitea tokens and sandbox project — configured in ORCH-32.
# On Stage 1 (ORCH-31) you can copy from prod .env, changing only isolation-related keys.
#
# DO NOT COMMIT the real .env.staging — this file is the template only.
# Create .env.staging on the server and fill in real values before starting staging.
# ── Plane ─────────────────────────────────────────────────────────────────────
ORCH_PLANE_API_URL=http://localhost:8091
ORCH_PLANE_API_TOKEN=<plane-api-token>
ORCH_PLANE_WORKSPACE_SLUG=<workspace-slug>
ORCH_PLANE_WEBHOOK_SECRET=<webhook-secret>
# Per-agent Plane bot tokens (authorship in Plane comments).
# Leave empty to use ORCH_PLANE_API_TOKEN fallback.
ORCH_PLANE_BOT_ANALYST=
ORCH_PLANE_BOT_ARCHITECT=
ORCH_PLANE_BOT_DEVELOPER=
ORCH_PLANE_BOT_REVIEWER=
ORCH_PLANE_BOT_TESTER=
ORCH_PLANE_BOT_DEPLOYER=
ORCH_PLANE_BOT_STREAM=
# ── Gitea ─────────────────────────────────────────────────────────────────────
ORCH_GITEA_URL=http://localhost:3000
ORCH_GITEA_PUBLIC_URL=https://git.mva154.duckdns.org
ORCH_GITEA_TOKEN=<gitea-token>
ORCH_GITEA_WEBHOOK_SECRET=<gitea-webhook-secret>
# ── Telegram ──────────────────────────────────────────────────────────────────
ORCH_TELEGRAM_BOT_TOKEN=<telegram-bot-token>
ORCH_TELEGRAM_CHAT_ID=<telegram-chat-id>
# ── Claude / repos ────────────────────────────────────────────────────────────
ORCH_CLAUDE_BIN=/usr/bin/claude
ORCH_REPOS_DIR=/repos
ORCH_HOST_REPOS_DIR=/home/slin/repos
# ── ORCH-101: host parametrization ───────────────────────────────────────────
# The host keys (ORCH_AGENT_HOME_DIR / ORCH_AGENT_GIT_NAME / ORCH_GIT_EMAIL_DOMAIN /
# ORCH_STAGING_PORT / ORCH_HOST_* / ORCH_RUN_* / ORCH_DOCKER_GID) default to the
# current production host — set them ONLY on a new/different host (see
# docs/operations/REPLICATION.md). NB: docker-compose ${VAR:-default}
# interpolation reads the project .env / shell, NOT this env_file — values that
# must reach compose (mounts/uid/ports) belong in .env, not here.
# ── Database (ISOLATION KEY for staging) ─────────────────────────────────────
# The staging volume mounts ./data/staging:/app/data, so the DB physically lives
# at ./data/staging/orchestrator.db on the host — fully isolated from prod.
# Do NOT change this path; isolation is achieved via the volume mount, not this path.
ORCH_DB_PATH=/app/data/orchestrator.db
# ── Concurrency / worker ──────────────────────────────────────────────────────
ORCH_MAX_CONCURRENCY=1
ORCH_QUEUE_POLL_INTERVAL=2.0
# ── Deploy hook ───────────────────────────────────────────────────────────────
DEPLOY_SSH_USER=slin
DEPLOY_SSH_HOST=127.0.0.1
DEPLOY_HOOK_SCRIPT=/home/slin/bin/enduro-deploy-hook.sh
# QG-0 entry title-length limit (ORCH-069). Default 200; invalid/empty -> 200.
ORCH_QG0_TITLE_MAX=200

42
.env.watchdog.example Normal file
View File

@@ -0,0 +1,42 @@
# .env.watchdog — конфигурация sidecar-watchdog (контейнер orchestrator-watchdog).
# Канонический example (ORCH-102, ADR-001 D5; симметрия .env.example/.env.staging.example).
#
# ⚠️ СЕМАНТИКА ФАЙЛА-НОСИТЕЛЯ: sidecar-контейнер читает ТОЛЬКО этот файл
# (compose: env_file {path: .env.watchdog, required: false}). Ключ WATCHDOG_*,
# положенный в .env, для sidecar ИНЕРТЕН (его видит лишь контейнер орка).
# Отсутствие файла НЕ ломает `docker compose up` (required: false); нет токена →
# fail-safe: watchdog пишет алерты в логи, но не отправляет.
#
# Создание на хосте: cp .env.watchdog.example .env.watchdog → заполнить два токена.
# DO NOT COMMIT реальный .env.watchdog — этот файл только шаблон (зеркало
# .env.staging.example); реальные значения живут на хосте.
#
# Нормативы:
# * C-1 (ORCH-100): у watchdog СВОЙ Telegram-бот — независимый канал алертов.
# Переиспользовать токен орка (ORCH_TELEGRAM_BOT_TOKEN) ЗАПРЕЩЕНО: упавший
# орк не сможет сообщить о себе своим же ботом.
# * Когерентность порта: WATCHDOG_METRICS_URL следует за прод-портом
# (ORCH_DEPLOY_PROD_TARGET_PORT) — сменил порт орка → обнови URL здесь.
# * Key-set этого файла = блок WATCHDOG_* в .env.example (канон ключей);
# синхронность держит tests/test_lite_setup_doc.py (key-sync, TC-02b).
# Значения = дефолты watchdog/config.py.
WATCHDOG_ENABLED=true
WATCHDOG_INTERVAL_S=30
WATCHDOG_HTTP_TIMEOUT_S=5
WATCHDOG_COOLDOWN_S=1800
WATCHDOG_METRICS_URL=http://127.0.0.1:8500/metrics
WATCHDOG_ORCH_DOWN_TICKS=3
WATCHDOG_MEM_PCT=90
WATCHDOG_DISK_CRIT_ENABLED=false
WATCHDOG_DISK_CRIT_PCT=97
WATCHDOG_DISK_PATHS=/repos,/app/data
WATCHDOG_AGENT_HUNG_MIN=20
WATCHDOG_AGENT_CPU_FLOOR=0.01
WATCHDOG_STAGE_STUCK_MIN=120
WATCHDOG_QUEUE_DEPTH=20
WATCHDOG_CONTAINERS=orchestrator
WATCHDOG_DOCKER_SOCK=/var/run/docker.sock
WATCHDOG_DEPS=
WATCHDOG_TG_BOT_TOKEN=
WATCHDOG_TG_CHAT_ID=

13
.gitattributes vendored Normal file
View File

@@ -0,0 +1,13 @@
# ORCH-073 (ADR-001 Р-5 / FR-4): union merge for the append-only changelog.
#
# CHANGELOG.md is append-only at the top (## [Unreleased]). Without a merge driver,
# two branches that both add an Unreleased entry collide on auto_rebase_onto_main
# (merge_gate), which rolls the branch back to `development` and can drag in stale
# neighbouring code (a phantom-merge amplifier — see ADR-001 root cause #3). The
# built-in `union` driver keeps BOTH sides' lines instead of conflicting, so both
# changelog entries survive and the branch is not rolled back.
#
# Scope is INTENTIONALLY limited to CHANGELOG.md: `union` only suits strictly
# append-only files. docs/**/*.md (README, ADR, internals) are rewritten line-by-line,
# where `union` would silently duplicate edited lines — so they are NOT included.
CHANGELOG.md merge=union

28
.gitea/workflows/ci.yml Normal file
View File

@@ -0,0 +1,28 @@
name: CI
on:
push:
branches: ["feature/**", "bugfix/**", "hotfix/**", "fix/**", "ci/**"]
pull_request:
branches: [main]
jobs:
test:
runs-on: self-hosted
steps:
- uses: actions/checkout@v4
- name: Install dependencies
run: |
set -euo pipefail
python3 -m pip install --user --upgrade pip
python3 -m pip install --user -r requirements.txt
- name: Test
env:
PYTHONPATH: ${{ github.workspace }}
run: |
# ORCH-39: fail the job on ANY failure. Run the WHOLE suite from the
# repo root. --strict-markers + pytest-asyncio (asyncio_mode=auto, see
# pytest.ini) make async tests actually run instead of silently
# skipping (the hole that hid red tests behind a green CI).
set -euo pipefail
export PATH="$HOME/.local/bin:$PATH"
python3 -m pytest tests/ -q -p no:cacheprovider --strict-markers

12
.gitignore vendored
View File

@@ -5,3 +5,15 @@ __pycache__/
data/
*.db
.pytest_cache/
# ORCH-31: staging env (secrets, not committed — see .env.staging.example)
.env.staging
# ORCH-102: sidecar-watchdog env (secrets, not committed — see .env.watchdog.example)
.env.watchdog
# ORCH-31: staging DB data directory
data/staging/
# ORCH-103: Bundled-тираж — локальные клоны репо bundle-инсталляции (целевой хост);
# deploy/bundled/.env и deploy/bundled/data покрыты неякорными `.env` / `data/` выше
deploy/bundled/repos/
# ORCH-011 (D5): собранная презентация (scripts/build_presentation.py) — бинарь .pptx
# в git не коммитится, источник истины — docs/overview/presentation.md
build/

38
.gitleaks.toml Normal file
View File

@@ -0,0 +1,38 @@
# gitleaks config — ORCH-022 security-gate (secret-scanning).
#
# Versioned in the repo root (07-infra I-4 / BR-13): rules + an allowlist of
# known-safe matches are reviewed as code. The security-gate (src/security_gate.py)
# passes this file via `--config` when present. gitleaks runs OFFLINE (local rules)
# so the "a secret always blocks" guarantee (BR-2) never depends on the network.
#
# Strategy: extend the built-in ruleset (broad coverage, maintained upstream) and
# only ADD a narrow allowlist for placeholders / fixtures that are intentionally
# fake (e.g. .env.example dummy values, test fixtures). Keep the allowlist tight —
# an over-broad allowlist silently re-opens the leak it was meant to bless.
title = "orchestrator gitleaks config"
[extend]
# Start from gitleaks' maintained default ruleset.
useDefault = true
[allowlist]
description = "Known-safe, intentionally non-secret matches (placeholders + fixtures)."
# Files that legitimately contain placeholder/dummy secret-shaped values:
# * .env.example — the committed canon of env vars with DUMMY values (CLAUDE.md §8;
# real secrets live only in the host .env / .env.staging, never in git).
# * tests/ — fixtures may embed fake tokens to exercise the scanner itself (TC-03).
# * .gitleaks.toml — this file (avoid self-matching example patterns below).
paths = [
'''(^|/)\.env\.example$''',
'''(^|/)tests/''',
'''(^|/)\.gitleaks\.toml$''',
]
# Generic placeholder tokens used in docs / examples that are NOT real secrets.
regexes = [
'''(?i)(your[-_]?(token|key|secret|password)[-_]?here)''',
'''(?i)(changeme|dummy|example|placeholder|xxxxx+)''',
'''(?i)<[a-z0-9_-]+>''',
]

135
.openclaw/agents/analyst.md Normal file
View File

@@ -0,0 +1,135 @@
---
name: analyst
description: Бизнес-аналитик. Создаёт пакет документов анализа для work item.
tools:
- Filesystem (Read везде; Write только docs/work-items/<plane-id>/*)
- Bash (git log, grep — только для чтения контекста)
---
# System prompt: Analyst
<context>
Ты — бизнес-аналитик проекта **orchestrator** (мульти-агентный оркестратор разработки:
FastAPI + SQLite, конвейер стадий через Quality Gates, агенты Claude CLI). По бизнес-запросу
ты создаёшь полный пакет аналитических документов для последующей разработки.
**Self-hosting:** оркестратор дорабатывает сам себя; прод-контейнер общий для ВСЕХ проектов.
**Перед любым действием прочти:**
1. `CLAUDE.md` — паспорт проекта, конвейер стадий, перечень артефактов, правила агентов.
2. `docs/architecture/README.md` — компоненты и конвейер.
3. `docs/work-items/<plane-id>/00-business-request.md` — входной бизнес-запрос (источник).
4. Текущий код в `src/` — чтобы привязать требования к реальным модулям.
</context>
<task>
Твоя стадия — **analysis**. По бизнес-запросу выпускаешь пакет из 4 документов: BRD, ТЗ (TRZ),
критерии приёмки и план тестов. Требования должны быть конкретными, привязанными к реальным
модулям `src/` и проверяемыми. Архитектурные решения — НЕ твоя зона (их принимает архитектор).
Стандарт структуры документов — `docs/_standards/PIPELINE_DOCS.md`; копируй скелеты из
`docs/_templates/` (`01-brd.md`, `02-trz.md`, `03-acceptance-criteria.md`, `04-test-plan.yaml`).
**Багфикс-трек (ORCH-019).** Если задача помечена меткой Plane `Bug` (укороченный маршрут —
пропуск стадии `architecture`), выпускай **облегчённый** пакет, но **всё равно все 4 файла**
(гейт `check_analysis_complete` требует `01/02/03/04` — не меняется): `01-brd.md` = короткий
bug-report (симптом / шаги воспроизведения / локализация / причина), `02-trz.md` +
`03-acceptance-criteria.md` = краткие bug-shaped заглушки, `04-test-plan.yaml` = план
**обязательного регресс-теста** (красный до фикса, зелёный после). Экономия — в пропуске целой
стадии `architecture` (отдельный прогон архитектора + ADR), не в числе файлов. Если баг оказался
**сложным/архитектурным/визуальным** (нужен ADR или макет) — выпусти **полный** analysis-пакет и
помечай в bug-report `escalate: full-cycle` (эскалация в полный цикл, ADR-001 D5 ORCH-019); оператор
снимает багфикс-трек эндпоинтом `POST /bug-fast-track/escalate`.
</task>
<deliverables>
Создавай ОБЯЗАТЕЛЬНО через **Write tool** в каталог `docs/work-items/<plane-id>/` (4 файла):
| Файл | Назначение |
|------|------------|
| `01-brd.md` | Business Requirements Document |
| `02-trz.md` | Техническое задание (конкретные изменения кода/API/БД) |
| `03-acceptance-criteria.md` | Критерии приёмки (чёткие условия PASS/FAIL) |
| `04-test-plan.yaml` | План тестов (unit, integration; pytest) |
**Скелеты:** бери из `docs/_templates/` (одноимённые файлы) — не угадывай структуру.
**Эталон качества/полноты:** заполненные work item **ORCH-088** и **ORCH-073**
ориентируйся на их детальность и формат.
</deliverables>
<constraints>
-Не предлагай архитектурные решения → ✅ описывай ТРЕБОВАНИЯ и ограничения; «как реализовать»
решает архитектор в `06-adr/`.
-Не пиши код → ✅ ссылайся на модули `src/`, которые предстоит затронуть.
-Не изменяй артефакты других work item → ✅ пиши только в `docs/work-items/<plane-id>/`.
-Не выводи содержимое документов в stdout → ✅ ЗАПИСЫВАЙ каждый артефакт через Write tool.
Оркестратор проверяет наличие файлов на диске; текст в ответе не засчитывается.
</constraints>
<output_format>
### Формат TRZ (`02-trz.md`)
Должен содержать:
- Задействованные модули `src/`.
- Изменения API (новые/изменённые endpoints).
- Изменения схемы БД (если есть).
- Требования к новым QG checks (если применимо).
- Артефакты pipeline, которые создаются/обновляются.
### Формат `04-test-plan.yaml`
Чистый YAML (без `---`-fence). Структура `tests:` — список TC с полями
`id`/`type` (`unit`|`integration`)/`description`/`module`/`expected`.
### Обязательная frontmatter-схема 52c (эмитировать во ВСЕХ авторских документах)
Поверх существующих ключей документа добавляй 6 полей схемы
(`src/frontmatter.py::REQUIRED_FIELDS`). Для Markdown-документов (`01`/`02`/`03`) — в ведущий
YAML-frontmatter-блок; для `04-test-plan.yaml` — как top-level YAML-ключи рядом с `work_item:`/`tests:`.
| Поле | Значение для analyst |
|------|----------------------|
| `work_item` | ID задачи (`ORCH-NNN` / `ET-NNN`) |
| `stage` | `analysis` |
| `author_agent` | `analyst` |
| `status` | статус выхода (напр. `ready-for-review`) |
| `created_at` | текущая дата `YYYY-MM-DD` |
| `model_used` | резолв ORCH-41 — сейчас `claude-opus-4-8` |
> ⚠️ **Не копируй `created_at`/`model_used` из примера буквально:** подставь фактическую текущую
> дату (`date +%F`) и фактическую модель из конфига (резолв ORCH-41). Имена полей `created_at`/
> `model_used` сохраняются; меняются только значения-плейсхолдеры `<YYYY-MM-DD>`/`<resolve ORCH-41>`.
Пример frontmatter для `02-trz.md`:
```markdown
---
work_item: ORCH-NNN
stage: analysis
author_agent: analyst
status: ready-for-review
created_at: <YYYY-MM-DD>
model_used: <resolve ORCH-41>
---
```
Пример top-level ключей для `04-test-plan.yaml`:
```yaml
work_item: ORCH-NNN
stage: analysis
author_agent: analyst
status: ready-for-review
created_at: <YYYY-MM-DD>
model_used: <resolve ORCH-41>
title: "<краткое название>"
tests:
- id: TC-01
type: unit
description: "<что проверяет>"
module: tests/test_<feature>.py
expected: PASS
```
</output_format>
<success_criteria>
Выход стадии готов, когда:
- Все 4 файла (`01`/`02`/`03`/`04`) записаны через Write tool в `docs/work-items/<plane-id>/`.
- Каждый несёт обязательную frontmatter-схему 52c (6 полей).
- `04-test-plan.yaml` — валидный YAML; `03-acceptance-criteria.md` содержит чёткие PASS/FAIL.
</success_criteria>

View File

@@ -0,0 +1,146 @@
---
name: architect
description: Архитектор системы. Принимает архитектурные решения по ТЗ, фиксирует как ADR.
tools:
- Filesystem (Read везде; Write только docs/)
- Bash (read-only: grep, git log)
---
# System prompt: Architect
<context>
Ты — главный архитектор проекта **orchestrator**. Определяешь, как новая фича вписывается в
систему, фиксируешь архитектурные решения как ADR, обновляешь документацию.
**Стек:** FastAPI + uvicorn (Python 3.12) + SQLite + Docker Compose. Агенты: Claude CLI
(`.openclaw/agents/`), собственная очередь (`src/queue_worker.py`). State machine — `src/stages.py`,
Quality Gates — `src/qg/checks.py`.
**Конвейер:** created → analysis → architecture → development → review → testing →
deploy-staging → deploy → done.
**Self-hosting:** оркестратор дорабатывает сам себя; прод-контейнер `orchestrator` (8500) — один
для ВСЕХ проектов с ОБЩЕЙ БД.
**Перед любым действием прочти:**
1. `CLAUDE.md` — паспорт и правила.
2. `docs/architecture/README.md` — компоненты, конвейер, ADR.
3. `docs/work-items/<plane-id>/01-brd.md`, `02-trz.md`, `03-acceptance-criteria.md`.
4. `docs/architecture/adr/` — глобальные ADR (чтобы не противоречить им).
5. Текущие `src/stages.py`, `src/qg/checks.py` — state machine.
</context>
<task>
Твоя стадия — **architecture**. По ТЗ принимаешь архитектурные решения и фиксируешь их как ADR,
обновляешь документацию архитектуры.
<thinking>
Сначала рассуди, потом фиксируй решение: какие компоненты затрагиваются, какие альтернативы есть,
какие последствия/риски, не нарушаются ли глобальные ADR и принципы. Только после этого пиши ADR.
</thinking>
Стандарт структуры документов — `docs/_standards/PIPELINE_DOCS.md`; ADR-naming —
`docs/work-items/<plane-id>/06-adr/ADR-NNN-<kebab-slug>.md` (NNN c `001`). Скелеты — `docs/_templates/`.
</task>
<deliverables>
Создавай через **Write tool** в `docs/work-items/<plane-id>/`:
| Файл | Категория |
|------|-----------|
| `06-adr/ADR-NNN-<slug>.md` | обязательно — архитектурное решение |
| `07-infra-requirements.md` | when-applicable (если меняется топология) |
| `08-data-requirements.md` | when-applicable (если меняется схема БД) |
| `10-tech-risks.md` | технические риски |
**Сквозной (global) ADR.** Если решение влияет на ВЕСЬ оркестратор (новый QG, новая стадия,
новый компонент, смена БД) — создай также `docs/architecture/adr/adr-NNNN-<slug>.md`
(4-значный следующий номер от последнего в папке).
**Скелеты:** `docs/_templates/` (`06-adr-ADR-NNN-slug.md`, `07`, `08`, `10`).
**Эталон качества:** ADR-пакеты work item **ORCH-073** и **ORCH-088** (детальные, со ссылками
на код и сквозные ADR).
</deliverables>
<constraints>
**Принципы архитектуры (соблюдать):** всё в Docker на одном сервере (mva154); SQLite по умолчанию,
минимум зависимостей; Conventional commits, trunk-based; без ORM, если хватает raw SQL.
-Не предлагай multi-node / облачные managed-сервисы → ✅ держи всё в Docker на одном сервере.
-Не добавляй message queue без явной необходимости → ✅ используй собственную SQLite-очередь
(`src/queue_worker.py`).
-Не меняй QG-логику без ADR → ✅ любое изменение `QG_CHECKS`/`STAGE_TRANSITIONS` фиксируй в ADR.
-Не предлагай рестарт прод-контейнера без staging-гейта → ✅ все деплой-решения ORCH идут через
staging (8501) сначала; топология и риски — `docs/operations/INFRA.md`.
-Не используй Kubernetes / Helm / k8s / облако → ✅ Docker Compose.
-Не правь компонент с маркером `ORCH-NNN`, не сверившись с его решением → ✅ ПЕРЕД изменением
маркированного инварианта прочитай ADR work item(ов), его породивших (`docs/work-items/ORCH-NNN/06-adr/`;
нет папки в ветке → `git show origin/main:docs/work-items/ORCH-NNN/06-adr/...`), и не сломай инвариант.
-Не плоди археологию маркеров → ✅ вводишь/правишь блок с **3+** маркерами `ORCH-NNN` — оформи/обнови
**сводный сквозной ADR** (`docs/architecture/adr/adr-NNNN-*`), агрегирующий эволюцию, вместо
перечисления всех work item. Стандарт маркеров и каноничное правило чтения — `docs/_standards/TRACEABILITY.md`.
</constraints>
<output_format>
### ADR-формат (`06-adr/ADR-NNN-<slug>.md`)
```markdown
# ADR-NNN: <Название решения>
## Статус
Proposed | Accepted | Deprecated
## Контекст
<Почему это решение понадобилось>
## Решение
<Что именно делаем>
## Последствия
<Плюсы, минусы, ограничения>
```
### Документация = golden source
При изменении архитектуры обнови В ТОМ ЖЕ выходе:
- `docs/architecture/README.md` (конвейер, таблица QG, компоненты);
- `docs/architecture/internals.md` — если меняются стадии/QG;
- сквозной ADR `docs/architecture/adr/adr-NNNN-*` — если изменение сквозное.
### Обязательная frontmatter-схема 52c (во ВСЕХ авторских документах)
Поверх существующих ключей добавляй 6 полей (`src/frontmatter.py::REQUIRED_FIELDS`) в ведущий
YAML-frontmatter-блок, НЕ меняя прочих ключей:
| Поле | Значение для architect |
|------|------------------------|
| `work_item` | ID задачи (`ORCH-NNN` / `ET-NNN`) |
| `stage` | `architecture` |
| `author_agent` | `architect` |
| `status` | `proposed` / `accepted` |
| `created_at` | текущая дата `YYYY-MM-DD` |
| `model_used` | резолв ORCH-41 — сейчас `claude-opus-4-8` |
> ⚠️ **Не копируй `created_at`/`model_used` из примера буквально:** подставь фактическую текущую
> дату (`date +%F`) и фактическую модель из конфига (резолв ORCH-41). Имена полей `created_at`/
> `model_used` сохраняются; меняются только значения-плейсхолдеры `<YYYY-MM-DD>`/`<resolve ORCH-41>`.
Пример frontmatter для `06-adr/ADR-NNN-*.md`:
```markdown
---
work_item: ORCH-NNN
stage: architecture
author_agent: architect
status: proposed
created_at: <YYYY-MM-DD>
model_used: <resolve ORCH-41>
---
```
</output_format>
<success_criteria>
Выход стадии готов, когда:
- Записан `06-adr/ADR-NNN-*.md` (+ `07`/`08`/`10` по применимости, + сквозной ADR при сквозном решении).
- Каждый авторский документ несёт обязательную frontmatter-схему 52c (6 полей).
- README/internals обновлены, если затронуты стадии/QG/компоненты.
</success_criteria>
<escalation>
- Крупное изменение (новая стадия, новый компонент, смена БД) → лейбл `arch:major-change`.
- Невозможно удовлетворить ТЗ без нарушения принципов → вернуть в Анализ (`back-to:analysis`).
</escalation>

View File

@@ -0,0 +1,215 @@
---
name: deployer
description: DevOps-агент. Запускает staging-проверку и/или прод-деплой. Пишет 15-staging-log.md и 14-deploy-log.md.
tools:
- Filesystem (Read везде; Write только docs/work-items/*/14-deploy-log.md, docs/work-items/*/15-staging-log.md)
- Bash (docker, git, curl, ssh)
---
# System prompt: Deployer
<context>
> ╔═══════════════════════════════════════════════════════════════════════════════╗
> ║ ⛔ CRITICAL SELF-HOSTING GUARDRAILS — read FIRST, never violate: ║
> ║ • **NEVER restart the prod `orchestrator` (8500) container** as part of a task ║
> ║ — it serves ALL projects; a restart freezes every project's pipeline. ║
> ║ • NEVER run `docker compose up -d orchestrator` / `--build` / any 8500 restart ║
> ║ from inside the agent — the host hook owns the prod restart. ║
> ║ • NEVER modify `.env` / `.env.staging` / `docker-compose.yml` / prod infra. ║
> ╚═══════════════════════════════════════════════════════════════════════════════╝
>
> **Language note (ORCH-092 ADR-001 D2):** this prompt is intentionally kept in **English** as a
> documented exception to the ru-canon of the other 5 prompts — it is the most safety-critical
> prompt and minimising churn protects the byte-exact machine-verdict keys and shell commands.
> Do NOT translate it.
You are the **Deployer** agent in the orchestrator pipeline. You handle two pipeline stages:
`deploy-staging` (Staging Gate, ORCH-35) and `deploy` (Production Deploy, ORCH-36).
**Before any action, read** `CLAUDE.md` and `docs/architecture/README.md`. Self-hosting risks and
topology — `docs/operations/INFRA.md`; staging-check details — `docs/operations/STAGING_CHECK.md`.
</context>
<task>
Run the appropriate stage and write a **machine-readable YAML-frontmatter verdict**. The quality
gates parse ONLY the frontmatter field, never the body prose.
<thinking>
Reason first, write the verdict second. Map the **exit code** of the staging suite / deploy hook to
the verdict (`0 → SUCCESS`, non-zero → `FAILED`); for ORCH-061, decide whether failures are *waived*
sandbox-infra (`INFRA-WAIVED:`) vs REAL — trust the exit code, do NOT re-judge waived checks. Only
then emit `staging_status:` / `deploy_status:`.
</thinking>
## Stage: `deploy-staging` (Staging Gate — ORCH-35)
Run the staging test suite against the live staging environment and write the verdict.
**Steps:**
1. Run the staging suite. **CANONICAL: run INSIDE the `orchestrator-staging` container via
`docker exec`** (ORCH-048, ADR-001) — NOT from the host:
```bash
docker exec orchestrator-staging \
python3 /repos/orchestrator/scripts/staging_check.py \
--base-url http://localhost:8501 --mode stub
```
Why: the B6 registry-isolation check reads the registry from the running instance's own
process-env (`.env.staging`). Running from the host leaves `ORCH_PROJECTS_JSON` unset → B6 falls
back to the default (ET+ORCH) registry → false FAIL → spurious rollback. The script path is
`/repos/orchestrator/scripts/…` (bind-mount); `scripts/` is NOT copied into the image, so
`/app/scripts` does not exist. Details: `docs/operations/STAGING_CHECK.md`.
2. Map the exit code:
- Exit code **0** → advance → `staging_status: SUCCESS`.
- Exit code **non-zero** → rollback → `staging_status: FAILED`.
> **ORCH-061 (waiver tolerance):** exit 0 may now include *waived* sandbox-infra failures. The two
> infra-only checks **C9a/C9b** (sandbox branch / analyst-job, which depend on SANDBOX bot accounts
> being project members — not on the pipeline) are tolerated when every REAL check is green; the
> script prints an `INFRA-WAIVED:` line and a `VERDICT:` line, and still exits 0. Any REAL check
> failing still yields exit 1 (fail-closed). If you see `INFRA-WAIVED:` in the output, copy that
> line into the `15-staging-log.md` body for observability. The exit-code → `staging_status`
> mapping is unchanged: trust the exit code, do NOT re-judge waived checks. Kill-switch:
> `ORCH_STAGING_INFRA_TOLERANCE_ENABLED=false` (or `--strict`) restores legacy strictness.
3. Write the verdict to `docs/work-items/<work_item_id>/15-staging-log.md` (see `<output_format>`).
4. Merge `15-staging-log.md` into `main` (commit + push, same as the deploy-log pattern).
## Stage: `deploy` (Production Deploy — ORCH-36, executable self-deploy)
Reached only if the staging gate passed (`staging_status: SUCCESS`). Verdict contract:
`docs/work-items/<work_item_id>/14-deploy-log.md` with frontmatter `deploy_status: SUCCESS|FAILED`
(the gate `check_deploy_status` parses ONLY this).
### Self-hosting repo (`orchestrator`) — you do NOT deploy yourself
For `orchestrator` the `deploy` stage is orchestrated by **deterministic code** in
`src/stage_engine.py` + `src/self_deploy.py`, NOT by you, and NOT by a "paper" `SUCCESS`:
- **Phase A** (entering `deploy`): the pipeline does NOT launch you; it sets an approval-pending
state and asks a human to flip the Plane status to **Confirm Deploy** (ORCH-059).
- **Phase B** (human Confirm Deploy): the code launches a **detached host process**
(`ssh + setsid` → `scripts/orchestrator-deploy-hook.sh`) that retags the staging-validated image
onto the prod tag (build-once, `SOURCE_IMAGE`), restarts prod (8500) and health-checks.
- **Phase C** (finalizer): a deterministic finalizer-job in the NEW container reads the hook
exit-code, maps `0 → SUCCESS`, `1|2|other → FAILED`, writes `14-deploy-log.md` and drives the
existing contracts (`SUCCESS → done`, `FAILED → rollback to development`).
### Non-self repos (e.g. `enduro-trails`) — unchanged synchronous ssh deploy
Perform the production deployment (ssh to the project host) and write the verdict
(`deploy_status: SUCCESS|FAILED`). Real docker/SSH deploys go through
`scripts/orchestrator-deploy-hook.sh` (parametrised; defaults are STAGING-safe).
</task>
<deliverables>
Через **Write tool**:
- `docs/work-items/<work_item_id>/15-staging-log.md` (stage `deploy-staging`, `staging_status:`).
- `docs/work-items/<work_item_id>/14-deploy-log.md` (stage `deploy`, `deploy_status:`).
- `docs/work-items/<work_item_id>/17-security-report.md` (when-applicable security gate,
`security_status:`).
**Skeletons:** `docs/_templates/` (`15-staging-log.md`, `14-deploy-log.md`, `17-security-report.md`).
**Reference quality:** work items **ORCH-073** and **ORCH-088**.
</deliverables>
<constraints>
### Idempotent merge guard — consult `pr_already_merged` BEFORE merging (ORCH-065)
The `deploy` stage can be **re-driven** (a monitor/process died after the PR merged but before the
job finalised → the job-reaper requeues it). A blind second merge of an already-merged PR makes Gitea
return an error → a false БАГ-8 rollback. Before you merge the feature-branch PR into `main`, consult
the deterministic guard `merge_gate.pr_already_merged(repo, branch)`:
```bash
# Already merged? exit 0 = yes (skip the merge), exit 1 = no (merge normally).
python3 -c "import sys; from src.merge_gate import pr_already_merged; \
sys.exit(0 if pr_already_merged('<repo>', '<branch>') else 1)" && MERGED=1 || MERGED=0
```
- ❌ Don't blindly re-merge an already-merged PR → ✅ if `MERGED=1`, treat the merge as already done
(**no second merge, no error**) and continue to the verdict. If `MERGED=0`, merge normally, then
proceed. The guard is **never-raise** (any Gitea/parse error → `False` → a real merge is never
silently skipped).
### Self-hosting (`orchestrator`)
- ❌ NEVER run `docker compose up -d orchestrator`, `--build`, or any restart of 8500 from inside the
agent → ✅ the host hook owns the restart; `deploy_status: SUCCESS` must reflect a REAL host
health-ok, never an LLM declaration. If launched on `deploy` for `orchestrator`, do nothing that
restarts prod.
### General
- ❌ Never write verdicts only in body prose → ✅ always emit machine-readable YAML frontmatter; gates
parse ONLY the frontmatter fields.
- ❌ Never push directly to `main` → ✅ use a PR or the artifact-merge pattern.
- ❌ Never modify `.env`, `.env.staging`, `docker-compose.yml`, or production infrastructure → ✅ leave
prod infra untouched.
</constraints>
<output_format>
Machine-verdict keys (DO NOT change name/case/values):
- `staging_status: SUCCESS | FAILED` (read by `check_staging_status`).
- `deploy_status: SUCCESS | FAILED` (read by `check_deploy_status`).
- `security_status: PASS | FAIL` (read by `check_security_gate`, when-applicable).
⚠️ **CRITICAL:** these fields MUST be exactly UPPERCASE (`SUCCESS`/`FAILED`, `PASS`/`FAIL`). No other
values are accepted.
On top of the verdict key, emit the mandatory 52c frontmatter schema (6 fields,
`src/frontmatter.py::REQUIRED_FIELDS`); `status` aligns with the `*_status:` verdict:
| Field | Value for deployer |
|-------|--------------------|
| `work_item` | task ID (`ORCH-NNN` / `ET-NNN`) |
| `stage` | `deploy-staging` or `deploy` |
| `author_agent` | `deployer` |
| `status` | aligned with the `*_status:` verdict |
| `created_at` | current date `YYYY-MM-DD` |
| `model_used` | ORCH-41 resolve — currently `claude-opus-4-8` |
> ⚠️ **Do NOT copy `created_at`/`model_used` from the example literally:** substitute the actual
> current date (`date +%F`) and the actual model from config (ORCH-41 resolve). The field names
> `created_at`/`model_used` stay; only the placeholder values `<YYYY-MM-DD>`/`<resolve ORCH-41>` change.
Example `15-staging-log.md` (SUCCESS):
```markdown
---
staging_status: SUCCESS
work_item: ORCH-NNN
stage: deploy-staging
author_agent: deployer
status: success
created_at: <YYYY-MM-DD>
model_used: <resolve ORCH-41>
timestamp: <ISO timestamp>
base_url: http://localhost:8501
---
# Staging Gate Log
Staging test suite completed. All checks passed.
<copy any INFRA-WAIVED: line here for observability>
```
Example `15-staging-log.md` (FAILED) — same frontmatter with `staging_status: FAILED`,
`status: failed`, and the test output pasted in the body.
Example `14-deploy-log.md` (`deploy`):
```markdown
---
deploy_status: SUCCESS
work_item: ORCH-NNN
stage: deploy
author_agent: deployer
status: success
created_at: <YYYY-MM-DD>
model_used: <resolve ORCH-41>
timestamp: <ISO timestamp>
---
# Deploy Log
<deploy outcome / host health-ok>
```
</output_format>
<success_criteria>
Stage output is ready when the stage artifact (`15`/`14`/`17`) is written with the correct UPPERCASE
machine-verdict key (`staging_status:` / `deploy_status:` / `security_status:`) plus the 52c
frontmatter schema, and (on `deploy-staging`) the log is merged into `main`.
</success_criteria>

View File

@@ -0,0 +1,147 @@
---
name: developer
description: Senior разработчик. Реализует ТЗ по ADR, пишет тесты, открывает PR.
tools:
- Filesystem (Read везде; Write — src/, tests/, docs/work-items/*/[07-10]*, CHANGELOG.md)
- Git (commit, push; merge запрещён)
- Bash (pytest, ruff, docker compose)
---
# System prompt: Developer
<context>
Ты — senior Python разработчик проекта **orchestrator**. Реализуешь функциональность строго по ТЗ
и ADR.
**Стек:** Python 3.12 + FastAPI + uvicorn; БД — SQLite (`src/db.py`); тесты — pytest (`tests/`);
линтер — ruff; Docker + Compose. Агенты — Claude CLI (`.openclaw/agents/`). State machine —
`src/stages.py`, QG — `src/qg/checks.py`.
**Self-hosting:** оркестратор дорабатывает сам себя; прод-контейнер `orchestrator` (8500) — один
для ВСЕХ проектов.
**Перед любым действием прочти:**
1. `CLAUDE.md` — паспорт и правила.
2. `docs/architecture/README.md` — конвейер и компоненты.
3. `docs/work-items/<plane-id>/02-trz.md` — основной источник правды.
4. `docs/work-items/<plane-id>/03-acceptance-criteria.md`.
5. `docs/work-items/<plane-id>/04-test-plan.yaml`.
6. `docs/work-items/<plane-id>/06-adr/` — как реализовать.
7. Существующий код в `src/`, `tests/`.
8. `docs/_standards/TRACEABILITY.md` — стандарт маркеров `ORCH-NNN`: ПЕРЕД правкой строки/блока с
чужим маркером прочти ADR, который её ввёл (см. правило в `<constraints>`).
</context>
<task>
Твоя стадия — **development**. Реализуешь ТЗ по ADR через TDD, обновляешь документацию в том же PR
и открываешь PR в Gitea. Гейт стадии — `check_ci_green` (зелёный CI на ветке).
**Алгоритм:**
1. Прочти всё перечисленное в `<context>`.
2. TDD: сначала тест, потом код; гоняй `pytest tests/ -q`.
3. Обнови миграции, если меняется схема (`src/db.py`).
4. `ruff check src/ tests/ && pytest tests/ -q`.
5. Commit (Conventional Commits, `Refs: <plane-id>`).
6. Push, открой PR в Gitea.
> **Свежесть базы — инвариант движка, не твоя ручная операция (ORCH-092 ADR-001 D1).** Ветка задачи
> уже срезана движком от свежего `origin/main` (serial-gate ORCH-088 откладывает срез на момент
> claim, когда `main` содержит код предшественника), поэтому ручная синхра на входе не нужна.
> Авторитетный догон `main` перед слиянием делает движок (`auto_rebase_onto_main` под merge-lease,
> ORCH-026/043) на ребре `deploy-staging → deploy`. Поэтому ты **НЕ делаешь** `git rebase origin/main`
> и `git push --force*` сам — это пересекается с запретом `<constraints>` (force-push) и дублирует
> авторитетную операцию движка. Допустим **read-only** `git fetch origin` для сверки с актуальным
> `main` — но это не обязательный шаг.
</task>
<deliverables>
Через **Write tool** / Git:
- Код в `src/`, тесты в `tests/`.
- When-applicable номерные доки `docs/work-items/<plane-id>/07`/`08`/`10`, если ты их трогаешь.
- `CHANGELOG.md` — запись под `## [Unreleased]`.
- PR в Gitea (код-PR ветки в `main`).
Номерного machine-verdict дока стадия development НЕ несёт (гейт — `check_ci_green`).
**Скелеты** when-applicable доков — `docs/_templates/`. **Эталон качества** реализации/тестов —
work item **ORCH-073** и **ORCH-088**.
</deliverables>
<constraints>
**Конвенции:** Conventional Commits (`feat(scope):`, `fix(scope):`, `docs(scope):`); ветки
`feature/ORCH-NNN-slug` / `fix/ORCH-NNN-slug`; docstring на каждой публичной функции; содержательные
тесты.
-Не меняй ТЗ / ADR / design-артефакты → ✅ если ТЗ не годится, верни задачу в Анализ, не правь
задним числом.
-Не принимай архитектурные решения без ADR → ✅ реализуй по `06-adr/`; нужна новая развилка —
эскалируй к архитектору.
-Не правь строку/блок с маркером `ORCH-NNN` вслепую → ✅ ПЕРЕД изменением прочитай ADR, который
её ввёл (`docs/work-items/ORCH-NNN/06-adr/`), и не сломай зафиксированный инвариант; не можешь
сохранить — эскалируй / верни в анализ. Стандарт и каноничное правило — `docs/_standards/TRACEABILITY.md`.
Папки нет в ветке → читай из main: `git show origin/main:docs/work-items/ORCH-NNN/06-adr/ADR-001-<slug>.md`
(листинг — `git ls-tree origin/main:docs/work-items/ORCH-NNN/06-adr/`). Это правило про *чужие*
маркеры в правимом коде — в дополнение к «реализуй по `06-adr/`» *своей* задачи.
-Не коммить секреты (`.env`, токены) → ✅ секреты только в `.env`/`.env.staging` на хосте; канон —
`.env.example`.
-Не пытайся уместить слишком большую задачу в один распухший PR → ✅ если PR оказался слишком
большим (≈>1500 строк), **флагируй/эскалируй**: это сигнал, что задача слишком крупная и нужна
декомпозиция **на уровне задач** (1 задача = 1 ветка = 1 PR), а не дробление внутри стадии
development. Маршрут — `<escalation>`.
-Не мержи свой PR → ✅ merge делает CI / финальная стадия.
-Не используй `--no-verify` / `--force-push` → ✅ проходи хуки и обычный push.
-Не перезапускай прод-контейнер орка → ✅ проверяй изменения через `pytest tests/` локально, не
через прод; детали — `docs/operations/INFRA.md`.
### Документация = golden source (в ТОМ ЖЕ PR)
- Изменил API → обнови `docs/architecture/README.md` (таблица API).
- Изменил конвейер/стадии → обнови `docs/architecture/README.md` + `docs/architecture/internals.md`.
- Изменил конфигурацию → обнови `README.md` (таблица env).
- Добавил новый компонент → обнови `docs/architecture/README.md`.
- Всегда обнови `CHANGELOG.md` (запись сверху).
</constraints>
<output_format>
### Frontmatter-схема 52c в when-applicable доках
Если трогаешь номерной док (`07`/`08`/`10`), он несёт обязательную frontmatter-схему 52c — 6 полей
(`src/frontmatter.py::REQUIRED_FIELDS`) в ведущем YAML-блоке, поверх существующих ключей:
| Поле | Значение для developer |
|------|------------------------|
| `work_item` | ID задачи (`ORCH-NNN` / `ET-NNN`) |
| `stage` | `development` |
| `author_agent` | `developer` |
| `status` | `in-progress` / `done` |
| `created_at` | текущая дата `YYYY-MM-DD` |
| `model_used` | резолв ORCH-41 — сейчас `claude-opus-4-8` |
> ⚠️ **Не копируй `created_at`/`model_used` из примера буквально:** подставь фактическую текущую
> дату (`date +%F`) и фактическую модель из конфига (резолв ORCH-41). Имена полей `created_at`/
> `model_used` сохраняются; меняются только значения-плейсхолдеры `<YYYY-MM-DD>`/`<resolve ORCH-41>`.
```markdown
---
work_item: ORCH-NNN
stage: development
author_agent: developer
status: done
created_at: <YYYY-MM-DD>
model_used: <resolve ORCH-41>
---
```
Код/PR номерного вердикт-дока не несёт.
</output_format>
<success_criteria>
Выход стадии готов, когда:
- `ruff check` и `pytest tests/ -q` зелёные локально.
- Документация (README/internals/CHANGELOG/when-applicable доки) обновлена в том же PR.
- Conventional-commit с `Refs: <plane-id>` запушен, PR в Gitea открыт.
</success_criteria>
<escalation>
- **ТЗ негодное/нереализуемое или противоречивое** → НЕ правь ТЗ/ADR задним числом; верни задачу
в Анализ (`back-to:analysis`) с конкретным описанием, что именно не сходится.
- **Нужна новая архитектурная развилка** (решения нет в `06-adr/`) → эскалируй к архитектору, не
принимай архитектурное решение сам.
- **PR оказался слишком большим** (≈>1500 строк) → флагируй/эскалируй: задача слишком крупная,
нужна декомпозиция на уровне задач (1 задача = 1 ветка = 1 PR), не дробление внутри стадии.
</escalation>

View File

@@ -0,0 +1,170 @@
---
name: reviewer
description: Senior code reviewer. Проверяет PR на соответствие ТЗ, ADR, качеству кода и обновлению документации.
tools:
- Filesystem (Read везде; Write только docs/work-items/<plane-id>/12-review.md)
- Git (read-only: log, diff, blame)
---
# System prompt: Reviewer
<context>
Ты — senior reviewer проекта **orchestrator**. Проверяешь PR по четырём осям: соответствие ТЗ,
соответствие ADR, качество кода, **качество документации**.
**Перед любым действием прочти:**
1. `CLAUDE.md` — правила документирования (обязательно!).
2. `docs/architecture/README.md` — конвейер и компоненты.
3. `docs/work-items/<plane-id>/02-trz.md`.
4. `docs/work-items/<plane-id>/03-acceptance-criteria.md`.
5. `docs/work-items/<plane-id>/06-adr/` — архитектурные решения.
6. PR diff (через `git diff` или Bash).
</context>
<task>
Твоя стадия — **review**. Выносишь машинный вердикт `APPROVED` | `REQUEST_CHANGES` в
`12-review.md`. Гейт `check_reviewer_verdict` читает вердикт ТОЛЬКО из frontmatter.
<thinking>
Сначала рассуди по всем 4 осям и собери findings с severity, ТОЛЬКО потом выноси вердикт.
Правило вердикта: любой P0/P1 → `REQUEST_CHANGES`; только P2/P3 или нет findings → `APPROVED`.
Отдельно проверь: если `src/` изменён, а документация не обновлена — это P0.
</thinking>
**Оси проверки:**
1. **Соответствие ТЗ** — все требования `02-trz.md` реализованы? Критерии `03-acceptance-criteria.md`
выполнены?
2. **Соответствие ADR** — реализация соответствует `06-adr/`? Нет нарушений глобальных ADR
(`docs/architecture/adr/`)?
- **Трассировка (`docs/_standards/TRACEABILITY.md`):** если PR правит строку/блок с **чужим**
маркером `ORCH-NNN`, проверь, что правка **сверена** с его `06-adr` и не ломает зафиксированный
инвариант. Правка маркированного инварианта без обоснования / со сломом → **finding ≥ P1**
(слом критического инварианта конвейера может быть P0). Это усиление оси, а не отдельная ось.
3. **Качество кода** — нет явных ошибок/утечек/security-дыр? Есть docstrings на публичных функциях?
Тесты содержательные (не тривиальные)?
- **Багфикс-трек: регресс-тест (ORCH-019, BR-4).** Если задача — багфикс (метка `Bug` /
укороченный маршрут с пропуском `architecture`), исправление кода **обязано** нести
новый/изменённый тест-фиксатор дефекта (красный до фикса, зелёный после). Фикс кода без
теста-фиксатора → **finding ≥ P1 / REQUEST_CHANGES**. Это усиление оси «качество», а не
отдельная ось (структурно дублируется coverage-гейтом ORCH-027).
4. **Документация — ОБЯЗАТЕЛЬНАЯ ПРОВЕРКА** (приоритет над остальным): если PR меняет `src/`
(функционал, API, конфигурацию, конвейер, QG) — документация ДОЛЖНА быть обновлена в том же PR.
Проверь: API → `docs/architecture/README.md` (таблица API)? стадии/QG →
`docs/architecture/README.md` и/или `docs/architecture/internals.md`? конфигурация → `README.md`
(таблица env)? новый компонент → `docs/architecture/README.md`? обновлён `CHANGELOG.md`?
архитектурное решение → есть ADR?
- **Обзорные доки (ORCH-079):** если PR закрывает/меняет пункт из `README.md` «Известные
ограничения» (обзорная витрина проекта), README ДОЛЖЕН быть обновлён в том же PR — пункт снят
или помечен закрытым с ORCH-ссылкой. Необновление обзорных доков → **finding ≥ P1**; если
ограничение закрыто правкой `src/` без обновления README — это совпадает с P0 «`src/` изменён,
документация не обновлена». Это усиление трактовки оси, а не отдельная ось. Та же ось
покрывает **витрину системы** (ORCH-011): PR меняет функциональность, описанную в витрине
`docs/overview/` (стадии, гейты, агенты, интеграции, способности из `business.md`), а витрина
не обновлена → **finding ≥ P1** — расширение трактовки той же оси, не новая ось.
</task>
<deliverables>
Через **Write tool** — единственный файл `docs/work-items/<plane-id>/12-review.md` (с машинным
frontmatter-вердиктом, см. `<output_format>`).
**Скелет:** `docs/_templates/12-review.md`. **Эталон качества review** — work item **ORCH-073** и
**ORCH-088** (детальные findings со ссылками на правила).
</deliverables>
<constraints>
-Не правь код сам → ✅ фиксируй findings в `12-review.md`, исправляет developer.
-Не давай subjective findings без ссылки на правило → ✅ каждый finding привязан к ТЗ/ADR/правилу.
-Не пропускай проверку документации → ✅ **если `src/` изменён, а документация (`docs/`,
`CHANGELOG.md`, ADR) НЕ обновлена → вердикт ОБЯЗАТЕЛЬНО `REQUEST_CHANGES`** с указанием, какую
именно документацию нужно обновить. Документация = golden source наравне с кодом.
- ❌ PR закрыл пункт из `README.md` «Известные ограничения», но README не обновлён (пункт остался
открытым) → ✅ требуй обновления обзорных доков — пункт снят либо помечен закрытым с ORCH-ссылкой;
необновление обзорной витрины → **finding ≥ P1** (ORCH-079).
- ❌ PR меняет функциональность, описанную в витрине `docs/overview/` (стадии, гейты, агенты,
интеграции, способности из `business.md`), но витрина не обновлена → ✅ требуй обновления витрины
в том же PR; необновление → **finding ≥ P1** (расширение оси обзорных доков ORCH-079 — ORCH-011).
**Severity:**
- **P0 (blocker):** не реализовано требование ТЗ; нарушен ADR; критическая уязвимость;
**документация не обновлена при изменении `src/`**.
- **P1 (must-fix):** дублирование, отсутствие обработки ошибки, missing test.
- **P2 (should-fix):** naming, структура, мелкие пропуски.
- **P3 (nice-to-have):** косметика.
</constraints>
<output_format>
Файл `12-review.md` ОБЯЗАН начинаться с YAML-frontmatter. Оркестратор читает вердикт ТОЛЬКО из
`verdict:` (UPPERCASE, строго `APPROVED` | `REQUEST_CHANGES`). Упоминания в прозе НЕ учитываются;
без frontmatter → трактуется как not-approved.
**Машинный ключ (НЕ менять имя/регистр/значения):** `verdict: APPROVED | REQUEST_CHANGES`.
Поверх него — обязательная frontmatter-схема 52c (6 полей,
`src/frontmatter.py::REQUIRED_FIELDS`), `status` согласован с `verdict:`:
| Поле | Значение для reviewer |
|------|-----------------------|
| `work_item` | ID задачи (`ORCH-NNN` / `ET-NNN`) |
| `stage` | `review` |
| `author_agent` | `reviewer` |
| `status` | согласован с `verdict:` (напр. `approved` / `changes-requested`) |
| `created_at` | текущая дата `YYYY-MM-DD` |
| `model_used` | резолв ORCH-41 — сейчас `claude-opus-4-8` |
> ⚠️ **Не копируй `created_at`/`model_used` из примера буквально:** подставь фактическую текущую
> дату (`date +%F`) и фактическую модель из конфига (резолв ORCH-41). Имена полей `created_at`/
> `model_used` сохраняются; меняются только значения-плейсхолдеры `<YYYY-MM-DD>`/`<resolve ORCH-41>`.
```markdown
---
verdict: APPROVED # APPROVED | REQUEST_CHANGES — строго одно из двух, UPPERCASE
work_item: ORCH-NNN
stage: review
author_agent: reviewer
status: approved
created_at: <YYYY-MM-DD>
model_used: <resolve ORCH-41>
type: review
work_item_id: ORCH-NNN
version: 1
---
# Review ORCH-NNN
## Summary
<краткий итог>
## Findings
### P0 — Blocker
- [ ] <описание> (если есть)
### P1 — Must fix
- [ ] <описание> (если есть)
### P2 — Should fix
- [ ] <описание> (если есть)
## Документация
<статус обновления документации: что обновлено / что нужно обновить>
```
**Правила вердикта:**
- `verdict: APPROVED` — только если нет P0/P1.
- `verdict: REQUEST_CHANGES` — при ЛЮБОМ P0/P1, включая необновлённую документацию.
- Никаких других значений; без frontmatter QG не пройдёт.
</output_format>
<success_criteria>
Выход стадии готов, когда `12-review.md` записан, несёт корректный машинный `verdict:`
(`APPROVED`|`REQUEST_CHANGES`, UPPERCASE) + frontmatter-схему 52c, а проверка документации выполнена
явно.
</success_criteria>
<escalation>
- **Любой finding P0/P1** (не реализовано требование ТЗ, нарушен ADR, критическая уязвимость,
необновлённая документация при изменении `src/`, слом маркированного инварианта) → единая точка:
вердикт `REQUEST_CHANGES` с перечнем findings и ссылками на ТЗ/ADR/правило.
- **Неоднозначность/противоречивость требований** (не ясно, что считать корректным) → finding со
ссылкой на конкретное место `02-trz.md`/`03-acceptance-criteria.md`/`06-adr/`, а не subjective-оценка.
</escalation>

131
.openclaw/agents/tester.md Normal file
View File

@@ -0,0 +1,131 @@
---
name: tester
description: QA-инженер. Прогоняет тесты, оформляет отчёт.
tools:
- Filesystem (Read везде; Write только docs/work-items/<plane-id>/13-test-report.md)
- Bash (pytest, curl)
---
# System prompt: Tester
<context>
Ты — QA-инженер проекта **orchestrator**. Прогоняешь полный регресс и оформляешь отчёт.
**Перед любым действием прочти:**
1. `CLAUDE.md` — паспорт и правила.
2. `docs/architecture/README.md` — конвейер и компоненты.
3. `docs/work-items/<plane-id>/02-trz.md`.
4. `docs/work-items/<plane-id>/03-acceptance-criteria.md`.
5. `docs/work-items/<plane-id>/04-test-plan.yaml`.
6. `docs/work-items/<plane-id>/12-review.md` — убедись, что вердикт `APPROVED`.
</context>
<task>
Твоя стадия — **testing**. Прогоняешь регресс и smoke, выносишь машинный вердикт `result:`
(`PASS`|`FAIL`) в `13-test-report.md`. Гейт `check_tests_passed` читает вердикт из frontmatter.
<thinking>
Сначала прогони тесты и собери факты (pytest, smoke, покрытие ТЗ), классифицируй каждый TC, и
ТОЛЬКО потом выноси вердикт. Любой FAIL/смок-сбой → `result: FAIL`; всё зелёное → `result: PASS`.
</thinking>
**Алгоритм:**
1. **Окружение:** `curl -s http://localhost:8500/health`.
2. **Тесты — в worktree ветки задачи, НЕ в общем `/repos/orchestrator`.** Прогоняй `pytest` из
рабочего дерева именно этой задачи (`/repos/_wt/orchestrator/<branch-slug>/`, например
`feature_ORCH-NNN-slug`), где лежит код ветки. Общий чекаут `/repos/orchestrator` могут
параллельно переключать другие задачи (гонка checkout) → можно прогнать чужой код. Команда:
`cd <worktree-ветки> && pytest tests/ -v --tb=short`.
3. **Smoke API (read-only):** `curl -s http://localhost:8500/health`, `…/status`, `…/queue`.
В ответе `/queue` проверь наличие блока `serial_gate` (ORCH-088) — он должен присутствовать в
полезной нагрузке (наряду с `auto_labels`); его отсутствие = регресс смока.
4. **Покрытие ТЗ:** для **каждого** TC из `04-test-plan.yaml` — выполнен? PASS/FAIL? Сопоставь с
критериями `03-acceptance-criteria.md`. Готовность = каждый TC сопоставлен, а не «файл записан».
</task>
<deliverables>
Через **Write tool** — единственный файл `docs/work-items/<plane-id>/13-test-report.md` (с машинным
frontmatter-вердиктом, см. `<output_format>`).
**Скелет:** `docs/_templates/13-test-report.md`. **Эталон полноты отчёта** — work item **ORCH-073**
и **ORCH-088**.
</deliverables>
<constraints>
-Не пиши продакшн-код → ✅ только прогоняй тесты и фиксируй результаты.
-Не подгоняй тесты под код → ✅ если тест падает обоснованно, фиксируй `result: FAIL`.
-Не запускай деструктивные операции на прод-контейнере → ✅ smoke только read-only
(`/health`, `/status`, `/queue`).
</constraints>
<output_format>
Файл `13-test-report.md` ОБЯЗАН начинаться с YAML-frontmatter. Машинный ключ (НЕ менять
имя/регистр/значения): `result: PASS | FAIL`.
Поверх него — обязательная frontmatter-схема 52c (6 полей, `src/frontmatter.py::REQUIRED_FIELDS`),
`status` согласован с `result:`:
| Поле | Значение для tester |
|------|---------------------|
| `work_item` | ID задачи (`ORCH-NNN` / `ET-NNN`) |
| `stage` | `testing` |
| `author_agent` | `tester` |
| `status` | согласован с `result:` (`pass` / `fail`) |
| `created_at` | текущая дата `YYYY-MM-DD` |
| `model_used` | резолв ORCH-41 — сейчас `claude-opus-4-8` |
> ⚠️ **Не копируй `created_at`/`model_used` из примера буквально:** подставь фактическую текущую
> дату (`date +%F`) и фактическую модель из конфига (резолв ORCH-41). Имена полей `created_at`/
> `model_used` сохраняются; меняются только значения-плейсхолдеры `<YYYY-MM-DD>`/`<resolve ORCH-41>`.
```markdown
---
result: PASS # PASS | FAIL — машинный вердикт, UPPERCASE
work_item: ORCH-NNN
stage: testing
author_agent: tester
status: pass
created_at: <YYYY-MM-DD>
model_used: <resolve ORCH-41>
type: test-report
work_item_id: ORCH-NNN
---
# Test Report — ORCH-NNN
## Окружение
- Python: <версия>
- pytest: <версия>
- Дата: <ISO дата>
## Результаты
| TC ID | Описание | Результат |
|-------|----------|-----------|
| TC-01 | ... | PASS |
## Вывод pytest
<вставь вывод>
## Итог
PASS / FAIL
```
**Вердикт:**
- Все тесты PASS + smoke OK → `result: PASS` → задача переходит на `deploy-staging`.
- Любой FAIL → `result: FAIL` → откат на `development` (`back-to:dev`).
</output_format>
<success_criteria>
Выход стадии готов, когда `13-test-report.md` записан, несёт корректный машинный `result:`
(`PASS`|`FAIL`, UPPERCASE) + frontmatter-схему 52c, таблицу TC и вывод pytest, И **каждый TC из
`04-test-plan.yaml` выполнен и сопоставлен** с `03-acceptance-criteria.md` (а не только «файл
записан»).
</success_criteria>
<escalation>
- **Обоснованный FAIL** (тест/смок падает по делу) → `result: FAIL` → откат на development
(`back-to:dev`); НЕ подгоняй тесты под код.
- **Смок-сбой инфраструктуры** (окружение/`/health`/`/queue` недоступны) → зафиксируй как
`result: FAIL` с диагностикой (что именно недоступно), а не «зелено по умолчанию».
</escalation>

4
.task-arch.md Normal file
View File

@@ -0,0 +1,4 @@
Work item: ORCH-061
Repo: orchestrator
Branch: feature/ORCH-061-bug-deploy-staging-development
Stage: architecture

4
.task-dev.md Normal file
View File

@@ -0,0 +1,4 @@
Work item: ORCH-011
Repo: orchestrator
Branch: feature/ORCH-011-
Stage: development

8
.task.md Normal file
View File

@@ -0,0 +1,8 @@
Work item: ORCH-061
Repo: orchestrator
Branch: feature/ORCH-061-bug-deploy-staging-development
Stage: analysis
Title: BUG: deploy-staging петля — откат на development (self-deploy)
Description:
Симптом: на стадии deploy-staging для self-hosting orchestrator задача откатывается deploy-staging -> development и крутится по кругу.ДВЕ подтверждённые причины (ORCH-58 + ORCH-60):1. check_staging_status FAILED (ложный). deployer гоняет staging_check.py, тот падает на C9a/C9b (sandbox e2e: branch not found + analyst job in queue) с пометкой «Plane comment check skipped: bot-tokens not added to SANDBOX project». 8/10 PASS, 2 ложных FAIL из-за ненастроенных bot-токенов SANDBOX-проекта. QG check_staging_status -> FAILED -> rollback deploy-staging->development. Это НЕ регресс кода, а отсутствие sandbox-настроек.2. no changes to commit. для action-стадий (деплой = рестарт/retag, не правка кода) deployer exit0 + «no changes» тоже трактуется stage_engine как недовыполнение -> откат.Последствие: прод-деплой self-hosting репо НЕВОЗМОЖЕН автономно — ORCH-58 и ORCH-60 доводились ВРУЧНУЮ (merge PR + build-once retag + --deploy). Прямой блокер автономного внедрения (эпик ORCH-54).Fix-направления (одно или оба):(а) Настроить sandbox bot-токены в SANDBOX Plane-проект, чтобы staging_check C9a/C9b проходили честно (10/10). Тогда check_staging_status не будет ложно падать.(б) Отвязать advance deploy-стадии от git-changes для self-deploy репо: успех = exit0 + health PASS (+ опц. staging_check), а не наличие коммита.Acceptance: ORCH-задача для self-hosting orchestrator проходит deploy-staging -> deploy -> Done БЕЗ ручного вмешательства и без петли. Priority P0.

241
CHANGELOG.md Normal file

File diff suppressed because one or more lines are too long

421
CLAUDE.md Normal file
View File

@@ -0,0 +1,421 @@
# 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 …/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-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)
## Среды
- **prod** — `orchestrator` (8500), внешний URL `https://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/Gitea
- `tests/` — pytest
- `docs/` — документация, ADR, work-items, operations; **витрина системы — `docs/overview/`** (единая точка входа «бизнес + тех», ORCH-011)
- `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`), для `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-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-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), 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_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, 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)`: `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-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: `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-closed `staging_port == прод-порт` → отказ
freshness-пути ДО любого ssh/build, без тихого fallback.
- **Секреты нового хоста:** stdlib `scripts/gen_secrets.py` (`secrets.token_hex(32)`; печать по
умолчанию; `--write` отказывает при существующем `.env`, перезапись только `--force`); норматив —
боевые секреты не копируются. `.env.example` — канон 100% ключей старта.
- **Smoke тиража:** runbook `docs/operations/REPLICATION.md` (карта env, чек-лист секретов,
пошаговый smoke с PASS/FAIL до артефактов `0104`/`done`, границы 10-common vs Lite vs Bundled).
Анти-регресс — `tests/test_no_host_hardcodes.py` (запрещённые литералы в исполняемом коде
`src/**`+`watchdog/**`; `tokenize`-исключение комментариев/докстрингов; config-модули — канон
дефолтов, вне скана; allowlist пуст). Детали —
`docs/work-items/ORCH-101/06-adr/ADR-001-host-parametrization-secrets-smoke.md`, сквозной
`docs/architecture/adr/adr-0036-replication-foundation-host-parametrization.md`.
## Lite-тираж: орк+watchdog на инфре заказчика (ORCH-102)
Закрыт **Type A** эпика ORCH-10 (поверх фундамента 10-common ORCH-101): заказчик разворачивает
у себя ТОЛЬКО `orchestrator`+`orchestrator-watchdog` и донастраивает окружение
(Plane/Gitea/Telegram/LLM — его инсталляции) по одной сквозной инструкции. **Docs+tests**
(паттерн ORCH-077/092): `src/**`/compose/Dockerfile/`scripts/**` не тронуты; конвейер байт-в-байт.
- **Golden source** — `docs/deployment/LITE_SETUP.md` (новый раздел `docs/deployment/` — витрина
тиража, читатель — внешний оператор; vs `docs/operations/` — эксплуатация НАШЕГО прода): 13
нормативных разделов в порядке маршрута оператора, каждый шаг = fenced-команда + явная
«Проверка:»/PASS/FAIL, хост-специфика только плейсхолдерами; канон не форкается — статусы/env/
вебхуки/smoke ссылками на ONBOARDING §1 / REPLICATION §2§4 / SETUP_WEBHOOKS (явно в доке —
только fail-closed имена `Confirm Deploy`/`STOP` и обязательные ключи нового хоста).
- **Канон watchdog-конфига** — новый `.env.watchdog.example` (key-set = блок `WATCHDOG_*`
`.env.example`, держится key-sync тестом): sidecar читает ТОЛЬКО `.env.watchdog`, ключ
`WATCHDOG_*` в `.env` для него инертен (ловушка файла-носителя закрыта); C-1 ORCH-100 — свой
бот, токен орка не переиспользовать; `.env.watchdog` в `.gitignore`.
- **Нормативы:** Gitea — branch protection на `main` НЕ включать (ADR D10 ORCH-009 / INV-4),
pre-receive не вводится, ОДИН глобальный webhook-секрет; compose НЕ форкается (дефолтный
`up -d` = ровно орк+watchdog, staging строго за `profiles: [staging]` — вилка только под
self-hosting развитие платформы); stateless — данные/задачи/секреты боевого хоста НЕ
переносятся, проверка чистоты через `GET /queue`.
- **Анти-дрейф** — `tests/test_lite_setup_doc.py` (структурный, без сети/LLM/subprocess):
13 разделов в порядке, кирпичи, env-ключи ⊂ `.env.example`, compose-подмножество
(анти-появление `plane*`/`gitea*`), fenced-скан `FORBIDDEN` (импорт из
`test_no_host_hardcodes.py`) + секрет-эвристика, «22 статуса» сверкой импорта
`plane_sync._PLANE_NAME_TO_KEY`, перекрёстность REPLICATION→LITE_SETUP. **Норматив
сопровождения (NFR-5):** меняешь шаги тиража → обнови LITE_SETUP.md в том же PR. Детали —
`docs/work-items/ORCH-102/06-adr/ADR-001-lite-setup-doc-canon.md`, сквозной
`docs/architecture/adr/adr-0037-lite-replication-canon.md`.
## Bundled-тираж: весь стек одним комплектом (ORCH-103)
Закрыт **Type B** эпика ORCH-10 (поверх 10-common ORCH-101 и канона Lite ORCH-102): заказчик
**без собственной инфраструктуры** получает весь стек одним комплектом — новый top-level каталог
**`deploy/bundled/`** (самодостаточный compose: орк + watchdog + Gitea + зеркало upstream Plane CE
≈14 контейнеров; project name `orchestrator-bundle` = узнаваемый префикс томов/контейнеров;
`container_name` не пиннится; staging-контура нет вовсе — самразвитие платформы у заказчика =
маршрут Lite) + **`scripts/bootstrap_bundle.py`** (python stdlib-only, режимы `plan` (дефолт) /
`apply`/`verify`, step-движок check→ensure, exit 0/2/1), доводящий стек одним прогоном: preflight
(fail-fast до мутаций) → секреты (webhook — строго `gen_secrets.py`; bundle-креды — stdlib
`secrets`, без перетирания без `--force-secrets`) → up+готовность → init Gitea (полностью
автоматом, `gitea admin …`; branch protection НЕ включается — D10 ORCH-009/INV-4) → init Plane
(честные manual-step c API-верификацией; молчаливый пропуск запрещён) → онбординг sandbox-проекта
**строго** `onboard_project.py apply+verify` (22 статуса — `plane_sync._PLANE_NAME_TO_KEY`, нулевой
дрейф канона) → git-доступ агентов HTTP token-remote (ssh-контур не вводится) → сборка корневых
`.env`/`.env.watchdog` (bootstrap — единственный писатель live-конфигов) → health/итог.
Сеть — одна bridge, машинный трафик строго сервис-DNS (`http://orchestrator:8500/webhook/*`),
наружу — только человеческие порты (`BUNDLE_ORCH_PORT`/`BUNDLE_PLANE_PORT`/`BUNDLE_GITEA_HTTP_PORT`);
мина Gitea закрыта `GITEA__webhook__ALLOWED_HOST_LIST=orchestrator`. Все сторонние образы пиннованы
неподвижными тегами; teardown — только документированная процедура BUNDLED_SETUP §13 (delete-операций
в скрипте НЕТ вообще). Рантайм байт-в-байт: `src/**`, корневой compose, `Dockerfile`,
`STAGE_TRANSITIONS`/`QG_CHECKS`/схема БД — не тронуты; kill-switch не нужен (активация — только
явный запуск оператором, паттерн ORCH-009/102). Golden source — `docs/deployment/BUNDLED_SETUP.md`
(14 разделов канона LITE_SETUP; общие шаги — ссылками на LITE_SETUP/ONBOARDING/REPLICATION).
Анти-дрейф — `tests/test_bundle_compose.py` (состав/пины/key-set-sync/заморозка корневого compose),
`tests/test_bundled_setup_doc.py` (разделы/FORBIDDEN-импорт/секрет-эвристика/env-ключи/кросс-рефы),
`tests/test_bootstrap_script.py` (кирпичи/stdlib-only ast-сканом/нет delete-операций/unit чистых
функций). **Норматив сопровождения (NFR-5):** меняешь шаги Bundled-тиража → обнови BUNDLED_SETUP.md
в том же PR. Детали — `docs/work-items/ORCH-103/06-adr/ADR-001-bundled-stack-compose-and-bootstrap.md`,
сквозной `docs/architecture/adr/adr-0038-bundled-replication-canon.md`.
## Конвенции
- Conventional Commits (`feat:`, `fix:`, `docs:`, `refactor:`, `test:`)
- Ветки: `feature/ORCH-NNN-slug`, `fix/ORCH-NNN-slug`
- ADR per work-item: `docs/work-items/<plane-id>/06-adr/ADR-NNN-slug.md`
- Global ADR (сквозные решения): `docs/architecture/adr/adr-NNNN-slug.md`
- Work items: `docs/work-items/<plane-id>/`
- Машинные вердикты Quality Gate — строго YAML-frontmatter (`verdict:`, `deploy_status:`, `staging_status:`, `security_status:`), никогда проза. **ORCH-52c (ORCH-076):** парсинг frontmatter сведён к единому контракту `src/frontmatter.py` (reader `read_frontmatter_value` — BC; единый парс-примитив `parse_frontmatter`; writer `render/write_frontmatter`; валидатор схемы `validate_schema`/`REQUIRED_FIELDS` — warning-only по умолчанию, hard-fail только под kill-switch `frontmatter_validation_strict`, дефолт `False`). Пять вердикт-парсеров (`check_reviewer_verdict`, `_parse_tests_verdict`, `_parse_deploy_status`, `_parse_staging_status`, `parse_security_status`) читают через ОДНУ точку парсинга; семантика вердиктов и `STAGE_TRANSITIONS`/состав `QG_CHECKS` — 1:1. Формальная спека «стадия → обязательный выход» + обязательная frontmatter-схема — `docs/_standards/HANDOFF_PROTOCOL.md`
## Артефакты задачи (`docs/work-items/<plane-id>/`)
`00-business-request.md`, `01-brd.md`, `02-trz.md`, `03-acceptance-criteria.md`, `04-test-plan.yaml`, `06-adr/ADR-NNN-slug.md`, `07-infra-requirements.md`, `08-data-requirements.md`, `10-tech-risks.md`, `12-review.md`, `13-test-report.md`, `14-deploy-log.md`, `15-staging-log.md`, `16-post-deploy-log.md` (post-deploy наблюдение, ORCH-021), `17-security-report.md` (security-гейт: `security_status:`/secrets/deps, ORCH-022), `18-coverage-report.md` (coverage-гейт: `coverage_status:`/measured/baseline, ORCH-027).
**Стандарт документов (ORCH-075, ORCH-52b):** структура каждого дока, карта «стадия→агент→документ→гейт→machine-key» и конвенция ADR-naming зафиксированы в `docs/_standards/PIPELINE_DOCS.md` (golden source); копируемые скелеты — в `docs/_templates/`. Перед написанием номерного дока бери скелет из `docs/_templates/` и не меняй имя machine-key frontmatter (регистр чувствителен — иначе гейт упадёт ложно).
## Правила для агентов
1. Перед любым действием прочесть этот файл и `docs/architecture/README.md`.
2. **Документация = golden source наравне с кодом.** Изменил функционал → обнови доку В ТОМ ЖЕ PR. Архитектурное решение → заведи ADR (формат — `docs/_standards/PIPELINE_DOCS.md` §4). Структура номерных доков и шаблоны — `docs/_standards/PIPELINE_DOCS.md` + `docs/_templates/`. Обнови `CHANGELOG.md`. **Витрина системы `docs/overview/` (ORCH-011):** изменил функциональность платформы → обнови витрину в том же PR (какой файл какому классу изменений — таблица в индексе витрины); машинно-проверяемые факты витрины держит `tests/test_system_docs.py`.
3. Никогда не править артефакты других этапов.
4. Никогда не комментировать ТЗ задним числом — если ТЗ не годится, возвращай в Анализ.
5. Никогда не закрывать задачу самостоятельно — это делает CI / финальная стадия.
6. **Reviewer проверяет: обновлена ли документация. Нет → REQUEST_CHANGES.** Это включает **обзорные доки** (ORCH-079, 52f — финал эпика 52): если PR закрывает пункт `README.md` «Известные ограничения», но README не обновлён → finding ≥P1 (витрина проекта не должна выдавать решённое за открытое). Та же ось покрывает витрину системы (ORCH-011): PR меняет функциональность, описанную в `docs/overview/`, а витрина не обновлена → 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).*

View File

@@ -1,11 +1,65 @@
FROM python:3.12-slim
# ORCH-058 (Strategy B): stamp the image with the git commit it was built from so
# the deploy hook can fail-close if a stale staging image would be promoted to prod
# (INV-FRESH). Passed at build time via `--build-arg GIT_SHA=<sha>` (the staging
# rebuild in check_staging_image_fresh / the --build-staging hook mode supplies it).
# Without the build-arg the label is empty -> the hook treats it as a mismatch
# (fail-closed). The OCI-standard key is read by `docker image inspect`.
ARG GIT_SHA=""
LABEL org.opencontainers.image.revision=$GIT_SHA
WORKDIR /app
RUN apt-get update -qq && apt-get install -y -qq openssh-client git && rm -rf /var/lib/apt/lists/*
RUN apt-get update -qq && apt-get install -y -qq openssh-client git curl ca-certificates && rm -rf /var/lib/apt/lists/*
# git operations run as root over bind-mounted /repos (may be owned by host uid) -> trust it.
RUN git config --system --add safe.directory '*'
# ORCH-022: pinned gitleaks static Go binary for the offline secret-scan sub-gate
# (07-infra I-1). Baked into the image (NOT a pip package): the gate runs INSIDE the
# orchestrator container over a per-task worktree. Pinned release => deterministic
# rules; gitleaks needs no network so the "a secret always blocks" guarantee (BR-2)
# is independent of internet access. Multi-arch aware (amd64/arm64).
ARG GITLEAKS_VERSION=8.18.4
RUN set -eux; \
arch="$(dpkg --print-architecture)"; \
case "$arch" in \
amd64) gl_arch="x64" ;; \
arm64) gl_arch="arm64" ;; \
*) echo "unsupported arch: $arch" >&2; exit 1 ;; \
esac; \
curl -fsSL -o /tmp/gitleaks.tar.gz \
"https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_${gl_arch}.tar.gz"; \
tar -xzf /tmp/gitleaks.tar.gz -C /usr/local/bin gitleaks; \
chmod +x /usr/local/bin/gitleaks; \
rm -f /tmp/gitleaks.tar.gz; \
gitleaks version
# ORCH-58: compose runs the container as uid:gid 1000:1000 (ORCH-40), but the base
# image has no passwd entry for uid 1000 -> ssh/whoami fail with
# "No user exists for uid 1000" (rc=255), breaking the detached self-deploy ssh
# launch (ORCH-36 Phase B). Create a real user 1000 with a home dir so getpwuid()
# resolves and ssh can start.
# ORCH-101 (D5): uid/gid/home/username are build ARGs (defaults = current prod
# values); compose build.args wires APP_UID/APP_GID/APP_HOME from the SAME env
# vars as the runtime user: and the mount targets, so the ORCH-040 group
# (uid/gid/HOME/mounts/useradd) moves coherently. APP_USER is passwd cosmetics
# (the ENTRY matters for getpwuid/ssh, not the name) — Dockerfile-default only.
ARG APP_UID=1000
ARG APP_GID=1000
ARG APP_USER=slin
ARG APP_HOME=/home/slin
RUN groupadd -g ${APP_GID} app && useradd -u ${APP_UID} -g ${APP_GID} -m -d ${APP_HOME} -s /bin/bash ${APP_USER}
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY src/ ./src/
COPY data/ ./data/
# ORCH-021: do NOT `COPY data/ ./data/`. `data/` is gitignored (SQLite DB dir) and
# is provided at runtime as a bind-mount volume (`./data:/app/data`, see
# docker-compose.yml) which shadows anything baked into the image — so the COPY was
# dead weight. Worse, the ORCH-058 staging rebuild (`check_staging_image_fresh`)
# builds with the task *worktree* as the docker build context; a fresh worktree never
# contains the untracked `data/`, so `COPY data/` failed `docker build` with exit 1
# and bounced the task off `deploy-staging`. We just ensure the mountpoint exists.
RUN mkdir -p /app/data
ENV PYTHONPATH=/app
# ORCH-101 (D5): CMD deliberately stays exec-form with the documented 8500
# default — an ARG cannot reach a runtime CMD, and a shell-form CMD would break
# the verified `init: true` + exec-form PID-1/signal semantics (B-2). The prod
# port is parametrised on the compose layer (`command:` with
# ${ORCH_DEPLOY_PROD_TARGET_PORT:-8500}), which overrides this CMD.
CMD ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8500"]

146
README.md
View File

@@ -1,5 +1,10 @@
# Multi-Agent Orchestrator
> См. [CLAUDE.md](CLAUDE.md) (паспорт проекта) и [docs/architecture/README.md](docs/architecture/README.md) (архитектура).
>
> **Витрина системы — [docs/overview/](docs/overview/README.md)**: единая точка входа в документацию
> (бизнес + тех, 7 блоков, маршруты для заказчика / менеджера / разработчика, презентация). ORCH-011.
FastAPI-сервис для оркестрации мульти-агентного пайплайна разработки. Принимает webhooks от Plane и Gitea, управляет жизненным циклом задач через Quality Gates, запускает Claude CLI агентов на каждой стадии.
## Архитектура
@@ -17,9 +22,9 @@ Gitea (git events) ─webhook──┘ │
## Стадии пайплайна
```
created → analysis → architecture → development → review → testing → deploy → done
└─── REQUEST_CHANGES ─┘ (max 3 retries)
created → analysis → architecture → development → review → testing → deploy-staging → deploy → done
───── REQUEST_CHANGES ─────┘ (max 3 retries)
```
| Стадия | Агент | Quality Gate (выход) | Триггер перехода |
@@ -27,10 +32,11 @@ created → analysis → architecture → development → review → testing →
| created | — | — | Plane webhook (work_item.created) |
| analysis | analyst | Файлы BRD/TRZ/AC/TestPlan | Push docs/ |
| architecture | architect | ADR или infra-requirements | Push docs/ |
| development | developer | check_tests_local (орк сам гоняет `make test`) | Auto-advance после developer |
| development | developer | check_ci_green (Gitea CI зелёный на ветке) | Auto-advance после developer |
| review | reviewer | check_reviewer_verdict (`verdict:` во frontmatter 12-review.md) | Auto-advance после reviewer |
| testing | tester | Test report с PASS | Auto-advance после tester |
| deploy | deployer | — | SSH deploy-hook |
| testing | tester | check_tests_passed (test-report.md) | Auto-advance после tester |
| deploy-staging | deployer | check_staging_status (15-staging-log.md) | Auto-advance после tester |
| deploy | deployer | check_deploy_status (14-deploy-log.md) | Auto-advance после staging |
| done | — | — | — |
## API Endpoints
@@ -42,6 +48,7 @@ created → analysis → architecture → development → review → testing →
| GET | `/queue` | Очередь задач (ORCH-1): counts по статусам + max_concurrency + последние 10 jobs |
| POST | `/webhook/plane` | Plane webhook receiver |
| POST | `/webhook/gitea` | Gitea webhook receiver |
| POST | `/bug-fast-track/escalate?work_item=<id>` | Эскалация багфикс-задачи в полный цикл (ORCH-019): сброс `track` `'bug'→'full'` → следующий переход уходит в `architecture` |
## Структура проекта
@@ -65,10 +72,21 @@ data/
├── orchestrator.db # SQLite database
└── runs/ # Agent output logs ({run_id}.log)
docs/
├── ARCHITECTURE.md # Подробная архитектура
├── LESSONS_ET006.md # Lessons learned из ET-006
├── BUGFIXES_2026-05-21.md # Багфиксы
── SETUP_WEBHOOKS.md # Настройка webhooks
├── PRODUCT_VISION.md # Видение продукта
├── deployment/
│ └── LITE_SETUP.md # Lite-тираж: орк+watchdog на инфре заказчика (ORCH-102)
── architecture/
│ ├── README.md # Обзор архитектуры, компоненты, API
│ ├── internals.md # Схема БД, потоки, resilience-слой
│ └── adr/ # Архитектурные решения (ADR-0001, ADR-0002, ADR-0003)
├── operations/
│ ├── INFRA.md # Топология, порты, env, self-hosting риски
│ ├── DEPLOY_HOOK.md # Деплой-хук
│ ├── STAGING.md # Staging-окружение
│ ├── STAGING_CHECK.md # Проверки staging
│ └── SETUP_WEBHOOKS.md # Настройка webhooks
├── work-items/ # Артефакты задач (00-15-*)
└── history/ # Исторические записи (BUGFIXES, INCIDENTS, ADR-архив)
docker-compose.yml # Deployment config
Dockerfile # Python 3.12 + Docker CLI + tini
```
@@ -109,6 +127,7 @@ uvicorn src.main:app --reload --port 8500
| `ORCH_REPOS_DIR` | Repos dir (container) | `/repos` |
| `ORCH_HOST_REPOS_DIR` | Repos dir (host) | `/home/slin/repos` |
| `ORCH_DB_PATH` | SQLite path | `/app/data/orchestrator.db` |
| `ORCH_RUNS_DIR` | Базовый каталог per-run логов агентов (`<runs_dir>/{run_id}.log`, ORCH-087) | `/app/data/runs` |
| `ORCH_MAX_CONCURRENCY` | Сколько jobs воркер запускает параллельно (ORCH-1) | `1` |
| `ORCH_QUEUE_POLL_INTERVAL` | Период опроса очереди воркером, сек (ORCH-1) | `2.0` |
| `ORCH_PREFLIGHT_CACHE_TTL` | Кэш preflight (CLI/net), сек (ORCH-1 resilience) | `45` |
@@ -117,6 +136,30 @@ uvicorn src.main:app --reload --port 8500
| `ORCH_TRANSIENT_MAX_ATTEMPTS` | Ретраи для 429/недоступности | `5` |
| `ORCH_BREAKER_THRESHOLD` | transient подряд до открытия breaker | `3` |
| `ORCH_BREAKER_PAUSE_SECONDS` | Пауза при открытом breaker | `300` |
| `ORCH_RECONCILE_ENABLED` | Kill-switch sweeper потерянных webhook (ORCH-053) | `true` |
| `ORCH_RECONCILE_PLANE_ENABLED` | Отдельный флаг F-2 (опрос Plane API) | `true` |
| `ORCH_RECONCILE_INTERVAL_S` | Период фонового прохода reconciler, сек | `120` |
| `ORCH_RECONCILE_GRACE_DEFAULT_S` | Порог «застряла» по `tasks.updated_at`, сек | `600` |
| `ORCH_RECONCILE_GRACE_OVERRIDES_JSON` | Per-stage пороги, напр. `{"development":300}` | `""` |
| `ORCH_RECONCILE_NOTIFY_UNBLOCK` | Telegram при разблокировке застрявшей задачи | `true` |
| `ORCH_RECONCILE_SKIP_BLOCKED_ENABLED` | F-1 Guard 2 (ORCH-060): пропуск задач в Plane-статусе Blocked / Needs Input; `false` глушит только сетевой Guard 2 (Guard 1 escalated всегда активен) | `true` |
| `ORCH_QG0_TITLE_MAX` | Верхний лимит длины заголовка QG-0 (вход `_qg0_errors`); невалидное/пустое значение → дефолт (ORCH-069) | `200` |
| `ORCH_STOP_STATUS_ENABLED` | Kill-switch отмены задачи по Plane-статусу **STOP** + закрытия дыры релонча (ORCH-090); `false` → поведение 1:1 как до ORCH-090 | `true` |
| `ORCH_STOP_STATUS_REPOS` | CSV область репо для STOP-отмены; пусто = все репо (ORCH-090) | `""` |
| `ORCH_BUG_FAST_TRACK_ENABLED` | Kill-switch багфикс-трека (ORCH-019): задача с меткой Plane `Bug` пропускает стадию `architecture`; `false` → старт и маршрут 1:1 как до ORCH-019 (нулевая регрессия) | `true` |
| `ORCH_BUG_FAST_TRACK_LABEL` | Имя метки Plane, активирующей багфикс-трек (ORCH-019) | `Bug` |
| `ORCH_BUG_FAST_TRACK_REPOS` | CSV область репо для багфикс-трека; **пусто → self-hosting only** (`orchestrator`) — enduro подключается явным CSV (ORCH-019) | `""` |
| `ORCH_AGENT_HOME_DIR` | ORCH-101: HOME акторских процессов + таргет маунтов `.claude`/`.ssh` + `ARG APP_HOME` (группа ORCH-040) | `/home/slin` |
| `ORCH_AGENT_GIT_NAME` / `ORCH_GIT_EMAIL_DOMAIN` | ORCH-101: git-идентичность коммитов агентов (`claude-bot@mva154.local` при дефолтах) | `claude-bot` / `mva154.local` |
| `ORCH_STAGING_PORT` | ORCH-101: порт staging (читают `image_freshness` и compose); guard fail-closed при совпадении с прод-портом (ORCH-058 AC-9) | `8501` |
| `ORCH_HOST_CLAUDE_DIR` / `_CLAUDE_JSON` / `_SSH_DIR` / `_CLAUDE_CODE_DIR` / `_NODE_BIN` | ORCH-101: host-источники bind-маунтов (compose-интерполяция) | боевые пути mva154 |
| `ORCH_RUN_UID` / `ORCH_RUN_GID` / `ORCH_DOCKER_GID` | ORCH-101: uid:gid контейнера и gid docker-группы (`group_add`, ORCH-040) | `1000`/`1000`/`999` |
Тираж платформы на новый хост (полная карта, секреты, smoke) — `docs/operations/REPLICATION.md` (ORCH-101).
**Lite-тираж под ключ (ORCH-102):** разворачивание орк+watchdog на инфраструктуре заказчика
по одной сквозной инструкции «голый хост → работающий конвейер» (Plane/Gitea/Telegram/LLM
заказчик ставит сам и подключает по шагам) — `docs/deployment/LITE_SETUP.md`; канон конфига
sidecar-watchdog — `.env.watchdog.example`; анти-дрейф — `tests/test_lite_setup_doc.py`.
## Очередь задач (ORCH-1 / F-2b)
@@ -133,12 +176,65 @@ Webhook-хэндлеры больше не спавнят claude-агентов
- **Ретраи.** Упавший job (exit≠0) ретраится пока `attempts < max_attempts`,
потом `failed` + Telegram-нотификация.
Статусы job: `queued → running → done | failed`. Наблюдаемость — через `GET /queue`.
Статусы job: `queued → running → done | failed`; **`cancelled`** — терминальный
исход STOP-отмены (ORCH-090), нигде не реквью'ится. Наблюдаемость — через `GET /queue`.
## Отмена задачи: статус STOP (ORCH-090)
Перевод задачи в выделенный Plane-статус **STOP** отменяет её: оркестратор
останавливает активного агента (graceful SIGTERM-каскад), снимает все job'ы
(терминальный `cancelled`, без авто-requeue), удаляет worktree и **рабочую**
ветку в Gitea (**никогда** `main`, без force-push), сбрасывает прогресс в
durable-терминал `tasks.stage='cancelled'` и тумбстонит натуральные ключи
(`#cancelled-<id>`), чтобы повторный «To Analyse» создал задачу **с нуля**.
Docs-артефакты (`01..17`) сохраняются. STOP во время критичного шага merge/deploy
**откладывается** до его честного завершения (никакого half-merge / рестарта
прода). Параллельно закрыта «дыра релонча»: ручной перевод в промежуточный рабочий
статус больше не релончит агента — единственный вход к запуску пайплайна остаётся
«To Analyse» (релонч агента сменой статуса разрешён только на стадии `analysis`
владельце Needs Input). Всё под kill-switch `ORCH_STOP_STATUS_ENABLED`, аддитивно,
never-raise. Наблюдаемость — блок `stop` в `GET /queue`. Деталь — `docs/work-items/
ORCH-090/06-adr/ADR-001-stop-cancel-task.md` + сквозной
`docs/architecture/adr/adr-0026-stop-cancel-task.md`.
> **Инфра-предусловие:** на доске Plane проекта ORCH создать статус **«STOP»** с
> группой `cancelled`. До создания статуса фича в fail-safe (нет UUID → ветка STOP
> не активируется).
## Багфикс-трек: дешёвый маршрут для багов (ORCH-019)
Задача с меткой Plane `Bug` (имя метки — `ORCH_BUG_FAST_TRACK_LABEL`, дефолт `Bug`)
идёт **укороченным маршрутом** конвейера: `analysis(lite) → development → review →
testing → deploy-staging → deploy → done`, т.е. **пропускается стадия `architecture`**
(отдельный прогон opus-агента `architect` + ADR + exit-гейт `check_architecture_done`).
Мини-аналитик выдаёт облегчённый пакет (короткий bug-report + обязательный план
регресс-теста), но всё равно все 4 файла analysis — гейт `check_analysis_complete`
не меняется.
**Корневой инвариант:** упрощается только аналитика/архитектура — **все Quality
Gate'ы и под-гейты исполняются без изменений** (`STAGE_TRANSITIONS` / `QG_CHECKS` /
`check_*` / machine-verdict ключи — байт-в-байт прежние). Маршрутизация багфикса —
свойство планировщика (routing-override в `advance_stage` по `tasks.track='bug'`),
**не** Quality Gate.
Классификация (`src/bug_fast_track.py`, never-raise): локальный `bug_fast_track_applies(repo)`
ПЕРВЫМ (выключенный флаг = нулевой сетевой оверхед), затем `is_bug_task` через
`labels.has_label` (источник истины — Plane API). Тип хранится в аддитивной колонке
`tasks.track` (`'full'` | `'bug'`), читается в горячем пути из БД (не из сети).
**Эскалация** сложного/архитектурного бага в полный цикл — `POST /bug-fast-track/escalate?work_item=<id>`
(сброс `'bug'→'full'`). Всё под kill-switch `ORCH_BUG_FAST_TRACK_ENABLED`, область —
`ORCH_BUG_FAST_TRACK_REPOS` (пусто → self-hosting only), fail-safe → полный цикл.
Наблюдаемость — блок `bug_fast_track` в `GET /queue` + отметка `🐞` в Telegram-карточке.
Деталь — `docs/work-items/ORCH-019/06-adr/ADR-001-bug-fast-track.md` + сквозной
`docs/architecture/adr/adr-0032-bug-fast-track.md`.
> **Инфра-предусловие:** на доске Plane проекта ORCH создать метку **`Bug`**. До её
> создания фича в fail-safe (нет метки → задача идёт полным циклом).
**Resilience-слой:** дешёвый preflight (CLI/net, кэш, без токенов) гейтит claim;
429/overload детектится по логу (transient vs permanent), transient ретраится с
exp-backoff (`available_at`, Retry-After); circuit breaker паузит воркер после N
transient подряд. Подробности: `docs/ORCH-1_JOB_QUEUE.md`.
transient подряд. Подробности: `docs/history/ORCH-1_JOB_QUEUE.md`.
## Multi-repo: реестр проектов (ORCH-6)
@@ -176,7 +272,7 @@ Plane-проект из маппинга.
docker exec orchestrator python3 -c "from src.projects import get_project_by_plane_id as g; print(g('<новый-uuid>'))"
```
Поля `name` опционально (по умолчанию = `repo`). Подробности — `docs/ARCHITECTURE.md`.
Поля `name` опционально (по умолчанию = `repo`). Подробности — `docs/architecture/internals.md`.
## Ключевые механизмы
@@ -202,7 +298,7 @@ stdout/stderr агента перенаправляются СРАЗУ в `/app/
Gitea events роутятся по типу:
- `push` → проверка файлов, advance architecture/development
- `pull_request*` (wildcard) → review approved/rejected, PR merge
- `status` → (legacy) Gitea CI; С-1: больше не authoritative, `failure` логируется на debug и не блокирует/не алертит (QG развития = локальный `check_tests_local`)
- `status` → Gitea CI статус; ORCH-045: авторитетный гейт развития (`development → review`) — `check_ci_green` читает статус ветки с polling-retry (устраняет гонку «pending сразу после push»)
## Тесты
@@ -212,9 +308,19 @@ pytest tests/ -v
## Известные ограничения
1. **Single-task / shared `/repos` checkout** — одновременно безопасно обрабатывается одна задача: все агенты и `check_tests_local` делают `git checkout` в одном `/repos/<repo>` → гонки при параллельных задачах. Исправление — git worktree per task (S-4, отдельно).
2. **Plane sync** — маппинг issue ID может быть некорректным (P3, в работе)
3. **In-process daemon-потоки**агенты живут в потоках uvicorn; при рестарте ловит orphan-recovery. Целевое — очередь задач (F-2b)
4. **Gitea CI не настроен**тесты гоняет сам оркестратор локально
3. **Tester timeout**e2e тесты с Playwright могут занимать >25 мин на тяжёлых фичах
4. **No retry on API errors** — httpx вызовы к Gitea/Plane без retry logic
Реально открытые ограничения (сверено с кодом, ORCH-079):
1. **Telegram 48h** — карточки-сироты старше 48 часов неудаляемы (лимит Telegram Bot API); зачистка сирот самозалечивает только свежие (ORCH-087).
2. **Зависимости задач — только intra-repo (v1)** — `job_deps` выражают связи в пределах одного репозитория; кросс-репо зависимости пока не поддержаны (ORCH-026).
3. **Пакетный автоном — Этап 1** — per-repo serial gate сериализует задачи одного репо (ORCH-088); полный пакетный автономный прогон «1020 задач за ночь» — в развитии (эпик ORCH-088).
### Закрыто (история)
Пункты, ранее значившиеся ограничениями, закрыты кодом — оставлены как трассировка:
- **Single-task / shared `/repos` checkout** → git worktree per task (`ensure_worktree`) + serial-gate (ORCH-088) + task-deps (ORCH-026).
- **In-process daemon-потоки** → персистентная очередь задач (SQLite `jobs`, `src/queue_worker.py`), restart-safe (ORCH-1).
- **Gitea CI не настроен** → активный гейт стадии `development` — `check_ci_green` (`src/qg/checks.py`); `check_tests_local` помечен DEPRECATED.
- **No retry on API errors** → exp-backoff + circuit breaker в `queue_worker.py` (`ORCH_BACKOFF_*` / `ORCH_BREAKER_*` / `ORCH_TRANSIENT_MAX_ATTEMPTS`) + retry-loop в `check_ci_green` (ORCH-1 resilience / ORCH-045).
- **Plane sync — маппинг issue ID** → зрелый `src/plane_sync.py` (`find_issue_id`, `fetch_issue_sequence_id`) со статус-моделью и TTL-самозалечиванием (ORCH-010 / 066 / 068).
- **Tester timeout — Playwright e2e** → orchestrator является pytest-сервисом (Playwright неприменим); реальный механизм — конфигурируемый watchdog (`agent_timeout_seconds`, ORCH-7).

View File

@@ -0,0 +1,61 @@
# deploy/bundled/.env — конфиг bundle-ИНФРЫ (ORCH-103, ADR-001 D2).
# Канонический example: 100% ключей интерполяции deploy/bundled/docker-compose.yml
# (key-set-sync держит tests/test_bundle_compose.py) + ключи init-кред, которые
# заполняет bootstrap. Создание: cp .env.example .env (или это сделает
# scripts/bootstrap_bundle.py apply); права 600.
#
# ⚠️ СЕМАНТИКА ФАЙЛА-НОСИТЕЛЯ (TR-8): этот файл читает ТОЛЬКО compose-интерполяция
# bundle (авто-чтение .env из project dir deploy/bundled/). Runtime-конфиг самого
# оркестратора и watchdog — КОРНЕВЫЕ .env / .env.watchdog (каноны Lite 1:1:
# .env.example / .env.watchdog.example, карта — docs/operations/REPLICATION.md §2).
# Единственный писатель всех live-файлов — scripts/bootstrap_bundle.py: дублируемые
# ключи (uid/gid, HOME, пути Claude CLI) когерентны механически, не дисциплиной.
#
# DO NOT COMMIT реальный deploy/bundled/.env (покрыт неякорным `.env` в .gitignore).
# Секреты: НИ ОДНОГО дефолтного пароля — пустые значения ниже генерирует bootstrap
# (stdlib secrets) и никогда не печатает (NFR-3); повторный запуск НЕ перетирает
# существующие значения без явного --force-secrets.
# --- Публичная точка инсталляции -------------------------------------------
# Хост, по которому браузер оператора открывает Plane/Gitea и по которому
# строятся публичные ссылки (ORCH_GITEA_PUBLIC_URL / ORCH_PLANE_WEB_URL / WEB_URL
# Plane / ROOT_URL Gitea). HTTPS/домены/reverse-proxy заказчика — вне bundle.
BUNDLE_PUBLIC_HOST=localhost
# --- Карта публикуемых портов (D4: только человеческие точки) ---------------
# Конфликт порта на хосте → отказ preflight bootstrap ДО любых мутаций (BR-7).
BUNDLE_ORCH_PORT=8500
BUNDLE_PLANE_PORT=8080
BUNDLE_GITEA_HTTP_PORT=3000
# --- Идентичность контейнера орка (реюз имён ORCH-101: один факт = одно имя) --
# uid:gid владельца deploy/bundled/repos (инвариант ORCH-040); docker-gid хоста
# («МИНА 1», узнать: getent group docker). Заполняет bootstrap из id -u/-g/getent.
ORCH_RUN_UID=1000
ORCH_RUN_GID=1000
ORCH_DOCKER_GID=999
# HOME всех акторов в контейнере (группа ORCH-040 двигается одной переменной).
ORCH_AGENT_HOME_DIR=/home/orchestrator
# --- LLM-предусловие хоста заказчика (bundle НЕ поставляет Claude CLI) -------
# Пути дистрибутива claude-code/node и кред CLI на хосте (канон — LITE_SETUP §7).
ORCH_HOST_CLAUDE_CODE_DIR=/usr/lib/node_modules/@anthropic-ai/claude-code
ORCH_HOST_NODE_BIN=/usr/bin/node
ORCH_HOST_CLAUDE_DIR=~/.claude
ORCH_HOST_CLAUDE_JSON=~/.claude.json
# --- Внутренние креды Plane CE-стека (upstream-имена; значения — bootstrap) --
POSTGRES_USER=plane
POSTGRES_PASSWORD=
POSTGRES_DB=plane
SECRET_KEY=
RABBITMQ_DEFAULT_USER=plane
RABBITMQ_DEFAULT_PASS=
RABBITMQ_DEFAULT_VHOST=plane
MINIO_ROOT_USER=plane-minio-admin
MINIO_ROOT_PASSWORD=
# --- Init-креды Gitea (D6: один пользователь-бот = админ, владелец репо,
# носитель API-токена; создаёт bootstrap через `gitea admin user create`) --
GITEA_ADMIN_USERNAME=orchestrator-bot
GITEA_ADMIN_PASSWORD=

View File

@@ -0,0 +1,338 @@
# ORCH-103 (Type B Bundled, ADR-001 D1D4): самодостаточный compose ВСЕГО стека
# для тиража «под ключ» на хост заказчика: orchestrator + orchestrator-watchdog +
# Gitea + Plane CE (зеркало официального selfhost-référence makeplane/plane
# v0.23.1: имена сервисов и env-контракт — upstream, анти-дрейф к их докам; наши
# отличия от référence: пиннинг неподвижными тегами литералом вместо ${APP_RELEASE}
# (NFR-6, держится tests/test_bundle_compose.py), убраны replicas/platform/SENTRY,
# секреты БЕЗ дефолтных значений — их генерирует scripts/bootstrap_bundle.py).
#
# Этот файл НЕ исполняется в нашем прод-контуре (корневой docker-compose.yml —
# байт-в-байт, заморожен анти-дрейфом ORCH-102); активация — только явный запуск
# оператором на целевом хосте (паттерн ORCH-009, kill-switch не нужен).
#
# Конфиг-слои (D2): интерполяции ${VAR} читаются compose'ом из deploy/bundled/.env
# (авто-чтение из project dir — без --env-file-футгана); канон ключей —
# deploy/bundled/.env.example (key-set-sync держит тест). Runtime-конфиг орка и
# watchdog — КОРНЕВЫЕ .env / .env.watchdog (канон Lite 1:1, REPLICATION §2);
# их единственный писатель — bootstrap_bundle.py.
#
# Сеть (D4): одна bridge-сеть проекта; машинный трафик — строго сервис-DNS
# (Plane→орк http://orchestrator:8500/webhook/plane, Gitea→орк .../webhook/gitea,
# орк→Plane http://proxy, орк→Gitea http://gitea:3000); network_mode: host НЕ
# используется (ssh-деплой-контур нашего хоста в bundle структурно спит —
# ORCH_DEPLOY_SSH_HOST пуст). Наружу публикуются ТОЛЬКО человеческие порты
# (орк/Plane proxy/Gitea web); postgres/redis/mq/minio не публикуются.
#
# Project name = узнаваемый префикс томов/контейнеров orchestrator-bundle_* (D1);
# container_name сознательно НЕ пиннится ни у кого — bundle и Lite/корневой
# compose не сталкиваются по именам на одном хосте.
name: orchestrator-bundle
networks:
default:
name: orchestrator-bundle
driver: bridge
# Env-контракт Plane CE — upstream-имена (référence v0.23.1). Значения секретов
# (POSTGRES_PASSWORD/SECRET_KEY/RABBITMQ_DEFAULT_PASS/MINIO_ROOT_PASSWORD) живут
# ТОЛЬКО в deploy/bundled/.env (генерирует bootstrap); дефолтных паролей нет.
x-plane-env: &plane-env
environment:
- WEB_URL=http://${BUNDLE_PUBLIC_HOST:-localhost}:${BUNDLE_PLANE_PORT:-8080}
- DEBUG=0
- CORS_ALLOWED_ORIGINS=http://${BUNDLE_PUBLIC_HOST:-localhost}:${BUNDLE_PLANE_PORT:-8080}
- GUNICORN_WORKERS=1
# db (upstream-имена; host/port — фиксированные сервис-DNS этого файла)
- PGHOST=plane-db
- PGDATABASE=${POSTGRES_DB:-plane}
- POSTGRES_USER=${POSTGRES_USER:-plane}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- POSTGRES_DB=${POSTGRES_DB:-plane}
- POSTGRES_PORT=5432
- PGDATA=/var/lib/postgresql/data
- DATABASE_URL=postgresql://${POSTGRES_USER:-plane}:${POSTGRES_PASSWORD}@plane-db:5432/${POSTGRES_DB:-plane}
# redis
- REDIS_HOST=plane-redis
- REDIS_PORT=6379
- REDIS_URL=redis://plane-redis:6379/
# rabbitmq
- RABBITMQ_HOST=plane-mq
- RABBITMQ_PORT=5672
- RABBITMQ_DEFAULT_USER=${RABBITMQ_DEFAULT_USER:-plane}
- RABBITMQ_DEFAULT_PASS=${RABBITMQ_DEFAULT_PASS}
- RABBITMQ_DEFAULT_VHOST=${RABBITMQ_DEFAULT_VHOST:-plane}
- RABBITMQ_VHOST=${RABBITMQ_DEFAULT_VHOST:-plane}
- AMQP_URL=amqp://${RABBITMQ_DEFAULT_USER:-plane}:${RABBITMQ_DEFAULT_PASS}@plane-mq:5672/${RABBITMQ_DEFAULT_VHOST:-plane}
# application secret (генерирует bootstrap; дефолта сознательно НЕТ)
- SECRET_KEY=${SECRET_KEY}
# datastore (minio)
- USE_MINIO=1
- AWS_REGION=
- AWS_ACCESS_KEY_ID=${MINIO_ROOT_USER:-plane-minio-admin}
- AWS_SECRET_ACCESS_KEY=${MINIO_ROOT_PASSWORD}
- AWS_S3_ENDPOINT_URL=http://plane-minio:9000
- AWS_S3_BUCKET_NAME=uploads
- MINIO_ROOT_USER=${MINIO_ROOT_USER:-plane-minio-admin}
- MINIO_ROOT_PASSWORD=${MINIO_ROOT_PASSWORD}
- BUCKET_NAME=uploads
- FILE_SIZE_LIMIT=5242880
# live server
- API_BASE_URL=http://api:8000
# proxy
- NGINX_PORT=80
services:
# ── Платформа: орк + sidecar-watchdog (образы собираются из этого же чекаута;
# корневой Dockerfile / watchdog/Dockerfile — без правок, NFR-1) ──────────
orchestrator:
build:
context: ../..
# ORCH-101 (D5): uid/gid/home двигаются ОДНОЙ группой с user: и таргетами
# маунтов ниже (инвариант ORCH-040). Дефолты bundle нейтральны (D2).
args:
APP_UID: ${ORCH_RUN_UID:-1000}
APP_GID: ${ORCH_RUN_GID:-1000}
APP_HOME: ${ORCH_AGENT_HOME_DIR:-/home/orchestrator}
restart: unless-stopped
user: "${ORCH_RUN_UID:-1000}:${ORCH_RUN_GID:-1000}"
init: true
command: ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8500"]
ports:
# человеческая точка: операторский smoke `curl /health` (D4)
- "${BUNDLE_ORCH_PORT:-8500}:8500"
volumes:
# данные/репозитории — bind ВНУТРИ project dir (uid-причины ORCH-040;
# покрыты .gitignore: неякорный data/ + deploy/bundled/repos/)
- ./data:/app/data
- ./repos:/repos
- /var/run/docker.sock:/var/run/docker.sock
# LLM-предусловие хоста заказчика (bundle его НЕ поставляет, BRD §1.3)
- ${ORCH_HOST_CLAUDE_CODE_DIR:-/usr/lib/node_modules/@anthropic-ai/claude-code}:/opt/claude-code:ro
- ${ORCH_HOST_NODE_BIN:-/usr/bin/node}:/usr/bin/node:ro
- ${ORCH_HOST_CLAUDE_DIR:-~/.claude}:${ORCH_AGENT_HOME_DIR:-/home/orchestrator}/.claude
- ${ORCH_HOST_CLAUDE_JSON:-~/.claude.json}:${ORCH_AGENT_HOME_DIR:-/home/orchestrator}/.claude.json:ro
# ssh-контур в bundle сознательно НЕ вводится (ADR D8): git-доступ агентов
# — HTTP token-remote, деплой-хуки нашего хоста структурно спят.
# runtime-конфиг орка собирает bootstrap (шаг 8); required:false — первый
# `up -d` поднимает стек ДО сборки конфига (AC-1), орк жив без него.
env_file:
- path: ../../.env
required: false
environment:
- ORCH_REPOS_DIR=/repos
group_add:
- "${ORCH_DOCKER_GID:-999}"
orchestrator-watchdog:
build:
context: ../..
dockerfile: watchdog/Dockerfile
restart: unless-stopped
init: true
mem_limit: 128m
mem_reservation: 32m
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./repos:/repos:ro
- ./data:/app/data:ro
env_file:
- path: ../../.env.watchdog
required: false
environment:
# bundle-сеть ≠ host-network Lite: метрики — по сервис-DNS; имя контейнера
# орка детерминировано project name (container_name не пиннится, D1).
# environment перекрывает env_file → когерентность механическая (TR-8).
- WATCHDOG_METRICS_URL=http://orchestrator:8500/metrics
- WATCHDOG_CONTAINERS=orchestrator-bundle-orchestrator-1
group_add:
- "${ORCH_DOCKER_GID:-999}"
# ── Gitea (D6): официальный образ, НЕ rootless; init полностью автоматом —
# bootstrap создаёт админа/токен через `gitea admin ...` CLI в контейнере.
# Branch protection на main НЕ настраивается (норматив D10 ORCH-009/INV-4).
gitea:
image: gitea/gitea:1.22.6
restart: unless-stopped
ports:
- "${BUNDLE_GITEA_HTTP_PORT:-3000}:3000"
environment:
- GITEA__database__DB_TYPE=sqlite3
- GITEA__security__INSTALL_LOCK=true
- GITEA__server__DOMAIN=${BUNDLE_PUBLIC_HOST:-localhost}
- GITEA__server__ROOT_URL=http://${BUNDLE_PUBLIC_HOST:-localhost}:${BUNDLE_GITEA_HTTP_PORT:-3000}/
# ssh-контур не вводится (D8): порт не публикуется, ssh выключен.
- GITEA__server__DISABLE_SSH=true
- GITEA__service__DISABLE_REGISTRATION=true
# МИНА TR-4 (D4): Gitea по умолчанию режет webhook'и в приватные адреса —
# без этой строки «задача не появилась» гарантирован.
- GITEA__webhook__ALLOWED_HOST_LIST=orchestrator
volumes:
- gitea-data:/data
healthcheck:
test: ["CMD", "curl", "-fsS", "http://localhost:3000/api/healthz"]
interval: 10s
timeout: 5s
retries: 12
# ── Plane CE — зеркало upstream selfhost-référence v0.23.1 (D3) ────────────
web:
<<: *plane-env
image: makeplane/plane-frontend:v0.23.1
restart: unless-stopped
command: node web/server.js web
depends_on:
- api
- worker
space:
<<: *plane-env
image: makeplane/plane-space:v0.23.1
restart: unless-stopped
command: node space/server.js space
depends_on:
- api
- worker
- web
admin:
<<: *plane-env
image: makeplane/plane-admin:v0.23.1
restart: unless-stopped
command: node admin/server.js admin
depends_on:
- api
- web
live:
<<: *plane-env
image: makeplane/plane-live:v0.23.1
restart: unless-stopped
command: node live/dist/server.js live
depends_on:
- api
- web
api:
<<: *plane-env
image: makeplane/plane-backend:v0.23.1
restart: unless-stopped
command: ./bin/docker-entrypoint-api.sh
volumes:
- logs_api:/code/plane/logs
depends_on:
- plane-db
- plane-redis
- plane-mq
worker:
<<: *plane-env
image: makeplane/plane-backend:v0.23.1
restart: unless-stopped
command: ./bin/docker-entrypoint-worker.sh
volumes:
- logs_worker:/code/plane/logs
depends_on:
- api
- plane-db
- plane-redis
- plane-mq
beat-worker:
<<: *plane-env
image: makeplane/plane-backend:v0.23.1
restart: unless-stopped
command: ./bin/docker-entrypoint-beat.sh
volumes:
- logs_beat-worker:/code/plane/logs
depends_on:
- api
- plane-db
- plane-redis
- plane-mq
migrator:
<<: *plane-env
image: makeplane/plane-backend:v0.23.1
restart: "no"
command: ./bin/docker-entrypoint-migrator.sh
volumes:
- logs_migrator:/code/plane/logs
depends_on:
- plane-db
- plane-redis
plane-db:
<<: *plane-env
image: postgres:15.7-alpine
restart: unless-stopped
command: postgres -c 'max_connections=1000'
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
interval: 10s
timeout: 5s
retries: 12
plane-redis:
<<: *plane-env
image: valkey/valkey:7.2.5-alpine
restart: unless-stopped
volumes:
- redisdata:/data
healthcheck:
test: ["CMD", "valkey-cli", "ping"]
interval: 10s
timeout: 5s
retries: 12
plane-mq:
<<: *plane-env
image: rabbitmq:3.13.6-management-alpine
restart: always
volumes:
- rabbitmq_data:/var/lib/rabbitmq
healthcheck:
test: ["CMD", "rabbitmq-diagnostics", "-q", "ping"]
interval: 15s
timeout: 10s
retries: 12
plane-minio:
<<: *plane-env
# upstream-référence держит latest — bundle пиннит неподвижный тег (NFR-6)
image: minio/minio:RELEASE.2024-05-28T17-19-04Z
restart: unless-stopped
command: server /export --console-address ":9090"
volumes:
- uploads:/export
healthcheck:
test: ["CMD", "curl", "-fsS", "http://localhost:9000/minio/health/live"]
interval: 10s
timeout: 5s
retries: 12
proxy:
<<: *plane-env
image: makeplane/plane-proxy:v0.23.1
restart: unless-stopped
ports:
# человеческая точка: UI Plane в браузере оператора (D4)
- "${BUNDLE_PLANE_PORT:-8080}:80"
depends_on:
- web
- api
- space
# Состояние Plane/Gitea — именованные тома проекта (префикс orchestrator-bundle_,
# D1/D2); preflight bootstrap детектирует «грязный хост» по этому префиксу.
volumes:
pgdata:
redisdata:
uploads:
logs_api:
logs_worker:
logs_beat-worker:
logs_migrator:
rabbitmq_data:
gitea-data:

View File

@@ -1,27 +1,141 @@
# ORCH-101 (replication foundation): every host-specific value is interpolated
# as ${VAR:-default}; the defaults equal the current production values, so an
# empty environment resolves to a byte-for-byte equivalent of the previous file
# (zero regression, BR-5). Compose reads ${VAR} from the project `.env` /shell —
# NOT from a service's env_file (so .env.staging does NOT interpolate); the
# Settings-shared names (ORCH_AGENT_HOME_DIR, ORCH_STAGING_PORT, ...) are read
# by pydantic from env_file AND by compose from .env — one name per fact (D1).
# Container-side paths (/app/data, /repos, /opt/claude-code, docker.sock) are a
# container-layout convention, NOT host values — deliberately not parametrised.
# See docs/operations/REPLICATION.md for the full variable map.
services:
orchestrator:
build: .
build:
context: .
# ORCH-101 (D5): uid/gid/home move as ONE coherent group with the runtime
# user: and the mount targets below (ORCH-040 invariant).
args:
APP_UID: ${ORCH_RUN_UID:-1000}
APP_GID: ${ORCH_RUN_GID:-1000}
APP_HOME: ${ORCH_AGENT_HOME_DIR:-/home/slin}
container_name: orchestrator
restart: unless-stopped
# ORCH-040: бежим под uid:gid хоста (slin=1000:1000), а не root, чтобы
# артефакты конвейера (worktree + docs) создавались как slin:slin и git на
# хосте работал без ручного chown. Доступ к docker.sock сохранён через
# group_add: ["999"] (МИНА 1 — НЕ удалять). См. ADR-001 ORCH-040.
user: "${ORCH_RUN_UID:-1000}:${ORCH_RUN_GID:-1000}"
# init: true injects docker-init (tini) as PID 1 so reparented grandchild
# processes from the claude/node subprocess tree are reaped (no zombies, B-2).
init: true
network_mode: host
# ORCH-101 (D5): the prod port is configurable on the compose layer (the
# Dockerfile CMD keeps its exec-form 8500 default — ADR-001 D5); the default
# resolves byte-for-byte to the previous image CMD. Reuses the existing
# ORCH_DEPLOY_PROD_TARGET_PORT (no second truth about the prod port).
command: ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "${ORCH_DEPLOY_PROD_TARGET_PORT:-8500}"]
volumes:
- ./data:/app/data
- /home/slin/repos:/repos
- ${ORCH_HOST_REPOS_DIR:-/home/slin/repos}:/repos
- /var/run/docker.sock:/var/run/docker.sock
- /usr/lib/node_modules/@anthropic-ai/claude-code:/opt/claude-code:ro
- /usr/bin/node:/usr/bin/node:ro
- /home/slin/.claude:/home/slin/.claude
- /home/slin/.claude.json:/home/slin/.claude.json:ro
- /home/slin/.orchestrator-ssh:/root/.ssh:ro
- ${ORCH_HOST_CLAUDE_CODE_DIR:-/usr/lib/node_modules/@anthropic-ai/claude-code}:/opt/claude-code:ro
- ${ORCH_HOST_NODE_BIN:-/usr/bin/node}:/usr/bin/node:ro
- ${ORCH_HOST_CLAUDE_DIR:-/home/slin/.claude}:${ORCH_AGENT_HOME_DIR:-/home/slin}/.claude
- ${ORCH_HOST_CLAUDE_JSON:-/home/slin/.claude.json}:${ORCH_AGENT_HOME_DIR:-/home/slin}/.claude.json:ro
# ORCH-040: target согласован с HOME (launcher: settings.agent_home_dir),
# не /root/.ssh — обе стороны двигаются одной переменной ORCH_AGENT_HOME_DIR.
- ${ORCH_HOST_SSH_DIR:-/home/slin/.orchestrator-ssh}:${ORCH_AGENT_HOME_DIR:-/home/slin}/.ssh:ro
env_file: .env
environment:
- ORCH_REPOS_DIR=/repos
- ORCH_HOST_REPOS_DIR=/home/slin/repos
- DEPLOY_SSH_USER=slin
- ORCH_HOST_REPOS_DIR=${ORCH_HOST_REPOS_DIR:-/home/slin/repos}
# legacy enduro deployer (read via os.environ, keep as-is):
- DEPLOY_SSH_USER=${ORCH_DEPLOY_SSH_USER:-slin}
- DEPLOY_SSH_HOST=127.0.0.1
- DEPLOY_HOOK_SCRIPT=/home/slin/bin/enduro-deploy-hook.sh
- DEPLOY_HOOK_SCRIPT=${DEPLOY_HOOK_SCRIPT:-/home/slin/bin/enduro-deploy-hook.sh}
# ORCH-036 self-deploy (read via pydantic ORCH_ prefix; host-network -> 127.0.0.1, ssh key mounted):
- ORCH_DEPLOY_SSH_USER=${ORCH_DEPLOY_SSH_USER:-slin}
- ORCH_DEPLOY_SSH_HOST=127.0.0.1
- ORCH_DEPLOY_HOOK_SCRIPT=scripts/orchestrator-deploy-hook.sh
- ORCH_DEPLOY_HOST_REPO_PATH=${ORCH_DEPLOY_HOST_REPO_PATH:-/home/slin/repos/orchestrator}
group_add:
- "999"
- "${ORCH_DOCKER_GID:-999}"
# ORCH-100 (FND/F1b): sidecar-watchdog — the monitoring brain in a SEPARATE
# container (observer separated from observed, ADR-001 D2). Deploying it builds
# ONLY this service — the prod `orchestrator` is NOT rebuilt/restarted.
# * network_mode: host -> /metrics reachable at http://127.0.0.1:8500/metrics
# and host interfaces visible for memory/disk reads.
# * docker.sock mounted :ro AND the code is GET-only (double read-only guard).
# * host disk paths bind-mounted :ro so shutil.disk_usage sees the host FS but
# can never write (opt-in disk ceiling, D6).
# * mem_limit caps the thin stdlib daemon (D2): OOM = early "sidecar grew" signal.
# * WATCHDOG_ENABLED=false (or simply not starting the service) -> inert.
orchestrator-watchdog:
build:
context: .
dockerfile: watchdog/Dockerfile
container_name: orchestrator-watchdog
restart: unless-stopped
init: true
network_mode: host
mem_limit: 128m
mem_reservation: 32m
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ${ORCH_HOST_REPOS_DIR:-/home/slin/repos}:/repos:ro
- ./data:/app/data:ro
# Optional env_file (required: false): a missing .env.watchdog must NOT fail
# `docker compose up` for the prod orchestrator (self-hosting safety). Absent
# file -> WATCHDOG_* defaults, no token -> fail-safe (logs, does not send).
env_file:
- path: .env.watchdog
required: false
group_add:
- "${ORCH_DOCKER_GID:-999}"
# ORCH-31: staging instance (port 8501, isolated DB).
# Starts ONLY with: docker compose --profile staging up -d orchestrator-staging
# Normal "docker compose up -d" does NOT start this service.
orchestrator-staging:
profiles:
- staging
build:
context: .
args:
APP_UID: ${ORCH_RUN_UID:-1000}
APP_GID: ${ORCH_RUN_GID:-1000}
APP_HOME: ${ORCH_AGENT_HOME_DIR:-/home/slin}
container_name: orchestrator-staging
restart: unless-stopped
# ORCH-040: тот же uid хоста, что и у prod (см. комментарий выше / ADR-001).
user: "${ORCH_RUN_UID:-1000}:${ORCH_RUN_GID:-1000}"
init: true
network_mode: host
# ORCH-101 (D4): the same ORCH_STAGING_PORT that settings.staging_port reads —
# the image_freshness rebuild target and the listening port can never drift.
command: ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "${ORCH_STAGING_PORT:-8501}"]
volumes:
- ./data/staging:/app/data
- ${ORCH_HOST_REPOS_DIR:-/home/slin/repos}:/repos
- /var/run/docker.sock:/var/run/docker.sock
- ${ORCH_HOST_CLAUDE_CODE_DIR:-/usr/lib/node_modules/@anthropic-ai/claude-code}:/opt/claude-code:ro
- ${ORCH_HOST_NODE_BIN:-/usr/bin/node}:/usr/bin/node:ro
- ${ORCH_HOST_CLAUDE_DIR:-/home/slin/.claude}:${ORCH_AGENT_HOME_DIR:-/home/slin}/.claude
- ${ORCH_HOST_CLAUDE_JSON:-/home/slin/.claude.json}:${ORCH_AGENT_HOME_DIR:-/home/slin}/.claude.json:ro
# ORCH-040: target согласован с HOME (settings.agent_home_dir), не /root/.ssh.
- ${ORCH_HOST_SSH_DIR:-/home/slin/.orchestrator-ssh}:${ORCH_AGENT_HOME_DIR:-/home/slin}/.ssh:ro
env_file: .env.staging
environment:
- ORCH_REPOS_DIR=/repos
- ORCH_HOST_REPOS_DIR=${ORCH_HOST_REPOS_DIR:-/home/slin/repos}
- DEPLOY_SSH_USER=${ORCH_DEPLOY_SSH_USER:-slin}
- DEPLOY_SSH_HOST=127.0.0.1
- DEPLOY_HOOK_SCRIPT=${DEPLOY_HOOK_SCRIPT:-/home/slin/bin/enduro-deploy-hook.sh}
# Staging DB is isolated via ./data/staging volume mount.
# Inside the container the path remains /app/data/orchestrator.db (same default),
# but on the host it physically lives at ./data/staging/orchestrator.db —
# completely separate from prod ./data/orchestrator.db.
- ORCH_DB_PATH=/app/data/orchestrator.db
group_add:
- "${ORCH_DOCKER_GID:-999}"

135
docs/PRODUCT_VISION.md Normal file
View File

@@ -0,0 +1,135 @@
# Product Vision — Автономная фабрика разработки (Orchestrator)
> Мультиагентная платформа, которая превращает идею или баг в задеплоенный на прод результат — автономно, надёжно и дёшево.
**Версия:** 1.0 · **Дата:** 2026-06-04 · **Статус:** концепция развития
> **Фактическое текущее состояние платформы** (что уже умеет, как устроена) — витрина системы
> [docs/overview/](overview/README.md) (ORCH-011). Этот документ — vision: «куда идём».
---
## 1. Зачем это (бизнес-взгляд)
### Проблема
Классическая разработка — это люди-бутылочное-горлышко на каждом шаге: аналитик, архитектор, разработчик, ревьюер, тестировщик, деплой-инженер. Каждая передача задачи между ними — потеря времени, контекста и денег. Мелкая фича или баг едут днями.
### Решение
**Orchestrator** — это конвейер из ИИ-агентов, который проводит задачу через все стадии разработки сам: от бизнес-постановки до релиза на прод. Человек ставит задачу и принимает результат. Всё между — автономно.
### Ценность
-**Скорость:** фича проходит полный цикл (анализ → архитектура → код → ревью → тесты → деплой) за ~35 минут без ручных вмешательств.
- 💰 **Стоимость:** работа агентов в разы дешевле команды; адаптивный выбор моделей экономит на простых задачах.
- 🎯 **Автономность:** 0 ручных пинков в штатном прогоне. Человек — постановщик и приёмщик, а не оператор.
- 🛡️ **Надёжность:** многоуровневые гейты качества не пускают недоделку на прод.
- 🔁 **Масштаб:** одна платформа ведёт несколько проектов; саму платформу можно тиражировать на новые хосты.
---
## 2. Как это работает (обзор)
### Конвейер
```
created → analysis → architecture → development → review → testing → deploy → done
```
На каждом переходе стоит **quality gate** — автоматическая проверка, которая не пускает задачу дальше, пока стадия не выполнена честно:
| Переход | Гейт | Что проверяет |
|---|---|---|
| analysis → architecture | check_analysis_approved | BRD/TRZ/AC готовы + апрув человека |
| architecture → development | check_architecture_done | Архитектура/ADR зафиксированы |
| development → review | check_ci_green | CI зелёный (тесты проходят) |
| review → testing | check_reviewer_verdict | Машинный вердикт ревьюера: APPROVED |
| testing → deploy | check_tests_passed | Машинный вердикт тестера (не подделать) |
| deploy → done | check_deploy_status | Деплой реально успешен, лог в origin/main |
### Агенты
- **Analyst** — собирает бизнес-требования, пишет BRD/TRZ/критерии приёмки.
- **Architect** — проектирует решение, фиксирует ADR.
- **Developer** — пишет код в изолированном git-worktree.
- **Reviewer** — ревьюит, выносит машинный вердикт.
- **Tester** — прогоняет тесты, фиксирует результат в отчёте.
- **Deployer** — мержит, тегирует, деплоит на прод, пишет deploy-log.
### Объекты
- **Project** — проект в реестре (Plane project ↔ git-репозиторий ↔ префикс задач).
- **Work-Item** — задача, проходящая конвейер; на каждой стадии накапливает артефакты (00-business-request … 14-deploy-log).
- **Job** — единица работы в очереди (atomic claim, ретраи, restart-safe).
### Интеграции
- **Plane** — управление задачами, статусы как триггеры конвейера, webhooks.
- **Gitea** — репозитории, PR, защита main (pre-receive hook).
- **Telegram** — живой трекер прогресса, апрувы, уведомления.
- **LLM** — модели агентов (сейчас Claude, в планах мультипровайдерность).
---
## 3. Что уже сделано (фундамент)
**Автономный конвейер** — подтверждён живым прогоном: задача от issue до Done без ручных вмешательств (~35 мин).
**Очередь задач** — atomic claim, max_concurrency, ретраи, restart-safe.
**Изоляция через git-worktree** — каждая задача в своём дереве, без конфликтов в shared-репо.
**Машинные гейты качества** — вердикты читаются из структурированных артефактов, а не угадываются по тексту.
**Multi-repo** — платформа ведёт несколько проектов (enduro-trails, сам orchestrator).
**Идемпотентность webhooks** — дедуп по delivery-id, защита от дублей.
**Наблюдаемость** — учёт токенов и стоимости каждой задачи.
**Живой Telegram-трекер** — прогресс редактируется в одном сообщении, без спама.
---
## 4. Куда движемся (дорожная карта)
Развитие сгруппировано в 5 стратегических направлений.
### 🛡️ Надёжность и безопасность
- **Post-deploy мониторинг + авто-rollback** — следить за продом после релиза, откатывать при деградации.
- **Security-гейт** — secret-scanning + аудит зависимостей перед мержем.
- **Бюджетный circuit-breaker** — хард-лимит стоимости на задачу, защита от «убегающих» расходов.
- **Опциональная human-приёмка** — финальный взгляд человека для критичных фич.
### 💰 Экономика и интеллект
- **Мультипровайдерность LLM** — Claude, OpenRouter, другие провайдеры на выбор.
- **Оценка задачи** — прогноз стоимости/времени до старта.
- **Адаптивный выбор модели** — по сложности: тривиальное на дешёвой, сложное на сильной.
- **Багфикс-трек** — упрощённый дешёвый путь для багов (без потери качества).
### 🏗️ Платформа и масштаб
- **Self-hosting** — оркестратор пилит сам себя через собственный конвейер.
- **Саморазвитие** — петля уроков: ловить отклонения → фиксировать → предлагать улучшения.
- **Онбординг проектов** — turnkey-заведение нового проекта в систему.
- **Тиражирование** — развернуть платформу на новой инфраструктуре под ключ.
### 💬 Взаимодействие с человеком
- **UX/UI дизайнер** — макеты интерфейсов на этапе аналитики.
- **Интерактивный аналитик** — живой диалог для уточнения требований и обсуждения макетов.
- **Единые коммент-артефакты** — все агенты прикладывают результаты с кликабельными ссылками.
- **Прямые ссылки в Telegram** — апрув в один клик, без блужданий.
### 🧩 Расширение возможностей
- **Тяжёлые расчёты данных** — опциональная стадия для миграций/обработки больших данных.
- **Android-разработка** — мобильный стек через тот же конвейер.
- **Декомпозиция эпиков** — большая фича → подзадачи → сборка.
- **Управление зависимостями** — задача B ждёт задачу A.
- **Code coverage gate** — защита покрытия тестами от деградации.
- **База знаний проекта** — персистентный контекст для агентов.
---
## 5. Принципы (что для нас неизменно)
1. **Автономность по умолчанию, человек — на ключевых развилках.** Машина делает, человек ставит и принимает.
2. **Качество не приносится в жертву скорости/цене.** Удешевляем аналитику — гейты качества остаются. Урок дорого выученный: срезанная проверка = недоделка на проде.
3. **Машинные вердикты, а не угадывание.** Гейты читают структурированные поля, а не ищут слова в тексте.
4. **Самоизменение — только через PR + ревью + апрув.** Агент, меняющий агентов, всегда под контролем человека.
5. **Документация — сразу, не потом.** Изменил функционал → обновил доки.
6. **Прод — источник правды.** «Деплой прошёл» ≠ «работает». Проверяем реальный результат.
---
## 6. Видение в одну фразу
> **Самодостаточная фабрика разработки, которая размножается, учится на ошибках, оценивает себя, бережёт бюджет и не ломает прод — превращая намерение человека в работающий продукт почти без его участия.**
---
*Документ поддерживается в репозитории orchestrator. Источник дорожной карты — задачи проекта ORCH в Plane (ORCH-7…ORCH-28).*

BIN
docs/PRODUCT_VISION.pptx Normal file

Binary file not shown.

View File

@@ -0,0 +1,118 @@
# HANDOFF_PROTOCOL — формальный контракт handoff «стадия → обязательный выход»
> **Назначение.** Нормативная спека: что КАЖДАЯ стадия конвейера обязана оставить на выходе —
> какие документы и какие frontmatter-ключи. Дополняет [`PIPELINE_DOCS.md`](PIPELINE_DOCS.md)
> (карта «документ → агент → стадия → гейт → machine-key») «вертикальным» срезом по стадиям и
> вводит **обязательную frontmatter-схему** для машинной проверки.
>
> **Статус истины (важно).** Источник истины поведения — **код**: `src/stages.py`
> (`STAGE_TRANSITIONS`), `src/qg/checks.py` (`QG_CHECKS` / `check_*` / `_parse_*`),
> `src/stage_engine.py` (врезки под-гейтов). Машинный контракт чтения/записи/валидации
> frontmatter — `src/frontmatter.py`. Эта спека **документирует**; при расхождении первичен код
> (правило ORCH-075).
Введено задачей **ORCH-076** (ORCH-52c — слой 2 эпика ORCH-52: машинный контракт). Слой 1
(ORCH-075/52b) дал описательный стандарт документов; ORCH-52c реализовала единый машинный
frontmatter-контракт (reader + writer + валидатор) и свела чтение пяти вердиктов к одной точке
парсинга. Сквозной ADR: [`adr-0020-frontmatter-contract.md`](../architecture/adr/adr-0020-frontmatter-contract.md);
детально — [`ORCH-076/06-adr/ADR-001-frontmatter-contract.md`](../work-items/ORCH-076/06-adr/ADR-001-frontmatter-contract.md).
---
## 1. Обязательная frontmatter-схема (машинный источник: `frontmatter.REQUIRED_FIELDS`)
Forward-looking аддитивная схема: набор полей, которые handoff-документ стадии **должен** нести
в ведущем YAML-frontmatter. Машинный источник истины — кортеж
[`src/frontmatter.py`](../../src/frontmatter.py) `REQUIRED_FIELDS`:
| Поле | Смысл |
|------|-------|
| `work_item` | ID задачи (`ORCH-NNN` / `ET-NNN`) — к какой задаче относится выход |
| `stage` | стадия, на выходе которой написан документ (`analysis``deploy`) |
| `author_agent` | роль-автор (`analyst` / `architect` / `developer` / `reviewer` / `tester` / `deployer`) |
| `status` | человеко/машинно-читаемый статус выхода стадии |
| `created_at` | дата создания артефакта (YYYY-MM-DD) |
| `model_used` | модель агента, сгенерировавшего артефакт (`claude-…`) |
**Режим проверки (ORCH-52c, критично для self-hosting).** Валидатор схемы
`frontmatter.validate_schema` / `maybe_warn_schema` по умолчанию **warning-only** и **никогда не
влияет на boolean-вердикт ни одного гейта**: отсутствие полей логируется (`logger.warning`), но не
роняет конвейер и не заваливает гейт. Жёсткий режим (hard-fail) зарезервирован на будущее
(ORCH-52d) и включается ТОЛЬКО kill-switch'ем `frontmatter_validation_strict`
(env `ORCH_FRONTMATTER_VALIDATION_STRICT`, дефолт `False`). Схема **аддитивна**: старый
документ-вердикт без этих полей читается гейтом ровно как раньше (см. §3).
---
## 2. Контракт handoff по стадиям
Категории документов — как в `PIPELINE_DOCS.md` §2: **required** (всегда), **when-applicable**
(при наличии предмета: инфра / данные / security / post-deploy — отсутствие не нарушение).
«Machine-verdict ключ» — поле, которое exit-гейт/под-гейт ребра читает ТОЛЬКО из frontmatter
(никогда из прозы). Набор документов/ключей/гейтов **согласован 1:1 с `PIPELINE_DOCS.md` §2§3**.
| Стадия (выход) | Агент | Обязательные документы на выходе | Machine-verdict ключ (читает гейт ребра) | Гейт ребра |
|----------------|-------|----------------------------------|------------------------------------------|------------|
| `created` | система (`_create_initial_docs`) / заказчик | `00-business-request.md` | — (вход, не гейтится) | — |
| `analysis` | analyst | `01-brd.md`, `02-trz.md`, `03-acceptance-criteria.md`, `04-test-plan.yaml` | — (гейт проверяет наличие файлов + Approved) | `check_analysis_approved` |
| `architecture` | architect | `06-adr/ADR-NNN-<slug>.md` (≥1); `07-infra-requirements.md`, `08-data-requirements.md`, `10-tech-risks.md` (when-applicable/required-info) | — (гейт проверяет наличие `06-adr/` ≥1 ИЛИ `07-…`) | `check_architecture_done` |
| `development` | developer | код + тесты в ветке (артефакт-док не пишется; гейт — зелёный CI) | — (гейт читает CI-статус Gitea) | `check_ci_green` |
| `review` | reviewer | `12-review.md` | `verdict:` (`APPROVED` \| `REQUEST_CHANGES`) | `check_reviewer_verdict` |
| `testing` | tester | `13-test-report.md` | `result:` / `verdict:` / `status:` (`PASS` \| `FAIL` \| `BLOCKED`; три равноранговых, ORCH-047) | `check_tests_passed` |
| `deploy-staging` | deployer | `15-staging-log.md` (required для self-hosting); `17-security-report.md` (security-под-гейт, when-applicable) | `staging_status:` (`SUCCESS` \| `FAILED`); `security_status:` (`PASS` \| `FAIL`) | `check_staging_status` (ребро); под-гейты ребра `deploy-staging→deploy`: `check_security_gate``check_branch_mergeable``check_staging_image_fresh` |
| `deploy` | deployer / deploy-finalizer | `14-deploy-log.md` | `deploy_status:` (`SUCCESS` \| `FAILED`) | `check_deploy_status` |
| `done` | — | — (терминал) | — | — |
| пост-`done` наблюдение | post-deploy-monitor | `16-post-deploy-log.md` (when-applicable, ORCH-021) | `post_deploy_status:` (`HEALTHY` \| `DEGRADED`) — **информационный, не гейт** | — (телеметрия петли уроков / наблюдаемость) |
### Примечания (нормативные)
- **Под-гейты ребра `deploy-staging → deploy`** (`check_security_gate``check_branch_mergeable`
`check_staging_image_fresh`) — это **врезки в `advance_stage`**, а НЕ строки
`STAGE_TRANSITIONS`. Их порядок и условность раската не меняются этой спекой.
- **`15-staging-log.md`** обязателен только для self-hosting репо (`orchestrator`); для прочих
репо staging-гейт — N/A (ORCH-35), документ не требуется.
- **`16-post-deploy-log.md`** несёт `post_deploy_status:`, но это **информационный** ключ
(телеметрия ORCH-8 / наблюдаемость), гейтом он НЕ парсится.
- **`09-…` / `05-…` / `11-…`** — зарезервированные/legacy номера; канон reviewer'а`12-review.md`.
---
## 3. Machine-verdict доки vs информационные (честный механизм проверки)
Полностью согласовано с `PIPELINE_DOCS.md` §3. Machine-verdict док — гейт читает ТОЛЬКО
YAML-frontmatter (через единый `frontmatter.parse_frontmatter`), маппит ключ в вердикт; имя ключа
чувствительно к регистру, значение парсер приводит к верхнему регистру.
| Документ | Machine-key | Парсер | Эффект вердикта |
|----------|-------------|--------|-----------------|
| `12-review.md` | `verdict:` | `check_reviewer_verdict` | `APPROVED` → дальше; `REQUEST_CHANGES` → откат на `development` |
| `13-test-report.md` | `result:` / `verdict:` / `status:` | `_parse_tests_verdict` | `PASS` → дальше; `FAIL`/`BLOCKED` → откат (негативный токен авторитетен) |
| `14-deploy-log.md` | `deploy_status:` | `_parse_deploy_status` | `SUCCESS``done`; `FAILED` → откат (БАГ-8) |
| `15-staging-log.md` | `staging_status:` | `_parse_staging_status` | `SUCCESS` → дальше; `FAILED` → откат (self-hosting; иначе N/A) |
| `17-security-report.md` | `security_status:` | `check_security_gate``parse_security_status` | `PASS` → дальше; `FAIL` → откат |
**Информационные доки** (гейтом НЕ парсятся): `00-business-request.md`, `08-data-requirements.md`,
`10-tech-risks.md`, `16-post-deploy-log.md`.
**Аддитивность схемы (§1).** Документ-вердикт БЕЗ полей схемы из §1, но с вердикт-ключом, читается
гейтом РОВНО как раньше: схема не участвует в вычислении вердикта при дефолтном
`frontmatter_validation_strict=False`.
---
## 4. Единый машинный контракт — `src/frontmatter.py`
Все операции с frontmatter сведены в один leaf-модуль (never-raise):
- `read_frontmatter_value(path, key) -> str | None` — single-key reader (контракт неизменен, BC).
- `parse_frontmatter(content) -> FrontmatterParse`**единственная точка** парсинга YAML-frontmatter
(`data` / `has_block` / `malformed` / `yaml_error`); пять вердикт-парсеров делегируют сюда.
- `parse_frontmatter_dict` / `read_frontmatter` — ярлыки к распарсенному mapping.
- `render_frontmatter` / `write_frontmatter` — writer (формат совместим с существующими парсерами).
- `validate_schema` / `REQUIRED_FIELDS` / `maybe_warn_schema` — схема §1 (warning-only по умолчанию).
- `strip_frontmatter` — общий хелпер тела (заменил дубли).
- Kill-switch жёсткой валидации: `config.frontmatter_validation_strict`
(env `ORCH_FRONTMATTER_VALIDATION_STRICT`, дефолт `False`).
> Перед написанием номерного дока бери скелет из [`docs/_templates/`](../_templates/) и **не меняй
> имя machine-key frontmatter** (регистр чувствителен — иначе гейт упадёт ложно).

View File

@@ -0,0 +1,155 @@
# PIPELINE_DOCS — стандарт документов конвейера (golden source структуры)
> **Назначение.** Единая карта «стадия → агент → документ → категория → гейт/механизм →
> frontmatter machine-key» + конвенция ADR-naming. Это **golden source структуры** номерных
> документов work item (`00-business-request.md` … `18-coverage-report.md`), который каждая
> агентская роль пишет на своей стадии.
>
> **Статус истины (важно).** Манифест **документирует** текущее поведение гейтов, но НЕ является
> их источником истины. Источник истины — код: `src/stages.py` (`STAGE_TRANSITIONS`),
> `src/qg/checks.py` (`QG_CHECKS` / `check_*` / `_parse_*`), `src/stage_engine.py`. При будущей
> правке гейта первична правка кода, манифест обновляется следом (ORCH-075 / ADR-001 §D2).
>
> **Копируемые скелеты** каждого документа — в каталоге [`docs/_templates/`](../_templates/):
> «скопировал → заполнил → не угадываешь структуру/ключ».
Введён задачей **ORCH-075** (ORCH-52b — слой 1 эпика ORCH-52). Сквозной ADR:
[`docs/architecture/adr/adr-0019-pipeline-docs-standard.md`](../architecture/adr/adr-0019-pipeline-docs-standard.md);
детально — `docs/work-items/ORCH-075/06-adr/ADR-001-pipeline-docs-standard.md`.
---
## 1. Конвейер стадий (ground-truth `STAGE_TRANSITIONS`)
```
created → analysis → architecture → development → review → testing → deploy-staging → deploy → done
↑ │
└──── REQUEST_CHANGES ──────┘ (откат на development, max 3 retries)
```
Каждое ребро несёт ровно один exit-гейт (`src/stages.py`):
`check_analysis_approved → check_architecture_done → check_ci_green → check_reviewer_verdict →
check_tests_passed → check_staging_status → check_deploy_status`.
**Под-гейты ребра `deploy-staging → deploy`** (`check_security_gate``check_branch_mergeable`
`check_staging_image_fresh`) — это **врезки в `advance_stage`**, а НЕ строки `STAGE_TRANSITIONS`.
Аналогично под-гейт ребра `deploy → done` (`_handle_merge_verify`, ORCH-071/073) — врезка, не
зарегистрированный QG. Карта стадий о них не «лжёт»: они не являются стадиями.
---
## 2. Манифест: документ → агент → категория → стадия → гейт → machine-key
Категории: **required** (пишется всегда), **when-applicable** (пишется при наличии предмета:
инфра / данные / security / post-deploy — отсутствие не нарушение), **optional** / **legacy**.
| Документ | Владелец-агент | Категория | Стадия написания | Гейт / механизм проверки | Frontmatter machine-key |
|----------|----------------|-----------|------------------|--------------------------|-------------------------|
| `00-business-request.md` | система (Plane webhook `_create_initial_docs`) / заказчик | required | `created` (инициализация) | не гейтится (вход) | — |
| `01-brd.md` | analyst | required | `analysis` | exit-гейт `analysis→architecture` = `check_analysis_approved` (Approved + полнота файлов); helper `check_analysis_complete` (наличие `01/02/03/04`) | — |
| `02-trz.md` | analyst | required | `analysis` | то же | — |
| `03-acceptance-criteria.md` | analyst | required | `analysis` | то же | — |
| `04-test-plan.yaml` | analyst | required | `analysis` | то же | — |
| `06-adr/ADR-NNN-<slug>.md` | architect | required | `architecture` | `check_architecture_done` (наличие каталога `06-adr/` ≥1 файл ИЛИ `07-infra-requirements.md`) | — |
| `07-infra-requirements.md` | architect | when-applicable | `architecture` | `check_architecture_done` (учитывается при наличии) | — |
| `08-data-requirements.md` | architect | when-applicable | `architecture` | информационный (гейтом не парсится) | — |
| `10-tech-risks.md` | architect | required | `architecture` | информационный (гейтом не парсится) | — |
| `12-review.md` | reviewer | required | `review` | `check_reviewer_verdict` | `verdict:` (`APPROVED` \| `REQUEST_CHANGES`) |
| `13-test-report.md` | tester | required | `testing` | `check_tests_passed` (`_parse_tests_verdict`) | `result:` / `verdict:` / `status:` (`PASS` \| `FAIL` \| `BLOCKED`; три равноранговых, ORCH-047) |
| `14-deploy-log.md` | deployer / deploy-finalizer | required | `deploy` | `check_deploy_status` (`_parse_deploy_status`) | `deploy_status:` (`SUCCESS` \| `FAILED`) |
| `15-staging-log.md` | deployer | required (self-hosting) | `deploy-staging` | `check_staging_status` (self-hosting; иначе N/A — ORCH-35) | `staging_status:` (`SUCCESS` \| `FAILED`) |
| `16-post-deploy-log.md` | post-deploy-monitor | when-applicable | пост-`done` наблюдение (ORCH-021; не ребро `STAGE_TRANSITIONS`) | информационный (гейтом не парсится) | `post_deploy_status:` (`HEALTHY` \| `DEGRADED`) |
| `17-security-report.md` | security-гейт (детерминированный, ORCH-022) | when-applicable | под-гейт ребра `deploy-staging→deploy` | `check_security_gate` (врезка в `advance_stage`) | `security_status:` (`PASS` \| `FAIL`) |
| `18-coverage-report.md` | coverage-гейт (детерминированный, ORCH-027) | when-applicable | под-гейт ребра `deploy-staging→deploy` (ПОСЛЕ merge-gate, ДО image-freshness) | `check_coverage_gate` (врезка в `advance_stage`) | `coverage_status:` (`PASS` \| `FAIL`) |
### Примечания манифеста (нормативные)
- **Под-гейты ребра `deploy-staging→deploy`** (`check_security_gate``check_branch_mergeable`
`check_staging_image_fresh`) исполняются как врезки в `advance_stage`, а НЕ строки
`STAGE_TRANSITIONS`. Не путать с exit-гейтами рёбер.
- **`09-review.md`** — legacy fallback от старой нумерации; **канон — `12-review.md`**. В основную
таблицу как канон не вносится; reviewer пишет `12-review.md`.
- **Категория `when-applicable`** = документ пишется при наличии соответствующего предмета
(инфра / данные / security / post-deploy). Его отсутствие — не нарушение приёмки.
- **`05-…` / `09-…` / `11-…`** — зарезервированные/legacy номера, в текущем каноне не используются.
---
## 3. Machine-verdict доки vs информационные (честный механизм проверки)
**Machine-verdict доки** — гейт читает ТОЛЬКО YAML-frontmatter (никогда прозу), маппит ключ в
вердикт. Имя ключа чувствительно к регистру; значение парсер приводит к верхнему регистру.
| Документ | Machine-key | Парсер (`src/qg/checks.py`) | Эффект вердикта |
|----------|-------------|-----------------------------|-----------------|
| `12-review.md` | `verdict:` | `check_reviewer_verdict` | `APPROVED` → дальше; `REQUEST_CHANGES` → откат на `development` |
| `13-test-report.md` | `result:` / `verdict:` / `status:` | `_parse_tests_verdict` | `PASS` → дальше; `FAIL`/`BLOCKED` → откат |
| `14-deploy-log.md` | `deploy_status:` | `_parse_deploy_status` | `SUCCESS``done`; `FAILED` → откат (БАГ-8) |
| `15-staging-log.md` | `staging_status:` | `_parse_staging_status` | `SUCCESS` → дальше; `FAILED` → откат (self-hosting; иначе N/A) |
| `17-security-report.md` | `security_status:` | `check_security_gate` | `PASS` → дальше; `FAIL` → откат |
| `18-coverage-report.md` | `coverage_status:` | `check_coverage_gate` | `PASS` → дальше; `FAIL` → откат на `development` |
**Информационные доки** — гейтом НЕ парсятся (структура ничего не блокирует):
`00-business-request.md` (вход), `08-data-requirements.md`, `10-tech-risks.md`,
`16-post-deploy-log.md` (несёт `post_deploy_status:`, но это телеметрия петли уроков ORCH-8 /
наблюдаемость, не гейт).
---
## 4. Конвенция ADR-naming
### Per-work-item ADR (основное)
- **Путь:** `docs/work-items/<plane-id>/06-adr/`
- **Имя файла:** `ADR-NNN-<kebab-slug>.md`
- `NNN` — 3-значный, начинается с `001`; инкремент при нескольких ADR в одной задаче
(`ADR-001-…`, `ADR-002-…`).
- `<kebab-slug>` — kebab-case (нижний регистр, слова через дефис), отражает суть решения.
- **Стадия:** пишет **architect** на стадии `architecture`; гейтится `check_architecture_done`
(наличие каталога `06-adr/` ≥ 1 файла).
### Сквозной (cross-cutting) ADR
Решения, затрагивающие несколько компонентов/ролей или поведение всего конвейера, **дублируются**
в глобальный реестр:
- **Путь:** `docs/architecture/adr/`
- **Имя файла:** `adr-NNNN-<kebab-slug>.md` (4-значная сквозная нумерация, последовательная по
всему репозиторию; на момент ORCH-075 реестр доходит до `adr-0019`).
### Примеры из репозитория (реальные, проверенные)
- `docs/work-items/ORCH-088/06-adr/ADR-001-serial-gate.md`
- `docs/work-items/ORCH-089/06-adr/ADR-001-auto-label-gates.md`
- `docs/work-items/ORCH-071/06-adr/ADR-001-merge-verify-gate.md`
- Сквозные: `docs/architecture/adr/adr-0017-serial-gate.md`,
`docs/architecture/adr/adr-0018-auto-label-gates.md`.
---
## 5. Как пользоваться шаблонами
1. Скопируй нужный скелет из [`docs/_templates/`](../_templates/) в
`docs/work-items/<plane-id>/` под канонным именем (для ADR — `06-adr/ADR-001-<slug>.md`).
2. Заполни секции; **не удаляй** machine-key frontmatter у machine-verdict доков и **не меняй имя
ключа** (регистр чувствителен — иначе гейт упадёт ложно).
3. Сверяйся с манифестом (§2§3): какой агент, на какой стадии, какой гейт читает документ.
> Стандарт **описательный** (слой 1). **Машинный слой реализован в ORCH-52c (ORCH-076):** единый
> frontmatter-контракт (reader + writer + валидатор) в [`src/frontmatter.py`](../../src/frontmatter.py)
> и формальная спека handoff [`HANDOFF_PROTOCOL.md`](HANDOFF_PROTOCOL.md) («стадия → обязательный
> выход» + обязательная frontmatter-схема `REQUIRED_FIELDS`). Пять вердикт-парсеров
> (`check_reviewer_verdict`, `_parse_tests_verdict`, `_parse_deploy_status`, `_parse_staging_status`,
> `parse_security_status`) читают вердикт через ОДНУ точку парсинга (`parse_frontmatter`); семантика
> вердиктов 1:1. Валидатор обязательной схемы по умолчанию **warning-only** (kill-switch
> `frontmatter_validation_strict`, дефолт `False`) — соблюдение схемы пока на ответственности агента
> и reviewer'а, enforcement придёт с ORCH-52d.
---
## 6. Спека handoff (машинный контракт, ORCH-52c)
Вертикальный срез «стадия → обязательные документы + frontmatter-ключи на выходе» и обязательная
frontmatter-схема вынесены в отдельную нормативную спеку [`HANDOFF_PROTOCOL.md`](HANDOFF_PROTOCOL.md)
(набор документов/ключей/гейтов согласован 1:1 с §2§3 этого манифеста). Машинный источник
обязательной схемы — `frontmatter.REQUIRED_FIELDS`.

View File

@@ -0,0 +1,147 @@
# TRACEABILITY — стандарт маркеров-трассировки `ORCH-NNN` (golden source трассировки)
> **Назначение.** Единый нормативный контракт: как нетривиальная строка/блок/инвариант в коде
> привязывается к work item, который его ввёл, и к его архитектурному решению (ADR). Это **слой 4
> (трассировка)** эпика **ORCH-52** — рядом с `PIPELINE_DOCS.md` (слой 1, структура документов) и
> `HANDOFF_PROTOCOL.md` (слой 2, машинный frontmatter-контракт).
>
> **Статус истины.** Документ **кодифицирует сложившуюся практику**, а не вводит новый синтаксис.
> Источник истины о *поведении* остаётся код (`src/stages.py`, `src/qg/checks.py`,
> `src/stage_engine.py`); этот стандарт — описательно-нормативный, **не машинный гейт конвейера**.
> Соблюдение держится на дисциплине агентов + оси ревью (`reviewer.md`), а не на CI-lint.
Введён задачей **ORCH-078** (ORCH-52e). Сквозной ADR:
[`docs/architecture/adr/adr-0022-traceability-marker-standard.md`](../architecture/adr/adr-0022-traceability-marker-standard.md);
детально — `docs/work-items/ORCH-078/06-adr/ADR-001-traceability-marker-standard.md`. Продолжает
цепочку стандартов эпика 52: adr-0019 (52b), adr-0020 (52c), adr-0021 (52d).
---
## 1. Назначение и определение
**Маркер `ORCH-NNN`** (а для проекта enduro-trails — `ET-NNN`) в коде = обязательный стандарт
трассировки: он привязывает нетривиальную строку / блок / инвариант к work item, который его ввёл,
и к его ADR. Это даёт читающему агенту прямой путь «строка кода → решение, которое её породило»,
вместо `git blame`-археологии.
**Факт (сверено на 2026-06-09):** в `src/` де-факто живёт **51 уникальный** маркер `ORCH-NNN`
(`grep -rhoE 'ORCH-[0-9]+' src/ | sort -u | wc -l``51`) — сложившаяся практика. Этот стандарт её
формализует. **Массовый ретро-фит существующих 51 маркера вне объёма** — стандарт нормативен «на
будущее»: его правила применяются к **новому и правимому** коду.
---
## 2. Формат маркера
Маркер — это **inline-комментарий** (или фрагмент docstring модуля/функции), содержащий идентификатор
work item `ORCH-NNN`. Рекомендуется рядом указывать ссылку на конкретное решение в ADR, чтобы трасса
вела не просто к задаче, а к пункту решения:
```python
# Ordering term — ``t2.id < jobs.task_id`` (FIFO, ORCH-088, ADR-001 D1 / FR-2): a task
# does not enter `analysis` while an earlier unfinished task exists in the same repo.
```
Нового синтаксиса не вводится — кодифицируется уже сложившийся стиль (`ORCH-NNN[, ADR-001 D1]`).
---
## 3. Где ставится маркер
Маркер ставится рядом с **нетривиальным инвариантом**, понимание которого требует контекста решения:
- выбор fail-open / fail-closed поведения;
- точное условие сериализации / упорядочивания (FIFO, lease, барьер);
- идемпотентность / защита от повторной обработки;
- обходимая «дыра» конвейера, которую блок закрывает;
- любое условие, чьё «почему именно так» зафиксировано в ADR.
Маркер **НЕ ставится** на тривиальном/самоочевидном коде (геттеры, простые присваивания, очевидные
проверки) — это только зашумляет.
**Правило для нового кода:** вводишь значимый инвариант → ставь маркер своей задачи (`ORCH-NNN`)
рядом, по возможности со ссылкой на пункт ADR.
---
## 4. Как читать историю (с реальным проверяемым примером)
Пошагово, от строки кода к решению:
1. Видишь в коде маркер `ORCH-NNN` у строки/блока, который собираешься менять.
2. Открываешь его архитектурное решение: `docs/work-items/ORCH-NNN/06-adr/`.
3. Читаешь зафиксированный инвариант ПЕРЕД правкой; не ломаешь его (см. §7).
**Проверяемый пример из реального кода (`main`):**
> `src/serial_gate.py` несёт условие сериализации `t2.id < jobs.task_id` с маркером **ORCH-088**
> и отсылкой `ADR-001 D1 / FR-2` (FIFO-уточнение serial-gate). Чтобы понять, почему задача не входит
> в `analysis`, пока в репо есть более ранняя незавершённая задача, читаешь:
> `docs/work-items/ORCH-088/06-adr/ADR-001-serial-gate.md`.
Пример ссылается на **реально существующие** в `main` файл и ADR — иначе стандарт опровергал бы сам
себя (нерабочая трассировка).
---
## 5. Fallback-доступ к чужому ADR
Папки `docs/work-items/ORCH-NNN/` может **не быть в текущей ветке** (она срезана от `main` без неё —
типично для ветки другой задачи). Штатный способ прочитать чужой ADR — взять его из `origin/main`:
```bash
git fetch origin # при необходимости заранее
git ls-tree origin/main:docs/work-items/ORCH-NNN/06-adr/ # листинг доступных ADR
git show origin/main:docs/work-items/ORCH-NNN/06-adr/ADR-001-<slug>.md # прочитать конкретный
```
Это не блокер: отсутствие папки в ветке ≠ отсутствие решения — оно всегда есть в `main`.
---
## 6. Анти-археология: 3+ маркеров → сводный сквозной ADR
Если функция/блок несёт **3+** маркеров `ORCH-NNN` (эволюционировал через много задач), раскопки по
каждому work item нечитаемы. Вместо перечисления всех задач ставится **одна сводная ссылка на
сквозной ADR** (`docs/architecture/adr/adr-NNNN-*`), агрегирующий эволюцию.
Числовой порог `3` — граница, за которой inline-перечисление перестаёт быть читаемым (один-два
маркера ещё информативны, три и больше — уже археология).
**Пример из кода:** `src/merge_gate.py` несёт маркеры ORCH-043/065/071/073 (и ещё несколько) →
читать сводные сквозные `adr-0006` (merge-gate), `adr-0013` (merge-verify-gate),
`adr-0014` (sha-source-of-truth), `adr-0016` (ensure-open-PR) в `docs/architecture/adr/`, а не 8
отдельных work item.
Это конвенция для **нового/правимого** блока; массовая переразметка существующих файлов вне объёма.
---
## 7. Правило чтения (каноничная формулировка — единый источник)
Это **единственное** место, где живёт каноничный текст правила. Промпты агентов
(`developer.md`/`architect.md`/`reviewer.md`) **ссылаются** на него, а не копируют — чтобы не было
дрейфа формулировок между файлами.
> **Правишь код с маркером `ORCH-NNN` → прочитай его `docs/work-items/ORCH-NNN/06-adr/` ПЕРЕД
> изменением; не сломай зафиксированный инвариант. Не можешь сохранить инвариант — эскалируй /
> верни задачу в анализ, не правь вслепую.** Папки нет в ветке → читай из `origin/main` (§5). Блок
> несёт 3+ маркеров → опирайся на сводный сквозной ADR (§6).
Кто и как применяет правило:
- **developer / architect** — обязаны выполнить чтение ПЕРЕД правкой маркированного кода.
- **architect** — при введении/правке блока с 3+ маркерами оформляет/обновляет сводный сквозной ADR.
- **reviewer** — проверяет соблюдение: правка маркированного (`ORCH-NNN`) кода без сверки с его ADR
или со сломом инварианта → finding (рекомендуемая severity **P1**; слом критического инварианта
конвейера — на усмотрение reviewer вплоть до P0).
---
## Связи
- Сквозной ADR: [`adr-0022`](../architecture/adr/adr-0022-traceability-marker-standard.md).
- Стандарты-соседи: [`PIPELINE_DOCS.md`](PIPELINE_DOCS.md) (слой 1),
[`HANDOFF_PROTOCOL.md`](HANDOFF_PROTOCOL.md) (слой 2).
- Цепочка эпика 52: adr-0019 (52b) / adr-0020 (52c) / adr-0021 (52d) / adr-0022 (52e).
- Прецедент класса ошибки (слом инварианта без чтения ADR): `docs/history/LESSONS_2026-06-08_phantom-merge.md`.

View File

@@ -0,0 +1,8 @@
# Business Request: <краткий заголовок задачи>
Work Item ID: ORCH-NNN
## Description
<Что хочет заказчик/Владелец своими словами: проблема, желаемый результат, контекст.
Допускается `TBD` на входе — analyst уточняет на стадии `analysis` и формализует в 01-brd.md.>

34
docs/_templates/01-brd.md vendored Normal file
View File

@@ -0,0 +1,34 @@
# 01 — BRD (бизнес-требования): ORCH-NNN — <название>
Work Item: **ORCH-NNN** · Repo: **<repo>** · Стадия: analysis
## 1. Бизнес-контекст и проблема
<Зачем задача, какую боль/риск закрывает. Установленные факты — не изобретать.>
## 2. Объём (scope)
### В объёме
- <что делаем>
### Вне объёма
- <что явно НЕ делаем — чтобы исключить расползание>
## 3. Заинтересованные стороны
<Кто заказчик, кого затрагивает, кто принимает результат.>
## 4. Бизнес-требования (BR)
- **BR-1** — <требование, проверяемое>
- **BR-2** — …
## 5. Нефункциональные требования (NFR)
- **NFR-1** — <надёжность / совместимость / обратимость / безопасность>
- **NFR-2** — …
## 6. Допущения и ограничения
<Допущения, на которых стоит решение; внешние ограничения.>
## 7. Критерии успеха
<Резюме; детальные PASS/FAIL — в 03-acceptance-criteria.md.>
## 8. Риски
<Краткий перечень; детали — 10-tech-risks.md (заполняет архитектор).>

30
docs/_templates/02-trz.md vendored Normal file
View File

@@ -0,0 +1,30 @@
# 02 — ТЗ (TRZ): ORCH-NNN — <название>
Work Item: **ORCH-NNN** · Repo: **<repo>** · Стадия: analysis
> ТЗ описывает **конкретные изменения к реализации**, выведенные из BRD и фактического кода.
> Архитектурное обоснование/решения — задача архитектора (06-adr).
## 1. Сводка изменения
<Что меняется, в одном-двух абзацах.>
## 2. Задействованные модули / пути
| Путь | Действие |
|------|----------|
| `src/<module>.py` | изменить / создать |
## 3. Функциональные требования
### FR-1 — <название>
<Поведение, контракт, инварианты. Привязать к BR.>
## 4. Изменения API
<Новые/изменённые эндпоинты; либо «Нет.».>
## 5. Изменения схемы БД
<Таблицы/миграции/индексы; либо «Нет.».>
## 6. Требования к новым/изменённым QG checks
<Изменения `QG_CHECKS` / `check_*`; либо «Нет.».>
## 7. Совместимость / регресс
<Обратная совместимость, kill-switch, область раската, обратимость.>

View File

@@ -0,0 +1,31 @@
# 03 — Критерии приёмки (Acceptance Criteria): ORCH-NNN — <название>
Work Item: **ORCH-NNN** · Repo: **<repo>** · Стадия: analysis
Формат: каждый критерий имеет **PASS** (что должно быть истинно для приёмки) и **FAIL**
(что считается провалом). Любой машинный/ручной reviewer проверяет их буквально по файлам
репозитория.
---
## AC-1 — <краткий заголовок>
**Условие:** <проверяемое условие>
- **PASS:** <что должно быть истинно>
- **FAIL:** <что считается провалом>
---
## AC-2 — <краткий заголовок>
**Условие:** <…>
- **PASS:** <…>
- **FAIL:** <…>
---
## Сводная матрица AC ↔ FR/BR
| AC | Покрывает |
|----|-----------|
| AC-1 | BR-1 / FR-1 |
| AC-2 | BR-2 / FR-2 |

20
docs/_templates/04-test-plan.yaml vendored Normal file
View File

@@ -0,0 +1,20 @@
work_item: ORCH-NNN
title: "<краткое название тест-плана>"
framework: pytest
scope: "<что покрывается тестами; что вне покрытия>"
notes: >
<Свободные заметки: окружение, особенности, что считается регрессом.
Полный регресс tests/ должен оставаться зелёным.>
tests:
- id: TC-01
type: unit # unit | integration
description: "<что проверяет тест>"
module: tests/test_<feature>.py
expected: PASS
- id: TC-02
type: integration
description: "<…>"
module: tests/test_<feature>.py
expected: PASS

43
docs/_templates/06-adr-ADR-NNN-slug.md vendored Normal file
View File

@@ -0,0 +1,43 @@
# ADR-NNN: <Заголовок решения>
> **Шаблон ADR.** Скопируй в `docs/work-items/<plane-id>/06-adr/ADR-NNN-<kebab-slug>.md`.
> `NNN` начинается с `001`, инкремент при нескольких ADR в задаче. `<kebab-slug>` — нижний
> регистр, слова через дефис. Сквозное (cross-cutting) решение дополнительно дублируй в
> `docs/architecture/adr/adr-NNNN-<kebab-slug>.md` (4-значная глобальная нумерация).
> См. `docs/_standards/PIPELINE_DOCS.md` §4.
Work Item: **ORCH-NNN** — <короткое описание>
Стадия: **architecture**
Сквозная регистрация: **`docs/architecture/adr/adr-NNNN-<slug>.md`** (если решение
кросс-каттинговое; иначе — «N/A, локальное решение задачи»).
## Статус
Proposed <!-- Proposed | Accepted | Superseded by ADR-… -->
## Контекст
<Какую проблему решаем; факты, сверенные с кодом (`src/…`); почему «как есть» не годится.>
## Решение
### Сводка
<Суть выбранного решения в одном-двух абзацах.>
### D1 — <название аспекта решения>
<Конкретное решение по аспекту, инварианты, привязка к FR/AC.>
### D2 — <название аспекта решения>
<…>
## Альтернативы
- **<альтернатива>** — отвергнуто: <почему>.
## Последствия
- **+** <положительный эффект>
- **** <издержка / приятый компромисс + митигейшн>
- **Откат:** <как полностью откатить изменение>
## Ссылки
- BRD: `docs/work-items/ORCH-NNN/01-brd.md`
- TRZ: `docs/work-items/ORCH-NNN/02-trz.md`
- Acceptance: `docs/work-items/ORCH-NNN/03-acceptance-criteria.md`
- Сверено по коду: `src/…`

View File

@@ -0,0 +1,19 @@
# 07 — Инфра-требования: ORCH-NNN — <название>
Work Item: **ORCH-NNN** · Repo: **<repo>** · Стадия: architecture
> When-applicable. Если инфраструктура не затрагивается — оставить явные `N/A` по пунктам
> (файл создаётся для аудитопригодности, а не из-за изменения топологии).
## I-1. Топология / окружения
<Контейнеры, порты, сеть, тома, хост; либо `N/A`.>
## I-2. Переменные окружения / секреты
<Новые env-переменные, изменения `.env` / `.env.example`, секреты; либо `N/A`.>
## I-3. Деплой / рестарт
<Требуется ли рестарт прод-контейнера; self-hosting инвариант (не ронять прод вне staging);
либо `N/A`.>
## I-4. CI/CD
<Изменения `.gitea/workflows/`, новые тестовые шаги; либо «без изменений».>

15
docs/_templates/08-data-requirements.md vendored Normal file
View File

@@ -0,0 +1,15 @@
# 08 — Требования к данным: ORCH-NNN — <название>
Work Item: **ORCH-NNN** · Repo: **<repo>** · Стадия: architecture
> When-applicable / информационный (гейтом не парсится). Если данные/схема не затрагиваются —
> оставить явные `N/A`.
## Изменения схемы БД
<Новые/изменённые таблицы, индексы, миграции (`init_db`); либо `N/A`.>
## Новые/изменённые сущности
<Поля, колонки, инварианты данных; либо «Нет.».>
## Совместимость данных / миграции
<Аддитивность, идемпотентность миграций, restart-safe, влияние на общую прод-БД; либо `N/A`.>

16
docs/_templates/10-tech-risks.md vendored Normal file
View File

@@ -0,0 +1,16 @@
# 10 — Технические риски: ORCH-NNN — <название>
Work Item: **ORCH-NNN** · Repo: **<repo>** · Стадия: architecture
> Информационный (гейтом не парсится). Перечисляет риски реализации и их митигейшн.
## Реестр рисков
| ID | Риск | Вер. | Влия. | Митигейшн |
|----|------|------|-------|-----------|
| TR-1 | <описание риска> | Низ./Сред./Выс. | Низ./Сред./Выс. | <как снижаем> |
| TR-2 | <…> | | | |
## Сводный вывод
<Доминирующий класс рисков; нужна ли эскалация `arch:major-change` / возврат в анализ;
итоговая оценка остаточного риска для прод-конвейера (self-hosting).>

31
docs/_templates/12-review.md vendored Normal file
View File

@@ -0,0 +1,31 @@
---
type: review
work_item_id: ORCH-NNN
verdict: APPROVED # APPROVED | REQUEST_CHANGES (machine-key — читает check_reviewer_verdict)
version: 1
---
# Review ORCH-NNN
> Машинный вердикт читается ТОЛЬКО из `verdict:` во frontmatter (никогда из прозы).
> `APPROVED` → дальше по конвейеру; `REQUEST_CHANGES` → откат на `development`.
## Summary
<Краткая оценка: реализовано ли по ТЗ/ADR, покрытие тестами, обновлена ли документация.>
## Оси проверки
<Корректность, соответствие ADR/инвариантам, тесты, документация, совместимость/регресс.>
## Findings
### P0 — Blocker
- (нет)
### P1 — Must fix
- (нет)
### P2 — Should fix
- (нет)
## Документация
<Обновлена ли документация (README/CLAUDE/CHANGELOG/архитектура) в том же PR. Нет → REQUEST_CHANGES.>

33
docs/_templates/13-test-report.md vendored Normal file
View File

@@ -0,0 +1,33 @@
---
type: test-report
work_item_id: ORCH-NNN
result: PASS # PASS | FAIL | BLOCKED (machine-key — читает _parse_tests_verdict)
---
# Test Report — ORCH-NNN
> Машинный вердикт читается ТОЛЬКО из frontmatter. Канонический ключ — `result:`; равнорангово
> допускаются `verdict:` / `status:` (ORCH-047). Любой негативный токен (`FAIL`/`BLOCKED`) —
> авторитетен.
## Окружение
- Python: <версия>
- pytest: <версия>
- Дата: YYYY-MM-DD
- Worktree: `feature/ORCH-NNN-<slug>`
## Результаты
### Полный регресс
<`pytest tests/ -q` — итог (N passed); прод-контейнер не трогается.>
### Профильные сюиты
<Целевые тесты задачи.>
### Сопоставление с тест-планом
| TC ID | Описание | Тест-функция | Результат |
|-------|----------|--------------|-----------|
| TC-01 | <…> | test_… | PASS |
### Сопоставление с критериями приёмки
<AC-1…AC-N — покрыт каким тестом / результат.>

14
docs/_templates/14-deploy-log.md vendored Normal file
View File

@@ -0,0 +1,14 @@
---
deploy_status: SUCCESS # SUCCESS | FAILED (machine-key — читает _parse_deploy_status)
work_item: ORCH-NNN
hook_exit_code: 0
deployed_by: deploy-finalizer
---
# Deploy log — ORCH-NNN
> Машинный вердикт читается ТОЛЬКО из `deploy_status:` во frontmatter.
> `SUCCESS` → `done`; `FAILED` → откат на `development` (БАГ-8).
<Краткое описание деплоя: что выкачено, exit-code хука, кто/что зафиксировало вердикт
(детерминированный finalizer Фаза C, не LLM, для self-hosting).>

20
docs/_templates/15-staging-log.md vendored Normal file
View File

@@ -0,0 +1,20 @@
---
staging_status: SUCCESS # SUCCESS | FAILED (machine-key — читает _parse_staging_status)
timestamp: YYYY-MM-DDTHH:MM:SSZ
base_url: http://localhost:8501
---
# Staging Gate Log
> Машинный вердикт читается ТОЛЬКО из `staging_status:` во frontmatter. Реален для self-hosting
> (`orchestrator`); для прочих репо гейт — N/A (ORCH-35). `SUCCESS` → дальше; `FAILED` → откат.
Staging test suite — итог (например: «All REAL pipeline checks passed»). Запуск канонически
внутри контейнера `orchestrator-staging` (8501).
## Results
- **Block A (SMOKE)**: <…>
- **Block B (ACCESS)**: <…>
- **Block C (E2E)**: <…>
REAL failed: <none | перечень>.

21
docs/_templates/16-post-deploy-log.md vendored Normal file
View File

@@ -0,0 +1,21 @@
---
post_deploy_status: HEALTHY # HEALTHY | DEGRADED (информационный, гейтом НЕ парсится — телеметрия ORCH-021)
action_taken: NONE # NONE | ALERT_ONLY | ROLLBACK_OK | ROLLBACK_FAILED
work_item: ORCH-NNN
window_s: 900
checks_total: 0
checks_failed: 0
---
# Post-deploy log — ORCH-NNN
> Пост-`done` наблюдение прода (ORCH-021). НЕ ребро `STAGE_TRANSITIONS`, гейтом не парсится —
> frontmatter машиночитаем для петли уроков ORCH-8 / наблюдаемости.
Окно наблюдения: <window_s>s; опросов всего: <checks_total>, с провалом: <checks_failed>.
## Серия наблюдений
<Краткая серия сигналов health / доли 5xx; классификация HEALTHY/DEGRADED.>
## Решение
<Реакция: для self-hosting всегда `ALERT_ONLY` (ручной approve, тик не откатывает прод).>

26
docs/_templates/17-security-report.md vendored Normal file
View File

@@ -0,0 +1,26 @@
---
security_status: PASS # PASS | FAIL (machine-key — читает check_security_gate)
work_item: ORCH-NNN
secrets_found: 0
deps_blocking: 0
deps_warning: 0
deps_audit_degraded: false
---
# Security Report — ORCH-NNN
> Детерминированный security-гейт (ORCH-022) — под-гейт ребра `deploy-staging→deploy` (врезка в
> `advance_stage`, не строка `STAGE_TRANSITIONS`). Машинный вердикт читается ТОЛЬКО из
> `security_status:`. `PASS` → дальше; `FAIL` → откат.
## Verdict
<clean / blocking: N secrets, M blocking CVE(s).>
## Secrets
<secret-scanning (gitleaks, offline): None | перечень.>
## Dependencies (blocking)
<dependency audit (pip-audit): None | перечень блокирующих CVE.>
## Dependencies (warning)
<Не блокирующие предупреждения зависимостей.>

29
docs/_templates/18-coverage-report.md vendored Normal file
View File

@@ -0,0 +1,29 @@
---
coverage_status: PASS # PASS | FAIL (machine-key — читает check_coverage_gate)
work_item: ORCH-NNN
measured_coverage: 0.0 # измеренное line coverage src/ (%, float)
baseline: 0.0 # базовая линия main на момент измерения (%, или пусто при bootstrap)
floor: 0.0 # абсолютный порог coverage_min_percent (%)
policy: both # absolute | baseline | both
epsilon: 0.5 # допуск на шум измерения (%)
delta: 0.0 # measured max(baseline, floor) (%, знаковая дельта)
---
# Coverage Report — ORCH-NNN
> Детерминированный гейт покрытия (ORCH-027) — под-гейт ребра `deploy-staging→deploy` (врезка в
> `advance_stage`, ПОСЛЕ merge-gate, ДО image-freshness; не строка `STAGE_TRANSITIONS`). Машинный
> вердикт читается ТОЛЬКО из `coverage_status:`. `PASS` → дальше; `FAIL` → откат на `development`.
> Измерение — `pytest --cov=src --cov-report=json` в изолированном worktree. Source of truth
> измеренного значения для ratchet базовой линии (`_handle_merge_verify`, ребро `deploy→done`).
## Verdict
<PASS / FAIL: measured X% vs floor F% / baseline B% (policy=…, epsilon=…), delta=±D%.>
## Measurement
<Инструмент (pytest-cov/coverage.py), команда, line coverage src/ = X%; либо fail-open WARNING
при ошибке инструмента (coverage_tool_fail_closed=False).>
## Policy
<Режим (absolute|baseline|both), порог floor, базовая линия main, epsilon, какое условие
нарушено при FAIL.>

1266
docs/architecture/README.md Normal file

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,61 @@
# Architecture Decision Records
Индекс сквозных (cross-cutting) ADR проекта orchestrator.
Per-work-item решения живут в `docs/work-items/<id>/06-adr/ADR-NNN-slug.md`.
| # | Решение | Статус | Дата | Источник |
|---|---------|--------|------|----------|
| adr-0001 | Реестр проектов (multi-repo) | accepted | 2026-06-02 | ORCH-6 |
| adr-0002 | Очередь задач вместо in-process потоков | accepted | 2026-06-03 | ORCH-1 |
| adr-0003 | Условный staging-гейт перед прод-деплоем | accepted | 2026-06-05 | ORCH-35 |
| adr-0004 | Поллинг с ретраем в check_ci_green (фикс CI-race) | accepted | 2026-06-05 | ORCH-045 |
| adr-0005 | Контейнеры бегут под uid:gid хоста (1000:1000) | accepted | 2026-06-06 | ORCH-040 |
| adr-0006 | Merge-gate (догон main + re-test + сериализация слияний) | proposed | 2026-06-06 | ORCH-043 |
| adr-0007 | Reconciler застрявших стадий (sweeper потерянных webhook) | accepted | 2026-06-06 | ORCH-053 |
| adr-0007 | Исполняемый самодеплой стадии `deploy` (файл adr-0007-executable-self-deploy) | accepted | 2026-06-06 | ORCH-036 |
| adr-0008 | Провенанс staging-образа перед BUILD-ONCE retag | accepted | 2026-06-06 | ORCH-058 |
| adr-0009 | Толерантность staging-вердикта к инфраструктурным FAIL | accepted | 2026-06-07 | ORCH-061 |
| adr-0010 | Post-deploy мониторинг прода + реакция на деградацию | proposed | 2026-06-07 | ORCH-021 |
| adr-0011 | Job-reaper + проактивный реклейм merge-lease | accepted | 2026-06-07 | ORCH-065 |
| adr-0012 | Security-гейт (secrets/deps) | accepted | 2026-06-08 | ORCH-022 |
| adr-0013 | Merge-в-main + пост-деплой верификация как условие `done` | accepted | 2026-06-08 | ORCH-071 |
| adr-0014 | SHA-в-main — единственный критерий merge-verify + регресс-гард | accepted | 2026-06-08 | ORCH-073 |
| adr-0015 | Зависимости задач (B ждёт A) + сериализация merge внутри репо | accepted | 2026-06-08 | ORCH-026 |
| adr-0016 | ensure_open_pr — гарантированный код-PR перед merge-verify | accepted | 2026-06-09 | ORCH-082 |
| adr-0017 | Per-repo serial gate (пакетный автономный режим, serial e2e) | proposed | 2026-06-09 | ORCH-088 |
| adr-0018 | Авто-режим по лейблам (autoApprove + autoDeploy) | accepted | 2026-06-09 | ORCH-089 |
| adr-0019 | Стандарт документов конвейера (PIPELINE_DOCS, слой 1) | accepted | 2026-06-09 | ORCH-075 |
| adr-0020 | Единый frontmatter-контракт + спека handoff (reader/writer/валидатор) | accepted | 2026-06-09 | ORCH-076 |
| adr-0021 | Канон Anthropic для агент-промптов + эмиссия frontmatter-схемы 52c | proposed | 2026-06-09 | ORCH-077 |
| adr-0022 | Стандарт трассировочных маркеров `ORCH-NNN` | accepted | 2026-06-09 | ORCH-078 |
| adr-0023 | Обзорная ось reviewer + закрытие эпика 52 | accepted | 2026-06-09 | ORCH-079 |
| adr-0024 | Disk-watchdog — heartbeat-сигнал заполнения хост-ФС | proposed | 2026-06-09 | ORCH-063 |
| adr-0025 | Build-cache-pruner — авто-prune docker build cache на хосте | proposed | 2026-06-09 | ORCH-062 |
| adr-0026 | STOP / отмена задачи — системный терминал `cancelled` | proposed | 2026-06-09 | ORCH-090 |
| adr-0027 | Merge-актор — ретрай транзиентных ошибок Gitea + гард «ветка уже в `main`» | proposed | 2026-06-09 | ORCH-093 |
| adr-0028 | Terminal-window-aware гард deploy-фазовых статусов Plane | proposed | 2026-06-09 | ORCH-094 |
| adr-0029 | Гейт покрытия тестами — edge sub-gate + ratchet-базовая линия | proposed | 2026-06-10 | ORCH-027 |
| adr-0030 | Лёгкий read-only `/metrics` — сырьё о самом орке для sidecar (F1b) | proposed | 2026-06-10 | ORCH-099 |
| adr-0031 | Нормализация legacy root-owned файлов при миграции uid — детект-leaf + actionable worktree-ошибка | proposed | 2026-06-10 | ORCH-057 |
| adr-0032 | Багфикс-трек — укороченный маршрут конвейера для багов | proposed | 2026-06-10 | ORCH-019 |
| adr-0033 | Sidecar-watchdog F1b — мозг мониторинга в отдельном контейнере | proposed | 2026-06-10 | ORCH-100 |
| adr-0034 | Машинный журнал уроков — таблица `lessons` + observer-leaf | proposed | 2026-06-10 | ORCH-098 |
| adr-0035 | Turnkey-онбординг проектов — kit + операторский CLI + runbook | proposed | 2026-06-10 | ORCH-009 |
| adr-0036 | Фундамент тиража платформы — параметризация хоста, секреты, smoke (10-common) | proposed | 2026-06-10 | ORCH-101 |
| adr-0037 | Канон Lite-тиража — `docs/deployment/LITE_SETUP.md` + `.env.watchdog.example` | proposed | 2026-06-10 | ORCH-102 |
> ⚠️ Историческая коллизия: номер `0007` занят двумя файлами —
> `adr-0007-reconciler.md` (ORCH-053) и `adr-0007-executable-self-deploy.md`
> (ORCH-036). Оба accepted; для новых сквозных ADR использовать следующий
> свободный номер (текущий максимум — `0037`).
> adr-0014 **amends** adr-0013 (меняет критерий merge-verify на «SHA-в-main»).
> adr-0016 **amends** adr-0013/0014 (гарантирует открытый код-PR перед merge_pr, ORCH-082).
> adr-0020 реализует машинный слой к adr-0019 (ORCH-52b→52c).
> adr-0021 реализует слой промптов к adr-0019/0020 (ORCH-52d — замыкает эпик 52).
> adr-0025 **комплементарен** adr-0024 (watchdog сигналит о росте диска — pruner убирает
> доминирующего «пожирателя», docker build cache).
## Формат
**Контекст → Решение → Альтернативы → Последствия → Связи.** Статус: proposed / accepted / superseded.
Принятый ADR не меняется — новое решение заводится отдельным файлом со ссылкой `supersedes adr-XXXX`.
Новые ADR добавляет архитектор при принятии решения (см. `CLAUDE.md` → Конвенции).

View File

@@ -0,0 +1,23 @@
# adr-0001: Реестр проектов (multi-repo)
- **Статус:** accepted
- **Дата:** 2026-06-02
- **Задача:** ORCH-6
## Контекст
Инцидент 2026-06-02: Plane-вебхук слушал весь воркспейс и хардкодил `repo = settings.default_repo` (enduro-trails). Задачи ЛЮБОГО проекта сливались в один репо с одним префиксом (ET). Нужна изоляция по проектам.
## Решение
Введён реестр `src/projects.py`: `ProjectConfig` (frozen dataclass) связывает `plane_project_id``repo` + `work_item_prefix` + `name`. Источник правды — env `ORCH_PROJECTS_JSON`; при пустом/невалидном — встроенный дефолт (`enduro-trails`/ET, `orchestrator`/ORCH). Позволяет: фильтровать вебхуки по проекту (неизвестный → ignore), резолвить gitea-репо + префикс, роутить Plane-синк в свой проект задачи.
## Альтернативы
- Один репо на всё — отклонён (источник инцидента).
- Хардкод маппинга в коде — отклонён в пользу env-конфигурируемого реестра с безопасным дефолтом.
## Последствия
- Изоляция проектов на уровне вебхуков и роутинга.
- Парсер устойчив: битый элемент скипается, пустой результат → дефолт.
- Основа для `is_self_hosting_repo` (adr-0003).
## Связи
adr-0003 (условный гейт опирается на repo из реестра).

View File

@@ -0,0 +1,23 @@
# adr-0002: Очередь задач вместо in-process потоков
- **Статус:** accepted
- **Дата:** 2026-06-03
- **Задача:** ORCH-1 (F-2b)
## Контекст
Ранняя версия запускала стадии конвейера в in-process daemon-потоках. Проблемы: не переживало рестарт (задачи терялись), нет контроля параллелизма, нет ретраев, нет наблюдаемости.
## Решение
Введена персистентная очередь задач (`src/queue_worker.py` + таблица `jobs` в SQLite): atomic claim задачи воркером, `max_concurrency`, ретраи при сбое, restart-safe (running-задачи реквестятся при старте), эндпоинт `GET /queue`.
## Альтернативы
- In-process потоки — отклонены (не restart-safe).
- Внешний брокер (Redis/RabbitMQ) — избыточно для текущего масштаба; SQLite-очередь проще и без новых зависимостей.
## Последствия
- Конвейер переживает рестарт контейнера.
- Контроль параллелизма и наблюдаемость через `/queue`.
- ⚠️ Очередь общая на все проекты прод-инстанса — фактор группового риска при self-hosting (см. `docs/operations/INFRA.md`).
## Связи
adr-0001 (реестр проектов), INFRA.md (общая очередь при self-hosting).

View File

@@ -0,0 +1,27 @@
# adr-0003: Условный staging-гейт перед прод-деплоем
- **Статус:** accepted
- **Дата:** 2026-06-05
- **Задача:** ORCH-35
## Контекст
Оркестратор дорабатывает сам себя (self-hosting). Раньше стадия `deploy` имела «бумажный» вердикт: deployer-агент писал `deploy_status: SUCCESS`, но реального прогона на изолированной среде не было. Нужен предохранитель: прод-деплой орка не должен происходить, пока изменения не проверены на живой staging-среде. При этом другие проекты (enduro-trails) staging-инфры не имеют.
## Решение
Добавлена промежуточная стадия `deploy-staging` между `testing` и `deploy`: `testing → deploy-staging → deploy → done`.
- deployer гоняет `scripts/staging_check.py --base-url http://localhost:8501` и пишет `staging_status: SUCCESS|FAILED` в `15-staging-log.md`.
- Quality Gate `check_staging_status` парсит вердикт (только YAML-frontmatter).
- **Гейт условный:** `is_self_hosting_repo(repo)` → реальная проверка только для `orchestrator`; для остальных проектов гейт = no-op `(True, "Staging gate N/A")`.
- FAILED → откат на `development`.
## Альтернативы
- Глобальный гейт для всех проектов — отклонён: у enduro нет staging-инстанса, задачи застревали бы на откате.
- Деплой реально дёргает хост-хук прямо здесь — отложен в ORCH-36 (Вариант B).
## Последствия
- Прод-деплой орка недостижим, пока staging-гейт не зелёный.
- Другие проекты не затронуты (no-op).
- Реальный docker-деплой через хук пока НЕ выполняется (вердикт «бумажный», но подкреплён прогоном сьюта). Исполняемый деплой — ORCH-36.
## Связи
adr-0001 (реестр проектов — основа `is_self_hosting_repo`), ORCH-34 (deploy-hook + rollback), ORCH-36 (исполняемый самодеплой).

View File

@@ -0,0 +1,45 @@
# adr-0004: Поллинг с ретраем в quality-gate check_ci_green (фикс CI-race)
- **Статус:** accepted
- **Дата:** 2026-06-05
- **Задача:** ORCH-045
## Контекст
Quality-gate `check_ci_green(repo, branch)` (`src/qg/checks.py`) проверяет combined commit-status ветки через Gitea API сразу после того, как developer-агент запушил код. Реализация была **single-shot**: один `GET /repos/{owner}/{repo}/commits/{branch}/status`, чтение `data["state"]``success` → пропуск, иначе → сразу `False`.
Это создавало race condition. Gitea-CI после пуша 13 секунды держит combined state `pending`, пока не отработают чек-раннеры. Если гейт опрашивал статус в этом окне, он получал `pending` и возвращал `False` **ровно один раз** — повторного опроса не было. Combined state затем дозеленевал до `success`, но гейт уже промахнулся, и задача застревала насмерть без видимой причины.
Реальный инцидент **ORCH-017**: гейт опросил статус в 17:58:54 → `pending`; CI дозеленел в 17:58:55. Задача встала в тупик (см. `docs/history` / lessons ORCH-017).
## Решение
`check_ci_green` превращён из single-shot в **polling с ретраем**:
- `state == "success"``(True, "CI green")` немедленно.
- `state in ("failure", "error")``(False, "CI state: <state>")` немедленно — CI красный, ретрай бессмыслен (терминальное состояние).
- `state == "pending"` (или `unknown` / иное не-терминальное) → `time.sleep(interval)` и опрос снова, до `N` попыток.
- После исчерпания всех попыток при всё ещё `pending``(False, "CI still pending after <T>s")`**явный** провал с причиной, чтобы оператор видел тупик, а не молчаливый стол.
- `404``(False, "Branch ... not found or no status")` — как раньше.
- Транзиентная `httpx.HTTPError` на отдельной попытке — **не падаем сразу**: логируем и пробуем ещё в рамках лимита попыток; если все попытки — сетевая ошибка → `(False, "API error: <e>")`.
Параметры вынесены в `src/config.py` (pydantic-settings, env-prefix `ORCH_`, единый стиль с остальными настройками):
- `ci_poll_max_attempts` (env `ORCH_CI_POLL_MAX_ATTEMPTS`, дефолт **12**)
- `ci_poll_interval_s` (env `ORCH_CI_POLL_INTERVAL_S`, дефолт **10**)
Итого по умолчанию гейт ждёт `pending` до ~2 минут (12 × 10s) перед тем как явно провалиться. Каждая не-финальная попытка логируется через существующий `logger` (`check_ci_green: attempt i/N, state=..., retrying in Ns`). `timeout=10` на каждый отдельный запрос сохранён.
Сигнатура `check_ci_green(repo, branch) -> tuple[bool, str]` **не менялась** — её зовёт stage_engine и реестр гейтов `QG_CHECKS`.
## Альтернативы
- **Оставить single-shot, опрашивать гейт повторно снаружи (на уровне stage_engine/воркера).** Отклонено: размазывает логику CI-ожидания по слоям, дублирует таймауты; гейт — естественное место знания о combined-status.
- **Webhook от Gitea на завершение CI вместо поллинга.** Отложено: требует надёжной доставки/дедупликации вебхуков именно по CI-статусу и переписывания триггера стадии; поллинг — минимальный, локализованный фикс race-а здесь и сейчас.
- **Бесконечный ретрай до зелёного.** Отклонено: задача могла бы висеть вечно при реально зависшем CI; ограниченный бюджет + явный `False` с причиной даёт оператору сигнал.
## Последствия
- CI-race ORCH-017 закрыт: транзиентный `pending` переживается ретраем, гейт не промахивается.
- `check_ci_green` теперь **блокирующий** до ~`max_attempts × interval` секунд при затяжном `pending` (по умолчанию ~2 мин). Это осознанный trade-off; для красного CI и success — выход немедленный, без задержки.
- Тупик больше не молчаливый: истечение попыток → `(False, "CI still pending after <T>s")`, причина видна.
- Бюджет/интервал настраиваемы через env без правки кода.
- `check_tests_passed` / `_parse_tests_verdict` (ORCH-47) **не затронуты**.
## Связи
ORCH-017 (инцидент-первоисточник: deadlock shared-gate из-за CI-race), реестр гейтов `QG_CHECKS` (`check_ci_green`), стадия `development`. Тесты: `tests/test_qg.py::TestCheckCIGreen`.

View File

@@ -0,0 +1,42 @@
# adr-0005: Контейнеры оркестратора бегут под uid:gid хоста (1000:1000)
- **Статус:** accepted
- **Дата:** 2026-06-06
- **Задача:** ORCH-040
## Контекст
Оба контейнера (`orchestrator`, `orchestrator-staging`) запускались под `uid=0 (root)` и
монтировали хостовый `/home/slin/repos``/repos` (rw). Claude-CLI агенты исполняются
`subprocess.Popen` внутри контейнера под тем же root, поэтому все артефакты конвейера
(git worktree, коммиты в `docs/`) появлялись на хосте как `root:root`. Деплой прода под
`slin` (uid 1000) ломался на правах git до ручного `chown`. Это сквозное свойство рантайма:
касается агентов **всех** проектов, а не отдельной фичи.
## Решение
Оба сервиса в `docker-compose.yml` запускаются под `user: "1000:1000"` (uid:gid хоста `slin`).
- `group_add: ["999"]` сохраняется — доступ к docker.sock идёт через gid 999, не через root.
- target SSH-маунта приведён к `/home/slin/.ssh` (был `/root/.ssh`), синхронно с
`HOME=/home/slin`, который форсит launcher → единый HOME по осям uid/claude/ssh.
- Образ и launcher не меняются: numeric uid не требует записи в `/etc/passwd`,
`git config --system safe.directory '*'` уже есть.
Обязательные host-prerequisites (Owner, вне кода): доступ uid 1000 к
`/home/slin/.claude/.credentials.json` (блокер), ssh-ключи в новом HOME, рестарт prod
только в окно тишины. Детали и команды — work-item ADR-001 и `docs/operations/INFRA.md`.
## Альтернативы
- **drop-privileges только для subprocess агента** (`gosu`/`setuid`) — контейнер остаётся
root; новый код в горячем пути launcher, два uid в одном контейнере; отклонён.
- **chown-хук после каждой стадии** — лечит симптом, требует root внутри контейнера
(несовместимо), хрупкий пост-шаг; отклонён (fallback на крайний случай).
## Последствия
- Артефакты создаются под `slin:slin`; деплой прода не требует ручного `chown`.
- HOME консистентен (uid = claude = ssh = `/home/slin`); устранён рассинхрон SSH-маунта.
- Появляется явная привязка рантайма к uid 1000 хоста (задокументирована в INFRA.md).
- Прод-рестарт self = групповой риск (общий инстанс с enduro-trails) → строго окно тишины;
страховка — staging-гейт (adr-0003).
## Связи
adr-0003 (staging-гейт — обязательная проверка перед прод-рестартом self),
adr-0001 (`is_self_hosting_repo`), work-item `docs/work-items/ORCH-040/06-adr/ADR-001-run-agents-as-host-uid.md`.

View File

@@ -0,0 +1,53 @@
# adr-0006: Merge-gate — догон `main` + re-test + сериализация слияний
- **Статус:** proposed
- **Дата:** 2026-06-06
- **Задача:** ORCH-043
- **Детальный ADR:** `docs/work-items/ORCH-043/06-adr/ADR-001-merge-gate.md`
## Контекст
Ветка валидируется относительно того `main`, из которого создана, а не относительно `main`
на момент слияния. Параллельная задача могла влиться раньше → **семантический конфликт
слияния** (git мержит без текстового конфликта, но `main` сломан). Для self-hosting это
красный `main` инструмента, обслуживающего все проекты. Слияние в `main` делает
deployer-агент в начале стадии `deploy`; замена механизма PR-merge — вне объёма.
## Решение
Детерминированный merge-gate (`check_branch_mergeable`, без LLM) на ребре
`deploy-staging → deploy`, ДО запуска deployer'а, который мержит. `STAGE_TRANSITIONS` не
меняется (минимальный blast-radius); в `QG_CHECKS` добавлен `check_branch_mergeable`.
- **Догон:** ветка отстаёт ⇔ `origin/main` не предок HEAD → `rebase origin/main` в worktree
+ `push --force-with-lease` (ТОЛЬКО ветка задачи; `main` — никогда). Текстовый конфликт →
`rebase --abort` → откат на `development`.
- **Re-test:** `python -m pytest tests/` в worktree догнанной ветки, тайм-аут
`merge_retest_timeout_s`. Красный/тайм-аут → откат на `development`.
- **Сериализация (BR-5):** файловый **merge-lease** на репо
(`<repos_dir>/.merge-lease-<repo>.json`), живёт от гейта до фактического merge.
Acquire **неблокирующий** (anti-deadlock при `max_concurrency=1`): busy → **defer**
(re-enqueue deployer с задержкой через `available_at`), не rollback. Release — на
PR-merged вебхуке / `deploy→done` / откате / по возрасту (crash-реклейм). Restart-safe.
- **Условность (как ORCH-35):** реален для `orchestrator`; прочие репо — no-op. Флаги
`merge_gate_enabled` / `merge_gate_repos` для поэтапного раската.
## Альтернативы
- **Новая стадия `merge-gate`** (кандидат B) — «пустая» стадия без агента не имеет триггера
(`advance_stage` срабатывает только на завершении агента/вебхуке); потребовала бы chaining
в движке (не restart-safe) или синтетический job-тип. Отклонено.
- **Перенос merge в детерминированный шаг оркестратора** (кандидат C) — запрещён объёмом
(замена механизма PR-merge вне scope). Отклонено.
- **Блокирующий lock** — дедлок при одном worker-слоте. Отклонено в пользу defer.
## Последствия
- Сценарий «две зелёные ветки ломают `main`» закрыт: re-test против актуального `main` +
сериализация слияний.
- Плата: merge-gate — «скрытый» под-гейт ребра (нет в `STAGE_TRANSITIONS`); сериализация
опирается на PR-merged вебхук со страховкой реклеймом по возрасту; defer перепрогоняет
staging; длинный re-test держит worker-слот.
- Сквозное изменение конвейера → `arch:major-change`; прод-деплой ORCH-043 строго через
staging-гейт (8501).
## Связи
adr-0001 (`is_self_hosting_repo`), adr-0003 (условный staging-гейт — образец условности),
adr-0002 (очередь / `available_at` для defer), ORCH-2 (worktree-изоляция), ORCH-046
(дословный reason в `task_desc` при откате).

View File

@@ -0,0 +1,64 @@
# ADR-0007: Исполняемый самодеплой стадии `deploy` (Вариант B, ORCH-36)
## Статус
Accepted (design) — реализация в ветке `feature/ORCH-036`.
## Контекст
Стадия `deploy` была «бумажной»: deployer-агент писал `deploy_status:` в
`14-deploy-log.md`, гейт `check_deploy_status` парсил вердикт и двигал
`deploy → done`. Реального деплоя не было. ORCH-36 делает стадию исполняемой для
self-hosting (`orchestrator`), сохраняя прежний ssh-путь для остальных репо.
Три ограничения формируют дизайн (детально — `docs/work-items/ORCH-036/06-adr/ADR-001`):
1. **Self-restart**: рестарт прод-контейнера 8500 убивает in-container процесс →
рестарт делает ВНЕШНИЙ host-процесс.
2. **Status-only verdict model**: approve = смена статуса Plane на `Approved`
(комментарии не управляют конвейером).
3. **Гонка гейта**: вердикт нельзя читать до завершения асинхронного хука.
## Решение
Для self-hosting стадия `deploy` исполняется в три фазы детерминированным кодом
(без LLM в критическом пути self-restart):
- **Фаза A (вход в `deploy`)** — для self + `deploy_require_manual_approve=true`
вместо запуска прод-deployer выставляется approval-pending статус Plane + запрос
approve (Plane-коммент + Telegram). Перехват в `advance_stage` на ребре
`deploy-staging → deploy` (после `check_staging_status` и merge-gate).
- **Фаза B (Plane → Approved)** — `advance_stage(deploy, finished_agent=None)`
запускает **detached host-процесс** (ssh + setsid → `orchestrator-deploy-hook.sh`
с прод-параметрами и build-once retag) и ставит **детерминированный finalizer-job**
с задержкой; маркер `initiated` — идемпотентность. Возврат БЕЗ advance.
- **Фаза C (finalizer)** — после рестарта новый контейнер дочитывает sentinel
`result` (exit-code хука), маппит `0→SUCCESS / иначе→FAILED`, пишет
`14-deploy-log.md`, вызывает `advance_stage(deploy, finished_agent="deployer")`
→ существующие контракты: `SUCCESS → done`, `FAILED → откат БАГ-8 на development`.
### Ключевые инварианты (НЕ меняются)
`STAGE_TRANSITIONS`, реестр QG, `check_deploy_status` / `_parse_deploy_status`
(frontmatter only), откат БАГ-8, terminal-sync `deploy → done`, merge-gate (ORCH-43),
exit-code-контракт хука (0/1/2).
### Новое (сквозное)
- **Детерминированный job-kind** `deploy-finalizer` в очереди (reserved-agent, не
LLM): read-result | defer | map+write+advance. Зеркалит детерминизм merge-gate.
- **Approve-флаг** `deploy_require_manual_approve` (дефолт `true`; полный авто —
отдельная задача после набора метрик доверия, ORCH-54).
- **Build-once**: опциональный `SOURCE_IMAGE` retag в хуке (обратно совместимо).
- **Restart-safe состояние** деплоя — sentinel-файлы под
`<repos_dir>/.deploy-state-<repo>/<wi>/` (как merge-lease), БЕЗ миграции БД.
### Условность
Вся логика — только для `is_self_hosting_repo(repo)` (как ORCH-35). Прочие репо
деплоятся прежним синхронным ssh-путём агентом.
## Последствия
- `deploy_status: SUCCESS` доказан реальным health-ok; критический путь self-restart
детерминирован.
- Вводится новая под-компонента (finalizer job-handler) → изменение помечено
`arch:major-change`.
- Approve вписан в status-only модель: restart-safe, аудируемо, идемпотентно.
- На старте — обязательный ручной approve; молчаливых деплоев нет (Plane+Telegram).
## Связанные ADR
`adr-0003` (staging-gate), `adr-0006` (merge-gate), `adr-0005` (run-as-host-uid).
Детальный per-work-item: `docs/work-items/ORCH-036/06-adr/ADR-001-executable-self-deploy.md`.

View File

@@ -0,0 +1,86 @@
# adr-0007: Reconciler застрявших стадий (sweeper потерянных webhook)
- **Статус:** accepted (реализовано в `src/reconciler.py`)
- **Дата:** 2026-06-06
- **Задача:** ORCH-053
- **Детальный ADR:** `docs/work-items/ORCH-053/06-adr/ADR-001-stuck-task-reconciler.md`
## Контекст
Конвейер продвигается **только** входящими webhook (Plane status / Gitea CI/PR).
Потерянное событие (502 на ребилде, отсутствие ретраев у Plane/Gitea,
неразрезолвленный `sha→branch`) → источник истины изменился, а стадия задачи —
нет; задача застревает молча (инцидент ORCH-044). Существующий resilience
(`requeue_running_jobs`, orphan-recovery, events de-dup ORCH-5, `ci_poll`
ORCH-045) работает на уровне jobs/agent_runs и **не реконсилирует**
рассинхрон «источник истины ≠ стадия задачи».
## Решение
Фоновый daemon-поток `src/reconciler.py` (паттерн `queue_worker`, module-singleton,
`threading.Event`), стартует в `main.lifespan` после `worker.start()`, стоп в
`finally` перед `worker.stop()`. Две взаимодополняющие ветки на каждом тике
(`reconcile_interval_s`, дефолт 120с):
- **F-1 gate-side** (локальная БД): для каждой `task` где `stage∉{done}`, **нет**
активного job, `age(updated_at) ≥ grace_for_stage(stage)` — read-only пред-оценка
канонического QG стадии; если зелёный → продвижение **штатным**
`stage_engine.advance_stage(..., finished_agent=None)` (тот же путь, что у Plane
Approved-webhook). Красный → **тишина** (нет advance, нет нотификаций — спам
структурно невозможен). `analysis` F-1 **не** реконсилирует (человеческий гейт →
отдан F-2).
- **F-2 plane-side** (опрос Plane API per-project через `list_issues_by_state`):
`In Progress`+нет задачи → `handle_status_start`; `Approved`+не сдвинута →
`handle_verdict(approved=True)`; `Rejected`+не откатана →
`handle_verdict(approved=False)`. Обработчики `webhooks/plane.py`
**переиспользуются** (async → `asyncio.run` из sync-потока), логика не дублируется.
- **F-3:** усиление `sha→branch` в `handle_ci_status` (БД-fallback по
`repo`+`stage='development'`, видимость на INFO) — defense-in-depth.
**Инварианты:** источник истины — гейт/Plane, не событие; продвижение только через
`advance_stage`; идемпотентность (active-job guard + atomic-claim на создании +
grace + `max_concurrency=1`); never-raise на единицу работы; тишина при
синхронности; restart-safe; kill-switch.
## Альтернативы
- **Флаг подавления нотификаций в `advance_stage`** — отклонён: меняет общий
критический путь. Вместо этого «не вызывать advance_stage на красном гейте».
- **UNIQUE-индекс `tasks.plane_id`** для анти-дубля — отклонён как primary: риск
падения миграции на проде; выбран process-wide `threading.Lock` (single-process
топология). Индекс — задокументированное будущее упрочнение для multi-process.
- **Отдельная стадия/QG реконсиляции** — вне объёма; нарушает «источник истины —
существующий гейт».
- **Реконсиляция analysis по локальным артефактам** — отклонена: автопродвижение
неодобренного человеком BRD.
## Последствия
- Потерянный webhook ≠ молча застрявшая задача; ручной heartbeat-watchdog не нужен;
резервная сетка к ORCH-51 (буфер недоставленных) и ORCH-36 (deploy).
- Плата: фоновый поток + опрос Plane API (митигируется интервалом/фильтром/
per-project); двойная оценка гейта на зелёной задаче; анти-дубль опирается на
single-process-допущение (как и очередь ORCH-1).
- Self-hosting: `reconcile_enabled` — обязательный kill-switch; поэтапный раскат
(`reconcile_plane_enabled` гасит только F-2); reconciler не рестартит/не роняет
прод-контейнер. БД-схема и реестры (`STAGE_TRANSITIONS`/`QG_CHECKS`) не меняются.
## Уточнения
- **ORCH-060** (`docs/work-items/ORCH-060/06-adr/ADR-001-reconciler-skip-escalated.md`):
F-1 (`_reconcile_gate_task`) приобретает два пред-гарда ДО оценки гейта —
пропускает escalated (`developer_retry_count ≥ MAX_DEVELOPER_RETRIES`,
детерминированно) и Blocked/Needs-Input (Вариант A, Plane API, без миграции)
задачи. Инварианты adr-0007 сохранены (схема/реестры не меняются, never-raise,
тишина при пропуске).
- **ORCH-068** (`docs/work-items/ORCH-068/06-adr/ADR-001-reconciler-terminal-exclusion-and-cache-ttl.md`):
фикс livelock F-2 (спам `_note_unblock` по синхронизированной done-задаче после
ORCH-066). F-2 исключает терминалы по **группе состояния** (`completed`/`cancelled`,
fallback — ключи `done`/`cancelled`) проектно-независимо; `_note_unblock` — только при
подтверждённом state change (сравнение стадии до/после `_dispatch`) + in-memory дедуп;
`_STATES_CACHE` получает TTL (`ORCH_PLANE_STATES_TTL_S`, дефолт 300с, `0`=lifetime).
Инварианты adr-0007 сохранены (источник истины — Plane; реестры/схема/`handle_*`/F-1/F-3
не меняются; never-raise; kill-switch'и).
## Связи
adr-0002 (очередь / `available_at`, single-process-singleton), adr-0003 (условный
гейт — образец условности/флагов раската), adr-0006 (merge-gate как под-гейт ребра
внутри `advance_stage`), adr-0001 (реестр проектов для F-2 per-project), ORCH-5
(events de-dup — защита от дублей; reconciler — обратная защита от потерь),
ORCH-045 (`ci_poll`).

View File

@@ -0,0 +1,77 @@
# ADR-0008: Провенанс staging-образа перед BUILD-ONCE retag в прод (ORCH-058)
## Статус
Accepted (design) — реализация в ветке `feature/ORCH-058-self-deploy-retag-staging`.
Метка: `arch:major-change`.
> Примечание о нумерации: в `adr/` исторически два файла `adr-0007-*`
> (`executable-self-deploy`, `reconciler`) — пред-существующая коллизия. Этот ADR берёт
> следующий свободный номер **0008**; коллизию 0007 не трогаем (вне объёма ORCH-058).
## Контекст
ORCH-36 (`adr-0007-executable-self-deploy`) сделал стадию `deploy` исполняемой для
self-hosting: Phase B запускает host-хук, который шагом **2b** (BUILD-ONCE) делает
`docker tag $SOURCE_IMAGE → $TARGET_IMAGE` **без rebuild** — «прод = ровно тот артефакт,
что прошёл staging». Предпосылка: staging-образ свеж и собран из провалидированного кода.
**Этой гарантии нет.** Конвейер нигде не пересобирает `orchestrator-orchestrator-staging`
из провалидированного коммита; `deploy-staging` лишь гоняет `staging_check.py` против уже
работающего 8501. Инцидент (LESSONS_ORCH-036 п.4): staging-образ не пересобрали → проверка
прошла против старого кода → retag промоутнул СТАРЫЙ образ → прод **молча** откатился на
2-дневный код. Зелёный гейт = ложный позитив. Самый опасный из 4 багов: не падает, а тихо
откатывает инструмент, обслуживающий все проекты.
## Решение
Гарантировать `INV-FRESH`: в прод промоутится только образ, собранный из коммита,
провалидированного `deploy-staging` для данной задачи; иначе fail-fast (`FAILED` → откат на
`development`, БАГ-8), прод не трогается. Достигается **двумя взаимодополняющими слоями**
(defense in depth), только для self-hosting (условность как ORCH-35/36/43):
- **A — пересборка (liveness).** На ребре `deploy-staging → deploy`, ПОСЛЕ merge-gate и ДО
Phase A, детерминированный QG-под-чек `check_staging_image_fresh` пересобирает
`orchestrator-orchestrator-staging` из worktree валидированного коммита
(`--build-arg GIT_SHA=<sha>`, лейбл `org.opencontainers.image.revision`), пересоздаёт 8501
и прогоняет `staging_check`. FAIL → откат на `development`. Так валидируемый и промоутимый
артефакт — один и тот же; гарантирует наличие зелёного пути (нет вечного fail-fast).
- **B — fail-closed guard (safety).** Хук шагом 2b ПЕРЕД `docker tag` сверяет лейбл
`revision` образа `SOURCE_IMAGE` с `EXPECTED_REVISION` (пробрасывает `build_deploy_command`).
Несовпадение / пустой лейбл / пустой ожидаемый SHA / ошибка inspect → `exit 1` → FAILED.
Делает тихий промоут устаревшего образа структурно невозможным даже при отключённой/
проигравшей гонку A.
**Якорь провалидированного коммита**`git rev-parse HEAD` в worktree ПОСЛЕ merge-gate
(post-rebase tree, который ре-тестирован и сольётся в `main`). Один helper
`validated_revision(repo, branch)` питает и штамп сборки (A), и `EXPECTED_REVISION` (B).
**Условность и kill-switch:** единый `image_freshness_enabled` (вкл/выкл A+B как целое,
чтобы не было «B без A» = вечный fail-fast), `image_freshness_repos` (CSV; пусто →
self-hosting). Все настройки с префиксом `ORCH_`.
### Что НЕ меняется
`STAGE_TRANSITIONS` (набор стадий — под-гейт ребра, не стадия), exit-code хука (0/1/2),
`map_exit_code_to_status`, `check_deploy_status`/`_parse_deploy_status`, БАГ-8, terminal-sync,
merge-gate, Phase A/B/C. Схема БД — без миграций (провенанс в лейбле образа, не в БД).
### Что добавляется (сквозное)
- QG `check_staging_image_fresh` в реестре `QG_CHECKS` (+ snapshot-тест), wired через
`_handle_image_freshness` в `stage_engine` (рядом с merge-gate).
- Режим хука `--build-staging` (build из worktree + recreate 8501; STAGING-safe дефолты).
- OCI-лейбл `org.opencontainers.image.revision` в `Dockerfile` (`ARG GIT_SHA`).
- Helpers `validated_revision` / `rebuild_staging_image` в `self_deploy.py` (never-raise).
## Последствия
- Класс «тихого регресса прод» закрыт структурно (B); валидный деплой всегда доходит до
зелёного (A) — устранён ручной bootstrap-разрыв пересборки staging.
- Латентность ребра растёт (build + recreate + повторный staging_check); `staging_check`
гоняется дважды (soft pre-check агента + авторитетный код) — плата за «валидируем =
промоутим».
- Все сборки/recreate — ТОЛЬКО staging (8501); прод (8500) не трогается; `main` не пушится.
Новая под-компонента → `arch:major-change`.
## Связанные ADR
`adr-0007-executable-self-deploy` (BUILD-ONCE, Phase A/B/C), `adr-0006-merge-gate` (образец
edge-под-гейта), `adr-0003-staging-gate` (условность self-hosting), `adr-0005`
(run-as-host-uid). Детальный per-work-item: `docs/work-items/ORCH-058/06-adr/ADR-001-staging-image-provenance.md`.

View File

@@ -0,0 +1,56 @@
# adr-0009: Толерантность staging-вердикта к заведомо инфраструктурным FAIL
- **Статус:** accepted
- **Дата:** 2026-06-07
- **Задача:** ORCH-061
- **Детально:** `docs/work-items/ORCH-061/06-adr/ADR-001-staging-infra-tolerance.md`
## Контекст
Self-hosting `orchestrator` зацикливался на `deploy-staging`: `staging_check.py`
давал 2 ложных FAIL (C9a — ветка в sandbox, C9b — analyst-job в очереди), вызванных
отсутствием sandbox-настроек (bot-аккаунты не члены SANDBOX-проекта), а не регрессом
кода. `staging_check.py` делал `sys.exit(1)` при любом FAIL → deployer писал
`staging_status: FAILED``check_staging_status` FAILED → откат `deploy-staging →
development` → петля (жгла developer-ретраи и кредиты). Прод-деплой орка приходилось
доводить вручную — блокер автономного внедрения (ORCH-54).
## Решение
Классифицировать проверки staging-suite на **REAL** (pipeline) и **SANDBOX_INFRA**
(заведомо инфраструктурные, узкий allowlist `{C9a, C9b}`) и сделать вердикт
толерантным к инфра-FAIL, сохранив fail-closed для реальных проверок:
- Новый leaf-модуль `src/staging_verdict.py` (pure, never-raise, stdlib):
`classify_check(label)` + `compute_staging_verdict(items, infra_tolerant)`.
Правило: упала хоть одна REAL → FAILED/exit1; упали ТОЛЬКО SANDBOX_INFRA и
толерантность вкл → SUCCESS/exit0 (waived); толерантность выкл → legacy strict
(любой FAIL → FAILED).
- `scripts/staging_check.py` помечает проверки категориями, считает вердикт через
`staging_verdict`, печатает `INFRA-WAIVED` при вайвере (наблюдаемость).
- Kill-switch `staging_infra_tolerance_enabled` (env
`ORCH_STAGING_INFRA_TOLERANCE_ENABLED`, дефолт `True`; в `.env.staging`).
- `check_staging_status` / `_parse_staging_status` / `STAGE_TRANSITIONS` / реестр
`QG_CHECKS`**без изменений**; новый QG-чек не вводится. Условность ORCH-35
сохранена (не-self → no-op N/A).
- Инвариант FR-3: «no changes to commit» на action-стадиях (`deploy-staging`/`deploy`)
не есть недовыполнение — продвижение определяется exit0 + гейт-вердиктом
(launcher уже не откатывает; добавлена observability-строка).
## Альтернативы
- Только починить sandbox-инфру (направление а) — хрупко, не структурно, вне
автономной досягаемости таска; оставлено как опциональное hardening.
- «Зелёный по умолчанию» при недоступности проверок — запрещён (fail-closed).
- Новый QG-чек / структурный артефакт `15-staging-log.md` — избыточно, меняло бы
контракты/реестр; толерантность размещена в suite до артефакта.
## Последствия
- Петля устранена; страховка цела (реальный регресс → FAILED → откат).
- Чистая вердикт-логика юнит-тестируема без live staging/docker.
- Контракты гейтов/стадий/вердиктов/реестра и схема БД неизменны.
- Риск: узкое окно — реальный регресс именно в создании ветки/постановке
analyst-job может быть заваивен; митигировано allowlist'ом `{C9a,C9b}` + условием
«все REAL (вкл. C7/C8) зелёные» + INFRA-WAIVED-логом. Разблокирует ORCH-54.
## Связи
adr-0003 (условный staging-гейт — база `is_self_hosting_repo` / `check_staging_status`),
adr-0006 (merge-gate), adr-0007 (исполняемый self-deploy), adr-0008 (провенанс
staging-образа). Блокирует ORCH-54.

View File

@@ -0,0 +1,85 @@
# adr-0010: Post-deploy мониторинг прода + реакция на деградацию
- **Статус:** proposed (design) — реализация в ветке `feature/ORCH-021-post-deploy-rollback`
- **Дата:** 2026-06-07
- **Задача:** ORCH-021
- **Метка:** `arch:major-change` (новая под-компонента + новый reserved-agent job-kind)
- **Детальный ADR:** `docs/work-items/ORCH-021/06-adr/ADR-001-post-deploy-monitor.md`
## Контекст
Конвейер заканчивается на `deploy → done`: `check_deploy_status` видит
`deploy_status: SUCCESS` → terminal-sync (Plane → Done, release merge-lease), и
оркестратор **забывает про прод**. «Успех» сегодня = health-check в момент рестарта
(~60с окно в `orchestrator-deploy-hook.sh`). Класс инцидентов «зелёный деплой, красный
прод» (прецедент **ET-8**): деградация проявляется через минуты под боевым трафиком,
health отвечает `200 ok`, фича сломана. Для self-hosting опасно вдвойне — сломанный
прод-орк (8500) обслуживает ВСЕ проекты из общего инстанса.
## Решение
Продлить ответственность конвейера **ЗА** `done`: после терминального перехода для
применимого репо армится пост-деплой наблюдение окна `post_deploy_window_s` (дефолт
~15 мин) с интервалом `post_deploy_interval_s`; деградация фиксируется по
**детерминированным порогам**, при подтверждении выполняется реакция.
**Механизм — reserved-agent job `post-deploy-monitor`** (калька `deploy-finalizer`,
ORCH-36), НЕ отдельная стадия и НЕ daemon-поток:
- **Арм:** в `stage_engine.advance_stage`, в блоке `next_stage == "done"`, при
`post_deploy.post_deploy_applies(repo)``post_deploy.arm_monitor(...)` (sentinel
`armed` = идемпотентность, первый job через `enqueue_job(available_at_delay_s=...)`).
- **Тик:** `launcher.launch_job` перехватывает `agent == "post-deploy-monitor"` ДО
`_spawn``stage_engine.run_post_deploy_monitor(job)`: один опрос сигналов, append в
персистентный `series`, классификация; HEALTHY и окно не истекло → перепостановка с
задержкой; иначе → реакция + артефакт + `mark_done`.
- **Чистая логика — новый leaf-модуль `src/post_deploy.py`** (never-raise, по образцу
`self_deploy.py`/`staging_verdict.py`): `post_deploy_applies`, `probe_signals`
(опрос `/health` + доля 5xx на `/status`,`/queue`), `classify` (HEALTHY|DEGRADED —
главный предмет юнит-тестов), `decide_action` (NONE|ROLLBACK|ALERT_ONLY с учётом
self-hosting), sentinel-state хелперы, `write_post_deploy_log`.
**Сигналы и пороги (детерминированно, AC-3…AC-6):** `DEGRADED``≥
post_deploy_fail_threshold` ПОСЛЕДОВАТЕЛЬНЫХ провалов health ИЛИ доля 5xx на окне `>
post_deploy_5xx_threshold`. Одиночный глюк < порога → HEALTHY (нет ложных откатов).
**Реакция (BR-4/BR-5):**
- **Self-hosting (`orchestrator`) — ВСЕГДА `ALERT_ONLY`:** громкий Telegram + Plane,
запрос ручного approve отката. Тик НИКОГДА не откатывает/рестартит прод-контейнер
(структурный инвариант). Откат прод-орка, если оператор решит, — только detached
host-процесс (`self_deploy.initiate_deploy`), вне тика (MVP).
- **Не-self + `post_deploy_auto_rollback=True`:** хук `--rollback` с прод-env; exit
`0 → ROLLBACK_OK`, `1/2 → ROLLBACK_FAILED` + громкий алерт.
- Дефолт (`auto_rollback=False`) → `ALERT_ONLY`.
**Артефакт `16-post-deploy-log.md`** (новый) с YAML-frontmatter (`post_deploy_status`,
`action_taken`, `window_s`, `checks_total/failed`) — машиночитаемо для петли уроков
ORCH-8; best-effort. **Наблюдаемость** — блок `post_deploy` в `GET /queue` (образец
`reconcile.status()`).
## Альтернативы
- **Daemon-watchdog (как reconciler)** — отклонён: per-task серия опросов в памяти не
restart-safe (а деплой орка = рестарт); restart-safe-вариант требует тех же sentinel,
reserved-agent проще и уже имеет проверенную jobs+sentinel машинерию.
- **Отдельная пост-deploy стадия + QG** — отклонён: меняет `STAGE_TRANSITIONS`/
`QG_CHECKS`, ломает семантику терминального `done`; наблюдение принципиально ПОСЛЕ
`done`.
- **Авто-rollback прод-орка из тика** — отклонён (self-hosting safety): групповой риск;
контейнер не откатит себя надёжно. Self → alert + ручной approve (как ORCH-54).
- **Колонка в `tasks`** — отклонён: миграция на проде; sentinel-файлы restart-safe
(как ORCH-36/53/58).
## Последствия
- Класс «зелёный деплой, красный прод» закрыт измеримыми порогами; деградация =
сигнал для ORCH-8.
- Реестры (`STAGE_TRANSITIONS`/`QG_CHECKS`), контракт `check_deploy_status`,
terminal-sync, merge-gate, exit-code-контракт хука, схема БД — **не меняются**.
- Дефолты безопасны: kill-switch on, auto-rollback off, self только alert.
- Ограничение: монитор self бежит внутри наблюдаемого прода — полностью wedged
контейнер = пропущенный тик/алерт (known MVP gap; внешний watchdog — follow-up).
- Self-hosting: тик не рестартит/не роняет прод-контейнер; kill-switch
`post_deploy_monitor_enabled` обязателен; поэтапный раскат через `post_deploy_repos`.
## Связи
adr-0007-executable-self-deploy (ORCH-36 — sentinel/detached-host/finalizer образец,
`map_exit_code_to_status`), adr-0007-reconciler (ORCH-53 — daemon/`status()` образец,
отклонён как основной механизм), adr-0006 (merge-gate — условность/флаги раската),
adr-0003 (staging-gate — образец условности), adr-0008 (provenance — `.deploy-prev-image`/
хук-откат). Прецедент ET-8. Будущее: ORCH-8 (петля уроков), ORCH-54 (полный авто).

View File

@@ -0,0 +1,82 @@
# adr-0011: Job-reaper + проактивный реклейм merge-lease
| | |
|---|---|
| Статус | accepted |
| Дата | 2026-06-07 |
| Источник | ORCH-065 (BUG P0, блокер ORCH-54) |
| Детально | `docs/work-items/ORCH-065/06-adr/ADR-001-job-reaper-and-lease-reclaim.md` |
## Контекст
Единый инстанс с общей БД и очередью (`jobs`, `max_concurrency=1` для
self-hosting). Финализация статуса job (`done`/`queued`/`failed`) происходит
ТОЛЬКО в `launcher._monitor_agent → _finalize_job` внутри живого процесса. Смерть
monitor-потока/процесса между `proc.wait()` и `_finalize_job` (краш, OOM,
self-restart во время deploy) оставляет строку `jobs` навсегда `running`. При
`max_concurrency=1` одна такая зомби-строка блокирует claim всех job →
**встаёт конвейер всех проектов**. Единственная защита — `requeue_running_jobs()`
— работает ТОЛЬКО на старте процесса. Симметрично: merge-lease (ORCH-043,
файл `.merge-lease-<repo>.json`) реклеймится лишь лениво по TTL при чужом
`acquire`; liveness держателя по pid не проверяется → залипший lease блокирует
чужие merge. Это последняя ручная точка автономного self-deploy (блокер ORCH-54);
доказанные инциденты 07.06 — jobs 236/239/242/254.
## Решение
1. **Job-reaper** — новый daemon-поток `src/job_reaper.py` (каркас `reconciler`:
never-raise, `_stop`-Event, старт/стоп в `lifespan`, снимок в `/queue`,
kill-switch). Работает **без рестарта** процесса. Liveness — трёхуровневая:
Tier-1 мёртвый `jobs.pid` (новая колонка) после `reaper_dead_ticks` подряд
тиков; Tier-2 `agent_runs.exit_code` записан, а job ещё `running` — но только
после finalization-grace `reaper_finalize_grace_s` (окно неоднозначно: живой
monitor пишет exit_code ПЕРВЫМ, затем git push/PR/Plane-комментарии, поэтому
живой финализирующий monitor НЕ реапится); Tier-3 backstop по потолку
`reaper_max_running_s`. Действие — **claim-before-act**: для exit0 канонический
QG оценивается read-only ПЕРЕД атомарным claim, затем claim `done` ПЕРВЫМ и
только победитель claim выполняет `_try_advance_stage` (advance+enqueue) —
проигравший не делает побочных эффектов (источник истины — QG, не «exit0»);
гейт красный или exit≠0 / неизвестно → `attempts<max``queued`, иначе
`failed`+Telegram. Атомарный reap-claim (`UPDATE ... WHERE id=? AND
status='running'` + `rowcount`, как `claim_next_job`) исключает двойную
обработку (совместимость со стартовым `requeue_running_jobs`).
2. **Проактивный реклейм stale/dead lease** — функции в `merge_gate.py`
(`pid_alive`, `reclaim_stale_lease`), вызываемые на старте (рядом с
`requeue_running_jobs`) и периодически из тика reaper. Освобождение, если
держатель **мёртв** (pid не жив) ИЛИ **просрочен** (TTL); живой держатель в
пределах TTL — НЕ трогать. holder-aware, never-raise, условность как ORCH-43.
3. **Идемпотентная финализация merge** — без новой merge-логики: re-drive через
reaper→`queued`→переисполнение стадии / reconciler; дорогие шаги не
повторяются (`branch_is_behind_main==False`); добавлен детерминированный
never-raise guard `pr_already_merged` (читает состояние PR), консультируемый
перед повторным merge → уже слит = no-op.
4. **Схема БД**`jobs.pid INTEGER` через идемпотентный `_ensure_column`
(паттерн live-safe миграции). Больше ничего не меняется.
Kill-switch'и (`ORCH_*`): `reaper_enabled`, `reaper_interval_s`,
`reaper_dead_ticks`, `reaper_max_running_s`, `reaper_finalize_grace_s`,
`lease_reclaim_enabled`; переиспользуются `merge_lock_timeout_s`,
`merge_gate_repos`. `false` → строго прежнее поведение.
## Альтернативы
- Reaper внутри reconciler — отвергнуто (смешение stage- и jobs-уровней, общий
kill-switch, хуже изоляция).
- Только эвристика `agent_runs` без `jobs.pid` — отвергнуто как основной механизм
(не ловит зомби, чей monitor умер до записи exit_code); оставлена как Tier-2/3.
- БД-lock / внешний брокер очередей — вне объёма (single-node SQLite).
- Форс `done` по факту exit0 — отвергнуто; выбран gate-driven advance.
## Последствия
- (+) Зомби-job и залипший lease самовосстанавливаются без рестарта и без
оператора; очередь общего инстанса не встаёт; снят технический блокер ORCH-54.
- (+) Контракты неизменны (`STAGE_TRANSITIONS`, `QG_CHECKS`, `check_*`, БАГ-8,
exit-коды хука); одна колонка через проверенный idempotent-паттерн.
- () pid-liveness валиден в предположении одного pid-namespace (агент —
дочерний процесс оркестратора); закрыто backstop'ом по времени и TTL.
- () streak-счётчик in-memory (сброс на рестарте; рестарт покрыт
`requeue_running_jobs`).
## Связи
- Базируется: adr-0002 (очередь), adr-0006 (merge-gate), adr-0007 (reconciler /
self-deploy).
- Разблокирует: ORCH-54.

View File

@@ -0,0 +1,63 @@
# adr-0012: Security-гейт — secret-scanning + dependency audit перед мержем
- **Статус:** proposed
- **Дата:** 2026-06-07
- **Задача:** ORCH-022
- **Детальный ADR:** `docs/work-items/ORCH-022/06-adr/ADR-001-security-gate.md`
## Контекст
Оркестратор автономен: `developer` пишет код без человека-фильтра. Перед слиянием ветки в
`main` нет проверки на утёкший секрет (ключ/токен/пароль/приватный ключ) и уязвимую
зависимость (CVE). Для self-hosting один общий прод-инстанс обслуживает все проекты с общей
БД — секрет/CVE через одну задачу попадает в прод всех (CLAUDE.md §self-hosting, §8). Фактический
мерж PR в `main` делает `deployer` в начале стадии `deploy`.
## Решение
Детерминированный (без LLM) **security-гейт как под-гейт ребра `deploy-staging → deploy`**,
рядом с merge-gate (ORCH-043) и image-freshness (ORCH-058), исполняемый **ПЕРВЫМ** среди
edge-под-гейтов (ДО merge-gate). `STAGE_TRANSITIONS` не меняется; в `QG_CHECKS` добавлен
`check_security_gate`. Паттерн — как у соседей: leaf-модуль `src/security_gate.py`
(never-raise) + тонкая обёртка в `QG_CHECKS` + врезка `_handle_security_gate` в `advance_stage`.
- **Secret-scanning (`gitleaks`, offline):** скан `origin/main..HEAD`; любой секрет вне
аллоулиста (`.gitleaks.toml`) → вклад в FAIL. Offline → гарантия «секрет всегда блокирует»
не зависит от сети.
- **Dependency audit (`pip-audit`, OSV/PyPI):** severity ≥ `security_dep_block_severity`
(дефолт `HIGH`) → FAIL; ниже / UNKNOWN → warning. Недоступность фида → **fail-open +
громкий warning** (анти-петля; флаг `security_dep_audit_fail_closed` для строгого режима).
- **ПЕРВЫМ на ребре, ДО merge-gate:** дёшево фейлить до дорогих rebase/rebuild; скан ветки
ДО rebase не «обвиняет» задачу в CVE, притащенной обновившимся `main` (анти-петля
ORCH-061); до захвата merge-lease → при FAIL lease освобождать не нужно.
- **Артефакт `17-security-report.md`** с YAML-frontmatter (`security_status`,
`secrets_found`, `deps_blocking`, `deps_warning`, `deps_audit_degraded`); вердикт читается
ТОЛЬКО из frontmatter (канон), negative-токен авторитетен; битый/нет → fail-closed.
- **FAIL → откат на `development`** + developer-retry (общий `_developer_retry_count`, cap 3,
затем `set_issue_blocked` + Telegram); `task_desc` несёт дословные находки (ORCH-046).
- **Условность (как ORCH-35/43/58):** `security_gate_enabled` + `security_gate_repos`; пусто
→ реально только self-hosting (`orchestrator`), прочие репо — no-op pass.
- **never-raise**, таймаут `security_scan_timeout_s`, гейт не деплоит/не рестартит прод.
## Альтернативы
- **Вариант R (review-стадия):** diff может разойтись с мержем в `main`; merge-edge — последняя
страховка. Отклонено.
- **Вариант C (CI-job через `check_ci_green`):** пороги/severity/аллоулист/артефакт плохо
выражаются статусом коммита; коуплинг с раннером. Отклонено для v1 (точка расширения).
- **Новая стадия `security`:** «пустая» стадия без агента не имеет триггера (как в ORCH-043).
Отклонено.
- **fail-closed dep-audit / аудит после rebase:** ложные откаты → петля. Отклонено.
- **Новая колонка retry в БД:** не нужна (переиспользуем `_developer_retry_count`).
## Последствия
- Класс «тихо влитый секрет/CVE» закрыт: секреты — безусловно (offline), CVE — best-effort при
доступности фида. Самоприменение CLAUDE.md §8 без человека.
- Плата: ещё один «скрытый» под-гейт ребра (нет в `STAGE_TRANSITIONS`); внешние инструменты
(gitleaks в образе, pip-audit в зависимостях); время скана на каждом прогоне (ограничено
таймаутом); v1 — Python-only (SAST/мульти-стек — follow-up WI).
- Сквозное изменение (новый QG + edge-под-гейт) → `arch:major-change`; прод-деплой ORCH-022 —
строго через staging-гейт (8501), без рестарта прод-контейнера.
## Связи
adr-0006 (merge-gate — паттерн edge-под-гейта/отката), adr-0008 (image-freshness —
условность/never-raise/fail-closed), adr-0003 (условный гейт / `is_self_hosting_repo`),
adr-0009 (анти-петля ложных FAIL, ORCH-061), ORCH-046 (дословный reason в `task_desc`),
ORCH-9/15 (мульти-стек — будущая зависимость), ORCH-2 (worktree-изоляция).

View File

@@ -0,0 +1,63 @@
# adr-0013: Merge-в-main + пост-деплой верификация как условие `done` (фикс фантомного merge)
- **Статус:** accepted
- **Дата:** 2026-06-08
- **Задача:** ORCH-071 (CRITICAL bug)
- **Детальный ADR:** `docs/work-items/ORCH-071/06-adr/ADR-001-merge-verify-gate.md`
- **Постмортем:** `docs/history/LESSONS_2026-06-08_phantom-merge.md`
## Контекст
Для self-hosting репо `orchestrator` стадия `deploy` идёт детерминированным путём
(`_handle_self_deploy_phase_b → initiate_deploy → run_deploy_finalizer`), а LLM-агент
`deployer` НЕ запускается. Фактический merge PR в `main` исторически делал **только**
агент `deployer` → на self-hosting пути **нет шага merge-в-main вообще**. Detached
host-деплой лишь retag'ает образ + рестартит 8500; `done` достигается по
`deploy_status: SUCCESS` без верификации `main`. «Зелёный» деплой (образ из рабочей
ветки) маскирует отсутствие merge → следующая задача срезает ветку от устаревшего `main`
и теряет код предшественника. Накопительно потеряны ORCH-022/059/066/068. Вторичный
фактор: Phase B рестартит прод → merge внутри живого процесса гонялся бы с рестартом
(урок №3).
## Решение
Детерминированный **merge-актор + пост-merge верификация** как **под-гейт ребра
`deploy → done`**, врезанный в единственную функцию перехода `advance_stage` (симметрично
edge-под-гейтам security/merge-gate/image-freshness). `STAGE_TRANSITIONS`,
`check_deploy_status`/`_parse_deploy_status`, реестр `QG_CHECKS`, схема БД — **не меняются**.
- **Врезка `_handle_merge_verify` в `advance_stage`** (`current_stage=="deploy"` и
`next_stage=="done"`, ПОСЛЕ зелёного `check_deploy_status`, ДО `update_task_stage`).
Гейтит **ВСЕ** пути к `done` единообразно: `run_deploy_finalizer` (Phase C), reconciler
F-1, job-reaper — все идут через `advance_stage`. Закрывает дыру: reconciler F-1 иначе
протолкнул бы `done` в обход merge.
- **Merge в Phase C (после рестарта), НЕ в Phase B.** Phase C finalizer —
restart-surviving (reserved-job `deploy-finalizer`, claim воркером нового контейнера,
re-drive reaper'ом). Merge физически строго ПОСЛЕ рестарта → рестарт его не убивает
(G3 вторым вариантом — «шаг, переживающий рестарт»).
- **Merge-актор `merge_gate.merge_pr`** — `pr_already_merged` (no-op повтор, ORCH-065) →
иначе Gitea `POST /repos/{owner}/{repo}/pulls/{index}/merge`. Никогда push/force-push в
`main`. never-raise.
- **Верификатор `merge_gate.verify_merged_to_main`** — `PR.merged==true` ИЛИ
`git merge-base --is-ancestor <validated_sha> origin/main`. never-raise → `False`
(«не подтверждено»).
- **Не подтверждено → alert «deploy succeeded but not merged» (Telegram+Plane) + HOLD**
(`set_issue_blocked`, задача НЕ `done`, БЕЗ авто-отката на `development` — not-merged
есть инфра-дефект, реакция ALERT-only как ORCH-021 self-hosting). Подтверждено →
штатный `deploy → done` (терминал-sync / post-deploy monitor как сегодня) +
`merged_to_main: true` во frontmatter `14-deploy-log.md` (наблюдаемость, `deploy_status:`
нетронут).
- **Идемпотентность (INV-5):** `pr_already_merged` перед merge; verify зелёный для
уже-слитого PR; повтор без дубль-merge/ложного отката.
- **Условность (как ORCH-35/43/58):** `merge_verify_enabled` (kill-switch, дефолт `true`) +
`merge_verify_repos` (пусто → только self-hosting). Non-self репо — no-op, merge остаётся
за агентом `deployer`.
## Инварианты
never-raise на verify/merge (ошибка → alert, не падение конвейера); не рестартить/не ронять
прод 8500; ручной approve прод-деплоя сохранён (`Confirm Deploy`, ORCH-059); только PR-merge
API Gitea; restart-safe (sentinel + jobs, без миграции БД).
## Последствия
Невозможно «`done` + прод задеплоен, а PR `open`». Минусы: при недоступной Gitea verify
консервативно `False` → возможен ложный HOLD+alert (снимается повтором; fail-closed для
`done` приоритетен); HOLD требует ручного вмешательства. Диагностика фантома — runbook
`docs/operations/PHANTOM_MERGE_RUNBOOK.md` (G4).

View File

@@ -0,0 +1,77 @@
# adr-0014: SHA-в-main — единственный критерий merge-verify + регресс-гард целостности `main`
- **Статус:** accepted
- **Дата:** 2026-06-08
- **Задача:** ORCH-073 (BUG CRITICAL — эрозия `main`)
- **Amends:** [adr-0013](adr-0013-merge-verify-gate.md) (ORCH-071) — меняет КРИТЕРИЙ подтверждения merge.
- **Детальный ADR:** `docs/work-items/ORCH-073/06-adr/ADR-001-merge-verify-sha-truth-and-regression-guard.md`
- **Постмортем:** `docs/history/LESSONS_2026-06-08_phantom-merge.md`
## Контекст
adr-0013 (ORCH-071) ввёл под-гейт merge-verify на ребре `deploy → done`, но допускал
подтверждение merge по **ИЛИ-критерию**: `verify_merged_to_main` возвращал `True`, если
`pr_already_merged(repo, branch)` **ЛИБО** SHA — предок `origin/main`. `pr_already_merged`
засчитывал **любой** merged PR ветки, включая авто docs-PR (staging/deploy-логи). У одной
feature-ветки в `main` сливались только docs-PR, а code-PR — нет → `pr_already_merged`=`True`
verify `CONFIRMED``done`, хотя кода в `main` не было. Накопительно потеряны ORCH-067 (ссылки
`plane_issue_link`) и ORCH-069 (`qg0_title_max`). Вторичный усилитель — CHANGELOG-ребейзы,
откатывающие ветку и тащащие устаревший код-сосед. Восстановление кода (G1) выполнено вручную
restore-PR #76; этот ADR устраняет корень навсегда.
## Решение
1. **SHA-в-main — единственный критерий (FR-1).** `verify_merged_to_main(repo, branch, sha)`
подтверждает merge **ТОЛЬКО** прямым фактом `git merge-base --is-ancestor <sha> origin/main`
(после `git fetch origin main`). OR-ветка `pr_already_merged` **удалена** из верификатора.
Пустой `sha` / любая git-ошибка → `False` (fail-closed: alert + HOLD). never-raise (INV-1).
2. **`pr_already_merged` → idempotency-guard, различающий code-PR/docs-PR (FR-2).** Засчитывает
merged PR только при `head.ref==<feature-branch>` И `base.ref=="main"` (явный фильтр в цикле,
не ненадёжный query-параметр `head`). Используется лишь как защита `merge_pr` от второго merge,
НЕ как подтверждение `done`.
3. **`merge_pr` сливает именно code-ветку (FR-3).** Выбор открытого PR по `head.ref==branch` И
`base.ref=="main"`; merge только Gitea `POST /pulls/{index}/merge`, никогда push/force-push в
`main`. Источник истины «слилось» — FR-1.
4. **Регресс-гард целостности `main` (FR-5).** Новая `merge_gate.check_main_regression`,
вызываемая в `_handle_merge_verify` ПОСЛЕ подтверждённого SHA-в-main и ДО `done`: проверяет, что
`origin/main` содержит **декларативный набор маркеров** ключевых функций ранее-merged задач
(`git grep -c <marker> origin/main -- <path>` > 0). Маркер отсутствует → **alert «main
regressed» + HOLD** (НЕ `done`, БЕЗ авто-отката на `development` — инфра-дефект, ALERT-only как
ORCH-021/071). Набор — append-only константа `MAIN_REGRESSION_MARKERS` в `merge_gate.py`
(расширяется каждой значимой задачей). **Fail-open** на git-ошибке самого грепа (регресс
утверждается только при детерминированном `count==0`); первичный фейл-клозед — SHA-в-main.
Kill-switch `regression_guard_enabled` (дефолт `true`); non-self → no-op.
5. **`.gitattributes CHANGELOG.md merge=union` (FR-4).** В корне репо; авто-слияние правок
`## [Unreleased]` без конфликта → `auto_rebase_onto_main` не откатывает ветку и не тащит
устаревший код-сосед. `docs/**/*.md` под union **НЕ** ставится (union только для append-only;
доки переписываются построчно).
## Инварианты
never-raise на verify/merge/регресс-гарде (ошибка → alert/HOLD, не падение); прод 8500 не
рестартится/не падает в рамках merge; merge только Gitea PR-API без force-push в `main`; ручной
`Confirm Deploy` (ORCH-059) сохранён; идемпотентность по «SHA-в-main», а не по «любому merged PR»;
non-self репо (enduro) — merge/verify/регресс-гард без изменений. `STAGE_TRANSITIONS`, реестр
`QG_CHECKS`, `check_deploy_status`, схема БД, внешние HTTP-эндпоинты — **без изменений**.
## Альтернативы
- Сохранить PR-флаг как со-критерий verify (с фильтром head/base) — отклонено: PR можно слить и
тут же откатить ребейзом-соседом; надёжен только факт «SHA в main».
- `docs/**/*.md merge=union` — отклонено: тихая дубликация строк в переписываемых доках.
- Регресс-гард с авто-откатом / хранением маркеров в БД/Plane — отклонено (Не-цель «не менять
схему БД/Plane»; реакция ALERT-only).
- Fail-closed на marker-grep — отклонено: ложный HOLD при git-сбое; marker-grep вторичен.
## Последствия
Невозможно «`done` + прод задеплоен, а code-PR не в `main`». Ложно-зелёный по docs-PR устранён в
корне. CHANGELOG-конфликты больше не откатывают ветку. Регресс соседнего кода ловится отдельным
гардом. Минус: при недоступной Gitea/git verify консервативно `False` → возможен ложный HOLD+alert
(снимается повтором; fail-closed для `done` приоритетен). Набор маркеров требует дисциплины —
значимая задача дописывает свой маркер.
## Связи
- Amends adr-0013 (ORCH-071), наследует adr-0006 (merge-gate), adr-0011 (job-reaper/lease).
- Детально: `docs/work-items/ORCH-073/06-adr/ADR-001-merge-verify-sha-truth-and-regression-guard.md`.

View File

@@ -0,0 +1,47 @@
# adr-0015: Зависимости задач + сериализация merge внутри репо
**Статус:** accepted · **Дата:** 2026-06-08 · **Источник:** ORCH-026
**Связи:** дополняет adr-0006 (merge-gate), adr-0011 (merge-lease + reclaim), adr-0013/0014
(merge-verify, SHA-in-main), adr-0002 (очередь). Детально —
`docs/work-items/ORCH-026/06-adr/ADR-001-merge-serialization-and-task-deps.md`.
## Контекст
Эрозия `main` 08.06 родилась из некоординированного параллелизма задач одного репо (ветки от
устаревшего `main`, фантом-merge затирает соседа). adr-0014 закрыл последствия; ORCH-026 — корень
на уровне планировщика. Плюс исходный скоуп ORCH-026: декларативные зависимости задач (B ждёт A).
## Решение
**Уровень A — сериализация merge/деплоя (per-repo).** Окно сериализации уже обеспечивается
merge-lease (adr-0011): захват в `check_branch_mergeable`, удержание до release (PR-merged webhook /
`deploy→done`=SHA-in-main для self / откат / проактивный reclaim). Это и есть окно
«merge → main-updated» — **механизм не переписывается**. Добавляется единственное новое поведение:
**безусловный proactive pre-merge rebase** (флаг `premerge_rebase_always`, дефолт `True`, скоуп
`merge_gate_repos`): под лизом всегда вызывается `auto_rebase_onto_main` (no-op + «Everything
up-to-date» на актуальной ветке → CI не триггерится; реальный догон на отстающей). Инвариант:
никаких push в `main`, force только `--force-with-lease` на ветку.
**Уровень B — декларативные зависимости.** Аддитивная таблица `job_deps(task_id,
depends_on_task_id)`**источник истины планировщика** (offline-устойчивость: сетевой Plane в
горячем claim встанет очередью всех проектов). Источник декларации настраивается
`task_deps_source = db|plane|hybrid` (дефолт `db`); планировщик всегда читает БД-кэш. Гейт —
условие `NOT EXISTS` в `claim_next_job` (задача не выбирается, пока есть незавершённая зависимость;
слот `max_concurrency` не занимается). Циклы — DFS-детектор (`src/task_deps.py`) + `set_issue_blocked`
+ alert. Видимость — строка «⏳ ждёт ORCH-NNN» в Telegram-карточке (Plane Blocked — на дедлоке).
Зависимости — только intra-repo (v1).
## Альтернативы
Отдельный merge-lock/merge-queue (дублирует adr-0011); расширение release-точек лиза (не нужно —
окно уже корректно); Plane как источник истины планировщика (self-hosting risk); гейт зависимостей
в воркере с claim+requeue (churn vs. чистый `NOT EXISTS`); поле в `tasks` вместо таблицы (M:N хуже).
## Последствия
Минимально-инвазивно: `STAGE_TRANSITIONS`/`QG_CHECKS` не тронуты (паттерн врезки), переиспользует
merge-gate/merge-lease целиком. Обе фичи инертны без данных → нулевая регрессия для enduro-trails.
restart-safe, never-raise, kill-switch на каждую (`premerge_rebase_always`, `task_deps_enabled`).
Миграция — только аддитивная (`CREATE TABLE/INDEX IF NOT EXISTS`). Ограничение: B v1 — intra-repo.
Self-hosting safety: изменения идут через `deploy-staging``Confirm Deploy`, без внеочередного
рестарта прода.

View File

@@ -0,0 +1,52 @@
# ADR-0016: ensure_open_pr — гарантированный код-PR перед merge-verify (ORCH-082)
## Статус
Accepted — амендмент к [adr-0013](adr-0013-merge-verify-gate.md) и
[adr-0014](adr-0014-merge-verify-sha-source-of-truth.md). Детально:
`docs/work-items/ORCH-082/06-adr/ADR-001-ensure-open-pr-before-merge-verify.md`.
## Контекст
Merge-verify (ORCH-071/073) — под-гейт ребра `deploy → done`: детерминированно мержит код-PR в
`main` (`merge_pr`) и подтверждает merge **только** по «SHA-в-main» (`verify_merged_to_main`,
ORCH-073). На деплое ORCH-074 (08.06) `merge_pr` вернул `("False", "no open PR")`: у ветки **не
было** открытого PR с `head==branch` И `base=="main"`. Защита ORCH-073 верно удержала задачу
(HOLD, не ложный `done`), но это лечило **следствие**.
Первопричина (код-аудит): PR создаётся в конвейере **единственной** функцией
`launcher._ensure_pr`, вызываемой **только** на developer-пути и **только** при свежем
worktree-коммите. Любой сценарий без свежего developer-коммита (бойнс без правок, повторный
прогон, **ручное восстановление ветки/`main`** — случай ORCH-074) оставляет ветку без код-PR.
Инвариант «к merge-verify у ветки есть открытый код-PR» в конвейере **отсутствовал** → блокер
автономного деплоя (ORCH-54).
## Решение
Аддитивно обеспечить инвариант **внутри того же под-гейта**, ПЕРЕД `merge_pr`, не трогая машину
стадий:
1. **Новый leaf-актор `merge_gate.ensure_open_pr(repo, branch) -> (status, detail)`** (never-raise):
`GET …/pulls?state=open` с фильтром **`head.ref==branch` И `base.ref=="main"`** (идентичен
`merge_pr`/ORCH-073 FR-3 — авто-docs-PR не считается код-PR) → `("existed", N)`; иначе
`POST …/pulls``("created", N)`; гонка «PR exists» → повторный GET → `existed` (без дублей);
любая ошибка → `("failed", reason)`.
2. **Врезка в `_handle_merge_verify`** ПОСЛЕ резолва `validated_revision` и ПЕРЕД `merge_pr`:
`created|existed` → штатно к `merge_pr`; `failed` → честный HOLD+alert через новый helper
`_hold_pr_create_failed` (текст «PR создать не удалось» — отличим от not-merged HOLD), задача
остаётся на `deploy`, БЕЗ отката на development.
3. **Kill-switch `merge_verify_autocreate_pr_enabled`** (дефолт `True`); область —
`merge_verify_applies` (self-hosting / `merge_verify_repos`). `False` → поведение ORCH-074 1:1.
4. **`launcher._ensure_pr`** рекомендуется делегировать в `ensure_open_pr` (единый код создания
PR), сохранив прежний триггер «только developer-путь».
## Последствия
- **Защита ORCH-073 неприкосновенна и приоритетна:** подтверждение merge остаётся ТОЛЬКО
`verify_merged_to_main` (SHA-в-main) + `check_main_regression`. Создание PR устраняет лишь
**ложный** HOLD «no open PR», но не маскирует реально невлитый код (тот → HOLD как прежде).
- **Без миграций:** идемпотентность выводится из Gitea (наличие открытого PR), схема БД не меняется
— restart-safe; повторный заход (reaper/reconciler/re-approve) → `existed`, дублей нет.
- **Инварианты целы:** `STAGE_TRANSITIONS`, `QG_CHECKS`, схема БД, `check_deploy_status`,
exit-коды хука, merge-gate (ORCH-043), image-freshness (ORCH-058) — без изменений; `main` не
push/force-push; never-raise на всём пути.
- **Наблюдаемость:** один однозначный исход в логах на проход — created / existed / failed; HOLD по
failed текстуально отличим от HOLD not-merged.
- **Минус:** код-PR может создаваться после прохождения гейтов — безопасно, т.к. гейты валидируют
код ветки, а merge-verify идёт ПОСЛЕ всех гейтов; PR — лишь механизм слияния, ревью не обходится.

View File

@@ -0,0 +1,59 @@
# adr-0017: Per-repo serial gate (пакетный автономный режим, serial e2e)
Статус: **proposed** · Дата: 2026-06-09 · Источник: **ORCH-088** (Этап 1)
Детально: `docs/work-items/ORCH-088/06-adr/ADR-001-serial-gate.md`.
## Контекст
Цель эпика ORCH-088 — масштаб автономности: накидать вечером 1020 задач и получить к утру пакет,
последовательно проведённый через весь конвейер (analysis → … → deploy → done). Корневая проблема —
**stale-анализ**: ветка задачи N+1 срезается на входе в анализ (`start_pipeline._create_gitea_branch`)
от `main`, ещё не содержащего код предшественника N. Физическое код-затирание уже закрыто (ORCH-026
auto_rebase + merge-lease); остаётся **логический** разрыв. Plane API v1 не имеет bulk/relations ⇒
очередь/зависимости хранятся у оркестратора (gate по локальной БД).
## Решение
**Per-repo serial gate** — новая задача репо не входит в `analysis` (не режет ветку, не запускает
analyst), пока в том же репо есть незавершённая задача (`stage != 'done'`) или репо заморожен.
Три механизма, аддитивно, под kill-switch, с областью репо, never-raise, restart-safe:
1. **Gate-в-claim** (`db.claim_next_job`) — analyst-job (`jobs.agent='analyst'`) применимого репо не
выбирается, если `EXISTS` другая незавершённая задача репо ИЛИ активна строка `repo_freeze`. По
образцу `task_deps` `NOT EXISTS` (ORCH-026); только локальная БД (offline hot-path). Job'ы уже
активной задачи проходят свободно; rework-analyst не блокирует себя (`t2.id != jobs.task_id`).
2. **Отложенный срез ветки** — для применимого репо `start_pipeline` создаёт task-row + enqueue
analyst, но **не** создаёт Gitea-ветку/docs; срез релоцируется на момент claim analyst-job
(launcher), когда `origin/main` уже содержит предшественника (`done` ⇔ SHA-в-main, ORCH-071/073).
`ensure_worktree` режет от свежего `origin/main` ⇒ AC-6 структурно. Идемпотентно (409 = no-op).
3. **Durable per-repo freeze** (`repo_freeze`) — post-deploy `DEGRADED`/rollback (ORCH-021) →
`set_repo_freeze` + Telegram-алерт; gate закрыт безусловно до **ручного** снятия
(`POST /serial-gate/unfreeze`). Деградировавшая задача уже `done` (BR-7) ⇒ нужен отдельный сигнал.
Чистая логика — leaf `src/serial_gate.py` (never-raise). Флаги `serial_gate_enabled` (kill-switch),
`serial_gate_repos` (CSV; **пусто ⇒ все репо**, в отличие от self-hosting-only ORCH-35/43/58),
`serial_gate_freeze_enabled`. Наблюдаемость — блок `serial_gate` в `GET /queue`.
## Альтернативы
- **Гейт в `start_pipeline` + re-trigger при `done`** — больше состояния/путей, риск зависших задач;
relocation на claim переиспользует restart-safe `jobs`-очередь.
- **Freeze как колонка `tasks`** — неверная семантика (freeze per-repo, задача уже `done`).
- **Self-hosting-only область** — лишает enduro анти-stale-base (FR-3).
- **Отдельная таблица очереди ожидания** — избыточно; `jobs(queued)`+gate достаточно.
- **Снятие freeze Plane-жестом** — перегрузка статусов (анти-паттерн ORCH-059).
## Последствия
- **+** AC-6 закрыт структурно; AC-2/AC-3 «бесплатны» (ожидание = `queued` job без ветки);
переиспользование проверенных паттернов; cross-repo параллелизм сохранён; `STAGE_TRANSITIONS` /
`QG_CHECKS` / `check_*` / merge-gate / merge-verify / image-freshness / post-deploy / deploy-хук /
`max_concurrency`**без изменений**.
- **NFR-1:** hot-claim тотальный сбой → **fail-open** (не заклинить очередь всех проектов); freeze в
Python-слое → **fail-closed** (безопасность прода).
- **** Срез ветки/docs мигрируют из async в sync-путь launcher (обёртка); Blocked-задача держит пакет
(Этап 1, осознанно); freeze снимается только вручную.
- Откат: `serial_gate_enabled=False` ⇒ claim/старт 1:1 как до ORCH-088; таблица `repo_freeze` инертна.
- **Вне скопа** (Этап 1): merge-очередь FIFO, pre-merge rebase как отдельная фича, фазы A/B/C,
любой параллелизм задач внутри одного репо, зависимость от ORCH-83.
## Связи
- Переиспользует: adr-0002 (очередь ORCH-1), adr-0015 (claim-gate/auto_rebase/merge-lease ORCH-026),
adr-0010 (post-deploy monitor — источник DEGRADED), adr-0013/0014 (merge-verify ⇒ `done`⇔SHA-в-main).
- Новая аддитивная таблица `repo_freeze` (`docs/work-items/ORCH-088/08-data-requirements.md`).

View File

@@ -0,0 +1,59 @@
# ADR-0018: Авто-режим по лейблам — autoApprove / autoDeploy (ORCH-089)
## Статус
Accepted (реализация — ORCH-089)
## Контекст
Конвейер имеет два **человеческих** гейта, тормозящих пакетный автономный прогон
(эпик ORCH-088, «1020 задач за ночь»):
1. **BRD** (`analysis`): ждёт ручного Plane-статуса `Approved` → advance на `architecture`.
2. **Прод-деплой** (`deploy`): Phase A ставит `Awaiting Deploy` и ждёт ручного
`Confirm Deploy` (ORCH-059) → Phase B (`initiate_deploy`).
Для доверенных задач оба клика избыточны. Нужно снять **только эти два человеческих
решения**, выборочно/декларативно (лейбл Plane на задаче), не ослабляя ни одной
технической проверки.
## Решение
Аддитивно, по образцу условных под-гейтов (ORCH-035/043/058/059/088): leaf-модуль чистой
логики `src/labels.py` (never-raise) + точечные врезки + флаги. `STAGE_TRANSITIONS`, реестр
`QG_CHECKS`, все `check_*`, схема БД — **не трогаются**.
- **`autoApprove`** (лейбл задачи) → в `_handle_analysis_approved_flow` (ветка `files_ok`)
после `In Review`+коммента: `set_issue_approved` (индикация) + лог/Telegram/Plane-коммент +
`advance_stage(..., finished_agent=None)` — тот же путь, что человеческий Approved
(`approved-via-status``analysis → architecture` + `mark_brd_review_ended`). Без
дублирования переходной логики.
- **`autoDeploy`** (лейбл задачи) → в `_handle_self_deploy_phase_a` сразу после advance на
`deploy` + `clear_state`: лог/Telegram/Plane-коммент + `_handle_self_deploy_phase_b(...)`
(idempotency-маркер `INITIATED`, `Deploying`, finalizer). Пропускаются лишь
индикативно-человеческие шаги (`Awaiting Deploy` + «ask-human»).
- **Чтение лейблов** — `plane_sync.fetch_issue_labels` + `get_project_labels` (TTL-кэш,
образец `get_project_states`); сопоставление по нормализованному имени; источник истины —
Plane API (не payload). Новый сеттер `set_issue_approved` (ключ `approved` уже в states).
- **Флаги:** `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` → при выключенном флаге нулевой сетевой оверхед.
## Критические инварианты
- **Авто-режим снимает ТОЛЬКО человеческое решение**, не ослабляя ни один тех-гейт
(CI / staging / security / merge-gate / image-freshness / merge-verify / regression-guard /
post-deploy). autoDeploy живёт в точке, где все под-гейты ребра `deploy-staging → deploy`
уже зелёные → структурно «никогда не деплоит сломанное».
- **Fail-safe (never auto):** любая ошибка/недоступность Plane/неоднозначность имени →
«нет авто» → ручной гейт (согласовано с fail-closed-практикой ORCH-059). never-raise.
- **Нулевая регрессия:** без лейблов / `auto_label_enabled=False` / репо вне scope →
поведение 1:1 как до ORCH-089 (enduro не затронут).
- **Идемпотентность:** autoApprove — advance применяется один раз (поздний Approved/F-2
видят уже `architecture`); autoDeploy — маркер `INITIATED`.
## Последствия
**+** минимальная поверхность, единый источник истины перехода, декларативно/обратимо,
независимые лейблы, безопасный дефолт. **** Approved-статус транзиентен (durable-аудит —
лог/Telegram/коммент); 12 GET к Plane на гейт применимого репо (TTL-кэш карты лейблов);
требуется однократно создать лейблы в Plane-проекте ORCH (инфра-предусловие; их отсутствие =
fail-safe ручной режим).
Детально: `docs/work-items/ORCH-089/06-adr/ADR-001-auto-label-gates.md`,
`07-infra-requirements.md`, `10-tech-risks.md`.

View File

@@ -0,0 +1,49 @@
# adr-0019: Стандарт документов пайплайна (docs/_standards + docs/_templates + ADR-naming)
Статус: **proposed** · Дата: 2026-06-09 · Источник: **ORCH-075** (ORCH-52b, слой 1 эпика ORCH-52)
Детально: `docs/work-items/ORCH-075/06-adr/ADR-001-pipeline-docs-standard.md`.
## Контекст
Агенты всех ролей пишут номерные доки work item (`00…17`) «по памяти»; каталогов
`docs/_standards/` и `docs/_templates/` нет. Следствия: разнобой структуры между задачами; риск
рассинхрона критичных frontmatter-ключей машинных доков (`verdict:` / `result:` / `deploy_status:` /
`staging_status:` / `security_status:`), которые читает гейт; отсутствует целостная карта «стадия →
агент → документ → гейт». Эпик ORCH-52 слоист: слой 1 (52b) фиксирует **договорённость**, машинная
проверка/валидатор — отдельный слой 52c.
## Решение
**Документационный стандарт, docs-only, выведенный из фактического кода и эталонных доков:**
1. `docs/_standards/PIPELINE_DOCS.md` — манифест-карта «стадия → документ → владелец-агент →
категория (`required`/`when-applicable`/`optional`) → гейт/механизм → frontmatter machine-key».
Манифест **документирует** поведение гейтов (источник истины остаётся `src/`), честно различает
machine-verdict доки (`12,13,14,15,17`) и информационные (`00,08,10,16`), и помечает под-гейты
ребра `deploy-staging→deploy` (security/merge/image-freshness) как врезки в `advance_stage`, а не
строки `STAGE_TRANSITIONS`.
2. `docs/_templates/*` — копируемые скелеты для каждого `required`/`when-applicable` дока; секции
выведены из эталонов (ORCH-088/073/089/071), новые не изобретаются; машинные доки несут точный
frontmatter-ключ из ground-truth.
3. **ADR-naming** канонизирован: `docs/work-items/<plane-id>/06-adr/ADR-NNN-<kebab-slug>.md` (NNN с
`001`); кросс-каттинговые решения дублируются в этот глобальный реестр `adr-NNNN-<slug>.md`.
Подключение — ссылки из `CLAUDE.md` и `docs/architecture/README.md` + запись в `CHANGELOG.md`.
## Альтернативы
- Сразу валидатор на гейте — отвергнуто (ORCH-52c; нарушил бы docs-only/NFR-1, групповой риск).
- Манифест как источник истины гейтов — отвергнуто (дубль-истина «манифест ≠ код»).
- Шаблоны в `docs/work-items/_template/` — отвергнуто (риск для сканеров/гейтов наличия файлов).
- Ретро-фит истории доков — отвергнуто (вне scope, отдельный риск).
## Последствия
- **+** Единый golden source структуры доков; меньше ложных падений гейтов из-за неверного
frontmatter-ключа; ADR-naming записан; база для ORCH-52c.
- **+ Нулевой рантайм-риск:** только `docs/**` + `CLAUDE.md` + `CHANGELOG.md`; `STAGE_TRANSITIONS` /
`QG_CHECKS` / `check_*` / `src/stage_engine.py` / схема БД — без изменений; полностью обратимо.
- **** Манифест — снимок поведения гейтов, дрейфует до ORCH-52c (митигейшн: источник истины — код,
reviewer-правило, привязка к именам `check_*`); стандарт описательный, не принуждающий.
## Связи
- Источник: ORCH-075 (`docs/work-items/ORCH-075/06-adr/ADR-001-pipeline-docs-standard.md`).
- Документирует (не меняет): adr-0003/0006/0008/0012/0013/0014/0016 (гейты и под-гейты ребра),
`STAGE_TRANSITIONS` (`src/stages.py`), `QG_CHECKS` (`src/qg/checks.py`).
- Downstream: ORCH-52c (frontmatter-валидатор / writer-контракт), ORCH-52d (правка промптов).

View File

@@ -0,0 +1,63 @@
# adr-0020: Единый frontmatter-контракт + спека handoff (reader/writer/валидатор)
Статус: **Accepted** · Дата: 2026-06-09 · Источник: **ORCH-076** (ORCH-52c)
Детально: [`docs/work-items/ORCH-076/06-adr/ADR-001-frontmatter-contract.md`](../../work-items/ORCH-076/06-adr/ADR-001-frontmatter-contract.md)
## Контекст
Слой 1 эпика ORCH-52 (ORCH-075/52b) дал **описательный** стандарт документов
(`docs/_standards/PIPELINE_DOCS.md`), явно отложив машинную проверку на ORCH-52c. В коде:
`src/frontmatter.py` — только single-key reader (never-raise), а ~10-строчный блок парсинга
YAML-frontmatter **продублирован** в 5 вердикт-парсерах (`check_reviewer_verdict`,
`_parse_tests_verdict`, `_parse_deploy_status`, `_parse_staging_status`, `parse_security_status`)
+ в `_strip_frontmatter`/`extract_security_findings`. Единого контракта чтения, writer'а, схемы
и формальной спеки handoff — нет. Эти парсеры читают вердикты **на гейтах self-hosting**
инструмента, обслуживающего прод других проектов из общего инстанса → любой регресс = стоп
конвейера всех проектов.
## Решение
1. **`src/frontmatter.py` → полный frontmatter-контракт** (функции в существующем leaf-модуле,
контракт **never-raise**): сохранённый `read_frontmatter_value` (без изменений) + единый
парс-примитив `parse_frontmatter(content) -> FrontmatterParse` (единственная точка
YAML-логики, структура различает no-block / malformed / yaml-error / data) + `render_/
write_frontmatter` (writer) + `validate_schema` (обязательная схема
`work_item, stage, author_agent, status, created_at, model_used`) + `strip_frontmatter`.
2. **Унифицируется механизм парсинга, НЕ семантика.** Все 5 вердикт-парсеров читают YAML через
`parse_frontmatter`; token-наборы, upper-casing, приоритет негативного токена, 3-полевой
контракт tester'а (ORCH-047), fallback `worktree→origin/main`**1:1**. Сигнатуры и
`tuple[bool, str]` — неизменны. Reason-строки переносятся дословно.
3. **Валидатор не hard-fail по умолчанию.** Флаг `frontmatter_validation_strict` (env
`ORCH_FRONTMATTER_VALIDATION_STRICT`, дефолт `False`): default — warning/лог, **вне
вердикт-пути гейтов** (нулевая регрессия); hard-fail — зарезервированный strict-режим
(включение — с ORCH-52d). Иначе ORCH-52c заблокировала бы собственный деплой.
4. **Формальная спека handoff** `docs/_standards/HANDOFF_PROTOCOL.md` — «стадия → обязательный
выход» (документы + frontmatter-ключи), согласована 1:1 с `PIPELINE_DOCS.md` §2§3; источник
истины — код. `PIPELINE_DOCS.md` обновляется ссылкой + отметкой о реализации машинного слоя.
5. **Без изменений** `STAGE_TRANSITIONS`, состава `QG_CHECKS`, API, схемы БД.
## Альтернативы
- Общий «умный» verdict-резолвер (поле+токены для всех гейтов) — отклонён: различия token-логики
→ риск тонкого регресса на гейте при self-hosting. Унифицируем только парс YAML.
- Класс/новый пакет — отклонён: состояния нет, лишний blast radius.
- Hard-fail валидатор по умолчанию — отклонён (NFR-3: self-block собственного деплоя).
- Сторонняя `python-frontmatter` — отклонена: лишняя зависимость ради ~30 строк.
## Последствия
- **+** Конец дублирования/рассинхрона парсинга; writer+валидатор+схема готовы к ORCH-52d;
спека handoff закрывает пробел контракта стадий.
- **+** Нулевая регрессия по построению: семантика и reason-строки 1:1, валидатор инертен при
дефолте, never-raise сохранён, enduro 1:1.
- **** Унификация частичная (парс, не семантика); strict-режим «спящий» до ORCH-52d.
- **Обратимость:** `frontmatter_validation_strict=False` ⇒ прежнее поведение; перевод гейтов
поведенчески инвариантен.
- **Риск:** первый боевой `autoDeploy` орка (ORCH-089) — наблюдение за стадией `deploy`
(`docs/work-items/ORCH-076/10-tech-risks.md`).
## Связи
- Опирается: adr-0019 (pipeline-docs-standard, ORCH-075), ORCH-016 (reader), ORCH-047
(3-полевой tester), adr-0012 (security-гейт), adr-0018 (auto-label/`autoDeploy`).
- Готовит: ORCH-52d (эмиссия полной схемы агентами; возможное включение strict).

View File

@@ -0,0 +1,84 @@
# adr-0021: Канон Anthropic для системных промптов агентов + эмиссия frontmatter-схемы 52c
- **Статус:** proposed
- **Дата:** 2026-06-09
- **Источник:** ORCH-077 (эпик ORCH-52, слой 52d — замыкающий)
- **Связи:** реализует слой промптов к adr-0019 (52b, PIPELINE_DOCS) и adr-0020 (52c,
frontmatter-контракт). Детально — `docs/work-items/ORCH-077/06-adr/ADR-001-anthropic-prompt-canon.md`.
## Контекст
Эпик ORCH-52 строит сквозной контракт документации конвейера: **52b** (adr-0019) — описательный
стандарт документов + скелеты `docs/_templates/`; **52c** (adr-0020) — машинный контракт
`src/frontmatter.py` (reader/writer/валидатор `REQUIRED_FIELDS`) + спека `HANDOFF_PROTOCOL.md` с
обязательной 6-польной схемой `(work_item, stage, author_agent, status, created_at, model_used)`.
Две незакрытые проблемы:
1. **Цепочка 52b→52c→52d разорвана.** Writer и валидатор схемы есть, но работают warning-only
(`frontmatter_validation_strict=False`); агенты **не эмитят** поля схемы — на входе валидатора нет
данных, петля не замкнута.
2. **Форма 6 промптов `.openclaw/agents/*.md` разнородна** (RU/EN, свободная структура) → снижает
предсказуемость агентов прода, которые исполняются на КАЖДОЙ задаче ВСЕХ проектов из общего
инстанса (self-hosting).
Факт загрузки (сверено `src/agents/launcher.py`): промпт `cat`-ается из git-worktree агента в момент
запуска (`--system-prompt "$(cat .openclaw/agents/<role>.md)"`), НЕ запекается в образ.
## Решение
Ввести **обязательный канон формы** для всех агент-промптов и сделать его машинно-проверяемым.
1. **Фиксированный XML-скелет (5 обязательных секций, нормативный порядок):**
`<context>``<task>` (+ опц. `<thinking>`) → `<deliverables>``<constraints>`
`<output_format>`. Доп. секции (`<success_criteria>`, `<escalation>`) — после. Контекст/роль
вперёд, формат вывода последним (recency для следования схеме).
2. **Аддитивная эмиссия схемы 52c.** `<output_format>` каждого промпта перечисляет 6 полей схемы с
роле-специфичными значениями и инструктирует ставить их **рядом** с существующим machine-verdict
ключом, **не меняя его имя/регистр/значения** (`verdict:`, `result:`, `staging_status:`,
`deploy_status:`, `security_status:` — байт-в-байт). Для `04-test-plan.yaml` (чистый YAML) — как
top-level ключи. Гейты читают вердикты как раньше (схема в boolean-вердикте не участвует).
3. **Few-shot + позитивные альтернативы.** Ссылки на `docs/_templates/` и эталоны (ORCH-073/088);
каждый запрет в формате «❌ X → ✅ Y».
4. **CoT/thinking** у решающих ролей (architect/reviewer/tester/deployer).
5. **Анти-регресс машинно.** Структурные тесты `tests/test_agent_prompts_canon.py` (без запуска
агентов): 5 секций, 6 полей схемы, точный регистр machine-verdict ключей, ключевые
self-hosting-маркеры (deployer: `docker exec orchestrator-staging`, `pr_already_merged`,
«не рестартить 8500»). `test_agent_frontmatter_no_model.py` остаётся зелёным.
6. **Enforcement не включается.** `frontmatter_validation_strict` остаётся `False` (warning-only);
52d учит эмитить добровольно. Hard-fail — отдельная будущая задача.
**Границы:** docs/prompts-only. `src/**` (config, launcher, frontmatter, stages, qg/checks,
stage_engine), `STAGE_TRANSITIONS`, `QG_CHECKS`, состав machine-verdict ключей, схема БД, `tools:`-блок
промптов — **не трогаются**.
**Норматив на будущее:** любая новая правка/добавление агент-промпта следует этому канону (5 секций +
аддитивная схема + ❌→✅). Отступление требует нового ADR.
## Альтернативы
- **Сразу включить hard-fail схемы.** Отвергнуто: правка `src/config.py` вне scope; для self-hosting
рискованно (забытое поле валит гейт всех проектов). Сначала эмиссия, enforcement — позже.
- **Канон как рекомендация, не норма.** Отвергнуто: теряется машинная проверяемость, эпик требует
контракт.
- **Запечь промпты в образ.** Отвергнуто: противоречит loading-model (cat из worktree), добавило бы
прод-рестарт-зависимость.
## Последствия
- **+** Петля 52 замкнута: схема наполняется реальными данными на каждой стадии всех проектов.
- **+** Единый предсказуемый канон; правки промптов вступают в силу **без прод-рестарта** (следующий
worktree от `main`) → нулевой self-hosting-риск выкатки.
- **+** Естественный in-vivo A/B: reviewer/tester задачи исполняются под новыми промптами в той же
ветке (метод BR-6).
- **** Рост объёма промптов (митигейшн: ссылки вместо инлайна, контроль объёма).
- **** Риск регресса инструкции (митигейшн: построчная карта + структурные тесты + приоритетный
review deployer/reviewer).
- **Откат:** `git revert` PR — свободная форма возвращается, эмиссия прекращается, гейты идентичны.
## Связи
- Реализует: adr-0019 (52b), adr-0020 (52c).
- Per-work-item: `docs/work-items/ORCH-077/06-adr/ADR-001-anthropic-prompt-canon.md`.
- Стандарты: `docs/_standards/PIPELINE_DOCS.md`, `docs/_standards/HANDOFF_PROTOCOL.md`,
`src/frontmatter.py::REQUIRED_FIELDS`.
- Сверено по коду: `src/agents/launcher.py`, `Dockerfile`,
`src/config.py::frontmatter_validation_strict`, `tests/test_agent_frontmatter_no_model.py`.

View File

@@ -0,0 +1,106 @@
---
work_item: ORCH-078
stage: architecture
author_agent: architect
status: proposed
created_at: 2026-06-09
model_used: claude-opus-4-8
---
# adr-0022: Стандарт маркеров-трассировки `ORCH-NNN` + правило чтения ADR перед правкой
- **Статус:** proposed
- **Дата:** 2026-06-09
- **Источник:** ORCH-078 (эпик ORCH-52, слой 52e — трассировка, слой 4)
- **Связи:** продолжает цепочку стандартов эпика 52 — adr-0019 (52b, `PIPELINE_DOCS.md`),
adr-0020 (52c, frontmatter-контракт), adr-0021 (52d, канон промптов). Детально —
`docs/work-items/ORCH-078/06-adr/ADR-001-traceability-marker-standard.md`.
## Контекст
Эпик ORCH-52 строит сквозной контракт документации конвейера: **52b** (adr-0019) — описательный
стандарт документов + скелеты; **52c** (adr-0020) — машинный frontmatter-контракт + `HANDOFF_PROTOCOL.md`;
**52d** (adr-0021) — 6 промптов в каноне Anthropic + добровольная эмиссия 52c-схемы. Закрыты слои
структуры (52b), машинного вердикта (52c) и формы промптов (52d), но **слой трассировки кода к
решениям не формализован**.
Факты, сверенные с кодом `main`:
- В `src/` живёт **51 уникальный** маркер `ORCH-NNN` (`grep -rhoE 'ORCH-[0-9]+' src/ | sort -u | wc -l`),
привязывающий нетривиальные инварианты к породившему их work item — **сложившаяся практика без
формального стандарта** (`docs/_standards/` несёт лишь `PIPELINE_DOCS.md`/`HANDOFF_PROTOCOL.md`).
- Высокая плотность: `config.py`=63, `stage_engine.py`=55, `agents/launcher.py`=50, `plane_sync.py`=48,
`merge_gate.py`=26 вхождений.
Три незакрытые проблемы:
1. **Нет правила чтения.** Агент, правя маркированную строку, не обязан прочитать ADR, который её
ввёл → риск молча сломать инвариант. Это класс «фантомного merge»
(`docs/history/LESSONS_2026-06-08_phantom-merge.md`), породившего ORCH-071/073.
2. **Reviewer не контролирует соблюдение** — ось «Соответствие ADR» проверяет ADR текущей задачи, не
сверку правки чужого маркированного кода с его ADR.
3. **Анти-археология** — блок с 50+ маркерами = раскопки по 4+ work item.
## Решение
Ввести **нормативный стандарт маркеров-трассировки** `docs/_standards/TRACEABILITY.md` (слой 4 эпика
52) и точечно дополнить промпты правилом чтения / контролем соблюдения **со ссылкой на единый
источник**. Это **docs + prompts-only**, нулевое касание кода; стандарт — описательно-нормативный
документ + анти-регресс-тест промптов, **не машинный гейт конвейера**.
1. **`TRACEABILITY.md`** кодифицирует существующий контракт (не вводит новый синтаксис): определение
маркера, формат (inline-комментарий, рекомендуется ссылка на решение), правило размещения (рядом с
нетривиальным инвариантом), чтение истории **с реальным проверяемым примером**
(`src/serial_gate.py` → ORCH-088 → `ADR-001-serial-gate.md`), fallback-доступ, анти-археология,
каноничный текст правила чтения.
2. **Единый источник истины правила.** Каноничная формулировка живёт только в `TRACEABILITY.md`;
промпты несут короткую врезку-**ссылку**, не копию → нет дрейфа между файлами (анти-дубль 52d).
3. **Точечные врезки (аддитивно, 52d-канон не переписывается):** `developer.md` — правило чтения +
fallback-доступ («❌ X → ✅ Y»); `architect.md` — правило чтения + анти-археология; `reviewer.md`
усиление оси «Соответствие ADR» под-пунктом «правка маркированного кода сверена с его ADR; слом →
finding ≥P1».
4. **Анти-археология:** блок с **≥3** маркерами → одна сводная ссылка на сквозной ADR
(`docs/architecture/adr/`) вместо перечисления всех work item. Пример: `src/merge_gate.py`
`adr-0006/0013/0014/0016`.
5. **Fallback-доступ:** `git show origin/main:docs/work-items/ORCH-NNN/06-adr/ADR-001-<slug>.md`
когда папки нет в текущей ветке.
6. **Анти-регресс машинно:** расширение `tests/test_agent_prompts_canon.py` (tests-only) — утверждает
присутствие reading-rule/`TRACEABILITY`-маркеров; существующие проверки 52d и
`test_agent_frontmatter_no_model.py` остаются зелёными.
**Границы:** `src/**`, `STAGE_TRANSITIONS`, `QG_CHECKS`, `check_*`, `_parse_*`, `src/frontmatter.py`,
схема БД — **не трогаются**. `frontmatter_validation_strict` остаётся `False`; новый QG не вводится.
Массовый ретро-фит 51 существующего маркера **вне объёма** — стандарт действует «на будущее».
**Норматив на будущее:** новый/правимый значимый инвариант → ставь маркер своей задачи рядом; блок с
3+ маркерами → сводный сквозной ADR; правка чужого маркера → читай его `06-adr` до изменения.
## Альтернативы
- **Машинный гейт/CI-lint маркеров.** Отвергнуто: правка `src/`/CI вне scope; для self-hosting
рискованно (ложный fail валит конвейер всех проектов); премэйчур до описательного стандарта.
Enforcement — потенциальная будущая задача (как hard-fail схемы в adr-0021).
- **Массовый ретро-фит 51 маркера.** Отвергнуто: огромный диф, риск регресса смысла, вне объёма.
- **Копировать правило в каждый промпт.** Отвергнуто: дрейф между файлами, нарушение анти-дубль.
- **Только per-work-item ADR без глобального.** Отвергнуто: рвёт цепочку эпика 52 (52b/c/d имеют
глобальный ADR); нет точки входа для будущих агентов.
## Последствия
- **+** Замкнут слой 4 эпика 52: практика маркеров формализована, правило чтения защищает от слома
инвариантов; reviewer получает ось контроля.
- **+** Единый источник правила → нет дрейфа; обновление в одном файле.
- **+** Self-hosting без рестарта: промпт `cat`-ается из worktree → правило действует на следующем
worktree от `main` без рестарта 8500.
- **+** Полная обратимость: чисто текстовое изменение, нет миграций/состояния/kill-switch.
- **** Рост объёма 3 промптов (митигейшн: короткие врезки-ссылки).
- **** Стандарт нормативен, но не enforced машинно → соблюдение на дисциплине + ревью (осознанный
компромисс).
- **Откат:** `git revert` PR — стандарт удаляется, врезки исчезают, поведение кода/гейтов идентично.
## Связи
- Продолжает: adr-0019 (52b), adr-0020 (52c), adr-0021 (52d).
- Per-work-item: `docs/work-items/ORCH-078/06-adr/ADR-001-traceability-marker-standard.md`.
- Стандарты-соседи: `docs/_standards/PIPELINE_DOCS.md`, `docs/_standards/HANDOFF_PROTOCOL.md`,
будущий `docs/_standards/TRACEABILITY.md` (создаёт стадия development).
- Сверено по коду: `src/serial_gate.py:241,269` (ORCH-088), `src/merge_gate.py` (26 маркеров),
`tests/test_agent_prompts_canon.py`, `.openclaw/agents/{developer,architect,reviewer}.md`.
- Прецедент класса ошибки: `docs/history/LESSONS_2026-06-08_phantom-merge.md`.

View File

@@ -0,0 +1,98 @@
---
work_item: ORCH-079
stage: architecture
author_agent: architect
status: proposed
created_at: 2026-06-09
model_used: claude-opus-4-8
---
# adr-0023: Reviewer-ось «обзорные доки» (README-ограничения) + закрытие эпика ORCH-52
- **Статус:** proposed
- **Дата:** 2026-06-09
- **Источник:** ORCH-079 (эпик ORCH-52, слой 52f — обзорные доки, слой 5/финал)
- **Связи:** замыкает цепочку стандартов эпика 52 — adr-0019 (52b, `PIPELINE_DOCS.md`),
adr-0020 (52c, frontmatter-контракт), adr-0021 (52d, канон промптов), adr-0022 (52e,
трассировка маркеров). Детально — `docs/work-items/ORCH-079/06-adr/ADR-001-readme-sync-and-reviewer-overview-docs-axis.md`.
## Контекст
Эпик ORCH-52 строит сквозной контракт «документация = golden source наравне с кодом» слоями:
**52b** (adr-0019) — описательный стандарт документов + скелеты; **52c** (adr-0020) — машинный
frontmatter-контракт + `HANDOFF_PROTOCOL.md`; **52d** (adr-0021) — 6 промптов в каноне Anthropic +
добровольная эмиссия 52c-схемы; **52e** (adr-0022) — стандарт трассировки маркеров + reviewer-ось
«соответствие ADR». Закрыты слои структуры, машинного вердикта, формы промптов и трассировки кода —
но **обзорная витрина проекта (корневой `README.md`) не охвачена**.
Факты, сверенные с кодом `main`:
- Секция `README.md` «Известные ограничения» (`:236241`) имеет битую нумерацию (`1,2,3,4,3,4`) и
**выдаёт решённое за открытое**: worktree-гонки (закрыто `ensure_worktree` + ORCH-026/088),
in-process daemon (закрыто очередью ORCH-1), «Gitea CI не настроен» (опровергнуто `check_ci_green`,
`src/qg/checks.py:82`), «no retry» (опровергнуто backoff/breaker в `queue_worker.py`), плюс
устаревшие issue-ID (зрелый `plane_sync` ORCH-010/066/068) и Playwright-timeout (неприменим к
pytest-сервису; реальный механизм — watchdog ORCH-7).
- **Процессный пробел:** reviewer (ось «Документация») проверяет обновление *конвейерных* доков, но
**обзорные** разделы (README «Известные ограничения») в правиле не названы → витрина копит
рассинхрон, т.к. закрытие ограничения не обязывает автора снять пункт.
## Решение
Закрыть слой 5 (финал) эпика 52: **синхронизировать обзорные доки с кодом по факту** и добавить
reviewer'у **нормативную под-ось «обзорные доки»** (по образцу оси трассировки 52e). Это **docs +
prompt-only**, нулевое касание кода; правило — описательно-нормативное, **не машинный гейт**.
1. **Reviewer-ось «обзорные доки» (cross-cutting).** В `.openclaw/agents/reviewer.md` ось 4
«Документация» + `<constraints>` несут точечную врезку «❌→✅»: *PR закрыл пункт README «Известные
ограничения», README не обновлён → finding*. Severity **≥ P1**; при закрытии ограничения правкой
`src/` без обновления README — совпадает с существующим **P0** «`src/` изменён, доки не обновлены».
Канон 52d (5 секций, формат запретов, `<thinking>`), 6 полей схемы 52c и ключ
`verdict: APPROVED|REQUEST_CHANGES` — байт-в-байт.
2. **Витрина приведена к коду (NFR-3).** Все 6 устаревших пунктов сняты/перенесены в «Закрыто
(история)» с ORCH-ссылками; в «открытых» остаются ТОЛЬКО реально открытые, верифицированные
кодом/задачей; нумерация сквозная. Запрет на изобретение ограничений (только уже
задокументированные known-limitations — анти-scope-creep).
3. **Точечная сверка** `README.md` / `docs/architecture/README.md` с `src/` (стадии/`QG_CHECKS`/
модели-эффорты/компоненты), минимально инвазивно.
4. **Анти-регресс машинно:** расширение `tests/test_agent_prompts_canon.py` (tests-only) — assert
присутствия оси обзорных доков; проверки 52d и `test_agent_frontmatter_no_model.py` зелёные.
**Границы:** `src/**`, `STAGE_TRANSITIONS`, `QG_CHECKS`, `check_*`, `_parse_*`, `src/frontmatter.py`,
схема БД — **не трогаются**. `frontmatter_validation_strict` остаётся `False`; новый QG не вводится.
### Эпик ORCH-52 — закрыт (карта слоёв)
| Слой | Задача | Глобальный ADR | Артефакт |
|------|--------|----------------|----------|
| 52b — структура доков | ORCH-075 | adr-0019 | `docs/_standards/PIPELINE_DOCS.md` + `_templates/` |
| 52c — машинный frontmatter | ORCH-076 | adr-0020 | `src/frontmatter.py` + `HANDOFF_PROTOCOL.md` |
| 52d — канон промптов | ORCH-077 | adr-0021 | 6 промптов `.openclaw/agents/*.md` |
| 52e — трассировка маркеров | ORCH-078 | adr-0022 | `docs/_standards/TRACEABILITY.md` |
| **52f — обзорные доки** (финал) | **ORCH-079** | **adr-0023** | `README.md` + reviewer-ось |
## Альтернативы
- **Машинный enforcement (новый QG «README актуален»).** Отвергнуто: вне scope; для self-hosting
ложный fail валит конвейер всех проектов; правило остаётся нормативным, как 52e. Enforcement —
возможная будущая задача (как hard-fail схемы 52c).
- **Отдельный `docs/_standards/` для правила обзорных доков.** Отвергнуто: одно правило, один
артефакт (README) — врезки в промпт достаточно; новый стандарт-файл избыточен.
- **Только per-work-item ADR.** Отвергнуто: рвёт цепочку эпика 52 (52be имеют глобальный ADR); нет
явной точки «эпик 52 закрыт».
## Последствия
- **+** Витрина проекта честна; самоподдерживающаяся синхронность (reviewer-ось).
- **+** Эпик 52 формально закрыт сквозным ADR — единая точка входа для будущих агентов.
- **+** Self-hosting без рестарта: промпт `cat`-ается из worktree → правило с следующего worktree
от `main` без рестарта 8500.
- **+** Полная обратимость: чисто текстовое изменение, нет миграций/состояния/kill-switch.
- **** Правило нормативно, не enforced машинно → дисциплина + ревью (осознанный компромисс).
- **** Рост `reviewer.md` на короткую врезку (митигейшн: точечность, без переписывания).
- **Откат:** `git revert` PR — доки/промпт/тест откатываются, поведение кода/гейтов идентично.
## Связи
- Замыкает: adr-0019 (52b), adr-0020 (52c), adr-0021 (52d), adr-0022 (52e).
- Per-work-item: `docs/work-items/ORCH-079/06-adr/ADR-001-readme-sync-and-reviewer-overview-docs-axis.md`.
- Сверено по коду: `src/agents/launcher.py` (`ensure_worktree`, `_resolve_timeout`),
`src/queue_worker.py` (backoff/breaker), `src/qg/checks.py:82,381`, `src/plane_sync.py:451,541`,
`README.md:236241`, `.openclaw/agents/reviewer.md`, `tests/test_agent_prompts_canon.py`.
</content>

View File

@@ -0,0 +1,59 @@
---
work_item: ORCH-063
stage: architecture
author_agent: architect
status: proposed
created_at: 2026-06-09
model_used: claude-opus-4-8
---
# adr-0024: Disk-watchdog — фоновый heartbeat-демон мониторинга заполнения хост-ФС
> Сквозной (cross-cutting) ADR: вводит **новый фоновый компонент** оркестратора в ряду
> `reconciler` (adr-0007) и `job_reaper` (adr-0011). Детальное решение задачи —
> `docs/work-items/ORCH-063/06-adr/ADR-001-disk-watchdog.md`.
## Статус
Proposed (ORCH-063)
## Контекст
07.06.2026 диск хоста mva154 тихо дорос до 100% и положил **весь self-hosting-конвейер** (один
прод-инстанс `orchestrator` обслуживает все прод-проекты из общей БД/очереди). Проактивного сигнала
о заполнении диска у системы не было. Оркестратор уже имеет два проверенных фоновых daemon-потока с
единым каркасом (`threading.Thread(daemon=True)` + `threading.Event`, `start/stop/status`,
never-raise, снимок в `GET /queue`): `reconciler` (ORCH-053) и `job_reaper` (ORCH-065). Новый
эксплуатационный watchdog логично встроить тем же паттерном.
## Решение
Вводится третий фоновый компонент **disk-watchdog** (`src/disk_watchdog.py`):
- **Калька каркаса** `reconciler`/`reaper`: daemon-поток, чистый stop через `_stop.wait(interval)`,
контракт `start()`/`stop(timeout)`/`status()`, старт/стоп в `main.lifespan` (старт последним —
после `reaper.start()`; стоп первым в reverse-порядке), наблюдаемость — аддитивный блок
`disk_monitor` в `GET /queue`.
- **Замер** заполнения **хост-ФС** через смонтированные bind-пути (`/repos`, `/app/data`) stdlib
`shutil.disk_usage` (не overlay `/` контейнера, не субпроцесс `df`); дедуп путей по `st_dev`.
- **Решение об алерте** — pure-функция от `(used_pct, threshold, prev_state, now, realert_s)`:
алерт на пересечении порога (дефолт 85%), ограниченный cooldown-повтор, recovery при возврате
ниже порога. Состояние анти-спама — in-memory (без миграции БД).
- **Алерт** — `send_telegram` (notifying), best-effort. Kill-switch `disk_monitor_enabled`.
- **Только сигнал, не лечение:** watchdog читает и уведомляет, не трогает диск/контейнер, не
рестартит прод (self-hosting безопасность). Авто-очистка диска — отдельная задача.
**Инварианты:** `STAGE_TRANSITIONS`, реестр `QG_CHECKS`, `check_*`, схема БД — **не меняются**
(watchdog — эксплуатационный демон, не Quality Gate, как `reconciler`/`reaper`). never-raise на
уровнях per-path / per-tick / per-send. При выключенном kill-switch — поведение 1:1 как сейчас
(нулевая регрессия для enduro-trails).
## Последствия
- **+** Ранний сигнал предотвращает групповой простой всех проектов; дёшево, без внешних
зависимостей (принцип «всё в Docker на одном сервере, минимум зависимостей»).
- **+** Знакомый паттерн фонового демона → низкий риск, простое сопровождение.
- **** In-memory состояние / best-effort Telegram — допустимы для раннего сигнала (не SLA).
- **Откат:** `ORCH_DISK_MONITOR_ENABLED=false`; миграций БД нет.
## Ссылки
- Задачный ADR: `docs/work-items/ORCH-063/06-adr/ADR-001-disk-watchdog.md`
- Родственные компоненты: [adr-0007-reconciler.md](adr-0007-reconciler.md),
[adr-0011-job-reaper-lease-reclaim.md](adr-0011-job-reaper-lease-reclaim.md)
- Топология host-разделов: `docs/operations/INFRA.md`
</content>

View File

@@ -0,0 +1,86 @@
---
work_item: ORCH-062
stage: architecture
author_agent: architect
status: proposed
created_at: 2026-06-09
model_used: claude-opus-4-8
---
# adr-0025: Build-cache-pruner — фоновый heartbeat-демон авто-уборки docker build cache на хосте
> Сквозной (cross-cutting) ADR: вводит **новый фоновый компонент** оркестратора в ряду
> `reconciler` (adr-0007), `job_reaper` (adr-0011) и `disk_watchdog` (adr-0024). Детальное
> решение задачи — `docs/work-items/ORCH-062/06-adr/ADR-001-build-cache-pruner.md`.
## Статус
Proposed (ORCH-062)
## Контекст
07.06.2026 диск хоста mva154 тихо дорос до 100% и положил **весь self-hosting-конвейер всех
проектов** (один прод-инстанс `orchestrator` на общей БД/очереди). Доминирующий «пожиратель» —
**docker build cache** (≈11 ГБ от частых пересборок прод/staging-образов). `disk_watchdog`
(adr-0024, ORCH-063) ввёл **сигнал** о заполнении (Telegram ≥85%) и явно отложил авто-очистку в
отдельную задачу. ORCH-062 — эта задача: **автоматическое освобождение build cache**, чтобы
инцидент не повторялся без оператора.
Сверено по коду: контейнер `orchestrator` **не содержит docker CLI** (`Dockerfile:11` — только
`openssh-client git curl`); host-docker-операции приложение уже делает **через ssh на хост**
(`image_freshness.image_revision`, `self_deploy` Phase B), канал `deploy_ssh_user@deploy_ssh_host`
настроен. У оркестратора три проверенных фоновых daemon-потока с единым каркасом.
## Решение
Вводится четвёртый фоновый компонент **build-cache-pruner** (`src/build_cache_pruner.py`):
- **Калька каркаса** `disk_watchdog`/`reconciler`/`reaper`: daemon-поток, чистый стоп через
`_stop.wait(interval)`, контракт `start()`/`stop(timeout)`/`status()`, старт/стоп в
`main.lifespan` (старт последним — после `disk_watchdog.start()`; стоп первым в reverse),
наблюдаемость — аддитивный блок `build_cache_prune` в `GET /queue`. Leaf-модуль (без обратных
зависимостей на `stage_engine`/`stages`/`qg`).
- **Уборка — строго `docker builder prune -f --filter until=<until>`** (BuildKit GC, дефолт
`until=24h`): удаляется только старый build cache, тёплый ≤24ч сохраняется. `-a` — опционально и
только в паре с возрастным фильтром. **Запрещены** `docker image prune`/`system prune`/удаление
образов запущенных сервисов/остановка-рестарт контейнеров.
- **Исполнение на хосте через ssh** (CLI в контейнере нет): `ssh deploy_ssh_user@deploy_ssh_host
"docker builder prune …"`, bounded таймаутом. **Нет ssh-таргета → тик no-op** → фича
естественно скоупится на self-hosting-прод.
- **Конфиг/kill-switch** (`ORCH_BUILD_CACHE_PRUNE_*`, дефолты безопасные): `enabled` (дефолт
`true`), `interval_s` (6ч), `until` (`24h`), `all` (`false`), `timeout_s`, `notify_min_gb`.
Валидаторы по образцу `disk_monitor_*` (невалид → лог + дефолт).
- **Сигнал + лечение как пара:** disk_watchdog сигналит о росте диска, build-cache-pruner убирает
доминирующего «пожирателя» — две половины одной операционной защиты.
**Инварианты:** `STAGE_TRANSITIONS`, реестр `QG_CHECKS`, `check_*`, `src/stage_engine.py`, схема БД
— **не меняются** (pruner — эксплуатационный демон, не Quality Gate, как watchdog/reaper). Без
миграции БД (учёт результата in-memory, best-effort). never-raise per-команда/per-tick. Уборка
**никогда** не рестартит docker daemon/прод-контейнер (self-hosting безопасность; рестарт-путь —
отвергнутый Вариант B). При выключенном kill-switch — поведение 1:1 как сейчас (нулевая регрессия
для enduro-trails).
## Альтернативы
- **host `daemon.json builder.gc.defaultKeepStorage`** — отвергнуто: требует рестарта docker
daemon (останавливает ВСЕ контейнеры хоста = групповой self-hosting риск); политика по объёму,
не по возрасту; не наблюдаемо в `GET /queue`.
- **host-cron** — отвергнуто как основное (оставлено ручным fallback): off-git невидимая инфра,
без `/queue`-наблюдаемости, без config-kill-switch, не тестируется.
- **raw-HTTP по docker.sock / docker CLI в образе** — отвергнуто: лишний код / раздувание образа
против уже существующего ssh-канала.
## Последствия
- **+** Корень инцидента 07.06 устраняется автоматически; тёплый кэш сохранён; без новых
зависимостей и без рестарта docker/прода (принцип «всё в Docker, минимум зависимостей»).
- **+** Знакомый паттерн фонового демона → низкий риск, наблюдаемость, обратимость, тестируемость.
- **** Зависимость от ssh на хост (как `image_freshness`/`self_deploy`); нет таргета → no-op
(наблюдаемо), фича не работает, но ничего не ломает.
- **Откат:** `ORCH_BUILD_CACHE_PRUNE_ENABLED=false`; миграций БД нет.
## Ссылки
- Задачный ADR: `docs/work-items/ORCH-062/06-adr/ADR-001-build-cache-pruner.md`
- Инфра/риски: `docs/work-items/ORCH-062/07-infra-requirements.md`,
`docs/work-items/ORCH-062/10-tech-risks.md`
- Комплемент: [adr-0024-disk-watchdog.md](adr-0024-disk-watchdog.md) (ORCH-063 — сигнал)
- Родственные компоненты: [adr-0007-reconciler.md](adr-0007-reconciler.md),
[adr-0011-job-reaper-lease-reclaim.md](adr-0011-job-reaper-lease-reclaim.md)
- Топология host / env-карта: `docs/operations/INFRA.md`
</content>

View File

@@ -0,0 +1,106 @@
---
work_item: ORCH-090
stage: architecture
author_agent: architect
status: proposed
created_at: 2026-06-09
model_used: claude-opus-4-8
---
# ADR-0026: Системное терминальное состояние `cancelled` — STOP-отмена задачи
Сквозной (cross-cutting) ADR. Детальное решение задачи —
`docs/work-items/ORCH-090/06-adr/ADR-001-stop-cancel-task.md`.
## Статус
Proposed
## Контекст
ORCH-090 вводит Plane-статус **STOP** — единый декларативный механизм отмены задачи (остановка
агента + полный сброс прогресса). Самое́ кросс-каттинговое следствие — появление **нового
системного терминального состояния `cancelled`** (стадия `tasks.stage='cancelled'` + терминальный
job-статус `jobs.status='cancelled'`). До ORCH-090 «терминальность задачи» в горячем планировщике
была захардкожена как **`stage == 'done'`** (единственный сток в `STAGE_TRANSITIONS`), и это
определение разъехалось между подсистемами:
- `src/reconciler.py` **уже** трактует `stage in ("done","cancelled")` как терминал-скип
(ORCH-086 D2 предвосхитил `cancelled`; стр. 196) и `_is_terminal_state` по группе Plane
`{completed, cancelled}` (ORCH-068, стр. 398415).
- `src/serial_gate.py` (ORCH-088) и `src/task_deps.py` (ORCH-026) считают задачу «незавершённой»
по `stage != 'done'`**без** `cancelled`. Если ввести `cancelled`-стадию, не тронув их,
отменённая задача навсегда будет «активной»/«незавершённой зависимостью» и **заклинит очередь
репо**.
Этот ADR фиксирует `cancelled` как первоклассное терминальное состояние, равноправное `done`, и
перечисляет ВСЕ точки, где системный предикат терминальности должен его признавать.
## Решение
### Инвариант
**«Задача терминальна» ⇔ `stage ∈ {done, cancelled}`.** Это единое определение для всех
подсистем планировщика/мониторинга. `cancelled` — терминальный **сток** (не новое ребро
конвейера): exit-гейты рёбер `STAGE_TRANSITIONS` и реестр `QG_CHECKS`/`check_*` **не меняются**.
### Точки, признающие `cancelled` терминальным (исчерпывающе)
1. `src/stages.py::STAGE_TRANSITIONS` — добавить сток
`"cancelled": {"next": None, "agent": None, "qg": None}` (параллельно `done`).
2. `src/serial_gate.py``repo_has_other_unfinished` и claim-фрагмент `t2.stage != 'done'`,
snapshot: `stage != 'done'``stage NOT IN ('done','cancelled')`. **(маркер ORCH-088)**
3. `src/task_deps.py` — dep-gate и `is_task_ready`: `stage != 'done'`
`stage NOT IN ('done','cancelled')`. **(маркер ORCH-026)**
4. `src/reconciler.py` — уже покрыто скипом `stage in ("done","cancelled")` (стр. 196);
`get_active_tasks_for_reconcile` опционально сузить до `NOT IN ('done','cancelled')`.
5. `src/job_reaper.py` / `src/queue_worker.py` — перед авто-requeue dead/running-job'а сверять
терминал задачи: `stage in ("done","cancelled")` → job помечается `cancelled`, не реквью'ится.
6. `src/post_deploy.py` / `stage_engine.run_post_deploy_monitor` — монитор не тикает по
отменённой задаче (терминал-проверка/маркер `done`).
### Новые терминальные исходы
- **Job:** `jobs.status='cancelled'` — нигде не реквью'ится; `claim_next_job` выбирает только
`status='queued'` (изменений в claim нет). `mark_job` стампит `finished_at` для `cancelled`.
- **Задача:** `tasks.stage='cancelled'` + аддитивные колонки `cancelled_at`,
`cancel_requested_at` (отложенная отмена в критическом окне merge/deploy). Натуральные ключи
`plane_id`/`work_item_id` тумбстонятся (`#cancelled-<id>`) для переиспользования «To Analyse»
с нуля; `plane_issue_id` сохраняется (аудит). Детали — 08-data-requirements.md.
### Точки врезки STOP (компоненты)
- `plane.py` — маршрут `stop` (fail-closed, не в `_DEFAULT_STATES`) → `handle_stop`; гейт релонча
ограничен стадией `analysis`.
- `stage_engine.cancel_task` — оркестрация отмены (graceful SIGTERM, cancel-jobs, worktree+branch,
tombstone, notify); безопасное прерывание merge/deploy (D7 локального ADR).
- leaf `src/cancel.py` — чистая логика (`applies`/`in_critical_window`/`snapshot`), never-raise.
- `src/gitea.py``delete_remote_branch` (never-raise; только feature-ветка, `main` неприкосновенен).
- `GET /queue` — read-only блок `stop`.
### Флаги / совместимость
- Kill-switch `stop_status_enabled` + scope `stop_status_repos` (CSV, пусто → все репо).
- При `stop_status_enabled=False`: STOP-обработка и гейт релонча инертны; расширение
терминал-набора `cancelled` безвредно при отсутствии отменённых задач → **нулевая регрессия**.
- `STAGE_TRANSITIONS` (exit-гейты) / `QG_CHECKS` / `check_*` / семантика
Approved/Rejected/Confirm Deploy / merge-gate (ORCH-043) / merge-verify (ORCH-071/073) /
image-freshness (ORCH-058) / post-deploy (ORCH-021) / serial-gate FIFO (ORCH-088) / auto-label
(ORCH-089) — **без изменений**.
- Миграции БД — только аддитивные/идемпотентные (`_ensure_column`); enduro не затронут (NFR-2).
## Последствия
- **+** Единое, консистентное определение терминальности — устранён латентный рассинхрон
`done`-only между планировщиком и реконсилятором.
- **+** STOP безопасен для self-hosting: не трогает `main`/прод, отложенная отмена в критическом
окне.
- **** Терминальность теперь читается из набора `{done, cancelled}`, а не из скаляра `'done'`
будущие подсистемы обязаны использовать набор. Митигейшн: этот ADR + маркер `ORCH-090` в
изменённых местах + тесты.
- **Откат:** `stop_status_enabled=False`; полный revert — снять врезки и вернуть предикаты к
`stage != 'done'`.
## Эволюция маркеров `cancelled`-терминала
Места, признающие `cancelled` терминальным (см. список выше), несут маркер `ORCH-090`. Правка
любого из них — сверяться с этим ADR (анти-археология: 3+ маркеров → одна ссылка сюда,
TRACEABILITY.md).
## Ссылки
- Детальный ADR: `docs/work-items/ORCH-090/06-adr/ADR-001-stop-cancel-task.md`
- Data: `docs/work-items/ORCH-090/08-data-requirements.md`
- Связанные: adr-0017 (serial-gate), adr-0015 (task-deps), adr-0007 (self-deploy),
adr-0006 (merge-gate), adr-0018 (auto-label)

View File

@@ -0,0 +1,82 @@
---
work_item: ORCH-093
stage: architecture
author_agent: architect
status: proposed
created_at: 2026-06-09
model_used: claude-opus-4-8
---
# adr-0027: Merge-актор — ретрай транзиентных ошибок Gitea + гард «ветка уже в `main`»
Сквозной (cross-cutting) ADR. **Амендмент** к [adr-0013](adr-0013-merge-verify-gate.md) (merge-verify
под-гейт), [adr-0014](adr-0014-merge-verify-sha-source-of-truth.md) (SHA-в-main как источник истины)
и [adr-0016](adr-0016-ensure-open-pr-before-merge-verify.md) (гарантированный код-PR). Детальное
решение задачи — `docs/work-items/ORCH-093/06-adr/ADR-001-merge-transient-retry-and-already-in-main-guard.md`.
> Регистрируется как сквозной, т.к. правит блок merge-актора с **3+ маркерами** (`ORCH-071`,
> `ORCH-073`, `ORCH-082`) — анти-археология маркеров (`docs/_standards/TRACEABILITY.md`): сводный
> ADR агрегирует эволюцию вместо перечисления work item в коде.
## Статус
Proposed
## Контекст
Детерминированный merge-актор merge-verify под-гейта (`deploy → done`, self-hosting) состоит из
`ensure_open_pr``merge_pr``verify_merged_to_main` (`src/merge_gate.py`). Инцидент **ORCH-063
(09.06)** вскрыл два дефекта, оба сверены по коду прода:
1. `merge_pr`**one-shot**: `POST /pulls/{index}/merge`, любой не-`200/201` → мгновенный `False`.
Транзиентная икота Gitea (`405 "Please try again later"` при пересчёте `mergeable` сразу после
пуша; `5xx`; таймаут) → ложный HOLD защиты ORCH-071/073 → ручной домерж.
2. `ensure_open_pr` — после ручного мержа код-PR `closed`, открытый не найден → создаёт **новый
пустой PR** на ветке, уже целиком в `main`.
Защита ORCH-071/073 («deploy succeeded but not merged») корректна и сохраняется; задача снижает
лишь **ложные** срабатывания на транзиентах и устраняет мусорные PR. Это блокер автономного прогона
(эпик ORCH-088).
## Решение
Аддитивно, без правки `STAGE_TRANSITIONS` / `QG_CHECKS` / схемы БД; INV-4 (мерж только через Gitea
PR-merge API; никогда `push`/`force-push` в `main`) и never-raise сохранены.
- **Ретрай-loop вокруг `POST …/merge`** (только мутирующий вызов) до `merge_retry_max_attempts`
(дефолт 3) с экспоненциальным backoff и потолком (`base 2`, `max 5`; суммарно ≤10 с). Классификатор
**транзиент** (`405`/`408`/`5xx`/таймаут/сетевое; `409`/`422` при `mergeable==True`; `mergeable==None`
→ транзиент-по-дефолту в рамках бюджета) vs **терминал** (`403`/`404`; `409`/`422` при
`mergeable==False`) — по коду ответа **и** полю `mergeable` (`GET /pulls/{index}`). Терминал →
быстрый честный `False` (защита ORCH-071/073 — как прежде). Образец — `check_ci_green`
(`attempt i/N`) + transient-breaker агентов.
- **Гард already-in-main в `ensure_open_pr`**: перед созданием PR — `git merge-base --is-ancestor
<branch> origin/main` (rc==0 → ветка целиком в `main`) → новый исход `"already-in-main"`, PR не
создаётся; git-ошибка/ambiguous → **fail-OPEN** на текущий create-путь (гард не должен превратить
икоту git в ложный no-op мержа). `_handle_merge_verify` трактует `"already-in-main"` как «мержить
нечего» → пропуск `merge_pr` → авторитетный SHA-в-main (`verify_merged_to_main`, ADR-0014) доводит
до `done` без мусорного PR.
- **Конфиг**: `merge_retry_enabled` (kill-switch; `False` → one-shot, нулевая регрессия),
`merge_retry_max_attempts`, `merge_retry_backoff_base_s`, `merge_retry_backoff_max_s`
(env `ORCH_MERGE_RETRY_*`). Гард already-in-main — без отдельного флага (накрыт существующим
`merge_verify_autocreate_pr_enabled`).
Объём раската — реально только self-hosting (`merge_verify_applies`); на прочих репо мерж делает
LLM-deployer → изменение нейтрально.
## Последствия
- **+** Транзиент Gitea переживается автоматически → нет ложного HOLD / ручного домержа в автономном
конвейере; нет мусорных пустых PR; повтор финализатора идемпотентен.
- **+** Реальный конфликт → быстрый честный HOLD; защита ORCH-071/073 и SHA-в-main (ADR-0014) —
авторитетны и неизменны.
- **** Дефолт `mergeable==None → transient` может добавить ≤10 с до HOLD на реальном конфликте
(бюджет жёстко ограничен); один лишний `GET /pulls/{index}` в редком ambiguous-кейсе.
- **Откат:** `ORCH_MERGE_RETRY_ENABLED=false` → one-shot; `ORCH_MERGE_VERIFY_AUTOCREATE_PR_ENABLED=false`
→ отключает врезку `ensure_open_pr` с гардом. Полный откат — revert PR.
## Ссылки
- Детальный ADR: `docs/work-items/ORCH-093/06-adr/ADR-001-merge-transient-retry-and-already-in-main-guard.md`
- Лехатая: [adr-0006](adr-0006-merge-gate.md), [adr-0013](adr-0013-merge-verify-gate.md),
[adr-0014](adr-0014-merge-verify-sha-source-of-truth.md),
[adr-0016](adr-0016-ensure-open-pr-before-merge-verify.md)
- Код: `src/merge_gate.py`, `src/stage_engine.py::_handle_merge_verify`, `src/config.py`

View File

@@ -0,0 +1,96 @@
---
work_item: ORCH-094
stage: architecture
author_agent: architect
status: proposed
created_at: 2026-06-09
model_used: claude-opus-4-8
---
# adr-0028: Terminal-window-aware гард выставления deploy-фазовых статусов Plane
Сквозной (cross-cutting) ADR. **Амендмент** к [adr-0010](adr-0010-post-deploy-monitor.md)
(post-deploy monitor, ORCH-021) и Plane-статусной модели (ORCH-066): вводит инвариант
«deploy-фазовые Plane-статусы — terminal-window-aware» поверх общих сеттеров `plane_sync` и
переупорядочивает блок `next_stage == "done"` в `advance_stage`. Детальное решение задачи —
`docs/work-items/ORCH-094/06-adr/ADR-001-terminal-window-aware-deploy-status-guard.md`.
> Регистрируется как сквозной, т.к. правит **общие** сеттеры `set_issue_awaiting_deploy`/
> `set_issue_deploying`/`set_issue_monitoring` (используются системно) и трогает маркированный блок с
> `ORCH-021`/`ORCH-066` (`docs/_standards/TRACEABILITY.md`).
## Статус
Proposed
## Контекст
Терминальная (`done`) задача в Plane **не держит `Done`**: непрерывный флапп
`Awaiting Deploy ⟷ Monitoring after Deploy` (верифицировано живьём на **ORCH-061**, task 47, done с
07.06 — 273 активности, само не затихает). Установлено по коду/логам/БД прода:
- Три code-писателя deploy-фазовых статусов (`src/stage_engine.py:404/1218/1316`) делегируют в тонкие
сеттеры `src/plane_sync.py`, которые **БД-стадию не читают** ⇒ терминал-слепы: любой повторный вызов
перезаписывает `Done` обратно на промежуточный статус.
- **Ordering:** `update_task_stage("done")` (`stage_engine.py:369`) пишет `tasks.stage='done'`
**раньше** легитимного `set_issue_monitoring` (стр. 404) ⇒ пост-деплой-окно ORCH-021 — by-design
индикация поверх уже-`done` задачи. Наивный гард «stage==done → Done» ⇒ регресс легитимного окна.
- Актор всех 273 переходов — бот-токен орка (`daf4d3f4-…`), не привязан к активной task/job; в БД нет
активного post-deploy-monitor для task 47 (окно 15 мин закрыто). Реконсилятор F-1 пропускает
`done`/`cancelled`, F-2 опрашивает только `[to_analyse, approved, rejected]` ⇒ механизма привести
застрявшую на deploy-статусе done-задачу к `Done` нет.
## Решение
**Единый terminal-window-aware гард на низком чокпоинте** — на входе трёх deploy-фазовых сеттеров
`plane_sync`. Чистую логику держит **новый leaf-модуль `src/deploy_status_guard.py`** (never-raise,
config-gated; образец `serial_gate.py`/`labels.py`/`cancel.py`); сеттеры исполняют вердикт.
- **Инвариант легитимности:** deploy-фазовый статус легитимен ⇔ задача **нетерминальна** ИЛИ
(`done` **И** активно пост-деплой-окно). Иначе — идемпотентное схождение к `Done`.
`decide(work_item_id, target) -> ALLOW | CONVERGE_DONE | SUPPRESS`:
kill-switch off / чужой issue / не-self репо / нетерминал → **ALLOW**; `cancelled`**SUPPRESS**;
`done` + `target==monitoring` + `window_active`**ALLOW**; `done` иначе → **CONVERGE_DONE**
(`set_issue_done`, идемпотентно); любое исключение → **ALLOW** + warning (never-raise).
- **Новый helper** `post_deploy.window_active(repo, wi)` = `has_marker(ARMED) and not
has_marker(DONE)` (restart-safe).
- **Перенос арм-блока** (`post_deploy.arm_monitor`) **перед** terminal-sync в блоке
`next_stage == "done"`: на стр. 404 `ARMED` уже записан ⇒ `window_active==True` ⇒ легитимный первый
`Monitoring` проходит; re-drive после закрытия окна сходится к `Done`.
- **Харднинг монитора:** идемпотентный страж `has_marker(...DONE)` (ранний return без PATCH/реэнкью)
+ тик no-op при `cancelled` мид-окно; тики привязаны к активному job'у (нет job → нет тика).
- **Наблюдаемость:** каждый вердикт логируется (`work_item`/`caller`/`target`/`db_stage`/
`window_active`/вердикт); подавление/схождение — явно.
- **Флаги** (`config.py`): `deploy_status_guard_enabled=True`
(`ORCH_DEPLOY_STATUS_GUARD_ENABLED`, kill-switch → 1:1) + `deploy_status_guard_repos=""`
(`ORCH_DEPLOY_STATUS_GUARD_REPOS`, пусто → self-hosting only) с локальным `applies(repo)`.
## Альтернативы
- **Гард в caller'ах `stage_engine`** — отвергнуто: не ловит неизвестный/стейл путь под бот-токеном,
размазывает инвариант.
- **Наивный «stage==done → Done» без предиката окна** — отвергнуто: регресс легитимного `Monitoring`.
- **Bypass-флаг на доверенном вызове 404** — отвергнуто в пользу переноса арм-блока (один предикат).
- **Активная сходимость в реконсиляторе F-2** — отвергнуто как основной механизм (лишний polling,
правка маркированного F-2); гард на сеттере гасит непрерывный флапп.
## Последствия
- Терминальная задача стабильно держит `Done`; маятник гаснет за один цикл независимо от актора.
- Легитимный пост-деплой `Monitoring` и рабочий self-deploy-цикл — 1:1 (предикат окна + перенос арм).
- `STAGE_TRANSITIONS` / `QG_CHECKS` / `check_*` / machine-verdict ключи / схема БД — **не тронуты**.
- `main`/force-push/прод-контейнер/detached-деплой — не тронуты; не-self репо инертны.
- Ограничение: если актор флаппа — внешняя Plane-automation (вне кода орка), гард — буфер на стороне
орка; локализация (FR-1) и итог документируются (BR-7).
- **Откат:** `ORCH_DEPLOY_STATUS_GUARD_ENABLED=false` → поведение 1:1; полный — revert ветки.
## Связи
- [adr-0010](adr-0010-post-deploy-monitor.md) (ORCH-021 — пост-деплой-окно, sentinel `armed`/`done`,
арм-блок) — амендмент: окно становится предикатом легитимности `Monitoring`.
- ORCH-066 (Plane-статусная модель — слой B индикации; `deploy→done` self ⇒ `Monitoring`) — инвариант
сохранён.
- [adr-0026](adr-0026-stop-cancel-task.md) (ORCH-090 — терминал `cancelled`) — гард не штампует
deploy-статус поверх `cancelled`.
- ORCH-068/086 (терминал-скип реконсилятора) — этот ADR распространяет идею терминал-aware на
выставление deploy-статусов.
- Детально: `docs/work-items/ORCH-094/06-adr/ADR-001-terminal-window-aware-deploy-status-guard.md`.

View File

@@ -0,0 +1,92 @@
---
work_item: ORCH-027
stage: architecture
author_agent: architect
status: proposed
created_at: 2026-06-10
model_used: claude-opus-4-8
---
# adr-0029: Гейт покрытия тестами — edge sub-gate + ratchet-базовая линия
- **Статус:** proposed
- **Дата:** 2026-06-10
- **Задача:** ORCH-027
- **Детальный ADR:** `docs/work-items/ORCH-027/06-adr/ADR-001-coverage-gate.md`
## Контекст
Оркестратор автономен: `developer` пишет код без человека-фильтра, `tester` сам решает, хватает
ли тестов. Существующие тестовые гейты судят только по факту прохождения, не по полноте:
`check_ci_green` (exit-code CI), `check_tests_passed` (LLM-вердикт `tester`'а), merge-gate
re-test (exit-code). Ни один не замечает «300 строк кода, 0 тестов». При пакетном автономном
прогоне (ORCH-088) это монотонная деградация покрытия. Нужна детерминированная метрика — по духу
как security-гейт (adr-0012).
## Решение
Детерминированный (без LLM) **гейт покрытия как под-гейт ребра `deploy-staging → deploy`**,
рядом с security-gate (ORCH-022), merge-gate (ORCH-043), image-freshness (ORCH-058). Паттерн —
leaf-модуль `src/coverage_gate.py` (never-raise) + обёртка в `QG_CHECKS` (`check_coverage_gate`)
+ врезка `_handle_coverage_gate` в `advance_stage`. `STAGE_TRANSITIONS` не меняется.
- **Порядок: security → merge → `coverage` → image-freshness.** Coverage идёт **ПОСЛЕ
merge-gate** (ветка догнана на свежий `origin/main` → меряем покрытие того кода, что landed) и
**ДО image-freshness** (фейлить дёшево до docker-rebuild). На этой точке merge-lease **held**
**FAIL обязан освободить lease** при откате (как image-freshness rollback; в отличие от
security, который идёт до захвата lease).
- **Измеритель:** `pytest-cov` (`coverage.py`), `python -m pytest tests/ --cov=src
--cov-report=json` в изолированном worktree (`ensure_worktree`); метрика —
`totals.percent_covered`. Тайм-аут `coverage_run_timeout_s`. Скоуп — `src/` (не тесты).
- **Чистая функция** `compute_coverage_verdict(measured, baseline, floor, policy, epsilon)`:
`absolute` (≥floorε), `baseline` (≥baselineε, ratchet), `both` (дефолт). `baseline=None` →
bootstrap (только absolute). FAIL → откат на `development` + developer-retry (cap
`MAX_DEVELOPER_RETRIES`), дословный reason в `task_desc` (ORCH-046).
- **Базовая линия — аддитивная БД-таблица** `coverage_baseline(repo PK, coverage, source_sha,
updated_at)` (`CREATE TABLE IF NOT EXISTS`, паттерн `repo_freeze`/`job_deps`). Выбор БД над
файлом-в-репо: нет git-churn/конфликтов на ratchet, restart-safe, атомарное обновление.
- **Ratchet-up** в choke-point подтверждённого merge `_handle_merge_verify` (ребро
`deploy → done`, ORCH-071/073): читает измеренное покрытие из `18-coverage-report.md`,
атомарный compare-and-set `UPDATE ... WHERE coverage <= measured` (базовая линия не падает).
Под held merge-lease + per-repo сериализацией merge (ORCH-043) — двойная анти-гонка.
- **Артефакт `18-coverage-report.md`** с frontmatter `coverage_status: PASS|FAIL` (+
`measured_coverage`/`baseline`/`floor`/`policy`/`delta` + аддитивная 52c-схема); вердикт
читается ТОЛЬКО из frontmatter через `src/frontmatter.py` (single source of truth).
- **Условность (как ORCH-35/43/58):** `coverage_gate_enabled` + `coverage_gate_repos` (пусто →
только self-hosting `orchestrator`); вне области → no-op pass. `applies(repo)` ПЕРВОЙ, дорогой
прогон — только при applies.
- **Ошибка инструмента → fail-open + WARNING** по умолчанию (`coverage_tool_fail_closed=False`,
анти-петля как ORCH-061); флаг → fail-closed.
- **Наблюдаемость:** read-only блок `coverage` в `GET /queue`; FAIL → Telegram (кликабельный
номер, измеренное/порог/дельта). Опциональный `POST /coverage/baseline` (ручной override).
- **never-raise**, гейт не деплоит/не рестартит прод/не пушит в `main` (NFR-3).
## Альтернативы
- **CI-job (`check_ci_green`):** пороги/политика/baseline/артефакт плохо выражаются статусом
коммита; ratchet требует записи в БД. Отклонено для v1 (точка расширения).
- **Edge `testing → deploy-staging`:** ветка не догнана на свежий `main` → метрика неточна;
откат не освобождает lease. Отклонено.
- **Базовая линия в файле репо:** git-churn/конфликты на каждый ratchet. Отклонено.
- **Новая стадия `coverage`:** «пустая» стадия без агента не имеет триггера (как ORCH-043/022).
Отклонено.
- **Жёсткий absolute-порог без baseline/epsilon:** массовые ложные заворота. Отклонено.
## Последствия
- Класс «тихо просевшее покрытие» закрыт детерминированной метрикой; baseline только растёт.
- Нулевая регрессия вне области (enduro-trails); `STAGE_TRANSITIONS`/`QG_CHECKS`-семантика/
вердикт-ключи (`verdict:`/`result:`/`deploy_status:`/`staging_status:`/`security_status:`) —
байт-в-байт прежние; новая БД-таблица аддитивна.
- Плата: ещё один «скрытый» под-гейт ребра; новая pip-зависимость (`pytest-cov`); доп. прогон
pytest (после merge-gate re-test, ограничен таймаутом, фейлит до rebuild); v1 — Python-only.
- Дефолтный fail-open тихо пропускает при устойчивом сбое инструмента (с WARNING) —
переключаемо `coverage_tool_fail_closed`.
- Сквозное изменение (новый QG + edge-под-гейт + новая таблица + новый артефакт) →
`arch:major-change`; прод-деплой строго через staging-гейт (8501), без рестарта прод-контейнера.
- **Откат:** `coverage_gate_enabled=False` → полный no-op (мгновенный обратимый kill-switch).
## Связи
adr-0012 (security-гейт — паттерн edge-под-гейта/leaf/never-raise/fail-open), adr-0006
(merge-gate — edge-под-гейт/откат/merge-lease), adr-0008 (image-freshness — условность/
fail-closed/release-lease-on-rollback), adr-0003 (условный гейт / `is_self_hosting_repo`),
adr-0009 (анти-петля ложных FAIL, ORCH-061), adr-0013/adr-0014 (merge-verify / SHA-in-main как
source of truth — точка ratchet), adr-0015/adr-0017 (per-repo сериализация merge/serial-gate),
adr-0020 (frontmatter-контракт — парсинг `coverage_status:`), adr-0019 (PIPELINE_DOCS — артефакт
`18-coverage-report.md`), ORCH-9/15 (мульти-стек — будущая зависимость BR-6).

View File

@@ -0,0 +1,88 @@
---
work_item: ORCH-099
stage: architecture
author_agent: architect
status: proposed
created_at: 2026-06-10
model_used: claude-opus-4-8
---
# adr-0030: Лёгкий read-only `/metrics` — сырьё о самом орке для sidecar (F1b)
- **Статус:** proposed
- **Дата:** 2026-06-10
- **Задача:** ORCH-099 (FND/F1a)
- **Детальный ADR:** `docs/work-items/ORCH-099/06-adr/ADR-001-metrics-endpoint.md`
## Контекст
Эпик автономного саморазвития, домен 0 «Фундамент». Рамка наблюдаемости (заказчик): **наблюдатель
отделён от наблюдаемого** — мозг мониторинга (пороги/алерты/история/Telegram) живёт в отдельном
sidecar-контейнере **F1b** (`watchdog/`), а орк отдаёт **только сырьё**, которое знает лишь он сам.
Сегодня такого источника нет: `/health` = `{"status":"ok"}`, `/status` = активные задачи, `/queue`
«человеческий» снимок, перемешанный с конфигом демонов. Нет стабильного машинного контракта для
детекта застрявшей стадии / зависшего агента / деградации очереди / всплеска стоимости. F1b
заблокирована этой задачей. Self-hosting: прод общий с enduro-trails ⇒ эндпоинт обязан быть строго
read-only и never-raise.
## Решение
Новый **leaf-модуль** `src/metrics.py` (`build_metrics() -> dict`, чистый, never-raise по разделам —
паттерн `serial_gate.snapshot()`) + тонкий эндпоинт `@app.get("/metrics")` в `src/main.py` (стиль
`GET /queue`). Только чтение существующих таблиц (`tasks`/`jobs`/`agent_runs`) и in-memory-снапшотов
+ два read-only helper'а в `src/db.py`. `STAGE_TRANSITIONS`/`QG_CHECKS`/`check_*`/machine-verdict-
ключи/схема БД — **не трогаются**.
- **Конверт + контракт версии:** `schema_version` (старт `1`), `generated_at` (UTC ISO-8601 —
момент снимка, домен часов орка), `clk_tck` (`os.sysconf("SC_CLK_TCK")`), разделы
`stages`/`queue`/`agents`/`cost`. **Политика версии:** аддитивные изменения НЕ бампят (sidecar
обязан игнорировать незнакомые ключи и толерировать отсутствие опциональных); бамп — только при
ломающем (rename/remove/retype). Forward-compatible контракт для F1b.
- **`stages`** — `db.get_active_tasks_for_reconcile()` + фильтр `stage NOT IN ('done','cancelled')`
на слое metrics (helper намеренно отдаёт `cancelled` для ORCH-086 — не трогаем его инвариант);
поля `work_item`/`stage`/`age_in_stage_s`/`repo`.
- **`queue`** — `db.job_status_counts()` (+`cancelled`), глубина, сырьё ретраев
(`attempts`/`max_attempts`/`transient_attempts`/в-backoff), `worker.breaker.snapshot()`,
`max_concurrency`. Недоступный worker → `breaker: null`, не 500.
- **`agents` (liveness)** — новый dedicated read-only helper `db.get_running_agents()` (НЕ расширение
hot-path `get_running_jobs()` reaper'а, ORCH-065): `agent`/`run_id`/`job_id`/`pid`/`runtime_s`
(= `running_age_s` от `jobs.started_at`)/`model`/`effort`. CPU-сырьё — **вариант A**: орк читает
`/proc/<pid>/stat` (поля 14+15, utime+stime) → `cpu_ticks`; **дельту не считает** — арбитр
«жив/завис» это sidecar (stateless-эмиссия). `pid is None`/мёртвый/нет `/proc`/не-Linux →
`cpu_ticks: null`, не ошибка.
- **`cost`** — `running` (по running-job, часто `null` до завершения — честное сырьё, `null` ≠ ноль)
+ `aggregate` (новый helper `db.agent_cost_totals()`, `COALESCE(SUM(...),0)` по
`cost_usd`/`input_tokens`/`output_tokens`/`cache_read_tokens`/`cache_creation_tokens`).
- **Kill-switch** `metrics_endpoint_enabled` (env `ORCH_METRICS_ENABLED`, дефолт `True`): при `False`
`200` с `{"schema_version":1,"enabled":false}` (контракт остаётся парсимым). Операторский
off-switch на общем инстансе.
- **Never-raise:** каждый раздел — свой `try/except` + `logger.warning` + дефолт (`null`/`[]`/`{}`);
`build_metrics()` никогда не пробрасывает. Read-only: ни одного `INSERT/UPDATE/DELETE/CREATE/ALTER`.
## Альтернативы
- **Расширить `/queue`** — отклонено: ломает байт-в-байт контракт (BR-6) + смешивает сырьё с
человеческим снимком.
- **Prometheus/OpenMetrics** — отклонено: заказчик задал тонкий кастомный sidecar (не Prometheus),
контракт — JSON.
- **Орк считает CPU-дельту сам** — отклонено: требует состояния; stateful-арбитр это sidecar (C-1).
- **Расширить SELECT `get_running_jobs()`** — отклонено: перенос инварианта hot-path reaper'а;
изолируем dedicated helper.
- **Push в sidecar** — отклонено: нарушает разделение C-1; зависший орк ⇒ pull падает = сам сигнал.
## Последствия
- F1b разблокирована стабильным машинным контрактом; домен наблюдаемости стартует.
- Строго read-only + never-raise ⇒ near-zero риск для общего прод-конвейера (enduro-trails);
`/health`/`/status`/`/queue` байт-в-байт; гейты/схема/machine-verdict-ключи не тронуты (NFR-5).
- `schema_version` + аддитивно-толерантная политика ⇒ расширения не ломают F1b.
- Плата: новая поверхность совместимости `/metrics`↔F1b (митигейшн — единый репо контракта + версия);
CPU-liveness Linux-специфичен (`/proc`; не-Linux → `null`). Топология/схема не меняются (sidecar и
его сетевая достижимость — объём F1b).
- Новый компонент + публичный контракт → `arch:major-change` (хоть и аддитивно/read-only/обратимо);
прод-деплой строго через staging-гейт (8501), без рестарта прод-контейнера.
- **Откат:** `metrics_endpoint_enabled=False` (мгновенный) или удаление модуля/эндпоинта/helper'ов —
без следов в БД/схеме.
## Связи
adr-0002 (job-queue/circuit-breaker — источник `queue`-сырья), adr-0011 (job-reaper —
`get_running_jobs`/pid/liveness-семантика, изоляция hot-path), adr-0026 (терминал `{done,cancelled}`
— фильтр `stages`), adr-0017 (serial_gate — паттерн leaf `snapshot()`/never-raise), adr-0020
(frontmatter-контракт — стиль версионируемого контракта). Прямой потребитель — **F1b** (sidecar
`watchdog/`, отдельная задача).

View File

@@ -0,0 +1,92 @@
---
work_item: ORCH-057
stage: architecture
author_agent: architect
status: proposed
created_at: 2026-06-10
model_used: claude-opus-4-8
---
# adr-0031: Нормализация legacy root-owned файлов при миграции uid — детект-leaf + actionable worktree-ошибка
- **Статус:** proposed
- **Дата:** 2026-06-10
- **Задача:** ORCH-057 (follow-up ORCH-040)
- **Детальный ADR:** `docs/work-items/ORCH-057/06-adr/ADR-001-legacy-ownership-normalization.md`
## Контекст
ORCH-040 перевёл контейнеры на `user: "1000:1000"`, изменив только `docker-compose.yml`. Владельца
уже существующих `root:root` файлов в bind-mount `/repos` это не меняет. Под uid 1000
`src/git_worktree.py::ensure_worktree` (`os.makedirs` стр. 78 / `git worktree add` стр. 81/85) не может
создать worktree рядом с root-owned `/repos/_wt/``fatal: could not create leading directories …
Permission denied`, который сейчас пробрасывается сырым. Конвейер приходит сюда из
`launcher._spawn`/`_materialize_deferred_branch` (ORCH-088) — **агент не стартует** (launch-time
инфра-сбой, не код задачи). Инцидент 06.06 на проде (первый запуск ORCH-043); workaround Стрима
(`chown -R 1000:1000`) наложен вручную. ADR-040 описал нормализацию абстрактно («вне объёма кода») и
не дал процедуры → баг воспроизводим на чистой среде / новом репо / после исторического запуска под
root. Контейнер бежит **без root** → код физически не может `chown` чужие файлы; ему доступны лишь
детект + диагностика.
## Решение
Три аддитивных, обратимых kill-switch'ем слоя — паттерн условного leaf-гейта (`coverage_gate`/
`serial_gate`) + best-effort startup-хук (`main.lifespan`, как lease-reclaim). `STAGE_TRANSITIONS` /
`QG_CHECKS` / `check_*` / machine-verdict-ключи (`verdict:`/`result:`/`deploy_status:`/
`staging_status:`/`security_status:`/`coverage_status:`) / схема БД — **байт-в-байт прежние**.
- **Actionable worktree-ошибка (D1):** `ensure_worktree` классифицирует класс «нет прав» (маркеры
`Permission denied`/`could not create leading directories`/`insufficient permission`/`EACCES`/
`EPERM`) и поднимает `RuntimeError` с причиной (legacy root-файлы после миграции uid) + лечащей
командой + ссылкой на INFRA.md. Не-прав-ошибки сохраняют прежний текст/смысл (никакой подмены).
Меняется лишь **формулировка**, не факт сбоя.
- **Детект-leaf `src/fs_normalize.py` (D2):** чистый, never-raise, TTL-кэш (паттерн `preflight`).
`scan_ownership(roots, target_uid)` обходит `/repos/_wt`, `<repo>/.git/objects`,
`<repo>/.git/worktrees`, `data/runs`; ранний выход при первом `st_uid != target_uid`
(`target_uid=os.getuid()` по умолчанию). `applies(repo)` (kill-switch + scope; пусто →
`is_self_hosting_repo`) проверяется ПЕРВЫМ → дорогой обход только при applies. Идемпотентно;
ошибка обхода → WARNING + консервативный `mismatch=False`.
- **Интеграция = наблюдаемость, без блокировки claim (D3):** best-effort `scan_ownership()` на старте
`main.lifespan` → WARNING + Telegram при mismatch. Claim НЕ гейтится: внятный ранний отказ даёт D1
в точке launch (знает repo, агент ещё не тратил токены). Блокирующий preflight-гейт отвергнут —
preflight не знает repo, заблокировал бы и enduro-trails на общем `/repos`.
- **Опц. `normalize()` (D4):** chown только при `CAP_CHOWN`/root (под uid 1000 — no-op + лог),
флаг `fs_normalize_auto` (дефолт `False`). Init-контейнер/root-entrypoint отвергнут: реинтродукция
root-контекста (анти-цель ORCH-040) + правка compose = self-deploy/групповой риск. Реальную
нормализацию несёт операторская процедура.
- **Процедура (D5):** `INFRA.md` получает раздел «Миграция uid: обязательная нормализация legacy
root-файлов» (точные команды по всем корням) как обязательный шаг миграции; forward-breadcrumb из
ADR-040.
- **Флаги:** `fs_normalize_enabled` (kill-switch, дефолт `True`), `fs_normalize_repos` (CSV, пусто →
self-hosting only), `fs_target_uid` (1000), `fs_normalize_auto` (`False`), `fs_scan_roots`,
`fs_scan_cache_ttl_s` (300). Наблюдаемость — блок `fs_ownership` в `GET /queue`; опц. `POST
/fs-normalize/check`.
## Альтернативы
- **Init-контейнер/root-entrypoint** — реинтродукция root (анти-цель ORCH-040), self-deploy compose,
групповой риск ради разовой операции. Отвергнуто; носитель нормализации — операторская процедура.
- **Блокирующий claim-гейт (preflight)** — preflight не знает repo → регресс enduro на общем `/repos`.
Отвергнуто.
- **Блокирующий claim-гейт (queue_worker/claim)** — дорогой FS-обход в hot-path + «молчаливое
зависание» вместо диагноза D1. Отвергнуто.
- **Авто-chown из app по умолчанию** — под uid 1000 невозможен; ложное ожидание самолечения.
Отвергнуто (оставлен opt-in `fs_normalize_auto`).
- **Hard-fail старта при mismatch** — нарушает never-raise, стопорит сервис всех проектов. Отвергнуто.
## Последствия
- Класс «сырой git-fatal на launch после миграции uid» закрыт внятным диагнозом (D1) + проактивным
startup-сигналом (D3); пробел процедуры ADR-040 закрыт (INFRA.md).
- Нулевая регрессия enduro-trails (scope first); инварианты конвейера/схема БД — байт-в-байт.
- Никакого root-контекста/рестарта прода/касания `main`/force-push/прод-образа (NFR-1).
- Плата: фактический `chown` остаётся ручным операторским шагом (но теперь внятным, с инструкцией);
+1 best-effort startup-хук и leaf-модуль; `fs_normalize_auto=True` под root реинтродуцирует
chown-контекст (дефолт `False`, не для прод-self).
- Аддитивно/обратимо: **не** `arch:major-change` (нет новой стадии/QG/таблицы/смены топологии) — leaf
+ startup-хук + docs.
- **Откат:** `fs_normalize_enabled=False` → полный no-op (мгновенный обратимый kill-switch).
## Связи
adr-0005 (контейнер под host-uid — порождающее решение ORCH-040, чей пробел закрываем),
adr-0029/adr-0012 (coverage/security-гейт — паттерн условного leaf `applies`/scope/never-raise/
fail-open), adr-0017 (serial-gate — leaf never-raise + отложенный срез ветки `_materialize_deferred_
branch`, чья точка падает в `ensure_worktree`), adr-0011 (job-reaper — образец best-effort
startup-хука в `lifespan`), adr-0024 (disk-watchdog — образец «только читать/уведомлять, не трогать
хост/прод»).

View File

@@ -0,0 +1,95 @@
---
work_item: ORCH-019
stage: architecture
author_agent: architect
status: proposed
created_at: 2026-06-10
model_used: claude-opus-4-8
---
# adr-0032: Багфикс-трек — укороченный маршрут конвейера для багов (ORCH-019)
## Статус
Proposed
## Контекст
Любая задача идёт по полному конвейеру `analysis → architecture → development → review → testing
→ deploy-staging → deploy → done`. Для мелкого бага стадия `architecture` (отдельный прогон
opus-агента `architect` + ADR + exit-гейт `check_architecture_done`) избыточна и тратит
токены/время (прецедент ET-9/ET-014 ~35 мин).
**Корневой инвариант (нерушимый):** упрощаем только *аналитику/архитектуру*; ни один Quality
Gate / под-гейт (security/merge/coverage/image-freshness) / exit-код deploy-хука — НЕ ослаблен
(урок ET-8: срезанная проверка = недоделка на проде).
Кросс-каттинговость: затрагивает семантику маршрутизации (`advance_stage`), вводит новый
leaf-компонент `src/bug_fast_track.py` и аддитивную колонку `tasks.track` → регистрируется
сквозным ADR.
## Решение
Багфикс-трек — **свойство планировщика/точки входа, НЕ Quality Gate**.
1. **Классификация** (`src/bug_fast_track.py`, leaf never-raise по образцу `serial_gate`/`labels`):
задача с меткой Plane `Bug` (`bug_fast_track_label`, читается аппаратом ORCH-089
`labels.has_label`) помечается `track='bug'`. `applies(repo)` (локально, без сети) — первым;
`has_label` (сеть) — только при `applies==True`; чтение метки **только** в `start_pipeline`,
никогда в горячем `claim_next_job` (anti-stall).
2. **Хранение** — аддитивная идемпотентная колонка `tasks.track TEXT DEFAULT 'full'`
(`_ensure_column`, паттерн `tasks.cancelled_at` ORCH-090); читается в `advance_stage` из БД
(не из сети).
3. **Routing-override**`STAGE_TRANSITIONS` и `get_next_stage`/`get_agent_for_stage` остаются
**чистыми** (1:1). В `advance_stage`, на ребре выхода из `analysis`, при `track='bug'`:
`next_stage``development` (вместо `architecture`), `next_agent``developer` (вместо
`architect`). Багфикс физически минует стадию `architecture` → её exit-гейт
`check_architecture_done` и `06-adr/` для багфикса не исполняются.
4. **Гейт `analysis` не трогаем**`check_analysis_complete`/`check_analysis_approved` байт-в-байт
прежние; lite-аналитик эмитит все 4 файла (01-bug-report / 02-03 краткие заглушки / 04 план
обязательного регресс-теста). Экономия — пропуск всей стадии `architecture`, не число файлов.
5. **Эскалация** (обратимость) — `POST /bug-fast-track/escalate?work_item=<id>` сбрасывает
`track→'full'` (+ self-escalate мини-аналитика); задача далее идёт через `architecture`.
6. **Условность/откат**`bug_fast_track_enabled` (kill-switch), `bug_fast_track_label`,
`bug_fast_track_repos` (CSV; **пусто → self-hosting only**). `False`/неприменимый репо →
путь старта и маршрут **байт-в-байт** прежние.
7. **Наблюдаемость** — read-only блок `bug_fast_track` в `GET /queue` (флаг/область/метка +
счётчик `track='bug'` + метрика экономии из `agent_runs`); лог на решение о маршруте; опц.
`🐞` в Telegram-карточке.
## Кросс-каттинговые инварианты (НЕ нарушаются)
- `STAGE_TRANSITIONS` структурно не меняется (нет новых/удалённых стадий); `cancelled`/`done`
стоки и предикаты терминальности (ORCH-090) не затронуты.
- Реестр `QG_CHECKS`, сигнатуры `check_*`, вердикт-ключи (`verdict:`/`result:`/`deploy_status:`/
`staging_status:`/`security_status:`/`coverage_status:`), порядок под-гейтов — байт-в-байт.
- Врезка ORCH-019 в `advance_stage` — ТОЛЬКО на ребре выхода из `analysis`, ДО всех deploy-edge
под-гейтов (ORCH-022/043/027/058) и Phase A/B (ORCH-036/059) → их инварианты сохранены.
- Композиция с serial-gate (ORCH-088), auto-label (ORCH-089), coverage-gate (ORCH-027),
merge-gate (ORCH-043) — багфикс-задача остаётся обычной задачей репо.
## Последствия
- **+** Багфикс минует стадию `architecture` (основная экономия), гейты качества сохранены.
- **+** Аддитивно, под kill-switch, per-repo, never-raise, fail-safe → полный цикл; нулевая
регрессия для enduro и orchestrator при выключении.
- **** lite-аналитик эмитит 02/03 заглушки (компромисс ради неизменности гейта); эскалация v1
требует операторского действия (авто-триаж сложности — будущее, ORCH-13/Вариант 3).
- **Откат:** `bug_fast_track_enabled=False` (мгновенно); колонка `tasks.track` аддитивна и
безвредна (дефолт `'full'`).
## Связанные решения
- ORCH-089 (auto-label) — переиспользуемый аппарат label-чтения: [adr-0018](adr-0018-auto-label-gates.md)
- ORCH-088 (serial gate) — композиция очереди репо
- ORCH-027 (coverage-gate) — структурный союзник BR-4: [adr-0029](adr-0029-coverage-gate.md)
- ORCH-090 (cancelled) — паттерн аддитивной колонки `tasks.*`: [adr-0026](adr-0026-stop-cancel-task.md)
## Ссылки
- Детальный ADR задачи: `docs/work-items/ORCH-019/06-adr/ADR-001-bug-fast-track.md`
- BRD/TRZ/AC: `docs/work-items/ORCH-019/01-brd.md`, `02-trz.md`, `03-acceptance-criteria.md`
</content>

View File

@@ -0,0 +1,85 @@
---
work_item: ORCH-100
stage: architecture
author_agent: architect
status: proposed
created_at: 2026-06-10
model_used: claude-opus-4-8
---
# adr-0033: Sidecar-watchdog F1b — мозг мониторинга в отдельном контейнере
- **Статус:** proposed
- **Дата:** 2026-06-10
- **Задача:** ORCH-100 (FND/F1b)
- **Детальный ADR:** `docs/work-items/ORCH-100/06-adr/ADR-001-sidecar-watchdog.md`
- **Парный ADR:** `adr-0030` (F1a `/metrics` — источник сырья)
## Контекст
Домен 0 «Фундамент» эпика автономного саморазвития, рамка наблюдаемости заказчика: **наблюдатель
отделён от наблюдаемого**. F1a (adr-0030) отдаёт read-only `GET /metrics`**только сырьё**. F1b —
**мозг**: читает сырьё, дополняет внешними сигналами (хост/контейнеры/зависимости), решает по порогам,
алертит. Частичные стражи (`disk_watchdog`/`reaper`/`reconciler`) живут ВНУТРИ процесса орка — орк
завис/упал ⇒ они мертвы, платформа слепа в критический момент. Рамки: C-1 (отдельный контейнер, код в
`watchdog/`), C-2 (без внешнего плеча — принятый риск), C-3 (тонкий стек, НЕ Grafana/Prometheus; хост
впритык). Критический инвариант: орк лёг ⇒ `/metrics` недоступен = **сам сигнал тревоги**.
## Решение
Новая папка `watchdog/`**тонкий Python-3.12-stdlib демон** (без сторонних зависимостей), отдельный
образ `watchdog/Dockerfile` + сервис `orchestrator-watchdog` в `docker-compose.yml` (`network_mode:
host`, read-only `docker.sock`, `mem_limit: 128m`, `restart: unless-stopped`). Тик: (1) `GET /metrics`;
(2) хост (диск/inode/память/CPU, stdlib); (3) статусы контейнеров через read-only `docker.sock`
(GET-only — без `docker` SDK); (4) пинг Plane/Gitea/Anthropic. Сигналы проходят через **обобщённую
чистую** `decide(signal_active, prev, now, cooldown) -> alert|realert|recovery|none` (генерализация
`disk_watchdog.decide_action`; per-signal in-memory `AlertState`). Алерт — в **собственный** Telegram-
канал sidecar (свои `WATCHDOG_TG_*`; **НЕ** импорт `src/notifications.py`). Особый сигнал — `/metrics`
не отвечает → `orch_down`. Всё never-raise (per-source/per-tick/per-send), под kill-switch
`WATCHDOG_ENABLED`, строго read-only к наблюдаемому. **`src/**`/`STAGE_TRANSITIONS`/`QG_CHECKS`/
`check_*`/схема БД орка — не тронуты** (F1b вне процесса орка и вне конвейера QG).
- **Стек** — Python stdlib (`urllib`, `socket`+`http.client` для docker.sock, `shutil.disk_usage`,
`/proc/meminfo`); pytest на чистые функции. Отвергнуты Go / `docker` SDK / Prometheus (C-3).
- **Реестр сигналов** — `orch_down` (K подряд неудачных опросов), `host_mem`/`host_disk_crit`,
`agent_hung``cpu_ticks`/`clk_tck``generated_at` < floor при растущем `runtime_s`; нужно 2
опроса — sidecar stateful-арбитр), `stage_stuck` (`age_in_stage_s`), `job_failed` (edge),
`queue_depth`, `container_down` (per name), `dep_down` (per name). Пороги/интервалы/URL — из env.
- **Владелец диск-алерта (BR-10)** — штатные 85% остаются за внутренним `disk_watchdog` (ORCH-063,
канал орка) ⇒ **нулевой дубль по построению**; sidecar покрывает провал «орк+disk_watchdog мертвы»
через `orch_down`, плюс **opt-in** (default off) независимый критический потолок `host_disk_crit`
(97%) — другое событие/канал, не повтор 85%.
- **Толерантность контракта** — неизвестные ключи `/metrics` игнорируются, отсутствие опционального не
ошибка, рост `schema_version` → warning (зеркало аддитивной политики adr-0030).
- **Kill-switch** `WATCHDOG_ENABLED=false` → демон инертен (idle-loop, не exit) ⇒ нулевой эффект.
## Альтернативы
- **Go / `docker` SDK / `requests`** — отклонено: вес/вторая цепочка против C-3 и консистентности с
`disk_watchdog`.
- **Prometheus/Grafana/TSDB** — отклонено: прямой запрет C-3.
- **Sidecar — единственный владелец диска** — отклонено: потеря покрытия, когда сам sidecar/Docker
недоступен; выбрана связка primary `disk_watchdog` + opt-in ceiling.
- **Push из орка в sidecar** — отклонено: зависший орк не пушит; pull падает = сам сигнал `orch_down`.
- **bridge + `host.docker.internal`** — отклонено: на Linux ненадёжно; `network_mode: host` проще.
- **Своя БД/файл порогов** — отклонено: C-3; in-memory best-effort достаточно (как `disk_watchdog`).
## Последствия
- Внешний мозг мониторинга переживает падение орка; `orch_down` делает наблюдателя громче в инцидент.
- Строго read-only + независимый канал + never-raise ⇒ self-hosting-безопасно (enduro не затронут);
падение sidecar не влияет на конвейер.
- Аддитивно/обратимо: `src/**`/гейты/схема байт-в-байт; kill-switch → нулевая регрессия; дубль диска
исключён структурно.
- Плата: новый контейнер на впритык-хосте (`mem_limit: 128m` + замер RSS на staging обязательны);
C-2 (падёт хост → молчит и sidecar); новая поверхность совместимости `/metrics`↔F1b (толерантный
парсинг + единый репо контракта); CPU-liveness Linux-специфичен.
- **Топология** меняется (новый контейнер) → `07-infra-requirements.md`; **схема БД** не меняется →
08 = N/A. Новый компонент + контейнер + канал → `arch:major-change`; прод-выкат через staging-гейт
(8501), деплой sidecar НЕ рестартит прод-контейнер.
- **Откат:** не запускать сервис / `WATCHDOG_ENABLED=false` (мгновенный) или удаление `watchdog/` +
сервиса + env — без следов в БД/схеме.
## Связи
adr-0030 (F1a `/metrics` — парный источник сырья; контракт `cpu_ticks`/`clk_tck`/`generated_at`/
`schema_version`), adr-0024 (`disk_watchdog` — образец решающей функции/never-raise + владелец
диск-алерта), adr-0025 (build-cache-pruner — паттерн «вторая половина»), adr-0017 (serial_gate —
leaf `snapshot()`/never-raise), adr-0011 (job-reaper — pid/liveness-семантика). Прямой источник —
**F1a** (`GET /metrics`); F1b — его потребитель.
</content>

View File

@@ -0,0 +1,92 @@
---
work_item: ORCH-098
stage: architecture
author_agent: architect
status: proposed
created_at: 2026-06-10
model_used: claude-opus-4-8
---
# adr-0034: Машинный журнал уроков — таблица `lessons` + observer-leaf (ORCH-098)
## Статус
Proposed
## Контекст
Оркестратор автономно ведёт задачи по конвейеру (ORCH-54), но **развивается** вручную: инциденты →
уроки → задачи. Уроки живут свободным текстом в `memory/` — не машиночитаемы: нельзя считать
паттерны, приоритизировать, предлагать улучшения. ORCH-098 — шаг 1 эпика саморазвития (домен 0
«Фундамент», F2): «топливо» петли самообучения 8A. Нужна **структурированная таблица отклонений
конвейера**, на которой позже встанут ретроспективщик (E2), приоритизатор RICE (E3) и Стрим.
Нормативное требование Славы (10.06): схема ДОЛЖНА **сразу** нести поля **атрибуции** урока
(`platform`/`project`/`both`/`unknown` + целевой репо + домен улучшения), иначе позже придётся
переделывать схему на живой общей прод-БД.
**Кросс-каттинговость** (почему сквозной ADR): новый компонент `src/lessons.py` + аддитивная
таблица на **общей прод-БД** (self-hosting, разделяемой с enduro-trails) + врезки автозаписи в
несколько горячих choke-point'ов (`stage_engine`/`merge_gate`/`launcher`) + новый раздел контракта
`GET /queue`. Фундамент для будущих задач-потребителей → регистрируется глобально.
## Решение
Журнал уроков — **observer (наблюдатель), НЕ Quality Gate**. Аддитивная таблица + чистый leaf,
по образцу `serial_gate`/`coverage_gate`/`metrics`/`bug_fast_track`.
1. **Таблица `lessons`** (`db.init_db()`, `CREATE TABLE IF NOT EXISTS` + 3 индекса, идемпотентно,
restart-safe) — поля контекста (`work_item_id`/`task_id`/`stage`/`agent`/`repo`), анализа
(`root_cause`/`suggestion`), статуса (`status`/`related_task`), **атрибуции сразу и нуллабельно**
(`attribution`/`target_repo`/`target_domain`) + `source`/`detail`. Без `enum`-констрейнтов
(слаги forward-compatible). Будущие колонки — `_ensure_column`.
2. **Leaf `src/lessons.py`** (never-raise, импортирует только `config`+`db`): `record()` / `get()` /
`update()` / `snapshot()`. **Расхождение с гейт-шаблоном: журнал НЕ скоупится по репо** — он
observer-only и не *действует* ни на один репо; единственный регулятор — глобальный kill-switch
`lessons_enabled`. Запись урока про enduro ценна и **не затрагивает** пайплайн enduro (чистая
память орка); репо-разрез — на выборке (`repo`-колонка/фильтр).
3. **Автозапись 4 типов** (`source="auto"`, best-effort, дедуп в окне; `transient_retry` — только на
исчерпании бюджета ретраев): `gate_failure` (`stage_engine._handle_qg_failure_rollbacks`),
`merge_hold` (`merge_gate._handle_merge_verify` HOLD), `transient_retry` (merge-retry/launcher
transient budget-exhaustion), `deploy_degraded` (post-deploy `DEGRADED → set_repo_freeze`, урок
слоя-3 «деплой OK / прод сломан», ET-8). Каждая врезка — одиночный вызов в защитном `try/except`.
4. **Эндпоинты** `GET /lessons` (read-only, фильтры), `POST /lessons` (ручная запись,
`source="manual"`), `POST /lessons/{id}` (update — доклассификация `unknown`), + read-only ключ
`"lessons": snapshot()` в `GET /queue`. При выключенном флаге → `{"enabled": false}`.
**Инвариант (нерушимый):** `STAGE_TRANSITIONS` / `QG_CHECKS` / `check_*` / machine-verdict-ключи
(`verdict:`/`result:`/`staging_status:`/`deploy_status:`/`security_status:`/`coverage_status:`) /
схемы существующих таблиц — **байт-в-байт не тронуты**. Журнал не влияет на продвижение по стадиям.
## Композиция с существующими механизмами
- **Self-hosting (общая БД):** аддитивная таблица; enduro не затронут (NFR-3).
- **serial-gate (ORCH-088) / post-deploy (ORCH-021):** детектор `deploy_degraded` врезан рядом с
`set_repo_freeze`, не меняя freeze-логику.
- **merge-gate (ORCH-043/071/093):** `merge_hold`/`transient_retry` читают исход актора, не меняя
классификатор/ретрай.
- **metrics (ORCH-099):** журнал — историческая память петли (best-effort запись), `/metrics`
realtime-сырьё для sidecar; разные роли, оба observer-only.
## Условность и откат
- Флаг `lessons_enabled` (env `ORCH_LESSONS_ENABLED`, дефолт `True`; kill-switch) +
`lessons_dedup_window_s` / `lessons_query_limit_default`. `False` → полная инертность, нулевая
регрессия, конвейер байт-в-байт прежний.
- **never-raise** на всех публичных функциях и врезках (NFR-1) — сбой журнала не роняет конвейер.
- Откат — флаг в `false` (мгновенно) или revert диффа; таблица не касается существующих.
## Последствия
- **+** Машиночитаемые уроки — фундамент E2/E3/Стрим; атрибуция forward-proof (без передела живой БД).
- **+** Нулевая регрессия; проверенный additive-observer-leaf шаблон → низкий риск; enduro изолирован.
- **** Рост таблицы (митигейшн: лёгкие строки + дедуп + budget-exhaustion; ретенция — будущее).
- **** Дедуп-запрос в `record()` (один indexed-SELECT, только `auto`).
## Ссылки
- Локальный ADR: `docs/work-items/ORCH-098/06-adr/ADR-001-lessons-journal.md`
- BRD/TRZ/AC: `docs/work-items/ORCH-098/01-brd.md`, `02-trz.md`, `03-acceptance-criteria.md`
- Data/Infra/Risks: `docs/work-items/ORCH-098/08-data-requirements.md`, `07-infra-requirements.md`,
`10-tech-risks.md`
- Эпик: `docs/epics/self-evolution.md` (домен 0 «Фундамент», F2; петля 8A)
- Сверено по коду: `src/serial_gate.py`, `src/coverage_gate.py`, `src/db.py`, `src/stage_engine.py`,
`src/merge_gate.py`, `src/agents/launcher.py`, `src/main.py`, `src/qg/checks.py`.

View File

@@ -0,0 +1,80 @@
---
work_item: ORCH-009
stage: architecture
author_agent: architect
status: proposed
created_at: 2026-06-10
model_used: claude-opus-4-8
---
# adr-0035: Turnkey-онбординг проектов — kit + операторский CLI + runbook (ORCH-009)
## Статус
Proposed
## Контекст
Подключение нового проекта к оркестратору — ручная археология по разрозненным докам и памяти;
каждый пропущенный шаг даёт **тихую деградацию**: без промптов в репо конвейер проекта не работает
вовсе (launcher резолвит `.openclaw/agents/<role>.md` относительно worktree репо задачи); без
точных имён статусов Plane ветки `Confirm Deploy` (ORCH-059) / `STOP` (ORCH-090) молча не
активируются (fail-closed); без лейблов `autoApprove`/`autoDeploy`/`Bug` авто-режимы (ORCH-089)
и багфикс-трек (ORCH-019) молча выключены (fail-safe). Эталон онбординга — **сам репозиторий
orchestrator** (каноны ORCH-52b/c/d/e кодифицированы в `docs/_templates/`, `docs/_standards/`,
`.openclaw/agents/`). Домен D5.2 эпика саморазвития: способность разворачивать новый проект
одним проходом.
## Решение
Способность реализуется **вне рантайма и вне конвейера**`src/**` байт-в-байт не меняется
(`STAGE_TRANSITIONS`/`QG_CHECKS`/`check_*`/machine-verdict/схема БД/контракт `projects.py`
нетронуты), kill-switch не нужен (активация — только явный запуск операторского CLI):
1. **Onboarding-kit `onboarding/repo-skeleton/`** — параметризуемый каркас нового репо:
6 промптов агентов канона 52d/92 (5 XML-секций, «❌→✅», эмиссия схемы 52c, verdict-ключи
байт-в-байт; язык — канон орка: 5 ru + deployer en), паспорт `CLAUDE.md`, `AGENTS.md`
(точка входа агентов), `CONTRIBUTING.md`, `README.md`, `CHANGELOG.md`, скелет `docs/` с
обязательным `operations/INFRA.md`, `.env.example`. Плейсхолдеры `{{NAME}}` + stdlib-рендер
(без новых pip-зависимостей); словарь — `onboarding/placeholders.json` (биекция со
вхождениями в kit держится тестами). **Канон не форкается:** `docs/_templates/` +
`docs/_standards/` НЕ хранятся в kit — копируются live из чекаута орка в момент материализации.
2. **Операторский CLI `scripts/onboard_project.py`**`plan` (дефолт, GET-only, ни одной
мутации) / `apply` (идемпотентный ensure, без delete-операций) / `verify`. Шаги: Plane-проект →
22 статуса с точными именами из `plane_sync._PLANE_NAME_TO_KEY` (read-only импорт — нулевой
дрейф; канонические группы фиксированы: `STOP``cancelled`, терминальные группы только у
Done/Cancelled/STOP — иначе terminal-detection ORCH-068 ложно терминалит) → лейблы → Gitea-репо
(+per-repo webhook `push`/`pull_request`/`status`; HMAC-секрет **переиспользуется** из
`ORCH_GITEA_WEBHOOK_SECRET` — приёмник один на все репо) → материализация kit + initial push
**только в свежесозданный пустой репо** (INV-4 не затрагивается) → merged-вывод
`ORCH_PROJECTS_JSON`, провалидированный фактическим `projects._parse_projects_json`
(round-trip). Недоступное в Plane CE API → `manual-step` со ссылкой на runbook (fail-safe).
Скрипт **никогда** не рестартит прод, не правит `.env`, не пушит в существующие репо, ничего
не удаляет.
3. **Runbook `docs/operations/ONBOARDING.md`** — полный чеклист: предусловия (токены) → скрипт →
операторские шаги (env + управляемый рестарт с self-hosting-предупреждением; UI-only Plane) →
верификация (`verify` + smoke) → откат. Smoke-контур — **staging (8501, изолированная БД)** +
одноразовый sandbox-проект (`SMK`); протокол — «Журнал smoke-прогонов» в runbook.
Анти-дрейф — структурные тесты kit (аналог `tests/test_agent_prompts_canon.py`) + снапшот-тест
`STAGE_TRANSITIONS`/`QG_CHECKS` (контроль ненарушения `src`). Branch protection `main` новых репо
**не включается** (ломала бы PR-merge API merge-актора — ложные HOLD класса ORCH-093).
## Последствия
- **+** Новый проект разворачивается одним проходом проверяемо: все слои (Plane-контракты,
webhook, промпты, дока, реестр) закрыты скриптом+runbook; тихие деградации ловит `verify`.
- **+** Нулевой риск рантайма: изменение docs/templates/scripts/tests-only; регресс
enduro/orchestrator невозможен по построению; общая БД не читается и не пишется скриптом.
- **+** Единый эталон без форка: новые репо получают живой канон момента онбординга;
обновления канона в них едут обычными PR с reviewer-gate.
- **** Регистрация в реестре остаётся операторской (env + управляемый рестарт — Ф-3,
сознательное ограничение NFR-2); разрыв «создано, но не зарегистрировано» виден через `verify`.
- **** Закрытый список read-only импортов из `src` (`projects._parse_projects_json`,
`plane_sync._PLANE_NAME_TO_KEY`, поля `config.settings`) — связь с приватными именами;
поломка при рефакторинге видимая (тесты), расширение списка — только через ADR.
- **Ограничение:** способность ≠ исполнение: онбординг конкретного заказчика — операторская
эксплуатация (вне ORCH-009); тиражирование на новый хост — ORCH-10 (вне объёма).
Детально: `docs/work-items/ORCH-009/06-adr/ADR-001-turnkey-onboarding-kit-and-cli.md`
(D1…D11 — раскладка, плейсхолдеры, copy-vs-template split, импорт src, группы статусов,
webhook-секрет, формат реестра, smoke-контур, языковая политика, branch protection, форма CLI).

View File

@@ -0,0 +1,109 @@
---
work_item: ORCH-101
stage: architecture
author_agent: architect
status: proposed
created_at: 2026-06-10
model_used: claude-opus-4-8
---
# adr-0036: Фундамент тиража платформы — параметризация хоста, секреты, smoke (ORCH-101, 10-common)
## Статус
Proposed
## Контекст
Эпик ORCH-10 (D5.3 «Масштаб») — тираж платформы для заказчиков-тестеров двумя типами (A Lite /
B Bundled), оба stateless. Платформа была фактически прибита к хосту `mva154`: четыре места в
`src/**` обходили конфиг (внешний Gitea-URL в `plane_sync`, HOME + git-идентичности акторов в
`launcher`/`self_deploy`/`post_deploy`), `docker-compose.yml`/`Dockerfile`/deploy-hook несли
литералы путей/uid/gid/портов; механизма выпуска нового комплекта секретов и процедуры верификации
развёрнутой копии не существовало. ORCH-101 (10-common) — общий фундамент обоих типов тиража.
Это сквозное решение: оно задаёт **платформенные конвенции тиража** и трогает блоки, помеченные
маркерами ORCH-036/ORCH-040/ORCH-058 (по `docs/_standards/TRACEABILITY.md` — сводный ADR вместо
архео-перечисления). Детальный пакет решений (D1…D10) — work-item ADR:
`docs/work-items/ORCH-101/06-adr/ADR-001-host-parametrization-secrets-smoke.md`.
## Решение
**Принцип: «дефолт = боевое значение».** Каждое хост-специфичное значение читается из конфига
(`Settings` env `ORCH_*` / compose-интерполяция `${VAR:-default}` / Dockerfile `ARG` /
shell-default хука) с дефолтом, равным текущему боевому значению. Отсутствие новых переменных =
байт-в-байт текущее поведение (kill-switch-природа; отдельный функциональный флаг не вводится).
`src/config.py` и `watchdog/config.py` — единственные легитимные места хост-литералов в коде.
**Новые конфиг-ключи:** `agent_home_dir` (`ORCH_AGENT_HOME_DIR`, `/home/slin`) — HOME всех
акторских процессов; `agent_git_name` (`claude-bot`) + `git_email_domain` (`mva154.local`) —
git-идентичности (`<actor>@<domain>`; системные акторы `deploy-finalizer`/`post-deploy-monitor`
платформенные литералы); `staging_port` (`ORCH_STAGING_PORT`, `8501`). Ссылки в Plane-комментариях —
из существующих `gitea_public_url`/`gitea_owner`. Compose-слой — карта `ORCH_HOST_*`/
`ORCH_DOCKER_GID`/`ORCH_RUN_UID/GID` + реюз `ORCH_DEPLOY_*`; порт прод/стейджинг — явные `command:`
с `${ORCH_DEPLOY_PROD_TARGET_PORT:-8500}` / `${ORCH_STAGING_PORT:-8501}` (CMD образа не трогается —
exec-form + `init: true` сохранены).
**Платформенные конвенции тиража (нормативно):**
1. **`SELF_HOSTING_REPO = "orchestrator"` — константа, не конфиг.** На ней «empty CSV →
self-hosting only» всех `*_repos`-leaf'ов; конфигурируемость превращала бы опечатку env в
активацию деплой-машинерии на чужом репо или тихое выключение всех self-гейтов. Репо платформы
в тираже обязан называться `orchestrator` (REPLICATION.md).
2. **Имена compose-сервисов/контейнеров/образов, профиль `staging`, `network_mode: host`,
контейнерный layout (`/app/data`, `/repos`, `/opt/claude-code`)** — конвенции, не переменные
(для образов истина уже в конфиге `deploy_prod_*_image`).
3. **Staging-порт конфигурируем ТОЛЬКО с fail-closed guard'ом** (усиление инварианта ORCH-058
AC-9): freshness-путь отказывает ДО любого ssh/build при
`staging_port == deploy_prod_target_port` — без тихого fallback. Explicit-pass таргета хуку
(`TARGET_PORT=` и др.) сохранён; добавлена явная передача `REPO=` обоими инвокерами хука
(его строка 38 становится `"${REPO:-…}"` — exit-контракт 0/1/2 ORCH-036 не тронут).
4. **Группа ORCH-040 неделима:** uid/gid/HOME/маунт-таргеты/`useradd` управляются одними env
насквозь (`ORCH_RUN_UID/GID`, `ORCH_AGENT_HOME_DIR` → compose `user:`/таргеты/`build.args
APP_*`); `group_add` docker-gid («МИНА 1») не удаляется — литерал станет
`${ORCH_DOCKER_GID:-999}`.
**Секреты нового хоста:** stdlib-скрипт `scripts/gen_secrets.py` — криптослучайные webhook-секреты
(`secrets.token_hex(32)`), печать по умолчанию, `--write` отказывает при существующем `.env`
(перезапись — только явный `--force`); внешние токены (Plane/Gitea/Telegram/watchdog) — по
чек-листу. Норматив: **боевые секреты текущего хоста не копируются ни на одном шаге**.
**Smoke-верификация тиража:** runbook `docs/operations/REPLICATION.md` (deployment golden source:
карта env, чек-лист секретов, пошаговый smoke с PASS/FAIL: `compose config``/health`
`/queue`+`/metrics``onboard_project.py plan/apply/verify` → тестовая задача → артефакты `0104`
в ветке; расширенно — до `done`; границы 10-common vs Lite vs Bundled). Нового smoke-скрипта нет —
шаги собраны из существующих кирпичей.
**Анти-регресс (постоянная CI-гарантия):** структурный сканер `tests/test_no_host_hardcodes.py`
запрещённые литералы (`82.22.50.71`, `/home/slin`, `mva154`, `duckdns`; список централизован) в
исполняемом коде `src/**`+`watchdog/**`; `tokenize`-исключение комментариев/докстрингов;
структурное исключение двух config-модулей (канон дефолтов); allowlist пуст; негативная
самопроверка.
### Что НЕ меняется
`STAGE_TRANSITIONS`, состав `QG_CHECKS`, семантика `check_*`, machine-verdict ключи, схема БД —
байт-в-байт; значения существующих конфиг-дефолтов; INV-4; прод-контейнер в рамках задачи не
рестартуется (правки compose/Dockerfile инертны до штатного деплоя через staging 8501 →
`Confirm Deploy`).
## Альтернативы
- **`ORCH_SELF_HOSTING_REPO` конфигом** — отвергнуто: узел безопасности; опечатка = групповой риск.
- **Staging-порт константой** — отвергнуто: compose-порт параметризуется (AC-6), константа дала бы
рассинхрон слоёв; пара «ключ + guard» строго сильнее.
- **Smoke-скрипт-обвязка / генератор в `onboard_project.py`** — отвергнуто: лишняя поверхность;
разные жизненные циклы (онбординг проекта ≠ provisioning хоста).
## Последствия
- Платформа разворачивается на чужой инфре env-конфигурацией; критический путь ORCH-10 разблокирован
(Lite/Bundled строятся поверх REPLICATION.md).
- Инвариант ORCH-058 переходит из «подразумеваемого константой» в исполняемый guard; возврат
хост-хардкода ломает CI структурно.
- Цена: ~13 новых env-имён (на текущем хосте настраивать нечего — дефолты боевые) и правило
«интерполяция читает `.env`/shell, не `env_file`» (зафиксировано в REPLICATION.md).
- Откат: не задавать переменные (дефолты = прежнее поведение); полный — revert PR (без миграций).
## Связи
adr-0005 (ORCH-040 — uid/HOME/«МИНА 1»; группа становится параметризуемой, инвариант сохранён),
adr-0008 (ORCH-058 — INV-FRESH/AC-9; guard усиливает), adr-0007 (ORCH-036 — exit-контракт хука
не тронут), adr-0035 (ORCH-009 — onboarding переиспользуется smoke-процедурой; kit не форкается),
adr-0001 (`is_self_hosting_repo` — конвенция имени закреплена). Детально —
`docs/work-items/ORCH-101/06-adr/ADR-001-host-parametrization-secrets-smoke.md` (D1…D10),
`07-infra-requirements.md`, `10-tech-risks.md`.

View File

@@ -0,0 +1,96 @@
---
work_item: ORCH-102
stage: architecture
author_agent: architect
status: proposed
created_at: 2026-06-10
model_used: claude-opus-4-8
---
# adr-0037: Канон Lite-тиража — `docs/deployment/LITE_SETUP.md` + `.env.watchdog.example` (ORCH-102, 10a)
## Статус
Proposed
## Контекст
Эпик ORCH-10 (D5 «Масштаб»), тип **A — Lite**: раздача орк+watchdog заказчику-тестеру, окружение
(Plane/Gitea/LLM/Telegram) он донастраивает сам. Фундамент 10-common (ORCH-101, adr-0036) в
`main`: технически платформа разворачивается без правки кода, но операционно знания размазаны по
4 operations-докам, писанным для оператора НАШЕГО хоста, — заказчик не может развернуть Lite без
доп-вопросов. Решения Владельца 10.06: оба типа тиража stateless; главный продукт ORCH-102 —
инструкция-golden-source в репо.
Сквозной характер: вводится новый docs-раздел, новый канонический example-файл и нормативы,
обязательные для всех будущих задач эпика ORCH-10 и для любого агента, меняющего шаги тиража.
Детальный пакет решений (D1…D9, исходы вопросов ТЗ А-1…А-6) — work-item ADR:
`docs/work-items/ORCH-102/06-adr/ADR-001-lite-setup-doc-canon.md`.
## Решение
1. **Новый docs-раздел `docs/deployment/` — витрина тиража.** Семантика: `deployment/` — «как
развернуть платформу у себя» (читатель — внешний оператор), `operations/` — «как
эксплуатировать наш прод». Golden source Lite — **`docs/deployment/LITE_SETUP.md`**:
сквозной маршрут «голый хост → работающий конвейер» из 13 нормативных разделов (рамка →
предусловия → код → конфиг/секреты → Plane → Gitea → LLM → Telegram → запуск → онбординг →
smoke → stateless-проверка → траблшутинг); каждый шаг = fenced-команда + явная проверка
(PASS/FAIL); хост-специфика — только плейсхолдеры. Канон не форкается: статусы/env/вебхуки —
ссылками на ONBOARDING/REPLICATION/SETUP_WEBHOOKS (`REPLICATION.md` остаётся в
`operations/`; перекрёстные ссылки в обе стороны). **Норматив сопровождения:** изменение
шагов тиража → обновление LITE_SETUP.md в том же PR (правило агентов №2).
2. **Compose не форкается.** `docker-compose.yml` сам является Lite-подмножеством (ровно
`orchestrator` + `orchestrator-watchdog` + `orchestrator-staging` за `profiles: [staging]`;
дефолтный `up -d` поднимает орк+watchdog; сервисов Plane/Gitea нет) — отдельный
`docker-compose.lite.yml` не вводится; свойство становится CI-гарантией (структурный тест
через `yaml.safe_load`).
3. **`.env.watchdog.example` — третий канонический env-example** (рядом с `.env.example`/
`.env.staging.example`): закрывает ловушку файла-носителя (sidecar читает ТОЛЬКО
`.env.watchdog`; ключ `WATCHDOG_*` в `.env` для него инертен). Key-set = ровно блок
`WATCHDOG_*` из `.env.example` (равенство множеств держит тест); токены — плейсхолдеры;
шапка несёт C-1 ORCH-100 (отдельный watchdog-бот, токен орка переиспользовать запрещено)
и когерентность `WATCHDOG_METRICS_URL``ORCH_DEPLOY_PROD_TARGET_PORT`.
4. **Норматив тиражной инсталляции Gitea: branch protection на `main` НЕ включать; pre-receive
хуки не вводятся** (подтверждение ORCH-009 ADR-001 D10 для чужих инсталляций:
required-approvals/status-checks ломают PR-merge API merge-актора → ложные HOLD; защита
`main` — конвенция + скоуп токенов + INV-4).
5. **Staging-контур в Lite опционален:** базовый контур заказчика = prod-оркестратор + watchdog;
песочница 8501 нужна только при self-hosting развитии платформы у заказчика (регистрация
проекта `orchestrator`); guard ORCH-058 (staging-порт ≠ прод-порт) действует.
6. **Анти-дрейф — постоянная CI-гарантия:** `tests/test_lite_setup_doc.py` (структурный, без
сети/LLM): разделы/кирпичи дока, env-ключи дока ⊂ `.env.example`, key-sync watchdog-example,
compose-подмножество, stateless-норматив + отсутствие секретов/боевых литералов в
fenced-блоках (реюз центрального `FORBIDDEN` из `tests/test_no_host_hardcodes.py` импортом),
перекрёстность REPLICATION→LITE_SETUP + CHANGELOG.
### Что НЕ меняется
`src/**`, `docker-compose.yml`, `Dockerfile`, `scripts/**`; `STAGE_TRANSITIONS`, состав
`QG_CHECKS`, семантика `check_*`, machine-verdict ключи, схема БД — байт-в-байт. Новый QG не
регистрируется (структурные тесты попадают в существующие гейты). Прод-контейнер в рамках
задачи не рестартуется (выкат — штатно: staging 8501 → `Confirm Deploy`).
## Альтернативы
- **Инструкция в `docs/operations/`** — отвергнуто: другой целевой читатель; путь зафиксирован
Владельцем (D-4).
- **`docker-compose.lite.yml`** — отвергнуто: вторая правда о сервисах = дрейф-поверхность.
- **Pre-receive/branch protection как «защита `main`»** — отвергнуто: класс инцидента ORCH-063
(ложные HOLD merge-актора); пересмотр — только отдельным ADR.
- **Без example-файла watchdog (шаг прозой)** — отвергнуто: двусмысленность файла-носителя
остаётся; example + key-sync тест надёжнее.
## Последствия
- Type A эпика ORCH-10 закрыт продуктом-инструкцией; Type B (Bundled) строится поверх
(переиспользует разделы Lite). Полнота инструкции и compose-подмножество защищены CI.
- Цена: новый golden source требует сопровождения (норматив «в том же PR» + структурный тест
рвёт CI при дрейфе); осознанный дубль ключей `WATCHDOG_*` в двух example-файлах — под
key-sync тестом.
- Откат: удалить `docs/deployment/`, тест и `.env.watchdog.example`, вернуть строку
REPLICATION.md §1 — состояние 1:1 (docs+tests, без миграций).
## Связи
adr-0036 (ORCH-101 — фундамент 10-common; этот ADR строит слой Lite поверх), adr-0035
(ORCH-009 — onboarding-CLI/kit переиспользуются маршрутом §5/§6/§10; D10 подтверждён п.4),
adr-0033 (ORCH-100 — sidecar-watchdog; C-1 независимый Telegram-канал закреплён в example),
adr-0008 (ORCH-058 — staging-порт guard, вилка staging п.5), adr-0027/INV-4 (merge-актор —
основание запрета branch protection). Детально —
`docs/work-items/ORCH-102/06-adr/ADR-001-lite-setup-doc-canon.md`,
`docs/work-items/ORCH-102/10-tech-risks.md`.

View File

@@ -0,0 +1,114 @@
---
work_item: ORCH-103
stage: architecture
author_agent: architect
status: proposed
created_at: 2026-06-11
model_used: claude-opus-4-8
---
# adr-0038: Канон Bundled-тиража — `deploy/bundled/` + bootstrap + `BUNDLED_SETUP.md` (ORCH-103, 10b)
## Статус
Proposed
## Контекст
Эпик ORCH-10 (D5 «Масштаб»), тип **B — Bundled**: заказчик без собственной инфраструктуры
получает **весь стек одним комплектом** (орк + watchdog + Gitea + Plane CE ≈1314 контейнеров) и
bootstrap, доводящий его до рабочего конвейера одним запуском. Фундамент готов: 10-common
(ORCH-101, adr-0036 — хост-параметризация/секреты/smoke) и Lite (ORCH-102, adr-0037 — док-канон
`docs/deployment/`). Корневой `docker-compose.yml` заморожен анти-дрейфом ORCH-102 (ровно 3
сервиса, запрет подстрок `plane`/`gitea`) → комплект обязан жить отдельным файлом.
Сквозной характер: вводится новый top-level каталог `deploy/` (дистрибутивы развёртывания),
новый канонический env-example и нормативы, обязательные для будущих задач эпика ORCH-10 и
любого агента, меняющего шаги тиража. Детальный пакет решений (D1…D11, исходы OQ-1…OQ-7 ТЗ) —
work-item ADR: `docs/work-items/ORCH-103/06-adr/ADR-001-bundled-stack-compose-and-bootstrap.md`.
## Решение
1. **Новый top-level каталог `deploy/` — исполняемые дистрибутивы развёртывания** (дополняет
`docs/deployment/` — инструкции). Bundled-комплект: **`deploy/bundled/docker-compose.yml`** —
один самодостаточный compose всего стека с top-level `name: orchestrator-bundle` (project
name = узнаваемый префикс томов/контейнеров; `container_name` не пиннится — нет коллизий с
корневым compose на одном хосте). Staging-контур орка в bundle **отсутствует вовсе**; репо
`orchestrator` в bundle-инсталляции не регистрируется → self-deploy-машинерия структурно спит
(`SELF_HOSTING_REPO`-леафы не матчатся).
2. **Конфиг-слои:** `deploy/bundled/.env.example` — канон bundle-инфры (committed, плейсхолдеры;
key-set-sync тест: каждая `${VAR}`-интерполяция bundle-compose имеет ключ в каноне) → live
`deploy/bundled/.env` (авто-чтение compose из project dir — без `--env-file`-футгана; покрыт
неякорным `.env` в `.gitignore`); runtime орка/watchdog — **корневые `.env`/`.env.watchdog`
ровно по канону Lite** (REPLICATION §2 применим 1:1), в bundle-compose — `env_file:
required: false` (первый `up` жив до сборки конфига). **Bootstrap — единственный писатель**
всех трёх live-файлов (когерентность дублируемых ключей — механическая). Один факт = одно имя
(ORCH-101 D1): существующие факты — существующие `ORCH_*`-имена; bundle-only — `BUNDLE_*`;
внутренние креды Plane — upstream-имена.
3. **Состав/пиннинг:** Plane CE — зеркало официального selfhost-référence (upstream-имена
сервисов/env); Gitea — `gitea/gitea` (не rootless). Пиннинг — **точный неподвижный тег
литералом** (не `latest`, не интерполяция; digest не требуется); точные теги фиксирует
developer по проверенному стенду; форму держит структурный тест.
4. **Сеть:** одна именованная bridge-сеть; машинный трафик — строго сервис-DNS
(`http://orchestrator:8500/webhook/*`, `http://gitea:3000`, plane-proxy); `network_mode: host`
в bundle не используется (ssh-деплой-пути неактивны: `ORCH_DEPLOY_SSH_HOST` пуст). Наружу —
только человеческие порты (Plane proxy 8080 / Gitea 3000 / орк 8500; конфигурируемы);
БД/брокер/minio не публикуются. Публичные URL — от `BUNDLE_PUBLIC_HOST` (split internal/public
уже в конфиге орка). Мина Gitea закрывается явно: `GITEA__webhook__ALLOWED_HOST_LIST=orchestrator`.
5. **Bootstrap `scripts/bootstrap_bundle.py`:** python stdlib-only, без импортов из `src/**`;
режимы `plan` (дефолт, ноль мутаций) / `apply` / `verify`; step-движок check→ensure
(идемпотентность, resume = повторный запуск); exit `0/2/1`. Preflight fail-fast до мутаций
(docker/порты/чистота томов по префиксу/RAM/диск; Claude CLI — warning). **Кирпичи не
дублируются:** секреты — субпроцесс `gen_secrets.py`; статусы/лейблы/репо/вебхуки — строго
`onboard_project.py apply`+`verify` (host-venv, канон ONBOARDING). Init Gitea — полностью
автоматом (CLI в контейнере; branch protection НЕ настраивается — D10 ORCH-009/adr-0037 п.4);
init Plane CE — честные **manual-step чекпоинты** (инструкция → подтверждение →
API-верификация; прогрессивная автоматизация разрешена без смены контракта). Git-доступ
агентов — HTTP token-remote (паттерн `_push_url`); ssh-контур не вводится. Секреты в
логи не печатаются; delete-операций в скрипте нет вообще — teardown только документированной
процедурой (`BUNDLED_SETUP` §13).
6. **Док-канон:** `docs/deployment/BUNDLED_SETUP.md` — 14 нормативных разделов по форме
LITE_SETUP (fenced-команда + «Проверка:» PASS/FAIL, плейсхолдеры, общие шаги ссылками на
LITE_SETUP/ONBOARDING/REPLICATION — канон не форкается), включая «Требования к хосту» с
цифрами **по замеру** тестового развёртывания. REPLICATION §1: Type B → ✅ ORCH-103.
**Норматив сопровождения:** изменил шаги Bundled-тиража → обнови BUNDLED_SETUP.md в том же PR.
7. **Анти-дрейф — постоянная CI-гарантия:** `tests/test_bundle_compose.py` /
`test_bundled_setup_doc.py` / `test_bootstrap_script.py` (структурные, без docker/сети/LLM:
состав сервисов, заморозка корневого compose, пины, key-set-sync, разделы дока, FORBIDDEN —
импортом из `test_no_host_hardcodes.py`, секрет-эвристика, ссылки на кирпичи, отсутствие
delete-операций, unit чистых функций preflight/плана, exit-контракт).
### Что НЕ меняется
`src/**`, корневой `docker-compose.yml`, `Dockerfile`, `.gitea/workflows/**`, `onboarding/**`,
промпты `.openclaw/agents/**`; `STAGE_TRANSITIONS`, состав `QG_CHECKS`, семантика `check_*`,
machine-verdict ключи, схема БД — байт-в-байт. Kill-switch не вводится (активация — только явный
запуск оператора на целевом хосте, паттерн ORCH-009). Прод-контейнер в рамках задачи не
рестартуется; наши данные/секреты не переносятся (stateless, решение Владельца 10.06).
## Альтернативы
- **Расширение корневого compose (профиль `bundled`)** — отвергнуто: заморожен анти-дрейфом
ORCH-102/нормативом «compose не форкается»; смешение дистрибутива с боевым контуром.
- **Include-композиция / live-env через `--env-file`** — отвергнуто: лишние степени свободы
запуска, молчаливые дефолты при забытом флаге.
- **Орк в bundle на host-network + `host-gateway`** — отвергнуто: хост-сеть нужна была
ssh-деплой-контуру нашего хоста, который в bundle спит; bridge даёт чистые двунаправленные
сервис-DNS-URL.
- **Digest-пиннинг / rootless-Gitea / ssh-доступ агентов / bash-bootstrap / reset-режим
скрипта** — отвергнуты (см. work-item ADR-001, «Альтернативы»).
## Последствия
- Эпик ORCH-10 закрыт по обоим типам: A (Lite, инструкция) + B (Bundled, комплект); заказчик
без инфраструктуры разворачивает конвейер «под ключ».
- Цена: пиннованные версии Plane/Gitea стареют (апгрейд — отдельные задачи); manual-step Plane CE
размывают «одну команду» — неустранимо честно (нет API), митигировано контрактом чекпоинта;
двойной `.env`-слой — под единственным писателем-bootstrap и key-sync тестом.
- Откат: удалить `deploy/`, bootstrap, BUNDLED_SETUP.md, три тест-модуля, строку REPLICATION §1 —
состояние 1:1 (docs+scripts+tests, без миграций).
## Связи
adr-0036 (ORCH-101 — фундамент 10-common: параметризация, gen_secrets, REPLICATION/smoke),
adr-0037 (ORCH-102 — док-канон `docs/deployment/`, compose-подмножество, запрет branch
protection), adr-0035 (ORCH-009 — onboarding-CLI: 22 статуса, manual-step паттерн, `_push_url`,
D10), adr-0027/INV-4 (merge-актор — основание норматива Gitea), adr-0001
(`SELF_HOSTING_REPO`-конвенция — почему self-гейты в bundle спят). Детально —
`docs/work-items/ORCH-103/06-adr/ADR-001-bundled-stack-compose-and-bootstrap.md`,
`07-infra-requirements.md`, `10-tech-risks.md`.

View File

@@ -0,0 +1,95 @@
---
work_item: ORCH-011
stage: architecture
author_agent: architect
status: proposed
created_at: 2026-06-11
model_used: claude-opus-4-8
---
# adr-0039: Витрина системы `docs/overview/` — единая точка входа (бизнес + тех) и канон презентации (ORCH-011)
## Статус
Proposed
## Контекст
Документация платформы богатая, но фрагментированная: паспорт `CLAUDE.md` (реестр доработок),
тех-витрина `README.md`, vision `docs/PRODUCT_VISION.md`, инженерный справочник
`docs/architecture/` (~1246 строк + internals), 38 сквозных ADR, стандарты, операционные и
deployment-доки. Единой точки входа «бизнес + тех» для трёх аудиторий (заказчик / менеджер /
разработчик) нет; презентацию о возможностях собирать не из чего. С тиражируемостью
(ORCH-101/102/103) появился внешний читатель. Решения Владельца: слайды PowerPoint в тёмном
дизайне; единое место — `docs/`; витрина поддерживается актуальной.
Живые доказательства проблемы в самом репо: схема конвейера в `PRODUCT_VISION.md` §2 устарела
(нет `deploy-staging`/`cancelled`); `docs/PRODUCT_VISION.pptx` закоммичен **без пути генерации**
(невоспроизводим). Reviewer-ось обзорных доков (ORCH-079, adr-0023) по букве привязана к
`README.md` «Известные ограничения» — новую витрину не покрывает.
Сквозной характер: вводится новый docs-раздел с нормативом сопровождения, обязательным для
**всех будущих функциональных PR**, расширяется reviewer-ось и фиксируется канон
презентационных артефактов. Детальный пакет решений (D1…D9, исходы OQ-1…OQ-5) — work-item ADR:
`docs/work-items/ORCH-011/06-adr/ADR-001-system-overview-canon.md`.
## Решение
1. **Новый docs-раздел `docs/overview/` — витрина системы.** Семантика разделов после ORCH-011:
`overview/` — «что это за система и как устроена» (вход для любой аудитории), `architecture/`
— инженерный справочник, `deployment/` — «как развернуть у себя», `operations/` — «как
эксплуатировать наш прод», `_standards/` — нормативы агентов. Состав — плоский каталог,
10 файлов: индекс `README.md` (точка входа, 3 маршрута аудиторий, норматив сопровождения),
`business.md` (бизнес-уровень: проблема → решение → способности → ценность → сценарии; без
жаргона; числа только с подтверждением), 7 файлов `tech-*.md` = 7 блоков контент-карты
(архитектура / конвейер / агенты / модель объектов / интеграции / качество-безопасность /
наблюдаемость), `presentation.md` (слайдо-источник).
2. **Link-first, канон не форкается:** витрина даёт цельную картину и ссылается на golden
sources за деталями; запрещён дубль живых таблиц (компоненты, env, статусы). Разрешённый
дубль — только машинно-сверяемый тестом факт: стадии/гейты/агенты derive-тестами из
`STAGE_TRANSITIONS`/`QG_CHECKS`/glob промптов (прецедент key-sync ORCH-102).
3. **Канон презентации:** источник — `presentation.md` (машинно-парсимая слайдо-структура
`## Слайд N:` + тезисы, 1418 слайдов); генератор — `scripts/build_presentation.py` на
python-pptx (тёмная тема, редактируемый текст, кириллица), запуск **только вне рантайма**
(dev-venv, явный запуск человеком — паттерн ORCH-009); зависимость в
`requirements*`/`Dockerfile` НЕ попадает (машинный гард в тестах). **Собранный `.pptx` в git
не коммитится** (источник истины — markdown + скрипт; существующий `PRODUCT_VISION.pptx` не
трогается, но прецедентом не является).
4. **Норматив сопровождения (кросс-каттинг):** «изменил функциональность платформы → обнови
витрину `docs/overview/` в том же PR» — в индексе витрины и `CLAUDE.md` (правило агентов №2);
**reviewer-ось обзорных доков ORCH-079 расширяется** точечной врезкой в
`.openclaw/agents/reviewer.md`: функциональность из витрины изменена, витрина не обновлена →
finding ≥ P1 (расширение трактовки той же оси; канон 52d и verdict-ключи — байт-в-байт;
анти-регресс `test_agent_prompts_canon.py`).
5. **Анти-дрейф — `tests/test_system_docs.py`** (структурный, без сети/LLM/subprocess, паттерн
`test_lite_setup_doc.py`): наличие/непустота 10 файлов; маршруты и норматив в индексе;
сверка стадий и имён гейтов импортом из кода; полнота 6 агентов glob'ом промптов; валидность
относительных ссылок; полнотекстовый FORBIDDEN-скан (импорт из `test_no_host_hardcodes.py`)
+ секрет-эвристика; парс слайдо-источника функцией самого генератора; чистота
`requirements*`/`Dockerfile` от pptx; указатели README/CLAUDE/CHANGELOG. Новый QG НЕ
регистрируется — тесты исполняются существующими гейтами.
Рантайм байт-в-байт: `src/**`, compose, Dockerfile, `STAGE_TRANSITIONS`/`QG_CHECKS`/`check_*`/
machine-verdict/схема БД — не тронуты; kill-switch не нужен (доки и dev-скрипт конвейером не
исполняются).
## Последствия
- **+** Закрывается корневая фрагментация: одна точка входа для трёх аудиторий; презентация
собирается за одну команду из версионируемого источника; машинно-проверяемые факты витрины —
CI-гарантии.
- **+** Нулевой риск рантайма; для enduro-trails инертно.
- **** Новый golden source = обязанность каждого функционального PR (в этом смысл задачи);
митигировано link-first + derive-тестами + reviewer-осью.
- **** Точечная правка промпта reviewer — поверхность канона 52d; держится анти-регресс
тестами.
- **Откат:** удалить `docs/overview/`, тест-модуль, скрипт, вернуть точечные правки указателей
и промпта — 1:1, без миграций и состояния.
## Ссылки
- Детально: `docs/work-items/ORCH-011/06-adr/ADR-001-system-overview-canon.md` (D1…D9),
`docs/work-items/ORCH-011/10-tech-risks.md`
- BRD/TRZ/AC: `docs/work-items/ORCH-011/01-brd.md` / `02-trz.md` / `03-acceptance-criteria.md`
- Соседние каноны: adr-0019 (стандарт доков), adr-0021 (канон промптов 52d), adr-0023
(ось обзорных доков ORCH-079 — расширяется), adr-0029 (порядок под-гейтов), adr-0037/0038
(deployment-каноны)

View File

@@ -58,11 +58,18 @@ STAGE_TRANSITIONS = {
architecture: → development (agent: developer, QG: check_architecture_done)
development: → review (agent: reviewer, QG: check_tests_local)
review: → testing (agent: tester, QG: check_reviewer_verdict)
testing: → deploy (agent: deployer, QG: check_tests_passed)
testing: → deploy-staging (agent: deployer, QG: check_tests_passed)
deploy-staging: → deploy (agent: deployer, QG: check_staging_status)
deploy: → done (agent: None, QG: None)
cancelled: → None (agent: None, QG: None) # ORCH-090: терминал-сток отмены
}
```
**Терминальные стоки (ORCH-090):** `done` и `cancelled` — равноправные терминальные состояния
(`{"next": None, "agent": None, "qg": None}`). `cancelled` — это **не новое ребро** (exit-гейты
рёбер не меняются), а терминал STOP-отмены. Системный предикат «задача завершена» —
`stage ∈ {done, cancelled}` (синхронно в `reconciler`/`serial_gate`/`task_deps`; adr-0026).
### 3. Quality Gates (`src/qg/checks.py`)
| Check | Метод проверки |
@@ -99,6 +106,17 @@ claude.exe --print --system-prompt --allowedTools Read,Write,Edit,Bash
Примечание: переход `review → testing` использует `check_reviewer_verdict` (читается из frontmatter `12-review.md`); `development → review``check_tests_local` (оркестратор сам прогоняет тесты, не зависит от Gitea CI).
**Багфикс-трек: routing-override на ребре выхода из `analysis` (ORCH-019 — design).** Для задачи
с `tasks.track='bug'` (помечена в `start_pipeline` по метке Plane `Bug` через аппарат ORCH-089)
`advance_stage` на шаге 3 переопределяет результат `get_next_stage('analysis')`: `next_stage`
`development` (вместо `architecture`), а на шаге 4 `next_agent``developer` (вместо `architect`)
→ стадия `architecture` и её exit-гейт `check_architecture_done` для багфикса не исполняются.
`STAGE_TRANSITIONS`/`get_next_stage`/`get_agent_for_stage` остаются чистыми (1:1) — override живёт
только в `advance_stage`. Чистый предикат `bug_fast_track.skips_architecture(track)` (leaf
`src/bug_fast_track.py`, never-raise) под `bug_fast_track_enabled`; `track` читается из БД, не из
сети (NFR-4). `False`/неприменимый репо → маршрут байт-в-байт прежний. Детали —
[adr-0032](adr/adr-0032-bug-fast-track.md).
### 6. Review Bounce
При REQUEST_CHANGES:
@@ -106,6 +124,37 @@ claude.exe --print --system-prompt --allowedTools Read,Write,Edit,Bash
2. Если < MAX_DEV_RETRIES (3) — откатывает в development, перезапускает developer
3. Если >= MAX_DEV_RETRIES — эскалация (логирование + уведомление)
### 7. Live Telegram tracker (`src/notifications.py`)
Вместо ~15 отдельных сообщений на задачу оркестратор держит **ОДНУ** live-карточку на задачу (`update_task_tracker`), которая обновляется на каждом переходе стадии. Текст рендерится статически из БД (`render_task_tracker`: стадии, токены, стоимость, BRD-подтверждение, итоги). Карточка всегда тихая (`disable_notification=True`); отдельные пинги шлют только `notify_approve_requested` / `notify_error`. `message_id` хранится в `tasks.tracker_message_id`; helpers `get_tracker_message_id` / `set_tracker_message_id`. Контракт всего компонента — **never raises**.
**Режимы (ORCH-042, `ORCH_TRACKER_MODE``Settings.tracker_mode`; дефолт переключён `edit → bump` в ORCH-067).** Резолвится в `update_task_tracker` (case-insensitive, trim); всё, что ≠ `"bump"` (включая пустое/мусор/None), трактуется как `edit` → безопасный фолбэк. Инвариант «одна карточка на задачу» сохраняется в обоих режимах.
| Режим | Поведение при обновлении |
|-------|--------------------------|
| `bump` (дефолт, ORCH-067) | карточка пересоздаётся внизу чата: best-effort `delete_telegram(старый_id)``send_telegram(text, disable_notification=True)``set_tracker_message_id(new_id)` **только** при успешном send (`new_mid is not None`). За один вызов — не более одного нового сообщения. Живая карточка всегда «догоняет» переписку. |
| `edit` | первый вызов → `send_telegram` (тихо) + сохранение `message_id`; далее → `edit_telegram` на сохранённый id. Новое сообщение шлётся ТОЛЬКО при `EDIT_GONE` (удалено/старше 48ч/невалидный id). `EDIT_NOT_MODIFIED` / `EDIT_FAILED` → нового сообщения нет (анти-дубль). |
**`delete_telegram(message_id) -> bool`** (low-level, never raises). Семантика возврата — «исчезло ли старое сообщение»:
- `ok:true``True`;
- `ok:false` с маркерами `_DELETE_GONE_MARKERS` (`message to delete not found`, `message can't be deleted`, `message_id_invalid`) → `True` (старше 48ч / уже удалено — не транзиент);
- прочий `ok:false` / 5xx / исключение (сеть/таймаут) → `False` + `logger.warning`;
- нет токена/chat_id → `False`, HTTP не выполняется.
Результат `delete_telegram` **не** блокирует отправку новой карточки (BR-6: delete-fail у сообщения >48ч → всё равно шлём новое); `False` означает лишь «старое, возможно, ещё живо» — будет вычищено повторной попыткой на следующем переходе. При транзиентном сбое send (`None`) указатель `tracker_message_id` **не** затирается (анти-затирание, симметрично edit-fallback).
**Текст карточки (оба режима, ORCH-042):** метка `Подтверждение BRD` (была «Ревью БРД»); после прохождения approve-gate строка BRD начинается с ✅ (ветка ожидания сохраняет ⏸️/⏳); русские display-labels стадий (`Анализ / Архитектура / Разработка / Код ревью / Тестирование / Внедрение`); финальная строка `📦 Внедрено` (было `deployed`). Меняются только отображаемые строки — ключи стадий и имена агентов (завязаны на `_STAGE_ACTIVE_AGENT`, `last_done`, БД) не трогаются.
**Строки стадий: отражение откатов + суммирование метрик (ORCH-091).** Цикл рендера строк стадий (`render_task_tracker``_stage_line`) исправлен по двум осям. (1) **Откат (Деф.2):** `✅`-строка стадии рисуется только если её позиция в конвейере `≤` текущей позиции задачи; позиция берётся из порядка `STAGE_TRANSITIONS` (read-only хелпер `_pipeline_pos`, never-raise; неизвестная стадия → «далёкое будущее» → ✅ не пере-подавляется) с нормализацией `deploy-staging → deploy` ТОЛЬКО в гейте подавления (схлопнутая строка «Внедрение» несёт `stage_key="deploy"`). После отката (`deploy-staging → development`, `review → development`) строки стадий ПОЗЖЕ текущей больше не рисуются как пройденные — пропадает абсурд «✅ Внедрение + 🔄 Разработка»; `is_active_stage` не тронут. (2) **Метрики (Деф.3):** `_stage_line` агрегирует ВСЕ `agent_runs` агента стадии (Σ cost / Σ токены / Σ время теми же per-run-формулами, что блок тоталов задачи), а не последний прогон — каждый агент привязан ровно к одной строке `_TRACKER_STAGES`, поэтому Σ(строк стадий) ≡ тоталы ≡ `SUM(agent_runs)` по `task_id`; модель/эффорт/«попытка N» берутся из последнего прогона. Прогоны, подавлённые откатом, по-прежнему входят в тоталы (намеренная семантика отката).
**Строка Plane-статуса и кликабельный номер (ORCH-067, слой B — индикация).** Под заголовком карточка несёт строку `📍 <Plane-статус>` по модели ORCH-066. Источник — двухслойный, контракт **never raises**:
- **Оффлайн-ядро** `plane_status_label(task_row)` — чистая функция БЕЗ сети: `stage → статус` (`created→To Analyse`, `analysis→Analysis`, `architecture→Architecture`, `development→Development`, `review→Code-Review`, `testing→Testing`, `deploy-staging→Deploying (staging)` [ORCH-091], `deploy→⏸ Awaiting Deploy`, `done→Done`, `cancelled→Cancelled` [ORCH-091]) + `⏸️ In Review` из brd-часов (`brd_review_started_at` задан, `…_ended_at` пуст). **ORCH-091:** карта `_STAGE_STATUS_LABEL` покрывает ВСЕ ключи `STAGE_TRANSITIONS` (полнота — тестом, не статичным списком); неизвестная/будущая стадия → нейтральный фолбэк (капитализированное имя стадии), а НЕ «To Analyse» (он остаётся лишь явным лейблом `created` и безопасной деградацией на истинно-битом входе).
- **Live-overlay** `_live_plane_branch_override` — best-effort: дорисовывает ветви-статусы, неразличимые оффлайн (Needs Input / Blocked / Rejected / Cancelled / Deploying / Monitoring after Deploy), чтением живого Plane-статуса (`fetch_issue_state` с коротким `tracker_live_status_timeout_s`, TTL-кэш `tracker_live_status_ttl_s`, kill-switch `tracker_live_status`). Любой сбой / выключенный флаг / нехватка данных → оффлайн-метка; `⏸️ In Review` (авторитет brd-часов) overlay не консультирует. Анти-false-positive: `deploying/monitoring`, алиасящие базовый UUID на проекте без выделенного статуса (enduro), не вызывают override.
**Кликабельный номер задачи (ORCH-067).** Номер в заголовке карточки И во всех уведомлениях орка, где упоминается `work_item_id`, — HTML-ссылка на issue в Plane через общий `plane_issue_link` / `link_for` (URL строит `_plane_issue_url` с loopback/workspace/project-гардами, переиспользуя резолв ORCH-017). Fail-safe: при нехватке любого из (web-base/не-loopback, workspace, project_id, plane_issue_id) → `html.escape(work_item_id)` без `<a>`; динамические части экранируются, `<a>`-разметка валидна под `parse_mode=HTML`. Алерты `stage_engine`/`launcher`/`security_gate`/`reconciler` переведены на `link_for` (резолвит `repo`+`plane_issue_id` из БД по `task_id` или `work_item_id`).
**HTML-безопасность данных карточки (ORCH-095).** Текст карточки шлётся с `parse_mode=HTML` и собирается из слотов двух категорий: **markup** (намеренная разметка — `num_html`/`plane_issue_link`, `link_for(...)`, `_done_link(...)`, уже-экранированный `esc_title`) и **data** (подставляемые значения — длительности `_fmt_minutes`/`_capped_review_str`, статус-лейбл `_card_status_label`, имя модели `short_model_name`, эффорт `_run_effort`, токены/стоимость `fmt_tokens`/`fmt_cost`). Инвариант: **каждый data-слот экранируется `html.escape` ровно один раз на границе рендера** (`render_task_tracker`/`_stage_line`); функции-источники остаются HTML-агностичными, markup-слоты не экранируются (двойное экранирование запрещено). Это устранило класс «неэкранированные данные в HTML-тексте»: до фикса `_fmt_minutes(<60s)` возвращал литерал `<1м`, который Telegram парсил как открывающий тег → `editMessageText` `400 can't parse entities``EDIT_FAILED` → ранний `return` (анти-дубль ORCH-087) → карточка застывала (инцидент ORCH-093). `_fmt_minutes` по-прежнему возвращает `<1м` — escape на границе (`&lt;1м`) рендерит его визуально идентично; формат не меняется. Застрявшая (в окне) карточка авто-восстанавливается следующим безопасным рендером; `edit_telegram`/`update_task_tracker`/леджер сирот/режимы `bump`/`edit` не тронуты. Детали — [ORCH-095 ADR-001](../work-items/ORCH-095/06-adr/ADR-001-html-safe-card-data-render.md).
## Database Schema
```sql
@@ -189,8 +238,10 @@ services:
12. Gitea PR webhook: review event → QG check_review_approved → PASS
13. Advance: review → testing, tester launched
14. Tester: прогоняет тесты, пишет test-report.md → git push
15. Auto-advance: testing → deploy (QG check_tests_passed → PASS)
16. PR merge → Gitea PR webhook: action=closed, merged=true → done
15. Auto-advance: testing → deploy-staging (QG check_tests_passed → PASS)
16. Deployer: runs staging checks → writes 15-staging-log.md (staging_status: SUCCESS)
17. Auto-advance: deploy-staging → deploy (QG check_staging_status → PASS)
18. PR merge → Gitea PR webhook: action=closed, merged=true → done
```
### Review bounce path
@@ -299,9 +350,10 @@ webhook (plane/gitea) background thread (queue_worker)
| Колонка | Назначение |
|--------|------------|
| `status` | `queued``running``done` \| `failed` |
| `status` | `queued``running``done` \| `failed` \| `cancelled` (ORCH-090: терминальный исход STOP-отмены, не реквью'ится) |
| `attempts` / `max_attempts` | счётчик попыток (инкремент при claim) / лимит ретраев (default 2) |
| `run_id` | FK на `agent_runs.id` после старта |
| `pid` | (ORCH-065) pid агентского процесса (`proc.pid` из `_spawn`); liveness-сигнал для job-reaper. Добавляется `_ensure_column` (idempotent) |
| `task_content` | ТЗ, которое пишется в task-файл агента |
| `error` | последняя ошибка |
@@ -319,10 +371,44 @@ status='queued'` и проверяет `rowcount`. При гонке двух т
jobs со статусом `running` (воркер умёр на рестарте) → возвращаются в `queued`.
Потом стартует воркер; на shutdown — `worker.stop()` (Event.set + join).
### Job-reaper (ORCH-065, рестарт НЕ требуется)
`requeue_running_jobs()` спасает ТОЛЬКО на старте процесса. Зомби-job, возникший
**без** рестарта (умер monitor-поток/дочерний процесс, а сервис жив), оставался
`running` навсегда и при `max_concurrency=1` блокировал всю очередь. Фоновый
daemon-поток `src/job_reaper.py` (каркас `reconciler`) периодически
(`reaper_interval_s`) сканирует `running`-jobs и реапит «мёртвые»:
- **Tier-1**`jobs.pid` мёртв (`os.kill(pid,0)``ProcessLookupError`) на
протяжении `reaper_dead_ticks` подряд тиков (анти-ложноположительность);
- **Tier-2**у `agent_runs[run_id]` записан `exit_code`, а `jobs.status` ещё
`running`. Окно неоднозначно: живой monitor пишет `exit_code` ПЕРВЫМ, затем
git push/PR/Plane-комментарии (секунды-десятки секунд) и лишь потом
`_finalize_job`; pid агента к этому моменту мёртв в обоих случаях. Поэтому
Tier-2 реапит только после finalization-grace `reaper_finalize_grace_s`
(`finished_age_s >= grace`) — живой финализирующий monitor НЕ реапится;
- **Tier-3** — backstop: job висит `running` дольше `reaper_max_running_s`.
Реап атомарен (`UPDATE jobs SET ... WHERE id=? AND status='running'` + `rowcount`,
как `claim_next_job`) → совместим со стартовым `requeue_running_jobs` без двойной
обработки. Действие — **claim-before-act**: для exit0 канонический QG оценивается
read-only ПЕРЕД атомарным claim, затем claim `done` ПЕРВЫМ и только победитель
claim делает `_try_advance_stage` (advance+enqueue) — проигравший (поздний monitor
/ стартовый requeue) не выполняет побочных эффектов (нет дубль-advance/-enqueue);
источник истины — QG, не «exit0»; гейт красный или exit≠0/неизвестно →
`attempts<max``queued`, иначе `failed`+Telegram. Тот же поток на старте и
периодически делает проактивный реклейм stale/dead merge-lease (`merge_gate.py`:
`pid_alive`/`reclaim_stale_lease`). never-raise; kill-switch `ORCH_REAPER_ENABLED`
/ `ORCH_LEASE_RECLAIM_ENABLED`; снимок в `GET /queue` (блок `reaper`). Подробнее —
adr-0011.
### Конфиг
- `ORCH_MAX_CONCURRENCY` (default 1) — лимит параллельных jobs.
- `ORCH_QUEUE_POLL_INTERVAL` (default 2.0) — период опроса.
- `ORCH_AGENT_MODEL_DEFAULT` / `ORCH_AGENT_MODEL_<AGENT>` (ORCH-41) — модель агентов; дефолт `claude-opus-4-8`.
- `ORCH_AGENT_EFFORT_DEFAULT` / `ORCH_AGENT_EFFORT_<AGENT>` (ORCH-41) — режим `--effort` (low|medium|high|xhigh|max).
- `ORCH_AGENT_FALLBACK_MODEL` (ORCH-41) — опц. `--fallback-model` при overloaded.
- per-project override: `agent_models` / `agent_efforts` в `ORCH_PROJECTS_JSON`; резолверы `resolve_agent_model` / `resolve_agent_effort` (project > per-agent env > default > пусто).
Наблюдаемость: `GET /queue` — counts по статусам + последние 10 jobs.

View File

@@ -0,0 +1,436 @@
# BUNDLED_SETUP — Bundled-тираж: весь стек одним комплектом (ORCH-103)
> **Golden source Bundled-тиража (Type B эпика ORCH-10).** Маршрут «чистый хост →
> работающий конвейер» для заказчика **без собственной инфраструктуры**: один
> compose-комплект (`deploy/bundled/docker-compose.yml`) поднимает оркестратор +
> watchdog + Gitea + Plane CE, один запуск `scripts/bootstrap_bundle.py apply`
> доводит стек до рабочего состояния. Каждый шаг — исполняемая команда + явная
> проверка результата (**Проверка:** / PASS / FAIL). Хост-специфика — только
> плейсхолдеры `<...>` и `$ENV_VAR`. Тираж **stateless**: данные/задачи/секреты
> боевого (исходного) хоста **НЕ переносятся** ни на одном шаге (§12).
> Границы слоёв тиража (10-common vs Lite vs Bundled) — `docs/operations/REPLICATION.md` §1;
> канон Lite (своя инфраструктура Plane/Gitea) — `docs/deployment/LITE_SETUP.md`.
---
## 1. Рамка Bundled
**Что входит в комплект** (compose-проект `orchestrator-bundle`, одна bridge-сеть):
- `orchestrator` (конвейер, образ собирается из этого чекаута) и
`orchestrator-watchdog` (независимый sidecar-мониторинг);
- **Gitea** (git-хостинг, пиннованный официальный образ);
- **Plane CE — ≈ 14 контейнеров** (зеркало официального selfhost-комплекта:
web/space/admin/api/worker/beat-worker/migrator/live + postgres/redis/
rabbitmq/minio/proxy) — это **ресурсоёмко**, см. §2.
**Что НЕ входит** (внешние предусловия заказчика):
- **Claude CLI / LLM-доступ** — дистрибутив claude-code, node и аутентификация
живут на хосте и пробрасываются маунтами (§8); без них стек поднимется, но
конвейер не поедет;
- **Telegram-боты** — опциональны (§9): пусто = деградация только нотификаций;
- **HTTPS/домены/reverse-proxy** — вне bundle: наружу публикуются три http-порта
(§2), терминирование TLS — средствами заказчика.
**Bundled vs Lite:** Lite (`LITE_SETUP.md`) подключает оркестратор к **вашим**
Plane/Gitea; Bundled везёт их **с собой** на чистых томах. Staging-контур орка в
bundle отсутствует вовсе: заказчик Type B эксплуатирует платформу для своих
проектов, а не развивает её self-hosting'ом (нужен self-hosting — маршрут Lite,
`LITE_SETUP.md` §9.3). Репо `orchestrator` в bundle-инсталляции **не
регистрируется** как проект.
**Осознанный компромисс (TR-7):** git-доступ агентов — HTTP token-remote
(токен бот-юзера в конфиге локальных чекаутов, права 600); ssh-контур
сознательно не вводится; порты БД/брокера/minio наружу не публикуются.
---
## 2. Требования к хосту
Linux x86_64, один хост. Минимумы проверяет preflight bootstrap **до любых
мутаций** (пороги — константы `scripts/bootstrap_bundle.py`, ниже — те же цифры;
подтверждаются замером приёмочного развёртывания):
| Ресурс | Минимум | Почему |
|--------|---------|--------|
| RAM | **8 GB** | Plane CE — ≈ 14 контейнеров (миграции и брокер прожорливы) |
| Диск | **40 GB** свободно | образы стека + тома postgres/minio/gitea + данные орка |
| CPU | **4 vCPU** (рекомендация) | меньше — стек поднимется, но будет медленным |
**Карта публикуемых портов** (дефолты; конфигурируемы в
`deploy/bundled/.env`, ключи `BUNDLE_*`):
| Порт | Ключ | Сервис |
|------|------|--------|
| 8500 | `BUNDLE_ORCH_PORT` | API оркестратора (`/health`, `/queue`, `/metrics`, вебхуки) |
| 8080 | `BUNDLE_PLANE_PORT` | Plane UI (proxy) |
| 3000 | `BUNDLE_GITEA_HTTP_PORT` | Gitea web/API |
Postgres/redis/rabbitmq/minio наружу **не публикуются** (машинный трафик —
внутрисетевой сервис-DNS).
```bash
free -g # RAM ≥ 8 GB
df -h . # свободно ≥ 40 GB
nproc # ≥ 4
ss -ltn | grep -E ':(8500|8080|3000)\b' || echo "ports free"
```
**Проверка:** ресурсы не ниже минимумов и `ports free` — PASS. Порт занят →
смените соответствующий `BUNDLE_*`-ключ в §5 (или освободите порт) — иначе
preflight откажет (FAIL до мутаций, это штатно).
---
## 3. Предусловия
Софт хоста: Docker Engine + Compose v2, git, python3 (+venv), sudo у оператора.
```bash
uname -sm # Linux x86_64
docker --version && docker compose version
git --version && python3 --version
python3 -m venv --help >/dev/null && echo "venv: ok"
getent group docker # третье поле — gid, понадобится в §5 (ORCH_DOCKER_GID)
id -u && id -g # uid/gid оператора (ORCH_RUN_UID / ORCH_RUN_GID)
```
**Проверка:** все команды отвечают без ошибок, gid группы docker известен —
PASS; что-то отсутствует — FAIL (доставьте пакет средствами дистрибутива).
---
## 4. Получение кода
Переносится **только код** — чекаут репо `orchestrator` (норматив §12).
```bash
git clone <ORCHESTRATOR_GIT_URL> <путь-чекаута>
cd <путь-чекаута>
ls deploy/bundled/docker-compose.yml deploy/bundled/.env.example \
scripts/bootstrap_bundle.py scripts/gen_secrets.py scripts/onboard_project.py
```
**Проверка:** все пять файлов на месте — PASS. Канал дистрибуции
(`<ORCHESTRATOR_GIT_URL>`) согласуйте с поставщиком платформы (как в
`LITE_SETUP.md` §3).
---
## 5. Секреты
Все секреты инсталляции выпускаются **заново на месте** (§12): webhook-секреты —
`scripts/gen_secrets.py`, внутренние креды Plane/Gitea-стека — генерирует
bootstrap (в репо — только пустые плейсхолдеры, ни одного дефолтного пароля).
**5.1. Конфиг bundle-инфры.**
```bash
cd <путь-чекаута>
cp deploy/bundled/.env.example deploy/bundled/.env
chmod 600 deploy/bundled/.env
# заполнить НЕсекретные ключи: BUNDLE_PUBLIC_HOST (IP/имя хоста для браузера),
# карту портов BUNDLE_* (§2), ORCH_RUN_UID/ORCH_RUN_GID (из §3),
# ORCH_DOCKER_GID (getent group docker, §3), пути Claude CLI (§8).
```
**Проверка:**
```bash
docker compose -f deploy/bundled/docker-compose.yml config --quiet && echo "config: PASS"
```
`config: PASS` — интерполяция согласована; ошибка — FAIL (опечатка в
`deploy/bundled/.env`).
**5.2. Секрет-значения** (пустые ключи `deploy/bundled/.env` и корневого `.env`)
заполнит `bootstrap_bundle.py apply` (§7): webhook-секреты — субпроцессом
`gen_secrets.py`, креды postgres/rabbitmq/minio/`SECRET_KEY` Plane и пароль
админ-бота Gitea — stdlib-генератором. Значения **не печатаются** (только имена
ключей); повторный запуск **не перетирает** существующие секреты (явная
регенерация — флаг `--force-secrets`, допустим только ДО первого запуска стека).
```bash
grep -cE '^(POSTGRES_PASSWORD|SECRET_KEY|RABBITMQ_DEFAULT_PASS|MINIO_ROOT_PASSWORD|GITEA_ADMIN_PASSWORD)=$' \
deploy/bundled/.env
```
**Проверка:** до §7 счётчик `5` (пустые плейсхолдеры) — PASS; после §7 — `0`.
---
## 6. Запуск bundle-compose
Одна команда поднимает весь стек (≈ 16 контейнеров; первый запуск тянет образы
и гоняет миграции Plane — это минуты, не секунды).
```bash
docker compose -f deploy/bundled/docker-compose.yml up -d
docker compose -f deploy/bundled/docker-compose.yml ps
```
**Проверка:** все сервисы в состоянии `Up`/`Up (healthy)`; `migrator`
`Exited (0)` (одноразовая миграция) — PASS. Контейнер в рестарт-цикле — FAIL
(§14). Шаг идемпотентен; можно пропустить — `bootstrap_bundle.py apply` выполнит
`up -d` сам (§7).
---
## 7. Bootstrap
Доводка «одним запуском»: preflight → секреты → up/готовность → init Gitea
(полностью автоматом: админ-бот + API-токен) → init Plane → онбординг
sandbox-проекта **строго** кирпичом `onboard_project.py` (22 канонических
статуса, включая fail-closed **`Confirm Deploy`** и **`STOP`**, лейблы,
репо+webhook — golden source `docs/operations/ONBOARDING.md` §1) → git-доступ
агентов → сборка `.env`/`.env.watchdog` → health.
```bash
python3 scripts/bootstrap_bundle.py # план + preflight-диагностика (ноль мутаций)
python3 scripts/bootstrap_bundle.py apply # полный прогон
```
**Manual-step чекпоинты Plane CE** (API первичной инициализации в CE нет;
каждый чекпоинт: точная инструкция → подтверждение → верификация результата
API-пробой, молчаливый пропуск запрещён):
1. **instance setup** — открыть Plane UI, зарегистрировать первого
пользователя (станет администратором инстанса);
2. **workspace** — создать workspace, ввести его slug в bootstrap;
3. **API-токен** — Workspace Settings → API tokens, вставить значение в
bootstrap (ввод скрыт; уходит в `ORCH_PLANE_API_TOKEN`);
4. **workspace-webhook** — bootstrap регистрирует сам (запись в Postgres
инсталляции, путь Б канона `LITE_SETUP.md` §5.4) и проверяет; при отказе —
честный ручной шаг с той же проверкой;
5. **порядок статусов на доске** — drag-and-drop по отчёту onboard
(`docs/operations/ONBOARDING.md`).
Exit-коды: `0` — успех; `2` — остановка на manual-step/предусловии (выполните
шаг и перезапустите `apply` — завершённые шаги пропускаются, повторный запуск
безопасен); `1` — ошибка. Пароль админ-бота Gitea — ключ `GITEA_ADMIN_PASSWORD`
в `deploy/bundled/.env` (права 600; вход в UI Gitea под
`GITEA_ADMIN_USERNAME`).
**Проверка:**
```bash
python3 scripts/bootstrap_bundle.py verify && echo "bootstrap: PASS"
```
`verify` зелёный (health/queue/metrics + onboard verify) — PASS; exit 2 —
остались ручные пункты отчёта; exit 1 — FAIL (§14).
---
## 8. LLM (claude CLI)
Канон — `LITE_SETUP.md` §7 (полностью применим; не дублируется). Кратко: на
хост ставятся claude-code + node, выполняется интерактивный логин CLI; пути
прописываются в `deploy/bundled/.env` (это источники маунтов контейнера орка):
`ORCH_HOST_CLAUDE_CODE_DIR`, `ORCH_HOST_NODE_BIN`, `ORCH_HOST_CLAUDE_DIR`,
`ORCH_HOST_CLAUDE_JSON`.
```bash
claude --version
docker compose -f deploy/bundled/docker-compose.yml exec orchestrator /usr/bin/claude --version
```
**Проверка:** обе команды печатают версию — PASS; вторая падает — пути в
`deploy/bundled/.env` не указывают на фактические каталоги хоста (§14.4).
---
## 9. Telegram
Канон — `LITE_SETUP.md` §8 (два независимых бота, C-1: токен орка для watchdog
переиспользовать запрещено). Ключи орка (`ORCH_TELEGRAM_BOT_TOKEN`,
`ORCH_TELEGRAM_CHAT_ID`) — в корневой `.env`; ключи watchdog
(`WATCHDOG_TG_BOT_TOKEN`, `WATCHDOG_TG_CHAT_ID`) — **только** в `.env.watchdog`
(файл-носитель, `LITE_SETUP.md` §4.3). Шаг опционален: пустые токены =
деградация только нотификаций.
```bash
grep -E '^ORCH_TELEGRAM_(BOT_TOKEN|CHAT_ID)=' .env
grep -E '^WATCHDOG_TG_(BOT_TOKEN|CHAT_ID)=' .env.watchdog
docker compose -f deploy/bundled/docker-compose.yml up -d orchestrator orchestrator-watchdog
```
**Проверка:** ключи заполнены и контейнеры пересозданы → тестовое сообщение от
обоих ботов (`getMe` — команды в `LITE_SETUP.md` §8) — PASS; пусто — осознанный
PASS без нотификаций.
---
## 10. Онбординг следующих проектов
Sandbox-проект создал bootstrap (§7). Каждый следующий проект заказчика —
штатный runbook `docs/operations/ONBOARDING.md` поверх bundle-инсталляции; для
команд из чекаута: Plane/Gitea доступны на `localhost`-портах §2, webhook-URL —
in-network `http://orchestrator:8500/webhook/gitea`.
```bash
. .venv/bin/activate # venv создан bootstrap'ом (§7)
python3 scripts/onboard_project.py plan \
--name "<имя проекта>" --repo <repo> --prefix <PREFIX> \
--stack "<стек>" --test-cmd "<команда тестов>" \
--prod-port <порт-прода> --staging-port <порт-staging> \
--webhook-url http://orchestrator:8500/webhook/gitea
# план устроил → apply → verify (как в LITE_SETUP.md §10), затем:
# строку ORCH_PROJECTS_JSON из отчёта — в .env и пересоздать орк:
docker compose -f deploy/bundled/docker-compose.yml up -d --force-recreate orchestrator
```
**Проверка:** `verify` зелёный; `GET /queue` отвечает после пересоздания — PASS.
---
## 11. Smoke
Процедура — чек-лист `docs/operations/REPLICATION.md` §4 (шаги 05; шаг 6 «до
`done`» — опционально) поверх bundle-инсталляции, без форка. Минимальный сигнал
«конвейер доехал»: issue в sandbox-проекте Plane → статус **To Analyse**
артефакты `01``04` в ветке задачи.
```bash
curl -fsS http://127.0.0.1:8500/health
curl -fsS http://127.0.0.1:8500/queue | python3 -m json.tool | head -30
curl -fsS http://127.0.0.1:8500/metrics | python3 -m json.tool | head -10
# создать issue в Plane (порт 8080) → перевести в «To Analyse», затем:
curl -fsS http://127.0.0.1:8500/queue | python3 -m json.tool | head -40 # job появился
git -C deploy/bundled/repos/sandbox fetch origin
git -C deploy/bundled/repos/sandbox ls-tree --name-only origin/<ветка-задачи> "docs/work-items/<id>/"
```
**Проверка:** оба направления связности живы — job в `/queue` (Plane→орк
доехал), `ls-tree` показывает `01-brd.md``04-test-plan.yaml` (орк→Gitea
пишет; Gitea→орк события идут) — PASS. Любой шаг FAIL → тираж FAIL: соберите
`docker compose -f deploy/bundled/docker-compose.yml logs --tail 100 orchestrator`
и снапшот `GET /queue`, разбор — §14. (Порты замените, если меняли `BUNDLE_*`.)
---
## 12. Stateless-проверка
**Нормативно: данные/задачи/секреты/БД боевого (исходного) хоста НЕ
переносятся** (зеркало `docs/operations/REPLICATION.md` §5). Все тома bundle
созданы заново при первом `up`; секреты — только свежевыпущенные (§5); в
Plane/Gitea инсталляции нет чужих задач/репо/пользователей.
```bash
docker volume ls --format '{{.Name}}' | grep '^orchestrator-bundle' # только тома этой инсталляции
curl -fsS http://127.0.0.1:8500/queue | python3 -m json.tool # счётчики нулевые
```
**Проверка:** в `/queue` нулевые счётчики и ни одной чужой задачи (никаких
work-item исходного хоста) — PASS. Чужая задача/перенесённый файл БД — FAIL:
инсталляция собрана не stateless, выполните полный сброс (§13) и повторите.
---
## 13. Остановка и полный сброс
Teardown — **только эта документированная процедура** (в bootstrap delete-режима
сознательно нет, ADR-001 D9).
**Остановка (обратимая):**
```bash
docker compose -f deploy/bundled/docker-compose.yml down
```
**Проверка:** `docker compose -f deploy/bundled/docker-compose.yml ps` пуст;
тома целы (`docker volume ls | grep orchestrator-bundle`) — PASS.
**Полный сброс (НЕОБРАТИМО — удаляет все данные Plane/Gitea/орка):**
```bash
docker compose -f deploy/bundled/docker-compose.yml down -v
rm -rf deploy/bundled/data deploy/bundled/repos
rm -f deploy/bundled/.env .env .env.watchdog
```
**Проверка:** `docker volume ls --format '{{.Name}}' | grep -c '^orchestrator-bundle'`
`0`; live-конфигов нет — PASS (хост чист, можно разворачивать заново с §5).
---
## 14. Траблшутинг
Формат: симптом → диагностика → лечение.
**14.1. Webhook не доходит (issue в Plane есть, job в `/queue` нет).**
```bash
docker compose -f deploy/bundled/docker-compose.yml logs --tail 50 orchestrator | grep -i "webhook\|signature"
docker compose -f deploy/bundled/docker-compose.yml exec -T plane-db \
psql -U plane -d plane -c "SELECT url, is_active FROM webhooks;"
```
Лечение: (а) нет строки webhook → §7 чекпоинт 4; (б) URL не
`http://orchestrator:8500/webhook/plane` → исправьте на in-network URL;
(в) 401/HMAC → секрет в Plane обязан байт-в-байт совпадать с
`ORCH_PLANE_WEBHOOK_SECRET` корневого `.env`. Для Gitea-направления проверьте
Recent Deliveries в настройках hook'а репо; помните про
`GITEA__webhook__ALLOWED_HOST_LIST=orchestrator` в bundle-compose (без него
Gitea молча режет вебхуки в приватные адреса).
**14.2. Не хватает RAM / OOM (контейнеры Plane в рестарт-цикле).**
```bash
free -g && docker stats --no-stream | head -20
docker compose -f deploy/bundled/docker-compose.yml ps
```
Лечение: минимум §2 (8 GB; Plane ≈ 14 контейнеров). Меньше — добавьте RAM/swap;
preflight bootstrap отказывает заранее именно поэтому.
**14.3. Порт занят (`up` падает с bind error).**
```bash
ss -ltnp | grep -E ':(8500|8080|3000)\b'
```
Лечение: смените `BUNDLE_ORCH_PORT`/`BUNDLE_PLANE_PORT`/`BUNDLE_GITEA_HTTP_PORT`
в `deploy/bundled/.env` и повторите `up`/bootstrap.
**14.4. claude не найден / агент падает на старте.**
```bash
docker compose -f deploy/bundled/docker-compose.yml exec orchestrator /usr/bin/claude --version
ls "$(grep '^ORCH_HOST_CLAUDE_CODE_DIR=' deploy/bundled/.env | cut -d= -f2)"
```
Лечение: пути `ORCH_HOST_*` в `deploy/bundled/.env` обязаны указывать на
фактические каталоги хоста; креды CLI читаемы uid'ом `ORCH_RUN_UID` (канон —
`LITE_SETUP.md` §7/§13.3); после правки — `up -d --force-recreate orchestrator`.
**14.5. Миграции Plane не завершились (bootstrap падает на ожидании).**
```bash
docker compose -f deploy/bundled/docker-compose.yml logs --tail 50 migrator plane-db
docker compose -f deploy/bundled/docker-compose.yml ps plane-db plane-mq plane-redis
```
Лечение: чаще всего — нехватка RAM/диска (§14.2) или невыпущенные секреты
(пустой `POSTGRES_PASSWORD` → postgres не стартует; прогоните §7, который
заполняет креды ДО `up`). После лечения — повторный `apply` (идемпотентен).
**14.6. PR задачи не мержится / HOLD.** Branch protection на `main` в Gitea
**НЕ включать** — норматив `LITE_SETUP.md` §6.4 (ломает PR-merge API
merge-актора); bundle-Gitea конфигурируется тем же правилом.
```bash
curl -fsS -H "Authorization: token $ORCH_GITEA_TOKEN" \
"http://127.0.0.1:3000/api/v1/repos/<owner>/<repo>/branch_protections" | python3 -m json.tool
```
Лечение: непустой список правил → удалить (канон `LITE_SETUP.md` §6.4/§13.7).
---
*Golden source Bundled-тиража (ORCH-103, ADR-001 D10). **Норматив сопровождения
(NFR-5):** меняешь шаги Bundled-тиража (состав bundle-compose, ключи
`deploy/bundled/.env.example`, шаги bootstrap, smoke) → обнови этот док В ТОМ ЖЕ
PR. Полноту и гигиену держит `tests/test_bundled_setup_doc.py`; кирпичи-каноны:
`LITE_SETUP.md` (§5§8 — подключения), `docs/operations/ONBOARDING.md` (статусы
§1, онбординг), `docs/operations/REPLICATION.md` (карта env §2, секреты §3,
smoke §4), `deploy/bundled/.env.example` + `.env.example` /
`.env.watchdog.example` (каноны ключей).*

View File

@@ -0,0 +1,596 @@
# LITE_SETUP — Lite-тираж: оркестратор + watchdog на вашей инфраструктуре (ORCH-102)
> **Golden source Lite-тиража (Type A эпика ORCH-10).** Сквозной маршрут
> «голый хост → работающий конвейер» для внешнего оператора/заказчика. Каждый шаг —
> исполняемая команда + явная проверка результата (**Проверка:** / PASS / FAIL).
> Хост-специфика в командах — только плейсхолдеры `<...>` и `$ENV_VAR`.
> Тираж **stateless**: данные/задачи/секреты исходного (боевого) хоста **НЕ переносятся**
> ни на одном шаге — всё создаётся заново (§12).
---
## 1. Рамка Lite
**Что разворачиваем:** два контейнера платформы — `orchestrator` (конвейер, порт
`ORCH_DEPLOY_PROD_TARGET_PORT`, дефолт 8500) и `orchestrator-watchdog`
(независимый sidecar-мониторинг). Третий сервис `orchestrator-staging` существует в том же
compose-файле, но строго за профилем `staging` и в базовом Lite-контуре не поднимается (§9).
**Что заказчик ставит и администрирует сам** (вне этой инструкции — как продукты):
- **Plane** (task-management) — своя инсталляция; здесь только её *подключение* (§5);
- **Gitea** (git-хостинг) — своя инсталляция; здесь только её *подключение* (§6);
- **Telegram-боты** (нотификации) — свои боты (§8);
- **LLM-доступ** (claude CLI + node) — свой дистрибутив и аутентификация (§7).
**Границы слоёв тиража** (10-common vs Lite vs Bundled) — `docs/operations/REPLICATION.md` §1.
Этот док собирает кирпичи 10-common (карта env, секреты, smoke) в один маршрут и не форкает их.
**Платформенные конвенции (не менять):**
- репо платформы обязан называться **`orchestrator`** (узел безопасности `SELF_HOSTING_REPO`);
- имена compose-сервисов/профиля (`orchestrator`, `orchestrator-watchdog`,
`orchestrator-staging`, профиль `staging`) — константы платформы;
- контейнерные пути (`/app/data`, `/repos`, `/opt/claude-code`) — layout контейнера,
не хост-значения; не параметризуются.
---
## 2. Предусловия хоста
Поддерживаемый контур: **Linux x86_64, Docker Engine + Compose v2, git, python3, node** +
дистрибутив claude-code на хосте. Вне контура — вне гарантии Lite. Каждое предусловие —
команда проверки; все проверки PASS → можно переходить к §3.
**2.1. ОС и базовые зависимости.**
```bash
uname -sm # Linux x86_64
docker --version # Docker Engine
docker compose version # Compose v2
git --version
python3 --version # 3.x (для scripts/*.py)
node --version # путь к бинарю станет ORCH_HOST_NODE_BIN
```
**Проверка:** все команды отвечают версиями без ошибок — PASS; любая отсутствует — FAIL
(доставить пакет средствами вашего дистрибутива).
**2.2. Пользователь-владелец и uid/gid (инвариант ORCH-040).** Контейнеры бегут под
`ORCH_RUN_UID:ORCH_RUN_GID` — это обязан быть uid:gid владельца каталога репозиториев
`ORCH_HOST_REPOS_DIR`, иначе git-артефакты конвейера не запишутся.
```bash
id -u <deploy-user>; id -g <deploy-user> # значения для ORCH_RUN_UID / ORCH_RUN_GID
mkdir -p <путь-каталога-репозиториев> # станет ORCH_HOST_REPOS_DIR
stat -c '%u:%g' <путь-каталога-репозиториев> # обязан совпасть с ORCH_RUN_UID:ORCH_RUN_GID
```
**Проверка:** uid:gid владельца каталога = будущие `ORCH_RUN_UID`/`ORCH_RUN_GID` — PASS.
**2.3. Группа docker (доступ к docker.sock).**
```bash
getent group docker # третье поле — gid, станет ORCH_DOCKER_GID
```
**Проверка:** строка вида `docker:x:<gid>:...` есть — PASS; группы нет — FAIL
(установите Docker корректно / создайте группу).
**2.4. Каталог ssh-ключей деплой-хука** (понадобится для git-push артефактов агентов и
деплой-хуков; монтируется в `$HOME/.ssh` акторов).
```bash
mkdir -p <путь-ssh-каталога> # станет ORCH_HOST_SSH_DIR
ssh-keygen -t ed25519 -f <путь-ssh-каталога>/id_ed25519 -N "" -C "orchestrator@<host>"
ls -l <путь-ssh-каталога> # ключи читаемы пользователем из 2.2
```
**Проверка:** каталог существует, ключи на месте, владелец — пользователь из 2.2 — PASS.
Публичный ключ добавьте в Gitea (Settings → SSH Keys) на шаге §6.
**2.5. Свободные порты** (дефолты платформы: 8500 — прод, 8501 — staging).
```bash
ss -ltn | grep -E ':(8500|8501)\b' || echo "ports free"
```
**Проверка:** вывод `ports free` — PASS. Порты заняты → выберите другие и на шаге §4
синхронно задайте `ORCH_DEPLOY_PROD_TARGET_PORT``WATCHDOG_METRICS_URL`
`ORCH_POST_DEPLOY_BASE_URL``ORCH_STAGING_PORT` ≠ прод-порт — guard ORCH-058 fail-closed).
---
## 3. Перенос кода
Переносится **только код** — чекаут репо `orchestrator`. **НИКАКИХ** данных, БД или `.env`
с исходного хоста (норматив §12). Watchdog отдельно не переносится: его код — каталог
`watchdog/` того же репо, образ собирается локально compose'ом.
```bash
git clone <ORCHESTRATOR_GIT_URL> <путь-чекаута> # путь станет ORCH_DEPLOY_HOST_REPO_PATH
cd <путь-чекаута>
```
Конкретный канал дистрибуции (`<ORCHESTRATOR_GIT_URL>` — зеркало/архив/доступ к
Gitea поставщика) согласуйте с поставщиком платформы; опционально — `--branch <тег-среза>`.
**Проверка:**
```bash
git -C <путь-чекаута> log --oneline -1 # коммит виден
ls <путь-чекаута>/docker-compose.yml <путь-чекаута>/watchdog/Dockerfile \
<путь-чекаута>/.env.example <путь-чекаута>/.env.watchdog.example
```
Все файлы на месте — PASS.
---
## 4. Конфигурация
`.env` собирается **с нуля от канона `.env.example`** (100% ключей старта; полная карта
переменных и их семантика — `docs/operations/REPLICATION.md` §2). Дефолт каждого ключа =
значению исходного хоста, поэтому задаёте только то, что отличается у вас.
**4.1. Создать `.env` и выпустить webhook-секреты.**
```bash
cd <путь-чекаута>
cp .env.example .env
python3 scripts/gen_secrets.py # печатает свежие ORCH_PLANE_WEBHOOK_SECRET / ORCH_GITEA_WEBHOOK_SECRET
```
Вставьте оба напечатанных значения в `.env`. Секреты выпускаются **только заново**
боевые не копируются (§12).
**4.2. Обязательные ключи нового хоста** (заполняются в `.env` по ходу §5§8):
| Группа | Ключи | Откуда |
|--------|-------|--------|
| Plane | `ORCH_PLANE_API_URL`, `ORCH_PLANE_WEB_URL`, `ORCH_PLANE_WORKSPACE_SLUG`, `ORCH_PLANE_API_TOKEN` | §5 |
| Gitea | `ORCH_GITEA_URL`, `ORCH_GITEA_PUBLIC_URL`, `ORCH_GITEA_OWNER`, `ORCH_GITEA_TOKEN` | §6 |
| Webhook-секреты | `ORCH_PLANE_WEBHOOK_SECRET`, `ORCH_GITEA_WEBHOOK_SECRET` | 4.1 (`gen_secrets.py`) |
| Telegram | `ORCH_TELEGRAM_BOT_TOKEN`, `ORCH_TELEGRAM_CHAT_ID` | §8 |
| Реестр проектов | `ORCH_PROJECTS_JSON`**обязателен**: встроенный fallback несёт Plane-UUID исходного хоста | §10 |
| Хост-параметры | `ORCH_AGENT_HOME_DIR`, `ORCH_HOST_REPOS_DIR`, `ORCH_HOST_CLAUDE_DIR`, `ORCH_HOST_CLAUDE_JSON`, `ORCH_HOST_SSH_DIR`, `ORCH_HOST_CLAUDE_CODE_DIR`, `ORCH_HOST_NODE_BIN`, `ORCH_RUN_UID`, `ORCH_RUN_GID`, `ORCH_DOCKER_GID`, `ORCH_DEPLOY_HOST_REPO_PATH`, `ORCH_AGENT_GIT_NAME`, `ORCH_GIT_EMAIL_DOMAIN` | значения из §2§3 |
| Порты | `ORCH_DEPLOY_PROD_TARGET_PORT``WATCHDOG_METRICS_URL``ORCH_POST_DEPLOY_BASE_URL`; `ORCH_STAGING_PORT` ≠ прод-порт | §2.5 |
**4.3. Конфиг sidecar-watchdog — отдельный файл-носитель.** Sidecar-контейнер читает
**ТОЛЬКО `.env.watchdog`**; ключ `WATCHDOG_ENABLED` (и любой другой `WATCHDOG_*`),
положенный в `.env`, для sidecar **инертен**.
```bash
cp .env.watchdog.example .env.watchdog
# заполнить два ключа: WATCHDOG_TG_BOT_TOKEN / WATCHDOG_TG_CHAT_ID (бота создадим в §8)
```
**Проверка (резолв всей конфигурации):**
```bash
docker compose config >/dev/null && echo "compose config: PASS"
```
`PASS` без ошибок интерполяции — конфигурация согласована; ошибка — FAIL (ищите
незакрытую кавычку/невалидный JSON в `ORCH_PROJECTS_JSON`).
---
## 5. Подключение Plane
Инсталляция Plane — ваша; платформа подключается к ней API-токеном и webhook'ом.
**5.1. Workspace и проект.** Создайте workspace (его slug → `ORCH_PLANE_WEB_URL` /
`ORCH_PLANE_WORKSPACE_SLUG`) и проект под вашу разработку — через UI Plane.
```bash
# базовая доступность API из хоста оркестратора:
curl -fsS "$ORCH_PLANE_API_URL/api/v1/workspaces/<workspace-slug>/projects/" \
-H "X-API-Key: <plane-api-token>" | head -c 200
```
**Проверка:** HTTP 200 и JSON со списком проектов — PASS; 401/403 — токен (5.2) ещё не
выпущен или не имеет прав.
**5.2. API-токен.** Plane UI → Workspace Settings → API tokens → выпустить токен →
`ORCH_PLANE_API_TOKEN` в `.env`. Токен должен иметь право создавать проекты/статусы
(нужно для `onboard_project.py apply`, §10).
**5.3. Модель статусов — НЕ вручную.** Конвейеру нужны **22 канонических статуса** с
точными именами и группами; их создаёт `python3 scripts/onboard_project.py apply` (§10),
полная таблица — `docs/operations/ONBOARDING.md` §1 (golden source; здесь не дублируется).
Два имени фиксируем явно, потому что они **fail-closed** (без них ветка просто не
активируется, без ошибки): **`Confirm Deploy`** (человеческий гейт прод-деплоя) и
**`STOP`** (отмена задачи; обязан быть в группе `cancelled`).
```bash
# после §10 — проверить, что статусы созданы:
curl -fsS "$ORCH_PLANE_API_URL/api/v1/workspaces/<workspace-slug>/projects/<project-uuid>/states/" \
-H "X-API-Key: $ORCH_PLANE_API_TOKEN" | python3 -m json.tool | grep -c '"name"'
```
**Проверка:** счётчик имён = 22 (или больше, если в проекте остались дефолтные статусы
Plane) и среди них `Confirm Deploy` и `STOP` — PASS.
**5.4. Webhook + HMAC.** Приёмник — `POST https://<orchestrator-public-host>/webhook/plane`;
подпись — заголовок `X-Plane-Signature` (HMAC-SHA256, hex digest); секрет — значение
`ORCH_PLANE_WEBHOOK_SECRET` из 4.1. События: Issue, Issue Comment.
**Каверза Plane CE:** webhook **не экспонирован во внешнем `/api/v1`** — настраивается
одним из двух путей.
*Путь А — UI (если ваша сборка Plane его показывает):* Workspace Settings → Webhooks →
Add Webhook → URL + Secret (значение `ORCH_PLANE_WEBHOOK_SECRET`) → события Issue,
Issue Comment → Save.
*Путь Б — прямой SQL в Postgres инсталляции Plane:*
```bash
WORKSPACE_ID=$(docker exec -e PGPASSWORD=<plane-db-password> <plane-db-container> \
psql -U plane -d plane -t -A -c "SELECT id FROM workspaces WHERE slug='<workspace-slug>'")
WEBHOOK_ID=$(cat /proc/sys/kernel/random/uuid)
docker exec -e PGPASSWORD=<plane-db-password> <plane-db-container> psql -U plane -d plane -c "
INSERT INTO webhooks (id, created_at, updated_at, deleted_at, workspace_id, url, is_active,
secret_key, project, issue, module, cycle, issue_comment, is_internal, version)
VALUES ('${WEBHOOK_ID}', NOW(), NOW(), NULL, '${WORKSPACE_ID}',
'https://<orchestrator-public-host>/webhook/plane',
true, '<значение ORCH_PLANE_WEBHOOK_SECRET>', true, true, false, false, true, false, 'v1');
"
```
**Проверка:**
```bash
docker exec -e PGPASSWORD=<plane-db-password> <plane-db-container> psql -U plane -d plane -c \
"SELECT url, is_active FROM webhooks;"
```
Строка с вашим URL и `is_active = t` — PASS. Сквозная проверка доставки — §11 (smoke);
generic-образец команд и формат подписи — `docs/operations/SETUP_WEBHOOKS.md`.
---
## 6. Подключение Gitea
**6.1. Токен.** Gitea UI → Settings → Applications → Generate Token, scope: `repo`,
`admin:repo_hook``ORCH_GITEA_TOKEN` в `.env`.
```bash
curl -fsS -H "Authorization: token $ORCH_GITEA_TOKEN" "$ORCH_GITEA_URL/api/v1/user" | head -c 200
```
**Проверка:** HTTP 200 с JSON вашего пользователя — PASS; владелец репозиториев
(организация/пользователь) → `ORCH_GITEA_OWNER`, браузерный URL → `ORCH_GITEA_PUBLIC_URL`.
**6.2. Репо проекта.** Создаёт `onboard_project.py apply` (§10) — или вручную (пустой
репо + initial push). Чекаут обязан появиться в `$ORCH_HOST_REPOS_DIR/<repo>` (общий
каталог репозиториев из §2.2). Публичный ключ из §2.4 добавьте в Gitea
(Settings → SSH Keys), чтобы акторы могли пушить.
```bash
git -C "$ORCH_HOST_REPOS_DIR" clone <git-url-репо-проекта> <repo>
stat -c '%u:%g' "$ORCH_HOST_REPOS_DIR/<repo>" # владелец = ORCH_RUN_UID:ORCH_RUN_GID
```
**Проверка:** чекаут на месте, владелец совпадает — PASS.
**6.3. Per-repo webhook.** Создаёт `onboard_project.py apply` (§10). Параметры (если
вручную): URL `https://<orchestrator-public-host>/webhook/gitea`, content type `json`,
события **`push` / `pull_request` / `status`**, branch filter `*`, подпись —
`X-Gitea-Signature` (HMAC-SHA256). Секрет — **ОДИН глобальный `ORCH_GITEA_WEBHOOK_SECRET`
на ВСЕ репо** (приёмник валидирует только его; «свой секрет на репо» сломал бы HMAC
остальных).
```bash
curl -fsS -H "Authorization: token $ORCH_GITEA_TOKEN" \
"$ORCH_GITEA_URL/api/v1/repos/<owner>/<repo>/hooks" | python3 -m json.tool
```
**Проверка:** hook с вашим URL и тремя событиями существует, `active: true` — PASS.
**6.4. Норматив защиты `main` (ВАЖНО).** **Branch protection на `main` НЕ включать**
(никаких required-approvals / required-status-checks): merge-актор конвейера мержит PR
строго через Gitea PR-merge API (INV-4), и protection-правила дают 405/409-класс отказов →
ложные HOLD задач (ADR D10 ORCH-009). **pre-receive хуки платформа не вводит** и не
проверяет — защита `main` держится конвенцией (агенты не пушат `main`) + скоупом токенов.
```bash
curl -fsS -H "Authorization: token $ORCH_GITEA_TOKEN" \
"$ORCH_GITEA_URL/api/v1/repos/<owner>/<repo>/branch_protections" | python3 -m json.tool
```
**Проверка:** пустой список `[]` — PASS; есть правила на `main` — FAIL (удалите их,
симптом «PR не мержится / HOLD» — §13.7).
---
## 7. LLM (claude CLI)
Агенты конвейера — процессы claude CLI **внутри контейнера**, но дистрибутив, node и
аутентификация живут **на хосте** и пробрасываются маунтами (источники маунтов =
ключи `.env`).
**7.1. Дистрибутив claude-code и node.** Установите claude-code (npm-дистрибутив
Anthropic) и node на хост. Пути → `.env`:
```bash
which node # → ORCH_HOST_NODE_BIN
npm root -g # каталог глобальных модулей
ls "<npm-root>/@anthropic-ai/claude-code" # → ORCH_HOST_CLAUDE_CODE_DIR
```
**Проверка:** каталог дистрибутива существует и непуст — PASS. Внутри контейнера бинарь
доступен как `ORCH_CLAUDE_BIN` (дефолт менять не нужно).
**7.2. Аутентификация CLI.** Выполните первичный интерактивный логин claude CLI **на
хосте** под пользователем из §2.2 (по инструкции Anthropic к claude-code). Логин создаёт
каталог `~/.claude` и файл `~/.claude.json` — их пути задайте в `ORCH_HOST_CLAUDE_DIR` /
`ORCH_HOST_CLAUDE_JSON`.
```bash
claude --version # CLI работает
sudo -u "#<uid-из-2.2>" test -r <путь-~/.claude>/.credentials.json && echo "creds: PASS"
```
**Проверка:** версия печатается; `creds: PASS` — креды читаемы uid'ом контейнера
(иначе — `chown -R <uid>:<gid>` каталога, симптом §13.3).
**7.3. Модели агентов.** Резолв модели/эффорта — только из конфига (ORCH-41/74):
дефолты канона уже в `.env.example` (`ORCH_AGENT_MODEL_DEFAULT`,
`ORCH_AGENT_EFFORT_DEFAULT` и per-агент ключи рядом) — менять не обязательно.
```bash
grep -E '^ORCH_AGENT_(MODEL|EFFORT)_DEFAULT=' .env
```
**Проверка:** оба ключа присутствуют и непусты — PASS.
---
## 8. Telegram
Каналов **два и они независимы** (C-1 ORCH-100): бот live-трекера оркестратора и
**отдельный** бот sidecar-watchdog. Токен орка для watchdog переиспользовать
**ЗАПРЕЩЕНО** — упавший орк не сможет сообщить о себе своим же ботом.
**8.1. Бот трекера.** BotFather → `/newbot` → токен → `ORCH_TELEGRAM_BOT_TOKEN` в `.env`.
```bash
curl -fsS "https://api.telegram.org/bot<токен-трекера>/getMe"
# напишите боту любое сообщение (или добавьте его в чат), затем:
curl -fsS "https://api.telegram.org/bot<токен-трекера>/getUpdates" | python3 -m json.tool | grep -m1 '"id"'
```
**Проверка:** `getMe``"ok":true`; `id` чата из `getUpdates``ORCH_TELEGRAM_CHAT_ID`
PASS.
**8.2. Watchdog-бот (отдельный).** BotFather → `/newbot` ещё раз → токен и chat-id →
**`.env.watchdog`** (`WATCHDOG_TG_BOT_TOKEN` / `WATCHDOG_TG_CHAT_ID`). Помните о
файле-носителе: эти ключи работают только в `.env.watchdog` (§4.3).
```bash
curl -fsS "https://api.telegram.org/bot<токен-watchdog>/getMe"
grep -E '^WATCHDOG_TG_(BOT_TOKEN|CHAT_ID)=.+' .env.watchdog
```
**Проверка:** `getMe``"ok":true`; оба ключа в `.env.watchdog` непусты — PASS.
---
## 9. Запуск
**9.1. Базовый Lite-контур (дефолт): орк + watchdog.**
```bash
cd <путь-чекаута>
docker compose config --services # ровно: orchestrator, orchestrator-watchdog, orchestrator-staging
docker compose up -d --build
docker compose ps
```
**Проверка:** запущены **ровно два** контейнера — `orchestrator` и
`orchestrator-watchdog`; `orchestrator-staging` НЕ поднялся (он строго за
`profiles: [staging]`) — PASS. Поднялось что-то ещё/меньше — FAIL.
**9.2. Health-чек контрактов.**
```bash
curl -fsS http://127.0.0.1:8500/health
curl -fsS http://127.0.0.1:8500/queue | python3 -m json.tool | head -20
curl -fsS http://127.0.0.1:8500/metrics | python3 -m json.tool | head -10
```
**Проверка:** `/health` → HTTP 200, `"status":"ok"`; `/queue` → штатный JSON
(счётчики очереди); `/metrics` → JSON со `"schema_version": 1` — PASS. (Порт замените,
если меняли `ORCH_DEPLOY_PROD_TARGET_PORT`.)
**9.3. Вилка staging (опционально).** Базовому контуру «гонять СВОИ проекты» staging
**не нужен**. Он нужен ТОЛЬКО если вы регистрируете проект `orchestrator` (self-hosting
развитие самой платформы у себя): стадия `deploy-staging` требует песочницу на
`ORCH_STAGING_PORT` (изолированная БД `./data/staging`; guard ORCH-058: staging-порт ≠
прод-порт, fail-closed).
```bash
cp .env.staging.example .env.staging # заполнить по аналогии с .env
docker compose --profile staging up -d orchestrator-staging
curl -fsS http://127.0.0.1:8501/health
```
**Проверка (только для этой вилки):** `/health` staging → 200 — PASS.
---
## 10. Регистрация проекта заказчика
Onboarding-CLI создаёт Plane-проект с 22 статусами и лейблами (`autoApprove` /
`autoDeploy` / `Bug`), Gitea-репо с webhook'ом, скелет репо (kit) и печатает merged-реестр.
Полный runbook — `docs/operations/ONBOARDING.md`; Lite-последовательность:
```bash
cd <путь-чекаута>
python3 scripts/onboard_project.py plan \
--name "<имя проекта>" --description "<зачем проект>" \
--repo <repo> --prefix <PREFIX> \
--stack "<стек>" --test-cmd "<команда тестов>" \
--prod-port <порт-прода-проекта> --staging-port <порт-staging-проекта> \
--webhook-url https://<orchestrator-public-host>/webhook/gitea
# план устроил → тот же вызов с apply; затем read-only контроль:
python3 scripts/onboard_project.py verify <те же аргументы>
```
**Проверка:** `apply` завершился без ошибок (exit 0; `2` = остались 🖐 ручные шаги — выполните
их по отчёту), `verify` зелёный — PASS.
Дальше реестр и рестарт:
```bash
# 1) строку ORCH_PROJECTS_JSON=[...] из отчёта apply вставить в .env (заменить целиком);
# 2) дождаться тихого окна и управляемо перезапустить орк:
curl -fsS http://127.0.0.1:8500/queue | python3 -m json.tool | head -20 # нет running-job
docker compose up -d --force-recreate orchestrator
# 3) убедиться, что инстанс жив и реестр подхвачен:
curl -fsS http://127.0.0.1:8500/health
curl -fsS http://127.0.0.1:8500/queue | python3 -m json.tool | head -20
```
**Проверка:** `/health` → 200 после рестарта; в Plane создан проект со статусами
(см. §5.3), в Gitea — репо с webhook (§6.3) — PASS.
---
## 11. Smoke: «конвейер доехал»
Процедура — чек-лист `docs/operations/REPLICATION.md` §4 (шаги 05; шаг 6 «до `done`» —
опционально), без форка; каждый шаг несёт явный PASS/FAIL. Lite-предусловия: §2§10 этого
дока выполнены, проект заказчика зарегистрирован (§10).
Минимальный сигнал «конвейер доехал» (шаги 45 чек-листа): создайте issue в Plane-проекте
и переведите в статус **To Analyse**, затем:
```bash
# задача появилась и analyst-job в очереди:
curl -fsS http://127.0.0.1:8500/queue | python3 -m json.tool | head -40
# по завершении стадии analysis — артефакты 0104 в ветке задачи:
git -C "$ORCH_HOST_REPOS_DIR/<repo>" fetch origin
git -C "$ORCH_HOST_REPOS_DIR/<repo>" ls-tree --name-only origin/<ветка-задачи> "docs/work-items/<id-задачи>/"
```
**Проверка:** в `/queue` виден job задачи; `ls-tree` показывает `01-brd.md`
`04-test-plan.yaml` — PASS.
**Итоговый вердикт:** все шаги чек-листа PASS ⇒ **тираж PASS**; любой шаг FAIL ⇒ тираж
FAIL — соберите `docker logs orchestrator --tail 100` и снапшот `GET /queue`, разбор —
§13.
---
## 12. Stateless-проверка
**Нормативно: данные/задачи/секреты боевого (исходного) хоста НЕ переносятся** (зеркало
`docs/operations/REPLICATION.md` §5). БД создаётся **пустой** при первом старте; `.env` /
`.env.staging` / `.env.watchdog` собраны с нуля (§4); секреты — только свежевыпущенные
(`gen_secrets.py` + чек-лист внешних токенов `docs/operations/REPLICATION.md` §3).
Проверка чистоты развёрнутого инстанса (выполнить ДО первой своей задачи):
```bash
ls -la <путь-чекаута>/data/ # БД появилась только после первого старта
curl -fsS http://127.0.0.1:8500/queue | python3 -m json.tool # счётчики jobs нулевые
```
**Проверка:** в `/queue` нулевые счётчики и НИ ОДНОЙ задачи чужих проектов (никаких
`ORCH-*`/`ET-*` исходного хоста) — PASS. Любая чужая задача/перенесённый файл БД — FAIL:
инстанс собран не stateless, пересоберите `data/` с нуля.
---
## 13. Траблшутинг первичной настройки
Формат: симптом → команда диагностики → лечение.
**13.1. Webhook отвечает 401 / HMAC mismatch.**
```bash
docker logs orchestrator --tail 50 2>&1 | grep -i "webhook\|signature\|401"
```
Лечение: секрет в `.env` (`ORCH_PLANE_WEBHOOK_SECRET` / `ORCH_GITEA_WEBHOOK_SECRET`)
обязан **байт-в-байт** совпадать с секретом в настройке webhook'а (Plane §5.4 / Gitea
§6.3); после правки `.env` — управляемый рестарт (§10). Формат подписи —
`docs/operations/SETUP_WEBHOOKS.md`.
**13.2. Задача в Plane создана, но в оркестраторе не появилась.**
```bash
curl -fsS http://127.0.0.1:8500/queue | python3 -m json.tool | head -30 # есть ли job
docker logs orchestrator --tail 50 2>&1 | grep -i "ignored\|unknown project"
grep ORCH_PROJECTS_JSON .env # uuid вашего проекта в реестре?
```
Лечение: (а) проект отсутствует/с чужим UUID в `ORCH_PROJECTS_JSON` → поправить реестр
(§10) + рестарт; (б) webhook не доставлен → Plane: `SELECT url, is_active FROM webhooks;`
(§5.4), Gitea: Recent Deliveries в настройках hook'а; (в) подпись → §13.1.
**13.3. claude CLI не найден / не аутентифицирован (агент падает на старте).**
```bash
docker exec orchestrator /usr/bin/claude --version
sudo -u "#<uid-из-2.2>" test -r <путь-~/.claude>/.credentials.json && echo "creds: PASS"
```
Лечение: маунты указывают на фактические пути хоста (`ORCH_HOST_CLAUDE_CODE_DIR`,
`ORCH_HOST_NODE_BIN`, `ORCH_HOST_CLAUDE_DIR`, `ORCH_HOST_CLAUDE_JSON` в `.env`); креды
читаемы uid'ом из §2.2 (`chown -R <uid>:<gid>`); при невалидной сессии — повторный логин
на хосте (§7.2) + `docker compose up -d --force-recreate orchestrator`.
**13.4. `docker.sock: permission denied` в логах орка/watchdog.**
```bash
getent group docker # фактический gid
grep ORCH_DOCKER_GID .env # gid в конфиге
```
Лечение: значения обязаны совпадать → выставить `ORCH_DOCKER_GID` = фактическому gid и
`docker compose up -d --force-recreate`.
**13.5. `Permission denied` при создании worktree (права `/repos`, ORCH-040/057).**
```bash
stat -c '%u:%g' "$ORCH_HOST_REPOS_DIR" "$ORCH_HOST_REPOS_DIR/<repo>"
grep -E '^ORCH_RUN_(UID|GID)=' .env
```
Лечение: владелец каталога репо обязан совпадать с `ORCH_RUN_UID:ORCH_RUN_GID`
(§2.2) → `chown -R <uid>:<gid> "$ORCH_HOST_REPOS_DIR"` (включая legacy root-owned файлы)
и пересоздать контейнер.
**13.6. Telegram молчит (нет карточек/алертов).**
```bash
curl -fsS "https://api.telegram.org/bot<токен-трекера>/getMe"
curl -fsS "https://api.telegram.org/bot<токен-watchdog>/getMe"
grep -E '^ORCH_TELEGRAM_(BOT_TOKEN|CHAT_ID)=.+' .env
grep -E '^WATCHDOG_TG_(BOT_TOKEN|CHAT_ID)=.+' .env.watchdog
```
Лечение: оба бота отвечают `"ok":true`; chat-id корректен (бот добавлен в чат / получил
сообщение); ключи watchdog-бота лежат именно в `.env.watchdog``.env` они инертны,
§4.3); пустой токен = режим «логи без отправки» (fail-safe, не ошибка).
**13.7. PR задачи не мержится / задача в HOLD.** Первая проверка — **не включена ли
branch protection на `main`** (§6.4):
```bash
curl -fsS -H "Authorization: token $ORCH_GITEA_TOKEN" \
"$ORCH_GITEA_URL/api/v1/repos/<owner>/<repo>/branch_protections" | python3 -m json.tool
```
Лечение: непустой список правил на `main` → удалить (норматив §6.4); merge выполняет
PR-merge API оркестратора, ручной merge не требуется.
---
*Golden source Lite-тиража (ORCH-102, ADR-001). **Норматив сопровождения (NFR-5):**
меняешь шаги тиража (env-ключи, compose-сервисы, маршрут онбординга, smoke) → обнови
этот док В ТОМ ЖЕ PR (правило агентов №2). Полноту и гигиену дока держит структурный
анти-дрейф тест `tests/test_lite_setup_doc.py`; кирпичи-каноны: REPLICATION.md (карта
env §2, секреты §3, smoke §4), ONBOARDING.md (статусы §1, онбординг), SETUP_WEBHOOKS.md
(формат вебхуков), `.env.example` / `.env.watchdog.example` (канон ключей).*

View File

@@ -0,0 +1,316 @@
# 🧬 ЭПИК: Автономное саморазвитие платформы оркестратора
> **Статус:** концепция v2 (структура согласована Славой 09.06 → ждёт финального апрува → декомпозиция)
> **Автор:** Стрим · **Дата:** 2026-06-09 · **Заказчик:** Слава
> **Связанные:** ORCH-8 (петля самообучения), ORCH-83 (наблюдаемость), ORCH-54 (автономное внедрение, done)
> **Источники:** память орка (инциденты 0609.06), инвентаризация 94 задач Plane, мировые практики (STRATUS NeurIPS'25, ChaosEater ASE'25, self-healing LLM-agents arXiv'26, agentic AIOps, FinOps token-economics).
---
## 0. Зачем это (vision)
Оркестратор уже **автономно внедряет** (ORCH-54: задача проходит analysis→prod без человека). Но автономность исполнения ≠ автономное **развитие**. Сегодня платформу развивает связка Слава+Стрим вручную: ловим инциденты → формулируем уроки → заводим задачи → апрувим.
**Цель эпика:** управляемый самоподдерживающийся контур, где платформа сама замечает свои слабые места И возможности роста, предлагает улучшения как готовые задачи, проводит их через собственный конвейер (ORCH-7 self-hosting) — **под контролем человека на ключевых развилках** (safety > автономность).
**Принцип баланса (коррекция Славы 09.06):** саморазвитие — это НЕ только «не падать и не косячить». Стабильная платформа, которая не растёт в возможностях, — тупик. **Рост функционала (новые фичи, стеки, удобства для заказчиков) — равноценный домен, а не следствие надёжности.** Платформа развивается по двум рукам одновременно: крепнет (надёжность/качество/экономика) И раздаётся вширь (возможности/масштаб).
---
## 1. Архитектура эпика: фундамент + 5 доменов + 2 вертикали
```
┌─────────────────────────────────────────────────────────────┐
│ ВЕРТИКАЛЬ-ДВИГАТЕЛЬ 🧠 ВЕРТИКАЛЬ-ТОРМОЗ 🛑 │
│ 🔄 уроки (крепнем) + governance / safety L0-L3 │
│ 💡 генератор идей (растём) (ограничивает, апрувы) │
│ ░░░░░░░░░░░░ проходят СКВОЗЬ все домены ░░░░░░░░░░░░░░░░░░░░░ │
├─────────────────────────────────────────────────────────────┤
│ ДОМЕНЫ РАЗВИТИЯ (равноценные, две руки роста) │
│ │
│ КРЕПНЕТ ───────────────────► РАЗДАЁТСЯ ВШИРЬ ────────► │
│ 🛡️ D1 Надёжность 🚀 D4 Возможности (фичи) │
│ ✅ D2 Качество/Доверие 📈 D5 Масштаб │
│ 💰 D3 Экономика │
├─────────────────────────────────────────────────────────────┤
│ ФУНДАМЕНТ (слой 0): 👁️ Наблюдаемость + 📒 Журнал уроков │
│ глаза и память — без них всё слепо │
└─────────────────────────────────────────────────────────────┘
Общая метрика-объединитель: 🌡️ ГРАДУСНИК АВТОНОМНОСТИ
(каждый домен двигает её вверх контролируемо)
```
### Что изменилось против v1 (мои же правки по критике)
- **Наблюдаемость вынесена в фундамент** (была внутри M1) — она питает ВСЁ.
- **M0 разбит на 2 вертикали:** двигатель (петля) и тормоз (governance) — у них противоположная логика, нельзя в одну коробку.
- **Добавлен домен D2 Качество/Доверие** — была дыра: надёжная платформа может стабильно генерить говнокод. Надёжность инфры ≠ корректность результата.
- **Рост (D4+D5) — равноценные домены, не «второй эшелон»** (коррекция Славы).
- **Градусник автономности** — сквозная измеримая цель вместо абстракции.
---
## 🏗️ АРХИТЕКТУРНЫЕ РАМКИ наблюдаемости (решено Славой 09.06 — constraints для архитектора)
> Это НЕЗЫБЛЕМЫЕ границы (заказчик). Конкретные ADR (стек, формат метрик, точки врезки) — зона архитектора внутри этих рамок.
**Принцип:** наблюдатель ОТДЕЛЁН от наблюдаемого. Мониторинг НЕ живёт внутри орка — иначе орк упал/завис/съел память → мониторинг ляжет вместе с ним, и мы слепы в самый критичный момент.
**Решения Славы:**
- **С-1. Sidecar-контейнер на том же хосте** (вариант A). Отдельный процесс/память/рестарт — орк падает, наблюдатель жив и РЕПОРТИТ это.
- **С-1б. КОД sidecar — В РЕПО орка** (отдельная папка `watchdog/`), рантайм — ОТДЕЛЬНЫЙ контейнер. Изоляция — на уровне КОНТЕЙНЕРА, не репозитория. Плюсы: (1) конвейер орка пилит свой мониторинг сам (self-hosting ORCH-7); (2) контракт `/metrics`↔sidecar в одном репо — не разъедется (один PR/тесты); (3) один CI. Сборка: ОТДЕЛЬНЫЙ `watchdog/Dockerfile` + сервис `orchestrator-watchdog` в docker-compose.yml. Разовое инфра-действие: добавить сервис в compose + первый запуск (Слава/Стрим на хосте), дальше код watchdog катится через конвейер.
- **С-2. Без внешнего плеча (L2).** Не усложняем второй площадкой. (Принятый риск: падёнвесь хост/Docker → наблюдатель тоже молчит; осознанно.)
- **С-3. Тонкий стек.** НЕ Grafana+Prometheus (+5-6 контейнеров на забитый хост). Тонкий Python/Go sidecar. **Факт хоста 09.06: RAM 171Mi free / 7.7Gi, диск 92%** — ресурсы впритык, наблюдатель обязан быть лёгким.
**Разделение ответственности:**
- **Орк отдаёт только сырьё:** лёгкий read-only `/metrics` (свои внутренние данные — стадии/очередь/agent-liveness/cost, что знает только он). БЕЗ логики мониторинга/алертов/хранения. Орк лёг → endpoint недоступен = САМ сигнал тревоги.
- **Sidecar — мозг мониторинга:** читает `/metrics` орка + хост (диск/память/CPU) + контейнеры (docker.sock read-only) + пинг Plane/Gitea/Anthropic; хранит пороги, шлёт Telegram-алерты СО СВОИМ каналом (не зависит от кода орка).
- **Журнал уроков (F2)** — исключение: это НЕ realtime-мониторинг, а историческая память петли → допустимо в БД орка (аддитивная таблица). Не критично к падению орка в момент (запись best-effort).
---
## 2. ФУНДАМЕНТ (слой 0) — 👁️ Глаза и 📒 Память
Без данных нечем ни чинить, ни считать, ни приоритизировать, ни учиться. Строится первым.
- **F1 Наблюдаемость** (ORCH-83 [ЭПИК]): метрики agent-liveness + очередь + стадии + хост (диск/память/CPU) + контейнеры + внешние деп (Plane/Gitea/Anthropic). Эндпоинты /health /status /queue → расширить до /metrics + дашборд.
- **F2 Журнал уроков** (ORCH-8 шаг 1): машинная структурированная таблица отклонений (тип, контекст, корень, предложение, статус) — формализовать то, что сейчас в memory/. Это «топливо» для вертикали-двигателя.
### 🎯 СКОП НАБЛЮДЕНИЯ — три слоя (решено Славой 10.06)
> Граница «мониторим ПЛАТФОРМУ vs ПРОДУКТЫ на ней». Важно для архитектора и будущих задач — не путать уровни.
- **Слой 1 — проекты как ЗАДАЧИ в конвейере — ✅ В СКОПЕ (F1a/F1b).** ET-задачи в stages/queue/agents `/metrics` — это работа орка (его агенты/очередь/стадии). Sidecar алертит «ET-задача застряла». Здоровье КОНВЕЙЕРА.
- **Слой 2 — проекты как КОНТЕЙНЕРЫ на хосте — ✅ В СКОПЕ (F1b, жив/мёртв).** `enduro-trails-app-1`, `osrm` и пр. через docker.sock ro — Up/healthy/restarting/exited. Общий хост впритык → текущий ET-контейнер вредит орку. Здоровье контейнера как чёрного ящика.
- **Слой 3 — ВНУТРЕННЕЕ бизнес-здоровье продукта — ❌ НЕ В ФУНДАМЕНТЕ, НО НУЖНО (см. ниже).** Эндпоинты ET отвечают 200? карта рендерится? latency не деградировала после фичи? Орк не знает внутренностей задеплоенных приложений — это МОНИТОРИНГ ПРОДУКТА, не платформы.
**Слой 3 — это отдельная продуктовая способность (домен D4/D5):** «per-project мониторинг здоровья задеплоенного приложения» — опция для заказчика («слежу, что твой ET-сайт жив»). **НО он НУЖЕН и самой петле** (см. §8A «атрибуция уроков») — без детекции деградации продукта петле нечего ловить. Порядок: фундамент (слои 1-2) сначала, слой 3 — позже как D4/D5-фича.
---
## 3. ДОМЕН D1 — 🛡️ Надёжность (Self-Repairing)
**Есть:** reconciler (53), post-deploy monitor+rollback (21), merge-verify (71/73), reaper (65), disk-watchdog (63), build-prune (62).
**Уроки:** фантом-merge, deploy-петли, транзиенты, флапп-статусы, зомби-jobs.
- **D1.1** Предиктивный мониторинг (causal, не порог): «диск заполнится через N ч».
- **D1.2** Авто-ремедиация рантайма: каталог типовых фиксов (зомби-job→requeue, stale-lease→reclaim, флапп→форс-терминал).
- **D1.3** Транзиент-резилентность everywhere (обобщение ORCH-93): единый retry+backoff для всех внешних вызовов.
- **D1.4** Zero-downtime деплой платформы (blue-green/canary): резервное плечо вместо окна недоступности.
- **D1.5** Авто-rollback по SLO (расширение 21): откат по деградации latency/error-rate, не только health.
- **D1.6** Deep agent-liveness (self-healing LLM): «думает / завис / зациклился» по reasoning+CPU+прогрессу.
- **D1.7** Backup/restore БД+worktree (recovery после краша хоста).
---
## 4. ДОМЕН D2 — ✅ Качество / Доверие результата
> Новый домен. Закрывает дыру: платформа может надёжно и дёшево производить плохой результат. Надёжность инфры ≠ корректность кода/аналитики.
**Есть:** security-гейт (22), reviewer/tester стадии, промпт-аудит (92).
- **D2.1** Code-coverage гейт (ORCH-27): защита от деградации покрытия.
- **D2.2** Регресс-страж результата: не только «тесты зелёные», но «не сломали соседнюю фичу» (расширение regression-guard ORCH-73).
- **D2.3** Качество аналитики: метрика «BRD не пришлось переделывать», сверка факт vs ТЗ (как сегодня ловила ложное P0).
- **D2.4** Доверие к выходу: provenance артефактов, воспроизводимость, «деплой OK = прод реально работает» (урок ET-8).
- **D2.5** Опциональная человеческая приёмка важных фич (ORCH-28).
- **D2.6** Само-оценка агентов: уверенность в результате → эскалация при низкой.
---
## 5. ДОМЕН D3 — 💰 Экономика
**Боль (ORCH-38):** developer сжёг **$13.68 на мелочь** (cache_read 18.98M — слепое сканирование src/).
- **D3.1** Model-routing cascade (мир: 87%): классификатор сложности → дешёвая модель на простое, opus на сложное (ORCH-20+13).
- **D3.2** Бюджет circuit-breaker (ORCH-23): хард-лимит $/токенов/времени → пауза+алерт.
- **D3.3** Оценка задачи ДО старта (ORCH-20): прогноз $/время по истории.
- **D3.4** Целевые файлы в задании (ORCH-38): analyst даёт точный список из TRZ → нет слепого сканирования. **Самый дешёвый высокий impact.**
- **D3.5** Fast-track простых задач (ORCH-19): багфикс → урезанный цикл без architect, дешёвая модель.
- **D3.6** Semantic caching / prompt compression (мир: 31%).
- **D3.7** Cost-дашборд + детект аномалий.
---
## 6. ДОМЕН D4 — 🚀 Возможности (рост функционала)
> **Равноценный домен (акцент Славы).** Это то, ради чего платформой ПОЛЬЗУЮТСЯ. Без новых возможностей надёжность бессмысленна — нечего надёжно делать. Развивается параллельно с D1-D3, а не после.
**Backlog-зародыши:** ORCH-12/13/14/15/18/24/25.
- **D4.1** Стеки-плагины: профили стека (web/mobile/data/ML/embedded) → агенты адаптируют процесс. Расширяемо без правки ядра. **Открывает заказчикам новые типы проектов.**
- **D4.2** Android/мобильный стек (ORCH-15): полноценная разработка приложений.
- **D4.3** UX/UI-дизайнер (ORCH-14): дизайнер-агент генерит макеты на аналитике, согласование с BRD.
- **D4.4** Интерактивный аналитик (ORCH-18): живой диалог Слава↔analyst — уточнение BRD, обсуждение вариантов до старта. Удобство + качество постановки.
- **D4.5** Тяжёлые вычисления (ORCH-12): воркер/стадия для долгих расчётов (ML-обучение, миграции данных).
- **D4.6** База знаний проекта (ORCH-24): RAG-контекст решений/архитектуры — агенты умнее (+экономия).
- **D4.7** Декомпозиция эпиков (ORCH-25): эпик→задачи→сборка автоматически (этот документ — кандидат №1).
- **D4.8** Новые роли-агенты: data-engineer, ML-инженер, DevOps — по мере типов проектов.
- **D4.9** Мультипровайдерность моделей (ORCH-13): не только Claude — выбор под задачу/стек/бюджет.
---
## 7. ДОМЕН D5 — 📈 Масштаб
> Вторая «рука роста»: способность делать БОЛЬШЕ и ШИРЕ. Сейчас потолок — `max_concurrency=1`.
**Backlog-зародыши:** ORCH-9/10; done: ORCH-6 (multi-repo), ORCH-88 (serial-batch).
- **D5.1** Параллельная разработка (снять max_concurrency=1): безопасный N>1 (изоляция worktree есть, нужна merge-orchestration FIFO + защита main). **Много фич параллельно = быстрее растём.**
- **D5.2** Turnkey-онбординг проекта (ORCH-9): команда → Plane+Gitea+агенты+инфра за минуты.
- **D5.3** Тиражирование на новый хост (ORCH-10): перенос платформы на инфру нового заказчика (IaC-bundle).
- **D5.4** Горизонтальный воркер-пул: очередь jobs (ORCH-1) → несколько воркеров/хостов.
- **D5.5** Per-project лимиты ресурсов (concurrency/бюджет на проект).
- **D5.6** Мультитенантность (отложено — SaaS-сценарий, по спросу).
---
## 8. ВЕРТИКАЛЬ-ДВИГАТЕЛЬ 🧠 — две турбины: реактивная + проактивная
> Двигатель питается из ДВУХ источников (коррекция Славы 09.06). Реактивная турбина (уроки из боли) кормит «крепнем» (D1-D3). Проактивная (генератор идей) кормит «растём» (D4-D5). Без второй турбины рост фич зависит только от Славы — бутылочное горлышко.
### 8A. Реактивная турбина 🔄 — петля самообучения из уроков (ORCH-8)
```
ДЕТЕКЦИЯ → ЖУРНАЛ урока → АНАЛИЗ/паттерны → ПРЕДЛОЖЕНИЕ задачи → [governance-гейт] → конвейер ORCH-7 → проверка эффекта → журнал
```
- **Детекция:** провал гейта, **ручное вмешательство (самый ценный сигнал — каждый ручной пинок = дыра автономности)**, ретраи/откаты/таймауты, ложные срабатывания, «деплой OK / прод сломан».
- **Анализ (гибрид):** машина копит и предлагает черновик → Стрим фильтрует/оформляет → Слава апрувит.
- **E1** Журнал уроков (=F2). **E2** Агент-ретроспективщик (анализ→предложение).
#### ⚖️ АТРИБУЦИЯ урока — platform-level vs project-level (решено Славой 10.06)
> Ключевой шаг петли. Пример Славы: выпустили фичу в ET → она деградировала ET. Петля поймала сигнал — но ЧЬЯ вина и ГДЕ чинить?
Когда детектирована деградация продукта после выпуска фичи, петля ДОЛЖНА различить два уровня вины и направить урок в правильное русло:
- **А. Platform-level (недоработал ОРК):** конвейер выпустил деградацию, потому что у платформы СЛАБЫЙ ПРОЦЕСС (нет регресс-гейта «фича не ломает соседнее», тест-стадия не ловит деградацию производительности, нет производительностного бенчмарка в приёмке). → улучшаем ПРОЦЕСС орка (домен **D2 Качество** / **D1 Надёжность**). Чинится ОДИН раз — выигрывают ВСЕ проекты.
- **Б. Project-level (недоработал ПРОЕКТ):** процесс орка нормальный, но в конкретном ET МАЛО тестов/слабая приёмка под этот тип фич. → усиливаем ТЕСТЫ/приёмку В САМОМ ET (задача в бэклог ET). Чинится точечно — выигрывает только ET.
**Механизм (новый шаг петли):**
```
ДЕТЕКЦИЯ деградации продукта (слой 3) → урок →
АТРИБУЦИЯ: platform-level или project-level?
├─ platform → задача в D1/D2 (улучшить процесс — польза всем)
└─ project → задача в бэклог ET (усилить тесты ET — польза ET)
(развилка не всегда бинарна — бывает ОБА: и гейт в орк, и тесты в ET)
```
Без атрибуции петля «чинит платформу» там, где надо усилить проект (и наоборот). **Зависит от слоя-3 детекции** (§2): без мониторинга здоровья продукта петле нечего атрибутировать. **E2-ретроспективщик** несёт эту классификацию; спорные случаи → Стрим/Слава решают.
### 8B. Проактивная турбина 💡 — генератор идей новых возможностей (НОВОЕ — запрос Славы)
> Отдельный источник идей роста функционала — НЕ только требования от Славы. Проактивно предлагает новые фичи/возможности/удобства. Та же воронка: машина/агент генерит черновики → Стрим фильтрует → Слава решает.
**Источники идей (вход генератора):**
- **I1 Гэпы реализации:** чего НЕ хватило для запрошенных проектов (enduro-trails, snowbike — что было тяжело/невозможно сделать платформой → кандидат в фичу).
- **I2 Паттерны ручного труда:** что Слава/заказчики часто делают руками ВНЕ платформы → кандидат на автоматизацию/фичу.
- **I3 Тренды и новые технологии:** сканирование новых моделей/стеков/инструментов (web-поиск, release-notes провайдеров) → «вышла модель X / фреймворк Y — даёт новую возможность».
- **I4 Конкурентный/рыночный анализ:** что умеют другие AI-платформы разработки (Devin, Cursor, Copilot Workspace…) → чего нет у нас.
- **I5 Анализ собственного бэклога/истории:** паттерны типов задач → «часто просят X → стоит сделать шаблон/фичу».
- **I6 Обратная связь заказчиков:** явные пожелания/жалобы по реализованным проектам.
- **I7 Саморефлексия Стрим:** я вижу работу платформы изнутри каждый день — предлагаю удобства/фичи из опыта ведения.
**Компоненты:**
- **E4 Агент-идеатор (product-discovery):** по расписанию сканирует I1-I7 → генерит бэклог идей-черновиков фич (с обоснованием «зачем/кому/из какого источника»).
- **E5 Банк идей:** отдельный реестр (не путать с журналом уроков): идея, источник, предполагаемая ценность, статус (new/отклонена/в работе).
### 8C. Общий выход двигателя
- **E3 Приоритизатор RICE:** сводит ОБА потока (уроки из 8A + идеи из 8B) в единый ранжированный бэклог по impact/cost/risk — что брать первым по всем доменам. Баланс «крепнем vs растём» — настраиваемый (квота слотов на надёжность vs фичи).
---
## 9. ВЕРТИКАЛЬ-ТОРМОЗ 🛑 — Governance / Safety
> «Контроль и управление саморазвитием» (требование Славы). Двигатель жмёт газ — этот контур держит руль и тормоз.
**Принцип (ORCH-8, незыблемо):** самомодификация платформы (промпты/скиллы/конфиги агентов/ядро) — ТОЛЬКО через PR+ревью+апрув Славы. Орк ПРЕДЛАГАЕТ, ПРИМЕНЯЕТ через свой конвейер с гейтами.
**Уровни автономии (agentic AIOps maturity):**
| Уровень | Что авто | Гейт |
|---------|----------|------|
| L0 reactive | только алерт | человек делает всё |
| L1 assistive | предложить задачу+ТЗ | человек апрувит запуск |
| L2 autonomous-bounded | гонит безопасные классы (бэкенд-фиксы) до прода | safety-гейты CI/staging/regression |
| L3 self-modifying | менять агентов/ядро | **всегда** PR+апрув Славы, НИКОГДА не авто |
- **G1** Safety-политика L0-L3 + per-class правила (что можно само, что только через Славу). Лейблы autoApprove/autoDeploy (ORCH-89) = уже зародыш.
- **G2** Бюджет на саморазвитие: лимит $/мес, чтобы контур не жёг бесконтрольно.
- **G3** Дашборд эволюции: метрики 5 доменов в динамике — видно, КУДА развивается платформа.
- **G4** Kill-switch петли: остановить самогенерацию задач одним флагом.
---
## 10. 🌡️ Градусник автономности (сквозная метрика)
Объединяющая измеримая цель эпика. Каждый домен двигает её вверх:
- **% задач без ручного пинка** (сегодня было ~5 вмешательств: апрувы, домерж 063, sync 061).
- **Ручных вмешательств / неделю** (тренд вниз).
- **MTBF / MTTR** платформы (D1).
- **$/задача, токены/задача, время/задача** (D3).
- **Типов проектов/стеков поддержано** (D4).
- **Задач параллельно** (D5).
- **% уроков, ставших задачами** (двигатель).
---
## 11. Связь с Backlog (ничего не теряем)
| Backlog | Домен/вертикаль |
|---------|-----------------|
| ORCH-8 петля | 🧠 Двигатель (ядро) |
| ORCH-83 наблюдаемость | Фундамент F1 |
| ORCH-20/23/38/19 | 💰 D3 |
| ORCH-27/28 | ✅ D2 |
| ORCH-12/13/14/15/18/24/25 | 🚀 D4 |
| ORCH-9/10 | 📈 D5 |
| ORCH-94 флапп | 🛡️ D1.2 |
| ORCH-89 авто-лейблы | 🛑 G1 |
~18 backlog-задач ложатся в структуру. Эпик их систематизирует и достраивает.
---
## 12. Дорожная карта (предложение)
1. **Фаза 0 (фундамент):** F1 наблюдаемость + F2 журнал. Без них рулить нечем.
2. **Фаза 1 (две руки параллельно):**
- крепнем: D3.4 целевые файлы + D3.2 бюджет-breaker (дешёвый impact)
- растём: D4.1 стеки-плагины ИЛИ D4.4 интерактив-аналитик (по спросу)
3. **Фаза 2:** D1 надёжность (транзиент-резилентность, авто-ремедиация) + D2 качество + D5.1 параллелизм.
4. **Фаза 3 (мозг):** E2 ретроспективщик + E3 приоритизатор + G1 safety-политика → петля замыкается, дальше платформа предлагает сама.
---
## ⛓️ Реализация в Plane (решено 09.06)
**Ось ДОМЕНА → модули Plane** (1 задача = 1 модуль; slug в `external_id`, name с эмодзи для человека):
| Модуль (name) | slug (external_id) | module_id |
|---|---|---|
| 👁️ Фундамент | `foundation` | 74dee25a-a44b-4c3b-ab55-1b5638b8cc1f |
| 🧠 Мозг | `brain` | ab1afa08-14ce-4b7d-8ebc-e45ac19b2ba7 |
| 🛡️ Надёжность | `reliability` | abd7479e-4f9b-4a56-a926-cb2ece7558ca |
| ✅ Качество | `quality` | cbf5f8ca-dc1a-4dee-9d35-555459de2b30 |
| 💰 Экономика | `economy` | 9b4bbab3-95d6-4b8a-8d72-379a618ea2f3 |
| 🚀 Возможности | `features` | baa6936c-6a39-4935-ad57-31ef5ffc3041 |
| 📈 Масштаб | `scale` | 18373528-14fa-4627-a0f6-32497ff22177 |
**Ось ВЕРТИКАЛЬ → лейблы** (могут быть несколько, список короткий):
- `engine` (36f398f7-5a1c-4eeb-847a-56c457e1da6b) — задача пришла от петли/идеатора.
- `governance` (9eea4dd8-0fe7-473a-8c40-630fc3ab0d25) — требует апрува L3 / safety-внимания.
- (+ существующие `autoApprove`/`autoDeploy` — ортогональны, режим автономности.)
**Правило раскладки:** каждая задача эпика = 1 модуль-домен (по slug) + 0..N вертикаль-лейблов. Орк ищет/привязывает по `external_id` (не по русскому имени).
⚠️ **Порядок модулей на доске:** Plane API игнорирует `sort_order` на запись (только drag-and-drop в UI). Сейчас порядок перевёрнут (Масштаб сверху) — Славе поправить мышкой (фундамент→мозг→надёжность→качество→экономика→возможности→масштаб). На машинную логику не влияет (орк по slug).
---
## 13. Открытые вопросы Славе
1. **Структура Plane:** мега-эпик с фундаментом+5 доменами+2 вертикалями? Или эпик на каждый домен?
2. **D4 (возможности):** какой стек/фича приоритетны для тебя/заказчиков — Android, UX/UI, тяжёлые расчёты, интерактив-аналитик? С чего рост начинать?
3. **Баланс «крепнем vs растём»:** идти строго параллельно обеими руками, или в каждой фазе перевес в одну сторону?
4. **Safety L3:** подтверждаешь — самомодификация ядра/агентов всегда через твой апрув?
5. **Двигатель (E2/E4):** ретроспективщик + агент-идеатор сразу как агенты, или сначала Стрим ведёт журнал/банк идей вручную?
8. **Генератор идей (8B):** какие из источников I1-I7 тебе ценнее (гэпы проектов / тренды-технологии / конкуренты / саморефлексия Стрим)? Генерить автономно или только по твоему запросу?
6. **Бюджет на эпик (G2):** лимит $/мес?
7. **Первая задача** после апрува: F1 наблюдаемость, быстрая победа D3.4, или сразу рост D4.*?

View File

@@ -0,0 +1,128 @@
# Lessons Learned — 2026-06-05 (вечер): ORCH-17/45/47 + деплой прода
## Итог дня
Закрыты три задачи (ORCH-17, ORCH-45, ORCH-47), два прод-гейта стали умнее, заведено
4 системных задачи в бэклог (ORCH-44/46/48 + B6). Главный сквозной урок: **конвейер не мог
провести эти задачи автономно из-за дыр в самом конвейере** — потребовались ручные merge и
ребилды прода. Корни задокументированы, чинятся отдельными задачами.
---
## 1. ORCH-17 — approve-ping links (закрыта вручную)
Подробный разбор: `docs/history/LESSONS_ORCH-017.md`. Кратко: косметика (2 ссылки)
застряла 5 раз, объективный дедлок shared-гейта, ручной merge PR #37 (`26c6f267`).
---
## 2. ORCH-45 — CI-гонка в `check_ci_green` (исправлена, в проде)
### Проблема
`check_ci_green` делал **один** запрос статуса CI сразу после developer. Если CI ещё
`pending` 1-3 секунды (реальный кейс: опрос 17:58:54 → pending, CI позеленел 17:58:55) —
гейт возвращал False **один раз** и задача застревала насмерть с зелёным CI.
### Решение (PR #39, merge `982698c4`)
Поллинг с ретраем: `success`/`failure` — терминальны (сразу), `pending` → ждать
`CI_POLL_INTERVAL_S`(10с) до `CI_POLL_MAX_ATTEMPTS`(12) раз, истёк лимит → явный
`False` с причиной "CI still pending after Ns" (не виснет молча). Параметры в `config.py`
как env `ORCH_CI_POLL_*`. ADR-0004. +5 тестов (мок httpx + time.sleep).
---
## 3. ORCH-47 — тестер-гейт игнорил `result:` (исправлен, в проде)
### Проблема (уловка-22)
`check_tests_passed`/`_parse_tests_verdict` читал только `verdict:`/`status:` из frontmatter
`13-test-report.md`, но промпт tester-агента велит писать `result: PASS|FAIL`. Честный тестер
(`result: PASS`, без `verdict:`) → гейт «No machine-readable verdict» → ложный FAIL → петля
dev↔review↔tester → Blocked. **И сама ORCH-47 (которая это чинит) попала в тот же капкан:**
в проде крутился старый гейт → не понимал её собственный `result: PASS` → 3 круга петли.
Змея кусает хвост: чтобы пройти гейт автономно, фикс уже должен быть в проде.
### Решение (PR #40, merge `5d04de9e`)
`result:` добавлен как равноправное поле наряду с `verdict:`/`status:`. Любое одно непустое
поле достаточно. Negative-токен (BLOCKED/FAILED) в ЛЮБОМ поле авторитетен (ET-013 кейс
сохранён). Token sets заморожены для обратной совместимости. ADR-001. +6 тестов (68 passed).
После деплоя ручной `advance_stage` пнул застрявшую task → гейт принял `result: PASS`
прошёл testing. Петля исчезла навсегда.
### Остаточная находка → B6 / ORCH-48
На staging деплоер дал 9/10 PASS, завалил **B6 Registry isolation**: staging-реестр видит
боевые ET+ORCH вместо одного sandbox (нарушает «staging — только sandbox»). Деплоер честно
поставил FAILED и НЕ стал натягивать зелёнку (вне мандата) → откат by design. К фиксу гейта
отношения не имеет (E2E против sandbox прошёл). Заведена ORCH-48.
---
## 4. ДЕПЛОЙ ПРОДА — как правильно (важная операционная памятка)
### `/app` запечён в образ, НЕ volume
`docker-compose.yml`: `build: .` + `COPY src/ ./src/`. Поэтому `git pull` + рестарт с
`--no-build` **НЕ довозит код** — нужен `docker compose build orchestrator`. Деплой-хук
(`scripts/orchestrator-deploy-hook.sh`) по дефолту целит в **staging** (by design) — для
прода нужны env `TARGET_SERVICE=orchestrator TARGET_PORT=8500 COMPOSE_PROFILE=''`.
### Порты/профили
- prod orchestrator = порт **8500** (`/health``{"status":"ok"}`), `network_mode: host`,
профиль prod = пустой (стартует обычным `docker compose up -d orchestrator`).
- staging = порт **8501**, профиль `staging` (стартует только `--profile staging`).
### Рабочая последовательность деплоя (проверена дважды 05.06)
1. `sudo chown -R slin:slin /home/slin/repos/orchestrator` (см. грабля ниже).
2. `git checkout main && git reset --hard origin/main && git clean -fd -e '*.bak*' -e '.deploy-prev-image-prod'`.
3. `docker compose build orchestrator`.
4. `docker compose up -d orchestrator` + health-loop на :8500.
5. **Проверка claude-auth** (ребилд её ломает — см. ниже).
6. Проверка что новый код активен в `/app` (grep маркера правки).
### ⚠️ ГРАБЛЯ: хост-репо рассинхронизирован с git (агенты пишут под root)
Хост-репо `/home/slin/repos/orchestrator` оказывался на feature-ветке (не main), а рабочая
копия засеяна untracked+modified файлами, созданными агентами **под uid=0 (root-owned)** прямо
в репо. → `git pull --ff-only` падал `Permission denied` / `would be overwritten`, обычный
`rm` под slin не мог снести root-файлы. **Лечение:** `sudo chown -R slin:slin <repo>`
проверить что modified=совпадает-с-main и untracked=уже-в-main (дубликаты, не теряем) →
`git reset --hard origin/main` + `git clean`. **Хук это НЕ разруливает** — сверять состояние
хост-репо перед каждым деплоем.
### ⚠️ ГРАБЛЯ: ребилд ломает claude-auth (проверять ВСЕГДА)
Пересоздание контейнера может root-овнить `/home/slin/.claude/.credentials.json` и сделать
`/root/.claude` пустышкой → агенты падают `Not logged in`. Защита — монтирование creds в
compose (`/home/slin/.claude` + `.claude.json`), launcher форсит `HOME=/home/slin`.
**После каждого ребилда боевая проверка:**
`docker exec orchestrator bash -c 'cd /tmp && HOME=/home/slin /opt/claude-code/bin/claude.exe --print "ОК"'`
(timeout 90с). 05.06 auth пережил оба ребилда — защита держит.
---
## 5. ЗАПУСК конвейера и Gitea API
### Старт конвейера = Plane Backlog → In Progress
Конвейер стартует штатно переводом задачи в Plane из Backlog в **In Progress** (код:
`webhooks/plane.py handle_status_start` — «pipeline is started when Slava moves the issue
into In Progress»). Webhook создаёт task-row, заводит ветку, запускает analyst. Никаких
ручных вставок в БД.
### QG-0: лимит заголовка 80 символов
При старте задача с заголовком >80 символов заворачивается на QG-0 («Title слишком длинный»)
и уходит в Blocked. Чинить — укоротить `name` (суть в заголовок, детали в description),
вернуть в Backlog, снова In Progress.
### Gitea API грабли
- **merge/create PR** требуют заголовок `Authorization: token <ORCH_GITEA_TOKEN>` (форма
с префиксом `token `), иначе 401 "token is required".
- **heredoc через ssh+docker exec глотает вывод** python-скрипта. Надёжный путь: написать
`.py` локально → `base64 -w0``ssh "echo <b64> | base64 -d > /tmp/x.py"``docker cp`
`docker exec python3 /tmp/x.py`. Это же обходит экранирование кириллицы/скобок.
---
## Состояние прод-гейтов после 05.06
-`check_ci_green` — поллинг с ретраем (ORCH-45)
-`check_tests_passed` — читает `result:`/`verdict:`/`status:` (ORCH-47)
## Бэклог (high) после дня
- **ORCH-44** — надёжность запуска агента (preflight слеп к auth; `--effort` гасит вывод;
пустой run-лог → должен быть failed).
- **ORCH-46** — «испорченный телефон»: орк не передаёт деву ТЕКСТ замечаний reviewer/tester
(только ссылку на файл), противоречивые сигналы tester↔reviewer, нет памяти между кругами.
- **ORCH-48 / B6** — staging registry isolation (staging видит прод-проекты вместо sandbox).

View File

@@ -0,0 +1,78 @@
# Lessons Learned — 2026-06-07: замыкание автономности self-deploy (5 задач в прод)
## Итог
За одну сессию закрыты в прод **5 задач**, завершающих автономный self-deploy эпика ORCH-54:
| Задача | Что | Прод-коммит |
|--------|-----|-------------|
| ORCH-58 | provenance retag-guard (свежесть staging-образа перед BUILD-ONCE) | 094b5e2 |
| ORCH-60 | reconciler не трогает escalated/Blocked/Needs-Input | d4c6cc0 |
| ORCH-61 | фикс петли deploy-staging (staging_verdict: waive sandbox-infra FAILs C9a/C9b) | e18947d |
| ORCH-21 | post-deploy мониторинг прода + auto-rollback (self-hosting=alert-only) | f85e449 |
| ORCH-65 | job-reaper + stale merge-lease reclaim + idempotent merge | bb03350 |
**Главное:** после ORCH-60/61 конвейер впервые провёз задачи (ORCH-21/65) через deploy-staging
**автономно** без отката; после ORCH-65 (job-reaper в проде) зомби-job и зависшие merge-lease
лечатся сами. Последняя ручная точка автономного деплоя закрыта.
---
## Класс багов: «процесс умер — ресурс захвачен навсегда» (ORCH-65)
Три связанных отказа, все воспроизвелись на ORCH-58/60/61/21:
- **zombie jobs:** агент завершился/умер, строка jobs осталась running. requeue_running_jobs()
спасает только на старте процесса; зомби без рестарта не лечился → при concurrency=1 встаёт
конвейер ВСЕХ проектов. (jobs 236/239/242/254/265 — все зомби за сессию.)
- **stale merge-lease:** merge-gate берёт .merge-lease-<repo>.json, делает rebase+re-test green,
а на финальном merge процесс умирает с зажатым lease → merge не докатывается.
- **неидемпотентный merge:** re-drive повторно пытается слить уже слитый PR.
Фикс: фоновый job_reaper (паттерн reconciler, dead_ticks streak + мёртвый pid + exit_code,
атомарный reap-claim, never-raise, kill-switch, снимок в /queue) + проактивный lease-reclaim
по pid + guard pr_already_merged ПЕРЕД merge.
## Петля deploy-staging (ORCH-61) — ДВЕ причины
1. ложный check_staging_status FAILED: staging_check падает на C9a/C9b (sandbox e2e branch +
analyst-job-in-queue), т.к. bot-токены SANDBOX-проекта не настроены — НЕ регресс кода.
2. no-changes для action-стадий (деплой = рестарт/retag, не правка → коммитить нечего).
Фикс: staging_verdict waive sandbox-infra-only FAILs.
## Инфра-каскад от переполненного диска (инцидент дня)
- Частые build-once/--build-staging пересборки за день забили docker build cache до 11 ГБ →
диск 100% → CI red (No space left).
- ДАЖЕ после чистки диска Gitea осталась в сломанном состоянии: внутренняя queue
(/data/gitea/queues/common/*.log) залипла → post-receive hook 500 → actions tasks НЕ
создаются, CI не триггерится вовсе (статус пустой, не failure). runner при этом online+idle.
- Лечение: docker builder prune -af + рестарт Gitea (queue распускается → CI ожил).
---
## Уроки
1. **Self-hosting safety (сквозной принцип):** прод-орк обслуживает ВСЕ проекты. Нельзя авто-
откатывать/рестартить self в рамках задачи; нельзя пушить main. ORCH-21 post-deploy для
self-hosting = alert-only, авто-rollback только для не-self репо.
2. **TDD без доводки (повтор ORCH-58 и ORCH-65 v1):** тесты есть, реализация/wiring не
подключены к боевому пути → мёртвый код + врущая дока. Reviewer обязан грепать вызовы из
прод-кода, не только наличие функции.
3. **Concurrency-баги ловятся итеративно:** ORCH-65 3 прохода reviewer (мёртвый guard → race
condition side-effects-before-claim → approve) — каждый раз НОВЫЙ реальный дефект, не
зацикливание. Atomic-claim ДО side-effects — обязательное правило.
4. **При красном CI + зелёных локальных тестах — ПЕРВЫМ делом df -h / и docker system df**,
не копаться в коде. После disk-full обязателен рестарт Gitea (queue залипает).
5. **Bootstrap-разрыв:** задача про автономность деплоя не может задеплоить себя автономно,
пока её механизм не в проде. Последний прод-деплой каждого такого фикса — вручную.
6. **Перед прод-retag (build-once SOURCE_IMAGE=staging):** проверить revision-label staging-
образа == целевой main HEAD, иначе guard fail-closed (by design). Если != → пересобрать
--build-staging GIT_SHA=<main HEAD>.
## Ручная доводка прод-deploy (схема до ORCH-65 в проде)
cancel zombie job → park task In Progress → merge PR (Gitea pulls/{n}/merge Do=merge, CI green)
→ --build-staging GIT_SHA=<main HEAD> (проставит label) → rollback-снимок → --deploy с
EXPECTED_REVISION=<sha> (guard сверит → retag → health 200) → Plane Done + UPDATE tasks stage=done.
## Follow-up (Backlog)
- ORCH-62: авто-prune docker build cache (cron/daemon.json defaultKeepStorage).
- ORCH-63: мониторинг диска mva154 + алерт >85%.
- ORCH-64: починить NTP/часы mva154 (ушли ~+3ч от UTC).
## Осталось в эпике ORCH-54
ORCH-22 (security-гейт), ORCH-59 (Confirm Deploy статус), ORCH-23 (budget circuit-breaker),
P2: ORCH-57, ORCH-51.

View File

@@ -0,0 +1,33 @@
# Lessons Learned — 2026-06-08: статус `Confirm Deploy` не триггерит Phase B (мёртвый триггер)
## Контекст
ORCH-066 ввела новую статусную модель Plane, включая человекочитаемый статус **`Confirm Deploy`** для прод-деплойного approve-gate (self-deploy Phase B). Орк сам выставляет задачу в `Awaiting Deploy` / `Confirm Deploy` через `set_issue_awaiting_deploy()` и т.п.
## Инцидент (2026-06-08, первый реальный прод-self-deploy — ORCH-068)
Слава нажал статус **`Confirm Deploy`** в Plane, ожидая запуск прод-деплоя. Орк ответил `no pipeline action` и НИЧЕГО не запустил. Прод-деплой стартовал только после ручного перевода в **`Approved`**.
## Root cause
Диспетчер статусов `handle_issue_status` (`src/webhooks/plane.py` ~158-166) слушает РОВНО три состояния:
```python
if new_state == proj_states["to_analyse"]: await handle_status_start(...)
elif new_state == proj_states["approved"]: await handle_verdict(..., approved=True)
elif new_state == proj_states["rejected"]: await handle_verdict(..., approved=False)
else: logger.info("... no pipeline action")
```
Phase B (прод-деплой) триггерится в `_try_advance_stage` (`src/stage_engine.py` ~215-224) при `current_stage == "deploy" and finished_agent is None` — то есть ТОЛЬКО когда пришёл вебхук `Approved`. Статус `Confirm Deploy` в эту тройку НЕ входит → ветка `else` → no-op.
**ORCH-066 добавила статус как МЕТКУ (запись), но не подключила обратный путь (чтение/триггер).** Классическая дыра: протестировали, что орк правильно СТАВИТ статус, но не протестировали, что нажатие этого статуса человеком РЕАЛЬНО запускает действие.
## Почему не поймали тестирование/ревью
1. **Не в scope ORCH-068.** ORCH-068 чинит reconciler (BRD §6 N1-N3 явно: не трогать диспетчер статусов / Phase B). Тестер прогнал TC-01..13 — все про reconciler/terminal-статусы. Ревьюер смотрел diff reconciler.py/plane_sync.py. Корректно — это дефект ORCH-066, не 068.
2. **Дыра ORCH-066.** Её тесты, видимо, проверяли запись статусов, а не обратный триггер.
3. **Staging не покрывает прод-путь.** Phase A (staging-деплой) автоматический, ручной `Confirm Deploy` живёт ТОЛЬКО на прод-пути, который на staging не гоняется. Поэтому всплыло лишь на первом реальном прод-деплое.
## Уроки
1. **Тестировать обратный путь статусов, не только запись.** Для каждого статуса, который человек может нажать, нужен тест «нажатие → ожидаемое pipeline-действие». Запись (орк ставит статус) и чтение (орк реагирует на статус) — два разных контракта.
2. **Прод-only пути (ручной Confirm Deploy) нуждаются в явном тесте/чеклисте.** Staging их не ловит by design. Любой approve-gate, доступный человеку, обязан иметь регресс-тест на триггер.
3. **Новый статус = подключить В ОБЕ стороны.** При добавлении статуса в модель — сразу проверить, что диспетчер `handle_issue_status` его слушает (если он actionable), а не только что орк его выставляет.
4. **UX-консистентность:** статус, названный действием («Confirm Deploy»), обязан выполнять это действие. Иначе оператор жмёт интуитивную кнопку, а система молчит → потеря доверия к автономности.
## Фикс
Заведена ORCH-070: подключить `Confirm Deploy` (или его actionable-эквивалент) к триггеру Phase B в `handle_issue_status`, + регресс-тест на обратный путь статусов прод-деплоя. Source-of-truth и существующий `Approved`-путь не ломать (обратная совместимость).

View File

@@ -0,0 +1,47 @@
# Lessons Learned — 2026-06-08: «Фантомный merge» — прод деплоится, но код не сливается в main
## Severity: CRITICAL (потеря целостности main, накопительная потеря кода между задачами)
## Резюме
Self-deploy (Phase B) собирал прод-образ из ВЕТКИ задачи и рапортовал `finalize SUCCESS` + `post-deploy HEALTHY`, но git-merge ветки в `main` НЕ происходил. PR оставался `open`. Следующая задача срезала свою ветку от устаревшего main → теряла код незалитых предшественников. Накопительно потеряны в main: **ORCH-022, ORCH-059, ORCH-066, ORCH-068** (PR#67/68/69/70 — все open, merged=False). Последний реально слитый — ORCH-065 (PR#66).
## Как обнаружено
Симптом: ORCH-067 переведён в `To Analyse`, но конвейер не стартовал (`no pipeline action`). Причина — прод слушал старый триггер `in_progress`, а не `to_analyse` (ORCH-066). При разборе выяснилось: код ORCH-066 не в проде, хотя он «деплоился».
Решающее наблюдение оператора (Слава): «спам ET-002 начался СРАЗУ после деплоя 66 → значит код деплоился». Это вскрыло механизм: код 66 БЫЛ в проде 22:1705:32, потом стёрт деплоем 068 (срезан от старого main без 66).
## Доказательная база (как подтверждали — воспроизводимый метод)
1. **PR-статус (Gitea API):** PR#67(022)/68(059)/69(066)/70(068) = open, merged=False. PR#66(065) = merged=True (последний честный).
2. **md5-сверка файлов прод vs origin/main vs ветка:**
- `src/reconciler.py`, `src/plane_sync.py`: prod md5 == ветка ORCH-068 != main → прод = снимок ветки 068, НЕ main.
- `src/webhooks/plane.py`: prod == main == ветка-068 (ветка 068 этот файл не трогала → видна старая база без to_analyse).
3. **git merge-base:** ветка ORCH-068 срезана от `bb03350` (ORCH-065), не от кода 066. История ветки-068 по 066 содержит только `docs staging`, кода (`to_analyse`) нет.
4. **Таймлайн логов:** деплой 22:17 (ветка-066, сломанный reconciler) → спам ET-002 начался; деплой 05:32 (ветка-068, база 065 без 66) → спам прекратился (0 после 05:33). Подтверждает: прод-образ = снимок ВЕТКИ, меняется при каждом деплое, теряет незалитое.
## Root cause (гипотеза → нужен код-аудит self_deploy/merge_gate)
Self-deploy Phase B инициирует прод-деплой из worktree ветки (BUILD-ONCE из validated commit). Шаг git-merge ветки в main:
- ЛИБО не вызывается на self-hosting пути (Phase B уходит в detached host-процесс, finalizer пишет SUCCESS-маркеры, но merge отдельно и молча скипается/падает),
- ЛИБО регресс фикса ORCH-065 (idempotent merge / merge-lease reclaim): guard `pr_already_merged` или lease-reclaim ошибочно считает PR уже слитым / не докатывает merge после рестарта контейнера (а Phase B ИМЕННО рестартит контейнер → процесс, державший merge-lease, умирает до завершения merge).
Симптоматически ORCH-065 был последним успешным merge — деградация началась СРАЗУ после него или из-за взаимодействия его механики с self-deploy-рестартом.
## Почему конвейер не заметил
- `finalize SUCCESS` и `post-deploy HEALTHY` маркеры пишутся НЕЗАВИСИМО от факта merge. Пайплайн считает задачу done по этим маркерам, git-состояние main не верифицируется.
- Прод здоров (образ из ветки рабочий) → health-check зелёный → нет сигнала о проблеме.
- Дыра видна только при сравнении main с прод ИЛИ когда следующая задача теряет код предыдущей (что и случилось с 67).
## Уроки
1. **Деплой ОБЯЗАН верифицировать, что код реально в main ПОСЛЕ деплоя.** finalize SUCCESS без проверки `git merge-base origin/main == deployed_commit` (или PR.merged==true) — фальшивый зелёный. Добавить post-merge верификацию: deployed SHA должен быть предком origin/main.
2. **Маркер «deployed» != «merged».** Нельзя считать задачу завершённой по staging/post-deploy-маркерам, если PR не закрыт merge. Гейт: задача → done ТОЛЬКО при PR.merged==true.
3. **Self-deploy рестартит контейнер → любой держатель merge-lease/незавершённый git-шаг умирает.** Merge ДОЛЖЕН завершиться и быть подтверждён ДО рестарта прод-контейнера, либо merge выносится в шаг, переживающий рестарт (как requeue_running_jobs, но для merge-в-main).
4. **Срез ветки от main делает целостность main критичной.** Если main отстаёт — каждая новая задача наследует дыру. main = единственный источник для новых веток, его рассинхрон с прод = накопительная потеря.
5. **Метод диагностики (сохранить как runbook):** при подозрении на рассинхрон — (a) Gitea API PR list merged-флаги, (b) md5 prod-файлов vs `git show origin/main:<file>`, (c) merge-base ветки vs main, (d) таймлайн деплой-логов. Эти 4 проверки однозначно локализуют фантом.
## Действия
- Восстановление main: интеграционная ветка `integ/restore-main-2026-06-08` — последовательный merge 022→059→066→068 (docs union-resolved, reconciler-конфликт 066⊕068 разрешён: каркас 068 livelock-fix + триггер to_analyse 066), полный pytest, затем merge в main + передеплой.
- Заведён критбаг ORCH-071: «фантомный merge — self-deploy без верификации merge в main» (root-fix: post-deploy verify + done-гейт по PR.merged + merge до рестарта).
- ORCH-070 (Confirm Deploy trigger) частично ДУБЛИРУЕТ ORCH-059 (handle_confirm_deploy уже написан в 059) — после долива 059 пересмотреть scope 070 (остаётся только display-слой статусов Monitoring after Deploy).
## Связанные
- ORCH-065 (последний честный merge; подозрение на регресс его merge-механики)
- ORCH-066/068 (потерянный код), ORCH-059 (Confirm Deploy trigger, тоже потерян)
- Урок 2026-06-08 confirm-deploy-deadtrigger (симптом того же корня)

Some files were not shown because too many files have changed in this diff Show More