@nitra/cursor 12.8.5 → 12.8.7

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 (202) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/bin/n-cursor.js +5 -5
  3. package/package.json +1 -1
  4. package/rules/abie/js/http_route_base.mdc +25 -0
  5. package/rules/abie/js/ua_http_route.mdc +1 -1
  6. package/rules/abie/main.mdc +12 -0
  7. package/rules/adr/js/hooks.mdc +32 -0
  8. package/rules/adr/js/madr_format.mdc +96 -0
  9. package/rules/adr/js/settings_policy.mdc +34 -0
  10. package/rules/adr/main.mdc +13 -95
  11. package/rules/bun/js/bunfig.mdc +12 -0
  12. package/rules/bun/js/layout.mdc +60 -0
  13. package/rules/bun/js/lint.mdc +9 -0
  14. package/rules/bun/js/package_json.mdc +19 -0
  15. package/rules/bun/main.mdc +9 -61
  16. package/rules/capacitor/js/ios_spm.mdc +69 -0
  17. package/rules/capacitor/js/version.mdc +29 -0
  18. package/rules/capacitor/main.mdc +8 -22
  19. package/rules/changelog/js/agent-workflow.mdc +15 -0
  20. package/rules/changelog/js/changelog-format.mdc +33 -0
  21. package/rules/changelog/js/comparison-models.mdc +40 -0
  22. package/rules/changelog/main.mdc +4 -98
  23. package/rules/ci4/js/marksman_config.mdc +31 -0
  24. package/rules/ci4/js/vscode_extensions.mdc +33 -0
  25. package/rules/ci4/main.mdc +14 -14
  26. package/rules/docker/js/compile.mdc +44 -0
  27. package/rules/docker/js/hadolint.mdc +50 -0
  28. package/rules/docker/js/mirror.mdc +13 -0
  29. package/rules/docker/js/multistage.mdc +13 -0
  30. package/rules/docker/js/native-addon.mdc +43 -0
  31. package/rules/docker/js/nginx-tag.mdc +7 -0
  32. package/rules/docker/js/nginx-user.mdc +37 -0
  33. package/rules/docker/js/non-root.mdc +39 -0
  34. package/rules/docker/main.mdc +15 -196
  35. package/rules/ga/js/lint_toolchain.mdc +15 -0
  36. package/rules/ga/js/required_workflows.mdc +35 -0
  37. package/rules/ga/js/vscode.mdc +17 -0
  38. package/rules/ga/js/workflow_common.mdc +108 -0
  39. package/rules/ga/js/workflows.mdc +32 -0
  40. package/rules/ga/js/zizmor.mdc +7 -0
  41. package/rules/ga/main.mdc +17 -125
  42. package/rules/graphql/js/tooling.mdc +13 -0
  43. package/rules/graphql/js/vscode_extensions.mdc +13 -0
  44. package/rules/graphql/main.mdc +3 -22
  45. package/rules/hasura/js/internal_urls.mdc +27 -0
  46. package/rules/hasura/js/migrations.mdc +13 -0
  47. package/rules/hasura/js/svc_hl.mdc +17 -0
  48. package/rules/hasura/main.mdc +8 -30
  49. package/rules/image-avif/js/avif_generation.mdc +26 -0
  50. package/rules/image-avif/js/package_json_optout.mdc +21 -0
  51. package/rules/image-avif/main.mdc +7 -34
  52. package/rules/image-compress/js/package_json.mdc +7 -0
  53. package/rules/image-compress/js/package_setup.mdc +13 -0
  54. package/rules/image-compress/main.mdc +4 -12
  55. package/rules/js/docs/index.md +3 -3
  56. package/rules/js/js/dep-policy.mdc +17 -0
  57. package/rules/js/js/eslint-config.mdc +28 -0
  58. package/rules/js/js/extensions.mdc +8 -0
  59. package/rules/js/js/file-extensions.mdc +12 -0
  60. package/rules/js/js/for-in.mdc +26 -0
  61. package/rules/js/js/jscpd.mdc +42 -0
  62. package/rules/js/js/knip.mdc +15 -0
  63. package/rules/js/js/lint-js-workflow.mdc +58 -0
  64. package/rules/js/js/oxlintrc.mdc +20 -0
  65. package/rules/js/js/package-json.mdc +31 -0
  66. package/rules/js/js/tests.mdc +9 -0
  67. package/rules/js/js/utils-lib-structure.mdc +15 -0
  68. package/rules/js/main.mdc +21 -214
  69. package/rules/js-bun-db/js/bun-sql-migration.mdc +15 -0
  70. package/rules/js-bun-db/js/connection.mdc +42 -0
  71. package/rules/js-bun-db/js/pg-format-identifiers.mdc +102 -0
  72. package/rules/js-bun-db/js/pg-format-shim.mdc +99 -0
  73. package/rules/js-bun-db/js/pg-leftover.mdc +27 -0
  74. package/rules/js-bun-db/js/pg-listen-notify.mdc +51 -0
  75. package/rules/js-bun-db/js/query-safety.mdc +117 -0
  76. package/rules/js-bun-db/js/sql-array.mdc +88 -0
  77. package/rules/js-bun-db/js/unsafe.mdc +65 -0
  78. package/rules/js-bun-db/main.mdc +15 -605
  79. package/rules/js-bun-redis/js/imports.mdc +47 -0
  80. package/rules/js-bun-redis/js/package_json.mdc +44 -0
  81. package/rules/js-bun-redis/main.mdc +3 -11
  82. package/rules/js-mssql/js/mssql-in-list.mdc +38 -0
  83. package/rules/js-mssql/js/mssql-pool.mdc +56 -0
  84. package/rules/js-mssql/js/mssql-query-template.mdc +33 -0
  85. package/rules/js-mssql/js/mssql-tvp.mdc +75 -0
  86. package/rules/js-mssql/js/mssql-version.mdc +7 -0
  87. package/rules/js-mssql/main.mdc +10 -198
  88. package/rules/js-run/js/check-env.mdc +35 -0
  89. package/rules/js-run/js/conn-aliases.mdc +109 -0
  90. package/rules/js-run/js/jsconfig.mdc +20 -0
  91. package/rules/js-run/js/otel-configmap.mdc +6 -0
  92. package/rules/js-run/js/pino.mdc +6 -0
  93. package/rules/js-run/js/project-structure.mdc +11 -0
  94. package/rules/js-run/js/runtime.mdc +14 -0
  95. package/rules/js-run/js/scope.mdc +11 -0
  96. package/rules/js-run/js/settimeout.mdc +11 -0
  97. package/rules/js-run/js/temporal.mdc +5 -0
  98. package/rules/js-run/main.mdc +16 -218
  99. package/rules/k8s/js/configmap.mdc +41 -0
  100. package/rules/k8s/js/deployment_resources.mdc +49 -0
  101. package/rules/k8s/js/hasura_httproute.mdc +91 -0
  102. package/rules/k8s/js/hpa_apiversion.mdc +27 -0
  103. package/rules/k8s/js/ingress_gateway.mdc +16 -0
  104. package/rules/k8s/js/kustomize_structure.mdc +144 -0
  105. package/rules/k8s/js/lint_k8s.mdc +72 -0
  106. package/rules/k8s/js/multidoc_yaml.mdc +5 -0
  107. package/rules/k8s/js/network_policy.mdc +136 -0
  108. package/rules/k8s/js/schema_modeline.mdc +57 -0
  109. package/rules/k8s/js/service.mdc +44 -0
  110. package/rules/k8s/js/topology_hpa_pdb.mdc +181 -0
  111. package/rules/k8s/main.mdc +30 -843
  112. package/rules/nginx-default-tpl/js/dockerfile.mdc +36 -0
  113. package/rules/nginx-default-tpl/js/http-route.mdc +41 -0
  114. package/rules/nginx-default-tpl/js/ini-keys.mdc +21 -0
  115. package/rules/nginx-default-tpl/js/template-structure.mdc +86 -0
  116. package/rules/nginx-default-tpl/js/vscode.mdc +37 -0
  117. package/rules/nginx-default-tpl/main.mdc +6 -112
  118. package/rules/npm-module/js/docs/index.md +5 -5
  119. package/rules/npm-module/js/docs/rule_meta.md +6 -6
  120. package/rules/npm-module/js/docs/skill_meta.md +8 -8
  121. package/rules/npm-module/js/header_doc_pointer.mdc +18 -0
  122. package/rules/npm-module/js/package_structure.mdc +62 -0
  123. package/rules/npm-module/js/rule_meta.mdc +11 -0
  124. package/rules/npm-module/js/skill_meta.mdc +11 -0
  125. package/rules/npm-module/main.mdc +10 -55
  126. package/rules/php/js/lint_php_yml.mdc +12 -0
  127. package/rules/php/js/tooling.mdc +66 -0
  128. package/rules/php/main.mdc +7 -66
  129. package/rules/python/js/lint_python_yml.mdc +23 -0
  130. package/rules/python/js/pyproject_toml.mdc +32 -0
  131. package/rules/python/js/tooling.mdc +23 -0
  132. package/rules/python/main.mdc +9 -33
  133. package/rules/rego/js/rego-lint.mdc +31 -0
  134. package/rules/rego/js/vscode_extensions.mdc +11 -0
  135. package/rules/rego/js/vscode_settings.mdc +13 -0
  136. package/rules/rego/main.mdc +8 -24
  137. package/rules/rust/js/coverage.mdc +28 -0
  138. package/rules/rust/js/lint.mdc +22 -0
  139. package/rules/rust/js/tauri_composition.mdc +8 -0
  140. package/rules/rust/js/vscode_extensions.mdc +12 -0
  141. package/rules/rust/main.mdc +8 -38
  142. package/rules/security/js/rego_policies.mdc +15 -0
  143. package/rules/security/js/sample_secret.mdc +19 -0
  144. package/rules/security/js/trufflehog.mdc +21 -0
  145. package/rules/security/main.mdc +7 -35
  146. package/rules/style/js/admin-table.mdc +88 -0
  147. package/rules/style/js/colors.mdc +21 -0
  148. package/rules/style/js/gap.mdc +22 -0
  149. package/rules/style/js/quasar-fixes.mdc +32 -0
  150. package/rules/style/js/quasar.mdc +7 -0
  151. package/rules/style/js/tooling.mdc +85 -0
  152. package/rules/style/main.mdc +13 -253
  153. package/rules/tauri/js/cargo_mutants_config.mdc +39 -0
  154. package/rules/tauri/js/tool_surface.mdc +21 -0
  155. package/rules/tauri/js/tooling.mdc +25 -0
  156. package/rules/tauri/main.mdc +8 -78
  157. package/rules/test/js/cargo_mutants_config.mdc +18 -0
  158. package/rules/test/js/docs/index.md +7 -7
  159. package/rules/test/js/location.mdc +52 -0
  160. package/rules/test/js/no-console-store-restore.mdc +11 -0
  161. package/rules/test/js/no-process-chdir.mdc +15 -0
  162. package/rules/test/js/no-relative-fs-path.mdc +22 -0
  163. package/rules/test/js/sandbox-aware-test.mdc +28 -0
  164. package/rules/test/js/stryker_config.mdc +26 -0
  165. package/rules/test/js/vitest-config-pool-forks.mdc +33 -0
  166. package/rules/test/main.mdc +18 -184
  167. package/rules/text/js/ci-lint-text.mdc +15 -0
  168. package/rules/text/js/cspell.mdc +81 -0
  169. package/rules/text/js/dotenv-linter.mdc +16 -0
  170. package/rules/text/js/forbidden-prettier.mdc +13 -0
  171. package/rules/text/js/markdownlint.mdc +25 -0
  172. package/rules/text/js/oxfmt.mdc +35 -0
  173. package/rules/text/js/package-json.mdc +26 -0
  174. package/rules/text/js/shellcheck.mdc +18 -0
  175. package/rules/text/js/v8r.mdc +23 -0
  176. package/rules/text/js/vscode.mdc +86 -0
  177. package/rules/text/main.mdc +20 -237
  178. package/rules/vue/js/composition-api.mdc +82 -0
  179. package/rules/vue/js/nheader-layout.mdc +171 -0
  180. package/rules/vue/js/node-imports.mdc +25 -0
  181. package/rules/vue/js/quasar-ui.mdc +32 -0
  182. package/rules/vue/js/structure.mdc +101 -0
  183. package/rules/vue/js/testing.mdc +32 -0
  184. package/rules/vue/js/tfm-translations.mdc +26 -0
  185. package/rules/vue/js/vite-config.mdc +126 -0
  186. package/rules/vue/js/vite-env.mdc +55 -0
  187. package/rules/vue/js/vue-imports.mdc +25 -0
  188. package/rules/vue/main.mdc +16 -640
  189. package/scripts/auto-rules.mjs +6 -6
  190. package/scripts/auto-skills.mjs +3 -3
  191. package/scripts/docs/auto-rules.md +17 -31
  192. package/scripts/docs/auto-skills.md +18 -163
  193. package/scripts/docs/index.md +16 -16
  194. package/scripts/lib/docs/index.md +36 -36
  195. package/scripts/lib/docs/mirror-parity.md +7 -7
  196. package/scripts/lib/docs/rule-meta.md +12 -12
  197. package/scripts/lib/docs/skill-meta.md +9 -9
  198. package/scripts/lib/docs/worktree-notice.md +10 -8
  199. package/scripts/lib/rule-meta.mjs +6 -6
  200. package/scripts/lib/skill-meta.mjs +6 -6
  201. package/scripts/lib/worktree-notice.mjs +2 -2
  202. package/scripts/utils/docs/index.md +14 -14
@@ -7,867 +7,54 @@ alwaysApply: false
7
7
 
8
8
  # Kubernetes YAML у шляхах з `k8s`
9
9
 
10
- Для кожного файлу `*.yaml`, у шляху якого є сегмент директорії **`k8s`** (наприклад `site/k8s/base/deployment.yaml`), якщо існує **публічна** схема (kustomization / yannh / datree CRDs-catalog див. «Визначення схеми YAML»), **перший рядок** — коментар-директива для [YAML Language Server](https://github.com/redhat-developer/yaml-language-server) з URL за `https://`:
10
+ Для кожного файлу `*.yaml`, у шляху якого є сегмент директорії **`k8s`** перевірка modeline `$schema`, структури Kustomize, маніфестів Deployment / Service / NetworkPolicy та лінт через kubeconform і kubescape.
11
11
 
12
- ```yaml
13
- # yaml-language-server: $schema=https://...
14
- ```
15
-
16
- Далі — вміст маніфесту. Зайвий порожній рядок між коментарем і YAML не додавай, якщо в проєкті не прийнято інше.
17
-
18
- **Modeline — опційний:** якщо для конкретного поєднання `apiVersion`/`kind` **немає** надійної публічної схеми (yannh/datree/schemastore не покривають), залиш файл **без** рядка `# yaml-language-server: $schema=…`. **Заборонено** ставити `$schema=file:…` як заглушку — це створює видимість валідації без неї. Без modeline `check-k8s` не валідує URL, але **`lint-k8s`** (kubeconform/kubescape) продовжить роботу. Якщо modeline присутній — він обов'язково перший рядок і **тільки** `https://` URL, який відповідає очікуваному за `apiVersion`/`kind`.
19
-
20
- **Виняток — modeline заборонено:** `apiVersion: alb.yc.io/v1alpha1`, `kind: HttpBackendGroup` (Yandex ALB) — рядка **`# yaml-language-server: $schema=…`** у файлі **не** має бути (ні в першому рядку, ні далі). Перший рядок — одразу YAML (`apiVersion:` тощо). Перевірка — **`rules/k8s/fix.mjs`**.
21
-
22
- **Розширення:** усі маніфести під **`k8s`**, включно з **`kustomization.yaml`**, — лише **`.yaml`** (розширення **`.yml`** не використовуй).
23
-
24
- **Скоп — поза `.github/`:** правило стосується YAML-маніфестів у каталогах **`k8s`** (визначаються відносно кореня репо, не за абсолютним шляхом). Файли під **`.github/workflows/`** та **`.github/actions/`** ця перевірка **не** зачіпає — їхній скоп визначає **`ga.mdc`** (там канон — **`.yml`**). Це робить два правила несуперечливими навіть у проєктах, де сам корінь репо випадково має ім'я `k8s/` (тоді сегмент `k8s` присутній у абсолютному шляху всіх файлів, але **відносно кореня** його там немає).
12
+ **Скоп — поза `.github/`:** правило стосується YAML-маніфестів у каталогах **`k8s`** (визначаються відносно кореня репо, не за абсолютним шляхом). Файли під **`.github/workflows/`** та **`.github/actions/`** ця перевірка **не** зачіпає — їхній скоп визначає **`ga.mdc`** (там канон — **`.yml`**).
25
13
 
26
14
  **Dockerfile / hadolint** — окреме правило **`docker.mdc`** і **`npx @nitra/cursor fix docker`**.
27
15
 
28
- ## lint-k8s: kubeconform і kubescape
29
-
30
- Окремо від modeline `$schema` у редакторі варто ганяти CLI-лінтери (**kubeconform** і **kubescape**) по тих самих дерев’ях **`…/k8s`**.
31
-
32
- **Залежності:** виконувані файли kubeconform, kubescape і kubectl у **PATH** (kustomize використовуємо як вшиту підкоманду **`kubectl kustomize`** — окремий бінарник `kustomize` не потрібен); не додавай їх у **devDependencies**.
33
-
34
- **Версія Kubernetes для kubeconform** має відповідати PIN yannh у цьому правилі та в **`rules/k8s/fix.mjs`** (зараз **`-kubernetes-version 1.33.9`** — semver без префікса `v`, еквівалент релізу **v1.33.9**; набір схем **`v1.33.9-standalone-strict`**). Для CRD додатково підключай реєстр [datreeio/CRDs-catalog](https://github.com/datreeio/CRDs-catalog) другим **`-schema-location`**, як у [прикладах kubeconform](https://github.com/yannh/kubeconform#readme). За потреби **`-ignore-missing-schemas`**, якщо частина CRD ще без публічної схеми.
35
-
36
- **kubescape — вхід через зібраний kustomize-маніфест:** для кожного dir-у з `kustomization.yaml` (`kind: Kustomization`; **`kind: Component`** пропускається — він не білдиться окремо) `lint-k8s` виконує **`kubectl kustomize <dir>`** і передає stdout у **`kubescape scan <tmp-file>`** з порогом **`--severity-threshold high`** (вбудована в kubectl підкоманда `kustomize` — окремий бінарник `kustomize` не потрібен; рендеринг локальний і не потребує доступу до кластера). Маніфест проходить через тимчасовий файл, бо **`kubescape scan` у v4.x не читає stdin** (`-` як шлях → `no resources found to scan`; прапорця `--input`/`--stdin` немає); тимчасова директорія створюється під `os.tmpdir()` і прибирається після скану. Збірка через kustomize нормалізує namespace на workload-manifest-файлах і **NetworkPolicy у `base/networkpolicy.yaml`** (через `base/kustomization.yaml` `namespace:`), що дає коректний матчинг `podSelector` у `C-0260` (`Missing network policy`) і дозволяє kubescape бачити дерево overlays / components зі справжніми ресурсами. Якщо в дереві **`…/k8s`** немає жодного `kustomization.yaml` (проєкт без Kustomize) — fallback на старий dir-скан **`kubescape scan <каталог-k8s>`**. Перший запуск kubescape може завантажувати артефакти — у CI потрібна мережа або [offline](https://github.com/kubescape/kubescape#readme). На відміну від kubeconform, у **kubescape scan** немає прапорця **`-kubernetes-version`**: перевірка йде за **framework/control** (NSA, MITRE, CIS тощо), а не проти OpenAPI-схеми конкретного релізу Kubernetes. **Орієнтир** для репозиторію той самий, що й для kubeconform — кластер **v1.33.9** (див. **`-kubernetes-version 1.33.9`** вище); для CIS і подібних наближень обирай актуальний framework під політику команди (**`kubescape list frameworks`**, див. [CLI reference](https://github.com/kubescape/kubescape/blob/master/docs/cli-reference.md)).
37
-
38
- ### Винятки kubescape: `.kubescape-exceptions.json`
39
-
40
- Якщо в **корені проєкту** є файл **`.kubescape-exceptions.json`** — `lint-k8s` автоматично передає його в `kubescape scan` через **`--exceptions`** ([postureExceptionPolicy](https://github.com/kubescape/kubescape/blob/master/docs/exceptions.md)). Файл — JSON-масив об'єктів з полями `name`, `policyType: "postureExceptionPolicy"`, `actions` (`["alertOnly"]` — знижує fail до alert, не блокує lint), `resources` (resource designator) і `posturePolicies` (масив `controlID`).
41
-
42
- Канонічний кейс — **C-0012** (`Applications credentials in configuration files`, High): control тригериться на **імʼя** env, що містить підрядок `secret`/`password`/`key`/`token`, а **не** на значення. Для `HASURA_GRAPHQL_JWT_SECRET` у ConfigMap значення — публічний JWT-конфіг (`jwk_url`, `issuer`), не credentials, але kubescape падає лише через імʼя. Точкове виключення для ConfigMap із цим env — канон: [.kubescape-exceptions.json.snippet.json](./js/templates/kubescape_exceptions/.kubescape-exceptions.json.snippet.json)
43
-
44
- Підстав свою `attributes.name` (рядок або regex), якщо ConfigMap зветься інакше; виключай контрольно, а не глобально (не додавай винятки без `attributes.name`/`labels`, бо тоді C-0012 знімається для усіх ConfigMap-ів проєкту і реальні витоки credentials теж пройдуть).
45
-
46
- Лінт запускається через правило **`n-cursor lint k8s`** (реалізація — **`npm/rules/k8s/js/lint.mjs`**, делегує kubeconform/kubescape у **`npm/rules/k8s/lint/lint.mjs`**). Окремий `package.json`-скрипт `lint-k8s` не потрібен і не перевіряється.
47
-
48
- Додай workflow **`.github/workflows/lint-k8s.yml`** (гілки **`dev`** і **`main`**, лише **`.yml`**, узгоджено з **`ga.mdc`**). **Не** дублюй **`setup-node`**, **`oven-sh/setup-bun`**, **`actions/cache`** і **`bun install`** у job — після **`checkout`** використовуй локальний composite **`setup-bun-deps`** (шлях **`./.github/actions/setup-bun-deps`** або **`./npm/github-actions/setup-bun-deps`**, як у репозиторії). Встановлення **kubeconform** / **kubescape** лишаються окремими кроками.
49
-
50
- ```yaml title=".github/workflows/lint-k8s.yml"
51
- name: Lint K8s
52
-
53
- on:
54
- push:
55
- branches:
56
- - dev
57
- - main
58
- paths:
59
- - '**/k8s/**/*.yaml'
60
-
61
- pull_request:
62
- branches:
63
- - dev
64
- - main
65
-
66
- concurrency:
67
- group: ${{ github.ref }}-${{ github.workflow }}
68
- cancel-in-progress: true
69
-
70
- jobs:
71
- lint-k8s:
72
- runs-on: ubuntu-latest
73
- permissions:
74
- contents: read
75
- steps:
76
- - uses: actions/checkout@v6
77
- with:
78
- persist-credentials: false
79
-
80
- - uses: ./.github/actions/setup-bun-deps
81
-
82
- - name: Install kubeconform
83
- run: |
84
- curl -sSL "https://github.com/yannh/kubeconform/releases/download/v0.7.0/kubeconform-linux-amd64.tar.gz" | tar xz
85
- sudo mv kubeconform /usr/local/bin/
86
-
87
- - name: Install kubescape
88
- run: |
89
- curl -s https://raw.githubusercontent.com/kubescape/kubescape/master/install.sh | /bin/bash
90
- echo "$HOME/.kubescape/bin" >> $GITHUB_PATH
91
-
92
- # kustomize не встановлюємо окремо — використовуємо вбудовану підкоманду `kubectl kustomize`
93
- # (kubectl уже доступний на github-hosted runner'ах: https://github.com/actions/runner-images).
94
-
95
- - name: Lint K8s
96
- run: n-cursor lint k8s --read-only
97
- ```
98
-
99
- ## Deployment: `resources.requests` (CPU і memory)
100
-
101
- У кожному контейнері **`Deployment`** обов’язкові **`resources.requests.cpu`** і **`resources.requests.memory`** (непорожні скаляри Kubernetes Quantity).
102
-
103
- ### Шар **`…/k8s/…/base/…`** (dev / щільний packing)
104
-
105
- У **всіх** `Deployment` у файлах під **`…/k8s/…/base/…`** значення **жорстко фіксовані** (для **cpu** допускається число **`0.02`** у YAML):
106
-
107
- ```yaml
108
- resources:
109
- requests:
110
- cpu: '0.02'
111
- memory: '128Mi'
112
- ```
113
-
114
- **HPA і PDB у base не тримаємо**: ні локальних `hpa.yaml` / `pdb.yaml` поруч із workload-manifest-файлами, ні через `resources` / `components` / `bases`. Канон — sibling каталог **`components/`** (Kustomize Component) поруч з `base/`, який підключають лише прод-overlays (див. розділ нижче). **NetworkPolicy** — навпаки: **обов'язковий і у `base/`**, у вигляді `base/networkpolicy.yaml` поруч з workload-маніфестом, підключений через `base/kustomization.yaml` `resources:` — щоб обмеження діяли і на dev-середовищі.
115
-
116
- ### Поза base (оверлеї, окремі каталоги)
117
-
118
- Якщо ще не підібрано власні ліміти під сервіс, орієнтир для **`requests`**:
119
-
120
- ```yaml
121
- resources:
122
- requests:
123
- cpu: '0.5'
124
- memory: '512Mi'
125
- ```
126
-
127
- У прод-оверлеях підіймай **`cpu` / `memory`** до реального споживання через **`patches`** або окремі фрагменти маніфестів. **`check k8s`** не вимагає саме **`0.5` / `512Mi`** поза base — лише непорожні **`requests.cpu`** і **`requests.memory`**.
128
-
129
- ```yaml title="k8s/prod/kustomization.yaml (фрагмент)"
130
- patches:
131
- - target:
132
- kind: Deployment
133
- name: backend-api
134
- patch: |-
135
- - op: replace
136
- path: /spec/template/spec/containers/0/resources/requests/cpu
137
- value: '500m'
138
- - op: replace
139
- path: /spec/template/spec/containers/0/resources/requests/memory
140
- value: 1Gi
141
- ```
142
-
143
- Поле **`imagePullPolicy`** скрипт **не** перевіряє (залишається політиці Kubernetes за тегом образу).
144
-
145
- Образ **`hasura/graphql-engine`**: дозволений лише канонічний тег зі списку **`allowed_hasura_images`** у rego-пакеті **`k8s.manifest`** (`policy/manifest/manifest.rego` — єдине джерело істини; допускається префікс **`docker.io/`**); решта — помилка **check k8s**.
146
-
147
- ### HTTPRoute для Deployment з `hasura/graphql-engine`
148
-
149
- **Прив'язка:** **check k8s** вважає **HTTPRoute** Hasura-маршрутом, якщо в **тому самому каталозі** є **`Deployment`** з образом **`hasura/graphql-engine`**, а його **`metadata.name`** збігається з **`metadata.name`** цього **`HTTPRoute`**. Саме за цією конвенцією скрипт шукає пари для звірки канону.
150
-
151
- **Префікс параметризовано:** **`<prefix>`** — рядок перед **`/ql`** у першому Hasura-правилі (**`Exact <prefix>/ql`**). Може бути порожнім (**`<prefix>`** = **``**, шлях **`/ql`**) або непорожнім (наприклад **`<prefix>`** = **`/notify`**, шлях **`/notify/ql`**). Усі інші Hasura-правила цього **HTTPRoute** мають містити той самий **`<prefix>`**.
152
-
153
- **Канон — 4 правила у цьому порядку** (додаткові правила поверх канону дозволені — вони просто ігноруються під час зіставлення):
154
-
155
- 1. **`Exact <prefix>/ql`** → **`RequestRedirect`** **`ReplaceFullPath <prefix>/ql/console`** **`statusCode: 302`**.
156
- 2. **`Exact <prefix>/ql/`** → те саме (редирект на **`<prefix>/ql/console`** 302).
157
- 3. **`PathPrefix <prefix>/ql`** → **`URLRewrite`** **`ReplacePrefixMatch /`**, один **`backendRef`** на headless **Service** (**`-hl`**).
158
- 4. **WebSocket:** **`PathPrefix <prefix>/ql`** + header **`Upgrade: websocket`** → **`URLRewrite`** **`ReplacePrefixMatch /`** + **`RequestHeaderModifier`** **`remove: [Authorization]`** (авторизація для WebSocket іде всередині messages). Той самий **`backendRef`**.
159
-
160
- **`parentRefs`**, **`hostnames`**, **`metadata.namespace`** / **`name`** підлаштуй під середовище. У **`backendRefs.name`** вказуй **headless** **Service** з суфіксом **`-hl`** (див. розділ **«Service: `svc.yaml` і `svc-hl.yaml`»**); у прикладі **`db-h-hl`** заміни на фактичне ім’я.
161
-
162
- ```yaml
163
- # yaml-language-server: $schema=https://datreeio.github.io/CRDs-catalog/gateway.networking.k8s.io/httproute_v1beta1.json
164
- apiVersion: gateway.networking.k8s.io/v1
165
- kind: HTTPRoute
166
- metadata:
167
- name: db-h
168
- namespace: dev
169
- spec:
170
- parentRefs:
171
- - group: gateway.networking.k8s.io
172
- kind: Gateway
173
- name: gw
174
- namespace: dev
175
- sectionName: https
176
- hostnames:
177
- - aiml.live
178
- rules:
179
- - matches:
180
- - path:
181
- type: Exact
182
- value: /ql
183
- filters:
184
- - type: RequestRedirect
185
- requestRedirect:
186
- path:
187
- type: ReplaceFullPath
188
- replaceFullPath: /ql/console
189
- statusCode: 302
190
- - matches:
191
- - path:
192
- type: Exact
193
- value: /ql/
194
- filters:
195
- - type: RequestRedirect
196
- requestRedirect:
197
- path:
198
- type: ReplaceFullPath
199
- replaceFullPath: /ql/console
200
- statusCode: 302
201
- - matches:
202
- - path:
203
- type: PathPrefix
204
- value: /ql
205
- filters:
206
- - type: URLRewrite
207
- urlRewrite:
208
- path:
209
- type: ReplacePrefixMatch
210
- replacePrefixMatch: /
211
- backendRefs:
212
- - name: db-h-hl
213
- port: 8080
214
- # у websocket авторизація йде всередині messages
215
- # Той самий URLRewrite, що й для HTTP: інакше бекенд бачить /ql/v1/graphql замість /v1/graphql
216
- - matches:
217
- - path:
218
- type: PathPrefix
219
- value: /ql
220
- headers:
221
- - type: Exact
222
- name: Upgrade
223
- value: websocket
224
- filters:
225
- - type: URLRewrite
226
- urlRewrite:
227
- path:
228
- type: ReplacePrefixMatch
229
- replacePrefixMatch: /
230
- - type: RequestHeaderModifier
231
- requestHeaderModifier:
232
- remove:
233
- - Authorization
234
- backendRefs:
235
- - name: db-h-hl
236
- port: 8080
237
- ```
238
-
239
- ## Service: заборонені анотації GKE
240
-
241
- У **`kind: Service`** не додавай у **`metadata.annotations`** **`cloud.google.com/neg`** і **`cloud.google.com/backend-config`** (legacy під Ingress / старе балансування GKE). **check k8s** падає, якщо ключ є.
242
-
243
- ## Service: `svc.yaml` і `svc-hl.yaml` (Gateway API)
244
-
245
- Пара файлів для кластерного й headless **Service**: **`svc.yaml`** + **`svc-hl.yaml`** в одному каталозі, **`spec.type: ClusterIP`** / **`clusterIP: None`**, імена **`-hl`**, узгодженість пар, маршрути **`gateway.networking.k8s.io`** (**HTTPRoute**, **GRPCRoute**, **TCPRoute**, **TLSRoute**, **UDPRoute**) — **backendRef** лише на сервіси з суфіксом **`-hl`**; якщо **kustomization** посилається на **`svc.yaml`**, у **тому ж** **`kustomization.yaml`** має бути посилання на sibling **`svc-hl.yaml`**. Скрипт **не** створює файли — додай **`svc-hl.yaml`** вручну (копія з правками **name** / **clusterIP**).
246
-
247
- **Точні умови та повідомлення `fail`** — верхній JSDoc **`npm/scripts/rules/k8s/fix.mjs`**.
248
-
249
- ### Gateway API: не дублюй namespace у `backendRef`
250
-
251
- У маршрутах Gateway API (**HTTPRoute**, **GRPCRoute**, **TCPRoute**, **TLSRoute**, **UDPRoute**) у `spec.rules[*].backendRefs[*]` **не** додавай поле **`namespace`**, якщо його значення збігається з **`metadata.namespace`** самого маршруту. За замовчуванням Gateway API визначає backend у тому ж namespace, що й маршрут, тож такий рядок — мертвий: він плутає під час перенесень між середовищами і ламається мовчки, якщо overlay змінює namespace маршруту через Kustomize, а в backendRef залишився старий рядок. Прибери поле — поведінка не зміниться. **`check k8s`** падає на такому збігу.
252
-
253
- ```yaml
254
- # ❌ погано — namespace дублює metadata.namespace маршруту
255
- apiVersion: gateway.networking.k8s.io/v1
256
- kind: HTTPRoute
257
- metadata:
258
- name: admin-site
259
- namespace: dev-b2b
260
- spec:
261
- rules:
262
- - backendRefs:
263
- - name: auth-hl
264
- namespace: dev-b2b # ← прибери
265
- port: 8080
266
- ```
267
-
268
- ```yaml
269
- # ✅ добре — namespace лише в metadata
270
- apiVersion: gateway.networking.k8s.io/v1
271
- kind: HTTPRoute
272
- metadata:
273
- name: admin-site
274
- namespace: dev-b2b
275
- spec:
276
- rules:
277
- - backendRefs:
278
- - name: auth-hl
279
- port: 8080
280
- ```
281
-
282
- Якщо backend **дійсно** живе в іншому namespace — поле **`namespace`** у `backendRef` обов'язкове (і потрібен `ReferenceGrant` у тому namespace); правило цього випадку не торкається.
283
-
284
- ## ConfigMap: ім'я збігається з Deployment
285
-
286
- Якщо в `k8s/base/` є **`configmap.yaml`** і **Deployment**, і цей Deployment посилається рівно на **один** ConfigMap — `metadata.name` ConfigMap має збігатися з `metadata.name` Deployment. Точні умови перевірки — **`rules/k8s/fix.mjs`**.
287
-
288
- ## ConfigMap для Hasura-Deployment
289
-
290
- Якщо в `k8s/base/` поруч із **`configmap.yaml`** є **Deployment** з образом **`hasura/graphql-engine`**, у `data` ConfigMap **обов'язково** мають бути env-ключі:
291
-
292
- - **`HASURA_GRAPHQL_ENABLE_REMOTE_SCHEMA_PERMISSIONS`** зі значенням **`"true"`**;
293
- - **`HASURA_GRAPHQL_ENABLE_RELAY`** зі значенням **`"false"`**;
294
- - **`HASURA_GRAPHQL_ENABLE_TELEMETRY`** зі значенням **`"false"`**;
295
- - **`HASURA_GRAPHQL_ENABLED_LOG_TYPES`** зі значенням **`"startup,http-log"`** (точний рядок);
296
- - **`HASURA_GRAPHQL_ENABLED_APIS`** зі значенням **`"metadata,graphql,pgdump"`** (точний рядок) — **значення для base/dev**;
297
- - **`HASURA_GRAPHQL_DISABLE_EVENTING`** — ключ обов'язковий, значення довільне (за замовчуванням **`"true"`**).
298
-
299
- Точні умови перевірки — rego-пакет **`k8s.hasura_configmap`** (cross-file прив'язка ConfigMap↔Deployment — у `rules/k8s/js/manifests.mjs`).
300
-
301
- ```yaml
302
- data:
303
- HASURA_GRAPHQL_ENABLE_REMOTE_SCHEMA_PERMISSIONS: 'true'
304
- HASURA_GRAPHQL_ENABLE_RELAY: 'false'
305
- HASURA_GRAPHQL_ENABLE_TELEMETRY: 'false'
306
- HASURA_GRAPHQL_ENABLED_LOG_TYPES: 'startup,http-log'
307
- HASURA_GRAPHQL_ENABLED_APIS: 'metadata,graphql,pgdump'
308
- HASURA_GRAPHQL_DISABLE_EVENTING: 'true'
309
- ```
310
-
311
- ### `HASURA_GRAPHQL_ENABLED_APIS` поза base/dev
312
-
313
- `pgdump` дозволено **лише** для **base**/**dev**. Кожен **не-base** overlay (`k8s/<env>/`, де `<env>` ≠ `base`/`dev`), що успадковує Hasura-base, **зобов'язаний** у своєму **`kustomization.yaml`** перевизначити `HASURA_GRAPHQL_ENABLED_APIS` до **`"metadata,graphql"`** (без `pgdump`) — патчем JSON6902 або Strategic Merge на ціль **ConfigMap**. Перевірка — cross-file у `rules/k8s/js/manifests.mjs` (`validateHasuraOverlayEnabledApisOverride`); `kind: Component` пропускається.
314
-
315
- ```yaml title="k8s/prod/kustomization.yaml"
316
- patches:
317
- - target:
318
- kind: ConfigMap
319
- name: db-h
320
- patch: |
321
- - op: replace
322
- path: /data/HASURA_GRAPHQL_ENABLED_APIS
323
- value: metadata,graphql
324
- ```
325
-
326
- ## Kustomize: структура каталогів (`base` / overlays)
327
-
328
- Трансформуй дерева **`**/k8s`**, щоб **винести спільне** через [Kustomize](https://kustomize.io/): один канонічний **`base`** і тонкі **overlays** для інших середовищ.
329
-
330
- ### Джерело правди — середовище dev
331
-
332
- - За основу бери **все, що відповідає середовищу dev** (як воно має виглядати в кластері для dev).
333
- - У **такому вигляді** цей набір стає каталогом **`base`**: спільні маніфести без окремої директорії **`dev/`**.
334
- - Окремої директорії **`dev`** **не повинно існувати**: за середовище **dev** відповідає **`base`** (застосування **`kubectl apply -k …/base`** або збірка з overlay, який посилається на `base`).
335
-
336
- ### Overlays (не-dev)
337
-
338
- - У каталозі кожного іншого середовища (наприклад **`ua/`**, **`prod/`**) має бути **мінімум файлів**: типово лише **`kustomization.yaml`** (посилання на `base`, `patches`, `replacements`, `components` тощо) і ресурси чи додаткові YAML, **необхідні лише для цього overlay**.
339
- - Відмінності від dev вносяться **оверрайдами** (patches, `images`, `replicas`, `configMapGenerator` тощо), а не копіюванням повного дерева з `base`.
340
-
341
- ### Namespace
342
-
343
- - **`base/kustomization.yaml`:** поле **`namespace:`** має бути **непорожнім** (перевіряє **check k8s**, якщо файл є).
344
-
345
- - **Коли `metadata.namespace` обов’язковий у файлі:** YAML під **`k8s`** — непорожній **`metadata.namespace`** для namespaced **kind** (винятки — кластерні **kind**, перелік **`CLUSTER_SCOPED_KINDS`** у **`rules/k8s/fix.mjs`**). У overlays Kustomize значення в маніфесті буде перезаписано полем **`namespace:`** з відповідного **`kustomization.yaml`**, тому в `base` пиши канонічний dev-namespace.
346
-
347
- - **Не додавай** окремі **patches** Kustomize, які лише змінюють **namespace**: **namespace** визначає Kustomize; у overlays додаткові зміни — без дублювання логіки **namespace**.
348
-
349
- ### Рядки, що змінюються між середовищами
350
-
351
- - У manifest-файлах у **`base`** для полів (або значень), які **будуть відрізнятися** в інших середовищах, на **тому самому рядку** додай коментар:
352
-
353
- ```yaml
354
- image: my-app:dev-tag # буде замінено через kustomize
355
- ```
356
-
357
- Текст коментаря узгодь у команді; важливо, щоб було видно, що значення **навіть у base** може бути замінене overlay.
358
-
359
- ### Міграція зі старої структури
360
-
361
- - Після перенесення маніфестів у **`base`** та overlays і перевірки (**`check k8s`**, **`lint-k8s`**) **видали** застарілі файли та директорії, які замінені новою схемою (дубльовані копії, колишні шляхи без Kustomize), щоб у репозиторії не залишалося зайвих або суперечливих маніфестів.
362
-
363
- ### Зміна image — через `images:`, не через `patches[]`
364
-
365
- Підміну image у Pod-шаблоні Deployment в overlay роби директивою `images:`, а не JSON6902-патчем `op: replace` на `/spec/template/spec/containers/<N>/image`. `images:` — канонічний механізм Kustomize (матчить за іменем образу, не за індексом контейнера, тож стійкий до перестановки).
366
-
367
- - У **`name`** — те, що **дослівно** стоїть у `image:` у base (або в попередньому шарі) **без тегу**: інакше підміна не спрацює. Тег у `name` зайвий — Kustomize матчить лише за іменем.
368
- - **`newName`** — кінцеве ім'я образу (як правило, збігається з `name`); `**newTag**` — тег для прода. Якщо `newTag` дорівнює тегу, який вже в base, його можна не вказувати.
369
- - **`digest`** (`@sha256:…`) у `name` / `newName` не чіпай — це не тег.
370
-
371
- ```yaml title="k8s/prod/kustomization.yaml (фрагмент)"
372
- images:
373
- - name: europe-west4-docker.pkg.dev/abie-ua/c/apply-on-invoice-discount
374
- newName: europe-west4-docker.pkg.dev/abie-ua/c/apply-on-invoice-discount
375
- newTag: v2025-04-29
376
- ```
377
-
378
- **`check k8s` автоматично** для кожного `kustomization.yaml`:
379
-
380
- 1. конвертує кожну JSON6902-операцію `op: replace` на `/spec/template/spec/containers/<N>/image` (target `kind: Deployment`) у запис `images:` (визначає оригінальний image у base через `resources:` / `bases` / `components` / `crds`). Якщо у `patches[i].patch` після конвертації не залишилось ops — патч прибирається повністю; інакше у `patches[i].patch` залишаються лише не-image ops у вихідному порядку;
381
- 2. чистить існуючий блок `images:` — зрізає `:tag` з `name` і видаляє `newTag`, який збігається з відрізаним тегом.
382
-
383
- ## Ingress → Gateway API (GKE)
384
-
385
- Якщо в дереві **`k8s`** трапляється маніфест з **`kind: Ingress`**, його потрібно **замінити на Gateway API**, а не залишати Ingress.
386
-
387
- 1. **HTTPRoute** — окремий файл **`hr.yaml`** (або узгоджене ім’я в команді), **`kind: HTTPRoute`**, `apiVersion` з групи **`gateway.networking.k8s.io`** (див. приклад `$schema` для HTTPRoute у розділі «Визначення схеми YAML»).
388
- 2. **HealthCheckPolicy (GKE)** — окремий файл **`hc.yaml`**, наприклад:
389
-
390
- ```yaml
391
- apiVersion: networking.gke.io/v1
392
- kind: HealthCheckPolicy
393
- ```
394
-
395
- Для `$schema` у першому рядку див. приклад **HealthCheckPolicy** у тому ж розділі (datree CRDs-catalog).
396
-
397
- **`spec.targetRef`** (типово **`kind: Service`**) має вказувати на **headless** сервіс — ім’я з суфіксом **`-hl`** (див. **«Service: `svc.yaml` і `svc-hl.yaml`»**); для проєктів **abie** точні умови — Rego-пакет **`abie.health_check_policy`** + **abie.mdc**.
398
-
399
- За потреби розшир **`target`** (`name`, `namespace`), щоб однозначно вказати об’єкт.
400
-
401
- **`check k8s`:** заборонено **`kind: Ingress`**.
402
-
403
- ## Deployment: `topologySpreadConstraints`, HPA / PDB через `components/`, NetworkPolicy у `base/`
404
-
405
- Для **кожного** `kind: Deployment` у каталозі **`…/k8s/…/base/`** (у будь-якому файлі `.yaml`, наприклад **`deploy.yaml`**, **`deployment.yaml`**) сам Deployment має канонічні **`spec.template.spec.topologySpreadConstraints`**, а **HPA і PDB** живуть у **sibling каталозі** **`…/k8s/…/components/`** (Kustomize Component, фіксована назва каталогу — `components`). У `base/` локальні `hpa.yaml` і `pdb.yaml` **заборонені** (file-existence error). У дереві base-kustomize HPA / PDB також **не дозволені** через `resources` / `components` / `bases`.
406
-
407
- Кожен **Deployment** має явно задавати безпечну rollout strategy: **`spec.strategy.type: RollingUpdate`**, **`spec.strategy.rollingUpdate.maxUnavailable: 0`**, **`spec.strategy.rollingUpdate.maxSurge: 1`**. Це гарантує, що під час оновлення image Kubernetes спершу створить один новий Pod, дочекається його `Ready`, і лише потім прибере один старий Pod; для prod-overlays, що успадковують `base`, це запобігає короткому вікну без ready backend-ів.
408
-
409
- ```yaml
410
- strategy:
411
- type: RollingUpdate
412
- rollingUpdate:
413
- maxUnavailable: 0
414
- maxSurge: 1
415
- ```
416
-
417
- **NetworkPolicy** — інша історія: оскільки обмеження мережі мають діяти **і на dev**, NP лежить **у `base/`** (а не в `components/`). Для **кожного** з **`Deployment`**, **`StatefulSet`**, **`DaemonSet`**, **`Job`**, **`CronJob`** під `k8s` обов'язковий **NetworkPolicy**: у **`…/k8s/…/base/`** — у **`base/networkpolicy.yaml`** поруч з workload-маніфестом (multi-doc, якщо workload-ів кілька); у **не-base** — **`networkpolicy.yaml`** поруч із маніфестом workload у тому ж каталозі (overlay-specific override). `metadata.name` NetworkPolicy **= `metadata.name`** workload; `spec.podSelector.matchLabels.app` **= мітка `app`** з `spec.selector.matchLabels` (для **Job** — з `spec.template.metadata.labels.app`, для **CronJob** — з `spec.jobTemplate.spec.template.metadata.labels.app`; у Job/CronJob ручний `spec.selector` невалідний без `manualSelector: true`, тож джерело — pod-template labels). У `base/kustomization.yaml` `resources:` має бути `networkpolicy.yaml`. Відсутні документи **`check k8s`** створює автоматично і додає `networkpolicy.yaml` у `base/kustomization.yaml` `resources:`.
418
-
419
- **Канонічна структура `<pkg>/k8s/components/`** (sibling до `base/`) — лише HPA і PDB:
420
-
421
- - **`kustomization.yaml`** — `apiVersion: kustomize.config.k8s.io/v1alpha1`, `kind: Component`, `resources: [hpa.yaml, pdb.yaml]` (відсортовано за алфавітом).
422
- - **`hpa.yaml`** — `autoscaling/v2`, `HorizontalPodAutoscaler`, **без** `metadata.namespace` (namespace задає kustomization-споживач), `spec.scaleTargetRef.name` **= `metadata.name`** Deployment з base, dev-like значення `minReplicas: 1`, `maxReplicas: 1`.
423
- - **`pdb.yaml`** — `policy/v1`, `PodDisruptionBudget`, **без** `metadata.namespace`, `spec.selector.matchLabels.app` **= `spec.selector.matchLabels.app`** Deployment, dev-like `minAvailable: 0`.
424
-
425
- **Канонічний `base/networkpolicy.yaml`** — `networking.k8s.io/v1`, `NetworkPolicy`, **без** `metadata.namespace` (namespace додає `base/kustomization.yaml`); один або кілька документів (`---`) — по одному на кожен workload (**Deployment** / **StatefulSet** / **DaemonSet** / **Job** / **CronJob**) у тому ж `base/`; `metadata.name` **= `metadata.name`** workload, `spec.podSelector.matchLabels.app` **= мітка `app`** workload. **Ingress:** `from.podSelector: {}` (інші Pod у namespace). **Egress (усі workload-и):** kube-dns через `kube-system` namespaceSelector (UDP/TCP 53); link-local DNS `169.254.0.0/16` (UDP/TCP 53, GKE NodeLocal DNSCache); **TCP 80 і 443** на `0.0.0.0/0` (HTTP/HTTPS назовні); **in-cluster** — `to.namespaceSelector: {}` з **явним списком TCP-портів** (`80, 443, 5432, 3306, 1433, 6379, 8080, 4222, 4317, 4318`; трафік на `*.svc` / Pod-и в кластері). **StatefulSet** додатково має egress/ingress `to/from.podSelector: {}` для intra-replica реплікації. Заборонено: `egress: [{}]`; `to.namespaceSelector: {}` без `ports:` (catch-all). Додаткові правила можна дописати поряд — superset-підхід. Канон — два **повних** snippet-файли (без merge у runtime; JS-генератор/rego обирають один за `kind` workload-у через анотацію `metadata.annotations['nitra.dev/workload-kind']`): [deployment.snippet.yaml](./policy/network_policy/template/deployment.snippet.yaml) (для `Deployment`/`Job`/`CronJob`/`DaemonSet`) та [stateful-set.snippet.yaml](./policy/network_policy/template/stateful-set.snippet.yaml) (для `StatefulSet`; містить deployment-канон + intra-replica правила). Якщо документа для workload немає — **`check k8s`** дописує його автоматично.
426
-
427
- Інші назви каталогу (`scale/`, `hpa-component/`, `pdb-component/`) — fail.
428
-
429
- **Overlays** (`ua/`, прод-overlays) підключають `components: [- ../components]` і додають JSON6902-патчі для прод-значень: `/spec/minReplicas`, `/spec/maxReplicas` (HPA), `/spec/minAvailable` (PDB). NetworkPolicy успадковується з base через `resources: [- ../base]` — окремий `networkpolicy.yaml` у overlay не обов'язковий і додається тільки для overlay-specific правил. Dev-середовище (`base`) HPA/PDB не отримує — як і потрібно — а NetworkPolicy діє з тим самим каноном.
430
-
431
- **`<pkg>/k8s/components/kustomization.yaml`** має `kind: Component` (не `kind: Kustomization`) — це **джерело** канонічних HPA/PDB для всіх overlays, а не overlay сам по собі. Прод-перезаписи (`/spec/minReplicas`, `/spec/maxReplicas`, `/spec/minAvailable`) живуть у `<env>/kustomization.yaml`, що підключає Component через `components:`. У самому Component patches не потрібні — він env-неутральний; **`check k8s`** не вимагає прод-патчів від `components/kustomization.yaml`.
432
-
433
- У **не-base** оверлеях (без `components/`) поруч із `Deployment` лишається звична схема: окремі **`hpa.yaml`**, **`networkpolicy.yaml`** і **`pdb.yaml`**, якщо такі потрібні для цього середовища (NP overlay-specific override). Для **StatefulSet**, **DaemonSet**, **Job**, **CronJob** у не-base — обов'язкового локального `networkpolicy.yaml` немає, бо NP вже успадковується з base; додавайте лише якщо overlay змінює правила. **`check k8s`** звіряє прив'язку за іменами (і **дописує** відсутні NetworkPolicy-документи автоматично у файл поруч):
434
-
435
- - **`hpa.yaml`** (поза **`…/base/`**) — `autoscaling/v2`, `HorizontalPodAutoscaler`, `spec.scaleTargetRef.name` **= `metadata.name`** Deployment.
436
- - **`networkpolicy.yaml`** (overlay-specific, опціональний) — той самий канон egress/ingress, що в `base/` (multi-doc, якщо у каталозі кілька workload-ів).
437
- - **`pdb.yaml`** — `policy/v1`, `PodDisruptionBudget`, `spec.selector.matchLabels.app` **= `spec.selector.matchLabels.app`** Deployment.
438
- - **`topologySpreadConstraints`** — запис з `maxSkew: 1`, `topologyKey: kubernetes.io/hostname`, `whenUnsatisfiable: ScheduleAnyway`, `labelSelector.matchLabels.app` рівне тій самій мітці `app`.
439
-
440
- **Перевірка структури `components/`** (для кожного Deployment у `base/`): наявність каталогу, валідний `kustomization.yaml` як Component, `hpa.yaml` і `pdb.yaml` з відповідністю до Deployment-name / app-label. Алгоритм — функція `validateComponentsForBaseDeployment` у **`rules/k8s/fix.mjs`**.
441
-
442
- **Локальні шляхи в `kustomization.yaml`:** кожен запис без `://` (remote) з `resources` / `bases` / `components` / `crds`, `patchesStrategicMerge`, `patches[].path`, `patchesJson6902[].path`, `configurations[]`, `replacements[].path` має вказувати на **існуючий** у репозиторії файл (`.yaml` / `.yml`) або **каталог**; биті посилання — помилка **`check k8s`**.
443
-
444
- **Порядок `resources`:** у маніфесті з **`apiVersion: kustomize.config.k8s.io/…`**, **`kind: Kustomization`**, елементи **`resources:`** (лише непорожні рядки) мають бути **відсортовані за алфавітом (англ. локаль, як `localeCompare('en')` у `rules/k8s/fix.mjs`)**. Поля **`bases`**, **`components`**, **`crds`** цією перевіркою **не** впорядковуються.
445
-
446
- ### HTTPRoute → NetworkPolicy ingress (GCLB + Envoy)
447
-
448
- Якщо в каталозі workload є **`HTTPRoute`** (Gateway API; `apiVersion: gateway.networking.k8s.io/*`) з **`backendRef`** на **`<workload>-hl`** Service (mapping через `service.spec.selector.app`), **`check k8s`** автоматично додає в NetworkPolicy цього workload **ingress-правило** з фіксованим набором CIDR-ів і **TCP-портами з `backendRefs[].port`** (дедуп, відсортовано за зростанням).
449
-
450
- Без цього правила трафік від **GKE Gateway** (Envoy data-plane з proxy-only subnet регіону, наприклад `us-central1-proxy-only` = `10.10.0.0/23`) і **Google health checks** (`35.191.0.0/16`, `130.211.0.0/22`) блокується базовим NetworkPolicy (бо canon ingress допускає тільки `podSelector: {}` — intra-namespace pod ↔ pod).
451
-
452
- CIDR-набір зафіксовано (без конфігурації):
453
-
454
- - `35.191.0.0/16` — GCP HC global
455
- - `130.211.0.0/22` — GCP HC global (legacy)
456
- - `10.0.0.0/8` — широкий range, покриває proxy-only subnets усіх регіонів GKE
457
-
458
- Приклад згенерованого ingress-правила (поверх baseline canon):
459
-
460
- ```yaml
461
- ingress:
462
- - from:
463
- - podSelector: {}
464
- - from:
465
- - ipBlock: { cidr: 35.191.0.0/16 }
466
- - ipBlock: { cidr: 130.211.0.0/22 }
467
- - ipBlock: { cidr: 10.0.0.0/8 }
468
- ports:
469
- - { protocol: TCP, port: 8080 }
470
- ```
471
-
472
- Якщо workload не прив'язаний до жодного HTTPRoute — правило **не** додається; NP лишається baseline (intra-namespace + canon egress). Не-HTTP routes (`GRPCRoute`, `TCPRoute`, `TLSRoute`, `UDPRoute`) поки не покриті — додається лише за HTTPRoute. Алгоритм: функція `collectHttpRouteIngressForWorkload` у **`rules/k8s/js/manifests.mjs`** індексує `HTTPRoute.backendRefs` і `Service` у каталозі, визначає через `selector.app`, дедуп TCP-портів. Виклики — з `appendNetworkPolicyDocuments` і `regenerateLegacyNetworkPolicyDocsInFile`.
473
-
474
- ### Env-залежні межі (за сегментом після `/k8s/`)
475
-
476
- **Dev-like середовища** — сегмент `base`, `dev`, або з суфіксом `-qa` (напр. `tr-qa`):
477
-
478
- - У **зібраному** маніфесті (після Kustomize), якщо лишився **HPA**: `minReplicas` — рівно **1**, `maxReplicas` — рівно **1**.
479
- - PDB: `minAvailable` — рівно **0**.
480
-
481
- **Прод-середовища** — усе інше (будь-який overlay без суфікса `-qa`):
482
-
483
- - HPA: `minReplicas` — мінімум **2**, `maxReplicas` — мінімум **2**.
484
- - PDB: `minAvailable` — мінімум **1**.
485
-
486
- ### Прод-оверрайди у `kustomization.yaml`
487
-
488
- У прод-накладенні `kustomization.yaml`, що підключає `components: [- ../components]` (тобто overlay-tree містить **HorizontalPodAutoscaler** і **PodDisruptionBudget**), у `patches[]` **обов'язкові** JSON6902-перевизначення прод-значень:
489
-
490
- - **`HorizontalPodAutoscaler`**: `/spec/minReplicas` і `/spec/maxReplicas` (мінімум 2).
491
- - **`PodDisruptionBudget`**: `/spec/minAvailable` (мінімум 1).
492
-
493
- Формат patch — JSON6902 або Strategic Merge; `check k8s` перевіряє **наявність** відповідних JSON Pointer-ів у **`patches[]`**.
494
-
495
- ```yaml title="k8s/prod/kustomization.yaml (фрагмент)"
496
- apiVersion: kustomize.config.k8s.io/v1beta1
497
- kind: Kustomization
498
- namespace: prod
499
- resources:
500
- - ../base
501
- components:
502
- - ../components
503
- patches:
504
- - target:
505
- kind: HorizontalPodAutoscaler
506
- name: backend-api
507
- patch: |-
508
- - op: replace
509
- path: /spec/minReplicas
510
- value: 2
511
- - op: replace
512
- path: /spec/maxReplicas
513
- value: 10
514
- - target:
515
- kind: PodDisruptionBudget
516
- name: backend-api
517
- patch: |-
518
- - op: replace
519
- path: /spec/minAvailable
520
- value: 1
521
- ```
522
-
523
- ### Приклади `components/` і `base/networkpolicy.yaml`
524
-
525
- ```yaml title="k8s/components/kustomization.yaml"
526
- apiVersion: kustomize.config.k8s.io/v1alpha1
527
- kind: Component
528
- resources:
529
- - hpa.yaml
530
- - pdb.yaml
531
- ```
532
-
533
- ```yaml title="k8s/base/kustomization.yaml (фрагмент)"
534
- apiVersion: kustomize.config.k8s.io/v1beta1
535
- kind: Kustomization
536
- namespace: dev
537
- resources:
538
- - deploy.yaml
539
- - networkpolicy.yaml
540
- - svc.yaml
541
- - svc-hl.yaml
542
- ```
543
-
544
- ```yaml title="k8s/base/networkpolicy.yaml"
545
- # yaml-language-server: $schema=https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.33.9-standalone-strict/networkpolicy-networking-v1.json
546
- apiVersion: networking.k8s.io/v1
547
- kind: NetworkPolicy
548
- metadata:
549
- name: backend-api
550
- annotations:
551
- nitra.dev/workload-kind: Deployment
552
- spec:
553
- podSelector:
554
- matchLabels:
555
- app: backend-api
556
- policyTypes:
557
- - Ingress
558
- - Egress
559
- ingress:
560
- - from:
561
- - podSelector: {}
562
- egress:
563
- - to:
564
- - namespaceSelector:
565
- matchLabels:
566
- kubernetes.io/metadata.name: kube-system
567
- podSelector:
568
- matchLabels:
569
- k8s-app: kube-dns
570
- ports:
571
- - protocol: UDP
572
- port: 53
573
- - protocol: TCP
574
- port: 53
575
- - to:
576
- - ipBlock:
577
- cidr: 169.254.0.0/16
578
- ports:
579
- - protocol: UDP
580
- port: 53
581
- - protocol: TCP
582
- port: 53
583
- - to:
584
- - ipBlock:
585
- cidr: 0.0.0.0/0
586
- ports:
587
- - protocol: TCP
588
- port: 80
589
- - protocol: TCP
590
- port: 443
591
- - to:
592
- - namespaceSelector: {}
593
- ports:
594
- - protocol: TCP
595
- port: 80
596
- - protocol: TCP
597
- port: 443
598
- - protocol: TCP
599
- port: 5432
600
- - protocol: TCP
601
- port: 3306
602
- - protocol: TCP
603
- port: 1433
604
- - protocol: TCP
605
- port: 6379
606
- - protocol: TCP
607
- port: 8080
608
- - protocol: TCP
609
- port: 4222
610
- - protocol: TCP
611
- port: 4317
612
- - protocol: TCP
613
- port: 4318
614
- ```
615
-
616
- ```yaml title="k8s/base/networkpolicy.yaml — workload з HTTPRoute (з GCLB ingress)"
617
- # yaml-language-server: $schema=https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.33.9-standalone-strict/networkpolicy-networking-v1.json
618
- apiVersion: networking.k8s.io/v1
619
- kind: NetworkPolicy
620
- metadata:
621
- name: backend-api
622
- annotations:
623
- nitra.dev/workload-kind: Deployment
624
- spec:
625
- podSelector:
626
- matchLabels:
627
- app: backend-api
628
- policyTypes:
629
- - Ingress
630
- - Egress
631
- ingress:
632
- - from:
633
- - podSelector: {}
634
- - from: # auto-added by check k8s for HTTPRoute-paired workloads
635
- - ipBlock: { cidr: 35.191.0.0/16 }
636
- - ipBlock: { cidr: 130.211.0.0/22 }
637
- - ipBlock: { cidr: 10.0.0.0/8 }
638
- ports:
639
- - { protocol: TCP, port: 8080 }
640
- egress:
641
- # ... (ідентично до базового прикладу вище)
642
- ```
643
-
644
- ```yaml title="k8s/components/hpa.yaml"
645
- # yaml-language-server: $schema=https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.33.9-standalone-strict/horizontalpodautoscaler-autoscaling-v2.json
646
- apiVersion: autoscaling/v2
647
- kind: HorizontalPodAutoscaler
648
- metadata:
649
- name: backend-api
650
- spec:
651
- scaleTargetRef:
652
- apiVersion: apps/v1
653
- kind: Deployment
654
- name: backend-api
655
- minReplicas: 1 # прод overlay підіймає до >= 2
656
- maxReplicas: 1 # прод overlay підіймає до >= 2
657
- metrics:
658
- - type: Resource
659
- resource:
660
- name: cpu
661
- target:
662
- type: Utilization
663
- averageUtilization: 70
664
- behavior:
665
- scaleUp:
666
- stabilizationWindowSeconds: 15
667
- policies:
668
- - type: Percent
669
- value: 100
670
- periodSeconds: 30
671
- - type: Pods
672
- value: 4
673
- periodSeconds: 30
674
- selectPolicy: Max
675
- scaleDown:
676
- stabilizationWindowSeconds: 300
677
- policies:
678
- - type: Percent
679
- value: 25
680
- periodSeconds: 120
681
- selectPolicy: Min
682
- ```
683
-
684
- ```yaml title="k8s/components/pdb.yaml"
685
- # yaml-language-server: $schema=https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.33.9-standalone-strict/poddisruptionbudget-policy-v1.json
686
- apiVersion: policy/v1
687
- kind: PodDisruptionBudget
688
- metadata:
689
- name: backend-api
690
- spec:
691
- minAvailable: 0 # прод overlay підіймає до >= 1
692
- selector:
693
- matchLabels:
694
- app: backend-api
695
- ```
696
-
697
- ```yaml title="k8s/base/deploy.yaml (фрагмент)"
698
- spec:
699
- template:
700
- spec:
701
- containers:
702
- - name: backend-api
703
- image: example.registry/backend-api:tag
704
- resources:
705
- requests:
706
- cpu: '0.02'
707
- memory: '128Mi'
708
- topologySpreadConstraints:
709
- - maxSkew: 1
710
- topologyKey: kubernetes.io/hostname
711
- whenUnsatisfiable: ScheduleAnyway
712
- labelSelector:
713
- matchLabels:
714
- app: backend-api
715
- ```
716
-
717
- Точні умови та повідомлення `fail` — JSDoc на початку `npm/scripts/rules/k8s/fix.mjs` і функції `validateComponentsForBaseDeployment` / `prodOverlayHpaPdbOverrideNeeds`.
718
-
719
- ## HorizontalPodAutoscaler: `autoscaling/v2`
720
-
721
- У manifest-файлах під **`k8s`** заборонено **`apiVersion: autoscaling/v1`** (legacy HPA з єдиною метрикою CPU). Мігруй **HorizontalPodAutoscaler** на **`autoscaling/v2`**: поле **`spec.metrics`** (замість **`spec.targetCPUUtilizationPercentage`**) з **`type: Resource`** і **`target.type: Utilization`** / **`AverageUtilization`** — підтримує декілька метрик і зовнішні метрики. `check k8s` падає на будь-якому документі з **`apiVersion: autoscaling/v1`**.
722
-
723
- Ресурси **batch** (наприклад **CronJob**, **Job**): застаріле **`apiVersion: batch/v1beta1`** у файлах під **`k8s` під час `check k8s` переписується** на **`apiVersion: batch/v1`**.
724
-
725
- ```yaml
726
- # yaml-language-server: $schema=https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.33.9-standalone-strict/horizontalpodautoscaler-autoscaling-v2.json
727
- apiVersion: autoscaling/v2
728
- kind: HorizontalPodAutoscaler
729
- metadata:
730
- name: app
731
- spec:
732
- scaleTargetRef:
733
- apiVersion: apps/v1
734
- kind: Deployment
735
- name: app
736
- minReplicas: 1
737
- maxReplicas: 5
738
- metrics:
739
- - type: Resource
740
- resource:
741
- name: cpu
742
- target:
743
- type: Utilization
744
- averageUtilization: 70
745
- ```
746
-
747
- 3. **JSON patch у kustomization:** де можливо, змінюй ресурс через **`op: replace`** (одна операція на `path`), а не пару **`remove` + `add`** на той самий шлях. **`add`** / **`remove`** лишай лише коли **`replace`** не підходить (наприклад додати новий ключ або прибрати поле без заміни).
748
-
749
- ```yaml title="overlay/kustomization.yaml (фрагмент)"
750
- patches:
751
- - target:
752
- kind: Deployment
753
- name: x
754
- patch: |-
755
- - op: replace
756
- path: /spec/template/spec/nodeSelector
757
- value:
758
- preem: "false"
759
- ```
760
-
761
- ### `patches[].target`: лише `kind` і `name`
762
-
763
- У `patches[].target` залишай **тільки** **`kind`** і **`name`** — поля **`group`** і **`version`** прибирай. Kustomize визначає ціль за GVK+name; `group`/`version` — звужувальні фільтри, потрібні лише за реальної колізії `kind+name` між різними API-групами або версіями. У межах одного namespace apiserver зберігає об'єкт у єдиному storage-GVK, тож для звичайних маніфестів така колізія неможлива, і `group`/`version` у `target` — мертвий шум, який ламається мовчки під час змін API (наприклад, перехід `v1beta1` → `v1`).
764
-
765
- ```yaml
766
- # ❌ зайві group / version
767
- patches:
768
- - target:
769
- group: gateway.networking.k8s.io
770
- version: v1beta1
771
- kind: Gateway
772
- name: gw
773
-
774
- # ✅
775
- patches:
776
- - target:
777
- kind: Gateway
778
- name: gw
779
- ```
780
-
781
- **Виняток:** залишай `group` / `version`, лише якщо в дереві overlay реально співіснують ресурси з однаковими `kind`+`name`, але різними API-групами/версіями (наприклад, дві CRD з одним `kind`). У такому разі вкажи мінімальний набір полів, потрібний для дисамбігуації.
782
-
783
- ### Структурний сорт `patches[]` і inline JSON6902
784
-
785
- `patches[]` у `kustomization.yaml` має бути відсортовано за tuple **`target.kind` → `target.name` → `target.namespace` → `path`** (`localeCompare('en', { sensitivity: 'base' })`). Це робить діфи передбачуваними і прибирає «гойдання» порядку при додаванні нових цілей. Поля `target.group` / `target.version` у tuple не входять — для них діє правило «patches[].target: лише kind і name».
786
-
787
- ```yaml
788
- # ❌ atlas йде перед apruv
789
- patches:
790
- - target:
791
- kind: ReferenceGrant
792
- name: atlas-to-base
793
- - target:
794
- kind: ReferenceGrant
795
- name: apruv-to-base
796
-
797
- # ✅
798
- patches:
799
- - target:
800
- kind: ReferenceGrant
801
- name: apruv-to-base
802
- - target:
803
- kind: ReferenceGrant
804
- name: atlas-to-base
805
- ```
806
-
807
- Усередині кожного inline `patches[i].patch` (literal block scalar — масив JSON6902-операцій) операції теж сортуються за **`path`**, **але лише** коли набір «безпечний»: усі ops — `add` / `replace` і всі `path` попарно дизʼюнктні (жоден не префікс іншого, наприклад `/spec` і `/spec/replicas`). Інакше порядок не чіпається — у `move` / `copy` / `test` / `remove` чи на спільних шляхах послідовність ops семантично значуща (RFC 6902), і пересорт ламає логіку.
16
+ ## Активація
808
17
 
809
- ```yaml
810
- # ❌ minReplicas перед maxReplicas (за алфавітом max < min)
811
- patch: |-
812
- - op: add
813
- path: /spec/minReplicas
814
- value: 2
815
- - op: replace
816
- path: /spec/maxReplicas
817
- value: 10
18
+ Правило активується автоматично для `**/k8s/**/*.yaml` (через `globs`). Додаткова конфігурація в `.n-cursor.json` не потрібна.
818
19
 
819
- # ✅
820
- patch: |-
821
- - op: replace
822
- path: /spec/maxReplicas
823
- value: 10
824
- - op: add
825
- path: /spec/minReplicas
826
- value: 2
827
- ```
20
+ [k8s-schema-modeline](./js/schema_modeline.mdc)
828
21
 
829
- ## Визначення схеми YAML (канон)
22
+ [k8s-lint-k8s](./js/lint_k8s.mdc)
830
23
 
831
- Орієнтир — **перший документ** (до наступного `---`).
24
+ [k8s-deployment-resources](./js/deployment_resources.mdc)
832
25
 
833
- 1. **Ім’я** `kustomization.yaml` → `https://json.schemastore.org/kustomization.json`.
834
- 2. **`apiVersion: v1`** → yannh, PIN набору схем **`v1.33.9-standalone-strict`**, ref репозиторію для raw URL — **`master`** (каталог версії не є коренем репо на GitHub):
835
- `https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/<PIN>/<kind>-v1.json`
836
- `<kind>`: літери в нижньому регістрі без роздільників між CamelCase (наприклад `Service` → `service`).
26
+ [k8s-hasura-httproute](./js/hasura_httproute.mdc)
837
27
 
838
- **`kind: Secret`** і **`type: kubernetes.io/basic-auth`** — той самий шаблон, **`secret-v1.json`**:
28
+ [k8s-service](./js/service.mdc)
839
29
 
840
- ```yaml
841
- # yaml-language-server: $schema=https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.33.9-standalone-strict/secret-v1.json
842
- ```
30
+ [k8s-configmap](./js/configmap.mdc)
843
31
 
844
- 3. **`apiVersion: group/version`** і **group** у **`YANNH_GROUPS`** у скрипті → yannh:
845
- `https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/<PIN>/<kind>-<group-частина>-<version>.json`
846
- де **`<group-частина>`** — **перший сегмент** `group` до першої крапки: для груп без крапок збігається з усією group (`apps/v1` + `Deployment` → `deployment-apps-v1.json`); для `*.k8s.io` / `*.apiserver.k8s.io` — лише префікс до `.k8s.io` (`networking.k8s.io/v1` + `Ingress` → `ingress-networking-v1.json`; `networking.k8s.io/v1` + `NetworkPolicy` → `networkpolicy-networking-v1.json`; `rbac.authorization.k8s.io/v1` + `ClusterRole` → `clusterrole-rbac-v1.json`; `flowcontrol.apiserver.k8s.io/v1` + `FlowSchema` → `flowschema-flowcontrol-v1.json`). У yannh **немає** файлів з фрагментом `-k8s-io-` у назві — патерн `<group-з-крапками-як-дефіси>` дає 404.
847
- 4. **Інакше** (CRD тощо) → [datreeio/CRDs-catalog](https://github.com/datreeio/CRDs-catalog). Типово для `$schema` у редакторі — **GitHub Pages**:
848
- `https://datreeio.github.io/CRDs-catalog/<group>/<kind>_<version>.json`
849
- (`<kind>` — лише літери та цифри в нижньому регістрі, без роздільників між CamelCase, як для yannh.)
32
+ [k8s-kustomize-structure](./js/kustomize_structure.mdc)
850
33
 
851
- **Виняток — `InfisicalSecret`:** `apiVersion: secrets.infisical.com/v1alpha1`, `kind: InfisicalSecret` — канонічний modeline через **raw** на гілці **`main`** (не GitHub Pages):
34
+ [k8s-topology-hpa-pdb](./js/topology_hpa_pdb.mdc)
852
35
 
853
- ```yaml
854
- # yaml-language-server: $schema=https://raw.githubusercontent.com/datreeio/CRDs-catalog/main/secrets.infisical.com/infisicalsecret_v1alpha1.json
855
- ```
36
+ [k8s-network-policy](./js/network_policy.mdc)
856
37
 
857
- **Приклад (Gateway API):** `apiVersion: gateway.networking.k8s.io/v1`, `kind: HTTPRoute`:
38
+ [k8s-ingress-gateway](./js/ingress_gateway.mdc)
858
39
 
859
- ```yaml
860
- # yaml-language-server: $schema=https://datreeio.github.io/CRDs-catalog/gateway.networking.k8s.io/httproute_v1beta1.json
861
- ```
40
+ [k8s-hpa-apiversion](./js/hpa_apiversion.mdc)
862
41
 
863
- **Приклад (GKE):** `apiVersion: networking.gke.io/v1`, `kind: HealthCheckPolicy`:
42
+ [k8s-multidoc-yaml](./js/multidoc_yaml.mdc)
864
43
 
865
- ```yaml
866
- # yaml-language-server: $schema=https://datreeio.github.io/CRDs-catalog/networking.gke.io/healthcheckpolicy_v1.json
867
- ```
44
+ ## Швидкий gate через conftest (Rego)
868
45
 
869
- 5. **Немає надійного публічного URL** не вигадуй URL і **не** використовуй `$schema=file:…` як заглушку (фальшива валідація). Залиш файл **без** рядка `# yaml-language-server: $schema=…` зовсім — `check-k8s` пропустить перевірку URL для таких файлів, а `lint-k8s` (kubeconform з `-ignore-missing-schemas`) усе ще покриє валідацію.
46
+ Підмножину пер-документних правил продубльовано як rego-полісі у **`npm/rules/k8s/policy/`** (запускається через **`bun run lint-rego`** для `*_test.rego` юніт-тестів і через **`npx @nitra/cursor fix k8s`** для прогону по реальних YAML). JS authoritative; rego швидкий gate для одиничного маніфеста.
870
47
 
871
- ## Багатодокументні YAML
48
+ Пакети (директорія в **`npm/rules/k8s/policy/`** → namespace → що перевіряє):
872
49
 
873
- Одна схема на файл; скрипт звіряє **перший** документ. Інші `kind` у тому ж файлі розділи файли або узгодь у рев’ю.
50
+ - **`manifest/`** `k8s.manifest` per-doc: заборона `kind: Ingress` і `autoscaling/v1`; заборонені GKE-анотації Service; resources.requests.cpu/memory в Deployment; дозволений образ hasura; RollingUpdate strategy; topologySpreadConstraints.
51
+ - **`base_manifest/`** → `k8s.base_manifest` — Deployment у `base/`: фіксовані cpu=`0.02` / memory=`128Mi`; непорожній `metadata.namespace` для namespaced kinds.
52
+ - **`base_kustomization/`** → `k8s.base_kustomization` — `base/kustomization.yaml`: непорожній `namespace:`; заборона `hpa.yaml`/`pdb.yaml` у `resources:`.
53
+ - **`gateway/`** → `k8s.gateway` — HTTPRoute/GRPCRoute/TCPRoute/TLSRoute/UDPRoute: backendRef на headless Service (суфікс `-hl`); заборона redundant `namespace` у backendRef; HealthCheckPolicy: `targetRef.name` з суфіксом `-hl`.
54
+ - **`hasura_configmap/`** → `k8s.hasura_configmap` — ConfigMap поруч з Hasura Deployment: обов'язкові env-ключі та значення.
55
+ - **`hasura_httproute/`** → `k8s.hasura_httproute` — HTTPRoute парний з Hasura Deployment: канон 4 правил (Exact redirect × 2 + PathPrefix rewrite + WebSocket).
56
+ - **`hpa_pdb/`** → `k8s.hpa_pdb` — структурна перевірка HPA (`autoscaling/v2`, spec.behavior, spec.metrics) і PDB (`policy/v1`, spec.selector.matchLabels).
57
+ - **`kustomization/`** → `k8s.kustomization` — `kustomization.yaml`: сортування `resources[]` і `patches[]`; заборона `remove`+`add` на той самий path (вимагає `replace`).
58
+ - **`network_policy/`** → `k8s.network_policy` — NetworkPolicy: apiVersion, spec.podSelector, policyTypes Ingress+Egress, superset-перевірка egress/ingress за snippet-файлами.
59
+ - **`svc_yaml/`** → `k8s.svc_yaml` — `svc.yaml`: `spec.type: ClusterIP`.
60
+ - **`svc_hl_yaml/`** → `k8s.svc_hl_yaml` — `svc-hl.yaml`: `metadata.name` з суфіксом `-hl`; `spec.clusterIP: None`.