@7n/rules 1.15.0 → 1.16.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.16.1] - 2026-07-18
4
+
5
+ ### Fixed
6
+
7
+ - taze-оркестратор на репо без кореневого package.json (чисто-Python/Rust): npm/bun-гілка тихо пропускається (лог + без npm-рядків у звіті) замість смертельного `bun install → exit 1` до екосистемних провайдерів. Знайдено live-прогоном lang-python плагіна на реальному Python-репо
8
+
9
+ ## [1.16.0] - 2026-07-18
10
+
11
+ ### Added
12
+
13
+ - k8s: новий концерн dremio_logging — logback.xml Dremio Helm-чарту має містити WARN-оверрайди для шести відомо-шумних loggerів (rego, XML через conftest, detection-only)
14
+
3
15
  ## [1.15.0] - 2026-07-18
4
16
 
5
17
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@7n/rules",
3
- "version": "1.15.0",
3
+ "version": "1.16.1",
4
4
  "description": "CLI еталонних правил і skills (префікс n-): синк у репозиторій, дельта-lint, конформність",
5
5
  "keywords": [
6
6
  "cli",
@@ -0,0 +1,8 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@7n/rules/schemas/concern.json",
3
+ "policy": {
4
+ "files": {
5
+ "walkGlob": "**/dremio_v2/config/logback.xml"
6
+ }
7
+ }
8
+ }
@@ -0,0 +1,31 @@
1
+ ## WARN-оверрайди шумних loggerів у logback.xml Dremio
2
+
3
+ Rego-пакет: `k8s.dremio_logging`
4
+
5
+ **Цільові файли:** `**/dremio_v2/config/logback.xml` — vendored-конфіг логування Dremio Helm-чарту. Це XML (не YAML): conftest парсить його нативно (парсер інферується за розширенням). Кожна env-копія (`dev/dremio_v2/…`, `ua/dremio_v2/…` тощо) перевіряється окремо — без крос-файлової дедуплікації. Cross-file gating (як у `hasura_configmap`) не потрібен: шлях сам ідентифікує Dremio-чарт.
6
+
7
+ **Що перевіряється:** для кожного FQCN зі списку нижче має існувати `<logger name="<FQCN>" level="warn"/>` (рівень case-insensitive; «строгіше» — `error`/`off` — теж ок). Кожен запис-дублікат для FQCN має задовольняти вимогу окремо (конфліктні дублікати — порушення: logback бере останній, конфіг має бути однозначним).
8
+
9
+ | FQCN |
10
+ |------|
11
+ | `com.dremio.sabot.exec.fragment.FragmentExecutor` |
12
+ | `com.dremio.sabot.exec.fragment.FragmentStatusReporter` |
13
+ | `com.dremio.sabot.exec.QueryTicket` |
14
+ | `com.dremio.service.reflection.descriptor.MaterializationCache` |
15
+ | `com.dremio.exec.planner.plancache.PlanCacheSynchronizer` |
16
+ | `com.dremio.exec.planner.plancache.CacheRefresher` |
17
+
18
+ **Чому:** ці loggerи (усі на одному `planning-cache-refresher` треді; підтверджено живою перевіркою в проді) пишуть INFO на кожен запит/фоновий цикл і домінують обсяг логів у Cloud Logging без діагностичної цінності понад WARN. Список фіксований — не розширюй «про запас»; нові loggerи — окремою ітерацією.
19
+
20
+ **Приклад валідного фрагмента:**
21
+
22
+ ```xml
23
+ <logger name="com.dremio.sabot.exec.fragment.FragmentExecutor" level="warn"/>
24
+ <logger name="com.dremio.sabot.exec.fragment.FragmentStatusReporter" level="warn"/>
25
+ <logger name="com.dremio.sabot.exec.QueryTicket" level="warn"/>
26
+ <logger name="com.dremio.service.reflection.descriptor.MaterializationCache" level="warn"/>
27
+ <logger name="com.dremio.exec.planner.plancache.PlanCacheSynchronizer" level="warn"/>
28
+ <logger name="com.dremio.exec.planner.plancache.CacheRefresher" level="warn"/>
29
+ ```
30
+
31
+ **Autofix:** відсутній (detection-only, як `hasura_httproute`) — XML-трансформ складніший за YAML `setIn`; T0-fix, за потреби, окремою ітерацією.
@@ -0,0 +1,86 @@
1
+ # WARN-оверрайди шумних loggerів у logback.xml Dremio Helm-чарту.
2
+ #
3
+ # Проблема: перелічені нижче FQCN (усі живуть на одному `planning-cache-refresher`
4
+ # треді) пишуть INFO-логи на кожен запит/фоновий цикл і домінують обсяг у
5
+ # Cloud Logging без діагностичної цінності понад WARN. Вимога: у кожній копії
6
+ # vendored-конфігу `dremio_v2/config/logback.xml` (наприклад `dev/…` і `ua/…` —
7
+ # кожна env-копія перевіряється окремо, без крос-файлової дедуплікації) має бути
8
+ # `<logger name="<FQCN>" level="warn"/>` для кожного FQCN зі списку
9
+ # `required_loggers`, з рівнем "warn" або строгішим ("error", "off").
10
+ #
11
+ # Вхід — XML: conftest інферить парсер за розширенням `.xml`. Конвенція
12
+ # представлення (див. `conftest parse`):
13
+ # - атрибути мають префікс "-" (`-name`, `-level`);
14
+ # - один дочірній елемент → об'єкт, кілька → масив. Тому `<logger>`
15
+ # нормалізується у `loggers` (завжди масив).
16
+ #
17
+ # Запуск (локально):
18
+ # conftest test path/to/dremio_v2/config/logback.xml \
19
+ # -p npm/rules/k8s/dremio_logging \
20
+ # --namespace k8s.dremio_logging
21
+ #
22
+ # Список FQCN підтверджено живою перевіркою в проді — не розширюй «про запас».
23
+ #
24
+ # Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
25
+ # Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`.
26
+ package k8s.dremio_logging
27
+
28
+ import rego.v1
29
+
30
+ # Обов'язкові WARN-оверрайди (FQCN відомо-шумних loggerів Dremio).
31
+ required_loggers := {
32
+ "com.dremio.sabot.exec.fragment.FragmentExecutor",
33
+ "com.dremio.sabot.exec.fragment.FragmentStatusReporter",
34
+ "com.dremio.sabot.exec.QueryTicket",
35
+ "com.dremio.service.reflection.descriptor.MaterializationCache",
36
+ "com.dremio.exec.planner.plancache.PlanCacheSynchronizer",
37
+ "com.dremio.exec.planner.plancache.CacheRefresher",
38
+ }
39
+
40
+ # Рівні logback, що задовольняють вимогу «warn або строгіше».
41
+ allowed_levels := {"warn", "error", "off"}
42
+
43
+ # Нормалізація <logger>: один елемент XML-парсер віддає об'єктом, кілька — масивом.
44
+ # Тіла взаємовиключні (is_array/is_object), default покриває відсутність елемента
45
+ # (зокрема logback.xml без <configuration>-кореня → порожній список → deny на
46
+ # кожен обов'язковий FQCN).
47
+ default loggers := []
48
+
49
+ loggers := input.configuration.logger if is_array(input.configuration.logger)
50
+
51
+ loggers := [input.configuration.logger] if is_object(input.configuration.logger)
52
+
53
+ # Усі записи <logger> для заданого FQCN (записи без атрибута name пропускаються).
54
+ entries_for(name) := [l |
55
+ some l in loggers
56
+ l["-name"] == name
57
+ ]
58
+
59
+ # Нормалізований рівень запису; undefined, якщо атрибут level відсутній або не рядок.
60
+ entry_level(l) := lower(trim_space(l["-level"])) if is_string(l["-level"])
61
+
62
+ # Запис задовольняє вимогу: level читається і є warn/error/off.
63
+ entry_ok(l) if entry_level(l) in allowed_levels
64
+
65
+ # FQCN узагалі не має <logger>-оверрайду.
66
+ deny contains msg if {
67
+ some name in required_loggers
68
+ count(entries_for(name)) == 0
69
+ msg := sprintf(
70
+ "logback.xml: додай <logger name=\"%s\" level=\"warn\"/> — шумний Dremio-logger заливає Cloud Logging (k8s.mdc)",
71
+ [name],
72
+ )
73
+ }
74
+
75
+ # Оверрайд є, але слабший за warn (info/debug/trace), без level або з не-рядковим
76
+ # level. Вимога до КОЖНОГО запису з цим FQCN: дублікати з конфліктними рівнями
77
+ # (logback бере останній) — теж порушення, конфіг має бути однозначним.
78
+ deny contains msg if {
79
+ some name in required_loggers
80
+ some l in entries_for(name)
81
+ not entry_ok(l)
82
+ msg := sprintf(
83
+ "logback.xml: <logger name=\"%s\"> має рівень %v — постав level=\"warn\" або строгіше (error/off) (k8s.mdc)",
84
+ [name, object.get(l, "-level", "«відсутній»")],
85
+ )
86
+ }
@@ -3,7 +3,7 @@ type: JS Module
3
3
  title: orchestrate.mjs
4
4
  resource: npm/skills/taze/js/orchestrate.mjs
5
5
  docgen:
6
- crc: a1a1ade0
6
+ crc: 256c4e77
7
7
  model: openai-codex/gpt-5.4-mini
8
8
  score: 100
9
9
  issues: judge:inaccurate:0.98
@@ -36,5 +36,6 @@ docgen:
36
36
 
37
37
  - Виконує файлові операції (копіювання/видалення бекапів) і запускає зовнішні команди — НЕ read-only.
38
38
  - Перед будь-якою мутацією перевіряє, що `cwd` — ізольований worktree (`assertRunningInWorktree`), інакше кидає виняток.
39
+ - npm/bun-гілка активна лише за кореневим package.json: на чисто-Python/Rust репо вона тихо пропускається (лог + без npm-рядків у звіті), а не валить прогін через `bun install → exit 1`.
39
40
  - Виняток усередині одного провайдера (bump/diff/команда) не зупиняє інших — фіксується в `error` запису екосистеми й у звіті; `ok` результату тоді false.
40
41
  - Падіння одного пакета в ізольованому виклику раннера не втрачає прогрес по інших записах.
@@ -289,19 +289,19 @@ function appendEcosystemSection(lines, eco) {
289
289
  * minorPatch: number,
290
290
  * totalChanged: number,
291
291
  * results: Array<{pkg:string, workspace:string, from:string, to:string, ok:boolean, error:string|null}>,
292
- * ecosystems?: Array<object>
293
- * }} args дані звіту (`ecosystems` — записи з `runEcosystem`, по одному на провайдера)
292
+ * ecosystems?: Array<object>,
293
+ * npmPresent?: boolean
294
+ * }} args дані звіту (`ecosystems` — записи з `runEcosystem`, по одному на провайдера;
295
+ * `npmPresent: false` — репо без кореневого package.json, npm-рядки не друкуються)
294
296
  * @returns {string} markdown-звіт
295
297
  */
296
- export function formatReport({ minorPatch, totalChanged, results, ecosystems = [] }) {
297
- const lines = [
298
- '## taze: підсумок',
299
- '',
300
- `- **Оновлено (minor/patch):** ${minorPatch}`,
301
- `- **Major-оновлення:** ${results.length}`
302
- ]
303
- for (const r of results) {
304
- lines.push(formatResultLine(r, r.workspace))
298
+ export function formatReport({ minorPatch, totalChanged, results, ecosystems = [], npmPresent = true }) {
299
+ const lines = ['## taze: підсумок', '']
300
+ if (npmPresent) {
301
+ lines.push(`- **Оновлено (minor/patch):** ${minorPatch}`, `- **Major-оновлення:** ${results.length}`)
302
+ for (const r of results) {
303
+ lines.push(formatResultLine(r, r.workspace))
304
+ }
305
305
  }
306
306
 
307
307
  let ecosystemsTotal = 0
@@ -339,27 +339,37 @@ export async function runTazeOrchestrator(options = {}) {
339
339
 
340
340
  assertRunningInWorktree(cwd, spawnFn)
341
341
 
342
- log('📦 Бекап package.json...')
343
- const backedUpWorkspaces = await backupWorkspacePackageFiles(cwd, deps)
342
+ // npm/bun-гілка активна лише за кореневим package.json — на чисто-Python/Rust
343
+ // репо `bun install` падає з exit 1, і без цього гейта весь прогін гинув би
344
+ // до екосистемних провайдерів. Той самий принцип «тиші», що й для мовних
345
+ // екосистем: немає сигналу — немає ані кроків, ані згадки у звіті.
346
+ const npmPresent = existsSync(join(cwd, 'package.json'))
347
+ let diff = { major: [], minorPatch: 0, totalChanged: 0 }
348
+ const results = []
349
+ if (npmPresent) {
350
+ log('📦 Бекап package.json...')
351
+ const backedUpWorkspaces = await backupWorkspacePackageFiles(cwd, deps)
344
352
 
345
- log('⬆️ bunx taze -w -r latest...')
346
- runCommand('bunx', ['taze', '-w', '-r', 'latest'], cwd, spawnFn)
347
- log('📥 bun install...')
348
- runCommand('bun', ['install'], cwd, spawnFn)
353
+ log('⬆️ bunx taze -w -r latest...')
354
+ runCommand('bunx', ['taze', '-w', '-r', 'latest'], cwd, spawnFn)
355
+ log('📥 bun install...')
356
+ runCommand('bun', ['install'], cwd, spawnFn)
349
357
 
350
- const collectDiff = deps.collectTazeDiff ?? collectTazeDiff
351
- const diff = await collectDiff(cwd)
352
- log(`🔍 diff: ${diff.major.length} major, ${diff.minorPatch} minor/patch`)
358
+ const collectDiff = deps.collectTazeDiff ?? collectTazeDiff
359
+ diff = await collectDiff(cwd)
360
+ log(`🔍 diff: ${diff.major.length} major, ${diff.minorPatch} minor/patch`)
353
361
 
354
- const results = []
355
- for (const entry of diff.major) {
356
- log(`🔧 ${entry.pkg} (${entry.workspace}): ${entry.from} → ${entry.to}...`)
357
- const outcome = await call(runner, buildDependencyPrompt(entry), cwd, deps)
358
- results.push({ ...entry, ...outcome })
359
- log(outcome.ok ? ` ✅ ${entry.pkg}` : ` ❌ ${entry.pkg}: ${outcome.error}`)
360
- }
362
+ for (const entry of diff.major) {
363
+ log(`🔧 ${entry.pkg} (${entry.workspace}): ${entry.from} ${entry.to}...`)
364
+ const outcome = await call(runner, buildDependencyPrompt(entry), cwd, deps)
365
+ results.push({ ...entry, ...outcome })
366
+ log(outcome.ok ? ` ✅ ${entry.pkg}` : ` ❌ ${entry.pkg}: ${outcome.error}`)
367
+ }
361
368
 
362
- await cleanupBackups(cwd, backedUpWorkspaces, deps)
369
+ await cleanupBackups(cwd, backedUpWorkspaces, deps)
370
+ } else {
371
+ log('⏭ npm/bun: кореневого package.json немає — гілка пропущена')
372
+ }
363
373
 
364
374
  const providers = deps.ecosystemProviders ?? [rustProvider, ...(await loadPluginTazeProviders(cwd, log, deps))]
365
375
  const ecosystems = []
@@ -367,7 +377,13 @@ export async function runTazeOrchestrator(options = {}) {
367
377
  ecosystems.push(await runEcosystem(provider, { cwd, runner, log, deps, spawnFn, call }))
368
378
  }
369
379
 
370
- const report = formatReport({ minorPatch: diff.minorPatch, totalChanged: diff.totalChanged, results, ecosystems })
380
+ const report = formatReport({
381
+ minorPatch: diff.minorPatch,
382
+ totalChanged: diff.totalChanged,
383
+ results,
384
+ ecosystems,
385
+ npmPresent
386
+ })
371
387
  log(report)
372
388
 
373
389
  const ecosystemsOk = ecosystems.every(eco => eco.error === null && eco.results.every(r => r.ok))