@go-to-k/cdkd 0.289.2 → 0.289.4

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
- import { d as applyDefaultNameForFallback, f as explicitNamePropertyFor, g as looksLikeCdkdGeneratedName, h as getCurrentStackName, m as generateResourceNameWithFallback, n as getLogger, p as generateResourceName, r as isStdoutReservedForPayload, s as getLiveRenderer, v as withStackName } from "./logger-D2Md1kTq.js";
1
+ import { d as applyDefaultNameForFallback, f as explicitNamePropertyFor, g as looksLikeCdkdGeneratedName, h as getCurrentStackName, m as generateResourceNameWithFallback, n as getLogger, p as generateResourceName, r as isStdoutReservedForPayload, s as getLiveRenderer, v as withStackName, y as displaySafe } from "./logger-CecsQ1UT.js";
2
2
  import { t as awsClientDefaults } from "./aws-client-defaults-D-iYiIJ6.js";
3
- import { t as getCdkdVersion } from "./version-DUhfbsEF.js";
3
+ import { t as getCdkdVersion } from "./version-C3wRsCF2.js";
4
4
  import { AsyncLocalStorage } from "node:async_hooks";
5
5
  import { createHash, 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";
@@ -1554,7 +1554,10 @@ function isCdkdError(error) {
1554
1554
  function formatError(error) {
1555
1555
  if (isCdkdError(error)) {
1556
1556
  let message = `${error.name}: ${error.message}`;
1557
- if (error.cause) message += `\nCaused by: ${error.cause.message}`;
1557
+ if (error.cause) {
1558
+ const cause = displaySafe(error.cause.message);
1559
+ if (cause) message += `\nCaused by: ${cause}`;
1560
+ }
1558
1561
  return message;
1559
1562
  }
1560
1563
  if (error instanceof Error) return `${error.name}: ${error.message}`;
@@ -3987,56 +3990,6 @@ async function expectedOwnerParam(client) {
3987
3990
  return owner ? { ExpectedBucketOwner: owner } : {};
3988
3991
  }
3989
3992
 
3990
- //#endregion
3991
- //#region src/utils/display-safe.ts
3992
- /**
3993
- * Make an untrusted value safe to render in a terminal or persist into a log.
3994
- *
3995
- * A LEAF module with no imports, and deliberately in `src/utils/` rather than
3996
- * beside its first caller: issue [#2170](https://github.com/go-to-k/cdkd/issues/2170)'s
3997
- * review found the same rule being widened BY HAND one module at a time and
3998
- * missing an instance every round — the change sanitized 1 of 5 readers of
3999
- * `LockInfo.owner`. One shared definition, imported by everything that renders
4000
- * such a value, is what stops the next reader from inheriting nothing.
4001
- *
4002
- * The stripped class is wider than C0 + DEL, which a first cut used and which
4003
- * misses every mechanism that actually forges a line:
4004
- *
4005
- * - `U+0085` (NEL) and the C1 range — xterm reads `U+009B` as CSI in UTF-8;
4006
- * - `U+2028` / `U+2029` — this text is PERSISTED and re-rendered by JSON and
4007
- * web log viewers, where both are line terminators;
4008
- * - `U+202A`-`U+202E` / `U+2066`-`U+2069` — the Trojan-Source bidi overrides
4009
- * and isolates, which visually REORDER the command being pasted.
4010
- *
4011
- * Known residual, recorded rather than implied away: the invisible formatters
4012
- * (`U+200B`-`U+200D`, `U+FEFF`) and the bidi MARKS (`U+200E` / `U+200F` /
4013
- * `U+061C`) survive, as do bare RTL letters, which no denylist can reach. All
4014
- * of them can only make a rendered name differ visually from its bytes — the
4015
- * command a user pastes still acts on exactly what is shown, and the blast
4016
- * radius stays the attacker's own stack name.
4017
- *
4018
- * A caller whose value has a KNOWN ASCII charset (a stack name, an AWS region)
4019
- * should pass `asciiOnly`, which is a positive allowlist and therefore has no
4020
- * such residual at all.
4021
- *
4022
- * ONE CALLER DELIBERATELY GOES WIDER, and it is recorded here so an editor of
4023
- * the residual note above knows a second module now disagrees with it.
4024
- * `src/deployment/outputs-export-alias.ts` deletes a class derived from
4025
- * `\p{Cc}` / `\p{Cf}` / `\p{Zl}` / `\p{Zp}` /
4026
- * `\p{Default_Ignorable_Code_Point}`, because on THAT path the subject is a possibly
4027
- * secret-bearing name in an operator's log: a plaintext split by a zero-width
4028
- * character is READ as if it were contiguous, so it is disclosed without any
4029
- * paste, and the command-forgery reasoning above does not transfer (issue
4030
- * [#2874](https://github.com/go-to-k/cdkd/issues/2874)). Nothing here changes
4031
- * -- widening this helper would alter every caller that merely wants a
4032
- * terminal-safe string.
4033
- */
4034
- function displaySafe(value, opts) {
4035
- if (value === void 0 || value === null) return "";
4036
- const text = String(value);
4037
- return (opts?.asciiOnly ? text.replace(/[^ -~]/g, " ") : text.replace(/[\u0000-\u001f\u007f-\u009f\u2028\u2029\u202a-\u202e\u2066-\u2069]/g, " ")).trim();
4038
- }
4039
-
4040
3993
  //#endregion
4041
3994
  //#region src/state/s3-replication-purge-gap.ts
4042
3995
  /**
@@ -6075,16 +6028,16 @@ var FileAssetPublisher = class {
6075
6028
  * Create ZIP archive and upload to S3
6076
6029
  */
6077
6030
  async uploadZip(client, dirPath, bucket, key) {
6078
- const archiver = await import("archiver");
6031
+ const { ZipArchive } = await import("archiver");
6079
6032
  const body = await new Promise((resolve, reject) => {
6080
6033
  const chunks = [];
6081
- const archive = archiver.default("zip", { zlib: { level: 9 } });
6034
+ const archive = new ZipArchive({ zlib: { level: 9 } });
6082
6035
  archive.on("data", (chunk) => chunks.push(chunk));
6083
6036
  archive.on("end", () => resolve(Buffer.concat(chunks)));
6084
6037
  archive.on("error", reject);
6085
6038
  if (statSync(dirPath).isDirectory()) archive.directory(dirPath, false);
6086
6039
  else archive.file(dirPath, { name: basename(dirPath) });
6087
- archive.finalize();
6040
+ archive.finalize().catch(reject);
6088
6041
  });
6089
6042
  await client.send(new PutObjectCommand({
6090
6043
  Bucket: bucket,
@@ -9656,18 +9609,20 @@ var S3StateBackend = class {
9656
9609
  async getState(stackName, region) {
9657
9610
  await this.ensureClientForBucket();
9658
9611
  const newKey = this.getStateKey(stackName, region);
9612
+ const shownStackName = this.displayName(stackName);
9613
+ const shownRegionName = displaySafe(region, { asciiOnly: true }) || "<unrenderable>";
9659
9614
  try {
9660
- this.logger.debug(`Getting state for stack: ${stackName} (${region})`);
9615
+ this.logger.debug(`Getting state for stack: ${shownStackName} (${shownRegionName})`);
9661
9616
  const response = await this.s3Client.send(new GetObjectCommand({
9662
9617
  Bucket: this.config.bucket,
9663
9618
  ...await this.ownerParam(),
9664
9619
  Key: newKey
9665
9620
  }));
9666
- if (!response.Body) throw new StateError(`State file for stack '${stackName}' (${region}) has no body`);
9667
- if (!response.ETag) throw new StateError(`State file for stack '${stackName}' (${region}) has no ETag`);
9621
+ if (!response.Body) throw new StateError(`State file for stack '${shownStackName}' (${shownRegionName}) has no body`);
9622
+ if (!response.ETag) throw new StateError(`State file for stack '${shownStackName}' (${shownRegionName}) has no ETag`);
9668
9623
  const bodyString = await response.Body.transformToString();
9669
9624
  const state = this.parseStateBody(bodyString, stackName);
9670
- this.logger.debug(`Retrieved state: ${stackName} (${region}), ETag: ${response.ETag}`);
9625
+ this.logger.debug(`Retrieved state: ${shownStackName} (${shownRegionName}), ETag: ${response.ETag}`);
9671
9626
  return {
9672
9627
  state,
9673
9628
  etag: response.ETag
@@ -9675,13 +9630,15 @@ var S3StateBackend = class {
9675
9630
  } catch (error) {
9676
9631
  if (!isNoSuchKey(error)) {
9677
9632
  if (error instanceof StateError) throw error;
9678
- throw new StateError(`Failed to get state for stack '${stackName}' (${region}): ${error instanceof Error ? error.message : String(error)}`, error instanceof Error ? error : void 0);
9633
+ const detail = displaySafe(error instanceof Error ? error.message : String(error), { asciiOnly: true }) || "<unrenderable>";
9634
+ throw new StateError(`Failed to get state for stack '${shownStackName}' (${shownRegionName}): ${detail}`, error instanceof Error ? error : void 0);
9679
9635
  }
9680
- this.logger.debug(`No state at new key for stack: ${stackName} (${region})`);
9636
+ this.logger.debug(`No state at new key for stack: ${shownStackName} (${shownRegionName})`);
9681
9637
  }
9682
9638
  const legacy = await this.tryGetLegacy(stackName, region);
9683
9639
  if (legacy) {
9684
- this.logger.warn(`Loaded legacy state for stack '${stackName}' from '${this.getLegacyStateKey(stackName)}'. It will be migrated to the region-scoped layout on next save.`);
9640
+ const shownKey = displaySafe(this.getLegacyStateKey(stackName), { asciiOnly: true }) || "<unrenderable>";
9641
+ this.logger.warn(`Loaded legacy state for stack '${shownStackName}' from '${shownKey}'. It will be migrated to the region-scoped layout on next save.`);
9685
9642
  return {
9686
9643
  ...legacy,
9687
9644
  migrationPending: true
@@ -10309,7 +10266,7 @@ var S3StateBackend = class {
10309
10266
  const bodyString = await response.Body.transformToString();
10310
10267
  const state = this.parseStateBody(bodyString, stackName);
10311
10268
  if (state.region && state.region !== region) {
10312
- this.logger.debug(`Legacy state for stack '${stackName}' has region '${state.region}', not '${region}' — skipping legacy fallback.`);
10269
+ this.logger.debug(`Legacy state for stack '${this.displayName(stackName)}' has region '${displaySafe(state.region, { asciiOnly: true }) || "<unrenderable>"}', not '${displaySafe(region, { asciiOnly: true }) || "<unrenderable>"}' — skipping legacy fallback.`);
10313
10270
  return null;
10314
10271
  }
10315
10272
  return {
@@ -10318,7 +10275,8 @@ var S3StateBackend = class {
10318
10275
  };
10319
10276
  } catch (error) {
10320
10277
  if (isNoSuchKey(error)) return null;
10321
- throw new StateError(`Failed to get legacy state for stack '${stackName}': ${error instanceof Error ? error.message : String(error)}`, error instanceof Error ? error : void 0);
10278
+ const detail = displaySafe(error instanceof Error ? error.message : String(error), { asciiOnly: true }) || "<unrenderable>";
10279
+ throw new StateError(`Failed to get legacy state for stack '${this.displayName(stackName)}': ${detail}`, error instanceof Error ? error : void 0);
10322
10280
  }
10323
10281
  }
10324
10282
  /**
@@ -10332,10 +10290,15 @@ var S3StateBackend = class {
10332
10290
  try {
10333
10291
  parsed = JSON.parse(bodyString);
10334
10292
  } catch (error) {
10335
- throw new StateError(`State file for stack '${stackName}' is not valid JSON: ${error instanceof Error ? error.message : String(error)}`, error instanceof Error ? error : void 0);
10293
+ const raw = error instanceof Error ? error.message : String(error);
10294
+ const detail = displaySafe(raw, { asciiOnly: true }) || "<unrenderable>";
10295
+ throw new StateError(`State file for stack '${this.displayName(stackName)}' is not valid JSON: ${detail}`, error instanceof Error ? error : void 0);
10336
10296
  }
10337
10297
  const v = parsed.version;
10338
- if (v !== void 0 && !STATE_SCHEMA_VERSIONS_READABLE.includes(v)) throw new StateError(`Unsupported state schema version ${String(v)} for stack '${stackName}'. This cdkd binary supports versions ${STATE_SCHEMA_VERSIONS_READABLE.join(", ")}. Upgrade cdkd to a version that supports schema ${String(v)}.`);
10298
+ if (v !== void 0 && !STATE_SCHEMA_VERSIONS_READABLE.includes(v)) {
10299
+ const shown = displaySafe(String(v), { asciiOnly: true }) || "<unrenderable>";
10300
+ throw new StateError(`Unsupported state schema version ${shown} for stack '${this.displayName(stackName)}'. This cdkd binary supports versions ${STATE_SCHEMA_VERSIONS_READABLE.join(", ")}. Upgrade cdkd to a version that supports schema ${shown}.`);
10301
+ }
10339
10302
  return parsed;
10340
10303
  }
10341
10304
  };
@@ -10381,6 +10344,20 @@ const MIN_RENEWAL_INTERVAL_MS = 1e3;
10381
10344
  */
10382
10345
  const LOCK_OBJECT_DESCRIPTION = "a stack lock heartbeat, which records the lock owner, its acquisition timestamp, its deadline and the operation name — no secret, but one row per renewal";
10383
10346
  /**
10347
+ * One spelling of "this value came from an S3 key or a lock record, and is
10348
+ * about to be interpolated into a message a terminal will render". A stack
10349
+ * name and an AWS region both have a known charset, so the ASCII allowlist is
10350
+ * a no-op on every legitimate input while an S3 key admits any UTF-8.
10351
+ *
10352
+ * Call it for a stack name or a region. Not every value in this file goes
10353
+ * through it, and this comment does not say which do: three revisions of that
10354
+ * sentence carried a count or a scope claim and each was wrong.
10355
+ * `grep safeSegment` answers it exactly.
10356
+ */
10357
+ function safeSegment(value) {
10358
+ return displaySafe(value, { asciiOnly: true }) || "<unrenderable>";
10359
+ }
10360
+ /**
10384
10361
  * S3-based lock manager using conditional writes (If-None-Match)
10385
10362
  *
10386
10363
  * Implements distributed locking using S3's If-None-Match: "*" condition
@@ -10585,7 +10562,7 @@ var LockManager = class {
10585
10562
  }
10586
10563
  return false;
10587
10564
  }
10588
- throw new LockError(`Failed to acquire lock for stack '${displaySafe(stackName, { asciiOnly: true }) || "<unrenderable>"}' (${displaySafe(region, { asciiOnly: true }) || "<unrenderable>"}): ${displaySafe(error instanceof Error ? error.message : String(error))}`, error instanceof Error ? error : void 0);
10565
+ throw new LockError(`Failed to acquire lock for stack '${safeSegment(stackName)}' (${safeSegment(region)}): ${displaySafe(error instanceof Error ? error.message : String(error), { asciiOnly: true }) || "<unrenderable>"}`, error instanceof Error ? error : void 0);
10589
10566
  }
10590
10567
  }
10591
10568
  /**
@@ -10628,18 +10605,19 @@ var LockManager = class {
10628
10605
  async getLockRecord(stackName, region) {
10629
10606
  await this.ensureClientForBucket();
10630
10607
  const key = this.getLockKey(stackName, region);
10608
+ const shownStack = safeSegment(stackName);
10631
10609
  try {
10632
- this.logger.debug(`Getting lock info for stack: ${stackName}`);
10610
+ this.logger.debug(`Getting lock info for stack: ${shownStack}`);
10633
10611
  const response = await this.s3Client.send(new GetObjectCommand({
10634
10612
  Bucket: this.config.bucket,
10635
10613
  ...await this.ownerParam(),
10636
10614
  Key: key
10637
10615
  }));
10638
- if (!response.Body) throw new LockError(`Lock file for stack '${stackName}' has no body`);
10616
+ if (!response.Body) throw new LockError(`Lock file for stack '${shownStack}' has no body`);
10639
10617
  const bodyString = await response.Body.transformToString();
10640
10618
  const raw = JSON.parse(bodyString);
10641
10619
  if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
10642
- this.logger.debug(`Lock file for stack ${stackName} is not an object; treating as absent`);
10620
+ this.logger.debug(`Lock file for stack ${shownStack} is not an object; treating as absent`);
10643
10621
  return null;
10644
10622
  }
10645
10623
  const parsed = raw;
@@ -10648,18 +10626,19 @@ var LockManager = class {
10648
10626
  owner: displaySafe(parsed.owner),
10649
10627
  ...parsed.operation !== void 0 && { operation: displaySafe(parsed.operation) }
10650
10628
  };
10651
- this.logger.debug(`Lock info for stack: ${stackName}:`, lockInfo);
10629
+ this.logger.debug(`Lock info for stack: ${shownStack}:`, lockInfo);
10652
10630
  return {
10653
10631
  info: lockInfo,
10654
10632
  etag: response.ETag
10655
10633
  };
10656
10634
  } catch (error) {
10657
10635
  if (error instanceof NoSuchKey) {
10658
- this.logger.debug(`No lock exists for stack: ${stackName}`);
10636
+ this.logger.debug(`No lock exists for stack: ${shownStack}`);
10659
10637
  return null;
10660
10638
  }
10661
10639
  if (error instanceof LockError) throw error;
10662
- throw new LockError(`Failed to get lock info for stack '${stackName}': ${error instanceof Error ? error.message : String(error)}`, error instanceof Error ? error : void 0);
10640
+ const detail = displaySafe(error instanceof Error ? error.message : String(error), { asciiOnly: true }) || "<unrenderable>";
10641
+ throw new LockError(`Failed to get lock info for stack '${shownStack}': ${detail}`, error instanceof Error ? error : void 0);
10663
10642
  }
10664
10643
  }
10665
10644
  /**
@@ -11156,7 +11135,7 @@ var LockManager = class {
11156
11135
  if (lockInfo) {
11157
11136
  const remainingMs = lockInfo.expiresAt - Date.now();
11158
11137
  if (attempt < maxRetries) {
11159
- this.logger.info(`Stack '${displaySafe(stackName, { asciiOnly: true }) || "<unrenderable>"}' (${displaySafe(region, { asciiOnly: true }) || "<unrenderable>"}) is locked by ${lockInfo.owner}${lockInfo.operation ? ` (operation: ${lockInfo.operation})` : ""}. Lock expires in ${this.formatDuration(remainingMs)}. Retrying in ${this.formatDuration(retryDelay)}... (attempt ${attempt + 1}/${maxRetries})`);
11138
+ this.logger.info(`Stack '${safeSegment(stackName)}' (${safeSegment(region)}) is locked by ${lockInfo.owner}${lockInfo.operation ? ` (operation: ${lockInfo.operation})` : ""}. Lock expires in ${this.formatDuration(remainingMs)}. Retrying in ${this.formatDuration(retryDelay)}... (attempt ${attempt + 1}/${maxRetries})`);
11160
11139
  await new Promise((resolve) => setTimeout(resolve, retryDelay));
11161
11140
  continue;
11162
11141
  }
@@ -11166,8 +11145,8 @@ var LockManager = class {
11166
11145
  const expiresIn = lockInfo ? this.formatDuration(lockInfo.expiresAt - Date.now()) : "unknown";
11167
11146
  const forceUnlockCommand = buildForceUnlockCommand(stackName, region);
11168
11147
  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)}`;
11169
- const safeStack = displaySafe(stackName, { asciiOnly: true }) || "<unrenderable>";
11170
- const safeRegion = displaySafe(region, { asciiOnly: true }) || "<unrenderable>";
11148
+ const safeStack = safeSegment(stackName);
11149
+ const safeRegion = safeSegment(region);
11171
11150
  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}`));
11172
11151
  }
11173
11152
  };
@@ -11272,6 +11251,15 @@ function resolvedPlaintextOf(secrets, expression) {
11272
11251
  return typeof recorded === "string" ? recorded : void 0;
11273
11252
  }
11274
11253
  /**
11254
+ * Every expression the pass that owns `secrets` resolved — the pair table's
11255
+ * keys, a CONFLICTING one included (it is still an expression this pass saw,
11256
+ * and a second sighting is what a uniqueness rule must count). The map's own
11257
+ * values name only the SURVIVOR per plaintext; this names the losers too.
11258
+ */
11259
+ function resolvedExpressionsOf(secrets) {
11260
+ return [...resolvedPairsOf.get(secrets)?.keys() ?? []];
11261
+ }
11262
+ /**
11275
11263
  * The bag OBJECTS a deploy pass produced ITSELF and installed on a success
11276
11264
  * path — the fact the resolved-pair evidence above cannot state (issue
11277
11265
  * [#2516](https://github.com/go-to-k/cdkd/issues/2516)).
@@ -11289,8 +11277,9 @@ function resolvedPlaintextOf(secrets, expression) {
11289
11277
  * is carried on the object: {@link markSameGenerationBag} at the moment the
11290
11278
  * successful result is installed, consulted by {@link redactSecretsForState}
11291
11279
  * for the object it is handed. A copy of the bag, a derived needle map, a
11292
- * previous generation's record, a scrub / import / drift walk and a bag the
11293
- * engine did not mark all answer `false` and keep the fall-through.
11280
+ * previous generation's record, a scrub / drift walk and a bag nobody marked
11281
+ * all answer `false` and keep the fall-through. `cdkd import` marks the one
11282
+ * bag its own resolver produced (the sixth site below, issue #2745).
11294
11283
  *
11295
11284
  * A `WeakSet` for the reason {@link resolvedPairsOf} is a `WeakMap`: the mark
11296
11285
  * dies with the object it is on, and nothing has to clear it.
@@ -11313,9 +11302,11 @@ const sameGenerationBags = /* @__PURE__ */ new WeakSet();
11313
11302
  * be written as the token this pass recorded. What never qualifies is a bag of
11314
11303
  * MIXED provenance: a provider's `effectiveProperties` replacement may carry
11315
11304
  * previous-state values IN, so an object-level mark on it would prove nothing
11316
- * for those leaves — it stays unmarked and keeps the residual. Nor does the
11317
- * object the resolver produced, marked at resolution time: it is not
11318
- * necessarily the object state ends up holding.
11305
+ * for those leaves — it stays unmarked and keeps the residual. Nor does an
11306
+ * object marked INSIDE the resolver, at resolution time: whether that object,
11307
+ * a narrowed copy of it or a provider's replacement is what gets redacted and
11308
+ * stored is the CALLER's decision, so the mark is taken by the caller at the
11309
+ * redaction call — the engine's five sites, and `cdkd import`'s one.
11319
11310
  *
11320
11311
  * The CONDITIONS are per SITE, not global, and stating them globally is what
11321
11312
  * this paragraph kept getting wrong (PR 2753, rounds 3 and 4). "After the
@@ -11323,9 +11314,9 @@ const sameGenerationBags = /* @__PURE__ */ new WeakSet();
11323
11314
  * and may skip it entirely. "Every leaf this pass produced" is false for the
11324
11315
  * auto-refresh readback of an UNCHANGED resource, where nothing was resolved
11325
11316
  * at all — there the safety comes from the empty secrets map, not from the
11326
- * mark. Read the per-site list below rather than a rule over all five.
11317
+ * mark. Read the per-site list below rather than a rule over all six.
11327
11318
  *
11328
- * The five call sites, and which of them the record HOLDS, because the earlier
11319
+ * The six call sites, and which of them the record HOLDS, because the earlier
11329
11320
  * "two never-installed copies" reading of this paragraph was false once the
11330
11321
  * third copy arrived:
11331
11322
  * - `propertiesToRecord` — the record's `properties`, installed. The resolved
@@ -11359,11 +11350,28 @@ const sameGenerationBags = /* @__PURE__ */ new WeakSet();
11359
11350
  * - the rollback journal's FAILED-op `attemptedProperties` — a marked copy, so
11360
11351
  * that persisted artifact does not carry the plaintext on exactly the
11361
11352
  * failure path. NOT installed on the record.
11353
+ * - `cdkd import`'s `resolveImportedProperties` (issue #2745) — the bag
11354
+ * import's OWN resolver produced from the imported template, marked as the
11355
+ * redaction input; the record holds the redacted COPY, unmarked. Not a
11356
+ * deploy pass, and the mark does not say it is: what it claims is PAIR
11357
+ * PROVENANCE — this bag was produced from this source, in this pass, by the
11358
+ * resolver that recorded these pairs — which import's resolve satisfies
11359
+ * exactly as the create arm's does, since `unresolvedProperties` is the
11360
+ * template bag it resolved and its per-resource map holds the pairs. What
11361
+ * it does NOT claim is that AWS holds the value: `observedProperties` is
11362
+ * captured separately, against the redacted record, and is never marked
11363
+ * here.
11362
11364
  */
11363
11365
  function markSameGenerationBag(bag) {
11364
11366
  sameGenerationBags.add(bag);
11365
11367
  return bag;
11366
11368
  }
11369
+ /**
11370
+ * Exported for `cdkd import`'s unit test, which asserts that the STORED record
11371
+ * is not the marked object (maintainer review of PR 3052). Read-only over the
11372
+ * `WeakSet`: no behavior in this module changes with the export, and the only
11373
+ * writer, {@link markSameGenerationBag}, was exported already.
11374
+ */
11367
11375
  function isSameGenerationBag(bag) {
11368
11376
  return sameGenerationBags.has(bag);
11369
11377
  }
@@ -12683,6 +12691,55 @@ function joinSkeletonSegments(segments) {
12683
12691
  * choice, not the only option.
12684
12692
  */
12685
12693
  function intrinsicSkeletonPattern(source) {
12694
+ const segments = intrinsicSkeletonSegments(source);
12695
+ if (segments === void 0) return void 0;
12696
+ return anchoredSkeletonPattern(segments);
12697
+ }
12698
+ /**
12699
+ * {@link joinSkeletonSegments} anchored at both ends, or `undefined` where the
12700
+ * wildcard cap refused it. The one place the skeleton's regex is built, for
12701
+ * both readers of the segment form — and the one place a literal segment is
12702
+ * ESCAPED: it takes the segment form itself (literal text, or
12703
+ * {@link UNKNOWN_PART}), never pre-built regex text, so a third caller cannot
12704
+ * hand it raw template text and get a pattern with live metacharacters and no
12705
+ * type error (maintainer review of PR 3052).
12706
+ */
12707
+ function anchoredSkeletonPattern(segments) {
12708
+ const body = joinSkeletonSegments(segments.map((segment) => segment === UNKNOWN_PART ? SKELETON_WILDCARD : escapeRegExp(segment)));
12709
+ return body === void 0 ? void 0 : new RegExp(`^${body}$`);
12710
+ }
12711
+ /**
12712
+ * A source part the skeleton cannot know — an `Fn::Join` element that is
12713
+ * itself an intrinsic, or an `Fn::Sub` `${...}` variable. The segment form of
12714
+ * {@link SKELETON_WILDCARD}: what a part IS, before {@link anchoredSkeletonPattern}
12715
+ * — the one speller — turns it into the wildcard.
12716
+ */
12717
+ const UNKNOWN_PART = Symbol("unknown intrinsic part");
12718
+ /**
12719
+ * Stands in, while a source is RENDERED to text, for a part the skeleton cannot
12720
+ * know. A single character so the rendered string keeps the literal parts at
12721
+ * their true offsets, and NOT `}` so {@link dynamicReferenceSpans} reads it as
12722
+ * a token's INNER text (the resolver's `[^}]+`) rather than as its terminator —
12723
+ * which is exactly how a `{Ref}` sitting inside a `{{resolve:...}}` opening
12724
+ * must read. A NUL, RESERVED here as the placeholder: a template string can
12725
+ * carry one (CDK preserves it), so a source whose literal text does is refused
12726
+ * outright rather than rendered with one unknowable part too many.
12727
+ */
12728
+ const UNKNOWN_PART_PLACEHOLDER = "\0";
12729
+ /**
12730
+ * The text of an `Fn::Join` / `Fn::Sub` source in order: a literal part as its
12731
+ * RAW text, an unknowable part as {@link UNKNOWN_PART}. ONE parser for the two
12732
+ * readers of that shape — {@link intrinsicSkeletonPattern}, which hands every
12733
+ * segment to {@link anchoredSkeletonPattern} to spell as a regex, and
12734
+ * {@link positionByIntrinsicFrame}, which needs the
12735
+ * literal text VERBATIM to know where a token's frame begins and ends — so
12736
+ * the two cannot disagree about what a source says (issue #2745).
12737
+ *
12738
+ * `undefined` for any source this cannot describe, per the pattern reader's
12739
+ * doc: a delimiter that is itself an intrinsic, a non-array `Fn::Join`, a
12740
+ * non-string `Fn::Sub` template, an object that is not a single-key intrinsic.
12741
+ */
12742
+ function intrinsicSkeletonSegments(source) {
12686
12743
  const keys = Object.keys(source);
12687
12744
  if (keys.length !== 1) return void 0;
12688
12745
  const key = keys[0];
@@ -12691,14 +12748,12 @@ function intrinsicSkeletonPattern(source) {
12691
12748
  if (!Array.isArray(args) || args.length !== 2) return void 0;
12692
12749
  const [delimiter, parts] = args;
12693
12750
  if (typeof delimiter !== "string" || !Array.isArray(parts)) return void 0;
12694
- const separator = escapeRegExp(delimiter);
12695
12751
  const segments = [];
12696
12752
  parts.forEach((part, index) => {
12697
- if (index > 0) segments.push(separator);
12698
- segments.push(typeof part === "string" ? escapeRegExp(part) : SKELETON_WILDCARD);
12753
+ if (index > 0) segments.push(delimiter);
12754
+ segments.push(typeof part === "string" ? part : UNKNOWN_PART);
12699
12755
  });
12700
- const body = joinSkeletonSegments(segments);
12701
- return body === void 0 ? void 0 : new RegExp(`^${body}$`);
12756
+ return segments;
12702
12757
  }
12703
12758
  if (key === "Fn::Sub") {
12704
12759
  const args = source[key];
@@ -12709,14 +12764,13 @@ function intrinsicSkeletonPattern(source) {
12709
12764
  const variable = /\$\{([^}]*)\}/g;
12710
12765
  let hit;
12711
12766
  while ((hit = variable.exec(template)) !== null) {
12712
- segments.push(escapeRegExp(template.slice(cursor, hit.index)));
12767
+ segments.push(template.slice(cursor, hit.index));
12713
12768
  const inner = hit[1];
12714
- segments.push(inner.startsWith("!") ? escapeRegExp(`\${${inner.slice(1)}}`) : SKELETON_WILDCARD);
12769
+ segments.push(inner.startsWith("!") ? `\${${inner.slice(1)}}` : UNKNOWN_PART);
12715
12770
  cursor = hit.index + hit[0].length;
12716
12771
  }
12717
- segments.push(escapeRegExp(template.slice(cursor)));
12718
- const body = joinSkeletonSegments(segments);
12719
- return body === void 0 ? void 0 : new RegExp(`^${body}$`);
12772
+ segments.push(template.slice(cursor));
12773
+ return segments;
12720
12774
  }
12721
12775
  }
12722
12776
  /** Poison for an expression this pass recorded against two different plaintexts. */
@@ -12754,10 +12808,11 @@ const CONFLICTING_PLAINTEXT = Symbol("conflicting plaintext");
12754
12808
  * iterating it never yields one plaintext twice and a second sighting of an
12755
12809
  * expression is always a DIFFERENT plaintext.
12756
12810
  *
12757
- * SHARED by {@link positionByIntrinsicSkeleton} and
12758
- * {@link positionByCrossStackSource} (issue #2059) rather than copied into the
12759
- * second: the poisoning rule is the subtle half of condition 3, and two copies
12760
- * are two places for it to be relaxed independently.
12811
+ * SHARED by {@link positionByIntrinsicSkeleton},
12812
+ * {@link positionByCrossStackSource} (issue #2059) and
12813
+ * {@link positionByIntrinsicFrame} (issue #2745) rather than copied: the
12814
+ * poisoning rule is the subtle half of condition 3, and every copy is another
12815
+ * place for it to be relaxed independently.
12761
12816
  */
12762
12817
  function plaintextIndexOf(secrets) {
12763
12818
  const plaintextOf = /* @__PURE__ */ new Map();
@@ -12903,8 +12958,9 @@ function associationForSource(source, secrets) {
12903
12958
  * leaf identity; the arm fires only for the spellings
12904
12959
  * {@link crossStackSourceKey} can key; and
12905
12960
  * condition 1 still demands that the bag leaf be a plaintext this pass
12906
- * resolved. Every rejection degrades to {@link positionByIntrinsicSkeleton} and
12907
- * then to the value scan, i.e. to today's behavior.
12961
+ * resolved. Every rejection degrades to {@link positionByIntrinsicSkeleton},
12962
+ * {@link positionByIntrinsicFrame} and then to the value scan, i.e. to today's
12963
+ * behavior.
12908
12964
  */
12909
12965
  function positionByCrossStackSource(bag, source, secrets) {
12910
12966
  const association = associationForSource(source, secrets);
@@ -12930,7 +12986,8 @@ function positionByCrossStackSource(bag, source, secrets) {
12930
12986
  * 1. The bag leaf's WHOLE value is a recorded secret plaintext. The skeleton
12931
12987
  * describes ONE complete `{{resolve:...}}` token, so a leaf that merely
12932
12988
  * EMBEDS a secret (a join with surrounding text) is not this shape at all
12933
- * and must keep going to the value scan, which rewrites just the substring.
12989
+ * and goes on to {@link positionByIntrinsicFrame} (issue #2745), and only
12990
+ * where that refuses to the value scan, which rewrites just the substring.
12934
12991
  * 2. EXACTLY ONE candidate matches. Two matching candidates mean the skeleton
12935
12992
  * genuinely cannot separate them (`{Ref}` in the position that differs), and
12936
12993
  * guessing would be the collapse this fix exists to remove, one step over.
@@ -12942,8 +12999,9 @@ function positionByCrossStackSource(bag, source, secrets) {
12942
12999
  * candidate absent from that map is a collapsed LOSER, which is the case
12943
13000
  * this whole function exists to serve, so it is accepted.
12944
13001
  *
12945
- * Every rejection degrades to the value scan, i.e. to today's behavior, so no
12946
- * case gets worse than it is without this pass.
13002
+ * Every rejection falls to {@link positionByIntrinsicFrame} (issue #2745) and,
13003
+ * where that arm refuses too, to the value scan, i.e. to today's behavior, so
13004
+ * no case gets worse than it is without this pass.
12947
13005
  *
12948
13006
  * One residual is worth naming rather than leaving to be rediscovered: a single
12949
13007
  * WRONG candidate can win only when this leaf's own expression is in NEITHER
@@ -12990,13 +13048,14 @@ function positionByIntrinsicSkeleton(bag, source, secrets, secretExpressions) {
12990
13048
  return matched;
12991
13049
  }
12992
13050
  /**
12993
- * The ONE-span frame shared by {@link positionByEmbeddedSpan} and
13051
+ * The ONE-span frame shared by {@link positionByEmbeddedSpan},
13052
+ * {@link positionByIntrinsicFrame} (over the source's rendered text) and
12994
13053
  * {@link learnMixedLeafNeedle}: a source holding exactly one `{{resolve:...}}`
12995
13054
  * token, and a bag that starts with the source's prefix and ends with its
12996
13055
  * suffix with something non-empty between them that is NOT itself a complete
12997
13056
  * token (an already-redacted record is a persisted answer, not a plaintext).
12998
- * `undefined` for any other shape. One helper rather than two copies so the
12999
- * two refusals cannot drift apart.
13057
+ * `undefined` for any other shape. One helper rather than three copies so the
13058
+ * three refusals cannot drift apart.
13000
13059
  */
13001
13060
  function singleSpanFrame(bag, source) {
13002
13061
  const spans = dynamicReferenceSpans(source);
@@ -13105,32 +13164,28 @@ function singleSpanFrame(bag, source) {
13105
13164
  * for a 4+ character coincidence in a readback. Every refusal of this arm
13106
13165
  * returns the scan's answer, so a leaf refused for interference (a frame
13107
13166
  * that is itself a recorded plaintext, say) has its frame rewritten and its
13108
- * sub-floor middle left in plaintext. And this arm positions a LITERAL
13109
- * source leaf only: an `Fn::Join` / `Fn::Sub` source rendering the same
13110
- * `port:` + token goes to {@link positionByIntrinsicSkeleton}, which refuses
13111
- * unless the WHOLE bag is a recorded plaintext, so a sub-floor secret
13112
- * embedded through an intrinsic — the dominant CDK shape — still falls to
13113
- * the value scan and persists in plaintext. `cdkd scrub`, the documented
13114
- * repair tool for a pre-GHSA record, cannot repair a sub-floor embedded leaf
13115
- * either: it walks a STORED bag, which no deploy marked, so the arm is
13116
- * unreachable from it by construction and the leaf keeps the scan's answer.
13117
- * The MASKING channel keeps the residual whole: `maskSecretsInText`'s
13118
- * substring arm carries the same four-character floor, so once this arm has
13119
- * put `port:{{resolve:...}}` in state, a warn line quoting an AWS message can
13120
- * still print `port:q7`. Pre-existing and not a regression -- the #2453 class,
13121
- * and the reason the list would otherwise read as complete when it is not
13122
- * (maintainer review of PR 2753, round 2).
13123
- *
13124
- * The next DEPLOY of that resource repairs the leaves this arm is eligible
13125
- * for — a LITERAL source leaf on a bag the engine marks — because the
13126
- * re-check compares unequal against a record holding the plaintext and the
13127
- * resource is written again from a bag this pass produced. The residuals
13128
- * named above (an `effectiveProperties` substitution, an intrinsic source
13129
- * shape) are not repaired by that deploy either; they stay tracked by
13130
- * #2745. Tracked, with the sub-floor
13131
- * residuals of a nested-stack child's inherited parameter and of `cdkd
13132
- * import`'s own resolution, by issue
13133
- * [#2745](https://github.com/go-to-k/cdkd/issues/2745).
13167
+ * sub-floor middle left in plaintext. This arm positions a LITERAL source
13168
+ * leaf; an `Fn::Join` / `Fn::Sub` source rendering the same `port:` + token
13169
+ * — the dominant CDK shape — is {@link positionByIntrinsicFrame}'s (issue
13170
+ * #2745), which shares this arm's bound and mark and refuses a frame that is
13171
+ * not wholly literal (a region `Ref` OUTSIDE the token), where a sub-floor
13172
+ * secret still falls to the value scan and persists in plaintext. `cdkd
13173
+ * scrub`, the documented repair tool for a pre-GHSA record, cannot repair a
13174
+ * sub-floor embedded leaf: it walks a STORED bag, which no deploy marked, so
13175
+ * both arms are unreachable from it by construction and the leaf keeps the
13176
+ * scan's answer. The MASKING channel keeps the residual whole:
13177
+ * `maskSecretsInText`'s substring arm carries the same four-character floor,
13178
+ * so once this arm has put `port:{{resolve:...}}` in state, a warn line
13179
+ * quoting an AWS message can still print `port:q7`. Pre-existing and not a
13180
+ * regression -- the #2453 class, and the reason the list would otherwise
13181
+ * read as complete when it is not (maintainer review of PR 2753, round 2).
13182
+ *
13183
+ * The next DEPLOY of that resource repairs the leaves the two arms are
13184
+ * eligible for — a literal-frame source leaf on a bag the engine marks —
13185
+ * because the re-check compares unequal against a record holding the
13186
+ * plaintext and the resource is written again from a bag this pass produced.
13187
+ * The residuals named above (an `effectiveProperties` substitution, a
13188
+ * nonliteral frame) are not repaired by that deploy either.
13134
13189
  *
13135
13190
  * One shape reaches this arm that a reader may not expect: a WHOLE-token
13136
13191
  * source that FAILED the whole-token arm's `isKnownSecretExpression` gate (an
@@ -13140,31 +13195,58 @@ function singleSpanFrame(bag, source) {
13140
13195
  * where the scan wrote the survivor. Stated so it is not mistaken for a leak.
13141
13196
  *
13142
13197
  * Everything else keeps the pre-#2485 fall-through: two or more spans (which
13143
- * span produced which value is genuinely ambiguous when they share one), an
13144
- * `Fn::Sub` / `Fn::Join` source (an object, not this arm at all — issue #2320's
13145
- * placeholder primitive), a frame mismatch, a middle that is itself a complete
13146
- * token (an already-redacted record, per the same refusal
13147
- * {@link learnMixedLeafNeedle} makes), and a middle this pass cannot vouch for.
13198
+ * span produced which value is genuinely ambiguous when they share one), a
13199
+ * frame mismatch, a middle that is itself a complete token (an
13200
+ * already-redacted record, per the same refusal {@link learnMixedLeafNeedle}
13201
+ * makes), and a middle this pass cannot vouch for. An `Fn::Sub` / `Fn::Join`
13202
+ * source (an object, not this arm at all — issue #2320's placeholder
13203
+ * primitive) is {@link positionByIntrinsicFrame}'s since #2745, and reaches
13204
+ * the value scan only where that arm refuses.
13148
13205
  *
13149
13206
  * The frame is copied from the SOURCE, not scanned. A needle occurring in the
13150
13207
  * literal frame would be a reference the template never had at that offset —
13151
13208
  * the fabricated-baseline direction {@link preferPositionDecisions} refuses —
13152
13209
  * and the whole-token arm returns its source unscanned for the same reason.
13153
13210
  *
13154
- * RETURNS THE VALUE SCAN'S ANSWER ON EVERY REFUSAL, not `undefined`: the scan
13155
- * is computed once, here, for `(bag, secrets)` — the arm's bound below compares
13156
- * against it, and every fall-through IS it — so the compared value provably
13211
+ * RETURNS THE VALUE SCAN'S ANSWER ON EVERY REFUSAL, not `undefined`: the two
13212
+ * early returns below compute it for `(bag, secrets)`, and the bound in
13213
+ * {@link writeFramedTokenWithinScanBound} computes its own from the same two
13214
+ * arguments — every fall-through IS that scan — so the compared value provably
13157
13215
  * comes from the same bag and map the arm positions. An earlier revision took
13158
13216
  * the scan as a parameter, which left the bound one wrong caller away from
13159
- * comparing against a scan of some other bag with no type error.
13217
+ * comparing against a scan of some other bag with no type error; the shared
13218
+ * helper keeps that property by taking `(bag, secrets)` rather than a scan.
13160
13219
  */
13161
13220
  function positionByEmbeddedSpan(bag, source, secrets, bagIsSameGeneration) {
13162
- const scanned = redactSecretsForState(bag, secrets);
13163
13221
  const frame = singleSpanFrame(bag, source);
13164
- if (frame === void 0) return scanned;
13222
+ if (frame === void 0) return redactSecretsForState(bag, secrets);
13223
+ const recorded = resolvedPlaintextOf(secrets, frame.token);
13224
+ if (recorded === void 0 || recorded !== frame.middle) return redactSecretsForState(bag, secrets);
13225
+ return writeFramedTokenWithinScanBound(bag, secrets, frame, bagIsSameGeneration);
13226
+ }
13227
+ /**
13228
+ * Write `prefix + token + suffix` for a framed leaf whose pass-local pair the
13229
+ * CALLER has already verified (`token` resolved to `middle` in the pass that
13230
+ * owns `secrets`), and ONLY where that stays within the value scan's own class
13231
+ * of answer — or, below the scan's floor, on a bag the engine marked. Returns
13232
+ * the scan's answer on every refusal.
13233
+ *
13234
+ * ONE helper for {@link positionByEmbeddedSpan} (a literal source, issue
13235
+ * #2485) and {@link positionByIntrinsicFrame} (an `Fn::Join` / `Fn::Sub`
13236
+ * source, issue #2745), for the reason {@link singleSpanFrame} is one: the
13237
+ * bound and the mark are the two claims this module makes about a FRAMED
13238
+ * leaf, and two copies are two places for one of them to be relaxed alone.
13239
+ *
13240
+ * THE SCAN IS COMPUTED HERE, for `(bag, secrets)` — the bound below compares
13241
+ * against it, and every fall-through IS it — so the compared value provably
13242
+ * comes from the same bag and map the token is written into. An earlier
13243
+ * revision of the literal arm took the scan as a parameter, which left the
13244
+ * bound one wrong caller away from comparing against a scan of some other
13245
+ * bag with no type error.
13246
+ */
13247
+ function writeFramedTokenWithinScanBound(bag, secrets, frame, bagIsSameGeneration) {
13248
+ const scanned = redactSecretsForState(bag, secrets);
13165
13249
  const { token, prefix, suffix, middle } = frame;
13166
- const recorded = resolvedPlaintextOf(secrets, token);
13167
- if (recorded === void 0 || recorded !== middle) return scanned;
13168
13250
  const survivor = secrets.get(middle);
13169
13251
  if (survivor === void 0) return scanned;
13170
13252
  if (scanned === prefix + survivor + suffix) return prefix + token + suffix;
@@ -13172,6 +13254,152 @@ function positionByEmbeddedSpan(bag, source, secrets, bagIsSameGeneration) {
13172
13254
  return scanned;
13173
13255
  }
13174
13256
  /**
13257
+ * Position a bag leaf whose `Fn::Join` / `Fn::Sub` source EMBEDS exactly one
13258
+ * `{{resolve:...}}` token inside LITERAL surrounding text — `port:` + token —
13259
+ * by writing that token into the frame the source states (issue
13260
+ * [#2745](https://github.com/go-to-k/cdkd/issues/2745)).
13261
+ *
13262
+ * This is the intrinsic twin of {@link positionByEmbeddedSpan}, and the shape
13263
+ * is the DOMINANT one rather than a corner: `'port:' + secret.secretValueFromJson('pin')`
13264
+ * renders the secret ARN as a `Ref`, so CDK emits
13265
+ * `{"Fn::Join": ["", ["port:{{resolve:secretsmanager:", {"Ref": ...}, ":SecretString:pin::}}"]]}`
13266
+ * — the prefix FUSED into the token's opening part, the `Ref` INSIDE the
13267
+ * token. Before this arm such a leaf had no positioning route at all:
13268
+ * {@link positionByIntrinsicSkeleton} refuses unless the WHOLE bag is a
13269
+ * recorded plaintext (its condition 1), and {@link positionByEmbeddedSpan}
13270
+ * needs a source STRING to copy its frame from. So it fell to the value scan,
13271
+ * which for a 4+ character middle writes the map's SURVIVOR (a same-plaintext
13272
+ * sibling's spelling, the #1904 / #2485 class) and for a 1-3 character middle
13273
+ * writes NOTHING — {@link buildNeedleRegex} drops it from the alternation —
13274
+ * and `port:q7` was persisted in plaintext.
13275
+ *
13276
+ * HOW THE FRAME IS FOUND. The source is rendered to text by
13277
+ * {@link intrinsicSkeletonSegments} — the same parser the skeleton arm reads —
13278
+ * with each unknowable part as one {@link UNKNOWN_PART_PLACEHOLDER}, and
13279
+ * {@link singleSpanFrame} then finds the ONE token in that text exactly as it
13280
+ * does for a literal source. The prefix and suffix must be wholly literal: a
13281
+ * placeholder OUTSIDE the token (a region `Ref` after it, the `DB_URL` shape
13282
+ * of the `secrets-dynamic-ref` fixture) means the source cannot establish
13283
+ * where the middle ends, and the leaf is REFUSED — the nonliteral-frame shape
13284
+ * #2745 deliberately defers. Inside the token a placeholder becomes a
13285
+ * {@link SKELETON_WILDCARD}, capped by {@link MAX_SKELETON_WILDCARDS} through
13286
+ * the same {@link joinSkeletonSegments}, so the token pattern is the skeleton
13287
+ * arm's pattern restricted to the token's own extent.
13288
+ *
13289
+ * THE EVIDENCE, three checks and each removes a different way of being wrong:
13290
+ *
13291
+ * 1. EXACTLY ONE candidate expression matches the token pattern, and is not
13292
+ * DEMONSTRABLY another value's — the skeleton arm's conditions 2 and 3,
13293
+ * over its two candidate stores PLUS the pass-local pair table
13294
+ * ({@link resolvedExpressionsOf}; the code says why), and the same
13295
+ * {@link plaintextIndexOf} poisoning rule, with `middle` in the role the
13296
+ * whole bag plays there.
13297
+ * 2. THIS PASS resolved that candidate to the middle ({@link recordResolvedPair},
13298
+ * per map instance) — the literal arm's evidence, and what a
13299
+ * shape-plausible expression this pass never resolved to the middle
13300
+ * (another resource's, typically) cannot satisfy. What it does NOT prove
13301
+ * is that the candidate is THIS leaf's token. A leaf
13302
+ * whose own reference resolved PUBLIC (an `ssm` `String`, which nothing
13303
+ * records) and carries an unknowable part INSIDE its token (a `Ref` in the
13304
+ * parameter name — a wholly literal public token matches no candidate and
13305
+ * refuses), beside a same-service SECRET sibling whose value coincides
13306
+ * with the middle, passes all three checks and takes the sibling's
13307
+ * expression — a wrong REFERENCE, not a disclosure, the class the
13308
+ * floorless whole-value scan already accepts for a whole-leaf coincidence.
13309
+ * Against the value scan: the scan writes the map's SURVIVOR for a 4+
13310
+ * character middle and nothing below the floor, while this arm writes the
13311
+ * same-service candidate — the two coincide only when that candidate is
13312
+ * the survivor, so at 4+ the arm can change WHICH wrong reference is
13313
+ * taken, and below the floor on a marked bag it adds one where the scan
13314
+ * wrote nothing. "Not a disclosure" is a claim about the STORED artifact
13315
+ * only: `resolveReplayProps` (`rollback-executor.ts`) re-resolves every
13316
+ * `{{resolve:` token in a replayed bag without regard to which reference
13317
+ * the leaf named, so a later failed deploy plus `cdkd rollback` resolves
13318
+ * the sibling's expression and writes its CURRENT plaintext into the live
13319
+ * property — the transformed-value-meets-inverse-transform shape of
13320
+ * GHSA-p5qg-v9gv-hc7w's consumer, reached here through a wrong reference
13321
+ * rather than a wrong value (maintainer review of PR 3052). THREE live
13322
+ * consumers re-resolve a stored expression this way, each with a weaker
13323
+ * precondition than the last. `cdkd drift --revert`
13324
+ * (`resolveStateSecretExpressions`, `drift.ts`) needs no failed deploy:
13325
+ * once the sibling rotates, the re-resolved baseline diverges from AWS,
13326
+ * drift fires, and `--revert` pushes the sibling's plaintext to the live
13327
+ * property — and `--accept` is no way out there, since the AWS-side value
13328
+ * of a secret-classified path is masked and `runAccept` refuses to persist
13329
+ * the mask, leaving `--revert` (the harmful button) or a redeploy. A
13330
+ * consumer stack's cross-stack read (`reresolveCrossStackValue`,
13331
+ * `intrinsic-function-resolver.ts`: `Fn::ImportValue`, `Fn::GetStackOutput`,
13332
+ * a parent's `Fn::GetAtt Nested.Outputs.X`) needs nothing at all: the
13333
+ * outputs bag is marked, so a sub-floor write reaches `state.outputs`, and
13334
+ * an ORDINARY deploy of the consumer after the sibling rotates hands the
13335
+ * sibling's current plaintext to the consumer's `provider.create` /
13336
+ * `update`. Stated rather than closed, and pinned by the unit file:
13337
+ * nothing records a public resolution, so this arm cannot tell "absent
13338
+ * because public" from "absent because collapsed"; the PR that closes
13339
+ * #2745's nested-stack site weighs these consumers rather than re-deriving
13340
+ * them.
13341
+ * 3. The write stays within the value scan's class of answer, or — below the
13342
+ * scan's floor — the bag carries the engine's same-generation mark:
13343
+ * {@link writeFramedTokenWithinScanBound}, shared with the literal arm.
13344
+ *
13345
+ * One shape reaches this arm that a reader may not expect, the literal arm's
13346
+ * own: a WHOLE-token source, i.e. an EMPTY frame, where the leaf IS the
13347
+ * middle. {@link positionByIntrinsicSkeleton} runs first and answers for
13348
+ * every such leaf it can; what falls through is its REFUSAL, and this arm
13349
+ * re-asks over a wider candidate set — the pair table names a token this pass
13350
+ * resolved but never pinned (an `ssm` reference whose type came back
13351
+ * unclassifiable, #1901) and which lost the map slot to a sibling, a token
13352
+ * neither of the skeleton's stores can see. With the pair and the bound that
13353
+ * leaf is written back as ITSELF, where the scan wrote the survivor: the
13354
+ * leaf's own expression, never another's. Where the skeleton refused for two
13355
+ * matches, so does this arm (same pattern, a superset of candidates, same
13356
+ * rule); where it refused for want of one — nothing matched, or its only
13357
+ * match was skipped as another plaintext's — this arm may find the leaf's
13358
+ * own token in the pair table. Either way the whole-token answer changes ONLY
13359
+ * where the skeleton had none.
13360
+ *
13361
+ * Every refusal before the bound returns `undefined` and the walk falls to the
13362
+ * value scan; the bound's own refusals return that scan directly — the same
13363
+ * answer either way, today's behavior, so no REFUSAL makes a case worse. The
13364
+ * one ACCEPTANCE that can is check 2's residual above, where a public leaf
13365
+ * gains a wrong reference in place of its plaintext. What stays: the
13366
+ * nonliteral frame above; a source with more than one token; an `Fn::Join`
13367
+ * whose delimiter is itself an intrinsic (the parser refuses it); and
13368
+ * everything the shared bound refuses, listed on the literal arm.
13369
+ */
13370
+ function positionByIntrinsicFrame(bag, source, secrets, secretExpressions, bagIsSameGeneration) {
13371
+ const segments = intrinsicSkeletonSegments(source);
13372
+ if (segments === void 0) return void 0;
13373
+ if (segments.some((s) => s !== UNKNOWN_PART && s.includes(UNKNOWN_PART_PLACEHOLDER))) return;
13374
+ const frame = singleSpanFrame(bag, segments.map((s) => s === UNKNOWN_PART ? UNKNOWN_PART_PLACEHOLDER : s).join(""));
13375
+ if (frame === void 0) return void 0;
13376
+ const { token, prefix, suffix, middle } = frame;
13377
+ if (prefix.includes(UNKNOWN_PART_PLACEHOLDER) || suffix.includes(UNKNOWN_PART_PLACEHOLDER)) return;
13378
+ const pattern = anchoredSkeletonPattern(token.split(UNKNOWN_PART_PLACEHOLDER).flatMap((literal, i) => i === 0 ? [literal] : [UNKNOWN_PART, literal]));
13379
+ if (pattern === void 0) return void 0;
13380
+ const plaintextOf = plaintextIndexOf(secrets);
13381
+ let matched;
13382
+ for (const candidate of /* @__PURE__ */ new Set([
13383
+ ...secretExpressions,
13384
+ ...recordedSecretExpressions$1,
13385
+ ...resolvedExpressionsOf(secrets)
13386
+ ])) {
13387
+ if (candidate.length > MAX_SKELETON_CANDIDATE_LENGTH) return void 0;
13388
+ if (!pattern.test(candidate)) continue;
13389
+ const recordedPlaintext = plaintextOf.get(candidate);
13390
+ if (recordedPlaintext !== void 0 && recordedPlaintext !== middle) continue;
13391
+ if (matched !== void 0) return void 0;
13392
+ matched = candidate;
13393
+ }
13394
+ if (matched === void 0 || resolvedPlaintextOf(secrets, matched) !== middle) return;
13395
+ return writeFramedTokenWithinScanBound(bag, secrets, {
13396
+ token: matched,
13397
+ prefix,
13398
+ suffix,
13399
+ middle
13400
+ }, bagIsSameGeneration);
13401
+ }
13402
+ /**
13175
13403
  * Keys tried, in order, when pairing two arrays whose ORDER cannot be trusted
13176
13404
  * (issue #1915).
13177
13405
  *
@@ -13337,7 +13565,7 @@ function positionListByCrossStackSource(bag, source, secrets) {
13337
13565
  * fetch and no value matching.
13338
13566
  *
13339
13567
  * A source leaf that is an intrinsic OBJECT has no string to copy, so it goes
13340
- * through two positioning passes before the value scan, in this order:
13568
+ * through three positioning passes before the value scan, in this order:
13341
13569
  *
13342
13570
  * - {@link positionByCrossStackSource} (issue #2059), for the two CROSS-STACK
13343
13571
  * spellings `Fn::ImportValue` / `Fn::GetStackOutput`. Those carry no text
@@ -13348,6 +13576,11 @@ function positionListByCrossStackSource(bag, source, secrets) {
13348
13576
  * `Fn::Sub`: when the intrinsic's literal parts describe exactly one of the
13349
13577
  * recorded secret expressions, THAT is persisted. This is the dominant CDK
13350
13578
  * shape — an L2 secret token renders the ARN as a `Ref`, hence a join.
13579
+ * - {@link positionByIntrinsicFrame} (issue #2745), for the same two spellings
13580
+ * when their literal parts FRAME one token (`port:` +
13581
+ * `secretValueFromJson(...)`): the one candidate this pass resolved to the
13582
+ * framed middle is written into the frame, under the literal span arm's
13583
+ * bound and mark.
13351
13584
  *
13352
13585
  * An ARRAY leaf beside such an intrinsic OBJECT — the shape a
13353
13586
  * `CommaDelimitedList` nested-stack parameter produces once the child has
@@ -13357,9 +13590,10 @@ function positionListByCrossStackSource(bag, source, secrets) {
13357
13590
  * is aligned against a source array that does not exist; see that function for
13358
13591
  * the two shapes it refuses.
13359
13592
  *
13360
- * The value scan is still applied wherever none can answer: a leaf that merely
13361
- * EMBEDS a secret inside surrounding text, an intrinsic whose skeleton matches
13362
- * zero or several candidates, a cross-stack leaf whose identity is not
13593
+ * The value scan is still applied wherever none can answer: an embedding leaf
13594
+ * the span arms refuse (a nonliteral frame, a middle no pair of this pass
13595
+ * vouches for), an intrinsic whose skeleton matches several candidates (or
13596
+ * none, the pair table included), a cross-stack leaf whose identity is not
13363
13597
  * literally computable, a diverged shape, a key the source lacks. So the passes
13364
13598
  * are complementary rather than alternatives — path where position is knowable,
13365
13599
  * association where the position is a cross-stack read, skeleton where it is a
@@ -13378,6 +13612,8 @@ function redactByPath(bag, source, secrets, rules, secretExpressions, bagIsSameG
13378
13612
  if (certified !== void 0) return certified;
13379
13613
  const positioned = positionByIntrinsicSkeleton(bag, source, secrets, secretExpressions);
13380
13614
  if (positioned !== void 0) return positioned;
13615
+ const framed = positionByIntrinsicFrame(bag, source, secrets, secretExpressions, bagIsSameGeneration);
13616
+ if (framed !== void 0) return framed;
13381
13617
  }
13382
13618
  if (Array.isArray(bag) && isPlainObject$2(source)) {
13383
13619
  const positionedList = positionListByCrossStackSource(bag, source, secrets);
@@ -20276,11 +20512,15 @@ const drainDeadlines = new AsyncLocalStorage();
20276
20512
  *
20277
20513
  * THE TRADE, stated because it is real: iterations that actually WAIT spend
20278
20514
  * the shared budget, so a later one can find it exhausted and its drain get
20279
- * no grace. That is the same exposure issue
20515
+ * no grace. That is the exposure issue
20280
20516
  * [#2814](https://github.com/go-to-k/cdkd/issues/2814) records for nesting,
20281
- * now reachable across a loop as well -- and on the `Export.Name` leg the
20282
- * cost is a DROPPED write rather than a late one, since that block's
20283
- * `nameSecrets` is a per-iteration local. What bounds it is that only real
20517
+ * reachable across a loop as well: a part such a drain stops waiting for
20518
+ * records LATE. Late, not lost -- the engine's `Export.Name` block writes each
20519
+ * recording through to the pass map (it used to copy a per-iteration local,
20520
+ * which DROPPED a late one), and the outputs readers after the pass redact
20521
+ * against the pass map at the moment they read -- but a reader that already
20522
+ * took its copy does not see it, and the resolver warns when a drain stops
20523
+ * waiting. What bounds it is that only real
20284
20524
  * waiting spends the budget: reaching zero takes a full cap of drain wait
20285
20525
  * inside one pass, not merely a slow pass. It is taken deliberately -- an
20286
20526
  * unbounded hold on a deploy's state save costs a first deploy every
@@ -21116,9 +21356,12 @@ const NAMEABLE_INTRINSIC_KEYS = /* @__PURE__ */ new Set([
21116
21356
  * now throw on an intrinsic — `route53-provider.ts` (`HostedZoneConfig`),
21117
21357
  * `cloudfront-oai-provider.ts` (`CloudFrontOriginAccessIdentityConfig`),
21118
21358
  * `kinesis-provider.ts` (`StreamModeDetails`), `ecs-provider.ts`
21119
- * (`DeploymentController`), `secretsmanager-secret-provider.ts`
21120
- * (`GenerateSecretString`, via `generateSecretString`) and `codebuild-provider.ts`
21121
- * (reached from `update()` through `mapProperties`). Not a count, for the same
21359
+ * (`DeploymentController`) and `codebuild-provider.ts` (reached from `update()`
21360
+ * through `mapProperties`). `secretsmanager-secret-provider.ts` was on this
21361
+ * list until issue #3048 moved its update path to a SKIP (the container is
21362
+ * refused by `requireConfigObject` before `generateSecretString` reads it;
21363
+ * `create()` still throws, and so does the MEMBER-level `ExcludeCharacters`
21364
+ * read inside a well-formed container -- issue #3056). Not a count, for the same
21122
21365
  * reason as the near-copy list below: two successive reviews each found
21123
21366
  * another. Re-derive with
21124
21367
  * `grep -rn 'readConfigString(' src/provisioning/providers/`.
@@ -22776,6 +23019,13 @@ const CAP_EXPIRED = Symbol("drain-cap-expired");
22776
23019
  */
22777
23020
  const concurrentDrainCap = {};
22778
23021
  /**
23022
+ * The budgets whose drain has already reported abandoned inputs (issue
23023
+ * [#2814](https://github.com/go-to-k/cdkd/issues/2814)). A `WeakSet` so a
23024
+ * budget's entry dies with the budget, as the budget itself does with the
23025
+ * async context that opened it.
23026
+ */
23027
+ const abandonReported = /* @__PURE__ */ new WeakSet();
23028
+ /**
22779
23029
  * `Promise.all`'s RESULT and its choice of error, with `Promise.allSettled`'s
22780
23030
  * TIMING: every promise started here has settled before this returns, and
22781
23031
  * before it throws unless the cap below expires first (issue
@@ -22853,8 +23103,25 @@ const concurrentDrainCap = {};
22853
23103
  * require the "healthy work slower than 60 s" shape the sizing paragraph
22854
23104
  * describes; that shape is the cheapest way to reach it with no nesting and
22855
23105
  * no wrapped loop, not the floor.
22856
- * Residual: issue
22857
- * [#2814](https://github.com/go-to-k/cdkd/issues/2814).
23106
+ *
23107
+ * WHAT HAPPENS TO A PART THE CAP STOPS WAITING FOR (issue
23108
+ * [#2814](https://github.com/go-to-k/cdkd/issues/2814)). It keeps running, and
23109
+ * its recording still lands in the context's map whenever it arrives -- the
23110
+ * cap costs it ORDER, not the write. So the answer sits with the READERS: one
23111
+ * that runs after the recording arrives must see it, which `DeployEngine`'s
23112
+ * outputs pass arranges for its `Export.Name` block (a map that writes each
23113
+ * recording through to the pass map at once, where a local copied in a
23114
+ * `finally` used to drop it) and for the persisted outputs, the exports index
23115
+ * and the deploy summary (each redacting against the pass map as it stands
23116
+ * at the moment it is written). A reader that took its
23117
+ * copy BEFORE the recording arrived -- a failure message already printed, a
23118
+ * state save already sent -- cannot be helped without waiting, and the wait
23119
+ * is what the cap bounds. Cancelling the
23120
+ * part would not close that either: a cancelled part never records, so its
23121
+ * plaintext would be missing for EVERY reader rather than for the early ones.
23122
+ * What this helper adds is the report: releasing a rejection with inputs
23123
+ * still running calls `onAbandoned` with how many, at most once per budget,
23124
+ * and the resolver turns that into a warning.
22858
23125
  *
22859
23126
  * ONE REMAINING CONSEQUENCE, deliberate and pinned by a case rather than only
22860
23127
  * described: the earliest-in-time rule holds PER INVOCATION, not across
@@ -22872,11 +23139,16 @@ const concurrentDrainCap = {};
22872
23139
  * failure where the old race was arbitrary. Residual: issue
22873
23140
  * [#2805](https://github.com/go-to-k/cdkd/issues/2805).
22874
23141
  */
22875
- async function allSettledKeepingFirstRejection(promises) {
23142
+ async function allSettledKeepingFirstRejection(promises, onAbandoned) {
22876
23143
  let rejection;
22877
23144
  const shared = drainDeadlines.getStore();
22878
23145
  let armCap;
22879
- const guarded = promises.map((promise) => promise.catch((error) => {
23146
+ let settled = 0;
23147
+ const guarded = promises.map((promise) => promise.then((value) => {
23148
+ settled += 1;
23149
+ return value;
23150
+ }, (error) => {
23151
+ settled += 1;
22880
23152
  if (rejection === void 0) {
22881
23153
  rejection = { error };
22882
23154
  armCap?.();
@@ -22901,7 +23173,17 @@ async function allSettledKeepingFirstRejection(promises) {
22901
23173
  });
22902
23174
  try {
22903
23175
  const outcome = await Promise.race([Promise.all(guarded), capped]);
22904
- if (rejection !== void 0) throw rejection.error;
23176
+ if (rejection !== void 0) {
23177
+ const pending = promises.length - settled;
23178
+ if (pending > 0) {
23179
+ if (shared === void 0) onAbandoned(pending);
23180
+ else if (!abandonReported.has(shared)) {
23181
+ abandonReported.add(shared);
23182
+ onAbandoned(pending);
23183
+ }
23184
+ }
23185
+ throw rejection.error;
23186
+ }
22905
23187
  if (outcome === CAP_EXPIRED) throw markNonRetryable(/* @__PURE__ */ new Error("drain cap expired with no rejection recorded"));
22906
23188
  return outcome;
22907
23189
  } finally {
@@ -23869,6 +24151,17 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
23869
24151
  return conditions;
23870
24152
  }
23871
24153
  /**
24154
+ * Warn that a drain stopped waiting for `pending` inputs of a failed
24155
+ * resolution (issue [#2814](https://github.com/go-to-k/cdkd/issues/2814)).
24156
+ * The helper calls this at most once per budget. The text carries a count
24157
+ * and the cap, nothing the template or AWS supplied, so it needs no masking.
24158
+ */
24159
+ warnAbandonedParts(pending) {
24160
+ const capSeconds = (concurrentDrainCap.ms ?? DRAIN_AFTER_REJECTION_MS) / 1e3;
24161
+ const parts = pending === 1 ? "1 concurrent part was" : `${pending} concurrent parts were`;
24162
+ this.logger.warn(`A resolution failed while ${parts} still running, and cdkd stopped waiting because the wait after a failure (capped at ${capSeconds}s) was used up. A secret such a part resolves from now on is still recorded, but only what runs after it arrives can mask it: output already printed or saved is not revisited.`);
24163
+ }
24164
+ /**
23872
24165
  * Recursively resolve a value
23873
24166
  */
23874
24167
  async resolveValue(value, context) {
@@ -23876,7 +24169,7 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
23876
24169
  if (typeof value === "string" && value.includes("{{resolve:")) return await this.resolveDynamicReferences(value, context);
23877
24170
  return value;
23878
24171
  }
23879
- if (Array.isArray(value)) return (await allSettledKeepingFirstRejection(value.map((v) => this.resolveValue(v, context)))).filter((v) => v !== AWS_NO_VALUE);
24172
+ if (Array.isArray(value)) return (await allSettledKeepingFirstRejection(value.map((v) => this.resolveValue(v, context)), (pending) => this.warnAbandonedParts(pending))).filter((v) => v !== AWS_NO_VALUE);
23880
24173
  const obj = value;
23881
24174
  if ("Ref" in obj) return await this.resolveRef(obj["Ref"], context);
23882
24175
  if ("Fn::GetAtt" in obj) return await this.resolveGetAtt(obj["Fn::GetAtt"], context);
@@ -24783,7 +25076,7 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
24783
25076
  let result = (await allSettledKeepingFirstRejection(values.map(async (v) => {
24784
25077
  const resolved = await this.resolveValue(v, context);
24785
25078
  return String(resolved);
24786
- }))).join(delimiter);
25079
+ }), (pending) => this.warnAbandonedParts(pending))).join(delimiter);
24787
25080
  if (result.includes("{{resolve:")) result = await this.resolveDynamicReferences(result, context);
24788
25081
  this.logger.debug(`Resolved Fn::Join: ${this.maskSecretsForLog(result, context)}`);
24789
25082
  return result;
@@ -27748,7 +28041,7 @@ var CloudControlProvider = class {
27748
28041
  const indeterminateGuard = await this.confirmDeleteTargetIdentity(logicalId, resourceType, physicalId, context);
27749
28042
  if (context?.removeProtection === true && resourceType === "AWS::AutoScaling::AutoScalingGroup") {
27750
28043
  this.logger.debug(`Delegating protected AutoScalingGroup ${logicalId} delete to the SDK ASGProvider (Cloud Control cannot force-delete a protected ASG)`);
27751
- const { ASGProvider } = await import("./asg-provider-C5FQJwUN.js").then((n) => n.n);
28044
+ const { ASGProvider } = await import("./asg-provider-CpxZ9sVH.js").then((n) => n.n);
27752
28045
  const asgProvider = new ASGProvider();
27753
28046
  return withIndeterminateGuard(await asgProvider.delete(logicalId, physicalId, resourceType, _properties, context), indeterminateGuard);
27754
28047
  }
@@ -37059,6 +37352,47 @@ var DeploymentEventsReader = class {
37059
37352
  */
37060
37353
  const EMPTY_SECRETS = /* @__PURE__ */ new Map();
37061
37354
  /**
37355
+ * A secrets map that keeps its OWN entries and also writes each one through
37356
+ * to `target` the moment it is recorded (issue
37357
+ * [#2814](https://github.com/go-to-k/cdkd/issues/2814)).
37358
+ *
37359
+ * The outputs pass resolves each intrinsic `Export.Name` through one of these.
37360
+ * Its own entries are what `exportNameSecretExposure` reads as "substituted
37361
+ * into THIS name", which must stay exact -- a view of the whole pass map
37362
+ * (`cdkd scrub`'s `SharedEntriesSecrets`) would report every secret the pass
37363
+ * has recorded as one the name holds, and refuse every alias. The write-through
37364
+ * is what a copy could not give: a part the drain cap stopped waiting for
37365
+ * records AFTER the name's block has ended, and when this was a local copied
37366
+ * into the pass map in a `finally`, such a record reached nothing.
37367
+ *
37368
+ * Only `set` writes through: it is the one operation the resolver performs on
37369
+ * a recording map (grepped -- no resolver path calls `delete` or `clear` on
37370
+ * one). `cdkd scrub`'s `SharedEntriesSecrets` overrides those too, and the
37371
+ * asymmetry is deliberate: that one is a VIEW holding no entries of its own,
37372
+ * while a `delete` here would leave the pass map carrying an entry this map
37373
+ * dropped -- one needle too many, which over-redacts rather than leaks.
37374
+ * The ENTRIES only, never the resolved pairs beside them
37375
+ * (issue #2485): those are keyed by map INSTANCE (`recordResolvedPair`), so
37376
+ * they stay on this one. A name never positions a leaf, and a value re-using
37377
+ * the same token records its own pair at the seam, so carrying them could add
37378
+ * nothing -- what it COULD do is mark a pair conflicting (an `ssm` reference
37379
+ * whose `Type` came back unclassifiable is never cached, so a name resolving
37380
+ * it re-asks AWS and can see a value that moved since the value pass) and
37381
+ * destroy the positioning the value pass had earned.
37382
+ */
37383
+ var ForwardingSecrets = class extends Map {
37384
+ target;
37385
+ constructor(target) {
37386
+ super();
37387
+ this.target = target;
37388
+ }
37389
+ set(plaintext, expression) {
37390
+ super.set(plaintext, expression);
37391
+ this.target?.set(plaintext, expression);
37392
+ return this;
37393
+ }
37394
+ };
37395
+ /**
37062
37396
  * Default per-resource warn threshold: warn the user when a single
37063
37397
  * resource has been in flight for 5 minutes. Most CC API resources
37064
37398
  * complete in under a minute; 5m is the agreed elbow.
@@ -37333,6 +37667,16 @@ var DeployEngine = class DeployEngine {
37333
37667
  */
37334
37668
  outputSecrets = /* @__PURE__ */ new Map();
37335
37669
  /**
37670
+ * The outputs pass's own recording map(s), one per `resolveOutputs` call
37671
+ * this deploy, so every redaction of the outputs bag re-reads them rather
37672
+ * than trusting the copy taken when the pass ended (issue
37673
+ * [#2814](https://github.com/go-to-k/cdkd/issues/2814)): a part the drain
37674
+ * cap stopped waiting for records into its pass map LATE, and the final
37675
+ * save, the exports index and the deploy summary all run after that copy.
37676
+ * Reset per `deploy()`.
37677
+ */
37678
+ outputsPassSecretMaps = [];
37679
+ /**
37336
37680
  * UNRESOLVED template `Outputs` values, keyed by output name (issue #1910) —
37337
37681
  * the outputs' POSITION source, the sibling of `perResourceTemplateProps` for
37338
37682
  * the bag `resolveOutputs` produces. Without it two outputs resolving one
@@ -37461,6 +37805,7 @@ var DeployEngine = class DeployEngine {
37461
37805
  this.perResourceResolvedType = /* @__PURE__ */ new Map();
37462
37806
  this.attemptedResolvedProps = /* @__PURE__ */ new Map();
37463
37807
  this.outputSecrets = /* @__PURE__ */ new Map();
37808
+ this.outputsPassSecretMaps = [];
37464
37809
  this.outputsTemplateSource = Object.create(null);
37465
37810
  this.outputsSourceUsable = true;
37466
37811
  this.skippedOutputs = void 0;
@@ -37532,12 +37877,16 @@ var DeployEngine = class DeployEngine {
37532
37877
  * Redact the resolved stack OUTPUTS bag, positioned by the unresolved
37533
37878
  * template `Outputs` values (issue #1910).
37534
37879
  *
37535
- * A single entry point because THREE call sites redact this same bag — the
37536
- * state-persist choke point, the no-change re-check, and the post-deploy
37537
- * publish that feeds the exports index / deploy summary — and before this
37538
- * they each spelled the value-only redaction separately. Two outputs
37539
- * resolving one secret collapsed onto whichever expression was recorded last
37540
- * at all three.
37880
+ * A single entry point because SEVEN call sites redact this same bag — the
37881
+ * state-persist choke point, the no-change re-check, that path's exports
37882
+ * index and deploy summary, the changes path's index and summary, and the
37883
+ * outputs pass itself. THREE of those are the ones issue #1910 unified —
37884
+ * the persist choke point, the re-check, and the outputs pass, which the
37885
+ * list above ends on rather than opens with: before it they each spelled
37886
+ * the value-only redaction separately, and two outputs resolving one secret
37887
+ * collapsed onto whichever expression was recorded last at all three. The
37888
+ * other FOUR are issue #2814's, both paths' index and summary: each read
37889
+ * the bag unredacted until then, and now redacts at the moment it reads.
37541
37890
  */
37542
37891
  /**
37543
37892
  * Record the plaintext behind every output {@link redactOutputs} just masked,
@@ -37550,9 +37899,18 @@ var DeployEngine = class DeployEngine {
37550
37899
  * value read back out of a previous run's state — is skipped, because there
37551
37900
  * is no plaintext behind it to remember.
37552
37901
  *
37553
- * Called only from the REAL-DEPLOY outputs pass. The other two `redactOutputs`
37554
- * callers hand it a bag from a previous generation, where a mask is already
37555
- * unrecoverable and pretending otherwise would serve a stale value.
37902
+ * Called only from the REAL-DEPLOY outputs pass. Of the six other
37903
+ * `redactOutputs` callers, three CAN hand it a bag from a previous
37904
+ * generation — the persist walk always may, and the no-change path's
37905
+ * exports index and summary do on the arms where that path keeps the
37906
+ * previous bag — and there a mask is already unrecoverable, so pretending
37907
+ * otherwise would
37908
+ * serve a stale value. The other three are not this pass either: the
37909
+ * no-change path performs the FIRST redaction of its own resolution there,
37910
+ * and the changes path's index and summary re-redact the bag this pass
37911
+ * already produced. (The "other two" this replaces counted the base's three
37912
+ * callers correctly; what was wrong was calling both of the others
37913
+ * previous-generation, when only the persist walk was one.)
37556
37914
  */
37557
37915
  rememberRecoverableMaskedOutputs(stackName, resolved, redacted) {
37558
37916
  if (resolved === redacted) return;
@@ -37563,7 +37921,51 @@ var DeployEngine = class DeployEngine {
37563
37921
  recordRecoverableMaskedOutput(stackName, this.stackRegion, key, plaintext);
37564
37922
  }
37565
37923
  }
37924
+ /**
37925
+ * Fold every outputs-pass recording map into `outputSecrets` — the entries,
37926
+ * and the uncollapsed evidence beside them (issue #2485: without it a
37927
+ * literal Output embedding one of two same-plaintext references would lose
37928
+ * its span positioning and persist the sibling's expression). Only what the
37929
+ * outputs pass itself resolved, so the outputs redaction (GHSA fix) uses
37930
+ * only outputs-substituted references: a literal output equal to a secret
37931
+ * nothing resolved is not recorded, and is not touched.
37932
+ *
37933
+ * Run by every {@link redactOutputs} (issue
37934
+ * [#2814](https://github.com/go-to-k/cdkd/issues/2814)), because a part the
37935
+ * drain cap stopped waiting for records into its pass map LATE: a secret
37936
+ * that arrives between the pass and the final save is then still a needle
37937
+ * for the save, the exports index and the deploy summary. Repeating it is
37938
+ * safe for the ENTRIES — re-setting one the bag holds changes nothing, and
37939
+ * `Map.set` on an existing key keeps its insertion order, so last-wins
37940
+ * cannot be reordered by a refold.
37941
+ *
37942
+ * The PAIRS are where repeating is not merely a no-op, stated because the
37943
+ * end-of-pass copy could not see it: a part the cap stopped waiting for can
37944
+ * record a pair LATE, and a second value for one expression marks it
37945
+ * `CONFLICTING_PLAINTEXT` (a non-cacheable `{{resolve:ssm:X}}` re-resolved
37946
+ * to a moved value). A later refold then carries that conflict into
37947
+ * `outputSecrets`, the leaf loses its positioning and falls to the value
37948
+ * scan, which can persist a sibling's expression (the issue #2485 class).
37949
+ * The plaintext stays MASKED either way, so the cost is which expression
37950
+ * is stored, not a disclosure — and the alternative, dropping the late
37951
+ * pair, would keep a positioning the pass itself no longer vouches for.
37952
+ */
37953
+ absorbOutputsPassSecrets() {
37954
+ for (const passSecrets of this.outputsPassSecretMaps) {
37955
+ for (const [value, expr] of passSecrets) this.outputSecrets.set(value, expr);
37956
+ mergeResolvedPairs(passSecrets, this.outputSecrets);
37957
+ }
37958
+ }
37959
+ /**
37960
+ * Redact the outputs bag. NOT a pure transform: it folds the outputs pass
37961
+ * map into `outputSecrets` first (issue #2814), so every call can grow that
37962
+ * bag. Its KEY set only grows, and the bag has no other reader, which is
37963
+ * what makes calling this on every save, index write and summary safe. The
37964
+ * resolved PAIRS beside those keys are not equally free to refold — see
37965
+ * {@link absorbOutputsPassSecrets}, which states what a late one costs.
37966
+ */
37566
37967
  redactOutputs(outputs) {
37968
+ this.absorbOutputsPassSecrets();
37567
37969
  if (this.outputSecrets.size === 0) return outputs;
37568
37970
  return redactSecretsForState(outputs, this.outputSecrets, this.outputsSourceUsable ? this.outputsTemplateSource : void 0, TEMPLATE_SOURCED_RULES);
37569
37971
  }
@@ -38196,7 +38598,10 @@ var DeployEngine = class DeployEngine {
38196
38598
  persistedOutputs = refreshedState.outputs;
38197
38599
  if (outputsChanged) this.logger.info("Persisted Outputs-only change (no resource diff).");
38198
38600
  else this.logger.debug("Persisted export-set change (no outputs-value diff, no-change path, #2193)");
38199
- if (this.exportIndexStore) await this.exportIndexStore.updateForStack(stackName, this.stackRegion, importableOutputs(refreshedState));
38601
+ if (this.exportIndexStore) await this.exportIndexStore.updateForStack(stackName, this.stackRegion, importableOutputs({
38602
+ ...refreshedState,
38603
+ outputs: this.redactOutputs(refreshedState.outputs)
38604
+ }));
38200
38605
  } else if (observedRefresh) this.logger.debug("Persisted refreshed observedProperties (no-change path)");
38201
38606
  else this.logger.debug("Persisted skipped-outputs record (no outputs-value diff, no-change path, #2740)");
38202
38607
  } catch (saveError) {
@@ -38213,7 +38618,7 @@ var DeployEngine = class DeployEngine {
38213
38618
  updatePartial: 0,
38214
38619
  unchanged: Object.keys(currentState.resources).length,
38215
38620
  durationMs: Date.now() - startTime,
38216
- outputs: this.buildDisplayOutputs(template, persistedOutputs),
38621
+ outputs: this.buildDisplayOutputs(template, this.redactOutputs(persistedOutputs)),
38217
38622
  attributeFallbackCount: this.resolver.getPhysicalIdFallbackCount()
38218
38623
  };
38219
38624
  }
@@ -38244,7 +38649,10 @@ var DeployEngine = class DeployEngine {
38244
38649
  await this.drainObservedCaptures(newState.resources);
38245
38650
  const newEtag = await this.stateBackend.saveState(stackName, this.stackRegion, this.withParentInfo(newState));
38246
38651
  this.logger.debug(`State saved (ETag: ${newEtag})`);
38247
- await Promise.all([this.deleteRollbackJournalBestEffort(stackName), this.exportIndexStore ? this.exportIndexStore.updateForStack(stackName, this.stackRegion, importableOutputs(newState)) : Promise.resolve()]);
38652
+ await Promise.all([this.deleteRollbackJournalBestEffort(stackName), this.exportIndexStore ? this.exportIndexStore.updateForStack(stackName, this.stackRegion, importableOutputs({
38653
+ ...newState,
38654
+ outputs: this.redactOutputs(newState.outputs)
38655
+ })) : Promise.resolve()]);
38248
38656
  const durationMs = Date.now() - startTime;
38249
38657
  const unchangedCount = this.diffCalculator.filterByType(changes, "NO_CHANGE").length + actualCounts.skipped;
38250
38658
  return {
@@ -38256,7 +38664,7 @@ var DeployEngine = class DeployEngine {
38256
38664
  updatePartial: actualCounts.updatePartial,
38257
38665
  unchanged: unchangedCount,
38258
38666
  durationMs,
38259
- outputs: this.buildDisplayOutputs(template, newState.outputs ?? {}),
38667
+ outputs: this.buildDisplayOutputs(template, this.redactOutputs(newState.outputs ?? {})),
38260
38668
  attributeFallbackCount: this.resolver.getPhysicalIdFallbackCount()
38261
38669
  };
38262
38670
  } finally {
@@ -39868,8 +40276,10 @@ var DeployEngine = class DeployEngine {
39868
40276
  * defense in depth here — but two masking sites in one flow must not argue
39869
40277
  * opposite sides of the same question. `secrets` is the outputs pass's own
39870
40278
  * map: everything recorded before this handler runs, an `Export.Name`
39871
- * resolution's entries included (its `finally` merges them back before the
39872
- * `catch` reaches here). Since issue #2563 a still-pending concurrent part
40279
+ * resolution's entries included (its map writes each one through to the
40280
+ * pass map as the resolver records it, so they are here before the `catch`
40281
+ * is; issue #2814 replaced the `finally` that copied them at the end of
40282
+ * the block). Since issue #2563 a still-pending concurrent part
39873
40283
  * is in the PASS bag before this handler runs: the resolver drains every
39874
40284
  * part it started before a rejection reaches a caller. Not
39875
40285
  * unconditionally, and the weaker claim is the true one -- the drain is
@@ -40004,6 +40414,7 @@ var DeployEngine = class DeployEngine {
40004
40414
  };
40005
40415
  const outputsPassSecrets = context.recordedSecretValues ?? EMPTY_SECRETS;
40006
40416
  const outputsPassInherited = context.inheritedSecrets ?? EMPTY_SECRETS;
40417
+ if (context.recordedSecretValues) this.outputsPassSecretMaps.push(context.recordedSecretValues);
40007
40418
  const publishedOutputNames = collectPublishedOutputNames(template.Outputs, conditions);
40008
40419
  let outputsPassCompleted = false;
40009
40420
  try {
@@ -40029,20 +40440,16 @@ var DeployEngine = class DeployEngine {
40029
40440
  if (!output.Export?.Name) continue;
40030
40441
  const value = outputs[outputKey];
40031
40442
  if (value === void 0) continue;
40032
- const nameSecrets = /* @__PURE__ */ new Map();
40443
+ const nameSecrets = new ForwardingSecrets(context.recordedSecretValues);
40033
40444
  const nameReads = [];
40034
40445
  let exportName;
40035
40446
  try {
40036
- try {
40037
- exportName = typeof output.Export.Name === "string" ? output.Export.Name : await this.resolver.resolve(output.Export.Name, {
40038
- ...context,
40039
- recordedSecretValues: nameSecrets,
40040
- redactedAttributeReads: nameReads
40041
- });
40042
- refuseMaskedOutputReads(outputKey, nameReads);
40043
- } finally {
40044
- for (const [plaintext, expression] of nameSecrets) context.recordedSecretValues?.set(plaintext, expression);
40045
- }
40447
+ exportName = typeof output.Export.Name === "string" ? output.Export.Name : await this.resolver.resolve(output.Export.Name, {
40448
+ ...context,
40449
+ recordedSecretValues: nameSecrets,
40450
+ redactedAttributeReads: nameReads
40451
+ });
40452
+ refuseMaskedOutputReads(outputKey, nameReads);
40046
40453
  } catch (error) {
40047
40454
  this.handleOutputResolutionFailure(error, outputKey, outputs, outputsPassSecrets, outputsPassInherited);
40048
40455
  continue;
@@ -40059,10 +40466,6 @@ var DeployEngine = class DeployEngine {
40059
40466
  }
40060
40467
  outputsPassCompleted = true;
40061
40468
  } finally {
40062
- if (context.recordedSecretValues) {
40063
- for (const [value, expr] of context.recordedSecretValues) this.outputSecrets.set(value, expr);
40064
- mergeResolvedPairs(context.recordedSecretValues, this.outputSecrets);
40065
- }
40066
40469
  if (!outputsPassCompleted) this.outputsSourceUsable = false;
40067
40470
  }
40068
40471
  for (const [outputKey, output] of Object.entries(template.Outputs)) {
@@ -40084,5 +40487,5 @@ var DeployEngine = class DeployEngine {
40084
40487
  };
40085
40488
 
40086
40489
  //#endregion
40087
- export { collectInlinePolicyNamesManagedBySiblings as $, describeDockerCapturedOutput as $n, LocalInvokeBuildError as $r, DagBuilder as $t, isStatefulRecreateTargetForReplace as A, orphansAfterRollback as An, uploadCfnTemplate as Ar, coerceCfnBoolean as At, gray as B, AssetModeResolver as Bn, resolveBucketRegion as Br, withSharedDrainBudget as Bt, refusesFinalSnapshot as C, forceQuitRecoveryClause as Cn, resolveUseCdkBootstrapAssets as Cr, isUnboundTemplateParameter as Ct, makeSiblingClaimReader as D, exportNamesCarriedFrom as Dn, CFN_TEMPLATE_URL_LIMIT as Dr, normalizeAwsTagsToCfn as Dt, extractDeploymentEventError as E, DEFAULT_STATE_PREFIX as En, CFN_TEMPLATE_BODY_LIMIT as Er, WAFv2WebACLProvider as Et, isWarmThroughputDecrease as F, WorkGraph as Fn, canonicalizeRegion as Fr, requireConfigArray as Ft, collectPublishedOutputNames as G, isCrossRegionRedirect as Gn, AssetError as Gr, s3BucketWebsiteUrl as Gt, red as H, assertAssetBucketRegion as Hn, getAwsClients as Hr, s3BucketDomainName as Ht, toFiniteNumber as I, buildAssetRedirectMap as In, derivePartitionAndUrlSuffix as Ir, requireConfigObject as It, secretBearing as J, validateAssetBucketName as Jn, CrossAccountSecretRefusalError as Jr, INTRINSIC_KEYS as Jt, exportAliasCollisionScrubWarning as K, parseBootstrapMarker as Kn, CdkdError as Kr, applyRoleArnIfSet as Kt, formatResourceLine as L, createAssetRedirectResolver as Ln, AssemblyReader as Lr, requireConfigString as Lt, renderStatefulReason as M, shouldRetainResource as Mn, displaySafe as Mr, configStringRefusal as Mt, WARM_THROUGHPUT_MEMBERS as N, AssetPublisher as Nn, expectedOwnerParam as Nr, readConfigString as Nt, planOrphanAdoption as O, importableOutputKeys as On, MIGRATE_TMP_PREFIX as Or, resolveExplicitPhysicalId as Ot, coerceWarmThroughput as P, stringifyValue as Pn, PARTITION_TABLE as Pr, replayWarn as Pt, IAMRoleProvider as Q, buildDockerImage as Qn, IntrinsicResolutionRefusalError as Qr, withRetry as Qt, bold as R, loadPublishableAssetManifest as Rn, processStackMessages as Rr, classifyReplaySecretRegion as Rt, isFinalSnapshotError as S, __exportAll as Si, buildLockContentionMessage as Sn, resolveStateBucketWithDefaultAndSource as Sr, getAccountInfo as St, makeCanonicalizePropertiesFn as T, CUSTOM_RESOURCE_RESPONSE_PREFIX as Tn, warnDeprecatedNoPrefixCliFlag as Tr, refStateLookupFromResource as Tt, yellow as U, ensureAssetStorage as Un, resetAwsClients as Ur, s3BucketDualStackDomainName as Ut, green as V, BOOTSTRAP_MARKER_PREFIX as Vn, AwsClients as Vr, s3BucketArn as Vt, collectDeclaredOutputNames as W, getBootstrapMarkerKey as Wn, setAwsClients as Wr, s3BucketRegionalDomainName as Wt, secretSafeKeyDisplay as X, buildDenyExternalAccessPolicy as Xn, DeployCancelledError as Xr, findSilentDropProperties as Xt, secretBearingStateKeyWarning as Y, validateContainerRepoName as Yn, DependencyError as Yr, findActionableSilentDrops as Yt, getCurrentResourceSecrets as Z, describeAwsFailure as Zn, DynamicReferenceRegionAmbiguousError as Zr, describeTypeWithThrottleRetry as Zt, ATOMIC_FINAL_SNAPSHOT_TYPES as _, isThrottlingError as _i, LockManager as _n, resolveApp as _r, isTerminationProtectionPropagationError as _t, DeploymentEventsStore as a, ResourceTimeoutError as ai, carriesSecretMask as an, partitionSensitiveEnv as ar, maskerOrIdentity as at, ccRoutedFinalSnapshotError as b, markRedactedCause as bi, UNRENDERABLE as bn, resolveSkipPrefix as br, cfnRefValueFromPhysicalId as bt, replayFailedOperations as c, StackTerminationProtectionError as ci, errorCauseChain as cn, runDockerStreaming as cr, interruptWatchListenerCount as ct, updatePartialReason as d, formatError as di, maskSecretsInError as dn, AssetManifestLoader as dr, CloudControlProvider as dt, LocalStartServiceError as ei, TemplateParser as en, describeDockerExecFailure as er, clearOnUpdateRemoval as et, withResourceDeadline as f, isCdkdError as fi, maskSecretsInText as fn, getDockerImageBySourceHash as fr, slowCcOperationTimeoutMs as ft, bindingSkippedOutputs as g, isRetryableTransientError as gi, scrubResourceRecord as gn, getLegacyStateBucketName as gr, disableInstanceApiTermination as gt, computeImplicitDeleteEdges as h, isMarkedNonRetryable as hi, redactSecretsForState as hn, getDefaultStateBucketName as hr, deleteSkipReason as ht, DeploymentEventsReader as i, ProvisioningError as ii, TEMPLATE_SOURCED_RULES as in, getDockerCmd as ir, maskDeep as it, isStatefulRecreateTargetSync as j, orphansCarriedFrom as jn, CUSTOM_RESOURCE_RESPONSE_OBJECT_DESCRIPTION as jr, configBooleanRefusal as jt, MULTI_REGION_RECREATE_BLOCKED_TYPES as k, importableOutputs as kn, findLargeInlineResources as kr, assertRegionMatch as kt, replayRollback as l, StateError as li, identityKeyFor as ln, escapeRegExp$1 as lr, isInterruptedWaitError as lt, IMPLICIT_DELETE_DEPENDENCIES as m, withErrorHandling as mi, recoverMaskedOutput as mn, synthesisStatusMessage as mr, deleteIndeterminateGuards as mt, DEFAULT_RESOURCE_WARN_AFTER_MS as n, NestedStackChildDirectDestroyError as ni, STATE_SOURCED_CROSS_GENERATION_RULES as nn, dockerSpawnEnvWithSensitive as nr, wouldReturnToSdkProvider as nt, planFailedOps as o, ResourceUpdateNotSupportedError as oi, createSecretMasker as on, redactDockerArgvValues as or, beginCommandInterruptScope as ot, maskingRetryLogger as p, normalizeAwsError as pi, recordMaskOnlyValue as pn, Synthesizer as pr, UNSPECIFIED_SKIP_REASON as pt, isExportAliasCollision as q, readBootstrapMarkerBody as qn, ConfigError as qr, DiffCalculator as qt, DeployEngine as r, PartialFailureError as ri, STATE_SOURCED_READBACK_RULES as rn, formatDockerLoginError as rr, createMaskedRetryLogger as rt, planRollback as s, StackHasActiveImportsError as si, dynamicReferenceTokens as sn, runDockerForeground as sr, endCommandInterruptScope as st, DEFAULT_RESOURCE_TIMEOUT_MS as t, LockError as ti, STATE_SOURCED_BASELINE_RULES as tn, describeDockerFailure as tr, ProviderRegistry as tt, updatePartialMessage as u, SynthesisError as ui, isSingleDynamicReferenceToken as un, stripControlChars as ur, startInterruptWatch as ut, PRE_DELETE_SNAPSHOT_TYPES as v, isTransientServerError as vi, S3StateBackend as vn, resolveAutoAssetStorage as vr, IntrinsicFunctionResolver as vt, unsupportedFinalSnapshotError as w, shellQuote as wn, stateBucketExistenceConfirmed as wr, parameterTypeMayLoseSecretIdentity as wt, createPreDeleteFinalSnapshot as x, retryClassificationText as xi, buildForceUnlockCommand as xn, resolveStateBucketWithDefault as xr, coerceParameterTypedValue as xt, buildFinalSnapshotIdentifier as y, markNonRetryable as yi, rebuildClientForBucketRegion as yn, resolveCaptureObservedState as yr, carriesDynamicReference as yt, cyan as z, rewriteTemplateAssetReferences as zn, clearBucketRegionCache as zr, producerRegionsFromState as zt };
40088
- //# sourceMappingURL=deploy-engine-ClLLdy68.js.map
40490
+ export { collectInlinePolicyNamesManagedBySiblings as $, buildDockerImage as $n, LocalInvokeBuildError as $r, DagBuilder as $t, isStatefulRecreateTargetForReplace as A, importableOutputs as An, findLargeInlineResources as Ar, coerceCfnBoolean as At, gray as B, rewriteTemplateAssetReferences as Bn, resolveBucketRegion as Br, withSharedDrainBudget as Bt, refusesFinalSnapshot as C, buildLockContentionMessage as Cn, resolveStateBucketWithDefaultAndSource as Cr, isUnboundTemplateParameter as Ct, makeSiblingClaimReader as D, DEFAULT_STATE_PREFIX as Dn, CFN_TEMPLATE_BODY_LIMIT as Dr, normalizeAwsTagsToCfn as Dt, extractDeploymentEventError as E, CUSTOM_RESOURCE_RESPONSE_PREFIX as En, warnDeprecatedNoPrefixCliFlag as Er, WAFv2WebACLProvider as Et, isWarmThroughputDecrease as F, stringifyValue as Fn, canonicalizeRegion as Fr, requireConfigArray as Ft, collectPublishedOutputNames as G, getBootstrapMarkerKey as Gn, AssetError as Gr, s3BucketWebsiteUrl as Gt, red as H, BOOTSTRAP_MARKER_PREFIX as Hn, getAwsClients as Hr, s3BucketDomainName as Ht, toFiniteNumber as I, WorkGraph as In, derivePartitionAndUrlSuffix as Ir, requireConfigObject as It, secretBearing as J, readBootstrapMarkerBody as Jn, CrossAccountSecretRefusalError as Jr, INTRINSIC_KEYS as Jt, exportAliasCollisionScrubWarning as K, isCrossRegionRedirect as Kn, CdkdError as Kr, applyRoleArnIfSet as Kt, formatResourceLine as L, buildAssetRedirectMap as Ln, AssemblyReader as Lr, requireConfigString as Lt, renderStatefulReason as M, orphansCarriedFrom as Mn, CUSTOM_RESOURCE_RESPONSE_OBJECT_DESCRIPTION as Mr, configStringRefusal as Mt, WARM_THROUGHPUT_MEMBERS as N, shouldRetainResource as Nn, expectedOwnerParam as Nr, readConfigString as Nt, planOrphanAdoption as O, exportNamesCarriedFrom as On, CFN_TEMPLATE_URL_LIMIT as Or, resolveExplicitPhysicalId as Ot, coerceWarmThroughput as P, AssetPublisher as Pn, PARTITION_TABLE as Pr, replayWarn as Pt, IAMRoleProvider as Q, describeAwsFailure as Qn, IntrinsicResolutionRefusalError as Qr, withRetry as Qt, bold as R, createAssetRedirectResolver as Rn, processStackMessages as Rr, classifyReplaySecretRegion as Rt, isFinalSnapshotError as S, __exportAll as Si, buildForceUnlockCommand as Sn, resolveStateBucketWithDefault as Sr, getAccountInfo as St, makeCanonicalizePropertiesFn as T, shellQuote as Tn, stateBucketExistenceConfirmed as Tr, refStateLookupFromResource as Tt, yellow as U, assertAssetBucketRegion as Un, resetAwsClients as Ur, s3BucketDualStackDomainName as Ut, green as V, AssetModeResolver as Vn, AwsClients as Vr, s3BucketArn as Vt, collectDeclaredOutputNames as W, ensureAssetStorage as Wn, setAwsClients as Wr, s3BucketRegionalDomainName as Wt, secretSafeKeyDisplay as X, validateContainerRepoName as Xn, DeployCancelledError as Xr, findSilentDropProperties as Xt, secretBearingStateKeyWarning as Y, validateAssetBucketName as Yn, DependencyError as Yr, findActionableSilentDrops as Yt, getCurrentResourceSecrets as Z, buildDenyExternalAccessPolicy as Zn, DynamicReferenceRegionAmbiguousError as Zr, describeTypeWithThrottleRetry as Zt, ATOMIC_FINAL_SNAPSHOT_TYPES as _, isThrottlingError as _i, scrubResourceRecord as _n, getLegacyStateBucketName as _r, isTerminationProtectionPropagationError as _t, DeploymentEventsStore as a, ResourceTimeoutError as ai, carriesSecretMask as an, getDockerCmd as ar, maskerOrIdentity as at, ccRoutedFinalSnapshotError as b, markRedactedCause as bi, rebuildClientForBucketRegion as bn, resolveCaptureObservedState as br, cfnRefValueFromPhysicalId as bt, replayFailedOperations as c, StackTerminationProtectionError as ci, errorCauseChain as cn, runDockerForeground as cr, interruptWatchListenerCount as ct, updatePartialReason as d, formatError as di, markSameGenerationBag as dn, stripControlChars as dr, CloudControlProvider as dt, LocalStartServiceError as ei, TemplateParser as en, describeDockerCapturedOutput as er, clearOnUpdateRemoval as et, withResourceDeadline as f, isCdkdError as fi, maskSecretsInError as fn, AssetManifestLoader as fr, slowCcOperationTimeoutMs as ft, bindingSkippedOutputs as g, isRetryableTransientError as gi, redactSecretsForState as gn, getDefaultStateBucketName as gr, disableInstanceApiTermination as gt, computeImplicitDeleteEdges as h, isMarkedNonRetryable as hi, recoverMaskedOutput as hn, synthesisStatusMessage as hr, deleteSkipReason as ht, DeploymentEventsReader as i, ProvisioningError as ii, TEMPLATE_SOURCED_RULES as in, formatDockerLoginError as ir, maskDeep as it, isStatefulRecreateTargetSync as j, orphansAfterRollback as jn, uploadCfnTemplate as jr, configBooleanRefusal as jt, MULTI_REGION_RECREATE_BLOCKED_TYPES as k, importableOutputKeys as kn, MIGRATE_TMP_PREFIX as kr, assertRegionMatch as kt, replayRollback as l, StateError as li, identityKeyFor as ln, runDockerStreaming as lr, isInterruptedWaitError as lt, IMPLICIT_DELETE_DEPENDENCIES as m, withErrorHandling as mi, recordMaskOnlyValue as mn, Synthesizer as mr, deleteIndeterminateGuards as mt, DEFAULT_RESOURCE_WARN_AFTER_MS as n, NestedStackChildDirectDestroyError as ni, STATE_SOURCED_CROSS_GENERATION_RULES as nn, describeDockerFailure as nr, wouldReturnToSdkProvider as nt, planFailedOps as o, ResourceUpdateNotSupportedError as oi, createSecretMasker as on, partitionSensitiveEnv as or, beginCommandInterruptScope as ot, maskingRetryLogger as p, normalizeAwsError as pi, maskSecretsInText as pn, getDockerImageBySourceHash as pr, UNSPECIFIED_SKIP_REASON as pt, isExportAliasCollision as q, parseBootstrapMarker as qn, ConfigError as qr, DiffCalculator as qt, DeployEngine as r, PartialFailureError as ri, STATE_SOURCED_READBACK_RULES as rn, dockerSpawnEnvWithSensitive as rr, createMaskedRetryLogger as rt, planRollback as s, StackHasActiveImportsError as si, dynamicReferenceTokens as sn, redactDockerArgvValues as sr, endCommandInterruptScope as st, DEFAULT_RESOURCE_TIMEOUT_MS as t, LockError as ti, STATE_SOURCED_BASELINE_RULES as tn, describeDockerExecFailure as tr, ProviderRegistry as tt, updatePartialMessage as u, SynthesisError as ui, isSingleDynamicReferenceToken as un, escapeRegExp$1 as ur, startInterruptWatch as ut, PRE_DELETE_SNAPSHOT_TYPES as v, isTransientServerError as vi, LockManager as vn, resolveApp as vr, IntrinsicFunctionResolver as vt, unsupportedFinalSnapshotError as w, forceQuitRecoveryClause as wn, resolveUseCdkBootstrapAssets as wr, parameterTypeMayLoseSecretIdentity as wt, createPreDeleteFinalSnapshot as x, retryClassificationText as xi, UNRENDERABLE as xn, resolveSkipPrefix as xr, coerceParameterTypedValue as xt, buildFinalSnapshotIdentifier as y, markNonRetryable as yi, S3StateBackend as yn, resolveAutoAssetStorage as yr, carriesDynamicReference as yt, cyan as z, loadPublishableAssetManifest as zn, clearBucketRegionCache as zr, producerRegionsFromState as zt };
40491
+ //# sourceMappingURL=deploy-engine-BEpYvI14.js.map