Files
orchestrator/CHANGELOG.md
claude-bot f81715bd39
All checks were successful
CI / test (push) Successful in 12s
CI / test (pull_request) Successful in 12s
fix(infra): run orchestrator containers as host uid 1000:1000 (not root)
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

19 KiB
Raw Permalink Blame History

Changelog

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

[Unreleased]

Added

  • Режим 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

  • Контейнер и агенты бегут под 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_).*