analyst(ET): auto-commit from analyst run_id=428
This commit is contained in:
69
docs/work-items/ORCH-087/01-brd.md
Normal file
69
docs/work-items/ORCH-087/01-brd.md
Normal file
@@ -0,0 +1,69 @@
|
||||
# BRD — ORCH-087
|
||||
|
||||
**Тип:** Багфикс (UX live-трекера) + малая фича (эффорт в карточке) + корректность метрики времени
|
||||
**Приоритет:** MEDIUM
|
||||
**Зона:** `src/notifications.py` (`update_task_tracker` bump-режим, `render_task_tracker`), `src/db.py` (учёт message_id / колонка effort), `src/agents/launcher.py` + `src/usage.py` (стамп эффорта)
|
||||
**Связь:** ORCH-067 (формат карточки/ссылки/статусы), ORCH-042 (режим bump), ORCH-52h/ORCH-081 (эффорт реально работает), ORCH-086 (свежий reconciler — см. G6)
|
||||
|
||||
---
|
||||
|
||||
## 1. Контекст и проблема
|
||||
|
||||
Каждая задача имеет ОДНУ live-карточку в Telegram (`update_task_tracker`, инвариант «одна карточка на задачу»). Дефолтный режим — `bump` (ORCH-067): на каждом обновлении старая карточка удаляется и новая шлётся вниз чата, указатель `tasks.tracker_message_id` перепонтуется на свежий `message_id`.
|
||||
|
||||
**Скриншот Славы (08.06, задача ORCH-082):**
|
||||
1. В чате висит карточка с заголовком `📍 To Analyse`, хотя конвейер прошёл весь путь и все стадии ✅ вплоть до «Внедрение».
|
||||
2. Статусы деплоя не отражены (нет `⏸️ Awaiting Deploy / Confirm Deploy`), хотя задача реально на стадии `deploy`.
|
||||
|
||||
**Диагноз код-аудита (08.06):** сам рендер `render_task_tracker` исправен (на стадии `deploy` корректно даёт заголовок и весь deploy-цикл). Карточка со скриншота — **ОСИРОТЕВШАЯ** старая (`msg 18204`), застрявшая на первом рендере (`To Analyse` = `_DEFAULT_STATUS_LABEL`). `bump` её не удалил: `delete_telegram(mid)` — best-effort и НЕ блокирует `send` (BR-6); указатель `tracker_message_id` хранит ТОЛЬКО последний `mid`, поэтому удаляется только он. При рассинхроне указателя часть карточек осиротевает и висит «замёрзшей» на старом статусе. Проверено: бот МОЖЕТ удалять (`deleteMessage → ok:true` и для 18204, и для 18227) — дело не в правах, а в **потере ссылки на старые `message_id`**.
|
||||
|
||||
**Расширение (09.06) — G5:** итоговое время в карточке (`⏱️ Всего … · агенты … · твоё …`) считается неверно — раздувается на простое/застое (пример ORCH-087: «Подтверждение BRD 392м» при реальном отсутствии обдумывания).
|
||||
|
||||
**Расширение — эффорт в карточке:** строка стадии показывает модель (`opus-4-8`), но НЕ эффорт. После ORCH-52h эффорт реально работает (developer=`xhigh`, прочие `high`/`medium`) — его надо показать.
|
||||
|
||||
## 2. Цель
|
||||
|
||||
Обеспечить, чтобы в чате жила РОВНО ОДНА актуальная карточка задачи с корректным текущим статусом (включая весь deploy-цикл), без осиротевших «замёрзших» карточек; показать эффорт каждой стадии рядом с моделью; считать итоговое время честно и сходимо. Перед разработкой G0-исследование фиксирует ТОЧНУЮ механику рассинхрона и даёт обоснованную (data-backed) рекомендацию `bump` vs `edit` → ADR.
|
||||
|
||||
## 3. Бизнес-требования
|
||||
|
||||
| ID | Требование |
|
||||
|----|-----------|
|
||||
| **BR-G0** | **Сначала расследование, не фикс вслепую.** Установить точную механику bump-режима, не принимая на веру workaround-диагноз. Ответить на вопросы расследования (см. §4). Воспроизвести на staging. Вывод → ADR (`06-adr/`), и только ПОТОМ фикс. |
|
||||
| **BR-G1** | Не оставлять осиротевших карточек: при bump гарантировать удаление ВСЕХ ранее созданных карточек задачи (хранить полный учёт `message_id`, а не только последний), либо иной механизм, доказательно исключающий сирот. |
|
||||
| **BR-G2** | Заголовок живой карточки отражает ТЕКУЩУЮ стадию на каждой карточке — не застывает на `To Analyse`. |
|
||||
| **BR-G3** | Статусы деплоя (`Awaiting Deploy` / `Deploying` / `Confirm Deploy` / `Monitoring` / `Done`) видимы на карточке на соответствующих стадиях (offline-label + live-overlay покрывают весь deploy-цикл). |
|
||||
| **BR-EFF** | Строка каждой стадии карточки показывает уровень эффорта рядом с моделью (формат `… · opus-4-8 · xhigh` или `opus-4-8/xhigh`). developer-строка → `xhigh`; механические (tester/deployer) → `medium`. |
|
||||
| **BR-G5** | Итоговое время разделить честно: (1) чистое рабочее время агентов (Σ `agent_runs`) — главная метрика; (2) человеческое время BRD-approve — ТОЛЬКО фактическое, без аномального застоя/рассинхрона; (3) wall-clock — если показываем, помечать как «общее (с ожиданием)», не выдавать за рабочее. Итог должен СХОДИТЬСЯ. |
|
||||
| **BR-G6** | Ветка ORCH-087 должна разрабатываться/мержиться поверх свежего `origin/main` (где уже ORCH-86). Использовать свежий `notifications/reconciler` из 86. Явно проверить на merge-gate (пересечение `reconciler.py` — не append-only, `.gitattributes union` не спасёт). |
|
||||
|
||||
## 4. Вопросы G0-расследования (обязательны к ответу в ADR)
|
||||
|
||||
1. **Сколько РЕАЛЬНО карточек одной задачи висело** в чате к моменту бага (собрать `message_id` из логов/Telegram) — сирот могло быть >1.
|
||||
2. **В какие МОМЕНТЫ `tracker_message_id` рассинхронизируется** с реальными сообщениями:
|
||||
- (a) `send` вернул `None` (нет креды / transient) → `mid` не перезаписан;
|
||||
- (b) рестарт орка между `delete` и `send`;
|
||||
- (c) пересоздание карточки во время CLI-фикса / ручных операций;
|
||||
- (d) гонка двух `update_task_tracker` подряд (быстрые стадии);
|
||||
- (e) `delete` упал (rate-limit / >48ч), но `send` прошёл.
|
||||
3. **Почему ИМЕННО заголовок застывает на `To Analyse`:** это старый рендер (до смены stage) или баг плана-лейбла? Воспроизвести на staging: прогнать задачу, на каждой стадии зафиксировать что РЕАЛЬНО в Telegram (заголовок+тело) vs что в БД (`stage`).
|
||||
4. **`bump` vs `edit`:** какой режим реально надёжнее против сирот — замерить, а не предполагать. `edit` правит ОДНО сообщение in-place (нет сирот, но не держит карточку внизу); `bump` держит внизу (фича-просьба ORCH-042), но плодит сирот при рассинхроне. Дать обоснованную рекомендацию с данными.
|
||||
|
||||
## 5. Не-цели
|
||||
|
||||
- Не плодить дубликаты — инвариант «одна карточка на задачу» сохранить.
|
||||
- Не пинговать — `disable_notification` остаётся (карточка тихая).
|
||||
- Не ломать ссылки ORCH-067 (`plane_issue_link`, кликабельный номер) и `disable_web_page_preview` (ORCH-080).
|
||||
- Не вводить новую стадию конвейера / не менять `STAGE_TRANSITIONS` / `QG_CHECKS`.
|
||||
- Не предрешать `bump` vs `edit` в BRD — это вывод G0/ADR.
|
||||
|
||||
## 6. Ограничения и грабли
|
||||
|
||||
- Telegram не даёт удалять сообщения **старше 48ч** — для совсем старых сирот зачистка может не сработать. Документировать как ограничение (`delete_telegram` уже классифицирует это как «gone»/не-transient).
|
||||
- Эффорт **не возвращается** Claude CLI в result-JSON (в отличие от модели, которая берётся из `modelUsage`). Поэтому надёжный источник эффорта — стамп резолва (`resolve_agent_effort`) В МОМЕНТ запуска, а не пересчёт постфактум.
|
||||
- Контракт всего компонента нотификаций — **never raises**; карточка всегда silent.
|
||||
- Self-hosting: задача правит инструмент, работающий в проде и обслуживающий enduro-trails. НЕ ронять прод-контейнер; обязательная страховка — `deploy-staging` (8501).
|
||||
|
||||
## 7. Бизнес-ценность
|
||||
|
||||
Наблюдатель (Слава) видит ровно одну достоверную карточку: текущий статус, эффорт каждой стадии и честное время. Уходит класс багов «замёрзшая сирота вводит в заблуждение» и «магическое раздутое итоговое время».
|
||||
117
docs/work-items/ORCH-087/02-trz.md
Normal file
117
docs/work-items/ORCH-087/02-trz.md
Normal file
@@ -0,0 +1,117 @@
|
||||
# ТЗ — ORCH-087
|
||||
|
||||
Техническое задание для архитектора/разработчика. Конкретные изменения кода/БД с привязкой к BR (см. `01-brd.md`). Архитектурные РЕШЕНИЯ (выбор механизма зачистки сирот, выбор `bump`/`edit`, формула отсечки аномалий времени) принимает архитектор в ADR на основе G0 — здесь зафиксированы требования и точки врезки.
|
||||
|
||||
---
|
||||
|
||||
## 0. Задействованные модули `src/`
|
||||
|
||||
| Модуль | Роль в задаче |
|
||||
|--------|---------------|
|
||||
| `src/notifications.py` | `update_task_tracker` (bump/edit), `render_task_tracker`, `_stage_line`, итоговая строка времени, `plane_status_label`/`_card_status_label` (заголовок/deploy-цикл) |
|
||||
| `src/db.py` | учёт `message_id` карточек задачи (BR-G1); колонка `agent_runs.effort` (BR-EFF); геттеры/сеттеры |
|
||||
| `src/agents/launcher.py` | `_spawn`: стамп `resolve_agent_effort(agent)` в `agent_runs.effort` в момент запуска (BR-EFF) |
|
||||
| `src/usage.py` | `short_model_name` (рядом — рендер эффорта); при необходимости — пробрасывать effort в строку стадии |
|
||||
| `tests/test_notifications*.py`, `tests/test_*tracker*` | unit-покрытие |
|
||||
|
||||
**НЕ трогать** (BR-G6): `src/reconciler.py` / `tests/test_reconciler.py` — задача не требует их правок; пересечение с ORCH-86 неприемлемо. Если правка всё же понадобится — сохранить ORCH-086 (`skipped_terminal_total`, `state_uuid`-dedup, terminal-skip F-1) и явно проверить на merge-gate.
|
||||
|
||||
## 1. G0 — расследование (BR-G0) → ADR
|
||||
|
||||
- Исследование выполняется ДО кода: собрать факты по §4 BRD (логи орка `data/runs`, Telegram message_id, БД `tracker_message_id`/`stage` по ORCH-082), воспроизвести прогон на staging (8501), зафиксировать таблицу «стадия → (заголовок+тело в Telegram) vs (stage в БД)».
|
||||
- Артефакт расследования и обоснованная рекомендация `bump` vs `edit` → `06-adr/ADR-NNN-tracker-orphan-cleanup.md`.
|
||||
- Код фикса (G1–G3) реализует выбранный в ADR механизм. ТЗ ниже задаёт ИНВАРИАНТЫ, которым любой выбранный механизм обязан удовлетворять.
|
||||
|
||||
## 2. G1 — гарантированная зачистка сирот (BR-G1)
|
||||
|
||||
**Требование-инвариант:** после любого `update_task_tracker` в чате не остаётся НИ ОДНОЙ ранее созданной карточки этой задачи, кроме текущей (в пределах 48ч-лимита Telegram).
|
||||
|
||||
Точка проблемы (текущий код, `update_task_tracker`, ветка `mode == "bump"`):
|
||||
```python
|
||||
if mid is not None:
|
||||
delete_telegram(mid) # удаляется ТОЛЬКО последний mid
|
||||
new_mid = send_telegram(text, disable_notification=True)
|
||||
if new_mid is not None:
|
||||
set_tracker_message_id(task_id, new_mid)
|
||||
```
|
||||
`tasks.tracker_message_id` — скаляр (последний `mid`). При рассинхроне (send→None / рестарт между delete и send / пересоздание / гонка / delete-fail+send-ok) прежние карточки теряют ссылку и осиротевают.
|
||||
|
||||
**Требования к решению (любой механизм из ADR):**
|
||||
- R-1. Система должна знать обо ВСЕХ незакрытых `message_id` карточек задачи (а не только о последнем), чтобы подчищать их при следующем bump / на рассинхроне / при старте.
|
||||
- R-2. Перед/в момент создания новой карточки удаляются ВСЕ известные незакрытые `message_id`; успешно удалённые (включая «already gone» по `_DELETE_GONE_MARKERS`) исключаются из учёта; не удалённые transient — остаются в учёте для повторной попытки.
|
||||
- R-3. Новый `message_id` записывается в учёт ТОЛЬКО при успешном `send` (`new_mid is not None`) — transient send не должен обнулять/терять учёт (сохранить текущую защиту BR-6).
|
||||
- R-4. Инвариант «одна карточка на задачу» и «не более одного `send` за вызов» сохраняются → дубликатов внутри вызова нет.
|
||||
- R-5. **Кандидатные механизмы для ADR** (выбор за архитектором, не предрешать в коде до ADR):
|
||||
- (A) bump + полный учёт `message_id` (новая таблица `tracker_messages(task_id, message_id, created_at, deleted_at)` ИЛИ JSON-массив в колонке `tasks.tracker_message_ids`), зачистка всех незакрытых;
|
||||
- (B) переход дефолта на `edit` (нет сирот by design; теряется «карточка внизу» ORCH-042) — взвесить против фича-просьбы.
|
||||
- R-6. Изменение схемы БД (если выбран вариант A) — строго аддитивное (`CREATE TABLE IF NOT EXISTS` / `_ensure_column`), идемпотентное, restart-safe на живой общей прод-БД (данные enduro не трогаются). Детали данных — `08-data-requirements.md`.
|
||||
|
||||
## 3. G2 — заголовок отражает текущую стадию (BR-G2)
|
||||
|
||||
- Рендер `render_task_tracker` уже строит заголовок/статус-строку из `tasks.stage` (`plane_status_label` → `_card_status_label`). Замёрзший `To Analyse` — следствие осиротевшей карточки (G1), а не бага рендера.
|
||||
- Требование: после фикса G1 единственная живая карточка всегда несёт заголовок текущей стадии. Регресс-тест: на каждой стадии заголовок/статус-строка соответствуют `stage` в БД (часть staging-воспроизведения G0 + unit на `plane_status_label`).
|
||||
|
||||
## 4. G3 — deploy-цикл на карточке (BR-G3)
|
||||
|
||||
- Проверить, что `_STAGE_STATUS_LABEL["deploy"]` (`⏸️ Awaiting Deploy — ожидание Confirm Deploy`) + live-overlay `_live_plane_branch_override` (`deploying`, `monitoring`) покрывают весь цикл `Awaiting Deploy → Deploying → Confirm Deploy → Monitoring → Done`.
|
||||
- Если какой-то под-статус не отображается на соответствующей стадии — добить offline-label/overlay. `Done` рендерится из `stage == "done"`. Контракт never-raise и kill-switch `tracker_live_status` сохраняются.
|
||||
|
||||
## 5. BR-EFF — эффорт в строке стадии
|
||||
|
||||
**API/данные:**
|
||||
- Новая колонка `agent_runs.effort TEXT` (миграция `_ensure_column(conn, "agent_runs", "effort", "TEXT")` в `src/db.py`, рядом с `model`).
|
||||
- **Стамп в момент запуска** (`launcher._spawn`): сразу после резолва `effort = resolve_agent_effort(agent, project_id)` записать его в строку `agent_runs` (тот же `run_id`). Источник — РЕАЛЬНО ушедшее в `--effort` значение (`""`/без флага → сохранить пусто/`NULL`). Это надёжнее пересчёта (CLI не возвращает эффорт в result-JSON).
|
||||
- Допустимо: расширить `INSERT INTO agent_runs (task_id, agent, effort) VALUES (?,?,?)` или отдельным `UPDATE agent_runs SET effort=? WHERE id=?` после резолва. Выбор — архитектор; значение должно соответствовать фактическому флагу запуска.
|
||||
|
||||
**Рендер** (`render_task_tracker._stage_line`):
|
||||
- Текущий суффикс: `f" · {model}"` при наличии модели.
|
||||
- Добавить эффорт рядом: формат `· opus-4-8 · xhigh` ИЛИ компактно `· opus-4-8/xhigh` (на усмотрение, выбрать единый). При пустом эффорте — суффикс эффорта опускается (как опускается модель при пустой `short_model_name`).
|
||||
- Брать `effort` из строки `agent_runs` соответствующей стадии (последний завершённый run, как `model`). Допустим fallback на `resolve_agent_effort(agent)` для исторических строк без колонки.
|
||||
|
||||
**Ожидаемо:** developer-строка → `xhigh`; tester/deployer → `medium`; analyst/architect/reviewer → `high` (по таблице ORCH-41/081).
|
||||
|
||||
## 6. BR-G5 — честное и сходимое итоговое время
|
||||
|
||||
Текущая итоговая строка (`done`):
|
||||
```python
|
||||
wall = _duration_seconds(created_at, updated_at) # раздут: вся очередь+ожидание+застой
|
||||
review_seconds = _duration_seconds(brd_review_started, brd_review_ended) # раздут при застое
|
||||
"⏱️ Всего {wall} · агенты {agent_seconds} · твоё {review}"
|
||||
```
|
||||
Проблема: `wall ≠ agent_seconds + review_seconds` (незалогированные queue-паузы) → итог визуально «врёт»; `review_seconds` засчитывает застой/рассинхрон (ORCH-087: 392м).
|
||||
|
||||
**Требования (формула — за архитектором, G5 «КАК — архитектору»):**
|
||||
- T-1. Чистое рабочее время агентов = `Σ _duration_seconds(started, finished)` по `agent_runs` (текущий `agent_seconds`) — **главная метрика**, оставить точной.
|
||||
- T-2. Человеческое BRD-время — ТОЛЬКО фактическое: НЕ включать аномальный застой/рассинхрон (`brd_review` болтался открытым из-за рассинхрона In Review→Backlog). Ограничить разумным порогом ИЛИ считать только активные окна. Аномалия не должна показываться как «твоё время».
|
||||
- T-3. Wall-clock — если показываем, помечать как «общее (с ожиданием)», НЕ выдавать за рабочее время.
|
||||
- T-4. Итог должен СХОДИТЬСЯ: либо `wall = Σ(стадии) + Σ(паузы с подписью)`, либо не показывать wall как сумму. Прозрачность вместо «магического» числа.
|
||||
- T-5. `agent_runs`-агрегация (`total_in/total_out/total_cost/agent_seconds`) и `💰`-строка — без регресса.
|
||||
|
||||
## 7. Изменения API (endpoints)
|
||||
|
||||
Нет новых/изменённых HTTP-endpoint. (Опционально — отразить учёт карточек/effort в read-only снимке `GET /queue`, если архитектор сочтёт нужным; не обязательно.)
|
||||
|
||||
## 8. Изменения схемы БД
|
||||
|
||||
- `agent_runs.effort TEXT` — аддитивно, идемпотентно (`_ensure_column`). **Обязательно.**
|
||||
- Учёт `message_id` (BR-G1, если выбран вариант A) — аддитивная таблица `tracker_messages` ИЛИ колонка-массив `tasks.tracker_message_ids`. **Зависит от ADR.** Подробности — `08-data-requirements.md`.
|
||||
- Существующие колонки/таблицы (`tasks.tracker_message_id`, `brd_review_*`, `agent_runs.model`) — не ломать; при варианте A сохранить обратную совместимость со скалярным `tracker_message_id` (миграция/со-существование).
|
||||
|
||||
## 9. Требования к новым QG-проверкам
|
||||
|
||||
Нет. `STAGE_TRANSITIONS`, реестр `QG_CHECKS`, машинные вердикты гейтов — без изменений.
|
||||
|
||||
## 10. Артефакты pipeline, создаваемые/обновляемые
|
||||
|
||||
- `06-adr/ADR-NNN-tracker-orphan-cleanup.md` (G0 вывод + рекомендация bump/edit + механизм G1 + формула G5) — архитектор.
|
||||
- Обновить `CLAUDE.md` (§ Нотификации) и `docs/architecture/README.md` (компонент Notifications) — отразить учёт карточек, эффорт-в-строке, честное время. **Golden source наравне с кодом.**
|
||||
- `CHANGELOG.md` — `## [Unreleased]` запись (под `.gitattributes merge=union`).
|
||||
|
||||
## 11. Инварианты (не нарушать)
|
||||
|
||||
- never-raise во всём пути нотификаций; карточка всегда silent (`disable_notification`).
|
||||
- «одна карточка на задачу»; ≤1 `send` за вызов `update_task_tracker`.
|
||||
- Ссылки ORCH-067 (`plane_issue_link`), `disable_web_page_preview` ORCH-080 — сохранены.
|
||||
- `STAGE_TRANSITIONS` / `QG_CHECKS` / стадии конвейера — без изменений.
|
||||
- БР-G6: разработка/merge поверх свежего `origin/main` (ORCH-86); `reconciler.py` не эродировать.
|
||||
- Миграции БД аддитивны и идемпотентны (общая прод-БД, enduro не трогать).
|
||||
71
docs/work-items/ORCH-087/03-acceptance-criteria.md
Normal file
71
docs/work-items/ORCH-087/03-acceptance-criteria.md
Normal file
@@ -0,0 +1,71 @@
|
||||
# Критерии приёмки — ORCH-087
|
||||
|
||||
Каждый критерий — чёткое условие PASS/FAIL. Привязка к BR (`01-brd.md`) и ТЗ (`02-trz.md`).
|
||||
|
||||
---
|
||||
|
||||
## G0 — расследование
|
||||
|
||||
| ID | Критерий | PASS | FAIL |
|
||||
|----|----------|------|------|
|
||||
| AC-0.1 | ADR `06-adr/ADR-NNN-tracker-orphan-cleanup.md` существует и отвечает на ВСЕ 4 вопроса §4 BRD (число реальных сирот; точки рассинхрона a–e; причина застывания `To Analyse`; bump vs edit с данными). | ADR содержит ответы по всем 4 пунктам + явную рекомендацию | Любой вопрос без ответа / рекомендация без обоснования |
|
||||
| AC-0.2 | В ADR зафиксировано staging-воспроизведение: таблица «стадия → (заголовок+тело в Telegram) vs (stage в БД)» по прогону задачи на 8501. | Таблица воспроизведения приложена | Воспроизведения нет / только предположения |
|
||||
| AC-0.3 | Фикс (G1–G3) реализует механизм, выбранный и обоснованный в ADR (не противоречит выводу). | Код соответствует ADR | Код расходится с ADR без объяснения |
|
||||
|
||||
## G1 — нет осиротевших карточек
|
||||
|
||||
| ID | Критерий | PASS | FAIL |
|
||||
|----|----------|------|------|
|
||||
| AC-1.1 (=AC-1) | После прохождения стадий в чате НЕ остаётся карточек с устаревшим заголовком (нет `To Analyse` на завершённой задаче). | На staging-прогоне в чате только одна карточка, заголовок актуальный | Видна ≥1 замёрзшая/устаревшая карточка |
|
||||
| AC-1.2 | Система ведёт учёт ВСЕХ незакрытых `message_id` задачи (не только последнего); при bump удаляются ВСЕ известные незакрытые. | Учёт присутствует, unit-тест на мульти-mid зачистку зелёный | Учёт только скаляр / сироты остаются |
|
||||
| AC-1.3 (=AC-3) | При сбое `send` (`new_mid=None`) / рестарте орка / гонке указатель не теряет старые карточки — они подчищаются (или остаются в учёте до следующей попытки). | Unit моделирует send→None / повторный вызов: прежние mid не потеряны | mid теряется → сирота |
|
||||
| AC-1.4 | Telegram-лимит 48ч на удаление задокументирован как known-limitation (старые сироты могут не удалиться). | Ограничение в ADR/доке | Не упомянуто |
|
||||
|
||||
## G2 — актуальный заголовок
|
||||
|
||||
| ID | Критерий | PASS | FAIL |
|
||||
|----|----------|------|------|
|
||||
| AC-2.1 (=AC-2) | Единственная актуальная карточка показывает текущий статус, включая весь deploy-цикл. | На каждой стадии заголовок/статус соответствует `stage` в БД | Расхождение заголовка и `stage` |
|
||||
| AC-2.2 | `plane_status_label(stage)` детерминированно даёт корректный лейбл для всех стадий `created…done` (unit). | Unit перебирает все стадии, лейблы верны | Любой stage даёт неверный/`To Analyse` по умолчанию некорректно |
|
||||
|
||||
## G3 — deploy-цикл виден
|
||||
|
||||
| ID | Критерий | PASS | FAIL |
|
||||
|----|----------|------|------|
|
||||
| AC-3.1 | Стадия `deploy` показывает `⏸️ Awaiting Deploy` (offline). | Unit/staging подтверждает | Не показывает |
|
||||
| AC-3.2 | Live-overlay покрывает `Deploying` / `Monitoring` (когда Plane-статус реально такой). | Overlay рисует ветку при наличии UUID статуса | Ветка не рисуется при живом статусе |
|
||||
| AC-3.3 | `Done` рендерится по `stage == "done"` (`ГОТОВО` + итог). | Карточка done корректна | — |
|
||||
|
||||
## BR-EFF — эффорт в карточке
|
||||
|
||||
| ID | Критерий | PASS | FAIL |
|
||||
|----|----------|------|------|
|
||||
| AC-E.1 | Колонка `agent_runs.effort` создаётся идемпотентно; стамп фактического эффорта происходит в момент запуска агента. | Миграция + стамп есть, unit подтверждает запись | Колонки нет / эффорт не стампится |
|
||||
| AC-E.2 | Строка каждой завершённой стадии карточки показывает эффорт рядом с моделью (выбранный формат `· model · effort` или `· model/effort`). | Рендер содержит эффорт, unit зелёный | Эффорт отсутствует в строке |
|
||||
| AC-E.3 | developer-строка показывает `xhigh`; tester/deployer — `medium`; analyst/architect/reviewer — `high`. | Значения соответствуют ORCH-41/081 | Значения не совпадают |
|
||||
| AC-E.4 | Пустой/неизвестный эффорт → суффикс эффорта опускается, рендер не падает. | Unit на пустой effort зелёный | Падение/мусорный суффикс |
|
||||
|
||||
## BR-G5 — честное время
|
||||
|
||||
| ID | Критерий | PASS | FAIL |
|
||||
|----|----------|------|------|
|
||||
| AC-5.1 | На задаче с искусственным застоем (открытый `brd_review` ~6ч) итоговое «твоё время» НЕ показывает ~6ч. | Unit с brd-окном 6ч → «твоё время» ограничено/активное, не 6ч | Показывает ~6ч |
|
||||
| AC-5.2 | agent-время = `Σ agent_runs` точно (без регресса). | Unit сверяет сумму | Расхождение |
|
||||
| AC-5.3 | Числа в итоговой строке сходятся: wall помечен как «общее (с ожиданием)» ИЛИ wall = Σ(стадии)+Σ(паузы с подписью). | Итог прозрачен и согласован | wall выдаётся за рабочее/не сходится |
|
||||
|
||||
## BR-G6 — свежий main / без эрозии reconciler
|
||||
|
||||
| ID | Критерий | PASS | FAIL |
|
||||
|----|----------|------|------|
|
||||
| AC-6.1 | Ветка разработана/смержена поверх `origin/main`, содержащего ORCH-86 (`merge-base` = merge-коммит 86 или новее). | `git merge-base --is-ancestor origin/main HEAD` → true; маркеры ORCH-086 в `src/reconciler.py` ветки присутствуют | Ветка отстаёт / маркеры 86 потеряны |
|
||||
| AC-6.2 | `src/reconciler.py` / `tests/test_reconciler.py` не эродированы (ORCH-086 terminal-skip + `state_uuid`-dedup на месте). Проверено на merge-gate. | Диф не удаляет ORCH-086 логику; merge-gate зелёный | Логика 86 затёрта |
|
||||
|
||||
## Сквозные
|
||||
|
||||
| ID | Критерий | PASS | FAIL |
|
||||
|----|----------|------|------|
|
||||
| AC-X.1 (=AC-4) | Инвариант «одна карточка на задачу» соблюдён; дубликатов нет; ≤1 `send` за вызов. | Unit/staging: одна карточка | Дубликаты |
|
||||
| AC-X.2 (=AC-5 задачи) | `pytest tests/ -q` зелёный; весь путь нотификаций never-raise (любая ошибка Telegram/БД не валит конвейер). | Тесты зелёные; unit на исключения не поднимает | Падение/raise |
|
||||
| AC-X.3 | Документация обновлена в ТОМ ЖЕ PR: `CLAUDE.md` (§Нотификации), `docs/architecture/README.md` (Notifications), `CHANGELOG.md`. | Доки обновлены | Reviewer → REQUEST_CHANGES |
|
||||
| AC-X.4 | Ссылки ORCH-067 (`plane_issue_link`) и `disable_web_page_preview` (ORCH-080) сохранены. | Кликабельный номер + нет link-preview | Регресс |
|
||||
| AC-X.5 | `STAGE_TRANSITIONS` / `QG_CHECKS` без изменений; миграции БД аддитивны/идемпотентны (enduro-данные не тронуты). | Диф не меняет машину стадий; миграции безопасны | Изменение машины стадий / небезопасная миграция |
|
||||
115
docs/work-items/ORCH-087/04-test-plan.yaml
Normal file
115
docs/work-items/ORCH-087/04-test-plan.yaml
Normal file
@@ -0,0 +1,115 @@
|
||||
work_item: ORCH-087
|
||||
description: >
|
||||
Тест-план для багфикса live-трекера (сироты/заголовок/deploy-цикл),
|
||||
эффорта-в-карточке, честного итогового времени. Юнит-тесты — pytest,
|
||||
изоляция Telegram через monkeypatch (send/edit/delete не ходят в сеть).
|
||||
Интеграция/воспроизведение — на staging (8501). Контракт never-raise
|
||||
проверяется отдельными negative-тестами.
|
||||
|
||||
tests:
|
||||
# ---------------- G1: зачистка сирот ----------------
|
||||
- id: TC-01
|
||||
type: unit
|
||||
description: "bump удаляет ВСЕ известные незакрытые message_id задачи, не только последний (мок delete/send)"
|
||||
module: tests/test_notifications_orphans.py
|
||||
expected: PASS
|
||||
- id: TC-02
|
||||
type: unit
|
||||
description: "send вернул None (нет креды/transient) → учёт прежних message_id не теряется, mid не обнуляется (BR-6 + R-3)"
|
||||
module: tests/test_notifications_orphans.py
|
||||
expected: PASS
|
||||
- id: TC-03
|
||||
type: unit
|
||||
description: "delete вернул False (transient, >48ч) → message_id остаётся в учёте для повторной попытки; 'already gone' (_DELETE_GONE_MARKERS) → исключается из учёта"
|
||||
module: tests/test_notifications_orphans.py
|
||||
expected: PASS
|
||||
- id: TC-04
|
||||
type: unit
|
||||
description: "повторные вызовы update_task_tracker подряд (быстрые стадии/гонка) → ровно одна живая карточка, ≤1 send за вызов, без дублей (AC-X.1)"
|
||||
module: tests/test_notifications_orphans.py
|
||||
expected: PASS
|
||||
- id: TC-05
|
||||
type: unit
|
||||
description: "учёт message_id переживает 'рестарт' (читается из БД) → старые карточки подчищаются при следующем bump (AC-1.3)"
|
||||
module: tests/test_notifications_orphans.py
|
||||
expected: PASS
|
||||
|
||||
# ---------------- G2/G3: заголовок и deploy-цикл ----------------
|
||||
- id: TC-06
|
||||
type: unit
|
||||
description: "plane_status_label детерминированно даёт корректный лейбл для всех stage created..done; deploy → 'Awaiting Deploy' (AC-2.2, AC-3.1)"
|
||||
module: tests/test_notifications.py
|
||||
expected: PASS
|
||||
- id: TC-07
|
||||
type: unit
|
||||
description: "render_task_tracker: заголовок/статус-строка соответствуют tasks.stage на каждой стадии (нет застывшего To Analyse) (AC-2.1)"
|
||||
module: tests/test_notifications.py
|
||||
expected: PASS
|
||||
- id: TC-08
|
||||
type: unit
|
||||
description: "live-overlay рисует Deploying/Monitoring при наличии соответствующего Plane-UUID; деградирует на offline-label при ошибке/выкл. kill-switch (AC-3.2)"
|
||||
module: tests/test_notifications.py
|
||||
expected: PASS
|
||||
|
||||
# ---------------- BR-EFF: эффорт в карточке ----------------
|
||||
- id: TC-09
|
||||
type: unit
|
||||
description: "миграция agent_runs.effort идемпотентна (_ensure_column дважды — без ошибки) (AC-E.1)"
|
||||
module: tests/test_db.py
|
||||
expected: PASS
|
||||
- id: TC-10
|
||||
type: unit
|
||||
description: "launcher стампит resolve_agent_effort(agent) в agent_runs.effort в момент запуска; значение = фактический --effort (AC-E.1)"
|
||||
module: tests/test_launcher.py
|
||||
expected: PASS
|
||||
- id: TC-11
|
||||
type: unit
|
||||
description: "строка стадии рендерит эффорт рядом с моделью в выбранном формате; developer=xhigh, tester/deployer=medium, прочие=high (AC-E.2, AC-E.3)"
|
||||
module: tests/test_notifications.py
|
||||
expected: PASS
|
||||
- id: TC-12
|
||||
type: unit
|
||||
description: "пустой/неизвестный effort → суффикс эффорта опускается, рендер не падает (AC-E.4)"
|
||||
module: tests/test_notifications.py
|
||||
expected: PASS
|
||||
|
||||
# ---------------- BR-G5: честное время ----------------
|
||||
- id: TC-13
|
||||
type: unit
|
||||
description: "brd_review-окно ~6ч (искусственный застой) → итоговое 'твоё время' НЕ показывает ~6ч (отсечка/активные окна) (AC-5.1)"
|
||||
module: tests/test_notifications.py
|
||||
expected: PASS
|
||||
- id: TC-14
|
||||
type: unit
|
||||
description: "agent-время = Σ _duration_seconds(agent_runs) точно; 💰-итоги без регресса (AC-5.2)"
|
||||
module: tests/test_notifications.py
|
||||
expected: PASS
|
||||
- id: TC-15
|
||||
type: unit
|
||||
description: "итоговая строка done: wall помечен как 'общее (с ожиданием)' ИЛИ wall сходится с Σ(стадии)+Σ(паузы); числа согласованы (AC-5.3)"
|
||||
module: tests/test_notifications.py
|
||||
expected: PASS
|
||||
|
||||
# ---------------- never-raise / сквозные ----------------
|
||||
- id: TC-16
|
||||
type: unit
|
||||
description: "update_task_tracker / render_task_tracker никогда не поднимают исключение при ошибке Telegram/БД (моки бросают) (AC-X.2)"
|
||||
module: tests/test_notifications.py
|
||||
expected: PASS
|
||||
- id: TC-17
|
||||
type: unit
|
||||
description: "ссылки ORCH-067 (plane_issue_link кликабельный номер) и disable_web_page_preview (ORCH-080) сохранены в payload (AC-X.4)"
|
||||
module: tests/test_notifications.py
|
||||
expected: PASS
|
||||
|
||||
# ---------------- интеграция / воспроизведение ----------------
|
||||
- id: TC-18
|
||||
type: integration
|
||||
description: "staging-прогон задачи (8501): на каждой стадии зафиксировать (заголовок+тело в Telegram) vs (stage в БД); в чате остаётся одна актуальная карточка без сирот (G0 воспроизведение, AC-0.2, AC-1.1)"
|
||||
module: docs/work-items/ORCH-087/06-adr # фиксируется в ADR как таблица воспроизведения
|
||||
expected: PASS
|
||||
- id: TC-19
|
||||
type: integration
|
||||
description: "merge-gate: ветка поверх origin/main с ORCH-86; reconciler.py не эродирован (маркеры ORCH-086 на месте), pytest tests/ -q зелёный (AC-6.1, AC-6.2, AC-X.2)"
|
||||
module: tests/
|
||||
expected: PASS
|
||||
Reference in New Issue
Block a user