auto-sync: 2026-06-05 14:30:02

This commit is contained in:
Stream
2026-06-05 14:30:02 +03:00
parent b59f0224e2
commit 286e633e6a
2 changed files with 110 additions and 1 deletions

View File

@@ -0,0 +1,108 @@
# 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-задач.
## Сделано
- [x] Создана ветка `feature/ORCH-10-per-project-states` из `origin/main`
- [x] Прочитан и изучен существующий код `src/plane_sync.py`, `src/webhooks/plane.py`, `src/projects.py`
- [x] Реализован `get_project_states(project_id)` — динамический резолвинг UUID по проекту через Plane API
- [x] Добавлен `_DEFAULT_STATES` (= прежний `PLANE_STATES`), `PLANE_STATES` оставлен как alias
- [x] Добавлен кэш `_STATES_CACHE` per project_id и функция `reload_project_states()`
- [x] Добавлен `stage_to_state(stage, project_id)` через `get_project_states`
- [x] `update_issue_state()` переключён на `stage_to_state()` вместо `STAGE_TO_STATE` dict
- [x] Все `set_issue_*()` функции резолвят UUID через `get_project_states(project_id)`
- [x] `webhooks/plane.py::handle_issue_updated()` — сравнение через `get_project_states(project_id)` вместо глобального `PLANE_STATES`
- [x] `webhooks/plane.py::start_pipeline()` QG-0 blocked path — `get_project_states(plane_project_id)["blocked"]`
- [x] Написаны 23 новых теста в `tests/test_orch10_states.py`
- [x] `pytest tests/ -q` — 342 passed (было 319, +23)
- [x] Коммит: `fix(plane): resolve issue states per-project instead of hardcoded enduro UUIDs (ORCH-10)`
- [x] 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** — https://git.mva154.duckdns.org/admin/orchestrator/pulls/33
Ветка: `feature/ORCH-10-per-project-states``main`. Мержить НЕ нужно (Стрим смержит вручную после ревью).
## Проверка всех критериев ТЗ
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 `b873d9eb``handle_status_start` called
- `test_webhook_in_progress_orch_starts_pipeline`: ORCH `e331bfb3``handle_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-образа → ручная проверка на проде.