common: добавлен язык записи конвенций

- правило = номер + модальность (ДОЛЖЕН / СЛЕДУЕТ / ДОПУСКАЕТСЯ) плюс
  обязательный блок «Почему»; из OpenSpec взяты идентификаторы правил, но
  не SHALL и не GIVEN/WHEN/THEN
- файловый статус отменён: обязательность живёт на правиле, а в шапке ещё
  не переведённых конвенций ключ остаётся меткой «это проза»
This commit is contained in:
av
2026-07-25 18:56:29 +03:00
parent 4a59c71737
commit 22c6855968
2 changed files with 206 additions and 24 deletions
+25 -24
View File
@@ -1,16 +1,12 @@
---
status: обязательная
---
# Как мы ведём конвенции # Как мы ведём конвенции
Конвенция описывает повторяющийся выбор: как называть директории, как Конвенция описывает повторяющийся выбор: как называть директории, как
раскладывать данные, как оформлять ошибки. Она отвечает на вопрос «как раскладывать данные, как оформлять ошибки. Она отвечает на вопрос «как
принято», а не «что здесь происходит». Одна конвенция — один файл. принято», а не «что здесь происходит». Одна конвенция — один файл.
Механической проверки у самой этой конвенции нет — осознанное исключение: Как записывается сама конвенция — правила, модальность, обоснования — в
проверять «правильно ли написана конвенция» нечем, а обязательный статус [language.md](language.md). Здесь — про то, зачем они заводятся, где живут
нужен, чтобы правила ниже не обсуждались заново в каждом репозитории. и как соотносятся с соседними видами документов.
## Канон и копии ## Канон и копии
@@ -54,17 +50,16 @@ status: обязательная
текущем состоянии репозитория. «Так сделано у нас» — это регион текущем состоянии репозитория. «Так сделано у нас» — это регион
отступлений; норма пишется в настоящем предписывающем времени. отступлений; норма пишется в настоящем предписывающем времени.
## Статус ## Насколько правило обязательно
Каждая конвенция объявляет статус в шапке: Обязательность живёт **на правиле**, а не на файле: один документ почти
всегда смешивает жёсткие требования с советами, и общая пометка на нём
неизбежно врёт про часть содержимого. Шкала модальных слов — в
[language.md](language.md).
- **рекомендуемая** — так стоит делать в новом коде; существующий переезжает Правило без механической проверки держится только на внимании. Для
по мере касания, отдельной кампанией не переписывается; **СЛЕДУЕТ** это нормально, для **ДОЛЖЕН** — плохо: такое правило либо
- **обязательная** — нарушение считается ошибкой; по возможности проверяется механизируется, либо честно понижается.
линтером или хуком, а не вниманием.
Конвенция без механической проверки держится только на внимании — это
нормально для рекомендуемой и плохо для обязательной.
## Когда заводить ## Когда заводить
@@ -89,14 +84,19 @@ status: обязательная
- **из канона формулировка не удаляется**, пока правило не механизировано - **из канона формулировка не удаляется**, пока правило не механизировано
у всех потребителей: иначе те, у кого линтера нет, останутся без правила; у всех потребителей: иначе те, у кого линтера нет, останутся без правила;
- **факт механизации** фиксируется в локальном регионе `механизировано` - **факт механизации** фиксируется в локальном регионе `механизировано`
со ссылкой на конкретное правило; со ссылкой на номер правила и на конкретную проверку;
- когда механизация стала общей (правило уехало в общий конфиг линтера или - когда механизация стала общей (правило уехало в общий конфиг линтера или
в общую роль), формулировка удаляется из канона одним `push`. в общую роль), формулировка удаляется из канона одним `push`.
Обоснование правила («Почему») не удаляется никогда, даже когда сама норма
уехала в линтер: линтер сообщает, что нарушено, но не сообщает, зачем
правило существует, — а именно это нужно, чтобы понять, когда его пора
отменить.
## Трудноизменяемые слои ## Трудноизменяемые слои
У схемы БД, формата хранения и раскладки директорий шкала У схемы БД, формата хранения и раскладки директорий не работает привычное
«рекомендуемая → переезжает по мере касания» не работает: таблица не «новое пишем правильно, старое переезжает по мере касания»: таблица не
переезжает от того, что её потрогали. Для таких конвенций: переезжает от того, что её потрогали. Для таких конвенций:
- **область действия пишется явно** — «применяется к новым таблицам и - **область действия пишется явно** — «применяется к новым таблицам и
@@ -108,10 +108,11 @@ status: обязательная
## Честный список отступлений ## Честный список отступлений
В локальном регионе перечисляем отступления, которые уже есть в коде, — В локальном регионе перечисляем отступления, которые уже есть в коде, — со
иначе репозиторий делает вид, что правилу следует. У рекомендуемой ссылкой на номера правил. Иначе репозиторий делает вид, что конвенции
конвенции пустой список отступлений почти всегда означает, что их просто не следует, а проверить это можно только чтением всего кода.
искали.
Пустой список отступлений почти всегда означает, что их не искали.
Отступление — это «правилу не следуем здесь и вот почему». Если регион Отступление — это «правилу не следуем здесь и вот почему». Если регион
разросся до «мы это правило вообще не применяем», значит либо у правила разросся до «мы это правило вообще не применяем», значит либо у правила
@@ -129,7 +130,7 @@ status: обязательная
- **Короткие инварианты дублируются туда, что агент читает безусловно** - **Короткие инварианты дублируются туда, что агент читает безусловно**
(`AGENTS.md` / `CLAUDE.md`): сама по себе конвенция агенту не видна, он (`AGENTS.md` / `CLAUDE.md`): сама по себе конвенция агенту не видна, он
дойдёт до неё, только если его туда отправили. Детали остаются здесь, дойдёт до неё, только если его туда отправили. Детали остаются здесь,
в файл-точку-входа едет одна строка на правило. в файл-точку-входа едет одна строка на правило с его номером.
<!-- local:точки-входа --> <!-- local:точки-входа -->
<!-- /local --> <!-- /local -->
+181
View File
@@ -0,0 +1,181 @@
# Язык конвенций
Как записываются правила в этом каноне. Документ описывает форму, а не
содержание: что такое правило, чем оно отличается от прозы вокруг и как на
него сослаться.
Форма подсмотрена у OpenSpec, но взято оттуда не всё — см. «Чего мы не
берём».
## Зачем формализовать
Не ради строгости. Три конкретные вещи, которые без адресуемых правил не
работают:
- **Механизация.** Регион `механизировано` должен говорить «правило R4
проверяет `archrules`», а не «`AUTOINCREMENT` в новых миграциях —
`archrules`»: во втором случае читатель сам догадывается, к какому
утверждению это относится, и догадывается по-разному.
- **Отступления.** «У нас не так» бесполезно, пока не сказано, что именно
«не так». Со ссылкой на правило отступления становятся счётными: видно,
сколько правил конвенции репозиторий реально не соблюдает.
- **Промоут находки.** Путь «находка → конвенция → правило линтера →
удаление прозы» требует ручки, за которую берут конкретное правило.
Общий знаменатель — **идентификатор**. Модальные слова и таблицы полезны,
но вторичны.
## Единица — правило
```markdown
### R5. Разбор внешнего идентификатора на границе
**ДОЛЖЕН.** Идентификатор, пришедший снаружи, проходит разбор до запроса
к базе.
**Почему.** Разбор валидирует формат и нормализует регистр. Сравнение строк
в базе побайтовое, поэтому без нормализации запрос молча не находит
существующую запись — отладка такого случая стоит дороже, чем сам разбор.
```
Четыре обязательные части: **номер**, **заголовок**, **модальность с
нормой**, **почему**. Норма — одна фраза; если в неё не влезает, это два
правила.
## Правило без «почему» не принимается
Это жёсткое требование к форме, а не пожелание. Причины:
- **«Почему» — единственный способ понять, когда правило перестало
действовать.** Норма стареет молча; обоснование стареет заметно. Когда
причина отпала, видно, что правило пора убрать, а не соблюдать по
инерции.
- **Правило без обоснования не переживает спор.** Через год ни автор, ни
агент не восстановят мотив, и правило будет либо отменено первым же
возражением, либо соблюдено там, где вредит.
- **Формулировка «почему» — проверка на то, что это вообще правило.** Если
причина не формулируется, перед нами привычка или вкусовщина; ей место в
черновиках, а не в конвенции.
«Почему» отвечает на «что сломается, если сделать иначе», а не пересказывает
норму другими словами. «Потому что так принято» — не обоснование.
## Модальные слова
Пишутся капсом — это ключевые слова, а не обычный текст.
| Слово | Значение | Отступление |
|---|---|---|
| **ДОЛЖЕН** | нарушение считается ошибкой | только с записью в регион `отступления` |
| **НЕ ДОЛЖЕН** | запрет | то же |
| **СЛЕДУЕТ** | сильная рекомендация; новый код пишем так | допустимо, причину записываем |
| **НЕ СЛЕДУЕТ** | обратное к СЛЕДУЕТ | то же |
| **ДОПУСКАЕТСЯ** | явное разрешение | не требуется — правило ничего не запрещает |
**ДОПУСКАЕТСЯ** нужно не для симметрии: оно снимает вопрос «а так можно?»
там, где соседнее правило звучит строго и его легко перечитать шире, чем
задумано.
Модальность живёт на **правиле**, а не на файле. Прежний файловый статус
(`status: рекомендуемая` / `обязательная` в шапке) отменён: он неизбежно
врал, потому что один файл смешивает жёсткие требования с советами. В ещё
не переведённых конвенциях ключ остаётся как метка «этот файл — проза», без
нормативного значения, и исчезает при переводе.
Мы **не используем SHALL и прочие английские ключевые слова**. Они заняты
спецификациями (OpenSpec), и общий словарь стирал бы границу «конвенция —
не capability». Разный словарь эту границу держит бесплатно.
## Идентификаторы
- Формат — `R<номер>`, сквозная нумерация внутри файла, начиная с `R1`.
- Строка таблицы, если на неё нужно ссылаться отдельно, — `R5.1`, `R5.2`.
- **Номера стабильны и не переиспользуются.** Удалённое правило оставляет
дыру в нумерации; занимать её новым правилом нельзя — иначе ссылка из
чужого репозитория начнёт указывать на другое утверждение.
- Глобальный адрес — путь файла плюс номер: `arch/db-identifiers.md R5`.
В пределах одного файла достаточно `R5`.
Порядок правил в файле выбирается по читаемости, не по номерам: номер — это
идентификатор, а не позиция.
## Таблицы вместо сценариев
Часть правил **классифицирует ситуации**: какой уровень лога, какая
категория директории, что делать с невалидным вводом в зависимости от его
источника. Для них каноническая форма — таблица «ситуация → вердикт»,
строки которой при необходимости нумеруются.
Таблица плотнее прозы и не даёт пропустить ветку: пустая клетка видна, а
неупомянутый случай в абзаце — нет.
## Чего мы не берём из OpenSpec
**GIVEN/WHEN/THEN.** У спецификации субъект — система, и её поведение
разворачивается во времени: состояние, событие, исход. У конвенции субъект
— автор кода, и разворачивать нечего: есть ситуация выбора и вердикт. Это
таблица, а не траектория.
**SHALL.** См. выше про словарь.
**Сценарии как общая форма.** Прозаический сценарий остаётся точечным
инструментом — для **стыка правил**, когда два правила вместе дают
неочевидный результат:
```
WHEN зависимость недоступна и ретраи вызова исчерпаны → ext-запись ERROR
AND тик фонового цикла упал по той же причине → доменная запись WARN
```
Такой блок ставится после обоих правил и ссылается на их номера. Если
стыков нет — сценариев в файле нет.
## Что правилом не является
Модальные слова в этих частях **не употребляются** — иначе перестанет быть
понятно, что адресуемо, а что нет:
- **Область действия** — на что конвенция распространяется во времени
(«новые таблицы; существующие не переписываются»). Это рамка для всех
правил файла, а не правило.
- **Связано** — ссылки на смежные конвенции, ADR, код.
- **Локальные регионы** — содержимое принадлежит репозиторию.
- Вводная проза, объясняющая предмет конвенции.
## Как на правила ссылаются копии
В репозитории:
```markdown
<!-- local:механизировано -->
R2, R4 — `internal/archrules` (проверяются в новых миграциях).
<!-- /local -->
<!-- local:отступления -->
R6 — не соблюдается в легаси-таблицах `show_history`, `queue`: составные
ключи там появились до конвенции, переписывание требует миграции данных.
<!-- /local -->
```
Отсюда видно и то, чего раньше не было видно: конвенция из восьми правил,
из которых два механизированы и одно не соблюдается.
## Что стоит проверять машиной
Сейчас не реализовано; список — на будущее для `conv`:
- номера уникальны внутри файла и не имеют пропусков вниз (новое правило
берёт следующий свободный, а не первый освободившийся);
- у каждого `### R<n>` есть модальное слово и блок «Почему»;
- ссылки вида `R<n>` в локальных регионах копии указывают на правила,
которые в каноне ещё существуют;
- модальные слова не встречаются вне правил.
## Порядок перевода
Конвенции переводятся на этот язык по мере касания, а не кампанией.
Смешение форм в каноне допустимо: пока файл не тронут, он остаётся прозой
со статусом в шапке.
Сейчас на формальном языке записаны `arch/db-identifiers.md` и
`stack/ansible/app-directories.md`.