доменные ошибки сравниваются через errors.As, отказ Close не теряется
- признаки «работы нет» и «задача не найдена» узнаются по смыслу, а не приведением типа: обёртка `%w` на пути больше не превращает пустой прогон воркера в отказ раз в секунду - отказ закрытия соединения с распознавателем доходит до вызывающего (`errors.Join`) либо до журнала; у `errcheck` включён `check-blank`, иначе критерий принимал реализацию, выбрасывающую отказ в пустоту - заведены первые тесты пакета worker и capability `pipeline`; долг из четырёх замечаний линтера закрыт, гейт зелёный целиком
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
# 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** записи об отказе в журнале нет
|
||||
|
||||
Reference in New Issue
Block a user