Files
enduro-trails/docs/work-items/ET-015/02-trz.md
claude-bot 68076fca1a
Some checks failed
CI / lint (push) Failing after 4s
CI / test (push) Failing after 5s
CI / build (push) Has been skipped
analyst(ET): auto-commit from analyst run_id=56
2026-06-02 19:03:41 +00:00

17 KiB
Raw Blame History

ТЗ — 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.2find_issue_id должен корректно работать с разными проектами: поиск идёт в Plane-проекте, к которому относится work_item_id (определяется по префиксу или через DB-lookup tasks.work_item_id → tasks.repo).
  • FR-4.3STAGE_TO_STATE остаётся справочной маппой (stage → ключ состояния, например in_progress), но PLANE_STATESтеперь per-project. Финальный UUID =project.plane_states[state_key]`.

FR-5. Webhook handlers

  • FR-5.1verify_plane_signature использует secret того проекта, которому принадлежит событие. Если проект не определяется — secret проверяется по дефолтному (текущее поведение для обратной совместимости), либо событие отбрасывается. Решение оформить в ADR.
  • FR-5.2handle_work_item_created использует repo, prefix, branch_template, plane_states целевого проекта вместо settings.default_repo.
  • FR-5.3_create_gitea_branch и _create_initial_docs пишут в тот репо, к которому относится событие.
  • FR-5.4handle_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.1ensure_worktree(repo, branch) корректно работает для любого репо. Уже так; нужны лишь интеграционные тесты на 2 разных репо одновременно.
  • FR-7.2 — если /repos/<repo> не существует, оркестратор: логирует ERROR, шлёт Telegram «main-clone для не найден, подключение проекта неполное», возвращает 202 (не 500) на вебхук. Не пытается клонировать сам.

FR-8. /status и наблюдаемость

  • FR-8.1GET /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 приводит к ID ET-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:

  1. Клонировать orchestrator в /repos/orchestrator (если ещё нет).
  2. Создать в Plane workspace проект Orchestrator.
  3. Получить из Plane UUID состояний (backlog, todo, in_progress, …).
  4. Добавить запись в реестр проектов оркестратора (способ — ADR).
  5. Создать webhook в Plane (target = оркестратор) и в Gitea для репо orchestrator.
  6. Перезапустить контейнер оркестратора.
  7. Создать тестовый 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. Открытые вопросы для архитектора

  1. Где хранить реестр проектов: env-vars (ORCH_PROJECTS_JSON=...), YAML-файл (/app/config/projects.yaml), таблица в SQLite, или гибрид? — ADR.
  2. Как валидировать конфиг проекта на старте? Что делать при некорректной записи (отказ при старте vs. skip-с-warning)? — ADR.
  3. Нужна ли горячая перезагрузка конфига (SIGHUP / endpoint) или достаточно рестарта контейнера? — рекомендация: достаточно рестарта.
  4. Как маппить event → project key при отсутствии project_id в Plane payload (старые webhook'и)? — ADR.
  5. Нужно ли поддерживать миграцию префикса для уже существующих задач? — рекомендация: нет, 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».