@intentius/chant 0.18.0 → 0.18.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"graph.d.ts","sourceRoot":"","sources":["../../../src/cli/handlers/graph.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAGlD;;;;;;;GAOG;AACH,wBAAsB,QAAQ,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAcnE"}
1
+ {"version":3,"file":"graph.d.ts","sourceRoot":"","sources":["../../../src/cli/handlers/graph.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAGlD;;;;;;;GAOG;AACH,wBAAsB,QAAQ,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAcnE"}
@@ -1 +1 @@
1
- {"version":3,"file":"lifecycle.d.ts","sourceRoot":"","sources":["../../../src/cli/handlers/lifecycle.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAoBlD;;GAEG;AACH,wBAAsB,oBAAoB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAwE/E;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAoC3E;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAmC3E;AA0OD;;;;;;;GAOG;AACH,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CA6G3E;AAED;;GAEG;AACH,wBAAsB,eAAe,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAiB1E;AAED;;GAEG;AACH,wBAAsB,mBAAmB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAM9E;AAkBD;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CA+C/E"}
1
+ {"version":3,"file":"lifecycle.d.ts","sourceRoot":"","sources":["../../../src/cli/handlers/lifecycle.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAoBlD;;GAEG;AACH,wBAAsB,oBAAoB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAwE/E;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAoC3E;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CA+C3E;AA2SD;;;;;;;GAOG;AACH,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CA6G3E;AAED;;GAEG;AACH,wBAAsB,eAAe,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAiB1E;AAED;;GAEG;AACH,wBAAsB,mBAAmB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAM9E;AAkBD;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CA+C/E"}
@@ -1 +1 @@
1
- {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../src/cli/main.ts"],"names":[],"mappings":";AAKA,OAAO,EAAmC,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAiB9E;;GAEG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,UAAU,CAuLpD"}
1
+ {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../src/cli/main.ts"],"names":[],"mappings":";AAKA,OAAO,EAAmC,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAiB9E;;GAEG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,UAAU,CA2LpD"}
@@ -60,6 +60,10 @@ export interface ParsedArgs {
60
60
  /** `chant graph --live --overlay` — classify the provisioned graph against
61
61
  * declared source: managed / foreign / pending (#780). */
62
62
  overlay?: boolean;
63
+ /** `chant lifecycle diff <env> --between <refA> <refB>` (#822) — diff two saved
64
+ * snapshots (orphan-branch commits from `lifecycle log`) against each other. */
65
+ betweenA?: string;
66
+ betweenB?: string;
63
67
  /** `chant graph --live --overlay --overlay-anchor <source|live>` (#821) — which
64
68
  * graph is the canvas. `source` (default) keeps the declared edges (the
65
69
  * cross-substrate topology) and joins live status per node; `live` keeps the
@@ -1 +1 @@
1
- {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/cli/registry.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/cli/registry.ts"],"names":[],"mappings":"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"}
@@ -46,6 +46,15 @@ export declare function writeSnapshot(environment: string, lexicon: string, json
46
46
  export declare function readSnapshot(environment: string, lexicon: string, opts?: {
47
47
  cwd?: string;
48
48
  }): Promise<string | null>;
49
+ /**
50
+ * Read a snapshot at a specific orphan-branch commit (#822). `readSnapshot` reads
51
+ * the branch tip; this reads `<ref>:<env>/<lexicon>.json` for any commit `ref`
52
+ * (as listed by {@link listSnapshots}), so two historical snapshots can be diffed.
53
+ * Returns null if that env/lexicon wasn't captured at `ref`.
54
+ */
55
+ export declare function readSnapshotAt(environment: string, lexicon: string, ref: string, opts?: {
56
+ cwd?: string;
57
+ }): Promise<string | null>;
49
58
  /**
50
59
  * Append one immutable release record line to `<environment>/releases.jsonl`
51
60
  * on the orphan branch (#568, epic #551 "Build & deploy observability"). Same
@@ -1 +1 @@
1
- {"version":3,"file":"git.d.ts","sourceRoot":"","sources":["../../src/lifecycle/git.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"git.d.ts","sourceRoot":"","sources":["../../src/lifecycle/git.ts"],"names":[],"mappings":"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"}
@@ -46,6 +46,24 @@ export interface DiffLiveInput {
46
46
  /** Resources captured by the previous snapshot, if any. */
47
47
  observedThen: Record<string, ResourceMetadata> | undefined;
48
48
  }
49
+ /** The delta between two saved snapshots (#822): a two-way observed diff. */
50
+ export interface SnapshotDiffResult {
51
+ /** In `next`, not in `prev`. */
52
+ added: string[];
53
+ /** In `prev`, not in `next`. */
54
+ removed: string[];
55
+ /** In both; metadata differs (with the attribute-level changes). */
56
+ changed: ResourceDrift[];
57
+ /** In both; metadata identical. */
58
+ unchanged: string[];
59
+ }
60
+ /**
61
+ * Diff two **observed** snapshots (#822) — `prev` vs `next`, each a resources map
62
+ * read from the orphan branch. A two-way diff (no "declared" axis), so it answers
63
+ * "what changed in the cloud between two points" independent of source. Pure and
64
+ * deterministic (results sorted). Feeds the deployment-lanes frame-pair diff.
65
+ */
66
+ export declare function diffSnapshots(prev: Record<string, ResourceMetadata>, next: Record<string, ResourceMetadata>): SnapshotDiffResult;
49
67
  export declare function diffLive(input: DiffLiveInput): LiveDiffResult;
50
68
  export interface LiveArtifactDiffResult {
51
69
  /** Observed now, not in previous snapshot. */
@@ -1 +1 @@
1
- {"version":3,"file":"live-diff.d.ts","sourceRoot":"","sources":["../../src/lifecycle/live-diff.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAErE,MAAM,WAAW,eAAe;IAC9B,2EAA2E;IAC3E,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,eAAe,EAAE,CAAC;CAC5B;AAED,MAAM,WAAW,cAAc;IAC7B,sEAAsE;IACtE,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,qDAAqD;IACrD,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,mDAAmD;IACnD,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,mEAAmE;IACnE,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,oDAAoD;IACpD,oBAAoB,EAAE,aAAa,EAAE,CAAC;IACtC,sDAAsD;IACtD,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,aAAa;IAC5B,2CAA2C;IAC3C,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACtB,oEAAoE;IACpE,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAC9C,2DAA2D;IAC3D,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,GAAG,SAAS,CAAC;CAC5D;AAyCD,wBAAgB,QAAQ,CAAC,KAAK,EAAE,aAAa,GAAG,cAAc,CAiE7D;AAID,MAAM,WAAW,sBAAsB;IACrC,8CAA8C;IAC9C,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,8CAA8C;IAC9C,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,iCAAiC;IACjC,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,mCAAmC;IACnC,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,sBAAsB;IACrC,gEAAgE;IAChE,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAC9C,2DAA2D;IAC3D,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,GAAG,SAAS,CAAC;CAC5D;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,sBAAsB,GAAG,sBAAsB,CAmCvF"}
1
+ {"version":3,"file":"live-diff.d.ts","sourceRoot":"","sources":["../../src/lifecycle/live-diff.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAErE,MAAM,WAAW,eAAe;IAC9B,2EAA2E;IAC3E,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,eAAe,EAAE,CAAC;CAC5B;AAED,MAAM,WAAW,cAAc;IAC7B,sEAAsE;IACtE,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,qDAAqD;IACrD,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,mDAAmD;IACnD,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,mEAAmE;IACnE,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,oDAAoD;IACpD,oBAAoB,EAAE,aAAa,EAAE,CAAC;IACtC,sDAAsD;IACtD,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,aAAa;IAC5B,2CAA2C;IAC3C,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACtB,oEAAoE;IACpE,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAC9C,2DAA2D;IAC3D,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,GAAG,SAAS,CAAC;CAC5D;AAyCD,6EAA6E;AAC7E,MAAM,WAAW,kBAAkB;IACjC,gCAAgC;IAChC,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,gCAAgC;IAChC,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,oEAAoE;IACpE,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,mCAAmC;IACnC,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,EACtC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,GACrC,kBAAkB,CAiBpB;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,aAAa,GAAG,cAAc,CAiE7D;AAID,MAAM,WAAW,sBAAsB;IACrC,8CAA8C;IAC9C,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,8CAA8C;IAC9C,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,iCAAiC;IACjC,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,mCAAmC;IACnC,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,sBAAsB;IACrC,gEAAgE;IAChE,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAC9C,2DAA2D;IAC3D,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,GAAG,SAAS,CAAC;CAC5D;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,sBAAsB,GAAG,sBAAsB,CAmCvF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intentius/chant",
3
- "version": "0.18.0",
3
+ "version": "0.18.2",
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",
@@ -26,6 +26,28 @@ vi.mock("../../graph-layout", () => ({
26
26
  getLayoutEngine: (name?: string) => ({ name: name ?? "dagre", layout: (input: unknown) => layoutMock(input) }),
27
27
  }));
28
28
 
29
+ // --live path deps. `graph` isn't `requiresPlugins`, so the live handler loads
30
+ // plugins itself; mock that plus observation/build/config (existing tests only
31
+ // hit the Op-graph and source-view modes, so these mocks don't touch them).
32
+ const loadPluginsMock = vi.fn();
33
+ const resolveLexMock = vi.fn();
34
+ vi.mock("../plugins", () => ({
35
+ loadPlugins: (...a: unknown[]) => loadPluginsMock(...a),
36
+ resolveProjectLexicons: (...a: unknown[]) => resolveLexMock(...a),
37
+ }));
38
+ const observeMock = vi.fn();
39
+ vi.mock("../../lifecycle/observe", () => ({
40
+ observeResources: (...a: unknown[]) => observeMock(...a),
41
+ }));
42
+ vi.mock("../../config", () => ({
43
+ loadChantConfig: () => Promise.resolve({ config: {} }),
44
+ }));
45
+ vi.mock("../../build", () => ({
46
+ build: () => Promise.resolve({ errors: [] }),
47
+ partitionByLexicon: () => ({}),
48
+ computeStackGraph: () => ({}),
49
+ }));
50
+
29
51
  const { runGraph } = await import("./graph");
30
52
 
31
53
  function makeArgs(overrides: Partial<ParsedArgs> = {}): ParsedArgs {
@@ -204,4 +226,26 @@ describe("runGraph", () => {
204
226
  expect(stderrBuf.join("\n")).toContain("boom");
205
227
  });
206
228
  });
229
+
230
+ describe("live graph (--live)", () => {
231
+ // Regression: `graph` is not `requiresPlugins`, so `ctx.plugins` is empty. The
232
+ // live path must load the project's plugins itself — otherwise it wrongly
233
+ // reports "No lexicons implement describeResources" and observes nothing.
234
+ test("loads plugins for --live when ctx.plugins is empty", async () => {
235
+ resolveLexMock.mockResolvedValue(["aws"]);
236
+ loadPluginsMock.mockResolvedValue([
237
+ { name: "aws", serializer: {}, describeResources: () => Promise.resolve({}) },
238
+ ]);
239
+ observeMock.mockResolvedValue({
240
+ observations: [{ lexicon: "aws", resources: { "web-vpc": { type: "AWS::EC2::VPC", status: "OK" } } }],
241
+ errors: [],
242
+ });
243
+ const exit = await runGraph({ args: makeArgs({ format: "ir", live: true, env: "prod" }), plugins: [], serializers: [] });
244
+ expect(exit).toBe(0);
245
+ expect(loadPluginsMock).toHaveBeenCalled();
246
+ const out = stdoutBuf.join("\n");
247
+ expect(out).not.toContain("No lexicons implement describeResources");
248
+ expect(out).toContain("web-vpc");
249
+ });
250
+ });
207
251
  });
@@ -12,6 +12,7 @@ import { toMermaid } from "../../graph-mermaid";
12
12
  import { toDot } from "../../graph-dot";
13
13
  import { getLayoutEngine, toLayoutInput, type NodeSize } from "../../graph-layout";
14
14
  import { lintCommand } from "../commands/lint";
15
+ import { loadPlugins, resolveProjectLexicons } from "../plugins";
15
16
  import { readFileSync } from "node:fs";
16
17
  import { formatError, formatWarning, formatBold } from "../format";
17
18
  import type { CommandContext } from "../registry";
@@ -53,7 +54,7 @@ async function runGraphLive(
53
54
  ctx: CommandContext,
54
55
  format: "ir" | "mermaid" | "dot" | "layout",
55
56
  ): Promise<number> {
56
- const { args, plugins } = ctx;
57
+ const { args } = ctx;
57
58
  const environment = args.env;
58
59
  if (!environment) {
59
60
  console.error(formatError({ message: "chant graph --live needs an environment: --live --env <name>" }));
@@ -62,6 +63,10 @@ async function runGraphLive(
62
63
 
63
64
  const projectPath = resolve(".");
64
65
  const { config } = await loadChantConfig(projectPath);
66
+ // `graph` is not `requiresPlugins` (Op/source-graph modes must work without a
67
+ // lexicon), so `ctx.plugins` is empty. The live path needs the project's
68
+ // observation plugins — load them here, mirroring the lifecycle handlers.
69
+ const plugins = ctx.plugins.length > 0 ? ctx.plugins : await loadPlugins(await resolveProjectLexicons(projectPath));
65
70
  if (config.environments && !config.environments.includes(environment)) {
66
71
  console.error(formatError({
67
72
  message: `Unknown environment "${environment}"`,
@@ -1,9 +1,9 @@
1
1
  import { resolve } from "node:path";
2
2
  import { build } from "../../build";
3
3
  import { takeSnapshot } from "../../lifecycle/snapshot";
4
- import { readSnapshot, readEnvironmentSnapshots, listSnapshots, fetchLifecycle, StaleLifecycleBranchError } from "../../lifecycle/git";
4
+ import { readSnapshot, readSnapshotAt, readEnvironmentSnapshots, listSnapshots, fetchLifecycle, StaleLifecycleBranchError } from "../../lifecycle/git";
5
5
  import { computeBuildDigest, diffDigests } from "../../lifecycle/digest";
6
- import { diffLive, diffLiveArtifacts, type LiveDiffResult, type LiveArtifactDiffResult } from "../../lifecycle/live-diff";
6
+ import { diffLive, diffLiveArtifacts, diffSnapshots, type LiveDiffResult, type LiveArtifactDiffResult, type SnapshotDiffResult } from "../../lifecycle/live-diff";
7
7
  import { buildChangeSet, renderChangeSet, gitlabMrReport, type ChangeSet } from "../../lifecycle/change-set";
8
8
  import { affectedStacks } from "../../lifecycle/affected";
9
9
  import { loadChantConfig } from "../../config";
@@ -159,6 +159,18 @@ export async function runLifecycleDiff(ctx: CommandContext): Promise<number> {
159
159
  return 1;
160
160
  }
161
161
 
162
+ // `--between <refA> <refB>` (#822): diff two saved snapshots against each other.
163
+ // Purely historical — no build, no cloud query; reads the orphan branch.
164
+ if (args.betweenA && args.betweenB) {
165
+ return runLifecycleDiffBetween({
166
+ environment,
167
+ lexiconFilter,
168
+ refA: args.betweenA,
169
+ refB: args.betweenB,
170
+ json: !!args.json,
171
+ });
172
+ }
173
+
162
174
  // Filter serializers to target lexicon before building
163
175
  const targetSerializers = lexiconFilter
164
176
  ? plugins.filter((p) => p.name === lexiconFilter).map((p) => p.serializer)
@@ -186,6 +198,71 @@ export async function runLifecycleDiff(ctx: CommandContext): Promise<number> {
186
198
  return runLifecycleDiffDigest({ environment, lexicons, buildResult });
187
199
  }
188
200
 
201
+ interface BetweenDiffArgs {
202
+ environment: string;
203
+ lexiconFilter?: string;
204
+ refA: string;
205
+ refB: string;
206
+ json: boolean;
207
+ }
208
+
209
+ /**
210
+ * `chant lifecycle diff <env> --between <refA> <refB>` (#822). Diffs two saved
211
+ * snapshots against each other — read-only, no build, no cloud query. Reads
212
+ * `<ref>:<env>/<lexicon>.json` from the orphan branch at each ref and reports the
213
+ * two-way delta per lexicon.
214
+ */
215
+ async function runLifecycleDiffBetween(args: BetweenDiffArgs): Promise<number> {
216
+ await fetchLifecycle();
217
+ const { config } = await loadChantConfig(resolve("."));
218
+ const lexicons = args.lexiconFilter ? [args.lexiconFilter] : config.lexicons ?? [];
219
+ if (lexicons.length === 0) {
220
+ console.error(formatError({ message: "No lexicons to diff — pass a lexicon or declare `lexicons` in chant.config." }));
221
+ return 1;
222
+ }
223
+
224
+ const parseResources = (content: string | null): Record<string, ResourceMetadata> | null => {
225
+ if (content === null) return null;
226
+ try {
227
+ return (JSON.parse(content) as LifecycleSnapshot).resources ?? {};
228
+ } catch {
229
+ return null;
230
+ }
231
+ };
232
+
233
+ const byLexicon: Record<string, SnapshotDiffResult> = {};
234
+ for (const lexicon of lexicons) {
235
+ const prev = parseResources(await readSnapshotAt(args.environment, lexicon, args.refA));
236
+ const next = parseResources(await readSnapshotAt(args.environment, lexicon, args.refB));
237
+ if (prev === null && next === null) {
238
+ if (!args.json) console.error(formatWarning({ message: `${lexicon}: not captured at either ref — skipping` }));
239
+ continue;
240
+ }
241
+ byLexicon[lexicon] = diffSnapshots(prev ?? {}, next ?? {});
242
+ }
243
+
244
+ if (args.json) {
245
+ console.log(JSON.stringify({ environment: args.environment, refA: args.refA, refB: args.refB, lexicons: byLexicon }, null, 2));
246
+ return 0;
247
+ }
248
+
249
+ if (Object.keys(byLexicon).length === 0) {
250
+ console.log(`No snapshots for ${args.environment} at ${args.refA} / ${args.refB}.`);
251
+ return 0;
252
+ }
253
+
254
+ console.log(formatBold(`Snapshot diff — ${args.environment}: ${args.refA} → ${args.refB}`));
255
+ for (const [lexicon, d] of Object.entries(byLexicon)) {
256
+ console.log(
257
+ `\n${formatBold(lexicon)} — ${d.added.length} added, ${d.removed.length} removed, ${d.changed.length} changed, ${d.unchanged.length} unchanged`,
258
+ );
259
+ for (const name of d.added) console.log(` + ${name}`);
260
+ for (const name of d.removed) console.log(` - ${name}`);
261
+ for (const drift of d.changed) console.log(` ~ ${drift.name} (${drift.changes.map((c) => c.path).join(", ")})`);
262
+ }
263
+ return 0;
264
+ }
265
+
189
266
  interface DigestDiffArgs {
190
267
  environment: string;
191
268
  lexicons: string[];
package/src/cli/main.ts CHANGED
@@ -94,6 +94,10 @@ export function parseArgs(args: string[]): ParsedArgs {
94
94
  result.live = true;
95
95
  } else if (arg === "--overlay") {
96
96
  result.overlay = true;
97
+ } else if (arg === "--between") {
98
+ result.betweenA = args[++i];
99
+ result.betweenB = args[++i];
100
+ if (!result.betweenA || !result.betweenB) throw new Error("--between needs two snapshot refs: --between <refA> <refB>");
97
101
  } else if (arg === "--overlay-anchor") {
98
102
  const v = args[++i];
99
103
  if (v !== "source" && v !== "live") throw new Error(`--overlay-anchor must be 'source' or 'live', got '${v}'`);
@@ -61,6 +61,10 @@ export interface ParsedArgs {
61
61
  /** `chant graph --live --overlay` — classify the provisioned graph against
62
62
  * declared source: managed / foreign / pending (#780). */
63
63
  overlay?: boolean;
64
+ /** `chant lifecycle diff <env> --between <refA> <refB>` (#822) — diff two saved
65
+ * snapshots (orphan-branch commits from `lifecycle log`) against each other. */
66
+ betweenA?: string;
67
+ betweenB?: string;
64
68
  /** `chant graph --live --overlay --overlay-anchor <source|live>` (#821) — which
65
69
  * graph is the canvas. `source` (default) keeps the declared edges (the
66
70
  * cross-substrate topology) and joins live status per node; `live` keeps the
@@ -6,6 +6,7 @@ import { join } from "node:path";
6
6
  import {
7
7
  writeSnapshot,
8
8
  readSnapshot,
9
+ readSnapshotAt,
9
10
  readEnvironmentSnapshots,
10
11
  listSnapshots,
11
12
  getHeadCommit,
@@ -56,6 +57,21 @@ describe("lifecycle/git", () => {
56
57
  });
57
58
  });
58
59
 
60
+ test("readSnapshotAt reads a snapshot at a historical orphan-branch commit (#822)", async () => {
61
+ await withTestDir(async (dir) => {
62
+ await initRepo(dir);
63
+ await writeSnapshot("prod", "aws", JSON.stringify({ resources: { vpc: { type: "T", status: "OK" } } }), { cwd: dir });
64
+ await writeSnapshot("prod", "aws", JSON.stringify({ resources: { vpc: { type: "T", status: "OK" }, subnet: { type: "S", status: "OK" } } }), { cwd: dir });
65
+ const snaps = await listSnapshots({ cwd: dir }); // newest-first
66
+ expect(snaps.length).toBeGreaterThanOrEqual(2);
67
+ const older = await readSnapshotAt("prod", "aws", snaps[1].commit, { cwd: dir });
68
+ const newer = await readSnapshotAt("prod", "aws", snaps[0].commit, { cwd: dir });
69
+ expect(Object.keys(JSON.parse(older!).resources)).toEqual(["vpc"]);
70
+ expect(Object.keys(JSON.parse(newer!).resources).sort()).toEqual(["subnet", "vpc"]);
71
+ expect(await readSnapshotAt("prod", "gcp", snaps[0].commit, { cwd: dir })).toBeNull();
72
+ });
73
+ });
74
+
59
75
  test("subsequent writes preserve other env+lexicon entries", async () => {
60
76
  await withTestDir(async (dir) => {
61
77
  await initRepo(dir);
@@ -166,6 +166,27 @@ export async function readSnapshot(
166
166
  return readBlobFromPath(environment, `${lexicon}.json`, opts);
167
167
  }
168
168
 
169
+ /**
170
+ * Read a snapshot at a specific orphan-branch commit (#822). `readSnapshot` reads
171
+ * the branch tip; this reads `<ref>:<env>/<lexicon>.json` for any commit `ref`
172
+ * (as listed by {@link listSnapshots}), so two historical snapshots can be diffed.
173
+ * Returns null if that env/lexicon wasn't captured at `ref`.
174
+ */
175
+ export async function readSnapshotAt(
176
+ environment: string,
177
+ lexicon: string,
178
+ ref: string,
179
+ opts?: { cwd?: string },
180
+ ): Promise<string | null> {
181
+ const rt = getRuntime();
182
+ const result = await rt.spawn(
183
+ ["git", "show", `${ref}:${environment}/${lexicon}.json`],
184
+ { cwd: opts?.cwd },
185
+ );
186
+ if (result.exitCode !== 0) return null;
187
+ return result.stdout;
188
+ }
189
+
169
190
  /**
170
191
  * Append one immutable release record line to `<environment>/releases.jsonl`
171
192
  * on the orphan branch (#568, epic #551 "Build & deploy observability"). Same
@@ -1,5 +1,5 @@
1
1
  import { describe, test, expect } from "vitest";
2
- import { diffLive, diffLiveArtifacts } from "./live-diff";
2
+ import { diffLive, diffLiveArtifacts, diffSnapshots } from "./live-diff";
3
3
  import type { ResourceMetadata, ArtifactMetadata } from "../lexicon";
4
4
 
5
5
  const meta = (overrides: Partial<ResourceMetadata> = {}): ResourceMetadata => ({
@@ -182,3 +182,30 @@ describe("diffLiveArtifacts", () => {
182
182
  expect(result.unchanged).toEqual(["release/default/b"]);
183
183
  });
184
184
  });
185
+
186
+ describe("diffSnapshots (#822)", () => {
187
+ test("classifies added / removed / changed / unchanged between two snapshots", () => {
188
+ const prev = {
189
+ vpc: meta({ physicalId: "vpc-1" }),
190
+ subnet: meta({ physicalId: "subnet-1", status: "CREATE_COMPLETE" }),
191
+ gone: meta({ physicalId: "old" }),
192
+ };
193
+ const next = {
194
+ vpc: meta({ physicalId: "vpc-1" }), // unchanged
195
+ subnet: meta({ physicalId: "subnet-1", status: "UPDATE_COMPLETE" }), // changed (status)
196
+ fresh: meta({ physicalId: "new" }), // added
197
+ };
198
+ const d = diffSnapshots(prev, next);
199
+ expect(d.added).toEqual(["fresh"]);
200
+ expect(d.removed).toEqual(["gone"]);
201
+ expect(d.unchanged).toEqual(["vpc"]);
202
+ expect(d.changed.map((c) => c.name)).toEqual(["subnet"]);
203
+ expect(d.changed[0].changes.map((c) => c.path)).toContain("status");
204
+ });
205
+
206
+ test("empty vs empty is all-unchanged-empty; results are sorted", () => {
207
+ expect(diffSnapshots({}, {})).toEqual({ added: [], removed: [], changed: [], unchanged: [] });
208
+ const d = diffSnapshots({}, { b: meta(), a: meta() });
209
+ expect(d.added).toEqual(["a", "b"]);
210
+ });
211
+ });
@@ -90,6 +90,46 @@ function shallowEqual(a: unknown, b: unknown): boolean {
90
90
  return JSON.stringify(a) === JSON.stringify(b);
91
91
  }
92
92
 
93
+ /** The delta between two saved snapshots (#822): a two-way observed diff. */
94
+ export interface SnapshotDiffResult {
95
+ /** In `next`, not in `prev`. */
96
+ added: string[];
97
+ /** In `prev`, not in `next`. */
98
+ removed: string[];
99
+ /** In both; metadata differs (with the attribute-level changes). */
100
+ changed: ResourceDrift[];
101
+ /** In both; metadata identical. */
102
+ unchanged: string[];
103
+ }
104
+
105
+ /**
106
+ * Diff two **observed** snapshots (#822) — `prev` vs `next`, each a resources map
107
+ * read from the orphan branch. A two-way diff (no "declared" axis), so it answers
108
+ * "what changed in the cloud between two points" independent of source. Pure and
109
+ * deterministic (results sorted). Feeds the deployment-lanes frame-pair diff.
110
+ */
111
+ export function diffSnapshots(
112
+ prev: Record<string, ResourceMetadata>,
113
+ next: Record<string, ResourceMetadata>,
114
+ ): SnapshotDiffResult {
115
+ const added: string[] = [];
116
+ const removed: string[] = [];
117
+ const changed: ResourceDrift[] = [];
118
+ const unchanged: string[] = [];
119
+ for (const name of [...new Set([...Object.keys(prev), ...Object.keys(next)])].sort()) {
120
+ const a = prev[name];
121
+ const b = next[name];
122
+ if (!a && b) added.push(name);
123
+ else if (a && !b) removed.push(name);
124
+ else if (a && b) {
125
+ const changes = compareMetadata(a, b);
126
+ if (changes.length > 0) changed.push({ name, type: b.type ?? a.type, changes });
127
+ else unchanged.push(name);
128
+ }
129
+ }
130
+ return { added, removed, changed, unchanged };
131
+ }
132
+
93
133
  export function diffLive(input: DiffLiveInput): LiveDiffResult {
94
134
  const { declared, observedNow, observedThen } = input;
95
135
  const observedThenMap = observedThen ?? {};