auto-sync: 2026-06-02 22:20:01
This commit is contained in:
@@ -263,4 +263,28 @@ ssh slin@82.22.50.71 "
|
||||
### Открытые вопросы Славе
|
||||
1. Смержить PR #19 (enduro-trails)?
|
||||
2. Смержить PR #1 (orchestrator worktree)?
|
||||
3. Что делать с ET-012 (M-3 analyst в неправильном репо) — откатить или использовать?
|
||||
|
||||
---
|
||||
|
||||
## ИНЦИДЕНТ: Plane-webhook авто-запустил конвейер по всем ORCH-1..7 (2026-06-02 19:00)
|
||||
|
||||
### Что случилось
|
||||
- Создала в Plane задачи ORCH-1..7 — **Plane-webhook поймал каждую и авто-запустил конвейер** (analyst→architect, auto-commit)
|
||||
- Всё ушло в **неправильный репо enduro-trails** (`plane.py:91` hardcode `repo=settings.default_repo`), наплодило мусор ET-010..016
|
||||
- Корень: Plane-webhook (id `93f0c342-a614-4248-9d0f-c107276f5620`) срабатывает на ЛЮБ\u041eЕ issue в workspace, без фильтра по проекту
|
||||
- ⚠️ **ПОЗИТИВ:** автономность реально работает — analyst exit=0, auto-commit, worktree (ORCH-2) сработал
|
||||
|
||||
### Меры (сделано)
|
||||
- 🛡️ **Plane-webhook ДЕАКТИВИРОВАН** в Plane postgres: `UPDATE webhooks SET is_active=false`. Проверено: `is_active=f`
|
||||
- Plane postgres: контейнер `plane-app-plane-db-1`, `PGPASSWORD=plane`, db `plane`, user `plane`, table `webhooks`
|
||||
- Обратно включить после ORCH-6: `UPDATE webhooks SET is_active=true WHERE id=...`
|
||||
- 🧹 Вычищено: все ветки ET-010..016 (git local+remote, 204), worktree `_wt/enduro-trails/*` (root-owned → sudo rm), тестовые iso-A/iso-B, tasks≥19 в БД орка, agent_runs≥19
|
||||
- Plane чист: orchestrator=7 (ORCH-1..7), enduro=5 (родные). Junk ET-issues в Plane не было (орк генерил ET-номера сам в git+БД)
|
||||
- Заметка на сервере: `orchestrator/docs/INCIDENT_2026-06-02_webhook_autorun.txt`
|
||||
|
||||
### ROOT FIX = ORCH-6 (multi-repo)
|
||||
- `plane.py:91` `repo=settings.default_repo` → нужен фильтр по `plane_project_id` + маппинг repo per project
|
||||
- Пока ORCH-6 не сделан — webhook ДЕРЖАТЬ ВЫКЛЮЧЕННЫМ
|
||||
|
||||
### Открытые вопросы
|
||||
- Смержить PR #19 (enduro-trails) + PR #1 (orchestrator worktree)?
|
||||
245
tasks/orchestrator/DEV_TASK_ORCH6_MULTIREPO.md
Normal file
245
tasks/orchestrator/DEV_TASK_ORCH6_MULTIREPO.md
Normal file
@@ -0,0 +1,245 @@
|
||||
# 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.
|
||||
|
||||
### Структура
|
||||
|
||||
```python
|
||||
# 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), чтобы система работала из коробки.
|
||||
|
||||
### Резолверы
|
||||
|
||||
```python
|
||||
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` добавить:
|
||||
```python
|
||||
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` →
|
||||
```python
|
||||
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, без реальных запусков):**
|
||||
```bash
|
||||
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)*
|
||||
40
tasks/orchestrator/reports/dev-2026-06-02-orch6-multirepo.md
Normal file
40
tasks/orchestrator/reports/dev-2026-06-02-orch6-multirepo.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# Dev Report: ORCH-6 — Multi-repo (фильтр проекта + маппинг repo per project)
|
||||
Дата: 2026-06-02
|
||||
Статус: IN PROGRESS
|
||||
|
||||
## Задача
|
||||
Plane-webhook ловит issue из любого проекта и льёт всё в default_repo=enduro-trails (хардкод).
|
||||
Нужно: реестр проектов (Plane project id → repo + prefix), фильтр webhook по проекту,
|
||||
resolve repo/prefix из маппинга, plane_sync в правильный проект.
|
||||
|
||||
ТЗ: tasks/orchestrator/DEV_TASK_ORCH6_MULTIREPO.md
|
||||
|
||||
## Инфра
|
||||
- Сервер slin@82.22.50.71, репо /home/slin/repos/orchestrator (remote admin/orchestrator)
|
||||
- Контейнер orchestrator (port 8500)
|
||||
- Ветка: feature/ORCH-6-multirepo (от feature/ORCH-2-worktree)
|
||||
|
||||
## Сделано
|
||||
- [x] Task 0: прочитал ТЗ, аудит, весь релевантный код. Создал ветку.
|
||||
- [ ] Task 1: src/projects.py — реестр + резолверы + config.projects_json
|
||||
- [ ] Task 2: plane-webhook фильтр + resolve
|
||||
- [ ] Task 3: plane_sync параметризован по project_id (обратно совместимо)
|
||||
- [ ] Task 4: db.py prefix per project
|
||||
- [ ] Task 5: gitea неизвестный repo → ignored
|
||||
- [ ] Task 6: тесты + доки
|
||||
- [ ] Task 7: деплой + проверка резолва/фильтра
|
||||
|
||||
## Изменённые файлы
|
||||
(заполняется по ходу)
|
||||
|
||||
## Результат
|
||||
(в конце)
|
||||
|
||||
## Проблемы и решения
|
||||
- ВАЖНО: существующие тесты в test_webhooks.py используют `"project": "proj-1"` —
|
||||
это НЕ в реестре. После добавления фильтра задача не создастся → тесты сломаются.
|
||||
Решение: зарегистрировать proj-1 через ORCH_PROJECTS_JSON в env тестов
|
||||
(gitea-тесты используют repo enduro-trails — уже в дефолтном реестре, ок).
|
||||
|
||||
## Следующий шаг
|
||||
Task 1 — создать src/projects.py
|
||||
Reference in New Issue
Block a user