@intentius/chant 0.54.2 → 0.55.0

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.
@@ -46,6 +46,17 @@ interface ValidatorResult {
46
46
  * Exported for testability.
47
47
  */
48
48
  export declare function tryValidateExternal(yamlText: string): ValidatorResult;
49
+ /**
50
+ * Format the migration report as Markdown, mirroring the output format
51
+ * prescribed by the upstream gitlab-org/ci-cd/github-actions-to-gitlab-ci
52
+ * skill: overview, classification, diagnostic table, aggregated manual
53
+ * setup steps, suggested GitLab improvements, caveats.
54
+ *
55
+ * The same data backs the SARIF report (via formatSarif); this is the
56
+ * human-readable surface. Exported so downstream migrate consumers can render
57
+ * the same report without reimplementing it (#2066).
58
+ */
59
+ export declare function formatMarkdownSummary(provenance: Array<Record<string, unknown>>, diagnostics: Array<Record<string, unknown>>, sourceContent?: string): string;
49
60
  export declare function printMigrateResult(result: MigrateCliResult): void;
50
61
  export {};
51
62
  //# sourceMappingURL=migrate.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"migrate.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/migrate.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAGnD,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,OAAO,CAAC;IAClB,aAAa,EAAE,OAAO,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,aAAa,EAAE,CAAC;CAC1B;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,oCAAoC;IACpC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,6BAA6B;IAC7B,WAAW,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAC5C,4DAA4D;IAC5D,UAAU,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAC3C,uCAAuC;IACvC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,+CAA+C;IAC/C,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,wBAAsB,cAAc,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAsJpF;AAED,UAAU,eAAe;IACvB,GAAG,EAAE,OAAO,CAAC;IACb,EAAE,EAAE,OAAO,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC1B,MAAM,EAAE,MAAM,CAAC;CAChB;AASD;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,eAAe,CAUrE;AA6OD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,gBAAgB,GAAG,IAAI,CAajE"}
1
+ {"version":3,"file":"migrate.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/migrate.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAGnD,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,OAAO,CAAC;IAClB,aAAa,EAAE,OAAO,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,aAAa,EAAE,CAAC;CAC1B;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,oCAAoC;IACpC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,6BAA6B;IAC7B,WAAW,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAC5C,4DAA4D;IAC5D,UAAU,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAC3C,uCAAuC;IACvC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,+CAA+C;IAC/C,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,wBAAsB,cAAc,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAsJpF;AAED,UAAU,eAAe;IACvB,GAAG,EAAE,OAAO,CAAC;IACb,EAAE,EAAE,OAAO,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC1B,MAAM,EAAE,MAAM,CAAC;CAChB;AASD;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,eAAe,CAUrE;AAqBD;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CACnC,UAAU,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EAC1C,WAAW,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EAC3C,aAAa,CAAC,EAAE,MAAM,GACrB,MAAM,CAkER;AAyID,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,gBAAgB,GAAG,IAAI,CAajE"}
@@ -275,8 +275,22 @@ export interface CrossEnvComparison {
275
275
  envB: string;
276
276
  digestA?: string;
277
277
  digestB?: string;
278
- /** True only when both envs have a recorded digest and they match. */
278
+ /**
279
+ * True only when both envs have a recorded digest and their *comparable*
280
+ * identities match. For a record carrying `inputDigest` (a pinned helm
281
+ * deploy, chant#1242/#1243), that field is the comparable identity —
282
+ * `ReleaseRecord.inputDigest`'s own contract: profiles are per cluster, two
283
+ * environments legitimately render to different bytes, so cross-environment
284
+ * "is prod running what staging tested" joins on the input side while
285
+ * `digest` proves the exact bytes each cluster got. Records without it
286
+ * compare on `digest` exactly as before.
287
+ */
279
288
  same: boolean;
289
+ /** The identities `same` was decided on (`inputDigest ?? digest` per side), when both sides had one and they differ from the digests shown. */
290
+ comparedOn?: {
291
+ a: string;
292
+ b: string;
293
+ };
280
294
  }
281
295
  /** Compare the latest recorded digest for `component` between two environments' release records. */
282
296
  export declare function compareAcrossEnvironments(component: string, envA: {
@@ -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":"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"}
@@ -8,7 +8,10 @@ export interface OpDiscoveryResult {
8
8
  errors: string[];
9
9
  }
10
10
  /**
11
- * Discover all Op definitions from *.op.ts files under the git root.
11
+ * Discover all Op definitions from `*.op.ts` files under the nearest chant
12
+ * project root (the directory holding `chant.config.ts`/`.json`, walking up
13
+ * from `cwd`), or under the git root when no config exists — see
14
+ * {@link findDiscoveryRoot} (#2058).
12
15
  */
13
16
  export declare function discoverOps(opts?: {
14
17
  cwd?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"discover.d.ts","sourceRoot":"","sources":["../../src/op/discover.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAExC,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,QAAQ,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,iBAAiB;IAChC,GAAG,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IAC/B,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAiCD;;GAEG;AACH,wBAAsB,WAAW,CAAC,IAAI,CAAC,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAuCrF"}
1
+ {"version":3,"file":"discover.d.ts","sourceRoot":"","sources":["../../src/op/discover.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAExC,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,QAAQ,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,iBAAiB;IAChC,GAAG,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IAC/B,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AA0DD;;;;;GAKG;AACH,wBAAsB,WAAW,CAAC,IAAI,CAAC,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAuCrF"}
@@ -57,6 +57,14 @@ export interface ChangeSet {
57
57
  org: string;
58
58
  /** All proposed changes, in stable order. */
59
59
  entries: ChangeSetEntry[];
60
+ /**
61
+ * Live entries under management across the diffed collections (#2067).
62
+ * `diffCollection` returns each collection's live count so the diff layer
63
+ * can sum it here. Optional — when absent, guardrails that want a live
64
+ * denominator (see {@link removalDeltaCap}) fall back to the plan-relative
65
+ * one.
66
+ */
67
+ managedCount?: number;
60
68
  }
61
69
  /** Options controlling diff behaviour. */
62
70
  export interface DiffOptions {
@@ -105,8 +113,11 @@ export interface DiffCollectionParams<D, L> {
105
113
  * when `compareFields` reports differences, and ownership-gated deletes for
106
114
  * live-not-desired. This is the selective-by-omission + ownership-gated-delete
107
115
  * pattern shared by every keyed-collection diff.
116
+ *
117
+ * Returns the live entry count for this collection, so the diff layer can sum
118
+ * `ChangeSet.managedCount` across the collections it diffs (#2067).
108
119
  */
109
- export declare function diffCollection<D, L>(params: DiffCollectionParams<D, L>): void;
120
+ export declare function diffCollection<D, L>(params: DiffCollectionParams<D, L>): number;
110
121
  /** Count entries per change kind. */
111
122
  export declare function summarizeChangeSet(cs: ChangeSet): Record<ChangeKind, number>;
112
123
  /** Human-readable plan summary for dry-run output. Pure. */
@@ -125,12 +136,30 @@ export type GuardrailResult = {
125
136
  ok: false;
126
137
  diagnostics: GuardrailDiagnostic[];
127
138
  };
128
- /** A guardrail check over a (rename-resolved) change set. Returns null when it passes. */
129
- export type GuardrailCheck = (resolved: ChangeSet) => GuardrailDiagnostic | null;
139
+ /** Live context threaded to guardrail checks by `runGuardrailChecks` (#2067). */
140
+ export interface GuardrailContext {
141
+ /** Live entries under management this cycle (see `ChangeSet.managedCount`). */
142
+ managedCount?: number;
143
+ }
144
+ /**
145
+ * A guardrail check over a (rename-resolved) change set. Returns null when it
146
+ * passes. `ctx` carries live context when the caller has it (#2067); a check
147
+ * written against the single-argument shape keeps working unchanged.
148
+ */
149
+ export type GuardrailCheck = (resolved: ChangeSet, ctx?: GuardrailContext) => GuardrailDiagnostic | null;
130
150
  /** Config for `removalDeltaCap`. */
131
151
  export interface RemovalDeltaCapOptions {
132
152
  /** Max fraction of pre-existing entries that may be deleted. Must be in (0,1]. Default 0.25. */
133
153
  maxFraction?: number;
154
+ /**
155
+ * Live managed-entry denominator (#2067). When positive, deletes are divided
156
+ * by this count of live entries under management instead of the plan's
157
+ * non-create entries — a converged cycle deleting one stale entry out of
158
+ * many no longer reads as 1/1 = 100%. Defaults to `changeSet.managedCount`;
159
+ * when neither is present (or not positive) the cap keeps its plan-relative
160
+ * behavior.
161
+ */
162
+ managedTotal?: number;
134
163
  }
135
164
  /**
136
165
  * Resolve rename aliases. A create entry carrying a `previously` key matching a
@@ -144,6 +173,11 @@ export declare function resolveRenames(changeSet: ChangeSet): ChangeSet;
144
173
  * (deletes + updates; creates excluded so a flood of new entries can't dilute
145
174
  * the delete fraction). Guards against a typo wiping the config in one apply.
146
175
  *
176
+ * With a live denominator (`opts.managedTotal`, else `changeSet.managedCount`;
177
+ * #2067) deletes are measured against the live entries under management
178
+ * instead — so a converged cycle's plan of exactly one stale delete does not
179
+ * trip at 1/1 = 100%. Without one, behavior is plan-relative as before.
180
+ *
147
181
  * CONTRACT: pass a RENAME-RESOLVED change set (see {@link resolveRenames}).
148
182
  */
149
183
  export declare function removalDeltaCap(changeSet: ChangeSet, opts?: RemovalDeltaCapOptions): GuardrailDiagnostic | null;
@@ -151,8 +185,10 @@ export declare function removalDeltaCap(changeSet: ChangeSet, opts?: RemovalDelt
151
185
  * Run a set of guardrail checks against a change set. Resolves renames ONCE,
152
186
  * then runs every check on the resolved set, aggregating any diagnostics. The
153
187
  * caller composes provider-specific checks (e.g. an admin floor) as closures.
188
+ * `ctx` (optional, #2067) threads live context — e.g. the managed-live count —
189
+ * to every check; single-argument checks ignore it.
154
190
  */
155
- export declare function runGuardrailChecks(changeSet: ChangeSet, checks: GuardrailCheck[]): GuardrailResult;
191
+ export declare function runGuardrailChecks(changeSet: ChangeSet, checks: GuardrailCheck[], ctx?: GuardrailContext): GuardrailResult;
156
192
  /** Controls how a cycle tracks its API usage against a shared request budget. */
157
193
  export interface RateBudget {
158
194
  /** Remaining request capacity for this run. */
@@ -1 +1 @@
1
- {"version":3,"file":"reconcile.d.ts","sourceRoot":"","sources":["../src/reconcile.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"reconcile.d.ts","sourceRoot":"","sources":["../src/reconcile.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../src/terraform/manifest.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../src/terraform/manifest.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intentius/chant",
3
- "version": "0.54.2",
3
+ "version": "0.55.0",
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",
@@ -1,5 +1,5 @@
1
1
  import { describe, test, expect } from "vitest";
2
- import { mkdtempSync, writeFileSync, rmSync, readFileSync, existsSync } from "fs";
2
+ import { mkdtempSync, mkdirSync, writeFileSync, rmSync, readFileSync, existsSync } from "fs";
3
3
  import { tmpdir } from "os";
4
4
  import { join } from "path";
5
5
  import { carveApply, formatCarveApply } from "./carve-apply";
@@ -108,6 +108,36 @@ describe("carveApply", () => {
108
108
  });
109
109
  });
110
110
 
111
+ test("a RELATIVE --output still records stampedFiles relative to the manifest's directory, not the cwd (#2059)", async () => {
112
+ if (!parserAvailable) return;
113
+ await withEstate(async (dir) => {
114
+ // The behold carve-demo shape: run FROM the project dir with
115
+ // `--output carveout`. The bug: resolveManifestFilePath joined against
116
+ // the relative outDir, producing a cwd-relative path ("carveout/…")
117
+ // that normalize-on-write passed through — two spellings in one manifest.
118
+ const out = join(dir, "carveout");
119
+ const manifest = manifestFor("aws_s3_bucket.assets", dir);
120
+ manifest.emit = { source: "tfstate", files: [join("src", "assets.ts")], at: "t" };
121
+ writeCarveManifest(out, manifest);
122
+ mkdirSync(join(out, "src"), { recursive: true });
123
+ writeFileSync(join(out, "src", "assets.ts"), 'export const assets = new Bucket({\n BucketName: "b",\n});\n');
124
+
125
+ const prevCwd = process.cwd();
126
+ process.chdir(dir);
127
+ try {
128
+ const res = await carveApply({ from: ".", output: "carveout", env: "prod", stack: "assets", writeSource: true });
129
+ expect(res.ok).toBe(true);
130
+ const m = readCarveManifest(res.manifestPath!)!;
131
+ // One spelling: manifest-dir-relative, same as emit.files — never
132
+ // "carveout/src/assets.ts" (relative to wherever the command ran).
133
+ expect(m.apply!.stampedFiles).toEqual([join("src", "assets.ts")]);
134
+ expect(m.emit!.files).toEqual([join("src", "assets.ts")]);
135
+ } finally {
136
+ process.chdir(prevCwd);
137
+ }
138
+ });
139
+ });
140
+
111
141
  test("--write-source without an emit record in the manifest is a clear error", async () => {
112
142
  if (!parserAvailable) return;
113
143
  await withEstate(async (dir) => {
@@ -251,9 +251,10 @@ async function loadMigrationRules(targetLexicon: string): Promise<LintRule[]> {
251
251
  * setup steps, suggested GitLab improvements, caveats.
252
252
  *
253
253
  * The same data backs the SARIF report (via formatSarif); this is the
254
- * human-readable surface.
254
+ * human-readable surface. Exported so downstream migrate consumers can render
255
+ * the same report without reimplementing it (#2066).
255
256
  */
256
- function formatMarkdownSummary(
257
+ export function formatMarkdownSummary(
257
258
  provenance: Array<Record<string, unknown>>,
258
259
  diagnostics: Array<Record<string, unknown>>,
259
260
  sourceContent?: string,
@@ -413,6 +413,44 @@ describe("status", () => {
413
413
  expect(result.digestB).toBe("sha256:abc");
414
414
  expect(result.same).toBe(false);
415
415
  });
416
+
417
+ test("pinned helm records compare on inputDigest — per-cluster bytes legitimately differ (ReleaseRecord.inputDigest's own contract)", () => {
418
+ // Same chart+values rendered against two clusters' capability profiles:
419
+ // contentDigests (recorded as `digest`) differ, inputDigests match —
420
+ // prod IS running what staging tested.
421
+ const result = compareAcrossEnvironments(
422
+ "web",
423
+ { name: "staging", records: [record({ component: "web", env: "staging", digest: "sha256:staging-bytes", inputDigest: "sha256:inputs" })] },
424
+ { name: "prod", records: [record({ component: "web", env: "prod", digest: "sha256:prod-bytes", inputDigest: "sha256:inputs" })] },
425
+ );
426
+ expect(result.same).toBe(true);
427
+ expect(result.comparedOn).toEqual({ a: "sha256:inputs", b: "sha256:inputs" });
428
+ // The exact bytes each cluster got stay visible.
429
+ expect(result.digestA).toBe("sha256:staging-bytes");
430
+ expect(result.digestB).toBe("sha256:prod-bytes");
431
+ });
432
+
433
+ test("differing inputDigests are a real difference, whatever the bytes say", () => {
434
+ const result = compareAcrossEnvironments(
435
+ "web",
436
+ { name: "staging", records: [record({ component: "web", env: "staging", digest: "sha256:same-bytes", inputDigest: "sha256:inputs-v2" })] },
437
+ { name: "prod", records: [record({ component: "web", env: "prod", digest: "sha256:same-bytes", inputDigest: "sha256:inputs-v1" })] },
438
+ );
439
+ expect(result.same).toBe(false);
440
+ });
441
+
442
+ test("one side input-identified, the other content-identified: compared honestly on what each carries", () => {
443
+ // Mixed deploys (a pinned helm release in prod, an unpinned one in
444
+ // staging where the record's digest IS the input digest): staging's
445
+ // digest against prod's inputDigest is the only meaningful join.
446
+ const result = compareAcrossEnvironments(
447
+ "web",
448
+ { name: "staging", records: [record({ component: "web", env: "staging", digest: "sha256:inputs" })] },
449
+ { name: "prod", records: [record({ component: "web", env: "prod", digest: "sha256:prod-bytes", inputDigest: "sha256:inputs" })] },
450
+ );
451
+ expect(result.same).toBe(true);
452
+ expect(result.comparedOn).toEqual({ a: "sha256:inputs", b: "sha256:inputs" });
453
+ });
416
454
  });
417
455
 
418
456
  describe("mergeLiveEvidence (#57 — per-component stack presence overlay)", () => {
@@ -478,8 +478,19 @@ export interface CrossEnvComparison {
478
478
  envB: string;
479
479
  digestA?: string;
480
480
  digestB?: string;
481
- /** True only when both envs have a recorded digest and they match. */
481
+ /**
482
+ * True only when both envs have a recorded digest and their *comparable*
483
+ * identities match. For a record carrying `inputDigest` (a pinned helm
484
+ * deploy, chant#1242/#1243), that field is the comparable identity —
485
+ * `ReleaseRecord.inputDigest`'s own contract: profiles are per cluster, two
486
+ * environments legitimately render to different bytes, so cross-environment
487
+ * "is prod running what staging tested" joins on the input side while
488
+ * `digest` proves the exact bytes each cluster got. Records without it
489
+ * compare on `digest` exactly as before.
490
+ */
482
491
  same: boolean;
492
+ /** The identities `same` was decided on (`inputDigest ?? digest` per side), when both sides had one and they differ from the digests shown. */
493
+ comparedOn?: { a: string; b: string };
483
494
  }
484
495
 
485
496
  /** Compare the latest recorded digest for `component` between two environments' release records. */
@@ -490,12 +501,16 @@ export function compareAcrossEnvironments(
490
501
  ): CrossEnvComparison {
491
502
  const latestA = latestPerComponent(envA.records).get(component);
492
503
  const latestB = latestPerComponent(envB.records).get(component);
504
+ const idA = latestA ? latestA.inputDigest ?? latestA.digest : undefined;
505
+ const idB = latestB ? latestB.inputDigest ?? latestB.digest : undefined;
506
+ const comparedOnInputs = (latestA?.inputDigest ?? latestB?.inputDigest) !== undefined;
493
507
  return {
494
508
  component,
495
509
  envA: envA.name,
496
510
  envB: envB.name,
497
511
  digestA: latestA?.digest,
498
512
  digestB: latestB?.digest,
499
- same: !!latestA && !!latestB && latestA.digest === latestB.digest,
513
+ same: !!idA && !!idB && idA === idB,
514
+ ...(comparedOnInputs && idA && idB ? { comparedOn: { a: idA, b: idB } } : {}),
500
515
  };
501
516
  }
@@ -0,0 +1,68 @@
1
+ import { describe, test, expect, vi, beforeEach, afterEach } from "vitest";
2
+ import { mkdtempSync, mkdirSync, writeFileSync, rmSync, realpathSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { discoverOps } from "./discover";
6
+
7
+ /**
8
+ * #2058 — Op discovery scoped to the project root, the way entity discovery
9
+ * is. The repro: a chant project nested in a larger checkout (behold's
10
+ * committed `example-carve/app` beside `example-writes`, `example-k8s`)
11
+ * discovered every sibling project's `*.op.ts` as its own, because the scan
12
+ * root was the git root unconditionally.
13
+ *
14
+ * The fake git root is a temp tree; the runtime mock answers `git rev-parse`
15
+ * with whatever the current test set (`realpath`ed, since macOS's tmpdir is a
16
+ * symlink and the walk compares resolved paths).
17
+ */
18
+ let fakeGitRoot = "";
19
+ vi.mock("../runtime-adapter", () => ({
20
+ getRuntime: () => ({
21
+ spawn: async (cmd: string[]) =>
22
+ cmd[0] === "git" && cmd[1] === "rev-parse"
23
+ ? { stdout: fakeGitRoot, stderr: "", exitCode: 0 }
24
+ : { stdout: "", stderr: "", exitCode: 0 },
25
+ }),
26
+ }));
27
+
28
+ /** A minimal default export shaped the way discovery validates (entity.props = OpConfig). */
29
+ const OP_FILE = (name: string): string =>
30
+ `export default { props: { name: ${JSON.stringify(name)}, overview: "t", phases: [{ name: "Run", steps: [] }] } };\n`;
31
+
32
+ describe("discoverOps — the scan root is the project, not the checkout (#2058)", () => {
33
+ let checkout: string;
34
+ beforeEach(() => {
35
+ checkout = realpathSync(mkdtempSync(join(tmpdir(), "chant-op-root-")));
36
+ fakeGitRoot = checkout;
37
+ // The monorepo shape from the issue: two sibling chant projects, each
38
+ // with its own config and ops, nested in one git checkout.
39
+ mkdirSync(join(checkout, "app", "ops"), { recursive: true });
40
+ writeFileSync(join(checkout, "app", "chant.config.json"), JSON.stringify({ lexicons: ["aws"] }));
41
+ writeFileSync(join(checkout, "app", "ops", "mine.op.ts"), OP_FILE("mine"));
42
+ mkdirSync(join(checkout, "sibling", "ops"), { recursive: true });
43
+ writeFileSync(join(checkout, "sibling", "chant.config.json"), JSON.stringify({ lexicons: ["aws"] }));
44
+ writeFileSync(join(checkout, "sibling", "ops", "theirs.op.ts"), OP_FILE("theirs"));
45
+ // A configless dir at the checkout root, with an op of its own.
46
+ mkdirSync(join(checkout, "ops"), { recursive: true });
47
+ writeFileSync(join(checkout, "ops", "root.op.ts"), OP_FILE("root"));
48
+ });
49
+ afterEach(() => rmSync(checkout, { recursive: true, force: true }));
50
+
51
+ test("from inside a project, only that project's Ops are discovered — never a sibling's", async () => {
52
+ const { ops, errors } = await discoverOps({ cwd: join(checkout, "app") });
53
+ expect(errors).toEqual([]);
54
+ expect([...ops.keys()]).toEqual(["mine"]);
55
+ });
56
+
57
+ test("the walk finds the config from a subdirectory of the project too (ops/ beside src/, #1675)", async () => {
58
+ const { ops } = await discoverOps({ cwd: join(checkout, "app", "ops") });
59
+ expect([...ops.keys()]).toEqual(["mine"]);
60
+ });
61
+
62
+ test("with no chant config anywhere up to the git root, the git root stands — #1675's original scope", async () => {
63
+ rmSync(join(checkout, "app", "chant.config.json"));
64
+ rmSync(join(checkout, "sibling", "chant.config.json"));
65
+ const { ops } = await discoverOps({ cwd: join(checkout, "app") });
66
+ expect([...ops.keys()].sort()).toEqual(["mine", "root", "theirs"]);
67
+ });
68
+ });
@@ -1,6 +1,7 @@
1
1
  import { getRuntime } from "../runtime-adapter";
2
2
  import { readdir } from "node:fs/promises";
3
- import { join } from "node:path";
3
+ import { existsSync } from "node:fs";
4
+ import { dirname, join, resolve } from "node:path";
4
5
  import type { OpConfig } from "./types";
5
6
 
6
7
  export interface DiscoveredOp {
@@ -20,6 +21,31 @@ async function findGitRoot(cwd?: string): Promise<string> {
20
21
  return result.stdout.trim();
21
22
  }
22
23
 
24
+ /**
25
+ * The root the `*.op.ts` scan starts from (#2058): the nearest chant project
26
+ * root — the directory holding `chant.config.ts`/`chant.config.json`, walking
27
+ * up from `cwd` and never past the git root. #1675's convention keeps Op
28
+ * files OUTSIDE `sourceDir` (`ops/` beside `src/`), which is why discovery
29
+ * walks up at all — but "up" used to mean the git root unconditionally, so a
30
+ * chant project nested in a larger checkout (a monorepo, behold's committed
31
+ * examples) discovered every SIBLING project's Ops as its own. The config
32
+ * file is the project boundary, the same one entity discovery respects.
33
+ * With no config anywhere up to the git root, the git root stands —
34
+ * #1675's original scope, kept for configless layouts.
35
+ */
36
+ async function findDiscoveryRoot(cwd?: string): Promise<string> {
37
+ const gitRoot = resolve(await findGitRoot(cwd));
38
+ let dir = resolve(cwd ?? process.cwd());
39
+ for (;;) {
40
+ if (existsSync(join(dir, "chant.config.ts")) || existsSync(join(dir, "chant.config.json"))) return dir;
41
+ if (dir === gitRoot) break;
42
+ const parent = dirname(dir);
43
+ if (parent === dir) break; // filesystem root — cwd was outside the git root
44
+ dir = parent;
45
+ }
46
+ return gitRoot;
47
+ }
48
+
23
49
  async function collectOpFiles(dir: string): Promise<string[]> {
24
50
  const files: string[] = [];
25
51
  let entries;
@@ -45,14 +71,17 @@ async function collectOpFiles(dir: string): Promise<string[]> {
45
71
  }
46
72
 
47
73
  /**
48
- * Discover all Op definitions from *.op.ts files under the git root.
74
+ * Discover all Op definitions from `*.op.ts` files under the nearest chant
75
+ * project root (the directory holding `chant.config.ts`/`.json`, walking up
76
+ * from `cwd`), or under the git root when no config exists — see
77
+ * {@link findDiscoveryRoot} (#2058).
49
78
  */
50
79
  export async function discoverOps(opts?: { cwd?: string }): Promise<OpDiscoveryResult> {
51
80
  const errors: string[] = [];
52
81
  const ops = new Map<string, DiscoveredOp>();
53
82
 
54
- const gitRoot = await findGitRoot(opts?.cwd);
55
- const files = await collectOpFiles(gitRoot);
83
+ const root = await findDiscoveryRoot(opts?.cwd);
84
+ const files = await collectOpFiles(root);
56
85
 
57
86
  const nameToFile = new Map<string, string>();
58
87
 
@@ -102,6 +102,23 @@ describe("diffCollection", () => {
102
102
  ]);
103
103
  });
104
104
 
105
+ test("returns the live entry count for managedCount accumulation (#2067)", () => {
106
+ const out: ChangeSetEntry[] = [];
107
+ const count = diffCollection<D, L>({
108
+ resourceType: "thing",
109
+ desired: new Map([["a", { name: "a", v: 1 }]]),
110
+ live: new Map([
111
+ ["a", { name: "a", v: 1 }],
112
+ ["b", { name: "b", v: 2 }],
113
+ ["c", { name: "c", v: 3 }],
114
+ ]),
115
+ compareFields: () => [],
116
+ opts: noOpts,
117
+ out,
118
+ });
119
+ expect(count).toBe(3);
120
+ });
121
+
105
122
  test("honours createAfter / updateAfter mappers", () => {
106
123
  const out: ChangeSetEntry[] = [];
107
124
  diffCollection<D, L>({
@@ -171,6 +188,18 @@ describe("resolveRenames", () => {
171
188
  const cs: ChangeSet = { org: "acme", entries: [{ kind: "delete", resourceType: "team", key: "old" }] };
172
189
  expect(resolveRenames(cs)).toBe(cs);
173
190
  });
191
+
192
+ test("preserves managedCount on the resolved set (#2067)", () => {
193
+ const cs: ChangeSet = {
194
+ org: "acme",
195
+ managedCount: 7,
196
+ entries: [
197
+ { kind: "delete", resourceType: "team", key: "old", before: { slug: "old" } },
198
+ { kind: "create", resourceType: "team", key: "new", after: { previously: "old" } },
199
+ ],
200
+ };
201
+ expect(resolveRenames(cs).managedCount).toBe(7);
202
+ });
174
203
  });
175
204
 
176
205
  describe("removalDeltaCap", () => {
@@ -199,6 +228,64 @@ describe("removalDeltaCap", () => {
199
228
  };
200
229
  expect(removalDeltaCap(cs)).toBeNull(); // 1/4 = 25%, not > 25%
201
230
  });
231
+
232
+ // Live-denominator mode (#2067) — the warden reference cases.
233
+
234
+ test("one stale delete of 10 live managed entries passes at the default cap", () => {
235
+ const cs: ChangeSet = {
236
+ org: "acme",
237
+ entries: [{ kind: "delete", resourceType: "x", key: "stale" }],
238
+ };
239
+ // Plan-relative this is 1/1 = 100% and trips; against the live count it
240
+ // is 1/10 = 10% and passes — the routine converged-cycle cleanup.
241
+ expect(removalDeltaCap(cs)).not.toBeNull();
242
+ expect(removalDeltaCap(cs, { managedTotal: 10 })).toBeNull();
243
+ });
244
+
245
+ test("4 deletes of 10 live managed entries blocks, with the live-mode message", () => {
246
+ const cs: ChangeSet = {
247
+ org: "acme",
248
+ entries: Array.from({ length: 4 }, (_, i) => ({
249
+ kind: "delete" as const,
250
+ resourceType: "x",
251
+ key: `k${i}`,
252
+ })),
253
+ };
254
+ const d = removalDeltaCap(cs, { managedTotal: 10 })!;
255
+ expect(d.guardrail).toBe("removalDeltaCap");
256
+ expect(d.message).toContain("4 of 10 live managed entries (40%)");
257
+ });
258
+
259
+ test("reads changeSet.managedCount when opts.managedTotal is absent", () => {
260
+ const cs: ChangeSet = {
261
+ org: "acme",
262
+ managedCount: 10,
263
+ entries: [{ kind: "delete", resourceType: "x", key: "stale" }],
264
+ };
265
+ expect(removalDeltaCap(cs)).toBeNull(); // 1/10 via the change set's own count
266
+ // An explicit managedTotal wins over the change set's count.
267
+ expect(removalDeltaCap({ ...cs, managedCount: 2 }, { managedTotal: 10 })).toBeNull();
268
+ });
269
+
270
+ test("without managed info the diagnostic deep-equals the plan-relative one", () => {
271
+ const cs: ChangeSet = {
272
+ org: "acme",
273
+ entries: [
274
+ { kind: "delete", resourceType: "x", key: "a" },
275
+ { kind: "delete", resourceType: "x", key: "b" },
276
+ ],
277
+ };
278
+ const expected = {
279
+ guardrail: "removalDeltaCap",
280
+ message:
281
+ "2 of 2 managed entries (100%) would be deleted, exceeding the 25% threshold. " +
282
+ "Check for typos in config or raise maxFraction to proceed.",
283
+ };
284
+ expect(removalDeltaCap(cs)).toEqual(expected);
285
+ // A zero live count means "no live info" — it never divides by zero and
286
+ // never loosens the cap; behavior stays exactly plan-relative.
287
+ expect(removalDeltaCap(cs, { managedTotal: 0 })).toEqual(expected);
288
+ });
202
289
  });
203
290
 
204
291
  describe("runGuardrailChecks", () => {
@@ -221,6 +308,19 @@ describe("runGuardrailChecks", () => {
221
308
  const cs: ChangeSet = { org: "acme", entries: [{ kind: "create", resourceType: "x", key: "a" }] };
222
309
  expect(runGuardrailChecks(cs, [() => null])).toEqual({ ok: true });
223
310
  });
311
+
312
+ test("threads live context to checks that take it (#2067)", () => {
313
+ const cs: ChangeSet = {
314
+ org: "acme",
315
+ entries: [{ kind: "delete", resourceType: "x", key: "stale" }],
316
+ };
317
+ const check: GuardrailCheck = (resolved, ctx) =>
318
+ removalDeltaCap(resolved, { managedTotal: ctx?.managedCount });
319
+ // Without ctx the check falls back to plan-relative and trips (1/1).
320
+ expect(runGuardrailChecks(cs, [check]).ok).toBe(false);
321
+ // With the live count threaded through, 1/10 passes.
322
+ expect(runGuardrailChecks(cs, [check], { managedCount: 10 })).toEqual({ ok: true });
323
+ });
224
324
  });
225
325
 
226
326
  // ---------------------------------------------------------------------------
package/src/reconcile.ts CHANGED
@@ -66,6 +66,14 @@ export interface ChangeSet {
66
66
  org: string;
67
67
  /** All proposed changes, in stable order. */
68
68
  entries: ChangeSetEntry[];
69
+ /**
70
+ * Live entries under management across the diffed collections (#2067).
71
+ * `diffCollection` returns each collection's live count so the diff layer
72
+ * can sum it here. Optional — when absent, guardrails that want a live
73
+ * denominator (see {@link removalDeltaCap}) fall back to the plan-relative
74
+ * one.
75
+ */
76
+ managedCount?: number;
69
77
  }
70
78
 
71
79
  /** Options controlling diff behaviour. */
@@ -147,8 +155,11 @@ export interface DiffCollectionParams<D, L> {
147
155
  * when `compareFields` reports differences, and ownership-gated deletes for
148
156
  * live-not-desired. This is the selective-by-omission + ownership-gated-delete
149
157
  * pattern shared by every keyed-collection diff.
158
+ *
159
+ * Returns the live entry count for this collection, so the diff layer can sum
160
+ * `ChangeSet.managedCount` across the collections it diffs (#2067).
150
161
  */
151
- export function diffCollection<D, L>(params: DiffCollectionParams<D, L>): void {
162
+ export function diffCollection<D, L>(params: DiffCollectionParams<D, L>): number {
152
163
  const {
153
164
  resourceType,
154
165
  keyPrefix = "",
@@ -193,6 +204,8 @@ export function diffCollection<D, L>(params: DiffCollectionParams<D, L>): void {
193
204
  out.push({ kind: "delete", resourceType, key: entryKey, before: l });
194
205
  }
195
206
  }
207
+
208
+ return live.size;
196
209
  }
197
210
 
198
211
  // ---------------------------------------------------------------------------
@@ -263,13 +276,32 @@ export interface GuardrailDiagnostic {
263
276
  /** Aggregated guardrail result. */
264
277
  export type GuardrailResult = { ok: true } | { ok: false; diagnostics: GuardrailDiagnostic[] };
265
278
 
266
- /** A guardrail check over a (rename-resolved) change set. Returns null when it passes. */
267
- export type GuardrailCheck = (resolved: ChangeSet) => GuardrailDiagnostic | null;
279
+ /** Live context threaded to guardrail checks by `runGuardrailChecks` (#2067). */
280
+ export interface GuardrailContext {
281
+ /** Live entries under management this cycle (see `ChangeSet.managedCount`). */
282
+ managedCount?: number;
283
+ }
284
+
285
+ /**
286
+ * A guardrail check over a (rename-resolved) change set. Returns null when it
287
+ * passes. `ctx` carries live context when the caller has it (#2067); a check
288
+ * written against the single-argument shape keeps working unchanged.
289
+ */
290
+ export type GuardrailCheck = (resolved: ChangeSet, ctx?: GuardrailContext) => GuardrailDiagnostic | null;
268
291
 
269
292
  /** Config for `removalDeltaCap`. */
270
293
  export interface RemovalDeltaCapOptions {
271
294
  /** Max fraction of pre-existing entries that may be deleted. Must be in (0,1]. Default 0.25. */
272
295
  maxFraction?: number;
296
+ /**
297
+ * Live managed-entry denominator (#2067). When positive, deletes are divided
298
+ * by this count of live entries under management instead of the plan's
299
+ * non-create entries — a converged cycle deleting one stale entry out of
300
+ * many no longer reads as 1/1 = 100%. Defaults to `changeSet.managedCount`;
301
+ * when neither is present (or not positive) the cap keeps its plan-relative
302
+ * behavior.
303
+ */
304
+ managedTotal?: number;
273
305
  }
274
306
 
275
307
  /**
@@ -318,7 +350,7 @@ export function resolveRenames(changeSet: ChangeSet): ChangeSet {
318
350
  !(e.kind === "create" && resolvedCreates.has(e.key)),
319
351
  );
320
352
 
321
- return { org: changeSet.org, entries: [...filteredEntries, ...syntheticUpdates] };
353
+ return { ...changeSet, entries: [...filteredEntries, ...syntheticUpdates] };
322
354
  }
323
355
 
324
356
  /**
@@ -326,6 +358,11 @@ export function resolveRenames(changeSet: ChangeSet): ChangeSet {
326
358
  * (deletes + updates; creates excluded so a flood of new entries can't dilute
327
359
  * the delete fraction). Guards against a typo wiping the config in one apply.
328
360
  *
361
+ * With a live denominator (`opts.managedTotal`, else `changeSet.managedCount`;
362
+ * #2067) deletes are measured against the live entries under management
363
+ * instead — so a converged cycle's plan of exactly one stale delete does not
364
+ * trip at 1/1 = 100%. Without one, behavior is plan-relative as before.
365
+ *
329
366
  * CONTRACT: pass a RENAME-RESOLVED change set (see {@link resolveRenames}).
330
367
  */
331
368
  export function removalDeltaCap(
@@ -333,9 +370,25 @@ export function removalDeltaCap(
333
370
  opts: RemovalDeltaCapOptions = {},
334
371
  ): GuardrailDiagnostic | null {
335
372
  const maxFraction = opts.maxFraction ?? 0.25;
373
+ const deletes = changeSet.entries.filter((e) => e.kind === "delete").length;
374
+
375
+ const managedTotal = opts.managedTotal ?? changeSet.managedCount;
376
+ if (managedTotal !== undefined && managedTotal > 0) {
377
+ const fraction = deletes / managedTotal;
378
+ if (fraction > maxFraction) {
379
+ return {
380
+ guardrail: "removalDeltaCap",
381
+ message:
382
+ `${deletes} of ${managedTotal} live managed entries (${Math.round(fraction * 100)}%) would be deleted, ` +
383
+ `exceeding the ${Math.round(maxFraction * 100)}% threshold. ` +
384
+ `Check for typos in config or raise maxFraction to proceed.`,
385
+ };
386
+ }
387
+ return null;
388
+ }
389
+
336
390
  const total = changeSet.entries.filter((e) => e.kind !== "create").length;
337
391
  if (total === 0) return null;
338
- const deletes = changeSet.entries.filter((e) => e.kind === "delete").length;
339
392
  const fraction = deletes / total;
340
393
  if (fraction > maxFraction) {
341
394
  return {
@@ -353,12 +406,18 @@ export function removalDeltaCap(
353
406
  * Run a set of guardrail checks against a change set. Resolves renames ONCE,
354
407
  * then runs every check on the resolved set, aggregating any diagnostics. The
355
408
  * caller composes provider-specific checks (e.g. an admin floor) as closures.
409
+ * `ctx` (optional, #2067) threads live context — e.g. the managed-live count —
410
+ * to every check; single-argument checks ignore it.
356
411
  */
357
- export function runGuardrailChecks(changeSet: ChangeSet, checks: GuardrailCheck[]): GuardrailResult {
412
+ export function runGuardrailChecks(
413
+ changeSet: ChangeSet,
414
+ checks: GuardrailCheck[],
415
+ ctx?: GuardrailContext,
416
+ ): GuardrailResult {
358
417
  const resolved = resolveRenames(changeSet);
359
418
  const diagnostics: GuardrailDiagnostic[] = [];
360
419
  for (const check of checks) {
361
- const d = check(resolved);
420
+ const d = check(resolved, ctx);
362
421
  if (d) diagnostics.push(d);
363
422
  }
364
423
  return diagnostics.length > 0 ? { ok: false, diagnostics } : { ok: true };
@@ -9,7 +9,7 @@
9
9
  */
10
10
 
11
11
  import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "fs";
12
- import { isAbsolute, join, relative } from "path";
12
+ import { isAbsolute, join, relative, resolve } from "path";
13
13
  import type { CarveReport } from "./carve";
14
14
  import type { OwnershipMarker } from "../ownership";
15
15
 
@@ -100,10 +100,14 @@ export function carveManifestPath(outDir: string, target: string): string {
100
100
  /**
101
101
  * Relative-to-`outDir` spelling of a recorded file path. Absolute entries
102
102
  * (the run-time spelling every step naturally produces, and every version-1
103
- * manifest on disk) are rewritten; already-relative entries pass through.
103
+ * manifest on disk) are rewritten against the RESOLVED outDir — a caller's
104
+ * relative `--output` must not change what "relative to the manifest's
105
+ * directory" means (#2059). Already-relative entries pass through: they can
106
+ * only have come from a reader honouring the contract, and re-basing them
107
+ * against an unknowable original cwd would corrupt, not normalize.
104
108
  */
105
109
  function relativizePath(outDir: string, p: string): string {
106
- return isAbsolute(p) ? relative(outDir, p) : p;
110
+ return isAbsolute(p) ? relative(resolve(outDir), p) : p;
107
111
  }
108
112
 
109
113
  /**
@@ -136,7 +140,11 @@ function normalizeManifest(outDir: string, manifest: CarveManifest): CarveManife
136
140
  * on-disk spellings read the same.
137
141
  */
138
142
  export function resolveManifestFilePath(manifestDir: string, p: string): string {
139
- return isAbsolute(p) ? p : join(manifestDir, p);
143
+ // Always ABSOLUTE (#2059): `join` with a relative manifestDir produced a
144
+ // cwd-relative path, which `relativizePath` then passed through untouched —
145
+ // so `carve apply --output carveout` recorded `stampedFiles` relative to
146
+ // wherever the command happened to run, two spellings in one manifest.
147
+ return isAbsolute(p) ? p : resolve(manifestDir, p);
140
148
  }
141
149
 
142
150
  /** Write (or overwrite) a manifest into the output dir. Returns the path. */