@rt-tools/agent-kit 0.5.3 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/README.md +15 -1
  2. package/assets/checks/check-file-size.mjs +127 -0
  3. package/assets/checks/check-push-gate.mjs +139 -0
  4. package/assets/checks/rt-kit-checks.config.mjs +21 -0
  5. package/assets/defaults/gate-map.sh +90 -34
  6. package/assets/defaults/project.sh +26 -3
  7. package/assets/hooks/skill-gate-layers.sh +156 -0
  8. package/assets/hooks/skill-gate.sh +11 -2
  9. package/assets/laws/code-structure.md +3 -0
  10. package/assets/laws/delivery.md +9 -0
  11. package/assets/laws/observability.md +46 -0
  12. package/assets/laws/project-documentation.md +4 -0
  13. package/assets/laws/reuse-first.md +2 -0
  14. package/assets/laws/verifiability.md +5 -0
  15. package/assets/patterns/browser-verification-stand.md +22 -2
  16. package/assets/patterns/doc-style-trace.md +111 -0
  17. package/assets/patterns/git-workflow-commit.azure.md +18 -0
  18. package/assets/patterns/git-workflow-commit.github.md +19 -1
  19. package/assets/patterns/git-workflow-commit.gitlab.md +18 -0
  20. package/assets/patterns/git-workflow-docker.md +203 -0
  21. package/assets/patterns/git-workflow-secrets.md +93 -0
  22. package/assets/patterns/observability-record.md +114 -0
  23. package/assets/patterns/seo-verify.md +1 -1
  24. package/assets/patterns/spec-driven-domain.md +2 -2
  25. package/assets/patterns/spec-driven-rule.md +5 -0
  26. package/assets/patterns/styling-bem-sheet.md +178 -0
  27. package/assets/patterns/task-flow-close.md +20 -0
  28. package/assets/patterns/task-flow-resume.md +5 -0
  29. package/assets/patterns/translations-content.md +107 -0
  30. package/assets/patterns/translations-key.md +1 -1
  31. package/assets/rules/angular-patterns.md +5 -0
  32. package/assets/rules/browser-verification.md +17 -12
  33. package/assets/rules/component-structure.md +6 -2
  34. package/assets/rules/doc-style.md +16 -0
  35. package/assets/rules/git-workflow.azure.md +60 -1
  36. package/assets/rules/git-workflow.github.md +67 -1
  37. package/assets/rules/git-workflow.gitlab.md +61 -1
  38. package/assets/rules/lists.md +13 -0
  39. package/assets/rules/observability.md +147 -0
  40. package/assets/rules/permissions.md +23 -0
  41. package/assets/rules/reuse-first.md +9 -0
  42. package/assets/rules/seo.md +57 -9
  43. package/assets/rules/shared-code.md +6 -0
  44. package/assets/rules/spec-driven.md +9 -0
  45. package/assets/rules/styling-bem.md +34 -1
  46. package/assets/rules/task-flow.md +5 -0
  47. package/assets/rules/testing.md +46 -8
  48. package/assets/rules/translations.md +11 -5
  49. package/assets/rules/typescript-conventions.md +5 -0
  50. package/lib/assets.d.ts +1 -1
  51. package/lib/assets.d.ts.map +1 -1
  52. package/lib/assets.js +2 -4
  53. package/lib/assets.js.map +1 -1
  54. package/lib/catalog.d.ts +73 -0
  55. package/lib/catalog.d.ts.map +1 -1
  56. package/lib/catalog.js +121 -0
  57. package/lib/catalog.js.map +1 -1
  58. package/lib/commands.d.ts.map +1 -1
  59. package/lib/commands.js +84 -5
  60. package/lib/commands.js.map +1 -1
  61. package/lib/integrity.d.ts +25 -12
  62. package/lib/integrity.d.ts.map +1 -1
  63. package/lib/integrity.js +40 -18
  64. package/lib/integrity.js.map +1 -1
  65. package/lib/proposals.d.ts +9 -1
  66. package/lib/proposals.d.ts.map +1 -1
  67. package/lib/proposals.js +11 -2
  68. package/lib/proposals.js.map +1 -1
  69. package/lib/retired.d.ts +30 -0
  70. package/lib/retired.d.ts.map +1 -0
  71. package/lib/retired.js +19 -0
  72. package/lib/retired.js.map +1 -0
  73. package/lib/sync.d.ts +45 -1
  74. package/lib/sync.d.ts.map +1 -1
  75. package/lib/sync.js +44 -10
  76. package/lib/sync.js.map +1 -1
  77. package/package.json +1 -1
  78. package/rt-tools-agent-kit-0.7.0.tgz +0 -0
  79. package/assets/laws/application/money.md +0 -41
  80. package/assets/laws/application/ownership.md +0 -32
  81. package/assets/patterns/ownership-scope-resolve.md +0 -69
  82. package/assets/patterns/pricing-quote.md +0 -71
  83. package/assets/rules/ownership-scope.md +0 -63
  84. package/assets/rules/pricing.md +0 -64
  85. package/rt-tools-agent-kit-0.5.3.tgz +0 -0
@@ -2,7 +2,7 @@
2
2
  name: git-workflow
3
3
  kind: rule
4
4
  law: delivery
5
- description: Правило под «Закон о поставке» для дерева на GitLab. Брать на заведение задачи, ветки, коммит, пуш, создание MR, слияние, а также на правку схемы хранилища, её миграций и вызовы миграций. Называет задачу на борде как начало работы, колонку задачи как ход работы, соответствие задачи и ветки один к одному, имя ветки, формат коммита, учётную запись машинной работы, обязательный состав MR, гарды поставки и сверку очереди работ. Готовый код — в паттернах git-workflow-commit, git-workflow-merge, git-workflow-migration и git-workflow-restart.
5
+ description: Правило под «Закон о поставке» для дерева на GitLab. Брать на заведение задачи, ветки, коммит, пуш, создание MR, слияние, а также на правку схемы хранилища, её миграций и вызовы миграций. Называет задачу на борде как начало работы, колонку задачи как ход работы, соответствие задачи и ветки один к одному, имя ветки, формат коммита, учётную запись машинной работы, обязательный состав MR, гарды поставки и сверку очереди работ. Готовый код — в паттернах git-workflow-commit, git-workflow-merge, git-workflow-migration, git-workflow-restart, git-workflow-docker и git-workflow-secrets.
6
6
  ---
7
7
 
8
8
  # Поставка — как это устроено здесь
@@ -62,6 +62,11 @@ description: Правило под «Закон о поставке» для д
62
62
  - **Задачи, чинящиеся одной правкой, сливаются до слияния ветки.** Вторая стирается вместе с
63
63
  номером, а недостающее из неё дописывается в первую. После слияния слить уже нельзя: ветка
64
64
  въехала, и откатывается она целиком.
65
+ - **Работа, которую одним заходом не закрыть, помечена в двух местах, и они сверяются.** Метка
66
+ на доске и строка о заходах с передачей в линии работ говорят одно и то же двум читателям:
67
+ исполнитель открывает карточку раньше, чем линию, а планирует по линии. Одна пометка без
68
+ другой лжёт молча, поэтому сверка очереди судит пару в обе стороны. Помечается только то, что
69
+ законно не делится: пометка объёма правом делить не становится.
65
70
  - **Слияние в главную ветку выкатывает прод.** Правила `only`/`rules` конвейера покрывают
66
71
  документы отдельно, поэтому переменные окружения, секреты и записи имён ставятся до слияния,
67
72
  а не после.
@@ -71,6 +76,25 @@ description: Правило под «Закон о поставке» для д
71
76
  умолчаниями, которые он разрешает. Умолчание образа задаётся в самом образе.
72
77
  - **Образы выкатываются по sha коммита, а не по метке «последний».** Метка в реестре отстаёт
73
78
  от главной ветки, и прод молча возвращается к прежней версии, продолжая отвечать.
79
+ - **Выкатка убирает за собой старые образы, оставляя три последних sha.** Помеченный sha образ
80
+ висячим не бывает никогда, и чистка висячего его не касается: за полгода они съедают диск
81
+ сервера целиком. Три sha — это глубина отката, и меньше брать нельзя: поломка, замеченная
82
+ через две выкатки, откатывается уже некуда.
83
+ - **Описание прода правится вместе с составом прода.** Устройство, путь запроса, гейты и
84
+ бэкапы описаны текстами вне слоёв правил, и ни линтер, ни сборка их не читают: расхождение
85
+ копится молча, а читают эти тексты как действующие. Пару стережёт гард документов.
86
+ - **Правка конвейера прогоняется до слияния ручным запуском.** Конвейер запускается на любой
87
+ ветке, а задание выкатки прибито правилом к главной: прогон ради проверки доходит до сборок и
88
+ там кончается. Прогон команд задания на своей машине его не покрывает: он проверяет команды,
89
+ а не файл конвейера, — верность самого файла читается только по списку конвейеров после
90
+ пуша, и синтаксис отдельно судит проверка `.gitlab-ci.yml` в проекте.
91
+ - **Отчёт проверяется до слияния тем же конвейером, что и главная ветка.** Проверки и сборки
92
+ образов идут на конвейере запроса слияния, выкатка — нет: её держит правило по главной ветке
93
+ у своего задания, а образ отчёта в реестр не уезжает.
94
+ - **Расхождение прода с главной веткой видно сверкой очереди работ.** Задача уходит из очереди
95
+ слиянием, но слияние — ещё не прод: отказавшая выкатка не трогает ни задачу, ни её список, и
96
+ заметить её неоткуда. Сверка спрашивает последний конвейер главной ветки и судит только
97
+ завершённый: идущий ещё может кончиться выкаткой.
74
98
  - **Цепочка миграций прогоняется с пустого хранилища до слияния.** Порядок применения
75
99
  лексикографический по имени каталога, а метку времени ставит момент создания: миграция из
76
100
  ветки, начатой раньше, встаёт перед той, от которой зависит.
@@ -80,6 +104,25 @@ description: Правило под «Закон о поставке» для д
80
104
  заголовок читается списком, а свободный текст — только целиком.
81
105
  - **Перед пушем прогоняются все линтеры, а не один.** Линтер кода обычно не читает файлы
82
106
  стилей вовсе, и правила оформления без второго прогона не проверяет ничто.
107
+ - **Сборка входит в набор наравне с линтом и юнитами.** Линтер типов не читает, а юниты читают
108
+ только то, что импортировано тестом: ошибка типов в непокрытом коде доживает до сборки
109
+ образа, то есть до слияния. Четыре слияния подряд так и уехали в главную ветку, ломая
110
+ выкатку.
111
+ - **Набор гейта пуша не бывает уже набора конвейера.** Гейт — обещание, что пуш не приедет
112
+ красным; набор, из которого выкинуты сборка и снимки, обещает то, чего не проверяет. Шаг
113
+ конвейера, которому в наборе гейта нет ни строки, ни объявленного исключения с причиной,
114
+ отбивает пуш, а не печатается рядом с ним: напечатанное предупреждение исполнитель читает как
115
+ разрешение. Дважды подряд правка, прошедшая гейт целиком, была отбита конвейером — и оба раза
116
+ зелёный гейт был прочитан как «локально всё зелено».
117
+ - **После вливания главной ветки набор проверок пересматривается по тому, что ветка везёт
118
+ теперь.** Вливание меняет состав правки: проверять по тому, что правил автор, — значит
119
+ проверять половину, а отвечает ветка целиком. Ветка, не тронувшая ни строки показа, прогоняет
120
+ снимки витрин с того момента, как вливание принесло чужую правку оформления.
121
+ - **Утверждение о главной ветке делается по удалённой ссылке, а не по локальной.** Локальная
122
+ протухает в ту минуту, когда её подтянули в последний раз, и молчит об этом: она не пуста и не
123
+ сломана, она описывает вчерашний день. Сравнение веток пишется от `origin/main` целиком —
124
+ смешав в одной команде удалённую ссылку для одной стороны и локальную для другой, промах
125
+ изнутри выглядит правильным.
83
126
  - **Слияние по кнопке «Merge when pipeline succeeds» не заменяет проверок до пуша.** Конвейер
84
127
  видит только то, что уже отправлено, а отправленная красная ветка занимает очередь работ и
85
128
  выглядит готовой к разбору.
@@ -121,6 +164,8 @@ description: Правило под «Закон о поставке» для д
121
164
  - `git-workflow-merge` — главная ветка влита в ветку задачи, конфликт разобран.
122
165
  - `git-workflow-migration` — правка схемы хранилища и её миграций.
123
166
  - `git-workflow-restart` — ручной перезапуск прода.
167
+ - `git-workflow-docker` — образы на своей машине: демон, реестр, сборка под платформу сервера.
168
+ - `git-workflow-secrets` — ключи внешних служб: где лежат, как заводятся, что говорит их состояние.
124
169
 
125
170
  ## Ловушки
126
171
 
@@ -144,6 +189,21 @@ description: Правило под «Закон о поставке» для д
144
189
  записи, на следующий вызов это не переносится: MR открывают токеном учётной записи машинной
145
190
  работы, и от того, чьей записью он открыт, зависит, кого можно назначить ревьювером. Однажды
146
191
  смена записи ради пуша утекла в публикацию — отчёт вышел от владельца.
192
+ - **Невалидный файл конвейера виден отказом сразу после пуша, а не упавшим заданием.** Конвейер
193
+ на такой файл не заводится вовсе: в списке стоит запись об ошибке разбора, а внутри нет ни
194
+ задания, ни лога. Поэтому список конвейеров ветки смотрится тем же движением, что и пуш —
195
+ `glab ci list --branch <ветка>`, — а сам файл до пуша судит проверка `.gitlab-ci.yml` в
196
+ проекте.
197
+ - **`online` у раннера на своей машине означает запущенный процесс, а не работающий
198
+ конвейер.** Две стороны сходятся отдельно: `tags` у заданий и теги самого раннера. Пока
199
+ пересечения нет, раннер стоит `online` и не берёт ничего, а задания ждут общего раннера — по
200
+ состоянию это выглядит настроенным. Владельцу называют выполненное задание с его номером, а
201
+ не строку состояния.
202
+ - **Вход в реестр образов из раннера, запущенного службой, отказывает молча.** Служба идёт без
203
+ сеанса пользователя, а клиент реестра уходит в системный помощник хранения ключей и получает
204
+ отказ во взаимодействии — задание падает до сборки. Свой каталог настроек с пустым помощником
205
+ не спасает: клиент переписывает пустое значение обратно сам. Готовые команды — паттерн
206
+ `git-workflow-docker`.
147
207
  - **Новое рабочее дерево получает только то, что лежит в индексе.** `git worktree add`
148
208
  разворачивает коммит, а настройки, ключи, локальные разрешения и зависимости в коммит не
149
209
  входят: свежее дерево выглядит готовым и упирается в нехватку не сразу, а на первом гарде,
@@ -30,11 +30,24 @@ description: Правило под «Закон о списке записей»
30
30
 
31
31
  ## Как закон применяется здесь
32
32
 
33
+ - **Страница собирается общим компонентом страницы списка, а не своей разметкой.** Заголовок,
34
+ панель действий, зона прокрутки и переключатель страниц одинаковы у всех списков, и
35
+ переписанные заново они расходятся молча.
36
+ - **Механика экрана берётся из общей основы списочного экрана, а не пишется заново.** Экран
37
+ домена объявляет стор, ключ таблицы, поля сортировки и столбцы; выборка из адреса и в адрес,
38
+ страница, её размер, сортировка, тост отказа и переходы в панель уже там.
39
+ - **Таблицу экран объявляет сам и кладёт внутрь шаблона.** Обернуть её нельзя: столбцы она
40
+ собирает собственным запросом по содержимому, и через посредника они до неё не доходят.
33
41
  - **Список собирается `<префикс>-table`, а не своей разметкой.** Скелетоны, пустое состояние,
34
42
  карточки на узком экране и настройка столбцов — входы таблицы; свой
35
43
  `@if (rows().length === 0)` означает, что экран собран мимо неё.
36
44
  - **Строки объявляются на `rowsTable.displayedColumns()`, а не на своём списке.** Столбец с
37
45
  меню таблица добавляет сама.
46
+ - **Заголовок сортируемой колонки называет поле сервера, а не ключ колонки.** Колонка и поле
47
+ совпадают не всегда, и пока в разметке стоит ключ колонки, экран держит две карты перевода в
48
+ обе стороны.
49
+ - **Сортируема та колонка, у чьей ячейки шапки стоит заголовок сортировки.** Отдельного
50
+ признака рядом со списком столбцов нет, и расходиться нечему.
38
51
  - **Клик по строке открывает запись, а меню — для действий над ней.** Вид нажимаемой строки
39
52
  даёт `clickable`, активацию мышью и с клавиатуры — `vmTableRow`; клик по кнопке внутри строки
40
53
  активацией не считается.
@@ -0,0 +1,147 @@
1
+ ---
2
+ name: observability
3
+ kind: rule
4
+ law: observability
5
+ description: Правило под «Закон о наблюдаемости». Брать при правке логгера, контекста запроса, домена отказов и домена оповещений, при заведении новой строки лога и когда решается, что владелец узнает об отказе. Называет уровни, номер обращения, вычистку секретов, сводку старта и то, что уходит наружу при отказе. Готовый код — в паттерне observability-record.
6
+ ---
7
+
8
+ # Наблюдаемость — как это устроено здесь
9
+
10
+ Правило под закон `docs/constitution/observability.md`. Закон говорит, что владелец должен
11
+ знать о работе приложения; здесь — как это названо в этом дереве, где лежит и чего пока нет.
12
+
13
+ ## Как это называется здесь
14
+
15
+ | В законе | Здесь |
16
+ | ---------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
17
+ | то, что приложение о себе пишет | логи приложения: одна строка на каждый вызов логгера, в машинном виде |
18
+ | ступень важности | уровень: от подробностей разбора до отказа, поднявшего процесс |
19
+ | номер обращения | идентификатор запроса, он же заголовок ответа |
20
+ | признак, по которому находятся все отказы одного обращения | тот же номер обращения — он стоит в каждой строке лога |
21
+ | подробности отказа | разобранная причина: класс, текст, код протокола, код и подробности от хранилища, срезанный стек |
22
+ | общий текст ошибки наружу | внутренняя ошибка без подробностей |
23
+ | вычистка секретов | замена значений по имени поля |
24
+ | отказ в хранилище | то, что домен отказов сохранил: группа отказов и её вхождения |
25
+ | то, с чем приложение поднялось | сводка старта: одна строка лога с версией, портом, адресом хранилища и списками возможностей — включённых, выключенных и сломанных |
26
+
27
+ ## Где это лежит
28
+
29
+ В этом дереве — таблица в `implementation.md` рядом. Пути живут там, а не здесь: правило
30
+ переносится между репозиториями, раскладка — нет, и путь, названный в правиле, врёт в первом
31
+ же дереве, которое держит код иначе.
32
+
33
+ ## Как закон применяется здесь
34
+
35
+ - **Номер обращения заводится один раз на запрос и стоит в каждой строке лога о нём.** По
36
+ времени строки одного обращения не отобрать: обращений бывает десяток в секунду.
37
+ - **Номер обращения уходит вызывающему заголовком ответа и в подробностях отказа.** Он же стоит
38
+ в логах, поэтому названный человеком номер ищется прямым отбором. Заголовок при этом открыт
39
+ странице чужого домена: без явного разрешения браузер его не отдаёт.
40
+ - **Номер, пришедший снаружи, чистится и укорачивается, а пустой заводится заново.** Значение
41
+ из заголовка приходит от кого угодно, а попадает в отбор по хранилищу.
42
+ - **Наружу уходит код и общий текст ошибки, подробности остаются в логах.** Иначе гость на
43
+ сломанной выкатке прочитает имя колонки и устройство хранилища.
44
+ - **Отказ по вводу и правам пишется отдельно от поломки и без стека.** Это сработавшая
45
+ проверка. На одном уровне с поломками она забивает тревогу опечатками гостей.
46
+ - **Причина отказа разбирается в одном месте.** Иначе одна и та же ошибка приходит в логи
47
+ тремя разными формами.
48
+ - **Поля строки лога вычищаются всегда, а не по решению того, кто пишет.** Решать на каждом
49
+ вызове, есть ли в полях секрет, — значит однажды ошибиться.
50
+ - **Приложение при старте пишет, с чем поднялось.** Половина возможностей включается наличием
51
+ переменной окружения и без неё молча выключена. Иначе на вопрос «почему там не работает то,
52
+ что работает локально» отвечают чтением настроек прода по ssh.
53
+ - **В сводке старта стоят имена возможностей и их состояние, но не значения переменных.**
54
+ Состояния разложены тремя списками, а не картой «имя → состояние»: вычистка работает по
55
+ имени ключа, и карта приехала бы в лог вычищенной ровно там, где состояние и нужно.
56
+ - **Возможность, включаемая парой ключей, знает третье состояние.** Половины пары лежат по
57
+ разные стороны поставки, и заполнить можно ровно одну; тогда возможность не выключена и не
58
+ включена, а сломана — списком из двух состояний этот случай назвать нечем, и он читается как
59
+ включённый.
60
+ - **Порог уровня решает, появится ли строка лога в выводе, но не в хранилище отказов.** Порог
61
+ ставится ради объёма вывода, и при высоком пороге отобранные строки нижнего уровня пропали бы
62
+ из хранилища молча.
63
+ - **Отобранная строка лога сохраняется в хранилище отказом.** Отбирается уровень отказа и всё,
64
+ что выше, и строки уровнем ниже — по списку имён, объявленному в коде.
65
+ - **Логгер отдаёт отказ приёмнику, а хранилища не видит.** Интерфейс приёмника объявлен рядом с
66
+ логгером, исполняет его домен отказов, и ставится он извне при старте.
67
+ - **Строки лога клиента хранилища, домена отказов и домена оповещений в приёмник не идут.**
68
+ Отсекается источник, а не момент записи: медленная вставка в таблицу отказов сама порождает
69
+ строку лога клиента хранилища, и признаком «я внутри записи» её не поймать. Домен оповещений
70
+ отсечён по той же причине: сбой оповещения стал бы новым отказом, тот — новым оповещением.
71
+ - **Обращение к чужой службе идёт с явным пределом ожидания.** Без него молчащая — не
72
+ отказавшая — служба держит соединение до умолчания среды, а это минуты: отказа нет,
73
+ записывать нечего, и владельцу такое молчание неотличимо от исправной работы. Предел
74
+ называется числом рядом с клиентом, потому что цена ожидания у каждой службы своя.
75
+ - **Отправка наружу заводит свою строку до обращения, а исход дописывается в неё после.**
76
+ Строку заводит тот, кто видит хранилище, а не тот, кто отправляет: клиент внешней службы
77
+ хранилища не видит вовсе. Она же служит замком — второе обращение по той же записи не
78
+ уходит, — и по незакрытой строке видно разницу между «идёт прямо сейчас» и «упало посреди
79
+ отправки».
80
+ - **Запись отказа не задерживает ответ и не роняет запрос.** Между логгером и хранилищем стоит
81
+ очередь с пределом; переполненная очередь отбрасывает новый отказ и считает отброшенное.
82
+ - **Контекст запроса снимается в момент вызова логгера, а не в момент записи.** К моменту
83
+ записи хранилище исполнения уже отдано следующему запросу.
84
+ - **Записанное владелец читает своим разделом, закрытым отдельным правом.** Группы своего
85
+ владения, а под группой — её вхождения с причиной, полями строки лога и телами.
86
+ - **Хранилище не растёт без предела: раз в сутки лишнее и старое удаляются.** Удаляются
87
+ вхождения: сперва лишние за пределом, потом старые за сроком. Группа, у которой после этого
88
+ не осталось ни одного вхождения, уходит третьим шагом — и только если она старше часа: между
89
+ её заведением и первым вхождением проходит отдельный запрос.
90
+ - **Предел хранилища назван числом строк, а не байтами.** Удаление физический размер таблицы не
91
+ уменьшает, и условие «размер под пределом» не стало бы верным никогда.
92
+ - **О новом отказе владелец узнаёт сам — строкой журнала событий и письмом.** Обе дороги
93
+ открывает один порог: новая группа или группа, молчавшая дольше суток.
94
+ - **В письме нет ничего, что закрыто правом на экран отказов.** Оно уходит на адрес, который
95
+ правами не закрыт ничем, и подробности в нём обошли бы право почтой.
96
+ - **Частоту отказов владелец читает кривой над лентой: столбик — ведёрко выбранного периода.**
97
+ Счётчик группы говорит, сколько раз она случилась, но не когда: по нему не отличить поломку,
98
+ которая идёт потоком сейчас, от набравшейся за месяц.
99
+ - **Тревогу поднимает рост поломок, а не всех отказов.** Отклонённый вызов — сработавшая
100
+ проверка, и таких девять из десяти: на их фоне рост поломок не заметен вовсе.
101
+ - **Всплеск считается по последнему закрытому ведёрку, текущее правилу не отдаётся.** Оно ещё
102
+ набирается, и в начале каждого периода сравнение с ним показывало бы падение частоты.
103
+ - **Частоту разбирает такт расписания, а не запрос экрана.** Кривая считает тревогу при каждом
104
+ ответе, но экран владелец может и не открыть, а узнать о всплеске должен без этого.
105
+ - **О всплеске владелец узнаёт строкой журнала и письмом; повтор письма держит
106
+ предохранитель.** Одно происшествие занимает в ленте одну строку, а письмо о затяжной
107
+ поломке нужно и назавтра.
108
+
109
+ ## Чего из закона здесь нет
110
+
111
+ Отказ, случившийся до подъёма приложения, записать некуда: хранилища в этот момент ещё нет.
112
+ Такие остаются только в выводе контейнера. Туда же уходит отказ самого хранилища: его строки
113
+ лога отобраны из отказов целиком, иначе поломка хранилища порождает поток, который сам себя
114
+ разгоняет.
115
+
116
+ Список отобранных имён нижнего уровня не сверяется ни с чем. Новое место, которому надо в
117
+ хранилище, дописывает себя в него руками, а забытое молча остаётся только в выводе.
118
+
119
+ Правильность выбора уровня не проверяет ничто. Новое место само решает, какой уровень взять, и
120
+ ошибку видно только при чтении кода.
121
+
122
+ Полнота сводки старта не сверяется ничем. Имена возможностей перечислены руками, а переменную
123
+ окружения читают десятки файлов по всем доменам: новая возможность, забывшая дописать себя в
124
+ сводку, молча не попадёт ни в один из трёх списков — и на проде будет выглядеть не выключенной,
125
+ а несуществующей. Само чтение переменной окружения поэтому и требует этого правила вторым
126
+ слоем: гард видит обращение к окружению в тексте правки, но не то, дописали себя в сводку или
127
+ нет.
128
+
129
+ ## Паттерны
130
+
131
+ - `observability-record` — как завести новую строку лога.
132
+
133
+ ## Ловушки
134
+
135
+ - **Контекст запроса живёт в хранилище исполнения и в отложенную работу не переезжает.**
136
+ Отложенная запись прочитает контекст того обращения, которое заняло место исходного. Снимать
137
+ контекст надо в момент вызова логгера.
138
+ - **Логгер ставится на всё приложение, поэтому строки каркаса идут тем же путём, что и свои.**
139
+ Всё, что каркас пишет о себе, попадает в тот же вывод и в тот же отбор.
140
+ - **Отказ потока приходит после того, как ответ начался.** Управление возвращается, когда отдан
141
+ только заголовок. Без обёртки такой отказ не попадёт в логи вовсе.
142
+ - **Сериализация строки лога не должна бросать исключение.** Циклическая ссылка в полях иначе
143
+ уронит запрос, ради лога которого её и складывали.
144
+ - **Ключ, в который заворачивают чужое тело для вычистки, выбирается по правилам вычистки.**
145
+ Она работает по имени ключа, и голое значение мимо неё проходит вовсе, — но ключ, который сам
146
+ числится свободным текстом, увозит тело в хранилище отметкой о вычистке целиком. Новая
147
+ обёртка сверяется со списками ключей до того, как её так назвали.
@@ -36,6 +36,29 @@ description: Правило под «Закон о доступе». Брать
36
36
  Последний отдаёт вошедшему больше, чем гостю, — так владелец видит скрытые объекты в общем
37
37
  списке.
38
38
  - **Права пользователя — это права пресета, поверх которых применены его оверрайды.**
39
+ - **У человека одна роль во владении, и держит это хранилище.** Две принадлежности в одном
40
+ владении пришлось бы складывать, а результат сложения запретов и разрешений по двум строкам
41
+ не прочитать. Права разных владений не складываются вовсе: они записаны у принадлежности, а
42
+ не у учётной записи.
43
+ - **Право, о котором роль ничего не говорит, считается неданным.** Значение, которое не булево,
44
+ отбрасывается при сложении: отсутствующее право и прямо отобранное означают одно и то же, и
45
+ «непусто» правом не считается.
46
+ - **Права вошедшего читаются при каждом вызове, а не берутся из выданного входа.** Выданный
47
+ вход говорит только о том, кто пришёл: подписанное однажды живёт часами и правку прав не
48
+ переживает, поэтому отобранное право открывало бы раздел до конца дня.
49
+ - **Учётная запись, которой больше нет, вызовов, требующих входа, не открывает.** Тем же
50
+ чтением отбивается и человек, потерявший принадлежность в своём владении: работать ему не в
51
+ чем.
52
+ - **Счётчик и рекламные сигналы включает ответ гостя, а не наличие ключа в настройках.**
53
+ Решений два, и хранятся они парой: «разрешил счёт посещений, но не рекламу» — законное
54
+ состояние, а третьим значением перечисления его пришлось бы заводить заново на каждое новое
55
+ разрешение. Всё, что не пара булевых значений, читается как неотвеченный вопрос, то есть как
56
+ отказ: хранилище принимает что угодно, а решать по испорченной записи нельзя. Своя
57
+ статистика к согласию не привязана — она не уходит наружу.
58
+ - **Публичная процедура, заводящая запись, закрыта ещё и ограничителем частоты.** Право её не
59
+ сторожит, и без предела скорость роста таблицы задаёт отправитель, а не владелец. Считается
60
+ по ключу клиента, и ключ у всех таких процедур общий: второй ответ на вопрос «кто это»
61
+ разошёлся бы с первым. Публичная процедура, которая только читает, ограничителя не требует.
39
62
  - **Запрос без входа отбивается как неаутентифицированный, а вход без права — как отказ в
40
63
  доступе.** Это разные ответы: первый лечится входом, второй — нет.
41
64
  - **Право проверяется перехватчиком до тела процедуры.** Обработчик не решает, пускать ли
@@ -27,6 +27,10 @@ description: Правило под «Закон о единообразии пр
27
27
 
28
28
  ## Как закон применяется здесь
29
29
 
30
+ - **Источник вида выбирается по приложению, а не по привычке.** У каждого приложения дерева
31
+ своя опора: у публичного сайта — его дизайн-система, у остальных — кит. Перепутанный источник
32
+ приносит на экран форму, которой в этом приложении больше нигде нет. Какое приложение на что
33
+ опирается, названо в именах дерева.
30
34
  - **Работа начинается с чтения готового, а не с чистого файла.** Сначала находится опора —
31
35
  компонент кита, базовый класс, образец в соседнем домене, — потом пишется своё поверх неё.
32
36
  - **Свой примитив и своя основа заводятся только с явного одобрения владельца.** Спрашивается
@@ -76,6 +80,11 @@ description: Правило под «Закон о единообразии пр
76
80
 
77
81
  ## Ловушки
78
82
 
83
+ - **Образец ищется по именам кита, а не по тому, на чём кит написан.** Поиск по именам
84
+ библиотеки, поверх которой кит собран, не находит ни одного файла: наложение, портал и окно
85
+ закрыты китом и зовутся его именами. Обратное тоже бывает: библиотека стоит прямой
86
+ зависимостью, и то, чего кит не закрывает, зовётся в дереве её собственным именем. Прежде чем
87
+ решать, что имени в дереве нет, его ищут — переделок из-за этого выходит по две на приём.
79
88
  - Перенос переизобретением не считается: строку, которая уже лежит в файле, гард из
80
89
  проверяемого текста вычёркивает, а сверка идёт без отступов — при переезде блок меняет
81
90
  отступ, оставаясь тем же кодом.
@@ -2,7 +2,7 @@
2
2
  name: seo
3
3
  kind: rule
4
4
  law: search-visibility
5
- description: Правило под «Закон о видимости в поиске». Брать при любой правке, доходящей до разметки публичного сайта — шаблоны страниц, meta/title/description, JSON-LD, canonical, hreflang, маршруты сайта, sitemap, robots, nginx. Называет восемь локалей, где что лежит и чего у нас нет. Готовый код — в паттернах seo-page и seo-verify.
5
+ description: Правило под «Закон о видимости в поиске». Брать при любой правке, доходящей до разметки публичного сайта — шаблоны страниц, meta/title/description, JSON-LD, canonical, hreflang, маршруты сайта, sitemap, robots, nginx. Называет локали перевода, где что лежит и чего у нас нет. Готовый код — в паттернах seo-page и seo-verify.
6
6
  ---
7
7
 
8
8
  # Видимость в поиске — как это устроено здесь
@@ -12,14 +12,14 @@ description: Правило под «Закон о видимости в пои
12
12
 
13
13
  ## Как это называется здесь
14
14
 
15
- | В законе | Здесь |
16
- | ---------------------------- | ---------------------------------------------------------------------------- |
17
- | язык страницы | локаль; их восемь`en`, `ru`, `de`, `zh-Hans`, `zh-Hant`, `ko`, `th`, `hi` |
18
- | язык по умолчанию | `DEFAULT_LOCALE`, то есть `en`; отдаётся из корня, без префикса в адресе |
19
- | язык, перевод которого готов | `readyLocales` — поле объекта, а не список локалей сайта |
20
- | канонический адрес | `${SITE_ORIGIN}${propertyPath(slug, locale)}` |
21
- | структурированные данные | JSON-LD типа `LodgingBusiness` |
22
- | прежний адрес страницы | прежний slug объекта |
15
+ | В законе | Здесь |
16
+ | ---------------------------- | ------------------------------------------------------------------------ |
17
+ | язык страницы | локаль перевода; их наборв `implementation.md` рядом |
18
+ | язык по умолчанию | `DEFAULT_LOCALE`, то есть `en`; отдаётся из корня, без префикса в адресе |
19
+ | язык, перевод которого готов | `readyLocales` — поле объекта, а не список локалей сайта |
20
+ | канонический адрес | `${SITE_ORIGIN}${propertyPath(slug, locale)}` |
21
+ | структурированные данные | JSON-LD типа `LodgingBusiness` |
22
+ | прежний адрес страницы | прежний slug объекта |
23
23
 
24
24
  Локаль по умолчанию отдаётся из корня, остальные — с префиксом `/<код>/`. Префикс — часть
25
25
  маршрута, а не часть сборки: `<base href>` в разметке всегда `/`.
@@ -46,6 +46,54 @@ description: Правило под «Закон о видимости в пои
46
46
  - **Перенаправление с прежнего адреса отдаётся с `Cache-Control: max-age`** и покрыто
47
47
  `proxy_cache_valid 200 301`. Ключ кэша строится без `$args`, поэтому запросы со строкой
48
48
  запроса идут мимо кэша (`proxy_cache_bypass` / `proxy_no_cache $is_args`).
49
+ - **Страница отдаёт блоки JSON-LD каждый своим тегом**, а не одним графом: отказ одного не
50
+ уносит остальные, и в отданной разметке видно, какой из них собрался.
51
+ - **Цена уходит и предложением, и диапазоном** — суммой в валюте хранения и с единицей своего
52
+ периода. Цена по датам, скидки и пересчёт в валюту гостя в разметку не идут.
53
+ - **Сводная оценка появляется от первого отзыва** и считается из оценок самих отзывов. Ноль
54
+ отзывов — поля нет вовсе: ни нуля, ни пустой строки.
55
+ - **Блок вопросов собирается из вопросов записи**, а ноль вопросов блока не даёт.
56
+ - **Крошки ведут от корня локали к странице записи** адресами той же локали, что и открытая
57
+ страница. Без названия сайта крошки не выпускаются вовсе.
58
+ - **Сохранение записи уведомляет поисковика об изменении её адресов.** В уведомление уходят
59
+ страницы записи на готовых локалях по нынешнему и прежним адресам; корни локалей и карта
60
+ сайта — нет. Отказ приёмника сохранение не отменяет и возвращается владельцу исходом.
61
+ - **Приёмников несколько, и каждый получает свой запрос.** Опрашиваются они одновременно, а
62
+ потолок ожидания считается на приёмник: последовательный обход сложил бы все потолки в одно
63
+ ожидание владельца. Переменная окружения перекрывает адреса всех приёмников разом — этим
64
+ отправку и проверяют локальным приёмником.
65
+ - **Исход возвращается перечислением по приёмнику, а владельцу показывается списком.** Приёмник
66
+ в нём назван словом, а не адресом. Строка отвечает за приём запроса, а не за осведомлённость
67
+ поисковика: раздаёт полученное любой приёмник, поэтому отказ одного адреса не значит, что его
68
+ поисковик не узнал. Подписью объявляется только полный отказ.
69
+ - **Удаление записи уведомляет поисковика теми же адресами.** Отдельного «страница исчезла»
70
+ протокол не знает: площадка приходит по адресу и видит ответ сайта сама. Адреса собираются до
71
+ удаления строки — каскад унесёт прежние вместе с записью, — а исход наружу не выходит:
72
+ страницы уже нет, и читать подпись некому. Снятие с публикации идёт через сохранение и
73
+ уведомляет тем же путём.
74
+ - **Переименование адреса и снятие прежнего адреса тоже уведомляют поисковика.**
75
+ Переименование — всеми адресами записи, снятие — одним снятым; готовые локали у снятого
76
+ считаются по текстам записи, которой он принадлежал, и ради них выборка снятия отдаёт не
77
+ только адрес. Исход наружу не выходит ни там, ни там: владелец на странице адресов
78
+ спрашивает про адрес, а не про уведомление.
79
+ - **Правка настроек владельца уведомляет поисковика адресами всех действующих записей.**
80
+ Видимой гостю считается та же правка, которой сбрасывается кэш, — а правка невидимого поля
81
+ уведомления не шлёт. Уходят живые адреса, без прежних, и готовые локали считаются по каждой
82
+ записи отдельно; отправка идёт на владеющую сущность — записи разных сущностей в одну не
83
+ попадают. Работа идёт в фоне за сохранением настроек, поэтому исход остаётся в логах:
84
+ показать его владельцу негде.
85
+ - **Каждая отправка оставляет запись, и заводится она до обращения к приёмникам.** Повод,
86
+ записи, объявленные адреса и исход по каждому приёмнику ложатся рядом; пустой исход означает
87
+ «отправка идёт», а спустя несколько минут — что процесс упал посреди опроса. Пишут её все
88
+ места, откуда уведомление уходит. Отказ записи отправку не отменяет.
89
+ - **Непрошедшие запросы владелец повторяет нажатием, и повтор заводит новую запись.**
90
+ Повторяется последняя отправка записи — у ранней адреса могли устареть, — и запрос уходит
91
+ только тем приёмникам, чей исход не «принял»; у оборванной записи это все её приёмники. Повод
92
+ у новой записи свой, а право строже, чем у чтения: повтор объявляет адреса поисковику.
93
+ Автоповтора по расписанию нет.
94
+ - **Записи отправок держит ночная чистка: сперва предел, потом срок.** Тот же порядок, что у
95
+ ленты происшествий: срок, снятый первым, оставил бы предел меряться по уже почищенной
96
+ таблице.
49
97
 
50
98
  ## Чего из закона здесь нет
51
99
 
@@ -39,6 +39,12 @@ description: Правило под «Закон об общем коде при
39
39
  - **Общим стал только маппер страницы.** У сторон разная политика на непонятное значение, и
40
40
  общим может быть лишь то, где она одна: номер меньше единицы обе стороны читают как первую
41
41
  страницу.
42
+ - **Общая выборка держит форму запроса, а не набор условий.** Имена полей отбора объявляет сам
43
+ домен списком разрешённых, а в запрос к хранилищу их переводит его же выборка. Поэтому
44
+ условие вправе лечь на связанные строки, а не только на колонки самой записи, и отбор по
45
+ набору идентификаторов ей не запрещён: признак связанной записи — такое же поле набора, как
46
+ повод и объект. Общими здесь остаются разбор страницы, порядка и поиска, а не сам список
47
+ полей.
42
48
  - **Перечисления полей порядка и отбора домена копией не считаются.** `EActivitySortProperty`
43
49
  и подобные повторяют имена, по которым сортирует сервер именно этого домена.
44
50
  - **Строковая настройка и таблица соответствий сверяются по значению, а не по имени.** Имя
@@ -109,6 +109,15 @@ description: Правило под «Закон о документации пр
109
109
  про дерево, где того гарда не разложили; поправить это дерево не может ничем, если у ресурса
110
110
  нет надстройки. Требование ресурса к ресурсу при этом объявляется строкой в шапке, а не
111
111
  выводится из такой фразы.
112
+ - **Паттерн находится по полю `rule:`, а не по приставке имени.** Приставку имени несут не все
113
+ паттерны, и поиск по имени правила таких не видит: сверка ищет их полем, человек — разделом
114
+ «Паттерны» самого правила. Счёт паттернов, собранный приставками, выходит меньше настоящего, а
115
+ число потом уезжает в деление работы.
116
+ - **Якорь сверяется по сырому тексту файла, и комментарий засчитывается наравне с кодом.**
117
+ Существование символа проверка ищет словом по всему файлу, не вычищая комментарии, а живость
118
+ считает только у объявленного в коде. Имя, стоящее в одном лишь пояснении, проходит мимо обеих
119
+ сторон: якорем утверждения оказывается слово из комментария, тогда как объявление рядом
120
+ называется иначе.
112
121
 
113
122
  ## Чего из закона здесь нет
114
123
 
@@ -16,7 +16,7 @@ description: Правило под «Закон о фронтовом прило
16
16
 
17
17
  | В законе | Здесь |
18
18
  | ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
19
- | общий набор значений оформления | шкалы кита `--rt-*`; своё поверх них — `--vm-*` в `styles.scss` приложения |
19
+ | общий набор значений оформления | шкалы кита `--rt-*`; своё поверх них — `--<префикс>-*` в `styles.scss` приложения |
20
20
  | класс в разметке | директивы `rtBlock` и `rtElem` из `@rt-tools`, а не строка в атрибуте |
21
21
  | правило стилей | объявление `&__<элемент>` в `.scss` — своём или в общем слое приложения |
22
22
  | общий слой раскладки | `apps/<app>/src/styles/`: `<префикс>-page`, `<префикс>-form`, `<префикс>-panel`, `<префикс>-window` у админки, `<префикс>-site-page` у сайта |
@@ -38,6 +38,19 @@ description: Правило под «Закон о фронтовом прило
38
38
  инъекцией от ближайшего предка с `rtBlock`, и повторить этот разбор по тексту шаблона нечем.
39
39
  - **Раскладка объявлена в общем слое приложения, а не в стилях экрана.** У компонента экрана
40
40
  вне кита файл стилей по умолчанию пустой.
41
+ - **Шторку и окно открывает служба кита, а не номер слоя.** Числа шкалы сравниваются только
42
+ между соседями по разметке; служба выносит разметку наружу, к `<body>`, и сравнивать её
43
+ становится не с чем.
44
+ - **Номер слоя берётся из шкалы, а не пишется числом в файле компонента.** Шкала — единственное
45
+ место, где слои видно рядом: написанное на месте число в неё не попадает, и следующий узел
46
+ занимает тот же номер, ничего об этом не узнав.
47
+ - **Размер элемента управления выбирается по признаку указателя, а не по ширине экрана.**
48
+ Планшет в ландшафте шире порога узкого вьюпорта, а нажимают по нему пальцем: `pointer: coarse`
49
+ отвечает про способ нажатия, ширина — про место под раскладку. Ступени берутся у кита, а не
50
+ назначаются пикселями.
51
+ - **Файл стилей не длиннее 500 строк.** Предел общий с кодом и текстами, но stylelint длину не
52
+ судит вовсе — держит его проверка дерева. Выросший файл экрана делится по блокам, а общая
53
+ раскладка уходит в свой слой.
41
54
  - **Предупреждение stylelint роняет прогон наравне с ошибкой.** `!important` объявлен
42
55
  предупреждением, а прогон идёт с `--max-warnings 0`: иначе запрет читается как пожелание —
43
56
  два таких предупреждения лежали в дереве, а `npm run stylelint` возвращал ноль и гейтом не был.
@@ -52,10 +65,30 @@ description: Правило под «Закон о фронтовом прило
52
65
 
53
66
  - `styling-bem-layout` — экран на общем слое раскладки, блоки приложения.
54
67
  - `styling-bem-component` — стили компонента кита, `:host`, модификаторы, язык оформления сайта.
68
+ - `styling-bem-sheet` — шторка и окно поверх страницы: чем открываются, подложка, замер.
55
69
 
56
70
  ## Ловушки
57
71
 
58
72
  - **`rtElem` без предка с `rtBlock` роняет отрисовку в рантайме** — сборка и линт молчат.
73
+ - **Спроецированный узел блока-предка не имеет.** `rtElem` берёт имя блока инъекцией от
74
+ ближайшего предка **по месту объявления шаблона**, а не по месту вставки: элемент, который
75
+ экран объявляет у себя и отдаёт в проекцию чужого компонента, ищет `rtBlock` в своём шаблоне
76
+ и не находит. Отрисовка падает в рантайме, сборка и линт зелёные. Класс на такой узел
77
+ вешается правилом по селектору кита в общем слое раскладки, а не директивой.
78
+ - **Элемент с `backdrop-filter` или своим `z-index` замыкает потомков в свой слой.** Липкая
79
+ шапка с размытием — самый частый случай: номер слоя у того, что лежит внутри неё,
80
+ сравнивается не с соседями по странице, а только с соседями внутри шапки, и нижняя панель
81
+ накрывает открытую шторку вместе с её кнопкой. Проверяется это `elementFromPoint` в центре
82
+ кнопки: сборка, линт и скриншот показывают тут целую страницу.
83
+ - **До узла, вынесенного к `<body>`, стили компонента не достают.** Превью и заглушку переноса
84
+ кладёт туда библиотека, а правила компонента заскоуплены атрибутом: файл выглядит рабочим и
85
+ не красит ничего. Такие правила объявляются в общем слое приложения. Ни сборка, ни линт, ни
86
+ проверка «класс без правила» этого не видят: класса такого в шаблоне нет вовсе, и пролежать
87
+ это может несколько задач подряд.
88
+ - **Имя токена не сверяется ничем.** Ссылка на несуществующий токен собирается, проходит
89
+ stylelint и проверку класса без правила, а свойство молча берёт наследованное значение:
90
+ правило выглядит написанным и не красит ничего. Ловится это только замером в браузере, а
91
+ имена берутся из объявлений кита, а не по догадке о том, как токен должен был бы называться.
59
92
  - **`rtBlock` на `<ng-container>` класса не ставит вовсе:** узел это комментарий, и имя блока
60
93
  он только объявляет потомкам. Класс блока экрана вешает хост через `host: { class: … }`.
61
94
  - **`justify-content: center` во flex-контейнере с `overflow-x` уводит первые элементы за
@@ -73,6 +73,11 @@ description: Правило под «Закон о ведении работы»
73
73
  — признак; правила, тексты, обвязка и зависимости под него не подпадают. Обход — строка
74
74
  `**Поведение:** не меняется — <причина владельца>` в замысле; пустая причина не
75
75
  принимается.
76
+ - **Гард замысла — нижняя граница, а не признак папки задачи.** Он требует её только под правку
77
+ кода приложения; нужна ли папка работе, которая туда не доходит, решает число заходов, а не
78
+ путь. Работа в один заход и один коммит целиком помещается в тело отчёта — так закрывается
79
+ разбор чужой папки. Работа с этапами и передачей папку заводит: между заходами её состояние
80
+ не держит ничто, кроме хода работы.
76
81
  - **Ход, в котором владельцу задан вопрос, не заканчивается, пока за этот же ход не читались
77
82
  законы и правила.** Чтением считается любой из трёх путей: загрузка правила, чтение файла
78
83
  законов или правил, поиск по ним. Отбивает гард разговора — на завершении хода, а не на