diff --git a/memory/2026-06-03.md b/memory/2026-06-03.md index 12cdc5a..83fded4 100644 --- a/memory/2026-06-03.md +++ b/memory/2026-06-03.md @@ -63,7 +63,21 @@ Dev **запаниковал** на старте orch1b: «параллельн - Хук `enduro-deploy-hook.sh` (876b, pull+restart+gps-collector) — rollback'а НЕТ. План: rollback В ХУК (prev-image перед рестартом + флаг `--rollback`), в deployer.md → `ssh ... bash $HOOK --rollback`. - 🟡 **M-5:** architect.md:16 (82.22.50.71), tester.md:27,38,39 (/home/slin/ пути) → env с дефолтами (deployer.md уже параметризован — образец). - ⚠️ ПРОД-ДЕПЛОЙ: Dev НЕ деплоит на прод, бэкап хука перед правкой, проверка = bash -n. Мерж и прод-применение хука — я со Славой. -- Бэклог orchestrator ЗАКРЫТ: ORCH-1/1b/2/4/6/7 + ORCH-5 в main. ORCH-8 отменена. Мелочь M-6/L-1/L-2 — по настроению. +- Бэклог orchestrator ЗАКРЫТ: ORCH-1/1b/2/4/6/7 + ORCH-5 в main. ORCH-8 отменена. + +### Финальная гигиена L-1/L-2/L-3 (Слава: «делай все в т.ч. l-3») — ЗАПУЩЕНО (taskName `orch_cleanup_l1l2l3`, ветка feature/ORCH-cleanup-L1L2L3, ТЗ `DEV_TASK_ORCH_CLEANUP_L1L2L3.md`) +- ⚠️ **Разведка переписала остаток — аудит 02.06 отстал:** + - ❌ **L-4 ОТПАЛ** — мусорной папки `{src` уже нет. + - ❌ **L-5 ОТПАЛ** — `tests/test_launcher.py` УЖЕ есть (18 тестов: запись·verdict·timeout·watchdog SIGTERM→SIGKILL). Долг закрыт по ходу ORCH-1b/2/4. + - ⚠️ **M-6 НЕ мелочь, ОТЛОЖЕНА:** Plane НЕ присылает `sequence_id` в webhook payload (проверила 5 реальных events в БД — ключа нет). Значит «источник правды = Plane sequence» требует ОТДЕЛЬНОГО GET к Plane API по issue_id — сетевой вызов, не правка функции. Рассинхрон теоретический (per-repo инкремент `get_next_work_item_id` работает). Ждёт отдельного решения Славы — нужен ли вообще. + - ✅ **L-1+L-2+L-3 ЗАМЕРЖЕНЫ** (PR #7 `be27f506`, прод пересобран, health/queue ok, **151 passed** = 145+6, 9 pre-existing). L-1 шапка stages.py исправлена, L-2 `prune_run_logs` best-effort в lifespan (keep_days=30/keep_max=500, не трогает active), L-3 эмодзи-константы. +- ✅ **M-6 ВАЖНА** (Слава подтвердил) — берём в работу. Разведка: + - Инфра УЖЕ ЕСТЬ: `plane_sync.py` — полный httpx-клиент (PLANE_BASE/HEADERS/WORKSPACE из config), уже читает `sequence_id` (стр.99,115), `find_issue_id` ищет issue. M-6 НЕ с нуля. + - Payload `work_item.created` содержит `id` (issue UUID) + `project` — по ним GET к Plane API достаёт настоящий `sequence_id`. `sequence_id` В ПАЙЛОАДЕ НЕТ (проверено), но есть через GET по id. + - 🔴 **БОНУС-баг найден:** `find_issue_id` хардкодит `f"ET-{seq:03d}"` (стр.99) — сломано для ORCH-префикса из ORCH-6. M-6 должна убрать хардкод ET-. + - ⚠️ ГОНКА ВЕТОК: M-6 запускать ТОЛЬКО после мержа L1L2L3 из чистого main (иначе 2 Dev дерутся за рабочую копию). L1L2L3 уже смержен → можно. + - 🔄 **M-6 ЗАПУЩЕН** (taskName `orch_m6_plane_sequence`, ветка feature/ORCH-M6-plane-sequence, ТЗ `DEV_TASK_ORCH_M6_PLANE_SEQUENCE.md`). План: helper `fetch_issue_sequence_id` (GET по issue UUID), в handle_work_item_created seq→`{prefix}-{seq:03d}` или fallback DB-инкремент (автономность!), + фикс хардкода `ET-` в find_issue_id. +- ⚠️ **ТОКЕН Gitea (находка Dev 03.06):** `.env` на сервере содержит УСТАРЕВШИЙ `ORCH_GITEA_TOKEN` (28 симв, HTTP 401). РАБОЧИЙ токен (40 симв) — в env контейнера `orchestrator`: `docker exec orchestrator printenv | grep -i gitea`. ⚠️ Мои прошлые мержи (PR #5/6/7) работали — значит я брала токен из .env и он рабочий?? ПРОВЕРИТЬ при след. мерже M-6 — если .env-токен 401, брать из контейнера. (Славе на заметку: стоит синхронизировать .env с рабочим токеном.) - Мерж-рецепт (работает): проверить `git log origin/main..origin/ветка` (не пусто!), мой прогон тестов, clean-merge check, мерж через Gitea API `/pulls/N/merge` `{"Do":"merge"}`, пересборка из main. - ТЗ-образцы: `DEV_TASK_ORCH7_HARDENING.md`, `DEV_TASK_ORCH4_STAGE_ENGINE.md`. - ⚠️ **Грабля memory-файла:** ранний `write` сделал APPEND (задвоил файл), почистила перезаписью. Для точечных правок memory — `edit`, не `write`. diff --git a/tasks/orchestrator/DEV_TASK_ORCH_CLEANUP_L1L2L3.md b/tasks/orchestrator/DEV_TASK_ORCH_CLEANUP_L1L2L3.md new file mode 100644 index 0000000..9f647eb --- /dev/null +++ b/tasks/orchestrator/DEV_TASK_ORCH_CLEANUP_L1L2L3.md @@ -0,0 +1,74 @@ +# DEV TASK — финальная гигиена: L-1 (комментарий) + L-2 (ротация логов) + L-3 (эмодзи-константы) + +**Проект:** orchestrator | **Сервер:** slin@82.22.50.71 | **Репо:** /home/slin/repos/orchestrator | **Контейнер:** orchestrator (8500) +**Ветка:** `feature/ORCH-cleanup-L1L2L3` из свежего main (git checkout main && git pull && git checkout -b feature/ORCH-cleanup-L1L2L3). + +Три мелких независимых пункта, отдельными коммитами. Низкий риск, но аккуратность та же. + +⚠️ **ГРАБЛЯ push (ORCH-7, НЕ повтори):** после push `git log origin/main..origin/feature/ORCH-cleanup-L1L2L3` ДОЛЖЕН показать твои коммиты ДО отчёта «PR готов». + +--- + +## L-1 — поправить врущий комментарий в stages.py (1 строка) +**Где:** `src/stages.py:8` — в docstring/шапке `STAGE_TRANSITIONS`: +``` + - agent: the agent to launch when entering the NEXT stage +``` +**Проблема:** это НЕВЕРНО и именно этот нейминг породил баг ORCH-4 (launcher брал агента не той стадии). Строка 33 уже верная: «agent to launch when advancing FROM this stage». +**Фикс:** привести шапку (стр.8) в соответствие с фактической семантикой: +``` + - agent: the agent to launch when advancing FROM this stage (NOT the next stage's agent) +``` +Никакого кода не менять — только комментарий. STAGE_TRANSITIONS и логику НЕ трогать. +**Коммит:** `docs(stages): fix misleading STAGE_TRANSITIONS comment (L-1)` + +--- + +## L-2 — ротация логов прогонов (реальная гигиена) +**Где:** логи пишутся в `/app/data/runs/{run_id}.log` (`src/agents/launcher.py:143` и др.). Накапливаются без ограничения. +**Что сделать:** +- Добавить функцию очистки старых логов в `runs/` — например `prune_run_logs(runs_dir, keep_days=30, keep_max=500)`: удаляет .log старше keep_days ИЛИ оставляет только keep_max самых свежих (что наступит раньше). Безопасно: только `*.log` в этом каталоге, не трогать другие файлы. +- Вызывать её в подходящий момент: либо при старте приложения (`init`/lifespan), либо после финализации job (`_finalize_job`). Выбери наименее инвазивный — вызов при старте проще и безопаснее. Best-effort: ошибка ротации НЕ должна ронять приложение (обернуть try/except + logger.warning). +- Параметры (keep_days/keep_max) — через env/config с разумными дефолтами (по образцу resilience-ключей в ORCH-1b, если такой паттерн есть; иначе константы с комментарием). +- НЕ удалять лог текущего/активного прогона. +**Тест:** `tests/test_log_rotation.py` — создать tmp-каталог с N фейковыми .log разного возраста (touch с mtime), вызвать prune, проверить что старые/лишние удалены, свежие и не-.log файлы целы. +**Коммит:** `feat(launcher): prune old run logs (L-2)` + +--- + +## L-3 — эмодзи-литералы → константы (косметика) +**Где:** `src/plane_sync.py` — захардкоженные эмодзи в строках: +- стр.197 `f"🔄 Stage: {old_stage} → {new_stage}"` +- стр.235 `f"⚠️ QG failed at {stage}: ..."` +- стр.242 `"✅ Task completed! ..."` +**Что сделать:** вынести эмодзи в именованные константы в начало модуля (или в маленький общий модуль `src/emoji.py` если хочешь переиспользовать), например: +```python +EMOJI_STAGE = "🔄" +EMOJI_QG_FAIL = "⚠️" +EMOJI_DONE = "✅" +``` +и подставить в f-строки. Текст сообщений НЕ менять — байт-в-байт тот же вывод. Это чисто читаемость. +**Тест:** не обязателен (поведение не меняется); если просто — добавь assert что константы непусты. Главное — существующие тесты plane_sync (если есть) зелёные. +**Коммит:** `refactor(plane_sync): extract emoji literals to constants (L-3)` + +--- + +## Ограничения +- 🚫 НЕ трогай: nginx, openclaw.json, .env-секреты, deploy-хук, Plane-webhook is_active, ORCH-1/1b/2/4/5/6/7, stage_engine.py, STAGE_TRANSITIONS (логику), HMAC, очередь. +- Baseline: **145 passed**, 9 pre-existing 401 — НЕ чинить и НЕ ломать. Новые тесты зелёные, старые не падают. +- Conventional Commits, 3 отдельных коммита по пунктам. +- Тесты в контейнере: `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` + +## Проверка (Стрим проверит вживую) +| # | Что | Критерий | +|---|-----|----------| +| 1 | L-1 | stages.py:8 больше не врёт; логика/STAGE_TRANSITIONS не тронуты | +| 2 | L-2 | prune-функция + вызов; ошибка ротации не роняет app; активный лог не трогается; тест зелёный | +| 3 | L-3 | эмодзи в константах; вывод сообщений байт-в-байт прежний | +| 4 | тесты | мой прогон: было 145 → стало 145+новые passed, 9 pre-existing не тронуты | +| 5 | git | PR в main, remote содержит коммиты (грабля ORCH-7) | + +## Отчёт +- НЕ деплоить, НЕ мержить (мерж делает Стрим после живой проверки). +- Запушь ветку (ПРОВЕРЬ remote!), открой PR в main. После каждого пункта — короткий отчёт. +- Если ТЗ расходится с кодом — отчитайся, не выдумывай. Один исполнитель, не паникуй про параллельные сессии. diff --git a/tasks/orchestrator/DEV_TASK_ORCH_M6_PLANE_SEQUENCE.md b/tasks/orchestrator/DEV_TASK_ORCH_M6_PLANE_SEQUENCE.md new file mode 100644 index 0000000..2edeaeb --- /dev/null +++ b/tasks/orchestrator/DEV_TASK_ORCH_M6_PLANE_SEQUENCE.md @@ -0,0 +1,98 @@ +# DEV TASK — M-6: work_item_id из Plane sequence_id (источник правды = Plane) + +**Проект:** orchestrator | **Сервер:** slin@82.22.50.71 | **Репо:** /home/slin/repos/orchestrator | **Контейнер:** orchestrator (8500) +**Ветка:** `feature/ORCH-M6-plane-sequence` из свежего main (git checkout main && git pull && git checkout -b feature/ORCH-M6-plane-sequence). main содержит ORCH-1/1b/2/4/5/6/7 + L1L2L3. + +⚠️ **ГРАБЛЯ push (ORCH-7):** после push `git log origin/main..origin/feature/ORCH-M6-plane-sequence` ДОЛЖЕН показать твои коммиты ДО отчёта «PR готов». + +## Проблема (верифицировано по живому коду + реальным payload в БД) +`db.get_next_work_item_id(repo, prefix)` нумерует `work_item_id` как `max(tasks по repo+prefix)+1` — **независимо от Plane**. Plane ведёт свой `sequence_id`. При любом рассинхроне (ручное создание/удаление issue в Plane, гонка, пересоздание задачи) номер оркестратора (ET-003/ORCH-005) разъедется с тикетом в Plane → путаница. + +**Цель:** источник правды для номера — **Plane `sequence_id`**, маппить детерминированно в `-NNN`. + +### Что уже есть (НЕ строй с нуля) +- `src/plane_sync.py` — рабочий httpx-клиент Plane: `PLANE_BASE`, `PLANE_HEADERS` (X-API-Key), `WORKSPACE` из `settings`. Уже читает `issue.get("sequence_id")` (стр.~99/115) и ходит на `{PLANE_BASE}/workspaces/{WORKSPACE}/projects/{project_id}/issues/...`. +- В `src/webhooks/plane.py:handle_work_item_created` в момент создания task УЖЕ доступны: `plane_id` (= issue UUID, стр.110), `plane_project_id` (стр.124), `proj.work_item_prefix`. Payload `work_item.created` содержит `id` и `project`, но **НЕ содержит `sequence_id`** (проверено на 5 реальных events) — поэтому нужен GET к Plane API по issue id. + +## Что сделать + +### 1. Helper: достать sequence_id из Plane по issue UUID (`src/plane_sync.py`) +Добавить функцию, напр.: +```python +def fetch_issue_sequence_id(issue_id: str, project_id: str) -> int | None: + """GET the Plane issue by UUID and return its sequence_id (the authoritative + per-project number), or None if unavailable (network error / missing field).""" + url = f"{PLANE_BASE}/workspaces/{WORKSPACE}/projects/{project_id}/issues/{issue_id}/" + try: + resp = httpx.get(url, headers=PLANE_HEADERS, timeout=10) + resp.raise_for_status() + seq = resp.json().get("sequence_id") + return int(seq) if seq is not None else None + except Exception as e: + logger.warning(f"fetch_issue_sequence_id failed for {issue_id}: {e}") + return None +``` +(подгони под фактический формат ответа Plane — проверь живым GET'ом, см. ниже). + +### 2. Использовать sequence_id при создании task (`src/webhooks/plane.py:handle_work_item_created`) +Заменить безусловный `work_item_id = get_next_work_item_id(repo, proj.work_item_prefix)` на: +```python +seq = fetch_issue_sequence_id(plane_id, plane_project_id) +if seq is not None: + work_item_id = f"{proj.work_item_prefix}-{seq:03d}" +else: + # Plane недоступен / нет sequence_id → НЕ блокируем pipeline, падаем на DB-инкремент + work_item_id = get_next_work_item_id(repo, proj.work_item_prefix) + logger.warning(f"Plane sequence_id unavailable, fell back to DB increment: {work_item_id}") +``` +⚠️ **Автономность важнее точности:** если Plane не ответил — НЕ ронять создание task, использовать старый fallback. Залогировать что был fallback. + +### 3. Фикс хардкода `ET-` в `find_issue_id` (`src/plane_sync.py`, БОНУС, тот же скоуп) +Стр.~99: `identifier = f"ET-{seq:03d}"` — хардкодит префикс ET, сломано для ORCH-проектов из ORCH-6. +- Убрать хардкод `ET-`. Сравнивать по `sequence_id` напрямую с числом из `work_item_id` (распарсить `-NNN` → NNN), а не собирать строку с фиксированным `ET-`. +- Аналогично fallback-блок стр.~103 (`if work_item_id.startswith("ET-")`) — обобщить на любой префикс (парсить число после последнего `-`). +- Логику поиска не ломать, только убрать ET-привязку. + +## Ограничения +- 🚫 НЕ трогай: nginx, openclaw.json, .env-секреты, deploy-хук, Plane-webhook is_active, ORCH-1/1b/2/4/5/6/7, stage_engine.py, STAGE_TRANSITIONS, HMAC, очередь, `get_next_work_item_id` (оставить как fallback, НЕ удалять). +- Сетевой вызов к Plane — с timeout (есть, 10с) и try/except: ошибка НЕ должна ронять webhook-обработку. +- Baseline: **151 passed**, 9 pre-existing 401 — не чинить, не ломать. +- Conventional Commits: `feat(webhook): derive work_item_id from Plane sequence_id (M-6)`, `fix(plane_sync): drop hardcoded ET- prefix in find_issue_id (M-6)`. + +## Разведка перед кодом (сделай ОБЯЗАТЕЛЬНО) +Проверь живым GET'ом РЕАЛЬНЫЙ формат ответа Plane по одному issue (есть ли `sequence_id` в одиночном GET, как называется поле): +```bash +docker exec orchestrator python3 -c " +import httpx +from src.config import settings +base=f'{settings.plane_api_url}/api/v1'; h={'X-API-Key':settings.plane_api_token}; ws=settings.plane_workspace_slug +# взять любой реальный issue_id из tasks +import sqlite3 +r=sqlite3.connect('/app/data/orchestrator.db').execute(\"SELECT plane_issue_id, repo FROM tasks WHERE plane_issue_id IS NOT NULL ORDER BY id DESC LIMIT 1\").fetchone() +print('task:', r) +" +``` +Если реальных issue в Plane нет (только fake-test-id) — проверь формат по списочному endpoint'у issues проекта. Если `sequence_id` приходит иначе (вложенный, другое имя) — адаптируй helper и **отчитайся что нашёл**, не выдумывай. + +## Тесты +`tests/test_m6_sequence.py`: +- `fetch_issue_sequence_id` возвращает int при валидном ответе (мок httpx). +- возвращает None при сетевой ошибке / отсутствии поля (НЕ кидает). +- `handle_work_item_created`: при доступном seq → `work_item_id == f"{prefix}-{seq:03d}"`; при None → fallback на `get_next_work_item_id` (мок, assert вызван). +- `find_issue_id`: больше НЕ хардкодит `ET-` — работает для произвольного префикса (ORCH-005 матчится по sequence_id=5). +Тесты в контейнере: `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` + +## Проверка (Стрим проверит вживую) +| # | Что | Критерий | +|---|-----|----------| +| 1 | helper | GET по issue UUID → sequence_id; ошибка → None, не кидает | +| 2 | создание task | seq доступен → prefix-NNN из Plane; недоступен → fallback DB, залогирован | +| 3 | автономность | Plane down НЕ роняет webhook (task всё равно создаётся) | +| 4 | хардкод ET- | убран в find_issue_id, работает для ORCH-/любого префикса | +| 5 | тесты | мой прогон: 151 → 151+новые passed, 9 pre-existing не тронуты | +| 6 | git | PR в main, remote содержит коммиты (грабля ORCH-7) | + +## Отчёт +- НЕ деплоить, НЕ мержить (мерж — Стрим после живой проверки). Запушь ветку (ПРОВЕРЬ remote!), открой PR в main. +- В отчёте обязательно: что реально вернул живой GET к Plane (формат sequence_id), какой fallback-путь, результаты прогона. +- После каждого блока — короткий отчёт. Если ТЗ расходится с кодом/Plane — отчитайся, не выдумывай. Один исполнитель, не паникуй про параллельные сессии.