- убраны клиент бота, транспорт обновлений, отправитель сообщений, сборка входа при старте, секция настроек и зависимость go-telegram-bot-api; из конвейера ушла доставка ответа отправителю — исход виден опросом готовности. Колонки адресата и значение источника остались в схеме: применённые шаги не переписываются - шаг 202608140003 запрещает пустого владельца у аудиозаписи и у файла; существующие строки он не проверяет, и это принято сознательно — искать их надо запросом до выкладки - ревью нашло два пред-существующих дефекта, оба закрыты: пустой второй ответ распознавателя стирал сохранённую расшифровку, а пустая расшифровка перестала быть заметной вместе с убранной доставкой. Попутно поднят golang.org/x/image до v0.45.0 — красный шаг vulns, воспроизводился и на чистом master
259 lines
19 KiB
Markdown
259 lines
19 KiB
Markdown
## ADDED Requirements
|
||
|
||
### Requirement: Конвейер ответа отправителю не шлёт
|
||
|
||
Шаг конвейера SHALL доводить запись до достигнутого рубежа и MUST не обращаться
|
||
к отправителю вовсе — ни с готовым текстом, ни с сообщением о неудаче. Исход
|
||
своей записи отправитель узнаёт опросом готовности и в панели владельца; адрес
|
||
опроса и содержимое ответа нормирует capability `intake`.
|
||
|
||
Требование заведено взамен доставки в чат, убранной вместе с входом Telegram.
|
||
Без него молчание конвейера читалось бы как недоделка: прежде ответ уходил, и
|
||
всякий, кто помнит это, ищет в шаге отправку, а её отсутствие принимает за
|
||
потерянную ветку.
|
||
|
||
Инвариант проекта «Принятая запись не теряется молча» держится теперь опросом
|
||
готовности — там остановка видна признаком — и журналом владельца, где у неё
|
||
стоит причина. Обязанность при этом сменила направление: прежде об отказе
|
||
сообщали, теперь отказ доступен спросившему. Отправитель, который не
|
||
спрашивает, об остановке не узнаёт.
|
||
|
||
Записи, которой этот канал недоступен, не бывает: у каждой записи есть владелец,
|
||
и опрос отдаёт ему её исход. Держится это обязательностью владельца в схеме
|
||
хранилища — норму держит capability `storage`.
|
||
|
||
#### Scenario: Готовый текст отправителю не уходит
|
||
|
||
- **GIVEN** запись дошла до конечного рубежа
|
||
- **WHEN** шаг конвейера её завершает
|
||
- **THEN** ни одного обращения наружу с текстом расшифровки не уходит
|
||
- **AND** текст достаётся опросом готовности
|
||
|
||
#### Scenario: Остановка видна опросом, а не сообщением
|
||
|
||
- **GIVEN** запись остановлена по исчерпании отказов
|
||
- **WHEN** владелец записи спрашивает её рубеж
|
||
- **THEN** ответ несёт достигнутый рубеж и признак остановки
|
||
- **AND** в журнале владельца сервиса есть запись об остановке с причиной
|
||
|
||
## MODIFIED Requirements
|
||
|
||
### Requirement: Захват задачи неделим
|
||
|
||
Захват записи воркером SHALL быть одним неделимым шагом хранилища: выбор
|
||
подходящей записи и пометка её захваченной MUST происходить вместе.
|
||
|
||
Захват MUST возвращать **идентификатор записи и признак этого захвата**, а не
|
||
перечень её колонок. Колонки записи шаг читает сам, обычным чтением. Иначе
|
||
всякая новая колонка аудиозаписи попадала бы под инвариант проекта о колонках
|
||
очереди, и забытая в захвате колонка приезжала бы нулевой, а первое же
|
||
сохранение писало бы этот ноль поверх сохранённого значения.
|
||
|
||
**Признак захвата MUST быть значением, уникальным для каждого захвата**, а не
|
||
признаком занятости. Условие записи результата сверяет именно это значение:
|
||
захват, перевыданный другому — по протуханию срока или после того, как человек
|
||
снял признак остановки в панели, — обязан обращать запись первого в отказ.
|
||
Условие, проверяющее лишь непустоту признака или срок, пропустило бы обоих, и
|
||
два шага записали бы в одну запись по очереди, испортив её результат.
|
||
|
||
Одна и та же запись MUST доставаться ровно одному захватившему. Двум вызывающим,
|
||
пришедшим за работой одновременно, запись MUST достаться одному, а второй MUST
|
||
получить признак «работы сейчас нет».
|
||
|
||
Срок протухания захвата MUST ехать с рубежом записи, а не с воркером: воркер не
|
||
привязан к шагу и не знает заранее, что вытянет. Срок MUST записываться числом
|
||
при самом захвате.
|
||
|
||
Порядок выборки MUST быть определён однозначно: сравнения по неуникальному
|
||
значению для этого мало, и к нему MUST добавляться ключ записи. Иначе порядок
|
||
обработки невоспроизводим, а проверка, опирающаяся на «следующую» запись, зелена
|
||
через раз.
|
||
|
||
Требование стоит на инварианте проекта «Принятая запись не теряется молча»:
|
||
захват, разделённый на два шага, отдаёт одну запись двум воркерам, и работа
|
||
одного из них теряется без следа.
|
||
|
||
Признак «работы нет» этим требованием не переопределяется — его нормирует
|
||
требование «Пустой прогон воркера — не отказ».
|
||
|
||
#### Scenario: За работой пришли трое разом
|
||
|
||
- **GIVEN** к работе пригодна ровно одна запись
|
||
- **WHEN** три захвата идут одновременно
|
||
- **THEN** запись получает ровно один из них
|
||
- **AND** двое остальных получают признак «работы сейчас нет»
|
||
|
||
#### Scenario: Захваченная запись не выдаётся второй раз
|
||
|
||
- **GIVEN** запись захвачена и срок захвата не истёк
|
||
- **WHEN** приходит следующий захват
|
||
- **THEN** эта запись ему не выдаётся
|
||
|
||
#### Scenario: Захват отдаёт идентификатор и свой признак
|
||
|
||
- **GIVEN** к работе пригодна запись
|
||
- **WHEN** воркер её захватывает
|
||
- **THEN** захват возвращает идентификатор записи и признак этого захвата
|
||
- **AND** колонки записи шаг читает отдельным чтением
|
||
|
||
#### Scenario: Признак перевыданного захвата отличается от прежнего
|
||
|
||
- **GIVEN** запись захвачена, и признак первого захвата известен
|
||
- **WHEN** человек снимает признак остановки, и запись захватывает другой воркер
|
||
- **THEN** признак нового захвата отличается от признака первого
|
||
|
||
### Requirement: Результат пишет только держатель захвата
|
||
|
||
Шаг конвейера SHALL записывать свой результат только тогда, когда захват записи
|
||
всё ещё принадлежит ему. Запись MUST быть условна по **признаку этого захвата** —
|
||
значению, уникальному для каждого захвата, — а не по занятости записи вообще.
|
||
Шаг, чей захват за время работы достался другому, MUST завершиться без записи
|
||
результата.
|
||
|
||
Требование закрывает то, чего неделимость захвата не закрывает: захват протухает
|
||
не только у мёртвого воркера, но и у живого — шаг, идущий дольше своего срока,
|
||
теряет запись, продолжая работать. Снять захват может и человек, вернувший
|
||
остановленную запись в работу. Без условия по уникальному признаку два воркера
|
||
пишут в одну запись по очереди, а счётчик отказов сбрасывает тот, кто уже не
|
||
владелец.
|
||
|
||
Довод про два ответа отправителю из требования ушёл вместе с доставкой: обращений
|
||
наружу шаг не делает. Требование от этого не ослабло — порча записи двумя
|
||
пишущими остаётся его предметом целиком.
|
||
|
||
Шаг MUST записывать только те поля, которыми распоряжается сам. Запись он держит
|
||
снимком с момента захвата и до записи — это часы, — и безусловная запись снимка
|
||
стёрла бы всё, что владелец правил в панели за это время: молча, без строки в
|
||
журнале и без отказа в панели. Владелец увидел бы успешное сохранение и был бы
|
||
уверен, что правка на месте. Владелец записи, заголовок, краткое описание и темы
|
||
конвейер MUST не трогать.
|
||
|
||
#### Scenario: Правка владельца пережила сохранение шага
|
||
|
||
- **GIVEN** шаг держит захваченную запись
|
||
- **AND** владелец за это время изменил в панели поле, которого шаг не касается
|
||
- **WHEN** шаг записывает свой результат
|
||
- **THEN** результат шага записан
|
||
- **AND** правка владельца на месте
|
||
|
||
#### Scenario: Захват ушёл под работающим шагом
|
||
|
||
- **GIVEN** шаг работает над захваченной записью
|
||
- **AND** за это время та же запись досталась другому захвату
|
||
- **WHEN** первый шаг доходит до записи результата
|
||
- **THEN** результат не записывается
|
||
|
||
#### Scenario: Человек снял остановку под работающим шагом
|
||
|
||
- **GIVEN** шаг работает над захваченной записью
|
||
- **AND** человек за это время снял с неё признак остановки, освободив захват
|
||
- **AND** запись досталась другому воркеру
|
||
- **WHEN** первый шаг доходит до записи результата
|
||
- **THEN** результат не записывается
|
||
|
||
### Requirement: Число отказов ограничивает повторы шага
|
||
|
||
У аудиозаписи SHALL быть число отказов. Оно MUST расти при каждом захвате и MUST
|
||
возвращаться к нулю, когда шаг завершился без отказа либо отложил работу. Рост
|
||
при захвате, а не при отказе, засчитывает попытку и записи, брошенной на
|
||
середине: шаг, уносящий с собой процесс, до объявления отказа не доходит
|
||
никогда.
|
||
|
||
**Остановка сервиса отказом не считается.** Шаг, прерванный отменой по
|
||
собственной остановке сервиса, MUST возвращать число отказов назад и MUST не
|
||
выносить записи приговора: запись не виновата в том, что нас перезапустили, и
|
||
несколько выкладок подряд иначе останавливают здоровую многочасовую запись с
|
||
приговором «отказы исчерпаны». Всякая другая причина, по которой шаг не дошёл до
|
||
объявления исхода, отказ тратит.
|
||
|
||
Запись, захваченная с числом отказов сверх заданного предела, MUST
|
||
останавливаться признаком тем, кто её захватил, и MUST не отдаваться шагу в
|
||
работу. Остановка эта видна отправителю опросом готовности наравне с прочими —
|
||
норму держит capability `intake`.
|
||
|
||
Этот сторож MUST отвечать только за повторы внутри шага. Время, проведённое
|
||
записью в рубеже, MUST мериться отдельным сторожем: одно число не справляется ни
|
||
с одной из двух обязанностей — опрос, вернувший «ещё в работе», обнуляет его, и
|
||
зависшая чужая операция опрашивается вечно, а не обнулял бы — убивал бы здоровую
|
||
запись.
|
||
|
||
#### Scenario: Запись отказывает на каждой попытке
|
||
|
||
- **GIVEN** шаг конвейера отказывает на каждой попытке
|
||
- **WHEN** запись проходит заданное число отказов
|
||
- **THEN** у неё появляется признак остановки
|
||
- **AND** следующий захват её не выдаёт
|
||
- **AND** опрос готовности отдаёт владельцу записи признак остановки
|
||
|
||
#### Scenario: Шаг уносит процесс, не объявив отказа
|
||
|
||
- **GIVEN** шаг конвейера обрывается вместе с процессом на каждой попытке
|
||
- **WHEN** запись захватывается снова заданное число раз
|
||
- **THEN** у неё появляется признак остановки
|
||
|
||
#### Scenario: Остановка сервиса отказа не тратит
|
||
|
||
- **GIVEN** шаг работает над записью
|
||
- **WHEN** сервис останавливают, и шаг прерывается отменой
|
||
- **THEN** число отказов записи прежнее
|
||
- **AND** признака остановки у записи не появляется
|
||
|
||
#### Scenario: Прошедшая запись отказов не копит
|
||
|
||
- **GIVEN** запись прошла подряд несколько рубежей без единого отказа
|
||
- **WHEN** смотрят её число отказов
|
||
- **THEN** оно не приблизилось к пределу
|
||
|
||
### Requirement: Выборка воркера владельцем не сужается
|
||
|
||
Воркер SHALL брать записи всех владельцев подряд и MUST не учитывать владельца
|
||
при выборе очередной записи.
|
||
|
||
Владелец решает, кому запись показывать, а не кому её считать. Сужение выборки
|
||
владельцем поставило бы записи одних людей в зависимость от того, кто первым
|
||
завёл учётную запись.
|
||
|
||
Оговорка про записи без владельца из требования ушла: заводить их стало нечем —
|
||
колонка владельца пустого значения не принимает, и норму держит capability
|
||
`storage`.
|
||
|
||
Владелец записи MUST переживать работу конвейера: шаг, сохраняющий свой
|
||
результат, владельца не трогает и не затирает.
|
||
|
||
#### Scenario: Записи двух владельцев проходят одним воркером
|
||
|
||
- **GIVEN** заведены записи двух разных владельцев на одном рубеже
|
||
- **WHEN** воркер забирает работу
|
||
- **THEN** ему достаются обе, в порядке заведения
|
||
|
||
#### Scenario: Шаг конвейера владельца не затирает
|
||
|
||
- **GIVEN** запись с владельцем прошла шаг конвейера
|
||
- **WHEN** шаг сохраняет свой результат
|
||
- **THEN** владелец записи остаётся прежним
|
||
|
||
## REMOVED Requirements
|
||
|
||
### Requirement: Недоставленный ответ не роняет шаг
|
||
|
||
**Reason**: Доставка ответа отправителю убрана вместе с входом Telegram, и
|
||
недоставке взяться неоткуда: обращения наружу шаг больше не делает. Обе прежние
|
||
причины недоставки — неподнятый вход отправителя и неназванный адресат записи —
|
||
описывали именно этот вход.
|
||
|
||
**Migration**: Счётчик недоставленных ответов и записи журнала о недоставке
|
||
уходят вместе с требованием; наблюдателю, построившему на них отбор, ждать от
|
||
них значений больше нечего. Исход записи виден опросом готовности и журналом
|
||
событий записи.
|
||
|
||
### Requirement: Всякая остановка сообщает отправителю
|
||
|
||
**Reason**: Обязанность сообщить требовала адресата, а адресатом был чат
|
||
Telegram. С убранным входом сообщать стало нечем и некуда, и обязанность
|
||
переходит к опросу готовности — её держит требование «Конвейер ответа
|
||
отправителю не шлёт» вместе с capability `intake`.
|
||
|
||
**Migration**: Отправитель узнаёт об остановке признаком в ответе опроса
|
||
готовности. Владелец сервиса видит остановку записью журнала и полем причины у
|
||
самой записи — как и прежде.
|