analyst(ET): auto-commit from analyst run_id=420
All checks were successful
CI / test (push) Successful in 27s

This commit is contained in:
2026-06-09 01:54:24 +03:00
parent 53260f6794
commit 1d6c7663a4
4 changed files with 451 additions and 0 deletions

View File

@@ -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.
</content>
</invoke>

View File

@@ -0,0 +1,130 @@
# 02-ТЗ — ORCH-087
Техническое задание. Конкретные изменения кода/БД. Архитектурное решение (включая выбор
`bump`-с-зачисткой vs `edit`, схему хранения message_id'ов, вывод G0) принимает архитектор и
фиксирует в `06-adr/`. Ниже — границы и обязательные требования к реализации.
> ⚠️ Порядок работ жёсткий: **G0 (расследование → ADR) ПЕРВЫМ**, фикс G1G3 и расширение 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) сохранены.
- Миграции идемпотентны и безопасны на живой общей прод-БД.
</content>

View File

@@ -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), подтверждённые/опровергнутые источники рассинхрона (ae), точная причина застывания
заголовка (старый рендер vs баг лейбла) с воспроизведением на staging, и обоснованная рекомендация
`bump`-с-зачисткой vs `edit` С ДАННЫМИ.
- **FAIL:** фикс начат без ADR; вывод опирается на предположения, а не на собранные message_id /
staging-прогон.
### AC-0.2 — фикс следует за расследованием
- **PASS:** изменения кода G1G4 соответствуют решению ADR (выбранный режим/схема хранения).
- **FAIL:** реализация противоречит выводу G0 или сделана до него.
## G1G3 — сироты, заголовок, 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:** красные/отсутствующие тесты.
</content>

View File

@@ -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
</content>