Files
dev-conventions/TODO.md
T
av c04a54ffd5 todo: заведены четыре открытые темы по языку записи
- 12: `WHEN`/`AND` в блоке стыка правил — английские служебные слова там же,
  где `SHALL` отклонён как занятый OpenSpec; либо перевод, либо явная
  оговорка, что форма спецификации в этом месте намеренна
- 13: критерий «названного вреда» для ДОЛЖЕН описан в языке, но правила под
  него нет — META-6 обязывает только понижать при отсутствии проверки
- 14 и 15: одиннадцать таблиц не прочитаны на взаимоисключительность
  (подозреваемый SLOG-11), и канон не просмотрен на факты, записанные
  модальным словом
2026-07-26 13:43:45 +03:00

25 KiB
Raw Blame History

К обсуждению

Черновик для следующего разговора: вопросы и варианты, а не принятые решения.

1. Ссылка на язык — закрыто

Заменено boilerplate-строкой по образцу BCP 14: каждая конвенция называет ключевые слова, версию языка и правило заглавных, но не путь. Строка стоит отдельным абзацем после вводной прозы во всех двенадцати файлах, «Форма записи — LANGUAGE.md» удалена. Точный текст — в LANGUAGE.md, раздел «Ссылка на язык из конвенции».

Побочно: строка перечисляет модальные слова заглавными, то есть сама нарушает проверку «заглавные модальные слова не встречаются вне правил». Исключение записано в список проверок — так же, как оно устроено в BCP 14, где boilerplate тоже содержит ключевые слова.

2. Тулинг: две разные задачи в одном conv

Сейчас в conv смешаны две категории работы, и они расходятся по всему — по частоте запуска, по тому, кто запускает, и по тому, что считается провалом.

Целостность канона. Префиксы уникальны и не переиспользованы, шапка совпадает с реестром, номера без дыр вниз, у каждого правила модальность и «Почему», ссылки разрешаются, чужой префикс не лезет в норму (META-20), путей канона в тексте нет (META-21). Запускается в каноне, при каждой правке, провал — это ошибка. Логика уже написана и много раз прогнана руками, но живёт в скретчпаде, а не в репозитории.

Установка в проект. Манифест .conventions.toml, сборка файла темы из секций (arch → языки → стеки → local), сохранение локальной секции при пересборке, отчёт «канон ушёл вперёд», предупреждение о висячих ссылках на неподписанные темы. Запускается в репозитории-потребителе, изредка, провал — это чаще «посмотри глазами», чем «ошибка».

Что обсудить:

  • Разделять ли на два исполняемых файла, или хватит подкоманд с честной границей внутри.
  • Валидацию канона стоит ли отдать агенту скиллом вместо (или вдобавок к) скрипту: часть проверок формулируется как «прочитай и скажи, самодостаточна ли норма» — механически это не берётся, а агентом берётся.
  • Куда в этой раскладке ложится запаркованное предупреждение о висячих ссылках: это установка, а не целостность, но список подписок ему нужен из манифеста.

3. Модель сборки — записана, закрыто

Записана в README.md (разделы «Копия в репозитории» и «Манифест») и в GUIDE.md (META-22, META-23). Плоская раскладка, файл на тему, один маркер <!-- conv:local -->, манифест из источника и списка тем, направление строго одностороннее.

Против прежних заготовок разошлось в трёх местах:

  • лока нет — «что было в прошлый раз» знает git, копии закоммичены, и автоматического обновления не существует; заодно из шапки копии ушли origin_hash и synced, осталось одно origin:;
  • маркеров секций нет — раз обновление перезаписывает всё выше локального маркера, разметка слоёв тулингу не нужна; слои идут обычными заголовками;
  • локальные префиксы не объявляются в манифесте — за репозиториями зарезервирована буква X, столкновение невозможно по построению.

Остаётся переписать conv: сейчас в нём зеркальное дерево, именованные регионы, origin_hash и команды status/diff/push. Это вопрос 2.

4. Именованные регионы — удалить из канона

Решение принято и записано: регионов нет, есть один маркер <!-- conv:local -->, который ставит сборщик в копии. Значит регионы не переносятся в единую секцию, а удаляются: в каноне их нечем наполнять, локальное принадлежит копии.

Остаётся механическая работа — 31 регион в 12 файлах:

7  связано        7  отступления     7  механизировано
1  эталон / эталоны / словарь / секреты / секретные-поля
1  проверки / поля / модель-владельца / маппинг / границы

Вопрос про связано снят: META-17 переписан, канонический раздел «Связано» остаётся в тексте и содержит только ссылки, верные у всех, а репо-специфичные уходят под маркер.

5. Пары слоёв и темы без базы

Отложено сознательно, но список стоит держать перед глазами:

  • SLOG объявляет extends: arch/time.md — расширение чужой темы. Сборщик темы logging на это наткнётся: базового слоя с темой logging нет, а arch/time.md он тянуть не должен. Чинится переводом в обычную ссылку «связано».
  • Темы без арх-слоя: db-schema, errors, web-ui. Собираются в файл с одной секцией — само по себе не ломается, но это и есть тот невыделенный арх-слой из известного долга.
  • Имена тем в паре не совпадают: arch/db-identifiers.md против lang/go/db-schema.md. При сборке по имени темы это две разные темы — проверить, что так и задумано.
  • В lang/go/db-schema.md сидит целый пласт stack/sqlite/ (типы колонок), тоже из известного долга README.

6. Подключение к репозиториям

Ничего ещё не подключено. Кандидаты — jellybit и pet-project-server. Понадобится: заполнить локальные секции тем, что сейчас в этих репозиториях записано по факту; обёртка в раннере (inv conventions / task conventions, единый интерфейс команд у трёх ansible-репозиториев); строка в AGENTS.md каждого потребителя про то, что файлы в docs/conventions/ — копии.

Открытый кусок с прошлого раза: как копия ссылается на канон, не ломая самодостаточность. Абсолютный путь ~/projects/private/dev-conventions в закоммиченном файле не годится — репозиторий перестаёт быть самодостаточным и получает хардкод путей. Пересекается с вопросом 1.

7. Не переизобретено ли это — разобрано

Проверка перед вложением в тулинг сделана. По слоям:

  • Язык записи — совпал со стандартами почти во всём: шкала и правило «нормативно только заглавное» — BCP 14, обязательное обоснование и единичность нормы — ISO/IEC/IEEE 29148, категории — ISO/IEC Directives Part 2, таблицы решений — DMN, стабильные идентификаторы — semgrep/ESLint. Шесть точечных дельт применены, опора на источники записана в LANGUAGE.md разделом «Опора на стандарты».
  • Оси и сборка — аналога не нашлось. Ближайшие соседи (каскад extends в ESLint, слои стилей Vale) дают праву слоя отменять базу; наш запрет строже, и это содержательная часть. Вкладываться сюда.
  • Транспорт — переизобретался, но задача у нас другая, чем у copier и cruft. У них трёхсторонний merge потому, что шаблон порождает код, который правят везде: расхождение неограниченно. У нас расхождение ограничено и живёт под маркером, поэтому исключение из перезаписи лучше merge — детерминированно и без конфликтных маркеров. Из vendir/cruft взят разбор «манифест против лока», из ansible blockinfile — идея размечать управляемый кусок, а не локальный. Лок при этом не взят: его роль играет git.
  • Раздача агентам — не наша задача, остаёмся на AGENTS.md/CLAUDE.md плюс скиллы.

Vale отклонён. Из восьми проверок в LANGUAGE.md он честно закрывает две-три (модальные слова вне правил, запрещённые формулировки); остальные пять структурные — «у каждого ### ПРЕФИКС-N есть модальность и Почему», «номера без дыр вниз», «префикс совпадает с реестром». Это разбор документа, а не проверка токенов в области. Ради двух-трёх проверок тянуть ещё один бинарь и директорию стилей при одном пользователе не стоит.

Что стоит посмотреть перед переписыванием conv (вопрос 2): дистрибуцию пакетов Vale (.vale.inivale syncstyles/) как готовый прототип манифеста, и vendir.yml — как пример того, где проходит граница между «чего хочу» и «что получил».

8. Мультиязычность ключевых слов — закрыто по механике

Вопрос был поставлен как «нужен ли английский набор параллельно русскому». Ответ оказался другой формы: словарь — параметр естественного языка набора, а не часть языка конвенций. Шкала из пяти ступеней инвариантна, слова под неё подбираются: для английского готовый словарь даёт BCP 14, для любого другого языка слова берут из перевода стандарта или переводят сами.

Отсюда следствия, снимающие исходный вопрос:

  • параллельных наборов не бывает. Словарь один на канон: два словаря дают две формы записи одного требования и удваивают каждую проверку;
  • версия языка при смене словаря не меняется — версия принадлежит шкале и правилам формы, а не буквам. Прежняя оценка «английский набор = версия 2» неверна;
  • SHALL не берётся ни в одном словаре, потому что занято OpenSpec; для английского это выбор в пользу MUST из BCP 14, а не shall из ISO;
  • отметка о механизации стандартом не даётся ни в одном языке и подбирается так же, как остальные слова (МЕХАНИЗИРОВАНО / MECHANIZED).

Открытым остаётся только прикладное: понадобится ли этому канону английская версия вообще. Механика для неё уже описана, заводить заранее нечего.

9. Описание языка отдельно от набора конвенций

LANGUAGE.md и GUIDE.md описывают, как пишутся конвенции; conventions/один конкретный набор. Сейчас они склеены в одном репозитории, и из одного описания нельзя собрать второй набор (рабочий, доменный, чужой).

Что это даёт, если разнести:

  • канон объявляет, какой версии языка следует, а тулинг валидирует набор против объявленного описания, а не против зашитых в код правил;
  • таблица модальных слов (вопрос 8) живёт в описании и одна на все наборы;
  • вопрос 1 (LANGUAGE.md не переживает сборку) меняет форму: собранный файл ссылается не на путь, а на язык с версией.

Цена — ещё одна сущность и ещё одна синхронизация. Проверить, не выйдет ли лечение хуже болезни при одном пользователе.

Срочность снята: версия появилась у LANGUAGE.md (ключ version: в шапке), и boilerplate-строка из вопроса 1 уже ссылается на язык номером, а не путём — не дожидаясь выноса. Отдельный репозиторий остаётся вопросом второго набора конвенций, а не вопросом самодостаточности копии.

Что осталось поводом: из одного описания по-прежнему нельзя собрать второй набор, и тулинг валидирует правила, зашитые в код, а не объявленную версию языка. Оба повода включаются, только когда появится второй набор.

10. Тулинг на Go, живущий независимо

Сейчас conv — питоновский скрипт внутри канона, то есть тулинг и данные в одном репозитории и правятся одним движением. Мысль: вынести в отдельный Go-бинарь со своим релизным циклом, ставить через eget (механизм уже есть в pet-project-server).

Что за этим стоит помимо вкуса: независимый бинарь физически не даёт править инструмент «заодно» с правкой конвенции, работает против любого канона и любого потребителя — что прямо требуется вопросом 9, — и снимает питон из зависимостей репозиториев-потребителей.

Связано с вопросом 2: если тулинг всё равно переписывается, разделение «целостность канона / установка в проект» дешевле заложить сразу, чем отпиливать потом.

Порядок при этом обратный написанному: пока вопрос 9 не сделан, инструмент всё равно работает против одного конкретного канона, и независимый релизный цикл ему нечего обслуживать. Сначала 9, потом 10.

11. Ссылки на родительский слой своей темы

Из lang/ и stack/ ссылаться на идентификаторы своего же арх-слоя безопасно: при сборке они оказываются секциями одного файла, и ссылка никуда не ведёт — правило рядом. Ограничение META-20 писалось именно про чужую тему, так что формально это уже разрешено.

Формулировка проверки исправлена: в LANGUAGE.md теперь «префикс чужой темы не встречается в абзаце с модальностью», и там же сказано, что префикс своего базового слоя допустим. Ложного срабатывания на GTIMTIME-3 больше нет.

Осталось решить одно: добавлять ли в META-20 явную строку ДОПУСКАЕТСЯ про родительский слой. За — правило, которое читают строже, чем оно есть, заставляет авторов дублировать текст без нужды. Против — норма META-20 уже говорит «чужой темы», и второе правило про то же место придётся держать согласованным с первым.

12. WHEN/AND в блоке стыка правил

Блок для стыка двух правил записан английскими словами:

WHEN зависимость недоступна и ретраи вызова исчерпаны → ext-запись ERROR
AND тик фонового цикла упал по той же причине        → доменная запись WARN

Рядом сказано, что SHALL не берётся ни в один словарь, потому что занят OpenSpec. WHEN и AND — из того же набора и по той же причине должны бы не браться, но взяты. Это нестыковка, а не решение.

Варианты:

  • перевести на КОГДА / И: словарь набора один, и служебные слова внутри канона следуют ему же;
  • оставить и объяснить: блок стыка описывает поведение системы во времени, а не выбор автора, — то есть это единственное место, где форма спецификации уместна, и заимствование её синтаксиса намеренно.

Второе честнее по смыслу (субъект там действительно система), но требует явной оговорки в LANGUAGE.md, иначе читается как недосмотр. Заодно решить, подпадают ли WHEN/AND под проверку «заглавные модальные слова не встречаются вне правил»: сейчас формально нет, потому что в словаре их нет.

13. Критерий «названного вреда» никого не обязывает

LANGUAGE.md говорит, что ДОЛЖЕН требует двух условий сразу: нарушение причиняет названный вред (критерий BCP 14) и норма проверяема машиной (META-6). Но правила под первое условие нет: META-6 работает только в одну сторону — «нет машинной проверки, понижай в СЛЕДУЕТ». Обратной, «проверка есть, а вреда нет — не повышай», не существует, и автор ничем не связан.

Решить, заводить ли META-24 под первое условие или оставить его семантикой шкалы в описании языка. За правило: механически проверяемых мелочей больше, чем важных вещей, и без нормы шкала размывается тем же способом, от которого META-6 её защищает. Против: критерий «вред назван» проверяется чтением, а не машиной, — то есть по META-6 сам он может быть только СЛЕДУЕТ.

14. Одиннадцать таблиц не прочитаны на взаимоисключительность

LANGUAGE.md объявил, что строки таблицы решений взаимоисключающи по умолчанию, а иной порядок объявляется явно. Объявление не делает таблицы такими: нумерованные строки есть в одиннадцати файлах канона, и ни одна таблица под новое требование не прочитана.

Конкретный подозреваемый — SLOG-11: «по реальному действию или изменению» против «повторяющаяся служебная, по таймеру или поллингу». Периодическая операция, которая всё-таки меняет данные, подходит под обе строки, и уровень из таблицы не выводится однозначно.

Работа читательская, машине не даётся; в список проверок она уже записана в разделе «Чтением, потому что машине не даётся».

15. Возможность, записанная модальным словом

Четвёртая категория ISO — возможность и осуществимость — ключевого слова не имеет: такие утверждения пишутся обычной прозой. Значит канон надо просмотреть на обратную ошибку: где утверждение о факте («библиотеки по умолчанию отдают именно его», «SQLite сравнивает строки побайтово») записано модальным словом и тем самым превратилось в норму, которую никто не вводил.

Смотреть в первую очередь абзацы «Почему»: там факты и стоят, там же соблазн усилить их модальностью выше всего.

Из вчерашнего, не закрыто

  • Вынос арх-ядра из errors и logging: закроет две хрупкие ссылки из web-ui (стек) в go-слой — единственные ссылки стек → язык в каноне.
  • conv check должен уметь отличать ссылку на удалённое правило от упоминания дыры: сейчас META-16 в прозе «Оформления» даёт ложное срабатывание.