свёртка доставки в объекты и сшивка с приёмом

- internal/fold — свёртка по идентификатору доставки, тело из архива: тот же
  код, каким пойдёт пересборка витрины
- приём зовёт свёртку на context.WithoutCancel с собственным дедлайном;
  исход разбора на код ответа не влияет
- слой доставки хранится в delivery.derived_layer и наследуется от
  ПРЕДШЕСТВУЮЩЕЙ доставки автоматизации: без границы по времени свёртка
  переставала быть функцией от префикса журнала (1737 объектов против 1742)
- task verify:archive — сходимость на живом архиве, 99 доставок из 99
This commit is contained in:
av
2026-08-01 18:03:59 +03:00
parent c9ddf0b4dd
commit 32f21044e4
17 changed files with 898 additions and 36 deletions
+25 -3
View File
@@ -11,8 +11,10 @@ import (
"errors"
"fmt"
"log/slog"
"time"
"git.vakhrushev.me/av/healthlog/internal/archive"
"git.vakhrushev.me/av/healthlog/internal/fold"
"git.vakhrushev.me/av/healthlog/internal/ident"
"git.vakhrushev.me/av/healthlog/internal/store"
)
@@ -46,16 +48,25 @@ type Result struct {
RawPath string
}
// Service принимает пакеты: сохраняет тело в архив и учитывает доставку.
// foldTimeout — сколько отводится свёртке принятой доставки.
//
// Свёртка идёт на контексте, отвязанном от запроса, поэтому собственный
// дедлайн обязателен: без него зависшая запись держала бы горутину до конца
// жизни процесса.
const foldTimeout = 2 * time.Minute
// Service принимает пакеты: сохраняет тело в архив, учитывает доставку и
// запускает её свёртку.
type Service struct {
arch *archive.Archive
store *store.Store
fold *fold.Service
log *slog.Logger
}
// New собирает use-case приёма.
func New(arch *archive.Archive, st *store.Store, log *slog.Logger) *Service {
return &Service{arch: arch, store: st, log: log.With("capability", "ingest")}
func New(arch *archive.Archive, st *store.Store, f *fold.Service, log *slog.Logger) *Service {
return &Service{arch: arch, store: st, fold: f, log: log.With("capability", "ingest")}
}
// Accept принимает тело пакета: проверяет форму, кладёт в сырой архив и
@@ -117,6 +128,17 @@ func (s *Service) Accept(ctx context.Context, body []byte, meta Meta) (Result, e
"aggregation", meta.Aggregation,
"period", meta.Period)
// Свёртка идёт после того, как доставка учтена, и на контексте, ОТВЯЗАННОМ
// от запроса: обрыв соединения клиентом или прокси на середине оставил бы
// часть объектов записанной, а доставку — со статусом, по которому её
// никто не подберёт. Исход свёртки на код ответа не влияет — сохранили
// значит приняли.
foldCtx, cancel := context.WithTimeout(context.WithoutCancel(ctx), foldTimeout)
defer cancel()
// Ошибку не возвращаем: она уже записана в лог и в parse_status свёрткой,
// а доставка принята.
_, _ = s.fold.Fold(foldCtx, res.DeliveryID)
return res, nil
}