Files
healthlog/internal/store/errors.go
T
av 8331328134 Дозакрыты находки ревью по слиянию сущностей
- Правило покрытия получило второй разряд (условный, как у точек), запрет
  вырождения формы и счёт содержательных элементов ряда: скелет из скаляров и
  ряд из null больше не затирают маршрут. Победитель внутри доставки стал
  функцией множества версий — общим помощником с точками, — а провенанс
  поднимается и при совпавшем хеше, иначе отложенная доставка возвращала витрину
  к прежнему содержимому.
- Одно поле не того типа больше не уносит сущность, а пропуски видны в учётной
  записи доставки (миграция 00008, NULL = «не измерялось»); каноническая форма
  считается один раз и вне транзакции; откат бинаря поверх новой схемы отказывает
  на старте; текст ошибки разбора не несёт значений из тела.
- Ревью кода профилем deep (девять проходов) нашло две регрессии и обе закрыты:
  безусловный второй разряд запирал законный досчёт навсегда, а выбор победителя
  был квадратичен по числу присланных версий одного ключа.
2026-08-02 16:38:18 +03:00

48 lines
3.2 KiB
Go

package store
import (
"context"
"errors"
)
// ErrNotFound — записи нет. Граничную ошибку драйвера (sql.ErrNoRows)
// транслируем в доменную здесь же, у источника, чтобы выше по коду не торчал
// database/sql.
var ErrNotFound = errors.New("запись не найдена")
// errNoCandidates — выбор победителя позван на пустом множестве. Нарушенный
// инвариант вызывающего, а не свойство данных: множество собирается из карты и
// пустым быть не может. Ошибкой, а не паникой, потому что путь проходит внутри
// свёртки принятой доставки — отказ обязан быть диагностируемым, а не «index
// out of range» в стеке фоновой горутины.
var errNoCandidates = errors.New("выбор победителя на пустом множестве версий")
// ErrBusy — база занята, и повторы транзакции этого не пересидели.
//
// Доменная ошибка, а не код драйвера: на неё ветвится свёртка. Отказ по
// занятости не является свойством доставки — работа просто не сделана, и
// доставка обязана остаться в очереди. Без этого различения конкуренция за
// базу выводила бы доставку из очереди навсегда.
var ErrBusy = errors.New("база занята")
// Transient отвечает, вызван ли отказ ОБСТОЯТЕЛЬСТВАМИ, а не данными.
//
// Ровно два случая: работу прекратили снаружи и база оказалась занята дольше,
// чем длятся повторы транзакции. Оба означают «не сделано», а не «не выходит»,
// поэтому работа обязана остаться к повторению.
//
// Определение живёт здесь, в одном месте, и его читают двое: тот, кто пишет
// исход разбора доставки, и тот, кто классифицирует этот исход в счётчики. Две
// копии правила разошлись бы, и доставка одновременно осталась бы в очереди и
// числилась отказавшей.
//
// Дедлайн самой операции сюда НЕ входит: не уложившаяся в бюджет работа не
// уложится в него и в следующий раз, а бесконечный повтор заведомо
// безнадёжного — это очередь, которая не движется.
func Transient(err error) bool {
if errors.Is(err, context.DeadlineExceeded) {
return false
}
return errors.Is(err, context.Canceled) || errors.Is(err, ErrBusy)
}