17 KiB
ТЗ — ET-015 / ORCH-6: Multi-repo support для оркестратора
Work Item ID: ET-015
Target repo для кода: orchestrator
Документация и трекинг: enduro-trails/docs/work-items/ET-015/
ТЗ описывает что должна делать система. Конкретные технические решения (формат реестра, способ хранения state-id'ов, и т.п.) принимаются на стадии Architecture (ADR).
1. Глоссарий
- Project — логическое подразделение, обслуживаемое оркестратором. Каждый project имеет один Gitea-репо, один Plane-проект, один префикс work item ID.
- Project key — короткий уникальный идентификатор проекта в реестре
(например
enduro-trails,orchestrator). Используется внутри оркестратора как ключ для поиска настроек. - Work item prefix — буквенный префикс, который ставится в ID
задач конкретного проекта (например
ET,ORCH). - Project config — набор настроек конкретного проекта (см. FR-1).
2. Функциональные требования
FR-1. Хранение конфигурации проектов
Оркестратор должен иметь возможность хранить конфигурацию N проектов. Для каждого проекта должны храниться следующие поля:
| Поле | Тип | Описание | Обязательное |
|---|---|---|---|
key |
str | Project key, совпадает с именем gitea-репо | да |
repo |
str | Имя gitea-репо | да (= key, как правило) |
work_item_prefix |
str (A-Z) | Префикс ID задач (например ET, ORCH) |
да |
plane_workspace_slug |
str | Plane workspace slug | да |
plane_project_id |
str (UUID) | Plane project UUID | да |
plane_states |
map | UUID для состояний backlog/todo/in_progress/needs_input/in_review/blocked/done/cancelled | да |
plane_webhook_secret |
str | Секрет для HMAC-проверки Plane webhook | опционально (если не задан — проверка отключается, как сейчас) |
gitea_webhook_secret |
str | Секрет для HMAC-проверки Gitea webhook | опционально |
enabled |
bool | Можно ли принимать события и запускать пайплайн | да, default true |
default_branch |
str | База для feature-веток (обычно main) |
опционально, default main |
has_deploy_stage |
bool | Есть ли deploy-этап (deployer-агент) | опционально, default true |
Способ хранения (env-vars / YAML-файл / JSON / DB) выбирает архитектор.
Важно: подключение нового проекта НЕ должно требовать изменений в src/.
FR-2. Поиск проекта по событию
Оркестратор должен уметь определить project key:
- FR-2.1 — по Plane webhook payload: по полю
project/project_id(Plane передаёт UUID проекта). Поиск идёт поplane_project_idв реестре. - FR-2.2 — по Gitea webhook payload: по
repository.name. Поиск идёт поrepo. - FR-2.3 — по уже существующей задаче в БД: достаточно
tasks.repo.
Если событие пришло из неизвестного проекта — оно логируется на уровне
WARNING и игнорируется (HTTP 202, не 500). Telegram-нотификация
отправляется один раз в N минут (анти-флуд), чтобы Owner мог заметить,
что вебхук неправильно настроен.
FR-3. Генерация work item ID per-prefix
Функция get_next_work_item_id(project_key) (или эквивалентная)
должна:
- Вернуть
<prefix>-<NNN>, гдеprefixберётся из конфигурации проекта. - Номер — следующий после максимального уже существующего ID с тем
же префиксом в таблице
tasks. Поиск ведётся поwork_item_id LIKE '<prefix>-%'(а не поrepo), чтобы переименование репо не сбивало нумерацию. - Нумерация per-project продолжается независимо. Никаких глобальных счётчиков.
FR-4. Plane sync — per-project
plane_sync.py должен:
- FR-4.1 — на каждый исходящий запрос (
update_issue_state,add_comment,find_issue_id,set_issue_*) определять, к какому проекту относитсяwork_item_id, и брать из конфига этого проектаworkspace_slug,project_id,plane_states. Текущие модульные константыWORKSPACE,PROJECT_ID,PLANE_STATESдолжны превратиться в значения, зависящие от project key. - FR-4.2 —
find_issue_idдолжен корректно работать с разными проектами: поиск идёт в Plane-проекте, к которому относитсяwork_item_id(определяется по префиксу или через DB-lookuptasks.work_item_id → tasks.repo). - FR-4.3 —
STAGE_TO_STATEостаётся справочной маппой (stage → ключ состояния, напримерin_progress), ноPLANE_STATESтеперь per-project. Финальный UUID =project.plane_states[state_key]`.
FR-5. Webhook handlers
- FR-5.1 —
verify_plane_signatureиспользует secret того проекта, которому принадлежит событие. Если проект не определяется — secret проверяется по дефолтному (текущее поведение для обратной совместимости), либо событие отбрасывается. Решение оформить в ADR. - FR-5.2 —
handle_work_item_createdиспользуетrepo,prefix,branch_template,plane_statesцелевого проекта вместоsettings.default_repo. - FR-5.3 —
_create_gitea_branchи_create_initial_docsпишут в тот репо, к которому относится событие. - FR-5.4 —
handle_comment,handle_push,handle_pr,handle_ci_statusопределяют project key из payload и используют его настройки.
FR-6. Agent launcher
AgentLauncher.launch(agent, repo, ...) уже принимает repo.
Изменения:
- FR-6.1 — путь к agent-prompts читается из worktree этого репо
(
.openclaw/agents/<agent>.md), а не из шаренногоenduro-trails. Текущая реализация уже так делает (worktree-relative); проверить и подтвердить тестом. - FR-6.2 — если у проекта
has_deploy_stage=false, то стадияtestingпосле успешного теста сразу advance'ится вdone(без запуска deployer'а). Поведениеenduro-trails(где deploy есть) не меняется. - FR-6.3 — task-description, передаваемый в
.task*.md, содержит полеRepo: <repo>— это уже так. Подтвердить.
FR-7. Worktree
- FR-7.1 —
ensure_worktree(repo, branch)корректно работает для любого репо. Уже так; нужны лишь интеграционные тесты на 2 разных репо одновременно. - FR-7.2 — если
/repos/<repo>не существует, оркестратор: логируетERROR, шлёт Telegram «main-clone для не найден, подключение проекта неполное», возвращает 202 (не 500) на вебхук. Не пытается клонировать сам.
FR-8. /status и наблюдаемость
- FR-8.1 —
GET /statusвозвращает массив задач с полями как минимум:task_id,work_item_id,repo,branch,stage,agent_running,created_at,updated_at. Полеrepo— обязательно. - FR-8.2 — все логи
orchestrator.*содержатrepo=<key>в строке записи там, где это применимо (push/PR handler, agent launch). - FR-8.3 — Telegram-сообщения для конкретной задачи содержат либо
<work_item_id>(он уже несёт префикс), либо префикс[<repo>]. Не вводить нового поля, использовать существующий work_item_id.
FR-9. Обратная совместимость и миграция
- FR-9.1 — После релиза существующие задачи
ET-001..ET-014не должны менять состояние, repo или branch. - FR-9.2 — После релиза создание новой задачи в Plane-проекте
enduro-trailsприводит к IDET-016(следующий послеET-015), как и раньше. - FR-9.3 — Schema БД не меняется (поле
repoуже есть). Если в DB обнаружатся записи сrepo=NULL— это считается багом данных и логируется, но не блокирует работу. - FR-9.4 — Дефолты
ORCH_PLANE_*иORCH_DEFAULT_REPOостаются вconfig.pyкак fallback для проектаenduro-trails(можно убрать на следующем этапе после стабилизации, не в рамках этого WI).
FR-10. Подключение второго проекта (приёмочный сценарий)
Owner может за следующий список шагов подключить проект orchestrator:
- Клонировать
orchestratorв/repos/orchestrator(если ещё нет). - Создать в Plane workspace проект
Orchestrator. - Получить из Plane UUID состояний (backlog, todo, in_progress, …).
- Добавить запись в реестр проектов оркестратора (способ — ADR).
- Создать webhook в Plane (target = оркестратор) и в Gitea для репо
orchestrator. - Перезапустить контейнер оркестратора.
- Создать тестовый work item в Plane → проверить, что появилась ветка
feature/ORCH-001-…в gitea-репоorchestrator, и вtasksсоздался рядrepo=orchestrator, work_item_id=ORCH-001.
3. Нефункциональные требования
NFR-1. Конфигурируемость
Добавление и удаление проекта не требует пересборки docker-образа. Конфигурация загружается на старте.
NFR-2. Изоляция
Сбой работы с одним проектом (например, недоступный Plane workspace) не должен ломать обработку событий других проектов. Каждый webhook обрабатывается независимо.
NFR-3. Производительность
Добавление 5 проектов не должно увеличивать latency обработки одного вебхука более чем на 10% относительно текущей точки.
NFR-4. Логирование
В каждом ERROR/WARNING логе, связанном с конкретной задачей, должны
присутствовать как минимум: repo, work_item_id (если уже создан),
branch (если применимо).
NFR-5. Безопасность
HMAC проверяется тем секретом, который привязан к проекту-владельцу события. Глобальный «accept-all-if-no-secret» оставляется только для совместимости; в продакшене Owner обязан задать secret для каждого webhook'а.
NFR-6. Надёжность миграции
Релиз должен катиться без остановки активных задач: задача в стадии
development не должна потерять контекст. Этого достичь можно тем,
что schema БД не меняется.
4. Интерфейсы
4.1 HTTP endpoints оркестратора
| Endpoint | Изменения |
|---|---|
GET /health |
без изменений |
GET /status |
в каждом элементе массива есть repo |
POST /webhook/plane |
определяет project key по project_id payload'а |
POST /webhook/gitea |
определяет project key по repository.name payload'а |
4.2 Plane webhook payload — какие поля используются
event(work_item.created,comment.created, ...).projectилиdata.project_id— Plane project UUID → используется для поиска project key в реестре.- остальные поля — без изменений.
4.3 Gitea webhook payload — какие поля используются
repository.name— используется для поиска project key в реестре.- остальные — без изменений.
4.4 Файлы артефактов
Структура docs/work-items/<work_item_id>/... сохраняется. Имена
файлов (BRD, TRZ, AC, TestPlan, ADR) — те же.
5. Что НЕ нужно делать в этом WI
- Не вводить новые стадии пайплайна.
- Не менять схему БД (нет миграций).
- Не переписывать
git_worktree.py— он уже multi-repo-ready. - Не делать UI для управления проектами.
- Не выносить агент-промпты из репо в оркестратор.
6. Открытые вопросы для архитектора
- Где хранить реестр проектов: env-vars (
ORCH_PROJECTS_JSON=...), YAML-файл (/app/config/projects.yaml), таблица в SQLite, или гибрид? — ADR. - Как валидировать конфиг проекта на старте? Что делать при некорректной записи (отказ при старте vs. skip-с-warning)? — ADR.
- Нужна ли горячая перезагрузка конфига (SIGHUP / endpoint) или достаточно рестарта контейнера? — рекомендация: достаточно рестарта.
- Как маппить event → project key при отсутствии
project_idв Plane payload (старые webhook'и)? — ADR. - Нужно ли поддерживать миграцию префикса для уже существующих задач? — рекомендация: нет, ID иммутабельны.
7. Зависимости
- Plane API доступен (для подтягивания state-id'ов в момент настройки — это операция Owner'а, не код оркестратора).
- Gitea API доступен (для создания веток).
- Для каждого репо есть main-clone в
/repos/<repo>.
8. Связанные ADR / документы
- Будут созданы в стадии Architecture:
- ADR: «Project registry: формат и место хранения».
- ADR: «Plane state mapping per project».
- Возможно: «Migration plan — config rollout».