@nitra/cursor 1.8.222 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/bin/n-cursor.js +3 -2
  3. package/mdc/abie.mdc +13 -0
  4. package/mdc/changelog.mdc +3 -2
  5. package/mdc/ci4.mdc +8 -0
  6. package/mdc/ga.mdc +3 -2
  7. package/mdc/graphql.mdc +3 -2
  8. package/mdc/hasura.mdc +3 -2
  9. package/mdc/image-avif.mdc +3 -2
  10. package/mdc/image-compress.mdc +3 -2
  11. package/mdc/k8s.mdc +1 -3
  12. package/mdc/nginx-default-tpl.mdc +3 -1
  13. package/mdc/php.mdc +3 -2
  14. package/mdc/style-lint.mdc +3 -2
  15. package/mdc/vue.mdc +3 -2
  16. package/package.json +1 -1
  17. package/policy/abie/base_deployment_preem/base_deployment_preem.rego +56 -0
  18. package/policy/abie/base_deployment_preem/base_deployment_preem_test.rego +60 -0
  19. package/policy/abie/clean_merged_ignore_branches/clean_merged_ignore_branches.rego +100 -0
  20. package/policy/abie/clean_merged_ignore_branches/clean_merged_ignore_branches_test.rego +48 -0
  21. package/policy/abie/health_check_policy/health_check_policy.rego +91 -22
  22. package/policy/abie/health_check_policy/health_check_policy_test.rego +99 -0
  23. package/policy/abie/http_route_base/http_route_base_test.rego +64 -0
  24. package/policy/k8s/kustomization/kustomization.rego +2 -2
  25. package/policy/k8s/manifest/manifest.rego +4 -2
  26. package/scripts/check-abie.mjs +102 -369
  27. package/scripts/check-ga.mjs +89 -9
  28. package/scripts/check-k8s.mjs +129 -704
  29. package/scripts/lint-conftest.mjs +25 -2
  30. package/scripts/lint-ga.mjs +18 -132
  31. package/scripts/utils/run-conftest-batch.mjs +117 -0
  32. package/policy/k8s/kustomize_managed/kustomize_managed.rego +0 -31
  33. package/policy/k8s/kustomize_managed/kustomize_managed_test.rego +0 -30
@@ -1,22 +1,25 @@
1
- # Порт мінімальної структурної перевірки `HealthCheckPolicy` з
1
+ # Порт структурної перевірки `HealthCheckPolicy` з
2
2
  # `npm/scripts/check-abie.mjs` (abie.mdc).
3
3
  #
4
4
  # Запуск (локально):
5
- # conftest test path/to/k8s/.../hc.yaml -p npm/policy/abie \
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
- # Перевіряє, для документів з `kind: HealthCheckPolicy` (apiVersion
9
- # `networking.gke.io/v1`):
10
- # - `spec.config.httpHealthCheck.requestPath` — непорожній шлях, що починається з `/`;
11
- # - `spec.config.httpHealthCheck.port` (або `spec.targetRef.name` суфікс) — `8080`;
12
- # - `spec.targetRef.name` має закінчуватись на `-hl` (headless backend).
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` правило в `.n-cursor.json`, парність з Deployment-каталогу,
15
- # узгодження з `metadata.name` Deployment) — у JS (`check-abie.mjs`). JS-перевірка
16
- # в `check-abie.mjs` (`validateAbieHcPolicy`) authoritative й тестує ширший набір полів
17
- # (apiVersion, spec.default.config.type=="HTTP", targetRef.kind=="Service",
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
- # ── deny: requestPath ──────────────────────────────────────────────────────
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.name закінчується на `-hl` ────────────────────────────
99
+ # ── deny: targetRef.kind == Service ──────────────────────────────────────
59
100
 
60
101
  deny contains msg if {
61
102
  is_health_check_policy
62
- name := object.get(object.get(input.spec, "targetRef", {}), "name", "")
63
- name != ""
64
- not endswith(name, "-hl")
65
- msg := sprintf("HealthCheckPolicy: targetRef.name має закінчуватись на `-hl` (зараз %q) (abie.mdc)", [name])
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
- # ── helpers ────────────────────────────────────────────────────────────────
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
- http_health_check := object.get(object.get(object.get(input, "spec", {}), "config", {}), "httpHealthCheck", {})
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", "")
@@ -0,0 +1,99 @@
1
+ # Тести для `abie.health_check_policy`. Запуск:
2
+ # conftest verify -p npm/policy/abie/health_check_policy
3
+ package abie.health_check_policy_test
4
+
5
+ import rego.v1
6
+
7
+ import data.abie.health_check_policy
8
+
9
+ valid_hcp := {
10
+ "apiVersion": "networking.gke.io/v1",
11
+ "kind": "HealthCheckPolicy",
12
+ "metadata": {"name": "api"},
13
+ "spec": {
14
+ "default": {"config": {
15
+ "type": "HTTP",
16
+ "httpHealthCheck": {"requestPath": "/healthz", "port": 8080},
17
+ }},
18
+ "targetRef": {"group": "", "kind": "Service", "name": "api-hl"},
19
+ },
20
+ }
21
+
22
+ # ── happy path ────────────────────────────────────────────────────────────
23
+
24
+ test_allow_canonical if {
25
+ count(health_check_policy.deny) == 0 with input as valid_hcp
26
+ }
27
+
28
+ # ── apiVersion ────────────────────────────────────────────────────────────
29
+
30
+ test_deny_wrong_api_version if {
31
+ bad := json.patch(valid_hcp, [{"op": "replace", "path": "/apiVersion", "value": "networking.gke.io/v1beta1"}])
32
+ count(health_check_policy.deny) > 0 with input as bad
33
+ }
34
+
35
+ # ── metadata.name ─────────────────────────────────────────────────────────
36
+
37
+ test_deny_empty_name if {
38
+ bad := json.patch(valid_hcp, [{"op": "replace", "path": "/metadata/name", "value": ""}])
39
+ count(health_check_policy.deny) > 0 with input as bad
40
+ }
41
+
42
+ # ── spec.default.config.type ──────────────────────────────────────────────
43
+
44
+ test_deny_config_type_not_http if {
45
+ bad := json.patch(valid_hcp, [{"op": "replace", "path": "/spec/default/config/type", "value": "TCP"}])
46
+ count(health_check_policy.deny) > 0 with input as bad
47
+ }
48
+
49
+ # ── requestPath ───────────────────────────────────────────────────────────
50
+
51
+ test_deny_empty_request_path if {
52
+ bad := json.patch(valid_hcp, [{
53
+ "op": "replace",
54
+ "path": "/spec/default/config/httpHealthCheck/requestPath",
55
+ "value": "",
56
+ }])
57
+ count(health_check_policy.deny) > 0 with input as bad
58
+ }
59
+
60
+ test_deny_request_path_without_slash if {
61
+ bad := json.patch(valid_hcp, [{
62
+ "op": "replace",
63
+ "path": "/spec/default/config/httpHealthCheck/requestPath",
64
+ "value": "healthz",
65
+ }])
66
+ count(health_check_policy.deny) > 0 with input as bad
67
+ }
68
+
69
+ # ── port ──────────────────────────────────────────────────────────────────
70
+
71
+ test_deny_port_not_8080 if {
72
+ bad := json.patch(valid_hcp, [{
73
+ "op": "replace",
74
+ "path": "/spec/default/config/httpHealthCheck/port",
75
+ "value": 9090,
76
+ }])
77
+ count(health_check_policy.deny) > 0 with input as bad
78
+ }
79
+
80
+ # ── targetRef ─────────────────────────────────────────────────────────────
81
+
82
+ test_deny_target_ref_kind_not_service if {
83
+ bad := json.patch(valid_hcp, [{"op": "replace", "path": "/spec/targetRef/kind", "value": "Gateway"}])
84
+ count(health_check_policy.deny) > 0 with input as bad
85
+ }
86
+
87
+ test_deny_target_ref_name_without_hl if {
88
+ bad := json.patch(valid_hcp, [{"op": "replace", "path": "/spec/targetRef/name", "value": "api"}])
89
+ count(health_check_policy.deny) > 0 with input as bad
90
+ }
91
+
92
+ # Не HCP — пакет не діє.
93
+ test_allow_other_kind if {
94
+ count(health_check_policy.deny) == 0 with input as {
95
+ "apiVersion": "v1",
96
+ "kind": "ConfigMap",
97
+ "metadata": {"name": "x"},
98
+ }
99
+ }
@@ -0,0 +1,64 @@
1
+ # Тести для `abie.http_route_base`. Запуск:
2
+ # conftest verify -p npm/policy/abie/http_route_base
3
+ package abie.http_route_base_test
4
+
5
+ import rego.v1
6
+
7
+ import data.abie.http_route_base
8
+
9
+ mk_route(hostnames) := {
10
+ "apiVersion": "gateway.networking.k8s.io/v1",
11
+ "kind": "HTTPRoute",
12
+ "metadata": {"name": "r", "namespace": "dev"},
13
+ "spec": {"hostnames": hostnames},
14
+ }
15
+
16
+ # ── allow ────────────────────────────────────────────────────────────────
17
+
18
+ test_allow_apex if {
19
+ count(http_route_base.deny) == 0 with input as mk_route(["aiml.live"])
20
+ }
21
+
22
+ test_allow_subdomain if {
23
+ count(http_route_base.deny) == 0 with input as mk_route(["api.aiml.live"])
24
+ }
25
+
26
+ test_allow_wildcard if {
27
+ count(http_route_base.deny) == 0 with input as mk_route(["*.aiml.live"])
28
+ }
29
+
30
+ test_allow_uppercase_apex if {
31
+ count(http_route_base.deny) == 0 with input as mk_route(["AIML.LIVE"])
32
+ }
33
+
34
+ test_allow_multiple_subdomains if {
35
+ count(http_route_base.deny) == 0 with input as mk_route(["api.aiml.live", "admin.aiml.live"])
36
+ }
37
+
38
+ # ── deny ─────────────────────────────────────────────────────────────────
39
+
40
+ test_deny_other_apex if {
41
+ count(http_route_base.deny) > 0 with input as mk_route(["example.com"])
42
+ }
43
+
44
+ test_deny_wrong_subdomain if {
45
+ count(http_route_base.deny) > 0 with input as mk_route(["api.example.com"])
46
+ }
47
+
48
+ test_deny_mixed_one_bad if {
49
+ count(http_route_base.deny) > 0 with input as mk_route(["api.aiml.live", "evil.com"])
50
+ }
51
+
52
+ test_deny_aiml_live_substring if {
53
+ # "aiml.live.example.com" не має закінчуватись на ".aiml.live" — це інший домен.
54
+ count(http_route_base.deny) > 0 with input as mk_route(["aiml.live.example.com"])
55
+ }
56
+
57
+ # Не HTTPRoute — пакет не діє.
58
+ test_allow_non_httproute if {
59
+ count(http_route_base.deny) == 0 with input as {
60
+ "apiVersion": "v1",
61
+ "kind": "Service",
62
+ "metadata": {"name": "x"},
63
+ }
64
+ }
@@ -117,11 +117,11 @@ is_kustomization if {
117
117
  }
118
118
 
119
119
  resources_present if {
120
- _ := input.resources
120
+ "resources" in object.keys(input)
121
121
  }
122
122
 
123
123
  patches_present if {
124
- _ := input.patches
124
+ "patches" in object.keys(input)
125
125
  }
126
126
 
127
127
  # Список непорожніх рядкових шляхів resources у порядку файлу (для повідомлення).
@@ -215,7 +215,8 @@ has_non_empty_cpu_request(container) if {
215
215
 
216
216
  # Чи у контейнера в реальності присутнє поле resources.requests.cpu (хай і порожнє).
217
217
  has_cpu_field(container) if {
218
- _ := container.resources.requests.cpu
218
+ requests := object.get(object.get(container, "resources", {}), "requests", {})
219
+ "cpu" in object.keys(requests)
219
220
  }
220
221
 
221
222
  # Чи у контейнера є непорожнє resources.requests.memory (рядок або число > 0).
@@ -233,7 +234,8 @@ has_non_empty_memory_request(container) if {
233
234
 
234
235
  # Чи у контейнера в реальності присутнє поле resources.requests.memory.
235
236
  has_memory_field(container) if {
236
- _ := container.resources.requests.memory
237
+ requests := object.get(object.get(container, "resources", {}), "requests", {})
238
+ "memory" in object.keys(requests)
237
239
  }
238
240
 
239
241
  # Чи рядок `image` посилається на репозиторій `hasura/graphql-engine` (з тегом