@intentius/chant-lexicon-k8s 0.46.0 → 0.50.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 (248) hide show
  1. package/dist/components/kubectl-apply.d.ts +5 -0
  2. package/dist/components/kubectl-apply.d.ts.map +1 -1
  3. package/dist/composites/flux-app.d.ts +12 -0
  4. package/dist/composites/flux-app.d.ts.map +1 -1
  5. package/dist/composites/index.d.ts +8 -0
  6. package/dist/composites/index.d.ts.map +1 -1
  7. package/dist/composites/inference-service.d.ts +86 -0
  8. package/dist/composites/inference-service.d.ts.map +1 -0
  9. package/dist/composites/model.d.ts +80 -0
  10. package/dist/composites/model.d.ts.map +1 -0
  11. package/dist/composites/operator-stack.d.ts +183 -0
  12. package/dist/composites/operator-stack.d.ts.map +1 -0
  13. package/dist/composites/vllm-serving-runtime.d.ts +100 -0
  14. package/dist/composites/vllm-serving-runtime.d.ts.map +1 -0
  15. package/dist/crd/crd-sources.d.ts.map +1 -1
  16. package/dist/deep-observe-hooks.d.ts.map +1 -1
  17. package/dist/generated/index.d.ts +167 -40
  18. package/dist/generated/index.d.ts.map +1 -1
  19. package/dist/group-namespace.d.ts.map +1 -1
  20. package/dist/index.d.ts +3 -2
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/integrity.json +41 -33
  23. package/dist/kube/apply.d.ts.map +1 -1
  24. package/dist/lint/audit-catalog.d.ts.map +1 -1
  25. package/dist/lint/post-synth/argo-helpers.d.ts.map +1 -1
  26. package/dist/lint/post-synth/crd-schema-helpers.d.ts.map +1 -1
  27. package/dist/lint/post-synth/index.d.ts.map +1 -1
  28. package/dist/lint/post-synth/k8s-helpers.d.ts +56 -6
  29. package/dist/lint/post-synth/k8s-helpers.d.ts.map +1 -1
  30. package/dist/lint/post-synth/sops-helpers.d.ts +38 -0
  31. package/dist/lint/post-synth/sops-helpers.d.ts.map +1 -0
  32. package/dist/lint/post-synth/wk8005.d.ts.map +1 -1
  33. package/dist/lint/post-synth/wk8006.d.ts.map +1 -1
  34. package/dist/lint/post-synth/wk8041.d.ts.map +1 -1
  35. package/dist/lint/post-synth/wk8042.d.ts.map +1 -1
  36. package/dist/lint/post-synth/wk8101.d.ts.map +1 -1
  37. package/dist/lint/post-synth/wk8102.d.ts.map +1 -1
  38. package/dist/lint/post-synth/wk8103.d.ts.map +1 -1
  39. package/dist/lint/post-synth/wk8104.d.ts.map +1 -1
  40. package/dist/lint/post-synth/wk8105.d.ts.map +1 -1
  41. package/dist/lint/post-synth/wk8201.d.ts.map +1 -1
  42. package/dist/lint/post-synth/wk8202.d.ts.map +1 -1
  43. package/dist/lint/post-synth/wk8203.d.ts.map +1 -1
  44. package/dist/lint/post-synth/wk8204.d.ts.map +1 -1
  45. package/dist/lint/post-synth/wk8205.d.ts.map +1 -1
  46. package/dist/lint/post-synth/wk8207.d.ts.map +1 -1
  47. package/dist/lint/post-synth/wk8208.d.ts.map +1 -1
  48. package/dist/lint/post-synth/wk8209.d.ts.map +1 -1
  49. package/dist/lint/post-synth/wk8301.d.ts.map +1 -1
  50. package/dist/lint/post-synth/wk8302.d.ts.map +1 -1
  51. package/dist/lint/post-synth/wk8303.d.ts.map +1 -1
  52. package/dist/lint/post-synth/wk8304.d.ts.map +1 -1
  53. package/dist/lint/post-synth/wk8305.d.ts.map +1 -1
  54. package/dist/lint/post-synth/wk8306.d.ts.map +1 -1
  55. package/dist/lint/post-synth/wk8401.d.ts.map +1 -1
  56. package/dist/lint/post-synth/wk8402.d.ts.map +1 -1
  57. package/dist/lint/post-synth/wk8403.d.ts.map +1 -1
  58. package/dist/lint/post-synth/wk8404.d.ts +12 -0
  59. package/dist/lint/post-synth/wk8404.d.ts.map +1 -0
  60. package/dist/lint/post-synth/wk8405.d.ts +13 -0
  61. package/dist/lint/post-synth/wk8405.d.ts.map +1 -0
  62. package/dist/lint/post-synth/wk8406.d.ts +12 -0
  63. package/dist/lint/post-synth/wk8406.d.ts.map +1 -0
  64. package/dist/lint/post-synth/wk8407.d.ts +21 -0
  65. package/dist/lint/post-synth/wk8407.d.ts.map +1 -0
  66. package/dist/lint/post-synth/wk8503.d.ts +39 -0
  67. package/dist/lint/post-synth/wk8503.d.ts.map +1 -0
  68. package/dist/lint/post-synth/wk8504.d.ts +28 -0
  69. package/dist/lint/post-synth/wk8504.d.ts.map +1 -0
  70. package/dist/lint/post-synth/wk8505.d.ts +28 -0
  71. package/dist/lint/post-synth/wk8505.d.ts.map +1 -0
  72. package/dist/manifest.json +1 -1
  73. package/dist/meta.json +1039 -68
  74. package/dist/okf/index.md +26 -2
  75. package/dist/okf/rules/FLUX002.md +0 -1
  76. package/dist/okf/rules/WK8404.md +11 -0
  77. package/dist/okf/rules/WK8405.md +17 -0
  78. package/dist/okf/rules/WK8406.md +11 -0
  79. package/dist/okf/rules/WK8407.md +15 -0
  80. package/dist/okf/rules/WK8503.md +17 -0
  81. package/dist/okf/rules/WK8504.md +11 -0
  82. package/dist/okf/rules/WK8505.md +15 -0
  83. package/dist/okf/types/AWSClusterControllerIdentity.md +14 -0
  84. package/dist/okf/types/AWSManagedCluster.md +14 -0
  85. package/dist/okf/types/AWSManagedControlPlane.md +14 -0
  86. package/dist/okf/types/AWSManagedMachinePool.md +14 -0
  87. package/dist/okf/types/CAPICluster.md +14 -0
  88. package/dist/okf/types/Certificate.md +1 -0
  89. package/dist/okf/types/ClusterResourceSet.md +14 -0
  90. package/dist/okf/types/ClusterServingRuntime.md +14 -0
  91. package/dist/okf/types/{Cluster.md → CnpgCluster.md} +2 -2
  92. package/dist/okf/types/DBInstance.md +14 -0
  93. package/dist/okf/types/{Bucket.md → FluxBucket.md} +2 -6
  94. package/dist/okf/types/HelmChartProxy.md +14 -0
  95. package/dist/okf/types/IamRole.md +14 -0
  96. package/dist/okf/types/InferenceService.md +19 -0
  97. package/dist/okf/types/InfisicalDynamicSecret.md +4 -0
  98. package/dist/okf/types/InfisicalSecret.md +4 -0
  99. package/dist/okf/types/Kustomization.md +1 -0
  100. package/dist/okf/types/MachinePool.md +14 -0
  101. package/dist/okf/types/PDB.md +1 -0
  102. package/dist/okf/types/PodIdentityAssociation.md +14 -0
  103. package/dist/okf/types/Policy.md +14 -0
  104. package/dist/okf/types/S3Bucket.md +14 -0
  105. package/dist/okf/types/ServingRuntime.md +14 -0
  106. package/dist/okf/types/StatefulSet.md +1 -0
  107. package/dist/okf/types/User.md +14 -0
  108. package/dist/op/activities/ensure-secret.d.ts +41 -0
  109. package/dist/op/activities/ensure-secret.d.ts.map +1 -0
  110. package/dist/op/activities/index.d.ts +2 -0
  111. package/dist/op/activities/index.d.ts.map +1 -1
  112. package/dist/op/activities/kubectl.d.ts +9 -0
  113. package/dist/op/activities/kubectl.d.ts.map +1 -1
  114. package/dist/op/activities/wait-for-ready.d.ts.map +1 -1
  115. package/dist/op/builders.d.ts +69 -0
  116. package/dist/op/builders.d.ts.map +1 -0
  117. package/dist/plugin.d.ts.map +1 -1
  118. package/dist/rules/argo-helpers.ts +2 -6
  119. package/dist/rules/crd-schema-helpers.ts +4 -6
  120. package/dist/rules/k8s-helpers.ts +87 -24
  121. package/dist/rules/sops-helpers.ts +76 -0
  122. package/dist/rules/wk8005.ts +27 -32
  123. package/dist/rules/wk8006.ts +33 -38
  124. package/dist/rules/wk8041.ts +19 -24
  125. package/dist/rules/wk8042.ts +17 -22
  126. package/dist/rules/wk8101.ts +29 -34
  127. package/dist/rules/wk8102.ts +13 -18
  128. package/dist/rules/wk8103.ts +17 -22
  129. package/dist/rules/wk8104.ts +30 -35
  130. package/dist/rules/wk8105.ts +16 -21
  131. package/dist/rules/wk8201.ts +26 -31
  132. package/dist/rules/wk8202.ts +17 -22
  133. package/dist/rules/wk8203.ts +17 -22
  134. package/dist/rules/wk8204.ts +46 -51
  135. package/dist/rules/wk8205.ts +27 -32
  136. package/dist/rules/wk8207.ts +14 -19
  137. package/dist/rules/wk8208.ts +14 -19
  138. package/dist/rules/wk8209.ts +14 -19
  139. package/dist/rules/wk8301.ts +25 -30
  140. package/dist/rules/wk8302.ts +19 -24
  141. package/dist/rules/wk8303.ts +23 -26
  142. package/dist/rules/wk8304.ts +35 -40
  143. package/dist/rules/wk8305.ts +39 -42
  144. package/dist/rules/wk8306.ts +20 -25
  145. package/dist/rules/wk8401.ts +59 -64
  146. package/dist/rules/wk8402.ts +13 -18
  147. package/dist/rules/wk8403.ts +32 -37
  148. package/dist/rules/wk8404.ts +49 -0
  149. package/dist/rules/wk8405.ts +85 -0
  150. package/dist/rules/wk8406.ts +55 -0
  151. package/dist/rules/wk8407.ts +66 -0
  152. package/dist/rules/wk8503.ts +220 -0
  153. package/dist/rules/wk8504.ts +51 -0
  154. package/dist/rules/wk8505.ts +63 -0
  155. package/dist/secret-labels.d.ts +31 -0
  156. package/dist/secret-labels.d.ts.map +1 -0
  157. package/dist/secret-store.d.ts +73 -0
  158. package/dist/secret-store.d.ts.map +1 -0
  159. package/dist/serializer.d.ts.map +1 -1
  160. package/dist/sops/encrypted-secret-file.d.ts +52 -0
  161. package/dist/sops/encrypted-secret-file.d.ts.map +1 -0
  162. package/dist/sops/entity.d.ts +31 -0
  163. package/dist/sops/entity.d.ts.map +1 -0
  164. package/dist/teardown.d.ts.map +1 -1
  165. package/dist/types/index.d.ts +261 -9
  166. package/package.json +4 -4
  167. package/src/components/kubectl-apply.ts +5 -0
  168. package/src/composites/composites.test.ts +556 -0
  169. package/src/composites/flux-app.ts +23 -0
  170. package/src/composites/index.ts +15 -0
  171. package/src/composites/inference-service.ts +140 -0
  172. package/src/composites/model.ts +150 -0
  173. package/src/composites/operator-stack.ts +385 -0
  174. package/src/composites/vllm-serving-runtime.test.ts +167 -0
  175. package/src/composites/vllm-serving-runtime.ts +196 -0
  176. package/src/crd/cnpg.test.ts +8 -3
  177. package/src/crd/crd-sources.ts +183 -0
  178. package/src/crd/parser.test.ts +14 -0
  179. package/src/deep-observe-hooks.ts +24 -0
  180. package/src/deep-observe.test.ts +89 -0
  181. package/src/generated/index.d.ts +261 -9
  182. package/src/generated/index.ts +167 -40
  183. package/src/generated/lexicon-k8s.json +1039 -68
  184. package/src/generated/operations.json +272 -0
  185. package/src/group-namespace.test.ts +31 -0
  186. package/src/group-namespace.ts +13 -0
  187. package/src/index.ts +17 -0
  188. package/src/kube/apply.ts +5 -0
  189. package/src/lint/audit-catalog.ts +7 -0
  190. package/src/lint/post-synth/argo-helpers.ts +2 -6
  191. package/src/lint/post-synth/crd-schema-helpers.ts +4 -6
  192. package/src/lint/post-synth/index.ts +14 -0
  193. package/src/lint/post-synth/k8s-helpers.test.ts +26 -9
  194. package/src/lint/post-synth/k8s-helpers.ts +87 -24
  195. package/src/lint/post-synth/post-synth.test.ts +826 -0
  196. package/src/lint/post-synth/sops-helpers.ts +76 -0
  197. package/src/lint/post-synth/wk8005.ts +27 -32
  198. package/src/lint/post-synth/wk8006.ts +33 -38
  199. package/src/lint/post-synth/wk8041.ts +19 -24
  200. package/src/lint/post-synth/wk8042.ts +17 -22
  201. package/src/lint/post-synth/wk8101.ts +29 -34
  202. package/src/lint/post-synth/wk8102.ts +13 -18
  203. package/src/lint/post-synth/wk8103.ts +17 -22
  204. package/src/lint/post-synth/wk8104.ts +30 -35
  205. package/src/lint/post-synth/wk8105.ts +16 -21
  206. package/src/lint/post-synth/wk8201.ts +26 -31
  207. package/src/lint/post-synth/wk8202.ts +17 -22
  208. package/src/lint/post-synth/wk8203.ts +17 -22
  209. package/src/lint/post-synth/wk8204.ts +46 -51
  210. package/src/lint/post-synth/wk8205.ts +27 -32
  211. package/src/lint/post-synth/wk8207.ts +14 -19
  212. package/src/lint/post-synth/wk8208.ts +14 -19
  213. package/src/lint/post-synth/wk8209.ts +14 -19
  214. package/src/lint/post-synth/wk8301.ts +25 -30
  215. package/src/lint/post-synth/wk8302.ts +19 -24
  216. package/src/lint/post-synth/wk8303.ts +23 -26
  217. package/src/lint/post-synth/wk8304.ts +35 -40
  218. package/src/lint/post-synth/wk8305.ts +39 -42
  219. package/src/lint/post-synth/wk8306.ts +20 -25
  220. package/src/lint/post-synth/wk8401.ts +59 -64
  221. package/src/lint/post-synth/wk8402.ts +13 -18
  222. package/src/lint/post-synth/wk8403.ts +32 -37
  223. package/src/lint/post-synth/wk8404.ts +49 -0
  224. package/src/lint/post-synth/wk8405.ts +85 -0
  225. package/src/lint/post-synth/wk8406.ts +55 -0
  226. package/src/lint/post-synth/wk8407.ts +66 -0
  227. package/src/lint/post-synth/wk8503.ts +220 -0
  228. package/src/lint/post-synth/wk8504.ts +51 -0
  229. package/src/lint/post-synth/wk8505.ts +63 -0
  230. package/src/op/activities/ensure-secret.ts +71 -0
  231. package/src/op/activities/index.ts +7 -0
  232. package/src/op/activities/kubectl.test.ts +32 -0
  233. package/src/op/activities/kubectl.ts +28 -5
  234. package/src/op/activities/wait-for-ready.test.ts +34 -0
  235. package/src/op/activities/wait-for-ready.ts +26 -0
  236. package/src/op/builders.test.ts +82 -0
  237. package/src/op/builders.ts +95 -0
  238. package/src/plugin.ts +21 -2
  239. package/src/secret-labels.ts +36 -0
  240. package/src/secret-store.test.ts +184 -0
  241. package/src/secret-store.ts +165 -0
  242. package/src/serializer.ts +41 -2
  243. package/src/sops/encrypted-secret-file.test.ts +333 -0
  244. package/src/sops/encrypted-secret-file.ts +264 -0
  245. package/src/sops/entity.ts +53 -0
  246. package/src/teardown.test.ts +36 -0
  247. package/src/teardown.ts +14 -0
  248. package/src/testdata/sops/db-credentials.sops.yaml +23 -0
@@ -6,7 +6,7 @@
6
6
  */
7
7
 
8
8
  import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
9
- import { getPrimaryOutput, parseK8sManifests } from "./k8s-helpers";
9
+ import { docsToManifests } from "./k8s-helpers";
10
10
  import type { K8sManifest } from "./k8s-helpers";
11
11
 
12
12
  export const wk8305: PostSynthCheck = {
@@ -16,60 +16,57 @@ export const wk8305: PostSynthCheck = {
16
16
  check(ctx: PostSynthContext): PostSynthDiagnostic[] {
17
17
  const diagnostics: PostSynthDiagnostic[] = [];
18
18
 
19
- for (const [, output] of ctx.outputs) {
20
- const yaml = getPrimaryOutput(output);
21
- const manifests = parseK8sManifests(yaml);
19
+ const manifests = docsToManifests(ctx);
22
20
 
23
- // Build a map of Service name+namespace → set of port numbers
24
- const servicePorts = collectServicePorts(manifests);
21
+ // Build a map of Service name+namespace → set of port numbers
22
+ const servicePorts = collectServicePorts(manifests);
25
23
 
26
- for (const manifest of manifests) {
27
- if (manifest.kind !== "Ingress") continue;
24
+ for (const manifest of manifests) {
25
+ if (manifest.kind !== "Ingress") continue;
28
26
 
29
- const ingressName = manifest.metadata?.name ?? "Ingress";
30
- const ingressNamespace = manifest.metadata?.namespace ?? "default";
31
- const spec = manifest.spec;
32
- if (!spec) continue;
27
+ const ingressName = manifest.metadata?.name ?? "Ingress";
28
+ const ingressNamespace = manifest.metadata?.namespace ?? "default";
29
+ const spec = manifest.spec;
30
+ if (!spec) continue;
33
31
 
34
- const rules = spec.rules as Array<Record<string, unknown>> | undefined;
35
- if (!rules) continue;
32
+ const rules = spec.rules as Array<Record<string, unknown>> | undefined;
33
+ if (!rules) continue;
36
34
 
37
- for (const rule of rules) {
38
- const http = rule.http as Record<string, unknown> | undefined;
39
- if (!http) continue;
35
+ for (const rule of rules) {
36
+ const http = rule.http as Record<string, unknown> | undefined;
37
+ if (!http) continue;
40
38
 
41
- const paths = http.paths as Array<Record<string, unknown>> | undefined;
42
- if (!paths) continue;
39
+ const paths = http.paths as Array<Record<string, unknown>> | undefined;
40
+ if (!paths) continue;
43
41
 
44
- for (const pathEntry of paths) {
45
- const backend = pathEntry.backend as Record<string, unknown> | undefined;
46
- if (!backend) continue;
42
+ for (const pathEntry of paths) {
43
+ const backend = pathEntry.backend as Record<string, unknown> | undefined;
44
+ if (!backend) continue;
47
45
 
48
- const service = backend.service as Record<string, unknown> | undefined;
49
- if (!service) continue;
46
+ const service = backend.service as Record<string, unknown> | undefined;
47
+ if (!service) continue;
50
48
 
51
- const svcName = service.name as string | undefined;
52
- const port = service.port as Record<string, unknown> | undefined;
53
- const portNumber = port?.number as number | undefined;
49
+ const svcName = service.name as string | undefined;
50
+ const port = service.port as Record<string, unknown> | undefined;
51
+ const portNumber = port?.number as number | undefined;
54
52
 
55
- if (!svcName || portNumber === undefined) continue;
53
+ if (!svcName || portNumber === undefined) continue;
56
54
 
57
- // Look up the Service in the manifest set
58
- const key = `${ingressNamespace}/${svcName}`;
59
- const knownPorts = servicePorts.get(key);
55
+ // Look up the Service in the manifest set
56
+ const key = `${ingressNamespace}/${svcName}`;
57
+ const knownPorts = servicePorts.get(key);
60
58
 
61
- // Skip if the Service is not in the manifest set (external service)
62
- if (!knownPorts) continue;
59
+ // Skip if the Service is not in the manifest set (external service)
60
+ if (!knownPorts) continue;
63
61
 
64
- if (!knownPorts.has(portNumber)) {
65
- diagnostics.push({
66
- checkId: "WK8305",
67
- severity: "warning",
68
- message: `Ingress "${ingressName}" references Service "${svcName}" port ${portNumber}, but the Service only declares ports [${[...knownPorts].join(", ")}]`,
69
- entity: ingressName,
70
- lexicon: "k8s",
71
- });
72
- }
62
+ if (!knownPorts.has(portNumber)) {
63
+ diagnostics.push({
64
+ checkId: "WK8305",
65
+ severity: "warning",
66
+ message: `Ingress "${ingressName}" references Service "${svcName}" port ${portNumber}, but the Service only declares ports [${[...knownPorts].join(", ")}]`,
67
+ entity: ingressName,
68
+ lexicon: "k8s",
69
+ });
73
70
  }
74
71
  }
75
72
  }
@@ -8,7 +8,7 @@
8
8
  */
9
9
 
10
10
  import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
11
- import { getPrimaryOutput, parseK8sManifests, extractContainers, WORKLOAD_KINDS } from "./k8s-helpers";
11
+ import { docsToManifests, extractContainers, WORKLOAD_KINDS } from "./k8s-helpers";
12
12
 
13
13
  export const wk8306: PostSynthCheck = {
14
14
  id: "WK8306",
@@ -17,30 +17,25 @@ export const wk8306: PostSynthCheck = {
17
17
  check(ctx: PostSynthContext): PostSynthDiagnostic[] {
18
18
  const diagnostics: PostSynthDiagnostic[] = [];
19
19
 
20
- for (const [, output] of ctx.outputs) {
21
- const yaml = getPrimaryOutput(output);
22
- const manifests = parseK8sManifests(yaml);
23
-
24
- for (const manifest of manifests) {
25
- if (!manifest.kind || !WORKLOAD_KINDS.has(manifest.kind)) continue;
26
-
27
- const resourceName = manifest.metadata?.name ?? manifest.kind;
28
- const containers = extractContainers(manifest);
29
-
30
- for (const container of containers) {
31
- const command = (container as Record<string, unknown>).command as unknown[] | undefined;
32
- if (!Array.isArray(command) || command.length === 0) continue;
33
-
34
- const firstArg = String(command[0]);
35
- if (firstArg.startsWith("-")) {
36
- diagnostics.push({
37
- checkId: "WK8306",
38
- severity: "error",
39
- message: `Container "${container.name ?? "(unnamed)"}" in ${manifest.kind} "${resourceName}" has command[0]="${firstArg}" which starts with a flag — the first element should be the binary, flags belong in args`,
40
- entity: resourceName,
41
- lexicon: "k8s",
42
- });
43
- }
20
+ for (const manifest of docsToManifests(ctx)) {
21
+ if (!manifest.kind || !WORKLOAD_KINDS.has(manifest.kind)) continue;
22
+
23
+ const resourceName = manifest.metadata?.name ?? manifest.kind;
24
+ const containers = extractContainers(manifest);
25
+
26
+ for (const container of containers) {
27
+ const command = (container as Record<string, unknown>).command as unknown[] | undefined;
28
+ if (!Array.isArray(command) || command.length === 0) continue;
29
+
30
+ const firstArg = String(command[0]);
31
+ if (firstArg.startsWith("-")) {
32
+ diagnostics.push({
33
+ checkId: "WK8306",
34
+ severity: "error",
35
+ message: `Container "${container.name ?? "(unnamed)"}" in ${manifest.kind} "${resourceName}" has command[0]="${firstArg}" which starts with a flag — the first element should be the binary, flags belong in args`,
36
+ entity: resourceName,
37
+ lexicon: "k8s",
38
+ });
44
39
  }
45
40
  }
46
41
  }
@@ -8,7 +8,7 @@
8
8
  */
9
9
 
10
10
  import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
11
- import { getPrimaryOutput, parseK8sManifests, parseMemoryBytes } from "./k8s-helpers";
11
+ import { docsToManifests, parseMemoryBytes } from "./k8s-helpers";
12
12
 
13
13
  export const wk8401: PostSynthCheck = {
14
14
  id: "WK8401",
@@ -17,77 +17,72 @@ export const wk8401: PostSynthCheck = {
17
17
  check(ctx: PostSynthContext): PostSynthDiagnostic[] {
18
18
  const diagnostics: PostSynthDiagnostic[] = [];
19
19
 
20
- for (const [, output] of ctx.outputs) {
21
- const yaml = getPrimaryOutput(output);
22
- const manifests = parseK8sManifests(yaml);
20
+ for (const manifest of docsToManifests(ctx)) {
21
+ if (manifest.kind !== "RayCluster") continue;
23
22
 
24
- for (const manifest of manifests) {
25
- if (manifest.kind !== "RayCluster") continue;
23
+ const clusterName = manifest.metadata?.name ?? "RayCluster";
24
+ const spec = manifest.spec as Record<string, unknown> | undefined;
25
+ if (!spec) continue;
26
26
 
27
- const clusterName = manifest.metadata?.name ?? "RayCluster";
28
- const spec = manifest.spec as Record<string, unknown> | undefined;
29
- if (!spec) continue;
27
+ const groups: Array<{ label: string; templateSpec: Record<string, unknown> }> = [];
30
28
 
31
- const groups: Array<{ label: string; templateSpec: Record<string, unknown> }> = [];
29
+ // Head group
30
+ const headGroupSpec = spec.headGroupSpec as Record<string, unknown> | undefined;
31
+ if (headGroupSpec) {
32
+ const tmpl = headGroupSpec.template as Record<string, unknown> | undefined;
33
+ const podSpec = tmpl?.spec as Record<string, unknown> | undefined;
34
+ if (podSpec) groups.push({ label: "head", templateSpec: podSpec });
35
+ }
32
36
 
33
- // Head group
34
- const headGroupSpec = spec.headGroupSpec as Record<string, unknown> | undefined;
35
- if (headGroupSpec) {
36
- const tmpl = headGroupSpec.template as Record<string, unknown> | undefined;
37
+ // Worker groups
38
+ const workerGroupSpecs = spec.workerGroupSpecs as Array<Record<string, unknown>> | undefined;
39
+ if (Array.isArray(workerGroupSpecs)) {
40
+ for (const wg of workerGroupSpecs) {
41
+ const name = (wg.groupName as string | undefined) ?? "worker";
42
+ const tmpl = wg.template as Record<string, unknown> | undefined;
37
43
  const podSpec = tmpl?.spec as Record<string, unknown> | undefined;
38
- if (podSpec) groups.push({ label: "head", templateSpec: podSpec });
39
- }
40
-
41
- // Worker groups
42
- const workerGroupSpecs = spec.workerGroupSpecs as Array<Record<string, unknown>> | undefined;
43
- if (Array.isArray(workerGroupSpecs)) {
44
- for (const wg of workerGroupSpecs) {
45
- const name = (wg.groupName as string | undefined) ?? "worker";
46
- const tmpl = wg.template as Record<string, unknown> | undefined;
47
- const podSpec = tmpl?.spec as Record<string, unknown> | undefined;
48
- if (podSpec) groups.push({ label: `worker "${name}"`, templateSpec: podSpec });
49
- }
44
+ if (podSpec) groups.push({ label: `worker "${name}"`, templateSpec: podSpec });
50
45
  }
46
+ }
51
47
 
52
- for (const { label, templateSpec } of groups) {
53
- // Find the emptyDir Memory volume (dshm)
54
- const volumes = templateSpec.volumes as Array<Record<string, unknown>> | undefined;
55
- if (!Array.isArray(volumes)) continue;
56
-
57
- for (const vol of volumes) {
58
- const emptyDir = vol.emptyDir as Record<string, unknown> | undefined;
59
- if (!emptyDir || emptyDir.medium !== "Memory") continue;
60
-
61
- const sizeLimit = emptyDir.sizeLimit as string | undefined;
62
- if (!sizeLimit) continue;
63
-
64
- const shmBytes = parseMemoryBytes(sizeLimit);
65
- if (isNaN(shmBytes)) continue;
66
-
67
- // Find memory limit from the first container that mounts this volume
68
- const containers = templateSpec.containers as Array<Record<string, unknown>> | undefined;
69
- if (!Array.isArray(containers)) continue;
70
-
71
- for (const container of containers) {
72
- const resources = container.resources as Record<string, unknown> | undefined;
73
- const limits = resources?.limits as Record<string, unknown> | undefined;
74
- const memLimit = limits?.memory as string | undefined;
75
- if (!memLimit) continue;
76
-
77
- const memBytes = parseMemoryBytes(memLimit);
78
- if (isNaN(memBytes)) continue;
79
-
80
- if (shmBytes > memBytes) {
81
- diagnostics.push({
82
- checkId: "WK8401",
83
- severity: "error",
84
- message: `RayCluster "${clusterName}" ${label}: shmSize "${sizeLimit}" exceeds memory limit "${memLimit}" — pod will not schedule`,
85
- entity: clusterName,
86
- lexicon: "k8s",
87
- });
88
- }
89
- break; // Only check the first container per group
48
+ for (const { label, templateSpec } of groups) {
49
+ // Find the emptyDir Memory volume (dshm)
50
+ const volumes = templateSpec.volumes as Array<Record<string, unknown>> | undefined;
51
+ if (!Array.isArray(volumes)) continue;
52
+
53
+ for (const vol of volumes) {
54
+ const emptyDir = vol.emptyDir as Record<string, unknown> | undefined;
55
+ if (!emptyDir || emptyDir.medium !== "Memory") continue;
56
+
57
+ const sizeLimit = emptyDir.sizeLimit as string | undefined;
58
+ if (!sizeLimit) continue;
59
+
60
+ const shmBytes = parseMemoryBytes(sizeLimit);
61
+ if (isNaN(shmBytes)) continue;
62
+
63
+ // Find memory limit from the first container that mounts this volume
64
+ const containers = templateSpec.containers as Array<Record<string, unknown>> | undefined;
65
+ if (!Array.isArray(containers)) continue;
66
+
67
+ for (const container of containers) {
68
+ const resources = container.resources as Record<string, unknown> | undefined;
69
+ const limits = resources?.limits as Record<string, unknown> | undefined;
70
+ const memLimit = limits?.memory as string | undefined;
71
+ if (!memLimit) continue;
72
+
73
+ const memBytes = parseMemoryBytes(memLimit);
74
+ if (isNaN(memBytes)) continue;
75
+
76
+ if (shmBytes > memBytes) {
77
+ diagnostics.push({
78
+ checkId: "WK8401",
79
+ severity: "error",
80
+ message: `RayCluster "${clusterName}" ${label}: shmSize "${sizeLimit}" exceeds memory limit "${memLimit}" — pod will not schedule`,
81
+ entity: clusterName,
82
+ lexicon: "k8s",
83
+ });
90
84
  }
85
+ break; // Only check the first container per group
91
86
  }
92
87
  }
93
88
  }
@@ -7,7 +7,7 @@
7
7
  */
8
8
 
9
9
  import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
10
- import { getPrimaryOutput, parseK8sManifests } from "./k8s-helpers";
10
+ import { docsToManifests } from "./k8s-helpers";
11
11
 
12
12
  export const wk8402: PostSynthCheck = {
13
13
  id: "WK8402",
@@ -16,25 +16,20 @@ export const wk8402: PostSynthCheck = {
16
16
  check(ctx: PostSynthContext): PostSynthDiagnostic[] {
17
17
  const diagnostics: PostSynthDiagnostic[] = [];
18
18
 
19
- for (const [, output] of ctx.outputs) {
20
- const yaml = getPrimaryOutput(output);
21
- const manifests = parseK8sManifests(yaml);
19
+ for (const manifest of docsToManifests(ctx)) {
20
+ if (manifest.kind !== "RayCluster") continue;
22
21
 
23
- for (const manifest of manifests) {
24
- if (manifest.kind !== "RayCluster") continue;
22
+ const name = manifest.metadata?.name ?? "RayCluster";
23
+ const rayVersion = (manifest.spec as Record<string, unknown> | undefined)?.rayVersion;
25
24
 
26
- const name = manifest.metadata?.name ?? "RayCluster";
27
- const rayVersion = (manifest.spec as Record<string, unknown> | undefined)?.rayVersion;
28
-
29
- if (!rayVersion) {
30
- diagnostics.push({
31
- checkId: "WK8402",
32
- severity: "warning",
33
- message: `RayCluster "${name}" is missing spec.rayVersion — KubeRay autoscaler will pull the "latest" image tag`,
34
- entity: name,
35
- lexicon: "k8s",
36
- });
37
- }
25
+ if (!rayVersion) {
26
+ diagnostics.push({
27
+ checkId: "WK8402",
28
+ severity: "warning",
29
+ message: `RayCluster "${name}" is missing spec.rayVersion — KubeRay autoscaler will pull the "latest" image tag`,
30
+ entity: name,
31
+ lexicon: "k8s",
32
+ });
38
33
  }
39
34
  }
40
35
 
@@ -7,7 +7,7 @@
7
7
  */
8
8
 
9
9
  import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
10
- import { getPrimaryOutput, parseK8sManifests, extractRayVersion } from "./k8s-helpers";
10
+ import { docsToManifests, extractRayVersion } from "./k8s-helpers";
11
11
 
12
12
  export const wk8403: PostSynthCheck = {
13
13
  id: "WK8403",
@@ -16,42 +16,37 @@ export const wk8403: PostSynthCheck = {
16
16
  check(ctx: PostSynthContext): PostSynthDiagnostic[] {
17
17
  const diagnostics: PostSynthDiagnostic[] = [];
18
18
 
19
- for (const [, output] of ctx.outputs) {
20
- const yaml = getPrimaryOutput(output);
21
- const manifests = parseK8sManifests(yaml);
22
-
23
- for (const manifest of manifests) {
24
- if (manifest.kind !== "RayCluster") continue;
25
-
26
- const clusterName = manifest.metadata?.name ?? "RayCluster";
27
- const spec = manifest.spec as Record<string, unknown> | undefined;
28
- if (!spec) continue;
29
-
30
- const rayVersion = spec.rayVersion as string | undefined;
31
- if (!rayVersion) continue; // WK8402 covers the missing case
32
-
33
- // Extract version from head container image
34
- const headGroupSpec = spec.headGroupSpec as Record<string, unknown> | undefined;
35
- const tmpl = headGroupSpec?.template as Record<string, unknown> | undefined;
36
- const podSpec = tmpl?.spec as Record<string, unknown> | undefined;
37
- const containers = podSpec?.containers as Array<Record<string, unknown>> | undefined;
38
- if (!Array.isArray(containers) || containers.length === 0) continue;
39
-
40
- const image = containers[0].image as string | undefined;
41
- if (!image) continue;
42
-
43
- const imageVersion = extractRayVersion(image);
44
- if (!imageVersion) continue; // Can't determine version from tag
45
-
46
- if (imageVersion !== rayVersion) {
47
- diagnostics.push({
48
- checkId: "WK8403",
49
- severity: "warning",
50
- message: `RayCluster "${clusterName}": spec.rayVersion "${rayVersion}" does not match head image tag "${imageVersion}" — autoscaler may run a mismatched Ray version`,
51
- entity: clusterName,
52
- lexicon: "k8s",
53
- });
54
- }
19
+ for (const manifest of docsToManifests(ctx)) {
20
+ if (manifest.kind !== "RayCluster") continue;
21
+
22
+ const clusterName = manifest.metadata?.name ?? "RayCluster";
23
+ const spec = manifest.spec as Record<string, unknown> | undefined;
24
+ if (!spec) continue;
25
+
26
+ const rayVersion = spec.rayVersion as string | undefined;
27
+ if (!rayVersion) continue; // WK8402 covers the missing case
28
+
29
+ // Extract version from head container image
30
+ const headGroupSpec = spec.headGroupSpec as Record<string, unknown> | undefined;
31
+ const tmpl = headGroupSpec?.template as Record<string, unknown> | undefined;
32
+ const podSpec = tmpl?.spec as Record<string, unknown> | undefined;
33
+ const containers = podSpec?.containers as Array<Record<string, unknown>> | undefined;
34
+ if (!Array.isArray(containers) || containers.length === 0) continue;
35
+
36
+ const image = containers[0].image as string | undefined;
37
+ if (!image) continue;
38
+
39
+ const imageVersion = extractRayVersion(image);
40
+ if (!imageVersion) continue; // Can't determine version from tag
41
+
42
+ if (imageVersion !== rayVersion) {
43
+ diagnostics.push({
44
+ checkId: "WK8403",
45
+ severity: "warning",
46
+ message: `RayCluster "${clusterName}": spec.rayVersion "${rayVersion}" does not match head image tag "${imageVersion}" — autoscaler may run a mismatched Ray version`,
47
+ entity: clusterName,
48
+ lexicon: "k8s",
49
+ });
55
50
  }
56
51
  }
57
52
 
@@ -0,0 +1,49 @@
1
+ /**
2
+ * WK8404: GPU request without a matching toleration
3
+ *
4
+ * A pod that requests `nvidia.com/gpu` but carries no toleration for the
5
+ * `nvidia.com/gpu` taint won't schedule onto a tainted GPU node pool (see
6
+ * `GpuNodePool`, #987, which taints its nodes `nvidia.com/gpu=present` by
7
+ * default). The pod sits `Pending` with `0/N nodes are available` until a
8
+ * toleration is added.
9
+ */
10
+
11
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
12
+ import {
13
+ docsToManifests,
14
+ extractContainers,
15
+ extractPodSpec,
16
+ requestsGpu,
17
+ toleratesGpu,
18
+ GPU_POD_KINDS,
19
+ } from "./k8s-helpers";
20
+
21
+ export const wk8404: PostSynthCheck = {
22
+ id: "WK8404",
23
+ description: "GPU request without a matching nvidia.com/gpu toleration",
24
+
25
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
26
+ const diagnostics: PostSynthDiagnostic[] = [];
27
+
28
+ for (const manifest of docsToManifests(ctx)) {
29
+ if (!manifest.kind || !GPU_POD_KINDS.has(manifest.kind)) continue;
30
+
31
+ const containers = extractContainers(manifest);
32
+ if (!requestsGpu(containers)) continue;
33
+
34
+ const podSpec = extractPodSpec(manifest);
35
+ if (toleratesGpu(podSpec?.tolerations)) continue;
36
+
37
+ const resourceName = manifest.metadata?.name ?? manifest.kind;
38
+ diagnostics.push({
39
+ checkId: "WK8404",
40
+ severity: "error",
41
+ message: `${manifest.kind} "${resourceName}" requests nvidia.com/gpu but has no toleration for the nvidia.com/gpu taint — it will not schedule onto a tainted GPU node pool`,
42
+ entity: resourceName,
43
+ lexicon: "k8s",
44
+ });
45
+ }
46
+
47
+ return diagnostics;
48
+ },
49
+ };
@@ -0,0 +1,85 @@
1
+ /**
2
+ * WK8405: Serving workload without a PodDisruptionBudget
3
+ *
4
+ * An InferenceService (or a Deployment carrying a serving
5
+ * `app.kubernetes.io/component` label — e.g. `inference-service` or
6
+ * `vllm-serving-runtime`) with no PodDisruptionBudget selecting it can lose
7
+ * every replica at once during a voluntary disruption (node drain, cluster
8
+ * upgrade), taking the model endpoint down. Serving workloads should ship
9
+ * with a PDB.
10
+ */
11
+
12
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
13
+ import { docsToManifests } from "./k8s-helpers";
14
+ import type { K8sManifest } from "./k8s-helpers";
15
+
16
+ /** `app.kubernetes.io/component` values this lexicon's composites use for serving workloads. */
17
+ const SERVING_COMPONENTS = new Set(["inference-service", "vllm-serving-runtime", "model-serving"]);
18
+
19
+ function isServingWorkload(manifest: K8sManifest): boolean {
20
+ if (manifest.kind === "InferenceService") return true;
21
+ if (manifest.kind !== "Deployment" && manifest.kind !== "StatefulSet") return false;
22
+ const component = manifest.metadata?.labels?.["app.kubernetes.io/component"];
23
+ return component !== undefined && SERVING_COMPONENTS.has(component);
24
+ }
25
+
26
+ export const wk8405: PostSynthCheck = {
27
+ id: "WK8405",
28
+ description: "Serving workload (InferenceService / serving Deployment) has no PodDisruptionBudget",
29
+
30
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
31
+ const diagnostics: PostSynthDiagnostic[] = [];
32
+
33
+ const manifests = docsToManifests(ctx);
34
+
35
+ const pdbSelectors = collectPdbSelectors(manifests);
36
+
37
+ for (const manifest of manifests) {
38
+ if (!isServingWorkload(manifest)) continue;
39
+
40
+ const labels = manifest.metadata?.labels;
41
+ const resourceName = manifest.metadata?.name ?? manifest.kind ?? "workload";
42
+
43
+ if (!labels || !hasCoveringPdb(labels, pdbSelectors)) {
44
+ diagnostics.push({
45
+ checkId: "WK8405",
46
+ severity: "info",
47
+ message: `${manifest.kind} "${resourceName}" is a serving workload with no PodDisruptionBudget — add a PDB so a node drain or cluster upgrade cannot take down every replica at once`,
48
+ entity: resourceName,
49
+ lexicon: "k8s",
50
+ });
51
+ }
52
+ }
53
+
54
+ return diagnostics;
55
+ },
56
+ };
57
+
58
+ function collectPdbSelectors(manifests: K8sManifest[]): Array<Record<string, string>> {
59
+ const selectors: Array<Record<string, string>> = [];
60
+
61
+ for (const manifest of manifests) {
62
+ if (manifest.kind !== "PodDisruptionBudget") continue;
63
+
64
+ const spec = manifest.spec;
65
+ if (!spec) continue;
66
+
67
+ const selector = spec.selector as Record<string, unknown> | undefined;
68
+ const matchLabels = selector?.matchLabels as Record<string, string> | undefined;
69
+
70
+ if (matchLabels && typeof matchLabels === "object") {
71
+ selectors.push(matchLabels);
72
+ }
73
+ }
74
+
75
+ return selectors;
76
+ }
77
+
78
+ function hasCoveringPdb(
79
+ workloadLabels: Record<string, string>,
80
+ pdbSelectors: Array<Record<string, string>>,
81
+ ): boolean {
82
+ return pdbSelectors.some((pdbLabels) =>
83
+ Object.entries(pdbLabels).every(([key, value]) => workloadLabels[key] === value),
84
+ );
85
+ }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * WK8406: No resource limits on a GPU pod
3
+ *
4
+ * A container that requests `nvidia.com/gpu` but sets no cpu/memory limits
5
+ * can starve other pods on the same (expensive, scarce) GPU node of CPU and
6
+ * memory headroom. Unlike WK8201's general resource-limits guidance, this
7
+ * check is scoped to GPU-requesting containers specifically, since GPU node
8
+ * capacity is costlier and more contended than a general-purpose pool.
9
+ */
10
+
11
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
12
+ import { docsToManifests, extractContainers, GPU_POD_KINDS, NVIDIA_GPU_RESOURCE } from "./k8s-helpers";
13
+
14
+ export const wk8406: PostSynthCheck = {
15
+ id: "WK8406",
16
+ description: "GPU-requesting container has no cpu/memory resource limits",
17
+
18
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
19
+ const diagnostics: PostSynthDiagnostic[] = [];
20
+
21
+ for (const manifest of docsToManifests(ctx)) {
22
+ if (!manifest.kind || !GPU_POD_KINDS.has(manifest.kind)) continue;
23
+
24
+ const containers = extractContainers(manifest);
25
+ const resourceName = manifest.metadata?.name ?? manifest.kind;
26
+
27
+ for (const container of containers) {
28
+ const requests = container.resources?.requests as Record<string, unknown> | undefined;
29
+ const limits = container.resources?.limits as Record<string, unknown> | undefined;
30
+ const requestsGpu = Boolean(requests?.[NVIDIA_GPU_RESOURCE]) || Boolean(limits?.[NVIDIA_GPU_RESOURCE]);
31
+ if (!requestsGpu) continue;
32
+
33
+ const missing: string[] = [];
34
+ if (!limits || typeof limits !== "object") {
35
+ missing.push("cpu", "memory");
36
+ } else {
37
+ if (!limits.cpu) missing.push("cpu");
38
+ if (!limits.memory) missing.push("memory");
39
+ }
40
+
41
+ if (missing.length > 0) {
42
+ diagnostics.push({
43
+ checkId: "WK8406",
44
+ severity: "warning",
45
+ message: `GPU container "${container.name ?? "(unnamed)"}" in ${manifest.kind} "${resourceName}" is missing resource limits for ${missing.join(", ")} — a GPU pod without cpu/memory limits can starve its (scarce, expensive) node`,
46
+ entity: resourceName,
47
+ lexicon: "k8s",
48
+ });
49
+ }
50
+ }
51
+ }
52
+
53
+ return diagnostics;
54
+ },
55
+ };