@7n/rules 1.7.0 → 1.7.1

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 (110) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/package.json +1 -1
  3. package/rules/abie/http_route_base/http_route_base.mdc +23 -1
  4. package/rules/adr/madr_format/concern.json +1 -0
  5. package/rules/adr/madr_format/madr_format.mdc +119 -0
  6. package/rules/bun/bunfig/bunfig.mdc +5 -0
  7. package/rules/bun/lint-surface/concern.json +3 -0
  8. package/rules/bun/lint-surface/lint-surface.mdc +13 -0
  9. package/rules/capacitor/platforms/docs/main.md +1 -1
  10. package/rules/capacitor/platforms/main.mjs +5 -1
  11. package/rules/capacitor/platforms/platforms.mdc +108 -0
  12. package/rules/changelog/consistency/comparison-models.mdc +46 -0
  13. package/rules/changelog/consistency/consistency.mdc +35 -0
  14. package/rules/docker/main.mdc +235 -2
  15. package/rules/image-avif/avif_generation/avif_generation.mdc +14 -0
  16. package/rules/js/check/check.mdc +26 -0
  17. package/rules/js/file-extensions/concern.json +3 -0
  18. package/rules/js/file-extensions/file-extensions.mdc +12 -0
  19. package/rules/js/jscpd_config/jscpd_config.mdc +28 -0
  20. package/rules/js/knip/knip.mdc +15 -0
  21. package/rules/js/utils_imports/utils_imports.mdc +15 -0
  22. package/rules/js-bun-db/connection/concern.json +3 -0
  23. package/rules/js-bun-db/connection/connection.mdc +42 -0
  24. package/rules/js-bun-db/package_json/package_json.mdc +15 -1
  25. package/rules/js-bun-db/pg_format_identifiers/concern.json +3 -0
  26. package/rules/js-bun-db/pg_format_identifiers/pg_format_identifiers.mdc +104 -0
  27. package/rules/js-bun-db/safety/safety.mdc +458 -0
  28. package/rules/js-mssql/main.mdc +130 -0
  29. package/rules/js-mssql/mssql-tvp/concern.json +3 -0
  30. package/rules/js-mssql/mssql-tvp/mssql-tvp.mdc +77 -0
  31. package/rules/js-run/configmap/configmap.mdc +6 -0
  32. package/rules/js-run/jsconfig/jsconfig.mdc +23 -0
  33. package/rules/js-run/package_json/package_json.mdc +6 -0
  34. package/rules/js-run/project-structure/concern.json +3 -0
  35. package/rules/js-run/project-structure/project-structure.mdc +11 -0
  36. package/rules/js-run/runtime/runtime.mdc +170 -0
  37. package/rules/js-run/scope/concern.json +3 -0
  38. package/rules/js-run/scope/scope.mdc +11 -0
  39. package/rules/k8s/hasura_configmap/hasura_configmap.mdc +6 -0
  40. package/rules/k8s/hpa_pdb/hpa_pdb.mdc +134 -0
  41. package/rules/k8s/kubeconform/kubeconform.mdc +38 -0
  42. package/rules/k8s/kustomization/kustomization.mdc +73 -0
  43. package/rules/k8s/main.mdc +68 -0
  44. package/rules/k8s/manifest/manifest.mdc +37 -0
  45. package/rules/k8s/manifests/docs/fix-manifests.md +3 -1
  46. package/rules/k8s/manifests/fix-manifests.mjs +11 -0
  47. package/rules/k8s/manifests/main.mjs +28 -0
  48. package/rules/k8s/network_policy/network_policy.mdc +33 -0
  49. package/rules/nginx-default-tpl/http-route/concern.json +1 -0
  50. package/rules/nginx-default-tpl/http-route/http-route.mdc +54 -0
  51. package/rules/nginx-default-tpl/template/template.mdc +152 -0
  52. package/rules/php/tooling/tooling.mdc +7 -6
  53. package/rules/python/pyproject_toml/pyproject_toml.mdc +17 -1
  54. package/rules/python/tooling/tooling.mdc +9 -10
  55. package/rules/rego/main.mdc +14 -0
  56. package/rules/rust/check/check.mdc +16 -0
  57. package/rules/style/admin_table/admin_table.mdc +88 -0
  58. package/rules/style/admin_table/concern.json +7 -0
  59. package/rules/style/admin_table/docs/index.md +9 -0
  60. package/rules/style/admin_table/docs/main.md +14 -0
  61. package/rules/style/admin_table/main.mjs +46 -0
  62. package/rules/style/colors/colors.mdc +21 -0
  63. package/rules/style/colors/concern.json +3 -0
  64. package/rules/style/gap/concern.json +7 -0
  65. package/rules/style/gap/docs/index.md +9 -0
  66. package/rules/style/gap/docs/main.md +15 -0
  67. package/rules/style/gap/gap.mdc +22 -0
  68. package/rules/style/gap/main.mjs +51 -0
  69. package/rules/style/quasar/concern.json +3 -0
  70. package/rules/style/quasar/quasar.mdc +7 -0
  71. package/rules/style/quasar_fixes/concern.json +7 -0
  72. package/rules/style/quasar_fixes/docs/index.md +9 -0
  73. package/rules/style/quasar_fixes/docs/main.md +16 -0
  74. package/rules/style/quasar_fixes/main.mjs +57 -0
  75. package/rules/style/quasar_fixes/quasar_fixes.mdc +32 -0
  76. package/rules/tauri/tool_surface/concern.json +16 -0
  77. package/rules/tauri/tool_surface/docs/index.md +9 -0
  78. package/rules/tauri/tool_surface/docs/main.md +24 -0
  79. package/rules/tauri/tool_surface/main.mjs +145 -0
  80. package/rules/tauri/tool_surface/tool_surface.mdc +29 -0
  81. package/rules/test/vitest-api-conventions/concern.json +7 -0
  82. package/rules/test/vitest-api-conventions/docs/index.md +9 -0
  83. package/rules/test/vitest-api-conventions/docs/main.md +40 -0
  84. package/rules/test/vitest-api-conventions/main.mjs +186 -0
  85. package/rules/test/vitest-api-conventions/vitest-api-conventions.mdc +129 -0
  86. package/rules/text/cspell/cspell.mdc +18 -0
  87. package/rules/text/markdownlint/markdownlint.mdc +4 -0
  88. package/rules/text/run-dotenv-linter/run-dotenv-linter.mdc +17 -0
  89. package/rules/text/run-shellcheck/run-shellcheck.mdc +17 -0
  90. package/rules/text/run-v8r/run-v8r.mdc +23 -0
  91. package/rules/vue/composition-api/composition-api.mdc +82 -0
  92. package/rules/vue/composition-api/concern.json +3 -0
  93. package/rules/vue/main.mdc +1 -1
  94. package/rules/vue/nheader-layout/concern.json +3 -0
  95. package/rules/vue/nheader-layout/nheader-layout.mdc +171 -0
  96. package/rules/vue/packages/packages.mdc +56 -0
  97. package/rules/vue/quasar-ui/concern.json +3 -0
  98. package/rules/vue/quasar-ui/quasar-ui.mdc +32 -0
  99. package/rules/vue/structure/concern.json +3 -0
  100. package/rules/vue/structure/structure.mdc +101 -0
  101. package/rules/vue/testing/concern.json +3 -0
  102. package/rules/vue/testing/testing.mdc +40 -0
  103. package/rules/vue/tfm-translations/concern.json +7 -0
  104. package/rules/vue/tfm-translations/docs/main.md +29 -0
  105. package/rules/vue/tfm-translations/main.mjs +55 -0
  106. package/rules/vue/tfm-translations/tfm-translations.mdc +32 -0
  107. package/rules/vue/vite-config/concern.json +3 -0
  108. package/rules/vue/vite-config/vite-config.mdc +153 -0
  109. package/rules/vue/vite-env/concern.json +3 -0
  110. package/rules/vue/vite-env/vite-env.mdc +61 -0
@@ -20,3 +20,71 @@ alwaysApply: false
20
20
  ## Швидкий gate через conftest (Rego)
21
21
 
22
22
  Підмножину пер-документних правил продубльовано як rego-полісі у **`npm/rules/k8s/policy/`** (запускається через **`bun run lint-rego`** для `*_test.rego` юніт-тестів і через **`npx @7n/rules fix k8s`** для прогону по реальних YAML). JS authoritative; rego — швидкий gate для одиничного маніфеста.
23
+
24
+ ## Визначення схеми YAML (канон `yaml-language-server: $schema`)
25
+
26
+ Для кожного файлу `*.yaml` у шляху з `k8s`, якщо існує **публічна** схема — **перший рядок** — коментар-директива для [YAML Language Server](https://github.com/redhat-developer/yaml-language-server) з URL за `https://`:
27
+
28
+ ```yaml
29
+ # yaml-language-server: $schema=https://...
30
+ ```
31
+
32
+ Далі — вміст маніфесту без зайвого порожнього рядка між коментарем і YAML. Орієнтир для визначення URL — **перший документ** (до наступного `---`); багатодокументні файли — див. нижче.
33
+
34
+ **Modeline — опційний:** якщо для конкретного поєднання `apiVersion`/`kind` **немає** надійної публічної схеми (yannh/datree не покривають), залиш файл **без** рядка `# yaml-language-server: $schema=…`. **Заборонено** ставити `$schema=file:…` як заглушку.
35
+
36
+ **Виняток — modeline заборонено:** `apiVersion: alb.yc.io/v1alpha1`, `kind: HttpBackendGroup` (Yandex ALB) — рядка **`# yaml-language-server: $schema=…`** у файлі **не** має бути. Перший рядок — одразу YAML (`apiVersion:` тощо).
37
+
38
+ Перевірка й побудова URL — **`npm/rules/k8s/manifests/main.mjs`** (`expectedSchemaUrl`, `expectedSchemaUrlForTypedManifest`).
39
+
40
+ ### Правила визначення URL схеми
41
+
42
+ 1. **Ім'я** `kustomization.yaml` → `https://json.schemastore.org/kustomization.json`.
43
+
44
+ 2. **`apiVersion: v1`** → yannh, PIN набору схем **`v1.33.9-standalone-strict`**, ref репозиторію для raw URL — **`master`**:
45
+ `https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/<PIN>/<kind>-v1.json`
46
+ `<kind>`: літери в нижньому регістрі без роздільників між CamelCase (наприклад `Service` → `service`).
47
+
48
+ **`kind: Secret`** і **`type: kubernetes.io/basic-auth`** — той самий шаблон, **`secret-v1.json`**:
49
+
50
+ ```yaml
51
+ # yaml-language-server: $schema=https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.33.9-standalone-strict/secret-v1.json
52
+ ```
53
+
54
+ 3. **`apiVersion: group/version`** і **group** у переліку yannh-груп (`apps`, `autoscaling`, `batch`, `policy`, `networking.k8s.io`, `rbac.authorization.k8s.io`, `flowcontrol.apiserver.k8s.io`, `apiextensions.k8s.io` та ін. — повний перелік `YANNH_GROUPS` у `npm/rules/k8s/manifests/main.mjs`) → yannh:
55
+ `https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/<PIN>/<kind>-<group-частина>-<version>.json`
56
+ де **`<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-` у назві.
57
+
58
+ 4. **Інакше** (CRD, Gateway API тощо) → [datreeio/CRDs-catalog](https://github.com/datreeio/CRDs-catalog). Типово для `$schema` у редакторі — **GitHub Pages**:
59
+ `https://datreeio.github.io/CRDs-catalog/<group>/<kind>_<version>.json`
60
+ (`<kind>` — лише літери та цифри в нижньому регістрі, без роздільників між CamelCase.)
61
+
62
+ **Виняток — `InfisicalSecret`:** `apiVersion: secrets.infisical.com/v1alpha1`, `kind: InfisicalSecret` — канонічний modeline через **raw** на гілці **`main`** (явний запис у таблиці `EXPLICIT_K8S_SCHEMAS`):
63
+
64
+ ```yaml
65
+ # yaml-language-server: $schema=https://raw.githubusercontent.com/datreeio/CRDs-catalog/main/secrets.infisical.com/infisicalsecret_v1alpha1.json
66
+ ```
67
+
68
+ **Приклад (Gateway API):** `apiVersion: gateway.networking.k8s.io/v1`, `kind: HTTPRoute` — канон **`v1`** (не `v1beta1`, див. **`gateway.mdc`**):
69
+
70
+ ```yaml
71
+ # yaml-language-server: $schema=https://datreeio.github.io/CRDs-catalog/gateway.networking.k8s.io/httproute_v1.json
72
+ ```
73
+
74
+ **Приклад (GKE):** `apiVersion: networking.gke.io/v1`, `kind: HealthCheckPolicy`:
75
+
76
+ ```yaml
77
+ # yaml-language-server: $schema=https://datreeio.github.io/CRDs-catalog/networking.gke.io/healthcheckpolicy_v1.json
78
+ ```
79
+
80
+ 5. **Немає надійного публічного URL** — не вигадуй URL і **не** використовуй `$schema=file:…`. Залиш файл **без** рядка `# yaml-language-server: $schema=…` зовсім.
81
+
82
+ ## Багатодокументні YAML
83
+
84
+ Одна схема на файл; перевірка $schema-modeline звіряє **перший** документ (до наступного `---`). Інші `kind` у тому ж файлі — розділи файли або узгодь у рев'ю.
85
+
86
+ ## Розширення файлів: лише `.yaml`
87
+
88
+ Усі маніфести під **`k8s`** (визначаються відносно кореня репо), включно з **`kustomization.yaml`**, — лише **`.yaml`**. Розширення **`.yml`** під `k8s` заборонене: `check k8s` падає з порадою перейменувати файл на `.yaml` (перевірка — `npm/rules/k8s/manifests/main.mjs`, повідомлення `розширення .yml — перейменуй на .yaml`).
89
+
90
+ Цей канон стосується лише дерев **`k8s`** — під **`.github/workflows/`** і **`.github/actions/`** діє протилежний канон (`.yml`), див. **`ga.mdc`**.
@@ -15,3 +15,40 @@ Rego-пакет: `k8s.manifest`
15
15
  - `kind: Deployment` — канонічний запис у `spec.template.spec.topologySpreadConstraints`: `maxSkew: 1`, `topologyKey: kubernetes.io/hostname`, `whenUnsatisfiable: ScheduleAnyway`, `labelSelector.matchLabels.app: <app-label>` (перевіряється лише для Deployment з міткою `app` у `spec.selector.matchLabels`)
16
16
 
17
17
  **Примітка:** cross-file логіка (Kustomize-резолюція, парність svc/svc-hl, HPA/PDB за каталогом, schema modeline) лишається у JS `rules/k8s/fix.mjs`.
18
+
19
+ ## Deployment: `resources.requests` поза `base/`
20
+
21
+ Базовий шар (`…/k8s/…/base/…`) має жорстко фіксовані `resources.requests.cpu`/`memory` — див. **`base_manifest.mdc`**. Поза `base` (overlays, окремі каталоги) ця перевірка (рядок 12 вище) вимагає лише непорожні `resources.requests.cpu` і `resources.requests.memory` — конкретне значення не фіксується.
22
+
23
+ Якщо ще не підібрано власні ліміти під сервіс, орієнтир для **`requests`**:
24
+
25
+ ```yaml
26
+ resources:
27
+ requests:
28
+ cpu: '0.5'
29
+ memory: '512Mi'
30
+ ```
31
+
32
+ У прод-оверлеях підіймай **`cpu` / `memory`** до реального споживання через **`patches`**:
33
+
34
+ ```yaml title="k8s/prod/kustomization.yaml (фрагмент)"
35
+ patches:
36
+ - target:
37
+ kind: Deployment
38
+ name: backend-api
39
+ patch: |-
40
+ - op: replace
41
+ path: /spec/template/spec/containers/0/resources/requests/cpu
42
+ value: '500m'
43
+ - op: replace
44
+ path: /spec/template/spec/containers/0/resources/requests/memory
45
+ value: 1Gi
46
+ ```
47
+
48
+ **`imagePullPolicy`** — цю перевірку скрипт **не** виконує.
49
+
50
+ ## Batch-ресурси: `apiVersion: batch/v1beta1` → `batch/v1`
51
+
52
+ Ресурси **batch** (наприклад **CronJob**, **Job**): застаріле **`apiVersion: batch/v1beta1`** у файлах під **`k8s`** під час `check k8s` автоматично переписується на **`apiVersion: batch/v1`** (рядково, зі збереженням відступів/лапок). Детектор — `detectBatchV1beta1InK8sYamlFiles`, автофікс — T0-патерн `k8s-manifests-batch-v1beta1-apiversion` (обидва в **`npm/rules/k8s/manifests/main.mjs`** / **`fix-manifests.mjs`**), той самий механізм, що й `gateway-httproute-v1beta1` вище по домену gateway (див. **`gateway.mdc`**).
53
+
54
+ `apiVersion: autoscaling/v1` (заборонено для HPA, потрібен `autoscaling/v2`) — рядок 10 вище і **`hpa_pdb.mdc`**.
@@ -3,7 +3,7 @@ type: JS Module
3
3
  title: fix-manifests.mjs
4
4
  resource: npm/rules/k8s/manifests/fix-manifests.mjs
5
5
  docgen:
6
- crc: 31bc471b
6
+ crc: 7245cdbe
7
7
  ---
8
8
 
9
9
  ## Огляд
@@ -29,6 +29,8 @@ T0-autofix для `k8s/manifests`: детерміновані правки Kuber
29
29
  що й детектор (kind → name → namespace → path).
30
30
  - **gateway-httproute-v1beta1** — піднімає `apiVersion` HTTPRoute з `gateway.networking.k8s.io/v1beta1`
31
31
  до `v1` разом із відповідним `$schema`-modeline.
32
+ - **batch-v1beta1-apiversion** — переписує `apiVersion: batch/v1beta1` (CronJob/Job) на `batch/v1`,
33
+ рядково, зі збереженням відступів і лапок.
32
34
 
33
35
  ## Публічний API
34
36
 
@@ -4,6 +4,7 @@
4
4
  * T0-autofix для `k8s/manifests` — детерміновані правки без LLM, керовані structured
5
5
  * `data` детектора (#3 fix-hints). Покриває механічні родини порушень k8s.mdc:
6
6
  * - `gateway-httproute-v1beta1` — apiVersion v1beta1 → v1 (+ $schema-modeline);
7
+ * - `batch-v1beta1-apiversion` — apiVersion batch/v1beta1 → batch/v1 (CronJob/Job);
7
8
  * - `schema-modeline-first` — перемістити `# yaml-language-server: $schema=…` у перший рядок;
8
9
  * - `kustomization-patches-sort` — впорядкувати `patches[]` (реюз детекторних sort-ключів);
9
10
  * - `deployment-strategy` — проставити канонічний `spec.strategy` RollingUpdate.
@@ -21,6 +22,7 @@ import {
21
22
  compareStringTuplesEn,
22
23
  kustomizationPatchSortKey,
23
24
  loadSnippetSpec,
25
+ replaceBatchV1beta1ApiVersionInYamlText,
24
26
  replaceGatewayHttpRouteV1beta1ApiVersionInYamlText,
25
27
  snippetNameForKind
26
28
  } from './main.mjs'
@@ -190,6 +192,15 @@ export const patterns = [
190
192
  },
191
193
  n => `gateway HTTPRoute apiVersion v1beta1 → v1: ${n} файл(ів)`
192
194
  ),
195
+ fileTransformPattern(
196
+ 'k8s-manifests-batch-v1beta1-apiversion',
197
+ 'batch-v1beta1-apiversion',
198
+ content => {
199
+ const { changed, content: next } = replaceBatchV1beta1ApiVersionInYamlText(content)
200
+ return changed ? next : null
201
+ },
202
+ n => `batch apiVersion v1beta1 → v1: ${n} файл(ів)`
203
+ ),
193
204
  fileTransformPattern(
194
205
  'k8s-manifests-schema-modeline-first',
195
206
  'schema-modeline-first',
@@ -1795,6 +1795,32 @@ async function detectGatewayHttpRouteV1beta1InK8sYamlFiles(yamlFiles, root, fail
1795
1795
  }
1796
1796
  }
1797
1797
 
1798
+ /**
1799
+ * Read-only детектор: знаходить файли з `apiVersion: batch/v1beta1` (застаріле для CronJob/Job)
1800
+ * і реєструє violation з `data: { kind: 'batch-v1beta1-apiversion' }` для T0-фіксу.
1801
+ * @param {string[]} yamlFiles абсолютні шляхи до YAML-файлів
1802
+ * @param {string} root корінь репозиторію
1803
+ * @param {(msg: string, opts?: object) => void} fail колбек реєстрації порушення
1804
+ * @returns {Promise<void>}
1805
+ */
1806
+ async function detectBatchV1beta1InK8sYamlFiles(yamlFiles, root, fail) {
1807
+ for (const abs of yamlFiles) {
1808
+ const rel = (relative(root, abs) || abs).replaceAll('\\', '/')
1809
+ let raw
1810
+ try {
1811
+ raw = await readFile(abs, 'utf8')
1812
+ } catch {
1813
+ continue
1814
+ }
1815
+ if (!BATCH_V1BETA1_API_VERSION_LINE_RE.test(raw)) continue
1816
+ fail(`${rel}: apiVersion: batch/v1beta1 застаріло — оновіть до batch/v1 (k8s.mdc)`, {
1817
+ reason: 'batch-v1beta1-apiversion',
1818
+ file: rel,
1819
+ data: { kind: 'batch-v1beta1-apiversion' }
1820
+ })
1821
+ }
1822
+ }
1823
+
1798
1824
  /**
1799
1825
  * Прибирає BOM і ділить на рядки.
1800
1826
  * @param {string} content вміст файлу
@@ -6566,6 +6592,8 @@ export async function lint(ctx) {
6566
6592
 
6567
6593
  await detectGatewayHttpRouteV1beta1InK8sYamlFiles(yamlFiles, root, fail)
6568
6594
 
6595
+ await detectBatchV1beta1InK8sYamlFiles(yamlFiles, root, fail)
6596
+
6569
6597
  // Plan B: пер-документні структурні правила — у rego-полісі `npm/policy/k8s/*`,
6570
6598
  // викликаємо одним батчем на namespace через runConftestBatch. JS нижче робить
6571
6599
  // лише cross-file orchestration, modeline та FS-existence перевірки.
@@ -20,3 +20,36 @@ Rego-пакет: `k8s.network_policy`
20
20
  **Передача snippets:** через `--data` при виклику conftest (JS передає `templateData` для `runConftestBatch`).
21
21
 
22
22
  **Примітка:** cross-file визначення `metadata.name` воркнавантаження і мітки `app` — JS (`validateNetworkPolicyForWorkload`).
23
+
24
+ ## Обов'язковість і авто-створення
25
+
26
+ Для **кожного** workload (**Deployment**, **StatefulSet**, **DaemonSet**, **Job**, **CronJob**) під `k8s` обов'язковий **NetworkPolicy** у **`base/networkpolicy.yaml`** поруч з workload-маніфестом (multi-doc через `---`, якщо workload-ів кілька). Відсутні документи **`check k8s`** створює автоматично (канон і snippet-структура — вище) і додає `networkpolicy.yaml` у `base/kustomization.yaml` `resources:`.
27
+
28
+ Перевірка/генерація — **`npm/rules/k8s/manifests/main.mjs`** (`validateNetworkPoliciesForK8sWorkloads`, `regenerateLegacyNetworkPolicyDocsInFile`).
29
+
30
+ ## HTTPRoute → NetworkPolicy ingress (GCLB + Envoy)
31
+
32
+ Якщо в каталозі workload є **`HTTPRoute`** (Gateway API, див. **`gateway.mdc`**) з **`backendRef`** на **`<workload>-hl`** Service, **`check k8s`** автоматично додає в NetworkPolicy цього workload **ingress-правило** з фіксованим набором CIDR-ів і **TCP-портами з `backendRefs[].port`** (дедуп, відсортовано за зростанням).
33
+
34
+ Без цього правила трафік від **GKE Gateway** (Envoy proxy-only subnet) і **Google health checks** блокується базовим NetworkPolicy.
35
+
36
+ CIDR-набір зафіксовано (`npm/rules/k8s/manifests/main.mjs`, `NETWORK_POLICY_GCLB_INGRESS_FROM`):
37
+
38
+ - `35.191.0.0/16` — GCP HC global
39
+ - `130.211.0.0/22` — GCP HC global (legacy)
40
+ - `10.0.0.0/8` — широкий range, покриває proxy-only subnets усіх регіонів GKE
41
+
42
+ ```yaml title="k8s/base/networkpolicy.yaml — workload з HTTPRoute (з GCLB ingress)"
43
+ spec:
44
+ ingress:
45
+ - from:
46
+ - podSelector: {}
47
+ - from: # auto-added by check k8s for HTTPRoute-paired workloads
48
+ - ipBlock: { cidr: 35.191.0.0/16 }
49
+ - ipBlock: { cidr: 130.211.0.0/22 }
50
+ - ipBlock: { cidr: 10.0.0.0/8 }
51
+ ports:
52
+ - { protocol: TCP, port: 8080 }
53
+ ```
54
+
55
+ Алгоритм: функція `collectHttpRouteIngressForWorkload` у **`npm/rules/k8s/manifests/main.mjs`** (mapping `backendRef.name` → `Service.metadata.name` → `service.spec.selector.matchLabels.app`).
@@ -0,0 +1 @@
1
+ {"$schema": "https://unpkg.com/@7n/rules/schemas/concern.json"}
@@ -0,0 +1,54 @@
1
+ ## HTTPRoute в k8s — редирект і backendRefs
2
+
3
+ Якщо в `default.conf.template` були `proxy_pass`-секції — їхню логіку потрібно перенести
4
+ до HTTPRoute в k8s. `default.conf.template` **не повинен** містити жодного proxy (див.
5
+ `nginx-default-tpl/template/template.mdc`).
6
+
7
+ HTTPRoute має містити два правила:
8
+
9
+ 1. **Exact → RequestRedirect 301 (https)** — редирект з `/$PUBLIC_PATH` на `/$PUBLIC_PATH/`
10
+ 2. **PathPrefix → backendRefs:8080** — проксі трафіку на сервіс
11
+
12
+ ```yaml
13
+ spec:
14
+ rules:
15
+ - matches:
16
+ - path:
17
+ type: Exact
18
+ value: /$PUBLIC_PATH
19
+ filters:
20
+ - type: RequestRedirect
21
+ requestRedirect:
22
+ scheme: https
23
+ path:
24
+ type: ReplaceFullPath
25
+ replaceFullPath: /$PUBLIC_PATH/
26
+ statusCode: 301
27
+ - matches:
28
+ - path:
29
+ type: PathPrefix
30
+ value: /$PUBLIC_PATH/
31
+ backendRefs:
32
+ - name: $SERVICE_NAME
33
+ port: 8080
34
+ ```
35
+
36
+ де `$PUBLIC_PATH` підставляється з ini-файлу dev-середовища, а для інших середовищ — через `kustomization.yaml`.
37
+ `$SERVICE_NAME` — ім'я k8s-сервісу, що приймає трафік на порту 8080.
38
+
39
+ Патерн розпізнається функцією `httpRouteMatchesNginxDefaultTpl` у
40
+ `npm/rules/nginx-default-tpl/template/main.mjs`:
41
+
42
+ - перше правило має `path.type: Exact` і фільтр `RequestRedirect` з `scheme: https`, `path.type: ReplaceFullPath`, `statusCode: 301`
43
+ - друге правило має `path.type: PathPrefix` і `backendRefs` з `port: 8080`
44
+
45
+ **Статус: реалізовано, але НЕ підключено.** `httpRouteMatchesNginxDefaultTpl` визначена в коді,
46
+ але не викликається з `lint()` у `template/main.mjs` — перевірка HTTPRoute зараз **не входить**
47
+ в активний прогін `nginx-default-tpl`. Причина: щоб увімкнути її безпечно, потрібно ще визначити,
48
+ *коли саме* HTTPRoute з таким патерном є обов'язковим (навряд чи для будь-якого HTTPRoute в
49
+ репозиторії — лише для того, що відповідає сервісу з `default.conf.template`, з якого прибрали
50
+ proxy), і знайти/розпарсити відповідні YAML-файли (на кшталт інфраструктури
51
+ `npm/rules/abie/lib/http-route.mjs` + `findK8sYamlFiles`). Без цього умова спрацьовування
52
+ неоднозначна, а вмикання наосліп ризикує хибними спрацюваннями на репозиторіях зі своїми
53
+ валідними HTTPRoute-патернами. Перш ніж покладатись на автоматичний контроль цього патерну
54
+ або вмикати його в `lint()`, звір з поточним станом `lint()` у `template/main.mjs`.
@@ -0,0 +1,152 @@
1
+ ## Канонічна структура default.conf.template
2
+
3
+ `default.conf.template` — єдиний nginx-конфіг для фронтенду зі статичними файлами.
4
+ Він **не повинен** містити жодних `proxy_pass`, `proxy_redirect`, `proxy_set_header`,
5
+ `fastcgi_pass`, `grpc_pass`, `uwsgi_pass`. Усю проксі-логіку потрібно винести в HTTPRoute (k8s).
6
+
7
+ Канонічний вигляд файлу:
8
+
9
+ ```nginx
10
+ server_tokens off;
11
+ port_in_redirect off;
12
+ client_max_body_size 0;
13
+ client_body_buffer_size 512M;
14
+
15
+ server {
16
+ listen 8080;
17
+ server_name _;
18
+
19
+ # disable all log
20
+ access_log off;
21
+ # `error_log off;` — НЕ валідний nginx: "off" трактується як ім'я файлу (/etc/nginx/off)
22
+ # і падає під readOnlyRootFilesystem. /dev/null — writable device.
23
+ error_log /dev/null crit;
24
+
25
+ # This would be the directory where your Vite app's static files are stored at
26
+ root /usr/share/nginx/html;
27
+
28
+ location /healthz {
29
+ add_header Content-Type text/plain;
30
+ access_log off;
31
+ return 200 "healthy";
32
+ }
33
+
34
+ # Без gz стиснених файлів, 1 year is 31536000 seconds
35
+ location ~ ^$PUBLIC_PATH/(.+\.(?:gif|jpe?g|png|ico|woff2|xlsx))$ {
36
+ alias /usr/share/nginx/html/$1;
37
+ add_header 'Cache-Control' "public,max-age=31536000,immutable";
38
+ }
39
+
40
+ # С gz стисненими файлами, 1 year is 31536000 seconds
41
+ location ~ ^$PUBLIC_PATH/(.+\.(?:svg|js|css|ttf|map|xml|webmanifest|wasm))$ {
42
+ alias /usr/share/nginx/html/$1;
43
+ add_header 'Cache-Control' "public,max-age=31536000,immutable";
44
+
45
+ # дозволяє віддавати замість звичайного файлу попередньо стиснутий файл з таким же ім'ям та з розширенням ".gz"
46
+ gzip_static on;
47
+ }
48
+
49
+ location $PUBLIC_PATH/ {
50
+ index index.html;
51
+ alias /usr/share/nginx/html/;
52
+
53
+ # eliminates the step of copying the data into the buffer and enables direct copying data from one file descriptor to another.
54
+ sendfile on;
55
+ # to prevent one fast connection from entirely occupying the worker process
56
+ sendfile_max_chunk 512k;
57
+ # to send HTTP response headers in one packet right after the chunk of data has been obtained by sendfile().
58
+ tcp_nopush on;
59
+
60
+ # дозволяє віддавати замість звичайного файлу попередньо стиснутий файл з таким же ім'ям та з розширенням ".gz"
61
+ gzip_static on;
62
+
63
+ try_files $uri $uri/ /index.html =404;
64
+ }
65
+ }
66
+ ```
67
+
68
+ Обов'язкові вимоги до структури (перевіряються автоматично, `nginxTemplateViolations` у
69
+ `npm/rules/nginx-default-tpl/template/main.mjs`):
70
+
71
+ - `server_tokens off` — приховує версію nginx у відповідях
72
+ - `port_in_redirect off` — виключає порт з redirect-заголовків
73
+ - `client_max_body_size 0` — без ліміту тіла запиту
74
+ - `client_body_buffer_size 512M`
75
+ - `listen 8080`
76
+ - `server_name _` — catch-all
77
+ - `access_log off`
78
+ - `error_log /dev/null crit` (НЕ `error_log off;` — це некоректно в nginx)
79
+ - `root /usr/share/nginx/html`
80
+ - `location /healthz` з `return 200` і рядком `healthy`
81
+ - location для статики без gzip (`gif|jpeg|png|ico|woff2|xlsx`) з `Cache-Control: max-age=31536000,immutable`
82
+ - location для статики з gzip (`svg|js|css|ttf|map|xml|webmanifest|wasm`) з `gzip_static on`
83
+ - `gzip_static on` щонайменше двічі (два location зі стисненням)
84
+ - `$PUBLIC_PATH` у location — плейсхолдер підставляється через envsubst
85
+ - `sendfile on`, `sendfile_max_chunk 512k`, `tcp_nopush on`
86
+ - `try_files $uri $uri/ /index.html =404`
87
+ - **Відсутність** будь-якого `proxy_pass` / `fastcgi_pass` / `grpc_pass` / `uwsgi_pass`
88
+
89
+ ## Конфігураційні *.ini файли і envsubst
90
+
91
+ Поруч з `default.conf.template` мають бути конфігураційні файли `*.ini` для кожного
92
+ середовища (наприклад, `values-dev.ini`, `values-prod.ini`).
93
+
94
+ Формат ini-файлу:
95
+
96
+ ```ini
97
+ PUBLIC_PATH=/app
98
+ SERVICE_NAME=my-frontend
99
+ ```
100
+
101
+ Кожна змінна з `*.ini` **повинна використовуватись** у `default.conf.template` як `$KEY`
102
+ (через envsubst). Якщо у `*.ini` є ключ, що відсутній у шаблоні — його треба вилучити з ini
103
+ або додати в шаблон.
104
+
105
+ Перевірки (автоматичні, `checkTemplateFile`/`iniKeysMissingInTemplate` у
106
+ `npm/rules/nginx-default-tpl/template/main.mjs`):
107
+
108
+ - Поруч з кожним `default.conf.template` є щонайменше один `*.ini` файл
109
+ - Кожен ключ з `*.ini` (рядки виду `KEY=value`, без коментарів `#`/`;` і порожніх рядків)
110
+ присутній у шаблоні як `$KEY`
111
+
112
+ ## Dockerfile — стиснення статики та envsubst шаблону
113
+
114
+ Dockerfile потребує двох кроків, пов'язаних з nginx-шаблоном.
115
+
116
+ ### Стиснення статики (gzip_static)
117
+
118
+ Щоб nginx міг використовувати `gzip_static on`, файли необхідно стиснути під час збирання образу:
119
+
120
+ ```dockerfile
121
+ RUN find /usr/share/nginx/html -type f \( \
122
+ -name '*.js' -o -name '*.css' -o -name '*.map' -o -name '*.xml' -o \
123
+ -name '*.webmanifest' -o -name '*.html' -o -name '*.wasm' -o -name '*.ttf' \
124
+ \) -exec gzip -k {} +
125
+ ```
126
+
127
+ Прапорець `-k` зберігає оригінальний файл поруч зі стиснутим `.gz`.
128
+
129
+ ### envsubst для default.conf.template
130
+
131
+ Для підстановки змінних середовища у шаблон:
132
+
133
+ ```dockerfile
134
+ # 1) Витягнути імена змінних з ini (ігноруємо коментарі/порожні)
135
+ # 2) Зробити список для envsubst: $NAMESPACE $NAMESPACE2 ...
136
+ # 3) Підвантажити значення з ini і підставити лише їх
137
+ RUN NAMES=$(sed -nE '/^\s*[#;]/d; /^\s*$/d; s/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=.*/\1/p' /tpl/values-$BRANCH.ini) && \
138
+ VARS=$(printf '%s\n' $NAMES | awk '{printf "$%s ", $0}') && \
139
+ export $(grep -v '^#' /tpl/values-$BRANCH.ini | xargs) && \
140
+ envsubst "$VARS" < /tpl/default.conf.template > /app/default.conf
141
+ ```
142
+
143
+ `/tpl/values-$BRANCH.ini` і `/tpl/default.conf.template` — приклад шляхів у контейнері;
144
+ конкретні шляхи залежать від проєкту, важливі лише сама послідовність кроків і імена
145
+ директив (`envsubst`, `default.conf.template`).
146
+
147
+ Перевірки (автоматичні, `checkDockerfiles` у `npm/rules/nginx-default-tpl/template/main.mjs`,
148
+ викликається один раз для всього репозиторію — не окремо по кожному `default.conf.template`):
149
+
150
+ - Десь у репозиторії є Dockerfile/Containerfile, що містить `find … /usr/share/nginx/html … gzip -k` — крок стиснення статики
151
+ - Десь у репозиторії є Dockerfile/Containerfile, що містить `envsubst` і посилання на `default.conf.template`
152
+ - Якщо в репозиторії є `default.conf.template`, але жодного Dockerfile / Containerfile не знайдено — це помилка
@@ -56,11 +56,12 @@ composer audit
56
56
 
57
57
  ## Запуск lint-php
58
58
 
59
- `composer`-інструменти не мають єдиного CLI, який сам обходить репозиторій, тому php-лінт делегується у JS-скрипт-обгортку. Запуск — через **`n-rules lint php`** (CI — `--no-fix`); окремого `package.json`-скрипта немає.
59
+ `composer`-інструменти не мають єдиного CLI, який сам обходить репозиторій, тому php-лінт делегується у JS-детектори. Запуск — через **`n-rules lint php`** (CI — `--no-fix`); окремого `package.json`-скрипта немає.
60
60
 
61
- Скрипт `run-php.mjs`:
61
+ Три концерни:
62
62
 
63
- - якщо `composer.json` у корені відсутній вихід 0 (перевірка пропущена);
64
- - якщо `composer.json` є, але `composer` не знайдено в PATH — це помилка;
65
- - `composer audit` — обовʼязковий;
66
- - `vendor/bin/php-cs-fixer`, `vendor/bin/phpcs`, `vendor/bin/phpstan`, `vendor/bin/psalm` — запускаються лише якщо встановлені (інакше крок пропускається з повідомленням).
63
+ - `npm/rules/php/project/main.mjs` `composer audit` (обовʼязковий) + PHPStan + Psalm; project-wide, поза delta-планом (лише `n-rules lint --full` або scoped `n-rules lint php`);
64
+ - `npm/rules/php/phpcs/main.mjs` `vendor/bin/phpcs --standard=Security`; per-file;
65
+ - `npm/rules/php/cs_fixer/main.mjs` — `vendor/bin/php-cs-fixer fix --dry-run --diff`; per-file.
66
+
67
+ Якщо `composer.json` у корені відсутній — усі концерни виходять без порушень (проєкт не PHP). Якщо `composer.json` є, але `composer` не знайдено в PATH — це помилка. `vendor/bin/php-cs-fixer`, `vendor/bin/phpcs`, `vendor/bin/phpstan`, `vendor/bin/psalm` — запускаються лише якщо встановлені (інакше крок пропускається з повідомленням).
@@ -8,6 +8,22 @@ Rego-пакет: `python.pyproject_toml`
8
8
 
9
9
  **1. Заборона Poetry** — перевіряє заборонені під-таблиці `[tool.*]` за deny-template (drift-safe через `--data template`): [pyproject.toml.deny.toml](./template/pyproject.toml.deny.toml)
10
10
 
11
- **2. PEP 621** — `[project].name` і `[project].version` мають бути непорожніми рядками.
11
+ **2. PEP 621** — метадані проєкту мають бути у секції **`[project]`** (PEP 621), а **не** в `[tool.poetry]`. Обовʼязкові поля:
12
+
13
+ - `[project].name` — назва пакета (непорожній рядок);
14
+ - `[project].version` — статична версія (непорожній рядок);
15
+ - `[project].requires-python` — мінімальна версія Python (наприклад `>=3.12`);
16
+ - `[project].dependencies` — залежності (навіть порожній список `[]`).
12
17
 
13
18
  Канонічний цільовий вигляд: [pyproject.toml.snippet.toml](./template/pyproject.toml.snippet.toml)
19
+
20
+ ## Залежності та lock-файл (uv)
21
+
22
+ Залежності додаються командою `uv add <pkg>`; dev-залежності — `uv add --dev <pkg>`. Lock-файл — **`uv.lock`** (іде в коміт, оновлюється командою `uv lock`).
23
+
24
+ ## Міграція з Poetry на uv
25
+
26
+ 1. Прибери `[tool.poetry]` і `poetry.lock` / `poetry.toml`.
27
+ 2. Перенеси метадані в `[project]` (name, version, requires-python, dependencies) за PEP 621.
28
+ 3. Згенеруй lock: `uv lock` → `uv.lock`.
29
+ 4. Dev-залежності: `uv add --dev ruff mypy …`.
@@ -1,6 +1,6 @@
1
1
  ## Перевірка наявності uv-toolchain і відсутності Poetry
2
2
 
3
- JS-перевірка (`js/tooling.mjs`) запускається, якщо в корені репо є `pyproject.toml`. Перевіряє:
3
+ JS-перевірка (`npm/rules/python/tooling/main.mjs`) запускається, якщо в корені репо є `pyproject.toml`. Перевіряє:
4
4
 
5
5
  - `uv.lock` — має існувати (зафіксувати командою `uv lock`);
6
6
  - `poetry.lock` / `poetry.toml` — мають **бути відсутніми** (Poetry заборонено);
@@ -9,15 +9,14 @@ JS-перевірка (`js/tooling.mjs`) запускається, якщо в
9
9
 
10
10
  Якщо `pyproject.toml` відсутній — перевірка пропускається з кодом 0 (проєкт не Python).
11
11
 
12
- ## lint-python: JS-скрипт-обгортка
12
+ ## lint-python: JS-детектори за concern
13
13
 
14
- Інструменти uv-екосистеми не мають єдиного CLI-обходу репо, тому python-лінт делегується у JS-скрипт-обгортку. Запуск — через **`n-rules lint python`** (CI — `--no-fix`); окремого `package.json`-скрипта немає.
14
+ Інструменти uv-екосистеми не мають єдиного CLI-обходу репо, тому python-лінт делегується у JS-детектори. Запуск — через **`n-rules lint python`** (CI — `--no-fix`); окремого `package.json`-скрипта немає.
15
15
 
16
- Скрипт `rules/python/lint/lint.mjs`:
16
+ Три концерни (кожен — окремий per-file чи project-wide детектор):
17
17
 
18
- - якщо `pyproject.toml` у корені відсутній вихід 0 (перевірка пропущена);
19
- - якщо `pyproject.toml` є, але `uv` не знайдено в PATH це помилка;
20
- - `uv lock --check` і `uv sync --frozen` — обовʼязкові;
21
- - `uv run ruff check --fix .` + `uv run ruff format .` — auto-fix (мутують робоче дерево, як `markdownlint-cli2 --fix` у lint-text);
22
- - `uv run mypy .`статична перевірка типів;
23
- - усі `ruff`/`mypy`-кроки запускаються лише якщо інструмент доступний у середовищі (інакше крок пропускається з повідомленням).
18
+ - `npm/rules/python/project/main.mjs` `uv lock --check` + `uv sync --frozen` + ліцензійний аудит (`pip-licenses`); project-wide, поза delta-планом (лише `n-rules lint --full` або scoped `n-rules lint python`);
19
+ - `npm/rules/python/ruff/main.mjs` (+ `fix-ruff.mjs`) `ruff check` + `ruff format --check` (детект), `ruff check --fix` + `ruff format` (autofix); per-file;
20
+ - `npm/rules/python/mypy/main.mjs` `mypy` (лише детект, немає autofix); per-file.
21
+
22
+ Якщо `pyproject.toml` у корені відсутній — усі концерни виходять без порушень (проєкт не Python). Якщо `pyproject.toml` є, але `uv` не знайдено в PATH це помилка. `ruff`/`mypy`-кроки запускаються лише якщо інструмент доступний у середовищі (інакше крок пропускається з повідомленням).
@@ -9,6 +9,20 @@ alwaysApply: false
9
9
 
10
10
  Синтаксичні правила (`rego.v1`, `import rego.v1`, заборона legacy v0) — у `conftest.mdc` (alwaysApply). Цей файл — про **інструментарій**: VS Code, лінтери, форматування.
11
11
 
12
+ ## Лінт-пайплайн (opa → regal → conftest)
13
+
14
+ ```bash
15
+ n-rules lint rego
16
+ ```
17
+
18
+ Цілі — `npm/rules/` (рекурсивно знаходить `.rego` у `<rule>/<concern>/`). Три незалежні per-file-детектори (кожен свій concern), у такому порядку:
19
+
20
+ 1. **`opa check --strict`** (`npm/rules/rego/opa_check/`) — компіляція з типами і строгим режимом: ловить мертвий код, неоднозначні правила, незадекларовані змінні. Зупиняє відповідний крок при помилці.
21
+ 2. **`regal lint`** (`npm/rules/rego/regal/`) — статичний лінтер: v0-синтаксис, неявні set-rules, відхилення від `rego.v1`, bugs/idiomatic/performance-правила.
22
+ 3. **`conftest verify`** (`npm/rules/rego/conftest_verify/`) — виконує `test_*` правила у `*_test.rego`. Крос-package (`import data.<pkg>`), тож коректний лише на всьому `npm/rules`; не входить у delta-план — спрацьовує лише через `n-rules lint --full` або scoped `n-rules lint rego`. Якщо `conftest` відсутній у `PATH` — пропускається без помилки.
23
+
24
+ `opa` і `regal` — лише у `PATH`, **не** додавай у `dependencies` / `devDependencies` (toolchain-інструменти, як і в інших мовних правилах цього репо). Це механічно перевіряється Rego-gate'ом `rego.package_json` (`npm/rules/rego/package_json/`) — deny, якщо `opa` чи `regal` з'являються у будь-якій секції залежностей `package.json`.
25
+
12
26
  ## Швидкий gate через conftest
13
27
 
14
28
  | Пакет | Ціль | Що перевіряє |
@@ -0,0 +1,16 @@
1
+ ## Лінт Rust: rustfmt + clippy + cargo-deny
2
+
3
+ **rustfmt** ([rust-lang/rustfmt](https://github.com/rust-lang/rustfmt)) — форматер; **clippy** ([rust-lang/rust-clippy](https://github.com/rust-lang/rust-clippy)) — лінтер; **cargo-deny** — перевірка ліцензій залежностей. Запуск — через **`n-rules lint rust`** (detector `npm/rules/rust/check/main.mjs`, autofix `npm/rules/rust/check/fix-check.mjs`):
4
+
5
+ - **read-only (детект, CI):** `cargo fmt --all -- --check` → `cargo clippy --all-targets --all-features -- -D warnings` → (за наявності `deny.toml` і встановленого `cargo-deny`) `cargo deny check licenses`.
6
+ - **`fix` (локально):** `cargo fmt --all` (реальне форматування) і, якщо `deny.toml` відсутній та `cargo-deny` встановлено, `cargo deny init` для генерації канонічного файлу.
7
+
8
+ `clippy` **не** автофіксимо (`--fix` потенційно небезпечний і може прибрати навмисний код) — clippy-порушення йдуть у LLM-ladder, а не в детермінований autofix.
9
+
10
+ Без `Cargo.toml` у cwd — no-op (exit 0). Окремого `package.json`-скрипта немає.
11
+
12
+ `cargo`, `rustfmt`, `clippy`, `cargo-deny` **не додавай** у `devDependencies` — це Rust toolchain, ставиться через `rustup`/`cargo install` локально або через `dtolnay/rust-toolchain@stable` у CI.
13
+
14
+ ### Канон CI workflow `.github/workflows/lint-rust.yml`
15
+
16
+ Канонічний workflow, drift-safe перевірка (`rust.lint_rust_yml`) і вимога `Swatinem/rust-cache@v2` — `npm/rules/rust/lint_rust_yml/lint_rust_yml.mdc`. Тут лише сам лінт-скрипт; CI-канон workflow **не дублюється**.