- признаки «работы нет» и «задача не найдена» узнаются по смыслу, а не приведением типа: обёртка `%w` на пути больше не превращает пустой прогон воркера в отказ раз в секунду - отказ закрытия соединения с распознавателем доходит до вызывающего (`errors.Join`) либо до журнала; у `errcheck` включён `check-blank`, иначе критерий принимал реализацию, выбрасывающую отказ в пустоту - заведены первые тесты пакета worker и capability `pipeline`; долг из четырёх замечаний линтера закрыт, гейт зелёный целиком
6.2 KiB
pipeline Specification
Purpose
Конвейер расшифровки: как задача движется по состояниям, что делает воркер, когда работы нет, и что считается отказом шага.
Описан пока только пустой прогон воркера — тот, что нормируют проверки
пакета internal/controller/worker и перевод признака в internal/service.
Сознательно не описаны переходы состояний и цепочка created → converted → transcribe → done | failed, захват задачи и срок его протухания, отмена
контекста посреди шага, освобождение ресурсов внешних клиентов. Это не значит,
что такого поведения нет: оно живёт в коде, а требования на него не написаны,
потому что требование без проверки — предположение, а не норма. Первая задача,
которая трогает любое из перечисленного, дописывает его сюда.
Requirements
Requirement: Пустой прогон воркера — не отказ
Воркер SHALL отличать «работы в этом состоянии сейчас нет» от отказа шага. На пустом прогоне он MUST не считать прогон отказом: не увеличивать счётчик работы и не писать о нём на уровне владельца сервиса. Признак пустого прогона MUST узнаваться по смыслу значения, а не по его точной форме, и MUST переживать пояснения, добавленные к этому значению на любом промежуточном шаге пути.
Требование стоит на инварианте проекта «NoopJobError — не ошибка»: три воркера
опрашивают базу раз в секунду, и пустой прогон, принятый за отказ, даёт три
записи отказа в секунду и столько же засчитанных сбоев, которых не было.
Признак пустого прогона MUST рождаться только ответом хранилища на опрос этим же шагом. Слой, придающий отказу собственный смысл, MUST не сохранять чужой признак в цепочке своей ошибки. Воркер узнаёт признак по смыслу на любой глубине, поэтому отказ, к которому признак примешался, тоже зачёл бы пустым прогоном: задача осталась бы в своём состоянии и переопрашивалась раз в секунду без единой записи — ровно то, что запрещает инвариант «Принятая запись не теряется молча».
Отказ шага, наоборот, MUST быть виден владельцу сервиса записью в журнале и MUST быть засчитан в счётчик работы с пометкой отказа.
Сколько раз он записывается и каким уровнем — это требование не нормирует, и
умолчанием тут считать нечего. Сегодня один отказ даёт две записи: пишет шаг
конвейера и следом воркер, — а уровень стоит ERROR там, где конвенция просит
WARN для повторяющегося сбоя фонового цикла. И то и другое записано долгом в
docs/conventions/logging.md, раздел «Ошибки», строкой «Расхождение, и оно
системное». Долгом оно и остаётся: требование, объявившее одиночную запись
нормой, сделало бы недостижимое обязательным, а требование, объявившее нормой
двойную, — закрыло бы долг контрактом. Задача, которая возьмётся за этот долг,
дописывает норму сюда.
Scenario: Работы в состоянии нет
- GIVEN ни одной задачи в опрашиваемом состоянии нет
- WHEN воркер делает свой прогон
- THEN на уровне владельца сервиса об этом прогоне не пишется ничего
- AND счётчик работы воркера не растёт
Scenario: Признак пустого прогона дошёл с пояснением
- GIVEN работы в опрашиваемом состоянии нет
- AND промежуточный шаг добавил к этому признаку своё пояснение
- WHEN воркер делает свой прогон
- THEN прогон по-прежнему считается пустым: счётчик не растёт, записи на уровне владельца нет
Scenario: Шаг отказал
- GIVEN шаг конвейера вернул отказ
- WHEN воркер завершает прогон
- THEN отказ виден владельцу сервиса записью в журнале
- AND счётчик работы воркера растёт с пометкой отказа
Scenario: Шаг сделал работу
- GIVEN шаг конвейера отработал задачу без отказа
- WHEN воркер завершает прогон
- THEN счётчик работы воркера растёт с пометкой успеха
- AND записи об отказе в журнале нет