Compare commits

...
5 Commits
Author SHA1 Message Date
av a961aa2b40 todo: закрытые вопросы удалены, оставшиеся перенумерованы
- ушли четыре закрытых раздела (ссылка на язык, модель сборки, разбор
  прототипов, мультиязычность) и закрытые куски внутри оставшихся: принятые
  решения живут в README, GUIDE и LANGUAGE, а черновик их дублировал
- полезные остатки перенесены, а не потеряны: прототипы Vale и vendir — в
  вопрос про тулинг, вынос арх-ядра из errors и logging — в пары слоёв
- нумерация сплошная 1–11, внутренние ссылки поправлены под неё; в шапку
  добавлено правило, что закрытый вопрос отсюда удаляется
2026-07-26 13:46:48 +03:00
av c04a54ffd5 todo: заведены четыре открытые темы по языку записи
- 12: `WHEN`/`AND` в блоке стыка правил — английские служебные слова там же,
  где `SHALL` отклонён как занятый OpenSpec; либо перевод, либо явная
  оговорка, что форма спецификации в этом месте намеренна
- 13: критерий «названного вреда» для ДОЛЖЕН описан в языке, но правила под
  него нет — META-6 обязывает только понижать при отсутствии проверки
- 14 и 15: одиннадцать таблиц не прочитаны на взаимоисключительность
  (подозреваемый SLOG-11), и канон не просмотрен на факты, записанные
  модальным словом
2026-07-26 13:43:45 +03:00
av 9318087248 язык записи опёрт на стандарты, словарь стал параметром
- шкала обязательности объявлена инвариантом, а набор ключевых слов —
  параметром естественного языка набора: для английского готовый словарь
  даёт BCP 14, для прочих берут перевод стандарта или делают свой; отклонены
  синонимы ступеней и `SHALL`, занятый OpenSpec
- применены шесть дельт: нормативно только заглавное написание (RFC 8174),
  ДОЛЖЕН требует названного вреда и машинной проверки сразу, ДОПУСКАЕТСЯ
  адресовано рецензенту, МЕХАНИЗИРОВАНО выведено из шкалы в отметку рядом с
  модальностью (ISO/IEC/IEEE 29148), у таблиц объявлены политика совпадения
  и полнота (DMN)
- «Форма записи — LANGUAGE.md» в двенадцати конвенциях заменена строкой о
  версии языка по образцу boilerplate BCP 14: пути канона в копии не
  существует, а словарь и правило заглавных строка несёт сама
2026-07-26 13:42:10 +03:00
av fea0285619 todo: закрыт вопрос про модель сборки, разобраны прототипы
- вопрос 3 закрыт: модель записана в README и GUIDE, отмечены три места,
  где итог разошёлся с прежними заготовками — нет лока, нет маркеров секций,
  локальные префиксы не объявляются, а резервируются буквой `X`
- вопрос 4 переформулирован: регионы не переносятся в единую секцию, а
  удаляются из канона — наполнять их в каноне нечем
- вопрос 7 заменён разбором: транспорт переизобретался, оси и сборка аналога
  не имеют, Vale отклонён; в 1, 9 и 10 добавлены следствия
2026-07-26 12:55:16 +03:00
av c2f68e6be1 обвязка: модель копий переписана под один маркер и манифест
- именованные регионы `<!-- local:имя -->` заменены на единственный
  `<!-- conv:local -->`: всё ниже него принадлежит репозиторию, всё выше
  пересобирается, поэтому имени-которое-можно-осиротить больше нет
- лок-файла и `origin_hash` в шапке нет — «что было в прошлый раз» знает git,
  копии закоммичены, автоматического обновления не существует; транспорт
  назад (`push`) убран вместе с ними
- заведены META-22 (репозиторное пишется ниже маркера) и META-23 (форк не
  носит `origin:`), META-17 переписан под маркер; буква `X` в префиксе
  зарезервирована за локальными правилами потребителей
2026-07-26 12:55:05 +03:00
18 changed files with 675 additions and 440 deletions
+37 -17
View File
@@ -21,15 +21,28 @@ code in this repository.
`**МОДАЛЬНОСТЬ.** норма`, абзац `**Почему.** …`. Правило без «Почему» не `**МОДАЛЬНОСТЬ.** норма`, абзац `**Почему.** …`. Правило без «Почему» не
принимается. принимается.
- Норма — одна фраза; если в неё не влезает, это два правила. - Норма — одна фраза; если в неё не влезает, это два правила.
- Модальные слова: **ДОЛЖЕН**, **НЕ ДОЛЖЕН**, **СЛЕДУЕТ**, **НЕ СЛЕДУЕТ**, - Шкала инвариантна, словарь — параметр языка набора. Канон русский, значит
**ДОПУСКАЕТСЯ**, плюс не-модальная отметка **МЕХАНИЗИРОВАНО**. Английские слова: **ДОЛЖЕН**, **НЕ ДОЛЖЕН**, **СЛЕДУЕТ**, **НЕ СЛЕДУЕТ**,
ключевые слова (SHALL, MUST) не используются — они заняты OpenSpec. **ДОПУСКАЕТСЯ**. Словарь один на канон, синонимов на ступень нет.
- Модальные слова не употребляются вне правил: ни в «Область действия», ни в - `SHALL` не используется ни в одном словаре — занято OpenSpec.
«Связано», ни в локальных регионах, ни во вводной прозе. - Нормативно только заглавное написание (правило RFC 8174): строчное
«должен» в прозе нормой не является.
- ДОЛЖЕН требует двух условий сразу: нарушение причиняет названный вред
и норма проверяема машиной. Проверяемость сама по себе до ДОЛЖЕН не
повышает.
- ДОПУСКАЕТСЯ адресовано рецензенту: помеченный им выбор на ревью не
обсуждается.
- **МЕХАНИЗИРОВАНО** — не модальность, а способ проверки: отметка стоит
рядом с модальным словом (`**ДОЛЖЕН. МЕХАНИЗИРОВАНО.**`), а не вместо него.
- Заглавные модальные слова не употребляются вне правил: ни в «Область
действия», ни в «Связано», ни в локальной части копии, ни во вводной прозе.
Исключение — строка о версии языка, которая их перечисляет.
- «Почему» отвечает на «что сломается, если сделать иначе», а не - «Почему» отвечает на «что сломается, если сделать иначе», а не
пересказывает норму. «Потому что так принято» — не обоснование. пересказывает норму. «Потому что так принято» — не обоснование.
- Правило, классифицирующее ситуации, пишется таблицей «ситуация → вердикт»; - Правило, классифицирующее ситуации, пишется таблицей «ситуация → вердикт»;
строки нумеруются `KEYS-5.1`. строки нумеруются `KEYS-5.1`. Строки взаимоисключающи по умолчанию; иной
порядок объявляется явно, а перечисленные случаи покрывают область
действия.
- Модальность принадлежит правилу, а не файлу: `status:` в шапке отменён. - Модальность принадлежит правилу, а не файлу: `status:` в шапке отменён.
## Идентификаторы и префиксы ## Идентификаторы и префиксы
@@ -44,6 +57,8 @@ code in this repository.
секция `[live]`, путём от корня репозитория. секция `[live]`, путём от корня репозитория.
- Удаление или разделение файла: префикс уходит в `[retired]` с причиной и - Удаление или разделение файла: префикс уходит в `[retired]` с причиной и
датой, а не освобождается. датой, а не освобождается.
- Префиксы на букву `X` канон не занимает: они зарезервированы за локальными
правилами репозиториев-потребителей.
- Перенос правила в другой файл — смысловое изменение: новый префикс и новый - Перенос правила в другой файл — смысловое изменение: новый префикс и новый
номер. Переезд самого файла между осями идентификаторы не трогает. номер. Переезд самого файла между осями идентификаторы не трогает.
@@ -72,9 +87,9 @@ code in this repository.
норма уехала в линтер. норма уехала в линтер.
- META-1: один файл — ровно один повторяющийся выбор. META-2: конвенция - META-1: один файл — ровно один повторяющийся выбор. META-2: конвенция
заводится, когда решение принимается третий раз. заводится, когда решение принимается третий раз.
- Локальные регионы `<!-- local:имя --> … <!-- /local -->` в каноне остаются - Репозиторного в каноне нет вовсе: механизация, отступления и ссылки на код
пустыми: их содержимое принадлежит репозиторию-потребителю. Имя региона и живут в копии ниже маркера `<!-- conv:local -->` (META-22), который ставит
путь файла — API, переименование осиротит все копии. сборщик. Заводить пустые местные разделы в каноне не нужно.
## Выбор оси ## Выбор оси
@@ -86,11 +101,12 @@ code in this repository.
## Оформление файла ## Оформление файла
Шапка `prefix:` (плюс `extends:`) → `# Тема` → вводная проза со строкой Шапка `prefix:` (плюс `extends:`) → `# Тема` → вводная проза → отдельным
«Форма записи — `LANGUAGE.md`» → `## Область действия` (обязателен для абзацем строка о версии языка (её точный текст — в `LANGUAGE.md`, раздел
трудноизменяемых слоёв — META-11) → правила → `## Связано` с пустым «Ссылка на язык из конвенции») → `## Область действия` (обязателен для
`<!-- local:связано -->`. Имя файла — kebab-case по теме. Проза переносится трудноизменяемых слоёв — META-11) → правила → `## Связано` только с
по ~76 колонок; таблицы и блоки кода не переносятся. каноническими ссылками (META-17). Имя файла — kebab-case по теме. Проза
переносится по ~76 колонок; таблицы и блоки кода не переносятся.
## Ревью формы ## Ревью формы
@@ -110,10 +126,14 @@ code in this repository.
- Тестов, линтеров и CI нет. `conv` — python3 CLI на одной stdlib; запускают - Тестов, линтеров и CI нет. `conv` — python3 CLI на одной stdlib; запускают
его из корня репозитория-потребителя (`~/projects/private/dev-conventions/` его из корня репозитория-потребителя (`~/projects/private/dev-conventions/`
плюс `conv status`). `status` и `diff` всегда возвращают 0 — это отчёт, а не плюс команда).
проверка. - Модель копий, описанная в `README.md`, согласована, но не реализована:
`conv` собран под прежнюю (зеркальное дерево, именованные регионы,
`origin_hash`, команды `status`/`diff`/`push`), и в двенадцати файлах
канона ещё лежит 31 пустой регион `<!-- local:имя -->` — их предстоит
удалить. При правке обвязки истина — README, а не код `conv`.
- Ни один репозиторий-потребитель ещё не подключён: копий с шапкой `origin:` - Ни один репозиторий-потребитель ещё не подключён: копий с шапкой `origin:`
в природе нет, все локальные регионы канона пусты. в природе нет.
- `TODO.md` — площадка для обсуждения на будущее, а не принятые решения; при - `TODO.md` — площадка для обсуждения на будущее, а не принятые решения; при
работе над обвязкой его стоит прочесть, но истина о текущем устройстве — работе над обвязкой его стоит прочесть, но истина о текущем устройстве —
`README.md`. `README.md`.
+52 -31
View File
@@ -41,16 +41,20 @@ prefix: META
## Канон и копии ## Канон и копии
Файлы в `docs/conventions/` с шапкой `origin:` — копии из общего канона Файлы в `docs/conventions/` с шапкой `origin:` — копии из общего канона
`dev-conventions`, а не собственные документы репозитория. Репозиторное `dev-conventions`, а не собственные документы репозитория. Копия собирается
живёт только внутри локальных регионов `<!-- local:имя --> … <!-- /local -->`: из канона целиком, поэтому репозиторное живёт ниже маркера локальной части
они исключены из сравнения с каноном, и расхождение по ним — норма, а не в конце файла: обновление сохраняет всё, что ниже маркера, и переписывает
дрейф. Правка вне регионов означает одно из двух: улучшение, которое всё, что выше. Откуда взяты копии и где брать обновления — в манифесте
возвращают в канон, или сознательное расхождение, записанное в ключ `local:` `.conventions.toml` в корне репозитория.
шапки. Состояние копий показывает `conv status`, различия — `conv diff`,
обновление из канона — `conv pull`; всё через раннер репозитория.
Имя региона обязательно и стабильно: перенос содержимого при обновлении Отдельного механизма отчёта о расхождении нет: обновление перезаписывает
идёт по именам, и переименование осиротит содержимое во всех копиях. файлы в рабочем дереве, а что именно изменилось, показывает `git diff` до
коммита. Поэтому в шапке копии хранится только `origin:` — отпечатков
канона и дат синхронизации в ней нет, историю держит git.
Правка выше маркера означает одно из двух: улучшение, которое переносят в
канон, или документ, переставший быть копией, — тогда `origin:` из шапки
убирают.
## Правила ## Правила
@@ -58,11 +62,11 @@ prefix: META
**ДОЛЖЕН.** Файл описывает ровно один повторяющийся выбор. **ДОЛЖЕН.** Файл описывает ровно один повторяющийся выбор.
**Почему.** Подписка — это набор лежащих в репозитории файлов, и берут файл **Почему.** Подписка перечисляется по темам, и тему берут целиком. Файл,
целиком. Файл, собравший две темы, вынуждает репозиторий взять правила, собравший две темы, вынуждает репозиторий взять правила, которые ему не
которые ему не нужны, и получать шум в `diff` по чужой половине. Разрезать нужны, и вычитывать чужую половину при каждом обновлении. Разрезать позже
позже дорого: путь файлачасть адреса правила, и после разреза внешние дорого: перенос правила в другой файл — это новый префикс и новая
ссылки указывают не туда. нумерация, поэтому после разреза все внешние ссылки обходят руками.
### META-2. Конвенция заводится, когда решение принимается третий раз ### META-2. Конвенция заводится, когда решение принимается третий раз
@@ -152,9 +156,9 @@ prefix: META
(вкус формулировки, выбор границы, суждение о ситуации), не может быть (вкус формулировки, выбор границы, суждение о ситуации), не может быть
ДОЛЖЕН — его модальность СЛЕДУЕТ по построению, а не по слабости. ДОЛЖЕН — его модальность СЛЕДУЕТ по построению, а не по слабости.
### META-7. Факт механизации фиксируется в локальном регионе со ссылкой на правило ### META-7. Факт механизации фиксируется в копии со ссылкой на правило
**ДОЛЖЕН.** Регион `механизировано` называет идентификатор правила и **ДОЛЖЕН.** Запись о механизации называет идентификатор правила и
конкретную проверку. конкретную проверку.
**Почему.** Механизация — состояние конкретного репозитория, канон о ней не **Почему.** Механизация — состояние конкретного репозитория, канон о ней не
@@ -181,7 +185,7 @@ prefix: META
### META-9. Общая механизация разрешает удалить норму из канона ### META-9. Общая механизация разрешает удалить норму из канона
**ДОПУСКАЕТСЯ.** Правило, уехавшее в общий конфиг линтера или в общую роль, **ДОПУСКАЕТСЯ.** Правило, уехавшее в общий конфиг линтера или в общую роль,
удаляется из канона одним `push`. удаляется из канона.
**Почему.** Формулировка, дублирующая работающую у всех проверку, **Почему.** Формулировка, дублирующая работающую у всех проверку,
размазывает внимание: файл на несколько сотен строк заставляет человека и размазывает внимание: файл на несколько сотен строк заставляет человека и
@@ -232,15 +236,15 @@ prefix: META
### META-14. Отступления перечисляются поимённо, со ссылкой на правила ### META-14. Отступления перечисляются поимённо, со ссылкой на правила
**ДОЛЖЕН.** В локальном регионе перечислены отступления, которые уже есть в **ДОЛЖЕН.** В локальной части копии перечислены отступления, которые уже
коде, с идентификатором правила и причиной. есть в коде, с идентификатором правила и причиной.
**Почему.** Иначе репозиторий выглядит соблюдающим конвенцию, а проверить **Почему.** Иначе репозиторий выглядит соблюдающим конвенцию, а проверить
это можно только чтением всего кода. Со ссылками отступления счётны: видно, это можно только чтением всего кода. Со ссылками отступления счётны: видно,
сколько правил конвенции репозиторий реально не соблюдает. Пустой регион при сколько правил конвенции репозиторий реально не соблюдает. Пустой список при
этом почти всегда означает не отсутствие отступлений, а то, что их не искали. этом почти всегда означает не отсутствие отступлений, а то, что их не искали.
### META-15. Запись в регионе отступлений разбирается по масштабу ### META-15. Запись об отступлении разбирается по масштабу
**ДОЛЖЕН.** Судьба записи зависит от того, что в ней сказано: **ДОЛЖЕН.** Судьба записи зависит от того, что в ней сказано:
@@ -256,15 +260,35 @@ prefix: META
правила в каноне, которую чинят один раз для всех, или лишнюю подписку, правила в каноне, которую чинят один раз для всех, или лишнюю подписку,
где файл просто не нужен. Оставленная отступлением, она прячет обе. где файл просто не нужен. Оставленная отступлением, она прячет обе.
### META-17. Репо-специфичная часть «Связано» — в локальном регионе ### META-17. Ссылки на код репозитория стоят в локальной части, а не в «Связано»
**ДОЛЖЕН.** Раздел «Связано» стоит в конце файла; канонические ссылки — в **ДОЛЖЕН.** Раздел «Связано» канона содержит только ссылки, верные у всех
общем тексте, ссылки на ADR, код и файлы конкретного репозитория — в потребителей; ссылки на ADR, код и файлы конкретного репозитория — в
локальном регионе. локальной части копии.
**Почему.** Общий текст уезжает `push`-ем ко всем потребителям, и ссылка на **Почему.** Текст канона приезжает ко всем потребителям, и ссылка на чужой
чужой файл у них битая с первого дня. Регион исключён из сравнения, поэтому файл у них битая с первого дня. Ниже маркера та же ссылка никого не
та же ссылка внутри него никого не задевает и не даёт вечного шума в `diff`. задевает и переживает обновление, потому что обновление её не трогает.
### META-22. Репозиторное в копии пишется ниже маркера локальной части
**ДОЛЖЕН.** Правки репозитория вносятся ниже маркера, а не в текст,
пришедший из канона.
**Почему.** Обновление перезаписывает всё, что выше маркера, поэтому правка
там живёт до первого `pull`. Заметить пропажу можно, только вычитав
`git diff` целиком — а он в этот момент и без того полон изменений канона,
и своя строка теряется среди чужих.
### META-23. Документ, переставший быть копией, не носит `origin:`
**НЕ ДОЛЖЕН.** Файл, который развели с каноном намеренно, шапку `origin:`
не сохраняет.
**Почему.** По `origin:` решается, какие файлы пересобирать из канона. Форк,
оставивший шапку, при первом же обновлении теряет ровно то, ради чего его
заводили. Происхождение такого документа остаётся в истории коммита, где оно
никого не вводит в заблуждение.
### META-18. README директории перечисляет конвенции с однострочным описанием ### META-18. README директории перечисляет конвенции с однострочным описанием
@@ -284,6 +308,3 @@ prefix: META
идентификатором служит и напоминанием, и адресом, по которому за идентификатором служит и напоминанием, и адресом, по которому за
подробностями идут; перенос деталей туда же вернул бы задачу поддержки двух подробностями идут; перенос деталей туда же вернул бы задачу поддержки двух
текстов. текстов.
<!-- local:точки-входа -->
<!-- /local -->
+266 -121
View File
@@ -1,21 +1,60 @@
---
version: 1
---
# Язык конвенций # Язык конвенций
Как записываются правила в этом каноне. Документ описывает форму, а не Формальный язык, на котором записаны правила этого канона: что считается
содержание: что такое правило, чем оно отличается от прозы вокруг и как на правилом, чем оно отличается от прозы вокруг, какими словами задаётся
него сослаться. обязательность и как на правило сослаться извне.
Форма подсмотрена у OpenSpec, но взято оттуда не всё — см. «Чего мы не Версия языка — **1**. Номер называется в каждой конвенции: словарь может
берём». пополниться, и текст, написанный по предыдущей версии, должен читаться по
той, по которой написан.
## Зачем формализовать ## Опора на стандарты
Не ради строгости. Три конкретные вещи, которые без адресуемых правил не Язык не выводится из вкуса автора. Каждое решение о форме взято из
работают: документа, где эта задача уже решена и обкатана, и отклонения от источника
названы явно.
- **Механизация.** Регион `механизировано` должен говорить «правило | Источник | Что взято | Что отклонено |
`MIGR-4` проверяет `archrules`», а не «`AUTOINCREMENT` в новых миграциях — |---|---|---|
`archrules`»: во втором случае читатель сам догадывается, к какому | **BCP 14** (RFC 2119 + RFC 8174) | шкала модальности; правило «нормативно только заглавное»; сдержанность в высшей модальности; англоязычный словарь как готовый | синонимы `REQUIRED`, `RECOMMENDED`, `OPTIONAL`; `SHALL` как форма требования |
утверждению это относится, и догадывается по-разному. | **ISO/IEC Directives, Part 2** | разделение «требование — рекомендация — разрешение — возможность»; запрет модальных глаголов в утверждениях о факте | списки равнозначных словесных форм («is to», «is required to», «only … is permitted») |
| **ISO/IEC/IEEE 29148** | обоснование как обязательный атрибут; единичность нормы; проверяемость; метод верификации отдельным атрибутом | остальной аппарат требований: приоритеты, источники, матрицы трассируемости |
| **DMN** | таблица решений с объявленной политикой совпадения и требованием полноты | исполняемая семантика и всё, что предполагает движок решений |
| **EARS** | вывод о том, что выигрыш даёт жёсткий шаблон, а не его конкретный вид; паттерн «нежелательное поведение» — в виде таблицы | шаблоны с субъектом-системой: `WHEN`, `WHILE`, `WHERE` |
| **OpenSpec** | четырёхчастная форма правила; адресуемость правила идентификатором | `SHALL`; `GIVEN/WHEN/THEN` как общая форма записи |
Три отклонения стоят объяснения, потому что выглядят как произвол.
**Синонимов нет.** BCP 14 держит `REQUIRED` рядом с `MUST` и `OPTIONAL`
рядом с `MAY` ради читаемости английской прозы. Одна форма записи на ступень
означает, что проверка «модальное слово употреблено вне правила» становится
перечислением, а не разбором синонимических рядов.
**`SHALL` не используется ни в каком словаре этого языка.** Слово занято
спецификациями (OpenSpec), и общая с ними форма стирала бы границу между
конвенцией и описанием поведения системы: `SHALL` в конвенции читался бы как
контракт, которого конвенция не даёт. Для англоязычного словаря это означает
выбор в пользу `MUST` из BCP 14, а не `shall` из ISO/IEC Directives.
**`GIVEN/WHEN/THEN` не берётся.** У спецификации субъект — система, и её
поведение разворачивается во времени: состояние, событие, исход. У конвенции
субъект — автор кода, и разворачивать нечего: есть ситуация выбора и вердикт.
Это таблица, а не траектория. Тем же рассуждением отклонены шаблоны EARS с
субъектом-системой, а взят из EARS другой результат: измеримый выигрыш дала
там сама обязательность шаблона, а не его конкретная форма.
## Что даёт формализация
Адресуемое правило — не украшение формы, а условие работы трёх механизмов:
- **Механизация.** Запись о ней должна говорить «правило `MIGR-4` проверяет
`archrules`», а не «`AUTOINCREMENT` в новых миграциях — `archrules`»: во
втором случае читатель сам догадывается, к какому утверждению это
относится, и догадывается по-разному.
- **Отступления.** «У нас не так» бесполезно, пока не сказано, что именно - **Отступления.** «У нас не так» бесполезно, пока не сказано, что именно
«не так». Со ссылкой на правило отступления становятся счётными: видно, «не так». Со ссылкой на правило отступления становятся счётными: видно,
сколько правил конвенции репозиторий реально не соблюдает. сколько правил конвенции репозиторий реально не соблюдает.
@@ -39,51 +78,202 @@
``` ```
Четыре обязательные части: **идентификатор**, **заголовок**, **модальность Четыре обязательные части: **идентификатор**, **заголовок**, **модальность
с нормой**, **почему**. Норма — одна фраза; если в неё не влезает, это два с нормой**, **обоснование**. Норма — одна фраза; если в неё не влезает, это
правила. два правила. Требование единичности взято из ISO/IEC/IEEE 29148: составная
норма не проверяема целиком, и нарушение одной её половины нечем
адресовать.
## Правило без «почему» не принимается ## Обоснование обязательно
Это жёсткое требование к форме, а не пожелание. Причины: Правило без блока «Почему» не принимается. Это требование к форме, а не
пожелание; в 29148 обоснование — атрибут требования наравне с самим
требованием, и по тем же причинам:
- **«Почему» — единственный способ понять, когда правило перестало - **Обоснование — единственный способ увидеть, что правило устарело.**
действовать.** Норма стареет молча; обоснование стареет заметно. Когда Норма стареет молча; причина стареет заметно. Когда причина отпала, видно,
причина отпала, видно, что правило пора убрать, а не соблюдать по что правило пора убрать, а не соблюдать по инерции.
инерции.
- **Правило без обоснования не переживает спор.** Через год ни автор, ни - **Правило без обоснования не переживает спор.** Через год ни автор, ни
агент не восстановят мотив, и правило будет либо отменено первым же агент не восстановят мотив, и правило будет либо отменено первым же
возражением, либо соблюдено там, где вредит. возражением, либо соблюдено там, где вредит.
- **Формулировка «почему» — проверка на то, что это вообще правило.** Если - **Формулировка обоснования — проверка на то, что это вообще правило.**
причина не формулируется, перед нами привычка или вкусовщина; ей место в Если причина не формулируется, перед нами привычка или вкусовщина; ей
черновиках, а не в конвенции. место в черновиках, а не в конвенции.
«Почему» отвечает на «что сломается, если сделать иначе», а не пересказывает «Почему» отвечает на «что сломается, если сделать иначе», а не пересказывает
норму другими словами. «Потому что так принято» — не обоснование. норму другими словами. «Потому что так принято» — не обоснование.
## Модальные слова ## Модальные слова
Пишутся капсом — это ключевые слова, а не обычный текст. Инвариант языка — **шкала**: пять ступеней в четырёх категориях ISO/IEC
Directives, Part 2, по одной форме записи на ступень, заглавными. Какими
словами ступени названы — параметр естественного языка набора, а не часть
языка конвенций. Этот канон написан по-русски и несёт русский словарь.
| Слово | Значение | Отступление | Пишутся заглавными — это ключевые слова, а не обычный текст.
| Слово | Категория | Значение | Отступление |
|---|---|---|---|
| **ДОЛЖЕН** | требование | нарушение считается ошибкой | только с записью в отступления |
| **НЕ ДОЛЖЕН** | требование | запрет | то же |
| **СЛЕДУЕТ** | рекомендация | сильная рекомендация; новый код пишем так | допустимо, причину записываем |
| **НЕ СЛЕДУЕТ** | рекомендация | обратное к СЛЕДУЕТ | то же |
| **ДОПУСКАЕТСЯ** | разрешение | выбор за автором кода; возражение на ревью не принимается | не требуется — правило ничего не запрещает |
**Нормативно только заглавное написание.** Это правило RFC 8174, и оно
здесь по той же причине, по которой понадобилось там: без него каждое
строчное «должен» во вводной прозе становится предметом спора о том, норма
это или речь. Строчное слово нормой не является никогда, поэтому проза
свободна, а проверка «модальное слово вне правила» сводится к поиску
заглавных форм.
**Четвёртая категория ISO — возможность — ключевого слова не имеет.**
Утверждения о том, что бывает и что технически осуществимо, пишутся обычной
прозой и модальных слов не несут. Модальное слово в таком утверждении
превращает описание в норму, которую никто не собирался вводить.
**ДОПУСКАЕТСЯ адресовано рецензенту.** В BCP 14 у `MAY` есть вторая
половина, которую обычно не замечают: сторона, не выбравшая опцию, обязана
работать с той, что выбрала. В конвенции этому соответствует запрет
возражать: выбор, помеченный ДОПУСКАЕТСЯ, на ревью не обсуждается. Без этой
половины слово было бы удобством читателя, а не нормой, и не работало бы в
единственной точке, где у конвенции есть принуждение.
**ДОЛЖЕН требует двух условий сразу:**
1. нарушение причиняет названный вред, а не расходится со вкусом — критерий
BCP 14, где высшая модальность резервируется под то, что действительно
ломается, и не употребляется для навязывания метода;
2. норма проверяема машиной — критерий META-6, иначе обязательность
держится на внимании и обещает то, чего не делает.
Не выполнено первое — правилу место в СЛЕДУЕТ или нигде. Не выполнено
второе — в СЛЕДУЕТ. Проверяемость сама по себе не повышает правило до
ДОЛЖЕН: механически проверяемых мелочей больше, чем важных вещей, и
безразборное повышение обесценивает шкалу быстрее, чем её отсутствие.
Модальность живёт на **правиле**, а не на файле. Файловый статус
(`status: рекомендуемая` / `обязательная` в шапке) не используется: он
неизбежно врёт, потому что один файл смешивает жёсткие требования с
советами. В шапке остаются только `prefix` и `extends`.
## Словарь другого языка
Для английского готовый словарь даёт BCP 14. Для любого другого языка слова
берут из перевода стандарта, если он есть, или переводят сами: шкала и
семантика ступеней при этом не меняются — меняется только запись.
| Ступень | Русский | Английский (BCP 14) |
|---|---|---| |---|---|---|
| **ДОЛЖЕН** | нарушение считается ошибкой | только с записью в регион `отступления` | | требование | ДОЛЖЕН | MUST |
| **НЕ ДОЛЖЕН** | запрет | то же | | запрет | НЕ ДОЛЖЕН | MUST NOT |
| **СЛЕДУЕТ** | сильная рекомендация; новый код пишем так | допустимо, причину записываем | | рекомендация | СЛЕДУЕТ | SHOULD |
| **НЕ СЛЕДУЕТ** | обратное к СЛЕДУЕТ | то же | | рекомендация против | НЕ СЛЕДУЕТ | SHOULD NOT |
| **ДОПУСКАЕТСЯ** | явное разрешение | не требуется — правило ничего не запрещает | | разрешение | ДОПУСКАЕТСЯ | MAY |
| отметка о способе проверки | МЕХАНИЗИРОВАНО | MECHANIZED |
**ДОПУСКАЕТСЯ** нужно не для симметрии: оно снимает вопрос «а так можно?» Последняя строка стандартом не даётся ни в одном языке: способа проверки в
там, где соседнее правило звучит строго и его легко перечитать шире, чем шкале BCP 14 нет, слово подбирается под язык так же, как остальные.
задумано.
Модальность живёт на **правиле**, а не на файле. Прежний файловый статус Что требуется от любого словаря:
(`status: рекомендуемая` / `обязательная` в шапке) отменён: он неизбежно
врал, потому что один файл смешивает жёсткие требования с советами. В шапке
остаются только `prefix`, `extends` и служебные ключи копии.
Мы **не используем SHALL и прочие английские ключевые слова**. Они заняты - **одна форма на ступень.** Синонимы отклонены не из аскетизма: проверка
спецификациями (OpenSpec), и общий словарь стирал бы границу «конвенция — «модальное слово вне правила» перечисляет формы, и синонимический ряд
не capability». Разный словарь эту границу держит бесплатно. превращает перечисление в разбор.
- **слово заглавными не встречается в обычной прозе этого языка.** Иначе
правило «нормативно только заглавное» перестаёт спасать: проверка ловит
оформление, а не модальность.
- **словарь перечислен целиком в строке о версии языка.** Читателю копии он
известен из самого файла, без обращения к этому документу, — иначе
конвенция в чужом репозитории теряет ключ к собственному тексту.
- **словарь один на канон.** Два словаря параллельно дают две формы записи
одного требования и удваивают каждую проверку; выбор языка — свойство
набора, а не отдельного файла.
Смена словаря версию языка не меняет: версия принадлежит шкале и правилам
формы, а не буквам.
## Ссылка на язык из конвенции
Каждая конвенция называет язык одной строкой во вводной прозе:
> Ключевые слова ДОЛЖЕН, НЕ ДОЛЖЕН, СЛЕДУЕТ, НЕ СЛЕДУЕТ, ДОПУСКАЕТСЯ и
> отметка МЕХАНИЗИРОВАНО толкуются как описано в языке конвенций версии 1 —
> тогда и только тогда, когда написаны заглавными.
Слова в строке — из словаря того языка, на котором написан набор. Для
англоязычного набора та же строка выглядит так:
> The key words MUST, MUST NOT, SHOULD, SHOULD NOT, MAY and the mark
> MECHANIZED are to be interpreted as described in the conventions language,
> version 1, and only when written in capitals.
Форма скопирована у BCP 14, где та же задача решается тем же способом:
спецификация не прикладывает к себе словарь и не указывает путь к нему, а
называет документ и версию. Пути в этой строке нет намеренно — конвенция
уезжает в чужой репозиторий, где путей канона не существует, а норму
исполнить всё равно можно: строка сама перечисляет ключевые слова набора и
сама несёт правило заглавных.
## Обязательность и способ проверки — разные атрибуты
Механизация не входит в шкалу модальности: она говорит не о том, насколько
правило обязательно, а о том, чем эта обязательность обеспечена. В 29148 это
два разных атрибута требования, и здесь тоже два.
Когда правило механизировано у всех потребителей, его норма из канона
удаляется, а модальность и обоснование остаются:
```markdown
### MIGR-6. Дефолтов времени в схеме БД нет
**ДОЛЖЕН. МЕХАНИЗИРОВАНО.** Проверяется общим правилом линтера;
формулировка удалена, потому что дублировала работающую проверку.
**Почему.** Дефолт превращает забытую вставку в тихо работающий код…
```
- Идентификатор и заголовок сохраняются: ссылки из репозиториев продолжают
указывать на то же утверждение.
- Модальность сохраняется, поэтому «все ли ДОЛЖЕН механизированы»
остаётся вычислимым вопросом, а не предметом чтения всего канона.
- «Почему» остаётся навсегда — линтер сообщает, что нарушено, но не
сообщает, зачем правило существует, и без обоснования нельзя понять,
когда проверку пора отменять.
Факт «механизировано у всех» устанавливается вручную: канон по построению
не знает списка подписчиков, и обойти репозитории перед удалением нормы —
часть работы, а не то, что можно проверить автоматически.
## Таблицы решений
Часть правил **классифицирует ситуации**: какой уровень лога, какая
категория директории, что делать с невалидным вводом в зависимости от его
источника. Каноническая форма для них — таблица «ситуация → вердикт», строки
которой нумеруются как подпункты правила (`SLOG-8.1`).
Таблица плотнее прозы и не даёт пропустить ветку: пустая клетка видна, а
неупомянутый случай в абзаце — нет. Два свойства такой таблицы взяты из DMN,
где они называются и проверяются:
- **Политика совпадения.** По умолчанию строки взаимоисключающи: любой
ситуации соответствует ровно одна. Если это не так, таблица объявляет
порядок строкой над собой — «применяется первое совпадение». Молчание об
этом означает, что при двух подходящих строках читатель выбирает сам, и
два автора выберут по-разному.
- **Полнота.** Перечислены все случаи, попадающие в область действия. Если
возможен случай вне перечисленных, он назван отдельной строкой, а не
оставлен на догадку.
Прозаический сценарий остаётся точечным инструментом — для **стыка правил**,
когда два правила вместе дают неочевидный результат:
```
WHEN зависимость недоступна и ретраи вызова исчерпаны → ext-запись ERROR
AND тик фонового цикла упал по той же причине → доменная запись WARN
```
Такой блок ставится после обоих правил и ссылается на их идентификаторы.
Если стыков нет — сценариев в файле нет.
## Идентификаторы ## Идентификаторы
@@ -104,6 +294,8 @@
чтобы по нему искать, а не чтобы его разбирать. Выводимый префикс вдобавок чтобы по нему искать, а не чтобы его разбирать. Выводимый префикс вдобавок
привязал бы идентификатор к таксономии, которую канон перестраивает, и привязал бы идентификатор к таксономии, которую канон перестраивает, и
упёрся бы в потолок из числа букв алфавита. упёрся бы в потолок из числа букв алфавита.
- Префиксы на букву `X` каноном не занимаются: они принадлежат локальным
правилам репозиториев-потребителей.
- Перенос правила в другой файл — смысловое изменение, а не переименование: - Перенос правила в другой файл — смысловое изменение, а не переименование:
новый файл означает новый префикс и новую нумерацию. Переезд самого файла новый файл означает новый префикс и новую нумерацию. Переезд самого файла
между осями идентификаторы не трогает. между осями идентификаторы не трогает.
@@ -111,90 +303,30 @@
Порядок правил в файле выбирается по читаемости, не по номерам: номер — это Порядок правил в файле выбирается по читаемости, не по номерам: номер — это
идентификатор, а не позиция. идентификатор, а не позиция.
## Правило, чья норма уехала в линтер
Когда правило механизировано у всех потребителей, его норма из канона
удаляется, а обоснование — нет. Остаётся **правило без модальности**, и
чтобы оно не выглядело недописанным, место нормы занимает отметка:
```markdown
### MIGR-6. Дефолтов времени в схеме БД нет
**МЕХАНИЗИРОВАНО.** Проверяется общим правилом линтера; формулировка
удалена, потому что дублировала работающую проверку.
**Почему.** Дефолт превращает забытую вставку в тихо работающий код…
```
- Идентификатор и заголовок сохраняются: ссылки из репозиториев продолжают
указывать на то же утверждение.
- «Почему» остаётся навсегда — линтер сообщает, что нарушено, но не
сообщает, зачем правило существует, и без обоснования нельзя понять,
когда проверку пора отменять.
- **МЕХАНИЗИРОВАНО** — не шестое модальное слово: оно не задаёт
обязательность, а сообщает, что обязательность теперь обеспечена машиной.
В остальном такое правило равно ДОЛЖЕН.
Факт «механизировано у всех» устанавливается вручную: канон по построению
не знает списка подписчиков, и обойти репозитории перед удалением нормы —
часть работы, а не то, что можно проверить автоматически.
## Таблицы вместо сценариев
Часть правил **классифицирует ситуации**: какой уровень лога, какая
категория директории, что делать с невалидным вводом в зависимости от его
источника. Для них каноническая форма — таблица «ситуация → вердикт»,
строки которой при необходимости нумеруются.
Таблица плотнее прозы и не даёт пропустить ветку: пустая клетка видна, а
неупомянутый случай в абзаце — нет.
## Чего мы не берём из OpenSpec
**GIVEN/WHEN/THEN.** У спецификации субъект — система, и её поведение
разворачивается во времени: состояние, событие, исход. У конвенции субъект
— автор кода, и разворачивать нечего: есть ситуация выбора и вердикт. Это
таблица, а не траектория.
**SHALL.** См. выше про словарь.
**Сценарии как общая форма.** Прозаический сценарий остаётся точечным
инструментом — для **стыка правил**, когда два правила вместе дают
неочевидный результат:
```
WHEN зависимость недоступна и ретраи вызова исчерпаны → ext-запись ERROR
AND тик фонового цикла упал по той же причине → доменная запись WARN
```
Такой блок ставится после обоих правил и ссылается на их номера. Если
стыков нет — сценариев в файле нет.
## Что правилом не является ## Что правилом не является
Модальные слова в этих частях **не употребляются** — иначе перестанет быть Заглавные модальные слова в этих частях **не употребляются** — иначе
понятно, что адресуемо, а что нет: перестанет быть понятно, что адресуемо, а что нет:
- **Область действия** — на что конвенция распространяется во времени - **Область действия** — на что конвенция распространяется во времени
(«новые таблицы; существующие не переписываются»). Это рамка для всех («новые таблицы; существующие не переписываются»). Это рамка для всех
правил файла, а не правило. правил файла, а не правило.
- **Связано** — ссылки на смежные конвенции, ADR, код. - **Связано** — ссылки на смежные конвенции, ADR, код.
- **Локальные регионы** — содержимое принадлежит репозиторию. - **Локальная часть копии** — содержимое принадлежит репозиторию.
- Вводная проза, объясняющая предмет конвенции. - Вводная проза, объясняющая предмет конвенции.
## Как на правила ссылаются копии ## Как на правила ссылаются копии
В репозитории: Ниже маркера локальной части, в репозитории:
```markdown ```markdown
<!-- local:механизировано --> <!-- conv:local -->
MIGR-2, MIGR-4 — `internal/archrules` (проверяются в новых миграциях).
<!-- /local -->
<!-- local:отступления --> MIGR-2, MIGR-4 механизированы — `internal/archrules` (проверяются в новых
MIGR-6 — не соблюдается в легаси-таблицах `show_history`, `queue`: составные миграциях).
MIGR-6 не соблюдается в легаси-таблицах `show_history`, `queue`: составные
ключи там появились до конвенции, переписывание требует миграции данных. ключи там появились до конвенции, переписывание требует миграции данных.
<!-- /local -->
``` ```
Отсюда видно и то, чего раньше не было видно: конвенция из восьми правил, Отсюда видно и то, чего раньше не было видно: конвенция из восьми правил,
@@ -202,22 +334,35 @@ MIGR-6 — не соблюдается в легаси-таблицах `show_hi
## Что стоит проверять машиной ## Что стоит проверять машиной
Сейчас не реализовано; список — на будущее для `conv`: Проверки применяются к файлам конвенций; обвязка канона в них не входит —
она ключевые слова цитирует, а не употребляет. Ни одна пока не реализована,
поэтому при ревью их выполняют чтением.
Разбором текста:
- модальные слова принадлежат объявленному словарю канона, а не смеси
словарей;
- префикс в шапке файла совпадает с реестром, состоит из четырёх заглавных - префикс в шапке файла совпадает с реестром, состоит из четырёх заглавных
латинских букв и не значится в списке выбывших; латинских букв, не начинается на `X` и не значится в списке выбывших;
- заголовки правил файла используют только его собственный префикс; - заголовки правил файла используют только его собственный префикс;
- номера уникальны внутри файла и не имеют пропусков вниз (новое правило - номера уникальны внутри файла и не имеют пропусков вниз (новое правило
берёт следующий свободный, а не первый освободившийся); берёт следующий свободный, а не первый освободившийся);
- у каждого `### <ПРЕФИКС>-<n>` есть модальное слово (или отметка - у каждого `### <ПРЕФИКС>-<n>` есть модальное слово и блок «Почему»;
МЕХАНИЗИРОВАНО) и блок «Почему»; отметка МЕХАНИЗИРОВАНО стоит рядом с модальностью, а не вместо неё;
- ссылки вида `<ПРЕФИКС>-<n>` — хоть в тексте канона, хоть в локальных - вводная проза содержит строку о версии языка;
регионах копии — указывают на правила, которые ещё существуют; - ссылки вида `<ПРЕФИКС>-<n>` — хоть в тексте канона, хоть в локальной части
- чужой префикс не встречается в абзаце с модальностью (META-20); копии — указывают на правила, которые ещё существуют;
- путь файла канона не встречается в тексте конвенции (META-21); - префиксы локальных правил копии начинаются на `X`;
- модальные слова не встречаются вне правил. - заглавные модальные слова не встречаются вне правил — кроме строки о
версии языка, которая их перечисляет по назначению;
- префикс **чужой темы** не встречается в абзаце с модальностью (META-20);
префикс своего же базового слоя там допустим — в собранной копии это
соседняя секция того же файла;
- путь файла канона не встречается в тексте конвенции (META-21).
## Порядок перевода Чтением, потому что машине не даётся:
Все конвенции канона записаны на этом языке. Новая конвенция пишется на нём - строки таблицы взаимоисключающи либо политика совпадения объявлена;
сразу; смешение форм в каноне больше не предполагается. - перечисленные в таблице случаи покрывают область действия;
- норма исполнима без обращения к другим файлам;
- обоснование отвечает на «что сломается», а не пересказывает норму.
+126 -90
View File
@@ -3,22 +3,22 @@
Общие конвенции для личных проектов. Репозитории берут отсюда копии в свой Общие конвенции для личных проектов. Репозитории берут отсюда копии в свой
`docs/conventions/`, коммитят их и живут дальше самостоятельно — как с `docs/conventions/`, коммитят их и живут дальше самостоятельно — как с
`ansible-roles`: канон не источник истины во время работы, а лавка, из `ansible-roles`: канон не источник истины во время работы, а лавка, из
которой берут и в которую возвращают улучшения. которой берут.
Сами конвенции лежат в `conventions/`, обвязка — в корне: Сами конвенции лежат в `conventions/`, обвязка — в корне:
| Файл | Что описывает | | Файл | Что описывает |
|---|---| |---|---|
| `README.md` | устройство канона, оси, синхронизация, жизненный цикл | | `README.md` | устройство канона, оси, сборка копий, жизненный цикл |
| [LANGUAGE.md](LANGUAGE.md) | язык записи правил: идентификаторы, модальность, «Почему» | | [LANGUAGE.md](LANGUAGE.md) | язык записи правил: идентификаторы, модальность, «Почему» |
| [GUIDE.md](GUIDE.md) | как ведут конвенции: когда заводить, механизация, отступления | | [GUIDE.md](GUIDE.md) | как ведут конвенции: когда заводить, механизация, отступления |
| `prefixes.toml` | реестр префиксов правил | | `prefixes.toml` | реестр префиксов правил |
| `conv` | синхронизация копий | | `conv` | сборка копий |
Обвязка живёт только в каноне и в репозитории не оказывается — `conv` Обвязка живёт только в каноне и в репозитории не оказывается — в копию едет
синхронизирует лишь содержимое `conventions/`. Пока это осознанное лишь содержимое `conventions/`. Самодостаточность копии это не нарушает:
ограничение: копия конвенции ссылается на `LANGUAGE.md` как на внешний конвенция называет язык записи одной строкой с номером версии и не ссылается
документ. на путь (`LANGUAGE.md`, раздел «Ссылка на язык из конвенции»).
Правило то же, что у ролей: **деплоится и читается только то, что лежит в Правило то же, что у ролей: **деплоится и читается только то, что лежит в
git репозитория**. Канон никем не подключается на лету. git репозитория**. Канон никем не подключается на лету.
@@ -28,7 +28,7 @@ git репозитория**. Канон никем не подключаетс
Конвенция формулируется независимо от конкретного приложения. Она задаёт Конвенция формулируется независимо от конкретного приложения. Она задаёт
правило; код ему следует. Обратное направление запрещено: то, что правило; код ему следует. Обратное направление запрещено: то, что
приложение уже делает иначе, **не является аргументом против правила** — это приложение уже делает иначе, **не является аргументом против правила** — это
отступление, и его место в локальном регионе того репозитория, а не в отступление, и его место в локальной части копии того репозитория, а не в
переформулировке канона. переформулировке канона.
Отсюда практические следствия: Отсюда практические следствия:
@@ -51,11 +51,12 @@ conventions/
stack/<стек>/ привязка к инструменту, хранилищу, транспорту stack/<стек>/ привязка к инструменту, хранилищу, транспорту
``` ```
Пути **файлов** даются относительно `conventions/`: `arch/db-identifiers.md`, Оси — раскладка **канона**; в репозитории копия лежит плоско, файлом на
а не `conventions/arch/…` — так же, как они лягут в `docs/conventions/` тему. Пути файлов даются относительно `conventions/`
репозитория. На **правила** ссылаются идентификатором без пути: `KEYS-5`. (`arch/db-identifiers.md`) и адресуют исходник канона, а не место в копии.
Префикс уникален по всему канону (реестр — `prefixes.toml`), поэтому На **правила** ссылаются идентификатором без пути: `KEYS-5`. Префикс
идентификатор не зависит от того, на какой оси файл лежит сегодня. уникален по всему канону (реестр — `prefixes.toml`), поэтому идентификатор
не зависит ни от оси, ни от того, как собран файл у потребителя.
Тест — по тому, замена чего убивает правило: Тест — по тому, замена чего убивает правило:
@@ -93,6 +94,12 @@ prefix: KEYS
по формуле, и не переиспользуется никогда. Правила адресуются идентификатором по формуле, и не переиспользуется никогда. Правила адресуются идентификатором
`KEYS-5` — без пути к файлу. Подробности формы — `LANGUAGE.md`. `KEYS-5` — без пути к файлу. Подробности формы — `LANGUAGE.md`.
Буква `X` в начале префикса зарезервирована за репозиториями: канон её не
занимает никогда, а локальные правила потребителя берут префиксы только на
неё (`XTIM`, `XLOG`). Так столкновение локального префикса с будущим
префиксом канона невозможно по построению, и согласовывать заранее ничего не
нужно.
## Расширение ## Расширение
Файл в `lang/` или `stack/` может объявить в шапке ещё и базу: Файл в `lang/` или `stack/` может объявить в шапке ещё и базу:
@@ -106,67 +113,88 @@ extends: arch/db-identifiers.md
неверно сформулировано условие применимости (чинится в каноне), либо неверно сформулировано условие применимости (чинится в каноне), либо
репозиторий на базу просто не подписан. репозиторий на базу просто не подписан.
`extends` — документация связи, а не механизм: `conv` о ней только `extends` — документация связи, а не механизм: за тем, чтобы база лежала
напоминает при `add` и никак не следит за тем, чтобы база лежала рядом. рядом, никто не следит.
## Служебная разметка ## Копия в репозитории
**Шапка копии** ставится при `conv add` и в каноне не хранится: Копия плоская: **один файл на тему**, слои осей идут внутри него секциями в
порядке `arch` → язык → стек. Пути канона в копии не воспроизводятся.
```yaml
---
origin: arch/time.md # откуда взято
origin_hash: a1b2c3d4 # отпечаток канона на момент синхронизации
synced: 2026-07-25
local: нет # или: чем и почему разошлись
---
```
`origin_hash` — контент-отпечаток, а не git-SHA. Он позволяет отличать
«канон обновился» от «изменено локально»; без него `status` умеет только
«differs», а такой отчёт быстро перестают читать. Прочие ключи шапки
(`prefix`, `extends`) — часть документа: они сравниваются наравне с телом.
**Локальные регионы** — куски, принадлежащие репозиторию по определению.
Из сравнения исключаются, поэтому вечного шума в `diff` не дают:
```markdown
<!-- local:механизировано -->
`AUTOINCREMENT` в новых миграциях — `internal/archrules`.
<!-- /local -->
```
Имя обязательно — перенос при `pull` идёт по именам, безымянные регионы
`conv` отвергает. Что всегда локально:
- **механизация** — канон не знает, у кого линтер уже настроен;
- **отступления** — «у нас пока не так», честно и поимённо;
- **разрешение условия** — «Здесь: INTEGER PK, id наружу не выходят»;
- **эталоны и ссылки** — имена функций, файлов, ADR конкретного репозитория;
- **список конвенций** в README репозитория.
Путь файла в каноне и имя региона — это API: переименование осиротит все
копии (`origin` строковый). Переименовывать — только вместе с обходом
потребителей.
После `pull` копию нужно перечитать глазами: содержимое региона могло
устареть относительно переписанного вокруг текста, и автоматика этого не
увидит.
## Раскладка в репозитории
Копии повторяют структуру канона:
``` ```
docs/conventions/ docs/conventions/
README.md собственный, не синхронизируется README.md собственный, не собирается
arch/db-identifiers.md time.md arch/time.md + lang/go/time.md
lang/go/db-identifiers.md db-identifiers.md arch/db-identifiers.md + lang/go/db-identifiers.md
app-directories.md arch/… + stack/ansible/…
``` ```
Подписка не описана отдельным файлом — она **и есть** набор лежащих файлов, Так конвенция остаётся самодостаточным документом: тот, кто проверяет по ней
видимый в `git ls-files`. README директории перечисляет их одной плоской код — человек или агент, — читает один файл и не собирает тему из трёх мест.
таблицей, чтобы вложенность не мешала навигации.
**Шапка копии** ставится при сборке и в каноне не хранится:
```yaml
---
origin: time
---
```
Больше в шапке ничего нет. Отпечатка канона и даты синхронизации в ней не
хранится: обновление перезаписывает файл в рабочем дереве, и что именно
изменилось, показывает `git diff` до коммита. Второй механизм сравнения
рядом с git не нужен.
**Маркер локальной части** — единственная машинно значимая разметка внутри
файла:
```markdown
<!-- conv:local -->
MIGR-2, MIGR-4 механизированы — `internal/archrules`.
MIGR-6 не соблюдается в `show_history`, `queue`: составные ключи там
появились до конвенции, миграция данных не окупается.
```
Всё ниже маркера принадлежит репозиторию и переживает обновление; всё выше —
пересобирается из канона. Маркер один и безымянный, поэтому у него нет
имени, которое можно осиротить переименованием.
Ниже маркера живёт то, чего канон о репозитории не знает: механизация,
отступления, разрешение условий («Здесь: INTEGER PK, id наружу не выходят»),
ссылки на ADR и код, а также **собственные правила**с префиксом на `X`,
по тем же правилам формы, что и канон.
Если местных правок стало больше, чем каноничного текста, копия перестаёт
быть копией: `origin:` из шапки убирают, и дальше это обычный документ
репозитория. Файл, оставивший шапку, при следующем обновлении потеряет
всё, что выше маркера.
## Манифест
Откуда взяты копии и где брать обновления — `.conventions.toml` в корне
репозитория-потребителя:
```toml
source = "ssh://git@git.vakhrushev.me:2222/av/dev-conventions.git"
lang = ["go"]
stack = ["sqlite", "htmx"]
topics = ["time", "config", "db-identifiers"]
```
`lang` и `stack` выбирают строку разреженной матрицы: файл темы собирает
только те слои, которые репозиторию подходят. `topics` — подписка; списка
подписчиков у канона по-прежнему нет, список тем есть только у потребителя.
Как именно инструмент добирается до канона — путь на диске, git, HTTP —
дело инструмента, а не модели. Манифест отвечает на два вопроса: откуда это
взято и где искать обновления.
Отдельного лок-файла нет. Он отвечал бы на «что было в прошлый раз», а на
это отвечает git: копии закоммичены, автоматического обновления не
существует, и любое изменение проходит через чтение диффа человеком.
## Контракт с агентом ## Контракт с агентом
@@ -175,45 +203,53 @@ docs/conventions/
`AGENTS.md` каждого потребителя должен явно говорить: `AGENTS.md` каждого потребителя должен явно говорить:
> Файлы в `docs/conventions/` с шапкой `origin:` — копии из канона > Файлы в `docs/conventions/` с шапкой `origin:` — копии из канона
> `dev-conventions`. Репозиторное пишется только внутрь > `dev-conventions`. Репозиторное пишется только ниже `<!-- conv:local -->`;
> `<!-- local:… -->`. Правка вне регионов — либо `conv push` в канон, либо > всё выше маркера перезаписывается при обновлении. Своё правило — с
> запись причины в `local:`. > префиксом на `X`.
## Команды ## Команды
```bash ```bash
conv list # что есть в каноне conv list # какие темы есть в каноне
conv add arch/time.md # взять к себе (можно несколько за раз) conv add time # добавить тему в манифест и собрать файл
conv status # ok / изменено локально / канон обновился / разошлись conv pull # пересобрать всё, что перечислено в манифесте
conv diff [arch/time.md] # чем копия отличается, без учёта локальных регионов
conv pull arch/time.md # забрать обновление канона (регионы переносятся)
conv push arch/time.md # вернуть локальное улучшение в канон
conv push --new lang/go/x.md # завести в каноне новую конвенцию
``` ```
`status` и `diff` всегда завершаются кодом 0: это отчёт, а не проверка. Отчёт о том, что изменилось, отдельной командой не выдаётся: после `pull`
Расхождение — нормальное состояние, а постоянный шум в `diff` означает не его показывает `git diff`, а решение — принять, поправить или откатить —
«догони канон», а «пора разрезать файл». Симметрично: разросшийся до спора принимает человек перед коммитом.
с базой локальный регион означает «пора чинить условие применимости в
каноне». Транспорт обратно в канон не предусмотрен. Улучшение, найденное в
репозитории, переносится в канон руками: это редкая операция, и её цена —
не аргумент против того, чтобы направление оставалось односторонним.
Запускать из корня репозитория: Запускать из корня репозитория:
```bash ```bash
~/projects/private/dev-conventions/conv status ~/projects/private/dev-conventions/conv pull
``` ```
Обёртка в раннере репозитория (`inv conventions -- status` для ansible, Обёртка в раннере репозитория (`inv conventions -- pull` для ansible,
`task conventions -- status` для Go) — тонкий проброс аргументов, чтобы `task conventions -- pull` для Go) — тонкий проброс аргументов, чтобы
логика не размножалась по репозиториям в двух диалектах. логика не размножалась по репозиториям в двух диалектах.
## Жизненный цикл ## Жизненный цикл
- **В канон.** Новая конвенция пишется в том репозитории, где заболело, и - **В канон.** Новая конвенция пишется в том репозитории, где заболело, и
продвигается `conv push --new`. Локальные регионы при этом опустошаются: переносится в канон, когда стало ясно, что общего в ней больше, чем
в канон едет только норма. местного. Локальная часть при этом не едет: в канон попадает только норма,
а префикс на `X` меняется на канонический — то есть правила получают новые
идентификаторы.
- **Из канона.** Устаревшая конвенция удаляется вместе с обходом - **Из канона.** Устаревшая конвенция удаляется вместе с обходом
потребителей — тихо осиротить копии нельзя. потребителей — тихо осиротить копии нельзя.
- **История.** Канон коммитится при каждом `push`: `origin_hash` отвечает - **История.** Канон коммитится при каждой правке: только git канона
на «отличается ли», но только git канона отвечает на «почему база отвечает на вопрос, почему база сформулирована так.
сформулирована так».
## Состояние
Модель выше — согласованная, а не реализованная. `conv` пока собран под
прежнюю: зеркальное дерево копий вместо плоского, именованные регионы
`<!-- local:имя -->` вместо одного маркера, `origin_hash` в шапке и команды
`status`, `diff`, `push`. Двенадцать файлов канона всё ещё несут 31 пустой
именованный регион. Ни один репозиторий-потребитель не подключён, поэтому
переход никого не ломает.
+125 -156
View File
@@ -1,39 +1,10 @@
# К обсуждению # К обсуждению
Черновик для следующего разговора: вопросы и варианты, а не принятые Черновик для следующего разговора: вопросы и варианты, а не принятые
решения. решения. Закрытый вопрос отсюда удаляется — принятое решение живёт в
`README.md`, `GUIDE.md` или `LANGUAGE.md`, а не в этом файле.
## 1. Ссылка на `LANGUAGE.md` не переживает сборку ## 1. Тулинг: две разные задачи в одном `conv`
Все двенадцать конвенций во вводной прозе пишут «Форма записи —
`LANGUAGE.md`». Обвязка в репозиторий не едет (`conv` синхронизирует только
`conventions/`), поэтому в собранной копии эта ссылка указывает в никуда.
Это тот же класс дефекта, который вчера закрыло META-21 для ссылок между
темами: путь, верный в каноне, молча умирает при сборке. Разница в том, что
META-21 предлагает заменить путь на имя темы — а здесь заменять не на что,
целевого документа в репозитории просто нет.
Варианты, которые видно сейчас:
- **Убрать упоминание из тел конвенций.** Язык записи — забота того, кто
пишет канон, а не того, кто читает конвенцию в своём репозитории. Читателю
достаточно самого текста: модальные слова и «Почему» самоописательны.
Дешевле всего, но копия теряет указание, по каким правилам её править.
- **Возить обвязку вместе с конвенциями.** Тогда `LANGUAGE.md` и, возможно,
`GUIDE.md` появляются в `docs/conventions/` как ещё одни синхронизируемые
файлы. Честно, но противоречит нынешнему разделению «канон — конвенции,
корень — обвязка» и добавляет в репозиторий текст, который агенту при
чтении конвенции не нужен.
- **Вкладывать короткую преамбулу в собранный файл.** Сборщик пишет в шапку
три-четыре строки: что такое модальное слово, что «Почему» обязательно,
где лежит полный документ. Самодостаточно и не тащит весь язык, но
преамбула дублируется в каждом файле темы.
Сопутствующее: `GUIDE.md` в телах конвенций не упоминается ни разу —
проверено, так что вопрос только про `LANGUAGE.md`.
## 2. Тулинг: две разные задачи в одном `conv`
Сейчас в `conv` смешаны две категории работы, и они расходятся по всему — Сейчас в `conv` смешаны две категории работы, и они расходятся по всему —
по частоте запуска, по тому, кто запускает, и по тому, что считается по частоте запуска, по тому, кто запускает, и по тому, что считается
@@ -41,16 +12,18 @@ META-21 предлагает заменить путь на имя темы —
**Целостность канона.** Префиксы уникальны и не переиспользованы, шапка **Целостность канона.** Префиксы уникальны и не переиспользованы, шапка
совпадает с реестром, номера без дыр вниз, у каждого правила модальность и совпадает с реестром, номера без дыр вниз, у каждого правила модальность и
«Почему», ссылки разрешаются, чужой префикс не лезет в норму (META-20), «Почему», ссылки разрешаются, префикс чужой темы не лезет в норму (META-20),
путей канона в тексте нет (META-21). Запускается в каноне, при каждой путей канона в тексте нет (META-21), строка о версии языка на месте.
правке, провал — это ошибка. Логика уже написана и много раз прогнана Запускается в каноне, при каждой правке, провал — это ошибка. Логика уже
руками, но живёт в скретчпаде, а не в репозитории. написана и много раз прогнана руками, но живёт в скретчпаде, а не в
репозитории.
**Установка в проект.** Манифест `.conventions.toml`, сборка файла темы из **Установка в проект.** Манифест `.conventions.toml`, сборка файла темы из
секций (arch → языки → стеки → local), сохранение локальной секции при слоёв (`arch` → язык → стек), сохранение при пересборке всего, что ниже
пересборке, отчёт «канон ушёл вперёд», предупреждение о висячих ссылках на маркера, предупреждение о висячих ссылках на неподписанные темы. Запускается
неподписанные темы. Запускается в репозитории-потребителе, изредка, провал — в репозитории-потребителе, изредка, провал — это чаще «посмотри глазами»,
это чаще «посмотри глазами», чем «ошибка». чем «ошибка». Отчёта «канон ушёл вперёд» здесь нет: его делает `git diff`
после пересборки.
Что обсудить: Что обсудить:
@@ -63,46 +36,19 @@ META-21 предлагает заменить путь на имя темы —
ссылках: это установка, а не целостность, но список подписок ему нужен из ссылках: это установка, а не целостность, но список подписок ему нужен из
манифеста. манифеста.
## 3. Согласованная модель сборки нигде не записана Перед тем как переписывать, стоит посмотреть на два готовых прототипа:
дистрибуцию пакетов Vale (`.vale.ini``vale sync``styles/`) как образец
манифеста и `vendir.yml` — как пример того, где проходит граница между «чего
хочу» и «что получил».
Самое срочное. Договорённости про плоскую раскладку живут только в ## 2. Именованные регионы — удалить из канона
переписке, а репозиторий описывает прежнюю модель — и противоречит новой в
нескольких местах сразу.
Что решено, но не зафиксировано: Решение принято и записано: регионов нет, есть один маркер
`<!-- conv:local -->`, который ставит сборщик в копии. Значит регионы не
переносятся в единую секцию, а **удаляются**: в каноне их нечем наполнять,
локальное принадлежит копии.
- копия плоская, файл на тему: `docs/conventions/config.md`, а не дерево Остаётся механическая работа — **31 регион в 12 файлах**:
`arch/` + `lang/`;
- файл темы собирается из секций `arch → языки → стеки → local` с
машиночитаемыми маркерами `<!-- conv:section … -->` и `<!-- conv:local -->`;
- языки и стеки образуют разреженную матрицу; файл темы собирает её строку,
многоязычная тема держит несколько языковых секций в одном файле;
- выбор описывается манифестом `.conventions.toml` в корне
репозитория-потребителя; путь к канону там **не** хранится;
- направление строго одностороннее: канон → код. Правка канона делается
руками в каноне, потом пересборка;
- локальные префиксы правил репозитория объявляются в манифесте и не
пересекаются с реестром канона.
Что этому прямо противоречит в репозитории сейчас:
- `README.md` → «Раскладка в репозитории» показывает зеркальное дерево
`docs/conventions/arch/db-identifiers.md`;
- `README.md` → «Команды» и «Контракт с агентом» описывают `conv push` и
`conv push --new` как штатный путь; при односторонней модели транспорт
назад исчезает, остаётся только детект «копия правлена вне локальной
секции»;
- `conv` содержит `cmd_push` со всей обвязкой (`--new`, `--force`).
## 4. Именованные регионы → одна локальная секция
Решено заменить регионы `<!-- local:имя -->` на одну локальную секцию в
конце собранного файла: отступление ссылается на идентификатор правила
(`DIRS-5`), а не стоит рядом с ним. Это то, что делает
сравнение копии с каноном одним хешем и выкидывает из `conv` перенос
регионов по именам.
Сделать перенос ещё предстоит: в каноне сейчас **31 регион в 12 файлах**.
``` ```
7 связано 7 отступления 7 механизировано 7 связано 7 отступления 7 механизировано
@@ -110,11 +56,7 @@ META-21 предлагает заменить путь на имя темы —
1 проверки / поля / модель-владельца / маппинг / границы 1 проверки / поля / модель-владельца / маппинг / границы
``` ```
Отдельно решить, что делать с `связано`: сейчас это репо-специфичная часть ## 3. Пары слоёв и темы без базы
раздела «Связано» (META-17), и при единой локальной секции она переезжает
туда же — надо проверить, что META-17 после этого не противоречит сам себе.
## 5. Пары слоёв и темы без базы
Отложено сознательно, но список стоит держать перед глазами: Отложено сознательно, но список стоит держать перед глазами:
@@ -130,75 +72,40 @@ META-21 предлагает заменить путь на имя темы —
проверить, что так и задумано. проверить, что так и задумано.
- В `lang/go/db-schema.md` сидит целый пласт `stack/sqlite/` (типы колонок), - В `lang/go/db-schema.md` сидит целый пласт `stack/sqlite/` (типы колонок),
тоже из известного долга README. тоже из известного долга README.
- Вынос арх-ядра из `errors` и `logging` закроет две хрупкие ссылки из
`web-ui` (стек) в go-слой — единственные ссылки стек → язык в каноне.
## 6. Подключение к репозиториям ## 4. Подключение к репозиториям
Ничего ещё не подключено. Кандидаты — jellybit и pet-project-server. Ничего ещё не подключено. Кандидаты — jellybit и pet-project-server.
Понадобится: заполнить локальные секции тем, что сейчас в этих репозиториях Понадобится: заполнить локальную часть копий тем, что сейчас в этих
записано по факту; обёртка в раннере (`inv conventions` / `task conventions`, репозиториях записано по факту; обёртка в раннере (`inv conventions` /
единый интерфейс команд у трёх ansible-репозиториев); строка в `AGENTS.md` `task conventions`, единый интерфейс команд у трёх ansible-репозиториев);
каждого потребителя про то, что файлы в `docs/conventions/` — копии. строка в `AGENTS.md` каждого потребителя про то, что файлы в
`docs/conventions/` — копии.
Открытый кусок с прошлого раза: **как копия ссылается на канон, не ломая ## 5. Описание языка отдельно от набора конвенций
самодостаточность**. Абсолютный путь `~/projects/private/dev-conventions`
в закоммиченном файле не годится — репозиторий перестаёт быть
самодостаточным и получает хардкод путей. Пересекается с вопросом 1.
## 7. Не переизобретено ли это
Проверить перед тем, как вкладываться в тулинг. Ближайшие кандидаты, куда
смотреть:
- **copier / cruft** — шаблон проекта с последующим `update`: ровно та же
задача «стянуть обновление апстрима, не затерев локальные правки», с
ответом через три-way merge вместо наших регионов. Стоит понять, почему у
них merge, а у нас исключение из сравнения.
- **vendir** — вендоринг чужого содержимого с лок-файлом; ближе к нашей
модели «канон это лавка».
- **Vale** — линтер прозы с правилами в файлах: значительная часть проверок
целостности канона (модальные слова вне правил, запрещённые формулировки)
выражается его языком.
- **RFC 2119 / 8174** — канонический источник модальных слов; наш словарь
фактически его перевод, полезно сверить границы значений.
- **EARS** — шаблоны требований (ubiquitous / event-driven / state-driven);
соседняя формализация того же, что мы решили таблицами.
- **Наборы правил для агентов** — `AGENTS.md`, cursor rules, скиллы: задачу
«раздать читаемые агентом договорённости по репозиториям» сейчас решают
несколько продуктов, и там уже могли устояться форматы.
## 8. Мультиязычность ключевых слов
Модальные слова сейчас русские, и это осознанно: разный словарь держит
границу «конвенция — не спецификация» бесплатно. Вопрос — нужен ли
английский набор параллельно.
За: канон может однажды понадобиться на английском; агенты натренированы на
MUST/SHOULD плотнее, чем на ДОЛЖЕН/СЛЕДУЕТ. Против: два набора — это два
способа записать одно, и проверка «модальные слова не встречаются вне
правил» усложняется вдвое.
Если делать, то таблица ключевых слов должна принадлежать **описанию
языка**, а не каждой конвенции — то есть вопрос завязан на следующий.
## 9. Описание языка отдельно от набора конвенций
`LANGUAGE.md` и `GUIDE.md` описывают, **как** пишутся конвенции; `LANGUAGE.md` и `GUIDE.md` описывают, **как** пишутся конвенции;
`conventions/`**один конкретный** набор. Сейчас они склеены в одном `conventions/`**один конкретный** набор. Сейчас они склеены в одном
репозитории, и из одного описания нельзя собрать второй набор (рабочий, репозитории, и из одного описания нельзя собрать второй набор (рабочий,
доменный, чужой). доменный, чужой).
Что это даёт, если разнести: Срочности нет: версия языка объявлена в самом `LANGUAGE.md` (ключ
`version:`), и конвенции ссылаются на неё номером, а не путём, — то есть
самодостаточность копии выноса не требует.
- канон объявляет, какой версии языка следует, а тулинг валидирует набор Что осталось поводом:
**против объявленного описания**, а не против зашитых в код правил;
- таблица модальных слов (вопрос 8) живёт в описании и одна на все наборы;
- вопрос 1 (`LANGUAGE.md` не переживает сборку) меняет форму: собранный файл
ссылается не на путь, а на язык с версией.
Цена — ещё одна сущность и ещё одна синхронизация. Проверить, не выйдет ли - из одного описания по-прежнему нельзя собрать второй набор;
лечение хуже болезни при одном пользователе. - тулинг валидирует правила, зашитые в его код, а не объявленную версию
языка.
## 10. Тулинг на Go, живущий независимо Оба повода включаются, только когда появится второй набор. Цена — ещё одна
сущность и ещё одна синхронизация; при одном наборе лечение выходит хуже
болезни.
## 6. Тулинг на Go, живущий независимо
Сейчас `conv` — питоновский скрипт внутри канона, то есть тулинг и данные в Сейчас `conv` — питоновский скрипт внутри канона, то есть тулинг и данные в
одном репозитории и правятся одним движением. Мысль: вынести в отдельный одном репозитории и правятся одним движением. Мысль: вынести в отдельный
@@ -207,34 +114,96 @@ Go-бинарь со своим релизным циклом, ставить ч
Что за этим стоит помимо вкуса: независимый бинарь физически не даёт править Что за этим стоит помимо вкуса: независимый бинарь физически не даёт править
инструмент «заодно» с правкой конвенции, работает против **любого** канона и инструмент «заодно» с правкой конвенции, работает против **любого** канона и
любого потребителя — что прямо требуется вопросом 9, — и снимает питон из любого потребителя — что прямо требуется вопросом 5, — и снимает питон из
зависимостей репозиториев-потребителей. зависимостей репозиториев-потребителей.
Связано с вопросом 2: если тулинг всё равно переписывается, разделение Порядок обратный ожидаемому: пока вопрос 5 не сделан, инструмент всё равно
«целостность канона / установка в проект» дешевле заложить сразу, чем работает против одного конкретного канона, и независимый релизный цикл ему
отпиливать потом. нечего обслуживать. Сначала 5, потом 6. Разделение из вопроса 1 при этом
дешевле заложить сразу, чем отпиливать потом.
## 11. Ссылки на родительский слой своей темы ## 7. META-20 и родительский слой своей темы
Из `lang/` и `stack/` ссылаться на идентификаторы своего же арх-слоя Из `lang/` и `stack/` ссылаться на идентификаторы своего же арх-слоя
безопасно: при сборке они оказываются секциями одного файла, и ссылка безопасно: при сборке они оказываются секциями одного файла, и ссылка
никуда не ведёт — правило рядом. Ограничение META-20 писалось именно про никуда не ведёт — правило рядом. Ограничение META-20 писалось про **чужую
**чужую тему**, так что формально это уже разрешено. тему**, так что формально это уже разрешено, и формулировка проверки в
`LANGUAGE.md` под это исправлена.
Но стоит проговорить явно, потому что сейчас читается уже как запрет: Осталось решить одно: добавлять ли в META-20 явную строку **ДОПУСКАЕТСЯ**
про родительский слой. За — правило, которое читают строже, чем оно есть,
заставляет авторов дублировать текст без нужды. Против — норма META-20 уже
говорит «чужой темы», и второе правило про то же место придётся держать
согласованным с первым.
- в `LANGUAGE.md` проверка сформулирована как «**чужой префикс** не ## 8. `WHEN`/`AND` в блоке стыка правил
встречается в абзаце с модальностью» — по букве это ловит и `GTIM`
`TIME-3`, то есть ложно срабатывает на ровно том случае, который
разрешён. Должно быть «префикс **чужой темы**»;
- обсудить, не добавить ли в META-20 явную строку **ДОПУСКАЕТСЯ** про
родительский слой: правило, которое читают как более строгое, чем оно
есть, заставляет авторов дублировать текст без нужды.
## Из вчерашнего, не закрыто Блок для стыка двух правил записан английскими словами:
```
WHEN зависимость недоступна и ретраи вызова исчерпаны → ext-запись ERROR
AND тик фонового цикла упал по той же причине → доменная запись WARN
```
Рядом сказано, что `SHALL` не берётся ни в один словарь, потому что занят
OpenSpec. `WHEN` и `AND` — из того же набора и по той же причине должны бы
не браться, но взяты. Это нестыковка, а не решение.
Варианты:
- **перевести** на `КОГДА` / `И`: словарь набора один, и служебные слова
внутри канона следуют ему же;
- **оставить и объяснить**: блок стыка описывает поведение системы во
времени, а не выбор автора, — то есть это единственное место, где форма
спецификации уместна, и заимствование её синтаксиса намеренно.
Второе честнее по смыслу (субъект там действительно система), но требует
явной оговорки в `LANGUAGE.md`, иначе читается как недосмотр. Заодно решить,
подпадают ли `WHEN`/`AND` под проверку «заглавные модальные слова не
встречаются вне правил»: сейчас формально нет, потому что в словаре их нет.
## 9. Критерий «названного вреда» никого не обязывает
`LANGUAGE.md` говорит, что ДОЛЖЕН требует двух условий сразу: нарушение
причиняет названный вред (критерий BCP 14) и норма проверяема машиной
(META-6). Но правила под первое условие нет: META-6 работает только в одну
сторону — «нет машинной проверки, понижай в СЛЕДУЕТ». Обратной, «проверка
есть, а вреда нет — не повышай», не существует, и автор ничем не связан.
Решить, заводить ли META-24 под первое условие или оставить его семантикой
шкалы в описании языка. За правило: механически проверяемых мелочей больше,
чем важных вещей, и без нормы шкала размывается тем же способом, от которого
META-6 её защищает. Против: критерий «вред назван» проверяется чтением, а не
машиной, — то есть по META-6 сам он может быть только СЛЕДУЕТ.
## 10. Одиннадцать таблиц не прочитаны на взаимоисключительность
`LANGUAGE.md` объявил, что строки таблицы решений взаимоисключающи по
умолчанию, а иной порядок объявляется явно. Объявление не делает таблицы
такими: нумерованные строки есть в одиннадцати файлах канона, и ни одна
таблица под новое требование не прочитана.
Конкретный подозреваемый — SLOG-11: «по реальному действию или изменению»
против «повторяющаяся служебная, по таймеру или поллингу». Периодическая
операция, которая всё-таки меняет данные, подходит под обе строки, и уровень
из таблицы не выводится однозначно.
Работа читательская, машине не даётся; в список проверок она уже записана в
разделе «Чтением, потому что машине не даётся».
## 11. Возможность, записанная модальным словом
Четвёртая категория ISO — возможность и осуществимость — ключевого слова не
имеет: такие утверждения пишутся обычной прозой. Значит канон надо просмотреть
на обратную ошибку: где утверждение о факте («библиотеки по умолчанию отдают
именно его», «SQLite сравнивает строки побайтово») записано модальным словом
и тем самым превратилось в норму, которую никто не вводил.
Смотреть в первую очередь абзацы «Почему»: там факты и стоят, там же соблазн
усилить их модальностью выше всего.
## Мелкое, не закрыто
- Вынос арх-ядра из `errors` и `logging`: закроет две хрупкие ссылки из
`web-ui` (стек) в go-слой — единственные ссылки стек → язык в каноне.
- `conv check` должен уметь отличать ссылку на удалённое правило от - `conv check` должен уметь отличать ссылку на удалённое правило от
упоминания дыры: сейчас `META-16` в прозе «Оформления» даёт ложное упоминания дыры: сейчас `META-16` в прозе «Оформления» даёт ложное
срабатывание. срабатывание.
+5 -1
View File
@@ -8,7 +8,11 @@ prefix: DIRS
создания и ценности содержимого: конфигурация, данные, кеш. Категория сразу создания и ценности содержимого: конфигурация, данные, кеш. Категория сразу
отвечает на два вопроса, которые иначе выясняются чтением кода приложения: отвечает на два вопроса, которые иначе выясняются чтением кода приложения:
**кто создаёт** содержимое и **что будет, если его потерять**. Из категорий **кто создаёт** содержимое и **что будет, если его потерять**. Из категорий
механически выводится состав бэкапа. Форма записи — `LANGUAGE.md`. механически выводится состав бэкапа.
Ключевые слова ДОЛЖЕН, НЕ ДОЛЖЕН, СЛЕДУЕТ, НЕ СЛЕДУЕТ, ДОПУСКАЕТСЯ и отметка
МЕХАНИЗИРОВАНО толкуются как описано в языке конвенций версии 1 — тогда и
только тогда, когда написаны заглавными.
## Область действия ## Область действия
+5 -1
View File
@@ -5,7 +5,11 @@ prefix: CONF
# Конфигурация приложения # Конфигурация приложения
Как устроена конфигурация: где лежит, как попадает в процесс, что с Как устроена конфигурация: где лежит, как попадает в процесс, что с
секретами и когда падает. Форма записи — `LANGUAGE.md`. секретами и когда падает.
Ключевые слова ДОЛЖЕН, НЕ ДОЛЖЕН, СЛЕДУЕТ, НЕ СЛЕДУЕТ, ДОПУСКАЕТСЯ и отметка
МЕХАНИЗИРОВАНО толкуются как описано в языке конвенций версии 1 — тогда и
только тогда, когда написаны заглавными.
## Область действия ## Область действия
+5 -2
View File
@@ -4,8 +4,11 @@ prefix: KEYS
# Идентификаторы сущностей # Идентификаторы сущностей
Как выбираются и как выглядят первичные ключи сущностей. Форма записи — Как выбираются и как выглядят первичные ключи сущностей.
`LANGUAGE.md`.
Ключевые слова ДОЛЖЕН, НЕ ДОЛЖЕН, СЛЕДУЕТ, НЕ СЛЕДУЕТ, ДОПУСКАЕТСЯ и отметка
МЕХАНИЗИРОВАНО толкуются как описано в языке конвенций версии 1 — тогда и
только тогда, когда написаны заглавными.
## Область действия ## Область действия
+5 -2
View File
@@ -5,8 +5,11 @@ prefix: TIME
# Время # Время
Как приложение записывает моменты и длительности: в каком формате, откуда Как приложение записывает моменты и длительности: в каком формате, откуда
берётся значение и где появляется не-UTC. Форма записи — берётся значение и где появляется не-UTC.
`LANGUAGE.md`.
Ключевые слова ДОЛЖЕН, НЕ ДОЛЖЕН, СЛЕДУЕТ, НЕ СЛЕДУЕТ, ДОПУСКАЕТСЯ и отметка
МЕХАНИЗИРОВАНО толкуются как описано в языке конвенций версии 1 — тогда и
только тогда, когда написаны заглавными.
## Область действия ## Область действия
+5 -1
View File
@@ -6,7 +6,11 @@ extends: arch/config.md
# Конфигурация: реализация на Go # Конфигурация: реализация на Go
Как базовый слой выглядит в Go-приложении: формат, загрузчик, границы Как базовый слой выглядит в Go-приложении: формат, загрузчик, границы
запрета на окружение. Форма записи — `LANGUAGE.md`. запрета на окружение.
Ключевые слова ДОЛЖЕН, НЕ ДОЛЖЕН, СЛЕДУЕТ, НЕ СЛЕДУЕТ, ДОПУСКАЕТСЯ и отметка
МЕХАНИЗИРОВАНО толкуются как описано в языке конвенций версии 1 — тогда и
только тогда, когда написаны заглавными.
Секретов Go-специфика не касается: они приходят из деплоя уже в файле, а Секретов Go-специфика не касается: они приходят из деплоя уже в файле, а
проверка их непустоты идёт вместе с остальной валидацией — как описано в проверка их непустоты идёт вместе с остальной валидацией — как описано в
+5 -2
View File
@@ -5,8 +5,11 @@ extends: arch/db-identifiers.md
# Идентификаторы: реализация на Go # Идентификаторы: реализация на Go
Как базовый слой выглядит в Go-приложении. Форма записи — Как базовый слой выглядит в Go-приложении.
`LANGUAGE.md`.
Ключевые слова ДОЛЖЕН, НЕ ДОЛЖЕН, СЛЕДУЕТ, НЕ СЛЕДУЕТ, ДОПУСКАЕТСЯ и отметка
МЕХАНИЗИРОВАНО толкуются как описано в языке конвенций версии 1 — тогда и
только тогда, когда написаны заглавными.
Единая точка из `KEYS-3` — пакет `internal/ident`: он Единая точка из `KEYS-3` — пакет `internal/ident`: он
порождает идентификаторы (`NewID`, `NewIDAt`) и он же их разбирает порождает идентификаторы (`NewID`, `NewIDAt`) и он же их разбирает
+5 -1
View File
@@ -5,7 +5,11 @@ prefix: MIGR
# Схема и миграции (SQLite, Go) # Схема и миграции (SQLite, Go)
Как пишутся миграции и какие типы колонок выбираются для схемы SQLite в Как пишутся миграции и какие типы колонок выбираются для схемы SQLite в
Go-приложении. Форма записи — `LANGUAGE.md`. Go-приложении.
Ключевые слова ДОЛЖЕН, НЕ ДОЛЖЕН, СЛЕДУЕТ, НЕ СЛЕДУЕТ, ДОПУСКАЕТСЯ и отметка
МЕХАНИЗИРОВАНО толкуются как описано в языке конвенций версии 1 — тогда и
только тогда, когда написаны заглавными.
## Область действия ## Область действия
+7 -3
View File
@@ -4,9 +4,13 @@ prefix: GERR
# Ошибки # Ошибки
Как ошибки строятся, оборачиваются и проверяются. Форма записи — Как ошибки строятся, оборачиваются и проверяются. Где и когда ошибку
`LANGUAGE.md`. Где и когда ошибку **логировать** — в конвенции `logging` **логировать** — в конвенции `logging` (коротко: лог один раз на доменной
(коротко: лог один раз на доменной границе). границе).
Ключевые слова ДОЛЖЕН, НЕ ДОЛЖЕН, СЛЕДУЕТ, НЕ СЛЕДУЕТ, ДОПУСКАЕТСЯ и отметка
МЕХАНИЗИРОВАНО толкуются как описано в языке конвенций версии 1 — тогда и
только тогда, когда написаны заглавными.
Две границы, о которых говорят правила ниже: Две границы, о которых говорят правила ниже:
+5 -1
View File
@@ -7,7 +7,11 @@ extends: arch/time.md
Как и когда писать логи. Это правила оформления кода (How), а не Как и когда писать логи. Это правила оформления кода (How), а не
спецификация поведения: наблюдаемые требования к логам, входящие в контракт спецификация поведения: наблюдаемые требования к логам, входящие в контракт
функциональности, живут в спеках. Форма записи — `LANGUAGE.md`. функциональности, живут в спеках.
Ключевые слова ДОЛЖЕН, НЕ ДОЛЖЕН, СЛЕДУЕТ, НЕ СЛЕДУЕТ, ДОПУСКАЕТСЯ и отметка
МЕХАНИЗИРОВАНО толкуются как описано в языке конвенций версии 1 — тогда и
только тогда, когда написаны заглавными.
Лог читают инструментами, а не глазами: повседневно — `jq` Лог читают инструментами, а не глазами: повседневно — `jq`
(`jq 'select(.download_id=="a1b2")' app.jsonl`), тяжёлое (агрегации, JOIN) — (`jq 'select(.download_id=="a1b2")' app.jsonl`), тяжёлое (агрегации, JOIN) —
+6 -3
View File
@@ -5,9 +5,12 @@ extends: arch/time.md
# Время: реализация на Go # Время: реализация на Go
Как требования базового слоя выполняются в Go-коде: откуда берётся Как требования базового слоя выполняются в Go-коде: откуда берётся «сейчас»,
«сейчас», в каком виде время попадает в базу и в логи, что делать с зонами. в каком виде время попадает в базу и в логи, что делать с зонами.
Форма записи — `LANGUAGE.md`.
Ключевые слова ДОЛЖЕН, НЕ ДОЛЖЕН, СЛЕДУЕТ, НЕ СЛЕДУЕТ, ДОПУСКАЕТСЯ и отметка
МЕХАНИЗИРОВАНО толкуются как описано в языке конвенций версии 1 — тогда и
только тогда, когда написаны заглавными.
## Правила ## Правила
+5 -2
View File
@@ -5,8 +5,11 @@ extends: arch/app-directories.md
# Категории директорий: реализация в Ansible # Категории директорий: реализация в Ansible
Как категории из базового слоя раскладываются на сервере Как категории из базового слоя раскладываются на сервере плейбуком.
плейбуком. Форма записи — `LANGUAGE.md`.
Ключевые слова ДОЛЖЕН, НЕ ДОЛЖЕН, СЛЕДУЕТ, НЕ СЛЕДУЕТ, ДОПУСКАЕТСЯ и отметка
МЕХАНИЗИРОВАНО толкуются как описано в языке конвенций версии 1 — тогда и
только тогда, когда написаны заглавными.
## Область действия ## Область действия
+7 -4
View File
@@ -4,10 +4,13 @@ prefix: HTMX
# Веб-UI на htmx # Веб-UI на htmx
Как пишется код веб-UI: частичный своп фрагментов, поллинг живых Как пишется код веб-UI: частичный своп фрагментов, поллинг живых обновлений,
обновлений, обработчики действий, деградация без JS, ошибки. Что именно UI обработчики действий, деградация без JS, ошибки. Что именно UI показывает и
показывает и какие действия поддерживает — в спеках, не здесь. Форма записи какие действия поддерживает — в спеках, не здесь.
`LANGUAGE.md`.
Ключевые слова ДОЛЖЕН, НЕ ДОЛЖЕН, СЛЕДУЕТ, НЕ СЛЕДУЕТ, ДОПУСКАЕТСЯ и отметка
МЕХАНИЗИРОВАНО толкуются как описано в языке конвенций версии 1 — тогда и
только тогда, когда написаны заглавными.
Логирование запросов — конвенция `logging` (HTTP-поля, рутинно-частое на Логирование запросов — конвенция `logging` (HTTP-поля, рутинно-частое на
`DEBUG`). Трансляция доменных ошибок наружу — конвенция `errors` `DEBUG`). Трансляция доменных ошибок наружу — конвенция `errors`
+4 -2
View File
@@ -21,8 +21,10 @@
# Пути даются от корня репозитория, а не от `conventions/`: реестр покрывает # Пути даются от корня репозитория, а не от `conventions/`: реестр покрывает
# и обвязку тоже. # и обвязку тоже.
# #
# Локальные правила репозиториев берут свои префиксы и объявляют их в # Буква `X` в начале префикса зарезервирована за репозиториями-потребителями:
# `.conventions.toml` копии. Они обязаны не пересекаться с этим реестром. # канон её не занимает никогда, локальные правила берут префиксы только на
# неё (XTIM, XLOG). Согласовывать их с этим реестром не нужно — столкновение
# невозможно по построению.
[live] [live]
DIRS = "conventions/arch/app-directories.md" DIRS = "conventions/arch/app-directories.md"