auto-sync: 2026-06-03 09:20:01
This commit is contained in:
@@ -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`.
|
||||
|
||||
67
tasks/orchestrator/DEV_TASK_ORCH5_WEBHOOK_DEDUP.md
Normal file
67
tasks/orchestrator/DEV_TASK_ORCH5_WEBHOOK_DEDUP.md
Normal file
@@ -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. **НЕ мержи** — мерж делает Стрим после проверки.
|
||||
- Других исполнителей на репо нет — только ты, не паникуй про параллельные сессии.
|
||||
Reference in New Issue
Block a user