6.9 KiB
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_CACHEper project_id и функцияreload_project_states() - Добавлен
stage_to_state(stage, project_id)черезget_project_states update_issue_state()переключён наstage_to_state()вместоSTAGE_TO_STATEdict- Все
set_issue_*()функции резолвят UUID черезget_project_states(project_id) webhooks/plane.py::handle_issue_updated()— сравнение черезget_project_states(project_id)вместо глобальногоPLANE_STATESwebhooks/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
#33 — admin/orchestrator#33
Ветка: feature/ORCH-10-per-project-states → main. Мержить НЕ нужно (Стрим смержит вручную после ревью).
Проверка всех критериев ТЗ
-
pytest tests/ -q — ✅ 342 passed (было 319)
-
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: ETin_progress=b873d9eb-993c-48cd-97ac-99a9b1623967test_get_project_states_orch_in_progress_uuid: ORCHin_progress=e331bfb3-e17e-4699-ba48-4abb89c21b7b
-
webhook in_progress распознаётся для обоих проектов — ✅
test_webhook_in_progress_et_starts_pipeline: ETb873d9eb→handle_status_startcalledtest_webhook_in_progress_orch_starts_pipeline: ORCHe331bfb3→handle_status_startcalledtest_webhook_et_in_progress_not_confused_with_orch: ET UUID в ORCH-проекте НЕ триггерит
-
Fallback при недоступном API → _DEFAULT_STATES — ✅
test_get_project_states_api_error_fallback: Exception →_DEFAULT_STATEStest_get_project_states_non_200_fallback: HTTP 500 →_DEFAULT_STATEStest_get_project_states_empty_response_fallback: пустой results →_DEFAULT_STATEStest_get_project_states_none_project_id_fallback: None →_DEFAULT_STATESбез API-вызова
-
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-образа → ручная проверка на проде.