- признаки «работы нет» и «задача не найдена» узнаются по смыслу, а не приведением типа: обёртка `%w` на пути больше не превращает пустой прогон воркера в отказ раз в секунду - отказ закрытия соединения с распознавателем доходит до вызывающего (`errors.Join`) либо до журнала; у `errcheck` включён `check-blank`, иначе критерий принимал реализацию, выбрасывающую отказ в пустоту - заведены первые тесты пакета worker и capability `pipeline`; долг из четырёх замечаний линтера закрыт, гейт зелёный целиком
79 lines
6.2 KiB
Markdown
79 lines
6.2 KiB
Markdown
# 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** записи об отказе в журнале нет
|
|
|