Files
orchestrator/docs/history/BUGFIXES_2026-06-03.md
Dev Agent 7c68d1d812
All checks were successful
CI / test (pull_request) Successful in 9s
docs(orchestrator): adopt enduro doc canon + CLAUDE.md + ADR (ORCH-9)
2026-06-05 12:33:55 +03:00

83 lines
5.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# BUGFIXES / CHANGES — 2026-06-03
## ORCH-6 — Multi-repo: фильтр проекта + маппинг repo per project
**Тип:** root-fix инцидента + новая возможность (multi-repo)
**Ветка:** `feature/ORCH-6-multirepo`
**Plane:** ORCH-6 (project `8da6aa25-a60e-44d6-a1e2-d8ae59aa7d6a`)
**Связанный инцидент:** [`INCIDENT_2026-06-02_webhook_autorun.txt`](./INCIDENT_2026-06-02_webhook_autorun.txt)
### Контекст инцидента
При создании задач ORCH-1..7 в Plane (проект `orchestrator`) Plane-webhook
(id `93f0c342-a614-4248-9d0f-c107276f5620`) сработал на каждую задачу и запустил
конвейер — но **всё ушло в репо `enduro-trails`**, потому что `plane.py:91`
хардкодил `repo = settings.default_repo`. Webhook слушал **весь workspace без
фильтра по проекту**, наплодив мусорные ET-010..016.
Митигация на время фикса: Plane-webhook **деактивирован** (`is_active=false`).
### Root cause
1. Нет фильтра по Plane-проекту — любая issue из любого проекта попадала в конвейер.
2. `repo` хардкожен на единственный `default_repo` (enduro-trails).
3. `work_item_prefix` всегда `ET` (db.py).
4. `plane_sync` ходил в единственный хардкоженный `PROJECT_ID` (enduro).
### Что сделано
| Файл | Изменение |
|------|-----------|
| `src/projects.py` (новый) | Реестр проектов: `ProjectConfig` + дефолт-список (enduro-trails + orchestrator) + резолверы `get_project_by_plane_id` / `get_project_by_repo` / `known_plane_project_ids`. Источник переопределения — `ORCH_PROJECTS_JSON`; устойчивый парсинг (битый JSON / битые записи → fallback на дефолт). |
| `src/config.py` | Добавлен `projects_json: str = ""` (env `ORCH_PROJECTS_JSON`). |
| `src/webhooks/plane.py` | **Фильтр по проекту**: `data.project` не в реестре → `{"status":"ignored","reason":"unknown project"}`. Резолв `repo`/`prefix`/Plane-проекта из реестра. Plane-sync для задачи идёт в её собственный проект. |
| `src/db.py` | `get_next_work_item_id(repo, prefix="ET")` — нумерация per (repo, prefix); `ORCH-001` независимо от `ET-010`. Дефолт `ET` сохранён для обратной совместимости. |
| `src/plane_sync.py` | `_resolve_project_id` + параметризация `project_id` (дефолт на enduro → обратная совместимость существующих вызовов). |
| `src/webhooks/gitea.py` | Неизвестный repo (`get_project_by_repo` → None) → `ignored` в 3 хэндлерах. |
### Тесты
- `tests/test_projects.py` (16 тестов): резолверы (by plane_id, by repo, unknown→None,
known_plane_project_ids), парсинг `ORCH_PROJECTS_JSON` (валидный / битый JSON / не массив /
битые записи → skip / all-bad → fallback), reload с кастомным JSON.
- `tests/test_plane_webhook.py` (4 теста, FastAPI TestClient, `launcher.launch` замокан):
unknown project → `ignored` + нет task/branch/agent; orchestrator-проект → `repo=orchestrator`,
`ORCH-*`; enduro-проект → `repo=enduro-trails`, `ET-*`; независимые префиксы (`ORCH-001`/`ORCH-002`
параллельно с `ET-001`).
**Прогон (в контейнере, образ `orchestrator-orchestrator`):** `57 passed`. 9 падений в
`tests/test_webhooks.py`**pre-existing** (webhook signature 401 / TypeError, не связаны с ORCH-6,
не трогались).
```bash
IMG=$(docker inspect orchestrator --format '{{.Config.Image}}')
docker run --rm -v /home/slin/repos/orchestrator:/code -w /code --entrypoint python3 $IMG -m pytest tests/ -q
```
### Проверка резолва (offline, в работающем контейнере)
```bash
docker exec orchestrator python3 -c "
from src.projects import get_project_by_plane_id, known_plane_project_ids
o = get_project_by_plane_id('8da6aa25-a60e-44d6-a1e2-d8ae59aa7d6a')
e = get_project_by_plane_id('7a79f0a9-5278-49cd-9007-9a338f238f9c')
assert o.repo=='orchestrator' and o.work_item_prefix=='ORCH'
assert e.repo=='enduro-trails' and e.work_item_prefix=='ET'
assert get_project_by_plane_id('00000000-0000-0000-0000-000000000000') is None
print('RESOLVE OK:', o.repo, e.repo, '| known:', len(known_plane_project_ids()))
"
```
### ⚠️ Важно
- Plane-webhook **остаётся выключенным** (`is_active=false`). Включение — отдельный
шаг Стрим после ревью PR.
- `ORCH_PROJECTS_JSON` (если задан) **полностью заменяет** дефолт — перечислять все нужные проекты.
- Обратная совместимость `plane_sync` сохранена (дефолт project_id = enduro), ET-задачи не сломаны.
### Re-enable webhook (после ревью, делает Стрим)
```sql
UPDATE webhooks SET is_active=true WHERE id='93f0c342-a614-4248-9d0f-c107276f5620';
```