From 0f02a9e351f5495fef57316c794c40280a88fdbb Mon Sep 17 00:00:00 2001 From: Stream Date: Wed, 3 Jun 2026 09:20:01 +0300 Subject: [PATCH] auto-sync: 2026-06-03 09:20:01 --- memory/2026-06-03.md | 6 +- .../DEV_TASK_ORCH5_WEBHOOK_DEDUP.md | 67 +++++++++++++++++++ 2 files changed, 70 insertions(+), 3 deletions(-) create mode 100644 tasks/orchestrator/DEV_TASK_ORCH5_WEBHOOK_DEDUP.md diff --git a/memory/2026-06-03.md b/memory/2026-06-03.md index d6724c6..2918d94 100644 --- a/memory/2026-06-03.md +++ b/memory/2026-06-03.md @@ -44,14 +44,14 @@ Dev **запаниковал** на старте orch1b: «параллельн - 🔴 **БАГ выбора агента (ИСПРАВЛЕН):** launcher брал `get_agent_for_stage(next_stage)` — БЫЛО НЕПРАВИЛЬНО (пропуск стадии: created→analysis запускал *architect* вместо *analyst*). Правильно = `current_stage` (Я ПЕРЕПРОВЕРИЛА логику сама по STAGE_TRANSITIONS — вердикт Dev верный). Унифицировано на current_stage, stages.py не тронут. - 🔴 СОХРАНЕНО ЦЕЛИКОМ: approved-флоу, REQUEST_CHANGES→retry max3, tester FAIL→rollback, architect conflict→rollback в analysis, check_review_approved (PR по ветке + file-fallback). - Слито в `src/stage_engine.py` (425 строк) `advance_stage(...)`. launcher (+18−169), plane (+21−72 через `asyncio.to_thread`) = тонкие обёртки. -5. 🔁 **ORCH-5 (M-7) — СЛЕДУЮЩИЙ:** dedup webhook. ПОДТВЕРЖДЁН: events логируются (INSERT INTO events) но dedup'а по delivery-id НЕТ. Теперь можно запускать (ORCH-4 в main). ✅ валиден. +5. 🔄 **ORCH-5 (M-7) ЗАПУЩЕН** (taskName `orch5_webhook_dedup`, ветка feature/ORCH-5-webhook-dedup, ТЗ `DEV_TASK_ORCH5_WEBHOOK_DEDUP.md`). Разведка подтвердила: dedup ОТСУТСТВУЕТ полностью (grep: delivery-id нигде не читается). events имеет `id,timestamp,source,event_type,payload,processed` — нет delivery_id, нет UNIQUE. План: +колонка events.delivery_id (через _ensure_column) + partial UNIQUE index, helper delivery_id (gitea=X-Gitea-Delivery, fallback sha256(body); plane=fallback sha256("plane"+body)), INSERT OR IGNORE + ранний выход на duplicate без enqueue. dedup ПОСЛЕ HMAC, для plane до ORCH-6 фильтра (не сломать ignored). Baseline 136 passed. - Помельче (потом): M-6 (work_item_id из Plane sequence), L-1/L-2 (нейминг/логи), M-5 (хардкод инфры в промптах — enduro-trails, с ORCH-3). - ⚠️ **УРОК:** аудит устаревает — ВСЕГДА верифицировать по живому коду перед запуском Dev (S-1b уже была сделана, чуть не запустила дубль). ### ⏭️ ТОЧКА ВХОДА (06:20 UTC) - **ORCH-7 закрыт. ORCH-4 ЗАМЕРЖЕН** (PR #5 `2f0fd246`, прод пересобран, health/queue ok, 136 passed). Баг выбора агента починен. -- **СЛЕДУЮЩИЙ: ORCH-5 (M-7 dedup webhook).** Перед ЗАПУСКОМ: свериться с ОК Славы (он сегодня много видел). Разведка: как Gitea/Plane передают delivery-id (X-Gitea-Delivery / X-Plane-Delivery?), где dedup'ить (до запуска конвейера, UNIQUE по delivery_id в events?). -- Потом **ORCH-3+M-5** (репо enduro-trails: rollback в хук + чистка deployer.md). +- **ORCH-5 (M-7 dedup webhook) ЗАПУЩЕН** (Dev, Opus 4.8). После завершения → проверка ВЖИВУЮ: `git log origin/main..origin/feature/ORCH-5-webhook-dedup`, мой прогон тестов, фокус: HMAC цел (401 на невалидной подписи) + ORCH-6 ignored на первой доставке не сломан + миграция на живой БД применилась (delivery_id в events). Мерж — я. +- Потом **ORCH-3+M-5** (репо enduro-trails: rollback в хук + чистка deployer.md) — последнее по бэклогу. - Мерж-рецепт (работает): проверить `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_ORCH5_WEBHOOK_DEDUP.md b/tasks/orchestrator/DEV_TASK_ORCH5_WEBHOOK_DEDUP.md new file mode 100644 index 0000000..0eaa30b --- /dev/null +++ b/tasks/orchestrator/DEV_TASK_ORCH5_WEBHOOK_DEDUP.md @@ -0,0 +1,67 @@ +# DEV TASK — ORCH-5: идемпотентность webhook (M-7) + +**Проект:** orchestrator | **Сервер:** slin@82.22.50.71 | **Репо:** /home/slin/repos/orchestrator | **Контейнер:** orchestrator (8500) +**Ветка:** `feature/ORCH-5-webhook-dedup` из свежего main (main содержит ORCH-1/1b/2/4/6/7). + +⚠️ **ГРАБЛЯ push (ORCH-7, НЕ повтори):** после коммитов `git log origin/main..HEAD` (твои коммиты есть), после push `git log origin/main..origin/feature/ORCH-5-webhook-dedup` ДОЛЖЕН показать твои коммиты. НЕ отчитывайся «PR готов» пока remote-ветка реально не содержит коммитов. + +## Проблема (M-7) +Webhook-хендлеры (`src/webhooks/gitea.py`, `src/webhooks/plane.py`) при каждом запросе делают `INSERT INTO events` и затем диспетчеризуют → `enqueue_job(...)`. **Dedup'а НЕТ** — delivery-id нигде не читается (проверено grep'ом: нигде). Повторная доставка одного и того же вебхука (ретрай Gitea/Plane, сетевой реброд, ручной replay) → повторный enqueue → **дубль запуска конвейера**. Это тот же класс бага, что инцидент ET-009 (параллельные конвейеры на одном репо). + +**Цель:** сделать обработку webhook идемпотентной — один delivery обрабатывается ровно один раз. Повторный с тем же delivery-id → залогировать как duplicate и вернуть `{"status":"duplicate"}` БЕЗ диспетчеризации/enqueue. + +## Текущее состояние (верифицировано по живому коду) +- `gitea_webhook` (gitea.py:42): проверяет HMAC `X-Gitea-Signature` → INSERT events → диспетч (push/pr/status). +- `plane_webhook` (plane.py:52): HMAC `X-Plane-Signature` → INSERT events → ORCH-6 project-filter → диспетч. +- Таблица `events` (db.py:14): `id, timestamp, source, event_type, payload, processed` — **нет delivery-id, нет UNIQUE**. +- Gitea шлёт заголовок **`X-Gitea-Delivery`** (GUID на доставку). Plane свой delivery-заголовок шлёт НЕнадёжно. + +## Что сделать +1. **Миграция БД (паттерн `_ensure_column`, db.py:74):** + - Добавить колонку `events.delivery_id TEXT` (idempotent через `_ensure_column`). + - Создать UNIQUE-индекс: `CREATE UNIQUE INDEX IF NOT EXISTS idx_events_delivery ON events(delivery_id) WHERE delivery_id IS NOT NULL`. + (partial unique — старые строки с NULL delivery_id не конфликтуют). +2. **Вычисление delivery-id (helper, напр. в `src/webhooks/_dedup.py` или в db.py):** + - Gitea: `request.headers.get("X-Gitea-Delivery")`. Если пусто → fallback `sha256(source + event_type + body)`. + - Plane: попробовать `X-Plane-Delivery`/`X-Hook-Delivery` если есть; иначе **fallback `sha256("plane" + body)`** (стабильный hash тела — повторная доставка идентичного тела даст тот же id). Префиксуй source чтобы gitea/plane не пересекались: итоговый `delivery_id = f"{source}:{raw_or_hash}"`. +3. **Идемпотентный INSERT + ранний выход:** + - Заменить текущий `INSERT INTO events (source,event_type,payload)` на INSERT с `delivery_id` и `INSERT OR IGNORE` (или ловить `sqlite3.IntegrityError`). + - Если строка НЕ вставилась (delivery уже есть) → `logger.info("webhook duplicate delivery_id=...")`, **вернуть `{"status":"duplicate"}` и НЕ вызывать диспетч/enqueue.** + - Если вставилась (новый delivery) → продолжить как сейчас (диспетч). + - ⚠️ Порядок: dedup-проверка ПОСЛЕ HMAC-верификации (невалидную подпись по-прежнему 401), для plane — реши: dedup до или после ORCH-6 project-filter. Рекомендация: dedup СРАЗУ после HMAC и INSERT, ДО project-filter (чтобы повторная доставка не делала лишнюю работу). Но НЕ сломай ORCH-6 поведение `{"status":"ignored"}` для unknown project на ПЕРВОЙ доставке. +4. **Не трогать** саму диспетч-логику, enqueue, ORCH-6 фильтр, stage_engine, очередь. + +## Ограничения +- 🚫 НЕ трогай: nginx, openclaw.json, .env-секреты, deploy-хук, Plane-webhook is_active, ORCH-1/1b/2/4/6/7, stage_engine.py, очередь, HMAC-проверку (только добавляешь dedup ПОСЛЕ неё). +- ⚠️ Миграция должна быть restart-safe и не падать на уже существующей БД с данными (как `_ensure_column` для jobs). +- ⚠️ 9 pre-existing webhook-тестов (401) — это baseline, НЕ чинить и НЕ ломать дополнительно. +- Conventional Commits: `feat(webhook): dedup deliveries by delivery_id (M-7)`, `feat(db): add events.delivery_id + unique index`, `test(webhook): ...`. + +## Тесты (в контейнере) +- Новый `tests/test_webhook_dedup.py`: + - первый webhook с delivery-id X → обрабатывается (status accepted), enqueue вызван. + - повторный webhook с тем же delivery-id X → `{"status":"duplicate"}`, enqueue НЕ вызван (мок enqueue_job, assert call_count не вырос). + - два РАЗНЫХ delivery-id → оба обработаны. + - gitea без X-Gitea-Delivery → fallback hash работает (повтор того же тела = duplicate). + - plane fallback hash аналогично. + - миграция на «старой» БД без колонки delivery_id не падает. +- Прогон: `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` +- Baseline: сейчас **136 passed**, 9 pre-existing 401 — не трогать. Новые зелёные, старые не сломать. + +## Acceptance (проверит Стрим вживую) +| # | Что | Критерий | +|---|-----|----------| +| 1 | миграция | `events.delivery_id` + partial UNIQUE index, restart-safe, не падает на проде-БД | +| 2 | gitea dedup | повтор `X-Gitea-Delivery` → duplicate, без enqueue | +| 3 | plane dedup | повтор тела → duplicate (fallback hash) | +| 4 | HMAC цел | невалидная подпись по-прежнему 401 | +| 5 | ORCH-6 цел | unknown project на первой доставке → ignored (не сломано) | +| 6 | тесты | новые зелёные, baseline 136 не сломан | +| 7 | прод | пересобран из ветки, health ok, /queue ok, миграция применилась на живой БД | + +## Деплой + отчёт +- `docker compose up -d --build && sleep 6 && curl -s :8500/health && curl -s :8500/queue` +- Проверь что миграция применилась: `docker exec orchestrator python3 -c "import sqlite3;print([r[1] for r in sqlite3.connect('/app/data/orchestrator.db').execute('PRAGMA table_info(events)')])"` — должна быть `delivery_id`. +- После каждого блока — короткий отчёт + результат. +- Запушь ветку (ПРОВЕРЬ remote!), открой PR в main. **НЕ мержи** — мерж делает Стрим после проверки. +- Других исполнителей на репо нет — только ты, не паникуй про параллельные сессии.