@7n/rules-ci-github 2.4.5 → 2.4.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.4.7] - 2026-08-29
4
+
5
+ ### Changed
6
+
7
+ - `ensure_step_uses_key_present` прибрано цілком — після longhand-правок `lint_ga.rego` (#563) і `lint_text.rego` (#565) жоден вшитий `.rego` не читає `step.uses` незахищеним прямим доступом, тож обхід regorus-бага на боці Rust став мертвим кодом
8
+
9
+ ### Fixed
10
+
11
+ - text/lint_text: `.rego` переписано на longhand-форму `job_uses_set` і позитивне звʼязування актуальних значень перед `not` — workflow без джоби `text` більше не валить пакет у `rego-engine-error`, а віддає канонічний набір `policy-deny`
12
+
13
+ ## [2.4.6] - 2026-08-29
14
+
15
+ ### Fixed
16
+
17
+ - `ga/lint_ga`: `.github/workflows/lint-ga.yml` із джобою, названою НЕ `lint-ga`, більше не дає у wasm-порті (`crates/plugin-ci-github`) одну діагностику `rego-engine-error` замість набору deny. Причина — дві розбіжності `regorus` vs OPA на UNDEFINED-значеннях: shorthand-голова `job_uses_set contains job.steps[_].uses` (і та сама форма в `job_run_blob`) на undefined корені `jobs["lint-ga"]` кидала помилку `eval` замість того щоб дати undefined — переписано в longhand; а `not helper(gha_on.push.branches, …)` з undefined-аргументом у `regorus` давало `not undefined == true` (три зайві deny, коли гілки `on:` немає) — тепер значення звʼязується окремим позитивним виразом. Канон `conftest` НЕ змінився: вихід порту звірено з живим `conftest test` на 11 фікстурах біт-у-біт, набір deny пінить `lint_ga_test.rego` (`test_deny_missing_job`, `test_missing_on_hook_denies_only_pr_paths`) (§2.81)
18
+
3
19
  ## [2.4.5] - 2026-08-29
4
20
 
5
21
  ### Changed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@7n/rules-ci-github",
3
- "version": "2.4.5",
3
+ "version": "2.4.7",
4
4
  "description": "Плагін @7n/rules: канон GitHub Actions (.github/workflows) — обов'язкові workflow, zizmor, VS Code",
5
5
  "keywords": [
6
6
  "github-actions",
@@ -14,10 +14,20 @@ gha_on := object.get(input, "on", object.get(input, "true", {}))
14
14
 
15
15
  job := input.jobs["lint-ga"]
16
16
 
17
- job_uses_set contains job.steps[_].uses
18
-
19
- job_run_blob := concat("\n", [run |
20
- run := job.steps[_].run
17
+ # LONGHAND-форма (`some step in job.steps`), а НЕ shorthand
18
+ # `job_uses_set contains job.steps[_].uses` / `run := job.steps[_].run`:
19
+ # під regorus (wasm-порт `crates/plugin-ci-github`) shorthand кидає помилку
20
+ # eval («item cannot be indexed»), коли `job` undefined — тобто саме тоді,
21
+ # коли `jobs["lint-ga"]` відсутній, — замість того щоб дати undefined і
22
+ # пропустити керування канонічній deny «jobs.lint-ga відсутній».
23
+ # Longhand під regorus дає ТУ САМУ семантику, що й OPA/conftest.
24
+ job_uses_set contains u if {
25
+ some step in job.steps
26
+ u := step.uses
27
+ }
28
+
29
+ job_run_blob := concat("\n", [step.run |
30
+ some step in job.steps
21
31
  ])
22
32
 
23
33
  expected_name := data.template.snippet.name
@@ -46,9 +56,9 @@ expected_uses_set contains u if {
46
56
  # Opt-in для runner-образу, у якому вже є uv та conftest. Мітка runner
47
57
  # залишається `ubuntu-latest` для сумісності, але workflow явно декларує
48
58
  # джерело tooling, щоб policy не приховувала залежність від custom image.
49
- preinstalled_ci_tools if object.get(object.get(job, "env", {}), "NITRA_CI_TOOLS", false) == true
59
+ preinstalled_ci_tools if job.env.NITRA_CI_TOOLS == true
50
60
 
51
- preinstalled_ci_tools if object.get(object.get(job, "env", {}), "NITRA_CI_TOOLS", "false") == "true"
61
+ preinstalled_ci_tools if job.env.NITRA_CI_TOOLS == "true"
52
62
 
53
63
  required_uses_set contains u if {
54
64
  not preinstalled_ci_tools
@@ -70,23 +80,35 @@ expected_run_blob := concat("\n", [r |
70
80
 
71
81
  # ── deny rules ─────────────────────────────────────────────────────────────
72
82
 
83
+ # Три deny нижче спершу ЗВʼЯЗУЮТЬ `actual := gha_on.<hook>.<key>` окремим
84
+ # позитивним виразом, а вже потім негують helper. Це не косметика: у формі
85
+ # `not helper(gha_on.push.branches, …)` undefined-аргумент (гілки `on:` немає)
86
+ # в OPA робить УВЕСЬ вираз undefined (deny мовчить), а в regorus дає
87
+ # `not undefined` == true (deny спрацьовує) — розбіжність порту. Позитивне
88
+ # звʼязування падає однаково в обох двигунах, тож канон і порт збігаються.
89
+ # (`actual_pr_paths` навмисно інший: `object.get(…, [])` — там канон САМЕ
90
+ # СИГНАЛИТЬ про відсутній ключ.)
91
+
73
92
  deny contains msg if {
74
93
  input.name != expected_name
75
94
  msg := sprintf("lint-ga.yml: name має бути \"%v\" (ga.mdc)", [expected_name])
76
95
  }
77
96
 
78
97
  deny contains msg if {
79
- not branches_superset_of(gha_on.push.branches, expected_push_branches)
98
+ actual := gha_on.push.branches
99
+ not branches_superset_of(actual, expected_push_branches)
80
100
  msg := "lint-ga.yml: on.push.branches має містити dev і main (ga.mdc)"
81
101
  }
82
102
 
83
103
  deny contains msg if {
84
- not branches_superset_of(gha_on.pull_request.branches, expected_pr_branches)
104
+ actual := gha_on.pull_request.branches
105
+ not branches_superset_of(actual, expected_pr_branches)
85
106
  msg := "lint-ga.yml: on.pull_request.branches має містити dev і main (ga.mdc)"
86
107
  }
87
108
 
88
109
  deny contains msg if {
89
- not paths_superset_of(gha_on.push.paths, expected_push_paths)
110
+ actual := gha_on.push.paths
111
+ not paths_superset_of(actual, expected_push_paths)
90
112
  msg := "lint-ga.yml: on.push.paths має містити .github/actions/** і .github/workflows/** (ga.mdc)"
91
113
  }
92
114
 
@@ -29,10 +29,19 @@ gha_on := object.get(input, "on", object.get(input, "true", {}))
29
29
 
30
30
  job := input.jobs.text
31
31
 
32
- job_uses_set contains job.steps[_].uses
32
+ # LONGHAND, а не shorthand `job_uses_set contains job.steps[_].uses`:
33
+ # shorthand-форма падає під regorus з «item cannot be indexed», коли `job`
34
+ # undefined (у workflow немає джоби `text`) або коли крок не має `uses:` —
35
+ # замість канонічного набору `deny` порт віддавав одну `rego-engine-error`.
36
+ # Під OPA обидві форми еквівалентні; longhand — спільний знаменник.
37
+ job_uses_set contains u if {
38
+ some step in job.steps
39
+ u := step.uses
40
+ }
33
41
 
34
- job_run_blob := concat("\n", [run |
35
- run := job.steps[_].run
42
+ job_run_blob := concat("\n", [object.get(step, "run", "") |
43
+ some step in job.steps
44
+ object.get(step, "run", "") != ""
36
45
  ])
37
46
 
38
47
  expected_uses_set contains u if {
@@ -41,10 +50,9 @@ expected_uses_set contains u if {
41
50
  u != ""
42
51
  }
43
52
 
44
- expected_run_substrings contains r if {
53
+ expected_run_substrings contains object.get(step, "run", "") if {
45
54
  some step in data.template.snippet.jobs.text.steps
46
- r := object.get(step, "run", "")
47
- r != ""
55
+ object.get(step, "run", "") != ""
48
56
  }
49
57
 
50
58
  deny contains msg if {
@@ -52,18 +60,25 @@ deny contains msg if {
52
60
  msg := sprintf("lint-text.yml: name має бути \"%v\" (text.mdc)", [expected_name])
53
61
  }
54
62
 
63
+ # Актуальне значення звʼязується ОКРЕМИМ позитивним виразом перед `not`:
64
+ # при undefined-аргументі regorus рахує `not helper(undefined, …)` істиною
65
+ # (зайвий deny), тоді як OPA лишає весь body undefined і deny мовчить.
66
+ # Звʼязування вирівнює обидва рушії на OPA-семантиці.
55
67
  deny contains msg if {
56
- not branches_superset_of(gha_on.push.branches, expected_push_branches)
68
+ actual_push_branches := gha_on.push.branches
69
+ not branches_superset_of(actual_push_branches, expected_push_branches)
57
70
  msg := "lint-text.yml: on.push.branches має містити dev і main (text.mdc)"
58
71
  }
59
72
 
60
73
  deny contains msg if {
61
- not branches_superset_of(gha_on.pull_request.branches, expected_pr_branches)
74
+ actual_pr_branches := gha_on.pull_request.branches
75
+ not branches_superset_of(actual_pr_branches, expected_pr_branches)
62
76
  msg := "lint-text.yml: on.pull_request.branches має містити dev і main (text.mdc)"
63
77
  }
64
78
 
65
79
  deny contains msg if {
66
- not paths_superset_of(gha_on.push.paths, expected_push_paths)
80
+ actual_push_paths := gha_on.push.paths
81
+ not paths_superset_of(actual_push_paths, expected_push_paths)
67
82
  msg := "lint-text.yml: on.push.paths має містити очікувані glob-и (text.mdc)"
68
83
  }
69
84