@nitra/cursor 1.8.222 → 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.
@@ -131,6 +131,7 @@ import { isSeq, parseAllDocuments, parseDocument } from 'yaml'
131
131
 
132
132
  import { createCheckReporter } from './utils/check-reporter.mjs'
133
133
  import { loadCursorIgnorePaths } from './utils/load-cursor-config.mjs'
134
+ import { runConftestBatch } from './utils/run-conftest-batch.mjs'
134
135
  import { walkDir } from './utils/walkDir.mjs'
135
136
 
136
137
  /** Версія набору схем yannh — узгоджено з k8s.mdc */
@@ -477,25 +478,9 @@ export function kustomizationResourcesSortedAlphabeticallyViolation(obj) {
477
478
  return null
478
479
  }
479
480
 
480
- /**
481
- * Усі **`kustomization.yaml`**: **`resources`**, відсортовані за en.
482
- * @param {string} root корінь репо
483
- * @param {string[]} yamlFilesAbs yaml під k8s
484
- * @param {(msg: string) => void} fail функція для фіксації порушення
485
- * @returns {Promise<void>} завершується після перевірки всіх kustomization.yaml
486
- */
487
- async function validateKustomizationResourcesSortedAlphabetically(root, yamlFilesAbs, fail) {
488
- for (const kustAbs of yamlFilesAbs.filter(p => basename(p).toLowerCase() === 'kustomization.yaml')) {
489
- const rel = (relative(root, kustAbs) || kustAbs).replaceAll('\\', '/')
490
- const kust = await readFirstYamlObject(kustAbs)
491
- if (kust !== null) {
492
- const v = kustomizationResourcesSortedAlphabeticallyViolation(kust)
493
- if (v !== null) {
494
- fail(`${rel}: ${v}`)
495
- }
496
- }
497
- }
498
- }
481
+ // Plan B: per-document `resources[]` sort у Kustomization — у rego-пакеті
482
+ // `k8s.kustomization`, викликається з `runAllK8sRego` на початку `check()`.
483
+ // JS-orchestrator validateKustomizationResourcesSortedAlphabetically видалено.
499
484
 
500
485
  /**
501
486
  * Лексичне порівняння двох tuple рядків через `localeCompare('en', { sensitivity: 'base' })`.
@@ -662,42 +647,9 @@ export function kustomizationInlinePatchOpsSortedViolation(patchText) {
662
647
  return `inline patch (JSON6902) має бути за алфавітом по path. Зараз: ${paths.join(', ')}; очікувано: ${want.join(', ')} (k8s.mdc)`
663
648
  }
664
649
 
665
- /**
666
- * Усі **`kustomization.yaml`**: `patches[]` відсортовано за `[target.kind, target.name, …]`,
667
- * а вміст inline `patches[i].patch` (де всі ops — `add`/`replace` і шляхи дизʼюнктні) — за `path`.
668
- * @param {string} root корінь репо
669
- * @param {string[]} yamlFilesAbs yaml під k8s
670
- * @param {(msg: string) => void} fail функція для фіксації порушення
671
- * @returns {Promise<void>} завершується після перевірки всіх kustomization.yaml
672
- */
673
- async function validateKustomizationPatchesStructuralSort(root, yamlFilesAbs, fail) {
674
- for (const kustAbs of yamlFilesAbs.filter(p => basename(p).toLowerCase() === 'kustomization.yaml')) {
675
- const rel = (relative(root, kustAbs) || kustAbs).replaceAll('\\', '/')
676
- const kust = await readFirstYamlObject(kustAbs)
677
- if (kust === null) continue
678
- const outer = kustomizationPatchesSortedViolation(kust)
679
- if (outer !== null) fail(`${rel}: ${outer}`)
680
- if (!Array.isArray(kust.patches)) continue
681
- validateInlinePatchesSorted(rel, kust.patches, fail)
682
- }
683
- }
684
-
685
- /**
686
- * Перевіряє, що inline-`patch:` (рядок YAML/JSON) у кожному `patches[i]` має ops у канонічному порядку
687
- * (`add`/`replace` за `path`). Чужі форми (без `patch`-стрічки, з `target` без inline-блока) пропускаються.
688
- * @param {string} rel відносний шлях `kustomization.yaml` для повідомлень
689
- * @param {unknown[]} patches масив `kust.patches` (рекордів)
690
- * @param {(msg: string) => void} fail callback при порушенні
691
- */
692
- function validateInlinePatchesSorted(rel, patches, fail) {
693
- for (const [i, p] of patches.entries()) {
694
- if (p === null || typeof p !== 'object' || Array.isArray(p)) continue
695
- const rec = /** @type {Record<string, unknown>} */ (p)
696
- if (typeof rec.patch !== 'string') continue
697
- const v = kustomizationInlinePatchOpsSortedViolation(rec.patch)
698
- if (v !== null) fail(`${rel}: patches[${i}] (${kustomizationPatchLabel(p, i)}): ${v}`)
699
- }
700
- }
650
+ // Plan B: validateKustomizationPatchesStructuralSort видалено. Per-document
651
+ // `patches[]` sort + inline JSON6902 ops sort — у rego-пакеті `k8s.kustomization`,
652
+ // викликається з `runAllK8sRego`.
701
653
 
702
654
  /**
703
655
  * Шляхи з полів Kustomization для resolve відносно каталогу **`kustomization.yaml`**.
@@ -2285,111 +2237,11 @@ export function json6902PathsWithRemoveAndAddOnSamePath(ops) {
2285
2237
  return out.toSorted((a, b) => a.localeCompare(b))
2286
2238
  }
2287
2239
 
2288
- /**
2289
- * Реєструє порушення, якщо в JSON6902-операціях є **remove** і **add** на один **path**.
2290
- * @param {string} rel відносний шлях до kustomization.yaml
2291
- * @param {string} label фрагмент повідомлення (наприклад `patches[1] inline JSON6902`)
2292
- * @param {string} patchText текст patch
2293
- * @param {(msg: string) => void} fail реєстрація порушення
2294
- * @returns {void}
2295
- */
2296
- function failIfJson6902RemoveAddConflictOnSamePath(rel, label, patchText, fail) {
2297
- const ops = collectJson6902OperationsFromPatchText(patchText)
2298
- const bad = json6902PathsWithRemoveAndAddOnSamePath(ops)
2299
- if (bad.length > 0) {
2300
- fail(`${rel}: ${label}: один path має і remove, і add — оформи як op: replace (k8s.mdc): ${bad.join(', ')}`)
2301
- }
2302
- }
2303
-
2304
- /**
2305
- * Зовнішній patch-файл (масив JSON6902): remove+add на один path.
2306
- * @param {string} rel відносний шлях до kustomization.yaml
2307
- * @param {string} resolved абсолютний шлях до файлу patch
2308
- * @param {string} root корінь репо
2309
- * @param {string} patchRef відносне посилання з kustomization
2310
- * @param {(msg: string) => void} fail реєстрація порушення
2311
- * @returns {Promise<void>}
2312
- */
2313
- async function auditJson6902PatchExternalFile(rel, resolved, root, patchRef, fail) {
2314
- /** @type {import('node:fs').Stats | null} */
2315
- let st
2316
- try {
2317
- st = await stat(resolved)
2318
- } catch {
2319
- st = null
2320
- }
2321
- if (st === null || !st.isFile()) {
2322
- return
2323
- }
2324
- let pRaw
2325
- try {
2326
- pRaw = await readFile(resolved, 'utf8')
2327
- } catch {
2328
- return
2329
- }
2330
- const ops = collectJson6902OperationsFromPatchText(pRaw)
2331
- if (ops.length === 0) {
2332
- return
2333
- }
2334
- const bad = json6902PathsWithRemoveAndAddOnSamePath(ops)
2335
- if (bad.length === 0) {
2336
- return
2337
- }
2338
- const relPatch = (relative(root, resolved) || patchRef).replaceAll('\\', '/')
2339
- fail(
2340
- `${rel}: patch-файл «${relPatch}»: один path має і remove, і add — оформи як op: replace (k8s.mdc): ${bad.join(', ')}`
2341
- )
2342
- }
2343
-
2344
- /**
2345
- * Один елемент **`patches[]`**: inline JSON6902 або зовнішній patch-файл.
2346
- * @param {string} rel відносний шлях до kustomization.yaml
2347
- * @param {Record<string, unknown>} pr об’єкт patch
2348
- * @param {number} patchIdx 1-based індекс у масиві
2349
- * @param {string} kustAbs абсолютний шлях до kustomization.yaml
2350
- * @param {string} rootNorm нормалізований корінь репо
2351
- * @param {string} root корінь репо
2352
- * @param {(msg: string) => void} fail реєстрація порушення
2353
- * @returns {Promise<void>}
2354
- */
2355
- async function auditOneKustomizationJson6902Patch(rel, pr, patchIdx, kustAbs, rootNorm, root, fail) {
2356
- if (typeof pr.patch === 'string' && pr.patch.trim() !== '') {
2357
- failIfJson6902RemoveAddConflictOnSamePath(rel, `patches[${patchIdx}] inline JSON6902`, pr.patch, fail)
2358
- }
2359
- if (typeof pr.path !== 'string' || pr.path.trim() === '') {
2360
- return
2361
- }
2362
- const patchRef = pr.path.trim()
2363
- const resolved = resolve(dirname(kustAbs), patchRef)
2364
- if (!resolvedFilePathIsUnderRoot(rootNorm, resolved) || !existsSync(resolved)) {
2365
- return
2366
- }
2367
- await auditJson6902PatchExternalFile(rel, resolved, root, patchRef, fail)
2368
- }
2369
-
2370
- /**
2371
- * Усі **`patches[]`** у Kustomization: inline та зовнішні файли.
2372
- * @param {string} rel відносний шлях до kustomization.yaml
2373
- * @param {unknown} patches поле **patches**
2374
- * @param {string} kustAbs абсолютний шлях до kustomization.yaml
2375
- * @param {string} rootNorm нормалізований корінь репо
2376
- * @param {string} root корінь репо
2377
- * @param {(msg: string) => void} fail реєстрація порушення
2378
- * @returns {Promise<void>}
2379
- */
2380
- async function auditKustomizationPatchesJson6902(rel, patches, kustAbs, rootNorm, root, fail) {
2381
- if (!Array.isArray(patches)) {
2382
- return
2383
- }
2384
- let patchIdx = 0
2385
- for (const p of patches) {
2386
- patchIdx++
2387
- if (p !== null && typeof p === 'object' && !Array.isArray(p)) {
2388
- const pr = /** @type {Record<string, unknown>} */ (p)
2389
- await auditOneKustomizationJson6902Patch(rel, pr, patchIdx, kustAbs, rootNorm, root, fail)
2390
- }
2391
- }
2392
- }
2240
+ // Plan B: вся audit-ланка JSON6902 (failIfJson6902RemoveAddConflictOnSamePath,
2241
+ // auditJson6902PatchExternalFile, auditOneKustomizationJson6902Patch,
2242
+ // auditKustomizationPatchesJson6902) видалена. Per-document inline JSON6902
2243
+ // remove+add conflict у rego-пакеті `k8s.kustomization`. Зовнішні patch-файли
2244
+ // не охоплені rego-кроком (потребує FS-доступу) — це trade-off Plan B.
2393
2245
 
2394
2246
  /**
2395
2247
  * Один YAML-документ: якщо це Kustomization — перевірка **patches** на JSON6902 remove+add.
@@ -2401,140 +2253,18 @@ async function auditKustomizationPatchesJson6902(rel, patches, kustAbs, rootNorm
2401
2253
  * @param {(msg: string) => void} fail реєстрація порушення
2402
2254
  * @returns {Promise<void>}
2403
2255
  */
2404
- async function auditJson6902ForKustomizationYamlDoc(rel, rootObj, kustAbs, rootNorm, root, fail) {
2405
- const rec = /** @type {Record<string, unknown>} */ (rootObj)
2406
- if (rec.kind !== 'Kustomization') {
2407
- return
2408
- }
2409
- await auditKustomizationPatchesJson6902(rel, rec.patches, kustAbs, rootNorm, root, fail)
2410
- }
2411
-
2412
2256
  /**
2413
- * Один **`kustomization.yaml`**: JSON6902 remove+add на одному path.
2414
- * @param {string} root корінь репозиторію
2415
- * @param {string} rootNorm нормалізований корінь
2416
- * @param {string} kustAbs абсолютний шлях до файлу
2417
- * @param {(msg: string) => void} fail реєстрація порушення
2418
- * @returns {Promise<void>}
2257
+ * Plan B: per-document JSON6902 remove+add conflict у rego-пакеті
2258
+ * `k8s.kustomization`, виклик через `runAllK8sRego`. JS-функції
2259
+ * auditJson6902ForKustomizationYamlDoc, auditJson6902OneKustomizationYamlFile,
2260
+ * validateKustomizationJson6902NoRemoveAddSamePath видалено.
2419
2261
  */
2420
- async function auditJson6902OneKustomizationYamlFile(root, rootNorm, kustAbs, fail) {
2421
- const rel = (relative(root, kustAbs) || kustAbs).replaceAll('\\', '/')
2422
- let raw
2423
- try {
2424
- raw = await readFile(kustAbs, 'utf8')
2425
- } catch (error) {
2426
- const msg = error instanceof Error ? error.message : String(error)
2427
- fail(`${rel}: не вдалося прочитати для перевірки JSON6902 (${msg})`)
2428
- return
2429
- }
2430
- const lines = toLines(raw)
2431
- const body = lines.length > 0 && MODELINE_RE.test(lines[0]) ? yamlBodyAfterModeline(lines) : lines.join('\n')
2432
- /** @type {import('yaml').Document[]} */
2433
- let docs
2434
- try {
2435
- docs = parseAllDocuments(body)
2436
- } catch {
2437
- return
2438
- }
2439
- for (const doc of docs) {
2440
- if (doc.errors.length === 0) {
2441
- const rootObj = doc.toJSON()
2442
- if (rootObj !== null && typeof rootObj === 'object' && !Array.isArray(rootObj)) {
2443
- await auditJson6902ForKustomizationYamlDoc(rel, rootObj, kustAbs, rootNorm, root, fail)
2444
- }
2445
- }
2446
- }
2447
- }
2448
2262
 
2449
- /**
2450
- * Перевіряє всі **`kustomization.yaml`** під **`k8s`**: у inline **`patch`** і у зовнішніх patch-файлах не має бути **remove** і **add** на той самий **path**.
2451
- * @param {string} root корінь репозиторію
2452
- * @param {string[]} yamlFilesAbs абсолютні шляхи до yaml під k8s
2453
- * @param {(msg: string) => void} fail реєстрація порушення
2454
- * @returns {Promise<void>}
2455
- */
2456
- async function validateKustomizationJson6902NoRemoveAddSamePath(root, yamlFilesAbs, fail) {
2457
- const rootNorm = resolve(root)
2458
- for (const kustAbs of yamlFilesAbs.filter(p => basename(p).toLowerCase() === 'kustomization.yaml')) {
2459
- await auditJson6902OneKustomizationYamlFile(root, rootNorm, kustAbs, fail)
2460
- }
2461
- }
2462
-
2463
- /**
2464
- * Заборонений **kind: Ingress** у документі.
2465
- * @param {string} rel відносний шлях до файлу
2466
- * @param {number} docIndex 1-based індекс документа
2467
- * @param {Record<string, unknown>} rec корінь маніфесту
2468
- * @param {(msg: string) => void} fail реєстрація помилки
2469
- * @returns {void}
2470
- */
2471
- function failIfIngressInDocument(rel, docIndex, rec, fail) {
2472
- if (rec.kind !== 'Ingress') {
2473
- return
2474
- }
2475
- fail(
2476
- `${rel}: знайдено kind: Ingress (документ ${docIndex}) — заміни на Gateway API: HTTPRoute (hr.yaml), HealthCheckPolicy (hc.yaml) (див. k8s.mdc)`
2477
- )
2478
- }
2479
-
2480
- /**
2481
- * Чи маніфест використовує заборонений **`apiVersion: autoscaling/v1`** (HPA).
2482
- * Канон — **`autoscaling/v2`** (див. k8s.mdc).
2483
- * @param {unknown} manifest корінь YAML-документа
2484
- * @returns {boolean} true, якщо `apiVersion === 'autoscaling/v1'`
2485
- */
2486
- export function isForbiddenAutoscalingV1Manifest(manifest) {
2487
- if (manifest === null || manifest === undefined || typeof manifest !== 'object' || Array.isArray(manifest))
2488
- return false
2489
- const rec = /** @type {Record<string, unknown>} */ (manifest)
2490
- return rec.apiVersion === 'autoscaling/v1'
2491
- }
2492
-
2493
- /**
2494
- * Заборонена група **`apiVersion: autoscaling/v1`** (HPA) — вимагається міграція на **`autoscaling/v2`**.
2495
- * @param {string} rel відносний шлях до файлу
2496
- * @param {number} docIndex 1-based індекс документа
2497
- * @param {Record<string, unknown>} rec корінь маніфесту
2498
- * @param {(msg: string) => void} fail реєстрація помилки
2499
- * @returns {void}
2500
- */
2501
- function failIfAutoscalingV1InDocument(rel, docIndex, rec, fail) {
2502
- if (!isForbiddenAutoscalingV1Manifest(rec)) {
2503
- return
2504
- }
2505
- const kind = typeof rec.kind === 'string' ? rec.kind : '(невідомо)'
2506
- fail(
2507
- `${rel}: знайдено apiVersion: autoscaling/v1 (документ ${docIndex}, kind: ${kind}) — мігруй на autoscaling/v2 (див. k8s.mdc)`
2508
- )
2509
- }
2510
-
2511
- /**
2512
- * Шукає заборонені маніфести у розібраних документах: **kind: Ingress** і **apiVersion: autoscaling/v1**.
2513
- * @param {string} rel відносний шлях до файлу
2514
- * @param {string} body YAML після modeline
2515
- * @param {(msg: string) => void} fail callback для помилки
2516
- * @returns {void}
2517
- */
2518
- function scanForbiddenManifestsInYamlDocuments(rel, body, fail) {
2519
- /** @type {import('yaml').Document[]} */
2520
- let docs
2521
- try {
2522
- docs = parseAllDocuments(body)
2523
- } catch {
2524
- return
2525
- }
2526
-
2527
- for (const [di, doc] of docs.entries()) {
2528
- if (doc.errors.length === 0) {
2529
- const obj = doc.toJSON()
2530
- if (obj !== null && typeof obj === 'object' && !Array.isArray(obj)) {
2531
- const rec = /** @type {Record<string, unknown>} */ (obj)
2532
- failIfIngressInDocument(rel, di + 1, rec, fail)
2533
- failIfAutoscalingV1InDocument(rel, di + 1, rec, fail)
2534
- }
2535
- }
2536
- }
2537
- }
2263
+ // Plan B: per-document `kind: Ingress` і `apiVersion: autoscaling/v1` заборонено —
2264
+ // у rego-пакеті `k8s.manifest`, виклик через `runAllK8sRego`. JS-функції
2265
+ // failIfIngressInDocument, failIfAutoscalingV1InDocument, scanForbiddenManifestsInYamlDocuments
2266
+ // видалено. `isForbiddenAutoscalingV1Manifest` як публічний predicate теж видалено
2267
+ // (rego авторитативне джерело).
2538
2268
 
2539
2269
  /**
2540
2270
  * Рекомендоване **`resources.requests.cpu`** поза шарем base для підказок у повідомленнях (k8s.mdc).
@@ -3086,15 +2816,6 @@ export function serviceForbiddenGcpAnnotationsViolation(manifest) {
3086
2816
  /** Суфікс **`metadata.name`** headless-сервісу поруч із **`svc.yaml`** (див. k8s.mdc). */
3087
2817
  const SVC_HL_NAME_SUFFIX = '-hl'
3088
2818
 
3089
- /**
3090
- * Kind маршрутів Gateway API, у **`spec`** яких шукаємо **`backendRefs`** / **`backendRef`** до **Service**.
3091
- * @type {Set<string>}
3092
- */
3093
- const GATEWAY_API_ROUTE_KINDS = new Set(['HTTPRoute', 'GRPCRoute', 'TCPRoute', 'TLSRoute', 'UDPRoute'])
3094
-
3095
- /** Префікс **`apiVersion`** стандартних ресурсів Gateway API. */
3096
- const GATEWAY_API_GROUP_PREFIX = 'gateway.networking.k8s.io/'
3097
-
3098
2819
  /**
3099
2820
  * Чи **Service** у **`svc.yaml`** має **`spec.type: ClusterIP`** (k8s.mdc).
3100
2821
  * @param {unknown} manifest корінь YAML-документа
@@ -3281,64 +3002,9 @@ export function collectGatewayApiRouteBackendRefsWithRedundantNamespace(spec, ro
3281
3002
  * @param {(msg: string) => void} fail callback помилки
3282
3003
  * @returns {void}
3283
3004
  */
3284
- function failIfGatewayRouteUsesNonHeadlessService(rel, docIndex, rec, fail) {
3285
- const av = rec.apiVersion
3286
- const kind = rec.kind
3287
- if (
3288
- typeof av !== 'string' ||
3289
- !av.startsWith(GATEWAY_API_GROUP_PREFIX) ||
3290
- typeof kind !== 'string' ||
3291
- !GATEWAY_API_ROUTE_KINDS.has(kind)
3292
- ) {
3293
- return
3294
- }
3295
- const names = collectGatewayApiRouteBackendServiceNames(rec.spec)
3296
- for (const svcName of names) {
3297
- if (!svcName.endsWith(SVC_HL_NAME_SUFFIX)) {
3298
- fail(
3299
- `${rel}: Gateway API ${kind} (документ ${docIndex}): backendRef до Service має вказувати headless-сервіс з суфіксом «${SVC_HL_NAME_SUFFIX}» у name (зараз: «${svcName}»; див. k8s.mdc)`
3300
- )
3301
- }
3302
- }
3303
- const meta = rec.metadata
3304
- if (meta !== null && typeof meta === 'object' && !Array.isArray(meta)) {
3305
- const routeNs = /** @type {Record<string, unknown>} */ (meta).namespace
3306
- if (typeof routeNs === 'string' && routeNs !== '') {
3307
- const redundant = collectGatewayApiRouteBackendRefsWithRedundantNamespace(rec.spec, routeNs)
3308
- for (const svcName of redundant) {
3309
- fail(
3310
- `${rel}: Gateway API ${kind} (документ ${docIndex}): backendRef «${svcName}» має namespace «${routeNs}», що збігається з metadata.namespace маршруту — прибери поле namespace з backendRef (див. k8s.mdc)`
3311
- )
3312
- }
3313
- }
3314
- }
3315
- }
3316
-
3317
- /**
3318
- * Реєструє порушення: маршрути Gateway API мають посилатися на **Service** з суфіксом **`-hl`**.
3319
- * @param {string} rel відносний шлях до файлу
3320
- * @param {string} body YAML після modeline
3321
- * @param {(msg: string) => void} fail callback помилки
3322
- * @returns {void}
3323
- */
3324
- function scanGatewayApiRouteBackendRefsInYamlBody(rel, body, fail) {
3325
- /** @type {import('yaml').Document[]} */
3326
- let docs
3327
- try {
3328
- docs = parseAllDocuments(body)
3329
- } catch {
3330
- return
3331
- }
3332
-
3333
- for (const [di, doc] of docs.entries()) {
3334
- if (doc.errors.length === 0) {
3335
- const obj = doc.toJSON()
3336
- if (obj !== null && typeof obj === 'object' && !Array.isArray(obj)) {
3337
- failIfGatewayRouteUsesNonHeadlessService(rel, di + 1, /** @type {Record<string, unknown>} */ (obj), fail)
3338
- }
3339
- }
3340
- }
3341
- }
3005
+ // Plan B: Gateway API маршрут backendRef з суфіксом `-hl` і redundant namespace —
3006
+ // у rego-пакеті `k8s.gateway`, виклик через `runAllK8sRego`. JS-функції
3007
+ // failIfGatewayRouteUsesNonHeadlessService, scanGatewayApiRouteBackendRefsInYamlBody видалено.
3342
3008
 
3343
3009
  /**
3344
3010
  * Звузити `unknown` до `Record<string, unknown>` (`null`, масиви, примітиви → null).
@@ -3815,20 +3481,28 @@ async function validateHasuraHttpRouteCanon(root, yamlFiles, fail) {
3815
3481
  const { hasuraByDir, httpRoutes } = await collectHasuraDeploymentsAndHttpRoutes(yamlFiles)
3816
3482
  if (hasuraByDir.size === 0 || httpRoutes.length === 0) return
3817
3483
 
3484
+ // JS gating: відберемо файли HTTPRoute, що paired з Hasura-Deployment у тому ж каталозі
3485
+ // (за `metadata.name` HTTPRoute === metadata.name Hasura-Deployment). Per-document валідація
3486
+ // канону 4 правил Hasura — у rego-пакеті `k8s.hasura_httproute`.
3487
+ const pairedFiles = new Set()
3818
3488
  for (const hr of httpRoutes) {
3819
3489
  const meta = asPlainRecord(hr.obj.metadata)
3820
3490
  const name = meta === null ? undefined : meta.name
3821
3491
  const set = typeof name === 'string' && name !== '' ? hasuraByDir.get(hr.dir) : undefined
3822
3492
  if (set !== undefined && typeof name === 'string' && set.has(name)) {
3823
- const v = httpRouteHasuraCanonViolation(hr.obj)
3824
- if (v !== null) {
3825
- const rel = (relative(root, hr.abs) || hr.abs).replaceAll('\\', '/')
3826
- fail(
3827
- `${rel}: HTTPRoute «${name}» (документ ${hr.docIndex}; прив'язано до Hasura-Deployment у тому ж каталозі): ${v}`
3828
- )
3829
- }
3493
+ pairedFiles.add(hr.abs)
3830
3494
  }
3831
3495
  }
3496
+ if (pairedFiles.size === 0) return
3497
+ const violations = runConftestBatch({
3498
+ policyDirRel: 'k8s/hasura_httproute',
3499
+ namespace: 'k8s.hasura_httproute',
3500
+ files: [...pairedFiles]
3501
+ })
3502
+ for (const v of violations) {
3503
+ const rel = (relative(root, v.filename) || v.filename).replaceAll('\\', '/')
3504
+ fail(`${rel}: ${v.message}`)
3505
+ }
3832
3506
  }
3833
3507
 
3834
3508
  /**
@@ -3898,117 +3572,11 @@ export function isK8sBaseManifestYamlPath(rel, baseLower) {
3898
3572
  return K8S_BASE_SEGMENT_RE.test(n)
3899
3573
  }
3900
3574
 
3901
- /**
3902
- * Правила **metadata.namespace** для одного документа.
3903
- * @param {string} rel відносний шлях
3904
- * @param {number} docIndex 1-based
3905
- * @param {unknown} obj корінь документа
3906
- * @param {boolean} skipMetaNs пропуск для **kustomization.yaml**
3907
- * @param {boolean} inBaseManifest файл у **k8s/base/**
3908
- * @param {boolean} kustomizeManaged файл у графі kustomization
3909
- * @param {(msg: string) => void} fail реєстрація помилки
3910
- * @returns {void}
3911
- */
3912
- function failIfK8sPolicyNamespaceRulesViolated(rel, docIndex, obj, skipMetaNs, inBaseManifest, kustomizeManaged, fail) {
3913
- if (skipMetaNs) {
3914
- return
3915
- }
3916
- if (inBaseManifest) {
3917
- const req = metadataNamespaceRequiredViolation(obj, true)
3918
- if (req !== null) {
3919
- fail(`${rel}: документ ${docIndex}: ${req}`)
3920
- }
3921
- return
3922
- }
3923
- if (kustomizeManaged) {
3924
- const ns = metadataNamespaceForbiddenViolation(obj)
3925
- if (ns !== null) {
3926
- fail(`${rel}: документ ${docIndex}: ${ns}`)
3927
- }
3928
- return
3929
- }
3930
- const req = metadataNamespaceRequiredViolation(obj, false)
3931
- if (req !== null) {
3932
- fail(`${rel}: документ ${docIndex}: ${req}`)
3933
- }
3934
- }
3935
-
3936
- /**
3937
- * Deployment / Service / HealthCheckPolicy — політики для одного документа.
3938
- * @param {string} rel відносний шлях
3939
- * @param {string} baseLower basename (нижній регістр)
3940
- * @param {number} docIndex 1-based
3941
- * @param {unknown} obj корінь документа
3942
- * @param {(msg: string) => void} fail реєстрація помилки
3943
- * @returns {void}
3944
- */
3945
- function failIfK8sPolicyResourceRulesViolated(rel, baseLower, docIndex, obj, fail) {
3946
- const relPosix = rel.replaceAll('\\', '/')
3947
- const inK8sBaseLayer = isK8sYamlUnderBaseDirectory(relPosix)
3948
- const resV = deploymentResourcesViolation(obj, inK8sBaseLayer)
3949
- if (resV !== null) {
3950
- fail(`${rel}: Deployment (документ ${docIndex}): ${resV}`)
3951
- }
3952
- const hasuraV = deploymentHasuraGraphqlEngineImageViolation(obj)
3953
- if (hasuraV !== null) {
3954
- fail(`${rel}: Deployment (документ ${docIndex}): ${hasuraV}`)
3955
- }
3956
- const svcGcpV = serviceForbiddenGcpAnnotationsViolation(obj)
3957
- if (svcGcpV !== null) {
3958
- fail(`${rel}: Service (документ ${docIndex}): ${svcGcpV}`)
3959
- }
3960
- if (baseLower === 'svc.yaml') {
3961
- const svcT = serviceSvcYamlClusterIpTypeViolation(obj)
3962
- if (svcT !== null) {
3963
- fail(`${rel}: Service (документ ${docIndex}): ${svcT}`)
3964
- }
3965
- }
3966
- if (baseLower === 'svc-hl.yaml') {
3967
- const svcH = serviceSvcHlYamlHeadlessViolation(obj)
3968
- if (svcH !== null) {
3969
- fail(`${rel}: Service (документ ${docIndex}): ${svcH}`)
3970
- }
3971
- }
3972
- const hcpHl = healthCheckPolicyTargetRefHeadlessServiceViolation(obj)
3973
- if (hcpHl !== null) {
3974
- fail(`${rel}: документ ${docIndex}: ${hcpHl}`)
3975
- }
3976
- }
3977
-
3978
- /**
3979
- * Парсить усі YAML-документи: **metadata.namespace**, **Deployment.resources**, **Hasura image pin**,
3980
- * **Service — заборонені GKE-анотації**, **`svc.yaml`** (**`spec.type: ClusterIP`**), **`svc-hl.yaml`**
3981
- * (**headless**, суфікс **`-hl`** у **`metadata.name`**), **HealthCheckPolicy** (**`targetRef.name`** з **`-hl`**).
3982
- * @param {string} rel відносний шлях
3983
- * @param {string} baseLower basename файлу (нижній регістр)
3984
- * @param {string} body вміст після modeline
3985
- * @param {(msg: string) => void} fail реєстрація помилки
3986
- * @param {boolean} kustomizeManaged чи файл досяжний з kustomization.yaml (resources / patches / …)
3987
- */
3988
- function validateK8sYamlPolicyDocuments(rel, baseLower, body, fail, kustomizeManaged) {
3989
- /** @type {import('yaml').Document[]} */
3990
- let docs
3991
- try {
3992
- docs = parseAllDocuments(body)
3993
- } catch (error) {
3994
- const msg = error instanceof Error ? error.message : String(error)
3995
- fail(`${rel}: не вдалося розібрати YAML для перевірок маніфестів (${msg})`)
3996
- return
3997
- }
3998
-
3999
- const skipMetaNs = isKustomizationFileName(baseLower)
4000
- const inBaseManifest = isK8sBaseManifestYamlPath(rel, baseLower)
4001
-
4002
- for (const [di, doc] of docs.entries()) {
4003
- if (doc.errors.length > 0) {
4004
- fail(`${rel}: YAML (документ ${di + 1}): ${doc.errors.map(e => e.message).join('; ')}`)
4005
- } else {
4006
- const obj = doc.toJSON()
4007
- failIfK8sPolicyNamespaceRulesViolated(rel, di + 1, obj, skipMetaNs, inBaseManifest, kustomizeManaged, fail)
4008
- failIfK8sPolicyResourceRulesViolated(rel, baseLower, di + 1, obj, fail)
4009
- }
4010
- }
4011
- }
3575
+ // Plan B: per-document валідаційне ядро для k8s YAML повністю в rego —
3576
+ // `k8s.manifest`, `k8s.gateway`, `k8s.svc_yaml`, `k8s.svc_hl_yaml`,
3577
+ // `k8s.kustomize_managed`, `k8s.base_manifest`. Виклик через `runAllK8sRego`.
3578
+ // JS-функції failIfK8sPolicyNamespaceRulesViolated, failIfK8sPolicyResourceRulesViolated,
3579
+ // validateK8sYamlPolicyDocuments видалено.
4012
3580
 
4013
3581
  /**
4014
3582
  * Kind для імен файлів yannh/datree: лише літери та цифри, нижній регістр (Service → service, HTTPRoute → httproute).
@@ -4103,20 +3671,6 @@ function countSchemaModelines(lines) {
4103
3671
  return lines.filter(l => OXLINT_SCHEMA_MODELINE_RE.test(l.trim())).length
4104
3672
  }
4105
3673
 
4106
- /**
4107
- * Політики маніфестів і Gateway backendRefs після розбору тіла.
4108
- * @param {string} rel відносний шлях
4109
- * @param {string} baseLower basename (нижній регістр)
4110
- * @param {string} body YAML після modeline
4111
- * @param {(msg: string) => void} fail реєстрація помилки
4112
- * @param {Set<string>} kustomizeManagedRel kustomize-managed шляхи
4113
- * @returns {void}
4114
- */
4115
- function runK8sYamlPolicyAndGatewayScans(rel, baseLower, body, fail, kustomizeManagedRel) {
4116
- const kustomizeManaged = kustomizeManagedRel.has(rel)
4117
- validateK8sYamlPolicyDocuments(rel, baseLower, body, fail, kustomizeManaged)
4118
- scanGatewayApiRouteBackendRefsInYamlBody(rel, body, fail)
4119
- }
4120
3674
 
4121
3675
  /**
4122
3676
  * Файл з першим документом **HttpBackendGroup** (ALB Yandex): без modeline **$schema**.
@@ -4128,11 +3682,11 @@ function runK8sYamlPolicyAndGatewayScans(rel, baseLower, body, fail, kustomizeMa
4128
3682
  * @param {Set<string>} kustomizeManagedRel kustomize-managed шляхи
4129
3683
  * @returns {void}
4130
3684
  */
4131
- function checkK8sYamlHttpBackendGroupFile(rel, baseLower, lines, fail, pass, kustomizeManagedRel) {
4132
- const body = lines.join('\n')
4133
- scanForbiddenManifestsInYamlDocuments(rel, body, fail)
3685
+ function checkK8sYamlHttpBackendGroupFile(rel, _baseLower, _lines, _fail, pass, _kustomizeManagedRel) {
3686
+ // Per-document валідація (Ingress/autoscaling/v1 заборонено, Gateway API backendRef,
3687
+ // metadata.namespace правила) — у rego (`k8s.manifest`, `k8s.gateway`, `k8s.kustomize_managed`,
3688
+ // `k8s.base_manifest`), батч-виклик з `runAllK8sRego` на початку `check()`.
4134
3689
  pass(`${rel}: HttpBackendGroup (alb.yc.io/v1alpha1) — modeline $schema не застосовується (k8s.mdc)`)
4135
- runK8sYamlPolicyAndGatewayScans(rel, baseLower, body, fail, kustomizeManagedRel)
4136
3690
  }
4137
3691
 
4138
3692
  /**
@@ -4160,7 +3714,9 @@ function checkK8sYamlFileWithSchemaModeline(abs, rel, baseLower, lines, fail, pa
4160
3714
 
4161
3715
  const body = yamlBodyAfterModeline(lines)
4162
3716
 
4163
- scanForbiddenManifestsInYamlDocuments(rel, body, fail)
3717
+ // Per-document валідація (Ingress/autoscaling/v1 заборонено, Gateway API backendRef,
3718
+ // metadata.namespace правила, Service GCP-анотації, Deployment resources/Hasura image,
3719
+ // topologySpread, HCP, svc/svc-hl) — делегована rego, виконано у `runAllK8sRego` вище.
4164
3720
 
4165
3721
  if (schemaUrl.startsWith('file:')) {
4166
3722
  pass(`${rel}: локальна схема (file:) — перевірка URL за apiVersion/kind пропущена`)
@@ -4181,10 +3737,7 @@ function checkK8sYamlFileWithSchemaModeline(abs, rel, baseLower, lines, fail, pa
4181
3737
  pass(`${rel}: $schema узгоджено (${reason})`)
4182
3738
  } else {
4183
3739
  fail(`${rel}: $schema має бути https URL або file: (див. k8s.mdc)`)
4184
- return
4185
3740
  }
4186
-
4187
- runK8sYamlPolicyAndGatewayScans(rel, baseLower, body, fail, kustomizeManagedRel)
4188
3741
  }
4189
3742
 
4190
3743
  /**
@@ -4272,46 +3825,9 @@ function assertNoForbiddenK8sDevPaths(yamlFiles, root, fail) {
4272
3825
  * @param {(msg: string) => void} fail реєстрація порушення
4273
3826
  * @returns {Promise<void>}
4274
3827
  */
4275
- async function verifyBaseKustomizationNamespaceOnFile(root, abs, fail) {
4276
- const rel = relative(root, abs).replaceAll('\\', '/')
4277
- try {
4278
- const raw = await readFile(abs, 'utf8')
4279
- const lines = toLines(raw)
4280
- const body = yamlBodyAfterModeline(lines)
4281
- /** @type {import('yaml').Document[] | undefined} */
4282
- let docs
4283
- try {
4284
- docs = parseAllDocuments(body)
4285
- } catch {
4286
- fail(`${rel}: не вдалося розпарсити YAML для перевірки namespace у base (див. k8s.mdc)`)
4287
- return
4288
- }
4289
- const first = docs[0]?.toJSON()
4290
- const v = baseKustomizationNamespaceViolation(first)
4291
- if (v) {
4292
- fail(`${rel}: ${v}`)
4293
- }
4294
- } catch (error) {
4295
- const msg = error instanceof Error ? error.message : String(error)
4296
- fail(`${rel}: не вдалося прочитати (${msg})`)
4297
- }
4298
- }
4299
-
4300
- /**
4301
- * Якщо є **`k8s/base/kustomization.yaml`**, у ньому **завжди** має бути непорожній **`namespace:`**.
4302
- * @param {string} root корінь репозиторію
4303
- * @param {string[]} yamlFiles абсолютні шляхи
4304
- * @param {(msg: string) => void} fail callback для реєстрації порушення
4305
- * @returns {Promise<void>}
4306
- */
4307
- async function ensureBaseKustomizationHasNamespace(root, yamlFiles, fail) {
4308
- for (const abs of yamlFiles) {
4309
- const rel = relative(root, abs).replaceAll('\\', '/')
4310
- if (isBaseKustomizationPath(rel)) {
4311
- await verifyBaseKustomizationNamespaceOnFile(root, abs, fail)
4312
- }
4313
- }
4314
- }
3828
+ // Plan B: per-document `k8s/base/kustomization.yaml` має непорожнє поле `namespace:` —
3829
+ // у rego-пакеті `k8s.base_kustomization`, виклик через `runAllK8sRego`.
3830
+ // JS-функції verifyBaseKustomizationNamespaceOnFile, ensureBaseKustomizationHasNamespace видалено.
4315
3831
 
4316
3832
  const CONFIGMAP_BASE_PATH_RE = /\/k8s\/base\/configmap\.yaml$/u
4317
3833
 
@@ -4374,19 +3890,6 @@ async function validateConfigMapNameMatchesDeployment(root, yamlFilesAbs, fail,
4374
3890
  }
4375
3891
  }
4376
3892
 
4377
- /**
4378
- * Знаходить перший документ **ConfigMap** у файлі (з `metadata.name`).
4379
- * @param {string} absPath абсолютний шлях до YAML-файлу
4380
- * @returns {Promise<Record<string, unknown> | null>} об'єкт ConfigMap або null
4381
- */
4382
- async function readFirstConfigMapDoc(absPath) {
4383
- const raw = await tryReadFileUtf8(absPath)
4384
- if (raw === undefined) return null
4385
- const docs = tryParseAllYamlDocs(raw)
4386
- if (docs === undefined) return null
4387
- return findFirstDocByKind(docs, 'ConfigMap')
4388
- }
4389
-
4390
3893
  /**
4391
3894
  * Для кожного `k8s/base/configmap.yaml`, у каталозі якого поруч є Hasura-Deployment,
4392
3895
  * вимагає у `data` ключ **`HASURA_GRAPHQL_ENABLE_REMOTE_SCHEMA_PERMISSIONS`** зі значенням **`"true"`** (k8s.mdc).
@@ -4400,21 +3903,29 @@ async function validateHasuraConfigMapRemoteSchemaPermissions(root, yamlFilesAbs
4400
3903
  const rel = relative(root, abs).replaceAll('\\', '/')
4401
3904
  return CONFIGMAP_BASE_PATH_RE.test(`/${rel}`) || rel === 'k8s/base/configmap.yaml'
4402
3905
  })
3906
+ // JS gating: відберемо ConfigMap-файли, у каталозі яких поруч є Hasura-Deployment.
3907
+ // Per-document валідація `data.HASURA_GRAPHQL_ENABLE_REMOTE_SCHEMA_PERMISSIONS == "true"`
3908
+ // — у rego-пакеті `k8s.hasura_configmap`.
3909
+ const paired = []
4403
3910
  for (const cmAbs of cmFiles) {
4404
- const rel = relative(root, cmAbs).replaceAll('\\', '/') || cmAbs
4405
3911
  const deployment = await findDeploymentDocInDir(dirname(cmAbs))
4406
3912
  if (deployment !== null && isHasuraDeploymentManifest(deployment)) {
4407
- const cm = await readFirstConfigMapDoc(cmAbs)
4408
- if (cm !== null) {
4409
- const violation = hasuraConfigMapRemoteSchemaPermissionsViolation(cm)
4410
- if (violation === null) {
4411
- passFn(`${rel}: ${HASURA_REMOTE_SCHEMA_PERMISSIONS_KEY}="true" для Hasura-Deployment (k8s.mdc)`)
4412
- } else {
4413
- fail(`${rel}: ${violation}`)
4414
- }
4415
- }
3913
+ paired.push(cmAbs)
4416
3914
  }
4417
3915
  }
3916
+ if (paired.length === 0) return
3917
+ const violations = runConftestBatch({
3918
+ policyDirRel: 'k8s/hasura_configmap',
3919
+ namespace: 'k8s.hasura_configmap',
3920
+ files: paired
3921
+ })
3922
+ for (const v of violations) {
3923
+ const rel = (relative(root, v.filename) || v.filename).replaceAll('\\', '/')
3924
+ fail(`${rel}: ${v.message}`)
3925
+ }
3926
+ if (violations.length === 0) {
3927
+ passFn(`Hasura-ConfigMap (${paired.length}) відповідає ${HASURA_REMOTE_SCHEMA_PERMISSIONS_KEY}="true" (rego)`)
3928
+ }
4418
3929
  }
4419
3930
 
4420
3931
  /**
@@ -6518,6 +6029,56 @@ async function runKustomizationImagesCleanup(kustAbs, rel, fail, pass) {
6518
6029
  * Перевіряє відповідність проєкту правилам k8s.mdc.
6519
6030
  * @returns {Promise<number>} 0 — все OK, 1 — є проблеми
6520
6031
  */
6032
+ /**
6033
+ * Plan B (rego-authoritative): на початку `check()` батч-викликаємо всі 9 path-фільтрованих
6034
+ * rego-пакетів з `npm/policy/k8s/` через `runConftestBatch`. Пакети hasura_configmap і
6035
+ * hasura_httproute мають cross-file gating (паруються з Hasura-Deployment) — вони запускаються
6036
+ * з відповідних orchestrator-функцій (`validateHasuraConfigMapRemoteSchemaPermissions`,
6037
+ * `validateHasuraHttpRouteCanon`). Структурна частина HPA/PDB (`k8s.hpa_pdb`) тут на всіх yaml,
6038
+ * env-залежні межі min/maxReplicas і expected-name — JS-cross-file у `validateDeploymentHpaPdbAndTopology`.
6039
+ * @param {string} root корінь репозиторію (cwd)
6040
+ * @param {string[]} yamlFiles абсолютні шляхи знайдених *.yaml під `…/k8s/`
6041
+ * @param {Set<string>} kustomizeManagedRel відносні posix-шляхи kustomize-managed файлів
6042
+ * @param {(msg: string) => void} fail callback при помилці
6043
+ * @returns {void}
6044
+ */
6045
+ function runAllK8sRego(root, yamlFiles, kustomizeManagedRel, fail) {
6046
+ const relOf = abs => relative(root, abs).replaceAll('\\', '/') || abs
6047
+
6048
+ const allYaml = yamlFiles
6049
+ const kustYaml = yamlFiles.filter(p => basename(p).toLowerCase() === 'kustomization.yaml')
6050
+ const svcYaml = yamlFiles.filter(p => basename(p) === 'svc.yaml')
6051
+ const svcHlYaml = yamlFiles.filter(p => basename(p) === 'svc-hl.yaml')
6052
+ const baseKustYaml = yamlFiles.filter(p => isBaseKustomizationPath(relOf(p)))
6053
+ const baseResourceYaml = yamlFiles.filter(p => {
6054
+ const r = relOf(p)
6055
+ if (!K8S_BASE_SEGMENT_RE.test(r)) return false
6056
+ return basename(p).toLowerCase() !== 'kustomization.yaml'
6057
+ })
6058
+ const kustomizeManagedFiles = yamlFiles.filter(p => kustomizeManagedRel.has(relOf(p)))
6059
+
6060
+ /** @type {Array<{ ns: string, dir: string, files: string[] }>} */
6061
+ const targets = [
6062
+ { ns: 'k8s.manifest', dir: 'k8s/manifest', files: allYaml },
6063
+ { ns: 'k8s.gateway', dir: 'k8s/gateway', files: allYaml },
6064
+ { ns: 'k8s.hpa_pdb', dir: 'k8s/hpa_pdb', files: allYaml },
6065
+ { ns: 'k8s.kustomization', dir: 'k8s/kustomization', files: kustYaml },
6066
+ { ns: 'k8s.svc_yaml', dir: 'k8s/svc_yaml', files: svcYaml },
6067
+ { ns: 'k8s.svc_hl_yaml', dir: 'k8s/svc_hl_yaml', files: svcHlYaml },
6068
+ { ns: 'k8s.base_kustomization', dir: 'k8s/base_kustomization', files: baseKustYaml },
6069
+ { ns: 'k8s.base_manifest', dir: 'k8s/base_manifest', files: baseResourceYaml },
6070
+ { ns: 'k8s.kustomize_managed', dir: 'k8s/kustomize_managed', files: kustomizeManagedFiles }
6071
+ ]
6072
+
6073
+ for (const t of targets) {
6074
+ if (t.files.length === 0) continue
6075
+ const violations = runConftestBatch({ policyDirRel: t.dir, namespace: t.ns, files: t.files })
6076
+ for (const v of violations) {
6077
+ fail(`${relOf(v.filename)}: ${v.message}`)
6078
+ }
6079
+ }
6080
+ }
6081
+
6521
6082
  export async function check() {
6522
6083
  const reporter = createCheckReporter()
6523
6084
  const { pass, fail } = reporter
@@ -6544,6 +6105,12 @@ export async function check() {
6544
6105
 
6545
6106
  const kustomizeManagedRel = await collectKustomizeManagedRelPaths(root, yamlFiles)
6546
6107
 
6108
+ // Plan B: пер-документні структурні правила — у rego-полісі `npm/policy/k8s/*`,
6109
+ // викликаємо одним батчем на namespace через runConftestBatch. JS нижче робить
6110
+ // лише cross-file orchestration, modeline та FS-existence перевірки.
6111
+ runAllK8sRego(root, yamlFiles, kustomizeManagedRel, fail)
6112
+ pass(`Rego-полісі (npm/policy/k8s/*) виконано на ${yamlFiles.length} файл(ах)`)
6113
+
6547
6114
  for (const abs of yamlFiles) {
6548
6115
  await checkK8sYamlFile(abs, root, fail, pass, kustomizeManagedRel)
6549
6116
  }
@@ -6554,20 +6121,12 @@ export async function check() {
6554
6121
 
6555
6122
  await validateKustomizationIncludesSvcHlWithSvc(root, yamlFiles, fail)
6556
6123
 
6557
- await validateKustomizationJson6902NoRemoveAddSamePath(root, yamlFiles, fail)
6558
-
6559
6124
  await validateKustomizationPathRefsExistOnDisk(root, yamlFiles, fail)
6560
6125
 
6561
- await validateKustomizationResourcesSortedAlphabetically(root, yamlFiles, fail)
6562
-
6563
- await validateKustomizationPatchesStructuralSort(root, yamlFiles, fail)
6564
-
6565
6126
  await validateKustomizationPatchTargetsResolved(root, yamlFiles, fail)
6566
6127
 
6567
6128
  await validateKustomizeHpaPdbOnlyWithBaseDeployment(root, yamlFiles, fail, pass)
6568
6129
 
6569
- await ensureBaseKustomizationHasNamespace(root, yamlFiles, fail)
6570
-
6571
6130
  await validateConfigMapNameMatchesDeployment(root, yamlFiles, fail, pass)
6572
6131
 
6573
6132
  await validateHasuraConfigMapRemoteSchemaPermissions(root, yamlFiles, fail, pass)