ссылки между конвенциями переписаны на темы и идентификаторы

- 41 ссылка вида `lang/go/logging.md` заменена на «конвенция `logging`»,
  идентификатор правила или «базовый слой» для своей же темы
- MIGR-8 больше не отсылает за форматом меток времени, а называет его;
  MIGR-11 перенёс ссылку на KEYS-1/KEYS-2 из нормы в «Почему»
This commit is contained in:
av
2026-07-25 21:12:26 +03:00
parent 421374c4a2
commit 2ed568bad1
11 changed files with 54 additions and 53 deletions
+2 -2
View File
@@ -293,9 +293,9 @@ prefix: CONF
## Связано
- `arch/time.md` — формат времени; зона отображения — единственный
- конвенция `time` — формат времени; зона отображения — единственный
конфигурируемый параметр времени, семантика описана там.
- `arch/app-directories.md` — конфиг лежит в категории «конфигурация» и
- конвенция `app-directories` — конфиг лежит в категории «конфигурация» и
доступен приложению только на чтение.
<!-- local:связано -->
+1 -1
View File
@@ -136,7 +136,7 @@ KEYS-1 требует **сортируемый** строковый иденти
## Связано
- `arch/time.md` — метки времени тоже генерирует приложение, а не схема.
- конвенция `time` — метки времени тоже генерирует приложение, а не схема.
<!-- local:связано -->
<!-- /local -->
+5 -4
View File
@@ -154,8 +154,8 @@ prefix: TIME
### TIME-11. Зона отображения берётся из конфигурации, по умолчанию `UTC`
**ДОЛЖЕН.** Значение приходит из конфигурации (`arch/config.md`), значение
по умолчанию — `UTC`.
**ДОЛЖЕН.** Значение приходит из конфигурации, значение по умолчанию —
`UTC`.
**Почему.** Зашитая в код зона превращает переезд или второго пользователя в
другом поясе в правку кода и релиз. Значение по умолчанию `UTC` выбрано
@@ -184,8 +184,9 @@ prefix: TIME
## Связано
- `arch/config.md` — где задаётся зона отображения.
- `arch/db-identifiers.md` — то же правило «генерирует приложение» для id.
- конвенция `config` — где задаётся зона отображения.
- конвенция `db-identifiers` — то же правило «генерирует приложение» для
идентификаторов.
<!-- local:связано -->
<!-- /local -->
+3 -3
View File
@@ -5,7 +5,7 @@ extends: arch/config.md
# Конфигурация: реализация на Go
Как `arch/config.md` выглядит в Go-приложении: формат, загрузчик, границы
Как базовый слой выглядит в Go-приложении: формат, загрузчик, границы
запрета на окружение. Форма записи — `LANGUAGE.md`.
Секретов Go-специфика не касается: они приходят из деплоя уже в файле, а
@@ -224,8 +224,8 @@ zoneinfo, а сообщение указывает не на ту причину
## Связано
- `lang/go/time.md` — зона отображения и формат времени.
- `lang/go/logging.md``slog`, которым падает невалидный конфиг.
- конвенция `time` — зона отображения и формат времени.
- конвенция `logging``slog`, которым падает невалидный конфиг.
<!-- local:связано -->
<!-- /local -->
+3 -3
View File
@@ -5,7 +5,7 @@ extends: arch/db-identifiers.md
# Идентификаторы: реализация на Go
Как `arch/db-identifiers.md` выглядит в Go-приложении. Форма записи —
Как базовый слой выглядит в Go-приложении. Форма записи —
`LANGUAGE.md`.
Единая точка из `KEYS-3` — пакет `internal/ident`: он
@@ -118,8 +118,8 @@ HTTP-кодов. В случае GKEY-8.1 снаружи это неотличи
**НЕ ДОЛЖЕН.** Обработчик не конструирует доменный sentinel (например
`ErrNotFound`), чтобы тут же сопоставить его со своим ответом.
**Почему.** Инверсия правила «трансляция у источника» из
`lang/go/errors.md`. Sentinel — сообщение от слоя, который знает факт:
**Почему.** Инверсия правила «трансляция у источника» из конвенции
`errors`. Sentinel — сообщение от слоя, который знает факт:
строка не найдена, потому что store её искал. Сфабрикованный транспортом,
он утверждает непроверенное, и по типу ошибки перестаёт быть видно, был ли
вообще поход в хранилище — а на этом держится вся диагностика по ошибкам.
+12 -11
View File
@@ -119,14 +119,15 @@ down останавливает сразу и заставляет пересо
таблицы соответствия, которую пришлось бы держать в голове для числового
кода.
### MIGR-8. Метки времени — `TEXT` в формате из `arch/time.md`
### MIGR-8. Метки времени — `TEXT` в каноническом формате
**ДОЛЖЕН.** Колонка с меткой времени объявляется как `TEXT`, значения
пишутся в формате из `arch/time.md`.
пишутся как RFC 3339 в UTC с суффиксом `Z` и фиксированной шириной.
**Почему.** Типа даты в SQLite нет, поэтому единственное, что делает
значения сравнимыми, — договорённость о формате. Текст в формате из
`arch/time.md` сортируется лексикографически в том же порядке, что и
значения сравнимыми, — договорённость о формате; сам формат выбран не
здесь, а конвенцией `time` (`TIME-1`, `TIME-2`). Текст в нём сортируется
лексикографически в том же порядке, что и
хронологически: `ORDER BY` и диапазонные условия работают без функций
преобразования, а значит и без потери индекса. Соседство двух форматов в
одной колонке ломает и сравнение, и разбор на стороне Go.
@@ -159,12 +160,12 @@ down останавливает сразу и заставляет пересо
### MIGR-11. Первичный ключ новой таблицы — TEXT ULID
**ДОЛЖЕН.** Колонка ключа объявляется как `TEXT`, значение приходит из
приложения (`KEYS-1`, `KEYS-2`).
приложения.
**Почему.** Здесь конвенция схемы ничего не решает — она реализует решение,
принятое в `arch/db-identifiers.md`. Повторить там ветвление или условие
значило бы завести второй источник правды, и соседние таблицы разъехались бы
по разным ответам на один вопрос.
принятое конвенцией `db-identifiers` (`KEYS-1`, `KEYS-2`). Повторить там
ветвление или условие значило бы завести второй источник правды, и соседние
таблицы разъехались бы по разным ответам на один вопрос.
`AUTOINCREMENT` в такой таблице невозможен: SQLite разрешает его только на
`INTEGER PRIMARY KEY`. То есть для новых таблиц запрещать нечего.
@@ -192,9 +193,9 @@ down останавливает сразу и заставляет пересо
## Связано
- `arch/time.md` — формат меток времени.
- `arch/db-identifiers.md` — выбор первичных ключей.
- `lang/go/errors.md` — граничные ошибки `database/sql` транслируются в
- конвенция `time` — формат меток времени.
- конвенция `db-identifiers` — выбор первичных ключей.
- конвенция `errors` — граничные ошибки `database/sql` транслируются в
доменные у источника, в слое store.
<!-- local:связано -->
+6 -7
View File
@@ -5,8 +5,8 @@ prefix: GERR
# Ошибки
Как ошибки строятся, оборачиваются и проверяются. Форма записи —
`LANGUAGE.md`. Где и когда ошибку **логировать** — в
`lang/go/logging.md` (коротко: лог один раз на доменной границе).
`LANGUAGE.md`. Где и когда ошибку **логировать** — в конвенции `logging`
(коротко: лог один раз на доменной границе).
Две границы, о которых говорят правила ниже:
@@ -169,7 +169,7 @@ HTTP-клиентов, файловой системы, внешних SDK.
### GERR-12. Полная ошибка идёт в приватный канал
**ДОЛЖЕН.** В лог уходит вся цепочка `%w` с контекстом; где и когда именно
`lang/go/logging.md`.
конвенция `logging`.
**Почему.** Цепочка — единственный носитель диагностики (GERR-1), и
единственный канал, где её можно показать целиком, — тот, который видит
@@ -231,7 +231,7 @@ HTTP-клиентов, файловой системы, внешних SDK.
**Почему.** Непокрытая ошибка — не класс отказа, а дефект: ветвь забыли
завести вопреки GERR-16. Адресат у неё владелец в смысле «надо чинить», отсюда
`ERROR` — уровень выбирается по адресату (`lang/go/logging.md`). Статус
`ERROR` — уровень выбирается по адресату (конвенция `logging`). Статус
тоже не выбирается: известное пользовательское состояние лежало бы в
маппинге, а про неизвестное сказать пользователю нечего, поэтому 4xx
отпадает.
@@ -385,9 +385,8 @@ HTTP-клиентов, файловой системы, внешних SDK.
## Связано
- `lang/go/logging.md` — где и когда ошибка попадает в лог.
- `KEYS-7` (`arch/db-identifiers.md`) — формат корреляционного ключа
из `GERR-14`.
- конвенция `logging` — где и когда ошибка попадает в лог.
- `KEYS-7` — формат корреляционного ключа из `GERR-14`.
<!-- local:механизировано -->
<!-- /local -->
+10 -10
View File
@@ -44,7 +44,7 @@ dev-выводом перестаёшь ежедневно гонять собс
### SLOG-3. Время записи — UTC
**ДОЛЖЕН.** `time` приводится к UTC через `ReplaceAttr` по `slog.TimeKey`
(см. `lang/go/time.md`).
(см. конвенцию `time`).
**Почему.** По умолчанию UTC не получится: встроенные хендлеры пишут время
в зоне самого `time.Time`, то есть в локальной зоне процесса. Записи одного
@@ -54,8 +54,8 @@ dev-выводом перестаёшь ежедневно гонять собс
событий.
Точность `JSONHandler` — миллисекунды фиксированной ширины; это другая
точность, чем в БД, и по `arch/time.md` так и должно быть: ширина
фиксируется на носитель.
точность, чем в БД, и по `TIME-2` так и должно быть: ширина фиксируется
на носитель.
## Сообщение
@@ -243,7 +243,7 @@ dev-выводом перестаёшь ежедневно гонять собс
**НЕ СЛЕДУЕТ.** Отдельный случайный `trace_id` не заводится, если у
сущностей есть стабильные уникальные идентификаторы. (Как их выбирают —
`arch/db-identifiers.md`, если конвенция взята.)
конвенция `db-identifiers`, если взята.)
**Почему.** Идентификатор сущности уже существует, стабилен между
процессами и во времени — по нему собираются записи не одного прохода, а
@@ -347,7 +347,7 @@ ctx = logctx.With(ctx, log) // достаём логгер из ctx в кажд
Нарушение инварианта в собственном коде — паника, недостижимая ветка — в
таблицу не входит: это не доменный отказ, и логирует его recover-граница
вместе со стеком (`lang/go/errors.md`). Искать его класс здесь не нужно.
вместе со стеком (конвенция `errors`). Искать его класс здесь не нужно.
Мимо таблицы идёт и доменная ошибка, которой нет в маппинге: класса у неё
нет, потому что её просто забыли завести. Она логируется `ERROR` с
@@ -505,7 +505,7 @@ API-ключи и токены, `Authorization`-заголовки, аутент
### SLOG-37. `*url.Error` санитизируется на границе клиента
**ДОЛЖЕН.** Ошибка разворачивается в первопричину **до** лога и до
обёртки — раньше трансляции в доменную (`lang/go/errors.md`).
обёртки — раньше трансляции в доменную (конвенция `errors`).
**Почему.** `*url.Error` встраивает полный URL запроса, а секрет живёт
прямо в нём: токен в пути, `api_key` в query. Go редактирует только пароль
@@ -553,11 +553,11 @@ API-ключи и токены, `Authorization`-заголовки, аутент
## Связано
- `arch/time.md` — точность и зона меток времени фиксируются на носитель.
- `lang/go/time.md` как ставится UTC в `ReplaceAttr` (SLOG-3).
- `lang/go/errors.md` — трансляция ошибки в доменную, порядок относительно
- конвенция `time` — точность и зона меток времени фиксируются на носитель;
как ставится UTC в `ReplaceAttr` (SLOG-3).
- конвенция `errors` — трансляция ошибки в доменную, порядок относительно
санитизации (SLOG-37).
- `arch/db-identifiers.md` — откуда берутся стабильные идентификаторы,
- конвенция `db-identifiers` — откуда берутся стабильные идентификаторы,
на которых держится корреляция (SLOG-18).
<!-- local:механизировано -->
+5 -5
View File
@@ -5,7 +5,7 @@ extends: arch/time.md
# Время: реализация на Go
Как требования `arch/time.md` выполняются в Go-коде: откуда берётся
Как требования базового слоя выполняются в Go-коде: откуда берётся
«сейчас», в каком виде время попадает в базу и в логи, что делать с зонами.
Форма записи — `LANGUAGE.md`.
@@ -178,13 +178,13 @@ func utcTime(_ []string, a slog.Attr) slog.Attr {
суток у того, что давно посчитано и сохранено.
Календарные вычисления бизнес-логики берут зону явно — как описано в
`arch/time.md`.
базовом слое.
<!-- local:механизировано -->
<!-- /local -->
## Связано
- `arch/time.md` — базовая конвенция: UTC как формат хранения, нормализация
чужого входа, явная зона в календарных вычислениях.
- `lang/go/config.md` — валидация зоны отображения загрузчиком конфига.
- базовый слой — UTC как формат хранения, нормализация чужого входа, явная
зона в календарных вычислениях.
- конвенция `config` — валидация зоны отображения загрузчиком конфига.
+1 -1
View File
@@ -5,7 +5,7 @@ extends: arch/app-directories.md
# Категории директорий: реализация в Ansible
Как категории из `arch/app-directories.md` раскладываются на сервере
Как категории из базового слоя раскладываются на сервере
плейбуком. Форма записи — `LANGUAGE.md`.
## Область действия
+6 -6
View File
@@ -9,8 +9,8 @@ prefix: HTMX
показывает и какие действия поддерживает — в спеках, не здесь. Форма записи
`LANGUAGE.md`.
Логирование запросов — `lang/go/logging.md` (HTTP-поля, рутинно-частое на
`DEBUG`). Трансляция доменных ошибок наружу — `lang/go/errors.md`
Логирование запросов — конвенция `logging` (HTTP-поля, рутинно-частое на
`DEBUG`). Трансляция доменных ошибок наружу — конвенция `errors`
(приватный канал = логи, публичный = сообщение плюс корреляционный ключ).
Здесь — только специфика htmx-транспорта, без дублирования.
@@ -215,8 +215,8 @@ s.render(w, "source_block", view) // фрагмент = тот же шаб
`?err=` статус по-прежнему используется: там его кто-то читает.
Цена решения: в логе доступа провалившееся действие выглядит как `200`.
Искать его надо по доменной записи об исходе операции (`lang/go/logging.md`),
а не по коду ответа.
Искать его надо по доменной записи об исходе операции (конвенция
`logging`), а не по коду ответа.
### HTMX-34. Сбой без ответа-фрагмента показывается глобальным слушателем
@@ -238,8 +238,8 @@ s.render(w, "source_block", view) // фрагмент = тот же шаб
### HTMX-15. Наружу идёт сообщение публичного канала
**ДОЛЖЕН.** Во фрагмент попадает нейтральный текст по правилам
`lang/go/errors.md`; `err.Error()` в разметку не рендерится.
**ДОЛЖЕН.** Во фрагмент попадает нейтральный текст публичного канала;
`err.Error()` в разметку не рендерится.
**Почему.** htmx-фрагмент выглядит внутренней деталью приложения, и на нём
легче всего забыть, что это тот же публичный канал, что и страница: