docs: документация переведена на канон av-dev-pm
- беклог и план переехали в docs/tasks (38 задач, 11 целей), слаги переименованы с транслита на английские, 85 ссылок поправлены - conventions.md разобран в docs/conventions/, local-research.md — в docs/research/, review-journal.md — в docs/review.md с разделом настройки конвейера; заведены security.md, adr/ и .pm.json - шаг docs.py check добавлен в task gate; поведение в architecture.md помечено девятью маркерами долга, database.md получил настройки с числовым значением
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
# База данных и идентификаторы
|
||||
|
||||
Схема как таковая — в [database.md](../database.md); здесь только правила, по
|
||||
которым она пишется.
|
||||
|
||||
- Первичные ключи сущностей — **TEXT ULID**, генерируется приложением
|
||||
(`internal/ident`). Сортируется по времени создания, удобен в логах и URL.
|
||||
Разбор внешнего id — `ident.Parse` на входной границе; синтаксически
|
||||
невалидный id — 404 без похода в БД.
|
||||
- Естественный ключ вместо ULID там, где он есть по природе данных: `workout` —
|
||||
по `id` из HealthKit, `record` — по паре `род секции + id` (форму
|
||||
идентификатора у пяти из шести секций живьём никто не видел, и несквозной `id`
|
||||
в двух секциях затёр бы одну запись другой молча).
|
||||
- Новая единица хранения тем же изменением входит в **отпечаток витрины** и в
|
||||
счётчики отчёта пересборки. Отпечаток отвечает «да/нет» за витрину целиком, и
|
||||
единица, которой нет в счётчиках, делает расхождение безадресным: человек
|
||||
видит «не совпало» при неизменившемся числе объектов и принимает по этому
|
||||
необратимое решение о подмене базы.
|
||||
- Правило выбора между двумя версиями одних данных объявляется либо **функцией
|
||||
множества версий**, либо явно **функцией порядка журнала** — третьего
|
||||
состояния нет. «Побеждает последняя пришедшая» третьим состоянием и является:
|
||||
порядок свёртки порядку журнала не равен, и живая витрина расходится с
|
||||
пересборкой молча.
|
||||
- Любое значение из чужого JSON, попадающее в ключ, в лог или в отчёт, имеет
|
||||
названный предел длины (имена непокрытых секций, `id` сущности).
|
||||
- **Колонка, по которой принимается необратимое решение, отличает ноль от «не
|
||||
измерялось».** Миграция, добавляющая такую колонку, не подставляет ноль
|
||||
историческим строкам: ноль означает «проверено, пусто», а не «не знаем», и
|
||||
подстановка выдаёт неизмеренное за измеренное — с видом измерения. Пример:
|
||||
`delivery.skipped_entities`, по которому ретеншен решает, можно ли удалить
|
||||
тело.
|
||||
- **Метка изменения строки меняется только при изменении содержимого.** Апдейт,
|
||||
трогающий одни метаданные (провенанс, ссылки), `updated_at` не двигает — иначе
|
||||
она становится меткой касания, и запрос «что изменилось с момента X» получает
|
||||
столько ложных изменений, сколько раз источник переприслал то же самое (у
|
||||
тренировки — двадцать шесть).
|
||||
- **Новая производная от разбора колонка в момент появления вносится в перечень
|
||||
того, что пересборка не переносит.** Перечень — единственное место, где это
|
||||
сказано, и следующий автор решает по нему; поле, не внесённое туда, однажды
|
||||
перенесут «для полноты учёта», и витрина снова станет функцией предыдущего
|
||||
прогона.
|
||||
- Временные метки — `TEXT` в RFC 3339, **UTC**, суффикс `Z`. Фиксированная
|
||||
ширина сохраняет лексикографическую сортировку = хронологию. Единая точка
|
||||
генерации — `store.Now()`, а не дефолт в схеме: забытая вставка должна
|
||||
падать громко.
|
||||
- Enum-поля — обычный `TEXT` без `CHECK`, допустимые значения держит код.
|
||||
- Миграции — goose (`internal/store/migrations`), SQL для DDL. При изменении
|
||||
структуры обновляем схему в [database.md](../database.md) тем же изменением —
|
||||
это проверяет `task gate`.
|
||||
Reference in New Issue
Block a user