diff --git a/docs/work-items/ORCH-087/01-brd.md b/docs/work-items/ORCH-087/01-brd.md new file mode 100644 index 0000000..9521179 --- /dev/null +++ b/docs/work-items/ORCH-087/01-brd.md @@ -0,0 +1,113 @@ +# 01-BRD — ORCH-087 + +**Заголовок:** Трекер-карточка застревает на старом статусе («To Analyse») + осиротевшие карточки при `bump`; + эффорт в строке стадии. + +**Тип:** Багфикс (UX live-трекера) + малое расширение карточки. +**Приоритет:** MEDIUM. +**Зона:** `src/notifications.py` (`update_task_tracker` bump-режим, `render_task_tracker`), `src/db.py` (хранение message_id'ов задачи + колонка `agent_runs.effort`), `src/agents/launcher.py` (стамп фактического эффорта). +**Связь:** ORCH-042 (введён bump), ORCH-067 (формат карточки, статус-строка, ссылки), ORCH-052h/ORCH-081 (эффорт реально работает), ORCH-080 (link-preview). + +--- + +## 1. Контекст и проблема + +Live-трекер ведёт **одну карточку на задачу** (`update_task_tracker`). Дефолтный режим — `bump` +(ORCH-067): на каждом обновлении `delete_telegram(old_mid)` (best-effort, НЕ блокирует send) → +`send_telegram(new)` (тихо, вниз чата) → `set_tracker_message_id(new_mid)` — но указатель +`tasks.tracker_message_id` хранит **только последний** message_id. + +**Симптом (Слава, скриншот 08.06, задача ORCH-082):** +1. В Telegram висит карточка с заголовком «📍 To Analyse», хотя конвейер прошёл весь путь и все + стадии ✅ вплоть до «Внедрение». +2. Статусы деплоя не отражены (нет строки «Awaiting Deploy / Confirm Deploy»), хотя задача реально + на стадии `deploy`. + +**Код-аудит 08.06 (предварительная гипотеза, подлежит ПОДТВЕРЖДЕНИЮ — см. G0):** +`render_task_tracker` СЕЙЧАС рендерит корректно (заголовок текущей стадии, deploy виден). Значит +карточка со скриншота — **осиротевшая** старая (msg 18204), застрявшая на первом рендере +(`_DEFAULT_STATUS_LABEL = "To Analyse"`). `bump` не удалил её: при рассинхроне `tracker_message_id` +(сбой send → `new_mid=None` → указатель не перезаписан; рестарт орка между delete и send; +пересоздание во время ручного фикса/CLI) часть карточек осиротевает и навсегда замирает на старом +статусе. Проверено: бот МОЖЕТ удалять (`deleteMessage → ok:true` для 18204 и 18227) — дело не в +правах, а в потере ссылки на старые message_id. + +Это **диагноз-кандидат**, его нельзя принимать на веру (см. требование G0 ниже — расследование +должно установить точную механику, потому что предыдущие диагнозы уже путали причины). + +## 2. Цели (бизнес-уровень) + +- **G0 (расследование ПЕРВЫМ, до фикса):** установить ТОЧНУЮ механику бага по данным, не вслепую. +- **G1:** Не оставлять осиротевших карточек: при `bump` старая карточка удаляется ВСЕГДА, либо + хранится список ВСЕХ созданных message_id задачи и незакрытые подчищаются. +- **G2:** Заголовок живой карточки отражает ТЕКУЩУЮ стадию (не застывает на «To Analyse»). +- **G3:** Статусы деплой-цикла (Awaiting Deploy / Confirm Deploy / Deploying / Monitoring / Done) + видны на карточке на соответствующих стадиях. +- **G4 (расширение):** В строку каждой стадии карточки добавить уровень эффорта рядом с моделью. + +## 3. G0 — обязательное расследование (вход в ADR) + +Вывод G0 оформляется архитектором в `06-adr/` ДО фикса. Расследование обязано ответить на: + +- **R-1. Сколько РЕАЛЬНО карточек одной задачи висело в чате** к моменту бага — собрать `message_id` + из логов/Telegram (сирот могло быть >1, а не ровно 2). +- **R-2. В какие МОМЕНТЫ `tracker_message_id` рассинхронизируется** с реальными сообщениями: + (a) `send` вернул `None` (нет креды/transient) → mid не перезаписан; + (b) рестарт орка между `delete` и `send`; + (c) пересоздание карточки во время CLI-фикса/ручных операций; + (d) гонка двух `update_task_tracker` подряд (быстрые стадии); + (e) `delete` упал (rate-limit/48ч), но `send` прошёл. + По каждому пункту — подтвердить/опровергнуть как реальный источник сирот. +- **R-3. Почему ИМЕННО заголовок застывает на «To Analyse»:** это старый рендер (до смены stage) или + баг плана-лейбла? Воспроизвести на staging: прогнать задачу, на КАЖДОЙ стадии зафиксировать + факт в Telegram (заголовок + тело) против БД (`tasks.stage`). +- **R-4. `bump` vs `edit` — что реально надёжнее против сирот.** Замерить, не предполагать. + `edit` правит ОДНО сообщение in-place (нет сирот, но карточка не уезжает вниз чата); + `bump` держит карточку внизу (фича-просьба ORCH-042), но плодит сирот при рассинхроне. Дать + обоснованную рекомендацию С ДАННЫМИ (оставить `bump` с гарантированной зачисткой ИЛИ перейти на + `edit`). + +## 4. G4 — эффорт в строке стадии (расширение) + +Сейчас строка стадии показывает модель (`opus-4-8`), но не эффорт. После ORCH-052h/ORCH-081 эффорт +реально применяется (developer=`xhigh`, tester/deployer=`medium`, прочие=`high`). Нужно показывать +фактически применённый уровень рядом с моделью. + +- **Формат** (компактно, на усмотрение архитектора): `… · opus-4-8 · xhigh` ИЛИ `… · opus-4-8/xhigh`. + Пример полной строки: `✅ Разработка 7м · 4.9M↓/32k↑ · $3.97 · opus-4-8 · xhigh`. +- **Источник эффорта (предпочтительно):** сохранять ФАКТИЧЕСКИ применённый эффорт в `agent_runs` + (то, что реально ушло в CLI), а не пересчитывать `resolve_agent_effort(agent)` на рендере — + новая колонка `agent_runs.effort` (как уже есть `agent_runs.model`). Это надёжнее: не зависит от + изменения конфига между запуском и рендером. +- developer-строка показывает `xhigh`; механические (tester/deployer) — `medium`. +- Архитектор вправе вынести G4 отдельной мелкой задачей; **по умолчанию — в этом же PR** (одна зона: + `notifications.py` + `agent_runs`). + +## 5. Не-цели + +- Не плодить дубликаты — инвариант «одна карточка на задачу» сохранить. +- Не пинговать — `disable_notification` остаётся (карточка тихая). +- Не ломать ссылки ORCH-067 (`plane_issue_link`) и подавление link-preview ORCH-080. +- Не менять `STAGE_TRANSITIONS`, `QG_CHECKS`, статусную модель ORCH-066. +- Не трогать поведение для не-self проектов сверх необходимого (общая БД/очередь — enduro-trails). + +## 6. Ограничения / грабли + +- **Telegram 48ч:** сообщения старше 48ч удалить нельзя (`deleteMessage` вернёт «message can't be + deleted»). Для совсем старых сирот зачистка может не сработать — документировать как ограничение + (поведение `delete_telegram`: такой исход трактуется как «уже не наша проблема», `_DELETE_GONE_MARKERS`). +- **Rate-limit / 429** на массовом удалении при накопившихся сиротах. +- **Общая БД/очередь** с enduro-trails — любое изменение схемы строго аддитивно и идемпотентно + (`_ensure_column`), нулевая регрессия для других проектов. +- **never-raise:** компонент нотификаций никогда не валит конвейер (CLAUDE.md, ORCH-067). + +## 7. Заинтересованные лица +- **Owner / наблюдатель (Слава)** — видит карточку, инициатор бага. +- **Self-hosting прод** — карточки всех проектов из одного инстанса. + +## 8. Критерии успеха (бизнес) +См. `03-acceptance-criteria.md`. Кратко: завершённая задача не оставляет карточек с устаревшим +заголовком; единственная актуальная карточка показывает текущий статус включая deploy-цикл; сироты +не возникают/подчищаются при сбое send/рестарте; эффорт виден в каждой строке стадии; `pytest` +зелёный, never-raise. + + diff --git a/docs/work-items/ORCH-087/02-trz.md b/docs/work-items/ORCH-087/02-trz.md new file mode 100644 index 0000000..2e74cf5 --- /dev/null +++ b/docs/work-items/ORCH-087/02-trz.md @@ -0,0 +1,130 @@ +# 02-ТЗ — ORCH-087 + +Техническое задание. Конкретные изменения кода/БД. Архитектурное решение (включая выбор +`bump`-с-зачисткой vs `edit`, схему хранения message_id'ов, вывод G0) принимает архитектор и +фиксирует в `06-adr/`. Ниже — границы и обязательные требования к реализации. + +> ⚠️ Порядок работ жёсткий: **G0 (расследование → ADR) ПЕРВЫМ**, фикс G1–G3 и расширение G4 — только +> после. Фикс вслепую без подтверждённой механики запрещён (см. BRD §3). + +## 0. Задействованные модули `src/` +- `src/notifications.py` — `update_task_tracker` (ветка `mode == "bump"`), `render_task_tracker` + (`_stage_line` — строка стадии), низкоуровневые `send_telegram`/`delete_telegram`. +- `src/db.py` — хранение message_id'ов задачи (G1) + миграция `agent_runs.effort` (G4), + геттеры/сеттеры. +- `src/agents/launcher.py` — `_spawn`: стамп фактически применённого `effort` в `agent_runs` (G4). +- `src/config.py` — при необходимости новый kill-switch (например `tracker_orphan_sweep_enabled`). +- `tests/` — новые/расширенные тесты (см. `04-test-plan.yaml`). + +## 1. G0 — расследование (артефакт: `06-adr/`) +Не код. Архитектор по данным логов/Telegram/staging-прогона отвечает на R-1…R-4 (BRD §3) и выносит +решение: **остаётся `bump` с гарантированной зачисткой ИЛИ переход на `edit` по умолчанию**. Всё +последующее ТЗ ниже описывает требования, инвариантные к этому выбору, и явно помечает пункты, +зависящие от него. + +## 2. G1 — не оставлять сирот (anti-orphan) +**Требование:** при `bump`-обновлении в чате не должно оставаться более одной живой карточки задачи; +старые message_id, не подтверждённые удалёнными, должны подчищаться. + +Допустимые подходы (архитектор выбирает один; рекомендация — Б как наиболее устойчивый): + +- **Подход А (только указатель + строгий swap):** `set_tracker_message_id(new_mid)` ТОЛЬКО при + успешном `delete` старого ИЛИ при подтверждённом «уже нет» — но это не покрывает рестарт между + delete и send. Недостаточно сам по себе. +- **Подход Б (реестр всех message_id — рекомендуется):** хранить ВСЕ созданные message_id задачи и + их состояние. Перед/после успешного `send` нового — подчистить все ранее не удалённые. Реализация: + - Новая аддитивная таблица, например + `tracker_messages(task_id INTEGER, message_id INTEGER, created_at TEXT, deleted INTEGER DEFAULT 0, + PRIMARY KEY(task_id, message_id))` через `CREATE TABLE IF NOT EXISTS` (идемпотентно, не трогает + `tasks`/`agent_runs`/enduro-данные). + - При `send` нового — записать его в реестр; при удачном `delete` старого — пометить `deleted=1`. + - **Sweep незакрытых:** перед отправкой свежей карточки (или фоновым проходом) пытаться удалить все + `deleted=0` записи задачи, кроме актуального `tracker_message_id`; успешный/«уже-нет» delete → + `deleted=1`. 48ч-«can't be deleted» (`_DELETE_GONE_MARKERS`) → тоже пометить `deleted=1` (наша + проблема исчерпана) + лог. + - `set_tracker_message_id` обновляется ТОЛЬКО при `new_mid is not None` (как сейчас — не обнулять + указатель при transient send-failure). +- **Подход В (переход на `edit`):** сменить дефолт `tracker_mode` на `edit` (правка одного сообщения + in-place) — структурно исключает сирот, но теряет «карточка внизу чата» (фича ORCH-042). Допустим + ТОЛЬКО если G0 докажет, что выгода против сирот перевешивает (решение — ADR). + +**Инвариант (любой подход):** `update_task_tracker` остаётся `never-raise`; ни один сетевой сбой +delete/send не валит конвейер; карточка всегда `disable_notification=True`. + +## 3. G2 — заголовок отражает текущую стадию +- При живой (не осиротевшей) карточке заголовок/статус-строка строятся `render_task_tracker` → + `_card_status_label` → `plane_status_label(task_row)` из `tasks.stage` на момент рендера. Эта часть + уже корректна (код-аудит). Реальная причина «застывания» — сирота (G1). Требование G2 удовлетворяется + устранением сирот (G1): единственная живая карточка по определению отрисована на текущей стадии. +- Доп. требование: убедиться (тест R-3), что при ПЕРЕрисовке (bump) свежая карточка всегда несёт + актуальный `plane_status_label` — регрессионный тест «stage меняется → статус-строка меняется». + +## 4. G3 — статусы deploy-цикла видимы +- Offline-ядро уже даёт для `stage="deploy"` → «⏸️ Awaiting Deploy — ожидание Confirm Deploy» + (`_STAGE_STATUS_LABEL`), для `done` → «Done». +- Ветки `Deploying` / `Monitoring after Deploy` / `Confirm Deploy` рисуются live-overlay + (`_live_plane_branch_override`, kill-switch `tracker_live_status`). Требование: проверить тестами, + что весь deploy-цикл покрыт (offline-label для `deploy`/`done`; overlay-маппинг для + `deploying`/`monitoring`). Если обнаружится дыра покрытия — закрыть в `_STAGE_STATUS_LABEL` / + `_LIVE_BRANCH_LABELS` без изменения статусной модели ORCH-066. +- Изменения `STAGE_TRANSITIONS` / `plane_sync` ключей НЕ требуются. + +## 5. G4 — эффорт в строке стадии +### 5.1 Схема БД (`src/db.py`) +- Добавить колонку `agent_runs.effort TEXT` идемпотентной миграцией: + `_ensure_column(conn, "agent_runs", "effort", "TEXT")` (рядом с `..."model"...`). NULL для старых + строк — рендер деградирует (см. 5.3). + +### 5.2 Стамп фактического эффорта (`src/agents/launcher._spawn`) +- В `_spawn` эффорт уже резолвится: `effort = resolve_agent_effort(agent, project_id)` (стр. ~475). + Записать ФАКТИЧЕСКИ применённое значение в `agent_runs` строкой run'а. Варианты: + - расширить начальный `INSERT INTO agent_runs (task_id, agent)` → `(task_id, agent, effort)` с + `effort` (то, что ушло в `--effort`; пустая строка `""` при omit → хранить NULL или `""`); ИЛИ + - отдельный `UPDATE agent_runs SET effort=? WHERE id=?` сразу после получения `run_id`. +- Источник — РОВНО та строка `effort`, что формирует `effort_flag` (фактически в CLI), НЕ повторный + `resolve_agent_effort` на рендере. Если `effort==""` (флаг опущен) — хранить пустое/NULL. +- Никогда не валить `_spawn` из-за записи эффорта (контракт launcher). + +### 5.3 Рендер (`src/notifications.render_task_tracker._stage_line`) +- В `SELECT ... FROM agent_runs` (стр. ~322) добавить `effort` в выборку. +- В `_stage_line` после `model` добавить эффорт. Формат — компактный, рядом с моделью; рекомендуемый: + `… · {model} · {effort}` при наличии обоих; при пустом `effort` — НЕ добавлять суффикс (как уже + делает `model_suffix` для пустой модели). Точный разделитель (`·` vs `/`) — на усмотрение + архитектора, но единообразно. +- Деградация: `effort is None/""` → строка как сейчас (без эффорт-суффикса), без падения. + +## 6. Изменения API +Нет внешних HTTP API. Внутренние сигнатуры: +- Возможен новый helper в `db.py` для реестра message_id (G1, подход Б): например + `record_tracker_message(task_id, message_id)`, `mark_tracker_message_deleted(task_id, message_id)`, + `list_live_tracker_messages(task_id) -> list[int]`. Сигнатуры — на усмотрение архитектора. +- `get_tracker_message_id` / `set_tracker_message_id` — сохраняются (обратная совместимость). + +## 7. Изменения схемы БД (сводно, всё аддитивно/идемпотентно) +- `agent_runs.effort TEXT` (G4) — `_ensure_column`. +- (Подход Б) `tracker_messages(...)` — `CREATE TABLE IF NOT EXISTS`. +- Никаких изменений существующих колонок; нулевая регрессия для enduro-trails в общей БД. + +## 8. Требования к новым QG checks +Нет. Изменения только в слое нотификаций/БД; `QG_CHECKS`, гейты, машина стадий не трогаются. + +## 9. Конфигурация (`src/config.py`) +- При подходе Б — опциональный kill-switch `tracker_orphan_sweep_enabled: bool = True` (env + `ORCH_TRACKER_ORPHAN_SWEEP_ENABLED`) для безопасного раската/отката зачистки. +- При подходе В — менять дефолт `tracker_mode` (сейчас `"bump"`). Решение — ADR. + +## 10. Артефакты pipeline (обновить в ТОМ ЖЕ PR) +- `CLAUDE.md` — секция «Нотификации / Telegram live-tracker»: описать anti-orphan-зачистку и + эффорт-в-строке-стадии. +- `docs/architecture/README.md` — компонент «Notifications / Live-tracker»: то же. +- `CHANGELOG.md` — `## [Unreleased]` (union-merge, ORCH-073). +- `06-adr/ADR-NNN-*.md` — вывод G0 + принятое решение (bump-зачистка vs edit) + схема хранения. +- При необходимости `08-data-requirements.md` (схема `tracker_messages` / `agent_runs.effort`). + +## 11. Инварианты (не нарушать) +- never-raise во всём слое нотификаций; конвейер не блокируется сетевыми сбоями Telegram. +- «Одна карточка на задачу» — после устранения сирот строго одна живая карточка. +- Карточка тихая (`disable_notification=True`); пингуют только alert-хелперы. +- `disable_web_page_preview=True` (ORCH-080) и кликабельный `plane_issue_link` (ORCH-067) сохранены. +- Миграции идемпотентны и безопасны на живой общей прод-БД. + diff --git a/docs/work-items/ORCH-087/03-acceptance-criteria.md b/docs/work-items/ORCH-087/03-acceptance-criteria.md new file mode 100644 index 0000000..9423d9a --- /dev/null +++ b/docs/work-items/ORCH-087/03-acceptance-criteria.md @@ -0,0 +1,93 @@ +# 03-Критерии приёмки — ORCH-087 + +Каждый критерий имеет явное условие PASS/FAIL. Проверяется тестами (`04-test-plan.yaml`) и/или +ручной верификацией на staging. + +## G0 — расследование + +### AC-0.1 — механика бага установлена по данным +- **PASS:** в `06-adr/` зафиксированы ответы на R-1…R-4 (BRD §3): реальное число висевших карточек + (с message_id), подтверждённые/опровергнутые источники рассинхрона (a–e), точная причина застывания + заголовка (старый рендер vs баг лейбла) с воспроизведением на staging, и обоснованная рекомендация + `bump`-с-зачисткой vs `edit` С ДАННЫМИ. +- **FAIL:** фикс начат без ADR; вывод опирается на предположения, а не на собранные message_id / + staging-прогон. + +### AC-0.2 — фикс следует за расследованием +- **PASS:** изменения кода G1–G4 соответствуют решению ADR (выбранный режим/схема хранения). +- **FAIL:** реализация противоречит выводу G0 или сделана до него. + +## G1–G3 — сироты, заголовок, deploy-цикл + +### AC-1 — нет устаревшего заголовка на завершённой задаче +- **PASS:** после прохождения всех стадий в чате НЕ остаётся карточек с заголовком/статус-строкой + «To Analyse» (или иным устаревшим). Тест: прогон полного жизненного цикла → единственная карточка + показывает финальный статус. +- **FAIL:** в чате остаётся хотя бы одна карточка с устаревшим статусом. + +### AC-2 — единственная актуальная карточка показывает текущий статус, включая deploy-цикл +- **PASS:** для `stage="deploy"` статус-строка = «⏸️ Awaiting Deploy — ожидание Confirm Deploy»; + для `done` = «Done»; ветки `Deploying` / `Confirm Deploy` / `Monitoring after Deploy` отображаются + через live-overlay при соответствующем Plane-статусе. Весь deploy-цикл покрыт. +- **FAIL:** на стадии deploy/done карточка показывает не-deploy статус, или ветка deploy-цикла не + покрыта рендером. + +### AC-3 — нет сирот при сбое send / рестарте орка +- **PASS:** при `send`, вернувшем `None` (нет креды/transient), указатель `tracker_message_id` не + обнуляется; ранее созданные карточки либо удалены, либо учтены в реестре и подчищаются при следующем + обновлении / sweep. После симуляции «рестарт между delete и send» сироты не накапливаются. +- **FAIL:** transient send обнуляет указатель; осиротевшие карточки накапливаются и не подчищаются. + +### AC-4 — инвариант «одна карточка на задачу» +- **PASS:** в любой момент после успешного обновления у задачи живёт строго одна карточка; дубликаты в + пределах одного `update_task_tracker` не создаются (≤1 send за вызов). +- **FAIL:** наблюдается >1 живой карточки или дублирующий send в одном вызове. + +### AC-5 — never-raise и тихая карточка +- **PASS:** любой сбой Telegram (delete/send/edit: network/timeout/5xx/429/48ч) не поднимает + исключение из `update_task_tracker` и не блокирует конвейер; карточка отправляется с + `disable_notification=True`; `disable_web_page_preview=True`; `plane_issue_link` кликабелен. +- **FAIL:** исключение пробивается наружу, карточка пингует, link-preview разворачивается, или ссылка + сломана. + +### AC-6 — ограничение 48ч документировано +- **PASS:** в коде/ADR/CLAUDE.md зафиксировано: сироты старше 48ч удалить нельзя (Telegram); такой + delete трактуется как «уже не наша проблема» (`_DELETE_GONE_MARKERS`) и помечается обработанным без + падения. +- **FAIL:** ограничение не документировано или 48ч-ошибка ломает зачистку. + +## G4 — эффорт в строке стадии + +### AC-7 — эффорт виден в каждой строке стадии +- **PASS:** строка завершённой стадии показывает уровень эффорта рядом с моделью, формат компактный + (например `… · opus-4-8 · xhigh` или `… · opus-4-8/xhigh`). +- **FAIL:** эффорт не отображается, либо ломает формат/выравнивание существующей строки. + +### AC-8 — корректные уровни по ролям +- **PASS:** developer-строка показывает `xhigh`; механические (tester/deployer) — `medium`; прочие + (analyst/architect/reviewer) — `high` (при дефолтном конфиге). Значение берётся из фактически + применённого эффорта (`agent_runs.effort`), а не пересчётом на рендере. +- **FAIL:** уровни не соответствуют фактически применённым / берутся пересчётом и расходятся с CLI. + +### AC-9 — деградация при отсутствии эффорта +- **PASS:** для старых `agent_runs` строк (`effort` NULL/пусто) рендер опускает эффорт-суффикс без + падения (как для пустой модели). +- **FAIL:** NULL-эффорт ломает рендер или печатает мусор (`None`). + +### AC-10 — миграция безопасна и аддитивна +- **PASS:** `agent_runs.effort` (и при подходе Б — `tracker_messages`) добавляются идемпотентно + (`_ensure_column` / `CREATE TABLE IF NOT EXISTS`); повторный `init_db` — no-op; существующие колонки + и данные enduro-trails не затронуты. +- **FAIL:** миграция не идемпотентна, меняет существующие колонки или ломает общую прод-БД. + +## Сквозное + +### AC-11 — документация обновлена в том же PR +- **PASS:** обновлены `CLAUDE.md` (секция трекера), `docs/architecture/README.md` (компонент + Notifications), `CHANGELOG.md` (`## [Unreleased]`), создан `06-adr/ADR-NNN-*.md`. +- **FAIL:** функционал изменён, документация — нет (reviewer → REQUEST_CHANGES). + +### AC-12 — pytest зелёный +- **PASS:** `pytest tests/ -q` зелёный; новые тесты из `04-test-plan.yaml` присутствуют и проходят. +- **FAIL:** красные/отсутствующие тесты. + diff --git a/docs/work-items/ORCH-087/04-test-plan.yaml b/docs/work-items/ORCH-087/04-test-plan.yaml new file mode 100644 index 0000000..a30b33d --- /dev/null +++ b/docs/work-items/ORCH-087/04-test-plan.yaml @@ -0,0 +1,115 @@ +work_item: ORCH-087 +description: > + Тест-план для anti-orphan bump-зачистки live-трекера, корректности заголовка/deploy-статусов + и эффорта в строке стадии. Все тесты автономны (Telegram-примитивы мокаются monkeypatch'ем, + как в существующих tests/test_tracker_bump*.py). Контракт never-raise проверяется явно. +tests: + + # --- G1: anti-orphan bump ------------------------------------------------ + - id: TC-01 + type: unit + description: > + bump: успешный send нового сообщения помечает старые незакрытые карточки на удаление и + delete_telegram вызывается для каждого ранее созданного, не удалённого message_id (sweep). + module: tests/test_tracker_orphan_sweep.py + expected: PASS + + - id: TC-02 + type: unit + description: > + bump: send вернул None (нет креды/transient) -> tracker_message_id НЕ обнуляется, + реестр прежних message_id не теряется, дубликата не создано (<=1 send за вызов). + module: tests/test_tracker_orphan_sweep.py + expected: PASS + + - id: TC-03 + type: unit + description: > + Симуляция рассинхрона "рестарт между delete и send": в реестре остаётся незакрытый старый + mid; следующий update_task_tracker подчищает его (delete вызван) -> сироты не накапливаются. + module: tests/test_tracker_orphan_sweep.py + expected: PASS + + - id: TC-04 + type: unit + description: > + delete старше 48ч (Telegram "message can't be deleted", _DELETE_GONE_MARKERS) -> запись + помечается обработанной (deleted=1), без падения, без бесконечных повторов. + module: tests/test_tracker_orphan_sweep.py + expected: PASS + + - id: TC-05 + type: unit + description: > + never-raise: delete и send оба бросают/возвращают ошибку -> update_task_tracker не поднимает + исключение; карточка отправляется с disable_notification=True и disable_web_page_preview=True. + module: tests/test_tracker_orphan_sweep.py + expected: PASS + + - id: TC-06 + type: unit + description: > + Инвариант "одна карточка": за один update_task_tracker отправляется не более одного нового + сообщения; после успешного цикла в реестре ровно один live (deleted=0) message_id. + module: tests/test_tracker_orphan_sweep.py + expected: PASS + + # --- G2/G3: заголовок и deploy-статусы ----------------------------------- + - id: TC-07 + type: unit + description: > + Регресс: при смене tasks.stage статус-строка карточки меняется (created->To Analyse, + analysis->Analysis, deploy->Awaiting Deploy, done->Done); НЕ застывает на To Analyse. + module: tests/test_tracker_status_line.py + expected: PASS + + - id: TC-08 + type: unit + description: > + plane_status_label покрывает весь deploy-цикл offline: stage=deploy -> "Awaiting Deploy", + stage=done -> "Done"; live-overlay маппит deploying/monitoring/confirm на свои лейблы. + module: tests/test_tracker_status_line.py + expected: PASS + + # --- G4: эффорт в строке стадии ------------------------------------------ + - id: TC-09 + type: unit + description: > + Миграция agent_runs.effort идемпотентна: повторный init_db не падает, колонка присутствует, + существующие колонки/данные не затронуты. + module: tests/test_db_effort_column.py + expected: PASS + + - id: TC-10 + type: unit + description: > + _spawn стампит фактически применённый effort в agent_runs (то, что ушло в --effort), без + повторного resolve на рендере; пустой effort (omit) -> NULL/"" без падения. + module: tests/test_launcher.py + expected: PASS + + - id: TC-11 + type: unit + description: > + render_task_tracker: строка стадии показывает эффорт рядом с моделью в компактном формате; + developer-строка -> xhigh, tester/deployer -> medium (по данным agent_runs.effort). + module: tests/test_tracker_effort_line.py + expected: PASS + + - id: TC-12 + type: unit + description: > + Деградация: agent_runs.effort NULL/пусто -> строка рендерится без эффорт-суффикса (как при + пустой модели), без вывода "None" и без падения. + module: tests/test_tracker_effort_line.py + expected: PASS + + # --- Сквозное ------------------------------------------------------------- + - id: TC-13 + type: integration + description: > + Полный жизненный цикл задачи (created->done) через update_task_tracker на каждой стадии: + в конце ровно одна live-карточка с финальным статусом "Done", сирот нет, never-raise. + module: tests/test_tracker_lifecycle.py + expected: PASS +