@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.
- package/dist/cli/commands/migrate.d.ts +11 -0
- package/dist/cli/commands/migrate.d.ts.map +1 -1
- package/dist/lifecycle/status.d.ts +15 -1
- package/dist/lifecycle/status.d.ts.map +1 -1
- package/dist/op/discover.d.ts +4 -1
- package/dist/op/discover.d.ts.map +1 -1
- package/dist/reconcile.d.ts +40 -4
- package/dist/reconcile.d.ts.map +1 -1
- package/dist/terraform/manifest.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/cli/commands/carve-apply.test.ts +31 -1
- package/src/cli/commands/migrate.ts +3 -2
- package/src/lifecycle/status.test.ts +38 -0
- package/src/lifecycle/status.ts +17 -2
- package/src/op/discover-root.test.ts +68 -0
- package/src/op/discover.ts +33 -4
- package/src/reconcile.test.ts +100 -0
- package/src/reconcile.ts +66 -7
- package/src/terraform/manifest.ts +12 -4
|
@@ -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"}
|
|
@@ -275,8 +275,22 @@ export interface CrossEnvComparison {
|
|
|
275
275
|
envB: string;
|
|
276
276
|
digestA?: string;
|
|
277
277
|
digestB?: string;
|
|
278
|
-
/**
|
|
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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"}
|
package/dist/op/discover.d.ts
CHANGED
|
@@ -8,7 +8,10 @@ export interface OpDiscoveryResult {
|
|
|
8
8
|
errors: string[];
|
|
9
9
|
}
|
|
10
10
|
/**
|
|
11
|
-
* Discover all Op definitions from
|
|
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":"
|
|
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"}
|
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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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../src/terraform/manifest.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAC3C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAEpD,eAAO,MAAM,qBAAqB,gBAAgB,CAAC;AAEnD;;;;;;GAMG;AACH,eAAO,MAAM,sBAAsB,IAAI,CAAC;AAExC,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,CAAC,GAAG,CAAC,CAAC;IACf,6EAA6E;IAC7E,MAAM,EAAE,MAAM,CAAC;IACf,2CAA2C;IAC3C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,iEAAiE;IACjE,IAAI,EAAE,MAAM,CAAC;IACb,qEAAqE;IACrE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uEAAuE;IACvE,QAAQ,EAAE,WAAW,CAAC;IACtB,gCAAgC;IAChC,IAAI,CAAC,EAAE;QACL,MAAM,EAAE,SAAS,GAAG,MAAM,CAAC;QAC3B;;;;;;WAMG;QACH,KAAK,EAAE,MAAM,EAAE,CAAC;QAChB;;;WAGG;QACH,MAAM,CAAC,EAAE,MAAM,CACb,MAAM,EACN;YACE,8EAA8E;YAC9E,MAAM,EAAE,MAAM,CAAC;YACf,mEAAmE;YACnE,QAAQ,EAAE,MAAM,CAAC;YACjB,kCAAkC;YAClC,KAAK,EAAE,MAAM,EAAE,CAAC;YAChB,2CAA2C;YAC3C,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;SACrC,CACF,CAAC;QACF,EAAE,EAAE,MAAM,CAAC;KACZ,CAAC;IACF,kCAAkC;IAClC,MAAM,CAAC,EAAE;QACP,6FAA6F;QAC7F,OAAO,EAAE,MAAM,EAAE,CAAC;QAClB,cAAc,EAAE,OAAO,CAAC;QACxB,2IAA2I;QAC3I,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,yEAAyE;QACzE,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;QACnB,EAAE,EAAE,MAAM,CAAC;KACZ,CAAC;IACF,iCAAiC;IACjC,KAAK,CAAC,EAAE;QACN,MAAM,EAAE,eAAe,CAAC;QACxB,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACtC,4IAA4I;QAC5I,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;QACxB,EAAE,EAAE,MAAM,CAAC;KACZ,CAAC;CACH;AAED,oFAAoF;AACpF,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEhD;AAED,kEAAkE;AAClE,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAExE;
|
|
1
|
+
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../src/terraform/manifest.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -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)", () => {
|
package/src/lifecycle/status.ts
CHANGED
|
@@ -478,8 +478,19 @@ export interface CrossEnvComparison {
|
|
|
478
478
|
envB: string;
|
|
479
479
|
digestA?: string;
|
|
480
480
|
digestB?: string;
|
|
481
|
-
/**
|
|
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: !!
|
|
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
|
+
});
|
package/src/op/discover.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { getRuntime } from "../runtime-adapter";
|
|
2
2
|
import { readdir } from "node:fs/promises";
|
|
3
|
-
import {
|
|
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
|
|
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
|
|
55
|
-
const files = await collectOpFiles(
|
|
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
|
|
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 };
|
|
@@ -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
|
|
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
|
-
|
|
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. */
|