удалён вход Telegram, владелец записи стал обязателен в схеме
- убраны клиент бота, транспорт обновлений, отправитель сообщений, сборка входа при старте, секция настроек и зависимость go-telegram-bot-api; из конвейера ушла доставка ответа отправителю — исход виден опросом готовности. Колонки адресата и значение источника остались в схеме: применённые шаги не переписываются - шаг 202608140003 запрещает пустого владельца у аудиозаписи и у файла; существующие строки он не проверяет, и это принято сознательно — искать их надо запросом до выкладки - ревью нашло два пред-существующих дефекта, оба закрыты: пустой второй ответ распознавателя стирал сохранённую расшифровку, а пустая расшифровка перестала быть заметной вместе с убранной доставкой. Попутно поднят golang.org/x/image до v0.45.0 — красный шаг vulns, воспроизводился и на чистом master
This commit is contained in:
+57
-127
@@ -3,14 +3,14 @@
|
||||
## Purpose
|
||||
|
||||
Конвейер расшифровки: как аудиозапись движется по рубежам, что делает воркер,
|
||||
когда работы нет, что считается отказом шага и что бывает с ответом отправителю,
|
||||
когда доставить его некуда.
|
||||
когда работы нет, и что считается отказом шага.
|
||||
|
||||
Описаны цепочка рубежей и смысл рубежа, остановка признаком и её причины, оба
|
||||
сторожа — число отказов и время в рубеже, — откладывание работы отдельно от
|
||||
перехода, неделимость захвата и срок его протухания, условие записи результата
|
||||
держателем захвата, нарастающая пауза перед повтором, число воркеров настройкой,
|
||||
журнал событий записи и недоставка ответа при неподнятом входе.
|
||||
журнал событий записи и молчание конвейера наружу: обращений к отправителю он не
|
||||
делает вовсе, и свой исход тот узнаёт опросом готовности.
|
||||
|
||||
Сознательно не описаны: освобождение ресурсов внешних клиентов и **какие отказы
|
||||
считаются приговором записи, а какие поводом к повтору**. Второе — не пробел
|
||||
@@ -101,7 +101,7 @@
|
||||
захват, перевыданный другому — по протуханию срока или после того, как человек
|
||||
снял признак остановки в панели, — обязан обращать запись первого в отказ.
|
||||
Условие, проверяющее лишь непустоту признака или срок, пропустило бы обоих, и
|
||||
два шага записали бы в одну запись и оба ответили бы отправителю.
|
||||
два шага записали бы в одну запись по очереди, испортив её результат.
|
||||
|
||||
Одна и та же запись MUST доставаться ровно одному захватившему. Двум вызывающим,
|
||||
пришедшим за работой одновременно, запись MUST достаться одному, а второй MUST
|
||||
@@ -155,14 +155,18 @@
|
||||
всё ещё принадлежит ему. Запись MUST быть условна по **признаку этого захвата** —
|
||||
значению, уникальному для каждого захвата, — а не по занятости записи вообще.
|
||||
Шаг, чей захват за время работы достался другому, MUST завершиться без записи
|
||||
результата и без ответа отправителю.
|
||||
результата.
|
||||
|
||||
Требование закрывает то, чего неделимость захвата не закрывает: захват протухает
|
||||
не только у мёртвого воркера, но и у живого — шаг, идущий дольше своего срока,
|
||||
теряет запись, продолжая работать. Снять захват может и человек, вернувший
|
||||
остановленную запись в работу. Без условия по уникальному признаку два воркера
|
||||
пишут в одну запись по очереди, счётчик отказов сбрасывает тот, кто уже не
|
||||
владелец, а отправитель получает два ответа на одну запись.
|
||||
пишут в одну запись по очереди, а счётчик отказов сбрасывает тот, кто уже не
|
||||
владелец.
|
||||
|
||||
Довод про два ответа отправителю из требования ушёл вместе с доставкой: обращений
|
||||
наружу шаг не делает. Требование от этого не ослабло — порча записи двумя
|
||||
пишущими остаётся его предметом целиком.
|
||||
|
||||
Шаг MUST записывать только те поля, которыми распоряжается сам. Запись он держит
|
||||
снимком с момента захвата и до записи — это часы, — и безусловная запись снимка
|
||||
@@ -185,7 +189,6 @@
|
||||
- **AND** за это время та же запись досталась другому захвату
|
||||
- **WHEN** первый шаг доходит до записи результата
|
||||
- **THEN** результат не записывается
|
||||
- **AND** отправителю ничего не отправляется
|
||||
|
||||
#### Scenario: Человек снял остановку под работающим шагом
|
||||
|
||||
@@ -263,89 +266,18 @@ MUST расти с числом её отказов до объявленног
|
||||
- **THEN** задержка до следующей проверки каждый раз одна и та же
|
||||
- **AND** число отказов записи не растёт
|
||||
|
||||
### Requirement: Недоставленный ответ не роняет шаг
|
||||
|
||||
Шаг конвейера SHALL доводить запись до достигнутого рубежа, когда ответ
|
||||
отправителю доставить не удалось, и MUST не считать недоставку отказом шага.
|
||||
Недоставка MUST быть записана в журнал владельца, MUST нести идентификатор
|
||||
записи, MUST называть причину и MUST считаться отдельной метрикой с причиной
|
||||
меткой.
|
||||
|
||||
Причин у недоставки две, и исход у них общий: **вход отправителя не поднят** —
|
||||
запись заведена прошлым запуском, а сервис поднялся без этого входа; и **адресат
|
||||
у записи не назван** — источником значится Telegram, а чата в записи нет.
|
||||
|
||||
Уровень записи MUST различать эти причины. Неподнятый вход — объявленный режим,
|
||||
и его уровень «может стать проблемой». Неназванный адресат — симптом порчи
|
||||
записи: у записи из Telegram чат есть всегда, и пропасть он может только от
|
||||
дефекта, самый коварный источник которого назван инвариантом проекта про колонки
|
||||
очереди. Один уровень на обе причины утопил бы этот сигнал в потоке штатных
|
||||
записей о ненастроенном боте.
|
||||
|
||||
Общий исход — не упрощение, а следствие момента: ответ уходит **после** того, как
|
||||
достигнутый рубеж сохранён. Работа к этой минуте сделана, и объявленный отказ
|
||||
засчитался бы воркеру сбоем и лёг бы владельцу записью отказа — то есть соврал бы
|
||||
про исход дважды. Повтор делу не помогает: ни бот, ни адресат от ожидания не
|
||||
появятся. Поэтому запись остаётся на достигнутом рубеже, в повтор не уходит и
|
||||
**признака остановки не получает**, а причина недоставки живёт в записи журнала,
|
||||
а не в рубеже записи.
|
||||
|
||||
То же MUST относиться к недоставке сообщения об **остановке**: остановка уже
|
||||
сохранена, и недоставка её MUST не отменять.
|
||||
|
||||
Идентификатор записи в этой строке обязателен: без него владелец видит, что
|
||||
ответ не ушёл, но не может найти, чей. Текст расшифровки и сообщение отправителя
|
||||
в эту запись MUST не попадать — приватность содержимого записи требование не
|
||||
ослабляет.
|
||||
|
||||
Отложенной доставки это требование не заводит: ответ, не ушедший сегодня, не
|
||||
уходит и потом. Забрать расшифровку можно там же, где лежат остальные.
|
||||
|
||||
#### Scenario: Вход отправителя не поднят
|
||||
|
||||
- **GIVEN** запись принята входом Telegram прошлым запуском сервиса
|
||||
- **AND** сервис поднялся без этого входа
|
||||
- **WHEN** шаг конвейера доходит до ответа отправителю
|
||||
- **THEN** шаг завершается без отказа, и воркер не считает прогон сбоем
|
||||
- **AND** запись остаётся на достигнутом рубеже, в повтор не уходит и признака
|
||||
остановки не получает
|
||||
- **AND** в журнале есть запись уровня `WARN` о недоставке с идентификатором
|
||||
записи и причиной
|
||||
- **AND** счётчик недоставленных ответов вырос с этой причиной меткой
|
||||
- **AND** ни текста расшифровки, ни сообщения отправителя в этой записи нет
|
||||
|
||||
#### Scenario: Адресат у записи не назван
|
||||
|
||||
- **GIVEN** у записи источником значится Telegram, а чат не назван
|
||||
- **WHEN** шаг конвейера доходит до ответа отправителю
|
||||
- **THEN** шаг завершается без отказа, и воркер не считает прогон сбоем
|
||||
- **AND** запись остаётся на достигнутом рубеже
|
||||
- **AND** в журнале есть запись уровня `ERROR` о недоставке с идентификатором
|
||||
записи и причиной: неназванный адресат — симптом порчи записи
|
||||
|
||||
#### Scenario: Не доехало сообщение об остановке
|
||||
|
||||
- **GIVEN** запись остановлена признаком
|
||||
- **AND** вход отправителя не поднят
|
||||
- **WHEN** шаг доходит до ответа отправителю
|
||||
- **THEN** признак остановки у записи остаётся
|
||||
- **AND** в журнале есть запись о недоставке с идентификатором записи и причиной
|
||||
|
||||
#### Scenario: Отвечать некуда, потому что запись пришла не из Telegram
|
||||
|
||||
- **GIVEN** запись принята по HTTP
|
||||
- **WHEN** шаг конвейера доходит до ответа отправителю
|
||||
- **THEN** шаг завершается без отказа и без записи о недоставке
|
||||
|
||||
### Requirement: Выборка воркера владельцем не сужается
|
||||
|
||||
Воркер SHALL брать записи всех владельцев подряд и MUST не учитывать владельца
|
||||
при выборе очередной записи. Запись без владельца — принятая ботом — MUST
|
||||
обрабатываться наравне с прочими.
|
||||
при выборе очередной записи.
|
||||
|
||||
Владелец решает, кому запись показывать, а не кому её считать. Сужение выборки
|
||||
владельцем остановило бы расшифровку записей бота вовсе, а записи остальных
|
||||
поставило бы в зависимость от того, кто первым завёл учётную запись.
|
||||
владельцем поставило бы записи одних людей в зависимость от того, кто первым
|
||||
завёл учётную запись.
|
||||
|
||||
Оговорка про записи без владельца из требования ушла: заводить их стало нечем —
|
||||
колонка владельца пустого значения не принимает, и норму держит capability
|
||||
`storage`.
|
||||
|
||||
Владелец записи MUST переживать работу конвейера: шаг, сохраняющий свой
|
||||
результат, владельца не трогает и не затирает.
|
||||
@@ -356,12 +288,6 @@ MUST расти с числом её отказов до объявленног
|
||||
- **WHEN** воркер забирает работу
|
||||
- **THEN** ему достаются обе, в порядке заведения
|
||||
|
||||
#### Scenario: Запись без владельца обрабатывается
|
||||
|
||||
- **GIVEN** заведена запись, принятая ботом, — без владельца
|
||||
- **WHEN** воркер забирает работу
|
||||
- **THEN** она достаётся ему наравне с прочими
|
||||
|
||||
#### Scenario: Шаг конвейера владельца не затирает
|
||||
|
||||
- **GIVEN** запись с владельцем прошла шаг конвейера
|
||||
@@ -463,38 +389,6 @@ MUST расти с числом её отказов до объявленног
|
||||
- **THEN** число отказов, пауза и время входа в рубеж сброшены
|
||||
- **AND** ближайший захват выдаёт запись, а не останавливает её снова
|
||||
|
||||
### Requirement: Всякая остановка сообщает отправителю
|
||||
|
||||
Остановка записи по любой причине SHALL сообщать отправителю о неудаче ровно
|
||||
так же, как сообщает о ней отказ шага, и MUST быть видна владельцу сервиса
|
||||
записью в журнале.
|
||||
|
||||
Требование стоит на инварианте проекта «Принятая запись не теряется молча»:
|
||||
инвариант допускает два исхода — запись пригодна к повтору либо об отказе
|
||||
сказано, — а остановленная запись захвату не выдаётся, значит первый исход
|
||||
исключён.
|
||||
|
||||
Причин остановки больше одной, и обязанность общая для всех: исчерпанные
|
||||
отказы, застревание в рубеже, приговор шага. Обязанность, записанная у одной
|
||||
причины, у остальных читалась бы как снятая.
|
||||
|
||||
Ответ уходит **после** того, как признак остановки сохранён, и недоставка этого
|
||||
ответа MUST не отменять остановку: её нормирует требование «Недоставленный ответ
|
||||
не роняет шаг».
|
||||
|
||||
#### Scenario: Остановка по отказам сообщает отправителю
|
||||
|
||||
- **GIVEN** запись остановлена по исчерпании отказов
|
||||
- **WHEN** шаг доходит до ответа отправителю
|
||||
- **THEN** отправитель получает сообщение о неудаче
|
||||
|
||||
#### Scenario: Остановка по времени сообщает отправителю
|
||||
|
||||
- **GIVEN** запись остановлена по пределу времени в рубеже
|
||||
- **WHEN** шаг доходит до ответа отправителю
|
||||
- **THEN** отправитель получает сообщение о неудаче
|
||||
- **AND** в журнале владельца есть запись об остановке с причиной
|
||||
|
||||
### Requirement: Время в рубеже ограничено
|
||||
|
||||
У аудиозаписи SHALL быть время входа в рубеж, и оно MUST ставиться только при
|
||||
@@ -674,8 +568,8 @@ MUST не быть привязаны к отдельному шагу: кажд
|
||||
|
||||
Запись, захваченная с числом отказов сверх заданного предела, MUST
|
||||
останавливаться признаком тем, кто её захватил, и MUST не отдаваться шагу в
|
||||
работу. Об этой остановке отправителю сообщается наравне с прочими — норму
|
||||
держит требование «Всякая остановка сообщает отправителю».
|
||||
работу. Остановка эта видна отправителю опросом готовности наравне с прочими —
|
||||
норму держит capability `intake`.
|
||||
|
||||
Этот сторож MUST отвечать только за повторы внутри шага. Время, проведённое
|
||||
записью в рубеже, MUST мериться отдельным сторожем: одно число не справляется ни
|
||||
@@ -689,7 +583,7 @@ MUST не быть привязаны к отдельному шагу: кажд
|
||||
- **WHEN** запись проходит заданное число отказов
|
||||
- **THEN** у неё появляется признак остановки
|
||||
- **AND** следующий захват её не выдаёт
|
||||
- **AND** отправитель получает сообщение о неудаче
|
||||
- **AND** опрос готовности отдаёт владельцу записи признак остановки
|
||||
|
||||
#### Scenario: Шаг уносит процесс, не объявив отказа
|
||||
|
||||
@@ -710,3 +604,39 @@ MUST не быть привязаны к отдельному шагу: кажд
|
||||
- **WHEN** смотрят её число отказов
|
||||
- **THEN** оно не приблизилось к пределу
|
||||
|
||||
### Requirement: Конвейер ответа отправителю не шлёт
|
||||
|
||||
Шаг конвейера SHALL доводить запись до достигнутого рубежа и MUST не обращаться
|
||||
к отправителю вовсе — ни с готовым текстом, ни с сообщением о неудаче. Исход
|
||||
своей записи отправитель узнаёт опросом готовности и в панели владельца; адрес
|
||||
опроса и содержимое ответа нормирует capability `intake`.
|
||||
|
||||
Требование заведено взамен доставки в чат, убранной вместе с входом Telegram.
|
||||
Без него молчание конвейера читалось бы как недоделка: прежде ответ уходил, и
|
||||
всякий, кто помнит это, ищет в шаге отправку, а её отсутствие принимает за
|
||||
потерянную ветку.
|
||||
|
||||
Инвариант проекта «Принятая запись не теряется молча» держится теперь опросом
|
||||
готовности — там остановка видна признаком — и журналом владельца, где у неё
|
||||
стоит причина. Обязанность при этом сменила направление: прежде об отказе
|
||||
сообщали, теперь отказ доступен спросившему. Отправитель, который не
|
||||
спрашивает, об остановке не узнаёт.
|
||||
|
||||
Записи, которой этот канал недоступен, не бывает: у каждой записи есть владелец,
|
||||
и опрос отдаёт ему её исход. Держится это обязательностью владельца в схеме
|
||||
хранилища — норму держит capability `storage`.
|
||||
|
||||
#### Scenario: Готовый текст отправителю не уходит
|
||||
|
||||
- **GIVEN** запись дошла до конечного рубежа
|
||||
- **WHEN** шаг конвейера её завершает
|
||||
- **THEN** ни одного обращения наружу с текстом расшифровки не уходит
|
||||
- **AND** текст достаётся опросом готовности
|
||||
|
||||
#### Scenario: Остановка видна опросом, а не сообщением
|
||||
|
||||
- **GIVEN** запись остановлена по исчерпании отказов
|
||||
- **WHEN** владелец записи спрашивает её рубеж
|
||||
- **THEN** ответ несёт достигнутый рубеж и признак остановки
|
||||
- **AND** в журнале владельца сервиса есть запись об остановке с причиной
|
||||
|
||||
|
||||
Reference in New Issue
Block a user