@intentius/chant-lexicon-k8s 0.44.7 → 0.44.8

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.
@@ -1 +1 @@
1
- {"version":3,"file":"describe-resources.d.ts","sourceRoot":"","sources":["../src/describe-resources.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAwD,MAAM,0BAA0B,CAAC;AAGxH,OAAO,KAAK,EAAa,SAAS,EAAE,MAAM,6BAA6B,CAAC;AACxE,OAAO,EAAuB,KAAK,YAAY,EAAE,MAAM,eAAe,CAAC;AAmBvE;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,SAAS,GAAG,MAAM,CA2CvD;AA4FD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,SAAS,GAAG,MAAM,EAAE,GAAG,SAAS,CAetE;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,SAAS,GAAG;IAClD,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,CAUA;AAQD,MAAM,WAAW,wBAAwB;IACvC,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAC,CAAC;IAC9E,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,sFAAsF;IACtF,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;;;;;;;;;;;;;;;OAiBG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,wBAAwB,EACjC,OAAO,GAAE,YAAkC,GAC1C,OAAO,CAAC,iBAAiB,CAAC,CA6J5B"}
1
+ {"version":3,"file":"describe-resources.d.ts","sourceRoot":"","sources":["../src/describe-resources.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAwD,MAAM,0BAA0B,CAAC;AAGxH,OAAO,KAAK,EAAa,SAAS,EAAE,MAAM,6BAA6B,CAAC;AACxE,OAAO,EAAuB,KAAK,YAAY,EAAE,MAAM,eAAe,CAAC;AAmBvE;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,SAAS,GAAG,MAAM,CA2CvD;AA4FD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,SAAS,GAAG,MAAM,EAAE,GAAG,SAAS,CAuBtE;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,SAAS,GAAG;IAClD,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,CAUA;AAQD,MAAM,WAAW,wBAAwB;IACvC,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAC,CAAC;IAC9E,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,sFAAsF;IACtF,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;;;;;;;;;;;;;;;OAiBG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,wBAAwB,EACjC,OAAO,GAAE,YAAkC,GAC1C,OAAO,CAAC,iBAAiB,CAAC,CA6J5B"}
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "algorithm": "sha256",
3
3
  "artifacts": {
4
- "manifest.json": "136b0cb515682abbfaf9014b9c8f4384b1c3c37f89556f55cc78c98293ea89e1",
4
+ "manifest.json": "1f650968faca1af0e095cca8ffae3d53597a3bb14013bec3e528d395e9e09b28",
5
5
  "meta.json": "f568fb8a0cbd4adf041baccaa3e1164c2e3c5c0090186236f887f0084f5521a8",
6
6
  "types/index.d.ts": "ef2c9df6a4d1ebfbe33f96c094fbbe3fc7909522ea38e086666fbf3c8ac477fd",
7
7
  "rules/argo-appset-single-project.ts": "afa9f310753aa2d475f35012b13135b2dfaebed2a35d44edbe185ebc07673674",
@@ -54,5 +54,5 @@
54
54
  "skills/chant-k8s-argo.md": "b1a0b826559d8c5033a479c5781efaf650320f0aee4419d8841170bd3393cea5",
55
55
  "skills/chant-k8s-flux.md": "9670efc2e9854bd14686b5adeb707396b9030a4dcaee6529b30abf56efe2c50c"
56
56
  },
57
- "composite": "db6ab2e7d8a2f62551ccd99c0cf23e0eaebf1b48d486c3879cd6f826ece4e87b"
57
+ "composite": "a7892db10dc02e8e856dcd2b7f1d64fd6f59fd2e1b3997e609f6c519a39f07da"
58
58
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "k8s",
3
- "version": "0.44.7",
3
+ "version": "0.44.8",
4
4
  "chantVersion": ">=0.1.0",
5
5
  "namespace": "K8s",
6
6
  "intrinsics": [],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intentius/chant-lexicon-k8s",
3
- "version": "0.44.7",
3
+ "version": "0.44.8",
4
4
  "description": "Kubernetes lexicon for chant — declarative IaC in TypeScript",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://intentius.io/chant",
@@ -76,7 +76,7 @@
76
76
  "@types/js-yaml": "^4.0.9"
77
77
  },
78
78
  "optionalDependencies": {
79
- "@intentius/chant-k8s-client": "^0.44.7"
79
+ "@intentius/chant-k8s-client": "^0.44.8"
80
80
  },
81
81
  "devDependencies": {
82
82
  "@intentius/chant": "*",
@@ -84,7 +84,7 @@
84
84
  },
85
85
  "peerDependencies": {
86
86
  "zod": "^4.3.6",
87
- "@intentius/chant": "^0.44.7",
87
+ "@intentius/chant": "^0.44.8",
88
88
  "typescript": "^5.9.3"
89
89
  }
90
90
  }
@@ -657,7 +657,7 @@ describe("k8s describeResources", () => {
657
657
  cluster.connector,
658
658
  );
659
659
 
660
- const child = result.resources["prod/backup-29157"];
660
+ const child = result.resources["Job/prod/backup-29157"];
661
661
  expect(child).toMatchObject({
662
662
  type: "K8s::Batch::Job",
663
663
  physicalId: "job-uid",
@@ -683,7 +683,7 @@ describe("k8s describeResources", () => {
683
683
  cluster.connector,
684
684
  );
685
685
 
686
- expect(result.resources["prod/hand-made"]).toBeUndefined();
686
+ expect(result.resources["Job/prod/hand-made"]).toBeUndefined();
687
687
  });
688
688
 
689
689
  // The issue's own estate: a declared MicroVMReplicaSet whose operator
@@ -730,7 +730,7 @@ describe("k8s describeResources", () => {
730
730
  cluster.connector,
731
731
  );
732
732
 
733
- expect(result.resources["microvm-demo/kmv-dev-a-vm-42sgf"]).toMatchObject({
733
+ expect(result.resources["MicroVM/microvm-demo/kmv-dev-a-vm-42sgf"]).toMatchObject({
734
734
  type: "K8s::KubeMicroVM::MicroVM",
735
735
  physicalId: "vm-uid",
736
736
  ownerChain: { root: "declared", entity: "workloadReplicaSet" },
@@ -771,7 +771,7 @@ describe("k8s describeResources", () => {
771
771
  cluster.connector,
772
772
  );
773
773
 
774
- expect(result.resources["ray/ml-head"]).toMatchObject({
774
+ expect(result.resources["Deployment/ray/ml-head"]).toMatchObject({
775
775
  type: "K8s::Apps::Deployment",
776
776
  physicalId: "head-uid",
777
777
  ownerChain: { root: "declared", entity: "ml" },
@@ -834,7 +834,7 @@ describe("k8s describeResources", () => {
834
834
  cluster.connector,
835
835
  );
836
836
 
837
- expect(result.resources["cluster:team-a-admin"]).toMatchObject({
837
+ expect(result.resources["cluster:ClusterRole/team-a-admin"]).toMatchObject({
838
838
  type: "K8s::Rbac::ClusterRole",
839
839
  physicalId: "role-uid",
840
840
  ownerChain: { root: "declared", entity: "team" },
@@ -887,7 +887,7 @@ describe("k8s describeResources", () => {
887
887
  cluster.connector,
888
888
  );
889
889
 
890
- expect(result.resources["prod/web"]).toMatchObject({
890
+ expect(result.resources["Deployment/prod/web"]).toMatchObject({
891
891
  type: "K8s::Apps::Deployment",
892
892
  ownerChain: { root: "declared", entity: "apps" },
893
893
  });
@@ -928,12 +928,62 @@ describe("k8s describeResources", () => {
928
928
  cluster.connector,
929
929
  );
930
930
 
931
- expect(result.resources["prod/web"]).toMatchObject({
931
+ expect(result.resources["Deployment/prod/web"]).toMatchObject({
932
932
  type: "K8s::Apps::Deployment",
933
933
  ownerChain: { root: "declared", entity: "webApp" },
934
934
  });
935
935
  });
936
936
 
937
+ // #1643 — the common convention names a workload and its Service alike, so
938
+ // a kind-less `namespace/name` key made them one row and only whichever
939
+ // kind swept first survived. Found by behold's argo estate: Application
940
+ // boxes held Endpoints instead of the Deployment.
941
+ test("same-named objects of different kinds are all reported — the key carries the kind (#1643)", async () => {
942
+ const app = {
943
+ apiVersion: "argoproj.io/v1alpha1",
944
+ kind: "Application",
945
+ metadata: { name: "app-a", namespace: "argocd", uid: "app-uid" },
946
+ status: { health: { status: "Healthy" }, sync: { status: "Synced" } },
947
+ };
948
+ const label = { "app.kubernetes.io/instance": "app-a" };
949
+ const deployment = {
950
+ apiVersion: "apps/v1",
951
+ kind: "Deployment",
952
+ metadata: { name: "app-a", namespace: "app-a", uid: "dep-uid", labels: label },
953
+ };
954
+ const service = {
955
+ apiVersion: "v1",
956
+ kind: "Service",
957
+ metadata: { name: "app-a", namespace: "app-a", uid: "svc-uid", labels: label },
958
+ };
959
+ const cluster = fakeCluster({
960
+ objects: {
961
+ [objectKey("argoproj.io/v1alpha1", "Application", "app-a", "argocd")]: app,
962
+ [objectKey("apps/v1", "Deployment", "app-a", "app-a")]: deployment,
963
+ [objectKey("v1", "Service", "app-a", "app-a")]: service,
964
+ },
965
+ });
966
+
967
+ const result = await describeResources(
968
+ {
969
+ environment: "prod",
970
+ buildOutput: "",
971
+ entityNames: ["appA"],
972
+ entities: makeEntities([
973
+ {
974
+ name: "appA",
975
+ entityType: "K8s::Argo::Application",
976
+ props: { metadata: { name: "app-a", namespace: "argocd" }, spec: { destination: { namespace: "app-a" } } },
977
+ },
978
+ ]),
979
+ },
980
+ cluster.connector,
981
+ );
982
+
983
+ expect(result.resources["Deployment/app-a/app-a"]).toMatchObject({ type: "K8s::Apps::Deployment", physicalId: "dep-uid" });
984
+ expect(result.resources["Service/app-a/app-a"]).toMatchObject({ type: "K8s::Core::Service", physicalId: "svc-uid" });
985
+ });
986
+
937
987
  test("a generic instance label matching NO declared Application claims nothing — not an inventory (#1549)", async () => {
938
988
  // `app.kubernetes.io/instance` is also what plain helm sets; on an
939
989
  // estate declaring a Flux CR (so the widened sweep runs), a foreign
@@ -967,7 +1017,7 @@ describe("k8s describeResources", () => {
967
1017
  cluster.connector,
968
1018
  );
969
1019
 
970
- expect(result.resources["flux-system/cert-manager"]).toBeUndefined();
1020
+ expect(result.resources["Deployment/flux-system/cert-manager"]).toBeUndefined();
971
1021
  });
972
1022
 
973
1023
  test("the sweep's bound is the estate's namespaces, not a kind list — and no cluster-scoped list without a cluster-scoped entity", async () => {
@@ -1047,7 +1097,7 @@ describe("k8s describeResources", () => {
1047
1097
  cluster.connector,
1048
1098
  );
1049
1099
 
1050
- const child = result.resources["prod/web-7d9f8c9c8-abcde"];
1100
+ const child = result.resources["Pod/prod/web-7d9f8c9c8-abcde"];
1051
1101
  expect(child).toMatchObject({
1052
1102
  type: "K8s::Core::Pod",
1053
1103
  physicalId: "pod-uid",
@@ -1078,7 +1128,7 @@ describe("k8s describeResources", () => {
1078
1128
  cluster.connector,
1079
1129
  );
1080
1130
 
1081
- expect(result.resources["prod/standalone"]).toMatchObject({
1131
+ expect(result.resources["Pod/prod/standalone"]).toMatchObject({
1082
1132
  type: "K8s::Core::Pod",
1083
1133
  ownerChain: { root: "unowned" },
1084
1134
  });
@@ -1113,7 +1163,7 @@ describe("k8s describeResources", () => {
1113
1163
  cluster.connector,
1114
1164
  );
1115
1165
 
1116
- expect(result.resources["prod/other-pod"]).toMatchObject({ ownerChain: { root: "foreign" } });
1166
+ expect(result.resources["Pod/prod/other-pod"]).toMatchObject({ ownerChain: { root: "foreign" } });
1117
1167
  });
1118
1168
 
1119
1169
  test("--owned withholds an unrelated Pod, but keeps a runtime child of a declared (owned) entity", async () => {
@@ -1156,8 +1206,8 @@ describe("k8s describeResources", () => {
1156
1206
  );
1157
1207
 
1158
1208
  expect(result.resources.web).toBeDefined(); // the marker let it through
1159
- expect(result.resources["prod/standalone"]).toBeUndefined();
1160
- expect(result.resources["prod/web-abc"]).toMatchObject({ ownerChain: { root: "declared", entity: "web" } });
1209
+ expect(result.resources["Pod/prod/standalone"]).toBeUndefined();
1210
+ expect(result.resources["Pod/prod/web-abc"]).toMatchObject({ ownerChain: { root: "declared", entity: "web" } });
1161
1211
  });
1162
1212
 
1163
1213
  test("a Pod that is itself declared is never duplicated as a runtime child", async () => {
@@ -1188,7 +1238,7 @@ describe("k8s describeResources", () => {
1188
1238
  );
1189
1239
 
1190
1240
  expect(result.resources.standalonePod).toBeDefined();
1191
- expect(result.resources["prod/standalone-pod"]).toBeUndefined();
1241
+ expect(result.resources["Pod/prod/standalone-pod"]).toBeUndefined();
1192
1242
  expect(Object.keys(result.resources)).toHaveLength(2); // web + standalonePod, no synthetic duplicate
1193
1243
  });
1194
1244
 
@@ -1398,6 +1448,34 @@ describe("unhappyConditions (#1401)", () => {
1398
1448
  expect(unhappyConditions({ status: {} } as never)).toBeUndefined();
1399
1449
  expect(unhappyConditions({} as never)).toBeUndefined();
1400
1450
  });
1451
+
1452
+ // #1644 — Argo's ApplicationCondition is {type, message, lastTransitionTime},
1453
+ // no status field, written only for problems. The polarity test has nothing
1454
+ // to read on it; the message IS the report, and it was the one diagnostic
1455
+ // line a broken Application writes.
1456
+ test("a status-less condition with a message passes through — Argo's ComparisonError (#1644)", () => {
1457
+ expect(
1458
+ unhappyConditions({
1459
+ status: {
1460
+ conditions: [
1461
+ {
1462
+ type: "ComparisonError",
1463
+ message: "Failed to load target state: app path does not exist",
1464
+ lastTransitionTime: "2026-08-10T00:00:00Z",
1465
+ },
1466
+ ],
1467
+ },
1468
+ } as never),
1469
+ ).toEqual(["ComparisonError: Failed to load target state: app path does not exist"]);
1470
+ });
1471
+
1472
+ test("a status-less condition with no message stays silent", () => {
1473
+ expect(
1474
+ unhappyConditions({
1475
+ status: { conditions: [{ type: "SomeMarker", lastTransitionTime: "2026-08-10T00:00:00Z" }] },
1476
+ } as never),
1477
+ ).toBeUndefined();
1478
+ });
1401
1479
  });
1402
1480
 
1403
1481
  describe("revisionAttributes — the GitOps convergence fields (#1632)", () => {
@@ -222,10 +222,18 @@ export function unhappyConditions(obj: K8sObject): string[] | undefined {
222
222
  const out: string[] = [];
223
223
  for (const c of conditions) {
224
224
  const cond = c as { type?: unknown; status?: unknown; reason?: unknown; message?: unknown };
225
- if (typeof cond.type !== "string" || typeof cond.status !== "string") continue;
226
- const goodWhenFalse = CONDITIONS_GOOD_WHEN_FALSE.test(cond.type);
227
- const happy = goodWhenFalse ? cond.status === "False" : cond.status === "True";
228
- if (happy) continue;
225
+ if (typeof cond.type !== "string") continue;
226
+ if (typeof cond.status === "string") {
227
+ const goodWhenFalse = CONDITIONS_GOOD_WHEN_FALSE.test(cond.type);
228
+ const happy = goodWhenFalse ? cond.status === "False" : cond.status === "True";
229
+ if (happy) continue;
230
+ } else if (!(typeof cond.message === "string" && cond.message)) {
231
+ // No status, no message: nothing to say. But a status-less condition
232
+ // WITH a message is an object speaking — Argo's ApplicationCondition is
233
+ // `{type, message, lastTransitionTime}`, written only for problems, and
234
+ // the polarity test above has nothing to read on it (#1644).
235
+ continue;
236
+ }
229
237
  const reason = typeof cond.reason === "string" && cond.reason ? `=${cond.reason}` : "";
230
238
  const message = typeof cond.message === "string" && cond.message ? `: ${cond.message}` : "";
231
239
  out.push(`${cond.type}${reason}${message}`);
@@ -701,8 +709,11 @@ async function addRuntimeChildren(
701
709
  const uid = obj.metadata?.uid;
702
710
  const name = obj.metadata?.name;
703
711
  if (!uid || !name || declaredByUid.has(uid)) return; // declared directly, or unaddressable
704
- const key = namespace ? `${namespace}/${name}` : `cluster:${name}`;
705
- if (resources[key]) return; // already reported by an earlier kind
712
+ // The kind is part of the identity: a Deployment, Service and Endpoints
713
+ // all named `app-a` in namespace `app-a` are three objects, and a
714
+ // kind-less key reported only whichever kind swept first (#1643).
715
+ const key = namespace ? `${swept.kind}/${namespace}/${name}` : `cluster:${swept.kind}/${name}`;
716
+ if (resources[key]) return; // re-swept object, not a new report
706
717
 
707
718
  let ownerChain = await resolveK8sOwnerChain(obj, { declaredByUid, reader: client, namespace });
708
719
  // The label channel (#1549) — only when the chain itself said nothing: