telegram: сервис поднимается без бота и работает одним входом
- Клиент бота собирается один раз и достаётся отправителю и транспорту; разрез прошёл по «ответил ли Telegram»: ответ «такого бота нет» роняет старт, недоступность даёт подъём без Telegram (ADR-2026-08-13). Ожидание при сборке ограничено сроком — иначе молчащий Telegram вешал подъём. - Недоставленный ответ не роняет шаг: пишется с job_id и считается метрикой, уровень по причине — WARN для неподнятого входа, ERROR для неназванного адресата. Заведены transcriber_intake_up и transcriber_undelivered_reply_count. - Закрыта утечка токена в журнал: отказ разбора адреса рождается раньше обращения к клиенту, то есть мимо чистки на его границе.
This commit is contained in:
+80
@@ -0,0 +1,80 @@
|
||||
## Purpose
|
||||
|
||||
Конвейер расшифровки: как задача движется по состояниям, что делает воркер,
|
||||
когда работы нет, что считается отказом шага и что бывает с ответом отправителю,
|
||||
когда доставить его некуда.
|
||||
|
||||
Описаны пустой прогон воркера, неделимость захвата и срок его протухания, число
|
||||
попыток и состояние «мертва», нарастающая пауза перед повтором, условие записи
|
||||
результата держателем захвата и недоставка ответа при неподнятом входе.
|
||||
Сознательно не описаны: цепочка переходов `created → converted → transcribe →
|
||||
done | failed`, отмена контекста посреди шага и освобождение ресурсов внешних
|
||||
клиентов. Это не значит, что такого поведения нет: оно живёт в коде, а
|
||||
требования на него не написаны, потому что требование без проверки —
|
||||
предположение, а не норма. Первая задача, которая трогает любое из
|
||||
перечисленного, дописывает его сюда.
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Недоставленный ответ не роняет шаг
|
||||
|
||||
Шаг конвейера SHALL доводить задачу до достигнутого состояния, когда ответ
|
||||
отправителю доставить не удалось, и MUST не считать недоставку отказом шага.
|
||||
Недоставка MUST быть записана в журнал владельца, MUST нести идентификатор
|
||||
задачи, MUST называть причину и MUST считаться отдельной метрикой с причиной
|
||||
меткой.
|
||||
|
||||
Причин у недоставки две, и исход у них общий: **вход отправителя не поднят** —
|
||||
задача заведена прошлым запуском, а сервис поднялся без этого входа; и **адресат
|
||||
у задачи не назван** — источником значится Telegram, а чата в задаче нет.
|
||||
|
||||
Уровень записи MUST различать эти причины. Неподнятый вход — объявленный режим,
|
||||
и его уровень «может стать проблемой». Неназванный адресат — симптом порчи
|
||||
записи: у задачи из Telegram чат есть всегда, и пропасть он может только от
|
||||
дефекта, самый коварный источник которого назван инвариантом проекта про колонки
|
||||
очереди. Один уровень на обе причины утопил бы этот сигнал в потоке штатных
|
||||
записей о ненастроенном боте.
|
||||
|
||||
Общий исход — не упрощение, а следствие момента: ответ уходит **после** того, как
|
||||
достигнутое состояние сохранено. Работа к этой минуте сделана, и объявленный
|
||||
отказ засчитался бы воркеру сбоем и лёг бы владельцу записью отказа — то есть
|
||||
соврал бы про исход дважды. Повтор делу не помогает: ни бот, ни адресат от
|
||||
ожидания не появятся. Поэтому задача остаётся в достигнутом состоянии, в повтор
|
||||
не уходит и в `failed` не переводится, а причина недоставки живёт в записи
|
||||
журнала, а не в состоянии задачи.
|
||||
|
||||
Идентификатор задачи в записи обязателен: без него владелец видит, что ответ не
|
||||
ушёл, но не может найти, чей. Текст расшифровки и сообщение отправителя в эту
|
||||
запись MUST не попадать — приватность содержимого записи требование не
|
||||
ослабляет.
|
||||
|
||||
Отложенной доставки это требование не заводит: ответ, не ушедший сегодня, не
|
||||
уходит и потом. Забрать расшифровку можно там же, где лежат остальные.
|
||||
|
||||
#### Scenario: Вход отправителя не поднят
|
||||
|
||||
- **GIVEN** задача принята входом Telegram прошлым запуском сервиса
|
||||
- **AND** сервис поднялся без этого входа
|
||||
- **WHEN** шаг конвейера доходит до ответа отправителю
|
||||
- **THEN** шаг завершается без отказа, и воркер не считает прогон сбоем
|
||||
- **AND** задача остаётся в достигнутом состоянии, в повтор не уходит и в
|
||||
`failed` не переводится
|
||||
- **AND** в журнале есть запись уровня `WARN` о недоставке с идентификатором
|
||||
задачи и причиной
|
||||
- **AND** счётчик недоставленных ответов вырос с этой причиной меткой
|
||||
- **AND** ни текста расшифровки, ни сообщения отправителя в этой записи нет
|
||||
|
||||
#### Scenario: Адресат у задачи не назван
|
||||
|
||||
- **GIVEN** у задачи источником значится Telegram, а чат не назван
|
||||
- **WHEN** шаг конвейера доходит до ответа отправителю
|
||||
- **THEN** шаг завершается без отказа, и воркер не считает прогон сбоем
|
||||
- **AND** задача остаётся в достигнутом состоянии
|
||||
- **AND** в журнале есть запись уровня `ERROR` о недоставке с идентификатором
|
||||
задачи и причиной: неназванный адресат — симптом порчи записи
|
||||
|
||||
#### Scenario: Отвечать некуда, потому что запись пришла не из Telegram
|
||||
|
||||
- **GIVEN** задача принята по HTTP
|
||||
- **WHEN** шаг конвейера доходит до ответа отправителю
|
||||
- **THEN** шаг завершается без отказа и без записи о недоставке
|
||||
Reference in New Issue
Block a user