Дозакрыты находки ревью по слиянию сущностей

- Правило покрытия получило второй разряд (условный, как у точек), запрет
  вырождения формы и счёт содержательных элементов ряда: скелет из скаляров и
  ряд из null больше не затирают маршрут. Победитель внутри доставки стал
  функцией множества версий — общим помощником с точками, — а провенанс
  поднимается и при совпавшем хеше, иначе отложенная доставка возвращала витрину
  к прежнему содержимому.
- Одно поле не того типа больше не уносит сущность, а пропуски видны в учётной
  записи доставки (миграция 00008, NULL = «не измерялось»); каноническая форма
  считается один раз и вне транзакции; откат бинаря поверх новой схемы отказывает
  на старте; текст ошибки разбора не несёт значений из тела.
- Ревью кода профилем deep (девять проходов) нашло две регрессии и обе закрыты:
  безусловный второй разряд запирал законный досчёт навсегда, а выбор победителя
  был квадратичен по числу присланных версий одного ключа.
This commit is contained in:
av
2026-08-02 16:38:18 +03:00
parent 51a5272c96
commit 8331328134
52 changed files with 4921 additions and 481 deletions
+8 -2
View File
@@ -28,8 +28,14 @@ func writeReport(w io.Writer, r report) {
p(" свёрнуто: %d; отказов: слой не выведен %d, содержимое %d, прочее %d, отложено %d",
r.replay.Folded, r.replay.FailedLayer, r.replay.FailedMalformed, r.replay.FailedOther,
r.replay.Deferred)
p(" слияние: частично разобрано %d, несравнимых наборов %d",
r.replay.Partial, r.replay.Incomparable)
// Удержанные версии сущностей печатаются ВСЕГДА, а не только при ненулевом
// значении: ноль здесь утверждение, а не отсутствие новостей. Отпечаток это
// правило не проверяет по построению — живой приём и пересборка пользуются
// одним правилом и одинаково сойдутся на одинаково удержанной версии, — так
// что счётчик и есть единственный способ увидеть, что правило слияния
// сущностей стало слишком строгим.
p(" слияние: частично разобрано %d, несравнимых наборов %d, удержано версий сущностей %d, версий одного ключа в одном теле %d",
r.replay.Partial, r.replay.Incomparable, r.replay.EntitiesHeld, r.replay.EntitiesDiverging)
if r.replay.Canceled {
// Ни отпечаток пересобранной витрины, ни число доставок после прогона при
+25
View File
@@ -2,6 +2,7 @@ package main
import (
"bytes"
"fmt"
"os"
"path/filepath"
"strings"
@@ -312,3 +313,27 @@ func TestФайлНазначенияНеМожетБытьПутёмОтсут
t.Error("--force позволил собрать витрину прямо на место рабочей базы")
}
}
// Число удержанных версий сущностей печатается ВСЕГДА, включая ноль: здесь ноль
// это утверждение, а не отсутствие новостей. Отпечаток правило слияния
// сущностей не проверяет по построению — живой приём и пересборка пользуются
// одним правилом и одинаково сойдутся на одинаково удержанной версии, — так что
// строка отчёта и есть единственный способ увидеть, что правило стало слишком
// строгим. Без этого теста её можно удалить, и гейт останется зелёным.
func TestОтчётВсегдаНазываетУдержанныеВерсии(t *testing.T) {
t.Parallel()
for _, held := range []int{0, 3} {
var buf bytes.Buffer
writeReport(&buf, report{replay: replay.Report{
Outcome: replay.Outcome{EntitiesHeld: held, EntitiesDiverging: held + 1},
}})
out := buf.String()
if !strings.Contains(out, fmt.Sprintf("удержано версий сущностей %d", held)) {
t.Errorf("при удержаниях %d строки в отчёте нет:\n%s", held, out)
}
if !strings.Contains(out, fmt.Sprintf("версий одного ключа в одном теле %d", held+1)) {
t.Errorf("второй счётчик не напечатан:\n%s", out)
}
}
}