## Why Свёртка выполняется **внутри обработчика запроса**, поэтому время ответа равно времени свёртки: 16 тысяч точек — 11 секунд. `WriteTimeout` в Go ставится в `readRequest`, то есть до вызова обработчика, и его 30 секунд — общий бюджет на всё: дочитать тело по мобильной сети, записать архив, вставить строку, свернуть. Когда бюджет выходит, сервер считает, что отдал `200` (ошибки записи обработчику не видно, ответ ушёл в буфер), клиент получает обрыв, а `accessLog` пишет `status_code=200` — единственный канал наблюдаемости в этом сценарии врёт. Бьёт это по **широким проходам** (`Today`, `Previous 7 Days`, ручной экспорт) — ровно по тем, ради которых заведён инвариант «дыры закрываются сами». ## What Changes - Приём отвечает `200` **после архивации тела и вставки строки `delivery`**. Свёртка из обработчика уходит: время ответа перестаёт зависеть от ширины доставки. - Свёртку ведёт **фоновый воркер** — одна горутина, обработка в порядке журнала (`received_at`, `id`) среди доставок, видимых ему на момент выборки. - **Очередью служит сама таблица**, а не список идентификаторов в памяти: доставка ждёт свёртки в статусе `pending`, канал несёт только сигнал «есть работа». Отсюда три следствия: переполнять нечего (доставка и так `pending`, это не отказ), падение процесса очередь не теряет, а «подбор `pending` при старте» перестаёт быть отдельным кодом — это обычный проход воркера. - Классификация исхода свёртки (`folded` / слой не выведен / содержимое не разобрано / прочее / частичный разбор) становится **общей с пересборкой**: один проигрыватель в `internal/replay`, а не второй рядом. - **Исход свёртки начинает отражать доставку, а не обстоятельства.** Сегодня `failed` пишется на любой ошибке, включая занятость базы; с воркером это означало бы, что доставка выбывает из очереди навсегда — а конкуренция за базу как раз становится штатной. Отмена и занятость статус больше не меняют, доставка остаётся `pending`; всё прочее по-прежнему `failed` и возвращается только пересборкой. - Остановка сервиса формулируется **инвариантом**, а не обещанием досчитать: приём прекращается раньше воркера, и после остановки нет доставки, которая числится разобранной, а записана наполовину. Свёртка идёт на контексте, отвязанном от остановки. - Наблюдаемость воркера: `WARN`, когда доставка ждала свёртки дольше периода быстрого прохода, и одна строка `INFO` о размере задолженности при старте. Метка считается на выборке прохода, а сам проход будит не только сигнал, но и тик — иначе «работа есть, прогресса нет» неотличимо от пустого потока. Счётчики в `/stats` — задача `stats-nablyudaemost`, здесь только метки в логе. - Убирается второй, оставшийся источник молчаливого обрыва: общий `write_timeout` (30 с) меньше `read_timeout` (5 мин), а он покрывает и чтение тела — то есть медленная загрузка 64 МиБ обрывается независимо от свёртки. Длинный бюджет даётся **маршруту приёма** собственным дедлайном ответа; общий таймаут и конфиг не меняются, и прочие маршруты защиту не теряют. - Миграция: частичный индекс по неразобранным доставкам — воркер спрашивает их чаще, чем раз в минуту, а таблица растёт на ~300 строк в сутки. ## Capabilities ### New Capabilities - `ingest`: приём доставки как самостоятельное поведение — что делает ответ `200` заслуженным, когда он отдаётся, кто и в каком порядке сворачивает принятое, что происходит с несвёрнутым при остановке и рестарте. ### Modified Capabilities - `parsing`: требование «Разбор не влияет на код ответа приёма» уточняется — разбор идёт **после** ответа, поэтому исход становится виден не в ответе и не сразу, а асинхронно, в `parse_status` и в логе. - `storage`: требование «Учёт частично разобранной доставки» уточняется — отказ обстоятельств (отмена, занятость базы) статуса не меняет вовсе, а прежняя формулировка «ошибка ⇒ `failed`» этого не допускала. - `reindex`: требование «Отчёт, оракул и исход команды» получает четвёртый класс отказа — «работа отложена по обстоятельствам»: классы у пересборки и у фоновой свёртки общие. ## Impact - `internal/ingest` — теряет зависимость от `internal/fold`: `Accept` кладёт тело, учитывает доставку и будит воркер. - `internal/replay` — общий проигрыватель (свернуть доставку, классифицировать исход) и фоновый воркер поверх него. - `internal/store` — выборка неразобранных доставок в порядке журнала с курсором, сентинел занятости базы; миграция `00006` с частичным индексом. - `internal/fold` — отмена и занятость базы больше не переводят доставку в `failed`. - `cmd/healthlog/serve.go` — жизненный цикл воркера и согласованная остановка. - `internal/httpapi` — сборка `ingest.Service` без свёртки; собственный дедлайн ответа на маршруте приёма. `config.example.toml` — комментарий к `write_timeout`. - `docs/architecture.md`, `docs/database.md` — путь приёма и новый индекс.