Files
wiki/tasks/orchestrator/DEV_TASK_ORCH6_MULTIREPO.md
2026-06-02 22:20:01 +03:00

16 KiB
Raw Blame History

DEV TASK: ORCH-6 — Multi-repo (фильтр проекта + маппинг repo per project)

Статус: Ready for dev Проект: orchestrator Plane: ORCH-6 (project id 8da6aa25-a60e-44d6-a1e2-d8ae59aa7d6a) Источник: tasks/orchestrator/AUDIT_2026-06-02.md + ИНЦИДЕНТ 2026-06-02 (webhook auto-run в неправильный репо) Исполнитель: Dev-агент (model: tokenator/claude-opus-4-8) Приоритет: 🔴 №1 — закрывает корень инцидента и снимает предохранитель (отключённый Plane-webhook)


Контекст инцидента (зачем эта задача)

При создании задач ORCH-1..7 в Plane (проект orchestrator) Plane-webhook поймал каждую и запустил конвейер — но всё ушло в репо enduro-trails (plane.py:91 хардкодит repo = settings.default_repo), наплодив мусор ET-010..016. Plane-webhook слушает весь workspace без фильтра по проекту.

Сейчас Plane-webhook ДЕАКТИВИРОВАН (предохранитель, is_active=false в Plane postgres, webhook id 93f0c342-a614-4248-9d0f-c107276f5620). После ORCH-6 его включат обратно.

Цель

Оркестратор должен:

  1. Фильтровать webhook по проекту — игнорировать issue из неизвестных/неконфигурированных проектов.
  2. Резолвить repo/prefix/Plane-проект из маппинга по project id из payload, а не из единственного default_repo.
  3. Plane-sync (state/comment) ходить в ПРАВИЛЬНЫЙ проект (сейчас PROJECT_ID хардкод на enduro).

Критерий приёмки: оркестратор корректно ведёт задачу в проекте orchestrator (repo orchestrator, prefix ORCH, свой Plane-проект) и в enduro-trails (repo enduro-trails, prefix ET) — не путая их.


Инфраструктура

Параметр Значение
Сервер slin@82.22.50.71 (SSH key)
Репо орка /home/slin/repos/orchestrator/ (remote admin/orchestrator)
Контейнер orchestrator (port 8500, network host)
Health curl -s http://localhost:8500/health
Тесты 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
Plane API http://localhost:8091/api/v1, token из ORCH_PLANE_API_TOKEN
Plane workspace ag_proj (slug), workspace uuid 903e12e8-65c9-40a0-a7f6-0186f8af42d4

⚠️ Хостовый .venv сломан — тесты ТОЛЬКО через образ.

Известные id (для маппинга по умолчанию)

Проект Plane project id repo (gitea) prefix
enduro-trails 7a79f0a9-5278-49cd-9007-9a338f238f9c enduro-trails ET
orchestrator 8da6aa25-a60e-44d6-a1e2-d8ae59aa7d6a orchestrator ORCH

Корневые точки в коде (собрано Стрим — не ищи вслепую)

Файл:строка Сейчас Проблема
src/webhooks/plane.py:91 repo = settings.default_repo хардкод, игнорит data.project
src/webhooks/plane.py:125 get_next_work_item_id(repo) prefix ET хардкод в db.py
src/webhooks/plane.py:105,240 импорт PROJECT_ID из plane_sync хардкод на enduro
src/plane_sync.py:12 PROJECT_ID = settings.plane_project_id or "7a79f0a9..." хардкод; все sync-URL (строки 55,97,113,150) на единственный проект
src/qg/checks.py:177-182 импорт+использование PROJECT_ID хардкод
src/db.py:82 get_next_work_item_id prefix = "ET" если нет записей prefix не зависит от проекта
src/webhooks/gitea.py:85,148,169,219 repo = payload["repository"]["name"] (fallback default) уже почти ок, repo из payload
src/config.py плоский, один default_repo, один plane_project_id нет маппинга
payload webhook data.project = Plane project uuid есть чем фильтровать

Решение (архитектура)

Ввести реестр проектов — список конфигов, ключ = Plane project id.

Структура

# src/projects.py  (НОВЫЙ модуль)
@dataclass
class ProjectConfig:
    plane_project_id: str   # uuid Plane-проекта (ключ маппинга)
    repo: str               # имя gitea-репо (= папка в /repos)
    work_item_prefix: str   # ET / ORCH
    name: str               # человекочитаемое

# источник: settings.projects_json (JSON в .env) ИЛИ дефолт-список в коде

Конфиг через .env (НЕ трогать секреты, добавить новый ключ): ORCH_PROJECTS_JSON — JSON-массив. Если пусто — дефолт = два проекта из таблицы выше (enduro-trails + orchestrator), чтобы система работала из коробки.

Резолверы

def get_project_by_plane_id(plane_project_id) -> ProjectConfig | None
def get_project_by_repo(repo) -> ProjectConfig | None
def known_plane_project_ids() -> set[str]

Задачи

Task 1: контекст + модуль реестра

  • 1.1 Прочитай AUDIT_2026-06-02.md, текущие plane.py, plane_sync.py, config.py, db.py. Пойми все точки из таблицы выше.
  • 1.2 Работай в ветке feature/ORCH-6-multirepo репо orchestrator. git status чистый.
  • 1.3 Создать src/projects.py с ProjectConfig, дефолт-списком (enduro-trails + orchestrator из таблицы) и резолверами. Источник переопределения — settings.projects_json (если задан и валиден).
  • 1.4 В config.py добавить:
projects_json: str = ""  # ORCH-6: JSON-массив ProjectConfig; пусто = дефолт в projects.py

Критерий: get_project_by_plane_id("8da6aa25-...") → orchestrator; get_project_by_repo("enduro-trails") → ET.


Task 2: фильтр + резолв в plane-webhook

Файл: src/webhooks/plane.py

  • 2.1 В plane_webhook() (или в handle_work_item_created/handle_comment): извлечь project_id = data.get("project"). Если project_id НЕ в known_plane_project_ids()залогировать и проигнорировать (return {"status":"ignored","reason":"unknown project"}). Это и есть фильтр, который предотвратит инцидент.
  • 2.2 plane.py:91: вместо repo = settings.default_repo
proj = get_project_by_plane_id(project_id)
repo = proj.repo
  • 2.3 get_next_work_item_id(repo) — prefix должен браться из proj.work_item_prefix (см. Task 4).
  • 2.4 Все локальные использования PROJECT_ID (строки ~105-117, ~240-244) — заменить на proj.plane_project_id (Plane-sync для ЭТОЙ задачи должен идти в её проект). Передавай project_id дальше в helper'ы.

Критерий: issue из проекта orchestrator → repo orchestrator, sync в проект orchestrator; неизвестный проект → ignored.


Task 3: plane_sync — параметризовать проект

Файл: src/plane_sync.py

  • 3.1 Функции (notify_stage_change, notify_qg_failure, notify_done, find_issue_id, и др.), которые строят URL с PROJECT_ID — должны принимать project_id параметром (с дефолтом на enduro для обратной совместимости, ЧТОБЫ не сломать существующие вызовы). Резолвить project_id из task (по repo задачи через get_project_by_repo).
  • 3.2 Вызовы из plane.py и qg/checks.py — передавать правильный project_id (из задачи/проекта). ⚠️ Если task знает только repo — резолвь get_project_by_repo(repo).plane_project_id.

Критерий: state/comment задачи orchestrator пишутся в Plane-проект orchestrator, а не в enduro.


Task 4: work_item prefix per project

Файл: src/db.py (get_next_work_item_id)

  • 4.1 Сделать prefix параметром: get_next_work_item_id(repo, prefix="ET"). Нумерация — по последней записи ЭТОГО repo+prefix. Вызов из plane.py передаёт proj.work_item_prefix.
  • 4.2 Убедиться, что фильтр WHERE repo=? корректен (orchestrator-задачи нумеруются ORCH-001, ORCH-002 независимо от ET).

Критерий: задача в orchestrator → ORCH-001; в enduro → ET-010 (продолжая существующую нумерацию).


Task 5: gitea-webhook (минимально)

Файл: src/webhooks/gitea.py

  • 5.1 Уже берёт repo из payload.repository.name — проверить, что для repo вне реестра — игнор (по аналогии с Task 2.1: get_project_by_repo(repo) None → ignored). Не запускать стадии для неизвестного repo.

Критерий: push в неизвестный repo не триггерит конвейер.


Task 6: тесты + документация

  • 6.1 tests/test_projects.py: резолверы (by plane_id, by repo, unknown → None, known_plane_project_ids), парсинг projects_json.
  • 6.2 tests/test_plane_webhook.py (или дополнить существующий): webhook из неизвестного проекта → ignored; из orchestrator → repo=orchestrator; из enduro → repo=enduro-trails. Мокать launcher.launch чтобы не запускать реальных агентов.
  • 6.3 Прогнать ВСЕ тесты (команда из Инфраструктуры). test_webhooks.py 9 pre-existing падений (401/signature) — не твои, не сломай остальное.
  • 6.4 Обновить docs/ARCHITECTURE.md (раздел multi-repo, реестр проектов), README (как добавить новый проект через ORCH_PROJECTS_JSON). Создать docs/BUGFIXES_2026-06-03.md (или дату прогона) с описанием ORCH-6 + ссылкой на инцидент.

Критерий: тесты зелёные, доки описывают как заводить новый проект.


Task 7: деплой + проверка (БЕЗ включения webhook!)

  • 7.1 Коммиты (Conventional Commits), push в feature/ORCH-6-multirepo, PR в orchestrator.
  • 7.2 Пересобрать: cd /home/slin/repos/orchestrator && docker compose up -d --build && sleep 5 && curl -s http://localhost:8500/health.
  • 7.3 Тест резолва (offline, без реальных запусков):
docker exec orchestrator python3 -c "
import sys; sys.path.insert(0,'/app')
from src.projects import get_project_by_plane_id, get_project_by_repo, 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', o
assert e.repo=='enduro-trails' and e.work_item_prefix=='ET', e
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()))
"
  • 7.4 Тест фильтра (симуляция webhook неизвестного проекта) — отправить POST /webhook/plane с data.project = фейковый uuid, убедиться что ответ ignored и НЕ создаётся task/ветка. (Можно через pytest с TestClient — предпочтительно, чтобы не дёргать живой эндпоинт.)
  • 7.5 ⚠️ НЕ включать Plane-webhook (is_active остаётся false). Включение — отдельный шаг Стрим после ревью.
  • 7.6 Отчитаться Стрим: резолв + фильтр подтверждены, готово к включению webhook.

Критерий: резолв корректен, неизвестный проект игнорируется, webhook остаётся выключенным.


Acceptance (что проверит Стрим)

# Проверка Ожидаемо
1 резолв by plane_id orchestrator→ORCH/repo, enduro→ET/repo
2 неизвестный проект None / ignored
3 webhook из orchestrator-проекта repo=orchestrator, sync в его Plane-проект
4 prefix per project ORCH-001 vs ET-010
5 gitea неизвестный repo ignored
6 тесты all pass (кроме 9 pre-existing)
7 webhook is_active остаётся false (Стрим включит после ревью)
8 доки multi-repo + как добавить проект

Ограничения

  • 🚫 НЕ трогай: nginx, openclaw.json, .env-СЕКРЕТЫ (можно добавить НОВЫЙ ключ ORCH_PROJECTS_JSON, но не трогать существующие токены), deploy-хук.
  • 🚫 НЕ включай Plane-webhook (is_active) — это сделает Стрим после ревью.
  • ⚠️ Обратная совместимость plane_sync: существующие вызовы без project_id должны продолжать работать (дефолт на enduro). Не сломай ET-009/текущие задачи.
  • ⚠️ Не ломай ORCH-2 (worktree), B-1/B-2/S-1/S-5 фиксы.
  • 🚫 Per-project deploy-хуки и per-project агентские инструкции — в этой задаче НЕ обязательны (можно вынести в подзадачу). Минимум для снятия предохранителя = фильтр проекта + resolve repo/prefix + plane_sync в правильный проект.
  • 🚫 Авто-clone нового repo — НЕ в этой задаче (оба repo уже в /repos).

Деплой-чеклист

  • src/projects.py + реестр + резолверы
  • plane-webhook фильтр по проекту + resolve repo
  • plane_sync параметризован по project_id (обратно совместимо)
  • prefix per project в db.py
  • gitea неизвестный repo → ignored
  • тесты зелёные (кроме pre-existing)
  • орк пересобран, health ok
  • резолв + фильтр подтверждены
  • webhook ОСТАЁТСЯ выключенным
  • доки + BUGFIXES
  • отчёт Стрим

Создано: 2026-06-02 | Автор ТЗ: Стрим | Исполнитель: Dev (Opus 4.8 Tokenator)