заведён реестр префиксов, правила канона перенумерованы

- идентификатор правила теперь `<ПРЕФИКС>-<номер>` вместо `R<номер>`:
  префикс уникален по всему канону, поэтому ссылка больше не требует пути
  к файлу и не зависит от того, на какой оси файл лежит
- префикс выбирается под файл, а не выводится по формуле, и хранится в
  conventions/prefixes.toml вместе с выбывшими; номера сохранены один в
  один вместе с дырами
This commit is contained in:
av
2026-07-25 20:55:37 +03:00
parent 972627f641
commit dcdd92230b
13 changed files with 552 additions and 470 deletions
+31 -26
View File
@@ -1,3 +1,7 @@
---
prefix: TIME
---
# Время
Как приложение записывает моменты и длительности: в каком формате, откуда
@@ -14,7 +18,7 @@
## Правила
### R1. Единый формат — RFC 3339, UTC, суффикс `Z`
### TIME-1. Единый формат — RFC 3339, UTC, суффикс `Z`
**ДОЛЖЕН.** Момент времени записывается как `2026-06-28T11:23:45Z`
одинаково в хранении, логах, API и обмене с внешними системами.
@@ -25,7 +29,7 @@
совпадать с тем, которое подразумевалось при написании кода. UTC с явным `Z`
убирает из данных и смещение, и сам вопрос «в какой зоне это записано».
### R2. Ширина строки фиксируется на каждый носитель
### TIME-2. Ширина строки фиксируется на каждый носитель
**ДОЛЖЕН.** Внутри одной колонки БД и внутри одного потока логов длина
строки времени одна и от записи к записи не плавает.
@@ -38,18 +42,18 @@
везде, а только на тех парах записей, где дробная часть оказалась короче, —
то есть редко, выборочно и невоспроизводимо.
### R3. Точность разных носителей может различаться
### TIME-3. Точность разных носителей может различаться
**ДОПУСКАЕТСЯ.** У колонки БД и у потока логов каждая своя точность.
**Почему.** Квантор в R2 — на носитель, а не на приложение, потому что
**Почему.** Квантор в TIME-2 — на носитель, а не на приложение, потому что
строки разных носителей между собой не сравниваются: сортировка идёт внутри
колонки, чтение — внутри потока. Явное разрешение нужно, чтобы R2 не читался
колонки, чтение — внутри потока. Явное разрешение нужно, чтобы TIME-2 не читался
как «одна точность на всё приложение»: от подгонки формата логов под формат
колонки ни одна пара строк не становится сравнимой, зато точность режется до
худшего из носителей.
### R4. Локальное время не хранится и не передаётся
### TIME-4. Локальное время не хранится и не передаётся
**НЕ ДОЛЖЕН.** Ни в базе, ни в логах, ни в JSON API нет меток в локальной
зоне.
@@ -60,44 +64,45 @@
разберёт час перехода на зимнее время: этот час идёт дважды, две записи
получают одинаковую метку, и порядок между ними не восстанавливается ничем.
### R13. Чужой вход нормализуется при разборе, а не отклоняется
### TIME-13. Чужой вход нормализуется при разборе, а не отклоняется
**ДОЛЖЕН.** Валидное по RFC 3339 значение с офсетом, отличным от `Z`, или с
долями секунды принимается от внешней системы и приводится к каноническому
виду (R1) в точке разбора (R5).
виду (TIME-1) в точке разбора (TIME-5).
**Почему.** Канонический вид — обязательство нашего писателя, а не
контракт, наложенный на внешние системы: `…14:23:45+03:00` называет тот же
момент, что `…11:23:45Z`, и отклонять его — значит ломать интеграцию со
стороной, которая стандарт соблюла. Пропущенное же как есть, такое значение
нарушает форму и ширину носителя (R1, R2) и портит сортировку выборочно —
только на записях, пришедших извне, и далеко от места разбора. Нормализация
нарушает форму и ширину носителя (TIME-1, TIME-2) и портит сортировку
выборочно — только на записях, пришедших извне, и далеко от места разбора.
Нормализация
в единой точке разбора оставляет ровно одно место, где неканонический вид
существует, — по ту сторону границы его уже нет.
### R5. Единая точка получения «сейчас», форматирования и разбора
### TIME-5. Единая точка получения «сейчас», форматирования и разбора
**ДОЛЖЕН.** Один модуль отдаёт текущий момент, он же форматирует и разбирает
метки; прямые вызовы часов по коду не разбросаны.
**Почему.** Формат, зона (R1) и ширина (R2) обязаны выполняться для всех
**Почему.** Формат, зона (TIME-1) и ширина (TIME-2) обязаны выполняться для всех
меток без исключения, а каждый прямой вызов часов заводит ещё одно место,
где их можно не соблюсти. Промах такого вызова проявляется не в коде, а в
данных, и обнаруживается, когда испорченных записей уже накопилось.
Соображение то же, что для идентификаторов (`arch/db-identifiers.md R3`).
Соображение то же, что для идентификаторов (`arch/db-identifiers.md TIME-3`).
### R6. Дефолтов времени в схеме БД нет
### TIME-6. Дефолтов времени в схеме БД нет
**НЕ ДОЛЖЕН.** Колонки времени не имеют `DEFAULT` с текущим моментом.
**Почему.** Дефолт превращает забытую вставку `created_at` в тихо работающий
код: значение появляется, но приходит от сервера БД — то есть с других часов
и в формате, который выбирала не единая точка (R5). Без дефолта та же ошибка
и в формате, который выбирала не единая точка (TIME-5). Без дефолта та же ошибка
падает громко и чинится в момент написания, а не при разборе расхождения
между временем в записи и временем в логе. Правило то же, что для
идентификаторов (`arch/db-identifiers.md R2`).
идентификаторов (`arch/db-identifiers.md TIME-2`).
### R7. Длительность — отдельная величина, а не пара меток
### TIME-7. Длительность — отдельная величина, а не пара меток
**ДОЛЖЕН.** Длительность операции записывается числом (обычно
миллисекундами) в поле вида `duration_ms`.
@@ -107,9 +112,9 @@
образуют операцию, и вычитать их самому — в запросе, в дашборде и глазами в
логе; число сравнивается, агрегируется и попадает в перцентили без этого
шага. Кроме того, разность сохранённых меток считается по стенным часам и
наследует их дефект (R9).
наследует их дефект (TIME-9).
### R8. Длительность засекает слой, который делает вызов
### TIME-8. Длительность засекает слой, который делает вызов
**СЛЕДУЕТ.** Замер живёт там же, где вызов, границы которого он измеряет.
@@ -118,14 +123,14 @@
вызова. В обоих случаях число остаётся правдоподобным и потому не
оспаривается, хотя отвечает не на тот вопрос, который к нему задают.
### R9. Момент и интервал берутся с разных часов
### TIME-9. Момент и интервал берутся с разных часов
**ДОЛЖЕН.** Источник зависит от того, что записывается:
| № | Величина | Источник |
|---|---|---|
| R9.1 | момент события | стенные часы через единую точку (R5) |
| R9.2 | длительность операции | монотонные часы процесса |
| TIME-9.1 | момент события | стенные часы через единую точку (TIME-5) |
| TIME-9.2 | длительность операции | монотонные часы процесса |
**Почему.** Стенные часы подводит NTP: они могут шагнуть назад, и тогда
интервал, посчитанный вычитанием, выйдет отрицательным, а при шаге вперёд —
@@ -135,7 +140,7 @@
упустить: источник меток времени и источник интервалов — разные, даже если
оба называются «часы».
### R10. Не-UTC существует только на слое отображения
### TIME-10. Не-UTC существует только на слое отображения
**ДОЛЖЕН.** Преобразование в зону пользователя происходит при выводе и не
проникает в хранение, сортировку и логи.
@@ -147,7 +152,7 @@
смещение удваивается, результат остаётся похожим на правду, а найти
виновный слой можно только перечитав их все.
### R11. Зона отображения берётся из конфигурации, по умолчанию `UTC`
### TIME-11. Зона отображения берётся из конфигурации, по умолчанию `UTC`
**ДОЛЖЕН.** Значение приходит из конфигурации (`arch/config.md`), значение
по умолчанию — `UTC`.
@@ -158,7 +163,7 @@
тем, что лежит в базе и в логах, поэтому несовпадение с ожиданиями читается
как «зону не задали», а не как «где-то потерялось смещение».
### R12. В календарных вычислениях зона указывается явно
### TIME-12. В календарных вычислениях зона указывается явно
**ДОЛЖЕН.** «Сегодня», «за месяц» и прочие календарные границы считаются с
явно переданной зоной, а не с системной зоной процесса.
@@ -168,7 +173,7 @@
расхождение не воспроизводится там, где его заметили, и объясняется средой,
а не кодом. Явно переданная зона делает результат функцией от аргументов.
Зона по умолчанию здесь та же, что и для отображения (R11); календарная
Зона по умолчанию здесь та же, что и для отображения (TIME-11); календарная
логика, которой нужна другая, получает её тем же явным аргументом.
<!-- local:механизировано -->