@intentius/chant 0.41.11 → 0.41.13

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":"components.d.ts","sourceRoot":"","sources":["../../../src/cli/handlers/components.ts"],"names":[],"mappings":"AA+CA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAGlD,OAAO,KAAK,EAAE,KAAK,EAAa,MAAM,4BAA4B,CAAC;AAGnE;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,0BAA0B,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CA4ErF;AA4DD;;;;;;;;;;;;;;;GAeG;AACH;;;;;;;;6EAQ6E;AAC7E,wBAAgB,eAAe,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,EAAE,CAMzD;AA4DD,wBAAsB,mBAAmB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CA6R9E;AAED,kEAAkE;AAClE,wBAAsB,oBAAoB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAM/E"}
1
+ {"version":3,"file":"components.d.ts","sourceRoot":"","sources":["../../../src/cli/handlers/components.ts"],"names":[],"mappings":"AA+CA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAGlD,OAAO,KAAK,EAAE,KAAK,EAAa,MAAM,4BAA4B,CAAC;AAGnE;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,0BAA0B,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CA4ErF;AA4DD;;;;;;;;;;;;;;;GAeG;AACH;;;;;;;;6EAQ6E;AAC7E,wBAAgB,eAAe,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,EAAE,CAMzD;AAmED,wBAAsB,mBAAmB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CA6R9E;AAED,kEAAkE;AAClE,wBAAsB,oBAAoB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAM/E"}
@@ -100,6 +100,17 @@ export interface ComponentStatusRow {
100
100
  * where it exists.
101
101
  */
102
102
  resources?: ComponentResourceRollup;
103
+ /**
104
+ * Set when some but not all of this component's deploy units were observed
105
+ * present (#1528). `live` is `false` — deployed means all of it — but a
106
+ * consumer painting the row can distinguish "half up" from "gone", and the
107
+ * missing unit names are the actionable part.
108
+ */
109
+ partial?: {
110
+ present: number;
111
+ total: number;
112
+ missing: string[];
113
+ };
103
114
  }
104
115
  /** A component's owning deploy unit and its provider-native status. */
105
116
  export interface LiveStackInfo {
@@ -143,6 +154,19 @@ export interface LiveComponentEvidence {
143
154
  /** The owning deploy unit's raw status, when observed (AWS: the component's own
144
155
  * CFN stack). Surfaced onto `ComponentStatusRow.stack` for a richer palette. */
145
156
  stack?: LiveStackInfo;
157
+ /**
158
+ * Set when some but not all of the component's deploy units were observed
159
+ * present (#1528). `live` is `false` — a component is deployed when all of
160
+ * it is — but "nothing observed live" would be a lie, and it was one: a
161
+ * multi-unit component with a single absent unit reported exactly that
162
+ * while its Helm releases sat deployed and healthy in the same row's
163
+ * `stack` field. Consumers get the split and the names of what is missing.
164
+ */
165
+ partial?: {
166
+ present: number;
167
+ total: number;
168
+ missing: string[];
169
+ };
146
170
  /**
147
171
  * How the component's own resources answered, before any merge collapsed
148
172
  * them (behold#98). Always present when evidence exists — a component that
@@ -1 +1 @@
1
- {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/lifecycle/status.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/lifecycle/status.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5D,OAAO,EAAsB,KAAK,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAC1E,OAAO,KAAK,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAC5E,OAAO,EAAwB,KAAK,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAE7E,+FAA+F;AAC/F,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,kEAAkE;IAClE,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB,+IAA+I;IAC/I,KAAK,CAAC,EAAE,gBAAgB,CAAC;IACzB;;;;;;;;;OASG;IACH,YAAY,CAAC,EAAE,mBAAmB,CAAC;IACnC,gDAAgD;IAChD,cAAc,EAAE,YAAY,GAAG,YAAY,GAAG,OAAO,GAAG,SAAS,GAAG,SAAS,CAAC;IAC9E,iDAAiD;IACjD,MAAM,EAAE,MAAM,CAAC;IACf;;;;;;;OAOG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;IACf;;;;OAIG;IACH,UAAU,CAAC,EAAE;QAAE,MAAM,EAAE,gBAAgB,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3D;;;;;OAKG;IACH,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB;;;;;;;;;;;OAWG;IACH,SAAS,CAAC,EAAE,uBAAuB,CAAC;IACpC;;;;;OAKG;IACH,OAAO,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;CACjE;AAED,uEAAuE;AACvE,MAAM,WAAW,aAAa;IAC5B,iEAAiE;IACjE,IAAI,EAAE,MAAM,CAAC;IACb,6DAA6D;IAC7D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,sDAAsD;IACtD,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,qBAAqB;IACpC,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,kBAAkB,EAAE,CAAC;IAC3B,4GAA4G;IAC5G,oBAAoB,EAAE,MAAM,CAAC;CAC9B;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,qBAAqB;IACpC,4FAA4F;IAC5F,IAAI,EAAE,OAAO,CAAC;IACd;;;;OAIG;IACH,UAAU,CAAC,EAAE;QAAE,MAAM,EAAE,gBAAgB,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3D,8HAA8H;IAC9H,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,qCAAqC;IACrC,SAAS,CAAC,EAAE,OAAO,GAAG,SAAS,GAAG,SAAS,CAAC;IAC5C;oFACgF;IAChF,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IAChE;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,uBAAuB,CAAC;CAClC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,qBAAqB,CAAC,GAAG,SAAS,EACpD,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,qBAAqB,CAAC,GAC7C,GAAG,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAsBpC;AAED;;;;;;;;GAQG;AACH,MAAM,MAAM,eAAe,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC;AAEhE,4HAA4H;AAC5H,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,MAAM,EAAE,CAGvF;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,uBAAuB;IACtC,gEAAgE;IAChE,KAAK,EAAE,MAAM,CAAC;IACd,wBAAwB;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,iFAAiF;IACjF,MAAM,EAAE,MAAM,CAAC;IACf,+DAA+D;IAC/D,UAAU,EAAE,MAAM,CAAC;CACpB;AAgDD;;;;;;;;;;;GAWG;AACH,wBAAgB,yBAAyB,CACvC,EAAE,EAAE,SAAS,EACb,WAAW,CAAC,EAAE,eAAe,GAC5B,GAAG,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAiDpC;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAC7B,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,aAAa,EAAE,EACxB,IAAI,CAAC,EAAE;IACL,YAAY,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAC;IAClD,2DAA2D;IAC3D,cAAc,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAC/C,+MAA+M;IAC/M,oBAAoB,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;IACxD,6KAA6K;IAC7K,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;CAC1B,GACA,kBAAkB,EAAE,CA2EtB;AAED;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,sEAAsE;IACtE,IAAI,EAAE,OAAO,CAAC;CACf;AAED,oGAAoG;AACpG,wBAAgB,yBAAyB,CACvC,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,aAAa,EAAE,CAAA;CAAE,EAChD,IAAI,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,aAAa,EAAE,CAAA;CAAE,GAC/C,kBAAkB,CAWpB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intentius/chant",
3
- "version": "0.41.11",
3
+ "version": "0.41.13",
4
4
  "description": "Declarative infrastructure-as-code toolkit — TypeScript on Node.js",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://intentius.io/chant",
@@ -286,10 +286,17 @@ async function observeComponentStacks(
286
286
  // palette than the reconciliation verdict. One cfn-deploy stack per component
287
287
  // is the norm; a multi-stack component reports its representative unit.
288
288
  const repr = determinate.find((o) => o.present) ?? determinate[0];
289
+ // Some-but-not-all present is its own answer (#1528): `live` stays false —
290
+ // deployed means all of it — but the split and the missing unit names ride
291
+ // along, so the row can say "half up" instead of "nothing observed live"
292
+ // while its own `stack` field shows a healthy unit.
293
+ const presentCount = determinate.filter((o) => o.present).length;
294
+ const missing = determinate.filter((o) => !o.present).map((o) => o.stack);
289
295
  evidence.set(name, {
290
296
  live: present,
291
297
  ownership: present ? "owned" : undefined,
292
298
  stack: { name: repr.stack, status: repr.status, healthy: repr.healthy },
299
+ ...(presentCount > 0 && !present ? { partial: { present: presentCount, total: determinate.length, missing } } : {}),
293
300
  });
294
301
  }
295
302
  return evidence;
@@ -549,3 +549,47 @@ describe("status", () => {
549
549
  });
550
550
  });
551
551
  });
552
+
553
+ describe("partial unit presence is said, not rounded down to nothing (#1528)", () => {
554
+ // The lie this fixes, verbatim from a real estate: a three-unit component
555
+ // (CRDs by kubectl-apply, two Helm releases) whose one absent unit produced
556
+ // `detail: "no release record and nothing observed live"` in the same row
557
+ // whose `stack` field showed a deployed, healthy release.
558
+ const partial = { present: 2, total: 3, missing: ["kmv-crds"] };
559
+
560
+ test("unrecorded + partial names the split and the missing units", () => {
561
+ const liveEvidence = new Map<string, LiveComponentEvidence>([
562
+ ["operator", { live: false, partial, stack: { name: "cert-manager", status: "deployed", healthy: true } }],
563
+ ]);
564
+ const rows = reconcileStatus("prod", [], { liveEvidence, allComponents: ["operator"] });
565
+ expect(rows[0].reconciliation).toBe("unrecorded");
566
+ expect(rows[0].detail).toContain("2 of 3 deploy units observed live");
567
+ expect(rows[0].detail).toContain("missing: kmv-crds");
568
+ expect(rows[0].detail).not.toContain("nothing observed live");
569
+ expect(rows[0].partial).toEqual(partial);
570
+ });
571
+
572
+ test("recorded + partial is stale, with the split instead of 'nothing'", () => {
573
+ const liveEvidence = new Map<string, LiveComponentEvidence>([
574
+ ["search-service", { live: false, partial }],
575
+ ]);
576
+ const rows = reconcileStatus("prod", [record()], { liveEvidence });
577
+ expect(rows[0].reconciliation).toBe("stale");
578
+ expect(rows[0].detail).toContain("only 2 of 3 deploy units observed live now");
579
+ expect(rows[0].detail).toContain("missing: kmv-crds");
580
+ });
581
+
582
+ test("wholly absent still reads as nothing observed live, no split invented", () => {
583
+ const liveEvidence = new Map<string, LiveComponentEvidence>([["gone", { live: false }]]);
584
+ const rows = reconcileStatus("prod", [], { liveEvidence, allComponents: ["gone"] });
585
+ expect(rows[0].detail).toContain("no release record and nothing observed live");
586
+ expect(rows[0].partial).toBeUndefined();
587
+ });
588
+
589
+ test("the split survives mergeLiveEvidence's field-by-field rebuild", () => {
590
+ const base = new Map<string, LiveComponentEvidence>([["operator", { live: false }]]);
591
+ const supplement = new Map<string, LiveComponentEvidence>([["operator", { live: false, partial }]]);
592
+ const merged = mergeLiveEvidence(base, supplement);
593
+ expect(merged.get("operator")!.partial).toEqual(partial);
594
+ });
595
+ });
@@ -99,6 +99,13 @@ export interface ComponentStatusRow {
99
99
  * where it exists.
100
100
  */
101
101
  resources?: ComponentResourceRollup;
102
+ /**
103
+ * Set when some but not all of this component's deploy units were observed
104
+ * present (#1528). `live` is `false` — deployed means all of it — but a
105
+ * consumer painting the row can distinguish "half up" from "gone", and the
106
+ * missing unit names are the actionable part.
107
+ */
108
+ partial?: { present: number; total: number; missing: string[] };
102
109
  }
103
110
 
104
111
  /** A component's owning deploy unit and its provider-native status. */
@@ -142,6 +149,15 @@ export interface LiveComponentEvidence {
142
149
  /** The owning deploy unit's raw status, when observed (AWS: the component's own
143
150
  * CFN stack). Surfaced onto `ComponentStatusRow.stack` for a richer palette. */
144
151
  stack?: LiveStackInfo;
152
+ /**
153
+ * Set when some but not all of the component's deploy units were observed
154
+ * present (#1528). `live` is `false` — a component is deployed when all of
155
+ * it is — but "nothing observed live" would be a lie, and it was one: a
156
+ * multi-unit component with a single absent unit reported exactly that
157
+ * while its Helm releases sat deployed and healthy in the same row's
158
+ * `stack` field. Consumers get the split and the names of what is missing.
159
+ */
160
+ partial?: { present: number; total: number; missing: string[] };
145
161
  /**
146
162
  * How the component's own resources answered, before any merge collapsed
147
163
  * them (behold#98). Always present when evidence exists — a component that
@@ -189,6 +205,9 @@ export function mergeLiveEvidence(
189
205
  ownership: sup.ownership ?? b?.ownership,
190
206
  action: b?.action,
191
207
  stack: sup.stack ?? b?.stack,
208
+ // The unit split rides the same direct observation `live` came from —
209
+ // dropping it here would resurrect the lie the field exists to fix.
210
+ ...(sup.partial ? { partial: sup.partial } : {}),
192
211
  // Base first: the counts come from the change set, and a stack
193
212
  // observation has none to offer.
194
213
  ...(b?.rollup ?? sup.rollup ? { rollup: b?.rollup ?? sup.rollup } : {}),
@@ -408,10 +427,16 @@ export function reconcileStatus(
408
427
  detail = `live${evidence.ownership === "owned" ? " and chant-owned" : ""}, but no release record exists — deployed outside the recorded path`;
409
428
  } else if (!recorded && !evidence?.live) {
410
429
  reconciliation = "unrecorded";
411
- detail = "no release record and nothing observed live";
430
+ // "Nothing observed live" was a lie for a partially-present component
431
+ // (#1528) — say what was seen and name what was not.
432
+ detail = evidence?.partial
433
+ ? `no release record; ${evidence.partial.present} of ${evidence.partial.total} deploy units observed live (missing: ${evidence.partial.missing.join(", ")})`
434
+ : "no release record and nothing observed live";
412
435
  } else if (recorded && !evidence?.live) {
413
436
  reconciliation = "stale";
414
- detail = `recorded ${recorded.timestamp} (digest ${recorded.digest}), but nothing observed live now`;
437
+ detail = evidence?.partial
438
+ ? `recorded ${recorded.timestamp} (digest ${recorded.digest}), but only ${evidence.partial.present} of ${evidence.partial.total} deploy units observed live now (missing: ${evidence.partial.missing.join(", ")})`
439
+ : `recorded ${recorded.timestamp} (digest ${recorded.digest}), but nothing observed live now`;
415
440
  } else if (recorded && evidence?.action === "update") {
416
441
  reconciliation = "drifted";
417
442
  detail = `recorded ${recorded.timestamp} (digest ${recorded.digest}), but live configuration has drifted since`;
@@ -435,6 +460,7 @@ export function reconcileStatus(
435
460
  ...(evidence?.unobserved ? { unobserved: evidence.unobserved } : {}),
436
461
  ...(evidence?.stack ? { stack: evidence.stack } : {}),
437
462
  ...(evidence?.rollup ? { resources: evidence.rollup } : {}),
463
+ ...(evidence?.partial ? { partial: evidence.partial } : {}),
438
464
  });
439
465
  }
440
466