Files
orchestrator/CHANGELOG.md
claude-bot 6ddff5583d
All checks were successful
CI / test (push) Successful in 19s
CI / test (pull_request) Successful in 18s
fix(ORCH-058): parametrize staging_check in --build-staging + explicit staging target
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

38 KiB
Raw Blame History

Changelog

Формат: Keep a Changelog. Записи — на смысловой PR/задачу.

[Unreleased]

Added

  • Провенанс staging-образа перед BUILD-ONCE retag в прод (свежесть артефакта, INV-FRESH) (ORCH-058): BUILD-ONCE retag (ORCH-036) промоутит staging-образ (orchestrator-orchestrator-staging) в прод без rebuild, полагаясь на «образ свеж и провалидирован» — гарантии не было: конвейер нигде не пересобирал staging-образ из провалидированного коммита, поэтому retag мог тихо промоутнуть УСТАРЕВШИЙ образ (инцидент LESSONS_ORCH-036 п.4 — зелёный деплой молча откатывал прод). Закрыто двумя слоями (defense in depth), только для self-hosting. Новый модуль src/image_freshness.py (контракт «never raise», по образцу merge_gate): provenance_verdict (чистая функция вердикта match/mismatch/fail-closed), validated_revision (git rev-parse HEAD в worktree валидированного коммита — единый якорь и для штампа A, и для EXPECTED_REVISION B), image_revision (OCI-лейбл org.opencontainers.image.revision через docker image inspect, <no value>/ошибка → пусто), rebuild_staging_image (ssh-хук --build-staging), image_freshness_applies (условность), check_staging_image_fresh (композитный QG). Strategy A (liveness): новый детерминированный QG-под-чек check_staging_image_fresh (зарегистрирован в QG_CHECKS, src/qg/checks.py) на ребре deploy-staging → deploy ПОСЛЕ merge-gate и ДО Phase A — пересобирает staging-образ из worktree валидированного коммита (хук --build-staging, --build-arg GIT_SHA=<sha>), пересоздаёт 8501 и прогоняет staging_check.py --mode stub против свежего 8501 (health + e2e, внутри staging-контейнера через docker exec — канон ORCH-048) → валидируем РОВНО тот артефакт (build + e2e), что промоутится в прод (AC-4); FAIL/не-ноль staging_check → откат на development (как merge-gate, кап MAX_DEVELOPER_RETRIES). rebuild_staging_image пробрасывает в хук явный staging-таргет (service/port/profile/container), исключая дрейф на прод 8500. Сборки/recreate/validate — только staging (8501), прод (8500) не трогается. Strategy B (safety): Dockerfile штампует LABEL org.opencontainers.image.revision=$GIT_SHA (ARG GIT_SHA); build_deploy_command (src/self_deploy.py) пробрасывает EXPECTED_REVISION; хост-хук шагом 2b ПЕРЕД docker tag fail-closed сверяет лейбл revision у SOURCE_IMAGE с EXPECTED_REVISION — несовпадение / пустой лейбл / ошибка inspect → exit 1 (FAILED → БАГ-8 откат), делает тихий промоут устаревшего образа структурно невозможным даже при проигравшей гонку/отключённой A. Хост-хук scripts/orchestrator-deploy-hook.sh расширен обратно-совместимым режимом --build-staging (пересборка+recreate staging, exit 0/1) и fail-closed guard'ом (активен только при заданном EXPECTED_REVISION). Единый kill-switch ORCH_IMAGE_FRESHNESS_ENABLED (true) включает A+B как целое (нет «B без A» = вечного fail-fast); область — ORCH_IMAGE_FRESHNESS_REPOS (CSV; пусто → только self-hosting orchestrator). Контракты НЕ менялись: STAGE_TRANSITIONS (под-гейт ребра, не стадия), exit-code-контракт хука (0/1/2), map_exit_code_to_status, check_deploy_status/_parse_deploy_status, БАГ-8, terminal-sync, merge-gate; схема БД — без миграций. ADR docs/work-items/ORCH-058/06-adr/ADR-001-staging-image-provenance.md, глобальный docs/architecture/adr/adr-0008-staging-image-provenance.md. Документация: docs/architecture/README.md, docs/operations/DEPLOY_HOOK.md, docs/operations/STAGING.md, docs/operations/INFRA.md, .env.example. Тесты: tests/test_image_freshness.py, tests/test_deploy_hook_provenance.py, tests/test_deploy_build_once.py (TC-06), tests/test_deploy_hook_mapping.py (TC-09), tests/test_stage_engine.py::TestImageFreshnessGate, tests/test_qg_registry_snapshot.py, tests/test_config.py.
  • Исполняемый самодеплой стадии deploy (стадия дёргает хост-хук, manual-approve) (ORCH-036): стадия deploy перестаёт быть «бумажной» — для self-hosting репозитория orchestrator deploy_status: SUCCESS означает ДОКАЗАННЫЙ health-ok реального рестарта прод-контейнера (8500), а не декларацию LLM. Критический путь self-restart детерминирован (без LLM), по образцу merge-gate ORCH-043, и разбит на три фазы (src/stage_engine.py + новый модуль src/self_deploy.py): Фаза A (вход в deploy) — вместо запуска прод-deployer'а при deploy_require_manual_approve=true задача переводится в approval-pending (set_issue_in_review) и ждёт ручного approve; restart-safe маркер approve-requested. Фаза B (человек ставит статус Plane → Approved; advance_stage(deploy, finished_agent=None)) — запускается detached host-процесс (ssh + setsidscripts/orchestrator-deploy-hook.sh, чтобы рестарт 8500 пережил гибель контейнера; орк НЕ убивает себя из docker.sock) с build-once retag staging-образа (SOURCE_IMAGE), ставится детерминированный finalizer-job; маркер initiated — идемпотентность повторного Approved. Фаза C (run_deploy_finalizer, reserved-agent deploy-finalizer, claim'ится новым контейнером после рестарта) — читает sentinel result (exit-code хука, записан host-обёрткой), not-ready → defer (бюджет deploy_finalize_max_attempts, restart-safe по task_content), маппит 0→SUCCESS / 1|2|иное→FAILED (чистая функция map_exit_code_to_status, unit-тест), пишет 14-deploy-log.md и вызывает advance_stage(deploy, finished_agent="deployer") → существующие контракты: SUCCESS → done + release merge-lease, FAILED → откат БАГ-8 на development + set_issue_blocked. Уведомления Plane+Telegram на approve-request / initiate / success / rollback (BR-5, ни одного «молчаливого» деплоя). Хост-хук scripts/orchestrator-deploy-hook.sh расширен обратно-совместимым SOURCE_IMAGE: при заданном — docker tag $SOURCE_IMAGE $TARGET_IMAGE перед up -d --no-build (деплой РОВНО протестированного образа, без docker build); не задан → прежнее поведение; exit-code-контракт (0/1/2) и health-loop (10×6с, авто-rollback) не тронуты. Restart-safe состояние — sentinel-файлы (<repos_dir>/.deploy-state-<repo>/<work_item_id>/), без миграции БД. Условность как ORCH-35: реальный самодеплой только для is_self_hosting_repo("orchestrator"); прочие репо (enduro-trails) — прежний синхронный ssh-путь агентом. Контракты НЕ менялись: STAGE_TRANSITIONS, реестр QG_CHECKS, check_deploy_status/_parse_deploy_status (frontmatter-only), terminal-sync deploy→done, merge-gate (ORCH-43), БАГ-8. Флаг DEPLOY_REQUIRE_MANUAL_APPROVE остаётся true (полный авто — отдельная задача ORCH-54). Новые настройки: ORCH_DEPLOY_REQUIRE_MANUAL_APPROVE (true), ORCH_DEPLOY_SSH_USER, ORCH_DEPLOY_SSH_HOST, ORCH_DEPLOY_HOOK_SCRIPT, ORCH_DEPLOY_PROD_SOURCE_IMAGE, ORCH_DEPLOY_PROD_TARGET_SERVICE/PORT/IMAGE, ORCH_DEPLOY_FINALIZE_DELAY_S, ORCH_DEPLOY_FINALIZE_MAX_ATTEMPTS. ADR docs/work-items/ORCH-036/06-adr/ADR-001-executable-self-deploy.md, глобальный docs/architecture/adr/adr-0007-executable-self-deploy.md. Документация: .openclaw/agents/deployer.md (стадия deploy = вызов хука, запрет self-restart), docs/operations/INFRA.md, docs/operations/DEPLOY_HOOK.md. Тесты: tests/test_deploy_hook_mapping.py, tests/test_deploy_approve.py, tests/test_deploy_routing.py, tests/test_deploy_rollback.py, tests/test_deploy_notifications.py, tests/test_deploy_build_once.py, tests/test_deploy_terminal_sync.py, tests/test_staging_precondition.py, tests/test_deploy_hook_rollback_sim.py.
  • Sweeper потерянных webhook (реконсиляция застрявших стадий) (ORCH-053): фоновый daemon-поток src/reconciler.py (паттерн queue_worker), который устраняет тихое застревание задач, когда конвейер не двигается из-за потерянного события (502 на ребилде инстанса, отсутствие ретраев у Plane/Gitea, неразрезолвленный sha→branch — класс инцидента ORCH-044). Реконсилятор периодически (reconcile_interval_s) доигрывает пропущенный переход через те же штатные гейты/обработчики, что и webhook, не дублируя логику конвейера: F-1 gate-side (reconcile_gate_once) — для задач stage≠done, без активного job и age(updated_at) ≥ grace_for_stage(stage) делает read-only пред-оценку канонического QG стадии; зелёный → продвижение строго через неизменный stage_engine.advance_stage(..., finished_agent=None); красный → тишина (спам нотификаций структурно невозможен — advance_stage на красном гейте не вызывается вовсе); analysis F-1 не трогает (человеческий гейт). F-2 plane-side (reconcile_plane_once) — опрос Plane API per-project (новый plane_sync.list_issues_by_state, курсорная пагинация, never-raise) и реплей In Progress / Approved / Rejected через существующие webhooks.plane.handle_status_start / handle_verdict (async-обработчики вызываются из sync-потока через asyncio.run). F-3 — усиление sha→branch в handle_ci_status: при неразрезолвленном sha — БД-fallback по единственной development-задаче repo (db.get_development_tasks_by_repo; неоднозначность → не резолвим, ложного матча нет), logger.debuglogger.info для видимости потерянного CI-события. Анти-дубль на создании задачи (db.create_task_atomic под process-wide threading.Lock: SELECT-exists→INSERT, проигравший в гонке reconcile↔webhook не плодит второй task/branch/worktree/стартовый analyst-job). Старт/стоп в main.lifespan (после worker.start() / перед worker.stop()), restart-safe, never-raise на единицу работы. Наблюдаемость (F-4): при разблокировке — лог-строка reconciler: <wi> <stage> разблокирована (потерян webhook) + Telegram (reconcile_notify_unblock) и блок reconcile в GET /queue. Kill-switches: ORCH_RECONCILE_ENABLED (глобально), ORCH_RECONCILE_PLANE_ENABLED (гасит только F-2), ORCH_RECONCILE_INTERVAL_S (120), ORCH_RECONCILE_GRACE_DEFAULT_S (600), ORCH_RECONCILE_GRACE_OVERRIDES_JSON (per-stage), ORCH_RECONCILE_NOTIFY_UNBLOCK (true). Схема БД и реестры (STAGE_TRANSITIONS/QG_CHECKS) НЕ менялись. ADR docs/work-items/ORCH-053/06-adr/ADR-001-stuck-task-reconciler.md, глобальный docs/architecture/adr/adr-0007-reconciler.md. Тесты: tests/test_reconciler.py, tests/test_reconciler_plane.py, tests/test_gitea_sha_resolve.py, tests/test_config.py.
  • Merge-gate: авто-rebase на текущий origin/main + повторный прогон тестов + сериализация мержей (ORCH-043): детерминированный (без LLM) суб-гейт на ребре deploy-staging → deploy, выполняемый ПЕРЕД мержем PR деплоером. Закрывает класс гонок «две зелёные ветки в одном репо ломают main»: пайплайн валидирует ветку против того main, от которого она ответвилась, а не против main в момент мержа — между «ветка зелёная» и «ветка смержена» параллельная задача может сдвинуть main (семантический конфликт: git мержит без текстового конфликта, но совмещённый main красный). Для self-hosting репозитория orchestrator это означало бы красный main инструмента, обслуживающего ВСЕ проекты. Новый модуль src/merge_gate.py (контракт «never raise», все git-операции — в per-branch worktree, ORCH-2/S-4): branch_is_behind_main (git merge-base --is-ancestor origin/main HEAD), auto_rebase_onto_main (rebase + git push --force-with-lease ТОЛЬКО ветки задачи — main НИКОГДА не пушится; текстовый конфликт → rebase --abort + чистый worktree), retest_branch (python -m pytest <target> в догнанном worktree, бюджет merge_retest_timeout_s), файловый merge-lease (acquire_merge_lease/release_merge_lease, атомарный O_CREAT|O_EXCL, holder-aware release, реклейм протухшего/битого лиза — без изменения схемы БД). Новый quality-gate check_branch_mergeable (src/qg/checks.py, зарегистрирован в QG_CHECKS) композирует примитивы под лизом: kill-switch/вне-области → no-op pass; lock занят → (False, "merge-lock busy") (сигнал DEFER, не код-фолт); ветка свежая → pass (лиз ДЕРЖИТСЯ до мержа); отстала → rebase → конфликт = fail+release, чисто → retest → зелёный = pass (лиз держится) / красный|timeout = fail+release. Интеграция в src/stage_engine.py (суб-гейт на deploy-staging, БЕЗ новой стадии в STAGE_TRANSITIONS): pass → advance на deploy; «merge-lock busy» → DEFER (повторная постановка деплоера на deploy-staging с задержкой available_at, анти-дедлок при max_concurrency=1, restart-safe счётчик по task_content, лимит merge_defer_max_attempts → block+Telegram); конфликт/красный retest → ROLLBACK на development + ретрай developer-а (кап MAX_DEVELOPER_RETRIES, без бесконечного баунса). Лиз освобождается на deploy→done, на rollback и по webhook смерженного PR (src/webhooks/gitea.py). Новый параметр enqueue_job(..., available_at_delay_s=...) (src/db.py) — отложенная постановка без изменения схемы. Условность раскатки (зеркало ORCH-35): merge_gate_repos (CSV) или по умолчанию только self-hosting orchestrator; глобальный kill-switch merge_gate_enabled. Новые настройки 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). ADR docs/work-items/ORCH-043/06-adr/ADR-001-merge-gate.md, глобальный docs/architecture/adr/adr-0006-merge-gate.md. Тесты: tests/test_merge_gate.py, tests/test_qg_merge_gate.py, tests/test_merge_gate_race.py, tests/test_stage_engine.py::TestMergeGate, tests/test_config.py.
  • Режим bump live-трекера Telegram (ORCH-042): новый ORCH_TRACKER_MODE (Settings.tracker_mode, дефолт edit) выбирает поведение карточки задачи. edit (как было) — карточка редактируется на месте (editMessageText). bump — на каждом обновлении старое сообщение удаляется и карточка отправляется заново вниз чата (best-effort delete_telegram(старый_id)send_telegram(text, disable_notification=True)set_tracker_message_id(new_id)), чтобы актуальный статус всегда был последним в чате при активной переписке. Инвариант «одна карточка на задачу» сохранён в обоих режимах: за один вызов update_task_tracker шлётся ≤1 нового сообщения; set_tracker_message_id вызывается ТОЛЬКО при успешном send (транзиентный None не затирает указатель); результат delete НЕ блокирует отправку новой карточки (delete-fail у сообщения >48ч → всё равно шлём новое). Резолюция режима в notifications (case-insensitive, trim): всё, что ≠ "bump" (включая пустое/мусор) → edit → нулевая регрессия и оркестратор не падает на любом значении флага. Новый low-level helper delete_telegram(message_id) -> bool (контракт «never raises», маркеры _DELETE_GONE_MARKERS): ok:true или «уже нет / нельзя удалить» → True; неизвестный ok:false/5xx/исключение → False; нет кредов → False без HTTP. Сигнатуры send_telegram/edit_telegram/update_task_tracker и схема БД (tasks.tracker_message_id) не менялись. ADR docs/work-items/ORCH-042/06-adr/ADR-001-tracker-bump-mode.md. Тесты: tests/test_tracker_bump.py, tests/test_config.py.
  • Дословный текст findings reviewer/tester встраивается в task_desc заворота (ORCH-046): при откате на development строка task_desc (попадает в .task-dev.md developer-агента) теперь несёт суть претензий, а не только ссылку на файл — устраняет «испорченный телефон», из-за которого агент шёл «читать файл», терял ключевые P0/P1 / причину FAIL и заворачивался снова, выжигая MAX_DEVELOPER_RETRIES и токены. Новый defensive-модуль src/review_parse.py (контракт «never raise», как src/frontmatter.py): extract_review_findings(path) — дословные пункты P0/P1 из секции ## Findings файла 12-review.md; extract_test_failures(path) — релевантный фрагмент тела 13-test-report.md (приоритет ## Вывод pytest → FAIL-строки ## Результаты## Итог). Обе функции усекают результат до MAX_FINDINGS_CHARS/MAX_FAILURES_CHARS (≈2000) с маркером …(truncated). Две rollback-ветки src/stage_engine.py (reviewer REQUEST_CHANGES, tester check_tests_passed FAIL) встраивают извлечённый текст и сохраняют ссылку на полный файл («Полный контекст»); при пустом/битом артефакте — graceful-фоллбэк на прежнюю ссылку-строку (никаких исключений в advance_stage). Tester-ветка дополнительно всегда включает reason гейта. Последовательность отката, _developer_retry_count, поля AdvanceResult и реестр QG_CHECKS не менялись. ADR docs/work-items/ORCH-046/06-adr/ADR-001-embed-findings-in-task-desc.md. Тесты: tests/test_review_parse.py, tests/test_stage_engine.py::TestRollbackTaskDescEmbedding.
  • Поллинг с ретраем в quality-gate check_ci_green (ORCH-045): гейт CI превращён из single-shot в polling, чтобы устранить race condition — раньше один опрос combined commit-status сразу после пуша developer-а ловил транзиентный pending (типично 1-3с, реальный кейс ORCH-017: опрос 17:58:54 → pending, CI дозеленел 17:58:55) и задача застревала насмерть без повторного опроса. Теперь: success → пропуск сразу; failure/error → провал сразу (терминально, ретрай бессмыслен); pending/unknown → time.sleep и повторный опрос до ci_poll_max_attempts раз; истечение попыток → явный (False, "CI still pending after <T>s") (тупик больше не молчаливый); 404 → как раньше; транзиентная httpx.HTTPError на попытке логируется и ретраится в рамках бюджета. Параметры — новые настройки ORCH_CI_POLL_MAX_ATTEMPTS (12) и ORCH_CI_POLL_INTERVAL_S (10) в src/config.py (~2 мин ожидания pending). Сигнатура check_ci_green(repo, branch) и реестр QG_CHECKS не менялись; check_tests_passed не затронут. ADR docs/architecture/adr/adr-0004-ci-poll-retry.md. Тесты: tests/test_qg.py::TestCheckCIGreen.
  • Прямые ссылки на BRD и Plane-таску в Telegram-уведомлении об апруве (ORCH-017): пингующее сообщение notify_approve_requested теперь встраивает две HTML-<a>-ссылки — на docs/work-items/<WI>/01-brd.md (Gitea branch-view: gitea_public_urlgitea_url) и на issue в Plane ({web_base}/{workspace}/projects/{project_id}/issues/{plane_issue_id}/). Новая настройка ORCH_PLANE_WEB_URL (внешний браузерный web-URL Plane; фолбэк на plane_api_url). Loopback-guard: если итоговый Plane web-base указывает на localhost/127.0.0.1/0.0.0.0/::1 или пуст — Plane-ссылка опускается (не выпускаем битый localhost-URL). Graceful degradation: каждая ссылка строится независимо и опускается при нехватке данных, сообщение и призыв «Переведите задачу в статус Approved …» сохраняются всегда; ровно одно пингующее сообщение, разделяемая send_telegram не тронута. Динамические подписи экранируются html.escape, parse_mode=HTML сохранён. ADR docs/work-items/ORCH-017/06-adr/ADR-001-telegram-approve-links.md. Тесты: test_notify_approve_links.py, test_analysis_approve_flow_links.py.
  • Конфигурируемые модель LLM и режим работы (--effort) агентов (ORCH-41): модель/effort каждого агента вынесены из хардкода launcher.py в конфиг — глобально per-agent (ORCH_AGENT_MODEL_<AGENT> / ORCH_AGENT_EFFORT_<AGENT>, дефолты ORCH_AGENT_MODEL_DEFAULT=claude-opus-4-8, ORCH_AGENT_EFFORT_DEFAULT=high) и per-project (agent_models / agent_efforts в ORCH_PROJECTS_JSON). Резолверы resolve_agent_model / resolve_agent_effort (приоритет project > per-agent env > default > пусто), валидация effort {low,medium,high,xhigh,max}, опц. ORCH_AGENT_FALLBACK_MODEL (--fallback-model). Хардкод "model":"opus" (architect/reviewer) удалён. Тесты: test_resolve_agent_model.py, test_resolve_agent_effort.py.
  • Единый status-коммент агентов в Plane (ORCH-016): usage.build_status_comment(...) — один хелпер для ВСЕХ ролей (analyst..deployer). HTML-формат: header {icon} {Role} — {описание}, опциональная строка Verdict/Status: … из YAML-frontmatter артефакта, строка Длительность: 4m 12s (явный duration_s от launcher, fallback из agent_runs для аналитика), <b>Документы:</b><ul><li><a>…</a></li></ul>, тех-хвост <sub>tokens · cost</sub>. Утилитки: usage.fmt_duration, usage.get_agent_duration, новый модуль src/frontmatter.py (defensive YAML reader). ADR docs/work-items/ORCH-016/06-adr/ADR-001-unified-status-comment.md.
  • Документация по канону (ORCH-9): CLAUDE.md (паспорт проекта), структура docs/ (architecture/ + adr/, operations/, work-items/, history/), docs/operations/INFRA.md (RUNBOOK с инфра-изоляцией и self-hosting рисками).
  • ADR: adr-0001 (multi-repo registry), adr-0002 (job queue), adr-0003 (условный staging-гейт).
  • Стадия deploy-staging (ORCH-35): промежуточный гейт между testing и deploy. QG check_staging_status (условный, только для self-hosting repo). PR #31.
  • Деплой-хук (ORCH-34): scripts/orchestrator-deploy-hook.sh с health-check и авто-rollback. PR #30.
  • Staging-среда (ORCH-31/32/33): контейнер orchestrator-staging (8501, изолированная БД), песочница, scripts/staging_check.py. PR #28/#29.
  • Очередь задач (ORCH-1): таблица jobs, queue_worker.py, atomic claim, max_concurrency, ретраи, restart-safe, эндпоинт /queue.
  • Реестр проектов (ORCH-6): src/projects.py, фильтрация вебхуков по проекту.

Changed

  • Русификация и косметика карточки live-трекера Telegram (ORCH-042, оба режима): метка Подтверждение BRD вместо «Ревью БРД» (_BRD_LABEL); после прохождения approve-gate строка подтверждения BRD начинается с вместо ⏸️ (ветка ожидания человека сохраняет ⏸️/); русские display-labels стадий в _TRACKER_STAGES (Анализ / Архитектура / Разработка / Код ревью / Тестирование / Внедрение) — применяются и в « …», и в «🔄 … идёт»; финальная строка готовой задачи 📦 Внедрено вместо deployed (_done_link). Меняются только отображаемые строки — ключи стадий и имена агентов не трогаются. Существующие ассерты tests/test_telegram_tracker.py обновлены под русские метки.
  • Status-коммент агентов теперь HTML и единообразен (ORCH-016): src/usage.usage_comment(...) помечен deprecated и стал тонкой обёрткой над build_status_comment; src/usage.artifact_links(...) теперь возвращает <li><a>…</a></li> HTML-фрагменты (раньше — markdown [label](url)); stage_engine._build_analyst_ready_comment(...) — тонкая обёртка, аналитик идёт через ту же ветку build_status_comment(agent="analyst", ...). Реестр QG_CHECKS и STAGE_TRANSITIONS НЕ изменялись.
  • Цепочка стадий: ... testing → deploy-staging → deploy → done (была без deploy-staging).

Fixed

  • Re-deploy после отката больше не зависает на deploy; .env.example дополнен (ORCH-036, review-fix): sentinel-маркеры самодеплоя (approve-requested/initiated/result) ключуются по стабильному work_item_id, поэтому при FAILED-деплое и откате БАГ-8 (deploy → development) они оставались на диске — после фикса developer-ом и повторного захода задачи на deploy Фаза B по idempotency-guard видела STALE initiated и становилась no-op: detached-хук не перезапускался, finalizer не ставился, задача висела на deploy навсегда (нарушался retry-контракт стадии, AC-4/AC-10; устаревший result к тому же был бы перечитан новым finalizer'ом). Добавлен self_deploy.clear_state(repo, work_item_id) (never-raise, idempotent, рекурсивное удаление <repos_dir>/.deploy-state-<repo>/<wi>/), вызывается в ветке БАГ-8-отката check_deploy_status FAILED (src/stage_engine.py) и дополнительно в начале Фазы A (_handle_self_deploy_phase_a) — каждый новый прод-деплой-проход стартует с чистого состояния. Отдельно: канонический .env.example (CLAUDE.md правило №8, ТЗ §2.6) дополнен полным блоком новых дескрипторов ORCH_SELF_DEPLOY_* / ORCH_DEPLOY_* (плейсхолдеры, секреты не коммитятся) по образцу merge-gate ORCH-043. Контракты STAGE_TRANSITIONS / QG_CHECKS / _parse_deploy_status / БАГ-8 / merge-gate не тронуты. Тесты: tests/test_deploy_rollback.py::test_tc11_re_deploy_after_rollback_not_wedged, tests/test_deploy_hook_mapping.py::test_clear_state_removes_all_markers_and_is_idempotent.
  • Контейнер и агенты бегут под uid хоста (1000:1000), не root (ORCH-040): оба сервиса в docker-compose.yml (orchestrator, orchestrator-staging) получили user: "1000:1000" (slin) — устраняет корень проблемы, при которой Claude-CLI агенты, запускаемые через subprocess.Popen внутри root-контейнера, создавали все артефакты конвейера (git worktree /repos/_wt/..., коммиты в docs/work-items/...) с владельцем root:root на хосте, из-за чего git pull/git reset под slin падали с insufficient permission for adding an object и каждый деплой требовал ручного chown. Теперь файлы сразу slin:slin. Доступ к docker.sock сохранён через group_add: ["999"] (МИНА 1 — НЕ удалена). SSH-маунт приведён к единому HOME агента: target /root/.ssh/home/slin/.ssh (/home/slin/.orchestrator-ssh:/home/slin/.ssh:ro), синхронно с HOME=/home/slin, который launcher форсит в env Popen и git_env — устранён скрытый рассинхрон SSH-маунта с форсимым HOME. src/agents/launcher.py и Dockerfile НЕ менялись (numeric uid работает без записи в /etc/passwd; safe.directory '*' уже покрывает git над bind-mount). Требует host-prerequisites Owner (P-1…P-4, вне кода): блокер P-1 — chown -R 1000:1000 /home/slin/.claude для доступа uid 1000 к claude creds (иначе preflight заворачивает конвейер); прод-рестарт self — только в окно тишины (общий инстанс с enduro-trails), страховка — staging-гейт (adr-0003). ADR docs/work-items/ORCH-040/06-adr/ADR-001-run-agents-as-host-uid.md, глобальный docs/architecture/adr/adr-0005-container-runs-as-host-uid.md; INFRA.md обновлён (рантайм-uid, volumes/SSH target, host-prerequisites). Тесты: tests/test_orch040_compose.py.
  • Staging-чек B6 читает реестр из окружения работающего staging-инстанса (ORCH-048): блок B6 «Registry: sandbox present, prod ET/ORCH absent» в scripts/staging_check.py давал ложный FAIL (prod-ET=YES(BAD!), prod-ORCH=YES(BAD!)) при фактически исправной изоляции — единственный чек suite, который не ходил к инстансу по HTTP, а импортировал src.projects локально через host-path хак sys.path.insert(0, "/repos/orchestrator") + importlib.reload, строя реестр из ORCH_PROJECTS_JSON process-env запускающего процесса. При фактическом запуске деплоером с хоста переменная не задана → дефолт _DEFAULT_PROJECTS (ET+ORCH) → ложный FAIL → лишний откат deploy-staging → development. Решение (вариант «в», ADR-001): host-path хак удалён; suite канонически запускается ВНУТРИ контейнера orchestrator-staging через docker exec … python3 /repos/orchestrator/scripts/staging_check.py (scripts/ доступен только через bind-mount, import src.projects резолвится через PYTHONPATH=/app из кода контейнера, env — .env.staging) → B6 читает реестр именно работающего инстанса, без HTTP-bootstrap и «курицы-яйца». Логика вердикта вынесена в чистую _evaluate_b6(known) -> (passed, detail) (инвариант passed ⟺ SANDBOX ∈ known ∧ PROD_ET ∉ known ∧ PROD_ORCH ∉ known, формат detail сохранён) + _known_project_ids_from_registry() / _run_b6() с детерминированным FAIL при недоступности источника (не ложный PASS, не необработанное исключение). Синхронно обновлены .openclaw/agents/deployer.md (команда стадии через docker exec) и docs/operations/STAGING_CHECK.md. src/projects.py, .env* и прочие чеки A/B4/B5/C не тронуты; реестр QG_CHECKS и check_staging_status (ADR-0003) не менялись. ADR docs/work-items/ORCH-048/06-adr/ADR-001-b6-registry-via-in-container-run.md. Тесты: tests/test_staging_check_b6.py.
  • Testing-гейт check_tests_passed читает result: наравне с verdict:/status: (ORCH-047): парсер _parse_tests_verdict (src/qg/checks.py) теперь принимает три равноправных машиночитаемых поля frontmatter 13-test-report.mdresult: (канон промпта тестера .openclaw/agents/tester.md, result: PASS|FAIL), плюс легаси verdict: и status: (enduro-trails ET-001..ET-014); достаточно любого одного непустого. Устраняет рассинхрон контракта: тестер честно эмитил result: PASS без verdict:/status:, парсер попадал в ветку «нет машинного вердикта» → откат testing → development в петлю до исчерпания MAX_DEVELOPER_RETRIES (наблюдалось на ORCH-17; ORCH-016 прошёл лишь из-за избыточного дублирования полей). Семантика приоритетов сохранена и распространена на все три поля через объединённую строку: negative-токен в любом поле авторитетен (перебивает positive), наборы токенов заморожены (обратная совместимость). Сигнатура гейта, имя и реестр QG_CHECKS не менялись. ADR docs/work-items/ORCH-047/06-adr/ADR-001-result-field-in-tests-gate.md. Тесты: tests/test_qg.py::TestCheckTestsPassed.
  • БАГ-8: провал deploy/deploy-staging → корректный откат на development.
  • Изоляция тестов от живого Plane API (PR #27): autouse-фикстура сброса settings.

Историю до введения канона см. в docs/history/ (BUGFIXES_, LESSONS_, INCIDENT_).*