From df8c58671f094bd9686cf2c27a50283622f94eb5 Mon Sep 17 00:00:00 2001 From: Anton Vakhrushev Date: Sun, 26 Jul 2026 15:10:59 +0300 Subject: [PATCH] =?UTF-8?q?=D1=8F=D0=B7=D1=8B=D0=BA:=20=D0=BE=D0=B1=D1=8A?= =?UTF-8?q?=D1=8F=D0=B2=D0=BB=D0=B5=D0=BD=D0=B0=20=D0=B3=D1=80=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D1=86=D0=B0=20=D0=BF=D1=80=D0=B0=D0=B2=D0=B8=D0=BB=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - область правила — от его заголовка до следующего заголовка любого уровня; метка открывает блок, хвост после ПОЧЕМУ — продолжение обоснования, а таблица после модальной метки — часть нормы - проверка «заглавных модальных слов вне правил нет» стала реализуемой: прозой считается то, что лежит вне областей правил - нормы, сидевшие в хвостах, подняты в блок нормы: заведены SLOG-25.4 и GERR-26.3, у GTIM-12 «базовый слой» заменён на TIME-12 --- CLAUDE.md | 5 +++ LANGUAGE.md | 53 +++++++++++++++++++++++++++-- TODO.md | 62 ++++++++++++---------------------- conventions/lang/go/errors.md | 19 ++++++----- conventions/lang/go/logging.md | 9 +++-- conventions/lang/go/time.md | 4 +-- 6 files changed, 95 insertions(+), 57 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 88fc4e7..3a178d1 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -21,6 +21,11 @@ code in this repository. `**МОДАЛЬНОСТЬ.** норма`, абзац `**ПОЧЕМУ.** …`. Правило без обоснования не принимается. - Норма — одна фраза; если в неё не влезает, это два правила. +- Область правила — от его заголовка до следующего заголовка любого уровня; + метка открывает блок, блок длится до следующей метки или до конца области. + Абзацы после `**ПОЧЕМУ.**` — продолжение обоснования: требований в них не + живёт, требование ставят в блок нормы. Таблица и список после модальной + метки — часть нормы. - Шкала инвариантна, словарь — параметр языка набора. Канон русский, значит слова: **ДОЛЖЕН**, **НЕ ДОЛЖЕН**, **СЛЕДУЕТ**, **НЕ СЛЕДУЕТ**, **ДОПУСКАЕТСЯ**. Словарь один на канон, синонимов на ступень нет. diff --git a/LANGUAGE.md b/LANGUAGE.md index 1b36c1c..d9103ff 100644 --- a/LANGUAGE.md +++ b/LANGUAGE.md @@ -92,6 +92,45 @@ version: 1 принадлежат словарю набора: скелет правила читается одинаково в любом языке, на который канон переведён. +**Правило кончается перед следующим заголовком.** Область правила — от его +заголовка до следующего заголовка любого уровня. Внутри области текст +принадлежит последнему открытому блоку: метка блок открывает, и блок длится +до следующей метки или до конца области. + +```markdown +### XKEY-3. Заголовок правила + +**ДОЛЖЕН.** Норма одной фразой. + +| № | ситуация | вердикт | ← блок нормы: таблица уточняет её + +**ПОЧЕМУ.** Причина. + +Продолжение причины, пример, ← блок обоснования продолжается +ссылка на внешнюю практику. + +### XKEY-4. Следующее правило ← здесь область кончилась +``` + +Отсюда три следствия: + +- **Хвост после ПОЧЕМУ — обоснование**, а не безымянная часть правила и не + проза вокруг. Требований в нём не живёт: то, что подлежит исполнению, стоит + в блоке нормы, где у него есть модальность и адрес. Требование, оставленное + в хвосте, требованием не является — сослаться на него нельзя и отступление + от него записать нельзя. +- **Таблица и список после модальной метки — часть нормы.** Правило, + классифицирующее ситуации, ровно так и записывается («Таблицы решений»), а + вердикт из такой таблицы адресуется номером строки. +- **Проза — это то, что лежит вне областей правил.** Тем самым проверка + «заглавных модальных слов вне правил нет» становится реализуемой: границу + считает разметка, а не читательское суждение о том, где правило кончилось. + +Заглавное модальное слово внутри области правила законно, когда это +упоминание ступени в обосновании («для СЛЕДУЕТ это честно»). Метку от +упоминания отличает положение: метка стоит первой в своём абзаце, полужирным +и с точкой. + ## Обоснование обязательно Правило без блока ПОЧЕМУ не принимается. Это требование к форме, а не @@ -378,6 +417,10 @@ Directives, Part 2, по одной форме записи на ступень, - **Локальная часть копии** — содержимое принадлежит репозиторию. - Вводная проза, объясняющая предмет конвенции. +Все четыре части лежат вне областей правил: до первого заголовка правила или +после заголовка, которым область закрылась. Хвост обоснования сюда не +относится — он внутри правила, и модальные слова в нём законны как упоминания. + ## Как на правила ссылаются копии Ниже маркера локальной части, в репозитории: @@ -416,8 +459,11 @@ MIGR-6 не соблюдается в легаси-таблицах `show_histor - ссылки вида `<ПРЕФИКС>-` — хоть в тексте канона, хоть в локальной части копии — указывают на правила, которые ещё существуют; - префиксы локальных правил копии начинаются на `X`; -- заглавные модальные слова не встречаются вне правил — кроме строки о - версии языка, которая их перечисляет по назначению; +- заглавные модальные слова не встречаются вне областей правил (область — + от заголовка правила до следующего заголовка) — кроме строки о версии + языка, которая их перечисляет по назначению; +- модальная метка стоит первой в своём абзаце: заглавное слово в середине + фразы — упоминание ступени, а не вторая норма правила; - префикс **чужой темы** не встречается в абзаце с модальностью (META-20); префикс арх-слоя своей темы там допустим (META-24), префикс другого языка или стека — нет; @@ -428,7 +474,8 @@ MIGR-6 не соблюдается в легаси-таблицах `show_histor - строки таблицы взаимоисключающи либо политика совпадения объявлена; - перечисленные в таблице случаи покрывают область действия; - норма исполнима без обращения к другим файлам; -- обоснование отвечает на «что сломается», а не пересказывает норму. +- обоснование отвечает на «что сломается», а не пересказывает норму; +- хвост обоснования не вводит требований, которых нет в блоке нормы. ## Версия языка diff --git a/TODO.md b/TODO.md index c2b296e..b498dda 100644 --- a/TODO.md +++ b/TODO.md @@ -4,32 +4,12 @@ решения. Закрытый вопрос отсюда удаляется — принятое решение живёт в `README.md`, `GUIDE.md` или `LANGUAGE.md`, а не в этом файле. -Две секции: сначала язык и подход, потом канон с тулингом. Пункты 1–8 +Две секции: сначала язык и подход, потом канон с тулингом. Пункты 1–7 пришли из внешнего ревью описания языка и проверены по файлам на месте. # Язык и подход -## 1. Граница правила не определена - -Форма объявляет четыре части, но почти каждое крупное правило несёт абзацы -**после** блока ПОЧЕМУ: KEYS-5, SLOG-25, MIGR-11, GERR-26. Язык не говорит, -чем эти абзацы являются — частью правила, продолжением обоснования или -вводной прозой, где заглавные модальные слова запрещены. - -В хвостах прячутся настоящие нормы без модальности и без адреса: у SLOG-25 — -«логируется `ERROR` с признаком непокрытой», у GERR-26 — «Продолжать, не -исключив упавший элемент, нельзя». Это ровно тот неадресуемый текст, против -которого язык построен: сослаться нельзя, отступление записать нельзя. - -Побочно это блокирует главную машинную проверку. «Заглавные модальные слова -не встречаются вне правил» нереализуема, пока не сказано, где правило -кончается: парсер «от `###` до следующего заголовка» включит хвосты, парсер -«две метки и всё» объявит хвосты прозой и покраснеет на законных пояснениях. - -Решить надо две вещи: где кончается правило и что делать с нормами, которые -уже сидят в хвостах. - -## 2. Примеры в LANGUAGE.md сидят на живых идентификаторах +## 1. Примеры в LANGUAGE.md сидят на живых идентификаторах Учебные примеры используют настоящие префиксы канона с номерами, которые в каноне означают другое: @@ -48,7 +28,7 @@ Лечится дёшево: примеры берут префиксы на `X`, зарезервированные как раз под то, что каноном не занято. -## 3. «Тема» — несущий идентификатор без определения и реестра +## 2. «Тема» — несущий идентификатор без определения и реестра META-21 велит ссылаться на соседнюю конвенцию именем темы, манифест подписывается `topics = ["time", …]`, сборка собирает файл темы из слоёв — @@ -61,13 +41,13 @@ META-21 велит ссылаться на соседнюю конвенцию файла темы тихо осиротит все текстовые ссылки во всех копиях. Вопрос уже не гипотетический: пара `arch/db-identifiers.md` против -`lang/go/db-schema.md` (см. вопрос 12) показывает, что имена слоёв одной +`lang/go/db-schema.md` (см. вопрос 11) показывает, что имена слоёв одной темы могут не совпадать. Смежно: `extends: arch/time.md` у SLOG ломает гарантию META-24 («базовый слой отсутствовать не может») — она верна только для базы своей темы, а машинной проверке негде узнать тему, кроме имени файла. -## 4. GUIDE выведен из-под проверок ложным основанием +## 3. GUIDE выведен из-под проверок ложным основанием `LANGUAGE.md` исключает обвязку из проверок формулировкой «она ключевые слова цитирует, а не употребляет». Для `GUIDE.md` это неверно: META-правила @@ -83,7 +63,7 @@ META-21 велит ссылаться на соседнюю конвенцию Честнее развести: `GUIDE.md` язык **употребляет** и проверяется как конвенция, `LANGUAGE.md` и `README.md` — цитируют. -## 5. МЕХАНИЗИРОВАНО не переживает нового подписчика +## 4. МЕХАНИЗИРОВАНО не переживает нового подписчика META-8 запрещает удалять норму, пока механизирована не у всех, и защищает тем самым потребителей, существующих **на момент удаления**. Будущих не @@ -101,7 +81,7 @@ META-8 запрещает удалять норму, пока механизир в тексте канона, то есть ровно то, что запрещает META-4. Либо это законное исключение, и тогда его надо назвать, либо конфликт. -## 6. Семантика ключевых слов в копию не едет +## 5. Семантика ключевых слов в копию не едет Строка о версии языка перечисляет слова, но не их значения, а всё нетривиальное в шкале живёт только в `LANGUAGE.md`, который в репозиторий не @@ -117,7 +97,7 @@ META-8 запрещает удалять норму, пока механизир копиями короткую выжимку семантики; расширить строку о версии до двух-трёх предложений; или признать ограничение и записать его явно. -## 7. Две «механические» проверки без источника данных +## 6. Две «механические» проверки без источника данных В списке «разбором текста» стоят два пункта, которые без дополнительного реестра нерешаемы: @@ -133,7 +113,7 @@ META-8 запрещает удалять норму, пока механизир реестр снятых номеров не объявлен частью языка, оба пункта принадлежат списку «чтением». -## 8. Натяжки в опоре на стандарты +## 7. Натяжки в опоре на стандарты Три места, где источнику приписано чуть больше, чем в нём есть: @@ -152,7 +132,7 @@ META-8 запрещает удалять норму, пока механизир Остальное в таблице проверку выдержало, включая вторую половину `MAY` из BCP 14 и списки эквивалентных словесных форм ISO Directives. -## 9. Одиннадцать таблиц не прочитаны на взаимоисключительность +## 8. Одиннадцать таблиц не прочитаны на взаимоисключительность `LANGUAGE.md` объявил, что строки таблицы решений взаимоисключающи по умолчанию, а иной порядок объявляется явно. Объявление не делает таблицы @@ -167,7 +147,7 @@ BCP 14 и списки эквивалентных словесных форм IS Работа читательская, машине не даётся; в список проверок она уже записана в разделе «Чтением, потому что машине не даётся». -## 10. Описание языка отдельно от набора конвенций +## 9. Описание языка отдельно от набора конвенций `LANGUAGE.md` и `GUIDE.md` описывают, **как** пишутся конвенции; `conventions/` — **один конкретный** набор. Сейчас они склеены в одном @@ -190,7 +170,7 @@ BCP 14 и списки эквивалентных словесных форм IS # Канон, тулинг, подключение -## 11. Тулинг: две разные задачи в одном `conv` +## 10. Тулинг: две разные задачи в одном `conv` Сейчас в `conv` смешаны две категории работы, и они расходятся по всему — по частоте запуска, по тому, кто запускает, и по тому, что считается @@ -221,22 +201,22 @@ BCP 14 и списки эквивалентных словесных форм IS ссылках: это установка, а не целостность, но список подписок ему нужен из манифеста. -Часть проверок из этого списка сейчас нереализуема по причинам из вопросов 1 -и 7, так что порядок такой: сначала язык, потом чекер. +Часть проверок из этого списка сейчас нереализуема по причине из вопроса 6, +так что порядок такой: сначала язык, потом чекер. Перед тем как переписывать, стоит посмотреть на два готовых прототипа: дистрибуцию пакетов Vale (`.vale.ini` → `vale sync` → `styles/`) как образец манифеста и `vendir.yml` — как пример того, где проходит граница между «чего хочу» и «что получил». -## 12. Пары слоёв и темы без базы +## 11. Пары слоёв и темы без базы Отложено сознательно, но список стоит держать перед глазами: - `SLOG` объявляет `extends: arch/time.md` — расширение **чужой** темы. Сборщик темы `logging` на это наткнётся: базового слоя с темой `logging` нет, а `arch/time.md` он тянуть не должен. Чинится переводом в обычную - ссылку «связано». Вдобавок это ломает гарантию META-24 — см. вопрос 3. + ссылку «связано». Вдобавок это ломает гарантию META-24 — см. вопрос 2. - Темы без арх-слоя: `db-schema`, `errors`, `web-ui`. Собираются в файл с одной секцией — само по себе не ломается, но это и есть тот невыделенный арх-слой из известного долга. @@ -248,7 +228,7 @@ BCP 14 и списки эквивалентных словесных форм IS - Вынос арх-ядра из `errors` и `logging` закроет две хрупкие ссылки из `web-ui` (стек) в go-слой — единственные ссылки стек → язык в каноне. -## 13. Подключение к репозиториям +## 12. Подключение к репозиториям Ничего ещё не подключено. Кандидаты — jellybit и pet-project-server. Понадобится: заполнить локальную часть копий тем, что сейчас в этих @@ -257,7 +237,7 @@ BCP 14 и списки эквивалентных словесных форм IS строка в `AGENTS.md` каждого потребителя про то, что файлы в `docs/conventions/` — копии. -## 14. Тулинг на Go, живущий независимо +## 13. Тулинг на Go, живущий независимо Сейчас `conv` — питоновский скрипт внутри канона, то есть тулинг и данные в одном репозитории и правятся одним движением. Мысль: вынести в отдельный @@ -266,10 +246,10 @@ Go-бинарь со своим релизным циклом, ставить ч Что за этим стоит помимо вкуса: независимый бинарь физически не даёт править инструмент «заодно» с правкой конвенции, работает против **любого** канона и -любого потребителя — что прямо требуется вопросом 10, — и снимает питон из +любого потребителя — что прямо требуется вопросом 9, — и снимает питон из зависимостей репозиториев-потребителей. -Порядок обратный ожидаемому: пока вопрос 10 не сделан, инструмент всё равно +Порядок обратный ожидаемому: пока вопрос 9 не сделан, инструмент всё равно работает против одного конкретного канона, и независимый релизный цикл ему -нечего обслуживать. Сначала 10, потом 14. Разделение из вопроса 11 при этом +нечего обслуживать. Сначала 9, потом 13. Разделение из вопроса 10 при этом дешевле заложить сразу, чем отпиливать потом. diff --git a/conventions/lang/go/errors.md b/conventions/lang/go/errors.md index b058408..db174d0 100644 --- a/conventions/lang/go/errors.md +++ b/conventions/lang/go/errors.md @@ -344,8 +344,9 @@ HTTP-клиентов, файловой системы, внешних SDK. | № | Где перехвачена паника | Что дальше | |---|---|---| -| GERR-26.1 | обработчик HTTP-запроса | ответ 500, если он ещё не начат; процесс и прочие запросы не затрагиваются | +| GERR-26.1 | обработчик HTTP-запроса, паника любая, кроме сигнала намеренного прерывания | ответ 500, если он ещё не начат; процесс и прочие запросы не затрагиваются | | GERR-26.2 | итерация цикла обработки — бот, воркер | цикл продолжается со следующего элемента, упавший элемент повторно не берётся | +| GERR-26.3 | обработчик HTTP-запроса, паника — сигнал намеренного прерывания (`http.ErrAbortHandler`) | значение пробрасывается дальше, ответ не подменяется | **ПОЧЕМУ.** Паника внутри обработки одного элемента почти всегда говорит о баге в работе с данными этого элемента, а не о порче общего состояния, — @@ -356,10 +357,11 @@ HTTP-клиентов, файловой системы, внешних SDK. обязательным (GERR-22): неперехваченная паника в любой горутине завершает весь процесс. -Продолжать, не исключив упавший элемент, нельзя: детерминированная паника -даёт бесконечный цикл — тот же элемент, тот же стек, залитый лог и нулевой -прогресс. Это классический poison message, и лекарство берём то же, что -принято в очередях: элемент выводится из оборота, а не берётся снова. У +Исключение упавшего элемента в GERR-26.2 — не осторожность, а условие +прогресса: детерминированная паника даёт бесконечный цикл — тот же элемент, +тот же стек, залитый лог и нулевой прогресс. Это классический poison message, +и лекарство здесь то же, что принято в очередях: элемент выводится из +оборота, а не берётся снова. У цикла, который и так подтверждает прогресс — сдвигает офсет, помечает строку состоянием, — механизм для этого уже есть, заводить отдельный не нужно. @@ -369,9 +371,10 @@ HTTP-клиентов, файловой системы, внешних SDK. клиент получит обрывок с кодом 200. Отсюда же общее предпочтение собирать ответ целиком до записи там, где это возможно. -Из GERR-26.1 есть одно исключение: `http.ErrAbortHandler` — сигнал «прервать -обработку намеренно», и recover-обёртка пробрасывает его дальше, а не -превращает в 500. Так поступают и стандартные обёртки вроде chi. +Отдельная строка GERR-26.3 нужна потому, что `http.ErrAbortHandler` — не +отказ, а сигнал «прервать обработку намеренно»: подмена его на 500 превратила +бы штатный разрыв в ложную ошибку в логе и в метриках. Так поступают и +стандартные обёртки вроде chi. ### GERR-24. Независимые ошибки собираются `errors.Join` diff --git a/conventions/lang/go/logging.md b/conventions/lang/go/logging.md index 9e87c2b..e8d04ba 100644 --- a/conventions/lang/go/logging.md +++ b/conventions/lang/go/logging.md @@ -337,6 +337,7 @@ ctx = logctx.With(ctx, log) // достаём логгер из ctx в кажд | SLOG-25.1 | штатный конфликт состояния или некорректный ввод | пользователю, он уже получил ответ | `DEBUG` | | SLOG-25.2 | расхождение производного или учётного состояния, первичные данные целы | владельцу, «может стать проблемой» | `WARN` | | SLOG-25.3 | сбой БД, ФС, недоступность зависимости | владельцу, в разбор | `ERROR` | +| SLOG-25.4 | класса нет: отказ в классификацию не заведён | владельцу, как пропуск в классификации | `ERROR` с отметкой о непокрытом классе | **ПОЧЕМУ.** Это применение SLOG-8 к отказам: пользователь уже увидел причину на экране — владельцу разбирать нечего; целостность первичных данных отделяет @@ -349,9 +350,11 @@ ctx = logctx.With(ctx, log) // достаём логгер из ctx в кажд таблицу не входит: это не доменный отказ, и логирует его recover-граница вместе со стеком (конвенция `errors`). Искать его класс здесь не нужно. -Мимо таблицы идёт и доменная ошибка, которой нет в маппинге: класса у неё -нет, потому что её просто забыли завести. Она логируется `ERROR` с -признаком непокрытой (`GERR-25`). +Строка SLOG-25.4 говорит не о классе отказа, а о пропуске в самой +классификации: ошибку забыли завести в маппинге. `ERROR` здесь — громкость, +по которой пропуск находят фильтром, а не оценка тяжести отказа; саму отметку +о непокрытом классе ставит трансляция ошибки (`GERR-25` в конвенции +`errors`). ### SLOG-26. Тот же отказ в асинхронной стадии — уровнем выше diff --git a/conventions/lang/go/time.md b/conventions/lang/go/time.md index 1b29a1b..b6597a4 100644 --- a/conventions/lang/go/time.md +++ b/conventions/lang/go/time.md @@ -180,8 +180,8 @@ func utcTime(_ []string, a slog.Attr) slog.Attr { текущего значения настройки: смена зоны задним числом сдвигает границы суток у того, что давно посчитано и сохранено. -Календарные вычисления бизнес-логики берут зону явно — как описано в -базовом слое. +Явную зону в календарных вычислениях требует TIME-12 — это его правило, а не +второе такое же здесь. ## Связано