Files
wiki/tasks/orchestrator/DEV_TASK_OBSERVABILITY_FIXES.md
2026-06-04 11:10:16 +03:00

11 KiB
Raw Permalink Blame History

DEV TASK: orchestrator — 4 правки (merge-gate, токены, Plane→Done, артефакты)

Все правки в репо orchestrator на проде: slin@82.22.50.71:/home/slin/repos/orchestrator. Прямой push в main запрещён (pre-receive hook) → только через PR в Gitea. Gitea токен: docker exec orchestrator printenv ORCH_GITEA_TOKEN. Одна ветка fix/observability-and-merge-gate от актуального main, один PR со всеми 4 правками. Baseline pytest: 227 passed + 10 failed (10 failed = off-limits: 9 HMAC/401 + 1 webhook-POST — НЕ чинить). После всех правок: passed должно вырасти (новые тесты), те же 10 failed.


ПРАВКА 1 (критичная): вторая дверь баг-8 — gitea merge обходит deploy-QG

Файл: src/webhooks/gitea.py

Проблема: ветка action=="closed" and pr.get("merged", False) слепо ставит done для ЛЮБОЙ стадии. deployer мержит PR в начале работы → webhook merged прилетает за ~30 сек → задача done ПОКА deployer ещё работает → его вердикт deploy_status: FAILED игнорируется, check_deploy_status не вызывается. Фейк-done.

Текущий код (≈стр 336-339):

    elif action == "closed" and pr.get("merged", False):
        update_task_stage(task_id, "done")
        notify_stage_change(task_id, current_stage, "done")
        logger.info(f"Task {task_id}: PR merged, stage → done")

Нужно: при current_stage == "deploy" merge-вебхук НЕ должен ставить done напрямую — done на deploy решает ТОЛЬКО check_deploy_status (через advance_stage при завершении deployer-job). Для остальных стадий — поведение merge сохранить.

    elif action == "closed" and pr.get("merged", False):
        # БАГ 8 (вторая дверь): на стадии deploy done гейтится вердиктом deployer'а
        # (check_deploy_status), а НЕ фактом merge PR — deployer мержит PR в начале
        # работы, merge != успешный деплой. advance_stage отработает при завершении job.
        if current_stage == "deploy":
            logger.info(
                f"Task {task_id}: PR merged at deploy stage — done gated by deployer "
                f"verdict (check_deploy_status), ignoring merge-driven done."
            )
            return
        update_task_stage(task_id, "done")
        notify_stage_change(task_id, current_stage, "done")
        logger.info(f"Task {task_id}: PR merged, stage → done")

НЕ дублировать advance_stage из вебхука — только молчаливый return на deploy (иначе гонка).


ПРАВКА 2 (критичная): учёт токенов — показывать ПОЛНЫЙ вход (cache_read теряется)

Файлы: src/usage.py (+ миграция БД для cache_creation, если решишь хранить).

Проблема: Claude CLI --output-format json отдаёт usage.input_tokens = только НЕкэшированный свежий вход (~20-80 токенов). 99.99% реального входа в cache_read_input_tokens (1-8 МЛН) + cache_creation_input_tokens. Сейчас:

  • parse_usage_from_text сохраняет input_tokens и cache_read_tokens отдельно, но cache_creation_input_tokens НЕ парсится/НЕ хранится → теряется.
  • usage_comment показывает ТОЛЬКО fmt_tokens(input_tokens) → "21 in" при реальных 1.1М.
  • task_usage_summary агрегирует SUM(input_tokens) → "221 токенов вход" на всю задачу. Абсурд.

Что сделать:

  1. Парсинг (parse_usage_from_text, возвращаемый dict): добавить cache_creation_tokens = _int(usage.get("cache_creation_input_tokens")).
  2. БД: добавить колонку cache_creation_tokens INTEGER в agent_runs (idempotent миграция: ALTER TABLE ... ADD COLUMN в try/except, как делаются существующие миграции в проекте — найди где инициализируется схема, например db.py/migrations; НЕ ломай существующую схему). record_usage пишет это поле.
  3. Отображение usage_comment — формат:
    💻 Developer готов · 8.5M in (8.4M cached) / 45.8k out · $7.29
    
    где in_total = input_tokens + cache_read_tokens + cache_creation_tokens, а cached = cache_read_tokens + cache_creation_tokens. Если cached==0 — показывать просто 8.5M in / 45.8k out (без скобки).
  4. task_usage_summary: агрегировать total_in как SUM(input_tokens + cache_read_tokens + cache_creation_tokens), плюс отдельно total_cached = SUM(cache_read_tokens + cache_creation_tokens). task_summary_comment показывает: 📊 Итого по задаче: <total_in> вход (<cached> cached) / <out> выход · <cost>.
  5. Существующие строки в БД (с NULL в cache_creation) — COALESCE(...,0), не падать.

НЕ трогать расчёт cost_usd — он берётся из total_cost_usd CLI и корректен.


ПРАВКА 3: статус Plane должен доезжать до Done

Файлы: найти где после успешного deploy задача завершается (stage_engine.advance_stage deploy→done success-ветка) и где синхронизируется Plane-state (plane_sync / plane_notify_stage).

Проблема: ET-012 в Plane застрял на In Progress (последний переход Testing → In Progress), финального Plane-state Done нет. Done проставился только в очереди оркестратора (из-за правки 1 раньше merge-вебхук вообще обходил флоу). После правки 1 deploy→done пойдёт через check_deploy_status SUCCESS-ветку — на этом переходе нужно синхронизировать Plane-state в Done.

Что сделать: в success-ветке deploy→done (когда check_deploy_status вернул True) вызвать обновление Plane-state в финальный Done/Completed (использовать существующий маппинг PLANE_STATES — НЕ менять сам маппинг, только убедиться что финальный переход вызывается). Проверь по PLANE_STATES, как называется терминальный стейт, и что plane_notify_stage(work_item_id, "deploy", "done") доводит до него. Ограничение: НЕ менять PLANE_STATES маппинг и status-only verdict-модель.


ПРАВКА 4: все агенты прикладывают ссылки на артефакты (не только analyst)

Файлы: где формируются per-agent finish-комменты (usage_comment в usage.py и/или место вызова в launcher/stage_engine, где постится коммент в Plane после агента).

Проблема: только analyst прикладывает ссылки на доки. Architect/developer/reviewer/ tester/deployer постят только токены, без ссылок на свои артефакты.

Что сделать: к финиш-комменту каждого агента добавить ссылку(и) на его артефакт(ы) в Gitea (использовать gitea_public_url — он уже есть для кликабельных ссылок, см. PR #14). Маппинг агент → артефакт (путь в репо docs/work-items/{work_item_id}/):

  • architect → ADR-файлы docs/work-items/{wid}/06-adr/*.md (или design-док стадии architecture)
  • developer → ссылка на PR (номер PR уже известен в флоу) + branch
  • reviewerdocs/work-items/{wid}/12-review.md
  • testerdocs/work-items/{wid}/13-test-report.md
  • deployerdocs/work-items/{wid}/14-deploy-log.md
  • analyst → как сейчас (не ломать).

Формат ссылки — как у analyst сейчас (markdown-ссылка на файл в Gitea через gitea_public_url: http://git.../admin/{repo}/src/branch/{branch}/docs/work-items/{wid}/12-review.md). Если конкретный артефакт-файл для агента отсутствует — ссылку для него пропустить (не падать). Ограничение: НЕ менять формат коммента analyst, НЕ трогать gitea_public_url конфиг.


ОГРАНИЧЕНИЯ (общие, НЕ трогать)

  • HMAC, project-filter (get_project_by_repo), nginx, openclaw.json, .env, queue (кроме чтения), PLANE_STATES маппинг, conftest.py, status-only verdict-модель, gitea_public_url конфиг.
  • Сохранить блок launcher.py:475.
  • НЕ менять ветки action=="reviewed" (APPROVED/REQUEST_CHANGES) в gitea.py.
  • Стадии кроме deploy в gitea.py merge-обработчике — поведение сохранить.

ТЕСТЫ (обязательно)

  • Правка 1: 2 теста (merge на deploy → НЕ done; merge на не-deploy → done).
  • Правка 2: тесты парсинга cache_creation; usage_comment с cached/без; task_usage_summary суммирует все три компонента входа.
  • Правка 3: тест/проверка что success deploy→done доводит Plane до терминала (мок plane_sync).
  • Правка 4: тест что usage_comment/finish-коммент содержит ссылку на артефакт для reviewer/tester/deployer (мок).
  • Полный pytest зелёный (кроме тех же 10 off-limits).

СДАЧА

  • Ветка fix/observability-and-merge-gate от актуального main, один PR в Gitea.
  • НЕ мержить PR сам — оставить на ревью Стрим.
  • Отчёт: tasks/orchestrator/reports/dev-2026-06-04-observability-fixes.md — commit-хеш, номер PR, вывод pytest (passed/failed), краткий diff по каждой из 4 правок.
  • Сообщить: номер PR + результат pytest + что именно изменилось в каждой правке.