@intentius/chant 0.54.3 → 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"}
@@ -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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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intentius/chant",
3
- "version": "0.54.3",
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",
@@ -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,
@@ -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 };