@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
package/dist/render.d.ts CHANGED
@@ -30,6 +30,7 @@
30
30
  * values: { installCRDs: true },
31
31
  * });
32
32
  */
33
+ import { type HelmCapabilityProfile, type HelmCapabilityProfileRef } from "./config.js";
33
34
  export interface HelmRenderProps {
34
35
  /** Logical name for the render (used in cache key + composite name). */
35
36
  name: string;
@@ -50,6 +51,86 @@ export interface HelmRenderProps {
50
51
  * fresh render.
51
52
  */
52
53
  noCache?: boolean;
54
+ /**
55
+ * Capability profile the render is pinned against (#1235, epic #1228).
56
+ *
57
+ * A string names a profile declared in `chant.config.ts`'s
58
+ * `helm.capabilityProfiles` (per cluster — see `./config.ts`); an inline
59
+ * object carries the same facts directly. When set, `helm template` runs
60
+ * with `--kube-version` and `--api-versions` from the profile, so
61
+ * `.Capabilities` reflects the declared cluster instead of whatever the
62
+ * helm binary defaults to. A named profile the config does not declare is
63
+ * an error at synth, never a silent fallback. Absent, rendering is
64
+ * unpinned — exactly today's behavior.
65
+ */
66
+ capabilityProfile?: HelmCapabilityProfileRef;
67
+ /**
68
+ * Whether this render persists to the content-addressed render store
69
+ * (#1238) — canonical bytes under their `contentDigest` plus a
70
+ * `RenderManifest`, in `~/.chant/helm-renders/sha256-<hex>/` (see
71
+ * ./render-store.ts).
72
+ *
73
+ * Only a pinned render (capabilityProfile present) can persist — an
74
+ * unpinned render has no content identity, and `persist: true` on one is
75
+ * a synth error naming that reason. For pinned renders the default
76
+ * follows the cache knob: persistence is on unless `noCache` is set
77
+ * (`noCache: true` + `persist: true` forces a fresh render that is still
78
+ * persisted; `persist: false` turns the store off entirely).
79
+ */
80
+ persist?: boolean;
81
+ /**
82
+ * Source ref/commit to record in the persisted `RenderManifest`, when the
83
+ * caller has one. Never resolved implicitly and never fabricated — absent
84
+ * means the manifest records `sourceRef: null`.
85
+ */
86
+ sourceRef?: string;
87
+ }
88
+ /**
89
+ * What one `HelmRender` invocation recorded about itself. `capabilityProfile`
90
+ * is the profile identity the render was pinned against; `undefined` means
91
+ * the render was unpinned and its bytes depend on the local helm binary's
92
+ * defaults.
93
+ *
94
+ * Pinned renders (profile present — the v1 gate, see #1237) also carry the
95
+ * digest pair:
96
+ *
97
+ * - `inputDigest` — `sha256:` over the canonical JSON of the declared inputs
98
+ * (chart reference, version, values, capability facts). Shared with the
99
+ * release-ledger digest #1243 records on deploy, via `helmInputDigest`.
100
+ * Answers "same inputs?" without touching any bytes.
101
+ * - `contentDigest` — `sha256:` over the canonical rendered bytes
102
+ * (`canonicalizeRender`). The artifact identity: answers "same bytes on
103
+ * the cluster?".
104
+ *
105
+ * They diverge exactly when the render is not a function of its declared
106
+ * inputs — `renderStability` in ./render-digest.ts names that.
107
+ *
108
+ * Unpinned renders record neither digest. Their bytes are a function of the
109
+ * local helm binary's defaulted capabilities, so a digest over them would
110
+ * assert an identity the render does not have — it would differ across
111
+ * machines that did nothing differently, and equal digests would still
112
+ * prove nothing about a cluster. No digest is the honest record.
113
+ */
114
+ export interface HelmRenderRecord {
115
+ /** The render's logical name (`HelmRenderProps.name`) — also the helm release name baked into the bytes. */
116
+ name: string;
117
+ chart: string;
118
+ version?: string;
119
+ capabilityProfile?: HelmCapabilityProfile;
120
+ /** Input-side identity (#1237/#1243). Present only for pinned renders. */
121
+ inputDigest?: string;
122
+ /** Content-side identity over canonical rendered bytes (#1237). Present only for pinned renders. */
123
+ contentDigest?: string;
124
+ /**
125
+ * The build-time coalesced-values probe's digest (#1251), present only
126
+ * when the probe ran — a pinned render of a local chart (the probe needs
127
+ * the chart source on disk, so a repo-fetched chart never gets one).
128
+ */
129
+ coalescedValuesDigest?: string;
53
130
  }
131
+ /** Every render recorded in this process, in invocation order. */
132
+ export declare function getHelmRenderRecords(): readonly HelmRenderRecord[];
133
+ /** Reset the record list (test isolation). */
134
+ export declare function clearHelmRenderRecords(): void;
54
135
  export declare const HelmRender: import("@intentius/chant").CompositeDefinition<HelmRenderProps, import("@intentius/chant").CompositeMembers>;
55
136
  //# sourceMappingURL=render.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAeH,MAAM,WAAW,eAAe;IAC9B,wEAAwE;IACxE,IAAI,EAAE,MAAM,CAAC;IACb,8DAA8D;IAC9D,IAAI,EAAE,MAAM,CAAC;IACb,0CAA0C;IAC1C,KAAK,EAAE,MAAM,CAAC;IACd,0CAA0C;IAC1C,OAAO,EAAE,MAAM,CAAC;IAChB,8DAA8D;IAC9D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sDAAsD;IACtD,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,2EAA2E;IAC3E,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAuHD,eAAO,MAAM,UAAU,8GA8BP,CAAC"}
1
+ {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAeH,OAAO,EAA4B,KAAK,qBAAqB,EAAE,KAAK,wBAAwB,EAAE,MAAM,UAAU,CAAC;AAU/G,MAAM,WAAW,eAAe;IAC9B,wEAAwE;IACxE,IAAI,EAAE,MAAM,CAAC;IACb,8DAA8D;IAC9D,IAAI,EAAE,MAAM,CAAC;IACb,0CAA0C;IAC1C,KAAK,EAAE,MAAM,CAAC;IACd,0CAA0C;IAC1C,OAAO,EAAE,MAAM,CAAC;IAChB,8DAA8D;IAC9D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sDAAsD;IACtD,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,2EAA2E;IAC3E,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;;;;;;;;OAWG;IACH,iBAAiB,CAAC,EAAE,wBAAwB,CAAC;IAC7C;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,WAAW,gBAAgB;IAC/B,4GAA4G;IAC5G,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,iBAAiB,CAAC,EAAE,qBAAqB,CAAC;IAC1C,0EAA0E;IAC1E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oGAAoG;IACpG,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;OAIG;IACH,qBAAqB,CAAC,EAAE,MAAM,CAAC;CAChC;AAID,kEAAkE;AAClE,wBAAgB,oBAAoB,IAAI,SAAS,gBAAgB,EAAE,CAElE;AAED,8CAA8C;AAC9C,wBAAgB,sBAAsB,IAAI,IAAI,CAE7C;AAmRD,eAAO,MAAM,UAAU,8GAsFP,CAAC"}
@@ -0,0 +1,101 @@
1
+ /**
2
+ * WHM503: Pinned render artifact carries Secret data.
3
+ *
4
+ * WHM407 covers the pre-render concern — a chart TEMPLATE with an inline,
5
+ * literal `data`/`stringData` value baked into `templates/`. This rule
6
+ * covers a different concern (#1241, epic #1228 Phase 3): the STORED,
7
+ * POST-RENDER artifact (`render-store.ts`, #1238). A pinned render is
8
+ * durable, promoted, and pushed to a registry — unlike a normal `helm
9
+ * upgrade`, whose rendered bytes are computed at deploy time and discarded.
10
+ * By the time a render lands in the store, every `.Values` reference and
11
+ * `{{ }}` expression is resolved, so a `kind: Secret` document with
12
+ * populated `data`/`stringData` there is a live credential baked into
13
+ * something that outlives the deploy that produced it.
14
+ *
15
+ * Scans every pinned render recorded so far in this process
16
+ * (`getHelmRenderRecords()` from `../../render`) that carries a
17
+ * `contentDigest`, loads its manifest + canonical content from the render
18
+ * store, and flags any `Secret` document whose `data` or `stringData` is
19
+ * non-empty.
20
+ */
21
+
22
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
23
+ import yaml from "js-yaml";
24
+
25
+ import type { HelmRenderRecord } from "../../render";
26
+ import { getHelmRenderRecords } from "../../render";
27
+ import { loadRenderManifest, readRenderDocument } from "../../render-store";
28
+
29
+ interface ParsedSecretDoc {
30
+ data?: unknown;
31
+ stringData?: unknown;
32
+ }
33
+
34
+ /** True when a `data`/`stringData` map carries at least one key. */
35
+ function isPopulated(value: unknown): boolean {
36
+ return typeof value === "object" && value !== null && Object.keys(value).length > 0;
37
+ }
38
+
39
+ /**
40
+ * Scan a set of `HelmRenderRecord`s for pinned renders whose stored artifact
41
+ * carries a `Secret` document with populated `data`/`stringData`. Exported
42
+ * separately from `whm503` so tests can drive it directly with hand-built
43
+ * records — no real `helm` binary required, unlike a `HelmRender()` call.
44
+ * `opts.root` overrides the render store root (test isolation).
45
+ */
46
+ export function checkRenderRecordsForSecrets(
47
+ records: readonly HelmRenderRecord[],
48
+ opts?: { root?: string },
49
+ ): PostSynthDiagnostic[] {
50
+ const diagnostics: PostSynthDiagnostic[] = [];
51
+
52
+ for (const record of records) {
53
+ if (!record.contentDigest) continue; // unpinned — no stored artifact to scan
54
+
55
+ const manifest = loadRenderManifest(record.contentDigest, opts);
56
+ if (!manifest) continue; // recorded but not (or no longer) in the store
57
+
58
+ for (const doc of manifest.documents) {
59
+ if (doc.kind !== "Secret") continue;
60
+
61
+ const found = readRenderDocument(record.contentDigest, { kind: doc.kind, name: doc.name, namespace: doc.namespace }, opts);
62
+ if (!found) continue;
63
+
64
+ let parsed: ParsedSecretDoc;
65
+ try {
66
+ parsed = (yaml.load(found.text) ?? {}) as ParsedSecretDoc;
67
+ } catch {
68
+ continue; // unparseable — not this check's problem to diagnose
69
+ }
70
+
71
+ if (!isPopulated(parsed.data) && !isPopulated(parsed.stringData)) continue;
72
+
73
+ const locator = doc.namespace ? `${doc.namespace}/${doc.name}` : doc.name;
74
+ diagnostics.push({
75
+ checkId: "WHM503",
76
+ severity: "error",
77
+ message:
78
+ `pinned render "${record.name}" (${record.chart}, ${record.contentDigest}): Secret "${locator}" ` +
79
+ `carries populated data/stringData — a pinned render is stored, promoted, and pushed to a registry, ` +
80
+ `so this bakes a live credential into a durable artifact. Declare the value with runtimeSlot() so ` +
81
+ `it is supplied per environment, or replace this Secret with HelmExternalSecret.`,
82
+ entity: locator,
83
+ lexicon: "helm",
84
+ });
85
+ }
86
+ }
87
+
88
+ return diagnostics;
89
+ }
90
+
91
+ export const whm503: PostSynthCheck = {
92
+ id: "WHM503",
93
+ description: "Pinned render artifact must not carry populated Secret data/stringData",
94
+
95
+ // This check's data source is the render store (#1238), not `ctx.outputs`
96
+ // (the un-rendered chart template files WHM005-WHM502 read) — see the file
97
+ // doc comment. `ctx` is accepted to satisfy `PostSynthCheck` and is unused.
98
+ check(_ctx: PostSynthContext): PostSynthDiagnostic[] {
99
+ return checkRenderRecordsForSecrets(getHelmRenderRecords());
100
+ },
101
+ };
@@ -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
+ };
@@ -289,6 +289,13 @@ const lifecycle = HelmCRDLifecycle({
289
289
  | WHM407 | post-synth | Secret with inline data |
290
290
  | WHM501 | post-synth | Unused values keys |
291
291
  | WHM502 | post-synth | Deprecated K8s API versions |
292
+ | WHM503 | pinned-render | Pinned render artifact carries populated Secret data/stringData — use `runtimeSlot()` or `HelmExternalSecret` |
293
+
294
+ WHM503 is a different stage from the rest: it scans the STORED, rendered
295
+ artifact a pinned `HelmRender` writes to the render store (#1238), not the
296
+ chart source `WHM005`-`WHM502` validate. `WHM407` catches an inline literal
297
+ in a chart TEMPLATE before rendering; WHM503 catches a resolved credential
298
+ that made it into a durable, promotable artifact.
292
299
 
293
300
  ## OCI registry workflow
294
301
 
@@ -0,0 +1,177 @@
1
+ /**
2
+ * Build-time coalesced-values probe (#1251, epic #1228 Phase 7) and the
3
+ * provenance derived from it (#1252).
4
+ *
5
+ * `helm template --debug` does not print coalesced values on either major
6
+ * version, and `helm get values --all` needs an installed release — the
7
+ * wrong side of the build/deploy line. So the probe extracts them from helm
8
+ * itself: copy the chart, replace every (sub)chart's `templates/` with a
9
+ * single `chant-values-probe.yaml` template containing `{{ toYaml .Values }}`,
10
+ * render, lift the probe documents out, discard the copy. Coalescing —
11
+ * parent-overrides-child, `global` propagation, alias scoping,
12
+ * `import-values`, tags — is delegated to helm rather than reimplemented
13
+ * (the approximation in `pinnability/values.ts` remains only as the
14
+ * classifier's offline fallback).
15
+ *
16
+ * The probe filename must NOT begin with `_`: helm treats underscore-prefixed
17
+ * template files as partials and skips them without error.
18
+ *
19
+ * The probe runs against a private copy in a temp directory, so the probe
20
+ * document can never appear in a real render of the chart — the pinned
21
+ * artifact path (#1237/#1242) never sees the injected template.
22
+ *
23
+ * A dependency disabled by a `condition:` (or tags) is pruned by helm before
24
+ * rendering, so it yields no probe document. Those instances are reported in
25
+ * `disabled` instead, with the condition path where determinable. Note the
26
+ * ROOT probe document still carries the values subtree a disabled dependency
27
+ * would have received — an installed release omits it (epic findings 14, 15).
28
+ *
29
+ * On the probe output, three provenance products (#1252):
30
+ *
31
+ * - `digest` — `sha256:` over the canonical JSON (core's `canonicalJson`,
32
+ * the #1237 convention) of the per-instance coalesced trees, keyed by
33
+ * scope path. Same inputs, same coalesced values, same digest.
34
+ * - `valueSources` — each coalesced path attributed to its winning layer:
35
+ * chart default / parent override / supplied file / --set.
36
+ * - `deadAssignments` — supplied values that never survive coalescing:
37
+ * shadowed by a later supplied layer, or targeting a disabled or unknown
38
+ * subchart path. WHM504 turns these into lint diagnostics.
39
+ */
40
+ /** Where a coalesced value came from. `computed` = no supplied or authored layer matches (e.g. `import-values`). */
41
+ export type ValueOrigin = "chart default" | "parent override" | "supplied file" | "--set" | "computed";
42
+ /**
43
+ * One layer of values supplied to the render, in ascending precedence order
44
+ * (a later layer overrides an earlier one, like repeated `--values` flags;
45
+ * `--set` layers conventionally come last). `origin` is the label reported
46
+ * by `valueSources` and dead-assignment findings.
47
+ */
48
+ export interface SuppliedValuesLayer {
49
+ origin: "supplied file" | "--set";
50
+ /** Optional identifier (a file path, a flag) used in messages. */
51
+ name?: string;
52
+ values: Record<string, unknown>;
53
+ }
54
+ /** One chart instance and the values helm coalesced for it. */
55
+ export interface CoalescedChartValues {
56
+ /** Value scope from the root: `[]` for the root chart, `["kidtwo"]` for an aliased dependency, nested for grandchildren. */
57
+ scope: string[];
58
+ /** `.Chart.Name` as rendered — the alias for an aliased dependency. */
59
+ chartName: string;
60
+ /** The fully coalesced values this instance saw, exactly as helm computed them. */
61
+ values: Record<string, unknown>;
62
+ }
63
+ /** A declared dependency helm pruned before rendering. */
64
+ export interface DisabledDependency {
65
+ scope: string[];
66
+ /** The dependency's real chart name (not the alias). */
67
+ name: string;
68
+ /** The `condition:` path that resolved false, when determinable (tags-disabled dependencies carry none). */
69
+ condition?: string;
70
+ }
71
+ /** A supplied value that never survives coalescing. */
72
+ export interface DeadAssignment {
73
+ /** Dot-joined path in root coordinates. */
74
+ path: string;
75
+ /** The layer that supplied it, e.g. `supplied file (values-prod.yaml)`. */
76
+ origin: string;
77
+ reason: "shadowed" | "disabled-subchart" | "unknown-subchart";
78
+ /** What killed it: the shadowing layer, or the disabling condition. */
79
+ shadowedBy?: string;
80
+ }
81
+ export interface CoalescedValuesProbe {
82
+ /** Enabled chart instances, root first, then by scope path. */
83
+ instances: CoalescedChartValues[];
84
+ disabled: DisabledDependency[];
85
+ /** `sha256:` over the canonical JSON of the per-instance coalesced trees. */
86
+ digest: string;
87
+ /** Dot-joined path (root coordinates, subchart paths scope-prefixed) to winning layer. */
88
+ valueSources: Record<string, ValueOrigin>;
89
+ deadAssignments: DeadAssignment[];
90
+ warnings: string[];
91
+ }
92
+ export interface ValuesProbeOptions {
93
+ /** Directory of the chart to probe (must contain Chart.yaml). Never modified — the probe works on a copy. */
94
+ chartDir: string;
95
+ /** Logical name recorded with the probe. Defaults to the release name. */
96
+ name?: string;
97
+ /** Release name passed to `helm template`. Default `chant-values-probe`. */
98
+ releaseName?: string;
99
+ namespace?: string;
100
+ /** Values layers in ascending precedence order. */
101
+ supplied?: SuppliedValuesLayer[];
102
+ /** Injectable helm runner (tests). Receives the full argv after `helm`. */
103
+ runHelm?: (args: string[]) => string;
104
+ }
105
+ /**
106
+ * The coalesced-values identity: `sha256:` over the canonical JSON of the
107
+ * per-instance trees keyed by scope path — the #1237 digest conventions
108
+ * (core `canonicalJson`, `sha256:` prefix). Two renders that coalesce the
109
+ * same values for every instance share it; any value, in any subchart,
110
+ * changing breaks it.
111
+ */
112
+ export declare function coalescedValuesDigest(instances: readonly CoalescedChartValues[]): string;
113
+ /** Inputs the pure provenance analysis needs — separable from the render for tests. */
114
+ export interface ValuesAttributionInput {
115
+ instances: readonly CoalescedChartValues[];
116
+ supplied: readonly SuppliedValuesLayer[];
117
+ /** The chart-default `values.yaml` for an instance scope; undefined when unknown. */
118
+ defaultsFor: (scope: readonly string[]) => Record<string, unknown> | undefined;
119
+ }
120
+ /**
121
+ * Attribute each coalesced path to the layer that won it.
122
+ *
123
+ * Keys are dot-joined root-coordinate paths: a subchart instance's paths are
124
+ * prefixed with its scope, so `kidtwo.replicas` is the `replicas` the
125
+ * `kidtwo` instance actually sees. A key of an instance's tree that is
126
+ * itself an enabled child scope is skipped — helm pushes the child's
127
+ * coalesced tree back into the parent's `.Values`, and the child instance
128
+ * owns those paths.
129
+ *
130
+ * Layer precedence mirrors helm: supplied layers (last first), then ancestor
131
+ * chart defaults (root outranks intermediates — "parent override"), then the
132
+ * instance's own defaults ("chart default"). A path no layer explains — an
133
+ * `import-values` product, a helm-computed merge — reports `computed`.
134
+ * Attribution matches on value equality, so a layer that assigned a path but
135
+ * lost it reports as the layer that actually won.
136
+ */
137
+ export declare function computeValueSources(input: ValuesAttributionInput): Record<string, ValueOrigin>;
138
+ /**
139
+ * Supplied values that never survive coalescing:
140
+ *
141
+ * - `shadowed` — a later supplied layer assigns the same path (a map merging
142
+ * into a map is not a shadow; anything else replaces).
143
+ * - `disabled-subchart` — the path targets a dependency a `condition:` (or
144
+ * tags) disabled, so no rendered chart ever reads it.
145
+ * - `unknown-subchart` — a map supplied under a top-level key that matches
146
+ * no dependency, no root default, and not `global`, on a chart that HAS
147
+ * dependencies: the classic silently-ignored typo of a subchart name.
148
+ * Reported once per top-level key per layer.
149
+ */
150
+ export declare function findDeadAssignments(input: ValuesAttributionInput, disabled: readonly DisabledDependency[]): DeadAssignment[];
151
+ /**
152
+ * Run the probe: copy the chart, inject probe templates, render, lift the
153
+ * probe documents out, analyze, discard the copy. The source chart is never
154
+ * touched. Every run is recorded for WHM504 (`getValuesProbeRecords`).
155
+ */
156
+ export declare function probeCoalescedValues(options: ValuesProbeOptions): CoalescedValuesProbe;
157
+ /** What one probe run recorded about itself — the WHM504 lint surface. */
158
+ export interface HelmValuesProbeRecord {
159
+ name: string;
160
+ chartDir: string;
161
+ probe: CoalescedValuesProbe;
162
+ }
163
+ /** Every probe recorded in this process, in invocation order. */
164
+ export declare function getValuesProbeRecords(): readonly HelmValuesProbeRecord[];
165
+ /** Record a probe computed elsewhere (the render path integration point). */
166
+ export declare function recordValuesProbe(record: HelmValuesProbeRecord): void;
167
+ /** Reset the record list (test isolation). */
168
+ export declare function clearValuesProbeRecords(): void;
169
+ /**
170
+ * The values gap between the probe and an installed release: an installed
171
+ * release's `helm get values --all` omits subtrees for disabled
172
+ * dependencies; the probe's root document keeps them (epic findings 14, 15).
173
+ * On shared keys the two agree — this helper names the shared-key view for
174
+ * comparisons against a live release.
175
+ */
176
+ export declare function rootCoalescedValues(probe: CoalescedValuesProbe): Record<string, unknown>;
177
+ //# sourceMappingURL=values-probe.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"values-probe.d.ts","sourceRoot":"","sources":["../src/values-probe.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Wrapper-chart materialization for the pinned install path (#1242, epic
3
+ * #1228 Phase 4).
4
+ *
5
+ * Turns a `RoutedRender` (#1239) into an installable helm chart on disk.
6
+ * The wrapper preserves the recorded bytes exactly:
7
+ *
8
+ * - CRD-group documents are written verbatim into the wrapper's `crds/`,
9
+ * which is what keeps `helm uninstall` from deleting them — the A/B test
10
+ * behind epic finding 4 showed a flat wrap converts uninstall from safe
11
+ * to data-destroying.
12
+ * - Every other document (main and hook groups) is written verbatim into
13
+ * `manifests/`, and each gets a one-line shim in `templates/` that reads
14
+ * it back with `.Files.Get`. The shim exists because rendered output can
15
+ * legitimately contain template-looking text — a ConfigMap carrying
16
+ * alertmanager notification templates renders `{{ $labels }}` as literal
17
+ * bytes — and a document placed directly in `templates/` would be run
18
+ * through the template engine a second time, mangling or failing on
19
+ * exactly those bytes. `.Files.Get` returns file content untemplated, so
20
+ * what helm installs is what the render store recorded.
21
+ * - Hook documents go through the same shim; their `helm.sh/hook`
22
+ * annotations are part of the recorded bytes, so helm registers and runs
23
+ * them from the rendered output unchanged (epic finding 5).
24
+ *
25
+ * The wrapper inherits the source chart's name and version (epic
26
+ * Decisions), keeping `helm history` continuous across the pinned
27
+ * migration. A local chart path becomes its sanitized basename — helm
28
+ * requires a bare chart name — and a render recorded without a version
29
+ * falls back to `0.0.0`.
30
+ */
31
+ import type { RoutedRender } from "./render-wrapper.js";
32
+ /** What `materializeWrapperChart` wrote, with the counts the install result reports. */
33
+ export interface MaterializedWrapperChart {
34
+ /** Chart root directory — the `helm upgrade --install` argument. */
35
+ dir: string;
36
+ /** Wrapper chart name (sanitized from the source chart reference). */
37
+ chartName: string;
38
+ /** Wrapper chart version (`0.0.0` when the render recorded none). */
39
+ chartVersion: string;
40
+ /** Files written under `crds/`, chart-relative. */
41
+ crdFiles: string[];
42
+ /** Verbatim document files written under `manifests/`, chart-relative, in emit order. */
43
+ manifestFiles: string[];
44
+ /** Number of hook documents among `manifestFiles`. */
45
+ hookCount: number;
46
+ }
47
+ /**
48
+ * A valid helm chart name from a chart reference that may be a local path
49
+ * (`./charts/web`) or a repo-qualified name. Takes the last path segment,
50
+ * lowercases it, and collapses anything outside `[a-z0-9-]` to `-` — helm
51
+ * chart names are DNS-label-shaped. Falls back to `pinned-chart` when
52
+ * nothing survives.
53
+ */
54
+ export declare function wrapperChartName(chart: string): string;
55
+ /**
56
+ * Write `routed` as an installable wrapper chart under `dir` (which must
57
+ * exist and should be empty). Document text is written byte-for-byte; the
58
+ * only authored files are `Chart.yaml` and the `.Files.Get` shims.
59
+ */
60
+ export declare function materializeWrapperChart(routed: RoutedRender, dir: string): MaterializedWrapperChart;
61
+ //# sourceMappingURL=wrapper-chart.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wrapper-chart.d.ts","sourceRoot":"","sources":["../src/wrapper-chart.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAKH,OAAO,KAAK,EAAkB,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAErE,wFAAwF;AACxF,MAAM,WAAW,wBAAwB;IACvC,oEAAoE;IACpE,GAAG,EAAE,MAAM,CAAC;IACZ,sEAAsE;IACtE,SAAS,EAAE,MAAM,CAAC;IAClB,qEAAqE;IACrE,YAAY,EAAE,MAAM,CAAC;IACrB,mDAAmD;IACnD,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,yFAAyF;IACzF,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,sDAAsD;IACtD,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAOtD;AAYD;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,EAAE,MAAM,GAAG,wBAAwB,CAwCnG"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intentius/chant-lexicon-helm",
3
- "version": "0.45.0",
3
+ "version": "0.49.0",
4
4
  "description": "Helm chart lexicon for chant — declarative IaC in TypeScript",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://intentius.io/chant",
@@ -49,6 +49,11 @@
49
49
  "types": "./dist/components/index.d.ts",
50
50
  "default": "./src/components/index.ts"
51
51
  },
52
+ "./pinnability": {
53
+ "development": "./src/pinnability/index.ts",
54
+ "types": "./dist/pinnability/index.d.ts",
55
+ "default": "./src/pinnability/index.ts"
56
+ },
52
57
  "./lint/post-synth": {
53
58
  "development": "./src/lint/post-synth/index.ts",
54
59
  "types": "./dist/lint/post-synth/index.d.ts",
@@ -73,7 +78,7 @@
73
78
  },
74
79
  "dependencies": {
75
80
  "@intentius/chant-lexicon-k8s": "*",
76
- "js-yaml": "^4.1.0"
81
+ "js-yaml": "^4.3.1"
77
82
  },
78
83
  "devDependencies": {
79
84
  "@intentius/chant": "*",
@@ -81,7 +86,8 @@
81
86
  "@types/js-yaml": "^4.0.9"
82
87
  },
83
88
  "peerDependencies": {
84
- "@intentius/chant": "^0.45.0",
85
- "typescript": "^5.9.3"
89
+ "@intentius/chant": "^0.49.0",
90
+ "typescript": "^5.9.3",
91
+ "zod": "^4.3.6"
86
92
  }
87
93
  }