@intentius/chant-lexicon-k8s 0.42.1 → 0.44.2

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 (127) hide show
  1. package/dist/api/classify.d.ts.map +1 -1
  2. package/dist/api/connect.d.ts.map +1 -1
  3. package/dist/codegen/docs.d.ts.map +1 -1
  4. package/dist/components/argo-app.d.ts +104 -0
  5. package/dist/components/argo-app.d.ts.map +1 -0
  6. package/dist/components/builders.d.ts +8 -0
  7. package/dist/components/builders.d.ts.map +1 -1
  8. package/dist/components/capability-plugin.d.ts +1 -0
  9. package/dist/components/capability-plugin.d.ts.map +1 -1
  10. package/dist/components/flux-reconcile.d.ts +84 -0
  11. package/dist/components/flux-reconcile.d.ts.map +1 -0
  12. package/dist/components/index.d.ts +2 -0
  13. package/dist/components/index.d.ts.map +1 -1
  14. package/dist/components/kustomize-apply.d.ts +29 -7
  15. package/dist/components/kustomize-apply.d.ts.map +1 -1
  16. package/dist/composites/flux-app.d.ts +132 -0
  17. package/dist/composites/flux-app.d.ts.map +1 -0
  18. package/dist/composites/index.d.ts +2 -0
  19. package/dist/composites/index.d.ts.map +1 -1
  20. package/dist/config-schema.d.ts +3 -0
  21. package/dist/config-schema.d.ts.map +1 -1
  22. package/dist/config.d.ts +32 -5
  23. package/dist/config.d.ts.map +1 -1
  24. package/dist/crd/crd-sources.d.ts.map +1 -1
  25. package/dist/crd/parser.d.ts.map +1 -1
  26. package/dist/describe-resources.d.ts +10 -8
  27. package/dist/describe-resources.d.ts.map +1 -1
  28. package/dist/generated/index.d.ts +15 -4
  29. package/dist/generated/index.d.ts.map +1 -1
  30. package/dist/index.d.ts +2 -2
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/integrity.json +10 -6
  33. package/dist/kube/connect.d.ts +5 -4
  34. package/dist/kube/connect.d.ts.map +1 -1
  35. package/dist/kube/get.d.ts +4 -4
  36. package/dist/kustomize/render.d.ts +15 -0
  37. package/dist/kustomize/render.d.ts.map +1 -0
  38. package/dist/kustomize/rendered-entity.d.ts +39 -0
  39. package/dist/kustomize/rendered-entity.d.ts.map +1 -0
  40. package/dist/kustomize/root.d.ts +19 -0
  41. package/dist/kustomize/root.d.ts.map +1 -0
  42. package/dist/lint/audit-catalog.d.ts.map +1 -1
  43. package/dist/lint/post-synth/argo-helpers.d.ts +2 -0
  44. package/dist/lint/post-synth/argo-helpers.d.ts.map +1 -1
  45. package/dist/lint/post-synth/flux002.d.ts +14 -0
  46. package/dist/lint/post-synth/flux002.d.ts.map +1 -0
  47. package/dist/lint/post-synth/flux003.d.ts +17 -0
  48. package/dist/lint/post-synth/flux003.d.ts.map +1 -0
  49. package/dist/lint/post-synth/index.d.ts.map +1 -1
  50. package/dist/lint/rules/flux-source-ref-pin.d.ts +16 -0
  51. package/dist/lint/rules/flux-source-ref-pin.d.ts.map +1 -0
  52. package/dist/manifest.json +1 -1
  53. package/dist/meta.json +92 -16
  54. package/dist/okf/index.md +7 -1
  55. package/dist/okf/rules/FLUX001.md +17 -0
  56. package/dist/okf/rules/FLUX002.md +19 -0
  57. package/dist/okf/rules/FLUX003.md +15 -0
  58. package/dist/okf/types/Addon.md +14 -0
  59. package/dist/okf/types/Bucket.md +4 -0
  60. package/dist/okf/types/{HelmChart.md → FluxHelmChart.md} +2 -2
  61. package/dist/okf/types/GitRepository.md +5 -0
  62. package/dist/okf/types/HelmChartConfig.md +14 -0
  63. package/dist/okf/types/K3sHelmChart.md +14 -0
  64. package/dist/okf/types/Kustomization.md +6 -0
  65. package/dist/okf/types/Namespace.md +1 -0
  66. package/dist/okf/types/Node.md +1 -0
  67. package/dist/okf/types/OCIRepository.md +4 -0
  68. package/dist/plugin.d.ts.map +1 -1
  69. package/dist/rules/argo-helpers.ts +2 -0
  70. package/dist/rules/flux-source-ref-pin.ts +52 -0
  71. package/dist/rules/flux002.ts +77 -0
  72. package/dist/rules/flux003.ts +75 -0
  73. package/dist/serializer.d.ts.map +1 -1
  74. package/dist/skills/chant-k8s-flux.md +162 -0
  75. package/dist/types/index.d.ts +41 -3
  76. package/dist/validate.d.ts.map +1 -1
  77. package/package.json +3 -3
  78. package/src/api/classify.test.ts +37 -0
  79. package/src/api/classify.ts +11 -1
  80. package/src/api/connect.ts +10 -0
  81. package/src/codegen/docs.ts +11 -0
  82. package/src/components/argo-app.test.ts +137 -0
  83. package/src/components/argo-app.ts +163 -0
  84. package/src/components/builders.ts +8 -0
  85. package/src/components/capability-plugin.ts +11 -1
  86. package/src/components/flux-reconcile.test.ts +145 -0
  87. package/src/components/flux-reconcile.ts +159 -0
  88. package/src/components/index.ts +13 -0
  89. package/src/components/kustomize-apply.ts +12 -41
  90. package/src/composites/composites.test.ts +122 -0
  91. package/src/composites/flux-app.ts +274 -0
  92. package/src/composites/index.ts +9 -0
  93. package/src/config-augmentation.test.ts +72 -0
  94. package/src/config-schema.ts +5 -0
  95. package/src/config.ts +33 -5
  96. package/src/crd/crd-sources.ts +37 -0
  97. package/src/crd/parser.ts +6 -0
  98. package/src/describe-resources.test.ts +290 -6
  99. package/src/describe-resources.ts +235 -127
  100. package/src/generated/index.d.ts +41 -3
  101. package/src/generated/index.ts +15 -4
  102. package/src/generated/lexicon-k8s.json +92 -16
  103. package/src/generated/operations.json +48 -0
  104. package/src/index.ts +4 -0
  105. package/src/kube/connect.ts +5 -4
  106. package/src/kube/get.ts +4 -4
  107. package/src/kustomize/render.ts +84 -0
  108. package/src/kustomize/rendered-entity.ts +72 -0
  109. package/src/kustomize/root.test.ts +268 -0
  110. package/src/kustomize/root.ts +109 -0
  111. package/src/lint/audit-catalog.ts +2 -0
  112. package/src/lint/post-synth/argo-helpers.ts +2 -0
  113. package/src/lint/post-synth/flux002.ts +77 -0
  114. package/src/lint/post-synth/flux003.ts +75 -0
  115. package/src/lint/post-synth/index.ts +4 -0
  116. package/src/lint/post-synth/post-synth.test.ts +132 -0
  117. package/src/lint/rules/flux-source-ref-pin.ts +52 -0
  118. package/src/lint/rules/rules.test.ts +51 -0
  119. package/src/plugin.test.ts +2 -0
  120. package/src/plugin.ts +47 -0
  121. package/src/serializer.ts +34 -4
  122. package/src/skills/chant-k8s-flux.md +162 -0
  123. package/src/testdata/kustomize-root/base/deployment.yaml +17 -0
  124. package/src/testdata/kustomize-root/base/kustomization.yaml +3 -0
  125. package/src/testdata/kustomize-root/base/service.yaml +9 -0
  126. package/src/testdata/kustomize-root/overlays/prod/kustomization.yaml +6 -0
  127. package/src/validate.ts +4 -0
@@ -42,17 +42,19 @@
42
42
  * exactly the shape `lifecycle diff --live` has always classified `orphan`.
43
43
  * On Kubernetes that is wrong: it is expected runtime, not drift, and will be
44
44
  * recreated the moment it is deleted. After the declared-entity reads above
45
- * resolve, this scans Pods in the namespaces they actually live in and walks
46
- * each one's `ownerReferences` chain (`./api/owner-chain.ts`) against the
47
- * entities this very call resolved. A chain reaching one of them is reported
48
- * with `ownerChain: { root: "declared", ... }`; core's diff/change-set engine
49
- * reads that as `runtime` instead of `orphan`. Deliberately scoped to Pods —
50
- * the concrete case the issue and its acceptance criteria name — and to the
51
- * namespaces this observation already touched, not a cluster-wide sweep;
52
- * widening to other controller-spawned kinds is the same walk repeated.
45
+ * resolve, this sweeps candidate kinds in the namespaces they actually live in
46
+ * and walks each object's `ownerReferences` chain (`./api/owner-chain.ts`)
47
+ * against the entities this very call resolved. A chain reaching one of them
48
+ * is reported with `ownerChain: { root: "declared", ... }`; core's
49
+ * diff/change-set engine reads that as `runtime` instead of `orphan`.
50
+ *
51
+ * Which kinds are candidates is the cluster's own API discovery's answer
52
+ * (#1517): every listable kind it serves, one version per group. The sweep's
53
+ * bound is the namespaces the declared estate touches, never a kind table —
54
+ * see `addRuntimeChildren`.
53
55
  */
54
56
 
55
- import type { ObservationResult, ResourceMetadata, UnobservedEntity } from "@intentius/chant/lexicon";
57
+ import type { ObservationResult, ResourceMetadata, UnobservedEntity, UnobservedReason } from "@intentius/chant/lexicon";
56
58
  import { observation, unobservedAll } from "@intentius/chant/observation";
57
59
  import { hasOwnershipMarker, classifyOwnership, LABEL_OWNERSHIP_KEYS } from "@intentius/chant/ownership";
58
60
  import type { K8sClient, K8sObject } from "@intentius/chant-k8s-client";
@@ -253,6 +255,14 @@ export async function describeResources(
253
255
  ): Promise<ObservationResult> {
254
256
  const resources: Record<string, ResourceMetadata> = {};
255
257
  const unobserved: Record<string, UnobservedEntity> = {};
258
+ // The resolved query address per declared entity (#1620) — the exact request
259
+ // path each read was issued against, with the namespace the client actually
260
+ // defaulted to when the declaration carries none. This is what lets an
261
+ // absence explain itself: a Flux-deployed object declaring no
262
+ // metadata.namespace reads from `default`, correctly comes back 404, and
263
+ // without the address the report says "not there" when the truth is "not
264
+ // looked for where it lives".
265
+ const queried: Record<string, string> = {};
256
266
 
257
267
  const declared: Declared[] = [...options.entities].map(([entityName, entity]) => ({
258
268
  entityName,
@@ -321,13 +331,27 @@ export async function describeResources(
321
331
  return;
322
332
  }
323
333
 
334
+ const ref = {
335
+ apiVersion: operation.apiVersion,
336
+ kind: operation.kind,
337
+ name,
338
+ ...(metadata?.namespace ? { namespace: metadata.namespace } : {}),
339
+ };
340
+
341
+ // Resolve the address BEFORE the read, so every verdict below — present,
342
+ // absent, filtered, read-failed — carries the same record of what was
343
+ // asked. Resolution rides the client's cached discovery; a kind discovery
344
+ // does not serve has no path, and a resolution failure classifies through
345
+ // the read below, never here.
346
+ try {
347
+ const address = typeof client.pathFor === "function" ? await client.pathFor(ref) : undefined;
348
+ if (address) queried[entityName] = address;
349
+ } catch {
350
+ // The read below hits the same resolution and classifies its failure.
351
+ }
352
+
324
353
  try {
325
- const obj = await client.read({
326
- apiVersion: operation.apiVersion,
327
- kind: operation.kind,
328
- name,
329
- ...(metadata?.namespace ? { namespace: metadata.namespace } : {}),
330
- });
354
+ const obj = await client.read(ref);
331
355
 
332
356
  // owned filter: withhold resources not carrying chant's marker label.
333
357
  // Withheld is not absent (chant #1089) — this object exists, it just
@@ -338,6 +362,7 @@ export async function describeResources(
338
362
  type: entityType,
339
363
  reason: "filtered",
340
364
  detail: "live object carries no chant ownership marker and --owned was requested",
365
+ ...(queried[entityName] ? { queried: queried[entityName] } : {}),
341
366
  };
342
367
  return;
343
368
  }
@@ -359,51 +384,60 @@ export async function describeResources(
359
384
  } catch (err) {
360
385
  const outcome = classifyApiFailure(err);
361
386
  if (outcome.kind === "unobserved") {
362
- unobserved[entityName] = { type: entityType, reason: outcome.reason, detail: outcome.detail };
387
+ unobserved[entityName] = {
388
+ type: entityType,
389
+ reason: outcome.reason,
390
+ detail: outcome.detail,
391
+ ...(queried[entityName] ? { queried: queried[entityName] } : {}),
392
+ };
363
393
  }
364
394
  // `absent` deliberately records nothing: in neither map is how the
365
- // contract spells "asked, and it is not there".
395
+ // contract spells "asked, and it is not there". Its address stays in
396
+ // `queried` (#1620), which is the only record an absence gets.
366
397
  }
367
398
  });
368
399
 
369
- // chant #1517 the API groups this estate's own declarations use. A
370
- // controller's children usually live in its controller's group (a
371
- // MicroVMReplicaSet makes MicroVMs), so the runtime scan sweeps those
372
- // groupVersions' kinds besides Pods. Core (`v1`) is excluded from the group
373
- // sweep — Pods represent it; sweeping every core kind would walk endpoints
374
- // and events for nothing.
375
- const runtimeGroupVersions = new Set<string>();
376
- for (const { entityType } of declared) {
377
- const op = operationFor(entityType);
378
- if (op && op.apiVersion.includes("/")) runtimeGroupVersions.add(op.apiVersion);
379
- }
380
-
381
- await addRuntimeChildren(client, resources, options.owned, runtimeGroupVersions, declared);
400
+ await addRuntimeChildren(client, resources, unobserved, options.owned, declared);
382
401
 
383
- return observation(resources, unobserved);
402
+ return observation(resources, unobserved, queried);
384
403
  }
385
404
 
386
405
  /**
387
- * Scan runtime-child candidates in the namespaces of entities this call just
406
+ * Sweep runtime-child candidates in the namespaces of entities this call just
388
407
  * resolved, and classify each one's owner-reference chain (#1077). Mutates
389
408
  * `resources` in place, adding an entry per object whose chain was worth
390
- * reporting.
409
+ * reporting, and `unobserved` for the parts of the sweep that could not look.
391
410
  *
392
- * Two candidate pools:
393
- * - **Pods** the built-in workload leaf, always scanned.
394
- * - **The estate's own API groups** (#1517) every namespaced, listable
395
- * kind in each `groupVersions` entry, from the cluster's own discovery.
396
- * An operator estate's interesting children are custom resources its
397
- * controllers made (a MicroVMReplicaSet's MicroVMs), which no kind table
398
- * could anticipate the same open-world inversion behold#74 made for
399
- * declared CRDs, applied to the runtime axis. Bounded by what the estate
400
- * declares, never a cluster-wide sweep.
411
+ * The candidate kinds are the cluster's own API discovery's answer (#1517):
412
+ * every listable kind it serves, one version per group (the group's
413
+ * preferredVersion, so nothing is listed twice at a second served version).
414
+ * The first cut scoped this to Pods, the second to the estate's own declared
415
+ * API groups and both missed exactly where the runtime tier says the most:
416
+ * an operator's children are whatever kinds its controllers make (a
417
+ * MicroVMReplicaSet's MicroVMs, a CR's Deployments in a group the estate
418
+ * never declares), which no kind table can anticipate. The same open-world
419
+ * inversion behold#74 made for declared CRDs, applied to the runtime axis.
401
420
  *
402
- * Best-effort and additive: a namespace or kind this scan cannot list (RBAC
403
- * denial, a transient error) is simply skipped rather than failing the
404
- * observation these are not declared entities, so there is no NOT-OBSERVED
405
- * axis for them to report against, the same way an out-of-band AWS child
406
- * resource has none either.
421
+ * What bounds the sweep is not a kind list but reach:
422
+ * - **Namespaced kinds** are listed only in the namespaces the declared
423
+ * estate touches (where its entities resolved, plus the namespaces its
424
+ * GitOps CRs target) owner references cannot cross a namespace, so a
425
+ * child elsewhere could never chain to this estate anyway.
426
+ * - **Cluster-scoped kinds** are listed only when the estate resolved a
427
+ * cluster-scoped entity — a cluster-scoped object's owners are themselves
428
+ * cluster-scoped (Kubernetes' own GC rule), so without one declared there
429
+ * is nothing such a chain could reach.
430
+ * - **It is not an inventory**: outside Pods (whose chain verdicts, foreign
431
+ * included, are #1077's original contract), a swept object is only
432
+ * reported when its chain resolves to a declared entity.
433
+ *
434
+ * Tri-state honesty (#1089, extended to the sweep): a kind discovery names
435
+ * but the sweep cannot list — RBAC denial, a flaking apiserver — becomes a
436
+ * `runtime-sweep:*` entry in `unobserved`, never a silent gap. Silence would
437
+ * read as "no runtime children there", which the failed list never proved.
438
+ * A list 404 (the kind vanished between discovery and the list) stays an
439
+ * absence, per `classifyApiFailure`'s standing rule. Declared-entity
440
+ * resolution is never failed by the sweep.
407
441
  */
408
442
  /** The declared GitOps CRs, indexed the way their controllers' labels can be
409
443
  * resolved (#1549). `byRef` keys are `type\0namespace\0name` (Flux — exact);
@@ -466,11 +500,29 @@ function gitopsOwner(labels: Record<string, string> | undefined, gitops: GitopsI
466
500
  return undefined;
467
501
  }
468
502
 
503
+ /** One candidate kind of the discovery-driven sweep. */
504
+ interface SweptKind {
505
+ apiVersion: string;
506
+ kind: string;
507
+ typeName: string;
508
+ }
509
+
510
+ /**
511
+ * Kinds the sweep skips even though discovery serves them. Not a kind table
512
+ * creeping back in: each entry is a kind that CANNOT attribute to a declared
513
+ * owner — an Event names its subject via `involvedObject`, never
514
+ * `ownerReferences` — and churns by the thousands in any active namespace,
515
+ * so listing it can only cost and never attach.
516
+ */
517
+ function isSweepNoise(info: { kind: string; group: string }): boolean {
518
+ return info.kind === "Event" && (info.group === "" || info.group === "events.k8s.io");
519
+ }
520
+
469
521
  async function addRuntimeChildren(
470
522
  client: K8sClient,
471
523
  resources: Record<string, ResourceMetadata>,
524
+ unobserved: Record<string, UnobservedEntity>,
472
525
  owned: boolean | undefined,
473
- groupVersions: ReadonlySet<string> = new Set(),
474
526
  declared: readonly Declared[] = [],
475
527
  ): Promise<void> {
476
528
  const declaredByUid = new Map<string, string>();
@@ -488,9 +540,11 @@ async function addRuntimeChildren(
488
540
  const gitops = gitopsIndex(declared);
489
541
 
490
542
  const namespaces = new Set<string>();
543
+ let clusterScopedDeclared = false;
491
544
  for (const meta of Object.values(resources)) {
492
545
  const namespace = (meta.attributes as { namespace?: string } | undefined)?.namespace;
493
546
  if (namespace) namespaces.add(namespace);
547
+ else clusterScopedDeclared = true;
494
548
  }
495
549
  // The namespaces a declared GitOps CR TARGETS are declared literals too
496
550
  // (Argo `spec.destination.namespace`, Flux `spec.targetNamespace`) — the
@@ -498,93 +552,147 @@ async function addRuntimeChildren(
498
552
  // reading it off the declaration keeps this bounded by the estate, never a
499
553
  // cluster-wide sweep.
500
554
  for (const ns of gitops.targetNamespaces) namespaces.add(ns);
501
- if (namespaces.size === 0) return;
502
-
503
- // The kinds to scan: Pods, plus each declared groupVersion's namespaced,
504
- // listable kinds from discovery (#1517). A groupVersion the cluster does
505
- // not serve contributes nothing an answer, not a failure.
506
- const kinds: Array<{ apiVersion: string; kind: string; typeName: string }> = [
507
- { apiVersion: "v1", kind: "Pod", typeName: "K8s::Core::Pod" },
508
- ];
509
- // A GitOps estate's managed WORKLOADS live in groups the estate itself never
510
- // declares (a pure Argo/Flux estate declares only the CRs), so without this
511
- // the label channel could only ever fire on Pods (#1549).
512
- if (gitops.present) {
513
- kinds.push(
514
- { apiVersion: "apps/v1", kind: "Deployment", typeName: "K8s::Apps::Deployment" },
515
- { apiVersion: "apps/v1", kind: "StatefulSet", typeName: "K8s::Apps::StatefulSet" },
516
- { apiVersion: "apps/v1", kind: "DaemonSet", typeName: "K8s::Apps::DaemonSet" },
517
- { apiVersion: "v1", kind: "Service", typeName: "K8s::Core::Service" },
518
- { apiVersion: "v1", kind: "ConfigMap", typeName: "K8s::Core::ConfigMap" },
519
- );
555
+ if (namespaces.size === 0 && !clusterScopedDeclared) return;
556
+
557
+ // Every kind the cluster serves, from its own discovery (#1517): one
558
+ // groupVersion per group, every listable kind, split by scope. Failing to
559
+ // enumerate is a hole the observation must admit silently sweeping
560
+ // nothing would read as "no runtime children", which was never established.
561
+ let groupVersions: string[];
562
+ try {
563
+ groupVersions = await client.preferredGroupVersions();
564
+ } catch (err) {
565
+ const outcome = classifyApiFailure(err);
566
+ unobserved["runtime-sweep"] = {
567
+ reason: outcome.kind === "unobserved" ? outcome.reason : "read-failed",
568
+ detail:
569
+ "cluster API discovery failed, so runtime children were not swept" +
570
+ (outcome.kind === "unobserved" ? `: ${outcome.detail}` : ""),
571
+ };
572
+ return;
520
573
  }
521
- for (const gv of [...groupVersions].sort()) {
522
- let infos;
574
+
575
+ const perGroup = await client.concurrently(groupVersions, async (gv) => {
523
576
  try {
524
- infos = await client.resources(gv);
525
- } catch {
526
- continue; // best-effort — see the function doc
577
+ return await client.resources(gv);
578
+ } catch (err) {
579
+ const outcome = classifyApiFailure(err);
580
+ if (outcome.kind === "unobserved") {
581
+ unobserved[`runtime-sweep:${gv}`] = {
582
+ reason: outcome.reason,
583
+ detail: `discovery for ${gv} failed, so its kinds were not swept: ${outcome.detail}`,
584
+ };
585
+ }
586
+ return [];
527
587
  }
528
- const [group, version] = [gv.slice(0, gv.indexOf("/")), gv.slice(gv.indexOf("/") + 1)];
588
+ });
589
+
590
+ const namespacedKinds: SweptKind[] = [];
591
+ const clusterKinds: SweptKind[] = [];
592
+ for (const infos of perGroup) {
529
593
  for (const info of infos) {
530
- if (!info.namespaced || !info.verbs.includes("list")) continue;
531
- kinds.push({ apiVersion: gv, kind: info.kind, typeName: gvkToTypeName({ group, version, kind: info.kind }) });
594
+ if (!info.verbs.includes("list") || isSweepNoise(info)) continue;
595
+ const swept: SweptKind = {
596
+ apiVersion: info.apiVersion,
597
+ kind: info.kind,
598
+ typeName: gvkToTypeName({ group: info.group, version: info.version, kind: info.kind }),
599
+ };
600
+ (info.namespaced ? namespacedKinds : clusterKinds).push(swept);
532
601
  }
533
602
  }
534
603
 
535
- await client.concurrently([...namespaces], async (namespace) => {
536
- for (const { apiVersion, kind, typeName } of kinds) {
537
- let objects: K8sObject[];
538
- try {
539
- objects = await client.list({ apiVersion, kind }, { namespace });
540
- } catch {
541
- continue; // best-effort — see the function doc
542
- }
604
+ // A failed list is recorded once per kind, naming every scope it failed in,
605
+ // rather than once per (kind, namespace) one RBAC rule denying a kind
606
+ // across N namespaces is one hole, not N.
607
+ const listFailures = new Map<
608
+ string,
609
+ { typeName: string; reason: UnobservedReason; scopes: string[]; detail: string }
610
+ >();
543
611
 
544
- await client.concurrently(objects, async (obj) => {
545
- const uid = obj.metadata?.uid;
546
- const name = obj.metadata?.name;
547
- if (!uid || !name || declaredByUid.has(uid)) return; // declared directly, or unaddressable
548
- if (resources[`${namespace}/${name}`]) return; // already reported by an earlier kind
549
-
550
- let ownerChain = await resolveK8sOwnerChain(obj, { declaredByUid, reader: client, namespace });
551
- // The label channel (#1549) — only when the chain itself said nothing:
552
- // a verdict the walk DID reach is never overridden.
553
- if (ownerChain.root !== "declared") {
554
- const entity = gitopsOwner(obj.metadata?.labels, gitops);
555
- if (entity) ownerChain = { root: "declared", entity };
556
- }
557
-
558
- // `--owned`: withhold an object that is neither a runtime child of a
559
- // declared entity nor carrying chant's own marker — the same rule the
560
- // declared-entity read above applies, extended to the undeclared axis
561
- // this scan introduces.
562
- const marker = hasOwnershipMarker(obj.metadata?.labels, LABEL_OWNERSHIP_KEYS);
563
- if (owned && ownerChain.root !== "declared" && !marker) return;
564
-
565
- // An undeclared object with NO owner chain at all is not a runtime
566
- // child — reporting every loose object a group serves would turn the
567
- // scan into an inventory. Pods keep their pre-#1517 reporting (their
568
- // chain verdicts, including foreign, were already part of #1077's
569
- // contract); swept group kinds only report when the chain reaches a
570
- // declared entity.
571
- if (kind !== "Pod" && ownerChain.root !== "declared") return;
572
-
573
- resources[`${namespace}/${name}`] = {
574
- type: typeName,
575
- physicalId: uid,
576
- status: statusFromObject(obj),
577
- lastUpdated: obj.metadata?.creationTimestamp,
578
- ownership: classifyOwnership(obj.metadata?.labels, LABEL_OWNERSHIP_KEYS),
579
- ownerChain,
580
- attributes: pruneUndefined({
581
- namespace,
582
- labels: obj.metadata?.labels,
583
- resourceVersion: obj.metadata?.resourceVersion,
584
- conditions: unhappyConditions(obj),
585
- }),
586
- };
587
- });
612
+ const sweep = async (swept: SweptKind, namespace: string | undefined): Promise<void> => {
613
+ let objects: K8sObject[];
614
+ try {
615
+ objects = await client.list(
616
+ { apiVersion: swept.apiVersion, kind: swept.kind },
617
+ namespace ? { namespace } : {},
618
+ );
619
+ } catch (err) {
620
+ const outcome = classifyApiFailure(err);
621
+ // `absent` (a list 404: the kind vanished between discovery and now)
622
+ // proves no instance exists — nothing to record.
623
+ if (outcome.kind === "unobserved") {
624
+ const key = `runtime-sweep:${swept.apiVersion}/${swept.kind}`;
625
+ const scope = namespace ? `namespace ${namespace}` : "cluster scope";
626
+ const failure = listFailures.get(key);
627
+ if (failure) failure.scopes.push(scope);
628
+ else listFailures.set(key, { typeName: swept.typeName, reason: outcome.reason, scopes: [scope], detail: outcome.detail });
629
+ }
630
+ return;
588
631
  }
632
+
633
+ await client.concurrently(objects, async (obj) => {
634
+ const uid = obj.metadata?.uid;
635
+ const name = obj.metadata?.name;
636
+ if (!uid || !name || declaredByUid.has(uid)) return; // declared directly, or unaddressable
637
+ const key = namespace ? `${namespace}/${name}` : `cluster:${name}`;
638
+ if (resources[key]) return; // already reported by an earlier kind
639
+
640
+ let ownerChain = await resolveK8sOwnerChain(obj, { declaredByUid, reader: client, namespace });
641
+ // The label channel (#1549) — only when the chain itself said nothing:
642
+ // a verdict the walk DID reach is never overridden.
643
+ if (ownerChain.root !== "declared") {
644
+ const entity = gitopsOwner(obj.metadata?.labels, gitops);
645
+ if (entity) ownerChain = { root: "declared", entity };
646
+ }
647
+
648
+ // `--owned`: withhold an object that is neither a runtime child of a
649
+ // declared entity nor carrying chant's own marker — the same rule the
650
+ // declared-entity read above applies, extended to the undeclared axis
651
+ // this scan introduces.
652
+ const marker = hasOwnershipMarker(obj.metadata?.labels, LABEL_OWNERSHIP_KEYS);
653
+ if (owned && ownerChain.root !== "declared" && !marker) return;
654
+
655
+ // An undeclared object with NO owner chain at all is not a runtime
656
+ // child — reporting every loose object the cluster serves would turn
657
+ // the sweep into an inventory. Pods keep their pre-#1517 reporting
658
+ // (their chain verdicts, including foreign, were already part of
659
+ // #1077's contract); every other swept kind only reports when the
660
+ // chain reaches a declared entity.
661
+ if (swept.kind !== "Pod" && ownerChain.root !== "declared") return;
662
+
663
+ resources[key] = {
664
+ type: swept.typeName,
665
+ physicalId: uid,
666
+ status: statusFromObject(obj),
667
+ lastUpdated: obj.metadata?.creationTimestamp,
668
+ ownership: classifyOwnership(obj.metadata?.labels, LABEL_OWNERSHIP_KEYS),
669
+ ownerChain,
670
+ attributes: pruneUndefined({
671
+ namespace,
672
+ labels: obj.metadata?.labels,
673
+ resourceVersion: obj.metadata?.resourceVersion,
674
+ conditions: unhappyConditions(obj),
675
+ }),
676
+ };
677
+ });
678
+ };
679
+
680
+ await client.concurrently([...namespaces], async (namespace) => {
681
+ for (const swept of namespacedKinds) await sweep(swept, namespace);
589
682
  });
683
+
684
+ // Cluster-scoped kinds are swept once (their lists are cluster-wide by
685
+ // nature), and only when a declared cluster-scoped entity exists for a
686
+ // chain to reach — a cluster-scoped object's owners are cluster-scoped.
687
+ if (clusterScopedDeclared) {
688
+ await client.concurrently(clusterKinds, async (swept) => sweep(swept, undefined));
689
+ }
690
+
691
+ for (const [key, failure] of listFailures) {
692
+ unobserved[key] = {
693
+ type: failure.typeName,
694
+ reason: failure.reason,
695
+ detail: `the runtime-children sweep could not list this kind in ${failure.scopes.join(", ")}: ${failure.detail}`,
696
+ };
697
+ }
590
698
  }
@@ -2,6 +2,18 @@
2
2
 
3
3
  // --- Kubernetes Resource classes ---
4
4
 
5
+ export declare class Addon {
6
+ constructor(props: {
7
+ /** Standard object's metadata. */
8
+ metadata?: ObjectMeta;
9
+ /** Spec provides information about the on-disk manifest backing this resource. */
10
+ spec?: Record<string, unknown>;
11
+ });
12
+ readonly name: string;
13
+ readonly namespace: string;
14
+ readonly uid: string;
15
+ }
16
+
5
17
  export declare class Alert {
6
18
  constructor(props: {
7
19
  /** Standard object's metadata. */
@@ -920,6 +932,19 @@ export declare class FlowSchemaList {
920
932
  readonly uid: string;
921
933
  }
922
934
 
935
+ export declare class FluxHelmChart {
936
+ constructor(props: {
937
+ /** Standard object's metadata. */
938
+ metadata?: ObjectMeta;
939
+ /** HelmChartSpec specifies the desired state of a Helm chart. */
940
+ spec?: Record<string, unknown>;
941
+ });
942
+ readonly name: string;
943
+ readonly namespace: string;
944
+ readonly status: Record<string, unknown>;
945
+ readonly uid: string;
946
+ }
947
+
923
948
  export declare class FluxInstance {
924
949
  constructor(props: {
925
950
  /** Standard object's metadata. */
@@ -999,16 +1024,16 @@ export declare class GRPCRoute {
999
1024
  readonly uid: string;
1000
1025
  }
1001
1026
 
1002
- export declare class HelmChart {
1027
+ export declare class HelmChartConfig {
1003
1028
  constructor(props: {
1004
1029
  /** Standard object's metadata. */
1005
1030
  metadata?: ObjectMeta;
1006
- /** HelmChartSpec specifies the desired state of a Helm chart. */
1031
+ /** HelmChartConfigSpec represents additional user-configurable details of an installed and configured Helm chart release.
1032
+ These fields are merged with or override the corresponding fields on the related HelmChart resource. */
1007
1033
  spec?: Record<string, unknown>;
1008
1034
  });
1009
1035
  readonly name: string;
1010
1036
  readonly namespace: string;
1011
- readonly status: Record<string, unknown>;
1012
1037
  readonly uid: string;
1013
1038
  }
1014
1039
 
@@ -1325,6 +1350,19 @@ export declare class JobList {
1325
1350
  readonly uid: string;
1326
1351
  }
1327
1352
 
1353
+ export declare class K3sHelmChart {
1354
+ constructor(props: {
1355
+ /** Standard object's metadata. */
1356
+ metadata?: ObjectMeta;
1357
+ /** HelmChartSpec represents the user-configurable details for installation and upgrade of a Helm chart release. */
1358
+ spec?: Record<string, unknown>;
1359
+ });
1360
+ readonly name: string;
1361
+ readonly namespace: string;
1362
+ readonly status: Record<string, unknown>;
1363
+ readonly uid: string;
1364
+ }
1365
+
1328
1366
  export declare class Kustomization {
1329
1367
  constructor(props: {
1330
1368
  /** Standard object's metadata. */
@@ -1,6 +1,7 @@
1
1
  // Code generated by chant generate. DO NOT EDIT.
2
2
  import { createResource, createProperty } from "./runtime";
3
3
 
4
+ export const Addon = createResource("K8s::K3s::Addon", "k8s", {"name":"name","namespace":"namespace","uid":"uid"});
4
5
  export const Alert = createResource("K8s::Flux::Alert", "k8s", {"name":"name","namespace":"namespace","uid":"uid"});
5
6
  export const APIGroup = createResource("K8s::Core::APIGroup", "k8s", {"name":"name","namespace":"namespace","uid":"uid"});
6
7
  export const APIGroupList = createResource("K8s::Core::APIGroupList", "k8s", {"name":"name","namespace":"namespace","uid":"uid"});
@@ -68,13 +69,14 @@ export const EventsEventList = createResource("K8s::Events::EventList", "k8s", {
68
69
  export const Eviction = createResource("K8s::Policy::Eviction", "k8s", {"name":"name","namespace":"namespace","uid":"uid"});
69
70
  export const FlowSchema = createResource("K8s::Flowcontrol::FlowSchema", "k8s", {"name":"name","namespace":"namespace","uid":"uid"});
70
71
  export const FlowSchemaList = createResource("K8s::Flowcontrol::FlowSchemaList", "k8s", {"name":"name","namespace":"namespace","uid":"uid"});
72
+ export const FluxHelmChart = createResource("K8s::Flux::HelmChart", "k8s", {"name":"name","namespace":"namespace","uid":"uid","status":"status"});
71
73
  export const FluxInstance = createResource("K8s::Flux::FluxInstance", "k8s", {"name":"name","namespace":"namespace","uid":"uid","status":"status"});
72
74
  export const FluxReport = createResource("K8s::Flux::FluxReport", "k8s", {"name":"name","namespace":"namespace","uid":"uid","status":"status"});
73
75
  export const Gateway = createResource("K8s::Gateway::Gateway", "k8s", {"name":"name","namespace":"namespace","uid":"uid","status":"status"});
74
76
  export const GatewayClass = createResource("K8s::Gateway::GatewayClass", "k8s", {"name":"name","namespace":"namespace","uid":"uid","status":"status"});
75
77
  export const GitRepository = createResource("K8s::Flux::GitRepository", "k8s", {"name":"name","namespace":"namespace","uid":"uid","status":"status"});
76
78
  export const GRPCRoute = createResource("K8s::Gateway::GRPCRoute", "k8s", {"name":"name","namespace":"namespace","uid":"uid","status":"status"});
77
- export const HelmChart = createResource("K8s::Flux::HelmChart", "k8s", {"name":"name","namespace":"namespace","uid":"uid","status":"status"});
79
+ export const HelmChartConfig = createResource("K8s::K3s::HelmChartConfig", "k8s", {"name":"name","namespace":"namespace","uid":"uid"});
78
80
  export const HelmRelease = createResource("K8s::Flux::HelmRelease", "k8s", {"name":"name","namespace":"namespace","uid":"uid","status":"status"});
79
81
  export const HelmRepository = createResource("K8s::Flux::HelmRepository", "k8s", {"name":"name","namespace":"namespace","uid":"uid","status":"status"});
80
82
  export const HorizontalPodAutoscaler = createResource("K8s::Autoscaling::HorizontalPodAutoscaler", "k8s", {"name":"name","namespace":"namespace","uid":"uid"});
@@ -100,6 +102,7 @@ export const IPAddressList = createResource("K8s::Networking::IPAddressList", "k
100
102
  export const Issuer = createResource("K8s::CertManager::Issuer", "k8s", {"name":"name","namespace":"namespace","uid":"uid","status":"status"});
101
103
  export const Job = createResource("K8s::Batch::Job", "k8s", {"name":"name","namespace":"namespace","uid":"uid"});
102
104
  export const JobList = createResource("K8s::Batch::JobList", "k8s", {"name":"name","namespace":"namespace","uid":"uid"});
105
+ export const K3sHelmChart = createResource("K8s::K3s::HelmChart", "k8s", {"name":"name","namespace":"namespace","uid":"uid","status":"status"});
103
106
  export const Kustomization = createResource("K8s::Flux::Kustomization", "k8s", {"name":"name","namespace":"namespace","uid":"uid","status":"status"});
104
107
  export const Lease = createResource("K8s::Coordination::Lease", "k8s", {"name":"name","namespace":"namespace","uid":"uid"});
105
108
  export const LeaseCandidate = createResource("K8s::Coordination::LeaseCandidate", "k8s", {"name":"name","namespace":"namespace","uid":"uid"});
@@ -332,6 +335,9 @@ export const EnvVar = createProperty("K8s::Core::EnvVar", "k8s");
332
335
  export const EnvVarSource = createProperty("K8s::Core::EnvVarSource", "k8s");
333
336
  export const EphemeralContainer = createProperty("K8s::Core::EphemeralContainer", "k8s");
334
337
  export const ExecAction = createProperty("K8s::Core::ExecAction", "k8s");
338
+ export const FluxHelmChart_SourceRef = createProperty("K8s::Flux::HelmChart.sourceRef", "k8s");
339
+ export const FluxHelmChart_Status = createProperty("K8s::Flux::HelmChart.status", "k8s");
340
+ export const FluxHelmChart_Verify = createProperty("K8s::Flux::HelmChart.verify", "k8s");
335
341
  export const FluxInstance_Cluster = createProperty("K8s::Flux::FluxInstance.cluster", "k8s");
336
342
  export const FluxInstance_CommonMetadata = createProperty("K8s::Flux::FluxInstance.commonMetadata", "k8s");
337
343
  export const FluxInstance_Distribution = createProperty("K8s::Flux::FluxInstance.distribution", "k8s");
@@ -362,9 +368,7 @@ export const GitRepository_Verify = createProperty("K8s::Flux::GitRepository.ver
362
368
  export const GRPCRoute_ParentRef = createProperty("K8s::Gateway::GRPCRoute.parentRefs", "k8s");
363
369
  export const GRPCRoute_Rule = createProperty("K8s::Gateway::GRPCRoute.rules", "k8s");
364
370
  export const GRPCRoute_Status = createProperty("K8s::Gateway::GRPCRoute.status", "k8s");
365
- export const HelmChart_SourceRef = createProperty("K8s::Flux::HelmChart.sourceRef", "k8s");
366
- export const HelmChart_Status = createProperty("K8s::Flux::HelmChart.status", "k8s");
367
- export const HelmChart_Verify = createProperty("K8s::Flux::HelmChart.verify", "k8s");
371
+ export const HelmChartConfig_ValuesSecret = createProperty("K8s::K3s::HelmChartConfig.valuesSecrets", "k8s");
368
372
  export const HelmRelease_Chart = createProperty("K8s::Flux::HelmRelease.chart", "k8s");
369
373
  export const HelmRelease_ChartRef = createProperty("K8s::Flux::HelmRelease.chartRef", "k8s");
370
374
  export const HelmRelease_CommonMetadata = createProperty("K8s::Flux::HelmRelease.commonMetadata", "k8s");
@@ -440,6 +444,13 @@ export const Issuer_SelfSigned = createProperty("K8s::CertManager::Issuer.selfSi
440
444
  export const Issuer_Status = createProperty("K8s::CertManager::Issuer.status", "k8s");
441
445
  export const Issuer_Vault = createProperty("K8s::CertManager::Issuer.vault", "k8s");
442
446
  export const Issuer_Venafi = createProperty("K8s::CertManager::Issuer.venafi", "k8s");
447
+ export const K3sHelmChart_AuthSecret = createProperty("K8s::K3s::HelmChart.authSecret", "k8s");
448
+ export const K3sHelmChart_DockerRegistrySecret = createProperty("K8s::K3s::HelmChart.dockerRegistrySecret", "k8s");
449
+ export const K3sHelmChart_PodSecurityContext = createProperty("K8s::K3s::HelmChart.podSecurityContext", "k8s");
450
+ export const K3sHelmChart_RepoCAConfigMap = createProperty("K8s::K3s::HelmChart.repoCAConfigMap", "k8s");
451
+ export const K3sHelmChart_SecurityContext = createProperty("K8s::K3s::HelmChart.securityContext", "k8s");
452
+ export const K3sHelmChart_Status = createProperty("K8s::K3s::HelmChart.status", "k8s");
453
+ export const K3sHelmChart_ValuesSecret = createProperty("K8s::K3s::HelmChart.valuesSecrets", "k8s");
443
454
  export const KeyToPath = createProperty("K8s::Core::KeyToPath", "k8s");
444
455
  export const Kustomization_CommonMetadata = createProperty("K8s::Flux::Kustomization.commonMetadata", "k8s");
445
456
  export const Kustomization_Decryption = createProperty("K8s::Flux::Kustomization.decryption", "k8s");