- убраны клиент бота, транспорт обновлений, отправитель сообщений, сборка входа при старте, секция настроек и зависимость go-telegram-bot-api; из конвейера ушла доставка ответа отправителю — исход виден опросом готовности. Колонки адресата и значение источника остались в схеме: применённые шаги не переписываются - шаг 202608140003 запрещает пустого владельца у аудиозаписи и у файла; существующие строки он не проверяет, и это принято сознательно — искать их надо запросом до выкладки - ревью нашло два пред-существующих дефекта, оба закрыты: пустой второй ответ распознавателя стирал сохранённую расшифровку, а пустая расшифровка перестала быть заметной вместе с убранной доставкой. Попутно поднят golang.org/x/image до v0.45.0 — красный шаг vulns, воспроизводился и на чистом master
19 KiB
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: Отправитель узнаёт об остановке признаком в ответе опроса готовности. Владелец сервиса видит остановку записью журнала и полем причины у самой записи — как и прежде.