14 KiB
ТЗ — 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 в БД)». - Артефакт расследования и обоснованная рекомендация
bumpvsedit→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"):
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) — взвесить против фича-просьбы.
- (A) bump + полный учёт
- 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-switchtracker_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):
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_previewORCH-080 — сохранены. STAGE_TRANSITIONS/QG_CHECKS/ стадии конвейера — без изменений.- БР-G6: разработка/merge поверх свежего
origin/main(ORCH-86);reconciler.pyне эродировать. - Миграции БД аддитивны и идемпотентны (общая прод-БД, enduro не трогать).