@intentius/chant-lexicon-helm 0.45.0 → 0.49.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 (118) hide show
  1. package/dist/archive-render.d.ts +94 -0
  2. package/dist/archive-render.d.ts.map +1 -0
  3. package/dist/commands.d.ts +54 -0
  4. package/dist/commands.d.ts.map +1 -0
  5. package/dist/config.d.ts +135 -0
  6. package/dist/config.d.ts.map +1 -0
  7. package/dist/deep-observe-hooks.d.ts +2 -0
  8. package/dist/deep-observe-hooks.d.ts.map +1 -0
  9. package/dist/deep-observe.d.ts +54 -0
  10. package/dist/deep-observe.d.ts.map +1 -0
  11. package/dist/describe-resources.d.ts +17 -0
  12. package/dist/describe-resources.d.ts.map +1 -0
  13. package/dist/index.d.ts +18 -2
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/integrity.json +5 -3
  16. package/dist/lint/audit-catalog.d.ts.map +1 -1
  17. package/dist/lint/post-synth/index.d.ts.map +1 -1
  18. package/dist/lint/post-synth/whm503.d.ts +34 -0
  19. package/dist/lint/post-synth/whm503.d.ts.map +1 -0
  20. package/dist/lint/post-synth/whm504.d.ts +20 -0
  21. package/dist/lint/post-synth/whm504.d.ts.map +1 -0
  22. package/dist/manifest.json +1 -1
  23. package/dist/okf/index.md +2 -0
  24. package/dist/okf/rules/WHM503.md +15 -0
  25. package/dist/okf/rules/WHM504.md +11 -0
  26. package/dist/okf/types/Values.md +1 -0
  27. package/dist/op/activities/cluster-probe.d.ts +44 -0
  28. package/dist/op/activities/cluster-probe.d.ts.map +1 -0
  29. package/dist/op/activities/helm.d.ts +179 -5
  30. package/dist/op/activities/helm.d.ts.map +1 -1
  31. package/dist/op/activities/index.d.ts +4 -2
  32. package/dist/op/activities/index.d.ts.map +1 -1
  33. package/dist/pinnability/actions.d.ts +76 -0
  34. package/dist/pinnability/actions.d.ts.map +1 -0
  35. package/dist/pinnability/classify.d.ts +129 -0
  36. package/dist/pinnability/classify.d.ts.map +1 -0
  37. package/dist/pinnability/conditions.d.ts +109 -0
  38. package/dist/pinnability/conditions.d.ts.map +1 -0
  39. package/dist/pinnability/index.d.ts +17 -0
  40. package/dist/pinnability/index.d.ts.map +1 -0
  41. package/dist/pinnability/localize.d.ts +124 -0
  42. package/dist/pinnability/localize.d.ts.map +1 -0
  43. package/dist/pinnability/render-stream.d.ts +27 -0
  44. package/dist/pinnability/render-stream.d.ts.map +1 -0
  45. package/dist/pinnability/values.d.ts +69 -0
  46. package/dist/pinnability/values.d.ts.map +1 -0
  47. package/dist/plugin.d.ts.map +1 -1
  48. package/dist/release-observe.d.ts +109 -0
  49. package/dist/release-observe.d.ts.map +1 -0
  50. package/dist/render-diff.d.ts +192 -0
  51. package/dist/render-diff.d.ts.map +1 -0
  52. package/dist/render-digest.d.ts +135 -0
  53. package/dist/render-digest.d.ts.map +1 -0
  54. package/dist/render-store.d.ts +310 -0
  55. package/dist/render-store.d.ts.map +1 -0
  56. package/dist/render-wrapper.d.ts +140 -0
  57. package/dist/render-wrapper.d.ts.map +1 -0
  58. package/dist/render.d.ts +81 -0
  59. package/dist/render.d.ts.map +1 -1
  60. package/dist/rules/whm503.ts +101 -0
  61. package/dist/rules/whm504.ts +52 -0
  62. package/dist/skills/chant-helm.md +7 -0
  63. package/dist/values-probe.d.ts +177 -0
  64. package/dist/values-probe.d.ts.map +1 -0
  65. package/dist/wrapper-chart.d.ts +61 -0
  66. package/dist/wrapper-chart.d.ts.map +1 -0
  67. package/package.json +10 -4
  68. package/src/archive-render.test.ts +201 -0
  69. package/src/archive-render.ts +119 -0
  70. package/src/commands.test.ts +487 -0
  71. package/src/commands.ts +445 -0
  72. package/src/config.test.ts +130 -0
  73. package/src/config.ts +220 -0
  74. package/src/deep-observe-hooks.ts +20 -0
  75. package/src/deep-observe.test.ts +165 -0
  76. package/src/deep-observe.ts +121 -0
  77. package/src/describe-resources.test.ts +255 -0
  78. package/src/describe-resources.ts +198 -0
  79. package/src/index.ts +98 -2
  80. package/src/lifecycle-integration.test.ts +289 -0
  81. package/src/lint/audit-catalog.ts +2 -0
  82. package/src/lint/post-synth/index.ts +4 -0
  83. package/src/lint/post-synth/whm503.test.ts +134 -0
  84. package/src/lint/post-synth/whm503.ts +101 -0
  85. package/src/lint/post-synth/whm504.test.ts +105 -0
  86. package/src/lint/post-synth/whm504.ts +52 -0
  87. package/src/op/activities/cluster-probe.test.ts +66 -0
  88. package/src/op/activities/cluster-probe.ts +148 -0
  89. package/src/op/activities/helm.test.ts +824 -0
  90. package/src/op/activities/helm.ts +628 -5
  91. package/src/op/activities/index.ts +17 -2
  92. package/src/pinnability/actions.ts +263 -0
  93. package/src/pinnability/classify.ts +579 -0
  94. package/src/pinnability/conditions.ts +0 -0
  95. package/src/pinnability/index.ts +77 -0
  96. package/src/pinnability/localize.test.ts +234 -0
  97. package/src/pinnability/localize.ts +644 -0
  98. package/src/pinnability/pinnability.test.ts +560 -0
  99. package/src/pinnability/render-stream.ts +59 -0
  100. package/src/pinnability/values.ts +205 -0
  101. package/src/plugin.test.ts +3 -1
  102. package/src/plugin.ts +47 -0
  103. package/src/release-observe.ts +392 -0
  104. package/src/render-diff.test.ts +366 -0
  105. package/src/render-diff.ts +407 -0
  106. package/src/render-digest.test.ts +186 -0
  107. package/src/render-digest.ts +207 -0
  108. package/src/render-store.test.ts +412 -0
  109. package/src/render-store.ts +621 -0
  110. package/src/render-wrapper.test.ts +259 -0
  111. package/src/render-wrapper.ts +274 -0
  112. package/src/render.test.ts +383 -4
  113. package/src/render.ts +319 -8
  114. package/src/skills/chant-helm.md +7 -0
  115. package/src/values-probe.test.ts +400 -0
  116. package/src/values-probe.ts +627 -0
  117. package/src/wrapper-chart.test.ts +110 -0
  118. package/src/wrapper-chart.ts +124 -0
@@ -0,0 +1,52 @@
1
+ /**
2
+ * WHM504: Dead value assignment (#1252, epic #1228 Phase 7).
3
+ *
4
+ * A supplied value that never survives coalescing is a lie in the config:
5
+ * it reads as if it configures something, and nothing ever sees it. The
6
+ * coalesced-values probe (#1251, ../../values-probe.ts) knows three ways a
7
+ * supplied value dies — shadowed by a later supplied layer, targeting a
8
+ * dependency a `condition:` disabled, or a values map under a top-level key
9
+ * that names no dependency at all (the silently-ignored subchart typo).
10
+ *
11
+ * The check reports over the probe records of the current process
12
+ * (`getValuesProbeRecords`): whatever ran the probe — the pinned-render
13
+ * build path, a test, a direct call — this check turns its dead assignments
14
+ * into diagnostics. No probe run, nothing to report; the probe itself needs
15
+ * the helm binary and the chart source, which a post-synth check must not
16
+ * require on its own.
17
+ */
18
+
19
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
20
+ import { getValuesProbeRecords, type DeadAssignment } from "../../values-probe";
21
+
22
+ function describe(dead: DeadAssignment): string {
23
+ switch (dead.reason) {
24
+ case "shadowed":
25
+ return `supplied value "${dead.path}" (${dead.origin}) never survives coalescing — shadowed by ${dead.shadowedBy}`;
26
+ case "disabled-subchart":
27
+ return `supplied value "${dead.path}" (${dead.origin}) targets a disabled subchart — ${dead.shadowedBy}`;
28
+ case "unknown-subchart":
29
+ return `supplied values under "${dead.path}" (${dead.origin}) target no subchart — ${dead.shadowedBy}`;
30
+ }
31
+ }
32
+
33
+ export const whm504: PostSynthCheck = {
34
+ id: "WHM504",
35
+ description: "Detect supplied values that never survive coalescing (shadowed, disabled subchart, unknown subchart)",
36
+
37
+ check(_ctx: PostSynthContext): PostSynthDiagnostic[] {
38
+ const diagnostics: PostSynthDiagnostic[] = [];
39
+ for (const record of getValuesProbeRecords()) {
40
+ for (const dead of record.probe.deadAssignments) {
41
+ diagnostics.push({
42
+ checkId: "WHM504",
43
+ severity: "warning",
44
+ message: describe(dead),
45
+ entity: record.name,
46
+ lexicon: "helm",
47
+ });
48
+ }
49
+ }
50
+ return diagnostics;
51
+ },
52
+ };
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Pure comparison/parsing halves of the deploy-time capability probe
3
+ * (#1244). The exec-driven probe itself is exercised through `helmInstall`
4
+ * in helm.test.ts, against the scripted kubectl double.
5
+ */
6
+ import { describe, test, expect } from "vitest";
7
+ import { compareCapabilityProfile, majorMinorOf, type LiveClusterCapabilities } from "./cluster-probe";
8
+
9
+ const live: LiveClusterCapabilities = {
10
+ kubeVersion: "v1.33.6+k3s1",
11
+ majorMinor: "1.33",
12
+ apiVersions: ["v1", "apps/v1", "batch/v1", "monitoring.coreos.com/v1"],
13
+ };
14
+
15
+ describe("majorMinorOf", () => {
16
+ test("accepts every shape KUBE_VERSION_PATTERN admits, plus live gitVersions", () => {
17
+ expect(majorMinorOf("1.33")).toBe("1.33");
18
+ expect(majorMinorOf("1.33.6")).toBe("1.33");
19
+ expect(majorMinorOf("v1.33.6")).toBe("1.33");
20
+ expect(majorMinorOf("v1.33.6+k3s1")).toBe("1.33");
21
+ });
22
+
23
+ test("rejects the unparseable", () => {
24
+ expect(majorMinorOf("latest")).toBeUndefined();
25
+ expect(majorMinorOf("")).toBeUndefined();
26
+ });
27
+ });
28
+
29
+ describe("compareCapabilityProfile", () => {
30
+ test("matching profile has no divergences — patch skew tolerated, extra live apiVersions ignored", () => {
31
+ expect(
32
+ compareCapabilityProfile(
33
+ { kubeVersion: "1.33.4", apiVersions: ["apps/v1", "monitoring.coreos.com/v1"] },
34
+ live,
35
+ ),
36
+ ).toEqual([]);
37
+ });
38
+
39
+ test("a kubeVersion divergence names both the declared and the live version", () => {
40
+ expect(compareCapabilityProfile({ kubeVersion: "1.31.4" }, live)).toEqual([
41
+ "kubeVersion: profile declares 1.31.4 (1.31), cluster runs v1.33.6+k3s1 (1.33)",
42
+ ]);
43
+ });
44
+
45
+ test("every missing apiVersion is named, one divergence each", () => {
46
+ expect(
47
+ compareCapabilityProfile(
48
+ { apiVersions: ["missing.io/v1", "apps/v1", "alsomissing.io/v1beta1"] },
49
+ live,
50
+ ),
51
+ ).toEqual([
52
+ "apiVersion missing.io/v1: declared by the profile, not served by the cluster",
53
+ "apiVersion alsomissing.io/v1beta1: declared by the profile, not served by the cluster",
54
+ ]);
55
+ });
56
+
57
+ test("kubeVersion and apiVersion divergences accumulate", () => {
58
+ expect(
59
+ compareCapabilityProfile({ kubeVersion: "1.31", apiVersions: ["missing.io/v1"] }, live),
60
+ ).toHaveLength(2);
61
+ });
62
+
63
+ test("a profile declaring nothing diverges from nothing", () => {
64
+ expect(compareCapabilityProfile({}, live)).toEqual([]);
65
+ });
66
+ });
@@ -0,0 +1,148 @@
1
+ /**
2
+ * Live-cluster capability probe for the deploy-time profile assertion
3
+ * (#1244, epic #1228 phase 4).
4
+ *
5
+ * A render pinned for one capability profile and deployed to a cluster with
6
+ * another is silently skewed: the bytes were computed against a declared
7
+ * `kubeVersion` and `apiVersions` set, and nothing at deploy time checked
8
+ * the target actually matches. This module reads the target cluster's real
9
+ * capabilities — `kubectl version -o json` for the server version,
10
+ * `kubectl api-versions` for the served API set — through the same
11
+ * exec-and-ambient-kubeconfig convention `helmInstall` already uses for the
12
+ * helm binary, and names every divergence from a declared profile.
13
+ *
14
+ * The comparison tolerance is deliberate: kubeVersion matches on
15
+ * major.minor. A profile declaring `1.33.6` matches a live `v1.33.4+k3s1` —
16
+ * the capability surface helm renders against (`.Capabilities.KubeVersion`)
17
+ * is a major.minor fact, and patch skew within a minor changes no API
18
+ * surface a chart can probe. Declared apiVersions are exact-match: each one
19
+ * the profile names must be served by the cluster.
20
+ */
21
+ import { exec } from "node:child_process";
22
+ import { promisify } from "node:util";
23
+
24
+ const execAsync = promisify(exec);
25
+
26
+ /** What the probe read off the live cluster. */
27
+ export interface LiveClusterCapabilities {
28
+ /** The server's full version string (`serverVersion.gitVersion`), e.g. `v1.33.6+k3s1`. */
29
+ kubeVersion: string;
30
+ /** The server's `major.minor`, e.g. `1.33`, parsed from `serverVersion.major`/`minor` (or `gitVersion` when those are absent). */
31
+ majorMinor: string;
32
+ /** Every API version the cluster serves, one `group/version` (or `v1`) per entry, from `kubectl api-versions`. */
33
+ apiVersions: string[];
34
+ }
35
+
36
+ /**
37
+ * Thrown when the cluster's capabilities cannot be read at all — kubectl
38
+ * missing, cluster unreachable, or unparseable output. A deploy that
39
+ * declared a profile cannot verify its guarantee through this error, so the
40
+ * caller must refuse to proceed rather than deploy blind.
41
+ */
42
+ export class ClusterProbeError extends Error {
43
+ constructor(message: string) {
44
+ super(message);
45
+ this.name = "ClusterProbeError";
46
+ }
47
+ }
48
+
49
+ /** `major.minor` from a declared or live version string (`1.33`, `1.33.6`, `v1.33.6+k3s1`). Undefined when the string has no parseable major.minor. */
50
+ export function majorMinorOf(version: string): string | undefined {
51
+ const match = /^v?(\d+)\.(\d+)/.exec(version.trim());
52
+ return match ? `${match[1]}.${match[2]}` : undefined;
53
+ }
54
+
55
+ function parseServerVersion(raw: string): { kubeVersion: string; majorMinor: string } {
56
+ let parsed: unknown;
57
+ try {
58
+ parsed = JSON.parse(raw);
59
+ } catch {
60
+ throw new ClusterProbeError(`kubectl version -o json produced unparseable output: ${raw.slice(0, 200)}`);
61
+ }
62
+ const server = (parsed as { serverVersion?: Record<string, unknown> }).serverVersion;
63
+ if (!server) {
64
+ throw new ClusterProbeError(
65
+ "kubectl version -o json reported no serverVersion — the cluster did not answer",
66
+ );
67
+ }
68
+ const gitVersion = typeof server.gitVersion === "string" ? server.gitVersion : "";
69
+ // major/minor are the authoritative fields; minor can carry a vendor
70
+ // suffix (GKE reports "33+"), so digits are extracted rather than trusted.
71
+ const major = typeof server.major === "string" ? /\d+/.exec(server.major)?.[0] : undefined;
72
+ const minor = typeof server.minor === "string" ? /\d+/.exec(server.minor)?.[0] : undefined;
73
+ const majorMinor = major && minor ? `${major}.${minor}` : gitVersion ? majorMinorOf(gitVersion) : undefined;
74
+ if (!majorMinor) {
75
+ throw new ClusterProbeError(
76
+ `kubectl version -o json reported no usable server version (gitVersion: ${gitVersion || "<absent>"})`,
77
+ );
78
+ }
79
+ return { kubeVersion: gitVersion || majorMinor, majorMinor };
80
+ }
81
+
82
+ /**
83
+ * Read the target cluster's capabilities. Uses whatever kubeconfig/context
84
+ * the process environment provides — the same ambient target `helm upgrade`
85
+ * itself will act on, so the probe and the deploy see one cluster.
86
+ */
87
+ export async function probeClusterCapabilities(signal?: AbortSignal): Promise<LiveClusterCapabilities> {
88
+ let versionRaw: string;
89
+ let apiVersionsRaw: string;
90
+ try {
91
+ const [versionResult, apiResult] = await Promise.all([
92
+ execAsync("kubectl version -o json", { signal }),
93
+ execAsync("kubectl api-versions", { signal }),
94
+ ]);
95
+ versionRaw = versionResult.stdout;
96
+ apiVersionsRaw = apiResult.stdout;
97
+ } catch (err) {
98
+ throw new ClusterProbeError(
99
+ `could not read the target cluster's capabilities: ${err instanceof Error ? err.message : String(err)}`,
100
+ );
101
+ }
102
+ const { kubeVersion, majorMinor } = parseServerVersion(versionRaw);
103
+ const apiVersions = apiVersionsRaw
104
+ .split("\n")
105
+ .map((line) => line.trim())
106
+ .filter((line) => line.length > 0);
107
+ return { kubeVersion, majorMinor, apiVersions };
108
+ }
109
+
110
+ /** The declared facts a profile asserts about a cluster — the activity-side profile shape. */
111
+ export interface DeclaredCapabilityProfile {
112
+ name?: string;
113
+ kubeVersion?: string;
114
+ apiVersions?: string[];
115
+ }
116
+
117
+ /**
118
+ * Every way `live` diverges from what `profile` declares, one message per
119
+ * divergence, each naming the declared and the live value. Empty when the
120
+ * cluster matches the profile.
121
+ *
122
+ * kubeVersion compares on major.minor (see module doc). apiVersions is a
123
+ * subset check: everything the profile declares must be served; extra API
124
+ * versions on the cluster are not a divergence, since a render only ever
125
+ * probed the declared set.
126
+ */
127
+ export function compareCapabilityProfile(
128
+ profile: DeclaredCapabilityProfile,
129
+ live: LiveClusterCapabilities,
130
+ ): string[] {
131
+ const divergences: string[] = [];
132
+ if (profile.kubeVersion) {
133
+ const declared = majorMinorOf(profile.kubeVersion);
134
+ if (declared !== live.majorMinor) {
135
+ divergences.push(
136
+ `kubeVersion: profile declares ${profile.kubeVersion} (${declared ?? "unparseable"}), ` +
137
+ `cluster runs ${live.kubeVersion} (${live.majorMinor})`,
138
+ );
139
+ }
140
+ }
141
+ const served = new Set(live.apiVersions);
142
+ for (const apiVersion of profile.apiVersions ?? []) {
143
+ if (!served.has(apiVersion)) {
144
+ divergences.push(`apiVersion ${apiVersion}: declared by the profile, not served by the cluster`);
145
+ }
146
+ }
147
+ return divergences;
148
+ }