From 93180872482bb0438e547a0e86aec418cfe66bed Mon Sep 17 00:00:00 2001 From: Anton Vakhrushev Date: Sun, 26 Jul 2026 13:42:10 +0300 Subject: [PATCH] =?UTF-8?q?=D1=8F=D0=B7=D1=8B=D0=BA=20=D0=B7=D0=B0=D0=BF?= =?UTF-8?q?=D0=B8=D1=81=D0=B8=20=D0=BE=D0=BF=D1=91=D1=80=D1=82=20=D0=BD?= =?UTF-8?q?=D0=B0=20=D1=81=D1=82=D0=B0=D0=BD=D0=B4=D0=B0=D1=80=D1=82=D1=8B?= =?UTF-8?q?,=20=D1=81=D0=BB=D0=BE=D0=B2=D0=B0=D1=80=D1=8C=20=D1=81=D1=82?= =?UTF-8?q?=D0=B0=D0=BB=20=D0=BF=D0=B0=D1=80=D0=B0=D0=BC=D0=B5=D1=82=D1=80?= =?UTF-8?q?=D0=BE=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - шкала обязательности объявлена инвариантом, а набор ключевых слов — параметром естественного языка набора: для английского готовый словарь даёт BCP 14, для прочих берут перевод стандарта или делают свой; отклонены синонимы ступеней и `SHALL`, занятый OpenSpec - применены шесть дельт: нормативно только заглавное написание (RFC 8174), ДОЛЖЕН требует названного вреда и машинной проверки сразу, ДОПУСКАЕТСЯ адресовано рецензенту, МЕХАНИЗИРОВАНО выведено из шкалы в отметку рядом с модальностью (ISO/IEC/IEEE 29148), у таблиц объявлены политика совпадения и полнота (DMN) - «Форма записи — LANGUAGE.md» в двенадцати конвенциях заменена строкой о версии языка по образцу boilerplate BCP 14: пути канона в копии не существует, а словарь и правило заглавных строка несёт сама --- CLAUDE.md | 30 +- LANGUAGE.md | 362 +++++++++++++------ README.md | 5 +- TODO.md | 115 +++--- conventions/arch/app-directories.md | 6 +- conventions/arch/config.md | 6 +- conventions/arch/db-identifiers.md | 7 +- conventions/arch/time.md | 7 +- conventions/lang/go/config.md | 6 +- conventions/lang/go/db-identifiers.md | 7 +- conventions/lang/go/db-schema.md | 6 +- conventions/lang/go/errors.md | 10 +- conventions/lang/go/logging.md | 6 +- conventions/lang/go/time.md | 9 +- conventions/stack/ansible/app-directories.md | 7 +- conventions/stack/htmx/web-ui.md | 11 +- 16 files changed, 395 insertions(+), 205 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 89e857b..4cec1cb 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -21,15 +21,28 @@ code in this repository. `**МОДАЛЬНОСТЬ.** норма`, абзац `**Почему.** …`. Правило без «Почему» не принимается. - Норма — одна фраза; если в неё не влезает, это два правила. -- Модальные слова: **ДОЛЖЕН**, **НЕ ДОЛЖЕН**, **СЛЕДУЕТ**, **НЕ СЛЕДУЕТ**, - **ДОПУСКАЕТСЯ**, плюс не-модальная отметка **МЕХАНИЗИРОВАНО**. Английские - ключевые слова (SHALL, MUST) не используются — они заняты OpenSpec. -- Модальные слова не употребляются вне правил: ни в «Область действия», ни в - «Связано», ни в локальной части копии, ни во вводной прозе. +- Шкала инвариантна, словарь — параметр языка набора. Канон русский, значит + слова: **ДОЛЖЕН**, **НЕ ДОЛЖЕН**, **СЛЕДУЕТ**, **НЕ СЛЕДУЕТ**, + **ДОПУСКАЕТСЯ**. Словарь один на канон, синонимов на ступень нет. +- `SHALL` не используется ни в одном словаре — занято OpenSpec. +- Нормативно только заглавное написание (правило RFC 8174): строчное + «должен» в прозе нормой не является. +- ДОЛЖЕН требует двух условий сразу: нарушение причиняет названный вред + и норма проверяема машиной. Проверяемость сама по себе до ДОЛЖЕН не + повышает. +- ДОПУСКАЕТСЯ адресовано рецензенту: помеченный им выбор на ревью не + обсуждается. +- **МЕХАНИЗИРОВАНО** — не модальность, а способ проверки: отметка стоит + рядом с модальным словом (`**ДОЛЖЕН. МЕХАНИЗИРОВАНО.**`), а не вместо него. +- Заглавные модальные слова не употребляются вне правил: ни в «Область + действия», ни в «Связано», ни в локальной части копии, ни во вводной прозе. + Исключение — строка о версии языка, которая их перечисляет. - «Почему» отвечает на «что сломается, если сделать иначе», а не пересказывает норму. «Потому что так принято» — не обоснование. - Правило, классифицирующее ситуации, пишется таблицей «ситуация → вердикт»; - строки нумеруются `KEYS-5.1`. + строки нумеруются `KEYS-5.1`. Строки взаимоисключающи по умолчанию; иной + порядок объявляется явно, а перечисленные случаи покрывают область + действия. - Модальность принадлежит правилу, а не файлу: `status:` в шапке отменён. ## Идентификаторы и префиксы @@ -88,8 +101,9 @@ code in this repository. ## Оформление файла -Шапка `prefix:` (плюс `extends:`) → `# Тема` → вводная проза со строкой -«Форма записи — `LANGUAGE.md`» → `## Область действия` (обязателен для +Шапка `prefix:` (плюс `extends:`) → `# Тема` → вводная проза → отдельным +абзацем строка о версии языка (её точный текст — в `LANGUAGE.md`, раздел +«Ссылка на язык из конвенции») → `## Область действия` (обязателен для трудноизменяемых слоёв — META-11) → правила → `## Связано` только с каноническими ссылками (META-17). Имя файла — kebab-case по теме. Проза переносится по ~76 колонок; таблицы и блоки кода не переносятся. diff --git a/LANGUAGE.md b/LANGUAGE.md index a467711..f2cb389 100644 --- a/LANGUAGE.md +++ b/LANGUAGE.md @@ -1,16 +1,55 @@ +--- +version: 1 +--- + # Язык конвенций -Как записываются правила в этом каноне. Документ описывает форму, а не -содержание: что такое правило, чем оно отличается от прозы вокруг и как на -него сослаться. +Формальный язык, на котором записаны правила этого канона: что считается +правилом, чем оно отличается от прозы вокруг, какими словами задаётся +обязательность и как на правило сослаться извне. -Форма подсмотрена у OpenSpec, но взято оттуда не всё — см. «Чего мы не -берём». +Версия языка — **1**. Номер называется в каждой конвенции: словарь может +пополниться, и текст, написанный по предыдущей версии, должен читаться по +той, по которой написан. -## Зачем формализовать +## Опора на стандарты -Не ради строгости. Три конкретные вещи, которые без адресуемых правил не -работают: +Язык не выводится из вкуса автора. Каждое решение о форме взято из +документа, где эта задача уже решена и обкатана, и отклонения от источника +названы явно. + +| Источник | Что взято | Что отклонено | +|---|---|---| +| **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,69 +303,10 @@ Порядок правил в файле выбирается по читаемости, не по номерам: номер — это идентификатор, а не позиция. -## Правило, чья норма уехала в линтер - -Когда правило механизировано у всех потребителей, его норма из канона -удаляется, а обоснование — нет. Остаётся **правило без модальности**, и -чтобы оно не выглядело недописанным, место нормы занимает отметка: - -```markdown -### MIGR-6. Дефолтов времени в схеме БД нет - -**МЕХАНИЗИРОВАНО.** Проверяется общим правилом линтера; формулировка -удалена, потому что дублировала работающую проверку. - -**Почему.** Дефолт превращает забытую вставку в тихо работающий код… -``` - -- Идентификатор и заголовок сохраняются: ссылки из репозиториев продолжают - указывать на то же утверждение. -- «Почему» остаётся навсегда — линтер сообщает, что нарушено, но не - сообщает, зачем правило существует, и без обоснования нельзя понять, - когда проверку пора отменять. -- **МЕХАНИЗИРОВАНО** — не шестое модальное слово: оно не задаёт - обязательность, а сообщает, что обязательность теперь обеспечена машиной. - В остальном такое правило равно ДОЛЖЕН. - -Факт «механизировано у всех» устанавливается вручную: канон по построению -не знает списка подписчиков, и обойти репозитории перед удалением нормы — -часть работы, а не то, что можно проверить автоматически. - -## Таблицы вместо сценариев - -Часть правил **классифицирует ситуации**: какой уровень лога, какая -категория директории, что делать с невалидным вводом в зависимости от его -источника. Для них каноническая форма — таблица «ситуация → вердикт», -строки которой при необходимости нумеруются. - -Таблица плотнее прозы и не даёт пропустить ветку: пустая клетка видна, а -неупомянутый случай в абзаце — нет. - -## Чего мы не берём из OpenSpec - -**GIVEN/WHEN/THEN.** У спецификации субъект — система, и её поведение -разворачивается во времени: состояние, событие, исход. У конвенции субъект -— автор кода, и разворачивать нечего: есть ситуация выбора и вердикт. Это -таблица, а не траектория. - -**SHALL.** См. выше про словарь. - -**Сценарии как общая форма.** Прозаический сценарий остаётся точечным -инструментом — для **стыка правил**, когда два правила вместе дают -неочевидный результат: - -``` -WHEN зависимость недоступна и ретраи вызова исчерпаны → ext-запись ERROR -AND тик фонового цикла упал по той же причине → доменная запись WARN -``` - -Такой блок ставится после обоих правил и ссылается на их номера. Если -стыков нет — сценариев в файле нет. - ## Что правилом не является -Модальные слова в этих частях **не употребляются** — иначе перестанет быть -понятно, что адресуемо, а что нет: +Заглавные модальные слова в этих частях **не употребляются** — иначе +перестанет быть понятно, что адресуемо, а что нет: - **Область действия** — на что конвенция распространяется во времени («новые таблицы; существующие не переписываются»). Это рамка для всех @@ -201,24 +334,35 @@ MIGR-6 не соблюдается в легаси-таблицах `show_histor ## Что стоит проверять машиной -Сейчас не реализовано; список — на будущее для `conv`: +Проверки применяются к файлам конвенций; обвязка канона в них не входит — +она ключевые слова цитирует, а не употребляет. Ни одна пока не реализована, +поэтому при ревью их выполняют чтением. +Разбором текста: + +- модальные слова принадлежат объявленному словарю канона, а не смеси + словарей; - префикс в шапке файла совпадает с реестром, состоит из четырёх заглавных - латинских букв и не значится в списке выбывших; + латинских букв, не начинается на `X` и не значится в списке выбывших; - заголовки правил файла используют только его собственный префикс; - номера уникальны внутри файла и не имеют пропусков вниз (новое правило берёт следующий свободный, а не первый освободившийся); -- у каждого `### <ПРЕФИКС>-` есть модальное слово (или отметка - МЕХАНИЗИРОВАНО) и блок «Почему»; +- у каждого `### <ПРЕФИКС>-` есть модальное слово и блок «Почему»; + отметка МЕХАНИЗИРОВАНО стоит рядом с модальностью, а не вместо неё; +- вводная проза содержит строку о версии языка; - ссылки вида `<ПРЕФИКС>-` — хоть в тексте канона, хоть в локальной части копии — указывают на правила, которые ещё существуют; -- префиксы локальных правил копии начинаются на `X` и не совпадают с - реестром канона; -- чужой префикс не встречается в абзаце с модальностью (META-20); -- путь файла канона не встречается в тексте конвенции (META-21); -- модальные слова не встречаются вне правил. +- префиксы локальных правил копии начинаются на `X`; +- заглавные модальные слова не встречаются вне правил — кроме строки о + версии языка, которая их перечисляет по назначению; +- префикс **чужой темы** не встречается в абзаце с модальностью (META-20); + префикс своего же базового слоя там допустим — в собранной копии это + соседняя секция того же файла; +- путь файла канона не встречается в тексте конвенции (META-21). -## Порядок перевода +Чтением, потому что машине не даётся: -Все конвенции канона записаны на этом языке. Новая конвенция пишется на нём -сразу; смешение форм в каноне больше не предполагается. \ No newline at end of file +- строки таблицы взаимоисключающи либо политика совпадения объявлена; +- перечисленные в таблице случаи покрывают область действия; +- норма исполнима без обращения к другим файлам; +- обоснование отвечает на «что сломается», а не пересказывает норму. diff --git a/README.md b/README.md index a03a6a6..bd6048f 100644 --- a/README.md +++ b/README.md @@ -16,8 +16,9 @@ | `conv` | сборка копий | Обвязка живёт только в каноне и в репозитории не оказывается — в копию едет -лишь содержимое `conventions/`. Пока это осознанное ограничение: копия -конвенции ссылается на `LANGUAGE.md` как на внешний документ. +лишь содержимое `conventions/`. Самодостаточность копии это не нарушает: +конвенция называет язык записи одной строкой с номером версии и не ссылается +на путь (`LANGUAGE.md`, раздел «Ссылка на язык из конвенции»). Правило то же, что у ролей: **деплоится и читается только то, что лежит в git репозитория**. Канон никем не подключается на лету. diff --git a/TODO.md b/TODO.md index 39a0348..70e0ea2 100644 --- a/TODO.md +++ b/TODO.md @@ -3,42 +3,18 @@ Черновик для следующего разговора: вопросы и варианты, а не принятые решения. -## 1. Ссылка на `LANGUAGE.md` не переживает сборку +## 1. Ссылка на язык — закрыто -Все двенадцать конвенций во вводной прозе пишут «Форма записи — -`LANGUAGE.md`». Обвязка в репозиторий не едет (`conv` синхронизирует только -`conventions/`), поэтому в собранной копии эта ссылка указывает в никуда. +Заменено boilerplate-строкой по образцу BCP 14: каждая конвенция называет +ключевые слова, версию языка и правило заглавных, но не путь. Строка стоит +отдельным абзацем после вводной прозы во всех двенадцати файлах, «Форма +записи — `LANGUAGE.md`» удалена. Точный текст — в `LANGUAGE.md`, раздел +«Ссылка на язык из конвенции». -Это тот же класс дефекта, который вчера закрыло META-21 для ссылок между -темами: путь, верный в каноне, молча умирает при сборке. Разница в том, что -META-21 предлагает заменить путь на имя темы — а здесь заменять не на что, -целевого документа в репозитории просто нет. - -Варианты, которые видно сейчас: - -- **Убрать упоминание из тел конвенций.** Язык записи — забота того, кто - пишет канон, а не того, кто читает конвенцию в своём репозитории. Читателю - достаточно самого текста: модальные слова и «Почему» самоописательны. - Дешевле всего, но копия теряет указание, по каким правилам её править. -- **Возить обвязку вместе с конвенциями.** Тогда `LANGUAGE.md` и, возможно, - `GUIDE.md` появляются в `docs/conventions/` как ещё одни синхронизируемые - файлы. Честно, но противоречит нынешнему разделению «канон — конвенции, - корень — обвязка» и добавляет в репозиторий текст, который агенту при - чтении конвенции не нужен. -- **Вкладывать короткую преамбулу в собранный файл.** Сборщик пишет в шапку - три-четыре строки: что такое модальное слово, что «Почему» обязательно, - где лежит полный документ. Самодостаточно и не тащит весь язык, но - преамбула дублируется в каждом файле темы. - -Появился четвёртый вариант, и он выглядит лучше трёх: **boilerplate-строка -как в RFC 8174**. Каждая конвенция несёт одну фразу — «ключевые слова … -толкуются как описано в „Языке конвенций“ версии 1 — тогда и только тогда, -когда написаны заглавными». Пути нет, документа рядом нет, а норму исполнить -можно: строка сама несёт и словарь, и правило капса. Разбирается в заходе -про язык записи, вместе с версией — она же цепляет вопрос 9. - -Сопутствующее: `GUIDE.md` в телах конвенций не упоминается ни разу — -проверено, так что вопрос только про `LANGUAGE.md`. +Побочно: строка перечисляет модальные слова заглавными, то есть сама +нарушает проверку «заглавные модальные слова не встречаются вне правил». +Исключение записано в список проверок — так же, как оно устроено в BCP 14, +где boilerplate тоже содержит ключевые слова. ## 2. Тулинг: две разные задачи в одном `conv` @@ -144,11 +120,12 @@ META-21 предлагает заменить путь на имя темы — Проверка перед вложением в тулинг сделана. По слоям: -- **Язык записи** — велосипед, но собранный из проверенных деталей: словарь - совпадает с RFC 2119/8174 вплоть до правила «нормативен только капс», - обязательное «Почему» — дисциплина из requirements engineering (ISO/IEC/IEEE - 29148), стабильные идентификаторы — из semgrep/ESLint. Менять архитектуру - нечего, остались шесть точечных дельт — отдельным заходом. +- **Язык записи** — совпал со стандартами почти во всём: шкала и правило + «нормативно только заглавное» — BCP 14, обязательное обоснование и + единичность нормы — ISO/IEC/IEEE 29148, категории — ISO/IEC Directives + Part 2, таблицы решений — DMN, стабильные идентификаторы — semgrep/ESLint. + Шесть точечных дельт применены, опора на источники записана в `LANGUAGE.md` + разделом «Опора на стандарты». - **Оси и сборка** — аналога не нашлось. Ближайшие соседи (каскад `extends` в ESLint, слои стилей Vale) дают праву слоя **отменять** базу; наш запрет строже, и это содержательная часть. Вкладываться сюда. @@ -175,19 +152,28 @@ META-21 предлагает заменить путь на имя темы — манифеста, и `vendir.yml` — как пример того, где проходит граница между «чего хочу» и «что получил». -## 8. Мультиязычность ключевых слов +## 8. Мультиязычность ключевых слов — закрыто по механике -Модальные слова сейчас русские, и это осознанно: разный словарь держит -границу «конвенция — не спецификация» бесплатно. Вопрос — нужен ли -английский набор параллельно. +Вопрос был поставлен как «нужен ли английский набор параллельно русскому». +Ответ оказался другой формы: словарь — **параметр естественного языка +набора**, а не часть языка конвенций. Шкала из пяти ступеней инвариантна, +слова под неё подбираются: для английского готовый словарь даёт BCP 14, для +любого другого языка слова берут из перевода стандарта или переводят сами. -За: канон может однажды понадобиться на английском; агенты натренированы на -MUST/SHOULD плотнее, чем на ДОЛЖЕН/СЛЕДУЕТ. Против: два набора — это два -способа записать одно, и проверка «модальные слова не встречаются вне -правил» усложняется вдвое. +Отсюда следствия, снимающие исходный вопрос: -Если делать, то таблица ключевых слов должна принадлежать **описанию -языка**, а не каждой конвенции — то есть вопрос завязан на следующий. +- **параллельных наборов не бывает.** Словарь один на канон: два словаря + дают две формы записи одного требования и удваивают каждую проверку; +- **версия языка при смене словаря не меняется** — версия принадлежит шкале + и правилам формы, а не буквам. Прежняя оценка «английский набор = версия 2» + неверна; +- **`SHALL` не берётся ни в одном словаре**, потому что занято OpenSpec; для + английского это выбор в пользу `MUST` из BCP 14, а не `shall` из ISO; +- отметка о механизации стандартом не даётся ни в одном языке и подбирается + так же, как остальные слова (`МЕХАНИЗИРОВАНО` / `MECHANIZED`). + +Открытым остаётся только прикладное: понадобится ли этому канону английская +версия вообще. Механика для неё уже описана, заводить заранее нечего. ## 9. Описание языка отдельно от набора конвенций @@ -207,12 +193,14 @@ MUST/SHOULD плотнее, чем на ДОЛЖЕН/СЛЕДУЕТ. Проти Цена — ещё одна сущность и ещё одна синхронизация. Проверить, не выйдет ли лечение хуже болезни при одном пользователе. -Оценка выросла: у выноса появилась вторая, независимая причина. Boilerplate- -строка из вопроса 1 обязана назвать язык **с версией** — иначе она врёт при -первом же изменении словаря. Версия предполагает документ, у которого версия -бывает, то есть отдельный от набора. Так IETF и решил ровно эту задачу: -RFC 2119 — самостоятельный документ, на который спецификации ссылаются -номером, а не путём. +Срочность снята: версия появилась у `LANGUAGE.md` (ключ `version:` в шапке), +и boilerplate-строка из вопроса 1 уже ссылается на язык номером, а не путём — +не дожидаясь выноса. Отдельный репозиторий остаётся вопросом второго набора +конвенций, а не вопросом самодостаточности копии. + +Что осталось поводом: из одного описания по-прежнему нельзя собрать второй +набор, и тулинг валидирует правила, зашитые в код, а не объявленную версию +языка. Оба повода включаются, только когда появится второй набор. ## 10. Тулинг на Go, живущий независимо @@ -241,15 +229,16 @@ Go-бинарь со своим релизным циклом, ставить ч никуда не ведёт — правило рядом. Ограничение META-20 писалось именно про **чужую тему**, так что формально это уже разрешено. -Но стоит проговорить явно, потому что сейчас читается уже как запрет: +Формулировка проверки исправлена: в `LANGUAGE.md` теперь «префикс **чужой +темы** не встречается в абзаце с модальностью», и там же сказано, что +префикс своего базового слоя допустим. Ложного срабатывания на `GTIM` → +`TIME-3` больше нет. -- в `LANGUAGE.md` проверка сформулирована как «**чужой префикс** не - встречается в абзаце с модальностью» — по букве это ловит и `GTIM` → - `TIME-3`, то есть ложно срабатывает на ровно том случае, который - разрешён. Должно быть «префикс **чужой темы**»; -- обсудить, не добавить ли в META-20 явную строку **ДОПУСКАЕТСЯ** про - родительский слой: правило, которое читают как более строгое, чем оно - есть, заставляет авторов дублировать текст без нужды. +Осталось решить одно: добавлять ли в META-20 явную строку **ДОПУСКАЕТСЯ** +про родительский слой. За — правило, которое читают строже, чем оно есть, +заставляет авторов дублировать текст без нужды. Против — норма META-20 уже +говорит «чужой темы», и второе правило про то же место придётся держать +согласованным с первым. ## Из вчерашнего, не закрыто diff --git a/conventions/arch/app-directories.md b/conventions/arch/app-directories.md index 89c13a6..2708045 100644 --- a/conventions/arch/app-directories.md +++ b/conventions/arch/app-directories.md @@ -8,7 +8,11 @@ prefix: DIRS создания и ценности содержимого: конфигурация, данные, кеш. Категория сразу отвечает на два вопроса, которые иначе выясняются чтением кода приложения: **кто создаёт** содержимое и **что будет, если его потерять**. Из категорий -механически выводится состав бэкапа. Форма записи — `LANGUAGE.md`. +механически выводится состав бэкапа. + +Ключевые слова ДОЛЖЕН, НЕ ДОЛЖЕН, СЛЕДУЕТ, НЕ СЛЕДУЕТ, ДОПУСКАЕТСЯ и отметка +МЕХАНИЗИРОВАНО толкуются как описано в языке конвенций версии 1 — тогда и +только тогда, когда написаны заглавными. ## Область действия diff --git a/conventions/arch/config.md b/conventions/arch/config.md index 0c80d57..27201a8 100644 --- a/conventions/arch/config.md +++ b/conventions/arch/config.md @@ -5,7 +5,11 @@ prefix: CONF # Конфигурация приложения Как устроена конфигурация: где лежит, как попадает в процесс, что с -секретами и когда падает. Форма записи — `LANGUAGE.md`. +секретами и когда падает. + +Ключевые слова ДОЛЖЕН, НЕ ДОЛЖЕН, СЛЕДУЕТ, НЕ СЛЕДУЕТ, ДОПУСКАЕТСЯ и отметка +МЕХАНИЗИРОВАНО толкуются как описано в языке конвенций версии 1 — тогда и +только тогда, когда написаны заглавными. ## Область действия diff --git a/conventions/arch/db-identifiers.md b/conventions/arch/db-identifiers.md index 559298e..a5b1d42 100644 --- a/conventions/arch/db-identifiers.md +++ b/conventions/arch/db-identifiers.md @@ -4,8 +4,11 @@ prefix: KEYS # Идентификаторы сущностей -Как выбираются и как выглядят первичные ключи сущностей. Форма записи — -`LANGUAGE.md`. +Как выбираются и как выглядят первичные ключи сущностей. + +Ключевые слова ДОЛЖЕН, НЕ ДОЛЖЕН, СЛЕДУЕТ, НЕ СЛЕДУЕТ, ДОПУСКАЕТСЯ и отметка +МЕХАНИЗИРОВАНО толкуются как описано в языке конвенций версии 1 — тогда и +только тогда, когда написаны заглавными. ## Область действия diff --git a/conventions/arch/time.md b/conventions/arch/time.md index 1c4737c..5e7cae3 100644 --- a/conventions/arch/time.md +++ b/conventions/arch/time.md @@ -5,8 +5,11 @@ prefix: TIME # Время Как приложение записывает моменты и длительности: в каком формате, откуда -берётся значение и где появляется не-UTC. Форма записи — -`LANGUAGE.md`. +берётся значение и где появляется не-UTC. + +Ключевые слова ДОЛЖЕН, НЕ ДОЛЖЕН, СЛЕДУЕТ, НЕ СЛЕДУЕТ, ДОПУСКАЕТСЯ и отметка +МЕХАНИЗИРОВАНО толкуются как описано в языке конвенций версии 1 — тогда и +только тогда, когда написаны заглавными. ## Область действия diff --git a/conventions/lang/go/config.md b/conventions/lang/go/config.md index d6a812d..e359909 100644 --- a/conventions/lang/go/config.md +++ b/conventions/lang/go/config.md @@ -6,7 +6,11 @@ extends: arch/config.md # Конфигурация: реализация на Go Как базовый слой выглядит в Go-приложении: формат, загрузчик, границы -запрета на окружение. Форма записи — `LANGUAGE.md`. +запрета на окружение. + +Ключевые слова ДОЛЖЕН, НЕ ДОЛЖЕН, СЛЕДУЕТ, НЕ СЛЕДУЕТ, ДОПУСКАЕТСЯ и отметка +МЕХАНИЗИРОВАНО толкуются как описано в языке конвенций версии 1 — тогда и +только тогда, когда написаны заглавными. Секретов Go-специфика не касается: они приходят из деплоя уже в файле, а проверка их непустоты идёт вместе с остальной валидацией — как описано в diff --git a/conventions/lang/go/db-identifiers.md b/conventions/lang/go/db-identifiers.md index 956eed5..ae7f420 100644 --- a/conventions/lang/go/db-identifiers.md +++ b/conventions/lang/go/db-identifiers.md @@ -5,8 +5,11 @@ extends: arch/db-identifiers.md # Идентификаторы: реализация на Go -Как базовый слой выглядит в Go-приложении. Форма записи — -`LANGUAGE.md`. +Как базовый слой выглядит в Go-приложении. + +Ключевые слова ДОЛЖЕН, НЕ ДОЛЖЕН, СЛЕДУЕТ, НЕ СЛЕДУЕТ, ДОПУСКАЕТСЯ и отметка +МЕХАНИЗИРОВАНО толкуются как описано в языке конвенций версии 1 — тогда и +только тогда, когда написаны заглавными. Единая точка из `KEYS-3` — пакет `internal/ident`: он порождает идентификаторы (`NewID`, `NewIDAt`) и он же их разбирает diff --git a/conventions/lang/go/db-schema.md b/conventions/lang/go/db-schema.md index d525242..65d5fc9 100644 --- a/conventions/lang/go/db-schema.md +++ b/conventions/lang/go/db-schema.md @@ -5,7 +5,11 @@ prefix: MIGR # Схема и миграции (SQLite, Go) Как пишутся миграции и какие типы колонок выбираются для схемы SQLite в -Go-приложении. Форма записи — `LANGUAGE.md`. +Go-приложении. + +Ключевые слова ДОЛЖЕН, НЕ ДОЛЖЕН, СЛЕДУЕТ, НЕ СЛЕДУЕТ, ДОПУСКАЕТСЯ и отметка +МЕХАНИЗИРОВАНО толкуются как описано в языке конвенций версии 1 — тогда и +только тогда, когда написаны заглавными. ## Область действия diff --git a/conventions/lang/go/errors.md b/conventions/lang/go/errors.md index 0708352..83298d2 100644 --- a/conventions/lang/go/errors.md +++ b/conventions/lang/go/errors.md @@ -4,9 +4,13 @@ prefix: GERR # Ошибки -Как ошибки строятся, оборачиваются и проверяются. Форма записи — -`LANGUAGE.md`. Где и когда ошибку **логировать** — в конвенции `logging` -(коротко: лог один раз на доменной границе). +Как ошибки строятся, оборачиваются и проверяются. Где и когда ошибку +**логировать** — в конвенции `logging` (коротко: лог один раз на доменной +границе). + +Ключевые слова ДОЛЖЕН, НЕ ДОЛЖЕН, СЛЕДУЕТ, НЕ СЛЕДУЕТ, ДОПУСКАЕТСЯ и отметка +МЕХАНИЗИРОВАНО толкуются как описано в языке конвенций версии 1 — тогда и +только тогда, когда написаны заглавными. Две границы, о которых говорят правила ниже: diff --git a/conventions/lang/go/logging.md b/conventions/lang/go/logging.md index 9075c06..5008cde 100644 --- a/conventions/lang/go/logging.md +++ b/conventions/lang/go/logging.md @@ -7,7 +7,11 @@ extends: arch/time.md Как и когда писать логи. Это правила оформления кода (How), а не спецификация поведения: наблюдаемые требования к логам, входящие в контракт -функциональности, живут в спеках. Форма записи — `LANGUAGE.md`. +функциональности, живут в спеках. + +Ключевые слова ДОЛЖЕН, НЕ ДОЛЖЕН, СЛЕДУЕТ, НЕ СЛЕДУЕТ, ДОПУСКАЕТСЯ и отметка +МЕХАНИЗИРОВАНО толкуются как описано в языке конвенций версии 1 — тогда и +только тогда, когда написаны заглавными. Лог читают инструментами, а не глазами: повседневно — `jq` (`jq 'select(.download_id=="a1b2")' app.jsonl`), тяжёлое (агрегации, JOIN) — diff --git a/conventions/lang/go/time.md b/conventions/lang/go/time.md index c1734ea..5e596b6 100644 --- a/conventions/lang/go/time.md +++ b/conventions/lang/go/time.md @@ -5,9 +5,12 @@ extends: arch/time.md # Время: реализация на Go -Как требования базового слоя выполняются в Go-коде: откуда берётся -«сейчас», в каком виде время попадает в базу и в логи, что делать с зонами. -Форма записи — `LANGUAGE.md`. +Как требования базового слоя выполняются в Go-коде: откуда берётся «сейчас», +в каком виде время попадает в базу и в логи, что делать с зонами. + +Ключевые слова ДОЛЖЕН, НЕ ДОЛЖЕН, СЛЕДУЕТ, НЕ СЛЕДУЕТ, ДОПУСКАЕТСЯ и отметка +МЕХАНИЗИРОВАНО толкуются как описано в языке конвенций версии 1 — тогда и +только тогда, когда написаны заглавными. ## Правила diff --git a/conventions/stack/ansible/app-directories.md b/conventions/stack/ansible/app-directories.md index b6cbc02..e742834 100644 --- a/conventions/stack/ansible/app-directories.md +++ b/conventions/stack/ansible/app-directories.md @@ -5,8 +5,11 @@ extends: arch/app-directories.md # Категории директорий: реализация в Ansible -Как категории из базового слоя раскладываются на сервере -плейбуком. Форма записи — `LANGUAGE.md`. +Как категории из базового слоя раскладываются на сервере плейбуком. + +Ключевые слова ДОЛЖЕН, НЕ ДОЛЖЕН, СЛЕДУЕТ, НЕ СЛЕДУЕТ, ДОПУСКАЕТСЯ и отметка +МЕХАНИЗИРОВАНО толкуются как описано в языке конвенций версии 1 — тогда и +только тогда, когда написаны заглавными. ## Область действия diff --git a/conventions/stack/htmx/web-ui.md b/conventions/stack/htmx/web-ui.md index bfe36f9..e8a536d 100644 --- a/conventions/stack/htmx/web-ui.md +++ b/conventions/stack/htmx/web-ui.md @@ -4,10 +4,13 @@ prefix: HTMX # Веб-UI на htmx -Как пишется код веб-UI: частичный своп фрагментов, поллинг живых -обновлений, обработчики действий, деградация без JS, ошибки. Что именно UI -показывает и какие действия поддерживает — в спеках, не здесь. Форма записи -— `LANGUAGE.md`. +Как пишется код веб-UI: частичный своп фрагментов, поллинг живых обновлений, +обработчики действий, деградация без JS, ошибки. Что именно UI показывает и +какие действия поддерживает — в спеках, не здесь. + +Ключевые слова ДОЛЖЕН, НЕ ДОЛЖЕН, СЛЕДУЕТ, НЕ СЛЕДУЕТ, ДОПУСКАЕТСЯ и отметка +МЕХАНИЗИРОВАНО толкуются как описано в языке конвенций версии 1 — тогда и +только тогда, когда написаны заглавными. Логирование запросов — конвенция `logging` (HTTP-поля, рутинно-частое на `DEBUG`). Трансляция доменных ошибок наружу — конвенция `errors`