From 0ac50b8c73d431488c74235d36b2b76a2465a6ef Mon Sep 17 00:00:00 2001 From: claude-bot Date: Sat, 6 Jun 2026 10:05:26 +0000 Subject: [PATCH] architect(ET): auto-commit from architect run_id=168 --- .../06-adr/ADR-001-tracker-bump-mode.md | 85 +++++++++++++++++++ docs/work-items/ORCH-042/10-tech-risks.md | 21 +++++ 2 files changed, 106 insertions(+) create mode 100644 docs/work-items/ORCH-042/06-adr/ADR-001-tracker-bump-mode.md create mode 100644 docs/work-items/ORCH-042/10-tech-risks.md diff --git a/docs/work-items/ORCH-042/06-adr/ADR-001-tracker-bump-mode.md b/docs/work-items/ORCH-042/06-adr/ADR-001-tracker-bump-mode.md new file mode 100644 index 0000000..bcd490c --- /dev/null +++ b/docs/work-items/ORCH-042/06-adr/ADR-001-tracker-bump-mode.md @@ -0,0 +1,85 @@ +# ADR-001: Режим bump live-трекера через delete+send+repoint, edit как дефолт + +**Work Item:** ORCH-042 · См. `01-brd.md`, `02-trz.md`, `03-acceptance-criteria.md`, `10-tech-risks.md`. + +## Статус +Accepted + +## Контекст + +Live-tracker (`src/notifications.py`, ветка `feat/telegram-live-tracker`, Variant B+) держит **ОДНУ** карточку на задачу и редактирует её на месте (`editMessageText`) на каждом переходе стадии. Это сознательно убило прежнюю боль — «~15 отдельных карточек/дублей на задачу». Защита от дублей — главный инвариант компонента и не должна регрессировать. + +Побочный эффект edit-режима: при активной переписке в чате карточка «тонет» вверху истории — актуальный статус задачи приходится искать скроллом. Слава просит альтернативу: карточка должна всегда быть последней в чате, но без возврата дублей и без звона на каждой стадии. + +Дополнительно — косметика текста карточки (смесь EN-меток стадий с RU-текстом, «Ревью БРД», технический хвост `deployed`). Текстовые правки тривиальны и сами по себе архитектурного решения не требуют; ключевое решение — как реализовать новый режим, не сломав инвариант «одна карточка». + +Ограничения окружения (см. `CLAUDE.md`, `docs/operations/INFRA.md`): +- Контракт компонента: `update_task_tracker` и low-level helpers **никогда не бросают** (сбой нотификации не должен валить конвейер). +- Self-hosting: правка инструмента, который сейчас в проде и обслуживает другие проекты из общей БД/очереди. Прод-рестарт self — только через `deploy-staging` (8501). +- Telegram Bot API: `deleteMessage` не работает для сообщений старше 48 ч и для уже удалённых/недоступных — это нормальный ожидаемый исход, а не ошибка. + +## Решение + +### Р-1. Поведение задаётся конфиг-флагом, дефолт `edit` (нулевая регрессия) +Новое поле `Settings.tracker_mode` (env `ORCH_TRACKER_MODE`), значения `edit` | `bump`, **дефолт `edit`**. Резолюция режима — в `notifications`, case-insensitive + trim; всё, что не равно `"bump"` (включая пустое/мусор/None), трактуется как `edit`. Без явного включения bump поведение неотличимо от текущего → нулевая регрессия и безопасный фолбэк (оркестратор не падает на любом значении флага). + +### Р-2. Режим bump = delete + send + repoint, инвариант «одна карточка» сохраняется иначе +edit-режим держит одну карточку, *редактируя* её. bump держит одну карточку, *пересоздавая* её внизу: +1. если сохранён `tracker_message_id` — best-effort `delete_telegram(старый_id)`; +2. `send_telegram(text, disable_notification=True)` — новая карточка внизу, тихо; +3. при успехе (`new_mid is not None`) — `set_tracker_message_id` перенаправляется на новый id. + +Итог: в чате всегда ровно одна карточка задачи, и она всегда последняя. За **один** вызов `update_task_tracker` отправляется **не более одного** нового сообщения → дублей в пределах вызова нет. + +### Р-3. delete — best-effort, никогда не блокирует отправку новой карточки +Новый low-level helper `delete_telegram(message_id) -> bool` с контрактом «never raises». Семантика возврата — «исчезло ли старое сообщение»: +- `ok:true` → `True`; +- `ok:false` с маркерами «уже нет / нельзя удалить» (`message to delete not found`, `message can't be deleted`, `message_id_invalid`, вынести в константу `_DELETE_GONE_MARKERS`) → `True` (не транзиент, сообщение и так недоступно); +- прочий `ok:false` / 5xx / исключение (сеть/таймаут) → `False` + `logger.warning`; +- нет токена/chat_id → `False`, HTTP не выполняется. + +**Результат `delete_telegram` НЕ влияет на решение отправлять новую карточку** — её шлём всегда (BR-6: delete-fail у сообщения >48 ч → всё равно новое). `False` означает лишь «старое, возможно, ещё живо»; на следующем переходе оно будет удалено повторно (или уже мёртво). Накопления карточек это не даёт, т.к. указатель всегда хранит ровно один id. + +### Р-4. repoint только при успешном send (анти-затирание указателя) +`set_tracker_message_id` вызывается **только** при `new_mid is not None`. Если send вернул None (нет кредов / транзиент 5xx/таймаут) — id **не трогаем** (не затираем на None): карточка перерисуется на следующем переходе, дубля нет (≤1 попытка send за вызов). Это симметрично существующему edit-fallback, который тоже не плодит сообщения при транзиенте. + +### Р-5. bump всегда тихий +Новая карточка отправляется с `disable_notification=True` — всплывает внизу, но без звука/пинга, как и edit сейчас. Состав отдельных НЕтихих пингов (approve-gate / error / deploy-fail / agent-fail) не меняется (вне scope). + +### Р-6. Текстовые правки — в одной точке, общие для обоих режимов +Правки (`_BRD_LABEL` → «Подтверждение BRD»; ✅ вместо ⏸️ после approve-gate; русские display-labels в `_TRACKER_STAGES`; `_done_link` → «Внедрено») затрагивают только **отображаемые** строки. Ключи стадий (`analysis`, …) и имена агентов (`analyst`, …) НЕ меняются — они завязаны на `_STAGE_ACTIVE_AGENT`, `last_done`, БД. Правка `_TRACKER_STAGES` в одном месте автоматически русифицирует и «✅ …», и «🔄 … идёт». + +### Что НЕ меняется (границы решения) +- БД: миграций нет, используется существующая колонка `tasks.tracker_message_id` и хелперы `get_tracker_message_id` / `set_tracker_message_id`. → `08-data-requirements.md` не требуется. +- Инфраструктура / топология / порты / контейнеры — без изменений. → `07-infra-requirements.md` не требуется. +- State machine (`src/stages.py`), реестр QG (`src/qg/checks.py`), стадии, компоненты — без изменений. → глобальный (cross-cutting) ADR не требуется, решение локально для компонента notifications. +- Сигнатуры `send_telegram` / `edit_telegram` / `update_task_tracker` — без изменений (внешние вызовы из `launcher`/`stage_engine` не трогаются). +- Новых зависимостей нет (`httpx` уже используется). + +## Альтернативы + +- **A1. Только bump, без флага.** Отклонено: ломает обратную совместимость и единственного пользователя (Слава может предпочесть edit); рост риска регрессии защиты от дублей. Флаг с дефолтом `edit` даёт мгновенный откат. +- **A2. Pin-сообщение (закрепить карточку).** Отклонено: pin не решает «карточка внизу при переписке», шлёт системное уведомление о закреплении (звон), и усложняет API-контракт. Вне духа «тихого» трекера. +- **A3. send-then-delete (сначала новое, потом удалить старое).** Отклонено как дефолтный порядок: в окне между send и delete в чате видны ДВЕ карточки; при падении на delete остаётся осиротевшая старая → визуальный дубль. delete-then-send гарантирует ≤1 карточку в любой момент при нормальном пути и ≤1 *новую* отправку за вызов в любом случае. +- **A4. Хранить историю/несколько карточек.** Вне scope и противоречит исходному инварианту «одна карточка». + +## Последствия + +**Плюсы** +- Слава получает актуальную карточку всегда внизу чата, одну на задачу, без звона. +- Нулевая регрессия по умолчанию (edit), мгновенный откат флагом. +- Контракт «never raises» и инвариант «одна карточка» сохранены в обоих режимах. +- Изменения локальны (`config.py` + `notifications.py`), без миграций и без рестарта-критичных зависимостей. + +**Минусы / ограничения** +- bump расходует Telegram API на 2 запроса вместо 1 (delete + send) на переход — для одного получателя несущественно (rate-limit Telegram не угрожает). +- При транзиентном delete-fail возможна кратко осиротевшая старая карточка до следующего перехода (она будет вычищена попыткой delete на следующем апдейте) — приемлемо, дублей всё равно не плодит. +- bump теряет визуальную «эволюцию на месте» edit-режима (история чата получает по карточке-замене) — но в чате всегда одна актуальная, что и требуется. + +**Риски** — см. `10-tech-risks.md`. + +## Связи +- BRD/ТЗ/AC: `docs/work-items/ORCH-042/01-brd.md`, `02-trz.md`, `03-acceptance-criteria.md`; тест-план `04-test-plan.yaml`. +- Компонент: live-tracker (`src/notifications.py`), `feat/telegram-live-tracker` (Variant B+). +- Контекст self-hosting / staging-страховка: `CLAUDE.md`, `docs/operations/INFRA.md`, `docs/architecture/adr/adr-0003-staging-gate.md`. +- Обновляемая дока (в том же PR, стадия development): `CHANGELOG.md`, `docs/architecture/internals.md` (секция live-tracker: режимы + `ORCH_TRACKER_MODE` + `delete_telegram`), `.env.example`. diff --git a/docs/work-items/ORCH-042/10-tech-risks.md b/docs/work-items/ORCH-042/10-tech-risks.md new file mode 100644 index 0000000..a8d67ee --- /dev/null +++ b/docs/work-items/ORCH-042/10-tech-risks.md @@ -0,0 +1,21 @@ +# 10 — Технические риски: ORCH-042 + +См. `02-trz.md`, `06-adr/ADR-001-tracker-bump-mode.md`, `03-acceptance-criteria.md`. + +Шкала: Вероятность × Влияние ∈ {низк., сред., выс.}. + +| # | Риск | Вер. | Влияние | Митигация | Контроль (AC/TC) | +|---|------|------|---------|-----------|-------------------| +| R-1 | **Регрессия защиты от дублей** — рефактор `update_task_tracker` ломает edit-ветку, возвращается боль «~15 карточек». | низк. | выс. | edit — дефолт и неизменяемая ветка; bump добавляется отдельной веткой `if mode == "bump"`, edit-код не трогается. Полное покрытие edit-регрессии тестами. | AC-3..AC-6, AC-8; TC-03..TC-06, TC-24 | +| R-2 | **Двойная отправка / накопление карточек в bump** — delete и send рассинхронизированы, в чате >1 карточки. | низк. | сред. | Инвариант: ≤1 `send_telegram` за вызов; `set_tracker_message_id` только при успешном send; delete best-effort и не блокирует. | AC-8, AC-10, AC-11; TC-08, TC-09, TC-11 | +| R-3 | **Затирание `tracker_message_id` на None** при транзиентном send-fail → потеря указателя, следующий апдейт не найдёт старое. | низк. | сред. | repoint только при `new_mid is not None`; при None id сохраняется как есть. | AC-13; TC-10 | +| R-4 | **Нарушение контракта «never raises»** — исключение из `delete_telegram`/новой ветки валит конвейер (групповой риск из-за общей очереди). | низк. | выс. | `delete_telegram` обёрнут try/except → bool; внешний try/except в `update_task_tracker` сохранён; сеть/httpx мокаются в тестах. | AC-12, AC-14; TC-12..TC-17 | +| R-5 | **Ложная классифик. delete-ответа** — неизвестный `ok:false` принят за «исчезло» (или наоборот), вечные ретраи/тишина. | низк. | низк. | Явные `_DELETE_GONE_MARKERS` → True; всё прочее (включая 5xx) → False; повтор delete на следующем апдейте безопасен (идемпотентно). | AC-12; TC-13, TC-14 | +| R-6 | **Падение CI на старых ассертах** — тесты `tests/test_telegram_tracker.py` проверяют EN-метки/«Ревью БРД». | сред. | сред. | ТЗ §5 явно требует обновить существующие ассерты под русские метки и «Подтверждение BRD» в том же PR. | AC-20; TC-18, TC-21, TC-24 | +| R-7 | **Сломанная human-gate индикация** — ✅ показан до прохождения approve-gate (ввод в заблуждение). | низк. | низк. | ✅ только при заданном `brd_review_ended_at`; ветка ожидания (`review_seconds is None`, ⏳) не меняется. | AC-16; TC-19, TC-20 | +| R-8 | **Скрытая зависимость от display-label** — русификация `_TRACKER_STAGES` ломает логику, завязанную на текст метки. | низк. | сред. | Меняется только 2-й элемент кортежа (label); ключи стадий и имена агентов (`_STAGE_ACTIVE_AGENT`, `last_done`, БД) не трогаются. | AC-17; TC-21 | +| R-9 | **Self-hosting: прод-сбой при выкатке self** — общая БД/очередь, рестарт орка останавливает все проекты. | низк. | выс. | Изменения только в коде нотификаций, миграций БД нет; обязательная страховка `deploy-staging` (8501) перед prod (CLAUDE.md, INFRA.md, adr-0003). Дефолт edit → даже при выкатке поведение не меняется без явного флага. | стадия deploy-staging; `check_staging_status` | +| R-10 | **Документация не обновлена** в том же PR (internals.md / .env.example / CHANGELOG) → REQUEST_CHANGES. | сред. | низк. | ТЗ §5 и AC-21 фиксируют список; reviewer проверяет наличие. | AC-21 | + +## Сводный вывод +Все риски — **низкие по вероятности** при соблюдении инвариантов из ADR-001 (edit-дефолт, ≤1 send/вызов, repoint-only-on-success, never-raises, правка только display-label). Остаточный групповой self-hosting-риск (R-9) полностью покрывается обязательным `deploy-staging`-гейтом и тем, что дефолтное поведение не меняется. Блокеров для перехода на стадию development нет.