@intentius/chant-lexicon-k8s 0.41.16 → 0.41.17

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,7 +1,7 @@
1
1
  {
2
2
  "algorithm": "sha256",
3
3
  "artifacts": {
4
- "manifest.json": "528b5cb5e20dcb4ab991f8ab6d98da91f361d833b65d8e40bdb62867498ddc0e",
4
+ "manifest.json": "579bf4095e47e58c245aa6e2531cabeaf1b6b558a25a5e1090810d9bf31f2675",
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": "f895a3588933ce1fa536eeceb58265524ea731115d96b1ca7b3a93df9c19d1e1"
53
+ "composite": "d2f25a5b946a9065204fc9c38ac8a5c39417015120bbdaeee93c12c2597dc397"
54
54
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "k8s",
3
- "version": "0.41.16",
3
+ "version": "0.41.17",
4
4
  "chantVersion": ">=0.1.0",
5
5
  "namespace": "K8s",
6
6
  "intrinsics": [],
@@ -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;AA0ED;;;;;;;GAOG;AACH,wBAAsB,aAAa,CACjC,IAAI,EAAE,gBAAgB,EACtB,MAAM,CAAC,EAAE,WAAW,EACpB,OAAO,GAAE,YAAkC,GAC1C,OAAO,CAAC,mBAAmB,CAAC,CAoD9B;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;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;AAmGD;;;;;;;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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intentius/chant-lexicon-k8s",
3
- "version": "0.41.16",
3
+ "version": "0.41.17",
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.16",
87
+ "@intentius/chant": "^0.41.17",
88
88
  "typescript": "^5.9.3"
89
89
  }
90
90
  }
@@ -164,6 +164,76 @@ describe("kubectlApply", () => {
164
164
  expect(bodies[1].metadata?.labels?.["chant.intentius.io/env"]).toBeUndefined();
165
165
  });
166
166
 
167
+ test("a conflict owned entirely by chant's own managers is retaken with force — the stack-label migration path", async () => {
168
+ // An estate applied before its components named per-unit stacks has every
169
+ // object's labels owned by `chant:<ownership.stack>`. The re-stamp under
170
+ // the unit's manager conflicts — with chant itself. Refusing that forever
171
+ // would strand the estate with no non-force path (the capability exposes
172
+ // no force flag), so chant retakes its OWN fields, once, per document.
173
+ const file = join(dir, "k8s.yaml");
174
+ writeFileSync(file, deploymentYaml);
175
+ let patches = 0;
176
+ const cluster = fakeCluster({
177
+ respond: (req) => {
178
+ if (req.method !== "PATCH") return undefined;
179
+ patches++;
180
+ const forced = req.query.force === "true";
181
+ if (forced) return { body: JSON.parse(String(req.body)) };
182
+ return {
183
+ status: 409,
184
+ body: {
185
+ ...statusBody(409, "Conflict", 'Apply failed with 1 conflict: conflict with "chant:kubemicrovm-ops"'),
186
+ details: {
187
+ causes: [
188
+ {
189
+ type: "FieldManagerConflict",
190
+ message: 'conflict with "chant:kubemicrovm-ops"',
191
+ field: ".metadata.labels.chant.intentius.io/stack",
192
+ },
193
+ ],
194
+ },
195
+ },
196
+ };
197
+ },
198
+ });
199
+
200
+ const result = await applyManifest({ manifest: file, stack: "kmv-workload" }, undefined, cluster.connector);
201
+ expect(result.applied).toHaveLength(2);
202
+ // Each of the two documents: one refused apply + one forced retake.
203
+ expect(patches).toBe(4);
204
+ const requests = cluster.layer.requests.filter((r) => r.method === "PATCH");
205
+ expect(requests.map((r) => r.query.force)).toEqual(["false", "true", "false", "true"]);
206
+ });
207
+
208
+ test("a conflict involving any FOREIGN manager still refuses — self-retake never widens", async () => {
209
+ const file = join(dir, "k8s.yaml");
210
+ writeFileSync(file, deploymentYaml);
211
+ const cluster = fakeCluster({
212
+ respond: (req) =>
213
+ req.method === "PATCH"
214
+ ? {
215
+ status: 409,
216
+ body: {
217
+ ...statusBody(409, "Conflict", "Apply failed with 2 conflicts"),
218
+ details: {
219
+ causes: [
220
+ { type: "FieldManagerConflict", message: 'conflict with "chant:old"', field: ".metadata.labels.chant.intentius.io/stack" },
221
+ { type: "FieldManagerConflict", message: 'conflict with "helm"', field: ".spec.replicas" },
222
+ ],
223
+ },
224
+ },
225
+ }
226
+ : undefined,
227
+ });
228
+
229
+ const err = (await applyManifest({ manifest: file, stack: "kmv-workload" }, undefined, cluster.connector).catch(
230
+ (e: unknown) => e,
231
+ )) as Error;
232
+ expect(err.name).toBe("FieldManagerConflictError");
233
+ // No forced retry was attempted.
234
+ expect(cluster.layer.requests.filter((r) => r.method === "PATCH" && r.query.force === "true")).toHaveLength(0);
235
+ });
236
+
167
237
  test("no resolvable stack applies the documents verbatim — no marker invented", async () => {
168
238
  const file = join(dir, "k8s.yaml");
169
239
  writeFileSync(file, deploymentYaml);
@@ -191,6 +191,31 @@ async function resolveApplyIdentity(
191
191
  };
192
192
  }
193
193
 
194
+ /**
195
+ * Structural mirror of the client's `FieldManagerConflictError` surface —
196
+ * matched by `name` + shape, NOT `instanceof`, because a static value import
197
+ * of `@intentius/chant-k8s-client` here would put the API-client chain on the
198
+ * build path (the #1074 boundary; same reason the applier itself is reached
199
+ * by dynamic import).
200
+ */
201
+ interface ConflictErrorLike {
202
+ name: string;
203
+ conflicts?: Array<{ manager?: unknown }>;
204
+ }
205
+
206
+ /**
207
+ * Is every contested field in this conflict owned by chant itself (under any
208
+ * of its field managers — `chant` bare or `chant:<stack>`)? True means the
209
+ * conflict is a self-migration, never a dispute with another tool.
210
+ */
211
+ function isChantSelfConflict(err: unknown): boolean {
212
+ const e = err as ConflictErrorLike;
213
+ if (!e || e.name !== "FieldManagerConflictError" || !Array.isArray(e.conflicts) || e.conflicts.length === 0) return false;
214
+ return e.conflicts.every(
215
+ (c) => typeof c.manager === "string" && (c.manager === "chant" || c.manager.startsWith("chant:")),
216
+ );
217
+ }
218
+
194
219
  /**
195
220
  * Stamp the apply's own stack identity onto a document's labels.
196
221
  *
@@ -257,12 +282,26 @@ export async function applyManifest(
257
282
 
258
283
  const applied: AppliedRef[] = [];
259
284
  for (const document of documents) {
260
- const result = await client.apply(stampOwnership(document as K8sObject, stack), {
261
- fieldManager,
262
- force: args.force ?? false,
263
- dryRun: args.dryRun,
264
- signal,
265
- });
285
+ const stamped = stampOwnership(document as K8sObject, stack);
286
+ let result: K8sObject;
287
+ try {
288
+ result = await client.apply(stamped, {
289
+ fieldManager,
290
+ force: args.force ?? false,
291
+ dryRun: args.dryRun,
292
+ signal,
293
+ });
294
+ } catch (err) {
295
+ // "chant never forces a conflict on its own" is about taking fields
296
+ // from ANOTHER tool. A conflict where every contested field is owned
297
+ // by another `chant:*` manager is chant contesting itself — the
298
+ // ownership-stack → unit-stack label migration, or a renamed deploy
299
+ // unit — and refusing that forever would strand every estate applied
300
+ // before the rename with no non-force path back. Retake those fields
301
+ // deliberately, once, and only when no foreign manager is involved.
302
+ if (!isChantSelfConflict(err)) throw err;
303
+ result = await client.apply(stamped, { fieldManager, force: true, dryRun: args.dryRun, signal });
304
+ }
266
305
  const ref: AppliedRef = {
267
306
  apiVersion: String(result.apiVersion ?? document.apiVersion ?? ""),
268
307
  kind: String(result.kind ?? document.kind ?? ""),