5.4 KiB
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
Контекст инцидента
При создании задач 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
- Нет фильтра по Plane-проекту — любая issue из любого проекта попадала в конвейер.
repoхардкожен на единственныйdefault_repo(enduro-trails).work_item_prefixвсегдаET(db.py).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,
не трогались).
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, в работающем контейнере)
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 (после ревью, делает Стрим)
UPDATE webhooks SET is_active=true WHERE id='93f0c342-a614-4248-9d0f-c107276f5620';