@7n/rules-ci-github 1.2.2 → 1.3.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.3.1] - 2026-07-18
4
+
5
+ ### Fixed
6
+
7
+ - ga/workflows: `runConftestBatch` викликається з `await` — ядро зробило його async у @7n/rules 1.14 (#109), без await детектор падав «violations is not iterable» і валив увесь lint-прогін
8
+
9
+ ## [1.3.0] - 2026-07-17
10
+
11
+ ### Changed
12
+
13
+ - ga/workflow_common: дозволено режим release-серіалізації concurrency — статичний group + cancel-in-progress: false
14
+
3
15
  ## [1.2.2] - 2026-07-16
4
16
 
5
17
  ### Changed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@7n/rules-ci-github",
3
- "version": "1.2.2",
3
+ "version": "1.3.1",
4
4
  "description": "\u041f\u043b\u0430\u0433\u0456\u043d @7n/rules: \u043a\u0430\u043d\u043e\u043d GitHub Actions (.github/workflows) \u2014 \u043e\u0431\u043e\u0432'\u044f\u0437\u043a\u043e\u0432\u0456 workflow, zizmor, VS Code",
5
5
  "keywords": [
6
6
  "github-actions",
@@ -6,7 +6,10 @@ Rego-пакет: `ga.workflow_common`
6
6
 
7
7
  ### Що перевіряється
8
8
 
9
- - **concurrency** — обов'язкова наявність блоку; `group` = `${{ github.ref }}-${{ github.workflow }}`; `cancel-in-progress: true`
9
+ - **concurrency** — обов'язкова наявність блоку; два допустимі режими:
10
+ - канонічний CI: `group` = `${{ github.ref }}-${{ github.workflow }}` + `cancel-in-progress: true`
11
+ - release-серіалізація: статичний `group` (без `${{ }}`-виразів, глобальний lock) + `cancel-in-progress: false` — для workflow, що піднімають версію/тег/CHANGELOG і не можуть бути скасовані на півдорозі; відсутній `cancel-in-progress` = `false` (семантика GitHub)
12
+ - змішані комбінації (статичний `group` + `true`, per-ref `group` + `false`) — заборонені
10
13
  - **Заборонені кроки** — `oven-sh/setup-bun`, `actions/cache`, `bun install` у будь-якому `uses:`/`run:` (мають бути інкапсульовані у composite `setup-bun-deps`)
11
14
  - **Порядок кроків** — якщо є `setup-bun-deps`, перед ним обов'язковий `actions/checkout@`
12
15
  - **Shell-продовження** — `\` перед переносом рядка у `run:` заборонено; треба `run: >-`
@@ -18,6 +18,10 @@ import rego.v1
18
18
 
19
19
  expected_concurrency_group := concat("", ["$", "{{ github.ref }}-$", "{{ github.workflow }}"])
20
20
 
21
+ # Маркер GitHub-виразу `${{` — через concat (та сама причина, що й у
22
+ # expected_concurrency_group): без сирого літерала в rego-джерелі.
23
+ gh_expr_marker := concat("", ["$", "{{"])
24
+
21
25
  # Локальні composite setup-bun-deps (ga.mdc) — два варіанти шляху:
22
26
  # `.github/actions/...` (cursor-репо) і `npm/github-actions/...` (npm-пакет dev-локально).
23
27
  local_setup_bun_markers := {
@@ -45,6 +49,16 @@ concurrency_missing_template := concat(" ", [
45
49
  "додай concurrency.group: %s і cancel-in-progress: true (ga.mdc)",
46
50
  ])
47
51
 
52
+ concurrency_group_template := concat(" ", [
53
+ "concurrency.group має бути %s,",
54
+ "або статичний рядок-lock для release-серіалізації (ga.mdc)",
55
+ ])
56
+
57
+ concurrency_release_cancel_template := concat(" ", [
58
+ "статичний concurrency.group — release-серіалізація: скасування на півдорозі",
59
+ "лишає репо в неконсистентному стані, тому cancel-in-progress: false (ga.mdc)",
60
+ ])
61
+
48
62
  shell_continuation_template := concat(" ", [
49
63
  "jobs.%s.steps[%d]: у run заборонено продовження рядків через зворотний сліш;",
50
64
  "оформи як folded block (run: >-) (ga.mdc)",
@@ -147,6 +161,14 @@ deny contains msg if {
147
161
  # Дублює окремі per-workflow перевірки для clean-ga-workflows / clean-merged-branch /
148
162
  # lint-ga / git-ai, але вкриває й решту workflow-файлів (apply-k8s, lint-js, …),
149
163
  # для яких поки немає виділеної polysi.
164
+ #
165
+ # Допустимі рівно два когерентні режими:
166
+ # 1. канонічний CI: per-ref group + cancel-in-progress: true;
167
+ # 2. release-серіалізація: статичний group (глобальний lock, без `${{ }}`-виразів)
168
+ # + cancel-in-progress: false — для workflow, що піднімають версію/тег і не
169
+ # можуть бути скасовані на півдорозі (per-ref lock не захищає від двох
170
+ # одночасних push-подій, які обидві піднімають версію).
171
+ # Змішані комбінації (статичний group + true, per-ref + false) — заборонені.
150
172
 
151
173
  deny contains msg if {
152
174
  # `object.get(…, default)` повертає `false` коли ключа немає — інакше `not is_object(…)`
@@ -157,16 +179,25 @@ deny contains msg if {
157
179
 
158
180
  deny contains msg if {
159
181
  is_object(object.get(input, "concurrency", false))
160
- input.concurrency.group != expected_concurrency_group
161
- msg := sprintf("concurrency.group має бути %s (ga.mdc)", [expected_concurrency_group])
182
+ group := object.get(input.concurrency, "group", "")
183
+ group != expected_concurrency_group
184
+ not is_release_lock_group(group)
185
+ msg := sprintf(concurrency_group_template, [expected_concurrency_group])
162
186
  }
163
187
 
164
188
  deny contains msg if {
165
189
  is_object(object.get(input, "concurrency", false))
166
- input.concurrency["cancel-in-progress"] != true
190
+ input.concurrency.group == expected_concurrency_group
191
+ object.get(input.concurrency, "cancel-in-progress", false) != true
167
192
  msg := "concurrency.cancel-in-progress має бути true (ga.mdc)"
168
193
  }
169
194
 
195
+ deny contains concurrency_release_cancel_template if {
196
+ is_object(object.get(input, "concurrency", false))
197
+ is_release_lock_group(object.get(input.concurrency, "group", ""))
198
+ object.get(input.concurrency, "cancel-in-progress", false) != false
199
+ }
200
+
170
201
  # ── deny: мінімальні версії marketplace actions у `uses:` ─────────────────
171
202
  #
172
203
  # Канон — `template/uses-min-versions.snippet.json` (через --data).
@@ -191,6 +222,14 @@ deny contains msg if {
191
222
 
192
223
  # ── helpers ────────────────────────────────────────────────────────────────
193
224
 
225
+ # Статичний непорожній group без `${{ }}`-виразів — глобальний lock
226
+ # (режим release-серіалізації concurrency).
227
+ is_release_lock_group(group) if {
228
+ is_string(group)
229
+ group != ""
230
+ not contains(group, gh_expr_marker)
231
+ }
232
+
194
233
  # Об'єднаний рядок `uses` + `run` для одного кроку — для substring-пошуку
195
234
  # заборонених патернів. `run` може бути рядком або масивом рядків (YAML).
196
235
  step_uses_or_run_blob(step) := blob if {
@@ -3,7 +3,7 @@ type: JS Module
3
3
  title: main.mjs
4
4
  resource: plugins/ci-github/rules/ga/workflows/main.mjs
5
5
  docgen:
6
- crc: d1b152f8
6
+ crc: 0e45c8c5
7
7
  ---
8
8
 
9
9
  ## Огляд
@@ -331,7 +331,7 @@ async function runAllGaRego(wfDir, ymlWorkflows, cwd, pass, fail) {
331
331
  const concernDir = join(GA_POLICY_DIR, target.policyDirRel.split('/', 2)[1])
332
332
  const tpl = await loadTemplate(concernDir)
333
333
  const templateData = tpl[basename(target.workflow)]
334
- const violations = runConftestBatch({
334
+ const violations = await runConftestBatch({
335
335
  policyDirRel: target.policyDirRel,
336
336
  // Правило живе у плагіні поза вбудованим rules/ ядра — резолвимо policy-теку від себе.
337
337
  policyDirAbs: concernDir,
@@ -351,7 +351,7 @@ async function runAllGaRego(wfDir, ymlWorkflows, cwd, pass, fail) {
351
351
  const workflowCommonDir = join(GA_POLICY_DIR, 'workflow_common')
352
352
  const workflowCommonTpl = await loadTemplate(workflowCommonDir)
353
353
  const usesMinVersionsSnippet = workflowCommonTpl['uses-min-versions']?.snippet
354
- const violations = runConftestBatch({
354
+ const violations = await runConftestBatch({
355
355
  policyDirRel: 'ga/workflow_common',
356
356
  policyDirAbs: workflowCommonDir,
357
357
  namespace: 'ga.workflow_common',