store: при равной полноте точек побеждает пришедшая доставка

- байтовый порядок канонических форм остался тай-брейком только внутри одной
  доставки: на живом корпусе он решал 98,8% спорных координат и системно хранил
  меньшее значение, из-за чего step_count терял род и verify:archive был красным
- правило перестало быть коммутативным осознанно, поэтому порядок свёртки
  приведён к журнальному: проход воркера прекращается на отложенной доставке,
  а свёртка вне порядка журнала пишет WARN
- заведены счётчики PointsHeld и PointsErased — удержание полнотой и
  единственное направление, в котором правило теряет содержание
This commit is contained in:
av
2026-08-04 11:16:24 +03:00
parent ae607f1ceb
commit b278501a6e
44 changed files with 3780 additions and 172 deletions
+43
View File
@@ -5,6 +5,7 @@ import (
"log/slog"
"os"
"path/filepath"
"regexp"
"strings"
"testing"
"time"
@@ -514,3 +515,45 @@ func TestFoldОтказРазбораНеПодделываетСчётчикП
t.Errorf("отказ разбора записал %d пропусков как измерение", *after.SkippedEntities)
}
}
// Удержание точки правилом полноты доезжает до итога свёртки и до лога:
// счётчик отдельный от перезаписей, координаты — без значений.
//
// Тела строятся из РЕАЛЬНОГО пакета: у второй доставки из каждой точки убран
// `qty`. Так получается ровно тот вход, на котором пришедшая точка теряет
// содержание сохранённой, — единственный случай, когда под новым тай-брейком
// она вообще проигрывает.
func TestFoldУдержаниеТочкиСчитаетсяОтдельно(t *testing.T) {
t.Parallel()
f, arch, st := newFold(t)
ctx := context.Background()
rich := fixture(t, "minute.json")
poor := regexp.MustCompile(`"qty"\s*:\s*[-0-9.eE+]+\s*,\s*`).ReplaceAll(rich, nil)
if len(poor) == len(rich) {
t.Fatal("фикстура не содержит qty — обеднить тело нечем")
}
deliver(t, arch, st, "d1", "Minutes", "auto-1", rich)
if _, err := f.Fold(ctx, "d1"); err != nil {
t.Fatalf("первая свёртка: %v", err)
}
deliver(t, arch, st, "d2", "Minutes", "auto-1", poor)
stats, err := f.Fold(ctx, "d2")
if err != nil {
t.Fatalf("вторая свёртка: %v", err)
}
if stats.PointsHeld == 0 {
t.Fatalf("удержаний 0: обеднённая точка вытеснила измерение, %+v", stats.MergeStats)
}
if len(stats.PointsHeldAt) == 0 {
t.Error("координат удержания нет: счётчик без адреса неразбираем")
}
if stats.PointsHeld > stats.Overwrites {
t.Errorf("удержаний %d при %d перезаписях: удержание — частный случай столкновения",
stats.PointsHeld, stats.Overwrites)
}
}