@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.
- package/dist/cli/commands/migrate.d.ts +11 -0
- package/dist/cli/commands/migrate.d.ts.map +1 -1
- package/dist/reconcile.d.ts +40 -4
- package/dist/reconcile.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/cli/commands/migrate.ts +3 -2
- package/src/reconcile.test.ts +100 -0
- package/src/reconcile.ts +66 -7
|
@@ -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;
|
|
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"}
|
package/dist/reconcile.d.ts
CHANGED
|
@@ -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>):
|
|
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
|
-
/**
|
|
129
|
-
export
|
|
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. */
|
package/dist/reconcile.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reconcile.d.ts","sourceRoot":"","sources":["../src/reconcile.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAoB,KAAK,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAMxE,mFAAmF;AACnF,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,OAAO,CAAC;IAChB,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,oDAAoD;AACpD,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAExD,wCAAwC;AACxC,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,UAAU,CAAC;IACjB,iFAAiF;IACjF,YAAY,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,IAAI,CAAC,EAAE,cAAc,CAAC;IACtB;;;;OAIG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ,6DAA6D;IAC7D,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,+DAA+D;IAC/D,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,wDAAwD;IACxD,MAAM,CAAC,EAAE,WAAW,EAAE,CAAC;CACxB;AAED,yEAAyE;AACzE,MAAM,WAAW,SAAS;IACxB,6EAA6E;IAC7E,GAAG,EAAE,MAAM,CAAC;IACZ,6CAA6C;IAC7C,OAAO,EAAE,cAAc,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"reconcile.d.ts","sourceRoot":"","sources":["../src/reconcile.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -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,
|
package/src/reconcile.test.ts
CHANGED
|
@@ -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>):
|
|
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
|
-
/**
|
|
267
|
-
export
|
|
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 {
|
|
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(
|
|
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 };
|