Files
orchestrator/docs/work-items/ORCH-087/02-trz.md
claude-bot 1d6c7663a4
All checks were successful
CI / test (push) Successful in 27s
analyst(ET): auto-commit from analyst run_id=420
2026-06-09 01:54:24 +03:00

12 KiB
Raw Blame History

02-ТЗ — ORCH-087

Техническое задание. Конкретные изменения кода/БД. Архитектурное решение (включая выбор bump-с-зачисткой vs edit, схему хранения message_id'ов, вывод G0) принимает архитектор и фиксирует в 06-adr/. Ниже — границы и обязательные требования к реализации.

⚠️ Порядок работ жёсткий: G0 (расследование → ADR) ПЕРВЫМ, фикс G1G3 и расширение G4 — только после. Фикс вслепую без подтверждённой механики запрещён (см. BRD §3).

0. Задействованные модули src/

  • src/notifications.pyupdate_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_labelplane_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) сохранены.
  • Миграции идемпотентны и безопасны на живой общей прод-БД.