@nitra/cursor 1.8.221 → 1.8.228
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.
- package/.claude-template/npm-CLAUDE.md +4 -0
- package/CHANGELOG.md +69 -0
- package/bin/auto-rules.md +2 -0
- package/bin/n-cursor.js +3 -2
- package/mdc/abie.mdc +13 -0
- package/mdc/ci4.mdc +8 -0
- package/mdc/tauri.mdc +20 -0
- package/package.json +1 -1
- package/policy/abie/base_deployment_preem/base_deployment_preem.rego +56 -0
- package/policy/abie/base_deployment_preem/base_deployment_preem_test.rego +60 -0
- package/policy/abie/clean_merged_ignore_branches/clean_merged_ignore_branches.rego +100 -0
- package/policy/abie/clean_merged_ignore_branches/clean_merged_ignore_branches_test.rego +48 -0
- package/policy/abie/health_check_policy/health_check_policy.rego +91 -22
- package/policy/abie/health_check_policy/health_check_policy_test.rego +99 -0
- package/policy/abie/http_route_base/http_route_base_test.rego +64 -0
- package/policy/k8s/base_kustomization/base_kustomization.rego +40 -0
- package/policy/k8s/base_kustomization/base_kustomization_test.rego +36 -0
- package/policy/k8s/base_manifest/base_manifest.rego +154 -0
- package/policy/k8s/base_manifest/base_manifest_test.rego +94 -0
- package/policy/k8s/gateway/gateway.rego +151 -0
- package/policy/k8s/gateway/gateway_test.rego +122 -0
- package/policy/k8s/hasura_configmap/hasura_configmap.rego +69 -0
- package/policy/k8s/hasura_configmap/hasura_configmap_test.rego +49 -0
- package/policy/k8s/hasura_httproute/hasura_httproute.rego +298 -0
- package/policy/k8s/hasura_httproute/hasura_httproute_test.rego +148 -0
- package/policy/k8s/hpa_pdb/hpa_pdb.rego +139 -0
- package/policy/k8s/hpa_pdb/hpa_pdb_test.rego +101 -0
- package/policy/k8s/kustomization/kustomization.rego +220 -0
- package/policy/k8s/kustomization/kustomization_test.rego +128 -0
- package/policy/k8s/kustomize_managed/kustomize_managed.rego +31 -0
- package/policy/k8s/kustomize_managed/kustomize_managed_test.rego +30 -0
- package/policy/k8s/manifest/manifest.rego +151 -4
- package/policy/k8s/manifest/manifest_test.rego +309 -0
- package/policy/k8s/svc_hl_yaml/svc_hl_yaml.rego +51 -0
- package/policy/k8s/svc_hl_yaml/svc_hl_yaml_test.rego +42 -0
- package/policy/k8s/svc_yaml/svc_yaml.rego +31 -0
- package/policy/k8s/svc_yaml/svc_yaml_test.rego +41 -0
- package/scripts/check-abie.mjs +102 -369
- package/scripts/check-ga.mjs +89 -9
- package/scripts/check-k8s.mjs +128 -569
- package/scripts/lint-conftest.mjs +98 -3
- package/scripts/lint-ga.mjs +18 -132
- package/scripts/lint-rego.mjs +19 -4
- package/scripts/utils/run-conftest-batch.mjs +117 -0
|
@@ -21,6 +21,10 @@ npx @nitra/cursor check changelog
|
|
|
21
21
|
npx @nitra/cursor check npm-module
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
+
## Перш ніж писати `check-*.mjs`
|
|
25
|
+
|
|
26
|
+
Перед створенням нового `npm/scripts/check-<rule>.mjs` оціни, чи задача лягає на rego-полісі. **Default — Rego**: пер-документні структурні перевірки (kind/apiVersion, поля, форма масивів) пишуться у `npm/policy/<rule>/<name>/<name>.rego` + `_test.rego`. JS — тільки для cross-file resolution, file-system access (`readdir`/`stat`), autofix/rewrite або парсингу до YAML-body. Гібрид (rego як швидкий gate + JS-оркестратор для cross-file) — нормальний патерн; референс — `npm/policy/k8s/*` ↔ `npm/scripts/check-k8s.mjs`. Деталі алгоритму рішення — `.cursor/rules/conftest.mdc` (alwaysApply).
|
|
27
|
+
|
|
24
28
|
## Джерело правил
|
|
25
29
|
|
|
26
30
|
- `.cursor/rules/n-changelog.mdc` — правило про CHANGELOG (PR-scoped, для всіх воркспейсів)
|
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,75 @@
|
|
|
4
4
|
|
|
5
5
|
Формат — [Keep a Changelog](https://keepachangelog.com/uk/1.1.0/), нумерація — [SemVer](https://semver.org/lang/uk/).
|
|
6
6
|
|
|
7
|
+
## [1.8.228] - 2026-05-10
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- **k8s / Plan B (rego-authoritative, повна централізація):** rego-крок переїхав на початок `check-k8s.mjs::check()` через новий helper `runAllK8sRego` — батч-виклик `runConftestBatch` для 9 пакетів (`k8s.manifest`, `k8s.gateway`, `k8s.hpa_pdb`, `k8s.kustomization`, `k8s.svc_yaml`, `k8s.svc_hl_yaml`, `k8s.base_kustomization`, `k8s.base_manifest`, `k8s.kustomize_managed`). JS у `check-k8s.mjs` робить лише cross-file orchestration + autofix + modeline. Cross-file orchestrators `validateHasuraConfigMapRemoteSchemaPermissions` і `validateHasuraHttpRouteCanon` рефакторнуто: JS відбирає paired-with-Hasura-Deployment файли, далі батч-conftest на `k8s.hasura_configmap`/`k8s.hasura_httproute`. Видалено JS-orchestrator-функції-дублі (≈10 шт): `scanForbiddenManifestsInYamlDocuments`, `failIfIngressInDocument`, `failIfAutoscalingV1InDocument`, `validateK8sYamlPolicyDocuments`, `failIfK8sPolicyNamespaceRulesViolated`, `failIfK8sPolicyResourceRulesViolated`, `runK8sYamlPolicyAndGatewayScans`, `scanGatewayApiRouteBackendRefsInYamlBody`, `failIfGatewayRouteUsesNonHeadlessService`, `validateKustomizationResourcesSortedAlphabetically`, `validateKustomizationPatchesStructuralSort`, `validateInlinePatchesSorted`, `validateKustomizationJson6902NoRemoveAddSamePath`, `auditJson6902OneKustomizationYamlFile`, `auditJson6902ForKustomizationYamlDoc`, `auditKustomizationPatchesJson6902`, `auditOneKustomizationJson6902Patch`, `auditJson6902PatchExternalFile`, `failIfJson6902RemoveAddConflictOnSamePath`, `verifyBaseKustomizationNamespaceOnFile`, `ensureBaseKustomizationHasNamespace`, `readFirstConfigMapDoc`. Видалено публічний predicate `isForbiddenAutoscalingV1Manifest` + його тест (rego `k8s.manifest` авторитативно). Решта predicates лишилися як публічні exports для back-compat (`hpaManifestViolations`, `pdbManifestViolations`, `deploymentTopologySpreadConstraintsViolation` все ще активно використовуються JS cross-file для expected-name/dev-like; інші — тестові shim, можна прибрати окремо).
|
|
12
|
+
- **`checkK8sYamlFile`** залишає тільки modeline + `$schema`-URL перевірки; per-document валідація (Ingress/autoscaling/v1 заборонено, Service GCP-анотації, Deployment resources/Hasura image/topologySpread, Gateway API backendRef правила, HCP, svc/svc-hl, namespace правила) — у rego, виконано на початку `check()`.
|
|
13
|
+
|
|
14
|
+
## [1.8.227] - 2026-05-10
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- **conftest.mdc (alwaysApply):** канонізовано патерн «Rego-authoritative + JS-orchestrator» (Plan B) як основний для всіх перевірок у репо. Розділ «Гібрид» переписано: замість «JS authoritative + rego-копія» (Plan A) — тепер чітко: пер-документне правило існує **рівно в одному місці** (rego), а `check-<rule>.mjs` делегує його через `runConftestBatch` (`npm/scripts/utils/run-conftest-batch.mjs`), один спавн на namespace. Додано конкретний шаблон `check()` (rego-крок перший, JS cross-file — після) і опис інтеграції з `lint-<rule>.mjs` (external-tools wrapper викликає `await checkX()` як останній крок). Реальні приклади — abie (пілот) і ga (повна централізація). Новий «червоний прапор» забороняє лишати JS-копію rego-правила «про всяк випадок» — це плодить дрифт.
|
|
19
|
+
|
|
20
|
+
## [1.8.226] - 2026-05-10
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
|
|
24
|
+
- **ga / Plan B (rego-authoritative, повна централізація):** rego-крок переїхав із `lint-ga.mjs` у `check-ga.mjs::check()` як **перший крок**. Раніше `bun run lint-ga` сам викликав 4 per-workflow conftest + 1 batch для `ga.workflow_common`, а `npx @nitra/cursor check ga` цю частину не робив — тепер вся ga-логіка (rego + JS cross-file) в одному `check-ga.check()`. `lint-ga.mjs::runLintGaCli` спрощено: preflight (shellcheck/uv) → actionlint → zizmor → `await checkGa()`. Видалено: `CONFTEST_TARGETS`, `GA_POLICY_DIR`, `runConftestStep`, `runConftestWorkflowCommon` — і непотрібні імпорти `existsSync`/`readdirSync`/`dirname`/`join`/`fileURLToPath`. `runLintGaCli` тепер `async`; `bin/n-cursor.js` оновлено на `await runLintGaCli()`. Тест `lint-ga.test.mjs` оновлено: `await fn()` замість `fn()`. Тест `check-ga.test.mjs::"exit 1 коли shellcheck відсутній"` переведений на точковий виклик експортованої `checkShellcheckInstalled` (бо `withBinRemovedFromPath('shellcheck')` на macOS заодно видаляв `/opt/homebrew/bin` де conftest, ламаючи hard-fail у `runConftestBatch`).
|
|
25
|
+
- **`check-ga.mjs::checkShellcheckInstalled`:** додано `export` (потрібен для точкового тесту після рефактору).
|
|
26
|
+
- **тестова фікстура `setupCanonicalGaProject` у check-ga.test.mjs:** додано секцію `concurrency` (з канонічними `group` і `cancel-in-progress: true`) у workflow `clean-ga-workflows.yml`, `clean-merged-branch.yml`, `git-ai.yml` — `ga.workflow_common` rego тепер запускається у `check()`, а ці workflow раніше не мали concurrency у фікстурі (правило `lint-ga.yml` уже мало). Це **правильна** реакція: rego-перевірка тепер ловить порушення на тих самих фікстурах, на яких раніше не запускалась.
|
|
27
|
+
|
|
28
|
+
## [1.8.225] - 2026-05-10
|
|
29
|
+
|
|
30
|
+
### Added
|
|
31
|
+
|
|
32
|
+
- **utility `runConftestBatch`:** новий `npm/scripts/utils/run-conftest-batch.mjs` — спавнить `conftest test` одним викликом для batched-списку файлів, парсить `--output json`, повертає структуровані `{filename, namespace, message}` порушення. Hard-fail зі install-hint якщо `conftest` не у PATH (узгоджено з рішенням Plan B). Використовується з `check-*.mjs` для делегування пер-документної валідації у Rego-полісі без помітного сповільнення (один спавн на namespace, не на файл).
|
|
33
|
+
|
|
34
|
+
### Changed
|
|
35
|
+
|
|
36
|
+
- **abie / Plan B (rego-authoritative, pilot):** `npm/scripts/check-abie.mjs` рефакторнуто — пер-документна валідація 4 правил тепер делегується rego через `runConftestBatch`, JS залишає лише cross-file-оркестрацію (walking, path-фільтрацію, парність файлів). Видалені JS-функції-предикати (тепер єдине джерело істини — rego): `abieBaseHttpRouteHostnamesErrors`, `deploymentDocumentHasAbieBasePreemNodeSelector`, `parseCleanMergedIgnoreBranches`, `ignoreBranchesIncludesRequired`, `validateAbieHcPolicy`, плюс хелпери `collectAbieHostnames`, `isAllowedAbieBaseDevHostname`, `isAbiePreemTruthy`, `processBaseHttpRouteDoc`, `httpRouteHasNonEmptyHostnames`, `findHealthCheckPolicyInDocs` і константа `ABIE_REQUIRED_IGNORE_BRANCHES`. Імпорти `flattenWorkflowSteps`, `getStepUses`, `parseWorkflowYaml` (`./utils/gha-workflow.mjs`) теж прибрано — orphan після видалення JS-парсера workflow.
|
|
37
|
+
- **abie.health_check_policy (rego):** виправлено divergence з JS — тепер targetRef.name перевіряється точним match-ем `<hcp.metadata.name>-hl` (з нормалізацією: якщо name вже закінчується на `-hl`, береться як є). До цього rego перевіряло лише суфікс `-hl`, що дозволяло `targetRef.name=bar-hl` для HCP з `name=foo` — це не дзеркалило JS.
|
|
38
|
+
- **`validateAbieHcYaml` → `validateAbieHcModeline`:** export перейменовано — JS-частина перевірки hc.yaml тепер обмежується modeline (`# yaml-language-server: $schema=…`); парсинг YAML і структурна валідація HCP делеговано rego.
|
|
39
|
+
- **`npm/tests/check-abie.test.mjs`:** прибрано тести видалених JS-предикатів (8 тестів) — їх покриття тепер забезпечують `_test.rego` фікстури через `conftest verify`.
|
|
40
|
+
- **`npm/tests/cross-check-rego-abie.test.mjs`:** видалено — після Plan B JS-сторони для крос-чеку немає; `_test.rego` фікстури в кожному abie-пакеті дають аналогічне покриття.
|
|
41
|
+
|
|
42
|
+
## [1.8.224] - 2026-05-10
|
|
43
|
+
|
|
44
|
+
### Added
|
|
45
|
+
|
|
46
|
+
- **golden cross-check тести JS↔rego (abie):** додано `npm/tests/cross-check-rego-abie.test.mjs` (25 тестів), який для кожної пари (JS-предикат у `check-abie.mjs` ↔ rego-пакет у `npm/policy/abie/`) подає однаковий вхід у обидва імплементації через `opa eval --format json` і перевіряє інваріант **«обидва бачать порушення або обидва ні»**. Покриває: `deploymentDocumentHasAbieBasePreemNodeSelector` ↔ `abie.base_deployment_preem`; `parseCleanMergedIgnoreBranches`+`ignoreBranchesIncludesRequired` ↔ `abie.clean_merged_ignore_branches`; `abieBaseHttpRouteHostnamesErrors` ↔ `abie.http_route_base`; rego-only golden-фікстури для `abie.health_check_policy` (бо JS-функція `validateAbieHcPolicy` приватна). Тест автоматично пропускається, якщо `opa` не у PATH. Sanity-check ламанням rego навмисно — drift детектується.
|
|
47
|
+
|
|
48
|
+
## [1.8.223] - 2026-05-10
|
|
49
|
+
|
|
50
|
+
### Added
|
|
51
|
+
|
|
52
|
+
- **abie / нові rego-пакети:** `npm/policy/abie/base_deployment_preem/` (Deployment у `…/k8s/.../base/...` має `spec.template.spec.nodeSelector.preem` зі значенням, що вважається істинним — boolean `true` або рядок `"true"`); `npm/policy/abie/clean_merged_ignore_branches/` (у workflow `.github/workflows/clean-merged-branch.yml` крок `phpdocker-io/github-actions-delete-abandoned-branches` має `with.ignore_branches` з токенами `dev,ua,ru`, case-insensitive). Реєстрація в `lint-conftest.mjs` TARGETS: walk-pattern для base-resource YAML і single-target для workflow.
|
|
53
|
+
- **abie / `_test.rego` фікстури:** додано юніт-тести для всіх 4 abie-пакетів — нових (`base_deployment_preem_test.rego`, `clean_merged_ignore_branches_test.rego`) і існуючих (`http_route_base_test.rego`, `health_check_policy_test.rego`). 35 тестів покривають happy paths і deny-кейси.
|
|
54
|
+
|
|
55
|
+
### Changed
|
|
56
|
+
|
|
57
|
+
- **abie.health_check_policy (rego):** виправлено помилковий шлях `spec.config.httpHealthCheck` → правильний `spec.default.config.httpHealthCheck` (узгоджено з `validateAbieHcPolicy` у `check-abie.mjs`). Розширено перевірками: точна `apiVersion: networking.gke.io/v1`, `metadata.name` непорожній, `spec.default.config.type: HTTP`, `targetRef.kind: Service`. Cross-file звірка `<deployment.name>-hl` лишається у JS.
|
|
58
|
+
- **abie.mdc:** додано розділ «Швидкий gate через conftest (Rego)» зі списком rego-пакетів і опису того, що cross-file логіка (парність HCP↔Deployment, обчислений `<name>-hl`, валідація ru/ua-overlay JSON6902 patches, env→cluster DNS, cross-namespace backendRefs) лишається у `check-abie.mjs`.
|
|
59
|
+
- **lint-conftest.mjs TARGETS:** `abie.health_check_policy` і `abie.http_route_base` — `policyDir` уточнено до конкретного підкаталогу (`abie/health_check_policy`, `abie/http_route_base`) замість загального `abie`. Додано шляховий regex `K8S_BASE_RESOURCE_PATH_RE` для базових ресурсних YAML.
|
|
60
|
+
|
|
61
|
+
## [1.8.222] - 2026-05-10
|
|
62
|
+
|
|
63
|
+
### Added
|
|
64
|
+
|
|
65
|
+
- **k8s / rego-полісі:** розширено `npm/policy/k8s/manifest/manifest.rego` (Deployment cpu+memory у `requests`, Hasura image pin із білим списком тегів, канонічний `topologySpreadConstraints` з мітки `app` самого Deployment). Додано `manifest_test.rego` із вхідними фікстурами; rego тестується через `conftest verify` (опційний крок у `bun run lint-rego`). JS у `check-k8s.mjs` лишається authoritative — нові правила Rego — швидкий gate для одиничного маніфеста.
|
|
66
|
+
- **k8s / нові rego-пакети:** `npm/policy/k8s/gateway/` (Gateway API: backendRef з суфіксом `-hl`, redundant `namespace` у backendRef, HCP `targetRef.name` `-hl`); `npm/policy/k8s/kustomization/` (resources/patches алфавітне сортування, JSON6902 `remove`+`add` на той самий `path`); `npm/policy/k8s/svc_yaml/` (`Service.spec.type: ClusterIP`); `npm/policy/k8s/svc_hl_yaml/` (headless Service з суфіксом `-hl` і `clusterIP: None`); `npm/policy/k8s/base_kustomization/` (обов'язковий `namespace:`); `npm/policy/k8s/base_manifest/` (`metadata.namespace` у base, base-canon `cpu='0.02'`/`memory='128Mi'`); `npm/policy/k8s/kustomize_managed/` (заборона `metadata.namespace` у kustomize-managed файлах); `npm/policy/k8s/hasura_configmap/` (`HASURA_GRAPHQL_ENABLE_REMOTE_SCHEMA_PERMISSIONS: "true"`); `npm/policy/k8s/hasura_httproute/` (канон 4 правил Hasura: `/ql` Exact + `/ql/` Exact + PathPrefix + WebSocket); `npm/policy/k8s/hpa_pdb/` (структурний gate HPA/PDB: `apiVersion`, `behavior.scaleUp/Down`, `metrics`, `selector.matchLabels`). До кожного пакета додано `*_test.rego` фікстури.
|
|
67
|
+
- **lint-rego:** додано опційний крок `conftest verify` у `npm/scripts/lint-rego.mjs` після `regal lint` для виконання `*_test.rego`. Якщо `conftest` не у PATH — крок мовчки пропускається з install-hint.
|
|
68
|
+
|
|
69
|
+
### Changed
|
|
70
|
+
|
|
71
|
+
- **lint-conftest:** `npm/scripts/lint-conftest.mjs` — `k8s.manifest` target тепер указує на `policyDir: 'k8s/manifest'` (вужчий policy-tree), додано targets для нових пакетів `k8s.gateway`, `k8s.hpa_pdb`, `k8s.kustomization`, `k8s.svc_yaml`, `k8s.svc_hl_yaml`, `k8s.base_kustomization`, `k8s.base_manifest`. Шляхові регекспи: `K8S_KUSTOMIZATION_PATH_RE`, `K8S_BASE_KUSTOMIZATION_PATH_RE`, `K8S_BASE_MANIFEST_PATH_RE`, `K8S_SVC_YAML_PATH_RE`, `K8S_SVC_HL_YAML_PATH_RE`. Пакети `kustomize_managed`, `hasura_configmap`, `hasura_httproute` потребують cross-file gating з `check-k8s.mjs` і не входять у `lint-conftest` walk-targets.
|
|
72
|
+
- **n-k8s.mdc:** додано розділ «Швидкий gate через conftest (Rego)» зі списком rego-пакетів і опису того, що cross-file логіка (резолюція kustomize-tree, парність svc.yaml/svc-hl.yaml, прив'язка ConfigMap/HTTPRoute до Hasura-Deployment, HPA/PDB by directory, env-залежні межі min/maxReplicas) лишається у `check-k8s.mjs`.
|
|
73
|
+
- **conftest.mdc (alwaysApply):** замість одного абзацу про «пріоритет conftest» — повний алгоритм рішення для нової перевірки: декізія-дерево (single-document → Rego за замовчуванням; cross-file/FS/autofix/text-pre-YAML → JS), workflow «спершу намалюй вхід → rego або гібрид», список «червоних прапорів» (Rego не вміє X — звір зі списком винятків). Мета: робити Rego default-вибором для нових перевірок.
|
|
74
|
+
- **npm/.claude-template/npm-CLAUDE.md:** додано path-scoped нагадування «Перш ніж писати `check-*.mjs`» з посиланням на алгоритм у `conftest.mdc`. Регенеровано `npm/CLAUDE.md`.
|
|
75
|
+
|
|
7
76
|
## [1.8.221] - 2026-05-10
|
|
8
77
|
|
|
9
78
|
### Changed
|
package/bin/auto-rules.md
CHANGED
package/bin/n-cursor.js
CHANGED
|
@@ -1327,8 +1327,9 @@ try {
|
|
|
1327
1327
|
break
|
|
1328
1328
|
}
|
|
1329
1329
|
case 'lint-ga': {
|
|
1330
|
-
// Канонічний lint-ga з preflight на shellcheck → actionlint → zizmor (ga.mdc).
|
|
1331
|
-
process.exitCode
|
|
1330
|
+
// Канонічний lint-ga з preflight на shellcheck → actionlint → zizmor → check-ga (ga.mdc).
|
|
1331
|
+
// Останній крок (check-ga) async — тому await обов'язковий, інакше process.exitCode буде Promise.
|
|
1332
|
+
process.exitCode = await runLintGaCli()
|
|
1332
1333
|
|
|
1333
1334
|
break
|
|
1334
1335
|
}
|
package/mdc/abie.mdc
CHANGED
|
@@ -388,3 +388,16 @@ with:
|
|
|
388
388
|
## Перевірка
|
|
389
389
|
|
|
390
390
|
**`npx @nitra/cursor check abie`**
|
|
391
|
+
|
|
392
|
+
### Швидкий gate через conftest (Rego)
|
|
393
|
+
|
|
394
|
+
Підмножину пер-документних правил продубльовано як rego-полісі у **`npm/policy/abie/`** (запускається через **`bun run lint-rego`** для `*_test.rego` тестів і **`npx @nitra/cursor lint-conftest`** для прогону по реальних YAML — деталі в **conftest.mdc** / **n-rego.mdc**). JS у **`check-abie.mjs`** authoritative — rego тільки швидкий gate для одиничного маніфеста (зокрема через IDE-розширення `tsandall.opa`).
|
|
395
|
+
|
|
396
|
+
Пакети (директорія в **`npm/policy/abie/`** → namespace → що перевіряє):
|
|
397
|
+
|
|
398
|
+
- **`http_route_base/`** → `abie.http_route_base` — у HTTPRoute під `…/k8s/.../base/...` усі `spec.hostnames` мають бути в домені `aiml.live` (включно з `*.aiml.live` та піддоменами). **Цільові файли:** `…/k8s/.../base/.../hr.yaml`.
|
|
399
|
+
- **`health_check_policy/`** → `abie.health_check_policy` — структура HealthCheckPolicy: `apiVersion: networking.gke.io/v1`, `metadata.name`, `spec.default.config.type: HTTP`, `httpHealthCheck.requestPath` починається з `/`, `port: 8080`, `targetRef.kind: Service`, `targetRef.name` має суфікс `-hl`. **Цільові файли:** `…/k8s/.../hc.yaml`.
|
|
400
|
+
- **`base_deployment_preem/`** → `abie.base_deployment_preem` — Deployment у base/ має `spec.template.spec.nodeSelector.preem` зі значенням `true` (boolean або рядок). **Цільові файли:** ресурсні YAML під `…/k8s/.../base/...`.
|
|
401
|
+
- **`clean_merged_ignore_branches/`** → `abie.clean_merged_ignore_branches` — у workflow `.github/workflows/clean-merged-branch.yml` крок з `uses: phpdocker-io/github-actions-delete-abandoned-branches` має `with.ignore_branches`, що містить токени `dev,ua,ru` (case-insensitive). **Цільові файли:** `.github/workflows/clean-merged-branch.yml`.
|
|
402
|
+
|
|
403
|
+
Cross-file логіка (парність HCP↔Deployment у каталозі, обчислений `<deployment.name>-hl` для `targetRef.name`, валідація ru/ua-overlay JSON6902 patches на Service/HTTPRoute, env→cluster DNS, аналіз cross-namespace backendRefs у пакетах) лишається у **`check-abie.mjs`** — Rego не читає файлову систему й не робить cross-document резолюцію.
|
package/mdc/ci4.mdc
CHANGED
|
@@ -15,6 +15,14 @@ C4-діаграми проєкту живуть у Markdown поряд із ко
|
|
|
15
15
|
тримати знання разом із кодом — версійно, в одному PR і доступно для агентів. Якщо щось
|
|
16
16
|
важливе про систему існує лише у Confluence/Notion/месенджері — для проєкту цього **немає**.
|
|
17
17
|
|
|
18
|
+
## Розташування
|
|
19
|
+
|
|
20
|
+
C4-діаграми проєкту живуть у теці `docs/ci4/` — це **канонічне місце** для всіх рівнів
|
|
21
|
+
моделі: `01-context.md`, `02-containers.md`, `03-components.md`, `04-code.md`, плюс
|
|
22
|
+
супутні `README.md` (вступ) і `decisions.md` (зведення ADR-впливів на C4). Інших місць
|
|
23
|
+
для C4-схем у репозиторії немає: розпорошення по підтеках сервісів ламає навігацію
|
|
24
|
+
«від системи вглиб» і робить агентський аналіз перед зміною дорожчим.
|
|
25
|
+
|
|
18
26
|
## Аналіз перед зміною
|
|
19
27
|
|
|
20
28
|
Перш ніж вносити зміни, агент **читає відповідні C4-файли**: контекст, контейнери, компоненти
|
package/mdc/tauri.mdc
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Tauri
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
version: '1.0'
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
в файлі .vscode/extensions.json є налаштування для Vue:
|
|
9
|
+
|
|
10
|
+
```json title=".vscode/extensions.json"
|
|
11
|
+
{
|
|
12
|
+
"recommendations": ["tauri-apps.tauri-vscode",
|
|
13
|
+
"rust-lang.rust-analyzer"]
|
|
14
|
+
}
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
## Перевірка
|
|
19
|
+
|
|
20
|
+
`npx @nitra/cursor check tauri`
|
package/package.json
CHANGED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Порт перевірки `deploymentDocumentHasAbieBasePreemNodeSelector` з
|
|
2
|
+
# `npm/scripts/check-abie.mjs` (abie.mdc): кожен `Deployment` у файлах під
|
|
3
|
+
# `…/k8s/.../base/…` має `spec.template.spec.nodeSelector.preem` зі
|
|
4
|
+
# значенням, що вважається істинним (boolean `true` або рядок `"true"`
|
|
5
|
+
# без урахування регістру). Overlays (ua/ru) далі підміняють селектор
|
|
6
|
+
# JSON6902-патчами на `preem: false` / `yandex.cloud/preemptible: false`.
|
|
7
|
+
#
|
|
8
|
+
# Запуск (локально, лише для одного base-YAML з Deployment):
|
|
9
|
+
# conftest test path/to/k8s/base/deployment.yaml \
|
|
10
|
+
# -p npm/policy/abie/base_deployment_preem \
|
|
11
|
+
# --namespace abie.base_deployment_preem
|
|
12
|
+
#
|
|
13
|
+
# JS відбирає файли під `…/k8s/.../base/…` (через `isAbieK8sBaseYamlPath`) і
|
|
14
|
+
# викликає conftest з цією намеспейс. JS authoritative (`check-abie.mjs`:
|
|
15
|
+
# `deploymentDocumentHasAbieBasePreemNodeSelector` + `ensureAbieBaseDeploymentPreemNodeSelector`).
|
|
16
|
+
# Cross-file gating (правило `abie` у `.n-cursor.json`, шлях файла) — у JS.
|
|
17
|
+
#
|
|
18
|
+
# Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
|
|
19
|
+
# Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`
|
|
20
|
+
# (.cursor/rules/conftest.mdc). Лінт — `bun run lint-rego` (regal).
|
|
21
|
+
package abie.base_deployment_preem
|
|
22
|
+
|
|
23
|
+
import rego.v1
|
|
24
|
+
|
|
25
|
+
deny_msg := concat(" ", [
|
|
26
|
+
"Deployment у base: потрібен spec.template.spec.nodeSelector.preem:",
|
|
27
|
+
"true (або 'true') — abie.mdc",
|
|
28
|
+
])
|
|
29
|
+
|
|
30
|
+
deny contains deny_msg if {
|
|
31
|
+
input.kind == "Deployment"
|
|
32
|
+
not has_truthy_preem
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
# preem truthy: boolean true або рядок "true" (case-insensitive, з обрізаними пробілами).
|
|
36
|
+
has_truthy_preem if {
|
|
37
|
+
preem := object.get(node_selector, "preem", null)
|
|
38
|
+
is_preem_truthy(preem)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
is_preem_truthy(true)
|
|
42
|
+
|
|
43
|
+
is_preem_truthy(v) if {
|
|
44
|
+
is_string(v)
|
|
45
|
+
lower(trim_space(v)) == "true"
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
node_selector := object.get(
|
|
49
|
+
object.get(
|
|
50
|
+
object.get(object.get(input, "spec", {}), "template", {}),
|
|
51
|
+
"spec",
|
|
52
|
+
{},
|
|
53
|
+
),
|
|
54
|
+
"nodeSelector",
|
|
55
|
+
{},
|
|
56
|
+
)
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Тести для `abie.base_deployment_preem`. Запуск:
|
|
2
|
+
# conftest verify -p npm/policy/abie/base_deployment_preem
|
|
3
|
+
package abie.base_deployment_preem_test
|
|
4
|
+
|
|
5
|
+
import rego.v1
|
|
6
|
+
|
|
7
|
+
import data.abie.base_deployment_preem
|
|
8
|
+
|
|
9
|
+
mk_deployment(node_selector) := {
|
|
10
|
+
"apiVersion": "apps/v1",
|
|
11
|
+
"kind": "Deployment",
|
|
12
|
+
"metadata": {"name": "api", "namespace": "dev"},
|
|
13
|
+
"spec": {"template": {"spec": object.union(
|
|
14
|
+
{"containers": [{"name": "main", "image": "x"}]},
|
|
15
|
+
{"nodeSelector": node_selector},
|
|
16
|
+
)}},
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
test_deny_no_node_selector if {
|
|
20
|
+
input_doc := {
|
|
21
|
+
"apiVersion": "apps/v1",
|
|
22
|
+
"kind": "Deployment",
|
|
23
|
+
"metadata": {"name": "api"},
|
|
24
|
+
"spec": {"template": {"spec": {"containers": [{"name": "main", "image": "x"}]}}},
|
|
25
|
+
}
|
|
26
|
+
count(base_deployment_preem.deny) > 0 with input as input_doc
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
test_deny_node_selector_without_preem if {
|
|
30
|
+
count(base_deployment_preem.deny) > 0 with input as mk_deployment({"role": "worker"})
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
test_deny_preem_false if {
|
|
34
|
+
count(base_deployment_preem.deny) > 0 with input as mk_deployment({"preem": false})
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
test_deny_preem_string_false if {
|
|
38
|
+
count(base_deployment_preem.deny) > 0 with input as mk_deployment({"preem": "false"})
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
test_allow_preem_boolean_true if {
|
|
42
|
+
count(base_deployment_preem.deny) == 0 with input as mk_deployment({"preem": true})
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
test_allow_preem_string_true if {
|
|
46
|
+
count(base_deployment_preem.deny) == 0 with input as mk_deployment({"preem": "true"})
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
test_allow_preem_string_uppercase_true if {
|
|
50
|
+
count(base_deployment_preem.deny) == 0 with input as mk_deployment({"preem": "TRUE"})
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
# Не Deployment — пакет не діє (дзеркало JS-предиката).
|
|
54
|
+
test_allow_non_deployment if {
|
|
55
|
+
count(base_deployment_preem.deny) == 0 with input as {
|
|
56
|
+
"apiVersion": "v1",
|
|
57
|
+
"kind": "ConfigMap",
|
|
58
|
+
"metadata": {"name": "x"},
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# Порт перевірки `parseCleanMergedIgnoreBranches` + `ignoreBranchesIncludesRequired`
|
|
2
|
+
# з `npm/scripts/check-abie.mjs` (abie.mdc): у workflow
|
|
3
|
+
# `.github/workflows/clean-merged-branch.yml` крок з
|
|
4
|
+
# `uses: phpdocker-io/github-actions-delete-abandoned-branches` має у
|
|
5
|
+
# `with.ignore_branches` містити усі обовʼязкові токени `dev,ua,ru`
|
|
6
|
+
# (case-insensitive, кома-розділені).
|
|
7
|
+
#
|
|
8
|
+
# Запуск (локально):
|
|
9
|
+
# conftest test .github/workflows/clean-merged-branch.yml \
|
|
10
|
+
# -p npm/policy/abie/clean_merged_ignore_branches \
|
|
11
|
+
# --namespace abie.clean_merged_ignore_branches
|
|
12
|
+
#
|
|
13
|
+
# JS authoritative (`check-abie.mjs`: `checkCleanMergedBranch`,
|
|
14
|
+
# `parseCleanMergedIgnoreBranches`, `ignoreBranchesIncludesRequired`); ця Rego —
|
|
15
|
+
# швидкий gate для одиничного workflow YAML. Cross-file гейтинг (правило
|
|
16
|
+
# `abie` у `.n-cursor.json`) — у JS.
|
|
17
|
+
#
|
|
18
|
+
# Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
|
|
19
|
+
# Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`
|
|
20
|
+
# (.cursor/rules/conftest.mdc). Лінт — `bun run lint-rego` (regal).
|
|
21
|
+
package abie.clean_merged_ignore_branches
|
|
22
|
+
|
|
23
|
+
import rego.v1
|
|
24
|
+
|
|
25
|
+
# Обовʼязкові гілки в `ignore_branches` (узгоджено з `ABIE_REQUIRED_IGNORE_BRANCHES`).
|
|
26
|
+
required_branches := {"dev", "ua", "ru"}
|
|
27
|
+
|
|
28
|
+
# Префікс `uses:` для GitHub Action, у якого читаємо `with.ignore_branches`.
|
|
29
|
+
target_action_marker := "phpdocker-io/github-actions-delete-abandoned-branches"
|
|
30
|
+
|
|
31
|
+
step_missing_msg := concat(" ", [
|
|
32
|
+
"clean-merged-branch.yml: не знайдено крок з uses: phpdocker-io/github-actions-delete-abandoned-branches",
|
|
33
|
+
"(abie.mdc)",
|
|
34
|
+
])
|
|
35
|
+
|
|
36
|
+
ignore_branches_missing_msg := concat(" ", [
|
|
37
|
+
"clean-merged-branch.yml: не знайдено with.ignore_branches у кроці",
|
|
38
|
+
"phpdocker-io/github-actions-delete-abandoned-branches (abie.mdc)",
|
|
39
|
+
])
|
|
40
|
+
|
|
41
|
+
# ── deny: крок не знайдено ────────────────────────────────────────────────
|
|
42
|
+
|
|
43
|
+
deny contains step_missing_msg if {
|
|
44
|
+
count(target_steps) == 0
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
# ── deny: з step нема with.ignore_branches ────────────────────────────────
|
|
48
|
+
|
|
49
|
+
deny contains ignore_branches_missing_msg if {
|
|
50
|
+
count(target_steps) > 0
|
|
51
|
+
not has_ignore_branches_value
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
# ── deny: ignore_branches не містить усіх обов'язкових токенів ────────────
|
|
55
|
+
|
|
56
|
+
deny contains msg if {
|
|
57
|
+
count(target_steps) > 0
|
|
58
|
+
ignore_branches_value != ""
|
|
59
|
+
missing := required_branches - parsed_ignore_tokens(ignore_branches_value)
|
|
60
|
+
count(missing) > 0
|
|
61
|
+
msg := sprintf(
|
|
62
|
+
"clean-merged-branch.yml: ignore_branches має містити %v (зараз: %q; не вистачає: %v) (abie.mdc)",
|
|
63
|
+
[concat(",", sort(required_branches)), ignore_branches_value, concat(",", sort(missing))],
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
# ── helpers ───────────────────────────────────────────────────────────────
|
|
68
|
+
|
|
69
|
+
# Усі steps з усіх jobs у workflow (підтримує jobs.<job>.steps[]).
|
|
70
|
+
target_steps contains step if {
|
|
71
|
+
some job in object.get(input, "jobs", {})
|
|
72
|
+
some step in object.get(job, "steps", [])
|
|
73
|
+
uses := object.get(step, "uses", "")
|
|
74
|
+
is_string(uses)
|
|
75
|
+
contains(uses, target_action_marker)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
# Чи у знайдених steps хоча б у одного є with.ignore_branches непорожнім рядком.
|
|
79
|
+
has_ignore_branches_value if {
|
|
80
|
+
some step in target_steps
|
|
81
|
+
v := object.get(object.get(step, "with", {}), "ignore_branches", null)
|
|
82
|
+
is_string(v)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
default ignore_branches_value := ""
|
|
86
|
+
|
|
87
|
+
ignore_branches_value := values[0] if {
|
|
88
|
+
values := [v |
|
|
89
|
+
some step in target_steps
|
|
90
|
+
v := object.get(object.get(step, "with", {}), "ignore_branches", null)
|
|
91
|
+
is_string(v)
|
|
92
|
+
]
|
|
93
|
+
count(values) > 0
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
# Розбирає `ignore_branches` як `,`-розділений список, нормалізує через trim+lower.
|
|
97
|
+
parsed_ignore_tokens(value) := {lower(trim_space(part)) |
|
|
98
|
+
some part in split(value, ",")
|
|
99
|
+
trim_space(part) != ""
|
|
100
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Тести для `abie.clean_merged_ignore_branches`. Запуск:
|
|
2
|
+
# conftest verify -p npm/policy/abie/clean_merged_ignore_branches
|
|
3
|
+
package abie.clean_merged_ignore_branches_test
|
|
4
|
+
|
|
5
|
+
import rego.v1
|
|
6
|
+
|
|
7
|
+
import data.abie.clean_merged_ignore_branches
|
|
8
|
+
|
|
9
|
+
# Каркас workflow з одним job, що містить step із заданим with.
|
|
10
|
+
mk_workflow(step_with) := {"jobs": {"cleanup": {"steps": [{
|
|
11
|
+
"uses": "phpdocker-io/github-actions-delete-abandoned-branches@v2",
|
|
12
|
+
"with": step_with,
|
|
13
|
+
}]}}}
|
|
14
|
+
|
|
15
|
+
other_step_workflow := {"jobs": {"cleanup": {"steps": [{"uses": "actions/checkout@v6"}]}}}
|
|
16
|
+
|
|
17
|
+
# Workflow без потрібного кроку.
|
|
18
|
+
test_deny_step_missing if {
|
|
19
|
+
count(clean_merged_ignore_branches.deny) > 0 with input as other_step_workflow
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
test_deny_ignore_branches_missing if {
|
|
23
|
+
count(clean_merged_ignore_branches.deny) > 0 with input as mk_workflow({})
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
test_deny_missing_required_token if {
|
|
27
|
+
count(clean_merged_ignore_branches.deny) > 0 with input as mk_workflow({"ignore_branches": "dev,ua"})
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
test_deny_completely_wrong_tokens if {
|
|
31
|
+
count(clean_merged_ignore_branches.deny) > 0 with input as mk_workflow({"ignore_branches": "main,develop"})
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
test_allow_all_three_tokens if {
|
|
35
|
+
count(clean_merged_ignore_branches.deny) == 0 with input as mk_workflow({"ignore_branches": "dev,ua,ru"})
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
# Регістронезалежне порівняння і пропуск пробілів.
|
|
39
|
+
test_allow_uppercase_with_spaces if {
|
|
40
|
+
count(clean_merged_ignore_branches.deny) == 0 with input as mk_workflow({"ignore_branches": " DEV , UA , RU "})
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
extra_branches_workflow := mk_workflow({"ignore_branches": "dev,ua,ru,main,release/*"})
|
|
44
|
+
|
|
45
|
+
# Додаткові гілки після обов'язкових — дозволено.
|
|
46
|
+
test_allow_extra_branches if {
|
|
47
|
+
count(clean_merged_ignore_branches.deny) == 0 with input as extra_branches_workflow
|
|
48
|
+
}
|
|
@@ -1,22 +1,25 @@
|
|
|
1
|
-
# Порт
|
|
1
|
+
# Порт структурної перевірки `HealthCheckPolicy` з
|
|
2
2
|
# `npm/scripts/check-abie.mjs` (abie.mdc).
|
|
3
3
|
#
|
|
4
4
|
# Запуск (локально):
|
|
5
|
-
# conftest test path/to/k8s/.../hc.yaml
|
|
5
|
+
# conftest test path/to/k8s/.../hc.yaml \
|
|
6
|
+
# -p npm/policy/abie/health_check_policy \
|
|
6
7
|
# --namespace abie.health_check_policy
|
|
7
8
|
#
|
|
8
|
-
#
|
|
9
|
-
# `networking.gke.io/v1`)
|
|
10
|
-
# - `
|
|
11
|
-
# - `spec.config.
|
|
12
|
-
# - `spec.
|
|
9
|
+
# Перевіряє для `kind: HealthCheckPolicy`:
|
|
10
|
+
# - `apiVersion: networking.gke.io/v1` (точна відповідність);
|
|
11
|
+
# - `metadata.name` — непорожній рядок;
|
|
12
|
+
# - `spec.default.config.type: HTTP`;
|
|
13
|
+
# - `spec.default.config.httpHealthCheck.requestPath` — непорожній і
|
|
14
|
+
# починається з `/`;
|
|
15
|
+
# - `spec.default.config.httpHealthCheck.port: 8080`;
|
|
16
|
+
# - `spec.targetRef.kind: Service`;
|
|
17
|
+
# - `spec.targetRef.name` має суфікс `-hl` (headless backend).
|
|
13
18
|
#
|
|
14
|
-
# Cross-file gating (`abie`
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
# (
|
|
18
|
-
# обчислений `<name>-hl` суфікс); ця Rego — швидкий gate для одиничного YAML
|
|
19
|
-
# (наприклад через IDE).
|
|
19
|
+
# Cross-file gating (правило `abie` у `.n-cursor.json`, парність з Deployment-каталогу,
|
|
20
|
+
# точна звірка `targetRef.name` з обчисленим `<deployment.name>-hl`) — у JS
|
|
21
|
+
# (`check-abie.mjs`: `validateAbieHcPolicy`, `checkHcYamlFiles`). JS authoritative;
|
|
22
|
+
# ця Rego — швидкий gate для одиничного YAML (наприклад через IDE).
|
|
20
23
|
#
|
|
21
24
|
# Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
|
|
22
25
|
# Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`
|
|
@@ -25,21 +28,58 @@ package abie.health_check_policy
|
|
|
25
28
|
|
|
26
29
|
import rego.v1
|
|
27
30
|
|
|
31
|
+
expected_api_version := "networking.gke.io/v1"
|
|
32
|
+
|
|
28
33
|
req_path_starts_with_slash_template := concat(" ", [
|
|
29
34
|
"HealthCheckPolicy: requestPath має починатись з `/`",
|
|
30
35
|
"(зараз %q) (abie.mdc)",
|
|
31
36
|
])
|
|
32
37
|
|
|
33
|
-
|
|
38
|
+
target_ref_name_template := concat(" ", [
|
|
39
|
+
"HealthCheckPolicy: targetRef.name має посилатися на headless Service",
|
|
40
|
+
"(очікується %q, суфікс -hl) (зараз %q) (abie.mdc)",
|
|
41
|
+
])
|
|
42
|
+
|
|
43
|
+
# ── deny: apiVersion / kind ───────────────────────────────────────────────
|
|
34
44
|
|
|
35
45
|
deny contains msg if {
|
|
46
|
+
input.kind == "HealthCheckPolicy"
|
|
47
|
+
api_version := object.get(input, "apiVersion", "")
|
|
48
|
+
api_version != expected_api_version
|
|
49
|
+
msg := sprintf(
|
|
50
|
+
"HealthCheckPolicy: apiVersion має бути %q (зараз %q) (abie.mdc)",
|
|
51
|
+
[expected_api_version, api_version],
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
# ── deny: metadata.name ───────────────────────────────────────────────────
|
|
56
|
+
|
|
57
|
+
deny contains "HealthCheckPolicy: metadata.name має бути непорожнім рядком (abie.mdc)" if {
|
|
58
|
+
input.kind == "HealthCheckPolicy"
|
|
59
|
+
startswith(object.get(input, "apiVersion", ""), "networking.gke.io/")
|
|
60
|
+
name := object.get(object.get(input, "metadata", {}), "name", "")
|
|
61
|
+
trim_space(name) == ""
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
# ── deny: spec.default.config.type ────────────────────────────────────────
|
|
65
|
+
|
|
66
|
+
deny contains "HealthCheckPolicy: spec.default.config.type має бути HTTP (abie.mdc)" if {
|
|
36
67
|
is_health_check_policy
|
|
68
|
+
is_object(default_config)
|
|
69
|
+
object.get(default_config, "type", "") != "HTTP"
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
# ── deny: requestPath ─────────────────────────────────────────────────────
|
|
73
|
+
|
|
74
|
+
deny contains "HealthCheckPolicy: spec.default.config.httpHealthCheck.requestPath має бути непорожнім (abie.mdc)" if {
|
|
75
|
+
is_health_check_policy
|
|
76
|
+
is_object(http_health_check)
|
|
37
77
|
req_path == ""
|
|
38
|
-
msg := "HealthCheckPolicy: spec.config.httpHealthCheck.requestPath має бути непорожнім (abie.mdc)"
|
|
39
78
|
}
|
|
40
79
|
|
|
41
80
|
deny contains msg if {
|
|
42
81
|
is_health_check_policy
|
|
82
|
+
is_object(http_health_check)
|
|
43
83
|
req_path != ""
|
|
44
84
|
not startswith(req_path, "/")
|
|
45
85
|
msg := sprintf(req_path_starts_with_slash_template, [req_path])
|
|
@@ -49,29 +89,58 @@ deny contains msg if {
|
|
|
49
89
|
|
|
50
90
|
deny contains msg if {
|
|
51
91
|
is_health_check_policy
|
|
92
|
+
is_object(http_health_check)
|
|
52
93
|
port := object.get(http_health_check, "port", null)
|
|
53
94
|
port != null
|
|
54
95
|
port != 8080
|
|
55
96
|
msg := sprintf("HealthCheckPolicy: port має бути 8080 (зараз %v) (abie.mdc)", [port])
|
|
56
97
|
}
|
|
57
98
|
|
|
58
|
-
# ── deny: targetRef.
|
|
99
|
+
# ── deny: targetRef.kind == Service ──────────────────────────────────────
|
|
59
100
|
|
|
60
101
|
deny contains msg if {
|
|
61
102
|
is_health_check_policy
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
103
|
+
target_ref := object.get(object.get(input, "spec", {}), "targetRef", null)
|
|
104
|
+
is_object(target_ref)
|
|
105
|
+
kind := object.get(target_ref, "kind", "")
|
|
106
|
+
kind != ""
|
|
107
|
+
kind != "Service"
|
|
108
|
+
msg := sprintf("HealthCheckPolicy: targetRef.kind має бути Service (зараз %q) (abie.mdc)", [kind])
|
|
66
109
|
}
|
|
67
110
|
|
|
68
|
-
# ──
|
|
111
|
+
# ── deny: targetRef.name = `<hcp.metadata.name>-hl` (exact, з нормалізацією)
|
|
112
|
+
|
|
113
|
+
deny contains msg if {
|
|
114
|
+
is_health_check_policy
|
|
115
|
+
hcp_name := object.get(object.get(input, "metadata", {}), "name", "")
|
|
116
|
+
hcp_name != ""
|
|
117
|
+
target_name := object.get(object.get(object.get(input, "spec", {}), "targetRef", {}), "name", "")
|
|
118
|
+
target_name != ""
|
|
119
|
+
expected_hl := expected_target_ref_name(hcp_name)
|
|
120
|
+
target_name != expected_hl
|
|
121
|
+
msg := sprintf(target_ref_name_template, [expected_hl, target_name])
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
# Нормалізація: якщо `metadata.name` уже закінчується на `-hl` — використовуємо
|
|
125
|
+
# як є; інакше додаємо суфікс. Узгоджено з `validateAbieHcPolicy`
|
|
126
|
+
# у `check-abie.mjs`.
|
|
127
|
+
expected_target_ref_name(name) := name if {
|
|
128
|
+
endswith(name, "-hl")
|
|
129
|
+
} else := concat("", [name, "-hl"])
|
|
130
|
+
|
|
131
|
+
# ── helpers ───────────────────────────────────────────────────────────────
|
|
69
132
|
|
|
70
133
|
is_health_check_policy if {
|
|
71
134
|
input.kind == "HealthCheckPolicy"
|
|
72
135
|
startswith(object.get(input, "apiVersion", ""), "networking.gke.io/")
|
|
73
136
|
}
|
|
74
137
|
|
|
75
|
-
|
|
138
|
+
default_config := object.get(
|
|
139
|
+
object.get(object.get(input, "spec", {}), "default", {}),
|
|
140
|
+
"config",
|
|
141
|
+
{},
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
http_health_check := object.get(default_config, "httpHealthCheck", {})
|
|
76
145
|
|
|
77
146
|
req_path := object.get(http_health_check, "requestPath", "")
|