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

- 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
+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` — валидация зоны отображения загрузчиком конфига.