@intentius/chant-lexicon-helm 0.124.30 → 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 (111) hide show
  1. package/dist/archive-render.js +76 -0
  2. package/dist/codegen/docs-cli.js +3 -0
  3. package/dist/codegen/docs.js +113 -0
  4. package/dist/codegen/generate-cli.js +19 -0
  5. package/dist/codegen/generate.js +210 -0
  6. package/dist/codegen/naming.js +29 -0
  7. package/dist/codegen/package.js +44 -0
  8. package/dist/codegen/values-type.js +87 -0
  9. package/dist/commands.js +413 -0
  10. package/dist/components/builders.js +9 -0
  11. package/dist/components/capability-plugin.js +17 -0
  12. package/dist/components/helm-upgrade.js +109 -0
  13. package/dist/components/index.js +11 -0
  14. package/dist/composites/catalog.js +1119 -0
  15. package/dist/composites/helm-batch-job.js +148 -0
  16. package/dist/composites/helm-crd-lifecycle.js +145 -0
  17. package/dist/composites/helm-cron-job.js +130 -0
  18. package/dist/composites/helm-daemon-set.js +125 -0
  19. package/dist/composites/helm-external-secret.js +63 -0
  20. package/dist/composites/helm-library.js +24 -0
  21. package/dist/composites/helm-microservice.js +260 -0
  22. package/dist/composites/helm-monitored-service.js +188 -0
  23. package/dist/composites/helm-namespace-env.js +116 -0
  24. package/dist/composites/helm-secure-ingress.js +86 -0
  25. package/dist/composites/helm-stateful-service.js +160 -0
  26. package/dist/composites/helm-web-app.js +201 -0
  27. package/dist/composites/helm-worker.js +164 -0
  28. package/dist/composites/index.js +13 -0
  29. package/dist/config.js +161 -0
  30. package/dist/coverage.js +37 -0
  31. package/dist/deep-observe-hooks.js +19 -0
  32. package/dist/deep-observe.js +55 -0
  33. package/dist/describe-resources.js +119 -0
  34. package/dist/describe-stack-status.js +66 -0
  35. package/dist/detect.js +11 -0
  36. package/dist/generated/index.js +11 -0
  37. package/dist/helpers.js +84 -0
  38. package/dist/import/generator.js +292 -0
  39. package/dist/import/parser.js +141 -0
  40. package/dist/import/template-stripper.js +86 -0
  41. package/dist/index.js +55 -0
  42. package/dist/integrity.json +2 -2
  43. package/dist/intrinsics.js +482 -0
  44. package/dist/lint/audit-catalog.js +33 -0
  45. package/dist/lint/audit-lineage.js +53 -0
  46. package/dist/lint/post-synth/helm-helpers.js +60 -0
  47. package/dist/lint/post-synth/index.js +48 -0
  48. package/dist/lint/post-synth/whm005-no-empty-wrapper.js +47 -0
  49. package/dist/lint/post-synth/whm101.js +38 -0
  50. package/dist/lint/post-synth/whm102.js +26 -0
  51. package/dist/lint/post-synth/whm103.js +51 -0
  52. package/dist/lint/post-synth/whm104.js +29 -0
  53. package/dist/lint/post-synth/whm105.js +23 -0
  54. package/dist/lint/post-synth/whm201.js +29 -0
  55. package/dist/lint/post-synth/whm202.js +42 -0
  56. package/dist/lint/post-synth/whm203.js +32 -0
  57. package/dist/lint/post-synth/whm204.js +51 -0
  58. package/dist/lint/post-synth/whm301.js +31 -0
  59. package/dist/lint/post-synth/whm302.js +33 -0
  60. package/dist/lint/post-synth/whm401.js +52 -0
  61. package/dist/lint/post-synth/whm402.js +38 -0
  62. package/dist/lint/post-synth/whm403.js +38 -0
  63. package/dist/lint/post-synth/whm404.js +30 -0
  64. package/dist/lint/post-synth/whm405.js +47 -0
  65. package/dist/lint/post-synth/whm406.js +27 -0
  66. package/dist/lint/post-synth/whm407.js +72 -0
  67. package/dist/lint/post-synth/whm501.js +89 -0
  68. package/dist/lint/post-synth/whm502.js +85 -0
  69. package/dist/lint/post-synth/whm503.js +82 -0
  70. package/dist/lint/post-synth/whm504.js +47 -0
  71. package/dist/lint/rules/chart-metadata.js +53 -0
  72. package/dist/lint/rules/no-hardcoded-image.js +50 -0
  73. package/dist/lint/rules/values-no-helm-tpl.js +78 -0
  74. package/dist/lint/rules/values-no-secrets.js +65 -0
  75. package/dist/list-artifacts.js +134 -0
  76. package/dist/lsp/completions.d.ts.map +1 -1
  77. package/dist/lsp/completions.js +18 -0
  78. package/dist/lsp/hover.d.ts.map +1 -1
  79. package/dist/lsp/hover.js +40 -0
  80. package/dist/manifest.json +1 -1
  81. package/dist/op/activities/cluster-probe.js +116 -0
  82. package/dist/op/activities/helm.js +449 -0
  83. package/dist/op/activities/index.js +10 -0
  84. package/dist/op/builders.js +45 -0
  85. package/dist/package-cli.js +21 -0
  86. package/dist/pinnability/actions.js +207 -0
  87. package/dist/pinnability/classify.js +416 -0
  88. package/dist/pinnability/conditions.js +0 -0
  89. package/dist/pinnability/index.js +16 -0
  90. package/dist/pinnability/localize.js +518 -0
  91. package/dist/pinnability/render-stream.js +53 -0
  92. package/dist/pinnability/values.js +151 -0
  93. package/dist/plugin.js +458 -0
  94. package/dist/project-codegen.js +215 -0
  95. package/dist/release-observe.js +287 -0
  96. package/dist/render-diff.js +279 -0
  97. package/dist/render-digest.js +0 -0
  98. package/dist/render-records.js +29 -0
  99. package/dist/render-store.js +401 -0
  100. package/dist/render-wrapper.js +164 -0
  101. package/dist/render.js +394 -0
  102. package/dist/resources.js +109 -0
  103. package/dist/serializer.js +903 -0
  104. package/dist/validate-cli.js +17 -0
  105. package/dist/validate.js +30 -0
  106. package/dist/values-probe.js +504 -0
  107. package/dist/wrapper-chart.js +95 -0
  108. package/package.json +11 -11
  109. package/src/codegen/package.ts +2 -2
  110. package/src/lsp/completions.ts +2 -1
  111. package/src/lsp/hover.ts +2 -1
@@ -0,0 +1,279 @@
1
+ /**
2
+ * Render diffing (#1249 offline, #1250 live — epic #1228 Phase 6), the two
3
+ * halves of `chant helm diff` (./commands.ts).
4
+ *
5
+ * `diffRenders` (#1249) is the offline half: given two content digests
6
+ * already in the render store (render-store.ts), report what changed
7
+ * between them — no cluster, no credentials, no live read. This is the
8
+ * pre-flight question a pin, a chart bump, or a values change all raise:
9
+ * "what will this touch?"
10
+ *
11
+ * Pairing is document-level, keyed on `(kind, namespace, name)` — the same
12
+ * identity `RenderManifest.documents` already indexes (render-store.ts).
13
+ * That index exists precisely so two renders can be matched without
14
+ * reparsing a whole stream: a document present on both sides with the same
15
+ * per-document digest is unchanged and costs nothing further; one whose
16
+ * digest differs is parsed on both sides and diffed field by field.
17
+ *
18
+ * Field-level diffing reuses core's `flattenDeepProperties` /
19
+ * `deepValueEqual` (packages/core/src/deep-observation.ts) — the same
20
+ * path-flattening core's own live-drift diff (lifecycle/deep-diff.ts) is
21
+ * built on. This module does not reuse `diffDeep` itself for the offline
22
+ * path: that function's shape is declared/live/baseline/fieldOwners,
23
+ * tailored to a source tree vs a cluster read. A stored render has neither
24
+ * a baseline nor a field owner, and both sides here are equally "declared"
25
+ * — reusing the flatten/compare primitives without the three-way apparatus
26
+ * is the right cut for render-to-render.
27
+ *
28
+ * Duplicate documents (an aliased dependency emitting the same CRD twice,
29
+ * render-digest.ts's canonicalization note) are matched positionally within
30
+ * their `(kind, namespace, name)` group: first occurrence to first, second
31
+ * to second. A group with more entries on one side reports the surplus as
32
+ * added or removed rather than guessing which duplicate corresponds to
33
+ * which.
34
+ *
35
+ * A document neither side's manifest could index (unparseable, or missing
36
+ * `kind`/`metadata.name` — render-store.ts's `indexRenderDocuments`) cannot
37
+ * be paired by identity. It is never silently dropped: `unindexed` counts
38
+ * it on whichever side(s) it appears, so a diff over two renders that both
39
+ * carry unindexable documents does not quietly under-report.
40
+ *
41
+ * `diffRenderLive` (#1250) is the online complement: given a single
42
+ * `contentDigest` resolved from the render store, read the live property
43
+ * tree for every document the render recorded — by identity
44
+ * (kind/namespace/name), not by helm release — and diff each one against
45
+ * the render's own stored bytes with `lifecycle/deep-diff.ts`. This *does*
46
+ * reuse `diffDeep`'s three-way apparatus (via `diffDeepObservation`): the
47
+ * declared side is the render's stored documents, the live side is a
48
+ * cluster read, which is exactly that function's shape.
49
+ *
50
+ * The declared side for the live path is the render's stored documents,
51
+ * never a fresh `helm template` run and never a chant TS build. A digest
52
+ * not in the store — an unpinned render was never persisted
53
+ * (`render-store.ts`'s `persistHelmRender` refuses that) — has nothing to
54
+ * diff against and comes back `found: false`, never a false "no drift" over
55
+ * documents nobody has (epic AC: unpinned releases handled without false
56
+ * drift).
57
+ *
58
+ * The live side reuses the exact seam `observeResourcesDeepHelm` (#1247)
59
+ * uses — the k8s lexicon's typed client and normalization hooks — so a
60
+ * document this reads and one a release's `describeResources` reads compare
61
+ * under the same rules (secret masking, managed-fields ownership, pruning).
62
+ * Hook resources need no special handling here the way the release-scoped
63
+ * read needs a second `helm get hooks` channel: `helm template` (what
64
+ * produced the stored render) already includes hook manifests as ordinary
65
+ * documents, so they are simply rows in `manifest.documents` like any other.
66
+ */
67
+ import yaml from "js-yaml";
68
+ import { deepValueEqual, flattenDeepProperties, normalizeDeepObservation } from "@intentius/chant/deep-observation";
69
+ import { diffDeepObservation } from "@intentius/chant/lifecycle/deep-observe";
70
+ import { observeResourcesDeepK8s } from "@intentius/chant-lexicon-k8s/deep-observe";
71
+ import { gvkToTypeName } from "@intentius/chant-lexicon-k8s/spec/parse";
72
+ import { helmDeepNormalizationHooks } from "./deep-observe-hooks.js";
73
+ import { CLUSTER_SCOPED_KINDS } from "./release-observe.js";
74
+ import { loadRenderManifest, readRenderDocument } from "./render-store.js";
75
+ function docKey(doc) {
76
+ return `${doc.kind}\0${doc.namespace ?? ""}\0${doc.name}`;
77
+ }
78
+ function docRef(doc) {
79
+ return { kind: doc.kind, namespace: doc.namespace, name: doc.name, source: doc.source };
80
+ }
81
+ /** Group a manifest's document entries by identity, preserving occurrence order for duplicate matching. */
82
+ function groupByIdentity(documents) {
83
+ const groups = new Map();
84
+ for (const doc of documents) {
85
+ const key = docKey(doc);
86
+ const group = groups.get(key);
87
+ if (group)
88
+ group.push(doc);
89
+ else
90
+ groups.set(key, [doc]);
91
+ }
92
+ return groups;
93
+ }
94
+ function compareRefs(a, b) {
95
+ return (a.kind.localeCompare(b.kind) ||
96
+ (a.namespace ?? "").localeCompare(b.namespace ?? "") ||
97
+ a.name.localeCompare(b.name));
98
+ }
99
+ /**
100
+ * Field-level diff between a document's two parsed trees. Both sides are
101
+ * flattened with `flattenDeepProperties` (no ordering hooks — a stored
102
+ * render carries no lexicon to supply one, so arrays are compared
103
+ * positionally, exactly as the canonicalized bytes ordered them) and walked
104
+ * path by path.
105
+ */
106
+ function diffDocumentFields(beforeText, afterText, entityType) {
107
+ const beforeTree = (yaml.load(beforeText) ?? {});
108
+ const afterTree = (yaml.load(afterText) ?? {});
109
+ const beforeFlat = flattenDeepProperties(beforeTree, { entityType, side: "declared" });
110
+ const afterFlat = flattenDeepProperties(afterTree, { entityType, side: "live" });
111
+ const paths = [...new Set([...beforeFlat.keys(), ...afterFlat.keys()])].sort();
112
+ const changes = [];
113
+ for (const path of paths) {
114
+ const hasBefore = beforeFlat.has(path);
115
+ const hasAfter = afterFlat.has(path);
116
+ const beforeValue = beforeFlat.get(path);
117
+ const afterValue = afterFlat.get(path);
118
+ if (hasBefore && hasAfter && deepValueEqual(beforeValue, afterValue))
119
+ continue;
120
+ const kind = !hasBefore ? "added" : !hasAfter ? "removed" : "changed";
121
+ changes.push({
122
+ path,
123
+ kind,
124
+ ...(hasBefore ? { before: beforeValue } : {}),
125
+ ...(hasAfter ? { after: afterValue } : {}),
126
+ });
127
+ }
128
+ return changes;
129
+ }
130
+ /**
131
+ * Diff two renders already in the store, by content digest. Offline: every
132
+ * byte compared comes from the archive (render-store.ts), never a cluster.
133
+ *
134
+ * Throws when either digest has no manifest — a diff over a render nobody
135
+ * persisted is a usage error, not an empty report.
136
+ */
137
+ export function diffRenders(from, to, options) {
138
+ const opts = { root: options?.root };
139
+ const fromManifest = requireManifest(from, opts.root);
140
+ const toManifest = requireManifest(to, opts.root);
141
+ const fromGroups = groupByIdentity(fromManifest.documents);
142
+ const toGroups = groupByIdentity(toManifest.documents);
143
+ const added = [];
144
+ const removed = [];
145
+ const changed = [];
146
+ const unchanged = [];
147
+ const keys = new Set([...fromGroups.keys(), ...toGroups.keys()]);
148
+ for (const key of keys) {
149
+ const fromEntries = fromGroups.get(key) ?? [];
150
+ const toEntries = toGroups.get(key) ?? [];
151
+ const pairCount = Math.min(fromEntries.length, toEntries.length);
152
+ for (let i = 0; i < pairCount; i++) {
153
+ const fromDoc = fromEntries[i];
154
+ const toDoc = toEntries[i];
155
+ if (fromDoc.digest === toDoc.digest) {
156
+ unchanged.push(docRef(fromDoc));
157
+ continue;
158
+ }
159
+ const fromRead = readRenderDocument(from, fromDoc, opts);
160
+ const toRead = readRenderDocument(to, toDoc, opts);
161
+ /* c8 ignore next 5 -- indexed entries always resolve; defensive only */
162
+ if (!fromRead || !toRead) {
163
+ throw new Error(`render diff: indexed document ${fromDoc.kind}/${fromDoc.name} failed to resolve from its own store entry`);
164
+ }
165
+ changed.push({
166
+ ...docRef(fromDoc),
167
+ beforeDigest: fromDoc.digest,
168
+ afterDigest: toDoc.digest,
169
+ changes: diffDocumentFields(fromRead.text, toRead.text, fromDoc.kind),
170
+ });
171
+ }
172
+ for (let i = pairCount; i < fromEntries.length; i++) {
173
+ removed.push({ ...docRef(fromEntries[i]), digest: fromEntries[i].digest });
174
+ }
175
+ for (let i = pairCount; i < toEntries.length; i++) {
176
+ added.push({ ...docRef(toEntries[i]), digest: toEntries[i].digest });
177
+ }
178
+ }
179
+ return {
180
+ from: { contentDigest: fromManifest.contentDigest, chart: fromManifest.chart, releaseName: fromManifest.releaseName },
181
+ to: { contentDigest: toManifest.contentDigest, chart: toManifest.chart, releaseName: toManifest.releaseName },
182
+ added: added.sort(compareRefs),
183
+ removed: removed.sort(compareRefs),
184
+ changed: changed.sort(compareRefs),
185
+ unchanged: unchanged.sort(compareRefs),
186
+ unindexed: {
187
+ from: fromManifest.docCount - fromManifest.documents.length,
188
+ to: toManifest.docCount - toManifest.documents.length,
189
+ },
190
+ };
191
+ }
192
+ function requireManifest(contentDigest, root) {
193
+ const manifest = loadRenderManifest(contentDigest, { root });
194
+ if (!manifest) {
195
+ throw new Error(`no stored render for ${contentDigest} — resolve it first (chant helm renders), or check ` +
196
+ `CHANT_HELM_RENDER_ROOT if the store root was overridden.`);
197
+ }
198
+ return manifest;
199
+ }
200
+ /** True when the diff found no added, removed, or changed documents. */
201
+ export function renderDiffIsEmpty(result) {
202
+ return result.added.length === 0 && result.removed.length === 0 && result.changed.length === 0;
203
+ }
204
+ function splitApiVersion(apiVersion) {
205
+ const slash = apiVersion.indexOf("/");
206
+ return slash > 0
207
+ ? { group: apiVersion.slice(0, slash), version: apiVersion.slice(slash + 1) }
208
+ : { group: "", version: apiVersion };
209
+ }
210
+ /** Row key convention shared with the release-scoped reads (#1246/#1247): `<Kind>/<namespace>/<name>`, `cluster:<Kind>/<name>`. */
211
+ export function documentKey(kind, namespace, name) {
212
+ return namespace ? `${kind}/${namespace}/${name}` : `cluster:${kind}/${name}`;
213
+ }
214
+ /**
215
+ * A namespace-silent document defaults to the render's target namespace —
216
+ * the same rule a deployed release applies to its manifest (`helm apply
217
+ * -n <ns>` targets whatever a namespace-silent object's `.Release.Namespace`
218
+ * would have filled in) — unless the kind is cluster-scoped, in which case a
219
+ * missing namespace is never defaulted.
220
+ */
221
+ export function resolveDocumentNamespace(kind, declared, renderNamespace) {
222
+ if (declared)
223
+ return declared;
224
+ if (CLUSTER_SCOPED_KINDS.has(kind))
225
+ return undefined;
226
+ return renderNamespace ?? undefined;
227
+ }
228
+ /** The document minus the envelope the row's `type` already carries — mirrors `deep-observe.ts`'s `declaredTreeOf`. */
229
+ function declaredTreeOf(doc, namespace) {
230
+ const { apiVersion: _apiVersion, kind: _kind, ...rest } = doc;
231
+ const metadata = (rest.metadata ?? {});
232
+ return {
233
+ ...rest,
234
+ ...(namespace ? { metadata: { ...metadata, ...(metadata.namespace === undefined ? { namespace } : {}) } } : {}),
235
+ };
236
+ }
237
+ /**
238
+ * Diff one stored render's documents against the live cluster.
239
+ *
240
+ * `found: false` when `contentDigest` is not in the store — never fabricates
241
+ * a document, and never reports "no drift" over a render this has no bytes
242
+ * for. On a found render, every document indexed in its manifest becomes a
243
+ * synthetic declared entity (keyed like a release's rows), read live through
244
+ * the k8s lexicon's deep observer, and diffed property-by-property.
245
+ */
246
+ export async function diffRenderLive(options, deps = {}) {
247
+ const manifest = loadRenderManifest(options.contentDigest, { root: options.root });
248
+ if (!manifest)
249
+ return { found: false, contentDigest: options.contentDigest };
250
+ const entities = new Map();
251
+ for (const docEntry of manifest.documents) {
252
+ const resolved = readRenderDocument(options.contentDigest, { kind: docEntry.kind, name: docEntry.name, namespace: docEntry.namespace }, { root: options.root });
253
+ if (!resolved)
254
+ continue; // integrity gap between the index and the content — skip, never fabricate
255
+ const parsed = yaml.load(resolved.text);
256
+ if (parsed === null || typeof parsed !== "object")
257
+ continue;
258
+ const namespace = resolveDocumentNamespace(docEntry.kind, docEntry.namespace, manifest.namespace);
259
+ const key = documentKey(docEntry.kind, namespace, docEntry.name);
260
+ const { group, version } = splitApiVersion(docEntry.apiVersion);
261
+ entities.set(key, {
262
+ entityType: gvkToTypeName({ group, version, kind: docEntry.kind }),
263
+ props: declaredTreeOf(parsed, namespace),
264
+ });
265
+ }
266
+ const k8sOptions = {
267
+ environment: options.environment,
268
+ buildOutput: "",
269
+ entityNames: [...entities.keys()],
270
+ entities,
271
+ ...(options.owned !== undefined ? { owned: options.owned } : {}),
272
+ };
273
+ const raw = deps.connect
274
+ ? await observeResourcesDeepK8s(k8sOptions, deps.connect)
275
+ : await observeResourcesDeepK8s(k8sOptions);
276
+ const live = normalizeDeepObservation(raw);
277
+ const diff = diffDeepObservation(entities, live, helmDeepNormalizationHooks);
278
+ return { found: true, manifest, diff };
279
+ }
Binary file
@@ -0,0 +1,29 @@
1
+ /**
2
+ * The in-process ledger of `HelmRender` invocations — split out of
3
+ * `./render.ts` so a post-synth check can read it without importing the
4
+ * render machinery (#1979 follow-up, found by the hosted audit's bump).
5
+ *
6
+ * WHM503 (`./lint/post-synth/whm503.ts`) scans these records, and post-synth
7
+ * barrels are edge-imported by the hosted audit service: everything they
8
+ * reach through static imports must be safe to initialize on workerd.
9
+ * `render.ts` is not — it imports the full `@intentius/chant` barrel, whose
10
+ * static graph reaches the TypeScript compiler (fold-import), and
11
+ * TypeScript's own module init touches `__filename`, which crashes the
12
+ * worker at startup. This module carries only the record shape and the
13
+ * ledger, imports nothing but a type, and keeps the barrel edge-clean;
14
+ * `render.ts` re-exports it all, so the lexicon's public surface is
15
+ * unchanged.
16
+ */
17
+ const renderRecords = [];
18
+ /** Append one invocation's record (called by `HelmRender`, ./render.ts). */
19
+ export function recordHelmRender(record) {
20
+ renderRecords.push(record);
21
+ }
22
+ /** Every render recorded in this process, in invocation order. */
23
+ export function getHelmRenderRecords() {
24
+ return renderRecords;
25
+ }
26
+ /** Reset the record list (test isolation). */
27
+ export function clearHelmRenderRecords() {
28
+ renderRecords.length = 0;
29
+ }