Files
wiki/tasks/orchestrator/reports/dev-2026-06-05-orch10-states.md
2026-06-05 14:30:02 +03:00

6.9 KiB
Raw Blame History

Dev Report: ORCH-10 — per-project Plane state resolution

Дата: 2026-06-05 Статус: DONE

Задача

Устранить блокер запуска ORCH-задач: src/plane_sync.py хардкодил глобальный PLANE_STATES с UUID статусов только enduro-проекта. Webhook-сравнение new_state == PLANE_STATES["in_progress"] совпадало только с ET UUID b873d9eb, ORCH UUID e331bfb3 игнорировался — pipeline никогда не стартовал для ORCH-задач.

Сделано

  • Создана ветка feature/ORCH-10-per-project-states из origin/main
  • Прочитан и изучен существующий код src/plane_sync.py, src/webhooks/plane.py, src/projects.py
  • Реализован get_project_states(project_id) — динамический резолвинг UUID по проекту через Plane API
  • Добавлен _DEFAULT_STATES (= прежний PLANE_STATES), PLANE_STATES оставлен как alias
  • Добавлен кэш _STATES_CACHE per project_id и функция reload_project_states()
  • Добавлен stage_to_state(stage, project_id) через get_project_states
  • update_issue_state() переключён на stage_to_state() вместо STAGE_TO_STATE dict
  • Все set_issue_*() функции резолвят UUID через get_project_states(project_id)
  • webhooks/plane.py::handle_issue_updated() — сравнение через get_project_states(project_id) вместо глобального PLANE_STATES
  • webhooks/plane.py::start_pipeline() QG-0 blocked path — get_project_states(plane_project_id)["blocked"]
  • Написаны 23 новых теста в tests/test_orch10_states.py
  • pytest tests/ -q — 342 passed (было 319, +23)
  • Коммит: fix(plane): resolve issue states per-project instead of hardcoded enduro UUIDs (ORCH-10)
  • Push ветки и создание PR #33

Изменённые файлы

Файл Что изменено
src/plane_sync.py _DEFAULT_STATES + PLANE_STATES alias; _STATES_CACHE; get_project_states(); reload_project_states(); _PLANE_NAME_TO_KEY; _STAGE_TO_STATE_KEY; stage_to_state(); update_issue_state()stage_to_state(); все set_issue_*() через get_project_states()
src/webhooks/plane.py handle_issue_updated(): proj_states = get_project_states(project_id), сравнение per-project; start_pipeline() QG-0: get_project_states(plane_project_id)["blocked"]
tests/test_orch10_states.py 23 новых теста (NEW FILE)

Результат

pytest

342 passed, 4 warnings in 9.66s

Было 319, стало 342 (+23 ORCH-10 теста). Ни один существующий тест не сломан.

git diff --name-status origin/main..feature/ORCH-10-per-project-states

M	src/plane_sync.py
M	src/webhooks/plane.py
A	tests/test_orch10_states.py

Только разрешённые файлы. qg/checks.py, launcher, queue — не тронуты.

PR

#33admin/orchestrator#33 Ветка: feature/ORCH-10-per-project-statesmain. Мержить НЕ нужно (Стрим смержит вручную после ревью).

Проверка всех критериев ТЗ

  1. pytest tests/ -q 342 passed (было 319)

  2. get_project_states(ET) → ET-UUID; get_project_states(ORCH) → ORCH-UUID

    • test_get_project_states_enduro: все 14 ET UUID совпадают
    • test_get_project_states_orchestrator: все 14 ORCH UUID совпадают
    • test_get_project_states_et_in_progress_uuid: ET in_progress = b873d9eb-993c-48cd-97ac-99a9b1623967
    • test_get_project_states_orch_in_progress_uuid: ORCH in_progress = e331bfb3-e17e-4699-ba48-4abb89c21b7b
  3. webhook in_progress распознаётся для обоих проектов

    • test_webhook_in_progress_et_starts_pipeline: ET b873d9ebhandle_status_start called
    • test_webhook_in_progress_orch_starts_pipeline: ORCH e331bfb3handle_status_start called
    • test_webhook_et_in_progress_not_confused_with_orch: ET UUID в ORCH-проекте НЕ триггерит
  4. Fallback при недоступном API → _DEFAULT_STATES

    • test_get_project_states_api_error_fallback: Exception → _DEFAULT_STATES
    • test_get_project_states_non_200_fallback: HTTP 500 → _DEFAULT_STATES
    • test_get_project_states_empty_response_fallback: пустой results → _DEFAULT_STATES
    • test_get_project_states_none_project_id_fallback: None → _DEFAULT_STATES без API-вызова
  5. git diff --name-status только plane_sync.py, webhooks/plane.py, tests/

Архитектура резолвинга/кэша

get_project_states(project_id: str) -> dict[str, str]
│
├── if project_id in _STATES_CACHE -> return cached
│
├── GET /api/v1/workspaces/{WORKSPACE}/projects/{project_id}/states/
│   ├── success → map by _PLANE_NAME_TO_KEY, fill missing from _DEFAULT_STATES
│   │            → _STATES_CACHE[project_id] = resolved; return resolved
│   └── failure → log warning; return _DEFAULT_STATES (ET values, safe fallback)
│
└── None project_id → _DEFAULT_STATES immediately

stage_to_state(stage, project_id) -> str | None
└── _STAGE_TO_STATE_KEY[stage] -> logical key
    └── get_project_states(project_id)[key] -> UUID

Кэш: _STATES_CACHE: dict[str, dict[str,str]] — per project_id, не протухает (процесс перезапускает). reload_project_states(project_id=None) — для тестов и ручного сброса.

Backward compat: PLANE_STATES = _DEFAULT_STATES (alias). STAGE_TO_STATE оставлен как статический dict на ET-значениях (для тестов). update_issue_state() теперь вызывает stage_to_state() вместо него.

Проблемы и решения

Проблема: $() в bash-heredoc-е SSH интерпретировался шеллом → bash command substitution error. Решение: файлы передавались через base64 | ssh 'base64 -d > file' — без heredoc.

Проблема: git commit -m с Python-кодом в строке вызывал bash syntax error. Решение: многострочный -m отдельной строкой через SSH; bash-парсер затронул только переменные типа $() — commit прошёл корректно.

Следующий шаг

Отправить на ревью → Стрим мержит PR #33 → ребилд docker-образа → ручная проверка на проде.