@intentius/chant-lexicon-k8s 0.124.31 → 0.124.32

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 (270) hide show
  1. package/dist/actions/apps.js +18 -0
  2. package/dist/actions/batch.js +6 -0
  3. package/dist/actions/core.js +48 -0
  4. package/dist/actions/index.js +13 -0
  5. package/dist/actions/networking.js +11 -0
  6. package/dist/actions/rbac.js +8 -0
  7. package/dist/api/classify.js +123 -0
  8. package/dist/api/connect.js +62 -0
  9. package/dist/api/fake-cluster.js +175 -0
  10. package/dist/api/operation-surface.d.ts.map +1 -1
  11. package/dist/api/operation-surface.js +109 -0
  12. package/dist/api/owner-chain.js +100 -0
  13. package/dist/api/sweep-types.js +35 -0
  14. package/dist/behaviour-kinds.js +82 -0
  15. package/dist/codegen/docs-cli.js +2 -0
  16. package/dist/codegen/docs.js +146 -0
  17. package/dist/codegen/generate-cli.js +30 -0
  18. package/dist/codegen/generate-lexicon.js +74 -0
  19. package/dist/codegen/generate-list-map-keys.js +88 -0
  20. package/dist/codegen/generate-operations.js +53 -0
  21. package/dist/codegen/generate-typescript.js +124 -0
  22. package/dist/codegen/generate.js +173 -0
  23. package/dist/codegen/naming.js +174 -0
  24. package/dist/codegen/package.js +36 -0
  25. package/dist/codegen/patches.js +88 -0
  26. package/dist/codegen/typecheck.js +4 -0
  27. package/dist/codegen/versions.js +41 -0
  28. package/dist/components/argo-app.js +51 -0
  29. package/dist/components/builders.js +22 -0
  30. package/dist/components/capability-plugin.js +31 -0
  31. package/dist/components/flux-reconcile.js +61 -0
  32. package/dist/components/index.d.ts +1 -1
  33. package/dist/components/index.d.ts.map +1 -1
  34. package/dist/components/index.js +18 -0
  35. package/dist/components/kubectl-apply.d.ts +83 -1
  36. package/dist/components/kubectl-apply.d.ts.map +1 -1
  37. package/dist/components/kubectl-apply.js +274 -0
  38. package/dist/components/kustomize-apply.js +25 -0
  39. package/dist/composites/adot-collector.js +184 -0
  40. package/dist/composites/agic-ingress.js +85 -0
  41. package/dist/composites/aks-external-dns-agent.js +146 -0
  42. package/dist/composites/alb-ingress.js +86 -0
  43. package/dist/composites/argo-app.js +199 -0
  44. package/dist/composites/autoscaled-service.js +174 -0
  45. package/dist/composites/azure-disk-storage-class.js +47 -0
  46. package/dist/composites/azure-file-storage-class.js +48 -0
  47. package/dist/composites/azure-monitor-collector.js +176 -0
  48. package/dist/composites/batch-job.js +139 -0
  49. package/dist/composites/catalog.js +4110 -0
  50. package/dist/composites/cockroachdb-cluster.js +371 -0
  51. package/dist/composites/cockroachdb-region-stack.js +369 -0
  52. package/dist/composites/config-connector-context.js +40 -0
  53. package/dist/composites/configured-app.js +135 -0
  54. package/dist/composites/cron-schedule.js +38 -0
  55. package/dist/composites/cron-workload.js +109 -0
  56. package/dist/composites/ebs-storage-class.js +54 -0
  57. package/dist/composites/efs-storage-class.js +46 -0
  58. package/dist/composites/external-dns-agent.js +122 -0
  59. package/dist/composites/filestore-storage-class.js +49 -0
  60. package/dist/composites/fluent-bit-agent.js +157 -0
  61. package/dist/composites/flux-app.js +156 -0
  62. package/dist/composites/gce-ingress.js +83 -0
  63. package/dist/composites/gce-pd-storage-class.js +49 -0
  64. package/dist/composites/gke-external-dns-agent.js +129 -0
  65. package/dist/composites/gke-fluent-bit-agent.js +165 -0
  66. package/dist/composites/gke-gateway.js +91 -0
  67. package/dist/composites/gke-otel-collector.js +94 -0
  68. package/dist/composites/index.js +51 -0
  69. package/dist/composites/inference-service.js +76 -0
  70. package/dist/composites/irsa-service-account.js +77 -0
  71. package/dist/composites/member-defaults.js +14 -0
  72. package/dist/composites/metrics-server.js +181 -0
  73. package/dist/composites/model.js +92 -0
  74. package/dist/composites/monitored-service.js +152 -0
  75. package/dist/composites/namespace-env.js +114 -0
  76. package/dist/composites/network-isolated-app.js +124 -0
  77. package/dist/composites/node-agent.js +149 -0
  78. package/dist/composites/operator-stack.js +268 -0
  79. package/dist/composites/otel-collector-agent.js +142 -0
  80. package/dist/composites/otel-collector-gateway.js +216 -0
  81. package/dist/composites/otel-collector-node.js +176 -0
  82. package/dist/composites/otel-collector-rbac.js +115 -0
  83. package/dist/composites/otel-collector-shape.js +128 -0
  84. package/dist/composites/otel-collector.js +115 -0
  85. package/dist/composites/otel-operator-collector.js +123 -0
  86. package/dist/composites/ray-cluster.js +378 -0
  87. package/dist/composites/ray-job.js +136 -0
  88. package/dist/composites/ray-service.js +178 -0
  89. package/dist/composites/secure-ingress.js +100 -0
  90. package/dist/composites/security-context.js +1 -0
  91. package/dist/composites/sidecar-app.js +105 -0
  92. package/dist/composites/stateful-app.js +111 -0
  93. package/dist/composites/vllm-serving-runtime.js +114 -0
  94. package/dist/composites/web-app.js +157 -0
  95. package/dist/composites/worker-pool.js +167 -0
  96. package/dist/composites/workload-identity-sa.js +81 -0
  97. package/dist/composites/workload-identity-service-account.js +77 -0
  98. package/dist/config-schema.js +72 -0
  99. package/dist/config.js +59 -0
  100. package/dist/coverage.js +27 -0
  101. package/dist/crd/crd-sources.js +615 -0
  102. package/dist/crd/loader.js +181 -0
  103. package/dist/crd/oci-chart.js +144 -0
  104. package/dist/crd/parser.js +382 -0
  105. package/dist/crd/project-codegen.js +273 -0
  106. package/dist/crd/types.js +7 -0
  107. package/dist/deep-observe-hooks.js +306 -0
  108. package/dist/deep-observe.js +234 -0
  109. package/dist/default-labels.js +87 -0
  110. package/dist/describe-resources.js +699 -0
  111. package/dist/describe-stack-status.js +99 -0
  112. package/dist/detect.js +11 -0
  113. package/dist/effect-receipt-row.js +205 -0
  114. package/dist/export-resources.js +39 -0
  115. package/dist/generated/index.js +1092 -0
  116. package/dist/generated/runtime.js +4 -0
  117. package/dist/group-namespace.js +96 -0
  118. package/dist/import/embedded.js +197 -0
  119. package/dist/import/generator.js +288 -0
  120. package/dist/import/live-export.js +85 -0
  121. package/dist/import/parser.js +204 -0
  122. package/dist/import/testdata/embedded/fixtures.js +60 -0
  123. package/dist/include/entity.js +78 -0
  124. package/dist/include/resolve.js +170 -0
  125. package/dist/index.js +59 -0
  126. package/dist/integrity.json +3 -3
  127. package/dist/kube/apply.js +90 -0
  128. package/dist/kube/connect.js +46 -0
  129. package/dist/kube/delete.js +138 -0
  130. package/dist/kube/describe.js +169 -0
  131. package/dist/kube/events.js +132 -0
  132. package/dist/kube/flags.js +91 -0
  133. package/dist/kube/get.js +159 -0
  134. package/dist/kube/group.js +123 -0
  135. package/dist/kube/logs.js +82 -0
  136. package/dist/kube/project.js +94 -0
  137. package/dist/kube/render.js +227 -0
  138. package/dist/kube/source.js +122 -0
  139. package/dist/kube/target.js +63 -0
  140. package/dist/kube/testing.js +28 -0
  141. package/dist/kube/top.js +99 -0
  142. package/dist/kube/verdict.js +72 -0
  143. package/dist/kube/wait.js +129 -0
  144. package/dist/kustomize/render.js +73 -0
  145. package/dist/kustomize/rendered-entity.js +67 -0
  146. package/dist/kustomize/root.js +68 -0
  147. package/dist/lint/audit-catalog.js +61 -0
  148. package/dist/lint/audit-lineage.js +118 -0
  149. package/dist/lint/post-synth/argo-helpers.js +36 -0
  150. package/dist/lint/post-synth/argo002.js +31 -0
  151. package/dist/lint/post-synth/argo003.js +53 -0
  152. package/dist/lint/post-synth/argo005.js +54 -0
  153. package/dist/lint/post-synth/crd-schema-helpers.d.ts.map +1 -1
  154. package/dist/lint/post-synth/crd-schema-helpers.js +214 -0
  155. package/dist/lint/post-synth/flux002.js +56 -0
  156. package/dist/lint/post-synth/flux003.js +50 -0
  157. package/dist/lint/post-synth/index.js +104 -0
  158. package/dist/lint/post-synth/k8s-helpers.js +186 -0
  159. package/dist/lint/post-synth/monitoring-selection-helpers.js +163 -0
  160. package/dist/lint/post-synth/otel-placement-helpers.js +293 -0
  161. package/dist/lint/post-synth/sops-helpers.js +43 -0
  162. package/dist/lint/post-synth/wk8005.js +44 -0
  163. package/dist/lint/post-synth/wk8006.js +56 -0
  164. package/dist/lint/post-synth/wk8041.js +60 -0
  165. package/dist/lint/post-synth/wk8042.js +36 -0
  166. package/dist/lint/post-synth/wk8101.js +52 -0
  167. package/dist/lint/post-synth/wk8102.js +30 -0
  168. package/dist/lint/post-synth/wk8103.js +33 -0
  169. package/dist/lint/post-synth/wk8104.js +58 -0
  170. package/dist/lint/post-synth/wk8105.js +33 -0
  171. package/dist/lint/post-synth/wk8201.js +44 -0
  172. package/dist/lint/post-synth/wk8202.js +34 -0
  173. package/dist/lint/post-synth/wk8203.js +34 -0
  174. package/dist/lint/post-synth/wk8204.js +71 -0
  175. package/dist/lint/post-synth/wk8205.js +38 -0
  176. package/dist/lint/post-synth/wk8207.js +33 -0
  177. package/dist/lint/post-synth/wk8208.js +33 -0
  178. package/dist/lint/post-synth/wk8209.js +33 -0
  179. package/dist/lint/post-synth/wk8301.js +49 -0
  180. package/dist/lint/post-synth/wk8302.js +34 -0
  181. package/dist/lint/post-synth/wk8303.js +68 -0
  182. package/dist/lint/post-synth/wk8304.js +58 -0
  183. package/dist/lint/post-synth/wk8305.js +96 -0
  184. package/dist/lint/post-synth/wk8306.js +38 -0
  185. package/dist/lint/post-synth/wk8401.js +86 -0
  186. package/dist/lint/post-synth/wk8402.js +31 -0
  187. package/dist/lint/post-synth/wk8403.js +49 -0
  188. package/dist/lint/post-synth/wk8404.js +36 -0
  189. package/dist/lint/post-synth/wk8405.js +65 -0
  190. package/dist/lint/post-synth/wk8406.js +50 -0
  191. package/dist/lint/post-synth/wk8407.js +58 -0
  192. package/dist/lint/post-synth/wk8501.js +36 -0
  193. package/dist/lint/post-synth/wk8502.js +35 -0
  194. package/dist/lint/post-synth/wk8503.js +164 -0
  195. package/dist/lint/post-synth/wk8504.js +20 -0
  196. package/dist/lint/post-synth/wk8505.js +31 -0
  197. package/dist/lint/post-synth/wk8601.js +40 -0
  198. package/dist/lint/post-synth/wk8602.js +120 -0
  199. package/dist/lint/post-synth/wk8603.js +63 -0
  200. package/dist/lint/post-synth/wk8604.js +31 -0
  201. package/dist/lint/post-synth/wk8605.js +124 -0
  202. package/dist/lint/post-synth/wk8606.js +22 -0
  203. package/dist/lint/post-synth/wk8701.js +31 -0
  204. package/dist/lint/post-synth/wk8702.js +37 -0
  205. package/dist/lint/post-synth/wk8703.js +31 -0
  206. package/dist/lint/post-synth/wk8704.js +135 -0
  207. package/dist/lint/post-synth/wk8705.js +38 -0
  208. package/dist/lint/rules/argo-appset-single-project.js +56 -0
  209. package/dist/lint/rules/argo-ast.js +94 -0
  210. package/dist/lint/rules/argo-automated-prune.js +59 -0
  211. package/dist/lint/rules/flux-source-ref-pin.js +44 -0
  212. package/dist/lint/rules/hardcoded-namespace.js +57 -0
  213. package/dist/lint/rules/latest-image-tag.js +103 -0
  214. package/dist/lint/rules/missing-resource-limits.js +85 -0
  215. package/dist/lsp/completions.d.ts.map +1 -1
  216. package/dist/lsp/completions.js +18 -0
  217. package/dist/lsp/hover.d.ts.map +1 -1
  218. package/dist/lsp/hover.js +57 -0
  219. package/dist/manifest-entity.js +71 -0
  220. package/dist/manifest.json +1 -1
  221. package/dist/micro-time.js +49 -0
  222. package/dist/op/activities/apply-diff.d.ts +31 -0
  223. package/dist/op/activities/apply-diff.d.ts.map +1 -0
  224. package/dist/op/activities/apply-diff.js +79 -0
  225. package/dist/op/activities/argo.js +98 -0
  226. package/dist/op/activities/ensure-secret.js +38 -0
  227. package/dist/op/activities/index.js +51 -0
  228. package/dist/op/activities/kubectl.d.ts +64 -0
  229. package/dist/op/activities/kubectl.d.ts.map +1 -1
  230. package/dist/op/activities/kubectl.js +517 -0
  231. package/dist/op/activities/wait-for-ready.js +229 -0
  232. package/dist/op/builders.js +68 -0
  233. package/dist/package-cli.js +21 -0
  234. package/dist/patch/json-patch.js +187 -0
  235. package/dist/patch/rendered-patch.js +94 -0
  236. package/dist/plugin.js +741 -0
  237. package/dist/project-kinds.js +56 -0
  238. package/dist/receipt-store.js +216 -0
  239. package/dist/rules/crd-schema-helpers.ts +2 -1
  240. package/dist/secret-labels.js +33 -0
  241. package/dist/secret-store.js +116 -0
  242. package/dist/serializer.d.ts.map +1 -1
  243. package/dist/serializer.js +578 -0
  244. package/dist/sops/encrypted-secret-file.js +194 -0
  245. package/dist/sops/entity.js +32 -0
  246. package/dist/spec/fetch.js +58 -0
  247. package/dist/spec/parse.js +622 -0
  248. package/dist/subscribe-changes.js +168 -0
  249. package/dist/teardown.js +197 -0
  250. package/dist/telemetry-release.js +86 -0
  251. package/dist/validate-cli.js +17 -0
  252. package/dist/validate.js +47 -0
  253. package/dist/variables.js +34 -0
  254. package/dist/whoami.js +109 -0
  255. package/package.json +12 -12
  256. package/src/api/operation-surface.ts +2 -1
  257. package/src/codegen/package.ts +2 -2
  258. package/src/components/index.ts +3 -0
  259. package/src/components/kubectl-apply-plan.k3d.e2e.test.ts +127 -0
  260. package/src/components/kubectl-apply.test.ts +262 -0
  261. package/src/components/kubectl-apply.ts +341 -12
  262. package/src/deep-observe-hooks.ts +2 -2
  263. package/src/import/roundtrip-build.test.ts +46 -0
  264. package/src/lint/post-synth/crd-schema-helpers.ts +2 -1
  265. package/src/lsp/completions.ts +2 -1
  266. package/src/lsp/hover.ts +2 -1
  267. package/src/op/activities/apply-diff.ts +87 -0
  268. package/src/op/activities/kubectl-plan.test.ts +224 -0
  269. package/src/op/activities/kubectl.ts +204 -16
  270. package/src/serializer.ts +2 -1
@@ -0,0 +1,214 @@
1
+ /**
2
+ * Shared helpers for the custom-resource spec checks (WK8501, WK8502) — chant #1372.
3
+ *
4
+ * A CRD's constructor takes `spec: Record<string, unknown>`, so a misspelled
5
+ * or wrong-typed field type-checks, serializes, and is accepted by the API
6
+ * server (which prunes what the structural schema does not know) — the
7
+ * controller then runs with a default nobody chose. The generated lexicon JSON
8
+ * carries each CRD's `spec` field schema (`specSchema`); these helpers walk a
9
+ * synthesized manifest against it and report what the schema would reject.
10
+ *
11
+ * Excluded from check auto-discovery by the "helper" filename filter.
12
+ */
13
+ import { createRequire } from "module";
14
+ import { moduleSourceUrl } from "@intentius/chant/module-source";
15
+ import { docsToManifests } from "./k8s-helpers.js";
16
+ import { projectSpecSchema } from "../../project-kinds.js";
17
+ let cachedRegistry = null;
18
+ /** Registry key: `<apiVersion>/<kind>`, the pair a manifest carries verbatim. */
19
+ function registryKey(apiVersion, kind) {
20
+ return `${apiVersion}/${kind}`;
21
+ }
22
+ /**
23
+ * Spec schemas of every CRD the lexicon ships, keyed by `apiVersion/kind`.
24
+ * Built-in kinds carry no `specSchema` and are never in the map.
25
+ */
26
+ export function getCrdSchemaRegistry() {
27
+ if (cachedRegistry)
28
+ return cachedRegistry;
29
+ cachedRegistry = new Map();
30
+ try {
31
+ // Built lazily so importing this module stays edge-safe: `createRequire`
32
+ // throws where import.meta.url is undefined (bundled Workers), and there
33
+ // the registry is simply empty.
34
+ const require = createRequire(moduleSourceUrl(import.meta.url));
35
+ const lexicon = require("../../generated/lexicon-k8s.json");
36
+ for (const entry of Object.values(lexicon)) {
37
+ if (entry.kind === "resource" && entry.apiVersion && entry.gvkKind && entry.specSchema) {
38
+ cachedRegistry.set(registryKey(entry.apiVersion, entry.gvkKind), entry.specSchema);
39
+ }
40
+ }
41
+ }
42
+ catch {
43
+ // Lexicon JSON not yet generated — empty registry, checks pass.
44
+ }
45
+ return cachedRegistry;
46
+ }
47
+ /** Test seam: replace the registry (pass `null` to reload from the lexicon JSON). */
48
+ export function setCrdSchemaRegistry(registry) {
49
+ cachedRegistry = registry;
50
+ }
51
+ /**
52
+ * The schema for a manifest's `apiVersion`/`kind`: one the project generated
53
+ * from its own CRDs (`chant generate`, ../../project-kinds.ts) first, then one
54
+ * the lexicon ships.
55
+ */
56
+ export function specSchemaFor(manifest) {
57
+ if (typeof manifest.apiVersion !== "string" || typeof manifest.kind !== "string")
58
+ return undefined;
59
+ return (projectSpecSchema(manifest.apiVersion, manifest.kind) ??
60
+ getCrdSchemaRegistry().get(registryKey(manifest.apiVersion, manifest.kind)));
61
+ }
62
+ /** Every manifest in the build that has a shipped spec schema, with that schema. */
63
+ export function customResources(ctx) {
64
+ const out = [];
65
+ for (const manifest of docsToManifests(ctx)) {
66
+ const schema = specSchemaFor(manifest);
67
+ if (schema)
68
+ out.push({ manifest, schema });
69
+ }
70
+ return out;
71
+ }
72
+ /**
73
+ * Walk `value` against `schema` and report every field the schema does not
74
+ * list (unless the enclosing object is `open`) and every scalar whose type or
75
+ * enum membership the schema rejects. Untyped nodes pass anything.
76
+ */
77
+ export function validateSpec(value, schema, path = "spec") {
78
+ const findings = [];
79
+ walk(value, schema, path, findings);
80
+ return findings;
81
+ }
82
+ function walk(value, schema, path, out) {
83
+ if (value === null || value === undefined)
84
+ return;
85
+ // int-or-string and other deliberately untyped nodes accept anything.
86
+ if (!schema.type)
87
+ return;
88
+ switch (schema.type) {
89
+ case "object": {
90
+ if (typeof value !== "object" || Array.isArray(value)) {
91
+ out.push(mismatch(path, "an object", value));
92
+ return;
93
+ }
94
+ const obj = value;
95
+ for (const [name, child] of Object.entries(obj)) {
96
+ const childSchema = schema.fields?.[name];
97
+ if (childSchema) {
98
+ walk(child, childSchema, `${path}.${name}`, out);
99
+ continue;
100
+ }
101
+ if (schema.open)
102
+ continue;
103
+ const known = Object.keys(schema.fields ?? {});
104
+ const suggestion = suggestField(name, known);
105
+ const hint = suggestion ? ` (did you mean "${suggestion}"?)` : "";
106
+ out.push({
107
+ kind: "unknown-field",
108
+ path: `${path}.${name}`,
109
+ message: `unknown field "${path}.${name}"${hint}`,
110
+ });
111
+ }
112
+ return;
113
+ }
114
+ case "array": {
115
+ if (!Array.isArray(value)) {
116
+ out.push(mismatch(path, "an array", value));
117
+ return;
118
+ }
119
+ if (schema.items) {
120
+ value.forEach((item, i) => walk(item, schema.items, `${path}[${i}]`, out));
121
+ }
122
+ return;
123
+ }
124
+ case "string": {
125
+ if (typeof value !== "string") {
126
+ // `x-kubernetes-int-or-string` never reaches here (untyped); a plain
127
+ // string field given a number is a real mismatch the server coerces
128
+ // or rejects depending on version, so say so.
129
+ out.push(mismatch(path, "a string", value));
130
+ return;
131
+ }
132
+ checkEnum(value, schema, path, out);
133
+ return;
134
+ }
135
+ case "integer":
136
+ case "number": {
137
+ if (typeof value !== "number" || (schema.type === "integer" && !Number.isInteger(value))) {
138
+ out.push(mismatch(path, schema.type === "integer" ? "an integer" : "a number", value));
139
+ return;
140
+ }
141
+ checkEnum(value, schema, path, out);
142
+ return;
143
+ }
144
+ case "boolean": {
145
+ if (typeof value !== "boolean")
146
+ out.push(mismatch(path, "a boolean", value));
147
+ return;
148
+ }
149
+ }
150
+ }
151
+ function checkEnum(value, schema, path, out) {
152
+ if (!schema.enum || schema.enum.length === 0)
153
+ return;
154
+ if (schema.enum.includes(String(value)))
155
+ return;
156
+ out.push({
157
+ kind: "type-mismatch",
158
+ path,
159
+ message: `"${path}" must be one of ${schema.enum.map((v) => `"${v}"`).join(", ")}, got ${describe(value)}`,
160
+ });
161
+ }
162
+ function mismatch(path, expected, value) {
163
+ return {
164
+ kind: "type-mismatch",
165
+ path,
166
+ message: `"${path}" expects ${expected}, got ${describe(value)}`,
167
+ };
168
+ }
169
+ function describe(value) {
170
+ if (typeof value === "string")
171
+ return `string "${value}"`;
172
+ if (typeof value === "number" || typeof value === "boolean")
173
+ return `${typeof value} ${String(value)}`;
174
+ if (Array.isArray(value))
175
+ return "an array";
176
+ if (value !== null && typeof value === "object")
177
+ return "an object";
178
+ return String(value);
179
+ }
180
+ /** Levenshtein distance between two strings. */
181
+ export function levenshtein(a, b) {
182
+ const m = a.length;
183
+ const n = b.length;
184
+ const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
185
+ for (let i = 0; i <= m; i++)
186
+ dp[i][0] = i;
187
+ for (let j = 0; j <= n; j++)
188
+ dp[0][j] = j;
189
+ for (let i = 1; i <= m; i++) {
190
+ for (let j = 1; j <= n; j++) {
191
+ dp[i][j] = a[i - 1] === b[j - 1]
192
+ ? dp[i - 1][j - 1]
193
+ : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
194
+ }
195
+ }
196
+ return dp[m][n];
197
+ }
198
+ /** Closest known field within Levenshtein distance 3 (case-insensitive), if any. */
199
+ export function suggestField(unknown, known) {
200
+ let best;
201
+ let bestDist = 4;
202
+ for (const field of known) {
203
+ const dist = levenshtein(unknown.toLowerCase(), field.toLowerCase());
204
+ if (dist < bestDist) {
205
+ bestDist = dist;
206
+ best = field;
207
+ }
208
+ }
209
+ return best;
210
+ }
211
+ /** `metadata.name` or the kind, for diagnostics. */
212
+ export function resourceLabel(manifest) {
213
+ return manifest.metadata?.name ?? manifest.kind ?? "resource";
214
+ }
@@ -0,0 +1,56 @@
1
+ import { allManifests, manifestsOfKind } from "./argo-helpers.js";
2
+ /** Source kinds the kustomize-controller can reconcile from. */
3
+ const SOURCE_KINDS = ["GitRepository", "OCIRepository", "Bucket"];
4
+ /** The GitRepository `flux bootstrap` creates alongside the controllers. */
5
+ const BOOTSTRAP_SOURCE_NAME = "flux-system";
6
+ export const flux002 = {
7
+ id: "FLUX002",
8
+ description: "Kustomization.spec.sourceRef must reference a declared source (or the bootstrap flux-system repo)",
9
+ check(ctx) {
10
+ const diagnostics = [];
11
+ const manifests = allManifests(ctx);
12
+ // Declared sources, keyed "Kind/name". Namespace is deliberately ignored:
13
+ // an unset sourceRef.namespace resolves in the Kustomization's own, and
14
+ // the common estate keeps everything in flux-system anyway.
15
+ const declaredSources = new Set();
16
+ for (const kind of SOURCE_KINDS) {
17
+ for (const source of manifestsOfKind(manifests, kind)) {
18
+ const name = source.metadata?.name;
19
+ if (typeof name === "string")
20
+ declaredSources.add(`${kind}/${name}`);
21
+ }
22
+ }
23
+ for (const kustomization of manifestsOfKind(manifests, "Kustomization")) {
24
+ // Flux and kustomize.config.k8s.io share the kind name; only the Flux CR
25
+ // carries a sourceRef-bearing spec.
26
+ const apiVersion = kustomization.apiVersion;
27
+ if (typeof apiVersion === "string" && !apiVersion.startsWith("kustomize.toolkit.fluxcd.io/"))
28
+ continue;
29
+ const name = kustomization.metadata?.name ?? "Kustomization";
30
+ const sourceRef = kustomization.spec?.sourceRef;
31
+ if (!sourceRef || typeof sourceRef.name !== "string" || sourceRef.name === "") {
32
+ diagnostics.push({
33
+ checkId: "FLUX002",
34
+ severity: "error",
35
+ message: `Kustomization "${name}" has no spec.sourceRef.name — the kustomize-controller cannot resolve a source.`,
36
+ entity: name,
37
+ lexicon: "k8s",
38
+ });
39
+ continue;
40
+ }
41
+ const kind = typeof sourceRef.kind === "string" && sourceRef.kind !== "" ? sourceRef.kind : "GitRepository";
42
+ if (kind === "GitRepository" && sourceRef.name === BOOTSTRAP_SOURCE_NAME)
43
+ continue;
44
+ if (declaredSources.has(`${kind}/${sourceRef.name}`))
45
+ continue;
46
+ diagnostics.push({
47
+ checkId: "FLUX002",
48
+ severity: "error",
49
+ message: `Kustomization "${name}" references ${kind} "${sourceRef.name}", which is not declared. Declare the source (e.g. FluxGitSource) or point sourceRef at the bootstrap flux-system repo.`,
50
+ entity: name,
51
+ lexicon: "k8s",
52
+ });
53
+ }
54
+ return diagnostics;
55
+ },
56
+ };
@@ -0,0 +1,50 @@
1
+ import { allManifests, manifestsOfKind } from "./argo-helpers.js";
2
+ /** Flux Kustomizations only — the kustomize.config.k8s.io kind shares the name. */
3
+ function fluxKustomizations(manifests) {
4
+ return manifestsOfKind(manifests, "Kustomization").filter((m) => {
5
+ const apiVersion = m.apiVersion;
6
+ return typeof apiVersion !== "string" || apiVersion.startsWith("kustomize.toolkit.fluxcd.io/");
7
+ });
8
+ }
9
+ export const flux003 = {
10
+ id: "FLUX003",
11
+ description: "Kustomization.spec.dependsOn entries should name Kustomizations declared in the build",
12
+ check(ctx) {
13
+ const diagnostics = [];
14
+ const kustomizations = fluxKustomizations(allManifests(ctx));
15
+ const declared = new Set(kustomizations
16
+ .map((k) => k.metadata?.name)
17
+ .filter((n) => typeof n === "string"));
18
+ for (const kustomization of kustomizations) {
19
+ const name = kustomization.metadata?.name ?? "Kustomization";
20
+ const dependsOn = kustomization.spec?.dependsOn;
21
+ if (!Array.isArray(dependsOn))
22
+ continue;
23
+ for (const entry of dependsOn) {
24
+ const depName = entry?.name;
25
+ if (typeof depName !== "string" || depName === "")
26
+ continue;
27
+ if (depName === name) {
28
+ diagnostics.push({
29
+ checkId: "FLUX003",
30
+ severity: "warning",
31
+ message: `Kustomization "${name}" depends on itself — Flux can never satisfy the edge and the app will not reconcile.`,
32
+ entity: name,
33
+ lexicon: "k8s",
34
+ });
35
+ continue;
36
+ }
37
+ if (declared.has(depName))
38
+ continue;
39
+ diagnostics.push({
40
+ checkId: "FLUX003",
41
+ severity: "warning",
42
+ message: `Kustomization "${name}" depends on Kustomization "${depName}", which nothing in the build declares — reconciliation stalls until it exists. Fix the name, or ignore if it is declared in another repo.`,
43
+ entity: name,
44
+ lexicon: "k8s",
45
+ });
46
+ }
47
+ }
48
+ return diagnostics;
49
+ },
50
+ };
@@ -0,0 +1,104 @@
1
+ import { argo002 } from "./argo002.js";
2
+ import { argo003 } from "./argo003.js";
3
+ import { argo005 } from "./argo005.js";
4
+ import { flux002 } from "./flux002.js";
5
+ import { flux003 } from "./flux003.js";
6
+ import { wk8005 } from "./wk8005.js";
7
+ import { wk8006 } from "./wk8006.js";
8
+ import { wk8041 } from "./wk8041.js";
9
+ import { wk8042 } from "./wk8042.js";
10
+ import { wk8101 } from "./wk8101.js";
11
+ import { wk8102 } from "./wk8102.js";
12
+ import { wk8103 } from "./wk8103.js";
13
+ import { wk8104 } from "./wk8104.js";
14
+ import { wk8105 } from "./wk8105.js";
15
+ import { wk8201 } from "./wk8201.js";
16
+ import { wk8202 } from "./wk8202.js";
17
+ import { wk8203 } from "./wk8203.js";
18
+ import { wk8204 } from "./wk8204.js";
19
+ import { wk8205 } from "./wk8205.js";
20
+ import { wk8207 } from "./wk8207.js";
21
+ import { wk8208 } from "./wk8208.js";
22
+ import { wk8209 } from "./wk8209.js";
23
+ import { wk8301 } from "./wk8301.js";
24
+ import { wk8302 } from "./wk8302.js";
25
+ import { wk8303 } from "./wk8303.js";
26
+ import { wk8304 } from "./wk8304.js";
27
+ import { wk8305 } from "./wk8305.js";
28
+ import { wk8306 } from "./wk8306.js";
29
+ import { wk8401 } from "./wk8401.js";
30
+ import { wk8402 } from "./wk8402.js";
31
+ import { wk8403 } from "./wk8403.js";
32
+ import { wk8404 } from "./wk8404.js";
33
+ import { wk8405 } from "./wk8405.js";
34
+ import { wk8406 } from "./wk8406.js";
35
+ import { wk8407 } from "./wk8407.js";
36
+ import { wk8501 } from "./wk8501.js";
37
+ import { wk8502 } from "./wk8502.js";
38
+ import { wk8503 } from "./wk8503.js";
39
+ import { wk8504 } from "./wk8504.js";
40
+ import { wk8505 } from "./wk8505.js";
41
+ import { wk8601 } from "./wk8601.js";
42
+ import { wk8602 } from "./wk8602.js";
43
+ import { wk8603 } from "./wk8603.js";
44
+ import { wk8604 } from "./wk8604.js";
45
+ import { wk8605 } from "./wk8605.js";
46
+ import { wk8606 } from "./wk8606.js";
47
+ import { wk8701 } from "./wk8701.js";
48
+ import { wk8702 } from "./wk8702.js";
49
+ import { wk8703 } from "./wk8703.js";
50
+ import { wk8704 } from "./wk8704.js";
51
+ import { wk8705 } from "./wk8705.js";
52
+ export const postSynthChecks = [
53
+ argo002,
54
+ argo003,
55
+ argo005,
56
+ flux002,
57
+ flux003,
58
+ wk8005,
59
+ wk8006,
60
+ wk8041,
61
+ wk8042,
62
+ wk8101,
63
+ wk8102,
64
+ wk8103,
65
+ wk8104,
66
+ wk8105,
67
+ wk8201,
68
+ wk8202,
69
+ wk8203,
70
+ wk8204,
71
+ wk8205,
72
+ wk8207,
73
+ wk8208,
74
+ wk8209,
75
+ wk8301,
76
+ wk8302,
77
+ wk8303,
78
+ wk8304,
79
+ wk8305,
80
+ wk8306,
81
+ wk8401,
82
+ wk8402,
83
+ wk8403,
84
+ wk8404,
85
+ wk8405,
86
+ wk8406,
87
+ wk8407,
88
+ wk8501,
89
+ wk8502,
90
+ wk8503,
91
+ wk8504,
92
+ wk8505,
93
+ wk8601,
94
+ wk8602,
95
+ wk8603,
96
+ wk8604,
97
+ wk8605,
98
+ wk8606,
99
+ wk8701,
100
+ wk8702,
101
+ wk8703,
102
+ wk8704,
103
+ wk8705,
104
+ ];
@@ -0,0 +1,186 @@
1
+ /**
2
+ * Shared helpers for Kubernetes post-synthesis lint rules.
3
+ *
4
+ * Provides the `ctx.docs` → `K8sManifest[]` bridge every check reads through,
5
+ * and container extraction logic that handles all common workload types.
6
+ */
7
+ import { parseOutputDocs } from "@intentius/chant/lint/post-synth";
8
+ export { getPrimaryOutput, getAdditionalFiles } from "@intentius/chant/lint/post-synth";
9
+ /**
10
+ * Every parsed Kubernetes manifest across all lexicon outputs, via `ctx.docs`
11
+ * (chant #975) — the shared, parse-once view every k8s post-synth check
12
+ * reads, in place of the old per-check `parseK8sManifests(getPrimaryOutput(output))`
13
+ * splitter (chant #976). Two documents are dropped rather than passed through
14
+ * as a "manifest":
15
+ *
16
+ * - `d.error` — a document `parseOutputDocs` could not make sense of.
17
+ * - `d.file` — a document that came from a `SerializerResult.files` sidecar
18
+ * (a committed-encrypted secret's ciphertext, in this lexicon) rather than
19
+ * a lexicon's primary output. `parseK8sManifests` only ever saw
20
+ * `getPrimaryOutput(output)`, so a sidecar was invisible to every general
21
+ * manifest-consuming check — see `getAdditionalFiles`'s doc comment on why
22
+ * that separation is deliberate. Only `sops-helpers.ts`'s
23
+ * `resolveEncryptedSecretClaims` (WK8504/WK8503's producer join) reads
24
+ * `.files` explicitly; this bridge must keep it that way.
25
+ *
26
+ * `ctx.docs` is populated by every context chant itself builds (`chant
27
+ * build`, `@intentius/chant-test-utils`'s `createPostSynthContext` /
28
+ * `makePostSynthCtx*`) — see `PostSynthContext.docs`'s doc comment for why
29
+ * it is still optional at the type level. A handful of this lexicon's own
30
+ * hand-rolled `PostSynthContext` test literals (`post-synth.test.ts`'s
31
+ * `makeCtx`) predate that field and never set it; for those, fall back to
32
+ * parsing `ctx.outputs` directly with the same `parseOutputDocs` primitive,
33
+ * rather than silently returning no manifests.
34
+ */
35
+ export function docsToManifests(ctx) {
36
+ const docs = ctx.docs ?? parseOutputDocs(ctx.outputs);
37
+ return docs
38
+ .filter((d) => !d.error && !d.file)
39
+ .map((d) => d.value);
40
+ }
41
+ /**
42
+ * Extract the pod spec from any workload manifest.
43
+ *
44
+ * Handles:
45
+ * - Pod: spec directly
46
+ * - Deployment, StatefulSet, DaemonSet: spec.template.spec
47
+ * - Job: spec.template.spec
48
+ * - CronJob: spec.jobTemplate.spec.template.spec
49
+ */
50
+ export function extractPodSpec(manifest) {
51
+ const kind = manifest.kind;
52
+ const spec = manifest.spec;
53
+ if (!spec)
54
+ return null;
55
+ switch (kind) {
56
+ case "Pod":
57
+ return spec;
58
+ case "Deployment":
59
+ case "StatefulSet":
60
+ case "DaemonSet":
61
+ case "Job": {
62
+ const template = spec.template;
63
+ return template?.spec ?? null;
64
+ }
65
+ case "CronJob": {
66
+ const jobTemplate = spec.jobTemplate;
67
+ const jobSpec = jobTemplate?.spec;
68
+ const template = jobSpec?.template;
69
+ return template?.spec ?? null;
70
+ }
71
+ // KServe ServingRuntime/ClusterServingRuntime carry `containers` and
72
+ // `tolerations` directly on spec — no template wrapper, unlike the
73
+ // built-in workload kinds above.
74
+ case "ServingRuntime":
75
+ case "ClusterServingRuntime":
76
+ return spec;
77
+ default:
78
+ return null;
79
+ }
80
+ }
81
+ /**
82
+ * Extract all containers (including init containers) from a workload manifest.
83
+ */
84
+ export function extractContainers(manifest) {
85
+ const podSpec = extractPodSpec(manifest);
86
+ if (!podSpec)
87
+ return [];
88
+ const containers = [];
89
+ if (Array.isArray(podSpec.containers)) {
90
+ for (const c of podSpec.containers) {
91
+ if (typeof c === "object" && c !== null) {
92
+ containers.push(c);
93
+ }
94
+ }
95
+ }
96
+ if (Array.isArray(podSpec.initContainers)) {
97
+ for (const c of podSpec.initContainers) {
98
+ if (typeof c === "object" && c !== null) {
99
+ containers.push(c);
100
+ }
101
+ }
102
+ }
103
+ return containers;
104
+ }
105
+ /**
106
+ * Workload kinds that contain pod templates.
107
+ */
108
+ export const WORKLOAD_KINDS = new Set([
109
+ "Pod",
110
+ "Deployment",
111
+ "StatefulSet",
112
+ "DaemonSet",
113
+ "Job",
114
+ "CronJob",
115
+ ]);
116
+ /**
117
+ * Workload kinds that can request `nvidia.com/gpu` — the built-in workload
118
+ * kinds plus KServe's ServingRuntime/ClusterServingRuntime, which declare
119
+ * `containers`/`tolerations` directly on spec (see `extractPodSpec`).
120
+ */
121
+ export const GPU_POD_KINDS = new Set([
122
+ ...WORKLOAD_KINDS,
123
+ "ServingRuntime",
124
+ "ClusterServingRuntime",
125
+ ]);
126
+ /** Resource name used for GPU requests/limits across the lexicon. */
127
+ export const NVIDIA_GPU_RESOURCE = "nvidia.com/gpu";
128
+ /**
129
+ * True when any container in `containers` requests or limits
130
+ * `nvidia.com/gpu`.
131
+ */
132
+ export function requestsGpu(containers) {
133
+ return containers.some((c) => {
134
+ const requests = c.resources?.requests;
135
+ const limits = c.resources?.limits;
136
+ return Boolean(requests?.[NVIDIA_GPU_RESOURCE]) || Boolean(limits?.[NVIDIA_GPU_RESOURCE]);
137
+ });
138
+ }
139
+ /**
140
+ * True when `tolerations` includes one that tolerates the `nvidia.com/gpu`
141
+ * taint — either an explicit `key: "nvidia.com/gpu"` toleration, or a
142
+ * wildcard `{ operator: "Exists" }` with no key (tolerates every taint).
143
+ */
144
+ export function toleratesGpu(tolerations) {
145
+ if (!Array.isArray(tolerations))
146
+ return false;
147
+ return tolerations.some((t) => t.key === NVIDIA_GPU_RESOURCE || (t.operator === "Exists" && !t.key));
148
+ }
149
+ /**
150
+ * Parse a Kubernetes memory string to bytes.
151
+ *
152
+ * Handles binary suffixes (Ki, Mi, Gi, Ti) and SI suffixes (K, M, G, T).
153
+ * Returns NaN for unrecognised strings — callers should skip checks on NaN.
154
+ *
155
+ * @example
156
+ * parseMemoryBytes("4Gi") // 4294967296
157
+ * parseMemoryBytes("512Mi") // 536870912
158
+ * parseMemoryBytes("2048") // 2048
159
+ */
160
+ export function parseMemoryBytes(s) {
161
+ const m = /^([0-9]+(?:\.[0-9]+)?)(Ki|Mi|Gi|Ti|K|M|G|T)?$/.exec(s.trim());
162
+ if (!m)
163
+ return NaN;
164
+ const n = parseFloat(m[1]);
165
+ const multipliers = {
166
+ Ki: 1024, Mi: 1024 ** 2, Gi: 1024 ** 3, Ti: 1024 ** 4,
167
+ K: 1000, M: 1000 ** 2, G: 1000 ** 3, T: 1000 ** 4,
168
+ };
169
+ return n * (multipliers[m[2] ?? ""] ?? 1);
170
+ }
171
+ /**
172
+ * Extract the Ray version from an image tag.
173
+ *
174
+ * Looks for a semver-style version prefix in the image tag:
175
+ * "rayproject/ray:2.40.0" → "2.40.0"
176
+ * "rayproject/ray:2.40.0-py310" → "2.40.0"
177
+ * "us-docker.pkg.dev/.../ray:2.9.3-gpu" → "2.9.3"
178
+ * "rayproject/ray:latest" → undefined
179
+ *
180
+ * Returns undefined when no version can be found.
181
+ */
182
+ export function extractRayVersion(image) {
183
+ const tag = image.includes(":") ? image.split(":").pop() : image;
184
+ const m = /^([0-9]+\.[0-9]+\.[0-9]+)/.exec(tag);
185
+ return m ? m[1] : undefined;
186
+ }