@intentius/chant-lexicon-k8s 0.41.19 → 0.41.20

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.
@@ -11,7 +11,11 @@
11
11
  * instead of a raw object literal.
12
12
  */
13
13
  import type { KubectlApplyInput } from "./kubectl-apply.js";
14
+ import type { KustomizeApplyInput } from "./kustomize-apply.js";
14
15
  export declare const kubectlApply: (input: KubectlApplyInput & {
15
16
  noRollback?: string;
16
17
  }) => import("@intentius/chant/components").Step;
18
+ export declare const kustomizeApply: (input: KustomizeApplyInput & {
19
+ noRollback?: string;
20
+ }) => import("@intentius/chant/components").Step;
17
21
  //# sourceMappingURL=builders.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"builders.d.ts","sourceRoot":"","sources":["../../src/components/builders.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAEzD,eAAO,MAAM,YAAY;iBAA2C,MAAM;gDAAoB,CAAC"}
1
+ {"version":3,"file":"builders.d.ts","sourceRoot":"","sources":["../../src/components/builders.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAE7D,eAAO,MAAM,YAAY;iBAA2C,MAAM;gDAAoB,CAAC;AAI/F,eAAO,MAAM,cAAc;iBAA6C,MAAM;gDAAsB,CAAC"}
@@ -1,6 +1,6 @@
1
1
  import { type CapabilityPlugin } from "@intentius/chant/components/capability-plugin";
2
2
  export declare const K8S_VERB_FAMILIES: {
3
- readonly apply: readonly ["kubectl-apply"];
3
+ readonly apply: readonly ["kubectl-apply", "kustomize-apply"];
4
4
  };
5
5
  export declare const k8sCapabilityPlugin: CapabilityPlugin;
6
6
  //# sourceMappingURL=capability-plugin.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"capability-plugin.d.ts","sourceRoot":"","sources":["../../src/components/capability-plugin.ts"],"names":[],"mappings":"AAWA,OAAO,EAAqB,KAAK,gBAAgB,EAAE,MAAM,+CAA+C,CAAC;AAGzG,eAAO,MAAM,iBAAiB;;CAEpB,CAAC;AAEX,eAAO,MAAM,mBAAmB,EAAE,gBAWjC,CAAC"}
1
+ {"version":3,"file":"capability-plugin.d.ts","sourceRoot":"","sources":["../../src/components/capability-plugin.ts"],"names":[],"mappings":"AAWA,OAAO,EAAqB,KAAK,gBAAgB,EAAE,MAAM,+CAA+C,CAAC;AAIzG,eAAO,MAAM,iBAAiB;;CAEpB,CAAC;AAEX,eAAO,MAAM,mBAAmB,EAAE,gBAWjC,CAAC"}
@@ -13,4 +13,5 @@
13
13
  export { k8sCapabilityPlugin, K8S_VERB_FAMILIES } from "./capability-plugin.js";
14
14
  export * from "./builders.js";
15
15
  export { kubectlApplyCapability, createKubectlApplyCapability, type KubectlApplyInput, type KubectlApplyOutcome, } from "./kubectl-apply.js";
16
+ export { kustomizeApplyCapability, createKustomizeApplyCapability, renderCommand, type KustomizeApplyInput, type KustomizeApplyOutcome, } from "./kustomize-apply.js";
16
17
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAC7E,cAAc,YAAY,CAAC;AAC3B,OAAO,EACL,sBAAsB,EACtB,4BAA4B,EAC5B,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,GACzB,MAAM,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAC7E,cAAc,YAAY,CAAC;AAC3B,OAAO,EACL,sBAAsB,EACtB,4BAA4B,EAC5B,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,GACzB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,wBAAwB,EACxB,8BAA8B,EAC9B,aAAa,EACb,KAAK,mBAAmB,EACxB,KAAK,qBAAqB,GAC3B,MAAM,mBAAmB,CAAC"}
@@ -0,0 +1,48 @@
1
+ import type { Capability } from "@intentius/chant/components/capability";
2
+ /** Injectable shell runner — the helm-upgrade seam, same buffer bound. */
3
+ export type KustomizeRunner = (command: string) => Promise<{
4
+ stdout: string;
5
+ }>;
6
+ /** Structural mirrors of the activity module's shapes (see the module doc). */
7
+ interface AppliedRef {
8
+ apiVersion: string;
9
+ kind: string;
10
+ name: string;
11
+ namespace?: string;
12
+ }
13
+ export interface KustomizeApplyOutcome {
14
+ fieldManager: string;
15
+ applied: AppliedRef[];
16
+ pruned: AppliedRef[];
17
+ }
18
+ interface ApplierArgs {
19
+ manifest: string;
20
+ documents?: Array<Record<string, unknown>>;
21
+ environment?: string;
22
+ stack?: string;
23
+ context?: string;
24
+ deleteMode?: "never" | "owned-only" | "gated";
25
+ }
26
+ type Applier = (args: ApplierArgs) => Promise<KustomizeApplyOutcome>;
27
+ export interface KustomizeApplyInput {
28
+ /** The kustomization directory (holds `kustomization.yaml`) — an overlay or a base. */
29
+ dir: string;
30
+ /**
31
+ * The deploy unit / ownership stack — same double duty as kubectl-apply's.
32
+ * Omitted derives from the project's `ownership.stack`, but then the unit is
33
+ * invisible to `components status --live`. Name it.
34
+ */
35
+ stack?: string;
36
+ /** kubectl context. Omitted resolves `k8s.profiles.<ctx.env>.context`. */
37
+ context?: string;
38
+ /** Same delete vocabulary as kubectl-apply; default `never`. */
39
+ delete?: "never" | "owned-only";
40
+ }
41
+ /** The exact render command, standalone binary first. Pure; exported for tests. */
42
+ export declare function renderCommand(dir: string, tool?: "kustomize" | "kubectl"): string;
43
+ /** Factory with injectable renderer + applier, so tests assert the render
44
+ * command and the exact applier delegation without kustomize or a cluster. */
45
+ export declare function createKustomizeApplyCapability(run?: KustomizeRunner, apply?: Applier): Capability<KustomizeApplyInput, KustomizeApplyOutcome>;
46
+ export declare const kustomizeApplyCapability: Capability<KustomizeApplyInput, KustomizeApplyOutcome>;
47
+ export {};
48
+ //# sourceMappingURL=kustomize-apply.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kustomize-apply.d.ts","sourceRoot":"","sources":["../../src/components/kustomize-apply.ts"],"names":[],"mappings":"AA8BA,OAAO,KAAK,EAAE,UAAU,EAAiB,MAAM,wCAAwC,CAAC;AAIxF,0EAA0E;AAC1E,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAQ/E,+EAA+E;AAC/E,UAAU,UAAU;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,qBAAqB;IACpC,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,MAAM,EAAE,UAAU,EAAE,CAAC;CACtB;AAED,UAAU,WAAW;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAC3C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,OAAO,GAAG,YAAY,GAAG,OAAO,CAAC;CAC/C;AAED,KAAK,OAAO,GAAG,CAAC,IAAI,EAAE,WAAW,KAAK,OAAO,CAAC,qBAAqB,CAAC,CAAC;AAErE,MAAM,WAAW,mBAAmB;IAClC,uFAAuF;IACvF,GAAG,EAAE,MAAM,CAAC;IACZ;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,0EAA0E;IAC1E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gEAAgE;IAChE,MAAM,CAAC,EAAE,OAAO,GAAG,YAAY,CAAC;CACjC;AAED,mFAAmF;AACnF,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,GAAE,WAAW,GAAG,SAAuB,GAAG,MAAM,CAE9F;AAoBD;8EAC8E;AAC9E,wBAAgB,8BAA8B,CAC5C,GAAG,GAAE,eAA+B,EACpC,KAAK,CAAC,EAAE,OAAO,GACd,UAAU,CAAC,mBAAmB,EAAE,qBAAqB,CAAC,CAmBxD;AAED,eAAO,MAAM,wBAAwB,wDAAmC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"describe-resources.d.ts","sourceRoot":"","sources":["../src/describe-resources.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoDG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAsC,MAAM,0BAA0B,CAAC;AAGtG,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,CA0BvD;AAgED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,SAAS,GAAG,MAAM,EAAE,GAAG,SAAS,CAetE;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;CACd;AAED,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,wBAAwB,EACjC,OAAO,GAAE,YAAkC,GAC1C,OAAO,CAAC,iBAAiB,CAAC,CAmI5B"}
1
+ {"version":3,"file":"describe-resources.d.ts","sourceRoot":"","sources":["../src/describe-resources.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoDG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAsC,MAAM,0BAA0B,CAAC;AAGtG,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;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;CACd;AAED,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,wBAAwB,EACjC,OAAO,GAAE,YAAkC,GAC1C,OAAO,CAAC,iBAAiB,CAAC,CAmI5B"}
package/dist/index.d.ts CHANGED
@@ -2,6 +2,7 @@ export { k8sSerializer } from "./serializer.js";
2
2
  export { k8sPlugin } from "./plugin.js";
3
3
  export { k8sCapabilityPlugin, K8S_VERB_FAMILIES } from "./components/capability-plugin.js";
4
4
  export { kubectlApplyCapability, createKubectlApplyCapability, type KubectlApplyInput } from "./components/kubectl-apply.js";
5
+ export { kustomizeApplyCapability, createKustomizeApplyCapability, type KustomizeApplyInput } from "./components/kustomize-apply.js";
5
6
  export { defaultLabels, defaultAnnotations, isDefaultLabels, isDefaultAnnotations } from "./default-labels.js";
6
7
  export { DEFAULT_LABELS_MARKER, DEFAULT_ANNOTATIONS_MARKER } from "./default-labels.js";
7
8
  export { K8sLabels, K8sAnnotations } from "./variables.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "algorithm": "sha256",
3
3
  "artifacts": {
4
- "manifest.json": "33f334c5e62c1afb85d92d215862e87c92e454a204b6cb979978c99806da3a3e",
4
+ "manifest.json": "5dc7c047316cefafc266f579d7a266cfed2d3682a197bef9dbcb0f27c70af2df",
5
5
  "meta.json": "c0b141b882c51483c1d1aead748a0eb171aceca7432edd2a0404b062bf6d7831",
6
6
  "types/index.d.ts": "3be2af7494aca94adfa16269cb9c4e7a155529872207a3d70b0c4d39914f9eec",
7
7
  "rules/argo-appset-single-project.ts": "afa9f310753aa2d475f35012b13135b2dfaebed2a35d44edbe185ebc07673674",
@@ -50,5 +50,5 @@
50
50
  "skills/chant-k8s-aks.md": "e18f0e2b055f72cd7a37deaf258d7027c2d4d3e286e8fd4975b27a1f981a3ad9",
51
51
  "skills/chant-k8s-argo.md": "b1a0b826559d8c5033a479c5781efaf650320f0aee4419d8841170bd3393cea5"
52
52
  },
53
- "composite": "f6735e62a85a052fe7dffe5c38147933df2bcd8254009f5d747a6a1410409f5f"
53
+ "composite": "624054fff650a57fd0f5c234a26aa362e45fed534780e98405f4ebae4c2470d0"
54
54
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "k8s",
3
- "version": "0.41.19",
3
+ "version": "0.41.20",
4
4
  "chantVersion": ">=0.1.0",
5
5
  "namespace": "K8s",
6
6
  "intrinsics": [],
@@ -30,6 +30,7 @@
30
30
  * - **Documents apply in file order**, as `kubectl apply -f` does, and a
31
31
  * directory's files are read in sorted order.
32
32
  */
33
+ import type { K8sObject } from "@intentius/chant-k8s-client";
33
34
  import { type K8sConnector } from "../../api/connect.js";
34
35
  /**
35
36
  * How the apply treats chant-owned objects that are no longer declared. The
@@ -39,8 +40,21 @@ import { type K8sConnector } from "../../api/connect.js";
39
40
  */
40
41
  export type ApplyDeleteMode = "never" | "owned-only" | "gated";
41
42
  export interface KubectlApplyArgs {
42
- /** Path to a manifest file, or a directory of them. */
43
+ /**
44
+ * Path to a manifest file, or a directory of them. With `documents` given,
45
+ * this becomes only the human-facing label the heartbeats and logs carry
46
+ * (e.g. `kustomize:<dir>`), and nothing is read from disk.
47
+ */
43
48
  manifest: string;
49
+ /**
50
+ * Already-parsed documents to apply INSTEAD of reading `manifest` (#1548):
51
+ * a renderer that produced the objects in memory — kustomize-apply's
52
+ * `kustomize build` — hands them straight in rather than round-tripping
53
+ * through a temp file. Everything downstream (ownership stamping, the
54
+ * marker-scoped prune, the self-conflict retake) is document-driven and
55
+ * behaves identically.
56
+ */
57
+ documents?: K8sObject[];
44
58
  /**
45
59
  * kubectl context name. Uses the ambient context if omitted. To target the
46
60
  * same cluster the read path (`describeResources`) resolved for an
@@ -1 +1 @@
1
- {"version":3,"file":"kubectl.d.ts","sourceRoot":"","sources":["../../../src/op/activities/kubectl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAaH,OAAO,EAAuB,KAAK,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAI3E;;;;;GAKG;AACH,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,YAAY,GAAG,OAAO,CAAC;AAE/D,MAAM,WAAW,gBAAgB;IAC/B,uDAAuD;IACvD,QAAQ,EAAE,MAAM,CAAC;IACjB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB;;;;OAIG;IACH,UAAU,CAAC,EAAE,eAAe,CAAC;IAC7B;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,sFAAsF;IACtF,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,oCAAoC;AACpC,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,sCAAsC;AACtC,MAAM,WAAW,mBAAmB;IAClC,qDAAqD;IACrD,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,sFAAsF;IACtF,MAAM,EAAE,UAAU,EAAE,CAAC;CACtB;AAED,+EAA+E;AAC/E,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAiB7E;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,MAAM,CAAC,CAEjF;AAqHD;;;;;;;GAOG;AACH,wBAAsB,aAAa,CACjC,IAAI,EAAE,gBAAgB,EACtB,MAAM,CAAC,EAAE,WAAW,EACpB,OAAO,GAAE,YAAkC,GAC1C,OAAO,CAAC,mBAAmB,CAAC,CAkE9B;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAChC,IAAI,EAAE,gBAAgB,EACtB,MAAM,CAAC,EAAE,WAAW,EACpB,OAAO,GAAE,YAAkC,GAC1C,OAAO,CAAC,IAAI,CAAC,CAEf"}
1
+ {"version":3,"file":"kubectl.d.ts","sourceRoot":"","sources":["../../../src/op/activities/kubectl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAYH,OAAO,KAAK,EAAa,SAAS,EAAE,MAAM,6BAA6B,CAAC;AACxE,OAAO,EAAuB,KAAK,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAI3E;;;;;GAKG;AACH,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,YAAY,GAAG,OAAO,CAAC;AAE/D,MAAM,WAAW,gBAAgB;IAC/B;;;;OAIG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB;;;;;;;OAOG;IACH,SAAS,CAAC,EAAE,SAAS,EAAE,CAAC;IACxB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB;;;;OAIG;IACH,UAAU,CAAC,EAAE,eAAe,CAAC;IAC7B;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,sFAAsF;IACtF,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,oCAAoC;AACpC,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,sCAAsC;AACtC,MAAM,WAAW,mBAAmB;IAClC,qDAAqD;IACrD,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,sFAAsF;IACtF,MAAM,EAAE,UAAU,EAAE,CAAC;CACtB;AAED,+EAA+E;AAC/E,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAiB7E;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,MAAM,CAAC,CAEjF;AAqHD;;;;;;;GAOG;AACH,wBAAsB,aAAa,CACjC,IAAI,EAAE,gBAAgB,EACtB,MAAM,CAAC,EAAE,WAAW,EACpB,OAAO,GAAE,YAAkC,GAC1C,OAAO,CAAC,mBAAmB,CAAC,CAkE9B;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAChC,IAAI,EAAE,gBAAgB,EACtB,MAAM,CAAC,EAAE,WAAW,EACpB,OAAO,GAAE,YAAkC,GAC1C,OAAO,CAAC,IAAI,CAAC,CAEf"}
@@ -32,7 +32,22 @@ export interface PathMatch {
32
32
  /** Holds when the value is one of these. */
33
33
  oneOf?: Array<string | number>;
34
34
  }
35
- export type ReadinessMatch = ConditionMatch | PathMatch;
35
+ /**
36
+ * Match a `status.conditions[]` entry by `type` AND its `reason` (#1549).
37
+ * The dot-path walk can't index into a conditions array, and the Flux
38
+ * toolkit's failure signal is exactly `conditions[type=Ready].reason` — a
39
+ * wedge like `BuildFailed` keeps `Ready=False` forever while the generic
40
+ * poll waits out its whole timeout. `status` defaults to "False": a reason
41
+ * on a True condition is progress vocabulary, not a wedge.
42
+ */
43
+ export interface ConditionReasonMatch {
44
+ conditionType: string;
45
+ /** Required `status` of the condition (default "False"). */
46
+ status?: string;
47
+ /** Holds when the condition's `reason` is one of these. */
48
+ reasonOneOf: string[];
49
+ }
50
+ export type ReadinessMatch = ConditionMatch | PathMatch | ConditionReasonMatch;
36
51
  export interface ReadinessSpec {
37
52
  /** All must hold for the resource to be ready. */
38
53
  ready: ReadinessMatch[];
@@ -50,11 +65,6 @@ export interface ReadinessSpec {
50
65
  * and the CockroachDB operator.
51
66
  */
52
67
  export declare const DEFAULT_READINESS: ReadinessSpec;
53
- /**
54
- * Per-resource overrides keyed by `"<group>/<kind>"`. Argo's `Application`
55
- * reports `health`/`sync`, not a `Ready` condition — the case that proves the
56
- * override is necessary (#365).
57
- */
58
68
  export declare const READINESS_OVERRIDES: Record<string, ReadinessSpec>;
59
69
  /** Resolve the readiness spec for a resource: registry override, else default. */
60
70
  export declare function readinessFor(group: string | undefined, kind: string): ReadinessSpec;
@@ -1 +1 @@
1
- {"version":3,"file":"wait-for-ready.d.ts","sourceRoot":"","sources":["../../../src/op/activities/wait-for-ready.ts"],"names":[],"mappings":"AACA,OAAO,EAAuB,KAAK,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAE3E;;;;;;;;;;;;;;;;;GAiBG;AAIH,sEAAsE;AACtE,MAAM,WAAW,cAAc;IAC7B,aAAa,EAAE,MAAM,CAAC;IACtB,4DAA4D;IAC5D,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,2DAA2D;AAC3D,MAAM,WAAW,SAAS;IACxB,mCAAmC;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,iDAAiD;IACjD,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;IACnC,4CAA4C;IAC5C,KAAK,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,CAAC;CAEhC;AAED,MAAM,MAAM,cAAc,GAAG,cAAc,GAAG,SAAS,CAAC;AAExD,MAAM,WAAW,aAAa;IAC5B,kDAAkD;IAClD,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,oEAAoE;IACpE,QAAQ,CAAC,EAAE,cAAc,EAAE,CAAC;IAC5B;;;OAGG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,EAAE,aAG/B,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAS7D,CAAC;AAEF,kFAAkF;AAClF,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,aAAa,CAEnF;AAwCD,qFAAqF;AACrF,wBAAgB,OAAO,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,aAAa,GAAG,OAAO,CAGlE;AAED,gFAAgF;AAChF,wBAAgB,aAAa,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,aAAa,GAAG,cAAc,GAAG,SAAS,CAE3F;AAID,6EAA6E;AAC7E,qBAAa,oBAAqB,SAAQ,KAAK;gBACjC,OAAO,EAAE,MAAM;CAI5B;AAED,MAAM,WAAW,gBAAgB;IAC/B,gGAAgG;IAChG,IAAI,EAAE,MAAM,CAAC;IACb,qBAAqB;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,2CAA2C;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,6EAA6E;IAC7E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gEAAgE;IAChE,IAAI,CAAC,EAAE,aAAa,CAAC;IACrB,kEAAkE;IAClE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,mEAAmE;AACnE,MAAM,MAAM,eAAe,GAAG,CAC5B,IAAI,EAAE,gBAAgB,EACtB,MAAM,CAAC,EAAE,WAAW,KACjB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAEtC;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,GAAE,YAAkC,GAAG,eAAe,CAmC/F;AAED;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,EAAE,eAAsE,CAAC;AAE5G;;;;;;;GAOG;AACH,wBAAsB,YAAY,CAChC,IAAI,EAAE,gBAAgB,EACtB,MAAM,CAAC,EAAE,WAAW,EACpB,OAAO,GAAE,eAAsC,GAC9C,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAuBlC"}
1
+ {"version":3,"file":"wait-for-ready.d.ts","sourceRoot":"","sources":["../../../src/op/activities/wait-for-ready.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intentius/chant-lexicon-k8s",
3
- "version": "0.41.19",
3
+ "version": "0.41.20",
4
4
  "description": "Kubernetes lexicon for chant — declarative IaC in TypeScript",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://intentius.io/chant",
@@ -84,7 +84,7 @@
84
84
  },
85
85
  "peerDependencies": {
86
86
  "zod": "^4.3.6",
87
- "@intentius/chant": "^0.41.19",
87
+ "@intentius/chant": "^0.41.20",
88
88
  "typescript": "^5.9.3"
89
89
  }
90
90
  }
@@ -13,5 +13,10 @@
13
13
 
14
14
  import { step } from "@intentius/chant/components";
15
15
  import type { KubectlApplyInput } from "./kubectl-apply";
16
+ import type { KustomizeApplyInput } from "./kustomize-apply";
16
17
 
17
18
  export const kubectlApply = step<KubectlApplyInput & { noRollback?: string }>("kubectl-apply");
19
+
20
+ // Same needs-opt-out posture, same sugar (#1548): a kustomize render in front
21
+ // of the identical server-side apply.
22
+ export const kustomizeApply = step<KustomizeApplyInput & { noRollback?: string }>("kustomize-apply");
@@ -11,9 +11,10 @@
11
11
  import type { Capability } from "@intentius/chant/components/capability";
12
12
  import { ownPackageVersion, type CapabilityPlugin } from "@intentius/chant/components/capability-plugin";
13
13
  import { kubectlApplyCapability } from "./kubectl-apply";
14
+ import { kustomizeApplyCapability } from "./kustomize-apply";
14
15
 
15
16
  export const K8S_VERB_FAMILIES = {
16
- apply: ["kubectl-apply"],
17
+ apply: ["kubectl-apply", "kustomize-apply"],
17
18
  } as const;
18
19
 
19
20
  export const k8sCapabilityPlugin: CapabilityPlugin = {
@@ -22,7 +23,7 @@ export const k8sCapabilityPlugin: CapabilityPlugin = {
22
23
  // a literal here was stale one release after it was written.
23
24
  version: ownPackageVersion(import.meta.url),
24
25
  capabilities(): Array<Capability<never, unknown>> {
25
- return [kubectlApplyCapability as Capability<never, unknown>];
26
+ return [kubectlApplyCapability as Capability<never, unknown>, kustomizeApplyCapability as Capability<never, unknown>];
26
27
  },
27
28
  families(): Record<string, readonly string[]> {
28
29
  return K8S_VERB_FAMILIES;
@@ -19,3 +19,10 @@ export {
19
19
  type KubectlApplyInput,
20
20
  type KubectlApplyOutcome,
21
21
  } from "./kubectl-apply";
22
+ export {
23
+ kustomizeApplyCapability,
24
+ createKustomizeApplyCapability,
25
+ renderCommand,
26
+ type KustomizeApplyInput,
27
+ type KustomizeApplyOutcome,
28
+ } from "./kustomize-apply";
@@ -0,0 +1,98 @@
1
+ /**
2
+ * `kustomize-apply` (#1548) — the render-then-apply leaf. What must hold:
3
+ * the capability registers under its kind with the lexicon's own version
4
+ * (#1505), the render command is exact and falls back to kubectl's vendored
5
+ * kustomize, the rendered documents reach the applier INLINE (no temp file)
6
+ * with the deploy-unit stack, and the rollback posture matches kubectl-apply
7
+ * (server-side apply has no native undo).
8
+ */
9
+ import { describe, test, expect } from "vitest";
10
+ import { createKustomizeApplyCapability, kustomizeApplyCapability, renderCommand, type KustomizeApplyInput } from "./kustomize-apply";
11
+ import { k8sCapabilityPlugin, K8S_VERB_FAMILIES } from "./capability-plugin";
12
+ import { isCapabilityPlugin } from "@intentius/chant/components/capability-plugin";
13
+ import type { DeployContext } from "@intentius/chant/components/capability";
14
+
15
+ const ctx = { env: "dev" } as DeployContext;
16
+
17
+ const RENDERED = `apiVersion: v1
18
+ kind: Namespace
19
+ metadata:
20
+ name: web
21
+ ---
22
+ apiVersion: apps/v1
23
+ kind: Deployment
24
+ metadata:
25
+ name: app
26
+ namespace: web
27
+ spec:
28
+ replicas: 1
29
+ `;
30
+
31
+ describe("kustomize-apply capability (#1548)", () => {
32
+ test("registers on the k8s plugin, in the apply family, at the package's own version", () => {
33
+ expect(isCapabilityPlugin(k8sCapabilityPlugin)).toBe(true);
34
+ expect(k8sCapabilityPlugin.capabilities().map((c) => c.kind)).toContain("kustomize-apply");
35
+ expect(K8S_VERB_FAMILIES.apply).toContain("kustomize-apply");
36
+ // #1505 — never a stale literal.
37
+ const { version } = JSON.parse(
38
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
39
+ require("node:fs").readFileSync(new URL("../../package.json", import.meta.url), "utf8"),
40
+ ) as { version: string };
41
+ expect(k8sCapabilityPlugin.version).toBe(version);
42
+ });
43
+
44
+ test("rollback is needs-opt-out — a server-side apply keeps no previous state", () => {
45
+ expect(kustomizeApplyCapability.rollbackPolicy).toBe("needs-opt-out");
46
+ });
47
+
48
+ test("renders with kustomize build and hands the parsed documents to the applier inline", async () => {
49
+ const commands: string[] = [];
50
+ const applierArgs: unknown[] = [];
51
+ const cap = createKustomizeApplyCapability(
52
+ async (command) => {
53
+ commands.push(command);
54
+ return { stdout: RENDERED };
55
+ },
56
+ async (args) => {
57
+ applierArgs.push(args);
58
+ return { fieldManager: "chant:web", applied: [], pruned: [] };
59
+ },
60
+ );
61
+
62
+ await cap.run(ctx, { dir: "overlays/dev", stack: "web", delete: "owned-only" } as KustomizeApplyInput as never);
63
+
64
+ expect(commands).toEqual(["kustomize build 'overlays/dev'"]);
65
+ expect(applierArgs).toHaveLength(1);
66
+ const args = applierArgs[0] as { manifest: string; documents: unknown[]; environment: string; stack: string; deleteMode: string };
67
+ expect(args.manifest).toBe("kustomize:overlays/dev"); // a label, not a path
68
+ expect(args.documents).toHaveLength(2);
69
+ expect((args.documents[1] as { kind: string }).kind).toBe("Deployment");
70
+ expect(args.environment).toBe("dev");
71
+ expect(args.stack).toBe("web");
72
+ expect(args.deleteMode).toBe("owned-only");
73
+ });
74
+
75
+ test("falls back to kubectl's vendored kustomize when the binary is missing", async () => {
76
+ const commands: string[] = [];
77
+ const cap = createKustomizeApplyCapability(
78
+ async (command) => {
79
+ commands.push(command);
80
+ if (command.startsWith("kustomize ")) throw new Error("spawn kustomize ENOENT");
81
+ return { stdout: RENDERED };
82
+ },
83
+ async () => ({ fieldManager: "chant", applied: [], pruned: [] }),
84
+ );
85
+ await cap.run(ctx, { dir: "overlays/dev" } as KustomizeApplyInput as never);
86
+ expect(commands).toEqual([renderCommand("overlays/dev", "kustomize"), renderCommand("overlays/dev", "kubectl")]);
87
+ });
88
+
89
+ test("a render failure that is NOT a missing binary propagates — a broken overlay is not silently retried", async () => {
90
+ const cap = createKustomizeApplyCapability(
91
+ async () => {
92
+ throw new Error("accumulating resources: missing base ../common");
93
+ },
94
+ async () => ({ fieldManager: "chant", applied: [], pruned: [] }),
95
+ );
96
+ await expect(cap.run(ctx, { dir: "overlays/dev" } as KustomizeApplyInput as never)).rejects.toThrow("missing base");
97
+ });
98
+ });
@@ -0,0 +1,133 @@
1
+ /**
2
+ * `kustomize-apply` — the kustomize leaf for the component model (#1548).
3
+ *
4
+ * Kustomize is a RENDERER, not a substrate: `kustomize build <dir>` emits
5
+ * exactly the Kubernetes manifests the existing apply pipeline was built for.
6
+ * So this capability is a render step in front of `kubectl-apply`'s machinery
7
+ * — the rendered documents go straight to the same server-side applier
8
+ * (inline, no temp file), which stamps the deploy-unit stack (#1539), prunes
9
+ * marker-scoped, retakes chant's own field conflicts (#1541/#1542), and is
10
+ * observed by the same `describeStackStatus` label sweep. An estate that
11
+ * keeps its overlay tree gets components-status honesty without adopting a
12
+ * single typed manifest.
13
+ *
14
+ * `stack` doubles as the ownership identity and the deploy unit, exactly as
15
+ * `kubectl-apply` — one name, both jobs.
16
+ *
17
+ * Renderer resolution: `kustomize build`, falling back to
18
+ * `kubectl kustomize` (the same renderer vendored into kubectl) when the
19
+ * standalone binary is absent. Both run through an injectable runner so tests
20
+ * assert the exact command without either binary installed. The 64MiB
21
+ * maxBuffer mirrors helm-upgrade's — a big overlay renders megabytes.
22
+ *
23
+ * The #1074 boundary applies as it does to kubectl-apply: nothing exported
24
+ * from the lexicon entry point may statically import the API-client chain, so
25
+ * the applier is reached by dynamic import inside `run()` and its argument
26
+ * shape is a structural mirror.
27
+ */
28
+ import { exec } from "node:child_process";
29
+ import { promisify } from "node:util";
30
+ import { loadAll } from "js-yaml";
31
+ import type { Capability, DeployContext } from "@intentius/chant/components/capability";
32
+
33
+ const execAsync = promisify(exec);
34
+
35
+ /** Injectable shell runner — the helm-upgrade seam, same buffer bound. */
36
+ export type KustomizeRunner = (command: string) => Promise<{ stdout: string }>;
37
+ const defaultRunner: KustomizeRunner = (command) => execAsync(command, { maxBuffer: 64 * 1024 * 1024 });
38
+
39
+ /** Single-quote shell escaping, as helm-upgrade quotes its argv. */
40
+ function q(v: string): string {
41
+ return `'${v.replace(/'/g, "'\\''")}'`;
42
+ }
43
+
44
+ /** Structural mirrors of the activity module's shapes (see the module doc). */
45
+ interface AppliedRef {
46
+ apiVersion: string;
47
+ kind: string;
48
+ name: string;
49
+ namespace?: string;
50
+ }
51
+
52
+ export interface KustomizeApplyOutcome {
53
+ fieldManager: string;
54
+ applied: AppliedRef[];
55
+ pruned: AppliedRef[];
56
+ }
57
+
58
+ interface ApplierArgs {
59
+ manifest: string;
60
+ documents?: Array<Record<string, unknown>>;
61
+ environment?: string;
62
+ stack?: string;
63
+ context?: string;
64
+ deleteMode?: "never" | "owned-only" | "gated";
65
+ }
66
+
67
+ type Applier = (args: ApplierArgs) => Promise<KustomizeApplyOutcome>;
68
+
69
+ export interface KustomizeApplyInput {
70
+ /** The kustomization directory (holds `kustomization.yaml`) — an overlay or a base. */
71
+ dir: string;
72
+ /**
73
+ * The deploy unit / ownership stack — same double duty as kubectl-apply's.
74
+ * Omitted derives from the project's `ownership.stack`, but then the unit is
75
+ * invisible to `components status --live`. Name it.
76
+ */
77
+ stack?: string;
78
+ /** kubectl context. Omitted resolves `k8s.profiles.<ctx.env>.context`. */
79
+ context?: string;
80
+ /** Same delete vocabulary as kubectl-apply; default `never`. */
81
+ delete?: "never" | "owned-only";
82
+ }
83
+
84
+ /** The exact render command, standalone binary first. Pure; exported for tests. */
85
+ export function renderCommand(dir: string, tool: "kustomize" | "kubectl" = "kustomize"): string {
86
+ return tool === "kustomize" ? `kustomize build ${q(dir)}` : `kubectl kustomize ${q(dir)}`;
87
+ }
88
+
89
+ /** Render the overlay, falling back to kubectl's vendored kustomize when the
90
+ * standalone binary is missing (ENOENT/127), and parse the emitted YAML. */
91
+ async function renderDocuments(dir: string, run: KustomizeRunner): Promise<Array<Record<string, unknown>>> {
92
+ let stdout: string;
93
+ try {
94
+ ({ stdout } = await run(renderCommand(dir, "kustomize")));
95
+ } catch (err) {
96
+ const message = err instanceof Error ? err.message : String(err);
97
+ if (!/ENOENT|not found|command not found|127/.test(message)) throw err;
98
+ ({ stdout } = await run(renderCommand(dir, "kubectl")));
99
+ }
100
+ const documents: Array<Record<string, unknown>> = [];
101
+ for (const doc of loadAll(stdout)) {
102
+ if (doc && typeof doc === "object" && !Array.isArray(doc)) documents.push(doc as Record<string, unknown>);
103
+ }
104
+ return documents;
105
+ }
106
+
107
+ /** Factory with injectable renderer + applier, so tests assert the render
108
+ * command and the exact applier delegation without kustomize or a cluster. */
109
+ export function createKustomizeApplyCapability(
110
+ run: KustomizeRunner = defaultRunner,
111
+ apply?: Applier,
112
+ ): Capability<KustomizeApplyInput, KustomizeApplyOutcome> {
113
+ return {
114
+ kind: "kustomize-apply",
115
+ // Server-side apply keeps no previous object state, and kustomize adds no
116
+ // undo of its own — same posture as kubectl-apply.
117
+ rollbackPolicy: "needs-opt-out",
118
+ async run(ctx: DeployContext, input: KustomizeApplyInput): Promise<KustomizeApplyOutcome> {
119
+ const documents = await renderDocuments(input.dir, run);
120
+ const applier: Applier = apply ?? ((await import("../op/activities/kubectl")).applyManifest as unknown as Applier);
121
+ return applier({
122
+ manifest: `kustomize:${input.dir}`,
123
+ documents,
124
+ environment: ctx.env,
125
+ ...(input.stack !== undefined ? { stack: input.stack } : {}),
126
+ ...(input.context !== undefined ? { context: input.context } : {}),
127
+ ...(input.delete !== undefined ? { deleteMode: input.delete } : {}),
128
+ });
129
+ },
130
+ };
131
+ }
132
+
133
+ export const kustomizeApplyCapability = createKustomizeApplyCapability();
@@ -573,6 +573,135 @@ describe("k8s describeResources", () => {
573
573
  expect(result.resources["prod/hand-made"]).toBeUndefined();
574
574
  });
575
575
 
576
+ test("a Flux-managed Deployment is attributed to its declared Kustomization by the controller's labels (#1549)", async () => {
577
+ // Flux stamps managed objects with name+namespace labels instead of
578
+ // ownerReferences, so the chain walk reaches nothing — the label
579
+ // channel resolves against the DECLARED CR, exactly. The workload also
580
+ // lives in a namespace the estate never declares an entity in: the
581
+ // Kustomization's own spec.targetNamespace is what puts it in scope.
582
+ const kustomization = {
583
+ apiVersion: "kustomize.toolkit.fluxcd.io/v1",
584
+ kind: "Kustomization",
585
+ metadata: { name: "apps", namespace: "flux-system", uid: "ks-uid" },
586
+ status: { conditions: [{ type: "Ready", status: "True" }] },
587
+ };
588
+ const managed = {
589
+ apiVersion: "apps/v1",
590
+ kind: "Deployment",
591
+ metadata: {
592
+ name: "web",
593
+ namespace: "prod",
594
+ uid: "dep-uid",
595
+ labels: { "kustomize.toolkit.fluxcd.io/name": "apps", "kustomize.toolkit.fluxcd.io/namespace": "flux-system" },
596
+ },
597
+ status: { readyReplicas: 1, replicas: 1 },
598
+ };
599
+ const cluster = fakeCluster({
600
+ objects: {
601
+ [objectKey("kustomize.toolkit.fluxcd.io/v1", "Kustomization", "apps", "flux-system")]: kustomization,
602
+ [objectKey("apps/v1", "Deployment", "web", "prod")]: managed,
603
+ },
604
+ });
605
+
606
+ const result = await describeResources(
607
+ {
608
+ environment: "prod",
609
+ buildOutput: "",
610
+ entityNames: ["apps"],
611
+ entities: makeEntities([
612
+ {
613
+ name: "apps",
614
+ entityType: "K8s::Flux::Kustomization",
615
+ props: { metadata: { name: "apps", namespace: "flux-system" }, spec: { targetNamespace: "prod", sourceRef: { kind: "GitRepository", name: "infra" } } },
616
+ },
617
+ ]),
618
+ },
619
+ cluster.connector,
620
+ );
621
+
622
+ expect(result.resources["prod/web"]).toMatchObject({
623
+ type: "K8s::Apps::Deployment",
624
+ ownerChain: { root: "declared", entity: "apps" },
625
+ });
626
+ });
627
+
628
+ test("Argo's bare instance label claims only a UNIQUE declared Application (#1549)", async () => {
629
+ const app = (name: string) => ({
630
+ apiVersion: "argoproj.io/v1alpha1",
631
+ kind: "Application",
632
+ metadata: { name, namespace: "argocd", uid: `${name}-uid` },
633
+ status: { health: { status: "Healthy" }, sync: { status: "Synced" } },
634
+ });
635
+ const managed = {
636
+ apiVersion: "apps/v1",
637
+ kind: "Deployment",
638
+ metadata: { name: "web", namespace: "prod", uid: "dep-uid", labels: { "app.kubernetes.io/instance": "web" } },
639
+ };
640
+ const cluster = fakeCluster({
641
+ objects: {
642
+ [objectKey("argoproj.io/v1alpha1", "Application", "web", "argocd")]: app("web"),
643
+ [objectKey("apps/v1", "Deployment", "web", "prod")]: managed,
644
+ },
645
+ });
646
+
647
+ const result = await describeResources(
648
+ {
649
+ environment: "prod",
650
+ buildOutput: "",
651
+ entityNames: ["webApp"],
652
+ entities: makeEntities([
653
+ {
654
+ name: "webApp",
655
+ entityType: "K8s::Argo::Application",
656
+ props: { metadata: { name: "web", namespace: "argocd" }, spec: { destination: { namespace: "prod" } } },
657
+ },
658
+ ]),
659
+ },
660
+ cluster.connector,
661
+ );
662
+
663
+ expect(result.resources["prod/web"]).toMatchObject({
664
+ type: "K8s::Apps::Deployment",
665
+ ownerChain: { root: "declared", entity: "webApp" },
666
+ });
667
+ });
668
+
669
+ test("a generic instance label matching NO declared Application claims nothing — not an inventory (#1549)", async () => {
670
+ // `app.kubernetes.io/instance` is also what plain helm sets; on an
671
+ // estate declaring a Flux CR (so the widened sweep runs), a foreign
672
+ // helm-installed Deployment must not be attributed.
673
+ const kustomization = {
674
+ apiVersion: "kustomize.toolkit.fluxcd.io/v1",
675
+ kind: "Kustomization",
676
+ metadata: { name: "apps", namespace: "flux-system", uid: "ks-uid" },
677
+ };
678
+ const foreign = {
679
+ apiVersion: "apps/v1",
680
+ kind: "Deployment",
681
+ metadata: { name: "cert-manager", namespace: "flux-system", uid: "cm-uid", labels: { "app.kubernetes.io/instance": "cert-manager" } },
682
+ };
683
+ const cluster = fakeCluster({
684
+ objects: {
685
+ [objectKey("kustomize.toolkit.fluxcd.io/v1", "Kustomization", "apps", "flux-system")]: kustomization,
686
+ [objectKey("apps/v1", "Deployment", "cert-manager", "flux-system")]: foreign,
687
+ },
688
+ });
689
+
690
+ const result = await describeResources(
691
+ {
692
+ environment: "prod",
693
+ buildOutput: "",
694
+ entityNames: ["apps"],
695
+ entities: makeEntities([
696
+ { name: "apps", entityType: "K8s::Flux::Kustomization", props: { metadata: { name: "apps", namespace: "flux-system" } } },
697
+ ]),
698
+ },
699
+ cluster.connector,
700
+ );
701
+
702
+ expect(result.resources["flux-system/cert-manager"]).toBeUndefined();
703
+ });
704
+
576
705
  test("a core-only estate sweeps no extra groups — Pods stay the core representative", async () => {
577
706
  const svc = { apiVersion: "v1", kind: "Service", metadata: { name: "web", namespace: "prod", uid: "svc-uid" } };
578
707
  const cluster = fakeCluster({
@@ -880,6 +1009,36 @@ describe("statusFromObject — the most specific failing signal (#1397)", () =>
880
1009
  expect(statusFromObject({ status: { readyReplicas: 1, replicas: 3 } } as never)).toBe("PROGRESSING(1/3)");
881
1010
  expect(statusFromObject({} as never)).toBe("PRESENT");
882
1011
  });
1012
+
1013
+ test("a conditions-first CR reads its Ready condition, not PRESENT (#1549)", () => {
1014
+ // A wedged Flux Kustomization/HelmRelease has no phase and no replica
1015
+ // counts, so it fell straight to PRESENT — a wedged reconciler read
1016
+ // exactly like a healthy one.
1017
+ expect(
1018
+ statusFromObject({ status: { conditions: [{ type: "Ready", status: "False", reason: "BuildFailed" }] } } as never),
1019
+ ).toBe("BuildFailed");
1020
+ expect(
1021
+ statusFromObject({ status: { conditions: [{ type: "Ready", status: "False" }] } } as never),
1022
+ ).toBe("NOT-READY");
1023
+ expect(
1024
+ statusFromObject({ status: { conditions: [{ type: "Ready", status: "True", reason: "ReconciliationSucceeded" }] } } as never),
1025
+ ).toBe("READY");
1026
+ });
1027
+
1028
+ test("the Ready rung never changes a Pod or workload word — phase and replicas rank above it (#1549)", () => {
1029
+ expect(
1030
+ statusFromObject({ status: { phase: "Running", conditions: [{ type: "Ready", status: "False" }] } } as never),
1031
+ ).toBe("Running");
1032
+ expect(
1033
+ statusFromObject({ status: { readyReplicas: 1, replicas: 3, conditions: [{ type: "Ready", status: "False" }] } } as never),
1034
+ ).toBe("PROGRESSING(1/3)");
1035
+ });
1036
+
1037
+ test("an Argo Application reads health/sync (#1549)", () => {
1038
+ expect(statusFromObject({ status: { health: { status: "Degraded" }, sync: { status: "Synced" } } } as never)).toBe("Degraded");
1039
+ expect(statusFromObject({ status: { health: { status: "Healthy" }, sync: { status: "OutOfSync" } } } as never)).toBe("OutOfSync");
1040
+ expect(statusFromObject({ status: { health: { status: "Healthy" }, sync: { status: "Synced" } } } as never)).toBe("READY");
1041
+ });
883
1042
  });
884
1043
 
885
1044
  // #1401 — #1397 made the status WORD honest; this carries the part that says
@@ -104,9 +104,54 @@ export function statusFromObject(obj: K8sObject): string {
104
104
  ? "READY"
105
105
  : `PROGRESSING(${status.readyReplicas}/${status.replicas})`;
106
106
  }
107
+
108
+ // The kstatus rung (#1549): an object with NO phase and NO replica counts —
109
+ // a Flux Kustomization/HelmRelease, cert-manager, any conditions-first CRD —
110
+ // used to fall straight to PRESENT, so a wedged reconciler read exactly like
111
+ // a healthy one. `Ready` is the contract those objects speak: False → its
112
+ // reason (`BuildFailed`, `UpgradeFailed`, …), True → READY. Deliberately
113
+ // BELOW the phase/replica rungs so no Pod or workload word changes — for
114
+ // those objects a False Ready is already visible through the rungs above.
115
+ const ready = readyConditionWord(status);
116
+ if (ready) return ready;
117
+
118
+ // Argo's Application speaks health/sync, not a Ready condition (the same
119
+ // split the readiness registry encodes) — `Degraded` / `OutOfSync` beats
120
+ // PRESENT for the one object whose whole job is convergence.
121
+ const argo = argoStatusWord(status);
122
+ if (argo) return argo;
123
+
107
124
  return "PRESENT";
108
125
  }
109
126
 
127
+ /** The `Ready` condition rendered as a status word: False → its reason (or
128
+ * NOT-READY when the controller gave none), True → READY, absent → nothing. */
129
+ function readyConditionWord(status: K8sObject["status"]): string | undefined {
130
+ const conditions = (status as { conditions?: unknown } | undefined)?.conditions;
131
+ if (!Array.isArray(conditions)) return undefined;
132
+ for (const c of conditions) {
133
+ const cond = c as { type?: unknown; status?: unknown; reason?: unknown };
134
+ if (cond.type !== "Ready") continue;
135
+ if (cond.status === "True") return "READY";
136
+ if (cond.status === "False") {
137
+ return typeof cond.reason === "string" && cond.reason.length > 0 ? cond.reason : "NOT-READY";
138
+ }
139
+ }
140
+ return undefined;
141
+ }
142
+
143
+ /** Argo Application health/sync as a status word: anything but the happy pair
144
+ * surfaces the unhappy half; Healthy+Synced reads as READY. */
145
+ function argoStatusWord(status: K8sObject["status"]): string | undefined {
146
+ const s = status as { health?: { status?: unknown }; sync?: { status?: unknown } } | undefined;
147
+ const health = typeof s?.health?.status === "string" ? s.health.status : undefined;
148
+ const sync = typeof s?.sync?.status === "string" ? s.sync.status : undefined;
149
+ if (!health && !sync) return undefined;
150
+ if (health && health !== "Healthy") return health;
151
+ if (sync && sync !== "Synced") return sync;
152
+ return "READY";
153
+ }
154
+
110
155
  /**
111
156
  * The reason a container is stuck, if one is — `CrashLoopBackOff`,
112
157
  * `ImagePullBackOff`, `CreateContainerConfigError`.
@@ -333,7 +378,7 @@ export async function describeResources(
333
378
  if (op && op.apiVersion.includes("/")) runtimeGroupVersions.add(op.apiVersion);
334
379
  }
335
380
 
336
- await addRuntimeChildren(client, resources, options.owned, runtimeGroupVersions);
381
+ await addRuntimeChildren(client, resources, options.owned, runtimeGroupVersions, declared);
337
382
 
338
383
  return observation(resources, unobserved);
339
384
  }
@@ -360,22 +405,99 @@ export async function describeResources(
360
405
  * axis for them to report against, the same way an out-of-band AWS child
361
406
  * resource has none either.
362
407
  */
408
+ /** The declared GitOps CRs, indexed the way their controllers' labels can be
409
+ * resolved (#1549). `byRef` keys are `type\0namespace\0name` (Flux — exact);
410
+ * `argoByName` maps an Application's name to its declared entity, or null
411
+ * when two declared Applications share the name (ambiguous → no claim). */
412
+ interface GitopsIndex {
413
+ present: boolean;
414
+ byRef: Map<string, string>;
415
+ argoByName: Map<string, string | null>;
416
+ targetNamespaces: Set<string>;
417
+ }
418
+
419
+ function gitopsIndex(declared: readonly Declared[]): GitopsIndex {
420
+ const byRef = new Map<string, string>();
421
+ const argoByName = new Map<string, string | null>();
422
+ const targetNamespaces = new Set<string>();
423
+ let present = false;
424
+ for (const { entityName, entityType, props } of declared) {
425
+ const isArgo = entityType === "K8s::Argo::Application";
426
+ const isFlux = entityType === "K8s::Flux::Kustomization" || entityType === "K8s::Flux::HelmRelease";
427
+ if (!isArgo && !isFlux) continue;
428
+ present = true;
429
+ const meta = props.metadata as { name?: string; namespace?: string } | undefined;
430
+ const spec = props.spec as { targetNamespace?: unknown; destination?: { namespace?: unknown } } | undefined;
431
+ const name = meta?.name;
432
+ if (!name) continue;
433
+ if (isFlux) {
434
+ byRef.set(`${entityType}\0${meta?.namespace ?? "default"}\0${name}`, entityName);
435
+ if (typeof spec?.targetNamespace === "string") targetNamespaces.add(spec.targetNamespace);
436
+ } else {
437
+ argoByName.set(name, argoByName.has(name) ? null : entityName);
438
+ if (typeof spec?.destination?.namespace === "string") targetNamespaces.add(spec.destination.namespace);
439
+ }
440
+ }
441
+ return { present, byRef, argoByName, targetNamespaces };
442
+ }
443
+
444
+ /** The declared GitOps CR a swept object's controller labels resolve to, or
445
+ * undefined. Flux's label pairs are exact; Argo's bare instance label claims
446
+ * only a UNIQUE declared Application. */
447
+ function gitopsOwner(labels: Record<string, string> | undefined, gitops: GitopsIndex): string | undefined {
448
+ if (!labels || !gitops.present) return undefined;
449
+ const ksName = labels["kustomize.toolkit.fluxcd.io/name"];
450
+ const ksNs = labels["kustomize.toolkit.fluxcd.io/namespace"];
451
+ if (ksName && ksNs) {
452
+ const hit = gitops.byRef.get(`K8s::Flux::Kustomization\0${ksNs}\0${ksName}`);
453
+ if (hit) return hit;
454
+ }
455
+ const hrName = labels["helm.toolkit.fluxcd.io/name"];
456
+ const hrNs = labels["helm.toolkit.fluxcd.io/namespace"];
457
+ if (hrName && hrNs) {
458
+ const hit = gitops.byRef.get(`K8s::Flux::HelmRelease\0${hrNs}\0${hrName}`);
459
+ if (hit) return hit;
460
+ }
461
+ const instance = labels["app.kubernetes.io/instance"];
462
+ if (instance) {
463
+ const hit = gitops.argoByName.get(instance);
464
+ if (hit) return hit; // null (ambiguous) and undefined both decline
465
+ }
466
+ return undefined;
467
+ }
468
+
363
469
  async function addRuntimeChildren(
364
470
  client: K8sClient,
365
471
  resources: Record<string, ResourceMetadata>,
366
472
  owned: boolean | undefined,
367
473
  groupVersions: ReadonlySet<string> = new Set(),
474
+ declared: readonly Declared[] = [],
368
475
  ): Promise<void> {
369
476
  const declaredByUid = new Map<string, string>();
370
477
  for (const [entityName, meta] of Object.entries(resources)) {
371
478
  if (meta.physicalId) declaredByUid.set(meta.physicalId, entityName);
372
479
  }
373
480
 
481
+ // GitOps attribution (#1549): Argo and Flux stamp their managed objects with
482
+ // labels, not ownerReferences, so the chain walk never reaches a declared
483
+ // entity for them. The label channel resolves against the DECLARED CRs by
484
+ // (type, namespace, name) — exact for Flux (its labels carry both halves) —
485
+ // and by unique name for Argo (its instance label carries no namespace, and
486
+ // it doubles as a generic Helm label, so anything ambiguous resolves to
487
+ // nothing: the module's standing conservatism).
488
+ const gitops = gitopsIndex(declared);
489
+
374
490
  const namespaces = new Set<string>();
375
491
  for (const meta of Object.values(resources)) {
376
492
  const namespace = (meta.attributes as { namespace?: string } | undefined)?.namespace;
377
493
  if (namespace) namespaces.add(namespace);
378
494
  }
495
+ // The namespaces a declared GitOps CR TARGETS are declared literals too
496
+ // (Argo `spec.destination.namespace`, Flux `spec.targetNamespace`) — the
497
+ // controller puts the workloads there, so the scan must look there, and
498
+ // reading it off the declaration keeps this bounded by the estate, never a
499
+ // cluster-wide sweep.
500
+ for (const ns of gitops.targetNamespaces) namespaces.add(ns);
379
501
  if (namespaces.size === 0) return;
380
502
 
381
503
  // The kinds to scan: Pods, plus each declared groupVersion's namespaced,
@@ -384,6 +506,18 @@ async function addRuntimeChildren(
384
506
  const kinds: Array<{ apiVersion: string; kind: string; typeName: string }> = [
385
507
  { apiVersion: "v1", kind: "Pod", typeName: "K8s::Core::Pod" },
386
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
+ );
520
+ }
387
521
  for (const gv of [...groupVersions].sort()) {
388
522
  let infos;
389
523
  try {
@@ -413,7 +547,13 @@ async function addRuntimeChildren(
413
547
  if (!uid || !name || declaredByUid.has(uid)) return; // declared directly, or unaddressable
414
548
  if (resources[`${namespace}/${name}`]) return; // already reported by an earlier kind
415
549
 
416
- const ownerChain = await resolveK8sOwnerChain(obj, { declaredByUid, reader: client, namespace });
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
+ }
417
557
 
418
558
  // `--owned`: withhold an object that is neither a runtime child of a
419
559
  // declared entity nor carrying chant's own marker — the same rule the
package/src/index.ts CHANGED
@@ -9,6 +9,7 @@ export { k8sPlugin } from "./plugin";
9
9
  // cfn-deploy.
10
10
  export { k8sCapabilityPlugin, K8S_VERB_FAMILIES } from "./components/capability-plugin";
11
11
  export { kubectlApplyCapability, createKubectlApplyCapability, type KubectlApplyInput } from "./components/kubectl-apply";
12
+ export { kustomizeApplyCapability, createKustomizeApplyCapability, type KustomizeApplyInput } from "./components/kustomize-apply";
12
13
 
13
14
  // Default labels & annotations
14
15
  export { defaultLabels, defaultAnnotations, isDefaultLabels, isDefaultAnnotations } from "./default-labels";
@@ -281,6 +281,28 @@ describe("kubectlApply", () => {
281
281
  expect(body.metadata?.labels).toBeUndefined();
282
282
  });
283
283
 
284
+ test("inline documents bypass the filesystem — the kustomize-apply seam (#1548)", async () => {
285
+ const cluster = fakeCluster({ respond: echoApplies });
286
+ const result = await applyManifest(
287
+ {
288
+ manifest: "kustomize:overlays/dev", // a label; nothing at this path
289
+ documents: [
290
+ { apiVersion: "v1", kind: "Namespace", metadata: { name: "web" } },
291
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "app", namespace: "web" } },
292
+ ],
293
+ stack: "web",
294
+ },
295
+ undefined,
296
+ cluster.connector,
297
+ );
298
+ expect(result.applied.map((r) => r.kind)).toEqual(["Namespace", "Deployment"]);
299
+ // The documents were stamped like any read-from-disk manifest.
300
+ const body = JSON.parse(String(cluster.layer.requests.find((r) => r.method === "PATCH")!.body)) as {
301
+ metadata?: { labels?: Record<string, string> };
302
+ };
303
+ expect(body.metadata?.labels?.["chant.intentius.io/stack"]).toBe("web");
304
+ });
305
+
284
306
  test("an explicit context is honored and skips the environment lookup entirely", async () => {
285
307
  const file = join(dir, "k8s.yaml");
286
308
  writeFileSync(file, deploymentYaml);
@@ -55,8 +55,21 @@ import { DEFAULT_IMPORT_TYPES } from "../../api/sweep-types";
55
55
  export type ApplyDeleteMode = "never" | "owned-only" | "gated";
56
56
 
57
57
  export interface KubectlApplyArgs {
58
- /** Path to a manifest file, or a directory of them. */
58
+ /**
59
+ * Path to a manifest file, or a directory of them. With `documents` given,
60
+ * this becomes only the human-facing label the heartbeats and logs carry
61
+ * (e.g. `kustomize:<dir>`), and nothing is read from disk.
62
+ */
59
63
  manifest: string;
64
+ /**
65
+ * Already-parsed documents to apply INSTEAD of reading `manifest` (#1548):
66
+ * a renderer that produced the objects in memory — kustomize-apply's
67
+ * `kustomize build` — hands them straight in rather than round-tripping
68
+ * through a temp file. Everything downstream (ownership stamping, the
69
+ * marker-scoped prune, the self-conflict retake) is document-driven and
70
+ * behaves identically.
71
+ */
72
+ documents?: K8sObject[];
60
73
  /**
61
74
  * kubectl context name. Uses the ambient context if omitted. To target the
62
75
  * same cluster the read path (`describeResources`) resolved for an
@@ -285,7 +298,7 @@ export async function applyManifest(
285
298
  signal?: AbortSignal,
286
299
  connect: K8sConnector = defaultK8sConnector,
287
300
  ): Promise<ApplyManifestResult> {
288
- const documents = readManifestDocuments(args.manifest);
301
+ const documents = args.documents ?? readManifestDocuments(args.manifest);
289
302
  const { fieldManager, stack } = await resolveApplyIdentity(args);
290
303
  const heartbeatInterval = setInterval(() => {
291
304
  safeHeartbeat({ step: "kubectl apply", manifest: args.manifest });
@@ -49,6 +49,33 @@ describe("readiness model", () => {
49
49
  expect(isReady(obj, DEFAULT_READINESS)).toBe(true);
50
50
  });
51
51
 
52
+ test("Flux overrides fail fast on Ready=False wedge reasons, wait on ordinary progress (#1549)", () => {
53
+ // A Flux wedge (bad build, failed upgrade, unreachable source) keeps
54
+ // Ready=False forever — before these entries the generic poll waited out
55
+ // its whole timeout. The ready half is still the kstatus default.
56
+ const spec = readinessFor("kustomize.toolkit.fluxcd.io", "Kustomization");
57
+ expect(spec).not.toBe(DEFAULT_READINESS);
58
+ expect(isReady(ready([{ type: "Ready", status: "True" }]), spec)).toBe(true);
59
+
60
+ const wedged = ready([{ type: "Ready", status: "False", reason: "BuildFailed" } as never]);
61
+ expect(isReady(wedged, spec)).toBe(false);
62
+ expect(firstTerminal(wedged, spec)).toBeDefined();
63
+
64
+ // Ready=False with a PROGRESS reason (a reconcile in flight) is not a
65
+ // wedge — keep polling.
66
+ const progressing = ready([{ type: "Ready", status: "False", reason: "Progressing" } as never]);
67
+ expect(firstTerminal(progressing, spec)).toBeUndefined();
68
+
69
+ // A reason on a TRUE condition is vocabulary, never terminal.
70
+ const healthyWithReason = ready([{ type: "Ready", status: "True", reason: "ReconciliationSucceeded" } as never]);
71
+ expect(firstTerminal(healthyWithReason, spec)).toBeUndefined();
72
+
73
+ const hr = readinessFor("helm.toolkit.fluxcd.io", "HelmRelease");
74
+ expect(firstTerminal(ready([{ type: "Ready", status: "False", reason: "UpgradeFailed" } as never]), hr)).toBeDefined();
75
+ const git = readinessFor("source.toolkit.fluxcd.io", "GitRepository");
76
+ expect(firstTerminal(ready([{ type: "Ready", status: "False", reason: "AuthenticationFailed" } as never]), git)).toBeDefined();
77
+ });
78
+
52
79
  test("Argo override uses health/sync, not a Ready condition", () => {
53
80
  const spec = readinessFor("argoproj.io", "Application");
54
81
  expect(spec).not.toBe(DEFAULT_READINESS);
@@ -40,7 +40,23 @@ export interface PathMatch {
40
40
  // With neither `equals` nor `oneOf`, holds when the value is present (non-null).
41
41
  }
42
42
 
43
- export type ReadinessMatch = ConditionMatch | PathMatch;
43
+ /**
44
+ * Match a `status.conditions[]` entry by `type` AND its `reason` (#1549).
45
+ * The dot-path walk can't index into a conditions array, and the Flux
46
+ * toolkit's failure signal is exactly `conditions[type=Ready].reason` — a
47
+ * wedge like `BuildFailed` keeps `Ready=False` forever while the generic
48
+ * poll waits out its whole timeout. `status` defaults to "False": a reason
49
+ * on a True condition is progress vocabulary, not a wedge.
50
+ */
51
+ export interface ConditionReasonMatch {
52
+ conditionType: string;
53
+ /** Required `status` of the condition (default "False"). */
54
+ status?: string;
55
+ /** Holds when the condition's `reason` is one of these. */
56
+ reasonOneOf: string[];
57
+ }
58
+
59
+ export type ReadinessMatch = ConditionMatch | PathMatch | ConditionReasonMatch;
44
60
 
45
61
  export interface ReadinessSpec {
46
62
  /** All must hold for the resource to be ready. */
@@ -69,6 +85,19 @@ export const DEFAULT_READINESS: ReadinessSpec = {
69
85
  * reports `health`/`sync`, not a `Ready` condition — the case that proves the
70
86
  * override is necessary (#365).
71
87
  */
88
+ /**
89
+ * Flux toolkit wedge reasons (#1549): a `Ready=False` with one of these
90
+ * `reason`s does not heal by waiting — a bad build, a failed install, an
91
+ * unreachable or invalid source. The toolkit is kstatus-conformant, so the
92
+ * READY half of every entry below is just the default; the value added is
93
+ * failing fast instead of polling out the full timeout.
94
+ */
95
+ const FLUX_TERMINAL = (reasons: string[]): ReadinessSpec => ({
96
+ ready: [{ conditionType: "Ready", status: "True" }],
97
+ terminal: [{ conditionType: "Ready", reasonOneOf: reasons }],
98
+ observedGeneration: true,
99
+ });
100
+
72
101
  export const READINESS_OVERRIDES: Record<string, ReadinessSpec> = {
73
102
  "argoproj.io/Application": {
74
103
  ready: [
@@ -78,6 +107,38 @@ export const READINESS_OVERRIDES: Record<string, ReadinessSpec> = {
78
107
  terminal: [{ path: "status.health.status", oneOf: ["Degraded", "Missing"] }],
79
108
  observedGeneration: false,
80
109
  },
110
+ "kustomize.toolkit.fluxcd.io/Kustomization": FLUX_TERMINAL([
111
+ "BuildFailed",
112
+ "HealthCheckFailed",
113
+ "ReconciliationFailed",
114
+ "ArtifactFailed",
115
+ "PruneFailed",
116
+ ]),
117
+ "helm.toolkit.fluxcd.io/HelmRelease": FLUX_TERMINAL([
118
+ "InstallFailed",
119
+ "UpgradeFailed",
120
+ "RollbackFailed",
121
+ "UninstallFailed",
122
+ "ArtifactFailed",
123
+ "ChartPullError",
124
+ ]),
125
+ "source.toolkit.fluxcd.io/GitRepository": FLUX_TERMINAL([
126
+ "AuthenticationFailed",
127
+ "GitOperationFailed",
128
+ "URLInvalid",
129
+ "StorageOperationFailed",
130
+ ]),
131
+ "source.toolkit.fluxcd.io/OCIRepository": FLUX_TERMINAL([
132
+ "AuthenticationFailed",
133
+ "OCIPullFailed",
134
+ "URLInvalid",
135
+ "StorageOperationFailed",
136
+ ]),
137
+ "source.toolkit.fluxcd.io/HelmChart": FLUX_TERMINAL([
138
+ "ChartPullError",
139
+ "ChartPackageError",
140
+ "StorageOperationFailed",
141
+ ]),
81
142
  };
82
143
 
83
144
  /** Resolve the readiness spec for a resource: registry override, else default. */
@@ -94,15 +155,31 @@ function getPath(obj: unknown, path: string): unknown {
94
155
  );
95
156
  }
96
157
 
158
+ function isConditionReason(m: ReadinessMatch): m is ConditionReasonMatch {
159
+ return (m as ConditionReasonMatch).reasonOneOf !== undefined;
160
+ }
161
+
97
162
  function isCondition(m: ReadinessMatch): m is ConditionMatch {
98
- return (m as ConditionMatch).conditionType !== undefined;
163
+ return (m as ConditionMatch).conditionType !== undefined && !isConditionReason(m);
99
164
  }
100
165
 
101
- function matchCondition(obj: unknown, m: ConditionMatch): boolean {
166
+ function findCondition(obj: unknown, conditionType: string): Record<string, unknown> | undefined {
102
167
  const conds = getPath(obj, "status.conditions");
103
- if (!Array.isArray(conds)) return false;
104
- const c = conds.find((x) => x && typeof x === "object" && (x as Record<string, unknown>).type === m.conditionType);
105
- return !!c && String((c as Record<string, unknown>).status) === (m.status ?? "True");
168
+ if (!Array.isArray(conds)) return undefined;
169
+ return conds.find((x) => x && typeof x === "object" && (x as Record<string, unknown>).type === conditionType) as
170
+ | Record<string, unknown>
171
+ | undefined;
172
+ }
173
+
174
+ function matchCondition(obj: unknown, m: ConditionMatch): boolean {
175
+ const c = findCondition(obj, m.conditionType);
176
+ return !!c && String(c.status) === (m.status ?? "True");
177
+ }
178
+
179
+ function matchConditionReason(obj: unknown, m: ConditionReasonMatch): boolean {
180
+ const c = findCondition(obj, m.conditionType);
181
+ if (!c || String(c.status) !== (m.status ?? "False")) return false;
182
+ return typeof c.reason === "string" && m.reasonOneOf.includes(c.reason);
106
183
  }
107
184
 
108
185
  function matchPath(obj: unknown, m: PathMatch): boolean {
@@ -113,6 +190,7 @@ function matchPath(obj: unknown, m: PathMatch): boolean {
113
190
  }
114
191
 
115
192
  function matches(obj: unknown, m: ReadinessMatch): boolean {
193
+ if (isConditionReason(m)) return matchConditionReason(obj, m);
116
194
  return isCondition(m) ? matchCondition(obj, m) : matchPath(obj, m);
117
195
  }
118
196