@go-to-k/cdkd 0.288.0 → 0.288.2

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.
@@ -1,6 +1,6 @@
1
1
  import { _ as withStackName, d as applyDefaultNameForFallback, f as generateResourceName, h as looksLikeCdkdGeneratedName, m as getCurrentStackName, n as getLogger, p as generateResourceNameWithFallback, r as isStdoutReservedForPayload, s as getLiveRenderer } from "./logger-Dw-3y48G.js";
2
2
  import { t as awsClientDefaults } from "./aws-client-defaults-D-iYiIJ6.js";
3
- import { t as getCdkdVersion } from "./version-BQMDIEQY.js";
3
+ import { t as getCdkdVersion } from "./version-BRByznpy.js";
4
4
  import { AsyncLocalStorage } from "node:async_hooks";
5
5
  import { randomUUID } from "node:crypto";
6
6
  import { CreateBucketCommand, DeleteObjectCommand, DeleteObjectsCommand, GetBucketLocationCommand, GetBucketReplicationCommand, GetObjectCommand, HeadBucketCommand, HeadObjectCommand, ListObjectVersionsCommand, ListObjectsV2Command, NoSuchKey, PutBucketEncryptionCommand, PutBucketPolicyCommand, PutObjectCommand, PutPublicAccessBlockCommand, S3Client, S3ServiceException } from "@aws-sdk/client-s3";
@@ -2337,7 +2337,7 @@ async function resolveCrossAccountStateBucket(accountId, credentials) {
2337
2337
  /** Cloud assembly schema version compatible with CDK v2 */
2338
2338
  const CDK_ASM_VERSION = "38.0.0";
2339
2339
  /** Maximum context size before overflow to temp file (32KB) */
2340
- const CONTEXT_OVERFLOW_LIMIT = 32 * 1024;
2340
+ const CONTEXT_OVERFLOW_LIMIT = 32768;
2341
2341
  /**
2342
2342
  * Remove one matched pair of surrounding quotes from a shell token, so a quoted
2343
2343
  * entrypoint (`"bin/app.js"`) is recognized as a .js file and re-quoted cleanly.
@@ -2551,7 +2551,6 @@ function processStackMessages(stacks, logger, options = {}) {
2551
2551
  case "error":
2552
2552
  hasErrors = true;
2553
2553
  logger.error(`[Error at ${msg.path}] ${msg.message}`);
2554
- break;
2555
2554
  }
2556
2555
  const failAt = options.strict ? "warn" : options.ignoreErrors ? "none" : "error";
2557
2556
  if (hasErrors && failAt !== "none") throw new SynthesisError("Found errors");
@@ -3174,10 +3173,7 @@ var CcApiContextProvider = class {
3174
3173
  case "at-least-one":
3175
3174
  if (count < 1) throw new Error(`Expected at least one ${typeName}${context}, found none`);
3176
3175
  break;
3177
- case "at-most-one":
3178
- if (count > 1) throw new Error(`Expected at most one ${typeName}${context}, found ${count}`);
3179
- break;
3180
- case "any": break;
3176
+ case "at-most-one": if (count > 1) throw new Error(`Expected at most one ${typeName}${context}, found ${count}`);
3181
3177
  }
3182
3178
  }
3183
3179
  /**
@@ -4514,7 +4510,8 @@ async function purgeNoncurrentKeyVersions(s3Client, bucket, keys, options = {})
4514
4510
  const elided = failed.size - named.length;
4515
4511
  logger.warn(`Could not purge noncurrent versions of ${failed.size} key(s) in s3://${displaySafe(bucket, { asciiOnly: true })}. Their previous versions survive and remain readable via GetObject with a VersionId (${options.objectDescription ?? "the body of an object cdkd has just reported as removed"}). Grant s3:ListBucketVersions and s3:DeleteObjectVersion on the state bucket, or purge the key(s) by hand. Failures: ${named.join(", ")}` + (elided > 0 ? ` (and ${elided} more)` : ""));
4516
4512
  }
4517
- await warnIfPurgeIsReplicated(s3Client, bucket, [.../* @__PURE__ */ new Set([...purged, ...unsettledBodies])], {
4513
+ const replicationKeys = [.../* @__PURE__ */ new Set([...purged, ...unsettledBodies])];
4514
+ await warnIfPurgeIsReplicated(s3Client, bucket, replicationKeys, {
4518
4515
  requestFields,
4519
4516
  logger,
4520
4517
  ...options.objectDescription !== void 0 && { objectDescription: options.objectDescription }
@@ -5114,7 +5111,8 @@ async function expandMacrosAttempt(template, opts, logger) {
5114
5111
  ChangeSetName: changeSetName
5115
5112
  })).catch(() => void 0);
5116
5113
  const reason = desc?.StatusReason ?? "unknown (DescribeChangeSet failed)";
5117
- throw new MacroExpansionError(`CloudFormation macro expansion failed (status=${desc?.Status ?? "UNKNOWN"}): ${reason}`, waiterError instanceof Error ? waiterError : void 0);
5114
+ const status = desc?.Status ?? "UNKNOWN";
5115
+ throw new MacroExpansionError(`CloudFormation macro expansion failed (status=${status}): ${reason}`, waiterError instanceof Error ? waiterError : void 0);
5118
5116
  }
5119
5117
  const tpl = await cfn.send(new GetTemplateCommand({
5120
5118
  StackName: transientStackName,
@@ -5123,7 +5121,10 @@ async function expandMacrosAttempt(template, opts, logger) {
5123
5121
  }));
5124
5122
  if (tpl.TemplateBody === void 0 || tpl.TemplateBody === null) throw new MacroExpansionError(`CloudFormation returned no Processed-stage template body for the macro-expansion changeset. This typically indicates a CFn-side regression — re-run, and if the failure persists open an issue with the transforms involved: [${macros.join(", ")}].`);
5125
5123
  const expanded = parseTemplateBody(tpl.TemplateBody);
5126
- if (containsMacro(expanded)) throw new MacroExpansionError(`Macro expansion produced a template that still contains macros [${enumerateMacros(expanded).join(", ")}]. Multi-stage macros (a macro whose expansion emits another macro reference) are intentionally out of scope in cdkd v1 — see https://github.com/go-to-k/cdkd/issues/463. If you need this pattern, manually pre-expand the template and deploy the result.`);
5124
+ if (containsMacro(expanded)) {
5125
+ const inner = enumerateMacros(expanded);
5126
+ throw new MacroExpansionError(`Macro expansion produced a template that still contains macros [${inner.join(", ")}]. Multi-stage macros (a macro whose expansion emits another macro reference) are intentionally out of scope in cdkd v1 — see https://github.com/go-to-k/cdkd/issues/463. If you need this pattern, manually pre-expand the template and deploy the result.`);
5127
+ }
5127
5128
  logger.debug(`Macro expansion: success — ${Object.keys(expanded.Resources ?? {}).length} resources after expansion.`);
5128
5129
  return expanded;
5129
5130
  } finally {
@@ -5245,7 +5246,8 @@ function loadJsonConfig(filePath) {
5245
5246
  * Load cdk.json from the current working directory
5246
5247
  */
5247
5248
  function loadCdkJson(cwd) {
5248
- return loadJsonConfig(resolve(cwd || process.cwd(), "cdk.json"));
5249
+ const dir = cwd || process.cwd();
5250
+ return loadJsonConfig(resolve(dir, "cdk.json"));
5249
5251
  }
5250
5252
  /**
5251
5253
  * Load user-level defaults from ~/.cdk.json
@@ -6200,8 +6202,10 @@ async function spawnStreaming(cmd, args, options = {}) {
6200
6202
  const stderrChunks = [];
6201
6203
  child.stdout.on("data", (chunk) => {
6202
6204
  stdoutChunks.push(chunk);
6203
- if (streamLive) if (isStdoutReservedForPayload()) process.stderr.write(chunk);
6204
- else process.stdout.write(chunk);
6205
+ if (streamLive) {
6206
+ if (isStdoutReservedForPayload()) process.stderr.write(chunk);
6207
+ else process.stdout.write(chunk);
6208
+ }
6205
6209
  });
6206
6210
  child.stderr.on("data", (chunk) => {
6207
6211
  stderrChunks.push(chunk);
@@ -7831,10 +7835,11 @@ async function assertAssetBucketRegion(s3Client, bucketName, expectedRegion, acc
7831
7835
  const fromHeader = readBucketRegionHeader(cause);
7832
7836
  if (fromHeader) actual = canonicalizeRegion(fromHeader);
7833
7837
  else try {
7834
- actual = canonicalizeRegion(bucketLocationToRegion$1((await s3Client.send(new GetBucketLocationCommand({
7838
+ const location = await s3Client.send(new GetBucketLocationCommand({
7835
7839
  Bucket: bucketName,
7836
7840
  ExpectedBucketOwner: accountId
7837
- }))).LocationConstraint));
7841
+ }));
7842
+ actual = canonicalizeRegion(bucketLocationToRegion$1(location.LocationConstraint));
7838
7843
  } catch (probeError) {
7839
7844
  const fromProbe = readBucketRegionHeader(probeError);
7840
7845
  if (fromProbe) actual = canonicalizeRegion(fromProbe);
@@ -9000,7 +9005,8 @@ var AssetPublisher = class {
9000
9005
  const err = error;
9001
9006
  const message = stringifyValue(err["message"] || err["name"] || error);
9002
9007
  const code = stringifyValue(err["Code"] || err["code"] || err["name"] || "");
9003
- throw new AssetError(`Asset publishing failed: ${code ? `${code}: ${message}` : message}`, error instanceof Error ? error : void 0);
9008
+ const detail = code ? `${code}: ${message}` : message;
9009
+ throw new AssetError(`Asset publishing failed: ${detail}`, error instanceof Error ? error : void 0);
9004
9010
  }
9005
9011
  }
9006
9012
  /**
@@ -10184,9 +10190,10 @@ var S3StateBackend = class {
10184
10190
  if (isNoSuchKey(error)) return { kind: "absent" };
10185
10191
  const { detail } = describeAwsFailure(error);
10186
10192
  this.logger.debug(`Could not read legacy state region for '${this.displayName(stackName)}': ${displaySafe(detail, { asciiOnly: true }) || "<unrenderable>"}`);
10193
+ const cls = error instanceof Error && error.name ? error.name : "an unknown error";
10187
10194
  return {
10188
10195
  kind: "unreadable",
10189
- reason: displaySafe(error instanceof Error && error.name ? error.name : "an unknown error", { asciiOnly: true }) || "<unrenderable>"
10196
+ reason: displaySafe(cls, { asciiOnly: true }) || "<unrenderable>"
10190
10197
  };
10191
10198
  }
10192
10199
  }
@@ -10289,7 +10296,7 @@ function isNoSuchKey(error) {
10289
10296
  * tolerates FOURTEEN consecutive missed renewals before it lapses, and an
10290
10297
  * ordinary broad-set integ (~8 min) performs three.
10291
10298
  */
10292
- const MAX_RENEWAL_INTERVAL_MS = 120 * 1e3;
10299
+ const MAX_RENEWAL_INTERVAL_MS = 12e4;
10293
10300
  /**
10294
10301
  * Renew after at most a quarter of the TTL, so a short TTL still gets three
10295
10302
  * chances to renew before it lapses. This is what binds the two numbers
@@ -11093,7 +11100,9 @@ var LockManager = class {
11093
11100
  const expiresIn = lockInfo ? this.formatDuration(lockInfo.expiresAt - Date.now()) : "unknown";
11094
11101
  const forceUnlockCommand = buildForceUnlockCommand(stackName, region);
11095
11102
  const recovery = forceUnlockCommand ? `If you are certain no other process is active, run: ${forceUnlockCommand}` : `If you are certain no other process is active, ${UNREPRODUCIBLE_LOCK_CLAUSE.charAt(0).toLowerCase()}${UNREPRODUCIBLE_LOCK_CLAUSE.slice(1)}`;
11096
- throw new LockError(`Failed to acquire lock for stack '${displaySafe(stackName, { asciiOnly: true }) || "<unrenderable>"}' (${displaySafe(region, { asciiOnly: true }) || "<unrenderable>"}) after ${maxRetries + 1} attempts. ` + (lockInfo ? `Locked by: ${lockInfo.owner}${lockInfo.operation ? `, operation: ${lockInfo.operation}` : ""}, expires in: ${expiresIn}. ` + recovery : `Lock exists but could not read lock info. ${recovery}`));
11103
+ const safeStack = displaySafe(stackName, { asciiOnly: true }) || "<unrenderable>";
11104
+ const safeRegion = displaySafe(region, { asciiOnly: true }) || "<unrenderable>";
11105
+ throw new LockError(`Failed to acquire lock for stack '${safeStack}' (${safeRegion}) after ${maxRetries + 1} attempts. ` + (lockInfo ? `Locked by: ${lockInfo.owner}${lockInfo.operation ? `, operation: ${lockInfo.operation}` : ""}, expires in: ${expiresIn}. ` + recovery : `Lock exists but could not read lock info. ${recovery}`));
11097
11106
  }
11098
11107
  };
11099
11108
 
@@ -11197,6 +11206,102 @@ function resolvedPlaintextOf(secrets, expression) {
11197
11206
  return typeof recorded === "string" ? recorded : void 0;
11198
11207
  }
11199
11208
  /**
11209
+ * The bag OBJECTS a deploy pass produced ITSELF and installed on a success
11210
+ * path — the fact the resolved-pair evidence above cannot state (issue
11211
+ * [#2516](https://github.com/go-to-k/cdkd/issues/2516)).
11212
+ *
11213
+ * A pair proves that THIS pass resolved a token to a plaintext; it says nothing
11214
+ * about which bag is being walked. The deploy engine's persist choke point
11215
+ * walks EVERY record in the state map against today's template, and a record
11216
+ * that merely ENTERED the create/update arm is still the PREVIOUS generation
11217
+ * until its provider call succeeds (an intermediate save, a pre/post-rollback
11218
+ * save, Ctrl-C — the same population the `sourceIsSameGeneration` note on
11219
+ * {@link PathSourceRules} names). One record also carries two bags of
11220
+ * different provenance: `properties`, this pass's resolved bag once the
11221
+ * provider succeeded, and `observedProperties`, an AWS readback installed
11222
+ * separately. So the fact is BAG-specific, not caller- or record-level, and it
11223
+ * is carried on the object: {@link markSameGenerationBag} at the moment the
11224
+ * successful result is installed, consulted by {@link redactSecretsForState}
11225
+ * for the object it is handed. A copy of the bag, a derived needle map, a
11226
+ * previous generation's record, a scrub / import / drift walk and a bag the
11227
+ * engine did not mark all answer `false` and keep the fall-through.
11228
+ *
11229
+ * A `WeakSet` for the reason {@link resolvedPairsOf} is a `WeakMap`: the mark
11230
+ * dies with the object it is on, and nothing has to clear it.
11231
+ */
11232
+ const sameGenerationBags = /* @__PURE__ */ new WeakSet();
11233
+ /**
11234
+ * Mark `bag` as an object THIS pass is entitled to speak for, and return it.
11235
+ * The one evidence {@link positionByEmbeddedSpan} needs beyond a resolved pair
11236
+ * to write a middle SHORTER than {@link MIN_NEEDLE_LENGTH} — see the arm.
11237
+ *
11238
+ * Deliberately NOT "produced by resolving today's template, read back from a
11239
+ * resource it just wrote, and about to be installed on a success path": each
11240
+ * of those three was in this sentence and each is false at one site (an
11241
+ * unchanged resource's auto-refresh resolves nothing; the no-change re-check
11242
+ * and the journal are never installed on the record). The conditions are per
11243
+ * site and are listed below.
11244
+ *
11245
+ * WHAT THE MARK CLAIMS, which is the only thing every site shares: the object
11246
+ * is one THIS PASS is entitled to speak for, so a sub-floor middle in it may
11247
+ * be written as the token this pass recorded. What never qualifies is a bag of
11248
+ * MIXED provenance: a provider's `effectiveProperties` replacement may carry
11249
+ * previous-state values IN, so an object-level mark on it would prove nothing
11250
+ * for those leaves — it stays unmarked and keeps the residual. Nor does the
11251
+ * object the resolver produced, marked at resolution time: it is not
11252
+ * necessarily the object state ends up holding.
11253
+ *
11254
+ * The CONDITIONS are per SITE, not global, and stating them globally is what
11255
+ * this paragraph kept getting wrong (PR 2753, rounds 3 and 4). "After the
11256
+ * provider call" is false for the no-change re-check, which marks BEFORE it
11257
+ * and may skip it entirely. "Every leaf this pass produced" is false for the
11258
+ * auto-refresh readback of an UNCHANGED resource, where nothing was resolved
11259
+ * at all — there the safety comes from the empty secrets map, not from the
11260
+ * mark. Read the per-site list below rather than a rule over all five.
11261
+ *
11262
+ * The five call sites, and which of them the record HOLDS, because the earlier
11263
+ * "two never-installed copies" reading of this paragraph was false once the
11264
+ * third copy arrived:
11265
+ * - `propertiesToRecord` — the record's `properties`, installed. The resolved
11266
+ * bag itself when the route drops nothing, a narrowed copy of it when it
11267
+ * does, and `withoutSilentDropProperties` returns its input by reference in
11268
+ * the first case, which is why the mark must be taken AFTER the narrowing
11269
+ * and not before.
11270
+ * - `drainObservedCaptures` — the `observedProperties` readback it installs,
11271
+ * and a COPY: the object a provider returned is never the marked one, so a
11272
+ * provider that hands back its own `properties` argument cannot get a
11273
+ * previous generation marked. It marks EVERY capture it drains, which is
11274
+ * wider than "a resource this pass wrote" — the schema-upgrade auto-refresh
11275
+ * of an UNCHANGED resource is marked too. What keeps that safe is the map,
11276
+ * not the mark: `perResourceSecrets` is populated only in the create /
11277
+ * update arms, so an unchanged resource's walk carries an empty one and the
11278
+ * span arm cannot fire whatever the object is marked.
11279
+ * - `resolveOutputs` — the `outputs` bag this pass resolved. What every site
11280
+ * shares is that the marked object is the redaction INPUT; whether the
11281
+ * record then holds that same object varies, and here it depends on whether
11282
+ * this pass recorded any output secret: with one,
11283
+ * `redactOutputs` returns a fresh redacted bag and that return value is
11284
+ * installed; with none — the ordinary deploy — it returns its input
11285
+ * unchanged and the marked object IS the one
11286
+ * stored. On the no-change path the newly redacted bag is installed when
11287
+ * the outputs CHANGED, and the previous `persistedOutputs` is kept
11288
+ * otherwise; that previous bag is unmarked, which is the answer that arm
11289
+ * wants.
11290
+ * - the update arm's no-change re-check — a marked `{ ...resolvedProps }`
11291
+ * compared against the stored record so a stored token reads as a no-op.
11292
+ * NOT installed; the object the provider is handed is the unmarked original.
11293
+ * - the rollback journal's FAILED-op `attemptedProperties` — a marked copy, so
11294
+ * that persisted artifact does not carry the plaintext on exactly the
11295
+ * failure path. NOT installed on the record.
11296
+ */
11297
+ function markSameGenerationBag(bag) {
11298
+ sameGenerationBags.add(bag);
11299
+ return bag;
11300
+ }
11301
+ function isSameGenerationBag(bag) {
11302
+ return sameGenerationBags.has(bag);
11303
+ }
11304
+ /**
11200
11305
  * Every `{{resolve:...}}` expression this process has PROVEN resolves to a
11201
11306
  * secret, as a SET — uncollapsed by resolved value (issue #1910).
11202
11307
  *
@@ -12847,15 +12952,78 @@ function singleSpanFrame(bag, source) {
12847
12952
  * not create is the whole-token arm's: a middle that is ALREADY an expression
12848
12953
  * (a persisted answer from another generation), which the token refusal below
12849
12954
  * keeps out — and, by the same argument, any leaf the value scan would NOT
12850
- * rewrite to exactly `prefix + survivor + suffix`: a middle shorter than the
12851
- * scan's needle floor (an embedded 1-3 character secret stays the scan's
12852
- * documented residualissue #2516 tracks closing it with a bound that
12853
- * proves the bag's generation, which this evidence does not), a whole leaf
12854
- * that is itself another recorded plaintext, a needle starting in the prefix
12855
- * and overlapping the middle. The
12856
- * arm checks that equivalence against the scan's own answer rather than
12857
- * re-deriving the scan's rules. Pinned by the cross-generation cases in
12858
- * `secret-redaction-embedded-span.test.ts`.
12955
+ * rewrite to exactly `prefix + survivor + suffix`: a whole leaf that is itself
12956
+ * another recorded plaintext, a needle starting in the prefix and overlapping
12957
+ * the middle, and on a bag whose generation is NOT proven — a middle shorter
12958
+ * than the scan's needle floor. The arm checks that equivalence against the
12959
+ * scan's own answer rather than re-deriving the scan's rules. Pinned by the
12960
+ * cross-generation cases in `secret-redaction-embedded-span.test.ts`.
12961
+ *
12962
+ * BELOW THE NEEDLE FLOOR the scan makes no claim at all (issue
12963
+ * [#2516](https://github.com/go-to-k/cdkd/issues/2516)): {@link buildNeedleRegex}
12964
+ * drops every plaintext shorter than {@link MIN_NEEDLE_LENGTH} from its
12965
+ * alternation, so an embedded 1-3 character secret is left in plaintext by
12966
+ * the scan, with or without a same-plaintext sibling. Accepting the scan's
12967
+ * silence (`scanned === bag`) as equivalence would therefore be a NEW claim
12968
+ * rather than a choice within the scan's class, and on a previous
12969
+ * generation's bag it would fabricate: a 1-3 character readback or old
12970
+ * record that COINCIDES with today's plaintext (`port:0` where AWS returns a
12971
+ * default and today's secret resolved to `0`) would be persisted as today's
12972
+ * expression, which round-trips today and, after a rotation, reports a drift
12973
+ * that never happened. So that arm is admitted only for a bag whose
12974
+ * generation IS proven: `bagIsSameGeneration`, the object-level mark
12975
+ * {@link markSameGenerationBag} puts on the bags the deploy engine hands to
12976
+ * redaction on a success path (the record's `properties`, which are the
12977
+ * resolved bag or the subset of it the SDK route writes; EVERY
12978
+ * `observedProperties` readback `drainObservedCaptures` drains, an unchanged
12979
+ * resource's auto-refresh included, where the empty secrets map rather than
12980
+ * the mark is what keeps it safe; and the `outputs` bag this pass resolved,
12981
+ * whose redacted RETURN VALUE is usually what the record holds) and on two
12982
+ * copies of the resolver's own output that the record never holds. That
12983
+ * function's contract is the authority — the conditions differ per site and
12984
+ * do not survive being stated once. Threaded down from
12985
+ * {@link redactSecretsForState} for exactly the object it was handed. With
12986
+ * the mark AND the pair, `recorded === middle` says this pass resolved the
12987
+ * source token to the middle and the bag is this pass's own, so
12988
+ * `prefix + token + suffix` is what the template says at that leaf and what
12989
+ * the resource holds. Without the mark the sub-floor middle keeps today's
12990
+ * bound, which is the scan's answer: the plaintext, unchanged.
12991
+ *
12992
+ * What stays, stated here rather than papered over. A provider that
12993
+ * substituted `effectiveProperties` built a bag of MIXED provenance, so that
12994
+ * object is never marked and a sub-floor middle in it keeps the scan's
12995
+ * answer. A readback AWS rewrote at that offset to a value that coincides
12996
+ * with the 1-3 character secret is persisted as the expression: value-equal
12997
+ * until the secret rotates, and the same residual the value scan already has
12998
+ * for a 4+ character coincidence in a readback. Every refusal of this arm
12999
+ * returns the scan's answer, so a leaf refused for interference (a frame
13000
+ * that is itself a recorded plaintext, say) has its frame rewritten and its
13001
+ * sub-floor middle left in plaintext. And this arm positions a LITERAL
13002
+ * source leaf only: an `Fn::Join` / `Fn::Sub` source rendering the same
13003
+ * `port:` + token goes to {@link positionByIntrinsicSkeleton}, which refuses
13004
+ * unless the WHOLE bag is a recorded plaintext, so a sub-floor secret
13005
+ * embedded through an intrinsic — the dominant CDK shape — still falls to
13006
+ * the value scan and persists in plaintext. `cdkd scrub`, the documented
13007
+ * repair tool for a pre-GHSA record, cannot repair a sub-floor embedded leaf
13008
+ * either: it walks a STORED bag, which no deploy marked, so the arm is
13009
+ * unreachable from it by construction and the leaf keeps the scan's answer.
13010
+ * The MASKING channel keeps the residual whole: `maskSecretsInText`'s
13011
+ * substring arm carries the same four-character floor, so once this arm has
13012
+ * put `port:{{resolve:...}}` in state, a warn line quoting an AWS message can
13013
+ * still print `port:q7`. Pre-existing and not a regression -- the #2453 class,
13014
+ * and the reason the list would otherwise read as complete when it is not
13015
+ * (maintainer review of PR 2753, round 2).
13016
+ *
13017
+ * The next DEPLOY of that resource repairs the leaves this arm is eligible
13018
+ * for — a LITERAL source leaf on a bag the engine marks — because the
13019
+ * re-check compares unequal against a record holding the plaintext and the
13020
+ * resource is written again from a bag this pass produced. The residuals
13021
+ * named above (an `effectiveProperties` substitution, an intrinsic source
13022
+ * shape) are not repaired by that deploy either; they stay tracked by
13023
+ * #2745. Tracked, with the sub-floor
13024
+ * residuals of a nested-stack child's inherited parameter and of `cdkd
13025
+ * import`'s own resolution, by issue
13026
+ * [#2745](https://github.com/go-to-k/cdkd/issues/2745).
12859
13027
  *
12860
13028
  * One shape reaches this arm that a reader may not expect: a WHOLE-token
12861
13029
  * source that FAILED the whole-token arm's `isKnownSecretExpression` gate (an
@@ -12883,7 +13051,7 @@ function singleSpanFrame(bag, source) {
12883
13051
  * the scan as a parameter, which left the bound one wrong caller away from
12884
13052
  * comparing against a scan of some other bag with no type error.
12885
13053
  */
12886
- function positionByEmbeddedSpan(bag, source, secrets) {
13054
+ function positionByEmbeddedSpan(bag, source, secrets, bagIsSameGeneration) {
12887
13055
  const scanned = redactSecretsForState(bag, secrets);
12888
13056
  const frame = singleSpanFrame(bag, source);
12889
13057
  if (frame === void 0) return scanned;
@@ -12892,8 +13060,9 @@ function positionByEmbeddedSpan(bag, source, secrets) {
12892
13060
  if (recorded === void 0 || recorded !== middle) return scanned;
12893
13061
  const survivor = secrets.get(middle);
12894
13062
  if (survivor === void 0) return scanned;
12895
- if (scanned !== prefix + survivor + suffix) return scanned;
12896
- return prefix + token + suffix;
13063
+ if (scanned === prefix + survivor + suffix) return prefix + token + suffix;
13064
+ if (bagIsSameGeneration && scanned === bag) return prefix + token + suffix;
13065
+ return scanned;
12897
13066
  }
12898
13067
  /**
12899
13068
  * Keys tried, in order, when pairing two arrays whose ORDER cannot be trusted
@@ -13089,13 +13258,13 @@ function positionListByCrossStackSource(bag, source, secrets) {
13089
13258
  * association where the position is a cross-stack read, skeleton where it is a
13090
13259
  * describable intrinsic, value where none is.
13091
13260
  */
13092
- function redactByPath(bag, source, secrets, rules, secretExpressions) {
13261
+ function redactByPath(bag, source, secrets, rules, secretExpressions, bagIsSameGeneration) {
13093
13262
  if (isDynamicReferenceString(source) && typeof bag === "string") {
13094
13263
  if (isSingleDynamicReferenceToken(source) && (rules.trustAnyExpression || isKnownSecretExpression(source, secretExpressions))) {
13095
13264
  if (!rules.sourceIsSameGeneration && isSingleDynamicReferenceToken(bag)) return secrets.get(bag) ?? bag;
13096
13265
  return source;
13097
13266
  }
13098
- return positionByEmbeddedSpan(bag, source, secrets);
13267
+ return positionByEmbeddedSpan(bag, source, secrets, bagIsSameGeneration);
13099
13268
  }
13100
13269
  if (typeof bag === "string" && isPlainObject$2(source)) {
13101
13270
  const certified = positionByCrossStackSource(bag, source, secrets);
@@ -13124,16 +13293,16 @@ function redactByPath(bag, source, secrets, rules, secretExpressions) {
13124
13293
  const positionalIsExact = rules.descendArrays && bag.length === source.length && orderPreserved;
13125
13294
  return bag.map((item, i) => {
13126
13295
  const j = partnerIndex[i];
13127
- if (j >= 0) return redactByPath(item, source[j], secrets, rules, secretExpressions);
13128
- if (positionalIsExact) return redactByPath(item, source[i], secrets, rules, secretExpressions);
13296
+ if (j >= 0) return redactByPath(item, source[j], secrets, rules, secretExpressions, bagIsSameGeneration);
13297
+ if (positionalIsExact) return redactByPath(item, source[i], secrets, rules, secretExpressions, bagIsSameGeneration);
13129
13298
  return redactSecretsForState(item, secrets);
13130
13299
  });
13131
13300
  }
13132
- if (rules.descendArrays && bag.length === source.length) return bag.map((item, i) => redactByPath(item, source[i], secrets, rules, secretExpressions));
13301
+ if (rules.descendArrays && bag.length === source.length) return bag.map((item, i) => redactByPath(item, source[i], secrets, rules, secretExpressions, bagIsSameGeneration));
13133
13302
  }
13134
13303
  if (isPlainObject$2(bag) && isPlainObject$2(source)) {
13135
13304
  const out = Object.create(null);
13136
- for (const [k, v] of Object.entries(bag)) out[k] = Object.hasOwn(source, k) ? redactByPath(v, source[k], secrets, rules, secretExpressions) : redactSecretsForState(v, secrets);
13305
+ for (const [k, v] of Object.entries(bag)) out[k] = Object.hasOwn(source, k) ? redactByPath(v, source[k], secrets, rules, secretExpressions, bagIsSameGeneration) : redactSecretsForState(v, secrets);
13137
13306
  return out;
13138
13307
  }
13139
13308
  return redactSecretsForState(bag, secrets);
@@ -14151,7 +14320,7 @@ function refuseUncertifiedReadbackPositions(bag, source, secrets, learn, mark) {
14151
14320
  function redactSecretsForState(bag, secrets, source, rules = TEMPLATE_DERIVED_RULES) {
14152
14321
  if (secrets.size === 0 && source === void 0) return bag;
14153
14322
  if (source !== void 0) {
14154
- const positioned = redactByPath(bag, source, secrets, rules, recordedExpressionsOf(secrets));
14323
+ const positioned = redactByPath(bag, source, secrets, rules, recordedExpressionsOf(secrets), isSameGenerationBag(bag));
14155
14324
  if (!isReadbackProjectedFromState(rules)) return positioned;
14156
14325
  const refused = refuseUncertifiedReadbackPositions(positioned, source, secrets);
14157
14326
  const derived = deriveReadbackNeedles(bag, source, secrets, rules);
@@ -14991,7 +15160,10 @@ var DagBuilder = class {
14991
15160
  this.logger.debug(`Dependency graph built: ${resourceIds.length} nodes, ${edgeCount} edges`);
14992
15161
  edgeCount += this.addCustomResourcePolicyEdges(graph, template);
14993
15162
  edgeCount += this.addLambdaVpcEdges(graph, template);
14994
- if (!alg.isAcyclic(graph)) throw new DependencyError(`Circular dependency detected in template. Cycles: ${this.findCycles(graph).map((c) => c.join(" -> ")).join("; ")}`);
15163
+ if (!alg.isAcyclic(graph)) {
15164
+ const cycles = this.findCycles(graph);
15165
+ throw new DependencyError(`Circular dependency detected in template. Cycles: ${cycles.map((c) => c.join(" -> ")).join("; ")}`);
15166
+ }
14995
15167
  return graph;
14996
15168
  }
14997
15169
  /**
@@ -15020,7 +15192,10 @@ var DagBuilder = class {
15020
15192
  const predecessors = graphCopy.predecessors(node);
15021
15193
  return !predecessors || predecessors.length === 0;
15022
15194
  });
15023
- if (readyNodes.length === 0) throw new DependencyError(`Circular dependency detected. Remaining nodes: ${graphCopy.nodes().join(", ")}`);
15195
+ if (readyNodes.length === 0) {
15196
+ const remaining = graphCopy.nodes();
15197
+ throw new DependencyError(`Circular dependency detected. Remaining nodes: ${remaining.join(", ")}`);
15198
+ }
15024
15199
  this.logger.debug(`Level ${levelNum}: ${readyNodes.length} resources - ${readyNodes.join(", ")}`);
15025
15200
  levels.push(readyNodes);
15026
15201
  readyNodes.forEach((node) => {
@@ -15826,9 +16001,10 @@ const defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
15826
16001
  * 4s/8s steps overshoot it. The dense schedule applies ONLY when the caller
15827
16002
  * left the schedule at its defaults — a caller that passed its own
15828
16003
  * `maxRetries` / `initialDelayMs` / `maxDelayMs` / `isRetryable` picked that
15829
- * schedule deliberately (e.g. the DELETE path's 3 x 5s, or the delete-then-
15830
- * re-create sites' ~64s budget covering SQS's 60s name cooldown) and gets it
15831
- * verbatim.
16004
+ * schedule deliberately (e.g. the DELETE path's 3 retries from 5s 5s/8s/8s,
16005
+ * since it passes `initialDelayMs` but leaves `maxDelayMs` at the 8s default
16006
+ * or the delete-then-re-create sites' ~64s budget covering SQS's 60s name
16007
+ * cooldown) and gets it verbatim.
15832
16008
  *
15833
16009
  * A THIRD class rides its own grid on the default schedule since issue
15834
16010
  * [#2116](https://github.com/go-to-k/cdkd/issues/2116): a NAME COOLDOWN (a
@@ -19370,9 +19546,7 @@ var DiffCalculator = class DiffCalculator {
19370
19546
  case "DELETE":
19371
19547
  summary.delete++;
19372
19548
  break;
19373
- case "NO_CHANGE":
19374
- summary.noChange++;
19375
- break;
19549
+ case "NO_CHANGE": summary.noChange++;
19376
19550
  }
19377
19551
  return summary;
19378
19552
  }
@@ -21122,7 +21296,8 @@ var WAFv2WebACLProvider = class {
21122
21296
  this.logger.debug(`Successfully deleted WAFv2 WebACL ${logicalId}`);
21123
21297
  } catch (error) {
21124
21298
  if (error instanceof WAFNonexistentItemException) {
21125
- assertRegionMatch(await this.getClient().config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
21299
+ const clientRegion = await this.getClient().config.region();
21300
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
21126
21301
  this.logger.debug(`WAFv2 WebACL ${physicalId} does not exist, skipping deletion`);
21127
21302
  return;
21128
21303
  }
@@ -21224,7 +21399,8 @@ var WAFv2WebACLProvider = class {
21224
21399
  result["TokenDomains"] = webACL.TokenDomains ? [...webACL.TokenDomains] : [];
21225
21400
  if (webACL.AssociationConfig) result["AssociationConfig"] = webACL.AssociationConfig;
21226
21401
  try {
21227
- result["Tags"] = normalizeAwsTagsToCfn((await this.getClient().send(new ListTagsForResourceCommand$11({ ResourceARN: physicalId }))).TagInfoForResource?.TagList);
21402
+ const tagsResp = await this.getClient().send(new ListTagsForResourceCommand$11({ ResourceARN: physicalId }));
21403
+ result["Tags"] = normalizeAwsTagsToCfn(tagsResp.TagInfoForResource?.TagList);
21228
21404
  } catch (err) {
21229
21405
  this.logger.debug(`WAFv2 ListTagsForResource(${physicalId}) failed: ${err instanceof Error ? err.message : String(err)}`);
21230
21406
  }
@@ -22929,14 +23105,15 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
22929
23105
  const resolved = {};
22930
23106
  for (const [key, val] of Object.entries(obj)) {
22931
23107
  const resolvedVal = await this.resolveValue(val, context);
22932
- if (resolvedVal !== AWS_NO_VALUE) if (key === "__proto__") Object.defineProperty(resolved, key, {
22933
- value: resolvedVal,
22934
- enumerable: true,
22935
- writable: true,
22936
- configurable: true
22937
- });
22938
- else resolved[key] = resolvedVal;
22939
- else this.logger.debug(`Property ${key} resolved to AWS::NoValue, omitting from object`);
23108
+ if (resolvedVal !== AWS_NO_VALUE) {
23109
+ if (key === "__proto__") Object.defineProperty(resolved, key, {
23110
+ value: resolvedVal,
23111
+ enumerable: true,
23112
+ writable: true,
23113
+ configurable: true
23114
+ });
23115
+ else resolved[key] = resolvedVal;
23116
+ } else this.logger.debug(`Property ${key} resolved to AWS::NoValue, omitting from object`);
22940
23117
  }
22941
23118
  return resolved;
22942
23119
  }
@@ -23649,9 +23826,7 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
23649
23826
  case "PublicDnsName":
23650
23827
  value = instance?.PublicDnsName;
23651
23828
  break;
23652
- case "AvailabilityZone":
23653
- value = instance?.Placement?.AvailabilityZone;
23654
- break;
23829
+ case "AvailabilityZone": value = instance?.Placement?.AvailabilityZone;
23655
23830
  }
23656
23831
  if (value !== void 0 && value !== null && value !== "") {
23657
23832
  cachedEc2InstanceAttributes[cacheKey] = value;
@@ -24775,7 +24950,7 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
24775
24950
  const credentials = await assumeRoleForCrossAccountStateRead(roleArn);
24776
24951
  const { bucket, region: bucketRegion } = await resolveCrossAccountStateBucket(parsed.accountId, credentials);
24777
24952
  const prefix = context.stateBackend?.prefix ?? "cdkd";
24778
- return new S3StateBackend(new S3Client({
24953
+ const s3 = new S3Client({
24779
24954
  ...awsClientDefaults(),
24780
24955
  region: bucketRegion,
24781
24956
  credentials: {
@@ -24789,7 +24964,8 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
24789
24964
  warn: () => {},
24790
24965
  error: () => {}
24791
24966
  }
24792
- }), {
24967
+ });
24968
+ return new S3StateBackend(s3, {
24793
24969
  bucket,
24794
24970
  prefix
24795
24971
  }, {
@@ -25984,7 +26160,7 @@ function unsupportedTypeIssueUrl(resourceType) {
25984
26160
 
25985
26161
  //#endregion
25986
26162
  //#region src/provisioning/slow-cc-operation-timeouts.ts
25987
- const MINUTE_MS = 60 * 1e3;
26163
+ const MINUTE_MS = 6e4;
25988
26164
  /**
25989
26165
  * 60 min covers the worst-case observed for each type with headroom over the
25990
26166
  * 15-30 min typical range. Keyed by CloudFormation type name. RDS / ElastiCache
@@ -26307,7 +26483,7 @@ var CloudControlProvider = class {
26307
26483
  cloudControlClient;
26308
26484
  logger = getLogger().child("CloudControlProvider");
26309
26485
  patchGenerator = new JsonPatchGenerator();
26310
- MAX_WAIT_TIME_MS = 900 * 1e3;
26486
+ MAX_WAIT_TIME_MS = 9e5;
26311
26487
  INITIAL_POLL_INTERVAL_MS = 1e3;
26312
26488
  MAX_POLL_INTERVAL_MS = 1e4;
26313
26489
  constructor() {
@@ -26466,8 +26642,9 @@ var CloudControlProvider = class {
26466
26642
  const indeterminateGuard = await this.confirmDeleteTargetIdentity(logicalId, resourceType, physicalId, context);
26467
26643
  if (context?.removeProtection === true && resourceType === "AWS::AutoScaling::AutoScalingGroup") {
26468
26644
  this.logger.debug(`Delegating protected AutoScalingGroup ${logicalId} delete to the SDK ASGProvider (Cloud Control cannot force-delete a protected ASG)`);
26469
- const { ASGProvider } = await import("./asg-provider-DQCVYO5-.js").then((n) => n.n);
26470
- return withIndeterminateGuard(await new ASGProvider().delete(logicalId, physicalId, resourceType, _properties, context), indeterminateGuard);
26645
+ const { ASGProvider } = await import("./asg-provider-DUlRn84u.js").then((n) => n.n);
26646
+ const asgProvider = new ASGProvider();
26647
+ return withIndeterminateGuard(await asgProvider.delete(logicalId, physicalId, resourceType, _properties, context), indeterminateGuard);
26471
26648
  }
26472
26649
  const isProtectedEc2Instance = context?.removeProtection === true && resourceType === "AWS::EC2::Instance";
26473
26650
  if (isProtectedEc2Instance) await disableInstanceApiTermination(getAwsClients().ec2, physicalId, this.logger);
@@ -27119,16 +27296,13 @@ var CloudControlProvider = class {
27119
27296
  }
27120
27297
  }
27121
27298
  break;
27122
- case "AWS::ResourceGroups::Group":
27123
- if (!enriched["Arn"]) {
27124
- const model = await this.readCcResourceModel(resourceType, physicalId);
27125
- if (model) {
27126
- if (typeof model["Arn"] === "string") enriched["Arn"] = model["Arn"];
27127
- this.logger.debug(`Enriched ResourceGroups Group ${physicalId} with Arn from CC GetResource`);
27128
- }
27299
+ case "AWS::ResourceGroups::Group": if (!enriched["Arn"]) {
27300
+ const model = await this.readCcResourceModel(resourceType, physicalId);
27301
+ if (model) {
27302
+ if (typeof model["Arn"] === "string") enriched["Arn"] = model["Arn"];
27303
+ this.logger.debug(`Enriched ResourceGroups Group ${physicalId} with Arn from CC GetResource`);
27129
27304
  }
27130
- break;
27131
- default: break;
27305
+ }
27132
27306
  }
27133
27307
  return enriched;
27134
27308
  }
@@ -30321,7 +30495,8 @@ var IAMRoleProvider = class {
30321
30495
  await this.iamClient.send(new GetRoleCommand({ RoleName: physicalId }));
30322
30496
  } catch (error) {
30323
30497
  if (error instanceof NoSuchEntityException) {
30324
- assertRegionMatch(await this.iamClient.config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
30498
+ const clientRegion = await this.iamClient.config.region();
30499
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
30325
30500
  this.logger.debug(`Role ${physicalId} does not exist, skipping deletion`);
30326
30501
  return;
30327
30502
  }
@@ -32121,9 +32296,9 @@ const EBS_FINAL_SNAPSHOT_TAG_KEY = "cdkd:final-snapshot-of";
32121
32296
  /** Test seam (matches the `describe-type.ts` / macro-expander pattern). */
32122
32297
  const finalSnapshotDelays = { sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)) };
32123
32298
  const PRE_DELETE_SNAPSHOT_POLL_INTERVAL_MS = 5e3;
32124
- const PRE_DELETE_SNAPSHOT_TIMEOUT_MS = 3600 * 1e3;
32299
+ const PRE_DELETE_SNAPSHOT_TIMEOUT_MS = 36e5;
32125
32300
  /** Post-snapshot settle budget for a Redshift cluster before its delete. */
32126
- const REDSHIFT_CLUSTER_SETTLE_TIMEOUT_MS = 1200 * 1e3;
32301
+ const REDSHIFT_CLUSTER_SETTLE_TIMEOUT_MS = 12e5;
32127
32302
  function errMsg(error) {
32128
32303
  return error instanceof Error ? error.message : String(error);
32129
32304
  }
@@ -33432,7 +33607,8 @@ var ReplayResolvers = class {
33432
33607
  * `state.json` already stores in the clear.
33433
33608
  */
33434
33609
  function regionAmbiguousReplaySecretError(logicalId, propertyPath, secretName, foreignProducerRegions, consumerRegion) {
33435
- return new CdkdError(`Rollback of ${logicalId}${propertyPath === "" ? "" : ` property '${propertyPath}'`} cannot re-resolve the secret reference '${secretName}': the reference carries no region of its own, and this stack read across a region boundary (producer region(s) on record: ${foreignProducerRegions.join(", ")}), so it may have been resolved in one of those rather than in '${consumerRegion}'. A secret of the same name in two regions is two independent values, so replaying this would write the WRONG secret to a live resource. Refusing instead. Resolve the reference in its own region and set the property directly (or spell it as a full ARN, which names its region and is resolved there), then re-run 'cdkd rollback'.`, "ROLLBACK_SECRET_REGION_AMBIGUOUS");
33610
+ const where = propertyPath === "" ? "" : ` property '${propertyPath}'`;
33611
+ return new CdkdError(`Rollback of ${logicalId}${where} cannot re-resolve the secret reference '${secretName}': the reference carries no region of its own, and this stack read across a region boundary (producer region(s) on record: ${foreignProducerRegions.join(", ")}), so it may have been resolved in one of those rather than in '${consumerRegion}'. A secret of the same name in two regions is two independent values, so replaying this would write the WRONG secret to a live resource. Refusing instead. Resolve the reference in its own region and set the property directly (or spell it as a full ARN, which names its region and is resolved there), then re-run 'cdkd rollback'.`, "ROLLBACK_SECRET_REGION_AMBIGUOUS");
33436
33612
  }
33437
33613
  /**
33438
33614
  * Re-resolve one LEAF string, sending each `{{resolve:...}}` reference in it to
@@ -33535,10 +33711,11 @@ async function resolveLeafByRegion(leaf, propertyPath, logicalId, execCtx, resol
33535
33711
  */
33536
33712
  function redactRollbackRecord(record, secrets, journaledProps) {
33537
33713
  if (secrets.size === 0) return record;
33538
- return scrubResourceRecord(journaledProps === void 0 ? record : {
33714
+ const positioned = journaledProps === void 0 ? record : {
33539
33715
  ...record,
33540
33716
  properties: redactSecretsForState(record.properties, secrets, journaledProps, STATE_DERIVED_RULES)
33541
- }, secrets);
33717
+ };
33718
+ return scrubResourceRecord(positioned, secrets);
33542
33719
  }
33543
33720
  async function updateWithRollbackRetry(provider, args, logicalId, logger, isInterrupted, secrets) {
33544
33721
  if (provider.disableOuterRetry) return await withCurrentResourceSecrets(secrets, () => provider.update(...args));
@@ -34759,13 +34936,13 @@ const EMPTY_SECRETS = /* @__PURE__ */ new Map();
34759
34936
  * resource has been in flight for 5 minutes. Most CC API resources
34760
34937
  * complete in under a minute; 5m is the agreed elbow.
34761
34938
  */
34762
- const DEFAULT_RESOURCE_WARN_AFTER_MS = 300 * 1e3;
34939
+ const DEFAULT_RESOURCE_WARN_AFTER_MS = 3e5;
34763
34940
  /**
34764
34941
  * Default per-resource hard timeout: abort after 30 minutes. Matches the
34765
34942
  * design doc — Custom-Resource-heavy stacks should pass `--resource-timeout 1h`
34766
34943
  * explicitly because the Custom Resource provider's polling cap is 1h.
34767
34944
  */
34768
- const DEFAULT_RESOURCE_TIMEOUT_MS = 1800 * 1e3;
34945
+ const DEFAULT_RESOURCE_TIMEOUT_MS = 18e5;
34769
34946
  var InterruptedError = class extends Error {
34770
34947
  constructor(reason = "user") {
34771
34948
  super(reason === "user" ? "Deployment interrupted by user (Ctrl+C)" : "Deployment aborted after another resource failed");
@@ -35104,7 +35281,7 @@ var DeployEngine = class {
35104
35281
  });
35105
35282
  this.options.concurrency = options.concurrency ?? 10;
35106
35283
  this.options.dryRun = options.dryRun ?? false;
35107
- this.options.lockTimeout = options.lockTimeout ?? 300 * 1e3;
35284
+ this.options.lockTimeout = options.lockTimeout ?? 3e5;
35108
35285
  this.options.noRollback = options.noRollback ?? false;
35109
35286
  this.options.resourceWarnAfterMs = options.resourceWarnAfterMs ?? 3e5;
35110
35287
  this.options.resourceTimeoutMs = options.resourceTimeoutMs ?? 18e5;
@@ -35119,6 +35296,7 @@ var DeployEngine = class {
35119
35296
  this.perResourceSecrets = /* @__PURE__ */ new Map();
35120
35297
  this.noEchoAttributeResources = /* @__PURE__ */ new Map();
35121
35298
  this.perResourceTemplateProps = /* @__PURE__ */ new Map();
35299
+ this.attemptedResolvedProps = /* @__PURE__ */ new Map();
35122
35300
  this.outputSecrets = /* @__PURE__ */ new Map();
35123
35301
  this.outputsTemplateSource = {};
35124
35302
  this.outputsSourceUsable = true;
@@ -35339,7 +35517,7 @@ var DeployEngine = class {
35339
35517
  const ownSecrets = secrets ?? /* @__PURE__ */ new Map();
35340
35518
  const next = { ...op };
35341
35519
  if (next.properties) next.properties = redactSecretsForState(next.properties, ownSecrets, templateProps);
35342
- if (next.attemptedProperties) next.attemptedProperties = redactSecretsForState(next.attemptedProperties, ownSecrets, templateProps);
35520
+ if (next.attemptedProperties) next.attemptedProperties = redactSecretsForState(markSameGenerationBag({ ...next.attemptedProperties }), ownSecrets, templateProps);
35343
35521
  if (next.previousState) next.previousState = scrubResourceRecord(next.previousState, ownSecrets);
35344
35522
  return next;
35345
35523
  });
@@ -35427,7 +35605,7 @@ var DeployEngine = class {
35427
35605
  const logicalId = entries[i][0];
35428
35606
  const observed = resolved[i];
35429
35607
  const target = stateResources[logicalId];
35430
- if (target && observed !== void 0) target.observedProperties = observed;
35608
+ if (target && observed !== void 0) target.observedProperties = markSameGenerationBag({ ...observed });
35431
35609
  }
35432
35610
  }
35433
35611
  /**
@@ -36361,7 +36539,8 @@ var DeployEngine = class {
36361
36539
  if (!redirect) return;
36362
36540
  const findings = findUnrewrittenAssetReferences(resolvedProps, redirect);
36363
36541
  if (findings.length === 0) return;
36364
- throw new ProvisioningError(`Unrewritten asset reference on '${logicalId}' (${resourceType}): this region uses cdkd-owned asset storage, but the following resolved properties still point at the CDK bootstrap storage that 'cdk gc' may garbage-collect:\n${findings.map((f) => ` - ${f.path}: still references '${f.source}'`).join("\n")}\nThis is a template shape cdkd's asset-reference rewrite did not cover — deploying it would split-brain the stack (assets in cdkd storage, properties reading the CDK bucket). Please report this at https://github.com/go-to-k/cdkd/issues with the property shape. Workaround: deploy with --use-cdk-bootstrap-assets to pin the legacy destinations for this app.`, resourceType, logicalId);
36542
+ const detail = findings.map((f) => ` - ${f.path}: still references '${f.source}'`).join("\n");
36543
+ throw new ProvisioningError(`Unrewritten asset reference on '${logicalId}' (${resourceType}): this region uses cdkd-owned asset storage, but the following resolved properties still point at the CDK bootstrap storage that 'cdk gc' may garbage-collect:\n${detail}\nThis is a template shape cdkd's asset-reference rewrite did not cover — deploying it would split-brain the stack (assets in cdkd storage, properties reading the CDK bucket). Please report this at https://github.com/go-to-k/cdkd/issues with the property shape. Workaround: deploy with --use-cdk-bootstrap-assets to pin the legacy destinations for this app.`, resourceType, logicalId);
36365
36544
  }
36366
36545
  /**
36367
36546
  * The `Snapshot`-policy gate every engine delete site runs BEFORE its
@@ -36503,7 +36682,7 @@ var DeployEngine = class {
36503
36682
  recordNestedStackParameterExpressions(updateSecrets, resourceType, resolvedProps, desiredProps);
36504
36683
  this.auditResolvedAssetReferences(logicalId, resourceType, resolvedProps);
36505
36684
  this.attemptedResolvedProps.set(logicalId, resolvedProps);
36506
- if (JSON.stringify(redactSecretsForState(resolvedProps, updateSecrets, desiredProps)) === JSON.stringify(currentPropsAsWritten)) {
36685
+ if (JSON.stringify(redactSecretsForState(markSameGenerationBag({ ...resolvedProps }), updateSecrets, desiredProps)) === JSON.stringify(currentPropsAsWritten)) {
36507
36686
  if (change.attributeChanges && change.attributeChanges.length > 0) {
36508
36687
  const attrSummary = change.attributeChanges.map((a) => `${a.attribute}: ${a.oldValue ?? "(unset)"} → ${a.newValue ?? "(unset)"}`).join(", ");
36509
36688
  this.logger.info(` ↻ ${logicalId} (${resourceType}) attribute update: ${attrSummary}`);
@@ -36733,7 +36912,8 @@ var DeployEngine = class {
36733
36912
  createResult = await this.withRetry(() => withCurrentResourceSecrets(updateSecrets, () => replProvider.create(logicalId, resourceType, replProps, { maskSecrets: createSecretMasker(updateSecrets) })), logicalId, void 0, void 0, replProvider);
36734
36913
  } catch (createError) {
36735
36914
  if (!retainOldOnReplace) throw new Error(maskSecretsInText(`Failed to create ${logicalId} after the UPDATE-not-supported replacement: the old resource (${currentResource.physicalId}) is now gone. Cause: ${createError instanceof Error ? createError.message : String(createError)}. Re-run the deploy to create it fresh.`, updateSecrets), { cause: createError instanceof Error ? createError : void 0 });
36736
- if (!isNameCollisionError(createError instanceof Error ? createError.message : String(createError))) throw createError;
36915
+ const createMsg = createError instanceof Error ? createError.message : String(createError);
36916
+ if (!isNameCollisionError(createMsg)) throw createError;
36737
36917
  const nameOrigin = this.replacementNameOrigin(logicalId, currentResource.physicalId);
36738
36918
  throw markNonRetryable(new CdkdError(`${logicalId} (${resourceType}) requires replacement because the provisioning layer cannot update it in place — but its physical name is still held by the existing resource AND UpdateReplacePolicy: Retain pins that resource in place. ${nameOrigin.descriptor}. ${nameOrigin.remedy} — with Retain, the old resource keeps the name, so a same-name replacement can never proceed. Removing UpdateReplacePolicy: Retain lets cdkd delete the old resource first, which destroys it and any data it holds.`, "NAMED_REPLACEMENT_COLLISION", createError instanceof Error ? createError : void 0));
36739
36919
  }
@@ -36782,8 +36962,10 @@ var DeployEngine = class {
36782
36962
  const updateCaptureSiblings = await this.buildObservedCaptureSiblings(resourceType, logicalId, result.physicalId, template, stateResources, stackName, parameterValues, conditions);
36783
36963
  this.kickOffObservedCapture(captureProvider, logicalId, result.physicalId, resourceType, resolvedProps, updateCaptureSiblings);
36784
36964
  const updatePartial = updatePartialReason(result);
36785
- if (counts) if (updatePartial !== void 0) counts.updatePartial++;
36786
- else counts.updated++;
36965
+ if (counts) {
36966
+ if (updatePartial !== void 0) counts.updatePartial++;
36967
+ else counts.updated++;
36968
+ }
36787
36969
  if (progress) progress.current++;
36788
36970
  const updatePrefix = progress ? `[${progress.current}/${progress.total}] ` : " ";
36789
36971
  renderer.removeTask(logicalId);
@@ -36903,9 +37085,9 @@ var DeployEngine = class {
36903
37085
  * `cdkd export`, `cdkd state`.
36904
37086
  */
36905
37087
  propertiesToRecord(desiredProperties, result, resourceType, provisionedBy) {
36906
- const effective = result.effectiveProperties ?? desiredProperties;
36907
- if (provisionedBy !== "sdk") return effective;
36908
- return withoutSilentDropProperties(resourceType, effective);
37088
+ if (result.effectiveProperties) return provisionedBy === "sdk" ? withoutSilentDropProperties(resourceType, result.effectiveProperties) : result.effectiveProperties;
37089
+ const written = provisionedBy === "sdk" ? withoutSilentDropProperties(resourceType, desiredProperties) : desiredProperties;
37090
+ return markSameGenerationBag(written);
36909
37091
  }
36910
37092
  /**
36911
37093
  * The name-origin half of the replacement-collision messages (issue #1636).
@@ -37285,7 +37467,7 @@ var DeployEngine = class {
37285
37467
  if (!publishedOutputNames.has(outputKey)) continue;
37286
37468
  this.outputsTemplateSource[outputKey] = output.Value;
37287
37469
  }
37288
- return outputs;
37470
+ return markSameGenerationBag(outputs);
37289
37471
  }
37290
37472
  buildDisplayOutputs(template, resolvedOutputs) {
37291
37473
  const display = {};
@@ -37300,4 +37482,4 @@ var DeployEngine = class {
37300
37482
 
37301
37483
  //#endregion
37302
37484
  export { createMaskedRetryLogger as $, runDockerStreaming as $n, StackTerminationProtectionError as $r, carriesSecretMask as $t, WARM_THROUGHPUT_MEMBERS as A, rewriteTemplateAssetReferences as An, clearBucketRegionCache as Ar, replayWarn as At, yellow as B, validateContainerRepoName as Bn, DependencyError as Br, s3BucketWebsiteUrl as Bt, unsupportedFinalSnapshotError as C, shouldRetainResource as Cn, displaySafe as Cr, normalizeAwsTagsToCfn as Ct, isStatefulRecreateTargetForReplace as D, buildAssetRedirectMap as Dn, derivePartitionAndUrlSuffix as Dr, configBooleanRefusal as Dt, MULTI_REGION_RECREATE_BLOCKED_TYPES as E, WorkGraph as En, canonicalizeRegion as Er, coerceCfnBoolean as Et, bold as F, getBootstrapMarkerKey as Fn, setAwsClients as Fr, producerRegionsFromState as Ft, secretBearingStateKeyWarning as G, describeDockerExecFailure as Gn, LocalStartServiceError as Gr, findSilentDropProperties as Gt, collectPublishedOutputNames as H, describeAwsFailure as Hn, DynamicReferenceRegionAmbiguousError as Hr, DiffCalculator as Ht, cyan as I, isCrossRegionRedirect as In, AssetError as Ir, s3BucketArn as It, IAMRoleProvider as J, formatDockerLoginError as Jn, PartialFailureError as Jr, DagBuilder as Jt, stateKeySecretExposure as K, describeDockerFailure as Kn, LockError as Kr, describeTypeWithThrottleRetry as Kt, gray as L, parseBootstrapMarker as Ln, CdkdError as Lr, s3BucketDomainName as Lt, isWarmThroughputDecrease as M, BOOTSTRAP_MARKER_PREFIX as Mn, AwsClients as Mr, requireConfigObject as Mt, toFiniteNumber as N, assertAssetBucketRegion as Nn, getAwsClients as Nr, requireConfigString as Nt, isStatefulRecreateTargetSync as O, createAssetRedirectResolver as On, AssemblyReader as Or, configStringRefusal as Ot, formatResourceLine as P, ensureAssetStorage as Pn, resetAwsClients as Pr, classifyReplaySecretRegion as Pt, wouldReturnToSdkProvider as Q, runDockerForeground as Qn, StackHasActiveImportsError as Qr, TEMPLATE_SOURCED_RULES as Qt, green as R, readBootstrapMarkerBody as Rn, ConfigError as Rr, s3BucketDualStackDomainName as Rt, refusesFinalSnapshot as S, importableOutputs as Sn, CUSTOM_RESOURCE_RESPONSE_OBJECT_DESCRIPTION as Sr, WAFv2WebACLProvider as St, extractDeploymentEventError as T, stringifyValue as Tn, PARTITION_TABLE as Tr, assertRegionMatch as Tt, exportAliasCollisionScrubWarning as U, buildDockerImage as Un, IntrinsicResolutionRefusalError as Ur, INTRINSIC_KEYS as Ut, collectDeclaredOutputNames as V, buildDenyExternalAccessPolicy as Vn, DeployCancelledError as Vr, applyRoleArnIfSet as Vt, isExportAliasCollision as W, describeDockerCapturedOutput as Wn, LocalInvokeBuildError as Wr, findActionableSilentDrops as Wt, clearOnUpdateRemoval as X, partitionSensitiveEnv as Xn, ResourceTimeoutError as Xr, STATE_SOURCED_CROSS_GENERATION_RULES as Xt, collectInlinePolicyNamesManagedBySiblings as Y, getDockerCmd as Yn, ProvisioningError as Yr, TemplateParser as Yt, ProviderRegistry as Z, redactDockerArgvValues as Zn, ResourceUpdateNotSupportedError as Zr, STATE_SOURCED_READBACK_RULES as Zt, PRE_DELETE_SNAPSHOT_TYPES as _, shellQuote as _n, CFN_TEMPLATE_BODY_LIMIT as _r, coerceParameterTypedValue as _t, DeploymentEventsStore as a, withErrorHandling as ai, maskSecretsInText as an, synthesisStatusMessage as ar, isInterruptedWaitError as at, createPreDeleteFinalSnapshot as b, exportNamesCarriedFrom as bn, findLargeInlineResources as br, parameterTypeMayLoseSecretIdentity as bt, replayFailedOperations as c, isThrottlingError as ci, redactSecretsForState as cn, resolveApp as cr, slowCcOperationTimeoutMs as ct, updatePartialReason as d, markRedactedCause as di, S3StateBackend as dn, resolveSkipPrefix as dr, deleteSkipReason as dt, StateError as ei, createSecretMasker as en, escapeRegExp$1 as er, maskDeep as et, withResourceDeadline as f, retryClassificationText as fi, rebuildClientForBucketRegion as fn, resolveStateBucketWithDefault as fr, disableInstanceApiTermination as ft, ATOMIC_FINAL_SNAPSHOT_TYPES as g, forceQuitRecoveryClause as gn, warnDeprecatedNoPrefixCliFlag as gr, cfnRefValueFromPhysicalId as gt, computeImplicitDeleteEdges as h, buildLockContentionMessage as hn, stateBucketExistenceConfirmed as hr, carriesDynamicReference as ht, DeploymentEventsReader as i, normalizeAwsError as ii, maskSecretsInError as in, Synthesizer as ir, interruptWatchListenerCount as it, coerceWarmThroughput as j, AssetModeResolver as jn, resolveBucketRegion as jr, requireConfigArray as jt, renderStatefulReason as k, loadPublishableAssetManifest as kn, processStackMessages as kr, readConfigString as kt, replayRollback as l, isTransientServerError as li, scrubResourceRecord as ln, resolveAutoAssetStorage as lr, UNSPECIFIED_SKIP_REASON as lt, IMPLICIT_DELETE_DEPENDENCIES as m, buildForceUnlockCommand as mn, resolveUseCdkBootstrapAssets as mr, IntrinsicFunctionResolver as mt, DEFAULT_RESOURCE_WARN_AFTER_MS as n, formatError as ni, errorCauseChain as nn, AssetManifestLoader as nr, beginCommandInterruptScope as nt, planFailedOps as o, isMarkedNonRetryable as oi, recordMaskOnlyValue as on, getDefaultStateBucketName as or, startInterruptWatch as ot, maskingRetryLogger as p, __exportAll as pi, UNRENDERABLE as pn, resolveStateBucketWithDefaultAndSource as pr, isTerminationProtectionPropagationError as pt, getCurrentResourceSecrets as q, dockerSpawnEnvWithSensitive as qn, NestedStackChildDirectDestroyError as qr, withRetry as qt, DeployEngine as r, isCdkdError as ri, isSingleDynamicReferenceToken as rn, getDockerImageBySourceHash as rr, endCommandInterruptScope as rt, planRollback as s, isRetryableTransientError as si, recoverMaskedOutput as sn, getLegacyStateBucketName as sr, CloudControlProvider as st, DEFAULT_RESOURCE_TIMEOUT_MS as t, SynthesisError as ti, dynamicReferenceTokens as tn, stripControlChars as tr, maskerOrIdentity as tt, updatePartialMessage as u, markNonRetryable as ui, LockManager as un, resolveCaptureObservedState as ur, deleteIndeterminateGuards as ut, buildFinalSnapshotIdentifier as v, CUSTOM_RESOURCE_RESPONSE_PREFIX as vn, CFN_TEMPLATE_URL_LIMIT as vr, getAccountInfo as vt, makeCanonicalizePropertiesFn as w, AssetPublisher as wn, expectedOwnerParam as wr, resolveExplicitPhysicalId as wt, isFinalSnapshotError as x, importableOutputKeys as xn, uploadCfnTemplate as xr, refStateLookupFromResource as xt, ccRoutedFinalSnapshotError as y, DEFAULT_STATE_PREFIX as yn, MIGRATE_TMP_PREFIX as yr, isUnboundTemplateParameter as yt, red as z, validateAssetBucketName as zn, CrossAccountSecretRefusalError as zr, s3BucketRegionalDomainName as zt };
37303
- //# sourceMappingURL=deploy-engine-DF3fAY6q.js.map
37485
+ //# sourceMappingURL=deploy-engine-Cxt3gn8N.js.map