analyst(ET): auto-commit from analyst run_id=56
This commit is contained in:
169
docs/work-items/ET-015/01-brd.md
Normal file
169
docs/work-items/ET-015/01-brd.md
Normal file
@@ -0,0 +1,169 @@
|
||||
# BRD — ET-015 / ORCH-6: Multi-repo support для оркестратора
|
||||
|
||||
Work Item ID: ET-015
|
||||
Plane title: [ORCH-6] Multi-repo: оркестратор работает по нескольким проектам
|
||||
Целевой репозиторий изменений: `orchestrator` (а не `enduro-trails`)
|
||||
Где трекается work item: `enduro-trails/docs/work-items/ET-015/` (организационная задача)
|
||||
|
||||
## 1. Контекст и проблема
|
||||
|
||||
Multi-Agent Orchestrator (FastAPI-сервис на порту 8500) сегодня жёстко
|
||||
завязан на один проект — `enduro-trails`. Это видно по нескольким местам:
|
||||
|
||||
1. `ORCH_DEFAULT_REPO=enduro-trails` — единственный fallback, который
|
||||
используется почти везде в `webhooks/plane.py`, `webhooks/gitea.py`,
|
||||
`agents/launcher.py`.
|
||||
2. Префикс work item ID жёстко `ET-` (`get_next_work_item_id` в `db.py`,
|
||||
строка ~93–99): любой новый таск, откуда бы он ни пришёл, получает
|
||||
ID вида `ET-NNN`.
|
||||
3. Plane-настройки одиночные: `ORCH_PLANE_WORKSPACE_SLUG`,
|
||||
`ORCH_PLANE_PROJECT_ID`, словарь `PLANE_STATES` со state-id'ами
|
||||
конкретного проекта `enduro-trails`. Webhooks от другого Plane-проекта
|
||||
будут обработаны, но patch'и состояний и комментарии полетят в чужой
|
||||
проект.
|
||||
4. Документация (`docs/phases/PH-1..PH-9`, фазы roadmap), agent-prompts
|
||||
(`.openclaw/agents/*.md`) и Quality Gates конвенции живут в `enduro-trails`
|
||||
и неявно считаются «универсальными».
|
||||
|
||||
В результате невозможно подключить к оркестратору ни сам `orchestrator` (мета-разработка),
|
||||
ни любой будущий проект (например `enduro-tiles-data`, `enduro-mobile`).
|
||||
|
||||
## 2. Бизнес-цель
|
||||
|
||||
Дать оркестратору возможность обслуживать **N проектов одновременно**, не теряя
|
||||
текущей функциональности по `enduro-trails` и без миграции исторических данных.
|
||||
|
||||
Это нужно, чтобы:
|
||||
|
||||
- запускать пайплайн на самом репо `orchestrator` (dogfooding —
|
||||
улучшения оркестратора проходят через тот же пайплайн);
|
||||
- начать вести новые проекты той же командой и тем же инструментарием
|
||||
(тайлы, мобильное приложение, бэкенды для соседних идей);
|
||||
- иметь единую точку наблюдения за разработкой нескольких репо
|
||||
(один `/status`, один Telegram-канал, одни логи).
|
||||
|
||||
## 3. Целевые пользователи и сценарии
|
||||
|
||||
| Роль | Сценарий | Ожидание |
|
||||
|------|----------|----------|
|
||||
| Owner (homenet542@gmail.com) | Подключить к оркестратору новый проект `foo` | За один шаг (правка конфига + перезапуск) проект становится известен оркестратору. Никаких code-change'ей в `src/`. |
|
||||
| Stakeholder в Plane | Создаёт work item в проекте `Foo` (Plane workspace) | Получает branch `feature/FOO-NNN-slug` в Gitea-репо `foo`, артефакты в `docs/work-items/FOO-NNN/`, анализ запускается автоматически. |
|
||||
| Developer (claude-bot) | Анализирует work item, который пришёл из проекта `Foo` | Видит корректный work item ID (`FOO-007`), корректный repo (`foo`), корректную ветку (`feature/FOO-007-...`). Worktree создаётся под нужным репо. |
|
||||
| Owner | Смотрит `/status` оркестратора | Видит все активные задачи across all repos, с указанием repo для каждой. |
|
||||
| Owner | Хочет временно поставить проект на паузу | Может выключить конкретный проект в конфиге, не трогая остальные. |
|
||||
|
||||
## 4. Бизнес-требования
|
||||
|
||||
### BR-1. Реестр проектов
|
||||
Оркестратор должен знать список обслуживаемых проектов. Источник истины
|
||||
конфигурируется (env / file / иной способ — на усмотрение архитектора).
|
||||
Для каждого проекта задаются как минимум:
|
||||
- имя Gitea-репо (`repo`),
|
||||
- префикс work item ID (`ET`, `ORCH`, `FOO`, ...),
|
||||
- идентификаторы Plane (workspace_slug, project_id),
|
||||
- маппинг состояний Plane (`backlog`, `todo`, `in_progress`,
|
||||
`needs_input`, `in_review`, `blocked`, `done`, `cancelled`) — каждый
|
||||
Plane-проект имеет собственные UUID состояний.
|
||||
|
||||
### BR-2. Маршрутизация Plane-вебхуков по проектам
|
||||
При получении вебхука от Plane оркестратор обязан определить, какому
|
||||
проекту из реестра принадлежит событие (по `project_id` в payload),
|
||||
и работать с настройками этого проекта (Plane state IDs, workspace,
|
||||
gitea-репо).
|
||||
|
||||
### BR-3. Маршрутизация Gitea-вебхуков по репозиториям
|
||||
При получении push / PR / status оркестратор должен использовать
|
||||
`repository.name` из payload как ключ для поиска проекта в реестре
|
||||
вместо `settings.default_repo`.
|
||||
|
||||
### BR-4. Префикс work item ID — per-project
|
||||
Генерация следующего ID должна продолжать существующую нумерацию
|
||||
**в пределах своего префикса**: для `enduro-trails` — `ET-016` после
|
||||
`ET-015`; для `orchestrator` — `ORCH-001` для первого таска, `ORCH-002`
|
||||
далее; ID одного проекта не пересекают и не сдвигают ID другого.
|
||||
|
||||
### BR-5. Изоляция worktree per (repo, branch)
|
||||
Текущая схема `/repos/_wt/<repo>/<safe-branch>` уже репо-aware, но
|
||||
оркестратор должен предполагать наличие main-clone'а для каждого репо
|
||||
в `/repos/<repo>`. Если main-clone отсутствует — внятная ошибка
|
||||
в логах + Telegram (а не падение).
|
||||
|
||||
### BR-6. Agent prompts и фазы — per-repo
|
||||
Agent-промпты (`.openclaw/agents/<agent>.md`), `CLAUDE.md`, `docs/phases/`
|
||||
у каждого проекта могут отличаться. Оркестратор не должен «протаскивать»
|
||||
артефакты `enduro-trails` в другие репозитории.
|
||||
|
||||
### BR-7. Обратная совместимость
|
||||
Существующий проект `enduro-trails` после миграции продолжает работать
|
||||
без потери истории:
|
||||
- активные задачи в `tasks` таблице не ломаются;
|
||||
- ID `ET-015`, `ET-016`, … продолжают выдаваться;
|
||||
- комментарии и состояния в Plane продолжают летать в текущий
|
||||
workspace/project.
|
||||
|
||||
### BR-8. Наблюдаемость
|
||||
`/status` (и любые админ-эндпоинты) выводит `repo` для каждой активной
|
||||
задачи. Telegram-уведомления и логи также включают `repo`,
|
||||
чтобы их можно было разделить per-project.
|
||||
|
||||
### BR-9. Минимально необходимый рантайм-конфиг
|
||||
Подключение нового проекта не требует изменения кода `src/`.
|
||||
Только конфиг + наличие main-clone'а репозитория на хосте + перезапуск
|
||||
контейнера. Webhook secret-ы (Plane / Gitea) — также per-project.
|
||||
|
||||
### BR-10. Безопасность
|
||||
Каждый Plane-проект может иметь собственный webhook secret;
|
||||
оркестратор должен проверять HMAC с правильным секретом для конкретного
|
||||
проекта, иначе вебхук отбрасывается с 401.
|
||||
|
||||
## 5. Out of scope для этой задачи
|
||||
|
||||
- Параллельная обработка нескольких задач (S-4 уже сделал worktree per
|
||||
branch, но in-process очередь задач — отдельный F-2b).
|
||||
- Веб-UI для управления проектами.
|
||||
- Автоматическое подтягивание main-clone'а нового репо (`git clone`
|
||||
выполняется руками или другим инструментом).
|
||||
- Изменение pipeline-stage машины (`stages.py`).
|
||||
- Изменение модели данных Plane (state-id'ы определяются в Plane,
|
||||
оркестратор только подхватывает).
|
||||
|
||||
## 6. Допущения и ограничения
|
||||
|
||||
- Все обслуживаемые репозитории живут в одном Gitea-инстансе
|
||||
(один `ORCH_GITEA_URL`, один `ORCH_GITEA_TOKEN`, один `owner=admin`).
|
||||
Multi-Gitea — не в скоупе.
|
||||
- Все Plane-проекты — в одном Plane-инстансе (`ORCH_PLANE_API_URL`),
|
||||
но могут быть в разных workspace.
|
||||
- Claude CLI бинарник один и тот же (`/opt/claude-code/bin/claude.exe`).
|
||||
- Hooks деплоя per-repo не обязаны существовать на этом этапе:
|
||||
если у репо нет деплой-стадии — `deploy` помечается `done` без
|
||||
смоук-теста (либо проект явно объявляет, что у него нет deploy).
|
||||
|
||||
## 7. Метрики успеха
|
||||
|
||||
1. Создание work item'а в Plane-проекте `Orchestrator` приводит к появлению
|
||||
ветки `feature/ORCH-NNN-…` в Gitea-репо `orchestrator` и запуску
|
||||
analyst-агента в worktree `/repos/_wt/orchestrator/<branch>`.
|
||||
2. ID нового таска в `orchestrator` имеет префикс `ORCH-`, а не `ET-`.
|
||||
3. Создание work item в `enduro-trails` продолжает работать: ID `ET-016`,
|
||||
ветка `feature/ET-016-...`, всё как раньше.
|
||||
4. `/status` возвращает поле `repo` для каждой задачи.
|
||||
5. Telegram-уведомление содержит `repo` в префиксе сообщения.
|
||||
6. Регрессии не наблюдаются на 2 последовательных `ET-NNN` задачах
|
||||
после раскатки.
|
||||
|
||||
## 8. Зависимости и риски (на уровне бизнеса)
|
||||
|
||||
- Plane state IDs другого проекта надо получить вручную (через Plane UI
|
||||
или API) — этим занимается Owner перед подключением.
|
||||
- Webhook secret per-project потребует пересоздать webhook'и
|
||||
в каждом Plane-проекте и каждом Gitea-репо.
|
||||
- Schema `tasks` уже хранит `repo` — миграции данных не требуется.
|
||||
|
||||
## 9. Связанные документы
|
||||
|
||||
- 02-trz.md — функциональные/нефункциональные требования (этот пакет).
|
||||
- 03-acceptance-criteria.md — критерии приёмки.
|
||||
- 04-test-plan.yaml — план тестов.
|
||||
- Architecture phase: создаст ADR по способу хранения реестра проектов
|
||||
и стратегии маппинга Plane state IDs.
|
||||
270
docs/work-items/ET-015/02-trz.md
Normal file
270
docs/work-items/ET-015/02-trz.md
Normal file
@@ -0,0 +1,270 @@
|
||||
# ТЗ — 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-lookup
|
||||
`tasks.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 для <repo> не найден,
|
||||
подключение проекта неполное», возвращает 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` приводит к 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».
|
||||
185
docs/work-items/ET-015/03-acceptance-criteria.md
Normal file
185
docs/work-items/ET-015/03-acceptance-criteria.md
Normal file
@@ -0,0 +1,185 @@
|
||||
# Acceptance Criteria — ET-015 / ORCH-6: Multi-repo support
|
||||
|
||||
Work Item ID: ET-015
|
||||
Target repo: `orchestrator`
|
||||
|
||||
Принять можно при выполнении ВСЕХ AC ниже. Формат — Given/When/Then.
|
||||
|
||||
---
|
||||
|
||||
## AC-1. Реестр проектов загружается на старте
|
||||
|
||||
**Given** оркестратор настроен с реестром из 2 проектов
|
||||
(`enduro-trails`, `orchestrator`),
|
||||
**When** контейнер стартует,
|
||||
**Then** в логах есть запись `Loaded N projects: enduro-trails, orchestrator`
|
||||
(или эквивалентная), и неработоспособный конфиг (например, отсутствие
|
||||
`plane_project_id`) приводит к WARNING/ERROR с указанием конкретного
|
||||
проекта и причины.
|
||||
|
||||
## AC-2. Plane webhook маршрутизируется по project_id
|
||||
|
||||
**Given** в реестре два проекта с разными `plane_project_id`,
|
||||
**When** в `/webhook/plane` приходит `work_item.created` с
|
||||
`project_id = <enduro_trails_uuid>`,
|
||||
**Then** создаётся ветка в gitea-репо `enduro-trails`,
|
||||
work item ID имеет префикс `ET-`, в Plane состояния обновляются
|
||||
через workspace/project из конфига `enduro-trails`.
|
||||
|
||||
**And When** аналогичный вебхук приходит с
|
||||
`project_id = <orchestrator_uuid>`,
|
||||
**Then** создаётся ветка в gitea-репо `orchestrator`,
|
||||
work item ID имеет префикс `ORCH-`, состояния обновляются в
|
||||
правильном Plane-проекте.
|
||||
|
||||
## AC-3. Gitea webhook маршрутизируется по repository.name
|
||||
|
||||
**Given** в gitea зарегистрированы webhook'и на оба репо,
|
||||
**When** в `/webhook/gitea` приходит `push` с
|
||||
`repository.name = "orchestrator"`,
|
||||
**Then** ищется задача `tasks` с `repo='orchestrator' AND
|
||||
branch=<refs/heads/...>`. Push'и в `enduro-trails` не должны затрагивать
|
||||
задачи `orchestrator` и наоборот.
|
||||
|
||||
## AC-4. Префикс work item ID — per-project
|
||||
|
||||
**Given** в БД уже есть `ET-001..ET-015` для `enduro-trails`
|
||||
и нет ни одной задачи для `orchestrator`,
|
||||
**When** через webhook создаётся новая задача в `enduro-trails`,
|
||||
**Then** её ID = `ET-016`.
|
||||
|
||||
**And When** через webhook создаётся первая задача в `orchestrator`,
|
||||
**Then** её ID = `ORCH-001`.
|
||||
|
||||
**And When** создаётся вторая задача в `orchestrator`,
|
||||
**Then** её ID = `ORCH-002`, **а не `ORCH-003`** (нумерация
|
||||
не подскакивает из-за параллельной нумерации в `enduro-trails`).
|
||||
|
||||
## AC-5. Plane state-id'ы берутся per-project
|
||||
|
||||
**Given** Plane state-id'ы у `enduro-trails` и `orchestrator`
|
||||
заведомо разные,
|
||||
**When** оркестратор пытается выставить состояние `in_progress`
|
||||
для work item `ORCH-001`,
|
||||
**Then** в Plane API улетает PATCH с UUID состояния из конфига
|
||||
проекта `orchestrator`, а **не** с UUID `b873d9eb-...` (in_progress
|
||||
для `enduro-trails`).
|
||||
|
||||
## AC-6. Plane comment роутится в правильный проект
|
||||
|
||||
**Given** активны задачи `ET-016` и `ORCH-001`,
|
||||
**When** оркестратор шлёт `add_comment("ORCH-001", "...")`,
|
||||
**Then** комментарий появляется в issue `ORCH-001` в Plane-проекте
|
||||
`Orchestrator`, и **не появляется** в Plane-проекте `enduro-trails`.
|
||||
|
||||
## AC-7. Worktree per repo
|
||||
|
||||
**Given** одновременно активны задачи в `enduro-trails`
|
||||
(branch `feature/ET-016-x`) и `orchestrator`
|
||||
(branch `feature/ORCH-001-y`),
|
||||
**When** оркестратор запускает analyst-агента для обеих,
|
||||
**Then** существуют два worktree:
|
||||
- `/repos/_wt/enduro-trails/feature_ET-016-x/`
|
||||
- `/repos/_wt/orchestrator/feature_ORCH-001-y/`
|
||||
|
||||
И каждый агент работает в своём worktree, не пересекаясь.
|
||||
|
||||
## AC-8. Agent-prompts читаются из своего репо
|
||||
|
||||
**Given** в `orchestrator` лежит свой `.openclaw/agents/analyst.md`,
|
||||
отличный от `enduro-trails`,
|
||||
**When** запускается analyst для задачи `ORCH-001`,
|
||||
**Then** в команде Claude CLI передаётся system-prompt из
|
||||
`/repos/_wt/orchestrator/<branch>/.openclaw/agents/analyst.md`,
|
||||
**а не** из `enduro-trails`.
|
||||
|
||||
## AC-9. Обратная совместимость по `enduro-trails`
|
||||
|
||||
**Given** релиз раскатан,
|
||||
**When** Owner создаёт новый work item в Plane-проекте `enduro-trails`,
|
||||
**Then** поведение идентично текущему: ветка `feature/ET-NNN-slug`,
|
||||
artifact'ы в `docs/work-items/ET-NNN/`, analyst запускается,
|
||||
ID = следующий после `ET-015`.
|
||||
|
||||
## AC-10. Существующие задачи не ломаются
|
||||
|
||||
**Given** в `tasks` есть задачи в стадиях `analysis`, `development`,
|
||||
`review` (из ET-001..ET-014, ET-015),
|
||||
**When** контейнер перезапущен после раскатки фичи,
|
||||
**Then** все эти задачи продолжают свой stage без потери контекста,
|
||||
их `repo` остаётся `enduro-trails`, ID остаются прежними,
|
||||
worktree для активных веток не пересоздаётся (или пересоздаётся без
|
||||
потери uncommitted changes).
|
||||
|
||||
## AC-11. /status — repo-aware
|
||||
|
||||
**Given** активны 2 задачи в разных репо,
|
||||
**When** Owner делает `GET /status`,
|
||||
**Then** ответ содержит массив объектов, в каждом из которых
|
||||
присутствуют поля `work_item_id`, `repo`, `branch`, `stage`.
|
||||
|
||||
## AC-12. Неизвестный проект — мягкая обработка
|
||||
|
||||
**Given** в `/webhook/plane` приходит событие с `project_id`, которого
|
||||
нет в реестре,
|
||||
**When** оркестратор обрабатывает событие,
|
||||
**Then** ответ 202, в логах WARNING `Unknown project_id: <uuid>`,
|
||||
никаких side-эффектов (ни ветки, ни записи в `tasks`).
|
||||
Telegram-уведомление отправляется не чаще 1 раза в 10 минут
|
||||
для одного и того же неизвестного `project_id`.
|
||||
|
||||
## AC-13. Отсутствие main-clone — внятная ошибка
|
||||
|
||||
**Given** в реестре проект `foo`, но `/repos/foo` не существует,
|
||||
**When** приходит вебхук на создание work item в `foo`,
|
||||
**Then** в логах ERROR `Main repo not found: /repos/foo`,
|
||||
Telegram-уведомление `[foo] main-clone отсутствует, проект не работает`,
|
||||
HTTP-ответ 202 (не 500), запись в `tasks` НЕ создаётся (либо
|
||||
создаётся со специальным флагом — на усмотрение архитектора, но
|
||||
не должна привести к падению agent launcher'а).
|
||||
|
||||
## AC-14. HMAC проверка per-project
|
||||
|
||||
**Given** для проекта `enduro-trails` задан webhook secret `S1`,
|
||||
для `orchestrator` — `S2`,
|
||||
**When** в `/webhook/plane` приходит событие от `orchestrator`,
|
||||
подписанное `S1`,
|
||||
**Then** запрос отвергается с 401.
|
||||
|
||||
**And When** то же событие подписано `S2`,
|
||||
**Then** запрос принимается.
|
||||
|
||||
## AC-15. Проект с `has_deploy_stage=false`
|
||||
|
||||
**Given** проект `orchestrator` объявлен без deploy-стадии,
|
||||
**When** в задаче `ORCH-001` стадия `testing` успешно завершена
|
||||
(test report содержит PASS),
|
||||
**Then** задача переходит сразу в `done`, deployer не запускается,
|
||||
Plane issue получает state `done`.
|
||||
|
||||
## AC-16. Тесты в репозитории `orchestrator` зелёные
|
||||
|
||||
**Given** код изменений выложен в ветку,
|
||||
**When** запускается `pytest tests/`,
|
||||
**Then** все существующие тесты проходят + добавлены новые тесты
|
||||
на multi-repo логику (см. test plan).
|
||||
|
||||
## AC-17. Документация обновлена
|
||||
|
||||
**Given** код мержится в `main` оркестратора,
|
||||
**Then** обновлены:
|
||||
- `README.md`: раздел про конфигурацию проектов;
|
||||
- `docs/SETUP_WEBHOOKS.md`: инструкция per-project (если есть такой файл);
|
||||
- Создан ADR в `docs/work-items/ET-015/06-adr/` по реестру проектов
|
||||
и Plane state mapping.
|
||||
|
||||
---
|
||||
|
||||
## Definition of Done
|
||||
|
||||
- Все AC-1..AC-17 выполнены.
|
||||
- Code review пройден (verdict: APPROVED во frontmatter 12-review.md).
|
||||
- `pytest tests/` зелёный в репо `orchestrator`.
|
||||
- Integration smoke на тестовом стенде: подключение второго проекта
|
||||
и end-to-end создание тестового WI проходят успешно.
|
||||
- Никаких регрессий на `enduro-trails`.
|
||||
253
docs/work-items/ET-015/04-test-plan.yaml
Normal file
253
docs/work-items/ET-015/04-test-plan.yaml
Normal file
@@ -0,0 +1,253 @@
|
||||
work_item_id: ET-015
|
||||
title: "Multi-repo support для оркестратора"
|
||||
target_repo: orchestrator
|
||||
notes: >
|
||||
Тесты пишутся и запускаются в репо `orchestrator` (pytest).
|
||||
Поскольку фича чисто бэкендовая (нет UI-изменений в enduro-trails),
|
||||
UI test cases НЕ создаются.
|
||||
|
||||
unit:
|
||||
- id: UT-01
|
||||
name: get_next_work_item_id — per-prefix numbering
|
||||
file: tests/test_db_multi_repo.py::test_next_id_per_prefix
|
||||
given: "tasks table has ET-001..ET-015 (repo=enduro-trails) and no ORCH-* rows"
|
||||
when: "вызывается get_next_work_item_id для проекта с prefix='ET'"
|
||||
expect: "возвращает 'ET-016'"
|
||||
and_when: "вызывается get_next_work_item_id для проекта с prefix='ORCH'"
|
||||
expect_2: "возвращает 'ORCH-001'"
|
||||
covers_ac: [AC-4]
|
||||
|
||||
- id: UT-02
|
||||
name: get_next_work_item_id — нумерация ORCH идёт независимо от ET
|
||||
file: tests/test_db_multi_repo.py::test_orch_numbering_independent
|
||||
given: "в tasks есть ET-001..ET-015 и ORCH-001..ORCH-003"
|
||||
when: "запрашивается следующий ID для ORCH"
|
||||
expect: "ORCH-004 (не ORCH-016)"
|
||||
covers_ac: [AC-4]
|
||||
|
||||
- id: UT-03
|
||||
name: ProjectRegistry — load from config
|
||||
file: tests/test_project_registry.py::test_load_two_projects
|
||||
given: "конфиг с двумя проектами"
|
||||
when: "registry загружается на старте"
|
||||
expect: "len(registry.projects) == 2; lookup по plane_project_id возвращает правильный key"
|
||||
covers_ac: [AC-1, AC-2]
|
||||
|
||||
- id: UT-04
|
||||
name: ProjectRegistry — invalid config
|
||||
file: tests/test_project_registry.py::test_invalid_config_logged
|
||||
given: "конфиг с проектом без plane_project_id"
|
||||
when: "registry загружается"
|
||||
expect: "запись пропускается с WARNING, остальные грузятся"
|
||||
covers_ac: [AC-1]
|
||||
|
||||
- id: UT-05
|
||||
name: ProjectRegistry — lookup by plane_project_id
|
||||
file: tests/test_project_registry.py::test_lookup_by_plane_project_id
|
||||
given: "registry с тремя проектами"
|
||||
when: "lookup по plane_project_id='unknown-uuid'"
|
||||
expect: "возвращает None"
|
||||
covers_ac: [AC-12]
|
||||
|
||||
- id: UT-06
|
||||
name: ProjectRegistry — lookup by repo name
|
||||
file: tests/test_project_registry.py::test_lookup_by_repo
|
||||
given: "registry с проектом repo='orchestrator'"
|
||||
when: "lookup('orchestrator')"
|
||||
expect: "возвращает конфиг проекта"
|
||||
covers_ac: [AC-3]
|
||||
|
||||
- id: UT-07
|
||||
name: plane_sync — workspace/project определяется по work_item_id
|
||||
file: tests/test_plane_sync_multi.py::test_workspace_per_project
|
||||
given: "tasks содержит ORCH-001 (repo=orchestrator); registry имеет настройки orchestrator"
|
||||
when: "вызывается update_issue_state('ORCH-001', 'in_progress')"
|
||||
expect: "httpx.patch вызывается с URL содержащим workspace проекта orchestrator и state-id из его plane_states"
|
||||
covers_ac: [AC-5, AC-6]
|
||||
|
||||
- id: UT-08
|
||||
name: verify_plane_signature — per-project secret
|
||||
file: tests/test_webhooks_multi.py::test_hmac_per_project
|
||||
given: "проект orchestrator имеет secret S2"
|
||||
when: "вебхук подписан S1"
|
||||
expect: "verify возвращает False"
|
||||
and_when: "вебхук подписан S2"
|
||||
expect_2: "verify возвращает True"
|
||||
covers_ac: [AC-14]
|
||||
|
||||
- id: UT-09
|
||||
name: get_repo_path_or_fail — отсутствие main-clone
|
||||
file: tests/test_launcher_multi.py::test_missing_main_clone
|
||||
given: "проект foo в реестре, /repos/foo не существует"
|
||||
when: "launcher.launch('analyst', 'foo', ...)"
|
||||
expect: "raises FileNotFoundError И логируется ERROR с текстом 'Main repo not found'"
|
||||
covers_ac: [AC-13]
|
||||
|
||||
- id: UT-10
|
||||
name: has_deploy_stage=false — skip deployer
|
||||
file: tests/test_launcher_multi.py::test_skip_deploy_stage
|
||||
given: "проект orchestrator с has_deploy_stage=false; задача в testing с PASS test report"
|
||||
when: "_try_advance_stage вызывается"
|
||||
expect: "task.stage становится done; deployer.launch НЕ вызывается"
|
||||
covers_ac: [AC-15]
|
||||
|
||||
- id: UT-11
|
||||
name: STAGE_TO_STATE — стейт берётся из конфига проекта
|
||||
file: tests/test_plane_sync_multi.py::test_stage_to_state_per_project
|
||||
given: "два проекта с разными UUID для in_progress"
|
||||
when: "оба переходят в стадию development"
|
||||
expect: "patch с разными UUID — каждый со своим"
|
||||
covers_ac: [AC-5]
|
||||
|
||||
- id: UT-12
|
||||
name: handle_work_item_created — branch создаётся в нужном репо
|
||||
file: tests/test_webhooks_multi.py::test_branch_in_correct_repo
|
||||
given: "вебхук с project_id orchestrator"
|
||||
when: "обрабатывается work_item.created"
|
||||
expect: "POST /api/v1/repos/admin/orchestrator/branches; НЕ /enduro-trails/branches"
|
||||
covers_ac: [AC-2]
|
||||
|
||||
integration:
|
||||
- id: IT-01
|
||||
name: End-to-end создание задачи в проекте enduro-trails
|
||||
file: tests/integration/test_e2e_enduro.py::test_create_et_task
|
||||
given: "оркестратор запущен, registry с обоими проектами; mock Plane + mock Gitea"
|
||||
when: "POST /webhook/plane с work_item.created для enduro-trails"
|
||||
expect:
|
||||
- "Plane mock получил PATCH issue.state = in_progress"
|
||||
- "Gitea mock получил POST /branches"
|
||||
- "tasks содержит запись (repo=enduro-trails, work_item_id=ET-NNN, stage=analysis)"
|
||||
- "Был вызван launcher.launch('analyst', 'enduro-trails', ...)"
|
||||
covers_ac: [AC-2, AC-9]
|
||||
|
||||
- id: IT-02
|
||||
name: End-to-end создание задачи в проекте orchestrator
|
||||
file: tests/integration/test_e2e_orch.py::test_create_orch_task
|
||||
given: "то же, что IT-01"
|
||||
when: "POST /webhook/plane с work_item.created для orchestrator (другой project_id)"
|
||||
expect:
|
||||
- "Gitea mock получил POST /repos/admin/orchestrator/branches"
|
||||
- "tasks содержит (repo=orchestrator, work_item_id=ORCH-001)"
|
||||
- "launcher.launch('analyst', 'orchestrator', ...) — НЕ enduro-trails"
|
||||
covers_ac: [AC-2, AC-4]
|
||||
|
||||
- id: IT-03
|
||||
name: Параллельные задачи в разных репо не пересекаются
|
||||
file: tests/integration/test_concurrent_repos.py::test_two_repos
|
||||
given: "две задачи: ET-099 в enduro-trails и ORCH-099 в orchestrator"
|
||||
when: "обе на стадии analysis с активными worktree"
|
||||
expect:
|
||||
- "/repos/_wt/enduro-trails/feature_ET-099-... существует"
|
||||
- "/repos/_wt/orchestrator/feature_ORCH-099-... существует"
|
||||
- "артефакты ET записаны в enduro-trails worktree, ORCH — в orchestrator worktree"
|
||||
covers_ac: [AC-7]
|
||||
|
||||
- id: IT-04
|
||||
name: Gitea push для одного репо не задевает задачи в другом
|
||||
file: tests/integration/test_gitea_routing.py::test_push_isolation
|
||||
given: "активны ET-099 и ORCH-099"
|
||||
when: "приходит push на ветку ORCH-099 в репо orchestrator"
|
||||
expect:
|
||||
- "tasks.stage для ORCH-099 обновляется при наличии валидных файлов"
|
||||
- "tasks.stage для ET-099 НЕ меняется"
|
||||
covers_ac: [AC-3]
|
||||
|
||||
- id: IT-05
|
||||
name: Plane comment :approved: для orchestrator advance'ит правильную задачу
|
||||
file: tests/integration/test_plane_comment_routing.py::test_approved_routing
|
||||
given: "ET-099 stage=analysis, ORCH-099 stage=analysis"
|
||||
when: "comment.created с :approved: на issue ORCH-099"
|
||||
expect:
|
||||
- "tasks для ORCH-099 advance в architecture"
|
||||
- "tasks для ET-099 не меняется"
|
||||
covers_ac: [AC-6]
|
||||
|
||||
- id: IT-06
|
||||
name: Неизвестный project_id игнорируется без 500
|
||||
file: tests/integration/test_unknown_project.py::test_unknown_returns_202
|
||||
given: "registry с одним проектом enduro-trails"
|
||||
when: "приходит вебхук с project_id='unknown'"
|
||||
expect:
|
||||
- "HTTP 202"
|
||||
- "WARNING в логах с текстом 'Unknown project_id'"
|
||||
- "tasks без новых записей"
|
||||
covers_ac: [AC-12]
|
||||
|
||||
- id: IT-07
|
||||
name: Отсутствие main-clone — Telegram + 202
|
||||
file: tests/integration/test_missing_clone.py::test_no_main_clone
|
||||
given: "registry с проектом foo, /repos/foo отсутствует; Telegram mock"
|
||||
when: "приходит work_item.created для foo"
|
||||
expect:
|
||||
- "HTTP 202"
|
||||
- "ERROR в логах 'Main repo not found: /repos/foo'"
|
||||
- "Telegram mock получил одно сообщение"
|
||||
covers_ac: [AC-13]
|
||||
|
||||
e2e:
|
||||
- id: E2E-01
|
||||
name: Smoke: подключение нового проекта на тестовом стенде
|
||||
type: manual
|
||||
where: "test stand (mva154)"
|
||||
steps:
|
||||
- "Подготовить /repos/orchestrator на хосте (git clone, если ещё нет)"
|
||||
- "Создать в Plane проект 'Orchestrator-smoke', получить state UUID'ы"
|
||||
- "Добавить запись в конфиг проектов оркестратора (см. ADR)"
|
||||
- "Перезапустить контейнер оркестратора: docker compose restart"
|
||||
- "Проверить логи: 'Loaded 2 projects: enduro-trails, orchestrator-smoke'"
|
||||
- "Зарегистрировать webhook Plane (target orchestrator) для Orchestrator-smoke"
|
||||
- "Создать тестовый work item 'Smoke ORCH-1' в Plane"
|
||||
- "Дождаться появления ветки в gitea-репо orchestrator (~30s)"
|
||||
- "Проверить tasks в БД оркестратора: repo=orchestrator, work_item_id=ORCH-001"
|
||||
- "Удалить тестовую запись Plane и проверить, что enduro-trails не пострадал"
|
||||
expect: "Все шаги без ошибок, регрессий на enduro-trails нет"
|
||||
covers_ac: [AC-2, AC-4, AC-7, AC-9, AC-17]
|
||||
|
||||
- id: E2E-02
|
||||
name: Smoke: создание следующей задачи в enduro-trails после релиза
|
||||
type: manual
|
||||
where: "test stand"
|
||||
steps:
|
||||
- "После раскатки фичи создать в Plane-проекте enduro-trails новый WI"
|
||||
- "Дождаться появления ветки feature/ET-016-..."
|
||||
- "Проверить artifact'ы в docs/work-items/ET-016/"
|
||||
expect: "ID=ET-016 (следующий после ET-015), всё как раньше"
|
||||
covers_ac: [AC-9, AC-4]
|
||||
|
||||
regression:
|
||||
- id: REG-01
|
||||
name: Активные задачи продолжают работу после рестарта
|
||||
type: manual
|
||||
where: "test stand"
|
||||
steps:
|
||||
- "Перед раскаткой запомнить snapshot tasks (id, stage, repo, branch)"
|
||||
- "Раскатать фичу"
|
||||
- "Перезапустить orchestrator container"
|
||||
- "Проверить tasks snapshot после рестарта"
|
||||
expect: "Записи идентичны (stage не сбился, repo/branch не изменились)"
|
||||
covers_ac: [AC-10]
|
||||
|
||||
- id: REG-02
|
||||
name: Test report check_tests_local продолжает работать
|
||||
given: "enduro-trails branch с зелёными тестами"
|
||||
when: "agent developer завершается"
|
||||
expect: "QG check_tests_local вызывает make test в worktree и проходит"
|
||||
covers_ac: [AC-9]
|
||||
|
||||
coverage:
|
||||
required_min_percent: 75
|
||||
applies_to:
|
||||
- src/config.py
|
||||
- src/db.py
|
||||
- src/plane_sync.py
|
||||
- src/webhooks/plane.py
|
||||
- src/webhooks/gitea.py
|
||||
- src/agents/launcher.py
|
||||
new_modules_expected:
|
||||
- src/project_registry.py # имя ориентировочное, точное название — на усмотрение архитектора
|
||||
|
||||
notes_for_tester:
|
||||
- "В существующих тестах могут быть моки на settings.default_repo — их нужно расширить под новый registry"
|
||||
- "find_issue_id в plane_sync.py делает DB lookup — учесть это при моках"
|
||||
- "Telegram-нотификации логировать через send_telegram моки, чтобы избежать реальных запросов"
|
||||
- "Не нужно тестировать сами Plane/Gitea API — только что оркестратор формирует правильные запросы"
|
||||
Reference in New Issue
Block a user