@intentius/chant-lexicon-pulumi 0.124.58 → 0.124.60

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.
@@ -68,7 +68,11 @@
68
68
  * each step's op, URN, provider, id, inputs before and after, `diffReasons`,
69
69
  * `replaceReasons` and `detailedDiff`, sorted by URN and op. It leaves out
70
70
  * `config`, `duration`, `diagnostics` and `changeSummary`, and the states'
71
- * outputs, which at preview are the last deployment's or unknown. For a
71
+ * outputs, which at preview are the last deployment's or unknown. It also
72
+ * leaves out what the engine makes up while planning a create or a
73
+ * replacement (plywood#56): an autoname such as `events-3f45941` is drawn
74
+ * again by every preview, so it reads as {@link PULUMI_GENERATED}. See
75
+ * {@link pulumiPlannedInputs}. Unknowns already print as one fixed string. For a
72
76
  * member on the resource lock grain the `pulumi-up` capability passes
73
77
  * `changedOnly` (#4541, ruling B), which leaves `same` and `read` steps out.
74
78
  *
@@ -140,14 +144,44 @@ export interface PulumiPreviewDigestOptions {
140
144
  */
141
145
  changedOnly?: boolean;
142
146
  }
147
+ /** What stands in a digest for an input the engine made up while planning (plywood#56). */
148
+ export declare const PULUMI_GENERATED = "<generated at plan time>";
149
+ /**
150
+ * The inputs a step would write, with the values the engine made up while
151
+ * planning replaced by {@link PULUMI_GENERATED} (plywood#56). Two previews of
152
+ * one create draw different autonames (`events-3f45941`, then
153
+ * `events-9c01d2e`), and a digest over them could never match a later plan of
154
+ * the same change.
155
+ *
156
+ * Only a create or a replacement draws a name: an update's inputs keep the one
157
+ * the resource was created with, and that stays in `before` and `after`. A
158
+ * value counts as generated when the engine lists its key in `__defaults`
159
+ * (the inputs the provider filled in, not the program) and it reads as an
160
+ * autoname of this resource: its logical name, or the start of it when the
161
+ * provider shortened it, then a dash and seven hex characters. An engine that
162
+ * prints no `__defaults` is held to the exact logical name. Every other input
163
+ * stays in, the defaults that are plain values included, so a changed input
164
+ * still changes the digest. `before` is the recorded state and is never masked.
165
+ */
166
+ export declare function pulumiPlannedInputs(step: PulumiStep): Json | null;
143
167
  /**
144
168
  * The member's plan digest: `computePlanDigest("pulumi-preview", …)` over
145
169
  * what each step proposes, sorted by URN and op, so two renderings of one
146
- * preview agree. See the module comment for what it leaves out. With
147
- * `changedOnly`, `same` and `read` steps are left out too, under the label
148
- * `pulumi-preview-changed`, so the two digests never collide.
170
+ * preview agree. See the module comment for what it leaves out, and
171
+ * {@link pulumiPlannedInputs} for the values the engine made up that it leaves
172
+ * out of a create. With `changedOnly`, `same` and `read` steps are left out
173
+ * too, under the label `pulumi-preview-changed`, so the two digests never
174
+ * collide.
149
175
  */
150
176
  export declare function pulumiPreviewDigest(preview: unknown, opts?: PulumiPreviewDigestOptions): string;
177
+ /**
178
+ * The digest of each resource the preview changes, by URN (plywood#55): the
179
+ * steps of that URN (a replace is several), as {@link pulumiPreviewDigest}
180
+ * reads them, with the values the engine made up left out. A `same` or `read` step is not in it whatever the lock grain,
181
+ * so what a pull request left alone never makes its plan differ from a plan
182
+ * made after another change landed on it.
183
+ */
184
+ export declare function pulumiResourceDigests(preview: unknown): Record<string, string>;
151
185
  /** The action and disruption one URN's steps mean, or undefined for an op the change set has no action for. */
152
186
  export declare function pulumiStepsAction(steps: PulumiStep[]): {
153
187
  action: ChangeSetAction;
@@ -1 +1 @@
1
- {"version":3,"file":"change-set.d.ts","sourceRoot":"","sources":["../src/change-set.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"change-set.d.ts","sourceRoot":"","sources":["../src/change-set.ts"],"names":[],"mappings":"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"}
@@ -68,7 +68,11 @@
68
68
  * each step's op, URN, provider, id, inputs before and after, `diffReasons`,
69
69
  * `replaceReasons` and `detailedDiff`, sorted by URN and op. It leaves out
70
70
  * `config`, `duration`, `diagnostics` and `changeSummary`, and the states'
71
- * outputs, which at preview are the last deployment's or unknown. For a
71
+ * outputs, which at preview are the last deployment's or unknown. It also
72
+ * leaves out what the engine makes up while planning a create or a
73
+ * replacement (plywood#56): an autoname such as `events-3f45941` is drawn
74
+ * again by every preview, so it reads as {@link PULUMI_GENERATED}. See
75
+ * {@link pulumiPlannedInputs}. Unknowns already print as one fixed string. For a
72
76
  * member on the resource lock grain the `pulumi-up` capability passes
73
77
  * `changedOnly` (#4541, ruling B), which leaves `same` and `read` steps out.
74
78
  *
@@ -108,30 +112,98 @@ export function pulumiExportResources(doc) {
108
112
  const byString = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
109
113
  /** Step ops that change nothing: what {@link PulumiPreviewDigestOptions.changedOnly} leaves out of the digest. */
110
114
  export const PULUMI_UNCHANGED_OPS = new Set(["same", "read"]);
115
+ /** What stands in a digest for an input the engine made up while planning (plywood#56). */
116
+ export const PULUMI_GENERATED = "<generated at plan time>";
117
+ /** Pulumi's autonaming: the logical name, a dash and seven random hex characters. */
118
+ const AUTONAME = /^(.+)-[0-9a-f]{7}$/;
119
+ /** The step's resource name: the last part of its URN. */
120
+ function logicalName(urn) {
121
+ const at = urn?.lastIndexOf("::") ?? -1;
122
+ return urn && at >= 0 ? urn.slice(at + 2) : (urn ?? "");
123
+ }
111
124
  /**
112
- * The member's plan digest: `computePlanDigest("pulumi-preview", …)` over
113
- * what each step proposes, sorted by URN and op, so two renderings of one
114
- * preview agree. See the module comment for what it leaves out. With
115
- * `changedOnly`, `same` and `read` steps are left out too, under the label
116
- * `pulumi-preview-changed`, so the two digests never collide.
125
+ * The inputs a step would write, with the values the engine made up while
126
+ * planning replaced by {@link PULUMI_GENERATED} (plywood#56). Two previews of
127
+ * one create draw different autonames (`events-3f45941`, then
128
+ * `events-9c01d2e`), and a digest over them could never match a later plan of
129
+ * the same change.
130
+ *
131
+ * Only a create or a replacement draws a name: an update's inputs keep the one
132
+ * the resource was created with, and that stays in `before` and `after`. A
133
+ * value counts as generated when the engine lists its key in `__defaults`
134
+ * (the inputs the provider filled in, not the program) and it reads as an
135
+ * autoname of this resource: its logical name, or the start of it when the
136
+ * provider shortened it, then a dash and seven hex characters. An engine that
137
+ * prints no `__defaults` is held to the exact logical name. Every other input
138
+ * stays in, the defaults that are plain values included, so a changed input
139
+ * still changes the digest. `before` is the recorded state and is never masked.
117
140
  */
118
- export function pulumiPreviewDigest(preview, opts = {}) {
119
- const steps = (pulumiSteps(preview) ?? [])
120
- .filter((s) => !opts.changedOnly || !PULUMI_UNCHANGED_OPS.has(s.op ?? ""))
121
- .map((s) => ({
141
+ export function pulumiPlannedInputs(step) {
142
+ const inputs = step.newState?.inputs;
143
+ if (!isObject(inputs))
144
+ return inputs ?? null;
145
+ const draws = step.oldState === undefined || step.op === "create" || step.op === "create-replacement" || step.op === "replace";
146
+ if (!draws)
147
+ return inputs;
148
+ const name = logicalName(step.urn).toLowerCase();
149
+ const filled = Array.isArray(inputs.__defaults) ? new Set(inputs.__defaults.filter((k) => typeof k === "string")) : undefined;
150
+ const out = {};
151
+ for (const [key, value] of Object.entries(inputs)) {
152
+ const stem = typeof value === "string" ? AUTONAME.exec(value)?.[1]?.toLowerCase() : undefined;
153
+ const generated = stem !== undefined && name !== "" && (filled ? filled.has(key) && (name.startsWith(stem) || stem.startsWith(name)) : stem === name);
154
+ out[key] = generated ? PULUMI_GENERATED : value;
155
+ }
156
+ return out;
157
+ }
158
+ /** One step as a digest sees it: what it proposes, without the values the engine made up. */
159
+ function digestStep(s) {
160
+ return {
122
161
  op: s.op ?? null,
123
162
  urn: s.urn ?? null,
124
163
  provider: s.provider ?? null,
125
164
  id: s.newState?.id ?? s.oldState?.id ?? null,
126
165
  before: s.oldState?.inputs ?? null,
127
- after: s.newState?.inputs ?? null,
166
+ after: pulumiPlannedInputs(s),
128
167
  diffReasons: [...(s.diffReasons ?? [])].sort(),
129
168
  replaceReasons: [...(s.replaceReasons ?? [])].sort(),
130
169
  detailedDiff: s.detailedDiff ?? null,
131
- }))
132
- .sort((a, b) => byString(`${a.urn}\u0000${a.op}`, `${b.urn}\u0000${b.op}`));
170
+ };
171
+ }
172
+ const byStep = (a, b) => byString(`${a.urn}\u0000${a.op}`, `${b.urn}\u0000${b.op}`);
173
+ /**
174
+ * The member's plan digest: `computePlanDigest("pulumi-preview", …)` over
175
+ * what each step proposes, sorted by URN and op, so two renderings of one
176
+ * preview agree. See the module comment for what it leaves out, and
177
+ * {@link pulumiPlannedInputs} for the values the engine made up that it leaves
178
+ * out of a create. With `changedOnly`, `same` and `read` steps are left out
179
+ * too, under the label `pulumi-preview-changed`, so the two digests never
180
+ * collide.
181
+ */
182
+ export function pulumiPreviewDigest(preview, opts = {}) {
183
+ const steps = (pulumiSteps(preview) ?? [])
184
+ .filter((s) => !opts.changedOnly || !PULUMI_UNCHANGED_OPS.has(s.op ?? ""))
185
+ .map(digestStep)
186
+ .sort(byStep);
133
187
  return computePlanDigest(opts.changedOnly ? "pulumi-preview-changed" : "pulumi-preview", steps);
134
188
  }
189
+ /**
190
+ * The digest of each resource the preview changes, by URN (plywood#55): the
191
+ * steps of that URN (a replace is several), as {@link pulumiPreviewDigest}
192
+ * reads them, with the values the engine made up left out. A `same` or `read` step is not in it whatever the lock grain,
193
+ * so what a pull request left alone never makes its plan differ from a plan
194
+ * made after another change landed on it.
195
+ */
196
+ export function pulumiResourceDigests(preview) {
197
+ const byUrn = new Map();
198
+ for (const s of pulumiSteps(preview) ?? []) {
199
+ if (PULUMI_UNCHANGED_OPS.has(s.op ?? ""))
200
+ continue;
201
+ const list = byUrn.get(s.urn) ?? [];
202
+ list.push(digestStep(s));
203
+ byUrn.set(s.urn, list);
204
+ }
205
+ return Object.fromEntries([...byUrn].sort(([a], [b]) => byString(a, b)).map(([urn, steps]) => [urn, computePlanDigest("pulumi-resource", steps.sort(byStep))]));
206
+ }
135
207
  const REPLACE_OPS = new Set(["replace", "create-replacement", "delete-replaced"]);
136
208
  /** The action and disruption one URN's steps mean, or undefined for an op the change set has no action for. */
137
209
  export function pulumiStepsAction(steps) {
@@ -13,8 +13,12 @@
13
13
  * | plunge | unset | `plunge drift --resource <urn> --stream`, once per resource in the export |
14
14
  * | plunge | `<urn>` | the same, for that URN alone |
15
15
  *
16
- * Each read passes `--stack` (#4496): the member's declared
17
- * `pulumi.member.stack`, else the stack the export's URNs name.
16
+ * Each read passes `--stack` (#4496): the stack asked for (`chant workspace
17
+ * drift --stack`), else the member's declared `pulumi.member.stack`, else the
18
+ * stack the export's URNs name. A stack asked for is read from that stack's
19
+ * export in the index store alone; with none there the read is refused
20
+ * (`no-drift-read`), where without `--stack` the member reads the store's
21
+ * export, then the checked-in file.
18
22
  *
19
23
  * The one read is `refresh`. The member's engine is `pulumiMemberEngine`'s:
20
24
  * the member config's `engine`, then a plunge backend URL in `Pulumi.yaml`.
@@ -25,10 +29,13 @@
25
29
  */
26
30
  import type { ReadDriftOptions, ReadDriftResult } from "@intentius/chant/drift-record";
27
31
  import { type PulumiRunner } from "./run.js";
32
+ import type { SnapshotStore } from "@intentius/chant/lifecycle/snapshot-store";
28
33
  /** The runners, injectable so the tests run no binary. */
29
34
  export interface PulumiDriftRunners {
30
35
  run?: PulumiRunner;
31
36
  plunge?: PulumiRunner;
37
+ /** The index store the exports are read from. Default: the project's. */
38
+ exportStore?: SnapshotStore | null;
32
39
  }
33
40
  /** `LexiconPlugin.readDrift` for the pulumi lexicon. */
34
41
  export declare function readPulumiDrift(options: ReadDriftOptions, runners?: PulumiDriftRunners): Promise<ReadDriftResult>;
@@ -1 +1 @@
1
- {"version":3,"file":"drift-read.d.ts","sourceRoot":"","sources":["../src/drift-read.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,OAAO,KAAK,EAA0B,gBAAgB,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAG/G,OAAO,EAA2C,KAAK,YAAY,EAAE,MAAM,OAAO,CAAC;AAEnF,0DAA0D;AAC1D,MAAM,WAAW,kBAAkB;IACjC,GAAG,CAAC,EAAE,YAAY,CAAC;IACnB,MAAM,CAAC,EAAE,YAAY,CAAC;CACvB;AAED,wDAAwD;AACxD,wBAAsB,eAAe,CAAC,OAAO,EAAE,gBAAgB,EAAE,OAAO,GAAE,kBAAuB,GAAG,OAAO,CAAC,eAAe,CAAC,CAmC3H"}
1
+ {"version":3,"file":"drift-read.d.ts","sourceRoot":"","sources":["../src/drift-read.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,OAAO,KAAK,EAA0B,gBAAgB,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAG/G,OAAO,EAA2C,KAAK,YAAY,EAAE,MAAM,OAAO,CAAC;AAEnF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2CAA2C,CAAC;AAE/E,0DAA0D;AAC1D,MAAM,WAAW,kBAAkB;IACjC,GAAG,CAAC,EAAE,YAAY,CAAC;IACnB,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,yEAAyE;IACzE,WAAW,CAAC,EAAE,aAAa,GAAG,IAAI,CAAC;CACpC;AAED,wDAAwD;AACxD,wBAAsB,eAAe,CAAC,OAAO,EAAE,gBAAgB,EAAE,OAAO,GAAE,kBAAuB,GAAG,OAAO,CAAC,eAAe,CAAC,CAgD3H"}
@@ -1,5 +1,6 @@
1
1
  import { driftRecordLine, driftSettle, driftStart } from "@intentius/chant/drift-record";
2
2
  import { pulumiMemberEngine, refreshPulumiMember } from "./run.js";
3
+ import { readStoredPulumiExport } from "./stack-export-snapshot.js";
3
4
  /** `LexiconPlugin.readDrift` for the pulumi lexicon. */
4
5
  export async function readPulumiDrift(options, runners = {}) {
5
6
  const member = options.config.pulumi?.member;
@@ -9,6 +10,18 @@ export async function readPulumiDrift(options, runners = {}) {
9
10
  return { refused: { code: "read-unsupported", message: `a Pulumi member answers refresh, not ${options.read}` } };
10
11
  }
11
12
  const { engine } = pulumiMemberEngine(member.dir, member.engine);
13
+ const stack = options.stack ?? member.stack;
14
+ if (options.stack !== undefined) {
15
+ const stored = await readStoredPulumiExport({ dir: member.dir, stack: options.stack, ...(runners.exportStore !== undefined ? { store: runners.exportStore } : {}) });
16
+ if (!stored) {
17
+ return {
18
+ refused: {
19
+ code: "no-drift-read",
20
+ message: `the index store holds no export for stack ${options.stack} of ${options.member}; the pulumi-up capability writes one after each apply through chant, so apply that stack once, or read without --stack`,
21
+ },
22
+ };
23
+ }
24
+ }
12
25
  let records;
13
26
  try {
14
27
  records = await refreshPulumiMember({
@@ -16,7 +29,8 @@ export async function readPulumiDrift(options, runners = {}) {
16
29
  dir: member.dir,
17
30
  observedAt: options.observedAt,
18
31
  ...(member.engine ? { engine: member.engine } : {}),
19
- ...(member.stack ? { stack: member.stack } : {}),
32
+ ...(stack ? { stack } : {}),
33
+ ...(runners.exportStore !== undefined ? { exportStore: runners.exportStore } : {}),
20
34
  ...(options.resource !== undefined ? { urns: [options.resource] } : {}),
21
35
  ...(runners.run ? { run: runners.run } : {}),
22
36
  ...(runners.plunge ? { plunge: runners.plunge } : {}),
package/dist/index.d.ts CHANGED
@@ -2,7 +2,7 @@ export { pulumiPlugin } from "./plugin.js";
2
2
  export { pulumiSerializer } from "./serializer.js";
3
3
  export { pulumiConfigSchema, pulumiMemberConfigSchema, type PulumiConfig } from "./config.js";
4
4
  export { readPulumiMember, PULUMI_EXPORT_FILE, type PulumiMemberConfig, type PulumiMemberReport } from "./workspace-member.js";
5
- export { pulumiPreviewPart, pulumiPreviewDigest, pulumiSteps, pulumiExportResources, type PulumiPreviewPartInput, type PulumiStep, type PulumiStepState, type PulumiExportedResource, } from "./change-set.js";
5
+ export { pulumiPreviewPart, pulumiPreviewDigest, pulumiResourceDigests, pulumiPlannedInputs, PULUMI_GENERATED, pulumiSteps, pulumiExportResources, type PulumiPreviewPartInput, type PulumiStep, type PulumiStepState, type PulumiExportedResource, } from "./change-set.js";
6
6
  export { pulumiRefreshArgs, pulumiRefreshDriftRecords, pulumiRefreshDocumentRecords, type PulumiRefreshRead, type PulumiRefreshDriftInput, type PulumiRefreshDocumentInput, } from "./drift-record.js";
7
7
  export { previewPulumiMember, previewPulumiMemberRead, refreshPulumiMember, pulumiMemberEngine, pulumiMemberStackOutputs, pulumiPreviewArgs, pulumiStackOutputArgs, runAllowedPlunge, runAllowedPulumi, readMemberExport, defaultPulumiRunner, PULUMI_ALLOWED_COMMANDS, PULUMI_APPLY_COMMANDS, runApplyPulumi, runApplyPlunge, pulumiUpArgs, plungeUpArgs, pulumiUpPreviewArgs, PULUMI_REVIEW_STACK_COMMANDS, REVIEW_STACK_NAME, isReviewStackName, reviewStackInitArgs, reviewStackDestroyArgs, reviewStackRmArgs, runReviewStackPulumi, runReviewStackPlunge, type PulumiPolicyPack, type PulumiApplyTail, type PulumiRunner, type PreviewPulumiMemberOptions, type PulumiMemberPreview, type PulumiMemberEngine, type PulumiMemberStackOutputsOptions, type RefreshPulumiMemberOptions, } from "./run.js";
8
8
  export { pulumiExportedStackOutputs, pulumiPlannedStackOutputs, type PulumiStackOutput } from "./stack-outputs.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@ export { pulumiPlugin } from "./plugin.js";
2
2
  export { pulumiSerializer } from "./serializer.js";
3
3
  export { pulumiConfigSchema, pulumiMemberConfigSchema } from "./config.js";
4
4
  export { readPulumiMember, PULUMI_EXPORT_FILE } from "./workspace-member.js";
5
- export { pulumiPreviewPart, pulumiPreviewDigest, pulumiSteps, pulumiExportResources, } from "./change-set.js";
5
+ export { pulumiPreviewPart, pulumiPreviewDigest, pulumiResourceDigests, pulumiPlannedInputs, PULUMI_GENERATED, pulumiSteps, pulumiExportResources, } from "./change-set.js";
6
6
  export { pulumiRefreshArgs, pulumiRefreshDriftRecords, pulumiRefreshDocumentRecords, } from "./drift-record.js";
7
7
  export { previewPulumiMember, previewPulumiMemberRead, refreshPulumiMember, pulumiMemberEngine, pulumiMemberStackOutputs, pulumiPreviewArgs, pulumiStackOutputArgs, runAllowedPlunge, runAllowedPulumi, readMemberExport, defaultPulumiRunner, PULUMI_ALLOWED_COMMANDS, PULUMI_APPLY_COMMANDS, runApplyPulumi, runApplyPlunge, pulumiUpArgs, plungeUpArgs, pulumiUpPreviewArgs, PULUMI_REVIEW_STACK_COMMANDS, REVIEW_STACK_NAME, isReviewStackName, reviewStackInitArgs, reviewStackDestroyArgs, reviewStackRmArgs, runReviewStackPulumi, runReviewStackPlunge, } from "./run.js";
8
8
  export { pulumiExportedStackOutputs, pulumiPlannedStackOutputs } from "./stack-outputs.js";
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "algorithm": "sha256",
3
3
  "artifacts": {
4
- "manifest.json": "d9975374a07d5764ef1b0b209c48fc2ec1b4ec4669808c6d6b1b6429d23a97d6",
4
+ "manifest.json": "6066d06e053b59c3170661488757ef2e85187346d1d3e6e5e69f126a38dd4913",
5
5
  "meta.json": "ca3d163bab055381827226140568f3bef7eaac187cebd76878e0b63e9e442356",
6
6
  "types/index.d.ts": "883bcdf6a960e21360e79df010ca2087719fffec60fc39541c5ecd32d661e328",
7
7
  "rules/member-dir-absolute.ts": "4805d5e247ad544f942e1b2c36fc15a5087ca0aacaf804183a269021bd60e276",
@@ -20,10 +20,10 @@
20
20
  "rules/pul012.ts": "136f9764a532f56ce72461641550c9c4cee6af06c30968d8374f1a65a1b810d4",
21
21
  "rules/pul013.ts": "a19b397fecc2bda3e5f32c8f339b0befbd8f1148b32e70a3ba4f9370ee103379",
22
22
  "rules/pul014.ts": "25dd5ca4cb7f9906c608f4b7f78559efd188215c9a9a2f9f131498a847d3259b",
23
- "rules/pul015.ts": "f5eb45e941dfbe1ec53a31fd12258d7adb9877de33b5bcc566a09e3a0829d9da",
23
+ "rules/pul015.ts": "d6805c3b8bfacf0fc4a08fb466b066d949a8a5d164b7927cebc50111d1807ae9",
24
24
  "skills/chant-pulumi.md": "aef93407423daf6d0acc492a4d5bbf002fbae67b4f993ad36f86725686b98d61",
25
25
  "skills/chant-pulumi-stack-export.md": "aadd67f5e067de61dfda43d57492bca08bd22429e4baf314d6856a810dc0e8dc",
26
26
  "skills/chant-pulumi-plunge.md": "8a7f2f9f1124744dee9645905f141057ca01f5a88e34e1bc3858d69406d7ff3a"
27
27
  },
28
- "composite": "a4603282326e0ed1abf7f6c24fd702e35975fbbc9b1cdee72341269a8f9a9ac1"
28
+ "composite": "d6d6dfcd5c8b2e4908518689e3727e4ca7e761bf6a407f7d6dc9bbb116ea9ac3"
29
29
  }
@@ -9,8 +9,9 @@
9
9
  * `plunge+file://...` or `plunge://...` runs stock `pulumi` against a
10
10
  * backend scheme only plunge opens, and every read fails.
11
11
  *
12
- * The other direction, `engine: "plunge"` with a stock backend, is not
13
- * reported: plunge is a Pulumi fork and the config may mean it.
12
+ * One direction only, by maintainer ruling (#4559): `engine: "plunge"` with a
13
+ * stock backend is not reported, because plunge opens stock backends and the
14
+ * config may mean it.
14
15
  */
15
16
  import type { PostSynthCheck } from "@intentius/chant/lint/post-synth";
16
17
  export declare const pul015: PostSynthCheck;
@@ -1 +1 @@
1
- {"version":3,"file":"pul015.d.ts","sourceRoot":"","sources":["../../../src/lint/post-synth/pul015.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,EAAE,cAAc,EAAyC,MAAM,kCAAkC,CAAC;AAK9G,eAAO,MAAM,MAAM,EAAE,cAkBpB,CAAC"}
1
+ {"version":3,"file":"pul015.d.ts","sourceRoot":"","sources":["../../../src/lint/post-synth/pul015.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,EAAE,cAAc,EAAyC,MAAM,kCAAkC,CAAC;AAK9G,eAAO,MAAM,MAAM,EAAE,cAkBpB,CAAC"}
@@ -3,7 +3,7 @@ import { lastMemberRead } from "../../workspace-member.js";
3
3
  import { finding } from "./member-read-helpers.js";
4
4
  export const pul015 = {
5
5
  id: "PUL015",
6
- description: "pulumi.member.engine says pulumi, but Pulumi.yaml names a plunge backend",
6
+ description: "pulumi.member.engine says pulumi, but the resolved engine is plunge",
7
7
  check(_ctx) {
8
8
  const read = lastMemberRead();
9
9
  if (read?.engine !== "pulumi")
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pulumi",
3
- "version": "0.124.58",
3
+ "version": "0.124.60",
4
4
  "chantVersion": ">=0.1.0",
5
5
  "namespace": "Pulumi"
6
6
  }
package/dist/okf/index.md CHANGED
@@ -18,5 +18,5 @@ okf_version: '0.2'
18
18
  * [PUL012](/rules/PUL012.md) - The pulumi member's stack export holds resources the stack reads and does not manage (external: true)
19
19
  * [PUL013](/rules/PUL013.md) - The pulumi member's stack export has a deployment version other than 3, the one chant reads
20
20
  * [PUL014](/rules/PUL014.md) - The pulumi member's stack export reads but holds no resources besides the stack and its providers
21
- * [PUL015](/rules/PUL015.md) - pulumi.member.engine says pulumi, but Pulumi.yaml names a plunge backend
21
+ * [PUL015](/rules/PUL015.md) - pulumi.member.engine says pulumi, but the resolved engine is plunge
22
22
  * [PUL101](/rules/PUL101.md) - `pulumi.member.dir` is an absolute path written in source.
@@ -1,11 +1,11 @@
1
1
  ---
2
2
  type: post-synth-check
3
3
  title: PUL015
4
- description: pulumi.member.engine says pulumi, but Pulumi.yaml names a plunge backend
4
+ description: pulumi.member.engine says pulumi, but the resolved engine is plunge
5
5
  id: PUL015
6
6
  severity: error
7
7
  category: post-synth
8
8
  lexicon: pulumi
9
9
  docs: https://intentius.io/chant/lexicons/pulumi/rules/
10
10
  ---
11
- pulumi.member.engine says pulumi, but Pulumi.yaml names a plunge backend
11
+ pulumi.member.engine says pulumi, but the resolved engine is plunge
@@ -26,6 +26,8 @@
26
26
  * through `DeployContext.lockGrain`, else `resource` on plunge and
27
27
  * `target` on stock Pulumi, #4552) the digest leaves `same` and `read`
28
28
  * steps out (ruling B);
29
+ * - `resourceDigests`: one digest per resource the preview changes, by URN,
30
+ * whatever the grain (plywood#55);
29
31
  * - `outputs` from the stack step's planned outputs;
30
32
  * - `shape` from `pulumiPlanShape` (`./plan-shape.ts`);
31
33
  * - `artifact`: the engine, the shape, the digest, CrossGuard's findings,
@@ -1 +1 @@
1
- {"version":3,"file":"pulumi-up.d.ts","sourceRoot":"","sources":["../src/pulumi-up.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsIG;AAMH,OAAO,KAAK,EACV,UAAU,EAEV,YAAY,EACZ,kBAAkB,EAClB,oBAAoB,EAEpB,aAAa,EACb,oBAAoB,EACpB,oBAAoB,EACrB,MAAM,wCAAwC,CAAC;AAChD,OAAO,EAAuB,KAAK,SAAS,EAAE,MAAM,2CAA2C,CAAC;AAChG,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAEvE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2CAA2C,CAAC;AAG/E,OAAO,EAWL,KAAK,kBAAkB,EACxB,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EAAsB,KAAK,kBAAkB,EAAE,KAAK,wBAAwB,EAAE,MAAM,iBAAiB,CAAC;AAG7G,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAKnE,OAAO,EAcL,KAAK,gBAAgB,EACrB,KAAK,YAAY,EAClB,MAAM,OAAO,CAAC;AAEf,OAAO,EAA6B,KAAK,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAM5F,kEAAkE;AAClE,MAAM,MAAM,eAAe,GAAG,QAAQ,GAAG,UAAU,GAAG,MAAM,CAAC;AAE7D,oCAAoC;AACpC,MAAM,WAAW,iBAAiB;IAChC,2FAA2F;IAC3F,GAAG,EAAE,MAAM,CAAC;IACZ,2DAA2D;IAC3D,KAAK,EAAE,MAAM,CAAC;IACd,uGAAuG;IACvG,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oGAAoG;IACpG,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,sDAAsD;IACtD,MAAM,CAAC,EAAE,kBAAkB,CAAC;IAC5B;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;;;OAOG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,4HAA4H;IAC5H,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,sFAAsF;IACtF,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,0IAA0I;IAC1I,SAAS,CAAC,EAAE,eAAe,CAAC;IAC5B;;;;OAIG;IACH,WAAW,CAAC,EAAE,gBAAgB,EAAE,CAAC;IACjC;;;;;;OAMG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAC1D;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,0BAA0B;AAC1B,MAAM,WAAW,kBAAkB;IACjC,0BAA0B;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,kBAAkB,CAAC;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,yFAAyF;IACzF,YAAY,EAAE,OAAO,CAAC;IACtB,oFAAoF;IACpF,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,6EAA6E;IAC7E,OAAO,CAAC,EAAE,kBAAkB,CAAC;IAC7B,kEAAkE;IAClE,MAAM,CAAC,EAAE,kBAAkB,CAAC;IAC5B,yFAAyF;IACzF,cAAc,CAAC,EAAE,iBAAiB,CAAC;CACpC;AAED,qEAAqE;AACrE,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,WAAW,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,kBAAkB,CAAC;IAC3B,gFAAgF;IAChF,YAAY,EAAE,wBAAwB,CAAC;IACvC,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,SAAS,CAAC;IACjB,mEAAmE;IACnE,OAAO,EAAE,MAAM,CAAC;IAChB,wCAAwC;IACxC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,0EAA0E;IAC1E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qDAAqD;IACrD,MAAM,EAAE,kBAAkB,CAAC;IAC3B;;;;;OAKG;IACH,WAAW,CAAC,EAAE,mBAAmB,CAAC;IAClC;;;;;;;OAOG;IACH,eAAe,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,EAAE,CAAC;QAAC,OAAO,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,GAAG,EAAE,MAAM,CAAA;SAAE,EAAE,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACpG;;;;;OAKG;IACH,cAAc,CAAC,EAAE,kBAAkB,EAAE,CAAC;CACvC;AAED,wDAAwD;AACxD,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,KAAK,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,aAAa,GAAG,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC3F,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,MAAM,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;CACtE;AAgCD,yHAAyH;AACzH,qBAAa,oBAAqB,SAAQ,KAAK;aAG3B,MAAM,EAAE,MAAM;IAFhC,QAAQ,CAAC,SAAS,QAAQ;IAC1B,YACkB,MAAM,EAAE,MAAM,EAC9B,OAAO,EAAE,MAAM,EAIhB;CACF;AAED,qGAAqG;AACrG,qBAAa,uBAAwB,SAAQ,KAAK;aAK9B,MAAM,EAAE,MAAM;aACd,QAAQ,EAAE,kBAAkB;IAL9C,QAAQ,CAAC,YAAY,QAAQ;IAC7B,+DAA+D;IAC/D,QAAQ,CAAC,QAAQ,IAAqB;IACtC,YACkB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,kBAAkB,EAC5C,MAAM,CAAC,EAAE,MAAM,EAKhB;CACF;AAED;;;;GAIG;AACH,qBAAa,uBAAwB,SAAQ,KAAK;aAE9B,MAAM,EAAE,MAAM;aACd,WAAW,EAAE,MAAM,EAAE;aACrB,OAAO,CAAC,EAAE,kBAAkB;IAH9C,YACkB,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EAAE,EACrB,OAAO,CAAC,EAAE,kBAAkB,YAAA,EAI7C;CACF;AAED;;;;;GAKG;AACH,qBAAa,mBAAoB,SAAQ,KAAK;aAE1B,MAAM,EAAE,MAAM;aAEd,OAAO,CAAC,EAAE,kBAAkB;IAH9C,YACkB,MAAM,EAAE,MAAM,EAC9B,OAAO,EAAE,MAAM,EACC,OAAO,CAAC,EAAE,kBAAkB,YAAA,EAI7C;CACF;AAED,oHAAoH;AACpH,qBAAa,+BAAgC,SAAQ,KAAK;aAC5B,MAAM,EAAE,MAAM;IAA1C,YAA4B,MAAM,EAAE,MAAM,EAGzC;CACF;AAED,qHAAqH;AACrH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,kBAAkB,GAAG,eAAe,CAElF;AAED,kEAAkE;AAClE,MAAM,WAAW,eAAe;IAC9B,GAAG,CAAC,EAAE,YAAY,CAAC;IACnB,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,4FAA4F;IAC5F,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,YAAY,CAAC;IACtC,6EAA6E;IAC7E,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IACzC,8DAA8D;IAC9D,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,uLAAuL;IACvL,SAAS,CAAC,EAAE,aAAa,GAAG,IAAI,CAAC;CAClC;AA0BD,sEAAsE;AACtE,wBAAgB,cAAc,CAAC,KAAK,EAAE,iBAAiB,GAAG,MAAM,CAI/D;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAkBvG;AAED,mGAAmG;AACnG,eAAO,MAAM,wBAAwB,uBAAuB,CAAC;AAE7D;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,iBAAiB,EAAE,GAAG,EAAE,MAAM,GAAG,SAAS,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,iBAAiB,CAAC;IAAC,MAAM,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,CAOvL;AA+DD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,IAAI,CAAC,iBAAiB,EAAE,eAAe,GAAG,YAAY,CAAC,EAAE,MAAM,EAAE,kBAAkB,GAAG,MAAM,GAAG,SAAS,CAWhJ;AA4ND,mGAAmG;AACnG,wBAAgB,eAAe,CAAC,QAAQ,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAIrE;AAED;;;;;;GAMG;AACH,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,oBAAoB,EACzB,OAAO,EAAE,SAAS,kBAAkB,EAAE,EACtC,IAAI,GAAE;IAAE,WAAW,CAAC,EAAE,aAAa,GAAG,IAAI,CAAA;CAAO,GAChD,OAAO,CAAC,YAAY,EAAE,CAAC,CAuBzB;AAyBD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS,CAK9E;AAgHD,0GAA0G;AAC1G,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,iBAAiB,EAAE,GAAG,EAAE,oBAAoB,GAAG,aAAa,GAAG,oBAAoB,GAAG,IAAI,CAqBpI;AAED,qEAAqE;AACrE,wBAAgB,wBAAwB,CAAC,IAAI,GAAE,eAAoB,GAAG,UAAU,CAAC,iBAAiB,EAAE,kBAAkB,CAAC,CAwDtH;AAED,eAAO,MAAM,kBAAkB,mDAA6B,CAAC"}
1
+ {"version":3,"file":"pulumi-up.d.ts","sourceRoot":"","sources":["../src/pulumi-up.ts"],"names":[],"mappings":"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"}
package/dist/pulumi-up.js CHANGED
@@ -26,6 +26,8 @@
26
26
  * through `DeployContext.lockGrain`, else `resource` on plunge and
27
27
  * `target` on stock Pulumi, #4552) the digest leaves `same` and `read`
28
28
  * steps out (ruling B);
29
+ * - `resourceDigests`: one digest per resource the preview changes, by URN,
30
+ * whatever the grain (plywood#55);
29
31
  * - `outputs` from the stack step's planned outputs;
30
32
  * - `shape` from `pulumiPlanShape` (`./plan-shape.ts`);
31
33
  * - `artifact`: the engine, the shape, the digest, CrossGuard's findings,
@@ -139,7 +141,7 @@ import { basename, isAbsolute, join, relative, resolve } from "node:path";
139
141
  import { PlanShapeMovedError } from "@intentius/chant/components/covered-round";
140
142
  import { computePlanDigest } from "@intentius/chant/lifecycle/plan-digest";
141
143
  import { parseYAML } from "@intentius/chant/yaml";
142
- import { pulumiPreviewPart } from "./change-set.js";
144
+ import { pulumiPreviewPart, pulumiResourceDigests } from "./change-set.js";
143
145
  import { PULUMI_STREAMING_PREVIEW_ENV, pulumiAppliedSteps, pulumiFailedUrns, pulumiLandedUrns, pulumiPolicyEvents, pulumiPolicyFindings, pulumiStreamError, pulumiStreamPreview, pulumiStreamSummary, pulumiStreamTimings, } from "./engine-events.js";
144
146
  import { CHANT_EXIT } from "@intentius/chant/exit-codes";
145
147
  import { pulumiExitOf } from "./exit-codes.js";
@@ -565,6 +567,8 @@ async function planStack(ctx, original, opts) {
565
567
  planDigest,
566
568
  artifact,
567
569
  changeSet: read.part,
570
+ // plywood#55: each changed resource's own digest, which a merged wave compares with the pull request's per resource.
571
+ resourceDigests: pulumiResourceDigests(read.preview),
568
572
  outputs: { known: planned.known, unknown: planned.unknown },
569
573
  shape: read.shape,
570
574
  ...(ctx.targets?.length ? { targets: ctx.targets } : {}),
@@ -9,8 +9,9 @@
9
9
  * `plunge+file://...` or `plunge://...` runs stock `pulumi` against a
10
10
  * backend scheme only plunge opens, and every read fails.
11
11
  *
12
- * The other direction, `engine: "plunge"` with a stock backend, is not
13
- * reported: plunge is a Pulumi fork and the config may mean it.
12
+ * One direction only, by maintainer ruling (#4559): `engine: "plunge"` with a
13
+ * stock backend is not reported, because plunge opens stock backends and the
14
+ * config may mean it.
14
15
  */
15
16
  import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
16
17
  import { pulumiMemberEngine } from "../../run";
@@ -19,7 +20,7 @@ import { finding } from "./member-read-helpers";
19
20
 
20
21
  export const pul015: PostSynthCheck = {
21
22
  id: "PUL015",
22
- description: "pulumi.member.engine says pulumi, but Pulumi.yaml names a plunge backend",
23
+ description: "pulumi.member.engine says pulumi, but the resolved engine is plunge",
23
24
 
24
25
  check(_ctx: PostSynthContext): PostSynthDiagnostic[] {
25
26
  const read = lastMemberRead();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intentius/chant-lexicon-pulumi",
3
- "version": "0.124.58",
3
+ "version": "0.124.60",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "src/",
@@ -54,7 +54,7 @@
54
54
  "access": "public"
55
55
  },
56
56
  "peerDependencies": {
57
- "@intentius/chant": "^0.124.58",
57
+ "@intentius/chant": "^0.124.60",
58
58
  "zod": "^4.3.6"
59
59
  },
60
60
  "description": "pulumi lexicon for chant: a Pulumi program as a workspace member, its preview in the change set and its refresh as drift records",
package/src/change-set.ts CHANGED
@@ -68,7 +68,11 @@
68
68
  * each step's op, URN, provider, id, inputs before and after, `diffReasons`,
69
69
  * `replaceReasons` and `detailedDiff`, sorted by URN and op. It leaves out
70
70
  * `config`, `duration`, `diagnostics` and `changeSummary`, and the states'
71
- * outputs, which at preview are the last deployment's or unknown. For a
71
+ * outputs, which at preview are the last deployment's or unknown. It also
72
+ * leaves out what the engine makes up while planning a create or a
73
+ * replacement (plywood#56): an autoname such as `events-3f45941` is drawn
74
+ * again by every preview, so it reads as {@link PULUMI_GENERATED}. See
75
+ * {@link pulumiPlannedInputs}. Unknowns already print as one fixed string. For a
72
76
  * member on the resource lock grain the `pulumi-up` capability passes
73
77
  * `changedOnly` (#4541, ruling B), which leaves `same` and `read` steps out.
74
78
  *
@@ -164,31 +168,104 @@ export interface PulumiPreviewDigestOptions {
164
168
  changedOnly?: boolean;
165
169
  }
166
170
 
171
+ /** What stands in a digest for an input the engine made up while planning (plywood#56). */
172
+ export const PULUMI_GENERATED = "<generated at plan time>";
173
+
174
+ /** Pulumi's autonaming: the logical name, a dash and seven random hex characters. */
175
+ const AUTONAME = /^(.+)-[0-9a-f]{7}$/;
176
+
177
+ /** The step's resource name: the last part of its URN. */
178
+ function logicalName(urn: string | undefined): string {
179
+ const at = urn?.lastIndexOf("::") ?? -1;
180
+ return urn && at >= 0 ? urn.slice(at + 2) : (urn ?? "");
181
+ }
182
+
183
+ /**
184
+ * The inputs a step would write, with the values the engine made up while
185
+ * planning replaced by {@link PULUMI_GENERATED} (plywood#56). Two previews of
186
+ * one create draw different autonames (`events-3f45941`, then
187
+ * `events-9c01d2e`), and a digest over them could never match a later plan of
188
+ * the same change.
189
+ *
190
+ * Only a create or a replacement draws a name: an update's inputs keep the one
191
+ * the resource was created with, and that stays in `before` and `after`. A
192
+ * value counts as generated when the engine lists its key in `__defaults`
193
+ * (the inputs the provider filled in, not the program) and it reads as an
194
+ * autoname of this resource: its logical name, or the start of it when the
195
+ * provider shortened it, then a dash and seven hex characters. An engine that
196
+ * prints no `__defaults` is held to the exact logical name. Every other input
197
+ * stays in, the defaults that are plain values included, so a changed input
198
+ * still changes the digest. `before` is the recorded state and is never masked.
199
+ */
200
+ export function pulumiPlannedInputs(step: PulumiStep): Json | null {
201
+ const inputs = step.newState?.inputs;
202
+ if (!isObject(inputs)) return inputs ?? null;
203
+ const draws = step.oldState === undefined || step.op === "create" || step.op === "create-replacement" || step.op === "replace";
204
+ if (!draws) return inputs;
205
+ const name = logicalName(step.urn).toLowerCase();
206
+ const filled = Array.isArray(inputs.__defaults) ? new Set(inputs.__defaults.filter((k): k is string => typeof k === "string")) : undefined;
207
+ const out: Json = {};
208
+ for (const [key, value] of Object.entries(inputs)) {
209
+ const stem = typeof value === "string" ? AUTONAME.exec(value)?.[1]?.toLowerCase() : undefined;
210
+ const generated = stem !== undefined && name !== "" && (filled ? filled.has(key) && (name.startsWith(stem) || stem.startsWith(name)) : stem === name);
211
+ out[key] = generated ? PULUMI_GENERATED : value;
212
+ }
213
+ return out;
214
+ }
215
+
216
+ /** One step as a digest sees it: what it proposes, without the values the engine made up. */
217
+ function digestStep(s: PulumiStep) {
218
+ return {
219
+ op: s.op ?? null,
220
+ urn: s.urn ?? null,
221
+ provider: s.provider ?? null,
222
+ id: s.newState?.id ?? s.oldState?.id ?? null,
223
+ before: s.oldState?.inputs ?? null,
224
+ after: pulumiPlannedInputs(s),
225
+ diffReasons: [...(s.diffReasons ?? [])].sort(),
226
+ replaceReasons: [...(s.replaceReasons ?? [])].sort(),
227
+ detailedDiff: s.detailedDiff ?? null,
228
+ };
229
+ }
230
+
231
+ const byStep = (a: { urn: string | null; op: string | null }, b: { urn: string | null; op: string | null }): number =>
232
+ byString(`${a.urn}\u0000${a.op}`, `${b.urn}\u0000${b.op}`);
233
+
167
234
  /**
168
235
  * The member's plan digest: `computePlanDigest("pulumi-preview", …)` over
169
236
  * what each step proposes, sorted by URN and op, so two renderings of one
170
- * preview agree. See the module comment for what it leaves out. With
171
- * `changedOnly`, `same` and `read` steps are left out too, under the label
172
- * `pulumi-preview-changed`, so the two digests never collide.
237
+ * preview agree. See the module comment for what it leaves out, and
238
+ * {@link pulumiPlannedInputs} for the values the engine made up that it leaves
239
+ * out of a create. With `changedOnly`, `same` and `read` steps are left out
240
+ * too, under the label `pulumi-preview-changed`, so the two digests never
241
+ * collide.
173
242
  */
174
243
  export function pulumiPreviewDigest(preview: unknown, opts: PulumiPreviewDigestOptions = {}): string {
175
244
  const steps = (pulumiSteps(preview) ?? [])
176
245
  .filter((s) => !opts.changedOnly || !PULUMI_UNCHANGED_OPS.has(s.op ?? ""))
177
- .map((s) => ({
178
- op: s.op ?? null,
179
- urn: s.urn ?? null,
180
- provider: s.provider ?? null,
181
- id: s.newState?.id ?? s.oldState?.id ?? null,
182
- before: s.oldState?.inputs ?? null,
183
- after: s.newState?.inputs ?? null,
184
- diffReasons: [...(s.diffReasons ?? [])].sort(),
185
- replaceReasons: [...(s.replaceReasons ?? [])].sort(),
186
- detailedDiff: s.detailedDiff ?? null,
187
- }))
188
- .sort((a, b) => byString(`${a.urn}\u0000${a.op}`, `${b.urn}\u0000${b.op}`));
246
+ .map(digestStep)
247
+ .sort(byStep);
189
248
  return computePlanDigest(opts.changedOnly ? "pulumi-preview-changed" : "pulumi-preview", steps);
190
249
  }
191
250
 
251
+ /**
252
+ * The digest of each resource the preview changes, by URN (plywood#55): the
253
+ * steps of that URN (a replace is several), as {@link pulumiPreviewDigest}
254
+ * reads them, with the values the engine made up left out. A `same` or `read` step is not in it whatever the lock grain,
255
+ * so what a pull request left alone never makes its plan differ from a plan
256
+ * made after another change landed on it.
257
+ */
258
+ export function pulumiResourceDigests(preview: unknown): Record<string, string> {
259
+ const byUrn = new Map<string, ReturnType<typeof digestStep>[]>();
260
+ for (const s of pulumiSteps(preview) ?? []) {
261
+ if (PULUMI_UNCHANGED_OPS.has(s.op ?? "")) continue;
262
+ const list = byUrn.get(s.urn!) ?? [];
263
+ list.push(digestStep(s));
264
+ byUrn.set(s.urn!, list);
265
+ }
266
+ return Object.fromEntries([...byUrn].sort(([a], [b]) => byString(a, b)).map(([urn, steps]) => [urn, computePlanDigest("pulumi-resource", steps.sort(byStep))]));
267
+ }
268
+
192
269
  const REPLACE_OPS = new Set(["replace", "create-replacement", "delete-replaced"]);
193
270
 
194
271
  /** The action and disruption one URN's steps mean, or undefined for an op the change set has no action for. */
@@ -9,13 +9,16 @@
9
9
  * `plunge-member`'s export is a cut of the recorded plunge stack's. No binary
10
10
  * runs here.
11
11
  */
12
- import { readFileSync } from "node:fs";
12
+ import { cpSync, mkdirSync, mkdtempSync, readFileSync } from "node:fs";
13
+ import { tmpdir } from "node:os";
13
14
  import { join } from "node:path";
14
15
  import { describe, expect, test } from "vitest";
16
+ import { createSnapshotStore } from "@intentius/chant/lifecycle/snapshot-store";
15
17
  import { foldDriftLines, type ReadDriftOptions } from "@intentius/chant/drift-record";
16
18
  import { readPulumiDrift } from "./drift-read";
17
19
  import { pulumiPlugin } from "./plugin";
18
20
  import type { PulumiRunner } from "./run";
21
+ import { writePulumiExportSnapshot } from "./stack-export-snapshot";
19
22
 
20
23
  const testdata = join(import.meta.dirname, "testdata");
21
24
  const text = (...path: string[]) => readFileSync(join(testdata, ...path), "utf-8");
@@ -104,3 +107,72 @@ describe("readPulumiDrift", () => {
104
107
  expect(never.calls).toEqual([]);
105
108
  });
106
109
  });
110
+
111
+ describe("readPulumiDrift with --stack", () => {
112
+ const exportText = text("pulumi-preview", "stack-export.json");
113
+
114
+ /** A member directory with Pulumi.yaml and no checked-in export, and a store holding the stacks named. */
115
+ async function memberWithStore(stacks: string[]) {
116
+ const dir = mkdtempSync(join(tmpdir(), "pulumi-drift-stack-"));
117
+ cpSync(join(testdata, "pulumi-preview", "Pulumi.yaml"), join(dir, "Pulumi.yaml"));
118
+ mkdirSync(join(dir, "node_modules"));
119
+ const store = createSnapshotStore({ store: "local", dir: mkdtempSync(join(tmpdir(), "pulumi-drift-store-")) });
120
+ for (const stack of stacks) {
121
+ const wrote = await writePulumiExportSnapshot({
122
+ dir,
123
+ stack,
124
+ environment: "prod",
125
+ engine: "pulumi",
126
+ run: async () => ({ stdout: exportText }),
127
+ store,
128
+ });
129
+ expect(wrote.written, JSON.stringify(wrote)).toBe(true);
130
+ }
131
+ return { dir, store };
132
+ }
133
+
134
+ test("reads the named stack's stored export and passes that stack to every refresh", async () => {
135
+ const { dir, store } = await memberWithStore(["staging", "prod-eu"]);
136
+ const pulumi = fake(() => text("pulumi-refresh", "bucket-edited.json"));
137
+ const answer = await readPulumiDrift(options(dir, { stack: "prod-eu" }), { run: pulumi.run, plunge: never.run, exportStore: store });
138
+ if (!("lines" in answer)) throw new Error(JSON.stringify(answer));
139
+ expect(pulumi.calls.length).toBeGreaterThan(0);
140
+ for (const call of pulumi.calls) expect(call.slice(call.indexOf("--stack"), call.indexOf("--stack") + 2)).toEqual(["--stack", "prod-eu"]);
141
+ });
142
+
143
+ test("beats the member's declared stack", async () => {
144
+ const { dir, store } = await memberWithStore(["staging", "prod-eu"]);
145
+ const pulumi = fake(() => text("pulumi-refresh", "bucket-edited.json"));
146
+ const config = { pulumi: { member: { dir, label: "site", stack: "staging" } } };
147
+ const answer = await readPulumiDrift({ ...options(dir, { stack: "prod-eu", resource: bucket }), config }, { run: pulumi.run, plunge: never.run, exportStore: store });
148
+ expect("lines" in answer).toBe(true);
149
+ expect(pulumi.calls).toEqual([["refresh", "--preview-only", "--json", "--stack", "prod-eu", "--target", bucket]]);
150
+ });
151
+
152
+ test("a store with no export for that stack is an error naming the stack, and nothing runs", async () => {
153
+ const { dir, store } = await memberWithStore(["staging"]);
154
+ const spy = fake(() => text("pulumi-refresh", "bucket-edited.json"));
155
+ const answer = await readPulumiDrift(options(dir, { stack: "prod-eu" }), { run: spy.run, plunge: never.run, exportStore: store });
156
+ expect(answer).toMatchObject({ refused: { code: "no-drift-read" } });
157
+ expect("refused" in answer && answer.refused.message).toMatch(/holds no export for stack prod-eu of site/);
158
+ expect(spy.calls).toEqual([]);
159
+ });
160
+
161
+ test("an empty store is refused too, though a checked-in export sits beside the member", async () => {
162
+ const dir = join(testdata, "pulumi-preview");
163
+ const store = createSnapshotStore({ store: "local", dir: mkdtempSync(join(tmpdir(), "pulumi-drift-store-")) });
164
+ const spy = fake(() => text("pulumi-refresh", "bucket-edited.json"));
165
+ const answer = await readPulumiDrift(options(dir, { stack: "floci" }), { run: spy.run, plunge: never.run, exportStore: store });
166
+ expect(answer).toMatchObject({ refused: { code: "no-drift-read" } });
167
+ expect(spy.calls).toEqual([]);
168
+ });
169
+
170
+ test("without --stack the read is as before: the checked-in export when the store has none", async () => {
171
+ const dir = join(testdata, "pulumi-preview");
172
+ const store = createSnapshotStore({ store: "local", dir: mkdtempSync(join(tmpdir(), "pulumi-drift-store-")) });
173
+ const pulumi = fake(() => text("pulumi-refresh", "bucket-edited.json"));
174
+ const answer = await readPulumiDrift(options(dir, { resource: bucket }), { run: pulumi.run, plunge: never.run, exportStore: store });
175
+ expect("lines" in answer).toBe(true);
176
+ expect(pulumi.calls).toEqual([["refresh", "--preview-only", "--json", "--stack", "floci", "--target", bucket]]);
177
+ });
178
+ });
package/src/drift-read.ts CHANGED
@@ -13,8 +13,12 @@
13
13
  * | plunge | unset | `plunge drift --resource <urn> --stream`, once per resource in the export |
14
14
  * | plunge | `<urn>` | the same, for that URN alone |
15
15
  *
16
- * Each read passes `--stack` (#4496): the member's declared
17
- * `pulumi.member.stack`, else the stack the export's URNs name.
16
+ * Each read passes `--stack` (#4496): the stack asked for (`chant workspace
17
+ * drift --stack`), else the member's declared `pulumi.member.stack`, else the
18
+ * stack the export's URNs name. A stack asked for is read from that stack's
19
+ * export in the index store alone; with none there the read is refused
20
+ * (`no-drift-read`), where without `--stack` the member reads the store's
21
+ * export, then the checked-in file.
18
22
  *
19
23
  * The one read is `refresh`. The member's engine is `pulumiMemberEngine`'s:
20
24
  * the member config's `engine`, then a plunge backend URL in `Pulumi.yaml`.
@@ -27,11 +31,15 @@ import type { DriftLine, DriftRecord, ReadDriftOptions, ReadDriftResult } from "
27
31
  import { driftRecordLine, driftSettle, driftStart } from "@intentius/chant/drift-record";
28
32
  import type { PulumiConfig } from "./config";
29
33
  import { pulumiMemberEngine, refreshPulumiMember, type PulumiRunner } from "./run";
34
+ import { readStoredPulumiExport } from "./stack-export-snapshot";
35
+ import type { SnapshotStore } from "@intentius/chant/lifecycle/snapshot-store";
30
36
 
31
37
  /** The runners, injectable so the tests run no binary. */
32
38
  export interface PulumiDriftRunners {
33
39
  run?: PulumiRunner;
34
40
  plunge?: PulumiRunner;
41
+ /** The index store the exports are read from. Default: the project's. */
42
+ exportStore?: SnapshotStore | null;
35
43
  }
36
44
 
37
45
  /** `LexiconPlugin.readDrift` for the pulumi lexicon. */
@@ -42,6 +50,18 @@ export async function readPulumiDrift(options: ReadDriftOptions, runners: Pulumi
42
50
  return { refused: { code: "read-unsupported", message: `a Pulumi member answers refresh, not ${options.read}` } };
43
51
  }
44
52
  const { engine } = pulumiMemberEngine(member.dir, member.engine);
53
+ const stack = options.stack ?? member.stack;
54
+ if (options.stack !== undefined) {
55
+ const stored = await readStoredPulumiExport({ dir: member.dir, stack: options.stack, ...(runners.exportStore !== undefined ? { store: runners.exportStore } : {}) });
56
+ if (!stored) {
57
+ return {
58
+ refused: {
59
+ code: "no-drift-read",
60
+ message: `the index store holds no export for stack ${options.stack} of ${options.member}; the pulumi-up capability writes one after each apply through chant, so apply that stack once, or read without --stack`,
61
+ },
62
+ };
63
+ }
64
+ }
45
65
  let records: DriftRecord[];
46
66
  try {
47
67
  records = await refreshPulumiMember({
@@ -49,7 +69,8 @@ export async function readPulumiDrift(options: ReadDriftOptions, runners: Pulumi
49
69
  dir: member.dir,
50
70
  observedAt: options.observedAt,
51
71
  ...(member.engine ? { engine: member.engine } : {}),
52
- ...(member.stack ? { stack: member.stack } : {}),
72
+ ...(stack ? { stack } : {}),
73
+ ...(runners.exportStore !== undefined ? { exportStore: runners.exportStore } : {}),
53
74
  ...(options.resource !== undefined ? { urns: [options.resource] } : {}),
54
75
  ...(runners.run ? { run: runners.run } : {}),
55
76
  ...(runners.plunge ? { plunge: runners.plunge } : {}),
package/src/index.ts CHANGED
@@ -5,6 +5,9 @@ export { readPulumiMember, PULUMI_EXPORT_FILE, type PulumiMemberConfig, type Pul
5
5
  export {
6
6
  pulumiPreviewPart,
7
7
  pulumiPreviewDigest,
8
+ pulumiResourceDigests,
9
+ pulumiPlannedInputs,
10
+ PULUMI_GENERATED,
8
11
  pulumiSteps,
9
12
  pulumiExportResources,
10
13
  type PulumiPreviewPartInput,
@@ -1,5 +1,7 @@
1
+ import { mkdirSync, mkdtempSync, writeFileSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
1
3
  import { join } from "node:path";
2
- import { afterEach, describe, expect, test } from "vitest";
4
+ import { afterEach, describe, expect, test, vi } from "vitest";
3
5
  import type { PostSynthContext } from "@intentius/chant/lint/post-synth";
4
6
  import { PULUMI_SECRET_SIG, PULUMI_SIG_KEY } from "../../values";
5
7
  import { setLastMemberRead, setLastMemberReport } from "../../workspace-member";
@@ -23,6 +25,7 @@ import { pul015 } from "./pul015";
23
25
  const ctx = { outputs: new Map(), entities: new Map() } as unknown as PostSynthContext;
24
26
 
25
27
  afterEach(() => {
28
+ vi.unstubAllEnvs();
26
29
  setLastMemberReport(undefined);
27
30
  setLastMemberRead(undefined);
28
31
  });
@@ -196,4 +199,47 @@ describe("pulumi post-synth checks", () => {
196
199
  readWith([], { dir: stockDir, engine: "plunge" });
197
200
  expect(pul015.check(ctx)).toEqual([]);
198
201
  });
202
+
203
+ test("PUL015 names the source of the resolved plunge engine", () => {
204
+ // Pulumi.yaml
205
+ readWith([], { dir: plungeDir, engine: "pulumi" });
206
+ expect(pul015.check(ctx)[0].message).toContain("Pulumi.yaml names the plunge backend");
207
+
208
+ // PULUMI_BACKEND_URL, on a program whose Pulumi.yaml names no backend
209
+ vi.stubEnv("PULUMI_BACKEND_URL", "plunge+s3://bucket/state");
210
+ readWith([], { dir: stockDir, engine: "pulumi" });
211
+ const fromEnv = pul015.check(ctx);
212
+ expect(fromEnv).toHaveLength(1);
213
+ expect(fromEnv[0].message).toContain("PULUMI_BACKEND_URL names the plunge backend plunge+s3://bucket/state");
214
+ readWith([], { dir: stockDir, engine: "plunge" });
215
+ expect(pul015.check(ctx)).toEqual([]);
216
+ vi.stubEnv("PULUMI_BACKEND_URL", "s3://bucket/state");
217
+ readWith([], { dir: stockDir, engine: "pulumi" });
218
+ expect(pul015.check(ctx)).toEqual([]);
219
+ vi.unstubAllEnvs();
220
+
221
+ // plywood.yml, engine and backend
222
+ const root = mkdtempSync(join(tmpdir(), "pul015-"));
223
+ mkdirSync(join(root, ".git"));
224
+ const member = join(root, "infra");
225
+ mkdirSync(member);
226
+ writeFileSync(join(member, "Pulumi.yaml"), "name: app\nruntime: nodejs\n");
227
+ writeFileSync(join(root, "plywood.yml"), "engine: plunge\n");
228
+ readWith([], { dir: member, engine: "pulumi" });
229
+ const fromEngine = pul015.check(ctx);
230
+ expect(fromEngine).toHaveLength(1);
231
+ expect(fromEngine[0].message).toContain("plywood.yml says engine: plunge");
232
+ writeFileSync(join(root, "plywood.yml"), "backend: plunge+file:///state\n");
233
+ const fromBackend = pul015.check(ctx);
234
+ expect(fromBackend).toHaveLength(1);
235
+ expect(fromBackend[0].message).toContain("plywood.yml names the plunge backend plunge+file:///state");
236
+ writeFileSync(join(root, "plywood.yml"), "engine: pulumi\n");
237
+ expect(pul015.check(ctx)).toEqual([]);
238
+ });
239
+
240
+ test("PUL015 does not report the other direction: engine plunge on a stock backend", () => {
241
+ vi.stubEnv("PULUMI_BACKEND_URL", "s3://bucket/state");
242
+ readWith([], { dir: stockDir, engine: "plunge" });
243
+ expect(pul015.check(ctx)).toEqual([]);
244
+ });
199
245
  });
@@ -9,8 +9,9 @@
9
9
  * `plunge+file://...` or `plunge://...` runs stock `pulumi` against a
10
10
  * backend scheme only plunge opens, and every read fails.
11
11
  *
12
- * The other direction, `engine: "plunge"` with a stock backend, is not
13
- * reported: plunge is a Pulumi fork and the config may mean it.
12
+ * One direction only, by maintainer ruling (#4559): `engine: "plunge"` with a
13
+ * stock backend is not reported, because plunge opens stock backends and the
14
+ * config may mean it.
14
15
  */
15
16
  import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
16
17
  import { pulumiMemberEngine } from "../../run";
@@ -19,7 +20,7 @@ import { finding } from "./member-read-helpers";
19
20
 
20
21
  export const pul015: PostSynthCheck = {
21
22
  id: "PUL015",
22
- description: "pulumi.member.engine says pulumi, but Pulumi.yaml names a plunge backend",
23
+ description: "pulumi.member.engine says pulumi, but the resolved engine is plunge",
23
24
 
24
25
  check(_ctx: PostSynthContext): PostSynthDiagnostic[] {
25
26
  const read = lastMemberRead();
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Values the engine makes up while planning stay out of the digest (plywood#56).
3
+ * `testdata/pulumi-preview/preview-create.json` is a recorded first preview:
4
+ * its queues carry autonames (`events-3f45941`, `worker-0-84f29f5`) the
5
+ * provider filled in, listed in each step's `__defaults`.
6
+ */
7
+ import { readFileSync } from "node:fs";
8
+ import { join } from "node:path";
9
+ import { describe, expect, test } from "vitest";
10
+ import { PULUMI_GENERATED, pulumiPlannedInputs, pulumiPreviewDigest, pulumiResourceDigests, type PulumiStep } from "./change-set";
11
+
12
+ const created = JSON.parse(readFileSync(join(import.meta.dirname, "testdata", "pulumi-preview", "preview-create.json"), "utf-8")) as { steps: PulumiStep[] };
13
+ const copy = () => JSON.parse(JSON.stringify(created)) as typeof created;
14
+ const stepOf = (doc: typeof created, name: string) => doc.steps.find((s) => s.urn?.endsWith(`::${name}`))!;
15
+ const inputsOf = (doc: typeof created, name: string) => stepOf(doc, name).newState!.inputs as Record<string, unknown>;
16
+
17
+ /** The same preview drawn again: every autoname gets another suffix. */
18
+ function replanned(): typeof created {
19
+ const doc = copy();
20
+ for (const s of doc.steps) {
21
+ const inputs = s.newState?.inputs as Record<string, unknown> | undefined;
22
+ if (!inputs || !Array.isArray(inputs.__defaults)) continue;
23
+ for (const key of inputs.__defaults as string[]) {
24
+ const v = inputs[key];
25
+ if (typeof v === "string" && /-[0-9a-f]{7}$/.test(v)) inputs[key] = v.replace(/[0-9a-f]{7}$/, "0123abc");
26
+ }
27
+ }
28
+ return doc;
29
+ }
30
+
31
+ describe("a create's digest leaves out what the engine drew while planning", () => {
32
+ test("the recorded preview holds autonames, and a second draw gives the same digest", () => {
33
+ expect(inputsOf(created, "events").name).toBe("events-3f45941");
34
+ expect(inputsOf(replanned(), "events").name).toBe("events-0123abc");
35
+ expect(pulumiPreviewDigest(replanned())).toBe(pulumiPreviewDigest(created));
36
+ expect(pulumiPreviewDigest(replanned(), { changedOnly: true })).toBe(pulumiPreviewDigest(created, { changedOnly: true }));
37
+ });
38
+
39
+ test("the name is replaced by one marker, and the rest of the inputs are kept", () => {
40
+ const planned = pulumiPlannedInputs(stepOf(created, "events")) as Record<string, unknown>;
41
+ expect(planned.name).toBe(PULUMI_GENERATED);
42
+ expect(planned.messageRetentionSeconds).toBe(3600);
43
+ expect(planned.maxMessageSize).toBe(262144);
44
+ expect(planned.tags).toEqual({ "corpus-owner": "plunge", "corpus-program": "aws-ts" });
45
+ });
46
+
47
+ test("two creates that differ in an input a reviewer saw still differ", () => {
48
+ for (const change of [
49
+ (d: typeof created) => { inputsOf(d, "events").messageRetentionSeconds = 7200; },
50
+ (d: typeof created) => { (inputsOf(d, "worker-0").tags as Record<string, string>)["corpus-index"] = "9"; },
51
+ // a default that is a plain value counts: it decides what gets applied
52
+ (d: typeof created) => { inputsOf(d, "events").maxMessageSize = 1024; },
53
+ // a name the program set is not a draw, and a changed one is a change
54
+ (d: typeof created) => { inputsOf(d, "app-role").name = "rec-ts-other"; },
55
+ ]) {
56
+ const other = replanned();
57
+ change(other);
58
+ expect(pulumiPreviewDigest(other)).not.toBe(pulumiPreviewDigest(created));
59
+ }
60
+ });
61
+
62
+ test("a different logical name or a new resource changes the digest", () => {
63
+ const renamed = replanned();
64
+ stepOf(renamed, "events").urn = stepOf(renamed, "events").urn!.replace("::events", "::journal");
65
+ expect(pulumiPreviewDigest(renamed)).not.toBe(pulumiPreviewDigest(created));
66
+ const extra = replanned();
67
+ extra.steps.push({ ...stepOf(extra, "docs-notify"), urn: stepOf(extra, "docs-notify").urn!.replace("::docs-notify", "::more-notify") });
68
+ expect(pulumiPreviewDigest(extra)).not.toBe(pulumiPreviewDigest(created));
69
+ });
70
+
71
+ test("a string that only looks like a suffix is kept unless the engine filled it in", () => {
72
+ const doc = copy();
73
+ const step = stepOf(doc, "events");
74
+ (step.newState!.inputs as Record<string, unknown>).description = "events-3f45941";
75
+ expect((pulumiPlannedInputs(step) as Record<string, unknown>).description).toBe("events-3f45941");
76
+ // a name the program wrote is not listed in __defaults
77
+ (step.newState!.inputs as Record<string, unknown>).__defaults = [];
78
+ expect((pulumiPlannedInputs(step) as Record<string, unknown>).name).toBe("events-3f45941");
79
+ });
80
+
81
+ test("an engine that prints no __defaults is held to the exact logical name", () => {
82
+ const step: PulumiStep = { op: "create", urn: "urn:pulumi:dev::p::aws:sqs/queue:Queue::events", newState: { inputs: { name: "events-3f45941", other: "worker-3f45941" } } };
83
+ expect(pulumiPlannedInputs(step)).toEqual({ name: PULUMI_GENERATED, other: "worker-3f45941" });
84
+ });
85
+
86
+ test("an update keeps its inputs, since it draws no name", () => {
87
+ const update: PulumiStep = {
88
+ op: "update",
89
+ urn: "urn:pulumi:dev::p::aws:sqs/queue:Queue::events",
90
+ oldState: { inputs: { name: "events-3f45941", messageRetentionSeconds: 60 } },
91
+ newState: { inputs: { __defaults: ["name"], name: "events-3f45941", messageRetentionSeconds: 7200 } },
92
+ };
93
+ expect(pulumiPlannedInputs(update)).toBe(update.newState!.inputs);
94
+ const other = JSON.parse(JSON.stringify(update)) as PulumiStep;
95
+ (other.oldState!.inputs as Record<string, unknown>).name = "events-aaaaaaa";
96
+ (other.newState!.inputs as Record<string, unknown>).name = "events-aaaaaaa";
97
+ expect(pulumiPreviewDigest({ steps: [other] })).not.toBe(pulumiPreviewDigest({ steps: [update] }));
98
+ });
99
+
100
+ test("a replacement draws a new name too", () => {
101
+ const replace = (suffix: string): PulumiStep => ({
102
+ op: "replace",
103
+ urn: "urn:pulumi:dev::p::aws:sqs/queue:Queue::events",
104
+ oldState: { inputs: { name: "events-1111111", fifoQueue: false } },
105
+ newState: { inputs: { __defaults: ["name"], name: `events-${suffix}`, fifoQueue: true } },
106
+ replaceReasons: ["fifoQueue"],
107
+ });
108
+ expect(pulumiPreviewDigest({ steps: [replace("2222222")] })).toBe(pulumiPreviewDigest({ steps: [replace("3333333")] }));
109
+ const flipped = replace("2222222");
110
+ (flipped.newState!.inputs as Record<string, unknown>).fifoQueue = false;
111
+ expect(pulumiPreviewDigest({ steps: [flipped] })).not.toBe(pulumiPreviewDigest({ steps: [replace("2222222")] }));
112
+ });
113
+ });
@@ -18,7 +18,7 @@ import { join, relative } from "node:path";
18
18
  import { describe, expect, test } from "vitest";
19
19
  import { isPlanMovedError, type DeployContext } from "@intentius/chant/components/capability";
20
20
  import { PlanShapeMovedError } from "@intentius/chant/components/covered-round";
21
- import { pulumiPreviewDigest } from "./change-set";
21
+ import { pulumiPreviewDigest, pulumiResourceDigests } from "./change-set";
22
22
  import { pulumiStreamPreview, PULUMI_STREAMING_PREVIEW_ENV } from "./engine-events";
23
23
  import {
24
24
  createPulumiUpCapability,
@@ -111,6 +111,17 @@ describe("plan on plunge", () => {
111
111
  expect(artifact.policy.warnings).toHaveLength(1);
112
112
  });
113
113
 
114
+ test("answers a digest for each resource the preview changes, on either grain (plywood#55)", async () => {
115
+ const cap = createPulumiUpCapability({ plunge: engine().run, env: {}, tmp: tmp(), snapshots: null });
116
+ const resource = await cap.plan!(ctx, step({ engine: "plunge" }));
117
+ const stack = await cap.plan!(ctx, step({ engine: "plunge", lockGrain: "target" }));
118
+ expect(resource.resourceDigests).toEqual(pulumiResourceDigests(pulumiStreamPreview(stream)));
119
+ expect(stack.resourceDigests).toEqual(resource.resourceDigests);
120
+ const changed = (pulumiStreamPreview(stream) as { steps: { op: string }[] }).steps.filter((x) => x.op !== "same" && x.op !== "read");
121
+ expect(Object.keys(resource.resourceDigests!).length).toBeGreaterThan(0);
122
+ expect(Object.keys(resource.resourceDigests!).length).toBeLessThanOrEqual(changed.length);
123
+ });
124
+
114
125
  test("the target grain keeps every step in the digest", async () => {
115
126
  const cap = createPulumiUpCapability({ plunge: engine().run, env: {}, tmp: tmp(), snapshots: null });
116
127
  const planned = await cap.plan!(ctx, step({ engine: "plunge", lockGrain: "target" }));
package/src/pulumi-up.ts CHANGED
@@ -26,6 +26,8 @@
26
26
  * through `DeployContext.lockGrain`, else `resource` on plunge and
27
27
  * `target` on stock Pulumi, #4552) the digest leaves `same` and `read`
28
28
  * steps out (ruling B);
29
+ * - `resourceDigests`: one digest per resource the preview changes, by URN,
30
+ * whatever the grain (plywood#55);
29
31
  * - `outputs` from the stack step's planned outputs;
30
32
  * - `shape` from `pulumiPlanShape` (`./plan-shape.ts`);
31
33
  * - `artifact`: the engine, the shape, the digest, CrossGuard's findings,
@@ -154,7 +156,7 @@ import type { GatePolicyFindings } from "@intentius/chant/gate-policy";
154
156
  import { computePlanDigest } from "@intentius/chant/lifecycle/plan-digest";
155
157
  import type { SnapshotStore } from "@intentius/chant/lifecycle/snapshot-store";
156
158
  import { parseYAML } from "@intentius/chant/yaml";
157
- import { pulumiPreviewPart } from "./change-set";
159
+ import { pulumiPreviewPart, pulumiResourceDigests } from "./change-set";
158
160
  import {
159
161
  PULUMI_STREAMING_PREVIEW_ENV,
160
162
  pulumiAppliedSteps,
@@ -795,6 +797,8 @@ async function planStack(ctx: DeployContext, original: PulumiUpStepInput, opts:
795
797
  planDigest,
796
798
  artifact,
797
799
  changeSet: read.part,
800
+ // plywood#55: each changed resource's own digest, which a merged wave compares with the pull request's per resource.
801
+ resourceDigests: pulumiResourceDigests(read.preview),
798
802
  outputs: { known: planned.known, unknown: planned.unknown },
799
803
  shape: read.shape,
800
804
  ...(ctx.targets?.length ? { targets: ctx.targets } : {}),