## 1. Опоры в хранилище

- [x] 1.1 Миграция `00006`: частичный индекс
      `delivery (received_at, id) WHERE parse_status = 'pending'`. Комментарий
      объясняет, почему частичный, а не по `parse_status`: в установившемся
      режиме в нём ноль–одна строка, полный хранил бы всю таблицу ради выборки
      из одной.
- [x] 1.2 `store.PendingDeliveries(ctx, after, limit)` — неразобранные доставки
      в порядке `(received_at, id)`, строго после курсора; отдаёт идентификатор
      и `received_at`. Сравнение курсора — **row-value** `(received_at, id) >
      (?, ?)`: развёрнутая форма через `OR` даёт `SCAN` вместо `SEARCH`
      (проверено `EXPLAIN QUERY PLAN`). Нулевой курсор — нулевое время и пустой
      идентификатор, без ветки «первая страница».
- [x] 1.3 `store.CountPendingDeliveries(ctx)` — размер задолженности для
      строки `INFO` при старте.
- [x] 1.4 `store.ErrBusy` — доменный сентинел занятости базы; `inTx` оборачивает
      им исчерпание повторов, чтобы вызывающий не разбирал коды драйвера.
- [x] 1.5 Обновить `docs/database.md`: новый индекс в перечне индексов
      `delivery`.

## 2. Исход свёртки отражает доставку, а не обстоятельства

- [x] 2.1 `fold.fail`: отмена контекста и `store.ErrBusy` статус **не меняют** —
      доставка остаётся `pending`; всё прочее по-прежнему `failed`. Уровень лога
      по адресату: занятость и отмена — `WARN` (пройдёт само), остальное как
      сейчас.
- [x] 2.2 Тест: свёртка на занятой базе оставляет доставку `pending`; свёртка
      непонятого содержимого оставляет `failed`.

## 3. Общий проигрыватель — `internal/replay`

- [x] 3.1 `replay.Player.Play(ctx, deliveryID) (Outcome, error)` — свернуть одну
      доставку и вернуть её исход **значением**: ровно один классовый счётчик
      равен единице, плюс `partial`/`incomparable` у успешной свёртки.
      Классифицируется **только ошибка**; на контекст `Player` не смотрит —
      решение «нас остановили» принимает цикл.
- [x] 3.2 `replay.Outcome` + `(*Outcome).Add(other)`; `replay.Report` встраивает
      `Outcome`, чтобы имена исходов не раздвоились. Существующие вызывающие
      (`cmd/healthlog/reindex*.go`, тесты) читают поля по-прежнему.
- [x] 3.3 `replay.Run` переводится на `Player`, свою ветку отмены оставляет
      себе. Поведение и отчёт не меняются — проверяется существующими тестами
      пакета.
- [x] 3.4 Табличный тест классификатора: ошибка → ожидаемый `Outcome`.

## 4. Воркер свёртки

- [x] 4.1 `replay.Worker` с синхронным швом: `Pass(ctx) (Outcome, error)` — один
      проход, без каналов; `Run(ctx)` — тонкий `select` поверх него по сигналу,
      тику и отмене; `Notify()` — неблокирующая отправка в канал ёмкостью 1;
      `done` закрывается в `defer` внутри `Run`.
- [x] 4.2 `Pass`: выбирать `pending` порциями по курсору, сворачивать через
      `Player`, курсор строго возрастает; отмена проверяется **между**
      доставками; проход конечен даже когда доставка осталась `pending`.
- [x] 4.3 Свёртка внутри прохода идёт на `context.WithoutCancel` от контекста
      прохода плюс собственный дедлайн (`foldTimeout`, переезжает из
      `internal/ingest`).
- [x] 4.4 Отказ выборки — `ERROR` и выход из `Pass`, но не из `Run`: воркер
      переживает временный отказ базы.
- [x] 4.5 Наблюдаемость: `INFO` с размером задолженности перед первым проходом;
      `WARN` «доставка ждала свёртки дольше пяти минут» — считается на выборке
      прохода, включается после того, как первый проход завершился. Ни значений
      точек, ни имён устройств.

## 5. Приём без свёртки

- [x] 5.1 `ingest.Service` теряет зависимость от `fold`: `Accept` пишет тело,
      учитывает доставку, логирует принятие и будит воркер. `foldTimeout` и
      вызов свёртки уходят.
- [x] 5.2 Учёт доставки — на `context.WithoutCancel` с коротким дедлайном:
      обрыв соединения после записи тела не должен оставлять тело без строки в
      журнале. Проверка формы тела остаётся на исходном контексте.
- [x] 5.3 Сигнал воркеру — параметр конструктора функцией; `nil` приводится к
      пустой функции **один раз в конструкторе**, как это уже делают `fold.New`
      и `replay.Run` со своими нулевыми значениями. Проверок на `nil` в местах
      вызова быть не должно.
- [x] 5.4 `internal/httpapi` собирается без `fold`; транспорт по-прежнему не
      логирует исход и переводит только ошибки приёма.

## 6. Жизненный цикл в `serve.go`

- [x] 6.1 Собрать воркер, запустить `Run` в горутине, передать его `Notify` в
      `ingest`, разбудить при старте — этим и делается подбор `pending`.
- [x] 6.2 Остановка: `srv.Shutdown` → отмена контекста воркера → ожидание
      `done` в остатке того же бюджета `shutdownTimeout` (30 с, как
      `stop_grace_period`).
- [x] 6.3 Контекстная ошибка `Shutdown` — `WARN`, а не отказ команды: stdlib
      возвращает `DeadlineExceeded` штатно, а `main` печатает на любой ошибке
      `fatal startup` и выходит с кодом 1.
- [x] 6.4 База не закрывается, пока воркер не вышел: `Close` под живой
      транзакцией свёртки дал бы `ERROR` по доставке, с которой всё в порядке.
      Не уложились — оставляем закрытие процессу и называем это `WARN`.

## 7. Бюджет ответа маршрута приёма

- [x] 7.1 `handleIngest` перед чтением тела ставит дедлайн записи ответа через
      `http.NewResponseController(w).SetWriteDeadline` на `read_timeout +
      write_timeout`. `http.ErrNotSupported` — `DEBUG` и продолжение, а не
      отказ приёма.
- [x] 7.2 Общий `write_timeout` и его умолчание не меняются; комментарий в
      `config.example.toml` объясняет, что он покрывает и чтение тела и потому
      приём держит собственный бюджет.

## 8. Проверки

Приёмочные критерии — рубрика ревью дизайна, перенесена сюда целиком.

- [x] 8.1 **Завершимость прохода.** Доставка, оставшаяся `pending`, не
      выбирается проходом повторно; `Pass` возвращает управление.
- [x] 8.2 **Атомарность единицы работы.** Прерванная свёртка оставляет доставку
      `pending` и не оставляет частично записанных объектов.
- [x] 8.3 **Идемпотентность повтора.** Двойная свёртка той же доставки даёт тот
      же отпечаток витрины.
- [x] 8.4 **Сигнал не теряет работу.** Доставка, чей сигнал потерян, всё равно
      подбирается — тиком или следующим проходом.
- [x] 8.5 **Тотальный порядок.** Доставки с одинаковым `received_at`
      сворачиваются в порядке `id` при любом размере порции; порядок проверяется
      наблюдаемым следствием — наследованием слоя.
- [x] 8.6 **Остановка.** Приём прекращается раньше воркера; после остановки нет
      доставки, числящейся разобранной и записанной наполовину; исчерпание
      бюджета не даёт ненулевого кода возврата.
- [x] 8.7 **Транзиентный отказ ≠ отказ доставки.** Занятость базы оставляет
      `pending`, содержимое даёт `failed` (задача 2.2).
- [x] 8.8 **Отставание наблюдаемо и при отсутствии прогресса.** Метка не
      срабатывает на задолженности первого прохода и срабатывает после него;
      считается на выборке, а не по факту свёртки.
- [x] 8.9 **Одна классификация на оба входа.** Табличный тест `Player` (задача
      3.4) плюс зелёные существующие тесты `replay`.
- [x] 8.10 **Тестируемость без сна.** Ни один тест воркера не ждёт по часам:
      проверки идут через `Pass`, тест на `Run` — один, «отмена завершает цикл».
- [x] 8.11 **Приём не платит за воркер.** После `Accept` доставка числится
      `pending`, обработчик не ждёт; приём с неработающим воркером отвечает
      `200`.
- [x] 8.12 **Данные о здоровье не в логе.** Записи воркера несут только
      идентификатор доставки, счётчики и длительности.
- [x] 8.13 `task gate` зелёный; `task verify:archive` даёт то же состояние.

## 9. Документация

- [x] 9.1 `docs/architecture.md`, раздел «Приём»: ответ отдаётся после архивации
      и учёта — это **контракт**, а не деталь реализации; очередь — таблица, а
      не память; порядок журнала и остановка; классификация «отказ доставки»
      против «отказ обстоятельств»; бюджет ответа маршрута приёма; отвергнутые
      варианты с причинами.
- [x] 9.2 Строка в задачу беклога `stats-endpoint`: длина `pending`,
      возраст самой старой неразобранной доставки и то, что `/healthz` их не
      отражает.

## 10. Правки по ревью кода (профиль `deep`)

- [x] 10.1 `store.CreateDelivery` — через `inTx` с повторами: одиночная вставка
      пересиживала только `busy_timeout`, и приём отвечал `500` по доставке,
      тело которой уже на диске (измерено: окно занятости 6.55 с при широкой
      свёртке против пяти секунд ожидания).
- [x] 10.2 Паника свёртки перехватывается в `fold.Fold` — у той же границы, что
      пишет исход разбора: в фоновой горутине она валила процесс, а
      `restart: unless-stopped` превращал дефект одной доставки в цикл
      перезапуска. Писатель `parse_status` остался единственным.
- [x] 10.3 Флаг «первый проход завершён» снимается только у прохода, дошедшего
      до пустой выборки: взведённый на отказе базы, он включал метку задержки
      после прохода, который ничего не свернул.
- [x] 10.4 Метка отставания — одна запись на проход (число задержанных и худшее
      ожидание), а не запись на доставку: задолженность в сотню тел давала бы
      сотню одинаковых `WARN` каждую минуту.
- [x] 10.5 Отмена не пишется `ERROR`-ом в цикле воркера; ветка отказа `Serve` в
      `serve.go` останавливает воркер прежде, чем закрыть базу.
- [x] 10.6 Значения заголовков обрезаются перед логом: `automation-name` длиной
      600 КБ выдавливал из ротации всю недавнюю историю (измерено).
- [x] 10.7 Запасной `store.Now()` для метки приёма убран: он заводил второй
      источник времени вопреки соседнему комментарию и был недостижим.
- [x] 10.8 `classify` неэкспортируема: вторая публичная дверь возвращала
      `Outcome` без `Partial`, то есть молча занижала счётчик, по которому
      решается судьба тела.
- [x] 10.9 Оракул правила «занятость — обстоятельство»: `task verify:busy` —
      свёртка под удерживаемой блокировкой. В гейт не входит (25 секунд), но
      мутацию «убрать ветку `Transient`» убивает.
- [x] 10.10 Дельты `storage` и `reindex`: «отказ ⇒ `failed`» сужено до отказов
      разбора, добавлен класс «отложено».
- [x] 10.11 Остаточный предел порядка при конкурентных приёмах назван в спеке и
      в `docs/architecture.md`, вынут блокером
      (`docs/tasks/items/journal-order-on-ingest.md`).
