@rt-tools/agent-kit 0.3.0 → 0.4.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 (201) hide show
  1. package/README.md +194 -30
  2. package/assets/agents/business-analyst.md +74 -0
  3. package/assets/agents/project-manager.md +70 -0
  4. package/assets/agents/qa-engineer.md +72 -0
  5. package/assets/agents/skill-curator.md +110 -0
  6. package/assets/agents/spec-critic.md +44 -0
  7. package/assets/agents/spec-writer.md +50 -0
  8. package/assets/checks/board.github.mjs +286 -0
  9. package/assets/checks/check-board.github.mjs +188 -0
  10. package/assets/checks/check-doc-paths.mjs +163 -0
  11. package/assets/checks/check-dupes.mjs +277 -0
  12. package/assets/checks/check-lib-layers.mjs +573 -0
  13. package/assets/checks/check-reuse.mjs +208 -0
  14. package/assets/checks/check-schema-drift.mjs +186 -0
  15. package/assets/checks/check-specs.mjs +1007 -0
  16. package/assets/checks/check-styles.mjs +109 -0
  17. package/assets/checks/rt-kit-checks.config.mjs +134 -0
  18. package/assets/checks/task-new.github.mjs +198 -0
  19. package/assets/commands/skill-curator.md +70 -0
  20. package/assets/defaults/gate-map.sh +100 -0
  21. package/assets/defaults/project.sh +179 -0
  22. package/assets/hooks/browser-device-id.sh +0 -0
  23. package/assets/hooks/browser-guard-device-id.sh +2 -1
  24. package/assets/hooks/browser-guard-no-asking.sh +27 -0
  25. package/assets/hooks/browser-guard-no-listing.sh +2 -1
  26. package/assets/hooks/browser-guard-no-other-drivers.sh +2 -1
  27. package/assets/hooks/browser-guard-require-select.sh +2 -1
  28. package/assets/hooks/commit-msg.sh +1 -1
  29. package/assets/hooks/constitution-index.sh +5 -4
  30. package/assets/hooks/dev-server-guard.sh +8 -6
  31. package/assets/hooks/docs-guard.sh +223 -37
  32. package/assets/hooks/git-guard-delivery.sh +86 -29
  33. package/assets/hooks/git-guard-main.sh +1 -0
  34. package/assets/hooks/git-guard-push-tests.sh +34 -13
  35. package/assets/hooks/glossary-load.sh +23 -0
  36. package/assets/hooks/lint-after-edit.sh +155 -30
  37. package/assets/hooks/qa-dataid-guard.sh +72 -32
  38. package/assets/hooks/reuse-first-guard.sh +105 -34
  39. package/assets/hooks/skill-gate-rearm.sh +1 -0
  40. package/assets/hooks/skill-gate.sh +75 -15
  41. package/assets/hooks/skill-loaded.sh +1 -0
  42. package/assets/hooks/sql-guard.sh +606 -56
  43. package/assets/hooks/task-context-load.sh +100 -0
  44. package/assets/hooks/task-flow-guard.sh +107 -0
  45. package/assets/laws/{access.md → application/access.md} +1 -4
  46. package/assets/laws/{locales.md → application/locales.md} +1 -3
  47. package/assets/laws/application/money.md +41 -0
  48. package/assets/laws/application/ownership.md +32 -0
  49. package/assets/laws/{search-visibility.md → application/search-visibility.md} +1 -1
  50. package/assets/laws/code-structure.md +7 -6
  51. package/assets/laws/delivery.md +53 -3
  52. package/assets/laws/entity-editing.md +49 -55
  53. package/assets/laws/entity-models.md +4 -14
  54. package/assets/laws/frontend-application.md +5 -5
  55. package/assets/laws/lib-imports.md +14 -1
  56. package/assets/laws/lists.md +33 -0
  57. package/assets/laws/navigation.md +40 -0
  58. package/assets/laws/project-documentation.md +17 -8
  59. package/assets/laws/reuse-first.md +26 -21
  60. package/assets/laws/shared-code.md +13 -1
  61. package/assets/laws/verifiability.md +17 -1
  62. package/assets/laws/work-conduct.md +48 -0
  63. package/assets/patterns/admin-lists-screen.md +131 -0
  64. package/assets/patterns/admin-nav-item.md +71 -0
  65. package/assets/patterns/angular-patterns-state.md +29 -22
  66. package/assets/patterns/api-layer-pair.md +40 -30
  67. package/assets/patterns/browser-verification-measure.md +41 -38
  68. package/assets/patterns/browser-verification-stand.md +106 -42
  69. package/assets/patterns/component-structure-new.md +33 -32
  70. package/assets/patterns/dependencies-upgrade.md +65 -0
  71. package/assets/patterns/doc-style-sweep.md +65 -28
  72. package/assets/patterns/doc-style-write.md +36 -33
  73. package/assets/patterns/entity-aside.md +136 -0
  74. package/assets/patterns/entity-models-new.md +124 -0
  75. package/assets/patterns/entity-store.md +91 -0
  76. package/assets/patterns/git-workflow-commit.azure.md +259 -0
  77. package/assets/patterns/git-workflow-commit.github.md +333 -0
  78. package/assets/patterns/git-workflow-commit.gitlab.md +283 -0
  79. package/assets/patterns/git-workflow-merge.md +42 -25
  80. package/assets/patterns/git-workflow-migration.md +61 -31
  81. package/assets/patterns/git-workflow-restart.md +20 -20
  82. package/assets/patterns/lib-layers-move.md +50 -32
  83. package/assets/patterns/lib-layers-new.md +41 -29
  84. package/assets/patterns/ownership-scope-resolve.md +69 -0
  85. package/assets/patterns/permissions-procedure.md +35 -33
  86. package/assets/patterns/platform-access-di.md +39 -25
  87. package/assets/patterns/pricing-quote.md +71 -0
  88. package/assets/patterns/reuse-first-extend.md +22 -22
  89. package/assets/patterns/seo-page.md +52 -40
  90. package/assets/patterns/seo-verify.md +48 -29
  91. package/assets/patterns/shared-code-new.md +37 -31
  92. package/assets/patterns/spec-driven-domain.md +44 -37
  93. package/assets/patterns/spec-driven-rule.md +55 -40
  94. package/assets/patterns/styling-bem-component.md +43 -32
  95. package/assets/patterns/styling-bem-layout.md +30 -24
  96. package/assets/patterns/task-flow-close.md +90 -0
  97. package/assets/patterns/task-flow-resume.md +94 -0
  98. package/assets/patterns/task-flow-start.md +117 -0
  99. package/assets/patterns/testing-e2e.md +53 -51
  100. package/assets/patterns/testing-unit.md +70 -46
  101. package/assets/patterns/translations-key.md +32 -19
  102. package/assets/patterns/ts-procedure.md +24 -25
  103. package/assets/rules/angular-patterns.md +46 -27
  104. package/assets/rules/api-layer.md +46 -28
  105. package/assets/rules/browser-verification.md +66 -48
  106. package/assets/rules/component-structure.md +43 -27
  107. package/assets/rules/dependencies.md +66 -0
  108. package/assets/rules/doc-style.md +81 -39
  109. package/assets/rules/entity-conventions.md +78 -0
  110. package/assets/rules/entity-models.md +70 -0
  111. package/assets/rules/git-workflow.azure.md +116 -0
  112. package/assets/rules/git-workflow.github.md +123 -0
  113. package/assets/rules/git-workflow.gitlab.md +113 -0
  114. package/assets/rules/lib-layers.md +56 -30
  115. package/assets/rules/lists.md +73 -0
  116. package/assets/rules/navigation.md +78 -0
  117. package/assets/rules/ownership-scope.md +63 -0
  118. package/assets/rules/permissions.md +43 -25
  119. package/assets/rules/platform-access.md +57 -29
  120. package/assets/rules/pricing.md +64 -0
  121. package/assets/rules/reuse-first.md +57 -43
  122. package/assets/rules/seo.md +51 -30
  123. package/assets/rules/shared-code.md +51 -26
  124. package/assets/rules/spec-driven.md +96 -50
  125. package/assets/rules/styling-bem.md +54 -39
  126. package/assets/rules/task-flow.md +110 -0
  127. package/assets/rules/testing.md +78 -47
  128. package/assets/rules/translations.md +48 -31
  129. package/assets/rules/typescript-conventions.md +57 -27
  130. package/assets/skills/agent-kit.md +81 -0
  131. package/assets/skills/write-a-skill.md +108 -0
  132. package/assets/templates/gate-map.sh +23 -15
  133. package/assets/templates/implementation.md +14 -8
  134. package/assets/templates/pattern.md +1 -1
  135. package/assets/templates/project.sh +32 -19
  136. package/assets/templates/rule.md +1 -1
  137. package/assets/variants.json +20 -0
  138. package/assets/workflows/feature.js +134 -0
  139. package/assets/workflows/plan.js +150 -0
  140. package/bin/agent-kit.d.ts.map +1 -1
  141. package/bin/agent-kit.js +78 -5
  142. package/bin/agent-kit.js.map +1 -1
  143. package/bin/prompt.d.ts +5 -0
  144. package/bin/prompt.d.ts.map +1 -1
  145. package/bin/prompt.js +19 -7
  146. package/bin/prompt.js.map +1 -1
  147. package/index.d.ts +1 -0
  148. package/index.d.ts.map +1 -1
  149. package/index.js +1 -0
  150. package/index.js.map +1 -1
  151. package/lib/assets.d.ts +8 -3
  152. package/lib/assets.d.ts.map +1 -1
  153. package/lib/assets.js +13 -3
  154. package/lib/assets.js.map +1 -1
  155. package/lib/catalog.d.ts +52 -5
  156. package/lib/catalog.d.ts.map +1 -1
  157. package/lib/catalog.js +104 -16
  158. package/lib/catalog.js.map +1 -1
  159. package/lib/commands.d.ts +22 -1
  160. package/lib/commands.d.ts.map +1 -1
  161. package/lib/commands.js +202 -14
  162. package/lib/commands.js.map +1 -1
  163. package/lib/companion.d.ts +5 -1
  164. package/lib/companion.d.ts.map +1 -1
  165. package/lib/companion.js +29 -2
  166. package/lib/companion.js.map +1 -1
  167. package/lib/config.d.ts +26 -9
  168. package/lib/config.d.ts.map +1 -1
  169. package/lib/config.js +41 -15
  170. package/lib/config.js.map +1 -1
  171. package/lib/freshness.d.ts +14 -0
  172. package/lib/freshness.d.ts.map +1 -0
  173. package/lib/freshness.js +116 -0
  174. package/lib/freshness.js.map +1 -0
  175. package/lib/hooks-map.d.ts +24 -0
  176. package/lib/hooks-map.d.ts.map +1 -0
  177. package/lib/hooks-map.js +72 -0
  178. package/lib/hooks-map.js.map +1 -0
  179. package/lib/integrity.d.ts +36 -0
  180. package/lib/integrity.d.ts.map +1 -0
  181. package/lib/integrity.js +44 -0
  182. package/lib/integrity.js.map +1 -0
  183. package/lib/picker.d.ts +11 -1
  184. package/lib/picker.d.ts.map +1 -1
  185. package/lib/picker.js +44 -6
  186. package/lib/picker.js.map +1 -1
  187. package/lib/sync.d.ts +26 -0
  188. package/lib/sync.d.ts.map +1 -1
  189. package/lib/sync.js +59 -4
  190. package/lib/sync.js.map +1 -1
  191. package/lib/variants.d.ts +44 -0
  192. package/lib/variants.d.ts.map +1 -0
  193. package/lib/variants.js +82 -0
  194. package/lib/variants.js.map +1 -0
  195. package/package.json +1 -1
  196. package/rt-tools-agent-kit-0.4.0.tgz +0 -0
  197. package/assets/laws/admin-lists.md +0 -35
  198. package/assets/laws/admin-navigation.md +0 -38
  199. package/assets/patterns/git-workflow-commit.md +0 -175
  200. package/assets/rules/git-workflow.md +0 -106
  201. package/rt-tools-agent-kit-0.3.0.tgz +0 -0
@@ -0,0 +1,113 @@
1
+ ---
2
+ name: git-workflow
3
+ kind: rule
4
+ law: delivery
5
+ description: Правило под «Закон о поставке» для дерева на GitLab. Брать на заведение задачи, ветки, коммит, пуш, создание MR, слияние, а также на правку схемы хранилища, её миграций и вызовы миграций. Называет задачу на борде как начало работы, колонку задачи как ход работы, соответствие задачи и ветки один к одному, имя ветки, формат коммита, учётную запись машинной работы, обязательный состав MR, гарды поставки и сверку очереди работ. Готовый код — в паттернах git-workflow-commit, git-workflow-merge, git-workflow-migration и git-workflow-restart.
6
+ ---
7
+
8
+ # Поставка — как это устроено здесь
9
+
10
+ Правило под закон `docs/constitution/delivery.md`. Закон говорит, что должно быть верно;
11
+ здесь — каким приёмом это держится в дереве, лежащем на GitLab. Ключ задач, адрес борды,
12
+ учётная запись машинной работы и области коммита — при этом дереве, в `implementation.md`
13
+ рядом: их не угадать, и общими они не бывают.
14
+
15
+ ## Как это называется здесь
16
+
17
+ | В законе | Здесь |
18
+ | -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
19
+ | главная ветка | `main` |
20
+ | отдельная ветка | `<КЛЮЧ>-<номер задачи>-<короткий-slug>`; ключ задач — в `implementation.md`. Имя без номера (`feat/…`, `fix/…`) законно, пока ветка живёт локально: MR с неё не откроется |
21
+ | задача | issue проекта, заголовок `[<КЛЮЧ>-<номер>] <Что не так>`, исполнитель — учётная запись машинной работы; MR прикрепляется к нему строкой `Closes #<номер>` в описании |
22
+ | очередь работ | доска задач проекта — Issue Board. Задача попадает на неё меткой списка, а не самим фактом заведения: доска показывает те issue, чью метку знает |
23
+ | состояние задачи в очереди работ | список доски, за которым стоит метка: заведённая, взятая в работу, ждущая разбора. Имена меток — в `implementation.md`; закрытая задача уходит из очереди слиянием, а не переносом в последний список |
24
+ | отчёт о задаче | заголовок MR `[<КЛЮЧ>-<номер>] <Что сделано>` — тот же номер, что у задачи, и её название, переведённое в сделанное; тип и область коммита сюда не идут |
25
+ | обсуждение правки | разбор MR: ревьювер — владелец проекта, исполнитель — учётная запись машинной работы, метки — те же, что у задачи |
26
+ | попадание правки в главную ветку | слияние MR; оно же запускает выкатку — `.gitlab-ci.yml` |
27
+ | образ того коммита | `IMAGE_TAG=<sha>` в командах `docker compose` на сервере |
28
+ | изменение хранилища | миграция в `prisma/migrations/<метка>_<имя>/` |
29
+ | запись о правке | коммит формата `type(scope): description` — типы `feat`, `fix`, `refactor`, `docs`, `style`, `test`, `chore`, `perf`; области — в `implementation.md` |
30
+ | автор машинной работы | отдельная учётная запись; её имя и место токена — в `implementation.md`. Токен лежит вне репозитория |
31
+
32
+ ## Где это лежит
33
+
34
+ В этом дереве — таблица в `implementation.md` рядом. Пути живут там, а не здесь: правило
35
+ переносится между репозиториями, раскладка — нет, и путь, названный в правиле, врёт в первом
36
+ же дереве, которое держит код иначе.
37
+
38
+ ## Как закон применяется здесь
39
+
40
+ - **Коммит в главную ветку отбивается гардом.** Гард ищет вызов коммита в любом месте команды
41
+ и смотрит текущую ветку на момент запуска, поэтому составная «создать ветку и сразу
42
+ коммитить» отклоняется целиком: ветки в момент разбора ещё нет.
43
+ - **Ветка без номера задачи MR не открывает.** Гард поставки отбивает `glab mr create` с такой
44
+ ветки: локально она законна, но правка из неё — это выкатка, за которой в очереди работ
45
+ ничего не стоит. Заводится задача, и работа переносится в ветку с её номером.
46
+ - **Номер ветки и номер в заголовке MR сверяются на месте, а состояние задачи — по доске.**
47
+ Формат читается из текста команды и работает без сети; существование задачи, её метка
48
+ списка, исполнитель и то, что она ещё открыта, — только когда есть чем спросить. Нет сети
49
+ или нет токена — второй ярус молча пропускается: проверка, падающая в самолёте, перестаёт
50
+ что-либо значить.
51
+ - **Список задачи двигается тем же движением, что и работа.** Ветка заведена — задача
52
+ переставляется во взятые в работу, MR открыт — в ждущие разбора; делает это команда
53
+ перевода, а не набор вызовов по памяти. Перевод идёт сразу за шагом, который его вызвал:
54
+ очередь работ читают между шагами, а не после них.
55
+ - **Отставший список находится сверкой очереди, а не глазами.** Сверка судит список по отчёту
56
+ в обе стороны: открытый MR при задаче не в разборе и разбор без открытого MR — оба
57
+ расхождения. Момента, когда задачу берут в работу, ей не видно: ветки на доске нет.
58
+ - **Задачи, чинящиеся одной правкой, сливаются до слияния ветки.** Вторая стирается вместе с
59
+ номером, а недостающее из неё дописывается в первую. После слияния слить уже нельзя: ветка
60
+ въехала, и откатывается она целиком.
61
+ - **Слияние в главную ветку выкатывает прод.** Правила `only`/`rules` конвейера покрывают
62
+ документы отдельно, поэтому переменные окружения, секреты и записи имён ставятся до слияния,
63
+ а не после.
64
+ - **Признак режима объявлен в образе, а не только в составе прода.** Значение, заданное
65
+ составом, действует лишь на контейнер, поднятый этим составом; ручной прогон того же образа
66
+ идёт с пустым значением, а пусто здесь означает локалхост — со всеми отладочными
67
+ умолчаниями, которые он разрешает. Умолчание образа задаётся в самом образе.
68
+ - **Образы выкатываются по sha коммита, а не по метке «последний».** Метка в реестре отстаёт
69
+ от главной ветки, и прод молча возвращается к прежней версии, продолжая отвечать.
70
+ - **Цепочка миграций прогоняется с пустого хранилища до слияния.** Порядок применения
71
+ лексикографический по имени каталога, а метку времени ставит момент создания: миграция из
72
+ ветки, начатой раньше, встаёт перед той, от которой зависит.
73
+ - **Документ едет в том же коммите, что и правка.** Обход — строка `Docs-skip: <причина>` в
74
+ теле коммита; пустая причина не принимается.
75
+ - **Заголовок коммита сверяется с форматом на месте.** Разобранный по типу и области
76
+ заголовок читается списком, а свободный текст — только целиком.
77
+ - **Перед пушем прогоняются все линтеры, а не один.** Линтер кода обычно не читает файлы
78
+ стилей вовсе, и правила оформления без второго прогона не проверяет ничто.
79
+ - **Слияние по кнопке «Merge when pipeline succeeds» не заменяет проверок до пуша.** Конвейер
80
+ видит только то, что уже отправлено, а отправленная красная ветка занимает очередь работ и
81
+ выглядит готовой к разбору.
82
+ - **Метки и исполнитель MR ставятся при создании, а не правкой после.** `glab mr create`
83
+ принимает их флагами; правка открытого MR второй командой обходится молча, когда токен
84
+ учётной записи машинной работы не видит проект целиком.
85
+ - **Задача переводится по списку правкой её меток.** Списки доски — это метки: перевод, не
86
+ снявший прежнюю метку, оставляет задачу в двух списках сразу, и очередь читается неверно.
87
+ - **Сценарии гардов задают настройки git сами, а не берут их с машины.** Коммит во временном
88
+ репозитории сценария наследует общий конфиг: если включена подпись, git идёт в агент ключей,
89
+ а заблокированный агент роняет весь набор — со стороны это выглядит сломанным гардом. Автор,
90
+ почта и подпись передаются флагами `-c` прямо в команду.
91
+ - **Расхождение миграций со схемой меряется на теневом хранилище, а не на том, где работает
92
+ тот, кто пушит.** Оно законно несёт след любой недоделанной ветки, и сверка с ним держала бы
93
+ чужую правку. Гейт и выкатка зовут одну и ту же проверку — иначе «сошлось» станет значить в
94
+ двух местах разное.
95
+
96
+ ## Чего из закона здесь нет
97
+
98
+ Гард поставки стоит на командах агента, поэтому ветку, заведённую руками в редакторе, он не
99
+ видит: имя такой ветки держится памятью. Требование от этого не слабеет — просто отдельной
100
+ проверки под него не заводится: работа опознаётся заголовком задачи и отчёта, а это сверяется
101
+ у всех. Сверка очереди имя ветки не судит вовсе: у открытого MR его не переименовать.
102
+
103
+ Взятие задачи в работу не стережёт ничто: доска ветки не видит, а гард поставки её видит, но
104
+ доску не правит — сетевой вызов в разборе команды падал бы вместе со связью и отбивал бы
105
+ работу вместо промаха. Держится это памятью и подсказкой, которую печатает команда заведения
106
+ задачи. Отставший список находит сверка очереди — но уже после того, как MR открыт.
107
+
108
+ ## Паттерны
109
+
110
+ - `git-workflow-commit` — задача, ветка, коммит, пуш и MR от учётной записи машинной работы.
111
+ - `git-workflow-merge` — главная ветка влита в ветку задачи, конфликт разобран.
112
+ - `git-workflow-migration` — правка схемы хранилища и её миграций.
113
+ - `git-workflow-restart` — ручной перезапуск прода.
@@ -2,37 +2,64 @@
2
2
  name: lib-layers
3
3
  kind: rule
4
4
  law: lib-imports
5
- description: Правило под закон «Импорты между либами». Брать при правке манифестов проектов, алиасов, конфигов границ, любого бареля и проверок раскладки, а также когда решается, где живёт общий символ. Готовый порядок действий — в паттернах lib-layers-new и lib-layers-move. Чем это названо здесь — в implementation.md рядом.
5
+ description: Правило под «Закон об импортах между либами». Брать при правке project.json, tsconfig.base.json, eslint/boundaries/**, любого src/index.ts и проверок раскладки, а также когда решается, где живёт общий символ. Называет семьи, слои, теги и границы этого дерева. Готовый порядок действий — в паттернах lib-layers-new и lib-layers-move.
6
6
  ---
7
7
 
8
- # Импорты между либами — каким приёмом
8
+ # Импорты между либами — как это устроено здесь
9
9
 
10
- Правило под закон `{{lawsDir}}/lib-imports.md`. Закон говорит, кто кого видит; здесь — каким
11
- приёмом это держится. Как нарезаны семьи и слои в этом дереве, чем названы теги и где лежат
12
- конфиги границ — `implementation.md` рядом.
10
+ Правило под закон `docs/constitution/lib-imports.md`. Закон говорит, кто кого видит; здесь —
11
+ как это нарезано в этом дереве, чем названо и чего у нас нет.
13
12
 
14
- ## Когда берётся
13
+ ## Как это называется здесь
15
14
 
16
- Правка манифеста проекта, алиасов, конфига границ, любого бареля, проверок раскладки. И раньше
17
- всего этого решение, в какой либе живёт общий символ.
15
+ | В законе | Здесь |
16
+ | -------------------------------- | ------------------------------------------------------ |
17
+ | семья либ | `libs/site`, `libs/admin`, `libs/api` |
18
+ | слой | `api`, `data-access`, `feature`, `shell`, `ui`, `util` |
19
+ | право видеть либу | тег в `eslint/boundaries/domains/<семья>.config.mjs` |
20
+ | общая всем трём приложениям либа | `libs/common/util`, тег `scope:common-util` |
21
+ | основание семейства | `<семья>/core`; его тег входит в `ADMIN_UNIVERSAL` |
22
+ | барель | `src/index.ts` либы и `index.ts` каталога компонента |
18
23
 
19
- ## Что здесь действует
24
+ Фичевый домен фронта — шесть слоёв (`api`, `data-access`, `feature/<экран>`, `shell`, `ui`,
25
+ `util`), общий домен — те же без `shell`. У бэкенда `ui` и `shell` нет: отдавать разметку и
26
+ роутиться ему нечем.
20
27
 
21
- - **Чужой символ не реэкспортируется ни одной из двух форм.** Запрещены и прямой реэкспорт по
22
- алиасу, и пара «импорт плюс экспорт того же имени»: вторая выглядит как собственное
23
- объявление и глазами в разборе проходит.
28
+ ## Где это лежит
29
+
30
+ В этом дереве — таблица в `implementation.md` рядом. Пути живут там, а не здесь: правило
31
+ переносится между репозиториями, раскладка — нет, и путь, названный в правиле, врёт в первом
32
+ же дереве, которое держит код иначе.
33
+
34
+ ## Как закон применяется здесь
35
+
36
+ - **Чужой символ не реэкспортируется ни одной из двух форм.** Запрещены и
37
+ `export { X } from '@<область>/…'`, и пара «импорт плюс `export { X };`»: вторая
38
+ выглядит как собственное объявление и глазами в ревью проходила.
24
39
  - **Строка с алиасом чужой либы в бареле — тот же реэкспорт.** Относительный путь в бареле
25
40
  законен: он собирает наружу собственные файлы либы.
26
41
  - **Не хватает права — оно дописывается строкой в конфиге домена с комментарием.** Импорт,
27
42
  который «просто заработал», означает, что тег ещё не сужен.
28
- - **У либы, общей всем приложениям, список зависимостей пуст, и каркас фронта туда не
29
- попадает.** Её импортирует и серверная сторона, и каркас уехал бы в её сборку; общий двум
30
- фронтовым семьям токен внедрения живёт отдельной либой.
31
- - **Основание семейства видит только слой утилит.** Его зовут все домены семьи, и любая его
43
+ - **У `libs/common/util` список зависимостей пуст, и Angular туда не попадает.** Либу
44
+ импортирует бэкенд, и фреймворк уехал бы в его бандл; токен DI, общий двум фронтовым
45
+ семьям, живёт в `common/platform`.
46
+ - **Основание семейства видит только `util`.** Его зовут все домены семьи, и любая его
32
47
  зависимость становится общей для всех сразу.
33
- - **Домен заводится под предмет, а не под механику.** Механика, общая нескольким доменам, едет
34
- в либу, которой она уже видна: у фронта это основание семейства, у серверной стороны — слой
35
- утилит, перечисленный у каждого домена.
48
+ - **Домен заводится под предмет, а не под механику.** Механика, общая нескольким доменам,
49
+ едет в либу, которой она уже видна: у фронта это основание семейства, у бэкенда — слой
50
+ `util`, перечисленный у каждого домена.
51
+ - **Домен, у которого непуст один слой, значится строкой с причиной.** Иначе он неотличим от
52
+ слота: пустые слои есть и у того, и у другого, а барель лежит в обоих.
53
+
54
+ ## Чего из закона здесь нет
55
+
56
+ Отклонение основания семейства от лесенки сегодня одно и названо в `CORE_EXCEPTIONS`
57
+ проверки: `common/proto` и `common/connect` у `admin/core` ради транспорта Connect.
58
+
59
+ Полный набор слоёв требуется у всех, и пустой слой дефектом не считается: `api` пуст у
60
+ домена, который ни с кем чужим не говорит. Судится только крайний случай — непуст ровно один
61
+ слой; такой домен обычно один, и он стоит в списке исключений с
62
+ причиной.
36
63
 
37
64
  ## Паттерны
38
65
 
@@ -41,14 +68,13 @@ description: Правило под закон «Импорты между либ
41
68
 
42
69
  ## Ловушки
43
70
 
44
- - **Либа, которую никто не импортирует, не проверена ничем.** Линтер и тесты проверяют её саму,
45
- а не договор с потребителем: потерянное поле в модели ошибкой не считается, пока нет
46
- вызывающего кода. Первый импортёр и есть первая проверка — слой моделей принимается после
47
- сборки и живого прогона сценария, а не по зелёному линтеру с тестами.
48
- - **Проверка раскладки принимается на нарушении, а не на зелёном прогоне.** Нарушение вносится
49
- руками, прогон краснеет, правка снимается. У самих проверок тестов обычно нет, и это
50
- единственная их приёмка.
51
- - **Удаление каталога средствами гита оставляет то, что гит не отслеживал.** Кэш сборщика
52
- внутри удалённой либы остаётся на диске, и проверка продолжает видеть её как домен без слоёв.
53
- - **Пустой слой механики неотличим от слота под будущую задачу.** Проверка требует полного
54
- набора слоёв у всех, и оба случая выглядят одинаково.
71
+ - **Либа, которую никто не импортирует, не проверена ничем.** `nx lint` и `nx test` проверяют
72
+ её саму, а не договор с потребителем: потерянное поле в `*.State` ошибкой не считается, пока
73
+ нет вызывающего кода. Первый импортёр и есть первая проверка — слой моделей принимается
74
+ после `nx build` и живого прогона сценария, а не по зелёному `lint test`.
75
+ - **Проверка принимается на нарушении, а не на зелёном прогоне.** Нарушение вносится руками,
76
+ прогон краснеет, правка снимается. У проверок в `tools/` тестов нет, и это единственная
77
+ приёмка.
78
+ - `git rm -r` оставляет `node_modules/.vite` внутри удалённого каталога, и проверка продолжает
79
+ видеть его как домен без слоёв. Добивать `rm -rf`.
80
+ - Образец, написанный второй раз, ловит `npm run check:dupes` правило `shared-code`.
@@ -0,0 +1,73 @@
1
+ ---
2
+ name: lists
3
+ kind: rule
4
+ law: lists
5
+ description: Правило под «Закон о списке записей». Брать при правке списочного экрана (libs/admin/*/feature/list), таблицы и пагинации кита. Называет порядок блоков, чем собирается список, где живёт выборка и что уже есть в ките. Готовый код экрана — в паттерне admin-lists-screen.
6
+ ---
7
+
8
+ # Списочный экран — как это устроено здесь
9
+
10
+ Правило под закон `docs/constitution/lists.md`. Закон говорит, что пользователь видит и
11
+ делает; здесь — из чего этот экран собирается в этом дереве и как он выглядит. Про вид
12
+ говорит правило: закон о нём молчит намеренно.
13
+
14
+ ## Как это называется здесь
15
+
16
+ | В законе | Здесь |
17
+ | ------------------------- | ------------------------------------------------------------------------- |
18
+ | таблица записей | `rt-table` из `@rt-tools/ui-kit-v2`, вход `[dataSource]` |
19
+ | состав и порядок столбцов | `[columnsConfig]`, хранятся по ключу `tableId` |
20
+ | карточка на узком экране | ветка `<префикс>-table`, а не своя разметка |
21
+ | тулбар | `<префикс>-toolbar` со слотами `vmToolbarLeft` и `vmToolbarRight` |
22
+ | выборка | `IList.Query.State` — страница, сортировка, условия отбора, строка поиска |
23
+ | панель настройки столбцов | асайд по маршруту `path: 'table-settings'` |
24
+
25
+ ## Где это лежит
26
+
27
+ В этом дереве — таблица в `implementation.md` рядом. Пути живут там, а не здесь: правило
28
+ переносится между репозиториями, раскладка — нет, и путь, названный в правиле, врёт в первом
29
+ же дереве, которое держит код иначе.
30
+
31
+ ## Как закон применяется здесь
32
+
33
+ - **Список собирается `<префикс>-table`, а не своей разметкой.** Скелетоны, пустое состояние,
34
+ карточки на узком экране и настройка столбцов — входы таблицы; свой
35
+ `@if (rows().length === 0)` означает, что экран собран мимо неё.
36
+ - **Строки объявляются на `rowsTable.displayedColumns()`, а не на своём списке.** Столбец с
37
+ меню таблица добавляет сама.
38
+ - **Клик по строке открывает запись, а меню — для действий над ней.** Вид нажимаемой строки
39
+ даёт `clickable`, активацию мышью и с клавиатуры — `vmTableRow`; клик по кнопке внутри строки
40
+ активацией не считается.
41
+ - **Доступность действия лежит полем строки, а не вызовом метода компонента.** Метод из шаблона
42
+ пересчитывался бы на каждой проверке.
43
+ - **Недоступное сейчас действие в меню строки не рисуется вовсе.** Пункт заводится под `@if` по
44
+ полю строки, а не выключенным: выключенный пункт перечисляет владельцу запреты вместо того,
45
+ что он может сделать, и набор их меняется от строки к строке.
46
+ - **Кнопка меню не показывается, если у строки не осталось доступных действий.** За это
47
+ отвечает вход `[rowHasActions]` — предикат по строке; считать по содержимому меню нельзя,
48
+ спроецированный шаблон известен только после отрисовки.
49
+ - **Отказ загрузки подаётся тостом, а не строкой над таблицей.** Ключ отказа читается сразу
50
+ после запроса, а не подпиской на сигнал стора: стор делят список и панель правки.
51
+ - **Экран берёт сортировку и условия отбора из ответа, а не из своего запроса.** Сервер мог
52
+ применить умолчание домена или отбросить условие.
53
+ - **Строка списка получает короткую модель сущности, а не полную.**
54
+
55
+ ## Чего из закона здесь нет
56
+
57
+ Страницу отдаёт только та процедура, в чьём ответе есть `page_model`; заявки и объекты
58
+ приходят целиком — долги `Q-L-5`, `Q-L-7` и `Q-M-2`. Выборка живёт в адресе не у каждого
59
+ списка — долг `Q-L-4`.
60
+
61
+ ## Паттерны
62
+
63
+ - `admin-lists-screen` — собрать экран: порядок блоков, таблица, меню строки, сортируемый
64
+ заголовок, тулбар.
65
+
66
+ ## Ловушки
67
+
68
+ - Без `[vmTableRowActionsRowType]` тип `let-row` выводится как `unknown`, и падает только
69
+ продовая сборка — юниты и дев-сервер проходят.
70
+ - Прокрутке нужны оба правила вместе: контейнер с `overflow-x`, таблица с
71
+ `min-width: max-content`. С одним столбцы сжимаются вместо сдвига.
72
+ - Тулбар и пагинация своих классов не носят: промежуток задаёт `<префикс>-page`.
73
+ - Заголовок стоит в своём `<header>`, а не внутри тулбара.
@@ -0,0 +1,78 @@
1
+ ---
2
+ name: navigation
3
+ kind: rule
4
+ law: navigation
5
+ description: Правило под «Закон о навигации по разделам». Брать при правке декларации меню, libs/admin/common/container/**, apps/admin/src/app/app.routes.ts и любого libs/admin/**/*.routes.ts. Называет декларацию меню, колонки и группы панели, гейтинг и вложение адресов. Готовый код — в паттерне admin-nav-item.
6
+ ---
7
+
8
+ # Навигация админки — как это устроено здесь
9
+
10
+ Правило под закон `docs/constitution/navigation.md`. Закон говорит, как пользователь
11
+ находит раздел и попадает в него; здесь — из чего меню собрано в этом дереве и как
12
+ выглядит. Про вид говорит правило: закон о нём молчит намеренно.
13
+
14
+ ## Как это называется здесь
15
+
16
+ | В законе | Здесь |
17
+ | ------------------------ | ----------------------------------------------------------------------- |
18
+ | пункт меню | запись в декларации `admin-nav.items.ts` |
19
+ | раздел с панелью | пункт с колонками; своего адреса не имеет |
20
+ | панель второго уровня | попап `rt-page-header` из пакета кита, разложенный колонками и группами |
21
+ | признак непросмотренного | точка, приходит предикатом по идентификатору пункта |
22
+ | флаг «экрана ещё нет» | `disabled` в декларации |
23
+
24
+ ## Где это лежит
25
+
26
+ В этом дереве — таблица в `implementation.md` рядом. Пути живут там, а не здесь: правило
27
+ переносится между репозиториями, раскладка — нет, и путь, названный в правиле, врёт в первом
28
+ же дереве, которое держит код иначе.
29
+
30
+ ## Как закон применяется здесь
31
+
32
+ - **Пункт объявляется один раз и служит источником и меню, и гейтинга маршрутов.** Второе
33
+ объявление рядом с маршрутами разошлось бы с первым, и получилось бы «пункта не видно, а
34
+ страница открывается».
35
+ - **Декларация не импортирует ни один `shell`.** Иначе граф `shell → container/feature → shell`
36
+ замкнётся и проверка раскладки встанет.
37
+ - **Гейтинг двухслойный: право пользователя и флаг раздела.** Пункт с флагом объявляется без
38
+ прав и без адреса — право открывает экран, а экрана нет; виден он при этом всем.
39
+ - **Данные домена приходят в шапку токеном, а не импортом.** Интерфейс и `InjectionToken` живут
40
+ в `container/util`, связывает их с реализацией композиционный корень.
41
+
42
+ ## Чего из закона здесь нет
43
+
44
+ В меню стоят семнадцать пунктов второго уровня и раздел «Финансы», у которых экрана пока нет:
45
+ они выкачены недоступными, потому что исчезнувший пункт неотличим от того, которого никогда
46
+ не было.
47
+
48
+ Отрисовка самой шапки живёт в `rt-page-header` из `@rt-tools/ui-kit-v2`. Проект её не пишет и
49
+ не может нарушить: он объявляет пункты, а рисует их кит. Китом заданы:
50
+
51
+ - подсказка у недоступного пункта и `aria-disabled` вместо нативного `disabled`;
52
+ - подсветка раздела по префиксу адреса;
53
+ - открытие панели второго уровня наведением, а на касании — нажатием;
54
+ - раскладка панели колонками, внутри колонки — группами с подписью; если первая группа в
55
+ колонке без заголовка, её пункты начинаются от верха панели;
56
+ - ширина панели по числу колонок, а не по длине подписей, и её помещаемость от своего
57
+ раздела до правого края экрана;
58
+ - указатель раскрытия у раздела с панелью и его отсутствие у раздела без неё;
59
+ - отдельная мобильная раскладка: меню сворачивается в кнопку, раскрывается теми же разделами
60
+ и группами и прокручивается, когда пункты не помещаются по высоте.
61
+
62
+ Ничего из этого правило к коду проекта не привязывает: привязывать нечего.
63
+
64
+ ## Паттерны
65
+
66
+ - `admin-nav-item` — завести пункт меню и его маршрут: декларация, права, флаг, вложение
67
+ адреса, подпись во всех локалях.
68
+
69
+ ## Ловушки
70
+
71
+ - Обработчика нажатия у недоступного пункта нет вовсе: `aria-disabled` нажатие не отбивает.
72
+ - Ветка, куда забыли подмешать константу ro-маршрута, отличается только тем, что кнопка в
73
+ шапке на ней ничего не открывает.
74
+ - Группы внутри раскрытого раздела на узком экране отдельно не сворачиваются, поэтому в
75
+ множестве раскрытых лежат только идентификаторы разделов.
76
+ - Ширина панели считается числом колонок, а не содержимым; числа задаются токенами `--rt-*`,
77
+ сырые значения запрещены правилом стилей.
78
+ - Переезд адреса трогает ссылки и спеки e2e — они ходят по адресам.
@@ -0,0 +1,63 @@
1
+ ---
2
+ name: ownership-scope
3
+ kind: rule
4
+ law: ownership
5
+ description: Правило под «Закон о владеющей сущности». Брать, когда процедура или экран работает с записью, принадлежащей владеющей сущности — разрешение идентификатора, умолчание при единственной действующей, сводки по всем. Называет обе формы разрешения и коды отказа. Готовый код — в паттерне ownership-scope-resolve. Чем это названо здесь — в implementation.md рядом.
6
+ ---
7
+
8
+ # Владеющая сущность — как это устроено здесь
9
+
10
+ Правило под закон `docs/constitution/application/ownership.md`. Закон говорит, что должно быть
11
+ верно; здесь — каким приёмом это держится. Как владеющая сущность названа в этом дереве и какие
12
+ экраны считаются сводками — в `implementation.md` рядом.
13
+
14
+ ## Как это называется здесь
15
+
16
+ | В законе | Здесь |
17
+ | ---------------------------------- | ------------------------------------------------------------------------------- |
18
+ | владеющая сущность | запись своего домена; в запросах — поле `<сущность>_id` |
19
+ | пустой идентификатор | пустая строка, а не отсутствующее поле: необязательных скаляров в контракте нет |
20
+ | сводка | списки и статистика, отвечающие сразу по всем действующим |
21
+ | отказ при двух и более действующих | `Code.InvalidArgument` |
22
+ | отказ, когда действующих нет вовсе | `Code.FailedPrecondition` |
23
+ | ненайденный явный идентификатор | `Code.NotFound` |
24
+
25
+ У списков, переведённых на общую выборку, идентификатор отдельным полем запроса не приходит
26
+ вовсе: это условие отбора в модели фильтра, а прежний номер поля помечен `reserved`.
27
+
28
+ ## Где это лежит
29
+
30
+ В этом дереве — таблица в `implementation.md` рядом. Пути живут там, а не здесь: правило
31
+ переносится между репозиториями, раскладка — нет, и путь, названный в правиле, врёт в первом
32
+ же дереве, которое держит код иначе.
33
+
34
+ ## Как закон применяется здесь
35
+
36
+ - **Запись принадлежит владеющей сущности, а не системе.** Общих настроек, действующих сразу
37
+ на все, нет: заведение второй не меняет поведение первой.
38
+ - **Пустой идентификатор означает единственную действующую.** Пока она одна, клиент вправе её
39
+ не называть.
40
+ - **При двух и более действующих запрос обязан назвать сущность.** Умолчание перестаёт быть
41
+ однозначным, и запрос отбивается как неверный.
42
+ - **Названная явно находится, даже если она скрыта.** У скрытой остаются записи, деньги и
43
+ переписка.
44
+ - **Там, где показана сводка, пустой идентификатор означает все действующие.** Списки и
45
+ статистика отвечают по всему набору, а не требуют сперва выбрать одну.
46
+ - **Когда действующих нет вовсе, запрос отбивается.** Скрытая вместо отсутствующей действующей
47
+ не подставляется.
48
+
49
+ ## Паттерны
50
+
51
+ - `ownership-scope-resolve` — разрешение сущности в процедуре и обе формы умолчания.
52
+
53
+ ## Ловушки
54
+
55
+ - **Разрешение разложено на две части намеренно:** чистая функция разбирает случай, обёртка
56
+ ходит в хранилище и переводит случай в отказ. Вторая реализация того же правила однажды уже
57
+ жила у обработчика статистики соседнего домена и успела разойтись порядком выборки.
58
+ - **Отсутствие действующей сущности и ненайденный явный идентификатор — разные случаи для
59
+ вызывающего:** первое отвечает `FailedPrecondition`, второе — `NotFound`.
60
+ - Таблица владеющих сущностей читается целиком одним запросом — она маленькая, и выборки ей не
61
+ нужно.
62
+ - Выбор сущности стоит в шапке и действует на разделы, которые говорят об одной. Снаружи выбора
63
+ нет: пользователь приходит на страницу конкретной.
@@ -2,51 +2,69 @@
2
2
  name: permissions
3
3
  kind: rule
4
4
  law: access
5
- description: Правило под закон «Доступ». Брать при заведении или правке обработчика серверной стороны, перехватчика входа, стража маршрута и декларации меню. Четыре вида доступа, объявление ровно одно, пресет плюс точечные правки, закрытие раздела и его адреса одной декларацией. Готовый код — в паттерне permissions-procedure. Чем это названо здесь — в implementation.md рядом.
5
+ description: Правило под «Закон о доступе». Брать при заведении или правке процедуры Connect, перехватчика входа, гварда маршрута админки и декларации меню. Называет четыре вида доступа, декораторы, сложение пресета с оверрайдами и гейтинг разделов админки. Готовый код — в паттерне permissions-procedure.
6
6
  ---
7
7
 
8
- # Доступ — каким приёмом
8
+ # Доступ — как это устроено здесь
9
9
 
10
- Правило под закон `{{lawsDir}}/access.md`. Закон говорит, что должно быть верно; здесь — каким
11
- приёмом это держится. Как названы права, метки объявления и где лежат перехватчик и сложение
12
- прав — `implementation.md` рядом.
10
+ Правило под закон `docs/constitution/application/access.md`. Закон говорит, что должно быть верно;
11
+ здесь чем это названо в этом дереве и где лежит. Устройство самих разделов админки —
12
+ `navigation`.
13
13
 
14
- ## Когда берётся
14
+ ## Как это называется здесь
15
15
 
16
- Заведение или правка обработчика серверной стороны, перехватчика входа, стража маршрута,
17
- декларации пункта меню.
16
+ | В законе | Здесь |
17
+ | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
18
+ | право | пара «ресурс и действие» строкой: `bookings:manage`, `chat:manage` |
19
+ | объявление доступа | декоратор на классе процедуры: `@RequiresPermission('bookings:manage')`, `@RequiresAuth('причина')`, `@PublicProcedure('причина')`, `@OptionalAuthProcedure('причина')` |
20
+ | пресет | именованный набор прав, выдаваемый пользователю целиком |
21
+ | оверрайд | точечная правка права поверх пресета для одного пользователя |
22
+ | отбивка без входа | `Code.Unauthenticated` |
23
+ | отбивка без права | `Code.PermissionDenied` |
18
24
 
19
- ## Что здесь действует
25
+ ## Где это лежит
20
26
 
21
- - **Каждый обработчик объявляет свой доступ меткой, и объявление ровно одно.** Обработчик без
22
- объявления или с двумя не даёт приложению подняться: молчание здесь опаснее отказа.
27
+ В этом дереве таблица в `implementation.md` рядом. Пути живут там, а не здесь: правило
28
+ переносится между репозиториями, раскладка нет, и путь, названный в правиле, врёт в первом
29
+ же дереве, которое держит код иначе.
30
+
31
+ ## Как закон применяется здесь
32
+
33
+ - **Каждая процедура объявляет свой доступ декоратором, и объявление ровно одно.**
34
+ Процедура без объявления или с двумя объявлениями не даёт приложению подняться.
23
35
  - **Видов доступа четыре: по праву, любому вошедшему, публично и публично с чтением входа.**
24
- Последний отдаёт вошедшему больше, чем гостю, — так владелец видит скрытые записи в общем
36
+ Последний отдаёт вошедшему больше, чем гостю, — так владелец видит скрытые объекты в общем
25
37
  списке.
26
- - **Права пользователя — это права пресета, поверх которых применены его точечные правки.**
38
+ - **Права пользователя — это права пресета, поверх которых применены его оверрайды.**
27
39
  - **Запрос без входа отбивается как неаутентифицированный, а вход без права — как отказ в
28
40
  доступе.** Это разные ответы: первый лечится входом, второй — нет.
29
- - **Право проверяется перехватчиком до тела обработчика.** Обработчик не решает, пускать ли
41
+ - **Право проверяется перехватчиком до тела процедуры.** Обработчик не решает, пускать ли
30
42
  вызывающего.
31
- - **Публичность объявляется с причиной.** Причина — аргумент метки, записанный для читателя
32
- кода; ни в ответ, ни в журнал она не уходит.
43
+ - **Публичность объявляется с причиной.** Причина — аргумент декоратора, записанный для
44
+ читателя кода; ни в ответ, ни в лог она не уходит.
33
45
  - **Пункт меню и адрес раздела закрыты по одной декларации.** Иначе скрытый пункт закрывает
34
46
  раздел лишь на вид: адрес открывается по прямой ссылке.
35
- - **Пока права не получены, интерфейс ничего не прячет.** Пустая шапка после сетевого сбоя
36
- выглядит как сломанное приложение и не оставляет выхода.
47
+ - **Пока права не получены, админка ничего не прячет.** Пустая шапка после сетевого сбоя
48
+ выглядит как сломанная админка и не оставляет выхода.
49
+
50
+ ## Чего из закона здесь нет
51
+
52
+ Право, отобранное посреди сессии, до перевхода не действует: токен живёт со своими правами до
53
+ истечения — это `Q-A-1` в законе. Смены пароля из интерфейса нет вовсе: ни экрана, ни
54
+ процедуры, ни восстановления забытого — `Q-A-2`.
37
55
 
38
56
  ## Паттерны
39
57
 
40
- - `permissions-procedure` — объявление доступа у обработчика и закрытие раздела интерфейса.
58
+ - `permissions-procedure` — объявление доступа у процедуры и гейтинг раздела админки.
41
59
 
42
60
  ## Ловушки
43
61
 
44
- - **Обработчик, о котором перехватчик ничего не знает, отбивается как отказ в доступе, а не
62
+ - **Процедура, о которой перехватчик ничего не знает, отбивается как отказ в доступе, а не
45
63
  пропускается.**
46
- - **Страж стоит на дочерних маршрутах защищённой группы, а не на самой группе.** Страж группы
64
+ - **Гвард стоит на дочерних маршрутах защищённой группы, а не на самой группе:** гвард группы
47
65
  отрабатывает один раз за загрузку страницы и переходов между разделами не видит.
48
- - **Права приходят ответом профиля уже внутри защищённой группы**, поэтому страж дожидается
49
- запуска приложения. Отказ запроса ожидание не роняет: с неизвестными правами не закрывается
66
+ - **Права приходят ответом профиля уже внутри защищённой группы**, поэтому гвард дожидается
67
+ запуска админки. Отказ запроса ожидание не роняет: с неизвестными правами не закрывается
50
68
  ничего.
51
- - **Метки объявления живут в утилитах домена входа, а не рядом с перехватчиком.** Их ставит
52
- каждый домен с обработчиками, и ребро к объявлениям дешевле ребра к секрету и хранилищу.
69
+ - Декораторы живут в `util` домена аутентификации, а не рядом с перехватчиком: их ставит
70
+ каждый домен с процедурами, и ребро к объявлениям дешевле ребра к секрету и базе.