# Conflicts: # .env.example # CHANGELOG.md # docs/architecture/README.md # docs/operations/INFRA.md # src/config.py
33 KiB
33 KiB
Changelog
Формат: Keep a Changelog. Записи — на смысловой PR/задачу.
[Unreleased]
Added
- Исполняемый самодеплой стадии
deploy(стадия дёргает хост-хук, manual-approve) (ORCH-036): стадияdeployперестаёт быть «бумажной» — для self-hosting репозиторияorchestratordeploy_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 + setsid→scripts/orchestrator-deploy-hook.sh, чтобы рестарт 8500 пережил гибель контейнера; орк НЕ убивает себя из docker.sock) с build-once retag staging-образа (SOURCE_IMAGE), ставится детерминированный finalizer-job; маркерinitiated— идемпотентность повторного Approved. Фаза C (run_deploy_finalizer, reserved-agentdeploy-finalizer, claim'ится новым контейнером после рестарта) — читает sentinelresult(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-syncdeploy→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. ADRdocs/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на красном гейте не вызывается вовсе);analysisF-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.debug→logger.infoдля видимости потерянного CI-события. Анти-дубль на создании задачи (db.create_task_atomicпод process-widethreading.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) НЕ менялись. ADRdocs/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-gatecheck_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-hostingorchestrator; глобальный kill-switchmerge_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). ADRdocs/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. - Режим
bumplive-трекера Telegram (ORCH-042): новыйORCH_TRACKER_MODE(Settings.tracker_mode, дефолтedit) выбирает поведение карточки задачи.edit(как было) — карточка редактируется на месте (editMessageText).bump— на каждом обновлении старое сообщение удаляется и карточка отправляется заново вниз чата (best-effortdelete_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 helperdelete_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) не менялись. ADRdocs/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.mddeveloper-агента) теперь несёт суть претензий, а не только ссылку на файл — устраняет «испорченный телефон», из-за которого агент шёл «читать файл», терял ключевые 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, testercheck_tests_passedFAIL) встраивают извлечённый текст и сохраняют ссылку на полный файл («Полный контекст»); при пустом/битом артефакте — graceful-фоллбэк на прежнюю ссылку-строку (никаких исключений вadvance_stage). Tester-ветка дополнительно всегда включаетreasonгейта. Последовательность отката,_developer_retry_count, поляAdvanceResultи реестрQG_CHECKSне менялись. ADRdocs/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не затронут. ADRdocs/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_url→gitea_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сохранён. ADRdocs/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). ADRdocs/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. QGcheck_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 видела STALEinitiatedи становилась 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_statusFAILED (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). ADRdocs/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_JSONprocess-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) не менялись. ADRdocs/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) теперь принимает три равноправных машиночитаемых поля frontmatter13-test-report.md—result:(канон промпта тестера.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не менялись. ADRdocs/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_).*