--- prefix: META --- # Как мы ведём конвенции Конвенция описывает повторяющийся выбор: как называть директории, как раскладывать данные, как оформлять ошибки. Она отвечает на вопрос «как принято», а не «что здесь происходит». Как записывается сама конвенция — правила, модальность, обоснования — в [LANGUAGE.md](LANGUAGE.md). Здесь — про то, зачем конвенции заводятся, где живут и как соотносятся с соседними видами документов. ## Область действия Правила ниже адресованы автору конвенции — тому, кто заводит новый файл или правит существующий, в каноне и в копиях репозиториев. Обязательность живёт на отдельном правиле, а не на файле; шкала модальных слов — в [LANGUAGE.md](LANGUAGE.md). ## Отличие от соседей - `docs/adr/` — **решение**, принятое однажды и постфактум («почему выбрали Authelia, а не Keycloak»). Запись неизменяема. - `docs/specs/` и OpenSpec, где они есть, — **что** система делает, наблюдаемое поведение как контракт. Конвенция — **как** написан код; в спеки она не переносится, это не capability. - `docs/drafts/` — оперативная хроника и черновики, «что собираюсь сделать». - `docs/conventions/` — **правило на будущее**, применяемое многократно. Живой документ: правится, когда договорённость меняется. ## Оформление Имя файла — kebab-case по теме: `app-directories.md`. Правилом это не записано, и это случай META-25: регуляркой имя проверяется тривиально, но обоснование сводится к «чтобы имя файла в реестре префиксов писалось одним способом» — вреда от нарушения нет, значит и высшей модальности нет, а на СЛЕДУЕТ такое правило не окупает строчку. Номер META-16, под которым оно существовало, оставлен свободным и не переиспользуется. ## Канон и копии Файлы в `docs/conventions/` с шапкой `origin:` — копии из общего канона `dev-conventions`, а не собственные документы репозитория. Копия собирается из канона целиком, поэтому репозиторное живёт ниже маркера локальной части в конце файла: обновление сохраняет всё, что ниже маркера, и переписывает всё, что выше. Откуда взяты копии и где брать обновления — в манифесте `.conventions.toml` в корне репозитория. Отдельного механизма отчёта о расхождении нет: обновление перезаписывает файлы в рабочем дереве, а что именно изменилось, показывает `git diff` до коммита. Поэтому в шапке копии хранится только `origin:` — отпечатков канона и дат синхронизации в ней нет, историю держит git. Правка выше маркера означает одно из двух: улучшение, которое переносят в канон, или документ, переставший быть копией, — тогда `origin:` из шапки убирают. ## Правила ### META-1. Одна конвенция — один файл **ДОЛЖЕН.** Файл описывает ровно один повторяющийся выбор. **Почему.** Подписка перечисляется по темам, и тему берут целиком. Файл, собравший две темы, вынуждает репозиторий взять правила, которые ему не нужны, и вычитывать чужую половину при каждом обновлении. Разрезать позже дорого: перенос правила в другой файл — это новый префикс и новая нумерация, поэтому после разреза все внешние ссылки обходят руками. ### META-2. Конвенция заводится, когда решение принимается третий раз **СЛЕДУЕТ.** Поводом служит одно и то же решение, принятое третий раз и каждый раз чуть по-другому. **Почему.** По одному-двум случаям не видно, что в решении повторяется, а что было частностью места: правило, выведенное из первого случая, кодирует частность и дальше мешает больше, чем помогает. Единичный выбор, к тому же спорный, читателю нужен вместе с мотивом — это ADR, где мотив и есть содержание записи. ### META-3. Новая конвенция пишется там, где заболело **СЛЕДУЕТ.** Первые шаги пути «находка → конвенция → правило линтера → удаление прозы» делаются в репозитории, где случилась находка; в канон продвигается общая часть. **Почему.** Правило, написанное сразу в общем виде, не проверено ни одним применением, и условие применимости у него придумано, а не найдено, — платят за это все потребители сразу. Формулировка, обкатанная на одном репозитории, приезжает в канон уже с известной границей. ### META-4. В тексте конвенции нет утверждений о состоянии репозитория **НЕ ДОЛЖЕН.** Норма пишется в настоящем предписывающем времени, без описаний того, как сейчас устроен конкретный репозиторий. **Почему.** Такое утверждение устаревает молча и подменяет норму описанием: читатель перестаёт понимать, что от него требуется, а что просто констатировано. В копиях у остальных потребителей чужой факт вдобавок ложен с первого дня. «Так сделано у нас» — содержимое региона отступлений, где оно и локально, и проверяемо. ### META-20. Норма самодостаточна, наружу смотрит только обоснование **ДОЛЖЕН.** Норму правила можно исполнить, имея один этот файл. Ссылка на правило чужой темы допустима в «Почему», в «Связано» и в разграничении области действия — но не в самой норме. Если норме нужен концепт соседней темы, он коротко повторяется здесь, а сосед называется в «Почему» как источник решения. **Почему.** Репозиторий подписывается на произвольное подмножество конвенций, и графа зависимостей у него нет по построению. Норма, которую нельзя исполнить без отсутствующего файла, делает такое подмножество невалидным молча: читатель видит связный текст и не замечает, что часть нормы не определена. Обоснование, потерявшее адресата, деградирует честно — пропадает перекрёстная проверка, смысл остаётся. Цена повтора — риск разойтись с источником; она платится сознательно и видна, в отличие от скрытой зависимости. ### META-21. Ссылка ведёт на тему или на правило, но не на путь в каноне **ДОЛЖЕН.** На соседнюю конвенцию ссылаются именем темы (конвенция `logging`), на конкретное правило — идентификатором (`SLOG-27`); путь файла канона в тексте конвенции не употребляется. **Почему.** В репозитории конвенция лежит собранной: слои одной темы — это секции одного файла, и пути `lang/go/logging.md` там не существует. Ссылка на путь канона умирает при сборке, причём молча — текст остаётся связным. Имя темы и идентификатор правила переживают и сборку, и переезд файла между осями. Слой своей темы поэтому называют идентификатором его правила, а не словами «базовый слой»: слова не проверяются и не ведут к утверждению. ### META-24. Слой ссылается на идентификаторы своего базового слоя **ДОПУСКАЕТСЯ.** Правило языкового или стекового слоя называет идентификатор правила арх-слоя своей темы прямо в норме. **Почему.** Подписываются темой, а не слоем: собранный файл начинается с арх-слоя независимо от того, какие язык и стек выбраны, — базовое правило в копии всегда рядом, и ссылка на него никуда не ведёт. Повтор его концепта здесь заводил бы второй источник правды внутри одного документа: META-20 требует повторять концепт там, где соседнего файла может не быть, а базовый слой отсутствовать не может. Остальные слои темы попадают в копию по манифесту, и такой гарантии у них нет — отсюда узость разрешения. Записано оно явно, потому что META-20 читают строже, чем он есть, и без этой строки базу дублируют без нужды. ### META-5. Расхождение кода с правилом — отступление, а не повод переписать правило **ДОЛЖЕН.** Правило правится, только когда неверно по существу: содержит фактическую ошибку, внутреннее противоречие или условие применимости, которое не даёт ответа. **Почему.** Правило, подогнанное под текущий код, перестаёт что-либо требовать — оно описывает то, что и так происходит, и первое же расхождение переписывает его снова. Направление «конвенция → код» держится ровно тем, что факт не считается аргументом. ### META-6. `ДОЛЖЕН` без механической проверки либо механизируется, либо понижается **ДОЛЖЕН.** Правило, нарушение которого объявлено ошибкой, получает машинную проверку или переводится в СЛЕДУЕТ. **Почему.** Без проверки правило держится на внимании: нарушения копятся молча и всплывают выборочно — на том ревью, куда дошли руки. Для СЛЕДУЕТ это честно, а ДОЛЖЕН при этом обещает то, чего не делает, и через несколько таких случаев обесценивает остальные ДОЛЖЕН в файле. Отсюда следствие: правило, машинная проверка которого невозможна в принципе (вкус формулировки, выбор границы, суждение о ситуации), не может быть ДОЛЖЕН — его модальность СЛЕДУЕТ по построению, а не по слабости. ### META-25. Высшая модальность выбирается, только когда назван вред **СЛЕДУЕТ.** Правило получает ДОЛЖЕН или НЕ ДОЛЖЕН, если в «Почему» сказано, что́ ломается при нарушении. **Почему.** Машинная проверка — условие необходимое (META-6), но не достаточное: проверяемых мелочей больше, чем важных вещей, и без второго условия единственным фильтром остаётся удобство проверки. Шкала наполняется опрятностью, читатель перестаёт отличать «уронит прод» от «неаккуратно» — и обесцениваются все ДОЛЖЕН в файле, ровно то, от чего META-6 защищает с другой стороны. Само это правило машиной не проверяется: «вред назван» устанавливается чтением, поэтому его собственная модальность по META-6 — СЛЕДУЕТ. ### META-7. Факт механизации фиксируется в копии со ссылкой на правило **ДОЛЖЕН.** Запись о механизации называет идентификатор правила и конкретную проверку. **Почему.** Механизация — состояние конкретного репозитория, канон о ней не знает, а без записи следующий автор либо заведёт вторую проверку того же, либо будет вычитывать глазами уже проверенное машиной. Без идентификатора читатель догадывается сам, к какому утверждению относится проверка, — и догадывается по-разному. ### META-8. Формулировка не удаляется из канона, пока механизирована не у всех **НЕ ДОЛЖЕН.** Норма остаётся в каноне, пока хотя бы у одного потребителя машинной проверки нет. **Почему.** У кого линтера нет, тот после удаления остаётся без правила вообще: ни проверки, ни текста. Механизация у одного потребителя ничего не говорит о прочих, поэтому удалять по факту «у нас уже проверяется» — значит чинить свой файл за чужой счёт. Списка подписчиков канон по построению не знает, поэтому факт «механизировано у всех» устанавливается обходом репозиториев вручную — это часть работы по удалению нормы, а не то, что можно проверить машиной (см. `LANGUAGE.md`, состояние МЕХАНИЗИРОВАНО). ### META-9. Общая механизация разрешает удалить норму из канона **ДОПУСКАЕТСЯ.** Правило, уехавшее в общий конфиг линтера или в общую роль, удаляется из канона. **Почему.** Формулировка, дублирующая работающую у всех проверку, размазывает внимание: файл на несколько сотен строк заставляет человека и агента добросовестно вычитывать тривиальное именование и не доходить до формы решения. Явное разрешение нужно, чтобы META-8 не читался как запрет удалять вообще. ### META-10. Обоснование не удаляется никогда **НЕ ДОЛЖЕН.** Блок «Почему» остаётся и после того, как норма уехала в линтер. **Почему.** Линтер сообщает, что нарушено, но не сообщает, зачем правило существует. Без обоснования не видно, когда причина отпала, — проверка продолжает работать по инерции, и возразить ей нечем, кроме как отключив. ### META-26. Обоснование объясняет, а не требует **НЕ СЛЕДУЕТ.** Абзац «Почему» не повторяет норму словами обязательства — на ту норму, на которую он опирается, ссылаются идентификатором. **Почему.** Обоснование, сформулированное как требование, заводит вторую копию нормы: у оригинала есть идентификатор, у копии нет, и расходятся они при первой же правке оригинала. Читатель не отличает объяснение от досказанной нормы, а отступление от копии не адресуемо — ссылаться на абзац нечем. Утверждения о невозможности («нельзя», «не выйдет») сюда не относятся: они описывают, чего не бывает, а не то, что запрещено, и проза — их законное место. Модальность здесь рекомендательная не по слабости, а по META-6: машинная проверка даёт кандидатов, а не вердикт, потому что слова омонимичны — «за попыткой следует повтор» и «присвоение становится обязанностью каждого вызывающего» описывают ход событий. Вердикт остаётся за чтением, грепу достаётся роль подсказки. ### META-11. У трудноизменяемого слоя область действия пишется явно **ДОЛЖЕН.** Конвенция о схеме БД, формате хранения или раскладке директорий называет, к чему применяется: к новым таблицам и миграциям, а не к состоянию схемы. **Почему.** Здесь не работает привычное «новое пишем правильно, старое переезжает по мере касания»: таблица не переезжает от того, что её потрогали. Без явной рамки правило читается как требование к текущему состоянию, и оба исхода плохи — миграция живых данных ради опрятности либо молчаливый вывод, что конвенция не соблюдается совсем. ### META-12. Механизируется граница изменения, а не состояние **ДОЛЖЕН.** Проверка запрещает нарушение в новых миграциях, а не в уже существующей схеме. **Почему.** Проверка состояния краснеет на легаси с первого дня: её отключают или обвешивают вечным списком исключений — и она перестаёт ловить новое, ради чего заводилась. Проверка границы оставляет старое в покое и делает новую ошибку невозможной. ### META-13. Список отступлений трудноизменяемого слоя — постоянный **ДОЛЖЕН.** Перечисленные старые таблицы и раскладки живут как есть, а не как задачи на дочистку. **Почему.** Список, записанный долгом, требует либо мигрировать живые данные без выгоды, либо год за годом объяснять невыполненный план. Второе кончается тем, что список перестают вести, — и пропадает единственное место, где видно, где именно правило не действует. ### META-14. Отступления перечисляются поимённо, со ссылкой на правила **ДОЛЖЕН.** В локальной части копии перечислены отступления, которые уже есть в коде, с идентификатором правила и причиной. **Почему.** Иначе репозиторий выглядит соблюдающим конвенцию, а проверить это можно только чтением всего кода. Со ссылками отступления счётны: видно, сколько правил конвенции репозиторий реально не соблюдает. Пустой список при этом почти всегда означает не отсутствие отступлений, а то, что их не искали. ### META-15. Запись об отступлении разбирается по масштабу **ДОЛЖЕН.** Судьба записи зависит от того, что в ней сказано: | № | Что записано | Куда идёт | |---|---|---| | META-15.1 | правилу не следуем в перечисленных местах, причина названа | остаётся отступлением | | META-15.2 | правило не применяется в репозитории целиком | чинится условие применимости — в каноне | | META-15.3 | конвенция репозиторию не нужна | копия удаляется, подписки нет | **Почему.** Отступление описывает исключение, и по нему видно, какая часть правила нарушена. Запись «мы это правило вообще не применяем» такой информации не несёт и маскирует одну из двух чинимых причин: неверную рамку правила в каноне, которую чинят один раз для всех, или лишнюю подписку, где файл просто не нужен. Оставленная отступлением, она прячет обе. ### META-17. Ссылки на код репозитория стоят в локальной части, а не в «Связано» **ДОЛЖЕН.** Раздел «Связано» канона содержит только ссылки, верные у всех потребителей; ссылки на ADR, код и файлы конкретного репозитория — в локальной части копии. **Почему.** Текст канона приезжает ко всем потребителям, и ссылка на чужой файл у них битая с первого дня. Ниже маркера та же ссылка никого не задевает и переживает обновление, потому что обновление её не трогает. ### META-22. Репозиторное в копии пишется ниже маркера локальной части **ДОЛЖЕН.** Правки репозитория вносятся ниже маркера, а не в текст, пришедший из канона. **Почему.** Обновление перезаписывает всё, что выше маркера, поэтому правка там живёт до первого `pull`. Заметить пропажу можно, только вычитав `git diff` целиком — а он в этот момент и без того полон изменений канона, и своя строка теряется среди чужих. ### META-23. Документ, переставший быть копией, не носит `origin:` **НЕ ДОЛЖЕН.** Файл, который развели с каноном намеренно, шапку `origin:` не сохраняет. **Почему.** По `origin:` решается, какие файлы пересобирать из канона. Форк, оставивший шапку, при первом же обновлении теряет ровно то, ради чего его заводили. Происхождение такого документа остаётся в истории коммита, где оно никого не вводит в заблуждение. ### META-18. README директории перечисляет конвенции с однострочным описанием **СЛЕДУЕТ.** Одна плоская таблица: файл и строка о том, про что он. **Почему.** Подписка — это набор лежащих файлов, и без описаний вопрос «какая из них про мой случай» решается открыванием каждой. Ценой в десяток файлов это означает, что не открывают ни одной. ### META-19. Короткие инварианты дублируются в точку входа агента **ДОЛЖЕН.** В `AGENTS.md` / `CLAUDE.md` едет одна строка на правило с его идентификатором; детали остаются в конвенции. **Почему.** Сама по себе конвенция агенту не видна: он дойдёт до неё, только если его туда отправили, — а безусловно он читает точку входа. Строка с идентификатором служит и напоминанием, и адресом, по которому за подробностями идут; перенос деталей туда же вернул бы задачу поддержки двух текстов.