# Dev Report: M-6 — work_item_id из Plane sequence_id Дата: 2026-06-03 Статус: DONE (запушено, PR #8 открыт; НЕ мержено — мерж за Стримом после живой проверки) ## Задача Источник правды для номера work_item_id — Plane `sequence_id`, маппить в `-NNN`. Fallback на `get_next_work_item_id` (DB-инкремент) если Plane недоступен (автономность > точность). Бонус: убрать хардкод `ET-` в `find_issue_id`. ## Разведка (живой GET к Plane) — КЛЮЧЕВОЕ - Endpoint одиночного issue: `{PLANE_BASE}/workspaces/{ws}/projects/{pid}/issues/{uuid}/` — **trailing slash ОБЯЗАТЕЛЕН**: без него 301 redirect (не JSON). - Успешный GET (issue `64e98247-...`, seq=5) → **200**, `sequence_id` — **top-level int** (`5`), поле называется именно `sequence_id`. Формат helper-шаблона из ТЗ совпал 1:1. - GET по `f9009756-...` (ET-010) → **404** с `{"error": ...}` JSON → `.get("sequence_id")` = None → helper вернёт None. Этот issue реально отсутствует в Plane — живая иллюстрация рассинхрона, ради которого делается M-6. - Списочный endpoint `/issues/` → 200, `results[]` с `sequence_id` (int) на каждом issue. - Вывод: шаблон helper-а из ТЗ корректен, использован как есть (trailing slash уже был). ## Сделано - [x] Разведка живым GET — формат подтверждён - [x] `plane_sync.py`: `fetch_issue_sequence_id(issue_id, project_id) -> int|None` (timeout=10, try/except, не кидает) - [x] `webhooks/plane.py` `handle_work_item_created`: seq→`f"{prefix}-{seq:03d}"`, None→fallback DB + logger.warning - [x] `plane_sync.py` `find_issue_id`: убран хардкод `ET-`, матч по sequence_id из суффикса work_item_id (rsplit("-",1)), обобщён на любой префикс - [x] `tests/test_m6_sequence.py` — 7 тестов - [x] 2 коммита (Conventional Commits), push, проверка remote, PR #8 ## Изменённые файлы - `src/plane_sync.py` — +helper `fetch_issue_sequence_id`; find_issue_id без ET-хардкода (матч по числу из суффикса) - `src/webhooks/plane.py` — work_item_id из Plane seq + fallback на get_next_work_item_id - `tests/test_m6_sequence.py` — новый, 7 тестов ## Результат - Новые тесты: **7 passed** (helper int/None×2/missing-field; created→ORCH-007 по seq=7; created→fallback ORCH-099 при seq=None; find_issue_id ORCH-005 по seq=5; ET-002 по seq=2). - Полный прогон в контейнере (IMG=orchestrator-orchestrator): **158 passed, 9 failed** — 9 = pre-existing 401/signature в test_webhooks.py (baseline 151 + 7 новых = 158). M-6 не трогает HMAC; 9 не починены и не сломаны (как требует ТЗ). - Fallback-путь: при seq=None task всё равно создаётся (тест `test_created_falls_back_to_db_when_plane_down` это assert-ит) → автономность сохранена. ## Git - Ветка `feature/ORCH-M6-plane-sequence` из свежего main (be27f50). - Коммиты: - `1d978ca feat(webhook): derive work_item_id from Plane sequence_id (M-6)` - `c431a3d fix(plane_sync): drop hardcoded ET- prefix in find_issue_id (M-6)` - `git log origin/main..origin/feature/ORCH-M6-plane-sequence` → оба коммита присутствуют на remote (грабля ORCH-7 проверена). - PR #8: https://git.mva154.duckdns.org/admin/orchestrator/pulls/8 (head→base = feature→main). - Токен: рабочий из env контейнера (`ORCH_GITEA_TOKEN=c81227...`), совпал с embedded в origin URL. ## Проблемы и решения - Remote-only репо (нет локального доступа) → редактировал через Python-скрипты по SSH; heredoc с Python мангал shell → решил через `cat > file` (base64-free, через stdin). - Коммит-сплит: оба изменения в plane_sync.py → разнёс `git add -p` (printf "y\nn\n") — feature-хелпер в коммит 1, find_issue_id-фикс в коммит 2. ## Ограничения соблюдены Не трогал: nginx/openclaw.json/.env/deploy-хук/is_active/ORCH-1..7/stage_engine/STAGE_TRANSITIONS/HMAC/очередь. `get_next_work_item_id` оставлен как fallback. НЕ деплоил, НЕ мержил.